namespace WPForms\Admin\Addons;
const ULTIMATE = 'ultimate';
* WPForms addons text domains.
private $addons_text_domains = [];
private $addons_titles = [];
* Determine if the class is allowed to load.
public function allow_load() {
$has_permissions = wpforms_current_user_can( [ 'create_forms', 'edit_forms' ] );
$allowed_pages = in_array( $pagenow ?? '', [ 'plugins.php', 'update-core.php', 'plugin-install.php' ], true );
$allowed_ajax = $pagenow === 'admin-ajax.php' && isset( $_POST['action'] ) && $_POST['action'] === 'update-plugin'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$allowed_requests = $allowed_pages || $allowed_ajax || wpforms_is_admin_ajax() || wpforms_is_admin_page() || wpforms_is_admin_page( 'builder' );
return $has_permissions && $allowed_requests;
if ( ! $this->allow_load() ) {
$this->cache = wpforms()->obj( 'addons_cache' );
// Force update addons cache if we are on the update-core.php page.
// This is necessary to update addons data while checking for all available updates.
if ( $pagenow === 'update-core.php' ) {
$this->cache->update( true );
$this->addons = $this->cache->get();
$this->populate_addons_data();
protected function hooks() {
* Fire before admin addons init.
do_action( 'wpforms_admin_addons_init' );
// Filter Gettext only on Plugin list and Updates pages.
if ( $pagenow === 'update-core.php' || $pagenow === 'plugins.php' ) {
add_action( 'gettext', [ $this, 'filter_gettext' ], 10, 3 );
* Get all addons data as array.
* @param bool $force_cache_update Determine if we need to update cache. Default is `false`.
public function get_all( bool $force_cache_update = false ) {
if ( ! $this->allow_load() ) {
if ( $force_cache_update ) {
$this->cache->update( true );
$this->addons = $this->cache->get();
// WPForms 1.8.7 core includes Custom Captcha.
// The Custom Captcha addon will only work on WPForms 1.8.6 and earlier versions.
unset( $this->addons['wpforms-captcha'] );
return $this->get_sorted_addons();
* Get sorted addons data.
* Recommended addons will be displayed first,
* then new addons, then featured addons,
* and then all other addons.
private function get_sorted_addons(): array {
if ( empty( $this->addons ) ) {
$recommended = array_filter(
static function ( $addon ) {
return ! empty( $addon['recommended'] );
static function ( $addon ) {
return ! empty( $addon['new'] );
$featured = array_filter(
static function ( $addon ) {
return ! empty( $addon['featured'] );
return array_merge( $recommended, $new, $featured, $this->addons );
* Get filtered addons data.
* ->get_filtered( $this->addons, [ 'category' => 'payments' ] ) - addons for the payments panel.
* ->get_filtered( $this->addons, [ 'license' => 'elite' ] ) - addons available for 'elite' license.
* @param array $addons Raw addons data.
* @param array $args Arguments array.
* @return array Addons data filtered according to given arguments.
private function get_filtered( array $addons, array $args ): array {
$args = array_map( 'strtolower', $args );
foreach ( $addons as $addon ) {
foreach ( $args as $arg_key => $arg_value ) {
$addon_value = wpforms_array_get_by_path( $addon, $arg_key, '' );
is_array( $addon_value ) &&
// We cannot use preg_quote here, as $arg_value could contain regex like 'crm|email-marketing|integration'.
preg_grep( '/^' . $arg_value . '$/', $addon_value )
$filtered_addons[] = $addon;
* Get available addons data by category.
* @param string $category Addon category.
public function get_by_category( string $category ) {
return $this->get_by_path( 'category', $category );
* Get available addons data by path.
* @param string $path Path in addons multidimensional array.
* May be 'category' or 'form_builder.category' or 'settings_integrations.category', etc.
* @param string $value Addons multidimensional array value we are looking for in the path.
public function get_by_path( string $path, $value ): array {
return $this->get_filtered( $this->get_available(), [ $path => $value ] );
* Get available addons data by license.
* @param string $license Addon license.
* @noinspection PhpUnused
public function get_by_license( string $license ) {
return $this->get_filtered( $this->get_available(), [ 'license' => $license ] );
* Get available addons data by slugs.
* @param array|mixed $slugs Addon slugs.
public function get_by_slugs( $slugs ) {
if ( empty( $slugs ) || ! is_array( $slugs ) ) {
foreach ( $slugs as $slug ) {
$addon = $this->get_addon( $slug );
if ( ! empty( $addon ) ) {
$result_addons[] = $addon;
* Get available addon data by slug.
* @param string|bool $slug Addon slug can be both "wpforms-drip" and "drip".
* @return array Single addon data. Empty array if addon is not found.
public function get_addon( $slug ) {
$slug = 'wpforms-' . str_replace( 'wpforms-', '', sanitize_key( $slug ) );
$addon = $this->get_available()[ $slug ] ?? [];
// In case if addon is "not available" let's try to get and prepare addon data from all addons.
$addon = ! empty( $this->addons[ $slug ] ) ? $this->prepare_addon_data( $this->addons[ $slug ] ) : [];
* Check if addon is active.
* @param string $slug Addon slug.
public function is_active( string $slug ): bool {
$addon = $this->get_addon( $slug );
return isset( $addon['status'] ) && $addon['status'] === 'active';
* Get license level of the addon.
* @param array|string $addon Addon data array OR addon slug.
* @return string License level: pro | elite.
private function get_license_level( $addon ) {
$levels = [ self::BASIC, self::PLUS, self::PRO, self::ELITE, self::AGENCY, self::ULTIMATE ];
$addon_license = $this->get_addon_license( $addon );
foreach ( $levels as $level ) {
if ( in_array( $level, $addon_license, true ) ) {
if ( empty( $license ) ) {
return in_array( $license, [ self::BASIC, self::PLUS, self::PRO ], true ) ? self::PRO : self::ELITE;
* @param array|string $addon Addon data array OR addon slug.
private function get_addon_license( $addon ) {
$addon = is_string( $addon ) ? $this->get_addon( $addon ) : $addon;
return $this->default_data( $addon, 'license', [] );
* Determine if a user's license level has access.
* @param array|string $addon Addon data array OR addon slug.
protected function has_access( $addon ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
* Return array of addons available to display. All data is prepared and normalized.
* "Available to display" means that addon needs to be displayed as an education item (addon is not installed or not activated).
public function get_available() {
static $available_addons = [];
if ( $available_addons ) {
return $available_addons;
if ( empty( $this->addons ) || ! is_array( $this->addons ) ) {
$available_addons = array_map( [ $this, 'prepare_addon_data' ], $this->addons );
$available_addons = array_filter(
static function ( $addon ) {
return isset( $addon['status'], $addon['plugin_allow'] ) && ( $addon['status'] !== 'active' || ! $addon['plugin_allow'] );
return $available_addons;
* @param array|mixed $addon Addon data.
* @return array Extended addon data.
protected function prepare_addon_data( $addon ) {
$addon['title'] = $this->default_data( $addon, 'title', '' );
$addon['slug'] = $this->default_data( $addon, 'slug', '' );