7 Commits

48 changed files with 1579 additions and 2 deletions
+12
View File
@@ -0,0 +1,12 @@
# MokoSuite standard ignores
.claude/
.mcp.json
TODO.md
vendor/
node_modules/
.env
*.min.css
*.min.js
.DS_Store
Thumbs.db
*.ppk
+21
View File
@@ -0,0 +1,21 @@
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
INGROUP: MokoSuiteChurch.Documentation
BRIEF: Version history using Keep a Changelog
-->
# Changelog
## [Unreleased]
### Added
- **Repository** -- initial repo creation with scaffolding
- **System Plugin** -- Extension class, service provider, ChurchHelper
- **SQL Schema** -- 8 tables: members, families, funds, contributions, ministries, ministry_members, attendance, facilities
- **Admin Component** -- 8 views with ListModels: dashboard, members, families, contributions, funds, ministries, attendance, facilities
- **Admin Templates** -- functional Joomla 6 list views with pagination and status badges
- **Webservices Plugin** -- 7 API routes: members, families, contributions, funds, ministries, attendance, facilities
- **Configuration** -- settings across basic, contributions, membership, attendance
- **Access Control** -- permissions for granular role-based access
- **Component Language Files** -- en-GB translations for menu, submenu, and extension manager
+31
View File
@@ -0,0 +1,31 @@
# MokoSuiteChurch
Member directory, contributions, ministries, attendance, facility booking for Joomla 6.
## Quick Reference
| Field | Value |
|---|---|
| **Package** | `pkg_mokosuitechurch` |
| **Layer** | 2 (requires: Client, CRM) |
| **Language** | PHP 8.3+ |
| **Branch** | develop on `dev`, merge to `main` (protected) |
| **Wiki** | [MokoSuiteChurch Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteChurch/wiki) |
## Architecture
Joomla **package** -- Layer 2 add-on. CRM contacts as church members, family grouping, contribution tracking, ministry management.
## Rules
- **Never commit** `.claude/`, `.mcp.json`, `TODO.md`, `*.min.css`/`*.min.js`
- **Attribution**: `Authored-by: Moko Consulting`
- **Workflow directory**: `.mokogitea/`
- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoCLI/wiki)
- **Changelog**: `[Unreleased]` only -- release system assigns versions
## Coding Standards
- PHP 8.3+ / Joomla 6 patterns
- `$this->getDatabase()` in models, `Factory::getContainer()->get(DatabaseInterface::class)` in helpers
- `Factory::getApplication()->getIdentity()` for user
+79 -1
View File
@@ -1,3 +1,81 @@
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
-->
# MokoSuite Church # MokoSuite Church
MokoSuite Church — church and congregation management for Joomla 6 Member directory, contributions, ministries, attendance, and facility booking for MokoSuite on Joomla 6.
## Overview
MokoSuiteChurch is a **Layer 2** extension in the MokoSuite platform, building on MokoSuiteClient (Layer 0) and MokoSuiteCRM (Layer 1) to provide complete church management. Members are CRM contacts -- no duplicate user tables.
## Package Contents
| Extension | Type | Description |
|---|---|---|
| `plg_system_mokosuitechurch` | System Plugin | Core helpers, SQL schema, service classes |
| `com_mokosuitechurch` | Component | Admin dashboard, list views, configuration |
| `plg_webservices_mokosuitechurch` | Webservices Plugin | REST API endpoints |
## Features
- **Member Directory** -- CRM contact-linked profiles with membership status lifecycle, family grouping, photo support
- **Family Management** -- household grouping with shared address and contact details
- **Contributions** -- tithe, offering, special, pledge, and in-kind tracking with multiple payment methods
- **Fund Accounting** -- general, building, missions, benevolence, and designated funds with goal tracking
- **Ministry Management** -- ministry teams with leader assignments, member roles, and meeting schedules
- **Attendance Tracking** -- service-level check-in for members and guests across event types
- **Facility Booking** -- room and space management with capacity, booking requirements, and hourly rates
- **REST API** -- full CRUD for members, families, contributions, funds, ministries, attendance, facilities
- **Access Control** -- granular permissions for role-based administration
## Database Schema
8 tables covering the full church management domain:
| Table | Purpose |
|---|---|
| `#__mokosuitechurch_members` | Member profiles linked to CRM contacts with family assignment |
| `#__mokosuitechurch_families` | Family/household grouping with shared contact info |
| `#__mokosuitechurch_funds` | Designated funds with type, goal, and tax-deductible flag |
| `#__mokosuitechurch_contributions` | Individual contributions with fund allocation and receipt tracking |
| `#__mokosuitechurch_ministries` | Ministry teams with type, leader, and schedule |
| `#__mokosuitechurch_ministry_members` | Ministry-member junction with roles |
| `#__mokosuitechurch_attendance` | Event attendance records for members and guests |
| `#__mokosuitechurch_facilities` | Church facilities with type, capacity, and booking rates |
## Requirements
- Joomla 6.x
- PHP 8.3+
- MokoSuiteClient (Layer 0)
- MokoSuiteCRM (Layer 1)
## Installation
Install via Joomla Extension Manager using the package file `pkg_mokosuitechurch.zip`. The package installs all three extensions (system plugin, component, webservices plugin) in the correct order.
Updates are delivered automatically via the [MokoGitea update server](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteChurch/updates.xml).
## Configuration
Settings are managed via the system plugin parameters:
| Fieldset | Key Settings |
|---|---|
| **Basic** | Church name, denomination, timezone |
| **Contributions** | Fiscal year start month, default fund, receipt format |
| **Membership** | Member ID prefix, auto-assign family |
| **Attendance** | Track attendance toggle, service types |
## License
GNU General Public License v3.0 or later.
## Links
- [Documentation](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteChurch/wiki)
- [Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteChurch/issues)
- [MokoSuite Platform](https://mokoconsulting.tech)
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<access component="com_mokosuitechurch">
<section name="component">
<action name="core.admin" title="JACTION_ADMIN" />
<action name="core.options" title="JACTION_OPTIONS" />
<action name="core.manage" title="JACTION_MANAGE" />
<action name="core.create" title="JACTION_CREATE" />
<action name="core.delete" title="JACTION_DELETE" />
<action name="core.edit" title="JACTION_EDIT" />
<action name="church.members.manage" title="Manage Members" />
<action name="church.families.manage" title="Manage Families" />
<action name="church.contributions.manage" title="Manage Contributions" />
<action name="church.funds.manage" title="Manage Funds" />
<action name="church.ministries.manage" title="Manage Ministries" />
<action name="church.attendance.manage" title="Manage Attendance" />
<action name="church.facilities.manage" title="Manage Facilities" />
<action name="church.reports" title="View Reports" />
<action name="church.settings" title="Manage Settings" />
</section>
</access>
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<config>
<fieldset name="basic" label="Church Defaults">
<field name="church_name" type="text" default="" label="Church Name" />
<field name="denomination" type="text" default="" label="Denomination" />
<field name="timezone" type="text" default="UTC" label="Timezone" />
</fieldset>
<fieldset name="contributions" label="Contributions">
<field name="fiscal_year_start_month" type="list" default="1" label="Fiscal Year Start Month">
<option value="1">January</option>
<option value="2">February</option>
<option value="3">March</option>
<option value="4">April</option>
<option value="5">May</option>
<option value="6">June</option>
<option value="7">July</option>
<option value="8">August</option>
<option value="9">September</option>
<option value="10">October</option>
<option value="11">November</option>
<option value="12">December</option>
</field>
<field name="default_fund" type="text" default="General" label="Default Fund" />
<field name="receipt_format" type="list" default="pdf" label="Receipt Format">
<option value="pdf">PDF</option>
<option value="email">Email</option>
<option value="both">Both</option>
</field>
</fieldset>
<fieldset name="membership" label="Membership">
<field name="member_id_prefix" type="text" default="MEM" label="Member ID Prefix" />
<field name="auto_assign_family" type="radio" default="1" label="Auto-Assign Family" class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
<fieldset name="attendance" label="Attendance">
<field name="track_attendance" type="radio" default="1" label="Track Attendance" class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="service_types" type="text" default="sunday_service,wednesday_service,bible_study" label="Service Types (comma-separated)" />
</fieldset>
</config>
@@ -0,0 +1,11 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
COM_MOKOSUITECHURCH="MokoSuite Church"
COM_MOKOSUITECHURCH_DASHBOARD="Dashboard"
COM_MOKOSUITECHURCH_MEMBERS="Members"
COM_MOKOSUITECHURCH_FAMILIES="Families"
COM_MOKOSUITECHURCH_CONTRIBUTIONS="Contributions"
COM_MOKOSUITECHURCH_FUNDS="Funds"
COM_MOKOSUITECHURCH_MINISTRIES="Ministries"
COM_MOKOSUITECHURCH_ATTENDANCE="Attendance"
COM_MOKOSUITECHURCH_FACILITIES="Facilities"
@@ -0,0 +1,4 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
COM_MOKOSUITECHURCH="MokoSuite Church"
COM_MOKOSUITECHURCH_XML_DESCRIPTION="Member directory, contributions, ministries, attendance, facility booking"
@@ -0,0 +1,28 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\Component\Router\RouterFactoryInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
return new class implements ServiceProviderInterface {
public function register(Container $container): void {
$container->set(ComponentInterface::class, function (Container $container) {
$c = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
$c->setMVCFactory($container->get(MVCFactoryInterface::class));
$c->setRouterFactory($container->get(RouterFactoryInterface::class));
return $c;
});
}
};
@@ -0,0 +1,18 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\BaseController;
class DisplayController extends BaseController
{
protected $default_view = 'churchdashboard';
}
@@ -0,0 +1,36 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class AttendanceModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__mokosuitechurch_attendance', 'a'));
$query->order($db->escape($this->getState('list.ordering', 'a.created'))
. ' ' . $db->escape($this->getState('list.direction', 'DESC')));
return $query;
}
protected function populateState($ordering = 'a.created', $direction = 'DESC'): void
{
parent::populateState($ordering, $direction);
}
}
@@ -0,0 +1,22 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Moko\Plugin\System\MokoSuiteChurch\Helper\ChurchHelper;
class ChurchDashboardModel extends BaseDatabaseModel
{
public function getDashboard(): object
{
return ChurchHelper::getDashboard();
}
}
@@ -0,0 +1,36 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class ContributionsModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__mokosuitechurch_contributions', 'a'));
$query->order($db->escape($this->getState('list.ordering', 'a.created'))
. ' ' . $db->escape($this->getState('list.direction', 'DESC')));
return $query;
}
protected function populateState($ordering = 'a.created', $direction = 'DESC'): void
{
parent::populateState($ordering, $direction);
}
}
@@ -0,0 +1,36 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class FacilitiesModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__mokosuitechurch_facilities', 'a'));
$query->order($db->escape($this->getState('list.ordering', 'a.ordering'))
. ' ' . $db->escape($this->getState('list.direction', 'ASC')));
return $query;
}
protected function populateState($ordering = 'a.ordering', $direction = 'ASC'): void
{
parent::populateState($ordering, $direction);
}
}
@@ -0,0 +1,36 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class FamiliesModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__mokosuitechurch_families', 'a'));
$query->order($db->escape($this->getState('list.ordering', 'a.family_name'))
. ' ' . $db->escape($this->getState('list.direction', 'ASC')));
return $query;
}
protected function populateState($ordering = 'a.family_name', $direction = 'ASC'): void
{
parent::populateState($ordering, $direction);
}
}
@@ -0,0 +1,36 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class FundsModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__mokosuitechurch_funds', 'a'));
$query->order($db->escape($this->getState('list.ordering', 'a.ordering'))
. ' ' . $db->escape($this->getState('list.direction', 'ASC')));
return $query;
}
protected function populateState($ordering = 'a.ordering', $direction = 'ASC'): void
{
parent::populateState($ordering, $direction);
}
}
@@ -0,0 +1,51 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class MembersModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select([
'm.*',
'f.family_name',
])
->from($db->quoteName('#__mokosuitechurch_members', 'm'))
->join('LEFT', $db->quoteName('#__mokosuitechurch_families', 'f') . ' ON f.id = m.family_id');
$search = $this->getState('filter.search');
if ($search) {
$search = $db->quote('%' . $db->escape($search, true) . '%');
$query->where('(m.member_id_number LIKE ' . $search . ' OR m.name LIKE ' . $search . ' OR m.email LIKE ' . $search . ')');
}
$status = $this->getState('filter.membership_status');
if ($status) {
$query->where($db->quoteName('m.membership_status') . ' = ' . $db->quote($status));
}
$query->order($db->escape($this->getState('list.ordering', 'm.created'))
. ' ' . $db->escape($this->getState('list.direction', 'DESC')));
return $query;
}
protected function populateState($ordering = 'm.created', $direction = 'DESC'): void
{
parent::populateState($ordering, $direction);
}
}
@@ -0,0 +1,36 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class MinistriesModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__mokosuitechurch_ministries', 'a'));
$query->order($db->escape($this->getState('list.ordering', 'a.ordering'))
. ' ' . $db->escape($this->getState('list.direction', 'ASC')));
return $query;
}
protected function populateState($ordering = 'a.ordering', $direction = 'ASC'): void
{
parent::populateState($ordering, $direction);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\View\Attendance;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected array $items = [];
protected mixed $pagination = null;
protected mixed $state = null;
public function display($tpl = null): void
{
$this->items = $this->get('Items') ?: [];
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
ToolbarHelper::title('MokoSuite Church - Attendance');
parent::display($tpl);
}
}
@@ -0,0 +1,29 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\View\ChurchDashboard;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected object $dashboard;
public function display($tpl = null): void
{
$this->dashboard = $this->get('Dashboard');
ToolbarHelper::title('MokoSuite Church - Dashboard');
ToolbarHelper::preferences('com_mokosuitechurch');
parent::display($tpl);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\View\Contributions;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected array $items = [];
protected mixed $pagination = null;
protected mixed $state = null;
public function display($tpl = null): void
{
$this->items = $this->get('Items') ?: [];
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
ToolbarHelper::title('MokoSuite Church - Contributions');
parent::display($tpl);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\View\Facilities;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected array $items = [];
protected mixed $pagination = null;
protected mixed $state = null;
public function display($tpl = null): void
{
$this->items = $this->get('Items') ?: [];
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
ToolbarHelper::title('MokoSuite Church - Facilities');
parent::display($tpl);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\View\Families;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected array $items = [];
protected mixed $pagination = null;
protected mixed $state = null;
public function display($tpl = null): void
{
$this->items = $this->get('Items') ?: [];
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
ToolbarHelper::title('MokoSuite Church - Families');
parent::display($tpl);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\View\Funds;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected array $items = [];
protected mixed $pagination = null;
protected mixed $state = null;
public function display($tpl = null): void
{
$this->items = $this->get('Items') ?: [];
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
ToolbarHelper::title('MokoSuite Church - Funds');
parent::display($tpl);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\View\Members;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected array $items = [];
protected mixed $pagination = null;
protected mixed $state = null;
public function display($tpl = null): void
{
$this->items = $this->get('Items') ?: [];
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
ToolbarHelper::title('MokoSuite Church - Members');
parent::display($tpl);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteChurch\Administrator\View\Ministries;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected array $items = [];
protected mixed $pagination = null;
protected mixed $state = null;
public function display($tpl = null): void
{
$this->items = $this->get('Items') ?: [];
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
ToolbarHelper::title('MokoSuite Church - Ministries');
parent::display($tpl);
}
}
@@ -0,0 +1,12 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
?>
<div class="alert alert-info">Coming soon.</div>
@@ -0,0 +1,62 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
$d = $this->dashboard;
?>
<div class="row g-3 mb-4">
<div class="col-md-4">
<div class="card text-bg-primary">
<div class="card-body">
<h5 class="card-title">Total Members</h5>
<p class="display-6"><?php echo (int) $d->total_members; ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-bg-success">
<div class="card-body">
<h5 class="card-title">Active Members</h5>
<p class="display-6"><?php echo (int) $d->active_members; ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-bg-info">
<div class="card-body">
<h5 class="card-title">Families</h5>
<p class="display-6"><?php echo (int) $d->total_families; ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-bg-warning">
<div class="card-body">
<h5 class="card-title">YTD Contributions</h5>
<p class="display-6"><?php echo number_format((float) $d->total_contributions_ytd, 2); ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Ministries</h5>
<p class="display-6"><?php echo (int) $d->total_ministries; ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Avg Attendance (30d)</h5>
<p class="display-6"><?php echo (int) $d->avg_attendance; ?></p>
</div>
</div>
</div>
</div>
@@ -0,0 +1,12 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
?>
<div class="alert alert-info">Coming soon.</div>
@@ -0,0 +1,12 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
?>
<div class="alert alert-info">Coming soon.</div>
@@ -0,0 +1,12 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
?>
<div class="alert alert-info">Coming soon.</div>
@@ -0,0 +1,12 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
?>
<div class="alert alert-info">Coming soon.</div>
@@ -0,0 +1,67 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Router\Route;
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
$statusBadges = [
'active' => 'success',
'inactive' => 'secondary',
'visitor' => 'info',
'prospect' => 'warning',
'transferred' => 'dark',
'deceased' => 'dark',
];
?>
<form action="<?php echo Route::_('index.php?option=com_mokosuitechurch&view=members'); ?>" method="post" name="adminForm" id="adminForm">
<div id="j-main-container">
<table class="table table-striped">
<thead>
<tr>
<th style="width:1%">#</th>
<th><?php echo HTMLHelper::_('searchtools.sort', 'Member ID', 'm.member_id_number', $listDirn, $listOrder); ?></th>
<th>Status</th>
<th><?php echo HTMLHelper::_('searchtools.sort', 'Name', 'm.name', $listDirn, $listOrder); ?></th>
<th>Email</th>
<th>Phone</th>
<th>Family</th>
<th>Membership Date</th>
<th><?php echo HTMLHelper::_('searchtools.sort', 'Created', 'm.created', $listDirn, $listOrder); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($this->items as $i => $item) :
$badge = $statusBadges[$item->membership_status] ?? 'secondary';
?>
<tr>
<td><?php echo $this->pagination->getRowOffset($i); ?></td>
<td><strong><?php echo $this->escape($item->member_id_number); ?></strong></td>
<td><span class="badge bg-<?php echo $badge; ?>"><?php echo $this->escape($item->membership_status); ?></span></td>
<td><?php echo $this->escape($item->name); ?></td>
<td><?php echo $this->escape($item->email); ?></td>
<td><?php echo $this->escape($item->phone); ?></td>
<td><?php echo $this->escape($item->family_name ?? ''); ?></td>
<td><?php echo $item->membership_date ? HTMLHelper::_('date', $item->membership_date, 'Y-m-d') : '-'; ?></td>
<td><?php echo HTMLHelper::_('date', $item->created, 'Y-m-d H:i'); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php echo $this->pagination->getListFooter(); ?>
<input type="hidden" name="task" value="">
<input type="hidden" name="boxchecked" value="0">
<?php echo HTMLHelper::_('form.token'); ?>
</div>
</form>
@@ -0,0 +1,12 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
?>
<div class="alert alert-info">Coming soon.</div>
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<extension type="component" method="upgrade">
<name>MokoSuite Church</name>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<creationDate>2026-06-27</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting.</copyright>
<license>GPL-3.0-or-later</license>
<version>06.00.00</version>
<description>Member directory, contributions, ministries, attendance, facility booking</description>
<namespace path="src">Moko\Component\MokoSuiteChurch</namespace>
<administration>
<files folder="admin">
<filename>access.xml</filename>
<filename>config.xml</filename>
<folder>language</folder>
<folder>services</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="admin/language">
<language tag="en-GB">en-GB/com_mokosuitechurch.ini</language>
<language tag="en-GB">en-GB/com_mokosuitechurch.sys.ini</language>
</languages>
<menu>COM_MOKOSUITECHURCH</menu>
<submenu>
<menu link="option=com_mokosuitechurch&amp;view=churchdashboard">COM_MOKOSUITECHURCH_DASHBOARD</menu>
<menu link="option=com_mokosuitechurch&amp;view=members">COM_MOKOSUITECHURCH_MEMBERS</menu>
<menu link="option=com_mokosuitechurch&amp;view=families">COM_MOKOSUITECHURCH_FAMILIES</menu>
<menu link="option=com_mokosuitechurch&amp;view=contributions">COM_MOKOSUITECHURCH_CONTRIBUTIONS</menu>
<menu link="option=com_mokosuitechurch&amp;view=funds">COM_MOKOSUITECHURCH_FUNDS</menu>
<menu link="option=com_mokosuitechurch&amp;view=ministries">COM_MOKOSUITECHURCH_MINISTRIES</menu>
<menu link="option=com_mokosuitechurch&amp;view=attendance">COM_MOKOSUITECHURCH_ATTENDANCE</menu>
<menu link="option=com_mokosuitechurch&amp;view=facilities">COM_MOKOSUITECHURCH_FACILITIES</menu>
</submenu>
</administration>
</extension>
@@ -0,0 +1,2 @@
PLG_SYSTEM_MOKOSUITECHURCH="System - MokoSuite Church"
PLG_SYSTEM_MOKOSUITECHURCH_DESC="Member directory, contributions, ministries, attendance, facility booking"
@@ -0,0 +1,2 @@
PLG_SYSTEM_MOKOSUITECHURCH="System - MokoSuite Church"
PLG_SYSTEM_MOKOSUITECHURCH_DESC="Member directory, contributions, ministries, attendance, facility booking"
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuite Church</name>
<element>mokosuitechurch</element>
<author>Moko Consulting</author>
<creationDate>2026-06-27</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>06.00.00</version>
<php_minimum>8.3</php_minimum>
<description>PLG_SYSTEM_MOKOSUITECHURCH_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteChurch</namespace>
<files>
<folder>src</folder>
<folder>services</folder>
<folder>language</folder>
<folder>sql</folder>
</files>
<languages folder="language">
<language tag="en-GB">en-GB/plg_system_mokosuitechurch.ini</language>
<language tag="en-GB">en-GB/plg_system_mokosuitechurch.sys.ini</language>
</languages>
<install><sql><file driver="mysql" charset="utf8">sql/install.mysql.sql</file></sql></install>
<uninstall><sql><file driver="mysql" charset="utf8">sql/uninstall.mysql.sql</file></sql></uninstall>
<config>
<fields name="params">
<fieldset name="basic" label="Church Defaults">
<field name="church_name" type="text" default="" label="Church Name" />
<field name="denomination" type="text" default="" label="Denomination" />
<field name="timezone" type="text" default="UTC" label="Timezone" />
</fieldset>
<fieldset name="contributions" label="Contributions">
<field name="fiscal_year_start_month" type="list" default="1" label="Fiscal Year Start Month">
<option value="1">January</option>
<option value="2">February</option>
<option value="3">March</option>
<option value="4">April</option>
<option value="5">May</option>
<option value="6">June</option>
<option value="7">July</option>
<option value="8">August</option>
<option value="9">September</option>
<option value="10">October</option>
<option value="11">November</option>
<option value="12">December</option>
</field>
<field name="default_fund" type="text" default="General" label="Default Fund" />
<field name="receipt_format" type="list" default="pdf" label="Receipt Format">
<option value="pdf">PDF</option>
<option value="email">Email</option>
<option value="both">Both</option>
</field>
</fieldset>
<fieldset name="membership" label="Membership">
<field name="member_id_prefix" type="text" default="MEM" label="Member ID Prefix" />
<field name="auto_assign_family" type="radio" default="1" label="Auto-Assign Family" class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
<fieldset name="attendance" label="Attendance">
<field name="track_attendance" type="radio" default="1" label="Track Attendance" class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="service_types" type="text" default="sunday_service,wednesday_service,bible_study" label="Service Types (comma-separated)" />
</fieldset>
</fields>
</config>
</extension>
@@ -0,0 +1,33 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Moko\Plugin\System\MokoSuiteChurch\Extension\Church;
return new class implements ServiceProviderInterface
{
public function register(Container $container): void
{
$container->set(
PluginInterface::class,
function (Container $container) {
$dispatcher = $container->get(DispatcherInterface::class);
$plugin = new Church($dispatcher, (array) PluginHelper::getPlugin('system', 'mokosuitechurch'));
$plugin->setApplication(Factory::getApplication());
return $plugin;
}
);
}
};
@@ -0,0 +1,128 @@
CREATE TABLE IF NOT EXISTS `#__mokosuitechurch_members` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`contact_id` INT DEFAULT NULL,
`member_id_number` VARCHAR(20) NOT NULL DEFAULT '',
`family_id` INT UNSIGNED DEFAULT NULL,
`family_role` ENUM('head','spouse','child','other') NOT NULL DEFAULT 'head',
`name` VARCHAR(255) NOT NULL,
`email` VARCHAR(255) NOT NULL DEFAULT '',
`phone` VARCHAR(50) NOT NULL DEFAULT '',
`membership_status` ENUM('active','inactive','visitor','prospect','transferred','deceased') NOT NULL DEFAULT 'visitor',
`membership_date` DATE DEFAULT NULL,
`baptism_date` DATE DEFAULT NULL,
`photo` VARCHAR(500) NOT NULL DEFAULT '',
`notes` TEXT,
`published` TINYINT NOT NULL DEFAULT 1,
`created` DATETIME NOT NULL,
`created_by` INT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_contact` (`contact_id`),
KEY `idx_family` (`family_id`),
KEY `idx_status` (`membership_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuitechurch_families` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`family_name` VARCHAR(255) NOT NULL,
`address` VARCHAR(500) NOT NULL DEFAULT '',
`phone` VARCHAR(50) NOT NULL DEFAULT '',
`email` VARCHAR(255) NOT NULL DEFAULT '',
`notes` TEXT,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuitechurch_funds` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`description` TEXT,
`fund_type` ENUM('general','building','missions','benevolence','youth','music','designated','other') NOT NULL DEFAULT 'general',
`goal_amount` DECIMAL(12,2) DEFAULT NULL,
`current_amount` DECIMAL(12,2) NOT NULL DEFAULT 0.00,
`tax_deductible` TINYINT NOT NULL DEFAULT 1,
`published` TINYINT NOT NULL DEFAULT 1,
`ordering` INT NOT NULL DEFAULT 0,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_type` (`fund_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuitechurch_contributions` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`contribution_ref` VARCHAR(20) NOT NULL,
`member_id` INT UNSIGNED DEFAULT NULL,
`family_id` INT UNSIGNED DEFAULT NULL,
`fund_id` INT UNSIGNED NOT NULL,
`amount` DECIMAL(10,2) NOT NULL,
`contribution_date` DATE NOT NULL,
`contribution_type` ENUM('tithe','offering','special','pledge','in_kind') NOT NULL DEFAULT 'offering',
`payment_method` ENUM('cash','check','card','online','bank_transfer','other') NOT NULL DEFAULT 'cash',
`check_number` VARCHAR(20) NOT NULL DEFAULT '',
`tax_deductible` TINYINT NOT NULL DEFAULT 1,
`receipt_sent` TINYINT NOT NULL DEFAULT 0,
`notes` TEXT,
`created` DATETIME NOT NULL,
`created_by` INT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_ref` (`contribution_ref`),
KEY `idx_member` (`member_id`),
KEY `idx_family` (`family_id`),
KEY `idx_fund` (`fund_id`),
KEY `idx_date` (`contribution_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuitechurch_ministries` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`description` TEXT,
`ministry_type` ENUM('worship','education','outreach','fellowship','youth','children','missions','admin','other') NOT NULL DEFAULT 'other',
`leader_member_id` INT UNSIGNED DEFAULT NULL,
`meeting_schedule` VARCHAR(255) NOT NULL DEFAULT '',
`photo` VARCHAR(500) NOT NULL DEFAULT '',
`published` TINYINT NOT NULL DEFAULT 1,
`ordering` INT NOT NULL DEFAULT 0,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_type` (`ministry_type`),
KEY `idx_leader` (`leader_member_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuitechurch_ministry_members` (
`ministry_id` INT UNSIGNED NOT NULL,
`member_id` INT UNSIGNED NOT NULL,
`role` ENUM('leader','co_leader','member','volunteer') NOT NULL DEFAULT 'member',
`joined_date` DATE DEFAULT NULL,
PRIMARY KEY (`ministry_id`, `member_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuitechurch_attendance` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`event_type` ENUM('sunday_service','wednesday_service','bible_study','youth_group','special_event','other') NOT NULL DEFAULT 'sunday_service',
`event_date` DATE NOT NULL,
`event_name` VARCHAR(255) NOT NULL DEFAULT '',
`member_id` INT UNSIGNED DEFAULT NULL,
`guest_name` VARCHAR(255) NOT NULL DEFAULT '',
`checked_in_at` DATETIME DEFAULT NULL,
`notes` TEXT,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_event` (`event_type`, `event_date`),
KEY `idx_member` (`member_id`),
KEY `idx_date` (`event_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuitechurch_facilities` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`description` TEXT,
`facility_type` ENUM('sanctuary','fellowship_hall','classroom','office','kitchen','gymnasium','outdoor','parking','other') NOT NULL DEFAULT 'classroom',
`capacity` INT UNSIGNED DEFAULT NULL,
`booking_required` TINYINT NOT NULL DEFAULT 0,
`hourly_rate` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`photo` VARCHAR(500) NOT NULL DEFAULT '',
`published` TINYINT NOT NULL DEFAULT 1,
`ordering` INT NOT NULL DEFAULT 0,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_type` (`facility_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -0,0 +1,8 @@
DROP TABLE IF EXISTS `#__mokosuitechurch_facilities`;
DROP TABLE IF EXISTS `#__mokosuitechurch_attendance`;
DROP TABLE IF EXISTS `#__mokosuitechurch_ministry_members`;
DROP TABLE IF EXISTS `#__mokosuitechurch_ministries`;
DROP TABLE IF EXISTS `#__mokosuitechurch_contributions`;
DROP TABLE IF EXISTS `#__mokosuitechurch_funds`;
DROP TABLE IF EXISTS `#__mokosuitechurch_families`;
DROP TABLE IF EXISTS `#__mokosuitechurch_members`;
@@ -0,0 +1,24 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Plugin\System\MokoSuiteChurch\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
class Church extends CMSPlugin implements SubscriberInterface
{
protected $autoloadLanguage = true;
public static function getSubscribedEvents(): array
{
return [];
}
}
@@ -0,0 +1,86 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
declare(strict_types=1);
namespace Moko\Plugin\System\MokoSuiteChurch\Helper;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
class ChurchHelper
{
public static function getDashboard(): object
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->select('COUNT(*)')
->from('#__mokosuitechurch_members')
->where($db->quoteName('published') . ' = 1'));
$totalMembers = (int) $db->loadResult();
$db->setQuery($db->getQuery(true)
->select('COUNT(*)')
->from('#__mokosuitechurch_members')
->where($db->quoteName('membership_status') . ' = ' . $db->quote('active'))
->where($db->quoteName('published') . ' = 1'));
$activeMembers = (int) $db->loadResult();
$db->setQuery($db->getQuery(true)
->select('COUNT(*)')
->from('#__mokosuitechurch_families'));
$totalFamilies = (int) $db->loadResult();
$db->setQuery($db->getQuery(true)
->select('COALESCE(SUM(' . $db->quoteName('amount') . '), 0)')
->from('#__mokosuitechurch_contributions')
->where('YEAR(' . $db->quoteName('contribution_date') . ') = YEAR(CURDATE())'));
$totalContributionsYtd = (float) $db->loadResult();
$db->setQuery($db->getQuery(true)
->select('COUNT(*)')
->from('#__mokosuitechurch_ministries')
->where($db->quoteName('published') . ' = 1'));
$totalMinistries = (int) $db->loadResult();
$db->setQuery($db->getQuery(true)
->select('COALESCE(AVG(cnt), 0)')
->from('(SELECT COUNT(*) AS cnt FROM ' . $db->quoteName('#__mokosuitechurch_attendance')
. ' WHERE ' . $db->quoteName('event_date') . ' >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)'
. ' GROUP BY ' . $db->quoteName('event_date') . ', ' . $db->quoteName('event_type') . ') AS sub'));
$avgAttendance = round((float) $db->loadResult(), 0);
return (object) [
'total_members' => $totalMembers,
'active_members' => $activeMembers,
'total_families' => $totalFamilies,
'total_contributions_ytd' => $totalContributionsYtd,
'total_ministries' => $totalMinistries,
'avg_attendance' => $avgAttendance,
];
}
public static function generateContributionRef(): string
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$prefix = 'CH';
do {
$ref = $prefix . strtoupper(bin2hex(random_bytes(4)));
$db->setQuery($db->getQuery(true)
->select('COUNT(*)')
->from('#__mokosuitechurch_contributions')
->where($db->quoteName('contribution_ref') . ' = ' . $db->quote($ref)));
} while ((int) $db->loadResult() > 0);
return $ref;
}
}
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<extension type="plugin" group="webservices" method="upgrade">
<name>Web Services - MokoSuite Church</name>
<element>mokosuitechurch</element>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<creationDate>2026-06-27</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting.</copyright>
<version>06.00.00</version>
<license>GPL-3.0-or-later</license>
<description>REST API routes for MokoSuite Church management operations</description>
<namespace path="src">Moko\Plugin\WebServices\MokoSuiteChurch</namespace>
<files><folder>src</folder><folder>services</folder></files>
</extension>
@@ -0,0 +1,24 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Moko\Plugin\WebServices\MokoSuiteChurch\Extension\MokoSuiteChurch;
return new class implements ServiceProviderInterface {
public function register(Container $container): void {
$container->set(PluginInterface::class, function (Container $container) {
return new MokoSuiteChurch($container->get(DispatcherInterface::class), (array) PluginHelper::getPlugin('webservices', 'mokosuitechurch'));
});
}
};
@@ -0,0 +1,36 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Plugin\WebServices\MokoSuiteChurch\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
class MokoSuiteChurch extends CMSPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return ['onBeforeApiRoute' => 'onBeforeApiRoute'];
}
public function onBeforeApiRoute(&$event): void
{
$router = $event->getArgument('router');
$opts = ['component' => 'com_mokosuitechurch'];
$router->createCRUDRoutes('v1/mokosuitechurch/members', 'members', $opts);
$router->createCRUDRoutes('v1/mokosuitechurch/families', 'families', $opts);
$router->createCRUDRoutes('v1/mokosuitechurch/contributions', 'contributions', $opts);
$router->createCRUDRoutes('v1/mokosuitechurch/funds', 'funds', $opts);
$router->createCRUDRoutes('v1/mokosuitechurch/ministries', 'ministries', $opts);
$router->createCRUDRoutes('v1/mokosuitechurch/attendance', 'attendance', $opts);
$router->createCRUDRoutes('v1/mokosuitechurch/facilities', 'facilities', $opts);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<extension type="package" method="upgrade">
<name>Package - MokoSuite Church</name>
<packagename>mokosuitechurch</packagename>
<version>06.00.00</version>
<creationDate>2026-06-27</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GNU General Public License version 3 or later; see LICENSE</license>
<description>Member directory, contributions, ministries, attendance, facility booking</description>
<php_minimum>8.3</php_minimum>
<dlid prefix="dlid=" suffix=""/>
<blockChildUninstall>true</blockChildUninstall>
<scriptfile>script.php</scriptfile>
<files folder="packages">
<file type="plugin" id="plg_system_mokosuitechurch" group="system">plg_system_mokosuitechurch.zip</file>
<file type="component" id="com_mokosuitechurch">com_mokosuitechurch.zip</file>
<file type="plugin" id="plg_webservices_mokosuitechurch" group="webservices">plg_webservices_mokosuitechurch.zip</file>
</files>
<updateservers>
<server type="extension" priority="1" name="Package - MokoSuite Church">https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteChurch/updates.xml</server>
</updateservers>
</extension>
+74
View File
@@ -0,0 +1,74 @@
<?php
/**
* @package MokoSuiteChurch
* @subpackage pkg_mokosuitechurch
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Log\Log;
/**
* Package installation script for MokoSuiteChurch.
*/
class Pkg_MokoSuiteChurchInstallerScript
{
public function postflight(string $type, InstallerAdapter $adapter): void
{
$this->warnMissingLicenseKey();
}
private function warnMissingLicenseKey(): void
{
try
{
$db = Factory::getDbo();
$app = Factory::getApplication();
$query = $db->getQuery(true)
->select([$db->quoteName('update_site_id'), $db->quoteName('extra_query')])
->from($db->quoteName('#__update_sites'))
->where('(' . $db->quoteName('name') . ' LIKE ' . $db->quote('%MokoSuiteChurch%')
. ' OR ' . $db->quoteName('location') . ' LIKE ' . $db->quote('%MokoSuiteChurch%') . ')')
->setLimit(1);
$db->setQuery($query);
$site = $db->loadObject();
if ($site)
{
$extraQuery = (string) ($site->extra_query ?? '');
if (!empty($extraQuery) && strpos($extraQuery, 'dlid=') !== false)
{
parse_str($extraQuery, $parsed);
if (!empty($parsed['dlid']))
{
return;
}
}
$editUrl = 'index.php?option=com_installer&task=updatesite.edit&update_site_id=' . (int) $site->update_site_id;
}
else
{
$editUrl = 'index.php?option=com_installer&view=updatesites';
}
$app->enqueueMessage(
'<strong>Moko Consulting License Key Required</strong> — '
. 'No download key is configured. Updates will not be available until a valid license key is entered. '
. '<a href="' . $editUrl . '" class="btn btn-sm btn-warning ms-2">Enter License Key</a>',
'warning'
);
}
catch (\Throwable $e)
{
// Silent — avoid breaking install if update_sites query fails
}
}
}