1, 'cart_button_position' => 'after_totals', // after_totals | before_checkout | after_checkout | top | manual 'cart_button_text' => '', 'cart_button_align' => 'left', // left | center | right 'style_cart' => self::default_style(), 'text_overrides' => array(), // Behaviour. 'min_cart_total' => 0, 'access_mode' => 'logged_in', // logged_in | roles 'allowed_roles' => array( 'customer', 'subscriber', 'administrator', 'shop_manager' ), 'validity_days' => 15, 'quote_prefix' => 'QT', 'customer_message_enabled' => 1, // E-mails & contact. 'notification_email' => '', 'from_email' => '', 'contact_email' => '', 'contact_url' => '', // Company block (PDF header/footer). 'company_name' => '', 'company_address' => '', 'company_postcode' => '', 'company_city' => '', 'company_phone' => '', 'company_email' => '', 'company_registration' => '', 'company_vat' => '', 'pdf_logo_id' => 0, 'pdf_footer_note' => '', // Integrations. // Housekeeping. 'remove_data_on_uninstall' => 0, ); } /** * Default button style. * * @return array */ public static function default_style() { return array( 'inherit' => 0, 'variant' => 'filled', // filled | outline | link 'bg' => '#1e293b', 'text' => '#ffffff', 'border' => '', 'hover_bg' => '#0f172a', 'hover_text' => '#ffffff', 'radius' => 6, 'size' => 'md', // sm | md | lg 'full_width' => 0, 'icon' => 1, ); } /** * Sanitize one style array. * * @param mixed $value Raw. * @param array $current Current value. * @return array */ private static function sanitize_style( $value, $current ) { $out = wp_parse_args( is_array( $current ) ? $current : array(), self::default_style() ); if ( ! is_array( $value ) ) { return $out; } foreach ( array( 'bg', 'text', 'border', 'hover_bg', 'hover_text' ) as $k ) { if ( array_key_exists( $k, $value ) ) { $c = sanitize_hex_color( (string) $value[ $k ] ); $out[ $k ] = $c ? $c : ''; } } if ( isset( $value['variant'] ) ) { $out['variant'] = in_array( $value['variant'], array( 'filled', 'outline', 'link' ), true ) ? $value['variant'] : 'filled'; } if ( isset( $value['size'] ) ) { $out['size'] = in_array( $value['size'], array( 'sm', 'md', 'lg' ), true ) ? $value['size'] : 'md'; } if ( isset( $value['radius'] ) ) { $out['radius'] = max( 0, min( 60, absint( $value['radius'] ) ) ); } foreach ( array( 'inherit', 'full_width', 'icon' ) as $k ) { if ( array_key_exists( $k, $value ) ) { $out[ $k ] = ! empty( $value[ $k ] ) ? 1 : 0; } } return $out; } /** * Get all settings merged with defaults (and runtime fallbacks). * * @return array */ public static function all() { if ( null !== self::$cache ) { return self::$cache; } $saved = get_option( self::OPTION, array() ); if ( ! is_array( $saved ) ) { $saved = array(); } $all = wp_parse_args( $saved, self::defaults() ); $all['style_cart'] = wp_parse_args( is_array( $all['style_cart'] ) ? $all['style_cart'] : array(), self::default_style() ); self::$cache = $all; return self::$cache; } /** * Get one setting (raw stored value or default). * * @param string $key Setting key. * @return mixed */ public static function get( $key ) { $all = self::all(); return isset( $all[ $key ] ) ? $all[ $key ] : null; } /** * Resolved values with sensible fallbacks (site name, WC store address, admin e-mail). * * @return array */ public static function resolved() { $s = self::all(); $admin_email = get_option( 'admin_email' ); $wc_from_email = get_option( 'woocommerce_email_from_address', $admin_email ); $s['notification_email'] = $s['notification_email'] ? $s['notification_email'] : $admin_email; $s['from_email'] = $s['from_email'] ? $s['from_email'] : ( $wc_from_email ? $wc_from_email : $admin_email ); $s['contact_email'] = $s['contact_email'] ? $s['contact_email'] : $admin_email; $s['company_name'] = $s['company_name'] ? $s['company_name'] : get_bloginfo( 'name' ); $s['company_address'] = $s['company_address'] ? $s['company_address'] : get_option( 'woocommerce_store_address', '' ); $s['company_postcode'] = $s['company_postcode'] ? $s['company_postcode'] : get_option( 'woocommerce_store_postcode', '' ); $s['company_city'] = $s['company_city'] ? $s['company_city'] : get_option( 'woocommerce_store_city', '' ); $s['company_email'] = $s['company_email'] ? $s['company_email'] : $s['contact_email']; $s['pdf_footer_note'] = $s['pdf_footer_note'] ? $s['pdf_footer_note'] : __( 'This document is a quote, not an invoice. Keep it for your records.', 'nuxwp-devis' ); /** * Filter the resolved settings. * * @param array $s Settings. */ return apply_filters( 'devisqr_settings', $s ); } /** * Save a (partial) settings array after sanitization. * * @param array $input Raw input. * @return array|WP_Error Saved settings or error. */ public static function save( $input ) { $current = self::all(); $defaults = self::defaults(); $clean = $current; $errors = array(); foreach ( $input as $key => $value ) { if ( ! array_key_exists( $key, $defaults ) ) { continue; } switch ( $key ) { case 'cart_button_position': $clean[ $key ] = in_array( $value, array( 'after_totals', 'before_checkout', 'after_checkout', 'top', 'manual' ), true ) ? $value : 'after_totals'; break; case 'cart_button_align': $clean[ $key ] = in_array( $value, array( 'left', 'center', 'right' ), true ) ? $value : 'left'; break; case 'style_cart': $clean[ $key ] = self::sanitize_style( $value, $current[ $key ] ); break; case 'text_overrides': $text_errors = array(); $clean[ $key ] = DevisQR_Texts::sanitize_overrides( $value, $text_errors ); $errors = array_merge( $errors, $text_errors ); break; case 'cart_button_enabled': case 'customer_message_enabled': case 'remove_data_on_uninstall': $clean[ $key ] = ! empty( $value ) ? 1 : 0; break; case 'min_cart_total': $clean[ $key ] = max( 0, (float) $value ); break; case 'validity_days': $clean[ $key ] = max( 1, min( 365, absint( $value ) ) ); break; case 'pdf_logo_id': $clean[ $key ] = absint( $value ); break; case 'access_mode': $clean[ $key ] = in_array( $value, array( 'logged_in', 'roles' ), true ) ? $value : 'logged_in'; break; case 'allowed_roles': $roles = array(); $known = array_keys( wp_roles()->roles ); foreach ( (array) $value as $role ) { $role = sanitize_key( $role ); if ( in_array( $role, $known, true ) ) { $roles[] = $role; } } $clean[ $key ] = $roles; break; case 'quote_prefix': $prefix = strtoupper( preg_replace( '/[^A-Za-z0-9]/', '', (string) $value ) ); $clean[ $key ] = $prefix ? substr( $prefix, 0, 6 ) : 'QT'; break; case 'notification_email': $value = trim( (string) $value ); if ( '' === $value ) { $clean[ $key ] = ''; break; } $valid = array(); foreach ( array_map( 'trim', explode( ',', $value ) ) as $email ) { if ( is_email( $email ) ) { $valid[] = sanitize_email( $email ); } elseif ( '' !== $email ) { $errors[] = __( 'One of the notification e-mail addresses is invalid.', 'nuxwp-devis' ); } } $clean[ $key ] = implode( ', ', $valid ); break; case 'from_email': case 'contact_email': case 'company_email': $value = trim( (string) $value ); if ( '' !== $value && ! is_email( $value ) ) { $errors[] = __( 'An e-mail address is invalid.', 'nuxwp-devis' ); break; } $clean[ $key ] = sanitize_email( $value ); break; case 'contact_url': $clean[ $key ] = esc_url_raw( trim( (string) $value ) ); break; case 'pdf_footer_note': $clean[ $key ] = sanitize_textarea_field( $value ); break; default: $clean[ $key ] = sanitize_text_field( $value ); } } if ( ! empty( $errors ) ) { return new WP_Error( 'devisqr_invalid_settings', implode( ' ', array_unique( $errors ) ), array( 'status' => 400 ) ); } update_option( self::OPTION, $clean, false ); self::$cache = null; // Branding stamp: stored PDFs older than this are regenerated on download (logo, company details, texts). update_option( 'devisqr_branding_stamp', time(), false ); return self::all(); } /** * My Account endpoint slug. * * @return string */ public static function account_endpoint() { /** * Filter the My Account endpoint slug used for the quotes tab. * * @param string $slug Default 'quotes'. */ $slug = apply_filters( 'devisqr_account_endpoint', 'quotes' ); return sanitize_title( $slug ) ? sanitize_title( $slug ) : 'quotes'; } /** * Roles that may request a quote (resolved). * * @return array|null Null when every logged-in user may request. */ public static function allowed_roles() { $s = self::all(); if ( 'roles' !== $s['access_mode'] ) { return null; } /** * Filter the roles allowed to request a quote. * * @param array $roles Role slugs. */ return apply_filters( 'devisqr_allowed_roles', (array) $s['allowed_roles'] ); } }