<?php if ( ! defined( 'ABSPATH' ) ) { die( 'You are not allowed to call this page directly.' ); } /** * @since 5.3 */ class FrmProApplicationTaxonomyController { /** * @var FrmProApplicationRelationHelper $helper */ private static $helper; /** * @return void */ public static function init() { self::register_application_taxonomy(); } /** * @return bool */ public static function maybe_include_embed_script() { if ( self::on_application_edit_page() ) { add_filter( 'frm_api_include_embed_form_script', '__return_true' ); } } /** * Check if active page is term.php?taxonomy=frm_application as Embed buttons are included in this table. * * @return bool */ private static function on_application_edit_page() { global $pagenow; return 'term.php' === $pagenow && 'frm_application' === FrmAppHelper::simple_get( 'taxonomy' ); } /** * @return void */ public static function admin_init() { $plugin_url = FrmProAppHelper::plugin_url(); $version = FrmProDb::$plug_version; $js_dependencies = array( 'formidable_dom', 'popper', 'bootstrap_tooltip', 'formidable_embed' ); FrmProApplicationsController::register_common_js(); FrmProApplicationsController::register_common_css(); wp_register_script( 'formidable_edit_application_term', $plugin_url . '/js/admin/taxonomy/frm_application.js', array_merge( $js_dependencies, array( 'frm_applications_common' ) ), $version, true ); wp_register_style( 'formidable_edit_application_term', $plugin_url . '/css/admin/taxonomy/frm_application.css', array( 'frm_applications_common' ), $version ); wp_register_script( 'formidable_custom_applications_index', $plugin_url . '/js/admin/taxonomy/frm_application_list.js', $js_dependencies, $version, true ); wp_register_style( 'formidable_custom_applications_index', $plugin_url . '/css/admin/taxonomy/frm_application_list.css', array(), $version ); if ( self::should_set_applications_in_submenu() ) { self::set_applications_active_in_submenu(); } self::maybe_enqueue_assets(); } /** * Load the css in the head to prevent icon flash. * * @since 5.3.1 */ private static function maybe_enqueue_assets() { if ( ! self::on_application_edit_page() ) { return; } wp_enqueue_style( 'formidable-admin' ); wp_enqueue_style( 'formidable_edit_application_term' ); global $wp_taxonomies; if ( isset( $wp_taxonomies['frm_application'] ) ) { $wp_taxonomies['frm_application']->show_ui = true; } } /** * Add custom applications data to wp_ajax_frm_get_applications_data AJAX hook. * * @param array $data * @return array */ public static function applications_data( $data ) { $view = FrmAppHelper::get_param( 'view', '', 'get', 'sanitize_text_field' ); if ( 'templates' === $view ) { // Avoid adding applications for 'templates' view option. return $data; } $limit = 'applications' === $view ? -1 : 8; $data['applications'] = self::get_custom_applications( $limit + 1 ); if ( -1 !== $limit && count( $data['applications'] ) > $limit ) { $data['moreApplications'] = 1; array_pop( $data['applications'] ); } return $data; } /** * @param int $limit 0 for no limit. * @return array */ private static function get_custom_applications( $limit = 0 ) { $terms = get_terms( array( 'taxonomy' => 'frm_application', 'hide_empty' => false, ) ); $applications = self::sort_custom_applications( $terms, $limit ); unset( $terms ); if ( $limit ) { $applications = array_slice( $applications, 0, $limit ); } return array_reduce( $applications, array( __CLASS__, 'custom_application_reducer' ), array() ); } /** * @param array $applications * @param FrmProApplication $application * @return array */ private static function custom_application_reducer( $applications, $application ) { $applications[] = $application->as_js_object(); return $applications; } /** * Sort applications, and wrap in FrmProApplication model. * * @param array<WP_Term> $terms * @return array<FrmProApplication> */ private static function sort_custom_applications( $terms, $limit = 0 ) { if ( 0 === $limit ) { $sort_type = FrmAppHelper::get_param( 'sort', '', 'get', 'sanitize_text_field' ); if ( $sort_type && 'alphabet' === $sort_type ) { usort( $terms, function( $a, $b ) { return strcmp( $a->name, $b->name ); } ); return array_map( function( $term ) { // Alphabet sorting is only used for dropdowns. // The timestamp is not passed in this case as it is not used in dropdowns. return new FrmProApplication( $term, '' ); }, $terms ); } } $args = array( 'order_by' => 'meta_value DESC', ); if ( $limit ) { $args['limit'] = $limit; } $metas = FrmDb::get_results( 'termmeta', array( 'meta_key' => '_frm_updated_at', ), 'term_id, meta_value', $args ); $terms_by_id = array_reduce( $terms, function( $total, $term ) { $total[ $term->term_id ] = $term; return $total; }, array() ); $applications = array(); foreach ( $metas as $meta ) { if ( ! array_key_exists( $meta->term_id, $terms_by_id ) ) { continue; } $applications[] = new FrmProApplication( $terms_by_id[ $meta->term_id ], $meta->meta_value ); } return $applications; } /** * Returns whether or not the active page is related to Applications but not the index page which is highlighted by default. * If this is true, the Applications submenu item will appear active for the page. * * @return bool */ private static function should_set_applications_in_submenu() { global $pagenow; return in_array( $pagenow, array( 'term.php', 'edit-tags.php' ), true ) && 'frm_application' === FrmAppHelper::simple_get( 'taxonomy' ); } /** * Adds filters that set Formidable to the active page with Applications as the active submenu page. * * @return void */ private static function set_applications_active_in_submenu() { add_filter( 'parent_file', function() { return 'formidable'; } ); add_filter( 'submenu_file', function() { return 'formidable-applications'; } ); } /** * @return void */ private static function register_application_taxonomy() { $capability = FrmProApplicationsHelper::get_custom_applications_capability(); if ( ! current_user_can( $capability ) && current_user_can( 'administrator' ) ) { $capability = 'administrator'; } register_taxonomy( 'frm_application', array( 'page', 'frm_display', 'frm_form' ), array( 'hierarchical' => false, 'labels' => array( 'name' => __( 'Applications' ), 'singular_name' => __( 'Application' ), 'search_items' => __( 'Search Applications' ), 'popular_items' => null, 'all_items' => __( 'All Applications' ), 'edit_item' => __( 'Edit Application' ), 'update_item' => __( 'Update Application' ), 'add_new_item' => __( 'Add New Application' ), 'new_item_name' => __( 'New Application Name' ), 'separate_items_with_commas' => null, 'add_or_remove_items' => null, 'choose_from_most_used' => null, 'back_to_items' => __( '&larr; Go to Applications' ), ), 'capabilities' => array( 'manage_terms' => $capability, 'edit_terms' => $capability, 'delete_terms' => $capability, 'assign_terms' => $capability, ), 'query_var' => false, 'rewrite' => false, 'public' => false, 'show_ui' => false, '_builtin' => true, ) ); } /** * @return void */ public static function maybe_render_custom_applications_list() { global $pagenow; if ( 'edit-tags.php' !== $pagenow || 'frm_application' !== FrmAppHelper::simple_get( 'taxonomy' ) ) { return; } FrmProApplicationsHelper::custom_application_permission_check(); wp_enqueue_style( 'formidable-admin' ); wp_enqueue_script( 'formidable_custom_applications_index' ); wp_enqueue_style( 'formidable_custom_applications_index' ); FrmProApplicationsController::load_new_application_modal_assets(); require_once ABSPATH . 'wp-admin/admin-header.php'; FrmProAppHelper::include_svg(); // SVG needs to be included after the admin header. require_once FrmProAppHelper::plugin_path() . '/classes/views/applications/taxonomy/list.php'; require_once ABSPATH . 'wp-admin/admin-footer.php'; die(); } /** * Overwrite behaviour of URL when editing Term at /wp-admin/term.php?taxonomy=frm_application&tag_ID=XX for frm_application Taxonomy. * * @param WP_Term $tag * @return void */ public static function pre_edit_form( $tag ) { if ( ! class_exists( 'FrmApplicationsController' ) ) { return; } FrmProApplicationsHelper::custom_application_permission_check(); FrmAppHelper::include_svg(); FrmProAppHelper::include_svg(); wp_enqueue_style( 'formidable-admin' ); $icons = array( 'form' => 'frm_form_icon', 'repeater form' => 'frm_file_text_icon', 'embedded form' => 'frm_file_text_icon', 'page' => 'frm_page_icon', ); $icons = apply_filters( 'frm_application_term_icons', $icons ); $js_vars = array( 'id' => $tag->term_id, 'name' => $tag->name, 'icons' => $icons, 'exportNonce' => wp_create_nonce( 'export-xml-nonce' ), ); wp_localize_script( 'formidable_edit_application_term', 'frmApplicationTerm', $js_vars ); wp_enqueue_script( 'formidable_edit_application_term' ); wp_enqueue_style( 'formidable_edit_application_term' ); require_once FrmProAppHelper::plugin_path() . '/classes/views/applications/taxonomy/edit.php'; require_once ABSPATH . 'wp-admin/admin-footer.php'; die(); } /** * @return void */ public static function get_data_for_application() { FrmProApplicationsHelper::custom_application_permission_check(); $id = FrmAppHelper::simple_get( 'id', 'absint' ); if ( ! $id ) { wp_die(); } $rows = self::get_table_data_for_application( $id ); $data = compact( 'rows' ); wp_send_json_success( $data ); } /** * @param int $id * @return array */ private static function get_table_data_for_application( $id ) { $children = self::get_children_for_application( $id ); return array_reduce( $children, array( __CLASS__, 'reduce_item' ) ); } /** * @param array $total * @param WP_Post|stdClass $current * @return array */ private static function reduce_item( $total, $current ) { if ( $current instanceof WP_Post ) { $name = $current->post_title; $post_type_object = get_post_type_object( $current->post_type ); $type_label = $post_type_object->labels->singular_name; $type = $current->post_type; $descriptive_type = $type; if ( 'frm_display' === $current->post_type ) { $type = 'view'; $descriptive_type = $type; if ( is_callable( 'FrmViewsDisplaysHelper::get_view_type' ) && is_callable( 'FrmViewsDisplaysHelper::get_view_type_label' ) ) { $view_type = FrmViewsDisplaysHelper::get_view_type( $current ); $type_label = FrmViewsDisplaysHelper::get_view_type_label( $view_type ) . ' ' . $type_label; $descriptive_type = $view_type . ' ' . $descriptive_type; } } $object_id = $current->ID; $object_key = $current->post_name; $is_draft = 'draft' === $current->post_status; } elseif ( $current instanceof stdClass ) { $name = $current->name; $type = 'form'; if ( ! empty( $current->parent_form_id ) ) { $name .= ' ' . __( '(child)', 'formidable-pro' ); $type_label = __( 'Repeater Form', 'formidable-pro' ); $descriptive_type = 'repeater ' . $type; } elseif ( self::$helper->form_is_an_embed_field_form( $current->id ) ) { $type_label = __( 'Embedded Form', 'formidable-pro' ); $descriptive_type = 'embedded ' . $type; } else { $type_label = __( 'Form', 'formidable-pro' ); $descriptive_type = $type; } $object_id = (int) $current->id; $object_key = $current->form_key; $is_draft = false; } else { return $total; } $edit_url = self::get_edit_url( $type, $object_id ); $total[] = array( 'itemId' => $object_id, 'itemKey' => $object_key, 'name' => $name, 'type' => $type, 'descriptiveType' => $descriptive_type, 'typeLabel' => $type_label, 'editUrl' => $edit_url, 'parentOf' => self::get_parent_of_data( $current ), 'embeddedIn' => self::get_embedded_in_data( $current ), 'isDraft' => $is_draft, ); return $total; } /** * @param int $id * @return array<WP_Post>|array<stdClass> */ private static function get_children_for_application( $id ) { $forms = FrmProApplication::get_forms_for_application( $id ); $posts = FrmProApplication::get_posts_for_application( $id ); self::maybe_sync_counts( $id, count( $forms ), $posts ); self::$helper = new FrmProApplicationRelationHelper( $forms, $posts ); return array_merge( $forms, $posts ); } /** * Maybe update form/view/page counts on application. * They may go out of sync if a form or post gets moved to trash. * * @param int $id Application id. * @param int $form_count * @param array<WP_Post> $posts * @return void */ private static function maybe_sync_counts( $id, $form_count, $posts ) { if ( $form_count !== self::get_count( $id, 'form' ) ) { FrmProApplication::update_form_count( $id ); } $post_counts = array( 'frm_display' => 0, 'page' => 0, ); foreach ( $posts as $post ) { if ( isset( $post_counts[ $post->post_type ] ) ) { ++$post_counts[ $post->post_type ]; } } if ( $post_counts['frm_display'] !== self::get_count( $id, 'view' ) ) { FrmProApplication::update_view_count( $id ); } if ( $post_counts['page'] !== self::get_count( $id, 'page' ) ) { FrmProApplication::update_page_count( $id ); } } /** * @param int $id Application id. * @param string $type supports 'view', 'page', and 'form'. * @return int */ private static function get_count( $id, $type ) { $count = get_term_meta( $id, '_frm_' . $type . '_count', true ); return is_numeric( $count ) ? absint( $count ) : 0; } /** * @param WP_Post|stdClass $item * @return array */ private static function get_parent_of_data( $item ) { return self::$helper->get_parent_of_data( $item ); } /** * @param WP_Post|stdClass $item * @return array */ private static function get_embedded_in_data( $item ) { return self::$helper->get_embedded_in_data( $item ); } /** * Create empty application via AJAX action. * * @return void */ public static function create_application() { FrmProApplicationsHelper::custom_application_permission_check(); check_ajax_referer( 'frm_ajax', 'nonce' ); $name = FrmAppHelper::get_post_param( 'application_name', '', 'sanitize_text_field' ); $term = FrmProApplication::create( $name ); if ( ! is_array( $term ) ) { wp_send_json_error( __( 'Unable to create application', 'formidable-pro' ) ); die(); } $term_id = $term['term_id']; $redirect = FrmProApplicationsHelper::get_edit_url( $term_id ); $data = compact( 'term_id', 'redirect' ); wp_send_json_success( $data ); die(); } /** * Delete application via AJAX action. * * @return void */ public static function delete_application() { FrmProApplicationsHelper::custom_application_permission_check(); check_ajax_referer( 'frm_ajax', 'nonce' ); $term_id = FrmAppHelper::get_post_param( 'term_id', '', 'absint' ); if ( ! $term_id ) { die(); } $term = get_term( $term_id, 'frm_application' ); if ( ! ( $term instanceof WP_Term ) ) { wp_send_json_error( 'Application does not exist' ); die(); } wp_delete_term( $term_id, 'frm_application' ); $data = array(); wp_send_json_success( $data ); die(); } /** * Add item to application via AJAX action. * * @return void */ public static function add_to_application() { FrmProApplicationsHelper::custom_application_permission_check(); check_ajax_referer( 'frm_ajax', 'nonce' ); $term_id = FrmAppHelper::get_post_param( 'term_id', 0, 'absint' ); if ( ! $term_id ) { die(); } $type = FrmAppHelper::get_post_param( 'type', '', 'sanitize_text_field' ); if ( ! $type || ! in_array( $type, array( 'form', 'page', 'view' ), true ) ) { die(); } $new = FrmAppHelper::get_post_param( 'new', 0, 'absint' ); if ( 1 === $new ) { $redirect = self::get_new_item_redirect( $type, $term_id ); if ( '' === $redirect ) { wp_send_json_error( 'Page name may be missing, or type is incorrect' ); } } else { $item_id = FrmAppHelper::get_post_param( 'item_id', 0, 'absint' ); if ( ! $item_id ) { die(); } self::add_existing_item_to_application( $type, $term_id, $item_id ); $data = array(); wp_send_json_success( $data ); die(); } if ( empty( $redirect ) ) { die(); } $data = compact( 'redirect' ); wp_send_json_success( $data ); die(); } /** * Maybe create an item (form, page, or view), or get the URL to the appropriate redirect. * * @param string $type supports 'form', 'page' and 'view'. * @param int $term_id * @return string redirect url to edit item. */ private static function get_new_item_redirect( $type, $term_id ) { switch ( $type ) { case 'form': return admin_url( 'admin.php?page=formidable&triggerNewFormModal=1&applicationId=' . $term_id ); case 'view': return admin_url( 'edit.php?post_type=frm_display&triggerNewViewModal=1&applicationId=' . $term_id ); case 'page': $name = FrmAppHelper::get_post_param( 'name', '', 'sanitize_text_field' ); if ( ! $name ) { return ''; } $post_id = wp_insert_post( array( 'post_type' => 'page', 'post_title' => $name, 'post_status' => 'private', ) ); FrmProApplication::add_post_to_application( $term_id, $post_id, $type ); $object_id = $post_id; return self::get_edit_url( $type, $object_id ); default: return ''; } } /** * @param string $type supports 'form', 'page' and 'view'. * @param int $term_id * @param int $item_id * @return void */ private static function add_existing_item_to_application( $type, $term_id, $item_id ) { switch ( $type ) { case 'form': self::add_missing_view_ids_to_application( $term_id, $item_id ); FrmProApplication::add_form_to_application( $term_id, $item_id ); break; case 'view': $form_id = get_post_meta( $item_id, 'frm_form_id', true ); if ( $form_id && is_numeric( $form_id ) ) { self::add_missing_form_id_to_application( $term_id, (int) $form_id ); } // Fall through to page case, we want to add post to application. case 'page': FrmProApplication::add_post_to_application( $term_id, $item_id, $type ); break; } } /** * @param int $term_id * @param int $form_id * @return void */ private static function add_missing_view_ids_to_application( $term_id, $form_id ) { global $wpdb; $where = array( 'meta_key' => 'frm_form_id', 'meta_value' => $form_id, ); $view_ids = FrmDb::get_col( $wpdb->postmeta, $where, 'post_id' ); $new_view_ids = array_diff( array_map( 'intval', $view_ids ), FrmProApplication::get_posts_for_application( $term_id, array( 'frm_display' ), array( 'fields' => 'ids' ) ) ); foreach ( $new_view_ids as $view_id ) { FrmProApplication::add_post_to_application( $term_id, $view_id, 'view' ); } } /** * @param int $term_id * @param int $form_id * @return void */ private static function add_missing_form_id_to_application( $term_id, $form_id ) { global $wpdb; $where = array( 'meta_key' => '_frm_form_id', 'meta_value' => $form_id, 'term_id' => $term_id, ); if ( FrmDb::get_var( $wpdb->termmeta, $where, 'term_id' ) ) { return; } FrmProApplication::add_form_to_application( $term_id, $form_id ); } /** * @param string $type * @param int $object_id * @return string */ private static function get_edit_url( $type, $object_id ) { switch ( $type ) { case 'form': return admin_url( 'admin.php?page=formidable&frm_action=edit&id=' . $object_id ); default: return admin_url( 'post.php?post=' . $object_id . '&action=edit' ); } } /** * Remove item from application via AJAX action. * * @return void */ public static function remove_from_application() { FrmProApplicationsHelper::custom_application_permission_check(); check_ajax_referer( 'frm_ajax', 'nonce' ); $term_id = FrmAppHelper::get_post_param( 'term_id', 0, 'absint' ); if ( ! $term_id ) { die(); } $type = FrmAppHelper::get_post_param( 'type', '', 'sanitize_text_field' ); if ( ! $type || ! in_array( $type, array( 'form', 'page', 'view' ), true ) ) { die(); } $item_id = FrmAppHelper::get_post_param( 'item_id', 0, 'absint' ); if ( ! $item_id ) { die(); } switch ( $type ) { case 'form': FrmProApplication::remove_form_from_application( $term_id, $item_id ); break; case 'view': case 'page': FrmProApplication::remove_post_from_application( $term_id, $item_id, $type ); break; } $data = array(); wp_send_json_success( $data ); die(); } /** * Save application settings via AJAX action (Rename action on My Application page). * * @return void */ public static function save_application_settings() { FrmProApplicationsHelper::custom_application_permission_check(); check_ajax_referer( 'frm_ajax', 'nonce' ); $application_id = FrmAppHelper::get_post_param( 'term_id', 0, 'absint' ); if ( ! $application_id ) { die(); } $name = FrmAppHelper::get_post_param( 'name', '', 'sanitize_text_field' ); if ( ! $name ) { die(); } $args = compact( 'name' ); wp_update_term( $application_id, 'frm_application', $args ); FrmProApplication::update_timestamp( $application_id ); $data = array(); wp_send_json_success( $data ); die(); } /** * Validate application name via AJAX action. * * @return void */ public static function validate_application_name() { FrmProApplicationsHelper::custom_application_permission_check(); check_ajax_referer( 'frm_ajax', 'nonce' ); $name = FrmAppHelper::get_post_param( 'name', '', 'sanitize_text_field' ); if ( ! $name ) { die(); } // Term id may be 0 if this is a new application (via the new Application Modal, or when installing a template). $application_id = FrmAppHelper::get_post_param( 'application_id', 0, 'absint' ); $valid = ! FrmProApplication::name_is_taken( $name, $application_id ); $data = array( 'valid' => $valid ); if ( $valid ) { wp_send_json_success( $data ); } else { wp_send_json_error( $data ); } die(); } /** * Sync via AJAX action. * * @return void */ public static function sync() { FrmProApplicationsHelper::custom_application_permission_check(); check_ajax_referer( 'frm_ajax', 'nonce' ); $application_id = FrmAppHelper::get_post_param( 'application_id', 0, 'absint' ); if ( ! $application_id ) { wp_send_json_error( 'Missing required application id param' ); die(); } $summary = FrmProApplicationsHelper::sync( $application_id ); $data = compact( 'summary' ); wp_send_json_success( $data ); die(); } /** * Search via AJAX action. Used for adding forms with applications. * * @return void */ public static function search() { FrmProApplicationsHelper::custom_application_permission_check(); check_ajax_referer( 'frm_ajax', 'nonce' ); global $wpdb; $name = FrmAppHelper::get_param( 'term', '', 'get', 'sanitize_text_field' ); $terms = get_terms( array( 'taxonomy' => 'frm_application', 'hide_empty' => false, 'orderby' => 'name', 'number' => 25, 'name__like' => $name, ) ); $results = array(); foreach ( $terms as $term ) { $results[] = array( 'value' => $term->term_id, 'label' => $term->name, ); } wp_send_json( $results ); } /** * Add hook before creating a page with form or view shortcode. * * @return void */ public static function before_create_page_with_shortcode() { add_action( 'wp_insert_post', array( __CLASS__, 'after_create_page_with_shortcode' ), 10, 3 ); } /** * Add application relation after page is created with shortcode. * * @param int $post_ID * @param WP_Post $post * @param bool $update * @return void */ public static function after_create_page_with_shortcode( $post_ID, $post, $update ) { if ( $update || 'page' !== $post->post_type ) { return; } $application_id = FrmAppHelper::get_post_param( 'application_id', 0, 'absint' ); if ( ! $application_id ) { return; } FrmProApplication::add_post_to_application( $application_id, $post_ID, 'page' ); } }