diff --git a/README.md b/README.md index 8b43ea3..a174555 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,38 @@ - -# KEKS Pay for WooCommerce. - -KEKS Pay for WooCommerce offers you a simple and seamless way of taking KEKS Pay payments. Your customers will have the best time saving user experience in the checkout process - especially while buying on smartphones. It will increase your conversion-rate and the likeliness to return to your shop. On the other hand, via KEKS Pay you will be able to accept VISA, Mastercard, Maestro or Diners Club cards and payments directly from your customer's bank account. - -Currently KEKS Pay for WooCommerce is available for merchants only in CROATIA. - -## Why choose KEKS Pay? - -KEKS Pay has no setup fees, no monthly fees, no hidden costs - you only get charged when you take payments through KEKS Pay. Earnings are transferred to your bank account on a 1-day rolling basis. - -Cancel whenever you want without additional costs. - -## How to refund a KEKS Pay payment? - -To do a refund, select the order and click the Refund button afterward. You will be able to type in the amount you would refund. The order status will change to “Refunded” once the order has been successfully refunded. - -On a side note, this plugin is provided “as-is” and we don't currently provide support around installing and optimizing it for your needs. - -## Installation -#### MINIMUM REQUIREMENTS - -* WooCommerce 3.3 or greater. -* WordPress 5.0 or greater. -* PHP version 7.2 or greater. -* SSL must be installed on your site and active on your Checkout pages. - -#### INSTALL - -1. Contact ERSTE KEKS Pay team and set up your merchant account. -2. Visit Plugins > Add New. -3. Search for "KEKS Pay for WooCommerce". -4. Install and activate KEKS Pay for WooCommerce plugin. -5. Visit plugin settings and fill in merchant info you received from ERSTE KEKS Pay team. - -For more installation options check the [official WordPress documentation](https://wordpress.org/support/article/managing-plugins/#manual-plugin-installation) about installing plugins. \ No newline at end of file + +# KEKS Pay for WooCommerce. + +KEKS Pay for WooCommerce offers you a simple and seamless way of taking KEKS Pay payments. Your customers will have the best time saving user experience in the checkout process - especially while buying on smartphones. It will increase your conversion-rate and the likeliness to return to your shop. On the other hand, via KEKS Pay you will be able to accept VISA, Mastercard, Maestro or Diners Club cards and payments directly from your customer's bank account. + +Currently KEKS Pay for WooCommerce is available for merchants only in CROATIA. + +## Why choose KEKS Pay? + +KEKS Pay has no setup fees, no monthly fees, no hidden costs - you only get charged when you take payments through KEKS Pay. Earnings are transferred to your bank account on a 1-day rolling basis. + +Cancel whenever you want without additional costs. + +## How to refund a KEKS Pay payment? + +To do a refund, select the order and click the Refund button afterward. You will be able to type in the amount you would refund. The order status will change to “Refunded” once the order has been successfully refunded. + +On a side note, this plugin is provided “as-is” and we don't currently provide support around installing and optimizing it for your needs. + +## Installation +#### MINIMUM REQUIREMENTS + +* WooCommerce 8.2 or greater. +* WordPress 6.3 or greater. +* PHP version 7.4 or greater. +* SSL must be installed on your site and active on your Checkout pages. + +#### INSTALL + +1. Contact ERSTE KEKS Pay team and set up your merchant account. +2. Visit Plugins > Add New. +3. Search for "KEKS Pay for WooCommerce". +4. Install and activate KEKS Pay for WooCommerce plugin. +5. Visit plugin settings and fill in merchant info you received from ERSTE KEKS Pay team. + +For more installation options check the [official WordPress documentation](https://wordpress.org/support/article/managing-plugins/#manual-plugin-installation) about installing plugins. + + diff --git a/composer.json b/composer.json index 98544bc..3202714 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "require": { - "php": "^7.2", - "chillerlan/php-qrcode": "^3.4" + "php": "^7.4 || ^8.0", + "chillerlan/php-qrcode": "^4.4" } } diff --git a/composer.lock b/composer.lock index 9301ab9..c036f9e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,34 +4,38 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e0028656b355389d2aff60386255d921", + "content-hash": "54b2a951672c3176ad97ff12e9896034", "packages": [ { "name": "chillerlan/php-qrcode", - "version": "3.4.0", + "version": "4.4.1", "source": { "type": "git", "url": "https://github.com/chillerlan/php-qrcode.git", - "reference": "d8bf297e6843a53aeaa8f3285ce04fc349d133d6" + "reference": "f5e243f3b61a60934780579430a951460f40888d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/d8bf297e6843a53aeaa8f3285ce04fc349d133d6", - "reference": "d8bf297e6843a53aeaa8f3285ce04fc349d133d6", + "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/f5e243f3b61a60934780579430a951460f40888d", + "reference": "f5e243f3b61a60934780579430a951460f40888d", "shasum": "" }, "require": { - "chillerlan/php-settings-container": "^1.2", + "chillerlan/php-settings-container": "^2.1.4 || ^3.1", "ext-mbstring": "*", - "php": "^7.2" + "php": "^7.4 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^8.5", - "setasign/fpdf": "^1.8.2" + "phan/phan": "^5.4", + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^9.6", + "setasign/fpdf": "^1.8.2", + "squizlabs/php_codesniffer": "^3.8" }, "suggest": { "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", - "setasign/fpdf": "Required to use the QR FPDF output." + "setasign/fpdf": "Required to use the QR FPDF output.", + "simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code" }, "type": "library", "autoload": { @@ -58,7 +62,7 @@ "homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors" } ], - "description": "A QR code generator. PHP 7.2+", + "description": "A QR code generator with a user friendly API. PHP 7.4+", "homepage": "https://github.com/chillerlan/php-qrcode", "keywords": [ "phpqrcode", @@ -67,28 +71,45 @@ "qrcode", "qrcode-generator" ], - "time": "2020-11-18T20:51:41+00:00" + "support": { + "issues": "https://github.com/chillerlan/php-qrcode/issues", + "source": "https://github.com/chillerlan/php-qrcode/tree/4.4.1" + }, + "funding": [ + { + "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", + "type": "custom" + }, + { + "url": "https://ko-fi.com/codemasher", + "type": "ko_fi" + } + ], + "time": "2024-01-06T16:56:58+00:00" }, { "name": "chillerlan/php-settings-container", - "version": "1.2.1", + "version": "2.1.5", "source": { "type": "git", "url": "https://github.com/chillerlan/php-settings-container.git", - "reference": "b9b0431dffd74102ee92348a63b4c33fc8ba639b" + "reference": "f705310389264c3578fdd9ffb15aa2cd6d91772e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/b9b0431dffd74102ee92348a63b4c33fc8ba639b", - "reference": "b9b0431dffd74102ee92348a63b4c33fc8ba639b", + "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/f705310389264c3578fdd9ffb15aa2cd6d91772e", + "reference": "f705310389264c3578fdd9ffb15aa2cd6d91772e", "shasum": "" }, "require": { "ext-json": "*", - "php": "^7.2" + "php": "^7.4 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^8.3" + "phan/phan": "^5.4", + "phpcsstandards/php_codesniffer": "^3.8", + "phpmd/phpmd": "^2.13", + "phpunit/phpunit": "^9.6" }, "type": "library", "autoload": { @@ -107,15 +128,30 @@ "homepage": "https://github.com/codemasher" } ], - "description": "A container class for immutable settings objects. Not a DI container. PHP 7.2+", + "description": "A container class for immutable settings objects. Not a DI container. PHP 7.4+", "homepage": "https://github.com/chillerlan/php-settings-container", "keywords": [ "PHP7", "Settings", + "configuration", "container", "helper" ], - "time": "2019-09-10T00:09:44+00:00" + "support": { + "issues": "https://github.com/chillerlan/php-settings-container/issues", + "source": "https://github.com/chillerlan/php-settings-container" + }, + "funding": [ + { + "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", + "type": "custom" + }, + { + "url": "https://ko-fi.com/codemasher", + "type": "ko_fi" + } + ], + "time": "2024-01-05T23:20:55+00:00" } ], "packages-dev": [], @@ -125,7 +161,8 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.2" + "php": "^7.4 || ^8.0" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "2.0.0" } diff --git a/includes/core/class-kekspay-connector.php b/includes/core/class-kekspay-connector.php index 9b47543..5959795 100644 --- a/includes/core/class-kekspay-connector.php +++ b/includes/core/class-kekspay-connector.php @@ -1,134 +1,128 @@ array( - 'Content-Type' => 'application/json', - ), - 'method' => 'POST', - 'timeout' => 55, - 'body' => $encoded_body, - 'cookies' => [], - ); - } - - /** - * Trigger refund for given order and amount. - * - * @param WC_Order $order - * @param float $amount - * - * @return array [ 'success' => bool, 'message' => string ] - */ - public function refund( $order, $amount ) { - if ( 'erste-kekspay-woocommerce' !== $order->get_payment_method() ) { - return; - } - - $currency = $order->get_currency(); - - $refund_amount = $amount; - $refund_currency = $currency; - - if ( 'HRK' === $currency ) { - $refund_amount = round( $amount / 7.5345, 2 ); - $refund_currency = 'EUR'; - } - - $timestamp = time(); - - $hash = Kekspay_Data::get_hash( $order, $refund_amount, $timestamp ); - if ( ! $hash ) { - return false; - } - - $body = array( - 'bill_id' => Kekspay_Data::get_bill_id_by_order_id( $order->get_id() ), - 'tid' => Kekspay_Data::get_settings( 'webshop-tid', true ), - 'cid' => Kekspay_Data::get_settings( 'webshop-cid', true ), - 'amount' => $refund_amount, - 'epochtime' => $timestamp, - 'hash' => $hash, - 'algo' => Kekspay_Data::get_algo(), - 'currency' => $refund_currency, - ); - - $wc_price = wc_price( $amount, array( 'currency' => $currency ) ); - - $response = wp_safe_remote_post( Kekspay_Data::get_kekspay_api_base() . 'keksrefund', $this->get_default_args( $body ) ); - Kekspay_Logger::log( 'Request sent to refund order ' . $order->get_id() . ' (' . $amount . $order->get_currency() . ') via KEKS Pay.', 'info' ); - - if ( is_wp_error( $response ) ) { - Kekspay_Logger::log( $response->get_error_message(), 'error' ); - return false; - } - - $status_code = wp_remote_retrieve_response_code( $response ); - if ( $status_code < 200 || $status_code > 299 ) { - Kekspay_Logger::log( 'Refund for order ' . $order->get_id() . ' (' . $amount . $order->get_currency() . ') via KEKS Pay failed, does not have a success status code.', 'error' ); - return false; - } - - $refund = wp_remote_retrieve_body( $response ); - if ( ! $refund ) { - Kekspay_Logger::log( 'Refund for order ' . $order->get_id() . ' (' . $amount . $order->get_currency() . ') via KEKS Pay failed, body corrupted or missing.', 'error' ); - return false; - } - - // Log response from refund request. - Kekspay_Logger::log( $refund, 'info' ); - - $response_data = json_decode( $refund ); - - if ( isset( $response_data->status ) && 0 === $response_data->status ) { - $note = sprintf( __( 'Uspješno izvršen povrat %s via KEKS Pay.', 'kekspay' ), $wc_price ); - Kekspay_Logger::log( 'Successfully refunded order ' . $order->get_id() . ' (' . $amount . $order->get_currency() . ') via KEKS Pay. Setting status refunded.', 'info' ); - $order->add_order_note( $note ); - $order->update_meta_data( 'kekspay_status', (int) $order->get_remaining_refund_amount() ? 'refunded_partially' : 'refunded' ); - $order->save(); - - return true; - } else { - $note = sprintf( __( 'Dogodila se greška pri povratu %s via KEKS Pay.', 'kekspay' ), $wc_price ); - $message = isset( $response_data->message ) ? $response_data->message : ''; - Kekspay_Logger::log( 'Failed to refund order ' . $order->get_id() . ' (' . $amount . $order->get_currency() . ') via KEKS Pay. Message: ' . $message, 'error' ); - $order->add_order_note( $note ); - $order->save(); - - return false; - } - } - } +/** + * Kekspay_Connector class + * + * @since 0.1 + */ +class Kekspay_Connector { + + /** + * Return an array for default args or false if failed to JSON encode. + * + * @param array $body + * @return array|false + */ + private static function get_default_args( $body ) { + $encoded_body = wp_json_encode( $body ); + + if ( ! $encoded_body ) { + return false; + } + + // Log body data for refund request. + Kekspay_Logger::log( $encoded_body, 'info' ); + + return [ + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'method' => 'POST', + 'timeout' => 55, + 'body' => $encoded_body, + 'cookies' => [], + ]; + } + + /** + * Trigger refund for given order and amount. + * + * @param WC_Order $order + * @param float $amount + * + * @return array [ 'success' => bool, 'message' => string ] + */ + public static function refund( $order, $amount ) { + if ( 'erste-kekspay-woocommerce' !== $order->get_payment_method() ) { + return; + } + + $currency = $order->get_currency(); + + $refund_amount = $amount; + $refund_currency = $currency; + + if ( 'HRK' === $currency ) { + $refund_amount = round( $amount / 7.5345, 2 ); + $refund_currency = 'EUR'; + } + + $timestamp = time(); + + $hash = Kekspay_Data::get_hash( $order, $refund_amount, $timestamp ); + if ( ! $hash ) { + return false; + } + + $body = [ + 'bill_id' => Kekspay_Data::get_bill_id_by_order_id( $order->get_id() ), + 'tid' => Kekspay_Data::get_settings( 'webshop-tid', true ), + 'cid' => Kekspay_Data::get_settings( 'webshop-cid', true ), + 'amount' => $refund_amount, + 'epochtime' => $timestamp, + 'hash' => $hash, + 'algo' => Kekspay_Data::get_algo(), + 'currency' => $refund_currency, + ]; + + $wc_price = wc_price( $amount, [ 'currency' => $currency ] ); + + $response = wp_safe_remote_post( Kekspay_Data::get_kekspay_api_base() . 'keksrefund', self::get_default_args( $body ) ); + Kekspay_Logger::log( 'Request sent to refund order ' . $order->get_id() . ' (' . $amount . $order->get_currency() . ') via KEKS Pay.', 'info' ); + + if ( is_wp_error( $response ) ) { + Kekspay_Logger::log( $response->get_error_message(), 'error' ); + return false; + } + + $status_code = wp_remote_retrieve_response_code( $response ); + if ( $status_code < 200 || $status_code > 299 ) { + Kekspay_Logger::log( 'Refund for order ' . $order->get_id() . ' (' . $amount . $order->get_currency() . ') via KEKS Pay failed, does not have a success status code.', 'error' ); + return false; + } + + $refund = wp_remote_retrieve_body( $response ); + if ( ! $refund ) { + Kekspay_Logger::log( 'Refund for order ' . $order->get_id() . ' (' . $amount . $order->get_currency() . ') via KEKS Pay failed, body corrupted or missing.', 'error' ); + return false; + } + + // Log response from refund request. + Kekspay_Logger::log( $refund, 'info' ); + + $response_data = json_decode( $refund ); + + if ( isset( $response_data->status ) && 0 === $response_data->status ) { + /* translators: successful refund */ + $note = sprintf( __( 'Uspješno izvršen povrat %s via KEKS Pay.', 'kekspay' ), $wc_price ); + Kekspay_Logger::log( 'Successfully refunded order ' . $order->get_id() . ' (' . $amount . $order->get_currency() . ') via KEKS Pay. Setting status refunded.', 'info' ); + $order->add_order_note( $note ); + $order->update_meta_data( 'kekspay_status', (int) $order->get_remaining_refund_amount() ? 'refunded_partially' : 'refunded' ); + $order->save(); + + return true; + } else { + /* translators: failed refund */ + $note = sprintf( __( 'Dogodila se greška pri povratu %s via KEKS Pay.', 'kekspay' ), $wc_price ); + $message = isset( $response_data->message ) ? $response_data->message : ''; + Kekspay_Logger::log( 'Failed to refund order ' . $order->get_id() . ' (' . $amount . $order->get_currency() . ') via KEKS Pay. Message: ' . $message, 'error' ); + $order->add_order_note( $note ); + $order->save(); + + return false; + } + } } diff --git a/includes/core/class-kekspay-ipn.php b/includes/core/class-kekspay-ipn.php index ff5afdc..2286b6b 100644 --- a/includes/core/class-kekspay-ipn.php +++ b/includes/core/class-kekspay-ipn.php @@ -1,204 +1,205 @@ -1, - 'message' => $message, - ) - ); - - if ( ! $encoded_message ) { - Kekspay_Logger::log( 'Failed to encode API response message.', 'error' ); - $encoded_message = -1; - } - - wp_die( $encoded_message ); - } - - /** - * Return assoc array of parameters from either 'php://input' (POST request body), or $_REQUEST. - * - * @return array $params - */ - private function resolve_params() { - $params = json_decode( file_get_contents( 'php://input' ), true ); - if ( empty( $params ) ) { - // NOTE: external request, signature is checked in the calling method to determine validity. - // phpcs:disable WordPress.Security.NonceVerification.Recommended - $params = $_REQUEST; - // phpcs:enable - } - - if ( ! $params ) { - return false; - } - - return array_map( - function( $item ) { - return filter_var( $item, FILTER_SANITIZE_STRING ); - }, - $params - ); - } - - /** - * Check if order status has changed and return the new status - * - * @return void - */ - public function kekspay_status_check() { - check_ajax_referer( 'kekspay_advice_status' ); - - $order = new WC_Order( filter_input( INPUT_POST, 'order_id', FILTER_SANITIZE_NUMBER_INT ) ); - $status = $order->get_meta( 'kekspay_status' ); - - $response = array( - 'status' => $status, - 'redirect' => null, - ); - - if ( 'failed' === $status ) { - wc_add_notice( sprintf( __( 'Nešto je pošlo po zlu pri pokušaju naplate vaše narudžbe (#%d) putem KEKS Pay servisa, molimo pokušajte ponoviti narudžbu ili kontaktirajte administratora web trgovine za više informacija.' ), $order->get_id() ), 'error' ); - $response['redirect'] = $order->get_cancel_order_url_raw(); - } elseif ( 'success' === $status ) { - $response['redirect'] = $order->get_checkout_order_received_url(); - } - - $this->respond( $response ); - } - - /** - * Should be used as a callback URL for KEKS Pay API checkout request. - */ - public function do_checkout_status() { - // Verify token authorization. - if ( ! $this->verify_kekspay_token() ) { - Kekspay_Logger::log( 'Failed to verify token.', 'error' ); - $this->respond_error( 'Webshop authentication failed, token mismatch.' ); - } - - $params = $this->resolve_params(); - Kekspay_Logger::log( wp_json_encode( $params ), 'info' ); - // Check if any parametars are received. - if ( ! $params ) { - Kekspay_Logger::log( 'Missing parameters in the request for IPN.', 'error' ); - $this->respond_error( 'Missing parameters.' ); - } - - // Check if required params are recieved. - foreach ( array( 'bill_id', 'status', 'keks_id', 'tid' ) as $required_param ) { - if ( ! isset( $params[ $required_param ] ) ) { - Kekspay_Logger::log( 'Missing ' . $required_param . ' parametar in the request for IPN.', 'error' ); - $this->respond_error( 'Missing or corrupt required parametars.' ); - } - } - - // Check if recieved TID matches the webshop TID. - if ( $params['tid'] !== Kekspay_Data::get_settings( 'webshop-tid', true ) ) { - Kekspay_Logger::log( 'Recieved TID ' . $params['tid'] . ' does not match webshop TID in the request for IPN.', 'error' ); - $this->respond_error( 'Webshop verification failed, mismatch for TID ' . $params['tid'] . '.' ); - } - - // Extract order id and check if order exists. - $order_id = Kekspay_Data::get_order_id_by_bill_id( $params['bill_id'] ); - $order = wc_get_order( $order_id ); - if ( ! $order ) { - Kekspay_Logger::log( 'Failed to find order ' . $params['bill_id'] . ' from the request for IPN.', 'error' ); - $this->respond_error( 'Couldn\'t find corresponding order ' . $params['bill_id'] . '.' ); - } - - if ( (int) $params['status'] === 0 ) { - Kekspay_Logger::log( 'KEKS Pay successfully completed payment for order ' . $order_id . ', setting status to ' . $params['message'], 'info' ); - $order->set_status( Kekspay_Data::get_settings( 'payed-order-status' ) ?: 'processing', __( 'Narudžba uspješno plaćena putem KEKS Pay aplikacije.', 'kekspay' ) ); //phpcs:ignore - $order->add_meta_data( 'kekspay_status', 'success', true ); - $order->add_meta_data( 'kekspay_id', $params['keks_id'], true ); - $order->save(); - } else { - Kekspay_Logger::log( 'KEKS Pay failed to complete payment for order ' . $order_id . ', message: ' . $params['message'], 'error' ); - $order->add_meta_data( 'kekspay_status', 'failed', true ); - $order->add_order_note( __( 'Dogodila se greška pri naplati putem KEKS Pay aplikacije.', 'kekspay' ) ); - $order->save(); - } - - $this->respond( - array( - 'status' => 0, - 'message' => 'Accepted', - ) - ); - } - - /** - * Compares tokens to verify valid request. - * - * @return bool - */ - private function verify_kekspay_token() { - $token_src = 'REQUEST'; - $token = isset( $_REQUEST['token'] ) ? filter_var( wp_unslash( $_REQUEST['token'] ), FILTER_SANITIZE_STRING ) : false; - if ( ! $token ) { // Legacy check. - $token_src = 'REQUEST'; - $token = isset( $_SERVER['HTTP_AUTHORIZATION'] ) ? filter_var( $_SERVER['HTTP_AUTHORIZATION'], FILTER_SANITIZE_STRING ) : false; - } - if ( ! $token ) { - Kekspay_Logger::log( 'Failed to recieve authentication token.', 'error' ); - $this->respond_error( 'Authentication token missing, failed to verify.' ); - } else { - Kekspay_Logger::log( 'Token src: ' . $token_src, 'info' ); - } - - return hash_equals( Kekspay_Data::get_settings( 'auth-token' ), str_replace( 'Token ', '', $token ) ); - } - } + /** + * Kekspay_IPN class + * + * @since 0.1 + */ + class Kekspay_IPN { + + /** + * Class constructor. + */ + public function __construct() { + add_action( 'woocommerce_api_' . Kekspay_Data::get_wc_endpoint(), [ $this, 'do_checkout_status' ] ); + add_action( 'wp_ajax_kekspay_status_check', [ $this, 'kekspay_status_check' ] ); + add_action( 'wp_ajax_nopriv_kekspay_status_check', [ $this, 'kekspay_status_check' ] ); + } + + /** + * Die with given message, encoded as JSON, and set HTTP response status. + * + * @param mixed $message + * @param integer $status_code Defaults to 200. + */ + private function respond( $message, $status_code = 200 ) { + status_header( $status_code ); + header( 'content-type: application/json; charset=utf-8' ); + + $encoded_message = wp_json_encode( $message ); + + if ( ! $encoded_message ) { + Kekspay_Logger::log( 'Failed to encode API response message.', 'error' ); + $encoded_message = -1; + } + + die( $encoded_message ); //@codingStandardsIgnoreLine - escaping not needed + } + + /** + * Die with error and given message, encoded as JSON, and set HTTP response status. + * + * @param string $message Message description about the failure of request. + */ + private function respond_error( $message ) { + status_header( 400 ); + header( 'content-type: application/json; charset=utf-8' ); + + $encoded_message = wp_json_encode( + [ + 'status' => -1, + 'message' => $message, + ] + ); + + if ( ! $encoded_message ) { + Kekspay_Logger::log( 'Failed to encode API response message.', 'error' ); + $encoded_message = -1; + } + + die( $encoded_message ); //@codingStandardsIgnoreLine - escaping not needed + } + + /** + * Return assoc array of parameters from either 'php://input' (POST request body), or $_REQUEST. + * + * @return array $params + */ + private function resolve_params() { + $params = json_decode( file_get_contents( 'php://input' ), true ); + if ( empty( $params ) ) { + // NOTE: external request, signature is checked in the calling method to determine validity. + // phpcs:disable WordPress.Security.NonceVerification.Recommended + $params = $_REQUEST; + // phpcs:enable + } + + if ( ! $params ) { + return false; + } + + return array_map( + function( $item ) { + return filter_var( $item, FILTER_SANITIZE_FULL_SPECIAL_CHARS ); + }, + $params + ); + } + + /** + * Check if order status has changed and return the new status + * + * @return void + */ + public function kekspay_status_check() { + check_ajax_referer( 'kekspay_advice_status' ); + + $order = new WC_Order( filter_input( INPUT_POST, 'order_id', FILTER_SANITIZE_NUMBER_INT ) ); + $status = $order->get_meta( 'kekspay_status' ); + + $response = [ + 'status' => $status, + 'redirect' => null, + ]; + + if ( 'failed' === $status ) { + /* translators: payment failed on checkout QR scanning */ + wc_add_notice( sprintf( __( 'Nešto je pošlo po zlu pri pokušaju naplate vaše narudžbe (#%d) putem KEKS Pay servisa, molimo pokušajte ponoviti narudžbu ili kontaktirajte administratora web trgovine za više informacija.' ), $order->get_id() ), 'error' ); + $response['redirect'] = $order->get_cancel_order_url_raw(); + } elseif ( 'success' === $status ) { + $response['redirect'] = $order->get_checkout_order_received_url(); + } + + $this->respond( $response ); + } + + /** + * Should be used as a callback URL for KEKS Pay API checkout request. + */ + public function do_checkout_status() { + // Verify token authorization. + if ( ! $this->verify_kekspay_token() ) { + Kekspay_Logger::log( 'Failed to verify token.', 'error' ); + $this->respond_error( 'Webshop authentication failed, token mismatch.' ); + } + + $params = $this->resolve_params(); + Kekspay_Logger::log( wp_json_encode( $params ), 'info' ); + // Check if any parametars are received. + if ( ! $params ) { + Kekspay_Logger::log( 'Missing parameters in the request for IPN.', 'error' ); + $this->respond_error( 'Missing parameters.' ); + } + + // Check if required params are recieved. + foreach ( [ 'bill_id', 'status', 'keks_id', 'tid' ] as $required_param ) { + if ( ! isset( $params[ $required_param ] ) ) { + Kekspay_Logger::log( 'Missing ' . $required_param . ' parametar in the request for IPN.', 'error' ); + $this->respond_error( 'Missing or corrupt required parametars.' ); + } + } + + // Check if recieved TID matches the webshop TID. + if ( $params['tid'] !== Kekspay_Data::get_settings( 'webshop-tid', true ) ) { + Kekspay_Logger::log( 'Recieved TID ' . $params['tid'] . ' does not match webshop TID in the request for IPN.', 'error' ); + $this->respond_error( 'Webshop verification failed, mismatch for TID ' . $params['tid'] . '.' ); + } + + // Extract order id and check if order exists. + $order_id = Kekspay_Data::get_order_id_by_bill_id( $params['bill_id'] ); + $order = wc_get_order( $order_id ); + if ( ! $order ) { + Kekspay_Logger::log( 'Failed to find order ' . $params['bill_id'] . ' from the request for IPN.', 'error' ); + $this->respond_error( 'Couldn\'t find corresponding order ' . $params['bill_id'] . '.' ); + } + + if ( (int) $params['status'] === 0 ) { + Kekspay_Logger::log( 'KEKS Pay successfully completed payment for order ' . $order_id . ', setting status to ' . $params['message'], 'info' ); + $order->set_status( Kekspay_Data::get_settings( 'payed-order-status' ) ?: 'processing', __( 'Narudžba uspješno plaćena putem KEKS Pay aplikacije.', 'kekspay' ) ); + $order->add_meta_data( 'kekspay_status', 'success', true ); + $order->add_meta_data( 'kekspay_id', $params['keks_id'], true ); + $order->save(); + } else { + Kekspay_Logger::log( 'KEKS Pay failed to complete payment for order ' . $order_id . ', message: ' . $params['message'], 'error' ); + $order->add_meta_data( 'kekspay_status', 'failed', true ); + $order->add_order_note( __( 'Dogodila se greška pri naplati putem KEKS Pay aplikacije.', 'kekspay' ) ); + $order->save(); + } + + $this->respond( + [ + 'status' => 0, + 'message' => 'Accepted', + ] + ); + } + + /** + * Compares tokens to verify valid request. + * + * @return bool + */ + private function verify_kekspay_token() { + $token_src = 'REQUEST'; + $token = isset( $_REQUEST['token'] ) ? filter_var( wp_unslash( $_REQUEST['token'] ), FILTER_SANITIZE_FULL_SPECIAL_CHARS ) : false; //@codingStandardsIgnoreLine - safe use of REQUEST. + if ( ! $token ) { // Legacy check. + $token_src = 'SERVER'; + $token = isset( $_SERVER['HTTP_AUTHORIZATION'] ) ? filter_var( $_SERVER['HTTP_AUTHORIZATION'], FILTER_SANITIZE_FULL_SPECIAL_CHARS ) : false; //@codingStandardsIgnoreLine - safe use of SERVER. + } + if ( ! $token ) { + Kekspay_Logger::log( 'Failed to recieve authentication token.', 'error' ); + $this->respond_error( 'Authentication token missing, failed to verify.' ); + } else { + Kekspay_Logger::log( 'Token src: ' . $token_src, 'info' ); + } + + return hash_equals( Kekspay_Data::get_settings( 'auth-token' ), str_replace( 'Token ', '', $token ) ); + } + } } new Kekspay_IPN(); diff --git a/includes/core/class-kekspay-order-admin.php b/includes/core/class-kekspay-order-admin.php index dcac938..7cb9237 100644 --- a/includes/core/class-kekspay-order-admin.php +++ b/includes/core/class-kekspay-order-admin.php @@ -1,58 +1,58 @@ base && 'shop_order' === $screen->id ) { - $order = wc_get_order( get_the_ID() ); - if ( ! is_a( $order, 'WC_Order' ) ) { - return; - } - - if ( Kekspay_Data::order_test_mode( $order ) ) { - $class = 'notice notice-warning'; - $message = __( 'Narudžba kreirana koristeći KEKS Pay u testnom načinu rada.', 'kekspay' ); - - printf( '

%2$s

', esc_attr( $class ), esc_html( $message ) ); - } - - if ( 'HRK' === $order->get_currency() ) { - $class = 'notice notice-warning'; - $message = __( 'KEKS Pay - Narudžba naplaćena u valuti HRK koja više nije podržana od strane KEKS Pay sustava. Ako želite napraviti povrat novca kroz KEKS Pay sustav, iznos za povrat prije povrata preračunati će se u EUR prema tečaju 7.5345 te biti vraćen u valuti EUR.', 'kekspay' ); - - printf( '

%2$s

', esc_attr( $class ), esc_html( $message ) ); - } - } - - } - - } + /** + * Kekspay_Order_Admin class + * + * @since 0.1 + */ + class Kekspay_Order_Admin { + + /** + * Class constructor. + */ + public function __construct() { + add_action( 'admin_notices', [ $this, 'display_test_order_notice' ], 100 ); + } + + /** + * Display WordPress warning notice if current order (admin view) is processed in sandbox/test mode. + * + * @return void + */ + public function display_test_order_notice() { + if ( ! is_admin() ) { + return; + } + + $screen = get_current_screen(); + if ( 'post' === $screen->base && 'shop_order' === $screen->id ) { + $order = wc_get_order( get_the_ID() ); + if ( ! is_a( $order, 'WC_Order' ) ) { + return; + } + + if ( Kekspay_Data::order_test_mode( $order ) ) { + $class = 'notice notice-warning'; + $message = __( 'Narudžba kreirana koristeći KEKS Pay u testnom načinu rada.', 'kekspay' ); + + printf( '

%2$s

', esc_attr( $class ), esc_html( $message ) ); + } + + if ( 'HRK' === $order->get_currency() ) { + $class = 'notice notice-warning'; + $message = __( 'KEKS Pay - Narudžba naplaćena u valuti HRK koja više nije podržana od strane KEKS Pay sustava. Ako želite napraviti povrat novca kroz KEKS Pay sustav, iznos za povrat prije povrata preračunati će se u EUR prema tečaju 7.5345 te biti vraćen u valuti EUR.', 'kekspay' ); + + printf( '

%2$s

', esc_attr( $class ), esc_html( $message ) ); + } + } + + } + + } } new Kekspay_Order_Admin(); diff --git a/includes/core/class-kekspay-payment-gateway.php b/includes/core/class-kekspay-payment-gateway.php index 0201d18..6b3ab65 100644 --- a/includes/core/class-kekspay-payment-gateway.php +++ b/includes/core/class-kekspay-payment-gateway.php @@ -1,224 +1,221 @@ id = KEKSPAY_PLUGIN_ID; - $this->method_title = __( 'KEKS Pay', 'kekspay' ); - $this->method_description = __( 'Najbrže i bez naknada putem KEKS Pay aplikacije!', 'kekspay' ); - $this->has_fields = true; - - $this->init_form_fields(); - $this->init_settings(); - - $this->supports = array( 'products', 'refunds' ); - - $this->connector = new Kekspay_Connector(); - $this->sell = new Kekspay_Sell(); - - $this->title = esc_attr( Kekspay_Data::get_settings( 'title' ) ); - - $this->add_hooks(); - } - - /** - * Register different hooks. - */ - private function add_hooks() { - add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) ); - add_action( 'woocommerce_receipt_' . $this->id, array( $this, 'do_receipt_page' ) ); - } - - /** - * Check if we need to make gateways available. - * - * @override - */ - public function is_available() { - if ( ! Kekspay_Data::required_keys_set() || ! Kekspay_Data::currency_supported() ) { - return false; - } - - return parent::is_available(); - } - - /** - * Add kekspay payment method icon. - * - * @override - */ - public function get_icon() { - return apply_filters( 'woocommerce_gateway_icon', Kekspay_Data::get_svg( 'keks-logo', [ 'class="kekspay-logo"' ] ), $this->id ); - } - - /** - * Echoes gateway's options (Checkout tab under WooCommerce's settings). - * - * @override - */ - public function admin_options() { - ?> -

- - - generate_settings_html(); ?> -
- form_fields = include( KEKSPAY_DIR_PATH . '/includes/settings/kekspay-settings.php' ); - } - - /** - * Display description of the gateway on the checkout page. - * - * @override - */ - public function payment_fields() { - echo '

' . wptexturize( __( 'Najbrže i bez naknada putem KEKS Pay aplikacije!', 'kekspay' ) ) . '

'; - - if ( Kekspay_Data::test_mode() ) { - $test_mode_notice = apply_filters( - 'kekspay_payment_description_test_mode_notice', - '

' . __( 'KEKS Pay je trenutno u testom načinu rada, ne zaboravite ga ugasiti po završetku testiranja.', 'kekspay' ) . '

' - ); - - if ( ! empty( $test_mode_notice ) ) { - echo $test_mode_notice; - } - } - } - - /** - * Trigger actions for 'receipt' page. - * - * @param int $order_id - */ - public function do_receipt_page( $order_id ) { - $order = wc_get_order( $order_id ); - - if ( ! $order ) { - Kekspay_Logger::log( 'Failed to find order ' . $order_id . ' while trying to show receipt page.', 'warning' ); - return false; - } - - if ( ! $order->get_meta( 'kekspay_status' ) ) { - Kekspay_Logger::log( 'Order ' . $order_id . ' created for payment via KEKS Pay, status set to pending.', 'info' ); - $order->add_meta_data( 'kekspay_status', 'pending', true ); - $order->save(); - } - - // Add order meta and note to mark order as TEST if test mode is enabled or order already has not been maked as TEST. - if ( Kekspay_Data::test_mode() && ! Kekspay_Data::order_test_mode( $order ) ) { - $order->add_order_note( __( 'Narudžba napravljena u testnom načinu rada!', 'kekspay' ) ); - $order->add_meta_data( 'kekspay_test_mode', 'yes', true ); - $order->save(); - } - - do_action( 'kekspay_receipt_before_payment_data', $order, Kekspay_Data::get_settings() ); - - ?> - - -
-
-
- sell->display_sell_url( $order ); ?> -
-
-
-
-
    -
  1. -
  2. -
  3. -
  4. -
-
- sell->display_sell_qr( $order ); ?> -
- -
- - cart->empty_cart(); - - return array( - 'result' => 'success', - 'redirect' => $order->get_checkout_payment_url( true ), - ); - } - - /** - * Process refund via KEKS Pay. - * - * @override - * @param int $order_id - * @param float $amount Defaults to null. - * @param string $reason Defaults to empty string. - * - * @return bool True or false based on success, or a WP_Error object. - */ - public function process_refund( $order_id, $amount = null, $reason = '' ) { - $order = wc_get_order( $order_id ); - if ( ! $order ) { - Kekspay_Logger::log( 'Failed to find order ' . $order_id . ' while processing refund.', 'warning' ); - return false; - } - - return $this->connector->refund( $order, $amount ); - } - - } + /** + * Kekspay_Payment_Gateway class + */ + class Kekspay_Payment_Gateway extends WC_Payment_Gateway { + + /** + * App data handler. + * + * @var Kekspay_Sell + */ + private $sell; + + /** + * Class constructor with basic gateway's setup. + */ + public function __construct() { + require_once KEKSPAY_DIR_PATH . '/includes/core/class-kekspay-connector.php'; + require_once KEKSPAY_DIR_PATH . '/includes/core/class-kekspay-sell.php'; + + $this->id = KEKSPAY_PLUGIN_ID; + $this->method_title = __( 'KEKS Pay', 'kekspay' ); + $this->method_description = __( 'Najbrže i bez naknada putem KEKS Pay aplikacije!', 'kekspay' ); + $this->has_fields = true; + + $this->init_form_fields(); + $this->init_settings(); + + $this->supports = [ 'products', 'refunds' ]; + + $this->sell = new Kekspay_Sell(); + + $this->title = esc_attr( Kekspay_Data::get_settings( 'title' ) ); + + $this->add_hooks(); + } + + /** + * Register different hooks. + */ + private function add_hooks() { + add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, [ $this, 'process_admin_options' ] ); + add_action( 'woocommerce_receipt_' . $this->id, [ $this, 'do_receipt_page' ] ); + } + + /** + * Check if we need to make gateways available. + * + * @override + */ + public function is_available() { + if ( ! Kekspay_Data::required_keys_set() || ! Kekspay_Data::currency_supported() ) { + return false; + } + + return parent::is_available(); + } + + /** + * Add kekspay payment method icon. + * + * @override + */ + public function get_icon() { + return apply_filters( 'woocommerce_gateway_icon', Kekspay_Data::get_svg( 'keks-logo', [ 'class="kekspay-logo"' ] ), $this->id ); + } + + /** + * Echoes gateway's options (Checkout tab under WooCommerce's settings). + * + * @override + */ + public function admin_options() { + ?> +

+ + + generate_settings_html(); ?> +
+ form_fields = include KEKSPAY_DIR_PATH . '/includes/settings/kekspay-settings.php'; + } + + /** + * Display description of the gateway on the checkout page. + * + * @override + */ + public function payment_fields() { + echo wp_kses_post( '

' . __( 'Najbrže i bez naknada putem KEKS Pay aplikacije!', 'kekspay' ) . '

' ); + + if ( Kekspay_Data::test_mode() ) { + echo wp_kses_post( + apply_filters( + 'kekspay_payment_description_test_mode_notice', + '

' . __( 'KEKS Pay je trenutno u testom načinu rada, ne zaboravite ga ugasiti po završetku testiranja.', 'kekspay' ) . '

' + ) + ); + } + } + + /** + * Trigger actions for 'receipt' page. + * + * @param int $order_id + */ + public function do_receipt_page( $order_id ) { + $order = wc_get_order( $order_id ); + + if ( ! $order ) { + Kekspay_Logger::log( 'Failed to find order ' . $order_id . ' while trying to show receipt page.', 'warning' ); + return false; + } + + if ( ! $order->get_meta( 'kekspay_status' ) ) { + Kekspay_Logger::log( 'Order ' . $order_id . ' created for payment via KEKS Pay, status set to pending.', 'info' ); + $order->add_meta_data( 'kekspay_status', 'pending', true ); + $order->save(); + } + + // Add order meta and note to mark order as TEST if test mode is enabled or order already has not been maked as TEST. + if ( Kekspay_Data::test_mode() && ! Kekspay_Data::order_test_mode( $order ) ) { + $order->add_order_note( __( 'Narudžba napravljena u testnom načinu rada!', 'kekspay' ) ); + $order->add_meta_data( 'kekspay_test_mode', 'yes', true ); + $order->save(); + } + + do_action( 'kekspay_receipt_before_payment_data', $order, Kekspay_Data::get_settings() ); + ?> + + + +
+
+
+ sell->display_sell_url( $order ) ); ?> +
+
+
+
+
    +
  1. + +
  2. +
  3. +
  4. +
+
+ sell->display_sell_qr( $order ); //@codingStandardsIgnoreLine - safe output ?> +
+
+ + cart->empty_cart(); + + return [ + 'result' => 'success', + 'redirect' => $order->get_checkout_payment_url( true ), + ]; + } + + /** + * Process refund via KEKS Pay. + * + * @override + * @param int $order_id + * @param float $amount Defaults to null. + * @param string $reason Defaults to empty string. + * + * @return bool True or false based on success, or a WP_Error object. + */ + public function process_refund( $order_id, $amount = null, $reason = '' ) { + $order = wc_get_order( $order_id ); + if ( ! $order ) { + Kekspay_Logger::log( 'Failed to find order ' . $order_id . ' while processing refund.', 'warning' ); + return false; + } + + return Kekspay_Connector::refund( $order, $amount ); + } + + } } diff --git a/includes/core/class-kekspay-sell.php b/includes/core/class-kekspay-sell.php index 20dda83..505f3fd 100644 --- a/includes/core/class-kekspay-sell.php +++ b/includes/core/class-kekspay-sell.php @@ -1,114 +1,115 @@ 6, - 'quietzoneSize' => 4, - 'eccLevel' => QRCode::ECC_L, - 'imageTransparent' => false, - ) - ); - - $qrcode = new QRCode( $options ); - - return $qrcode->render( wp_json_encode( $data ) ); - } catch ( \Exception $e ) { - Kekspay_Logger::log( 'Failed to create QR Code. Exception message: ' . $e->getMessage(), 'error' ); - } - - return false; - } - - /** - * Format QR Code with html for display. - * - * @param object $order Order for which to fetch QR code. - * - * @return string Path to or base64 encoded QR code for mobile app wrapped in img tags. - */ - public function display_sell_qr( $order ) { - $qrcode = $this->get_sell_qr( $order ); - - if ( ! $qrcode ) { - return esc_html_e( 'Dogodila se greška prilikom kreiranja QR kȏda za ovu narudžbu. Molimo rekreirajte narudžbu ili kontaktirajte vlasnika web stranice.', 'kekspay' ); - } - - return apply_filters( 'kekspay_sell_qr_code', '', $qrcode ); - } - - /** - * Format Keks pay url with html for display - * - * @param object $order Order for which to get the pay url. - * - * @return string Link for payment. - */ - public function display_sell_url( $order ) { - $sell_url = $this->get_sell_url( $order ); - - if ( ! $sell_url ) { - return esc_html_e( 'Dogodila se greška prilikom kreiranja poveznice za plaćanje putem KEKS Pay mobilne aplikacije za ovu narudžbu. Molimo rekreirajte narudžbu ili kontaktirajte vlasnika web stranice."', 'kekspay' ); - } - - $attrs = apply_filters( - 'kekspay_sell_link_attributes', - array( - 'id' => 'kekspay-pay-url', - 'class' => 'button kekspay-sell-button', - 'target' => '_blank', - 'label' => __( 'Otvori KEKS Pay', 'kekspay' ), - ) - ); - - return apply_filters( 'kekspay_sell_link', '' . esc_html( $attrs['label'] ) . '' ); - } - - } + /** + * Kekspay_Sell class + * + * @since 0.1 + */ + class Kekspay_Sell { + + /** + * Class constructor. + */ + public function __construct() { + require_once KEKSPAY_DIR_PATH . 'vendor/autoload.php'; + } + + /** + * Create url for mobile app. + * + * @param object $order Order for which to create url. + * + * @return string Url for mobile app. + */ + public function get_sell_url( $order ) { + $sell = Kekspay_Data::get_sell_data( $order, true ); + + if ( ! $sell ) { + return false; + } + + return add_query_arg( $sell, Kekspay_Data::get_kekspay_pay_base() ); + } + + /** + * Create QR code for mobile app. + * + * @param object $order Order for which to create QR code. + * + * @return string base64 encoded png file. + */ + public function get_sell_qr( $order ) { + $data = Kekspay_Data::get_sell_data( $order ); + + try { + $options = new QROptions( + [ + 'version' => 6, + 'quietzoneSize' => 4, + 'eccLevel' => QRCode::ECC_L, + 'imageTransparent' => false, + ] + ); + + $qrcode = new QRCode( $options ); + + return $qrcode->render( wp_json_encode( $data ) ); + } catch ( \Exception $e ) { + Kekspay_Logger::log( 'Failed to create QR Code. Exception message: ' . $e->getMessage(), 'error' ); + } + + return false; + } + + /** + * Format QR Code with html for display. + * + * @param object $order Order for which to fetch QR code. + * + * @return string Path to or base64 encoded QR code for mobile app wrapped in img tags. + */ + public function display_sell_qr( $order ) { + $qrcode = $this->get_sell_qr( $order ); + + if ( ! $qrcode ) { + return esc_html_e( 'Dogodila se greška prilikom kreiranja QR kȏda za ovu narudžbu. Molimo rekreirajte narudžbu ili kontaktirajte vlasnika web stranice.', 'kekspay' ); + } + + return apply_filters( 'kekspay_sell_qr_code', '', $qrcode ); + } + + /** + * Format Keks pay url with html for display + * + * @param object $order Order for which to get the pay url. + * + * @return string Link for payment. + */ + public function display_sell_url( $order ) { + $sell_url = $this->get_sell_url( $order ); + + if ( ! $sell_url ) { + return esc_html_e( 'Dogodila se greška prilikom kreiranja poveznice za plaćanje putem KEKS Pay mobilne aplikacije za ovu narudžbu. Molimo rekreirajte narudžbu ili kontaktirajte vlasnika web stranice."', 'kekspay' ); + } + + $attrs = apply_filters( + 'kekspay_sell_link_attributes', + [ + 'id' => 'kekspay-pay-url', + 'class' => 'button kekspay-sell-button', + 'target' => '_blank', + 'label' => __( 'Otvori KEKS Pay', 'kekspay' ), + ] + ); + + return apply_filters( 'kekspay_sell_link', '' . esc_html( $attrs['label'] ) . '' ); + } + + } } diff --git a/includes/settings/kekspay-settings.php b/includes/settings/kekspay-settings.php index 0dbdb53..73fb1bd 100644 --- a/includes/settings/kekspay-settings.php +++ b/includes/settings/kekspay-settings.php @@ -1,104 +1,106 @@ array( - 'title' => __( 'Omogući KEKS Pay', 'kekspay' ), - 'type' => 'checkbox', - 'label' => __( 'Omogući KEKS Pay metodu plaćanja.', 'kekspay' ), - 'default' => 'no', - 'desc_tip' => false, - ), - 'title' => array( - 'title' => __( 'Naslov', 'kekspay' ), - 'type' => 'text', - 'description' => __( 'Naslov KEKS Pay metode plaćanja koji korisnik vidi na stranici za naplatu. Upišite: KEKS Pay.', 'kekspay' ), - 'default' => _x( 'KEKS Pay', 'Title default value', 'kekspay' ), - 'desc_tip' => true, - ), - 'auth-token' => array( - 'title' => __( 'Sigurnosni token', 'kekspay' ), - 'type' => 'title', - 'description' => Kekspay_Data::get_settings_token_field(), - ), - 'webshop-options' => array( - 'title' => __( 'Podaci o Web trgovini', 'kekspay' ), - 'type' => 'title', - 'description' => '', - ), - 'webshop-cid' => array( - 'title' => __( 'CID', 'kekspay' ), - 'type' => 'text', - 'description' => __( 'Jedinstveni identifikator Web trgovine unutar KEKS Pay sustava. Bit će dodijeljen od strane KEKS Pay sustava.', 'kekspay' ), - 'default' => '', - 'desc_tip' => true, - 'required' => true, - ), - 'webshop-tid' => array( - 'title' => __( 'TID', 'kekspay' ), - 'type' => 'text', - 'description' => __( 'Jedinstven identifikator za vrstu usluge unutar Web trgovine. Bit će dodijeljen od strane KEKS Pay sustava.', 'kekspay' ), - 'default' => '', - 'desc_tip' => true, - ), - 'webshop-secret-key' => array( - 'title' => __( 'Tajni ključ', 'kekspay' ), - 'type' => 'password', - 'description' => __( 'Tajni ključ Web trgovine unutar KEKS Pay sustava. Bit će dodijeljen od strane KEKS Pay sustava.', 'kekspay' ), - 'default' => '', - 'desc_tip' => true, - ), - 'test-webshop-cid' => array( - 'title' => __( 'TEST CID', 'kekspay' ), - 'type' => 'text', - 'description' => __( 'Jedinstven testni identifikator Web trgovine unutar KEKS Pay sustava. Bit će dodijeljen od strane KEKS Pay sustava.', 'kekspay' ), - 'default' => '', - 'desc_tip' => true, - ), - 'test-webshop-tid' => array( - 'title' => __( 'TEST TID', 'kekspay' ), - 'type' => 'text', - 'description' => __( 'Jedinstven testni identifikator Web trgovine unutar KEKS Pay sustava. Bit će dodijeljen od strane KEKS Pay sustava.', 'kekspay' ), - 'default' => '', - 'desc_tip' => true, - ), - 'test-webshop-secret-key' => array( - 'title' => __( 'TEST Tajni ključ', 'kekspay' ), - 'type' => 'password', - 'description' => __( 'Testni tajni ključ Web trgovine unutar KEKS Pay sustava. Bit će dodijeljen od strane KEKS Pay sustava.', 'kekspay' ), - 'default' => '', - 'desc_tip' => true, - ), - 'advanced-options' => array( - 'title' => __( 'Dodatne postavke', 'kekspay' ), - 'type' => 'title', - 'description' => '', - ), - 'payed-order-status' => array( - 'title' => __( 'Status plaćene narudžbe', 'kekspay' ), - 'type' => 'select', - 'description' => sprintf( __( 'Status narudžbe koji će biti postavljen nakon što KEKS Pay uspješno izvrši naplatu same narudžbe. (Zadani status je "%s").', 'kekspay' ), _x( 'Processing', 'Order status', 'woocommerce' ) ), - 'default' => 'wc-processing', - 'options' => wc_get_order_statuses(), - ), - 'in-test-mode' => array( - 'title' => __( 'Testni način rada', 'kekspay' ), - 'type' => 'checkbox', - 'label' => __( 'Uključi testni način rada.', 'kekspay' ), - 'description' => __( 'Način rada koji omogućava testiranje, ne zaboravite ga ugasiti po završetku testiranja.', 'kekspay' ), - 'default' => 'no', - 'desc_tip' => true, - ), - 'use-logger' => array( - 'title' => __( 'Zapisnik grešaka', 'kekspay' ), - 'type' => 'checkbox', - 'label' => __( 'Uključi zapisnik grešaka', 'kekspay' ), - 'description' => sprintf( __( 'Zapisuje procese i greške pri radu, zapisnik je pohranjen u: %s. Zapisnik može sadržavati osjetljive informacije. Preporučamo korištenje zapisnika samo u svrhe otkrivanja te otklanjanja grešaka te brisanje zapisnika po završetku.', 'kekspay' ), '' . WC_Log_Handler_File::get_log_file_path( 'kekspay' ) . '' ), - 'default' => 'no', - ), - ) + 'wc_kekspay_settings', + [ + 'enabled' => [ + 'title' => __( 'Omogući KEKS Pay', 'kekspay' ), + 'type' => 'checkbox', + 'label' => __( 'Omogući KEKS Pay metodu plaćanja.', 'kekspay' ), + 'default' => 'no', + 'desc_tip' => false, + ], + 'title' => [ + 'title' => __( 'Naslov', 'kekspay' ), + 'type' => 'text', + 'description' => __( 'Naslov KEKS Pay metode plaćanja koji korisnik vidi na stranici za naplatu. Upišite: KEKS Pay.', 'kekspay' ), + 'default' => _x( 'KEKS Pay', 'Title default value', 'kekspay' ), + 'desc_tip' => true, + ], + 'auth-token' => [ + 'title' => __( 'Sigurnosni token', 'kekspay' ), + 'type' => 'title', + 'description' => Kekspay_Data::get_settings_token_field(), + ], + 'webshop-options' => [ + 'title' => __( 'Podaci o Web trgovini', 'kekspay' ), + 'type' => 'title', + 'description' => '', + ], + 'webshop-cid' => [ + 'title' => __( 'CID', 'kekspay' ), + 'type' => 'text', + 'description' => __( 'Jedinstveni identifikator Web trgovine unutar KEKS Pay sustava. Bit će dodijeljen od strane KEKS Pay sustava.', 'kekspay' ), + 'default' => '', + 'desc_tip' => true, + 'required' => true, + ], + 'webshop-tid' => [ + 'title' => __( 'TID', 'kekspay' ), + 'type' => 'text', + 'description' => __( 'Jedinstven identifikator za vrstu usluge unutar Web trgovine. Bit će dodijeljen od strane KEKS Pay sustava.', 'kekspay' ), + 'default' => '', + 'desc_tip' => true, + ], + 'webshop-secret-key' => [ + 'title' => __( 'Tajni ključ', 'kekspay' ), + 'type' => 'password', + 'description' => __( 'Tajni ključ Web trgovine unutar KEKS Pay sustava. Bit će dodijeljen od strane KEKS Pay sustava.', 'kekspay' ), + 'default' => '', + 'desc_tip' => true, + ], + 'test-webshop-cid' => [ + 'title' => __( 'TEST CID', 'kekspay' ), + 'type' => 'text', + 'description' => __( 'Jedinstven testni identifikator Web trgovine unutar KEKS Pay sustava. Bit će dodijeljen od strane KEKS Pay sustava.', 'kekspay' ), + 'default' => '', + 'desc_tip' => true, + ], + 'test-webshop-tid' => [ + 'title' => __( 'TEST TID', 'kekspay' ), + 'type' => 'text', + 'description' => __( 'Jedinstven testni identifikator Web trgovine unutar KEKS Pay sustava. Bit će dodijeljen od strane KEKS Pay sustava.', 'kekspay' ), + 'default' => '', + 'desc_tip' => true, + ], + 'test-webshop-secret-key' => [ + 'title' => __( 'TEST Tajni ključ', 'kekspay' ), + 'type' => 'password', + 'description' => __( 'Testni tajni ključ Web trgovine unutar KEKS Pay sustava. Bit će dodijeljen od strane KEKS Pay sustava.', 'kekspay' ), + 'default' => '', + 'desc_tip' => true, + ], + 'advanced-options' => [ + 'title' => __( 'Dodatne postavke', 'kekspay' ), + 'type' => 'title', + 'description' => '', + ], + 'payed-order-status' => [ + 'title' => __( 'Status plaćene narudžbe', 'kekspay' ), + 'type' => 'select', + /* translators: order status for payment complete */ + 'description' => sprintf( __( 'Status narudžbe koji će biti postavljen nakon što KEKS Pay uspješno izvrši naplatu same narudžbe. (Zadani status je "%s").', 'kekspay' ), _x( 'Processing', 'Order status', 'woocommerce' ) ), + 'default' => 'wc-processing', + 'options' => wc_get_order_statuses(), + ], + 'in-test-mode' => [ + 'title' => __( 'Testni način rada', 'kekspay' ), + 'type' => 'checkbox', + 'label' => __( 'Uključi testni način rada.', 'kekspay' ), + 'description' => __( 'Način rada koji omogućava testiranje, ne zaboravite ga ugasiti po završetku testiranja.', 'kekspay' ), + 'default' => 'no', + 'desc_tip' => true, + ], + 'use-logger' => [ + 'title' => __( 'Zapisnik grešaka', 'kekspay' ), + 'type' => 'checkbox', + 'label' => __( 'Uključi zapisnik grešaka', 'kekspay' ), + /* translators: issue logger. */ + 'description' => sprintf( __( 'Zapisuje procese i greške pri radu, zapisnik je pohranjen u: %s. Zapisnik može sadržavati osjetljive informacije. Preporučamo korištenje zapisnika samo u svrhe otkrivanja te otklanjanja grešaka te brisanje zapisnika po završetku.', 'kekspay' ), '' . WC_Log_Handler_File::get_log_file_path( 'kekspay' ) . '' ), + 'default' => 'no', + ], + ] ); diff --git a/includes/utilities/class-kekspay-data.php b/includes/utilities/class-kekspay-data.php index c128872..e23d939 100644 --- a/includes/utilities/class-kekspay-data.php +++ b/includes/utilities/class-kekspay-data.php @@ -1,364 +1,367 @@ get_meta( 'kekspay_test_mode' ); - } - - /** - * Returns true if required keys in gateways settings are set, false otherwise. - * - * @return bool - */ - public static function required_keys_set() { - if ( ! self::get_settings( 'webshop-cid', true ) || ! self::get_settings( 'webshop-tid', true ) || ! self::get_settings( 'webshop-secret-key', true ) ) { - return false; - } - - return true; - } - - /** - * Returns true if currency is EUR. - * - * @return bool - */ - public static function currency_supported() { - return 'EUR' === get_woocommerce_currency(); - } - - /** - * Fetch settings for use. - * - * @param string $name Name of specific setting to fetch. - * @param bool $test_check Whether to check if test mode is on to fetch test version of the setting. - * - * @return array/string - */ - public static function get_settings( $name = false, $test_check = false ) { - if ( empty( self::$settings ) ) { - self::load_settings(); - } - - if ( $name ) { - if ( $test_check ) { - $name = self::test_mode() ? 'test-' . $name : $name; - } - - return isset( self::$settings[ $name ] ) ? self::$settings[ $name ] : null; - } - - return self::$settings; - } - - /** - * Return auth token or generate if there is none. - * - * @return string - */ - public static function get_auth_token() { - $token = self::get_settings( 'auth-token' ); - if ( ! $token ) { - $token = hash_hmac( 'sha256', bin2hex( openssl_random_pseudo_bytes( 64 ) ), site_url() ); - - self::set_settings( array( 'auth-token' => $token ) ); - } - - return $token; - } - - /** - * Return auth token or generate if there is none. - * - * @param bool $absolute Wheter to fetch full url with endpoint or only the endpoint. - * - * @return string - */ - public static function get_svg( $svg ) { - if ( ! $svg || ! file_exists( KEKSPAY_DIR_PATH . 'assets/dist/img/' . $svg . '.svg' ) ) { - return false; - } - - return file_get_contents( KEKSPAY_DIR_PATH . 'assets/dist/img/' . $svg . '.svg' ); - } - - /** - * Return gateway endpoint on wc api. - * - * @param bool $absolute Wheter to fetch full url with endpoint or only the endpoint. - * - * @return string - */ - public static function get_wc_endpoint( $absolute = false ) { - return $absolute ? untrailingslashit( WC()->api_request_url( self::$endpoint ) ) : self::$endpoint; - } - - /** - * Return gateway url for KEKS Pay servers. - * - * @return string - */ - public static function get_kekspay_pay_base( $trailingslash = false ) { - $endpoint = self::$kekspay_pay . ( self::test_mode() ? 'galebpay' : 'pay' ); - return $trailingslash ? trailingslashit( $endpoint ) : $endpoint; - } - - /** - * Return gateway url for KEKS Pay servers. - * - * @return string - */ - public static function get_kekspay_api_base() { - return self::test_mode() ? self::$test_kekspay_api : self::$kekspay_api; - } - - /** - * Creates endpoint message for settings. - * - * @return string - */ - public static function get_settings_token_field() { - return sprintf( - __( '%1$s

Nakon pohrane unutar KEKS Pay sustava omogućava ovoj trgovini primanje obavijesti o stanju naplate. %2$s Kontakt %3$s', 'kekspay' ), - '' . add_query_arg( 'token', self::get_auth_token(), self::get_wc_endpoint( true ) ) . '', - '', - '' - ); - } - - /** - * Creates unique bill id using webshop cid and order id. - * - * @return string - */ - public static function get_bill_id_by_order_id( $order_id ) { - $order = wc_get_order( $order_id ); - if ( ! $order ) { - Kekspay_Logger::log( 'Could not fetch order with ID ' . $order_id . ' while generating bill_id.', 'error' ); - return false; - } - return self::get_settings( 'webshop-tid', true ) . '-' . $order_id; - } - - /** - * Extract order id from kekspay bill id. - * - * @return string - */ - public static function get_order_id_by_bill_id( $bill_id ) { - return str_replace( self::get_settings( 'webshop-tid', true ) . '-', '', $bill_id ); - } - - /** - * Extract order id from kekspay bill id. - * - * @return string - */ - public static function get_algo() { - return self::$algo; - } - - /** - * Gathers all data needed for payment and formats it as array. - * - * @param object $order Order from which to extract data. - * @param bool $callbacks Whether to include callback urls or not. - * - * @return array/bool Extracted data as array, false on failure. - */ - public static function get_sell_data( $order, $callbacks = false ) { - if ( ! self::required_keys_set() ) { - Kekspay_Logger::log( 'Payment gateway setup incomplete, please enter all requested data to gateway settings.', 'error' ); - return false; - } - - $sell = array( - 'qr_type' => 1, - 'cid' => self::get_settings( 'webshop-cid', true ), - 'tid' => self::get_settings( 'webshop-tid', true ), - 'bill_id' => self::get_bill_id_by_order_id( $order->get_id() ), - 'amount' => $order->get_total(), - 'currency' => $order->get_currency(), - ); - - if ( $callbacks ) { - $sell['success_url'] = $order->get_checkout_order_received_url(); - $sell['fail_url'] = $order->get_cancel_order_url_raw(); - } - - return $sell; - } - - /** - * Detects the cipher to use based on the key stored. - * Currently supporting: - 3DES - * - AES - * - * @param string $key Secret key used for hashing. - * - * @return string - */ - public static function get_cipher( $key ) { - $key_size = strlen( $key ); - - if ( ctype_xdigit( $key ) ) { - if ( $key_size === 24 ) { - self::$algo = 0; - return 'des-ede3-cbc'; - } else { - throw new Exception( 'Secret key must be 24 bytes.' ); - } - } else { - if ( in_array( $key_size, array( 16, 24, 32 ), true ) ) { - self::$algo = 1; - return 'aes-' . ( $key_size * 8 ) . '-cbc'; - } else { - throw new Exception( 'Secret key must be 16, 24 or 32 bytes.' ); - } - } - } - - /** - * Return hash created from the provided data and secret. - * - * @param object $order Order from which to extract data for hash. - * @param string $timestamp Timestamp for creating hash. - * - * @return string - */ - public static function get_hash( $order, $amount, $timestamp ) { - try { - // Get hashing key from the plugins settings. - $key = self::get_settings( 'webshop-secret-key', true ); - // Define the cipher used for hashing. - $cipher = self::get_cipher( $key ); - // Concat epochtime + webshop tid + order amount + bill_id for payload. - $payload = $timestamp . self::get_settings( 'webshop-tid', true ) . $amount . self::get_bill_id_by_order_id( $order->get_id() ); - // Extract bytes from md5 hex hash. - $payload_checksum = pack( 'H*', md5( $payload ) ); - // Create 8 or 16 (depending on cipher) byte binary initialization vector. - $iv = str_repeat( pack( 'c', 0 ), false !== strpos( $cipher, 'aes' ) ? 16 : 8 ); - // Encrypt data using 3DES or AES cipher and convert it to hex. - $hash = bin2hex( openssl_encrypt( $payload_checksum, $cipher, $key, OPENSSL_RAW_DATA, $iv ) ); - - return strtoupper( $hash ); - } catch ( Exception $e ) { - Kekspay_Logger::log( 'Error while generating hash:' . $e->getMessage(), 'error' ); - } - - } - - } + /** + * Kekspay_Data class + * + * @since 0.1 + */ + class Kekspay_Data { + /** + * Whether or not logging is enabled. + * + * @var array + */ + private static $settings = []; + + /** + * Set endpoint for webshop api. + * + * @var string + */ + private static $endpoint = 'wc-kekspay'; + + /** + * Set base url of kekspay pay. + * + * @var string + */ + private static $kekspay_pay = 'https://kekspay.hr/'; + + /** + * Set base url of kekspay API. + * + * @var string + */ + private static $kekspay_api = 'https://ewa.erstebank.hr/eretailer/'; + + /** + * Set base test url of kekspay API. + * + * @var string + */ + private static $test_kekspay_api = 'https://kekspayuat.erstebank.hr/eretailer/'; + + /** + * Per Kekpay documentation, parametar "algo" needs to be sent with the requests that use hash. + * Value of "algo" tells Kekspay servers which cipher was used for hashing: + * 0 : 3DES + * 1 : AES + * + * @var string + */ + private static $algo; + + /** + * Init data class. + */ + public function __construct() { + self::load_settings(); + } + + /** + * Load gateway settings from the database. + * + * @return void + */ + public static function load_settings() { + self::$settings = get_option( 'woocommerce_' . KEKSPAY_PLUGIN_ID . '_settings', [] ); + } + + /** + * Save gateway settings to the database. + * + * @return void + */ + public static function set_settings( $settings ) { + update_option( 'woocommerce_' . KEKSPAY_PLUGIN_ID . '_settings', array_merge( self::get_settings(), $settings ) ); + } + + /** + * Returns if payment gateway is enabled. + * + * @return bool + */ + public static function enabled() { + if ( empty( self::$settings ) ) { + self::load_settings(); + } + + return isset( self::$settings['enabled'] ) && 'yes' === self::$settings['enabled']; + } + + /** + * Returns true if test mode is turned on, false otherwise. + * + * @return bool + */ + public static function test_mode() { + if ( empty( self::$settings ) ) { + self::load_settings(); + } + + return 'yes' === self::$settings['in-test-mode']; + } + + /** + * Returns true if order was created in test mode, false otherwise. + * + * @return bool + */ + public static function order_test_mode( $order ) { + return 'yes' === $order->get_meta( 'kekspay_test_mode' ); + } + + /** + * Returns true if required keys in gateways settings are set, false otherwise. + * + * @return bool + */ + public static function required_keys_set() { + if ( ! self::get_settings( 'webshop-cid', true ) || ! self::get_settings( 'webshop-tid', true ) || ! self::get_settings( 'webshop-secret-key', true ) ) { + return false; + } + + return true; + } + + /** + * Returns true if currency is EUR. + * + * @return bool + */ + public static function currency_supported() { + return 'EUR' === get_woocommerce_currency(); + } + + /** + * Fetch settings for use. + * + * @param string $name Name of specific setting to fetch. + * @param bool $test_check Whether to check if test mode is on to fetch test version of the setting. + * + * @return array/string + */ + public static function get_settings( $name = false, $test_check = false ) { + if ( empty( self::$settings ) ) { + self::load_settings(); + } + + if ( $name ) { + if ( $test_check ) { + $name = self::test_mode() ? 'test-' . $name : $name; + } + + return isset( self::$settings[ $name ] ) ? self::$settings[ $name ] : null; + } + + return self::$settings; + } + + /** + * Return auth token or generate if there is none. + * + * @return string + */ + public static function get_auth_token() { + $token = self::get_settings( 'auth-token' ); + if ( ! $token ) { + $token = hash_hmac( 'sha256', bin2hex( openssl_random_pseudo_bytes( 64 ) ), site_url() ); + + self::set_settings( [ 'auth-token' => $token ] ); + } + + return $token; + } + + /** + * Return auth token or generate if there is none. + * + * @param bool $absolute Wheter to fetch full url with endpoint or only the endpoint. + * + * @return string + */ + public static function get_svg( $svg ) { + if ( ! $svg || ! file_exists( KEKSPAY_DIR_PATH . 'assets/dist/img/' . $svg . '.svg' ) ) { + return false; + } + + return file_get_contents( KEKSPAY_DIR_PATH . 'assets/dist/img/' . $svg . '.svg' ); // @codingStandardsIgnoreLine - safe use of file_get_contents + } + + /** + * Return gateway endpoint on wc api. + * + * @param bool $absolute Wheter to fetch full url with endpoint or only the endpoint. + * + * @return string + */ + public static function get_wc_endpoint( $absolute = false ) { + return $absolute ? untrailingslashit( WC()->api_request_url( self::$endpoint ) ) : self::$endpoint; + } + + /** + * Return gateway url for KEKS Pay servers. + * + * @return string + */ + public static function get_kekspay_pay_base( $trailingslash = false ) { + $endpoint = self::$kekspay_pay . ( self::test_mode() ? 'galebpay' : 'pay' ); + return $trailingslash ? trailingslashit( $endpoint ) : $endpoint; + } + + /** + * Return gateway url for KEKS Pay servers. + * + * @return string + */ + public static function get_kekspay_api_base() { + return self::test_mode() ? self::$test_kekspay_api : self::$kekspay_api; + } + + /** + * Creates endpoint message for settings. + * + * @return string + */ + public static function get_settings_token_field() { + return sprintf( + /* translators: token info. */ + __( '%1$s

Nakon pohrane unutar KEKS Pay sustava omogućava ovoj trgovini primanje obavijesti o stanju naplate. %2$s Kontakt %3$s', 'kekspay' ), + '' . add_query_arg( 'token', self::get_auth_token(), self::get_wc_endpoint( true ) ) . '', + '', + '' + ); + } + + /** + * Creates unique bill id using webshop cid and order id. + * + * @return string + */ + public static function get_bill_id_by_order_id( $order_id ) { + $order = wc_get_order( $order_id ); + if ( ! $order ) { + Kekspay_Logger::log( 'Could not fetch order with ID ' . $order_id . ' while generating bill_id.', 'error' ); + return false; + } + return self::get_settings( 'webshop-tid', true ) . '-' . $order_id; + } + + /** + * Extract order id from kekspay bill id. + * + * @return string + */ + public static function get_order_id_by_bill_id( $bill_id ) { + return str_replace( self::get_settings( 'webshop-tid', true ) . '-', '', $bill_id ); + } + + /** + * Extract order id from kekspay bill id. + * + * @return string + */ + public static function get_algo() { + return self::$algo; + } + + /** + * Gathers all data needed for payment and formats it as array. + * + * @param object $order Order from which to extract data. + * @param bool $callbacks Whether to include callback urls or not. + * + * @return array/bool Extracted data as array, false on failure. + */ + public static function get_sell_data( $order, $callbacks = false ) { + if ( ! self::required_keys_set() ) { + Kekspay_Logger::log( 'Payment gateway setup incomplete, please enter all requested data to gateway settings.', 'error' ); + return false; + } + + $sell = [ + 'qr_type' => 1, + 'cid' => self::get_settings( 'webshop-cid', true ), + 'tid' => self::get_settings( 'webshop-tid', true ), + 'bill_id' => self::get_bill_id_by_order_id( $order->get_id() ), + 'amount' => $order->get_total(), + 'currency' => $order->get_currency(), + ]; + + if ( $callbacks ) { + $sell['success_url'] = $order->get_checkout_order_received_url(); + $sell['fail_url'] = $order->get_cancel_order_url_raw(); + } + + return $sell; + } + + /** + * Detects the cipher to use based on the key stored. + * Currently supporting: - 3DES + * - AES + * + * @param string $key Secret key used for hashing. + * + * @throws Exception Issues with key length. + * + * @return string + */ + public static function get_cipher( $key ) { + $key_size = strlen( $key ); + + if ( ctype_xdigit( $key ) ) { + if ( $key_size === 24 ) { + self::$algo = 0; + return 'des-ede3-cbc'; + } else { + throw new Exception( 'Secret key must be 24 bytes.' ); + } + } else { + if ( in_array( $key_size, [ 16, 24, 32 ], true ) ) { + self::$algo = 1; + return 'aes-' . ( $key_size * 8 ) . '-cbc'; + } else { + throw new Exception( 'Secret key must be 16, 24 or 32 bytes.' ); + } + } + } + + /** + * Return hash created from the provided data and secret. + * + * @param object $order Order from which to extract data for hash. + * @param string $timestamp Timestamp for creating hash. + * + * @return string + */ + public static function get_hash( $order, $amount, $timestamp ) { + try { + // Get hashing key from the plugins settings. + $key = (string) self::get_settings( 'webshop-secret-key', true ); + // Define the cipher used for hashing. + $cipher = self::get_cipher( $key ); + // Concat epochtime + webshop tid + order amount + bill_id for payload. + $payload = $timestamp . self::get_settings( 'webshop-tid', true ) . $amount . self::get_bill_id_by_order_id( $order->get_id() ); + // Extract bytes from md5 hex hash. + $payload_checksum = pack( 'H*', md5( $payload ) ); + // Create 8 or 16 (depending on cipher) byte binary initialization vector. + $iv = str_repeat( pack( 'c', 0 ), false !== strpos( $cipher, 'aes' ) ? 16 : 8 ); + // Encrypt data using 3DES or AES cipher and convert it to hex. + $hash = bin2hex( openssl_encrypt( $payload_checksum, $cipher, $key, OPENSSL_RAW_DATA, $iv ) ); + + return strtoupper( $hash ); + } catch ( Exception $e ) { + Kekspay_Logger::log( 'Error while generating hash:' . $e->getMessage(), 'error' ); + } + + } + + } } diff --git a/includes/utilities/class-kekspay-logger.php b/includes/utilities/class-kekspay-logger.php index 8de188c..87fa2dc 100644 --- a/includes/utilities/class-kekspay-logger.php +++ b/includes/utilities/class-kekspay-logger.php @@ -1,66 +1,66 @@ log( - $level, - 'Kekspay v' . KEKSPAY_PLUGIN_VERSION . ' - ' . $message, - array( - 'source' => KEKSPAY_PLUGIN_ID, - ) - ); + $logger->log( + $level, + 'Kekspay v' . KEKSPAY_PLUGIN_VERSION . ' - ' . $message, + [ + 'source' => KEKSPAY_PLUGIN_ID, + ] + ); - return true; - } - } + return true; + } + } } diff --git a/package.json b/package.json index 1b8ded1..08eb626 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,18 @@ { - "name": "KEKS Pay for WooCommerce", - "version": "1.0.0", - "description": "KEKS Pay build", - "private": true, - "devDependencies": { - "copy-webpack-plugin": "^11.0.0", - "image-minimizer-webpack-plugin": "^3.8.3", - "imagemin-gifsicle": "^7.0.0", - "imagemin-mozjpeg": "^10.0.0", - "imagemin-optipng": "^8.0.0", - "imagemin-svgo": "^10.0.1", - "laravel-mix": "^6.0.49", - "resolve-url-loader": "^5.0.0", - "sass": "^1.72.0", - "sass-loader": "^12.1.0" - } + "name": "keks-pay-for-woocommerce", + "version": "1.0.0", + "description": "KEKS Pay assets build", + "private": true, + "devDependencies": { + "copy-webpack-plugin": "^11.0.0", + "image-minimizer-webpack-plugin": "^3.8.3", + "imagemin-gifsicle": "^7.0.0", + "imagemin-mozjpeg": "^10.0.0", + "imagemin-optipng": "^8.0.0", + "imagemin-svgo": "^10.0.1", + "laravel-mix": "^6.0.49", + "resolve-url-loader": "^5.0.0", + "sass": "^1.72.0", + "sass-loader": "^12.1.0" + } } diff --git a/phpcs.xml b/phpcs.xml index 5743f54..000c6c3 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,109 +1,12 @@ - - - Neuralab coding standards for WP development - - - + + + - - /vendor/* + + /assets/* /node_modules/* - */data/* - */tests/* - *.js - *.css + /vendor/* - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - - - - - - - - - - - - - - index.php - singular.php - single.php - page.php - 404.php - template-*.php - templates/* - woocommerce/* - diff --git a/readme.txt b/readme.txt index fc26db3..06ae7cf 100644 --- a/readme.txt +++ b/readme.txt @@ -1,10 +1,10 @@ === KEKS Pay for WooCommerce === Contributors: erstebank Tags: kekspay, woocommerce, gateway, payment -Requires at least: 5.0 +Requires at least: 6.3 Tested up to: 6.5 -Requires PHP: 7.2 -Stable tag: 1.1.0 +Requires PHP: 7.4 +Stable tag: 2.0.0 License: GPL v3 or later License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -32,9 +32,9 @@ On a side note, this plugin is provided “as-is” and we don't currently provi = MINIMUM REQUIREMENTS = -* WooCommerce 3.3 or greater. -* WordPress 5.0 or greater. -* PHP version 7.2 or greater. +* WooCommerce 8.2 or greater. +* WordPress 6.3 or greater. +* PHP version 7.4 or greater. * SSL must be installed on your site and active on your Checkout pages. = INSTALL = @@ -49,6 +49,9 @@ For more installation options check the [official WordPress documentation](https == Changelog == += 2.0.0 = +* Drop PHP 7.2 support. + = 1.1.0 = * Add assets build * Add AES cipher support diff --git a/vendor/chillerlan/php-qrcode/.github/FUNDING.yml b/vendor/chillerlan/php-qrcode/.github/FUNDING.yml deleted file mode 100644 index fc89a67..0000000 --- a/vendor/chillerlan/php-qrcode/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -ko_fi: codemasher diff --git a/vendor/chillerlan/php-qrcode/.github/workflows/tests.yml b/vendor/chillerlan/php-qrcode/.github/workflows/tests.yml deleted file mode 100644 index 5dd343a..0000000 --- a/vendor/chillerlan/php-qrcode/.github/workflows/tests.yml +++ /dev/null @@ -1,55 +0,0 @@ -# https://help.github.com/en/categories/automating-your-workflow-with-github-actions -# https://github.com/localheinz/php-library-template/blob/master/.github/workflows/continuous-integration.yml -# https://github.com/sebastianbergmann/phpunit/blob/master/.github/workflows/ci.yml - -on: - - pull_request - - push - -name: "Continuous Integration" - -jobs: - - tests: - name: "Unit Tests" - runs-on: ubuntu-latest - - strategy: - matrix: - php-binary: - - php7.2 - - php7.3 - - php7.4 - - steps: - - name: "Checkout" - uses: actions/checkout@v1.1.0 - - - name: "Install dependencies with composer" - run: ${{ matrix.php-binary }} $(which composer) update --no-interaction --no-progress --no-suggest - - - name: "Run unit tests with phpunit" - run: ${{ matrix.php-binary }} vendor/bin/phpunit --configuration=phpunit.xml --no-coverage - - - code-coverage: - name: "Code Coverage" - runs-on: ubuntu-latest - - steps: - - name: "Checkout" - uses: actions/checkout@v1.1.0 - - - name: "Install locked dependencies with composer" - run: php7.4 $(which composer) install --no-interaction --no-progress --no-suggest - - - name: "Dump Xdebug filter with phpunit/phpunit" - run: php7.4 vendor/bin/phpunit --configuration=phpunit.xml --dump-xdebug-filter=.build/phpunit/xdebug-filter.php - - - name: "Collect code coverage with Xdebug and phpunit/phpunit" - run: php7.4 vendor/bin/phpunit --configuration=phpunit.xml --prepend=.build/phpunit/xdebug-filter.php - - - name: "Send code coverage report to Codecov.io" - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - run: bash <(curl -s https://codecov.io/bash) diff --git a/vendor/chillerlan/php-qrcode/.gitignore b/vendor/chillerlan/php-qrcode/.gitignore deleted file mode 100644 index ecebb99..0000000 --- a/vendor/chillerlan/php-qrcode/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.build/* -.idea/* -vendor/* -composer.lock -*.phpunit.result.cache diff --git a/vendor/chillerlan/php-qrcode/.idea/inspectionProfiles/Project_Default.xml b/vendor/chillerlan/php-qrcode/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..565a038 --- /dev/null +++ b/vendor/chillerlan/php-qrcode/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/vendor/chillerlan/php-qrcode/.scrutinizer.yml b/vendor/chillerlan/php-qrcode/.scrutinizer.yml deleted file mode 100644 index 7fdd2a4..0000000 --- a/vendor/chillerlan/php-qrcode/.scrutinizer.yml +++ /dev/null @@ -1,5 +0,0 @@ -filter: - excluded_paths: - - examples/* - - tests/* - - vendor/* diff --git a/vendor/chillerlan/php-qrcode/.travis.yml b/vendor/chillerlan/php-qrcode/.travis.yml deleted file mode 100644 index 78b8d79..0000000 --- a/vendor/chillerlan/php-qrcode/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -addons: - apt: - packages: - - imagemagick - -language: php - -matrix: - include: - - php: 7.2 - - php: 7.3 - - php: 7.4 - -before_install: - - pecl channel-update pecl.php.net - - printf "\n" | pecl install imagick -install: travis_retry composer install --no-interaction --prefer-source -script: vendor/bin/phpunit --configuration phpunit.xml --coverage-clover clover.xml -after_script: bash <(curl -s https://codecov.io/bash) diff --git a/vendor/chillerlan/php-qrcode/README.md b/vendor/chillerlan/php-qrcode/README.md index 075b2a3..3ad7bad 100644 --- a/vendor/chillerlan/php-qrcode/README.md +++ b/vendor/chillerlan/php-qrcode/README.md @@ -1,77 +1,84 @@ # chillerlan/php-qrcode -A PHP7.2+ QR Code library based on the [implementation](https://github.com/kazuhikoarase/qrcode-generator) by [Kazuhiko Arase](https://github.com/kazuhikoarase), +A PHP 7.4+ QR Code library based on the [implementation](https://github.com/kazuhikoarase/qrcode-generator) by [Kazuhiko Arase](https://github.com/kazuhikoarase), namespaced, cleaned up, improved and other stuff. +**Attention:** there is now also a javascript port: [chillerlan/js-qrcode](https://github.com/chillerlan/js-qrcode). + +[![PHP Version Support][php-badge]][php] [![Packagist version][packagist-badge]][packagist] -[![License][license-badge]][license] -[![Travis CI][travis-badge]][travis] +[![Continuous Integration][gh-action-badge]][gh-action] [![CodeCov][coverage-badge]][coverage] -[![Scrunitizer CI][scrutinizer-badge]][scrutinizer] +[![Codacy][codacy-badge]][codacy] [![Packagist downloads][downloads-badge]][downloads] -[![PayPal donate][donate-badge]][donate] - -[![Continuous Integration][gh-action-badge]][gh-action] -[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-qrcode.svg?style=flat-square +[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-qrcode?logo=php&color=8892BF +[php]: https://www.php.net/supported-versions.php +[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-qrcode.svg?logo=packagist [packagist]: https://packagist.org/packages/chillerlan/php-qrcode -[license-badge]: https://img.shields.io/github/license/chillerlan/php-qrcode.svg?style=flat-square -[license]: https://github.com/chillerlan/php-qrcode/blob/main/LICENSE -[travis-badge]: https://img.shields.io/travis/chillerlan/php-qrcode.svg?style=flat-square -[travis]: https://travis-ci.org/chillerlan/php-qrcode -[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-qrcode.svg?style=flat-square -[coverage]: https://codecov.io/github/chillerlan/php-qrcode -[scrutinizer-badge]: https://img.shields.io/scrutinizer/g/chillerlan/php-qrcode.svg?style=flat-square -[scrutinizer]: https://scrutinizer-ci.com/g/chillerlan/php-qrcode -[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-qrcode.svg?style=flat-square +[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-qrcode/v4.3.x?logo=codecov +[coverage]: https://app.codecov.io/gh/chillerlan/php-qrcode/tree/v4.3.x +[codacy-badge]: https://img.shields.io/codacy/grade/edccfc4fe5a34b74b1c53ee03f097b8d/v4.3.x?logo=codacy +[codacy]: https://app.codacy.com/gh/chillerlan/php-qrcode/dashboard?branch=v4.3.x +[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-qrcode?logo=packagist [downloads]: https://packagist.org/packages/chillerlan/php-qrcode/stats -[donate-badge]: https://img.shields.io/badge/donate-paypal-ff33aa.svg?style=flat-square -[donate]: https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4 -[gh-action-badge]: https://github.com/chillerlan/php-qrcode/workflows/Continuous%20Integration/badge.svg -[gh-action]: https://github.com/chillerlan/php-qrcode/actions +[gh-action-badge]: https://img.shields.io/github/actions/workflow/status/chillerlan/php-qrcode/tests.yml?branch=v4.3.x&logo=github +[gh-action]: https://github.com/chillerlan/php-qrcode/actions/workflows/tests.yml?query=branch%3Av4.3.x -## Documentation +# Documentation -### Requirements -- PHP 7.2+ +## Requirements +- PHP 7.4+ - `ext-mbstring` - - optional: - - `ext-json`, `ext-gd` - - `ext-imagick` with [ImageMagick](https://imagemagick.org) installed - - [`setasign/fpdf`](https://github.com/setasign/fpdf) for the PDF output module + - optional: + - `ext-json`, `ext-gd` + - `ext-imagick` with [ImageMagick](https://imagemagick.org) installed + - [`setasign/fpdf`](https://github.com/setasign/fpdf) for the PDF output module -### Installation +## Installation **requires [composer](https://getcomposer.org)** via terminal: `composer require chillerlan/php-qrcode` -*composer.json* (note: replace `dev-master` with a [version boundary](https://getcomposer.org/doc/articles/versions.md), e.g. `^3.2`) +*composer.json* ```json { "require": { - "php": "^7.2", - "chillerlan/php-qrcode": "^3.4" + "php": "^7.4 || ^8.0", + "chillerlan/php-qrcode": "v4.3.x-dev#" } } ``` -### Usage +Note: replace `v4.3.x-dev` with a [version constraint](https://getcomposer.org/doc/articles/versions.md#writing-version-constraints), e.g. `^4.3` - see [releases](https://github.com/chillerlan/php-qrcode/releases) for valid versions. +For PHP version ... + - 7.4+ use `^4.3` + - 7.2+ use `^3.4.1` (v3.4.1 also supports PHP8) + - 7.0+ use `^2.0` + - 5.6+ use `^1.0` (please let PHP 5 die!) + +In case you want to keep using `v4.3.x-dev`, specify the hash of a commit to avoid running into unforseen issues like so: `v4.3.x-dev#c115f7bc51d466ccb24c544e88329804aad8c2a0` + +PSA: [PHP 7.0 - 7.4 are EOL](https://www.php.net/supported-versions.php) and therefore the respective `QRCode` versions are also no longer supported! + +## Quickstart We want to encode this URI for a mobile authenticator into a QRcode image: ```php $data = 'otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net'; -//quick and simple: +// quick and simple: echo 'QR Code'; ```

- QR codes are awesome! - QR codes are awesome! + QR codes are awesome! + QR codes are awesome!

Wait, what was that? Please again, slower! -### Advanced usage + +## Advanced usage Ok, step by step. First you'll need a `QRCode` instance, which can be optionally invoked with a `QROptions` (or a [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/master/src/SettingsContainerInterface.php), respectively) object as the only parameter. @@ -92,7 +99,7 @@ $qrcode->render($data); $qrcode->render($data, '/path/to/file.svg'); ``` -In case you just want the raw QR code matrix, call `QRCode::getMatrix()` - this method is also called internally from `QRCode::render()`. See also [Custom output modules](#custom-qroutputinterface). +In case you just want the raw QR code matrix, call `QRCode::getMatrix()` - this method is also called internally from `QRCode::render()`. See also [[Custom output interface]]. ```php $matrix = $qrcode->getMatrix($data); @@ -102,6 +109,8 @@ foreach($matrix->matrix() as $y => $row){ // get a module's value $value = $module; + + // or via the matrix's getter method $value = $matrix->get($x, $y); // boolean check a module @@ -116,88 +125,27 @@ foreach($matrix->matrix() as $y => $row){ } ``` -Have a look [in this folder](https://github.com/chillerlan/php-qrcode/tree/master/examples) for some more usage examples. - -#### Custom module values -Previous versions of `QRCode` held only boolean matrix values that only allowed to determine whether a module was dark or not. Now you can distinguish between different parts of the matrix, namely the several required patterns from the QR Code specification, and use them in different ways. - -The dark value is the module (light) value shifted by 8 bits to the left: `$value = $M_TYPE << ($bool ? 8 : 0);`, where `$M_TYPE` is one of the `QRMatrix::M_*` constants. -You can check the value for a type explicitly like... -```php -// for true (dark) -$value >> 8 === $M_TYPE; - -//for false (light) -$value === $M_TYPE; -``` -...or you can perform a loose check, ignoring the module value -```php -// for true -$value >> 8 > 0; - -// for false -$value >> 8 === 0 -``` - -See also `QRMatrix::set()`, `QRMatrix::check()` and [`QRMatrix` constants](#qrmatrix-constants). +Have a look [in the examples folder](https://github.com/chillerlan/php-qrcode/tree/main/examples) for some more usage examples. -To map the values and properly render the modules for the given `QROutputInterface`, it's necessary to overwrite the default values: -```php -$options = new QROptions; - -// for HTML, SVG and ImageMagick -$options->moduleValues = [ - // finder - 1536 => '#A71111', // dark (true) - 6 => '#FFBFBF', // light (false) - // alignment - 2560 => '#A70364', - 10 => '#FFC9C9', - // timing - 3072 => '#98005D', - 12 => '#FFB8E9', - // format - 3584 => '#003804', - 14 => '#00FB12', - // version - 4096 => '#650098', - 16 => '#E0B8FF', - // data - 1024 => '#4A6000', - 4 => '#ECF9BE', - // darkmodule - 512 => '#080063', - // separator - 8 => '#AFBFBF', - // quietzone - 18 => '#FFFFFF', -]; +### Notes +The QR encoder, especially the subroutines for mask pattern testing, can cause high CPU load on increased matrix size. +You can avoid a part of this load by choosing a fast output module, like `OUTPUT_IMAGE_*` and maybe setting the mask pattern manually (which may result in unreadable QR Codes). +Oh hey and don't forget to sanitize any user input! -// for the image output types -$options->moduleValues = [ - 512 => [0, 0, 0], - // ... -]; -// for string/text output -$options->moduleValues = [ - 512 => '#', - // ... -]; -``` +## Custom output interface -#### Custom `QROutputInterface` -Instead of bloating your code you can simply create your own output interface by extending `QROutputAbstract`. Have a look at the [built-in output modules](https://github.com/chillerlan/php-qrcode/tree/master/src/Output). +Instead of bloating your code you can simply create your own output interface by creating a `QROutputInterface` (i.e. extending `QROutputAbstract`). ```php class MyCustomOutput extends QROutputAbstract{ // inherited from QROutputAbstract - protected $matrix; // QRMatrix - protected $moduleCount; // modules QRMatrix::size() - protected $options; // MyCustomOptions or QROptions - protected $scale; // scale factor from options - protected $length; // length of the matrix ($moduleCount * $scale) + protected QRMatrix $matrix; // QRMatrix + protected int $moduleCount; // modules QRMatrix::size() + protected QROptions $options; // MyCustomOptions or QROptions + protected int $scale; // scale factor from options + protected int $length; // length of the matrix ($moduleCount * $scale) // ...check/set default module values (abstract method, called by the constructor) protected function setModuleValues():void{ @@ -219,20 +167,21 @@ class MyCustomOutput extends QROutputAbstract{ } ``` +For more examples, have a look at the [built-in output modules](https://github.com/chillerlan/php-qrcode/tree/main/src/Output). In case you need additional settings for your output module, just extend `QROptions`... ``` class MyCustomOptions extends QROptions{ - protected $myParam = 'defaultValue'; + protected string $myParam = 'defaultValue'; // ... } ``` -...or use the [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/master/src/SettingsContainerInterface.php), which is the more flexible approach. +...or use the [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/main/src/SettingsContainerInterface.php), which is the more flexible approach. ```php trait MyCustomOptionsTrait{ - protected $myParam = 'defaultValue'; + protected string $myParam = 'defaultValue'; // ... } @@ -270,123 +219,211 @@ $qrOutputInterface = new MyCustomOutput($myCustomOptions, (new QRCode($myCustomO $qrOutputInterface->dump(); ``` -### API - -#### `QRCode` methods -method | return | description ------- | ------ | ----------- -`__construct(QROptions $options = null)` | - | see [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/master/src/SettingsContainerInterface.php) -`render(string $data, string $file = null)` | mixed, `QROutputInterface::dump()` | renders a QR Code for the given `$data` and `QROptions`, saves `$file` optional -`getMatrix(string $data)` | `QRMatrix` | returns a `QRMatrix` object for the given `$data` and current `QROptions` -`initDataInterface(string $data)` | `QRDataInterface` | returns a fresh `QRDataInterface` for the given `$data` -`isNumber(string $string)` | bool | checks if a string qualifies for `Number` -`isAlphaNum(string $string)` | bool | checks if a string qualifies for `AlphaNum` -`isKanji(string $string)` | bool | checks if a string qualifies for `Kanji` - -#### `QRCode` constants -name | description ----- | ----------- -`VERSION_AUTO` | `QROptions::$version` -`MASK_PATTERN_AUTO` | `QROptions::$maskPattern` -`OUTPUT_MARKUP_SVG`, `OUTPUT_MARKUP_HTML` | `QROptions::$outputType` markup -`OUTPUT_IMAGE_PNG`, `OUTPUT_IMAGE_JPG`, `OUTPUT_IMAGE_GIF` | `QROptions::$outputType` image -`OUTPUT_STRING_JSON`, `OUTPUT_STRING_TEXT` | `QROptions::$outputType` string -`OUTPUT_IMAGICK` | `QROptions::$outputType` ImageMagick -`OUTPUT_FPDF` | `QROptions::$outputType` PDF, using [FPDF](https://github.com/setasign/fpdf) -`OUTPUT_CUSTOM` | `QROptions::$outputType`, requires `QROptions::$outputInterface` -`ECC_L`, `ECC_M`, `ECC_Q`, `ECC_H`, | ECC-Level: 7%, 15%, 25%, 30% in `QROptions::$eccLevel` -`DATA_NUMBER`, `DATA_ALPHANUM`, `DATA_BYTE`, `DATA_KANJI` | `QRDataInterface::$datamode` - -#### `QROptions` properties -property | type | default | allowed | description --------- | ---- | ------- | ------- | ----------- -`$version` | int | `QRCode::VERSION_AUTO` | 1...40 | the [QR Code version number](http://www.qrcode.com/en/about/version.html) -`$versionMin` | int | 1 | 1...40 | Minimum QR version (if `$version = QRCode::VERSION_AUTO`) -`$versionMax` | int | 40 | 1...40 | Maximum QR version (if `$version = QRCode::VERSION_AUTO`) -`$eccLevel` | int | `QRCode::ECC_L` | `QRCode::ECC_X` | Error correct level, where X = L (7%), M (15%), Q (25%), H (30%) -`$maskPattern` | int | `QRCode::MASK_PATTERN_AUTO` | 0...7 | Mask Pattern to use -`$addQuietzone` | bool | `true` | - | Add a "quiet zone" (margin) according to the QR code spec -`$quietzoneSize` | int | 4 | clamped to 0 ... `$matrixSize / 2` | Size of the quiet zone -`$dataMode` | string | `null` | `Number`, `AlphaNum`, `Kanji`, `Byte` | allows overriding the data type detection -`$outputType` | string | `QRCode::OUTPUT_IMAGE_PNG` | `QRCode::OUTPUT_*` | built-in output type -`$outputInterface` | string | `null` | * | FQCN of the custom `QROutputInterface` if `QROptions::$outputType` is set to `QRCode::OUTPUT_CUSTOM` -`$cachefile` | string | `null` | * | optional cache file path -`$eol` | string | `PHP_EOL` | * | newline string (HTML, SVG, TEXT) -`$scale` | int | 5 | * | size of a QR code pixel (SVG, IMAGE_*), HTML -> via CSS -`$cssClass` | string | `null` | * | a common css class -`$svgOpacity` | float | 1.0 | 0...1 | -`$svgDefs` | string | * | * | anything between [``](https://developer.mozilla.org/docs/Web/SVG/Element/defs) -`$svgViewBoxSize` | int | `null` | * | a positive integer which defines width/height of the [viewBox attribute](https://css-tricks.com/scale-svg/#article-header-id-3) -`$textDark` | string | '🔴' | * | string substitute for dark -`$textLight` | string | '⭕' | * | string substitute for light -`$markupDark` | string | '#000' | * | markup substitute for dark (CSS value) -`$markupLight` | string | '#fff' | * | markup substitute for light (CSS value) -`$imageBase64` | bool | `true` | - | whether to return the image data as base64 or raw like from `file_get_contents()` -`$imageTransparent` | bool | `true` | - | toggle transparency (no jpeg support) -`$imageTransparencyBG` | array | `[255, 255, 255]` | `[R, G, B]` | the RGB values for the transparent color, see [`imagecolortransparent()`](http://php.net/manual/function.imagecolortransparent.php) -`$pngCompression` | int | -1 | -1 ... 9 | `imagepng()` compression level, -1 = auto -`$jpegQuality` | int | 85 | 0 - 100 | `imagejpeg()` quality -`$imagickFormat` | string | 'png' | * | ImageMagick output type, see `Imagick::setType()` -`$imagickBG` | string | `null` | * | ImageMagick background color, see `ImagickPixel::__construct()` -`$moduleValues` | array | `null` | * | Module values map, see [Custom output modules](#custom-qroutputinterface) and `QROutputInterface::DEFAULT_MODULE_VALUES` - -#### `QRMatrix` methods -method | return | description ------- | ------ | ----------- -`__construct(int $version, int $eclevel)` | - | - -`matrix()` | array | the internal matrix representation as a 2 dimensional array -`version()` | int | the current QR Code version -`eccLevel()` | int | current ECC level -`maskPattern()` | int | the used mask pattern -`size()` | int | the absoulute size of the matrix, including quiet zone (if set). `$version * 4 + 17 + 2 * $quietzone` -`get(int $x, int $y)` | int | returns the value of the module -`set(int $x, int $y, bool $value, int $M_TYPE)` | `QRMatrix` | sets the `$M_TYPE` value for the module -`check(int $x, int $y)` | bool | checks whether a module is true (dark) or false (light) -`setLogoSpace(int $width, int $height, int $startX = null, int $startY = null)` | `QRMatrix` | creates a logo space in the matrix - -#### `QRMatrix` constants -name | light (false) | dark (true) | description ----- | ------------- | ----------- | ----------- -`M_NULL` | 0 | - | module not set (should never appear. if so, there's an error) -`M_DARKMODULE` | - | 512 | once per matrix at `$xy = [8, 4 * $version + 9]` -`M_DATA` | 4 | 1024 | the actual encoded data -`M_FINDER` | 6 | 1536 | the 7x7 finder patterns -`M_SEPARATOR` | 8 | - | separator lines around the finder patterns -`M_ALIGNMENT` | 10 | 2560 | the 5x5 alignment patterns -`M_TIMING` | 12 | 3072 | the timing pattern lines -`M_FORMAT` | 14 | 3584 | format information pattern -`M_VERSION` | 16 | 4096 | version information pattern -`M_QUIETZONE` | 18 | - | margin around the QR Code -`M_LOGO` | 20 | - | space for a logo image (not used yet) -`M_TEST` | 255 | 65280 | test value +### Custom module values +You can distinguish between different parts of the matrix, namely the several required patterns from the QR Code specification, and use them in different ways, i.e. to assign different colors for each part of the matrix (see the [image example](https://github.com/chillerlan/php-qrcode/blob/main/examples/image.php)). +The dark value is the module value (light) shifted by 8 bits to the left: `$value = $M_TYPE << ($bool ? 8 : 0);`, where `$M_TYPE` is one of the `QRMatrix::M_*` constants. +You can check the value for a type explicitly like... +```php +// for true (dark) +($value >> 8) === $M_TYPE; -### Notes -The QR encoder, especially the subroutines for mask pattern testing, can cause high CPU load on increased matrix size. -You can avoid a part of this load by choosing a fast output module, like `OUTPUT_IMAGE_*` and setting the mask pattern manually (which may result in unreadable QR Codes). -Oh hey and don't forget to sanitize any user input! +// for false (light) +$value === $M_TYPE; +``` +...or you can perform a loose check, ignoring the module value +```php +// for true +($value >> 8) > 0; -### Disclaimer! -I don't take responsibility for molten CPUs, misled applications, failed log-ins etc.. Use at your own risk! +// for false +($value >> 8) === 0; +``` -#### Trademark Notice +See also `QRMatrix::set()`, `QRMatrix::check()` and [`QRMatrix` constants](#qrmatrix-constants). -The word "QR Code" is registered trademark of *DENSO WAVE INCORPORATED*
-http://www.denso-wave.com/qrcode/faqpatent-e.html +To map the values and properly render the modules for the given `QROutputInterface`, it's necessary to overwrite the default values: +```php +$options = new QROptions; -### Framework Integration -- Drupal [Google Authenticator Login `ga_login`](https://www.drupal.org/project/ga_login) -- WordPress [`wp-two-factor-auth`](https://github.com/sjinks/wp-two-factor-auth) -- WordPress [Simple 2FA `simple-2fa`](https://wordpress.org/plugins/simple-2fa/) -- WoltLab Suite [two-step-verification](http://pluginstore.woltlab.com/file/3007-two-step-verification/) -- [Cachet](https://github.com/CachetHQ/Cachet) +// for HTML, SVG and ImageMagick +$options->moduleValues = [ + // finder + QRMatrix::M_FINDER_DARK => '#A71111', // dark (true) + QRMatrix::M_FINDER_DOT_DARK => '#A71111', // dark (true) + QRMatrix::M_FINDER => '#FFBFBF', // light (false) + // alignment + QRMatrix::M_ALIGNMENT_DARK => '#A70364', + QRMatrix::M_ALIGNMENT => '#FFC9C9', + // timing + QRMatrix::M_TIMING_DARK => '#98005D', + QRMatrix::M_TIMING => '#FFB8E9', + // format + QRMatrix::M_FORMAT_DARK => '#003804', + QRMatrix::M_FORMAT => '#00FB12', + // version + QRMatrix::M_VERSION_DARK => '#650098', + QRMatrix::M_VERSION => '#E0B8FF', + // data + QRMatrix::M_DATA_DARK => '#4A6000', + QRMatrix::M_DATA => '#ECF9BE', + // darkmodule + QRMatrix::M_DARKMODULE_DARK => '#080063', + // separator + QRMatrix::M_SEPARATOR => '#AFBFBF', + // quietzone + QRMatrix::M_QUIETZONE => '#FFFFFF', +]; + +// for the image output types +$options->moduleValues = [ + QRMatrix::M_DATA_DARK => [0, 0, 0], + // ... +]; + +// for string/text output +$options->moduleValues = [ + QRMatrix::M_DATA_DARK => '#', + // ... +]; +``` + + +## Public API + +### `QRCode` API +#### Methods +| method | return | description | +|---------------------------------------------|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------| +| `__construct(QROptions $options = null)` | - | see [`SettingsContainerInterface`](https://github.com/chillerlan/php-settings-container/blob/main/src/SettingsContainerInterface.php) | +| `render(string $data, string $file = null)` | mixed, `QROutputInterface::dump()` | renders a QR Code for the given `$data` and `QROptions`, saves `$file` optional | +| `getMatrix(string $data)` | `QRMatrix` | returns a `QRMatrix` object for the given `$data` and current `QROptions` | +| `initDataInterface(string $data)` | `QRDataInterface` | returns a fresh `QRDataInterface` for the given `$data` | +| `isNumber(string $string)` | bool | checks if a string qualifies for `Number` | +| `isAlphaNum(string $string)` | bool | checks if a string qualifies for `AlphaNum` | +| `isKanji(string $string)` | bool | checks if a string qualifies for `Kanji` | +| `isByte(string $string)` | bool | checks if a string is non-empty | + +#### Constants +| name | description | +|------------------------------------------------------------|------------------------------------------------------------------------------| +| `VERSION_AUTO` | `QROptions::$version` | +| `MASK_PATTERN_AUTO` | `QROptions::$maskPattern` | +| `OUTPUT_MARKUP_SVG`, `OUTPUT_MARKUP_HTML` | `QROptions::$outputType` markup | +| `OUTPUT_IMAGE_PNG`, `OUTPUT_IMAGE_JPG`, `OUTPUT_IMAGE_GIF` | `QROptions::$outputType` image | +| `OUTPUT_STRING_JSON`, `OUTPUT_STRING_TEXT` | `QROptions::$outputType` string | +| `OUTPUT_IMAGICK` | `QROptions::$outputType` ImageMagick | +| `OUTPUT_FPDF` | `QROptions::$outputType` PDF, using [FPDF](https://github.com/setasign/fpdf) | +| `OUTPUT_CUSTOM` | `QROptions::$outputType`, requires `QROptions::$outputInterface` | +| `ECC_L`, `ECC_M`, `ECC_Q`, `ECC_H`, | ECC-Level: 7%, 15%, 25%, 30% in `QROptions::$eccLevel` | +| `DATA_NUMBER`, `DATA_ALPHANUM`, `DATA_BYTE`, `DATA_KANJI` | `QRDataInterface::$datamode` | + +### `QRMatrix` API + +#### Methods +| method | return | description | +|-------------------------------------------------|------------|-------------------------------------------------------------------------------------------------------| +| `__construct(int $version, int $eclevel)` | - | - | +| `init(int $maskPattern, bool $test = null)` | `QRMatrix` | | +| `matrix()` | array | the internal matrix representation as a 2 dimensional array | +| `version()` | int | the current QR Code version | +| `eccLevel()` | int | current ECC level | +| `maskPattern()` | int | the used mask pattern | +| `size()` | int | the absoulute size of the matrix, including quiet zone (if set). `$version * 4 + 17 + 2 * $quietzone` | +| `get(int $x, int $y)` | int | returns the value of the module | +| `set(int $x, int $y, bool $value, int $M_TYPE)` | `QRMatrix` | sets the `$M_TYPE` value for the module | +| `check(int $x, int $y)` | bool | checks whether a module is true (dark) or false (light) | + +#### Constants +| name | description | +|----------------------|---------------------------------------------------------------| +| `M_NULL` | module not set (should never appear. if so, there's an error) | +| `M_DARKMODULE` | once per matrix at `$xy = [8, 4 * $version + 9]` | +| `M_DARKMODULE_LIGHT` | (reserved for reflectance reversal) | +| `M_DATA` | the actual encoded data | +| `M_DATA_DARK` | | +| `M_FINDER` | the 7x7 finder patterns | +| `M_FINDER_DARK` | | +| `M_FINDER_DOT` | the 3x3 dot inside the finder patterns | +| `M_FINDER_DOT_LIGHT` | (reserved for reflectance reversal) | +| `M_SEPARATOR` | separator lines around the finder patterns | +| `M_SEPARATOR_DARK` | (reserved for reflectance reversal) | +| `M_ALIGNMENT` | the 5x5 alignment patterns | +| `M_ALIGNMENT_DARK` | | +| `M_TIMING` | the timing pattern lines | +| `M_TIMING_DARK` | | +| `M_FORMAT` | format information pattern | +| `M_FORMAT_DARK` | | +| `M_VERSION` | version information pattern | +| `M_VERSION_DARK` | | +| `M_QUIETZONE` | margin around the QR Code | +| `M_QUIETZONE_DARK` | (reserved for reflectance reversal) | +| `M_LOGO` | space for a logo image | +| `M_LOGO_DARK` | (reserved for reflectance reversal) | + +### `QROptions` API + +#### Properties +| property | type | default | allowed | description | +|------------------------|--------|-----------------------------|---------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------| +| `$version` | int | `QRCode::VERSION_AUTO` | 1...40 | the [QR Code version number](http://www.qrcode.com/en/about/version.html) | +| `$versionMin` | int | 1 | 1...40 | Minimum QR version (if `$version = QRCode::VERSION_AUTO`) | +| `$versionMax` | int | 40 | 1...40 | Maximum QR version (if `$version = QRCode::VERSION_AUTO`) | +| `$eccLevel` | int | `QRCode::ECC_L` | `QRCode::ECC_X` | Error correct level, where X = L (7%), M (15%), Q (25%), H (30%) | +| `$maskPattern` | int | `QRCode::MASK_PATTERN_AUTO` | 0...7 | Mask Pattern to use | +| `$addQuietzone` | bool | `true` | - | Add a "quiet zone" (margin) according to the QR code spec | +| `$quietzoneSize` | int | 4 | clamped to 0 ... `$matrixSize / 2` | Size of the quiet zone | +| `$dataModeOverride` | string | `null` | `Number`, `AlphaNum`, `Kanji`, `Byte` | allows overriding the data type detection | +| `$outputType` | string | `QRCode::OUTPUT_IMAGE_PNG` | `QRCode::OUTPUT_*` | built-in output type | +| `$outputInterface` | string | `null` | * | FQCN of the custom `QROutputInterface` if `QROptions::$outputType` is set to `QRCode::OUTPUT_CUSTOM` | +| `$cachefile` | string | `null` | * | optional cache file path | +| `$eol` | string | `PHP_EOL` | * | newline string (HTML, SVG, TEXT) | +| `$scale` | int | 5 | * | size of a QR code pixel (SVG, IMAGE_*), HTML -> via CSS | +| `$cssClass` | string | `null` | * | a common css class | +| `$svgOpacity` | float | 1.0 | 0...1 | | +| `$svgDefs` | string | * | * | anything between [``](https://developer.mozilla.org/docs/Web/SVG/Element/defs) | +| `$svgViewBoxSize` | int | `null` | * | a positive integer which defines width/height of the [viewBox attribute](https://css-tricks.com/scale-svg/#article-header-id-3) | +| `$textDark` | string | '██' | * | string substitute for dark | +| `$textLight` | string | '░░' | * | string substitute for light | +| `$markupDark` | string | '#000' | * | markup substitute for dark (CSS value) | +| `$markupLight` | string | '#fff' | * | markup substitute for light (CSS value) | +| `$imageBase64` | bool | `true` | - | whether to return the image data as base64 or raw like from `file_get_contents()` | +| `$imageTransparent` | bool | `true` | - | toggle transparency (no jpeg support) | +| `$imageTransparencyBG` | array | `[255, 255, 255]` | `[R, G, B]` | the RGB values for the transparent color, see [`imagecolortransparent()`](http://php.net/manual/function.imagecolortransparent.php) | +| `$pngCompression` | int | -1 | -1 ... 9 | `imagepng()` compression level, -1 = auto | +| `$jpegQuality` | int | 85 | 0 - 100 | `imagejpeg()` quality | +| `$imagickFormat` | string | 'png' | * | ImageMagick output type, see `Imagick::setType()` | +| `$imagickBG` | string | `null` | * | ImageMagick background color, see `ImagickPixel::__construct()` | +| `$moduleValues` | array | `null` | * | Module values map, see [[Custom output interface]] and `QROutputInterface::DEFAULT_MODULE_VALUES` | + +## Framework Integration +- Drupal: + - [Google Authenticator Login `ga_login`](https://www.drupal.org/project/ga_login) +- Symfony + - [phpqrcode-bundle](https://github.com/jonasarts/phpqrcode-bundle) +- WordPress: + - [`wp-two-factor-auth`](https://github.com/sjinks/wp-two-factor-auth) + - [`simple-2fa`](https://wordpress.org/plugins/simple-2fa/) + - [`wordpress-seo`](https://github.com/Yoast/wordpress-seo) + - [`floating-share-button`](https://github.com/qriouslad/floating-share-button) +- WoltLab Suite + - [two-step-verification](http://pluginstore.woltlab.com/file/3007-two-step-verification/) - [Appwrite](https://github.com/appwrite/appwrite) +- [Cachet](https://github.com/CachetHQ/Cachet) +- [twill](https://github.com/area17/twill) - other uses: [dependents](https://github.com/chillerlan/php-qrcode/network/dependents) / [packages](https://github.com/chillerlan/php-qrcode/network/dependents?dependent_type=PACKAGE) - +## Shameless advertising Hi, please check out my other projects that are way cooler than qrcodes! - [php-oauth-core](https://github.com/chillerlan/php-oauth-core) - an OAuth 1/2 client library along with a bunch of [providers](https://github.com/chillerlan/php-oauth-providers) - [php-httpinterface](https://github.com/chillerlan/php-httpinterface) - a PSR-7/15/17/18 implemetation - [php-database](https://github.com/chillerlan/php-database) - a database client & querybuilder for MySQL, Postgres, SQLite, MSSQL, Firebird +## Disclaimer! +I don't take responsibility for molten CPUs, misled applications, failed log-ins etc.. Use at your own risk! + +### Trademark Notice + +The word "QR Code" is a registered trademark of *DENSO WAVE INCORPORATED*
+https://www.qrcode.com/en/faq.html#patentH2Title diff --git a/vendor/chillerlan/php-qrcode/composer.json b/vendor/chillerlan/php-qrcode/composer.json index c2299a3..97d8bbe 100644 --- a/vendor/chillerlan/php-qrcode/composer.json +++ b/vendor/chillerlan/php-qrcode/composer.json @@ -1,6 +1,6 @@ { "name": "chillerlan/php-qrcode", - "description": "A QR code generator. PHP 7.2+", + "description": "A QR code generator with a user friendly API. PHP 7.4+", "homepage": "https://github.com/chillerlan/php-qrcode", "license": "MIT", "minimum-stability": "stable", @@ -24,17 +24,21 @@ } ], "require": { - "php": "^7.2", + "php": "^7.4 || ^8.0", "ext-mbstring": "*", - "chillerlan/php-settings-container": "^1.2" + "chillerlan/php-settings-container": "^2.1.4 || ^3.1" }, "require-dev": { - "phpunit/phpunit": "^8.5", - "setasign/fpdf": "^1.8.2" + "phan/phan": "^5.4", + "phpmd/phpmd": "^2.15", + "phpunit/phpunit": "^9.6", + "setasign/fpdf": "^1.8.2", + "squizlabs/php_codesniffer": "^3.8" }, "suggest": { "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", - "setasign/fpdf": "Required to use the QR FPDF output." + "setasign/fpdf": "Required to use the QR FPDF output.", + "simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code" }, "autoload": { "psr-4": { @@ -43,9 +47,16 @@ }, "autoload-dev": { "psr-4": { - "chillerlan\\QRCodePublic\\": "public/", - "chillerlan\\QRCodeTest\\": "tests/", - "chillerlan\\QRCodeExamples\\": "examples/" + "chillerlan\\QRCodeTest\\": "tests/" } + }, + "scripts": { + "phpunit": "@php vendor/bin/phpunit", + "phan": "@php vendor/bin/phan" + }, + "config": { + "lock": false, + "sort-packages": true, + "platform-check": true } } diff --git a/vendor/chillerlan/php-qrcode/examples/MyCustomOutput.php b/vendor/chillerlan/php-qrcode/examples/MyCustomOutput.php deleted file mode 100644 index 3c01f86..0000000 --- a/vendor/chillerlan/php-qrcode/examples/MyCustomOutput.php +++ /dev/null @@ -1,36 +0,0 @@ - - * @copyright 2017 Smiley - * @license MIT - */ - -namespace chillerlan\QRCodeExamples; - -use chillerlan\QRCode\Output\QROutputAbstract; - -class MyCustomOutput extends QROutputAbstract{ - - protected function setModuleValues():void{ - // TODO: Implement setModuleValues() method. - } - - public function dump(string $file = null){ - - $output = ''; - - for($row = 0; $row < $this->moduleCount; $row++){ - for($col = 0; $col < $this->moduleCount; $col++){ - $output .= (int)$this->matrix->check($col, $row); - } - } - - return $output; - } - -} diff --git a/vendor/chillerlan/php-qrcode/examples/QRImageWithLogo.php b/vendor/chillerlan/php-qrcode/examples/QRImageWithLogo.php deleted file mode 100644 index f9d94ae..0000000 --- a/vendor/chillerlan/php-qrcode/examples/QRImageWithLogo.php +++ /dev/null @@ -1,81 +0,0 @@ - - * @copyright 2020 smiley - * @license MIT - * - * @noinspection PhpComposerExtensionStubsInspection - */ - -namespace chillerlan\QRCodeExamples; - -use chillerlan\QRCode\Output\{QRCodeOutputException, QRImage}; - -use function imagecopyresampled, imagecreatefrompng, imagesx, imagesy, is_file, is_readable; - -/** - * @property \chillerlan\QRCodeExamples\LogoOptions $options - */ -class QRImageWithLogo extends QRImage{ - - /** - * @param string|null $file - * @param string|null $logo - * - * @return string - * @throws \chillerlan\QRCode\Output\QRCodeOutputException - */ - public function dump(string $file = null, string $logo = null):string{ - // set returnResource to true to skip further processing for now - $this->options->returnResource = true; - - // of course you could accept other formats too (such as resource or Imagick) - // i'm not checking for the file type either for simplicity reasons (assuming PNG) - if(!is_file($logo) || !is_readable($logo)){ - throw new QRCodeOutputException('invalid logo'); - } - - $this->matrix->setLogoSpace( - $this->options->logoWidth, - $this->options->logoHeight - // not utilizing the position here - ); - - // there's no need to save the result of dump() into $this->image here - parent::dump($file); - - $im = imagecreatefrompng($logo); - - // get logo image size - $w = imagesx($im); - $h = imagesy($im); - - // set new logo size, leave a border of 1 module - $lw = ($this->options->logoWidth - 2) * $this->options->scale; - $lh = ($this->options->logoHeight - 2) * $this->options->scale; - - // get the qrcode size - $ql = $this->matrix->size() * $this->options->scale; - - // scale the logo and copy it over. done! - imagecopyresampled($this->image, $im, ($ql - $lw) / 2, ($ql - $lh) / 2, 0, 0, $lw, $lh, $w, $h); - - $imageData = $this->dumpImage(); - - if($file !== null){ - $this->saveToFile($imageData, $file); - } - - if($this->options->imageBase64){ - $imageData = 'data:image/'.$this->options->outputType.';base64,'.base64_encode($imageData); - } - - return $imageData; - } - -} diff --git a/vendor/chillerlan/php-qrcode/examples/QRImageWithText.php b/vendor/chillerlan/php-qrcode/examples/QRImageWithText.php deleted file mode 100644 index 5ca572f..0000000 --- a/vendor/chillerlan/php-qrcode/examples/QRImageWithText.php +++ /dev/null @@ -1,104 +0,0 @@ - - * @copyright 2019 smiley - * @license MIT - * - * @noinspection PhpComposerExtensionStubsInspection - */ - -namespace chillerlan\QRCodeExamples; - -use chillerlan\QRCode\Output\QRImage; -use function base64_encode, imagechar, imagecolorallocate, imagecolortransparent, imagecopymerge, imagecreatetruecolor, - imagedestroy, imagefilledrectangle, imagefontwidth, in_array, round, str_split, strlen; - -class QRImageWithText extends QRImage{ - - /** - * @param string|null $file - * @param string|null $text - * - * @return string - */ - public function dump(string $file = null, string $text = null):string{ - $this->image = imagecreatetruecolor($this->length, $this->length); - $background = imagecolorallocate($this->image, ...$this->options->imageTransparencyBG); - - if((bool)$this->options->imageTransparent && in_array($this->options->outputType, $this::TRANSPARENCY_TYPES, true)){ - imagecolortransparent($this->image, $background); - } - - imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $background); - - foreach($this->matrix->matrix() as $y => $row){ - foreach($row as $x => $M_TYPE){ - $this->setPixel($x, $y, $this->moduleValues[$M_TYPE]); - } - } - - // render text output if a string is given - if($text !== null){ - $this->addText($text); - } - - $imageData = $this->dumpImage($file); - - if((bool)$this->options->imageBase64){ - $imageData = 'data:image/'.$this->options->outputType.';base64,'.base64_encode($imageData); - } - - return $imageData; - } - - /** - * @param string $text - */ - protected function addText(string $text):void{ - // save the qrcode image - $qrcode = $this->image; - - // options things - $textSize = 3; // see imagefontheight() and imagefontwidth() - $textBG = [200, 200, 200]; - $textColor = [50, 50, 50]; - - $bgWidth = $this->length; - $bgHeight = $bgWidth + 20; // 20px extra space - - // create a new image with additional space - $this->image = imagecreatetruecolor($bgWidth, $bgHeight); - $background = imagecolorallocate($this->image, ...$textBG); - - // allow transparency - if((bool)$this->options->imageTransparent && in_array($this->options->outputType, $this::TRANSPARENCY_TYPES, true)){ - imagecolortransparent($this->image, $background); - } - - // fill the background - imagefilledrectangle($this->image, 0, 0, $bgWidth, $bgHeight, $background); - - // copy over the qrcode - imagecopymerge($this->image, $qrcode, 0, 0, 0, 0, $this->length, $this->length, 100); - imagedestroy($qrcode); - - $fontColor = imagecolorallocate($this->image, ...$textColor); - $w = imagefontwidth($textSize); - $x = round(($bgWidth - strlen($text) * $w) / 2); - - // loop through the string and draw the letters - foreach(str_split($text) as $i => $chr){ - imagechar($this->image, $textSize, $i * $w + $x, $this->length, $chr, $fontColor); - } - } - -} diff --git a/vendor/chillerlan/php-qrcode/examples/custom_output.php b/vendor/chillerlan/php-qrcode/examples/custom_output.php deleted file mode 100644 index 71ea626..0000000 --- a/vendor/chillerlan/php-qrcode/examples/custom_output.php +++ /dev/null @@ -1,38 +0,0 @@ - - * @copyright 2017 Smiley - * @license MIT - */ - -namespace chillerlan\QRCodeExamples; - -use chillerlan\QRCode\{QRCode, QROptions}; - -require_once __DIR__.'/../vendor/autoload.php'; - -$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s'; - -// invoke the QROutputInterface manually -$options = new QROptions([ - 'version' => 5, - 'eccLevel' => QRCode::ECC_L, -]); - -$qrOutputInterface = new MyCustomOutput($options, (new QRCode($options))->getMatrix($data)); - -var_dump($qrOutputInterface->dump()); - - -// or just -$options = new QROptions([ - 'version' => 5, - 'eccLevel' => QRCode::ECC_L, - 'outputType' => QRCode::OUTPUT_CUSTOM, - 'outputInterface' => MyCustomOutput::class, -]); - -var_dump((new QRCode($options))->render($data)); diff --git a/vendor/chillerlan/php-qrcode/examples/example_image.png b/vendor/chillerlan/php-qrcode/examples/example_image.png deleted file mode 100644 index b4a80f2..0000000 Binary files a/vendor/chillerlan/php-qrcode/examples/example_image.png and /dev/null differ diff --git a/vendor/chillerlan/php-qrcode/examples/example_svg.png b/vendor/chillerlan/php-qrcode/examples/example_svg.png deleted file mode 100644 index f1e7b32..0000000 Binary files a/vendor/chillerlan/php-qrcode/examples/example_svg.png and /dev/null differ diff --git a/vendor/chillerlan/php-qrcode/examples/fpdf.php b/vendor/chillerlan/php-qrcode/examples/fpdf.php deleted file mode 100644 index 9c690a7..0000000 --- a/vendor/chillerlan/php-qrcode/examples/fpdf.php +++ /dev/null @@ -1,47 +0,0 @@ - 7, - 'outputType' => QRCode::OUTPUT_FPDF, - 'eccLevel' => QRCode::ECC_L, - 'scale' => 5, - 'imageBase64' => false, - 'moduleValues' => [ - // finder - 1536 => [0, 63, 255], // dark (true) - 6 => [255, 255, 255], // light (false), white is the transparency color and is enabled by default - // alignment - 2560 => [255, 0, 255], - 10 => [255, 255, 255], - // timing - 3072 => [255, 0, 0], - 12 => [255, 255, 255], - // format - 3584 => [67, 191, 84], - 14 => [255, 255, 255], - // version - 4096 => [62, 174, 190], - 16 => [255, 255, 255], - // data - 1024 => [0, 0, 0], - 4 => [255, 255, 255], - // darkmodule - 512 => [0, 0, 0], - // separator - 8 => [255, 255, 255], - // quietzone - 18 => [255, 255, 255], - ], -]); - -\header('Content-type: application/pdf'); - -echo (new QRCode($options))->render($data); diff --git a/vendor/chillerlan/php-qrcode/examples/html.php b/vendor/chillerlan/php-qrcode/examples/html.php deleted file mode 100644 index aa5305d..0000000 --- a/vendor/chillerlan/php-qrcode/examples/html.php +++ /dev/null @@ -1,102 +0,0 @@ - - * @copyright 2017 Smiley - * @license MIT - */ - -namespace chillerlan\QRCodeExamples; - -use chillerlan\QRCode\{QRCode, QROptions}; - -require_once '../vendor/autoload.php'; - -header('Content-Type: text/html; charset=utf-8'); - -?> - - - - - - QRCode test - - - -
- 5, - 'outputType' => QRCode::OUTPUT_MARKUP_HTML, - 'eccLevel' => QRCode::ECC_L, - 'moduleValues' => [ - // finder - 1536 => '#A71111', // dark (true) - 6 => '#FFBFBF', // light (false) - // alignment - 2560 => '#A70364', - 10 => '#FFC9C9', - // timing - 3072 => '#98005D', - 12 => '#FFB8E9', - // format - 3584 => '#003804', - 14 => '#00FB12', - // version - 4096 => '#650098', - 16 => '#E0B8FF', - // data - 1024 => '#4A6000', - 4 => '#ECF9BE', - // darkmodule - 512 => '#080063', - // separator - 8 => '#AFBFBF', - // quietzone - 18 => '#FFFFFF', - ], - ]); - - echo (new QRCode($options))->render($data); - -?> -
- - - - - diff --git a/vendor/chillerlan/php-qrcode/examples/image.php b/vendor/chillerlan/php-qrcode/examples/image.php deleted file mode 100644 index 89ba2a9..0000000 --- a/vendor/chillerlan/php-qrcode/examples/image.php +++ /dev/null @@ -1,60 +0,0 @@ - - * @copyright 2017 Smiley - * @license MIT - */ - -namespace chillerlan\QRCodeExamples; - -use chillerlan\QRCode\{QRCode, QROptions}; - -require_once __DIR__.'/../vendor/autoload.php'; - -$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s'; - -$options = new QROptions([ - 'version' => 7, - 'outputType' => QRCode::OUTPUT_IMAGE_PNG, - 'eccLevel' => QRCode::ECC_L, - 'scale' => 5, - 'imageBase64' => false, - 'moduleValues' => [ - // finder - 1536 => [0, 63, 255], // dark (true) - 6 => [255, 255, 255], // light (false), white is the transparency color and is enabled by default - // alignment - 2560 => [255, 0, 255], - 10 => [255, 255, 255], - // timing - 3072 => [255, 0, 0], - 12 => [255, 255, 255], - // format - 3584 => [67, 191, 84], - 14 => [255, 255, 255], - // version - 4096 => [62, 174, 190], - 16 => [255, 255, 255], - // data - 1024 => [0, 0, 0], - 4 => [255, 255, 255], - // darkmodule - 512 => [0, 0, 0], - // separator - 8 => [255, 255, 255], - // quietzone - 18 => [255, 255, 255], - ], -]); - -header('Content-type: image/png'); - -echo (new QRCode($options))->render($data); - - - - - diff --git a/vendor/chillerlan/php-qrcode/examples/imageWithLogo.php b/vendor/chillerlan/php-qrcode/examples/imageWithLogo.php deleted file mode 100644 index 987e10c..0000000 --- a/vendor/chillerlan/php-qrcode/examples/imageWithLogo.php +++ /dev/null @@ -1,44 +0,0 @@ - - * @copyright 2020 smiley - * @license MIT - */ - -namespace chillerlan\QRCodeExamples; - -use chillerlan\QRCode\{QRCode, QROptions}; - -require_once __DIR__.'/../vendor/autoload.php'; - -$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s'; -/** - * @property int $logoWidth - * @property int $logoHeight - * - * @noinspection PhpIllegalPsrClassPathInspection - */ -class LogoOptions extends QROptions{ - protected $logoWidth; - protected $logoHeight; -} - -$options = new LogoOptions; - -$options->version = 7; -$options->eccLevel = QRCode::ECC_H; -$options->imageBase64 = false; -$options->logoWidth = 13; -$options->logoHeight = 13; -$options->scale = 5; -$options->imageTransparent = false; - -header('Content-type: image/png'); - -$qrOutputInterface = new QRImageWithLogo($options, (new QRCode($options))->getMatrix($data)); - -// dump the output, with an additional logo -echo $qrOutputInterface->dump(null, __DIR__.'/octocat.png'); diff --git a/vendor/chillerlan/php-qrcode/examples/imageWithText.php b/vendor/chillerlan/php-qrcode/examples/imageWithText.php deleted file mode 100644 index 050781c..0000000 --- a/vendor/chillerlan/php-qrcode/examples/imageWithText.php +++ /dev/null @@ -1,33 +0,0 @@ - - * @copyright 2019 Smiley - * @license MIT - */ - -namespace chillerlan\QRCodeExamples; - -use chillerlan\QRCode\{QRCode, QROptions}; - -require_once __DIR__.'/../vendor/autoload.php'; - -$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s'; - -$options = new QROptions([ - 'version' => 7, - 'outputType' => QRCode::OUTPUT_IMAGE_PNG, - 'scale' => 3, - 'imageBase64' => false, -]); - -header('Content-type: image/png'); - -$qrOutputInterface = new QRImageWithText($options, (new QRCode($options))->getMatrix($data)); - -// dump the output, with additional text -echo $qrOutputInterface->dump(null, 'example text'); diff --git a/vendor/chillerlan/php-qrcode/examples/imagick.php b/vendor/chillerlan/php-qrcode/examples/imagick.php deleted file mode 100644 index 6bec4d0..0000000 --- a/vendor/chillerlan/php-qrcode/examples/imagick.php +++ /dev/null @@ -1,59 +0,0 @@ - - * @copyright 2017 Smiley - * @license MIT - */ - -namespace chillerlan\QRCodeExamples; - -use chillerlan\QRCode\{QRCode, QROptions}; - -require_once __DIR__.'/../vendor/autoload.php'; - -$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s'; - -$options = new QROptions([ - 'version' => 7, - 'outputType' => QRCode::OUTPUT_IMAGICK, - 'eccLevel' => QRCode::ECC_L, - 'scale' => 5, - 'moduleValues' => [ - // finder - 1536 => '#A71111', // dark (true) - 6 => '#FFBFBF', // light (false) - // alignment - 2560 => '#A70364', - 10 => '#FFC9C9', - // timing - 3072 => '#98005D', - 12 => '#FFB8E9', - // format - 3584 => '#003804', - 14 => '#00FB12', - // version - 4096 => '#650098', - 16 => '#E0B8FF', - // data - 1024 => '#4A6000', - 4 => '#ECF9BE', - // darkmodule - 512 => '#080063', - // separator - 8 => '#DDDDDD', - // quietzone - 18 => '#DDDDDD', - ], -]); - -header('Content-type: image/png'); - -echo (new QRCode($options))->render($data); - - - - - diff --git a/vendor/chillerlan/php-qrcode/examples/octocat.png b/vendor/chillerlan/php-qrcode/examples/octocat.png deleted file mode 100644 index f9050b9..0000000 Binary files a/vendor/chillerlan/php-qrcode/examples/octocat.png and /dev/null differ diff --git a/vendor/chillerlan/php-qrcode/examples/svg.php b/vendor/chillerlan/php-qrcode/examples/svg.php deleted file mode 100644 index a7a159d..0000000 --- a/vendor/chillerlan/php-qrcode/examples/svg.php +++ /dev/null @@ -1,77 +0,0 @@ - - * @copyright 2017 Smiley - * @license MIT - */ - -namespace chillerlan\QRCodeExamples; - -use chillerlan\QRCode\{QRCode, QROptions}; - -require_once __DIR__.'/../vendor/autoload.php'; - -$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s'; -$gzip = true; - -$options = new QROptions([ - 'version' => 7, - 'outputType' => QRCode::OUTPUT_MARKUP_SVG, - 'eccLevel' => QRCode::ECC_L, - 'svgViewBoxSize' => 530, - 'addQuietzone' => true, - 'cssClass' => 'my-css-class', - 'svgOpacity' => 1.0, - 'svgDefs' => ' - - - - - - - - - ', - 'moduleValues' => [ - // finder - 1536 => 'url(#g1)', // dark (true) - 6 => '#fff', // light (false) - // alignment - 2560 => 'url(#g1)', - 10 => '#fff', - // timing - 3072 => 'url(#g1)', - 12 => '#fff', - // format - 3584 => 'url(#g1)', - 14 => '#fff', - // version - 4096 => 'url(#g1)', - 16 => '#fff', - // data - 1024 => 'url(#g2)', - 4 => '#fff', - // darkmodule - 512 => 'url(#g1)', - // separator - 8 => '#fff', - // quietzone - 18 => '#fff', - ], -]); - -$qrcode = (new QRCode($options))->render($data); - -header('Content-type: image/svg+xml'); - -if($gzip === true){ - header('Vary: Accept-Encoding'); - header('Content-Encoding: gzip'); - $qrcode = gzencode($qrcode ,9); -} -echo $qrcode; - - diff --git a/vendor/chillerlan/php-qrcode/examples/text.php b/vendor/chillerlan/php-qrcode/examples/text.php deleted file mode 100644 index 9bdf154..0000000 --- a/vendor/chillerlan/php-qrcode/examples/text.php +++ /dev/null @@ -1,68 +0,0 @@ - - * @copyright 2017 Smiley - * @license MIT - */ - -namespace chillerlan\QRCodeExamples; - -use chillerlan\QRCode\{QRCode, QROptions}; - -require_once __DIR__.'/../vendor/autoload.php'; - -$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s'; - -$options = new QROptions([ - 'version' => 5, - 'outputType' => QRCode::OUTPUT_STRING_TEXT, - 'eccLevel' => QRCode::ECC_L, -]); - -//
 to view it in a browser
-echo '
'.(new QRCode($options))->render($data).'
'; - - -// custom values -$options = new QROptions([ - 'version' => 5, - 'outputType' => QRCode::OUTPUT_STRING_TEXT, - 'eccLevel' => QRCode::ECC_L, - 'moduleValues' => [ - // finder - 1536 => 'A', // dark (true) - 6 => 'a', // light (false) - // alignment - 2560 => 'B', - 10 => 'b', - // timing - 3072 => 'C', - 12 => 'c', - // format - 3584 => 'D', - 14 => 'd', - // version - 4096 => 'E', - 16 => 'e', - // data - 1024 => 'F', - 4 => 'f', - // darkmodule - 512 => 'G', - // separator - 8 => 'h', - // quietzone - 18 => 'i', - ], -]); - -//
 to view it in a browser
-echo '
'.(new QRCode($options))->render($data).'
'; - - - - - diff --git a/vendor/chillerlan/php-qrcode/phpdoc.xml b/vendor/chillerlan/php-qrcode/phpdoc.xml deleted file mode 100644 index d191e98..0000000 --- a/vendor/chillerlan/php-qrcode/phpdoc.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - public/docs - - - public/docs - - - src - - -