diff --git a/.gitignore b/.gitignore index 7fd3f5d39..6ee057b96 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ phpcs.xml.dist # ...except the actual source of this project. !/source/wp-content/themes !/source/wp-content/themes/wporg-developer-2023 +!/source/wp-content/themes/wpr-developer-2024 diff --git a/.wp-env.json b/.wp-env.json index b53ad884b..7c071fa6f 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -18,6 +18,7 @@ "./source/wp-content/plugins/jetpack" ], "themes": [ + "./source/wp-content/themes/wpr-developer-2024", "./source/wp-content/themes/wporg-developer-2023", "./source/wp-content/themes/wporg-parent-2021" ], @@ -27,6 +28,6 @@ "wp-content/mu-plugins": "./source/wp-content/mu-plugins", "wp-content/mu-plugins/0-sandbox.php": "./env/0-sandbox.php", "wp-content/plugins/phpdoc-parser": "./source/wp-content/plugins/phpdoc-parser", - "wp-content/wp-rocket": "./source/wp-content/wp-rocket" + "wp-content/plugins/wp-rocket": "./source/wp-content/plugins/wp-rocket" } } diff --git a/source/wp-content/themes/wpr-developer-2024/Gruntfile.js b/source/wp-content/themes/wpr-developer-2024/Gruntfile.js new file mode 100644 index 000000000..ad4fb32ad --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/Gruntfile.js @@ -0,0 +1,36 @@ +module.exports = function( grunt ) { + + const sass = require('node-sass'); + + // Load tasks. + require( 'matchdep' ).filterDev(['grunt-*']).forEach( grunt.loadNpmTasks ); + + // Project configuration. + grunt.initConfig({ + sass: { + all: { + expand: true, + cwd: "scss", + dest: "stylesheets", + ext: '.css', + src: [ '**/*.scss' ], + options: { + implementation: sass, + outputStyle: 'expanded' + } + } + }, + watch: { + all: { + files: [ "scss/**/*.scss" ], + tasks: [ "sass:all" ], + options: { + spawn: false + } + } + } + }); + + grunt.registerTask( 'default', [ 'sass:all' ] ); +}; + diff --git a/source/wp-content/themes/wpr-developer-2024/README.md b/source/wp-content/themes/wpr-developer-2024/README.md new file mode 100644 index 000000000..d7ce6b795 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/README.md @@ -0,0 +1,15 @@ +# developer.wordpress.org + +developer.wordpress.org is the forthcoming resource for developers working with WordPress + +There are a number of ways to get involved: + +* get set up with the [devhub development environment](https://make.wordpress.org/docs/handbook/projects/devhub) to help with development +* help out with the [inline docs efforts](http://make.wordpress.org/core/tag/inline-docs) +* help out with the [plugin](http://make.wordpress.org/docs/plugin-developer-handbook/) and [theme](http://make.wordpress.org/docs/theme-developer-handbook/) developer handbooks + +The regular devhub meeting is on Tuesdays at 15:00 UTC in irc.freenode.net #wordpress-sfd. + +Discussion occurs on [make.wordpress.org/docs](http://make.wordpress.org/docs/tag/devhub/) + +Issues are on trac on the [developer.wordpress.org component](https://meta.trac.wordpress.org/query?status=!closed&component=developer.wordpress.org). diff --git a/source/wp-content/themes/wpr-developer-2024/embed.php b/source/wp-content/themes/wpr-developer-2024/embed.php new file mode 100644 index 000000000..1784a610b --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/embed.php @@ -0,0 +1,213 @@ + data type. + * + * @param int $post_id + * + * @return array + */ +function get_params( $post_id ) { + $tags = get_post_meta( $post_id, '_wp-parser_tags', true ); + $params = array(); + + if ( $tags ) { + foreach ( $tags as $tag ) { + if ( is_array( $tag ) && 'param' == $tag['name'] ) { + $params[ $tag['variable'] ] = implode( ' | ', $tag['types'] ); + } + } + } + + return $params; +} + +/** + * Returns a function string to display. + * + * @param int $post_id + * + * @return string + */ +function get_signature( $post_id ) { + $title = get_the_title(); + $has_args = count( get_params( $post_id ) ) > 0; + $post_type = get_post_type( $post_id ); + + if ( 'wp-parser-hook' === $post_type ) { + $hook_type = DevHub\get_hook_type_name( $post_id ); + $delimiter = false !== strpos( $title, '$' ) ? '"' : "'"; + + if ( $has_args ) { + return "{$hook_type}( {$delimiter}{$title}{$delimiter}, ... )"; + } + return "{$hook_type}( {$delimiter}{$title}{$delimiter} )"; + } + + if ( 'wp-parser-class' === $post_type ) { + return 'class ' . $title . ' {}'; + } + + if ( $has_args ) { + return $title . '( ... )'; + } + + return $title . '()'; +} + +$embed_post_id = get_the_ID(); +$params = get_params( $embed_post_id ); +$embed_title = get_signature( $embed_post_id ); +$param_count = count( $params ); +$parameter_display_max = 4; // We truncate the display of params + +?> + + class="no-js"> + + <?php echo esc_html( wp_get_document_title() ); ?> + + tag. + * + * @since 4.4.0 + */ + do_action( 'embed_head' ); + ?> + + + +> +
> + +

+ + + +

+ +
+ + + +
+
+
    + +
  • + + +
  • + + + $parameter_display_max ) { + /* translators: %d number of non printed parameter */ + echo '
  • ' . sprintf( '... %s more', esc_attr( $param_count - $parameter_display_max ) ) . '
  • '; + } + ?> +
+
+ + +
+ + + + +
+ + diff --git a/source/wp-content/themes/wpr-developer-2024/functions.php b/source/wp-content/themes/wpr-developer-2024/functions.php new file mode 100644 index 000000000..0472684d4 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/functions.php @@ -0,0 +1,785 @@ +' . $post_type_object->labels->name . ''; + // Replace what the plugin thinks is the parent with the hook name + $items[3] = $items[4]; + // Unset the last element since it shifted up in trail hierarchy + unset( $items[4] ); + + return $items; +} + +/** + * Remove the 'Resource' part of the breadcrumb trail. + * + * @param array $items The breadcrumb trail items. + * @param array $args Original args. + * @return array + */ +function breadcrumb_trail_items_remove_resource( $items, $args ) { + if ( ! is_page( 'dashicons' ) ) { + return $items; + } + + return array_filter( + $items, + function( $item ) { + // Remove the 'resource' parent based on the presence of its URL. + // A naked /resource/ request is always redirected to dashicons page. + $result = (bool) preg_match( '!href="[^"]+/resource/"!', $item ); + return ( false === $result ); + } + ); +} + +/** + * Fix breadcrumb for handbook root pages. + * + * The handbook root/landing pages do not need a duplicated breadcrumb trail + * item that simply links to the currently loaded page. The trailing breadcrumb + * item is already the unlinked handbook name, which is sufficient. + * + * @param array $items The breadcrumb trail items. + * @param array $args Original args. + * @return array + */ +function breadcrumb_trail_items_for_handbook_root( $items, $args ) { + // Bail early if not a handbook landing page. + if ( ! function_exists( 'wporg_is_handbook_landing_page' ) || ! wporg_is_handbook_landing_page() ) { + return $items; + } + + // Unset link to current handbook. + unset( $items[1] ); + + return $items; +} + +/** + * Fix the breadcrumb trail for the edit note page. + * + * @param array $items + * @return array + */ +function breadcrumb_trail_for_note_edit( $items ) { + if ( empty( get_query_var( 'edit_user_note' ) ) || ! is_single() ) { + return $items; + } + + $comment_id = get_query_var( 'edit_user_note' ); + $comment = get_comment( $comment_id ); + $post = get_queried_object(); + $post_id = get_queried_object_id(); + $post_url = get_permalink( $post_id ); + $post_title = single_post_title( '', false ); + $post_types = \DevHub\get_parsed_post_types( 'labels' ); + $type_single = get_post_type_object( $post->post_type )->labels->singular_name; + $type_url = get_post_type_archive_link( $post->post_type ); + $type_label = $post_types[ $post->post_type ]; + + $breadcrumbs = array( $items[0] ); // Ie: Home + $breadcrumbs[] = sprintf( '%s', esc_url( $type_url ), $type_label ); + $breadcrumbs[] = sprintf( '%s', esc_url( $post_url ), $post_title ); + $breadcrumbs[] = sprintf( + '%s', + esc_url( $post_url . '#comment-' . $comment_id ), + sprintf( /* translators: %d: comment ID */ + $comment->comment_parent ? __( 'feedback %d', 'wporg' ) : __( 'note %d', 'wporg' ), + $comment_id + ) + ); + $breadcrumbs[] = __( 'Edit', 'wporg' ); + return $breadcrumbs; +} + +/** + * Fix breadcrumb for wp-parser-since archive. + * + * @param array $items The breadcrumb trail items. + * @param array $args Original args. + * @return array + */ +function breadcrumb_trail_for_since_view( $items, $args ) { + + if ( ! is_archive() || ! get_query_var( 'wp-parser-since' ) ) { + return $items; + } + + // Remove the last item + unset( $items[ count( $items ) - 1 ] ); + + $items[] = sprintf( + /* translators: %s: WordPress version */ + __( 'New and updated in %s', 'wporg' ), + get_query_var( 'wp-parser-since' ) + ); + + return $items; +} + +/** + * Modify the default query. + * + * @param \WP_Query $query + */ +function pre_get_posts( $query ) { + + if ( $query->is_main_query() && $query->is_post_type_archive() ) { + $query->set( 'orderby', 'title' ); + $query->set( 'order', 'ASC' ); + } + + if ( $query->is_main_query() && $query->is_tax() && $query->get( 'wp-parser-source-file' ) ) { + $query->set( 'wp-parser-source-file', str_replace( array( '.php', '/' ), array( '-php', '_' ), $query->query['wp-parser-source-file'] ) ); + } + + // For search query modifications see DevHub_Search. +} + +/** + * Regiser navigation menu area. + */ +function register_nav_menus() { + \register_nav_menus( + array( + 'devhub-menu' => __( 'Developer Resources Menu', 'wporg' ), + 'devhub-cli-menu' => __( 'WP-CLI Commands Menu', 'wporg' ), + 'reference-home-api' => __( 'Reference API Menu', 'wporg' ), + ) + ); +} + +/** + * Filters the permalink for a wp-parser-method post. + * + * @param string $link The post's permalink. + * @param \WP_Post $post The post in question. + * @return string + */ +function method_permalink( $link, $post ) { + global $wp_rewrite; + + if ( ! $wp_rewrite->using_permalinks() || ( 'wp-parser-method' !== $post->post_type ) ) { + return $link; + } + + $parts = explode( '-', $post->post_name ); + $method = array_pop( $parts ); + $class = implode( '-', $parts ); + + return home_url( user_trailingslashit( "reference/classes/$class/$method" ) ); +} + +/** + * Filer the permalink for a taxonomy. + */ +function taxonomy_permalink( $link, $term, $taxonomy ) { + global $wp_rewrite; + + if ( ! $wp_rewrite->using_permalinks() ) { + return $link; + } + + if ( 'wp-parser-source-file' === $taxonomy ) { + $slug = $term->slug; + if ( substr( $slug, -4 ) === '-php' ) { + $slug = substr( $slug, 0, -4 ) . '.php'; + $slug = str_replace( '_', '/', $slug ); + } + $link = home_url( user_trailingslashit( "reference/files/$slug" ) ); + } elseif ( 'wp-parser-since' === $taxonomy ) { + $link = str_replace( $term->slug, str_replace( '-', '.', $term->slug ), $link ); + } + + return $link; +} + +/** + * Register and enqueue the theme assets. + */ +function theme_scripts_styles() { + // The parent style is registered as `wporg-parent-2021-style`, and will be loaded unless + // explicitly unregistered. We can load any child-theme overrides by declaring the parent + // stylesheet as a dependency. + wp_enqueue_style( + 'wporg-developer-2023-style', + get_stylesheet_directory_uri() . '/build/style/style-index.css', + array( 'wporg-parent-2021-style', 'wporg-global-fonts' ), + filemtime( __DIR__ . '/build/style/style-index.css' ) + ); + + // Preload the heading font(s). + if ( is_callable( 'global_fonts_preload' ) ) { + /* translators: Subsets can be any of cyrillic, cyrillic-ext, greek, greek-ext, vietnamese, latin, latin-ext. */ + $subsets = _x( 'Latin', 'Heading font subsets, comma separated', 'wporg' ); + // All headings. + global_fonts_preload( 'EB Garamond, Inter', $subsets ); + } + + if ( function_exists( 'wporg_is_handbook' ) && wporg_is_handbook() ) { + wp_enqueue_script( + 'wporg-developer-code-tabs', + get_stylesheet_directory_uri() . '/js/code-tabs.js', + array(), + filemtime( __DIR__ . '/js/code-tabs.js' ), + true + ); + } + + if ( is_page( 'dashicons' ) ) { + wp_enqueue_style( 'dashicons' ); + } +} + +/** + * Enqueue the editor styles. + */ +function enqueue_admin_styles() { + wp_enqueue_style( + 'wporg-developer-2023-editor-style', + get_stylesheet_directory_uri() . '/build/editor-style/style-index.css', + array( 'editor-buttons' ), + filemtime( __DIR__ . '/build/editor-style/style-index.css' ) + ); +} + +/** + * Rename the 'Comments' meta box to 'User Contributed Notes' for reference-editing screens. + * + * @param string $post_type Post type. + * @param WP_Post $post WP_Post object for the current post. + */ +function rename_comments_meta_box( $post_type, $post ) { + if ( is_parsed_post_type( $post_type ) ) { + remove_meta_box( 'commentsdiv', $post_type, 'normal' ); + add_meta_box( 'commentsdiv', __( 'User Contributed Notes', 'wporg' ), 'post_comment_meta_box', $post_type, 'normal', 'high' ); + } +} + +/** + * Customize the syntax highlighter style. + * See https://github.com/PrismJS/prism-themes. + * + * @param string $path Path to the file to override, relative to the theme. + * @return string + */ +function update_prism_css_path( $path ) { + return '/build/prism/style-index.css'; +} + +/** + * Filters breadcrumb items for the site-breadcrumb block. + * + * @return array + */ +function set_site_breadcrumbs() { + $breadcrumbs = array(); + + foreach ( get_breadcrumbs()->items as $crumb ) { + // Get the link and title from the breadcrumb. + preg_match( '!]+href="(?P[^"]+)"[^>]*>(?P.+)</a>!', $crumb, $matches ); + + $breadcrumbs[] = array( + 'url' => $matches['href'] ?? '', + 'title' => $matches['title'] ?? $crumb, + ); + } + + return $breadcrumbs; +} + +/** + * Provide a list of local navigation menus. + */ +function add_site_navigation_menus( $menus ) { + global $wp; + $is_cli_home = ( is_archive() && isset( $wp->request ) && 'cli/commands' === $wp->request ); + return array( + 'developer' => array( + array( + 'label' => __( 'Developer Blog', 'wporg' ), + 'url' => 'https://developer.wordpress.org/news/', + ), + array( + 'label' => __( 'Code Reference', 'wporg' ), + 'url' => '/reference/', + ), + array( + 'label' => __( 'WP-CLI Commands', 'wporg' ), + 'url' => '/cli/commands/', + 'className' => $is_cli_home ? 'current-menu-item' : '', + ), + ), + ); +} + +/** + * Filter the template heiarchy to add in a general handbook & github handbook template. + * + * @param string[] $templates A list of template candidates, in descending order of priority. + * @return string[] Updated list of templates. + */ +function add_handbook_templates( $templates ) { + $is_handbook = function_exists( 'wporg_is_handbook' ) && wporg_is_handbook(); + $is_github_source = ! empty( get_post_meta( get_the_ID(), 'wporg_markdown_source', true ) ); + + if ( $is_handbook ) { + array_unshift( $templates, 'single-handbook.php' ); + } + + if ( $is_github_source ) { + if ( get_post_type() === 'blocks-handbook' ) { + array_unshift( $templates, 'single-handbook-block-editor.php' ); + } else { + array_unshift( $templates, 'single-handbook-github.php' ); + } + } + + return $templates; +} + +/** + * Filters content for the code reference blocks so Table of Contents can be added. + * + * Note: This filter is added and removed in the files below to prevent infinite loops. + * - src/code-description/block.php + * - src/code-methods/block.php + * Any update to the function name should be reflected there. + * + * @param string $content + * @return string + */ +function filter_code_content( $content ) { + if ( ! is_single() || ! is_parsed_post_type() ) { + return $content; + } + + return do_blocks( + ' + <!-- wp:wporg/code-reference-summary /--> + <!-- wp:wporg/code-reference-description /--> + <!-- wp:wporg/code-reference-parameters /--> + <!-- wp:wporg/code-reference-return-value /--> + <!-- wp:wporg/code-reference-explanation /--> + <!-- wp:wporg/code-reference-methods /--> + <!-- wp:wporg/code-reference-source /--> + <!-- wp:wporg/code-reference-hooks /--> + <!-- wp:wporg/code-reference-related /--> + <!-- wp:wporg/code-reference-changelog /--> + <!-- wp:wporg/code-reference-comments /--> + ' + ); +} + +/** + * Filters content for the github handbooks to wrap tables with block markup for styles. + * + * @param string $content + * @return string + */ +function filter_standards_content( $content ) { + if ( function_exists( 'wporg_is_handbook' ) && ! wporg_is_handbook() ) { + return $content; + } + + // Find table elements in the content and wrap with figure.wp-block-table + $content = preg_replace_callback( + '!<table.*?</table>!is', + function( $matches ) { + return do_blocks( + '<!-- wp:table {"className":"is-style-borderless"} --><figure class="wp-block-table is-style-borderless">' . + $matches[0] . + '</figure><!-- /wp:table -->' + ); + }, + $content + ); + + return $content; +} + + +/** + * Filters content for the command content blocks so Table of Contents can be added. + * + * @param string $content + * @return string + */ +function filter_command_content( $content ) { + global $wp; + // Feed the static content from the CLI archive template to generate the ToC + // Note: ids must be added to the cli-commands-content pattern manually + if ( is_archive() && isset( $wp->request ) && 'cli/commands' === $wp->request ) { + // Stop infinite loop + remove_filter( 'the_content', 'DevHub\filter_command_content', 4 ); + + $content = do_blocks( '<!-- wp:pattern {"slug":"wporg-developer-2023/cli-commands-content"} /-->' ); + + add_filter( 'the_content', 'DevHub\filter_command_content', 4 ); + + return $content; + } + + if ( ! is_single() || ! ( 'command' == get_post_type() ) ) { + return $content; + } + + return do_blocks( + ' + <!-- wp:wporg/command-github /--> + <!-- wp:wporg/command-content /--> + <!-- wp:wporg/command-subcommand /--> + ' + ); +} + +/** + * Switch out the destination for next/prev links to mirror the Chapter List order. + * + * @param string $output The adjacent post link. + * @param string $format Link anchor format. + * @param string $link Link permalink format. + * @param WP_Post $post The adjacent post. + * @param string $adjacent Whether the post is previous or next. + * + * @return string Updated link tag. + */ +function get_adjacent_handbook_post_link( $output, $format, $link, $post, $adjacent ) { + if ( function_exists( 'wporg_is_handbook' ) && ! wporg_is_handbook() ) { + return $output; + } + + $post_id = get_the_ID(); + $pages = get_pages( + array( + 'sort_column' => 'menu_order, title', + 'post_type' => get_post_type( $post_id ), + ) + ); + $is_previous = 'previous' === $adjacent; + + foreach ( $pages as $i => $page ) { + if ( $page->ID === $post_id ) { + $adj_index = $is_previous ? $i - 1 : $i + 1; + break; + } + } + + if ( $adj_index < count( $pages ) && $adj_index > 0 ) { + $post = $pages[ $adj_index ]; + } else { + return ''; + } + + $title = apply_filters( 'the_title', $post->post_title, $post->ID ); + $url = get_permalink( $post ); + + $screen_reader_content = sprintf( + $is_previous + ? /* translators: %s: post title */ + __( 'Previous: %s', 'wporg' ) + : /* translators: %s: post title */ + __( 'Next: %s', 'wporg' ), + $title + ); + + $content = str_replace( + '%title', + sprintf( + '<span aria-hidden="true" class="post-navigation-link__label">%1$s</span> + <span aria-hidden="true" class="post-navigation-link__title">%2$s</span> + <span class="screen-reader-text">%3$s</span>', + $is_previous ? __( 'Previous', 'wporg' ) : __( 'Next', 'wporg' ), + $title, + $screen_reader_content, + ), + $link + ); + + $inlink = sprintf( + '<a href="%1$s" rel="%2$s">%3$s</a>', + $url, + $is_previous ? 'prev' : 'next', + $content + ); + + $output = str_replace( '%link', $inlink, $format ); + + return $output; +} diff --git a/source/wp-content/themes/wpr-developer-2024/images/alert.svg b/source/wp-content/themes/wpr-developer-2024/images/alert.svg new file mode 100644 index 000000000..4112e18c1 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/images/alert.svg @@ -0,0 +1 @@ +<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.6 12a7.6 7.6 0 1 1-15.2 0 7.6 7.6 0 0 1 15.2 0zm1.4 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0zm-8.321 2.093l.128-7.177h-1.619l.128 7.177h1.363zm-1.377 2.705a.96.96 0 0 0 .698.286.93.93 0 0 0 .487-.133 1.017 1.017 0 0 0 .497-.851.948.948 0 0 0-.295-.689.94.94 0 0 0-.689-.29.952.952 0 0 0-.698.29.914.914 0 0 0-.286.689.935.935 0 0 0 .286.698z" fill="#B7B35B"/></svg> diff --git a/source/wp-content/themes/wpr-developer-2024/images/chevron-small.svg b/source/wp-content/themes/wpr-developer-2024/images/chevron-small.svg new file mode 100644 index 000000000..657abe18b --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/images/chevron-small.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> + <path fill-rule="evenodd" clip-rule="evenodd" d="M15.9899 10.8888L12.0018 14.3071L8.01367 10.8888L8.98986 9.74988L12.0018 12.3315L15.0137 9.74988L15.9899 10.8888Z" fill="#1E1E1E"/> +</svg> diff --git a/source/wp-content/themes/wpr-developer-2024/images/chevron.svg b/source/wp-content/themes/wpr-developer-2024/images/chevron.svg new file mode 100644 index 000000000..437f58ee0 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/images/chevron.svg @@ -0,0 +1,3 @@ +<svg width="13" height="8" viewBox="0 0 13 8" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path fill-rule="evenodd" clip-rule="evenodd" d="M.956 6.445 6.961.986l6.004 5.459-1.009 1.11-4.995-4.541-4.996 4.54L.956 6.446Z" fill="#000"/> +</svg> diff --git a/source/wp-content/themes/wpr-developer-2024/images/dot.svg b/source/wp-content/themes/wpr-developer-2024/images/dot.svg new file mode 100644 index 000000000..a811a62aa --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/images/dot.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="4" height="4" viewBox="0 0 4 4" fill="none"> + <circle cx="2" cy="2" r="1.5" fill="#656A71"/> +</svg> diff --git a/source/wp-content/themes/wpr-developer-2024/images/info.svg b/source/wp-content/themes/wpr-developer-2024/images/info.svg new file mode 100644 index 000000000..b975e295a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/images/info.svg @@ -0,0 +1 @@ +<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.6 12a7.6 7.6 0 1 1-15.2 0 7.6 7.6 0 0 1 15.2 0zm1.4 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0zm-9.713-2.226v7.259h1.412V9.774h-1.412zm.08-1.365a.898.898 0 0 0 .633.245.882.882 0 0 0 .629-.245.792.792 0 0 0 .264-.596.783.783 0 0 0-.264-.595.873.873 0 0 0-.629-.25.889.889 0 0 0-.633.25.79.79 0 0 0-.26.595c0 .23.087.429.26.596z" fill="#3858E9"/></svg> diff --git a/source/wp-content/themes/wpr-developer-2024/images/line.svg b/source/wp-content/themes/wpr-developer-2024/images/line.svg new file mode 100644 index 000000000..2ced2a1c9 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/images/line.svg @@ -0,0 +1,3 @@ +<svg width="10" height="1" viewBox="0 0 10 1" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M0 0h10v1h-10V0Z" fill="#000"/> +</svg> diff --git a/source/wp-content/themes/wpr-developer-2024/images/opengraph-image.png b/source/wp-content/themes/wpr-developer-2024/images/opengraph-image.png new file mode 100644 index 000000000..bbe550fc6 Binary files /dev/null and b/source/wp-content/themes/wpr-developer-2024/images/opengraph-image.png differ diff --git a/source/wp-content/themes/wpr-developer-2024/images/tip.svg b/source/wp-content/themes/wpr-developer-2024/images/tip.svg new file mode 100644 index 000000000..d6fb5d18a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/images/tip.svg @@ -0,0 +1 @@ +<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.6 12a7.6 7.6 0 1 1-15.2 0 7.6 7.6 0 0 1 15.2 0zm1.4 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0zm-9.9 1.906v.086h1.297v-.086c.006-.347.045-.639.117-.875.073-.235.188-.44.345-.612a3.06 3.06 0 0 1 .626-.498c.272-.163.504-.35.698-.563.196-.211.347-.451.453-.72.106-.272.159-.578.159-.916 0-.493-.117-.923-.35-1.292a2.312 2.312 0 0 0-.97-.866c-.416-.209-.903-.313-1.46-.313-.507 0-.97.097-1.39.29a2.412 2.412 0 0 0-1.007.857c-.254.375-.392.834-.413 1.378h1.378c.021-.32.101-.581.24-.784.14-.206.315-.357.526-.453.212-.097.434-.145.667-.145.257 0 .49.054.698.163.212.106.38.257.508.453.127.197.19.43.19.698 0 .224-.042.428-.127.612-.084.185-.2.348-.344.49-.145.142-.309.27-.49.385-.281.17-.521.357-.72.563-.2.205-.354.474-.463.806-.106.333-.162.78-.168 1.342zm.045 2.58a.88.88 0 0 0 .64.263c.166 0 .317-.041.453-.123a.938.938 0 0 0 .326-.326.864.864 0 0 0 .127-.458.85.85 0 0 0-.272-.635.867.867 0 0 0-.634-.267.872.872 0 0 0-.64.267.858.858 0 0 0-.267.635c0 .251.09.466.267.644z" fill="#008A20"/></svg> diff --git a/source/wp-content/themes/wpr-developer-2024/images/warning.svg b/source/wp-content/themes/wpr-developer-2024/images/warning.svg new file mode 100644 index 000000000..cced17200 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/images/warning.svg @@ -0,0 +1 @@ +<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="a" fill="#fff"><path d="M20.543 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/></mask><path d="M19.142 12a7.6 7.6 0 0 1-7.6 7.6v2.8c5.744 0 10.4-4.656 10.4-10.4h-2.8zm-7.6 7.6a7.6 7.6 0 0 1-7.6-7.6h-2.8c0 5.744 4.657 10.4 10.4 10.4v-2.8zm-7.6-7.6a7.6 7.6 0 0 1 7.6-7.6V1.6C5.8 1.6 1.143 6.256 1.143 12h2.8zm7.6-7.6a7.6 7.6 0 0 1 7.6 7.6h2.8c0-5.744-4.656-10.4-10.4-10.4v2.8z" fill="#E26F56" mask="url(#a)"/><path fill="#E26F56" d="M15.573 15.04l-.99.99-7.071-7.07.99-.99z"/><path fill="#E26F56" d="M14.584 7.97l.99.99-7.07 7.07-.99-.99z"/></svg> diff --git a/source/wp-content/themes/wpr-developer-2024/inc/admin.php b/source/wp-content/themes/wpr-developer-2024/inc/admin.php new file mode 100644 index 000000000..a12d59535 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/inc/admin.php @@ -0,0 +1,138 @@ +<?php +/** + * Admin area customizations and tools. + * + * @package wporg-developer + */ + +/** + * Class to handle admin area customization and tools. + */ +class DevHub_Admin { + + /** + * Initializer. + */ + public static function init() { + add_action( 'admin_init', [ __CLASS__, 'do_init' ] ); + } + + /** + * Handles adding/removing hooks. + */ + public static function do_init() { + add_action( 'admin_enqueue_scripts', [ __CLASS__, 'admin_enqueue_scripts' ] ); + + add_action( 'comment_author', [ __CLASS__, 'append_user_nicename' ], 10, 2 ); + + if ( class_exists( 'DevHub_User_Contributed_Notes_Voting' ) ) { + // Add a reset votes checkbox to the comment submit metabox. + add_filter( 'edit_comment_misc_actions', [ __CLASS__, 'add_reset_votes_form_field' ], 10, 2 ); + + // Reset votes after editing a comment in the wp-admin. + add_filter( 'comment_edit_redirect', [ __CLASS__, 'comment_edit_redirect' ], 10, 2 ); + } + } + + /** + * Returns array of screen IDs for parsed post types. + * + * @access public + * + * @return array + */ + public static function get_parsed_post_types_screen_ids() { + $screen_ids = []; + foreach ( DevHub\get_parsed_post_types() as $parsed_post_type ) { + $screen_ids[] = $parsed_post_type; + $screen_ids[] = "edit-{$parsed_post_type}"; + } + + return $screen_ids; + } + + /** + * Enqueue JS and CSS on the edit screens for all parsed post types. + * + * @access public + */ + public static function admin_enqueue_scripts() { + // By default, only enqueue on parsed post type screens. + $screen_ids = self::get_parsed_post_types_screen_ids(); + + /** + * Filters whether or not admin.css should be enqueued. + * + * @param bool True if admin.css should be enqueued, false otherwise. + */ + if ( (bool) apply_filters( 'devhub-admin_enqueue_scripts', in_array( get_current_screen()->id, $screen_ids ) ) ) { + wp_enqueue_style( + 'wporg-admin', + get_stylesheet_directory_uri() . '/stylesheets/admin.css', + [], + filemtime( dirname( __DIR__ ) . '/stylesheets/admin.css' ), + ); + } + } + + /** + * Appends the user nicename to the user display name shown for comment authors. + * + * Facilitates discovery of @-mention name for users. + * + * @param string $author_name The comment author's display name. + * @param int $comment_id The comment ID. + * @return string + */ + public static function append_user_nicename( $author_name, $comment_id ) { + $comment = get_comment( $comment_id ); + + if ( $comment->user_id ) { + $username = get_user_by( 'id', $comment->user_id )->user_nicename; + + $author_name .= '</strong><div class="comment-author-nicename">@' . $username . '</div><strong>'; + } + + return $author_name; + } + + /** + * Adds a checkbox for resetting the contributor note votes in the comment submit meta box. + * + * Only displays the checkbox if the vote score is not zero. + * + * @param string $html Html in the submit meta box. + * @param object $comment Current comment object. + * @return string Output html. + */ + public static function add_reset_votes_form_field( $html, $comment ) { + $count = (int) DevHub_User_Contributed_Notes_Voting::count_votes( $comment->comment_ID, 'difference' ); + + if ( 0 !== $count ) { + $html .= '<div class="misc-pub-section misc-pub-reset_votes">'; + $html .= '<input id="reset_votes" type="checkbox" name="reset_votes" value="on" />'; + $html .= '<label for="reset_votes">' . sprintf( __( 'Reset votes (%d)', 'wporg' ), $count ) . '</label>'; + $html .= '</div>'; + } + + return $html; + } + + /** + * Reset votes before the user is redirected from the wp-admin (after editing a comment). + * + * @param string $location The URI the user will be redirected to. + * @param int $comment_id The ID of the comment being edited. + * @return string The redirect URI. + */ + public static function comment_edit_redirect( $location, $comment_id ) { + if ( isset( $_REQUEST['reset_votes'] ) && $_REQUEST['reset_votes'] ) { + DevHub_User_Contributed_Notes_Voting::reset_votes( $comment_id ); + } + + return $location; + } + +} // DevHub_Admin + +DevHub_Admin::init(); diff --git a/source/wp-content/themes/wpr-developer-2024/inc/autocomplete.php b/source/wp-content/themes/wpr-developer-2024/inc/autocomplete.php new file mode 100644 index 000000000..1b112e4ae --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/inc/autocomplete.php @@ -0,0 +1,151 @@ +<?php +/** + * Code Reference autocomplete for the search form. + * + * @package wporg-developer + */ + +/** + * Class to handle autocomplete for the search form. + */ +class DevHub_Search_Form_Autocomplete { + + public function __construct() { + $this->init(); + } + + /** + * Initialization + * + * @access public + */ + public function init() { + + add_action( 'wp_ajax_autocomplete', array( $this, 'autocomplete_data_update' ) ); + add_action( 'wp_ajax_nopriv_autocomplete', array( $this, 'autocomplete_data_update' ) ); + + // Enqueue scripts and styles. + add_action( 'wp_enqueue_scripts', array( $this, 'scripts_and_styles' ), 11 ); + } + + + /** + * Enqueues scripts and styles. + * + * @access public + */ + public function scripts_and_styles() { + + // Handbook searches don't have autocomplete. + if ( function_exists( 'wporg_is_handbook' ) && wporg_is_handbook() ) { + return; + } + + wp_enqueue_style( + 'awesomplete-css', + get_stylesheet_directory_uri() . '/stylesheets/awesomplete.css', + array(), + filemtime( dirname( __DIR__ ) . '/stylesheets/awesomplete.css' ) + ); + wp_enqueue_style( + 'autocomplete-css', + get_stylesheet_directory_uri() . '/stylesheets/autocomplete.css', + array(), + filemtime( dirname( __DIR__ ) . '/stylesheets/autocomplete.css' ) + ); + + wp_register_script( + 'awesomplete', + get_stylesheet_directory_uri() . '/js/awesomplete.min.js', + array(), + filemtime( dirname( __DIR__ ) . '/js/awesomplete.min.js' ), + true + ); + wp_enqueue_script( 'awesomplete' ); + + wp_register_script( 'autocomplete', get_stylesheet_directory_uri() . '/js/autocomplete.js', array( 'awesomplete' ), filemtime( dirname( __DIR__ ) . '/js/autocomplete.js' ), true ); + wp_localize_script( + 'autocomplete', + 'autocomplete', + array( + 'ajaxurl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'autocomplete_nonce' ), + 'post_type' => get_post_type(), + ) + ); + + wp_enqueue_script( 'autocomplete' ); + } + + + /** + * Handles AJAX updates for the autocomplete list. + * + * @access public + * + * @return string JSON data + */ + public function autocomplete_data_update() { + + check_ajax_referer( 'autocomplete_nonce', 'nonce' ); + + $parser_post_types = DevHub\get_parsed_post_types(); + $defaults = array( + 's' => '', + 'posts' => array(), + ); + + if ( ! ( isset( $_POST['data'] ) && $_POST['data'] ) ) { + wp_send_json_error( $defaults ); + } + + // Parse the search form fields. + wp_parse_str( $_POST['data'], $form_data ); + $form_data = array_merge( $defaults, $form_data ); + + // No search query. + if ( empty( $form_data['s'] ) ) { + wp_send_json_error( $defaults ); + } + + $post_types = isset( $_POST['post_type'] ) && 'command' === $_POST['post_type'] ? + array( 'command' ) : + $parser_post_types; + + $args = array( + 'posts_per_page' => -1, + 'post_type' => $post_types, + 's' => $form_data['s'], + 'orderby' => '', + 'search_orderby_title' => 1, + 'order' => 'ASC', + '_autocomplete_search' => true, + ); + + $search = get_posts( $args ); + + if ( ! empty( $search ) ) { + $post_types_function_like = array( 'wp-parser-function', 'wp-parser-method' ); + + foreach ( $search as $post ) { + $permalink = get_permalink( $post->ID ); + $title = $post->post_title; + + if ( in_array( $post->post_type, $post_types_function_like ) ) { + $title .= '()'; + } + + if ( $post->post_type == 'wp-parser-class' ) { + $title = 'class ' . $title . ' {}'; + } + + $form_data['posts'][ $title ] = $permalink; + } + } + + wp_send_json_success( $form_data ); + } + +} + +$autocomplete = new DevHub_Search_Form_Autocomplete(); diff --git a/source/wp-content/themes/wpr-developer-2024/inc/block-hooks.php b/source/wp-content/themes/wpr-developer-2024/inc/block-hooks.php new file mode 100644 index 000000000..5c4fec7bf --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/inc/block-hooks.php @@ -0,0 +1,81 @@ +<?php +/** + * Blocks hooks. + * + * @package wporg-developer-2023 + */ + +use function DevHub\is_parsed_post_type; + +add_filter( 'render_block_data', __NAMESPACE__ . '\modify_header_template_part' ); + +/** + * Filters the search block and conditionally inserts search filters. + * + * @param string $block_content + * @param array $block + * @return string + */ +function filter_search_block( $block_content, $block ) { + if ( 'core/search' !== $block['blockName'] ) { + return $block_content; + } + + if ( 'command' === get_post_type() ) { + $block_content = get_block_content_by_home_url( $block_content, home_url( 'cli/commands/' ) ); + } elseif ( function_exists( 'wporg_is_handbook' ) && wporg_is_handbook() ) { + $block_content = get_block_content_by_home_url( $block_content, get_query_var( 'current_handbook_home_url' ) ); + } else { + if ( isset( $block['attrs']['className'] ) && strpos( $block['attrs']['className'], 'wporg-filtered-search-form' ) ) { + $block_content = str_replace( '</form>', do_blocks( '<!-- wp:wporg/search-filters /-->' ) . '</form>', $block_content ); + } + } + + return $block_content; +} + +add_filter( 'render_block', __NAMESPACE__ . '\\filter_search_block', 10, 2 ); + +/** + * Replaces the action URL in a block content string with a given URL path. + * + * @param string $block_content The block content string to modify. + * @param string $replacement_home_url Replacement string for the action attribute. Defaults to an empty string. + * @return string The modified block content string. + */ +function get_block_content_by_home_url( $block_content, $replacement_home_url = '' ) { + return str_replace( + 'action="' . esc_url( home_url( '/' ) ) . '"', + 'action="' . esc_url( $replacement_home_url ) . '"', + $block_content + ); +} + +/** + * Update header template based on current query. + * + * @param array $parsed_block The block being rendered. + * + * @return array The updated block. + */ +function modify_header_template_part( $parsed_block ) { + if ( + 'core/template-part' === $parsed_block['blockName'] && + ! empty( $parsed_block['attrs']['slug'] ) && + str_starts_with( $parsed_block['attrs']['slug'], 'header' ) + ) { + $template_slug = 'header-third'; + if ( + function_exists( 'wporg_is_handbook' ) && + wporg_is_handbook() && + ! wporg_is_handbook_landing_page() + ) { + $parsed_block['attrs']['slug'] = $template_slug; + } elseif ( 'command' === get_post_type() && is_single() ) { + $parsed_block['attrs']['slug'] = $template_slug; + } elseif ( is_parsed_post_type() && ! is_search() ) { + $parsed_block['attrs']['slug'] = $template_slug; + } + } + return $parsed_block; +} diff --git a/source/wp-content/themes/wpr-developer-2024/inc/breadcrumb-trail.php b/source/wp-content/themes/wpr-developer-2024/inc/breadcrumb-trail.php new file mode 100644 index 000000000..1e193d2a8 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/inc/breadcrumb-trail.php @@ -0,0 +1,1198 @@ +<?php +/** + * Breadcrumb Trail - A breadcrumb menu script for WordPress. + * + * Breadcrumb Trail is a script for showing a breadcrumb trail for any type of page. It tries to + * anticipate any type of structure and display the best possible trail that matches your site's + * permalink structure. While not perfect, it attempts to fill in the gaps left by many other + * breadcrumb scripts. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @package BreadcrumbTrail + * @version 0.6.1.modified + * @author Justin Tadlock <justin@justintadlock.com> + * @copyright Copyright (c) 2008 - 2013, Justin Tadlock + * @link https://themehybrid.com/plugins/breadcrumb-trail + * @license https://www.gnu.org/licenses/old-licenses/gpl-2.0.html + */ + +/** + * Shows a breadcrumb for all types of pages. This is a wrapper function for the Breadcrumb_Trail class, + * which should be used in theme templates. + * + * @since 0.1.0 + * @access public + * @param array $args Arguments to pass to Breadcrumb_Trail. + * @return void + */ +function get_breadcrumbs( $args = array() ) { + + if ( function_exists( 'is_bbpress' ) && is_bbpress() ) + $breadcrumb = new bbPress_Breadcrumb_Trail( $args ); + else + $breadcrumb = new Breadcrumb_Trail( $args ); + + return $breadcrumb; +} + +/** + * Creates a breadcrumbs menu for the site based on the current page that's being viewed by the user. + * + * @since 0.6.0 + */ +class Breadcrumb_Trail { + + /** + * Array of items belonging to the current breadcrumb trail. + * + * @since 0.1.0 + * @access public + * @var array + */ + public $items = array(); + + /** + * Arguments used to build the breadcrumb trail. + * + * @since 0.1.0 + * @access public + * @var array + */ + public $args = array(); + + /** + * Sets up the breadcrumb trail. + * + * @since 0.6.0 + * @access public + * @param array $args The arguments for how to build the breadcrumb trail. + * @return void + */ + public function __construct( $args = array() ) { + + /* Remove the bbPress breadcrumbs. */ + add_filter( 'bbp_get_breadcrumb', '__return_false' ); + + $defaults = array( + 'container' => 'div', + 'item_container' => 'span', + 'separator' => '/', + 'before' => '', + 'after' => '', + 'show_on_front' => true, + 'network' => false, + //'show_edit_link' => false, + 'show_title' => true, + 'show_browse' => false, + 'echo' => false, + + /* Post taxonomy (examples follow). */ + 'post_taxonomy' => array( + // 'post' => 'post_tag', + // 'book' => 'genre', + ), + + /* Labels for text used (see Breadcrumb_Trail::default_labels). */ + 'labels' => array() + ); + + $this->args = apply_filters( 'breadcrumb_trail_args', wp_parse_args( $args, $defaults ) ); + + /* Merge the user-added labels with the defaults. */ + $this->args['labels'] = wp_parse_args( $this->args['labels'], $this->default_labels() ); + + $this->do_trail_items(); + } + + /** + * Returns an array of the default labels. + * + * @since 0.6.0 + * @access public + * @return array + */ + public function default_labels() { + + $labels = array( + 'browse' => __( 'Browse:', 'breadcrumb-trail' ), + 'home' => __( 'Home', 'breadcrumb-trail' ), + 'search' => __( 'Search results', 'breadcrumb-trail' ), + 'error_404' => __( '404 Not Found', 'breadcrumb-trail' ), + 'paged' => __( 'Page %d', 'breadcrumb-trail' ), + 'archives' => __( 'Archives', 'breadcrumb-trail' ), + 'archive_minute_hour' => __( 'g:i a', 'breadcrumb-trail' ), + 'archive_minute' => __( 'Minute %d', 'breadcrumb-trail' ), + 'archive_hour' => __( 'g a', 'breadcrumb-trail' ), + 'archive_day' => __( 'd', 'breadcrumb-trail' ), + 'archive_week' => __( 'Week %d', 'breadcrumb-trail' ), + 'archive_month' => __( 'F', 'breadcrumb-trail' ), + 'archive_year' => __( 'Y', 'breadcrumb-trail' ), + // 'edit' => __( 'Edit', 'breadcrumb-trail' ), // @todo Implement edit link + ); + + return $labels; + } + + /** + * Runs through the various WordPress conditional tags to check the current page being viewed. Once + * a condition is met, a specific method is launched to add items to the $items array. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_trail_items() { + + /* If viewing the front page. */ + if ( is_front_page() ) { + $this->do_front_page_items(); + } + + /* If not viewing the front page. */ + else { + + /* Add the network and site home links. */ + $this->do_network_home_link(); + $this->do_site_home_link(); + + /* If viewing the home/blog page. */ + if ( is_home() ) { + $this->do_posts_page_items(); + } + + /* If viewing a single post. */ + elseif ( is_singular() ) { + $this->do_singular_items(); + } + + /* If viewing a search results page. */ + elseif ( is_search() ) { + if ( function_exists( 'wporg_is_handbook' ) && wporg_is_handbook() ) { + $this->do_search_items_by_home_url( wporg_get_current_handbook_home_url() ); + } elseif ( 'command' === get_post_type() ) { + $this->do_search_items_by_home_url( esc_url( home_url( 'cli/commands/' ) ) ); + } else { + $this->do_search_items(); + } + } + + /* If viewing an archive page. */ + elseif ( is_archive() ) { + + if ( is_post_type_archive() ) + $this->do_post_type_archive_items(); + + elseif ( is_category() || is_tag() || is_tax() ) + $this->do_term_archive_items(); + + elseif ( is_author() ) + $this->do_user_archive_items(); + + elseif ( get_query_var( 'minute' ) && get_query_var( 'hour' ) ) + $this->do_minute_hour_archive_items(); + + elseif ( get_query_var( 'minute' ) ) + $this->do_minute_archive_items(); + + elseif ( get_query_var( 'hour' ) ) + $this->do_hour_archive_items(); + + elseif ( is_day() ) + $this->do_day_archive_items(); + + elseif ( get_query_var( 'w' ) ) + $this->do_week_archive_items(); + + elseif ( is_month() ) + $this->do_month_archive_items(); + + elseif ( is_year() ) + $this->do_year_archive_items(); + + else + $this->do_default_archive_items(); + } + + /* If viewing the 404 page. */ + elseif ( is_404() ) { + $this->do_404_items(); + } + } + + /* Add paged items if they exist. */ + $this->do_paged_items(); + + /* Allow developers to overwrite the items for the breadcrumb trail. */ + $this->items = apply_filters( 'breadcrumb_trail_items', $this->items, $this->args ); + } + + /** + * Gets front items based on $wp_rewrite->front. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_rewrite_front_items() { + global $wp_rewrite; + + if ( $wp_rewrite->front ) + $this->do_path_parents( $wp_rewrite->front ); + } + + /** + * Adds the page/paged number to the items array. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_paged_items() { + + /* If viewing a paged singular post. */ + if ( is_singular() && 1 < get_query_var( 'page' ) && true === $this->args['show_title'] ) + $this->items[] = sprintf( $this->args['labels']['paged'], absint( get_query_var( 'page' ) ) ); + + /* If viewing a paged archive-type page. */ + elseif ( is_paged() && true === $this->args['show_title'] ) + $this->items[] = sprintf( $this->args['labels']['paged'], absint( get_query_var( 'paged' ) ) ); + + } + + /** + * Adds the network (all sites) home page link to the items array. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_network_home_link() { + if ( is_multisite() && !is_main_site() && true === $this->args['network'] ) + $this->items[] = '<a href="' . network_home_url() . '" title="' . esc_attr( $this->args['labels']['home'] ) . '" rel="home">' . $this->args['labels']['home'] . '</a>'; + } + + /** + * Adds the current site's home page link to the items array. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_site_home_link() { + $label = ( is_multisite() && !is_main_site() && true === $this->args['network'] ) ? get_bloginfo( 'name' ) : $this->args['labels']['home']; + $rel = ( is_multisite() && !is_main_site() && true === $this->args['network'] ) ? '' : ' rel="home"'; + $this->items[] = '<a href="' . home_url() . '" title="' . esc_attr( get_bloginfo( 'name' ) ) . '"' . $rel .'>' . $label . '</a>'; + } + + /** + * Adds items for the front page to the items array. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_front_page_items() { + + /* Only show front items if the 'show_on_front' argument is set to 'true'. */ + if ( true === $this->args['show_on_front'] || is_paged() || ( is_singular() && 1 < get_query_var( 'page' ) ) ) { + + /* If on a paged view, add the home link items. */ + if ( is_paged() ) { + $this->do_network_home_link(); + $this->do_site_home_link(); + } + + /* If on the main front page, add the network home link item and the home item. */ + else { + $this->do_network_home_link(); + + if ( true === $this->args['show_title'] ) + $this->items[] = ( is_multisite() && true === $this->args['network'] ) ? get_bloginfo( 'name' ) : $this->args['labels']['home']; + } + } + } + + /** + * Adds items for the posts page (i.e., is_home()) to the items array. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_posts_page_items() { + + /* Get the post ID and post. */ + $post_id = get_queried_object_id(); + $post = get_page( $post_id ); + + /* If the post has parents, add them to the trail. */ + if ( 0 < $post->post_parent ) + $this->do_post_parents( $post->post_parent ); + + /* Get the page title. */ + $title = get_the_title( $post_id ); + + /* Add the posts page item. */ + if ( is_paged() ) + $this->items[] = '<a href="' . get_permalink( $post_id ) . '" title="' . esc_attr( $title ) . '">' . $title . '</a>'; + + elseif ( $title && true === $this->args['show_title'] ) + $this->items[] = $title; + } + + /** + * Adds singular post items to the items array. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_singular_items() { + + /* Get the queried post. */ + $post = get_queried_object(); + $post_id = get_queried_object_id(); + if ( ! $post || ! $post_id ) { + return; + } + + /* If the post has a parent, follow the parent trail. */ + if ( 0 < $post->post_parent ) + $this->do_post_parents( $post->post_parent ); + + /* If the post doesn't have a parent, get its hierarchy based off the post type. */ + else + $this->do_post_hierarchy( $post_id ); + + /* Display terms for specific post type taxonomy if requested. */ + $this->do_post_terms( $post_id ); + + /* End with the post title. */ + if ( $post_title = single_post_title( '', false ) ) { + + if ( 1 < get_query_var( 'page' ) ) + $this->items[] = '<a href="' . get_permalink( $post_id ) . '" title="' . esc_attr( $post_title ) . '">' . $post_title . '</a>'; + + elseif ( true === $this->args['show_title'] ) + $this->items[] = $post_title; + } + } + + /** + * Adds a specific post's parents to the items array. + * + * @since 0.6.0 + * @access public + * @param int $post_id The ID of the post to get the parents of. + * @return void + */ + public function do_post_parents( $post_id ) { + $parents = array(); + + while ( $post_id ) { + + /* Get the post by ID. */ + $post = get_post( $post_id ); + + /* Add the formatted post link to the array of parents. */ + $parents[] = '<a href="' . get_permalink( $post_id ) . '" title="' . esc_attr( get_the_title( $post_id ) ) . '">' . get_the_title( $post_id ) . '</a>'; + + /* If there's no longer a post parent, brea out of the loop. */ + if ( 0 >= $post->post_parent ) + break; + + /* Change the post ID to the parent post to continue looping. */ + $post_id = $post->post_parent; + } + + /* Get the post hierarchy based off the final parent post. */ + $this->do_post_hierarchy( $post_id ); + + /* Merge the parent items into the items array. */ + $this->items = array_merge( $this->items, array_reverse( $parents ) ); + } + + /** + * Adds a post's terms from a specific taxonomy to the items array. + * + * @since 0.6.0 + * @access public + * @param int $post_id The ID of the post to get the terms for. + * @return void + */ + public function do_post_terms( $post_id ) { + + /* Get the post type. */ + $post_type = get_post_type( $post_id ); + + /* Add the terms of the taxonomy for this post. */ + if ( !empty( $this->args['post_taxonomy'][ $post_type ] ) ) + $this->items[] = get_the_term_list( $post_id, $this->args['post_taxonomy'][ $post_type ], '', ', ', '' ); + } + + /** + * Adds a specific post's hierarchy to the items array. The hierarchy is determined by post type's + * rewrite arguments and whether it has an archive page. + * + * @since 0.6.0 + * @access public + * @param int $post_id The ID of the post to get the hierarchy for. + * @return void + */ + public function do_post_hierarchy( $post_id ) { + + /* Get the post type. */ + $post_type = get_post_type( $post_id ); + $post_type_object = get_post_type_object( $post_type ); + + /* If this is the 'post' post type, get the rewrite front items and map the rewrite tags. */ + if ( 'post' === $post_type ) { + + /* Add $wp_rewrite->front to the trail. */ + $this->do_rewrite_front_items(); + + /* Map the rewrite tags. */ + $this->map_rewrite_tags( $post_id, get_option( 'permalink_structure' ) ); + } + + /* If the post type has rewrite rules. */ + elseif ( false !== $post_type_object->rewrite ) { + + /* If 'with_front' is true, add $wp_rewrite->front to the trail. */ + if ( $post_type_object->rewrite['with_front'] ) + $this->do_rewrite_front_items(); + + /* If there's a path, check for parents. */ + if ( !empty( $post_type_object->rewrite['slug'] ) ) + $this->do_path_parents( $post_type_object->rewrite['slug'] ); + } + + /* If there's an archive page, add it to the trail. */ + if ( !empty( $post_type_object->has_archive ) ) { + + /* Add support for a non-standard label of 'archive_title' (special use case). */ + $label = !empty( $post_type_object->labels->archive_title ) ? $post_type_object->labels->archive_title : $post_type_object->labels->name; + + $this->items[] = '<a href="' . get_post_type_archive_link( $post_type ) . '">' . $label . '</a>'; + } + } + + /** + * Gets post types by slug. This is needed because the get_post_types() function doesn't exactly + * match the 'has_archive' argument when it's set as a string instead of a boolean. + * + * @since 0.6.0 + * @access public + * @param int $slug The post type archive slug to search for. + * @return void + */ + public function get_post_types_by_slug( $slug ) { + + $return = array(); + + $post_types = get_post_types( array(), 'objects' ); + + foreach ( $post_types as $type ) { + + if ( $slug === $type->has_archive || ( true === $type->has_archive && $slug === $type->rewrite['slug'] ) ) + $return[] = $type; + } + + return $return; + } + + /** + * Adds the items to the trail items array for taxonomy term archives. + * + * @since 0.6.0 + * @access public + * @global object $wp_rewrite + * @return void + */ + public function do_term_archive_items() { + global $wp_rewrite; + + /* Get some taxonomy and term variables. */ + $term = get_queried_object(); + $taxonomy = get_taxonomy( $term->taxonomy ); + + /* If there are rewrite rules for the taxonomy. */ + if ( false !== $taxonomy->rewrite ) { + + /* If 'with_front' is true, dd $wp_rewrite->front to the trail. */ + if ( $taxonomy->rewrite['with_front'] && $wp_rewrite->front ) + $this->do_rewrite_front_items(); + + /* Get parent pages by path if they exist. */ + $this->do_path_parents( $taxonomy->rewrite['slug'] ); + + /* Add post type archive if its 'has_archive' matches the taxonomy rewrite 'slug'. */ + if ( $taxonomy->rewrite['slug'] ) { + + $slug = trim( $taxonomy->rewrite['slug'], '/' ); + + /** + * Deals with the situation if the slug has a '/' between multiple strings. For + * example, "movies/genres" where "movies" is the post type archive. + */ + $matches = explode( '/', $slug ); + + /* If matches are found for the path. */ + if ( isset( $matches ) ) { + + /* Reverse the array of matches to search for posts in the proper order. */ + $matches = array_reverse( $matches ); + + /* Loop through each of the path matches. */ + foreach ( $matches as $match ) { + + /* If a match is found. */ + $slug = $match; + + /* Get public post types that match the rewrite slug. */ + $post_types = $this->get_post_types_by_slug( $match ); + + if ( !empty( $post_types ) ) { + + $post_type_object = $post_types[0]; + + /* Add support for a non-standard label of 'archive_title' (special use case). */ + $label = !empty( $post_type_object->labels->archive_title ) ? $post_type_object->labels->archive_title : $post_type_object->labels->name; + + /* Add the post type archive link to the trail. */ + $this->items[] = '<a href="' . get_post_type_archive_link( $post_type_object->name ) . '" title="' . esc_attr( $label ) . '">' . $label . '</a>'; + + /* Break out of the loop. */ + break; + } + } + } + } + } + + /* If the taxonomy is hierarchical, list its parent terms. */ + if ( is_taxonomy_hierarchical( $term->taxonomy ) && $term->parent ) + $this->do_term_parents( $term->parent, $term->taxonomy ); + + /* Add the term name to the trail end. */ + if ( is_paged() ) + $this->items[] = '<a href="' . esc_url( get_term_link( $term, $term->taxonomy ) ) . '" title="' . esc_attr( single_term_title( '', false ) ) . '">' . single_term_title( '', false ) . '</a>'; + + elseif ( true === $this->args['show_title'] ) + $this->items[] = single_term_title( '', false ); + } + + /** + * Adds the items to the trail items array for post type archives. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_post_type_archive_items() { + + /* Get the post type object. */ + $post_type_object = get_post_type_object( get_query_var( 'post_type' ) ); + + if ( false !== $post_type_object->rewrite ) { + + /* If 'with_front' is true, add $wp_rewrite->front to the trail. */ + if ( $post_type_object->rewrite['with_front'] ) + $this->do_rewrite_front_items(); + + /* If there's a rewrite slug, check for parents. */ + if ( !empty( $post_type_object->rewrite['slug'] ) ) + $this->do_path_parents( $post_type_object->rewrite['slug'] ); + } + + /* Add the post type [plural] name to the trail end. */ + if ( is_paged() ) + $this->items[] = '<a href="' . esc_url( get_post_type_archive_link( $post_type_object->name ) ) . '" title="' . esc_attr( post_type_archive_title( '', false ) ) . '">' . post_type_archive_title( '', false ) . '</a>'; + + elseif ( true === $this->args['show_title'] ) + $this->items[] = post_type_archive_title( '', false ); + } + + /** + * Adds the items to the trail items array for user (author) archives. + * + * @since 0.6.0 + * @access public + * @global object $wp_rewrite + * @return void + */ + public function do_user_archive_items() { + global $wp_rewrite; + + /* Add $wp_rewrite->front to the trail. */ + $this->do_rewrite_front_items(); + + /* Get the user ID. */ + $user_id = get_query_var( 'author' ); + + /* If $author_base exists, check for parent pages. */ + if ( !empty( $wp_rewrite->author_base ) ) + $this->do_path_parents( $wp_rewrite->author_base ); + + /* Add the author's display name to the trail end. */ + if ( is_paged() ) + $this->items[] = '<a href="'. esc_url( get_author_posts_url( $user_id ) ) . '" title="' . esc_attr( get_the_author_meta( 'display_name', $user_id ) ) . '">' . get_the_author_meta( 'display_name', $user_id ) . '</a>'; + + elseif ( true === $this->args['show_title'] ) + $this->items[] = get_the_author_meta( 'display_name', $user_id ); + } + + /** + * Adds the items to the trail items array for minute + hour archives. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_minute_hour_archive_items() { + + /* Add $wp_rewrite->front to the trail. */ + $this->do_rewrite_front_items(); + + /* Add the minute + hour item. */ + if ( true === $this->args['show_title'] ) + $this->items[] = get_the_time( $this->args['labels']['archive_minute_hour'] ); + } + + /** + * Adds the items to the trail items array for minute archives. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_minute_archive_items() { + + /* Add $wp_rewrite->front to the trail. */ + $this->do_rewrite_front_items(); + + /* Add the minute item. */ + if ( true === $this->args['show_title'] ) + $this->items[] = sprintf( $this->args['labels']['archive_minute'], date_i18n( 'i', get_the_time( 'U' ) ) ); + } + + /** + * Adds the items to the trail items array for hour archives. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_hour_archive_items() { + + /* Add $wp_rewrite->front to the trail. */ + $this->do_rewrite_front_items(); + + /* Add the hour item. */ + if ( true === $this->args['show_title'] ) + $this->items[] = get_the_time( $this->args['labels']['archive_hour'] ); + } + + /** + * Adds the items to the trail items array for day archives. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_day_archive_items() { + + /* Add $wp_rewrite->front to the trail. */ + $this->do_rewrite_front_items(); + + /* Get year, month, and day. */ + $year = get_the_time( $this->args['labels']['archive_year'] ); + $month = get_the_time( $this->args['labels']['archive_month'] ); + $day = get_the_time( $this->args['labels']['archive_day'] ); + + /* Add the year and month items. */ + $this->items[] = '<a href="' . get_year_link( get_the_time( 'Y' ) ) . '" title="' . esc_attr( $year ) . '">' . $year . '</a>'; + $this->items[] = '<a href="' . get_month_link( get_the_time( 'Y' ), get_the_time( 'm' ) ) . '" title="' . esc_attr( $month ) . '">' . $month . '</a>'; + + /* Add the day item. */ + if ( is_paged() ) + $this->items[] = '<a href="' . get_day_link( get_the_time( 'Y' ), get_the_time( 'm' ), get_the_time( 'd' ) ) . '" title="' . esc_attr( $day ) . '">' . $day . '</a>'; + + elseif ( true === $this->args['show_title'] ) + $this->items[] = $day; + } + + /** + * Adds the items to the trail items array for week archives. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_week_archive_items() { + + /* Add $wp_rewrite->front to the trail. */ + $this->do_rewrite_front_items(); + + /* Get the year and week. */ + $year = get_the_time( $this->args['labels']['archive_year'] ); + $week = sprintf( $this->args['labels']['archive_week'], date_i18n( 'W', get_the_time( 'U' ) ) ); + + /* Add the year item. */ + $this->items[] = '<a href="' . get_year_link( get_the_time( 'Y' ) ) . '" title="' . esc_attr( $year ) . '">' . $year . '</a>'; + + /* Add the week item. */ + if ( is_paged() ) + $this->items[] = get_archives_link( add_query_arg( array( 'm' => get_the_time( 'Y' ), 'w' => get_the_time( 'W' ) ), home_url() ), $week, false ); + + elseif ( true === $this->args['show_title'] ) + $this->items[] = $week; + } + + /** + * Adds the items to the trail items array for month archives. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_month_archive_items() { + + /* Add $wp_rewrite->front to the trail. */ + $this->do_rewrite_front_items(); + + /* Get the year and month. */ + $year = get_the_time( $this->args['labels']['archive_year'] ); + $month = get_the_time( $this->args['labels']['archive_month'] ); + + /* Add the year item. */ + $this->items[] = '<a href="' . get_year_link( get_the_time( 'Y' ) ) . '" title="' . esc_attr( $year ) . '">' . $year . '</a>'; + + /* Add the month item. */ + if ( is_paged() ) + $this->items[] = '<a href="' . get_month_link( get_the_time( 'Y' ), get_the_time( 'm' ) ) . '" title="' . esc_attr( $month ) . '">' . $month . '</a>'; + + elseif ( true === $this->args['show_title'] ) + $this->items[] = $month; + } + + /** + * Adds the items to the trail items array for year archives. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_year_archive_items() { + + /* Add $wp_rewrite->front to the trail. */ + $this->do_rewrite_front_items(); + + /* Get the year. */ + $year = get_the_time( $this->args['labels']['archive_year'] ); + + /* Add the year item. */ + if ( is_paged() ) + $this->items[] = '<a href="' . get_year_link( get_the_time( 'Y' ) ) . '" title="' . esc_attr( $year ) . '">' . $year . '</a>'; + + elseif ( true === $this->args['show_title'] ) + $this->items[] = $year; + } + + /** + * Adds the items to the trail items array for archives that don't have a more specific method + * defined in this class. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_default_archive_items() { + + /* If this is a date-/time-based archive, add $wp_rewrite->front to the trail. */ + if ( is_date() || is_time() ) + $this->do_rewrite_front_items(); + + if ( true === $this->args['show_title'] ) + $this->items[] = $this->args['labels']['archives']; + } + + /** + * Adds the items to the trail items array for search results. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_search_items() { + + if ( is_paged() ) + $this->items[] = '<a href="' . get_search_link() . '" title="' . esc_attr( sprintf( $this->args['labels']['search'], get_search_query() ) ) . '">' . sprintf( $this->args['labels']['search'], get_search_query() ) . '</a>'; + + elseif ( true === $this->args['show_title'] ) + $this->items[] = sprintf( $this->args['labels']['search'], get_search_query() ); + } + + /** + * Adds the items to the trail items array accroding to the home_url. + * + * @since 0.6.0 + * @access public + * @param string $home_url The home url of the search item. + * @return void + */ + public function do_search_items_by_home_url( $home_url ) { + $this->items[] = '<a href="' . $home_url . '">' . get_queried_object()->label . '</a>'; + + if ( is_paged() ) { + $search_url = add_query_arg( 's', get_search_query(), $home_url ); + $this->items[] = '<a href="' . esc_url( $search_url ) . '">' . esc_html( $this->args['labels']['search'] ) . '</a>'; + } elseif ( true === $this->args['show_title'] ) { + $this->items[] = $this->args['labels']['search']; + } + } + + /** + * Adds the items to the trail items array for 404 pages. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_404_items() { + + if ( true === $this->args['show_title'] ) + $this->items[] = $this->args['labels']['error_404']; + } + + /** + * Get parent posts by path. Currently, this method only supports getting parents of the 'page' + * post type. The goal of this function is to create a clear path back to home given what would + * normally be a "ghost" directory. If any page matches the given path, it'll be added. + * + * @since 0.6.0 + * @access public + * @param string $path The path (slug) to search for posts by. + * @return void + */ + function do_path_parents( $path ) { + + /* Trim '/' off $path in case we just got a simple '/' instead of a real path. */ + $path = trim( $path, '/' ); + + /* If there's no path, return. */ + if ( empty( $path ) ) + return; + + /* Get parent post by the path. */ + $post = get_page_by_path( $path ); + + if ( !empty( $post ) ) { + $this->do_post_parents( $post->ID ); + } + + elseif ( is_null( $post ) ) { + + /* Separate post names into separate paths by '/'. */ + $path = trim( $path, '/' ); + preg_match_all( "/\/.*?\z/", $path, $matches ); + + /* If matches are found for the path. */ + if ( isset( $matches ) ) { + + /* Reverse the array of matches to search for posts in the proper order. */ + $matches = array_reverse( $matches ); + + /* Loop through each of the path matches. */ + foreach ( $matches as $match ) { + + /* If a match is found. */ + if ( isset( $match[0] ) ) { + + /* Get the parent post by the given path. */ + $path = str_replace( $match[0], '', $path ); + $post = get_page_by_path( trim( $path, '/' ) ); + + /* If a parent post is found, set the $post_id and break out of the loop. */ + if ( !empty( $post ) && 0 < $post->ID ) { + $this->do_post_parents( $post->ID ); + break; + } + } + } + } + } + } + + /** + * Searches for term parents of hierarchical taxonomies. This function is similar to the WordPress + * function get_category_parents() but handles any type of taxonomy. + * + * @since 0.6.0 + * @param int $term_id ID of the term to get the parents of. + * @param string $taxonomy Name of the taxonomy for the given term. + * @return void + */ + function do_term_parents( $term_id, $taxonomy ) { + + /* Set up some default arrays. */ + $parents = array(); + + /* While there is a parent ID, add the parent term link to the $parents array. */ + while ( $term_id ) { + + /* Get the parent term. */ + $term = get_term( $term_id, $taxonomy ); + + /* Add the formatted term link to the array of parent terms. */ + $parents[] = '<a href="' . get_term_link( $term, $taxonomy ) . '" title="' . esc_attr( $term->name ) . '">' . $term->name . '</a>'; + + /* Set the parent term's parent as the parent ID. */ + $term_id = $term->parent; + } + + /* If we have parent terms, reverse the array to put them in the proper order for the trail. */ + if ( !empty( $parents ) ) + $this->items = array_merge( $this->items, $parents ); + } + + /** + * Turns %tag% from permalink structures into usable links for the breadcrumb trail. This feels kind of + * hackish for now because we're checking for specific %tag% examples and only doing it for the 'post' + * post type. In the future, maybe it'll handle a wider variety of possibilities, especially for custom post + * types. + * + * @since 0.6.0 + * @access public + * @param int $post_id ID of the post whose parents we want. + * @param string $path Path of a potential parent page. + * @param array $args Mixed arguments for the menu. + * @return array + */ + public function map_rewrite_tags( $post_id, $path ) { + + /* Get the post based on the post ID. */ + $post = get_post( $post_id ); + + /* If no post is returned, an error is returned, or the post does not have a 'post' post type, return. */ + if ( empty( $post ) || is_wp_error( $post ) || 'post' !== $post->post_type ) + return $trail; + + /* Trim '/' from both sides of the $path. */ + $path = trim( $path, '/' ); + + /* Split the $path into an array of strings. */ + $matches = explode( '/', $path ); + + /* If matches are found for the path. */ + if ( is_array( $matches ) ) { + + /* Loop through each of the matches, adding each to the $trail array. */ + foreach ( $matches as $match ) { + + /* Trim any '/' from the $match. */ + $tag = trim( $match, '/' ); + + /* If using the %year% tag, add a link to the yearly archive. */ + if ( '%year%' == $tag ) + $this->items[] = '<a href="' . get_year_link( get_the_time( 'Y', $post_id ) ) . '" title="' . get_the_time( __( 'Y', 'breadcrumb-trail' ), $post_id ) . '">' . get_the_time( $this->args['labels']['archive_year'], $post_id ) . '</a>'; + + /* If using the %monthnum% tag, add a link to the monthly archive. */ + elseif ( '%monthnum%' == $tag ) + $this->items[] = '<a href="' . get_month_link( get_the_time( 'Y', $post_id ), get_the_time( 'm', $post_id ) ) . '" title="' . get_the_time( esc_attr__( 'F Y', 'breadcrumb-trail' ), $post_id ) . '">' . get_the_time( $this->args['labels']['archive_month'], $post_id ) . '</a>'; + + /* If using the %day% tag, add a link to the daily archive. */ + elseif ( '%day%' == $tag ) + $this->items[] = '<a href="' . get_day_link( get_the_time( 'Y', $post_id ), get_the_time( 'm', $post_id ), get_the_time( 'd', $post_id ) ) . '" title="' . get_the_time( esc_attr__( 'F j, Y', 'breadcrumb-trail' ), $post_id ) . '">' . get_the_time( $this->args['labels']['archive_day'], $post_id ) . '</a>'; + + /* If using the %author% tag, add a link to the post author archive. */ + elseif ( '%author%' == $tag ) + $this->items[] = '<a href="' . get_author_posts_url( $post->post_author ) . '" title="' . esc_attr( get_the_author_meta( 'display_name', $post->post_author ) ) . '">' . get_the_author_meta( 'display_name', $post->post_author ) . '</a>'; + + /* If using the %category% tag, add a link to the first category archive to match permalinks. */ + elseif ( '%category%' == $tag ) { + + /* Force override terms in this post type. */ + $this->args['post_taxonomy'][ $post->post_type ] = false; + + /* Get the post categories. */ + $terms = get_the_category( $post_id ); + + /* Check that categories were returned. */ + if ( $terms ) { + + /* Sort the terms by ID and get the first category. */ + usort( $terms, '_usort_terms_by_ID' ); + $term = get_term( $terms[0], 'category' ); + + /* If the category has a parent, add the hierarchy to the trail. */ + if ( 0 < $term->parent ) + $this->do_term_parents( $term->parent, 'category' ); + + /* Add the category archive link to the trail. */ + $this->items[] = '<a href="' . get_term_link( $term, 'category' ) . '" title="' . esc_attr( $term->name ) . '">' . $term->name . '</a>'; + } + } + } + } + } +} + +/** + * Extends the Breadcrumb_Trail class for bbPress. Only use this if bbPress is in use. This should + * serve as an example for other plugin developers to build custom breadcrumb items. + * + * @since 0.6.0 + * @access public + */ +class bbPress_Breadcrumb_Trail extends Breadcrumb_Trail { + + /** + * Runs through the various bbPress conditional tags to check the current page being viewed. Once + * a condition is met, add items to the $items array. + * + * @since 0.6.0 + * @access public + * @return void + */ + public function do_trail_items() { + + /* Add the network and site home links. */ + $this->do_network_home_link(); + $this->do_site_home_link(); + + /* Get the forum post type object. */ + $post_type_object = get_post_type_object( bbp_get_forum_post_type() ); + + /* If not viewing the forum root/archive page and a forum archive exists, add it. */ + if ( !empty( $post_type_object->has_archive ) && !bbp_is_forum_archive() ) + $this->items[] = '<a href="' . get_post_type_archive_link( bbp_get_forum_post_type() ) . '">' . bbp_get_forum_archive_title() . '</a>'; + + /* If viewing the forum root/archive. */ + if ( bbp_is_forum_archive() ) { + + if ( true === $this->args['show_title'] ) + $this->items[] = bbp_get_forum_archive_title(); + } + + /* If viewing the topics archive. */ + elseif ( bbp_is_topic_archive() ) { + + if ( true === $this->args['show_title'] ) + $this->items[] = bbp_get_topic_archive_title(); + } + + /* If viewing a topic tag archive. */ + elseif ( bbp_is_topic_tag() ) { + + if ( true === $this->args['show_title'] ) + $this->items[] = bbp_get_topic_tag_name(); + } + + /* If viewing a topic tag edit page. */ + elseif ( bbp_is_topic_tag_edit() ) { + $this->items[] = '<a href="' . bbp_get_topic_tag_link() . '">' . bbp_get_topic_tag_name() . '</a>'; + + if ( true === $this->args['show_title'] ) + $this->items[] = __( 'Edit', 'breadcrumb-trail' ); + } + + /* If viewing a "view" page. */ + elseif ( bbp_is_single_view() ) { + + if ( true === $this->args['show_title'] ) + $this->items[] = bbp_get_view_title(); + } + + /* If viewing a single topic page. */ + elseif ( bbp_is_single_topic() ) { + + /* Get the queried topic. */ + $topic_id = get_queried_object_id(); + + /* Get the parent items for the topic, which would be its forum (and possibly forum grandparents). */ + $this->do_post_parents( bbp_get_topic_forum_id( $topic_id ) ); + + /* If viewing a split, merge, or edit topic page, show the link back to the topic. Else, display topic title. */ + if ( bbp_is_topic_split() || bbp_is_topic_merge() || bbp_is_topic_edit() ) + $this->items[] = '<a href="' . bbp_get_topic_permalink( $topic_id ) . '">' . bbp_get_topic_title( $topic_id ) . '</a>'; + + elseif ( true === $this->args['show_title'] ) + $this->items[] = bbp_get_topic_title( $topic_id ); + + /* If viewing a topic split page. */ + if ( bbp_is_topic_split() && true === $this->args['show_title'] ) + $this->items[] = __( 'Split', 'breadcrumb-trail' ); + + /* If viewing a topic merge page. */ + elseif ( bbp_is_topic_merge() && true === $this->args['show_title'] ) + $this->items[] = __( 'Merge', 'breadcrumb-trail' ); + + /* If viewing a topic edit page. */ + elseif ( bbp_is_topic_edit() && true === $this->args['show_title'] ) + $this->items[] = __( 'Edit', 'breadcrumb-trail' ); + } + + /* If viewing a single reply page. */ + elseif ( bbp_is_single_reply() ) { + + /* Get the queried reply object ID. */ + $reply_id = get_queried_object_id(); + + /* Get the parent items for the reply, which should be its topic. */ + $this->do_post_parents( bbp_get_reply_topic_id( $reply_id ) ); + + /* If viewing a reply edit page, link back to the reply. Else, display the reply title. */ + if ( bbp_is_reply_edit() ) { + $this->items[] = '<a href="' . bbp_get_reply_url( $reply_id ) . '">' . bbp_get_reply_title( $reply_id ) . '</a>'; + + if ( true === $this->args['show_title'] ) + $this->items[] = __( 'Edit', 'breadcrumb-trail' ); + + } elseif ( true === $this->args['show_title'] ) { + $this->items[] = bbp_get_reply_title( $reply_id ); + } + + } + + /* If viewing a single forum. */ + elseif ( bbp_is_single_forum() ) { + + /* Get the queried forum ID and its parent forum ID. */ + $forum_id = get_queried_object_id(); + $forum_parent_id = bbp_get_forum_parent_id( $forum_id ); + + /* If the forum has a parent forum, get its parent(s). */ + if ( 0 !== $forum_parent_id) + $this->do_post_parents( $forum_parent_id ); + + /* Add the forum title to the end of the trail. */ + if ( true === $this->args['show_title'] ) + $this->items[] = bbp_get_forum_title( $forum_id ); + } + + /* If viewing a user page or user edit page. */ + elseif ( bbp_is_single_user() || bbp_is_single_user_edit() ) { + + if ( bbp_is_single_user_edit() ) { + $this->items[] = '<a href="' . bbp_get_user_profile_url() . '">' . bbp_get_displayed_user_field( 'display_name' ) . '</a>'; + + if ( true === $this->args['show_title'] ) + $this->items[] = __( 'Edit', 'breadcrumb-trail' ); + } elseif ( true === $this->args['show_title'] ) { + $this->items[] = bbp_get_displayed_user_field( 'display_name' ); + } + } + + /* Return the bbPress breadcrumb trail items. */ + $this->items = apply_filters( 'breadcrumb_trail_get_bbpress_items', $this->items, $this->args ); + } +} + +?> diff --git a/source/wp-content/themes/wpr-developer-2024/inc/cli-commands.php b/source/wp-content/themes/wpr-developer-2024/inc/cli-commands.php new file mode 100644 index 000000000..815de3585 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/inc/cli-commands.php @@ -0,0 +1,314 @@ +<?php + +/** + * Implements devhub commands. + */ +class DevHub_Command extends WP_CLI_Command { + + /** + * Parses WP code. + * + * The source code for the version of WordPress to be parsed needs to be + * obtained and unpackaged locally. It should not be code used in an + * active install. + * + * ## OPTIONS + * + * [--src_path=<src_path>] + * : The path to a copy of WordPress to be parsed. Should not be code used in + * an active install. If not defined, then the latest version of WordPress will + * be downloaded to a temp directory and parsed. + * + * [--user_id=<user_id>] + * : ID of user to attribute all parsed posts to. Default is 5911429, the ID for wordpressdotorg. + * + * [--wp_ver=<wp_ver>] + * : Version of WordPress to install. Only taken into account if --src_path is + * not defined. Default is the latest release (or whatever version is present + * in --src_path if that is defined). + * + * ## EXAMPLES + * + * # Parse latest WP. + * $ wp devhub parse + * + * # Parse specific copy of WP. + * $ wp devhub parse --src_path=/path/to/wordpress + * + * # Parse a particular version of WP. + * $ wp devhub parse --wp_ver=5.5.2 + * + * @when after_wp_load + */ + public function parse( $args, $assoc_args ) { + $path = $assoc_args['src_path'] ?? null; + $user_id = $assoc_args['user_id'] ?? 5911429; // 5911429 = ID for wordpressdotorg + $wp_ver = $assoc_args['wp_ver'] ?? null; + + // Verify path is a file or directory. + if ( $path ) { + if ( file_exists( $path ) ) { + WP_CLI::log( 'Parsing WordPress source from specified directory: ' . $path ); + } else { + WP_CLI::error( 'Provided path for WordPress source to parse does not exist.' ); + } + } + + // If no path provided, use a temporary path. + if ( ! $path ) { + $path = WP_CLI\Utils\get_temp_dir() . 'devhub_' . time(); + + // @todo Attempt to reuse an existing temp dir. + if ( mkdir( $path ) ) { + if ( $wp_ver ) { + WP_CLI::log( "Installing WordPress {$wp_ver} into temporary directory ({$path})..." ); + } else { + WP_CLI::log( "Installing latest WordPress into temporary directory ({$path})..." ); + } + $cmd = "core download --path={$path}"; + if ( $wp_ver ) { + $cmd .= " --version={$wp_ver}"; + } + // Install WP into the temp directory. + WP_CLI::runcommand( $cmd, [] ); + } else { + $path = null; + } + } + + if ( ! $path ) { + WP_CLI::error( 'Unable to create temporary directory for downloading WordPress. If retrying fails, consider obtaining the files manually and supplying that path via --src_path argument.' ); + } + + // Verify path is not a file. + if ( is_file( $path ) ) { + WP_CLI::error( 'Path provided for WordPress source to parse does not appear to be a directory.' ); + } + + // Verify path looks like WP. + if ( ! file_exists( $path . '/wp-includes/version.php' ) ) { + WP_CLI::error( 'Path provided for WordPress source to parse does not contain WordPress files.' ); + } + + // Get WP version of files to be parsed. + $version_file = file_get_contents( $path . '/wp-includes/version.php' ); + preg_match( '/\$wp_version = \'([^\']+)\'/', $version_file, $matches ); + $version = $matches[1]; + + // Get WP version last parsed (if any) and confirm if reparsing that version. + $last_parsed_wp_ver = get_option( 'wp_parser_imported_wp_version' ); + if ( $last_parsed_wp_ver && $last_parsed_wp_ver == $version ) { + $last_parsed_date = get_option( 'wp_parser_last_import' ); + WP_CLI::confirm( "\nLooks like WP $version was already parsed on " . date_i18n( 'Y-m-d H:i', $last_parsed_date ) . '. Proceed anyway?' ); + } + + // Determine importing user's ID. + $user = get_user_by( 'id', $user_id ); + if ( ! $user ) { + WP_CLI::error( 'Invalid user_id provided.' ); + } + WP_CLI::log( "Importing as user ID $user_id ({$user->user_nicename})." ); + + $plugins = [ + 'phpdoc-parser' => 'phpdoc-parser/plugin.php', + 'posts-to-posts' => 'posts-to-posts/posts-to-posts.php', + ]; + + // Install phpdoc-parser plugin dependencies if not installed. + // Do a fresh install each time to guarantee they're up to date. + $plugin_dir = WP_PLUGIN_DIR . '/phpdoc-parser/'; + WP_CLI::log( 'About to install plugin dependencies for phpdoc-parser...' ); + + // Install Composer if necessary. + if ( ! file_exists( $plugin_dir . 'composer.phar' ) ) { + WP_CLI::log( 'About to install Composer...' ); + if ( ! copy( 'https://getcomposer.org/installer', $plugin_dir . 'composer-setup.php' ) ) { + WP_CLI::error( 'Unable to obtain the Composer setup script while attempting to install dependencies for phpdoc-parser.' ); + } + WP_CLI::launch( "cd {$plugin_dir} && COMPOSER_HOME={$plugin_dir} /usr/local/bin/php composer-setup.php" ); + unlink( $plugin_dir . 'composer-setup.php' ); + } + + // Install dependencies + WP_CLI::launch( "cd {$plugin_dir} && COMPOSER_HOME={$plugin_dir} /usr/local/bin/php composer.phar install", false, true ); + + if ( ! file_exists( "$plugin_dir/vendor/autoload.php" ) ) { + WP_CLI::error( 'Failed to install dependencies.' ); + } + + // Confirm the parsing. + WP_CLI::confirm( "\nAre you sure you want to parse the source code for WP {$version} (and that you've run a backup of the existing data)?" ); + + /** + * Fires just before actual parsing process takes place. + * + * @param string $path Path to the directory containing the WP files to parse. + * @param string $version Version of WP being parsed. + * @param WP_User $user User to be treated as post author for everything created. + */ + do_action( 'wporg_devhub_cli_before_parsing', $path, $version, $user ); + + // 1. Deactivate posts-to-posts plugin. + if ( is_plugin_active( $plugins['posts-to-posts'] ) ) { + WP_CLI::log( 'Deactivating posts-to-posts plugin...' ); + WP_CLI::runcommand( 'plugin deactivate ' . $plugins['posts-to-posts'] ); + } else { + WP_CLI::log( 'Warning: plugin posts-to-posts already deactivated.' ); + } + + // 2. Activate phpdoc-parser plugin. + if ( is_plugin_active( $plugins['phpdoc-parser'] ) ) { + WP_CLI::log( 'Warning: plugin phpdoc-parser already activated.' ); + } else { + WP_CLI::log( 'Activating phpdoc-parser plugin...' ); + WP_CLI::runcommand( 'plugin activate ' . $plugins['phpdoc-parser'] ); + } + + // 3. Run the parser. + // If running locally, run a quick parse which skips DB replication-lag sleep()'s + $quick = in_array( wp_get_environment_type(), array( 'local', 'development' ) ) ? '--quick' : ''; + WP_CLI::log( "\nRunning the parser (this will take awhile)..." ); + WP_CLI::runcommand( "parser create {$path} --user={$user_id} {$quick}" ); + + // 4. Deactivate phpdoc-parser plugin. + WP_CLI::log( "\nDeactivating phpdoc-parser plugin..." ); + WP_CLI::runcommand( 'plugin deactivate ' . $plugins['phpdoc-parser'] ); + + // 5. Activate posts-to-posts plugin. + WP_CLI::log( 'Activating posts-to-posts plugin...' ); + WP_CLI::runcommand( 'plugin activate ' . $plugins['posts-to-posts'] ); + + // 6. Pre-cache source code. + WP_CLI::runcommand( 'devhub pre-cache-source' ); + + // 7. Clean up after itself. + WP_CLI::runcommand( 'devhub clean' ); + + // Done. + WP_CLI::success( "Parsing of WP $version is complete." ); + + /** + * Fires after parsing process completes. + * + * @param string $path Path to the directory containing the WP files to parse. + * @param string $version Versin of WP being parsed. + * @param WP_User $user User to be treated as post author for everything created. + */ + do_action( 'wporg_devhub_cli_after_parsing', $path, $version, $user ); + } + + /** + * Pre-caches source for parsed post types that support showing source code. + * + * By default, source code shown for post types that have source code is read + * from the parsed file on page load if not already cached. This pre-caches all + * the source code and updates source code that has already been cached. + * + * ## EXAMPLES + * + * wp devhub pre-cache-source + * + * @when after_wp_load + * @subcommand pre-cache-source + */ + public function pre_cache_source() { + WP_CLI::log( "\nPre-caching source code..." ); + + $success = DevHub_Parser::cache_source_code(); + + if ( $success ) { + WP_CLI::success( 'Pre-caching of source code is complete.' ); + } else { + WP_CLI::error( 'Unable to pre-cache source code.' ); + } + } + + /** + * Cleans up temporary files created for, and used only, in parsing. + * + * Cleans: + * - Temp directory created to install the version of WP to parse + * - Removes the phpdoc-parser plugin (only if it was fully installed and + * configured as part of the parsing process) + * + * Note: This automatically gets called at the end of a successful `parse` + * invocation, so it would only need to be directly called if parsing + * didn't complete successfully. + * + * ## EXAMPLES + * + * wp devhub clean + * + * @when after_wp_load + */ + public function clean() { + WP_CLI::log( "\nCleaning up after the parser..." ); + + // Dependencies aren't intended to be in production/staging environments, and could contain + // vulnerabilities if left. Remove also ensures that they're up to date when the job is run again. + $plugin_dir = WP_PLUGIN_DIR . '/phpdoc-parser/'; + $cmd = "rm -rf {$plugin_dir}/vendor"; + WP_CLI::confirm( "About to delete vendor directory. Does this look proper? `$cmd`" ); + WP_CLI::launch( $cmd, false, true ); + + $tmp_dirs = glob( WP_CLI\Utils\get_temp_dir() . 'devhub_*' ); + + if ( count( $tmp_dirs ) > 1 ) { + WP_CLI::log( "\nMultiple temporary directories were detected. This can be the case if earlier parsings were aborted or cancelled without completing." ); + } + + foreach ( $tmp_dirs as $tmp_dir ) { + $cmd = "rm -rf {$tmp_dir}"; + WP_CLI::confirm( "About to delete temporary directory. Does this look proper? `$cmd`" ); + WP_CLI::launch( $cmd, false, true ); + } + + WP_CLI::success( "Clean-up is complete.\n" ); + } + + /** + * Returns information pertaining to the last parsing. + * + * ## OPTIONS + * + * <key> + * : The information from the last parsing to obtain. One of: 'date', 'import-dir', 'version'. + * + * ## EXAMPLES + * + * wp devhub last-parsed version + * + * @when after_wp_load + * @subcommand last-parsed + */ + public function last_parsed( $args, $assoc_args ) { + list( $key ) = $args; + + $valid_values = array( + 'date' => 'wp_parser_last_import', + 'import-dir' => 'wp_parser_root_import_dir', + 'version' => 'wp_parser_imported_wp_version', + ); + + if ( empty( $valid_values[ $key ] ) ) { + WP_CLI::error( 'Invalid value provided. Must be one of: ' . implode( ', ', array_keys( $valid_values ) ) ); + } + + $option = $valid_values[ $key ]; + + $value = get_option( $option ); + + if ( 'date' === $key ) { + $value = date_i18n( 'Y-m-d H:i', $value ); + } + + if ( ! $value ) { + WP_CLI::error( 'No value from previous parsing of WordPress source was detected.' ); + } else { + WP_CLI::log( $value ); + } + } + +} + +WP_CLI::add_command( 'devhub', 'DevHub_Command' ); diff --git a/source/wp-content/themes/wpr-developer-2024/inc/cli.php b/source/wp-content/themes/wpr-developer-2024/inc/cli.php new file mode 100644 index 000000000..4cc78b759 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/inc/cli.php @@ -0,0 +1,313 @@ +<?php + +class DevHub_CLI { + + private static $commands_manifest = 'https://raw.githubusercontent.com/wp-cli/handbook/main/bin/commands-manifest.json'; + private static $meta_key = 'wporg_cli_markdown_source'; + private static $supported_post_types = array( 'command' ); + private static $posts_per_page = -1; + + public static function init() { + add_action( 'init', array( __CLASS__, 'action_init_register_cron_jobs' ) ); + add_action( 'init', array( __CLASS__, 'action_init_register_post_types' ) ); + add_filter( 'jetpack_sitemap_post_types', array( __CLASS__, 'filter_jetpack_sitemap_post_types' ) ); + add_action( 'pre_get_posts', array( __CLASS__, 'action_pre_get_posts' ) ); + add_action( 'devhub_cli_manifest_import', array( __CLASS__, 'action_devhub_cli_manifest_import' ) ); + add_action( 'devhub_cli_markdown_import', array( __CLASS__, 'action_devhub_cli_markdown_import' ) ); + } + + public static function action_init_register_cron_jobs() { + if ( ! wp_next_scheduled( 'devhub_cli_manifest_import' ) ) { + wp_schedule_event( time(), 'twicedaily', 'devhub_cli_manifest_import' ); + } + if ( ! wp_next_scheduled( 'devhub_cli_markdown_import' ) ) { + wp_schedule_event( time(), 'twicedaily', 'devhub_cli_markdown_import' ); + } + } + + public static function action_init_register_post_types() { + $supports = array( + 'comments', + 'custom-fields', + 'editor', + 'excerpt', + 'revisions', + 'title', + + // Needed for manual inspection/modification of the post's parent. + 'page-attributes', + ); + register_post_type( 'command', array( + 'has_archive' => 'cli/commands', + 'label' => __( 'WP-CLI Commands', 'wporg' ), + 'labels' => array( + 'name' => __( 'WP-CLI Commands', 'wporg' ), + 'singular_name' => __( 'Command', 'wporg' ), + 'all_items' => __( 'Commands', 'wporg' ), + 'new_item' => __( 'New Command', 'wporg' ), + 'add_new' => __( 'Add New', 'wporg' ), + 'add_new_item' => __( 'Add New Command', 'wporg' ), + 'edit_item' => __( 'Edit Command', 'wporg' ), + 'view_item' => __( 'View Command', 'wporg' ), + 'search_items' => __( 'Search Commands', 'wporg' ), + 'not_found' => __( 'No Commands found', 'wporg' ), + 'not_found_in_trash' => __( 'No Commands found in trash', 'wporg' ), + 'parent_item_colon' => __( 'Parent Command', 'wporg' ), + 'menu_name' => __( 'Commands', 'wporg' ), + ), + 'menu_icon' => 'dashicons-arrow-right-alt2', + 'public' => true, + 'hierarchical'=> true, + 'rewrite' => array( + 'feeds' => false, + 'slug' => 'cli/commands', + 'with_front' => false, + ), + 'supports' => $supports, + ) ); + } + + public static function filter_jetpack_sitemap_post_types( $post_types ) { + $post_types[] = 'command'; + + return $post_types; + } + + public static function action_pre_get_posts( $query ) { + if ( ! is_admin() && $query->is_main_query() && $query->is_post_type_archive( 'command' ) ) { + $query->set( 'post_parent', 0 ); + $query->set( 'orderby', 'title' ); + $query->set( 'order', 'ASC' ); + $query->set( 'posts_per_page', 250 ); + } + } + + public static function action_devhub_cli_manifest_import() { + $response = wp_remote_get( self::$commands_manifest ); + if ( is_wp_error( $response ) ) { + return $response; + } elseif ( 200 !== wp_remote_retrieve_response_code( $response ) ) { + return new WP_Error( 'invalid-http-code', 'Markdown source returned non-200 http code.' ); + } + $manifest = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( ! $manifest ) { + return new WP_Error( 'invalid-manifest', 'Manifest did not unfurl properly.' );; + } + // Fetch all handbook posts for comparison + $q = new WP_Query( array( + 'post_type' => self::$supported_post_types, + 'post_status' => 'publish', + 'posts_per_page' => self::$posts_per_page, + ) ); + $existing = array(); + foreach( $q->posts as $post ) { + $cmd_path = self::get_cmd_path( $post->ID ); + $existing[ $cmd_path ] = array( + 'post_id' => $post->ID, + 'cmd_path' => $cmd_path, + ); + } + $created = 0; + foreach( $manifest as $doc ) { + // Already exists + $existing_doc = wp_filter_object_list( $existing, array( 'cmd_path' => $doc['cmd_path'] ) ); + if ( $existing_doc ) { + $existing_doc = array_shift( $existing_doc ); + if ( ! empty( $doc['repo_url'] ) ) { + update_post_meta( $existing_doc['post_id'], 'repo_url', esc_url_raw( $doc['repo_url'] ) ); + } + continue; + } + if ( self::process_manifest_doc( $doc, $existing, $manifest ) ) { + $created++; + } + } + if ( class_exists( 'WP_CLI' ) ) { + \WP_CLI::success( "Successfully created {$created} handbook pages." ); + } + } + + private static function process_manifest_doc( $doc, &$existing, $manifest ) { + $post_parent = null; + if ( ! empty( $doc['parent'] ) ) { + // Find the parent in the existing set + $parents = wp_filter_object_list( $existing, array( 'cmd_path' => $doc['parent'] ) ); + if ( empty( $parents ) ) { + if ( ! self::process_manifest_doc( $manifest[ $doc['parent'] ], $existing, $manifest ) ) { + return; + } + $parents = wp_filter_object_list( $existing, array( 'cmd_path' => $doc['parent'] ) ); + } + if ( ! empty( $parents ) ) { + $parent = array_shift( $parents ); + $post_parent = $parent['post_id']; + } + } + $post = self::create_post_from_manifest_doc( $doc, $post_parent ); + if ( $post ) { + $cmd_path = self::get_cmd_path( $post->ID ); + if ( ! empty( $doc['repo_url'] ) ) { + update_post_meta( $post->ID, 'repo_url', esc_url_raw( $doc['repo_url'] ) ); + } + $existing[ $cmd_path ] = array( + 'post_id' => $post->ID, + 'cmd_path' => $cmd_path, + ); + return true; + } + return false; + } + + public static function action_devhub_cli_markdown_import() { + $q = new WP_Query( array( + 'post_type' => self::$supported_post_types, + 'post_status' => 'publish', + 'fields' => 'ids', + 'posts_per_page' => self::$posts_per_page, + ) ); + $ids = $q->posts; + $success = 0; + foreach( $ids as $id ) { + $ret = self::update_post_from_markdown_source( $id ); + if ( class_exists( 'WP_CLI' ) ) { + if ( is_wp_error( $ret ) ) { + \WP_CLI::warning( $ret->get_error_message() ); + } else { + \WP_CLI::log( "Updated {$id} from markdown source" ); + $success++; + } + } + } + if ( class_exists( 'WP_CLI' ) ) { + $total = count( $ids ); + \WP_CLI::success( "Successfully updated {$success} of {$total} CLI command pages." ); + } + } + + /** + * Create a new handbook page from the manifest document + */ + private static function create_post_from_manifest_doc( $doc, $post_parent = null ) { + $post_data = array( + 'post_type' => 'command', + 'post_status' => 'publish', + 'post_parent' => $post_parent, + 'post_title' => sanitize_text_field( wp_slash( $doc['title'] ) ), + 'post_name' => sanitize_title_with_dashes( $doc['slug'] ), + ); + $post_id = wp_insert_post( $post_data ); + if ( ! $post_id ) { + return false; + } + if ( class_exists( 'WP_CLI' ) ) { + \WP_CLI::log( "Created post {$post_id} for {$doc['title']}." ); + } + update_post_meta( $post_id, self::$meta_key, esc_url_raw( $doc['markdown_source'] ) ); + return get_post( $post_id ); + } + + /** + * Update a post from its Markdown source + */ + private static function update_post_from_markdown_source( $post_id ) { + $markdown_source = self::get_markdown_source( $post_id ); + if ( is_wp_error( $markdown_source ) ) { + return $markdown_source; + } + + // Load Jetpack Markdown if it's not already loaded, for transforming markdown to HTML. + if ( ! class_exists( 'WPCom_GHF_Markdown_Parser' ) ) { + if ( defined( 'JETPACK__PLUGIN_DIR' ) ) { + require_once JETPACK__PLUGIN_DIR . '/_inc/lib/markdown.php'; + } else { + return new WP_Error( 'missing-jetpack-markdown', 'Jetpack Markdown is missing on system.' ); + } + } + + // Transform GitHub repo HTML pages into their raw equivalents + $markdown_source = preg_replace( '#https?://github\.com/([^/]+/[^/]+)/blob/(.+)#', 'https://raw.githubusercontent.com/$1/$2', $markdown_source ); + $markdown_source = add_query_arg( 'v', time(), $markdown_source ); + $response = wp_remote_get( $markdown_source ); + if ( is_wp_error( $response ) ) { + return $response; + } elseif ( 200 !== wp_remote_retrieve_response_code( $response ) ) { + return new WP_Error( 'invalid-http-code', 'Markdown source returned non-200 http code.' ); + } + + $markdown = wp_remote_retrieve_body( $response ); + // Strip YAML doc from the header + $markdown = preg_replace( '#^---(.+)---#Us', '', $markdown ); + + $title = null; + if ( preg_match( '/^#\s(.+)/', $markdown, $matches ) ) { + $title = $matches[1]; + $markdown = preg_replace( '/^#\swp\s(.+)/', '', $markdown ); + } + $markdown = trim( $markdown ); + + // Steal the first sentence as the excerpt + $excerpt = ''; + if ( preg_match( '/^(.+)/', $markdown, $matches ) ) { + $excerpt = $matches[1]; + $markdown = preg_replace( '/^(.+)/', '', $markdown ); + } + + // Transform to HTML and save the post + $parser = new \WPCom_GHF_Markdown_Parser; + $html = $parser->transform( $markdown ); + $post_data = array( + 'ID' => $post_id, + 'post_content' => wp_filter_post_kses( wp_slash( $html ) ), + 'post_excerpt' => sanitize_text_field( wp_slash( $excerpt ) ), + ); + if ( ! is_null( $title ) ) { + $post_data['post_title'] = sanitize_text_field( wp_slash( $title ) ); + } + wp_update_post( $post_data ); + return true; + } + + /** + * Retrieve the markdown source URL for a given post. + */ + public static function get_markdown_source( $post_id ) { + $markdown_source = get_post_meta( $post_id, self::$meta_key, true ); + if ( ! $markdown_source ) { + return new WP_Error( 'missing-markdown-source', 'Markdown source is missing for post.' ); + } + + return $markdown_source; + } + + /** + * Gets a normalized version of the command path from the post permalink. + * + * This normalization is needed because CPTs share the slug namespace with + * other CPTs, causing unexpected conflicts that result in changed URLs like + * "command-2". + * + * @see https://github.com/wp-cli/handbook/issues/202 + * + * @param int $post_id Post ID to get the normalized path for. + * + * @return string Normalized command path. + */ + private static function get_cmd_path( $post_id ) { + $cmd_path = rtrim( str_replace( home_url( 'cli/commands/' ), '', get_permalink( $post_id ) ), '/' ); + $parts = explode( '/', $cmd_path ); + $cleaned_parts = array(); + foreach ( $parts as $part ) { + $matches = null; + $result = preg_match( '/^(?<cmd>.*)(?<suffix>-[0-9]+)$/', $part, $matches ); + if ( 1 === $result ) { + $part = $matches['cmd']; + } + $cleaned_parts[] = $part; + } + return implode( '/', $cleaned_parts ); + } + +} + +DevHub_CLI::init(); + diff --git a/source/wp-content/themes/wpr-developer-2024/inc/dashicons.php b/source/wp-content/themes/wpr-developer-2024/inc/dashicons.php new file mode 100644 index 000000000..f2b5310ab --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/inc/dashicons.php @@ -0,0 +1,1873 @@ +<?php +/** + * Class for Dashicons data and related functionality. + * + * @package wporg-developer + */ + +/** + * Class to handle Dashicons. + */ +class Devhub_Dashicons { + + /** + * Returns dashicons. + */ + public static function get_dashicons() { + return [ + 'admin menu' => [ + 'label' => __( 'Admin Menu', 'wporg' ), + 'icons' => [ + 'dashicons-menu' => [ + 'code' => 'f333', + 'label' => 'menu', + 'keywords' => 'menu admin', + ], + 'dashicons-menu-alt' => [ + 'code' => 'f228', + 'label' => 'menu (alt)', + 'keywords' => 'menu alt admin', + ], + 'dashicons-menu-alt2' => [ + 'code' => 'f329', + 'label' => 'menu (alt2)', + 'keywords' => 'menu alt admin', + ], + 'dashicons-menu-alt3' => [ + 'code' => 'f349', + 'label' => 'menu (alt3)', + 'keywords' => 'menu alt admin', + ], + 'dashicons-admin-site' => [ + 'code' => 'f319', + 'label' => 'site', + 'keywords' => 'site admin', + ], + 'dashicons-admin-site-alt' => [ + 'code' => 'f11d', + 'label' => 'site (alt)', + 'keywords' => 'site alt admin', + ], + 'dashicons-admin-site-alt2' => [ + 'code' => 'f11e', + 'label' => 'site (alt2)', + 'keywords' => 'site alt admin', + ], + 'dashicons-admin-site-alt3' => [ + 'code' => 'f11f', + 'label' => 'site (alt3)', + 'keywords' => 'site alt admin', + ], + 'dashicons-dashboard' => [ + 'code' => 'f226', + 'label' => 'dashboard', + 'keywords' => 'dashboard admin', + ], + 'dashicons-admin-post' => [ + 'code' => 'f109', + 'label' => 'post', + 'keywords' => 'post admin', + ], + 'dashicons-admin-media' => [ + 'code' => 'f104', + 'label' => 'media', + 'keywords' => 'media admin', + ], + 'dashicons-admin-links' => [ + 'code' => 'f103', + 'label' => 'links', + 'keywords' => 'links admin', + ], + 'dashicons-admin-page' => [ + 'code' => 'f105', + 'label' => 'page', + 'keywords' => 'page admin', + ], + 'dashicons-admin-comments' => [ + 'code' => 'f101', + 'label' => 'comments', + 'keywords' => 'comments admin', + ], + 'dashicons-admin-appearance' => [ + 'code' => 'f100', + 'label' => 'appearance', + 'keywords' => 'appearance admin', + ], + 'dashicons-admin-plugins' => [ + 'code' => 'f106', + 'label' => 'plugins', + 'keywords' => 'plugins admin', + ], + 'dashicons-plugins-checked' => [ + 'code' => 'f485', + 'label' => 'plugins checked', + 'keywords' => 'plugins checked admin', + ], + 'dashicons-admin-users' => [ + 'code' => 'f110', + 'label' => 'users', + 'keywords' => 'users admin', + ], + 'dashicons-admin-tools' => [ + 'code' => 'f107', + 'label' => 'tools', + 'keywords' => 'tools admin', + ], + 'dashicons-admin-settings' => [ + 'code' => 'f108', + 'label' => 'settings', + 'keywords' => 'settings admin', + ], + 'dashicons-admin-network' => [ + 'code' => 'f112', + 'label' => 'network', + 'keywords' => 'network admin', + ], + 'dashicons-admin-home' => [ + 'code' => 'f102', + 'label' => 'home', + 'keywords' => 'home admin', + ], + 'dashicons-admin-generic' => [ + 'code' => 'f111', + 'label' => 'generic', + 'keywords' => 'generic admin', + ], + 'dashicons-admin-collapse' => [ + 'code' => 'f148', + 'label' => 'collapse', + 'keywords' => 'collapse admin', + ], + 'dashicons-filter' => [ + 'code' => 'f536', + 'label' => 'filter', + 'keywords' => 'filter admin', + ], + 'dashicons-admin-customizer' => [ + 'code' => 'f540', + 'label' => 'customizer', + 'keywords' => 'customizer admin', + ], + 'dashicons-admin-multisite' => [ + 'code' => 'f541', + 'label' => 'multisite', + 'keywords' => 'multisite admin', + ], + ], + ], + + 'welcome screen' => [ + 'label' => __( 'Welcome Screen', 'wporg' ), + 'icons' => [ + 'dashicons-welcome-write-blog' => [ + 'code' => 'f119', + 'label' => 'write blog', + 'keywords' => 'write blog welcome', + ], + /* Duplicate + 'dashicons-welcome-edit-page' => [ + 'code' => 'f119', + 'lable' => 'edit page', + 'keywords' => '', + ], + */ + 'dashicons-welcome-add-page' => [ + 'code' => 'f133', + 'label' => 'add page', + 'keywords' => 'add page welcome', + ], + 'dashicons-welcome-view-site' => [ + 'code' => 'f115', + 'label' => 'view site', + 'keywords' => 'view site welcome', + ], + 'dashicons-welcome-widgets-menus' => [ + 'code' => 'f116', + 'label' => 'widgets menus', + 'keywords' => 'widgets menus welcome', + ], + 'dashicons-welcome-comments' => [ + 'code' => 'f117', + 'label' => 'comments', + 'keywords' => 'comments welcome', + ], + 'dashicons-welcome-learn-more' => [ + 'code' => 'f118', + 'label' => 'learn more', + 'keywords' => 'learn more welcome', + ], + ], + ], + + 'post formats' => [ + 'label' => __( 'Post Formats', 'wporg' ), + 'icons' => [ + /* Duplicate + 'dashicons-format-standard' => [ + 'code' => 'f109', + 'keywords' => '', + ], + */ + 'dashicons-format-aside' => [ + 'code' => 'f123', + 'label' => 'aside', + 'keywords' => 'aside format', + ], + 'dashicons-format-image' => [ + 'code' => 'f128', + 'label' => 'image', + 'keywords' => 'image format', + ], + 'dashicons-format-gallery' => [ + 'code' => 'f161', + 'label' => 'gallery', + 'keywords' => 'gallery format', + ], + 'dashicons-format-video' => [ + 'code' => 'f126', + 'label' => 'video', + 'keywords' => 'video format', + ], + 'dashicons-format-status' => [ + 'code' => 'f130', + 'label' => 'status', + 'keywords' => 'status format', + ], + 'dashicons-format-quote' => [ + 'code' => 'f122', + 'label' => 'quote', + 'keywords' => 'quote format', + ], + /* Duplicate + 'dashicons-format-links' => [ + 'code' => 'f103', + 'label' => 'format links', + 'keywords' => '', + ], + */ + 'dashicons-format-chat' => [ + 'code' => 'f125', + 'label' => 'chat', + 'keywords' => 'chat format', + ], + 'dashicons-format-audio' => [ + 'code' => 'f127', + 'label' => 'audio', + 'keywords' => 'audio format', + ], + 'dashicons-camera' => [ + 'code' => 'f306', + 'label' => 'camera', + 'keywords' => 'camera format', + ], + 'dashicons-camera-alt' => [ + 'code' => 'f129', + 'label' => 'camera (alt)', + 'keywords' => 'camera alt format', + ], + 'dashicons-images-alt' => [ + 'code' => 'f232', + 'label' => 'images (alt)', + 'keywords' => 'images alt format', + ], + 'dashicons-images-alt2' => [ + 'code' => 'f233', + 'label' => 'images (alt2)', + 'keywords' => 'images alt format', + ], + 'dashicons-video-alt' => [ + 'code' => 'f234', + 'label' => 'video (alt)', + 'keywords' => 'video alt format', + ], + 'dashicons-video-alt2' => [ + 'code' => 'f235', + 'label' => 'video (alt2)', + 'keywords' => 'video alt format', + ], + 'dashicons-video-alt3' => [ + 'code' => 'f236', + 'label' => 'video (alt3)', + 'keywords' => 'video alt format', + ], + ], + ], + + 'media' => [ + 'label' => __( 'Media', 'wporg' ), + 'icons' => [ + 'dashicons-media-archive' => [ + 'code' => 'f501', + 'label' => 'archive', + 'keywords' => 'archive media', + ], + 'dashicons-media-audio' => [ + 'code' => 'f500', + 'label' => 'audio', + 'keywords' => 'audio media', + ], + 'dashicons-media-code' => [ + 'code' => 'f499', + 'label' => 'code', + 'keywords' => 'code media', + ], + 'dashicons-media-default' => [ + 'code' => 'f498', + 'label' => 'default', + 'keywords' => 'default media', + ], + 'dashicons-media-document' => [ + 'code' => 'f497', + 'label' => 'document', + 'keywords' => 'document media', + ], + 'dashicons-media-interactive' => [ + 'code' => 'f496', + 'label' => 'interactive', + 'keywords' => 'interactive media', + ], + 'dashicons-media-spreadsheet' => [ + 'code' => 'f495', + 'label' => 'spreadsheet', + 'keywords' => 'spreadsheet media', + ], + 'dashicons-media-text' => [ + 'code' => 'f491', + 'label' => 'text', + 'keywords' => 'text media', + ], + 'dashicons-media-video' => [ + 'code' => 'f490', + 'label' => 'video', + 'keywords' => 'video media', + ], + 'dashicons-playlist-audio' => [ + 'code' => 'f492', + 'label' => 'playlist audio', + 'keywords' => 'audio playlist media', + ], + 'dashicons-playlist-video' => [ + 'code' => 'f493', + 'label' => 'playlist video', + 'keywords' => 'video playlist media', + ], + 'dashicons-controls-play' => [ + 'code' => 'f522', + 'label' => 'play', + 'keywords' => 'play player controls media', + ], + 'dashicons-controls-pause' => [ + 'code' => 'f523', + 'label' => 'pause', + 'keywords' => 'player pause controls media', + ], + 'dashicons-controls-forward' => [ + 'code' => 'f519', + 'label' => 'forward', + 'keywords' => 'player forward controls media', + ], + 'dashicons-controls-skipforward' => [ + 'code' => 'f517', + 'label' => 'skip forward', + 'keywords' => 'player skip forward controls media', + ], + 'dashicons-controls-back' => [ + 'code' => 'f518', + 'label' => 'back', + 'keywords' => 'player back controls media', + ], + 'dashicons-controls-skipback' => [ + 'code' => 'f516', + 'label' => 'skip back', + 'keywords' => 'player skip back controls media', + ], + 'dashicons-controls-repeat' => [ + 'code' => 'f515', + 'label' => 'repeat', + 'keywords' => 'player repeat controls media', + ], + 'dashicons-controls-volumeon' => [ + 'code' => 'f521', + 'label' => 'volume on', + 'keywords' => 'player volume on controls media', + ], + 'dashicons-controls-volumeoff' => [ + 'code' => 'f520', + 'label' => 'volume off', + 'keywords' => 'player volume off controls media', + ], + ], + ], + + 'image editing' => [ + 'label' => __( 'Image Editing', 'wporg' ), + 'icons' => [ + 'dashicons-image-crop' => [ + 'code' => 'f165', + 'label' => 'crop', + 'keywords' => 'crop image', + ], + 'dashicons-image-rotate' => [ + 'code' => 'f531', + 'label' => 'rotate', + 'keywords' => 'rotate image', + ], + 'dashicons-image-rotate-left' => [ + 'code' => 'f166', + 'label' => 'rotate left', + 'keywords' => 'rotate left image', + ], + 'dashicons-image-rotate-right' => [ + 'code' => 'f167', + 'label' => 'rotate right', + 'keywords' => 'rotate right image', + ], + 'dashicons-image-flip-vertical' => [ + 'code' => 'f168', + 'label' => 'flip vertical', + 'keywords' => 'flip vertical image', + ], + 'dashicons-image-flip-horizontal' => [ + 'code' => 'f169', + 'label' => 'flip horizontal', + 'keywords' => 'flip horizontal image', + ], + 'dashicons-image-filter' => [ + 'code' => 'f533', + 'label' => 'filter', + 'keywords' => 'filter image', + ], + 'dashicons-undo' => [ + 'code' => 'f171', + 'label' => 'undo', + 'keywords' => 'undo image', + ], + 'dashicons-redo' => [ + 'code' => 'f172', + 'label' => 'redo', + 'keywords' => 'redo image', + ], + ], + ], + + 'databases' => [ + 'label' => __( 'Databases', 'wporg' ), + 'icons' => [ + 'dashicons-database-add' => [ + 'code' => 'f170', + 'label' => 'database add', + 'keywords' => 'database add', + ], + 'dashicons-database' => [ + 'code' => 'f17e', + 'label' => 'database', + 'keywords' => 'database', + ], + 'dashicons-database-export' => [ + 'code' => 'f17a', + 'label' => 'database export', + 'keywords' => 'database export', + ], + 'dashicons-database-import' => [ + 'code' => 'f17b', + 'label' => 'database import', + 'keywords' => 'database import', + ], + 'dashicons-database-remove' => [ + 'code' => 'f17c', + 'label' => 'database remove', + 'keywords' => 'database remove', + ], + 'dashicons-database-view' => [ + 'code' => 'f17d', + 'label' => 'database view', + 'keywords' => 'database view', + ], + ], + ], + + 'block editor' => [ + 'label' => __( 'Block Editor', 'wporg' ), + 'icons' => [ + 'dashicons-align-full-width' => [ + 'code' => 'f114', + 'label' => 'align full width', + 'keywords' => 'align full width block', + ], + 'dashicons-align-pull-left' => [ + 'code' => 'f10a', + 'label' => 'align pull left', + 'keywords' => 'align pull left block', + ], + 'dashicons-align-pull-right' => [ + 'code' => 'f10b', + 'label' => 'align pull right', + 'keywords' => 'align pull right block', + ], + 'dashicons-align-wide' => [ + 'code' => 'f11b', + 'label' => 'align wide', + 'keywords' => 'align wide block', + ], + 'dashicons-block-default' => [ + 'code' => 'f12b', + 'label' => 'block default', + 'keywords' => 'block default', + ], + 'dashicons-button' => [ + 'code' => 'f11a', + 'label' => 'button', + 'keywords' => 'button block', + ], + 'dashicons-cloud-saved' => [ + 'code' => 'f137', + 'label' => 'cloud saved', + 'keywords' => 'cloud saved block', + ], + 'dashicons-cloud-upload' => [ + 'code' => 'f13b', + 'label' => 'cloud upload', + 'keywords' => 'cloud upload block', + ], + 'dashicons-columns' => [ + 'code' => 'f13c', + 'label' => 'columns', + 'keywords' => 'columns block', + ], + 'dashicons-cover-image' => [ + 'code' => 'f13d', + 'label' => 'cover image', + 'keywords' => 'cover image block', + ], + 'dashicons-ellipsis' => [ + 'code' => 'f11c', + 'label' => 'ellipsis', + 'keywords' => 'ellipsis block', + ], + 'dashicons-embed-audio' => [ + 'code' => 'f13e', + 'label' => 'embed audio', + 'keywords' => 'embed audio block', + ], + 'dashicons-embed-generic' => [ + 'code' => 'f13f', + 'label' => 'embed generic', + 'keywords' => 'embed generic block', + ], + 'dashicons-embed-photo' => [ + 'code' => 'f144', + 'label' => 'embed photo', + 'keywords' => 'embed photo block', + ], + 'dashicons-embed-post' => [ + 'code' => 'f146', + 'label' => 'embed post', + 'keywords' => 'embed post block', + ], + 'dashicons-embed-video' => [ + 'code' => 'f149', + 'label' => 'embed video', + 'keywords' => 'embed video block', + ], + 'dashicons-exit' => [ + 'code' => 'f14a', + 'label' => 'exit', + 'keywords' => 'exit block', + ], + 'dashicons-heading' => [ + 'code' => 'f10e', + 'label' => 'heading', + 'keywords' => 'heading block', + ], + 'dashicons-html' => [ + 'code' => 'f14b', + 'label' => 'HTML', + 'keywords' => 'html block', + ], + 'dashicons-info-outline' => [ + 'code' => 'f14c', + 'label' => 'info outline', + 'keywords' => 'info outline block', + ], + 'dashicons-insert' => [ + 'code' => 'f10f', + 'label' => 'insert', + 'keywords' => 'insert block', + ], + 'dashicons-insert-after' => [ + 'code' => 'f14d', + 'label' => 'insert after', + 'keywords' => 'insert after block', + ], + 'dashicons-insert-before' => [ + 'code' => 'f14e', + 'label' => 'insert before', + 'keywords' => 'insert before block', + ], + 'dashicons-remove' => [ + 'code' => 'f14f', + 'label' => 'remove', + 'keywords' => 'remove block', + ], + 'dashicons-saved' => [ + 'code' => 'f15e', + 'label' => 'saved', + 'keywords' => 'saved block', + ], + 'dashicons-shortcode' => [ + 'code' => 'f150', + 'label' => 'shortcode', + 'keywords' => 'shortcode block', + ], + 'dashicons-table-col-after' => [ + 'code' => 'f151', + 'label' => 'table col after', + 'keywords' => 'table col after block', + ], + 'dashicons-table-col-before' => [ + 'code' => 'f152', + 'label' => 'table col before', + 'keywords' => 'table col before block', + ], + 'dashicons-table-col-delete' => [ + 'code' => 'f15a', + 'label' => 'table col delete', + 'keywords' => 'table col delete block', + ], + 'dashicons-table-row-after' => [ + 'code' => 'f15b', + 'label' => 'table row after', + 'keywords' => 'table row after block', + ], + 'dashicons-table-row-before' => [ + 'code' => 'f15c', + 'label' => 'table row before', + 'keywords' => 'table row before block', + ], + 'dashicons-table-row-delete' => [ + 'code' => 'f15d', + 'label' => 'table row delete', + 'keywords' => 'table row delete block', + ], + ], + ], + + 'tinymce' => [ + 'label' => __( 'TinyMCE', 'wporg' ), + 'icons' => [ + 'dashicons-editor-bold' => [ + 'code' => 'f200', + 'label' => 'bold', + 'keywords' => 'bold editor tinymce', + ], + 'dashicons-editor-italic' => [ + 'code' => 'f201', + 'label' => 'italic', + 'keywords' => 'italic editor tinymce', + ], + 'dashicons-editor-ul' => [ + 'code' => 'f203', + 'label' => 'unordered list', + 'keywords' => 'ul unordered list editor tinymce', + ], + 'dashicons-editor-ol' => [ + 'code' => 'f204', + 'label' => 'ordered list', + 'keywords' => 'ol ordered listeditor tinymce', + ], + 'dashicons-editor-ol-rtl' => [ + 'code' => 'f12c', + 'label' => 'ordered list RTL', + 'keywords' => 'ol ordered list rtl right left editor tinymce', + ], + 'dashicons-editor-quote' => [ + 'code' => 'f205', + 'label' => 'quote', + 'keywords' => 'quote editor tinymce', + ], + 'dashicons-editor-alignleft' => [ + 'code' => 'f206', + 'label' => 'align left', + 'keywords' => 'align left editor tinymce', + ], + 'dashicons-editor-aligncenter' => [ + 'code' => 'f207', + 'label' => 'align center', + 'keywords' => 'align center editor tinymce', + ], + 'dashicons-editor-alignright' => [ + 'code' => 'f208', + 'label' => 'align right', + 'keywords' => 'align right editor tinymce', + ], + 'dashicons-editor-insertmore' => [ + 'code' => 'f209', + 'label' => 'insert more', + 'keywords' => 'insert more editor tinymce', + ], + 'dashicons-editor-spellcheck' => [ + 'code' => 'f210', + 'label' => 'spellcheck', + 'keywords' => 'spellcheck editor tinymce', + ], + /* Duplicate + 'dashicons-editor-distractionfree' => [ + 'code' => 'f211', + 'label' => 'distraction-free", + 'keywords' => '', + ], + */ + 'dashicons-editor-expand' => [ + 'code' => 'f211', + 'label' => 'expand', + 'keywords' => 'expand editor tinymce', + ], + 'dashicons-editor-contract' => [ + 'code' => 'f506', + 'label' => 'contract', + 'keywords' => 'contract editor tinymce', + ], + 'dashicons-editor-kitchensink' => [ + 'code' => 'f212', + 'label' => 'kitchen sink', + 'keywords' => 'kitchen sink editor tinymce', + ], + 'dashicons-editor-underline' => [ + 'code' => 'f213', + 'label' => 'underline', + 'keywords' => 'underline editor tinymce', + ], + 'dashicons-editor-justify' => [ + 'code' => 'f214', + 'label' => 'justify', + 'keywords' => 'justify editor tinymce', + ], + 'dashicons-editor-textcolor' => [ + 'code' => 'f215', + 'label' => 'text color', + 'keywords' => 'textcolor editor text color tinymce', + ], + 'dashicons-editor-paste-word' => [ + 'code' => 'f216', + 'label' => 'paste word', + 'keywords' => 'paste editor word tinymce', + ], + 'dashicons-editor-paste-text' => [ + 'code' => 'f217', + 'label' => 'paste text', + 'keywords' => 'paste editor text tinymce', + ], + 'dashicons-editor-removeformatting' => [ + 'code' => 'f218', + 'label' => 'remove formatting', + 'keywords' => 'remove formatting editor tinymce', + ], + 'dashicons-editor-video' => [ + 'code' => 'f219', + 'label' => 'video', + 'keywords' => 'video editor tinymce', + ], + 'dashicons-editor-customchar' => [ + 'code' => 'f220', + 'label' => 'custom character', + 'keywords' => 'custom character editor tinymce', + ], + 'dashicons-editor-outdent' => [ + 'code' => 'f221', + 'label' => 'outdent', + 'keywords' => 'outdent editor tinymce', + ], + 'dashicons-editor-indent' => [ + 'code' => 'f222', + 'label' => 'indent', + 'keywords' => 'indent editor tinymce', + ], + 'dashicons-editor-help' => [ + 'code' => 'f223', + 'label' => 'help', + 'keywords' => 'help editor tinymce', + ], + 'dashicons-editor-strikethrough' => [ + 'code' => 'f224', + 'label' => 'strikethrough', + 'keywords' => 'strikethrough editor tinymce', + ], + 'dashicons-editor-unlink' => [ + 'code' => 'f225', + 'label' => 'unlink', + 'keywords' => 'unlink editor tinymce', + ], + 'dashicons-editor-rtl' => [ + 'code' => 'f320', + 'label' => 'RTL', + 'keywords' => 'rtl right left editor tinymce', + ], + 'dashicons-editor-ltr' => [ + 'code' => 'f10c', + 'label' => 'LTR', + 'keywords' => 'ltr left right editor tinymce', + ], + 'dashicons-editor-break' => [ + 'code' => 'f474', + 'label' => 'break', + 'keywords' => 'break editor tinymce', + ], + 'dashicons-editor-code' => [ + 'code' => 'f475', + 'label' => 'code', + 'keywords' => 'code editor tinymce', + ], + /* Duplicate + 'dashicons-editor-code-duplicate' => [ + 'code' => 'f494', + 'label' => 'code duplicate', + 'keywords' => '', + ], + */ + 'dashicons-editor-paragraph' => [ + 'code' => 'f476', + 'label' => 'paragraph', + 'keywords' => 'paragraph editor tinymce', + ], + 'dashicons-editor-table' => [ + 'code' => 'f535', + 'label' => 'table', + 'keywords' => 'table editor tinymce', + ], + ], + ], + + 'posts screen' => [ + 'label' => __( 'Posts Screen', 'wporg' ), + 'icons' => [ + 'dashicons-align-left' => [ + 'code' => 'f135', + 'label' => 'align left', + 'keywords' => 'align left', + ], + 'dashicons-align-right' => [ + 'code' => 'f136', + 'label' => 'align right', + 'keywords' => 'align right', + ], + 'dashicons-align-center' => [ + 'code' => 'f134', + 'label' => 'align center', + 'keywords' => 'align center', + ], + 'dashicons-align-none' => [ + 'code' => 'f138', + 'label' => 'align none', + 'keywords' => 'align none', + ], + 'dashicons-lock' => [ + 'code' => 'f160', + 'label' => 'lock', + 'keywords' => 'lock', + ], + /* Duplicate + 'dashicons-lock-duplicate' => [ + 'code' => 'f315', + 'label' => 'lock duplicate', + 'keywords' => '', + ], + */ + 'dashicons-unlock' => [ + 'code' => 'f528', + 'label' => 'unlock', + 'keywords' => 'unlock', + ], + 'dashicons-calendar' => [ + 'code' => 'f145', + 'label' => 'calendar', + 'keywords' => 'calendar', + ], + 'dashicons-calendar-alt' => [ + 'code' => 'f508', + 'label' => 'calendar (alt)', + 'keywords' => 'calendar alt', + ], + 'dashicons-visibility' => [ + 'code' => 'f177', + 'label' => 'visibility', + 'keywords' => 'visibility', + ], + 'dashicons-hidden' => [ + 'code' => 'f530', + 'label' => 'hidden', + 'keywords' => 'hidden', + ], + 'dashicons-post-status' => [ + 'code' => 'f173', + 'label' => 'post status', + 'keywords' => 'post status', + ], + 'dashicons-edit' => [ + 'code' => 'f464', + 'label' => 'edit', + 'keywords' => 'edit pencil', + ], + 'dashicons-trash' => [ + 'code' => 'f182', + 'label' => 'trash', + 'keywords' => 'trash remove delete', + ], + 'dashicons-sticky' => [ + 'code' => 'f537', + 'label' => 'sticky', + 'keywords' => 'sticky', + ], + ], + ], + + 'sorting' => [ + 'label' => __( 'Sorting', 'wporg' ), + 'icons' => [ + 'dashicons-external' => [ + 'code' => 'f504', + 'label' => 'external', + 'keywords' => 'external', + ], + 'dashicons-arrow-up' => [ + 'code' => 'f142', + 'label' => 'arrow up', + 'keywords' => 'arrow up', + ], + /* Duplicate + 'dashicons-arrow-up-duplicate' => [ + 'code' => 'f143', + 'label' => 'arrow up duplicate', + 'keywords' => '', + ], + */ + 'dashicons-arrow-down' => [ + 'code' => 'f140', + 'label' => 'arrow down', + 'keywords' => 'arrow down', + ], + 'dashicons-arrow-right' => [ + 'code' => 'f139', + 'label' => 'arrow right', + 'keywords' => 'arrow right', + ], + 'dashicons-arrow-left' => [ + 'code' => 'f141', + 'label' => 'arrow left', + 'keywords' => 'arrow left', + ], + 'dashicons-arrow-up-alt' => [ + 'code' => 'f342', + 'label' => 'arrow up (alt)', + 'keywords' => 'arrow up alt', + ], + 'dashicons-arrow-down-alt' => [ + 'code' => 'f346', + 'label' => 'arrow down (alt)', + 'keywords' => 'arrow down alt', + ], + 'dashicons-arrow-right-alt' => [ + 'code' => 'f344', + 'label' => 'arrow right (alt)', + 'keywords' => 'arrow right alt', + ], + 'dashicons-arrow-left-alt' => [ + 'code' => 'f340', + 'label' => 'arrow left (alt)', + 'keywords' => 'arrow left alt', + ], + 'dashicons-arrow-up-alt2' => [ + 'code' => 'f343', + 'label' => 'arrow up (alt2)', + 'keywords' => 'arrow up alt', + ], + 'dashicons-arrow-down-alt2' => [ + 'code' => 'f347', + 'label' => 'arrow down (alt2)', + 'keywords' => 'arrow down alt', + ], + 'dashicons-arrow-right-alt2' => [ + 'code' => 'f345', + 'label' => 'arrow right (alt2)', + 'keywords' => 'arrow right alt', + ], + 'dashicons-arrow-left-alt2' => [ + 'code' => 'f341', + 'label' => 'arrow left (alt2)', + 'keywords' => 'arrow left alt', + ], + 'dashicons-sort' => [ + 'code' => 'f156', + 'label' => 'sort', + 'keywords' => 'sort', + ], + 'dashicons-leftright' => [ + 'code' => 'f229', + 'label' => 'left right', + 'keywords' => 'left right', + ], + 'dashicons-randomize' => [ + 'code' => 'f503', + 'label' => 'randomize', + 'keywords' => 'randomize shuffle', + ], + 'dashicons-list-view' => [ + 'code' => 'f163', + 'label' => 'list view', + 'keywords' => 'list view', + ], + 'dashicons-excerpt-view' => [ + 'code' => 'f164', + 'label' => 'excerpt view', + 'keywords' => 'excerpt view', + ], + 'dashicons-grid-view' => [ + 'code' => 'f509', + 'label' => 'grid view', + 'keywords' => 'grid view', + ], + 'dashicons-move' => [ + 'code' => 'f545', + 'label' => 'move', + 'keywords' => 'move', + ], + ], + ], + + 'social' => [ + 'label' => __( 'Social', 'wporg' ), + 'icons' => [ + 'dashicons-share' => [ + 'code' => 'f237', + 'label' => 'share', + 'keywords' => 'share social', + ], + 'dashicons-share-alt' => [ + 'code' => 'f240', + 'label' => 'share (alt)', + 'keywords' => 'share alt social', + ], + 'dashicons-share-alt2' => [ + 'code' => 'f242', + 'label' => 'share (alt2)', + 'keywords' => 'share alt social', + ], + 'dashicons-rss' => [ + 'code' => 'f303', + 'label' => 'RSS', + 'keywords' => 'rss social', + ], + 'dashicons-email' => [ + 'code' => 'f465', + 'label' => 'email', + 'keywords' => 'email social', + ], + 'dashicons-email-alt' => [ + 'code' => 'f466', + 'label' => 'email (alt)', + 'keywords' => 'email alt social', + ], + 'dashicons-email-alt2' => [ + 'code' => 'f467', + 'label' => 'email (alt2)', + 'keywords' => 'email alt social', + ], + 'dashicons-networking' => [ + 'code' => 'f325', + 'label' => 'networking', + 'keywords' => 'networking social', + ], + 'dashicons-amazon' => [ + 'code' => 'f162', + 'label' => 'Amazon', + 'keywords' => 'amazon social', + ], + 'dashicons-facebook' => [ + 'code' => 'f304', + 'label' => 'Facebook', + 'keywords' => 'facebook social', + ], + 'dashicons-facebook-alt' => [ + 'code' => 'f305', + 'label' => 'Facebook (alt)', + 'keywords' => 'facebook social alt', + ], + 'dashicons-google' => [ + 'code' => 'f18b', + 'label' => 'Google', + 'keywords' => 'google social', + ], + /* Defunct + 'dashicons-googleplus' => [ + 'code' => 'f462', + 'label' => 'Google+', + 'keywords' => 'googleplus social', + ], + */ + 'dashicons-instagram' => [ + 'code' => 'f12d', + 'label' => 'Instagram', + 'keywords' => 'instagram social', + ], + 'dashicons-linkedin' => [ + 'code' => 'f18d', + 'label' => 'LinkedIn', + 'keywords' => 'linkedin social', + ], + 'dashicons-pinterest' => [ + 'code' => 'f192', + 'label' => 'Pinterest', + 'keywords' => 'pinterest social', + ], + 'dashicons-podio' => [ + 'code' => 'f19c', + 'label' => 'Podio', + 'keywords' => 'podio social', + ], + 'dashicons-reddit' => [ + 'code' => 'f195', + 'label' => 'Reddit', + 'keywords' => 'reddit social', + ], + 'dashicons-spotify' => [ + 'code' => 'f196', + 'label' => 'Spotify', + 'keywords' => 'spotify social', + ], + 'dashicons-twitch' => [ + 'code' => 'f199', + 'label' => 'Twitch', + 'keywords' => 'twitch social', + ], + 'dashicons-twitter' => [ + 'code' => 'f301', + 'label' => 'Twitter', + 'keywords' => 'twitter social', + ], + 'dashicons-twitter-alt' => [ + 'code' => 'f302', + 'label' => 'Twitter (alt)', + 'keywords' => 'twitter social alt', + ], + 'dashicons-whatsapp' => [ + 'code' => 'f19a', + 'label' => 'WhatsApp', + 'keywords' => 'whatsapp social', + ], + 'dashicons-xing' => [ + 'code' => 'f19d', + 'label' => 'Xing', + 'keywords' => 'xing social', + ], + 'dashicons-youtube' => [ + 'code' => 'f19b', + 'label' => 'YouTube', + 'keywords' => 'youtube social', + ], + ], + ], + + 'WordPress.org' => [ + 'label' => __( 'WordPress.org', 'wporg' ), + 'icons' => [ + 'dashicons-hammer' => [ + 'code' => 'f308', + 'label' => 'hammer', + 'keywords' => 'hammer development', + ], + 'dashicons-art' => [ + 'code' => 'f309', + 'label' => 'art', + 'keywords' => 'art design', + ], + 'dashicons-migrate' => [ + 'code' => 'f310', + 'label' => 'migrate', + 'keywords' => 'migrate migration', + ], + 'dashicons-performance' => [ + 'code' => 'f311', + 'label' => 'performance', + 'keywords' => 'performance', + ], + 'dashicons-universal-access' => [ + 'code' => 'f483', + 'label' => 'universal access', + 'keywords' => 'universal access accessibility', + ], + 'dashicons-universal-access-alt' => [ + 'code' => 'f507', + 'label' => 'universal access (alt)', + 'keywords' => 'universal access accessibility alt', + ], + 'dashicons-tickets' => [ + 'code' => 'f486', + 'label' => 'tickets', + 'keywords' => 'tickets', + ], + 'dashicons-nametag' => [ + 'code' => 'f484', + 'label' => 'nametag', + 'keywords' => 'nametag', + ], + 'dashicons-clipboard' => [ + 'code' => 'f481', + 'label' => 'clipboard', + 'keywords' => 'clipboard', + ], + 'dashicons-heart' => [ + 'code' => 'f487', + 'label' => 'heart', + 'keywords' => 'heart', + ], + 'dashicons-megaphone' => [ + 'code' => 'f488', + 'label' => 'megaphone', + 'keywords' => 'megaphone', + ], + 'dashicons-schedule' => [ + 'code' => 'f489', + 'label' => 'schedule', + 'keywords' => 'schedule', + ], + 'dashicons-tide' => [ + 'code' => 'f10d', + 'label' => 'Tide', + 'keywords' => 'Tide', + ], + 'dashicons-rest-api' => [ + 'code' => 'f124', + 'label' => 'REST API', + 'keywords' => 'REST API', + ], + 'dashicons-code-standards' => [ + 'code' => 'f13a', + 'label' => 'code standards', + 'keywords' => 'code standards', + ], + ], + ], + + 'buddicons' => [ + 'label' => __( 'Buddicons', 'wporg' ), + 'icons' => [ + 'dashicons-buddicons-activity' => [ + 'code' => 'f452', + 'label' => 'activity', + 'keywords' => 'activity buddicons', + ], + 'dashicons-buddicons-bbpress-logo' => [ + 'code' => 'f477', + 'label' => 'bbPress', + 'keywords' => 'bbPress buddicons', + ], + 'dashicons-buddicons-buddypress-logo' => [ + 'code' => 'f448', + 'label' => 'BuddyPress', + 'keywords' => 'BuddyPress buddicons', + ], + 'dashicons-buddicons-community' => [ + 'code' => 'f453', + 'label' => 'community', + 'keywords' => 'community buddicons', + ], + 'dashicons-buddicons-forums' => [ + 'code' => 'f449', + 'label' => 'forums', + 'keywords' => 'forums buddicons', + ], + 'dashicons-buddicons-friends' => [ + 'code' => 'f454', + 'label' => 'friends', + 'keywords' => 'friends buddicons', + ], + 'dashicons-buddicons-groups' => [ + 'code' => 'f456', + 'label' => 'groups', + 'keywords' => 'groups buddicons', + ], + 'dashicons-buddicons-pm' => [ + 'code' => 'f457', + 'label' => 'pm', + 'keywords' => 'private message buddicons pm', + ], + 'dashicons-buddicons-replies' => [ + 'code' => 'f451', + 'label' => 'replies', + 'keywords' => 'replies buddicons', + ], + 'dashicons-buddicons-topics' => [ + 'code' => 'f450', + 'label' => 'topics', + 'keywords' => 'topics buddicons', + ], + 'dashicons-buddicons-tracking' => [ + 'code' => 'f455', + 'label' => 'tracking', + 'keywords' => 'tracking buddicons', + ], + ], + ], + + 'products' => [ + 'label' => __( 'Products', 'wporg' ), + 'icons' => [ + 'dashicons-wordpress' => [ + 'code' => 'f120', + 'label' => 'WordPress', + 'keywords' => 'WordPress', + ], + 'dashicons-wordpress-alt' => [ + 'code' => 'f324', + 'label' => 'WordPress (alt)', + 'keywords' => 'WordPress alt', + ], + 'dashicons-pressthis' => [ + 'code' => 'f157', + 'label' => 'Pressthis', + 'keywords' => 'Pressthis', + ], + 'dashicons-update' => [ + 'code' => 'f463', + 'label' => 'update', + 'keywords' => 'update', + ], + 'dashicons-update-alt' => [ + 'code' => 'f113', + 'label' => 'update (alt)', + 'keywords' => 'update alt', + ], + 'dashicons-screenoptions' => [ + 'code' => 'f180', + 'label' => 'screen options', + 'keywords' => 'screenoptions', + ], + 'dashicons-info' => [ + 'code' => 'f348', + 'label' => 'info', + 'keywords' => 'info', + ], + 'dashicons-cart' => [ + 'code' => 'f174', + 'label' => 'cart', + 'keywords' => 'cart shopping', + ], + 'dashicons-feedback' => [ + 'code' => 'f175', + 'label' => 'feedback', + 'keywords' => 'feedback form', + ], + 'dashicons-cloud' => [ + 'code' => 'f176', + 'label' => 'cloud', + 'keywords' => 'cloud', + ], + 'dashicons-translation' => [ + 'code' => 'f326', + 'label' => 'translation', + 'keywords' => 'translation language', + ], + ], + ], + + 'taxonomies' => [ + 'label' => __( 'Taxonomies', 'wporg' ), + 'icons' => [ + 'dashicons-tag' => [ + 'code' => 'f323', + 'label' => 'tag', + 'keywords' => 'tag taxonomy', + ], + 'dashicons-category' => [ + 'code' => 'f318', + 'label' => 'category', + 'keywords' => 'category taxonomy', + ], + ], + ], + + 'widgets' => [ + 'label' => __( 'Widgets', 'wporg' ), + 'icons' => [ + 'dashicons-archive' => [ + 'code' => 'f480', + 'label' => 'archive', + 'keywords' => 'archive widget', + ], + 'dashicons-tagcloud' => [ + 'code' => 'f479', + 'label' => 'tagcloud', + 'keywords' => 'tagcloud widget', + ], + 'dashicons-text' => [ + 'code' => 'f478', + 'label' => 'text', + 'keywords' => 'text widget', + ], + ], + ], + + 'notifications' => [ + 'label' => __( 'Notifications', 'wporg' ), + 'icons' => [ + 'dashicons-bell' => [ + 'code' => 'f16d', + 'label' => 'bell', + 'keywords' => 'bell notifications', + ], + 'dashicons-yes' => [ + 'code' => 'f147', + 'label' => 'yes', + 'keywords' => 'yes check checkmark notifications', + ], + 'dashicons-yes-alt' => [ + 'code' => 'f12a', + 'label' => 'yes (alt)', + 'keywords' => 'yes check checkmark alt notifications', + ], + 'dashicons-no' => [ + 'code' => 'f158', + 'label' => 'no', + 'keywords' => 'no x notifications', + ], + 'dashicons-no-alt' => [ + 'code' => 'f335', + 'label' => 'no (alt)', + 'keywords' => 'no x alt notifications', + ], + 'dashicons-plus' => [ + 'code' => 'f132', + 'label' => 'plus', + 'keywords' => 'plus add increase notifications', + ], + 'dashicons-plus-alt' => [ + 'code' => 'f502', + 'label' => 'plus (alt)', + 'keywords' => 'plus add increase alt notifications', + ], + 'dashicons-plus-alt2' => [ + 'code' => 'f543', + 'label' => 'plus (alt2)', + 'keywords' => 'plus add increase alt notifications', + ], + 'dashicons-minus' => [ + 'code' => 'f460', + 'label' => 'minus', + 'keywords' => 'minus decrease notifications', + ], + 'dashicons-dismiss' => [ + 'code' => 'f153', + 'label' => 'dismiss', + 'keywords' => 'dismiss notifications', + ], + 'dashicons-marker' => [ + 'code' => 'f159', + 'label' => 'marker', + 'keywords' => 'marker notifications', + ], + 'dashicons-star-filled' => [ + 'code' => 'f155', + 'label' => 'star filled', + 'keywords' => 'filled star notifications', + ], + 'dashicons-star-half' => [ + 'code' => 'f459', + 'label' => 'star half', + 'keywords' => 'half star notifications', + ], + 'dashicons-star-empty' => [ + 'code' => 'f154', + 'label' => 'star empty', + 'keywords' => 'empty star notifications', + ], + 'dashicons-flag' => [ + 'code' => 'f227', + 'label' => 'flag', + 'keywords' => 'flag notifications', + ], + 'dashicons-warning' => [ + 'code' => 'f534', + 'label' => 'warning', + 'keywords' => 'warning notifications', + ], + ], + ], + + 'miscellaneous' => [ + 'label' => __( 'Miscellaneous', 'wporg' ), + 'icons' => [ + 'dashicons-location' => [ + 'code' => 'f230', + 'label' => 'location', + 'keywords' => 'location pin', + ], + 'dashicons-location-alt' => [ + 'code' => 'f231', + 'label' => 'location (alt)', + 'keywords' => 'location alt', + ], + 'dashicons-vault' => [ + 'code' => 'f178', + 'label' => 'vault', + 'keywords' => 'vault safe', + ], + 'dashicons-shield' => [ + 'code' => 'f332', + 'label' => 'shield', + 'keywords' => 'shield', + ], + 'dashicons-shield-alt' => [ + 'code' => 'f334', + 'label' => 'shield (alt)', + 'keywords' => 'shield alt', + ], + 'dashicons-sos' => [ + 'code' => 'f468', + 'label' => 'sos', + 'keywords' => 'sos help', + ], + 'dashicons-search' => [ + 'code' => 'f179', + 'label' => 'search', + 'keywords' => 'search', + ], + 'dashicons-slides' => [ + 'code' => 'f181', + 'label' => 'slides', + 'keywords' => 'slides', + ], + 'dashicons-text-page' => [ + 'code' => 'f121', + 'label' => 'text page', + 'keywords' => 'text page', + ], + 'dashicons-analytics' => [ + 'code' => 'f183', + 'label' => 'analytics', + 'keywords' => 'analytics', + ], + 'dashicons-chart-pie' => [ + 'code' => 'f184', + 'label' => 'chart pie', + 'keywords' => 'pie chart', + ], + 'dashicons-chart-bar' => [ + 'code' => 'f185', + 'label' => 'chart bar', + 'keywords' => 'bar chart', + ], + 'dashicons-chart-line' => [ + 'code' => 'f238', + 'label' => 'chart line', + 'keywords' => 'line chart', + ], + 'dashicons-chart-area' => [ + 'code' => 'f239', + 'label' => 'chart area', + 'keywords' => 'area chart', + ], + 'dashicons-groups' => [ + 'code' => 'f307', + 'label' => 'groups', + 'keywords' => 'groups', + ], + 'dashicons-businessman' => [ + 'code' => 'f338', + 'label' => 'businessman', + 'keywords' => 'businessman', + ], + 'dashicons-businesswoman' => [ + 'code' => 'f12f', + 'label' => 'businesswoman', + 'keywords' => 'businesswoman', + ], + 'dashicons-businessperson' => [ + 'code' => 'f12e', + 'label' => 'businessperson', + 'keywords' => 'businessperson', + ], + 'dashicons-id' => [ + 'code' => 'f336', + 'label' => 'id', + 'keywords' => 'id', + ], + 'dashicons-id-alt' => [ + 'code' => 'f337', + 'label' => 'id (alt)', + 'keywords' => 'id alt', + ], + 'dashicons-products' => [ + 'code' => 'f312', + 'label' => 'products', + 'keywords' => 'products', + ], + 'dashicons-awards' => [ + 'code' => 'f313', + 'label' => 'awards', + 'keywords' => 'awards', + ], + 'dashicons-forms' => [ + 'code' => 'f314', + 'label' => 'forms', + 'keywords' => 'forms', + ], + 'dashicons-testimonial' => [ + 'code' => 'f473', + 'label' => 'testimonial', + 'keywords' => 'testimonial', + ], + 'dashicons-portfolio' => [ + 'code' => 'f322', + 'label' => 'portfolio', + 'keywords' => 'portfolio', + ], + 'dashicons-book' => [ + 'code' => 'f330', + 'label' => 'book', + 'keywords' => 'book', + ], + 'dashicons-book-alt' => [ + 'code' => 'f331', + 'label' => 'book (alt)', + 'keywords' => 'book alt', + ], + 'dashicons-download' => [ + 'code' => 'f316', + 'label' => 'download', + 'keywords' => 'download', + ], + 'dashicons-upload' => [ + 'code' => 'f317', + 'label' => 'upload', + 'keywords' => 'upload', + ], + 'dashicons-backup' => [ + 'code' => 'f321', + 'label' => 'backup', + 'keywords' => 'backup', + ], + 'dashicons-clock' => [ + 'code' => 'f469', + 'label' => 'clock', + 'keywords' => 'clock', + ], + 'dashicons-lightbulb' => [ + 'code' => 'f339', + 'label' => 'lightbulb', + 'keywords' => 'lightbulb', + ], + 'dashicons-microphone' => [ + 'code' => 'f482', + 'label' => 'microphone', + 'keywords' => 'microphone mic', + ], + 'dashicons-desktop' => [ + 'code' => 'f472', + 'label' => 'desktop', + 'keywords' => 'desktop monitor', + ], + 'dashicons-laptop' => [ + 'code' => 'f547', + 'label' => 'laptop', + 'keywords' => 'laptop', + ], + 'dashicons-tablet' => [ + 'code' => 'f471', + 'label' => 'tablet', + 'keywords' => 'tablet ipad', + ], + 'dashicons-smartphone' => [ + 'code' => 'f470', + 'label' => 'smartphone', + 'keywords' => 'smartphone iphone', + ], + 'dashicons-phone' => [ + 'code' => 'f525', + 'label' => 'phone', + 'keywords' => 'phone', + ], + 'dashicons-index-card' => [ + 'code' => 'f510', + 'label' => 'index card', + 'keywords' => 'index card', + ], + 'dashicons-carrot' => [ + 'code' => 'f511', + 'label' => 'carrot', + 'keywords' => 'carrot food vendor', + ], + 'dashicons-building' => [ + 'code' => 'f512', + 'label' => 'building', + 'keywords' => 'building', + ], + 'dashicons-store' => [ + 'code' => 'f513', + 'label' => 'store', + 'keywords' => 'store', + ], + 'dashicons-album' => [ + 'code' => 'f514', + 'label' => 'album', + 'keywords' => 'album', + ], + 'dashicons-palmtree' => [ + 'code' => 'f527', + 'label' => 'palm tree', + 'keywords' => 'palm tree', + ], + 'dashicons-tickets-alt' => [ + 'code' => 'f524', + 'label' => 'tickets (alt)', + 'keywords' => 'tickets alt', + ], + 'dashicons-money' => [ + 'code' => 'f526', + 'label' => 'money', + 'keywords' => 'money', + ], + 'dashicons-money-alt' => [ + 'code' => 'f18e', + 'label' => 'money (alt)', + 'keywords' => 'money alt', + ], + 'dashicons-smiley' => [ + 'code' => 'f328', + 'label' => 'smiley', + 'keywords' => 'smiley smile', + ], + 'dashicons-thumbs-up' => [ + 'code' => 'f529', + 'label' => 'thumbs up', + 'keywords' => 'thumbs up', + ], + 'dashicons-thumbs-down' => [ + 'code' => 'f542', + 'label' => 'thumbs down', + 'keywords' => 'thumbs down', + ], + 'dashicons-layout' => [ + 'code' => 'f538', + 'label' => 'layout', + 'keywords' => 'layout', + ], + 'dashicons-paperclip' => [ + 'code' => 'f546', + 'label' => 'paperclip', + 'keywords' => 'paperclip', + ], + 'dashicons-color-picker' => [ + 'code' => 'f131', + 'label' => 'color picker', + 'keywords' => 'color picker', + ], + 'dashicons-edit-large' => [ + 'code' => 'f327', + 'label' => 'edit large', + 'keywords' => 'edit large', + ], + 'dashicons-edit-page' => [ + 'code' => 'f186', + 'label' => 'edit page', + 'keywords' => 'edit page', + ], + 'dashicons-airplane' => [ + 'code' => 'f15f', + 'label' => 'airplane', + 'keywords' => 'airplane', + ], + 'dashicons-bank' => [ + 'code' => 'f16a', + 'label' => 'bank', + 'keywords' => 'bank', + ], + 'dashicons-beer' => [ + 'code' => 'f16c', + 'label' => 'beer', + 'keywords' => 'beer', + ], + 'dashicons-calculator' => [ + 'code' => 'f16e', + 'label' => 'calculator', + 'keywords' => 'calculator', + ], + 'dashicons-car' => [ + 'code' => 'f16b', + 'label' => 'car', + 'keywords' => 'car', + ], + 'dashicons-coffee' => [ + 'code' => 'f16f', + 'label' => 'coffee', + 'keywords' => 'coffee', + ], + 'dashicons-drumstick' => [ + 'code' => 'f17f', + 'label' => 'drumstick', + 'keywords' => 'drumstick', + ], + 'dashicons-food' => [ + 'code' => 'f187', + 'label' => 'food', + 'keywords' => 'food', + ], + 'dashicons-fullscreen-alt' => [ + 'code' => 'f188', + 'label' => 'fullscreen (alt)', + 'keywords' => 'fullscreen alt', + ], + 'dashicons-fullscreen-exit-alt' => [ + 'code' => 'f189', + 'label' => 'fullscreen exit (alt)', + 'keywords' => 'fullscreen exit alt', + ], + 'dashicons-games' => [ + 'code' => 'f18a', + 'label' => 'games', + 'keywords' => 'games', + ], + 'dashicons-hourglass' => [ + 'code' => 'f18c', + 'label' => 'hourglass', + 'keywords' => 'hourglass', + ], + 'dashicons-open-folder' => [ + 'code' => 'f18f', + 'label' => 'open folder', + 'keywords' => 'open folder', + ], + 'dashicons-pdf' => [ + 'code' => 'f190', + 'label' => 'PDF', + 'keywords' => 'pdf', + ], + 'dashicons-pets' => [ + 'code' => 'f191', + 'label' => 'pets', + 'keywords' => 'pets', + ], + 'dashicons-printer' => [ + 'code' => 'f193', + 'label' => 'printer', + 'keywords' => 'printer', + ], + 'dashicons-privacy' => [ + 'code' => 'f194', + 'label' => 'privacy', + 'keywords' => 'privacy', + ], + 'dashicons-superhero' => [ + 'code' => 'f198', + 'label' => 'superhero', + 'keywords' => 'superhero', + ], + 'dashicons-superhero-alt' => [ + 'code' => 'f197', + 'label' => 'superhero (alt)', + 'keywords' => 'superhero alt', + ], + ], + ], + ]; + } + +} // Devhub_Dashicons diff --git a/source/wp-content/themes/wpr-developer-2024/inc/explanations.php b/source/wp-content/themes/wpr-developer-2024/inc/explanations.php new file mode 100644 index 000000000..e22f346f7 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/inc/explanations.php @@ -0,0 +1,677 @@ +<?php +/** + * Explanations functionality + * + * @package wporg-developer + */ + +/** + * Class to handle creating, editing, managing, and retrieving explanations + * for various Code Reference post types. + */ +class WPORG_Explanations { + + /** + * List of Code Reference post types. + * + * @access public + * @var array + */ + public $post_types = array(); + + /** + * Explanations post type slug. + * + * @access public + * @var string + */ + public $exp_post_type = 'wporg_explanations'; + + /** + * Explanation-specific screen IDs. + * + * @access public + * @var array + */ + public $screen_ids = []; + + /** + * Constructor. + * + * @access public + */ + public function __construct() { + $this->post_types = DevHub\get_parsed_post_types(); + + $this->screen_ids = [ $this->exp_post_type, "edit-{$this->exp_post_type}" ]; + + // Setup. + add_action( 'init', array( $this, 'register_post_type' ), 0 ); + add_action( 'init', array( $this, 'remove_editor_support' ), 100 ); + + // Admin. + add_action( 'edit_form_after_title', array( $this, 'post_to_expl_controls' ) ); + add_action( 'add_meta_boxes', array( $this, 'expl_to_post_controls' ) ); + add_action( 'admin_bar_menu', array( $this, 'toolbar_edit_link' ), 100 ); + add_action( 'admin_menu', array( $this, 'admin_menu' ) ); + add_action( 'load-post-new.php', array( $this, 'prevent_direct_creation') ); + // Add admin post listing column for explanations indicator. + add_filter( 'manage_posts_columns', array( $this, 'add_post_column' ) ); + // Output checkmark in explanations column if post has an explanation. + add_action( 'manage_posts_custom_column', array( $this, 'handle_column_data' ), 10, 2 ); + + add_filter( 'preview_post_link', array ( $this, 'preview_post_link' ), 10, 2 ); + + // Permissions. + add_action( 'after_switch_theme', array( $this, 'add_roles' ) ); + add_filter( 'user_has_cap', array( $this, 'grant_caps' ) ); + add_filter( 'post_row_actions', array( $this, 'expl_row_action' ), 10, 2 ); + + // Script and styles. + add_filter( 'devhub-admin_enqueue_scripts', array( $this, 'admin_enqueue_base_scripts' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); + + // AJAX. + add_action( 'wp_ajax_new_explanation', array( $this, 'new_explanation' ) ); + add_action( 'wp_ajax_un_publish', array( $this, 'un_publish_explanation' ) ); + } + + /** + * Register the Explanations post type. + * + * @access public + */ + public function register_post_type() { + register_post_type( $this->exp_post_type, array( + 'labels' => array( + 'name' => __( 'Explanations', 'wporg' ), + 'singular_name' => __( 'Explanation', 'wporg' ), + 'all_items' => __( 'Explanations', 'wporg' ), + 'edit_item' => __( 'Edit Explanation', 'wporg' ), + 'view_item' => __( 'View Explanation', 'wporg' ), + 'search_items' => __( 'Search Explanations', 'wporg' ), + 'not_found' => __( 'No Explanations found', 'wporg' ), + 'not_found_in_trash' => __( 'No Explanations found in trash', 'wporg' ), + ), + 'public' => false, + 'publicly_queryable'=> true, + 'hierarchical' => false, + 'show_ui' => true, + 'show_in_menu' => true, + 'menu_icon' => 'dashicons-info', + 'show_in_admin_bar' => false, + 'show_in_nav_menus' => false, + 'capability_type' => 'explanation', + 'map_meta_cap' => true, + 'supports' => array( 'editor', 'revisions' ), + 'rewrite' => false, + 'query_var' => false, + 'show_in_rest' => true, + ) ); + } + + /** + * Remove 'editor' support for the function, hook, class, and method post types. + * + * @access public + */ + public function remove_editor_support() { + foreach ( $this->post_types as $type ) { + remove_post_type_support( $type, 'editor' ); + } + } + + /** + * Override preview post links for explanations to preview the explanation + * within the context of its associated function/hook/method/class. + * + * The associated post's preview link is amended with query parameters used + * by `get_explanation_content()` to use the explanation being previewed + * instead of the published explanation currently associated with the post. + * + * @access public + * @see 'preview_post_link' filter + * + * @param string $preview_link URL used for the post preview. + * @param WP_Post $post Post object. + * @return string + **/ + public function preview_post_link( $preview_link, $post ) { + if ( $this->exp_post_type !== $post->post_type ) { + return $preview_link; + } + + if ( false !== strpos( $preview_link, 'preview_nonce=' ) ) { + $url = parse_url( $preview_link ); + $url_query = array(); + parse_str( $url['query'], $url_query ); + + $preview_link = get_preview_post_link( + $post->post_parent, + array( + 'wporg_explanations_preview_id' => $url_query['preview_id'], + 'wporg_explanations_preview_nonce' => $url_query['preview_nonce'], + ) + ); + } + + return $preview_link; + } + + /** + * Customizes admin menu. + * + * - Removes "Add new". + * - Adds count of pending explanations. + * + * @access public + */ + public function admin_menu() { + global $menu; + + $menu_slug = 'edit.php?post_type=' . $this->exp_post_type; + + // Remove 'Add New' from submenu. + remove_submenu_page( $menu_slug, 'post-new.php?post_type=' . $this->exp_post_type ); + + // Add pending posts count. + $counts = wp_count_posts( $this->exp_post_type ); + $count = $counts->pending; + if ( $count ) { + // Find the explanations menu item. + foreach ( $menu as $i => $item ) { + if ( $menu_slug == $item[2] ) { + // Modify it to include the pending count. + $menu[ $i ][0] = sprintf( + __( 'Explanations %s', 'wporg' ), + "<span class='update-plugins count-{$count}'><span class='plugin-count'>" . number_format_i18n( $count ) . '</span></span>' + ); + break; + } + } + } + } + + /** + * Prevents direct access to the admin page for creating a new explanation. + * + * Only prevents admin UI access to directly create a new explanation. It does + * not attempt to prevent direct programmatic creation of a new explanation. + * + * @access public + */ + public function prevent_direct_creation() { + if ( isset( $_GET['post_type'] ) && $this->exp_post_type == $_GET['post_type'] ) { + wp_safe_redirect( admin_url() ); + exit; + } + } + + /** + * Output the Post-to-Explanation controls in the post editor for functions, + * hooks, classes, and methods. + * + * @access public + * + * @param WP_Post $post Current post object. + */ + public function post_to_expl_controls( $post ) { + if ( ! in_array( $post->post_type, $this->post_types ) ) { + return; + } + + $explanation = DevHub\get_explanation( $post ); + $date_format = get_option( 'date_format' ) . ', ' . get_option( 'time_format' ); + ?> + <div class="postbox-container" style="margin-top:20px;"> + <div class="postbox"> + <h3 class="hndle"><?php _e( 'Explanation', 'wporg' ); ?></h3> + <div class="inside"> + <table class="form-table explanation-meta"> + <tbody> + <tr valign="top"> + <th scope="row"> + <label for="explanation-status"><?php _e( 'Status:', 'wporg' ); ?></label> + </th> + <td class="explanation-status" name="explanation-status"> + <div class="status-links"> + <?php $this->status_controls( $post ); ?> + </div><!-- .status-links --> + </td><!-- .explanation-status --> + </tr> + <?php if ( $explanation ) : ?> + <tr valign="top"> + <th scope="row"> + <label for="expl-modified"><?php _e( 'Last Modified:', 'wporg' ); ?></label> + </th> + <td name="expl-modified"> + <p><?php echo get_post_modified_time( $date_format, false, $post->ID ); ?></p> + </td> + </tr> + <?php endif; // $has_explanation ?> + </tbody> + </table> + </div> + </div> + </div> + <?php + } + + /** + * Output the Explanation-to-Post controls in the Explanation post editor. + * + * @access public + * + * @param string $post_type Post type. + */ + public function expl_to_post_controls( $post_type = '' ) { + if ( $this->exp_post_type !== $post_type ) { + return; + } + + add_meta_box( + 'wporg_explanations-associated-with', + __( 'Associated with', 'wporg' ), + array( $this, 'render_expl_to_post_controls' ), + 'wporg_explanations', + 'side' + ); + } + + /** + * Render the Explanation-to-Post meta box. + * + * @access public + * + * @param WP_Post $post Current post object. + */ + public function render_expl_to_post_controls( $post ) { + $parent_post_id = $post->post_parent; + $parent_post_type = get_post_type( $parent_post_id ); + + ?> + <strong><?php echo esc_html( get_post_type_object( $parent_post_type )->labels->singular_name ); ?>: </strong> + <?php + printf( + '<a href="%1$s">%2$s</a>', + esc_url( get_permalink( $parent_post_id ) ), + esc_html( str_replace( 'Explanation: ', '', get_the_title( $parent_post_id ) ) ), + ); + } + + /** + * Adds an 'Edit Explanation' link to the Toolbar on parsed post type single pages. + * + * @access public + * + * @param WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance. + */ + public function toolbar_edit_link( $wp_admin_bar ) { + global $wp_the_query; + + $screen = $wp_the_query->get_queried_object(); + + if ( is_admin() || empty( $screen->post_type ) || ! is_singular( $this->post_types ) ) { + return; + } + + // Proceed only if there's an explanation for the current reference post type. + if ( ! empty( $screen->post_type ) && $explanation = \DevHub\get_explanation( $screen ) ) { + + // Must be able to edit the explanation. + if ( is_user_member_of_blog() && current_user_can( 'edit_explanation', $explanation->ID ) ) { + $post_type = get_post_type_object( $this->exp_post_type ); + + $wp_admin_bar->add_menu( array( + 'id' => 'edit-explanation', + 'title' => $post_type->labels->edit_item, + 'href' => get_edit_post_link( $explanation ) + ) ); + } + } + } + + /** + * Adds the 'Explanation Editor' role. + * + * @access public + */ + public function add_roles() { + add_role( + 'expl_editor', + __( 'Explanation Editor', 'wporg' ), + array( + 'unfiltered_html' => true, + 'read' => true, + 'edit_explanations' => true, + 'edit_others_explanations' => true, + 'edit_published_explanations' => true, + 'edit_private_explanations' => true, + 'read_private_explanations' => true, + ) + ); + } + + /** + * Grants explanation capabilities to users. + * + * @access public + * + * @param array $caps Capabilities. + * @return array Modified capabilities array. + */ + public function grant_caps( $caps ) { + + if ( ! is_user_member_of_blog() ) { + return $caps; + } + + $role = current( wp_get_current_user()->roles ); + + // Only grant explanation post type caps for admins, editors, and explanation editors. + if ( in_array( $role, array( 'administrator', 'editor', 'expl_editor' ) ) ) { + $base_caps = array( + 'edit_explanations', + 'edit_others_explanations', + 'edit_published_explanations', + 'edit_posts', + ); + + foreach ( $base_caps as $cap ) { + $caps[ $cap ] = true; + } + + $editor_caps = array( + 'publish_explanations', + 'delete_explanations', + 'delete_others_explanations', + 'delete_published_explanations', + 'delete_private_explanations', + 'edit_private_explanations', + 'read_private_explanations', + ); + + if ( ! empty( $caps['edit_pages'] ) ) { + foreach ( $editor_caps as $cap ) { + $caps[ $cap ] = true; + } + } + } + + return $caps; + } + + /** + * Adds the 'Add/Edit Explanation' row actions to the parsed post type list tables. + * + * @access public + * + * @param array $actions Row actions. + * @param \WP_Post $post Parsed post object. + * @return array (Maybe) filtered row actions. + */ + public function expl_row_action( $actions, $post ) { + if ( ! in_array( $post->post_type, \DevHub\get_parsed_post_types() ) ) { + return $actions; + } + + $expl = \DevHub\get_explanation( $post ); + + $expl_action = array(); + + if ( $expl ) { + if ( ! current_user_can( 'edit_posts', $expl->ID ) ) { + return $actions; + } + + $expl_action['edit-expl'] = sprintf( + '<a href="%1$s" alt="%2$s">%3$s</a>', + esc_url( get_edit_post_link( $expl->ID ) ), + esc_attr__( 'Edit Explanation', 'wporg' ), + __( 'Edit Explanation', 'wporg' ) + ); + } else { + $expl_action['add-expl'] = sprintf( + '<a href="" class="create-expl" data-nonce="%1$s" data-id="%2$s">%3$s</a>', + esc_attr( wp_create_nonce( 'create-expl' ) ), + esc_attr( $post->ID ), + __( 'Add Explanation', 'wporg' ) + ); + } + + return array_merge( $expl_action, $actions ); + } + + /** + * Output the Explanation status controls. + * + * @access public + * + * @param int|WP_Post Post ID or WP_Post object. + */ + public function status_controls( $post ) { + $explanation = DevHub\get_explanation( $post ); + + if ( $explanation ) : + echo $this->get_status_label( $explanation->ID ); + ?> + <span id="expl-row-actions" class="expl-row-actions"> + <a id="edit-expl" href="<?php echo get_edit_post_link( $explanation->ID ); ?>"> + <?php _e( 'Edit Explanation', 'wporg' ); ?> + </a> + <?php if ( 'publish' == get_post_status( $explanation ) ) : ?> + <a href="#unpublish" id="unpublish-expl" data-nonce="<?php echo wp_create_nonce( 'unpublish-expl' ); ?>" data-id="<?php the_ID(); ?>"> + <?php _e( 'Unpublish', 'wporg' ); ?> + </a> + <?php endif; ?> + </span><!-- .expl-row-actions --> + <?php else : ?> + <p class="status" id="status-label"><?php _e( 'None', 'wporg' ); ?></p> + <span id="expl-row-actions" class="expl-row-actions"> + <a id="create-expl" href="" data-nonce="<?php echo wp_create_nonce( 'create-expl' ); ?>" data-id="<?php the_ID(); ?>"> + <?php _e( 'Add Explanation', 'wporg' ); ?> + </a><!-- #create-explanation --> + </span><!-- expl-row-actions --> + <?php + endif; + } + + /** + * Retrieve status label for the given post. + * + * @access public + * + * @param int|WP_Post $post Post ID or WP_Post object. + * @return string + */ + public function get_status_label( $post ) { + if ( ! $post = get_post( $post ) ) { + return ''; + } + + switch ( $status = $post->post_status ) { + case 'draft': + $label = __( 'Draft', 'wporg' ); + break; + case 'pending': + $label = __( 'Pending Review', 'wporg' ); + break; + case 'publish': + $label = __( 'Published', 'wporg' ); + break; + default: + $status = ''; + $label = __( 'None', 'wporg' ); + break; + } + + return '<p class="status ' . $status . '" id="status-label">' . $label . '</p>'; + } + + /** + * Enables enqueuing of admin.css for explanation pages. + * + * @access public + * + * @param bool $do_enqueue Should admin.css be enqueued? + * @return bool True if admin.css should be enqueued, false otherwise. + */ + public function admin_enqueue_base_scripts( $do_enqueue ) { + return $do_enqueue || in_array( get_current_screen()->id, $this->screen_ids ); + } + + /** + * Enqueue JS and CSS for all parsed post types and explanation pages. + * + * @access public + */ + public function admin_enqueue_scripts() { + + $parsed_post_types_screen_ids = DevHub_Admin::get_parsed_post_types_screen_ids(); + + if ( in_array( + get_current_screen()->id, + array_merge( + $parsed_post_types_screen_ids, + $this->screen_ids + ) + ) ) { + wp_enqueue_script( + 'wporg-explanations', + get_stylesheet_directory_uri() . '/js/explanations.js', + array( 'jquery', 'wp-util' ), + filemtime( dirname( __DIR__ ) . '/js/explanations.js' ), + true + ); + + wp_localize_script( + 'wporg-explanations', + 'wporg', + array( + 'editContentLabel' => __( 'Edit Explanation', 'wporg' ), + 'statusLabel' => array( + 'draft' => __( 'Draft', 'wporg' ), + 'pending' => __( 'Pending Review', 'wporg' ), + 'publish' => __( 'Published', 'wporg' ), + ), + ) + ); + } + } + + /** + * AJAX handler for creating and associating a new explanation. + * + * @access public + */ + public function new_explanation() { + check_ajax_referer( 'create-expl', 'nonce' ); + + $post_id = empty( $_REQUEST['post_id'] ) ? 0 : absint( $_REQUEST['post_id'] ); + $context = empty( $_REQUEST['context'] ) ? '' : sanitize_text_field( $_REQUEST['context'] ); + + if ( DevHub\get_explanation( $post_id ) ) { + wp_send_json_error( new WP_Error( 'post_exists', __( 'Explanation already exists.', 'wporg' ) ) ); + } else { + $title = get_post_field( 'post_title', $post_id ); + + $explanation = wp_insert_post( array( + 'post_type' => 'wporg_explanations', + 'post_title' => "Explanation: $title", + 'ping_status' => false, + 'post_parent' => $post_id, + ) ); + + if ( ! is_wp_error( $explanation ) && 0 !== $explanation ) { + wp_send_json_success( array( + 'post_id' => $explanation, + 'parent_id' => $post_id, + 'context' => $context + ) ); + } else { + wp_send_json_error( + new WP_Error( 'post_error', __( 'Explanation could not be created.', 'wporg' ) ) + ); + } + } + } + + /** + * AJAX handler for un-publishing an explanation. + * + * @access public + */ + public function un_publish_explanation() { + check_ajax_referer( 'unpublish-expl', 'nonce' ); + + $post_id = empty( $_REQUEST['post_id'] ) ? 0 : absint( $_REQUEST['post_id'] ); + + if ( $explanation = \DevHub\get_explanation( $post_id ) ) { + $update = wp_update_post( array( + 'ID' => $explanation->ID, + 'post_status' => 'draft' + ) ); + + if ( ! is_wp_error( $update ) && 0 !== $update ) { + wp_send_json_success( array( 'post_id' => $update ) ); + } else { + wp_send_json_error( + new WP_Error( 'unpublish_error', __( 'Explanation could not be un-published.', 'wporg' ) ) + ); + } + } + } + + /** + * Adds a column in the admin listing of posts for parsed post types to + * indicate if they have an explanation. + * + * Inserted as first column after title column. + * + * @access public + * + * @param array $columns Associative array of post column ids and labels. + * @return array + */ + public function add_post_column( $columns ) { + if ( ! empty( $_GET['post_type'] ) && DevHub\is_parsed_post_type( $_GET['post_type'] ) ) { + $index = array_search( 'title', array_keys( $columns ) ); + $pos = false === $index ? count( $columns ) : $index + 1; + + $col_data = [ + 'has_explanation' => sprintf( + '<span class="dashicons dashicons-info" title="%s"></span><span class="screen-reader-text">%s</span>', + esc_attr__( 'Has explanation?', 'wporg' ), + esc_html__( 'Explanation?', 'wporg' ) + ), + ]; + $columns = array_merge( array_slice( $columns, 0, $pos ), $col_data, array_slice( $columns, $pos ) ); + } + + return $columns; + } + + /** + * Outputs an indicator for the explanations column if post has an explanation. + * + * @access public + * + * @param string $column_name The name of the column. + * @param int $post_id The ID of the post. + */ + public function handle_column_data( $column_name, $post_id ) { + if ( 'has_explanation' === $column_name ) { + if ( $explanation = DevHub\get_explanation( $post_id ) ) { + printf( + '<a href="%s">%s%s</a>', + get_edit_post_link( $explanation ), + '<span class="dashicons dashicons-info" aria-hidden="true"></span>', + '<span class="screen-reader-text">' . __( 'Post has an explanation.', 'wporg' ) . '</span>' + ); + } + } + } + +} + +$explanations = new WPORG_Explanations(); diff --git a/source/wp-content/themes/wpr-developer-2024/inc/extras.php b/source/wp-content/themes/wpr-developer-2024/inc/extras.php new file mode 100644 index 000000000..2575dd5b4 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/inc/extras.php @@ -0,0 +1,107 @@ +<?php +/** + * Custom functions that act independently of the theme templates + * + * Eventually, some of the functionality here could be replaced by core features + * + * @package wporg-developer + */ + +/** + * Get our wp_nav_menu() fallback, wp_page_menu(), to show a home link. + * + * @param array $args Configuration arguments. + * @return array + */ +function wporg_developer_page_menu_args( $args ) { + $args['show_home'] = true; + return $args; +} +add_filter( 'wp_page_menu_args', 'wporg_developer_page_menu_args' ); + +/** + * Adds custom classes to the array of body classes. + * + * @param array $classes Classes for the body element. + * @return array + */ +function wporg_developer_body_classes( $classes ) { + // Adds a class of group-blog to blogs with more than 1 published author. + if ( is_multi_author() ) { + $classes[] = 'group-blog'; + } + + return $classes; +} +add_filter( 'body_class', 'wporg_developer_body_classes' ); + +/** + * Appends parentheses to titles in archive view for functions and methods. + * + * @param string $title The title. + * @param int|WP_Post $post Optional. The post ID or post object. + * @return string + */ +function wporg_filter_archive_title( $title, $post = null ) { + if ( ! is_admin() && $post && ( ! is_single() || doing_filter( 'single_post_title' ) ) && in_array( get_post_type( $post ), array( 'wp-parser-function', 'wp-parser-method' ) ) ) { + $title .= '()'; + } + + return $title; +} +add_filter( 'the_title', 'wporg_filter_archive_title', 10, 2 ); +add_filter( 'single_post_title', 'wporg_filter_archive_title', 10, 2 ); + +/** + * Removes the query string from get_pagenum_link() for loop pagination. + * Fixes pagination links like example.com/?foo=bar/page/2/. + * + * @param array $args Arguments for the paginate_links() function. + * @return array Arguments for the paginate_links() function. + */ +function wporg_loop_pagination_args( $args ) { + global $wp_rewrite; + + // Add the $base argument to the array if the user is using permalinks. + if ( $wp_rewrite->using_permalinks() && ! is_search() ) { + $pagenum = trailingslashit( preg_replace( '/\?.*/', '', get_pagenum_link() ) ); + $pagination_base = $wp_rewrite->pagination_base; + + $args['base'] = user_trailingslashit( $pagenum . "{$pagination_base}/%#%" ); + } + + return $args; +} +add_filter( 'loop_pagination_args', 'wporg_loop_pagination_args' ); + +/** + * Removes 'page/1' from pagination links with a query string. + * + * @param string $page_links Page links HTML. + * @return string Page links HTML. + */ +function wporg_loop_pagination( $page_links ) { + global $wp_rewrite; + + $pagination_base = $wp_rewrite->pagination_base; + $request = remove_query_arg( 'paged' ); + $query_string = explode( '?', $request ); + + if ( isset( $query_string[1] ) ) { + + $query_string = preg_quote( $query_string[1], '#' ); + + // Remove 'page/1' from the entire output since it's not needed. + $page_links = preg_replace( + array( + "#(href=['\"].*?){$pagination_base}/1(\?{$query_string}['\"])#", // 'page/1' + "#(href=['\"].*?){$pagination_base}/1/(\?{$query_string}['\"])#", // 'page/1/' + ), + '$1$2', + $page_links + ); + } + + return $page_links; +} +add_filter( 'loop_pagination', 'wporg_loop_pagination' ); diff --git a/source/wp-content/themes/wpr-developer-2024/inc/formatting.php b/source/wp-content/themes/wpr-developer-2024/inc/formatting.php new file mode 100644 index 000000000..c64b0336d --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/inc/formatting.php @@ -0,0 +1,882 @@ +<?php +/** + * Code Reference formatting. + * + * @package wporg-developer + */ + +/** + * Class to handle content formatting. + */ +class DevHub_Formatting { + + /** + * Initializer + */ + public static function init() { + add_action( 'init', array( __CLASS__, 'do_init' ) ); + } + + /** + * Handles adding/removing hooks to perform formatting as needed. + */ + public static function do_init() { + // NOTE: This filtering is temporarily disabled and then restored in + // reference/template-explanation.php + add_filter( 'the_content', array( __CLASS__, 'fix_unintended_markdown' ), 1 ); + + add_filter( 'the_excerpt', array( __CLASS__, 'lowercase_P_dangit_just_once' ) ); + add_filter( 'the_content', array( __CLASS__, 'make_doclink_clickable' ), 10, 5 ); + + add_filter( 'the_excerpt', array( __CLASS__, 'remove_inline_internal' ) ); + add_filter( 'the_content', array( __CLASS__, 'remove_inline_internal' ) ); + + add_filter( 'the_excerpt', array( __CLASS__, 'autolink_references' ), 11 ); + add_filter( 'the_content', array( __CLASS__, 'autolink_references' ), 11 ); + + add_filter( 'devhub-parameter-type', array( __CLASS__, 'autolink_references' ) ); + + add_filter( 'devhub-format-description', array( __CLASS__, 'autolink_references' ) ); + add_filter( 'devhub-format-description', array( __CLASS__, 'fix_param_hash_formatting' ), 9 ); + add_filter( 'devhub-format-description', array( __CLASS__, 'fix_param_description_html_as_code' ) ); + add_filter( 'devhub-format-description', array( __CLASS__, 'fix_param_description_quotes_to_code' ) ); + add_filter( 'devhub-format-description', array( __CLASS__, 'convert_lists_to_markup' ) ); + + add_filter( 'devhub-format-hash-param-description', array( __CLASS__, 'autolink_references' ) ); + add_filter( 'devhub-format-hash-param-description', array( __CLASS__, 'fix_param_description_parsedown_bug' ) ); + add_filter( 'devhub-format-hash-param-description', array( __CLASS__, 'fix_param_description_quotes_to_code' ) ); + add_filter( 'devhub-format-hash-param-description', array( __CLASS__, 'convert_lists_to_markup' ) ); + + add_filter( 'devhub-function-return-type', array( __CLASS__, 'autolink_references' ) ); + + add_shortcode( 'php', array( __CLASS__, 'do_shortcode_php' ) ); + add_shortcode( 'js', array( __CLASS__, 'do_shortcode_js' ) ); + add_shortcode( 'css', array( __CLASS__, 'do_shortcode_css' ) ); + add_shortcode( 'code', array( __CLASS__, 'do_shortcode_code' ) ); + + add_filter( + 'no_texturize_shortcodes', + function ( $shortcodes ) { + $shortcodes[] = 'php'; + $shortcodes[] = 'js'; + $shortcodes[] = 'css'; + $shortcodes[] = 'code'; + return $shortcodes; + } + ); + } + + /** + * Allows for "Wordpress" just for the excerpt value of the capital_P_dangit function. + * + * WordPress.org has a global output buffer that runs capital_P_dangit() over displayed + * content. For this one field of this one post, circumvent that function to + * to show the lowercase P. + * + * @param string $excerpt The post excerpt. + * @return string + */ + public static function lowercase_P_dangit_just_once( $excerpt ) { + if ( 'wp-parser-function' == get_post_type() && 'capital_P_dangit' == get_the_title() ) { + $excerpt = str_replace( 'Wordpress', 'Wordpress', $excerpt ); + } + + return $excerpt; + } + + /** + * Prevents display of the inline use of {@internal}} as it is not meant to be shown. + * + * @param string $content The post content. + * @param null|string $post_type Optional. The post type. Default null. + * @return string + */ + public static function remove_inline_internal( $content, $post_type = null ) { + // Only attempt a change for a parsed post type with an @internal reference in the text. + if ( DevHub\is_parsed_post_type( $post_type ) && false !== strpos( $content, '{@internal ' ) ) { + $content = preg_replace( '/\{@internal (.+)\}\}/', '', $content ); + } + + return $content; + } + + /** + * Makes phpDoc @see and @link references clickable. + * + * Handles these six different types of links: + * + * - {@link https://en.wikipedia.org/wiki/ISO_8601} + * - {@see WP_Rewrite::$index} + * - {@see WP_Query::query()} + * - {@see esc_attr()} + * - {@see 'pre_get_search_form'} + * - {@link https://codex.wordpress.org/The_Loop Use new WordPress Loop} + * + * Note: Though @see and @link are semantically different in meaning, that isn't always + * the case with use so this function handles them identically. + * + * @param string $content The content. + * @return string + */ + public static function make_doclink_clickable( $content ) { + + // Nothing to change unless a @link or @see reference is in the text. + if ( false === strpos( $content, '{@link ' ) && false === strpos( $content, '{@see ' ) ) { + return $content; + } + + return preg_replace_callback( + '/\{@(?:link|see) ([^\}]+)\}/', + function ( $matches ) { + + $link = $matches[1]; + + // We may have encoded a link, so unencode if so. + // (This would never occur natually.) + if ( 0 === strpos( $link, '<a ' ) ) { + $link = html_entity_decode( $link ); + } + + // Undo links made clickable during initial parsing + if ( 0 === strpos( $link, '<a ' ) ) { + + if ( preg_match( '/^<a .*href=[\'\"]([^\'\"]+)[\'\"]>(.*)<\/a>(.*)$/', $link, $parts ) ) { + $link = $parts[1]; + if ( $parts[3] ) { + $link .= ' ' . $parts[3]; + } + } + + } + + // Link to an external resource. + if ( 0 === strpos( $link, 'http' ) ) { + + $parts = explode( ' ', $link, 2 ); + + // Link without linked text: {@link https://en.wikipedia.org/wiki/ISO_8601} + if ( 1 === count( $parts ) ) { + $url = $text = $link; + } + + // Link with linked text: {@link https://codex.wordpress.org/The_Loop Use new WordPress Loop} + else { + $url = $parts[0]; + $text = $parts[1]; + } + + $link = self::generate_link( $url, $text ); + } + + // Link to an internal resource. + else { + $link = self::link_internal_element( $link ); + } + + return $link; + }, + $content + ); + } + + /** + * Parses and links an internal element if a valid element is found. + * + * @static + * @access public + * + * @param string $link Element string. + * @return string HTML link markup if a valid element was found. + */ + public static function link_internal_element( $link ) { + $url = ''; + + // Exceptions for externally-linked elements. + $exceptions = [ + 'error_log()' => 'https://www.php.net/manual/en/function.error-log.php', + ]; + + // Link exceptions that should actually point to external resources. + if ( ! empty( $exceptions[ $link ] ) ) { + $url = $exceptions[ $link ]; + } + + // Link to class variable: {@see WP_Rewrite::$index} + elseif ( false !== strpos( $link, '::$' ) ) { + // Nothing to link to currently. + } + + // Link to class method: {@see WP_Query::query()} + elseif ( false !== strpos( $link, '::' ) ) { + $url = get_post_type_archive_link( 'wp-parser-class' ) . + str_replace( array( '::', '()' ), array( '/', '' ), $link ); + } + + // Link to hook: {@see 'pre_get_search_form'} + elseif ( 1 === preg_match( '/^(?:\'|(?:‘))([\$\w\-&;]+)(?:\'|(?:’))$/', $link, $hook ) ) { + if ( ! empty( $hook[1] ) ) { + $url = get_post_type_archive_link( 'wp-parser-hook' ) . + sanitize_title_with_dashes( html_entity_decode( $hook[1] ) ) . '/'; + } + } + + // Link to class: {@see WP_Query} + elseif ( + ( in_array( $link, array( + 'wpdb', 'wp_atom_server', 'wp_xmlrpc_server', // Exceptions that start with lowercase letter + 'AtomFeed', 'AtomEntry', 'AtomParser', 'MagpieRSS', 'Requests', 'RSSCache', 'Translations', 'Walker' // Exceptions that lack an underscore + ) ) ) + || + ( 1 === preg_match ( '/^_?[A-Z][a-zA-Z]+_\w+/', $link ) ) // Otherwise, class names start with (optional underscore, then) uppercase and have underscore + ) { + $url = get_post_type_archive_link( 'wp-parser-class' ) . sanitize_key( $link ); + } + + // Link to function: {@see esc_attr()} + else { + $url = get_post_type_archive_link( 'wp-parser-function' ) . + sanitize_title_with_dashes( html_entity_decode( $link ) ); + } + + if ( $url ) { + $link = self::generate_link( $url, $link ); + } + return $link; + } + + /** + * Generates a link given a URL and text. + * + * @param string $url The URL, for the link's href attribute. + * @param string $text The text content of the link. + * @return string The HTML for the link. + */ + public static function generate_link( $url, $text ) { + /** + * Filters the HTML attributes applied to a link's anchor element. + * + * @param array $attrs The HTML attributes applied to the link's anchor element. + * @param string $url The URL for the link. + */ + $attrs = (array) apply_filters( 'devhub-format-link-attributes', array( 'href' => $url ), $url ); + + // Make sure the filter didn't completely remove the href attribute. + if ( empty( $attrs['href'] ) ) { + $attrs['href'] = $url; + } + + $attributes = ''; + foreach ( $attrs as $name => $value ) { + $value = 'href' === $name ? esc_url( $value ) : esc_attr( $value ); + $attributes .= sprintf( ' %s="%s"', esc_attr( $name ), $value ); + } + + return sprintf( '<a%s>%s</a>', $attributes, esc_html( $text ) ); + } + + /** + * Fixes unintended markup generated by Markdown during parsing. + * + * The parser interprets underscores surrounding text as Markdown indicating + * italics. That is never the intention, so undo it. + * + * @param string $content The post content. + * @param null|string $post_type Optional. The post type. Default null. + * @return string + */ + public static function fix_unintended_markdown( $content, $post_type = null ) { + // Only apply to parsed content that have the em tag. + if ( DevHub\is_parsed_post_type( $post_type ) && false !== strpos( $content, '<em>' ) ) { + $content = preg_replace_callback( + '/([^\s])<em>(.+)<\/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 `<em>` and `</em>`. + // In pretty much all cases, the docs mean literal '*' and never emphasis. + $text = str_replace( array( '<em>', '</em>' ), '*', $text ); + + // Undo parser's Markdown conversion of '__' to `<strong>` and `</strong>`. + $text = str_replace( array( '<strong>', '</strong>' ), '__', $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 <pre> or <code> + $nested_in_link = 0; // Keep track of whether this item is already inside a link. + + foreach ( $textarr as $piece ) { + + if ( + preg_match( '|^<code[\s>]|i', $piece ) || + preg_match( '|^<pre[\s>]|i', $piece ) || + preg_match( '|^<script[\s>]|i', $piece ) || + preg_match( '|^<style[\s>]|i', $piece ) + ) { + $nested_code_pre++; + } elseif ( + $nested_code_pre && + ( + '</code>' === strtolower( $piece ) || + '</pre>' === strtolower( $piece ) || + '</script>' === strtolower( $piece ) || + '</style>' === strtolower( $piece ) + ) + ) { + $nested_code_pre--; + } + + // Avoid creating links inside of other links. + if ( preg_match( '|^<a[\s>]|i', $piece ) ) { + $nested_in_link++; + } elseif ( $nested_in_link && ( '</a>' === 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<name> + (?P<class> + (\w+) # Class Name + (::|->|->) # Object reference + (\w+) # Method + (?P<after>\(\)| ) # () or whitespace to terminate. + ) + | + (?P<function>\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( + '<a href="%s" rel="method">%s</a>' . $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( + '<a href="%s" rel="function">%s</a>' . $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}`. + '~' + . '(?<!/)' + . '\b' // Word boundary + . '(' // Primary match grouping + . 'wpdb|wp_atom_server|wp_xmlrpc_server' // Exceptions that start with lowercase letter + . '|AtomFeed|AtomEntry|AtomParser|MagpieRSS|RSSCache|Walker' // Exceptions that lack an underscore + . '|_?[A-Z][a-zA-Z]+_\w+' // Most start with (optional underscore, then) uppercase, has underscore + . ')' // End primary match grouping + . '\b' // Word boundary + . '(?!([<:]|"|\'>))' // 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( + '<a href="%s" rel="class">%s</a>', + 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( '#(<a([ \r\n\t]+[^>]+?>|>))<a [^>]+?>([^>]+?)</a></a>#i', "$1$3</a>", $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( '!<br>\s*!', "<br>\n", $text ); + + // Trim any trailing <br>s on strings. + $text = preg_replace( '/<br>\s*$/s', '', $text ); + + // Add line items + $text = preg_replace( '!^\s*[*-] (.+?)(<br>)*$!m', '<li>$1</li>', $text, -1, $replacements_made ); + + if ( ! $replacements_made ) { + return $text; + } + + // Wrap in a `ul`. + $text = substr_replace( $text, '<ul><li>', strpos( $text, '<li>' ), 4 ); // First instance + $text = substr_replace( $text, '</li></ul>', strrpos( $text, '</li>' ), 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, '{' ) . '<ul class="param-hash">'; + $skip_closing_li = true; + } elseif ( '}' === substr( $description, -1 ) ) { + $description = substr( $description, 0, -1 ) . "</li></ul>\n"; + } + + if ( '@type' != $wordtype ) { + if ( $in_list ) { + $in_list = false; + $new_text .= "</li></ul>\n"; + } + + $new_text .= $part; + } else { + if ( $in_list ) { + $new_text .= '<li>'; + } else { + $new_text .= '<ul class="param-hash"><li>'; + $in_list = true; + } + + // Normalize argument name. + if ( $name === '{' ) { + // No name is specified, generally indicating an array of arrays. + $name = ''; + } else { + // The name is defined as a variable, so remove the leading '$'. + $name = ltrim( $name, '$' ); + } + if ( $name ) { + // Trailing space included for selection purposes. See #meta6585 + $new_text .= "<code>{$name}</code> "; + } + $new_text .= "<span class='type'>{$type}</span><div class='desc'>{$description}"; + if ( ! $skip_closing_li ) { + $new_text .= '</div></li>'; + } + $new_text .= "\n"; + } + } + + if ( $in_list ) { + $new_text .= "</li></ul>\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( + '/`(.+)<code>/' => '<code>$1</code>', + '/<\/code>(.+)`/' => ' <code>$1</code>', + ); + + // Determine if code tags look inverted. + $first_start = strpos( $text, '<code>' ); + $first_end = strpos( $text, '</code>' ); + if ( false !== $first_start && false !== $first_end && $first_end < $first_start ) { + $fixes[ '~</code>(.+)<code>~U' ] = ' <code>$1</code>'; + } + + $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( '/\'(<[^\']+>)\'/', '<code>$1</code>', $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, '<ul class="param-hash">' ) ) { + 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, '</code' ) ) { + $within_code = false; + } elseif ( ! $within_code ) { + $within_code = str_starts_with( $piece, '<code' ); + } + + continue; + } + + // Pipe delimited types inline. + $piece = preg_replace( "/(([\w'\[\]]+\|)+[\w'\[\]]+)/", '<code>$1</code>', $piece, -1 ); + + // Quoted strings. + $piece = preg_replace( "/('[^' ]*')/", '<code>$1</code>', $piece, -1 ); + + // Replace ###PARAM### too. + // Example: http://localhost:8888/reference/hooks/password_change_email/ + $piece = preg_replace( "/((#{2,})\w+\\2)/", '<code>$1</code>', $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( + '<!-- wp:code {"lineNumbers":%3$s} --><pre class="wp-block-code"><code lang="%1$s" class="language-%1$s %4$s">%2$s</code></pre><!-- /wp:code -->', + $lang, + $content, + $show_line_numbers ? 'true' : 'false', + $show_line_numbers ? 'line-numbers' : '' + ) + ); + } + + /** + * Trim off any extra space, including initial new lines. + * Strip out <br /> and <p> added by WordPress. + * + * @param string $content Shortcode content. + * @return string + */ + public static function _trim_code( $content ) { + $content = preg_replace( '/<br \/>/', '', $content ); + $content = preg_replace( '/<\/p>\s*<p>/', "\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 @@ +<?php +/** + * Code Reference configuration and customization of the Handbook plugin. + * + * @package wporg-developer + */ + +/** + * Class to handle handbook configuration and behavior. + */ +class Devhub_Handbooks { + + /** + * Handbook names. + * + * @var array + * @access public + */ + public static $post_types = []; + + /** + * Hidden handbook post types. + * + * Note: Hidden only from users who aren't logged in. + * + * @var array + * @access public + */ + public static $hidden_handbooks = []; + + /** + * Initializer + * + * @access public + */ + public static function init() { + add_filter( 'handbook_label', array( __CLASS__, 'change_handbook_label' ), 10, 2 ); + add_filter( 'handbook_post_type_defaults', array( __CLASS__, 'filter_handbook_post_type_defaults' ), 10, 2 ); + add_filter( 'handbook_post_types', array( __CLASS__, 'filter_handbook_post_types' ) ); + add_action( 'init', array( __CLASS__, 'do_init' ) ); + } + + /** + * Initialization + * + * @access public + */ + public static function do_init() { + add_filter( 'query_vars', array( __CLASS__, 'add_query_vars' ) ); + + add_action( 'template_redirect', array( __CLASS__, 'redirect_hidden_handbooks' ), 1 ); + + add_action( 'pre_get_posts', array( __CLASS__, 'pre_get_posts' ), 9 ); + + add_action( 'after_switch_theme', array( __CLASS__, 'add_roles' ) ); + + add_filter( 'user_has_cap', array( __CLASS__, 'adjust_handbook_editor_caps' ), 11 ); + + add_filter( 'the_content', array( __CLASS__, 'autolink_credits' ) ); + + // Add class to REST API handbook reference pages. + add_filter( 'body_class', array( __CLASS__, 'add_rest_api_handbook_reference_class' ) ); + + // Add the handbook's 'Watch' action link. + if ( class_exists( 'WPorg_Handbook_Watchlist' ) && method_exists( 'WPorg_Handbook_Watchlist', 'display_action_link' ) ) { + add_action( 'wporg_action_links', array( 'WPorg_Handbook_Watchlist', 'display_action_link' ) ); + } + } + + /** + * Add public query vars for handbooks. + * + * @param array $public_query_vars The array of whitelisted query variables. + * @return array Array with public query vars. + */ + public static function add_query_vars( $public_query_vars ) { + $public_query_vars[] = 'current_handbook'; + $public_query_vars[] = 'current_handbook_home_url'; + $public_query_vars[] = 'current_handbook_name'; + + return $public_query_vars; + } + + /** + * Redirects handbooks that should be inaccessible to visitors who aren't logged in. + */ + public static function redirect_hidden_handbooks() { + if ( ! self::$hidden_handbooks || get_current_user_id() || ! function_exists( 'wporg_is_handbook' ) || ! wporg_is_handbook() ) { + return; + } + + if ( in_array( wporg_get_current_handbook(), self::$hidden_handbooks ) ) { + wp_safe_redirect( home_url() ); + exit(); + } + } + + /** + * Add handbook query vars to the current query. + * + * @param \WP_Query $query + */ + public static function pre_get_posts( $query ) { + $query->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( + '<a href="https://profiles.wordpress.org/%s/">@%s</a>', + 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 @@ +<?php +/** + * HTML head markup and customizations. + * + * @package wporg-developer + */ + +/** + * Class to handle HTML head markup. + */ +class DevHub_Head { + + /** + * Initializes module. + */ + public static function init() { + add_action( 'init', array( __CLASS__, 'do_init' ) ); + } + + /** + * Handles adding/removing hooks as needed. + */ + public static function do_init() { + add_filter( 'document_title_parts', array( __CLASS__, 'document_title' ) ); + add_filter( 'document_title_separator', array( __CLASS__, 'document_title_separator' ) ); + add_action( 'wp_head', array( __CLASS__, 'output_head_tags' ), 2 ); + } + + /** + * Filters document title to add context based on what is being viewed. + * + * @param array $parts The document title parts. + * @return array The document title parts. + */ + public static function document_title( $parts ) { + global $wp_query; + + $parts['site'] = __( 'Developer.WordPress.org', 'wporg' ); + $post_type = get_query_var( 'post_type' ); + $sep = '–'; + + if ( is_front_page() || is_feed() ) { + $parts['title'] = 'WordPress Developer Resources'; + return $parts; + } + + if ( is_singular() && ( \DevHub\is_parsed_post_type( $post_type ) ) ) { + // Add post type to title if it's a parsed item. + if ( get_post_type_object( $post_type ) ) { + $parts['title'] .= " $sep " . get_post_type_object( $post_type )->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( + '<meta %s="%s" content="%s" />' . "\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 @@ +<?php + +class DevHub_Advanced_Admin extends DevHub_Docs_Importer { + /** + * Initializes object. + */ + public function init() { + parent::do_init( + 'adv-admin', // 'advanced-administration' makes for too long of a post type slug when appended with '-handbook' + 'advanced-administration', + 'https://raw.githubusercontent.com/WordPress/Advanced-administration-handbook/main/bin/handbook-manifest.json' + ); + + add_filter( 'handbook_label', array( $this, 'change_handbook_label' ), 10, 2 ); + } + + /** + * 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 = __( '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 @@ +<?php + +class DevHub_Block_Editor_Importer extends DevHub_Docs_Importer { + /** + * Initializes object. + */ + public function init() { + $manifest = 'https://raw.githubusercontent.com/WordPress/gutenberg/trunk/docs/manifest.json'; + + parent::do_init( + 'blocks', + 'block-editor', + $manifest + ); + + add_filter( 'template_redirect', array( $this, 'redirects' ), 1 ); + add_filter( 'handbook_label', array( $this, 'change_handbook_label' ), 10, 2 ); + add_filter( 'handbook_display_toc', array( $this, 'disable_toc' ) ); + add_filter( 'get_post_metadata', array( $this, 'fix_markdown_source_meta' ), 10, 4 ); + add_filter( 'wporg_markdown_before_transform', array( $this, 'wporg_markdown_before_transform' ), 10, 2 ); + add_filter( 'wporg_markdown_after_transform', array( $this, 'wporg_markdown_after_transform' ), 10, 2 ); + add_filter( 'wporg_markdown_edit_link', array( $this, 'wporg_markdown_edit_link' ), 10, 2 ); + + add_action( + 'pre_post_update', + function( $post_id, $data ) { + if ( $this->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 <s>some text</s> + $markdown = preg_replace( + '@~~(.*?)~~@', + '<s>$1</s>', + $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 <br>'s, any number of <p...>'s (with no text content), + // an img with 'codeispoetry' in an attribute, followed by any number of </p>'s and <br>'s before EOF + '@(<br\s*/?>)*(<p[^>]*>)*<img[^>]*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 = '<div class="code-tabs">'; + $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 .= "<button data-language='{$splitted_tabs[ $ii ]}' class='$classes'>{$splitted_tabs[ $ii ]}</button>"; + $code_blocks .= "<div class='$code_classes'>{$splitted_tabs[ $ii + 1 ]}</div>"; + } + + $html .= "$code_blocks</div>"; + + 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 @@ +<?php + +class DevHub_Coding_Standards_Importer extends DevHub_Docs_Importer { + /** + * Initializes object. + */ + public function init() { + parent::do_init( + 'wpcs', // 'coding-standards' makes for too long of a post type slug when appended with '-handbook' + 'coding-standards', + 'https://raw.githubusercontent.com/WordPress-Coding-Standards/docs/master/manifest.json' + ); + + add_filter( 'handbook_label', array( $this, 'change_handbook_label' ), 10, 2 ); + add_filter( 'the_content', array( $this, 'fix_double_encoding' ) ); + } + + /** + * 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 = __( '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( + [ '&amp;', '&#039;', '&042;', '&#042;', '&lt;', '&quest;', '&quot;' ], + [ '&', ''', '*', '*', '<', '?', '"' ], + $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 @@ +<?php +use WordPressdotorg\Markdown\Editor; +use WordPressdotorg\Markdown\Importer; + +class DevHub_Docs_Importer extends Importer { + /** + * Singleton instance. + * + * @var static + */ + protected static $instances; + + /** + * The post type for the given docs importer. (Hypenated and without "handbook".) + * + * @var string + */ + protected $post_type; + + /** + * The slug base. + * + * @var string + */ + protected $base; + + /** + * The manifest URL. + * + * E.g. https://raw.githubusercontent.com/WP-API/docs/master/bin/manifest.json + * + * @var string + */ + protected $manifest_url; + + /** + * The cron update interval schedule. + * + * @var string + */ + protected $cron_interval = '15_minutes'; + + /** + * Get the singleton instance, or create if needed. + * + * @return static + */ + public static function instance() { + $class = get_called_class(); + + if ( empty( static::$instances[ $class ] ) ) { + static::$instances[ $class ] = new $class; + } + + return static::$instances[ $class ]; + } + + /** + * Returns the base URL for the handbook. + * + * @return string + */ + protected function get_base() { + return home_url( "{$this->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 @@ +<?php +/** + * Jetpack Compatibility File + * See: https://jetpack.me/ + * + * @package wporg-developer + */ + +/** + * Add theme support for Infinite Scroll. + * See: https://jetpack.me/support/infinite-scroll/ + */ +function wporg_developer_jetpack_setup() { + add_theme_support( 'infinite-scroll', array( + 'container' => '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 @@ +<?php +/** + * Loop Pagination - A WordPress script for creating paginated links on archive-type pages. + * + * The Loop Pagination script was designed to give theme authors a quick way to paginate archive-type + * (archive, search, and blog) pages without having to worry about which of the many plugins a user might + * possibly be using. Instead, they can simply build pagination right into their themes. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @package LoopPagination + * @version 0.3.0 + * @author Justin Tadlock <justin@justintadlock.com> + * @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' => '<nav class="pagination loop-pagination">', + 'after' => '</nav>', + '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 @@ +<?php +/** + * Class to handle editing parsed content. + * + * @package wporg-developer + */ + +/** + * Class to handle editing parsed content for the Function-, Class-, Hook-, + * and Method-editing screens. + */ +class WPORG_Edit_Parsed_Content { + + /** + * Post types array. + * + * Includes the Code Reference post types. + * + * @access public + * @var array + */ + public $post_types; + + /** + * Constructor. + * + * @access public + */ + public function __construct() { + $this->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( '<a href="%1$s">%2$s</a>', esc_url( $src ), apply_filters( 'the_title', $ticket_label ) ); + } else { + $link = sprintf( '<a href="https://core.trac.wordpress.org/newticket">%s</a>', __( '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' ); + ?> + <table class="form-table"> + <tbody> + <tr valign="top"> + <th scope="row"> + <label for="excerpt"><?php _e( 'Parsed Summary:', 'wporg' ); ?></label> + </th> + <td> + <div class="wporg_parsed_readonly <?php echo $ticket ? 'hidden' : ''; ?>"><?php echo apply_filters( 'the_content', $post->post_excerpt ); ?></div> + <textarea rows="2" cols="40" name="excerpt" class="wporg_parsed_content <?php echo $ticket ? '' : 'hidden'; ?>"><?php echo $post->post_excerpt; ?></textarea> + </td> + </tr><!-- .wporg_parsed_content --> + <tr valign="top" data-id="<?php the_id(); ?>"> + <th scope="row"> + <label for="wporg_parsed_content"><?php _e( 'Parsed Description:', 'wporg' ); ?></label> + </th> + <td> + <div class="wporg_parsed_readonly <?php echo $ticket ? 'hidden' : ''; ?>"><?php echo apply_filters( 'the_content', $content ); ?></div> + <div class="wporg_parsed_content <?php echo $ticket ? '' : 'hidden'; ?>"> + <?php wp_editor( + $content, + 'content', + array( + 'media_buttons' => false, + 'tinymce' => false, + 'quicktags' => true, + 'textarea_rows' => 10, + 'textarea_name' => 'content', + ) + ); ?> + </div> + </td> + </tr><!-- .wporg_parsed_content --> + <?php if ( current_user_can( 'manage_options' ) ) : ?> + <tr valign="top" id="ticket_controls"> + <th scope="row"> + <label for="wporg_parsed_ticket"><?php _e( 'Trac Ticket Number:', 'wporg' ); ?></label> + </th> + <td> + <span class="attachment_controls"> + <input type="text" name="wporg_parsed_ticket" id="wporg_parsed_ticket" value="<?php echo esc_attr( $ticket ); ?>" /> + <a href="#attach-ticket" class="button secondary <?php echo $ticket ? 'hidden' : ''; ?>" id="wporg_ticket_attach" name="wporg_ticket_attach" aria-label="<?php esc_attr_e( 'Attach a Core Trac ticket', 'wporg' ); ?>" data-nonce="<?php echo wp_create_nonce( 'wporg-attach-ticket' ); ?>" data-id="<?php the_ID(); ?>"> + <?php esc_attr_e( 'Attach Ticket', 'wporg' ); ?> + </a> + <a href="#detach-ticket" class="button secondary <?php echo $ticket ? '' : 'hidden'; ?>" id="wporg_ticket_detach" name="wporg_ticket_detach" aria-label="<?php esc_attr_e( 'Detach the Trac ticket', 'wporg' ); ?>" data-nonce="<?php echo wp_create_nonce( 'wporg-detach-ticket' ); ?>" data-id="<?php the_ID(); ?>"> + <?php esc_attr_e( 'Detach Ticket', 'wporg' ); ?> + </a> + <span class="spinner"></span> + </span> + <div id="ticket_status"> + <span class="ticket_info_icon <?php echo $ticket ? 'dashicons dashicons-external' : ''; ?>"></span> + <span id="wporg_ticket_info"><em><?php echo $ticket_message; ?></em></span> + </div> + </td> + </tr><!-- #ticket_controls --> + <?php endif; // Admin-only controls ?> + </tbody> + </table> + <?php + } + + /** + * Handle saving parsed content. + * + * Excerpt (short description) saving is handled by core. + * + * @access public + * + * @param int $post_id Post ID. + */ + public function save_post( $post_id ) { + if ( ! empty( $_POST['wporg-parsed-content-nonce'] ) && wp_verify_nonce( $_POST['wporg-parsed-content-nonce'], 'wporg-parsed-content' ) ) { + // No cheaters! + if ( current_user_can( 'manage_options' ) ) { + // Parsed content. + empty( $_POST['wporg_parsed_content'] ) ? delete_post_meta( $post_id, 'wporg_parsed_content' ) : update_post_meta( $post_id, 'wporg_parsed_content', $_POST['wporg_parsed_content'] ); + } + } + } + + /** + * Enqueue JS and CSS on the edit screens for all four post types. + * + * @access public + */ + public function admin_enqueue_scripts() { + // Only enqueue 'wporg-parsed-content' script and styles on Code Reference post type screens. + if ( in_array( get_current_screen()->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( '<a href="%1$s">%2$s</a>', 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 @@ +<?php +/** + * Code Reference parser customizations and tools. + * + * @package wporg-developer + */ + +/** + * Class to handle parser customization and tools. + */ +class DevHub_Parser { + + // Files and directories to skip from parsing. + const SKIP_FROM_PARSING = [ + 'wp-admin/css/', + 'wp-admin/includes/class-ftp', + 'wp-admin/includes/class-pclzip.php', + 'wp-admin/js/', + 'wp-content/', + 'wp-includes/ID3/', + 'wp-includes/PHPMailer/', + 'wp-includes/Requests/', + 'wp-includes/SimplePie/', + 'wp-includes/Text/', + 'wp-includes/blocks/', + 'wp-includes/block-patterns/', + 'wp-includes/block-supports/', + 'wp-includes/certificates/', + 'wp-includes/class-IXR.php', + 'wp-includes/class-json.php', + 'wp-includes/class-phpass.php', + 'wp-includes/class-phpmailer.php', + 'wp-includes/class-pop3.php ', + 'wp-includes/class-simplepie.php', + 'wp-includes/class-smtp.php', + 'wp-includes/class-snoopy.php', + 'wp-includes/class-wp-block-parser.php', + 'wp-includes/compat.php', + 'wp-includes/js/', + 'wp-includes/random_compat/', + 'wp-includes/sodium_compat/', + ]; + + + /** + * Initializer. + */ + public static function init() { + add_action( 'init', [ __CLASS__, 'do_init' ] ); + } + + /** + * Handles adding/removing hooks. + */ + public static function do_init() { + // Skip duplicate hooks. + add_filter( 'wp_parser_skip_duplicate_hooks', '__return_true' ); + + // Skip parsing of certain files. + add_filter( 'wp_parser_pre_import_file', [ __CLASS__, 'should_file_be_imported' ], 10, 2 ); + } + + /** + * Indicates if the given file should be imported for parsing or not. + * + * @param bool $import Should the file be imported? + * @param array $file File data. + * @return bool True if file should be imported, else false. + */ + public static function should_file_be_imported( $import, $file ) { + // Bail early if file is already being skipped. + if ( ! $import ) { + return $import; + } + + // Skip file if it matches anything in the list. + foreach ( self::SKIP_FROM_PARSING as $skip ) { + if ( 0 === strpos( $file['path'], $skip ) ) { + $import = false; + break; + } + } + + return $import; + } + + /** + * Pre-caches source for parsed post types that support showing source code. + * + * By default, source code gets imported and cached as needed. + * + * Primarily intended to be run as a commandline convenience script. + * + * @return bool True on sucess, false on failure. + */ + public static function cache_source_code() { + // Ensure the parsed code source directory exists. + $import_dir = get_option( 'wp_parser_root_import_dir' ); + if ( ! $import_dir || ! file_exists( $import_dir ) ) { + echo "Unable to cache source code; import directory does not exist: {$import_dir}\n"; + return false; + } + + foreach ( \DevHub\get_post_types_with_source_code() as $post_type ) { + $posts = get_posts( array( 'fields' => '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 @@ +<?php +/** + * Code Reference redirects. + * + * @package wporg-developer + */ + +/** + * Class to handle redirects. + */ +class DevHub_Redirects { + + /** + * Initializer + */ + public static function init() { + add_action( 'init', array( __CLASS__, 'do_init' ) ); + } + + /** + * Handles adding/removing hooks to perform redirects as needed. + */ + public static function do_init() { + add_action( 'template_redirect', array( __CLASS__, 'redirect_blog' ) ); + add_action( 'template_redirect', array( __CLASS__, 'redirect_single_search_match' ) ); + add_action( 'template_redirect', array( __CLASS__, 'redirect_handbook' ) ); + add_action( 'template_redirect', array( __CLASS__, 'redirect_resources' ) ); + add_action( 'template_redirect', array( __CLASS__, 'redirect_singularized_handbooks' ), 1 ); + add_action( 'template_redirect', array( __CLASS__, 'redirect_pluralized_reference_post_types' ), 1 ); + add_action( 'template_redirect', array( __CLASS__, 'paginated_home_page_404' ) ); + add_action( 'template_redirect', array( __CLASS__, 'user_note_edit' ) ); + } + + /** + * Redirects a search query with only one result directly to that result. + * + * @globals \WP_Query $wp_query Global WP_Query instance. + */ + public static function redirect_single_search_match() { + global $wp_query; + + if ( is_search() && ! $wp_query->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 @@ +<?php +/** + * Code Reference registrations. + * + * @package wporg-developer + */ + +/** + * Class to register post types, taxonomies, etc. + */ +class DevHub_Registrations { + + /** + * Initializer. + */ + public static function init() { + add_action( 'init', array( __CLASS__, 'do_init' ) ); + } + + /** + * Handles adding/removing hooks. + */ + public static function do_init() { + // Register post types. + self::register_post_types(); + + // Register taxonomies. + self::register_taxonomies(); + + // Register P2P relationships. + add_action( 'p2p_init', array( __CLASS__, 'register_post_relationships' ) ); + + // Include them in Sitemaps. + add_filter( 'jetpack_sitemap_post_types', array( __CLASS__, 'jetpack_sitemap_post_types' ) ); + } + + /** + * Registers post types. + */ + public static function register_post_types() { + $supports = array( + 'comments', + 'custom-fields', + 'editor', + 'excerpt', + 'revisions', + 'title', + ); + + // Functions + register_post_type( 'wp-parser-function', array( + 'has_archive' => '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 @@ +<?php + +class DevHub_REST_API extends DevHub_Docs_Importer { + /** + * Initializes object. + */ + public function init() { + parent::do_init( + 'rest-api', + 'rest-api', + 'https://raw.githubusercontent.com/WP-API/docs/master/bin/manifest.json' + ); + + add_filter( 'handbook_label', array( $this, 'change_handbook_label' ), 10, 2 ); + } + + /** + * 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 = __( '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 @@ +<?php +/** + * Search query customization. + * + * @package wporg-developer + */ + +/** + * Class to handle search query customizations. + */ +class DevHub_Search { + + /** + * Initializer + */ + public static function init() { + add_action( 'init', array( __CLASS__, 'do_init' ) ); + } + + /** + * Handles adding/removing hooks. + */ + public static function do_init() { + add_action( 'pre_get_posts', array( __CLASS__, 'invalid_post_type_filter_404' ), 9 ); + add_action( 'pre_get_posts', array( __CLASS__, 'pre_get_posts' ), 20 ); + add_filter( 'posts_orderby', array( __CLASS__, 'search_posts_orderby' ), 10, 2 ); + add_filter( 'the_posts', array( __CLASS__, 'redirect_empty_search' ), 10, 2 ); + add_filter( 'the_posts', array( __CLASS__, 'rerun_search_without_results' ), 10, 2 ); + + add_filter( 'wporg_noindex_request', array( __CLASS__, 'noindex_query' ) ); + } + + /** + * Noindex post_type-filtered results. + */ + public static function noindex_query( $noindex ) { + if ( isset( $_GET[ 'post_type' ] ) ) { + $noindex = true; + } + + return $noindex; + } + + /* + * Makes request respond as a 404 if request is to filter by an invalid post_type. + * + * @access public + * + * @param WP_Query $query WP_Query object + */ + public static function invalid_post_type_filter_404( $query ) { + // If the main query is being filtered by post_type. + if ( $query->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 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023; + +require_once __DIR__ . '/dashicons.php'; + +/** + * Returns what used to be a php template. + * Since dashicons have been deprecated, this shortcode renders content in a block template. + */ +add_shortcode( + 'dashicons_page', + function() { + + wp_enqueue_style( + 'dashicons-page', + get_stylesheet_directory_uri() . '/stylesheets/page-dashicons.css', + array(), + filemtime( dirname( __DIR__ ) . '/stylesheets/page-dashicons.css' ) + ); + + wp_enqueue_script( + 'dashicons-page', + get_stylesheet_directory_uri() . '/js/page-dashicons.js', + array( 'jquery', 'wp-util' ), + filemtime( dirname( __DIR__ ) . '/js/page-dashicons.js' ) + ); + + $deprecation_notice = sprintf( + '<!-- wp:wporg/notice {"type":"alert"} --> + <div class="wp-block-wporg-notice is-alert-notice"> + <div class="wp-block-wporg-notice__icon"></div> + <div class="wp-block-wporg-notice__content"><p>%s</p></div> + </div> + <!-- /wp:wporg/notice -->', + __( 'The Dashicons project is no longer accepting icon requests. Here’s why: <a href="https://make.wordpress.org/design/2020/04/20/next-steps-for-dashicons/">Next steps for Dashicons</a>.', 'wporg' ) + ); + + ob_start() ?> + + <div id="content-area" <?php body_class( 'dashicons-page' ); ?>> + <?php while ( have_posts() ) : + the_post(); ?> + <main id="main" <?php post_class( 'site-main' ); ?> role="main"> + + <?php echo do_blocks( wp_kses_post( $deprecation_notice ) ); ?> + + <div class="details clear"> + <div id="glyph"></div> + + <div class="entry-content"> + <?php the_content(); ?> + </div><!-- .entry-content --> + + <div class="icon-filter"> + <input placeholder="<?php esc_attr_e( 'Filter…', 'wporg' ); ?>" name="search" id="search" type="text" value="" maxlength="150"> + </div> + + </div> + + <div id="icons"> + <div id="iconlist"> + + <?php + foreach ( \DevHub_Dashicons::get_dashicons() as $group => $group_info ) : + printf( + '<h4 id="%s">%s <a href="#%s" class="anchor"><span aria-hidden="true">#</span><span class="screen-reader-text">%s</span></a></h4>' . "\n\n", + esc_attr( 'icons-' . sanitize_title( $group ) ), + $group_info['label'], + esc_attr( 'icons-' . sanitize_title( $group ) ), + $group_info['label'] + ); + + echo "<!-- {$group} -->\n"; + + echo "<ul>\n"; + foreach ( $group_info['icons'] as $name => $info ) { + printf( + '<li data-keywords="%s" data-code="%s" class="dashicons %s"><span>%s</span></li>' . "\n", + esc_attr( $info['keywords'] ), + esc_attr( $info['code'] ), + esc_attr( $name ), + $info['label'] + ); + } + echo "</ul>\n"; + endforeach; + ?> + + </div> + </div> + + <div id="instructions"> + + <h3><?php _e( 'WordPress Usage', 'wporg' ); ?></h3> + + <p> + <?php printf( + __( 'Admin menu items can be added with <code><a href="%1$s">register_post_type()</a></code> and <code><a href="%2$s">add_menu_page()</a></code>, 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/', + '<code>\'dashicons-<span id="wp-class-example">{icon}</span>\'</code>' + ); ?></p> + + <h4><?php _e( 'Examples', 'wporg' ); ?></h4> + + <p> + <?php printf( + __( 'In <code><a href="%s">register_post_type()</a></code>, set <code>menu_icon</code> in the arguments array.', 'wporg' ), + 'https://developer.wordpress.org/reference/functions/register_post_type/' + ); ?></p> + +<pre><?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 ); +</pre> + + <p> + <?php printf( + __( 'The function <code><a href="%s">add_menu_page()</a></code> 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/' + ); ?></p> + +<pre><?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' + ); +}</pre> + + <h3><?php _e( 'CSS/HTML Usage', 'wporg' ); ?></h3> + + <p><?php _e( "If you want to use dashicons in the admin outside of the menu, there are two helper classes you can use. These are <code>dashicons-before</code> and <code>dashicons</code>, and they can be thought of as setting up dashicons (since you still need your icon's class, too).", 'wporg' ); ?></p> + + <h4><?php _e( 'Examples', 'wporg' ); ?></h4> + + <p><?php _e( 'Adding an icon to a header, with the <code>dashicons-before</code> class. This can be added right to the element with text.', 'wporg' ); ?></p> + +<pre> +<h2 class="dashicons-before dashicons-smiley"><?php _e( 'A Cheerful Headline', 'wporg' ); ?></h2> +</pre> + + <p><?php _e( 'Adding an icon to a header, with the <code>dashicons</code> class. Note that here, you need extra markup specifically for the icon.', 'wporg' ); ?></p> + +<pre> +<h2><span class="dashicons dashicons-smiley"></span> <?php _e( 'A Cheerful Headline', 'wporg' ); ?></h2> +</pre> + + <h3><?php _e( 'Block Usage', 'wporg' ); ?></h3> + + <p><?php _e( 'The block editor supports use of dashicons as block icons and as its own component.', 'wporg' ); ?></p> + + <h4><?php _e( 'Examples', 'wporg' ); ?></h4> + + <p> + <?php printf( + /* translators: %s: URL to Block Editor Handbook for registering a block. */ + __( 'Adding an icon to a block. The <code>registerBlockType</code> function accepts a parameter "icon" which accepts the name of a dashicon. The provided example is truncated. See the <a href="%s">full example</a> 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' + ); ?></p> + +<pre> +registerBlockType( 'gutenberg-examples/example-01-basic-esnext', { + apiVersion: 2, + title: 'Example: Basic (esnext)', + icon: 'universal-access-alt', + category: 'design', + example: {}, + edit() {}, + save() {}, +} ); +</pre> + <p> + <?php printf( + /* translators: %s: URL to handbook page for Dashicon component. */ + __( 'Using an icon as a component. A dedicated <code>Dashicon</code> component is available. See the <a href="%s">related documentation</a> in the Block Editor Handbook.', 'wporg' ), + 'https://developer.wordpress.org/block-editor/reference-guides/components/dashicon/' + ); ?></p> + +<pre> +import { Dashicon } from '@wordpress/components'; + +const MyDashicon = () => ( + <div> + <Dashicon icon="admin-home" /> + <Dashicon icon="products" /> + <Dashicon icon="wordpress" /> + </div> +); +</pre> + + <h3><?php _e( 'Photoshop Usage', 'wporg' ); ?></h3> + + <p><?php _e( 'Use the .OTF version of the font for Photoshop mockups, the web-font versions won\'t work. For most accurate results, pick the "Sharp" font smoothing.', 'wporg' ); ?></p> + + </div><!-- /#instructions --> + + </main><!-- #main --> + + <!-- Required for the Copy Glyph functionality --> + <div id="temp" style="display:none;"></div> + + <script type="text/html" id="tmpl-glyphs"> + <div class="dashicons {{data.cssClass}}"></div> + <div class="info"> + <span><strong>{{data.sectionName}}</strong></span> + <span class="name"><code>{{data.cssClass}}</code></span> + <span class="charCode"><code>{{data.charCode}}</code></span> + <span class="link"><a href='javascript:dashicons.copy( "content: \"\\{{data.attr}}\";", "css" )'><?php _e( 'Copy CSS', 'wporg' ); ?></a></span> + <span class="link"><a href="javascript:dashicons.copy( '{{data.html}}', 'html' )"><?php _e( 'Copy HTML', 'wporg' ); ?></a></span> + <span class="link"><a href="javascript:dashicons.copy( '{{data.glyph}}' )"><?php _e( 'Copy Glyph', 'wporg' ); ?></a></span> + </div> + </script> + + <?php endwhile; // end of the loop. ?> + + </div><!-- #primary --> + + <?php + return ob_get_clean(); + } +); diff --git a/source/wp-content/themes/wpr-developer-2024/inc/shortcodes.php b/source/wp-content/themes/wpr-developer-2024/inc/shortcodes.php new file mode 100644 index 000000000..0a8b14e70 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/inc/shortcodes.php @@ -0,0 +1,109 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023; + +use function DevHub\get_current_version_term; + +/** + * Get the current WordPress version. + */ +add_shortcode( + 'wordpress_version', + function() { + $version = get_current_version_term(); + return $version && ! is_wp_error( $version ) ? substr( $version->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 '<p style="font-style:normal;font-weight:700">' . esc_html__( 'Last updated', 'wporg' ) . '</p>'; + } + 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<repo>[^/]+/[^/]+)/(?P<editblob>blob|edit)/(?P<branchfile>.*)$!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 @@ +<?php + +namespace { + + /** + * Custom template tags for this theme. + * + * Eventually, some of the functionality here could be replaced by core features. + * + * @package wporg-developer + */ + + if ( ! function_exists( 'wporg_developer_paging_nav' ) ) : + /** + * Display navigation to next/previous set of posts when applicable. + * + * @return void + */ + function wporg_developer_paging_nav() { + // Don't print empty markup if there's only one page. + if ( $GLOBALS['wp_query']->max_num_pages < 2 ) { + return; + } + ?> + <nav class="navigation paging-navigation" role="navigation"> + <h1 class="screen-reader-text"><?php _e( 'Posts navigation', 'wporg' ); ?></h1> + + <div class="nav-links"> + + <?php if ( get_next_posts_link() ) : ?> + <div class="nav-previous"><?php next_posts_link( __( '<span class="meta-nav">←</span> Older posts', 'wporg' ) ); ?></div> + <?php endif; ?> + + <?php if ( get_previous_posts_link() ) : ?> + <div class="nav-next"><?php previous_posts_link( __( 'Newer posts <span class="meta-nav">→</span>', 'wporg' ) ); ?></div> + <?php endif; ?> + + </div> + <!-- .nav-links --> + </nav><!-- .navigation --> + <?php + } + endif; + + if ( ! function_exists( 'wporg_developer_post_nav' ) ) : + /** + * Display navigation to next/previous post when applicable. + * + * @return void + */ + function wporg_developer_post_nav() { + // Don't print empty markup if there's nowhere to navigate. + $previous = ( is_attachment() ) ? get_post( get_post()->post_parent ) : get_adjacent_post( false, '', true ); + $next = get_adjacent_post( false, '', false ); + + if ( ! $next && ! $previous ) { + return; + } + ?> + <nav class="navigation post-navigation" role="navigation"> + <h1 class="screen-reader-text"><?php _e( 'Post navigation', 'wporg' ); ?></h1> + + <div class="nav-links"> + + <?php previous_post_link( '%link', _x( '<span class="meta-nav">←</span> %title', 'Previous post link', 'wporg' ) ); ?> + <?php next_post_link( '%link', _x( '%title <span class="meta-nav">→</span>', 'Next post link', 'wporg' ) ); ?> + + </div> + <!-- .nav-links --> + </nav><!-- .navigation --> + <?php + } + endif; + + if ( ! function_exists( 'wporg_developer_get_ordered_notes' ) ) : + /** + * Get contibuted notes ordered by vote + * + * Only the parent notes are ordered by vote count. + * Child notes are added to to the parent note 'child_notes' property. + * Unapproved notes for the current user are included. + * Use `wporg_developer_list_notes()` to display the notes. + * + * @param integer $post_id Optional. Post id to get comments for + * @param array $args Arguments used for get_comments(). + * @return array Array with comment objects + */ + function wporg_developer_get_ordered_notes( $post_id = 0, $args = array() ) { + + $post_id = absint( $post_id ); + + if ( ! $post_id ) { + $post_id = get_the_ID(); + } + + $defaults = array( + 'post__in' => 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 "<section id='feedback-{$comment_id}' class='wporg-has-embedded-code feedback hide-if-js' data-comment-count='{$comment_count}'>\n"; + + // Display child comments. + if ( ! empty( $comment->child_notes ) ) { + echo "<ul class='children'>\n"; + foreach ( $comment->child_notes as $child_note ) { + wporg_developer_user_note( $child_note, $args, 2, $comment->show_editor ); + } + echo "</ul>\n"; + } + + // Add a feedback form for logged in users. + if ( $is_user_content && $is_user_verified ) { + /* Show the feedback editor if we're replying to a note and Javascript is disabled. + * If Javascript is enabled the editor is hidden (as normal) by the 'hide-if-js' class. + */ + $display = $comment->show_editor ? 'show' : 'hide'; + echo DevHub_User_Submitted_Content::wp_editor_feedback( $comment, $display ); + } + echo "</section><!-- .feedback -->\n"; + + // Feedback links to log in, add feedback or show feedback. + echo "<footer class='feedback-links wporg-dot-link-list' >\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 '<a role="button" class="feedback-' . $class . '" href="' . esc_url( $feedback_link ) . '"' . $display . ' rel="nofollow">' . $feedback_text . '</a>'; + } + + // close parent list item + echo "</footer>\n</article><!-- .comment-body -->\n</li>\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' ) ) + ); + ?> + <li id="comment-<?php comment_ID(); ?>" data-comment-id="<?php echo $comment->comment_ID; ?>" <?php comment_class( implode( ' ', $comment_class ) ); ?>> + <article id="div-comment-<?php comment_ID(); ?>" class="comment-body"> + + <?php if ( $is_parent ) : ?> + <a href="#comment-content-<?php echo $comment->comment_ID; ?>" class="screen-reader-text"><?php printf( __( 'Skip to note %d content', 'wporg' ), ++ $note_number ); ?></a> + <header class="comment-meta"> + <div class="comment-author vcard"> + <span class="comment-author-attribution"> + <?php echo wp_kses_post( $note_author ); ?> + </span> + <a class="comment-date" href="<?php echo esc_url( get_comment_link( $comment->comment_ID ) ); ?>"> + <time datetime="<?php comment_time( 'c' ); ?>"> + <?php echo $date; ?> + </time> + </a> + + <?php edit_comment_link( __( 'Edit', 'wporg' ), '<span class="edit-link">— ', '</span>' ); ?> + <?php if ( ! $has_edit_cap && $can_edit_note ) : ?> + — <span class="comment-author-edit-link"> + <!-- Front end edit comment link --> + <a class="comment-edit-link" href="<?php echo site_url( "/reference/comment/edit/{$comment->comment_ID}" ); ?>"><?php _e( 'Edit', 'wporg' ); ?></a> + </span> + <?php endif; ?> + <?php if ( $can_edit_note && $is_edited_note ) : ?> + — <span class="comment-edited"> + <?php _e( 'edited', 'wporg' ); ?> + </span> + <?php endif; ?> + <?php if ( ! $approved ) : ?> + — <span class="comment-awaiting-moderation"><?php _e( 'awaiting moderation', 'wporg' ); ?></span> + <?php endif; ?> + </div> + <?php + if ( $is_voting ) { + DevHub_User_Contributed_Notes_Voting::show_voting(); + } + ?> + </header> + <!-- .comment-metadata --> + <?php endif; ?> + + <div class="wporg-has-embedded-code comment-content" id="comment-content-<?php echo $comment->comment_ID; ?>"> + <?php + if ( $is_parent ) { + comment_text(); + } else { + $text = '<div>' . get_comment_text() . '</div><div>'; + $text .= $note_author; + $text .= ' <a class="comment-date" href="'. esc_url( get_comment_link( $comment->comment_ID ) ) . '">'; + $text .= '<time datetime="' . get_comment_time( 'c' ) . '">' . $date . '</time></a>'; + + if ( $has_edit_cap ) { + // WP admin edit comment link. + $text .= ' — <a class="comment-edit-link" href="' . get_edit_comment_link( $comment->comment_ID ) .'">'; + $text .= __( 'Edit', 'wporg' ) . '</a>'; + } elseif ( $can_edit_note ) { + // Front end edit comment link. + $text .= ' — <a class="comment-edit-link" href="' . site_url( "/reference/comment/edit/{$comment->comment_ID}" ) . '">'; + $text .= __( 'Edit', 'wporg' ) . '</a>'; + } + + if ( $can_edit_note && $is_edited_note ) { + $text .= ' — <span class="comment-edited">' . __( 'edited', 'wporg' ) . '</span>'; + } + + if ( ! $approved ) { + $text .= ' — <span class="comment-awaiting-moderation">' . __( 'awaiting moderation', 'wporg' ) . '</span>'; + } + + $text .= '</div>'; + + echo apply_filters( 'comment_text', $text ); + } + ?> + </div><!-- .comment-content --> + + <?php if ( ! $is_parent ) : ?> + </article> + </li> + <?php endif; ?> + <?php + } + endif; // ends check for wporg_developer_user_note() + + if ( ! function_exists( 'wporg_developer_posted_on' ) ) : + /** + * Prints HTML with meta information for the current post-date/time and author. + */ + function wporg_developer_posted_on() { + $time_string = '<time class="entry-date published" datetime="%1$s">%2$s</time>'; + if ( get_the_time( 'U' ) !== get_the_modified_time( 'U' ) ) { + $time_string .= '<time class="updated" datetime="%3$s">%4$s</time>'; + } + + $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( __( '<span class="posted-on">Posted on %1$s</span><span class="byline"> by %2$s</span>', 'wporg' ), + sprintf( '<a href="%1$s" rel="bookmark">%2$s</a>', + esc_url( get_permalink() ), + $time_string + ), + sprintf( '<span class="author vcard"><a class="url fn n" href="%1$s">%2$s</a></span>', + 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 ) { + ?> + <li <?php comment_class(); ?> id="li-comment-<?php comment_ID(); ?>"> + <article id="comment-<?php comment_ID(); ?>" class="comment"> + + <?php if ( $comment->comment_approved == '0' ) : ?> + <em class="comment-awaiting-moderation"><?php _e( 'Your note is awaiting moderation.', 'wporg' ); ?></em> + <br /> + <?php endif; ?> + + <pre class="user-note-content"><?php echo htmlentities( get_comment_text() ); ?></pre> + + <footer class="comment-meta"> + <div class="comment-author vcard"> + <?php + echo get_avatar( $comment ); + + /* translators: 1: comment author, 2: date and time */ + printf( __( 'Contributed by %1$s on %2$s', 'wporg' ), + sprintf( '<span class="fn">%s</span>', get_comment_author_link() ), + sprintf( '<a href="%1$s"><time datetime="%2$s">%3$s</time></a>', + esc_url( get_comment_link( $comment->comment_ID ) ), + get_comment_time( 'c' ), + /* translators: 1: date, 2: time */ + sprintf( __( '%1$s at %2$s', 'wporg' ), get_comment_date(), get_comment_time() ) + ) + ); + ?> + + <?php edit_comment_link( __( 'Edit', 'wporg' ), '<span class="edit-link">', '</span>' ); ?> + </div> + <!-- .comment-author .vcard --> + + </footer> + + </article> + <!-- #comment-## --> + + <?php + } + + /** + * Get current version of the parsed WP code. + * + * Prefers the 'wp_parser_imported_wp_version' option value set by more + * recent versions of the parser. Failing that, it checks the + * WP_CORE_LATEST_RELEASE constant (set on WordPress.org) though this is not + * guaranteed to be the latest parsed version. Failing that, it uses + * the WP version of the site, unless it isn't a release version, in + * which case a hardcoded value is assumed. + * + * @return string + */ + function get_current_version() { + global $wp_version; + + // Preference for the value saved as an option. + $current_version = get_option( 'wp_parser_imported_wp_version' ); + + // Otherwise, assume the value stored in a constant (which is set on WP.org), if defined. + if ( empty( $current_version ) && defined( 'WP_CORE_LATEST_RELEASE' ) && WP_CORE_LATEST_RELEASE ) { + $current_version = WP_CORE_LATEST_RELEASE; + } + + // Otherwise, use the version of the running WP instance. + if ( empty( $current_version ) ) { + $current_version = $wp_version; + + // However, if the running WP instance appears to not be a release + // version, assume a hardcoded version that is at least valid. + if ( false !== strpos( $current_version, '-' ) ) { + $current_version = '4.6'; + } + } + + return $current_version; + } + + /** + * Get current (latest) version of the parsed WP code as a wp-parser-since + * term object. + * + * By default returns the major version (X.Y.0) term object because minor + * releases rarely add enough, if any, new things to feature. + * + * For development versions, the development suffix ("-beta1", "-RC1") gets removed. + * + * @param boolean $ignore_minor Use the major release version X.Y.0 instead of the actual version X.Y.Z? + * @return object|WP_Error + */ + function get_current_version_term( $ignore_minor = true ) { + echo __METHOD__ . "<br>"; + $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 . "<br>"; + 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 . "<br>"; + $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 '<span class="keyword">class</span> ' . $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[] = ' <nobr><span class="arg-type">' . esc_html( $type ) . '</span> <span class="arg-name">' . esc_html( $arg ) . '</span></nobr>'; + } + + $hook_type = get_hook_type_name( $post_id ); + + $delimiter = false !== strpos( $signature, '$' ) ? '"' : "'"; + $signature = $delimiter . $signature . $delimiter; + $signature = '<span class="hook-func">' . $hook_type . '</span>( ' . $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 .= ' <span class="arg-type">' . $types[ $arg['name'] ] . '</span>'; + } + + if ( ! empty( $arg['name'] ) ) { + $arg_string .= ' <span class="arg-name">' . $arg['name'] . '</span>'; + } + + if ( ! empty( $arg['default'] ) ) { + $arg_string .= ' = <span class="arg-default">' . htmlentities( $arg['default'] ) . "</span>"; + } + + $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( '<span class="%s">%s</span>', $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)">([^<>()]+)[(][)]</a>#', $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 "<span class='return-type'>{$type}</span> $description"; + } else { + return "<span class='return-type'>{$type}</span>"; + } + } + + /** + * 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 = '<span class="since-description">' . \DevHub_Formatting::format_param_description( $meta['description'] ) . '</span>'; + } + + $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( '<a href="%s" rel="external nofollow" class="url">%s</a>', 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 '<code>' . htmlentities( $matches[1] ) . '</code>'; }, + $summary + ); + } + + // Fix https://developer.wordpress.org/reference/functions/get_extended/ + // until the 'more' delimiter in summary is backticked. + $summary = str_replace( array( '<!--', '-->' ), array( '<code><!--', '--></code>' ), $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] . '<code>' . htmlentities( $matches[2] ) . '</code>' . $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( '/<pre><code[^>]*>(.+)<\/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<option$selected value='" . esc_attr( $post_type ) . "'>$label</option>"; + } + + $form = "<form method='get' class='archive-filter-form' action=''>"; + + if ( ! $wp_rewrite->using_permalinks() ) { + // Add taxonomy and term when not using permalinks. + $form .= "<input type='hidden' name='" . esc_attr( $taxonomy ) . "' value='" . esc_attr( $term ) . "'>"; + } + + $form .= "<label for='archive-filter'>"; + $form .= __( 'Filter by type:', 'wporg' ) . ' '; + $form .= '<select name="post_type[]" id="archive-filter">'; + $form .= $options . '</select></label>'; + $form .= "<input class='shiny-blue' type='submit' value='Filter' /></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 @@ +<?php +/** + * Code Reference edit user submitted content (comments, notes, etc). + * + * Allows users to edit top level and child comments from parsed post types. + * + * @package wporg-developer + */ + +/** + * Class to handle editing user submitted content. + */ +class DevHub_User_Content_Edit { + + /** + * Initializer + */ + public static function init() { + // Priority 20 is after this theme and the WP Parser register post types. + add_action( 'init', array( __CLASS__, 'do_init' ), 20 ); + } + + /** + * Handles adding hooks to enable editing comments. + * Adds rewrite rules for editing comments in the front end. + */ + public static function do_init() { + // Add the edit user note rewrite rule + add_rewrite_rule( 'reference/comment/edit/([0-9]{1,})/?$', 'index.php?edit_user_note=$matches[1]', 'top' ); + + // Update comment for edit comment request + self::update_comment(); + + // Add edit_user_note query var for editing. + add_filter( 'query_vars', array( __CLASS__, 'comment_query_var' ) ); + + // Redirect to home page if the edit request is invalid. + add_action( 'template_redirect', array( __CLASS__, 'redirect_invalid_edit_request' ) ); + + // Include the comment edit template. + add_filter( 'template_include', array( __CLASS__, 'template_include' ) ); + + // Set the post_type and post id for use in the comment edit template. + add_action( 'pre_get_posts', array( __CLASS__, 'pre_get_posts' ) ); + } + + /** + * Add the edit_user_note query var to the public query vars. + * + * @param array $query_vars Array with public query vars. + * @return array Public query vars. + */ + public static function comment_query_var( $query_vars ) { + $query_vars[] = 'edit_user_note'; + return $query_vars; + } + + /** + * Update a comment after editing. + */ + public static function update_comment() { + + if ( is_admin() || ( 'POST' !== $_SERVER['REQUEST_METHOD'] ) ) { + return; + } + + $comment_data = wp_unslash( $_POST ); + + $defaults = array( + 'update_user_note', + '_wpnonce', + 'comment_ID', + 'comment', + 'comment_parent', + 'comment_post_ID' + ); + + foreach ( $defaults as $value ) { + if ( ! isset( $comment_data[ $value ] ) ) { + // Return if any of the $_POST keys are missing. + return; + } + } + + $comment = trim( (string) $comment_data['comment'] ); + if ( ! $comment ) { + // Bail and provide a way back to the edit form if a comment is empty. + $msg = __( '<strong>ERROR</strong>: please type a comment.', 'wporg' ); + $args = array( 'response' => 200, 'back_link' => true ); + wp_die( '<p>' . $msg . '</p>', __( 'Comment Submission Failure', 'wporg' ), $args ); + } + + $updated = 0; + $post_id = absint( $comment_data['comment_post_ID'] ); + $comment_id = absint( $comment_data['comment_ID'] ); + $can_user_edit = DevHub\can_user_edit_note( $comment_id ); + $action = 'update_user_note_' . $comment_id; + $nonce = wp_verify_nonce( $comment_data['_wpnonce'], $action ); + + if ( $nonce && $can_user_edit ) { + $comment_data['comment_content'] = $comment; + $updated = wp_update_comment( $comment_data ); + } + + $location = get_permalink( $post_id ); + if ( $location ) { + $query = $updated ? '?updated-note=' . $comment_id : ''; + $query .= '#comment-' . $comment_id; + wp_safe_redirect( $location . $query ); + exit; + } + } + + /** + * Redirects to the home page if the edit request is invalid for the current user. + * + * Redirects if the comment doesn't exist. + * Redirects if the comment is not for a parsed post type. + * Redirects if the current user is not the comment author. + * Redirects if a comment is already approved. + * + * Doesn't redirect for users with the edit_comment capability. + */ + public static function redirect_invalid_edit_request() { + $comment_id = absint( get_query_var( 'edit_user_note' ) ); + if ( ! $comment_id ) { + // Not a query for editing a note. + return; + } + + if ( ! DevHub\can_user_edit_note( $comment_id ) ) { + wp_redirect( home_url( '/reference' ) ); + exit(); + } + } + + /** + * Use the 'comments-edit.php' template for editing comments. + * + * The current user has already been verified in the template_redirect action. + * + * @param string $template Template to include. + * @return string Template to include. + */ + public static function template_include( $template ) { + $comment_id = absint( get_query_var( 'edit_user_note' ) ); + if ( ! $comment_id ) { + // Not a query for editing a note. + return $template; + } + + $comment_template = get_query_template( "comments-edit" ); + if ( $comment_template ) { + $template = $comment_template; + } + + return $template; + } + + /** + * Sets the post and post type for an edit request. + * + * Trows a 404 if the current user can't edit the requested note. + * + * @param WP_Query $query The WP_Query instance (passed by reference) + */ + public static function pre_get_posts( $query ) { + $comment_id = absint( get_query_var( 'edit_user_note' ) ); + + if ( is_admin() || ! ( $query->is_main_query() && $comment_id ) ) { + // Not a query for editing a note. + return; + } + + if ( DevHub\can_user_edit_note( $comment_id ) ) { + $comment = get_comment( $comment_id ); + if ( isset( $comment->comment_post_ID ) ) { + $query->is_singular = true; + $query->is_single = true; + $query->set( 'post_type', get_post_type( $comment->comment_post_ID ) ); + $query->set( 'p', $comment->comment_post_ID ); + + return; + } + } + + // Set 404 if a user can't edit a note. + $query->set_404(); + } + +} // DevHub_User_Content_Edit + +DevHub_User_Content_Edit::init(); + diff --git a/source/wp-content/themes/wpr-developer-2024/inc/user-content-preview.php b/source/wp-content/themes/wpr-developer-2024/inc/user-content-preview.php new file mode 100644 index 000000000..95391726c --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/inc/user-content-preview.php @@ -0,0 +1,112 @@ +<?php +/** + * Code Reference user submitted content preview. + * + * @package wporg-developer + */ + +/** + * Class to handle user submitted content preview. + */ +class DevHub_Note_Preview { + + /** + * Initializer + */ + public static function init() { + add_action( 'init', array( __CLASS__, 'do_init' ) ); + } + + /** + * Handles adding hooks to enable previews. + */ + public static function do_init() { + + // Ajax actions to process preview + add_action( "wp_ajax_preview_comment", array( __CLASS__, "ajax_preview" ) ); + add_action( "wp_ajax_nopriv_preview_comment", array( __CLASS__, "ajax_preview" ) ); + + // Enqueue scripts and styles + add_action( 'wp_enqueue_scripts', array( __CLASS__, 'scripts_and_styles' ), 11 ); + } + + /** + * Enqueues scripts and styles. + */ + public static function scripts_and_styles() { + if ( is_singular() ) { + wp_enqueue_script( + 'wporg-developer-tabs', + get_stylesheet_directory_uri() . '/js/tabs.js', + array( 'jquery' ), + filemtime( dirname( __DIR__ ) . '/js/tabs.js' ), + true + ); + + wp_enqueue_script( + 'wporg-developer-preview', + get_stylesheet_directory_uri() . '/js/user-notes-preview.js', + array( 'jquery', 'wporg-developer-function-reference', 'wporg-developer-tabs' ), + filemtime( dirname( __DIR__ ) . '/js/user-notes-preview.js' ), + true + ); + + wp_localize_script( + 'wporg-developer-preview', + 'wporg_note_preview', + array( + 'ajaxurl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'preview_nonce' ), + 'preview' => __( 'preview note', 'wporg' ), + 'preview_empty' => __( 'Nothing to preview', 'wporg' ), + 'is_admin' => is_admin(), + ) + ); + } + } + + /** + * Ajax action to update the comment preview. + */ + public static function ajax_preview() { + check_ajax_referer( 'preview_nonce', 'preview_nonce' ); + + if ( ! isset( $_POST['preview_comment'] ) ) { + wp_send_json_error( array( 'comment' => '' ) ); + } + + $comment = apply_filters( 'pre_comment_content', $_POST['preview_comment'] ); + $comment = wp_unslash( $comment ); + $comment = apply_filters( 'get_comment_text', $comment ); + $comment = apply_filters( 'comment_text', $comment ); + + wp_send_json_success( array( 'comment' => $comment ) ); + } + + /** + * Captures the comment-preview markup displayed (and populated) below the Add Note form. + * + * @access public + * @static + * + * @return string Comment preview HTML markup. + */ + public static function comment_preview() { + if ( ! class_exists( 'DevHub_Note_Preview' ) ) { + return ''; + } + + ob_start(); + ?> + <div id='comment-preview' class='tab-section comment byuser depth-1 comment-preview' aria-hidden="true"> + <article class='preview-body comment-body'> + <div class='preview-content comment-content'></div> + </article> + </div> + <?php + + return ob_get_clean(); + } +} + +DevHub_Note_Preview::init(); diff --git a/source/wp-content/themes/wpr-developer-2024/inc/user-content-voting.php b/source/wp-content/themes/wpr-developer-2024/inc/user-content-voting.php new file mode 100644 index 000000000..d0c0487f0 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/inc/user-content-voting.php @@ -0,0 +1,563 @@ +<?php +/** + * Code Reference voting for user contributed notes. + * + * Any user can vote once on any user contributed note. + * + * TODO: + * - If a user gets blocked as spam, any vote cast by that user should get removed. + * + * @package wporg-developer + */ + +/** + * Class to handle voting for user contributed notes. + */ +class DevHub_User_Contributed_Notes_Voting { + + /** + * Meta key name for list of all user IDs that submitted an upvote. + * + * @var array + * @access public + */ + public static $meta_upvotes = 'devhub_up_votes'; + + /** + * Meta key name for list of all user IDs that submitted a downvote. + * + * @var array + * @access public + */ + public static $meta_downvotes = 'devhub_down_votes'; + + /** + * Initializer + * + * @access public + */ + public static function init() { + add_action( 'init', array( __CLASS__, 'do_init' ) ); + } + + /** + * Initialization + * + * @access public + */ + public static function do_init() { + // Save a non-AJAX submitted vote. + add_action( 'template_redirect', array( __CLASS__, 'vote_submission' ) ); + + // Save AJAX submitted vote. + add_action( 'wp_ajax_note_vote', array( __CLASS__, 'ajax_vote_submission' ) ); + + // Enqueue scripts and styles. + add_action( 'wp_enqueue_scripts', array( __CLASS__, 'scripts_and_styles' ), 11 ); + } + + /** + * Enqueues scripts and styles. + * + * @access public + */ + public static function scripts_and_styles() { + // Only need to enqueue voting-related resources if there are comments to vote on. + if ( self::user_can_vote() && is_singular() && '0' != get_comments_number() ) { + wp_register_script( + 'wporg-developer-user-notes-voting', + get_stylesheet_directory_uri() . '/js/user-notes-voting.js', + array( 'wp-a11y' ), + filemtime( dirname( __DIR__ ) . '/js/user-notes-voting.js' ), + true + ); + + wp_localize_script( + 'wporg-developer-user-notes-voting', + 'wporg_note_voting', + array( + 'ajaxurl' => admin_url( 'admin-ajax.php' ), + ) + ); + wp_enqueue_script( 'wporg-developer-user-notes-voting' ); + } + } + + /** + * Handles vote submission. + * + * @access public + * + * @return bool True if vote resulted in success or a change. + */ + public static function vote_submission( $redirect = true ) { + $success = false; + + if ( isset( $_REQUEST['comment'] ) && $_REQUEST['comment'] + && isset( $_REQUEST['vote'] ) && $_REQUEST['vote'] && in_array( $_REQUEST['vote'], array( 'down', 'up' ) ) + && isset( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( $_REQUEST['_wpnonce'], 'user-note-vote-' . $_REQUEST['comment'] ) + && self::user_can_vote( get_current_user_id(), $_REQUEST['comment'] ) + ) { + $success = ( 'down' == $_REQUEST['vote'] ) ? + self::vote_down( (int) $_REQUEST['comment'], get_current_user_id() ) : + self::vote_up( (int) $_REQUEST['comment'], get_current_user_id() ); + + // Redirect user back to comment unless this was an AJAX request. + if ( ! isset( $_REQUEST['is_ajax'] ) ) { + wp_redirect( get_comment_link( $_REQUEST['comment'] ) ); + exit(); + } + } + + return $success; + } + + /** + * Handles AJAX vote submission. + * + * @access public + * + * @return int|string Returns 0 on error or no change; else the markup to be used to replace .user-note-voting + */ + public static function ajax_vote_submission() { + check_ajax_referer( 'user-note-vote-' . $_POST['comment'], $_POST['_wpnonce'] ); + + $_REQUEST['is_ajax'] = true; + // If voting succeeded and resulted in a change, send back full replacement + // markup. + if ( self::vote_submission( false ) ) { + self::show_voting( (int) $_POST['comment'] ); + die(); + } + die( 0 ); + } + + /** + * Returns the list of upvotes for a comment. + * + * @access public + * + * @param int $comment_id The comment ID. + * @return array + */ + public static function get_comment_upvotes( $comment_id ) { + return self::get_comment_votes( $comment_id, self::$meta_upvotes ); + } + + /** + * Returns the list of downvotes for a comment. + * + * @access public + * + * @param int $comment_id The comment ID. + * @return array + */ + public static function get_comment_downvotes( $comment_id ) { + return self::get_comment_votes( $comment_id, self::$meta_downvotes ); + } + + /** + * Returns the list of vote for a specific vote type for a comment. + * + * @access protected + * + * @param int $comment_id The comment ID. + * @param string $field + * @return array + */ + protected static function get_comment_votes( $comment_id, $field ) { + $votes = get_comment_meta( $comment_id, $field, true ); + + if ( ! $votes ) { + $votes = array(); + } + + return $votes; + } + + /** + * Determines if the user can vote on user contributed notes. + * + * By default, the only requirements are: + * - the user is logged in. + * - the comment must be approvedUse the + * filter 'devhub_user_can_vote' to configure custom permissions for the + * user and/or the comment. + * + * @access public + * + * @param int $user_id Optional. The user ID. If not defined, assumes current user. + * @param int $comment_id Optional. The comment ID. If not defined, assumes being able to comment generally. + * @return bool True if the user can vote. + */ + public static function user_can_vote( $user_id = '', $comment_id = '' ) { + // If no user specified, assume current user. + if ( ! $user_id ) { + $user_id = get_current_user_id(); + } + + // Must be a user to vote. + if ( ! $user_id ) { + return false; + } + + $can = true; + + // Comment, if provided, must be approved. + if ( $comment_id ) { + $can = ( '1' == get_comment( $comment_id )->comment_approved ); + // Users can't vote on their own comments. + if ( $can && self::is_current_user_note( $comment_id ) ) { + $can = false; + } + } + + return apply_filters( 'devhub_user_can_vote', $can, $user_id, $comment_id ); + } + + /** + * Determines if a note was submitted by the current user. + * + * @param int $comment_id The comment ID, or empty to use current comment. + * @return bool True if the note was submitted by the current user. + */ + public static function is_current_user_note( $comment_id = '' ) { + if ( ! $comment_id ) { + global $comment; + $comment_id = $comment->comment_ID; + } + + $note = get_comment( $comment_id ); + $user_id = get_current_user_id(); + + if ( ! $note || ! $user_id ) { + return false; + } + + if ( (int) $note->user_id === $user_id ) { + return true; + } + + return false; + } + + /** + * Has user upvoted the comment? + * + * @access public + * + * @param int $comment_id The comment ID + * @param int $user_id Optional. The user ID. If not defined, assumes current user. + * @return bool True if the user has upvoted the comment. + */ + public static function has_user_upvoted_comment( $comment_id, $user_id = '' ) { + // If no user specified, assume current user. + if ( ! $user_id ) { + $user_id = get_current_user_id(); + } + + // Must be logged in to have voted. + if ( ! $user_id ) { + return false; + } + + $upvotes = self::get_comment_upvotes( $comment_id ); + + return in_array( $user_id, $upvotes ); + } + + /** + * Has user downvoted the comment? + * + * @access public + * + * @param int $comment_id The comment ID + * @param int $user_id Optional. The user ID. If not defined, assumes current user. + * @return bool True if the user has downvoted the comment. + */ + public static function has_user_downvoted_comment( $comment_id, $user_id = '' ) { + // If no user specified, assume current user. + if ( ! $user_id ) { + $user_id = get_current_user_id(); + } + + // Must be logged in to have voted. + if ( ! $user_id ) { + return false; + } + + $downvotes = self::get_comment_downvotes( $comment_id ); + + return in_array( $user_id, $downvotes ); + } + + /** + * Outputs the voting markup for user contributed note. + * + * @access public + * + * @param int $comment_id The comment ID, or empty to use current comment. + */ + public static function show_voting( $comment_id = '' ) { + if ( ! $comment_id ) { + global $comment; + $comment_id = $comment->comment_ID; + } + + $can_vote = self::user_can_vote( get_current_user_id(), $comment_id ); + $user_note = self::is_current_user_note( $comment_id ); + $logged_in = is_user_logged_in(); + $comment_link = get_comment_link( $comment_id ); + $nonce = wp_create_nonce( 'user-note-vote-' . $comment_id ); + $disabled_str = __( 'Voting for this note is disabled', 'wporg' ); + $cancel_str = __( 'Click to cancel your vote', 'wporg' ); + $log_in_str = __( 'You must log in to vote on the helpfulness of this note', 'wporg' ); + $log_in_url = add_query_arg( 'redirect_to', urlencode( $comment_link ), 'https://login.wordpress.org' ); + + if ( ! $can_vote && $user_note ) { + $disabled_str = __( 'Voting for your own note is disabled', 'wporg' ); + } + + echo '<div class="user-note-voting" data-nonce="' . esc_attr( $nonce ) . '" data-can-vote="' . ( $can_vote ? 'true' : 'false' ) . '">'; + + // Up vote link + $user_upvoted = self::has_user_upvoted_comment( $comment_id ); + if ( $can_vote ) { + $cancel = $user_upvoted ? '. ' . $cancel_str . '.' : ''; + $title = $user_upvoted ? + __( 'You have voted to indicate this note was helpful', 'wporg' ) . $cancel : + __( 'Vote up if this note was helpful', 'wporg' ); + $tag = 'a'; + } else { + $title = ! $logged_in ? $log_in_str : $disabled_str; + $tag = $logged_in ? 'span' : 'a'; + } + echo "<{$tag} " + . 'class="user-note-voting-up' . ( $user_upvoted ? ' user-voted' : '' ) + . '" title="' . esc_attr( $title ) + . '" data-id="' . esc_attr( $comment_id ) + . '" data-vote="up'; + if ( 'a' === $tag ) { + $up_url = $logged_in ? + add_query_arg( + array( + '_wpnonce' => $nonce, + 'comment' => $comment_id, + 'vote' => 'up', + ), + $comment_link + ) : + $log_in_url; + echo '" href="' . esc_url( $up_url ); + } + echo '">'; + echo '<span class="screen-reader-text">' . $title . '</span>'; + echo "</{$tag}>"; + + // Total count + // Don't indicate a like percentage if no one voted. + $title = ( 0 == self::count_votes( $comment_id, 'total' ) ) ? + '' : + sprintf( __( '%s like this', 'wporg' ), self::count_votes( $comment_id, 'like_percentage' ) . '%' ); + $class = ''; + echo '<span ' + . 'class="user-note-voting-count ' . esc_attr( $class ) . '" ' + . 'title="' . esc_attr( $title ) . '">' + . '<span class="screen-reader-text">' . __( 'Vote results for this note: ', 'wporg' ) . '</span>' + . self::count_votes( $comment_id, 'difference' ) + . '</span>'; + + // Down vote link + $user_downvoted = ( $user_upvoted ? false : self::has_user_downvoted_comment( $comment_id ) ); + if ( $can_vote ) { + $cancel = $user_downvoted ? '. ' . $cancel_str . '.' : ''; + $title = $user_downvoted ? + __( 'You have voted to indicate this note was not helpful', 'wporg' ) . $cancel : + __( 'Vote down if this note was not helpful', 'wporg' ); + $tag = 'a'; + } else { + $title = ! $logged_in ? $log_in_str : $disabled_str; + $tag = $logged_in ? 'span' : 'a'; + } + echo "<{$tag} " + . 'class="user-note-voting-down' . ( $user_downvoted ? ' user-voted' : '' ) + . '" title="' . esc_attr( $title ) + . '" data-id="' . esc_attr( $comment_id ) + . '" data-vote="down'; + if ( 'a' === $tag ) { + $down_url = $logged_in ? + add_query_arg( + array( + '_wpnonce' => $nonce, + 'comment' => $comment_id, + 'vote' => 'down', + ), + $comment_link + ) : + $log_in_url; + echo '" href="' . esc_url( $down_url ); + } + echo '">'; + echo '<span class="screen-reader-text">' . $title . '</span>'; + echo "</{$tag}>"; + + echo '</div>'; + } + + /** + * Returns a count relating to the voting. + * + * Supported $type values: + * 'up' : The total number of upvotes + * 'down' : The total number of downvotes + * 'total' : The total number of votes (upvotes + downvotes) + * 'difference' : The difference between upvotes and downvotes (upvotes - downvotes) + * 'like_percentage' : The percentage of total votes that upvoted + * + * @access public + * + * @param string $type The type of count to return. + * @return int The requested count. + */ + public static function count_votes( $comment_id, $type ) { + // The 'up' count is needed in all cases except for 'down'. + if ( 'down' != $type ) { + $up = count( self::get_comment_upvotes( $comment_id ) ); + } + // The 'down' count is needed in all cases except for 'up'. + if ( 'up' != $type ) { + $down = count( self::get_comment_downvotes( $comment_id ) ); + } + + switch ( $type ) { + case 'up': + return $up; + case 'down': + return $down; + case 'total': + return $up + $down; + case 'difference': + return $up - $down; + case 'like_percentage': + $total = $up + $down; + // If no votes have been cast, return 0 to avoid dividing by 0. + if ( 0 == $total ) { + return 0; + } + // More precise, and floatval() will drop ".00" when present + //return floatval( round( ( $up / $total ) * 100, 2 ) ); + // Less precise; rounds to nearest integer + return round( ( $up / $total ) * 100 ); + } + } + + /** + * Records an up vote. + * + * @access public + * + * @param int $comment_id The comment ID + * @param int $user_id Optional. The user ID. Default is current user. + * @return bool Whether the up vote succeed (a new vote or a change in vote). + */ + public static function vote_up( $comment_id, $user_id = '' ) { + return self::vote_handler( $comment_id, $user_id, 'up' ); + } + + /** + * Records a down vote. + * + * @access public + * + * @param int $comment_id The comment ID + * @param int $user_id Optional. The user ID. Default is current user. + * @return bool Whether the down vote succeed (a new vote or a change in vote). + */ + public static function vote_down( $comment_id, $user_id = '' ) { + return self::vote_handler( $comment_id, $user_id, 'down' ); + } + + /** + * Resets the votes for a note.. + * + * The current user needs to have the edit_comment capability for the votes to be deleted. + * + * @access public + * + * @param int $comment_id The comment ID to reset votes for + */ + public static function reset_votes( $comment_id ) { + $comment_id = absint( $comment_id ); + if ( ! $comment_id || ! current_user_can( 'edit_comment', $comment_id ) ) { + return; + } + + delete_comment_meta( $comment_id, self::$meta_upvotes ); + delete_comment_meta( $comment_id, self::$meta_downvotes ); + } + + /** + * Handles abstraction between an up or down vote. + * + * @access protected + * + * @param int $comment_id The comment ID + * @param int $user_id Optional. The user ID. Default is current user. + * @param string $type Optional. 'up' for an up vote, 'down' for a down vote. Default is 'up'. + * @return bool Whether the vote succeed (a new vote or a change in vote). + */ + protected static function vote_handler( $comment_id, $user_id = '', $type = 'up' ) { + if ( ! $user_id ) { + $user_id = get_current_user_id(); + } + + // See if the user can vote on this comment. + $votable = self::user_can_vote( $user_id, $comment_id ); + + if ( ! $votable ) { + return false; + } + + // The difference between an up vote and a down vote is which meta list their + // vote was recorded in. + if ( 'up' == $type ) { + $add_to = self::$meta_upvotes; + $remove_from = self::$meta_downvotes; + } else { + $add_to = self::$meta_downvotes; + $remove_from = self::$meta_upvotes; + } + + // Get list of people who cast the same vote. + $add_to_list = get_comment_meta( $comment_id, $add_to, true ); + + // Remove user from list if recasting the same vote as before. + if ( in_array( $user_id, (array) $add_to_list ) ) { + unset( $add_to_list[ array_search( $user_id, $add_to_list ) ] ); + update_comment_meta( $comment_id, $add_to, $add_to_list ); + return true; + } + + // If the user had previously cast the opposite vote, undo that older vote. + $remove_from_list = (array) get_comment_meta( $comment_id, $remove_from, true ); + if ( in_array( $user_id, $remove_from_list ) ) { + unset( $remove_from_list[ array_search( $user_id, $remove_from_list ) ] ); + update_comment_meta( $comment_id, $remove_from, $remove_from_list ); + } + + // Add user to the list of people casting the identical vote. + if ( $add_to_list ) { + $add_to_list[] = $user_id; + } else { + $add_to_list = array( $user_id ); + } + update_comment_meta( $comment_id, $add_to, $add_to_list ); + + // TODO: Store some value (the like_percentage perhaps) in comment_karma so it can be custom sorted? + + return true; + } + +} // DevHub_User_Contributed_Notes_Voting + +DevHub_User_Contributed_Notes_Voting::init(); diff --git a/source/wp-content/themes/wpr-developer-2024/inc/user-content.php b/source/wp-content/themes/wpr-developer-2024/inc/user-content.php new file mode 100644 index 000000000..c09b3160a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/inc/user-content.php @@ -0,0 +1,489 @@ +<?php +/** + * Code Reference user submitted content (comments, notes, etc). + * + * @package wporg-developer + */ + +/** + * Class to handle user submitted content. + */ +class DevHub_User_Submitted_Content { + + /** + * Initializer + */ + public static function init() { + add_action( 'init', array( __CLASS__, 'do_init' ) ); + } + + /** + * Handles adding/removing hooks to enable comments as user contributed notes. + */ + public static function do_init() { + + // Disable pings. + add_filter( 'pings_open', '__return_false' ); + + // Sets whether submitting notes is open for the user + add_filter( 'comments_open', '\DevHub\\can_user_post_note', 10, 2 ); + + // Enqueue scripts and styles + add_action( 'wp_enqueue_scripts', array( __CLASS__, 'scripts_and_styles' ), 11 ); + + // Force comment registration to be true + add_filter( 'pre_option_comment_registration', '__return_true' ); + + // Force comment moderation to be true + add_filter( 'pre_option_comment_moderation', '__return_true' ); + + // Remove reply to link + add_filter( 'comment_reply_link', '__return_empty_string' ); + + // Remove cancel reply link + add_filter( 'cancel_comment_reply_link', '__return_empty_string' ); + + // Disable smilie conversion + remove_filter( 'comment_text', 'convert_smilies', 20 ); + + // Disable capital_P_dangit + remove_filter( 'comment_text', 'capital_P_dangit', 31 ); + + // Enable shortcodes for comments + add_filter( 'comment_text', 'do_shortcode' ); + + // Customize allowed tags + add_filter( 'wp_kses_allowed_html', array( __CLASS__, 'wp_kses_allowed_html' ), 10, 2 ); + + // Allowed HTML for a new child comment + add_filter( 'preprocess_comment', array( __CLASS__, 'comment_new_allowed_html' ) ); + + // Allowed HTML for an edited child comment (There is no decent hook to filter child comments only) + add_action( 'edit_comment', array( __CLASS__, 'comment_edit_allowed_html' ) ); + + // Adds hidden fields to a comment form for editing + add_filter( 'comment_form_submit_field', array( __CLASS__, 'add_hidden_fields' ), 10, 2 ); + + // Disable the search query in the insert link modal window + add_filter( 'wp_link_query_args', array( __CLASS__, 'disable_link_query' ) ); + + // Disable moderation emails to post author. + add_filter( 'comment_notification_recipients', array( __CLASS__, 'disable_comment_notifications' ), 10, 2 ); + } + + /** + * Customizes the allowed HTML tags for comments. + * + * @param array $allowed List of allowed tags and their allowed attributes. + * @param string $context The context for which to retrieve tags. + * @return array + */ + public static function wp_kses_allowed_html( $allowed, $context ) { + // Unfortunately comments don't have a specific context, so apply to any context not explicitly known. + if ( ! in_array( $context, array( 'post', 'user_description', 'pre_user_description', 'strip', 'entities', 'explicit' ) ) ) { + foreach ( array( 'ol', 'ul', 'li' ) as $tag ) { + $allowed[ $tag ] = array(); + } + } + + return $allowed; + } + + /** + * Updates edited child comments with allowed HTML. + * + * Allowed html is <strong>, <em>, <code> and <a>. + * + * @param int $comment_ID Comment ID. + */ + public static function comment_edit_allowed_html( $comment_ID ) { + + // Get the edited comment. + $comment = get_comment( $comment_ID, ARRAY_A ); + if ( ! $comment ) { + return; + } + + if ( ( 0 === (int) $comment['comment_parent'] ) || empty( $comment['comment_content'] ) ) { + return; + } + + $content = $comment['comment_content']; + $data = self::comment_new_allowed_html( $comment ); + + if ( $data['comment_content'] !== $content ) { + $commentarr = array( + 'comment_ID' => $data['comment_ID'], + 'comment_content' => $data['comment_content'] + ); + + // Update comment content. + wp_update_comment( $commentarr ); + } + } + + /** + * Filter new child comments content with allowed HTML. + * + * Allowed html is <strong>, <em>, <code> and <a>. + * + * @param array $commentdata Array with comment data. + * @return array Array with filtered comment data. + */ + public static function comment_new_allowed_html( $commentdata ) { + $comment_parent = isset( $commentdata['comment_parent'] ) ? absint( $commentdata['comment_parent'] ) : 0; + $comment_content = isset( $commentdata['comment_content'] ) ? trim( $commentdata['comment_content'] ) : ''; + + if ( ( $comment_parent === 0 ) || ! $comment_content ) { + return $commentdata; + } + + $allowed_html = array( + 'a' => array( + 'href' => true, + 'rel' => true, + 'target' => true, + ), + 'em' => array(), + 'strong' => array(), + 'code' => array(), + ); + + $allowed_protocols = array( 'http', 'https' ); + $comment_content = wp_kses( $comment_content, $allowed_html, $allowed_protocols ); + + // Replace newlines with a space. + $commentdata['comment_content'] = preg_replace( '/\r?\n|\r/', ' ', $comment_content ); + + return $commentdata; + } + + /** + * Enqueues scripts and styles. + */ + public static function scripts_and_styles() { + if ( is_singular() ) { + wp_enqueue_script( + 'wporg-developer-function-reference', + get_stylesheet_directory_uri() . '/js/function-reference.js', + array( 'jquery', 'wp-a11y' ), + filemtime( dirname( __DIR__ ) . '/js/function-reference.js' ), + true + ); + wp_localize_script( + 'wporg-developer-function-reference', + 'wporgFunctionReferenceI18n', + array( + 'copy' => __( 'Copy', 'wporg' ), + 'copied' => __( 'Code copied', 'wporg' ), + 'expand' => __( 'Expand code', 'wporg' ), + 'collapse' => __( 'Collapse code', 'wporg' ), + 'sourceFile' => DevHub\get_source_file(), + ) + ); + + wp_enqueue_script( + 'wporg-developer-user-notes', + get_stylesheet_directory_uri() . '/js/user-notes.js', + array( 'jquery', 'quicktags' ), + filemtime( dirname( __DIR__ ) . '/js/user-notes.js' ), + true + ); + + wp_enqueue_script( + 'wporg-developer-user-notes-feedback', + get_stylesheet_directory_uri() . '/js/user-notes-feedback.js', + array( 'jquery', 'quicktags' ), + filemtime( dirname( __DIR__ ) . '/js/user-notes-feedback.js' ), + true + ); + wp_localize_script( + 'wporg-developer-user-notes-feedback', + 'wporg_note_feedback', + array( + 'show' => __( 'Show feedback', 'wporg' ), + 'hide' => __( 'Hide feedback', 'wporg' ), + 'hide_feedback' => __( 'Hide feedback form', 'wporg' ), + 'add_feedback' => __( 'Add feedback', 'wporg' ), + ) + ); + } + } + + /** + * Add hidden fields to the comment form if the context is 'edit' + * + * @param string $submit_field HTML string with the submit button fields. + * @param array $args Arguments for the comment form. + * @return HTML string with the submit button fields. + */ + public static function add_hidden_fields( $submit_field, $args ) { + $context = isset( $args['context'] ) ? $args['context'] : ''; + $comment = isset( $args['comment_edit'] ) ? $args['comment_edit'] : false; + if ( ! ( $comment && ( 'edit' === $context ) ) ) { + return $submit_field; + } + + $comment_id = isset( $comment->comment_ID ) ? $comment->comment_ID : 0; + + return $submit_field . self::get_edit_fields( $comment_id, $instance = 0 ); + } + + /** + * Disable the search query in the insert link modal window. + * + * The search query field in the link modal is hidden with CSS. + * + * @param array $query An array of WP_Query arguments. + */ + public static function disable_link_query( $query ) { + $query['post__in'] = array(0); + return $query; + } + + /** + * Get the comment form arguments by context. + * + * @param WP_Comment|false $comment Comment object or false. Default false. + * @param string $context Context of arguments. Accepts 'edit' or empty string. + * @return array Array with comment form arguments. + */ + public static function comment_form_args( $comment = false, $context = '' ) { + $args = array( + 'logged_in_as' => '', + 'label_submit' => __( 'Submit feedback', 'wporg' ), + 'title_reply' => '', //'Add Example' + 'title_reply_to' => '', + 'title_reply_before' => '', + 'title_reply_after' => '', + ); + + $args['comment_field'] = self::wp_editor_comments( $comment ); + + // Args for adding hidden links after the comment form submit field. + $args['context'] = $context; + $args['comment_edit'] = $comment; + + if ( $comment && ( 'edit' === $context ) ) { + $comment_id = isset( $comment->comment_ID ) ? $comment->comment_ID : 0; + $post_id = isset( $comment->comment_post_ID ) ? $comment->comment_post_ID : 0; + + $args['action'] = get_permalink( $post_id ) . '#comment-' . $comment_id; + } + + if ( class_exists( 'DevHub_Note_Preview' ) ) { + $args['class_form'] = "comment-form tab-container"; + } + + return $args; + } + + /** + * Capture an {@see wp_editor()} instance as the 'User Contributed Notes' comment form. + * + * Uses output buffering to capture the editor instance for use with the {@see comments_form()}. + * + * @param WP_Comment|false $comment Comment object or false. Default false. + * @return string HTML output for the wp_editor-ized comment form. + */ + public static function wp_editor_comments( $comment = false ) { + $content = isset( $comment->comment_content ) ? trim( $comment->comment_content ) : ''; + + // wp_kses() converts htmlspecialchars in source code. + $content = $content ? htmlspecialchars_decode( $content ) : ''; + + ob_start(); + + if ( class_exists( 'DevHub_Note_Preview' ) ) { + echo "<ul class='tablist' style='display: none;'>"; + echo '<li><a href="#comment-form-comment">' . __( 'Write', 'wporg' ) . '</a></li>'; + echo '<li><a href="#comment-preview">' . __( 'Preview', 'wporg' ) . '</a></li></ul>'; + echo '<label class="screen-reader-text" for="comment">' . esc_html__( 'Note', 'wporg' ) . '</label>'; + } + + echo '<div class="comment-form-comment tab-section" id="comment-form-comment">'; + wp_editor( $content, 'comment', array( + 'media_buttons' => false, + 'textarea_name' => 'comment', + 'textarea_rows' => 8, + 'quicktags' => array( + 'buttons' => 'strong,em,ul,ol,li,link' + ), + 'teeny' => true, + 'tinymce' => false, + ) ); + echo '</div>'; + + if ( class_exists( 'DevHub_Note_Preview' ) ) { + echo DevHub_Note_Preview::comment_preview(); + } + + return ob_get_clean(); + } + + /** + * Capture an {@see wp_editor()} instance as the 'User Contributed Notes' feedback form. + * + * Uses output buffering to capture the editor instance. + * + * @param WP_Comment|false $comment Comment object or false. Default false. + * @param string $display Display the editor. Default 'show'. + * @param bool $edit True if the editor used for editing a note. Default false. + * @return string HTML output for the wp_editor-ized feedback form. + */ + public static function wp_editor_feedback( $comment, $display = 'show', $edit = false ) { + + if ( ! ( isset( $comment->comment_ID ) && absint( $comment->comment_ID ) ) ) { + return ''; + } + + $comment_id = absint( $comment->comment_ID ); + + static $instance = 0; + $instance++; + + $display = ( 'hide' === $display ) ? ' style="display: none;"' : ''; + $parent = $comment_id; + $action = site_url( '/wp-comments-post.php' ); + $button_text = __( 'Submit feedback', 'wporg' ); + $post_id = isset( $comment->comment_post_ID ) ? $comment->comment_post_ID : get_the_ID(); + $content = ''; + $form_type = ''; + $note_link = ''; + $class = ''; + + if ( $edit ) { + $content = isset( $comment->comment_content ) ? $comment->comment_content : ''; + $title = __( 'Edit feedback', 'wporg' ); + $form_type = '-edit'; + $button_text = __( 'Edit Note', 'wporg' ); + $post_url = get_permalink( $post_id ); + $action = $post_url ? $post_url . '#comment-' . $comment_id : ''; + $parent = isset( $comment->comment_parent ) ? $comment->comment_parent : 0; + $parent_author = \DevHub\get_note_author( $parent ); + $class = ' edit-feedback-editor'; + + if ( $parent && $post_url && $parent_author ) { + $post_url = $post_url . '#comment-' . $parent; + $parent_note = sprintf( __( 'note %d', 'wporg' ), $parent ); + + /* translators: 1: note, 2: note author name */ + $note_link = sprintf( __( '%1$s by %2$s', 'wporg' ), "<a href='{$post_url}'>{$parent_note}</a>", $parent_author ); + } + } + + $allowed_tags = ''; + foreach ( array( '<strong>', '<em>', '<code>', '<a>' ) as $tag ) { + $allowed_tags .= '<code>' . htmlentities( $tag ) . '</code>, '; + } + + ob_start(); + echo "<div id='feedback-editor-{$comment_id}' class='feedback-editor{$class}'{$display}>\n"; + echo "<form id='feedback-form-{$instance}{$form_type}' class='feedback-form' method='post' action='{$action}' name='feedback-form-{$instance}'>\n"; + printf( + "<label class='screen-reader-text' for='feedback-comment-%s'>%s</label>", + esc_attr( $instance ), + esc_html__( 'Feedback', 'wporg' ), + ); + wp_editor( $content, 'feedback-comment-' . $instance, array( + 'media_buttons' => false, + 'textarea_name' => 'comment', + 'textarea_rows' => 3, + 'quicktags' => array( + 'buttons' => 'strong,em,link' + ), + 'teeny' => true, + 'tinymce' => false, + ) ); + echo '<p class="wp-block-wporg-code-reference-comments-small"><strong>' . __( 'Note', 'wporg' ) . '</strong>: ' . __( 'No newlines allowed', 'wporg' ) . '. '; + printf( __( 'Allowed tags: %s', 'wporg' ), trim( $allowed_tags, ', ' ) ); + echo '</p>'; + echo "<div class='wp-block-button'><input id='submit-{$instance}' class='submit wp-block-button__link wp-element-button' type='submit' value='{$button_text}' name='submit-{$instance}'></div>"; + echo self::get_editor_rules( 'feedback', $note_link ); + echo "<input type='hidden' name='comment_post_ID' value='{$post_id}' id='comment_post_ID-{$instance}' />\n"; + echo "<input type='hidden' name='comment_parent' id='comment_parent-{$instance}' value='{$parent}' />\n"; + + if ( $edit ) { + echo self::get_edit_fields( $comment_id, $instance ); + } + + echo "</p>\n</form>\n</div><!-- #feedback-editor-{$comment_id} -->\n"; + return ob_get_clean(); + } + + /** + * Get the rules list for the comment form. + * + * @param string $context Accepts 'feedback' or empty sring. + * @param string $note_link Link to parent note. + * @return string Editor rules. + */ + public static function get_editor_rules( $context = '', $note_link = '' ) { + if ( 'feedback' === $context ) { + if ( $note_link ) { + $rules[] = sprintf( __( 'Use this form to report errors or to add additional information to %s.', 'wporg' ), $note_link ); + } else { + $rules[] = __( 'Feedback is part of documentation. Use this form to report errors or to add additional information to this note', 'wporg' ); + } + } else { + $rules[] = __( 'Notes should supplement code reference entries, for example examples, tips, explanations, use-cases, and best practices.', 'wporg' ); + $rules[] = __( 'Feedback can be to report errors or omissions with the documentation on this page. Feedback will not be publicly posted.', 'wporg' ); + } + + $rules[] = __( 'Notes and feedback must be written in English.', 'wporg' ); + $rules[] = __( 'This form is not for support requests, discussions, spam, bug reports, complaints, or self-promotion.', 'wporg' ); + $rules[] = __('Any notes not meeting these requirements will be removed by the moderation team.', 'wporg' ); + $rules[] = __('In the editing area the Tab key indents text. To move below this area by pressing Tab, press the Esc key followed by the Tab key. In some cases the Esc key will need to be pressed twice before the Tab key will allow you to continue.', 'wporg'); + + $rules[] = sprintf( + /* translators: 1: GFDL link */ + __( 'Any notes not meeting these requirements will be removed by the moderation team. All contributions are licensed under %s and are moderated before appearing on the site.', 'wporg' ), + '<a href="https://gnu.org/licenses/fdl.html">GFDL</a>' + ); + + return sprintf( '<ul class="wp-block-wporg-code-reference-comments-rules">%s</ul>', + implode( '', array_map( function( $rule ) { + return "<li>{$rule}</li>"; + }, $rules ) ) + ); + } + + /** + * Get the hidden input fields HTML used for editing a note. + * + * @param int $comment_id Comment ID. + * @param integer $instance Comment form instance number used in HTML id's. + * @return string Hidden input fields HTML. + */ + public static function get_edit_fields( $comment_id, $instance = 0 ) { + $fields = "<input type='hidden' name='comment_ID' id='comment_ID-{$instance}' value='{$comment_id}' />\n"; + $fields .= "<input type='hidden' name='update_user_note' id='update_user_note-{$instance}' value='1' />\n"; + $fields .= wp_nonce_field( 'update_user_note_' . $comment_id, '_wpnonce', true, false ); + + return $fields; + } + + /** + * Disables moderation emails to post author for parsed post types. + * + * Parsed post types aren't legitimately authored by any given user, so whoever + * is assigned does not need these notifications. A team of moderators are + * responsible for handling submitted comments, most of which start off in + * moderation. + * + * @param string[] $emails An array of email addresses to receive a comment notification. + * @param int $comment_id The comment ID. + */ + public static function disable_comment_notifications( $emails, $comment_id ) { + $comment = get_comment( $comment_id ); + + if ( $comment && DevHub\is_parsed_post_type( get_post_type( $comment->comment_post_ID ) ) ) { + $emails = []; + } + + return $emails; + } + +} // DevHub_User_Submitted_Content + +DevHub_User_Submitted_Content::init(); diff --git a/source/wp-content/themes/wpr-developer-2024/js/autocomplete.js b/source/wp-content/themes/wpr-developer-2024/js/autocomplete.js new file mode 100644 index 000000000..b99bd19a1 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/js/autocomplete.js @@ -0,0 +1,130 @@ +/** + * Autocomplete JS. + * + * Uses the Awesomplete widget from Lea Verou. + * https://leaverou.github.io/awesomplete/ + */ + +( function( $ ) { + + if ( typeof autocomplete === 'undefined' ) { + return; + } + + var form = $( '#wporg-search .wp-block-search' ); + if ( ! form.length ) { + return; + } + + var searchfield = $( 'input[type="search"]', form ), + processing = false, + search = '', + autocompleteResults = {}; + + var awesome = new Awesomplete( searchfield.get( 0 ), { + maxItems: 9999, + minChars: 3, + filter: function( text, input ) { + // Filter autocomplete matches + + // Full match + if ( Awesomplete.FILTER_CONTAINS( text, input ) ) { + // mark + return true; + } + + // Replace - _ and whitespace with a single space + var _text = Awesomplete.$.regExpEscape( text.trim().toLowerCase().replace( /[\_\-\s]+/g, ' ' ) ); + var _input = Awesomplete.$.regExpEscape( input.trim().toLowerCase().replace( /[\_\-\s]+/g, ' ' ) ); + + // Matches with with single spaces between words + if ( Awesomplete.FILTER_CONTAINS( _text, _input ) ) { + return true; + } + + _input = _input.split( " " ); + var words = _input.length; + + if ( 1 >= words ) { + return false; + } + + // Partial matches + var partials = 0; + for ( i = 0; i < words; i++ ) { + if ( _text.indexOf( _input[ i ].trim() ) !== -1 ) { + partials++; + } + } + + if ( partials === words ) { + return true; + } + + return false; + }, + replace: function( text ) { + searchfield.val( text ); + + if ( text in autocompleteResults ) { + window.location = autocompleteResults[ text ]; + } + } + } ); + + // On input event for the search field. + searchfield.on( 'input.autocomplete', function( e ) { + + // Update the autocomlete list: + // if there are more than 2 characters + // and it's not already processing an Ajax request + if ( !processing && $( this ).val().trim().length > 2 ) { + search = $( this ).val(); + autocomplete_update(); + } + } ); + + + /** + * Updates the autocomplete list + */ + function autocomplete_update() { + + processing = true; + + var data = { + action: "autocomplete", + data: form.serialize(), + nonce: autocomplete.nonce, + post_type: autocomplete.post_type + }; + + $.post( autocomplete.ajaxurl, data ) + .done( function( response ) { + + if ( typeof response.success === 'undefined' ) { + return false; + } + + if ( typeof response.data === 'undefined' ) { + return false; + } + + if ( response.success === true && Object.values( response.data.posts ).length ) { + autocompleteResults = response.data.posts; + + // Update the autocomplete list + awesome.list = Object.keys( response.data.posts ); + } + } ) + .always( function() { + processing = false; + + // Check if the search was updated during processing + if ( search !== searchfield.val() ) { + searchfield.trigger( "input.autocomplete" ); + } + } ); + } + +} )( jQuery ); \ No newline at end of file diff --git a/source/wp-content/themes/wpr-developer-2024/js/awesomplete.js b/source/wp-content/themes/wpr-developer-2024/js/awesomplete.js new file mode 100644 index 000000000..a0eda9caf --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/js/awesomplete.js @@ -0,0 +1,394 @@ +/** + * Simple, lightweight, usable local autocomplete library for modern browsers + * Because there weren’t enough autocomplete scripts in the world? Because I’m completely insane and have NIH syndrome? Probably both. :P + * @author Lea Verou http://leaverou.github.io/awesomplete + * MIT license + */ + +(function () { + +var _ = function (input, o) { + var me = this; + + // Setup + + this.input = $(input); + this.input.setAttribute("autocomplete", "off"); + this.input.setAttribute("aria-autocomplete", "list"); + + o = o || {}; + + configure.call(this, { + minChars: 2, + maxItems: 10, + autoFirst: false, + filter: _.FILTER_CONTAINS, + sort: _.SORT_BYLENGTH, + item: function (text, input) { + var html = input === '' ? text : text.replace(RegExp($.regExpEscape(input.trim()), "gi"), "<mark>$&</mark>"); + return $.create("li", { + innerHTML: html, + "aria-selected": "false" + }); + }, + replace: function (text) { + this.input.value = text; + } + }, o); + + this.index = -1; + + // Create necessary elements + + this.container = $.create("div", { + className: "awesomplete", + around: input + }); + + this.ul = $.create("ul", { + hidden: "hidden", + inside: this.container + }); + + this.status = $.create("span", { + className: "visually-hidden", + role: "status", + "aria-live": "assertive", + "aria-relevant": "additions", + inside: this.container + }); + + // Bind events + + $.bind(this.input, { + "input": this.evaluate.bind(this), + "blur": this.close.bind(this), + "keydown": function(evt) { + var c = evt.keyCode; + + // If the dropdown `ul` is in view, then act on keydown for the following keys: + // Enter / Esc / Up / Down + if(me.opened) { + if (c === 13 && me.selected) { // Enter + evt.preventDefault(); + me.select(); + } + else if (c === 27) { // Esc + me.close(); + } + else if (c === 38 || c === 40) { // Down/Up arrow + evt.preventDefault(); + me[c === 38? "previous" : "next"](); + } + } + } + }); + + $.bind(this.input.form, {"submit": this.close.bind(this)}); + + $.bind(this.ul, {"mousedown": function(evt) { + var li = evt.target; + + if (li !== this) { + + while (li && !/li/i.test(li.nodeName)) { + li = li.parentNode; + } + + if (li && evt.button === 0) { // Only select on left click + evt.preventDefault(); + me.select(li, evt); + } + } + }}); + + if (this.input.hasAttribute("list")) { + this.list = "#" + this.input.getAttribute("list"); + this.input.removeAttribute("list"); + } + else { + this.list = this.input.getAttribute("data-list") || o.list || []; + } + + _.all.push(this); +}; + +_.prototype = { + set list(list) { + if (Array.isArray(list)) { + this._list = list; + } + else if (typeof list === "string" && list.indexOf(",") > -1) { + this._list = list.split(/\s*,\s*/); + } + else { // Element or CSS selector + list = $(list); + + if (list && list.children) { + this._list = slice.apply(list.children).map(function (el) { + return el.textContent.trim(); + }); + } + } + + if (document.activeElement === this.input) { + this.evaluate(); + } + }, + + get selected() { + return this.index > -1; + }, + + get opened() { + return this.ul && this.ul.getAttribute("hidden") == null; + }, + + close: function () { + this.ul.setAttribute("hidden", ""); + this.index = -1; + + $.fire(this.input, "awesomplete-close"); + }, + + open: function () { + this.ul.removeAttribute("hidden"); + + if (this.autoFirst && this.index === -1) { + this.goto(0); + } + + $.fire(this.input, "awesomplete-open"); + }, + + next: function () { + var count = this.ul.children.length; + + this.goto(this.index < count - 1? this.index + 1 : -1); + }, + + previous: function () { + var count = this.ul.children.length; + + this.goto(this.selected? this.index - 1 : count - 1); + }, + + // Should not be used, highlights specific item without any checks! + goto: function (i) { + var lis = this.ul.children; + + if (this.selected) { + lis[this.index].setAttribute("aria-selected", "false"); + } + + this.index = i; + + if (i > -1 && lis.length > 0) { + lis[i].setAttribute("aria-selected", "true"); + this.status.textContent = lis[i].textContent; + } + + $.fire(this.input, "awesomplete-highlight"); + }, + + select: function (selected, originalEvent) { + selected = selected || this.ul.children[this.index]; + + if (selected) { + var prevented; + + $.fire(this.input, "awesomplete-select", { + text: selected.textContent, + preventDefault: function () { + prevented = true; + }, + originalEvent: originalEvent + }); + + if (!prevented) { + this.replace(selected.textContent); + this.close(); + $.fire(this.input, "awesomplete-selectcomplete"); + } + } + }, + + evaluate: function() { + var me = this; + var value = this.input.value; + + if (value.length >= this.minChars && this._list.length > 0) { + this.index = -1; + // Populate list with options that match + this.ul.innerHTML = ""; + + this._list + .filter(function(item) { + return me.filter(item, value); + }) + .sort(this.sort) + .every(function(text, i) { + me.ul.appendChild(me.item(text, value)); + + return i < me.maxItems - 1; + }); + + if (this.ul.children.length === 0) { + this.close(); + } else { + this.open(); + } + } + else { + this.close(); + } + } +}; + +// Static methods/properties + +_.all = []; + +_.FILTER_CONTAINS = function (text, input) { + return RegExp($.regExpEscape(input.trim()), "i").test(text); +}; + +_.FILTER_STARTSWITH = function (text, input) { + return RegExp("^" + $.regExpEscape(input.trim()), "i").test(text); +}; + +_.SORT_BYLENGTH = function (a, b) { + if (a.length !== b.length) { + return a.length - b.length; + } + + return a < b? -1 : 1; +}; + +// Private functions + +function configure(properties, o) { + for (var i in properties) { + var initial = properties[i], + attrValue = this.input.getAttribute("data-" + i.toLowerCase()); + + if (typeof initial === "number") { + this[i] = parseInt(attrValue); + } + else if (initial === false) { // Boolean options must be false by default anyway + this[i] = attrValue !== null; + } + else if (initial instanceof Function) { + this[i] = null; + } + else { + this[i] = attrValue; + } + + if (!this[i] && this[i] !== 0) { + this[i] = (i in o)? o[i] : initial; + } + } +} + +// Helpers + +var slice = Array.prototype.slice; + +function $(expr, con) { + return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; +} + +function $$(expr, con) { + return slice.call((con || document).querySelectorAll(expr)); +} + +$.create = function(tag, o) { + var element = document.createElement(tag); + + for (var i in o) { + var val = o[i]; + + if (i === "inside") { + $(val).appendChild(element); + } + else if (i === "around") { + var ref = $(val); + ref.parentNode.insertBefore(element, ref); + element.appendChild(ref); + } + else if (i in element) { + element[i] = val; + } + else { + element.setAttribute(i, val); + } + } + + return element; +}; + +$.bind = function(element, o) { + if (element) { + for (var event in o) { + var callback = o[event]; + + event.split(/\s+/).forEach(function (event) { + element.addEventListener(event, callback); + }); + } + } +}; + +$.fire = function(target, type, properties) { + var evt = document.createEvent("HTMLEvents"); + + evt.initEvent(type, true, true ); + + for (var j in properties) { + evt[j] = properties[j]; + } + + target.dispatchEvent(evt); +}; + +$.regExpEscape = function (s) { + return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); +} + +// Initialization + +function init() { + $$("input.awesomplete").forEach(function (input) { + new _(input); + }); +} + +// Are we in a browser? Check for Document constructor +if (typeof Document !== "undefined") { + // DOM already loaded? + if (document.readyState !== "loading") { + init(); + } + else { + // Wait for it + document.addEventListener("DOMContentLoaded", init); + } +} + +_.$ = $; +_.$$ = $$; + +// Make sure to export Awesomplete on self when in a browser +if (typeof self !== "undefined") { + self.Awesomplete = _; +} + +// Expose Awesomplete as a CJS module +if (typeof module === "object" && module.exports) { + module.exports = _; +} + +return _; + +}()); diff --git a/source/wp-content/themes/wpr-developer-2024/js/awesomplete.min.js b/source/wp-content/themes/wpr-developer-2024/js/awesomplete.min.js new file mode 100644 index 000000000..ded622e4b --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/js/awesomplete.min.js @@ -0,0 +1,10 @@ +// Awesomplete - Lea Verou - MIT license +(function(){function m(a,b){for(var c in a){var g=a[c],e=this.input.getAttribute("data-"+c.toLowerCase());this[c]="number"===typeof g?parseInt(e):!1===g?null!==e:g instanceof Function?null:e;this[c]||0===this[c]||(this[c]=c in b?b[c]:g)}}function d(a,b){return"string"===typeof a?(b||document).querySelector(a):a||null}function h(a,b){return k.call((b||document).querySelectorAll(a))}function l(){h("input.awesomplete").forEach(function(a){new f(a)})}var f=function(a,b){var c=this;this.input=d(a);this.input.setAttribute("autocomplete", +"off");this.input.setAttribute("aria-autocomplete","list");b=b||{};m.call(this,{minChars:2,maxItems:10,autoFirst:!1,filter:f.FILTER_CONTAINS,sort:f.SORT_BYLENGTH,item:function(a,b){var c=""===b?a:a.replace(RegExp(d.regExpEscape(b.trim()),"gi"),"<mark>$&</mark>");return d.create("li",{innerHTML:c,"aria-selected":"false"})},replace:function(a){this.input.value=a}},b);this.index=-1;this.container=d.create("div",{className:"awesomplete",around:a});this.ul=d.create("ul",{hidden:"hidden",inside:this.container}); +this.status=d.create("span",{className:"visually-hidden",role:"status","aria-live":"assertive","aria-relevant":"additions",inside:this.container});d.bind(this.input,{input:this.evaluate.bind(this),blur:this.close.bind(this),keydown:function(a){var b=a.keyCode;if(c.opened)if(13===b&&c.selected)a.preventDefault(),c.select();else if(27===b)c.close();else if(38===b||40===b)a.preventDefault(),c[38===b?"previous":"next"]()}});d.bind(this.input.form,{submit:this.close.bind(this)});d.bind(this.ul,{mousedown:function(a){var b= +a.target;if(b!==this){for(;b&&!/li/i.test(b.nodeName);)b=b.parentNode;b&&0===a.button&&(a.preventDefault(),c.select(b,a))}}});this.input.hasAttribute("list")?(this.list="#"+this.input.getAttribute("list"),this.input.removeAttribute("list")):this.list=this.input.getAttribute("data-list")||b.list||[];f.all.push(this)};f.prototype={set list(a){Array.isArray(a)?this._list=a:"string"===typeof a&&-1<a.indexOf(",")?this._list=a.split(/\s*,\s*/):(a=d(a))&&a.children&&(this._list=k.apply(a.children).map(function(a){return a.textContent.trim()})); +document.activeElement===this.input&&this.evaluate()},get selected(){return-1<this.index},get opened(){return this.ul&&null==this.ul.getAttribute("hidden")},close:function(){this.ul.setAttribute("hidden","");this.index=-1;d.fire(this.input,"awesomplete-close")},open:function(){this.ul.removeAttribute("hidden");this.autoFirst&&-1===this.index&&this["goto"](0);d.fire(this.input,"awesomplete-open")},next:function(){this["goto"](this.index<this.ul.children.length-1?this.index+1:-1)},previous:function(){var a= +this.ul.children.length;this["goto"](this.selected?this.index-1:a-1)},"goto":function(a){var b=this.ul.children;this.selected&&b[this.index].setAttribute("aria-selected","false");this.index=a;-1<a&&0<b.length&&(b[a].setAttribute("aria-selected","true"),this.status.textContent=b[a].textContent);d.fire(this.input,"awesomplete-highlight")},select:function(a,b){if(a=a||this.ul.children[this.index]){var c;d.fire(this.input,"awesomplete-select",{text:a.textContent,preventDefault:function(){c=!0},originalEvent:b}); +c||(this.replace(a.textContent),this.close(),d.fire(this.input,"awesomplete-selectcomplete"))}},evaluate:function(){var a=this,b=this.input.value;b.length>=this.minChars&&0<this._list.length?(this.index=-1,this.ul.innerHTML="",this._list.filter(function(c){return a.filter(c,b)}).sort(this.sort).every(function(c,d){a.ul.appendChild(a.item(c,b));return d<a.maxItems-1}),0===this.ul.children.length?this.close():this.open()):this.close()}};f.all=[];f.FILTER_CONTAINS=function(a,b){return RegExp(d.regExpEscape(b.trim()), +"i").test(a)};f.FILTER_STARTSWITH=function(a,b){return RegExp("^"+d.regExpEscape(b.trim()),"i").test(a)};f.SORT_BYLENGTH=function(a,b){return a.length!==b.length?a.length-b.length:a<b?-1:1};var k=Array.prototype.slice;d.create=function(a,b){var c=document.createElement(a),g;for(g in b){var e=b[g];"inside"===g?d(e).appendChild(c):"around"===g?(e=d(e),e.parentNode.insertBefore(c,e),c.appendChild(e)):g in c?c[g]=e:c.setAttribute(g,e)}return c};d.bind=function(a,b){if(a)for(var c in b){var d=b[c];c.split(/\s+/).forEach(function(b){a.addEventListener(b, +d)})}};d.fire=function(a,b,c){var d=document.createEvent("HTMLEvents");d.initEvent(b,!0,!0);for(var e in c)d[e]=c[e];a.dispatchEvent(d)};d.regExpEscape=function(a){return a.replace(/[-\\^$*+?.()|[\]{}]/g,"\\$&")};"undefined"!==typeof Document&&("loading"!==document.readyState?l():document.addEventListener("DOMContentLoaded",l));f.$=d;f.$$=h;"undefined"!==typeof self&&(self.Awesomplete=f);"object"===typeof module&&module.exports&&(module.exports=f);return f})(); \ No newline at end of file diff --git a/source/wp-content/themes/wpr-developer-2024/js/code-tabs.js b/source/wp-content/themes/wpr-developer-2024/js/code-tabs.js new file mode 100644 index 000000000..8900bd487 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/js/code-tabs.js @@ -0,0 +1,104 @@ +const init = () => { + const tabLists = document.querySelectorAll( '.code-tabs' ); + + if ( ! tabLists.length ) { + return; + } + + tabLists.forEach( ( tabList ) => { + const buttons = tabList.querySelectorAll( '.code-tab' ); + const panels = tabList.querySelectorAll( '.code-tab-block' ); + + if ( ! buttons.length || ! panels.length || buttons.length !== panels.length ) { + return; + } + + tabList.setAttribute( 'role', 'tablist' ); + // Block button styles require a parent with `class*="wp-block"`. + tabList.classList.add( 'wp-block', 'is-small', 'is-style-toggle' ); + + buttons.forEach( ( button ) => { + const isActive = button.classList.contains( 'is-active' ); + const buttonId = window.crypto?.randomUUID?.(); + const panelId = window.crypto?.randomUUID?.(); + const tabName = button.getAttribute( 'data-language' ); + const relatedPanel = Array.from( panels ).find( ( panel ) => panel.classList.contains( tabName ) ); + + button.classList.add( 'wp-block-button__link' ); + button.setAttribute( 'role', 'tab' ); + button.setAttribute( 'aria-selected', isActive ); + button.setAttribute( 'tabindex', isActive ? '0' : '-1' ); + button.setAttribute( 'id', buttonId ); + + if ( buttonId && panelId && relatedPanel ) { + button.setAttribute( 'aria-controls', panelId ); + relatedPanel.setAttribute( 'aria-labelledby', buttonId ); + relatedPanel.setAttribute( 'id', panelId ); + } + } ); + + panels.forEach( ( panel ) => { + panel.setAttribute( 'role', 'tabpanel' ); + } ); + + const maybeActivateTab = ( currentButton ) => { + if ( currentButton.classList.contains( 'is-active' ) ) { + return; + } + + const panelId = currentButton.getAttribute( 'aria-controls' ); + + // Update button states. + buttons.forEach( ( button ) => { + button.classList.remove( 'is-active' ); + button.setAttribute( 'aria-selected', 'false' ); + button.setAttribute( 'tabindex', '-1' ); + } ); + currentButton.classList.add( 'is-active' ); + currentButton.setAttribute( 'aria-selected', 'true' ); + currentButton.setAttribute( 'tabindex', '0' ); + + // Update panel states. + panels.forEach( ( panel ) => { + panel.classList.remove( 'is-active' ); + } ); + document.getElementById( panelId )?.classList.add( 'is-active' ); + }; + + tabList.addEventListener( 'click', ( event ) => { + if ( ! Array.from( buttons ).includes( event.target ) ) { + return; + } + + event.preventDefault(); + maybeActivateTab( event.target ); + } ); + + tabList.addEventListener( 'keydown', ( event ) => { + if ( ! Array.from( buttons ).includes( event.target ) ) { + return; + } + + if ( event.code === 'Enter' || event.code === 'Space' ) { + event.preventDefault(); + maybeActivateTab( event.target ); + } else if ( event.code === 'ArrowLeft' || event.code === 'ArrowRight' ) { + event.preventDefault(); + + // Cycle focus within the tab list. + const currentIndex = Array.from( buttons ).indexOf( event.target ); + const isMovingRight = event.code === 'ArrowRight'; + const nextIndex = isMovingRight ? currentIndex + 1 : currentIndex - 1; + const nextButton = buttons[ nextIndex ]; + + if ( nextButton ) { + nextButton.focus(); + } else { + buttons[ isMovingRight ? 0 : buttons.length - 1 ].focus(); + } + } + } ); + } ); +}; + +window.addEventListener( 'load', init ); diff --git a/source/wp-content/themes/wpr-developer-2024/js/customizer.js b/source/wp-content/themes/wpr-developer-2024/js/customizer.js new file mode 100644 index 000000000..16fef19ea --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/js/customizer.js @@ -0,0 +1,36 @@ +/** + * Theme Customizer enhancements for a better user experience. + * + * Contains handlers to make Theme Customizer preview reload changes asynchronously. + */ + +( function( $ ) { + // Site title and description. + wp.customize( 'blogname', function( value ) { + value.bind( function( to ) { + $( '.site-title a' ).text( to ); + } ); + } ); + wp.customize( 'blogdescription', function( value ) { + value.bind( function( to ) { + $( '.site-description' ).text( to ); + } ); + } ); + // Header text color. + wp.customize( 'header_textcolor', function( value ) { + value.bind( function( to ) { + if ( 'blank' === to ) { + $( '.site-title, .site-description' ).css( { + 'clip': 'rect(1px, 1px, 1px, 1px)', + 'position': 'absolute' + } ); + } else { + $( '.site-title, .site-description' ).css( { + 'clip': 'auto', + 'color': to, + 'position': 'relative' + } ); + } + } ); + } ); +} )( jQuery ); diff --git a/source/wp-content/themes/wpr-developer-2024/js/explanations.js b/source/wp-content/themes/wpr-developer-2024/js/explanations.js new file mode 100644 index 000000000..3e32eb868 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/js/explanations.js @@ -0,0 +1,105 @@ +/** + * Explanations JS. + */ + +( function( $ ) { + + // + // Explanations AJAX handlers. + // + + var statusLabel = $( '#status-label' ), + createLink = $( '#create-expl' ), + unPublishLink = $( '#unpublish-expl' ), + rowActions = $( '#expl-row-actions' ); + + var rowCreateLink = $( '.create-expl' ); + + /** + * AJAX handler for creating and associating a new explanation post. + * + * @param {object} event Event object. + */ + function createExplanation( event ) { + event.preventDefault(); + + wp.ajax.send( 'new_explanation', { + success: createExplSuccess, + error: createExplError, + data: { + nonce: $( this ).data( 'nonce' ), + post_id: $( this ).data( 'id' ), + context: event.data.context + } + } ); + } + + /** + * Success callback for creating a new explanation via AJAX. + * + * @param {object} data Data response object. + */ + function createExplSuccess( data ) { + var editLink = '<a href="post.php?post=' + data.post_id + '&action=edit">' + wporg.editContentLabel + '</a>'; + + if ( 'edit' == data.context ) { + // Action in the parsed post type edit screen. + createLink.hide(); + rowActions.html( editLink ); + statusLabel.text( wporg.statusLabel.draft ); + } else { + // Row link in the list table. + $( '#post-' + data.parent_id + ' .add-expl' ).html( editLink + ' | ' ); + } + } + + /** + * Error callback for creating a new explanation via AJAX. + * + * @param {object} data Data response object. + */ + function createExplError( data ) {} + + /** + * Handler for un-publishing an existing Explanation. + * + * @param {object} event Event object. + */ + function unPublishExplantaion( event ) { + event.preventDefault(); + + wp.ajax.send( 'un_publish', { + success: unPublishSuccess, + error: unPublishError, + data: { + nonce: $( this ).data( 'nonce' ), + post_id: $( this ).data( 'id' ) + } + } ); + } + + /** + * Success callback for un-publishing an explanation via AJAX. + * + * @param {object} data Data response object. + */ + function unPublishSuccess( data ) { + if ( statusLabel.hasClass( 'pending' ) || statusLabel.hasClass( 'publish' ) ) { + statusLabel.removeClass( 'pending publish' ).text( wporg.statusLabel.draft ); + } + unPublishLink.hide(); + } + + /** + * Error callback for un-publishing an explanation via AJAX. + * + * @param {object} data Data response object. + */ + function unPublishError( data ) {} + + // Events. + createLink.on( 'click', { context: 'edit' }, createExplanation ); + rowCreateLink.on( 'click', { context: 'list' }, createExplanation ); + unPublishLink.on( 'click', unPublishExplantaion ); + +} )( jQuery ); diff --git a/source/wp-content/themes/wpr-developer-2024/js/function-reference.js b/source/wp-content/themes/wpr-developer-2024/js/function-reference.js new file mode 100644 index 000000000..e0f2e4a87 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/js/function-reference.js @@ -0,0 +1,168 @@ +/* global jQuery, Prism, wporgFunctionReferenceI18n */ +/** + * function-reference.js + * + * Handles all interactivity on the single function page + */ + +// eslint-disable-next-line id-length -- $ OK. +jQuery( function ( $ ) { + // 22.5px (line height) * 15 for 15 lines + 15px top padding + 10px extra. + // The extra 10px added to partially show next line so it's clear there is more. + const MIN_HEIGHT = 22.5 * 15 + 15 + 10; + + function collapseCodeBlock( $element, $button ) { + $button.text( wporgFunctionReferenceI18n.expand ); + $button.attr( 'aria-expanded', 'false' ); + // This uses `css()` instead of `height()` to prevent jQuery from adding + // in the padding. We want to add in just the top padding, since the + // bottom is intentionally cut off. + $element.css( { height: MIN_HEIGHT + 'px' } ); + } + + function expandCodeBlock( $element, $button ) { + $button.text( wporgFunctionReferenceI18n.collapse ); + $button.attr( 'aria-expanded', 'true' ); + // { height: auto; } can't be used here or the transition effect won't work. + $element.height( $element.data( 'height' ) ); + } + + // For each code block, add the copy button & expanding functionality. + $( '.wp-block-code' ).each( function ( i, element ) { + const $element = $( element ); + let timeoutId; + + $element.wrap( '<div class="wporg-developer-code-block"></div>' ); + + const $copyButtonContainer = $( '<div class="wp-block-button is-style-outline is-small"></div>' ); + const $copyButton = $( '<a href="#" class="wp-block-button__link wp-element-button has-background"></a>' ); + $copyButtonContainer.append( $copyButton ); + + $copyButton.text( wporgFunctionReferenceI18n.copy ); + $copyButton.on( 'click', function ( event ) { + event.preventDefault(); + clearTimeout( timeoutId ); + // first() is used here as sometimes there are additional <code> tags in user-generated content, + // which causes copying the text twice. + const code = $element.find( 'code' ).first().text(); + if ( ! code ) { + return; + } + + // This returns a promise which will resolve if the copy suceeded, + // and we can set the button text to tell the user it worked. + // We don't do anything if it fails. + window.navigator.clipboard.writeText( code ).then( function () { + $copyButton.text( wporgFunctionReferenceI18n.copied ); + wp.a11y.speak( wporgFunctionReferenceI18n.copied ); + + // After 5 seconds, reset the button text. + timeoutId = setTimeout( function () { + $copyButton.text( wporgFunctionReferenceI18n.copy ); + }, 5000 ); + } ); + } ); + + const $container = $( document.createElement( 'div' ) ); + $container.addClass( 'wp-code-block-button-container' ); + + $container.append( '<code>' + wporgFunctionReferenceI18n.sourceFile + '</code>' ); + + const $btnContainer = $( document.createElement( 'span' ) ); + + // Check code block height, and if it's larger, add in the collapse + // button, and set it to be collapsed differently. + const originalHeight = $element.height(); + if ( originalHeight > MIN_HEIGHT ) { + $element.data( 'height', originalHeight ); + + const $expandButtonContainer = $( '<div class="wp-block-button is-style-outline is-small"></div>' ); + const $expandButton = $( + '<a href="#" class="wp-block-button__link wp-element-button has-background" aria-controls="wporg-source-code"></a>' + ); + $expandButton.on( 'click', function ( event ) { + event.preventDefault(); + if ( 'true' === $expandButton.attr( 'aria-expanded' ) ) { + collapseCodeBlock( $element, $expandButton ); + } else { + expandCodeBlock( $element, $expandButton ); + } + } ); + + collapseCodeBlock( $element, $expandButton ); + $expandButtonContainer.append( $expandButton ); + $btnContainer.append( $expandButtonContainer ); + } + + $btnContainer.append( $copyButtonContainer ); + $container.append( $btnContainer ); + + $element.before( $container ); + } ); + + let $usesList, $usedByList, $showMoreUses, $hideMoreUses, $showMoreUsedBy, $hideMoreUsedBy; + + function toggleUsageListInit() { + const usesToShow = $( '#uses-table' ).data( 'show' ), + usedByToShow = $( '#used-by-table' ).data( 'show' ); + + // We only expect one used_by and uses per document + $usedByList = $( 'tbody tr', '#used-by-table' ); + $usesList = $( 'tbody tr', '#uses-table' ); + + if ( $usedByList.length > usedByToShow ) { + $usedByList = $usedByList.slice( usedByToShow ).hide(); + + $showMoreUsedBy = $( '.used-by .show-more' ).show().on( 'click', toggleMoreUsedBy ); + $hideMoreUsedBy = $( '.used-by .hide-more' ).on( 'click', toggleMoreUsedBy ); + } + + if ( $usesList.length > usesToShow ) { + $usesList = $usesList.slice( usesToShow ).hide(); + + $showMoreUses = $( '.uses .show-more' ).show().on( 'click', toggleMoreUses ); + $hideMoreUses = $( '.uses .hide-more' ).on( 'click', toggleMoreUses ); + } + } + + function toggleMoreUses( event ) { + event.preventDefault(); + + $usesList.toggle(); + + $showMoreUses.toggle(); + $hideMoreUses.toggle(); + } + + function toggleMoreUsedBy( event ) { + event.preventDefault(); + + $usedByList.toggle(); + + $showMoreUsedBy.toggle(); + $hideMoreUsedBy.toggle(); + } + + toggleUsageListInit(); + + // Runs before the highlight parsing is run. + // `env` is defined here: https://github.com/PrismJS/prism/blob/2815f699970eb8387d741e3ac886845ce5439afb/prism.js#L583-L588 + Prism.hooks.add( 'before-highlight', function ( env ) { + // If the code starts with `<`, it's either already got an opening tag, + // or it starts with HTML. Either way, we don't want to inject here. + if ( 'php' === env.language && ! env.code.startsWith( '<' ) ) { + env.code = '<? ' + env.code; + env.hasAddedTag = true; + } + } ); + + // Runs before `highlightedCode` is set to the `innerHTML` of the container. + Prism.hooks.add( 'before-insert', function ( env ) { + if ( env.hasAddedTag ) { + env.highlightedCode = env.highlightedCode.replace( + '<span class="token delimiter important"><?</span> ', + '' + ); + } + } ); +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/js/page-dashicons.js b/source/wp-content/themes/wpr-developer-2024/js/page-dashicons.js new file mode 100644 index 000000000..3abe96135 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/js/page-dashicons.js @@ -0,0 +1,110 @@ +(function( wp, $, window, undefined ) { + + var dashicons = { + copy: function( text, copyMode ) { + if ( copyMode == "css" ) { + window.prompt( "Copy this, then paste in your CSS :before selector.", text ); + } else if ( copyMode == "html" ) { + window.prompt( "Copy this, then paste in your HTML.", text ); + } else { + window.prompt( "Copy this, then paste in your Photoshop textfield.", text ); + } + }, + + random: function() { + const listItems = jQuery("#iconlist li").get().sort(function(){ + return Math.round(Math.random())-0.5; + }).slice(0,1); + + attr = jQuery(listItems).attr('data-code'); + cssClass = jQuery(listItems).attr('class'); + dashicons.display( attr, cssClass ); + }, + + display: function( attr, cssClass ){ + // set permalink + var permalink = cssClass.split(' dashicons-')[1]; + window.location.hash = permalink; + + // html copy string + htmltext = '<span class="' + cssClass + '"></span>'; + + // glyph copy string + glyphtemp = "&#x" + attr + ";"; + jQuery('#temp').html( glyphtemp ); + glyphtext = jQuery('#temp').text(); + + // icon code + var charCode = jQuery('#iconlist li.dashicons-' + permalink ).attr('data-code'); + + var sectionName = jQuery('#iconlist li.dashicons-' + permalink ).parent().prevAll('h4')[0].childNodes[0].nodeValue; + + var tmpl = wp.template( 'glyphs' ); + + jQuery( '#glyph' ).html( tmpl({ + charCode: charCode, + cssClass: 'dashicons-' + permalink, + sectionName: sectionName, + attr: attr, + html: htmltext, + glyph: glyphtext + }) ); + + jQuery( '#wp-class-example' ).text( permalink ); + } + }; + + window.dashicons = dashicons; + + jQuery(document).ready(function() { + + // pick random icon if no permalink, otherwise go to permalink + if ( window.location.hash && '#instructions' !== window.location.hash ) { + permalink = "dashicons-" + window.location.hash.split('#')[1]; + + // sanitize + if ( !/^dashicons-[a-z-]+$/.test( permalink ) ) { + permalink = ""; + dashicons.random(); + } + + attr = jQuery( '.' + permalink ).attr( 'data-code' ); + cssClass = jQuery( '.' + permalink ).attr('class'); + dashicons.display( attr, cssClass ); + } else { + dashicons.random(); + } + + jQuery( '#iconlist li' ).click(function() { + + attr = jQuery( this ).attr( 'data-code' ); + cssClass = jQuery( this ).attr( 'class' ); + + dashicons.display( attr, cssClass ); + var headerWidth = 160; // Rough approximation of the header height. + $(window).scrollTop( $("#glyph").offset().top - headerWidth ); + + }); + + var $rows = jQuery('#iconlist li'); + jQuery('#search').keyup(function() { + + // remove update text when using search + jQuery('body').addClass('searching'); + + var val = jQuery.trim(jQuery(this).val()).replace(/ +/g, ' ').toLowerCase(); + + if ( val.length < 3 ) { + val = ''; + jQuery('body').removeClass('searching'); + } + + $rows.show().filter(function() { + var text = jQuery(this).attr('data-keywords').replace(/\s+/g, ' ').toLowerCase(); + return !~text.indexOf(val); + }).hide(); + }); + + }); + +})( wp, jQuery, window ); diff --git a/source/wp-content/themes/wpr-developer-2024/js/parsed-content.js b/source/wp-content/themes/wpr-developer-2024/js/parsed-content.js new file mode 100644 index 000000000..2fd921c8c --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/js/parsed-content.js @@ -0,0 +1,83 @@ +/** + * Admin extras backend JS. + */ + +( function( $ ) { + var ticketNumber = $( '#wporg_parsed_ticket' ), + attachButton = $( '#wporg_ticket_attach' ), + detachButton = $( '#wporg_ticket_detach' ), + ticketInfo = $( '#wporg_ticket_info' ), + spinner = $( '.attachment_controls .spinner' ); + + var handleTicket = function( event ) { + event.preventDefault(); + + var $this = $(this), + attachAction = 'attach' == event.data.action; + + spinner.addClass( 'is-active' ); + + // Searching ... text. + if ( attachAction ) { + ticketInfo.text( wporgParsedContent.searchText ); + } + + var request = wp.ajax.post( attachAction ? 'wporg_attach_ticket' : 'wporg_detach_ticket', { + ticket: ticketNumber.val(), + nonce: $this.data( 'nonce' ), + post_id: $this.data( 'id' ) + } ); + + // Success. + request.done( function( response ) { + // Refresh the nonce. + $this.data( 'nonce', response.new_nonce ); + + // Hide or show the parsed content boxes. + $( '.wporg_parsed_content' ).each( function() { + attachAction ? $(this).show() : $(this).hide(); + }); + + $( '.wporg_parsed_readonly' ).each( function() { + attachAction ? $(this).hide() : $(this).show(); + }); + + var otherButton = attachAction ? detachButton : attachButton; + + // Toggle the buttons. + $this.hide(); + otherButton.css( 'display', 'inline-block' ); + + // Update the ticket info text. + ticketInfo.html( response.message ).show(); + + // Clear the ticket number when detaching. + if ( ! attachAction ) { + ticketNumber.val( '' ); + } + + spinner.removeClass( 'is-active' ); + + // Set or unset the ticket link icon. + $( '.ticket_info_icon' ).toggleClass( 'dashicons dashicons-external', attachAction ); + + // Set the ticket number to readonly when a ticket is attached. + attachAction ? ticketNumber.prop( 'readonly', 'readonly' ) : ticketNumber.removeAttr( 'readonly' ); + } ); + + // Error. + request.fail( function( response ) { + // Refresh the nonce. + $this.data( 'nonce', response.new_nonce ); + + // Retry text. + ticketInfo.text( wporgParsedContent.retryText ); + + spinner.removeClass( 'is-active' ); + } ); + }; + + attachButton.on( 'click', { action: 'attach' }, handleTicket ); + detachButton.on( 'click', { action: 'detach' }, handleTicket ); + +} )( jQuery ); diff --git a/source/wp-content/themes/wpr-developer-2024/js/tabs.js b/source/wp-content/themes/wpr-developer-2024/js/tabs.js new file mode 100644 index 000000000..c14ad6ea7 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/js/tabs.js @@ -0,0 +1,156 @@ +/** + * Takes care of hiding and displaying sections with tabs + * + * Allow users to switch focus between the aria-selected tab and the content. + * Change focus between tabs using the left and right arrow keys. + * Use the TAB key as normal to focus the first element inside the visible tab panel. + * + * Html markup needed for a tabbed list + * + * <div class=".tab-container"> + * <ul class="tablist"> + * <li><a href="#section-1">Section 1</a></li> + * <li><a href="#section-2">Section 2</a></li> + * </ul> + * <div id="section-1" class="tab-section">Section 1 content</div> + * <div id="section-2" class="tab-section">Section 2 content</div> + * </div> + */ + + +( function( $ ) { + + container = $( '.tab-container' ); + + if ( container.length ) { + + container.each( function() { + + var tablist = $( this ).find( '.tablist' ); + var tabSections = $( this ).find( '.tab-section' ); + + if ( tablist.length || tabSections.length ) { + var tabs = tablist.find( 'a' ); + + if ( ( 1 < tabs.length ) && ( tabs.length === tabSections.length ) ) { + setupTabs( tablist, tabs, tabSections ); + tabEvents( tablist, tabs, tabSections ); + } + } + } ); + } + + function setupTabs( tablist, tabs, tabSections ) { + + tablist.attr( 'role', 'tablist' ); + tablist.find( 'li' ).attr( 'role', 'presentation' ); + + tabs.attr( { + 'role': 'tab', + 'tabindex': '-1' + } ); + + // Make each aria-controls correspond id of targeted section (re href) + tabs.each( function() { + $( this ).attr( + 'aria-controls', $( this ).attr( 'href' ).substring( 1 ) + ); + } ); + + // Make the first tab selected by default and allow it focus + tabs.first().attr( { + 'aria-selected': 'true', + 'tabindex': '0' + } ); + + // Add 'tab-section-selected' to first section + tabSections.first().addClass( 'tab-section-selected' ); + + // Make each section focusable and give it the tabpanel role + tabSections.attr( { + 'role': 'tabpanel' + } ); + + // Make first child of each panel focusable programmatically + tabSections.children().first().attr( { + 'tabindex': '0' + } ); + + // Make all but the first section hidden (ARIA state and display CSS) + $( tabSections ).not( ":first" ).attr( { + 'aria-hidden': 'true' + } ); + } + + function tabEvents( tablist, tabs, tabSections ) { + + // Change focus between tabs with arrow keys + tabs.on( 'keydown', function( e ) { + + // define current, previous and next (possible) tabs + var original = $( this ); + var prev = $( this ).parents( 'li' ).prev().children( '[role="tab"]' ); + var next = $( this ).parents( 'li' ).next().children( '[role="tab"]' ); + var target; + + // find the direction (prev or next) + switch ( e.keyCode ) { + case 37: + target = prev; + break; + case 39: + target = next; + break; + default: + target = false + break; + } + + if ( target.length ) { + original.attr( { + 'tabindex': '-1', + 'aria-selected': null + } ); + target.attr( { + 'tabindex': '0', + 'aria-selected': true + } ).focus(); + } + + // Hide panels + tabSections.attr( 'aria-hidden', 'true' ); + + // Show panel which corresponds to target + $( '#' + $( document.activeElement ).attr( 'href' ).substring( 1 ) ).attr( 'aria-hidden', null ); + + // Toggle 'tab-section-selected' class for tab sections + tabSections.toggleClass( 'tab-section-selected' ); + } ); + + // Handle click on tab to show + focus tabpanel + tabs.on( 'click', function( e ) { + e.preventDefault(); + + tabs.attr( { + 'tabindex': '-1', + 'aria-selected': null + } ); + + // replace above on clicked tab + $( this ).attr( { + 'aria-selected': true, + 'tabindex': '0' + } ); + + // Hide panels + tabSections.attr( 'aria-hidden', 'true' ); + + // show corresponding panel + $( '#' + $( this ).attr( 'href' ).substring( 1 ) ).attr( 'aria-hidden', null ); + + // Toggle 'tab-section-selected' class for tab sections + tabSections.toggleClass( 'tab-section-selected' ); + } ); + } + +} )( jQuery ); diff --git a/source/wp-content/themes/wpr-developer-2024/js/user-notes-feedback.js b/source/wp-content/themes/wpr-developer-2024/js/user-notes-feedback.js new file mode 100644 index 000000000..7abc8e6e0 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/js/user-notes-feedback.js @@ -0,0 +1,229 @@ +( function( $ ) { + + if ( undefined === wporg_note_feedback ) { + return; + } + + function appendCount( str, count ) { + return `${ str } (${ count })`; + } + + var headerOffset = 0; + var options = wporg_note_feedback; + var comments = $( '.comment' ); + var feedbackToggle = $( '<a role="button" class="feedback-toggle" href="#"></a>' ); + var commentID = window.location.hash; + + // Check if the fragment identifier is a comment ID (e.g. #comment-63) + if ( !commentID.match( /#comment\-[0-9]+$/ ) ) { + commentID = ''; + } + + $( '.feedback-editor' ).each( function() { + // Hide feedback editors with hide-if-js class + $( this ).not('.edit-feedback-editor').addClass( 'hide-if-js' ); + $( this ).removeAttr("style"); + + // Add quicktag 'inline code' button to editor. + var id = $( this ).find( 'textarea' ).attr( 'id' ); + if ( id.length ) { + QTags.addButton( 'inline-code', 'inline code', '<code>', '</code>', '', '', '', id ); + } + } ); + + // Loop through feedback notes + comments.each( function() { + var feedbackLinks = $( this ).find( '.feedback-links' ); + var childComments = $( this ).find( 'ul.children' ); + + if ( childComments.length && feedbackLinks.length ) { + var feedback = $( this ).find( '.feedback' ); + var toggle = feedbackToggle.clone(); + + var feedback_id = getCommentID( $(this) ); + toggle.attr( { + 'aria-expanded': 'false', + 'aria-controls': 'feedback-' + feedback_id + } ); + + // Set text to 'Hide Feedback' if feedback is displayed + if ( !feedback.hasClass( 'hide-if-js' ) ) { + toggle.text( appendCount( options.hide, feedback.attr( 'data-comment-count' ) ) ); + } else { + toggle.text( appendCount( options.show, feedback.attr( 'data-comment-count' ) ) ) + } + + // Display hidden add feedback link and add aria + feedbackLinks.find( '.feedback-add' ).removeAttr("style").attr( { + 'aria-expanded': 'false', + 'aria-controls': 'feedback-editor-' + feedback_id + } ); + + feedbackLinks.prepend( toggle ); + } + + if ( feedbackLinks.length ) { + // Move the feedback links before the feedback section. + var clonedElements = feedbackLinks.clone().children(); + var feedbackLinksTop = $( '<div class="feedback-links wporg-dot-link-list"></div>' ).append( clonedElements ); + $( this ).find( '.feedback' ).first().before( feedbackLinksTop ); + + // Hide the bottom feedback links. + feedbackLinks.addClass( 'bottom hide-if-js' ); + } + } ); + + // Returns comment ID from data attribute. + function getCommentID( el ) { + return $(el).is("[data-comment-id]") ? el.data( 'comment-id' ) : 0; + } + + // Removes added elements + function resetComment( el ) { + var children = el.find( 'ul.children' ); + if ( !children.length ) { + el.find( '.feedback-toggle' ).remove(); + } + + el.find( '.feedback-links.bottom' ).addClass( 'hide-if-js' ); + } + + // Show hidden child comments if the fragment identifier is a comment ID (e.g. #comment-63). + $( document ).ready( function() { + // Set wpAdminBar + headerOffset = $( '#wpadminbar' ).length ? 32 + 10 : 0; + + // Calculate the header height + $( '.global-header' ) + .parent() + .children() + .each( function () { + headerOffset += $( this ).outerHeight(); + } ); + + var childComments = comments.find( 'ul.children' ); + + if ( ! ( commentID.length && childComments.length ) ) { + return; + } + + var childComment = childComments.find( commentID + '.depth-2' ).first(); + if ( ! childComment.length ) { + return; + } + // Child comment exists. + + var parent = childComment.closest( '.comment.depth-1' ); + if ( parent.find( '.feedback' ).hasClass( 'hide-if-js' ) ) { + // Show child comments. + parent.find( '.feedback-toggle' ).first().trigger( 'click' ); + } + + // Scroll to child comment and adjust for admin bar + var pos = childComment.offset(); + $( 'html,body' ).animate( { + scrollTop: pos.top - headerOffset + }, 1 ); + + } ); + + // Click event for Show/Hide feedback toggle link. + comments.on( 'click', '.feedback-toggle', function( e ) { + e.preventDefault(); + + var parent = $( this ).closest( '.comment.depth-1' ); + if ( !parent.length ) { + return; + } + + resetComment( parent ); + var feedback = parent.find( '.feedback' ); + var toggleLinks = parent.find( '.feedback-toggle' ); + var children = parent.find( 'ul.children > li' ); + + if ( feedback.hasClass( 'hide-if-js' ) ) { + // Feedback is hidden. + + // Show feedback. + toggleLinks.text( appendCount( options.hide, children.length ) ); + feedback.removeClass( 'hide-if-js' ); + toggleLinks.attr( 'aria-expanded', 'true' ); + + // Go to the clicked feedback toggle link. + let pos = $( this ).offset(); + $( 'html,body' ).animate( + { + scrollTop: pos.top - headerOffset, + }, + 1000 + ); + } else { + // Hide feedback. + toggleLinks.text( appendCount( options.show, children.length ) ); + feedback.addClass( 'hide-if-js' ); + toggleLinks.attr( 'aria-expanded', 'false' ); + + // Hide editor. + var editor = feedback.find( '.feedback-editor' ); + editor.addClass( 'hide-if-js' ); + + $feedbackAdd = parent.find( '.feedback-add' ); + $feedbackAdd.attr( 'aria-expanded', 'false' ); + $feedbackAdd.text( options.add_feedback ); + } + } ); + + // Show editor when the add feedback link is clicked. + comments.on( 'click', '.feedback-add', function( e ) { + e.preventDefault(); + + var parent = $( this ).closest( '.comment.depth-1' ); + if ( ! parent.length ) { + return; + } + + var feedback = parent.find( '.feedback' ); + var editor = feedback.find( '.feedback-editor' ); + + var $button = $( e.target ); + if ( $button.text() === options.hide_feedback ) { + $button.text( options.add_feedback ); + + // Hide the editor + editor.addClass( 'hide-if-js' ); + } else { + resetComment( parent ); + + var children = parent.find( 'ul.children' ); + var feedbackLinks = parent.find( '.feedback-add' ); + + // Show feedback. + feedback.removeClass( 'hide-if-js' ); + feedbackLinks.attr( 'aria-expanded', 'true' ); + + // Show the feedback editor. + editor.removeClass( 'hide-if-js' ); + + // Change the toggle link text to 'Hide Feedback'. + var toggleLinks = parent.find( '.feedback-toggle' ); + if ( toggleLinks.length ) { + toggleLinks.attr( 'aria-expanded', 'true' ); + toggleLinks.text( appendCount( options.hide, feedback.attr( 'data-comment-count' ) ) ); + } + + // Go to the feedback editor and give it focus. + var pos = editor.offset(); + $( 'html,body' ).animate( + { + scrollTop: pos.top - headerOffset, + }, + 1000, + function () { + editor.find( 'textarea' ).focus(); + } + ); + editor.find( 'textarea' ).focus(); + $button.text( options.hide_feedback ); + } + } ); +} )( jQuery ); diff --git a/source/wp-content/themes/wpr-developer-2024/js/user-notes-preview.js b/source/wp-content/themes/wpr-developer-2024/js/user-notes-preview.js new file mode 100644 index 000000000..f2d819e82 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/js/user-notes-preview.js @@ -0,0 +1,127 @@ +/** + * Preview for user contributed notes. + * + */ + +( function( $ ) { + + var textarea, tabContentHeight, text, preview, previewContent, tabs, processing, spinner; + + function init() { + + if ( undefined === wporg_note_preview ) { + return; + } + + textarea = $( '.comment-form textarea' ); + preview = $( '#comment-preview' ); + tabs = $( '#commentform .tablist' ).find( 'a' ); + spinner = $( '<span class="spinner" style="display:none;"></span>' ); + text = ''; + processing = false; + + // Show tabs with Javascript. + $( '#commentform .tablist').show(); + + if ( textarea.length && preview.length && tabs.length ) { + + // Append spinner to preview tab + tabs.parents( 'li[role="presentation"]:last' ).append( spinner ); + + previewContent = $( '.preview-content', preview ); + + if ( previewContent.length ) { + + if ( !textarea.val().length ) { + previewContent.text( wporg_note_preview.preview_empty ); + } + + previewEvents(); + } + } + } + + function previewEvents() { + + tabs.on( "keydown.note_preview, click.note_preview", function( e ) { + + // Preview tab should be at least as tall input tab to prevent resizing wonkiness. + tabContentHeight = $( '#comment-form-comment' ).outerHeight( false ); + + if ( 0 < tabContentHeight ) { + preview.css( 'min-height', tabContentHeight + 'px' ); + } + + if ( 'comment-preview' === $( this ).attr( 'aria-controls' ) ) { + + if ( !processing ) { + current_text = $.trim( textarea.val() ); + if ( current_text.length && ( current_text !== wporg_note_preview.preview_empty ) ) { + if ( wporg_note_preview.preview_empty === previewContent.text() ) { + // Remove "Nothing to preview" if there's new current text. + previewContent.text( '' ); + } + // Update the preview. + updatePreview( current_text ); + } else { + previewContent.text( wporg_note_preview.preview_empty ); + } + } + + // Remove outline from tab if clicked. + if ( "click" === e.type ) { + $( this ).blur(); + } + } else { + textarea.focus(); + } + } ); + } + + function updatePreview( content ) { + + // Don't update preview if nothing changed + if ( text == content ) { + spinner.hide(); + return; + } + + spinner.show(); + text = content; + processing = true; + + $.post( wporg_note_preview.ajaxurl, { + action: "preview_comment", + preview_nonce: wporg_note_preview.nonce, + preview_comment: content + } ) + + .done( function( response ) { + updatePreview_HTML( response.data.comment ); + } ) + + .fail( function( response ) { + //console.log( 'fail', response ); + } ) + + .always( function( response ) { + spinner.hide(); + processing = false; + + // Make first child of the preview focusable + preview.children().first().attr( { + 'tabindex': '0' + } ); + } ); + } + + function updatePreview_HTML( content ) { + // Update preview content + previewContent.html( content ); + + spinner.hide(); + } + + init(); + +} )( jQuery ); diff --git a/source/wp-content/themes/wpr-developer-2024/js/user-notes-voting.js b/source/wp-content/themes/wpr-developer-2024/js/user-notes-voting.js new file mode 100644 index 000000000..f3d59fa6e --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/js/user-notes-voting.js @@ -0,0 +1,32 @@ +/** + * Dynamic functionality for voting on user submitted notes. + * + */ + +( function( $, wp ) { + $( '#comments' ).on( 'click', 'a.user-note-voting-up, a.user-note-voting-down', function( event ) { + event.preventDefault(); + + var $item = $( this ), + comment = $item.closest( '.comment' ); + + $.post( + wporg_note_voting.ajaxurl, + { + action: 'note_vote', + comment: $item.attr( 'data-id' ), + vote: $item.attr( 'data-vote' ), + _wpnonce: $item.parent().attr( 'data-nonce' ) + }, + function( data ) { + if ( '0' !== data ) { + $item.closest( '.user-note-voting' ).replaceWith( data ); + wp.a11y.speak( $( '.user-note-voting-count', comment ).text() ); + } + }, + 'text' + ); + + return false; + } ); +} )( window.jQuery, window.wp ); diff --git a/source/wp-content/themes/wpr-developer-2024/js/user-notes.js b/source/wp-content/themes/wpr-developer-2024/js/user-notes.js new file mode 100644 index 000000000..1ff4933ec --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/js/user-notes.js @@ -0,0 +1,121 @@ +/** + * Dynamic functionality for comments as user submitted notes. + * + */ + +( function( $ ) { + + var commentForm = $( '.comment-form textarea' ); + var commentID = window.location.hash; + var wpAdminBar = 0; + + // Check if the fragment identifier is a comment ID (e.g. #comment-63) + if ( ! commentID.match( /#comment\-[0-9]+$/ ) ) { + commentID = ''; + } + + // Actions for when the page is ready + $( document ).ready( function() { + // Set wpAdminBar + wpAdminBar = $( '#wpadminbar' ).length ? 32 : 0; + + // Display form and scroll to it + if ( '#respond' === window.location.hash ) { + showCommentForm(); + } + + if( ! wpAdminBar || ! commentID ) { + return; + } + + var comment = $('#comments').find( commentID + '.depth-1' ).first(); + if( ! comment.length ) { + return; + } + + // Scroll to top level comment and adjust for admin bar. + var pos = comment.offset(); + $( 'html,body' ).animate( { + scrollTop: pos.top - wpAdminBar + }, 1 ); + } ); + + // Scroll to comment if comment date link is clicked + $( '#comments' ).on( 'click', '.comment-date', function( e ) { + // Scroll to comment and adjust for admin bar + // Add 16px for child comments + var pos = $( this ).offset(); + $( 'html,body' ).animate( { + scrollTop: pos.top - wpAdminBar - 16 + }, 1 ); + } ); + + function showCommentForm() { + var target = $( '#commentform #add-note-or-feedback' ); + if ( target.length ) { + var pos = target.offset(); + + $( 'html,body' ).animate( { + scrollTop: pos.top - wpAdminBar + }, 1000 ); + + $('.wp-editor-area').focus(); + } + } + + if ( ! commentForm.length ) { + return; + } + + $( '.table-of-contents a[href="#add-note-or-feedback"]' ).click( function( e ) { + e.preventDefault(); + showCommentForm(); + } ); + + // Add php and js buttons to QuickTags. + QTags.addButton( 'php', 'php', '[php]', '[/php]', '', '', '', 'comment' ); + QTags.addButton( 'js', 'js', '[js]', '[/js]', '', '', '', 'comment' ); + QTags.addButton( 'inline-code', 'inline code', '<code>', '</code>', '', '', '', 'comment' ); + + // Override tab within user notes textarea to actually insert a tab character. + // Copied from code within core's wp-admin/js/common.js. + commentForm.bind('keydown.wpevent_InsertTab', function(e) { + var el = e.target, selStart, selEnd, val, scroll, sel; + + if ( e.keyCode == 27 ) { // escape key + // when pressing Escape: Opera 12 and 27 blur form fields, IE 8 clears them + e.preventDefault(); + $(el).data('tab-out', true); + return; + } + + if ( e.keyCode != 9 || e.ctrlKey || e.altKey || e.shiftKey ) // tab key + return; + + if ( $(el).data('tab-out') ) { + $(el).data('tab-out', false); + return; + } + + selStart = el.selectionStart; + selEnd = el.selectionEnd; + val = el.value; + + if ( document.selection ) { + el.focus(); + sel = document.selection.createRange(); + sel.text = '\t'; + } else if ( selStart >= 0 ) { + scroll = this.scrollTop; + el.value = val.substring(0, selStart).concat('\t', val.substring(selEnd) ); + el.selectionStart = el.selectionEnd = selStart + 1; + this.scrollTop = scroll; + } + + if ( e.stopPropagation ) + e.stopPropagation(); + if ( e.preventDefault ) + e.preventDefault(); + }); + +} )( jQuery ); diff --git a/source/wp-content/themes/wpr-developer-2024/package.json b/source/wp-content/themes/wpr-developer-2024/package.json new file mode 100644 index 000000000..1f610cfa0 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/package.json @@ -0,0 +1,25 @@ +{ + "name": "wporg-developer-2023", + "version": "1.0.0", + "description": "Theme for WordPress Developer Resources", + "author": "WordPress.org", + "license": "GPL-2.0-or-later", + "private": true, + "devDependencies": { + "@wordpress/eslint-plugin": "^17.7.0", + "@wordpress/scripts": "27.1.0" + }, + "eslintConfig": { + "extends": "../../../../.eslintrc.js" + }, + "stylelint": { + "extends": "../../../../.stylelintrc" + }, + "scripts": { + "build": "wp-scripts build", + "start": "wp-scripts start", + "lint:js": "wp-scripts lint-js src", + "lint:css": "wp-scripts lint-style *.css src/**/*.scss", + "format": "wp-scripts format src" + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/page-dashicons.php b/source/wp-content/themes/wpr-developer-2024/page-dashicons.php new file mode 100644 index 000000000..e69de29bb diff --git a/source/wp-content/themes/wpr-developer-2024/parts/footer.html b/source/wp-content/themes/wpr-developer-2024/parts/footer.html new file mode 100644 index 000000000..564215d69 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/parts/footer.html @@ -0,0 +1 @@ +<!-- wp:wporg/global-footer /--> diff --git a/source/wp-content/themes/wpr-developer-2024/parts/header-alt.html b/source/wp-content/themes/wpr-developer-2024/parts/header-alt.html new file mode 100644 index 000000000..7c718930a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/parts/header-alt.html @@ -0,0 +1,15 @@ +<!-- wp:wporg/global-header {"style":{"border":{"bottom":{"color":"var:preset|color|white-opacity-15","style":"solid","width":"1px"}}}} /--> + +<!-- wp:wporg/local-navigation-bar {"className":"has-display-contents","backgroundColor":"charcoal-2","style":{"elements":{"link":{"color":{"text":"var:preset|color|white"},":hover":{"color":{"text":"var:preset|color|white"}}}}},"textColor":"white","fontSize":"small"} --> + + <!-- wp:group {"style":{"spacing":{"blockGap":"0"}},"textColor":"light-grey-1","layout":{"type":"flex","flexWrap":"nowrap"}} --> + <div class="wp-block-group has-light-grey-1-color has-text-color"> + <!-- wp:site-title {"level":0,"fontSize":"small","textColor":"white"} /--> + + <!-- wp:wporg/page-title {"level":0,"fontSize":"small","fontFamily":"inter","className":"wporg-local-navigation-bar__fade-in-scroll"} /--> + </div> + <!-- /wp:group --> + + <!-- wp:navigation {"icon":"menu","overlayBackgroundColor":"charcoal-2","overlayTextColor":"white","layout":{"type":"flex","orientation":"horizontal"},"fontSize":"small","menuSlug":"developer"} /--> + +<!-- /wp:wporg/local-navigation-bar --> diff --git a/source/wp-content/themes/wpr-developer-2024/parts/header-third.html b/source/wp-content/themes/wpr-developer-2024/parts/header-third.html new file mode 100644 index 000000000..8cd3a9a85 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/parts/header-third.html @@ -0,0 +1,28 @@ +<!-- + This template part is not called directly from any templates, it's swapped out automatically by `modify_header_template_part`. + It should be used on "Level 3+" pages, pages with at least 2 ancestors (for example, Home > Handbook home > Article). +--> + +<!-- wp:wporg/global-header {"style":{"border":{"bottom":{"color":"var:preset|color|white-opacity-15","style":"solid","width":"1px"}}}} /--> + +<!-- wp:wporg/local-navigation-bar {"className":"has-display-contents","backgroundColor":"charcoal-2","style":{"elements":{"link":{"color":{"text":"var:preset|color|white"},":hover":{"color":{"text":"var:preset|color|white"}}}}},"textColor":"white","fontSize":"small"} --> + + <!-- wp:group {"style":{"spacing":{"blockGap":"0"}},"textColor":"light-grey-1","layout":{"type":"flex","flexWrap":"nowrap"}} --> + <div class="wp-block-group has-light-grey-1-color has-text-color"> + <!-- wp:site-title {"level":0,"fontSize":"small","textColor":"white"} /--> + + <!-- wp:wporg/page-title {"level":0,"fontSize":"small","fontFamily":"inter","className":"wporg-local-navigation-bar__fade-in-scroll"} /--> + </div> + <!-- /wp:group --> + + <!-- wp:navigation {"icon":"menu","overlayBackgroundColor":"charcoal-2","overlayTextColor":"white","layout":{"type":"flex","orientation":"horizontal"},"fontSize":"small","menuSlug":"developer"} /--> + +<!-- /wp:wporg/local-navigation-bar --> + +<!-- wp:group {"className":"wporg-breadcrumbs","align":"full","style":{"spacing":{"padding":{"top":"18px","bottom":"18px","left":"var:preset|spacing|edge-space","right":"var:preset|spacing|edge-space"}}},"backgroundColor":"white","layout":{"type":"flex","flexWrap":"wrap","justifyContent":"space-between"}} --> +<div class="wporg-breadcrumbs wp-block-group alignfull has-white-background-color has-background" style="padding-top:18px;padding-right:var(--wp--preset--spacing--edge-space);padding-bottom:18px;padding-left:var(--wp--preset--spacing--edge-space)"> + + <!-- wp:wporg/site-breadcrumbs {"fontSize":"small"} /--> + +</div> +<!-- /wp:group --> diff --git a/source/wp-content/themes/wpr-developer-2024/parts/header.html b/source/wp-content/themes/wpr-developer-2024/parts/header.html new file mode 100644 index 000000000..67667df02 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/parts/header.html @@ -0,0 +1,9 @@ +<!-- wp:wporg/global-header {"style":{"border":{"bottom":{"color":"var:preset|color|white-opacity-15","style":"solid","width":"1px"}}}} /--> + +<!-- wp:wporg/local-navigation-bar {"className":"has-display-contents","backgroundColor":"charcoal-2","style":{"elements":{"link":{"color":{"text":"var:preset|color|white"},":hover":{"color":{"text":"var:preset|color|white"}}}}},"textColor":"white","fontSize":"small"} --> + + <!-- wp:site-title {"level":0,"fontSize":"small"} /--> + + <!-- wp:navigation {"icon":"menu","overlayBackgroundColor":"charcoal-2","overlayTextColor":"white","layout":{"type":"flex","orientation":"horizontal"},"fontSize":"small","menuSlug":"developer"} /--> + +<!-- /wp:wporg/local-navigation-bar --> diff --git a/source/wp-content/themes/wpr-developer-2024/parts/search-wide.html b/source/wp-content/themes/wpr-developer-2024/parts/search-wide.html new file mode 100644 index 000000000..d455bc24c --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/parts/search-wide.html @@ -0,0 +1,7 @@ +<!-- wp:group {"style":{"spacing":{"padding":{"left":"var:preset|spacing|edge-space","right":"var:preset|spacing|edge-space"}}},"className":"alignfull","layout":{"type":"default"}} --> +<div class="wp-block-group alignfull" style="padding-right:var(--wp--preset--spacing--edge-space);padding-left:var(--wp--preset--spacing--edge-space)"> + + <!-- wp:pattern {"slug":"wporg-developer-2023/search-field"} /--> + +</div> +<!-- /wp:group --> diff --git a/source/wp-content/themes/wpr-developer-2024/parts/search.html b/source/wp-content/themes/wpr-developer-2024/parts/search.html new file mode 100644 index 000000000..4bb3cf315 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/parts/search.html @@ -0,0 +1,7 @@ +<!-- wp:group {"layout":{"type":"constrained","justifyContent":"left"},"style":{"spacing":{"padding":{"left":"var:preset|spacing|edge-space","right":"var:preset|spacing|edge-space"}}}} --> +<div class="wp-block-group alignfull" style="padding-left:var(--wp--preset--spacing--edge-space);padding-right:var(--wp--preset--spacing--edge-space)"> + + <!-- wp:pattern {"slug":"wporg-developer-2023/search-field"} /--> + +</div> +<!-- /wp:group --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/article-meta-block-editor.php b/source/wp-content/themes/wpr-developer-2024/patterns/article-meta-block-editor.php new file mode 100644 index 000000000..51eb95d13 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/article-meta-block-editor.php @@ -0,0 +1,55 @@ +<?php +/** + * Title: Article Meta for the Block Editor GitHub handbook + * Slug: wporg-developer-2023/article-meta-block-editor + * Inserter: no + */ + +?> + +<!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|40"},"margin":{"top":"var:preset|spacing|40","bottom":"var:preset|spacing|40"},"blockGap":"var:preset|spacing|20"},"border":{"top":{"color":"var:preset|color|light-grey-1","width":"1px"}}},"layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"left","verticalAlignment":"top"},"className":"entry-meta"} --> +<div class="wp-block-group entry-meta" style="border-top-color:var(--wp--preset--color--light-grey-1);border-top-width:1px;margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--40);padding-top:var(--wp--preset--spacing--40)"> + + <!-- wp:group {"style":{"spacing":{"blockGap":"0"}},"layout":{"type":"constrained"}} --> + <div class="wp-block-group"> + <!-- wp:paragraph {"style":{"typography":{"fontStyle":"normal","fontWeight":"700"}}} --> + <p style="font-style:normal;font-weight:700"><?php esc_html_e( 'First published', 'wporg' ); ?></p> + <!-- /wp:paragraph --> + + <!-- wp:post-date /--> + </div> + <!-- /wp:group --> + + <!-- wp:group {"style":{"spacing":{"blockGap":"0"}},"layout":{"type":"constrained"}} --> + <div class="wp-block-group"> + <!-- wp:paragraph {"style":{"typography":{"fontStyle":"normal","fontWeight":"700"}}} --> + <p style="font-style:normal;font-weight:700">[last_updated]</p> + <!-- /wp:paragraph --> + + <!-- wp:post-date {"displayType":"modified"} /--> + </div> + <!-- /wp:group --> + + <!-- wp:group {"style":{"spacing":{"blockGap":"0"}},"layout":{"type":"constrained"}} --> + <div class="wp-block-group"> + + <!-- wp:paragraph {"style":{"typography":{"fontStyle":"normal","fontWeight":"700"}}} --> + <p style="font-style:normal;font-weight:700"><?php esc_html_e( 'Edit article', 'wporg' ); ?></p> + <!-- /wp:paragraph --> + + <!-- wp:paragraph {"className":"external-link"} --> + <p class="external-link"><a href="[article_edit_link]"> + <?php echo wp_kses_post( + sprintf( + /* translators: %s: article title */ + __( 'Improve it on GitHub<span class="screen-reader-text">: %s</span>', 'wporg' ), + '[article_title]' + ) + ); ?> + </a></p> + <!-- /wp:paragraph --> + + </div> + <!-- /wp:group --> +</div> +<!-- /wp:group --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/article-meta-github.php b/source/wp-content/themes/wpr-developer-2024/patterns/article-meta-github.php new file mode 100644 index 000000000..71763276c --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/article-meta-github.php @@ -0,0 +1,78 @@ +<?php +/** + * Title: Article Meta for GitHub handbooks + * Slug: wporg-developer-2023/article-meta-github + * Inserter: no + */ + +?> + +<!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|40"},"margin":{"top":"var:preset|spacing|40","bottom":"var:preset|spacing|40"},"blockGap":"var:preset|spacing|20"},"border":{"top":{"color":"var:preset|color|light-grey-1","width":"1px"}}},"layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"left","verticalAlignment":"top"},"className":"entry-meta"} --> +<div class="wp-block-group entry-meta" style="border-top-color:var(--wp--preset--color--light-grey-1);border-top-width:1px;margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--40);padding-top:var(--wp--preset--spacing--40)"> + + <!-- wp:group {"style":{"spacing":{"blockGap":"0"}},"layout":{"type":"constrained"}} --> + <div class="wp-block-group"> + <!-- wp:paragraph {"style":{"typography":{"fontStyle":"normal","fontWeight":"700"}}} --> + <p style="font-style:normal;font-weight:700"><?php esc_html_e( 'First published', 'wporg' ); ?></p> + <!-- /wp:paragraph --> + + <!-- wp:post-date /--> + </div> + <!-- /wp:group --> + + <!-- wp:group {"style":{"spacing":{"blockGap":"0"}},"layout":{"type":"constrained"}} --> + <div class="wp-block-group"> + <!-- wp:paragraph {"style":{"typography":{"fontStyle":"normal","fontWeight":"700"}}} --> + <p style="font-style:normal;font-weight:700">[last_updated]</p> + <!-- /wp:paragraph --> + + <!-- wp:post-date {"displayType":"modified"} /--> + </div> + <!-- /wp:group --> + + <!-- wp:group {"style":{"spacing":{"blockGap":"0"}},"layout":{"type":"constrained"}} --> + <div class="wp-block-group"> + + <!-- wp:paragraph {"style":{"typography":{"fontStyle":"normal","fontWeight":"700"}}} --> + <p style="font-style:normal;font-weight:700"><?php esc_html_e( 'Edit article', 'wporg' ); ?></p> + <!-- /wp:paragraph --> + + <!-- wp:paragraph {"className":"external-link"} --> + <p class="external-link"><a href="[article_edit_link]"> + <?php echo wp_kses_post( + sprintf( + /* translators: %s: article title */ + __( 'Improve it on GitHub<span class="screen-reader-text">: %s</span>', 'wporg' ), + '[article_title]' + ) + ); ?> + </a></p> + <!-- /wp:paragraph --> + + </div> + <!-- /wp:group --> + + <!-- wp:group {"style":{"spacing":{"blockGap":"0"}},"layout":{"type":"constrained"}} --> + <div class="wp-block-group"> + + <!-- wp:paragraph {"style":{"typography":{"fontStyle":"normal","fontWeight":"700"}}} --> + <p style="font-style:normal;font-weight:700"><?php esc_html_e( 'Changelog', 'wporg' ); ?></p> + <!-- /wp:paragraph --> + + <!-- wp:paragraph {"className":"external-link"} --> + <p class="external-link"><a href="[article_changelog_link]"> + <?php echo wp_kses_post( + sprintf( + /* translators: %s: article title */ + __( 'See list of changes<span class="screen-reader-text">: %s</span>', 'wporg' ), + '[article_title]' + ) + ); ?> + </a></p> + <!-- /wp:paragraph --> + + </div> + <!-- /wp:group --> + +</div> +<!-- /wp:group --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/article-meta.php b/source/wp-content/themes/wpr-developer-2024/patterns/article-meta.php new file mode 100644 index 000000000..5db560163 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/article-meta.php @@ -0,0 +1,34 @@ +<?php +/** + * Title: Article Meta + * Slug: wporg-developer-2023/article-meta + * Inserter: no + */ + +?> + +<!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|40"},"margin":{"top":"var:preset|spacing|40","bottom":"var:preset|spacing|40"},"blockGap":"var:preset|spacing|20"},"border":{"top":{"color":"var:preset|color|light-grey-1","width":"1px"}}},"layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"left","verticalAlignment":"top"},"className":"entry-meta"} --> +<div class="wp-block-group entry-meta" style="border-top-color:var(--wp--preset--color--light-grey-1);border-top-width:1px;margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--40);padding-top:var(--wp--preset--spacing--40)"> + + <!-- wp:group {"style":{"spacing":{"blockGap":"0"}},"layout":{"type":"constrained"}} --> + <div class="wp-block-group"> + <!-- wp:paragraph {"style":{"typography":{"fontStyle":"normal","fontWeight":"700"}}} --> + <p style="font-style:normal;font-weight:700"><?php esc_html_e( 'First published', 'wporg' ); ?></p> + <!-- /wp:paragraph --> + + <!-- wp:post-date /--> + </div> + <!-- /wp:group --> + + <!-- wp:group {"style":{"spacing":{"blockGap":"0"}},"layout":{"type":"constrained"}} --> + <div class="wp-block-group"> + <!-- wp:paragraph {"style":{"typography":{"fontStyle":"normal","fontWeight":"700"}}} --> + <p style="font-style:normal;font-weight:700">[last_updated]</p> + <!-- /wp:paragraph --> + + <!-- wp:post-date {"displayType":"modified"} /--> + </div> + <!-- /wp:group --> + +</div> +<!-- /wp:group --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/article-sidebar.php b/source/wp-content/themes/wpr-developer-2024/patterns/article-sidebar.php new file mode 100644 index 000000000..7669a2c11 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/article-sidebar.php @@ -0,0 +1,14 @@ +<?php +/** + * Title: Article Sidebar + * Slug: wporg-developer-2023/article-sidebar + * Inserter: no + */ + +?> + +<!-- wp:wporg/sidebar-container --> + + <!-- wp:wporg/table-of-contents /--> + +<!-- /wp:wporg/sidebar-container --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/cli-commands-content.php b/source/wp-content/themes/wpr-developer-2024/patterns/cli-commands-content.php new file mode 100644 index 000000000..8569a10fd --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/cli-commands-content.php @@ -0,0 +1,35 @@ +<?php +/** + * Title: CLI Commands Page Static Content + * Slug: wporg-developer-2023/cli-commands-content + * Inserter: no + */ + +?> + +<!-- wp:paragraph --> +<p><?php esc_html_e( 'Below is a listing of all currently available WP-CLI commands with links to documentation on usage and subcommands.', 'wporg' ); ?></p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p> + <?php echo wp_kses_post( + sprintf( + /* translators: %1$s: URL of the WP-CLI handbook, %2$s: URL of the WP-CLI blog */ + __( 'Looking to learn more about the internal API of WP-CLI or to contribute to its development? Check out the WP-CLI team’s <a href="%1$s">handbook</a> and the <a href="%2$s">WP-CLI Blog</a>.', 'wporg' ), + 'https://make.wordpress.org/cli/handbook/', + 'https://make.wordpress.org/cli/' + ) + ); ?> +</p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"className":"is-toc-heading"} --> +<h2 class="wp-block-heading is-toc-heading" id="commands"><a href="#commands"><?php esc_html_e( 'Commands', 'wporg' ); ?></a></h2> +<!-- /wp:heading --> + +<!-- wp:wporg/cli-command-table {"style":{"spacing":{"margin":{"top":"var:preset|spacing|20"}}}} /--> + +<!-- wp:heading {"className":"is-toc-heading"} --> +<h2 class="wp-block-heading is-toc-heading" id="other-developer-resources"><a href="#other-developer-resources"><?php esc_html_e( 'Other Developer Resources', 'wporg' ); ?></a></h2> +<!-- /wp:heading --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/cli-commands.php b/source/wp-content/themes/wpr-developer-2024/patterns/cli-commands.php new file mode 100644 index 000000000..245fdf62d --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/cli-commands.php @@ -0,0 +1,75 @@ +<?php +/** + * Title: CLI Commands Page Content + * Slug: wporg-developer-2023/cli-commands + * Inserter: no + */ + +?> + +<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"right":"var:preset|spacing|edge-space","left":"var:preset|spacing|edge-space","bottom":"var:preset|spacing|60"}}},"layout":{"type":"constrained"}} --> +<div class="wp-block-group alignfull" style="padding-right:var(--wp--preset--spacing--edge-space);padding-left:var(--wp--preset--spacing--edge-space);padding-bottom:var(--wp--preset--spacing--60)"> + + <!-- wp:group {"align":"left","className":"has-three-columns","layout":{"type":"flex","flexWrap":"wrap","orientation":"vertical"}} --> + <div class="wp-block-group alignleft has-three-columns"> + + <!-- wp:group {"tagName":"main","className":"alignwide"} --> + <main class="wp-block-group alignwide"> + + <!-- wp:group {"tagName":"article","style":{"spacing":{"margin":{"top":"0px"}}}} --> + <article class="wp-block-group" style="margin-top:0px"> + + <!-- wp:heading {"level":1,"style":{"typography":{"fontSize":"36px","fontStyle":"normal","fontWeight":"400"},"spacing":{"margin":{"bottom":"40px"}}},"fontFamily":"eb-garamond"} --> + <h1 class="wp-block-heading has-eb-garamond-font-family" style="font-size:36px;font-style:normal;font-weight:400;margin-bottom:40px"><?php esc_html_e( 'WP-CLI Commands', 'wporg' ); ?></h1> + <!-- /wp:heading --> + + <!-- wp:pattern {"slug":"wporg-developer-2023/article-sidebar"} /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/cli-commands-content"} /--> + + <!-- wp:group {"style":{"spacing":{"blockGap":"var:preset|spacing|10"}},"className":"is-style-cards-grid","layout":{"type":"grid","minimumColumnWidth":"49%"}} --> + <div class="wp-block-group is-style-cards-grid"> + + <!-- wp:wporg/link-wrapper --> + <a class="wp-block-wporg-link-wrapper" href="https://make.wordpress.org/cli/"> + + <!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"700"}},"fontSize":"small","fontFamily":"inter"} --> + <h3 class="wp-block-heading has-inter-font-family has-small-font-size" style="font-style:normal;font-weight:700"><?php esc_html_e( 'CLI Blog', 'wporg' ); ?></h3> + <!-- /wp:heading --> + + <!-- wp:paragraph {"fontSize":"small"} --> + <p class="has-small-font-size"><?php esc_html_e( 'Catch up on the latest on WP-CLI in the main updates blog.', 'wporg' ); ?></p> + <!-- /wp:paragraph --> + + </a> + <!-- /wp:wporg/link-wrapper --> + + <!-- wp:wporg/link-wrapper --> + <a class="wp-block-wporg-link-wrapper" href="https://make.wordpress.org/cli/handbook/"> + + <!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"700"}},"fontSize":"small","fontFamily":"inter"} --> + <h3 class="wp-block-heading has-inter-font-family has-small-font-size" style="font-style:normal;font-weight:700"><?php esc_html_e( 'CLI Handbook', 'wporg' ); ?></h3> + <!-- /wp:heading --> + + <!-- wp:paragraph {"fontSize":"small"} --> + <p class="has-small-font-size"><?php esc_html_e( 'A collection of helpful guides and resources for using WP-CLI.', 'wporg' ); ?></p> + <!-- /wp:paragraph --> + + </a> + <!-- /wp:wporg/link-wrapper --> + + </div> + <!-- /wp:group --> + + + </article> + <!-- /wp:group --> + + </main> + <!-- /wp:group --> + + </div> + <!-- /wp:group --> + +</div> +<!-- /wp:group --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/command-meta.php b/source/wp-content/themes/wpr-developer-2024/patterns/command-meta.php new file mode 100644 index 000000000..1750e420a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/command-meta.php @@ -0,0 +1,14 @@ +<?php +/** + * Title: Command Meta + * Slug: wporg-developer-2023/command-meta + * Inserter: no + */ + +?> + +<!-- wp:paragraph {"style":{"typography":{"lineHeight":"1.88","fontStyle":"normal","fontWeight":"400"},"spacing":{"margin":{"top":"var:preset|spacing|20","bottom":"var:preset|spacing|20"}}},"textColor":"charcoal-1","fontSize":"normal","fontFamily":"inter"} --> +<p class="has-charcoal-1-color has-text-color has-inter-font-family has-normal-font-size" style="margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);font-style:normal;font-weight:400;line-height:1.88"> + <em><?php esc_html_e( 'Command documentation is regenerated at every release. To add or update an example, please submit a pull request against the corresponding part of the codebase.', 'wporg' ); ?></em> +</p> +<!-- /wp:paragraph --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/front-page-content.php b/source/wp-content/themes/wpr-developer-2024/patterns/front-page-content.php new file mode 100644 index 000000000..31c25986a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/front-page-content.php @@ -0,0 +1,224 @@ +<?php +/** + * Title: Front Page Content + * Slug: wporg-developer-2023/front-page-content + * Inserter: no + */ + +?> + +<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"var:preset|spacing|60","bottom":"var:preset|spacing|60"},"blockGap":"var:preset|spacing|50"}},"className":"entry-content"} --> +<div class="wp-block-group alignfull entry-content" style="padding-top:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60)"><!-- wp:group {"align":"full","style":{"spacing":{"padding":{"right":"var:preset|spacing|edge-space","left":"var:preset|spacing|edge-space"}}},"layout":{"type":"constrained"}} --> +<div class="wp-block-group alignfull" style="padding-right:var(--wp--preset--spacing--edge-space);padding-left:var(--wp--preset--spacing--edge-space)"><!-- wp:group {"align":"wide","layout":{"type":"default"}} --> +<div class="wp-block-group alignwide"><!-- wp:heading {"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"className":"is-style-short-text","fontSize":"heading-5"} --> +<h2 class="wp-block-heading is-style-short-text has-heading-5-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'Documentation', 'wporg' ); ?></h2> +<!-- /wp:heading --> + +<!-- wp:group {"style":{"spacing":{"blockGap":"var:preset|spacing|10"}},"className":"is-style-cards-grid","layout":{"type":"grid","minimumColumnWidth":"32.3%"},"fontSize":"small"} --> +<div class="wp-block-group is-style-cards-grid has-small-font-size"><!-- wp:wporg/link-wrapper --> +<a class="wp-block-wporg-link-wrapper" href="<?php echo esc_url( site_url( '/block-editor/' ) ); ?>"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'Block Editor', 'wporg' ); ?></h3> +<!-- /wp:heading --> + +<!-- wp:paragraph {"fontSize":"small"} --> +<p class="has-small-font-size"><?php esc_html_e( 'Create the building blocks of WordPress.', 'wporg' ); ?></p> +<!-- /wp:paragraph --></a> +<!-- /wp:wporg/link-wrapper --> + +<!-- wp:wporg/link-wrapper --> +<a class="wp-block-wporg-link-wrapper" href="<?php echo esc_url( site_url( '/themes/' ) ); ?>"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'Themes', 'wporg' ); ?></h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><?php esc_html_e( 'Learn how to build your own themes.', 'wporg' ); ?></p> +<!-- /wp:paragraph --></a> +<!-- /wp:wporg/link-wrapper --> + +<!-- wp:wporg/link-wrapper --> +<a class="wp-block-wporg-link-wrapper" href="<?php echo esc_url( site_url( '/plugins/' ) ); ?>"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'Plugins', 'wporg' ); ?></h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><?php esc_html_e( 'Dive into the world of plugin authoring.', 'wporg' ); ?></p> +<!-- /wp:paragraph --></a> +<!-- /wp:wporg/link-wrapper --> + +<!-- wp:wporg/link-wrapper --> +<a class="wp-block-wporg-link-wrapper" href="<?php echo esc_url( site_url( '/apis/' ) ); ?>"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'Common APIs', 'wporg' ); ?></h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><?php esc_html_e( 'Explore APIs in the software and the ecosystem.', 'wporg' ); ?></p> +<!-- /wp:paragraph --></a> +<!-- /wp:wporg/link-wrapper --> + +<!-- wp:wporg/link-wrapper --> +<a class="wp-block-wporg-link-wrapper" href="<?php echo esc_url( site_url( '/advanced-administration/' ) ); ?>"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'Advanced Administration', 'wporg' ); ?></h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><?php esc_html_e( 'Dig into the technical side of site management.', 'wporg' ); ?></p> +<!-- /wp:paragraph --></a> +<!-- /wp:wporg/link-wrapper --> + +<!-- wp:wporg/link-wrapper --> +<a class="wp-block-wporg-link-wrapper" href="https://developer.wordpress.org/playground/"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'WordPress Playground', 'wporg' ); ?></h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><?php esc_html_e( 'Run WordPress entirely in your browser.', 'wporg' ); ?></p> +<!-- /wp:paragraph --></a> +<!-- /wp:wporg/link-wrapper --></div> +<!-- /wp:group --></div> +<!-- /wp:group --></div> +<!-- /wp:group --> + +<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"right":"var:preset|spacing|edge-space","left":"var:preset|spacing|edge-space"}}},"layout":{"type":"constrained"}} --> +<div class="wp-block-group alignfull" style="padding-right:var(--wp--preset--spacing--edge-space);padding-left:var(--wp--preset--spacing--edge-space)"><!-- wp:group {"align":"wide","layout":{"type":"default"}} --> +<div class="wp-block-group alignwide"><!-- wp:heading {"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"className":"is-style-short-text","fontSize":"heading-5"} --> +<h2 class="wp-block-heading is-style-short-text has-heading-5-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'API Reference', 'wporg' ); ?></h2> +<!-- /wp:heading --> + +<!-- wp:group {"style":{"spacing":{"blockGap":"var:preset|spacing|10"}},"className":"is-style-cards-grid","layout":{"type":"grid","minimumColumnWidth":"49%"},"fontSize":"small"} --> +<div class="wp-block-group is-style-cards-grid has-small-font-size"><!-- wp:wporg/link-wrapper --> +<a class="wp-block-wporg-link-wrapper" href="<?php echo esc_url( site_url( '/reference/' ) ); ?>"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'Code reference', 'wporg' ); ?></h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><?php esc_html_e( 'Read through the codebase documentation.', 'wporg' ); ?></p> +<!-- /wp:paragraph --></a> +<!-- /wp:wporg/link-wrapper --> + +<!-- wp:wporg/link-wrapper --> +<a class="wp-block-wporg-link-wrapper" href="<?php echo esc_url( site_url( '/rest-api/' ) ); ?>"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'REST API', 'wporg' ); ?></h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><?php esc_html_e( 'Start creating your own apps with WordPress.', 'wporg' ); ?></p> +<!-- /wp:paragraph --></a> +<!-- /wp:wporg/link-wrapper --> + +<!-- wp:wporg/link-wrapper --> +<a class="wp-block-wporg-link-wrapper" href="<?php echo esc_url( site_url( '/cli/' ) ); ?>commands"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'Run WP-CLI Commands', 'wporg' ); ?></h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><?php esc_html_e( 'Accelerate your workflow managing WordPress.', 'wporg' ); ?></p> +<!-- /wp:paragraph --></a> +<!-- /wp:wporg/link-wrapper --> + +<!-- wp:wporg/link-wrapper --> +<a class="wp-block-wporg-link-wrapper" href="<?php echo esc_url( site_url( '/coding-standards/' ) ); ?>"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'Coding Standards', 'wporg' ); ?></h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><?php esc_html_e( 'Ensure your code is up to date.', 'wporg' ); ?></p> +<!-- /wp:paragraph --></a> +<!-- /wp:wporg/link-wrapper --></div> +<!-- /wp:group --></div> +<!-- /wp:group --></div> +<!-- /wp:group --> + +<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"right":"var:preset|spacing|edge-space","left":"var:preset|spacing|edge-space"}}},"backgroundColor":"white","layout":{"type":"constrained"}} --> +<div class="wp-block-group alignfull has-white-background-color has-background" style="padding-right:var(--wp--preset--spacing--edge-space);padding-left:var(--wp--preset--spacing--edge-space)"><!-- wp:group {"align":"wide","layout":{"type":"default"}} --> +<div class="wp-block-group alignwide"><!-- wp:heading {"style":{"typography":{"fontStyle":"normal","fontWeight":"600"},"spacing":{"margin":{"bottom":"var:preset|spacing|10"}}},"fontSize":"heading-5"} --> +<h2 class="wp-block-heading has-heading-5-font-size" style="margin-bottom:var(--wp--preset--spacing--10);font-style:normal;font-weight:600"><?php esc_html_e( 'Developer Blog', 'wporg' ); ?></h2> +<!-- /wp:heading --> + +<!-- wp:paragraph {"style":{"spacing":{"margin":{"bottom":"var:preset|spacing|20","top":"var:preset|spacing|10"}}},"textColor":"charcoal-4","fontSize":"small"} --> +<p class="has-charcoal-4-color has-text-color has-small-font-size" style="margin-top:var(--wp--preset--spacing--10);margin-bottom:var(--wp--preset--spacing--20)"> +<?php echo wp_kses_post( + sprintf( + /* translators: %s: url for the developer blog */ + __( 'Catch up on the latest news from the <a href="%s">Developer Blog</a>.', 'wporg' ), + 'https://developer.wordpress.org/news/' + ) +); ?></p> +<!-- /wp:paragraph --> + +<!-- wp:wporg/latest-news {"blogId":719,"showCategories":true,"className":"is-style-cards"} /--></div> +<!-- /wp:group --></div> +<!-- /wp:group --> + +<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"left":"var:preset|spacing|edge-space","right":"var:preset|spacing|edge-space"}}},"layout":{"type":"constrained"}} --> +<div class="wp-block-group alignfull" style="padding-right:var(--wp--preset--spacing--edge-space);padding-left:var(--wp--preset--spacing--edge-space)"><!-- wp:group {"align":"wide","layout":{"type":"default"}} --> +<div class="wp-block-group alignwide"><!-- wp:heading {"style":{"typography":{"fontStyle":"normal","fontWeight":"600"},"spacing":{"margin":{"bottom":"var:preset|spacing|10"}}},"fontSize":"heading-5"} --> +<h2 class="wp-block-heading has-heading-5-font-size" style="margin-bottom:var(--wp--preset--spacing--10);font-style:normal;font-weight:600"><?php esc_html_e( 'Get Involved', 'wporg' ); ?></h2> +<!-- /wp:heading --> + +<!-- wp:paragraph {"style":{"spacing":{"margin":{"bottom":"var:preset|spacing|20","top":"var:preset|spacing|10"}}},"textColor":"charcoal-4","fontSize":"small"} --> +<p class="has-charcoal-4-color has-text-color has-small-font-size" style="margin-top:var(--wp--preset--spacing--10);margin-bottom:var(--wp--preset--spacing--20)"><?php esc_html_e( 'Start your journey to contribute to WordPress.', 'wporg' ); ?></p> +<!-- /wp:paragraph --> + +<!-- wp:group {"style":{"spacing":{"blockGap":"var:preset|spacing|10"}},"className":"is-style-cards-grid","layout":{"type":"grid","minimumColumnWidth":"49%"},"fontSize":"small"} --> +<div class="wp-block-group is-style-cards-grid has-small-font-size"><!-- wp:wporg/link-wrapper --> +<a class="wp-block-wporg-link-wrapper" href="https://make.wordpress.org/docs/handbook/"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'Documentation Contributor Handbook', 'wporg' ); ?></h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><?php esc_html_e( 'Help document WordPress.', 'wporg' ); ?></p> +<!-- /wp:paragraph --></a> +<!-- /wp:wporg/link-wrapper --> + +<!-- wp:wporg/link-wrapper --> +<a class="wp-block-wporg-link-wrapper" href="https://make.wordpress.org/core/handbook/"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'Contribute to WordPress', 'wporg' ); ?></h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><?php esc_html_e( 'Start contributing to core.', 'wporg' ); ?></p> +<!-- /wp:paragraph --></a> +<!-- /wp:wporg/link-wrapper --></div> +<!-- /wp:group --></div> +<!-- /wp:group --></div> +<!-- /wp:group --></div> +<!-- /wp:group --> + +<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"right":"var:preset|spacing|edge-space","left":"var:preset|spacing|edge-space","top":"var:preset|spacing|40","bottom":"var:preset|spacing|40"}},"border":{"bottom":{"color":"var:preset|color|white-opacity-15","style":"solid","width":"1px"}},"elements":{"link":{"color":{"text":"var:preset|color|white"}}}},"backgroundColor":"charcoal-2","textColor":"white","className":"wporg-front-page-footer","layout":{"type":"constrained"}} --> +<div class="wp-block-group alignfull wporg-front-page-footer has-white-color has-charcoal-2-background-color has-text-color has-background has-link-color" style="border-bottom-color:var(--wp--preset--color--white-opacity-15);border-bottom-style:solid;border-bottom-width:1px;padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--edge-space);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--edge-space)"><!-- wp:columns {"align":"wide","style":{"spacing":{"blockGap":{"top":"var:preset|spacing|20"}}},"className":"is-style-default"} --> +<div class="wp-block-columns alignwide is-style-default"><!-- wp:column {"verticalAlignment":"top","width":"50%","className":"is-left-column","layout":{"inherit":false}} --> +<div class="wp-block-column is-vertically-aligned-top is-left-column" style="flex-basis:50%"><!-- wp:heading {"style":{"typography":{"fontStyle":"normal","fontWeight":"600","lineHeight":"1.3"}},"fontSize":"heading-5"} --> +<h2 class="wp-block-heading has-heading-5-font-size" style="font-style:normal;font-weight:600;line-height:1.3"><?php esc_html_e( 'More resources', 'wporg' ); ?></h2> +<!-- /wp:heading --> + +<!-- wp:columns {"style":{"spacing":{"blockGap":{"left":"var:preset|spacing|50"}}}} --> +<div class="wp-block-columns"><!-- wp:column {"style":{"spacing":{"blockGap":"var:preset|spacing|10"}}} --> +<div class="wp-block-column"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"700","lineHeight":1.6},"elements":{"link":{"color":{"text":"var:preset|color|blueberry-2"}}}},"textColor":"blueberry-2","className":"is-style-short-text","fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading is-style-short-text has-blueberry-2-color has-text-color has-link-color has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:700;line-height:1.6"><a href="https://make.wordpress.org/docs/handbook/"><?php esc_html_e( 'Documentation', 'wporg' ); ?><br><?php esc_html_e( 'Contributor Handbook', 'wporg' ); ?></a></h3> +<!-- /wp:heading --></div> +<!-- /wp:column --> + +<!-- wp:column {"style":{"spacing":{"blockGap":"var:preset|spacing|10"}}} --> +<div class="wp-block-column"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"700","lineHeight":1.6},"elements":{"link":{"color":{"text":"var:preset|color|blueberry-2"}}}},"textColor":"blueberry-2","className":"is-style-short-text","fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading is-style-short-text has-blueberry-2-color has-text-color has-link-color has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:700;line-height:1.6"><a href="https://make.wordpress.org/core/handbook/"><?php esc_html_e( 'Core Contributor', 'wporg' ); ?><br>Handbook</a></h3> +<!-- /wp:heading --></div> +<!-- /wp:column --></div> +<!-- /wp:columns --></div> +<!-- /wp:column --> + +<!-- wp:column {"width":"50%","layout":{"inherit":false}} --> +<div class="wp-block-column" style="flex-basis:50%"><!-- wp:heading {"style":{"typography":{"fontStyle":"normal","fontWeight":"600","lineHeight":"1.3"}},"fontSize":"heading-5"} --> +<h2 class="wp-block-heading has-heading-5-font-size" style="font-style:normal;font-weight:600;line-height:1.3"><?php esc_html_e( 'Related', 'wporg' ); ?></h2> +<!-- /wp:heading --> + +<!-- wp:columns {"style":{"spacing":{"blockGap":{"left":"var:preset|spacing|50"}}}} --> +<div class="wp-block-columns"><!-- wp:column {"style":{"spacing":{"blockGap":"var:preset|spacing|10"}}} --> +<div class="wp-block-column"><!-- wp:heading {"level":3,"style":{"typography":{"fontStyle":"normal","fontWeight":"700","lineHeight":1.6},"elements":{"link":{"color":{"text":"var:preset|color|blueberry-2"}}}},"textColor":"blueberry-2","className":"is-style-short-text","fontSize":"normal","fontFamily":"inter"} --> +<h3 class="wp-block-heading is-style-short-text has-blueberry-2-color has-text-color has-link-color has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:700;line-height:1.6"><a href="https://learn.wordpress.org"><?php esc_html_e( 'Learn WordPress', 'wporg' ); ?></a></h3> +<!-- /wp:heading --> + +</div><!-- /wp:column --> +</div><!-- /wp:columns --> +</div><!-- /wp:column --> +</div><!-- /wp:columns --> +</div><!-- /wp:group --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/front-page-header.php b/source/wp-content/themes/wpr-developer-2024/patterns/front-page-header.php new file mode 100644 index 000000000..765b3db57 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/front-page-header.php @@ -0,0 +1,54 @@ +<?php +/** + * Title: Front Page Header + * Slug: wporg-developer-2023/front-page-header + * Inserter: no + */ + +?> + +<!-- wp:wporg/local-navigation-bar {"backgroundColor":"charcoal-2","style":{"elements":{"link":{"color":{"text":"var:preset|color|white"},":hover":{"color":{"text":"var:preset|color|white"}}}}},"textColor":"white","fontSize":"small"} --> + + <!-- wp:site-title {"level":0,"fontSize":"small","className":"wporg-local-navigation-bar__show-on-scroll"} /--> + + <!-- wp:navigation {"icon":"menu","overlayBackgroundColor":"charcoal-2","overlayTextColor":"white","layout":{"type":"flex","orientation":"horizontal"},"fontSize":"small","menuSlug":"developer"} /--> + +<!-- /wp:wporg/local-navigation-bar --> + +<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"right":"var:preset|spacing|edge-space","left":"var:preset|spacing|edge-space"}}},"backgroundColor":"charcoal-2","className":"has-white-color has-charcoal-2-background-color has-text-color has-background has-link-color","layout":{"type":"constrained"}} --> +<div class="wp-block-group alignfull has-white-color has-charcoal-2-background-color has-text-color has-background has-link-color" style="padding-right:var(--wp--preset--spacing--edge-space);padding-left:var(--wp--preset--spacing--edge-space)"> + + <!-- wp:group {"align":"wide","style":{"spacing":{"padding":{"top":"var:preset|spacing|40","bottom":"var:preset|spacing|40"},"blockGap":"var:preset|spacing|30"}},"layout":{"type":"flex","flexWrap":"wrap","verticalAlignment":"bottom"}} --> + <div class="wp-block-group alignwide" style="padding-top:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40)"> + + <!-- wp:heading {"level":1,"style":{"typography":{"fontSize":"50px","fontStyle":"normal","fontWeight":"400"}},"fontFamily":"eb-garamond"} --> + <h1 class="wp-block-heading has-eb-garamond-font-family" style="font-size:50px;font-style:normal;font-weight:400"><?php esc_html_e( 'Developer Resources', 'wporg' ); ?></h1> + <!-- /wp:heading --> + + <!-- wp:paragraph {"style":{"typography":{"lineHeight":"2.3"}},"textColor":"white"} --> + <p class="has-white-color has-text-color" style="line-height:2.3"><?php esc_html_e( '/The freedom to build', 'wporg' ); ?></p> + <!-- /wp:paragraph --> + + </div> + <!-- /wp:group --> + +</div> +<!-- /wp:group --> + +<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"var:preset|spacing|10","bottom":"var:preset|spacing|10"}}},"backgroundColor":"lemon-3","layout":{"type":"constrained"}} --> +<div class="wp-block-group alignfull has-lemon-3-background-color has-background" style="padding-top:var(--wp--preset--spacing--10);padding-bottom:var(--wp--preset--spacing--10)"> + + <!-- wp:paragraph {"align":"center","fontSize":"small"} --> + <p class="has-text-align-center has-small-font-size"> + <?php echo wp_kses_post( + sprintf( + /* translators: %1$s: version link, %2$s: version number */ + __( 'See <a href="%1$s">what has changed</a> in the WordPress %2$s API.', 'wporg' ), + '[wordpress_version_link]', + '[wordpress_version]' + ) + ); ?></p> + <!-- /wp:paragraph --> + +</div> +<!-- /wp:group --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/handbook-pagination.php b/source/wp-content/themes/wpr-developer-2024/patterns/handbook-pagination.php new file mode 100644 index 000000000..b3ea7098c --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/handbook-pagination.php @@ -0,0 +1,15 @@ +<?php +/** + * Title: Handbook Pagination + * Slug: wporg-developer-2023/handbook-pagination + * Inserter: no + */ + +?> + +<!-- wp:group {"align":"full","style":{"border":{"top":{"color":"var:preset|color|light-grey-1","width":"1px","style":"solid"}},"spacing":{"margin":{"top":"var:preset|spacing|20","bottom":"var:preset|spacing|20"},"padding":{"top":"var:preset|spacing|30","bottom":"var:preset|spacing|30"}}},"layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"space-between"}} --> +<div class="wp-block-group alignfull" style="border-top-color:var(--wp--preset--color--light-grey-1);border-top-style:solid;border-top-width:1px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-bottom:var(--wp--preset--spacing--30)"> + <!-- wp:post-navigation-link {"type":"previous","showTitle":true} /--> + <!-- wp:post-navigation-link {"showTitle":true} /--> +</div> +<!-- /wp:group --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/no-search-results.php b/source/wp-content/themes/wpr-developer-2024/patterns/no-search-results.php new file mode 100644 index 000000000..0357cae57 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/no-search-results.php @@ -0,0 +1,12 @@ +<?php +/** + * Title: No Search Results + * Slug: wporg-developer-2023/no-search-results + * Inserter: no + */ + +?> + +<!-- wp:paragraph {"placeholder":"Add text or blocks that will display when a query returns no results.","style":{"spacing":{"margin":{"top":"var:preset|spacing|40"}}}} --> +<p style="margin-top:var(--wp--preset--spacing--40)"><?php esc_attr_e( 'Sorry, but nothing matched your search terms.', 'wporg' ); ?></p> +<!-- /wp:paragraph --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/reference-content.php b/source/wp-content/themes/wpr-developer-2024/patterns/reference-content.php new file mode 100644 index 000000000..a4eef85b1 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/reference-content.php @@ -0,0 +1,61 @@ +<?php +/** + * Title: Reference Content + * Slug: wporg-developer-2023/reference-content + * Inserter: no + */ + +?> +zzz2 +<!-- wp:group {"tagName":"main","style":{"spacing":{"padding":{"left":"var:preset|spacing|edge-space","right":"var:preset|spacing|edge-space","bottom":"var:preset|spacing|60"}}},"className":"alignfull","layout":{"type":"constrained","wideSize":"1280px","contentSize":"680px"}} --> +<main class="wp-block-group alignfull" style="padding-right:var(--wp--preset--spacing--edge-space);padding-bottom:var(--wp--preset--spacing--60);padding-left:var(--wp--preset--spacing--edge-space)"> + + <!-- wp:columns {"align":"wide","style":{"spacing":{"blockGap":{"left":"var:preset|spacing|20"},"margin":{"top":"var:preset|spacing|20"}}}} --> + <div class="wp-block-columns alignwide" style="margin-top:var(--wp--preset--spacing--20)"> + + <!-- wp:column {"style":{"spacing":{"blockGap":"0"}}} --> + <div class="wp-block-column"> +zzz3 + <!-- wp:wporg/reference-new-updated /--> + + </div> + <!-- /wp:column --> + + <!-- wp:column {"style":{"spacing":{"blockGap":"0"}}} --> + <div class="wp-block-column"> + + <!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"space-between"}} --> + <div class="wp-block-group"> + + <!-- wp:heading {"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"fontSize":"heading-5"} --> + <h2 class="wp-block-heading has-heading-5-font-size" style="font-style:normal;font-weight:600"><?php esc_html_e( 'API reference', 'wporg' ); ?></h2> + <!-- /wp:heading --> + + <!-- wp:paragraph --> + <p> + <a href="https://developer.wordpress.org/apis/"> + <span aria-hidden="true"><?php esc_html_e( 'View all', 'wporg' ); ?></span> + <span class="screen-reader-text"><?php esc_html_e( 'View all API reference', 'wporg' ); ?></span> + </a> + </p> + <!-- /wp:paragraph --> + + </div> + <!-- /wp:group --> + + <!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|20","bottom":"var:preset|spacing|20","left":"var:preset|spacing|20","right":"var:preset|spacing|20"},"margin":{"top":"var:preset|spacing|20"}},"border":{"width":"1px","radius":"2px"}},"borderColor":"light-grey-1","layout":{"type":"constrained"}} --> + <div class="wp-block-group has-border-color has-light-grey-1-border-color" style="border-width:1px;border-radius:2px;margin-top:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--20);padding-right:var(--wp--preset--spacing--20);padding-bottom:var(--wp--preset--spacing--20);padding-left:var(--wp--preset--spacing--20)"> + + <!-- wp:navigation {"ref":148843,"overlayMenu":"never","className":"wporg-reference-list","layout":{"type":"flex","orientation":"vertical"},"style":{"spacing":{"blockGap":"0"}},"fontSize":"small"} /--> + + </div> + <!-- /wp:group --> + + </div> + <!-- /wp:column --> + + </div> + <!-- /wp:columns --> + +</div> +<!-- /wp:group --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/release-post-type-filters.php b/source/wp-content/themes/wpr-developer-2024/patterns/release-post-type-filters.php new file mode 100644 index 000000000..67c7b91f4 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/release-post-type-filters.php @@ -0,0 +1,14 @@ +<?php +/** + * Title: Release Post Type filters + * Slug: wporg-developer-2023/release-post-type-filters + * Inserter: no + */ + +?> + +<!-- wp:wporg/form-wrapper {"className":"wporg-post-type-filters"} --> +<div class="wp-block-wporg-form-wrapper"> + <!-- wp:wporg/search-filters /--> +</div> +<!-- /wp:wporg/form-wrapper --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/search-content.php b/source/wp-content/themes/wpr-developer-2024/patterns/search-content.php new file mode 100644 index 000000000..63d5f3536 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/search-content.php @@ -0,0 +1,54 @@ +<?php +/** + * Title: Search Content + * Slug: wporg-developer-2023/search-content + * Inserter: no + */ + +?> + +<!-- wp:query {"queryId":0,"query":{"inherit":true,"perPage":25},"align":"wide"} --> +<div class="wp-block-query alignwide"> + + <!-- wp:group {"className":"align-left","layout":{"type":"constrained","contentSize":"","justifyContent":"left"},"style":{"spacing":{"margin":{"bottom":"var:preset|spacing|40"}}}} --> + <div class="wp-block-group align-left" style="margin-bottom:var(--wp--preset--spacing--40)"> + + <!-- wp:group {"align":"wide","className":"wporg-search-controls","layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"space-between","verticalAlignment":"top"},"style":{"spacing":{"margin":{"top":"var:preset|spacing|20","bottom":"var:preset|spacing|20"}}}} --> + <div id="wporg-search" class="wp-block-group alignwide wporg-search-controls" style="margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20)"> + + <!-- wp:search {"label":"<?php esc_attr_e( 'Search', 'wporg' ); ?>","showLabel":false,"placeholder":"<?php esc_attr_e( 'Search resources', 'wporg' ); ?>","width":232,"widthUnit":"px","buttonText":"<?php esc_attr_e( 'Search', 'wporg' ); ?>","buttonPosition":"button-inside","buttonUseIcon":true,"className":"is-style-secondary-search-control wporg-filtered-search-form"} /--> + + <!-- wp:wporg/resource-select {"label":"<?php esc_attr_e( 'Select resource to search', 'wporg' ); ?>","hideLabelFromVision":true} /--> + + </div> + <!-- /wp:group --> + + <!-- wp:wporg/search-results-context {"style":{"spacing":{"padding":{"top":"var:preset|spacing|20","bottom":"var:preset|spacing|20"}},"elements":{"link":{"color":{"text":"var:preset|color|charcoal-4"}}}},"textColor":"charcoal-4","fontSize":"small"} /--> + + <!-- wp:post-template {"align":"wide"} --> + + <!-- wp:wporg/search-post /--> + + <!-- /wp:post-template --> + + <!-- wp:query-no-results --> + + <!-- wp:paragraph {"placeholder":"Add text or blocks that will display when a query returns no results.","style":{"spacing":{"margin":{"top":"var:preset|spacing|40"}}}} --> + <p style="margin-top:var(--wp--preset--spacing--40)"><?php esc_attr_e( 'Sorry, but nothing matched your search terms.', 'wporg' ); ?></p> + <!-- /wp:paragraph --> + + <!-- /wp:query-no-results --> + + </div> + <!-- /wp:group --> + + <!-- wp:query-pagination {"layout":{"type":"flex","justifyContent":"center"}} --> + <!-- wp:query-pagination-previous {"label":"Previous"} /--> + + <!-- wp:query-pagination-numbers /--> + + <!-- wp:query-pagination-next {"label":"Next"} /--> + <!-- /wp:query-pagination --> + +</div> +<!-- /wp:query --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/search-field.php b/source/wp-content/themes/wpr-developer-2024/patterns/search-field.php new file mode 100644 index 000000000..82362a100 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/search-field.php @@ -0,0 +1,16 @@ +<?php +/** + * Title: Search Field + * Slug: wporg-developer-2023/search-field + * Inserter: no + */ + +?> + +<!-- wp:group {"align":"wide","style":{"spacing":{"margin":{"top":"var:preset|spacing|20","bottom":"var:preset|spacing|20"}}}} --> +<div id="wporg-search" class="wp-block-group alignwide" style="margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20)"> + + <!-- wp:search {"label":"<?php esc_attr_e( 'Search', 'wporg' ); ?>","showLabel":false,"placeholder":"<?php esc_attr_e( 'Search resources', 'wporg' ); ?>","width":232,"widthUnit":"px","buttonText":"<?php esc_attr_e( 'Search', 'wporg' ); ?>","buttonPosition":"button-inside","buttonUseIcon":true} /--> + +</div> +<!-- /wp:group --> diff --git a/source/wp-content/themes/wpr-developer-2024/patterns/single-content.php b/source/wp-content/themes/wpr-developer-2024/patterns/single-content.php new file mode 100644 index 000000000..d70fc035c --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/patterns/single-content.php @@ -0,0 +1,12 @@ +<?php +/** + * Title: Single Content + * Slug: wporg-developer-2023/single-content + * Inserter: no + */ + + // This content is filtered by `filter_code_content` in functions.php to add the post content. + // See: https://github.com/WordPress/wporg-developer/pull/194 +?> + +<!-- wp:post-content {"style":{"spacing":{"blockGap":"var:preset|spacing|40"}}} /--> diff --git a/source/wp-content/themes/wpr-developer-2024/screenshot.png b/source/wp-content/themes/wpr-developer-2024/screenshot.png new file mode 100644 index 000000000..2fba94499 Binary files /dev/null and b/source/wp-content/themes/wpr-developer-2024/screenshot.png differ diff --git a/source/wp-content/themes/wpr-developer-2024/scss/admin.scss b/source/wp-content/themes/wpr-developer-2024/scss/admin.scss new file mode 100644 index 000000000..406f5e76e --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/scss/admin.scss @@ -0,0 +1,131 @@ +// stylelint-disable selector-id-pattern -- These are generated names. + +/* =Admin CSS +----------------------------------------------- */ + +#wporg_parsed_ticket { + width: 100px; +} + +#ticket_info_icon { + font-size: 14px; + color: #a00; +} + +.attachment_controls .spinner { + position: relative; + margin-top: 3px; + bottom: 4px; + float: none; +} + +.attachment_controls { + margin-bottom: 10px; + display: block; +} + + +/* Explanations */ +.fixed { + .column-has_explanation { + width: 2em; + + .dashicons { + width: 30px; + } + } + + tbody .column-has_explanation a { + color: #72777c; + display: inline-block; + margin-top: 4px; + + &:focus, + &:hover { + color: #0073aa; + } + + .dashicons { + font-size: 26px; + } + + .screen-reader-text { + + @media screen and (max-width: 782px) { + position: static; + -webkit-clip-path: none; + clip-path: none; + width: auto; + height: auto; + margin: 0; + } + } + + [aria-hidden="true"] { + + @media screen and (max-width: 782px) { + display: none; + } + } + } +} + +.post-type-wporg_explanations .page-title-action { + display: none; +} + +.expl-row-actions { + display: block; + + a { + display: inline-block; + + &:first-child { + padding-right: 6px; + padding-left: 0; + } + + &:nth-child(n+2) { + padding-left: 8px; + border-left: 1px solid #ccc; + } + } +} + +.status { + font-weight: 700; + + &.pending { + color: #f00; + } + + &.publish { + color: #008000; + } +} + +.post-type-wp-parser-function, +.post-type-wp-parser-class, +.post-type-wp-parser-method, +.post-type-wp-parser-hook { + .form-table { + th { + padding-top: 10px; + padding-bottom: 10px; + } + + td { + padding: 10px; + + p { + margin-top: 0; + } + } + } +} + +/* Parsed Content Meta Box */ +.wporg_parsed_content { + width: 100%; +} + diff --git a/source/wp-content/themes/wpr-developer-2024/scss/settings/_colors.scss b/source/wp-content/themes/wpr-developer-2024/scss/settings/_colors.scss new file mode 100644 index 000000000..3165ccad7 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/scss/settings/_colors.scss @@ -0,0 +1,88 @@ +//Colors + +$color-white: #fff; +$color-black: #000; + +// wp4.css Colors. +$wp4--link-color: #21759b; +$wp4--link-color--hover: #d54e21; + +// Color map of the WordPress core palette hex values. +// Please keep this map in sync with the reference: https://codepen.io/ryelle/full/WNGVEjw. +$colors: ( + gray-0: #f6f7f7, + gray-2: #f0f0f1, + gray-5: #dcdcde, + gray-10: #c3c4c7, + gray-20: #a7aaad, + gray-30: #8c8f94, + gray-40: #787c82, + gray-50: #646970, + gray-60: #50575e, + gray-70: #3c434a, + gray-80: #2c3338, + gray-90: #1d2327, + gray-100: #101517, + blue-0: #f0f6fc, + blue-5: #c5d9ed, + blue-10: #9ec2e6, + blue-20: #72aee6, + blue-30: #4f94d4, + blue-40: #3582c4, + blue-50: #2271b1, + blue-60: #135e96, + blue-70: #0a4b78, + blue-80: #043959, + blue-90: #01263a, + blue-100: #00131c, + red-0: #fcf0f1, + red-5: #facfd2, + red-10: #ffabaf, + red-20: #ff8085, + red-30: #f86368, + red-40: #e65054, + red-50: #d63638, + red-60: #b32d2e, + red-70: #8a2424, + red-80: #691c1c, + red-90: #451313, + red-100: #240a0a, + yellow-0: #fcf9e8, + yellow-5: #f5e6ab, + yellow-10: #f2d675, + yellow-20: #f0c33c, + yellow-30: #dba617, + yellow-40: #bd8600, + yellow-50: #996800, + yellow-60: #755100, + yellow-70: #614200, + yellow-80: #4a3200, + yellow-90: #362400, + yellow-100: #211600, + green-0: #edfaef, + green-5: #b8e6bf, + green-10: #68de7c, + green-20: #1ed14b, + green-30: #00ba37, + green-40: #00a32a, + green-50: #008a20, + green-60: #007017, + green-70: #005c12, + green-80: #00450c, + green-90: #003008, + green-100: #001c05 +); + +// Simple function to retreive colors in the $colors map. +// e.g. `background-color: color(gray-50);` + +@function get-color($key) { + + @if map-has-key($colors, $key) { + + @return map-get($colors, $key); + } + + @warn "Unknown `#{$key}` in $colors."; + @return null; +} diff --git a/source/wp-content/themes/wpr-developer-2024/scss/settings/_dimensions.scss b/source/wp-content/themes/wpr-developer-2024/scss/settings/_dimensions.scss new file mode 100644 index 000000000..4433b7821 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/scss/settings/_dimensions.scss @@ -0,0 +1,5 @@ +$single-handbook-content-width: 960px; +$single-handbook-toc-width: 15vw; +$single-handbook-toc-position-top: 150px; + +$devhub-wrap-content-width: 60em; diff --git a/source/wp-content/themes/wpr-developer-2024/scss/settings/_index.scss b/source/wp-content/themes/wpr-developer-2024/scss/settings/_index.scss new file mode 100644 index 000000000..6d0657787 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/scss/settings/_index.scss @@ -0,0 +1,19 @@ + +// stylelint-disable + +$header-font: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, sans-serif; + +$body-font: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + +$serif-font: Georgia, Times, serif; + +$code-font: + Hack, "Fira Code", Consolas, Menlo, Monaco, "Andale Mono", + "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", + "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", + "Courier New", Courier, monospace; + +// stylelint-enable + +@import "colors"; +@import "dimensions"; diff --git a/source/wp-content/themes/wpr-developer-2024/src/chapter-list/block.json b/source/wp-content/themes/wpr-developer-2024/src/chapter-list/block.json new file mode 100644 index 000000000..6542ea5f8 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/chapter-list/block.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/chapter-list", + "version": "0.1.0", + "title": "Chapter Navigation", + "category": "widgets", + "icon": "smiley", + "description": "", + "usesContext": [ "postId" ], + "attributes": { + "postType": { + "type": "string" + } + }, + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true, + "blockGap": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css", + "viewScript": "file:./view.js" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/chapter-list/block.php b/source/wp-content/themes/wpr-developer-2024/src/chapter-list/block.php new file mode 100644 index 000000000..b546790c5 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/chapter-list/block.php @@ -0,0 +1,78 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Chapter_List; + +require_once __DIR__ . '/class-chapter-walker.php'; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/chapter-list', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $post_id = $block->context['postId']; + $post_type = get_post_type( $post_id ); + + $args = array( + 'title_li' => '', + 'echo' => 0, + 'sort_column' => 'menu_order, title', + 'post_type' => $post_type, + + // Use custom walker that excludes display of orphaned pages. (An ancestor + // of such a page is likely not published and thus this is not accessible.) + 'walker' => new Chapter_Walker(), + ); + + $post_type_obj = get_post_type_object( $post_type ); + + if ( $post_type_obj && current_user_can( $post_type_obj->cap->read_private_posts ) ) { + $args['post_status'] = array( 'publish', 'private' ); + } + + $content = wp_list_pages( $args ); + + $header = '<div class="wporg-chapter-list__header">'; + $header .= do_blocks( + '<!-- wp:heading {"style":{"typography":{"fontStyle":"normal","fontWeight":"400"}},"fontSize":"normal","fontFamily":"inter"} --> + <h2 class="wp-block-heading has-inter-font-family has-normal-font-size" style="font-style:normal;font-weight:400">' . esc_html__( 'Chapters', 'wporg' ) . '</h2> + <!-- /wp:heading -->' + ); + $header .= '<button type="button" class="wporg-chapter-list__toggle" aria-expanded="false">'; + $header .= '<span class="screen-reader-text">' . esc_html__( 'Chapter list', 'wporg' ) . '</span>'; + $header .= '</button>'; + $header .= '</div>'; + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<aside %1$s><nav>%2$s<ul class="wporg-chapter-list__list">%3$s</ul></nav></aside>', + $wrapper_attributes, + $header, + $content + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/chapter-list/class-chapter-walker.php b/source/wp-content/themes/wpr-developer-2024/src/chapter-list/class-chapter-walker.php new file mode 100644 index 000000000..0db2c87ef --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/chapter-list/class-chapter-walker.php @@ -0,0 +1,95 @@ +<?php +/** + * Custom walker for chapter block page list. + * + * Identical to `Walker_Page` except with a `walk()` that ignores orphaned + * pages, which are pages with an ancestor that is not published. + */ + +namespace WordPressdotorg\Theme\Developer_2023\Chapter_List; + +class Chapter_Walker extends \Walker_Page { + + /** + * Display array of elements hierarchically. + * + * Does not assume any existing order of elements. + * + * $max_depth = -1 means flatly display every element. + * $max_depth = 0 means display all levels. + * $max_depth > 0 specifies the number of display levels. + * + * NOTE: This is identical to `Walker::walk()` except that it ignores orphaned + * pages, which are essentially pages whose ancestor is not published. + * + * @param array $elements An array of elements. + * @param int $max_depth The maximum hierarchical depth. + * @param mixed ...$args Optional additional arguments. + * @return string The hierarchical item output. + */ + public function walk( $elements, $max_depth, ...$args ) { + $output = ''; + + //invalid parameter or nothing to walk + if ( $max_depth < -1 || empty( $elements ) ) { + return $output; + } + + $parent_field = $this->db_fields['parent']; + + // flat display + if ( -1 === $max_depth ) { + $empty_array = array(); + foreach ( $elements as $e ) { + $this->display_element( $e, $empty_array, 1, 0, $args, $output ); + } + return $output; + } + + /* + * Need to display in hierarchical order. + * Separate elements into two buckets: top level and children elements. + * Children_elements is two dimensional array, eg. + * Children_elements[10][] contains all sub-elements whose parent is 10. + */ + $top_level_elements = array(); + $children_elements = array(); + foreach ( $elements as $e ) { + if ( empty( $e->$parent_field ) ) { + $top_level_elements[] = $e; + } else { + $children_elements[ $e->$parent_field ][] = $e; + } + } + + /* + * When none of the elements is top level. + * Assume the first one must be root of the sub elements. + */ + if ( empty( $top_level_elements ) ) { + $first = array_slice( $elements, 0, 1 ); + $root = $first[0]; + + $top_level_elements = array(); + $children_elements = array(); + foreach ( $elements as $e ) { + if ( $root->$parent_field === $e->$parent_field ) { + $top_level_elements[] = $e; + } else { + $children_elements[ $e->$parent_field ][] = $e; + } + } + } + + foreach ( $top_level_elements as $e ) { + $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output ); + } + + /* + * Here is where it differs from the original `walk()`. The original would + * automatically display orphans. + */ + + return $output; + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/chapter-list/index.js b/source/wp-content/themes/wpr-developer-2024/src/chapter-list/index.js new file mode 100644 index 000000000..8dac349aa --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/chapter-list/index.js @@ -0,0 +1,15 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/chapter-list/style.scss b/source/wp-content/themes/wpr-developer-2024/src/chapter-list/style.scss new file mode 100644 index 000000000..09b8094d3 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/chapter-list/style.scss @@ -0,0 +1,158 @@ +.wp-block-wporg-chapter-list { + --local--line-height: var(--wp--custom--body--small--typography--line-height); + --local--icon-size: calc(var(--local--line-height) * 1em); + line-height: var(--local--line-height); + + @media (max-width: 767px) { + border: 1px solid var(--wp--preset--color--light-grey-1); + border-radius: 2px; + } + + .wporg-chapter-list__header { + position: relative; + + @media (max-width: 767px) { + padding: 15px var(--wp--preset--spacing--20); + } + + .wp-block-heading { + margin-bottom: 0; + } + } + + .wporg-chapter-list__list { + + @media (max-width: 767px) { + display: none; + margin-top: 0; + padding: 0 var(--wp--preset--spacing--20) 15px; + } + } + + ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; + padding-inline-start: 0; + } + + li { + margin-block: calc(var(--wp--preset--spacing--20) / 4); + color: var(--wp--preset--color--charcoal-4); + padding-inline-start: var(--local--icon-size); + position: relative; + + &::before { + content: ""; + display: inline-block; + position: absolute; + inset-inline-start: 0; + width: var(--local--icon-size); + height: var(--local--icon-size); + mask-image: url(../../images/dot.svg); + mask-repeat: no-repeat; + mask-position: center; + background-color: var(--wp--preset--color--charcoal-4); + } + } + + .children { + // Shift the children to the left by half the icon size, allowing for the dot width of 4px. + margin-inline-start: calc((var(--local--icon-size) - 4px) * -0.5); + } + + a { + text-decoration: none; + color: inherit; + } + + &.has-js-control { + .page_item_has_children { + padding-inline-start: 0; + + &::before { + display: none; + } + } + + .children { + display: none; + padding-inline-start: var(--local--icon-size); + + &.is-open { + display: revert; + } + } + } + + .wporg-chapter-list__button-group { + display: flex; + align-items: flex-start; + } + + .wporg-chapter-list__toggle, + .wporg-chapter-list__button-group > button { + font-size: inherit; + background-color: transparent; + border: none; + padding: 0; + cursor: pointer; + height: var(--local--icon-size); + + &::before { + content: ""; + display: inline-block; + height: var(--local--icon-size); + width: var(--local--icon-size); + mask-image: url(../../images/chevron-small.svg); + mask-repeat: no-repeat; + mask-position: center; + transform: rotate(-90deg); + background-color: var(--wp--preset--color--charcoal-4); + } + + &[aria-expanded="true"]::before { + transform: revert; + } + + &:focus-visible { + outline: 1px dashed var(--wp--preset--color--blueberry-1); + } + } + + .wporg-chapter-list__toggle { + display: flex; + justify-content: flex-end; + align-items: center; + position: absolute; + top: 0; + right: 0; + width: 100%; + height: 100%; + padding: 0 var(--wp--preset--spacing--20) 0 0; + + @media (min-width: 768px) { + display: none; + } + + &[aria-expanded="true"]::before { + background-color: var(--wp--preset--color--charcoal-1); + } + } + + // Descendent is `span` if there are children, or `a` if not. + .current_page_item, + .current_page_item > span a, + .current_page_item > a { + color: var(--wp--preset--color--charcoal-1); + } + + .current_page_item > span a, + .current_page_item > a { + font-weight: 700; + } + + .current_page_item > span button::before { + background-color: var(--wp--preset--color--charcoal-1); + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/chapter-list/view.js b/source/wp-content/themes/wpr-developer-2024/src/chapter-list/view.js new file mode 100644 index 000000000..727b0389a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/chapter-list/view.js @@ -0,0 +1,83 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; + +const init = () => { + const container = document.querySelector( '.wp-block-wporg-chapter-list' ); + const toggleButton = container?.querySelector( '.wporg-chapter-list__toggle' ); + const list = container?.querySelector( '.wporg-chapter-list__list' ); + + if ( toggleButton && list ) { + toggleButton.addEventListener( 'click', function () { + if ( toggleButton.getAttribute( 'aria-expanded' ) === 'true' ) { + toggleButton.setAttribute( 'aria-expanded', false ); + list.removeAttribute( 'style' ); + } else { + toggleButton.setAttribute( 'aria-expanded', true ); + list.setAttribute( 'style', 'display:block;' ); + } + } ); + } + + if ( container ) { + container.classList.toggle( 'has-js-control' ); + + const parents = container.querySelectorAll( '.page_item_has_children' ); + parents.forEach( ( item ) => { + // Get link, remove (will re-ad later). + const link = item.querySelector( ':scope > a' ); + link.remove(); + + // Get submenu + const submenu = item.querySelector( ':scope > ul' ); + + // Create the toggle button. + const button = document.createElement( 'button' ); + button.setAttribute( 'aria-expanded', false ); + // translators: %s link title. + button.setAttribute( 'aria-label', sprintf( __( 'Open %s submenu', 'wporg' ), link.innerText ) ); + button.onclick = () => { + submenu.classList.toggle( 'is-open' ); + // This attribute returns a string. + const isOpen = button.getAttribute( 'aria-expanded' ); + button.setAttribute( 'aria-expanded', isOpen === 'false' ); + if ( isOpen === 'false' ) { + button.setAttribute( + 'aria-label', + // translators: %s link title. + sprintf( __( 'Close %s submenu', 'wporg' ), link.innerText ) + ); + } else { + button.setAttribute( + 'aria-label', + // translators: %s link title. + sprintf( __( 'Open %s submenu', 'wporg' ), link.innerText ) + ); + } + }; + + const buttonGroup = document.createElement( 'span' ); + buttonGroup.className = 'wporg-chapter-list__button-group'; + buttonGroup.append( button, link ); + + item.insertBefore( buttonGroup, submenu ); + + // Automatically open the trail to the current page. + if ( + item.classList.contains( 'current_page_item' ) || + item.classList.contains( 'current_page_ancestor' ) + ) { + submenu.classList.toggle( 'is-open' ); + button.setAttribute( 'aria-expanded', true ); + button.setAttribute( + 'aria-label', + // translators: %s link title. + sprintf( __( 'Close %s submenu', 'wporg' ), link.innerText ) + ); + } + } ); + } +}; + +window.addEventListener( 'load', init ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/cli-command-table/block.json b/source/wp-content/themes/wpr-developer-2024/src/cli-command-table/block.json new file mode 100644 index 000000000..ed1148ee1 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/cli-command-table/block.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/cli-command-table", + "version": "0.1.0", + "title": "CLI Command Table", + "category": "widgets", + "icon": "smiley", + "description": "Show all parent CLI commands in table format.", + "supports": { + "html": false, + "color": { + "link": true + }, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/cli-command-table/block.php b/source/wp-content/themes/wpr-developer-2024/src/cli-command-table/block.php new file mode 100644 index 000000000..4560e8616 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/cli-command-table/block.php @@ -0,0 +1,76 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_CLI_Command_Table; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/cli-command-table', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @return string Returns the block markup. + */ +function render() { + $posts = get_posts( + array( + 'post_type' => 'command', + 'orderby' => 'title', + 'order' => 'ASC', + 'post_parent' => 0, + 'nopaging' => true, + ) + ); + + if ( is_wp_error( $posts ) || empty( $posts ) ) { + return ''; + } + + $table_block = sprintf( + '<!-- wp:wporg/code-table {"className":"is-responsive","itemsToShow":50,"headings":%s,"rows":%s,"style":{"spacing":{"margin":{"top":"var:preset|spacing|20"}}}} /--> ', + wp_json_encode( array( __( 'Command', 'wporg' ), __( 'Description', 'wporg' ) ) ), + wp_json_encode( get_row_data( $posts ) ) + ); + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %1$s>%2$s</section>', + $wrapper_attributes, + do_blocks( $table_block ) + ); +} + +/** + * Get the changelog data for the current post. + * + * @param WP_Post[] $posts The posts to get the data from. + * @return array + */ +function get_row_data( $posts ) { + $rows = array(); + + foreach ( $posts as $post ) { + $title_link = sprintf( + '<a href="%1$s">%2$s</a>', + esc_url( get_the_permalink( $post ) ), + esc_html( $post->post_title ), + ); + + $rows[] = array( $title_link, get_the_excerpt( $post ) ); + } + + return $rows; +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/cli-command-table/index.js b/source/wp-content/themes/wpr-developer-2024/src/cli-command-table/index.js new file mode 100644 index 000000000..26d63bc5a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/cli-command-table/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/cli-command-table/style.scss b/source/wp-content/themes/wpr-developer-2024/src/cli-command-table/style.scss new file mode 100644 index 000000000..bbbcfff8a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/cli-command-table/style.scss @@ -0,0 +1,7 @@ +@media screen and (max-width: 500px) { + .wp-block-wporg-cli-command-table { + thead { + display: none; + } + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-changelog/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-changelog/block.json new file mode 100644 index 000000000..55625ce51 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-changelog/block.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-changelog", + "version": "0.1.0", + "title": "Code Reference: Changelog", + "category": "widgets", + "icon": "smiley", + "description": "Show the changelog for a function, class, or method.", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-changelog/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-changelog/block.php new file mode 100644 index 000000000..7d5113d7b --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-changelog/block.php @@ -0,0 +1,93 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Changelog; + +use function DevHub\get_changelog_data; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-changelog', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $changelog_data = get_changelog_data(); + + if ( empty( $changelog_data ) ) { + return ''; + } + + $title_block = sprintf( + '<!-- wp:heading {"fontSize":"heading-5"} --><h2 class="wp-block-heading has-heading-5-font-size">%s</h2><!-- /wp:heading -->', + __( 'Changelog', 'wporg' ) + ); + + $table_block = sprintf( + '<!-- wp:wporg/code-table {"itemsToShow":5,"headings":%s,"rows":%s,"style":{"spacing":{"margin":{"top":"var:preset|spacing|20"}}}} /--> ', + wp_json_encode( array( __( 'Version', 'wporg' ), __( 'Description', 'wporg' ) ) ), + wp_json_encode( get_row_data( $changelog_data ) ) + ); + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %1$s>%2$s %3$s</section>', + $wrapper_attributes, + do_blocks( $title_block ), + do_blocks( $table_block ) + ); +} + +/** + * Get the changelog data for the current post. + * + * @param array $changelog_data + * @return array + */ +function get_row_data( $changelog_data ) { + $rows = array(); + $count = count( $changelog_data ); + $i = 0; + $changelog_data = array_reverse( $changelog_data ); + + foreach ( $changelog_data as $version => $data ) { + // Add "Introduced." for the initial version description, last since the array is reversed. + $data['description'] = ( ( $count - 1 ) == $i ) ? __( 'Introduced.', 'wporg' ) : $data['description']; + + $version_link = sprintf( + '<a href="%1$s" alt="%2$s">%3$s</a>', + esc_url( $data['since_url'] ), + esc_attr( "WordPress {$version}" ), + esc_html( $version ) + ); + + $i++; + + $rows[] = array( $version_link, $data['description'] ); + } + + return $rows; +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-changelog/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-changelog/index.js new file mode 100644 index 000000000..26d63bc5a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-changelog/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-changelog/style.scss b/source/wp-content/themes/wpr-developer-2024/src/code-changelog/style.scss new file mode 100644 index 000000000..a80f02c56 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-changelog/style.scss @@ -0,0 +1,6 @@ +.wp-block-wporg-code-reference-changelog .wp-block-table { + th:first-child, + td:first-child { + max-width: 8%; + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/block.json new file mode 100644 index 000000000..38e4a8590 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/block.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-comment-edit", + "version": "0.1.0", + "title": "Code Reference: Editing a comment", + "category": "widgets", + "icon": "smiley", + "description": "Shows the comment form for editing a comment.", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/block.php new file mode 100644 index 000000000..59f366e87 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/block.php @@ -0,0 +1,68 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Comment_Edit; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-comment-edit', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $comment_id = get_query_var( 'edit_user_note' ); + $comment = get_comment( $comment_id ); + $can_user_edit = \DevHub\can_user_edit_note( $comment_id ); + + // Move this to the redirect + if ( ! ( $comment && $can_user_edit ) ) { + return ''; + } + + $has_parent = $comment->comment_parent ? true : false; + $parent = $has_parent ? get_comment( $comment->comment_parent ) : false; + + ob_start(); + + if ( $has_parent ) { + echo \DevHub_User_Submitted_Content::wp_editor_feedback( $comment, 'show', true ); + } else { + $args = \DevHub_User_Submitted_Content::comment_form_args( $comment, 'edit' ); + comment_form( $args ); + } + $form = ob_get_clean(); + + $title_block = sprintf( + '<h2 class="wp-block-heading">%s %d</h2>', + $has_parent ? __( 'Edit Feedback', 'wporg' ) : __( 'Edit Note', 'wporg' ), + $comment_id + ); + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %1$s>%2$s %3$s %4$s</section>', + $wrapper_attributes, + $title_block, + __( "<p>You can edit this note as long as it's in moderation.</p>", 'wporg' ), + $form, + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/edit.js b/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/edit.js new file mode 100644 index 000000000..054d05ee0 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/edit.js @@ -0,0 +1,5 @@ +import ServerSideRender from '@wordpress/server-side-render'; + +export default function Edit( { name, attributes } ) { + return <ServerSideRender block={ name } attributes={ attributes } />; +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/index.js new file mode 100644 index 000000000..26d63bc5a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/style.scss b/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/style.scss new file mode 100644 index 000000000..2ddf50c7b --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-comment-edit/style.scss @@ -0,0 +1 @@ +/* css styles */ diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-comment-form/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-comment-form/block.json new file mode 100644 index 000000000..0c5e3ec6e --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-comment-form/block.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-comment-form", + "version": "0.1.0", + "title": "Code Reference: Comment form", + "category": "widgets", + "icon": "smiley", + "description": "Shows the comment form.", + "usesContext": [ "postId" ], + "supports": { + "align": true, + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-comment-form/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-comment-form/block.php new file mode 100644 index 000000000..e9bd6c796 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-comment-form/block.php @@ -0,0 +1,92 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Comment_Form; + +use function DevHub\can_user_post_note; + +add_action( 'init', __NAMESPACE__ . '\init' ); +add_filter( 'comment_form_submit_field', __NAMESPACE__ . '\modify_submit_field', 10, 2 ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-comment-form', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + if ( ! comments_open() || ! can_user_post_note( true, get_the_ID() ) ) { + return ''; + } + + if ( is_user_logged_in() ) { + ob_start(); // Capture form output + $args = \DevHub_User_Submitted_Content::comment_form_args(); + comment_form( $args ); + $form = ob_get_clean(); // End capture + + $editor_rules = \DevHub_User_Submitted_Content::get_editor_rules(); + $output = "<div class='wp-block-wporg-code-reference-comment-form-content'>{$form}<div class='comment-rules'>{$editor_rules}</div></div>"; + + } else { + $output = '<p>' . sprintf( + /* translators: %s: login URL */ + __( 'You must <a href="%s">log in</a> before being able to contribute a note or feedback.', 'wporg' ), + esc_url( 'https://login.wordpress.org/?redirect_to=' . urlencode( get_permalink() ) ) + ) . '</p>'; + } + + // If there are comments the heading will be displayed at the top of that block, and won't be required again here + $ordered_comments = wporg_developer_get_ordered_notes(); + $title_block = empty( $ordered_comments ) ? sprintf( + '<!-- wp:heading --><h2 class="wp-block-heading">%s</h2><!-- /wp:heading -->', + __( 'User Contributed Notes', 'wporg' ) + ) : ''; + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %1$s>%2$s %3$s</section>', + $wrapper_attributes, + $title_block, + $output, + ); +} + +/** + * Update the for submit field html to include the log in html. + * + * @param string $submit_field + * @return string + */ +function modify_submit_field( $submit_field ) { + $user_identity = wp_get_current_user(); + + $logged_in_link = sprintf( + wp_kses_post( + /* translators: %1$s: User url, %2$s: User name, %3$s: Logout url. */ + __( 'Logged in as <a href="%1$s" aria-label="%2$s">%2$s</a>. (<a href="%3$s">log out</a>)', 'wporg' ) + ), + esc_url( 'https://profiles.wordpress.org/' . esc_attr( $user_identity->user_nicename ) . '/' ), + esc_attr( $user_identity->display_name ), + esc_url( wp_logout_url( apply_filters( 'the_permalink', get_permalink() ) ) ) + ); + + return str_replace( '</p>', "<span class='logged-in-as'>{$logged_in_link}</span></p>", $submit_field ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-comment-form/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-comment-form/index.js new file mode 100644 index 000000000..4ccd2e67b --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-comment-form/index.js @@ -0,0 +1,15 @@ +import { registerBlockType } from '@wordpress/blocks'; +import './style.scss'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-comment-form/style.scss b/source/wp-content/themes/wpr-developer-2024/src/code-comment-form/style.scss new file mode 100644 index 000000000..6268f9655 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-comment-form/style.scss @@ -0,0 +1,8 @@ +.wp-block-wporg-code-reference-comment-form { + padding-bottom: var(--wp--style--block-gap); + + p { + margin-block-start: var(--wp--style--block-gap); + margin-block-end: var(--wp--style--block-gap); + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-comments/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-comments/block.json new file mode 100644 index 000000000..eecb3e7c8 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-comments/block.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-comments", + "version": "0.1.0", + "title": "Code Reference: Comments", + "category": "widgets", + "icon": "smiley", + "description": "Shows the comments.", + "usesContext": [ "postId", "postType" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-comments/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-comments/block.php new file mode 100644 index 000000000..8d92cd25f --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-comments/block.php @@ -0,0 +1,83 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Comments; + +use function DevHub\is_parsed_post_type; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-comments', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) || ! isset( $block->context['postType'] ) ) { + return ''; + } + + if ( post_password_required() ) { + return; + } + + if ( ! comments_open() ) { + return ''; + } + + if ( is_parsed_post_type( $block->context['postType'] ) ) { + // Ensure editor and dashicons styles are enqueued. + // When there are multiple comment forms this is not always the case. + // Handles must be different from default for these assets or they won't load. + // Don't include the version as we want the WordPress version. + wp_enqueue_style( // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion + 'editor-buttons-style', + includes_url() . 'css/editor.css', + array(), + ); + wp_enqueue_style( // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion + 'dashicons-style', + includes_url() . 'css/dashicons.css', + array(), + ); + } + + ob_start(); // Capture all output + + $ordered_comments = wporg_developer_get_ordered_notes(); + + if ( empty( $ordered_comments ) ) { + return ''; + } + + wporg_developer_list_notes( $ordered_comments, array() ); + + $output = ob_get_clean(); + + $title_block = sprintf( + '<!-- wp:heading --><h2 class="wp-block-heading">%s</h2><!-- /wp:heading -->', + __( 'User Contributed Notes', 'wporg' ) + ); + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %1$s>%2$s <ol class="comment-list">%3$s</ol></section>', + $wrapper_attributes, + $title_block, + $output + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-comments/edit.js b/source/wp-content/themes/wpr-developer-2024/src/code-comments/edit.js new file mode 100644 index 000000000..054d05ee0 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-comments/edit.js @@ -0,0 +1,5 @@ +import ServerSideRender from '@wordpress/server-side-render'; + +export default function Edit( { name, attributes } ) { + return <ServerSideRender block={ name } attributes={ attributes } />; +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-comments/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-comments/index.js new file mode 100644 index 000000000..4ccd2e67b --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-comments/index.js @@ -0,0 +1,15 @@ +import { registerBlockType } from '@wordpress/blocks'; +import './style.scss'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-comments/style.scss b/source/wp-content/themes/wpr-developer-2024/src/code-comments/style.scss new file mode 100644 index 000000000..56c19ab92 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-comments/style.scss @@ -0,0 +1,173 @@ +.wp-block-wporg-code-reference-comments { + margin-block-start: var(--wp--preset--spacing--50) !important; + + p { + margin-block-start: var(--wp--style--block-gap); + margin-block-end: var(--wp--style--block-gap); + } + + a:not(.comment-date):not(.comment-author-attribution .url):not(.user-note-voting +a):not(.wporg-developer-code-block a) { + text-decoration: underline; + } + + .comment-list > li, + .children > li { + list-style-type: none; + } + + .comment-list { + margin: 0; + padding: 0; + + > li { + border: 1px solid var(--wp--preset--color--light-grey-1); + + > .comment-body { + padding-bottom: var(--wp--preset--spacing--20); + + > *:not(.comment-meta) { + padding-inline-start: var(--wp--preset--spacing--20); + padding-inline-end: var(--wp--preset--spacing--20); + } + } + } + + > li:not(:last-child) { + margin-bottom: var(--wp--preset--spacing--40); + } + } + + .comment-author-attribution a { + color: var(--wp--preset--color--charcoal-1); + font-weight: 700; + text-decoration: none; + } + + .comment-date { + margin-inline-start: var(--wp--preset--spacing--10); + margin-inline-end: calc(var(--wp--preset--spacing--10) / 2); + color: var(--wp--preset--color--charcoal-4); + text-decoration: none; + } + + .comment-meta { + display: flex; + padding: var(--wp--preset--spacing--10) var(--wp--preset--spacing--20); + background-color: var(--wp--preset--color--light-grey-2); + border-bottom: 1px solid var(--wp--preset--color--light-grey-1); + align-items: center; + justify-content: space-between; + } + + .comment-author.vcard { + display: flex; + } + + // If the first element is a code block, add space + .comment-meta + .wporg-has-embedded-code { + margin-top: var(--wp--preset--spacing--20); + } + + .children { + padding: 0; + margin-top: var(--wp--preset--spacing--20); + margin-block-end: 0; + + li { + border-top: 1px solid var(--wp--preset--color--light-grey-1); + padding-top: var(--wp--preset--spacing--20); + + &:not(:last-child) { + margin-bottom: var(--wp--preset--spacing--20); + } + } + } + + .user-note-voting { + display: flex; + align-items: center; + + .user-note-voting-count { + font-weight: 700; + } + + &[data-can-vote="false"] { + .user-note-voting-up, + .user-note-voting-down { + opacity: 0.25; + } + } + + .user-note-voting-down, + .user-note-voting-up { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + + &:hover::after { + border-color: var(--wp--preset--color--blueberry-1); + } + + &::after { + content: ""; + display: inline-block; + width: 5px; + height: 5px; + border-top: 1px solid var(--wp--preset--color--charcoal-1); + border-right: 1px solid var(--wp--preset--color--charcoal-1); + } + } + + .user-note-voting-up::after { + transform: rotate(-45deg) translate(-2px, 2px); + } + + .user-note-voting-down::after { + transform: rotate(135deg); + } + } + + // Downvoted comments + .bad-note { + .comment-content { + opacity: 0.6; + + &:hover { + opacity: 1; + } + } + } + + .feedback-editor { + margin-top: var(--wp--preset--spacing--20); + } + + .submit { + margin-top: 8px; + } + + .hide-if-js { + display: none !important; + } + + // Hide the file name in comments. + .wporg-developer-code-block { + .wp-code-block-button-container { + justify-content: end; + + code { + display: none; + } + } + } +} + +.wp-block-wporg-code-reference-comments-rules { + margin: var(--wp--style--block-gap) 0; + padding-inline-start: var(--wp--preset--spacing--20); + list-style: disc; + font-size: var(--wp--preset--font-size--extra-small); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-deprecated/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-deprecated/block.json new file mode 100644 index 000000000..b28236664 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-deprecated/block.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-deprecated", + "version": "0.1.0", + "title": "Code Reference: Deprecated", + "category": "widgets", + "icon": "smiley", + "description": "Show the deprecated message for a function, class, or method.", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-deprecated/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-deprecated/block.php new file mode 100644 index 000000000..c8be0bf1e --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-deprecated/block.php @@ -0,0 +1,58 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Deprecated; + +use function DevHub\get_deprecated; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-deprecated', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $deprecated_html = get_deprecated( $block->context['postId'] ); + + if ( empty( $deprecated_html ) ) { + return ''; + } + + $block_markup = <<<EOT + <!-- wp:wporg/notice {"type":"warning"} --> + <div class="wp-block-wporg-notice is-warning-notice"> + <div class="wp-block-wporg-notice__icon"></div> + <div class="wp-block-wporg-notice__content"><p>$deprecated_html</p></div></div> + <!-- /wp:wporg/notice --> + EOT; + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<div %s>%s</div>', + $wrapper_attributes, + do_blocks( $block_markup ) + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-deprecated/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-deprecated/index.js new file mode 100644 index 000000000..abba51c28 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-deprecated/index.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-description/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-description/block.json new file mode 100644 index 000000000..34a246a3e --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-description/block.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-description", + "version": "0.1.0", + "title": "Code Reference: Description", + "category": "widgets", + "icon": "smiley", + "description": "Show the description for a function, class, or method.", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-description/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-description/block.php new file mode 100644 index 000000000..390032cc6 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-description/block.php @@ -0,0 +1,110 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Description; + +use function DevHub\get_description; +use function DevHub\get_see_tags; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-description', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $content = get_description_content( $block->context['postId'] ); + + if ( empty( $content ) ) { + return ''; + } + + $title_block = sprintf( + '<!-- wp:heading {"fontSize":"heading-5"} --><h2 class="wp-block-heading has-heading-5-font-size">%s</h2><!-- /wp:heading -->', + __( 'Description', 'wporg' ) + ); + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %s>%s %s</section>', + $wrapper_attributes, + $title_block, + $content + ); +} + +/** + * Return code description html. + * + * @return string + */ +function get_description_content( $post_id ) { + $output = ''; + + $description = get_description( $post_id ); + $see_tags = get_see_tags( $post_id ); + + if ( ! $description && ! $see_tags ) { + return ''; + } + + $output .= $description; + + if ( $see_tags ) { + $output .= '<h3 class="has-heading-5-font-size">' . __( 'See also', 'wporg' ) . '</h3>'; + + $output .= '<ul>'; + foreach ( $see_tags as $tag ) { + $see_ref = ''; + if ( ! empty( $tag['refers'] ) ) { + $see_ref .= '{@see ' . $tag['refers'] . '}'; + } + if ( ! empty( $tag['content'] ) ) { + if ( $see_ref ) { + $see_ref .= ': '; + } + $see_ref .= $tag['content']; + } + // Process text for auto-linking, etc. + remove_filter( 'the_content', 'wpautop' ); + + // Remove the filter that adds the code reference block to the content. + remove_filter( 'the_content', 'DevHub\filter_code_content', 4 ); + + $see_ref = apply_filters( 'the_content', apply_filters( 'get_the_content', $see_ref ) ); + + // Re-add the filter that adds this block to the content. + add_filter( 'the_content', 'DevHub\filter_code_content', 4 ); + + add_filter( 'the_content', 'wpautop' ); + + $output .= '<li>' . $see_ref . "</li>\n"; + } + $output .= '</ul>'; + } + + return $output; +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-description/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-description/index.js new file mode 100644 index 000000000..26d63bc5a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-description/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-description/style.scss b/source/wp-content/themes/wpr-developer-2024/src/code-description/style.scss new file mode 100644 index 000000000..2ddf50c7b --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-description/style.scss @@ -0,0 +1 @@ +/* css styles */ diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-explanation/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-explanation/block.json new file mode 100644 index 000000000..61d3b568d --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-explanation/block.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-explanation", + "version": "0.1.0", + "title": "Code Reference: Explanation", + "category": "widgets", + "icon": "smiley", + "description": "Show the explanation for a function, class, or method.", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-explanation/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-explanation/block.php new file mode 100644 index 000000000..76478beff --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-explanation/block.php @@ -0,0 +1,56 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Explanation; + +use function DevHub\get_explanation_content; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-explanation', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $explanation = get_explanation_content( $block->context['postId'] ); + + if ( empty( trim( $explanation ) ) ) { + return ''; + } + + $title_block = sprintf( + '<!-- wp:heading {"fontSize":"heading-5"} --><h2 class="wp-block-heading has-heading-5-font-size">%s</h2><!-- /wp:heading -->', + __( 'More Information', 'wporg' ) + ); + + $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => 'wporg-has-embedded-code' ) ); + return sprintf( + '<section %s>%s %s</section>', + $wrapper_attributes, + do_blocks( $title_block ), + $explanation + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-explanation/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-explanation/index.js new file mode 100644 index 000000000..abba51c28 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-explanation/index.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-hooks/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-hooks/block.json new file mode 100644 index 000000000..89e44c241 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-hooks/block.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-hooks", + "version": "0.1.0", + "title": "Code Reference: Hooks", + "category": "widgets", + "icon": "smiley", + "description": "Show the hooks for a function, class, or method.", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-hooks/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-hooks/block.php new file mode 100644 index 000000000..c02c979a8 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-hooks/block.php @@ -0,0 +1,74 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Hooks; + +use function DevHub\get_hooks; +use function DevHub\post_type_has_hooks_info; +use function DevHub\get_signature; +use function DevHub\get_summary; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-hooks', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $post_id = $block->context['postId']; + + if ( ! post_type_has_hooks_info( get_post_type( $post_id ) ) ) { + return ''; + } + + $hooks = get_hooks( $post_id ); + + if ( ! $hooks->have_posts() ) { + return ''; + } + + $content = array(); + while ( $hooks->have_posts() ) { + $hooks->the_post(); + + $content[] = do_blocks( '<!-- wp:wporg/code-reference-title {"isLink":true,"tagName":"dt","fontSize":"normal"} /-->' ); + $content[] = '<dd>' . get_summary() . '</dd>'; + } + wp_reset_postdata(); + + $title_block = sprintf( + '<!-- wp:heading {"fontSize":"heading-5"} --><h2 class="wp-block-heading has-heading-5-font-size">%s</h2><!-- /wp:heading -->', + __( 'Hooks', 'wporg' ) + ); + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %s>%s <dl>%s</dl></section>', + $wrapper_attributes, + do_blocks( $title_block ), + join( '', $content ) + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-hooks/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-hooks/index.js new file mode 100644 index 000000000..26d63bc5a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-hooks/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-hooks/style.scss b/source/wp-content/themes/wpr-developer-2024/src/code-hooks/style.scss new file mode 100644 index 000000000..ec41943b7 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-hooks/style.scss @@ -0,0 +1,10 @@ +.wp-block-wporg-code-reference-hooks { + dd { + margin-inline-start: unset; + + > p { + margin-block-start: calc(var(--wp--preset--spacing--10) / 2); + margin-block-end: var(--wp--preset--spacing--20); + } + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-methods/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-methods/block.json new file mode 100644 index 000000000..60890ffdd --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-methods/block.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-methods", + "version": "0.1.0", + "title": "Code Reference: Methods", + "category": "widgets", + "icon": "smiley", + "description": "Show class methods.", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-methods/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-methods/block.php new file mode 100644 index 000000000..439414bf0 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-methods/block.php @@ -0,0 +1,117 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Methods; + +use function DevHub\is_deprecated; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-methods', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $content = get_methods_content( $block->context['postId'] ); + + if ( empty( $content ) ) { + return ''; + } + + $title_block = sprintf( + '<!-- wp:heading {"fontSize":"heading-5"} --><h2 class="wp-block-heading has-heading-5-font-size">%s</h2><!-- /wp:heading -->', + __( 'Methods', 'wporg' ) + ); + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %1$s>%2$s %3$s</section>', + $wrapper_attributes, + $title_block, + do_blocks( $content ) + ); +} + +/** + * Return code methods html. + * + * @param int $post_id + * @return string + */ +function get_methods_content( $post_id ) { + if ( 'wp-parser-class' === get_post_type() ) { + $class_methods = get_children( + array( + 'post_parent' => $post_id, + 'post_status' => 'publish', + ) + ); + + if ( $class_methods ) { + usort( $class_methods, 'DevHub\compare_objects_by_name' ); + $headings = array( __( 'Name', 'wporg' ), __( 'Description', 'wporg' ) ); + + return sprintf( + '<!-- wp:wporg/code-table {"id":"uses","headings":%1$s,"rows":%2$s,"itemsToShow":150,"style":{"spacing":{"margin":{"top":"var:preset|spacing|20"}}}} /--> ', + wp_json_encode( $headings ), + wp_json_encode( get_row_data( $class_methods ) ) + ); + } + } +} + +/** + * Returns list of rows for the table. + * + * @param WP_Post[] $class_methods + * @return array[] + */ +function get_row_data( $class_methods ) { + $rows = array(); + + foreach ( $class_methods as $method ) { + // Remove the filter that adds the code reference block to the content to avoid a possible infinite loop. + remove_filter( 'the_content', 'DevHub\filter_code_content', 4 ); + $excerpt = sanitize_text_field( apply_filters( 'get_the_excerpt', $method->post_excerpt, $method ) ); + if ( is_deprecated( $method->ID ) ) { + $excerpt .= ' — <span class="deprecated-method">' . __( 'deprecated', 'wporg' ) . '</span>'; + } + // Re-add the filter that adds this block to the content. + add_filter( 'the_content', 'DevHub\filter_code_content', 4 ); + + $rows[] = array( + sprintf( + '<a href="%1$s">%2$s</a>', + get_permalink( $method->ID ), + get_the_title( $method ), + ), + $excerpt ? $excerpt : '-', + ); + wp_reset_postdata(); + } + + return $rows; +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-methods/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-methods/index.js new file mode 100644 index 000000000..abba51c28 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-methods/index.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-parameters/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-parameters/block.json new file mode 100644 index 000000000..f244fb139 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-parameters/block.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-parameters", + "version": "0.1.0", + "title": "Code Reference: Parameters", + "category": "widgets", + "icon": "smiley", + "description": "Show the code parameters for a function, class, or method.", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-parameters/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-parameters/block.php new file mode 100644 index 000000000..e70fe5dc7 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-parameters/block.php @@ -0,0 +1,114 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Parameters; + +use function DevHub\get_param_reference; +use function DevHub\get_params; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-parameters', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $params = get_params( $block->context['postId'] ); + + if ( empty( $params ) ) { + return ''; + } + + $title_block = sprintf( + '<!-- wp:heading {"fontSize":"heading-5"} --><h2 class="wp-block-heading has-heading-5-font-size">%s</h2><!-- /wp:heading -->', + __( 'Parameters', 'wporg' ) + ); + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %s>%s %s</section>', + $wrapper_attributes, + $title_block, + get_param_content( $params ) + ); +} + +/** + * Return code reference parameter html. + * + * @param [type] $params + * @return string + */ +function get_param_content( $params ) { + $output = '<dl>'; + + foreach ( $params as $param ) { + if ( ! empty( $param['variable'] ) ) { + $output .= '<dt>'; + $output .= '<code>' . esc_html( $param['variable'] ) . '</code>'; + + if ( ! empty( $param['types'] ) ) { + $output .= '<span class="type">' . wp_kses_post( $param['types'] ) . '</span>'; + } + + if ( ! empty( $param['required'] ) && 'wp-parser-hook' !== get_post_type() ) { + $output .= '<span class="required">' . esc_html( strtolower( $param['required'] ) ) . '</span>'; + } + + $output .= '</dt>'; + } + + $output .= '<dd>'; + $output .= '<div class="desc">'; + + if ( ! empty( $param['content'] ) ) { + $extra = get_param_reference( $param ); + + if ( $extra ) { + $output .= '<span class="description">' . wp_kses_post( $param['content'] ) . '</span>'; + $output .= '<details class="extended-description">'; + /* translators: 1: function name, 2: variable name */ + $output .= '<summary>' . esc_html( sprintf( __( 'More Arguments from %1$s( ... %2$s )', 'wporg' ), $extra['parent'], $extra['parent_var'] ) ) . '</summary>'; + $output .= '<span class="description">' . wp_kses_post( $extra['content'] ) . '</span>'; + $output .= '</details>'; + } else { + $output .= '<span class="description">' . wp_kses_post( $param['content'] ) . '</span>'; + } + } + + $output .= '</div>'; + + if ( ! empty( $param['default'] ) ) { + $output .= '<p class="default">' . __( 'Default:', 'wporg' ) . '<code>' . htmlentities( $param['default'] ) . '</code></p>'; + } + + $output .= '</dd>'; + } + + $output .= '</dl>'; + + return $output; +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-parameters/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-parameters/index.js new file mode 100644 index 000000000..26d63bc5a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-parameters/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-parameters/style.scss b/source/wp-content/themes/wpr-developer-2024/src/code-parameters/style.scss new file mode 100644 index 000000000..5af0c0001 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-parameters/style.scss @@ -0,0 +1,77 @@ +.wp-block-wporg-code-reference-parameters { + dl { + margin: 0; + } + + dt { + display: flex; + align-items: center; + gap: var(--wp--preset--spacing--10); + } + + dt > code, + li > code { // Nested parameters. + font-weight: 500; + color: var(--wp--preset--color--charcoal-1); + } + + dd { + margin: 0; + + &:not(:last-child) { + padding-bottom: var(--wp--preset--spacing--20); + } + } + + .required { + color: var(--wp--preset--color--charcoal-4); + font-size: var(--wp--preset--font-size--small); + } + + // Nested parameter list. + .param-hash { + padding-inline-start: var(--wp--preset--spacing--10); + margin-top: var(--wp--preset--spacing--10); + border-left: 1px solid var(--wp--preset--color--light-grey-1); + list-style: none; + + > li:not(:last-child) { + padding-bottom: var(--wp--preset--spacing--20); + } + } + + // Default values. + .default { + margin-bottom: 0; + } + + // This needs to be a direct descendant or properties of nested objects will also get this style. + .description > code, + .default > code, + .desc > code, + .param-hash .type { + padding: 4px 6px; + background-color: var(--wp--preset--color--light-grey-2); + border-radius: 2px; + } + + .default > code, + .param-hash .type { + margin-inline-start: 5px; + } + + .type { + color: var(--wp--preset--color--syntax-red); + font-family: var(--wp--preset--font-family--monospace); + font-size: var(--wp--preset--font-size--small); + } + + // Visible when parameters are imported from reference. + summary { + margin-top: var(--wp--preset--spacing--10); + color: var(--wp--preset--color--blueberry-1); + cursor: pointer; + list-style: none; + text-decoration: underline; + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-private-access/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-private-access/block.json new file mode 100644 index 000000000..75338d536 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-private-access/block.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-private-access", + "version": "0.1.0", + "title": "Code Reference: Private Access Message", + "category": "widgets", + "icon": "smiley", + "description": "Show the private access message for a function, class, or method.", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-private-access/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-private-access/block.php new file mode 100644 index 000000000..45af8f362 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-private-access/block.php @@ -0,0 +1,58 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Private_Access; + +use function DevHub\get_private_access_message; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-private-access', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $html = get_private_access_message( $block->context['postId'] ); + + if ( empty( $html ) ) { + return ''; + } + + $block_markup = <<<EOT + <!-- wp:wporg/notice {"type":"alert"} --> + <div class="wp-block-wporg-notice is-alert-notice"> + <div class="wp-block-wporg-notice__icon"></div> + <div class="wp-block-wporg-notice__content"><p>$html</p></div></div> + <!-- /wp:wporg/notice --> + EOT; + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<div %s>%s</div>', + $wrapper_attributes, + do_blocks( $block_markup ) + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-private-access/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-private-access/index.js new file mode 100644 index 000000000..abba51c28 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-private-access/index.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-related/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-related/block.json new file mode 100644 index 000000000..daed97526 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-related/block.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-related", + "version": "0.1.0", + "title": "Code Reference: Related", + "category": "widgets", + "icon": "smiley", + "description": "Show related code.", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-related/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-related/block.php new file mode 100644 index 000000000..f1e5fa65a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-related/block.php @@ -0,0 +1,138 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Related; + +use function DevHub\post_type_has_uses_info; +use function DevHub\post_type_has_usage_info; +use function DevHub\get_source_file; +use function DevHub\get_summary; +use function DevHub\get_uses; +use function DevHub\get_used_by; +use function DevHub\split_uses_by_frequent_funcs; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-related', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $uses = null; + $used_by = null; + $has_uses = false; + $has_used_by = false; + + $post_id = $block->context['postId']; + $post_type = get_post_type( $post_id ); + + if ( post_type_has_uses_info( $post_type ) ) { + $uses = get_uses( $post_id ); + $has_uses = $uses ? $uses->have_posts() : false; + } + + if ( post_type_has_usage_info( $post_type ) ) { + $used_by = get_used_by( $post_id ); + $has_used_by = $used_by ? $used_by->have_posts() : false; + } + + if ( ! $has_uses && ! $has_used_by ) { + return ''; + } + + $title_block = sprintf( + '<!-- wp:heading {"fontSize":"heading-5"} --><h2 class="wp-block-heading has-heading-5-font-size">%s</h2><!-- /wp:heading -->', + __( 'Related', 'wporg' ) + ); + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %1$s>%2$s %3$s %4$s</section>', + $wrapper_attributes, + $title_block, + $has_uses ? do_blocks( get_uses_table( $uses ) ) : '', + $has_used_by ? do_blocks( get_used_by_table( $used_by ) ) : '', + ); +} + +/** + * Returns list of rows for the table. + * + * @param WP_Post[] $posts + * @return array[] + */ +function get_row_data( $posts ) { + $rows = array(); + + while ( $posts->have_posts() ) { + $posts->the_post(); + $is_function = ! in_array( get_post_type(), array( 'wp-parser-class', 'wp-parser-hook' ) ); + + $rows[] = array( + sprintf( + '<a href="%1$s">%2$s%3$s</a><code>%4$s</code>', + get_permalink(), + get_the_title(), + $is_function ? '()' : '', + esc_attr( get_source_file() ) + ), + get_summary(), + ); + wp_reset_postdata(); + } + + return $rows; +} + +/** + * Returns a table for uses. + */ +function get_uses_table( $uses ) { + $uses_to_show = 5; + $min_uses_to_show = 2; + $uses_to_show = min( $uses_to_show, max( split_uses_by_frequent_funcs( $uses->posts ), $min_uses_to_show ) ); + $headings = array( __( 'Uses', 'wporg' ), __( 'Description', 'wporg' ) ); + + return sprintf( + '<!-- wp:wporg/code-table {"id":"uses","itemsToShow":%s,"headings":%s,"rows":%s,"style":{"spacing":{"margin":{"top":"var:preset|spacing|20"}}}} /--> ', + esc_attr( $uses_to_show ), + wp_json_encode( $headings ), + wp_json_encode( get_row_data( $uses ) ) + ); +} + +/** + * Returns a table for used by. + */ +function get_used_by_table( $used_by ) { + $headings = array( __( 'Used by', 'wporg' ), __( 'Description', 'wporg' ) ); + return sprintf( + '<!-- wp:wporg/code-table {"id":"used-by","itemsToShow":5,"headings":%s,"rows":%s,"style":{"spacing":{"margin":{"top":"var:preset|spacing|20"}}}} /--> ', + wp_json_encode( $headings ), + wp_json_encode( get_row_data( $used_by ) ) + ); +} + diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-related/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-related/index.js new file mode 100644 index 000000000..eaf0a841c --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-related/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; +import './style.scss'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-related/style.scss b/source/wp-content/themes/wpr-developer-2024/src/code-related/style.scss new file mode 100644 index 000000000..4b3451062 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-related/style.scss @@ -0,0 +1,13 @@ +.wp-block-wporg-code-reference-related { + tr th:last-child, + tr td:last-child { + display: none; + } + + @media (min-width: 600px) { + tr th:last-child, + tr td:last-child { + display: revert; + } + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-return-value/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-return-value/block.json new file mode 100644 index 000000000..9e164124a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-return-value/block.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-return-value", + "version": "0.1.0", + "title": "Code Reference: Return Value", + "category": "widgets", + "icon": "smiley", + "description": "Show the return value for a function, class, or method.", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-return-value/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-return-value/block.php new file mode 100644 index 000000000..a741ba441 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-return-value/block.php @@ -0,0 +1,56 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Return_Value; + +use function DevHub\get_return; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-return-value', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $content = get_return( $block->context['postId'] ); + + if ( empty( $content ) ) { + return ''; + } + + $title_block = sprintf( + '<!-- wp:heading {"fontSize":"heading-5"} --><h2 class="wp-block-heading has-heading-5-font-size">%s</h2><!-- /wp:heading -->', + __( 'Return', 'wporg' ) + ); + + $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => 'wporg-has-embedded-code' ) ); + return sprintf( + '<section %s>%s %s</section>', + $wrapper_attributes, + do_blocks( $title_block ), + $content + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-return-value/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-return-value/index.js new file mode 100644 index 000000000..26d63bc5a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-return-value/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-return-value/style.scss b/source/wp-content/themes/wpr-developer-2024/src/code-return-value/style.scss new file mode 100644 index 000000000..e6cc0b3ce --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-return-value/style.scss @@ -0,0 +1,8 @@ +.wp-block-wporg-code-reference-return-value { + .return-type, + .type { + font-family: var(--wp--preset--font-family--monospace); + font-size: var(--wp--preset--font-size--small); + color: var(--wp--preset--color--syntax-red); + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-short-title/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-short-title/block.json new file mode 100644 index 000000000..2fd421168 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-short-title/block.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-short-title", + "title": "Code Short Title", + "category": "layout", + "description": "The code short title for archive and search results.", + "keywords": [ "post" ], + "textdomain": "wporg", + "attributes": {}, + "supports": { + "html": false, + "color": { + "text": true, + "background": true, + "link": true + }, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "usesContext": [ "postId" ], + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-short-title/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-short-title/index.js new file mode 100644 index 000000000..77d99c307 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-short-title/index.js @@ -0,0 +1,16 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + edit: Edit, + save: () => null, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-short-title/index.php b/source/wp-content/themes/wpr-developer-2024/src/code-short-title/index.php new file mode 100644 index 000000000..d298c1925 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-short-title/index.php @@ -0,0 +1,79 @@ +<?php +/** + * Block Name: Code Short Title + * Description: The code short title for archive and search results. + * + * @package wporg + */ + +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Short_Title; + +use function DevHub\is_parsed_post_type; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-short-title', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $title = get_the_title( $block->context['postId'] ); + $post_type = get_post_type( $block->context['postId'] ); + $type = strtolower( get_post_type_object( $post_type )->labels->singular_name ); + $is_parsed_post_type = is_parsed_post_type( $post_type ); + + $content_html = ''; + if ( $is_parsed_post_type ) { + $content_html .= sprintf( + '<span class="wp-block-wporg-code-short-title__type">%1$s</span>', + $type + ); + } + + $content_html .= sprintf( + '<a href="%1$s">%2$s</a>', + esc_url( get_permalink( $block->context['postId'] ) ), + $title + ); + + $classes = array( + $type, + $is_parsed_post_type ? 'wp-block-wporg-code-short-title-parsed' : '', + ); + + $wrapper_attributes = get_block_wrapper_attributes( + array( + 'class' => implode( ' ', $classes ), + ) + ); + return sprintf( + '<div %1$s>%2$s</div>', + $wrapper_attributes, + $content_html, + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-short-title/style.scss b/source/wp-content/themes/wpr-developer-2024/src/code-short-title/style.scss new file mode 100644 index 000000000..6d0a9d48b --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-short-title/style.scss @@ -0,0 +1,97 @@ +.wp-block-wporg-code-short-title { + align-items: flex-start; + display: flex; + flex-direction: column; + font-family: var(--wp--preset--font-family--inter); + font-weight: 700; + font-size: var(--wp--preset--font-size--small); + line-height: var(--wp--custom--body--small--typography--line-height); + + &.wp-block-wporg-code-short-title-parsed a { + font-family: var(--wp--preset--font-family--monospace); + font-size: var(--wp--preset--font-size--extra-large); + line-height: var(--wp--custom--body--large--typography--line-height); + font-weight: 400; + } +} + +@media (min-width: 350px) { + .wp-block-wporg-code-short-title { + align-items: center; + display: flex; + flex-direction: row; + } +} + +.wp-block-wporg-code-short-title__type { + font-family: var(--wp--preset--font-family--monospace); + background: var(--wp--preset--color--light-grey-2); + border-radius: 2px; + margin-inline-end: 8px; + padding: 2px 8px; + font-weight: 500; + word-break: keep-all; +} + +.wp-block-wporg-code-short-title a { + display: block; + text-decoration: none; + + &:hover { + text-decoration: underline; + } +} + +.wp-block-wporg-code-short-title.function a, +.wp-block-wporg-code-short-title.class a, +.wp-block-wporg-code-short-title.method a, +.wp-block-wporg-code-short-title.hook a { + word-break: break-all; +} + +.function { + &.wp-block-wporg-code-short-title, + a { + color: var(--wp--custom--wporg-code-short-title--color--function); + } + + .wp-block-wporg-code-short-title__type { + color: var(--wp--custom--wporg-code-short-title--color--function-type); + background: var(--wp--custom--wporg-code-short-title--background-color--function-type); + } +} + +.hook { + &.wp-block-wporg-code-short-title, + a { + color: var(--wp--custom--wporg-code-short-title--color--hook); + } + + .wp-block-wporg-code-short-title__type { + color: var(--wp--custom--wporg-code-short-title--color--hook-type); + background: var(--wp--custom--wporg-code-short-title--background-color--hook-type); + } +} + +.class { + &.wp-block-wporg-code-short-title, + a { + color: var(--wp--custom--wporg-code-short-title--color--class); + } + + .wp-block-wporg-code-short-title__type { + color: var(--wp--custom--wporg-code-short-title--color--class-type); + background: var(--wp--custom--wporg-code-short-title--background-color--class-type); + } +} + +.method { + &.wp-block-wporg-code-short-title, + a { + color: var(--wp--custom--wporg-code-short-title--color--method); + } + + .wp-block-wporg-code-short-title__type { + background: var(--wp--custom--wporg-code-short-title--background-color--method-type); + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-source/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-source/block.json new file mode 100644 index 000000000..f6263e897 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-source/block.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-source", + "version": "0.1.0", + "title": "Code Reference: Source", + "category": "widgets", + "icon": "smiley", + "description": "Show the code source for a function, class, or method.", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-source/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-source/block.php new file mode 100644 index 000000000..aca687b30 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-source/block.php @@ -0,0 +1,113 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Source; + +use function DevHub\get_github_source_file_link; +use function DevHub\get_source_code; +use function DevHub\get_source_file; +use function DevHub\get_source_file_archive_link; +use function DevHub\get_source_file_link; +use function DevHub\post_type_has_source_code; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-source', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $content = get_source_content( $block->context['postId'] ); + + if ( empty( $content ) ) { + return ''; + } + + $title_block = sprintf( + '<!-- wp:heading {"fontSize":"heading-5"} --><h2 class="wp-block-heading has-heading-5-font-size">%s</h2><!-- /wp:heading -->', + __( 'Source', 'wporg' ) + ); + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %s>%s %s</section>', + $wrapper_attributes, + $title_block, + $content + ); +} + +/** + * Returns code sample html. + * + * @param int $post_id + * + * @return string + */ +function get_source_content( $post_id ) { + $post_type = get_post_type( $post_id ); + $source_file = get_source_file( $post_id ); + $output = ''; + + if ( ! empty( $source_file ) ) { + $source_code = post_type_has_source_code( $post_type ) ? get_source_code( $post_id ) : ''; + + $view_reference_button = sprintf( + '<a href="%s">%s</a>', + esc_url( get_source_file_archive_link( $source_file ) ), + __( 'View all references', 'wporg' ) + ); + + $view_trac_button = sprintf( + '<a href="%s">%s</a>', + esc_url( get_source_file_link( $post_id ) ), + __( 'View on Trac', 'wporg' ) + ); + + $view_github_button = sprintf( + '<a href="%s">%s</a>', + esc_url( get_github_source_file_link( $post_id ) ), + __( 'View on GitHub', 'wporg' ) + ); + + if ( ! empty( $source_code ) ) { + $output .= do_blocks( + sprintf( + '<!-- wp:code {"lineNumbers":true} --><pre class="wp-block-code" data-start="%1$s" aria-label="%2$s"><code id="wporg-source-code" lang="php" class="language-php line-numbers">%3$s</code></pre><!-- /wp:code -->', + esc_attr( get_post_meta( get_the_ID(), '_wp-parser_line_num', true ) ), + __( 'Function source code', 'wporg' ), + htmlentities( $source_code ) + ) + ); + + $output .= sprintf( '<p class="wporg-dot-link-list">%s</p>', implode( ' ', array( $view_reference_button, $view_trac_button, $view_github_button ) ) ); + } else { + $output .= sprintf( '<p class="wporg-dot-link-list">%s</p>', implode( ' ', array( $view_reference_button, $view_trac_button ) ) ); + } + } + + return $output; +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-source/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-source/index.js new file mode 100644 index 000000000..abba51c28 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-source/index.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-summary/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-summary/block.json new file mode 100644 index 000000000..0c523e7f9 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-summary/block.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-summary", + "version": "0.1.0", + "title": "Code Reference: Summary", + "category": "widgets", + "icon": "smiley", + "description": "Code summary for a function, method, or class.", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-summary/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-summary/block.php new file mode 100644 index 000000000..4b01b1ae1 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-summary/block.php @@ -0,0 +1,44 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Summary; + +use function DevHub\get_summary; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-summary', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %1$s>%2$s</section>', + $wrapper_attributes, + get_summary( $block->context['postId'] ) + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-summary/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-summary/index.js new file mode 100644 index 000000000..26d63bc5a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-summary/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-summary/style.scss b/source/wp-content/themes/wpr-developer-2024/src/code-summary/style.scss new file mode 100644 index 000000000..7c9d04e41 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-summary/style.scss @@ -0,0 +1,4 @@ +.wp-block-wporg-code-reference-summary p { + margin-block-start: var(--wp--style--block-gap); + margin-block-end: var(--wp--style--block-gap); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-table/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-table/block.json new file mode 100644 index 000000000..9f366d027 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-table/block.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-table", + "version": "0.1.0", + "title": "Code Reference: Table", + "category": "widgets", + "icon": "smiley", + "description": "Renders a table", + "attributes": { + "className": { + "type": "string", + "default": "" + }, + "headings": { + "type": "array", + "default": [] + }, + "id": { + "type": "string" + }, + "rows": { + "type": "array", + "default": [] + }, + "itemsToShow": { + "type": "integer", + "default": 5 + } + }, + "supports": { + "inserter": false, + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css", + "viewScript": "file:./view.js" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-table/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-table/block.php new file mode 100644 index 000000000..4d29045ab --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-table/block.php @@ -0,0 +1,114 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Table; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-table', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @return string Returns the block markup. + */ +function render( $attributes ) { + $table = '<!-- wp:table {"className":"' . $attributes['className'] . '"} --><figure class="wp-block-table ' . $attributes['className'] . '">'; + $table .= '<table>'; + $table .= '<thead>'; + $table .= '<tr>'; + foreach ( $attributes['headings'] as $heading ) { + $table .= render_table_heading_row( $heading ); + } + $table .= '</tr>'; + $table .= '</thead>'; + $table .= '<tbody>'; + foreach ( $attributes['rows'] as $key => $value ) { + $class_name = $key >= $attributes['itemsToShow'] ? 'wporg-hidden' : ''; + $table .= render_table_row( $value, $class_name ); + } + $table .= '</tbody></table>'; + $table .= '</figure><!-- /wp:table -->'; + + $row_count = count( $attributes['rows'] ); + + if ( $row_count > $attributes['itemsToShow'] ) { + $table .= render_toggle( $row_count - $attributes['itemsToShow'] ); + } + + $wrapper_attributes = get_block_wrapper_attributes(); + if ( isset( $attributes['id'] ) ) { + $wrapper_attributes .= ' id="' . esc_attr( $attributes['id'] ) . '"'; + } + + return sprintf( + '<section %1$s>%2$s</section>', + $wrapper_attributes, + do_blocks( $table ) + ); +} + +/** + * Render a table heading cell. + * + * @param string[] $heading Cell content. + * + * @return string Returns the table header markup. + */ +function render_table_heading_row( $heading ) { + return '<th scope="col">' . wp_kses_post( $heading ) . '</th>'; +} + +/** + * Render a table row. + * + * @param string[] $data A list of table row data. + * + * @return string Returns the row markup. + */ +function render_table_row( $data, $class_name ) { + $row = '<tr class="' . esc_attr( $class_name ) . '">'; + + foreach ( $data as $col ) { + $row .= '<td>' . wp_kses_post( $col ) . '</td>'; + } + $row .= '</tr>'; + + return $row; +} + +/** + * Render the expand/collapse toggle. + * + * @param number $remaining The number of remaining items to show. + * @return string + */ +function render_toggle( $remaining ) { + $show_more = sprintf( + '<a class="wp-block-wporg-code-table-show-more" href="#">%s</a>', + sprintf( + /* translators: %s: Number of remaining items. */ + __( 'Show %s more', 'wporg' ), + number_format_i18n( $remaining ) + ) + ); + + $show_less = sprintf( + '<a class="wp-block-wporg-code-table-show-less" href="#">%s</a>', + __( 'Show less', 'wporg' ) + ); + + return $show_more . $show_less; +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-table/edit.js b/source/wp-content/themes/wpr-developer-2024/src/code-table/edit.js new file mode 100644 index 000000000..054d05ee0 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-table/edit.js @@ -0,0 +1,5 @@ +import ServerSideRender from '@wordpress/server-side-render'; + +export default function Edit( { name, attributes } ) { + return <ServerSideRender block={ name } attributes={ attributes } />; +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-table/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-table/index.js new file mode 100644 index 000000000..8a57ac8b6 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-table/index.js @@ -0,0 +1,15 @@ +import { registerBlockType } from '@wordpress/blocks'; +import './style.scss'; + +/** + * Internal dependencies + */ +import Edit from './edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-table/style.scss b/source/wp-content/themes/wpr-developer-2024/src/code-table/style.scss new file mode 100644 index 000000000..8a7870256 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-table/style.scss @@ -0,0 +1,111 @@ +.wp-block-wporg-code-table { + &#uses, + &#used-by { + /* stylelint-disable-next-line max-line-length */ + scroll-margin-top: calc(var(--wp--custom--local-navigation-bar--spacing--height, 0) + var(--wp--preset--spacing--20)); + } + + .wp-block-table { + thead { + border-bottom: none; + } + + th, + td { + border-right: none; + border-left: none; + padding: var(--wp--preset--spacing--10) var(--wp--preset--spacing--10) var(--wp--preset--spacing--10) 0; + font-size: var(--wp--preset--font-size--normal); + width: 50%; + } + + th { + border-top: none; + font-weight: 700; + } + + td p { + margin: 0; + } + + td a { + text-decoration: underline; + word-break: break-word; + } + + tr td:first-child { + a, + code { + display: block; + } + } + + code { + color: var(--wp--preset--color--charcoal-4); + font-size: var(--wp--preset--font-size--small); + word-break: break-all; + } + + .wporg-hidden { + display: none; + } + } + + .wp-block-wporg-code-table-show-more, + .wp-block-wporg-code-table-show-less { + text-decoration: underline; + } + + .wp-block-wporg-code-table-show-less { + display: none; + } + + &.is-responsive { + + @media screen and (max-width: 500px) { + tr { + padding: calc(var(--wp--preset--spacing--10) / 2) 0; + display: flex; + flex-direction: column; + border-bottom: 1px solid var(--wp--preset--color--light-grey-1); + + &:last-child { + border-bottom: none; + } + } + + .wp-block-table { + th, + td { + padding: calc(var(--wp--preset--spacing--10) / 2); + width: 100%; + } + + td { + border: none; + } + } + } + + @media screen and (min-width: 501px) { + .wp-block-table th:first-child, + .wp-block-table td:first-child { + width: 40%; + } + } + } +} + +.expanded.wp-block-wporg-code-table { + .wporg-hidden { + display: table-row; + } + + .wp-block-wporg-code-table-show-more { + display: none; + } + + .wp-block-wporg-code-table-show-less { + display: block; + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-table/view.js b/source/wp-content/themes/wpr-developer-2024/src/code-table/view.js new file mode 100644 index 000000000..b424f3d38 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-table/view.js @@ -0,0 +1,25 @@ +const init = () => { + const tables = document.querySelectorAll( '.wp-block-wporg-code-table' ); + if ( tables ) { + tables.forEach( ( table ) => { + const showMoreLink = table.querySelector( '.wp-block-wporg-code-table-show-more' ); + const showLessLink = table.querySelector( '.wp-block-wporg-code-table-show-less' ); + + if ( showMoreLink ) { + showMoreLink.addEventListener( 'click', ( event ) => { + event.preventDefault(); + table.classList.add( 'expanded' ); + } ); + } + + if ( showLessLink ) { + showLessLink.addEventListener( 'click', ( event ) => { + event.preventDefault(); + table.classList.remove( 'expanded' ); + } ); + } + } ); + } +}; + +window.addEventListener( 'load', init ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-title/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-title/block.json new file mode 100644 index 000000000..af9bbd50d --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-title/block.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-reference-title", + "version": "0.1.0", + "title": "Code Reference: Title", + "category": "widgets", + "icon": "smiley", + "description": "Show the code signature for a function, class, or method.", + "usesContext": [ "postId" ], + "attributes": { + "isLink": { + "type": "boolean", + "default": false + }, + "tagName": { + "type": "string", + "default": "h1" + } + }, + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "editorStyle": "file:./index.css", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-title/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-title/block.php new file mode 100644 index 000000000..8c82529b4 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-title/block.php @@ -0,0 +1,56 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Title; + +use function DevHub\get_signature; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-title', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $post_ID = $block->context['postId']; + $content = get_signature( $post_ID ); + + if ( isset( $attributes['isLink'] ) && $attributes['isLink'] ) { + $content = sprintf( + '<a href="%1$s">%2$s</a>', + get_the_permalink( $post_ID ), + $content + ); + } + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<%1$s %2$s>%3$s</%1$s>', + esc_attr( $attributes['tagName'] ), + $wrapper_attributes, + $content + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-title/edit.js b/source/wp-content/themes/wpr-developer-2024/src/code-title/edit.js new file mode 100644 index 000000000..12fd91aac --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-title/edit.js @@ -0,0 +1,48 @@ +/** + * WordPress dependencies + */ +import { InspectorControls } from '@wordpress/block-editor'; +import { PanelBody, SelectControl, ToggleControl } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import DynamicEdit from '../shared/dynamic-edit'; +import './edit.scss'; + +export default function Edit( { ...props } ) { + const { attributes, setAttributes } = props; + const { isLink, tagName } = attributes; + return ( + <> + <InspectorControls> + <PanelBody title={ __( 'Settings', 'wporg' ) }> + <ToggleControl + label={ __( 'Make title a link', 'wporg' ) } + checked={ isLink } + onChange={ () => setAttributes( { isLink: ! isLink } ) } + /> + </PanelBody> + </InspectorControls> + <InspectorControls __experimentalGroup="advanced"> + <SelectControl + label={ __( 'HTML element', 'wporg' ) } + options={ [ + { label: __( 'Default (<p>)', 'wporg' ), value: 'p' }, + { label: '<div>', value: 'div' }, + { label: '<h1>', value: 'h1' }, + { label: '<h2>', value: 'h2' }, + { label: '<h3>', value: 'h3' }, + { label: '<h4>', value: 'h4' }, + { label: '<h5>', value: 'h5' }, + { label: '<h6>', value: 'h6' }, + ] } + value={ tagName } + onChange={ ( val ) => setAttributes( { tagName: val } ) } + /> + </InspectorControls> + <DynamicEdit { ...props } /> + </> + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-title/edit.scss b/source/wp-content/themes/wpr-developer-2024/src/code-title/edit.scss new file mode 100644 index 000000000..0472eecac --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-title/edit.scss @@ -0,0 +1,3 @@ +.wp-block .wp-block-wporg-code-reference-title { + font-size: inherit !important; +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-title/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-title/index.js new file mode 100644 index 000000000..0269f1e3a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-title/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from './edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-title/style.scss b/source/wp-content/themes/wpr-developer-2024/src/code-title/style.scss new file mode 100644 index 000000000..18bb97695 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-title/style.scss @@ -0,0 +1,32 @@ +.wp-block-wporg-code-reference-title { + color: var(--wp--preset--color--syntax-green); + + .hook-func, + .keyword { + color: var(--wp--preset--color--syntax-grey); + } + + .arg-type { + color: var(--wp--preset--color--syntax-red); + } + + .arg-name { + color: var(--wp--preset--color--syntax-blue); + } + + .arg-default { + color: var(--wp--preset--color--syntax-black); + } + + a, + .wp-block-post-content & a { + text-decoration: none; + color: inherit; + + &:hover, + &:focus { + text-decoration: none; + border-bottom: 1px dotted var(--wp--preset--color--blueberry-1); + } + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-type-usage-info/block.json b/source/wp-content/themes/wpr-developer-2024/src/code-type-usage-info/block.json new file mode 100644 index 000000000..4b1cd6a15 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-type-usage-info/block.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/code-type-usage-info", + "title": "Code Type Usage Info", + "category": "layout", + "description": "Displays information about code type references.", + "keywords": [ "post" ], + "textdomain": "wporg", + "attributes": {}, + "supports": { + "html": false, + "color": { + "text": true, + "background": true, + "link": true + }, + "spacing": { + "margin": true, + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-type-usage-info/index.js b/source/wp-content/themes/wpr-developer-2024/src/code-type-usage-info/index.js new file mode 100644 index 000000000..77d99c307 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-type-usage-info/index.js @@ -0,0 +1,16 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + edit: Edit, + save: () => null, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-type-usage-info/index.php b/source/wp-content/themes/wpr-developer-2024/src/code-type-usage-info/index.php new file mode 100644 index 000000000..8b64f342a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-type-usage-info/index.php @@ -0,0 +1,84 @@ +<?php +/** + * Block Name: Code Type Usage Info + * Description: Displays information about code type references. + * + * @package wporg + */ + +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_Type_Usage_Info; + +use function DevHub\get_source_file; +use function DevHub\get_line_number; +use function DevHub\show_usage_info; +use function DevHub\get_used_by; +use function DevHub\get_uses; +use function DevHub\get_github_source_file_link; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-type-usage-info', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + + if ( ! show_usage_info() ) { + return ''; + } + + $used_by_posts = get_used_by(); + $uses_posts = get_uses(); + $used_by = 0; + $uses = 0; + + if ( $used_by_posts ) { + $used_by = $used_by_posts->post_count; + } + + if ( $uses_posts ) { + $uses = $uses_posts->post_count; + } + + $used_by_html = sprintf( + /* translators: 1: permalink, 2: number of functions */ + _n( 'Used by <a href="%1$s">%2$d function</a>', 'Used by <a href="%1$s">%2$d functions</a>', $used_by, 'wporg' ), + esc_url( apply_filters( 'the_permalink', get_permalink() ) ) . '#used-by', + $used_by + ); + + $uses_html = sprintf( + /* translators: 1: permalink, 2: number of functions */ + _n( 'Uses <a href="%1$s">%2$d function</a>', 'Uses <a href="%1$s">%2$d functions</a>', $uses, 'wporg' ), + esc_url( apply_filters( 'the_permalink', get_permalink() ) ) . '#uses', + $uses + ); + + $source_html = __( 'Source:', 'wporg' ) . ' <a class="wp-block-wporg-code-type-usage-info__source external-link" href="' . get_github_source_file_link() . '">' . get_source_file() . ':' . get_line_number() . '</a>'; + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<div %1$s>%2$s | %3$s | %4$s</div>', + $wrapper_attributes, + $used_by_html, + $uses_html, + $source_html + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-type-usage-info/style.scss b/source/wp-content/themes/wpr-developer-2024/src/code-type-usage-info/style.scss new file mode 100644 index 000000000..60680efb4 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-type-usage-info/style.scss @@ -0,0 +1,7 @@ +.wp-block-wporg-code-type-usage-info { + margin-block-start: 0 !important; +} + +.wp-block-wporg-code-type-usage-info__source { + text-decoration: underline; +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/code-user-notes/block.php b/source/wp-content/themes/wpr-developer-2024/src/code-user-notes/block.php new file mode 100644 index 000000000..261e3ef34 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/code-user-notes/block.php @@ -0,0 +1,48 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Code_User_Notes; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/code-user-notes', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $title_block = sprintf( + '<!-- wp:heading {"fontSize":"heading-5"} --><h2 class="wp-block-heading has-heading-5-font-size">%s</h2><!-- /wp:heading -->', + __( 'User Contributed Notes', 'wporg' ) + ); + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %s>%s %s</section>', + $wrapper_attributes, + $title_block, + 'Not implemented' + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-content/block.json b/source/wp-content/themes/wpr-developer-2024/src/command-content/block.json new file mode 100644 index 000000000..f298405c4 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-content/block.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/command-content", + "version": "0.1.0", + "title": "Command Content", + "category": "widgets", + "description": "Command Content", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-content/block.php b/source/wp-content/themes/wpr-developer-2024/src/command-content/block.php new file mode 100644 index 000000000..1db41d905 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-content/block.php @@ -0,0 +1,120 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Command_Content; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/command-content', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + + $post_ID = $block->context['postId']; + + if ( ! isset( $post_ID ) ) { + return ''; + } + + $content = format_headings( get_the_content( null, false, $post_ID ) ); + + $wrapper_attributes = get_block_wrapper_attributes(); + $repo_url = get_post_meta( $post_ID, 'repo_url', true ); + + if ( is_bundled_commands( $repo_url ) ) { + return sprintf( + '<section %1$s>%2$s</section>', + $wrapper_attributes, + do_blocks( $content ), + ); + } + + $repo_slug = str_replace( 'https://github.com/', '', $repo_url ); + $command = get_the_title( $post_ID ); + $installing_instructions = sprintf( + '<!-- wp:heading {"level":3} --><h3 class="wp-block-heading">%1$s</h3><!-- /wp:heading -->', + __( 'Installing', 'wporg' ) + ) . sprintf( + /* translators: 1: command to be used, 2: package repo slug 3: command to be used */ + __( + ' + <p>Use the <code>%1$s</code> command by installing the command\'s package:</p> + <pre><code>wp package install %2$s</code></pre> + <p>Once the package is successfully installed, the <code>%3$s</code> command will appear in the list of available commands.</p> + ', + 'wporg' + ), + $command, + esc_html( $repo_slug ), + $command + ); + + // Insert before OPTIONS but after description if OPTIONS exists + $options_match = '#<h3>Options<\/h3>|<h3>OPTIONS<\/h3>#'; + if ( preg_match( $options_match, $content ) ) { + $content = preg_replace( $options_match, $installing_instructions . '$0', $content ); + } else { + // Otherwise, appending to description is fine. + $content .= $installing_instructions; + } + + return sprintf( + '<section %1$s>%2$s</section>', + $wrapper_attributes, + do_blocks( $content ), + ); +} + +/** + * Check if the repo url a bundled command + */ +function is_bundled_commands( $repo_url ) { + $non_bundled_commands = array( + 'https://github.com/wp-cli/admin-command', + 'https://github.com/wp-cli/dist-archive-command', + 'https://github.com/wp-cli/doctor-command', + 'https://github.com/wp-cli/find-command', + 'https://github.com/wp-cli/profile-command', + 'https://github.com/wp-cli/scaffold-package-command', + ); + + if ( in_array( $repo_url, $non_bundled_commands, true ) ) { + return false; + } + + return true; +} + +/** + * Title case the headings of a post content. + * + * @param string $content + * @return string Converted content. + */ +function format_headings( $content ) { + $tag = 'h(?P<level>[1-6])'; + + return preg_replace_callback( + "/(?P<opening_tag><{$tag}>)(?P<title>.*?)(?P<closing_tag><\/{$tag}>)/J", + function ( $matches ) { + return $matches['opening_tag'] . ucwords( strtolower( $matches['title'] ) ) . $matches['closing_tag']; + }, + $content + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-content/index.js b/source/wp-content/themes/wpr-developer-2024/src/command-content/index.js new file mode 100644 index 000000000..26d63bc5a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-content/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-content/style.scss b/source/wp-content/themes/wpr-developer-2024/src/command-content/style.scss new file mode 100644 index 000000000..7e29be996 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-content/style.scss @@ -0,0 +1,25 @@ +@media screen and (max-width: 599px) { + .wp-block-wporg-command-content { + table { + margin-top: 28px; + + thead { + display: none; + } + + tbody > tr { + display: flex; + flex-direction: column; + + td:first-child { + padding-bottom: 0; + margin-bottom: 4px; + } + + td:last-child { + padding-top: 0; + } + } + } + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-github/block.json b/source/wp-content/themes/wpr-developer-2024/src/command-github/block.json new file mode 100644 index 000000000..b1ff6632a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-github/block.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/command-github", + "version": "0.1.0", + "title": "Command Github", + "category": "widgets", + "description": "Show the Github Section of a Command.", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-github/block.php b/source/wp-content/themes/wpr-developer-2024/src/command-github/block.php new file mode 100644 index 000000000..371d95a49 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-github/block.php @@ -0,0 +1,121 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Command_Github; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/command-github', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + + $post_ID = $block->context['postId']; + + if ( ! isset( $post_ID ) ) { + return ''; + } + + $repo_url = get_post_meta( $post_ID, 'repo_url', true ); + $cmd_slug = str_replace( 'wp ', '', get_the_title( $post_ID ) ); + $open_issues = 'https://github.com/login?return_to=%2Fissues%3Fq%3Dlabel%3A' . urlencode( 'command:' . str_replace( ' ', '-', $cmd_slug ) ) . '+sort%3Aupdated-desc+org%3Awp-cli+is%3Aopen'; + $closed_issues = 'https://github.com/login?return_to=%2Fissues%3Fq%3Dlabel%3A' . urlencode( 'command:' . str_replace( ' ', '-', $cmd_slug ) ) . '+sort%3Aupdated-desc+org%3Awp-cli+is%3Aclosed'; + $issue_count = get_issue_count( $cmd_slug ); + + $content .= '<div class="github-tracker">'; + + if ( $repo_url ) { + $content .= sprintf( + ' + <a href="%s"> + <img src="https://make.wordpress.org/cli/wp-content/plugins/wporg-cli/assets/images/github-mark.svg" class="icon-github" alt="GitHub" /> + </a> + ', + esc_url( $repo_url ) + ); + } + + $content .= sprintf( + ' + <div class="btn-group"> + <a href="%1$s" class="button wporg-command-github-open"> + %2$s <span class="green">%3$s</span> + </a> + <a href="%4$s" class="button wporg-command-github-closed"> + %5$s <span class="red">%6$s</span> + </a> + ', + esc_url( $open_issues ), + __( 'View Open Issues', 'wporg' ), + false !== $issue_count['open'] ? '(' . number_format_i18n( (int) $issue_count['open'] ) . ')' : '', + esc_url( $closed_issues ), + __( 'View Closed Issues', 'wporg' ), + false !== $issue_count['open'] ? '(' . number_format_i18n( (int) $issue_count['closed'] ) . ')' : '', + ); + + if ( $repo_url ) { + $content .= sprintf( + '<a href="%1$s" class="button wporg-command-github-new">%2$s</a>', + esc_url( rtrim( $repo_url, '/' ) . '/issues/new' ), + __( 'Create New Issue', 'wporg' ), + ); + } + + $content .= '</div>'; + $content .= '</div>'; + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %1$s>%2$s</section>', + $wrapper_attributes, + $content, + ); +} + + +/** + * Get issue count + */ +function get_issue_count( $cmd_slug ) { + $issue_count = array( + 'open' => false, + 'closed' => false, + ); + + foreach ( $issue_count as $key => $value ) { + $cache_key = 'wpcli_issue_count_' . md5( $cmd_slug . $key ); + $value = get_transient( $cache_key ); + if ( false === $value ) { + $request_url = 'https://api.github.com/search/issues?q=' . rawurlencode( 'label:command:' . str_replace( ' ', '-', $cmd_slug ) . ' org:wp-cli state:' . $key ); + $response = wp_remote_get( $request_url ); + $ttl = 2 * MINUTE_IN_SECONDS; + if ( 200 === wp_remote_retrieve_response_code( $response ) ) { + $data = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( isset( $data['total_count'] ) ) { + $value = $data['total_count']; + $ttl = 2 * HOUR_IN_SECONDS; + } + } + set_transient( $cache_key, $value, $ttl ); + } + $issue_count[ $key ] = $value; + } + + return $issue_count; +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-github/index.js b/source/wp-content/themes/wpr-developer-2024/src/command-github/index.js new file mode 100644 index 000000000..eaf0a841c --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-github/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; +import './style.scss'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-github/style.scss b/source/wp-content/themes/wpr-developer-2024/src/command-github/style.scss new file mode 100644 index 000000000..177e6639f --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-github/style.scss @@ -0,0 +1,61 @@ +.wp-block-wporg-command-github { + // override standard block gap between sections in the content + margin-block-start: 0 !important; + + img { + width: 23px; + position: relative; + top: 7px; + margin-inline-end: 10px; + } + + .btn-group { + display: inline; + } + + a { + text-decoration: none !important; + + &.button { + display: inline-block; + margin: 4px 0; + border: 1px solid; + border-color: #ccc; + border-radius: 3px; + background: #f7f7f7; + color: rgba(0, 0, 0, 0.8); + appearance: none; + padding: 8px 12px; + font-size: var(--wp--preset--font-size--extra-small); + box-shadow: 0 1px 0 #ccc; + line-height: 1; + margin-top: var(--wp--preset--spacing--10); + + &:hover { + border-color: #ccc #bbb #aaa #bbb; + background: #eee; + } + + .green { + color: var(--wp--custom--wporg-command-github--color--open); + } + + .red { + color: var(--wp--custom--wporg-command-github--color--closed); + } + + @media screen and (min-width: 380px) { + &.wporg-command-github-open { + margin-inline-end: -6px; + border-start-end-radius: 0; + border-end-end-radius: 0; + } + + &.wporg-command-github-closed { + border-start-start-radius: 0; + border-end-start-radius: 0; + } + } + } + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-subcommand/block.json b/source/wp-content/themes/wpr-developer-2024/src/command-subcommand/block.json new file mode 100644 index 000000000..38e057661 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-subcommand/block.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/command-subcommand", + "version": "0.1.0", + "title": "Command Subcommand", + "category": "widgets", + "description": "Command Subcommands", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-subcommand/block.php b/source/wp-content/themes/wpr-developer-2024/src/command-subcommand/block.php new file mode 100644 index 000000000..7e91a7866 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-subcommand/block.php @@ -0,0 +1,87 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Command_Subcommand; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/command-subcommand', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $children = get_children( + array( + 'post_parent' => $block->context['postId'], + 'post_type' => 'command', + 'posts_per_page' => 250, + 'orderby' => 'title', + 'order' => 'ASC', + ) + ); + + // Append subcommands if they exist + if ( ! $children ) { + return ''; + } + + $title_block = sprintf( + '<!-- wp:heading {"level":3} --><h3 class="wp-block-heading">%1$s</h3><!-- /wp:heading -->', + __( 'Subcommands', 'wporg' ) + ); + + $table_block = sprintf( + '<!-- wp:wporg/code-table {"className":"is-responsive","itemsToShow":50,"headings":%s,"rows":%s,"style":{"spacing":{"margin":{"top":"var:preset|spacing|20"}}}} /--> ', + wp_json_encode( array( __( 'Name', 'wporg' ), __( 'Description', 'wporg' ) ) ), + wp_json_encode( get_row_data( $children ) ) + ); + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %1$s>%2$s %3$s</section>', + $wrapper_attributes, + do_blocks( $title_block ), + do_blocks( $table_block ), + ); +} + +/** + * Get the changelog data for the current post. + * + * @param WP_Post[] $posts The posts to get the data from. + * @return array + */ +function get_row_data( $posts ) { + $rows = array(); + + foreach ( $posts as $post ) { + $title_link = sprintf( + '<a href="%1$s">%2$s</a>', + esc_url( apply_filters( 'the_permalink', get_permalink( $post->ID ) ) ), + esc_html( apply_filters( 'the_title', $post->post_title ) ), + ); + + $rows[] = array( $title_link, apply_filters( 'the_excerpt', $post->post_excerpt ) ); + } + + return $rows; +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-subcommand/index.js b/source/wp-content/themes/wpr-developer-2024/src/command-subcommand/index.js new file mode 100644 index 000000000..eaf0a841c --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-subcommand/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; +import './style.scss'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-subcommand/style.scss b/source/wp-content/themes/wpr-developer-2024/src/command-subcommand/style.scss new file mode 100644 index 000000000..1e5e092ec --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-subcommand/style.scss @@ -0,0 +1,7 @@ +@media screen and (max-width: 500px) { + .wp-block-wporg-command-subcommand { + thead { + display: none; + } + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-title/block.json b/source/wp-content/themes/wpr-developer-2024/src/command-title/block.json new file mode 100644 index 000000000..e7b55a4d6 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-title/block.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/command-title", + "version": "0.1.0", + "title": "Command Title", + "category": "widgets", + "description": "Command Title", + "usesContext": [ "postId" ], + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-title/block.php b/source/wp-content/themes/wpr-developer-2024/src/command-title/block.php new file mode 100644 index 000000000..3c33c5ea1 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-title/block.php @@ -0,0 +1,80 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Command_Title; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/command-title', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + + $post_ID = $block->context['postId']; + + if ( ! isset( $post_ID ) ) { + return ''; + } + + $children = get_children( + array( + 'post_parent' => $post_ID, + 'post_type' => 'command', + 'posts_per_page' => 1, + 'orderby' => 'title', + 'order' => 'ASC', + ) + ); + + $content = sprintf( + ' + <h1><a href="%1$s" rel="bookmark">%2$s + ', + get_the_permalink( $post_ID ), + get_the_title( $post_ID ), + ); + + if ( $children ) { + $content .= sprintf( + '<span><%s></span>', + __( 'command', 'wporg' ), + ); + } + + $content .= '</a>'; + $content .= '</h1>'; + + // Remove the filter that adds the code reference block to the content. + remove_filter( 'the_content', 'DevHub\filter_command_content', 4 ); + + $excerpt = get_the_excerpt( $post_ID ); + if ( $excerpt ) { + $content .= '<p class="excerpt">' . $excerpt . '</p>'; + } + + // Re-add the filter that adds this block to the content. + add_filter( 'the_content', 'DevHub\filter_command_content', 4 ); + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<section %1$s>%2$s</section>', + $wrapper_attributes, + $content, + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-title/index.js b/source/wp-content/themes/wpr-developer-2024/src/command-title/index.js new file mode 100644 index 000000000..26d63bc5a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-title/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/command-title/style.scss b/source/wp-content/themes/wpr-developer-2024/src/command-title/style.scss new file mode 100644 index 000000000..2cba793b3 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/command-title/style.scss @@ -0,0 +1,25 @@ +.wp-block-wporg-command-title { + h1 { + margin-top: 0; + margin-bottom: var(--wp--preset--spacing--30); + } + + a { + font-size: var(--wp--preset--font-size--extra-large); + line-height: 1.73; + font-family: var(--wp--preset--font-family--monospace); + font-weight: 400; + color: var(--wp--preset--color--syntax-green); + text-decoration-line: none !important; + display: block; + + &:hover, + &:focus { + opacity: 0.8; + } + } + + span { + color: var(--wp--preset--color--syntax-grey); + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/editor-style/block.json b/source/wp-content/themes/wpr-developer-2024/src/editor-style/block.json new file mode 100644 index 000000000..0869f98b8 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/editor-style/block.json @@ -0,0 +1,3 @@ +{ + "script": "file:./index.js" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/editor-style/index.js b/source/wp-content/themes/wpr-developer-2024/src/editor-style/index.js new file mode 100644 index 000000000..d0a6cc3b9 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/editor-style/index.js @@ -0,0 +1,2 @@ +// Noop, just imports the CSS for webpack. +import './style.scss'; diff --git a/source/wp-content/themes/wpr-developer-2024/src/editor-style/style.scss b/source/wp-content/themes/wpr-developer-2024/src/editor-style/style.scss new file mode 100644 index 000000000..8aeaa6ad6 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/editor-style/style.scss @@ -0,0 +1,16 @@ +// User notes styles. +#wp-link-wrap { + height: 220px !important; + margin-top: -110px !important; + + #link-selector { + position: unset; + padding-inline-start: 16px; + + #search-panel, + #wplink-link-existing-content, + #link-options .link-target { + display: none; + } + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/block.json b/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/block.json new file mode 100644 index 000000000..1991fceea --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/block.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/form-wrapper", + "version": "0.1.0", + "title": "Form Wrapper", + "category": "widgets", + "icon": "smiley", + "description": "A form wrapper block that allows you to add a form to your post or page.", + "supports": { + "html": false, + "spacing": { + "margin": true, + "padding": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/block.php b/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/block.php new file mode 100644 index 000000000..10c83cbdd --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/block.php @@ -0,0 +1,38 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Form_Wrapper; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/form-wrapper', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<form %1$s>%2$s</form>', + $wrapper_attributes, + $content + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/edit.js b/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/edit.js new file mode 100644 index 000000000..3b6b41847 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/edit.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; + +export default function Edit() { + return ( + <div { ...useBlockProps() }> + <InnerBlocks /> + </div> + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/index.js b/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/index.js new file mode 100644 index 000000000..77f808113 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/index.js @@ -0,0 +1,22 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import Edit from './edit'; +import Save from './save'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, + /** + * @see ./save.js + */ + save: Save, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/save.js b/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/save.js new file mode 100644 index 000000000..14cd5044f --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/form-wrapper/save.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; + +export default function Save() { + return ( + <div { ...useBlockProps.save() }> + <InnerBlocks.Content /> + </div> + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/page-title/block.json b/source/wp-content/themes/wpr-developer-2024/src/page-title/block.json new file mode 100644 index 000000000..ffa7f300b --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/page-title/block.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/page-title", + "version": "0.1.0", + "title": "Page Title", + "category": "widgets", + "description": "Page Title", + "attributes": { + "level": { + "type": "integer", + "default": 1 + } + }, + "supports": { + "html": false, + "spacing": { + "margin": true, + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "textdomain": "wporg", + "editorScript": "file:./index.js" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/page-title/index.js b/source/wp-content/themes/wpr-developer-2024/src/page-title/index.js new file mode 100644 index 000000000..abba51c28 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/page-title/index.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/page-title/index.php b/source/wp-content/themes/wpr-developer-2024/src/page-title/index.php new file mode 100644 index 000000000..24541a676 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/page-title/index.php @@ -0,0 +1,50 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Page_Title; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/page-title', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + $tag_name = 'h1'; + if ( isset( $attributes['level'] ) ) { + $tag_name = 0 === $attributes['level'] ? 'p' : 'h' . (int) $attributes['level']; + } + + $title = get_the_title(); + + if ( is_search() ) { + $title = __( 'Search results', 'wporg' ); + } elseif ( is_archive() ) { + $title = get_the_archive_title(); + } elseif ( in_array( get_post_type(), array( 'wp-parser-function', 'wp-parser-method' ) ) ) { + $title .= '()'; + } + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<%1$s %2$s>%3$s</section>', + $tag_name, + $wrapper_attributes, + $title, + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/prism/block.json b/source/wp-content/themes/wpr-developer-2024/src/prism/block.json new file mode 100644 index 000000000..0869f98b8 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/prism/block.json @@ -0,0 +1,3 @@ +{ + "script": "file:./index.js" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/prism/index.js b/source/wp-content/themes/wpr-developer-2024/src/prism/index.js new file mode 100644 index 000000000..d0a6cc3b9 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/prism/index.js @@ -0,0 +1,2 @@ +// Noop, just imports the CSS for webpack. +import './style.scss'; diff --git a/source/wp-content/themes/wpr-developer-2024/src/prism/style.scss b/source/wp-content/themes/wpr-developer-2024/src/prism/style.scss new file mode 100644 index 000000000..6c9b6a16c --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/prism/style.scss @@ -0,0 +1,275 @@ +/** + * Custom theme for developer.wordpress.org. + * Forked from a11y-dark theme by ericwbailey, which was based on the okaidia theme. + * + * https://github.com/PrismJS/prism-themes/blob/master/themes/prism-a11y-dark.css + * https://github.com/PrismJS/prism/blob/gh-pages/themes/prism-okaidia.css + */ + +@import "../../scss/settings/colors"; + +code[class*="language-"], +pre[class*="language-"] { + color: get-color(gray-100); + background: none; + font-family: var(--wp--preset--font-family--monospace); + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; + + transition: height 500ms; +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + overflow: auto; + border-radius: 0.3em; + direction: ltr; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: get-color(gray-0); +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: get-color(green-60); +} + +.token.punctuation { + color: get-color(gray-60); +} + +.token.property, +.token.tag, +.token.constant, +.token.symbol, +.token.deleted { + color: get-color(blue-80); +} + +.token.boolean, +.token.number { + color: get-color(gray-100); +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: get-color(blue-50); +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { + color: #a85f00; +} + +.token.function { + color: #b8236d; +} + +.token.keyword { + color: get-color(blue-60); + font-weight: 600; +} + +.token.atrule, +.token.attr-value, +.token.function-definition { + color: get-color(gray-100); +} + +.token.important, +.token.bold { + font-weight: 700; +} + +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +@media screen and (-ms-high-contrast: active) { + code[class*="language-"], + pre[class*="language-"] { + color: windowText; + background: window; + } + + :not(pre) > code[class*="language-"], + pre[class*="language-"] { + background: window; + } + + .token.important { + background: highlight; + color: window; + font-weight: 700; + } + + .token.atrule, + .token.attr-value, + .token.function, + .token.keyword, + .token.operator, + .token.selector { + font-weight: 700; + } + + .token.attr-value, + .token.comment, + .token.doctype, + .token.function, + .token.keyword, + .token.operator, + .token.property, + .token.string { + color: highlight; + } + + .token.attr-value, + .token.url { + font-weight: 700; + } +} + +/* Line Numbers */ +pre.line-numbers { + position: relative; + padding-left: 3.8em; + counter-reset: linenumber; +} + +pre.line-numbers > code { + position: relative; +} + +.line-numbers .line-numbers-rows { + position: absolute; + pointer-events: none; + top: 0; + font-size: 100%; + left: -3.8em; + width: 4em; /* works for line-numbers below 10000 lines */ + letter-spacing: -1px; + border-right: 0; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + +} + +.line-numbers-rows > span { + pointer-events: none; + display: block; + counter-increment: linenumber; +} + +.line-numbers-rows > span::before { + content: counter(linenumber); + color: #5c6370; + display: block; + padding-right: 0.8em; + text-align: right; +} + +/* Copy button */ +.wp-code-block-button-container { + z-index: 1; + display: flex; + justify-content: right; + padding: 1rem; + background: $color-white; + border-width: 1px 1px 0; + border-style: solid; + border-color: get-color(gray-5); + border-top-left-radius: 0.3em; + border-top-right-radius: 0.3em; + + @media screen and (max-width: 889px) { + top: var(--wp-admin--admin-bar--height); + } + + @media screen and (max-width: 600px) { + top: 0; + } + + button { + font-size: 1.2rem !important; + } + + button + button { + margin-left: 0.5em; + } + + + pre { + margin-top: 0; + border-width: 0 1px 1px; + border-style: solid; + border-color: get-color(gray-5); + border-top-left-radius: 0; + border-top-right-radius: 0; + } +} + +/* + * Make the sticky wp-code-block-button-container not overlap its parent's border. + * A trikcy way is used here (plus and minus margin-bottom), + * as we can't just adjust the pre in _global.scss, + * or page like https://developer.wordpress.org/resource/dashicons/#randomize would be affected. + */ + +.wporg-developer-code-block { + display: flex; + flex-direction: column; + margin-bottom: 1.6em; + font-size: 16px; + + &::after { + content: ""; + + /* Since the margin-bottom of the pre defined in _global.scss is 1em, + * -1.1em is set here to make the shape of the rounded corners at the bottom of + * the code block complete when the sticky wp-code-block-button-container is scrolled there. + * + * See https://github.com/WordPress/wporg-developer/pull/148#issuecomment-1289595980 + */ + margin-bottom: -1.1em; + } +} + diff --git a/source/wp-content/themes/wpr-developer-2024/src/reference-new-updated/block.json b/source/wp-content/themes/wpr-developer-2024/src/reference-new-updated/block.json new file mode 100644 index 000000000..fd3d3075e --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/reference-new-updated/block.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/reference-new-updated", + "version": "0.1.0", + "title": "Reference: New and updated", + "category": "widgets", + "icon": "smiley", + "description": "Show a list of the things that are new or updated for a release", + "supports": {}, + "textdomain": "wporg", + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/reference-new-updated/block.php b/source/wp-content/themes/wpr-developer-2024/src/reference-new-updated/block.php new file mode 100644 index 000000000..c94fd3a84 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/reference-new-updated/block.php @@ -0,0 +1,106 @@ +<?php +namespace WordPressdotorg\Theme\Developer_2023\Reference_New_Updated; + +use function DevHub\get_current_version_term; +use function DevHub\get_parsed_post_types; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/reference-new-updated', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + $version = get_current_version_term(); + + if ( ! ( $version && ! is_wp_error( $version ) ) ) { + return ''; + } + + $list = new \WP_Query( + array( + 'posts_per_page' => 15, + 'post_type' => get_parsed_post_types(), + 'orderby' => 'title', + 'order' => 'ASC', + 'tax_query' => array( + array( + 'taxonomy' => 'wp-parser-since', + 'field' => 'ids', + 'terms' => $version->term_id, + ), + ), + ) + ); + + $content = '<!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|20","bottom":"var:preset|spacing|20","left":"var:preset|spacing|20","right":"var:preset|spacing|20"},"margin":{"top":"var:preset|spacing|20"}},"border":{"width":"1px","radius":"2px"}},"borderColor":"light-grey-1","layout":{"type":"constrained"}} --> + <div class="wp-block-group has-border-color has-light-grey-1-border-color" style="border-width:1px;border-radius:2px;margin-top:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--20);padding-right:var(--wp--preset--spacing--20);padding-bottom:var(--wp--preset--spacing--20);padding-left:var(--wp--preset--spacing--20)"> + + <!-- wp:list {"style":{"spacing":{"padding":{"left":"0"}}},"className":"wporg-reference-list","fontSize":"small"} --> + <ul class="wporg-reference-list has-small-font-size" style="padding-left:0">'; + + while ( $list->have_posts() ) : + $list->the_post(); + + $content .= sprintf( + '<!-- wp:list-item --><li><a href="%s">%s</a></li><!-- /wp:list-item -->', + esc_url( get_permalink() ), + esc_html( get_the_title() ) + ); + + endwhile; + + $content .= '</ul><!-- /wp:list --></div><!-- /wp:group -->'; + $version_name = substr( $version->name, 0, -2 ); + + $title_block = sprintf( + '<!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"space-between"}} --> + <div class="wp-block-group"> + + <!-- wp:heading {"style":{"typography":{"fontStyle":"normal","fontWeight":"600"}},"fontSize":"heading-5"} --> + <h2 class="wp-block-heading has-heading-5-font-size" style="font-style:normal;font-weight:600">%s</h2> + <!-- /wp:heading --> + + <!-- wp:paragraph --> + <p><a href="%s"><span aria-hidden="true">%s</span><span class="screen-reader-text">%s</span></a></p> + <!-- /wp:paragraph --> + + </div> + <!-- /wp:group -->', + // translators: %s is the version name + sprintf( __( 'New and updated in %s', 'wporg' ), $version_name ), + esc_attr( get_term_link( $version, 'wp-parser-since' ) ), + __( 'View all', 'wporg' ), + // translators: %s is the version name + sprintf( __( 'View all new and updated in %s', 'wporg' ), $version_name ), + ); + + $wrapper_attributes = get_block_wrapper_attributes(); + return __METHOD__. sprintf( + '<div %s>%s %s</div>', + $wrapper_attributes, + do_blocks( $title_block ), + do_blocks( $content ) + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/reference-new-updated/index.js b/source/wp-content/themes/wpr-developer-2024/src/reference-new-updated/index.js new file mode 100644 index 000000000..abba51c28 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/reference-new-updated/index.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + /** + * @see ./edit.js + */ + edit: Edit, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/resource-select/block.json b/source/wp-content/themes/wpr-developer-2024/src/resource-select/block.json new file mode 100644 index 000000000..548c5d03d --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/resource-select/block.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/resource-select", + "title": "Resource Select", + "category": "layout", + "description": "Displays a selector to choose which resource to search.", + "keywords": [ "post" ], + "textdomain": "wporg", + "attributes": { + "hideLabelFromVision": { + "type": "boolean", + "default": false + }, + "label": { + "type": "string", + "default": "" + } + }, + "supports": { + "html": false, + "color": { + "text": true, + "background": true + }, + "spacing": { + "margin": [ + "top", + "bottom" + ] + }, + "typography": { + "fontSize": true + } + }, + "editorScript": "file:./index.js", + "style": "file:./style-index.css", + "viewScript": "file:./view.js" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/resource-select/index.js b/source/wp-content/themes/wpr-developer-2024/src/resource-select/index.js new file mode 100644 index 000000000..77d99c307 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/resource-select/index.js @@ -0,0 +1,16 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + edit: Edit, + save: () => null, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/resource-select/index.php b/source/wp-content/themes/wpr-developer-2024/src/resource-select/index.php new file mode 100644 index 000000000..fad696c9e --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/resource-select/index.php @@ -0,0 +1,112 @@ +<?php +/** + * Block Name: Resource Select + * Description: Displays a selector to choose which resource to search. + * + * @package wporg + */ + +namespace WordPressdotorg\Theme\Developer_2023\Resource_Select; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/resource-select', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + global $wp_query; + $search_query = $wp_query->query_vars['s']; + + $resources = array( + array( + 'url' => '/', + 'label' => __( 'Code Reference', 'wporg' ), + ), + array( + 'url' => '/coding-standards/', + 'label' => __( 'Coding Standards', 'wporg' ), + ), + array( + 'url' => '/block-editor/', + 'label' => __( 'Block Editor', 'wporg' ), + ), + array( + 'url' => '/apis/', + 'label' => __( 'Common APIs', 'wporg' ), + ), + array( + 'url' => '/themes/', + 'label' => __( 'Themes', 'wporg' ), + ), + array( + 'url' => '/plugins/', + 'label' => __( 'Plugins', 'wporg' ), + ), + array( + 'url' => '/rest-api/', + 'label' => __( 'REST API', 'wporg' ), + ), + array( + 'url' => '/cli/commands/', + 'label' => __( 'CLI Commands', 'wporg' ), + ), + array( + 'url' => '/advanced-administration/', + 'label' => __( 'Advanced Administration', 'wporg' ), + ), + ); + + $options = ''; + foreach ( $resources as $resource ) { + $selected = false; + + // Compare the source url with the uri minus query vars. + if ( $resource['url'] === strtok( $_SERVER['REQUEST_URI'], '?' ) ) { + $selected = true; + } + + $options .= sprintf( + '<option value="%1$s" %2$s>%3$s</option>', + esc_attr( $resource['url'] . '?s=' . $search_query ), + esc_attr( selected( $selected, true, false ) ), + esc_html( $resource['label'] ) + ); + } + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<div %1$s> + <label class="%2$s" for="%3$s">%4$s</label> + <div class="wporg-resource-select-container"> + <select name="search-source" id="%3$s">%5$s</select> + </div> + </div>', + $wrapper_attributes, + esc_attr( isset( $attributes['hideLabelFromVision'] ) && true === $attributes['hideLabelFromVision'] ? 'screen-reader-text' : '' ), + esc_attr( 'wp-block-wporg-resource-select' ), + esc_html( $attributes['label'] ), + $options, + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/resource-select/style.scss b/source/wp-content/themes/wpr-developer-2024/src/resource-select/style.scss new file mode 100644 index 000000000..097ab45c3 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/resource-select/style.scss @@ -0,0 +1,10 @@ +.wp-block-wporg-resource-select { + select { + text-align: end; + + @media screen and (max-width: 768px) { + margin-top: 20px; + text-align: unset; + } + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/resource-select/view.js b/source/wp-content/themes/wpr-developer-2024/src/resource-select/view.js new file mode 100644 index 000000000..e1cf9c130 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/resource-select/view.js @@ -0,0 +1,13 @@ +const init = () => { + const selector = document.querySelector( '#wp-block-wporg-resource-select' ); + + if ( ! selector ) { + return; + } + + selector.addEventListener( 'change', ( event ) => { + window.location = event.target.value; + } ); +}; + +window.addEventListener( 'load', init ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/search-filters/block.json b/source/wp-content/themes/wpr-developer-2024/src/search-filters/block.json new file mode 100644 index 000000000..0fb5be559 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/search-filters/block.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/search-filters", + "title": "Search Filters", + "category": "layout", + "description": "List of filters for searching.", + "keywords": [ "search" ], + "textdomain": "wporg", + "attributes": {}, + "supports": { + "html": false, + "spacing": { + "margin": [ + "top", + "bottom" + ], + "padding": true + }, + "typography": { + "fontSize": true, + "lineHeight": true + } + }, + "editorScript": "file:./index.js", + "style": "file:./style-index.css", + "viewScript": "file:./view.js" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/search-filters/index.js b/source/wp-content/themes/wpr-developer-2024/src/search-filters/index.js new file mode 100644 index 000000000..c592cb95a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/search-filters/index.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; +import { useBlockProps } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import './style.scss'; + +function Edit() { + return <div { ...useBlockProps() }>Search Filters</div>; +} + +registerBlockType( metadata.name, { + edit: Edit, + save: () => null, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/search-filters/index.php b/source/wp-content/themes/wpr-developer-2024/src/search-filters/index.php new file mode 100644 index 000000000..efe7d52e3 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/search-filters/index.php @@ -0,0 +1,81 @@ +<?php +/** + * Block Name: Search Title + * Description: A dynamic list of code references. + * + * @package wporg + */ + +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Search_Filters; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/search-filters', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + $search_post_types = array( + 'wp-parser-function' => __( 'Functions', 'wporg' ), + 'wp-parser-hook' => __( 'Hooks', 'wporg' ), + 'wp-parser-class' => __( 'Classes', 'wporg' ), + 'wp-parser-method' => __( 'Methods', 'wporg' ), + ); + $qv_post_type = array_filter( (array) get_query_var( 'post_type' ) ); + $no_filters = true === $GLOBALS['wp_query']->is_empty_post_type_search; + if ( in_array( 'any', $qv_post_type ) || $no_filters ) { + // No filters used. + $qv_post_type = array(); + } + + $content = '<div class="wp-block-button is-style-toggle is-small">'; + $content .= sprintf( + '<button id="wp-block-wporg-search-filters-all" class="wp-block-button__link wp-element-button" aria-pressed="%1$s">%2$s</button>', + empty( $qv_post_type ) ? 'true' : 'false', + __( 'All', 'wporg' ), + ); + $content .= '</div>'; + + foreach ( $search_post_types as $post_type => $label ) { + $input_id = esc_attr( $post_type ); + $checked = checked( in_array( $post_type, $qv_post_type ), true, false ); + $content .= '<div class="wp-block-button is-style-toggle is-small">'; + $content .= sprintf( '<input id="%1$s" type="checkbox" name="post_type[]" value="%1$s" %2$s />', $input_id, $checked ); + $content .= sprintf( '<label for="%1$s" class="wp-block-button__link wp-element-button">%2$s</label>', $input_id, $label ); + $content .= '</div>'; + } + + $wrapper_attributes = get_block_wrapper_attributes(); + return sprintf( + '<div %1$s> + %2$s + <div class="wp-block-button is-style-text is-small"> + <button class="wp-block-button__link wp-element-button" type="submit">%3$s</button> + </div> + </div>', + $wrapper_attributes, + $content, + esc_attr( __( 'Apply', 'wporg' ) ) + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/search-filters/style.scss b/source/wp-content/themes/wpr-developer-2024/src/search-filters/style.scss new file mode 100644 index 000000000..2143b03e7 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/search-filters/style.scss @@ -0,0 +1,22 @@ +.wp-block-wporg-search-filters { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--wp--preset--spacing--20); + margin-top: var(--wp--preset--spacing--20); + + input[type="checkbox"] { + border: 0; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + top: 0; + inset-inline-start: 0; + width: 1px; + word-wrap: normal !important; + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/search-filters/view.js b/source/wp-content/themes/wpr-developer-2024/src/search-filters/view.js new file mode 100644 index 000000000..3420f83d7 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/search-filters/view.js @@ -0,0 +1,32 @@ +const init = () => { + const allButton = document.querySelector( '#wp-block-wporg-search-filters-all' ); + const container = document.querySelector( '.wp-block-wporg-search-filters' ); + + if ( ! allButton || ! container ) { + return; + } + + const inputs = container.querySelectorAll( 'input[type="checkbox"]' ); + + const hasFilterChecked = () => [ ...inputs ].some( ( input ) => input.checked ); + + const updateAllButtonState = () => { + allButton.setAttribute( 'aria-pressed', ! hasFilterChecked() ); + }; + + allButton.addEventListener( 'click', ( event ) => { + event.preventDefault(); + inputs.forEach( ( input ) => { + input.checked = false; + } ); + updateAllButtonState(); + } ); + + container.addEventListener( 'change', () => { + updateAllButtonState(); + } ); + + updateAllButtonState(); +}; + +window.addEventListener( 'load', init ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/search-post/block.json b/source/wp-content/themes/wpr-developer-2024/src/search-post/block.json new file mode 100644 index 000000000..33255383c --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/search-post/block.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/search-post", + "title": "Search Post", + "category": "layout", + "description": "The post template for search results.", + "keywords": [ "post" ], + "textdomain": "wporg", + "attributes": {}, + "supports": { + "html": false, + "spacing": { + "margin": true, + "padding": true + } + }, + "usesContext": [ "postId" ], + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/search-post/index.js b/source/wp-content/themes/wpr-developer-2024/src/search-post/index.js new file mode 100644 index 000000000..77d99c307 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/search-post/index.js @@ -0,0 +1,16 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import Edit from '../shared/dynamic-edit'; +import metadata from './block.json'; +import './style.scss'; + +registerBlockType( metadata.name, { + edit: Edit, + save: () => null, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/search-post/index.php b/source/wp-content/themes/wpr-developer-2024/src/search-post/index.php new file mode 100644 index 000000000..7cb5beb20 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/search-post/index.php @@ -0,0 +1,103 @@ +<?php +/** + * Block Name: Search Post + * Description: The post template for search results. + * + * @package wporg + */ + +namespace WordPressdotorg\Theme\Developer_2023\Dynamic_Search_Post; + +use function DevHub\is_parsed_post_type; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/search-post', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} + +/** + * Render the post to be shown in search results (default). + */ +function render_post() { + return do_blocks( + '<!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|20","bottom":"var:preset|spacing|20","left":"var:preset|spacing|20","right":"var:preset|spacing|20"}},"border":{"width":"1px","color":"#d9d9d9","radius":"2px"}}} --> + <div class="wp-block-group has-border-color" style="border-color:#d9d9d9;border-width:1px;border-radius:2px;padding-top:var(--wp--preset--spacing--20);padding-right:var(--wp--preset--spacing--20);padding-bottom:var(--wp--preset--spacing--20);padding-left:var(--wp--preset--spacing--20)"> + + <!-- wp:wporg/code-short-title /--> + + <!-- wp:post-excerpt /--> + + </div> + <!-- /wp:group -->' + ); +} + +/** + * Render the post to be shown in search results, when the post is a code reference item. + */ +function render_code_reference_post() { + return do_blocks( + '<!-- wp:group {"align":"wide","layout":{"type":"constrained","justifyContent":"left"},"style":{"spacing":{"padding":{"top":"var:preset|spacing|10","bottom":"var:preset|spacing|10"}}}} --> + <div class="wp-block-group alignwide" style="padding-top:var(--wp--preset--spacing--10);padding-bottom:var(--wp--preset--spacing--10)"> + + <!-- wp:group {"layout":{"type":"constrained"}} --> + <div class="wp-block-group"> + + <!-- wp:wporg/code-short-title /--> + + <!-- wp:post-excerpt /--> + + <!-- wp:wporg/code-type-usage-info {"style":{"elements":{"link":{"color":{"text":"var:preset|color|charcoal-4"}}}},"textColor":"charcoal-4","fontSize":"small"} /--> + + </div> + <!-- /wp:group --> + + </div> + <!-- /wp:group -->' + ); +} + +/** + * Render the block content. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block markup. + */ +function render( $attributes, $content, $block ) { + if ( ! isset( $block->context['postId'] ) ) { + return ''; + } + + $post_type = get_post_type( $block->context['postId'] ); + $is_parsed_type = is_parsed_post_type( $post_type ); + + $content_html = $is_parsed_type ? render_code_reference_post() : render_post(); + + $wrapper_attributes = get_block_wrapper_attributes( + array( + 'class' => $is_parsed_type ? 'wp-block-wporg-search-post-parsed' : '', + ) + ); + + return sprintf( + '<div %1$s>%2$s</div>', + $wrapper_attributes, + $content_html, + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/search-post/style.scss b/source/wp-content/themes/wpr-developer-2024/src/search-post/style.scss new file mode 100644 index 000000000..72ba4acca --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/search-post/style.scss @@ -0,0 +1,16 @@ +.wp-block-wporg-search-post { + .wp-block-post-excerpt { + margin: calc(var(--wp--preset--spacing--10) / 2) 0; + } + + .wp-block-post-excerpt__excerpt { + margin: 0; + } + + &:not(.wp-block-wporg-search-post-parsed) { + .wp-block-post-excerpt__excerpt { + font-size: var(--wp--preset--font-size--small); + line-height: var(--wp--custom--body--small--typography--line-height); + } + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/search-results-context/block.json b/source/wp-content/themes/wpr-developer-2024/src/search-results-context/block.json new file mode 100644 index 000000000..62864a27d --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/search-results-context/block.json @@ -0,0 +1,44 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "wporg/search-results-context", + "version": "0.1.0", + "title": "Search Results Context", + "category": "design", + "icon": "", + "description": "Displays context information for search results.", + "textdomain": "wporg", + "attributes": { + "tagName": { + "type": "string", + "default": "p" + } + }, + "supports": { + "align": true, + "color": true, + "html": false, + "spacing": { + "margin": true, + "padding": true, + "blockGap": false + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalFontStyle": true, + "__experimentalFontWeight": true, + "__experimentalLetterSpacing": true, + "__experimentalTextTransform": true, + "__experimentalTextDecoration": true, + "__experimentalDefaultControls": { + "fontSize": true, + "fontAppearance": true, + "textTransform": true + } + } + }, + "editorScript": "file:./index.js", + "style": "file:./style-index.css" +} \ No newline at end of file diff --git a/source/wp-content/themes/wpr-developer-2024/src/search-results-context/index.js b/source/wp-content/themes/wpr-developer-2024/src/search-results-context/index.js new file mode 100644 index 000000000..1981cd5f3 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/search-results-context/index.js @@ -0,0 +1,19 @@ +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; +import { useBlockProps } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; + +function Edit() { + return <div { ...useBlockProps() }>Search Results Context</div>; +} + +registerBlockType( metadata.name, { + edit: Edit, + save: () => null, +} ); diff --git a/source/wp-content/themes/wpr-developer-2024/src/search-results-context/index.php b/source/wp-content/themes/wpr-developer-2024/src/search-results-context/index.php new file mode 100644 index 000000000..737a53604 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/search-results-context/index.php @@ -0,0 +1,80 @@ +<?php +/** + * Block Name: Search Results Context + * Description: Displays context information for search results. + * + * @package wporg + */ + +namespace WordPressdotorg\Theme\Developer_2023\Search_Results_Context; + +add_action( 'init', __NAMESPACE__ . '\init' ); + +/** + * Render the block content. + * + * @return string Returns the block markup. + */ +function render( $attributes ) { + global $wp_query; + + if ( ! is_search() ) { + return ''; + } + + $results_count = $wp_query->found_posts; + + if ( 0 === $results_count ) { + return; + } + + $posts_per_page = get_query_var( 'posts_per_page' ); + $current_page = get_query_var( 'paged' ) ?: 1; + $first_result = ($current_page - 1) * $posts_per_page + 1; + $last_result = min($current_page * $posts_per_page, $results_count); + + $content = sprintf( + /* translators: %1$s number of results; %2$s keyword. */ + _n( + '%1$s result found for "%2$s".', + '%1$s results found for "%2$s".', + $results_count, + 'wporg' + ), + number_format_i18n( $results_count ), + esc_html( $wp_query->query['s'] ), + ); + + $showing = sprintf( + /* translators: %1$s number of first displayed result, %2$s number of last displayed result. */ + 'Showing results %1$s to %2$s.', + number_format_i18n( $first_result ), + number_format_i18n( $last_result ), + ); + + $wrapper_attributes = get_block_wrapper_attributes(); + + return sprintf( + '<%1$s %2$s>%3$s %4$s</%1$s>', + esc_attr( $attributes['tagName'] ), + $wrapper_attributes, + $content, + $showing, + ); +} + +/** + * Registers the block using the metadata loaded from the `block.json` file. + * Behind the scenes, it registers also all assets so they can be enqueued + * through the block editor in the corresponding context. + * + * @see https://developer.wordpress.org/reference/functions/register_block_type/ + */ +function init() { + register_block_type( + dirname( dirname( __DIR__ ) ) . '/build/search-results-context', + array( + 'render_callback' => __NAMESPACE__ . '\render', + ) + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/shared/dynamic-edit.js b/source/wp-content/themes/wpr-developer-2024/src/shared/dynamic-edit.js new file mode 100644 index 000000000..62679697b --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/shared/dynamic-edit.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { useBlockProps } from '@wordpress/block-editor'; +import ServerSideRender from '@wordpress/server-side-render'; + +export default function Edit( { name, attributes, context } ) { + const blockProps = useBlockProps(); + const { postId } = context; + return ( + <div { ...blockProps }> + <ServerSideRender + block={ name } + attributes={ attributes } + skipBlockSupportAttributes + urlQueryArgs={ { post_id: postId } } + /> + </div> + ); +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/style/block.json b/source/wp-content/themes/wpr-developer-2024/src/style/block.json new file mode 100644 index 000000000..0869f98b8 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/style/block.json @@ -0,0 +1,3 @@ +{ + "script": "file:./index.js" +} diff --git a/source/wp-content/themes/wpr-developer-2024/src/style/index.js b/source/wp-content/themes/wpr-developer-2024/src/style/index.js new file mode 100644 index 000000000..d0a6cc3b9 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/style/index.js @@ -0,0 +1,2 @@ +// Noop, just imports the CSS for webpack. +import './style.scss'; diff --git a/source/wp-content/themes/wpr-developer-2024/src/style/style.scss b/source/wp-content/themes/wpr-developer-2024/src/style/style.scss new file mode 100644 index 000000000..d91b7d310 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/src/style/style.scss @@ -0,0 +1,638 @@ +/* + * Note: only add styles here in cases where you can't achieve the style with + * templates or theme.json settings. + */ + +.wporg-breadcrumbs { + // If the breadcrumbs are present, the space after them needs to be reduced. + // The next content is typically the search field. + margin-bottom: calc(var(--wp--preset--spacing--10) * -1); +} + +pre { + overflow: auto; +} + +.entry-content { + // Parsed handbook content doesn't use image blocks, so large images are + // missing the max-width and break out of the page container. + p, + ol, + ul { + img { + max-width: 100%; + } + } + + blockquote { + border-left: 2px solid var(--wp--preset--color--charcoal-1); + margin-left: 0; + margin-right: 0; + padding-left: var(--wp--preset--spacing--20); + } + + pre:not(.wp-block-code) { + padding: 20px; + background-color: #f7f7f7; + border: 1px solid var(--wp--preset--color--light-grey-1); + border-radius: 2px; + overflow: scroll; + } + + code { + display: inline-block; + line-height: var(--wp--custom--body--extra-small--typography--line-height); + background: var(--wp--preset--color--light-grey-2); + border-radius: 2px; + padding-inline-start: 3px; + padding-inline-end: 3px; + max-width: 100%; + } + + table { + width: 100%; + } +} + +.wporg-filtered-search-form { + display: block; +} + +.is-content-justification-left > [class*="wp-block-"] { + margin-left: unset !important; + margin-right: unset !important; + margin-inline-start: 0; +} + +// Wrap meta on smaller screens. Double class needed to override core style. +.entry-meta.entry-meta { + + @media (max-width: 781px) { + flex-wrap: wrap; + + > * { + min-width: calc(50% - var(--wp--preset--spacing--20) / 2); + } + } + + @media (max-width: 479px) { + > * { + min-width: 100%; + } + } + + time { + white-space: nowrap; + } +} + +.wporg-hero-graphic { + overflow: hidden; +} + +@media (max-width: 1320px) { + .wp-block-image.wporg-hero-graphic img { + margin-left: -1%; + margin-right: -1%; + max-width: 102%; + width: 102%; + } +} + +.wporg-front-page-breadcrumb { + position: unset; // We don't want it to be sticky on the front page, but removing is-sticky breaks the layout. +} + +@media (min-width: 599px) { + .wporg-front-page-breadcrumb h2 { + display: none; + } +} + +.wporg-front-page-footer { + .wp-block-heading a { + text-decoration: none; + + &:hover { + text-decoration-line: underline; + } + } +} + +.home { + .entry-content { + --wp--preset--spacing--60: clamp(40px, 5.55556vw, 80px); + } +} + +.wp-block-search div.awesomplete > ul { + left: unset; + inset-inline-start: 0; +} + +// Style code tags but not ones inside of the code block. +.wporg-has-embedded-code *:not(.wp-block-code):not(.wp-code-block-button-container) > code { + padding: 4px 6px; + background-color: var(--wp--preset--color--light-grey-2); + font-size: var(--wp--preset--font-size--small); + border-radius: 2px; +} + +.wporg-dot-link-list { + margin: var(--wp--style--block-gap) 0; + + a { + position: relative; + text-decoration: underline; + margin-inline-end: var(--wp--preset--spacing--10); + padding-inline-end: var(--wp--preset--spacing--10); + + &::after { + content: "\b7"; + position: absolute; + inset-inline-end: -3px; + } + + &:last-child::after { + display: none; + } + } +} + +.wp-editor-container { + position: relative; + border: 1px solid var(--wp--preset--color--charcoal-4); + + .quicktags-toolbar { + padding: 5px; + background-color: var(--wp--preset--color--white); + border-bottom: none; + + input[type="button"] { + background-color: var(--wp--preset--color--white); + border: none; + color: var(--wp--preset--color--charcoal-1); + } + } +} + +.wp-editor-area { + display: block; + border: none; +} + +.comment-form { + position: relative; + + .tablist { + display: flex; + gap: var(--wp--preset--spacing--10); + margin: 0; + padding: var(--wp--preset--spacing--10); + list-style: none; + z-index: 1; + top: 0; + inset-inline-end: 0; + + li { + min-width: 50px; + } + + a { + color: var(--wp--preset--color--charcoal-1); + + &[aria-selected="true"] { + font-weight: 700; + } + } + } + + .tab-section:not(.comment-preview) { + margin-top: 0; + padding: 0; + border: none; + } + + .tab-section[aria-hidden="true"] { + display: none; + } + + .tab-section:focus { + background: #eee; + outline: thin dotted; + } + + .wp-editor-container, + .comment-preview { + border: 1px solid #dcdcde; + } + + .wp-editor-area, + .comment-preview { + padding: var(--wp--preset--spacing--10); + } + + .preview-content pre { + padding: var(--wp--preset--spacing--10); + background: var(--wp--preset--color--light-grey-2); + } + + .wp-editor-area { + min-height: 240px; + } + + .form-submit { + display: flex; + flex-direction: column; + gap: var(--wp--preset--spacing--10); + } + + @media screen and (min-width: 550px) { + .form-submit { + align-items: center; + flex-direction: row; + justify-content: space-between; + } + + .logged-in-as a { + text-decoration: underline; + } + } +} + +.wp-block-wporg-code-reference-changelog + .entry-meta { + padding-top: 0 !important; + border-top: none; +} + +.wporg-version-filters { + font-size: var(--wp--preset--font-size--small); + + form { + display: flex; + align-items: center; + } + + .wp-block-wporg-form-wrapper { + display: flex; + align-items: center; + gap: var(--wp--preset--spacing--20); + } + + .wp-block-wporg-search-filters { + margin-top: 0; + } +} + +.wp-block-wporg-resource-select { + select { + display: block; + appearance: none; + /* stylelint-disable-next-line function-url-quotes */ + background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M15.9899 10.8889L12.0018 14.3073L8.01367 10.8889L8.98986 9.75L12.0018 12.3316L15.0137 9.75L15.9899 10.8889Z' fill='%231E1E1E'/%3E%3C/svg%3E%0A"); + background-size: var(--wp--custom--select--icon-size); + background-repeat: no-repeat; + background-position: right var(--wp--custom--select--icon-position) center; + background-color: transparent; + padding-top: var(--wp--custom--select--padding-vertical); + padding-inline-end: calc(var(--wp--custom--select--icon-position) + var(--wp--custom--select--icon-size)); + padding-bottom: var(--wp--custom--select--padding-vertical); + padding-inline-start: var(--wp--custom--select--padding-left); + font-size: var(--wp--custom--select--font-size); + line-height: var(--wp--custom--select--line-height); + border-radius: var(--wp--custom--select--border-radius); + border: 0 solid transparent; + font-family: inherit; + cursor: pointer; + color: inherit; + white-space: nowrap; + text-overflow: ellipsis; + + &:focus { + outline: 1.5px solid var(--wp--preset--color--blueberry-1); + outline-offset: -1.5px; + } + } + + .hideLabelFromVision { + visibility: hidden; + } +} + +.wporg-search-controls { + + @media screen and (max-width: 768px) { + display: block !important; + } +} + +.wporg-reference-list, +.wp-block-navigation .wporg-reference-list { + list-style: none; + + > li, + .wp-block-navigation-item { + &:not(:last-child) { + margin-bottom: var(--wp--preset--spacing--20); + } + } + + > a, + .wp-block-navigation-item__content { + color: var(--wp--custom--link--color--text); + text-decoration: underline; + } +} + +.single-command { + h3 { + margin-bottom: var(--wp--preset--spacing--20); + } + + dt { + font-weight: 700; + } + + table { + border-collapse: collapse; + + code { + background: revert; + padding: revert; + border-radius: revert; + } + + tr { + vertical-align: top; + border-bottom: 1px solid var(--wp--preset--color--light-grey-1); + } + + th, + td { + text-align: start; + padding: 10px; + } + + td > p { + margin: 0; + } + } +} + +.wp-block-table code { + background-color: unset; +} + +.wporg-developer-code-block { + $half_padding: calc(var(--wp--preset--spacing--10) / 2); + $border_radius: 2px; + + .wp-code-block-button-container { + background-color: var(--wp--preset--color--light-grey-2); + border-radius: $border_radius $border_radius 0 0; + padding: var(--wp--preset--spacing--10); + align-items: center; + justify-content: space-between; + flex-direction: column; + gap: var(--wp--preset--spacing--10); + + > code { + word-break: break-all; + } + + .wp-block-button a { + background-color: var(--wp--preset--color--white); + text-wrap: nowrap; + } + + > span:last-child { + display: flex; + gap: 8px; + justify-content: end; + } + } + + @media screen and (min-width: 600px) { + .wp-code-block-button-container { + display: grid; + grid-template-columns: auto 13rem; + } + } + + .wp-code-block-button-container + pre { + background-color: var(--wp--preset--color--white); + border-color: var(--wp--preset--color--light-grey-1); + } + + .wp-block-code { + border-radius: 0 0 $border_radius $border_radius; + + code { + font-family: var(--wp--preset--font-family--monospace); + background-color: unset; + } + } + + .line-numbers-rows > span::before { + text-align: left; + padding-inline-start: var(--wp--preset--spacing--10); + } +} + +.code-tabs { + > button { + margin-inline-end: var(--wp--preset--spacing--10); + } + + .code-tab-block { + &:not(.is-active) { + display: none; + } + } +} + +// User notes styles. +#wp-link-wrap { + height: 320px !important; + margin-top: -160px !important; + + #link-selector { + position: unset !important; + padding: var(--wp--preset--spacing--10) var(--wp--preset--spacing--30) 0 !important; + + #search-panel, + #wplink-link-existing-content, + #link-options .link-target { + display: none; + } + } + + #link-modal-title { + padding: var(--wp--preset--spacing--20) var(--wp--preset--spacing--30); + border-bottom: 1px solid var(--wp--preset--color--light-grey-1); + } + + #wp-link-close { + top: var(--wp--preset--spacing--20); + inset-inline-end: var(--wp--preset--spacing--20); + + &:hover, + &:focus { + color: var(--wp--custom--button--hover--color--background); + } + } + + #wp-link input[type="text"] { + border: 1px solid var(--wp--preset--color--light-grey-1); + } + + #wp-link .submitbox { + padding: var(--wp--preset--spacing--30); + border-top: unset; + } + + /* stylelint-disable-next-line max-line-length */ + // All these button styles match those from wporg-parent-2021 theme. See https://github.com/WordPress/wporg-parent-2021/blob/trunk/source/wp-content/themes/wporg-parent-2021/sass/blocks/_button-mixins.scss + #wp-link-submit, + #wp-link-cancel button { + --wp--custom--button--spacing--padding--top: var(--wp--custom--button--small--spacing--padding--top); + --wp--custom--button--spacing--padding--bottom: var(--wp--custom--button--small--spacing--padding--bottom); + --wp--custom--button--spacing--padding--left: var(--wp--custom--button--small--spacing--padding--left); + --wp--custom--button--spacing--padding--right: var(--wp--custom--button--small--spacing--padding--right); + + min-height: unset; + font-size: var(--wp--custom--button--small--typography--font-size); + border-radius: var(--wp--custom--button--border--radius); + line-height: var(--wp--custom--button--typography--line-height); + // Standard button does not have a border, so the padding needs to include border size. + // This ensures the outline and filled button are the same height. + border-width: 0; + padding-top: calc(var(--wp--custom--button--spacing--padding--top) + var(--wp--custom--button--border--width)); + /* stylelint-disable-next-line max-line-length */ + padding-bottom: calc(var(--wp--custom--button--spacing--padding--bottom) + var(--wp--custom--button--border--width)); + /* stylelint-disable-next-line max-line-length */ + padding-inline-start: calc(var(--wp--custom--button--spacing--padding--left) + var(--wp--custom--button--border--width)); + /* stylelint-disable-next-line max-line-length */ + padding-inline-end: calc(var(--wp--custom--button--spacing--padding--right) + var(--wp--custom--button--border--width)); + } + + #wp-link-close, + #wp-link-submit, + #wp-link-cancel button { + &:focus { + box-shadow: inset 0 0 0 3px var(--wp--preset--color--white); + outline: 1.5px solid var(--wp--custom--button--outline--focus--border--color); + outline-offset: -1.5px; + border: transparent; + } + } + + #wp-link-submit { + background-color: var(--wp--custom--button--color--background); + + &:hover { + background-color: var(--wp--custom--button--hover--color--background); + } + + &:active { + background-color: var(--wp--custom--button--active--color--background); + } + } + + #wp-link-cancel button { + background-color: var(--wp--custom--button--outline--color--background); + border: var(--wp--custom--button--border--width) solid var(--wp--custom--button--outline--border--color); + color: var(--wp--custom--button--outline--color--text); + + // Padding does not account for border width. + padding-top: var(--wp--custom--button--spacing--padding--top); + padding-bottom: var(--wp--custom--button--spacing--padding--bottom); + padding-inline-start: var(--wp--custom--button--spacing--padding--left); + padding-inline-end: var(--wp--custom--button--spacing--padding--right); + + &:hover { + color: var(--wp--custom--button--outline--hover--color--text); + border-color: var(--wp--custom--button--outline--hover--border--color); + background-color: var(--wp--custom--button--outline--hover--color--background); + } + + &:focus { + color: var(--wp--custom--button--outline--focus--color--text); + background-color: var(--wp--custom--button--outline--focus--color--background); + } + + &:active { + color: var(--wp--custom--button--outline--active--color--text); + border-color: var(--wp--custom--button--outline--active--border--color); + background-color: var(--wp--custom--button--outline--active--color--background); + } + } +} + +// Theme handbook changelog +.wp-block-group.changelog.has-background { + padding: var(--wp--preset--spacing--20); +} + +// Matches notice block styles +// https://github.com/WordPress/wporg-mu-plugins/blob/trunk/mu-plugins/blocks/notice/postcss/style.pcss +.callout { + --wp--custom--wporg-callout--color--background: var(--wp--preset--color--acid-green-3); + --wp--custom--wporg-callout--color--text: var(--wp--preset--color--charcoal-1); + + padding: 1.25em 1.25em 1.25em 3.25em; + color: var(--wp--custom--wporg-callout--color--text); + background-color: var(--wp--custom--wporg-callout--color--background); + border-width: 0; + border-radius: 2px; + font-size: var(--wp--preset--font-size--small); + + &::before { + content: ""; + height: 24px; + width: 24px; + top: 0.65em; + left: 0.55em; + background-image: url(../../images/tip.svg); + background-repeat: no-repeat; + } + + &.callout-info { + --wp--custom--wporg-callout--color--background: var(--wp--preset--color--blueberry-4); + + &::before { + background-image: url(../../images/info.svg); + } + } + + &.callout-alert { + --wp--custom--wporg-callout--color--background: var(--wp--preset--color--lemon-3); + + &::before { + background-image: url(../../images/alert.svg); + } + } + + &.callout-warning { + --wp--custom--wporg-callout--color--background: var(--wp--preset--color--pomegrade-3); + + &::before { + background-image: url(../../images/warning.svg); + } + } +} + +.embed-youtube { + position: relative; + max-width: 100%; + // Maintain aspect ratio. + padding-top: 56.25%; + + .youtube-player { + position: absolute; + height: 100%; + width: 100%; + top: 0; + left: 0; + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/style.css b/source/wp-content/themes/wpr-developer-2024/style.css new file mode 100644 index 000000000..f726312c1 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/style.css @@ -0,0 +1,20 @@ +/* + * Theme Name: WP Rocket Developer, 2024 + * Theme URI: https://github.com/wp-media/developer-wp-rocket + * Author: WP Media + * Author URI: https://wp-media.me + * Description: A block-based child theme for developer.wp-rocket.me + * Version: 1.0.0 + * License: GNU General Public License v2 or later + * Text Domain: wporg + * Template: wporg-parent-2021 + */ + +/* + * Note: only add styles here in cases where you can't achieve the style with + * templates or theme.json settings. + */ + +body { + background-color: orange; +} diff --git a/source/wp-content/themes/wpr-developer-2024/stylesheets/admin.css b/source/wp-content/themes/wpr-developer-2024/stylesheets/admin.css new file mode 100644 index 000000000..414c2a5a1 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/stylesheets/admin.css @@ -0,0 +1,123 @@ +/* =Admin CSS +----------------------------------------------- */ +#wporg_parsed_ticket { + width: 100px; +} + +#ticket_info_icon { + font-size: 14px; + color: #a00; +} + +.attachment_controls .spinner { + position: relative; + margin-top: 3px; + bottom: 4px; + float: none; +} + +.attachment_controls { + margin-bottom: 10px; + display: block; +} + +/* Explanations */ +.fixed .column-has_explanation { + width: 2em; +} + +.fixed .column-has_explanation .dashicons { + width: 30px; +} + +.fixed tbody .column-has_explanation a { + color: #72777c; + display: inline-block; + margin-top: 4px; +} + +.fixed tbody .column-has_explanation a:focus, .fixed tbody .column-has_explanation a:hover { + color: #0073aa; +} + +.fixed tbody .column-has_explanation a .dashicons { + font-size: 26px; +} + +@media screen and (max-width: 782px) { + .fixed tbody .column-has_explanation a .screen-reader-text { + position: static; + -webkit-clip-path: none; + clip-path: none; + width: auto; + height: auto; + margin: 0; + } +} + +@media screen and (max-width: 782px) { + .fixed tbody .column-has_explanation a [aria-hidden="true"] { + display: none; + } +} + +.post-type-wporg_explanations .page-title-action { + display: none; +} + +.expl-row-actions { + display: block; +} + +.expl-row-actions a { + display: inline-block; +} + +.expl-row-actions a:first-child { + padding-right: 6px; + padding-left: 0; +} + +.expl-row-actions a:nth-child(n+2) { + padding-left: 8px; + border-left: 1px solid #ccc; +} + +.status { + font-weight: 700; +} + +.status.pending { + color: #f00; +} + +.status.publish { + color: #008000; +} + +.post-type-wp-parser-function .form-table th, +.post-type-wp-parser-class .form-table th, +.post-type-wp-parser-method .form-table th, +.post-type-wp-parser-hook .form-table th { + padding-top: 10px; + padding-bottom: 10px; +} + +.post-type-wp-parser-function .form-table td, +.post-type-wp-parser-class .form-table td, +.post-type-wp-parser-method .form-table td, +.post-type-wp-parser-hook .form-table td { + padding: 10px; +} + +.post-type-wp-parser-function .form-table td p, +.post-type-wp-parser-class .form-table td p, +.post-type-wp-parser-method .form-table td p, +.post-type-wp-parser-hook .form-table td p { + margin-top: 0; +} + +/* Parsed Content Meta Box */ +.wporg_parsed_content { + width: 100%; +} diff --git a/source/wp-content/themes/wpr-developer-2024/stylesheets/autocomplete.css b/source/wp-content/themes/wpr-developer-2024/stylesheets/autocomplete.css new file mode 100644 index 000000000..cd81e9169 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/stylesheets/autocomplete.css @@ -0,0 +1,65 @@ +/** + * Autocomplete Css + * + * Mainly overrides for awesomplete.css + */ + +div.awesomplete > ul > li:hover { + background: hsl(200, 40%, 90%); + color: #000; +} + +div.awesomplete > ul > li[aria-selected="true"] { + background: hsl(200, 40%, 80%); + color: #404040; +} + +div.awesomplete mark { + color: #404040; +} + +div.awesomplete li:hover mark, +div.awesomplete li[aria-selected="true"] mark { + background: hsl(65, 100%, 49%); +} + +.search-wrap div.awesomplete > ul { + top: 40px; + text-align: left; + max-height: 13.5em; + color: #404040; + overflow: auto; + background: linear-gradient(to bottom right, #fff, hsla(0, 0%, 100%, 0.9)); +} + +.search-wrap-inline div.awesomplete > ul { + right: 0; +} + +/* Matches submenu breakpoint */ +@media screen and ( min-width: 501px ) { + .search-wrap-inline div.awesomplete > ul { + left: auto; + } +} + +.search-wrap .searchform label div.awesomplete > input, +.search-wrap div.awesomplete { + width: 100%; +} + +.search-wrap .searchform, +.search-wrap .searchform div:first-child, +.search-wrap div.awesomplete { + /* Needs to be visible for the awesomplete popup */ + overflow: visible; +} + +.search-wrap div.awesomplete { + /* This is for the input to expand */ + display: flex; +} + +div.awesomplete > ul::before { + content: none; +} diff --git a/source/wp-content/themes/wpr-developer-2024/stylesheets/awesomplete.css b/source/wp-content/themes/wpr-developer-2024/stylesheets/awesomplete.css new file mode 100644 index 000000000..acd103428 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/stylesheets/awesomplete.css @@ -0,0 +1,98 @@ +[hidden] { display: none; } + +.visually-hidden { + position: absolute; + clip: rect(0, 0, 0, 0); +} + +div.awesomplete { + display: inline-block; + position: relative; + flex: 1; +} + +div.awesomplete > .wp-block-search__input { + width: 100%; +} + +div.awesomplete > ul { + position: absolute; + left: 0; + z-index: 1000; + min-width: 100%; + box-sizing: border-box; + list-style: none; + padding: 0; + border-radius: .3em; + margin: .2em 0 0; + background: hsla(0,0%,100%,.9); + background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8)); + border: 1px solid rgba(0,0,0,.3); + box-shadow: .05em .2em .6em rgba(0,0,0,.2); + text-shadow: none; +} + +div.awesomplete > ul[hidden], +div.awesomplete > ul:empty { + display: none; +} + +@supports (transform: scale(0)) { + div.awesomplete > ul { + transition: .3s cubic-bezier(.4,.2,.5,1.4); + transform-origin: 1.43em -.43em; + } + + div.awesomplete > ul[hidden], + div.awesomplete > ul:empty { + opacity: 0; + transform: scale(0); + display: block; + transition-timing-function: ease; + } +} + + /* Pointer */ + div.awesomplete > ul:before { + content: ""; + position: absolute; + top: -.43em; + left: 1em; + width: 0; height: 0; + padding: .4em; + background: white; + border: inherit; + border-right: 0; + border-bottom: 0; + -webkit-transform: rotate(45deg); + transform: rotate(45deg); + } + + div.awesomplete > ul > li { + position: relative; + padding: .2em .5em; + cursor: pointer; + } + + div.awesomplete > ul > li:hover { + background: hsl(200, 40%, 80%); + color: black; + } + + div.awesomplete > ul > li[aria-selected="true"] { + background: hsl(205, 40%, 40%); + color: white; + } + + div.awesomplete mark { + background: hsl(65, 100%, 50%); + } + + div.awesomplete li:hover mark { + background: hsl(68, 100%, 41%); + } + + div.awesomplete li[aria-selected="true"] mark { + background: hsl(86, 100%, 21%); + color: inherit; + } \ No newline at end of file diff --git a/source/wp-content/themes/wpr-developer-2024/stylesheets/layouts/content-sidebar.css b/source/wp-content/themes/wpr-developer-2024/stylesheets/layouts/content-sidebar.css new file mode 100644 index 000000000..4beed5ccc --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/stylesheets/layouts/content-sidebar.css @@ -0,0 +1,22 @@ +/* +Theme Name: wporg-developer +Layout: Content-Sidebar +*/ + +.content-area { + float: left; + margin: 0 -25% 0 0; + width: 100%; +} +.site-main { + margin: 0 25% 0 0; +} +.site-content .widget-area { + float: right; + overflow: hidden; + width: 25%; +} +.site-footer { + clear: both; + width: 100%; +} \ No newline at end of file diff --git a/source/wp-content/themes/wpr-developer-2024/stylesheets/layouts/sidebar-content.css b/source/wp-content/themes/wpr-developer-2024/stylesheets/layouts/sidebar-content.css new file mode 100644 index 000000000..d692a4b0d --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/stylesheets/layouts/sidebar-content.css @@ -0,0 +1,18 @@ +/* +Theme Name: wporg-developer +Layout: Sidebar-Content +*/ + +.content-area { + float: right; + margin: 0 0 0 -25%; + width: 100%; +} +.site-main { + margin: 0 0 0 25%; +} +.site-content .widget-area { + float: left; + overflow: hidden; + width: 25%; +} diff --git a/source/wp-content/themes/wpr-developer-2024/stylesheets/page-dashicons.css b/source/wp-content/themes/wpr-developer-2024/stylesheets/page-dashicons.css new file mode 100644 index 000000000..7f1286c50 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/stylesheets/page-dashicons.css @@ -0,0 +1,254 @@ +/** + * Dashicons Page + * + * CSS for Dashicons page + */ + +.dashicons-page { + padding: 0 !important; +} + +.dashicons-page * { + box-sizing: border-box; +} + +.dashicons-page main { + margin-top: 32px; +} + +.dashicons-page .details { + position: relative; + padding-bottom: 24px; +} + +.dashicons-page .wp-block-wporg-notice a { + text-decoration: underline; +} + +.dashicons-page .icon-filter { + padding: 0 2.5em; + width: 50%; +} + +.dashicons-page .icon-filter #search { + width: 100%; + border: 1px solid #ccc; + border-radius: 2px; + font: 11pt sans-serif; + padding: 10px; + background: rgba(255, 255, 255, 0.8); +} + +.dashicons-page .icon-filter #search:focus { + background: #fff; + outline: none; +} + +.dashicons-page .entry-content { + float: left; + margin-top: 0 !important; + padding: 1.5em 25px; + width: 50%; +} + +.dashicons-page .site-main { + padding-bottom: 40px; +} + +.dashicons-page #glyph { + float: left; + padding: 1.5em 25px; + width: 50%; +} + +.dashicons-page #glyph .dashicons { + margin-top: 24px; + font-size: 240px; + width: 240px; + height: 240px; + overflow: visible; +} + +.dashicons-page #glyph .info { + float: right; + margin-top: 25px; + width: 35%; +} + +.dashicons-page #glyph .info .charCode code { + color: #888; +} + +.dashicons-page pre { + background: #eee; + padding: 1.6em; + overflow: auto; +} + +.dashicons-page #glyph span { + display: block; + margin-bottom: 1em; +} + +.dashicons-page #glyph span a { + text-decoration: underline; +} + +.dashicons-page #glyph .name { + font-size: 0.9em; +} + +.dashicons-page h4 { + margin: 1.5em 0; + padding-top: 25px; + border-top: 1px solid #d3d3d3; +} + +.dashicons-page h4:first-of-type { + margin-top: 0; + padding-top: 0; + border-top: none; +} + +.searching .dashicons-page h4 { + display: none; +} + +.dashicons-page #iconlist { + margin-top: 1.5em; +} + +.dashicons-page #iconlist h4 { + padding-left: 10px; +} + +.dashicons-page #iconlist h4 a { + text-decoration: none; +} + +.dashicons-page #iconlist ul { + margin: 0; + padding: 0; +} + +.dashicons-page #iconlist .dashicons { + position: relative; + box-sizing: content-box; + padding: 10px; + margin-bottom: 20px; + width: 96px; + height: 96px; + white-space: nowrap; + font-size: 60px; + line-height: 1; + cursor: pointer; +} + +.dashicons-page #iconlist .dashicons:hover { + color: #d54e21; +} + +.dashicons-page #iconlist .dashicons span { + display: block; + font-size: 14px; + color: #888; + margin-top: 10px; + text-align: center; + word-wrap: break-word; + white-space: normal; +} + +.searching .dashicons-page #iconlist .dashicons { + float: left; +} + +.searching .dashicons-page #iconlist { + overflow: auto; +} + +.dashicons-page #instructions { + margin-top: 1.5em; + border-top: 1px solid #d3d3d3; +} + +.dashicons-page #instructions h3 { + margin: 1.5em 0 0.75em; +} + +.dashicons-page #instructions h4 { + margin-top: 0; + padding-top: 0; + border-top: none; +} + +.dashicons-page .code { + display: block; + font: 14px/1.5 monospace; + max-width: 600px; + white-space: pre; + border: 1px solid #ccc; + padding: 10px; + overflow: auto; + min-height: 180px; +} + +@media (max-width: 900px) { + .dashicons-page .icon-filter { + position: relative; + left: auto; + bottom: auto; + padding-left: 25px; + padding-right: 25px; + padding-bottom: 25px; + width: 100%; + } + + .dashicons-page .entry-content { + float: none; + padding-bottom: 0; + width: 100%; + } + + .dashicons-page #glyph { + float: none; + width: 100%; + text-align: center; + } + + .dashicons-page #glyph .dashicons { + font-size: 240px; + width: 240px; + height: 240px; + overflow: visible; + } + + .dashicons-page #glyph .info { + float: none; + margin-top: 0; + width: 100%; + } + + .dashicons-page #glyph span.link { + display: inline-block; + margin-right: 10px; + margin-bottom: 10px; + margin-left: 10px; + } + + .dashicons-page #glyph span.link:nth-of-type(2) { + margin-left: 0; + } + + .dashicons-page #glyph span.link:last-of-type { + margin-right: 0; + } +} + +@media (max-width: 400px) { + .dashicons-page #glyph, + .dashicons-page .entry-content, + .dashicons-page .icon-filter { + padding-left: 0; + padding-right: 0; + } +} diff --git a/source/wp-content/themes/wpr-developer-2024/templates/archive-command.html b/source/wp-content/themes/wpr-developer-2024/templates/archive-command.html new file mode 100644 index 000000000..82f5bd8cc --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/templates/archive-command.html @@ -0,0 +1,7 @@ +<!-- wp:template-part {"slug":"header","className":"has-display-contents"} /--> + +<!-- wp:template-part {"slug":"search-wide","className":"has-display-contents"} /--> + +<!-- wp:pattern {"slug":"wporg-developer-2023/cli-commands"} /--> + +<!-- wp:template-part {"slug":"footer"} /--> diff --git a/source/wp-content/themes/wpr-developer-2024/templates/archive.html b/source/wp-content/themes/wpr-developer-2024/templates/archive.html new file mode 100644 index 000000000..c6c68661a --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/templates/archive.html @@ -0,0 +1,45 @@ +<!-- wp:template-part {"slug":"header-alt","className":"has-display-contents"} /--> + +<!-- wp:template-part {"slug":"search","className":"has-display-contents"} /--> + +<!-- wp:group {"tagName":"main","layout":{"type":"constrained","justifyContent":"left"},"style":{"spacing":{"padding":{"left":"var:preset|spacing|edge-space","right":"var:preset|spacing|edge-space"}}}} --> +<main class="wp-block-group alignfull" style="padding-left:var(--wp--preset--spacing--edge-space);padding-right:var(--wp--preset--spacing--edge-space)"> + + <!-- wp:query {"queryId":0,"query":{"inherit":true,"perPage":10},"align":"wide"} --> + <div class="wp-block-query alignwide"> + + <!-- wp:group {"style":{"spacing":{"margin":{"bottom":"var:preset|spacing|40","top":"var:preset|spacing|20"}}},"align":"wide"} --> + <div class="wp-block-group alignwide" style="margin-bottom:var(--wp--preset--spacing--40);margin-top:var(--wp--preset--spacing--20)"> + <!-- wp:query-title {"type":"archive"} /--> + </div> + <!-- /wp:group --> + + <!-- wp:group {"className":"align-left","layout":{"type":"constrained","contentSize":"","justifyContent":"left"},"style":{"spacing":{"margin":{"bottom":"var:preset|spacing|40"}}}} --> + <div class="wp-block-group align-left" style="margin-bottom:var(--wp--preset--spacing--40)"> + + <!-- wp:post-template --> + + <!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|10","bottom":"var:preset|spacing|10"}}}} --> + <div class="wp-block-group" style="padding-top:var(--wp--preset--spacing--10);padding-bottom:var(--wp--preset--spacing--10)"> + <!-- wp:wporg/code-short-title /--> + <!-- wp:post-excerpt /--> + <!-- wp:wporg/code-type-usage-info {"style":{"elements":{"link":{"color":{"text":"var:preset|color|charcoal-4"}}}},"textColor":"charcoal-4","fontSize":"small"} /--></div> + <!-- /wp:group --> + + <!-- /wp:post-template --> + <!-- wp:query-no-results --> + <!-- wp:pattern {"slug":"wporg-developer-2023/no-search-results"} /--> + <!-- /wp:query-no-results --></div> + <!-- /wp:group --> + + <!-- wp:query-pagination {"layout":{"type":"flex","justifyContent":"center"}} --> + <!-- wp:query-pagination-previous /--> + + <!-- wp:query-pagination-numbers /--> + + <!-- wp:query-pagination-next /--> + <!-- /wp:query-pagination --></div> + <!-- /wp:query --></main> +<!-- /wp:group --> + +<!-- wp:template-part {"slug":"footer"} /--> diff --git a/source/wp-content/themes/wpr-developer-2024/templates/comments-edit.html b/source/wp-content/themes/wpr-developer-2024/templates/comments-edit.html new file mode 100644 index 000000000..3791a619b --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/templates/comments-edit.html @@ -0,0 +1,11 @@ +<!-- wp:template-part {"slug":"header","className":"has-display-contents"} /--> + +<!-- wp:group {"tagName":"main","style":{"spacing":{"blockGap":"0px","padding":{"top":"var:preset|spacing|50","bottom":"var:preset|spacing|50","left":"var:preset|spacing|edge-space","right":"var:preset|spacing|edge-space"}}},"className":"entry-content","layout":{"type":"constrained"}} --> +<main class="wp-block-group entry-content" style="padding-top:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50);padding-left:var(--wp--preset--spacing--edge-space);padding-right:var(--wp--preset--spacing--edge-space)"> + + <!-- wp:wporg/code-reference-comment-edit /--> + +</main> +<!-- /wp:group --> + +<!-- wp:template-part {"slug":"footer"} /--> \ No newline at end of file diff --git a/source/wp-content/themes/wpr-developer-2024/templates/front-page.html b/source/wp-content/themes/wpr-developer-2024/templates/front-page.html new file mode 100644 index 000000000..f512a7aaf --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/templates/front-page.html @@ -0,0 +1,7 @@ +<!-- wp:wporg/global-header {"style":{"border":{"bottom":{"color":"var:preset|color|white-opacity-15","style":"solid","width":"1px"}}}} /--> + +<!-- wp:pattern {"slug":"wporg-developer-2023/front-page-header"} /--> + +<!-- wp:pattern {"slug":"wporg-developer-2023/front-page-content"} /--> + +<!-- wp:template-part {"slug":"footer"} /--> diff --git a/source/wp-content/themes/wpr-developer-2024/templates/index.html b/source/wp-content/themes/wpr-developer-2024/templates/index.html new file mode 100644 index 000000000..53d13747b --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/templates/index.html @@ -0,0 +1,3 @@ +<!-- wp:template-part {"slug":"header","className":"has-display-contents"} /--> +index +<!-- wp:template-part {"slug":"footer"} /--> diff --git a/source/wp-content/themes/wpr-developer-2024/templates/page-dashicons.html b/source/wp-content/themes/wpr-developer-2024/templates/page-dashicons.html new file mode 100644 index 000000000..b10c28755 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/templates/page-dashicons.html @@ -0,0 +1,13 @@ +<!-- wp:template-part {"slug":"header-alt","className":"has-display-contents"} /--> + +<!-- wp:group {"style":{"spacing":{"padding":{"left":"var:preset|spacing|edge-space","right":"var:preset|spacing|edge-space"}}},"layout":{"type":"constrained"}} --> +<div class="wp-block-group" style="padding-right:var(--wp--preset--spacing--edge-space);padding-left:var(--wp--preset--spacing--edge-space)"><!-- wp:group {"align":"wide","layout":{"type":"default"}} --> + <div class="wp-block-group alignwide"> + <!-- wp:shortcode --> + [dashicons_page] + <!-- /wp:shortcode --> + </div> +<!-- /wp:group --></div> +<!-- /wp:group --> + +<!-- wp:template-part {"slug":"footer"} /--> diff --git a/source/wp-content/themes/wpr-developer-2024/templates/page-reference.html b/source/wp-content/themes/wpr-developer-2024/templates/page-reference.html new file mode 100644 index 000000000..783c6a4e0 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/templates/page-reference.html @@ -0,0 +1,7 @@ +<!-- wp:template-part {"slug":"header","className":"has-display-contents"} /--> + +<!-- wp:template-part {"slug":"search","className":"has-display-contents"} /--> +zzz1 +<!-- wp:pattern {"slug":"wporg-developer-2023/reference-content"} /--> + +<!-- wp:template-part {"slug":"footer"} /--> diff --git a/source/wp-content/themes/wpr-developer-2024/templates/search.html b/source/wp-content/themes/wpr-developer-2024/templates/search.html new file mode 100644 index 000000000..06dd6d730 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/templates/search.html @@ -0,0 +1,8 @@ +<!-- wp:template-part {"slug":"header-alt","className":"has-display-contents"} /--> + +<!-- wp:group {"tagName":"main","layout":{"type":"constrained","justifyContent":"left"},"style":{"spacing":{"padding":{"left":"var:preset|spacing|edge-space","right":"var:preset|spacing|edge-space"}}}} --> +<main class="wp-block-group alignfull" style="padding-left:var(--wp--preset--spacing--edge-space);padding-right:var(--wp--preset--spacing--edge-space)"> + <!-- wp:pattern {"slug":"wporg-developer-2023/search-content"} /--> +</main> +<!-- /wp:group --> +<!-- wp:template-part {"slug":"footer"} /--> diff --git a/source/wp-content/themes/wpr-developer-2024/templates/single-command.html b/source/wp-content/themes/wpr-developer-2024/templates/single-command.html new file mode 100644 index 000000000..0a907c39d --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/templates/single-command.html @@ -0,0 +1,41 @@ +<!-- wp:template-part {"slug":"header","className":"has-display-contents"} /--> + +<!-- wp:template-part {"slug":"search-wide","className":"has-display-contents"} /--> + +<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"right":"var:preset|spacing|edge-space","left":"var:preset|spacing|edge-space"}}},"layout":{"type":"constrained"}} --> +<div class="wp-block-group alignfull" style="padding-right:var(--wp--preset--spacing--edge-space);padding-left:var(--wp--preset--spacing--edge-space)"> + + <!-- wp:group {"align":"left","className":"has-three-columns","layout":{"type":"flex","flexWrap":"wrap","orientation":"vertical"}} --> + <div class="wp-block-group alignleft has-three-columns"> + + <!-- wp:group {"tagName":"main","className":"alignwide"} --> + <main class="wp-block-group alignwide"> + + <!-- wp:group {"tagName":"article"} --> + <article class="wp-block-group"> + + <!-- wp:wporg/command-title {"style":{"spacing":{"margin":{"bottom":"40px"}}}} /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/article-sidebar"} /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/single-content"} /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/command-meta"} /--> + + </article> + <!-- /wp:group --> + + </main> + <!-- /wp:group --> + + </div> + <!-- /wp:group --> + + <!-- wp:spacer {"height":"40px"} --> + <div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div> + <!-- /wp:spacer --> + +</div> +<!-- /wp:group --> + +<!-- wp:template-part {"slug":"footer"} /--> diff --git a/source/wp-content/themes/wpr-developer-2024/templates/single-handbook-block-editor.html b/source/wp-content/themes/wpr-developer-2024/templates/single-handbook-block-editor.html new file mode 100644 index 000000000..282e67628 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/templates/single-handbook-block-editor.html @@ -0,0 +1,44 @@ +<!-- wp:template-part {"slug":"header-alt","className":"has-display-contents"} /--> + +<!-- wp:template-part {"slug":"search-wide","className":"has-display-contents"} /--> + +<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"right":"var:preset|spacing|edge-space","left":"var:preset|spacing|edge-space"}}},"layout":{"type":"constrained"}} --> +<div class="wp-block-group alignfull" style="padding-right:var(--wp--preset--spacing--edge-space);padding-left:var(--wp--preset--spacing--edge-space)"> + + <!-- wp:group {"align":"left","className":"has-three-columns","layout":{"type":"flex","flexWrap":"wrap","orientation":"vertical"}} --> + <div class="wp-block-group alignleft has-three-columns"> + + <!-- wp:group {"tagName":"main","className":"alignwide"} --> + <main class="wp-block-group alignwide"> + + <!-- wp:group {"tagName":"article","style":{"spacing":{"margin":{"top":"0px"}}}} --> + <article class="wp-block-group" style="margin-top:0px"> + <!-- wp:post-title {"level":1,"style":{"spacing":{"margin":{"bottom":"40px"}}}} /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/article-sidebar"} /--> + + <!-- wp:post-content /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/article-meta-block-editor"} /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/handbook-pagination"} /--> + + </article> + <!-- /wp:group --> + + </main> + <!-- /wp:group --> + + <!-- wp:wporg/sidebar-container {"hasBackToTop":false,"inlineBreakpoint": "768px"} --> + + <!-- wp:wporg/chapter-list /--> + + <!-- /wp:wporg/sidebar-container --> + + </div> + <!-- /wp:group --> + +</div> +<!-- /wp:group --> + +<!-- wp:template-part {"slug":"footer"} /--> diff --git a/source/wp-content/themes/wpr-developer-2024/templates/single-handbook-github.html b/source/wp-content/themes/wpr-developer-2024/templates/single-handbook-github.html new file mode 100644 index 000000000..dbba8d2c3 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/templates/single-handbook-github.html @@ -0,0 +1,44 @@ +<!-- wp:template-part {"slug":"header-alt","className":"has-display-contents"} /--> + +<!-- wp:template-part {"slug":"search-wide","className":"has-display-contents"} /--> + +<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"right":"var:preset|spacing|edge-space","left":"var:preset|spacing|edge-space"}}},"layout":{"type":"constrained"}} --> +<div class="wp-block-group alignfull" style="padding-right:var(--wp--preset--spacing--edge-space);padding-left:var(--wp--preset--spacing--edge-space)"> + + <!-- wp:group {"align":"left","className":"has-three-columns","layout":{"type":"flex","flexWrap":"wrap","orientation":"vertical"}} --> + <div class="wp-block-group alignleft has-three-columns"> + + <!-- wp:group {"tagName":"main","className":"alignwide"} --> + <main class="wp-block-group alignwide"> + + <!-- wp:group {"tagName":"article","style":{"spacing":{"margin":{"top":"0px"}}}} --> + <article class="wp-block-group" style="margin-top:0px"> + <!-- wp:post-title {"level":1,"style":{"spacing":{"margin":{"bottom":"40px"}}}} /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/article-sidebar"} /--> + + <!-- wp:post-content /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/article-meta-github"} /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/handbook-pagination"} /--> + + </article> + <!-- /wp:group --> + + </main> + <!-- /wp:group --> + + <!-- wp:wporg/sidebar-container {"hasBackToTop":false,"inlineBreakpoint": "768px"} --> + + <!-- wp:wporg/chapter-list /--> + + <!-- /wp:wporg/sidebar-container --> + + </div> + <!-- /wp:group --> + +</div> +<!-- /wp:group --> + +<!-- wp:template-part {"slug":"footer"} /--> diff --git a/source/wp-content/themes/wpr-developer-2024/templates/single-handbook.html b/source/wp-content/themes/wpr-developer-2024/templates/single-handbook.html new file mode 100644 index 000000000..8dd43ed42 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/templates/single-handbook.html @@ -0,0 +1,44 @@ +<!-- wp:template-part {"slug":"header-alt","className":"has-display-contents"} /--> + +<!-- wp:template-part {"slug":"search-wide","className":"has-display-contents"} /--> + +<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"right":"var:preset|spacing|edge-space","left":"var:preset|spacing|edge-space"}}},"layout":{"type":"constrained"}} --> +<div class="wp-block-group alignfull" style="padding-right:var(--wp--preset--spacing--edge-space);padding-left:var(--wp--preset--spacing--edge-space)"> + + <!-- wp:group {"align":"left","className":"has-three-columns","layout":{"type":"flex","flexWrap":"wrap","orientation":"vertical"}} --> + <div class="wp-block-group alignleft has-three-columns"> + + <!-- wp:group {"tagName":"main","className":"alignwide"} --> + <main class="wp-block-group alignwide"> + + <!-- wp:group {"tagName":"article","style":{"spacing":{"margin":{"top":"0px"}}}} --> + <article class="wp-block-group" style="margin-top:0px"> + <!-- wp:post-title {"level":1,"style":{"spacing":{"margin":{"bottom":"40px"}}}} /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/article-sidebar"} /--> + + <!-- wp:post-content /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/article-meta"} /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/handbook-pagination"} /--> + + </article> + <!-- /wp:group --> + + </main> + <!-- /wp:group --> + + <!-- wp:wporg/sidebar-container {"hasBackToTop":false,"inlineBreakpoint": "768px"} --> + + <!-- wp:wporg/chapter-list /--> + + <!-- /wp:wporg/sidebar-container --> + + </div> + <!-- /wp:group --> + +</div> +<!-- /wp:group --> + +<!-- wp:template-part {"slug":"footer"} /--> diff --git a/source/wp-content/themes/wpr-developer-2024/templates/single.html b/source/wp-content/themes/wpr-developer-2024/templates/single.html new file mode 100644 index 000000000..66df928b4 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/templates/single.html @@ -0,0 +1,41 @@ +<!-- wp:template-part {"slug":"header","className":"has-display-contents"} /--> + +<!-- wp:template-part {"slug":"search-wide","className":"has-display-contents"} /--> + +<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"right":"var:preset|spacing|edge-space","left":"var:preset|spacing|edge-space"}}},"layout":{"type":"constrained"}} --> +<div class="wp-block-group alignfull" style="padding-right:var(--wp--preset--spacing--edge-space);padding-left:var(--wp--preset--spacing--edge-space)"> + + <!-- wp:group {"align":"left","className":"has-three-columns","layout":{"type":"flex","flexWrap":"wrap","orientation":"vertical"}} --> + <div class="wp-block-group alignleft has-three-columns"> + + <!-- wp:group {"tagName":"main","className":"alignwide"} --> + <main class="wp-block-group alignwide"> + + <!-- wp:group {"tagName":"article"} --> + <article class="wp-block-group"> + + <!-- wp:wporg/code-reference-title {"style":{"spacing":{"margin":{"bottom":"40px"}}}} /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/article-sidebar"} /--> + + <!-- wp:wporg/code-reference-deprecated /--> + + <!-- wp:wporg/code-reference-private-access /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/single-content"} /--> + + <!-- wp:wporg/code-reference-comment-form /--> + + </article> + <!-- /wp:group --> + + </main> + <!-- /wp:group --> + + </div> + <!-- /wp:group --> + +</div> +<!-- /wp:group --> + +<!-- wp:template-part {"slug":"footer"} /--> diff --git a/source/wp-content/themes/wpr-developer-2024/templates/taxonomy-wp-parser-since.html b/source/wp-content/themes/wpr-developer-2024/templates/taxonomy-wp-parser-since.html new file mode 100644 index 000000000..3634dc18f --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/templates/taxonomy-wp-parser-since.html @@ -0,0 +1,55 @@ +<!-- wp:template-part {"slug":"header","className":"has-display-contents"} /--> + +<!-- wp:template-part {"slug":"search","className":"has-display-contents"} /--> + +<!-- wp:group {"tagName":"main","layout":{"type":"constrained","justifyContent":"left"},"style":{"spacing":{"padding":{"left":"var:preset|spacing|edge-space","right":"var:preset|spacing|edge-space"}}}} --> +<main class="wp-block-group alignfull" style="padding-left:var(--wp--preset--spacing--edge-space);padding-right:var(--wp--preset--spacing--edge-space)"> + + <!-- wp:query {"queryId":0,"query":{"inherit":true,"perPage":10},"align":"wide"} --> + <div class="wp-block-query alignwide"> + + <!-- wp:query-title {"type":"archive","style":{"spacing":{"margin":{"bottom":"var:preset|spacing|40","top":"var:preset|spacing|20"}}}} /--> + + <!-- wp:pattern {"slug":"wporg-developer-2023/release-post-type-filters"} /--> + + <!-- wp:group {"className":"wporg-tax-wp-parser-since-container align-left","layout":{"type":"constrained","contentSize":"","justifyContent":"left"},"style":{"spacing":{"margin":{"bottom":"var:preset|spacing|40"}}}} --> + <div class="wporg-tax-wp-parser-since-container wp-block-group align-left" style="margin-bottom:var(--wp--preset--spacing--40)"> + + <!-- wp:post-template --> + + <!-- wp:group {"style":{"spacing":{"padding":{"top":"var:preset|spacing|10","bottom":"var:preset|spacing|10"}}}} --> + <div class="wp-block-group" style="padding-top:var(--wp--preset--spacing--10);padding-bottom:var(--wp--preset--spacing--10)"> + + <!-- wp:wporg/code-short-title /--> + <!-- wp:post-excerpt {"style":{"spacing":{"margin":{"top":"0"}}}} /--> + <!-- wp:wporg/code-type-usage-info {"style":{"elements":{"link":{"color":{"text":"var:preset|color|charcoal-4"}}}},"textColor":"charcoal-4","fontSize":"small"} /--> + + </div> + <!-- /wp:group --> + + <!-- /wp:post-template --> + + <!-- wp:query-no-results --> + + <!-- wp:pattern {"slug":"wporg-developer-2023/no-search-results"} /--> + + <!-- /wp:query-no-results --> + + </div> + <!-- /wp:group --> + + <!-- wp:query-pagination {"layout":{"type":"flex","justifyContent":"center"}} --> + + <!-- wp:query-pagination-previous /--> + <!-- wp:query-pagination-numbers /--> + <!-- wp:query-pagination-next /--> + + <!-- /wp:query-pagination --> + + </div> + <!-- /wp:query --> + +</main> +<!-- /wp:group --> + +<!-- wp:template-part {"slug":"footer"} /--> diff --git a/source/wp-content/themes/wpr-developer-2024/theme.json b/source/wp-content/themes/wpr-developer-2024/theme.json new file mode 100644 index 000000000..b52519608 --- /dev/null +++ b/source/wp-content/themes/wpr-developer-2024/theme.json @@ -0,0 +1,294 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 2, + "settings": { + "color": { + "palette": [ + { + "slug": "aquamarine", + "color": "#6ec9ae", + "name": "Aquamarine" + }, + { + "slug": "linen", + "color": "#fbfaf7", + "name": "Linen" + }, + { + "slug": "syntax-black", + "color": "var(--wp--preset--color--black)", + "name": "Syntax Black" + }, + { + "slug": "syntax-red", + "color": "var(--wp--custom--color--red-50)", + "name": "Syntax Red" + }, + { + "slug": "syntax-green", + "color": "var(--wp--custom--color--green-50)", + "name": "Syntax Green" + }, + { + "slug": "syntax-blue", + "color": "var(--wp--custom--color--blue-60)", + "name": "Syntax Blue" + }, + { + "slug": "syntax-grey", + "color": "var(--wp--custom--color--grey-50)", + "name": "Syntax Grey" + } + ] + }, + "custom": { + "color": { + "blue-60": "#135e96", + "blue-60-alpha-10": "#135e9619", + "green-50": "#008a20", + "green-50-alpha-5": "#008a200C", + "green-60": "#007017", + "grey-50": "#646970", + "red-50": "#d63638", + "red-50-alpha-5": "#d636380C", + "red-60": "#b32d2e", + "yellow-40": "#bd8600", + "yellow-40-alpha-5": "#bd86000C", + "yellow-50": "#996800" + }, + "heading": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--inter)", + "lineHeight": 1.2 + }, + "level-1": { + "typography": { + "lineHeight": 1.2 + }, + "breakpoint": { + "small-only": { + "typography": { + "fontSize": "26px", + "lineHeight": 1.2 + } + } + } + }, + "level-2": { + "typography": { + "lineHeight": 1.2 + }, + "breakpoint": { + "small-only": { + "typography": { + "fontSize": "24px", + "lineHeight": 1.2 + } + } + } + }, + "level-3": { + "typography": { + "lineHeight": 1.2 + }, + "breakpoint": { + "small-only": { + "typography": { + "fontSize": "22px", + "lineHeight": 1.2 + } + } + } + }, + "level-4": { + "typography": { + "lineHeight": 1.2 + }, + "breakpoint": { + "small-only": { + "typography": { + "fontSize": "20px", + "lineHeight": 1.2 + } + } + } + }, + "level-5": { + "typography": { + "lineHeight": 1.2 + }, + "breakpoint": { + "small-only": { + "typography": { + "fontSize": "18px", + "lineHeight": 1.2 + } + } + } + }, + "level-6": { + "typography": { + "lineHeight": 1.2 + }, + "breakpoint": { + "small-only": { + "typography": { + "fontSize": "16px", + "lineHeight": 1.2 + } + } + } + } + }, + "latest-news": { + "title": { + "fontSize": "var(--wp--preset--font-size--normal)" + } + }, + "select": { + "height": "40px", + "iconSize": "24px", + "iconPosition": "8px", + "paddingLeft": "12px", + "fontSize": "var(--wp--preset--font-size--small)", + "lineHeight": "var(--wp--custom--body--typography--line-height)", + "paddingVertical": "calc(( var(--wp--custom--select--height) - (var(--wp--custom--select--font-size) * var(--wp--custom--select--line-height))) / 2)", + "borderRadius": "var(--wp--custom--button--border--radius)" + }, + "wporg-command-github": { + "color": { + "open": "var(--wp--custom--color--green-60)", + "closed": "var(--wp--custom--color--red-60)" + } + }, + "wporg-code-short-title": { + "color": { + "class": "var(--wp--custom--color--yellow-40)", + "class-type": "var(--wp--custom--color--yellow-50)", + "function": "var(--wp--custom--color--green-50)", + "function-type": "var(--wp--custom--color--green-60)", + "hook": "var(--wp--custom--color--red-50)", + "hook-type": "var(--wp--custom--color--red-60)", + "method": "var(--wp--custom--color--blue-60)" + }, + "background-color": { + "class-type": "var(--wp--custom--color--yellow-40-alpha-5)", + "function-type": "var(--wp--custom--color--green-50-alpha-5)", + "hook-type": "var(--wp--custom--color--red-50-alpha-5)", + "method-type": "var(--wp--custom--color--blue-60-alpha-10)" + } + } + }, + "layout": { + "contentSize": "960px" + }, + "typography": { + "fontSizes": [ + { + "name": "Heading 6", + "size": "18px", + "slug": "heading-6" + }, + { + "name": "Heading 5", + "size": "20px", + "slug": "heading-5" + }, + { + "name": "Heading 4", + "size": "24px", + "slug": "heading-4" + }, + { + "name": "Heading 3", + "size": "29px", + "slug": "heading-3" + }, + { + "name": "Heading 2", + "size": "32px", + "slug": "heading-2" + }, + { + "name": "Heading 1", + "size": "38px", + "slug": "heading-1" + } + ] + } + }, + "styles": { + "blocks": { + "core/post-title": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--eb-garamond)", + "fontSize": "36px", + "lineHeight": "1.3" + } + }, + "core/query-title": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--eb-garamond)", + "fontSize": "36px", + "lineHeight": "1.3" + } + }, + "wporg/chapter-list": { + "typography": { + "fontSize": "var(--wp--preset--font-size--small)" + }, + "elements": { + "heading": { + "typography": { + "fontSize": "var(--wp--preset--font-size--normal) !important" + } + }, + "link": { + "color": { + "text": "var(--wp--preset--color--charcoal-4)" + } + } + } + }, + "wporg/code-reference-title": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--monospace)", + "fontSize": "var(--wp--preset--font-size--extra-large)", + "fontWeight": "400", + "lineHeight": "1.73" + } + } + }, + "elements": { + "h2": { + "typography": { + "fontSize": "var(--wp--preset--font-size--heading-4)", + "fontWeight": "600" + }, + "spacing": { + "margin": { + "top": "0", + "bottom": "var(--wp--style--block-gap)" + } + } + }, + "h3": { + "typography": { + "fontSize": "var(--wp--preset--font-size--heading-5)", + "fontWeight": "600" + } + }, + "h4": { + "typography": { + "fontSize": "var(--wp--preset--font-size--heading-6)", + "fontWeight": "600" + } + }, + "h5": { + "typography": { + "fontSize": "var(--wp--preset--font-size--heading-6)" + } + } + } + } +}