| 1 |
<?php |
| 2 |
/* |
| 3 |
This class is for extending the WooCommerce product editor with Vipps-specific settings. |
| 4 |
NT 2024-05-16 |
| 5 |
For WP-specific interactions. |
| 6 |
|
| 7 |
|
| 8 |
This file is part of the plugin Pay with Vipps and MobilePay for WooCommerce |
| 9 |
Copyright (c) 2023 WP-Hosting AS |
| 10 |
|
| 11 |
MIT License |
| 12 |
|
| 13 |
Copyright (c) 2023 WP-Hosting AS |
| 14 |
|
| 15 |
Permission is hereby granted, free of charge, to any person obtaining a copy |
| 16 |
of this software and associated documentation files (the "Software"), to deal |
| 17 |
in the Software without restriction, including without limitation the rights |
| 18 |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 19 |
copies of the Software, and to permit persons to whom the Software is |
| 20 |
furnished to do so, subject to the following conditions: |
| 21 |
|
| 22 |
The above copyright notice and this permission notice shall be included in all |
| 23 |
copies or substantial portions of the Software. |
| 24 |
|
| 25 |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 26 |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 27 |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 28 |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 29 |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 30 |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 31 |
SOFTWARE. |
| 32 |
|
| 33 |
|
| 34 |
*/ |
| 35 |
if (!defined('ABSPATH')) { |
| 36 |
exit; // Exit if accessed directly |
| 37 |
} |
| 38 |
|
| 39 |
class VippsWCProductEditorV2 |
| 40 |
{ |
| 41 |
private static $instance = null; |
| 42 |
private $gw = null; |
| 43 |
|
| 44 |
public static function instance() |
| 45 |
{ |
| 46 |
if (!static::$instance) |
| 47 |
static::$instance = new VippsWCProductEditorV2(); |
| 48 |
return static::$instance; |
| 49 |
} |
| 50 |
|
| 51 |
public function gateway() |
| 52 |
{ |
| 53 |
if ($this->gw) |
| 54 |
return $this->gw; |
| 55 |
$this->gw = Vipps::instance()->gateway(); |
| 56 |
return $this->gw; |
| 57 |
} |
| 58 |
|
| 59 |
public static function register_hooks() |
| 60 |
{ |
| 61 |
$VippsWCProductEditorV2 = static::instance(); |
| 62 |
add_action('init', array($VippsWCProductEditorV2, 'init')); |
| 63 |
} |
| 64 |
|
| 65 |
public function init() |
| 66 |
{ |
| 67 |
add_action('wp_ajax_vipps_generate_unused_shareable_meta_key', array($this, 'ajax_vipps_generate_unused_shareable_meta_key')); |
| 68 |
// Only enable the product editor extensions when the payment method is Vipps, since MobilePay is not supported as of now. |
| 69 |
// IOK 2025-09-01 no longer the case. |
| 70 |
// Inject some extra data to the product object when it is returned from the REST API. |
| 71 |
// This is useful for the product editor blocks, especially when we want to show/hide blocks based on the product's properties and/or the global settings. |
| 72 |
add_filter('woocommerce_rest_prepare_product_object', function ($response, $product) { |
| 73 |
if (empty($response->data)) { |
| 74 |
return $response; |
| 75 |
} |
| 76 |
$gw = $this->gateway(); |
| 77 |
// Check if the product can be bought with express checkout |
| 78 |
$can_be_bought_with_express_checkout = is_a($product, 'WC_Product') && $gw->product_supports_express_checkout($product); |
| 79 |
$response->data['vipps_product_can_be_bought_with_express_checkout'] = $can_be_bought_with_express_checkout; |
| 80 |
// Check if express checkout is enabled at all |
| 81 |
$response->data['vipps_global_singleproductexpress'] = $gw->get_option('singleproductexpress'); |
| 82 |
// Inject a nonce for product editor's shareable links |
| 83 |
$response->data['vipps_share_link_nonce'] = wp_create_nonce('vipps_share_link_nonce'); |
| 84 |
$response->data['vipps_buy_product_url'] = Vipps::instance()->buy_product_url(); |
| 85 |
return $response; |
| 86 |
}, 10, 2); |
| 87 |
|
| 88 |
// Initialize the Vipps/MobilePay product tab |
| 89 |
add_action('rest_api_init', function () { |
| 90 |
add_action('woocommerce_block_template_area_product-form_after_add_block_inventory', array($this, 'init_woo_vipps_product_tab')); |
| 91 |
}); |
| 92 |
} |
| 93 |
|
| 94 |
// This creates and returns an URL and a key that can be used to buy a product directly. The metadata key is stored in the product's metadata by the product editor. |
| 95 |
// Only products with these links can be bought like this; both to avoid having to create spurious orders from griefers and to ensure |
| 96 |
// that a link can be retracted if it has been printed or shared in emails with a specific price. IOK 2018-10-03 |
| 97 |
public function ajax_vipps_generate_unused_shareable_meta_key() |
| 98 |
{ |
| 99 |
check_ajax_referer('vipps_share_link_nonce', 'vipps_share_shareable_link_nonce'); |
| 100 |
Vipps::set_locale_if_in_header(); |
| 101 |
if (!current_user_can('manage_woocommerce')) { |
| 102 |
echo json_encode(array('ok' => 0, 'msg' => __('You don\'t have sufficient rights to edit this product', 'woo-vipps'))); |
| 103 |
wp_die(); |
| 104 |
} |
| 105 |
$prodid = intval($_POST['prodid']); |
| 106 |
$varid = intval($_POST['varid']); |
| 107 |
|
| 108 |
$product = ''; |
| 109 |
$variant = ''; |
| 110 |
$varname = ''; |
| 111 |
try { |
| 112 |
$product = wc_get_product($prodid); |
| 113 |
$variant = $varid ? wc_get_product($varid) : null; |
| 114 |
$varname = $variant ? $variant->get_id() : ''; |
| 115 |
if ($variant && $variant->get_sku()) { |
| 116 |
$varname .= ":" . sanitize_text_field($variant->get_sku()); |
| 117 |
} |
| 118 |
} catch (Exception $e) { |
| 119 |
echo json_encode(array('ok' => 0, 'msg' => $e->getMessage())); |
| 120 |
wp_die(); |
| 121 |
} |
| 122 |
if (!$product) { |
| 123 |
echo json_encode(array('ok' => 0, 'msg' => __('The product doesn\'t exist', 'woo-vipps'))); |
| 124 |
wp_die(); |
| 125 |
} |
| 126 |
|
| 127 |
// Find a free shareable link by generating a hash and testing it. Normally there won't be any collisions at all. |
| 128 |
$key = ''; |
| 129 |
while (!$key) { |
| 130 |
global $wpdb; |
| 131 |
$key = substr(sha1(mt_rand() . ":" . $prodid . ":" . $varid), 0, 8); |
| 132 |
$existing = $wpdb->get_row("SELECT post_id from {$wpdb->prefix}postmeta where meta_key='_vipps_shareable_link_$key' limit 1", 'ARRAY_A'); |
| 133 |
if (!empty($existing)) |
| 134 |
$key = ''; |
| 135 |
} |
| 136 |
$url = add_query_arg('pr', $key, Vipps::instance()->buy_product_url()); |
| 137 |
echo json_encode(array('ok' => 1, 'msg' => 'ok', 'product_id' => $prodid, 'variant' => $varname, 'key' => $key, 'url' => $url)); |
| 138 |
wp_die(); |
| 139 |
} |
| 140 |
|
| 141 |
|
| 142 |
public function init_woo_vipps_product_tab($general_group) |
| 143 |
{ |
| 144 |
$gw = $this->gateway(); |
| 145 |
$parent = $general_group->get_parent(); |
| 146 |
$payment_method_name = $gw->get_payment_method_name(); |
| 147 |
$company_name = Vipps::CompanyName(); |
| 148 |
|
| 149 |
// Add the Vipps/MobilePay tab |
| 150 |
$gr = $parent->add_group( |
| 151 |
[ |
| 152 |
'id' => 'woo-vipps-product-group', |
| 153 |
'order' => $general_group->get_order() + 5, |
| 154 |
'attributes' => [ |
| 155 |
'title' => $payment_method_name, |
| 156 |
], |
| 157 |
] |
| 158 |
); |
| 159 |
|
| 160 |
// Badges section |
| 161 |
$badges_section = $gr->add_section( |
| 162 |
[ |
| 163 |
'id' => 'woo-vipps-badges-section', |
| 164 |
'order' => 1, |
| 165 |
'attributes' => [ |
| 166 |
'title' => __("On-site messaging badge", 'woo-vipps'), |
| 167 |
'description' => sprintf(__('On-site messaging badges are small badges that can be added to your product pages to show that you accept %1$s payments.', 'woo-vipps'), $company_name), |
| 168 |
], |
| 169 |
] |
| 170 |
); |
| 171 |
$badges_section->add_block( |
| 172 |
[ |
| 173 |
'id' => 'woo-vipps-show-badge', |
| 174 |
'blockName' => 'woocommerce/product-select-field', |
| 175 |
'order' => 2, |
| 176 |
'attributes' => [ |
| 177 |
'label' => __('Override default settings', 'woo-vipps'), |
| 178 |
'property' => 'meta_data._vipps_show_badge', |
| 179 |
'autoFocus' => false, |
| 180 |
'help' => __('Choose a badge to show on this product', 'woo-vipps'), |
| 181 |
'options' => array( |
| 182 |
['value' => '', 'label' => __('Default setting', 'woo-vipps')], |
| 183 |
['value' => 'none', 'label' => __('No badge', 'woo-vipps')], |
| 184 |
['value' => 'white', 'label' => __('White', 'woo-vipps')], |
| 185 |
['value' => 'grey', 'label' => __('Grey', 'woo-vipps')], |
| 186 |
['value' => 'filled', 'label' => __('Filled', 'woo-vipps')], |
| 187 |
['value' => 'light', 'label' => __('Light', 'woo-vipps')], |
| 188 |
['value' => 'purple', 'label' => __('Purple', 'woo-vipps')], |
| 189 |
), |
| 190 |
], |
| 191 |
] |
| 192 |
); |
| 193 |
|
| 194 |
if ($payment_method_name == "Vipps") { |
| 195 |
// Buy now section |
| 196 |
$buy_now_section = $gr->add_section( |
| 197 |
[ |
| 198 |
'id' => 'woo-vipps-buy-now-section', |
| 199 |
'order' => 2, |
| 200 |
'attributes' => [ |
| 201 |
'title' => __("Buy Now Button", 'woo-vipps'), |
| 202 |
], |
| 203 |
] |
| 204 |
); |
| 205 |
// This block is shown if the product supports express checkout and the global setting is set to 'some' |
| 206 |
$buy_now_section->add_block( |
| 207 |
[ |
| 208 |
'id' => 'woo-vipps-buy-now-button-some', |
| 209 |
'blockName' => 'woocommerce/product-checkbox-field', |
| 210 |
'order' => 1, |
| 211 |
'hideConditions' => array( |
| 212 |
array( |
| 213 |
'expression' => 'editedProduct.vipps_global_singleproductexpress !== "some"', |
| 214 |
), |
| 215 |
), |
| 216 |
'attributes' => [ |
| 217 |
'label' => sprintf(__('Add %1$s Buy Now Button', 'woo-vipps'), $payment_method_name), |
| 218 |
'property' => 'meta_data._vipps_buy_now_button', |
| 219 |
'checkedValue' => 'yes', |
| 220 |
'help' => __('Add a Buy Now button to this product', 'woo-vipps'), |
| 221 |
'disabled' => false, |
| 222 |
'tooltip' => sprintf(__('Add a \'Buy now with %1$s\'-button to this product', 'woo-vipps'), $payment_method_name), |
| 223 |
], |
| 224 |
] |
| 225 |
); |
| 226 |
|
| 227 |
// This block is shown if the global setting is set to 'all' and the product supports express checkout |
| 228 |
$buy_now_section->add_block( |
| 229 |
[ |
| 230 |
'id' => 'woo-vipps-buy-now-button-all-not-supported', |
| 231 |
'blockName' => 'woocommerce/product-section-description', |
| 232 |
'order' => 1, |
| 233 |
'hideConditions' => array( |
| 234 |
array( |
| 235 |
'expression' => 'editedProduct.vipps_global_singleproductexpress !== "all" || editedProduct.vipps_product_can_be_bought_with_express_checkout === true', |
| 236 |
), |
| 237 |
), |
| 238 |
'attributes' => [ |
| 239 |
'content' => sprintf(__("The %1\$s settings are currently set up so all products that can be bought with Express Checkout will have a Buy Now button.", 'woo-vipps'), $payment_method_name) . ' ' . __("This product does not support express checkout, and so will not have a Buy Now button.", 'woo-vipps') |
| 240 |
], |
| 241 |
] |
| 242 |
); |
| 243 |
// This block is shown if the global setting is set to 'all' and the product supports express checkout |
| 244 |
$buy_now_section->add_block( |
| 245 |
[ |
| 246 |
'id' => 'woo-vipps-buy-now-button-all-supported', |
| 247 |
'blockName' => 'woocommerce/product-section-description', |
| 248 |
'order' => 1, |
| 249 |
'hideConditions' => array( |
| 250 |
array( |
| 251 |
'expression' => 'editedProduct.vipps_global_singleproductexpress !== "all" || editedProduct.vipps_product_can_be_bought_with_express_checkout === false', |
| 252 |
), |
| 253 |
), |
| 254 |
'attributes' => [ |
| 255 |
'content' => sprintf(__("The %1\$s settings are currently set up so all products that can be bought with Express Checkout will have a Buy Now button.", 'woo-vipps'), $payment_method_name) . ' ' . __("This product supports express checkout, and so will have a Buy Now button.", 'woo-vipps') |
| 256 |
], |
| 257 |
] |
| 258 |
); |
| 259 |
|
| 260 |
// This block is hidden if the product does not support express checkout |
| 261 |
$buy_now_section->add_block( |
| 262 |
[ |
| 263 |
'id' => 'woo-vipps-buy-now-button-none', |
| 264 |
'blockName' => 'woocommerce/product-section-description', |
| 265 |
'order' => 1, |
| 266 |
'hideConditions' => array( |
| 267 |
array( |
| 268 |
'expression' => 'editedProduct.vipps_global_singleproductexpress !== "none"', |
| 269 |
), |
| 270 |
), |
| 271 |
'attributes' => [ |
| 272 |
'content' => sprintf(__("The %1\$s settings are configured so that no products will have a Buy Now button - including this.", 'woo-vipps'), $payment_method_name), |
| 273 |
], |
| 274 |
] |
| 275 |
); |
| 276 |
|
| 277 |
// Shareable links section |
| 278 |
$qradmin = admin_url("/edit.php?post_type=vipps_qr_code"); |
| 279 |
$shareables_section = $gr->add_section( |
| 280 |
[ |
| 281 |
'id' => 'woo-vipps-shareable-links-section', |
| 282 |
'order' => 3, |
| 283 |
'attributes' => [ |
| 284 |
'title' => __("Shareable links", 'woo-vipps'), |
| 285 |
'description' => sprintf(__('Shareable links are links you can share externally on banners or other places that when followed will start %1$s of this product immediately. Maintain these links here for this product.', 'woo-vipps'), $payment_method_name), |
| 286 |
], |
| 287 |
] |
| 288 |
); |
| 289 |
$shareables_section->add_block( |
| 290 |
[ |
| 291 |
'id' => 'woo-vipps-shareable-links', |
| 292 |
'blockName' => 'woo-vipps/product-shareable-link', |
| 293 |
'order' => 1, |
| 294 |
'attributes' => [ |
| 295 |
'property' => 'meta_data._vipps_shareable_links', |
| 296 |
'title' => sprintf(__('Shareable links are links you can share externally on banners or other places that when followed will start %1$s of this product immediately. Maintain these links here for this product.', 'woo-vipps'), Vipps::ExpressCheckoutName()), |
| 297 |
'message' => sprintf(__("To create a QR code for your shareable link, we recommend copying the URL and then using the <a href='%2\$s'>%1\$s QR Api</a>", 'woo-vipps'), "Vipps", $qradmin), |
| 298 |
], |
| 299 |
] |
| 300 |
); |
| 301 |
} |
| 302 |
} |
| 303 |
} |
| 304 |
|