menu, 'register' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_filter( 'plugin_action_links_' . FORMPILOT_PLUGIN_BASENAME, array( $this, 'action_links' ) );
add_filter( 'set_screen_option_' . FormsListTable::PER_PAGE_OPTION, array( $this, 'save_screen_option' ), 10, 3 );
add_filter( 'set_screen_option_' . EntriesListTable::PER_PAGE_OPTION, array( $this, 'save_screen_option' ), 10, 3 );
add_action( 'admin_notices', array( $this, 'captcha_notice' ) );
}
/**
* Warns when a captcha is selected but cannot be used.
*
* A provider missing a key is treated as switched off, which is the right
* call — a widget that cannot be verified would lock every visitor out — but
* it used to happen in complete silence. A typo in the secret field turned the
* site's spam protection off and nothing said so, on any screen, ever.
*/
public function captcha_notice(): void {
// Shown only where it is relevant: the plugin's own screens and the Plugins screen.
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( ! $screen || ( 'plugins' !== $screen->id && ! $this->menu->owns_screen( (string) $screen->id ) ) ) {
return;
}
$provider = CaptchaManager::misconfigured();
if ( '' === $provider || ! Capabilities::current_user_can( Capabilities::MANAGE_SETTINGS ) ) {
return;
}
printf(
'
',
esc_html(
sprintf(
/* translators: %s: Name of the selected captcha provider. */
__( 'FormPilot — Smart Form Builder: %s is selected as the spam check but one of its keys is missing, so no challenge is being shown and no spam check is running. Add the missing key under FormPilot → Settings → Security.', 'devplugins-custom-form-builder' ),
$provider
)
)
);
}
/**
* Persists the per page screen option.
*
* WordPress discards the value unless a filter claims it.
*
* @param mixed $status Value returned by earlier filters.
* @param string $option Option being saved.
* @param mixed $value Value submitted by the user.
*
* @return mixed The value to store.
*/
public function save_screen_option( mixed $status, string $option, mixed $value ): mixed {
$known = array( FormsListTable::PER_PAGE_OPTION, EntriesListTable::PER_PAGE_OPTION );
if ( ! in_array( $option, $known, true ) ) {
return $status;
}
return max( 1, (int) $value );
}
/**
* Returns the menu registrar.
*/
public function menu(): Menu {
return $this->menu;
}
/**
* Enqueues the admin assets on the plugin screens.
*
* @param string $hook_suffix Current admin page hook suffix.
*/
public function enqueue_assets( string $hook_suffix ): void {
if ( ! $this->menu->owns_screen( $hook_suffix ) ) {
return;
}
wp_enqueue_style( Assets::ADMIN_STYLE );
wp_enqueue_script( Assets::ADMIN_SCRIPT );
wp_localize_script(
Assets::ADMIN_SCRIPT,
'formpilotAdmin',
array(
'confirmDelete' => __( 'This form and all of its entries will be deleted permanently. Continue?', 'devplugins-custom-form-builder' ),
)
);
/*
* No `wp_set_script_translations()` here on purpose. Every string the
* scripts show reaches them through `wp_localize_script()`, already
* translated on the server, so there is no `wp.i18n` call for a JSON
* translation file to satisfy. Registering one would ask translators to
* produce files nothing ever loads.
*
* If a script starts calling `wp.i18n.__()`, this needs to come back
* along with the `wp-i18n` dependency in Assets::register_admin_assets().
*/
if ( $hook_suffix !== $this->menu->hook_suffix( FormEditPage::SLUG ) ) {
return;
}
wp_enqueue_script( Assets::BUILDER_SCRIPT );
wp_enqueue_script( Assets::WEBHOOKS_SCRIPT );
wp_localize_script(
Assets::WEBHOOKS_SCRIPT,
'formpilotWebhooks',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'action' => Webhooks::TEST_ACTION,
'nonce' => wp_create_nonce( Webhooks::TEST_NONCE ),
'integrationAction' => Integrations::TEST_ACTION,
'integrationNonce' => wp_create_nonce( Integrations::TEST_NONCE ),
'strings' => array(
'sending' => __( 'Sending…', 'devplugins-custom-form-builder' ),
'needUrl' => __( 'Enter a URL first.', 'devplugins-custom-form-builder' ),
'error' => __( 'The test could not be completed.', 'devplugins-custom-form-builder' ),
),
)
);
wp_localize_script(
Assets::BUILDER_SCRIPT,
'formpilotBuilder',
array(
'confirmRemoveField' => __( 'Remove this field? Answers already collected for it will be deleted when you save.', 'devplugins-custom-form-builder' ),
'untitled' => __( 'Untitled field', 'devplugins-custom-form-builder' ),
/* translators: %1$s: Number of fields. */
'fieldCount' => __( '%1$s fields', 'devplugins-custom-form-builder' ),
/* translators: %1$s: Field label. */
'fieldAdded' => __( '%1$s added.', 'devplugins-custom-form-builder' ),
/* translators: %1$s: Field label. */
'fieldDuplicated' => __( '%1$s duplicated.', 'devplugins-custom-form-builder' ),
/* translators: %1$s: Field label. */
'fieldRemoved' => __( '%1$s removed.', 'devplugins-custom-form-builder' ),
/* translators: 1: Field label. 2: New position. 3: Total number of fields. */
'fieldMoved' => __( '%1$s moved to position %2$s of %3$s.', 'devplugins-custom-form-builder' ),
'undone' => __( 'Change undone.', 'devplugins-custom-form-builder' ),
'redone' => __( 'Change redone.', 'devplugins-custom-form-builder' ),
'collapsedAll' => __( 'All field settings collapsed.', 'devplugins-custom-form-builder' ),
'expandedAll' => __( 'All field settings expanded.', 'devplugins-custom-form-builder' ),
)
);
}
/**
* Adds a shortcut to the plugin row on the Plugins screen.
*
* @param array $links Existing action links.
*
* @return array The filtered action links.
*/
public function action_links( array $links ): array {
if ( ! Capabilities::current_user_can( Capabilities::MANAGE_FORMS ) ) {
return $links;
}
$shortcut = sprintf(
'%2$s',
esc_url( admin_url( 'admin.php?page=' . self::MENU_SLUG ) ),
esc_html__( 'Forms', 'devplugins-custom-form-builder' )
);
array_unshift( $links, $shortcut );
return $links;
}
}