# `MishkaCmsCore.Helpers`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/_helpers.ex#L5)

Runtime helpers for extensions.

# `address_path`

```elixir
@spec address_path(String.t()) :: String.t()
```

A path as every address of a site writes it: no slash at its end and never two in a row, so the
home page is empty and the site's address stands alone. The query and the fragment, if any, are
kept as they are.

The one rule behind the sitemap, the canonical address, `og:url`, the `hreflang` alternates, the
JSON-LD `url`s and `site_url/2`; none of them writes an address of its own.

    address_path("/blog/")         #=> "/blog"
    address_path("//blog//post/")  #=> "/blog/post"
    address_path("/blog/?page=2")  #=> "/blog?page=2"
    address_path("/")              #=> ""

# `host_version`

```elixir
@spec host_version() :: String.t()
```

The version of the application this CMS runs in — the host's own, as its `mix.exs` declares it and
`config :mishka_cms_core, :host_app` names it — as a string, read when it is asked for.

What a page that wants to say which version it is drawn by calls, so no template writes one:

    MishkaCmsCore.Helpers.host_version()   #=> "0.0.1"

A template writes `v{MishkaCmsCore.Helpers.host_version()}`; the installer's own header reads the
same function. Empty when the application is not loaded, which only a test that never started it
can see.

# `path`

Generates a path with the correct script_name prefix for deployment.

Extracts the router from the socket/conn to determine the script_name prefix.
Does NOT verify the route exists - only ensures the path has the correct
prefix for your deployment configuration.

## Parameters

  * `socket_or_conn` - A `Phoenix.LiveView.Socket` or `Plug.Conn`
  * `path` - The path string (e.g., `"/admin/dashboard"`)

## Examples

    # In templates:
    <.link navigate={path(@socket, "/admin/dashboard")}>Dashboard</.link>

    # With dynamic segments:
    <.link navigate={path(@socket, "/posts/#{@post.id}")}>View Post</.link>

    # If deployed at example.com/cms/:
    path(@socket, "/admin")  # => "/cms/admin"

Provides path generation and other utilities that work at runtime
without compile-time dependencies on the main CMS router/endpoint.

## Path Generation

Since extensions compile before the main CMS, they cannot use `~p` sigils
which require compile-time knowledge of the router. Instead, use the
runtime `path/2` and `url/2` functions:

    # In your LiveView template:
    <.link navigate={path(@socket, "/admin/dashboard/blog")}>Blog</.link>

    # Or in your LiveView module:
    def handle_event("navigate", _, socket) do
      {:noreply, push_navigate(socket, to: path(socket, "/posts"))}
    end

## How It Works

These functions use `Phoenix.VerifiedRoutes.unverified_path/3` internally.
The router parameter is needed to get the **script_name prefix**, not for
route verification.

### What is script_name?

When your app is deployed under a subdirectory (e.g., `example.com/cms/`),
the script_name is `/cms`. The path helper ensures all generated paths
include this prefix:

    # App deployed at: https://example.com/cms/
    path(@socket, "/admin/dashboard")
    # => "/cms/admin/dashboard"

    # App deployed at root: https://example.com/
    path(@socket, "/admin/dashboard")
    # => "/admin/dashboard"

### Verified vs Unverified

| Feature                    | `~p"/path"`  | `path(@socket, "/path")` |
|----------------------------|--------------|--------------------------|
| Compile-time route check   | Yes          | No                       |
| Adds script_name prefix    | Yes          | Yes                      |
| Works in extensions        | No           | Yes                      |

Extensions cannot use `~p` because the router doesn't exist at compile time.
These helpers provide the same script_name handling at runtime.

> **Note:** These functions do NOT verify routes exist - that's your
> responsibility. They only ensure paths have the correct deployment prefix.

# `site_url`

```elixir
@spec site_url(String.t(), String.t()) :: String.t()
```

The absolute URL of `path` on a site's own address: its `host`, with the scheme and port the
endpoint answers on. Needs no conn or socket, so a sitemap, a robots.txt or an admin page can link
a site it is not being asked from. A port of 80, 443 or none is left out. `path` is written as
`address_path/1` writes it, so the home page is the address alone.

## Examples

    # In production:
    site_url("blog.example.com", "/sitemap.xml")
    # => "https://blog.example.com/sitemap.xml"
    site_url("blog.example.com", "/")
    # => "https://blog.example.com"

    # In development:
    site_url("blog.localhost", "/sitemap.xml")
    # => "http://blog.localhost:4000/sitemap.xml"

Not `url/2` for a site: that builds on the endpoint's own configured host, which is `localhost`
in production whatever site is asked.

# `url`

Generates a full URL with scheme, host, port, and script_name prefix.

Extracts the router from the socket/conn to build the complete URL.
Does NOT verify the route exists - only ensures the URL has the correct
configuration for your deployment.

## Parameters

  * `socket_or_conn` - A `Phoenix.LiveView.Socket` or `Plug.Conn`
  * `path` - The path string (e.g., `"/posts/123"`)

## Examples

    # Generate full URL:
    url(@socket, "/posts/#{@post.id}")
    # => "https://example.com/posts/123"

    # If deployed at example.com/cms/:
    url(@socket, "/admin")
    # => "https://example.com/cms/admin"

---

*Consult [api-reference.md](api-reference.md) for complete listing*
