ermine which locations to remove and which to add. $form_ids_to_remove = array_diff( $form_ids_before, $form_ids_after ); $form_ids_to_add = array_diff( $form_ids_after, $form_ids_before ); // Loop through each form ID to remove the locations' meta. foreach ( $form_ids_to_remove as $form_id ) { update_post_meta( $form_id, self::LOCATIONS_META, $this->get_locations_without_current_post( $form_id, $post_after->ID ) ); } // Determine the titles and slugs. $old_title = $post_before->post_title ?? ''; $old_slug = $post_before->post_name ?? ''; $new_title = $post_after->post_title; $new_slug = $post_after->post_name; // If the title and slug are the same and there are no form IDs to add, bail. if ( empty( $form_ids_to_add ) && $old_title === $new_title && $old_slug === $new_slug ) { return; } // Merge the form IDs and remove duplicates. $form_ids = array_unique( array_merge( $form_ids_to_add, $form_ids_after ) ); $this->save_location_meta( $form_ids, $post_after->ID, $post_after ); } /** * Save the location meta. * * @since 1.8.2.3 * * @param array $form_ids Form IDs. * @param int $post_id Post ID. * @param WP_Post $post_after Post after the update. */ private function save_location_meta( $form_ids, $post_id, $post_after ) { // Build the URL. $url = get_permalink( $post_id ); $url = ( $url === false || is_wp_error( $url ) ) ? '' : $url; $url = str_replace( $this->home_url, '', $url ); // Loop through each Form ID and save the location meta. foreach ( $form_ids as $form_id ) { $locations = $this->get_locations_without_current_post( $form_id, $post_id ); $locations[] = [ 'type' => $post_after->post_type, 'title' => $post_after->post_title, 'form_id' => $form_id, 'id' => $post_id, 'status' => $post_after->post_status, 'url' => $url, ]; update_post_meta( $form_id, self::LOCATIONS_META, $locations ); } } /** * Get post types for search in. * * @since 1.7.4 * * @return string[] */ public function get_post_types() { $args = [ 'public' => true, 'publicly_queryable' => true, ]; $post_types = get_post_types( $args, 'names', 'or' ); unset( $post_types['attachment'] ); $post_types[] = self::WP_TEMPLATE; $post_types[] = self::WP_TEMPLATE_PART; return $post_types; } /** * Get post statuses for search in. * * @since 1.7.4 * * @return string[] */ public function get_post_statuses() { return [ 'publish', 'pending', 'draft', 'future', 'private' ]; } /** * Get form ids from the content. * * @since 1.7.4 * * @param string $content Content. * * @return int[] */ public function get_form_ids( $content ) { $form_ids = []; if ( preg_match_all( /** * Extract id from conventional wpforms shortcode or wpforms block. * Examples: * [wpforms id="32" title="true" description="true"] * * In both, we should find 32. */ '#\[\s*wpforms.+id\s*=\s*"(\d+?)".*]|#', $content, $matches ) ) { array_shift( $matches ); $form_ids = array_map( 'intval', array_unique( array_filter( array_merge( ...$matches ) ) ) ); } return $form_ids; } /** * Get form locations without a current post. * * @since 1.7.4 * * @param int $form_id Form id. * @param int $post_id Post id. * * @return array */ private function get_locations_without_current_post( $form_id, $post_id ) { $locations = get_post_meta( $form_id, self::LOCATIONS_META, true ); if ( ! is_array( $locations ) ) { $locations = []; } return array_filter( $locations, static function ( $location ) use ( $post_id ) { return $location['id'] !== $post_id; } ); } /** * Determine whether a post is visible. * * @since 1.7.4 * * @param array $location Post location. * * @return bool */ private function is_post_visible( $location ) { $edit_cap = 'edit_post'; $read_cap = 'read_post'; $post_id = $location['id']; if ( ! get_post_type_object( $location['type'] ) ) { // Post type is not registered. return false; } $post_status_obj = get_post_status_object( $location['status'] ); if ( ! $post_status_obj ) { // Post status is not registered, assume it's not public. return current_user_can( $edit_cap, $post_id ); } if ( $post_status_obj->public ) { return true; } if ( ! is_user_logged_in() ) { // User must be logged in to view unpublished posts. return false; } if ( $post_status_obj->protected ) { // User must have edit permissions on the draft to preview. return current_user_can( $edit_cap, $post_id ); } if ( $post_status_obj->private ) { return current_user_can( $read_cap, $post_id ); } return false; } /** * Build a standalone location. * * @since 1.8.7 * * @param int $form_id The form ID. * @param array $form_data Form data. * @param string $status Form status. * * @return array Location. */ public function build_standalone_location( int $form_id, array $form_data, string $status = 'publish' ): array { if ( empty( $form_id ) || empty( $form_data ) ) { return []; } // Form templates should not have any locations. if ( get_post_type( $form_id ) === 'wpforms-template' ) { return []; } foreach ( self::STANDALONE_LOCATION_TYPES as $location_type ) { if ( empty( $form_data['settings'][ "{$location_type}_enable" ] ) ) { continue; } return $this->build_standalone_location_type( $location_type, $form_id, $form_data, $status ); } return []; } /** * Build a standalone location. * * @since 1.8.8 * * @param string $location_type Standalone location type. * @param int $form_id The form ID. * @param array $form_data Form data. * @param string $status Form status. * * @return array Location. */ private function build_standalone_location_type( string $location_type, int $form_id, array $form_data, string $status ): array { $title_key = "{$location_type}_title"; $slug_key = "{$location_type}_page_slug"; $title = $form_data['settings'][ $title_key ] ?? ''; $slug = $form_data['settings'][ $slug_key ] ?? ''; // Return the location array. return [ 'type' => $location_type, 'title' => $title, 'form_id' => (int) $form_data['id'], 'id' => $form_id, 'status' => $status, 'url' => '/' . $slug . '/', ]; } /** * Add standalone form locations to post meta. * * Post meta is used to store all forms' locations, * which is displayed on the WPForms Overview page. * * @since 1.8.7 * * @param int $form_id Form ID. * @param array $data Form data. */ public function add_standalone_location_to_locations_meta( int $form_id, array $data ) { // Build standalone location. $location = $this->build_standalone_location( $form_id, $data ); // No location? Bail. if ( empty( $location ) ) { return; } // Setup data. $new_location[] = $location; $post_meta = get_post_meta( $form_id, self::LOCATIONS_META, true ); // If there is post meta, merge it with the new location. if ( ! empty( $post_meta ) ) { // Remove any previously set standalone locations. $post_meta = $this->remove_standalone_location_from_array( $form_id, $post_meta ); // Merge locations and remove duplicates. $new_location = array_unique( array_merge( $post_meta, $new_location ), SORT_REGULAR ); } // Update post meta. update_post_meta( $form_id, self::LOCATIONS_META, $new_location ); } /** * Remove a form page from an array. * * @since 1.8.7 * * @param int $form_id The form ID. * @param array $post_meta The post meta. * * @return array $post_meta Filtered post meta. */ private function remove_standalone_location_from_array( int $form_id, array $post_meta ): array { // No form ID or post meta? Bail. if ( empty( $form_id ) || empty( $post_meta ) ) { return []; } // Loop over all locations. foreach ( $post_meta as $key => $location ) { // Verify the location keys exist. if ( ! isset( $location['form_id'], $location['type'] ) ) { continue; } // If the form ID and location type match. if ( $location['form_id'] === $form_id && $this->is_standalone( $location['type'] ) ) { // Unset the form page location. unset( $post_meta[ $key ] ); } } return $post_meta; } }