Wenprise Forms WP Registry grade A. No security issues foundWP RegistryNo security issues foundOpen the reportA
Extend Nette Forms for WordPress use.
Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/iwillhappy1314/wenprise-forms/archive/refs/heads/master.zipExtend Nette Forms for WordPress use.
Basic usage: Nette Form
Features
- Supports both Composer library mode and WordPress plugin mode
- Extends Nette Forms with WordPress-ready fields and UI widgets
- Includes form validation, conditional display, datastore integration, and AJAX-friendly inputs
Requirements
- PHP
>=8.3 ext-jsonnette/forms ^3.2nette/utils ^4.1- WordPress
>=6.0(recommended)
Installation
Option A: Install as a Composer library
composer require wenprise/forms
Option B: Install as a WordPress plugin
- Clone or download this repository into
wp-content/plugins/wenprise-forms. - Run
composer installinside the plugin directory. - Activate
Wenprise Formsin WordPress admin.
Runtime Modes
Composer library mode
- Your project is responsible for loading Composer autoload.
- Your code decides when and where to bootstrap the form objects.
WordPress plugin mode
- Plugin entry file
wenprise-forms.phploadsvendor/autoload.phpautomatically. - Plugin bootstraps with
new \Wenprise\Forms\Init();.
Quick Start (Minimal Working Example)
Forms has its own HTTP request handling flow. Instantiate it before head_sent, usually on init.
<?php
use Wenprise\Forms\Form;
use Wenprise\Forms\Helpers;
use Wenprise\Forms\Renders\DefaultFormRender;
add_action('init', function () {
global $demo_form;
$demo_form = Helpers::get_form();
$demo_form->setRenderer(new DefaultFormRender('horizontal'));
$demo_form->setMethod('POST');
$demo_form->setAction(home_url('/'));
$demo_form->addText('first_name', 'First Name')->setRequired();
$demo_form->addSubmit('send', 'Save');
});
add_shortcode('wenprise_form_demo', function () {
global $demo_form;
if ($demo_form->isSuccess()) {
$values = $demo_form->getValues();
$first_name = $values->first_name;
// Save or process your value here.
}
return (string) $demo_form;
});
Usage
Quick start
Forms includes its own HTTP request handling flow, so instantiate it before head_sent, usually in the init action.
add_action('init', function ()
{
global $form;
// Get forms object
$form = Helpers::get_form();
});
Create a Form instance
use Wenprise\Forms\Form;
$form = new Form;
$form->setRenderer(new \Wenprise\Forms\Renders\DefaultFormRender('horizontal'));
// Set form method
$form->setMethod( 'POST' );
$form->setAction('https://www.example.com');
// Set form field
$form->addText('first_name', 'First Name');
// Set submit button
$form->addSubmit( 'send', 'Save' );
// Validate form and get form data
if ( $form->isSuccess() ) {
$values = $form->getValues();
$first_name = $values->first_name;
}
Frontend Assets
Some fields depend on JavaScript/CSS widgets (for example: Slider, Chosen, Date Picker, Signature, Star Rating, Autocomplete, Chained Select).
- Ensure assets are properly enqueued in your theme/plugin runtime.
- Prefer testing one advanced field at a time when integrating into existing themes.
Security Notes
- Always include CSRF protection for public forms (
$form->addCsrf(...)). - For AJAX endpoints, validate nonce and capability before processing input.
- Sanitize incoming data and escape output in templates.
- Do not trust client-side validation alone; keep validation rules on the server side.
Set Required
$form->addText('first_name', 'First Name')
->setRequired();
Add rule
$form->addPassword('re_password', 'Password again:')
->addRule($form::EQUAL, 'Password mismatch', $form['password']);
Add field description
$form->addText('first_name', 'First Name')
->setOption('description', 'This is your first name.');
With Html
$form->addTextArea('description', Html::el('p')
->setHtml('This number remains hidden. <a href="...">Terms of service.</a>')
);
Set condition display
$form->addColorPicker('first_name3', 'First Name')
->setHtmlAttribute('data-cond', '[name=first_name2] == 2');
Control width
setWidth() supports both Bootstrap-based renderers and TailwindGridFormRender.
$form->addText('first_name', 'First Name')
->setWidth(4);
You can also define responsive widths with breakpoint names:
$form->addText('first_name', 'First Name')
->setWidth(6, 'base')
->setWidth(3, 'md')
->setWidth(4, 'lg');
Or use the shorthand form:
$form->addText('first_name', 'First Name')
->setWidth(6, 3, 4);
The shorthand above means:
base = 6md = 3lg = 4
With DefaultFormRender('vertical') and InlineFormRender(), setWidth() is mapped to Bootstrap-style classes such as rs-col-md-4.
With TailwindGridFormRender('vertical'), setWidth() is mapped to grid span classes such as rs-grid-form__span-md-4.
DefaultFormRender('horizontal') keeps each field full width by design, so width settings are ignored there.
Repeater
Use addRepeater() to build repeatable field groups.
use Wenprise\Forms\Renders\GridFormRender;
$form = new \Wenprise\Forms\Form('contacts_form');
$form->setRenderer(new GridFormRender('vertical'));
$form->addRepeater('contacts', 'Contacts', function (\Wenprise\Forms\Container $row): void {
$row->addText('name', 'Name')->setWidth(6, 3, 4);
$row->addText('phone', 'Phone')->setWidth(6, 3, 4);
$row->addText('email', 'Email')->setWidth(12, 6, 4);
}, 1, 5);
$form->addSubmit('send', 'Save');
addRepeater() arguments:
$name: repeater field name$label: repeater label$factory: callback used to define each row container$copy_count: initial row count$max_copies: optional maximum row count
Submitted values are returned as nested arrays and can be stored by the built-in datastore layer.
Multiple Submit Button
$form->addSubmit('save', 'Save');
$form->addSubmit('delete', 'Delete');
if ($form->isSuccess()) {
if ($form['save']->isSubmittedBy()) {
....
}
if ($form['delete']->isSubmittedBy()) {
....
}
}
Use html in label or description
$confirm = Html::el('span')->setHtml('I agree the <a href="#">Terms of service.</a>');
$form->addCheckbox('confirm', $confirm)->setOption('description', $confirm);
Set Datastore
$form->setDatastore(new \Wenprise\Forms\Datastores\PostMetaDatastore(1));
$form->save();
Custom Settings Page Example
Use OptionsDatastore to build a WordPress admin settings page quickly.
<?php
use Wenprise\Forms\Form;
use Wenprise\Forms\Datastores\OptionsDatastore;
use Wenprise\Forms\Renders\AdminFormRender;
add_action('admin_menu', function () {
add_menu_page(
'Wenprise Form Settings',
'Form Settings',
'manage_options',
'wenprise-form-settings',
'render_wenprise_form_settings_page'
);
});
function render_wenprise_form_settings_page() {
$form = new Form('wenprise_form_settings');
$form->setRenderer(new AdminFormRender('vertical'));
$form->setMethod('POST');
$form->addText('wprs_company_name', 'Company Name')
->setDefaultValue(get_option('wprs_company_name', ''));
$form->addText('wprs_support_email', 'Support Email')
->setRequired()
->addRule($form::EMAIL, 'Please enter a valid email address.')
->setDefaultValue(get_option('wprs_support_email', ''));
$form->addCheckbox('wprs_enable_notifications', 'Enable Email Notifications')
->setDefaultValue((bool) get_option('wprs_enable_notifications', false));
$form->addSubmit('save', 'Save Settings');
$form->setDatastore(new OptionsDatastore());
$form->save();
echo '<div class="wrap"><h1>Wenprise Form Settings</h1>';
$form->render();
echo '</div>';
}
Tab Groups
Use tab groups to split long forms into multiple panels.
$form = new \Wenprise\Forms\Form();
$form->addTab('basic', 'Basic', true);
$form->addText('first_name', 'First Name');
$form->addText('email', 'Email');
$form->addTab('advanced', 'Advanced');
$form->addTextArea('notes', 'Notes');
$form->endTab();
// This submit button is outside tab groups.
$form->addSubmit('send', 'Save');
// Template context:
echo $form;
Tabs are rendered automatically through the existing render flow ($form->render() / echo $form), so no extra render method is required.
Submit buttons created inside tab groups are rendered outside tab panes automatically, so the save action stays visible.
You can also use underscore naming method:
$form->add_tab('basic', 'Basic', true);
echo $form;
Stepper Groups
Use stepper groups to render a progress bar with previous and next step actions.
$form = new \Wenprise\Forms\Form();
$form->addStep('campaign', 'Select master blaster campaign settings', true);
$form->addText('campaign_name', 'Campaign Name');
$form->endStep();
$form->addStep('ad_group', 'Create an ad group');
$form->addText('group_name', 'Group Name');
$form->endStep();
$form->addStep('ad', 'Create an ad');
$form->addTextArea('ad_content', 'Ad Content');
$form->endStep();
$form->addSubmit('send', 'Save');
echo $form;
You can also use underscore naming method:
$form->add_step('campaign', 'Campaign', true);
If you render inside a shortcode callback, return the form HTML instead of echoing:
add_shortcode('wenprise_form_demo', function () use ($form) {
return (string) $form;
});
Fields
nonce field
$form->addCsrf('postform', 'Nonce invalid');
WordPress Tinymce editor
$form->addEditor('post_extra', 'Extra content', []);
Ajax uploader
$form->addAjaxUpload('photos', 'Photos', true, )
->setUrl( admin_url( 'admin-ajax.php?action=upload' ) );
Uploader backend sample.
add_action('wp_ajax_upload', 'ajax_uploader');
add_action('wp_ajax_nopriv_upload', 'ajax_uploader');
function ajax_uploader()
{
require_once(ABSPATH . 'wp-admin/includes/image.php');
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/media.php');
$attachment_id = media_handle_upload('file', 0);
$thumb_url = wp_get_attachment_thumb_url($attachment_id);
$origin_url = wp_get_attachment_url($attachment_id);
$thumb = get_post($attachment_id);
$file_data = [
'id' => $attachment_id,
'original' => $thumb->post_title,
'size' => $thumb->size,
'state' => 'SUCCESS',
'title' => $thumb->post_title,
'type' => $thumb->post_mime_type,
'thumb' => $thumb_url,
'url' => $origin_url,
];
wp_send_json($file_data, 200);
return false;
}
Slider input
$form->addSlider('price', 'Price', []);
Data Picker
$form->AddBirthdaypicker('_birthday', 'Date of Birth', [
'format' => 'YYYY-MM-DD',
'template' => 'YYYY-MM-DD',
'minYear' => '1900',
'maxYear' => date("Y")
]);
Color Picker
$form->addColorPicker('color', 'Color', []);
Chosen
$choices = [
'php' => 'PHP',
'javascript' => 'JavaScript',
'css' => 'CSS',
'java' => 'Java',
];
$form->addChosen('category', 'Category', $choices);
Multi Chosen
$form->addMultiChosen('post_tags', 'Tags', $choices);
Signature Field
$options = [
'width' => '500',
'height' => '250',
'border' => '#999',
'background' => '#f3f3f3',
];
$form->addSignature('first_name9', 'First Name', $options);
Star rating input
$options = [
'displayOnly' => false,
'showClear' => false,
'theme' => 'krajee-svg',
'step' => 1,
'min' => 1,
'max' => 5,
];
$form->addStarRating('rating', 'Rating', $options);
Image Select
$options = [
'light' => 'https://via.placeholder.com/64/EEEEEE/000000/?text=Light',
'dark' => 'https://via.placeholder.com/64/000000/FFFFFF/?text=Dark',
];
$form->addImagePicker('theme', 'Theme', $options);
Autocomplete Input
Source is an array or a url returns an array.
$form->addAutocomplete('name', 'Name')->setSource([
[
'value' => 'aaa',
'data' => 'AAA',
],
[
'value' => 'bbb',
'data' => 'BBB',
],
[
'value' => 'ccc',
'data' => 'CCC',
],
]);
If Source is an ajax url, the backend need return data as bellow.
suggestions: [
{ "value": "United Arab Emirates", "data": "AE" },
{ "value": "United Kingdom", "data": "UK" },
{ "value": "United States", "data": "US" }
]
Table input
$fields = [
[
'name' => 'name',
'display' => 'Product Name',
'type' => 'text',
],
[
'name' => 'quantity',
'display' => 'Quantity',
'type' => 'text',
],
[
'name' => 'price',
'display' => 'Unit Price',
'type' => 'text',
],
];
$values = [
[
'name' => 'Macbook Pro',
'quantity' => '1',
'price' => '8500',
],
[
'name' => 'Pixel XL',
'quantity' => '2',
'price' => '8500',
],
];
$form->addTableInput('table', 'Table', [], $fields)
->setDefaultValue($values);
Group Input
Set a prefix or suffix for text input.
$form->addGroupInput('day1', 'Day')
->setPrefix('Email')
->setSuffix('gmail.com');
SMS input
Send SMS code
$form->addSmsInput('phone', 'Cellphone', )
->setUrl(admin_url('admin-ajax.php?action=validate_cellpone'));
Backend Example
/**
* Send SMS verification code.
*/
add_action('wp_ajax_validate_cellpone', 'validate_cellpone');
add_action('wp_ajax_nopriv_validate_cellpone', 'validate_cellpone');
function validate_cellpone()
{
$phone = Input::get( 'phone', null );
if ( ! $phone && is_user_logged_in() ) {
$phone = OpenAuth::get_open_id( 'phone', get_current_user_id() );
}
$random = mt_rand( 100000, 999999 );
// Save the verification code before sending SMS.
$code = PhoneCode::query()->firstOrCreate( [ 'phone' => $phone ] );
$code->code = $random;
$code->save();
$msg = Helper::send_sms( $phone, $code->code );
wp_send_json( $msg, '200' );
};
/**
* SMS backend example using Yunpian API.
*/
function send_sms( $mobile, $content )
{
$config = [
'apikey' => 'xxxxx',
'tpl_id' => '123456',
];
// Template API endpoint
$url = "https://sms.yunpian.com/v2/sms/tpl_single_send.json";
$args = [
'body' => [
'apikey' => $config[ 'apikey' ],
'mobile' => $mobile,
// Template SMS payload
'tpl_id' => $config[ 'tpl_id' ],
'tpl_value' => "#code#=$content",
],
];
$result = json_decode( wp_remote_retrieve_body( wp_remote_post( $url, $args ) ) );
// Return message based on gateway response
return [
'code' => $result->code,
'msg' => $result->msg,
];
}
Captcha Input
$form->AddCaptcha('captcha', 'Captcha')
->setUrl(admin_url('admin-ajax.php?action=get_captcha'));
Chained Select
$form->addChainedSelect('chained', 'Chained Select', [
'url' => get_theme_file_uri('cityData.min.json'),
'selects' => ['province', 'city', 'area'],
'emptyStyle' => 'none',
], ['province', 'city', 'area'])->setDefaultValue([001, 002, 003]);
Captcha backend sample.
/**
* Captcha backend sample.
* WordPress core do not use sessions, use cookie and transient instead
*
* Run composer require gregwar/captcha to install requirement
*/
add_action('wp_ajax_get_captcha', 'get_captcha');
add_action('wp_ajax_nopriv_get_captcha', 'get_captcha');
function get_captcha($type)
{
header('Content-type: image/jpeg');
$builder = new CaptchaBuilder();
$captcha_id = wp_generate_uuid4();
$expires = MINUTE_IN_SECONDS * 5;
setcookie('wprs-security-captcha-id', $captcha_id, time() + $expires);
set_transient($captcha_id, $builder->getPhrase(), $expires);
$builder->build()
->output();
}
// Validate captcha
$captcha_id = $_COOKIE[ 'wprs-security-captcha-id' ];
$session_captcha = get_transient($captcha_id);Releases
Newest 25 of 60 recorded releases.
These releases are tags only. The author does not attach a packaged zip, so there are no download counts to report.