| 1 |
<?php |
| 2 |
/* |
| 3 |
This class is for the Vipps QR-code feature, and is implemented as its own singleton instance because this isolates that 'aspect' of the |
| 4 |
system. It is instantiated as a member of the main Vipps class. |
| 5 |
|
| 6 |
This file is part of the plugin Pay with Vipps and MobilePay for WooCommerce |
| 7 |
Copyright (c) 2022 WP-Hosting AS |
| 8 |
|
| 9 |
MIT License |
| 10 |
|
| 11 |
Copyright (c) 2022 WP-Hosting AS |
| 12 |
|
| 13 |
Permission is hereby granted, free of charge, to any person obtaining a copy |
| 14 |
of this software and associated documentation files (the "Software"), to deal |
| 15 |
in the Software without restriction, including without limitation the rights |
| 16 |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 17 |
copies of the Software, and to permit persons to whom the Software is |
| 18 |
furnished to do so, subject to the following conditions: |
| 19 |
|
| 20 |
The above copyright notice and this permission notice shall be included in all |
| 21 |
copies or substantial portions of the Software. |
| 22 |
|
| 23 |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 24 |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 25 |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 26 |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 27 |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 28 |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 29 |
SOFTWARE. |
| 30 |
*/ |
| 31 |
|
| 32 |
|
| 33 |
|
| 34 |
class VippsQRCodeController { |
| 35 |
|
| 36 |
private static $instance = null; |
| 37 |
public function __construct() { |
| 38 |
} |
| 39 |
|
| 40 |
public static function instance() { |
| 41 |
if (!static::$instance) static::$instance = new VippsQRCodeController(); |
| 42 |
return static::$instance; |
| 43 |
} |
| 44 |
|
| 45 |
public static function register_hooks() { |
| 46 |
// We can only support the QR api if we are connected to Vipps. |
| 47 |
if (!get_option('woo-vipps-configured', false)) return; |
| 48 |
// Some people may want to turn this off |
| 49 |
if (!apply_filters('woo_vipps_support_qr_codes', true)) return; |
| 50 |
|
| 51 |
$controller = static::instance(); |
| 52 |
if (is_admin()) { |
| 53 |
add_action('admin_init',array($controller,'admin_init')); |
| 54 |
add_action('admin_menu',array($controller,'admin_menu')); |
| 55 |
} |
| 56 |
add_action('init',array($controller,'init')); |
| 57 |
add_action('woocommerce_loaded', array($controller,'woocommerce_loaded')); |
| 58 |
} |
| 59 |
|
| 60 |
public function woocommerce_loaded () { |
| 61 |
} |
| 62 |
public function admin_init() { |
| 63 |
add_meta_box( '_vipps_qr_data', __( 'URL', 'woo-vipps' ), array( $this, 'metabox_url' ), 'vipps_qr_code', 'normal', 'default' ); |
| 64 |
add_action('save_post_vipps_qr_code', array($this, 'save_post'), 10, 3); |
| 65 |
add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts')); |
| 66 |
add_action('admin_footer', array($this, 'admin_footer')); |
| 67 |
|
| 68 |
add_filter('views_edit-vipps_qr_code', array($this, 'qr_list_views')); |
| 69 |
|
| 70 |
add_action('admin_post_vipps_qr_handle_unsynched', array($this, 'handle_unsynched')); |
| 71 |
|
| 72 |
add_action('in_admin_header', function () { |
| 73 |
$screen = get_current_screen(); |
| 74 |
if ($screen && $screen->id == 'vipps_qr_code') { |
| 75 |
global $post; |
| 76 |
if (!$post) return; |
| 77 |
$errors = get_post_meta($post->ID, '_vipps_qr_errors', true); |
| 78 |
if ($errors) { |
| 79 |
foreach ($errors as $err) { |
| 80 |
add_action('admin_notices', function () use ($err) { |
| 81 |
printf( '<div class="notice notice-error"><p>%s</p></div>', esc_html( $err) ); |
| 82 |
}); |
| 83 |
} |
| 84 |
delete_post_meta($post->ID, '_vipps_qr_errors'); |
| 85 |
} |
| 86 |
|
| 87 |
$vid = get_post_meta($post->ID, '_vipps_qr_id', true); |
| 88 |
if ($vid) { |
| 89 |
// This is kept in a transient, so we should not need to call out |
| 90 |
$all = $this->get_all_qr_codes_at_vipps(); |
| 91 |
if (!isset($all[$vid])) { |
| 92 |
delete_post_meta($post->ID, '_vipps_qr_img'); |
| 93 |
add_action('admin_notices', function () use ($err) { |
| 94 |
printf( '<div class="notice notice-error"><p>%s</p></div>', sprintf(__("Somehow, this QR code has disappeard at %1\$s. Save it to make it work again!", 'woo-vipps'), "Vipps")); |
| 95 |
}); |
| 96 |
} |
| 97 |
} |
| 98 |
|
| 99 |
} |
| 100 |
}); |
| 101 |
} |
| 102 |
|
| 103 |
public function admin_enqueue_scripts ($hook) { |
| 104 |
$screen = get_current_screen(); |
| 105 |
if ($screen && $screen->id == 'edit-vipps_qr_code') { |
| 106 |
wp_enqueue_script( 'jquery-ui-dialog' ); |
| 107 |
wp_enqueue_style( 'wp-jquery-ui-dialog' ); |
| 108 |
} |
| 109 |
if ($screen && $screen->id == 'vipps_qr_code') { |
| 110 |
wp_enqueue_script('wc-enhanced-select'); |
| 111 |
wp_enqueue_style('woocommerce_admin_styles'); |
| 112 |
} |
| 113 |
} |
| 114 |
|
| 115 |
// This also does some garbage collection/cleanup by deleting code-objects no longer present at Vipps. It is called on the QR code overview screen. |
| 116 |
public function get_all_qr_codes_at_vipps () { |
| 117 |
$stored = get_transient('_woo_vipps_qr_codes'); |
| 118 |
if (is_array($stored)) return $stored; |
| 119 |
$api = WC_Gateway_Vipps::instance()->api; |
| 120 |
$all = []; |
| 121 |
try { |
| 122 |
$all = $api->get_all_merchant_redirect_qr(); |
| 123 |
} catch (Exception $e) { |
| 124 |
Vipps::instance()->log(sprintf(__("Error getting list of QR codes from %1\$s: %2\$s", 'woo-vipps'), "Vipps", $e->getMessage()), 'error'); |
| 125 |
delete_transient('_woo_vipps_qr_codes'); |
| 126 |
delete_transient('_woo_vipps_unsynched_qr_codes'); |
| 127 |
return $all; |
| 128 |
} |
| 129 |
$table = array(); |
| 130 |
foreach($all as &$entry) { |
| 131 |
$table[$entry['id']] = $entry; |
| 132 |
} |
| 133 |
set_transient('_woo_vipps_qr_codes', $table, DAY_IN_SECONDS); |
| 134 |
return $table; |
| 135 |
} |
| 136 |
// Called only to check if there are unsynchronized codes, which are cached. |
| 137 |
public function get_all_local_qr_codes () { |
| 138 |
global $wpdb; |
| 139 |
$all = $wpdb->get_results("SELECT post_id, meta_key, meta_value from `{$wpdb->postmeta}` WHERE meta_key = '_vipps_qr_id'", ARRAY_A); |
| 140 |
if (!$all) return []; |
| 141 |
$table = []; |
| 142 |
foreach ($all as $entry) { |
| 143 |
$table[$entry['meta_value']] = $entry['post_id']; |
| 144 |
} |
| 145 |
return $table; |
| 146 |
} |
| 147 |
public function get_unsynchronized_qr_codes() { |
| 148 |
$stored = get_transient('_woo_vipps_unsynched_qr_codes'); |
| 149 |
if (is_array($stored)) return $stored; |
| 150 |
$all = $this->get_all_qr_codes_at_vipps(); |
| 151 |
$local = $this->get_all_local_qr_codes(); |
| 152 |
$table = []; |
| 153 |
foreach(array_keys($all) as $key) { |
| 154 |
if (!isset($local[$key])) { |
| 155 |
$table[$key] = $all[$key]; |
| 156 |
} |
| 157 |
} |
| 158 |
set_transient('_woo_vipps_unsynched_qr_codes', $table, DAY_IN_SECONDS); |
| 159 |
return $table; |
| 160 |
} |
| 161 |
|
| 162 |
// POST handler for the import-unsynched feature |
| 163 |
public function handle_unsynched () { |
| 164 |
if (!current_user_can('manage_woocommerce')) { |
| 165 |
wp_die(__('You don\'t have sufficient rights to access this page', 'woo-vipps')); |
| 166 |
} |
| 167 |
if (!isset($_POST['vipps_qr_import']) || !wp_verify_nonce($_POST['vipps_qr_import'], 'vipps_qr_import')) { |
| 168 |
wp_die(__('Invalid request', 'woo-vipps')); |
| 169 |
} |
| 170 |
$operation = ""; |
| 171 |
if (isset($_POST['operation-delete']) && $_POST['operation-delete']) { |
| 172 |
$operation = "delete"; |
| 173 |
} else if (isset($_POST['operation-import']) && $_POST['operation-import']) { |
| 174 |
$operation = "import"; |
| 175 |
} else { |
| 176 |
wp_die(__('Unknown operation', 'woo-vipps')); |
| 177 |
} |
| 178 |
|
| 179 |
$qrids = isset($_POST['qrids']) ? $_POST['qrids'] : []; |
| 180 |
if (!is_array($qrids)) $qrids = []; |
| 181 |
if ($operation == 'delete') { |
| 182 |
$api = WC_Gateway_Vipps::instance()->api; |
| 183 |
foreach($qrids as $vid){ |
| 184 |
try { |
| 185 |
$api->delete_merchant_redirect_qr($vid); |
| 186 |
} catch (Exception $e) { |
| 187 |
Vipps::instance()->log(sprintf(__("Error deleting unsynched QR code with id %1\$s: %2\$s", 'woo-vipps'),$vid, $e->getMessage()), 'error'); |
| 188 |
} |
| 189 |
} |
| 190 |
} else if ($operation == 'import') { |
| 191 |
$all = $this->get_all_qr_codes_at_vipps(); |
| 192 |
foreach($qrids as $vid){ |
| 193 |
$entry = $all[$vid]; |
| 194 |
$url = isset($entry['redirectUrl']) ? $entry['redirectUrl'] : home_url("/"); |
| 195 |
if (!$url) { |
| 196 |
continue; |
| 197 |
} |
| 198 |
try { |
| 199 |
$postargs = array( |
| 200 |
'post_title' => __("Imported QR Code", 'woo-vipps'), |
| 201 |
'post_excerpt' => __("Imported QR Code", 'woo-vipps'), |
| 202 |
'post_status' => 'publish', |
| 203 |
'post_type' => 'vipps_qr_code', |
| 204 |
'meta_input' => ['_vipps_qr_id' => $vid, '_vipps_qr_urltype'=>'url', '_vipps_qr_stored' => true] |
| 205 |
); |
| 206 |
$ok = wp_insert_post($postargs, 'wp_error', true); |
| 207 |
if ($ok) { |
| 208 |
// This will trigger update + image download |
| 209 |
update_post_meta($ok, '_vipps_qr_url', sanitize_url($url)); |
| 210 |
} |
| 211 |
if (is_wp_error($ok)) { |
| 212 |
Vipps::instance()->log(sprintf(__("Error importing unsynched QR code with id %1\$s: %2\$s", 'woo-vipps'),$vid, $e->get_error_message()), 'error'); |
| 213 |
} |
| 214 |
} catch (Exception $e) { |
| 215 |
Vipps::instance()->log(sprintf(__("Error importing unsynched QR code with id %1\$s: %2\$s", 'woo-vipps'),$vid, $e->getMessage()), 'error'); |
| 216 |
} |
| 217 |
} |
| 218 |
|
| 219 |
} |
| 220 |
delete_transient('_woo_vipps_qr_codes'); |
| 221 |
delete_transient('_woo_vipps_unsynched_qr_codes'); |
| 222 |
wp_redirect(admin_url("/edit.php?post_type=vipps_qr_code")); |
| 223 |
} |
| 224 |
|
| 225 |
public function admin_footer() { |
| 226 |
$screen = get_current_screen(); |
| 227 |
if ($screen && $screen->id == 'edit-vipps_qr_code') { |
| 228 |
$all = $this->get_unsynchronized_qr_codes(); |
| 229 |
?> |
| 230 |
<!-- The modal / dialog box, hidden somewhere near the footer on the QR screen. --> |
| 231 |
<div id="vipps_unsynchronized_qr_codes" class="hidden" title="<?php _e('Vipps QR-codes not present on this site', 'woo-vipps'); ?>" |
| 232 |
<div> |
| 233 |
<p><?php _e("There are some QR codes present at Vipps that are not part of this Website. This may be because these are part of some <i>other</i> website using this same account, or they may have gotten 'lost' in a database restore - or maybe you are creating an entire new site. If neccessary, you can import these into your current site and manage them from here", 'woo-vipps'); ?></p> |
| 234 |
<div class="importsection"> |
| 235 |
<form action="<?php echo admin_url('/admin-post.php'); ?>" method="POST" onsubmit="return confirm('<?php _e("Are you sure?", 'woo-vipps'); ?>');"> |
| 236 |
<?php wp_nonce_field("vipps_qr_import", 'vipps_qr_import' ); ?> |
| 237 |
<input type="hidden" name="action" value="vipps_qr_handle_unsynched"> |
| 238 |
<div class="buttonsection"> |
| 239 |
<button class="button btn primary" name="operation-import" value="vipps_qr_unsynch_import"><?php _e("Import", 'woo-vipps'); ?></button> |
| 240 |
<button class="button btn secondary" name="operation-delete" value="vipps_qr_unsynch_delete"><?php _e("delete", 'woo-vipps'); ?></button> |
| 241 |
</div> |
| 242 |
<table class="table importtable"> |
| 243 |
<thead> |
| 244 |
<tr><th class='checkboxcell'><th class="idcell"><?php _e("Id", 'woo-vipps'); ?></th><th class='urlcell'><?php _e('URL', 'woo-vipps'); ?></th></tr> |
| 245 |
</thead> |
| 246 |
<tbody> |
| 247 |
<?php foreach ($all as $id=>$entry): ?> |
| 248 |
<tr> |
| 249 |
<td class='checkboxcell'><input type="checkbox" name='qrids[]' value="<?php echo esc_attr($id); ?>"></td> |
| 250 |
<td class='idcell'><?php echo esc_html($id); ?></td> |
| 251 |
<?php $urlvalue = (isset($entry['redirectUrl'])) ? $entry['redirectUrl'] : home_url("/"); ?> |
| 252 |
<td class='urlcell'><input type="url" readonly value="<?php echo esc_url($urlvalue); ?>"></td> |
| 253 |
</tr> |
| 254 |
|
| 255 |
|
| 256 |
<?php endforeach; ?> |
| 257 |
</tbody> |
| 258 |
</table> |
| 259 |
|
| 260 |
</form> |
| 261 |
</div> |
| 262 |
|
| 263 |
</div> |
| 264 |
<?php |
| 265 |
} |
| 266 |
} |
| 267 |
|
| 268 |
public function qr_list_views ($views) { |
| 269 |
$all = $this->get_unsynchronized_qr_codes(); |
| 270 |
$count = count(array_keys($all)); |
| 271 |
if ($count>0) { |
| 272 |
$unsynch = "<a class='open_unsynchronized_qr_codes' href=\"javascript:void(0);\" title='" . __("Show QR codes not synchronized to this site", 'woo-vipps') . "'>"; |
| 273 |
$unsynch .= __("Unsynchronized QR codes", 'woo-vipps'); |
| 274 |
$unsynch .= " <span class='count'>($count)</span>"; |
| 275 |
$unsynch .= "</a>"; |
| 276 |
|
| 277 |
$views['unsynchronized'] = $unsynch; |
| 278 |
} |
| 279 |
return $views; |
| 280 |
|
| 281 |
} |
| 282 |
|
| 283 |
public function metabox_url () { |
| 284 |
global $post; |
| 285 |
$pid = $post->ID; |
| 286 |
$id = get_post_meta($pid, '_vipps_qr_id', true); // Reference at Vipps |
| 287 |
$url = get_post_meta($pid, '_vipps_qr_url', true); // The URL to add/modify |
| 288 |
$qr = get_post_meta($pid, '_vipps_qr_img', true); // The QR image |
| 289 |
$qrpid = get_post_meta($pid, '_vipps_qr_pid', true); // If linking to a product or page, this would be it |
| 290 |
$urltype = get_post_meta($pid, '_vipps_qr_urltype', true); // What kind of URL we are working with |
| 291 |
|
| 292 |
$pngsize = apply_filters('woo_vipps_qr_png_size', 640); // width/height of PNG qr code |
| 293 |
$imagefilename = sanitize_file_name($post->post_title); // Filename for downloaded svgs and pngs |
| 294 |
|
| 295 |
if (!$urltype) { |
| 296 |
// Unknown urltype, either a new QR or some database issue. Try to handle gracefully. |
| 297 |
if ($qrpid) { |
| 298 |
if (is_page($qrpid)) { |
| 299 |
$urltype = 'pageid'; |
| 300 |
} else if (get_post_type($qrpid) == 'product') { |
| 301 |
$urltype = 'prodid'; |
| 302 |
} else if ($url) { // Unknown product type, we should have the URL still though |
| 303 |
$urltype = 'url'; |
| 304 |
} else { |
| 305 |
// This should never happen |
| 306 |
$qrpid = 0; |
| 307 |
$urltype = 'url'; |
| 308 |
} |
| 309 |
} else if ($url) { |
| 310 |
$urltype = 'url'; |
| 311 |
} else { |
| 312 |
// Brand new! Assume we want a page. |
| 313 |
$urltype = 'pageid'; |
| 314 |
} |
| 315 |
} |
| 316 |
$pageid = ($urltype == 'pageid') ? $qrpid : 0; |
| 317 |
$prodid = ($urltype == 'productid') ? $qrpid : 0; |
| 318 |
|
| 319 |
|
| 320 |
$screen = get_current_screen(); |
| 321 |
|
| 322 |
wp_nonce_field("vipps_qr_metabox_save", 'vipps_qr_metabox_nonce' ); |
| 323 |
?> |
| 324 |
<label><?php echo esc_html__( 'QR-id', 'woo-vipps' ); ?>: <?php echo esc_html($id); ?></label><br> |
| 325 |
|
| 326 |
<div class="url-section"> |
| 327 |
<label><?php echo esc_html__('Select link type', 'woo-vipps'); ?></label> |
| 328 |
<div class="link-selector"> |
| 329 |
<label for="pageidtab"><?php _e("Page", 'woo-vipps'); ?> |
| 330 |
<input type=radio class="vipps_urltype" id=pageidtab value="pageid" name="_vipps_qr_urltype" <?php if ($urltype=='pageid') echo " checked "; ?>> |
| 331 |
</label> |
| 332 |
|
| 333 |
<label for="productidtab"><?php _e("Product", 'woo-vipps'); ?> |
| 334 |
<input type=radio class="vipps_urltype" id=productidtab value="productid" name="_vipps_qr_urltype" <?php if ($urltype=='productid') echo " checked "; ?>> |
| 335 |
</label> |
| 336 |
|
| 337 |
<label for="urltab"><?php _e("URL", 'woo-vipps'); ?> |
| 338 |
<input type=radio class="vipps_urltype" id=urltab value="url" name="_vipps_qr_urltype" <?php if ($urltype=='url') echo " checked "; ?>> |
| 339 |
</label> |
| 340 |
|
| 341 |
</div> |
| 342 |
<div class="url-options"> |
| 343 |
<div class="url-option pageid <?php if ($urltype=='pageid') echo " active "; ?>"> |
| 344 |
<label><?php echo esc_html__('Choose page', 'woo-vipps'); ?>:</label> |
| 345 |
<?php wp_dropdown_pages(['selected'=>$pageid, 'echo'=>1, 'id'=>'page_id', 'name'=>'_vipps_qr_pid', 'class'=>'vipps-page-selector wc-enhanced-select', 'show_option_none'=> __('None chosen', 'woo-vipps')]); ?> |
| 346 |
</div> |
| 347 |
|
| 348 |
<div class="url-option productid <?php if ($urltype=='productid') echo " active "; ?>"> |
| 349 |
<label><?php echo esc_html__('Choose product', 'woo-vipps'); ?>:</label> |
| 350 |
<select class="wc-product-search" style="width:100%" id="product_id" name="_vipps_qr_pid" data-placeholder="<?php esc_attr_e( 'Search for a product…', 'woocommerce' ); ?>" data-action="woocommerce_json_search_products"> |
| 351 |
<?php if ($prodid): |
| 352 |
$product = wc_get_product($prodid); |
| 353 |
if (is_a($product, 'WC_Product')): |
| 354 |
echo "<option value='" . intval($prodid) . "' selected>" . wp_kses_post( $product->get_formatted_name() ) . "</option>"; |
| 355 |
endif; |
| 356 |
endif; ?> |
| 357 |
</select> |
| 358 |
</div> |
| 359 |
|
| 360 |
|
| 361 |
<div class="url-option url <?php if ($urltype=='url') echo " active "; ?>"> |
| 362 |
<label><?php echo esc_html__( 'URL', 'woo-vipps' ); ?></label> |
| 363 |
<input name="_vipps_qr_url" type="url" value="<?php echo esc_attr( sanitize_text_field( $url) ); ?>" style="width:100%;" /> |
| 364 |
</div> |
| 365 |
</div> |
| 366 |
</div> |
| 367 |
|
| 368 |
<div> |
| 369 |
<label for="excerpt"><?php _e( 'Description' ); ?></label><textarea style="margin-top:0px; width:100%" rows="1" cols="40" name="excerpt" id="excerpt"><?php echo $post->post_excerpt; // textarea_escaped ?></textarea> |
| 370 |
</div> |
| 371 |
|
| 372 |
<div class="qrimageholder" style="padding-top:1rem; padding-bottom: 1rem; display:flex; justify-content: space-between"> |
| 373 |
<?php if ($qr): ?> |
| 374 |
<div class="qrimagedownloadbuttons" style="display:flex;flex-direction:column;"> |
| 375 |
<a class="button downloadsvg" style="margin-top: .7rem"><?php echo esc_html__("Download as SVG", 'woo-vipps'); ?></a> |
| 376 |
<a class="button downloadpng" style="margin-top: .7rem"><?php echo esc_html__("Download as PNG", 'woo-vipps'); ?></a> |
| 377 |
</div> |
| 378 |
<div id='qrimage' class='qrimage' style='width:25%'><?php echo $qr ?></div> |
| 379 |
<?php endif; ?> |
| 380 |
</div> |
| 381 |
|
| 382 |
<script> |
| 383 |
jQuery('.button.downloadsvg').click(function () { |
| 384 |
var svgData = jQuery("#qrimage")[0].innerHTML; |
| 385 |
var svgBlob = new Blob([svgData], {type:"image/svg+xml;charset=utf-8"}); |
| 386 |
var domUrl = window.URL || window.webkitURL || window; |
| 387 |
var svgUrl = domUrl.createObjectURL(svgBlob); |
| 388 |
var downloadLink = document.createElement("a"); |
| 389 |
downloadLink.href = svgUrl; |
| 390 |
downloadLink.target = "_blank"; |
| 391 |
downloadLink.download = <?php echo json_encode($imagefilename . ".svg"); ?>; |
| 392 |
document.body.appendChild(downloadLink); |
| 393 |
downloadLink.click(); |
| 394 |
document.body.removeChild(downloadLink); |
| 395 |
}); |
| 396 |
|
| 397 |
jQuery('.button.downloadpng').click(function () { |
| 398 |
var svgData = jQuery("#qrimage")[0].innerHTML; |
| 399 |
var svgBlob = new Blob([svgData], {type:"image/svg+xml;charset=utf-8"}); |
| 400 |
var domUrl = window.URL || window.webkitURL || window; |
| 401 |
var svgUrl = domUrl.createObjectURL(svgBlob); |
| 402 |
var canvas = document.createElement("canvas"); |
| 403 |
canvas.width = <?php echo json_encode($pngsize); ?>; |
| 404 |
canvas.height= <?php echo json_encode($pngsize); ?>; |
| 405 |
var ctx = canvas.getContext('2d'); |
| 406 |
var img = new Image(); |
| 407 |
img.onload = function () { |
| 408 |
ctx.drawImage(img, 0, 0); |
| 409 |
domUrl.revokeObjectURL(svgUrl); |
| 410 |
var imgURI = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'); |
| 411 |
|
| 412 |
var downloadLink = document.createElement("a"); |
| 413 |
downloadLink.href = imgURI; |
| 414 |
downloadLink.target = "_blank"; |
| 415 |
downloadLink.download = <?php echo json_encode($imagefilename . ".png"); ?>; |
| 416 |
document.body.appendChild(downloadLink); |
| 417 |
downloadLink.click(); |
| 418 |
document.body.removeChild(downloadLink); |
| 419 |
|
| 420 |
}; |
| 421 |
|
| 422 |
img.src = svgUrl; |
| 423 |
}); |
| 424 |
|
| 425 |
|
| 426 |
</script> |
| 427 |
|
| 428 |
|
| 429 |
<?php |
| 430 |
} |
| 431 |
|
| 432 |
|
| 433 |
// This handles "save post" from the admin backend. IOK 2022-04-13 |
| 434 |
public function save_post ($qid, $post, $update) { |
| 435 |
if (!isset($_POST['vipps_qr_metabox_nonce']) || !wp_verify_nonce($_POST['vipps_qr_metabox_nonce'], 'vipps_qr_metabox_save')) return; |
| 436 |
if (!isset($_POST['_vipps_qr_urltype'])) return; |
| 437 |
|
| 438 |
$urltype = sanitize_title($_POST['_vipps_qr_urltype']); |
| 439 |
if (!$urltype) return false; |
| 440 |
|
| 441 |
// Product or page id |
| 442 |
$pid = intval($_POST['_vipps_qr_pid']); |
| 443 |
// Literal url |
| 444 |
$url = sanitize_url($_POST['_vipps_qr_url']); |
| 445 |
|
| 446 |
$newurl = ""; |
| 447 |
switch ($urltype) { |
| 448 |
case 'productid': |
| 449 |
case 'pageid': |
| 450 |
$newurl = get_permalink($pid); |
| 451 |
break; |
| 452 |
case 'url': |
| 453 |
$newurl = $url; |
| 454 |
$pid = 0; |
| 455 |
break; |
| 456 |
} |
| 457 |
update_post_meta( $qid, '_vipps_qr_urltype', sanitize_title($urltype)); |
| 458 |
update_post_meta( $qid, '_vipps_qr_pid', intval($pid)); |
| 459 |
|
| 460 |
// MUST BE LAST! |
| 461 |
if ($newurl){ |
| 462 |
update_post_meta( $qid, '_vipps_qr_url', sanitize_url($newurl)); |
| 463 |
} |
| 464 |
} |
| 465 |
|
| 466 |
|
| 467 |
public function admin_menu () { |
| 468 |
// Vipps QR-codes are handled as a custom post type, we add them to the Vipps admin menu |
| 469 |
add_submenu_page( 'vipps_admin_menu', __('QR Codes', 'woo-vipps'), __('QR Codes', 'woo-vipps'), 'manage_woocommerce', 'edit.php?post_type=vipps_qr_code', null, 20); |
| 470 |
} |
| 471 |
|
| 472 |
public function init () { |
| 473 |
$this->register_vipps_qr_code(); |
| 474 |
// Ensure we synch with backend when updating |
| 475 |
add_filter('update_post_metadata', array($this, 'update_post_metadata'), 10, 5); |
| 476 |
|
| 477 |
// On trash or delete, remove the QR code at vipps |
| 478 |
add_action('wp_trash_post', array($this, 'delete_qr_code')); // Before status goes to "trash" |
| 479 |
add_action('before_delete_post',array($this, 'delete_qr_code')); // Before deletion, metadata still available |
| 480 |
|
| 481 |
// Support resurection - the post id should then be available again |
| 482 |
add_action('untrashed_post', array($this, 'undelete_qr_code')); |
| 483 |
} |
| 484 |
|
| 485 |
// Called when deleting or trashing a post |
| 486 |
public function delete_qr_code($pid) { |
| 487 |
$vid = get_post_meta($pid, '_vipps_qr_id', true); // Reference at Vipps |
| 488 |
if ($vid) { |
| 489 |
$api = WC_Gateway_Vipps::instance()->api; |
| 490 |
try { |
| 491 |
$api->delete_merchant_redirect_qr($vid); |
| 492 |
} catch (Exception $e) { |
| 493 |
Vipps::instance()->log(sprintf(__("Error deleting QR code: %1\$s", 'woo-vipps'), $e->getMessage()), 'error'); |
| 494 |
} |
| 495 |
// Delete data from Vipps, including the ID which should now be free again |
| 496 |
delete_post_meta($pid, '_vipps_qr_img'); |
| 497 |
delete_post_meta($pid, '_vipps_qr_id'); |
| 498 |
delete_post_meta($pid, '_vipps_qr_stored'); |
| 499 |
} |
| 500 |
delete_transient('_woo_vipps_qr_codes'); |
| 501 |
delete_transient('_woo_vipps_unsynched_qr_codes'); |
| 502 |
} |
| 503 |
// Called when undeleting a post. We just need to recreate the QR code, so we do the same as when creating it. |
| 504 |
public function undelete_qr_code($pid) { |
| 505 |
$url = get_post_meta($pid, '_vipps_qr_url', true); |
| 506 |
if ($url) { |
| 507 |
$this->synch_url($pid, $url, 'NEW'); |
| 508 |
} |
| 509 |
} |
| 510 |
|
| 511 |
|
| 512 |
public function update_post_metadata ($nullonsuccess, $pid, $meta_key, $meta_value, $prev_value) { |
| 513 |
|
| 514 |
if ($meta_key != '_vipps_qr_url') return $nullonsuccess; |
| 515 |
if (get_post_type($pid) != 'vipps_qr_code') return $nullonsuccess; |
| 516 |
|
| 517 |
// Prev is only passed when setting multi-values, |
| 518 |
if (!$prev_value) $prev_value = get_post_meta($pid, '_vipps_qr_url', true); |
| 519 |
|
| 520 |
return $this->synch_url($pid, $meta_value, $prev_value); |
| 521 |
} |
| 522 |
|
| 523 |
// Called when updateing the URL meta-value of the post type: Synch the object with Vipps |
| 524 |
public function synch_url($pid, $url , $prev) { |
| 525 |
$vid = get_post_meta($pid, '_vipps_qr_id', true); // Reference at Vipps |
| 526 |
$gotimage = get_post_meta($pid, '_vipps_qr_img', true); // We only fetch this if necessary |
| 527 |
$notnew = get_post_meta($pid, '_vipps_qr_stored', true); // Only false if we have never stored this entry. |
| 528 |
$api = WC_Gateway_Vipps::instance()->api; |
| 529 |
|
| 530 |
$stored = null; |
| 531 |
try { |
| 532 |
$stored = $notnew ? $api-> get_merchant_redirect_qr_entry($vid) : null; |
| 533 |
} catch (Exception $e) { |
| 534 |
Vipps::instance()->log(sprintf(__("QR image with id %2\$s that was supposed to be stored at %1\$s, isnt. Recreating it. Error was:. : %3\$s", 'woo-vipps'), "Vipps", $vid, $e->getMessage()), 'debug'); |
| 535 |
} |
| 536 |
|
| 537 |
// Assume these have or will change. This way these can be updated by saving any QR code. |
| 538 |
delete_transient('_woo_vipps_qr_codes'); |
| 539 |
delete_transient('_woo_vipps_unsynched_qr_codes'); |
| 540 |
|
| 541 |
try { |
| 542 |
// Generate a new Vipps ID if we have none. |
| 543 |
if (!$vid) { |
| 544 |
$prefix = $api->get_orderprefix(); |
| 545 |
$vid = apply_filters('woo_vipps_qr_id', $prefix . "-qr-" . get_post($pid)->post_name, $pid); |
| 546 |
} |
| 547 |
|
| 548 |
// If object does not exist at Vipps, create it |
| 549 |
if (!$stored) { |
| 550 |
$ok = $api->create_merchant_redirect_qr ($vid, $url); |
| 551 |
update_post_meta( $pid, '_vipps_qr_stored', true); |
| 552 |
update_post_meta( $pid, '_vipps_qr_id', $vid); |
| 553 |
delete_post_meta($pid, '_vipps_qr_img'); |
| 554 |
} else { |
| 555 |
if ($url == $prev && $gotimage) { |
| 556 |
$ok = null; |
| 557 |
} else { |
| 558 |
$ok = $api->update_merchant_redirect_qr ($vid, $url) ; |
| 559 |
} |
| 560 |
} |
| 561 |
// This is a time-limited URL which can be used for one hour to download the QR code |
| 562 |
if ($ok && !$gotimage && isset($ok['url'])) { |
| 563 |
try { |
| 564 |
// We don't have to do this via the API, it's fine to use the normal get-content api. |
| 565 |
$data = $api->get_merchant_redirect_qr($ok['url']); |
| 566 |
$img = $data['message']; // Could also be png if we allow that, currenlty svg |
| 567 |
update_post_meta($pid, '_vipps_qr_img', $img); |
| 568 |
} catch (Exception $e) { |
| 569 |
Vipps::instance()->log(sprintf(__("Error downloading QR code image: %1\$s", 'woo-vipps'), $e->getMessage()), 'error'); |
| 570 |
if (is_admin() && ! wp_doing_ajax()) { |
| 571 |
update_post_meta($pid, '_vipps_qr_errors', array(sprintf(__("Couldn't download QR image: try saving post again! Error was: %1\$s", 'woo-vipps'), $e->getMessage()))); |
| 572 |
} |
| 573 |
return false; // Means *do not* store value. |
| 574 |
} |
| 575 |
} |
| 576 |
} catch (Exception $e) { |
| 577 |
// IOK here *in particular* catch the 409 thing for previously used ID and retry (If not handled in api). |
| 578 |
Vipps::instance()->log(sprintf(__("Error creating or updating QR code: %1\$s", 'woo-vipps'), $e->getMessage()), 'error'); |
| 579 |
if (is_admin() && ! wp_doing_ajax()) { |
| 580 |
$errors = array(); |
| 581 |
$errors[]=sprintf(__("Couldn't create or update QR image: %1\$s", 'woo-vipps'),$e->getMessage()); |
| 582 |
if ($e->responsecode == 409) { |
| 583 |
delete_post_meta($pid, '_vipps_qr_id'); |
| 584 |
delete_post_meta($pid, '_vipps_qr_stored'); |
| 585 |
delete_post_meta($pid, '_vipps_qr_img'); |
| 586 |
$errors[] = sprintf(__("It seems a QR code with this ID (%1\$s) already exists at Vipps. If you have recently done a database restore, try to instead import the QR code from the Unsynchronized codes, deleting this. If you have several Wordpress instances using the same Vipps account, make sure you use different prefixes (in the WooCommerce Vipps settings). You can try to change the slug of this entry (you may need to enable this in the page settings) and save again if you don't care about the duplicate. ", 'woo-vipps'), sanitize_title($vid)); |
| 587 |
} |
| 588 |
|
| 589 |
update_post_meta($pid, '_vipps_qr_errors', $errors); |
| 590 |
} |
| 591 |
return false; // Means *do not* store value. |
| 592 |
} |
| 593 |
|
| 594 |
return null; // null means "ok, store in database". |
| 595 |
} |
| 596 |
|
| 597 |
// Register Custom Post Type |
| 598 |
public function register_vipps_qr_code() { |
| 599 |
|
| 600 |
$labels = array( |
| 601 |
'name' => _x( 'Vipps QR Codes', 'Post Type General Name', 'woo-vipps' ), |
| 602 |
'singular_name' => _x( 'Vipps QR Code', 'Post Type Singular Name', 'woo-vipps' ), |
| 603 |
'menu_name' => __( 'QR Codes', 'woo-vipps' ), |
| 604 |
'name_admin_bar' => sprintf(__( '%1$s QR Codes', 'woo-vipps' ), "Vipps"), |
| 605 |
'archives' => __( 'Item Archives', 'woo-vipps' ), |
| 606 |
'attributes' => __( 'Item Attributes', 'woo-vipps' ), |
| 607 |
'parent_item_colon' => __( 'Parent Item:', 'woo-vipps' ), |
| 608 |
'all_items' => __( 'QR Codes', 'woo-vipps' ), |
| 609 |
'add_new_item' => __( 'Add New QR Code', 'woo-vipps' ), |
| 610 |
'add_new' => __( 'Add New', 'woo-vipps' ), |
| 611 |
'new_item' => __( 'New QR Code', 'woo-vipps' ), |
| 612 |
'edit_item' => __( 'Edit QR Code', 'woo-vipps' ), |
| 613 |
'update_item' => __( 'Update QR Code', 'woo-vipps' ), |
| 614 |
'view_item' => __( 'View QR Code', 'woo-vipps' ), |
| 615 |
'view_items' => __( 'View QR Codes', 'woo-vipps' ), |
| 616 |
'search_items' => __( 'Search Item', 'woo-vipps' ), |
| 617 |
'not_found' => __( 'Not found', 'woo-vipps' ), |
| 618 |
'not_found_in_trash' => __( 'Not found in Trash', 'woo-vipps' ), |
| 619 |
'featured_image' => __( 'Featured Image', 'woo-vipps' ), |
| 620 |
'set_featured_image' => __( 'Set featured image', 'woo-vipps' ), |
| 621 |
'remove_featured_image' => __( 'Remove featured image', 'woo-vipps' ), |
| 622 |
'use_featured_image' => __( 'Use as featured image', 'woo-vipps' ), |
| 623 |
'insert_into_item' => __( 'Insert into item', 'woo-vipps' ), |
| 624 |
'uploaded_to_this_item' => __( 'Uploaded to this item', 'woo-vipps' ), |
| 625 |
'items_list' => __( 'Items list', 'woo-vipps' ), |
| 626 |
'items_list_navigation' => __( 'Items list navigation', 'woo-vipps' ), |
| 627 |
'filter_items_list' => __( 'Filter items list', 'woo-vipps' ), |
| 628 |
); |
| 629 |
$capabilities = array( |
| 630 |
'edit_post' => 'manage_woocommerce', |
| 631 |
'read_post' => 'manage_woocommerce', |
| 632 |
'delete_post' => 'manage_woocommerce', |
| 633 |
'edit_posts' => 'manage_woocommerce', |
| 634 |
'edit_others_posts' => 'manage_woocommerce', |
| 635 |
'publish_posts' => 'manage_woocommerce', |
| 636 |
'read_private_posts' => 'manage_woocommerce', |
| 637 |
); |
| 638 |
$args = array( |
| 639 |
'label' => sprintf(__( '%1$s QR Code', 'woo-vipps' ), "Vipps"), |
| 640 |
'description' => sprintf(__( '%1$s QR Codes', 'woo-vipps' ), "Vipps"), |
| 641 |
'labels' => $labels, |
| 642 |
'supports' => array( 'title', 'custom-fields' ), |
| 643 |
'taxonomies' => array( 'post', 'tag' ), |
| 644 |
'hierarchical' => false, |
| 645 |
'public' => false, |
| 646 |
'show_ui' => true, |
| 647 |
'show_in_menu' => false, |
| 648 |
'menu_position' => 10, |
| 649 |
'show_in_admin_bar' => true, |
| 650 |
'show_in_nav_menus' => false, |
| 651 |
'can_export' => true, |
| 652 |
'has_archive' => false, |
| 653 |
'exclude_from_search' => true, |
| 654 |
'publicly_queryable' => false, |
| 655 |
'capabilities' => $capabilities, |
| 656 |
'show_in_rest' => true, |
| 657 |
); |
| 658 |
register_post_type( 'vipps_qr_code', $args ); |
| 659 |
|
| 660 |
} |
| 661 |
|
| 662 |
} |
| 663 |
|
| 664 |
|
| 665 |
|