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.
{% 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.
{% set purchases = getUserPurchasesById(123) %}Parameters:
userId(int) - User ID
Returns: Array of PurchaseRecord objects
getUserPurchasesByEmail
Get purchases by email address.
{% 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.
{% 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:
[
'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.
{% set summary = getUserPurchaseSummaryById(123) %}Parameters:
userId(int) - User ID
Returns: Same structure as getUserPurchaseSummary
getUserPurchaseSummaryByEmail
Get purchase summary by email address.
{% 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.
{% 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.
{% set summary = getCustomerPurchaseSummary('cus_abc123') %}Parameters:
stripeCustomerId(string) - Stripe customer ID
Returns: Array with structure:
[
'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).
{% 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.
{% 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).
{% 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.
{# 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:
[
'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
{% 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
{% 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
{# 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
{% 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
{% 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
{% 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
{% 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
{# 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
{# 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
{# 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
{% if currentUser is not empty %}
{% set memberships = getUserActiveMemberships(currentUser) %}
{# Process memberships #}
{% endif %}3. Error Handling
{% set purchases = getUserPurchases(currentUser) %}
{% if purchases|length > 0 %}
{# Display purchases #}
{% else %}
<p>No purchases found.</p>
{% endif %}4. Dev Mode Gating
{# 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:
Cache frequently accessed data:
twig{% cache globally using key "user-#{currentUser.id}-membership" for 5 minutes %} {% set hasActiveMembership = userHasActiveMembership(currentUser) %} {% endcache %}Limit data retrieval:
twig{# Only get what you need #} {% set hasActiveMembership = userHasActiveMembership(currentUser) %} {# Instead of getting all memberships just to check #}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:
- ✅ Disable
debugUserPurchases()function (wrap in dev mode check) - ✅ Verify all user permission checks are in place
- ✅ Test with non-admin users
- ✅ Clear template caches
Related Documentation
- Plugin Documentation - Main plugin features
- API Reference - PHP API methods
- Member Import Guide - Importing members
- Security Review - Security considerations