>, truncated: bool}|null */ public static function report(): ?array { $readers = array(); if ( defined( 'WPCF7_VERSION' ) ) { $readers[] = array( self::class, 'contact_form_7' ); } if ( defined( 'WPFORMS_VERSION' ) ) { $readers[] = array( self::class, 'wpforms' ); } if ( class_exists( 'FrmAppHelper' ) ) { $readers[] = array( self::class, 'formidable' ); } if ( class_exists( 'GFForms' ) ) { $readers[] = array( self::class, 'gravity_forms' ); } if ( array() === $readers ) { return null; } $admin = (string) get_option( 'admin_email', '' ); $forms = array(); foreach ( $readers as $reader ) { $forms = array_merge( $forms, call_user_func( $reader, $admin ) ); } return array( 'forms' => array_slice( $forms, 0, self::MAX_FORMS ), 'truncated' => count( $forms ) > self::MAX_FORMS, ); } /** * Contact Form 7: the mail and, when on, the second mail of each form. * * @return array> */ public static function contact_form_7( string $admin ): array { $out = array(); foreach ( self::posts_of( 'wpcf7_contact_form' ) as $row ) { $recipients = array(); foreach ( array( '_mail', '_mail_2' ) as $key ) { $mail = maybe_unserialize( get_post_meta( (int) $row['ID'], $key, true ) ); if ( ! is_array( $mail ) || ( '_mail_2' === $key && empty( $mail['active'] ) ) ) { continue; } if ( is_string( $mail['recipient'] ?? null ) ) { $recipients[] = $mail['recipient']; } } $out[] = self::form( 'contact-form-7', (string) $row['ID'], (string) $row['post_title'], $recipients, $admin ); } return $out; } /** * WPForms: every enabled notification's "send to" list. * * @return array> */ public static function wpforms( string $admin ): array { $out = array(); foreach ( self::posts_of( 'wpforms' ) as $row ) { $data = json_decode( (string) $row['post_content'], true ); $settings = is_array( $data['settings'] ?? null ) ? $data['settings'] : array(); $notifications = is_array( $settings['notifications'] ?? null ) ? $settings['notifications'] : array(); $recipients = array(); if ( '0' !== (string) ( $settings['notification_enable'] ?? '1' ) ) { foreach ( $notifications as $notification ) { if ( is_array( $notification ) && is_string( $notification['email'] ?? null ) ) { $recipients[] = $notification['email']; } } } $title = is_string( $settings['form_title'] ?? null ) && '' !== $settings['form_title'] ? $settings['form_title'] : (string) $row['post_title']; $out[] = self::form( 'wpforms', (string) $row['ID'], $title, $recipients, $admin ); } return $out; } /** * Formidable: the email actions of each form. * * @return array> */ public static function formidable( string $admin ): array { global $wpdb; $table = $wpdb->prefix . 'frm_forms'; if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table ) ) ) !== $table ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- schema probe. return array(); } $forms = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- a read on demand; bounded. $wpdb->prepare( "SELECT id, name FROM `{$table}` WHERE status = 'published' AND (parent_form_id = 0 OR parent_form_id IS NULL) ORDER BY id ASC LIMIT %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- the prefix plus a constant. self::MAX_FORMS + 1 ), ARRAY_A ); $actions = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- a read on demand; bounded. $wpdb->prepare( "SELECT menu_order, post_content FROM {$wpdb->posts} WHERE post_type = 'frm_form_actions' AND post_excerpt = 'email' AND post_status = 'publish' LIMIT %d", self::MAX_FORMS * 5 ), ARRAY_A ); $by_form = array(); foreach ( is_array( $actions ) ? $actions : array() as $action ) { $settings = json_decode( (string) $action['post_content'], true ); if ( is_array( $settings ) && is_string( $settings['email_to'] ?? null ) ) { $by_form[ (int) $action['menu_order'] ][] = $settings['email_to']; } } $out = array(); foreach ( is_array( $forms ) ? $forms : array() as $form ) { $id = (int) $form['id']; $out[] = self::form( 'formidable', (string) $id, (string) $form['name'], $by_form[ $id ] ?? array(), $admin ); } return $out; } /** * Gravity Forms: every active notification's "send to". * * @return array> */ public static function gravity_forms( string $admin ): array { global $wpdb; $forms_table = $wpdb->prefix . 'gf_form'; $meta_table = $wpdb->prefix . 'gf_form_meta'; foreach ( array( $forms_table, $meta_table ) as $table ) { if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table ) ) ) !== $table ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- schema probe. return array(); } } $rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- a read on demand; bounded. $wpdb->prepare( "SELECT f.id, f.title, m.notifications FROM `{$forms_table}` f LEFT JOIN `{$meta_table}` m ON m.form_id = f.id WHERE f.is_active = 1 AND f.is_trash = 0 ORDER BY f.id ASC LIMIT %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- the prefix plus constants. self::MAX_FORMS + 1 ), ARRAY_A ); $out = array(); foreach ( is_array( $rows ) ? $rows : array() as $row ) { $notifications = json_decode( (string) ( $row['notifications'] ?? '' ), true ); $recipients = array(); foreach ( is_array( $notifications ) ? $notifications : array() as $notification ) { if ( ! is_array( $notification ) || false === ( $notification['isActive'] ?? true ) ) { continue; } if ( is_string( $notification['to'] ?? null ) ) { // A "field" recipient is the id of a field the visitor fills in. $recipients[] = 'field' === ( $notification['toType'] ?? '' ) ? '{field:' . $notification['to'] . '}' : $notification['to']; } } $out[] = self::form( 'gravity-forms', (string) $row['id'], (string) $row['title'], $recipients, $admin ); } return $out; } /** * Published posts of one form post type, bounded. * * @return array> */ private static function posts_of( string $type ): array { global $wpdb; $rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- a read on demand; bounded. $wpdb->prepare( "SELECT ID, post_title, post_content FROM {$wpdb->posts} WHERE post_type = %s AND post_status = 'publish' ORDER BY ID ASC LIMIT %d", $type, self::MAX_FORMS + 1 ), ARRAY_A ); return is_array( $rows ) ? $rows : array(); } /** * One form as reported: recipients split on commas, admin placeholders * resolved, empties and duplicates dropped, bounded. * * @param string[] $saved Recipient strings as the plugin saved them. * @return array */ public static function form( string $plugin, string $id, string $title, array $saved, string $admin ): array { $recipients = array(); foreach ( $saved as $entry ) { foreach ( explode( ',', $entry ) as $one ) { $one = trim( $one ); if ( in_array( strtolower( $one ), self::ADMIN_PLACEHOLDERS, true ) && '' !== $admin ) { $one = $admin; } $one = sanitize_text_field( $one ); if ( '' !== $one && ! in_array( $one, $recipients, true ) ) { $recipients[] = substr( $one, 0, 200 ); } } } return array( 'plugin' => $plugin, 'form_id' => substr( $id, 0, 40 ), 'title' => substr( sanitize_text_field( $title ), 0, 200 ), 'recipients' => array_slice( $recipients, 0, self::MAX_RECIPIENTS ), ); } }