<?php
namespace SabaiApps\Directories\Component\Payment;

use SabaiApps\Directories\Component\AbstractComponent;
use SabaiApps\Directories\Component\Payment\Component\IComponent;
use SabaiApps\Directories\Component\System;
use SabaiApps\Directories\Component\Display;
use SabaiApps\Directories\Component\Field;
use SabaiApps\Directories\Component\Entity;
use SabaiApps\Directories\Component\CSV;
use SabaiApps\Directories\Context;
use SabaiApps\Directories\Exception;
use SabaiApps\Directories\Application;
use SabaiApps\Directories\Response;

class PaymentComponent extends AbstractComponent implements
	System\IMainRouter,
	System\IScheduledActions,
	IFeatures,
	Display\IButtons,
	Display\ILabels,
	Field\ITypes,
	Field\IRenderers,
	Field\IWidgets,
	CSV\IExporters,
	CSV\IImporters,
	System\ITools {

	const VERSION                   = '2.0.6', PACKAGE = 'directories-pro';
	const FEATURE_STATUS_PENDING    = 0, FEATURE_STATUS_APPLIED = 1, FEATURE_STATUS_UNAPPLIED = 2,
		FEATURE_STATUS_FAILED_APPLY = 3, FEATURE_STATUS_FAILED_UNAPPLY = 4;
	protected $_paymentComponentName;

	public static function interfaces() {
		return array( 'Dashboard\IPanels', 'Notification\IEvents', 'WordPressContent\INotifications' );
	}

	public static function description() {
		return 'Provides a generic payment platform which allows other components to use any of the payment methods that plug into Payment.';
	}

	public static function events() {
		return array(
			// For adding payment tab
			'EntityAdminBundleSettingsFormFilter' => 99,
			// For ensuring entity level is not loaded through entity level check if a payment plan is applied
			'EntityFieldValuesLoaded'             => 99,
			'EntityLevelFilter'                   => 99,
		);
	}

	protected function _init() {
		// Compat with <1.4.0
		if ( ! isset( $this->_config['plan'] ) ) {
			$this->_config['plan'] = array();
			if ( isset( $this->_config['expired'] ) ) {
				$this->_config['plan']['expired_action'] = $this->_config['expired']['action'];
			}
			if ( isset( $this->_config['selection'] ) ) {
				$this->_config['plan'] += $this->_config['selection'];
			}
			if ( isset( $this->_config['pricing_table'] ) ) {
				$this->_config['plan']['pricing_table'] = $this->_config['pricing_table'];
			}
			if ( isset( $this->_config['renewal'] ) ) {
				$this->_config['plan'] += $this->_config['renewal'];
			}
			if ( isset( $this->_config['upgrade'] ) ) {
				$this->_config['plan'] += $this->_config['upgrade'];
			}
			if ( isset( $this->_config['woocommerce'] ) ) {
				$this->_config['payment']['components']['woocommerce'] = $this->_config['woocommerce'];
			}
			unset( $this->_config['expired'], $this->_config['selection'], $this->_config['renewal'], $this->_config['upgrade'], $this->_config['pricing_table'], $this->_config['woocommerce'] );
		}
	}

	public function getDefaultConfig() {
		return array(
			'payment' => array(
				'component'  => 'default',
				'components' => array(
					'woocommerce' => array(
						'bypass_cart' => 0,
					),
				),
			),
			'plan'    => array(
				'expiring_days'     => 7,
				'grace_period_days' => null,
				'reactivation_fee'  => null,
				'switch_plan_fee'   => null,
				'prorated_discount' => 1,
				'pricing_table'     => array(
					'show'     => 0,
					'color'    => 'primary',
					'btn_text' => null,
				),
				'allow_none'        => 0,
				'none_label'        => null,
				'none_desc'         => null,
			),
		);
	}

	public function systemMainRoutes( $lang = null ) {
		$routes = array();
		if ( $this->_application->isComponentLoaded( 'Dashboard' ) ) {
			$dashboard_slug = '/' . $this->_application->getComponent( 'Dashboard' )->getSlug( 'dashboard', $lang );
			$routes[ $dashboard_slug . '/:user_name/:panel_name/payment_orders' ]                         = array(
				'controller'    => 'DashboardOrders',
				'callback_path' => 'dashboard',
			);
			$routes[ $dashboard_slug . '/:user_name/:panel_name/payment_orders/:order_id' ]               = array(
				'format'          => array( ':order_id' => '\d+' ),
				'controller'      => 'ViewOrder',
				'callback_path'   => 'order',
				'access_callback' => true,
				'title_callback'  => true,
			);
			$routes[ $dashboard_slug . '/:user_name/:panel_name/payment_orders/:order_id/cancel_order' ]  = array(
				'controller'      => 'CancelOrder',
				'callback_path'   => 'cancel_order',
				'access_callback' => true,
			);
			$routes[ $dashboard_slug . '/:user_name/:panel_name/payment_orders/:order_id/pay' ]           = array(
				'controller'    => 'PayOrder',
				'callback_path' => 'pay_order',
			);
			$routes[ $dashboard_slug . '/:user_name/:panel_name/payment_subscriptions' ]                  = array(
				'controller'    => 'DashboardSubscriptions',
				'callback_path' => 'dashboard',
			);
			$routes[ $dashboard_slug . '/:user_name/:panel_name/payment_subscriptions/:subscription_id' ] = array(
				'controller'      => 'ViewSubscription',
				'callback_path'   => 'subscription',
				'access_callback' => true,
				'title_callback'  => true,
			);
			$routes[ $dashboard_slug . '/:user_name/:panel_name/payment_subscriptions/:subscription_id/cancel_subscription' ] = array(
				'controller'      => 'CancelSubscription',
				'callback_path'   => 'cancel_subscription',
				'access_callback' => true,
			);
			$routes[ $dashboard_slug . '/:user_name/:panel_name/posts/:entity_id/submit' ]                                    = array(
				'controller'      => 'SubmitPost',
				'access_callback' => true,
				'title_callback'  => true,
				'callback_path'   => 'submit',
				'priority'        => 5,
			);
			$routes[ $dashboard_slug . '/:user_name/:panel_name/posts/:entity_id/renew' ]                                     = array(
				'controller'      => 'SubmitPost',
				'access_callback' => true,
				'title_callback'  => true,
				'callback_path'   => 'renew',
				'priority'        => 5,
			);
			$routes[ $dashboard_slug . '/:user_name/:panel_name/posts/:entity_id/upgrade' ]                                   = array(
				'controller'      => 'SubmitPost',
				'access_callback' => true,
				'title_callback'  => true,
				'callback_path'   => 'upgrade',
				'priority'        => 5,
			);
			$routes[ $dashboard_slug . '/:user_name/:panel_name/posts/:entity_id/order_addon' ]                               = array(
				'controller'      => 'OrderAddon',
				'title_callback'  => true,
				'access_callback' => true,
				'callback_path'   => 'order_addon',
				'priority'        => 3,
			);
		}

		if ( $this->_application->isComponentLoaded( 'FrontendSubmit' ) ) {
			foreach ( $this->_application->Entity_Bundles() as $bundle ) {
				if ( empty( $bundle->info['payment_enable'] )
					|| ! empty( $bundle->info['parent'] )
					|| ! empty( $bundle->info['parent_field'] )
				) {
					continue;
				}

				if ( $bundle->entitytype_name === 'post' ) {
					$routes[ '/' . $this->_application->FrontendSubmit_AddEntitySlug( $bundle, $lang ) ] = array(
						'controller'         => 'AddEntity',
						'access_callback'    => true,
						'title_callback'     => true,
						'callback_path'      => 'add_entity',
						'data'               => array(
							'bundle_type' => $bundle->type,
						),
						'callback_component' => 'FrontendSubmit',
						'priority'           => 5,
					);
				}
				// Add pricing table
				$routes[ $bundle->getPath( false, $lang ) . '/pricing' ] = array(
					'controller'     => 'PricingTable',
					'title_callback' => true,
					'callback_path'  => 'pricing',
					'priority'       => 3,
				);

				if ( $bundle->entitytype_name === 'post'
					&& $this->_application->isComponentLoaded( 'Claiming' )
					&& ! empty( $bundle->info['claiming_enable'] )
					&& ( $claim_bundle = $this->_application->Entity_Bundle( 'claiming_claim', $bundle->group ) )
				) {
					$routes[ '/' . $this->_application->FrontendSubmit_AddEntitySlug( $claim_bundle, $lang ) ] = array(
						'controller'         => 'ClaimEntity',
						'access_callback'    => true,
						'title_callback'     => true,
						'callback_path'      => 'add_child_entity',
						'data'               => array(
							'bundle_type' => $claim_bundle->type,
							'component'   => $claim_bundle->component,
							'group'       => $claim_bundle->group,
						),
						'callback_component' => 'FrontendSubmit',
						'priority'           => 5,
					);
				}
			}
		}

		$routes['/_drts/entity/:bundle_type/query_payment_entities'] = array(
			'controller' => 'QueryEntities',
			'type'       => Application::ROUTE_CALLBACK,
		);

		return $routes;
	}

	public function systemOnAccessMainRoute( Context $context, $path, $accessType, array &$route ) {
		switch ( $path ) {
			case 'dashboard':
				if ( $accessType === Application::ROUTE_ACCESS_LINK ) {
					if ( $context->dashboard_panel !== 'payment_payments' ) {
						return false;
					}
				}
				return true;
			case 'order':
				if ( ( ! $order_id = $context->getRequest()->asStr( 'order_id' ) )
					|| ( ! $payment_component = $this->getPaymentComponent( false ) )
					|| ( ! $order = $payment_component->paymentGetOrderById( $order_id ) )
					|| ( $order->paymentOrderUserId() !== $this->_application->getUser()->id && ! $this->_application->IsAdministrator() )
				) {
					return false;
				}
				$context->order = $order;
				return true;
			case 'cancel_order':
				return $context->order->paymentOrderIsPendingPayment();
			case 'subscription':
				if ( ( ! $subscription_id = $context->getRequest()->asStr( 'subscription_id' ) )
					|| ( ! $subscription_id_parts = explode( '-', $subscription_id ) )
					|| count( $subscription_id_parts ) !== 2
					|| ( ! $payment_component = $this->_application->Payment_Components_impl( $subscription_id_parts[0], true ) )
					|| ! $payment_component->paymentIsEnabled()
					|| ( ! $subscription = $payment_component->paymentGetSubscriptionById( $subscription_id_parts[1] ) )
					|| ( $subscription->paymentSubscriptionUserId() !== $this->_application->getUser()->id && ! $this->_application->IsAdministrator() )
				) {
					return false;
				}

				$context->payment_component_name = $subscription_id_parts[0];
				$context->subscription           = $subscription;
				return true;
			case 'cancel_subscription':
				return $context->subscription->paymentSubscriptionIsActive();
			case 'submit':
			case 'renew':
			case 'upgrade':
			case 'order_addon':
				if ( $accessType === Application::ROUTE_ACCESS_LINK ) {
					return true;
				}

				$context->action = $path;
				return $this->_application->Entity_IsRoutable( $context->bundle, $context->action, $context->entity );
		}
	}

	public function systemMainRouteTitle( Context $context, $path, $titleType, array $route ) {
		switch ( $path ) {
			case 'order':
				return $context->order->paymentOrderNumber();
			case 'renew':
				return $this->_application->Entity_Title( $context->entity ) . ' - ' . __( 'Renew', 'directories-pro' );
			case 'upgrade':
				return $this->_application->Entity_Title( $context->entity ) . ' - ' . __( 'Upgrade / Downgrade', 'directories-pro' );
			case 'order_addon':
				return $this->_application->Entity_Title( $context->entity ) . ' - ' . __( 'Order Add-on', 'directories-pro' );
			case 'pricing':
				return __( 'Pricing Table', 'directories-pro' );
		}
	}

	public function fieldGetTypeNames() {
		return array( 'payment_plan', 'payment_orders', 'payment_subscriptions' );
	}

	public function fieldGetType( $name ) {
		switch ( $name ) {
			case 'payment_plan':
				return new FieldType\PlanFieldType( $this->_application, $name );
			case 'payment_orders':
				return new FieldType\OrdersFieldType( $this->_application, $name );
			case 'payment_subscriptions':
				return new FieldType\SubscriptionsFieldType( $this->_application, $name );
		}
	}

	public function fieldGetRendererNames() {
		return array( 'payment_plan' );
	}

	public function fieldGetRenderer( $name ) {
		switch ( $name ) {
			case 'payment_plan':
				return new FieldRenderer\PlanFieldRenderer( $this->_application, $name );
		}
	}

	public function fieldGetWidgetNames() {
		return array( 'payment_plan', 'payment_orders', 'payment_subscriptions' );
	}

	public function fieldGetWidget( $name ) {
		switch ( $name ) {
			case 'payment_plan':
				return new FieldWidget\PlanFieldWidget( $this->_application, $name );
			case 'payment_orders':
				return new FieldWidget\OrdersFieldWidget( $this->_application, $name );
			case 'payment_subscriptions':
				return new FieldWidget\SubscriptionsFieldWidget( $this->_application, $name );
		}
	}

	public function paymentGetFeatureNames() {
		return array( 'payment_plan', 'payment_featured_entity', 'payment_taxonomy_terms', 'payment_fields', 'payment_amp' );
	}

	public function paymentGetFeature( $name ) {
		switch ( $name ) {
			case 'payment_plan':
				return new Feature\PlanFeature( $this->_application, $name );
			case 'payment_fields':
				return new Feature\FieldsFeature( $this->_application, $name );
			case 'payment_featured_entity':
				return new Feature\FeaturedEntityFeature( $this->_application, $name );
			case 'payment_taxonomy_terms':
				return new Feature\TaxonomyTermsFeature( $this->_application, $name );
			case 'payment_amp':
				return new Feature\AmpFeature( $this->_application, $name );
		}
	}

	public function onEntityFormFilter( &$form, $bundle, $entity, &$options ) {
		if ( empty( $bundle->info['payment_enable'] ) ) {
			return;
		}

		// Disable feature level selection field if payment plan selected
		if ( isset( $form['entity_level'] ) ) {
			$selector = $this->_application->Form_Form_fieldName( array( $options['wrap'], 'payment_plan', 0, 'plan_id' ), true );
			$form['entity_level']['#states']['enabled'][ $selector ] = array( 'value' => 0 );
		}

		// Apply plan features to form
		if ( empty( $options['payment_plan'] )
			|| ! $options['payment_plan'] instanceof IPlan
		) {
			return;
		}

		$form['#class'] .= ' drts-payment-plan-' . $options['payment_plan']->paymentPlanId();
		if ( $options['entity_level'] = $options['payment_plan']->paymentPlanEntityLevel() ) {
			return;
		}

		// Make sure to not load features through entity level check
		$options['entity_level'] = false;

		// Process payment features
		$features = $options['payment_plan']->paymentPlanFeatures();
		foreach ( array_keys( $features ) as $feature_name ) {
			if ( ! $feature = $this->_application->Payment_Features_impl( $feature_name, true ) ) {
				continue;
			}

			$feature->paymentFeatureOnEntityForm( $bundle, $features[ $feature_name ], $form, $entity, $options['is_admin'] );
		}
	}

	public function onEntityFieldValuesLoaded( $entity, $bundle, $cache ) {
		if ( ! $cache
			|| ( ! $plan = $this->_application->Payment_Plan( $entity ) )
			|| $plan->paymentPlanEntityLevel()
		) {
			return;
		}

		// Make sure to not load features through entity level check
		$entity->setFieldValue( 'entity_level', false );

		// Process payment features
		$features       = $plan->paymentPlanFeatures();
		$addon_features = $entity->getSingleFieldValue( 'payment_plan', 'addon_features' );
		foreach ( array_keys( $features ) as $feature_name ) {
			if ( ! $feature = $this->_application->Payment_Features_impl( $feature_name, true ) ) {
				continue;
			}

			if ( isset( $addon_features[ $feature_name ] ) ) {
				$features[ $feature_name ] = $feature->paymentAddonFeatureExtraApply( $features[ $feature_name ], $addon_features[ $feature_name ] );
			}
			$feature->paymentFeatureOnEntityFieldValues( $entity, $bundle, $features[ $feature_name ] );
		}

		// Load translated payment plan ID
		if ( empty( $bundle->info['is_taxonomy'] )
			&& ( $payment_plan = $entity->getSingleFieldValue( 'payment_plan' ) )
			&& $this->_application->getPlatform()->isTranslatable( $entity->getType(), $entity->getBundleName() )
			&& ( $default_lang = $this->_application->getPlatform()->getDefaultLanguage() )
			&& ( $lang = $this->_application->getPlatform()->getLanguageFor( $entity->getType(), $entity->getBundleName(), $entity->getId() ) )
			&& ( $default_lang !== $lang )
			&& ( $payment_component = $this->_application->Payment_Components_impl( empty( $payment_plan['component'] ) ? 'wooocommerce' : $payment_plan['component'], true ) )
			&& ( $plan_id = $payment_component->paymentGetPlanId( $entity->getBundleName(), $payment_plan['plan_id'], $lang ) )
			&& ( $plan_id !== $payment_plan['plan_id'] )
		) {
			$payment_plan['plan_id'] = $plan_id;
			$entity->setFieldValue( 'payment_plan', array( $payment_plan ) );
		}
	}

	public function onDirectoryContentTypeSettingsFormFilter( &$form, $directoryType, $contentType, $info, $settings, $parents, $submitValues ) {
		if ( empty( $info['payment_enable'] )
			|| ! empty( $info['is_taxonomy'] )
			|| ! empty( $info['parent'] )
			|| ! empty( $info['parent_field'] )
			|| empty( $info['public'] )
		) {
			return;
		}

		$form['payment_enable'] = array(
			'#type'          => 'checkbox',
			'#title'         => __( 'Enable payments', 'directories-pro' ),
			'#default_value' => ! empty( $settings['payment_enable'] ) || is_null( $settings ),
			'#horizontal'    => true,
			'#weight'        => 60,
		);
	}

	public function onDirectoryContentTypeInfoFilter( &$info, $contentType, $settings = null ) {
		if ( ! isset( $info['payment_enable'] ) ) {
			return;
		}

		if ( ! empty( $info['is_taxonomy'] )
			|| ! empty( $info['parent'] )
			|| ! empty( $info['parent_field'] )
			|| empty( $info['public'] )
		) {
			unset( $info['payment_enable'] );
		}

		if ( isset( $settings['payment_enable'] ) && ! $settings['payment_enable'] ) {
			$info['payment_enable'] = false;
		}
	}

	public function onEntityBundleInfoKeysFilter( &$keys ) {
		$keys[] = 'payment_enable';
	}

	public function onEntityBundleInfoUserKeysFilter( &$keys ) {
		$keys[] = 'payment_default_features';
	}

	protected function _onEntityCreateBundlesSuccess( $bundles, $update = false ) {
		foreach ( $bundles as $bundle ) {
			if ( empty( $bundle->info['is_taxonomy'] ) ) {
				// Add plan field?
				if ( ! empty( $bundle->info['payment_enable'] ) ) {
					$this->_application->getComponent( 'Entity' )->createEntityField(
						$bundle,
						'payment_plan',
						array(
							'type'          => 'payment_plan',
							'settings'      => array(),
							'weight'        => 99,
							'max_num_items' => 1,
						)
					);
					$this->_application->getComponent( 'Entity' )->createEntityField(
						$bundle,
						'payment_orders',
						array(
							'type'  => 'payment_orders',
							'label' => __( 'Orders', 'directories-pro' ),
						)
					);
					$this->_application->getComponent( 'Entity' )->createEntityField(
						$bundle,
						'payment_subscriptions',
						array(
							'type'  => 'payment_subscriptions',
							'label' => __( 'Subscriptions', 'directories-pro' ),
						)
					);
				}
			}
		}
	}

	public function onEntityCreateBundlesSuccess( $bundles ) {
		$this->_onEntityCreateBundlesSuccess( $bundles );
	}

	public function onEntityUpdateBundlesSuccess( $bundles ) {
		$this->_onEntityCreateBundlesSuccess( $bundles, true );
	}

	public function systemScheduledActions() {
		if ( ! $this->_application->Entity_Bundles_byFeature( 'payment_enable' ) ) {
			return array();
		}

		return array(
			'payment_expire_entities' => array(
				'title'             => __( 'Expire Paid Entities', 'directories-pro' ),
				'description'       => __( 'Expires entities whose payment expiration date has passed.', 'directories-pro' ),
				'recurrence'        => 'daily',
				'immediate_actions' => array(
					'payment_entity_expiring'          => array(
						'args_count' => 2,
					),
					'payment_entity_expired'           => array(
						'args_count' => 2,
					),
					'payment_entity_expired_processed' => array(
						'args_count' => 2,
					),
				),
			),
		);
	}

	public function onPaymentExpireEntities() {
		$grace_period_seconds = 86400 * $this->_config['plan']['grace_period_days'];
		$expiring_ts          = time() + 86400 * $this->_config['plan']['expiring_days'];

		// Fetch entities with expired level
		foreach ( $this->_application->Entity_BundleTypes_byFeatures( array( 'payment_enable' ) ) as $entity_type => $bundle_types ) {
			$entities = $this->_application->Entity_Query( $entity_type )
				->fieldIsIn( 'bundle_type', $bundle_types )
				->fieldIsIn( 'status', $this->_application->Entity_Status( $entity_type, array( 'publish', 'private' ) ) )
				->fieldIsOrSmallerThan( 'payment_plan', $expiring_ts, 'expires_at' )
				->fieldIsGreaterThan( 'payment_plan', 0, 'expires_at' ) // exclude those that never expire
				->fieldIs( 'payment_plan', 0, 'deactivated_at' ) // exclude those already deactivated
				->fetch();

			if ( ! count( $entities ) ) {
				continue;
			}
			$this->_application->logDebug( sprintf( 'Found %d entities with expiring or expired payment plan.', count( $entities ) ) . ' - ' . $entity_type );

			// Group entities expiring and those need deactivated
			$entities_expiring = $entities_expired = $entities_fully_expired = array();
			foreach ( array_keys( $entities ) as $entity_id ) {
				if ( ! $plan = $entities[ $entity_id ]->getSingleFieldValue( 'payment_plan' ) ) { // this should not happen, but just in case
					unset( $entities[ $entity_id ] );
					continue;
				}

				if ( $plan['expires_at'] > time() ) {
					$this->_application->logDebug( sprintf( 'Entity is expiring soon: %s - %s', $entities[ $entity_id ]->getTitle(), $entity_id ) );
					$entities_expiring[ $entities[ $entity_id ]->getBundleName() ][ $entity_id ] = $entities[ $entity_id ];
					unset( $entities[ $entity_id ] );
					continue;
				}

				if ( $plan['expires_at'] + $grace_period_seconds < time() ) {
					// Expired and renewal grace period has passed
					$this->_application->logDebug( sprintf( 'Entity is fully expired: %s - %s', $entities[ $entity_id ]->getTitle(), $entity_id ) );
					$entities_fully_expired[ $entities[ $entity_id ]->getBundleName() ][ $entity_id ] = $entities[ $entity_id ];
					unset( $entities[ $entity_id ] );
					continue;
				}

				$this->_application->logDebug( sprintf( 'Entity is expired but within renewal grace period: %s - %s', $entities[ $entity_id ]->getTitle(), $entity_id ) );
				$entities_expired[ $entities[ $entity_id ]->getBundleName() ][ $entity_id ] = $entities[ $entity_id ];
				unset( $entities[ $entity_id ] );
			}

			// Hook for expiring entities
			if ( ! empty( $entities_expiring ) ) {
				foreach ( array_keys( $entities_expiring ) as $bundle_name ) {
					$this->_application->logNotice( sprintf( __( 'Found %d expiring item(s).', 'directories-pro' ), count( $entities_expiring[ $bundle_name ] ) ) . ' - ' . $bundle_name );
					foreach ( $entities_expiring[ $bundle_name ] as $entity ) {
						$this->_application->getPlatform()->getScheduler()->enqueueAction( 'payment_entity_expiring', array( $entity->getType(), $entity->getId() ) );
					}

					// to be removed in future versions
					$this->_application->Action( 'payment_entities_expiring', array( $bundle_name, $entities_expiring[ $bundle_name ] ) );
				}
			}

			// Hook for expired but within renewal graceful period entities
			if ( ! empty( $entities_expired ) ) {
				foreach ( array_keys( $entities_expired ) as $bundle_name ) {
					$this->_application->logNotice( sprintf( __( 'Found %d expired item(s).', 'directories-pro' ), count( $entities_expired[ $bundle_name ] ) ) . ' - ' . $bundle_name );
					foreach ( $entities_expired[ $bundle_name ] as $entity ) {
						$this->_application->getPlatform()->getScheduler()->enqueueAction( 'payment_entity_expired', array( $entity->getType(), $entity->getId() ) );
					}

					// to be removed in future versions
					$this->_application->Action( 'payment_entities_expired', array( $bundle_name, $entities_expired[ $bundle_name ] ) );
				}
			}

			// Process entities expired and passed renewal grace period
			if ( ! empty( $entities_fully_expired ) ) {
				foreach ( array_keys( $entities_fully_expired ) as $bundle_name ) {
					$this->_application->logNotice( sprintf( __( 'Found %d fully expired item(s).', 'directories-pro' ), count( $entities_fully_expired[ $bundle_name ] ) ) . ' - ' . $bundle_name );
					$this->_application->callHelper( 'Payment_Util_handleExpired', array( $entity_type, $entities_fully_expired[ $bundle_name ], null ) );
					foreach ( $entities_fully_expired[ $bundle_name ] as $entity ) {
						$this->_application->getPlatform()->getScheduler()->enqueueAction( 'payment_entity_expired_processed', array( $entity->getType(), $entity->getId() ) );
					}

					// to be removed in future versions
					$this->_application->Action( 'payment_entities_expired_processed', array( $bundle_name, $entities_fully_expired[ $bundle_name ] ) );
				}
			}
		}
	}

	public function onEntityAdminBundleSettingsFormFilter( &$form, $bundle, $submitValues ) {
		if ( ! empty( $bundle->info['payment_enable'] )
			&& ! $this->_application->Entity_BundleTypeInfo( $bundle, 'levelable' )
		) {
			$values                                      = empty( $bundle->info['payment_default_features'] ) ? array() : $bundle->info['payment_default_features'];
			$form['payment']['payment_default_features'] = array(
				'#title'       => __( 'Default Feature Settings', 'directories-pro' ),
				'#description' => sprintf( __( 'Configure features applied to %s without any payment plan.', 'directories-pro' ), $bundle->getLabel() ),
				'#tree'        => true,
				'#weight'      => 10,
			) + $this->_application->Payment_Features_form(
				$bundle,
				'base',
				isset( $values['features']['enabled'] ) && isset( $values['features']['disabled'] )
					? array( 'features' => $values['features']['enabled'] + $values['features']['disabled'] )
					: $values,
				array( 'payment_default_features' )
			);
			unset( $form['payment']['payment_default_features'][ $bundle->type . '-base' ]['features']['payment_plan'] );
		}
		if ( ! empty( $form['payment'] ) ) {
			$form['#tabs'][ $this->_name ] = array(
				'#title'  => __( 'Payment', 'directories-pro' ),
				'#weight' => 25,
			);
			$form['payment']              += array(
				'#weight' => 10,
				'#tab'    => $this->_name,
			);
		}
	}

	public function displayGetButtonNames( Entity\Model\Bundle $bundle ) {
		$ret = array();
		if ( $this->_application->isComponentLoaded( 'Dashboard' ) ) {
			if ( $bundle->entitytype_name === 'post'
				&& ! empty( $bundle->info['payment_enable'] )
			) {
				$ret[] = 'payment_renew';
				$ret[] = 'payment_upgrade';
				$ret[] = 'payment_order_addon';
				$ret[] = 'payment_cancel_subscription';
			}
		}
		return $ret;
	}

	public function displayGetButton( $name ) {
		return new DisplayButton\PostDashboardDisplayButton( $this->_application, $name );
	}

	public function displayGetLabelNames( Entity\Model\Bundle $bundle ) {
		return empty( $bundle->info['payment_enable'] ) ? array() : array( 'payment_plan' );
	}

	public function displayGetLabel( $name ) {
		return new DisplayLabel\PlanDisplayLabel( $this->_application, $name );
	}

	public function dashboardGetPanelNames() {
		return array( 'payment_payments' );
	}

	public function dashboardGetPanel( $name ) {
		return new DashboardPanel\PaymentsDashboardPanel( $this->_application, $name );
	}

	public function onDirectoryShortcodesFilter( &$shortcodes ) {
		$shortcodes['payment-pricing'] = '/pricing';
	}

	public function onEntityIsRoutableFilter( &$isRoutable, $bundle, $action, $entity, &$args ) {
		if ( $isRoutable === false
			|| ! isset( $entity )
			|| ! in_array( $action, array( 'submit', 'renew', 'upgrade', 'delete', 'order_addon', 'cancel_subscription' ) )
			|| empty( $bundle->info['payment_enable'] )
		) {
			return;
		}

		if ( $action === 'submit' ) {
			if ( ! $this->getPaymentComponent( false ) ) {
				$isRoutable = true;
				return;
			}

			$isRoutable = false;
			if ( ! $this->_application->Payment_Plan( $entity )
				|| $entity->getSingleFieldValue( 'payment_plan', 'deactivated_at' )
			) {
				$plans      = $this->_application->Payment_Plans(
					$bundle->name,
					$this->_application->Filter( 'payment_base_plan_types', array( 'base' ), array( $bundle->name ) ),
					true
				);
				$isRoutable = count( $plans ) > 0;
			}
			return;
		}

		if ( ! $this->getPaymentComponent( false )
			|| ! $entity->isPublished()
		) {
			return;
		}

		switch ( $action ) {
			case 'delete':
				// Do no allow deleting published item with a subscription payment plan
				// @todo Check if subscription is active
				if ( ( $plan = $this->_application->Payment_Plan( $entity ) )
					&& $plan->paymentPlanType() === 'subscription'
				) {
					$isRoutable = false;
				}
				return;

			case 'order_addon':
				$isRoutable = false;
				if ( ! $plan = $this->_application->Payment_Plan( $entity ) ) {
					return;
				}

				// Make sure it has not yet expired
				$expires_at = $entity->getSingleFieldValue( 'payment_plan', 'expires_at' );
				if ( ! empty( $expires_at )
					&& $expires_at < time()
				) {
					return;
				}

				// Check perm if post
				if ( $entity->getType() === 'post' ) {
					$perm = ( $this->_application->Entity_IsAuthor( $entity ) ? 'entity_edit_' : 'entity_edit_others_' ) . $entity->getBundleName();
					if ( ! $this->_application->HasPermission( $perm ) ) {
						return;
					}
				}

				// Have at least one orderable addon plan?
				$addon_plans = $this->_application->Payment_Plans_orderableAddons( $entity );
				$isRoutable  = count( $addon_plans ) > 0;
				return;

			case 'renew':
				$isRoutable = false;
				if ( ! $plan = $this->_application->Payment_Plan( $entity ) ) {
					return;
				}

				if ( $plan->paymentPlanType() !== 'base' ) {
					return; // base only
				}

				if ( $plan->paymentPlanComponent() !== $this->getPaymentComponent( false, true ) ) {
					return; // not current component
				}

				// Make sure it has expired
				$expires_at   = $entity->getSingleFieldValue( 'payment_plan', 'expires_at' );
				$grace_period = isset( $this->_config['plan']['grace_period_days'] ) ? (int) $this->_config['plan']['grace_period_days'] : 30;
				if ( empty( $expires_at ) // never expires
					|| $expires_at - 86400 * $this->_config['plan']['expiring_days'] > time() // has not yet reached X days before expiration
					|| $expires_at + 86400 * $grace_period < time() // has passed renewal redemption period
				) {
					return;
				}

				// Check perm if post
				if ( $entity->getType() === 'post' ) {
					$perm = ( $this->_application->Entity_IsAuthor( $entity ) ? 'entity_edit_' : 'entity_edit_others_' ) . $entity->getBundleName();
					if ( ! $this->_application->HasPermission( $perm ) ) {
						return;
					}
				}

				// All passed!
				$isRoutable = true;
				return;

			case 'upgrade':
				$isRoutable = false;
				if ( $plan = $this->_application->Payment_Plan( $entity ) ) {
					if ( $plan->paymentPlanType() !== 'base' ) {
						return; // base only
					}

					if ( $plan->paymentPlanComponent() !== $this->getPaymentComponent( false, true ) ) {
						return; // not current component
					}

					// Make sure it has not yet expired
					$expires_at = $entity->getSingleFieldValue( 'payment_plan', 'expires_at' );
					if ( ! empty( $expires_at )
						&& $expires_at < time()
					) {
						return;
					}

					$min_plan_num = 1;
				} else {
					$min_plan_num = 0;
				}

				// Check perm if post
				if ( $entity->getType() === 'post' ) {
					$perm = ( $this->_application->Entity_IsAuthor( $entity ) ? 'entity_edit_' : 'entity_edit_others_' ) . $entity->getBundleName();
					if ( ! $this->_application->HasPermission( $perm ) ) {
						return;
					}
				}

				// Make sure there are more than the min number of plans to which current plan can upgrade/downgrade
				$plans      = $this->_application->Payment_Plans(
					$entity->getBundleName(),
					$this->_application->Filter( 'payment_base_plan_types', array( 'base' ), array( $entity->getBundleName() ) ),
					true
				);
				$isRoutable = count( $plans ) > $min_plan_num;
				return;

			case 'cancel_subscription':
				$isRoutable = false;
				if ( ( ! $plan = $this->_application->Payment_Plan( $entity ) )
					|| $plan->paymentPlanType() !== 'subscription'
					|| ( ! $payment_component_name = $plan->paymentPlanComponent() )
					|| ( ! $payment_component = $this->_application->Payment_Components_impl( $payment_component_name, true ) )
					|| ! $payment_component->paymentIsEnabled()
					|| ! $payment_component->paymentIsSubscriptionEnabled()
					|| ! $payment_component->paymentIsSubscriptionCancellable()
					|| ( ! $subscriptions = $payment_component->paymentGetSubscriptions( $entity, 1, 0 ) )
					|| ! $subscriptions[0]->paymentSubscriptionIsActive()
				) {
					return;
				}

				// Check perm if post
				if ( $entity->getType() === 'post' ) {
					$perm = ( $this->_application->Entity_IsAuthor( $entity ) ? 'entity_edit_' : 'entity_edit_others_' ) . $entity->getBundleName();
					if ( ! $this->_application->HasPermission( $perm ) ) {
						return;
					}
				}

				// All passed!
				$isRoutable           = true;
				$args['subscription'] = $subscriptions[0];
				return;
		}
	}

	/**
	 * @param bool $throwError
	 * @param bool $nameOnly
	 * @return IComponent|false
	 */
	public function getPaymentComponent( $throwError = false, $nameOnly = false ) {
		if ( ! isset( $this->_paymentComponentName ) ) {
			$payment_component_name = isset( $this->_config['payment']['component'] ) ? strtolower( $this->_config['payment']['component'] ) : 'default';
			if ( ( $payment_component = $this->_application->Payment_Components_impl( $payment_component_name, true ) )
				&& $payment_component->paymentIsEnabled()
			) {
				$this->_paymentComponentName = $this->_config['payment']['component'];
			} else {
				$this->_paymentComponentName = false;
			}
		}
		if ( ! $this->_paymentComponentName ) {
			if ( $throwError ) {
				throw new Exception\RuntimeException( 'Invalid payment component. A valid payment component must be configured in Settings -> Payment!' );
			}
			return false;
		}

		if ( $nameOnly ) {
			return $this->_paymentComponentName;
		}

		return $this->_application->Payment_Components_impl( $this->_paymentComponentName );
	}

	public function wpGetNotificationNames() {
		return array( 'payment_expiring', 'payment_expired', 'payment_deactivated' );
	}

	public function wpGetNotification( $name ) {
		return new WordPressNotification\PaymentWordPressNotification( $this->_application, $name );
	}

	public function onPaymentEntitiesExpiring( $bundleName, $entities ) {
		if ( $this->_application->isComponentLoaded( 'WordPressContent' ) ) {
			$expiring_remaining = null;
			if ( $this->_config['plan']['expiring_days'] > 1
				&& ( $send_notification_once = (int) $this->_application->Filter( 'payment_expiring_send_notification_once', false ) )
				&& $send_notification_once < $this->_config['plan']['expiring_days']
			) {
				// Remaining time must be more than [expiring_days - X] days if notifying limited times
				$expiring_remaining = 86400 * ( $this->_config['plan']['expiring_days'] - $send_notification_once );
			}
			foreach ( array_keys( $entities ) as $k ) {
				if ( empty( $expiring_remaining )
					|| $entities[ $k ]->getSingleFieldValue( 'payment_plan', 'expires_at' ) - time() > $expiring_remaining
				) {
					$this->_application->WordPressContent_Notifications_send( 'payment_expiring', $entities[ $k ] );
				}
			}
		}
	}

	public function onPaymentEntitiesExpired( $bundleName, $entities ) {
		if ( $this->_application->isComponentLoaded( 'WordPressContent' ) ) {
			$max_notify_period = null;
			if ( $this->_config['renewal']['grace_period_days'] > 1
				&& ( $send_notification_once = (int) $this->_application->Filter( 'payment_expired_send_notification_once', false ) )
				&& $send_notification_once < $this->_config['renewal']['grace_period_days']
			) {
				$max_notify_period = 86400 * $send_notification_once;
			}
			foreach ( array_keys( $entities ) as $k ) {
				if ( empty( $max_notify_period )
					|| time() - $entities[ $k ]->getSingleFieldValue( 'payment_plan', 'expires_at' ) <= $max_notify_period
				) {
					$this->_application->WordPressContent_Notifications_send( 'payment_expired', $entities[ $k ] );
				}
			}
		}
	}

	public function onPaymentEntitiesExpiredProcessed( $bundleName, $entities ) {
		switch ( $this->_config['expired']['action'] ) {
			case 'deactivate':
				if ( $this->_application->isComponentLoaded( 'WordPressContent' ) ) {
					foreach ( array_keys( $entities ) as $k ) {
						$this->_application->WordPressContent_Notifications_send( 'payment_deactivated', $entities[ $k ] );
					}
				}
				break;
			default:
		}
	}

	public function onWordpressNotificationMessageFilter( &$message, $entity ) {
		if ( strpos( $message, '[drts_payment_expire_days]' ) !== false ) {
			$message = str_replace( '[drts_payment_expire_days]', '[drts_entity field="payment_plan" key="expire_days" format="%value%"]', $message );
		}
		if ( strpos( $message, '[drts_payment_expire_on]' ) !== false ) {
			$message = str_replace( '[drts_payment_expire_on]', '[drts_entity field="payment_plan" key="expire_on" format="%value%"]', $message );
		}
		if ( strpos( $message, '[drts_payment_renew_grace_period_days]' ) !== false ) {
			$message = str_replace( '[drts_payment_renew_grace_period_days]', $this->_config['plan']['grace_period_days'], $message );
		}
	}

	public function onDirectoryAdminSettingsFormFilter( &$form ) {
		// Moved from Directory to System component
		$this->onSystemAdminSettingsFormFilter( $form );
	}

	public function onSystemAdminSettingsFormFilter( &$form ) {
		$this->_application->callHelper( 'Payment_DirectoryAdminSettingsForm', array( &$form ) );
	}

	public function onEntityHtmlClassesFilter( &$classes, $entity ) {
		if ( $plan_id = $entity->getSingleFieldValue( 'payment_plan', 'plan_id' ) ) {
			$classes[] = 'drts-entity-payment-plan-' . $plan_id;
		}
	}

	public function csvGetImporterNames() {
		return array( 'payment_plan' );
	}

	public function csvGetImporter( $name ) {
		return new CSVImporter\PaymentCSVImporter( $this->_application, $name );
	}

	public function csvGetExporterNames() {
		return array( 'payment_plan' );
	}

	public function csvGetExporter( $name ) {
		return new CSVExporter\PaymentCSVExporter( $this->_application, $name );
	}

	public function onViewQueryFilter( $query, $bundle, $mode, $name, $settings, $isDefault ) {
		if ( ! empty( $settings['sort']['payment_plans'] ) ) {
			if ( ! empty( $settings['sort']['payment_plan_orders'] ) ) {
				$query->sortByCases( 'payment_plan', $this->_application->Filter( 'payment_plan_orders', $settings['sort']['payment_plan_orders'], array( $bundle, $mode, $name, $settings, $isDefault ) ), 'plan_id' );
			} else {
				$query->sortByField( 'payment_plan', 'EMPTY_LAST', 'plan_id' ); // moves NULL or 0 to last in order
			}
		}
	}

	public function onViewFeatureSettingsFormFilter( &$form, $bundle, $settings, $submitValues ) {
		$plans = $this->_application->Payment_Plans(
			$bundle->name,
			$this->_application->Filter( 'payment_base_plan_types', array( 'base' ), array( $bundle->name ) )
		);
		if ( ! $plans ) {
			return;
		}

		foreach ( array_keys( $plans ) as $plan_id ) {
			$plans[ $plan_id ] = $plans[ $plan_id ]->paymentPlanTitle();
		}
		$form['sort']['payment_plans']       = array(
			'#type'          => 'checkbox',
			'#title'         => __( 'Show items with a payment plan first', 'directories-pro' ),
			'#default_value' => ! empty( $settings['sort']['payment_plans'] ),
			'#horizontal'    => true,
			'#weight'        => 20,
		);
		$form['sort']['payment_plan_orders'] = array(
			'#type'          => 'sortablecheckboxes',
			'#options'       => $plans,
			'#default_value' => isset( $settings['sort']['payment_plan_orders'] ) ? $settings['sort']['payment_plan_orders'] : array( current( array_keys( $plans ) ) ),
			'#title'         => __( 'Payment plan sort order', 'directories-pro' ),
			'#horizontal'    => true,
			'#weight'        => 20,
			'#states'        => array(
				'visible' => array(
					'input[name="sort[payment_plans]"]' => array(
						'type'  => 'checked',
						'value' => true,
					),
				),
			),
		);
	}

	public function onClaimingClaimApproved( $claim, $claimedEntity ) {
		if ( ! $payment_component = $this->getPaymentComponent() ) {
			return;
		}

		if ( ! $order_id = $payment_component->paymentGetClaimOrderId( $claim, $claimedEntity ) ) {
			$this->_application->logWarning( 'No payment order found for claim ID: ' . $claim->getId() );
			return;
		}
		$this->_application->Payment_Features_apply( $claimedEntity, $order_id );
	}

	public function onClaimingClaimRejected( $claim, $claimedEntity ) {
		if ( ! $payment_component = $this->getPaymentComponent() ) {
			return;
		}

		if ( ! $order_id = $payment_component->paymentGetClaimOrderId( $claim, $claimedEntity ) ) {
			$this->_application->logWarning( 'No payment order found for claim ID: ' . $claim->getId() );
			return;
		}
		$payment_component->paymentRefundOrder( $order_id, __( 'Claim Rejected', 'directories-pro' ) );
	}

	public function onEntityCreateEntitySuccess( $bundle, $entity, $values, $extraArgs ) {
		if ( ! empty( $bundle->info['is_taxonomy'] ) ) {
			return;
		}

		$is_default_entity = false;
		if ( $this->_application->getPlatform()->isTranslatable( $entity->getType(), $entity->getBundleName() )
			&& ( $default_lang = $this->_application->getPlatform()->getDefaultLanguage() )
		) {
			$save = true;
			if ( ( ! $default_entity_id = $this->_application->getPlatform()->getTranslatedId( $entity->getType(), $entity->getBundleName(), $entity->getId(), $default_lang ) )
				|| ( $is_default_entity = $default_entity_id == $entity->getId() )
			) {
				// Saving translated item but no default item or saving default item

				// Get payment plan already assigned to translated items if any
				if ( $translated_entities = $this->_application->Entity_Translations( $entity ) ) {
					foreach ( $translated_entities as $translated_entity ) {
						if ( $payment_plan_data = $translated_entity->getSingleFieldValue( 'payment_plan' ) ) {
							break;
						}
					}
					if ( empty( $payment_plan_data ) ) {
						// No payment plan assigned to any translated item, so make sure no payment plan is assigned

						if ( ! $entity->getSingleFieldValue( 'payment_plan' ) ) {
							return; // already empty
						}

						// Remove payment plan
						$payment_plan_data = false;
					}
				} else {
					$save = false;
				}
			} else {
				// Saving translated item and original exists

				if ( $default_entity = $this->_application->Entity_Entity( $entity->getType(), $default_entity_id ) ) {
					// Remove payment plan if default item does not have any
					if ( ( ! $payment_plan_data = $default_entity->getSingleFieldValue( 'payment_plan' ) )
						|| empty( $payment_plan_data['plan_id'] )
					) {
						// Default item does not have a payment plan
						if ( $entity->getSingleFieldValue( 'payment_plan' ) ) {
							// Remove payment plan from translated item
							$payment_plan_data = false;
						} else {
							// already empty
							$save = false;
						}
					}
				} else {
					$save = false;
				}
			}
			if ( $save ) {
				$this->_application->Entity_Save(
					$entity,
					array( 'payment_plan' => $payment_plan_data ),
					array( 'payment_skip_plan_sync' => true ) // prevents loop
				);
			}
		} else {
			$is_default_entity = true;
		}
	}

	public function onEntityUpdateEntitySuccess( $bundle, $entity, $oldEntity, $values, $extraArgs ) {
		if ( ! empty( $bundle->info['is_taxonomy'] )
			|| ! isset( $values['payment_plan'] )
		) {
			return;
		}

		if ( ( ! $payment_plan_data = $entity->getSingleFieldValue( 'payment_plan' ) )
			|| empty( $payment_plan_data['plan_id'] )
		) {
			$payment_plan_data = false;
		}

		$is_default_entity = false;
		if ( $this->_application->getPlatform()->isTranslatable( $entity->getType(), $entity->getBundleName() )
			&& ( $default_lang = $this->_application->getPlatform()->getDefaultLanguage() )
		) {
			if ( ( $default_entity_id = $this->_application->getPlatform()->getTranslatedId( $entity->getType(), $entity->getBundleName(), $entity->getId(), $default_lang ) )
				&& $default_entity_id == $entity->getId()
			) {
				$is_default_entity = true;
				if ( $translated_entities = $this->_application->Entity_Translations( $entity ) ) {
					// Payment plan of default item modified, update payment plan of translated items
					foreach ( array_keys( $translated_entities ) as $lang ) {
						$this->_application->Entity_Save(
							$translated_entities[ $lang ],
							array( 'payment_plan' => $payment_plan_data ),
							array( 'payment_skip_plan_sync' => true ) // prevents loop
						);
					}
				}
			}
		} else {
			$is_default_entity = true;
		}
	}

	public function onClaimingIsGuestClaimableFilter( &$result, $bundle ) {
		$result = ! empty( $bundle->info['parent'] )
			&& ( $parent_bundle = $this->_application->Entity_Bundle( $bundle->info['parent'] ) )
			&& ! empty( $parent_bundle->info['payment_enable'] )
			&& ( $payment_component = $this->getPaymentComponent() )
			&& ! $payment_component->paymentIsGuestCheckoutEnabled()
			&& ( $payment_component->paymentIsGuestSignupEnabled() || $payment_component->paymentIsGuestLoginEnabled() );
	}

	public function upgrade( $current, $newVersion, ?System\Progress $progress = null ) {
		parent::upgrade( $current, $newVersion, $progress );
		if ( version_compare( $current->version, '1.3.0', '<' ) ) {
			foreach ( $this->_application->Entity_Bundles( 'directory__listing', 'directory' ) as $bundle ) {
				$info = $bundle->info;
				$info['payment_default_features']['features']['enabled']['review_reviews']['enable'] = 1;
				$bundle->setInfo( $info )->commit();
			}
		}

		return $this;
	}

	public function notificationGetEventNames() {
		$ret = array();
		foreach ( $this->_application->Entity_BundleTypes_byFeatures( array( 'payment_enable' ), 'post' ) as $bundle_type ) {
			foreach ( $this->_getNotificationEvents() as $event ) {
				$ret[] = $event . '_' . $bundle_type;
			}
		}
		return $ret;
	}

	public function notificationGetEvent( $name ) {
		$parts = explode( '_', $name );
		$class = __NAMESPACE__ . '\\NotificationEvent\\' . ucfirst( $parts[1] ) . ucfirst( $parts[2] ) . 'NotificationEvent';
		if ( ! class_exists( $class ) ) {
			return;
		}

		unset( $parts[0], $parts[1], $parts[2] );
		return new $class( $this->_application, $name, implode( '_', $parts ) );
	}

	protected function _getNotificationEvents() {
		$ret = array( 'payment_plan_expiring', 'payment_plan_expired', 'payment_entity_deactivated' );
		if ( $this->getPaymentComponent( false, true ) == 'default' ) {
			$ret = array_merge(
				$ret,
				array(
					'payment_order_created',
					'payment_order_cancelled',
					'payment_order_completed',
					'payment_order_refunded',
					'payment_subscription_created',
					'payment_subscription_cancelled',
					'payment_subscription_expired',
				)
			);
		}
		return $ret;
	}

	public function onPaymentCheckoutBtnTextFilter( &$text, $bundleName, $action, $planId = null ) {
		if ( isset( $planId )
			&& ( $plan = $this->getPaymentComponent()->paymentGetPlanById( $bundleName, $planId ) )
			&& ! $this->_application->Payment_Plan_isCheckoutRequired( $plan, $this->_application->getUser()->id )
		) {
			$text = null;
		} elseif ( $_text = $this->getPaymentComponent( true )->paymentCheckoutBtnText( $bundleName, $action, $planId ) ) {
				$text = $_text;
		}
	}

	public function systemGetToolNames() {
		return array( 'payment_reassign_plans' );
	}

	public function systemGetTool( $name ) {
		return new SystemTool\ReassignPaymentPlansSystemTool( $this->_application, $name );
	}

	public function onEntitySaveEntityFields( $bundle, $entity, $isNew, &$fieldValues, &$updateProperties ) {
		if ( ! empty( $fieldValues['payment_plan'][0]['plan_id'] ) ) {
			$fieldValues['entity_level'] = array( false );
		}
	}

	public function onEntityLevelFilter( &$level, $entity ) {
		if ( ( $plan = $this->_application->Payment_Plan( $entity ) )
			&& ( $_level = $plan->paymentPlanEntityLevel() )
		) {
			$level = $_level;
		}
	}

	public function onEntityLevelFeaturesFilter( &$features, $entity ) {
		if ( ! $plan = $this->_application->Payment_Plan( $entity ) ) {
			return;
		}

		$features       = (array) $plan->paymentPlanFeatures();
		$addon_features = $entity->getSingleFieldValue( 'payment_plan', 'addon_features' );
		// Apply addon features
		foreach ( array_keys( $features ) as $feature_name ) {
			if ( ! $feature = $this->_application->Payment_Features_impl( $feature_name, true ) ) {
				continue;
			}

			if ( isset( $addon_features[ $feature_name ] ) ) {
				$features[ $feature_name ] = $feature->paymentAddonFeatureExtraApply( $features[ $feature_name ], $addon_features[ $feature_name ] );
			}
		}

		// Replace payment feature names with entity feature names
		$replace_names = array(
			'payment_fields'         => 'entity_fields',
			'payment_taxonomy_terms' => 'entity_taxonomy_terms',
		);
		foreach ( array_keys( $features ) as $feature_name ) {
			if ( isset( $replace_names[ $feature_name ] ) ) {
				$features[ $replace_names[ $feature_name ] ] = $features[ $feature_name ];
				unset( $features[ $feature_name ] );
			}
		}
	}

	public function onWordPressContentPostTypeFlatPathExcludeSlugsFilter( &$slugs, $bundle ) {
		if ( ! empty( $bundle->info['payment_enable'] ) ) {
			$slugs[] = 'pricing';
		}
	}

	public function onCoreResponseSendViewFrontendsubmit( Context $context, Response $response ) {
		if ( in_array( $context->getRoute()->getControllerName(), array( 'AddEntity' ) ) ) {
			Response::sendNoCacheHeaders();
		}
	}
}
