'',
'price_type' => 'onetime',
'price' => 0.0,
'compare_price' => 0.0,
'product_id' => 0,
'order' => 0,
// Settings.
'setup_fee' => false,
'setup_fee_name' => '',
'setup_fee_price' => 0.00,
'setup_fee_type' => '',
'trial' => false,
'trial_days' => 0,
'expire' => false,
'expire_days' => 0,
'payment_duration' => 0,
'payment_duration_type' => '',
'upgradeable' => false,
'tax_status' => ProductTaxStatus::TAXABLE,
'enable_license_activation_limit' => false,
'license_activation_limit' => 0,
'license_prefix' => null,
'license_segments' => null,
'down_payment' => '',
'installments' => [],
'is_hidden' => false,
];
protected ?string $product_title = null;
protected ?string $product_type = null;
protected ?string $shipping_type = null;
protected ?string $digital_auto_complete = null;
protected array $settings_props = [
'setup_fee',
'setup_fee_name',
'setup_fee_price',
'setup_fee_type',
'trial',
'trial_days',
'expire',
'expire_days',
'payment_duration',
'payment_duration_type',
'upgradeable',
'tax_status',
'enable_license_activation_limit',
'license_activation_limit',
'license_prefix',
'license_segments',
'down_payment',
'installments',
'is_hidden',
];
protected array $data_props = [
'price_name',
'price_type',
'price',
'compare_price',
'product_id',
'order',
];
protected function read_data(): array {
$data = parent::read_data();
if ( ! empty( $data['settings'] ) ) {
$settings = maybe_unserialize( $data['settings'] );
if ( ! is_array( $settings ) ) {
$settings = json_decode( $data['settings'], true );
}
unset( $data['settings'] );
if ( $settings ) {
$data = array_merge( $data, $settings );
}
}
return $data;
}
public function prepare_for_db( string $context = 'create' ): array {
$data = [];
$settings = [];
$format = [];
foreach ( $this->data_props as $prop ) {
$value = $this->{"get_$prop"}( 'edit' );
$format[] = $this->predict_format( $prop, $value );
$data[ $prop ] = $value;
}
foreach ( $this->settings_props as $prop ) {
$settings[ $prop ] = $this->{"get_$prop"}( 'edit' );
}
$data['settings'] = maybe_serialize( $settings );
$format[] = '%s';
return [
'data' => apply_filters( 'storeengine/' . $this->object_type . '/db/' . $context, $data, $this ),
'format' => $format,
];
}
public function delete( bool $force_delete = true ): bool {
$id = $this->get_id();
$deleted = parent::delete( $force_delete );
if ( $deleted ) {
/**
* @deprecated
* Use 'storeengine/price/deleted'
* @see parent::delete()
*/
do_action( 'storeengine/price_deleted', $id, $this, $force_delete );
}
return $deleted;
}
public function clear_cache( bool $flush_collection = true ) {
parent::clear_cache( $flush_collection );
wp_cache_delete( 'storeengine_product_' . $this->get_product_id() . '_prices' );
wp_cache_delete( 'storeengine_price_' . $this->get_id() . '_integrations' );
}
// Getters
public function get_price_name( string $context = 'view' ) {
return $this->get_prop( 'price_name', $context );
}
public function get_name(): string {
return $this->get_price_name();
}
public function get_price_type( string $context = 'view' ): string {
return $this->get_prop( 'price_type', $context );
}
public function get_type( string $context = 'view' ): string {
return $this->get_price_type( $context );
}
public function get_price( string $context = 'view' ): float {
return apply_filters('storeengine/product/get_price', (float) $this->get_prop( 'price', $context ), $context);
}
public function get_compare_price( string $context = 'view' ): ?float {
$compare_price = $this->get_prop( 'compare_price', $context );
return apply_filters('storeengine/product/get_compare_price', (float) $compare_price, $context);
}
public function get_product_id( string $context = 'view' ): int {
return (int) $this->get_prop( 'product_id', $context );
}
public function get_order( string $context = 'view' ): int {
return (int) $this->get_prop( 'order', $context );
}
public function get_menu_order( string $context = 'view' ): int {
return $this->get_order( $context );
}
public function get_setup_fee( string $context = 'view' ): bool {
return (bool) $this->get_prop( 'setup_fee', $context );
}
public function has_setup_fee(): bool {
return $this->get_setup_fee();
}
public function is_setup_fee(): bool {
return $this->get_setup_fee();
}
public function get_setup_fee_name( string $context = 'view' ) {
return $this->get_prop( 'setup_fee_name', $context );
}
public function get_setup_fee_price( string $context = 'view' ): float {
return (float) $this->get_prop( 'setup_fee_price', $context );
}
public function get_setup_fee_type( string $context = 'view' ) {
$value = $this->get_prop( 'setup_fee_type', $context );
if ( 'fixed' === $value ) {
$value = 'fee';
}
return $value;
}
public function get_trial( string $context = 'view' ) {
return $this->get_prop( 'trial', $context );
}
public function is_trial(): bool {
return (bool) $this->get_trial() && $this->get_trial_days() > 0;
}
public function get_trial_days( string $context = 'view' ): int {
return (int) $this->get_prop( 'trial_days', $context );
}
public function get_expire( string $context = 'view' ): bool {
return (bool) $this->get_prop( 'expire', $context );
}
public function get_expire_days( string $context = 'view' ): int {
return (int) $this->get_prop( 'expire_days', $context );
}
public function get_length( string $context = 'view' ): int {
return $this->get_expire_days( $context );
}
public function is_expire(): bool {
return $this->get_expire() && $this->get_expire_days();
}
/**
* @param Order $order
*
* @return ?StoreengineDatetime
* @throws \DateMalformedIntervalStringException
*/
public function get_access_expire_for_order( Order $order ): ?StoreengineDatetime {
if ( ! $this->is_expire() || ! $order->get_date_created_gmt() ) {
return null;
}
return $order->get_date_created_gmt()->add( new DateInterval( 'P' . $this->get_expire_days() . 'D' ) );
}
public function get_payment_duration( string $context = 'view' ): int {
return (int) $this->get_prop( 'payment_duration', $context );
}
public function get_period_interval( string $context = 'view' ): int {
return $this->get_payment_duration( $context );
}
public function get_payment_duration_type( string $context = 'view' ): string {
$value = $this->get_prop( 'payment_duration_type', $context );
if ( ! in_array( $value, [ 'day', 'week', 'month', 'year' ], true ) ) {
$old_values = [
'days' => 'day',
'weeks' => 'week',
'months' => 'month',
'years' => 'year',
];
$value = $old_values[ $value ] ?? 'month';
}
return $value;
}
public function get_period( string $context = 'view' ): string {
return $this->get_payment_duration_type( $context );
}
public function get_upgradeable( string $context = 'view' ): bool {
return (bool) $this->get_prop( 'upgradeable', $context );
}
public function get_enable_license_activation_limit( string $context = 'view' ): bool {
return (bool) $this->get_prop( 'enable_license_activation_limit', $context );
}
public function get_license_activation_limit( string $context = 'view' ): int {
return absint( $this->get_prop( 'license_activation_limit', $context ) );
}
public function get_license_prefix( string $context = 'view' ): ?string {
$value = $this->get_prop( 'license_prefix', $context );
return $value ? (string) $value : null;
}
public function get_license_segments( string $context = 'view' ): ?string {
$value = $this->get_prop( 'license_segments', $context );
return $value ? (string) $value : null;
}
public function get_down_payment( string $context = 'view' ): ?float {
$value = $this->get_prop( 'down_payment', $context );
return $value ? (float) $value : null;
}
public function get_installments( string $context = 'view' ): ?array {
return $this->get_prop( 'installments', $context );
}
public function get_is_hidden( string $context = 'view' ): bool {
return Formatting::string_to_bool( $this->get_prop( 'is_hidden', $context ) );
}
public function get_tax_status() {
$tax_status = ProductTaxStatus::TAXABLE;
if ( $this->get_product()->get_id() ) {
$tax_status = $this->get_product()->get_tax_status();
}
return apply_filters( 'storeengine/price/tax_status', $tax_status, $this );
}
/**
* Returns whether the product-pricing is taxable.
*
* @return bool
*/
public function is_taxable(): bool {
/**
* Filters whether a product is taxable.
*
* @param bool $taxable Whether the product is taxable.
* @param Price $price Product object.
*/
return apply_filters( 'storeengine/price/is_taxable', $this->get_product()->is_taxable(), $this );
}
public function is_subscription(): bool {
return 'subscription' === $this->get_type();
}
public function is_upgradeable(): bool {
return $this->get_upgradeable();
}
// Setters.
public function set_price_name( string $value ) {
$this->set_prop( 'price_name', $value );
}
public function set_name( string $value ) {
$this->set_price_name( $value );
}
public function set_price_type( string $value ) {
$this->set_prop( 'price_type', $value );
}
public function set_type( string $value ) {
$this->set_price_type( $value );
}
public function set_price( $value ) {
if ( null === $value ) {
$this->set_prop( 'price', null );
return;
}
$this->set_prop( 'price', abs( floatval( $value ) ) );
}
public function set_compare_price( $value ) {
if ( null === $value ) {
$this->set_prop( 'compare_price', null );
return;
}
$this->set_prop( 'compare_price', abs( floatval( $value ) ) );
}
public function set_product_id( $value ) {
$this->set_prop( 'product_id', absint( $value ) );
}
public function set_order( $value ) {
// set sort order.
$this->set_prop( 'order', absint( $value ) );
}
public function set_menu_order( $value ) {
// set sort order.
$this->set_prop( 'order', absint( $value ) );
}
public function set_setup_fee( bool $value ) {
$this->set_prop( 'setup_fee', $value );
}
public function set_setup_fee_name( string $value ) {
$this->set_prop( 'setup_fee_name', $value );
}
public function set_setup_fee_price( $value ) {
$this->set_prop( 'setup_fee_price', abs( floatval( $value ) ) );
}
public function set_setup_fee_type( string $value ) {
if ( 'fixed' === $value ) {
$value = 'fee';
}
$this->set_prop( 'setup_fee_type', $value );
}
public function set_trial( bool $value ) {
$this->set_prop( 'trial', $value );
}
public function set_trial_days( $value ) {
$this->set_prop( 'trial_days', absint( $value ) );
}
public function set_expire( bool $value ) {
$this->set_prop( 'expire', $value );
}
public function set_expire_days( $value ) {
$this->set_prop( 'expire_days', absint( $value ) );
}
public function set_payment_duration( $value ) {
$this->set_prop( 'payment_duration', absint( $value ) );
}
public function set_period_interval( $value ) {
$this->set_payment_duration( $value );
}
public function set_payment_duration_type( string $value ) {
if ( ! in_array( $value, [ 'day', 'week', 'month', 'year' ], true ) ) {
$old_values = [
'days' => 'day',
'weeks' => 'week',
'months' => 'month',
'years' => 'year',
];
$value = $old_values[ $value ] ?? 'month';
}
$this->set_prop( 'payment_duration_type', $value );
}
public function set_period( string $value ) {
$this->set_payment_duration_type( $value );
}
public function set_upgradeable( bool $value ) {
$this->set_prop( 'upgradeable', $value );
}
public function set_enable_license_activation_limit( bool $value ) {
$this->set_prop( 'enable_license_activation_limit', $value );
}
public function set_license_activation_limit( $value ) {
$this->set_prop( 'license_activation_limit', $value );
}
public function set_license_prefix( ?string $value = null ) {
$this->set_prop( 'license_prefix', $value );
}
public function set_license_segments( ?string $value = null ) {
$this->set_prop( 'license_segments', $value );
}
public function set_down_payment( ?string $value = null ) {
$this->set_prop( 'down_payment', $value ? abs(floatval( $value ) ) : null );
}
/**
* @param array|string|null $value
*
* @return void
*/
public function set_installments( $value = null ) {
$this->set_prop( 'installments', $value ? maybe_unserialize( $value ) : null );
}
/**
* @param string|int|bool $value Possible values are yes|no|0|1|true|false.
*
* @return void
*/
public function set_is_hidden( $value ): void {
$this->set_prop( 'is_hidden', Formatting::string_to_bool( $value ) );
}
// Extras
public function get_settings(): array {
return $this->get_props( $this->settings_props );
}
public function get_product() {
if ( ! $this->get_product_id() ) {
return false;
}
return ( new ProductFactory() )->get_product( $this->get_product_id() );
}
public function get_product_title(): string {
if ( null === $this->product_title && $this->get_product_id() ) {
// Do not load in the read_data.
// If we load this inside read_data method, then we need to clear
// every price cache for the product from object cache.
// @TODO convert product class into entity class to take advantage of the cache.
$this->product_title = get_the_title( $this->get_product_id() );
}
return $this->product_title;
}
public function get_post_title(): string {
return $this->get_product_title();
}
/**
* Allow setting temp product title custom item.
*
* @param string $value
*
* @return void
*/
public function set_product_title( string $value ) {
$this->product_title = trim( $value );
}
public function get_product_type(): ?string {
if ( null === $this->product_type && $this->get_product_id() ) {
// Do not load in the read_data.
// If we load this inside read_data method, then we need to clear
// every price cache for the product from object cache.
// @TODO convert product class into entity class to take advantage of the cache.
$this->product_type = get_post_meta( $this->get_product_id(), '_storeengine_product_type', true );
}
return $this->product_type;
}
public function get_shipping_type(): ?string {
if ( null === $this->shipping_type && $this->get_product_id() ) {
// Do not load in the read_data.
// If we load this inside read_data method, then we need to clear
// every price cache for the product from object cache.
// @TODO convert product class into entity class to take advantage of the cache.
$this->shipping_type = get_post_meta( $this->get_product_id(), '_storeengine_product_shipping_type', true );
}
return $this->shipping_type;
}
public function get_digital_auto_complete(): bool {
if ( null === $this->digital_auto_complete && $this->get_product_id() ) {
// Do not load in the read_data.
// If we load this inside read_data method, then we need to clear
// every price cache for the product from object cache.
// @TODO convert product class into entity class to take advantage of the cache.
$this->digital_auto_complete = Formatting::string_to_bool( get_post_meta( $this->get_product_id(), '_storeengine_product_digital_auto_complete', true ) );
}
return 'digital' === $this->get_shipping_type() && $this->digital_auto_complete;
}
/**
* Allow setting temp product title custom item.
*
* @param string $value
*
* @return void
*/
public function set_product_type( string $value ) {
$this->product_type = trim( $value );
}
/**
* @return Integration[]
*/
public function get_integrations(): array {
if ( 0 === $this->id ) {
return [];
}
$key = 'storeengine_price_' . $this->get_id() . '_integrations';
$cached = wp_cache_get( $key, $this->cache_group );
if ( $cached && is_array( $cached ) ) {
return array_map( fn( $result ) => ( new Integration() )->set_data( $result ), $cached );
}
global $wpdb;
$integrations = [];
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.DirectDatabaseQuery.DirectQuery
$results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}storeengine_integrations WHERE price_id = %d", $this->get_id() ) );
if ( ! $results ) {
return $integrations;
}
wp_cache_set( $key . $this->id . '_integrations', $results, $this->cache_group );
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.DirectDatabaseQuery.DirectQuery
return array_map( fn( $result ) => ( new Integration() )->set_data( $result ), $results );
}
/**
* @param string $provider
* @param int $integration_id
*
* @return false|Integration
*/
public function add_integration( string $provider, int $integration_id ) {
$integration = new Integration();
$integration->set_product_id( $this->get_product_id() );
$integration->set_price_id( $this->get_id() );
$integration->set_integration_id( $integration_id );
$integration->set_provider( $provider );
$integration->save();
if ( 0 === $integration->get_id() ) {
return false;
}
return $integration;
}
public function remove_integration( string $provider, int $integration_id ): bool {
$integration = new Integration();
$integration->set_product_id( $this->get_product_id() );
$integration->set_price_id( $this->get_id() );
$integration->set_integration_id( $integration_id );
$integration->set_provider( $provider );
return $integration->delete_by_price_and_integration();
}
/**
* @return string
* @deprecated
*/
public function get_formatted_payment_duration(): string {
$payment_duration = $this->get_payment_duration();
// @TODO use array of duration types & i18n properly.
if ( empty( $payment_duration ) || 1 === $payment_duration ) {
return Formatting::price( $this->get_price() ) . ' / Every ' . ucfirst( $this->get_payment_duration_type() );
}
return Formatting::price( $this->get_price() ) . ' / ' . $payment_duration . '-' . $this->get_payment_duration_type() . 's';
}
/**
* Get the suffix to display after prices > 0.
*
* This is skipped if the suffix
* has dynamic values such as {price_excluding_tax} for variable products.
*
* @param string|float $price to calculate, left blank to just use get_price().
* @param int $qty passed on to get_price_including_tax() or get_price_excluding_tax().
*
* @return string
* @see get_price_html for an explanation as to why.
*/
public function get_price_suffix( $price = '', int $qty = 1 ): string {
$html = '';
$suffix = Helper::get_settings( 'price_display_suffix' );
if ( $suffix && TaxUtil::is_tax_enabled() && ProductTaxStatus::TAXABLE === $this->get_tax_status() ) {
if ( '' === $price ) {
$price = $this->get_price();
}
$replacements = [
'{price_including_tax}' => Formatting::price( Formatting::get_price_including_tax( $this->get_price(), $this->get_id(), $this->get_product_id(), [
'qty' => $qty,
'price' => $price,
] ) ),
// @phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine, WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
'{price_excluding_tax}' => Formatting::price( Formatting::get_price_excluding_tax( $this->get_price(), $this->get_id(), $this->get_product_id(), [
'qty' => $qty,
'price' => $price,
] ) ),
// @phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
];
$html = str_replace( array_keys( $replacements ), array_values( $replacements ), ' ' . wp_kses_post( $suffix ) . '' );
}
return apply_filters( 'storeengine/get_price_suffix', $html, $this, $price, $qty );
}
public function is_product_synced(): bool {
return false;
}
public function get_formatted_price(): string {
if ( empty( $this->get_price() ) ) {
$price = apply_filters( 'storeengine/empty_price_html', '', $this );
} elseif ( ! empty( $this->get_compare_price() ) ) {
$price = Formatting::format_sale_price(
Formatting::get_price_to_display( $this->get_compare_price(), $this->get_id(), $this->get_product_id() ),
Formatting::get_price_to_display( $this->get_price(), $this->get_id(), $this->get_product_id() )
);
} else {
$price = Formatting::price( Formatting::get_price_to_display( $this->get_price(), $this->get_id(), $this->get_product_id() ) );
}
$price = $price ? $price . $this->get_price_suffix() : $price;
return apply_filters( 'storeengine/formatted_price', $price, $this );
}
public function get_price_html() {
return apply_filters( 'storeengine/get_price_html', $this->get_formatted_price(), $this );
}
public function print_price_html() {
echo wp_kses_post( $this->get_price_html() );
}
public function get_price_summery_html() {
return apply_filters( 'storeengine/get_price_summery_html', $this->get_formatted_price(), $this );
}
public function get_formatted_setup_fee(): string {
if ( $this->has_setup_fee() ) {
$fee_name = $this->get_setup_fee_name() ? $this->get_setup_fee_name() : __( 'Setup Fee', 'storeengine' );
return sprintf(
'%s %s',
Formatting::price( Formatting::get_price_to_display( $this->get_setup_fee_price(), $this->get_id(), $this->get_product_id() ) ),
esc_html( $fee_name )
);
}
return '';
}
public function get_formatted_price_meta(): array {
$price_meta = [
'setup-fee' => $this->get_formatted_setup_fee(),
];
return array_filter( apply_filters( 'storeengine/get_formatted_price_meta', $price_meta, $this ) );
}
public function get_formatted_price_meta_html() {
$details_html = [];
foreach ( $this->get_formatted_price_meta() as $key => $detail ) {
if ( ! $detail ) {
continue;
}
$details_html[] = '' . $detail . '';
}
$details_html = implode( '', $details_html );
return apply_filters( 'storeengine/get_formatted_price_meta_html', $details_html, $this );
}
public function print_price_summery_html() {
echo wp_kses_post( $this->get_price_summery_html() );
}
public function print_formatted_price_meta_html() {
echo wp_kses_post( $this->get_formatted_price_meta_html() );
}
}