Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,5 @@ _modules/*

/results/
/phpunit*.xml

node_modules
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
# ChurchDB — CodeIgniter 4 (PHP 8.2 + Apache)
# Frontend: compile Tailwind v4 + daisyUI for scanned PHP views
FROM node:22-bookworm AS frontend
WORKDIR /build
COPY package.json package-lock.json ./
RUN npm ci
COPY resources ./resources
COPY app ./app
RUN mkdir -p public/assets/css \
&& npx @tailwindcss/cli -i resources/css/app.css -o public/assets/css/tailwind.css --minify

FROM php:8.2-apache-bookworm

RUN apt-get update && apt-get install -y --no-install-recommends \
Expand All @@ -23,6 +33,7 @@ COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-interaction --prefer-dist

COPY . .
COPY --from=frontend /build/public/assets/css/tailwind.css public/assets/css/tailwind.css
RUN composer dump-autoload --optimize --classmap-authoritative --no-dev \
&& chown -R www-data:www-data writable \
&& chmod -R 775 writable
Expand Down
93 changes: 47 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,69 +1,70 @@
# CodeIgniter 4 Application Starter
# ChurchDB

## What is CodeIgniter?
ChurchDB is a **public, open source** web application for managing people, groups, and related records for faith communities and similar organizations. It is **not limited to a single congregation or denomination**: you can adapt labels, configuration, and data to your own context.

CodeIgniter is a PHP full-stack web framework that is light, fast, flexible and secure.
More information can be found at the [official site](https://codeigniter.com).
The project is **built with Ethiopian churches in mind**—including **Amharic** in the interface, workflows that match common local church administration needs, and defaults that fit how many congregations in Ethiopia operate. Contributors and deployers anywhere are welcome; the stack is standard PHP and MySQL.

This repository holds a composer-installable app starter.
It has been built from the
[development repository](https://github.com/codeigniter4/CodeIgniter4).
## Features (high level)

More information about the plans for version 4 can be found in [CodeIgniter 4](https://forum.codeigniter.com/forumdisplay.php?fid=28) on the forums.
- Member registration and profiles, groups, payments, notes, and reporting-oriented views
- Bilingual-friendly UI (Amharic-focused strings with English fallbacks where configured)
- CodeIgniter 4 application structure (`public/` document root, `.env` configuration)

You can read the [user guide](https://codeigniter.com/user_guide/)
corresponding to the latest version of the framework.
## Requirements

## Installation & updates
- **PHP** 8.2+ with extensions such as **intl**, **mbstring**, and **mysqli** / **mysqlnd** (see the [CodeIgniter 4 user guide](https://codeigniter.com/user_guide/) for full details)
- **MySQL** or **MariaDB** for the application database
- **Composer** for PHP dependencies
- **Node.js 20+** (optional) — only if you change styles and need to rebuild **`public/assets/css/tailwind.css`** (Tailwind CSS v4 + [daisyUI](https://daisyui.com/))

`composer create-project codeigniter4/appstarter` then `composer update` whenever
there is a new release of the framework.
## Quick start (local)

When updating, check the release notes to see if there are any changes you might need to apply
to your `app` folder. The affected files can be copied or merged from
`vendor/codeigniter4/framework/app`.
1. Clone the repository and install dependencies:

## Setup
```bash
composer install
```

Copy `env` to `.env` and tailor for your app, specifically the baseURL
and any database settings.
2. Copy the environment template and edit database and `app.baseURL` (and other values as needed):

## Important Change with index.php
```bash
cp env .env
```

`index.php` is no longer in the root of the project! It has been moved inside the *public* folder,
for better security and separation of components.
3. Create the database and import schema (and optional seed data):

This means that you should configure your web server to "point" to your project's *public* folder, and
not to the project root. A better practice would be to configure a virtual host to point there. A poor practice would be to point your web server to the project root and expect to enter *public/...*, as the rest of your logic and the
framework are exposed.
```bash
mysql -u USER -p DATABASE < database/schema.sql
mysql -u USER -p DATABASE < database/seed.sql # optional demo data
```

**Please** read the user guide for a better explanation of how CI4 works!
4. Point your web server at the **`public/`** directory (not the project root), or use Docker (see below).

## Repository Management
## Frontend CSS (Tailwind + daisyUI)

We use GitHub issues, in our main repository, to track **BUGS** and to track approved **DEVELOPMENT** work packages.
We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss
FEATURE REQUESTS.
The admin UI still loads **Bootstrap** and **AdminLTE**; **Tailwind v4** and **daisyUI** are included so you can add new screens or components with utility classes and daisyUI patterns. After editing views under `app/Views/` or `resources/css/app.css`, rebuild:

This repository is a "distribution" one, built by our release preparation script.
Problems with it can be raised on our forum, or as issues in the main repository.
```bash
npm ci
npm run build:css
```

## Server Requirements
Use `npm run watch:css` during development. The Docker image runs this build automatically so the container always includes a fresh `tailwind.css`.

PHP version 8.2 or higher is required, with the following extensions installed:
## Docker

- [intl](http://php.net/manual/en/intl.requirements.php)
- [mbstring](http://php.net/manual/en/mbstring.installation.php)
A **`Dockerfile`** and **`docker-compose.yml`** are included for local stacks (app + MariaDB). See the comments in `docker-compose.yml` for ports, environment variables, and how to load `database/schema.sql` after the containers are up.

> [!WARNING]
> - The end of life date for PHP 7.4 was November 28, 2022.
> - The end of life date for PHP 8.0 was November 26, 2023.
> - The end of life date for PHP 8.1 was December 31, 2025.
> - If you are still using below PHP 8.2, you should upgrade immediately.
> - The end of life date for PHP 8.2 will be December 31, 2026.
Images are also built in CI and can be published to **GitHub Container Registry**; see `.github/workflows/docker-ghcr.yml`.

Additionally, make sure that the following extensions are enabled in your PHP:
## Framework

- json (enabled by default - don't turn it off)
- [mysqlnd](http://php.net/manual/en/mysqlnd.install.php) if you plan to use MySQL
- [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library
ChurchDB is built on [CodeIgniter 4](https://codeigniter.com). For framework-specific concepts (routing, security, localization), refer to the [official documentation](https://codeigniter.com/user_guide/).

## License

This project is open source under the **MIT License**; see the `LICENSE` file in the repository.

## Contributing

Issues and pull requests are welcome. Please keep changes focused and consistent with existing patterns in the codebase.
2 changes: 1 addition & 1 deletion app/Config/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class App extends BaseConfig
* something else. If you have configured your web server to remove this file
* from your site URIs, set this variable to an empty string.
*/
public string $indexPage = 'index.php';
public string $indexPage = '';

/**
* --------------------------------------------------------------------------
Expand Down
5 changes: 5 additions & 0 deletions app/Config/Routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@
$routes->get('/', 'Users::index');
$routes->post('users/login', 'Users::login');
$routes->get('rest/get_members', 'Rest::get_members');

$routes->match(['get', 'post'], 'admin/members', 'Admin::listmembers');
$routes->get('admin/groups', 'Admin::listgroups');
$routes->get('admin/groups/(:num)', 'Admin::listgroups');
$routes->get('admin/group/(:num)', 'Admin::groupdetails/$1');
86 changes: 67 additions & 19 deletions app/Controllers/Admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ public function savesetting() {
$this->cnfgModel->edit_one('church_name', $this->request->getPost('church_name'));
$this->cnfgModel->edit_one('default_password', $this->request->getPost('default_password'));

session()->set([
'system_name' => (string) $this->request->getPost('system_name'),
'system_name_short' => (string) $this->request->getPost('system_name_short'),
'church_name' => (string) $this->request->getPost('church_name'),
]);

session()->setFlashdata('success', 'የሲስተም ለውጦች በትክክል ተመዝግበዋል።');

return redirect()->to(site_url('admin/generalsetting'));
Expand All @@ -180,7 +186,7 @@ public function savesetting() {

public function personregistration() {

$data['active_menu'] = "personregistration";
$data['active_menu'] = "listmembers";
$data['job_types'] = $this->jobTypeModel->get_all();
$data['membership_causes'] = $this->membershipCauseModel->get_all();
$data['membership_levels'] = $this->membershipLevelModel->get_all();
Expand Down Expand Up @@ -261,7 +267,7 @@ public function clearfilter () {
'age_group' => NULL
);
session()->set('filtermember', $filtermember);
return redirect()->to(site_url('admin/listmembers'));
return redirect()->to(site_url('admin/members'));
}

public function savemember() {
Expand Down Expand Up @@ -339,7 +345,7 @@ public function editmember($id = NULL) {

public function listgroups() {

$config['base_url'] = base_url('admin/listgroups');
$config['base_url'] = base_url('admin/groups');
$config['total_rows'] = $this->groupModel->record_count();
$config['per_page'] = 5;
$config["uri_segment"] = 3;
Expand All @@ -360,8 +366,9 @@ public function listgroups() {
$config['last_tagl_close'] = "</li>";

$page = (int) ($this->request->getUri()->getSegment(3) ?? 0);
$data['links'] = $this->paginationByOffset('admin/listgroups', $config['per_page'], $config['total_rows'], $page);
$data['links'] = $this->paginationByOffset('admin/groups', $config['per_page'], $config['total_rows'], $page);
$data['active_menu'] = "groups";
$data['groups_total'] = (int) $config['total_rows'];
$data['groups'] = $this->groupModel->get_all('created', 'DESC', $config["per_page"], $page);
echo view('templates/header', $data);
echo view('list_groups');
Expand All @@ -370,12 +377,12 @@ public function listgroups() {

public function savegroup() {
$this->groupModel->add();
return redirect()->to(site_url('admin/listgroups'));
return redirect()->to(site_url('admin/groups'));
}

public function export_families_csv() {
session()->setFlashdata('error', 'Family export is not available.');
return redirect()->to(site_url('admin/listmembers'));
return redirect()->to(site_url('admin/members'));
}

public function deletemembersbackup($id) {
Expand Down Expand Up @@ -588,7 +595,7 @@ public function export_members_print() {

public function add_group_member() {
$this->groupMemberModel->add_group_member();
return redirect()->to(site_url('admin/groupdetails/'.$this->request->getPost('group_id')));
return redirect()->to(site_url('admin/group/'.$this->request->getPost('group_id')));
}

public function ajax_get_member() {
Expand Down Expand Up @@ -617,7 +624,7 @@ public function sunday_school_classes() {

public function remove_group_member($mid, $gid) {
$this->groupMemberModel->remove_group_member($mid, $gid);
return redirect()->to(site_url('admin/groupdetails/'.$gid));
return redirect()->to(site_url('admin/group/'.$gid));
}

public function listformelements() {
Expand All @@ -631,17 +638,58 @@ public function listformelements() {
echo view('templates/footer');
}

public function adminreport() {
$data['active_menu'] = "adminreport";
$data['church_name'] = $this->cnfgModel->get('church_name');
private function buildGeneralReportData(): array
{
$gender = $this->memberModel->gender_count();
$systemName = $this->cnfgModel->get('system_name');
if ($systemName === null || $systemName === '') {
$systemName = 'ChurchDB';
}
$churchName = (string) ($this->cnfgModel->get('church_name') ?? '');

$cu = session()->get('current_user');
$issuedTo = '—';
if (is_array($cu)) {
$first = trim((string) ($cu['firstname'] ?? ''));
$last = trim((string) ($cu['lastname'] ?? ''));
$name = trim($first . ' ' . $last);
if ($name !== '') {
$issuedTo = $name;
}
}

$generatedAt = date('d-m-Y H:i');

$reportStats = [
['label' => lang('label.members'), 'value' => $this->memberModel->record_count()],
['label' => lang('label.male'), 'value' => (int) ($gender['male'] ?? 0)],
['label' => lang('label.female'), 'value' => (int) ($gender['female'] ?? 0)],
['label' => lang('label.groups'), 'value' => $this->groupModel->record_count()],
['label' => 'የሲስተም ተጠቃሚዎች', 'value' => $this->userModel->users_count()],
['label' => 'የአገልግሎት ዘርፎች', 'value' => count($this->ministryModel->get_all())],
];

return [
'system_name' => $systemName,
'church_name' => $churchName,
'issued_to_name' => $issuedTo,
'generated_at' => $generatedAt,
'report_stats' => $reportStats,
];
}

public function report() {
$data = $this->buildGeneralReportData();
$data['active_menu'] = 'report';
echo view('templates/header', $data);
echo view('report');
echo view('templates/footer');
echo view('templates/footer');
}

public function adminreportprint() {
$data['church_name'] = $this->cnfgModel->get('church_name');
echo view('report_print', $data);
public function reportprint() {
$data = $this->buildGeneralReportData();
$data['print_autoprint'] = $this->request->getGet('autoprint') === '1';
echo view('report_print', $data);
}

public function memberdetailsprint($mid) {
Expand Down Expand Up @@ -777,10 +825,10 @@ public function savenote() {
public function deletegroup($gid) {
if($this->groupModel->deletegroup($gid)) {
session()->setFlashdata('success', 'ቡድኑ በትክክል ጠፍቷል።');
return redirect()->to(site_url('admin/listgroups'));
return redirect()->to(site_url('admin/groups'));
} else {
session()->setFlashdata('error', 'የቡድኑን መረጃ ማጥፋት አልተቻለም።');
return redirect()->to(site_url('admin/listgroups'));
return redirect()->to(site_url('admin/groups'));
}
}

Expand Down Expand Up @@ -843,10 +891,10 @@ public function membersexport() {
public function deletemember($id) {
if($this->memberModel->delete_member($id)) {
session()->setFlashdata('success', 'የምዕመኑ መረጃ በትክክል ጠፍቷል።');
return redirect()->to(site_url('admin/listmembers'));
return redirect()->to(site_url('admin/members'));
} else {
session()->setFlashdata('error', 'የምዕመኑ መረጃ ማጥፋት አልተቻለም።');
return redirect()->to(site_url('admin/listmembers'));
return redirect()->to(site_url('admin/members'));
}
}

Expand Down
21 changes: 21 additions & 0 deletions app/Models/Cnfg.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@ public function get(string $name): string
return $row !== null ? (string) $row['value'] : '';
}

/**
* @param list<string> $names
* @return array<string, string>
*/
public function getValues(array $names): array
{
$out = [];
foreach ($names as $n) {
$out[$n] = '';
}
if ($names === []) {
return $out;
}
$rows = $this->db->table('cnfg')->whereIn('name', $names)->get()->getResultArray();
foreach ($rows as $row) {
$out[$row['name']] = (string) $row['value'];
}

return $out;
}

public function edit_one($name, $value)
{
return $this->db->table('cnfg')->where('name', $name)->update(['value' => $value]);
Expand Down
22 changes: 9 additions & 13 deletions app/Views/backup_database.php
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@


<div class="content-wrapper">
<section class="content-header">
<h1>የመረጃቋት ባካፕ</h1>
<ol class="breadcrumb">
<li><a href="<?php echo base_url(); ?>"><i class="fa fa-users"></i> ዳሽቦርድ </a></li>
<li class="active"> የመረጃቋት ባካፕ </li>
</ol>
</section>
<!-- Main content -->
<div class="space-y-4">
<?= view('templates/partials/page_heading', [
'title' => 'የመረጃቋት ባካፕ',
'breadcrumbs_html' => '<ul><li><a href="' . esc(base_url(), 'url') . '" class="link link-hover"><i class="fa fa-users"></i> ዳሽቦርድ </a></li><li class="text-base-content/80"> የመረጃቋት ባካፕ </li></ul>',
]); ?>
<section class="content">

<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">የመረጃቋቱ ባካፕ ሲደረግ የሚከተሉትን መተግበር የተሻለ መንገድ ነው።</h3>
<div class="card border border-base-content/15 bg-base-100 shadow-md">
<div class="card-body border-b border-base-content/15 pb-3 mb-3">
<h3 class="card-title text-lg">የመረጃቋቱ ባካፕ ሲደረግ የሚከተሉትን መተግበር የተሻለ መንገድ ነው።</h3>
</div>
<div class="box-body">
<div class="card-body">
<ul>
<li>የመረጃቋቱን ቢያንስ በወር አንድ ጊዜ ባካፕ ማድረግ ተመራጭ መንገድ ነው። ተደጋጋሚ የባካፕ እቅድ ማውታት ደግሞ የተሻለ ነው።</li><br>
<li>የመረጃቋቱን ባካፕ ከሰርቨሩ ካወረዱ በኋላ፣ ሁለት ኮፒ ማዘጋጀት ያስፈልጋል። አንደኛውን ሊጠፋ በማይችል ቦታ ኮምፒውር ላይ ማስቀመጥ ሁለተኛውን ደግሞ ኢንተርኔት ላይ በኢሜልም ቢሆን ማስቀመጥ ያስፈልጋል።</li><br>
Expand Down
Loading