id = PickupSelect_Plugin::METHOD_ID; $this->instance_id = absint($instance_id); $this->method_title = __('Pickup point (PickupSelect)', 'pickupselect'); $this->method_description = __('Pickup point or parcel locker of a carrier, chosen on a map in the checkout. Add one for each carrier, each with its own price.', 'pickupselect'); $this->supports = ['shipping-zones', 'instance-settings', 'instance-settings-modal']; $this->init(); add_action('woocommerce_update_options_shipping_' . $this->id, [$this, 'process_admin_options']); } public function init() { // az átalánydíj mezői (név, adó, díj, szállítási osztályok díja) a sajátjainkkal kiegészítve $flat = include WC()->plugin_path() . '/includes/shipping/flat-rate/includes/settings-flat-rate.php'; $flat['title']['default'] = __('Pickup point', 'pickupselect'); $flat['title']['description'] = __('The name the customer sees in the checkout, e.g. "GLS parcel locker".', 'pickupselect'); $fields = ['title' => $flat['title']] + $this->carrier_fields(); unset($flat['title']); $fields += array_slice($flat, 0, 2, true); $fields['prices_include_tax'] = [ 'title' => __('Prices include tax', 'pickupselect'), 'type' => 'checkbox', 'label' => __('The cost and the amounts below include tax', 'pickupselect'), 'description' => __('On: the customer pays exactly the cost entered. Off: tax is added on top, as WooCommerce does for shipping by default.', 'pickupselect'), 'default' => get_option('woocommerce_prices_include_tax', 'no') === 'yes' ? 'yes' : 'no', 'desc_tip' => true, ]; $fields += array_slice($flat, 2, null, true); // kapcsolat nélkül a beállító ablakban csak a Connect gomb látszik (szállító választék sincs nélküle) $this->instance_form_fields = $this->connect_field() ?: $fields + $this->limit_fields(); $this->title = $this->get_option('title') ?: __('Pickup point', 'pickupselect'); $this->tax_status = $this->get_option('tax_status'); $this->cost = $this->get_option('cost'); $this->type = $this->get_option('type', 'class'); } // kapcsolat nélkül nincs mit menteni: a már beállított mód (pl. lekapcsolás után) beállításai maradnak public function process_admin_options() { if ($this->connect_field()) { return false; } return parent::process_admin_options(); } // ha még nincs kapcsolat (se kézi kulcs), a beállító ablak tetején a Connect gomb: e nélkül nincs szállító választék private function connect_field(): array { if (!is_admin() || (string) get_option('pickupselect_widget_key', '') !== '') { return []; } return [ 'connect' => [ 'title' => __('Connect to PickupSelect first', 'pickupselect'), 'type' => 'title', 'description' => esc_html__('The carriers and the map come from PickupSelect. Connect your shop (free account), then come back to choose the carrier.', 'pickupselect') . '

' . esc_html__('Connect to PickupSelect', 'pickupselect') . '', ], ]; } // szállító(k) és pont típusok; a választék csak az admin felületen kell (a katalógus lekérése) private function carrier_fields(): array { $carriers = []; $types = []; $description = __('Usually one carrier. With several, the customer chooses among all their points on one map, at the same price. Empty: all carriers.', 'pickupselect'); if (is_admin()) { $choices = PickupSelect_Carriers::choices(); if (is_wp_error($choices)) { $description = $choices->get_error_message(); } else { foreach ($choices['carriers'] as $name => $countries) { $carriers[$name] = $name . ' (' . implode(', ', $countries) . ')'; } foreach ($choices['groups'] as $carrier => $groups) { foreach ($groups as $group) { $types[$carrier][$carrier . PickupSelect_Carriers::GROUP_SEPARATOR . $group] = $group; } } } } return [ 'carriers' => [ 'title' => __('Carrier', 'pickupselect'), 'type' => 'multiselect', 'class' => 'wc-enhanced-select', 'options' => $carriers, 'default' => [], 'description' => $description, 'desc_tip' => true, 'custom_attributes' => ['data-placeholder' => __('All carriers', 'pickupselect')], ], 'point_types' => [ 'title' => __('Point types', 'pickupselect'), 'type' => 'multiselect', 'class' => 'wc-enhanced-select', 'options' => $types, 'default' => [], 'description' => __('For example only parcel lockers. Empty: all point types of the carrier.', 'pickupselect'), 'desc_tip' => true, 'custom_attributes' => ['data-placeholder' => __('All point types', 'pickupselect')], ], ]; } private function limit_fields(): array { $classes = []; foreach (WC()->shipping()->get_shipping_classes() as $class) { $classes[(string) $class->term_id] = $class->name; } // mint a díj mező: a WooCommerce a pénznem jelével és a bolt számformátumával mutatja $amount = fn (string $title, string $description) => [ 'title' => $title, 'type' => 'text', 'class' => 'wc-shipping-modal-price', 'placeholder' => '', 'description' => $description, 'desc_tip' => true, 'default' => '', 'sanitize_callback' => [$this, 'sanitize_amount'], ]; return [ 'limits' => [ 'title' => __('Free shipping and limits', 'pickupselect'), 'type' => 'title', 'description' => __('Optional. Order value: the products after discounts, with or without tax as set above.', 'pickupselect'), ], 'free_above' => $amount(__('Free above', 'pickupselect'), __('The shipping is free from this order value.', 'pickupselect')), 'min_total' => $amount(__('Minimum order', 'pickupselect'), __('Not offered below this order value.', 'pickupselect')), 'max_total' => $amount(__('Maximum order', 'pickupselect'), __('Not offered above this order value.', 'pickupselect')), 'max_weight' => [ /* translators: %s: weight unit */ 'title' => sprintf(__('Maximum weight (%s)', 'pickupselect'), get_option('woocommerce_weight_unit', 'kg')), 'type' => 'decimal', 'placeholder' => '–', 'description' => __('Not offered above this cart weight. The products\' weight is set on the product page.', 'pickupselect'), 'desc_tip' => true, 'default' => '', ], 'hide_classes' => [ 'title' => __('Not for shipping classes', 'pickupselect'), 'type' => 'multiselect', 'class' => 'wc-enhanced-select', 'options' => $classes, 'default' => [], 'description' => $classes ? __('Not offered when the cart contains a product of these shipping classes, e.g. "Bulky" products that do not fit in a parcel locker.', 'pickupselect') : __('Create shipping classes (e.g. "Bulky") under WooCommerce › Settings › Shipping › Classes and set them on the products to use this.', 'pickupselect'), 'desc_tip' => true, ], ]; } // összeg a bolt formátumában (pl. "30 000 Ft" vagy "30.000,00") => "30000"; üres: nincs korlát public function sanitize_amount($value): string { $value = trim(wp_unslash((string) $value)); $symbol = get_woocommerce_currency_symbol(); $value = trim(str_replace([$symbol, html_entity_decode($symbol)], '', $value)); if ($value === '') { return ''; } $value = method_exists(\Automattic\WooCommerce\Utilities\NumberUtil::class, 'sanitize_cost_in_current_locale') ? \Automattic\WooCommerce\Utilities\NumberUtil::sanitize_cost_in_current_locale($value) : wc_format_decimal($value); if (!is_numeric($value) || (float) $value < 0) { throw new Exception(esc_html__('Enter an amount, for example 50, or leave it empty.', 'pickupselect')); } return (string) $value; } public function calculate_shipping($package = []) { // ha a térkép most nem működne (elfogyott napi keret, nincs csomag, nem elérhető), a mód nem jelenik meg: // a vásárló más szállítási móddal rendelhet (az admin felületen értesítés szól róla) if (!PickupSelect_Status::available() || !PickupSelect_Carriers::for_instance($this->instance_id)['codes']) { return; } $totals = self::package_totals($package, $this->prices_include_tax()); if ($this->has_hidden_class($package) || !$this->fits($totals)) { return; } $freeAbove = (string) $this->get_option('free_above', ''); if ($freeAbove !== '' && $totals['total'] >= (float) $freeAbove) { $this->add_rate([ 'id' => $this->get_rate_id(), 'label' => $this->title, 'cost' => 0, 'package' => $package, ]); return; } // az átalánydíj számolása (képlet, szállítási osztályok); a díj az add_rate()-be fut be parent::calculate_shipping($package); } public function add_rate($args = []) { // a WooCommerce a szállítási díjat nettóként kezeli és rászámolja az adót; bruttó árnál előbb levonjuk, // így a vásárló pontosan a megadott összeget fizeti $cost = is_numeric($args['cost'] ?? null) ? (float) $args['cost'] : null; if ($cost !== null && $cost > 0 && $this->is_taxable() && $this->prices_include_tax()) { $args['cost'] = $cost - array_sum(WC_Tax::calc_inclusive_tax($cost, WC_Tax::get_shipping_tax_rates())); } $carrier = $this->carrier_label(); if ($carrier !== '') { $args['meta_data'] = ($args['meta_data'] ?? []) + [__('Carrier', 'pickupselect') => $carrier]; } parent::add_rate($args); } public function prices_include_tax(): bool { return $this->get_option('prices_include_tax', 'no') === 'yes'; } // a választott pont szállítója, ha ehhez a módhoz tartozik; különben, ha a módnak egy szállítója van, az private function carrier_label(): string { $point = PickupSelect_Selection::get(); if ($point && (int) ($point['instance'] ?? 0) === (int) $this->instance_id) { return (string) $point['carrier']; } $names = []; foreach (PickupSelect_Carriers::for_instance($this->instance_id)['countries'] as $country) { $names += array_flip(array_keys($country['carriers'])); } return count($names) === 1 ? (string) array_key_first($names) : ''; } private function has_hidden_class(array $package): bool { $hidden = array_map('intval', (array) $this->get_option('hide_classes', [])); if (!$hidden) { return false; } foreach ($package['contents'] ?? [] as $item) { $product = $item['data'] ?? null; if ($product instanceof WC_Product && in_array((int) $product->get_shipping_class_id(), $hidden, true)) { return true; } } return false; } // a kosár a mód korlátain belül van-e (üres korlát: nincs) private function fits(array $totals): bool { $limit = function (string $key): ?float { $value = (string) $this->get_option($key, ''); return $value === '' ? null : (float) $value; }; return ($limit('min_total') === null || $totals['total'] >= $limit('min_total')) && ($limit('max_total') === null || $totals['total'] <= $limit('max_total')) && ($limit('max_weight') === null || $totals['weight'] <= $limit('max_weight')); } /** * A csomag értéke (a termékek kedvezmény után, adóval vagy anélkül) és súlya a bolt súlyegységében. * @return array{total: float, weight: float} */ public static function package_totals(array $package, bool $withTax): array { $total = 0.0; $weight = 0.0; foreach ($package['contents'] ?? [] as $item) { $total += (float) ($item['line_total'] ?? 0) + ($withTax ? (float) ($item['line_tax'] ?? 0) : 0); $product = $item['data'] ?? null; if ($product instanceof WC_Product && $product->get_weight() !== '') { $weight += (float) $product->get_weight() * (float) ($item['quantity'] ?? 1); } } return ['total' => $total, 'weight' => $weight]; } }