// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpUnnecessaryCurlyVarSyntaxInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
use WPForms\Tasks\Actions\FormsLocatorScanTask;
* Column name on Forms Overview admin page.
const COLUMN_NAME = 'locations';
const LOCATIONS_META = 'wpforms_form_locations';
const WPFORMS_WIDGET_NAME = 'wpforms-widget';
const WPFORMS_WIDGET_PREFIX = self::WPFORMS_WIDGET_NAME . '-';
* WPForms widgets option name.
const WPFORMS_WIDGET_OPTION = 'widget_' . self::WPFORMS_WIDGET_NAME;
const TEXT_WIDGET_NAME = 'text';
const TEXT_WIDGET_PREFIX = self::TEXT_WIDGET_NAME . '-';
* Text widgets option name.
const TEXT_WIDGET_OPTION = 'widget_' . self::TEXT_WIDGET_NAME;
const BLOCK_WIDGET_NAME = 'block';
const BLOCK_WIDGET_PREFIX = self::BLOCK_WIDGET_NAME . '-';
* Block widgets' option name.
const BLOCK_WIDGET_OPTION = 'widget_' . self::BLOCK_WIDGET_NAME;
* Location type for widget.
* For a page/post, the location type is the post type.
const WP_TEMPLATE = 'wp_template';
const WP_TEMPLATE_PART = 'wp_template_part';
* Standalone location types.
const STANDALONE_LOCATION_TYPES = [ 'form_pages', 'conversational_forms' ];
* Default title for WPForms widget.
* For WPForms widget, we extract title from the widget. If it is empty, we use the default one.
private $wpforms_widget_title = '';
* Default title for text widget.
* For text widget, we extract title from the widget. If it is empty, we use the default one.
private $text_widget_title = '';
* Fixed title for block widget.
private $block_widget_title = '';
$this->home_url = home_url();
$this->scan_status = (string) get_option( FormsLocatorScanTask::SCAN_STATUS );
$this->wpforms_widget_title = __( 'WPForms Widget', 'wpforms-lite' );
$this->text_widget_title = __( 'Text Widget', 'wpforms-lite' );
$this->block_widget_title = __( 'Block Widget', 'wpforms-lite' );
private function hooks() {
add_filter( 'wpforms_admin_forms_table_facades_columns_data', [ $this, 'add_column_data' ] );
add_filter( 'wpforms_overview_table_column_value', [ $this, 'column_value' ], 10, 3 );
add_filter( 'wpforms_overview_row_actions', [ $this, 'row_actions_all' ], 10, 2 );
add_action( 'wpforms_overview_enqueue', [ $this, 'localize_overview_script' ] );
add_action( 'save_post', [ $this, 'save_post' ], 10, 3 );
add_action( 'post_updated', [ $this, 'post_updated' ], 10, 3 );
add_action( 'wp_trash_post', [ $this, 'trash_post' ] );
add_action( 'untrash_post', [ $this, 'untrash_post' ] );
add_action( 'delete_post', [ $this, 'trash_post' ] );
add_action( 'permalink_structure_changed', [ $this, 'permalink_structure_changed' ], 10, 2 );
$wpforms_widget_option = self::WPFORMS_WIDGET_OPTION;
$text_widget_option = self::TEXT_WIDGET_OPTION;
$block_widget_option = self::BLOCK_WIDGET_OPTION;
add_action( "update_option_{$wpforms_widget_option}" , [ $this, 'update_option' ], 10, 3 );
add_action( "update_option_{$text_widget_option}" , [ $this, 'update_option' ], 10, 3 );
add_action( "update_option_{$block_widget_option}", [ $this, 'update_option' ], 10, 3 );
* Add locations' column to the table columns data.
* @param array|mixed $columns Columns data.
public function add_column_data( $columns ): array {
$columns = (array) $columns;
$columns[ self::COLUMN_NAME ] = [
'label' => esc_html__( 'Locations', 'wpforms-lite' ),
'<span class="wpforms-locations-column-title">%1$s</span>' .
'<span class="wpforms-locations-column-icon" title="%2$s"></span>',
esc_html__( 'Locations', 'wpforms-lite' ),
esc_html__( 'Form locations', 'wpforms-lite' )
* @param mixed $value Column value.
* @param WP_Post $form Form.
* @param string $column_name Column name.
public function column_value( $value, $form, $column_name ) {
if ( $column_name !== self::COLUMN_NAME ) {
$form_locations = get_post_meta( $form->ID, self::LOCATIONS_META, true );
if ( $form_locations === '' ) {
FormsLocatorScanTask::SCAN_STATUS_IN_PROGRESS => '...',
FormsLocatorScanTask::SCAN_STATUS_COMPLETED => '0',
return $empty_values[ $this->scan_status ];
$values = $this->get_location_rows( $form_locations );
'<span class="wpforms-locations-count"><a href="#" title="%s">%d</a></span>',
esc_attr__( 'View form locations', 'wpforms-lite' ),
$column_value .= '<p class="locations-list">' . implode( '', $values ) . '</p>';
* Row actions for view "All".
* @param array $row_actions Row actions.
* @param WP_Post $form Form object.
public function row_actions_all( $row_actions, $form ) {
$form_locations = get_post_meta( $form->ID, self::LOCATIONS_META, true );
if ( ! $form_locations ) {
'<a href="#" title="%s">%s</a>',
esc_attr__( 'View form locations', 'wpforms-lite' ),
esc_html__( 'Locations', 'wpforms-lite' )
// Insert Locations action before the first available position in the positions' list or at the end of $row_actions.
$keys = array_keys( $row_actions );
foreach ( $positions as $position ) {
$pos = array_search( $position, $keys, true );
$pos = $pos === false ? count( $row_actions ) : $pos;
return array_slice( $row_actions, 0, $pos ) + $locations + array_slice( $row_actions, $pos );
* Localize the overview script to pass translation strings.
public function localize_overview_script() {
'wpforms-admin-forms-overview',
'paneTitle' => __( 'Form Locations', 'wpforms-lite' ),
'close' => __( 'Close', 'wpforms-lite' ),
* Get id of the sidebar where the widget is positioned.
* @param string $widget_id Widget id.
private function get_widget_sidebar_id( $widget_id ) {
$sidebars_widgets = wp_get_sidebars_widgets();
foreach ( $sidebars_widgets as $sidebar_id => $sidebar_widgets ) {
foreach ( $sidebar_widgets as $sidebar_widget ) {
if ( $widget_id === $sidebar_widget ) {
return (string) $sidebar_id;
* Get the name of the sidebar where the widget is positioned.
* @param string $widget_id Widget id.
private function get_widget_sidebar_name( $widget_id ) {
$sidebar_id = $this->get_widget_sidebar_id( $widget_id );
$sidebar = $this->get_sidebar( $sidebar_id );
return isset( $sidebar['name'] ) ? (string) $sidebar['name'] : '';
* Retrieves the registered sidebar with the given ID.
* @global array $wp_registered_sidebars The registered sidebars.
* @param string $id The sidebar ID.
* @return array|null The discovered sidebar, or null if it is not registered.
private function get_sidebar( $id ) {
if ( function_exists( 'wp_get_sidebar' ) ) {
return wp_get_sidebar( $id );
global $wp_registered_sidebars;
if ( ! $wp_registered_sidebars ) {
foreach ( $wp_registered_sidebars as $sidebar ) {
if ( $sidebar['id'] === $id ) {
if ( $id === 'wp_inactive_widgets' ) {
'id' => 'wp_inactive_widgets',
'name' => __( 'Inactive widgets', 'wpforms-lite' ),
* Get post location title.
* @param array $form_location Form location.
private function get_post_location_title( $form_location ) {
$title = $form_location['title'];
if ( $this->is_wp_template( $form_location['type'] ) ) {
return __( 'Site editor template', 'wpforms-lite' ) . ': ' . $title;
* Whether locations' type is WP Template.
* @param string $location_type Location type.
private function is_wp_template( $location_type ) {
return in_array( $location_type, [ self::WP_TEMPLATE, self::WP_TEMPLATE_PART ], true );
* Whether a location type is standalone.
* @param string $location_type Location type.
private function is_standalone( string $location_type ): bool {
return in_array( $location_type, self::STANDALONE_LOCATION_TYPES, true );
* @param array $form_location Form location.
private function get_location_title( $form_location ) {
if ( $form_location['type'] !== self::WIDGET ) {
return $this->get_post_location_title( $form_location );