Skip to content

Latest commit

 

History

History
104 lines (64 loc) · 11.6 KB

File metadata and controls

104 lines (64 loc) · 11.6 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

Sparkling — a free classic (non-block) WordPress theme by Colorlib, v2.6.0, derived from Underscores (_s) and built on Bootstrap 3.4.1. Text domain: sparkling. Verified against WordPress 7.0.2 on PHP 8.5.

This directory is a WordPress theme folder, not an application project: there is no package.json, composer.json, linter config, or test suite committed. Files ship exactly as they sit on disk.

Working on the theme

  • No build step. style.css and assets/css/*.css are edited directly — there is no SCSS source. The .min.css/.min.js files are committed alongside their unminified twins and must be regenerated by hand (npx terser in.js -c -m -o out.min.js) when the source changes — nothing does it automatically.
  • Which file is actually loaded matters. sparkling_scripts() (functions.php:311) enqueues the minified bootstrap.min.js and skip-link-focus-fix.min.js but the unminified functions.js; the Customizer enqueues unminified customizer.js. Check the enqueue before editing a .min file.
  • Line endings are CRLF throughout. Keep them — mixing produces noisy diffs and a PHPCS warning.
  • To run it, copy or symlink this folder into a WordPress install's wp-content/themes/.
  • Version lives in the style.css theme header and is read once into the SPARKLING_VERSION constant (functions.php:8), which cache-busts every theme asset. Bump the header and every asset URL follows — never hardcode a version in an enqueue. The About screen's Changelog tab renders changelog.txt from the theme root.

Security model

The theme was hardened in 2.5.0; keep these invariants when touching it.

  • Every AJAX handler needs a capability check and check_ajax_referer(). Do not hook privileged work to admin_initadmin-ajax.php fires admin_init before its authentication branch, so an admin_init callback that acts on $_GET is reachable by anonymous requests. That was a real unauthenticated front-page takeover in 2.4.11.
  • Colours printed into the inline <style> block must go through sparkling_css_color() (inc/extras.php:299), which re-validates at output time rather than trusting stored options. Customizer sanitizers reject invalid input, but options saved by older versions may still hold arbitrary text.
  • Anything rendering user-supplied data (tag names, display names, author bios, titles) must be escaped — contributors can otherwise store markup that runs for every visitor.
  • Widgets sanitize on save in update(); the layout metabox validates against $site_layout before writing post meta.

Architecture

Globals are populated on init, not at parse time

$site_layout and $options_categories are declared at the top of functions.php but filled by sparkling_init_globals() on init (functions.php:434). Two reasons, both of which bit the theme before: calling esc_html__() while functions.php is parsed triggers WordPress 6.7+'s _load_textdomain_just_in_time notice on every request, and get_categories() at parse time added a term query to every request including REST, cron and AJAX. Anything reading those globals must run on init or later.

Two parallel option stores — the biggest gotcha

Settings live in one of two places, and reading from the wrong one silently returns the default:

  1. Serialized sparkling option array (legacy Options Framework style) — the majority. Customizer settings are declared as 'sparkling[key]' with 'type' => 'option', and read with the theme's own helper of_get_option( 'key', $default ) (functions.php:472), which unpacks get_option( 'sparkling' ).
  2. Plain theme mods — only sparkling_excerpts and sparkling_page_comments. Declared without the sparkling[...] wrapper and read with get_theme_mod().

When adding an option, match the declaration style in inc/customizer.php to the read function in the template.

Dynamic CSS from options

Colors, typography, and other option-driven styles are not in style.css. get_sparkling_theme_options() (inc/extras.php:256) echoes a <style> block into wp_head built by concatenating option values into selectors. Styling that must respond to a Customizer setting goes there; static styling goes in style.css.

Layout resolution and the template wrapper split

get_layout_class() (functions.php:565) resolves layout by precedence: per-post site_layout meta (set by the metabox in inc/metaboxes.php) → WooCommerce page's woo_site_layout option → global site_layout option. It returns a CSS class (side-pull-left, side-pull-right, no-sidebar, full-width) applied to the .row in header.php:96.

The sidebar #secondary is always rendered; no-sidebar and full-width hide it purely via CSS (style.css:223). Column widths come from sparkling_main_content_bootstrap_classes() (functions.php:31).

Wrapper divs are split across three files, so page templates must close what they open:

  • header.php opens #page#content.container.main-content-area.row.main-content-inner
  • sidebar.php closes .main-content-inner, then emits #secondary
  • footer.php closes .row, .container, .site-content, #page

A template that calls get_sidebar() is balanced. A template that skips it must close .main-content-inner itself — see page-fullwidth.php:39. Getting this wrong breaks the page markup rather than failing loudly.

Customizer controls are all first-party now

The theme had no third-party framework as of 2.6.0. The bundled Epsilon framework (MachoThemes, abandoned upstream) was deleted: the theme used exactly one thing from it — a checkbox control, seven times — while its AJAX layer registered handlers with no nonce or capability check, including a dispatcher that called static methods named in $_POST.

Sparkling_Customize_Toggle_Control (inc/class-sparkling-customize-toggle-control.php) replaces it. It is presentation only — it renders a checkbox bound with $this->link(), and the value is stored and sanitised entirely by the WP_Customize_Setting it attaches to. That is why the swap changed no saved data, and it's the property to preserve if you add controls: put validation in the setting's sanitize_callback, never in the control.

Epsilon_Control_Toggle survives as a deprecated subclass alias so child themes that registered their own controls with it keep working. Don't use it in new code, and don't remove it without a major version.

Switch styling lives in assets/css/customizer.css, loaded only on customize_controls_enqueue_scripts.

Other structural pieces

  • Templates: index.php/archive.php/search.php dispatch to template-parts/content-{post-format}.php via get_template_part(); content.php is the fallback, with content-single.php, content-page.php, content-video.php, content-none.php alongside.
  • Navigation: WP_Bootstrap_Navwalker (inc/class-wp-bootstrap-navwalker.php) emits BS3 markup. sparkling_make_top_level_menu_clickable() (inc/extras.php:537) injects inline JS on wp_footer. Three menu locations: primary, footer-links, social-menu.
  • Customizer sections (all under the sparkling_main_options panel): content, slider, layout, call-to-action, typography, header, footer, social, archive.
  • Widgets: Sparkling_Social_Widget, Sparkling_Popular_Posts, Sparkling_Categories in inc/widgets/, registered in sparkling_widgets_init(). Seven widget areas: sidebar, 3 homepage, 3 footer.
  • Admin welcome screen: inc/welcome-screen/ adds an "About Sparkling" theme page; the recommended-plugins list is data in inc/welcome-screen/welcome-page-setup.php.
  • WooCommerce: support declared in sparkling_woo_setup(); is_it_woocommerce_page() and get_woocommerce_page_id() (functions.php:505) exist because layout resolution needs to identify WC pages including the shortcode-based cart/checkout.

Conventions

  • Every theme function is prefixed sparkling_, and template-level ones are wrapped in if ( ! function_exists( ... ) ) : ... endif; so child themes can override. Preserve this when adding functions.
  • WordPress coding standards formatting: tabs for indent, spaces inside parentheses, esc_html__() / esc_html_e() for output.
  • Reuse the existing sanitize callbacks (sparkling_sanitize_checkbox, _hexcolor, _nohtml, _number, _textarea, _slidecat, _layout, _typo_*) at inc/customizer.php:936 rather than writing new ones.
  • Assets are conditionally enqueued: flexslider CSS/JS only when is_home()/is_front_page() and the slider option is on; academicons only when its option is on. Keep new assets conditional the same way.
  • i18n: .po/.mo pairs plus sparkling.pot in languages/. wpml-config.xml whitelists exactly four option keys (w2f_cfa_text, w2f_cfa_button, w2f_cfa_link, custom_footer_text) as WPML admin texts — a new user-entered option that needs translating must be added there too.

Front-end JavaScript

assets/js/functions.js is plain DOM APIs, no jQuery, and is enqueued with no jQuery dependency, in the footer. It keeps window.SparklingIsMobile and window.generateMobileMenu as globals because child themes may call them, reproduces jQuery's swing easing so the scroll-to-top motion is unchanged, and honours prefers-reduced-motion.

Bootstrap's own JS still requires jQuery and still loads it — only theme-owned code dropped the dependency.

Do not mix Bootstrap majors. Until 2.5.0 the theme shipped Bootstrap 3.3.7 CSS with Bootstrap 4.0.0 JS against Bootstrap 3 markup. The mobile menu only opened because BS4's JS adds .show and BS3 has an unrelated .show utility (display:block !important) — so the collapse animation never ran, and .open state was hand-patched in functions.js. Both files are now stock 3.4.1 (which is also the CVE-2019-8331 fix).

Verifying changes

There is no committed test suite, but this theme is straightforward to verify and regressions here are visual and silent:

  • Run it on WordPress 7.0.2 / PHP 8.5 with WP_DEBUG and WP_DEBUG_LOG on and sweep every template (home, single, each post format, page, full-width, category, tag, author, date, search, empty search, attachment, 404, feeds). The target is zero notices — 2.5.0 achieves that.
  • Exercise the option-driven paths too; they are dormant by default and hid several bugs: slider on/off and slide-links on/off, call-to-action, all colour pickers, sticky header, academicons.
  • PHPCompatibilityWP at testVersion 8.5 reports 0 errors/0 warnings; keep it there.
  • The WordPress.Security.* PHPCS sniffs report ~56 remaining hits that are reviewed false positives: sparkling_css_color()-validated colours, core-supplied $args['before_widget'], wp_list_categories() output, and pre-escaped $time_string. Check new hits against that list before assuming they are noise.
  • A pixel diff across templates at desktop and mobile widths is the fastest way to prove a refactor changed nothing — every 2.5.0 change was verified at 0 changed pixels.