self::SLUG, 'tab' => $tab, ), $args ), admin_url( 'admin.php' ) ); } public static function menus() { add_submenu_page( 'edit.php?post_type=accessible_form', __( 'Tools & guidance', 'accessible-forms-by-accessible-org' ), __( 'Tools & guidance', 'accessible-forms-by-accessible-org' ), 'edit_accessible_forms', self::SLUG, array( self::class, 'render' ) ); if ( current_user_can( 'read_accessible_form_entries' ) && ! current_user_can( 'edit_accessible_forms' ) ) { add_menu_page( __( 'Submissions', 'accessible-forms-by-accessible-org' ), __( 'Submissions', 'accessible-forms-by-accessible-org' ), 'read_accessible_form_entries', EntriesTable::SLUG, array( EntriesTable::class, 'render_page' ), 'dashicons-feedback', 26 ); } } public static function form_start( $operation, array $hidden = array() ) { echo '
'; // Add-ons add their own template tools, such as saving a form as a template. Extensions::notify( 'accessible_forms_templates_tab' ); } public static function form_picker( $selected = 0, $mode = 'edit', $empty_label = '' ) { $identity = wp_unique_id( 'af-form-picker-' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only catalog pagination; actions verify their own nonce. $number = max( 1, absint( $_GET['af_form_page'] ?? 1 ) ); $page = \AccessibleForms\FormCatalog::admin_options( '', $number, $mode ); $options = array( '' => $empty_label ?: __( 'Choose a form', 'accessible-forms-by-accessible-org' ) ); foreach ( $page['items'] as $item ) { $options[ $item['id'] ] = $item['label']; } $post = $selected ? get_post( $selected ) : null; if ( $post && 'accessible_form' === $post->post_type && ( 'entries' === $mode ? Capabilities::form( $selected ) : current_user_can( 'edit_post', $selected ) ) ) { $options[ $selected ] = ( $post->post_title ?: __( '(no title)', 'accessible-forms-by-accessible-org' ) ) . ' (#' . $selected . ')'; } echo '' . esc_html__( 'Checks stored pages, posts, public custom content, synced patterns and saved templates, including nested blocks. Theme files, widgets and third-party builder data may need separate inspection.', 'accessible-forms-by-accessible-org' ) . '
'; if ( ! $id || ! current_user_can( 'edit_post', $id ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only screen selection; every action verifies a POST nonce and capability. $page = max( 1, absint( $_GET['scan_page'] ?? 1 ) ); // Add-ons report placement that does not live in content, such as automatic rules. Extensions::notify( 'accessible_forms_usage_summary', $id ); $scan = Placement::scan_batch( $id, $page ); echo '' . esc_html__( 'No placements in this batch of content.', 'accessible-forms-by-accessible-org' ) . '
'; } if ( $page > 1 ) { echo '' . esc_html__( 'Previous batch', 'accessible-forms-by-accessible-org' ) . ' '; } if ( $page < $scan['pages'] ) { echo '' . esc_html__( 'Check next 100 items', 'accessible-forms-by-accessible-org' ) . ''; } } /** Forms that do not meet the accessibility checks, with a link to fix each. */ private static function accessibility_checks() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only audit pagination. $page = max( 1, absint( $_GET['check_page'] ?? 1 ) ); $counts = wp_count_posts( 'accessible_form' ); $total = 0; foreach ( array( 'publish', 'draft', 'pending', 'private', 'future' ) as $status ) { $total += (int) ( $counts->$status ?? 0 ); } $pages = max( 1, (int) ceil( $total / 200 ) ); $page = min( $page, $pages ); $forms = \AccessibleForms\FormCheck::forms( 200, $page ); echo '' . esc_html( sprintf( /* translators: 1: batch number, 2: number of batches. */ __( 'Reviewing batch %1$d of %2$d, up to 200 forms per batch.', 'accessible-forms-by-accessible-org' ), $page, $pages ) ) . '
'; if ( $page > 1 ) { echo '' . esc_html__( 'Previous batch', 'accessible-forms-by-accessible-org' ) . ' '; } if ( $page < $pages ) { echo '' . esc_html__( 'Check next batch', 'accessible-forms-by-accessible-org' ) . ''; } if ( ! $forms ) { echo '' . esc_html__( 'The forms in this batch meet the current configuration checks. Check the published pages and complete the manual accessibility review as well.', 'accessible-forms-by-accessible-org' ) . '
'; return; } echo '| ' . esc_html__( 'Form', 'accessible-forms-by-accessible-org' ) . ' | ' . esc_html__( 'Published version', 'accessible-forms-by-accessible-org' ) . ' | ' . esc_html__( 'Unpublished edits', 'accessible-forms-by-accessible-org' ) . ' |
|---|---|---|
| ' . ( current_user_can( 'edit_post', $id ) ? '' . esc_html( $title ) . '' : esc_html( $title ) ) . ' | ' . esc_html( $form['live'] /* translators: %s: number of problems. */ ? sprintf( _n( '%s problem, shown to visitors', '%s problems, shown to visitors', $form['live'], 'accessible-forms-by-accessible-org' ), number_format_i18n( $form['live'] ) ) : __( 'Meets the checks', 'accessible-forms-by-accessible-org' ) ) . ' | ' . esc_html( $form['pending'] ? __( 'Saved with problems; not published', 'accessible-forms-by-accessible-org' ) : __( 'None', 'accessible-forms-by-accessible-org' ) ) . ' |
| ' . esc_html( $label ) . ' | ' . esc_html( (string) $value ) . ' |
|---|
' . esc_html( sprintf( /* translators: %s: maximum number of days to keep spam and review entries. */ _n( 'Spam and review items are deleted after %s day, or sooner if their form retention expires first.', 'Spam and review items are deleted after %s days, or sooner if their form retention expires first.', $days, 'accessible-forms-by-accessible-org' ), number_format_i18n( $days ) ) ) . '
' . esc_html__( 'Change retention in Privacy settings', 'accessible-forms-by-accessible-org' ) . '
'; } /** Only shown to settings administrators when historical dates need attention. */ private static function historical_dates( array $report ) { $state = get_option( \AccessibleForms\TimeMigration::OPTION, array() ); $pending = $report['historical_dates'] ?? 0; if ( ! $pending && ! $state ) { return; } echo '' . esc_html( sprintf( /* translators: %s: number of entries with unconfirmed dates. */ __( '%s entries have historical dates whose original time zone is unconfirmed.', 'accessible-forms-by-accessible-org' ), number_format_i18n( $pending ) ) ) . '
'; echo '' . esc_html__( 'New submissions use UTC and display in your site time zone. Historical dates keep their original values until you confirm where they came from. Unconfirmed dates are excluded from response-time statistics and date-based cleanup; existing expiry dates still apply.', 'accessible-forms-by-accessible-org' ) . '
'; if ( 'running' === ( $state['status'] ?? '' ) ) { echo '' . esc_html__( 'Conversion is running in small background batches. You can also process the next batch here.', 'accessible-forms-by-accessible-org' ) . '
'; self::form_start( 'time_migrate_batch' ); self::submit( __( 'Process next date batch', 'accessible-forms-by-accessible-org' ), 'secondary' ); echo ''; } elseif ( $pending ) { self::form_start( 'time_migrate_start' ); self::form_picker( 0, 'entries', __( 'All forms', 'accessible-forms-by-accessible-org' ) ); $selected_zone = wp_timezone_string(); if ( preg_match( '/^([+-])(\d{2}):(\d{2})$/D', $selected_zone, $offset ) ) { $selected_zone = 'UTC' . $offset[1] . ( (int) $offset[2] + (int) $offset[3] / 60 ); } echo '' . esc_html__( 'Use the time zone in effect when these records were created. If it changed, process each form separately only when its original zone is known. Keep unknown records unchanged.', 'accessible-forms-by-accessible-org' ) . '
'; Editor::select( 'repeated_hour', __( 'Dates during a repeated daylight-saving hour', 'accessible-forms-by-accessible-org' ), array( 'preserve' => __( 'Keep these dates unconfirmed', 'accessible-forms-by-accessible-org' ), 'earlier' => __( 'Use the earlier occurrence', 'accessible-forms-by-accessible-org' ), 'later' => __( 'Use the later occurrence', 'accessible-forms-by-accessible-org' ) ), 'preserve', 'af-repeated-hour' ); echo ''; echo '' . esc_html__( 'Conversion preserves the original dates. Invalid or nonexistent local times remain unconfirmed. It does not change follow-up calendar dates, send emails or delete entries.', 'accessible-forms-by-accessible-org' ) . '
'; self::submit( __( 'Convert confirmed historical dates', 'accessible-forms-by-accessible-org' ), 'secondary' ); echo ''; } else { echo '' . esc_html__( 'All stored entry dates have confirmed UTC provenance.', 'accessible-forms-by-accessible-org' ) . '
'; } echo '' . esc_html__( 'Recent administrative changes. Answers and credentials are not copied into this log. Records expire after 90 days.', 'accessible-forms-by-accessible-org' ) . '
' . esc_html__( 'Scroll horizontally to see all columns.', 'accessible-forms-by-accessible-org' ) . '
| ' . esc_html__( 'When (site time)', 'accessible-forms-by-accessible-org' ) . ' | ' . esc_html__( 'Action', 'accessible-forms-by-accessible-org' ) . ' | ' . esc_html__( 'Target', 'accessible-forms-by-accessible-org' ) . ' | ' . esc_html__( 'User', 'accessible-forms-by-accessible-org' ) . ' |
|---|---|---|---|
| ' . esc_html( EntryWorkflow::local_time( $row['created_at'] ) ) . ' | ' . esc_html( EntryWorkflow::activity_label( $row['action'] ) ) . ' | ' . wp_kses_post( self::activity_target( $row ) ) . ' | ' . esc_html( EntryWorkflow::person( $row['actor'] ) ) . ' |
| ' . esc_html__( 'No recent administrative changes.', 'accessible-forms-by-accessible-org' ) . ' | |||
' . esc_html__( 'Choose a template or add the fields you need. Keep labels descriptive and required questions purposeful.', 'accessible-forms-by-accessible-org' ) . '
' . esc_html__( 'Confirm recipients, preview the confirmation and choose a palette that passes every contrast check.', 'accessible-forms-by-accessible-org' ) . '
' . esc_html( Extensions::registered( 'pro' ) ? __( 'Publish the form, then place it on a page or post, use an Accessible Form block or shortcode, or set an automatic placement rule.', 'accessible-forms-by-accessible-org' ) : __( 'Publish the form, then place it on a page or post with the placement tool, an Accessible Form block or its shortcode.', 'accessible-forms-by-accessible-org' ) ) . '
' . esc_html__( 'Use the keyboard, send a test entry and confirm both storage and actual inbox delivery. Check on a phone and with your site theme.', 'accessible-forms-by-accessible-org' ) . '
' . esc_html( $answer ) . '