wp_generate_uuid4(), 'slug' => 'contact', 'slug_locked' => false, 'published' => false, 'email' => '', 'photo_id' => 0, 'logo_id' => 0, 'accent' => '#c3f3d7', 'ink' => '#142a35', 'revision' => 0, 'updated' => gmdate( 'c' ) ) ); } public static function clean( array $input, array $old ): array { $new = $old; foreach ( self::TEXT_FIELDS as $key ) { $value = $input[ $key ] ?? ''; if ( ! is_string( $value ) || strlen( $value ) > 4000 ) { throw new \InvalidArgumentException( esc_html__( 'A text field is invalid or too long.', 'cardfaro' ) ); } $new[ $key ] = sanitize_textarea_field( $value ); } foreach ( self::URL_FIELDS as $key ) { $value = $input[ $key ] ?? ''; if ( ! is_string( $value ) || strlen( $value ) > 2000 || ( '' !== $value && ! wp_http_validate_url( $value ) ) ) { throw new \InvalidArgumentException( esc_html__( 'Enter a valid public HTTP or HTTPS URL.', 'cardfaro' ) ); } $new[ $key ] = esc_url_raw( $value, array( 'http', 'https' ) ); } $email = $input['email'] ?? ''; if ( ! is_string( $email ) || strlen( $email ) > 254 || ( '' !== $email && ! is_email( $email ) ) ) { throw new \InvalidArgumentException( esc_html__( 'Enter a valid email address.', 'cardfaro' ) ); } $new['email'] = sanitize_email( $email ); foreach ( array( 'accent', 'ink' ) as $key ) { $value = $input[ $key ] ?? ''; if ( ! is_string( $value ) || ! preg_match( '/^#[0-9a-fA-F]{6}$/D', $value ) ) { throw new \InvalidArgumentException( esc_html__( 'Enter a six-digit hexadecimal color.', 'cardfaro' ) ); } $new[ $key ] = strtolower( $value ); } foreach ( array( 'photo_id', 'logo_id' ) as $key ) { $value = $input[ $key ] ?? '0'; if ( ! is_string( $value ) || ! ctype_digit( $value ) ) { throw new \InvalidArgumentException( esc_html__( 'Invalid media selection.', 'cardfaro' ) ); } $new[ $key ] = (int) $value; if ( $new[ $key ] && ( ! wp_attachment_is_image( $new[ $key ] ) || ! current_user_can( 'edit_post', $new[ $key ] ) ) ) { throw new \InvalidArgumentException( esc_html__( 'Select an image you are allowed to edit.', 'cardfaro' ) ); } } if ( ! $old['slug_locked'] ) { $slug = $input['slug'] ?? ''; if ( ! is_string( $slug ) || ! preg_match( '/^[a-z0-9]+(?:-[a-z0-9]+)*$/D', $slug ) || strlen( $slug ) > 80 ) { throw new \InvalidArgumentException( esc_html__( 'The URL name must contain lowercase letters, numbers and single hyphens.', 'cardfaro' ) ); } $new['slug'] = $slug; } $new['published'] = ( $input['published'] ?? '' ) === '1'; if ( $new['published'] && '' === trim( $new['first'] . $new['last'] . $new['organization'] ) ) { throw new \InvalidArgumentException( esc_html__( 'Enter a name or organization before publishing.', 'cardfaro' ) ); } $new['slug_locked'] = $old['slug_locked'] || $new['published']; $new['revision'] = $old['revision'] + 1; $new['updated'] = gmdate( 'c' ); return $new; } public static function name( array $record ): string { $name = trim( $record['first'] . ' ' . $record['last'] ); return '' !== $name ? $name : ( $record['organization'] ?: __( 'Contact', 'cardfaro' ) ); } public static function language( string $query, string $header ): string { if ( in_array( $query, array( 'de', 'en' ), true ) ) { return $query; } $best = 'en'; $score = -1; foreach ( explode( ',', strtolower( $header ) ) as $part ) { if ( preg_match( '/^\s*(de|en)(?:-[a-z0-9-]+)?\s*(?:;\s*q=(0(?:\.\d+)?|1(?:\.0+)?))?\s*$/', $part, $match ) ) { $q = isset( $match[2] ) ? (float) $match[2] : 1; if ( $q > 0 && $q > $score ) { $best = $match[1]; $score = $q; } } } return $best; } private static function vescape( string $value ): string { return str_replace( array( '\\', "\r\n", "\r", "\n", ';', ',' ), array( '\\\\', '\\n', '\\n', '\\n', '\\;', '\\,' ), $value ); } private static function fold( string $line ): string { $output = ''; $part = ''; $length = 0; foreach ( preg_split( '//u', $line, -1, PREG_SPLIT_NO_EMPTY ) as $char ) { $bytes = strlen( $char ); if ( $length + $bytes > 75 ) { $output .= $part . "\r\n"; $part = ' '; $length = 1; } $part .= $char; $length += $bytes; } return $output . $part; } public static function vcard( array $record ): string { $escape = array( self::class, 'vescape' ); $lines = array( 'BEGIN:VCARD', 'VERSION:3.0', 'N:' . implode( ';', array_map( $escape, array( $record['last'], $record['first'], '', '', '' ) ) ), 'FN:' . self::vescape( self::name( $record ) ), 'UID:urn:uuid:' . $record['id'] ); foreach ( array( 'organization' => 'ORG', 'role' => 'TITLE', 'email' => 'EMAIL;TYPE=INTERNET', 'mobile' => 'TEL;TYPE=CELL', 'phone' => 'TEL;TYPE=WORK,VOICE', 'website' => 'URL' ) as $key => $property ) { if ( '' !== $record[ $key ] ) { $lines[] = $property . ':' . self::vescape( $record[ $key ] ); } } if ( implode( '', array_intersect_key( $record, array_flip( array( 'street', 'city', 'region', 'postal', 'country' ) ) ) ) !== '' ) { $lines[] = 'ADR;TYPE=WORK:' . implode( ';', array_map( $escape, array( '', '', $record['street'], $record['city'], $record['region'], $record['postal'], $record['country'] ) ) ); } if ( '' !== $record['note'] ) { $lines[] = 'NOTE:' . self::vescape( $record['note'] ); } $lines[] = 'REV:' . gmdate( 'Ymd\THis\Z', strtotime( $record['updated'] ) ); $lines[] = 'END:VCARD'; return implode( "\r\n", array_map( array( self::class, 'fold' ), $lines ) ) . "\r\n"; } }