$type ) { add_filter( 'pre_option_' . $name, array( __CLASS__, 'pre_option' ), 10, 2 ); if ( in_array( $type, array( 'text', 'number' ), true ) ) { add_filter( 'option_' . $name, array( __CLASS__, 'expand_option' ), 10, 2 ); } } } /** Wire hooks. */ public static function register() { add_action( 'admin_menu', array( __CLASS__, 'menu' ), 20 ); add_action( 'network_admin_menu', array( __CLASS__, 'network_menu' ), 20 ); add_action( 'network_admin_edit_mapp_save_network_settings', array( __CLASS__, 'save_network' ) ); add_action( 'admin_post_mapp_save_settings', array( __CLASS__, 'save' ) ); add_action( 'admin_notices', array( __CLASS__, 'key_notice' ) ); } /** Submenu under Applications. */ public static function menu() { add_submenu_page( 'edit.php?post_type=mortgage_application', __( 'Form & CRM', '1003-mortgage-application' ), __( 'Form & CRM', '1003-mortgage-application' ), 'manage_options', self::SLUG, array( __CLASS__, 'render' ) ); } /** Under the 2.6 network menu (MTG Application). */ public static function network_menu() { add_submenu_page( 'ma_setting', __( 'Form & CRM', '1003-mortgage-application' ), __( 'Form & CRM', '1003-mortgage-application' ), 'manage_network_options', self::SLUG, array( __CLASS__, 'render_network' ) ); } /** @return bool Whether this site reads the network's values. */ public static function uses_network() { return self::network_saved() && ! is_network_admin() && 'no' !== get_option( self::USE_NETWORK, 'yes' ); } /** @return bool Whether the network page has been saved (until then every site uses its own values). */ public static function network_saved() { return is_multisite() && (bool) get_site_option( 'mapp_network_settings_saved', false ); } /** * pre_option_*: a site on network settings gets the network value. * * @param mixed $pre Short-circuit value. * @param string $option Option name. * @return mixed */ public static function pre_option( $pre, $option ) { if ( false !== $pre || self::$raw || ! self::uses_network() ) { return $pre; } $value = get_site_option( $option, null ); if ( null === $value || false === $value ) { return $pre; } return self::expand( $option, $value ); } /** * option_*: run shortcodes in a site's own value. * * @param mixed $value Value. * @param string $option Option name. * @return mixed */ public static function expand_option( $value, $option ) { return self::$raw ? $value : self::expand( $option, $value ); } /** * Shortcodes in text and number values run on the site reading them, so * one network value such as [site_nmls] can differ per site. * * @param string $option Option name. * @param mixed $value Value. * @return mixed */ public static function expand( $option, $value ) { $types = self::types(); if ( ! is_string( $value ) || false === strpos( $value, '[' ) || ! isset( $types[ $option ] ) || ! in_array( $types[ $option ], array( 'text', 'number' ), true ) ) { return $value; } // A shortcode this site does not have shows blank, not as raw [tag] text. $out = trim( preg_replace( '/\[\/?[a-z0-9_\-]+(?:\s[^\]]*)?\]/i', '', wp_strip_all_tags( do_shortcode( $value ) ) ) ); return 'number' === $types[ $option ] ? ( is_numeric( $out ) ? $out : '' ) : $out; } /** * @param string $value Value as typed. * @return string[] Shortcode names in it that this site does not register. */ public static function unknown_shortcodes( $value ) { preg_match_all( '/\[([a-z0-9_\-]+)(?:\s[^\]]*)?\]/i', (string) $value, $m ); return array_values( array_unique( array_filter( $m[1], function ( $tag ) { return ! shortcode_exists( $tag ); } ) ) ); } /** @return array option => type, for every value the network can set. */ public static function types() { static $out = null; if ( null === $out ) { $out = array(); foreach ( self::fields() as $fields ) { foreach ( $fields as $name => $spec ) { $out[ $name ] = $spec[1]; } } // List values the Pro module stores (webhook events). $out = (array) apply_filters( 'mapp_form_crm_types', $out ); } return $out; } /** * A posted value, cleaned for its type. Text and number fields keep * shortcodes as typed. * * @param array $spec Field spec. * @param mixed $raw Posted value. * @return string */ private static function clean( $spec, $raw ) { $raw = is_string( $raw ) ? trim( $raw ) : ''; switch ( $spec[1] ) { case 'yesno': return 'yes' === $raw ? 'yes' : 'no'; case 'number': if ( false !== strpos( $raw, '[' ) ) { return sanitize_text_field( $raw ); } return '' === $raw ? '' : (string) (float) $raw; case 'select': return array_key_exists( $raw, $spec[3] ) ? $raw : $spec[2]; default: return sanitize_text_field( $raw ); } } /** * Text, checkbox and select fields: option => [ label, type, default, choices|help ]. * * @return array */ public static function fields() { // The Pro module adds the GHL and Webhooks sections (before Security). return (array) apply_filters( 'mapp_form_crm_fields', array( __( 'Company', '1003-mortgage-application' ) => array( 'mapp_company_name' => array( __( 'Company name (shown in consent and footer)', '1003-mortgage-application' ), 'text', '', __( 'Blank: the site name, so on a network each site shows its own.', '1003-mortgage-application' ) ), 'mapp_company_nmls' => array( __( 'Company NMLS #', '1003-mortgage-application' ), 'text', '' ), 'mapp_company_phone' => array( __( 'Company phone', '1003-mortgage-application' ), 'text', '' ), ), __( 'Alerts and reminders', '1003-mortgage-application' ) => array( 'mapp_alert_partial' => array( __( 'Email the LO when contact details are saved (before submit)', '1003-mortgage-application' ), 'yesno', 'yes' ), 'mapp_notify_site_too' => array( __( 'Also copy the site recipients on every LO alert', '1003-mortgage-application' ), 'yesno', 'no' ), 'mapp_reminders' => array( __( 'Remind borrowers who did not finish', '1003-mortgage-application' ), 'yesno', 'yes', __( 'Short application: 24 and 72 hours after they save their contact details (text on the Settings page). Full 1003: 2 and 5 days after they last worked on it (built-in text). Each has a fresh link.', '1003-mortgage-application' ) ), 'mapp_borrower_numbers' => array( __( 'Email borrowers their numbers and a resume link after the contact step', '1003-mortgage-application' ), 'yesno', 'yes' ), ), __( 'Numbers screen', '1003-mortgage-application' ) => array( 'mapp_reveal_payment' => array( __( 'Show an estimated principal and interest payment', '1003-mortgage-application' ), 'yesno', 'no', __( 'A payment amount in an advertisement can trigger Regulation Z disclosures. Have compliance review before turning this on.', '1003-mortgage-application' ) ), 'mapp_reveal_rate' => array( __( 'Rate used for the estimate (%)', '1003-mortgage-application' ), 'number', '' ), 'mapp_reveal_cash_ltv' => array( __( 'Maximum loan-to-value for cash-out estimates (%)', '1003-mortgage-application' ), 'number', '80' ), ), __( 'Security', '1003-mortgage-application' ) => array( 'mapp_resume_days' => array( __( 'Resume links stay valid for (days)', '1003-mortgage-application' ), 'number', '30' ), // Same option as the 2.x "Delete File After" setting, so saved values carry over. 'mortgage_ma_submissions_deleted_file' => array( __( 'Delete uploaded documents after (days)', '1003-mortgage-application' ), 'number', '', __( 'Documents borrowers upload are deleted this many days after upload, and each deletion is logged. Blank or 0 keeps them.', '1003-mortgage-application' ) ), 'mapp_turnstile_site_key' => array( __( 'Cloudflare Turnstile site key (optional)', '1003-mortgage-application' ), 'text', '' ), 'mapp_turnstile_secret' => array( __( 'Cloudflare Turnstile secret', '1003-mortgage-application' ), 'secret', '' ), ), ) ); } /** Page. */ public static function render() { if ( ! current_user_can( 'manage_options' ) ) { return; } echo '
' . esc_html__( 'Text and number fields accept shortcodes. They run on the site that uses the value, so one network value such as [site_nmls] can show something different on each site.', '1003-mortgage-application' ) . '
'; foreach ( self::fields() as $section => $fields ) { echo '| ';
switch ( $spec[1] ) {
case 'yesno':
echo '';
break;
case 'select':
echo '';
break;
case 'secret':
echo '';
break;
default:
// Text, so a number field can hold a shortcode.
echo '';
if ( ! $network && is_string( $value ) && false !== strpos( $value, '[' ) ) {
self::$raw = false;
echo ' ' . esc_html__( 'On this site:', '1003-mortgage-application' ) . ' ' . esc_html( sprintf( __( '[%s] is not a shortcode on this site, so it shows blank.', '1003-mortgage-application' ), $tag ) ) . ' '; } self::$raw = true; } } if ( isset( $spec[3] ) && is_string( $spec[3] ) ) { echo '' . esc_html( $spec[3] ) . ' '; } echo ' |
' . esc_html__( '1003 Mortgage Application: SSNs and documents are encrypted with a key stored in the database. For stronger protection, define MAPP_ENCRYPTION_KEY in wp-config.php (instructions in the readme).', '1003-mortgage-application' ) . '