(.+)<\/em>/U',
+ function ( $matches ) {
+ return $matches[1] . '_' . $matches[2] . '_';
+ },
+ $content
+ );
+ }
+
+ return $content;
+ }
+
+ /**
+ * Handles formatting of the parameter description.
+ *
+ * @param string $text The parameter description.
+ * @return string
+ */
+ public static function format_param_description( $text ) {
+ // Undo parser's Markdown conversion of '*' to `` and ` `.
+ // In pretty much all cases, the docs mean literal '*' and never emphasis.
+ $text = str_replace( array( '', ' ' ), '*', $text );
+
+ // Undo parser's Markdown conversion of '__' to `` and ` `.
+ $text = str_replace( array( '', ' ' ), '__', $text );
+
+ // Encode all htmlentities (but don't double-encode).
+ $text = htmlentities( $text, ENT_COMPAT | ENT_HTML401, 'UTF-8', false );
+
+ // Simple allowable tags that should get unencoded.
+ // Note: This precludes them from being able to be used in an encoded fashion
+ // within a parameter description.
+ $allowable_tags = array( 'code', 'br' );
+ foreach ( $allowable_tags as $tag ) {
+ $text = str_replace( array( "<{$tag}>", "</{$tag}>" ), array( "<{$tag}>", "{$tag}>" ), $text );
+ }
+
+ // Convert any @link or @see to actual link.
+ $text = self::make_doclink_clickable( $text );
+
+ return apply_filters( 'devhub-format-description', $text );
+ }
+
+ /**
+ * Automatically detects inline references to parsed resources and links to them.
+ *
+ * Examples:
+ * - Functions: get_the_ID()
+ * - Classes: WP_Query
+ * - Methods: WP_Query::is_single()
+ *
+ * Note: currently there is not a reliable way to infer references to hooks. Recommend
+ * using the {@}see 'hook_name'} notation as used in the inline docs.
+ *
+ * @param string $text The text.
+ * @return string
+ */
+ public static function autolink_references( $text ) {
+ // Temporary: Don't do anything if the text is a hash notation string.
+ if ( $text && '{' === $text[0] ) {
+ return $text;
+ }
+
+ $r = '';
+ $textarr = preg_split( '/(<[^<>]+>)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); // split out HTML tags
+ $nested_code_pre = 0; // Keep track of how many levels link is nested inside or
+ $nested_in_link = 0; // Keep track of whether this item is already inside a link.
+
+ foreach ( $textarr as $piece ) {
+
+ if (
+ preg_match( '|^]|i', $piece ) ||
+ preg_match( '|^]|i', $piece ) ||
+ preg_match( '|^' === strtolower( $piece ) ||
+ '' === strtolower( $piece )
+ )
+ ) {
+ $nested_code_pre--;
+ }
+
+ // Avoid creating links inside of other links.
+ if ( preg_match( '|^]|i', $piece ) ) {
+ $nested_in_link++;
+ } elseif ( $nested_in_link && ( ' ' === strtolower( $piece ) ) ) {
+ $nested_in_link--;
+ }
+
+ if (
+ $nested_code_pre ||
+ $nested_in_link ||
+ empty( $piece ) ||
+ ( $piece[0] === '<' && ! preg_match( '|^<\s*[\w]{1,20}+://|', $piece ) )
+ ) {
+ $r .= $piece;
+ continue;
+ }
+
+ // Long strings might contain expensive edge cases ...
+ if ( 10000 < strlen( $piece ) ) {
+ // ... break it up
+ foreach ( _split_str_by_whitespace( $piece, 2100 ) as $chunk ) { // 2100: Extra room for scheme and leading and trailing paretheses
+ if ( 2101 < strlen( $chunk ) ) {
+ $r .= $chunk; // Too big, no whitespace: bail.
+ } else {
+ $r .= make_clickable( $chunk );
+ }
+ }
+ } else {
+ /*
+ * Everthing outside of this conditional block was copied from core's
+ *`make_clickable()`.
+ */
+
+ $content = " $piece "; // Pad with whitespace to simplify the regexes
+
+ // Only if the text contains something that might be a function.
+ if ( str_contains( $content, '()' ) || str_contains( $content, '::' ) || str_contains( $content, '->' ) ) {
+
+ // Detect references to class methods, e.g. WP_Query::query()
+ // or functions, e.g. register_post_type().
+ $content = preg_replace_callback(
+ '~
+ (?!<.*?) # Non-capturing check to ensure not matching what looks like the inside of an HTML tag.
+ (?P
+ (?P
+ (\w+) # Class Name
+ (::|->|->) # Object reference
+ (\w+) # Method
+ (?P\(\)| ) # () or whitespace to terminate.
+ )
+ |
+ (?P\w+\(\)) # Functions must always end in ().
+ )
+ (?![^<>]*?>) # Non-capturing check to ensure not matching what looks like the inside of an HTML tag.
+ ~x',
+ function( $matches ) {
+ $name = rtrim( $matches['name'], '() ' );
+ $after = ( '()' === $matches['after'] ? '' : ' ' );
+
+ // Reference to a class method.
+ if ( $matches['class'] ) {
+ $name = str_replace( array( '->', '->' ), '::', $name );
+
+ // Only link actually parsed methods.
+ $args = array(
+ 'post_type' => 'wp-parser-method',
+ 'name' => $name,
+ 'posts_per_page' => 1,
+ );
+
+ $query = new WP_Query( $args );
+
+ if ( $query->have_posts() ) {
+ $post = $query->posts[0];
+
+ return sprintf(
+ '%s ' . $after,
+ get_permalink( $post->ID ),
+ $name . '()'
+ );
+ }
+
+ // Reference to a function.
+ } else {
+ // Only link actually parsed functions.
+ $args = array(
+ 'post_type' => 'wp-parser-function',
+ 'name' => $name,
+ 'posts_per_page' => 1,
+ );
+
+ $query = new WP_Query( $args );
+
+ if ( $query->have_posts() ) {
+ $post = $query->posts[0];
+
+ return sprintf(
+ '%s ' . $after,
+ get_permalink( $post->ID ),
+ $name . '()'
+ );
+ }
+ }
+
+ // It's not a reference to an actual thing, so restore original text.
+ return $matches[0];
+ },
+ $content
+ );
+
+ }
+
+ // Detect references to classes, e.g. WP_Query
+ $content = preg_replace_callback(
+ // Most class names start with an uppercase letter and have an underscore.
+ // The exceptions are explicitly listed since future classes likely won't violate previous statement.
+ // Requests and Translations, due to their higher likelihood of use as a word and not as an inline class
+ // reference, should be explicitly referenced, e.g. `{@see Requests}`.
+ '~'
+ . '(?))' // Does not appear within a tag
+ . '~',
+ function ( $matches ) {
+ // If match is all caps, it's not a possible class name.
+ // We'll chalk the sole exception, WP, as merely being an abbreviation (the regex won't match it anyhow).
+ if ( strtoupper( $matches[0] ) === $matches[0] ) {
+ return $matches[0];
+ }
+
+ // Only link actually parsed classes.
+ $args = array(
+ 'post_type' => 'wp-parser-class',
+ 'name' => $matches[0],
+ 'posts_per_page' => 1,
+ );
+
+ $query = new WP_Query( $args );
+
+ if ( $query->have_posts() ) {
+ $post = $query->posts[0];
+
+ return sprintf(
+ '%s ',
+ get_permalink( $post->ID ),
+ $matches[0]
+ );
+ }
+
+ // Not a class reference, so put the original reference back in.
+ return $matches[0];
+ },
+ $content
+ );
+
+ // Maybelater: Detect references to hooks, Currently not deemed reliably possible.
+
+ $content = substr( $content, 1, -1 ); // Remove our whitespace padding.
+ $r .= $content;
+
+ } // end else
+
+ } // end foreach
+
+ // Cleanup of accidental links within links
+ return preg_replace( '#(]+?>|>)) ]+?>([^>]+?) #i', "$1$3", $r );
+ }
+
+ /**
+ * Converts simple Markdown-like lists into list markup.
+ *
+ * Necessary in cases like hash param descriptions which don't see Markdown
+ * list processing during parsing.
+ *
+ * Recognizes lists where list items are denoted with an asterisk or dash.
+ * Examples:
+ * - https://developer.wordpress.org/reference/functions/add_menu_page/
+ * - https://developer.wordpress.org/reference/classes/wp_term_query/__construct/
+ * - https://developer.wordpress.org/reference/hooks/password_change_email/
+ * - https://developer.wordpress.org/reference/classes/WP_Query/parse_query/
+ *
+ * Does not handle nesting of lists.
+ *
+ * @param string $text The text to process for lists.
+ * @return string
+ */
+ public static function convert_lists_to_markup( $text ) {
+ // Expand new lines for ease of matching.
+ $text = preg_replace( '! \s*!', " \n", $text );
+
+ // Trim any trailing s on strings.
+ $text = preg_replace( '/ \s*$/s', '', $text );
+
+ // Add line items
+ $text = preg_replace( '!^\s*[*-] (.+?)( )*$!m', '$1 ', $text, -1, $replacements_made );
+
+ if ( ! $replacements_made ) {
+ return $text;
+ }
+
+ // Wrap in a `ul`.
+ $text = substr_replace( $text, '', strpos( $text, ' ' ), 4 ); // First instance
+ $text = substr_replace( $text, ' ', strrpos( $text, '' ), 5 ); // Last instance.
+
+ return $text;
+ }
+
+ /**
+ * Formats the output of params defined using hash notation.
+ *
+ * This is a temporary measure until the parser parses the hash notation
+ * into component elements that the theme could then handle and style
+ * properly.
+ *
+ * Also, as a stopgap this is going to begin as a barebones hack to simply
+ * keep the text looking like one big jumble.
+ *
+ * @param string $text The content for the param.
+ * @return string
+ */
+ public static function fix_param_hash_formatting( $text ) {
+ // Don't do anything if this isn't a hash notation string.
+ if ( ! $text || '{' != $text[0] ) {
+ return $text;
+ }
+
+ $new_text = '';
+ $text = trim( substr( $text, 1, -1 ) );
+ $text = str_replace( '@type', "\n@type", $text );
+
+ $in_list = false;
+ $parts = explode( "\n", $text );
+ foreach ( $parts as $part ) {
+ $part = preg_replace( '/\s+/', ' ', $part );
+ list( $wordtype, $type, $name, $description ) = explode( ' ', $part . ' ', 4 ); // extra spaces ensure we'll always have 4 items.
+ $description = trim( $description );
+
+ $description = apply_filters( 'devhub-format-hash-param-description', $description );
+
+ $skip_closing_li = false;
+
+ // Handle nested hashes.
+ if ( ( $description && '{' === $description[0] ) || '{' === $name ) {
+ $description = ltrim( $description, '{' ) . '';
+ $skip_closing_li = true;
+ } elseif ( '}' === substr( $description, -1 ) ) {
+ $description = substr( $description, 0, -1 ) . " \n";
+ }
+
+ if ( '@type' != $wordtype ) {
+ if ( $in_list ) {
+ $in_list = false;
+ $new_text .= "\n";
+ }
+
+ $new_text .= $part;
+ } else {
+ if ( $in_list ) {
+ $new_text .= '';
+ } else {
+ $new_text .= ' \n";
+ }
+
+ return $new_text;
+ }
+
+ /**
+ * Fix Parsedown bug that introduces unbalanced 'code' tags.
+ *
+ * Under very specific criteria, a bug in the Parsedown package used by the
+ * parser causes backtick-to-code-tag conversions to get mishandled, skipping
+ * conversion of a backtick and causing subsequent backticks to be converted
+ * incorrectly as an open or close 'code' tag (opposite of what it should've
+ * been). See referenced tickets for more details.
+ *
+ * Intended to be a temporary fix until/unless Parsedown is fixed.
+ *
+ * @see https://meta.trac.wordpress.org/ticket/2900
+ * @see https://github.com/erusev/parsedown/pull/515
+ */
+ public static function fix_param_description_parsedown_bug( $text ) {
+ $fixes = array(
+ '/`(.+)/' => '$1
',
+ '/<\/code>(.+)`/' => ' $1
',
+ );
+
+ // Determine if code tags look inverted.
+ $first_start = strpos( $text, '' );
+ $first_end = strpos( $text, '
' );
+ if ( false !== $first_start && false !== $first_end && $first_end < $first_start ) {
+ $fixes[ '~
(.+)~U' ] = ' $1
';
+ }
+
+ $matched = true;
+
+ foreach ( $fixes as $regex => $replace ) {
+ $text = preg_replace( $regex, $replace, $text );
+ }
+
+ return $text;
+ }
+
+ /**
+ * Wraps single-quoted HTML within 'code' tags.
+ *
+ * The HTML should have been denoted with backticks in the original source, in
+ * which case it would have been parsed properly, but committers aren't
+ * always sticklers for documentation formatting.
+ *
+ * @access public
+ *
+ * @param string $text Text.
+ * @return string
+ */
+ public static function fix_param_description_html_as_code( $text ) {
+ if ( false !== strpos( $text, "'<" ) ) {
+ $text = preg_replace( '/\'(<[^\']+>)\'/', '$1
', $text );
+ }
+
+ return $text;
+ }
+
+ /**
+ * Wraps code-like references within 'code' tags.
+ *
+ * Example: https://developer.wordpress.org/reference/classes/wp_term_query/__construct/
+ *
+ * @param string $text Text.
+ * @return string
+ */
+ public static function fix_param_description_quotes_to_code( $text ) {
+ // Don't do anything if this is a hash notation string.
+ if ( ! $text || str_starts_with( $text, '{' ) || str_contains( $text, '' ) ) {
+ return $text;
+ }
+
+ $textarr = preg_split( '/(<[^<>]+>)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); // split out HTML tags
+ $text = '';
+ $within_code = false;
+ foreach ( $textarr as $piece ) {
+ // HTML tags are untouched.
+ if ( str_starts_with( $piece, '<' ) || $within_code ) {
+ $text .= $piece;
+
+ if ( str_starts_with( $piece, '
$1
', $piece, -1 );
+
+ // Quoted strings.
+ $piece = preg_replace( "/('[^' ]*')/", '$1
', $piece, -1 );
+
+ // Replace ###PARAM### too.
+ // Example: http://localhost:8888/reference/hooks/password_change_email/
+ $piece = preg_replace( "/((#{2,})\w+\\2)/", '$1
', $piece );
+
+ $text .= $piece;
+ }
+
+ return $text;
+ }
+
+ /**
+ * Render the php shortcode using the Code Syntax Block syntax.
+ *
+ * This is a workaround for user-submitted code, which used the php shortcode from Syntax Highlighter Evolved.
+ *
+ * @param array|string $attr Shortcode attributes array or empty string.
+ * @param string $content Shortcode content.
+ * @param string $tag Shortcode name.
+ * @return string
+ */
+ public static function do_shortcode_php( $attr, $content, $tag ) {
+ $attr = is_array( $attr ) ? $attr : array();
+ $attr['lang'] = 'php';
+
+ return self::do_shortcode_code( $attr, $content, $tag );
+ }
+
+ /**
+ * Render the js shortcode using the Code Syntax Block syntax.
+ *
+ * This is a workaround for user-submitted code, which used the js shortcode from Syntax Highlighter Evolved.
+ *
+ * @param array|string $attr Shortcode attributes array or empty string.
+ * @param string $content Shortcode content.
+ * @param string $tag Shortcode name.
+ * @return string
+ */
+ public static function do_shortcode_js( $attr, $content, $tag ) {
+ $attr = is_array( $attr ) ? $attr : array();
+ $attr['lang'] = 'js';
+
+ return self::do_shortcode_code( $attr, $content, $tag );
+ }
+
+ /**
+ * Render the css shortcode using the Code Syntax Block syntax.
+ *
+ * This is a new shortcode, but built to mirror the above two, `js` & `php`.
+ *
+ * @param array|string $attr Shortcode attributes array or empty string.
+ * @param string $content Shortcode content.
+ * @param string $tag Shortcode name.
+ * @return string
+ */
+ public static function do_shortcode_css( $attr, $content, $tag ) {
+ $attr = is_array( $attr ) ? $attr : array();
+ $attr['lang'] = 'css';
+
+ return self::do_shortcode_code( $attr, $content, $tag );
+ }
+
+ /**
+ * Render the code shortcode using the Code Syntax Block syntax.
+ *
+ * This is used in the handbooks content.
+ *
+ * @param array|string $attr Shortcode attributes array or empty string.
+ * @param string $content Shortcode content.
+ * @param string $tag Shortcode name.
+ * @return string
+ */
+ public static function do_shortcode_code( $attr, $content, $tag ) {
+ // Use an allowedlist of languages, falling back to PHP.
+ // This should account for all languages used in the handbooks.
+ $lang_list = [ 'js', 'json', 'sh', 'bash', 'html', 'css', 'scss', 'php', 'markdown', 'yaml' ];
+ $lang = in_array( $attr['lang'] ?? '', $lang_list ) ? $attr['lang'] ?? '': 'php';
+
+ $content = self::_trim_code( $content );
+ // Hides numbers if <= 4 lines of code (last line has no linebreak).
+ $show_line_numbers = substr_count( $content, "\n" ) > 3;
+
+ // Shell is flagged with `sh` or `bash` in the handbooks, but Prism uses `shell`.
+ if ( 'sh' === $lang || 'bash' === $lang ) {
+ $lang = 'shell';
+ }
+
+ return do_blocks(
+ sprintf(
+ '%2$s
',
+ $lang,
+ $content,
+ $show_line_numbers ? 'true' : 'false',
+ $show_line_numbers ? 'line-numbers' : ''
+ )
+ );
+ }
+
+ /**
+ * Trim off any extra space, including initial new lines.
+ * Strip out and added by WordPress.
+ *
+ * @param string $content Shortcode content.
+ * @return string
+ */
+ public static function _trim_code( $content ) {
+ $content = preg_replace( '/ /', '', $content );
+ $content = preg_replace( '/<\/p>\s*
/', "\n\n", $content );
+ // Trim everything except leading spaces.
+ $content = trim( $content, "\n\r\t\v\x00" );
+ return $content;
+ }
+} // DevHub_Formatting
+
+DevHub_Formatting::init();
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/handbooks.php b/source/wp-content/themes/wpr-developer-2024/inc/handbooks.php
new file mode 100644
index 000000000..593d6450e
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/handbooks.php
@@ -0,0 +1,289 @@
+is_handbook = function_exists( 'wporg_is_handbook' ) && wporg_is_handbook();
+
+ $current_handbook = function_exists( 'wporg_get_current_handbook' ) ? (string) wporg_get_current_handbook() : '';
+ $query->set( 'current_handbook', $current_handbook );
+
+ $current_handbook_home_url = function_exists( 'wporg_get_current_handbook_home_url' ) ? (string) wporg_get_current_handbook_home_url() : '';
+ $query->set( 'current_handbook_home_url', $current_handbook_home_url );
+
+ $current_handbook_name = function_exists( 'wporg_get_current_handbook_name' ) ? (string) wporg_get_current_handbook_name() : '';
+ $query->set( 'current_handbook_name', $current_handbook_name );
+ }
+
+ /**
+ * Filter handbook post types to create handbooks for: apis, plugins, themes.
+ *
+ * @access public
+ *
+ * @param array $types The default handbook types.
+ * @return array
+ */
+ public static function filter_handbook_post_types( $types ) {
+ if ( ! self::$post_types ) {
+ self::$post_types = apply_filters( 'devhub_handbook_post_types', [ 'apis', 'plugin', 'theme' ] );
+ }
+
+ return self::$post_types;
+ }
+
+ /**
+ * Create the handbook_editor role which can only edit handbooks.
+ *
+ * @access public
+ *
+ */
+ public static function add_roles() {
+ add_role(
+ 'handbook_editor',
+ __( 'Handbook Editor', 'wporg' ),
+ array(
+ 'moderate_comments' => true,
+ 'upload_files' => true,
+ 'unfiltered_html' => true,
+ 'read' => true,
+ 'edit_handbook_pages' => true,
+ 'edit_others_handbook_pages' => true,
+ 'edit_published_handbook_pages' => true,
+ 'edit_private_handbook_pages' => true,
+ 'read_private_handbook_pages' => true,
+ )
+ );
+ }
+
+ /**
+ * Adjusts handbook capabilities for roles.
+ *
+ * Undoes some capability assignments by the handbook plugin since only
+ * administrators, editors, and handbook_editors can manipulate handbooks.
+ *
+ * @access public
+ *
+ * @param array $caps Array of user capabilities.
+ * @return array
+ */
+ public static function adjust_handbook_editor_caps( $caps ) {
+ if ( ! is_user_member_of_blog() || ! class_exists( 'WPorg_Handbook' ) ) {
+ return $caps;
+ }
+
+ // Get current user's role.
+ $role = current( wp_get_current_user()->roles );
+
+ // Unset caps set by handbook plugin.
+ // Only administrators, editors, and handbook_editors can manipulate handbooks.
+ if ( ! in_array( $role, array( 'administrator', 'editor', 'handbook_editor' ) ) ) {
+ foreach ( \WPorg_Handbook::caps() as $cap ) {
+ unset( $caps[ $cap ] );
+ }
+
+ foreach ( \WPorg_Handbook::editor_caps() as $cap ) {
+ unset( $caps[ $cap ] );
+ }
+ }
+
+ return $caps;
+ }
+
+ /**
+ * Overrides default handbook post type configuration.
+ *
+ * Specifically, uses a plural slug while retaining pre-existing singular post
+ * type name.
+ *
+ * @access public
+ *
+ * @param array $defaults The default post type configuration.
+ * @param string $slug The handbook post type slug.
+ * @return array
+ */
+ public static function filter_handbook_post_type_defaults( $defaults, $slug ) {
+ // Pluralize slug for plugin and theme handbooks.
+ if ( in_array( $slug, array( 'plugin', 'theme' ) ) ) {
+ $defaults['rewrite'] = array(
+ 'feeds' => false,
+ 'slug' => $slug . 's',
+ 'with_front' => false,
+ );
+ }
+
+ $defaults['show_in_rest'] = true;
+
+ return $defaults;
+ }
+
+ /**
+ * For specific credit pages, link @usernames references to their profiles on
+ * profiles.wordpress.org.
+ *
+ * Simplistic matching. Does not verify that the @username is a legitimate
+ * WordPress.org user.
+ *
+ * @param string $content Post content
+ * @return string
+ */
+ public static function autolink_credits( $content ) {
+ // Only apply to the 'credits' (themes handbook) and 'credits-2' (plugin
+ // handbook) pages
+ if ( is_single( 'credits' ) || is_single( 'credits-2' ) ) {
+ $content = preg_replace_callback(
+ '/\B@([\w\-]+)/i',
+ function ( $matches ) {
+ return sprintf(
+ '@%s ',
+ esc_attr( $matches[1] ),
+ esc_html( $matches[1] )
+ );
+ },
+ $content
+ );
+ }
+
+ return $content;
+ }
+
+ /**
+ * Adds class to REST API handbook reference sub-pages.
+ *
+ * Due to special formatting of particular pages in the REST API handbook, a
+ * class is needed to target CSS rules.
+ *
+ * @param array $classes Body classes.
+ * @return array
+ */
+ public static function add_rest_api_handbook_reference_class( $classes ) {
+ if (
+ is_single()
+ &&
+ 'rest-api-handbook' === get_query_var( 'current_handbook' )
+ &&
+ ( ( $parent = wp_get_post_parent_id( get_the_ID() ) ) && ( 'reference' === get_post( $parent )->post_name ) )
+ ) {
+ $classes[] = 'rest-api-handbook-reference';
+ }
+
+ return $classes;
+ }
+
+ /**
+ * Overrides the default handbook label when post type name does not directly
+ * translate to post type label.
+ *
+ * @param string $label The default label, which is merely a sanitized
+ * version of the handbook name.
+ * @param string $post_type The handbook post type.
+ * @return string
+ */
+ public static function change_handbook_label( $label, $post_type ) {
+ if ( 'apis-handbook' === $post_type ) {
+ $label = __( 'Common APIs Handbook', 'wporg' );
+ }
+
+ return $label;
+ }
+
+} // Devhub_Handbooks
+
+Devhub_Handbooks::init();
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/head.php b/source/wp-content/themes/wpr-developer-2024/inc/head.php
new file mode 100644
index 000000000..f0dbe3523
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/head.php
@@ -0,0 +1,191 @@
+labels->singular_name;
+ }
+ } elseif ( ( is_singular() || is_post_type_archive() ) && false !== strpos( $post_type, 'handbook' ) ) {
+ // Add handbook name to title if relevant.
+ if ( get_post_type_object( $post_type ) ) {
+ $handbook_label = get_post_type_object( $post_type )->labels->name;
+
+ // Replace title with handbook_label(CPT name) if they have too many repeated words. Otherwise, append the handbook_label.
+ if ( strpos( $parts['title'], $handbook_label ) !== false ) {
+ $parts['title'] = $handbook_label;
+ } else {
+ $parts['title'] .= " $sep " . $handbook_label;
+ }
+ }
+ } elseif ( is_singular( 'command' ) ) {
+ // Add "WP-CLI Command" to individual CLI command pages.
+ $parts['title'] .= " $sep WP-CLI Command";
+ }
+
+ // If results are paged and the max number of pages is known.
+ if ( is_paged() && $wp_query->max_num_pages ) {
+ $parts['page'] = sprintf(
+ // translators: 1: current page number, 2: total number of pages
+ __( 'Page %1$s of %2$s', 'wporg' ),
+ get_query_var( 'paged' ),
+ $wp_query->max_num_pages
+ );
+ }
+
+ return $parts;
+ }
+
+ /**
+ * Customizes the document title separator.
+ *
+ * @param string $separator Current document title separator.
+ * @return string
+ */
+ public static function document_title_separator( $separator ) {
+ return '|';
+ }
+
+ /**
+ * Outputs tags for the page head.
+ */
+ public static function output_head_tags() {
+ $fields = [
+ // FYI: 'description' and 'og:description' are set further down.
+ 'og:title' => wp_get_document_title(),
+ 'og:site_name' => get_bloginfo( 'name' ),
+ 'og:type' => 'website',
+ 'og:url' => home_url( '/' ),
+ 'og:image' => get_theme_file_uri( 'images/opengraph-image.png' ),
+ 'twitter:card' => 'summary_large_image',
+ 'twitter:site' => '@WordPress',
+ 'twitter:image' => get_theme_file_uri( 'images/opengraph-image.png' ),
+ ];
+
+ $desc = '';
+
+ // Customize description and any other tags.
+ if ( is_front_page() ) {
+ $desc = __( 'Official WordPress developer resources including a code reference, handbooks (for APIs, plugin and theme development, block editor), and more.', 'wporg' );
+ } elseif ( is_page( 'reference' ) ) {
+ $desc = __( 'Want to know what’s going on inside WordPress? Find out more information about its functions, classes, methods, and hooks.', 'wporg' );
+ } elseif ( DevHub\is_parsed_post_type() ) {
+ if ( is_singular() ) {
+ $desc = DevHub\get_summary();
+ } elseif ( is_post_type_archive() ) {
+ $post_type_items = get_post_type_object( get_post_type() )->labels->all_items;
+ /* translators: %s: translated label for all items of a post type. */
+ $desc = sprintf( __( 'Code Reference archive for WordPress %s.', 'wporg' ), strtolower( $post_type_items ) );
+ } elseif ( is_archive() ) {
+ $desc = get_the_archive_title();
+ }
+ } elseif ( function_exists( 'wporg_is_handbook' ) && wporg_is_handbook() ) {
+ // Exclude search pages, otherwise the blurb is the first search result.
+ if ( ! is_search() ) {
+ $desc = get_the_excerpt();
+ }
+ } elseif ( is_archive( 'command' ) && ! is_search() ) {
+ // CLI handbook homepage.
+ $desc = __( 'Documentation for all currently available WP-CLI commands, including usage and subcommands.', 'wporg' );
+ } elseif ( is_singular( 'command' ) ) {
+ // Individual command pages.
+ $desc = get_the_excerpt();
+ } elseif ( is_singular() ) {
+ $post = get_queried_object();
+ if ( $post ) {
+ $desc = $post->post_content;
+ }
+ }
+
+ // Actually set field values for description.
+ if ( $desc ) {
+ $desc = wp_strip_all_tags( $desc );
+ $desc = str_replace( ' ', ' ', $desc );
+ $desc = preg_replace( '/\s+/', ' ', $desc );
+
+ // Trim down to <150 characters based on full words.
+ if ( strlen( $desc ) > 150 ) {
+ $truncated = '';
+ $words = preg_split( "/[\n\r\t ]+/", $desc, -1, PREG_SPLIT_NO_EMPTY );
+
+ while ( $words ) {
+ $word = array_shift( $words );
+ if ( strlen( $truncated ) + strlen( $word ) >= 141 ) { /* 150 - strlen( ' …' ) */
+ break;
+ }
+
+ $truncated .= $word . ' ';
+ }
+
+ $truncated = trim( $truncated );
+
+ if ( $words ) {
+ $truncated .= '…';
+ }
+
+ $desc = $truncated;
+ }
+
+ $fields['description'] = $desc;
+ $fields['og:description'] = $desc;
+ }
+
+ // Output fields.
+ foreach ( $fields as $property => $content ) {
+ $attribute = 0 === strpos( $property, 'og:' ) ? 'property' : 'name';
+ printf(
+ ' ' . "\n",
+ esc_attr( $attribute ),
+ esc_attr( $property ),
+ esc_attr( $content )
+ );
+ }
+ }
+
+} // DevHub_Head
+
+DevHub_Head::init();
+
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/import-advanced-admin.php b/source/wp-content/themes/wpr-developer-2024/inc/import-advanced-admin.php
new file mode 100644
index 000000000..dc7c00ab8
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/import-advanced-admin.php
@@ -0,0 +1,35 @@
+get_post_type() === $post_type ) {
+ $label = __( 'Advanced Administration Handbook', 'wporg' );
+ }
+
+ return $label;
+ }
+}
+
+DevHub_Advanced_Admin::instance()->init();
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/import-block-editor.php b/source/wp-content/themes/wpr-developer-2024/inc/import-block-editor.php
new file mode 100644
index 000000000..6fee571b0
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/import-block-editor.php
@@ -0,0 +1,427 @@
+get_post_type() === $data['post_type'] ) {
+ add_filter( 'wp_kses_allowed_html', array( __CLASS__, 'allow_extra_tags' ), 10, 1 );
+ }
+ },
+ 10,
+ 2
+ );
+
+ add_action(
+ 'edit_post_' . $this->get_post_type(),
+ function( $post_id ) {
+ remove_filter( 'wp_kses_allowed_html', array( __CLASS__, 'allow_extra_tags' ), 10, 1 );
+ }
+ );
+ }
+
+ /**
+ * Handles redirects for renamed/removed handbook pages.
+ */
+ public function redirects() {
+ if ( 0 !== strpos( $_SERVER['REQUEST_URI'], '/block-editor/' ) ) {
+ return;
+ }
+
+ $handbook_path = explode( '/', trailingslashit( $_SERVER['REQUEST_URI'] ), 3 );
+ $handbook_path = $handbook_path[2] ?? null;
+
+ if ( is_null( $handbook_path ) ) {
+ return;
+ }
+
+ // Any handbook pages where the slug changes should be listed here.
+ $redirects = [
+ 'components/server-side-render' => 'packages/packages-server-side-render',
+ 'explanations/faq' => 'getting-started/faq',
+ 'explanations/glossary' => 'getting-started/glossary',
+ 'how-to-guides/platform/custom-block-editor/tutorial' => 'how-to-guides/platform/custom-block-editor',
+ 'reference-guides/block-api/versions' => 'reference-guides/block-api/block-api-versions',
+ 'reference-guides/packages/packages-experiments' => 'reference-guides/packages/packages-private-apis',
+
+ // After IAPI restructuring, April 2024.
+ 'reference-guides/packages/packages-interactivity/packages-interactivity-api-reference' => 'reference-guides/interactivity-api/api-reference',
+
+ // After handbook restructuring, January 2024.
+ 'how-to-guides/block-tutorial/generate-blocks-with-wp-cli' => 'getting-started/devenv/get-started-with-create-block',
+ 'how-to-guides/block-tutorial/block-controls-toolbar-and-sidebar' => 'getting-started/fundamentals/block-in-the-editor',
+ 'how-to-guides/block-tutorial/writing-your-first-block-type' => 'getting-started/tutorial',
+
+ // After handbook restructuring, December 2023.
+ 'getting-started/create-block' => 'getting-started/tutorial',
+ 'getting-started/create-block/wp-plugin' => 'getting-started/tutorial',
+ 'getting-started/create-block/block-anatomy' => 'getting-started/tutorial',
+ 'getting-started/create-block/attributes' => 'getting-started/tutorial',
+ 'getting-started/create-block/block-code' => 'getting-started/tutorial',
+ 'getting-started/create-block/author-experience' => 'getting-started/tutorial',
+ 'getting-started/create-block/finishing' => 'getting-started/tutorial',
+ 'getting-started/create-block/submitting-to-block-directory' => 'getting-started/tutorial',
+ 'how-to-guides/block-tutorial/introducing-attributes-and-editable-fields' => 'getting-started/fundamentals/block-json',
+ 'how-to-guides/block-tutorial/block-supports-in-static-blocks' => 'getting-started/fundamentals/block-json',
+ 'how-to-guides/block-tutorial/block-supports-in-dynamic-blocks' => 'getting-started/fundamentals/block-json',
+ 'how-to-guides/javascript' => 'getting-started/fundamentals/javascript-in-the-block-editor',
+ 'how-to-guides/javascript/plugins-background' => 'getting-started/fundamentals/javascript-in-the-block-editor',
+ 'how-to-guides/javascript/loading-javascript' => 'getting-started/fundamentals/javascript-in-the-block-editor',
+ 'how-to-guides/javascript/extending-the-block-editor' => 'getting-started/fundamentals/javascript-in-the-block-editor',
+ 'how-to-guides/javascript/troubleshooting' => 'getting-started/fundamentals/javascript-in-the-block-editor',
+ 'how-to-guides/javascript/versions-and-building' => 'getting-started/fundamentals/javascript-in-the-block-editor',
+ 'how-to-guides/javascript/scope-your-code' => 'getting-started/fundamentals/javascript-in-the-block-editor',
+ 'how-to-guides/javascript/js-build-setup' => 'getting-started/fundamentals/javascript-in-the-block-editor',
+ 'how-to-guides/javascript/esnext-js' => 'getting-started/fundamentals/javascript-in-the-block-editor',
+ 'how-to-guides/themes/theme-json' => 'how-to-guides/themes/global-settings-and-styles',
+
+ // After handbook restructuring, March 2021.
+ 'handbook/versions-in-wordpress' => 'contributors/versions-in-wordpress',
+ 'architecture/fse-templates' => 'explanations/architecture/full-site-editing-templates',
+ 'developers/internationalization' => 'how-to-guides/internationalization',
+ 'developers/richtext' => 'reference-guides/richtext',
+ 'developers/accessibility' => 'how-to-guides/accessibility',
+ 'developers/feature-flags' => 'how-to-guides/feature-flags',
+ 'tutorials/devenv' => 'getting-started/devenv',
+ 'tutorials/devenv/docker-ubuntu' => 'getting-started/devenv/docker-ubuntu',
+ 'tutorials/block-based-theme' => 'how-to-guides/themes/block-theme-overview',
+ ];
+
+ // General path redirects. (More specific path first.)
+ $path_redirects = [
+ // 'some-path/' => 'new-path/',
+
+ // After handbook restructuring, March 2021.
+ 'architecture/' => 'explanations/architecture/',
+ 'contributors/develop/' => 'contributors/code/',
+ 'contributors/document/' => 'contributors/documentation/',
+ 'data/' => 'reference-guides/data/',
+ 'designers/' => 'how-to-guides/designers/',
+ 'developers/backward-compatibility/' => 'how-to-guides/backward-compatibility/',
+ 'developers/block-api/' => 'reference-guides/block-api/',
+ 'developers/filters/' => 'reference-guides/filters/',
+ 'developers/platform/' => 'how-to-guides/platform/',
+ 'developers/slotfills/' => 'reference-guides/slotfills/',
+ 'developers/themes/' => 'how-to-guides/themes/',
+ 'handbook/tutorials/' => 'getting-started/',
+ 'packages/' => 'reference-guides/packages/',
+ 'tutorials/create-block/' => 'getting-started/create-block/',
+ 'tutorials/plugin-sidebar-0/' => 'how-to-guides/sidebar-tutorial/',
+ 'tutorials/' => 'how-to-guides/',
+ ];
+
+ // Redirects away from the block editor handbook.
+ $handbook_redirects = [
+ // 'some-block-editor-path' => '/new-path-relative-to-developer-root/',
+ 'how-to-guides/themes/block-theme-overview' => '/themes/block-themes/',
+ 'how-to-guides/block-based-theme' => '/themes/block-themes/',
+ 'how-to-guides/block-theme' => '/themes/block-themes/',
+ ];
+
+ $new_handbook_path = '';
+ $is_handbook_redirect = false;
+ if ( ! empty( $redirects[ untrailingslashit( $handbook_path ) ] ) ) {
+ $new_handbook_path = $redirects[ untrailingslashit( $handbook_path ) ];
+ } else if ( ! empty( $handbook_redirects[ untrailingslashit( $handbook_path ) ] ) ) {
+ $new_handbook_path = $handbook_redirects[ untrailingslashit( $handbook_path ) ];
+ $is_handbook_redirect = true;
+ } else {
+ foreach ( $path_redirects as $old_path => $new_path ) {
+ if ( 0 === strpos( $handbook_path, $old_path ) ) {
+ $new_handbook_path = str_replace( $old_path, $new_path, $handbook_path );
+ break;
+ }
+ }
+ }
+
+ if ( $new_handbook_path ) {
+ if ( $is_handbook_redirect ) {
+ $redirect_to = get_site_url() . $new_handbook_path;
+ } else {
+ $redirect_to = get_post_type_archive_link( $this->get_post_type() ) . $new_handbook_path;
+ }
+
+ wp_safe_redirect( $redirect_to, 301 );
+ exit;
+ }
+ }
+
+ /**
+ * Overrides the default handbook label since post type name does not directly
+ * translate to post type label.
+ *
+ * @param string $label The default label, which is merely a sanitized
+ * version of the handbook name.
+ * @param string $post_type The handbook post type.
+ * @return string
+ */
+ public function change_handbook_label( $label, $post_type ) {
+ if ( $this->get_post_type() === $post_type ) {
+ $label = __( 'Block Editor Handbook', 'wporg' );
+ }
+
+ return $label;
+ }
+
+ /**
+ * Disables table of contents in-page widget for the Block Editor handbook.
+ *
+ * @param $display Should the table of contents be displayed?
+ * @return bool
+ */
+ public function disable_toc( $display ) {
+ if ( $this->get_post_type() === get_post_type() ) {
+ $display = false;
+ }
+
+ return $display;
+ }
+
+ /**
+ * Fixes fetched value of markdown_source meta field to not be the
+ * raw.githubcontent.com domain that is currently incorrectly used
+ * in the block editor manifest.
+ *
+ * @param mixed $null A value for the meta if its data retrieval is
+ * overridden, else null.
+ * @param int $object_id Object ID.
+ * @param string $meta_key Meta key.
+ * @param bool $single Whether to return only the first value of the specified $meta_key.
+ * @return mixed
+ */
+ public function fix_markdown_source_meta( $null, $object_id, $meta_key, $single ) {
+ if (
+ // Running under cron.
+ wp_doing_cron()
+ ||
+ // Running under WP-CLI.
+ ( defined( 'WP_CLI' ) && WP_CLI )
+ ||
+ // Not the markdown source meta key.
+ $meta_key !== $this->meta_key
+ ||
+ // Not the block editor handbook.
+ $this->get_post_type() !== get_post_type( $object_id )
+ ) {
+ return $null;
+ }
+
+ $post = get_post( $object_id );
+
+ $meta_type = 'post';
+
+ /* Most of the rest of this is taken from get_metadata() */
+
+ $meta_cache = wp_cache_get( $object_id, $meta_type . '_meta' );
+
+ if ( ! $meta_cache ) {
+ $meta_cache = update_meta_cache( $meta_type, array( $object_id ) );
+ $meta_cache = $meta_cache[ $object_id ];
+ }
+
+ if ( ! $meta_key ) {
+ return $null;
+ }
+
+ if ( isset( $meta_cache[ $meta_key ] ) ) {
+ if ( $single ) {
+ $value = maybe_unserialize( $meta_cache[ $meta_key ][0] );
+ $value = str_replace( 'https://raw.githubusercontent.com/WordPress/gutenberg/', 'https://github.com/WordPress/gutenberg/edit/', $value );
+ } else {
+ $value = array_map( 'maybe_unserialize', $meta_cache[ $meta_key ] );
+ $value = array_map(
+ function( $x ) {
+ return str_replace( 'https://raw.githubusercontent.com/WordPress/gutenberg/', 'https://github.com/WordPress/gutenberg/edit/', $x );
+ },
+ $value
+ );
+ }
+ return $value;
+ }
+
+ return $null;
+ }
+
+ /**
+ * Modifies the Markdown text prior to being transformed into HTML.
+ *
+ * @param string $markdown The Markdown text.
+ * @param string $post_type The handbook post type.
+ * @return string
+ */
+ public function wporg_markdown_before_transform( $markdown, $post_type ) {
+ if ( $this->get_post_type() !== $post_type ) {
+ return $markdown;
+ }
+
+ // Remove any level 1 headings at the start of the markdown.
+ // This also effectively prevents the post title from being the one defined in the markdown doc.
+ if ( preg_match( '/^#\s(.+)/', $markdown, $matches ) ) {
+ $markdown = preg_replace( '/^#\s(.+)/', '', $markdown );
+ }
+
+ // Remove the .md extension from relative links and treat 'readme.md' as an index
+ $markdown = preg_replace(
+ '@(\[.*?\]\(((\.\./)+docs/|/docs/|/packages/).*?)(((?<=/)readme)?\.md)?(#.*?)?\)@i',
+ '$1$6)',
+ $markdown
+ );
+
+ // Remove the (../)*docs/ path from relative links, and replace it with an absolute URL
+ $markdown = preg_replace(
+ '@(\[.*?\])\((\.\./)+docs/(.*?)/?(#.*?)?\)@i',
+ '$1(https://developer.wordpress.org/block-editor/$3/$4)',
+ $markdown
+ );
+
+ // Handle /docs/(.+)(/README.md) path for internal links and replace it with an absolute URL
+ $markdown = preg_replace(
+ '@(\[.*?\])\(/docs/(.*?)/?(#.*?)?\)@i',
+ '$1(https://developer.wordpress.org/block-editor/$2/$3)',
+ $markdown
+ );
+
+ // Handle /packages/compomnents(/README.md)
+ $markdown = preg_replace(
+ '@(\[.*?\])\(/packages/components/?(#.*?)?\)@i',
+ '$1(https://developer.wordpress.org/block-editor/reference-guide/components/$2)',
+ $markdown
+ );
+
+ // Handle /packages/components/(src/)(.+)(/README.md)
+ $markdown = preg_replace(
+ '@(\[.*?\])\(/packages/components/(src/)?(.*?)/?(#.*?)?\)@i',
+ '$1(https://developer.wordpress.org/block-editor/reference-guide/components/$3/$4)',
+ $markdown
+ );
+
+ // Handle /packages/(.+)(/README.md)
+ $markdown = preg_replace(
+ '@(\[.*?\])\(/packages/(.*?)/?(#.*?)?\)@i',
+ '$1(https://developer.wordpress.org/block-editor/reference-guide/packages/packages-$2/$3)',
+ $markdown
+ );
+
+ // Support strikethrough.
+ // Transform ~~some text~~ to some text
+ $markdown = preg_replace(
+ '@~~(.*?)~~@',
+ '$1 ',
+ $markdown
+ );
+
+ // Strip the trailing Code is Poetry footer from packages.
+ // See https://github.com/WordPress/gutenberg/blob/trunk/packages/README.md
+ $markdown = preg_replace(
+ // Strip, any number of 's, any number of
's (with no text content),
+ // an img with 'codeispoetry' in an attribute, followed by any number of 's and 's before EOF
+ '@( )*(]*>)* ]*codeispoetry[^>]*/?>(<(/p|br\s*/?)>)*$@i',
+ '',
+ $markdown
+ );
+
+ return $markdown;
+ }
+
+ /**
+ * Modifies the HTML resulting from the Markdown transformation.
+ *
+ * @param string $html The result of converting Markdown to HTML.
+ * @param string $post_type The handbook post type.
+ * @return string
+ */
+ public function wporg_markdown_after_transform( $html, $post_type ) {
+ if ( $this->get_post_type() !== $post_type ) {
+ return $html;
+ }
+
+ // Turn the code blocks into tabs
+ $html = preg_replace_callback( '/{%\s+codetabs\s+%}(.*?){%\s+end\s+%}/ms', array( __CLASS__, 'parse_code_blocks' ), $html );
+ $html = str_replace( 'class="php"', 'class="language-php"', $html );
+ $html = str_replace( 'class="js"', 'class="language-javascript"', $html );
+ $html = str_replace( 'class="jsx"', 'class="language-jsx"', $html );
+ $html = str_replace( 'class="css"', 'class="language-css"', $html );
+
+ return $html;
+ }
+
+ /**
+ * Modifies the GitHub edit URL to point to trunk instead of the imported branch.
+ *
+ * @param string $link The link to edit the post on GitHub.
+ * @param int $post_id The post ID.
+ * @return string
+ */
+ public function wporg_markdown_edit_link( $link, $post_id ) {
+ if ( $this->get_post_type() === get_post_type( $post_id ) && defined( 'WP_CORE_STABLE_BRANCH' ) ) {
+ $link = str_replace( '/wp/' . WP_CORE_STABLE_BRANCH . '/', '/trunk/', $link );
+ }
+
+ return $link;
+ }
+
+ /**
+ * Callback for the preg_replace_callback() in wporg_markdown_after_transform()
+ * to transform a block of code tabs into HTML.
+ */
+ public static function parse_code_blocks( $matches ) {
+ $splitted_tabs = preg_split( '/{%\s+([\w]+)\s+%}/', trim( $matches[1] ), -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
+
+ $html = '
';
+ $code_blocks = '';
+
+ for ( $ii = 0; $ii < count( $splitted_tabs ); $ii += 2 ) {
+ $classes = 'code-tab ' . $splitted_tabs[ $ii ];
+ $code_classes = 'code-tab-block ' . $splitted_tabs[ $ii ];
+
+ if ( 0 === $ii ) {
+ $classes .= ' is-active';
+ $code_classes .= ' is-active';
+ }
+
+ $html .= "
{$splitted_tabs[ $ii ]} ";
+ $code_blocks .= "
{$splitted_tabs[ $ii + 1 ]}
";
+ }
+
+ $html .= "$code_blocks
";
+
+ return $html;
+ }
+
+ /**
+ * Add extra tags to the KSES allowed tags list.
+ *
+ * @param array $tags Allowed tags.
+ * @return array
+ */
+ public static function allow_extra_tags( $tags ) {
+ if ( ! isset( $tags['style'] ) ) {
+ $tags['style'] = [];
+ }
+
+ return $tags;
+ }
+
+}
+
+DevHub_Block_Editor_Importer::instance()->init();
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/import-coding-standards.php b/source/wp-content/themes/wpr-developer-2024/inc/import-coding-standards.php
new file mode 100644
index 000000000..550c22271
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/import-coding-standards.php
@@ -0,0 +1,65 @@
+get_post_type() === $post_type ) {
+ $label = __( 'Coding Standards Handbook', 'wporg' );
+ }
+
+ return $label;
+ }
+
+ /**
+ * Fixes (as a stopgap) encoding of already encoded characters in code shortcodes.
+ *
+ * Affected characters:
+ * - '&` (sometimes)
+ * - `<`
+ * - `*` (when encoded in the first place)
+ * - `?` (when encoded in the first place)
+ * - `"` (in some places when used as opening quote)
+ *
+ * This could probably be abrogated by the source using triple backticks to
+ * denote code.
+ *
+ * @see https://meta.trac.wordpress.org/ticket/5346
+ *
+ * @param string $content Post content.
+ * @return string
+ */
+ public function fix_double_encoding( $content ) {
+ if ( $this->get_post_type() === get_post_type() ) {
+ $content = str_replace(
+ [ '&', ''', '&042;', '*', '<', '?', '"' ],
+ [ '&', ''', '*', '*', '<', '?', '"' ],
+ $content
+ );
+ }
+ return $content;
+ }
+}
+
+DevHub_Coding_Standards_Importer::instance()->init();
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/import-docs.php b/source/wp-content/themes/wpr-developer-2024/inc/import-docs.php
new file mode 100644
index 000000000..4d5cd3b0d
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/import-docs.php
@@ -0,0 +1,177 @@
+base}/" );
+ }
+
+ /**
+ * Returns the manifest URL.
+ *
+ * @return string
+ */
+ protected function get_manifest_url() {
+ return $this->manifest_url;
+ }
+
+ /**
+ * Returns the post type for the imported handbook.
+ *
+ * @return string
+ */
+ public function get_post_type() {
+ return "{$this->post_type}-handbook";
+ }
+
+ /**
+ * Initializes the object.
+ *
+ * @param string $post_type The post type base. Hypenated and without "handbook".
+ * @param string $base The slug for the post type.
+ * @param string $manifest_url The manifest URL.
+ */
+ public function do_init( $post_type, $base, $manifest_url ) {
+ $this->post_type = $post_type;
+ $this->base = $base;
+ $this->manifest_url = $manifest_url;
+
+ add_filter( 'devhub_handbook_post_types', array( $this, 'amend_post_types' ) );
+ add_filter( 'handbook_post_type_defaults', array( $this, 'override_post_type_slug' ), 10, 2 );
+ add_filter( 'cron_schedules', array( $this, 'filter_cron_schedules' ) );
+ add_action( 'init', array( $this, 'register_cron_jobs' ) );
+ add_action( "devhub_{$this->post_type}_import_manifest", array( $this, 'import_manifest' ) );
+ add_action( "devhub_{$this->post_type}_import_all_markdown", array( $this, 'import_all_markdown' ) );
+
+ $editor = new Editor( $this );
+ $GLOBALS[ "devhub_handbook_editors" ][ $this->post_type ] = $editor;
+ $editor->init();
+ }
+
+ /**
+ * Adds the post type to the list of handbook post types.
+ *
+ * @param array Array of post type slugs.
+ * @return array
+ */
+ public function amend_post_types( $post_types ) {
+ if ( ! in_array( $this->post_type, $post_types ) ) {
+ $post_types[] = $this->post_type;
+ }
+
+ return $post_types;
+ }
+
+ /**
+ * Overrides the handbook post type slug.
+ *
+ * This is generally only necessary if the post type does not match the post
+ * type slug.
+ *
+ * @param array $config Array of post type config items.
+ * @param string $slug The post type slug.
+ * @return array
+ */
+ public function override_post_type_slug( $config, $slug ) {
+ // If filtering is for this post type and the base and the post type don't
+ // match, then filter post type defaults.
+ if ( $this->post_type === $config['rewrite']['slug'] && $this->post_type !== $this->base ) {
+ $config['rewrite']['slug'] = $this->base;
+ }
+
+ return $config;
+ }
+
+ /**
+ * Filters cron schedules to add a 15 minute schedule, if there isn't one.
+ *
+ * @param array $schedules Cron schedules.
+ * @return array
+ */
+ public function filter_cron_schedules( $schedules ) {
+ if ( empty( $schedules['15_minutes'] ) ) {
+ $schedules['15_minutes'] = array(
+ 'interval' => 15 * MINUTE_IN_SECONDS,
+ 'display' => '15 minutes'
+ );
+ }
+
+ return $schedules;
+ }
+
+ /**
+ * Registers cron jobs.
+ */
+ public function register_cron_jobs() {
+ // If configured cron interval does not exist, then fall back to default.
+ if ( ! in_array( $this->cron_interval, array_keys( wp_get_schedules() ) ) ) {
+ $this->cron_interval = '15_minutes';
+ }
+
+ if ( ! wp_next_scheduled( "devhub_{$this->post_type}_import_manifest" ) ) {
+ wp_schedule_event( time(), $this->cron_interval, "devhub_{$this->post_type}_import_manifest" );
+ }
+
+ if ( ! wp_next_scheduled( "devhub_{$this->post_type}_import_all_markdown" ) ) {
+ wp_schedule_event( time(), $this->cron_interval, "devhub_{$this->post_type}_import_all_markdown" );
+ }
+ }
+}
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/jetpack.php b/source/wp-content/themes/wpr-developer-2024/inc/jetpack.php
new file mode 100644
index 000000000..829f48d8a
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/jetpack.php
@@ -0,0 +1,19 @@
+ 'main',
+ 'footer' => 'page',
+ ) );
+}
+add_action( 'after_setup_theme', 'wporg_developer_jetpack_setup' );
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/loop-pagination.php b/source/wp-content/themes/wpr-developer-2024/inc/loop-pagination.php
new file mode 100644
index 000000000..bad89d378
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/loop-pagination.php
@@ -0,0 +1,113 @@
+
+ * @copyright Copyright (c) 2010 - 2013, Justin Tadlock
+ * @link https://themehybrid.com/docs/loop-pagination
+ * @license https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ */
+
+/**
+ * Loop pagination function for paginating loops with multiple posts. This should be used on archive, blog, and
+ * search pages. It is not for singular views.
+ *
+ * @since 0.1.0
+ * @access public
+ * @uses paginate_links() Creates a string of paginated links based on the arguments given.
+ * @param array $args Arguments to customize how the page links are output.
+ * @return string $page_links
+ */
+function loop_pagination( $args = array() ) {
+ global $wp_rewrite, $wp_query;
+
+ /* If there's not more than one page, return nothing. */
+ if ( 1 >= $wp_query->max_num_pages )
+ return;
+
+ /* Get the current page. */
+ $current = ( get_query_var( 'paged' ) ? absint( get_query_var( 'paged' ) ) : 1 );
+
+ /* Get the max number of pages. */
+ $max_num_pages = intval( $wp_query->max_num_pages );
+
+ /* Get the pagination base. */
+ $pagination_base = $wp_rewrite->pagination_base;
+
+ /* Set up some default arguments for the paginate_links() function. */
+ $defaults = array(
+ 'base' => add_query_arg( 'paged', '%#%' ),
+ 'format' => '',
+ 'total' => $max_num_pages,
+ 'current' => $current,
+ 'prev_next' => true,
+ 'prev_text' => __( 'Previous', 'wporg' ),
+ 'next_text' => __( 'Next', 'wporg' ),
+ 'show_all' => false,
+ 'end_size' => 2,
+ 'mid_size' => 1,
+ 'add_fragment' => '',
+ 'type' => 'plain',
+
+ // Begin loop_pagination() arguments.
+ 'before' => '',
+ 'echo' => true,
+ );
+
+ /* Add the $base argument to the array if the user is using permalinks. */
+ if ( $wp_rewrite->using_permalinks() && !is_search() )
+ $defaults['base'] = user_trailingslashit( trailingslashit( get_pagenum_link() ) . "{$pagination_base}/%#%" );
+
+ /* Allow developers to overwrite the arguments with a filter. */
+ $args = apply_filters( 'loop_pagination_args', $args );
+
+ /* Merge the arguments input with the defaults. */
+ $args = wp_parse_args( $args, $defaults );
+
+ /* Don't allow the user to set this to an array. */
+ if ( 'array' == $args['type'] )
+ $args['type'] = 'plain';
+
+ /* Get the paginated links. */
+ $page_links = paginate_links( $args );
+
+ /* Remove 'page/1' from the entire output since it's not needed. */
+ $page_links = preg_replace(
+ array(
+ "#(href=['\"].*?){$pagination_base}/1(['\"])#", // 'page/1'
+ "#(href=['\"].*?){$pagination_base}/1/(['\"])#", // 'page/1/'
+ "#(href=['\"].*?)\?paged=1(['\"])#", // '?paged=1'
+ "#(href=['\"].*?)&\#038;paged=1(['\"])#" // '&paged=1'
+ ),
+ '$1$2',
+ $page_links
+ );
+
+ /* Wrap the paginated links with the $before and $after elements. */
+ $page_links = $args['before'] . $page_links . $args['after'];
+
+ /* Allow devs to completely overwrite the output. */
+ $page_links = apply_filters( 'loop_pagination', $page_links );
+
+ /* Return the paginated links for use in themes. */
+ if ( $args['echo'] )
+ echo $page_links;
+ else
+ return $page_links;
+}
+
+
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/parsed-content.php b/source/wp-content/themes/wpr-developer-2024/inc/parsed-content.php
new file mode 100644
index 000000000..ccf967be7
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/parsed-content.php
@@ -0,0 +1,281 @@
+post_types = DevHub\get_parsed_post_types();
+
+ // Data.
+ add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
+ add_action( 'save_post', array( $this, 'save_post' ) );
+
+ // Script and styles.
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
+
+ // AJAX.
+ add_action( 'wp_ajax_wporg_attach_ticket', array( $this, 'attach_ticket' ) );
+ add_action( 'wp_ajax_wporg_detach_ticket', array( $this, 'detach_ticket' ) );
+
+ // Register meta fields.
+ register_meta( 'post', 'wporg_ticket_number', 'absint', '__return_false' );
+ register_meta( 'post', 'wporg_ticket_title', 'sanitize_text_field', '__return_false' );
+ register_meta( 'post', 'wporg_parsed_content', 'wp_kses_post', '__return_false' );
+ }
+
+ /**
+ * Add meta boxes.
+ *
+ * @access public
+ */
+ public function add_meta_boxes() {
+ if ( in_array( $screen = get_current_screen()->id, $this->post_types ) ) {
+ remove_meta_box( 'postexcerpt', $screen, 'normal' );
+ add_meta_box( 'wporg_parsed_content', __( 'Parsed Content', 'wporg' ), array( $this, 'parsed_meta_box_cb' ), $screen, 'normal' );
+ }
+ }
+
+ /**
+ * Parsed content meta box display callback.
+ *
+ * @access public
+ *
+ * @param WP_Post $post Current post object.
+ */
+ public function parsed_meta_box_cb( $post ) {
+ $ticket = get_post_meta( $post->ID, 'wporg_ticket_number', true );
+ $ticket_label = get_post_meta( $post->ID, 'wporg_ticket_title', true );
+ $ticket_info = get_post_meta( $post->ID, 'wporg_parsed_ticket_info', true );
+ $content = $post->post_content;
+
+ if ( $ticket ) {
+ $src = "https://core.trac.wordpress.org/ticket/{$ticket}";
+ $ticket_message = sprintf( '%2$s ', esc_url( $src ), apply_filters( 'the_title', $ticket_label ) );
+ } else {
+ $link = sprintf( '%s ', __( 'Core Trac', 'wporg' ) );
+ $ticket_message = sprintf( __( 'A valid, open ticket from %s is required to edit parsed content.', 'wporg' ), $link );
+ }
+ wp_nonce_field( 'wporg-parsed-content', 'wporg-parsed-content-nonce' );
+ ?>
+
+ id, $this->post_types ) ) {
+ wp_enqueue_script(
+ 'wporg-parsed-content',
+ get_stylesheet_directory_uri() . '/js/parsed-content.js',
+ array( 'jquery', 'utils' ),
+ filemtime( dirname( __DIR__ ) . '/js/parsed-content.js' ),
+ true
+ );
+
+ wp_localize_script(
+ 'wporg-parsed-content',
+ 'wporgParsedContent',
+ array(
+ 'ajaxURL' => admin_url( 'admin-ajax.php' ),
+ 'searchText' => __( 'Searching ...', 'wporg' ),
+ 'retryText' => __( 'Invalid ticket number, please try again.', 'wporg' ),
+ )
+ );
+ }
+ }
+
+ /**
+ * AJAX handler for fetching the title of a Core Trac ticket and 'attaching' it to the post.
+ *
+ * @access public
+ */
+ public function attach_ticket() {
+ check_ajax_referer( 'wporg-attach-ticket', 'nonce' );
+
+ $ticket_no = empty( $_REQUEST['ticket'] ) ? 0 : absint( $_REQUEST['ticket'] );
+ $ticket_url = "https://core.trac.wordpress.org/ticket/{$ticket_no}";
+
+ // Fetch the ticket.
+ $resp = wp_remote_get( esc_url( $ticket_url ) );
+ $status_code = wp_remote_retrieve_response_code( $resp );
+ $body = wp_remote_retrieve_body( $resp );
+
+ // Anything other than 200 is invalid.
+ if ( 200 === $status_code && null !== $body ) {
+ $title = '';
+
+ // Snag the page title from the ticket HTML.
+ if ( class_exists( 'DOMDocument' ) ) {
+ $doc = new DOMDocument();
+ @$doc->loadHTML( $body );
+
+ $nodes = $doc->getElementsByTagName( 'title' );
+ $title = $nodes->item( 0 )->nodeValue;
+
+ // Strip off the site name.
+ $title = str_ireplace( ' – WordPress Trac', '', $title );
+ } else {
+ die( -1 );
+ }
+
+ $post_id = empty( $_REQUEST['post_id'] ) ? 0 : absint( $_REQUEST['post_id'] );
+
+ update_post_meta( $post_id, 'wporg_ticket_number', $ticket_no );
+ update_post_meta( $post_id, 'wporg_ticket_title', $title );
+
+ $link = sprintf( '%2$s ', esc_url( $ticket_url ), apply_filters( 'the_title', $title ) );
+
+ // Can haz success.
+ wp_send_json_success( array(
+ 'message' => $link,
+ 'new_nonce' => wp_create_nonce( 'wporg-attach-ticket' )
+ ) );
+
+ } else {
+ // Ticket number is invalid.
+ wp_send_json_error( array(
+ 'message' => __( 'Invalid ticket number.', 'wporg' ),
+ 'new_nonce' => wp_create_nonce( 'wporg-attach-ticket' )
+ ) );
+ }
+
+ die( 0 );
+ }
+
+ /**
+ * AJAX handler for 'detaching' a ticket from the post.
+ *
+ * @access public
+ */
+ public function detach_ticket() {
+ check_ajax_referer( 'wporg-detach-ticket', 'nonce' );
+
+ $post_id = empty( $_REQUEST['post_id'] ) ? 0 : absint( $_REQUEST['post_id'] );
+
+ // Attempt to detach the ticket.
+ if ( delete_post_meta( $post_id, 'wporg_ticket_number' )
+ && delete_post_meta( $post_id, 'wporg_ticket_title' )
+ ) {
+ // Success!
+ wp_send_json_success( array(
+ 'message' => __( 'Ticket detached.', 'wporg' ),
+ 'new_nonce' => wp_create_nonce( 'wporg-detach-ticket' )
+ ) );
+
+ } else {
+ // Still attached.
+ wp_send_json_error( array(
+ 'message' => __( 'Ticket still attached.', 'wporg' ),
+ 'new_nonce' => wp_create_nonce( 'wporg-detach-ticket' )
+ ) );
+ }
+
+ die( 0 );
+ }
+
+} // WPORG_Edit_Parsed_Content
+
+$extras = new WPORG_Edit_Parsed_Content();
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/parser.php b/source/wp-content/themes/wpr-developer-2024/inc/parser.php
new file mode 100644
index 000000000..a8fe90463
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/parser.php
@@ -0,0 +1,118 @@
+ 'ids', 'post_type' => $post_type, 'posts_per_page' => '-1' ) );
+ foreach ( $posts as $post ) {
+ echo '.';
+ \DevHub\get_source_code( $post, true );
+ }
+ }
+ echo "\n";
+
+ return true;
+ }
+
+} // DevHub_Parser
+
+DevHub_Parser::init();
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/redirects.php b/source/wp-content/themes/wpr-developer-2024/inc/redirects.php
new file mode 100644
index 000000000..3243906a4
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/redirects.php
@@ -0,0 +1,199 @@
+is_handbook && 1 == $wp_query->found_posts ) {
+ wp_redirect( get_permalink( get_post() ) );
+ exit();
+ }
+ }
+
+ /**
+ * Redirects a naked handbook request to home.
+ */
+ public static function redirect_handbook() {
+ if (
+ // Naked /handbook/ request
+ ( 'handbook' == get_query_var( 'name' ) && ! get_query_var( 'post_type' ) )
+ ) {
+ wp_redirect( home_url() );
+ exit();
+ }
+ }
+
+ /**
+ * Redirects a /blog request to the Developer blog at /news/.
+ */
+ public static function redirect_blog() {
+ $path = trailingslashit( $_SERVER['REQUEST_URI'] );
+
+ if ( 0 === strpos( $path, '/blog' ) ) {
+ wp_redirect( '/news/', 301 );
+ exit();
+ }
+ }
+
+ /**
+ * Redirects a naked /resource/ request to dashicons page.
+ *
+ * Temporary until a resource page other than dashicons is created.
+ */
+ public static function redirect_resources() {
+ if ( is_page( 'resource' ) ) {
+ $args = array(
+ 'post_type' => 'page',
+ 'name' => 'dashicons',
+ 'posts_per_page' => 1,
+ );
+
+ $query = new WP_Query( $args );
+
+ if ( $query->have_posts() ) {
+ $post = $query->posts[0];
+ wp_redirect( get_permalink( $post->ID ) );
+ exit();
+ }
+ }
+ }
+
+ /**
+ * Redirects requests for the singularized form of handbook slugs to the
+ * pluralized version.
+ */
+ public static function redirect_singularized_handbooks() {
+ $path = trailingslashit( $_SERVER['REQUEST_URI'] );
+
+ // '/plugin' => '/plugins'
+ if ( 0 === strpos( $path, '/plugin/' ) ) {
+ $path = get_post_type_archive_link( 'plugin-handbook' ) . substr( $path, 8 );
+ wp_redirect( $path, 301 );
+ exit();
+ }
+
+ // '/theme' => '/themes'
+ if ( 0 === strpos( $path, '/theme/' ) ) {
+ $path = get_post_type_archive_link( 'theme-handbook' ) . substr( $path, 7 );
+ wp_redirect( $path, 301 );
+ exit();
+ }
+ }
+
+ /**
+ * Redirects requests for the pluralized slugs of the code reference parsed
+ * post types.
+ *
+ * Note: this is a convenience redirect and not a fix for any officially
+ * deployed links.
+ */
+ public static function redirect_pluralized_reference_post_types() {
+ $path = trailingslashit( $_SERVER['REQUEST_URI'] );
+
+ $post_types = array(
+ 'class' => 'classes',
+ 'function' => 'functions',
+ 'hook' => 'hooks',
+ 'method' => 'methods',
+ );
+
+ // '/reference/$singular(/*)?' => '/reference/$plural(/*)?'
+ foreach ( $post_types as $post_type_slug_singular => $post_type_slug_plural ) {
+ if ( 0 === stripos( $path, "/reference/{$post_type_slug_singular}/" ) ) {
+ $path = "/reference/{$post_type_slug_plural}/" . substr( $path, strlen( "/reference/{$post_type_slug_singular}/" ) );
+ wp_redirect( $path, 301 );
+ exit();
+ }
+ }
+ }
+
+ /**
+ * Returns 404 response to requests for non-first pages of the front page.
+ */
+ public static function paginated_home_page_404() {
+ // Paginated front page.
+ if ( is_front_page() && is_paged() ) {
+ // Add the usual 404 page body class so that styles are applied.
+ add_filter(
+ 'body_class',
+ function( $classes ) {
+ $classes[] = 'error404';
+
+ return $classes;
+ }
+ );
+
+ include( get_404_template() );
+ exit;
+ }
+ }
+
+ /**
+ * Loads the appropriate template for editing user notes.
+ */
+ public static function user_note_edit() {
+ $path = trailingslashit( $_SERVER['REQUEST_URI'] );
+
+ if ( strpos( $path, '/reference/comment/edit' ) !== false ) {
+ $comment_id = get_query_var( 'edit_user_note' );
+ $can_user_edit = \DevHub\can_user_edit_note( $comment_id );
+
+ if ( $can_user_edit ) {
+ $template_slug = 'comment-edit';
+
+ // This is returned by locate_block_template if no block template is found
+ $fallback = locate_template( dirname( __FILE__ ) . "/templates/$template_slug.html" );
+
+ // This internally sets the $template_slug to be the active template.
+ $template = locate_block_template( $fallback, $template_slug, array() );
+
+ if ( ! empty( $template ) ) {
+ load_template( $template );
+ exit;
+ }
+ } else {
+ // TODO: improve this error handling
+ wp_redirect( home_url() );
+ exit();
+ }
+ }
+ }
+
+} // DevHub_Redirects
+
+DevHub_Redirects::init();
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/registrations.php b/source/wp-content/themes/wpr-developer-2024/inc/registrations.php
new file mode 100644
index 000000000..89bf02eaf
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/registrations.php
@@ -0,0 +1,325 @@
+ 'reference/functions',
+ 'label' => __( 'Functions', 'wporg' ),
+ 'labels' => array(
+ 'name' => __( 'Functions', 'wporg' ),
+ 'singular_name' => __( 'Function', 'wporg' ),
+ 'all_items' => __( 'Functions', 'wporg' ),
+ 'new_item' => __( 'New Function', 'wporg' ),
+ 'add_new' => __( 'Add New', 'wporg' ),
+ 'add_new_item' => __( 'Add New Function', 'wporg' ),
+ 'edit_item' => __( 'Edit Function', 'wporg' ),
+ 'view_item' => __( 'View Function', 'wporg' ),
+ 'search_items' => __( 'Search Functions', 'wporg' ),
+ 'not_found' => __( 'No Functions found', 'wporg' ),
+ 'not_found_in_trash' => __( 'No Functions found in trash', 'wporg' ),
+ 'parent_item_colon' => __( 'Parent Function', 'wporg' ),
+ 'menu_name' => __( 'Functions', 'wporg' ),
+ ),
+ 'menu_icon' => 'dashicons-editor-code',
+ 'public' => true,
+ 'rewrite' => array(
+ 'feeds' => false,
+ 'slug' => 'reference/functions',
+ 'with_front' => false,
+ ),
+ 'supports' => $supports,
+ 'show_in_rest' => true,
+ ) );
+
+ // Rewrite rules. The more specific rules like `/embed` must be first, to override the more generic rules.
+ add_rewrite_rule( 'reference/classes/page/([0-9]{1,})/?$', 'index.php?post_type=wp-parser-class&paged=$matches[1]', 'top' );
+ add_rewrite_rule( 'reference/classes/([^/]+)/embed/?$', 'index.php?post_type=wp-parser-class&name=$matches[1]&embed=true', 'top' );
+ add_rewrite_rule( 'reference/classes/([^/]+)/([^/]+)/embed/?$', 'index.php?post_type=wp-parser-method&name=$matches[1]-$matches[2]&embed=true', 'top' );
+ add_rewrite_rule( 'reference/classes/([^/]+)/([^/]+)/?$', 'index.php?post_type=wp-parser-method&name=$matches[1]-$matches[2]', 'top' );
+
+ // Classes
+ register_post_type( 'wp-parser-class', array(
+ 'has_archive' => 'reference/classes',
+ 'label' => __( 'Classes', 'wporg' ),
+ 'labels' => array(
+ 'name' => __( 'Classes', 'wporg' ),
+ 'singular_name' => __( 'Class', 'wporg' ),
+ 'all_items' => __( 'Classes', 'wporg' ),
+ 'new_item' => __( 'New Class', 'wporg' ),
+ 'add_new' => __( 'Add New', 'wporg' ),
+ 'add_new_item' => __( 'Add New Class', 'wporg' ),
+ 'edit_item' => __( 'Edit Class', 'wporg' ),
+ 'view_item' => __( 'View Class', 'wporg' ),
+ 'search_items' => __( 'Search Classes', 'wporg' ),
+ 'not_found' => __( 'No Classes found', 'wporg' ),
+ 'not_found_in_trash' => __( 'No Classes found in trash', 'wporg' ),
+ 'parent_item_colon' => __( 'Parent Class', 'wporg' ),
+ 'menu_name' => __( 'Classes', 'wporg' ),
+ ),
+ 'menu_icon' => 'dashicons-editor-code',
+ 'public' => true,
+ 'rewrite' => array(
+ 'feeds' => false,
+ 'slug' => 'reference/classes',
+ 'with_front' => false,
+ ),
+ 'supports' => $supports,
+ 'show_in_rest' => true,
+ ) );
+
+ // Hooks
+ register_post_type( 'wp-parser-hook', array(
+ 'has_archive' => 'reference/hooks',
+ 'label' => __( 'Hooks', 'wporg' ),
+ 'labels' => array(
+ 'name' => __( 'Hooks', 'wporg' ),
+ 'singular_name' => __( 'Hook', 'wporg' ),
+ 'all_items' => __( 'Hooks', 'wporg' ),
+ 'new_item' => __( 'New Hook', 'wporg' ),
+ 'add_new' => __( 'Add New', 'wporg' ),
+ 'add_new_item' => __( 'Add New Hook', 'wporg' ),
+ 'edit_item' => __( 'Edit Hook', 'wporg' ),
+ 'view_item' => __( 'View Hook', 'wporg' ),
+ 'search_items' => __( 'Search Hooks', 'wporg' ),
+ 'not_found' => __( 'No Hooks found', 'wporg' ),
+ 'not_found_in_trash' => __( 'No Hooks found in trash', 'wporg' ),
+ 'parent_item_colon' => __( 'Parent Hook', 'wporg' ),
+ 'menu_name' => __( 'Hooks', 'wporg' ),
+ ),
+ 'menu_icon' => 'dashicons-editor-code',
+ 'public' => true,
+ 'rewrite' => array(
+ 'feeds' => false,
+ 'slug' => 'reference/hooks',
+ 'with_front' => false,
+ ),
+ 'supports' => $supports,
+ 'show_in_rest' => true,
+ ) );
+
+ // Methods
+ register_post_type( 'wp-parser-method', array(
+ 'has_archive' => 'reference/methods',
+ 'label' => __( 'Methods', 'wporg' ),
+ 'labels' => array(
+ 'name' => __( 'Methods', 'wporg' ),
+ 'singular_name' => __( 'Method', 'wporg' ),
+ 'all_items' => __( 'Methods', 'wporg' ),
+ 'new_item' => __( 'New Method', 'wporg' ),
+ 'add_new' => __( 'Add New', 'wporg' ),
+ 'add_new_item' => __( 'Add New Method', 'wporg' ),
+ 'edit_item' => __( 'Edit Method', 'wporg' ),
+ 'view_item' => __( 'View Method', 'wporg' ),
+ 'search_items' => __( 'Search Methods', 'wporg' ),
+ 'not_found' => __( 'No Methods found', 'wporg' ),
+ 'not_found_in_trash' => __( 'No Methods found in trash', 'wporg' ),
+ 'parent_item_colon' => __( 'Parent Method', 'wporg' ),
+ 'menu_name' => __( 'Methods', 'wporg' ),
+ ),
+ 'menu_icon' => 'dashicons-editor-code',
+ 'public' => true,
+ 'rewrite' => array(
+ 'feeds' => false,
+ 'slug' => 'classes',
+ 'with_front' => false,
+ ),
+ 'supports' => $supports,
+ 'show_in_rest' => true,
+ ) );
+ }
+
+ /**
+ * Registers taxonomies.
+ */
+ public static function register_taxonomies() {
+ // Files
+ register_taxonomy( 'wp-parser-source-file', \DevHub\get_parsed_post_types(), array(
+ 'label' => __( 'Files', 'wporg' ),
+ 'labels' => array(
+ 'name' => __( 'Files', 'wporg' ),
+ 'singular_name' => _x( 'File', 'taxonomy general name', 'wporg' ),
+ 'search_items' => __( 'Search Files', 'wporg' ),
+ 'popular_items' => null,
+ 'all_items' => __( 'All Files', 'wporg' ),
+ 'parent_item' => __( 'Parent File', 'wporg' ),
+ 'parent_item_colon' => __( 'Parent File:', 'wporg' ),
+ 'edit_item' => __( 'Edit File', 'wporg' ),
+ 'update_item' => __( 'Update File', 'wporg' ),
+ 'add_new_item' => __( 'New File', 'wporg' ),
+ 'new_item_name' => __( 'New File', 'wporg' ),
+ 'separate_items_with_commas' => __( 'Files separated by comma', 'wporg' ),
+ 'add_or_remove_items' => __( 'Add or remove Files', 'wporg' ),
+ 'choose_from_most_used' => __( 'Choose from the most used Files', 'wporg' ),
+ 'menu_name' => __( 'Files', 'wporg' ),
+ ),
+ 'public' => true,
+ // Hierarchical x 2 to enable (.+) rather than ([^/]+) for rewrites.
+ 'hierarchical' => true,
+ 'rewrite' => array( 'with_front' => false, 'slug' => 'reference/files', 'hierarchical' => true ),
+ 'sort' => false,
+ 'update_count_callback' => '_update_post_term_count',
+ 'show_in_rest' => true,
+ ) );
+
+ // Package
+ register_taxonomy( 'wp-parser-package', \DevHub\get_parsed_post_types(), array(
+ 'hierarchical' => true,
+ 'label' => '@package',
+ 'public' => true,
+ 'rewrite' => array( 'with_front' => false, 'slug' => 'reference/package' ),
+ 'sort' => false,
+ 'update_count_callback' => '_update_post_term_count',
+ 'show_in_rest' => true,
+ ) );
+
+ // @since
+ register_taxonomy( 'wp-parser-since', \DevHub\get_parsed_post_types(), array(
+ 'hierarchical' => true,
+ 'label' => __( '@since', 'wporg' ),
+ 'public' => true,
+ 'rewrite' => array( 'with_front' => false, 'slug' => 'reference/since' ),
+ 'sort' => false,
+ 'update_count_callback' => '_update_post_term_count',
+ 'show_in_rest' => true,
+ ) );
+ }
+
+ /**
+ * Registers P2P post relationships.
+ */
+ public static function register_post_relationships() {
+
+ /*
+ * Functions to functions, methods and hooks
+ */
+ p2p_register_connection_type( array(
+ 'name' => 'functions_to_functions',
+ 'from' => 'wp-parser-function',
+ 'to' => 'wp-parser-function',
+ 'can_create_post' => false,
+ 'self_connections' => true,
+ 'from_query_vars' => array( 'orderby' => 'post_title', 'order' => 'ASC' ),
+ 'to_query_vars' => array( 'orderby' => 'post_title', 'order' => 'ASC' ),
+ 'title' => array( 'from' => __( 'Uses Functions', 'wporg' ), 'to' => __( 'Used by Functions', 'wporg' ) ),
+ ) );
+
+ p2p_register_connection_type( array(
+ 'name' => 'functions_to_methods',
+ 'from' => 'wp-parser-function',
+ 'to' => 'wp-parser-method',
+ 'can_create_post' => false,
+ 'from_query_vars' => array( 'orderby' => 'post_title', 'order' => 'ASC' ),
+ 'to_query_vars' => array( 'orderby' => 'post_title', 'order' => 'ASC' ),
+ 'title' => array( 'from' => __( 'Uses Methods', 'wporg' ), 'to' => __( 'Used by Functions', 'wporg' ) ),
+ ) );
+
+ p2p_register_connection_type( array(
+ 'name' => 'functions_to_hooks',
+ 'from' => 'wp-parser-function',
+ 'to' => 'wp-parser-hook',
+ 'can_create_post' => false,
+ 'from_query_vars' => array( 'orderby' => 'post_title', 'order' => 'ASC' ),
+ 'to_query_vars' => array( 'orderby' => 'post_title', 'order' => 'ASC' ),
+ 'title' => array( 'from' => __( 'Uses Hooks', 'wporg' ), 'to' => __( 'Used by Functions', 'wporg' ) ),
+ ) );
+
+ /*
+ * Methods to functions, methods and hooks
+ */
+ p2p_register_connection_type( array(
+ 'name' => 'methods_to_functions',
+ 'from' => 'wp-parser-method',
+ 'to' => 'wp-parser-function',
+ 'can_create_post' => false,
+ 'from_query_vars' => array( 'orderby' => 'post_title', 'order' => 'ASC' ),
+ 'to_query_vars' => array( 'orderby' => 'post_title', 'order' => 'ASC' ),
+ 'title' => array( 'from' => __( 'Uses Functions', 'wporg' ), 'to' => __( 'Used by Methods', 'wporg' ) ),
+ ) );
+
+ p2p_register_connection_type( array(
+ 'name' => 'methods_to_methods',
+ 'from' => 'wp-parser-method',
+ 'to' => 'wp-parser-method',
+ 'can_create_post' => false,
+ 'self_connections' => true,
+ 'from_query_vars' => array( 'orderby' => 'post_title', 'order' => 'ASC' ),
+ 'to_query_vars' => array( 'orderby' => 'post_title', 'order' => 'ASC' ),
+ 'title' => array( 'from' => __( 'Uses Methods', 'wporg' ), 'to' => __( 'Used by Methods', 'wporg' ) ),
+ ) );
+
+ p2p_register_connection_type( array(
+ 'name' => 'methods_to_hooks',
+ 'from' => 'wp-parser-method',
+ 'to' => 'wp-parser-hook',
+ 'can_create_post' => false,
+ 'from_query_vars' => array( 'orderby' => 'post_title', 'order' => 'ASC' ),
+ 'to_query_vars' => array( 'orderby' => 'post_title', 'order' => 'ASC' ),
+ 'title' => array( 'from' => __( 'Used by Methods', 'wporg' ), 'to' => __( 'Uses Hooks', 'wporg' ) ),
+ ) );
+
+ }
+
+ /**
+ * Adds post types to Jetpack sitemaps.
+ *
+ * @param array $post_types Post types.
+ * @return array
+ */
+ public static function jetpack_sitemap_post_types( $post_types ) {
+ $post_types[] = 'wp-parser-function';
+ $post_types[] = 'wp-parser-class';
+ $post_types[] = 'wp-parser-method';
+ $post_types[] = 'wp-parser-hook';
+
+ return $post_types;
+ }
+
+} // DevHub_Registrations
+
+DevHub_Registrations::init();
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/rest-api.php b/source/wp-content/themes/wpr-developer-2024/inc/rest-api.php
new file mode 100644
index 000000000..288bfcfe9
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/rest-api.php
@@ -0,0 +1,36 @@
+get_post_type() === $post_type ) {
+ $label = __( 'REST API Handbook', 'wporg' );
+ }
+
+ return $label;
+ }
+}
+
+DevHub_REST_API::instance()->init();
+
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/search.php b/source/wp-content/themes/wpr-developer-2024/inc/search.php
new file mode 100644
index 000000000..57358680b
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/search.php
@@ -0,0 +1,298 @@
+is_main_query() && isset( $_GET['post_type'] ) ) {
+ // Get list of valid parsed post types specified in query.
+ $valid_post_types = array_intersect( (array) $_GET['post_type'], DevHub\get_parsed_post_types() );
+
+ // If no valid post types were specified, then request is a 404.
+ if ( ! $valid_post_types ) {
+ $query->set_404();
+ }
+ }
+ }
+
+ /**
+ * Query modifications.
+ *
+ * @param \WP_Query $query
+ */
+ public static function pre_get_posts( $query ) {
+ // Don't modify anything if not a non-admin main search query.
+ if (
+ (
+ // Not the admin
+ is_admin() ||
+ // Not non-main queries / non-searches
+ ( ! $query->is_main_query() || ! $query->is_search() )
+ ) &&
+ // but yes if it's the autocomplete search (which is admin, and not the main query).
+ ! $query->get( '_autocomplete_search' )
+ ) {
+ return;
+ }
+
+ // Order search result in ascending order by title.
+ $query->set( 'orderby', 'title' );
+ $query->set( 'order', 'ASC' );
+
+ // Set the default number of posts per page.
+ $query->set( 'posts_per_page', 25 );
+
+ // Separates searches for handbook pages from non-handbook pages depending on
+ // whether the search was performed within context of a handbook page or not.
+ if ( $query->is_handbook ) {
+ // Search only in current handbook post type.
+ // Just to make sure. post type should already be set.
+ $query->set( 'post_type', wporg_get_current_handbook() );
+ } else {
+ // If user has '()' at end of a search string, assume they want a specific function/method.
+ $s = htmlentities( $query->get( 's' ) );
+ if ( '()' === substr( $s, -2 ) || '(' == substr( $s, -1 ) ) {
+ // Enable exact search.
+ $query->set( 'exact', true );
+ // Modify the search query to omit the parentheses.
+ $query->set( 's', trim( $s, '()' ) );
+ // Restrict search to function-like content.
+ $query->set( 'post_type', array( 'wp-parser-function', 'wp-parser-method' ) );
+ }
+ }
+
+ // Get post types (if used, or set above)
+ $qv_post_types = array_filter( (array) $query->get( 'post_type' ) );
+ $qv_post_types = array_map( 'sanitize_key', $qv_post_types );
+
+ if ( ! $qv_post_types ) {
+ // Record the fact no post types were explicitly supplied.
+ $query->is_empty_post_type_search = true;
+
+ // Not a handbook page, or exact search, or filters used.
+ // Fallback to parsed post types.
+ $query->set( 'post_type', DevHub\get_parsed_post_types() );
+ }
+ }
+
+ /**
+ * Filter the SQL for the ORDER BY clause for search queries.
+ *
+ * Adds ORDER BY condition with spaces replaced with underscores in 'post_title'.
+ * Adds ORDER BY condition to order by title length.
+ *
+ * @param string $orderby The ORDER BY clause of the query.
+ * @param WP_Query $query The WP_Query instance (passed by reference).
+ * @return string Filtered order by clause
+ */
+ public static function search_posts_orderby( $orderby, $query ) {
+ global $wpdb;
+
+ if ( $query->is_main_query() && is_search() && ! $query->get( 'exact' ) ) {
+
+ $search_order_by_title = $query->get( 'search_orderby_title' );
+
+ // Check if search_orderby_title is set by WP_Query::parse_search.
+ if ( is_array( $search_order_by_title ) && $search_order_by_title ) {
+
+ // Get search orderby query.
+ $orderby = self::parse_search_order( $query->query_vars );
+
+ // Add order by title length.
+ $orderby .= " , CHAR_LENGTH( $wpdb->posts.post_title ) ASC, $wpdb->posts.post_title ASC";
+ }
+ }
+
+ return $orderby;
+ }
+
+ /**
+ * Generate SQL for the ORDER BY condition based on passed search terms.
+ *
+ * Similar to WP_Query::parse_search_order.
+ * Adds ORDER BY condition with spaces replaced with underscores in 'post_title'.
+ *
+ * @global wpdb $wpdb WordPress database abstraction object.
+ *
+ * @param array $q Query variables.
+ * @return string ORDER BY clause.
+ */
+ public static function parse_search_order( $q ) {
+ global $wpdb;
+
+ if ( $q['search_terms_count'] > 1 ) {
+ $num_terms = count( $q['search_orderby_title'] );
+
+ // If the search terms contain negative queries, don't bother ordering by sentence matches.
+ $like = $_like = '';
+ if ( ! preg_match( '/(?:\s|^)\-/', $q['s'] ) ) {
+ $like = '%' . $wpdb->esc_like( $q['s'] ) . '%';
+ }
+
+ $search_orderby = '';
+
+ // Sentence match in 'post_title'.
+ if ( $like ) {
+ $search_orderby .= $wpdb->prepare( "WHEN $wpdb->posts.post_title LIKE %s THEN 1 ", $like );
+ $_like = str_replace( '-', '_', sanitize_title_with_dashes( $q['s'] ) );
+ $_like = '%' . $wpdb->esc_like( $_like ) . '%';
+ if ( $_like !== $like ) {
+ // Sentence match in 'post_title' with spaces replaced with underscores.
+ $search_orderby .= $wpdb->prepare( "WHEN $wpdb->posts.post_title LIKE %s THEN 2 ", $_like );
+ }
+ }
+
+ // Sanity limit, sort as sentence when more than 6 terms.
+ // (few searches are longer than 6 terms and most titles are not)
+ if ( $num_terms < 7 ) {
+ // all words in title
+ $search_orderby .= 'WHEN ' . implode( ' AND ', $q['search_orderby_title'] ) . ' THEN 3 ';
+ // any word in title, not needed when $num_terms == 1
+ if ( $num_terms > 1 )
+ $search_orderby .= 'WHEN ' . implode( ' OR ', $q['search_orderby_title'] ) . ' THEN 4 ';
+ }
+
+ // Sentence match in 'post_content'.
+ if ( $like ) {
+ $search_orderby .= $wpdb->prepare( "WHEN $wpdb->posts.post_content LIKE %s THEN 5 ", $like );
+ }
+
+ if ( $search_orderby ) {
+ $search_orderby = '(CASE ' . $search_orderby . 'ELSE 6 END)';
+ }
+ } else {
+ // Single word or sentence search.
+ $search_orderby = reset( $q['search_orderby_title'] ) . ' DESC';
+ }
+
+ return $search_orderby;
+ }
+
+ /**
+ * Redirects empty searches.
+ *
+ * @access public
+ *
+ * @param array $posts Array of posts after the main query
+ * @param WP_Query $query WP_Query object
+ * @return array
+ *
+ */
+ public static function redirect_empty_search( $posts, $query ) {
+ $redirect = '';
+
+ // If request is an empty search.
+ if ( $query->is_main_query() && $query->is_search() && ! trim( get_search_query() ) ) {
+ // If search is filtered.
+ if ( isset( $_GET['post_type'] ) ) {
+ $post_types = $_GET['post_type'];
+
+ // Redirect to post type archive if only a single parsed post type is defined.
+ if ( 1 === count( $post_types ) ) {
+ // Note: By this point, via `invalid_post_type_filter_404()`, we know
+ // the post type is valid.
+ $redirect = get_post_type_archive_link( $post_types[0] );
+ }
+ // Otherwise, redirect to Code Reference landing page.
+ else {
+ $redirect = home_url( '/reference' );
+ }
+ }
+ // Else search is unfiltered, so redirect to Code Reference landing page.
+ else {
+ $redirect = home_url( '/reference' );
+ }
+ }
+
+ // Empty unfiltered search should redirect to Code Reference landing page.
+ if ( $redirect ) {
+ wp_safe_redirect( $redirect );
+ exit;
+ }
+
+ return $posts;
+ }
+
+ /**
+ * Potentially reruns a search if no posts were found.
+ *
+ * Situations:
+ * - For an exact search, try again with the same criteria but without exactness
+ * - For a search containing characters that can be converted to HTML entities,
+ * try again after converting those characters
+ *
+ * @access public
+ *
+ * @param array $posts Array of posts after the main query
+ * @param WP_Query $query WP_Query object
+ * @return array
+ */
+ public static function rerun_search_without_results( $posts, $query ) {
+ if ( $query->is_search() && ! $query->found_posts ) {
+ $s = $query->get( 's' );
+
+ // Return exact search without exactness.
+ if ( true === $query->get( 'exact' ) ) {
+ $query->set( 'exact', false );
+ $posts = $query->get_posts();
+ }
+
+ // Retry HTML entity convertible search term after such a conversion.
+ elseif ( $s != ( $she = htmlentities( $s ) ) ) {
+ $query->set( 's', $she );
+ $posts = $query->get_posts();
+ }
+ }
+
+ return $posts;
+ }
+
+} // DevHub_Search
+
+DevHub_Search::init();
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/shortcode-dashicons.php b/source/wp-content/themes/wpr-developer-2024/inc/shortcode-dashicons.php
new file mode 100644
index 000000000..38b058caf
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/shortcode-dashicons.php
@@ -0,0 +1,249 @@
+
+
+ ',
+ __( 'The Dashicons project is no longer accepting icon requests. Here’s why: Next steps for Dashicons .', 'wporg' )
+ );
+
+ ob_start() ?>
+
+ >
+
+
role="main">
+
+
+
+
+
+
+
+
+ $group_info ) :
+ printf(
+ '
' . "\n\n",
+ esc_attr( 'icons-' . sanitize_title( $group ) ),
+ $group_info['label'],
+ esc_attr( 'icons-' . sanitize_title( $group ) ),
+ $group_info['label']
+ );
+
+ echo "\n";
+
+ echo "
\n";
+ foreach ( $group_info['icons'] as $name => $info ) {
+ printf(
+ '%s ' . "\n",
+ esc_attr( $info['keywords'] ),
+ esc_attr( $info['code'] ),
+ esc_attr( $name ),
+ $info['label']
+ );
+ }
+ echo " \n";
+ endforeach;
+ ?>
+
+
+
+
+
+
+
+
+
+ register_post_type() and add_menu_page()
, which both have an option to set an icon. To show the current icon, you should pass in %3$s.', 'wporg' ),
+ 'https://developer.wordpress.org/reference/functions/register_post_type/',
+ 'https://developer.wordpress.org/reference/functions/add_menu_page/',
+ '\'dashicons-{icon} \'
'
+ ); ?>
+
+
+
+
+ register_post_type() , set menu_icon
in the arguments array.', 'wporg' ),
+ 'https://developer.wordpress.org/reference/functions/register_post_type/'
+ ); ?>
+
+
<?php
+/**
+* Register the Product post type with a Dashicon.
+*
+* @see register_post_type()
+*/
+function wpdocs_create_post_type() {
+ register_post_type( 'acme_product',
+ array(
+ 'labels' => array(
+ 'name' => __( 'Products', 'textdomain' ),
+ 'singular_name' => __( 'Product', 'textdomain' )
+ ),
+ 'public' => true,
+ 'has_archive' => true,
+ 'menu_icon' => 'dashicons-products',
+ )
+ );
+}
+add_action( 'init', 'wpdocs_create_post_type', 0 );
+
+
+
+ add_menu_page() accepts a parameter after the callback function for an icon URL, which can also accept a dashicons class.', 'wporg' ),
+ 'https://developer.wordpress.org/reference/functions/add_menu_page/'
+ ); ?>
+
+
<?php
+/**
+* Register a menu page with a Dashicon.
+*
+* @see add_menu_page()
+*/
+function wpdocs_add_my_custom_menu() {
+ // Add an item to the menu.
+ add_menu_page(
+ __( 'My Page', 'textdomain' ),
+ __( 'My Title', 'textdomain' ),
+ 'manage_options',
+ 'my-page',
+ 'my_admin_page_function',
+ 'dashicons-admin-media'
+ );
+}
+
+
+
+
dashicons-before and dashicons
, and they can be thought of as setting up dashicons (since you still need your icon's class, too).", 'wporg' ); ?>
+
+
+
+
dashicons-before class. This can be added right to the element with text.', 'wporg' ); ?>
+
+
+<h2 class="dashicons-before dashicons-smiley"></h2>
+
+
+
dashicons class. Note that here, you need extra markup specifically for the icon.', 'wporg' ); ?>
+
+
+<h2><span class="dashicons dashicons-smiley"></span> </h2>
+
+
+
+
+
+
+
+
+
+ registerBlockType function accepts a parameter "icon" which accepts the name of a dashicon. The provided example is truncated. See the full example in the Block Editor Handbook.', 'wporg' ),
+ 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/writing-your-first-block-type/#registering-the-block'
+ ); ?>
+
+
+registerBlockType( 'gutenberg-examples/example-01-basic-esnext', {
+ apiVersion: 2,
+ title: 'Example: Basic (esnext)',
+ icon: 'universal-access-alt',
+ category: 'design',
+ example: {},
+ edit() {},
+ save() {},
+} );
+
+
+ Dashicon component is available. See the related documentation in the Block Editor Handbook.', 'wporg' ),
+ 'https://developer.wordpress.org/block-editor/reference-guides/components/dashicon/'
+ ); ?>
+
+
+import { Dashicon } from '@wordpress/components';
+
+const MyDashicon = () => (
+ <div>
+ <Dashicon icon="admin-home" />
+ <Dashicon icon="products" />
+ <Dashicon icon="wordpress" />
+ </div>
+);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ name, 0, -2 ) : '';
+ }
+);
+
+/**
+ * Get the current WordPress version link.
+ */
+add_shortcode(
+ 'wordpress_version_link',
+ function() {
+ $version = get_current_version_term();
+ return $version && ! is_wp_error( $version ) ? esc_attr( get_term_link( $version, 'wp-parser-since' ) ) : '';
+ }
+);
+
+/**
+ * Get the link to edit the page.
+ */
+add_shortcode(
+ 'article_edit_link',
+ function() {
+ global $post;
+ $markdown_source = get_markdown_edit_link( $post->ID );
+ if ( $markdown_source ) {
+ return esc_url( $markdown_source );
+ }
+ return is_user_logged_in() ? get_edit_post_link() : wp_login_url( get_permalink() );
+ }
+);
+
+/**
+ * Get the link to the GH commit history.
+ */
+add_shortcode(
+ 'article_changelog_link',
+ function() {
+ global $post;
+ $markdown_source = get_markdown_edit_link( $post->ID );
+ // If this is a github page, use the edit URL to generate the
+ // commit history URL
+ if ( str_contains( $markdown_source, 'github.com' ) ) {
+ return str_replace( '/edit/', '/commits/', $markdown_source );
+ }
+ return '#';
+ }
+);
+
+/**
+ * Get the title of the article.
+ */
+add_shortcode(
+ 'article_title',
+ function() {
+ global $post;
+ return get_the_title();
+ }
+);
+
+/**
+ * Only display the 'Last updated' if the modified date is later than the publishing date.
+ */
+add_shortcode(
+ 'last_updated',
+ function() {
+ global $post;
+ if ( get_the_modified_date( 'Ymdhi', $post->ID ) > get_the_date( 'Ymdhi', $post->ID ) ) {
+ return '' . esc_html__( 'Last updated', 'wporg' ) . '
';
+ }
+ return '';
+ }
+);
+
+/**
+ * Get the markdown link.
+ *
+ * @param int $post_id Post ID.
+ */
+function get_markdown_edit_link( $post_id ) {
+ $markdown_source = get_post_meta( $post_id, 'wporg_markdown_source', true );
+ if ( ! $markdown_source ) {
+ return;
+ }
+
+ if ( 'github.com' !== parse_url( $markdown_source, PHP_URL_HOST ) ) {
+ return $markdown_source;
+ }
+
+ if ( preg_match( '!^https?://github.com/(?P[^/]+/[^/]+)/(?Pblob|edit)/(?P.*)$!i', $markdown_source, $m ) ) {
+ if ( 'edit' === $m['editblob'] ) {
+ return $markdown_source;
+ }
+
+ $markdown_source = "https://github.com/{$m['repo']}/edit/{$m['branchfile']}";
+ }
+
+ return $markdown_source;
+}
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/template-tags.php b/source/wp-content/themes/wpr-developer-2024/inc/template-tags.php
new file mode 100644
index 000000000..8a310ee7d
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/template-tags.php
@@ -0,0 +1,2016 @@
+max_num_pages < 2 ) {
+ return;
+ }
+ ?>
+
+
+
+
+
+
+
← Older posts', 'wporg' ) ); ?>
+
+
+
+
→', 'wporg' ) ); ?>
+
+
+
+
+
+ post_parent ) : get_adjacent_post( false, '', true );
+ $next = get_adjacent_post( false, '', false );
+
+ if ( ! $next && ! $previous ) {
+ return;
+ }
+ ?>
+
+
+
+
+
+ ← %title', 'Previous post link', 'wporg' ) ); ?>
+ →', 'Next post link', 'wporg' ) ); ?>
+
+
+
+
+ array( $post_id ),
+ 'type' => 'comment',
+ 'status' => 'approve',
+ 'include_unapproved' => array_filter( array( get_current_user_id() ) ),
+ );
+
+ if ( is_super_admin() ) {
+ $defaults['status'] = 'all';
+ }
+
+ $args = wp_parse_args( $args, $defaults );
+ $comments = get_comments( $args );
+
+ if ( ! $comments ) {
+ return;
+ }
+
+ // Check if the current page is a reply to a note.
+ $reply_id = 0;
+ if ( isset( $_GET['replytocom'] ) && $_GET['replytocom'] ) {
+ /* Javascript uses preventDefault() when clicking links with '?replytocom={comment_ID}'
+ * We assume Javascript is disabled when visiting a page with this query var.
+ * There are no consequences if Javascript is enabled.
+ */
+ $reply_id = absint( $_GET['replytocom'] );
+ }
+
+ $order = $children = array();
+ $voting = class_exists( 'DevHub_User_Contributed_Notes_Voting' );
+
+ // Remove child notes and add the vote count order for parent notes.
+ foreach ( $comments as $key => $comment ) {
+ if ( 0 === (int) $comment->comment_parent ) {
+ $vote_count = $voting ? (int) DevHub_User_Contributed_Notes_Voting::count_votes( $comment->comment_ID, 'difference' ) : 0;
+ $order[ $key ] = $vote_count;
+ } else {
+ unset( $comments[ $key ] );
+ $children[ $comment->comment_parent ][] = $comment;
+ }
+ }
+
+ $show_editor = false;
+
+ // Add children notes to their parents.
+ foreach ( $comments as $key => $comment ) {
+ $comments[ $key ]->child_notes = array();
+ $comments[ $key ]->show_editor = false;
+
+ if ( array_key_exists( $comment->comment_ID, $children ) ) {
+ $comments[ $key ]->child_notes = array_reverse( $children[ $comment->comment_ID ] );
+ }
+
+ if ( ! $show_editor && ( $reply_id && ( $reply_id === (int) $comment->comment_ID ) ) ) {
+ /* The query var 'replytocom' is used and the value is the same as the current comment ID.
+ * We show the editor for the current comment because we assume Javascript is disabled.
+ * If Javascript is not disabled the editor is hidden (as normal) by the class 'hide-if-js'.
+ */
+ $comments[ $key ]->show_editor = true;
+ $show_editor = true;
+ }
+ }
+
+ // sort the posts by votes
+ array_multisort( $order, SORT_DESC, $comments );
+
+ return $comments;
+ }
+ endif;
+
+ if ( ! function_exists( 'wporg_developer_list_notes' ) ) :
+ /**
+ * List user contributed notes.
+ *
+ * @param array $comments Array with comment objects.
+ * @param array $args Comment display arguments.
+ */
+ function wporg_developer_list_notes( $comments, $args ) {
+ $is_user_content = class_exists( 'DevHub_User_Submitted_Content' );
+ $is_user_logged_in = is_user_logged_in();
+ $can_user_post_note = DevHub\can_user_post_note( true, get_the_ID() );
+ $is_user_verified = $is_user_logged_in && $can_user_post_note;
+
+ $args['updated_note'] = 0;
+ if ( isset( $_GET['updated-note'] ) && $_GET['updated-note'] ) {
+ $args['updated_note'] = absint( $_GET['updated-note'] );
+ }
+
+ foreach ( $comments as $comment ) {
+
+ $comment_id = $comment->comment_ID;
+ $comment_count = count( $comment->child_notes );
+
+ // Display parent comment.
+ wporg_developer_user_note( $comment, $args, 1 );
+
+ /* Use hide-if-js class to hide the feedback section if Javascript is enabled.
+ * Users can display the section with Javascript.
+ */
+ echo "\n";
+
+ // Feedback links to log in, add feedback or show feedback.
+ echo "\n";
+ if ( $can_user_post_note ) {
+ $feedback_link = trailingslashit( get_permalink() ) . "?replytocom={$comment_id}#feedback-editor-{$comment_id}";
+ $display = '';
+ if ( ! $is_user_logged_in ) {
+ $class = 'login';
+ $feedback_text = __( 'Log in to add feedback', 'wporg' );
+ $feedback_link = 'https://login.wordpress.org/?redirect_to=' . urlencode( $feedback_link );
+ } else {
+ $class ='add';
+ $feedback_text = __( 'Add feedback', 'wporg' );
+
+ /* Hide the feedback link if the current user is logged in and the
+ * feedback editor is displayed (because Javascript is disabled).
+ * If Javascript is enabled the editor is hidden and the feedback link is displayed (as normal).
+ */
+ $display = $is_user_verified && $comment->show_editor ? ' style="display:none"' : '';
+ }
+ echo '' . $feedback_text . ' ';
+ }
+
+ // close parent list item
+ echo " \n\n\n";
+ }
+ }
+ endif;
+
+
+ if ( ! function_exists( 'wporg_developer_user_note' ) ) :
+ /**
+ * Template for user contributed notes.
+ *
+ * @param object $comment Comment object.
+ * @param array $args Arguments.
+ * @param int $depth Nested comment depth.
+ */
+ function wporg_developer_user_note( $comment, $args, $depth ) {
+ $GLOBALS['comment'] = $comment;
+ $GLOBALS['comment_depth'] = $depth;
+
+ static $note_number = 0;
+
+ $approved = ( 0 < (int) $comment->comment_approved ) ? true : false;
+ $is_parent = ( 0 === (int) $comment->comment_parent ) ? true : false;
+ $is_voting = class_exists( 'DevHub_User_Contributed_Notes_Voting' );
+ $count = $is_voting ? (int) DevHub_User_Contributed_Notes_Voting::count_votes( $comment->comment_ID, 'difference' ) : 0;
+ $curr_user_note = $is_voting ? (bool) DevHub_User_Contributed_Notes_Voting::is_current_user_note( $comment->comment_ID ) : false;
+ $edited_note_id = isset( $args['updated_note'] ) ? $args['updated_note'] : 0;
+ $is_edited_note = ( $edited_note_id === (int) $comment->comment_ID );
+ $note_author = \DevHub\get_note_author_link( $comment );
+ $can_edit_note = \DevHub\can_user_edit_note( $comment->comment_ID );
+ $has_edit_cap = current_user_can( 'edit_comment', $comment->comment_ID );
+
+ // CSS Classes
+ $comment_class = array();
+
+ if ( -1 > $count ) {
+ $comment_class[] = 'bad-note';
+ }
+
+ if ( $curr_user_note ) {
+ $comment_class[] = 'user-submitted-note';
+ }
+
+ if ( ! $approved ) {
+ $comment_class[] = 'user-note-moderated';
+ }
+
+ $date = sprintf( _x( '%1$s ago', '%1$s = human-readable time difference', 'wporg' ),
+ human_time_diff( get_comment_time( 'U' ),
+ current_time( 'timestamp' ) )
+ );
+ ?>
+
+
+ %2$s';
+ if ( get_the_time( 'U' ) !== get_the_modified_time( 'U' ) ) {
+ $time_string .= '%4$s ';
+ }
+
+ $time_string = sprintf( $time_string,
+ esc_attr( get_the_date( 'c' ) ),
+ esc_html( get_the_date() ),
+ esc_attr( get_the_modified_date( 'c' ) ),
+ esc_html( get_the_modified_date() )
+ );
+
+ printf( __( 'Posted on %1$s by %2$s ', 'wporg' ),
+ sprintf( '%2$s ',
+ esc_url( get_permalink() ),
+ $time_string
+ ),
+ sprintf( '%2$s ',
+ esc_url( get_author_posts_url( get_the_author_meta( 'ID' ) ) ),
+ esc_html( get_the_author() )
+ )
+ );
+ }
+ endif;
+
+ /**
+ * Returns true if a blog has more than 1 category.
+ */
+ function wporg_developer_categorized_blog() {
+ if ( false === ( $all_the_cool_cats = get_transient( 'all_the_cool_cats' ) ) ) {
+ // Create an array of all the categories that are attached to posts.
+ $all_the_cool_cats = get_categories( array(
+ 'hide_empty' => 1,
+ ) );
+
+ // Count the number of categories that are attached to the posts.
+ $all_the_cool_cats = count( $all_the_cool_cats );
+
+ set_transient( 'all_the_cool_cats', $all_the_cool_cats );
+ }
+
+ if ( '1' != $all_the_cool_cats ) {
+ // This blog has more than 1 category so wporg_developer_categorized_blog should return true.
+ return true;
+ } else {
+ // This blog has only 1 category so wporg_developer_categorized_blog should return false.
+ return false;
+ }
+ }
+
+ /**
+ * Flush out the transients used in wporg_developer_categorized_blog.
+ */
+ function wporg_developer_category_transient_flusher() {
+ // Like, beat it. Dig?
+ delete_transient( 'all_the_cool_cats' );
+ }
+
+ add_action( 'edit_category', 'wporg_developer_category_transient_flusher' );
+ add_action( 'save_post', 'wporg_developer_category_transient_flusher' );
+}
+
+namespace DevHub {
+
+ function wp_doc_comment( $comment, $args, $depth ) {
+ ?>
+ id="li-comment-">
+
+
+
+ ";
+ $current_version = get_current_version();
+
+ // Remove dev version suffix (e.g. 4.1-RC1 => 4.1)
+ if ( false !== strpos( $current_version, '-' ) ) {
+ list( $current_version, $dev_version ) = explode( '-', $current_version, 2 );
+ }
+ echo '$ignore_minor: ' . $ignore_minor . " ";
+ if ( $ignore_minor ) {
+ $version_parts = explode( '.', $current_version, 3 );
+ if ( count( $version_parts ) == 2 ) {
+ $version_parts[] = '0';
+ } else {
+ $version_parts[2] = '0';
+ }
+ $current_version = implode( '.', $version_parts );
+ }
+ echo '$current_version: ' . $current_version . " ";
+ $version = get_terms( 'wp-parser-since', array(
+ 'number' => '1',
+ 'order' => 'DESC',
+ 'slug' => $current_version,
+ ) );
+
+ return is_wp_error( $version ) ? $version : reset( $version );
+ }
+
+ /**
+ * Get an array of all parsed post types.
+ *
+ * @param string $labels If set to 'labels' post types with their labels are returned.
+ * @return array
+ */
+ function get_parsed_post_types( $labels = '' ) {
+ $post_types = array(
+ 'wp-parser-class' => __( 'Classes', 'wporg' ),
+ 'wp-parser-function' => __( 'Functions', 'wporg' ),
+ 'wp-parser-hook' => __( 'Hooks', 'wporg' ),
+ 'wp-parser-method' => __( 'Methods', 'wporg' ),
+ );
+
+ if ( 'labels' !== $labels ) {
+ return array_keys( $post_types );
+ }
+
+ return $post_types;
+ }
+
+ /**
+ * Checks if given post type is one of the parsed post types.
+ *
+ * @param null|string Optional. The post type. Default null.
+ * @return bool True if post has a parsed post type
+ */
+ function is_parsed_post_type( $post_type = null ) {
+ $post_type = $post_type ? $post_type : get_post_type();
+
+ return in_array( $post_type, get_parsed_post_types() );
+ }
+
+ /**
+ * Get the specific type of hook.
+ *
+ * @param int|WP_Post|null $post Optional. Post ID or post object. Default is global $post.
+ * @return string Either 'action', 'filter', or an empty string if not a hook post type.
+ */
+ function get_hook_type( $post = null ) {
+ $hook = '';
+
+ if ( 'wp-parser-hook' === get_post_type( $post ) ) {
+ $hook = get_post_meta( get_post_field( 'ID', $post ), '_wp-parser_hook_type', true );
+ }
+
+ return $hook;
+ }
+
+ /**
+ * Get site section root URL based on URL path.
+ *
+ * @return string
+ */
+ function get_site_section_url() {
+ return home_url( '/' );
+ }
+
+ /**
+ * Get site section title based on URL path.
+ *
+ * @return string
+ */
+ function get_site_section_title() {
+ $parts = explode( '/', $GLOBALS['wp']->request );
+ switch ( $parts[0] ) {
+ case 'resources':
+ case 'resource':
+ return sprintf( __( 'Developer Resources: %s', 'wporg' ), get_the_title() );
+ default:
+ if( is_page( 'reference' ) ) {
+ return __( 'Code Reference', 'wporg' );
+ }
+
+ return __( 'Developer Resources', 'wporg' );
+ }
+ }
+
+ /**
+ * Get post type name
+ *
+ * @param string $post_type
+ * @param bool $plural
+ *
+ * @return string
+ */
+ function get_post_type_name( $post_type = null, $plural = false ) {
+ if ( empty( $post_type ) ) {
+ $post_type = get_post_type();
+ }
+
+ $name = substr( $post_type, 6 );
+
+ if ( $plural ) {
+ $name .= ( 'class' == $name ) ? 'es' : 's';
+ }
+ return $name;
+ }
+
+ /**
+ * Returns the hook string.
+ *
+ * @param int $post_id
+ *
+ * @return string
+ */
+ function get_hook_type_name( $post_id ) {
+ $hook_type = get_post_meta( $post_id, '_wp-parser_hook_type', true );
+ if ( false !== strpos( $hook_type, 'action' ) ) {
+ if ( 'action_reference' === $hook_type ) {
+ $hook_type = 'do_action_ref_array';
+ } elseif ( 'action_deprecated' === $hook_type ) {
+ $hook_type = 'do_action_deprecated';
+ } else {
+ $hook_type = 'do_action';
+ }
+ } else {
+ if ( 'filter_reference' === $hook_type ) {
+ $hook_type = 'apply_filters_ref_array';
+ } elseif ( 'filter_deprecated' === $hook_type ) {
+ $hook_type = 'apply_filters_deprecated';
+ } else {
+ $hook_type = 'apply_filters';
+ }
+ }
+
+ return $hook_type;
+ }
+
+ /**
+ * Retrieve function name and arguments as signature string
+ *
+ * @param int $post_id
+ *
+ * @return string
+ */
+ function get_signature( $post_id = null ) {
+
+ if ( empty( $post_id ) ) {
+ $post_id = get_the_ID();
+ }
+
+ $args = get_post_meta( $post_id, '_wp-parser_args', true );
+ $tags = get_post_meta( $post_id, '_wp-parser_tags', true );
+ $signature = get_the_title( $post_id );
+ $params = get_params();
+ $args_strings = array();
+ $types = array();
+
+ if ( 'wp-parser-class' === get_post_type( $post_id ) ) {
+ return 'class ' . $signature . ' {}';
+ }
+
+ if ( $tags ) {
+ foreach ( $tags as $tag ) {
+ if ( is_array( $tag ) && 'param' == $tag['name'] ) {
+ $types[ $tag['variable'] ] = implode( '|', $tag['types'] );
+ }
+ }
+ }
+
+ // Decorate and return hook arguments.
+ if ( 'wp-parser-hook' === get_post_type( $post_id ) ) {
+ $hook_args = array();
+ foreach ( $types as $arg => $type ) {
+ $hook_args[] = ' ' . esc_html( $type ) . ' ' . esc_html( $arg ) . ' ';
+ }
+
+ $hook_type = get_hook_type_name( $post_id );
+
+ $delimiter = false !== strpos( $signature, '$' ) ? '"' : "'";
+ $signature = $delimiter . $signature . $delimiter;
+ $signature = '' . $hook_type . ' ( ' . $signature;
+ if ( $hook_args ) {
+ $signature .= ', ';
+ $signature .= implode( ', ', $hook_args );
+ }
+ $signature .= ' )';
+ return $signature;
+ }
+
+ // Decorate and return function/class arguments.
+ if ( $args ) {
+ foreach ( $args as $arg ) {
+ $arg = (array) $arg;
+ $arg_string = '';
+ if ( ! empty( $arg['name'] ) && ! empty( $types[ $arg['name'] ] ) ) {
+ $arg_string .= ' ' . $types[ $arg['name'] ] . ' ';
+ }
+
+ if ( ! empty( $arg['name'] ) ) {
+ $arg_string .= ' ' . $arg['name'] . ' ';
+ }
+
+ if ( ! empty( $arg['default'] ) ) {
+ $arg_string .= ' = ' . htmlentities( $arg['default'] ) . " ";
+ }
+
+ $args_strings[] = $arg_string;
+ }
+ }
+
+ $signature .= '(';
+ if ( $args = implode( ', ', $args_strings ) ) {
+ $signature .= $args . ' ';
+ }
+ $signature .= ')';
+
+ $return = get_return( $post_id, false );
+ if ( $return ) {
+ $signature .= ': ' . $return;
+ }
+
+ return wp_kses_post( $signature );
+ }
+
+ /**
+ * Retrieve parameters as an array
+ *
+ * @param int $post_id
+ *
+ * @return array
+ */
+ function get_params( $post_id = null ) {
+
+ if ( empty( $post_id ) ) {
+ $post_id = get_the_ID();
+ }
+ $params = [];
+ $args = get_post_meta( $post_id, '_wp-parser_args', true );
+ $tags = get_post_meta( $post_id, '_wp-parser_tags', true );
+
+ if ( $tags ) {
+ $encountered_optional = false;
+ foreach ( $tags as $tag ) {
+ if ( ! empty( $tag['name'] ) && 'param' == $tag['name'] ) {
+ $params[ $tag['variable'] ] = $tag;
+ $types = array();
+ foreach ( $tag['types'] as $i => $v ) {
+ $types[ $i ] = sprintf( '%s ', $v, apply_filters( 'devhub-parameter-type', $v, $post_id ) );
+ }
+
+ // Normalize spacing at beginning of hash notation params.
+ if ( $tag['content'] && '{' == $tag['content'][0] ) {
+ $tag['content'] = '{ ' . trim( substr( $tag['content'], 1 ) );
+ }
+
+ $params[ $tag['variable'] ]['types'] = implode( '|', $types );
+ if ( strtolower( substr( $tag['content'], 0, 8 ) ) == "optional" ) {
+ $params[ $tag['variable'] ]['required'] = 'Optional';
+ // Only trim 'Optional' as prefix if not part of sentence.
+ if ( false !== strpos( '.,', $tag['content'][8] ) ) {
+ $params[ $tag['variable'] ]['content'] = ucfirst( trim( substr( $tag['content'], 9 ) ) );
+ }
+ $encountered_optional = true;
+ } elseif ( strtolower( substr( $tag['content'], 2, 9 ) ) == "optional." ) { // Hash notation param
+ $params[ $tag['variable'] ]['required'] = 'Optional';
+ $params[ $tag['variable'] ]['content'] = '{ ' . substr( $tag['content'], 12 );
+ $encountered_optional = true;
+ } elseif ( $encountered_optional ) {
+ $params[ $tag['variable'] ]['required'] = 'Optional';
+ } else {
+ $params[ $tag['variable'] ]['required'] = 'Required';
+ }
+ $params[ $tag['variable'] ]['content'] = \DevHub_Formatting::format_param_description( $params[ $tag['variable'] ]['content'] );
+ }
+ }
+ }
+
+ if ( $args ) {
+ foreach ( $args as $arg ) {
+ if ( ! empty( $arg['name'] ) && ! empty( $params[ $arg['name'] ] ) ) {
+ $params[ $arg['name'] ]['default'] = $arg['default'];
+
+ // If a default value was supplied
+ if ( ! empty( $arg['default'] ) ) {
+ // Ensure the parameter was marked as optional (sometimes they aren't
+ // properly and explicitly documented as such)
+ $params[ $arg['name'] ]['required'] = 'Optional';
+
+ // If a known default is stated in the parameter's description, try to remove it
+ // since the actual default value is displayed immediately following description.
+ $default = htmlentities( $arg['default'] );
+ $params[ $arg['name'] ]['content'] = str_replace( "default is {$default}.", '', $params[ $arg['name'] ]['content'] );
+ $params[ $arg['name'] ]['content'] = str_replace( "Default {$default}.", '', $params[ $arg['name'] ]['content'] );
+
+ // When the default is '', docs sometimes say "Default empty." or similar.
+ if ( "''" == $arg['default'] ) {
+ $params[ $arg['name'] ]['content'] = str_replace( "Default empty.", '', $params[ $arg['name'] ]['content'] );
+ $params[ $arg['name'] ]['content'] = str_replace( "Default empty string.", '', $params[ $arg['name'] ]['content'] );
+
+ // Only a few cases of this. Remove once core is fixed.
+ $params[ $arg['name'] ]['content'] = str_replace( "default is empty string.", '', $params[ $arg['name'] ]['content'] );
+ }
+ // When the default is array(), docs sometimes say "Default empty array." or similar.
+ elseif ( 'array()' == $arg['default'] ) {
+ $params[ $arg['name'] ]['content'] = str_replace( "Default empty array.", '', $params[ $arg['name'] ]['content'] );
+ // Not as common.
+ $params[ $arg['name'] ]['content'] = str_replace( "Default empty.", '', $params[ $arg['name'] ]['content'] );
+ }
+ }
+ }
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * Recurse through parameters that referer to arguments in other functions or methods, and find the innermost parameter description.
+ * For example the description for the wp_count_terms( $args ) parameter refers to get_terms( $args ) which in turn refers to WP_Term_Query::__construct( $query ).
+ * Given the wp_count_terms( $args ) parameter, this will find and return the one for WP_Term_Query::__construct( $query ).
+ *
+ * @param array $param A single parameter array as found in `_wp-parser_args` postmeta.
+ * @param int $recursion_limit Maximum number of levels to recurse through.
+ *
+ * @return array|null
+ */
+
+ function get_param_reference( $param, $recursion_limit = 3 ) {
+ if ( $recursion_limit > 0 && preg_match_all( '#rel="(function|method)">([^<>()]+)[(][)]#', $param[ 'content' ], $matches, PREG_SET_ORDER ) ) {
+ foreach ( $matches as $match ) {
+ $args = array(
+ 'post_type' => 'wp-parser-' . $match[1],
+ 'name' => $match[2],
+ 'posts_per_page' => 1,
+ );
+
+ $query = new \WP_Query( $args );
+
+ if ( $query->have_posts() ) {
+ $_post = $query->posts[0];
+
+ if ( $_params = get_params( $_post->ID ) ) {
+
+ $arg_names_to_try = array_unique([
+ $param['variable'], // An exact match for the name eg $args
+ '$args',
+ '$query', // For example get_terms( $args ) -> WP_Term_Query::__construct( $query )
+ ]);
+
+ foreach ( $arg_names_to_try as $variable_name ) {
+ if ( isset( $_params[ $variable_name ] ) ) {
+ // Sometimes the referenced doc page has another level of indirection!
+ $recurse = get_param_reference( $_params[ $variable_name ], $recursion_limit - 1 );
+ if ( $recurse ) {
+ $recurse[ 'parent' ] = $_post->post_title;
+ $recurse[ 'parent_var' ] = $variable_name;
+ return $recurse;
+ } else {
+ $result = $_params[ $variable_name ];
+ $result[ 'parent' ] = $_post->post_title;
+ $result[ 'parent_var' ] = $variable_name;
+ return $result;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Retrieve arguments as an array
+ *
+ * @param int $post_id
+ *
+ * @return array
+ */
+ function get_arguments( $post_id = null ) {
+
+ if ( empty( $post_id ) ) {
+ $post_id = get_the_ID();
+ }
+ $arguments = array();
+ $args = get_post_meta( $post_id, '_wp-parser_args', true );
+
+ if ( $args ) {
+ foreach ( $args as $arg ) {
+ if ( ! empty( $arg['type'] ) ) {
+ $arguments[ $arg['name'] ] = $arg['type'];
+ }
+ }
+ }
+
+ return $arguments;
+ }
+
+ /**
+ * Retrieve return type and description if available.
+ *
+ * If there is no explicit return value, or it is explicitly "void", then
+ * an empty string is returned. This rules out display of return type for
+ * classes, hooks, and non-returning functions.
+ *
+ * @param int $post_id
+ *
+ * @return string
+ */
+ function get_return( $post_id = null, $include_description = true ) {
+
+ if ( empty( $post_id ) ) {
+ $post_id = get_the_ID();
+ }
+
+ $tags = get_post_meta( $post_id, '_wp-parser_tags', true );
+ $return = wp_filter_object_list( $tags, array( 'name' => 'return' ) );
+
+ // If there is no explicit or non-"void" return value, don't display one.
+ if ( empty( $return ) ) {
+ return '';
+ }
+
+ $return = array_shift( $return );
+ $description = empty( $return['content'] ) ? '' : \DevHub_Formatting::format_param_description( $return['content'] );
+ $type = empty( $return['types'] ) ? '' : esc_html( implode( '|', $return['types'] ) );
+ $type = apply_filters( 'devhub-function-return-type', $type, $post_id );
+
+ if ( $include_description ) {
+ return "{$type} $description";
+ } else {
+ return "{$type} ";
+ }
+ }
+
+ /**
+ * Retrieve changelog data for the current post.
+ *
+ * @param null $post_id Post ID, defaults to the ID of the global $post.
+ *
+ * @return array Associative array of changelog data.
+ */
+ function get_changelog_data( $post_id = null ) {
+ $post_id = empty( $post_id ) ? get_the_ID() : $post_id;
+
+ // Since terms assigned to the post.
+ $since_terms = wp_get_post_terms( $post_id, 'wp-parser-since' );
+
+ // Since data stored in meta.
+ $since_meta = get_post_meta( $post_id, '_wp-parser_tags', true );
+
+ $since_tags = wp_filter_object_list( $since_meta, array( 'name' => 'since' ) );
+ $deprecated = wp_filter_object_list( $since_meta, array( 'name' => 'deprecated' ) );
+
+ // If deprecated, add the since version to the term and meta lists.
+ if ( $deprecated ) {
+ $deprecated = array_shift( $deprecated );
+
+ if ( $term = get_term_by( 'name', $deprecated['content'], 'wp-parser-since' ) ) {
+ // Terms.
+ $since_terms[] = $term;
+
+ // Meta.
+ $since_tags[] = $deprecated;
+ }
+ }
+
+ $data = array();
+
+ // Pair the term data with meta data.
+ foreach ( $since_terms as $since_term ) {
+ foreach ( $since_tags as $meta ) {
+ if ( is_array( $meta ) && $since_term->name == $meta['content'] ) {
+ // Handle deprecation notice if deprecated.
+ if ( empty( $meta['description'] ) ) {
+ if ( $deprecated ) {
+ $description = get_deprecated( $post_id, false );
+ } else {
+ $description = '';
+ }
+ } else {
+ $description = '' . \DevHub_Formatting::format_param_description( $meta['description'] ) . ' ';
+ }
+
+ $data[ $since_term->name ] = array(
+ 'version' => $since_term->name,
+ 'description' => $description,
+ 'since_url' => get_term_link( $since_term )
+ );
+ }
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Retrieve URL to a since version archive.
+ *
+ * @param string $name Since version, e.g. 'x.x.x'.
+ *
+ * @return string Since term archive URL.
+ */
+ function get_since_link( $name = null ) {
+
+ $since_object = get_term_by( 'name', empty( $name ) ? get_since() : $name, 'wp-parser-since' );
+
+ return empty( $since_object ) ? '' : esc_url( get_term_link( $since_object ) );
+ }
+
+ /**
+ * Indicates if a post is deprecated.
+ *
+ * @param int $post_id
+ *
+ * @return bool
+ */
+ function is_deprecated( $post_id = null ) {
+ if ( empty( $post_id ) ) {
+ $post_id = get_the_ID();
+ }
+
+ $tags = get_post_meta( $post_id, '_wp-parser_tags', true );
+ $all_deprecated = wp_filter_object_list( $tags, array( 'name' => 'deprecated' ) );
+
+ return !! $all_deprecated;
+ }
+
+ /**
+ * Retrieve deprecated notice.
+ *
+ * @param int $post_id Optional. Post ID. Default is the ID of the global `$post`.
+ * @return string Deprecated notice. If `$formatted` is true, will be output in markup
+ * for a callout box.
+ */
+ function get_deprecated( $post_id = null ) {
+ if ( ! $post_id ) {
+ $post_id = get_the_ID();
+ }
+
+ $types = explode( '-', get_post_type( $post_id ) );
+ $type = array_pop( $types );
+ $tags = get_post_meta( $post_id, '_wp-parser_tags', true );
+ $deprecated = wp_filter_object_list( $tags, array( 'name' => 'deprecated' ) );
+ $deprecated = array_shift( $deprecated );
+
+ if ( ! $deprecated ) {
+ return '';
+ }
+
+ $deprecation_info = '';
+
+ $referral = wp_filter_object_list( $tags, array( 'name' => 'see' ) );
+ $referral = array_shift( $referral );
+
+ // Construct message pointing visitor to preferred alternative, as provided
+ // via @see, if present.
+ if ( ! empty( $referral['refers'] ) ) {
+ $refers = sanitize_text_field( $referral['refers'] );
+
+ if ( $refers ) {
+ // For some reason, the parser may have dropped the parentheses, so add them.
+ if ( in_array( $type, array( 'function', 'method' ) ) && false === strpos( $refers, '()' ) ) {
+ $refers .= '()';
+ }
+
+ /* translators: %s: Linked internal element name */
+ $deprecation_info = ' ' . sprintf( __( 'Use %s instead.', 'wporg' ), \DevHub_Formatting::link_internal_element( $refers ) );
+ }
+ }
+
+ // If no alternative resource was referenced, use the deprecation string, if
+ // present.
+ if ( ! $deprecation_info && ! empty( $deprecated['description'] ) ) {
+ $deprecation_info = ' ' . sanitize_text_field ( $deprecated['description'] );
+ // Many deprecation strings use the syntax "Use function()" instead of the
+ // preferred "Use function() instead." Add it in if missing.
+ if ( false === strpos( $deprecation_info, 'instead' ) ) {
+ $deprecation_info = rtrim( $deprecation_info, '. ' );
+ $deprecation_info .= ' instead.'; // Not making translatable since rest of string is not translatable.
+ }
+ }
+
+ /* translators: 1: parsed post post, 2: String for alternative function (if one exists) */
+ $contents = sprintf( __( 'This %1$s has been deprecated.%2$s', 'wporg' ),
+ $type,
+ $deprecation_info
+ );
+
+ return $contents;
+ }
+
+ /**
+ * Retrieve URL to source file archive.
+ *
+ * @param string $name
+ *
+ * @return string
+ */
+ function get_source_file_archive_link( $name = null ) {
+
+ $source_file_object = get_term_by( 'name', empty( $name ) ? get_source_file() : $name, 'wp-parser-source-file' );
+
+ return empty( $source_file_object ) ? '' : esc_url( get_term_link( $source_file_object ) );
+ }
+
+ /**
+ * Retrieve name of source file
+ *
+ * @param int $post_id
+ *
+ * @return string
+ */
+ function get_source_file( $post_id = null ) {
+
+ $source_file_object = wp_get_post_terms( empty( $post_id ) ? get_the_ID() : $post_id, 'wp-parser-source-file', array( 'fields' => 'names' ) );
+
+ return empty( $source_file_object ) ? '' : esc_html( $source_file_object[0] );
+ }
+
+ /**
+ * Retrieve either the starting or ending line number.
+ *
+ * @param int $post_id Optional. The post ID.
+ * @param string $type Optional. Either 'start' for starting line number, or 'end' for ending line number.
+ *
+ * @return int
+ */
+ function get_line_number( $post_id = null, $type = 'start' ) {
+
+ $post_id = empty( $post_id ) ? get_the_ID() : $post_id;
+ $meta_key = ( 'end' == $type ) ? '_wp-parser_end_line_num' : '_wp-parser_line_num';
+
+ return (int) get_post_meta( $post_id, $meta_key, true );
+ }
+
+ /**
+ * Retrieve the URL to the actual source file and line.
+ *
+ * @param null $post_id Post ID.
+ * @return string Source file URL with or without line number.
+ */
+ function get_source_file_link( $post_id = null ) {
+
+ $post_id = empty( $post_id ) ? get_the_ID() : $post_id;
+ $url = '';
+
+ // Source file.
+ $source_file = get_source_file( $post_id );
+ if ( ! empty( $source_file ) ) {
+ $url = 'https://core.trac.wordpress.org/browser/tags/' . get_current_version() . '/src/' . $source_file;
+ // Line number.
+ if ( $line_number = get_post_meta( get_the_ID(), '_wp-parser_line_num', true ) ) {
+ $url .= "#L{$line_number}";
+ }
+ }
+
+ return esc_url( $url );
+ }
+
+ /**
+ * Retrieve the URL to the GitHub source file and line.
+ *
+ * @param null $post_id Post ID.
+ * @return string Source file URL with or without line number.
+ */
+ function get_github_source_file_link( $post_id = null ) {
+
+ $post_id = empty( $post_id ) ? get_the_ID() : $post_id;
+ $url = '';
+
+ // Source file.
+ $source_file = get_source_file( $post_id );
+ if ( ! empty( $source_file ) ) {
+ $url = 'https://github.com/WordPress/wordpress-develop/blob/' . get_current_version() . '/src/' . $source_file;
+ // Line number.
+ if ( $line_number = get_post_meta( get_the_ID(), '_wp-parser_line_num', true ) ) {
+ $url .= "#L{$line_number}";
+ if ( $end_line_number = get_post_meta( get_the_ID(), '_wp-parser_end_line_num', true ) ) {
+ $url .= "-L{$end_line_number}";
+ }
+ }
+ }
+
+ return esc_url( $url );
+ }
+
+ /**
+ * Compare two objects by name for sorting.
+ *
+ * @param WP_Post $a Post A
+ * @param WP_Post $b Post B
+ *
+ * @return int
+ */
+ function compare_objects_by_name( $a, $b ) {
+ return strcmp( $a->post_name, $b->post_name );
+ }
+
+ function show_usage_info() {
+ $p2p_enabled = function_exists( 'p2p_register_connection_type' );
+
+ return $p2p_enabled && post_type_has_usage_info( get_post_type() );
+ }
+
+ /**
+ * Does the post type support usage information?
+ *
+ * @param string $post_type Optional. The post type name. If blank, assumes current post type.
+ *
+ * @return boolean
+ */
+ function post_type_has_usage_info( $post_type = null ) {
+ $post_type = $post_type ? $post_type : get_post_type();
+ $post_types_with_usage = array( 'wp-parser-function', 'wp-parser-method', 'wp-parser-hook', 'wp-parser-class' );
+
+ return in_array( $post_type, $post_types_with_usage );
+ }
+
+ /**
+ * Does the post type support uses information?
+ *
+ * @param string $post_type Optional. The post type name. If blank, assumes current post type.
+ *
+ * @return boolean
+ */
+ function post_type_has_uses_info( $post_type = null ) {
+ $post_type = $post_type ? $post_type : get_post_type();
+ $post_types_with_uses = array( 'wp-parser-function', 'wp-parser-method', 'wp-parser-class' );
+
+ return in_array( $post_type, $post_types_with_uses );
+ }
+
+ /**
+ * Does the post type support hooks information?
+ *
+ * @param string $post_type Optional. The post type name. If blank, assumes current post type.
+ *
+ * @return boolean
+ */
+ function post_type_has_hooks_info( $post_type = null ) {
+ $post_type = $post_type ? $post_type : get_post_type();
+ $post_types_with_hooks = array( 'wp-parser-function', 'wp-parser-method' );
+
+ return in_array( $post_type, $post_types_with_hooks );
+ }
+
+ /**
+ * Retrieves a WP_Query object for the posts that the current post uses.
+ *
+ * @param int|WP_Post|null $post Optional. Post ID or post object. Default is global $post.
+ * @return WP_Query|null The WP_Query if the post's post type supports 'uses', null otherwise.
+ */
+ function get_uses( $post = null ) {
+ $post_id = get_post_field( 'ID', $post );
+ $post_type = get_post_type( $post );
+
+ if ( 'wp-parser-class' === $post_type ) {
+ $extends = get_post_meta( $post_id, '_wp-parser_extends', true );
+ if ( ! $extends ) {
+ return;
+ }
+ $connected = new \WP_Query( array(
+ 'post_type' => array( 'wp-parser-class' ),
+ 'name' => $extends,
+ ) );
+ return $connected;
+ } elseif ( 'wp-parser-function' === $post_type ) {
+ $connection_types = array( 'functions_to_functions', 'functions_to_methods' );
+ } else {
+ $connection_types = array( 'methods_to_functions', 'methods_to_methods' );
+ }
+
+ $connected = new \WP_Query( array(
+ 'post_type' => array( 'wp-parser-function', 'wp-parser-method' ),
+ 'connected_type' => $connection_types,
+ 'connected_direction' => array( 'from', 'from' ),
+ 'connected_items' => $post_id,
+ 'nopaging' => true,
+ ) );
+
+ return $connected;
+ }
+
+ /**
+ * Retrieves a WP_Query object for the hook posts that the current post is linked to.
+ *
+ * @param int|WP_Post|null $post Optional. Post ID or post object. Default is global $post.
+ * @return WP_Query|null The WP_Query if the post's post type supports 'uses', null otherwise.
+ */
+ function get_hooks( $post = null ) {
+ $post_id = get_post_field( 'ID', $post );
+ $post_type = get_post_type( $post );
+
+ if ( 'wp-parser-function' === $post_type ) {
+ $connection_types = array( 'functions_to_hooks' );
+ } else {
+ $connection_types = array( 'methods_to_hooks' );
+ }
+
+ $connected = new \WP_Query( array(
+ 'post_type' => 'wp-parser-hook',
+ 'connected_type' => $connection_types,
+ 'connected_direction' => array( 'from' ),
+ 'connected_items' => $post_id,
+ 'nopaging' => true,
+ ) );
+
+ return $connected;
+ }
+
+ /**
+ * Retrieves a WP_Query object for the posts that use the specified post.
+ *
+ * @param int|WP_Post|null $post Optional. Post ID or post object. Default is global $post.
+ * @return WP_Query|null The WP_Query if the post's post type supports 'used by', null otherwise.
+ */
+ function get_used_by( $post = null ) {
+
+ switch ( get_post_type( $post ) ) {
+
+ case 'wp-parser-function':
+ $connection_types = array( 'functions_to_functions', 'methods_to_functions' );
+ break;
+
+ case 'wp-parser-method':
+ $connection_types = array( 'functions_to_methods', 'methods_to_methods', );
+ break;
+
+ case 'wp-parser-hook':
+ $connection_types = array( 'functions_to_hooks', 'methods_to_hooks' );
+ break;
+
+ case 'wp-parser-class':
+ $connected = new \WP_Query( array(
+ 'post_type' => array( 'wp-parser-class' ),
+ 'meta_key' => '_wp-parser_extends',
+ 'meta_value' => get_post_field( 'post_name', $post ),
+ ) );
+ return $connected;
+ break;
+
+ default:
+ return;
+
+ }
+
+ $connected = new \WP_Query( array(
+ 'post_type' => array( 'wp-parser-function', 'wp-parser-method' ),
+ 'connected_type' => $connection_types,
+ 'connected_direction' => array( 'to', 'to' ),
+ 'connected_items' => get_post_field( 'ID', $post ),
+ 'nopaging' => true,
+ ) );
+
+ return $connected;
+ }
+
+ /**
+ * Find functions & methods that are often used by other functions and methods.
+ */
+ function _get_functions_to_exclude_from_uses() {
+ global $wpdb;
+
+ $ids = get_transient( __METHOD__ );
+ if ( $ids ) {
+ return $ids;
+ }
+
+ $ids = $wpdb->get_col(
+ "SELECT p2p_to
+ FROM {$wpdb->p2p} p2p
+ WHERE p2p_type IN ( 'methods_to_functions', 'functions_to_functions', 'methods_to_methods', 'functions_to_methods' )
+ GROUP BY p2p_to
+ HAVING COUNT(*) > 50"
+ );
+
+ set_transient( __METHOD__, $ids, DAY_IN_SECONDS );
+
+ return $ids;
+ }
+
+ /**
+ * Rearrange the results of get_uses() so that frequent functions are pushed to the bottom.
+ * Sorts the array in-place.
+ *
+ * @return int The number of infrequent items in the list.
+ */
+ function split_uses_by_frequent_funcs( &$posts ) {
+
+ $frequent_funcs = _get_functions_to_exclude_from_uses();
+
+ // Sort the posts array so frequently used functions are at the end
+ usort( $posts, function( $a, $b ) use ( $frequent_funcs ) {
+ return (int) in_array( $a->ID, $frequent_funcs ) - (int) in_array( $b->ID, $frequent_funcs );
+ } );
+
+ $infrequent_count = 0;
+ foreach ( $posts as $i => $post ) {
+ if ( in_array( $post->ID, $frequent_funcs ) ) {
+ break;
+ }
+ $infrequent_count = $i + 1;
+ }
+
+ return $infrequent_count;
+ }
+
+ /**
+ * Returns the array of post types that have source code.
+ *
+ * @return array
+ */
+ function get_post_types_with_source_code() {
+ return array( 'wp-parser-class', 'wp-parser-method', 'wp-parser-function', 'wp-parser-hook' );
+ }
+
+ /**
+ * Does the post type have source code?
+ *
+ * @param null|string $post_type Optional. The post type name. If null, assumes current post type. Default null.
+ *
+ * @return bool
+ */
+ function post_type_has_source_code( $post_type = null ) {
+ $post_type = $post_type ? $post_type : get_post_type();
+
+ return in_array( $post_type, get_post_types_with_source_code() );
+ }
+
+ /**
+ * Retrieve the root directory of the parsed WP code.
+ *
+ * If the option 'wp_parser_root_import_dir' (as set by the parser) is not
+ * set, then assume ABSPATH.
+ *
+ * @return string
+ */
+ function get_source_code_root_dir() {
+ $root_dir = get_option( 'wp_parser_root_import_dir' );
+
+ return ( $root_dir && is_dir( $root_dir ) ) ? trailingslashit( $root_dir ) : ABSPATH;
+ }
+
+ /**
+ * Retrieve source code for a function or method.
+ *
+ * @param int $post_id Optional. The post ID.
+ * @param bool $force_parse Optional. Ignore potential value in post meta and reparse source file for source code?
+ *
+ * @return string The source code.
+ */
+ function get_source_code( $post_id = null, $force_parse = false ) {
+
+ if ( empty( $post_id ) ) {
+ $post_id = get_the_ID();
+ }
+
+ // Get the source code stored in post meta.
+ $meta_key = '_wp-parser_source_code';
+ if ( ! $force_parse && $source_code = get_post_meta( $post_id, $meta_key, true ) ) {
+ return $source_code;
+ }
+
+ /* Source code hasn't been stored in post meta, so parse source file to get it. */
+
+ // Get the name of the source file.
+ $source_file = get_source_file( $post_id );
+
+ // Get the start and end lines.
+ $start_line = intval( get_post_meta( $post_id, '_wp-parser_line_num', true ) ) - 1;
+ $end_line = intval( get_post_meta( $post_id, '_wp-parser_end_line_num', true ) );
+
+ // Sanity check to ensure proper conditions exist for parsing
+ if ( ! $source_file || ! $start_line || ! $end_line || ( $start_line > $end_line ) ) {
+ return '';
+ }
+
+ // Find just the relevant source code
+ $source_code = '';
+ $handle = @fopen( get_source_code_root_dir() . $source_file, 'r' );
+ if ( $handle ) {
+ $line = -1;
+ while ( ! feof( $handle ) ) {
+ $line++;
+ $source_line = fgets( $handle );
+
+ // Stop reading file once end_line is reached.
+ if ( $line >= $end_line ) {
+ break;
+ }
+
+ // Skip lines until start_line is reached.
+ if ( $line < $start_line ) {
+ continue;
+ }
+
+ $source_code .= $source_line;
+ }
+ fclose( $handle );
+ }
+
+ // Trim leading whitespace.
+ if ( preg_match_all( "!^([\t ]*).+$!m", $source_code, $m ) ) {
+ $strip_prefix = min( array_map( 'strlen', $m[1] ) );
+ if ( $strip_prefix ) {
+ $source_code = preg_replace( "!^[\t ]{" . $strip_prefix . "}!m", '$1', $source_code );
+ }
+ }
+
+ update_post_meta( $post_id, $meta_key, addslashes( $source_code ) );
+
+ return $source_code;
+ }
+
+ /**
+ * Indicates if the current user can post a user contibuted note.
+ *
+ * This only affects parsed post types as they are the only things
+ * that can have user contributed notes.
+ *
+ * A custom check can be performed by hooking the filter
+ * 'wporg_devhub-can_user_post_note' and returning a
+ * value other than null.
+ *
+ * By default, the ability to post notes is restricted to members of the
+ * blog.
+ *
+ * @param bool $open If the user can post comments in general.
+ * @param WP_Post $post Post ID or post object.
+ *
+ * @return bool True if the user can post a note.
+ */
+ function can_user_post_note( $open, $post ) {
+ // Only proceed if for a parsed post type.
+ if ( ! is_parsed_post_type( get_post_type( $post ) ) ) {
+ return $open;
+ }
+
+ // Permit default logic to be overridden via filter that returns value other than null.
+ if ( null !== ( $can = apply_filters( 'wporg_devhub-can_user_post_note', null, $post ) ) ) {
+ return $can;
+ }
+
+ return $open;
+ }
+
+ /**
+ * Indicates if the current user can edit a user contibuted note.
+ *
+ * A user can only edit their own notes if it's in moderation and
+ * if it's a note for a parsed post type.
+ *
+ * Users with the 'edit_comment' capability can edit
+ * all notes from a parsed post type (regardless if it's in moderation).
+ *
+ * @param integer $note_id Note ID.
+ * @return bool True if the current user can edit notes.
+ */
+ function can_user_edit_note( $note_id = 0 ) {
+ $user = get_current_user_id();
+ $note = get_comment( $note_id );
+ if ( ! $user || ! $note ) {
+ return false;
+ }
+
+ $post_id = isset( $note->comment_post_ID ) ? (int) $note->comment_post_ID : 0;
+ $is_note_author = isset( $note->user_id ) && ( (int) $note->user_id === $user );
+ $is_approved = isset( $note->comment_approved ) && ( 0 < (int) $note->comment_approved );
+ $can_edit_notes = isset( $note->comment_ID ) && current_user_can( 'edit_comment', $note->comment_ID );
+ $is_parsed_type = is_parsed_post_type( get_post_type( $post_id ) );
+
+ if ( $is_parsed_type && ( $can_edit_notes || ( $is_note_author && ! $is_approved ) ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the note author link to the profiles.wordpress.org author's URL.
+ *
+ * @param WP_Comment|int $comment Comment object or comment ID.
+ * @return string The HTML link to the profiles.wordpress.org author's URL.
+ */
+ function get_note_author_link( $comment ) {
+ return get_note_author( $comment, true );
+ }
+
+ /**
+ * Get the note author nicename.
+ *
+ * @param WP_Comment|int $comment Comment object or comment ID.
+ * @param bool $link. Whether to return a link to the author's profiles. Default false.
+ * @return string The comment author name or HTML link.
+ */
+ function get_note_author( $comment, $link = false ) {
+ $comment = get_comment( $comment );
+ $user_id = isset( $comment->user_id ) ? $comment->user_id : 0;
+ $commenter = get_user_by( 'id', $comment->user_id );
+ $author = '';
+
+ if ( $user_id && isset( $commenter->user_nicename ) ) {
+ $url = 'https://profiles.wordpress.org/' . sanitize_key( $commenter->user_nicename ) . '/';
+ $author = get_the_author_meta( 'display_name', $comment->user_id );
+ } else {
+ $url = isset( $comment->comment_author_url ) ? $comment->comment_author_url : '';
+ $author = isset( $comment->comment_author ) ? $comment->comment_author : '';
+ }
+
+ if ( $link && ( $url && $author ) ) {
+ $author = sprintf( '%s ', esc_url( $url ), $author );
+ }
+
+ return $author;
+ }
+
+ /**
+ * Gets the summary.
+ *
+ * The summary (aka short description) is stored in the 'post_excerpt' field.
+ *
+ * @param null|WP_Post Optional. The post. Default null.
+ * @return string
+ */
+ function get_summary( $post = null ) {
+ $post = get_post( $post );
+
+ $summary = $post->post_excerpt;
+
+ if ( $summary ) {
+ // Backticks in excerpts are not automatically wrapped in code tags, so do so.
+ // e.g. https://developer.wordpress.org/reference/functions/convert_chars/
+ if ( false !== strpos( $summary, '`' ) ) {
+ $summary = preg_replace_callback(
+ '/`([^`]*)`/',
+ function ( $matches ) { return '' . htmlentities( $matches[1] ) . '
'; },
+ $summary
+ );
+ }
+
+ // Fix https://developer.wordpress.org/reference/functions/get_extended/
+ // until the 'more' delimiter in summary is backticked.
+ $summary = str_replace( array( '' ), array( '<!--', '-->
' ), $summary );
+
+ // Fix standalone HTML tags that were not backticked.
+ // e.g. https://developer.wordpress.org/reference/hooks/comment_form/
+ if ( false !== strpos( $summary, '<' ) ) {
+ $summary = preg_replace_callback(
+ '/(\s)(<[^ >]+>)(\s)/',
+ function ( $matches ) { return $matches[1] . '' . htmlentities( $matches[2] ) . '
' . $matches[3]; },
+ $summary
+ );
+ }
+
+ $summary = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $summary, $post ) );
+ }
+
+ return $summary;
+ }
+
+ /**
+ * Gets the description.
+ *
+ * The (long) description is stored in the 'post_content' get_post_field.
+ *
+ * @param null|WP_Post Optional. The post.
+ * @return string
+ */
+ function get_description( $post = null ) {
+ $post = get_post( $post );
+
+ $description = $post->post_content;
+
+ // Replace code blocks generated by Markdown with wp:code blocks.
+ // Note: Currently assumes all code is PHP. `code` attributes (e.g. class="language-???") may eventually indicate otherwise.
+ $description = preg_replace( '/]*>(.+)<\/code><\/pre>/sU', '[code lang="php"]$1[/code]', $description );
+
+ if ( $description ) {
+ // Remove the filter that adds the code reference block to the content.
+ remove_filter( 'the_content', 'DevHub\filter_code_content', 4 );
+
+ $description = apply_filters( 'the_content', apply_filters( 'get_the_content' , $description ) );
+
+ // Re-add the filter that adds this block to the content.
+ add_filter( 'the_content', 'DevHub\filter_code_content', 4 );
+ }
+
+ return $description;
+ }
+
+ /**
+ * Gets list of additional resources linked via `@see` tags.
+ *
+ * @param null|WP_Post Optional. The post.
+ * @return array
+ */
+ function get_see_tags( $post = null ) {
+ $post = get_post( $post );
+
+ $tags = get_post_meta( $post->ID, '_wp-parser_tags', true );
+
+ return wp_list_filter( $tags, array( 'name' => 'see' ) );
+ }
+
+ /**
+ * Should the search bar be shown?
+ *
+ * @return bool True if search bar should be shown.
+ */
+ function should_show_search_bar() {
+ $post_types = get_parsed_post_types();
+ $taxonomies = array( 'wp-parser-since', 'wp-parser-package', 'wp-parser-source-file' );
+
+ return ! ( is_search() || is_404() ) && ( is_singular( $post_types ) || is_post_type_archive( $post_types ) || is_tax( $taxonomies ) || is_page( 'reference' ) );
+ }
+
+ /**
+ * Should the search bar filters be shown?
+ *
+ * @return bool True if search bar filters should be shown.
+ */
+ function should_show_search_filters() {
+ $is_handbook = $GLOBALS['wp_query']->is_handbook;
+ return ( is_page( 'reference' ) || is_search() ) && ! $is_handbook;
+ }
+
+ /**
+ * Retrieve an explanation for the given post.
+ *
+ * @param int|WP_Post $post Post ID or WP_Post object.
+ * @param bool $published Optional. Whether to only retrieve the explanation if it's published.
+ * Default false.
+ * @return WP_Post|null WP_Post object for the Explanation, null otherwise.
+ */
+ function get_explanation( $post, $published = false ) {
+ if ( ! $post = get_post( $post ) ) {
+ return null;
+ }
+
+ $args = array(
+ 'post_type' => 'wporg_explanations',
+ 'post_parent' => $post->ID,
+ 'no_found_rows' => true,
+ 'posts_per_page' => 1,
+ );
+
+ if ( true === $published ) {
+ $args['post_status'] = 'publish';
+ }
+
+ $explanation = get_children( $args, OBJECT );
+
+ if ( empty( $explanation ) ) {
+ return null;
+ }
+
+ $explanation = reset( $explanation );
+
+ if ( ! $explanation ) {
+ return null;
+ }
+ return $explanation;
+ }
+
+ /**
+ * Retrieve data from an explanation post field.
+ *
+ * Works only for published explanations.
+ *
+ * @see get_post_field()
+ *
+ * @param string $field Post field name.
+ * @param int|WP_Post $post Post ID or object for the function, hook, class, or method post
+ * to retrieve an explanation field for.
+ * @param string $context Optional. How to filter the field. Accepts 'raw', 'edit', 'db',
+ * or 'display'. Default 'display'.
+ * @return string The value of the post field on success, empty string on failure.
+ */
+ function get_explanation_field( $field, $post, $context = 'display' ) {
+ if ( ! $explanation = get_explanation( $post, $published = true ) ) {
+ return '';
+ }
+
+ return get_post_field( $field, $explanation, $context );
+ }
+
+ /**
+ * Retrieve the post content from an explanation post.
+ *
+ * @param int|WP_Post $_post Post ID or object for the function, hook, class, or method post
+ * to retrieve an explanation field for.
+ * @return string The post content of the explanation.
+ */
+ function get_explanation_content( $_post ) {
+ global $post;
+
+ // Store original global post.
+ $orig = $post;
+
+ // Set global post to the explanation post.
+ $post = get_explanation( $_post );
+
+ // Get explanation's raw post content.
+ $content = '';
+ if (
+ ! empty( $_GET['wporg_explanations_preview_nonce'] )
+ &&
+ false !== wp_verify_nonce( $_GET['wporg_explanations_preview_nonce'], 'post_preview_' . $post->ID )
+ ) {
+ $preview = wp_get_post_autosave( $post->ID );
+
+ if ( is_object( $preview ) ) {
+ $post = $preview;
+ $content = get_post_field( 'post_content', $preview, 'display' );
+ }
+ } else {
+ $content = get_explanation_field( 'post_content', $_post );
+ }
+
+ // Sanitize message content.
+ $content = wp_kses_post( $content );
+
+ // Remove the {@see} tag, leaving only the function name, so that there'd be no extra a tags being rendered.
+ $content = preg_replace( '/\{@see (.*?)\}/', '$1', $content );
+
+ // Restore original global post.
+ $post = $orig;
+
+ return $content;
+ }
+
+ /**
+ * Generates a private access message for a private element.
+ *
+ * @param int|WP_Post $post Optional. Post object or ID. Default global `$post`.
+ * @return string Private access message if the given reference is considered "private".
+ */
+ function get_private_access_message( $post = null ) {
+ if ( ! $post = get_post( $post ) ) {
+ return '';
+ }
+
+ // Currently only handling private access messages for functions, hooks, and methods.
+ if ( ! in_array( get_post_type( $post ), array( 'wp-parser-function', 'wp-parser-hook', 'wp-parser-method' ) ) ) {
+ return '';
+ }
+
+ $tags = get_post_meta( $post->ID, '_wp-parser_tags', true );
+
+ $access_tags = wp_filter_object_list( $tags, array(
+ 'name' => 'access',
+ 'content' => 'private'
+ ) );
+ $is_private = ! empty( $access_tags );
+
+ if ( ! $is_private ) {
+ if ( 'private' === get_post_meta( $post->ID, '_wp-parser_visibility', true ) ) {
+ $is_private = true;
+ }
+ }
+
+ // Bail if it isn't private.
+ if ( ! $is_private ) {
+ return '';
+ }
+
+ $referral = wp_filter_object_list( $tags, array( 'name' => 'see' ) );
+ $referral = array_shift( $referral );
+
+ if ( ! empty( $referral['refers'] ) ) {
+ $refers = sanitize_text_field( $referral['refers'] );
+
+ if ( ! empty( $refers ) ) {
+ /* translators: 1: Linked internal element name */
+ $alternative_string = sprintf( __( ' Use %s instead.', 'wporg' ), \DevHub_Formatting::link_internal_element( $refers ) );
+ }
+ } else {
+ $alternative_string = '';
+ }
+
+ /* translators: 1: String for alternative function (if one exists) */
+ $contents = sprintf( __( 'This function’s access is marked private. This means it is not intended for use by plugin or theme developers, only in other core functions. It is listed here for completeness.%s', 'wporg' ),
+ $alternative_string
+ );
+
+ return $contents;
+ }
+
+ /**
+ * Displays a post type filter dropdown on taxonomy pages.
+ *
+ * @return string HTML filter form.
+ */
+ function taxonomy_archive_filter() {
+ global $wp_rewrite;
+
+ $taxonomies = array( 'wp-parser-since', 'wp-parser-package', 'wp-parser-source-file' );
+ $taxonomy = get_query_var( 'taxonomy' );
+ $term = get_query_var( 'term' );
+
+ if ( ! ( is_tax() && in_array( $taxonomy, $taxonomies ) ) ) {
+ return;
+ }
+
+ $post_types = get_parsed_post_types( 'labels' );
+ $post_types = array( 'any' => __( 'Any type', 'wporg' ) ) + $post_types;
+
+ $qv_post_type = array_filter( (array) get_query_var( 'post_type' ) );
+ $qv_post_type = $qv_post_type ? $qv_post_type : array( 'any' );
+
+ $options = '';
+ foreach ( $post_types as $post_type => $label ) {
+ $selected = in_array( $post_type, $qv_post_type ) ? " selected='selected'" : '';
+ $options .= "\n\t$label ";
+ }
+
+ $form = "";
+
+ echo $form;
+ }
+
+ /**
+ * Retrieves all content for reference template parts.
+ *
+ * @return string Template part markup retrieved via output buffering.
+ */
+ function get_reference_template_parts() {
+ // Order dictates order of display.
+ $templates = array(
+ 'description',
+ 'params',
+ 'return',
+ 'explanation',
+ 'methods',
+ 'source',
+ 'hooks',
+ 'related',
+ 'changelog',
+ 'notes'
+ );
+
+ ob_start();
+
+ foreach ( $templates as $part ) {
+ get_template_part( 'reference/template', $part );
+ }
+
+ return ob_get_clean();
+ }
+}
diff --git a/source/wp-content/themes/wpr-developer-2024/inc/user-content-edit.php b/source/wp-content/themes/wpr-developer-2024/inc/user-content-edit.php
new file mode 100644
index 000000000..4b01970be
--- /dev/null
+++ b/source/wp-content/themes/wpr-developer-2024/inc/user-content-edit.php
@@ -0,0 +1,194 @@
+ERROR