functions.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. <?php
  2. if ( ! function_exists( 'dw_setup' ) ):
  3. function dw_setup() {
  4. require( get_template_directory() . '/inc/utils.php' );
  5. require( get_template_directory() . '/inc/template.php' );
  6. require( get_template_directory() . '/inc/tweaks.php' );
  7. require( get_template_directory() . '/inc/seo.php' );
  8. if ( ! isset( $content_width ) ) $content_width = 1310;
  9. add_theme_support( 'title-tag' );
  10. add_theme_support( 'wp-block-styles' );
  11. add_theme_support( 'automatic-feed-links' );
  12. add_theme_support( 'responsive-embeds' );
  13. add_theme_support( 'menus' );
  14. add_theme_support( 'post-thumbnails' );
  15. add_theme_support( 'html5', array( 'comment-list', 'comment-form', 'search-form', 'gallery', 'caption' ) );
  16. add_theme_support( 'post-formats', array( 'aside', 'gallery', 'image', 'video', 'audio', 'quote', 'link', 'status', 'chat' ) );
  17. set_post_thumbnail_size( 150, 150, true );
  18. add_image_size( 'post-large', 770, 577, true );
  19. add_image_size( 'studio', 400, 300, true );
  20. add_filter( 'big_image_size_threshold', '__return_false' );
  21. add_theme_support( 'align-wide' );
  22. // Load the front-end stylesheet into the block editor so editor typography/sizing
  23. // matches the front end (was missing — only a 2-line editor.css was enqueued).
  24. add_theme_support( 'editor-styles' );
  25. add_editor_style( array( 'v4-style.min.css', 'css/editor.css' ) );
  26. remove_action( 'init', 'wp_sitemaps_get_server' );
  27. // added this to remove depreciation warning in v6.4.1 b/c front page doesn't have wp_header
  28. remove_action( 'wp_footer', 'the_block_template_skip_link' );
  29. // see changes @ https://core.trac.wordpress.org/changeset/56932
  30. }
  31. endif;
  32. add_action( 'after_setup_theme', 'dw_setup' );
  33. if ( ! function_exists( 'dw_plugs' ) ):
  34. function dw_plugs() {
  35. require( get_template_directory() . '/inc/smtp.php');
  36. //echo wp_mail( 'david@davidawindham.com', 'WP Mail Test', 'Mail is working' );
  37. if (!class_exists('dw_crumbs')) {
  38. //include_once(TEMPLATEPATH.'/inc/crumbs.php');
  39. }
  40. }
  41. endif;
  42. add_action( 'after_setup_theme', 'dw_plugs' );
  43. // Keep login sessions alive for a year (default is 2 days, 14 with "Remember Me").
  44. // There's no WP constant for this — it's the auth_cookie_expiration filter, so it
  45. // can't live in wp-config.php. Lower YEAR_IN_SECONDS if you want a shorter session.
  46. function dw_login_session( $length, $user_id, $remember ) {
  47. return YEAR_IN_SECONDS;
  48. }
  49. add_filter( 'auth_cookie_expiration', 'dw_login_session', 10, 3 );
  50. function dw_remove_block_library_css(){
  51. wp_dequeue_style( 'wp-block-library' );
  52. }
  53. //add_action( 'wp_enqueue_scripts', 'dw_remove_block_library_css' );
  54. /* Guest Book — publish the "Guest" Gravity Form (id 2) entries below the form on
  55. /contact/guests: 4 Name · 6 Location · 9 Website · 10 Message (Email (7) is never
  56. shown; active entries only). Registered here rather than in the late-loaded
  57. inc/form.php because this block theme renders the page content BEFORE
  58. wp_enqueue_scripts, so the render_block hook has to be attached early. */
  59. function dw_guestbook() {
  60. if ( ! class_exists( 'GFAPI' ) ) {
  61. return '';
  62. }
  63. $entries = GFAPI::get_entries(
  64. 2,
  65. array( 'status' => 'active' ),
  66. array( 'key' => 'date_created', 'direction' => 'DESC' ),
  67. array( 'offset' => 0, 'page_size' => 200 )
  68. );
  69. if ( is_wp_error( $entries ) || empty( $entries ) ) {
  70. return '';
  71. }
  72. $rows = '';
  73. foreach ( $entries as $e ) {
  74. $name = trim( rgar( $e, '4' ) );
  75. $loc = trim( rgar( $e, '6' ) );
  76. $web = trim( rgar( $e, '9' ) );
  77. $msg = trim( rgar( $e, '10' ) );
  78. if ( $name === '' && $msg === '' ) {
  79. continue;
  80. }
  81. $weblink = '';
  82. if ( $web !== '' ) {
  83. $href = esc_url( $web );
  84. if ( $href !== '' ) {
  85. $label = preg_replace( '#^https?://(www\.)?#i', '', untrailingslashit( $web ) );
  86. $weblink = '<a href="' . $href . '" target="_blank" rel="nofollow ugc noopener">' . esc_html( $label ) . '</a>';
  87. }
  88. }
  89. $rows .= '<tr>'
  90. . '<td data-label="Name">' . esc_html( $name ) . '</td>'
  91. . '<td data-label="Location">' . esc_html( $loc ) . '</td>'
  92. . '<td data-label="Website">' . $weblink . '</td>'
  93. . '<td data-label="Message">' . nl2br( esc_html( $msg ) ) . '</td>'
  94. . '<td data-label="Date">' . esc_html( mysql2date( 'M Y', $e['date_created'] ) ) . '</td>'
  95. . '</tr>';
  96. }
  97. if ( $rows === '' ) {
  98. return '';
  99. }
  100. return '<div class="dw-guestbook">'
  101. . '<h3 class="dw-guestbook-title">Guest Book</h3>'
  102. . '<figure class="wp-block-table is-style-stripes"><table>'
  103. . '<thead><tr>'
  104. . '<th scope="col">Name</th><th scope="col">Location</th><th scope="col">Website</th>'
  105. . '<th scope="col">Message</th><th scope="col">Date</th>'
  106. . '</tr></thead>'
  107. . '<tbody>' . $rows . '</tbody>'
  108. . '</table></figure></div>';
  109. }
  110. function dw_guestbook_once() {
  111. static $done = false;
  112. if ( $done ) {
  113. return '';
  114. }
  115. $done = true;
  116. return dw_guestbook();
  117. }
  118. add_shortcode( 'dw_guestbook', 'dw_guestbook' );
  119. add_filter( 'render_block', 'dw_guestbook_render_block', 10, 2 );
  120. function dw_guestbook_render_block( $block_content, $block ) {
  121. if ( ! empty( $block['blockName'] ) && 'core/post-content' === $block['blockName'] && is_page( 'guests' ) ) {
  122. $block_content .= dw_guestbook_once();
  123. }
  124. return $block_content;
  125. }
  126. add_action( 'wp_enqueue_scripts', 'dw_scripts' );
  127. // On the search results page, rewrite each post-excerpt block to a snippet
  128. // centered on the match with the term(s) highlighted — mirroring the TIL column.
  129. // Hooked at render_block (not get_the_excerpt) because the core post-excerpt block
  130. // runs wp_trim_words() → wp_strip_all_tags() after the excerpt filters, which would
  131. // otherwise eat the <mark> tags. Titles are left alone (never marks <title>/nav).
  132. // When the term isn't in the body (title-only match) the block is left untouched.
  133. // Styled by `body.search mark`.
  134. function dw_search_excerpt_highlight( $block_content, $block, $instance ) {
  135. if ( is_admin() || ! is_search() ) {
  136. return $block_content;
  137. }
  138. $query = trim( get_search_query() );
  139. if ( '' === $query ) {
  140. return $block_content;
  141. }
  142. $post_id = isset( $instance->context['postId'] ) ? $instance->context['postId'] : 0;
  143. $post = $post_id ? get_post( $post_id ) : null;
  144. if ( ! $post instanceof WP_Post ) {
  145. return $block_content;
  146. }
  147. $terms = array_values( array_unique( array_filter( preg_split( '/\s+/', $query ) ) ) );
  148. $has_mb = function_exists( 'mb_stripos' );
  149. // Plain-text body (strip shortcodes, block markup, tags).
  150. $text = html_entity_decode(
  151. wp_strip_all_tags( strip_shortcodes( $post->post_content ) ),
  152. ENT_QUOTES,
  153. 'UTF-8'
  154. );
  155. $text = trim( preg_replace( '/\s+/', ' ', $text ) );
  156. // Locate the first term; bail (leave the intro excerpt) if it's not in the body.
  157. $pos = false;
  158. foreach ( $terms as $t ) {
  159. $p = $has_mb ? mb_stripos( $text, $t ) : stripos( $text, $t );
  160. if ( false !== $p ) { $pos = $p; break; }
  161. }
  162. if ( false === $pos || '' === $text ) {
  163. return $block_content;
  164. }
  165. // Window a snippet around the match, snapped to word boundaries.
  166. $len = $has_mb ? mb_strlen( $text ) : strlen( $text );
  167. $start = max( 0, $pos - 60 );
  168. $slice = $has_mb ? mb_substr( $text, $start, 220 ) : substr( $text, $start, 220 );
  169. if ( $start > 0 ) { $slice = preg_replace( '/^\S*\s/', '', $slice ); }
  170. if ( $start + 220 < $len ) { $slice = preg_replace( '/\s\S*$/', '', $slice ); }
  171. $snippet = esc_html( ( $start > 0 ? '… ' : '' ) . $slice . ( $start + 220 < $len ? ' …' : '' ) );
  172. foreach ( $terms as $t ) {
  173. $snippet = preg_replace( '/(' . preg_quote( $t, '/' ) . ')/i', '<mark>$1</mark>', $snippet );
  174. }
  175. // Swap the excerpt paragraph's inner content, preserving the block wrapper.
  176. return preg_replace_callback(
  177. '#(<p class="wp-block-post-excerpt__excerpt">).*?(</p>)#s',
  178. function ( $m ) use ( $snippet ) { return $m[1] . $snippet . $m[2]; },
  179. $block_content,
  180. 1
  181. );
  182. }
  183. add_filter( 'render_block_core/post-excerpt', 'dw_search_excerpt_highlight', 10, 3 );
  184. /**
  185. * Sticky posts on a custom Query Loop block (inherit:false — e.g. /desk/posts) get
  186. * prepended to EVERY page, not just the first, because the block query never sets
  187. * ignore_sticky_posts. Pin them to page 1 only: on later pages, stop prepending and
  188. * drop the stickies (already shown up top on page 1). The inherit:true search/archive
  189. * blocks handle stickies via the main query, so leave those alone.
  190. */
  191. function dw_query_loop_sticky_first_page_only( $query, $block ) {
  192. if ( ! empty( $block->context['query']['inherit'] ) ) {
  193. return $query;
  194. }
  195. $query_id = isset( $block->context['queryId'] ) ? $block->context['queryId'] : 0;
  196. $key = 'query-' . $query_id . '-page';
  197. $page = empty( $_GET[ $key ] ) ? 1 : absint( $_GET[ $key ] );
  198. if ( $page > 1 ) {
  199. $sticky = get_option( 'sticky_posts' );
  200. if ( ! empty( $sticky ) ) {
  201. $query['post__not_in'] = array_merge( isset( $query['post__not_in'] ) ? $query['post__not_in'] : array(), $sticky );
  202. }
  203. $query['ignore_sticky_posts'] = 1;
  204. }
  205. return $query;
  206. }
  207. add_filter( 'query_loop_block_query_vars', 'dw_query_loop_sticky_first_page_only', 10, 2 );
  208. function dw_scripts() {
  209. global $post;
  210. wp_enqueue_style( 'style-min', get_template_directory_uri() . '/v4-style.min.css', array(), filemtime( get_template_directory() . '/v4-style.min.css' ) );
  211. wp_deregister_script('jquery');
  212. if ( is_page('studio') ) {
  213. wp_enqueue_script( 'scriptmin', get_template_directory_uri() . '/js/v4-script.min.js', '', '', true );
  214. wp_enqueue_script( 'init', get_template_directory_uri() . '/js/studio.js', '', '', true );
  215. }
  216. elseif ( is_page('music') ) {
  217. wp_enqueue_script( 'scriptmin', get_template_directory_uri() . '/js/v4-script.min.js', '', '', true );
  218. wp_enqueue_script( 'last-fm', get_template_directory_uri() . '/js/music.js', '', '', true );
  219. }
  220. elseif ( is_page('art') ) {
  221. wp_enqueue_script( 'scriptmin', get_template_directory_uri() . '/js/v4-script.min.js', '', '', true );
  222. wp_enqueue_script( 'art', get_template_directory_uri() . '/js/art.js', '', '', true );
  223. }
  224. elseif ( is_page('contact') ) {
  225. require( get_template_directory() . '/inc/form.php' );
  226. //gravity_form_enqueue_scripts(1, false);
  227. wp_enqueue_script( 'scriptmin', get_template_directory_uri() . '/js/v4-script.min.js', '', '', true );
  228. wp_enqueue_style( 'gforms', get_template_directory_uri() . '/css/form.css');
  229. wp_enqueue_script('jquery', get_template_directory_uri() . '/js/jquery-3.1.0.min.js', array(), false, true);
  230. wp_enqueue_script('stripe', 'https://js.stripe.com/v2/', array(), '3', true);
  231. wp_enqueue_script( 'form', get_template_directory_uri() . '/js/contact.js', '', '', true );
  232. }
  233. elseif ( is_page('guests') ) {
  234. require( get_template_directory() . '/inc/form.php' );
  235. //gravity_form_enqueue_scripts(1, false);
  236. wp_enqueue_script( 'scriptmin', get_template_directory_uri() . '/js/v4-script.min.js', '', '', true );
  237. wp_enqueue_style( 'gforms', get_template_directory_uri() . '/css/form.css');
  238. wp_enqueue_script('jquery', get_template_directory_uri() . '/js/jquery-3.1.0.min.js', array(), false, true);
  239. wp_enqueue_script( 'form', get_template_directory_uri() . '/js/contact.js', '', '', true );
  240. }
  241. elseif ( is_page('contract') ) {
  242. require( get_template_directory() . '/inc/form.php' );
  243. //gravity_form_enqueue_scripts(1, false);
  244. wp_enqueue_script( 'scriptmin', get_template_directory_uri() . '/js/v4-script.min.js', '', '', true );
  245. wp_enqueue_style( 'gforms', get_template_directory_uri() . '/css/form.css');
  246. wp_enqueue_script('jquery', get_template_directory_uri() . '/js/jquery-3.1.0.min.js', array(), false, true);
  247. wp_enqueue_script( 'form', get_template_directory_uri() . '/js/contact.js', '', '', true );
  248. }
  249. elseif ( is_page('pay') ) {
  250. require( get_template_directory() . '/inc/form.php' );
  251. //gravity_form_enqueue_scripts(1, false);
  252. wp_enqueue_script( 'scriptmin', get_template_directory_uri() . '/js/v4-script.min.js', '', '', true );
  253. wp_enqueue_style( 'gforms', get_template_directory_uri() . '/css/form.css');
  254. wp_enqueue_script('jquery', get_template_directory_uri() . '/js/jquery-3.1.0.min.js', array(), false, true);
  255. }
  256. elseif ( is_page('analytics') ) {
  257. require( get_template_directory() . '/inc/analytics.php' );
  258. wp_enqueue_script( 'scriptmin', get_template_directory_uri() . '/js/v4-script.min.js', '', '', true );
  259. wp_enqueue_script( 'dashboardjs', get_template_directory_uri() . '/js/analytics.js', '', '', true );
  260. }
  261. elseif ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
  262. wp_enqueue_script( 'scriptmin', get_template_directory_uri() . '/js/v4-script.min.js', '', '', true );
  263. wp_enqueue_script( 'single', get_template_directory_uri() . '/js/single.js', '', '', true );
  264. wp_enqueue_script( 'comment-reply' );
  265. }
  266. elseif ( is_page('desk') || is_archive() || is_search() ) {
  267. wp_enqueue_script( 'jquery', get_template_directory_uri() . '/js/v4-script.min.js', '', '', true );
  268. wp_enqueue_script( 'desk', get_template_directory_uri() . '/js/desk.js', '', '', true );
  269. if ( is_page('desk') ) {
  270. // Lychee photo-stream embed (davidwindham.com/photo). The config <div> lives
  271. // in page-desk.php; this loads the embed lib. Not on archive/search (which
  272. // share this branch), hence the is_page guard.
  273. wp_enqueue_style( 'lychee-embed', 'https://davidwindham.com/photo/embed/lychee-embed.css', array(), '1.0.0' );
  274. wp_enqueue_script( 'lychee-embed', 'https://davidwindham.com/photo/embed/lychee-embed.js', array(), '1.0.0', true );
  275. }
  276. // --- TIL search (removable): merge the Docusaurus /til lunr results into the
  277. // WP search-results page. See js/til-search.js for the full removal checklist. ---
  278. if ( is_search() ) {
  279. wp_enqueue_script( 'lunr', get_template_directory_uri() . '/js/vendor/lunr.min.js', array(), '2.3.9', true );
  280. wp_enqueue_script( 'dw-til-search', get_template_directory_uri() . '/js/til-search.js', array( 'lunr' ), filemtime( get_template_directory() . '/js/til-search.js' ), true );
  281. wp_localize_script( 'dw-til-search', 'dwTilSearch', array(
  282. 'query' => get_search_query(),
  283. 'index' => home_url( '/til/lunr-index.json' ),
  284. 'docs' => home_url( '/til/search-doc.json' ),
  285. 'maxHits' => 10,
  286. ) );
  287. }
  288. // --- end TIL search ---
  289. }
  290. else {
  291. //wp_deregister_script('mediaelement');
  292. wp_enqueue_script( 'scriptmin', get_template_directory_uri() . '/js/v4-script.min.js', '', '', true );
  293. wp_enqueue_script( 'single', get_template_directory_uri() . '/js/single.js', '', '', true );
  294. wp_enqueue_script( 'mediaelement-me', get_template_directory_uri() .'/js/mediaelement-and-player.min.js', '', '', true);
  295. wp_enqueue_script( 'mediaelement-wp', get_template_directory_uri() .'/js/wp-mediaelement.min.js', '', '', true);
  296. }
  297. }
  298. // davo-bot 2000 — AI ask widget. Printed directly on wp_footer (like dw_analytics)
  299. // rather than enqueued, because front-page.php skips wp_head() so wp_enqueue_scripts
  300. // never fires there. The loader pulls /ask/widget.js (proxied same-origin to ralph).
  301. // On the homepage, window.dwAsk.open = 'once' auto-opens the terminal on first visit.
  302. function dw_ask_widget() {
  303. if ( is_admin() ) return;
  304. $src = get_template_directory_uri() . '/js/ask-widget.js';
  305. $ver = filemtime( get_template_directory() . '/js/ask-widget.js' );
  306. $open = is_front_page() ? 'once' : '';
  307. ?>
  308. <script>window.dwAsk = {open:"<?php echo esc_js( $open ); ?>"};</script>
  309. <script src="<?php echo esc_url( $src . '?ver=' . $ver ); ?>"></script>
  310. <?php
  311. }
  312. add_action( 'wp_footer', 'dw_ask_widget' );
  313. // front-page.php is fully self-contained (no wp_head, hand-rolled critical CSS, zero
  314. // blocks/presets), but WordPress still injects theme.json's global styles — and its
  315. // body/h1-h6 element rules override the homepage's intended typography. Strip them on
  316. // the front page only; the rest of the site keeps them. (front-page.php skips wp_head,
  317. // so the global styles print during wp_footer — dequeue before that late-print runs.)
  318. function dw_no_global_styles_on_home() {
  319. if ( is_front_page() ) {
  320. wp_dequeue_style( 'global-styles' );
  321. wp_dequeue_style( 'global-styles-css-custom-properties' );
  322. }
  323. }
  324. add_action( 'wp_enqueue_scripts', 'dw_no_global_styles_on_home', 100 );
  325. add_action( 'wp_footer', 'dw_no_global_styles_on_home', 1 );
  326. // Dev only: on a real content save, ping BrowserSync's reload endpoint so the front-end
  327. // preview refreshes — DB content changes aren't visible to a filesystem watcher otherwise.
  328. // Hooks the REST save events the block editor actually fires (+ customizer + nav menus).
  329. // Gated to WP_DEBUG → no-op in production.
  330. if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
  331. function dw_bs_reload_on_save( $post = null ) {
  332. if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
  333. if ( $post && is_object( $post ) && $post->post_status === 'auto-draft' ) return;
  334. wp_remote_get( 'https://daw.stu:81/__browser_sync__?method=reload', array(
  335. 'blocking' => false,
  336. 'sslverify' => false,
  337. ) );
  338. }
  339. add_action( 'rest_after_insert_page', 'dw_bs_reload_on_save', 10, 3 );
  340. add_action( 'rest_after_insert_post', 'dw_bs_reload_on_save', 10, 3 );
  341. add_action( 'customize_save_after', 'dw_bs_reload_on_save', 10, 3 );
  342. add_action( 'wp_update_nav_menu', 'dw_bs_reload_on_save', 10, 3 );
  343. }
  344. add_action('wp_footer', 'dw_analytics', 22);
  345. function dw_analytics() { ?>
  346. <script>
  347. var _paq = window._paq = window._paq || [];
  348. /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
  349. _paq.push(['trackPageView']);
  350. _paq.push(['enableLinkTracking']);
  351. (function() {
  352. var u="https://davidawindham.com/wik/";
  353. _paq.push(['setTrackerUrl', u+'matomo.php']);
  354. _paq.push(['setSiteId', '1']);
  355. var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
  356. g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
  357. })();
  358. </script>
  359. <noscript><p><img src="https://davidawindham.com/wik/matomo.php?idsite=1&amp;rec=1" style="border:0;" alt="" /></p></noscript>
  360. <?php }
  361. ?>