# Protecting a Theme

https://dev.wisecp.com/es/licensing-and-protecting-a-theme

A theme has no module class, and its templates cannot be encrypted. What can be protected, where the licence check goes, and what a template can honestly enforce.

## Overview

Only PHP can be encrypted. In a theme that is `hooks.php`, `theme.php`, and the views themselves only when the theme declares `engine: 'php'`.

> **No secrets and no decisions in templates**
> 
> A template prints. The value it compares against and the code that decides live in encrypted PHP.

## Prerequisites

- **A licence server of your own**: Where the question goes and what an answer means are yours: [Licensing a Marketplace Product](https://dev.wisecp.com/en/marketplace-product-licensing).
- **A hooks.php in the theme**: Optional for an ordinary theme, required for a licensed one. It is the only file that runs before the first page is shown.
- **Accepting that assets travel readable**: CSS, JavaScript and images ship as they are. They can be copied; a theme that runs on them alone cannot be installed.

## Structure

Three layers, and the first two are the same file.

| Layer | Where | What it does |
| --- | --- | --- |
| Check | `hooks.php`, encrypted | Asks your server. It is included once before the first page is shown, so an unlicensed copy is judged first |
| Publish | `hooks.php`, encrypted | Puts a seal into the template data when the answer was yes, and nothing when it was not |
| Enforce | every protected template | Compares against the literal seal it carries. The packager encrypts nothing here, so the comparison is the whole of it |

The file is included only if it exists. A deleted `hooks.php` publishes no seal, and every protected template fails closed.

## Walkthrough

### Put the check in hooks.php

1. Ask your server from `hooks.php`. It runs once, before the first page is shown, which is the only such place a theme has.
2. Cache the answer. A theme is on every page, so a check without a cache is a request per page view.
3. Decide there, in the encrypted file. What reaches the templates is a value, never a reason.

### Publish the seal from one handler

1. Register a single `filter:template.variables` handler for the theme and fold everything the theme needs into it.
2. Add the seal to the array it returns when the licence answered yes, and leave it out when it did not.
3. Never register a second handler. Only the last returned array survives, so a second registration silently drops the first one's data.

### Compare the value, not a flag

1. Compare against the literal string the template carries. A boolean is worthless here: anyone can register a hook of their own and publish a true.
2. Put the comparison in every template that must not run unlicensed, not in one shared wrapper. One check is one file to find and edit.
3. Make the licensed path produce something the page needs, so removing the check breaks the design instead of hiding a warning.

### Full protection means a PHP theme

1. Declare `engine: 'php'` in the manifest. The views become PHP and can be encrypted with the rest.
2. You give up the sandbox and take on escaping yourself, which is the trade the engine choice states plainly.
3. Assets stay readable either way.

## Reference

- **hooks.php**: Encryptable. The check, the cache and the publish live here.
- **theme.php**: Encryptable. Manifest: engine, meta and the settings schema.
- **views, engine php**: Encryptable, because they are PHP.
- **views, tpl or twig**: Not encryptable. Compiled to plain PHP on disk before any check can run.
- **assets**: Not encryptable. CSS, JavaScript, fonts and images ship as they are.

## Example

```php
\Hook::add('filter:template.variables', 1, function ($template, $data) {
    $answer = \License::remote_check('https://vendor.example/api/licence', [
        'product' => 'aurora-theme',
        'domain'  => \Utility::getDomain(),
    ]);

    $body = \Utility::jdecode((string) ($answer['body'] ?? ''), true);
    $ok   = $answer === null || ($body['data']['valid'] ?? false) === true;

    // The seal is added only on a yes. Everything else this theme publishes goes in the
    // same array: a second handler would drop all of it.
    if ($ok) $data['aurora_seal'] = 'a7f3c1d90e5b';

    return $data;
});
```

```smarty
{if $aurora_seal|default:'' !== 'a7f3c1d90e5b'}
    {include file='partials/unlicensed.tpl'}
{else}
    {* the section *}
{/if}
```

## Pitfalls

> **The seal is not a secret**
> 
> Anyone holding a licensed copy can watch what the licensed path publishes. It is a key with the lifetime of one version. Write a new one in every release, so a value taken from an old build dies at the first update.

> **One handler per theme**
> 
> Only the last returned array survives. A second registration drops the seal, the menus and everything else the first one published.

> **Weight belongs on the two real defences**
> 
> Make the licensed path carry something the page needs, and report installations so you can see where copies run. Encryption alone was never the theme's protection; the licence is.

## Related Articles

- [Licensing a Marketplace Product](https://dev.wisecp.com/en/marketplace-product-licensing)
- [Publishing a Product](https://dev.wisecp.com/en/publishing-a-marketplace-product)
- [Theme Hooks and Output Filters](https://dev.wisecp.com/en/theme-hooks-and-output-filters)
- [The Theme Engine](https://dev.wisecp.com/en/the-theme-engine)
