{"id":51496983,"url":"https://github.com/daun/statamic-cache-directives","last_synced_at":"2026-07-11T20:00:35.486Z","repository":{"id":369519752,"uuid":"1290100740","full_name":"daun/statamic-cache-directives","owner":"daun","description":"Parse conditional comments for dynamic fragments in cached Statamic pages","archived":false,"fork":false,"pushed_at":"2026-07-06T10:18:19.000Z","size":65,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-10T19:19:18.818Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/daun.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-05T15:24:15.000Z","updated_at":"2026-07-10T11:06:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/daun/statamic-cache-directives","commit_stats":null,"previous_names":["daun/statamic-cache-directives"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/daun/statamic-cache-directives","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daun%2Fstatamic-cache-directives","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daun%2Fstatamic-cache-directives/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daun%2Fstatamic-cache-directives/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daun%2Fstatamic-cache-directives/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/daun","download_url":"https://codeload.github.com/daun/statamic-cache-directives/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daun%2Fstatamic-cache-directives/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35374165,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-11T02:00:05.354Z","response_time":104,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-07T16:30:27.510Z","updated_at":"2026-07-11T20:00:35.301Z","avatar_url":"https://github.com/daun.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Statamic Cache Directives\n\nParse conditional HTML comment directives after Statamic has rendered a page, so cached pages can still include small dynamic fragments.\n\nThis package is a lightweight alternative to Statamic's [`nocache`](https://statamic.dev/tags/nocache) tag. It is useful for tiny auth-, role-, or request-dependent islands where you only need to keep or remove existing markup without the overhead of the nocache data/session pipeline.\n\nIt is implemented as a [static caching replacer](https://statamic.dev/advanced-topics/static-caching#replacers) for Statamic's half-measure static cache.\n\n## Installation\n\nInstall the package via composer:\n\n```bash\ncomposer require daun/statamic-cache-directives\n```\n\n## Registration\n\nEnable the replacer in `config/statamic/static_caching.php`.\n\n```diff\n+ use Daun\\StatamicCacheDirectives\\CacheDirectiveReplacer;\n\n  'replacers' =\u003e [\n      CsrfTokenReplacer::class,\n      NoCacheReplacer::class,\n+     CacheDirectiveReplacer::class,\n  ],\n```\n\n## Usage\n\nWrap markup in conditional comments. Matching blocks are kept when their expression evaluates to `true`; otherwise they are removed from the response.\n\n```html\n\u003c!--[if logged_in]--\u003e\n  \u003ca href=\"/account\"\u003eAccount\u003c/a\u003e\n\u003c!--[endif]--\u003e\n\n\u003c!--[if logged_out]\u003e\n  \u003ca href=\"/login\"\u003eLog in\u003c/a\u003e\n\u003c![endif]--\u003e\n```\n\nThe second format follows the [downlevel-hidden syntax](https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/ms537512(v=vs.85)#syntax-of-conditional-comments) for conditional comments.\n\n## Syntax\n\nExpressions use [Symfony Expression Language](https://symfony.com/doc/current/reference/formats/expression_language.html) syntax.\n\n### If\n\n```html\n\u003c!--[if logged_in]--\u003e\n  \u003cp\u003eVisible to signed-in users.\u003c/p\u003e\n\u003c!--[endif]--\u003e\n\n\u003c!--[if logged_in]\u003e\n  \u003cp\u003eVisible to signed-in users, hidden if unprocessed.\u003c/p\u003e\n\u003c![endif]--\u003e\n```\n\n### Unless\n\n```html\n\u003c!--[unless logged_in]--\u003e\n  \u003cp\u003eVisible to guests.\u003c/p\u003e\n\u003c!--[endunless]--\u003e\n\n\u003c!--[unless logged_in]\u003e\n  \u003cp\u003eVisible to guests, hidden if unprocessed.\u003c/p\u003e\n\u003c![endunless]--\u003e\n```\n\n### Not\n\nUse either `!` or `not`.\n\n```html\n\u003c!--[if !logged_in]--\u003e\n  \u003ca href=\"/login\"\u003eLog in\u003c/a\u003e\n\u003c!--[endif]--\u003e\n\n\u003c!--[if not super]--\u003e\n  \u003cp\u003eRegular user content.\u003c/p\u003e\n\u003c!--[endif]--\u003e\n```\n\n### And\n\nUse `and` or `\u0026\u0026`.\n\n```html\n\u003c!--[if logged_in and super]--\u003e\n  \u003ca href=\"/cp\"\u003eControl Panel\u003c/a\u003e\n\u003c!--[endif]--\u003e\n```\n\n### Or\n\nUse `or` or `||`.\n\n```html\n\u003c!--[if logged_out or super]--\u003e\n  \u003cscript src=\"/js/public-preview.js\"\u003e\u003c/script\u003e\n\u003c!--[endif]--\u003e\n```\n\n### Echo\n\nUse `echo` to print a variable value. Echo directives can be standalone or block-style. Output is escaped for html contexts.\n\n```html\n\u003ca href=\"/account\" data-cache=\"\u003c!--[echo cache_status]--\u003e\"\u003e\n  \u003c!--[echo account_label]--\u003e\n\u003c/a\u003e\n\n\u003c!--[echo cache_status]\u003eUnknown\u003c![endecho]--\u003e\n```\n\n### Raw\n\nUse `raw` to print a variable value without escaping. Use this only for values you fully control as printing untrusted data with `raw` is an XSS vector.\n\n```html\n\u003c!--[raw safe_svg_icon]--\u003e\n\n\u003c!--[raw safe_svg_icon]\u003eFallback\u003c![endraw]--\u003e\n```\n\n### Combined expressions\n\nUse parentheses to group subexpressions or override precedence.\n\n```html\n\u003c!--[if logged_out or (logged_in and super)]--\u003e\n  \u003ca href=\"/preview\"\u003ePreview tools\u003c/a\u003e\n\u003c!--[endif]--\u003e\n\n\u003c!--[if (logged_out or super) and has_preview]--\u003e\n  \u003ca href=\"/preview\"\u003ePreview tools\u003c/a\u003e\n\u003c!--[endif]--\u003e\n\n\u003c!--[if !(logged_in and super)]--\u003e\n  \u003cp\u003eVisible unless signed in as a super admin.\u003c/p\u003e\n\u003c!--[endif]--\u003e\n```\n\n### Nested data and object methods\n\nUse `[]` for array keys and `.` for object properties or methods.\n\n```html\n\u003c!--[if cart[\"totals\"][\"items\"] \u003e 0 and customer.canCheckout()]--\u003e\n  \u003ca href=\"/checkout\"\u003eCheckout (\u003c!--[echo cart[\"totals\"][\"items\"]]--\u003e)\u003c/a\u003e\n\u003c!--[endif]--\u003e\n```\n\nUnknown variable names throw an `InvalidArgumentException`, so typos fail loudly.\n\n## Built-in variables\n\nThese variables are available by default and can be used in expressions:\n\n- `logged_in`: Current Statamic user is authenticated.\n- `logged_out`: Current Statamic user is not authenticated.\n- `cp_access`: Current Statamic user has control panel access.\n- `super`: Current Statamic user is a super admin.\n\nAuthentication uses Statamic's configured control-panel guard: `config('statamic.users.guards.cp')`.\n\n## Real-world examples\n\n### Seed frontend auth state\n\n```html\n\u003cscript\u003e\n  window.app = window.app || {};\n  window.app.authenticated = false;\n\u003c/script\u003e\n\n\u003c!--[if logged_in]--\u003e\n  \u003cscript\u003ewindow.app.authenticated = true;\u003c/script\u003e\n\u003c!--[endif]--\u003e\n```\n\n### Swap account navigation without `nocache`\n\n```html\n\u003cnav\u003e\n  \u003c!--[if logged_in]--\u003e\n    \u003ca href=\"/account\"\u003eAccount\u003c/a\u003e\n    \u003cform method=\"POST\" action=\"/logout\"\u003e\n      \u003cbutton type=\"submit\"\u003eLog out\u003c/button\u003e\n    \u003c/form\u003e\n  \u003c!--[endif]--\u003e\n\n  \u003c!--[if logged_out]--\u003e\n    \u003ca href=\"/login\"\u003eLog in\u003c/a\u003e\n    \u003ca href=\"/register\" class=\"button\"\u003eCreate account\u003c/a\u003e\n  \u003c!--[endif]--\u003e\n\u003c/nav\u003e\n```\n\n### Show edit links to super admins\n\n```html\n\u003c!--[if super]--\u003e\n  \u003caside class=\"admin-tools\"\u003e\n    \u003ca href=\"{{ edit_url }}\"\u003eEdit this page\u003c/a\u003e\n    \u003ca href=\"/cp/collections/pages\"\u003ePages\u003c/a\u003e\n  \u003c/aside\u003e\n\u003c!--[endif]--\u003e\n```\n\n### Hide conversion prompts from signed-in users\n\n```html\n\u003c!--[unless logged_in]--\u003e\n  \u003csection class=\"cta\"\u003e\n    \u003ch2\u003eSave your favourites\u003c/h2\u003e\n    \u003cp\u003eCreate an account to keep this list across devices.\u003c/p\u003e\n    \u003ca href=\"/register\" class=\"button\"\u003eSign up\u003c/a\u003e\n  \u003c/section\u003e\n\u003c!--[endunless]--\u003e\n```\n\n### Load control-panel JavaScript\n\n```html\n\u003c!--[if cp_access]--\u003e\n  \u003cscript type=\"module\" src=\"/build/admin-bar.js\"\u003e\u003c/script\u003e\n\u003c!--[endif]--\u003e\n```\n\n## Custom variables\n\nAdd custom variables during application boot using `variable()` or `variables()`. Values can be scalar values, arrays,\nobjects or closures. Closures are evaluated lazily when the variable is first encountered.\n\nUse closures for request-dependent values such as auth, session, request data, or GeoIP lookups. In long-lived worker runtimes\nsuch as FrankenPHP, Swoole, RoadRunner, or Laravel Octane, service providers can boot once per worker instead of once per request.\nPassing a direct value would capture the value at boot; passing a closure resolves it for the current request.\n\n```php\nuse Daun\\StatamicCacheDirectives\\CacheDirectiveReplacer;\n\nclass AppServiceProvider extends ServiceProvider\n{\n    public function boot(): void\n    {\n        CacheDirectiveReplacer::variable('editor', fn () =\u003e auth()-\u003euser()?-\u003ehasRole('editor') ?? false);\n        CacheDirectiveReplacer::variable('member', fn () =\u003e auth()-\u003euser()?-\u003eisInGroup('members') ?? false);\n    }\n}\n```\n\nFor bulk registration, use `variables()`.\n\n```php\nuse Daun\\StatamicCacheDirectives\\CacheDirectiveReplacer;\n\nclass AppServiceProvider extends ServiceProvider\n{\n    public function boot(): void\n    {\n        CacheDirectiveReplacer::variables([\n            'in_uk' =\u003e fn () =\u003e app(GeoIp::class)-\u003ecountryCode(request()-\u003eip()) === 'UK',\n            'has_cart' =\u003e fn () =\u003e (bool) session('cart.items'),\n        ]);\n    }\n}\n```\n\nThen use those variables in comments:\n\n```html\n\u003c!--[if editor]--\u003e\n  \u003ca href=\"{{ edit_url }}\"\u003eEdit\u003c/a\u003e\n\u003c!--[endif]--\u003e\n\n\u003c!--[if logged_out and has_cart]--\u003e\n  \u003cp\u003eCreate an account before checkout to save your order history.\u003c/p\u003e\n\u003c!--[endif]--\u003e\n\n\u003c!--[if member]--\u003e\n  \u003ca href=\"/members/downloads\"\u003eMember downloads\u003c/a\u003e\n\u003c!--[endif]--\u003e\n\n\u003c!--[unless in_uk]--\u003e\n  \u003cp\u003eThis feature is only available in the UK.\u003c/p\u003e\n\u003c!--[endunless]--\u003e\n```\n\n## Disabling a response\n\nIf a response contains this marker anywhere, directive parsing is skipped for the **whole response**. Use it as a kill switch on routes that must never be processed (for example email or preview endpoints).\n\n```html\n\u003c!--[cache-directives-disable]--\u003e\n```\n\n## Ignoring a range\n\nWrap a section in ignore markers to leave it **verbatim** while the rest of the response is still parsed. Directives inside the range are not processed, and the wrapper comments are removed from the output. This is useful for fragments that legitimately contain conditional comments (for example Outlook `\u003c!--[if mso]\u003e` markup) or documentation showing directive syntax.\n\n```html\n\u003c!--[cache-directives-ignore]--\u003e\n  \u003c!--[if mso]\u003e\u003ctable\u003e\u003ctr\u003e\u003ctd\u003eOutlook\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\u003c![endif]--\u003e\n\u003c!--[cache-directives-endignore]--\u003e\n```\n\n\u003e [!WARNING]\n\u003e Both markers control directive processing, so they must **never** originate from untrusted, user-controlled content. See [Security](#security) below.\n\n## Security\n\nThis replacer scans the **entire** cached HTML response for directive comments. Cached pages frequently contain user-generated content (comments, reviews, usernames, profile fields, reflected search queries, form echoes). If any of that content can contain the strings below, it can subvert directive processing:\n\n- **`\u003c!--[cache-directives-disable]--\u003e`** disables all directive processing for the page. An attacker who injects it can prevent auth-gated blocks such as `\u003c!--[if super]--\u003e...\u003c!--[endif]--\u003e` from being stripped, exposing that markup to every visitor.\n- **`\u003c!--[cache-directives-ignore]--\u003e` … `\u003c!--[cache-directives-endignore]--\u003e`** leaves an arbitrary range unprocessed, which can likewise expose auth-gated markup wrapped inside it.\n- **`\u003c!--[if ...]--\u003e` / `\u003c!--[echo ...]--\u003e`** markers let injected content pair with, hide, or reveal surrounding directive blocks.\n\nTo stay safe, **strip or neutralize directive comments from user-controlled content before it is rendered into a cacheable page**. For example, remove `\u003c!--[` sequences from user input, or escape them:\n\n```php\n// When rendering untrusted values into a page, neutralize directive markers.\n$safe = str_replace('\u003c!--[', '\u003c!--\u0026#91;', $userContent);\n```\n\nFailure handling:\n\n- A directive that fails to evaluate (for example an unknown variable) is **removed** (fails closed) rather than throwing, so a single malformed or injected directive cannot break the whole page. The error is reported to your logger.\n- When `app.debug` is enabled (local/dev), the failure is **rethrown** instead, so template typos surface loudly during development.\n\n## License\n\n[MIT](https://opensource.org/licenses/MIT)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaun%2Fstatamic-cache-directives","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdaun%2Fstatamic-cache-directives","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaun%2Fstatamic-cache-directives/lists"}