Skip to content

Twig Extensions

The ICoTA Members plugin provides custom Twig functions for accessing purchase and membership data in templates.

Overview

Location: src/twigextensions/PurchaseExtension.php

The PurchaseExtension registers custom Twig functions that allow you to query membership and purchase data directly from templates.

Available Functions

Purchase Functions

getUserPurchases

Get all purchases for a specific user.

twig
{% set purchases = getUserPurchases(currentUser) %}

{% for purchase in purchases %}
  <div class="purchase">
    <p>Amount: {{ purchase.amount / 100 }} {{ purchase.currency|upper }}</p>
    <p>Status: {{ purchase.status }}</p>
    <p>Date: {{ purchase.dateCreated|date('Y-m-d') }}</p>
  </div>
{% endfor %}

Parameters:

  • user (User) - Craft User element

Returns: Array of PurchaseRecord objects


getUserPurchasesById

Get purchases by user ID.

twig
{% set purchases = getUserPurchasesById(123) %}

Parameters:

  • userId (int) - User ID

Returns: Array of PurchaseRecord objects


getUserPurchasesByEmail

Get purchases by email address.

twig
{% set purchases = getUserPurchasesByEmail('user@example.com') %}

Parameters:

  • email (string) - Email address

Returns: Array of PurchaseRecord objects

Note: This function attempts to find the user first, then falls back to querying Stripe directly if no user is found.


getUserPurchaseSummary

Get aggregated purchase statistics for a user.

twig
{% set summary = getUserPurchaseSummary(currentUser) %}

<div class="purchase-summary">
  <p>Total Purchases: {{ summary.total_purchases }}</p>
  <p>Total Amount: {{ summary.total_amount / 100 }}</p>

  <h3>By Status</h3>
  {% for status, data in summary.by_status %}
    <p>{{ status|title }}: {{ data.count }} purchases ({{ data.amount / 100 }})</p>
  {% endfor %}

  <h3>By Currency</h3>
  {% for currency, data in summary.by_currency %}
    <p>{{ currency|upper }}: {{ data.count }} purchases ({{ data.amount / 100 }})</p>
  {% endfor %}
</div>

Parameters:

  • user (User) - Craft User element

Returns: Array with structure:

php
[
    'total_purchases' => 5,
    'total_amount' => 50000,  // in cents
    'by_status' => [
        'succeeded' => ['count' => 4, 'amount' => 45000],
        'refunded' => ['count' => 1, 'amount' => 5000]
    ],
    'by_currency' => [
        'usd' => ['count' => 5, 'amount' => 50000]
    ]
]

getUserPurchaseSummaryById

Get purchase summary by user ID.

twig
{% set summary = getUserPurchaseSummaryById(123) %}

Parameters:

  • userId (int) - User ID

Returns: Same structure as getUserPurchaseSummary


getUserPurchaseSummaryByEmail

Get purchase summary by email address.

twig
{% set summary = getUserPurchaseSummaryByEmail('user@example.com') %}

Parameters:

  • email (string) - Email address

Returns: Same structure as getUserPurchaseSummary


getCustomerPurchases

Get purchases for a specific Stripe customer ID.

twig
{% set purchases = getCustomerPurchases('cus_abc123') %}

Parameters:

  • stripeCustomerId (string) - Stripe customer ID

Returns: Array of PurchaseRecord objects


getCustomerPurchaseSummary

Get purchase summary for a Stripe customer ID.

twig
{% set summary = getCustomerPurchaseSummary('cus_abc123') %}

Parameters:

  • stripeCustomerId (string) - Stripe customer ID

Returns: Array with structure:

php
[
    'customer_id' => 'cus_abc123',
    'total_purchases' => 3,
    'total_amount' => 30000,
    'by_status' => [...],
    'by_currency' => [...]
]

Membership Functions

getUserMemberships

Get all memberships for a user (active, expired, and cancelled).

twig
{% set memberships = getUserMemberships(currentUser) %}

{% for membership in memberships %}
  <div class="membership">
    <p>Status: {{ membership.status|title }}</p>
    <p>Expires: {{ membership.expiryDate|date('Y-m-d') }}</p>
    {% if membership.chapterId %}
      {% set chapter = craft.app.sites.getSiteByUid(membership.chapterId) %}
      <p>Chapter: {{ chapter.name }}</p>
    {% endif %}
  </div>
{% endfor %}

Parameters:

  • user (User) - Craft User element

Returns: Array of MembershipRecord objects


getUserActiveMemberships

Get only active memberships for a user.

twig
{% set activeMemberships = getUserActiveMemberships(currentUser) %}

{% if activeMemberships|length > 0 %}
  {% set membership = activeMemberships|first %}
  <div class="alert alert-success">
    Your membership is active until {{ membership.expiryDate|date('F j, Y') }}
  </div>
{% endif %}

Parameters:

  • user (User) - Craft User element

Returns: Array of active MembershipRecord objects

Filters:

  • Status: active
  • Start date: null or in the past
  • Expiry date: in the future

userHasActiveMembership

Check if a user has any active membership (boolean check).

twig
{% if userHasActiveMembership(currentUser) %}
  <div class="member-content">
    {# Premium content for members #}
  </div>
{% else %}
  <div class="join-prompt">
    <a href="/join">Become a member</a>
  </div>
{% endif %}

Parameters:

  • user (User) - Craft User element

Returns: Boolean (true if user has active membership)


Debug Functions

Debugging Only

These functions should only be used during development and should be removed or disabled in production.

debugUserPurchases

Debug function to troubleshoot purchase lookup issues.

twig
{# Only use in development! #}
{% if craft.app.config.general.devMode %}
  {% set debug = debugUserPurchases(currentUser) %}
  {{ dump(debug) }}
{% endif %}

Parameters:

  • user (User) - Craft User element

Returns: Array with debug information:

php
[
    'user_id' => 123,
    'user_email' => 'user@example.com',
    'stripe_plugin_exists' => true,
    'stripe_customer_count' => 2,
    'stripe_customers' => [...],
    'total_purchases_in_db' => 150,
    'purchases_linked_to_user' => 5,
    'sample_purchases' => [...]
]

Security Note: This function exposes internal system information and should be disabled in production.


Usage Examples

Display Member Status Dashboard

twig
{% set user = currentUser %}

{# Check membership status #}
{% if userHasActiveMembership(user) %}
  {% set activeMemberships = getUserActiveMemberships(user) %}
  {% set membership = activeMemberships|first %}

  <div class="membership-status">
    <h2>Your Membership</h2>
    <p class="status active">Active</p>
    <p>Expires: {{ membership.expiryDate|date('F j, Y') }}</p>

    {% if membership.chapterId %}
      {% set chapter = craft.app.sites.getSiteByUid(membership.chapterId) %}
      <p>Chapter: {{ chapter.name }}</p>
    {% endif %}
  </div>
{% else %}
  <div class="membership-status">
    <h2>No Active Membership</h2>
    <p><a href="/join" class="button">Join Now</a></p>
  </div>
{% endif %}

Display Purchase History

twig
{% set user = currentUser %}
{% set purchases = getUserPurchases(user) %}
{% set summary = getUserPurchaseSummary(user) %}

<div class="purchase-history">
  <h2>Purchase History</h2>

  <div class="summary">
    <p>Total Purchases: {{ summary.total_purchases }}</p>
    <p>Total Spent: ${{ (summary.total_amount / 100)|number_format(2) }}</p>
  </div>

  <table>
    <thead>
      <tr>
        <th>Date</th>
        <th>Amount</th>
        <th>Status</th>
      </tr>
    </thead>
    <tbody>
      {% for purchase in purchases %}
        <tr>
          <td>{{ purchase.dateCreated|date('Y-m-d H:i') }}</td>
          <td>${{ (purchase.amount / 100)|number_format(2) }} {{ purchase.currency|upper }}</td>
          <td class="status-{{ purchase.status }}">{{ purchase.status|title }}</td>
        </tr>
      {% endfor %}
    </tbody>
  </table>
</div>

Conditional Content for Members

twig
{# Show different content based on membership status #}
{% if userHasActiveMembership(currentUser) %}
  {# Premium member content #}
  <div class="premium-content">
    <h2>Member Resources</h2>
    <ul>
      <li><a href="/members/downloads">Downloads</a></li>
      <li><a href="/members/webinars">Webinars</a></li>
      <li><a href="/members/forums">Forums</a></li>
    </ul>
  </div>
{% else %}
  {# Free/expired member content #}
  <div class="join-prompt">
    <h2>Become a Member</h2>
    <p>Get access to exclusive resources and benefits.</p>
    <a href="/join" class="button">Join Now</a>
  </div>
{% endif %}

Membership Expiration Notice

twig
{% set activeMemberships = getUserActiveMemberships(currentUser) %}
{% if activeMemberships|length > 0 %}
  {% set membership = activeMemberships|first %}
  {% set daysRemaining = ((membership.expiryDate|date('U') - now|date('U')) / 86400)|round %}

  {% if daysRemaining <= 30 %}
    <div class="alert alert-warning">
      <strong>Membership Expiring Soon!</strong>
      Your membership expires in {{ daysRemaining }} days.
      <a href="/renew">Renew Now</a>
    </div>
  {% endif %}
{% endif %}

Chapter-Specific Content

twig
{% set activeMemberships = getUserActiveMemberships(currentUser) %}
{% if activeMemberships|length > 0 %}
  {% set membership = activeMemberships|first %}

  {% if membership.chapterId %}
    {% set chapter = craft.app.sites.getSiteByUid(membership.chapterId) %}

    <div class="chapter-content">
      <h2>{{ chapter.name }} Chapter</h2>
      {# Display chapter-specific content #}
      {% set chapterEntries = craft.entries()
        .site(chapter)
        .section('chapterNews')
        .all() %}

      {% for entry in chapterEntries %}
        <article>
          <h3>{{ entry.title }}</h3>
          {{ entry.summary }}
        </article>
      {% endfor %}
    </div>
  {% endif %}
{% endif %}

Multi-Currency Purchase Display

twig
{% set summary = getUserPurchaseSummary(currentUser) %}

{% if summary.by_currency|length > 1 %}
  <div class="currency-breakdown">
    <h3>Purchases by Currency</h3>
    {% for currency, data in summary.by_currency %}
      <div class="currency-item">
        <strong>{{ currency|upper }}:</strong>
        {{ data.count }} purchases,
        Total: {{ (data.amount / 100)|number_format(2) }}
      </div>
    {% endfor %}
  </div>
{% endif %}

Purchase Status Breakdown

twig
{% set summary = getUserPurchaseSummary(currentUser) %}

<div class="status-breakdown">
  <h3>Purchases by Status</h3>

  {% for status, data in summary.by_status %}
    <div class="status-item status-{{ status }}">
      <span class="status-badge">{{ status|title }}</span>
      <span class="count">{{ data.count }}</span>
      <span class="amount">${{ (data.amount / 100)|number_format(2) }}</span>
    </div>
  {% endfor %}
</div>

Integration with Craft Elements

Using with Entry Queries

twig
{# Only show entries to active members #}
{% if userHasActiveMembership(currentUser) %}
  {% set entries = craft.entries()
    .section('memberContent')
    .all() %}

  {% for entry in entries %}
    {# Display member-only content #}
  {% endfor %}
{% endif %}

Using in Matrix Fields

twig
{# Conditional rendering in Matrix blocks #}
{% for block in entry.contentBuilder.all() %}
  {% if block.type == 'memberOnlySection' %}
    {% if userHasActiveMembership(currentUser) %}
      {{ block.content }}
    {% else %}
      <div class="locked-content">
        <p>This content is only available to members.</p>
      </div>
    {% endif %}
  {% else %}
    {{ block.content }}
  {% endif %}
{% endfor %}

Best Practices

1. Cache Results

twig
{# Cache membership check for performance #}
{% cache globally for 5 minutes if currentUser %}
  {% set hasActiveMembership = userHasActiveMembership(currentUser) %}
{% endcache %}

{% if hasActiveMembership %}
  {# Member content #}
{% endif %}

2. Null Checks

twig
{% if currentUser is not empty %}
  {% set memberships = getUserActiveMemberships(currentUser) %}
  {# Process memberships #}
{% endif %}

3. Error Handling

twig
{% set purchases = getUserPurchases(currentUser) %}

{% if purchases|length > 0 %}
  {# Display purchases #}
{% else %}
  <p>No purchases found.</p>
{% endif %}

4. Dev Mode Gating

twig
{# Only show debug info in dev mode #}
{% if craft.app.config.general.devMode %}
  {% set debug = debugUserPurchases(currentUser) %}
  {{ dump(debug) }}
{% endif %}

Performance Considerations

Query Optimization

The Twig functions call the underlying service methods which are optimized, but consider:

  1. Cache frequently accessed data:

    twig
    {% cache globally using key "user-#{currentUser.id}-membership" for 5 minutes %}
      {% set hasActiveMembership = userHasActiveMembership(currentUser) %}
    {% endcache %}
  2. Limit data retrieval:

    twig
    {# Only get what you need #}
    {% set hasActiveMembership = userHasActiveMembership(currentUser) %}
    {# Instead of getting all memberships just to check #}
  3. Avoid in loops:

    twig
    {# Bad - queries for each user #}
    {% for user in craft.users.all() %}
      {% set purchases = getUserPurchases(user) %}
    {% endfor %}
    
    {# Better - pre-fetch or use different approach #}

Security Notes

Production Checklist

Before deploying to production:

  1. ✅ Disable debugUserPurchases() function (wrap in dev mode check)
  2. ✅ Verify all user permission checks are in place
  3. ✅ Test with non-admin users
  4. ✅ Clear template caches

ICoTA Members Plugin Documentation