Files
2026-04-28 15:13:50 +02:00

793 lines
28 KiB
PHP

<?php
/**
* The file that defines the core plugin class
*
* A class definition that includes attributes and functions used across both the
* public-facing side of the site and the admin area.
*
* @link https://example.com
* @since 1.0.0
*
* @package Polkurier
* @subpackage Polkurier/includes
*/
use Polkurier\Blocks\WooCommerceCheckoutIntegration;
use Polkurier\Controller\CheckoutController;
use Polkurier\Controller\CourierPointController;
use Polkurier\Controller\OrderController;
use Polkurier\Exception\HttpException;
use Polkurier\Http\JsonResponse;
use Polkurier\Http\Request;
use Polkurier\Http\Response;
use Polkurier\Integrations\AbstractIntegration;
use Polkurier\Integrations\FlexibleShipping;
use Polkurier\ShippingMethods\AbstractShippingMethod;
use Polkurier\ShippingMethods\DhlPopShippingMethodMethod;
use Polkurier\ShippingMethods\DpdShippingMethodMethod;
use Polkurier\ShippingMethods\FedExDtrShippingMethodMethod;
use Polkurier\ShippingMethods\InpostParcelMachineShippingMethodMethod;
use Polkurier\ShippingMethods\PaczkaWRuchuShippingMethodMethod;
use Polkurier\ShippingMethods\PocztexPointShippingMethod;
use Polkurier\ShippingMethods\UpsAccessPointShippingMethodMethod;
use Polkurier\Types\CheckoutPointButtonPosition;
use Polkurier\Util\Arr;
use Polkurier\Util\OrderTools;
/**
* The core plugin class.
*
* This is used to define internationalization, admin-specific hooks, and
* public-facing site hooks.
*
* Also maintains the unique identifier of this plugin as well as the current
* version of the plugin.
*
* @since 1.0.0
* @package Polkurier
* @subpackage Polkurier/includes
* @author Polkurier <test@example.com>
*/
class Polkurier
{
/**
* The loader that's responsible for maintaining and registering all hooks that power
* the plugin.
*
* @since 1.0.0
* @access protected
* @var Polkurier_Loader $loader Maintains and registers all hooks for the plugin.
*/
protected $loader;
/**
* The unique identifier of this plugin.
*
* @since 1.0.0
* @access protected
* @var string $plugin_name The string used to uniquely identify this plugin.
*/
protected $plugin_name;
/**
* The current version of the plugin.
*
* @since 1.0.0
* @access protected
* @var string $version The current version of the plugin.
*/
protected $version;
/**
* @var Polkurier
*/
private static $runtimeInstance;
/**
* @var string
*/
private $pluginDir;
/**
* @var string
*/
private $pluginUrl;
/**
* @var string
*/
private $text_domain = 'woocommerce-polkurier';
/**
* @var string[]
*/
private $shippingMethods = [
'pk_inpost_paczkomat' => InpostParcelMachineShippingMethodMethod::class,
'pk_paczka_w_ruchu' => PaczkaWRuchuShippingMethodMethod::class,
'pk_dpd_point' => DpdShippingMethodMethod::class,
'pk_fedex_dtr' => FedExDtrShippingMethodMethod::class,
'pk_dhl_point' => DhlPopShippingMethodMethod::class,
'pk_ups_access_point' => UpsAccessPointShippingMethodMethod::class,
'pk_pocztex_point' => PocztexPointShippingMethod::class,
];
/**
* @var AbstractIntegration[]
*/
private $integrations = [];
/**
* Define the core functionality of the plugin.
*
* Set the plugin name and the plugin version that can be used throughout the plugin.
* Load the dependencies, define the locale, and set the hooks for the admin area and
* the public-facing side of the site.
*
* @since 1.0.0
*/
public function __construct()
{
$this->version = POLKURIER_PLUGIN_VERSION;
$this->plugin_name = 'polkurier';
$this->pluginDir = plugin_dir_path(__DIR__);
$this->pluginUrl = plugin_dir_url(__DIR__);
$this->load_dependencies();
$this->set_locale();
$this->define_admin_hooks();
$this->define_public_hooks();
add_action('plugins_loaded', [$this, 'onPluginsLoaded'], 100);
add_action('wp_ajax_pk_courier_points', function () {
$this->handlerAjaxRequest(function (Request $request) {
return (new CourierPointController())->searchAction($request);
});
});
add_action('wp_ajax_pk_update_default_courier_point', function () {
$this->handlerAjaxRequest(function (Request $request) {
return (new CourierPointController())->setCourierPoint($request);
});
});
add_action('wp_ajax_pk_evaluate_order', function () {
$this->handlerAjaxRequest(function (Request $request) {
return (new OrderController())->evaluateAction($request);
});
});
// Rejestruje integrację z WC Blocks
add_action('woocommerce_blocks_checkout_block_registration', function ($integration_registry) {
$integration_registry->register(new WooCommerceCheckoutIntegration());
});
// Rejestruje zapis zmian na froncie
add_action('woocommerce_blocks_loaded', function () {
woocommerce_store_api_register_update_callback([
'namespace' => $this->get_plugin_name(),
'callback' => function ($data) {
(new CheckoutController())->handleStoreUpdateCallback(
(string)Arr::get($data, 'action'),
new Request((array)Arr::get($data, 'data'))
);
}
]);
});
}
/**
* @param callable $action
*/
public function handlerAjaxRequest(callable $action)
{
$nonce = Arr::get($_POST, 'nonce', Arr::get($_GET, 'nonce'));
if (!wp_verify_nonce($nonce, 'polkurier-nonce')) {
$response = new JsonResponse(['error' => 'Forbidden'], 403);
$response->send();
exit;
}
try {
$reponse = $action(new Request());
if (!$reponse instanceof Response) {
throw new HttpException('Controller must return instance of Response class', 500);
}
} catch (HttpException $e) {
$reponse = new JsonResponse(['error' => $e->getMessage()], $e->getCode());
} catch (\Exception $e) {
$reponse = new JsonResponse(['error' => $e->getMessage()], 500);
}
$reponse->send();
exit;
}
/**
* @return Polkurier
*/
public static function instance()
{
if (static::$runtimeInstance === null) {
static::$runtimeInstance = new Polkurier();
}
return static::$runtimeInstance;
}
/**
* @return string
*/
public function getPluginDir()
{
return $this->pluginDir;
}
/**
* @param $path
* @return string
*/
public function getPluginPath($path)
{
return $this->getPluginDir() . '/' . $path;
}
/**
* @return string
*/
public function getPluginUrl()
{
return $this->pluginUrl;
}
/**
* @return string
*/
public function getTextDomain()
{
return $this->text_domain;
}
/**
* Zawraca URL do katalogu bazowego
* @param $file
* @return string
*/
public function getPluginDirUrl($file)
{
$basePath = untrailingslashit(plugin_dir_url(__FILE__));
$lastPart = strrpos($basePath, '/');
if ($lastPart !== false) {
$basePath = substr($basePath, 0, $lastPart);
}
return trailingslashit($basePath) . $file;
}
/**
* Load the required dependencies for this plugin.
*
* Include the following files that make up the plugin:
*
* - Polkurier_Loader. Orchestrates the hooks of the plugin.
* - Polkurier_i18n. Defines internationalization functionality.
* - Polkurier_Admin. Defines all hooks for the admin area.
* - Polkurier_Public. Defines all hooks for the public side of the site.
*
* Create an instance of the loader which will be used to register the hooks
* with WordPress.
*
* @since 1.0.0
* @access private
*/
private function load_dependencies()
{
/**
* The class responsible for orchestrating the actions and filters of the
* core plugin.
*/
require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-polkurier-loader.php';
/**
* The class responsible for defining internationalization functionality
* of the plugin.
*/
require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-polkurier-i18n.php';
/**
* The class responsible for defining all actions that occur in the admin area.
*/
require_once plugin_dir_path(dirname(__FILE__)) . 'admin/class-polkurier-admin.php';
/**
* The class responsible for defining all actions that occur in the public-facing
* side of the site.
*/
require_once plugin_dir_path(dirname(__FILE__)) . 'public/class-polkurier-public.php';
require_once plugin_dir_path(dirname(__FILE__)) . 'includes/PolkurierOrderStatusUpdater.php';
$this->loader = new Polkurier_Loader();
}
/**
* Define the locale for this plugin for internationalization.
*
* Uses the Polkurier_i18n class in order to set the domain and to register the hook
* with WordPress.
*
* @since 1.0.0
* @access private
*/
private function set_locale()
{
$plugin_i18n = new Polkurier_i18n();
$this->loader->add_action('plugins_loaded', $plugin_i18n, 'load_plugin_textdomain');
}
/**
* Register all of the hooks related to the admin area functionality
* of the plugin.
*
* @since 1.0.0
* @access private
*/
private function define_admin_hooks()
{
$plugin_admin = new Polkurier_Admin($this->get_plugin_name(), $this->get_version());
$this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_styles');
$this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts');
// Hooks into admin_menu hook to add custom page
$this->loader->add_action('admin_menu', $plugin_admin, 'add_admin_page');
$this->loader->add_action('admin_init', $plugin_admin, 'register_setting');
}
/**
* Register all of the hooks related to the public-facing functionality
* of the plugin.
*
* @since 1.0.0
* @access private
*/
private function define_public_hooks()
{
$plugin_public = new Polkurier_Public( $this->get_plugin_name(), $this->get_version() );
// $this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_styles' );
// $this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_scripts' );
// Hook in
add_filter('woocommerce_checkout_fields', 'polkurier_override_checkout_fields');
// Our hooked in function - $fields is passed via the filter!
function polkurier_override_checkout_fields($fields)
{
$fields['shipping']['shipping_phone'] = array(
'label' => __('Telefon', 'woocommerce'),
'placeholder' => _x('Telefon', 'placeholder', 'woocommerce'),
'required' => false,
'class' => array('form-row-wide'),
'clear' => true
);
$fields['shipping']['shipping_email'] = array(
'label' => __('E-mail', 'woocommerce'),
'placeholder' => _x('E-mail', 'placeholder', 'woocommerce'),
'required' => false,
'class' => array('form-row-wide'),
'clear' => true
);
return $fields;
}
/**
* Display field value on the order edit page
*/
add_action('woocommerce_admin_order_data_after_shipping_address', 'polkurier_checkout_field_display_admin_order_meta', 10, 1);
/**
* @param WC_Order $order
*/
function polkurier_checkout_field_display_admin_order_meta($order)
{
echo '<p><strong>' . __('Numer telefonu') . ':</strong> ' . get_post_meta($order->get_id(), '_shipping_phone', true) . '</p>';
echo '<p><strong>' . __('Adres e-mail') . ':</strong> ' . get_post_meta($order->get_id(), '_shipping_email', true) . '</p>';
echo '<p><strong>' . __('Punkt odbioru') . ':</strong> ' . get_post_meta($order->get_id(), '_polkurier_point_id', true) . '</p>';
$shippingMethod = OrderTools::getShippingMethod($order);
if (!empty(get_post_meta($order->get_id(), '_polkurier_point_id', true))) {
echo '<p>'
. '<strong>' . ($shippingMethod !== null && OrderTools::isOwnShippingMethod($shippingMethod) ? $shippingMethod->getSelectedPointLabel() : 'Wybrany punkt odbioru') . ':</strong><br>'
. '<strong>' . get_post_meta($order->get_id(), '_polkurier_point_id', true) . '</strong><br>'
. nl2br(get_post_meta($order->get_id(), '_polkurier_point_label', true))
. '</p>';
}
}
}
/**
* @return array
*/
public function getShippingMethods()
{
$shippingMethods = [];
foreach ($this->getAllShippingMethods() as $shippingMethod) {
if ($shippingMethod instanceof AbstractShippingMethod) {
$shippingMethods[] = $shippingMethod;
}
}
return $shippingMethods;
}
/**
* @param $id
* @param null $instanceId
* @return WC_Shipping_Method|null
*/
public function getShippingMethodById($id, $instanceId = null)
{
foreach ($this->getAllShippingMethods() as $shippingMethod) {
if ($shippingMethod->id === $id && ($instanceId === null || $instanceId === $shippingMethod->instance_id)) {
return $shippingMethod;
}
}
return null;
}
/**
* @return WC_Shipping_Method[]
*/
public function getAllShippingMethods()
{
$zones = (array)WC_Shipping_Zones::get_zones();
$shippingMethods = [];
foreach ($zones as $zone) {
/** @var WC_Shipping_Method $method */
foreach ((array)$zone['shipping_methods'] as $method) {
$shippingMethods[] = $method;
}
}
return $shippingMethods;
}
public function getMapShippingMethods()
{
$methods = [];
foreach ($this->getAllShippingMethods() as $shippingMethod) {
if ($shippingMethod instanceof AbstractShippingMethod && $shippingMethod->isParcelPickupPoint()) {
$methods[] = $shippingMethod;
}
foreach ($this->integrations as $integration) {
if ($integration instanceof FlexibleShipping && $integration->shouldShowMapButton($shippingMethod)) {
$methods[] = $shippingMethod;
}
}
}
return $methods;
}
public function getMapShippingMethodsIds()
{
$methodsIds = [];
foreach ($this->getMapShippingMethods() as $shippingMethod) {
$methodsIds[] = $shippingMethod->id . ':' . $shippingMethod->instance_id;
}
return $methodsIds;
}
/**
* @return WC_Shipping_Method|null
*/
public function getCurrentShippingMethod()
{
$session = WC()->session;
if ($session === null) {
return null;
}
$chosenShippingMethods = $session->chosen_shipping_methods;
if (!is_array($chosenShippingMethods) || count($chosenShippingMethods) === 0) {
return null;
}
$currentShippingMethodId = $chosenShippingMethods[0];
foreach ($this->getAllShippingMethods() as $method) {
$methodId = $method->id . ':' . $method->instance_id;
if ($methodId === $currentShippingMethodId) {
return $method;
}
}
return null;
}
/**
* @return AbstractIntegration[]
*/
public function getIntegrations()
{
return $this->integrations;
}
public function onPluginsLoaded()
{
if (!class_exists('WooCommerce') || version_compare(WC()->version, '3.0', '<')) {
add_action('admin_notices', function () {
echo '<div class="error"><p><strong>Polkurier</strong> wymaga do działania wtyczki WooCommerce w wersji minimum 3.0</p></div>';
});
return;
}
$activePlugins = apply_filters('active_plugins', get_option('active_plugins'));
// Integracja z FlexibleShipping
if (in_array('flexible-shipping/flexible-shipping.php', $activePlugins, true)) {
$this->integrations[] = new FlexibleShipping();
}
add_filter('woocommerce_shipping_methods', [$this, 'onWoocommerceShippingMethods']);
add_filter('woocommerce_get_order_item_totals', [$this, 'onWoocommerceGetOrderItemTotals'], 2, 100);
add_action('wp_enqueue_scripts', function () {
if (is_checkout()) {
$this->enqueueFrontAssets();
}
});
// Formatowanie adresu wysyłki po złożeniu zamówienia
add_filter('woocommerce_order_get_formatted_shipping_address', function ($addressData) {
global $wp;
$orderId = (int)Arr::get($wp->query_vars, 'order-received');
if ($orderId > 0) {
$order = new WC_Order($orderId);
$orderShippingMethod = Arr::get(array_values($order->get_shipping_methods()), '0');
$shippingMethod = \Polkurier::instance()->getShippingMethodById($orderShippingMethod->get_method_id(), (int)$orderShippingMethod->get_instance_id());
if ($shippingMethod !== null) {
$shouldShowPickupPointAddress = false;
if ($shippingMethod instanceof AbstractShippingMethod && $shippingMethod->isParcelPickupPoint()) {
$shouldShowPickupPointAddress = true;
}
foreach ($this->integrations as $integration) {
if ($integration instanceof FlexibleShipping && $integration->shouldShowMapButton($shippingMethod)) {
$shouldShowPickupPointAddress = true;
}
}
if ($shouldShowPickupPointAddress) {
return wc_get_template_html(
'checkout/order_get_formatted_shipping_address.php',
[
'provider' => get_post_meta($orderId, '_polkurier_provider', true),
'pointId' => get_post_meta($orderId, '_polkurier_point_id', true),
'pointLabel' => get_post_meta($orderId, '_polkurier_point_label', true),
'pointType' => get_post_meta($orderId, '_polkurier_point_type', true),
],
'',
$this->getPluginPath('templates/')
);
}
}
}
return $addressData;
});
$renderMapButton = function ($method, $position) {
if (is_checkout() && get_option('polkurier_layout_checkout_point_button_position') === $position) {
$currentMethod = \Polkurier::instance()->getCurrentShippingMethod();
if ($method instanceof \WC_Shipping_Rate && $currentMethod !== null) {
$isValid = $method->id === $currentMethod->id . ':' . $currentMethod->get_instance_id();
} else {
$isValid = true;
}
if ($currentMethod !== null && $isValid) {
if ($currentMethod instanceof AbstractShippingMethod && $currentMethod->isParcelPickupPoint()) {
$currentMethod->render();
}
foreach ($this->integrations as $integration) {
if ($integration instanceof FlexibleShipping && $integration->shouldShowMapButton($currentMethod)) {
$integration->renderShippingMethod($currentMethod);
}
}
}
}
};
// Wyświetla przycisk "Wybierz paczkomat" pod blokiem wyboru metod wysyłki
add_action('woocommerce_review_order_after_shipping', function () use ($renderMapButton) {
$renderMapButton(null, CheckoutPointButtonPosition::AFTER_SHIPPING_BLOCK);
});
// Wyświetla przycisk "Wybierz paczkomat" w wybranym miejscu
foreach (CheckoutPointButtonPosition::VALID_POSITIONS as $positionName) {
$filterName = $positionName;
if (!empty(CheckoutPointButtonPosition::MAP_POSITIONS[$positionName])) {
$filterName = CheckoutPointButtonPosition::MAP_POSITIONS[$positionName];
}
$priority = CheckoutPointButtonPosition::DEFAULT_PRIORITY;
if (!empty(CheckoutPointButtonPosition::PRIORITIES[$positionName])) {
$priority = CheckoutPointButtonPosition::PRIORITIES[$positionName];
}
add_action($filterName, function ($method) use ($renderMapButton, $positionName) {
$renderMapButton($method, $positionName);
}, $priority);
}
add_action('woocommerce_checkout_update_order_review', function () {
$currentMethod = \Polkurier::instance()->getCurrentShippingMethod();
if ($currentMethod instanceof AbstractShippingMethod) {
$currentMethod->onCheckoutUpdateOrderReview();
}
});
// Walidacja zamówienia legacy
add_action('woocommerce_checkout_process', function () {
$currentMethod = \Polkurier::instance()->getCurrentShippingMethod();
if ($currentMethod instanceof AbstractShippingMethod && $currentMethod->isParcelPickupPoint()) {
$currentMethod->onCheckoutProcess();
}
foreach ($this->integrations as $integration) {
if ($integration instanceof FlexibleShipping) {
$integration->onCheckoutProcess();
}
}
});
// Walidacja zamówienia Blocks
add_action('woocommerce_store_api_checkout_order_processed', function (WC_Order $order) {
$currentMethod = \Polkurier::instance()->getCurrentShippingMethod();
if ($currentMethod instanceof AbstractShippingMethod && $currentMethod->isParcelPickupPoint()) {
$currentMethod->onCheckoutProcess($order);
}
foreach ($this->integrations as $integration) {
if ($integration instanceof FlexibleShipping) {
$integration->onCheckoutProcess($order);
}
}
}, 10, 3);
// Zapis zamówienia
add_action('woocommerce_new_order', function ($post_id) {
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
$sessionStore = WC()->session->get($this->get_plugin_name() . '_delivery_point');
$hasSessionStore = !empty($sessionStore);
$pointId = Arr::get($_POST, 'polkurier_point_id');
if ($hasSessionStore && empty($pointId)) {
$pointId = Arr::get($sessionStore, 'id');
}
if (empty($pointId) && !empty($post_id)) {
$pointId = Arr::get(get_post_meta($post_id, '_polkurier_point_id'), 0);
}
$providerName = Arr::get($_POST, 'polkurier_provider');
if ($hasSessionStore && empty($providerName)) {
$providerName = Arr::get($sessionStore, 'provider');
}
if (empty($providerName) && !empty($post_id)) {
$providerName = Arr::get(get_post_meta($post_id, '_polkurier_provider'), 0);
}
$pointLabel = Arr::get($_POST, 'polkurier_point_label');
if ($hasSessionStore && empty($pointLabel)) {
$pointLabel = Arr::get($sessionStore, 'label');
}
if (empty($pointLabel) && !empty($post_id)) {
$pointLabel = Arr::get(get_post_meta($post_id, '_polkurier_point_label'), 0);
}
$pointType = Arr::get($_POST, 'polkurier_point_type');
if ($hasSessionStore && empty($pointType)) {
$pointType = Arr::get($sessionStore, 'type');
}
if (empty($pointType) && !empty($post_id)) {
$pointType = Arr::get(get_post_meta($post_id, '_polkurier_point_type'), 0);
}
update_post_meta($post_id, '_polkurier_provider', esc_attr($providerName));
update_post_meta($post_id, '_polkurier_point_id', esc_attr($pointId));
update_post_meta($post_id, '_polkurier_point_label', esc_attr($pointLabel));
update_post_meta($post_id, '_polkurier_point_type', esc_attr($pointType));
WC()->session->set(\Polkurier::instance()->get_plugin_name() . '_delivery_point', null);
});
}
public function onWoocommerceGetOrderItemTotals($items, $wcOrder)
{
$pointId = get_post_meta($wcOrder->get_id(), '_polkurier_point_id', true);
if (isset($items['shipping']) && !empty($pointId)) {
$items['shipping']['value'] .= wc_get_template_html(
'checkout/get_order_item_totals_point.php',
[
'pointId' => $pointId,
'pointLabel' => get_post_meta($wcOrder->get_id(), '_polkurier_point_label', true),
],
'',
$this->getPluginPath('templates/')
);
}
return $items;
}
public function enqueueFrontAssets()
{
$name = $this->get_plugin_name();
wp_enqueue_script(uniqid($name, true), $this->getPluginUrl() . 'public/js/polkurier-checkout-page.js', ['jquery'], $this->version, true);
wp_enqueue_script(uniqid($name, true), 'https://maps.polkurier.pl/assets/dist/points-map.bundle.js', ['jquery'], $this->version, true);
wp_enqueue_style(uniqid($name, true), 'https://maps.polkurier.pl/assets/dist/points-map.css', [], $this->version);
}
/**
* @param array $methods
* @return array
*/
public function onWoocommerceShippingMethods($methods)
{
foreach ($this->shippingMethods as $methodId => $methodClass) {
$methods[$methodId] = $methodClass;
}
return $methods;
}
/**
* Run the loader to execute all of the hooks with WordPress.
*
* @since 1.0.0
*/
public function run()
{
$this->loader->run();
}
/**
* The name of the plugin used to uniquely identify it within the context of
* WordPress and to define internationalization functionality.
*
* @return string The name of the plugin.
* @since 1.0.0
*/
public function get_plugin_name()
{
return $this->plugin_name;
}
/**
* The reference to the class that orchestrates the hooks with the plugin.
*
* @return Polkurier_Loader Orchestrates the hooks of the plugin.
* @since 1.0.0
*/
public function get_loader()
{
return $this->loader;
}
/**
* Retrieve the version number of the plugin.
*
* @return string The version number of the plugin.
* @since 1.0.0
*/
public function get_version()
{
return $this->version;
}
}