New Form — a template picker. Picking a card creates the form * immediately (via Form::create) and sends the user straight into the * builder, the same flow the reference product uses instead of starting on a blank * unsaved canvas. */ class New_Form_Page { public function __construct() { add_action( 'admin_menu', [ $this, 'register' ] ); add_action( 'admin_post_chargeforms_create_from_template', [ $this, 'handle_create' ] ); add_action( 'admin_post_chargeforms_import_form', [ $this, 'handle_import' ] ); } public function register() { add_submenu_page( 'chargeforms', __( 'New Form', 'chargeforms' ), __( 'New Form', 'chargeforms' ), 'chargeforms_manage_forms', 'chargeforms-new', [ $this, 'render' ] ); } /** * Categories mirror the same set used on the marketing site's * /templates gallery — this list and that one should stay in sync so * "Templates" means the same thing in both places. */ public static function categories() { return [ 'business' => __( 'Business', 'chargeforms' ), 'registration' => __( 'Registration', 'chargeforms' ), 'survey' => __( 'Survey & Quiz', 'chargeforms' ), 'payment' => __( 'Payment', 'chargeforms' ), 'event' => __( 'Event', 'chargeforms' ), 'education' => __( 'Education', 'chargeforms' ), 'booking' => __( 'Booking', 'chargeforms' ), 'application' => __( 'Application', 'chargeforms' ), 'lead_generation' => __( 'Lead Generation', 'chargeforms' ), 'healthcare' => __( 'Healthcare', 'chargeforms' ), 'nonprofit' => __( 'Non-profit', 'chargeforms' ), 'developer' => __( 'Developer / QA', 'chargeforms' ), ]; } /** * Per-category tag color — matches the mockup's own catColor map * (design_handoff_chargeforms's New Form screen) for every category * that has a direct equivalent there: survey, payment, education, * healthcare, nonprofit, developer, and event ("Events" in the * mockup). ChargeForms' own category set has four categories with no * mockup equivalent (registration, booking, application, * lead_generation) — those fall back to the same neutral gray the * mockup uses for "General" rather than inventing new brand colors * the design never specified. */ private static function category_color( $category ) { $colors = [ 'business' => '#606266', 'survey' => '#00b27f', 'payment' => '#6c4ef5', 'education' => '#8a6d2f', 'healthcare' => '#e0538a', 'nonprofit' => '#00805c', 'developer' => '#1e1f21', 'event' => '#0d8a8a', ]; return $colors[ $category ] ?? '#606266'; } /** Whether a template has a real payment field — drives the mockup's "PAYMENT" badge. */ private static function template_has_payment( array $template ) { foreach ( $template['fields'] ?? [] as $field ) { if ( in_array( $field['type'] ?? '', [ 'payment_item', 'custom_amount', 'payment_method' ], true ) ) { return true; } } return false; } /** * Industry "Packs" — a curated view over the same templates() registry * below, grouped by industry instead of by generic field-category. A * template can belong to a pack via its own 'packs' key (an array of * these slugs) without needing a separate/duplicated data source. */ public static function packs() { return [ 'real_estate' => [ 'label' => __( 'Real Estate', 'chargeforms' ), 'description' => __( 'Property inquiries, viewing requests, and rental applications.', 'chargeforms' ), 'icon' => 'dashicons-admin-home', ], 'healthcare' => [ 'label' => __( 'Healthcare', 'chargeforms' ), 'description' => __( 'Patient intake, appointment requests, and consultation forms with consent built in.', 'chargeforms' ), 'icon' => 'dashicons-heart', ], 'education' => [ 'label' => __( 'Education', 'chargeforms' ), 'description' => __( 'Course enrollment and scholarship applications with document uploads.', 'chargeforms' ), 'icon' => 'dashicons-welcome-learn-more', ], 'legal' => [ 'label' => __( 'Legal', 'chargeforms' ), 'description' => __( 'Consultation requests and client intake with case details and consent.', 'chargeforms' ), 'icon' => 'dashicons-hammer', ], 'restaurant' => [ 'label' => __( 'Restaurant', 'chargeforms' ), 'description' => __( 'Table reservations and catering requests.', 'chargeforms' ), 'icon' => 'dashicons-store', ], 'events' => [ 'label' => __( 'Events', 'chargeforms' ), 'description' => __( 'Registration, RSVPs, and payment-ready ticketing.', 'chargeforms' ), 'icon' => 'dashicons-calendar-alt', ], 'nonprofit' => [ 'label' => __( 'Non-profit', 'chargeforms' ), 'description' => __( 'Donations, volunteer applications, and grant requests.', 'chargeforms' ), 'icon' => 'dashicons-groups', ], ]; } /** Templates belonging to a given pack, in registry order. */ public static function templates_for_pack( $pack_key ) { return array_filter( self::templates(), function ( $template ) use ( $pack_key ) { return in_array( $pack_key, $template['packs'] ?? [], true ); } ); } public static function templates() { return [ 'blank' => [ 'label' => __( 'Blank Form', 'chargeforms' ), 'description' => __( 'Start from scratch and drag in the fields you need.', 'chargeforms' ), 'icon' => 'dashicons-media-default', 'category' => 'business', 'fields' => [], ], 'contact' => [ 'label' => __( 'Simple Contact Form', 'chargeforms' ), 'description' => __( 'Name, email, and a message — the classic contact form.', 'chargeforms' ), 'icon' => 'dashicons-email-alt', 'category' => 'business', 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_message', 'type' => 'textarea', 'label' => __( 'Message', 'chargeforms' ), 'required' => true ], ], ], 'subject' => [ 'label' => __( 'Contact Form with Subject', 'chargeforms' ), 'description' => __( 'Adds a subject dropdown so replies get routed faster.', 'chargeforms' ), 'icon' => 'dashicons-index-card', 'category' => 'business', 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_subject', 'type' => 'select', 'label' => __( 'Subject', 'chargeforms' ), 'required' => true, 'options' => [ __( 'General Inquiry', 'chargeforms' ), __( 'Support', 'chargeforms' ), __( 'Sales', 'chargeforms' ) ], ], [ 'id' => 'f_message', 'type' => 'textarea', 'label' => __( 'Message', 'chargeforms' ), 'required' => true ], ], ], 'quote_request' => [ 'label' => __( 'Quote Request Form', 'chargeforms' ), 'description' => __( 'Collect project details and budget range before a sales call.', 'chargeforms' ), 'icon' => 'dashicons-clipboard', 'category' => 'business', 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_company', 'type' => 'text', 'label' => __( 'Company', 'chargeforms' ) ], [ 'id' => 'f_budget', 'type' => 'select', 'label' => __( 'Budget Range', 'chargeforms' ), 'options' => [ __( 'Under $1,000', 'chargeforms' ), __( '$1,000–$5,000', 'chargeforms' ), __( '$5,000+', 'chargeforms' ) ], ], [ 'id' => 'f_details', 'type' => 'textarea', 'label' => __( 'Project Details', 'chargeforms' ), 'required' => true ], ], ], 'job_application' => [ 'label' => __( 'Job Application Form', 'chargeforms' ), 'description' => __( 'Resume upload, work history, and screening questions in one flow.', 'chargeforms' ), 'icon' => 'dashicons-id-alt', 'category' => 'application', 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Full Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_phone', 'type' => 'phone', 'label' => __( 'Phone', 'chargeforms' ) ], [ 'id' => 'f_position', 'type' => 'select', 'label' => __( 'Position Applying For', 'chargeforms' ), 'required' => true, 'options' => [ __( 'Engineering', 'chargeforms' ), __( 'Design', 'chargeforms' ), __( 'Marketing', 'chargeforms' ), __( 'Support', 'chargeforms' ) ] ], [ 'id' => 'f_resume', 'type' => 'file', 'label' => __( 'Resume', 'chargeforms' ), 'required' => true, 'allowedExtensions' => 'pdf,doc,docx' ], [ 'id' => 'f_cover_letter', 'type' => 'textarea', 'label' => __( 'Cover Letter', 'chargeforms' ) ], ], ], 'event_registration' => [ 'label' => __( 'Event Registration Form', 'chargeforms' ), 'description' => __( 'Ticket tiers, dietary preferences, and a payment step in one form.', 'chargeforms' ), 'icon' => 'dashicons-calendar-alt', 'category' => 'event', 'packs' => [ 'events' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_dietary', 'type' => 'textarea', 'label' => __( 'Dietary Restrictions', 'chargeforms' ) ], [ 'id' => 'f_ticket', 'type' => 'payment_item', 'label' => __( 'Ticket', 'chargeforms' ), 'amount' => 25 ], [ 'id' => 'f_card', 'type' => 'payment_method', 'label' => __( 'Card', 'chargeforms' ) ], ], ], 'satisfaction_survey' => [ 'label' => __( 'Customer Satisfaction Survey', 'chargeforms' ), 'description' => __( 'NPS scale plus open-ended feedback, ready to analyze in Reports.', 'chargeforms' ), 'icon' => 'dashicons-chart-bar', 'category' => 'survey', 'fields' => [ [ 'id' => 'f_nps', 'type' => 'nps', 'label' => __( 'How likely are you to recommend us to a friend or colleague?', 'chargeforms' ), 'lowLabel' => __( 'Not likely', 'chargeforms' ), 'highLabel' => __( 'Very likely', 'chargeforms' ) ], [ 'id' => 'f_rating', 'type' => 'ratings', 'label' => __( 'Overall satisfaction', 'chargeforms' ), 'maxStars' => 5 ], [ 'id' => 'f_feedback', 'type' => 'textarea', 'label' => __( 'What could we do better?', 'chargeforms' ) ], ], ], 'donation' => [ 'label' => __( 'Donation Form', 'chargeforms' ), 'description' => __( 'Preset and custom donation amounts with a real Stripe payment step.', 'chargeforms' ), 'icon' => 'dashicons-heart', 'category' => 'nonprofit', 'packs' => [ 'nonprofit' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_amount', 'type' => 'custom_amount', 'label' => __( 'Donation Amount', 'chargeforms' ), 'minAmount' => 5 ], [ 'id' => 'f_card', 'type' => 'payment_method', 'label' => __( 'Card', 'chargeforms' ) ], ], ], 'volunteer_application' => [ 'label' => __( 'Volunteer Application Form', 'chargeforms' ), 'description' => __( 'Multi-step application with role-specific questions.', 'chargeforms' ), 'icon' => 'dashicons-groups', 'category' => 'nonprofit', 'packs' => [ 'nonprofit' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_phone', 'type' => 'phone', 'label' => __( 'Phone', 'chargeforms' ) ], [ 'id' => 'f_role', 'type' => 'select', 'label' => __( 'Role Interested In', 'chargeforms' ), 'required' => true, 'options' => [ __( 'Event Support', 'chargeforms' ), __( 'Mentoring', 'chargeforms' ), __( 'Administration', 'chargeforms' ), __( 'Fundraising', 'chargeforms' ) ] ], [ 'id' => 'f_page_break_1', 'type' => 'page_break', 'label' => '' ], [ 'id' => 'f_availability', 'type' => 'multiple_choice', 'label' => __( 'Availability', 'chargeforms' ), 'options' => [ __( 'Weekdays', 'chargeforms' ), __( 'Weekends', 'chargeforms' ), __( 'Evenings', 'chargeforms' ) ] ], [ 'id' => 'f_why', 'type' => 'textarea', 'label' => __( 'Why do you want to volunteer with us?', 'chargeforms' ), 'required' => true ], ], ], 'course_enrollment' => [ 'label' => __( 'Course Enrollment Form', 'chargeforms' ), 'description' => __( 'Student details, course selection, and payment in one submission.', 'chargeforms' ), 'icon' => 'dashicons-welcome-learn-more', 'category' => 'education', 'packs' => [ 'education' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Student Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_course', 'type' => 'payment_item', 'label' => __( 'Course', 'chargeforms' ), 'amount' => 199 ], [ 'id' => 'f_card', 'type' => 'payment_method', 'label' => __( 'Card', 'chargeforms' ) ], ], ], 'appointment_booking' => [ 'label' => __( 'Appointment Booking Form', 'chargeforms' ), 'description' => __( 'Service selection with a deposit payment.', 'chargeforms' ), 'icon' => 'dashicons-clock', 'category' => 'booking', 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_phone', 'type' => 'phone', 'label' => __( 'Phone', 'chargeforms' ) ], [ 'id' => 'f_date', 'type' => 'date', 'label' => __( 'Preferred Date', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_deposit', 'type' => 'custom_amount', 'label' => __( 'Deposit', 'chargeforms' ), 'minAmount' => 10 ], [ 'id' => 'f_card', 'type' => 'payment_method', 'label' => __( 'Card', 'chargeforms' ) ], ], ], 'newsletter' => [ 'label' => __( 'Newsletter Signup Form', 'chargeforms' ), 'description' => __( 'A minimal two-field form optimized for conversion.', 'chargeforms' ), 'icon' => 'dashicons-email', 'category' => 'lead_generation', 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'First Name', 'chargeforms' ) ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], ], ], 'consultation' => [ 'label' => __( 'Consultation Request Form', 'chargeforms' ), 'description' => __( 'Intake questionnaire for a healthcare or wellness consultation.', 'chargeforms' ), 'icon' => 'dashicons-heart', 'category' => 'healthcare', 'packs' => [ 'healthcare' ], 'settings' => [ 'double_optin' => 'on' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_phone', 'type' => 'phone', 'label' => __( 'Phone', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_reason', 'type' => 'textarea', 'label' => __( 'Reason for Consultation', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_consent', 'type' => 'gdpr', 'label' => __( 'I consent to my information being processed for this consultation request.', 'chargeforms' ), 'required' => true ], ], ], 'membership' => [ 'label' => __( 'Membership Signup Form', 'chargeforms' ), 'description' => __( 'Tiered membership plans with a recurring-friendly payment field.', 'chargeforms' ), 'icon' => 'dashicons-awards', 'category' => 'payment', 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_plan', 'type' => 'payment_item', 'label' => __( 'Membership Plan', 'chargeforms' ), 'amount' => 15 ], [ 'id' => 'f_card', 'type' => 'payment_method', 'label' => __( 'Card', 'chargeforms' ) ], ], ], 'registration' => [ 'label' => __( 'Event Attendee Registration', 'chargeforms' ), 'description' => __( 'A straightforward attendee sign-up with an address block.', 'chargeforms' ), 'icon' => 'dashicons-forms', 'category' => 'registration', 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Full Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_address', 'type' => 'address', 'label' => __( 'Mailing Address', 'chargeforms' ) ], ], ], // --- Industry pack-only templates (no standalone category tab; // only reachable via a pack's own view, see templates_for_pack()) --- 'property_inquiry' => [ 'label' => __( 'Property Inquiry Form', 'chargeforms' ), 'description' => __( 'Capture buyer/renter interest in a specific listing.', 'chargeforms' ), 'icon' => 'dashicons-admin-home', 'category' => 'business', 'packs' => [ 'real_estate' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_phone', 'type' => 'phone', 'label' => __( 'Phone', 'chargeforms' ) ], [ 'id' => 'f_property', 'type' => 'text', 'label' => __( 'Property Address / Listing ID', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_inquiry_type', 'type' => 'select', 'label' => __( 'I am interested in', 'chargeforms' ), 'options' => [ __( 'Buying', 'chargeforms' ), __( 'Renting', 'chargeforms' ), __( 'Selling', 'chargeforms' ) ] ], [ 'id' => 'f_message', 'type' => 'textarea', 'label' => __( 'Message', 'chargeforms' ) ], ], ], 'schedule_viewing' => [ 'label' => __( 'Schedule a Viewing Form', 'chargeforms' ), 'description' => __( 'Let prospects request a property viewing time.', 'chargeforms' ), 'icon' => 'dashicons-calendar-alt', 'category' => 'booking', 'packs' => [ 'real_estate' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_phone', 'type' => 'phone', 'label' => __( 'Phone', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_property', 'type' => 'text', 'label' => __( 'Property Address / Listing ID', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_date', 'type' => 'date', 'label' => __( 'Preferred Date', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_time', 'type' => 'select', 'label' => __( 'Preferred Time', 'chargeforms' ), 'options' => [ __( 'Morning', 'chargeforms' ), __( 'Afternoon', 'chargeforms' ), __( 'Evening', 'chargeforms' ) ] ], ], ], 'rental_application' => [ 'label' => __( 'Rental Application Form', 'chargeforms' ), 'description' => __( 'Employment, income, and ID verification for a rental application.', 'chargeforms' ), 'icon' => 'dashicons-media-document', 'category' => 'application', 'packs' => [ 'real_estate' ], 'settings' => [ 'double_optin' => 'on' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Full Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_phone', 'type' => 'phone', 'label' => __( 'Phone', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_address', 'type' => 'address', 'label' => __( 'Current Address', 'chargeforms' ) ], [ 'id' => 'f_employment', 'type' => 'select', 'label' => __( 'Employment Status', 'chargeforms' ), 'options' => [ __( 'Employed', 'chargeforms' ), __( 'Self-Employed', 'chargeforms' ), __( 'Student', 'chargeforms' ), __( 'Unemployed', 'chargeforms' ) ] ], [ 'id' => 'f_income', 'type' => 'numeric', 'label' => __( 'Monthly Income', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_id_document', 'type' => 'file', 'label' => __( 'ID Document', 'chargeforms' ), 'required' => true, 'allowedExtensions' => 'pdf,jpg,jpeg,png' ], [ 'id' => 'f_consent', 'type' => 'gdpr', 'label' => __( 'I consent to a background and credit check as part of this application.', 'chargeforms' ), 'required' => true ], ], ], 'patient_intake' => [ 'label' => __( 'Patient Intake Form', 'chargeforms' ), 'description' => __( 'New patient details, reason for visit, and consent.', 'chargeforms' ), 'icon' => 'dashicons-heart', 'category' => 'healthcare', 'packs' => [ 'healthcare' ], 'settings' => [ 'double_optin' => 'on' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Full Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_phone', 'type' => 'phone', 'label' => __( 'Phone', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_dob', 'type' => 'date', 'label' => __( 'Date of Birth', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_insurance', 'type' => 'text', 'label' => __( 'Insurance Provider', 'chargeforms' ) ], [ 'id' => 'f_reason', 'type' => 'textarea', 'label' => __( 'Reason for Visit', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_consent', 'type' => 'gdpr', 'label' => __( 'I consent to my health information being processed for this visit.', 'chargeforms' ), 'required' => true ], ], ], 'scholarship_application' => [ 'label' => __( 'Scholarship Application Form', 'chargeforms' ), 'description' => __( 'Academic details and supporting documents for a scholarship application.', 'chargeforms' ), 'icon' => 'dashicons-awards', 'category' => 'education', 'packs' => [ 'education' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Full Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_school', 'type' => 'text', 'label' => __( 'School / Institution', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_gpa', 'type' => 'numeric', 'label' => __( 'GPA', 'chargeforms' ) ], [ 'id' => 'f_essay', 'type' => 'textarea', 'label' => __( 'Personal Essay', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_transcript', 'type' => 'file', 'label' => __( 'Transcript', 'chargeforms' ), 'allowedExtensions' => 'pdf' ], ], ], 'legal_consultation' => [ 'label' => __( 'Legal Consultation Request', 'chargeforms' ), 'description' => __( 'Case type and details for an initial legal consultation.', 'chargeforms' ), 'icon' => 'dashicons-hammer', 'category' => 'business', 'packs' => [ 'legal' ], 'settings' => [ 'double_optin' => 'on' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Full Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_phone', 'type' => 'phone', 'label' => __( 'Phone', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_case_type', 'type' => 'select', 'label' => __( 'Case Type', 'chargeforms' ), 'required' => true, 'options' => [ __( 'Family Law', 'chargeforms' ), __( 'Business', 'chargeforms' ), __( 'Real Estate', 'chargeforms' ), __( 'Criminal', 'chargeforms' ), __( 'Other', 'chargeforms' ) ] ], [ 'id' => 'f_details', 'type' => 'textarea', 'label' => __( 'Case Details', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_consent', 'type' => 'gdpr', 'label' => __( 'I understand this does not establish an attorney-client relationship.', 'chargeforms' ), 'required' => true ], ], ], 'client_intake' => [ 'label' => __( 'Client Intake Form', 'chargeforms' ), 'description' => __( 'Full intake for a new legal client, with document upload.', 'chargeforms' ), 'icon' => 'dashicons-portfolio', 'category' => 'application', 'packs' => [ 'legal' ], 'settings' => [ 'double_optin' => 'on' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Full Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_phone', 'type' => 'phone', 'label' => __( 'Phone', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_address', 'type' => 'address', 'label' => __( 'Address', 'chargeforms' ) ], [ 'id' => 'f_case_type', 'type' => 'select', 'label' => __( 'Case Type', 'chargeforms' ), 'required' => true, 'options' => [ __( 'Family Law', 'chargeforms' ), __( 'Business', 'chargeforms' ), __( 'Real Estate', 'chargeforms' ), __( 'Criminal', 'chargeforms' ), __( 'Other', 'chargeforms' ) ] ], [ 'id' => 'f_opposing_party', 'type' => 'text', 'label' => __( 'Opposing Party (if any)', 'chargeforms' ) ], [ 'id' => 'f_details', 'type' => 'textarea', 'label' => __( 'Case Details', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_documents', 'type' => 'file', 'label' => __( 'Supporting Documents', 'chargeforms' ), 'allowedExtensions' => 'pdf,doc,docx' ], [ 'id' => 'f_consent', 'type' => 'gdpr', 'label' => __( 'I consent to my information being used for this intake.', 'chargeforms' ), 'required' => true ], ], ], 'table_reservation' => [ 'label' => __( 'Table Reservation Form', 'chargeforms' ), 'description' => __( 'Party size, date, and time for a dining reservation.', 'chargeforms' ), 'icon' => 'dashicons-store', 'category' => 'booking', 'packs' => [ 'restaurant' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_phone', 'type' => 'phone', 'label' => __( 'Phone', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_party_size', 'type' => 'numeric', 'label' => __( 'Party Size', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_date', 'type' => 'date', 'label' => __( 'Reservation Date', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_time', 'type' => 'select', 'label' => __( 'Reservation Time', 'chargeforms' ), 'options' => [ '5:00 PM', '6:00 PM', '7:00 PM', '8:00 PM', '9:00 PM' ] ], [ 'id' => 'f_requests', 'type' => 'textarea', 'label' => __( 'Special Requests', 'chargeforms' ) ], ], ], 'catering_request' => [ 'label' => __( 'Catering Request Form', 'chargeforms' ), 'description' => __( 'Event details and menu preferences for a catering quote.', 'chargeforms' ), 'icon' => 'dashicons-carrot', 'category' => 'event', 'packs' => [ 'restaurant' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_phone', 'type' => 'phone', 'label' => __( 'Phone', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_event_date', 'type' => 'date', 'label' => __( 'Event Date', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_guest_count', 'type' => 'numeric', 'label' => __( 'Guest Count', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_menu', 'type' => 'textarea', 'label' => __( 'Menu Preferences', 'chargeforms' ) ], [ 'id' => 'f_budget', 'type' => 'select', 'label' => __( 'Budget Range', 'chargeforms' ), 'options' => [ __( 'Under $500', 'chargeforms' ), __( '$500–$1,500', 'chargeforms' ), __( '$1,500+', 'chargeforms' ) ] ], ], ], 'rsvp_form' => [ 'label' => __( 'RSVP Form', 'chargeforms' ), 'description' => __( 'A simple attending/not-attending response with guest count.', 'chargeforms' ), 'icon' => 'dashicons-yes-alt', 'category' => 'event', 'packs' => [ 'events' ], 'fields' => [ [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_attending', 'type' => 'radio', 'label' => __( 'Will you attend?', 'chargeforms' ), 'required' => true, 'options' => [ __( 'Yes', 'chargeforms' ), __( 'No', 'chargeforms' ), __( 'Maybe', 'chargeforms' ) ] ], [ 'id' => 'f_guest_count', 'type' => 'numeric', 'label' => __( 'Number of Guests', 'chargeforms' ) ], [ 'id' => 'f_dietary', 'type' => 'textarea', 'label' => __( 'Dietary Restrictions', 'chargeforms' ) ], ], ], 'grant_application' => [ 'label' => __( 'Grant Application Form', 'chargeforms' ), 'description' => __( 'Organization details, project description, and requested amount.', 'chargeforms' ), 'icon' => 'dashicons-awards', 'category' => 'nonprofit', 'packs' => [ 'nonprofit' ], 'fields' => [ [ 'id' => 'f_org_name', 'type' => 'text', 'label' => __( 'Organization Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_name', 'type' => 'text', 'label' => __( 'Contact Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_phone', 'type' => 'phone', 'label' => __( 'Phone', 'chargeforms' ) ], [ 'id' => 'f_amount', 'type' => 'numeric', 'label' => __( 'Requested Amount', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_description', 'type' => 'textarea', 'label' => __( 'Project Description', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_budget_doc', 'type' => 'file', 'label' => __( 'Budget Document', 'chargeforms' ), 'allowedExtensions' => 'pdf,xls,xlsx' ], ], ], // --- Internal QA/developer template — not a customer-facing form. // Loads every completed builder capability (layout nesting, // containers, varied field settings, legacy + new-schema // conditional logic with nested AND/OR/NOT) into one canvas so it // can be manually exercised — see docs/research/ for the audit // this was built to support. Reuses the exact same template/ // Form::create/builder pipeline every other template already // goes through; nothing new was built to expose this. --- 'showcase_qa' => self::showcase_qa_template(), ]; } /** * Internal showcase/QA form — demonstrates every completed Milestone * 1-6 Phase A capability in one canvas: * - Layout Engine / Nested Sections / Columns / Containers: a Section * containing a two-column Row, one column holding a plain Container * wrapping two more fields (so Section, Row, Column, and Container * are all present and nested inside each other, not just siblings). * - Universal Field Capabilities / Settings Categories: a deliberately * varied spread of field types (choice, file, date, range, payment) * so every settings-panel section (General/Advanced/Conditional) has * something real to show for a range of field types, not just text * inputs. * - Conditional Logic: one field left on the OLD field.conditional * shape (exercises the "Enable conditional logic" auto-migration * path the first time someone opens it) and one field pre-populated * with a NEW-schema field.logicRule containing nested AND/OR/NOT * groups (exercises the full Rule Builder, Live Preview/Rule * Debugger, and Rule Inspector immediately on open, no setup needed). * Drag & drop, multi-selection, keyboard shortcuts, copy/paste, * duplicate, and the floating toolbar are pure canvas interactions — * they work on any loaded form, so this template just needs to have * enough varied, nested content for those to be worth trying, which it * does by construction (7+ top-level items, 3 levels of nesting). */ private static function showcase_qa_template() { return [ 'label' => __( 'Builder Showcase (QA/Dev)', 'chargeforms' ), 'description' => __( 'Every completed builder capability in one form — layout nesting, containers, varied field settings, and both the legacy and new conditional-logic Rule Builder. Internal testing use.', 'chargeforms' ), 'icon' => 'dashicons-screenoptions', 'category' => 'developer', 'fields' => [ [ 'id' => 'f_showcase_heading', 'type' => 'html', 'content' => '
' . __( 'This form exists to manually exercise every completed builder feature. Drag fields to reorder, shift-click to multi-select, Ctrl/Cmd+D to duplicate, Ctrl/Cmd+C/V to copy-paste, and open the two conditional-logic fields near the bottom to test the Rule Builder.', 'chargeforms' ) . '
' ], // Layout Engine: Section > Row > [Column, Column], one Column // holding a plain Container wrapping two more fields — Section, // Row, Column, and Container all present and nested, not flat. [ 'id' => 'f_showcase_section', 'type' => 'section', 'label' => __( 'Layout Engine: Nested Sections, Columns & Containers', 'chargeforms' ), 'children' => [ [ 'id' => 'f_showcase_row', 'type' => 'row', 'children' => [ [ 'id' => 'f_showcase_col_1', 'type' => 'column', 'width' => '50', 'widthMobile' => '100', 'children' => [ [ 'id' => 'f_showcase_first_name', 'type' => 'text', 'label' => __( 'First Name', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_showcase_last_name', 'type' => 'text', 'label' => __( 'Last Name', 'chargeforms' ), 'required' => true ], ], ], [ 'id' => 'f_showcase_col_2', 'type' => 'column', 'width' => '50', 'widthMobile' => '100', 'children' => [ [ 'id' => 'f_showcase_container', 'type' => 'container', 'label' => __( 'Container (nested inside a Column, inside a Row, inside a Section)', 'chargeforms' ), 'children' => [ [ 'id' => 'f_showcase_email', 'type' => 'email', 'label' => __( 'Email', 'chargeforms' ), 'required' => true ], [ 'id' => 'f_showcase_phone', 'type' => 'phone', 'label' => __( 'Phone', 'chargeforms' ) ], ], ], ], ], ], ], ], ], // Universal Field Capabilities / Settings Categories — a // deliberately varied spread so General/Advanced/Conditional // settings panels all have something real to inspect across // several field types, not just plain text inputs. [ 'id' => 'f_country', 'type' => 'select', 'label' => __( 'Country', 'chargeforms' ), 'required' => true, 'options' => [ __( 'Germany', 'chargeforms' ), __( 'France', 'chargeforms' ), __( 'United States', 'chargeforms' ), __( 'Other', 'chargeforms' ) ] ], [ 'id' => 'f_customer_type', 'type' => 'radio', 'label' => __( 'Customer Type', 'chargeforms' ), 'required' => true, 'options' => [ __( 'Individual', 'chargeforms' ), __( 'Business', 'chargeforms' ) ] ], [ 'id' => 'f_signup_date', 'type' => 'date', 'label' => __( 'Preferred Start Date', 'chargeforms' ) ], [ 'id' => 'f_satisfaction', 'type' => 'range_slider', 'label' => __( 'Expected Satisfaction', 'chargeforms' ), 'min' => 0, 'max' => 100, 'step' => 10 ], [ 'id' => 'f_attachment', 'type' => 'file', 'label' => __( 'Supporting Document', 'chargeforms' ), 'allowedExtensions' => 'pdf,doc,docx', 'maxSizeMb' => 5 ], [ 'id' => 'f_plan', 'type' => 'payment_item', 'label' => __( 'Plan', 'chargeforms' ), 'amount' => 25 ], // Conditional Logic — legacy shape (auto-migrates to the new // engine schema the first time "Enable conditional logic" is // toggled off/on in the builder). [ 'id' => 'f_newsletter_topic', 'type' => 'select', 'label' => __( 'Newsletter Topic', 'chargeforms' ), 'options' => [ __( 'Product Updates', 'chargeforms' ), __( 'Company News', 'chargeforms' ) ], 'conditional' => [ 'enabled' => true, 'action' => 'show', 'groups' => [ [ 'logic' => 'all', 'rules' => [ [ 'field' => 'f_country', 'operator' => 'is_not_empty', 'value' => '' ] ] ], ], ], ], // Conditional Logic — new engine schema, pre-populated with // nested AND/OR/NOT groups so opening this field's settings // immediately shows the full Rule Builder, Live Preview/Rule // Debugger, and Rule Inspector with real content to inspect, // no manual setup required to start testing. [ 'id' => 'f_vat_number', 'type' => 'text', 'label' => __( 'VAT Number', 'chargeforms' ), 'logicRule' => [ 'id' => 'rule_showcase_vat', 'schemaVersion' => 1, 'name' => 'Show VAT for EU business customers', 'enabled' => true, 'scope' => 'field', 'trigger' => [ 'id' => 'g_showcase_root', 'type' => 'group', 'combinator' => 'AND', 'not' => false, 'children' => [ [ 'id' => 'g_showcase_country', 'type' => 'group', 'combinator' => 'OR', 'not' => false, 'children' => [ [ 'id' => 'c_showcase_de', 'type' => 'condition', 'source' => 'field', 'ref' => 'f_country', 'operator' => 'is', 'value' => 'Germany' ], [ 'id' => 'c_showcase_fr', 'type' => 'condition', 'source' => 'field', 'ref' => 'f_country', 'operator' => 'is', 'value' => 'France' ], ], ], [ 'id' => 'g_showcase_not_individual', 'type' => 'group', 'combinator' => 'AND', 'not' => true, 'children' => [ [ 'id' => 'c_showcase_individual', 'type' => 'condition', 'source' => 'field', 'ref' => 'f_customer_type', 'operator' => 'is', 'value' => 'Individual' ], ], ], ], ], 'actions' => [ [ 'type' => 'show', 'target' => 'f_vat_number' ] ], 'priority' => 0, ], ], ], ]; } public function render() { $view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'picker'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! in_array( $view, [ 'templates', 'packs' ], true ) ) { $view = 'picker'; } $pack_key = isset( $_GET['pack'] ) ? sanitize_key( wp_unslash( $_GET['pack'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $packs = self::packs(); ?> field.logicRule // migration could still carry the legacy shape — no special // handling needed here though: Form::create() migrates every // field at the write boundary (see class-form.php), so nothing // un-migrated can land in the database via this or any other path. $form = Form::create( [ 'title' => ! empty( $data['title'] ) ? sanitize_text_field( $data['title'] ) . ' (Imported)' : __( 'Imported Form', 'chargeforms' ), 'fields' => $data['fields'], 'settings' => is_array( $data['settings'] ?? null ) ? $data['settings'] : [], ] ); if ( is_wp_error( $form ) ) { wp_die( esc_html( $form->get_error_message() ), 400 ); } wp_safe_redirect( admin_url( 'admin.php?page=chargeforms-builder&form_id=' . $form['id'] ) ); exit; } public function handle_create() { if ( ! current_user_can( 'chargeforms_manage_forms' ) || ! check_admin_referer( 'chargeforms_create_from_template' ) ) { wp_die( esc_html__( 'Not allowed.', 'chargeforms' ), 403 ); } $key = isset( $_POST['template'] ) ? sanitize_key( $_POST['template'] ) : 'blank'; $templates = self::templates(); $template = $templates[ $key ] ?? $templates['blank']; $form = Form::create( [ 'title' => $template['label'], 'fields' => $template['fields'], 'settings' => $template['settings'] ?? [], ] ); if ( is_wp_error( $form ) ) { wp_die( esc_html( $form->get_error_message() ), 400 ); } wp_safe_redirect( admin_url( 'admin.php?page=chargeforms-builder&form_id=' . $form['id'] ) ); exit; } }