Skip to content

Commit ac084a3

Browse files
obenlandclaude
andcommitted
WordPress.tv: Use the WordPress.org global footer in wptv-27.
WordPress.com doesn't run wporg-mu-plugins, so the theme takes the footer from wordpress.org's global-header-footer/v1/footer endpoint, the way sites outside the WordPress.org network do. A PHP-only wptv/global-footer block keeps the response's footer element, without the wp_footer() output that follows it, and caches it for 12 hours. A failed fetch keeps the last copy and retries in 5 minutes. The footer's stylesheet comes from s.w.org, and wptv-27 already defines the presets it uses. The global footer is a <footer> element, so the footer template parts become divs. The theme's own footer pattern, its styles, its WordPress.org logo and the footer menu location it read go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent f7b35b9 commit ac084a3

19 files changed

Lines changed: 205 additions & 141 deletions

‎wordpress.tv/public_html/wp-content/themes/wptv-27/assets/images/wordpress-org.svg‎

Lines changed: 0 additions & 5 deletions
This file was deleted.

‎wordpress.tv/public_html/wp-content/themes/wptv-27/functions.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
require_once WPTV_THEME_DIR . '/inc/content.php';
3333
require_once WPTV_THEME_DIR . '/inc/publishing.php';
3434
require_once WPTV_THEME_DIR . '/inc/open-graph.php';
35+
require_once WPTV_THEME_DIR . '/inc/global-footer.php';
3536
require_once WPTV_THEME_DIR . '/inc/redirects.php';
3637
require_once WPTV_THEME_DIR . '/inc/maintenance.php';
3738
require_once WPTV_THEME_DIR . '/inc/endpoints/api.php';
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php
2+
/**
3+
* The WordPress.org global footer, from wordpress.org's footer endpoint.
4+
*
5+
* WordPress.com doesn't run wporg-mu-plugins, so the footer block itself isn't
6+
* available here; the endpoint serves its markup to sites outside the network.
7+
*
8+
* @package WordPressTV
9+
*/
10+
11+
declare( strict_types = 1 );
12+
13+
namespace WordPressTV\GlobalFooter;
14+
15+
if ( ! defined( 'ABSPATH' ) ) {
16+
exit;
17+
}
18+
19+
/**
20+
* Endpoint that renders the global footer.
21+
*/
22+
const ENDPOINT = 'https://wordpress.org/wp-json/global-header-footer/v1/footer';
23+
24+
/**
25+
* Stylesheet the global header and footer share.
26+
*/
27+
const STYLESHEET = 'https://s.w.org/wp-content/mu-plugins/pub-sync/blocks/global-header-footer/build/style.css';
28+
29+
/**
30+
* Option caching the footer markup and when to fetch it again.
31+
*/
32+
const OPTION = 'wptv_global_footer';
33+
34+
/**
35+
* Seconds a fetched footer is kept.
36+
*/
37+
const LIFETIME = 12 * HOUR_IN_SECONDS;
38+
39+
/**
40+
* Seconds until a failed fetch is retried.
41+
*/
42+
const RETRY = 5 * MINUTE_IN_SECONDS;
43+
44+
/**
45+
* Registers the block the footer template part renders.
46+
*
47+
* @return void
48+
*/
49+
function register_block(): void {
50+
register_block_type(
51+
'wptv/global-footer',
52+
array(
53+
'api_version' => 3,
54+
'title' => __( 'WordPress.org footer', 'wptv' ),
55+
'category' => 'theme',
56+
'supports' => array(
57+
'autoRegister' => true,
58+
'html' => false,
59+
'inserter' => false,
60+
),
61+
'render_callback' => __NAMESPACE__ . '\render',
62+
)
63+
);
64+
}
65+
add_action( 'init', __NAMESPACE__ . '\register_block' );
66+
67+
/**
68+
* Renders the global footer, with the styles it needs.
69+
*
70+
* @return string Footer markup, or an empty string when it was never fetched.
71+
*/
72+
function render(): string {
73+
$markup = get_markup();
74+
if ( '' === $markup ) {
75+
return '';
76+
}
77+
78+
wp_enqueue_style( 'wporg-global-header-footer', STYLESHEET, array(), gmdate( 'Ymd' ) );
79+
// Core only loads block styles for blocks it renders, and the footer's social links aren't rendered here.
80+
wp_enqueue_style( 'wp-block-social-links' );
81+
82+
return $markup;
83+
}
84+
85+
/**
86+
* Returns the cached footer markup, fetching it again once it expires.
87+
*
88+
* A failed fetch keeps the last footer and tries again a few minutes later.
89+
*
90+
* @return string Footer markup, or an empty string when it was never fetched.
91+
*/
92+
function get_markup(): string {
93+
$cached = get_option( OPTION );
94+
if ( is_array( $cached ) && $cached['expires'] > time() ) {
95+
return $cached['markup'];
96+
}
97+
98+
$fetched = fetch_markup();
99+
$markup = $fetched ?? ( $cached['markup'] ?? '' );
100+
update_option(
101+
OPTION,
102+
array(
103+
'markup' => $markup,
104+
'expires' => time() + ( null === $fetched ? RETRY : LIFETIME ),
105+
),
106+
false
107+
);
108+
109+
return $markup;
110+
}
111+
112+
/**
113+
* Fetches the footer from wordpress.org.
114+
*
115+
* @return string|null Footer markup, or null when the request fails.
116+
*/
117+
function fetch_markup(): ?string {
118+
$response = wp_safe_remote_get( ENDPOINT, array( 'timeout' => 3 ) );
119+
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
120+
return null;
121+
}
122+
123+
return extract_footer( wp_remote_retrieve_body( $response ) );
124+
}
125+
126+
/**
127+
* Takes the footer element from the endpoint's response.
128+
*
129+
* The response goes on with wordpress.org's `wp_footer()` scripts and closes the
130+
* document, so only the element itself is kept.
131+
*
132+
* @param string $body Response body.
133+
* @return string|null Footer element, or null when the body doesn't start with one.
134+
*/
135+
function extract_footer( string $body ): ?string {
136+
$body = ltrim( $body );
137+
$end = strpos( $body, '</footer>' );
138+
if ( ! str_starts_with( $body, '<footer ' ) || false === $end ) {
139+
return null;
140+
}
141+
142+
return substr( $body, 0, $end + strlen( '</footer>' ) );
143+
}

‎wordpress.tv/public_html/wp-content/themes/wptv-27/inc/setup.php‎

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@
1919
function setup() {
2020
add_editor_style( 'style.css' );
2121

22-
// Same location names as wptv2, so the assigned menus survive switching between both themes.
22+
// Same location name as wptv2, so the assigned menu survives switching between both themes.
2323
register_nav_menus(
2424
array(
2525
'primary' => __( 'Primary Menu', 'wptv' ),
26-
'footer' => __( 'Footer Menu', 'wptv' ),
2726
)
2827
);
2928
}
@@ -84,46 +83,6 @@ function primary_menu_links( $parsed_block ) {
8483
}
8584
add_filter( 'render_block_data', __NAMESPACE__ . '\primary_menu_links' );
8685

87-
/**
88-
* Fills the footer "Contribute" list with the classic menu assigned to `footer`.
89-
*
90-
* Only top-level items are shown, as wptv2's footer menu (depth 1). Links to
91-
* other sites get the decorative arrow the footer design uses. Without an
92-
* assigned menu, or with an empty one, the pattern's own links are kept.
93-
*
94-
* @param string $content Rendered List block.
95-
* @param array $block Parsed block.
96-
* @return string
97-
*/
98-
function footer_menu_links( $content, $block ) {
99-
$classes = preg_split( '/\s+/', $block['attrs']['className'] ?? '' );
100-
if ( ! in_array( 'wptv-footer__contribute', $classes, true ) || ! preg_match( '/^\s*(<ul\b[^>]*>)/', $content, $opening ) ) {
101-
return $content;
102-
}
103-
104-
$locations = get_nav_menu_locations();
105-
$menu = wp_get_nav_menu_object( $locations['footer'] ?? 0 );
106-
$items = $menu ? wp_get_nav_menu_items( $menu->term_id ) : array();
107-
if ( ! $items ) {
108-
return $content;
109-
}
110-
111-
$home_host = wp_parse_url( home_url(), PHP_URL_HOST );
112-
$links = '';
113-
foreach ( $items as $item ) {
114-
if ( (int) $item->menu_item_parent ) {
115-
continue;
116-
}
117-
$host = wp_parse_url( $item->url, PHP_URL_HOST );
118-
$arrow = ( $host && $host !== $home_host ) ? ' <span aria-hidden="true">↗</span>' : '';
119-
$target = $item->target ? ' target="' . esc_attr( $item->target ) . '" rel="noopener"' : '';
120-
$links .= '<li><a href="' . esc_url( $item->url ) . '"' . $target . '>' . esc_html( $item->title ) . $arrow . '</a></li>';
121-
}
122-
123-
return $links ? $opening[1] . $links . '</ul>' : $content;
124-
}
125-
add_filter( 'render_block_core/list', __NAMESPACE__ . '\footer_menu_links', 10, 2 );
126-
12786
/**
12887
* Registers the recurring visual treatments used by theme patterns.
12988
*/
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<!-- wp:pattern {"slug":"wptv/footer"} /-->
1+
<!-- wp:wptv/global-footer /-->

‎wordpress.tv/public_html/wp-content/themes/wptv-27/patterns/footer.php‎

Lines changed: 0 additions & 65 deletions
This file was deleted.

‎wordpress.tv/public_html/wp-content/themes/wptv-27/style.css‎

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,21 +70,6 @@ img, video, iframe { max-width: 100%; }
7070
.wptv-header .is-menu-open .wp-block-navigation__container { align-items: stretch; width: 100%; gap: 0; }
7171
.wptv-header .is-menu-open .wp-block-navigation-item { padding-block: 12px; border-bottom: 1px solid var(--wp--preset--color--light-grey-1); }
7272
.wptv-header .is-menu-open .wp-block-search { width: 100%; }
73-
.wptv-footer { padding-block: var(--wp--preset--spacing--50) var(--wp--preset--spacing--30); font-size: var(--wp--preset--font-size--small); }
74-
.wptv-footer a { color: var(--wp--preset--color--white); text-decoration: none; }
75-
.wptv-footer a:hover { text-decoration: underline; }
76-
.wptv-footer a:focus { outline-color: var(--wp--preset--color--blueberry-2); }
77-
.wptv-footer__heading { font: 600 var(--wp--preset--font-size--small)/1.625 var(--wp--preset--font-family--inter); color: var(--wp--preset--color--white); }
78-
.wptv-footer__columns { gap: 30px; }
79-
.wptv-footer__columns > .wp-block-column:first-child { flex-grow: 2; }
80-
.wptv-footer__links { list-style: none; padding: 0; }
81-
.wptv-footer__links li + li { margin-top: 8px; }
82-
.wptv-footer__license { border-top: 1px solid var(--wp--preset--color--white-opacity-15); margin-top: var(--wp--preset--spacing--40); padding-top: var(--wp--preset--spacing--20); gap: 20px; }
83-
.wptv-footer .wp-block-site-title { font-family: var(--wp--preset--font-family--inter); font-size: var(--wp--preset--font-size--large); }
84-
.wptv-footer__wporg { margin: 0; }
85-
.wptv-footer .wp-block-site-title + p,
86-
.wptv-footer .wptv-footer__wporg + p { margin-block-start: 0; }
87-
.wptv-footer__wporg img { display: block; width: auto; max-width: 100%; height: 27px; }
8873
/* === cards === */
8974
.is-style-cards-grid { gap: var(--wp--preset--spacing--20); }
9075
.is-style-cards-grid > :where(li, .wp-block-group) { position: relative; border: 1px solid var(--wp--preset--color--light-grey-1); border-radius: 2px; box-sizing: border-box; overflow-wrap: anywhere; padding: 20px; }
@@ -230,8 +215,6 @@ img, video, iframe { max-width: 100%; }
230215
html { scroll-padding-top: 20px; }
231216
}
232217
@media (max-width: 781px) {
233-
.wptv-footer .wptv-footer__columns { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
234-
.wptv-footer__columns > .wp-block-column:first-child { grid-column: 1 / -1; }
235218
.wptv-home__section-heading { align-items: start; }
236219
}
237220
@media (max-width: 599px) {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
<!-- wp:template-part {"slug":"header","tagName":"header","area":"header"} /-->
22
<!-- wp:pattern {"slug":"wptv/404"} /-->
3-
<!-- wp:template-part {"slug":"footer","tagName":"footer","area":"footer"} /-->
3+
<!-- wp:template-part {"slug":"footer","tagName":"div","area":"footer"} /-->

‎wordpress.tv/public_html/wp-content/themes/wptv-27/templates/archive.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
<!-- /wp:group -->
66
<!-- wp:pattern {"slug":"wptv/video-grid"} /--></main>
77
<!-- /wp:group -->
8-
<!-- wp:template-part {"slug":"footer","tagName":"footer","area":"footer"} /-->
8+
<!-- wp:template-part {"slug":"footer","tagName":"div","area":"footer"} /-->

‎wordpress.tv/public_html/wp-content/themes/wptv-27/templates/category-wordcamptv.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
<!-- wp:pattern {"slug":"wptv/video-grid"} /-->
99
<!-- wp:pattern {"slug":"wptv/wordcamps"} /--></main>
1010
<!-- /wp:group -->
11-
<!-- wp:template-part {"slug":"footer","tagName":"footer","area":"footer"} /-->
11+
<!-- wp:template-part {"slug":"footer","tagName":"div","area":"footer"} /-->

0 commit comments

Comments
 (0)