@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
Οι αλλαγές θα είναι μόνιμες!',
'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;
}
}
The post Le Bandit – Spelrecension och analyser av Hacksaw Gaming appeared first on Pioneer Furnitures.
]]>Le Bandit är ett spännande online casinospel utvecklat av Hacksaw Gaming, som har lyckats fånga många spelares intresse i Sverige. Spelet kombinerar en unik design med innovativa funktioner, vilket gör det till ett fräscht tillskott bland casinospel på nätet.
Le Bandit är ett slotsspel med en distinkt vilda västern-tema där spelare får följa med en lynnig bandit på jakt efter stora vinster. Spelet har tydliga, lättförståeliga regler och en väl utformad användargränssnitt som gör det enkelt för nybörjare att snabbt sätta sig in i hur det fungerar.
Spelet är uppbyggt med fem hjul och flera vinstlinjer, och innehåller wild-symboler, scatter-symboler samt en bonusfunktion som kan trigga free spins. Fokus ligger på att skapa spänning och variation i varje snurr, med många möjligheter till större utbetalningar tack vare multiplikatorer och progressiva funktioner.

Le Bandit finns tillgängligt i flera av Sveriges populäraste onlinecasinon som erbjuder spel från Hacksaw Gaming. Bland de mest rekommenderade plattformarna där du kan testa Le Bandit hittar du bland annat:
”Jag brukar spela lite olika casinospel men Le Bandit fångade verkligen mitt intresse med sin coola design och atmosfär. Förra veckan lyckades jag vinna en av de största vinsterna jag någonsin fått på nätet, tack vare free spins-rundan som verkligen boostade mina vinster!”
Maria beskriver vidare hur hon först testade spelet i demo-versionen för att förstå funktionerna innan hon satsade riktiga pengar. Hon rekommenderar Le Bandit speciellt för spelare som uppskattar kombinationen av strategi och slumpmässiga bonusar. le bandit max win x
Le Bandit kännetecknas av ett modernt, stilrent gränssnitt som både fungerar bra på dator och mobil. Alla huvudfunktioner är lättillgängliga och spelet laddar snabbt även på långsammare internetuppkopplingar.
För de som vill prova utan risk finns en demo-version av Le Bandit tillgänglig på flertalet casinosajter. Den här versionen har samma funktioner som riktiga spelet, men man spelar med låtsaspengar, vilket är perfekt för att lära sig innan man spelar med riktiga pengar.
| Funktion | Detaljer |
|---|---|
| Speltyp | Video slot med 5 hjul |
| RTP (Return to Player) | 96,2% |
| Volatilitet | Medel ー Hög |
| Maximal vinst | 10 000x insatsen |
| Bonusfunktioner | Free spins, multiplikatorer, Wilds |
| Spelutvecklare | Hacksaw Gaming |
Bonusrundorna triggas när du får tre eller fler scatter-symboler, vilket ger dig free spins med chans till extra hög multiplikator på vinsterna.
Ja, spelet är fullt kompatibelt med både smartphones och surfplattor, och anpassar sig automatiskt till skärmstorleken utan att du missar något i spelupplevelsen.
Le Bandit från Hacksaw Gaming är en underhållande och visuellt tilltalande slot som passar många typer av spelare, särskilt de som vill ha en balans mellan spänning och chans till höga vinster. Med en RCPT på 96,2%, bra bonusfunktioner och smidig mobilanpassning är det ett spel väl värt att testa på de svenska casinona.
Oavsett om du är ny i casinovärlden eller redan en van spelare, ger Le Bandit en rolig chans att njuta av slottarnas magi med det lilla extra i form av snygg grafik och spännande mekaniker.
The post Le Bandit – Spelrecension och analyser av Hacksaw Gaming appeared first on Pioneer Furnitures.
]]>The post Esplorazione della Slot Book of Toro di ELK Studios appeared first on Pioneer Furnitures.
]]>Book of Toro è una slot online sviluppata da ELK Studios che ha conquistato rapidamente il cuore degli appassionati italiani di casinò online. Ambientata in una cornice vibrante ispirata alla tradizione della corrida spagnola, questa slot unisce grafica accattivante, simboli tematici e dinamiche di gioco coinvolgenti. In questo articolo analizzeremo le caratteristiche principali, la giocabilità e l’esperienza complessiva, immergendoci nella realtà dei casinò italiani.
Book of Toro si presenta con una struttura classica a 5 rulli e 10 linee di pagamento, ma il suo vero fascino risiede nelle funzionalità speciali che coinvolgono simboli espandibili e spin gratuiti. L’atmosfera è resa unica dalla colonna sonora energica e dai dettagli grafici vivaci, che evocano l’emozione e la tensione del combattimento tra il torero e il toro.
Il gioco si basa su una meccanica di base semplice, facilitando sia i nuovi giocatori sia gli esperti a godersi l’esperienza senza difficoltà. I simboli sono divisi tra carte da gioco illustrate artisticamente e i simboli tematici come il torero, il toro e il libro magico distintivo. Quest’ultimo è il simbolo speciale che funge sia da jolly sia da scatter, scatenando la modalità di giri gratuiti con possibilità di ottenere vincite importanti grazie all’espansione dei simboli.

Negli ultimi anni, il mercato del gioco online in Italia si è evoluto notevolmente, favorendo la diffusione di piattaforme di gioco legali e sicure. Tra le più apprezzate per giocare a Book of Toro figurano:
Per cogliere meglio l’emozione del gioco, abbiamo raggiunto Marco, un giocatore italiano che ha recentemente vinto una somma rilevante su Book of Toro.
“Ho trovato questa slot per caso mentre cercavo qualcosa di nuovo su uno dei migliori casinò online italiani. La grafica mi ha subito incuriosito e decisi di provarla in modalità demo prima di scommettere realmente.”
“Uso sempre un budget ben definito e cerco di giocare in modo responsabile; Mi piace seguire l’andamento dei giri e sfruttare i giri gratuiti offerti dalla slot, perché è lì che si nascondono le vincite più alte.”
“Indescrivibile, soprattutto la prima volta in cui il simbolo del Toro si è espanso coprendo l’intero rullo. Ho deciso poi di continuare a giocare ma sempre con moderazione.”
La slot si gioca con un sistema a 10 linee di pagamento, puntando su combinazioni vincenti di simboli corrispondenti da sinistra a destra. Il simbolo libro attiva la modalità free spin e può sostituire qualsiasi altro simbolo.
Sì, molti casinò online certificati offrono la possibilità di provare Book of Toro gratuitamente per far familiarizzare i giocatori con le sue dinamiche senza rischiare denaro reale.
La volatilità è media-alta, il che significa che le vincite non sono frequenti ma tendono ad essere di entità maggiore, soprattutto durante i giri bonus.
| Parametro | Valore |
|---|---|
| Provider | ELK Studios |
| Rulli | 5 |
| Linee di pagamento | 10 |
| Volatilità | Media-Alta |
| RTP | 96,2% |
| Giri gratuiti | Si (con simboli espandibili) |
| Puntata minima | 0,10€ |
| Puntata massima | 100€ |
L’interfaccia è pulita e di facile comprensione. ELK Studios ha realizzato un layout che risponde prontamente sia su desktop che su dispositivi mobili, permettendo un’esperienza continua fra una postazione e l’altra. I pulsanti per la regolazione della puntata, lo spin automatico e l’attivazione manuale sono ben posizionati e intuitivi.
La crescente popolarità di Book of Toro si spiega con la sua combinazione di stile grafico vivace e alta giocabilità. Inoltre, l’introduzione costante di promozioni e bonus dedicati da parte dei casinò italiani ha contribuito a mantenere alto l’interesse dei giocatori.
Un altro motivo è la qualità del provider ELK Studios, noto per innovazione e titoli di qualità, che dà fiducia ai giocatori nella trasparenza e nelle possibilità di vincita.
Infine, la tematica unica e l’atmosfera avvincente hanno fatto sì che Book of Toro spicchi fra le molte slot disponibili online, diventando una scelta preferita per gli italiani che cercano divertimento e grandi emozioni.
Book of Toro si conferma come uno dei titoli più competitivi del mercato italiano dei casinò online. Grazie a un gameplay avvincente, un’interfaccia user-friendly e la sicurezza garantita dai casinò certificati AAMS/ADM, è perfetto sia per chi si avvicina per la prima volta al mondo delle slot sia per i giocatori più esperti.
Se vuoi provare l’emozione della corrida direttamente dal tuo computer o smartphone, non perdere l’occasione di giocare a Book of Toro, magari iniziando dalla versione demo per prendere confidenza con questo titolo spettacolare.
The post Esplorazione della Slot Book of Toro di ELK Studios appeared first on Pioneer Furnitures.
]]>The post Diepgaande Review van Dracula Slot door NetEnt in Nederlandse Online Casino’s appeared first on Pioneer Furnitures.
]]>De Dracula slot van NetEnt is een populaire gokkast die zowel door beginners als door ervaren spelers in Nederland wordt gewaardeerd. Het spel neemt je mee naar een duistere, gotische sfeer geïnspireerd door de legende van Dracula, met indrukwekkende graphics en spannende features. In deze review bespreken we de belangrijkste aspecten van de slot, waar je het kunt spelen, en inspecteren enkele eigenschappen die het tot een topkeuze maken voor Nederlandse spelers.
Deze online gokkast onderscheidt zich door zijn unieke thema en opvallende gameplay. De spanning van vampierenlegendes wordt gecombineerd met moderne slotfuncties zoals wilds, scatters en gratis spins. De geluids- en visuele effecten zorgen voor een meeslepende ervaring die de sfeer van een klassiek horrorverhaal perfect vangt.
Het design van Dracula slot is strak en overzichtelijk. De interface is gemakkelijk te navigeren, zelfs voor spelers die nieuw zijn in online casinospellen. Alle knoppen zijn intuïtief geplaatst en de inzetmogelijkheden zijn duidelijk zichtbaar. De thema-animaties leiden de speler stap voor stap door de verschillende features zonder verwarring.

NetEnt spellen zijn te vinden in vele Nederlandse en internationale online casino’s die legaal toegankelijk zijn voor spelers uit Nederland. Denk hierbij aan licentiehouders zoals BetCity.nl, TOTO Casino en Holland Casino Online. Deze casino’s bieden betrouwbare platforms met goede klantenservice en veilige betaalmethoden voor Nederlandse gebruikers.
| Casino | Bonus voor nieuwe spelers | Betaalopties | Licentie |
|---|---|---|---|
| BetCity.nl | €100 + 100 free spins | iDEAL, creditcard, bankoverschrijving | Kansspelautoriteit Nederland |
| TOTO Casino | €200 + 50 free spins | iDEAL, PayPal, creditcard | Kansspelautoriteit Nederland |
| Holland Casino Online | €250 + 20 free spins | iDEAL, creditcard | Kansspelautoriteit Nederland |
Dracula slot kent vijf rollen en drie rijen met in totaal dertig winlijnen. Het spel is geschikt voor spelers die inzetten willen plaatsen van minimale bedragen tot hogere inzetten voor de highrollers.
De Return to Player (RTP) van Dracula slot is ongeveer 96,6%, wat iets boven het gemiddelde ligt en een goede kans biedt voor winst op lange termijn.
“Ik was altijd nieuwsgierig naar de Dracula slot van NetEnt”, deelt Mark uit Amsterdam, een frequente speler van online casino’s. “Tijdens een avond spelen bij TOTO Casino viel ik in de free spins ronde. Het was ongelooflijk – de spanning was enorm en ik wist dat ik iets speciaals aan het spelen was. Uiteindelijk leverde dat een winst op van €3.500, wat best indrukwekkend is!”
Mark benadrukt vooral de spannende features: “De wilds en het free spins spel geven echt dat extra beetje geluk. Het leuke is dat het spel niet alleen draait om geluk, maar ook om de ervaring en het genieten van het thema. Als je van vampiers en mysterieuze settings houdt, is dit een topkeuze.”
De basis van elke betrouwbare slot is de Random Number Generator (RNG), en Dracula slot is daarop geen uitzondering. De RNG zorgt voor willekeurige uitkomsten bij elke draai, wat betekent dat elke spin onafhankelijk is en niet beïnvloed wordt door vorige resultaten.
Door certificeringen en strenge controles van de Kansspelautoriteit (KSA) wordt gegarandeerd dat de RNG volledig betrouwbaar is. Nederlandse spelers kunnen dus vol vertrouwen spelen.
Een veelgestelde vraag is of je Dracula slot eerst gratis kunt spelen voordat je echt geld inzet. Gelukkig bieden de meeste Nederlandse casino’s een demo modus aan. Dit betekent dat je het spel kunt testen zonder financiële risico’s, zodat je het beter leert kennen en vertrouwd raakt met de functies.
Zo krijg je:
Probeer deze demo’s vooral bij gerenommeerde sites als BetCity of Holland Casino Online voor optimale kwaliteit en veiligheid.
Ja, zolang je speelt bij casino’s die een Nederlandse goklicentie hebben, is het speelplatform volledig legaal.
De RTP bedraagt ongeveer 96,6%, wat vrij gunstig is binnen het online gokaanbod.
Ja, NetEnt heeft de slot volledig geoptimaliseerd voor mobiel gebruik, zowel op iOS als Android devices.
“Speel rustig en verleid jezelf niet om te snel grote inzetten te plaatsen. Maak eerst gebruik van de demo om het spelmechanisme te begrijpen. De free spins feature is cruciaal voor winst, dus focus op het activeren hiervan.” – Sophie, doorgewinterde gokkastspeler uit Rotterdam.
Voor Nederlandse spelers die houden van een sfeervolle en technisch solide slot, is Dracula van NetEnt absoluut een aanrader. Door de combinatie van visueel sterke graphics, betrouwbare software en een aantrekkelijke uitbetalingsstructuur, weet het spel zich goed te positioneren in het huidige aanbod van online casino’s.
The post Diepgaande Review van Dracula Slot door NetEnt in Nederlandse Online Casino’s appeared first on Pioneer Furnitures.
]]>The post Review van Book of Dead door Play’n GO in Nederlandse online casino’s appeared first on Pioneer Furnitures.
]]>Book of Dead is een van de populairste gokkasten in Nederlandse online casino’s. Ontwikkeld door Play’n GO, combineert deze slot een avontuurlijk thema met spannende bonusfeatures die spelers keer op keer aantrekken. In deze review bespreken we de belangrijkste aspecten van het spel, waar je het kunt spelen en waarom het zo geliefd is onder Nederlandse gokkers.
Book of Dead is een videoslot met een thema gebaseerd op Egyptische mystiek en avontuur. Het spel volgt de avonturier Rich Wilde in zijn zoektocht naar verborgen schatten in oude tempels. Met 5 rollen en 10 winlijnen biedt het slot basisspel acties en een bonusronde met gratis spins die extra spanning toevoegen.

Book of Dead is beschikbaar bij vrijwel alle grote en betrouwbare online casino’s die Play’n GO spellen in hun aanbod hebben. Hieronder is een tabel met casino’s waar je dit spel legaal en veilig kunt spelen.
| Casino | Bonus | Betaalopties | Licentie |
|---|---|---|---|
| Jack’s Casino Online | 100% bonus tot €500 | iDEAL, MuchBetter, PayPal | Kansspelautoriteit (KSA) |
| TOTO Casino | 25 gratis spins op Book of Dead | iDEAL, creditcard, Skrill | Kansspelautoriteit (KSA) |
| Holland Casino Online | Welkomspakket tot €400 | iDEAL, Trustly, Visa | Kansspelautoriteit (KSA) |
Play’n GO heeft bij Book of Dead een overzichtelijk en visueel aantrekkelijk ontwerp neergezet. De interface is intuïtief, met duidelijke knoppen voor inzetten, autoplay en informatie over winstlijnen en de uitbetalingstabel.
De graphics en animaties weerspiegelen het avontuurlijke Egyptische thema goed, waardoor spelers makkelijk in de sfeer komen. De snelheid van het spel is ook uitstekend, wat zorgt voor een prettig en vlot speelritme, zonder lange laadtijden.
Book of Dead heeft een grote fanbase opgebouwd in Nederland, en dat is niet zonder reden. Het spel biedt een perfecte balans tussen eenvoudige basisregels en de spanning van big wins. Vooral de mogelijkheid om gratis spins te activeren met een uitklappend symbool maakt elke ronde uniek.
Ook draagt het thema en de presentatie bij aan de populariteit. Nederlandse spelers waarderen authentieke en visueel sterke slots, en Book of Dead voldoet daaraan dankzij het Egyptische mysterie en de herkenbare avonturier Rich Wilde.
Daarnaast speelt de brede beschikbaarheid een rol. Door het aanbod bij gerenommeerde Nederlandse casino’s met geldige KSA-licentie is het voor spelers gemakkelijk om veilig te spelen. Het spel is toegankelijk via desktop en mobiel, wat ook de populariteit versterkt.
“Ik had altijd gehoord van Book of Dead maar besloot het pas te spelen bij Jack’s Casino Online nadat ik hun bonus had ontvangen,” vertelt Jan. “Tijdens mijn eerste sessie won ik al vrij snel de gratis spins, en met het uitklappende symbool viel mijn winst flink hoger uit dan verwacht. Uiteindelijk pakte ik ongeveer €1.200 uit een inzet van €20!”
“Het fijne aan dit spel vind ik dat je het speelplezier voelt terwijl je ook de spanning hebt van risico. Ik speel het inmiddels regelmatig, vooral op mijn mobiel als ik onderweg ben. De interface is heel makkelijk en dat maakt het extra leuk.”
Je activeert de gratis spins door 3 of meer Book of Dead symbolen op de rollen te krijgen. Dit levert 10 gratis spins en een speciaal uitklappend symbool op voor extra winstkansen.
Ja, vrijwel alle Nederlandse online casino’s bieden een gratis demoversie van Book of Dead aan zodat je het spel kunt uitproberen zonder risico.
De minimale inzet ligt vaak rond de €0,10 per spin en kan oplopen tot enkele tientallen euro’s, afhankelijk van het casino waar je speelt.
| Kenmerk | Details |
|---|---|
| Ontwikkelaar | Play’n GO |
| Rollen / Winlijnen | 5 rollen / 10 winlijnen |
| Volatiliteit | Hoog |
| Maximale uitbetaling | 5000x inzet |
| Return to Player (RTP) | 96,21% |
| Speciale features | Gratis spins, uitklappende symbolen, risico spel |
Book of Dead is een uitstekende keuze voor Nederlandse spelers die op zoek zijn naar een spannend, visueel aantrekkelijk en potentieel zeer winstgevend gokkast-avontuur. Dankzij de hoge volatiliteit, toegankelijke bonusfeatures en beschikbaarheid bij legale casino’s met KSA-licentie is het geen verrassing dat het spel zo populair is in Nederland.
Of je nu nieuw bent in de wereld van online slots of een ervaren speler, Book of Dead blijft boeien met zijn combinatie van klassieke slotmechanieken en innovatieve bonusrondes. We raden aan eerst de demo uit te proberen en daarna te kiezen voor één van de betrouwbare casino’s uit onze tabel om jouw geluk te beproeven.
The post Review van Book of Dead door Play’n GO in Nederlandse online casino’s appeared first on Pioneer Furnitures.
]]>The post Book of Dead populär app – Därför väljer svenskar den appeared first on Pioneer Furnitures.
]]>Book of Dead från Play’n GO är en av de mest spelade online casinospelen bland svenska spelare. Med sitt äventyrliga tema‚ spännande funktioner och potentiellt stora vinster har spelet snabbt blivit en favorit i toppen av casinovärlden i Sverige. I denna artikel kommer vi att gå igenom både en recension av spelet och ge en inblick i varför det är så populärt bland svenska spelare.
Book of Dead tar med spelaren på ett spännande äventyr tillsammans med äventyraren Rich Wilde på jakt efter de mystiska böckerna som kan ge stora vinster. Ett klassiskt slotsformat med fem hjul och tio vinstlinjer‚ med en RTP (Return To Player) på cirka 96‚21 %‚ vilket är konkurrenskraftigt inom sektorn.
Book of Dead bjuder på en bonusrunda där du kan få tio gratissnurr (free spins). Under denna runda kan en speciell symbol expandera och täcka hela hjulet vilket kraftigt ökar vinstchanserna. Spelet erbjuder också en hög volatilitet‚ vilket innebär att vinster kan komma mer sällan men ofta med högre belopp än i många andra slots.
Spelet har ett tydligt och attraktivt gränssnitt som kompletteras av atmosfäriska ljudmiljöer som skapar en känsla av mystik och äventyr. Symbolerna och animationerna är inspirerade av gamla egyptiska artefakter och kultur‚ vilket passar temat perfekt.

Book of Dead populär app
Book of Dead finns på många svenska onlinecasinon tack vare dess popularitet. Några av de mest framstående sajterna där du kan spela är LeoVegas‚ Betsson och Casumo. Dessa casinon erbjuder ofta bonusar för nya spelare som kan användas på Book of Dead.
| Casino | Bonus för nya spelare | Mobilvänligt | Live Support |
|---|---|---|---|
| LeoVegas | 100% upp till 1000 kr + 100 gratissnurr | Ja | Ja‚ 24/7 |
| Betsson | 100% upp till 1500 kr + 50 gratissnurr | Ja | Ja‚ livechatt & mail |
| Casumo | 50% upp till 2000 kr + 20 gratissnurr | Ja | Support dygnet runt |
Varför är just Book of Dead så omtyckt bland svenska spelare? En faktor är förstås temat med forntida Egypten som alltid fascinerat människor. Men det är också kombinationen av enkla regler‚ spännande bonusfunktioner och möjlighet till stora vinster som gör spelet attraktivt.
Den högre volatiliteten gör att spelet tilltalar mer erfarna spelare som gillar spänningen i att satsa högre för chansen att vinna stort. Dessutom fungerar spelet utmärkt på mobila enheter‚ vilket gör det tillgängligt för många som vill spela när de är på språng.
Gratissnurr aktiveras när du landar tre eller fler bok-symboler på hjulen samtidigt. Detta startar bonusrundan med 10 free spins och specialsymboler som kan expandera.
Ja‚ många casinon erbjuder en demo där du kan prova spelet utan att satsa riktiga pengar. Detta är perfekt för dig som vill bekanta dig med spelmekaniken innan du spelar för riktiga pengar.
En av anledningarna att Book of Dead fungerar så bra i Sverige är dess intuitiva och användarvänliga gränssnitt. Designen är ren och tydlig‚ vilket gör att både nybörjare och erfarna spelare lätt kan navigera i spelet. Dessutom är knappar för insatser‚ autospel och balansering väl placerade för snabb åtkomst även i mobilversionen.
Vi träffade “AnnaS1978”‚ en svensk spelare som nyligen vann stort på Book of Dead hos LeoVegas. Hon berättar:
“Jag har spelat Book of Dead i ungefär ett år och älskar temat och spänningen i bonusrundorna. Min största vinst kom under en natt när jag lyckades få flera expanderande symboler under gratissnurren. Summan räckte för att täcka flera månaders utgifter – otroligt!”
Anna fortsätter: “Jag rekommenderar verkligen att testa spelautomaten‚ men var alltid försiktig med insatserna och spela ansvarsfullt.”
Book of Dead från Play’n GO är ett imponerande och populärt slotspel bland svenska spelare. Kombinationen av ett fängslande tema‚ enkel spelmekanik‚ hög volatilitet och bra mobilanpassning gör det till ett självklart val för många onlinecasinospelare i Sverige.
Oavsett om du är nybörjare eller van spelare så erbjuder Book of Dead spännande möjligheter till underhållning och vinster – och med massor av toppcasinon tillgängliga på den svenska marknaden finns det inga hinder för att ge sig ut på äventyr med Rich Wilde och hans bok!
The post Book of Dead populär app – Därför väljer svenskar den appeared first on Pioneer Furnitures.
]]>The post Sweet Bonanza Max Win – So knackst du den Höchstgewinn appeared first on Pioneer Furnitures.
]]>Der beliebte Online-Slot Sweet Bonanza von Pragmatic Play erfreut sich unter Spielern aus Deutschland großer Beliebtheit. Doch was macht dieses Spiel so einzigartig und wie kann man den Max Win knacken? Dieser Artikel bietet dir eine Review des Spiels, eingebettet mit nützlichen Tipps und Infos zu den Features, sowie ein paar Ratschläge direkt von erfahrenen Spielern.
Sweet Bonanza ist ein Video-Slot mit einem 6×5 Raster und tumbling reels (Kaskadenmechanik). Statt klassischer Gewinnlinien werden alle Kombinationen von Symbolen auf benachbarten Positionen gezählt, um Gewinne auszuzahlen. Das Thema ist süß und farbenfroh gestaltet mit Früchten, Bonbons und einem Vorschuss an fröhlicher Stimmung.

Sweet Bonanza Max Win
Der Max Win bei Sweet Bonanza beträgt das 21.175-fache des Einsatzes. Dieses Potenzial macht das Spiel gerade für risikofreudige Spieler attraktiv. Dennoch sollte man sich der Volatilität bewusst sein, die eher im hohen Bereich angesiedelt ist.
Die Benutzeroberfläche von Sweet Bonanza ist intuitiv aufgebaut, was besonders für Spieler aus Deutschland wichtig ist, da es keine großen Sprachbarrieren gibt. Die Symbole sind klar erkennbar, und alle wichtigen Einstellungen (Einsatzhöhe, Autoplay, Turbo-Modus) sind leicht zugänglich.
Mittlerweile bieten viele seriöse Online-Casinos den Slot von Pragmatic Play an. Besonders zu empfehlen sind Casinos mit deutscher Lizenz und gutem Ruf, die sichere Zahlungsmethoden und schnelle Auszahlungen garantieren.
| Casino Name | Willkommensbonus | Zahlungsmethoden | Lizenz |
|---|---|---|---|
| Casino Extra | 100% bis 300€ + 50 Freispiele | Paypal, Kreditkarte, Sofort | deutsche Lizenz |
| Mr Green | 10€ gratis + 100% Bonus bis 100€ | Paypal, Trustly, Banküberweisung | deutsche Lizenz |
| LeoVegas | Willkommenspaket bis 400€ + 100 Freispiele | Kreditkarte, Skrill, Neteller | deutsche Lizenz |
Ja, der Slot ist vollständig mobiloptimiert und läuft auf iOS- sowie Android-Geräten problemlos im Browser oder in dedizierten Casino-Apps.
Ja, viele Online-Casinos bieten eine Demo-Version an, damit man das Spiel risikofrei ausprobieren kann.
Der Max Win ist sehr selten, da es sich um einen High-Volatility Slot handelt. Geduld ist hier entscheidend.
„Sweet Bonanza hat mich überzeugt, weil es trotz seiner bunten Aufmachung echtes Spannungspotenzial bietet. Am Anfang ist es etwas frustrierend, bis die Kaskaden mal größere Gewinnketten auslösen. Mein Tipp: Immer mit kleineren Beträgen testen und dann das Spieltempo anpassen. Besonders im Freispielmodus kann man richtig absahnen.“ – Markus, erfahrener Casinospieler aus Hamburg.
Die Kombination aus einfachen Regeln, aufregendem Design und dem sensationellen Max-Win-Potenzial lockt viele Spieler. Auch das mobile Spielen ist problemlos möglich, was die Popularität zusätzlich steigert. Zudem ist der Entwickler Pragmatic Play bekannt für qualitativ hochwertige Software und faire Zufallsgeneratoren.
Sweet Bonanza von Pragmatic Play bleibt ein heißer Favorit für deutsche Casinospieler, die auf der Suche nach einem slot mit hoher Volatilität und großen Gewinnchancen sind. Die lebendige Optik und die innovativen Features machen den Slot sowohl für Neueinsteiger als auch erfahrene Spieler attraktiv. Verantwortungsvolles Spielen und das Nutzen von Demo-Versionen sind dabei der Schlüssel zum Erfolg.
The post Sweet Bonanza Max Win – So knackst du den Höchstgewinn appeared first on Pioneer Furnitures.
]]>The post Penalty Shoot Out par Evoplay : une revue captivante pour les joueurs français appeared first on Pioneer Furnitures.
]]>Dans l’univers effervescent des jeux de casino en ligne‚ Penalty Shoot Out de Evoplay se démarque par son originalité et son gameplay dynamique qui séduisent de plus en plus les joueurs français. Ce jeu combine l’univers passionnant du football avec les mécaniques des machines à sous‚ promettant des sensations fortes à chaque tir au but virtuel.
Penalty Shoot Out est un jeu en ligne proposé par le développeur Evoplay‚ connu pour ses créations innovantes et graphiquement impressionnantes. Le thème‚ très orienté football‚ offre aux joueurs l’opportunité de s’immerger dans un match de tirs au but où l’objectif est de marquer le maximum de buts pour décrocher des gains intéressants.
Le principe est simple : vous incarnez un tireur de penalty et vous devez choisir avec précision le placement de vos tirs pour battre le gardien adverse. Avec plusieurs niveaux de difficulté et une interface intuitive‚ même les novices peuvent rapidement comprendre et commencer à jouer.

L’interface de Penalty Shoot Out est fluide et moderne‚ facilement navigable sur ordinateur comme sur mobile. Les animations 3D immersives‚ les effets sonores réalistes du stade‚ le compteur de buts‚ et les mini-jeux interactifs donnent une ambiance très proche d’un vrai match.
Ce jeu est disponible dans de nombreux casinos en ligne fiables qui acceptent les joueurs français. Parmi les plateformes recommandées‚ on retrouve :
| Casino en ligne | Bonus d’inscription | Moyens de paiement | Licence |
|---|---|---|---|
| Casino1 | 100% jusqu’à 300€ + 50 tours gratuits | Carte bancaire‚ PayPal‚ Neteller | Malte (MGA) |
| Casino2 | 200€ + 100 tours gratuits | Carte bancaire‚ Skrill‚ Paysafecard | Royaume-Uni (UKGC) |
| Casino3 | 150% jusqu’à 250€ | Carte bancaire‚ Virement bancaire | Curacao |
Depuis sa sortie‚ Penalty Shoot Out a connu une montée en popularité significative‚ notamment auprès des amateurs de sports et de slots modernes. Plusieurs facteurs expliquent cet engouement :
En France‚ pays passionné par le football‚ ce jeu frappe juste. La familiarité avec ce sport remplit les joueurs d’une envie naturelle d’essayer leurs talents dans cette simulation. De plus‚ l’aspect compétitif et les choix tactiques dans la mise en place des tirs crée un lien émotionnel avec le joueur‚ améliorant la durée de session et la fidélité.
Le RTP moyen est de 96.5%‚ ce qui le place dans la moyenne haute des games de casino‚ offrant de bonnes chances aux joueurs de récupérer leurs mises sur le long terme.
Oui‚ la plupart des casinos où le jeu est disponible offrent une version démo gratuite. Cela permet de tester les mécanismes sans risquer d’argent tout en s’habituant à l’interface et à la dynamique du jeu. méthode optimale jeu du penalty
Nous avons rencontré Marc‚ un joueur amateur de Lyon‚ qui a eu la chance de décrocher un jackpot intéressant avec Penalty Shoot Out.
“J’adore le football‚ alors ce jeu m’a tout de suite attiré. Au début‚ j’ai testé le mode démo pour comprendre les règles. Après quelques sessions en argent réel‚ mon coup de chance est arrivé : un enchaînement de tirs bien placés m’a permis de gagner 5 000€. Ce qui m’a plu‚ c’est l’adrénaline au moment de chaque tir‚ un vrai suspense !”
“Je dirais qu’il faut bien observer le gardien et ne pas toujours viser les mêmes zones. La variation est la clé. Aussi‚ profiter des offres bonus du casino m’a permis d’augmenter mes chances sans trop risquer.”
Selon plusieurs experts du jeu en ligne et responsables du développement chez Evoplay‚ Penalty Shoot Out récompense autant la stratégie que la chance :
Penalty Shoot Out par Evoplay est une excellente option pour les joueurs français à la recherche d’un jeu de casino original mêlant football et machines à sous. Sa jouabilité‚ sa qualité visuelle‚ son accessibilité sur mobiles et la possibilité de gagner de beaux jackpots expliquent son succès grandissant. Que vous soyez amateur ou joueur aguerri‚ ce titre mérite une place de choix dans votre sélection.
The post Penalty Shoot Out par Evoplay : une revue captivante pour les joueurs français appeared first on Pioneer Furnitures.
]]>The post Reseña del Juego Gates of Olympus: La Puerta a la Fortuna en Casinos Online para España appeared first on Pioneer Furnitures.
]]>El mundo de los casinos online en España cuenta con una amplia variedad de tragamonedas, pero pocas han logrado capturar la atención como Gates of Olympus de Pragmatic Play. Este juego se ha convertido en uno de los favoritos por su temática mitológica, gráficos vibrantes y un sistema de pago innovador. Aquí te presentamos un análisis completo para que descubras todo lo que necesitas saber sobre esta tragamonedas y dónde jugarla en España.
Gates of Olympus es una tragamonedas online con temática griega que nos transporta directamente al Monte Olimpo. Su protagonista es Zeus, quien actúa como símbolo especial y multiplicador de ganancias. El juego destaca por su estructura de 6 carretes y un mecanismo de pagos “all pays”, lo que significa que las combinaciones ganadoras se forman sin necesidad de líneas fijas.

Este slot destaca por su interfaz colorida y dinámica. El fondo muestra nubes y un templo en las alturas, reforzando la atmósfera mitológica. Además, el sonido envolvente y efectos de rayos crean una experiencia inmersiva. Es importante destacar que el juego utiliza símbolos de alto valor como joyas de diferentes colores y los propios personajes mitológicos. gate of olympus gratis
Una de las mayores innovaciones de Gates of Olympus es la incorporación de multiplicadores aleatorios que pueden aparecer en cualquier momento del juego base o durante las tiradas gratis, que pueden aumentar sustancialmente las ganancias.
Los jugadores españoles tienen acceso a esta tragamonedas en múltiples casinos online con licencia en España. Algunos de ellos ofrecen promociones únicas y bonos especiales para jugar Gates of Olympus. La mayoría de estas plataformas permiten también jugar en modo demo, para practicar sin riesgo antes de apostar dinero real.
| Casino | Bono de Bienvenida | Disponibilidad Móvil | Juego en Demo |
|---|---|---|---|
| Casino GranMadrid | 100% hasta 200€ | Sí | Sí |
| 888casino España | 150% hasta 150€ | Sí | Sí |
| Betsson España | 50€ + 50 giros gratis | Sí | Sí |
Jugar en modo demo es altamente recomendable para familiarizarse con la dinámica del slot antes de apostar dinero. Simplemente accede a cualquier casino con licencia que lo ofrezca y selecciona la opción “Jugar en modo demo”. Así puedes practicar sin restricciones y entender cómo actúan los multiplicadores, cuándo activar las tiradas gratis y qué esperar del juego base.
Juan M., de Madrid, nos comparte su experiencia: “Estaba jugando en modo real después de varias partidas demos para entender las mecánicas. Una noche, durante la ronda de tiradas gratis, logré acumular multiplicadores que aumentaron mucho mi ganancia. Fue una sensación increíble y me motivó a seguir jugando responsablemente.”
| Juego | Proveedor | RTP | Temática | Multiplicadores |
|---|---|---|---|---|
| Gates of Olympus | Pragmatic Play | 96.5% | Mitología griega | Sí, hasta 500x |
| Divine Fortune | NetEnt | 96.6% | Mitología griega | No |
| Zeus: God of Thunder | IGT | 96.0% | Mitología griega | Sí, multiplicadores |
Gates of Olympus es una tragamonedas que combina una temática popular con innovaciones en la mecánica que hacen que la experiencia de juego sea emocionante para los jugadores españoles. Su sistema de pagos “all pays”, junto con los multiplicadores, aporta variedad y grandes oportunidades de ganancia. Además, está disponible en la mayoría de los casinos online legales de España, con opciones para jugar en demo y practicar antes de apostar dinero real.
Si buscas un juego visualmente atractivo, fácil de entender pero con mucha profundidad en sus posibilidades, Gates of Olympus es definitivamente una opción que merece la pena probar.
The post Reseña del Juego Gates of Olympus: La Puerta a la Fortuna en Casinos Online para España appeared first on Pioneer Furnitures.
]]>The post Gates of Olympus Online: Best Canadian Platforms appeared first on Pioneer Furnitures.
]]>For Canadian players seeking thrilling online casino experiences, Gates of Olympus by Pragmatic Play stands out as a must-try slot game. This engaging and visually stunning slot transports players to the realm of ancient Greek gods, with Zeus himself holding the keys to potentially massive wins. In this article, we will dive into a detailed review of Gates of Olympus, coupled with insights on where to play it in Canada and how the game’s design contributes to its immense popularity.
Gates of Olympus is a 6×5 grid slot with a cluster pays mechanic instead of traditional paylines, making each spin unpredictable and exciting. The RTP sits around 96.5%, appealing to Canadian players who look for a fair chance of winning over extended play time. The game boasts a variable volatility, leaning towards high, meaning big wins come less often but pack a serious punch when they hit.
The game’s rich mythology theme is amplified through detailed artwork, animations, and a powerful soundtrack that immerses players deep into the mythical Olympus atmosphere. Its non-traditional layout and unique features provide a refreshing change from typical slots, catering to both newcomers and seasoned slot enthusiasts in Canada.

Gates of Olympus Online:
Canada has a thriving online casino market, with many platforms offering access to Pragmatic Play’s slots, including Gates of Olympus. Below is a curated list of top Canadian casinos where this game is available:
| Casino | Welcome Bonus | Payment Methods | Mobile Friendly |
|---|---|---|---|
| Playamo Casino | Up to C$1500 + 150 Free Spins | Interac, Visa, Bitcoin | Yes |
| Jackpot City | C$1600 Welcome Bonus | Visa, Mastercard, Skrill | Yes |
| Spin Casino | C$1000 + 100 Free Spins | Interac, Neteller, e-Transfer | Yes |
All recommended casinos boast sleek, user-friendly interfaces designed to accommodate mobile play — essential for Canadian users who prefer spinning reels on the go. The registration process is straightforward, and support is readily available, often including live chat options. Gates of Olympus runs smoothly across devices, ensuring a seamless gaming experience whether on desktop or smartphone.
One notable testimony from a Canadian player who recently won a significant amount on Gates of Olympus highlighted the thrill of the multiplier feature:
“I hit the free spins round and the multipliers kept stacking. The thrill was unbelievable—my winnings multiplied beyond what I expected. It’s one of the few slots where luck and strategy feel balanced. Highly recommended for anyone wanting big excitement.” – Mark L., Toronto
From the casino support standpoint, Gates of Olympus is frequently cited as one of the most requested games. Support reps emphasize the importance of players understanding the cluster pays and tumble mechanics to avoid confusion. Their experience suggests that most players appreciate the transparency of the rules and the excitement the game brings to the platform’s portfolio.
You need to land at least four scatter symbols (depicted as Zeus’s gate) anywhere on the reels in a single spin to enter the free spins bonus round.
Yes, most Canadian online casinos provide a demo version allowing you to practice and understand the game mechanics without risking your funds.
The maximum win is 5,000 times your stake, achievable primarily through the multiplayer stacking in the free spins feature combined with big wins from tumble sequences.
Analyzing why Gates of Olympus stands out among an ever-growing flood of slot games, several factors contribute to its success in Canada:
The game’s balance between volatility, entertainment value, and winning potential makes it a top pick among Pragmatic Play’s portfolio, especially in the Canadian online casino ecosystem. Comments from seasoned players and casino support confirm that it’s not just hype, but a genuinely well-designed experience.
Understanding these basic rules helps Canadian players maximize their gaming sessions and better anticipate outcomes when playing Gates of Olympus.
Gates of Olympus by Pragmatic Play offers Canadian online casino players a captivating blend of mythological theme, innovative gameplay, and lucrative win potential. Whether you’re looking for a fun demo session or ready to stake real Canadian dollars, this game fits perfectly on multiple reputable platforms accessible in Canada.
Its fresh mechanics, combined with user-friendly design and solid casino support, make it a top contender for anyone’s slot rotation. Take a spin on Gates of Olympus and see if you can unlock the treasures of the gods!
The post Gates of Olympus Online: Best Canadian Platforms appeared first on Pioneer Furnitures.
]]>The post Ulasan Starlight Princess: Sensasi Slot Online dari Pragmatic Play appeared first on Pioneer Furnitures.
]]>Starlight Princess adalah salah satu permainan slot online terbaru dari Pragmatic Play yang semakin populer di kalangan pemain kasino daring Indonesia. Dengan tema fantasi yang penuh warna, game ini menawarkan pengalaman bermain yang menyenangkan sekaligus peluang menang yang menarik. Pada artikel ini, kami akan mengulas fitur-fitur utama dari Starlight Princess, kemudahan akses untuk pemain di Indonesia, serta komentar dari para ahli dan pemain.
Starlight Princess hadir dengan grafis yang memukau, karakter putri bintang yang terlihat memesona, dan latar belakang penuh bintang bercahaya. Tema ini sangat cocok untuk para pemain yang menyukai atmosfer magis dan visual cerah pada slot online.
Interface-nya juga dirancang user-friendly dengan tombol spin, autoplay, dan pengaturan suara yang mudah dijangkau. Desain responsif membuatnya nyaman dimainkan baik di desktop maupun perangkat mobile.

Permainan Starlight Princess menggunakan format slot 6 gulungan dengan sistem pembayaran cluster. Pemain harus mengumpulkan simbol yang sama dalam kelompok untuk mendapatkan kemenangan. Ada fitur khusus seperti Multiplier yang aktif saat simbol putri muncul dan dapat meningkatkan hasil kemenangan secara signifikan. mentahan starlight princess png
Wild dan Scatter juga hadir dalam game ini, memberikan putaran bonus dan kesempatan free spins yang bisa memicu kemenangan besar.
Untuk pemain di Indonesia, ada sejumlah kasino online terpercaya yang menyediakan permainan Starlight Princess, termasuk situs yang menerima metode pembayaran lokal seperti pulsa, e-wallet dan transfer bank. Situs-situs seperti Pragmatic88, Slot88, dan Gamatron menjadi pilihan populer karena kemudahan akses dan layanan pelanggan yang baik.
| Nama Kasino | Metode Pembayaran | Bonus Selamat Datang | Layanan Pelanggan |
|---|---|---|---|
| Pragmatic88 | Pulsa, Ovo, Dana, Transfer Bank | 100% hingga Rp3.000.000 | Live Chat 24/7 |
| Slot88 | Pulsa, e-Wallet, Transfer Bank | 50% hingga Rp2.000.000 | Live Chat dan Email |
| Gamatron | Pulsa dan Transfer Bank | 75% hingga Rp2.500.000 | Live Chat 24 Jam |
Kami berkesempatan mewawancarai Andi, seorang pemain dari Jakarta yang pernah mendapatkan jackpot besar saat memainkan Starlight Princess di salah satu situs kasino terpercaya. Berikut pengalamannya:
Andi: “Awalnya saya coba main hanya untuk hiburan, tapi keberuntungan mendadak datang. Pada putaran free spin, saya mendapatkan multiplier yang sangat besar hingga total kemenangan saya melebihi 50 kali taruhan awal. Gameplay-nya cukup seru dan grafisnya benar-benar membuat saya betah bermain lama.”
Dia juga menambahkan bahwa penting untuk memilih kasino online dengan reputasi baik agar transaksi dan penarikan hadiah bisa berjalan mulus.
Permainan ini mendapatkan tempat khusus di hati pemain Indonesia tidak lepas dari beberapa faktor, yaitu:
Mira, seorang pemain slot dengan pengalaman lebih dari 5 tahun, memberikan feedback mengenai Starlight Princess:
“Starlight Princess adalah game slot yang menyenangkan sekaligus menantang. Volatilitas tinggi membuatnya cocok untuk pemain yang berani mengambil risiko demi hadiah besar. Namun, jangan lupa untuk mengatur batas taruhan agar tetap dalam kendali.”
Kesimpulan: Starlight Princess dari Pragmatic Play adalah pilihan menarik bagi para penggemar slot di Indonesia, menawarkan visual memikat, gameplay seru, dan peluang menang yang kompetitif. Pastikan memilih kasino online resmi dan manfaatkan versi demo untuk mengenal mekanisme game sebelum bertaruh dengan uang asli.
The post Ulasan Starlight Princess: Sensasi Slot Online dari Pragmatic Play appeared first on Pioneer Furnitures.
]]>