@charset "UTF-8";.elementor-widget-loop-grid{scroll-margin-top:var(--auto-scroll-offset,initial)}.elementor-widget-loop-grid-1 .elementor-grid{grid-template-columns:repeat(1,minmax(0,1fr))}.elementor-widget-loop-grid-2 .elementor-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.elementor-widget-loop-grid-3 .elementor-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.elementor-widget-loop-grid-4 .elementor-grid{grid-template-columns:repeat(4,minmax(0,1fr))}.elementor-widget-loop-grid-5 .elementor-grid{grid-template-columns:repeat(5,minmax(0,1fr))}.elementor-widget-loop-grid-6 .elementor-grid{grid-template-columns:repeat(6,minmax(0,1fr))}.elementor-widget-loop-grid-7 .elementor-grid{grid-template-columns:repeat(7,minmax(0,1fr))}.elementor-widget-loop-grid-8 .elementor-grid{grid-template-columns:repeat(8,minmax(0,1fr))}.elementor-widget-loop-grid-9 .elementor-grid{grid-template-columns:repeat(9,minmax(0,1fr))}.elementor-widget-loop-grid-10 .elementor-grid{grid-template-columns:repeat(10,minmax(0,1fr))}.elementor-widget-loop-grid-11 .elementor-grid{grid-template-columns:repeat(11,minmax(0,1fr))}.elementor-widget-loop-grid-12 .elementor-grid{grid-template-columns:repeat(12,minmax(0,1fr))}@media (min-width:ELEMENTOR_SCREEN_WIDESCREEN_MIN){.elementor-widget-loop-grid-widescreen-1 .elementor-grid{grid-template-columns:repeat(1,minmax(0,1fr))}.elementor-widget-loop-grid-widescreen-2 .elementor-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.elementor-widget-loop-grid-widescreen-3 .elementor-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.elementor-widget-loop-grid-widescreen-4 .elementor-grid{grid-template-columns:repeat(4,minmax(0,1fr))}.elementor-widget-loop-grid-widescreen-5 .elementor-grid{grid-template-columns:repeat(5,minmax(0,1fr))}.elementor-widget-loop-grid-widescreen-6 .elementor-grid{grid-template-columns:repeat(6,minmax(0,1fr))}.elementor-widget-loop-grid-widescreen-7 .elementor-grid{grid-template-columns:repeat(7,minmax(0,1fr))}.elementor-widget-loop-grid-widescreen-8 .elementor-grid{grid-template-columns:repeat(8,minmax(0,1fr))}.elementor-widget-loop-grid-widescreen-9 .elementor-grid{grid-template-columns:repeat(9,minmax(0,1fr))}.elementor-widget-loop-grid-widescreen-10 .elementor-grid{grid-template-columns:repeat(10,minmax(0,1fr))}.elementor-widget-loop-grid-widescreen-11 .elementor-grid{grid-template-columns:repeat(11,minmax(0,1fr))}.elementor-widget-loop-grid-widescreen-12 .elementor-grid{grid-template-columns:repeat(12,minmax(0,1fr))}}@media (max-width:ELEMENTOR_SCREEN_LAPTOP_MAX){.elementor-widget-loop-grid-laptop-1 .elementor-grid{grid-template-columns:repeat(1,minmax(0,1fr))}.elementor-widget-loop-grid-laptop-2 .elementor-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.elementor-widget-loop-grid-laptop-3 .elementor-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.elementor-widget-loop-grid-laptop-4 .elementor-grid{grid-template-columns:repeat(4,minmax(0,1fr))}.elementor-widget-loop-grid-laptop-5 .elementor-grid{grid-template-columns:repeat(5,minmax(0,1fr))}.elementor-widget-loop-grid-laptop-6 .elementor-grid{grid-template-columns:repeat(6,minmax(0,1fr))}.elementor-widget-loop-grid-laptop-7 .elementor-grid{grid-template-columns:repeat(7,minmax(0,1fr))}.elementor-widget-loop-grid-laptop-8 .elementor-grid{grid-template-columns:repeat(8,minmax(0,1fr))}.elementor-widget-loop-grid-laptop-9 .elementor-grid{grid-template-columns:repeat(9,minmax(0,1fr))}.elementor-widget-loop-grid-laptop-10 .elementor-grid{grid-template-columns:repeat(10,minmax(0,1fr))}.elementor-widget-loop-grid-laptop-11 .elementor-grid{grid-template-columns:repeat(11,minmax(0,1fr))}.elementor-widget-loop-grid-laptop-12 .elementor-grid{grid-template-columns:repeat(12,minmax(0,1fr))}}@media (max-width:ELEMENTOR_SCREEN_TABLET_EXTRA_MAX){.elementor-widget-loop-grid-tablet_extra-1 .elementor-grid{grid-template-columns:repeat(1,minmax(0,1fr))}.elementor-widget-loop-grid-tablet_extra-2 .elementor-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.elementor-widget-loop-grid-tablet_extra-3 .elementor-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.elementor-widget-loop-grid-tablet_extra-4 .elementor-grid{grid-template-columns:repeat(4,minmax(0,1fr))}.elementor-widget-loop-grid-tablet_extra-5 .elementor-grid{grid-template-columns:repeat(5,minmax(0,1fr))}.elementor-widget-loop-grid-tablet_extra-6 .elementor-grid{grid-template-columns:repeat(6,minmax(0,1fr))}.elementor-widget-loop-grid-tablet_extra-7 .elementor-grid{grid-template-columns:repeat(7,minmax(0,1fr))}.elementor-widget-loop-grid-tablet_extra-8 .elementor-grid{grid-template-columns:repeat(8,minmax(0,1fr))}.elementor-widget-loop-grid-tablet_extra-9 .elementor-grid{grid-template-columns:repeat(9,minmax(0,1fr))}.elementor-widget-loop-grid-tablet_extra-10 .elementor-grid{grid-template-columns:repeat(10,minmax(0,1fr))}.elementor-widget-loop-grid-tablet_extra-11 .elementor-grid{grid-template-columns:repeat(11,minmax(0,1fr))}.elementor-widget-loop-grid-tablet_extra-12 .elementor-grid{grid-template-columns:repeat(12,minmax(0,1fr))}}@media (max-width:ELEMENTOR_SCREEN_TABLET_MAX){.elementor-widget-loop-grid-tablet-1 .elementor-grid{grid-template-columns:repeat(1,minmax(0,1fr))}.elementor-widget-loop-grid-tablet-2 .elementor-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.elementor-widget-loop-grid-tablet-3 .elementor-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.elementor-widget-loop-grid-tablet-4 .elementor-grid{grid-template-columns:repeat(4,minmax(0,1fr))}.elementor-widget-loop-grid-tablet-5 .elementor-grid{grid-template-columns:repeat(5,minmax(0,1fr))}.elementor-widget-loop-grid-tablet-6 .elementor-grid{grid-template-columns:repeat(6,minmax(0,1fr))}.elementor-widget-loop-grid-tablet-7 .elementor-grid{grid-template-columns:repeat(7,minmax(0,1fr))}.elementor-widget-loop-grid-tablet-8 .elementor-grid{grid-template-columns:repeat(8,minmax(0,1fr))}.elementor-widget-loop-grid-tablet-9 .elementor-grid{grid-template-columns:repeat(9,minmax(0,1fr))}.elementor-widget-loop-grid-tablet-10 .elementor-grid{grid-template-columns:repeat(10,minmax(0,1fr))}.elementor-widget-loop-grid-tablet-11 .elementor-grid{grid-template-columns:repeat(11,minmax(0,1fr))}.elementor-widget-loop-grid-tablet-12 .elementor-grid{grid-template-columns:repeat(12,minmax(0,1fr))}}@media (max-width:ELEMENTOR_SCREEN_MOBILE_EXTRA_MAX){.elementor-widget-loop-grid-mobile_extra-1 .elementor-grid{grid-template-columns:repeat(1,minmax(0,1fr))}.elementor-widget-loop-grid-mobile_extra-2 .elementor-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.elementor-widget-loop-grid-mobile_extra-3 .elementor-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.elementor-widget-loop-grid-mobile_extra-4 .elementor-grid{grid-template-columns:repeat(4,minmax(0,1fr))}.elementor-widget-loop-grid-mobile_extra-5 .elementor-grid{grid-template-columns:repeat(5,minmax(0,1fr))}.elementor-widget-loop-grid-mobile_extra-6 .elementor-grid{grid-template-columns:repeat(6,minmax(0,1fr))}.elementor-widget-loop-grid-mobile_extra-7 .elementor-grid{grid-template-columns:repeat(7,minmax(0,1fr))}.elementor-widget-loop-grid-mobile_extra-8 .elementor-grid{grid-template-columns:repeat(8,minmax(0,1fr))}.elementor-widget-loop-grid-mobile_extra-9 .elementor-grid{grid-template-columns:repeat(9,minmax(0,1fr))}.elementor-widget-loop-grid-mobile_extra-10 .elementor-grid{grid-template-columns:repeat(10,minmax(0,1fr))}.elementor-widget-loop-grid-mobile_extra-11 .elementor-grid{grid-template-columns:repeat(11,minmax(0,1fr))}.elementor-widget-loop-grid-mobile_extra-12 .elementor-grid{grid-template-columns:repeat(12,minmax(0,1fr))}}@media (max-width:ELEMENTOR_SCREEN_MOBILE_MAX){.elementor-widget-loop-grid-mobile-1 .elementor-grid{grid-template-columns:repeat(1,minmax(0,1fr))}.elementor-widget-loop-grid-mobile-2 .elementor-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.elementor-widget-loop-grid-mobile-3 .elementor-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.elementor-widget-loop-grid-mobile-4 .elementor-grid{grid-template-columns:repeat(4,minmax(0,1fr))}.elementor-widget-loop-grid-mobile-5 .elementor-grid{grid-template-columns:repeat(5,minmax(0,1fr))}.elementor-widget-loop-grid-mobile-6 .elementor-grid{grid-template-columns:repeat(6,minmax(0,1fr))}.elementor-widget-loop-grid-mobile-7 .elementor-grid{grid-template-columns:repeat(7,minmax(0,1fr))}.elementor-widget-loop-grid-mobile-8 .elementor-grid{grid-template-columns:repeat(8,minmax(0,1fr))}.elementor-widget-loop-grid-mobile-9 .elementor-grid{grid-template-columns:repeat(9,minmax(0,1fr))}.elementor-widget-loop-grid-mobile-10 .elementor-grid{grid-template-columns:repeat(10,minmax(0,1fr))}.elementor-widget-loop-grid-mobile-11 .elementor-grid{grid-template-columns:repeat(11,minmax(0,1fr))}.elementor-widget-loop-grid-mobile-12 .elementor-grid{grid-template-columns:repeat(12,minmax(0,1fr))}}.elementor-widget-loop-grid .elementor-grid{grid-column-gap:var(--grid-column-gap,30px);grid-row-gap:var(--grid-row-gap,30px)}.elementor-widget-loop-grid.e-loading-overlay{animation:loadingOpacityAnimation 1s infinite alternate}.elementor-widget-loop-grid .e-loop__load-more{text-align:var(--load-more-button-align)}.elementor-widget-loop-grid .e-loop__load-more .elementor-button{width:var(--load-more-button-width)}.elementor-widget-loop-grid.e-load-more-pagination-loading>.elementor-widget-container{cursor:default}.elementor-widget-loop-grid.e-load-more-pagination-loading>.elementor-widget-container .e-load-more-spinner{margin-top:var(--load-more—spacing,30px)}.elementor-widget-loop-grid.e-load-more-pagination-loading>.elementor-widget-container .e-load-more-spinner i,.elementor-widget-loop-grid.e-load-more-pagination-loading>.elementor-widget-container .e-load-more-spinner svg{display:flex}.elementor-widget-loop-grid.e-load-more-pagination-loading>.elementor-widget-container>.elementor-button-wrapper .elementor-button-content-wrapper{visibility:hidden}.elementor-widget-loop-grid.e-load-more-pagination-end:not(:has(>.elementor-widget-container))>.elementor-button-wrapper,.elementor-widget-loop-grid.e-load-more-pagination-end>.elementor-widget-container>.elementor-button-wrapper{display:none}.elementor-widget-loop-grid.e-load-more-pagination-end:not(:has(>.elementor-widget-container))>.e-load-more-message,.elementor-widget-loop-grid.e-load-more-pagination-end>.elementor-widget-container>.e-load-more-message{display:block}.elementor-widget-loop-grid.e-load-more-no-spinner:not(:has(>.elementor-widget-container))>.elementor-button-wrapper .elementor-button-content-wrapper,.elementor-widget-loop-grid.e-load-more-no-spinner>.elementor-widget-container>.elementor-button-wrapper .elementor-button-content-wrapper{visibility:visible}.elementor-widget-loop-grid:not(:has(>.elementor-widget-container)) .e-load-more-spinner,.elementor-widget-loop-grid>.elementor-widget-container .e-load-more-spinner{display:flex}.elementor-widget-loop-grid:not(:has(>.elementor-widget-container)) .e-load-more-spinner i,.elementor-widget-loop-grid:not(:has(>.elementor-widget-container)) .e-load-more-spinner svg,.elementor-widget-loop-grid>.elementor-widget-container .e-load-more-spinner i,.elementor-widget-loop-grid>.elementor-widget-container .e-load-more-spinner svg{display:none;margin:0 auto}.elementor-widget-loop-grid:not(:has(>.elementor-widget-container)) .e-load-more-spinner i,.elementor-widget-loop-grid>.elementor-widget-container .e-load-more-spinner i{color:var(--load-more-spinner-color)}.elementor-widget-loop-grid:not(:has(>.elementor-widget-container)) .e-load-more-spinner svg,.elementor-widget-loop-grid>.elementor-widget-container .e-load-more-spinner svg{fill:var(--load-more-spinner-color);height:1em;width:1em}.elementor-widget-loop-grid:not(:has(>.elementor-widget-container)) .e-load-more-message,.elementor-widget-loop-grid>.elementor-widget-container .e-load-more-message{color:var(--load-more-message-color);display:none;margin-top:var(--load-more—spacing,30px);text-align:var(--load-more-message-alignment,center)}.elementor-widget-loop-grid:not(:has(>.elementor-widget-container))>.elementor-button-wrapper,.elementor-widget-loop-grid>.elementor-widget-container>.elementor-button-wrapper{margin-top:var(--load-more—spacing,30px)}.elementor-widget-loop-grid:not(:has(>.elementor-widget-container))>.elementor-button-wrapper .e-load-more-spinner,.elementor-widget-loop-grid>.elementor-widget-container>.elementor-button-wrapper .e-load-more-spinner{inset-block-start:50%;inset-inline-start:50%;margin:inherit;position:absolute;transform:translate(-50%,-50%)}.elementor-widget-loop-grid:not(:has(>.elementor-widget-container))>.elementor-button-wrapper .elementor-button,.elementor-widget-loop-grid>.elementor-widget-container>.elementor-button-wrapper .elementor-button{cursor:pointer;position:relative}.elementor-widget-loop-grid .e-loop-nothing-found-message{color:var(--e-loop-nothing-found-message-color,#1f2124);padding-block-end:var(--e-loop-nothing-found-message-space-from-bottom,30px);padding-block-start:var(--e-loop-nothing-found-message-space-from-top,30px);text-align:var(--e-loop-nothing-found-message-align,center)}.elementor-loop-container.elementor-posts-masonry{align-items:flex-start}.elementor-loop-container:not(.elementor-posts-masonry){align-items:stretch}@keyframes loadingOpacityAnimation{0%,to{opacity:1}50%{opacity:.6}}/** * WooCommerce Stock Functions * * Functions used to manage product stock levels. * * @package WooCommerce\Functions * @version 3.4.0 */ defined( 'ABSPATH' ) || exit; /** * Update a product's stock amount. * * Uses queries rather than update_post_meta so we can do this in one query (to avoid stock issues). * * @since 3.0.0 this supports set, increase and decrease. * * @param int|WC_Product $product Product ID or product instance. * @param int|null $stock_quantity Stock quantity. * @param string $operation Type of operation, allows 'set', 'increase' and 'decrease'. * @param bool $updating If true, the product object won't be saved here as it will be updated later. * @return bool|int|null */ function wc_update_product_stock( $product, $stock_quantity = null, $operation = 'set', $updating = false ) { if ( ! is_a( $product, 'WC_Product' ) ) { $product = wc_get_product( $product ); } if ( ! $product ) { return false; } if ( ! is_null( $stock_quantity ) && $product->managing_stock() ) { // Some products (variations) can have their stock managed by their parent. Get the correct object to be updated here. $product_id_with_stock = $product->get_stock_managed_by_id(); $product_with_stock = $product_id_with_stock !== $product->get_id() ? wc_get_product( $product_id_with_stock ) : $product; $data_store = WC_Data_Store::load( 'product' ); // Fire actions to let 3rd parties know the stock is about to be changed. if ( $product_with_stock->is_type( 'variation' ) ) { do_action( 'woocommerce_variation_before_set_stock', $product_with_stock ); } else { do_action( 'woocommerce_product_before_set_stock', $product_with_stock ); } // Update the database. $new_stock = $data_store->update_product_stock( $product_id_with_stock, $stock_quantity, $operation ); // Update the product object. $data_store->read_stock_quantity( $product_with_stock, $new_stock ); // If this is not being called during an update routine, save the product so stock status etc is in sync, and caches are cleared. if ( ! $updating ) { $product_with_stock->save(); } // Fire actions to let 3rd parties know the stock changed. if ( $product_with_stock->is_type( 'variation' ) ) { do_action( 'woocommerce_variation_set_stock', $product_with_stock ); } else { do_action( 'woocommerce_product_set_stock', $product_with_stock ); } return $product_with_stock->get_stock_quantity(); } return $product->get_stock_quantity(); } /** * Update a product's stock status. * * @param int $product_id Product ID. * @param string $status Status. */ function wc_update_product_stock_status( $product_id, $status ) { $product = wc_get_product( $product_id ); if ( $product ) { $product->set_stock_status( $status ); $product->save(); } } /** * When a payment is complete, we can reduce stock levels for items within an order. * * @since 3.0.0 * @param int $order_id Order ID. */ function wc_maybe_reduce_stock_levels( $order_id ) { $order = wc_get_order( $order_id ); if ( ! $order ) { return; } $stock_reduced = $order->get_data_store()->get_stock_reduced( $order_id ); $trigger_reduce = apply_filters( 'woocommerce_payment_complete_reduce_order_stock', ! $stock_reduced, $order_id ); // Only continue if we're reducing stock. if ( ! $trigger_reduce ) { return; } wc_reduce_stock_levels( $order ); // Ensure stock is marked as "reduced" in case payment complete or other stock actions are called. $order->get_data_store()->set_stock_reduced( $order_id, true ); } add_action( 'woocommerce_payment_complete', 'wc_maybe_reduce_stock_levels' ); add_action( 'woocommerce_order_status_completed', 'wc_maybe_reduce_stock_levels' ); add_action( 'woocommerce_order_status_processing', 'wc_maybe_reduce_stock_levels' ); add_action( 'woocommerce_order_status_on-hold', 'wc_maybe_reduce_stock_levels' ); /** * When a payment is cancelled, restore stock. * * @since 3.0.0 * @param int $order_id Order ID. */ function wc_maybe_increase_stock_levels( $order_id ) { $order = wc_get_order( $order_id ); if ( ! $order ) { return; } $stock_reduced = $order->get_data_store()->get_stock_reduced( $order_id ); $trigger_increase = (bool) $stock_reduced; // Only continue if we're increasing stock. if ( ! $trigger_increase ) { return; } wc_increase_stock_levels( $order ); // Ensure stock is not marked as "reduced" anymore. $order->get_data_store()->set_stock_reduced( $order_id, false ); } add_action( 'woocommerce_order_status_cancelled', 'wc_maybe_increase_stock_levels' ); add_action( 'woocommerce_order_status_pending', 'wc_maybe_increase_stock_levels' ); /** * Reduce stock levels for items within an order, if stock has not already been reduced for the items. * * @since 3.0.0 * @param int|WC_Order $order_id Order ID or order instance. */ function wc_reduce_stock_levels( $order_id ) { if ( is_a( $order_id, 'WC_Order' ) ) { $order = $order_id; $order_id = $order->get_id(); } else { $order = wc_get_order( $order_id ); } // We need an order, and a store with stock management to continue. if ( ! $order || 'yes' !== get_option( 'woocommerce_manage_stock' ) || ! apply_filters( 'woocommerce_can_reduce_order_stock', true, $order ) ) { return; } $changes = array(); // Loop over all items. foreach ( $order->get_items() as $item ) { if ( ! $item->is_type( 'line_item' ) ) { continue; } // Only reduce stock once for each item. $product = $item->get_product(); $item_stock_reduced = $item->get_meta( '_reduced_stock', true ); if ( $item_stock_reduced || ! $product || ! $product->managing_stock() ) { continue; } /** * Filter order item quantity. * * @param int|float $quantity Quantity. * @param WC_Order $order Order data. * @param WC_Order_Item_Product $item Order item data. */ $qty = apply_filters( 'woocommerce_order_item_quantity', $item->get_quantity(), $order, $item ); $item_name = $product->get_formatted_name(); $new_stock = wc_update_product_stock( $product, $qty, 'decrease' ); if ( is_wp_error( $new_stock ) ) { /* translators: %s item name. */ $order->add_order_note( sprintf( __( 'Unable to reduce stock for item %s.', 'woocommerce' ), $item_name ) ); continue; } $item->add_meta_data( '_reduced_stock', $qty, true ); $item->save(); $change = array( 'product' => $product, 'from' => $new_stock + $qty, 'to' => $new_stock, ); $changes[] = $change; /** * Fires when stock reduced to a specific line item * * @param WC_Order_Item_Product $item Order item data. * @param array $change Change Details. * @param WC_Order $order Order data. * @since 7.6.0 */ do_action( 'woocommerce_reduce_order_item_stock', $item, $change, $order ); } wc_trigger_stock_change_notifications( $order, $changes ); do_action( 'woocommerce_reduce_order_stock', $order ); } /** * After stock change events, triggers emails and adds order notes. * * @since 3.5.0 * @param WC_Order $order order object. * @param array $changes Array of changes. */ function wc_trigger_stock_change_notifications( $order, $changes ) { if ( empty( $changes ) ) { return; } $order_notes = array(); $no_stock_amount = absint( get_option( 'woocommerce_notify_no_stock_amount', 0 ) ); foreach ( $changes as $change ) { $order_notes[] = $change['product']->get_formatted_name() . ' ' . $change['from'] . '→' . $change['to']; $low_stock_amount = absint( wc_get_low_stock_amount( wc_get_product( $change['product']->get_id() ) ) ); if ( $change['to'] <= $no_stock_amount ) { do_action( 'woocommerce_no_stock', wc_get_product( $change['product']->get_id() ) ); } elseif ( $change['to'] <= $low_stock_amount ) { do_action( 'woocommerce_low_stock', wc_get_product( $change['product']->get_id() ) ); } if ( $change['to'] < 0 ) { do_action( 'woocommerce_product_on_backorder', array( 'product' => wc_get_product( $change['product']->get_id() ), 'order_id' => $order->get_id(), 'quantity' => abs( $change['from'] - $change['to'] ), ) ); } } $order->add_order_note( __( 'Stock levels reduced:', 'woocommerce' ) . ' ' . implode( ', ', $order_notes ) ); } /** * Increase stock levels for items within an order. * * @since 3.0.0 * @param int|WC_Order $order_id Order ID or order instance. */ function wc_increase_stock_levels( $order_id ) { if ( is_a( $order_id, 'WC_Order' ) ) { $order = $order_id; $order_id = $order->get_id(); } else { $order = wc_get_order( $order_id ); } // We need an order, and a store with stock management to continue. if ( ! $order || 'yes' !== get_option( 'woocommerce_manage_stock' ) || ! apply_filters( 'woocommerce_can_restore_order_stock', true, $order ) ) { return; } $changes = array(); // Loop over all items. foreach ( $order->get_items() as $item ) { if ( ! $item->is_type( 'line_item' ) ) { continue; } // Only increase stock once for each item. $product = $item->get_product(); $item_stock_reduced = $item->get_meta( '_reduced_stock', true ); if ( ! $item_stock_reduced || ! $product || ! $product->managing_stock() ) { continue; } $item_name = $product->get_formatted_name(); $new_stock = wc_update_product_stock( $product, $item_stock_reduced, 'increase' ); if ( is_wp_error( $new_stock ) ) { /* translators: %s item name. */ $order->add_order_note( sprintf( __( 'Unable to restore stock for item %s.', 'woocommerce' ), $item_name ) ); continue; } $item->delete_meta_data( '_reduced_stock' ); $item->save(); $changes[] = $item_name . ' ' . ( $new_stock - $item_stock_reduced ) . '→' . $new_stock; } if ( $changes ) { $order->add_order_note( __( 'Stock levels increased:', 'woocommerce' ) . ' ' . implode( ', ', $changes ) ); } do_action( 'woocommerce_restore_order_stock', $order ); } /** * See how much stock is being held in pending orders. * * @since 3.5.0 * @param WC_Product $product Product to check. * @param integer $exclude_order_id Order ID to exclude. * @return int */ function wc_get_held_stock_quantity( WC_Product $product, $exclude_order_id = 0 ) { /** * Filter: woocommerce_hold_stock_for_checkout * Allows enable/disable hold stock functionality on checkout. * * @since 4.3.0 * @param bool $enabled Default to true if managing stock globally. */ if ( ! apply_filters( 'woocommerce_hold_stock_for_checkout', wc_string_to_bool( get_option( 'woocommerce_manage_stock', 'yes' ) ) ) ) { return 0; } return ( new \Automattic\WooCommerce\Checkout\Helpers\ReserveStock() )->get_reserved_stock( $product, $exclude_order_id ); } /** * Hold stock for an order. * * @throws ReserveStockException If reserve stock fails. * * @since 4.1.0 * @param \WC_Order|int $order Order ID or instance. */ function wc_reserve_stock_for_order( $order ) { /** * Filter: woocommerce_hold_stock_for_checkout * Allows enable/disable hold stock functionality on checkout. * * @since @since 4.1.0 * @param bool $enabled Default to true if managing stock globally. */ if ( ! apply_filters( 'woocommerce_hold_stock_for_checkout', wc_string_to_bool( get_option( 'woocommerce_manage_stock', 'yes' ) ) ) ) { return; } $order = $order instanceof WC_Order ? $order : wc_get_order( $order ); if ( $order ) { ( new \Automattic\WooCommerce\Checkout\Helpers\ReserveStock() )->reserve_stock_for_order( $order ); } } add_action( 'woocommerce_checkout_order_created', 'wc_reserve_stock_for_order' ); /** * Release held stock for an order. * * @since 4.3.0 * @param \WC_Order|int $order Order ID or instance. */ function wc_release_stock_for_order( $order ) { /** * Filter: woocommerce_hold_stock_for_checkout * Allows enable/disable hold stock functionality on checkout. * * @since 4.3.0 * @param bool $enabled Default to true if managing stock globally. */ if ( ! apply_filters( 'woocommerce_hold_stock_for_checkout', wc_string_to_bool( get_option( 'woocommerce_manage_stock', 'yes' ) ) ) ) { return; } $order = $order instanceof WC_Order ? $order : wc_get_order( $order ); if ( $order ) { ( new \Automattic\WooCommerce\Checkout\Helpers\ReserveStock() )->release_stock_for_order( $order ); } } add_action( 'woocommerce_checkout_order_exception', 'wc_release_stock_for_order' ); add_action( 'woocommerce_payment_complete', 'wc_release_stock_for_order', 11 ); add_action( 'woocommerce_order_status_cancelled', 'wc_release_stock_for_order', 11 ); add_action( 'woocommerce_order_status_completed', 'wc_release_stock_for_order', 11 ); add_action( 'woocommerce_order_status_processing', 'wc_release_stock_for_order', 11 ); add_action( 'woocommerce_order_status_on-hold', 'wc_release_stock_for_order', 11 ); /** * Return low stock amount to determine if notification needs to be sent * * Since 5.2.0, this function no longer redirects from variation to its parent product. * Low stock amount can now be attached to the variation itself and if it isn't, only * then we check the parent product, and if it's not there, then we take the default * from the store-wide setting. * * @param WC_Product $product Product to get data from. * @since 3.5.0 * @return int */ function wc_get_low_stock_amount( WC_Product $product ) { $low_stock_amount = $product->get_low_stock_amount(); if ( '' === $low_stock_amount && $product->is_type( 'variation' ) ) { $product = wc_get_product( $product->get_parent_id() ); $low_stock_amount = $product->get_low_stock_amount(); } if ( '' === $low_stock_amount ) { $low_stock_amount = get_option( 'woocommerce_notify_low_stock_amount', 2 ); } return (int) $low_stock_amount; } /** * WooCommerce REST Functions * * Functions for REST specific things. * * @package WooCommerce\Functions * @version 2.6.0 */ defined( 'ABSPATH' ) || exit; /** * Parses and formats a date for ISO8601/RFC3339. * * Required WP 4.4 or later. * See https://developer.wordpress.org/reference/functions/mysql_to_rfc3339/ * * @since 2.6.0 * @param string|null|WC_DateTime $date Date. * @param bool $utc Send false to get local/offset time. * @return string|null ISO8601/RFC3339 formatted datetime. */ function wc_rest_prepare_date_response( $date, $utc = true ) { if ( is_numeric( $date ) ) { $date = new WC_DateTime( "@$date", new DateTimeZone( 'UTC' ) ); $date->setTimezone( new DateTimeZone( wc_timezone_string() ) ); } elseif ( is_string( $date ) ) { $date = new WC_DateTime( $date, new DateTimeZone( 'UTC' ) ); $date->setTimezone( new DateTimeZone( wc_timezone_string() ) ); } if ( ! is_a( $date, 'WC_DateTime' ) ) { return null; } // Get timestamp before changing timezone to UTC. return gmdate( 'Y-m-d\TH:i:s', $utc ? $date->getTimestamp() : $date->getOffsetTimestamp() ); } /** * Returns image mime types users are allowed to upload via the API. * * @since 2.6.4 * @return array */ function wc_rest_allowed_image_mime_types() { return apply_filters( 'woocommerce_rest_allowed_image_mime_types', array( 'jpg|jpeg|jpe' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png', 'bmp' => 'image/bmp', 'tiff|tif' => 'image/tiff', 'ico' => 'image/x-icon', ) ); } /** * Upload image from URL. * * @since 2.6.0 * @param string $image_url Image URL. * @return array|WP_Error Attachment data or error message. */ function wc_rest_upload_image_from_url( $image_url ) { $parsed_url = wp_parse_url( $image_url ); // Check parsed URL. if ( ! $parsed_url || ! is_array( $parsed_url ) ) { /* translators: %s: image URL */ return new WP_Error( 'woocommerce_rest_invalid_image_url', sprintf( __( 'Invalid URL %s.', 'woocommerce' ), $image_url ), array( 'status' => 400 ) ); } // Ensure url is valid. $image_url = esc_url_raw( $image_url ); // download_url function is part of wp-admin. if ( ! function_exists( 'download_url' ) ) { include_once ABSPATH . 'wp-admin/includes/file.php'; } $file_array = array(); $file_array['name'] = basename( current( explode( '?', $image_url ) ) ); // Download file to temp location. $file_array['tmp_name'] = download_url( $image_url ); // If error storing temporarily, return the error. if ( is_wp_error( $file_array['tmp_name'] ) ) { return new WP_Error( 'woocommerce_rest_invalid_remote_image_url', /* translators: %s: image URL */ sprintf( __( 'Error getting remote image %s.', 'woocommerce' ), $image_url ) . ' ' /* translators: %s: error message */ . sprintf( __( 'Error: %s', 'woocommerce' ), $file_array['tmp_name']->get_error_message() ), array( 'status' => 400 ) ); } // Do the validation and storage stuff. $file = wp_handle_sideload( $file_array, array( 'test_form' => false, 'mimes' => wc_rest_allowed_image_mime_types(), ), current_time( 'Y/m' ) ); if ( isset( $file['error'] ) ) { @unlink( $file_array['tmp_name'] ); // @codingStandardsIgnoreLine. /* translators: %s: error message */ return new WP_Error( 'woocommerce_rest_invalid_image', sprintf( __( 'Invalid image: %s', 'woocommerce' ), $file['error'] ), array( 'status' => 400 ) ); } do_action( 'woocommerce_rest_api_uploaded_image_from_url', $file, $image_url ); return $file; } /** * Set uploaded image as attachment. * * @since 2.6.0 * @param array $upload Upload information from wp_upload_bits. * @param int $id Post ID. Default to 0. * @return int Attachment ID */ function wc_rest_set_uploaded_image_as_attachment( $upload, $id = 0 ) { $info = wp_check_filetype( $upload['file'] ); $title = ''; $content = ''; if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) { include_once ABSPATH . 'wp-admin/includes/image.php'; } $image_meta = @wp_read_image_metadata( $upload['file'] ); if ( $image_meta ) { if ( trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) { $title = wc_clean( $image_meta['title'] ); } if ( trim( $image_meta['caption'] ) ) { $content = wc_clean( $image_meta['caption'] ); } } $attachment = array( 'post_mime_type' => $info['type'], 'guid' => $upload['url'], 'post_parent' => $id, 'post_title' => $title ? $title : basename( $upload['file'] ), 'post_content' => $content, ); $attachment_id = wp_insert_attachment( $attachment, $upload['file'], $id ); if ( ! is_wp_error( $attachment_id ) ) { @wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $upload['file'] ) ); } return $attachment_id; } /** * Validate reports request arguments. * * @since 2.6.0 * @param mixed $value Value to validate. * @param WP_REST_Request $request Request instance. * @param string $param Param to validate. * @return WP_Error|boolean */ function wc_rest_validate_reports_request_arg( $value, $request, $param ) { $attributes = $request->get_attributes(); if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { return true; } $args = $attributes['args'][ $param ]; if ( 'string' === $args['type'] && ! is_string( $value ) ) { /* translators: 1: param 2: type */ return new WP_Error( 'woocommerce_rest_invalid_param', sprintf( __( '%1$s is not of type %2$s', 'woocommerce' ), $param, 'string' ) ); } if ( 'date' === $args['format'] ) { $regex = '#^\d{4}-\d{2}-\d{2}$#'; if ( ! preg_match( $regex, $value, $matches ) ) { return new WP_Error( 'woocommerce_rest_invalid_date', __( 'The date you provided is invalid.', 'woocommerce' ) ); } } return true; } /** * Encodes a value according to RFC 3986. * Supports multidimensional arrays. * * @since 2.6.0 * @param string|array $value The value to encode. * @return string|array Encoded values. */ function wc_rest_urlencode_rfc3986( $value ) { if ( is_array( $value ) ) { return array_map( 'wc_rest_urlencode_rfc3986', $value ); } return str_replace( array( '+', '%7E' ), array( ' ', '~' ), rawurlencode( $value ) ); } /** * Check permissions of posts on REST API. * * @since 2.6.0 * @param string $post_type Post type. * @param string $context Request context. * @param int $object_id Post ID. * @return bool */ function wc_rest_check_post_permissions( $post_type, $context = 'read', $object_id = 0 ) { $contexts = array( 'read' => 'read_private_posts', 'create' => 'publish_posts', 'edit' => 'edit_post', 'delete' => 'delete_post', 'batch' => 'edit_others_posts', ); if ( 'revision' === $post_type ) { $permission = false; } else { $cap = $contexts[ $context ]; $post_type_object = get_post_type_object( $post_type ); $permission = current_user_can( $post_type_object->cap->$cap, $object_id ); } return apply_filters( 'woocommerce_rest_check_permissions', $permission, $context, $object_id, $post_type ); } /** * Check permissions of users on REST API. * * @since 2.6.0 * @param string $context Request context. * @param int $object_id Post ID. * @return bool */ function wc_rest_check_user_permissions( $context = 'read', $object_id = 0 ) { $contexts = array( 'read' => 'list_users', 'create' => 'promote_users', // Check if current user can create users, shop managers are not allowed to create users. 'edit' => 'edit_users', 'delete' => 'delete_users', 'batch' => 'promote_users', ); // Check to allow shop_managers to manage only customers. if ( in_array( $context, array( 'edit', 'delete' ), true ) && wc_current_user_has_role( 'shop_manager' ) ) { $permission = false; $user_data = get_userdata( $object_id ); $shop_manager_editable_roles = apply_filters( 'woocommerce_shop_manager_editable_roles', array( 'customer' ) ); if ( isset( $user_data->roles ) ) { $can_manage_users = array_intersect( $user_data->roles, array_unique( $shop_manager_editable_roles ) ); // Check if Shop Manager can edit customer or with the is same shop manager. if ( 0 < count( $can_manage_users ) || intval( $object_id ) === intval( get_current_user_id() ) ) { $permission = current_user_can( $contexts[ $context ], $object_id ); } } } else { $permission = current_user_can( $contexts[ $context ], $object_id ); } return apply_filters( 'woocommerce_rest_check_permissions', $permission, $context, $object_id, 'user' ); } /** * Check permissions of product terms on REST API. * * @since 2.6.0 * @param string $taxonomy Taxonomy. * @param string $context Request context. * @param int $object_id Post ID. * @return bool */ function wc_rest_check_product_term_permissions( $taxonomy, $context = 'read', $object_id = 0 ) { $contexts = array( 'read' => 'manage_terms', 'create' => 'edit_terms', 'edit' => 'edit_terms', 'delete' => 'delete_terms', 'batch' => 'edit_terms', ); $cap = $contexts[ $context ]; $taxonomy_object = get_taxonomy( $taxonomy ); $permission = current_user_can( $taxonomy_object->cap->$cap, $object_id ); return apply_filters( 'woocommerce_rest_check_permissions', $permission, $context, $object_id, $taxonomy ); } /** * Check manager permissions on REST API. * * @since 2.6.0 * @param string $object Object. * @param string $context Request context. * @return bool */ function wc_rest_check_manager_permissions( $object, $context = 'read' ) { $objects = array( 'reports' => 'view_woocommerce_reports', 'settings' => 'manage_woocommerce', 'system_status' => 'manage_woocommerce', 'attributes' => 'manage_product_terms', 'shipping_methods' => 'manage_woocommerce', 'payment_gateways' => 'manage_woocommerce', 'webhooks' => 'manage_woocommerce', ); $permission = current_user_can( $objects[ $object ] ); return apply_filters( 'woocommerce_rest_check_permissions', $permission, $context, 0, $object ); } /** * Check product reviews permissions on REST API. * * @since 3.5.0 * @param string $context Request context. * @param string $object_id Object ID. * @return bool */ function wc_rest_check_product_reviews_permissions( $context = 'read', $object_id = 0 ) { $permission = false; $contexts = array( 'read' => 'moderate_comments', 'create' => 'edit_products', 'edit' => 'edit_products', 'delete' => 'edit_products', 'batch' => 'edit_products', ); if ( $object_id > 0 ) { $object = get_comment( $object_id ); if ( ! is_a( $object, 'WP_Comment' ) || get_comment_type( $object ) !== 'review' ) { return false; } } if ( isset( $contexts[ $context ] ) ) { $permission = current_user_can( $contexts[ $context ], $object_id ); } return apply_filters( 'woocommerce_rest_check_permissions', $permission, $context, $object_id, 'product_review' ); } /** * Normalize a filesystem path. */ if (!function_exists('wp_normalize_path')) { /** * WordPress function to normalize a filesystem path; was added to WP core in WP 3.9 * * @see wp_normalize_path() https://developer.wordpress.org/reference/functions/wp_normalize_path/#source for the original source code * * @param string $path Path to normalize. * @return string Normalized path. */ function wp_normalize_path($path) { $wrapper = ''; if (wp_is_stream($path)) { list($wrapper, $path) = explode('://', $path, 2); $wrapper .= '://'; } // Standardise all paths to use / $path = str_replace('\\', '/', $path); // Replace multiple slashes down to a singular, allowing for network shares having two slashes. $path = preg_replace('|(?<=.)/+|', '/', $path); // Windows paths should uppercase the drive letter if (':' === substr($path, 1, 1)) { $path = ucfirst($path); } return $wrapper.$path; } } /** * Unschedules all events attached to the hook. */ if (!function_exists('wp_unschedule_hook')) { /** * Unschedules all events attached to the hook. * * Can be useful for plugins when deactivating to clean up the cron queue. * * Warning: This function may return Boolean FALSE, but may also return a non-Boolean * value which evaluates to FALSE. For information about casting to booleans see the * {@link https://www.php.net/manual/en/language.types.boolean.php PHP documentation}. Use * the `===` operator for testing the return value of this function. * * @since 4.9.0 * @since 5.1.0 Return value added to indicate success or failure. * * @param string $hook Action hook, the execution of which will be unscheduled. * @return int|false On success an integer indicating number of events unscheduled (0 indicates no * events were registered on the hook), false if unscheduling fails. */ function wp_unschedule_hook($hook) { /** * Filter to preflight or hijack clearing all events attached to the hook. * * Returning a non-null value will short-circuit the normal unscheduling * process, causing the function to return the filtered value instead. * * For plugins replacing wp-cron, return the number of events successfully * unscheduled (zero if no events were registered with the hook) or false * if unscheduling one or more events fails. * * @since 5.1.0 * * @param null|int|false $pre Value to return instead. Default null to continue unscheduling the hook. * @param string $hook Action hook, the execution of which will be unscheduled. */ $pre = apply_filters('pre_unschedule_hook', null, $hook); if (null !== $pre) { return $pre; } $crons = _get_cron_array(); if (empty($crons)) { return 0; } $results = array(); foreach ($crons as $timestamp => $args) { if (!empty($crons[$timestamp][$hook])) { $results[] = count($crons[$timestamp][$hook]); } unset($crons[$timestamp][$hook]); if (empty($crons[$timestamp])) { unset($crons[$timestamp]); } } /* * If the results are empty (zero events to unschedule), no attempt * to update the cron array is required. */ if (empty($results)) { return 0; } if (_set_cron_array($crons)) { return array_sum($results); } return false; } } /** * Greek translation * @author yawd , Romanos * @version 2014-12-19 */ (function(root, factory) { if (typeof define === 'function' && define.amd) { define(['elfinder'], factory); } else if (typeof exports !== 'undefined') { module.exports = factory(require('elfinder')); } else { factory(root.elFinder); } }(this, function(elFinder) { elFinder.prototype.i18.el = { translator : 'yawd <ingo@yawd.eu>', language : 'Ελληνικά', direction : 'ltr', dateFormat : 'd.m.Y H:i', fancyDateFormat : '$1 H:i', messages : { /********************************** errors **********************************/ 'error' : 'Πρόβλημα', 'errUnknown' : 'Άγνωστο πρόβλημα.', 'errUnknownCmd' : 'Άγνωστη εντολή.', 'errJqui' : 'Μη έγκυρη ρύθμιση του jQuery UI. Τα components "selectable", "draggable" και "droppable" πρέπει να περιληφούν.', 'errNode' : 'το elFinder χρειάζεται να έχει δημιουργηθεί το DOM Element.', 'errURL' : 'Μη έγκυρες ρυθμίσεις για το elFinder! η επιλογή URL δεν έχει οριστεί.', 'errAccess' : 'Απαγορεύεται η πρόσβαση.', 'errConnect' : 'Δεν ήταν δυνατή η σύνδεση με το backend.', 'errAbort' : 'Η σύνδεση εγκαταλείφθηκε.', 'errTimeout' : 'Η σύνδεση έληξε.', 'errNotFound' : 'Δε βρέθηκε το backend.', 'errResponse' : 'Μή έγκυρη απάντηση από το backend.', 'errConf' : 'Μη έγκυρες ρυθμίσεις για το backend.', 'errJSON' : 'Το PHP JSON module δεν είναι εγκατεστημένο.', 'errNoVolumes' : 'Δεν βρέθηκαν αναγνώσιμα volumes.', 'errCmdParams' : 'Μη έγκυρες παράμετροι για την εντολή "$1".', 'errDataNotJSON' : 'Τα δεδομένα δεν είναι JSON.', 'errDataEmpty' : 'Τα δεδομένα είναι άδεια.', 'errCmdReq' : 'Το Backend request χρειάζεται όνομα εντολής.', 'errOpen' : 'Δεν ήταν δυνατό να ανοίξει το "$1".', 'errNotFolder' : 'Το αντικείμενο δεν είναι φάκελος.', 'errNotFile' : 'Το αντικείμενο δεν είναι αρχείο.', 'errRead' : 'Δεν ήταν δυνατόν να διαβαστεί το "$1".', 'errWrite' : 'Δεν ήταν δυνατή η εγγραφή στο "$1".', 'errPerm' : 'Απαγορεύεται η πρόσβαση.', 'errLocked' : '"$1" είναι κλειδωμένο και δεν μπορεί να μετονομαστεί, μετακινηθεί ή διαγραφεί.', 'errExists' : 'Το αρχείο με όνομα "$1" υπάρχει ήδη.', 'errInvName' : 'Μη έγκυρο όνομα αρχείου.', 'errFolderNotFound' : 'Ο φάκελος δε βρέθηκε.', 'errFileNotFound' : 'Το αρχείο δε βρέθηκε.', 'errTrgFolderNotFound' : 'Ο φάκελος "$1" δε βρέθηκε.', 'errPopup' : 'Το πρόγραμμα πλήγησης εμπόδισε το άνοιγμα αναδυόμενου παραθύρου. Για ανοίξετε το αρχείο ενεργοποιήστε το στις επιλογές του περιηγητή.', 'errMkdir' : 'Η δυμιουργία του φακέλου "$1" δεν ήταν δυνατή.', 'errMkfile' : 'Η δημιουργία του αρχείου "$1" δεν ήταν δυνατή.', 'errRename' : 'Η μετονομασία του αρχείου "$1" δεν ήταν δυνατή.', 'errCopyFrom' : 'Δεν επιτρέπεται η αντιγραφή αρχείων από το volume "$1".', 'errCopyTo' : 'Δεν επιτρέπεται η αντιγραφή αρχείων στο volume "$1".', 'errUpload' : 'Πρόβλημα κατά το upload.', 'errUploadFile' : 'Το αρχείο "$1" δεν μπόρεσε να γίνει upload.', 'errUploadNoFiles' : 'Δεν βρέθηκαν αρχεία για upload.', 'errUploadTotalSize' : 'Τα δεδομένα υπερβαίνουν το επιτρεπόμενο μέγιστο μέγεθος δεδομένων.', 'errUploadFileSize' : 'Το αρχείο υπερβαίνει το επιτρεπόμενο μέγιστο μέγεθος.', 'errUploadMime' : 'Ο τύπος αρχείου δεν επιτρέπεται.', 'errUploadTransfer' : 'Πρόβλημα μεταφοράς για το "$1".', 'errNotReplace' : 'Object "$1" already exists at this location and can not be replaced by object with another type.', 'errReplace' : 'Unable to replace "$1".', 'errSave' : 'Το "$1" δεν ήταν δυνατόν να αποθηκευτεί.', 'errCopy' : 'Δεν ήταν δυνατή η αντιγραφή του "$1".', 'errMove' : 'Δεν ήταν δυνατή η μετακίνηση του "$1".', 'errCopyInItself' : 'Δεν είναι δυνατή η αντιγραφή του "$1" στον εαυτό του.', 'errRm' : 'Δεν ήταν δυνατή η αφαίρεση του "$1".', 'errRmSrc' : 'Unable remove source file(s).', 'errExtract' : 'Δεν ήταν δυνατή η ανάγνωση των αρχείων από "$1".', 'errArchive' : 'Δεν ήταν δυνατή η δημιουργία του αρχείου.', 'errArcType' : 'Ο τύπος αρχείου δεν υποστηρίζεται.', 'errNoArchive' : 'Το αρχείο δεν είναι έγκυρο ή δεν υποστηρίζεται ο τύπος του.', 'errCmdNoSupport' : 'Το backend δεν υποστηρίζει αυτή την εντολή.', 'errReplByChild' : 'Ο φάκελος “$1” δεν μπορεί να αντικατασταθεί από οποιοδήποτε αρχείο περιέχεται σε αυτόν.', 'errArcSymlinks' : 'Για λόγους ασφαλείας δεν είναι δυνατόν να διαβαστούν αρχεία που περιέχουν symlinks orη αρχεία με μη επιτρεπτά ονόματα.', // edited 24.06.2012 'errArcMaxSize' : 'Το μέγεθος του αρχείου υπερβαίνει το μέγιστο επιτρεπτό όριο.', 'errResize' : 'Δεν ήταν δυνατή η αλλαγή μεγέθους του "$1".', 'errResizeDegree' : 'Invalid rotate degree.', 'errResizeRotate' : 'Unable to rotate image.', 'errResizeSize' : 'Invalid image size.', 'errResizeNoChange' : 'Image size not changed.', 'errUsupportType' : 'Ο τύπος αρχείου δεν υποστηρίζεται.', 'errNotUTF8Content' : 'Το αρχείο "$1" δεν είναι UTF-8 και δεν μπορεί να επεξεργασθεί.', // added 9.11.2011 'errNetMount' : 'Δεν ήταν δυνατή η φόρτωση του "$1".', // added 17.04.2012 'errNetMountNoDriver' : 'Μη υποστηριζόμενο πρωτόκολο.', // added 17.04.2012 'errNetMountFailed' : 'Η φόρτωση απέτυχε.', // added 17.04.2012 'errNetMountHostReq' : 'Απαιτείται host εξυπηρετητής.', // added 18.04.2012 'errSessionExpires' : 'Your session has expired due to inactivity.', 'errCreatingTempDir' : 'Unable to create temporary directory: "$1"', 'errFtpDownloadFile' : 'Unable to download file from FTP: "$1"', 'errFtpUploadFile' : 'Unable to upload file to FTP: "$1"', 'errFtpMkdir' : 'Unable to create remote directory on FTP: "$1"', 'errArchiveExec' : 'Error while archiving files: "$1"', 'errExtractExec' : 'Error while extracting files: "$1"', /******************************* commands names ********************************/ 'cmdarchive' : 'Δημιουργία archive αρχείου', 'cmdback' : 'Πίσω', 'cmdcopy' : 'Αντιγραφή', 'cmdcut' : 'Αφαίρεση', 'cmddownload' : 'Μεταφόρτωση', 'cmdduplicate' : 'Αντίγραφο', 'cmdedit' : 'Επεξεργασία αρχείου', 'cmdextract' : 'Εξαγωγή αρχείων από archive', 'cmdforward' : 'Προώθηση', 'cmdgetfile' : 'Επιλέξτε αρχεία', 'cmdhelp' : 'Σχετικά με αυτό το λογισμικό', 'cmdhome' : 'Home', 'cmdinfo' : 'Πληροφορίες', 'cmdmkdir' : 'Νέος φάκελος', 'cmdmkfile' : 'Νέος αρχείο', 'cmdopen' : 'Άνοιγμα', 'cmdpaste' : 'Επικόλληση', 'cmdquicklook' : 'Προεπισκόπηση', 'cmdreload' : 'Ανανέωση', 'cmdrename' : 'Μετονομασία', 'cmdrm' : 'Διαγραφή', 'cmdsearch' : 'Έυρεση αρχείων', 'cmdup' : 'Μετάβαση στο γονικό φάκελο', 'cmdupload' : 'Ανέβασμα αρχείων', 'cmdview' : 'Προβολή', 'cmdresize' : 'Αλλαγή μεγέθους εικόνας', 'cmdsort' : 'Ταξινόμηση', 'cmdnetmount' : 'Mount network volume', /*********************************** buttons ***********************************/ 'btnClose' : 'Κλείσιμο', 'btnSave' : 'Αποθήκευση', 'btnRm' : 'Αφαίρεση', 'btnApply' : 'Εφαρμογή', 'btnCancel' : 'Ακύρωση', 'btnNo' : 'Όχι', 'btnYes' : 'Ναι', 'btnMount' : 'Mount', /******************************** notifications ********************************/ 'ntfopen' : 'Άνοιγμα φακέλου', 'ntffile' : 'Άνοιγμα αρχείου', 'ntfreload' : 'Ανανέωση περιεχομένων φακέλου', 'ntfmkdir' : 'Δημιουργία φακέλου', 'ntfmkfile' : 'Δημιουργία αρχείων', 'ntfrm' : 'Διαγραφή αρχείων', 'ntfcopy' : 'Αντιγραφή αρχείων', 'ntfmove' : 'Μετακίνηση αρχείων', 'ntfprepare' : 'Προετοιμασία αντιγραφής αρχείων', 'ntfrename' : 'Μετονομασία αρχείων', 'ntfupload' : 'Ανέβασμα αρχείων', 'ntfdownload' : 'Μεταφόρτωση αρχείων', 'ntfsave' : 'Αποθήκευση αρχείων', 'ntfarchive' : 'Δημιουργία αρχείου', 'ntfextract' : 'Εξαγωγή αρχείων από το archive', 'ntfsearch' : 'Αναζήτηση αρχείων', 'ntfresize' : 'Resizing images', 'ntfsmth' : 'Σύστημα απασχολημένο>_<', 'ntfloadimg' : 'Φόρτωση εικόνας', 'ntfnetmount' : 'Φόρτωση δικτυακού δίσκου', // added 18.04.2012 'ntfdim' : 'Acquiring image dimension', /************************************ dates **********************************/ 'dateUnknown' : 'άγνωστο', 'Today' : 'Σήμερα', 'Yesterday' : 'Χθές', 'msJan' : 'Ιαν', 'msFeb' : 'Φεβ', 'msMar' : 'Μαρ', 'msApr' : 'Απρ', 'msMay' : 'Μαϊ', 'msJun' : 'Ιουν', 'msJul' : 'Ιουλ', 'msAug' : 'Αυγ', 'msSep' : 'Σεπ', 'msOct' : 'Οκτ', 'msNov' : 'Νοεμ', 'msDec' : 'Δεκ', 'January' : 'Ιανουάριος', 'February' : 'Φεβρουάριος', 'March' : 'Μάρτιος', 'April' : 'Απρίλιος', 'May' : 'Μάϊος', 'June' : 'Ιούνιος', 'July' : 'Ιούλιος', 'August' : 'Αύγουστος', 'September' : 'Σεπτέμβριος', 'October' : 'Οκτώβριος', 'November' : 'Νοέμβριος', 'December' : 'Δεκέμβριος', 'Sunday' : 'Κυριακή', 'Monday' : 'Δευτέρα', 'Tuesday' : 'Τρίτη', 'Wednesday' : 'Τετάρτη', 'Thursday' : 'Πέμπτη', 'Friday' : 'Παρασκευή', 'Saturday' : 'Σάββατο', 'Sun' : 'Κυρ', 'Mon' : 'Δευ', 'Tue' : 'Τρ', 'Wed' : 'Τετ', 'Thu' : 'Πεμ', 'Fri' : 'Παρ', 'Sat' : 'Σαβ', /******************************** sort variants ********************************/ 'sortname' : 'κατά όνομα', 'sortkind' : 'κατά είδος', 'sortsize' : 'κατά μέγεθος', 'sortdate' : 'κατά ημερομηνία', 'sortFoldersFirst' : 'Πρώτα οι φάκελοι', // added 22.06.2012 /********************************** messages **********************************/ 'confirmReq' : 'Απαιτείται επιβεβαίωση', 'confirmRm' : 'Είστε σίγουροι πως θέλετε να διαγράψετε τα αρχεία?
Οι αλλαγές θα είναι μόνιμες!', 'confirmRepl' : 'Αντικατάσταση του παλιού αρχείου με το νέο?', 'apllyAll' : 'Εφαρμογή σε όλα', 'name' : 'Όνομα', 'size' : 'Μέγεθος', 'perms' : 'Δικαιώματα', 'modify' : 'Τροποποιήθηκε', 'kind' : 'Είδος', 'read' : 'ανάγνωση', 'write' : 'εγγραφή', 'noaccess' : 'δεν υπάρχει πρόσβαση', 'and' : 'και', 'unknown' : 'άγνωστο', 'selectall' : 'Επιλογή όλων', 'selectfiles' : 'Επιλογή αρχείων', 'selectffile' : 'Επιλογή πρώτου αρχείου', 'selectlfile' : 'Επιλογή τελευταίου αρχείου', 'viewlist' : 'Προβολή λίστας', 'viewicons' : 'Προβολή εικονιδίων', 'places' : 'Τοποθεσίες', 'calc' : 'Υπολογισμός', 'path' : 'Διαδρομή', 'aliasfor' : 'Ψευδώνυμο για', 'locked' : 'Κλειδωμένο', 'dim' : 'Διαστάσεις', 'files' : 'Αρχεία', 'folders' : 'Φάκελοι', 'items' : 'Αντικείμενα', 'yes' : 'ναι', 'no' : 'όχι', 'link' : 'Σύνδεσμος', 'searcresult' : 'Αποτελέσματα αναζήτησης', 'selected' : 'επιλεγμένα αντικείμενα', 'about' : 'Σχετικά', 'shortcuts' : 'Συντομεύσεις', 'help' : 'Βοήθεια', 'webfm' : 'εργαλείο διαχείρισης αρχείων από το web', 'ver' : 'Έκδοση', 'protocolver' : 'έκδοση πρωτοκόλλου', 'homepage' : 'Σελίδα του project', 'docs' : 'Τεκμηρίωση (documentation)', 'github' : 'Κάντε μας fork στο Github', 'twitter' : 'Ακολουθήστε μας στο twitter', 'facebook' : 'Βρείτε μας στο facebook', 'team' : 'Ομάδα', 'chiefdev' : 'κύριος προγραμματιστής', 'developer' : 'προγραμματιστής', 'contributor' : 'συνεισφορά', 'maintainer' : 'συντηρητής', 'translator' : 'μεταφραστής', 'icons' : 'Εικονίδια', 'dontforget' : 'και μην ξεχάσεις την πετσέτα σου!', 'shortcutsof' : 'Οι συντομεύσεις είναι απενεργοποιημένες', 'dropFiles' : 'Κάντε drop τα αρχεία εδώ', 'or' : 'ή', 'selectForUpload' : 'Επιλογή αρχείων για ανέβασμα', 'moveFiles' : 'Μετακίνηση αρχείων', 'copyFiles' : 'Αντιγραφή αρχείων', 'rmFromPlaces' : 'Αντιγραφή από τοποθεσίες', 'aspectRatio' : 'Αναλογία διαστάσεων', 'scale' : 'Κλίμακα', 'width' : 'Πλάτος', 'height' : 'Ύψος', 'resize' : 'Αλλαγή μεγέθους', 'crop' : 'Crop', 'rotate' : 'Περιστροφή', 'rotate-cw' : 'Περιστροφή κατά 90 βαθμούς CW', 'rotate-ccw' : 'Περιστροφή κατά 90 βαθμούς CCW', 'degree' : 'Βαθμός', 'netMountDialogTitle' : 'Φορτώστε δικτυακό δίσκο', // added 18.04.2012 'protocol' : 'Πρωτόκολλο', // added 18.04.2012 'host' : 'Host', // added 18.04.2012 'port' : 'Port', // added 18.04.2012 'user' : 'Χρήστης', // added 18.04.2012 'pass' : 'Κωδικός', // added 18.04.2012 /********************************** mimetypes **********************************/ 'kindUnknown' : 'Άγνωστο', 'kindFolder' : 'Φάκελος', 'kindAlias' : 'Ψευδώνυμο (alias)', 'kindAliasBroken' : 'Μη έγκυρο ψευδώνυμο', // applications 'kindApp' : 'Εφαρμογή', 'kindPostscript' : 'Έγγραφο Postscript', 'kindMsOffice' : 'Έγγραφο Microsoft Office', 'kindMsWord' : 'Έγγραφο Microsoft Word', 'kindMsExcel' : 'Έγγραφο Microsoft Excel', 'kindMsPP' : 'Παρουσίαση Microsoft Powerpoint', 'kindOO' : 'Έγγραφο Open Office', 'kindAppFlash' : 'Εφαρμογή Flash', 'kindPDF' : 'Portable Document Format (PDF)', 'kindTorrent' : 'Αρχείο Bittorrent', 'kind7z' : 'Αρχείο 7z', 'kindTAR' : 'Αρχείο TAR', 'kindGZIP' : 'Αρχείο GZIP', 'kindBZIP' : 'Αρχείο BZIP', 'kindXZ' : 'Αρχείο XZ', 'kindZIP' : 'Αρχείο ZIP', 'kindRAR' : 'Αρχείο RAR', 'kindJAR' : 'Αρχείο Java JAR', 'kindTTF' : 'Γραμματοσειρά True Type', 'kindOTF' : 'Γραμματοσειρά Open Type', 'kindRPM' : 'Πακέτο RPM', // texts 'kindText' : 'Έγγραφο κειμένου', 'kindTextPlain' : 'Απλό κείμενο', 'kindPHP' : 'Κώδικας PHP', 'kindCSS' : 'Cascading style sheet', 'kindHTML' : 'Έγγραφο HTML', 'kindJS' : 'Κώδικας Javascript', 'kindRTF' : 'Rich Text Format', 'kindC' : 'Κώδικας C', 'kindCHeader' : 'Κώδικας κεφαλίδας C', 'kindCPP' : 'Κώδικας C++', 'kindCPPHeader' : 'Κώδικας κεφαλίδας C++', 'kindShell' : 'Unix shell script', 'kindPython' : 'Κώδικας Python', 'kindJava' : 'Κώδικας Java', 'kindRuby' : 'Κώδικας Ruby', 'kindPerl' : 'Perl script', 'kindSQL' : 'Κώδικας SQL', 'kindXML' : 'Έγγραφο XML', 'kindAWK' : 'Κώδικας AWK', 'kindCSV' : 'Τιμές χωρισμένες με κόμμα', 'kindDOCBOOK' : 'Έγγραφο Docbook XML', // images 'kindImage' : 'Εικόνα', 'kindBMP' : 'Εικόνα BMP', 'kindJPEG' : 'Εικόνα JPEG', 'kindGIF' : 'Εικόνα GIF', 'kindPNG' : 'Εικόνα PNG', 'kindTIFF' : 'Εικόνα TIFF', 'kindTGA' : 'Εικόνα TGA', 'kindPSD' : 'Εικόνα Adobe Photoshop', 'kindXBITMAP' : 'Εικόνα X bitmap', 'kindPXM' : 'Εικόνα Pixelmator', // media 'kindAudio' : 'Αρχεία ήχου', 'kindAudioMPEG' : 'Ήχος MPEG', 'kindAudioMPEG4' : 'Εικόνα MPEG-4', 'kindAudioMIDI' : 'Εικόνα MIDI', 'kindAudioOGG' : 'Εικόνα Ogg Vorbis', 'kindAudioWAV' : 'Εικόνα WAV', 'AudioPlaylist' : 'MP3 playlist', 'kindVideo' : 'Αρχεία media', 'kindVideoDV' : 'Ταινία DV', 'kindVideoMPEG' : 'Ταινία MPEG', 'kindVideoMPEG4' : 'Ταινία MPEG-4', 'kindVideoAVI' : 'Ταινία AVI', 'kindVideoMOV' : 'Ταινία Quick Time', 'kindVideoWM' : 'Ταινία Windows Media', 'kindVideoFlash' : 'Ταινία flash', 'kindVideoMKV' : 'Ταινία matroska', 'kindVideoOGG' : 'Ταινία ogg' } }; })); if (!defined('ABSPATH')) die('No direct access allowed'); if (!defined('WP_OPTIMIZE_MINIFY_DIR')) { die('No direct access.'); } if (!function_exists('wpo_delete_files')) { include WPO_PLUGIN_MAIN_PATH.'cache/file-based-page-cache-functions.php'; } class WP_Optimize_Minify_Cache_Functions { /** * Fix the permission bits on generated files * * @param String $file - full path to a file */ public static function fix_permission_bits($file) { if (function_exists('stat')) { if ($stat = stat(dirname($file))) { $perms = $stat['mode'] & 0007777; chmod($file, $perms); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- N/A clearstatcache(); return true; } } // Get permissions from parent directory $perms = 0777; if (function_exists('stat')) { if ($stat = stat(dirname($file))) { $perms = $stat['mode'] & 0007777; } } if (file_exists($file)) { if (($perms & ~umask() != $perms)) { $folder_parts = explode('/', substr($file, strlen(dirname($file)) + 1)); for ($i = 1, $c = count($folder_parts); $i <= $c; $i++) { chmod(dirname($file) . '/' . implode('/', array_slice($folder_parts, 0, $i)), $perms); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- N/A } } } return true; } /** * Get cache directories and urls * * @return Array */ public static function cache_path() { // get latest time stamp $cache_time = wp_optimize_minify_config()->get('last-cache-update'); $cache_base_dir = WPO_CACHE_MIN_FILES_DIR . "/$cache_time"; $cache_dir_url = WPO_CACHE_MIN_FILES_URL . "/$cache_time/assets"; $tmp_dir = WPO_CACHE_MIN_FILES_DIR . "/tmp"; $header_dir = WPO_CACHE_MIN_FILES_DIR . "/$cache_time/header"; $cache_dir = WPO_CACHE_MIN_FILES_DIR . "/$cache_time/assets"; // Create directories $dirs = array($cache_dir, $tmp_dir, $header_dir); foreach ($dirs as $target) { $enabled = wp_optimize_minify_config()->get('enabled'); if (false === $enabled) break; if (!is_dir($target) && !wp_mkdir_p($target)) { error_log('WP_Optimize_Minify_Cache_Functions::cache_path(): The folder "'.$target.'" could not be created.'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Used for debugging } } return array( 'tmpdir' => $tmp_dir, 'cachedir' => $cache_dir, 'cachedirurl' => $cache_dir_url, 'headerdir' => $header_dir, 'cachebasedir' => $cache_base_dir ); } /** * Increment file names */ public static function cache_increment() { $stamp = time(); wp_optimize_minify_config()->update(array( 'last-cache-update' => $stamp )); return $stamp; } /** * Reset the cache (Increment + purge temp files) */ public static function reset() { self::cache_increment(); self::purge_temp_files(); } /** * Will delete temporary intermediate stuff but leave final css/js alone for compatibility * * @return array */ public static function purge_temp_files() { // get cache directories and urls $cache_path = self::cache_path(); $tmp_dir = $cache_path['tmpdir']; $header_dir = $cache_path['headerdir']; // delete temporary directories only if (is_dir($tmp_dir)) { wpo_delete_files($tmp_dir, true); } if (is_dir($header_dir)) { wpo_delete_files($header_dir, true); } /** * Action triggered after purging temporary files */ do_action('wpo_min_after_purge_temp_files'); return array( 'tmpdir' => $tmp_dir, 'headerdir' => $header_dir, ); } /** * Purge supported hosting and plugins * * @return array An array of caches purged message */ public static function purge_others() { /** * Action triggered before purging other plugins cache */ do_action('wpo_min_before_purge_others'); // WordPress default cache if (function_exists('wp_cache_flush')) { wp_cache_flush(); } // Purge WP-Optimize $is_cache_purged = WP_Optimize()->get_page_cache()->purge(); if ($is_cache_purged) WP_Optimize()->get_page_cache()->file_log("Full Cache Purge triggered by: ". __METHOD__); // Store the messages of purged cache if it was successful $result = array(); // When plugins have a simple method, add them to the array ('Plugin Name' => 'method_name') $others = array( 'WP Super Cache' => 'wp_cache_clear_cache', 'W3 Total Cache' => 'w3tc_pgcache_flush', 'WP Fastest Cache' => 'wpfc_clear_all_cache', 'WP Rocket' => 'rocket_clean_domain', 'Cachify' => 'cachify_flush_cache', 'Comet Cache' => array('comet_cache', 'clear'), 'SG Optimizer' => 'sg_cachepress_purge_cache', 'Pantheon' => 'pantheon_wp_clear_edge_all', 'Zen Cache' => array('zencache', 'clear'), 'Breeze' => array('Breeze_PurgeCache', 'breeze_cache_flush'), 'Swift Performance' => array('Swift_Performance_Cache', 'clear_all_cache'), ); foreach ($others as $plugin => $method) { if (is_callable($method)) { call_user_func($method); $result[] = self::get_caches_purged_message($plugin); } } // Purge LiteSpeed Cache if (is_callable(array('LiteSpeed_Cache_Tags', 'add_purge_tag'))) { LiteSpeed_Cache_Tags::add_purge_tag('*'); $result[] = self::get_caches_purged_message('LiteSpeed Cache'); } // Purge Hyper Cache if (class_exists('HyperCache')) { do_action('autoptimize_action_cachepurged'); $result[] = self::get_caches_purged_message('Hyper Cache'); } // Purge Godaddy Managed WordPress Hosting (Varnish + APC) if (class_exists('WPaaS\Plugin')) { self::godaddy_request('BAN'); // translators: %s is a remote cache system name `Go Daddy Varnish` $result[] = sprintf(__('A cache purge request has been sent to %s.', 'wp-optimize'), 'Go Daddy Varnish') . ' ' . __('Please note that it may not work every time, due to cache rate limiting by your host.', 'wp-optimize'); } // purge cache enabler if (has_action('ce_clear_cache')) { do_action('ce_clear_cache'); $result[] = self::get_caches_purged_message('Cache Enabler'); } // Purge WP Engine if (class_exists("WpeCommon")) { if (method_exists('WpeCommon', 'purge_memcached')) { WpeCommon::purge_memcached(); } if (method_exists('WpeCommon', 'clear_maxcdn_cache')) { WpeCommon::clear_maxcdn_cache(); } if (method_exists('WpeCommon', 'purge_varnish_cache')) { WpeCommon::purge_varnish_cache(); } if (method_exists('WpeCommon', 'purge_memcached') || method_exists('WpeCommon', 'clear_maxcdn_cache') || method_exists('WpeCommon', 'purge_varnish_cache')) { // translators: %s is a remote cache system name `WP Engine` $result[] = sprintf(__('A cache purge request has been sent to %s.', 'wp-optimize'), 'WP Engine') . ' ' . __('Please note that it may not work every time, due to cache rate limiting by your host.', 'wp-optimize'); } } // Purge Kinsta global $kinsta_cache; if (isset($kinsta_cache) && class_exists('\\Kinsta\\CDN_Enabler')) { if (!empty($kinsta_cache->kinsta_cache_purge) && is_callable(array($kinsta_cache->kinsta_cache_purge, 'purge_complete_caches'))) { $kinsta_cache->kinsta_cache_purge->purge_complete_caches(); $result[] = self::get_remote_caches_purged_message('Kinsta'); } } // Purge Pagely if (class_exists('PagelyCachePurge')) { $purge_pagely = new PagelyCachePurge(); if (is_callable(array($purge_pagely, 'purgeAll'))) { $purge_pagely->purgeAll(); $result[] = self::get_remote_caches_purged_message('Pagely'); } } // Purge Pressidum if (defined('WP_NINUKIS_WP_NAME') && class_exists('Ninukis_Plugin') && is_callable(array('Ninukis_Plugin', 'get_instance'))) { $purge_pressidum = Ninukis_Plugin::get_instance(); if (is_callable(array($purge_pressidum, 'purgeAllCaches'))) { $purge_pressidum->purgeAllCaches(); $result[] = self::get_remote_caches_purged_message('Pressidium'); } } // Purge Savvii if (defined('\Savvii\CacheFlusherPlugin::NAME_DOMAINFLUSH_NOW')) { $purge_savvii = new \Savvii\CacheFlusherPlugin(); if (is_callable(array($purge_savvii, 'domainflush'))) { $purge_savvii->domainflush(); $result[] = self::get_remote_caches_purged_message('Savvii'); } } /** * Action triggered when purging other plugins cache, and nothing was triggered */ do_action('wpo_min_after_purge_others'); return $result; } /** * Returns the purged cache message for the given plugin name * * @param string $plugin_name Name of the plugin * * @return string */ public static function get_caches_purged_message($plugin_name) { $message = sprintf( // translators: %s is a plugin name __('All caches from %s have also been purged.', 'wp-optimize'), '' . esc_html($plugin_name) . '' ); return $message; } /** * Returns remote purged cache message for given host name * * @param string $host_name * * @return string */ public static function get_remote_caches_purged_message($host_name) { $message = sprintf( // translators: %s is a remote cache system name __('A cache purge request has been sent to %s.', 'wp-optimize'), '' . esc_html($host_name) . '' ); return $message; } /** * Purge all public files on uninstallation * This will break cached pages that ref minified JS/CSS * * @return Boolean */ public static function purge() { $log = ''; if (is_dir(WPO_CACHE_MIN_FILES_DIR)) { if (wpo_delete_files(WPO_CACHE_MIN_FILES_DIR, true)) { $log = "[Minify] files and folders are deleted recursively"; } else { $log = "[Minify] recursive files and folders deletion unsuccessful"; } if (wp_optimize_minify_config()->get('debug')) { error_log($log); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Used for debugging } } return true; } /** * Purge cache files older than 30 days * * @return array */ public static function purge_old() { if (!class_exists('WP_Optimize_Minify_Config')) { include_once WPO_PLUGIN_MAIN_PATH . 'minify/class-wp-optimize-minify-config.php'; } $cache_time = wp_optimize_minify_config()->get('last-cache-update'); $cache_lifespan = wp_optimize_minify_config()->get('cache_lifespan'); /** * Minify cache lifespan * * @param int The minify cache expiry timestamp */ $expires = apply_filters('wp_optimize_minify_cache_expiry_time', time() - 86400 * $cache_lifespan); $log = array(); // get all directories that are a direct child of current directory if (is_dir(WPO_CACHE_MIN_FILES_DIR) && wp_is_writable(dirname(WPO_CACHE_MIN_FILES_DIR))) { if ($handle = opendir(WPO_CACHE_MIN_FILES_DIR)) { while (false !== ($d = readdir($handle))) { if (strcmp($d, '.')==0 || strcmp($d, '..')==0) { continue; } $log[] = "cache expiration time - $expires"; $log[] = "checking if cache has expired - $d"; if ($d != $cache_time && (is_numeric($d) && $d <= $expires)) { $dir = WPO_CACHE_MIN_FILES_DIR.'/'.$d; if (is_dir($dir)) { $log[] = "deleting cache in $dir"; if (wpo_delete_files($dir, true)) { $log[] = "files and folders are deleted recursively - $dir"; } else { $log[] = "recursive files and folders deletion unsuccessful - $dir"; } if (file_exists($dir)) { if (rmdir($dir)) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- N/A $log[] = "folder deleted successfully - $dir"; } else { $log[] = "folder deletion unsuccessful - $dir"; } } } } } closedir($handle); } } if (wp_optimize_minify_config()->get('debug')) { foreach ($log as $message) { error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Used for debugging } } return $log; } /** * Get transients from the disk * * @return String|Boolean */ public static function get_transient($key) { $cache_path = self::cache_path(); $tmp_dir = $cache_path['tmpdir']; $f = $tmp_dir.'/'.$key.'.transient'; clearstatcache(); if (file_exists($f)) { return file_get_contents($f); } else { return false; } } /** * Set cache on disk * * @param String $key * @param Mixed $code * * @return Boolean */ public static function set_transient($key, $code) { if (is_null($code) || empty($code)) { return false; } $cache_path = self::cache_path(); $tmp_dir = $cache_path['tmpdir']; $f = $tmp_dir.'/'.$key.'.transient'; file_put_contents($f, $code); self::fix_permission_bits($f); return true; } /** * Get the cache size and count * * @param string $folder * @return String */ public static function get_cachestats($folder) { clearstatcache(); if (is_dir($folder)) { $dir = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS)); $size = 0; $file_count = 0; foreach ($dir as $file) { $size += $file->getSize(); $file_count++; } return WP_Optimize()->format_size($size) . ' ('.$file_count.' files)'; } else { // translators: %s is a folder path return sprintf(__('Error: %s is not a directory!', 'wp-optimize'), $folder); } } /** * Purge GoDaddy Managed WordPress Hosting (Varnish) * * Source: https://github.com/wp-media/wp-rocket/blob/master/inc/3rd-party/hosting/godaddy.php * * @param String $method * @param String|Null $url */ public static function godaddy_request($method, $url = null) { $url = empty($url) ? home_url() : $url; $host = wp_parse_url($url, PHP_URL_HOST); $url = set_url_scheme(str_replace($host, WPaas\Plugin::vip(), $url), 'http'); wp_cache_flush(); update_option('gd_system_last_cache_flush', time()); // purge apc wp_remote_request(esc_url_raw($url), array('method' => $method, 'blocking' => false, 'headers' => array('Host' => $host))); } /** * List all cache files * * @param integer $stamp A timestamp * @param boolean $use_cache If true, do not use transient value * @return array */ public static function get_cached_files($stamp = 0, $use_cache = true) { if ($use_cache && $files = get_transient('wpo_minify_get_cached_files')) { return $files; } $cache_path = self::cache_path(); $cache_dir = $cache_path['cachedir']; $size = self::get_cachestats($cache_dir); $total_size = self::get_cachestats(WPO_CACHE_MIN_FILES_DIR); $o = wp_optimize_minify_config()->get(); $cache_time = (0 == $o['last-cache-update']) ? __('Never.', 'wp-optimize') : self::format_date_time($o['last-cache-update']); $return = array( 'js' => array(), 'css' => array(), 'stamp' => $stamp, 'cachesize' => esc_html($size), 'total_cache_size' => esc_html($total_size), 'cacheTime' => $cache_time, 'cachePath' => $cache_path['cachedir'] ); // Inspect directory with opendir, since glob might not be available in some systems clearstatcache(); if (is_dir($cache_dir.'/') && $handle = opendir($cache_dir.'/')) { while (false !== ($file = readdir($handle))) { $file = $cache_dir.'/'.$file; $ext = pathinfo($file, PATHINFO_EXTENSION); if (in_array($ext, array('js', 'css'))) { $log = self::generate_log($file.'.json' ); $min_css = substr($file, 0, -4).'.min.css'; $minjs = substr($file, 0, -3).'.min.js'; $file_name = basename($file); $file_url = trailingslashit($cache_path['cachedirurl']).$file_name; if ('css' == $ext && file_exists($min_css)) { $file_name = basename($min_css); } if ('js' == $ext && file_exists($minjs)) { $file_name = basename($minjs); } $file_size = WP_Optimize()->format_size(filesize($file)); $uid = hash('adler32', $file_name); array_push($return[$ext], array('uid' => $uid, 'filename' => $file_name, 'file_url' => $file_url, 'log' => $log, 'fsize' => $file_size)); } } closedir($handle); } set_transient('wpo_minify_get_cached_files', $return, DAY_IN_SECONDS); return $return; } /** * Generate log information from a json file. * * @param string $file Full path of log file. * * @return object Could be either a 'json_decode' object upon successful parsing of the JSON file, or a stdClass object * upon failure. In the case of stdClass object, $obj->error will contain the error message. */ public static function generate_log($file) { $error_log = new stdClass(); $cache_path = self::cache_path(); $file_name = basename($file); $file_url = trailingslashit($cache_path['cachedirurl']) . $file_name; $file_link_html = '' . $file_name . ''; if (!file_exists($file)) { // translators: %s is a log file link $error_log->error = sprintf(__('Log file %s is missing', 'wp-optimize'), $file_link_html); return $error_log; } $log = json_decode(file_get_contents($file)); $is_valid_json = json_last_error() === JSON_ERROR_NONE ? true : false; if (!$is_valid_json) { // translators: %1$s is a log file link, %2$s is the error message $error_log->error = sprintf(__('JSON error in file %1$s | Error details: %2$s', 'wp-optimize'), $file_link_html, json_last_error_msg()); return $error_log; } if (!isset($log->header) || !isset($log->files)) { // translators: %s is a log file link $error_log->error = sprintf(__('Some data is missing in the log file %s', 'wp-optimize'), $file_link_html); return $error_log; } return $log; } /** * Format a timestamp using WP's date_format and time_format * * @param integer $timestamp - The timestamp * @return string */ public static function format_date_time($timestamp) { return WP_Optimize()->format_date_time($timestamp); } /** * Format the log created when merging assets. Called via array_map * * @param array $files The files array, containing the 'log' object or array. * @return array */ public static function format_file_logs($files) { $files['log'] = WP_Optimize()->include_template( 'minify/cached-file-log.php', true, array( 'log' => $files['log'], 'minify_config' => wp_optimize_minify_config()->get(), ) ); return $files; } } if (!defined('ABSPATH')) die('No direct access allowed'); if (!defined('WP_OPTIMIZE_MINIFY_DIR')) { die('No direct access.'); } if (!function_exists('wpo_delete_files')) { include WPO_PLUGIN_MAIN_PATH.'cache/file-based-page-cache-functions.php'; } class WP_Optimize_Minify_Cache_Functions { /** * Fix the permission bits on generated files * * @param String $file - full path to a file */ public static function fix_permission_bits($file) { if (function_exists('stat')) { if ($stat = stat(dirname($file))) { $perms = $stat['mode'] & 0007777; chmod($file, $perms); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- N/A clearstatcache(); return true; } } // Get permissions from parent directory $perms = 0777; if (function_exists('stat')) { if ($stat = stat(dirname($file))) { $perms = $stat['mode'] & 0007777; } } if (file_exists($file)) { if (($perms & ~umask() != $perms)) { $folder_parts = explode('/', substr($file, strlen(dirname($file)) + 1)); for ($i = 1, $c = count($folder_parts); $i <= $c; $i++) { chmod(dirname($file) . '/' . implode('/', array_slice($folder_parts, 0, $i)), $perms); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- N/A } } } return true; } /** * Get cache directories and urls * * @return Array */ public static function cache_path() { // get latest time stamp $cache_time = wp_optimize_minify_config()->get('last-cache-update'); $cache_base_dir = WPO_CACHE_MIN_FILES_DIR . "/$cache_time"; $cache_dir_url = WPO_CACHE_MIN_FILES_URL . "/$cache_time/assets"; $tmp_dir = WPO_CACHE_MIN_FILES_DIR . "/tmp"; $header_dir = WPO_CACHE_MIN_FILES_DIR . "/$cache_time/header"; $cache_dir = WPO_CACHE_MIN_FILES_DIR . "/$cache_time/assets"; // Create directories $dirs = array($cache_dir, $tmp_dir, $header_dir); foreach ($dirs as $target) { $enabled = wp_optimize_minify_config()->get('enabled'); if (false === $enabled) break; if (!is_dir($target) && !wp_mkdir_p($target)) { error_log('WP_Optimize_Minify_Cache_Functions::cache_path(): The folder "'.$target.'" could not be created.'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Used for debugging } } return array( 'tmpdir' => $tmp_dir, 'cachedir' => $cache_dir, 'cachedirurl' => $cache_dir_url, 'headerdir' => $header_dir, 'cachebasedir' => $cache_base_dir ); } /** * Increment file names */ public static function cache_increment() { $stamp = time(); wp_optimize_minify_config()->update(array( 'last-cache-update' => $stamp )); return $stamp; } /** * Reset the cache (Increment + purge temp files) */ public static function reset() { self::cache_increment(); self::purge_temp_files(); } /** * Will delete temporary intermediate stuff but leave final css/js alone for compatibility * * @return array */ public static function purge_temp_files() { // get cache directories and urls $cache_path = self::cache_path(); $tmp_dir = $cache_path['tmpdir']; $header_dir = $cache_path['headerdir']; // delete temporary directories only if (is_dir($tmp_dir)) { wpo_delete_files($tmp_dir, true); } if (is_dir($header_dir)) { wpo_delete_files($header_dir, true); } /** * Action triggered after purging temporary files */ do_action('wpo_min_after_purge_temp_files'); return array( 'tmpdir' => $tmp_dir, 'headerdir' => $header_dir, ); } /** * Purge supported hosting and plugins * * @return array An array of caches purged message */ public static function purge_others() { /** * Action triggered before purging other plugins cache */ do_action('wpo_min_before_purge_others'); // WordPress default cache if (function_exists('wp_cache_flush')) { wp_cache_flush(); } // Purge WP-Optimize $is_cache_purged = WP_Optimize()->get_page_cache()->purge(); if ($is_cache_purged) WP_Optimize()->get_page_cache()->file_log("Full Cache Purge triggered by: ". __METHOD__); // Store the messages of purged cache if it was successful $result = array(); // When plugins have a simple method, add them to the array ('Plugin Name' => 'method_name') $others = array( 'WP Super Cache' => 'wp_cache_clear_cache', 'W3 Total Cache' => 'w3tc_pgcache_flush', 'WP Fastest Cache' => 'wpfc_clear_all_cache', 'WP Rocket' => 'rocket_clean_domain', 'Cachify' => 'cachify_flush_cache', 'Comet Cache' => array('comet_cache', 'clear'), 'SG Optimizer' => 'sg_cachepress_purge_cache', 'Pantheon' => 'pantheon_wp_clear_edge_all', 'Zen Cache' => array('zencache', 'clear'), 'Breeze' => array('Breeze_PurgeCache', 'breeze_cache_flush'), 'Swift Performance' => array('Swift_Performance_Cache', 'clear_all_cache'), ); foreach ($others as $plugin => $method) { if (is_callable($method)) { call_user_func($method); $result[] = self::get_caches_purged_message($plugin); } } // Purge LiteSpeed Cache if (is_callable(array('LiteSpeed_Cache_Tags', 'add_purge_tag'))) { LiteSpeed_Cache_Tags::add_purge_tag('*'); $result[] = self::get_caches_purged_message('LiteSpeed Cache'); } // Purge Hyper Cache if (class_exists('HyperCache')) { do_action('autoptimize_action_cachepurged'); $result[] = self::get_caches_purged_message('Hyper Cache'); } // Purge Godaddy Managed WordPress Hosting (Varnish + APC) if (class_exists('WPaaS\Plugin')) { self::godaddy_request('BAN'); // translators: %s is a remote cache system name `Go Daddy Varnish` $result[] = sprintf(__('A cache purge request has been sent to %s.', 'wp-optimize'), 'Go Daddy Varnish') . ' ' . __('Please note that it may not work every time, due to cache rate limiting by your host.', 'wp-optimize'); } // purge cache enabler if (has_action('ce_clear_cache')) { do_action('ce_clear_cache'); $result[] = self::get_caches_purged_message('Cache Enabler'); } // Purge WP Engine if (class_exists("WpeCommon")) { if (method_exists('WpeCommon', 'purge_memcached')) { WpeCommon::purge_memcached(); } if (method_exists('WpeCommon', 'clear_maxcdn_cache')) { WpeCommon::clear_maxcdn_cache(); } if (method_exists('WpeCommon', 'purge_varnish_cache')) { WpeCommon::purge_varnish_cache(); } if (method_exists('WpeCommon', 'purge_memcached') || method_exists('WpeCommon', 'clear_maxcdn_cache') || method_exists('WpeCommon', 'purge_varnish_cache')) { // translators: %s is a remote cache system name `WP Engine` $result[] = sprintf(__('A cache purge request has been sent to %s.', 'wp-optimize'), 'WP Engine') . ' ' . __('Please note that it may not work every time, due to cache rate limiting by your host.', 'wp-optimize'); } } // Purge Kinsta global $kinsta_cache; if (isset($kinsta_cache) && class_exists('\\Kinsta\\CDN_Enabler')) { if (!empty($kinsta_cache->kinsta_cache_purge) && is_callable(array($kinsta_cache->kinsta_cache_purge, 'purge_complete_caches'))) { $kinsta_cache->kinsta_cache_purge->purge_complete_caches(); $result[] = self::get_remote_caches_purged_message('Kinsta'); } } // Purge Pagely if (class_exists('PagelyCachePurge')) { $purge_pagely = new PagelyCachePurge(); if (is_callable(array($purge_pagely, 'purgeAll'))) { $purge_pagely->purgeAll(); $result[] = self::get_remote_caches_purged_message('Pagely'); } } // Purge Pressidum if (defined('WP_NINUKIS_WP_NAME') && class_exists('Ninukis_Plugin') && is_callable(array('Ninukis_Plugin', 'get_instance'))) { $purge_pressidum = Ninukis_Plugin::get_instance(); if (is_callable(array($purge_pressidum, 'purgeAllCaches'))) { $purge_pressidum->purgeAllCaches(); $result[] = self::get_remote_caches_purged_message('Pressidium'); } } // Purge Savvii if (defined('\Savvii\CacheFlusherPlugin::NAME_DOMAINFLUSH_NOW')) { $purge_savvii = new \Savvii\CacheFlusherPlugin(); if (is_callable(array($purge_savvii, 'domainflush'))) { $purge_savvii->domainflush(); $result[] = self::get_remote_caches_purged_message('Savvii'); } } /** * Action triggered when purging other plugins cache, and nothing was triggered */ do_action('wpo_min_after_purge_others'); return $result; } /** * Returns the purged cache message for the given plugin name * * @param string $plugin_name Name of the plugin * * @return string */ public static function get_caches_purged_message($plugin_name) { $message = sprintf( // translators: %s is a plugin name __('All caches from %s have also been purged.', 'wp-optimize'), '' . esc_html($plugin_name) . '' ); return $message; } /** * Returns remote purged cache message for given host name * * @param string $host_name * * @return string */ public static function get_remote_caches_purged_message($host_name) { $message = sprintf( // translators: %s is a remote cache system name __('A cache purge request has been sent to %s.', 'wp-optimize'), '' . esc_html($host_name) . '' ); return $message; } /** * Purge all public files on uninstallation * This will break cached pages that ref minified JS/CSS * * @return Boolean */ public static function purge() { $log = ''; if (is_dir(WPO_CACHE_MIN_FILES_DIR)) { if (wpo_delete_files(WPO_CACHE_MIN_FILES_DIR, true)) { $log = "[Minify] files and folders are deleted recursively"; } else { $log = "[Minify] recursive files and folders deletion unsuccessful"; } if (wp_optimize_minify_config()->get('debug')) { error_log($log); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Used for debugging } } return true; } /** * Purge cache files older than 30 days * * @return array */ public static function purge_old() { if (!class_exists('WP_Optimize_Minify_Config')) { include_once WPO_PLUGIN_MAIN_PATH . 'minify/class-wp-optimize-minify-config.php'; } $cache_time = wp_optimize_minify_config()->get('last-cache-update'); $cache_lifespan = wp_optimize_minify_config()->get('cache_lifespan'); /** * Minify cache lifespan * * @param int The minify cache expiry timestamp */ $expires = apply_filters('wp_optimize_minify_cache_expiry_time', time() - 86400 * $cache_lifespan); $log = array(); // get all directories that are a direct child of current directory if (is_dir(WPO_CACHE_MIN_FILES_DIR) && wp_is_writable(dirname(WPO_CACHE_MIN_FILES_DIR))) { if ($handle = opendir(WPO_CACHE_MIN_FILES_DIR)) { while (false !== ($d = readdir($handle))) { if (strcmp($d, '.')==0 || strcmp($d, '..')==0) { continue; } $log[] = "cache expiration time - $expires"; $log[] = "checking if cache has expired - $d"; if ($d != $cache_time && (is_numeric($d) && $d <= $expires)) { $dir = WPO_CACHE_MIN_FILES_DIR.'/'.$d; if (is_dir($dir)) { $log[] = "deleting cache in $dir"; if (wpo_delete_files($dir, true)) { $log[] = "files and folders are deleted recursively - $dir"; } else { $log[] = "recursive files and folders deletion unsuccessful - $dir"; } if (file_exists($dir)) { if (rmdir($dir)) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- N/A $log[] = "folder deleted successfully - $dir"; } else { $log[] = "folder deletion unsuccessful - $dir"; } } } } } closedir($handle); } } if (wp_optimize_minify_config()->get('debug')) { foreach ($log as $message) { error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Used for debugging } } return $log; } /** * Get transients from the disk * * @return String|Boolean */ public static function get_transient($key) { $cache_path = self::cache_path(); $tmp_dir = $cache_path['tmpdir']; $f = $tmp_dir.'/'.$key.'.transient'; clearstatcache(); if (file_exists($f)) { return file_get_contents($f); } else { return false; } } /** * Set cache on disk * * @param String $key * @param Mixed $code * * @return Boolean */ public static function set_transient($key, $code) { if (is_null($code) || empty($code)) { return false; } $cache_path = self::cache_path(); $tmp_dir = $cache_path['tmpdir']; $f = $tmp_dir.'/'.$key.'.transient'; file_put_contents($f, $code); self::fix_permission_bits($f); return true; } /** * Get the cache size and count * * @param string $folder * @return String */ public static function get_cachestats($folder) { clearstatcache(); if (is_dir($folder)) { $dir = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS)); $size = 0; $file_count = 0; foreach ($dir as $file) { $size += $file->getSize(); $file_count++; } return WP_Optimize()->format_size($size) . ' ('.$file_count.' files)'; } else { // translators: %s is a folder path return sprintf(__('Error: %s is not a directory!', 'wp-optimize'), $folder); } } /** * Purge GoDaddy Managed WordPress Hosting (Varnish) * * Source: https://github.com/wp-media/wp-rocket/blob/master/inc/3rd-party/hosting/godaddy.php * * @param String $method * @param String|Null $url */ public static function godaddy_request($method, $url = null) { $url = empty($url) ? home_url() : $url; $host = wp_parse_url($url, PHP_URL_HOST); $url = set_url_scheme(str_replace($host, WPaas\Plugin::vip(), $url), 'http'); wp_cache_flush(); update_option('gd_system_last_cache_flush', time()); // purge apc wp_remote_request(esc_url_raw($url), array('method' => $method, 'blocking' => false, 'headers' => array('Host' => $host))); } /** * List all cache files * * @param integer $stamp A timestamp * @param boolean $use_cache If true, do not use transient value * @return array */ public static function get_cached_files($stamp = 0, $use_cache = true) { if ($use_cache && $files = get_transient('wpo_minify_get_cached_files')) { return $files; } $cache_path = self::cache_path(); $cache_dir = $cache_path['cachedir']; $size = self::get_cachestats($cache_dir); $total_size = self::get_cachestats(WPO_CACHE_MIN_FILES_DIR); $o = wp_optimize_minify_config()->get(); $cache_time = (0 == $o['last-cache-update']) ? __('Never.', 'wp-optimize') : self::format_date_time($o['last-cache-update']); $return = array( 'js' => array(), 'css' => array(), 'stamp' => $stamp, 'cachesize' => esc_html($size), 'total_cache_size' => esc_html($total_size), 'cacheTime' => $cache_time, 'cachePath' => $cache_path['cachedir'] ); // Inspect directory with opendir, since glob might not be available in some systems clearstatcache(); if (is_dir($cache_dir.'/') && $handle = opendir($cache_dir.'/')) { while (false !== ($file = readdir($handle))) { $file = $cache_dir.'/'.$file; $ext = pathinfo($file, PATHINFO_EXTENSION); if (in_array($ext, array('js', 'css'))) { $log = self::generate_log($file.'.json' ); $min_css = substr($file, 0, -4).'.min.css'; $minjs = substr($file, 0, -3).'.min.js'; $file_name = basename($file); $file_url = trailingslashit($cache_path['cachedirurl']).$file_name; if ('css' == $ext && file_exists($min_css)) { $file_name = basename($min_css); } if ('js' == $ext && file_exists($minjs)) { $file_name = basename($minjs); } $file_size = WP_Optimize()->format_size(filesize($file)); $uid = hash('adler32', $file_name); array_push($return[$ext], array('uid' => $uid, 'filename' => $file_name, 'file_url' => $file_url, 'log' => $log, 'fsize' => $file_size)); } } closedir($handle); } set_transient('wpo_minify_get_cached_files', $return, DAY_IN_SECONDS); return $return; } /** * Generate log information from a json file. * * @param string $file Full path of log file. * * @return object Could be either a 'json_decode' object upon successful parsing of the JSON file, or a stdClass object * upon failure. In the case of stdClass object, $obj->error will contain the error message. */ public static function generate_log($file) { $error_log = new stdClass(); $cache_path = self::cache_path(); $file_name = basename($file); $file_url = trailingslashit($cache_path['cachedirurl']) . $file_name; $file_link_html = '' . $file_name . ''; if (!file_exists($file)) { // translators: %s is a log file link $error_log->error = sprintf(__('Log file %s is missing', 'wp-optimize'), $file_link_html); return $error_log; } $log = json_decode(file_get_contents($file)); $is_valid_json = json_last_error() === JSON_ERROR_NONE ? true : false; if (!$is_valid_json) { // translators: %1$s is a log file link, %2$s is the error message $error_log->error = sprintf(__('JSON error in file %1$s | Error details: %2$s', 'wp-optimize'), $file_link_html, json_last_error_msg()); return $error_log; } if (!isset($log->header) || !isset($log->files)) { // translators: %s is a log file link $error_log->error = sprintf(__('Some data is missing in the log file %s', 'wp-optimize'), $file_link_html); return $error_log; } return $log; } /** * Format a timestamp using WP's date_format and time_format * * @param integer $timestamp - The timestamp * @return string */ public static function format_date_time($timestamp) { return WP_Optimize()->format_date_time($timestamp); } /** * Format the log created when merging assets. Called via array_map * * @param array $files The files array, containing the 'log' object or array. * @return array */ public static function format_file_logs($files) { $files['log'] = WP_Optimize()->include_template( 'minify/cached-file-log.php', true, array( 'log' => $files['log'], 'minify_config' => wp_optimize_minify_config()->get(), ) ); return $files; } } Licensed online casino Archives - Pioneer Furnitures https://pioneerfurnitures.in/archives/category/licensed-online-casino Best furniture at the best price Fri, 29 Aug 2025 14:10:46 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.1 https://pioneerfurnitures.in/wp-content/uploads/2023/02/Pion_Logo-1-150x150.png Licensed online casino Archives - Pioneer Furnitures https://pioneerfurnitures.in/archives/category/licensed-online-casino 32 32 Mission Uncrossable Trick Strategies for Higher Wins https://pioneerfurnitures.in/archives/11807 https://pioneerfurnitures.in/archives/11807#respond Fri, 29 Aug 2025 14:10:46 +0000 https://pioneerfurnitures.in/archives/11807  Mission Uncrossable has rapidly become a favorite among Canadian players in online casinos. This intriguing casino game combines engaging

The post Mission Uncrossable Trick Strategies for Higher Wins appeared first on Pioneer Furnitures.

]]>


Mission Uncrossable has rapidly become a favorite among Canadian players in online casinos. This intriguing casino game combines engaging mechanics with opportunities for lucrative payouts, making it a must-try for enthusiasts seeking novel challenges. This review explores core strategies players can use to enhance their odds, delves into the game’s unique features, and highlights where Canadian players can experience this title.

Understanding Mission Uncrossable

At its core, Mission Uncrossable is a skill-based casino game that requires players to think critically and adapt their approach as the session progresses. Unlike traditional slot machines that rely heavily on chance, this game introduces puzzle-like elements where crossing certain lines or completing missions means unlocking higher rewards.

General Rules

Players start with a matrix of colored tiles where the objective is to strategically select paths without overlapping lines, thereby “uncrossing” and clearing stages. Each successfully cleared path awards points and progressive multipliers. However, crossing lines can reset the multiplier or end missions prematurely. This structure encourages cautious planning and attentive gameplay.

Interface Overview

The game interface is sleek and user-friendly, with clearly marked paths and intuitive touch controls compatible with computers and mobile devices. Graphical elements emphasize clarity, making it easy even for newcomers to understand which moves to make next. Visual feedback is immediate, highlighting successful uncrossable moves and alerting players when lines cross.

Top Trick Strategies for Higher Wins

Mission Uncrossable Trick Strategies for Higher Wins

Mission Uncrossable Trick
Applying smart tactics can significantly spike your winnings in Mission Uncrossable. Here are some proven strategies adopted by experienced players:

  1. Pattern Recognition: Identify recurring path formations early. This helps predict safe uncrossable moves, preserving multipliers and advancing missions.
  2. Conservative Progression: Avoid rushing for big plays. Prioritize smaller successful moves that maintain the multiplier and build cumulative rewards.
  3. Use Demo Mode to Practice: Utilize the demo mode to familiarize yourself with level patterns and reaction times without risking bankroll.
  4. Timed Decision-making: Don’t overthink moves but also avoid hasty decisions. A balanced pace maximizes accuracy while keeping you engaged.
  5. Leverage Bonuses Strategically: Use in-game bonus features as safety nets—triggering a bonus at the right moment can secure otherwise risky winnings.

Where to Play Mission Uncrossable in Canada

Several regulated online casinos welcome Canadian players and offer Mission Uncrossable as part of their game libraries. These platforms include top-tier providers with reliable payouts and licenses compliant with Kahnawake and other Canadian regulations.

Leading Casinos Offering Mission Uncrossable in Canada
Casino License Bonus Offer Mobile Friendly
Maple Wins Casino Kahnawake 100% up to CAD 500 + 50 free spins Yes
Northern Lights Gaming Malta Gaming Authority 75% match bonus + cashback Yes
True North Slots Kahnawake 150% welcome bonus + free demo access Yes

Frequently Asked Questions

How do I improve my chances of uncrossable success?

Focusing on pattern recognition and avoiding line crossings early on improves your success rate. Practicing in demo mode is highly recommended before committing real money.

Is Mission Uncrossable available on mobile devices?

Yes. The game is optimized for both Android and iOS devices, allowing Canadian players to enjoy it on the go.

Expert Feedback from an Experienced Player

“Mission Uncrossable blends strategy with an addictive puzzle approach unlike typical slots. The challenge of not crossing lines adds an element of skill that’s refreshing, especially for players tired of pure luck-based games. The interface guides your choices well, which helps even casual players improve quickly. My biggest tip is to use the demo mode extensively and never rush your moves—patience pays off.” ─ Jamie R., Canadian online casino enthusiast

Mission Uncrossable offers Canadian players a thrilling twist on casino games with engaging strategy elements and rewarding gameplay. By applying the outlined trick strategies such as cautious progression and pattern spotting, players can maximize their wins while enjoying a fresh gaming experience. Pick a trusted Canadian-friendly online casino and give Mission Uncrossable a try to test your skills and luck today.

The post Mission Uncrossable Trick Strategies for Higher Wins appeared first on Pioneer Furnitures.

]]>
https://pioneerfurnitures.in/archives/11807/feed 0
How to Play Plinko Game – BGaming’s UK Version Explained https://pioneerfurnitures.in/archives/11800 https://pioneerfurnitures.in/archives/11800#respond Thu, 28 Aug 2025 16:30:11 +0000 https://pioneerfurnitures.in/archives/11800  Plinko by BGaming is rapidly gaining traction among players in the United Kingdom, offering an enticing blend of simplicity

The post How to Play Plinko Game – BGaming’s UK Version Explained appeared first on Pioneer Furnitures.

]]>


Plinko by BGaming is rapidly gaining traction among players in the United Kingdom, offering an enticing blend of simplicity and excitement. This casino game takes inspiration from the popular television game show, adapting it into an online casino format that promises thrilling wins and engaging gameplay. In this article, we’ll delve into how to play Plinko, its unique features, and where UK players can enjoy this experience safely and profitably.

Plinko Game Review: An Overview of BGaming’s Unique Offering

At its core, Plinko is a drop-ball game where players choose a slot on the top of a pegged board, then watch as the ball bounces unpredictably down through numerous pins to land in one of several prize slots at the bottom. BGaming’s version captures this excitement with vibrant graphics, smooth mechanics, and tailored odds for a UK audience.

General Rules of Plinko

  • Players start by selecting their bet amount – typically ranging from small to high stakes depending on the casino.
  • The Plinko board consists of a triangular grid of pegs, with numbered slots at the bottom representing payout multipliers.
  • Players place the ball at the top, choosing which slot to drop it from.
  • As the ball bounces through the pins, it randomly lands in one of the payout slots at the bottom;
  • The prize awarded is based on the multiplier of the slot where the ball lands, multiplied by the player’s bet.

This straightforward approach makes Plinko accessible to everyone while maintaining enough unpredictability to keep players engaged.

Interface & Gameplay Experience

BGaming’s design is minimalistic yet polished, focusing on clarity and ease of use. Players see the whole Plinko board, with payout multipliers clearly displayed. A quick tap or click sets the ball in motion, accompanied by cheerful sound effects enhancing the suspense. The user-friendly interface makes it ideal for both beginners and seasoned gamblers looking for quick entertainment.

Where to Play Plinko in the UK

How to Play Plinko Game – BGaming’s UK Version Explained

How to Play Plinko Game
The game is available in many UK-licensed online casinos, ensuring compliance with stringent regulatory standards that guarantee fairness and security.

Online Casino Bonus Offers Mobile Compatibility Customer Support
SlotGuru Casino 100% Welcome Bonus up to £200 Fully Optimized 24/7 Live Chat
BritBet Casino 50 Bonus Spins on Plinko iOS & Android Supported Email & Chat Support
Royal Ace £50 No Deposit Bonus Mobile & Desktop Phone & Live Chat

These casinos incorporate responsible gambling features, secure payment options, and prompt customer service to enhance the player experience.

Frequently Asked Questions About Plinko

Is Plinko safe and fair for UK players?

Absolutely. BGaming operates under rigorous licensing and the algorithms use approved random number generators ensuring fair play.

Can I play Plinko for free?

Yes, many online casinos offer a demo mode where you can try Plinko without wagering real money. This is excellent for learning the dynamics before betting.

What’s the maximum payout multiplier in Plinko?

The top payout multiplier on the BGaming Plinko board can go up to 100x your bet, though this depends on the casino’s configured parameters.

Interview with a Player Who Won Big on Plinko at a UK Casino

Q: Congratulations on your big win! How did your Plinko experience start?

Tom, 34, London: “Thanks! I started playing Plinko casually after a friend recommended it. The simplicity attracted me immediately.”

Q: What was your strategy?

Tom: “Honestly, Plinko is mostly luck, but I observed the outcome trends and varied my drop slots. I also set limits to avoid chasing losses.”

Q: How much did you win?

Tom: “I wagered £10 and landed on a 75x payout slot. That made my win £750, which was incredible!”

Q: Would you recommend Plinko to other players?

Tom: “Definitely. It’s thrilling, easy to understand, and you don’t need complex strategies to enjoy it. Just play responsibly.”

Expert Feedback from a Casino Support Representative

Julia, Senior Support Agent at Royal Ace Casino, shares insights:

“Plinko stands out for its fast pace and simple interface, which appeals especially to UK players who enjoy quick wins and instant fun. We make sure to guide our players on how to use our demo mode before playing with real stakes. Our support is ready 24/7 to assist with any queries about gameplay or bonuses.”

BGaming’s Plinko offers an exhilarating and accessible casino game with straightforward rules and the potential for large payouts. Its growing presence in licensed UK casinos demonstrates trustworthiness and entertainment value. With demo versions available and plenty of support, Plinko is an excellent choice for both beginners and experienced UK gamblers alike.

If you’re looking for a fun, engaging way to test your luck with instant results, dive into Plinko today at one of the UK casinos mentioned above and see where the ball drops!

The post How to Play Plinko Game – BGaming’s UK Version Explained appeared first on Pioneer Furnitures.

]]>
https://pioneerfurnitures.in/archives/11800/feed 0
Big Bass Splash Slot Review: Dive into the Excitement with Pragmatic Play https://pioneerfurnitures.in/archives/11798 https://pioneerfurnitures.in/archives/11798#respond Thu, 28 Aug 2025 10:33:32 +0000 https://pioneerfurnitures.in/archives/11798  The online casino scene in the UK boasts a myriad of thrilling slot games, but few capture the essence

The post Big Bass Splash Slot Review: Dive into the Excitement with Pragmatic Play appeared first on Pioneer Furnitures.

]]>


The online casino scene in the UK boasts a myriad of thrilling slot games, but few capture the essence of aquatic adventure quite like Big Bass Splash by Pragmatic Play. This exciting title brings anglers a fresh burst of energy with its immersive gameplay and lucrative features. In this review, we’ll explore the game’s mechanics, appeal, and where British players can cast their nets for a chance to reel in big wins.

Overview of Big Bass Splash

Big Bass Splash combines the charm of fishing and the thrill of gambling into an engaging slot experience. Driven by Pragmatic Play’s robust game engine, it sports a vibrant aquatic theme, immersive sound effects, and fluid animations. The game features a 5-reel layout with up to 10,000 ways to win, thanks to the dynamic Megaways mechanic that keeps every spin fresh and unpredictable.

Game Design and Interface

The interface is intuitive and user-friendly, ensuring both novice and veteran players can easily navigate the options. Players will notice a clear display of the paytable, bet controls, and an autoplay function; The backdrop showcases a serene lake scene, complete with water ripples and animated fish, which adds a relaxing yet exciting vibe. The game is fully optimized for desktop and mobile devices, catering well to the UK audience who often prefer on-the-go gaming.

Gameplay and General Rules

Big Bass Splash Slot Review: Dive into the Excitement with Pragmatic Play

Big Bass Splash employs the popular Megaways feature, which modifies the number of symbols appearing on each reel every spin. This can result in up to 46,656 ways to win, amplifying the excitement. The main symbols include various fish species, fishing gear, and the elusive big bass symbol, which holds the key to unlocking bonus rounds.

  • Players begin by setting their bets, which range comfortably for casual and high-stakes players alike.
  • Landing special scatter symbols triggers the Hold and Spin bonus round, where players can collect fish symbols with multipliers.
  • The Mega Bass symbol can appear, enhancing the potential payout during free spins.

Demo Mode for Risk-Free Play

For those wishing to familiarize themselves before staking real money, Big Bass Splash offers a demo mode at many UK online casinos. This feature allows players to test the waters without financial commitment, understanding the game mechanics and special features risk-free.

Where to Play Big Bass Splash in the UK

UK players are spoiled for choice when it comes to selecting an online casino that hosts Big Bass Splash. Licensed casinos under the UK Gambling Commission provide a secure and regulated environment to enjoy the game. Popular choices include:

  • LeoVegas Casino: Renowned for its comprehensive game library and mobile-friendly interface.
  • Casumo: Famous for its quirky design and rewarding loyalty programs. big bass hold and spin megaways slot
  • Betway Casino: Offers smooth gameplay and excellent customer support.

Expert Feedback: Experienced Player’s Perspective

“Having spent considerable time playing Big Bass Splash, I appreciate the game’s blend of simplicity and depth,” shares Hannah, an experienced slot enthusiast from London. “The Hold and Spin feature keeps me engaged, while the Megaways mechanic adds unpredictability, meaning each spin feels unique. I especially like the theme—it’s refreshing compared to typical slot settings, and the mobile version runs flawlessly.”

Frequently Asked Questions About Big Bass Splash

Is Big Bass Splash available on mobile devices?

Yes, Pragmatic Play has optimized the game for both iOS and Android platforms, allowing smooth gameplay anywhere.

What is the RTP of Big Bass Splash?

The game offers a competitive RTP of approximately 96.71%, aligning well with other popular Megaways slots.

Can I play Big Bass Splash for free?

Absolutely. Many UK online casinos offer a demo version where no real money is required.

Comparing Big Bass Splash with Similar Slots

Game Provider RTP Main Feature Max Win
Big Bass Splash Pragmatic Play 96.71% Megaways & Hold and Spin 10,000x
Big Bass Bonanza Pragmatic Play 96.71% Free Spins & Multipliers 2,100x
Dead or Alive 2 NetEnt 96.8% Free Spins & Sticky Wilds 100,000x

Big Bass Splash by Pragmatic Play is an engaging and visually appealing slot game that stands out in the crowded UK online casino market. Its high volatility and dynamic Megaways system provide ample chances for substantial wins, especially during the rewarding Hold and Spin feature. With a solid RTP and compatibility across devices, it’s an excellent choice for players seeking fishing fun combined with exciting casino action.

Whether you’re a casual player or a seasoned gamer in the UK, Big Bass Splash is definitely worth exploring—don your fishing hat and dive deep for a splash of big wins today!

The post Big Bass Splash Slot Review: Dive into the Excitement with Pragmatic Play appeared first on Pioneer Furnitures.

]]>
https://pioneerfurnitures.in/archives/11798/feed 0
Big Bass Bonanza Hold and Spinner Demo Play Review https://pioneerfurnitures.in/archives/11791 https://pioneerfurnitures.in/archives/11791#respond Fri, 22 Aug 2025 07:05:34 +0000 https://pioneerfurnitures.in/archives/11791  Big Bass Bonanza by Pragmatic Play has become a favorite among UK players seeking an entertaining and rewarding online

The post Big Bass Bonanza Hold and Spinner Demo Play Review appeared first on Pioneer Furnitures.

]]>


Big Bass Bonanza by Pragmatic Play has become a favorite among UK players seeking an entertaining and rewarding online slot experience. This review dives into the Hold and Spinner feature‚ highlighting the gameplay‚ interface‚ and where you can enjoy the demo play version to sharpen your skills before betting real money.

Building upon the original Big Bass Bonanza slot‚ the Hold and Spinner variant adds a fresh twist with enhanced bonus mechanics. Players are charmed by the fishing theme‚ colorful graphics‚ and sound effects evoking a sunny day by the lake. The Hold and Spinner bonus rounds increase excitement and offer greater winning potential.

Gameplay and General Rules

The game features 5 reels and 10 paylines. Players aim to land matching symbols—fishing rods‚ fish‚ tackle boxes‚ and the fisherman himself—to win payouts. The standard wild symbol substitutes for others‚ boosting combinations.

The Hold and Spinner bonus triggers when at least three Scatter symbols (fishing rods) appear. During this bonus‚ certain reels are held in place while the others spin‚ increasing chances for additional Scatters and bonus prizes.

Bonus Features

  • Free Spins: Triggered by 3+ Scatters‚ awarding 10 free spins initially.
  • Hold and Spinner Bonus: Reels 2‚ 3‚ and 4 are held while reels 1 and 5 spin for more bonus symbols.
  • Money Symbols: Fishing-related prizes that accumulate cash values during free spins.

Where to Play Big Bass Bonanza Hold and Spinner in the UK

Big Bass Bonanza Hold and Spinner Demo Play Review

Big Bass Bonanza Hold and Spinner Demo Play

Several licensed UK online casinos offer this engaging title. Popular operators such as LeoVegas‚ Casumo‚ and VideoSlots provide easy access to demo play as well as real-money modes. Using the demo allows new players to explore the game’s mechanics without risk.

Demo Play Advantages

Practicing in demo mode‚ you can familiarize yourself with paytables‚ volatility‚ and bonus triggers before placing real bets. This aspect is particularly useful for UK players aiming to manage their bankroll responsibly.

Interface and Experience

Pragmatic Play designed Big Bass Bonanza Hold and Spinner with a clean‚ colorful interface that works seamlessly on both desktop and mobile devices. Betting controls are intuitive‚ and animations flow smoothly‚ ensuring an immersive fishing adventure.

The sound design features cheerful lakeside tunes and splashes that enhance the atmosphere without becoming distracting during longer sessions.

Expert Feedback

Experienced Player Perspective

John M.‚ a frequent slot player based in London‚ shared: “The Hold and Spinner feature really keeps me hooked. It adds strategy and suspense beyond the usual free spins. I appreciate the chance to rack up symbol multipliers while reels hold steady.”

Reporter’s Quick Take

CasinoReporterUK remarked: “Pragmatic Play strikes a nice balance between engaging visuals‚ smooth gameplay‚ and rewarding bonus rounds. This version of Big Bass Bonanza keeps the theme fresh and elevates player excitement effectively.”

Frequently Asked Questions about Big Bass Bonanza Hold and Spinner

  1. Is the game available on mobile devices?

    Yes‚ the slot is optimized for both iOS and Android platforms‚ ensuring a great experience on smartphones and tablets.
  2. Can I try the game for free?

    Most UK licensed casino sites provide a demo mode which lets you play without wagering real money.
  3. What is the RTP of Big Bass Bonanza Hold and Spinner?

    The return to player rate is approximately 96.71%‚ which is competitive among online slots.
  4. What is the maximum win?

    Players can win up to 10‚000x their stake during bonus features.

Big Bass Bonanza Hold and Spinner Game Parameters Table

Parameter Value
Developer Pragmatic Play
Reels 5
Paylines 10
RTP 96.71%
Max Win 10‚000x bet
Volatility Medium
Bonus Features Hold and Spinner‚ Free Spins‚ Money Symbols
Mobile Compatibility iOS & Android
Demo Available Yes

Final Thoughts

Big Bass Bonanza Hold and Spinner by Pragmatic Play is a dynamic slot that appeals to UK players both new and seasoned. Its Hold and Spinner mechanic introduces engaging gameplay twists that keep spins exciting and potential winnings substantial.

Thanks to its excellent interface‚ mobile compatibility‚ and accessible demo mode‚ it remains an excellent choice for those who want to experience quality online casino entertainment with a fishing theme.

The post Big Bass Bonanza Hold and Spinner Demo Play Review appeared first on Pioneer Furnitures.

]]>
https://pioneerfurnitures.in/archives/11791/feed 0
Review do Penalty Shoot Out da Evoplay no Brasil https://pioneerfurnitures.in/archives/11780 https://pioneerfurnitures.in/archives/11780#respond Mon, 18 Aug 2025 18:37:31 +0000 https://pioneerfurnitures.in/archives/11780  Para os fãs de jogos de cassino que também adoram futebol, Penalty Shoot Out da Evoplay oferece uma combinação

The post Review do Penalty Shoot Out da Evoplay no Brasil appeared first on Pioneer Furnitures.

]]>


Para os fãs de jogos de cassino que também adoram futebol, Penalty Shoot Out da Evoplay oferece uma combinação emocionante que está conquistando o público brasileiro. Este jogo online traz toda a emoção de uma disputa de pênaltis, com gráficos realistas e uma jogabilidade dinâmico que consegue capturar a tensão da disputa.

Como Jogar Penalty Shoot Out

O jogo é simples, mas envolvente: você assume o papel do batedor de pênaltis e precisa escolher cuidadosamente onde chutar para marcar o gol, enfrentando a defesa do goleiro virtual. Cada rodada oferece a chance de aumentar o seu prêmio, dependendo da precisão e estratégia do chute.

Regras Gerais

  • Escolher entre diferentes direções para chutar a bola;
  • O goleiro pode tentar defender de forma randômica ou com padrões que você deve tentar antecipar;
  • Acertos consecutivos aumentam o multiplicador dos ganhos;
  • Perder a cobrança pode acabar com a rodada e o prêmio é acumulado até ali.

Onde Jogar Penalty Shoot Out no Brasil

Review do Penalty Shoot Out da Evoplay no Brasil

Muitos cassinos online internacionais já suportam jogadores brasileiros e disponibilizam Penalty Shoot Out. Alguns oferecem versões em português e métodos populares de pagamento como Pix e boleto, facilitando o acesso dos jogadores do Brasil.

Entre os cassinos recomendados para jogar Penalty Shoot Out estão:

Casino Bônus de Boas-Vindas Métodos de Pagamento Suporte em Português
Betway 100% até R$300 Pix, Boleto, Cartão Sim
22Bet Até R$1500 + 150 Rodadas Grátis Pix, Transferência Bancária Sim
Bet365 100% até R$200 Pix, Cartão Sim

Interface e Experiência do Usuário

O design do Penalty Shoot Out impressiona pela simplicidade e pelo foco na jogabilidade. A interface é clara e intuitiva, com animações que aumentam a imersão sem atrapalhar a fluidez do jogo. Ideal para jogadores iniciantes e para quem quer uma partida rápida.

Demo para Treinar

Uma grande vantagem é a possibilidade de jogar a versão demo gratuitamente. Isso permite que você treine a mira e compreenda melhor o funcionamento antes de apostar dinheiro real, ideal para entender estratégias e sentir a mecânica do jogo. penalty shoot out aplicativo

Depoimento de um Jogador Sorteado

Conversamos com o Rafael, um jogador de São Paulo, que recentemente ganhou um prêmio considerável no Penalty Shoot Out:

“Eu sempre curti futebol, então quando encontrei o Penalty Shoot Out, fiquei animado. A emoção de simular uma cobrança real é incrível! Com um pouco de sorte e estratégia, consegui multiplicar meu saldo mais que o esperado – foi inesquecível;”

FAQ – Perguntas Frequentes sobre Penalty Shoot Out

Posso jogar Penalty Shoot Out pelo celular?
Sim, o jogo é totalmente responsivo e funciona bem em dispositivos móveis Android e iOS.
Existe truque ou estratégia para ganhar mais?
Embora seja um jogo com sorte envolvida, observar padrões do goleiro virtual pode ajudar a melhorar suas chances.
Posso sacar meus ganhos rapidamente?
Depende do cassino, mas as plataformas que aceitam Pix costumam liberar saques em minutos.

Por que a Popularidade do Penalty Shoot Out Cresce no Brasil?

O futebol é o esporte mais amado pelos brasileiros e criar um jogo de cassino que remete diretamente a esse universo foi um passo estratégico da Evoplay. A simplicidade da mecânica combinada com a emoção de cada cobrança torna o jogo atrativo para todos os perfis de usuários. A facilidade em encontrar o jogo em cassinos que oferecem suporte ao jogador brasileiro e métodos de pagamento locais, como o Pix, também contribui para essa popularidade crescente.

Tabela com os Principais Parâmetros do Jogo

Parâmetro Descrição
Fornecedor Evoplay
Tipo de Jogo Skill Game / Cassino Temático
Retorno ao Jogador (RTP) Até 95%
Aposta Mínima R$1,00
Aposta Máxima R$500,00
Plataformas Suportadas Desktop, Android, iOS

Considerações Finais

Penalty Shoot Out da Evoplay é uma experiência inovadora para quem gosta de combinar a emoção do esporte com o mundo dos cassinos online. Perfeito para brasileiros que procuram entretenimento aliado à chance de ganhar prêmios reais. Teste a versão gratuita para sentir a adrenalina e depois desafie a sorte nas apostas reais.

Se você ainda não provou, vale a pena dar uma chance e conferir a popularidade que o jogo vem conquistando, principalmente entre os fãs do futebol e jogos de azar.

The post Review do Penalty Shoot Out da Evoplay no Brasil appeared first on Pioneer Furnitures.

]]>
https://pioneerfurnitures.in/archives/11780/feed 0
Pourquoi Sweet Bonanza est un pilier chez Pragmatic Play https://pioneerfurnitures.in/archives/11774 https://pioneerfurnitures.in/archives/11774#respond Sun, 17 Aug 2025 14:02:16 +0000 https://pioneerfurnitures.in/archives/11774  Dans l’univers dynamique des casinos en ligne français, Sweet Bonanza se démarque comme une valeur sûre proposée par le

The post Pourquoi Sweet Bonanza est un pilier chez Pragmatic Play appeared first on Pioneer Furnitures.

]]>


Dans l’univers dynamique des casinos en ligne français, Sweet Bonanza se démarque comme une valeur sûre proposée par le développeur Pragmatic Play. Ce jeu de machine à sous aux fruits colorés et aux confiseries est devenu rapidement un classique grâce à son gameplay captivant et ses gains potentiellement élevés. Découvrez dans cet article pourquoi cette slot rafraîchissante continue d’attirer un large public et s’impose comme un incontournable.

Présentation générale de Sweet Bonanza

Créé par Pragmatic Play, Sweet Bonanza est une machine à sous vidéo au format 6×5 qui embarque les joueurs dans un univers sucré avec un principe de gains en “cluster pays”. Ce mode de paiement permet des combinaisons gagnantes dès que plusieurs symboles identiques sont connectés, peu importe leur position exacte sur les rouleaux.

Les règles principales

  • Le jeu se déroule sur six rouleaux avec cinq rangées.
  • Il n’y a pas de lignes classiques ; les gains sont attribués par groupes de symboles similaires d’au moins 8 pièces.
  • La fonction “Tumble” permet aux symboles gagnants de disparaître pour laisser place à de nouveaux symboles, offrant plusieurs gains consécutifs lors d’un même tour.
  • La possibilité d’acheter directement le bonus “Free Spins” garantit une session de tours gratuits avec des multiplicateurs pouvant aller jusqu’à x100.

Analyse de sa popularité en France

Pourquoi Sweet Bonanza est un pilier chez Pragmatic Play

L’engouement autour de Sweet Bonanza ne se dément pas. Plusieurs facteurs expliquent cette popularité :

  • Accessibilité : son interface simple et colorée attire aussi bien les joueurs novices que les plus expérimentés.
  • Potentiel de gains élevé : avec un RTP autour de 96,51 % et une volatilité moyenne à élevée, l’espoir de décrocher des gains importants est réel.
  • Compatibilité mobile : Sweet Bonanza est parfaitement optimisé pour une expérience fluide sur smartphones et tablettes, très prisés par la communauté française.

Retour d’expérience d’un joueur qui a gagné gros

Nous avons interviewé Mathieu, un joueur français qui a récemment décroché un gain notable sur Sweet Bonanza dans un casino en ligne réputé à Paris.

Mathieu : « J’aime ce jeu car il est rapide et sympa visuellement, mais surtout parce que les multiplicateurs en free spins m’ont permis de remporter plus de 2 000 € en quelques minutes. Le mode “Tumble” contribue vraiment à prolonger le suspense et l’excitation; »

Où jouer à Sweet Bonanza en France ?

Pour les joueurs français désireux de tenter leur chance sur Sweet Bonanza, il est essentiel de choisir des casinos en ligne fiables et sécurisés. Voici une sélection de plateformes populaires où ce jeu est disponible :

Casino Bonus de bienvenue Méthodes de paiement Licence
Casino Extra 100% jusqu’à 300€ + 50 tours gratuits Visa, MasterCard, PayPal, Cryptomonnaies ANJ (France)
Vera&John 150% jusqu’à 200€ + 20 tours gratuits Visa, Neteller, Skrill, virement bancaire Curacao
Unique Casino 100% jusqu’à 500€ + 200 tours gratuits Visa, MasterCard, PaySafeCard Curacao

Questions fréquentes sur Sweet Bonanza

1. Quel est le RTP de Sweet Bonanza ?

Le retour au joueur (RTP) est de 96,51 %, ce qui est assez élevé pour une slot avec une volatilité moyenne à élevée. sweet bonanza pragmatic play

2; Puis-je jouer gratuitement à Sweet Bonanza ?

Oui, de nombreux casinos en ligne offrent une version démo gratuite du jeu pour découvrir ses mécanismes sans mise réelle;

3. Comment activer les tours gratuits ?

Il faut obtenir au moins 4 symboles Scatter (sucreries multicolores) n’importe où sur l’écran pour lancer 10 tours gratuits, avec la possibilité de réactiver ce bonus pendant le tour.

Expert feedback : un développeur de Pragmatic Play s’exprime

Luc Moreau, concepteur de jeux chez Pragmatic Play, apporte un éclairage sur le succès de Sweet Bonanza :

« Nous avons voulu créer une expérience à la fois engageante et innovante. La mécanique de paiement en cluster combinée à la fonction tumble a définitivement plu, car elle offre un réel suspens tout en donnant du rythme. Le design coloré et le thème sucré ont aussi été pensés pour rendre l’expérience accessible à tous les profils de joueurs. »

Sweet Bonanza est plus qu’un simple jeu de machine à sous : c’est un succès durable qui a su séduire la communauté française grâce à son ambiance joyeuse, son gameplay original et les belles opportunités de gains. Que vous soyez débutant ou joueur aguerri, cette slot signée Pragmatic Play mérite une place de choix dans votre sélection.

Alors, prêt à goûter à la douceur explosive de Sweet Bonanza et tenter votre chance ?

The post Pourquoi Sweet Bonanza est un pilier chez Pragmatic Play appeared first on Pioneer Furnitures.

]]>
https://pioneerfurnitures.in/archives/11774/feed 0
The Double Game Experience in Teen Patti Gold Live Dealer Mode https://pioneerfurnitures.in/archives/11773 https://pioneerfurnitures.in/archives/11773#respond Sat, 16 Aug 2025 03:27:22 +0000 https://pioneerfurnitures.in/archives/11773  For players in Pakistan seeking a thrilling blend of card strategy and live casino excitement, Teen Patti Gold offers

The post The Double Game Experience in Teen Patti Gold Live Dealer Mode appeared first on Pioneer Furnitures.

]]>


For players in Pakistan seeking a thrilling blend of card strategy and live casino excitement, Teen Patti Gold offers a unique online experience. This live dealer version allows players to dive deep into the age-old Indian card game with real dealers, adding authenticity and spontaneity to every hand. The game’s rising popularity in the Pakistani market marks it as both entertaining and strategically rich.

What Makes Teen Patti Gold Live Dealer Stand Out?

Unlike traditional RNG-based Teen Patti variants, the live dealer mode brings you face-to-face with professional dealers through high-definition video streams. The immersive setting of a real casino table enhances trust and adds a layer of excitement unmatched by automated games.

General Rules

At its core, Teen Patti—also called Indian Poker—is played with a standard 52-card deck without jokers. Each player receives three cards and wagers accordingly, aiming to have the best three-card hand among the table or bluff their way to winning. Live dealer Teen Patti follows these original rules strictly, but with added real-time human interaction.

Interface and Playing Environment

The Double Game Experience in Teen Patti Gold Live Dealer Mode

The user interface for Teen Patti Gold Live Dealer is designed to be intuitive even for new players while providing all necessary controls to seasoned gamblers. Players can place bets, fold, or request card glimpses smoothly. Live chat enables direct communication with the dealer, fostering engagement and a community feel.

Where to Play

Several top online casinos with licenses suitable for Pakistani players now host Teen Patti Gold Live Dealer. These platforms ensure secure transactions through trusted local payment methods and comply with international fair play standards.

Interview with a Player Who Won Big at Teen Patti Gold

To capture the real pulse of Teen Patti Gold, we spoke with Ahmed R., a regular Pakistani player who hit a significant winning streak last month.

“The atmosphere created by the live dealer really changes the game. It’s not just cards; it’s the anticipation, the human tells, and the actual competition that makes me come back. My biggest win was a thrilling comeback from a weak hand, thanks to a well-timed bluff. That wouldn’t have been as exciting in a computerized environment.”

Ahmed’s Strategy Insights

  • Observe Patterns: Live dealers and other players’ behavior provide clues beyond the cards.
  • Balance Patience and Aggression: Knowing when to fold and when to push the bet is critical.
  • Stay Composed: Emotional control lets you leverage every opportunity effectively.

Expert Feedback: Experienced Player’s Perspective

According to Fatima L., a seasoned online gambler based in Karachi, Teen Patti Gold’s live dealer mode revolutionizes the traditional game by introducing real-time decisions with authentic human cues.

“The tactile feel of interacting with a dealer, combined with the social aspect of the game, keeps it fresh. While the interface is sleek on desktops and mobiles alike, the real charm lies in the unpredictability of human elements;”

Frequently Asked Questions (FAQ)

Is Teen Patti Gold Live Dealer fair for Pakistani players?
Yes, all reputable casinos offering this game use licensed live streaming studios that ensure fairness and transparency.
Can I try Teen Patti Gold Live Dealer for free before betting real money?
Most live dealer modes do not offer free demos due to real-time dealer involvement, but practice versions of Teen Patti Gold without live dealers are available for beginners.
What payment methods work best for players in Pakistan?
Popular options include e-wallets, credit/debit cards, and even cryptocurrencies on selected platforms.

Table with the Main Parameters of Teen Patti Gold Live Dealer

Feature Description
Game Provider Live Dealers (Various studios)
Game Type Live Dealer Card Game / Teen Patti
Minimum Bet Varies by Casino (Usually starts at 50 PKR)
Maximum Bet Depending on Table Limits, up to 100,000 PKR or more
Platform Support Desktop, Mobile (iOS & Android)
Languages English, Hindi, Urdu

Why is Teen Patti Gold Growing So Popular in Pakistan?

Several factors fuel the rapid growth of Teen Patti Gold in the Pakistani online gambling scene. First, there’s the cultural familiarity—Teen Patti has deep roots in South Asian card traditions. Players feel comfortable immediately, unlike more foreign-styled card games.

Second, the live dealer format bridges the gap between traditional land-based casinos and online convenience, allowing players to experience human interaction without leaving their homes. This boosts trust and enjoyment. game game teen patti gold

Demo Availability

Though the live dealer version is not usually available for free demo play, many platforms offer Teen Patti Gold in classic RNG mode. This serves as an excellent introduction for beginners to understand betting structures and card rankings before engaging in the more intense live games.

Final Thoughts

From interface quality to the thrill of human interaction, Teen Patti Gold Live Dealer is a compelling choice for online players in Pakistan looking for an authentic and engaging casino experience. The mixing of traditional game rules with a live environment adds layers of strategy and excitement that automated versions can’t replicate.

Whether you’re a seasoned Teen Patti veteran or a newcomer eager to test your skill against the dealer and other players, this online casino game provides a dynamic, accessible platform that reflects the rich heritage of Teen Patti through a modern lens.

The post The Double Game Experience in Teen Patti Gold Live Dealer Mode appeared first on Pioneer Furnitures.

]]>
https://pioneerfurnitures.in/archives/11773/feed 0