self::MAX_NAME_LENGTH || 1 !== preg_match( self::NAME_PATTERN, $name ) ) { return false; } // Names beginning with "xml" (any case) are reserved by the XML specification. return 0 !== strncasecmp( $name, 'xml', 3 ); } /** * The prefix of a qualified name, or null for a plain name. * * @param string $name Name. * @return string|null */ public static function prefix_of( string $name ) { $pos = strpos( $name, ':' ); return false === $pos ? null : substr( $name, 0, $pos ); } /** * Whether a namespace prefix is acceptable: a plain name, not reserved ("xml*"). * * @param mixed $prefix Prefix. * @return bool */ public static function is_valid_prefix( $prefix ): bool { return is_string( $prefix ) && self::is_valid_name( $prefix ) && strlen( $prefix ) <= 32; } /** * Whether an XML attribute name is acceptable: a plain or qualified name, or one of the * three attributes the "xml" prefix is reserved for. Namespace declarations (xmlns...) * are never attribute names here; they come from the namespaces setting. * * @param mixed $name Candidate. * @param bool $allow_prefix Accept "prefix:local". * @return bool */ public static function is_valid_attribute_name( $name, bool $allow_prefix = true ): bool { if ( is_string( $name ) && in_array( $name, array( 'xml:lang', 'xml:space', 'xml:base' ), true ) ) { return true; } return self::is_valid_name( $name, $allow_prefix ); } /** * Splits a path into segments without validating them. * * @param string $path Output path. * @return string[] */ public static function segments( string $path ): array { return explode( '/', trim( $path ) ); } /** * Validates a path and returns human-readable errors. * * @param mixed $path Output path. * @param bool $allow_prefix Accept namespace-qualified segments ("prefix:name"). * @return string[] Empty when valid. */ public static function validate( $path, bool $allow_prefix = false ): array { if ( ! is_string( $path ) || '' === trim( $path ) ) { return array( __( 'An output path is required.', 'feedweaver' ) ); } $segments = self::segments( $path ); if ( count( $segments ) > self::MAX_DEPTH ) { /* translators: %d: maximum nesting depth. */ return array( sprintf( __( 'Output paths may be at most %d levels deep.', 'feedweaver' ), self::MAX_DEPTH ) ); } $errors = array(); foreach ( $segments as $segment ) { if ( ! self::is_valid_name( $segment, $allow_prefix ) ) { $errors[] = sprintf( /* translators: %s: the invalid path segment. */ __( '"%s" is not a valid name. Use letters, numbers, hyphens and underscores, start with a letter or underscore, and do not start with "xml". A namespace prefix and a colon (media:thumbnail) is also allowed once the prefix is declared.', 'feedweaver' ), $segment ); break; } } return $errors; } }