0 ? $this->forms->find( $form_id ) : null; if ( ! $form instanceof Form ) { return $this->editor_notice( __( 'FormPilot — Smart Form Builder: this form no longer exists.', 'devplugins-custom-form-builder' ) ); } if ( ! $form->is_published() ) { return $this->editor_notice( __( 'FormPilot — Smart Form Builder: this form is still a draft, so it is only visible to you.', 'devplugins-custom-form-builder' ) ); } $fields = $this->fields->for_form( $form->id() ); if ( array() === $fields ) { return $this->editor_notice( __( 'FormPilot — Smart Form Builder: this form has no fields yet.', 'devplugins-custom-form-builder' ) ); } ++self::$instances; $uid = sprintf( 'formpilot-form-%1$d-%2$d', $form->id(), self::$instances ); $steps = $this->build_steps( $this->steps->resolve( $fields ), $uid ); $views = array(); foreach ( $steps as $step ) { $views = array_merge( $views, $step['views'] ); } if ( array() === $views ) { return $this->editor_notice( __( 'FormPilot — Smart Form Builder: this form has no fields yet.', 'devplugins-custom-form-builder' ) ); } $markup = Template::get( 'frontend/form', array( 'form' => $form, 'field_views' => $views, 'steps' => $steps, 'multi_step' => count( $steps ) > 1, 'can_save' => count( $steps ) > 1 && $this->progress->is_enabled( $form ), 'resume_token' => $this->resume_token(), 'form_uid' => $uid, 'action_url' => $this->action_url(), 'nonce_action' => self::NONCE_ACTION, 'nonce_field' => self::NONCE_FIELD, 'post_id' => (int) get_the_ID(), 'has_uploads' => $this->has_uploads( $views ), 'submit_label' => $this->submit_label( $form ), 'honeypot' => SpamGuard::honeypot_enabled() ? SpamGuard::HONEYPOT_FIELD : '', 'captcha' => $this->captcha(), 'stamp_field' => SpamGuard::STAMP_FIELD, 'stamp' => SpamGuard::stamp(), ) ); /** * Filters the rendered form markup. * * @since 1.0.0 * * @param string $markup Rendered markup. * @param Form $form Form that was rendered. * @param array $views Field view models. */ return (string) apply_filters( 'formpilot_rendered_form', $markup, $form, $views ); } /** * Returns what the template needs to draw the challenge. * * @return array The provider details, or an empty array when none applies. */ private function captcha(): array { $provider = SpamGuard::captcha(); if ( ! $provider instanceof CaptchaProvider ) { return array(); } return array( 'id' => $provider->id(), 'site_key' => $provider->site_key(), // Empty for a provider that draws nothing and works in the background. 'widget' => $provider->widget_class(), 'field' => $provider->response_field(), ); } /** * Returns the resume token carried by the current URL. * * Only the shape is checked here. Whether the token exists, and whether it * belongs to this form, is settled by the endpoint that reads the values back, * so a guessed token cannot be told apart from an expired one by looking at * the rendered page. * * @return string The token, or an empty string. */ private function resume_token(): string { /* * Through Request like every other input, rather than the superglobal. * `raw()` reads POST then GET, so a resume link's query argument is found * without this having to name the method. */ $token = Request::text( ProgressStore::QUERY_ARG ); return ProgressStore::is_token( $token ) ? $token : ''; } /** * Turns the resolved steps into what the template needs. * * @param array $steps Steps of the form. * @param string $uid Unique id of this form instance. * * @return array}> The steps. */ private function build_steps( array $steps, string $uid ): array { $prepared = array(); foreach ( $steps as $step ) { $views = array(); foreach ( $step->fields() as $field ) { $views[] = new FieldView( $field, $uid ); } $prepared[] = array( 'index' => $step->index(), 'title' => $step->title(), 'description' => $step->description(), 'id' => $uid . '-step-' . $step->index(), 'views' => $views, ); } return $prepared; } /** * Returns the URL the form posts to. * * An empty string means the template omits the attribute, which makes the * browser post back to the current URL. That is the right behaviour in * archives and widgets, where there is no single permalink to point at. * * @return string The permalink, or an empty string. */ private function action_url(): string { if ( ! is_singular() ) { return ''; } $permalink = get_permalink(); return is_string( $permalink ) ? $permalink : ''; } /** * Returns the label of the submit button. * * @param Form $form Form being rendered. */ private function submit_label( Form $form ): string { return $this->messages->submit_label( $form ); } /** * Determines whether the form carries an upload field. * * @param array $views Field view models. * * @return bool True when the form needs a multipart encoding. */ private function has_uploads( array $views ): bool { foreach ( $views as $view ) { if ( FieldTypes::FILE === $view->field()->type() ) { return true; } } return false; } /** * Returns a notice that only users who can edit forms see. * * Visitors get an empty string, so a broken embed never leaks anything. * * @param string $message Message to show. * * @return string The markup, or an empty string. */ private function editor_notice( string $message ): string { /* * Gated on the capability that governs forms, not on `edit_posts`. The * notice says whether a given form id exists and whether it is still a * draft, and `edit_posts` reaches down to Contributors — who can enumerate * form ids with it and learn about drafts they have nothing to do with. * The people this message is for are the people who can edit forms. */ if ( ! Capabilities::current_user_can( Capabilities::MANAGE_FORMS ) ) { return ''; } return sprintf( '

%s

', esc_html( $message ) ); } }