refactor(inc, image-optimizer): restructure /inc architecture and standardize image optimizer module
- reorganized /inc directory structure for improved separation of concerns - grouped files into customizer, utilities, and tools - improved naming consistency across files (e.g. *-settings, template-tags, etc.) - simplified functions.php includes for better readability and maintainability - refactored Customizer structure - extracted image optimizer settings from core-settings into dedicated module - consolidated settings, UI logic, and styles into a single feature file - improved naming consistency for hooks and functions - standardized Image Optimizer admin tool - renamed "Performance Tools" to "Image Optimizer" across UI and hooks - updated admin page registration, callback names, and menu labels - aligned AJAX nonce naming for consistency and clarity - preserved all existing logic and behavior (no functional changes) - improved overall code organization and long-term maintainability no breaking changes
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
/**
|
||||
* Zeitfresser helper functions.
|
||||
*
|
||||
* @package zeitfresser
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fixed thumbnail size used for post cards and snippets.
|
||||
*
|
||||
* This keeps thumbnail selection deterministic while still allowing
|
||||
* a single central code-level switch if the size ever needs to change.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
if ( ! function_exists( 'zeitfresser_get_post_card_thumbnail_size' ) ) {
|
||||
function zeitfresser_get_post_card_thumbnail_size() {
|
||||
return 'thumbnail';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether post card featured images should be displayed.
|
||||
*
|
||||
* This replaces the old Customizer-based toggle with a static,
|
||||
* deterministic decision.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
if ( ! function_exists( 'zeitfresser_show_post_card_featured_image' ) ) {
|
||||
function zeitfresser_show_post_card_featured_image() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the excerpt length for post cards.
|
||||
*
|
||||
* This remains configurable via Theme Customizer, as it is a
|
||||
* meaningful editorial control.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
if ( ! function_exists( 'zeitfresser_get_post_card_excerpt_length' ) ) {
|
||||
function zeitfresser_get_post_card_excerpt_length() {
|
||||
return (int) get_theme_mod( 'post_snippet_excerpt_size', 20 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'zeitfresser_fs' ) ) {
|
||||
/**
|
||||
* Lightweight Freemius compatibility stub.
|
||||
*
|
||||
* The original premium theme used Freemius checks to gate premium-only controls.
|
||||
* Zeitfresser ships as a self-contained theme, so we keep a tiny compatibility
|
||||
* object instead of loading the full SDK.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
function zeitfresser_fs() {
|
||||
static $stub = null;
|
||||
|
||||
if ( null === $stub ) {
|
||||
$stub = new class() {
|
||||
public function is__premium_only() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return $stub;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'zeitfresser_get_social_defaults' ) ) {
|
||||
/**
|
||||
* Return default social profile URLs.
|
||||
*
|
||||
* @return array<string,string>
|
||||
*/
|
||||
function zeitfresser_get_social_defaults() {
|
||||
return array(
|
||||
'mastodon' => 'https://social.ztfr.eu/@dome',
|
||||
'github' => 'https://github.com/Domoel',
|
||||
'matrix' => 'https://look.ztfr.eu/#/@dome:ztfr.eu',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'zeitfresser_get_social_link_default' ) ) {
|
||||
/**
|
||||
* Return default social URL for a given service.
|
||||
*
|
||||
* @param string $social_key Social service key.
|
||||
* @return string
|
||||
*/
|
||||
function zeitfresser_get_social_link_default( $social_key ) {
|
||||
$defaults = zeitfresser_get_social_defaults();
|
||||
return isset( $defaults[ $social_key ] ) ? $defaults[ $social_key ] : '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ( ! function_exists( 'zeitfresser_get_mod' ) ) {
|
||||
/**
|
||||
* Return a cached theme modification value.
|
||||
*
|
||||
* @param string $key Theme mod key.
|
||||
* @param mixed $default Optional default value.
|
||||
* @return mixed
|
||||
*/
|
||||
function zeitfresser_get_mod( $key, $default = null ) {
|
||||
static $cache = array();
|
||||
|
||||
if ( array_key_exists( $key, $cache ) ) {
|
||||
return $cache[ $key ];
|
||||
}
|
||||
|
||||
$cache[ $key ] = get_theme_mod( $key, $default );
|
||||
|
||||
return $cache[ $key ];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'zeitfresser_get_social_links_settings' ) ) {
|
||||
/**
|
||||
* Return configured social links with defaults applied.
|
||||
*
|
||||
* @return array<string,string>
|
||||
*/
|
||||
function zeitfresser_get_social_links_settings() {
|
||||
$settings = array();
|
||||
|
||||
foreach ( zeitfresser_get_social_links() as $social_key => $social_label ) {
|
||||
$settings[ $social_key ] = zeitfresser_get_mod(
|
||||
'social_links_' . $social_key,
|
||||
zeitfresser_get_social_link_default( $social_key )
|
||||
);
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'zeitfresser_social_icon_svg' ) ) {
|
||||
/**
|
||||
* Return inline SVG markup for social icons.
|
||||
*
|
||||
* @param string $key Social service key.
|
||||
* @return string
|
||||
*/
|
||||
function zeitfresser_social_icon_svg( $key ) {
|
||||
$icons = array(
|
||||
'facebook' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" aria-hidden="true" focusable="false"><path d="M279.14 288l14.22-92.66h-88.91v-60.13c0-25.35 12.42-50.06 52.24-50.06h40.42V6.26S260.43 0 225.36 0c-73.22 0-121.08 44.38-121.08 124.72v70.62H22.89V288h81.39v224h100.17V288z"/></svg>',
|
||||
'instagram' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M8 0C5.829 0 5.556.01 4.703.048 3.85.088 3.269.222 2.76.42a3.917 3.917 0 0 0-1.417.923A3.927 3.927 0 0 0 .42 2.76C.222 3.268.087 3.85.048 4.7.01 5.555 0 5.827 0 8.001c0 2.172.01 2.444.048 3.297.04.852.174 1.433.372 1.942.205.526.478.972.923 1.417.444.445.89.719 1.416.923.51.198 1.09.333 1.942.372C5.555 15.99 5.827 16 8 16s2.444-.01 3.298-.048c.851-.04 1.434-.174 1.943-.372a3.916 3.916 0 0 0 1.416-.923c.445-.445.718-.891.923-1.417.197-.509.332-1.09.372-1.942C15.99 10.445 16 10.173 16 8s-.01-2.445-.048-3.299c-.04-.851-.175-1.433-.372-1.941a3.926 3.926 0 0 0-.923-1.417A3.911 3.911 0 0 0 13.24.42c-.51-.198-1.092-.333-1.943-.372C10.443.01 10.172 0 7.998 0h.003zm-.717 1.442h.718c2.136 0 2.389.007 3.232.046.78.035 1.204.166 1.486.275.373.145.64.319.92.599.28.28.453.546.598.92.11.281.24.705.275 1.485.039.843.047 1.096.047 3.231s-.008 2.389-.047 3.232c-.035.78-.166 1.203-.275 1.485a2.47 2.47 0 0 1-.599.919c-.28.28-.546.453-.92.598-.28.11-.704.24-1.485.276-.843.038-1.096.047-3.232.047s-2.39-.009-3.233-.047c-.78-.036-1.203-.166-1.485-.276a2.478 2.478 0 0 1-.92-.598 2.48 2.48 0 0 1-.6-.92c-.109-.281-.24-.705-.275-1.485-.038-.843-.046-1.096-.046-3.233 0-2.136.008-2.388.046-3.231.036-.78.166-1.204.276-1.486.145-.373.319-.64.599-.92.28-.28.546-.453.92-.598.282-.11.705-.24 1.485-.276.738-.034 1.024-.044 2.515-.045v.002zm4.988 1.328a.96.96 0 1 0 0 1.92.96.96 0 0 0 0-1.92zm-4.27 1.122a4.109 4.109 0 1 0 0 8.217 4.109 4.109 0 0 0 0-8.217zm0 1.441a2.667 2.667 0 1 1 0 5.334 2.667 2.667 0 0 1 0-5.334z"/></svg>',
|
||||
'youtube' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.007 2.007 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.007 2.007 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31.4 31.4 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.007 2.007 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A99.788 99.788 0 0 1 7.858 2h.193zM6.4 5.209v4.818l4.157-2.408L6.4 5.209z"/></svg>',
|
||||
'linkedin' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" aria-hidden="true" focusable="false"><path d="M100.28 448H7.4V148.9h92.88zM53.79 108.1C24.09 108.1 0 83.5 0 53.8A53.79 53.79 0 0 1 53.79 0A53.79 53.79 0 0 1 107.6 53.8c0 29.7-24.1 54.3-53.81 54.3zM447.9 448h-92.68V302.4c0-34.7-.7-79.2-48.29-79.2c-48.29 0-55.69 37.7-55.69 76.7V448h-92.78V148.9h89.08v40.8h1.3c12.4-23.5 42.69-48.29 87.88-48.29c94 0 111.28 61.9 111.28 142.3V448z"/></svg>',
|
||||
'twitter' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" aria-hidden="true" focusable="false"><path d="M459.37 151.72c.32 4.54.32 9.1.32 13.63c0 138.72-105.58 298.56-298.56 298.56c-59.45 0-114.68-17.22-161.14-47.11c8.45 1 16.57 1.32 25.34 1.32c49.06 0 94.21-16.57 130.27-44.84c-46.13-1-84.79-31.24-98.11-72.77c6.5 1 12.99 1.63 19.81 1.63c9.42 0 18.84-1.32 27.61-3.57c-48.08-9.74-84.14-51.98-84.14-102.98v-1.32c13.97 7.8 30.21 12.67 47.43 13.3c-28.3-18.84-46.78-51.02-46.78-87.39c0-19.49 5.2-37.36 14.29-52.95c51.98 63.67 129.3 105.26 216.36 109.75c-1.62-7.8-2.6-15.92-2.6-24.04c0-57.83 46.78-104.93 104.93-104.93c30.21 0 57.5 12.67 76.67 33.14c23.72-4.55 46.46-13.3 66.59-25.34c-7.8 24.37-24.37 44.84-46.13 57.83c21.12-2.27 41.58-8.12 60.42-16.24c-14.29 20.79-32.16 39.31-52.63 54.25z"/></svg>',
|
||||
'pinterest' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" aria-hidden="true" focusable="false"><path d="M204 6.7C93.4 6.7 0 100.1 0 210.7c0 69.2 40.5 128.9 101.6 161.4c-1.4-13.7-2.6-34.8.5-49.8c2.8-15.9 18.1-101.2 18.1-101.2s-4.6-9.3-4.6-23c0-21.6 12.5-37.8 28.1-37.8c13.2 0 19.6 9.9 19.6 21.8c0 13.2-8.4 33-12.7 51.3c-3.6 15.6 7.7 28.4 23 28.4c27.6 0 48.9-29.1 48.9-71.1c0-37.2-26.8-63.2-65-63.2c-44.3 0-70.3 33.1-70.3 67.5c0 13.3 5.1 27.5 11.6 35.2c1.3 1.6 1.5 3 .9 4.7c-1 5.1-3.2 16-3.6 18.2c-.6 3-2.1 3.6-4.8 2.2c-17.8-8.3-28.9-34.1-28.9-54.8c0-44.6 32.4-85.6 93.4-85.6c49 0 87.1 34.9 87.1 81.6c0 48.7-30.7 87.9-73.2 87.9c-14.3 0-27.7-7.4-32.3-16.2l-8.8 33.5c-3.2 12.2-11.8 27.5-17.6 36.8c13.2 4.1 27.1 6.2 41.5 6.2c110.6 0 204-93.4 204-204S314.6 6.7 204 6.7z"/></svg>',
|
||||
'tiktok' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" aria-hidden="true" focusable="false"><path d="M448,209.9a210.06,210.06,0,0,1-122.77-39.25V349.38A162.55,162.55,0,1,1,185,188.31V278.2a74.62,74.62,0,1,0,52.23,71.18V0h88a121.18,121.18,0,0,0,1.86,22.17h0A122.18,122.18,0,0,0,448,142.3Z"/></svg>',
|
||||
'mastodon' => '<svg viewBox="0 0 448 512" aria-hidden="true"><path d="M433 179.11c0-97.2-63.91-125.7-63.91-125.7C336.42 38.4 279.2 32 224.14 32h-.27c-55.06 0-112.28 6.4-144.95 21.41c0 0-63.92 28.5-63.92 125.7c0 22.34-.43 49.13.27 81.78c2.31 109.32 20.05 217.01 121.08 241.82c46.64 11.45 86.68 13.84 119.46 12.06c59.08-3.2 92.27-20.62 92.27-20.62l-1.96-43.47s-42.3 13.33-89.78 11.72c-47.03-1.6-96.73-5.09-104.41-63.16a116.85 116.85 0 0 1-1.06-15.65s46.27 11.3 104.92 13.99c35.27 1.62 68.32-2.06 101.96-6.15c64.49-7.83 120.53-48.23 127.62-85.36c11.18-58.65 10.26-143.5 10.26-143.5zm-80.32 145.27h-50.54V200.59c0-26.15-11.06-39.43-33.17-39.43c-24.46 0-36.72 15.84-36.72 47.1v67.82H181.8V208.26c0-31.26-12.26-47.1-36.73-47.1c-22.1 0-33.16 13.28-33.16 39.43v123.79H61.37V196.83c0-26.15 6.68-46.92 20.03-62.31c13.75-15.39 31.75-23.14 54.09-23.14c25.82 0 45.34 9.92 58.74 29.76l12.69 21.11l12.69-21.11c13.4-19.84 32.92-29.76 58.75-29.76c22.33 0 40.34 7.75 54.08 23.14c13.36 15.39 20.04 36.16 20.04 62.31z"/></svg>',
|
||||
'github' => '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 .297a12 12 0 0 0-3.794 23.403c.6.113.82-.26.82-.577v-2.234c-3.338.726-4.042-1.61-4.042-1.61a3.18 3.18 0 0 0-1.335-1.754c-1.09-.745.083-.73.083-.73a2.52 2.52 0 0 1 1.84 1.24 2.555 2.555 0 0 0 3.49.997 2.56 2.56 0 0 1 .763-1.606c-2.665-.303-5.466-1.334-5.466-5.933a4.64 4.64 0 0 1 1.235-3.216 4.31 4.31 0 0 1 .117-3.172s1.008-.322 3.301 1.23a11.46 11.46 0 0 1 6.003 0c2.293-1.552 3.3-1.23 3.3-1.23a4.31 4.31 0 0 1 .118 3.172 4.64 4.64 0 0 1 1.234 3.216c0 4.61-2.804 5.627-5.476 5.922a2.865 2.865 0 0 1 .815 2.222v3.293c0 .32.218.694.825.576A12.001 12.001 0 0 0 12 .297"/></svg>',
|
||||
'matrix' => '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2 2v20h3V5.5l5 7.5h1l5-7.5V22h3V2h-3l-5.5 8-5.5-8H2z"/></svg>',
|
||||
);
|
||||
|
||||
return isset( $icons[ $key ] ) ? $icons[ $key ] : '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Backward-compatible Freemius helper alias.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
function db_fs() {
|
||||
return zeitfresser_fs();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Backward-compatible social default alias.
|
||||
*
|
||||
* @param string $social_key Social service key.
|
||||
* @return string
|
||||
*/
|
||||
function graphthemes_get_social_link_default( $social_key ) {
|
||||
return zeitfresser_get_social_link_default( $social_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get asset URL with version (cache busting).
|
||||
*
|
||||
* @param string $path Relative path inside /assets (must start with /)
|
||||
* @return array{url:string, version:string}
|
||||
*/
|
||||
if ( ! function_exists( 'zeitfresser_asset_versioned' ) ) {
|
||||
function zeitfresser_asset_versioned( $path ) {
|
||||
|
||||
$full_path = '/assets' . $path;
|
||||
|
||||
return [
|
||||
'url' => get_template_directory_uri() . $full_path,
|
||||
'version' => file_exists( get_template_directory() . $full_path )
|
||||
? (string) filemtime( get_template_directory() . $full_path )
|
||||
: ZEITFRESSER_VERSION,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get asset URL only (no version).
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
if ( ! function_exists( 'zeitfresser_asset' ) ) {
|
||||
function zeitfresser_asset( $path ) {
|
||||
return get_template_directory_uri() . '/assets/' . ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
function zeitfresser_numeric_pagination() {
|
||||
|
||||
if( is_singular() )
|
||||
return;
|
||||
|
||||
global $wp_query;
|
||||
|
||||
/** Stop execution if there's only 1 page */
|
||||
if( $wp_query->max_num_pages <= 1 )
|
||||
return;
|
||||
|
||||
$paged = get_query_var( 'paged' ) ? absint( get_query_var( 'paged' ) ) : 1;
|
||||
$max = intval( $wp_query->max_num_pages );
|
||||
|
||||
/** Add current page to the array */
|
||||
if ( $paged >= 1 )
|
||||
$links[] = $paged;
|
||||
|
||||
/** Add the pages around the current page to the array */
|
||||
if ( $paged >= 3 ) {
|
||||
$links[] = $paged - 1;
|
||||
$links[] = $paged - 2;
|
||||
}
|
||||
|
||||
if ( ( $paged + 2 ) <= $max ) {
|
||||
$links[] = $paged + 2;
|
||||
$links[] = $paged + 1;
|
||||
}
|
||||
|
||||
echo '<div class="pagination"><ul>' . "\n";
|
||||
|
||||
/** Previous Post Link */
|
||||
if ( get_previous_posts_link() )
|
||||
printf( '<li>%s</li>' . "\n", get_previous_posts_link() );
|
||||
|
||||
/** Link to first page, plus ellipses if necessary */
|
||||
if ( ! in_array( 1, $links ) ) {
|
||||
$class = 1 == $paged ? ' class="active"' : '';
|
||||
|
||||
printf( '<li%s><a href="%s">%s</a></li>' . "\n", $class, esc_url( get_pagenum_link( 1 ) ), '1' );
|
||||
|
||||
if ( ! in_array( 2, $links ) )
|
||||
echo '<li>…</li>';
|
||||
}
|
||||
|
||||
/** Link to current page, plus 2 pages in either direction if necessary */
|
||||
sort( $links );
|
||||
foreach ( (array) $links as $link ) {
|
||||
$class = $paged == $link ? ' class="active"' : '';
|
||||
printf( '<li%s><a href="%s">%s</a></li>' . "\n", $class, esc_url( get_pagenum_link( $link ) ), $link );
|
||||
}
|
||||
|
||||
/** Link to last page, plus ellipses if necessary */
|
||||
if ( ! in_array( $max, $links ) ) {
|
||||
if ( ! in_array( $max - 1, $links ) )
|
||||
echo '<li>…</li>' . "\n";
|
||||
|
||||
$class = $paged == $max ? ' class="active"' : '';
|
||||
printf( '<li%s><a href="%s">%s</a></li>' . "\n", $class, esc_url( get_pagenum_link( $max ) ), $max );
|
||||
}
|
||||
|
||||
/** Next Post Link */
|
||||
if ( get_next_posts_link() )
|
||||
printf( '<li>%s</li>' . "\n", get_next_posts_link() );
|
||||
|
||||
echo '</ul></div>' . "\n";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* Functions which enhance the theme by hooking into WordPress
|
||||
*
|
||||
* @package zeitfresser
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds custom classes to the array of body classes.
|
||||
*
|
||||
* @param array $classes Classes for the body element.
|
||||
* @return array
|
||||
*/
|
||||
function zeitfresser_body_classes( $classes ) {
|
||||
// Adds a class of hfeed to non-singular pages.
|
||||
if ( ! is_singular() ) {
|
||||
$classes[] = 'hfeed';
|
||||
}
|
||||
|
||||
// Adds a class of no-sidebar when there is no sidebar present.
|
||||
if ( ! is_active_sidebar( 'sidebar-1' ) ) {
|
||||
$classes[] = 'no-sidebar';
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
add_filter( 'body_class', 'zeitfresser_body_classes' );
|
||||
|
||||
/**
|
||||
* Add a pingback url auto-discovery header for single posts, pages, or attachments.
|
||||
*/
|
||||
function zeitfresser_pingback_header() {
|
||||
if ( is_singular() && pings_open() ) {
|
||||
printf( '<link rel="pingback" href="%s">', esc_url( get_bloginfo( 'pingback_url' ) ) );
|
||||
}
|
||||
}
|
||||
add_action( 'wp_head', 'zeitfresser_pingback_header' );
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
/**
|
||||
* Custom template tags for this theme
|
||||
*
|
||||
* Eventually, some of the functionality here could be replaced by core features.
|
||||
*
|
||||
* @package zeitfresser
|
||||
*/
|
||||
|
||||
if ( ! function_exists( 'zeitfresser_posted_on' ) ) :
|
||||
/**
|
||||
* Prints HTML with meta information for the current post-date/time.
|
||||
*/
|
||||
function zeitfresser_posted_on() {
|
||||
$time_string = '<time class="entry-date published updated" datetime="%1$s">%2$s</time>';
|
||||
if ( get_the_time( 'U' ) !== get_the_modified_time( 'U' ) ) {
|
||||
$time_string = '<time class="entry-date published" datetime="%1$s">%2$s</time><time class="updated" datetime="%3$s">%4$s</time>';
|
||||
}
|
||||
|
||||
$time_string = sprintf(
|
||||
$time_string,
|
||||
esc_attr( get_the_date( DATE_W3C ) ),
|
||||
esc_html( get_the_date() ),
|
||||
esc_attr( get_the_modified_date( DATE_W3C ) ),
|
||||
esc_html( get_the_modified_date() )
|
||||
);
|
||||
|
||||
$posted_on = sprintf(
|
||||
/* translators: %s: post date. */
|
||||
esc_html_x( 'Posted on %s', 'post date', 'zeitfresser' ),
|
||||
'<a href="' . esc_url( get_permalink() ) . '" rel="bookmark">' . $time_string . '</a>'
|
||||
);
|
||||
|
||||
echo '<span class="posted-on">' . $posted_on . '</span>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
}
|
||||
endif;
|
||||
|
||||
if ( ! function_exists( 'zeitfresser_posted_by' ) ) :
|
||||
/**
|
||||
* Prints HTML with meta information for the current author.
|
||||
*/
|
||||
function zeitfresser_posted_by() {
|
||||
$byline = sprintf(
|
||||
/* translators: %s: post author. */
|
||||
esc_html_x( 'by %s', 'post author', 'zeitfresser' ),
|
||||
'<span class="author vcard"><a class="url fn n" href="' . esc_url( get_author_posts_url( get_the_author_meta( 'ID' ) ) ) . '">' . esc_html( get_the_author() ) . '</a></span>'
|
||||
);
|
||||
|
||||
echo '<span class="byline"> ' . $byline . '</span>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
}
|
||||
endif;
|
||||
|
||||
if ( ! function_exists( 'zeitfresser_entry_footer' ) ) :
|
||||
/**
|
||||
* Prints HTML with meta information for the categories, tags and comments.
|
||||
*/
|
||||
function zeitfresser_entry_footer() {
|
||||
// Hide category and tag text for pages.
|
||||
if ( 'post' === get_post_type() ) {
|
||||
/* translators: used between list items, there is a space after the comma */
|
||||
$categories_list = get_the_category_list( esc_html__( ', ', 'zeitfresser' ) );
|
||||
if ( $categories_list ) {
|
||||
/* translators: 1: list of categories. */
|
||||
printf( '<span class="cat-links">' . esc_html__( 'Posted in %1$s', 'zeitfresser' ) . '</span>', $categories_list ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/* translators: used between list items, there is a space after the comma */
|
||||
$tags_list = get_the_tag_list( '', esc_html_x( ', ', 'list item separator', 'zeitfresser' ) );
|
||||
if ( $tags_list ) {
|
||||
/* translators: 1: list of tags. */
|
||||
printf( '<span class="tags-links">' . esc_html__( 'Tagged %1$s', 'zeitfresser' ) . '</span>', $tags_list ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! is_single() && ! post_password_required() && ( comments_open() || get_comments_number() ) ) {
|
||||
echo '<span class="comments-link">';
|
||||
comments_popup_link(
|
||||
sprintf(
|
||||
wp_kses(
|
||||
/* translators: %s: post title */
|
||||
__( 'Leave a Comment<span class="screen-reader-text"> on %s</span>', 'zeitfresser' ),
|
||||
array(
|
||||
'span' => array(
|
||||
'class' => array(),
|
||||
),
|
||||
)
|
||||
),
|
||||
wp_kses_post( get_the_title() )
|
||||
)
|
||||
);
|
||||
echo '</span>';
|
||||
}
|
||||
|
||||
edit_post_link(
|
||||
sprintf(
|
||||
wp_kses(
|
||||
/* translators: %s: Name of current post. Only visible to screen readers */
|
||||
__( 'Edit <span class="screen-reader-text">%s</span>', 'zeitfresser' ),
|
||||
array(
|
||||
'span' => array(
|
||||
'class' => array(),
|
||||
),
|
||||
)
|
||||
),
|
||||
wp_kses_post( get_the_title() )
|
||||
),
|
||||
'<span class="edit-link">',
|
||||
'</span>'
|
||||
);
|
||||
}
|
||||
endif;
|
||||
|
||||
if ( ! function_exists( 'zeitfresser_post_thumbnail' ) ) :
|
||||
/**
|
||||
* Displays an optional post thumbnail.
|
||||
*
|
||||
* Wraps the post thumbnail in an anchor element on index views, or a div
|
||||
* element when on single views.
|
||||
*/
|
||||
function zeitfresser_post_thumbnail() {
|
||||
if ( post_password_required() || is_attachment() || ! has_post_thumbnail() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_singular() ) :
|
||||
?>
|
||||
|
||||
<div class="post-thumbnail">
|
||||
<?php the_post_thumbnail(); ?>
|
||||
</div><!-- .post-thumbnail -->
|
||||
|
||||
<?php else : ?>
|
||||
|
||||
<a class="post-thumbnail" href="<?php the_permalink(); ?>" aria-hidden="true" tabindex="-1">
|
||||
<?php
|
||||
the_post_thumbnail(
|
||||
'post-thumbnail',
|
||||
array(
|
||||
'alt' => the_title_attribute(
|
||||
array(
|
||||
'echo' => false,
|
||||
)
|
||||
),
|
||||
)
|
||||
);
|
||||
?>
|
||||
</a>
|
||||
|
||||
<?php
|
||||
endif; // End is_singular().
|
||||
}
|
||||
endif;
|
||||
|
||||
if ( ! function_exists( 'wp_body_open' ) ) :
|
||||
/**
|
||||
* Shim for sites older than 5.2.
|
||||
*
|
||||
* @link https://core.trac.wordpress.org/ticket/12563
|
||||
*/
|
||||
function wp_body_open() {
|
||||
do_action( 'wp_body_open' );
|
||||
}
|
||||
endif;
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
/**
|
||||
* Floating table of contents helpers.
|
||||
*
|
||||
* @package zeitfresser
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue TOC script when needed.
|
||||
*/
|
||||
function zeitfresser_enqueue_toc_assets() {
|
||||
|
||||
if ( ! is_singular( 'post' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! zeitfresser_has_floating_toc() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$asset = zeitfresser_asset_versioned('/js/toc.js');
|
||||
|
||||
wp_enqueue_script(
|
||||
'zeitfresser-toc',
|
||||
$asset['url'],
|
||||
[],
|
||||
$asset['version'],
|
||||
true
|
||||
);
|
||||
}
|
||||
add_action( 'wp_enqueue_scripts', 'zeitfresser_enqueue_toc_assets', 20 );
|
||||
|
||||
/**
|
||||
* Return whether article TOC output is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function zeitfresser_show_article_toc() {
|
||||
return (bool) get_theme_mod( 'show_article_toc', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the minimum heading count required before showing the TOC.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function zeitfresser_get_article_toc_min_headlines() {
|
||||
$threshold = absint( get_theme_mod( 'article_toc_min_headlines', 3 ) );
|
||||
return max( 1, $threshold );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a processed single post content payload with TOC metadata.
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @return array{content:string,items:array<int,array<string,mixed>>}
|
||||
*/
|
||||
function zeitfresser_build_toc_payload( $post_id ) {
|
||||
|
||||
static $cache = array();
|
||||
$post_id = (int) $post_id;
|
||||
|
||||
if ( isset( $cache[ $post_id ] ) ) {
|
||||
return $cache[ $post_id ];
|
||||
}
|
||||
|
||||
$payload = array(
|
||||
'content' => apply_filters( 'the_content', get_post_field( 'post_content', $post_id ) ),
|
||||
'items' => array(),
|
||||
);
|
||||
|
||||
// Early exit conditions
|
||||
if ( ! $post_id || ! is_singular( 'post' ) || ! zeitfresser_show_article_toc() ) {
|
||||
return $cache[ $post_id ] = $payload;
|
||||
}
|
||||
|
||||
$content = trim( (string) $payload['content'] );
|
||||
|
||||
if ( '' === $content || ! class_exists( 'DOMDocument' ) ) {
|
||||
return $cache[ $post_id ] = $payload;
|
||||
}
|
||||
|
||||
libxml_use_internal_errors( true );
|
||||
|
||||
$dom = new DOMDocument();
|
||||
$loaded = $dom->loadHTML(
|
||||
'<?xml encoding="utf-8" ?><div id="zeitfresser-toc-root">' . $content . '</div>',
|
||||
LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
|
||||
);
|
||||
|
||||
if ( ! $loaded ) {
|
||||
libxml_clear_errors();
|
||||
return $cache[ $post_id ] = $payload;
|
||||
}
|
||||
|
||||
$container = $dom->getElementById( 'zeitfresser-toc-root' );
|
||||
|
||||
if ( ! $container ) {
|
||||
libxml_clear_errors();
|
||||
return $cache[ $post_id ] = $payload;
|
||||
}
|
||||
|
||||
$index = 1;
|
||||
$toc_items = array();
|
||||
$xpath = new DOMXPath( $dom );
|
||||
$headings = $xpath->query( './/h2 | .//h3 | .//h4', $container );
|
||||
|
||||
if ( $headings instanceof DOMNodeList ) {
|
||||
foreach ( $headings as $heading ) {
|
||||
|
||||
$text = trim( wp_strip_all_tags( $heading->textContent ) );
|
||||
if ( '' === $text ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tag_name = strtolower( $heading->nodeName );
|
||||
$id = $heading->getAttribute( 'id' );
|
||||
|
||||
if ( '' === $id ) {
|
||||
$base_id = sanitize_title( $text );
|
||||
$id = $base_id ? $base_id : 'section-' . $index;
|
||||
|
||||
while ( $dom->getElementById( $id ) ) {
|
||||
$id = $base_id . '-' . $index;
|
||||
$index++;
|
||||
}
|
||||
|
||||
$heading->setAttribute( 'id', $id );
|
||||
}
|
||||
|
||||
$toc_items[] = array(
|
||||
'id' => $id,
|
||||
'text' => $text,
|
||||
'level' => (int) substr( $tag_name, 1 ),
|
||||
);
|
||||
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
|
||||
libxml_clear_errors();
|
||||
|
||||
// Respect minimum threshold
|
||||
if ( count( $toc_items ) < zeitfresser_get_article_toc_min_headlines() ) {
|
||||
return $cache[ $post_id ] = array(
|
||||
'content' => zeitfresser_extract_toc_inner_html( $container ),
|
||||
'items' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
return $cache[ $post_id ] = array(
|
||||
'content' => zeitfresser_extract_toc_inner_html( $container ),
|
||||
'items' => $toc_items,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract container inner HTML.
|
||||
*
|
||||
* @param DOMNode $node Source node.
|
||||
* @return string
|
||||
*/
|
||||
function zeitfresser_extract_toc_inner_html( $node ) {
|
||||
|
||||
$html = '';
|
||||
|
||||
if ( ! $node || ! $node->hasChildNodes() ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
foreach ( $node->childNodes as $child_node ) {
|
||||
$html .= $node->ownerDocument->saveHTML( $child_node );
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the current singular post has a TOC.
|
||||
*
|
||||
* @param int|null $post_id Optional post ID.
|
||||
* @return bool
|
||||
*/
|
||||
function zeitfresser_has_floating_toc( $post_id = null ) {
|
||||
|
||||
$post_id = $post_id ? (int) $post_id : get_the_ID();
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$payload = zeitfresser_build_toc_payload( $post_id );
|
||||
|
||||
return ! empty( $payload['items'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render floating TOC markup.
|
||||
*
|
||||
* @param int|null $post_id Optional post ID.
|
||||
* @return void
|
||||
*/
|
||||
function zeitfresser_render_floating_toc( $post_id = null ) {
|
||||
|
||||
$post_id = $post_id ? (int) $post_id : get_the_ID();
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$payload = zeitfresser_build_toc_payload( $post_id );
|
||||
|
||||
if ( empty( $payload['items'] ) ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
|
||||
<aside class="zeitfresser-floating-toc" id="zeitfresser-floating-toc" aria-label="<?php echo esc_attr__( 'Table of contents', 'zeitfresser' ); ?>">
|
||||
<div class="zeitfresser-floating-toc__header">
|
||||
<span class="zeitfresser-floating-toc__title"><?php echo esc_html__( 'Content', 'zeitfresser' ); ?></span>
|
||||
</div>
|
||||
|
||||
<div class="zeitfresser-floating-toc__progress" aria-hidden="true">
|
||||
<span class="zeitfresser-floating-toc__progress-bar" id="zeitfresser-floating-toc-progress"></span>
|
||||
</div>
|
||||
|
||||
<nav class="zeitfresser-floating-toc__nav">
|
||||
<ol class="zeitfresser-floating-toc__list">
|
||||
<?php foreach ( $payload['items'] as $item ) : ?>
|
||||
<li class="zeitfresser-floating-toc__item level-<?php echo (int) $item['level']; ?>">
|
||||
<a href="#<?php echo esc_attr( $item['id'] ); ?>" data-target="<?php echo esc_attr( $item['id'] ); ?>">
|
||||
<?php echo esc_html( $item['text'] ); ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ol>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<?php
|
||||
}
|
||||
Reference in New Issue
Block a user