| 1 |
<?php |
| 2 |
/** |
| 3 |
* Renders the unified Jetpack admin header and footer chrome on Akismet's admin pages. |
| 4 |
* |
| 5 |
* Akismet (5.7+) exposes two action hooks in its admin views: |
| 6 |
* - `akismet_header`: when an action is registered, it REPLACES Akismet's default masthead. |
| 7 |
* - `akismet_footer`: rendered at the bottom of every admin view; empty by default. |
| 8 |
* |
| 9 |
* This integration consumes those hooks from the Jetpack plugin so that Akismet's pages |
| 10 |
* share the same branded header bar, standardized footer AND contained layout as the rest |
| 11 |
* of the unified Jetpack admin (the `@wordpress/admin-ui` page header + `JetpackFooter` look |
| 12 |
* used by My Jetpack, Protect, Social, etc.): a fixed header, an internally-scrolling middle |
| 13 |
* and a footer pinned to the bottom of the viewport. |
| 14 |
* |
| 15 |
* The markup mirrors the admin-ui page header and `.jetpack-footer` component, and the |
| 16 |
* styling is a small self-contained stylesheet (no external CSS, no JS, no build step) that |
| 17 |
* reproduces the measured computed styles of those components plus the |
| 18 |
* `jetpack-admin-page-layout` mixin, adapted to Akismet's markup |
| 19 |
* (`#wpbody-content > #akismet-plugin-container > header / .akismet-lower / footer`). This |
| 20 |
* keeps the integration resilient to the CSS-Module class hashing used by the real React |
| 21 |
* components. |
| 22 |
* |
| 23 |
* NOTE: This class does not modify the Akismet plugin in any way. It only registers |
| 24 |
* callbacks on Akismet's own action hooks. |
| 25 |
* |
| 26 |
* @package automattic/jetpack |
| 27 |
*/ |
| 28 |
|
| 29 |
use Automattic\Jetpack\Plugin\Admin_Chrome_Logo; |
| 30 |
use Automattic\Jetpack\Plugin\Footer_Links; |
| 31 |
use Automattic\Jetpack\Redirect; |
| 32 |
use Automattic\Jetpack\Status; |
| 33 |
use Automattic\Jetpack\Status\Host; |
| 34 |
|
| 35 |
if ( ! defined( 'ABSPATH' ) ) { |
| 36 |
exit( 0 ); |
| 37 |
} |
| 38 |
|
| 39 |
/** |
| 40 |
* Wires the unified Jetpack header, footer and contained layout onto Akismet's admin pages. |
| 41 |
*/ |
| 42 |
class Akismet_Admin_Chrome { |
| 43 |
|
| 44 |
/** |
| 45 |
* The Akismet logo mark — the green rounded square with the white "A", taken from |
| 46 |
* Akismet's own `akismet-refresh-logo.svg`. Sized via the `height` attribute by callers. |
| 47 |
* |
| 48 |
* @param int $height Pixel height of the logo. |
| 49 |
* @return string SVG markup. |
| 50 |
*/ |
| 51 |
private function akismet_logo( $height ) { |
| 52 |
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44" height="' . (int) $height . '" class="jp-akismet-mark" aria-hidden="true"><rect width="44" height="44" fill="#357B49" rx="6"/><path fill="#fff" fill-rule="evenodd" d="m29.746 28.31-6.392-16.797c-.152-.397-.305-.672-.789-.675-.673 0-1.408.611-1.746 1.316l-7.378 16.154c-.072.16-.143.311-.214.454-.5.995-1.045 1.546-2.357 1.626a.399.399 0 0 0-.16.033l-.01.004a.399.399 0 0 0-.23.392v.01c0 .054.01.106.03.155l.004.01a.416.416 0 0 0 .394.252h6.212a.417.417 0 0 0 .307-.12.416.416 0 0 0 .124-.305.398.398 0 0 0-.105-.302.399.399 0 0 0-.294-.127c-.757 0-2.197-.062-2.197-1.164.02-.318.103-.63.245-.916l1.399-3.152c.52-1.163 1.654-1.163 2.572-1.163h5.843c.023 0 .044 0 .062.003.13.014.16.081.214.242l1.534 4.07a2.857 2.857 0 0 1 .216 1.04c0 .054-.003.104-.01.153-.09.726-.831.887-1.49.887a.4.4 0 0 0-.294.127l-.007.008-.007.008a.401.401 0 0 0-.092.286v.01c0 .054.01.106.03.155l.005.01a.42.42 0 0 0 .395.252h7.011a.413.413 0 0 0 .279-.13.412.412 0 0 0 .11-.297.387.387 0 0 0-.09-.294.388.388 0 0 0-.277-.135c-1.448-.122-2.295-.643-2.847-2.08Zm-11.985-5.844 2.847-6.304c.361-.728.659-1.486.889-2.265 0-.06.03-.092.06-.092s.061.032.061.091c.02.122.045.247.073.374.197.888.584 1.878.914 2.723l.176.453 1.684 4.529a.927.927 0 0 1 .092.4.473.473 0 0 1-.009.094c-.041.202-.228.272-.602.272h-6.063c-.122 0-.184-.03-.184-.092a.36.36 0 0 1 .062-.183Zm17.107-.721c0 .786-.446 1.231-1.25 1.231-.806 0-1.125-.409-1.125-1.034 0-.786.465-1.231 1.25-1.231.785 0 1.125.427 1.125 1.034ZM9.629 23.002c.803 0 1.25-.447 1.25-1.231 0-.607-.343-1.036-1.128-1.036-.785 0-1.25.447-1.25 1.231 0 .625.325 1.036 1.128 1.036Z" clip-rule="evenodd"/></svg>'; |
| 53 |
} |
| 54 |
|
| 55 |
/** |
| 56 |
* Register the hooks that drive the chrome. |
| 57 |
* |
| 58 |
* Safe to call unconditionally: the header/footer callbacks are only ever fired by |
| 59 |
* Akismet's own admin views, and the inline stylesheet is printed alongside the header. |
| 60 |
* |
| 61 |
* Idempotent: this can be wired from more than one place depending on the platform — |
| 62 |
* `Jetpack_Admin` on Atomic/self-hosted, and `Akismet_Admin_WPCOM` on WordPress.com |
| 63 |
* Simple sites (the two run under different load orders). The static guard ensures the |
| 64 |
* `akismet_header` / `akismet_footer` callbacks are only ever registered once, so the |
| 65 |
* chrome can never render twice regardless of how many call sites fire. |
| 66 |
*/ |
| 67 |
public function init_hooks() { |
| 68 |
static $registered = false; |
| 69 |
if ( $registered ) { |
| 70 |
return; |
| 71 |
} |
| 72 |
$registered = true; |
| 73 |
|
| 74 |
add_action( 'akismet_header', array( $this, 'render_header' ) ); |
| 75 |
add_action( 'akismet_footer', array( $this, 'render_footer' ) ); |
| 76 |
} |
| 77 |
|
| 78 |
/** |
| 79 |
* The self-contained stylesheet reproducing the admin-ui page header + `.jetpack-footer` |
| 80 |
* computed styles. Printed once, alongside the header. |
| 81 |
*/ |
| 82 |
private function print_styles() { |
| 83 |
static $printed = false; |
| 84 |
if ( $printed ) { |
| 85 |
return; |
| 86 |
} |
| 87 |
$printed = true; |
| 88 |
?> |
| 89 |
<style id="jp-akismet-chrome-css"> |
| 90 |
/* ── Header (admin-ui page header look) ── */ |
| 91 |
.jp-akismet-header { |
| 92 |
display: flex; |
| 93 |
align-items: center; |
| 94 |
box-sizing: border-box; |
| 95 |
min-height: 72px; |
| 96 |
padding: 16px 24px; |
| 97 |
background: #fff; |
| 98 |
/* `box-shadow` (not `border-bottom`) so the hairline never adds to the |
| 99 |
box and nudges the logo/title off the admin-ui header's vertical center. */ |
| 100 |
box-shadow: inset 0 -1px 0 #e4e4e4; |
| 101 |
} |
| 102 |
.jp-akismet-header__title { |
| 103 |
display: flex; |
| 104 |
align-items: center; |
| 105 |
gap: 8px; |
| 106 |
text-decoration: none; |
| 107 |
} |
| 108 |
/* Keep the ring to keyboard focus. Scoped to the container to outrank |
| 109 |
wp-admin's `a:focus` and Akismet's `#akismet-plugin-container |
| 110 |
a:focus-visible`, which both beat a bare class selector. */ |
| 111 |
#akismet-plugin-container .jp-akismet-header__title:focus:not( :focus-visible ) { |
| 112 |
box-shadow: none; |
| 113 |
outline: none; |
| 114 |
} |
| 115 |
#akismet-plugin-container .jp-akismet-header__title:focus-visible { |
| 116 |
border-radius: 2px; |
| 117 |
box-shadow: 0 0 0 2px #2271b1; |
| 118 |
outline: 2px solid transparent; |
| 119 |
} |
| 120 |
/* 24×24 visual slot centering the 20px logo, matching My Jetpack/Boost. */ |
| 121 |
.jp-akismet-header__visual { |
| 122 |
display: flex; |
| 123 |
align-items: center; |
| 124 |
justify-content: center; |
| 125 |
flex-shrink: 0; |
| 126 |
width: 24px; |
| 127 |
height: 24px; |
| 128 |
} |
| 129 |
.jp-akismet-header__title h1 { |
| 130 |
margin: 0; |
| 131 |
padding: 0; |
| 132 |
font-size: 15px; |
| 133 |
font-weight: 500; |
| 134 |
line-height: 20px; |
| 135 |
color: #1e1e1e; |
| 136 |
} |
| 137 |
/* ── Footer (.jetpack-footer look) ── */ |
| 138 |
.jp-akismet-footer { |
| 139 |
display: flex; |
| 140 |
align-items: center; |
| 141 |
flex-wrap: wrap; |
| 142 |
gap: 24px; |
| 143 |
box-sizing: border-box; |
| 144 |
padding: 20px 24px; |
| 145 |
border-top: 1px solid #e4e4e4; |
| 146 |
background: #fff; |
| 147 |
color: #1e1e1e; |
| 148 |
font-size: 13px; |
| 149 |
} |
| 150 |
.jp-akismet-footer__logo { |
| 151 |
display: flex; |
| 152 |
align-items: center; |
| 153 |
gap: 8px; |
| 154 |
font-weight: 500; |
| 155 |
} |
| 156 |
.jp-akismet-footer__menu { |
| 157 |
display: flex; |
| 158 |
gap: 16px; |
| 159 |
} |
| 160 |
/* `#akismet-plugin-container a` is green in Akismet's own CSS; this scoped |
| 161 |
selector outranks it so the footer links read as neutral grey (#707070, |
| 162 |
matching My Jetpack's footer links). */ |
| 163 |
#akismet-plugin-container .jp-akismet-footer__menu a { |
| 164 |
color: #707070; |
| 165 |
text-decoration: none; |
| 166 |
} |
| 167 |
#akismet-plugin-container .jp-akismet-footer__menu a:hover { |
| 168 |
color: #1e1e1e; |
| 169 |
text-decoration: underline; |
| 170 |
} |
| 171 |
.jp-akismet-footer__a8c { |
| 172 |
margin-inline-start: auto; |
| 173 |
display: inline-flex; |
| 174 |
align-items: center; |
| 175 |
} |
| 176 |
/* Mobile: the byline drops its right-push and wraps to its own full-width |
| 177 |
row, left-aligned — matching how JetpackFooter stacks on other pages. */ |
| 178 |
@media (max-width: 782px) { |
| 179 |
.jp-akismet-footer__a8c { |
| 180 |
margin-inline-start: 0; |
| 181 |
flex-basis: 100%; |
| 182 |
} |
| 183 |
} |
| 184 |
.jp-akismet-footer__a8c svg path { |
| 185 |
fill: #707070; |
| 186 |
} |
| 187 |
|
| 188 |
/* ── Hello Dolly ── |
| 189 |
The `.jetpack-admin-page #dolly` treatment (right-aligned, italic, |
| 190 |
WPDS colors) ships in the jetpack-components / My Jetpack admin CSS, |
| 191 |
which doesn't load on Akismet's page — so without this, Dolly's lyric |
| 192 |
falls back to left-aligned here. Re-declare it so Dolly lands top-right |
| 193 |
exactly like every other unified Jetpack admin page. */ |
| 194 |
.jetpack-admin-page #dolly { |
| 195 |
float: none; |
| 196 |
text-align: end; |
| 197 |
background: var(--wpds-color-background-surface-neutral-strong, #fff); |
| 198 |
font-style: italic; |
| 199 |
color: var(--wpds-color-foreground-content-neutral-weak, #87a6bc); |
| 200 |
border-bottom: none; |
| 201 |
} |
| 202 |
@media (max-width: 659px) { |
| 203 |
.jetpack-admin-page #dolly { |
| 204 |
display: none; |
| 205 |
} |
| 206 |
} |
| 207 |
|
| 208 |
/* ── Contained layout: fixed header, scrolling middle, pinned footer ── |
| 209 |
Mirrors the jetpack-admin-page-layout mixin, adapted to Akismet's |
| 210 |
markup (#wpbody-content > #akismet-plugin-container > header/.akismet-lower/footer). |
| 211 |
Scoped to both menu locations: jetpack_page_… and settings_page_… |
| 212 |
|
| 213 |
Logical properties throughout, like the mixin, so it flips under RTL. */ |
| 214 |
body[class*="_page_akismet-key-config"] #wpcontent { |
| 215 |
padding-inline-start: 0; |
| 216 |
} |
| 217 |
body[class*="_page_akismet-key-config"] #wpfooter { |
| 218 |
display: none; |
| 219 |
} |
| 220 |
/* `#screen-meta-links` (the Screen Options / Help tabs container) is always |
| 221 |
emitted by core's admin header even when empty, and core gives it |
| 222 |
`margin: 0 10px 20px 0`. On wp.com Simple sites its contents are hidden |
| 223 |
but the element — and its 20px bottom margin — remain, reserving a blank |
| 224 |
slot at the very top of the page above the Jetpack header. The |
| 225 |
`jetpack-admin-page-layout` mixin hides it for the same reason; do it here |
| 226 |
too. Left UNSCOPED (not under `_page_akismet-key-config`) on purpose: the |
| 227 |
inline stylesheet is only ever printed on Akismet admin views, and Simple |
| 228 |
renders its stats UI under a different slug (`dashboard_page_akismet-stats`), |
| 229 |
so an unscoped rule covers every page this chrome appears on. */ |
| 230 |
#screen-meta-links { |
| 231 |
display: none; |
| 232 |
} |
| 233 |
body[class*="_page_akismet-key-config"] #wpbody-content { |
| 234 |
box-sizing: border-box; |
| 235 |
position: fixed; |
| 236 |
top: var(--wp-admin-bar-height, 32px); |
| 237 |
inset-inline-start: 160px; |
| 238 |
inset-inline-end: 0; |
| 239 |
bottom: 0; |
| 240 |
width: auto; |
| 241 |
padding-bottom: 0; |
| 242 |
overflow: hidden; |
| 243 |
display: flex; |
| 244 |
flex-direction: column; |
| 245 |
} |
| 246 |
body[class*="_page_akismet-key-config"].folded #wpbody-content { |
| 247 |
inset-inline-start: 36px; |
| 248 |
} |
| 249 |
@media (max-width: 960px) { |
| 250 |
body[class*="_page_akismet-key-config"].auto-fold #wpbody-content { |
| 251 |
inset-inline-start: 36px; |
| 252 |
} |
| 253 |
} |
| 254 |
@media (min-width: 961px) { |
| 255 |
body[class*="_page_akismet-key-config"].is-nav-unification:not(.folded) #wpbody-content { |
| 256 |
inset-inline-start: 272px; |
| 257 |
} |
| 258 |
} |
| 259 |
body[class*="_page_akismet-key-config"] #akismet-plugin-container { |
| 260 |
flex: 1 1 auto; |
| 261 |
min-height: 0; |
| 262 |
min-width: 0; |
| 263 |
display: flex; |
| 264 |
flex-direction: column; |
| 265 |
/* Drop Akismet's 1px container border: it insets the whole column by 1px, |
| 266 |
pushing the header logo/title off the admin-ui alignment grid. */ |
| 267 |
border: 0; |
| 268 |
} |
| 269 |
body[class*="_page_akismet-key-config"] .jp-akismet-header { |
| 270 |
flex-shrink: 0; |
| 271 |
} |
| 272 |
/* The scrollable middle. Target the generic child between header and |
| 273 |
footer (not `.akismet-lower` specifically) so the config, start AND |
| 274 |
stats (bare iframe) views all get a full-width scroll surface. The |
| 275 |
`#wpbody-content` id keeps these rules ahead of Akismet's own |
| 276 |
`.akismet-lower { width: 720px }`. */ |
| 277 |
body[class*="_page_akismet-key-config"] #wpbody-content > #akismet-plugin-container > :not(.jp-akismet-header):not(.jp-akismet-footer) { |
| 278 |
flex: 1 1 auto; |
| 279 |
min-height: 0; |
| 280 |
min-width: 0; |
| 281 |
width: auto; |
| 282 |
max-width: none; |
| 283 |
margin: 0; |
| 284 |
overflow: auto; |
| 285 |
} |
| 286 |
/* Move the readable-width restriction onto the content blocks so the |
| 287 |
scroll surface itself spans the full width (scrolling works wherever |
| 288 |
the pointer is), while the cards stay a comfortable column, left- |
| 289 |
aligned with the header. 45rem ≈ Akismet's original 720px column. */ |
| 290 |
body[class*="_page_akismet-key-config"] .akismet-lower > * { |
| 291 |
box-sizing: border-box; |
| 292 |
max-width: 45rem; |
| 293 |
margin-inline: auto; |
| 294 |
} |
| 295 |
body[class*="_page_akismet-key-config"] .jp-akismet-footer { |
| 296 |
flex-shrink: 0; |
| 297 |
} |
| 298 |
@media (max-width: 782px) { |
| 299 |
body[class*="_page_akismet-key-config"] #wpbody-content, |
| 300 |
body[class*="_page_akismet-key-config"].folded #wpbody-content, |
| 301 |
body[class*="_page_akismet-key-config"].auto-fold #wpbody-content { |
| 302 |
top: var(--wp-admin-bar-height, 46px); |
| 303 |
inset-inline-start: 0; |
| 304 |
} |
| 305 |
} |
| 306 |
</style> |
| 307 |
<?php |
| 308 |
} |
| 309 |
|
| 310 |
/** |
| 311 |
* Render the admin-ui-style header (Akismet logo + title) that replaces Akismet's default masthead. |
| 312 |
*/ |
| 313 |
public function render_header() { |
| 314 |
$this->print_styles(); |
| 315 |
?> |
| 316 |
<header class="jp-akismet-header"> |
| 317 |
<a class="jp-akismet-header__title" href="<?php echo esc_url( admin_url( 'admin.php?page=akismet-key-config' ) ); ?>"> |
| 318 |
<span class="jp-akismet-header__visual" aria-hidden="true"><?php echo $this->akismet_logo( 20 ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static SVG. ?></span> |
| 319 |
<h1><?php esc_html_e( 'Akismet Anti-spam', 'jetpack' ); ?></h1> |
| 320 |
</a> |
| 321 |
</header> |
| 322 |
<?php |
| 323 |
} |
| 324 |
|
| 325 |
/** |
| 326 |
* Render the unified Jetpack footer (`.jetpack-footer` look) at the bottom of the page. |
| 327 |
*/ |
| 328 |
public function render_footer() { |
| 329 |
// Match wrap_ui(): link the byline to the local About page when Jetpack isn't connectable, |
| 330 |
// otherwise to the external jetpack.com redirect. |
| 331 |
$connectable = ! Jetpack::is_connection_ready() && ! ( new Status() )->is_offline_mode(); |
| 332 |
$a8c_url = ! $connectable |
| 333 |
? admin_url( 'admin.php?page=jetpack_about' ) |
| 334 |
: Redirect::get_url( 'jetpack' ); |
| 335 |
?> |
| 336 |
<footer class="jp-akismet-footer jetpack-footer" aria-label="<?php esc_attr_e( 'Jetpack', 'jetpack' ); ?>" role="contentinfo"> |
| 337 |
<div class="jp-akismet-footer__logo"> |
| 338 |
<?php echo Admin_Chrome_Logo::render( 16, 'jp-akismet-logo' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static SVG. ?> |
| 339 |
<span><?php esc_html_e( 'Jetpack', 'jetpack' ); ?></span> |
| 340 |
</div> |
| 341 |
<?php if ( ! ( new Host() )->is_wpcom_platform() && Footer_Links::is_my_jetpack_available() ) : ?> |
| 342 |
<?php $products_section = Footer_Links::get_my_jetpack_products_section(); ?> |
| 343 |
<nav class="jp-akismet-footer__menu"> |
| 344 |
<a href="<?php echo esc_url( admin_url( 'admin.php?page=my-jetpack#/' . $products_section['slug'] ) ); ?>"><?php echo esc_html( $products_section['label'] ); ?></a> |
| 345 |
<a href="<?php echo esc_url( admin_url( 'admin.php?page=my-jetpack#/help' ) ); ?>"><?php echo esc_html_x( 'Help', 'Navigation item', 'jetpack' ); ?></a> |
| 346 |
</nav> |
| 347 |
<?php endif; ?> |
| 348 |
<a class="jp-akismet-footer__a8c" href="<?php echo esc_url( $a8c_url ); ?>" aria-label="<?php esc_attr_e( 'An Automattic Airline', 'jetpack' ); ?>"> |
| 349 |
<svg role="img" x="0" y="0" viewBox="0 0 935 38.2" height="7" aria-hidden="true"><path d="M317.1 38.2c-12.6 0-20.7-9.1-20.7-18.5v-1.2c0-9.6 8.2-18.5 20.7-18.5 12.6 0 20.8 8.9 20.8 18.5v1.2C337.9 29.1 329.7 38.2 317.1 38.2zM331.2 18.6c0-6.9-5-13-14.1-13s-14 6.1-14 13v0.9c0 6.9 5 13.1 14 13.1s14.1-6.2 14.1-13.1V18.6zM175 36.8l-4.7-8.8h-20.9l-4.5 8.8h-7L157 1.3h5.5L182 36.8H175zM159.7 8.2L152 23.1h15.7L159.7 8.2zM212.4 38.2c-12.7 0-18.7-6.9-18.7-16.2V1.3h6.6v20.9c0 6.6 4.3 10.5 12.5 10.5 8.4 0 11.9-3.9 11.9-10.5V1.3h6.7V22C231.4 30.8 225.8 38.2 212.4 38.2zM268.6 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H268.6zM397.3 36.8V8.7l-1.8 3.1 -14.9 25h-3.3l-14.7-25 -1.8-3.1v28.1h-6.5V1.3h9.2l14 24.4 1.7 3 1.7-3 13.9-24.4h9.1v35.5H397.3zM454.4 36.8l-4.7-8.8h-20.9l-4.5 8.8h-7l19.2-35.5h5.5l19.5 35.5H454.4zM439.1 8.2l-7.7 14.9h15.7L439.1 8.2zM488.4 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H488.4zM537.3 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H537.3zM569.3 36.8V4.6c2.7 0 3.7-1.4 3.7-3.4h2.8v35.5L569.3 36.8 569.3 36.8zM628 11.3c-3.2-2.9-7.9-5.7-14.2-5.7 -9.5 0-14.8 6.5-14.8 13.3v0.7c0 6.7 5.4 13 15.3 13 5.9 0 10.8-2.8 13.9-5.7l4 4.2c-3.9 3.8-10.5 7.1-18.3 7.1 -13.4 0-21.6-8.7-21.6-18.3v-1.2c0-9.6 8.9-18.7 21.9-18.7 7.5 0 14.3 3.1 18 7.1L628 11.3zM321.5 12.4c1.2 0.8 1.5 2.4 0.8 3.6l-6.1 9.4c-0.8 1.2-2.4 1.6-3.6 0.8l0 0c-1.2-0.8-1.5-2.4-0.8-3.6l6.1-9.4C318.7 11.9 320.3 11.6 321.5 12.4L321.5 12.4z"></path><path d="M37.5 36.7l-4.7-8.9H11.7l-4.6 8.9H0L19.4 0.8H25l19.7 35.9H37.5zM22 7.8l-7.8 15.1h15.9L22 7.8zM82.8 36.7l-23.3-24 -2.3-2.5v26.6h-6.7v-36H57l22.6 24 2.3 2.6V0.8h6.7v35.9H82.8z"></path><path d="M719.9 37l-4.8-8.9H694l-4.6 8.9h-7.1l19.5-36h5.6l19.8 36H719.9zM704.4 8l-7.8 15.1h15.9L704.4 8zM733 37V1h6.8v36H733zM781 37c-1.8 0-2.6-2.5-2.9-5.8l-0.2-3.7c-0.2-3.6-1.7-5.1-8.4-5.1h-12.8V37H750V1h19.6c10.8 0 15.7 4.3 15.7 9.9 0 3.9-2 7.7-9 9 7 0.5 8.5 3.7 8.6 7.9l0.1 3c0.1 2.5 0.5 4.3 2.2 6.1V37H781zM778.5 11.8c0-2.6-2.1-5.1-7.9-5.1h-13.8v10.8h14.4c5 0 7.3-2.4 7.3-5.2V11.8zM794.8 37V1h6.8v30.4h28.2V37H794.8zM836.7 37V1h6.8v36H836.7zM886.2 37l-23.4-24.1 -2.3-2.5V37h-6.8V1h6.5l22.7 24.1 2.3 2.6V1h6.8v36H886.2zM902.3 37V1H935v5.6h-26v9.2h20v5.5h-20v10.1h26V37H902.3z"></path></svg> |
| 350 |
</a> |
| 351 |
</footer> |
| 352 |
<?php |
| 353 |
} |
| 354 |
} |
| 355 |
|