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];
}
}