Merged
Conversation
- Replace phpName format (Pledge.Amount) with TableMap constants - Use PledgeTableMap::COL_PLG_AMOUNT, FamilyTableMap::COL_FAM_NAME, etc. - Ensures compatibility with Perpl ORM strict column name requirements - Applies to: DepositQuery, GroupQuery, Deposit, DepositService, FinancialService, PledgeQuery, finance-deposits API, search providers
…atibility)" This reverts commit f3409f5.
Replace hardcoded column name strings with TableMap constants for improved type safety and maintainability with Perpl ORM. - Add TableMap imports to 10 files - Use COL_* constants in withColumn() calls - Fix GroupQuery addForeignValueCondition (expects column name only)
- Fix isPluginActive() to check state file first, then fall back to SystemConfig
- Add inline settings forms to plugin management page (collapsible cards)
- Add POST /api/plugins/{pluginId}/settings endpoint for saving settings
- Remove Integration category from System Settings (moved to /plugins page)
- Delete plugin-settings.php view (settings now inline)
- Redirect /{pluginId}/settings route to main plugins page
- Update all plugin.json minimumCRMVersion to 7.0.0
- Fix GravatarPlugin: remove reference to non-existent config key
- Add sandboxed config access to AbstractPlugin (getConfigValue, setConfigValue) - Update PluginManager to use SystemConfig for plugin enabled state - Migrate all 5 core plugins to use sandboxed config access - Add plugin.* config entries to SystemConfig.php (IDs 3000-3041) - Update 7.0.0.sql migration to migrate legacy keys to new format - Fix legacy config references in MailChimpService, Notification, OpenLPNotification - Fix Header.php and analyticstracking.php to use new plugin config keys - Update plugin management UI with clear Enabled/Disabled badges - Remove file-based plugin-states.json (now using SystemConfig) - Bump version to 7.0.0
- Add getHeadContent() and getFooterContent() methods to PluginInterface - Add default empty implementations in AbstractPlugin - Add PluginManager::getPluginHeadContent/getPluginFooterContent() collectors - Initialize plugin system in Header.php for logged-in users - Inject plugin head content in <head> section - Inject plugin footer content before </body> - Move Google Analytics tracking to GoogleAnalyticsPlugin::getFooterContent() - Remove legacy analyticstracking.php (replaced by plugin system)
- Simplify to GA4-only (remove deprecated Universal Analytics) - Use official gtag.js snippet in head via getHeadContent() - Extract tracking code to templates/tracking-code.php - Add plugin help system with help.json for localization - Add help modal UI with i18next.t() translation support - Add locale-build-plugin-help.js to extract help terms - Update labels to 'GA4 Measurement ID' throughout - Delete legacy analyticstracking.php - Fix community plugins path in UI (remove src/ prefix)
- Convert GravatarPlugin to config-only pattern (removes dead server-side code) - Add getClientConfig() to PluginInterface for plugins to expose settings to JS - Add getPluginsClientConfig() to PluginManager to collect all plugin configs - Update Header.php to expose window.CRM.plugins with all active plugin configs - Fix avatar-loader.ts to properly use avatar-initials library: - useGravatarFallback: true for Gravatar defaults (identicon, monsterid, etc.) - useGravatarFallback: false for 'blank' (falls back to initials) - Add optionLabels support to plugin settings select dropdowns - Add help.json for Gravatar plugin with usage documentation - Default to 'blank' (initials) to match previous behavior - Keep bEnableGravatarPhotos for backward compatibility
- Move OpenLPNotification.php into plugin directory (src/plugins/core/openlp/src/) - Update to OpenLP API v2 (POST /api/v2/plugins/alerts) - API v1 removed Jan 2026 - Notification.php now uses PluginManager to access OpenLP plugin - Kiosk routes use plugin system to check OpenLP availability - Add sendAlert() method to OpenLPPlugin as entry point - Mark SystemConfig::hasValidOpenLPSettings() as deprecated - Add help.json with setup instructions and kiosk usage documentation - Clarify plugin is currently used for kiosk check-in notifications
- Add getManifest() and getVersion() to AbstractPlugin base class - Remove hardcoded getVersion() from OpenLPPlugin, GravatarPlugin, GoogleAnalyticsPlugin - Version is now only defined in plugin.json manifest - Bump OpenLP plugin to version 2.0.0 (API v2 migration)
- Remove hardcoded getVersion() from VonagePlugin (use AbstractPlugin manifest) - Update Notification.php to use VonagePlugin instead of direct Vonage SDK - Update kiosk device.php to check SMS via plugin system - Add @deprecated notice to SystemConfig::hasValidSMSServerSettings() - Set plugin.json settingsUrl to null (config-only pattern) - Create help.json with setup instructions and troubleshooting Vonage SMS is used only by kiosk check-in notifications.
Changed from overview/setup structure to summary/sections structure that the plugin management UI expects for displaying help content.
- Create external-backup plugin with WebDAV support for Nextcloud, ownCloud, etc. - Migrate backup settings from SystemConfig to plugin configuration - Add plugin settings page with connection test and manual backup - Update BackupJob.copyToWebDAV with improved cURL handling: - Memory-based upload to avoid rewind issues with auth negotiation - CURLAUTH_ANY for flexible authentication methods - User-Agent header and follow redirects for compatibility - URL normalization to ensure proper path construction - Update backup.php view to show plugin status and configuration links - Update system-database.php API to use plugin for remote backups - Update RestoreJob to disable plugin after restore - Add default getMenuItems() to AbstractPlugin base class - Add help.json documentation for plugin users
DawoudIO
approved these changes
Feb 7, 2026
- Update composer.json require and platform PHP to 8.3 - Update php-error.php to read version dynamically from composer.json - Update documentation (copilot-instructions, CONTRIBUTING, devcontainer README) - Update SECURITY.md supported versions table for 7.0+ - Update Cypress test to use regex for dynamic version check - Regenerate composer.lock for PHP 8.3 platform
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
## What Changed <!-- Short summary - what and why (not how) --> - Update composer.json require and platform PHP to 8.3 - Update php-error.php to read version dynamically from composer.json - Update documentation (copilot-instructions, CONTRIBUTING, devcontainer README) - Update SECURITY.md supported versions table for 7.0+ - Update Cypress test to use regex for dynamic version check - Regenerate composer.lock for PHP 8.3 platform - ## Type <!-- Check one --> - [ ] ✨ Feature - [ ] 🐛 Bug fix - [ ] ♻️ Refactor - [x] 🏗️ Build/Infrastructure - [x] 🔒 Security ## Testing <!-- How to verify this works --> ## Screenshots <!-- Only for UI changes - drag & drop images here --> ## Security Check <!-- Only check if applicable --> - [ ] Introduces new input validation - [ ] Modifies authentication/authorization - [ ] Affects data privacy/GDPR ### Code Quality - [ ] Database: Propel ORM only, no raw SQL - [ ] No deprecated attributes (align, valign, nowrap, border, cellpadding, cellspacing, bgcolor) - [ ] Bootstrap CSS classes used - [ ] All CSS bundled via webpack ## Pre-Merge - [ ] Tested locally - [ ] No new warnings - [ ] Build passes - [ ] Backward compatible (or migration documented)
Security fixes: - OpenLP: TLS verification enabled by default, configurable allowSelfSigned setting - plugins/index.php: Config-driven error display, sanitized error responses - mailchimp routes: Use SlimUtils::renderErrorJSON instead of throwing exceptions Performance: - mailchimp routes: O(N*M) → O(N+M) using array_flip for set membership Code standards: - family-view.php: Use InputUtils::escapeAttribute instead of htmlspecialchars - management.php: Use RedirectUtils::redirect instead of manual withHeader - Remove unused HttpInternalServerErrorException import Critical bug fix: - 7.0.0.sql: Fix INSERTs using non-existent columns (cfg_type, cfg_tooltip, etc. were removed in 2.6.0). Table only has cfg_id, cfg_name, cfg_value. Cleanup: - Remove duplicate test file cypress/e2e/ui/admin/custom-menus.spec.js - Remove unused helper functions from custom-links.spec.js - Remove duplicate gettext() tooltips from plugin ConfigItems (UI reads plugin.json) Documentation: - copilot-instructions.md: Add TLS/SSL, Algorithm Performance, Slim error middleware patterns - Pre-commit checklist: Add InputUtils, RedirectUtils, SlimUtils, TLS, O(N*M) checks"
Replace hardcoded SQL column names with TableMap constants in all withColumn() calls for type safety and schema refactor resilience: - DepositService: getDepositItemsByType() SUM and GROUP_CONCAT - Deposit: generateQBDepositSlip(), getTotalChecks(), getTotalCash() - FinancialService: getPayments(), getYtdPaymentTotal(), getYtdPledgeTotal(), getYtdDonorFamilyCount() Addresses PR #7948 review comments from copilot-pull-request-reviewer.
- symfony/dependency-injection: ^6.0.20 → ^6.4.0 - Update composer.lock with latest compatible versions: - psr/log: 1.1.4 → 3.0.2 - symfony/console: 6.4.32 → 7.4.4 - Add symfony/var-exporter 7.4.0 - Add src/generated-conf/ to .gitignore (Perpl auto-generated)
## Migrate from Propel ORM to Perpl ORM
### Summary
Migrate ChurchCRM from the unmaintained Propel ORM to **Perpl ORM**
(`perplorm/perpl`), an actively maintained fork with PHP 8.4+ support
and improved performance.
### Why This Migration?
**Propel ORM Issues:**
- No longer actively maintained
- PHP 8.4 deprecation warnings and compatibility issues
- No support for modern PHP features
**Perpl ORM Benefits:**
- Actively maintained fork of Propel2
- Full PHP 8.4+ compatibility
- 30-50% faster query building
- Drop-in replacement (same API patterns)
- Strict return types for lifecycle hooks
### Changes
#### Core ORM Migration
- Update `composer.json` to use `perplorm/perpl ^2.6.0`
- Move schema configuration from `src/propel/` to `orm/`
- Remove deprecated `src/propel/propel.php` and `propel.php.dist`
- Add new `orm/propel.php.dist` with Perpl configuration
- Update `Bootstrapper.php` for Perpl initialization
#### Model Lifecycle Hook Signatures (Perpl Requirement)
Perpl ORM enforces strict return types for model lifecycle hooks:
- **Pre-hooks** return `bool`: `preSave()`, `preInsert()`,
`preUpdate()`, `preDelete()`
- **Post-hooks** return `void`: `postSave()`, `postInsert()`,
`postUpdate()`, `postDelete()`
Updated models:
- [Deposit.php](src/ChurchCRM/model/ChurchCRM/Deposit.php)
- [Event.php](src/ChurchCRM/model/ChurchCRM/Event.php)
- [Family.php](src/ChurchCRM/model/ChurchCRM/Family.php)
- [Group.php](src/ChurchCRM/model/ChurchCRM/Group.php)
- [Person.php](src/ChurchCRM/model/ChurchCRM/Person.php)
- [Pledge.php](src/ChurchCRM/model/ChurchCRM/Pledge.php)
- [User.php](src/ChurchCRM/model/ChurchCRM/User.php)
#### TableMap Constants for withColumn()
Replace hardcoded SQL column names with TableMap constants for type
safety:
- [DepositQuery.php](src/ChurchCRM/model/ChurchCRM/DepositQuery.php)
- [GroupQuery.php](src/ChurchCRM/model/ChurchCRM/GroupQuery.php)
- [PledgeQuery.php](src/ChurchCRM/model/ChurchCRM/PledgeQuery.php)
-
[FinanceDepositSearchResultProvider.php](src/ChurchCRM/Search/FinanceDepositSearchResultProvider.php)
-
[FinancePaymentSearchResultProvider.php](src/ChurchCRM/Search/FinancePaymentSearchResultProvider.php)
- [DepositService.php](src/ChurchCRM/Service/DepositService.php)
- [FinancialService.php](src/ChurchCRM/Service/FinancialService.php)
- [finance-deposits.php](src/api/routes/finance/finance-deposits.php)
#### Kiosk Module Refactor
Move Kiosk TypeScript module back to vanilla JavaScript for stability:
- Remove `webpack/kiosk/` TypeScript files (~1,200 lines)
- Add `src/skin/js/Kiosk.js` and `src/skin/js/KioskJSOM.js`
- Update kiosk templates and routes for new JS structure
#### Database Map Loading
- Add [LoadDatabaseMap.php](src/Include/LoadDatabaseMap.php) for
explicit TableMap loading
- Update report files to use explicit map loading pattern
#### Documentation
- Update `.github/copilot-instructions.md` with Perpl ORM guidelines
- Document `withColumn()` differences and lifecycle hook requirements
### Files Changed
- **56 files** changed
- **1,764 insertions**, **1,589 deletions**
### Testing
All 175 API tests pass ✅
### Technical Notes
1. **addForeignValueCondition()**: Expects table name and column name as
separate arguments. Use `'lst_ID'` string, not
`ListOptionTableMap::COL_LST_ID` (which resolves to
`'list_lst.lst_ID'`).
2. **withColumn() Syntax**: Use raw SQL column names in `withColumn()`,
not phpNames:
```php
// ❌ Wrong
->withColumn('SUM(Pledge.Amount)', 'total')
// ✅ Correct
->withColumn('SUM(' . PledgeTableMap::COL_PLG_AMOUNT . ')', 'total')
- BackupJob: Use streaming upload (CURLOPT_PUT) to avoid memory exhaustion
- BackupJob: Dynamic User-Agent from VersionUtils instead of hardcoded
- plugins/index.php: Fix middleware LIFO order (CORS→Auth→Version)
- plugins/index.php: Add public /plugins/status/{pluginId} endpoint
- PersonView/FamilyView.js: Use public status endpoint for non-admin users
- PersonView.js: Use data-loading attribute for locale-independent detection
- PersonView.php: Add data-loading attribute to MailChimp loading cells
- tracking-code.php: Use InputUtils and json_encode for proper escaping
- GoogleAnalyticsPlugin: Use json_encode for safe JS string construction
- api/management.php: Return generic gettext() messages, not raw exceptions
- custom-links/routes.php: Use SlimUtils::renderErrorJSON instead of throw
- system-database.php: Use BackupType::FULL_BACKUP constant for default
- Remove HTML required attribute from settings (visual indicator remains)
- Add placeholder text 'Required' for required fields instead
- Add Reset button next to Save Settings for each plugin
- Add POST /plugins/api/plugins/{pluginId}/reset API endpoint
- Clear all plugin settings to empty strings on reset
- Add confirmation dialog before resetting
- Update form fields after successful reset
- PluginManager: Don't expose password values in getPluginSettingsWithValues() - Add 'hasValue' flag to indicate when password is configured - management.php: Show green checkmark when password is set - management.php: Update placeholder text for configured passwords - JavaScript: Only submit password if user entered a new value - Preserve existing password when field is left blank
MailChimp Plugin (XSS - Medium): - list-missing.php: Add escapeHtml() helper, escape email/name/status in DataTable - list-unsubscribed.php: Add escapeHtml() helper, escape email/name in DataTable - Use encodeURIComponent() for mailto: href values - Cast person ID to integer to prevent injection External Backup Plugin: - testConnection(): Add explicit TLS verification (secure by default) - Support allowSelfSigned setting for local networks - executeManualBackup(): Don't expose exception messages to users - Log detailed error server-side instead Vonage Plugin (Slim 4 compatibility): - Import SlimUtils for JSON responses - Replace Slim 3 withJson() with SlimUtils::renderJSON() - Fix return types to ResponseInterface
- Change startup/diagnostic logs to debug level (plugin loaded, booted, activated, deactivated, settings updated, 404 redirects) - Keep admin actions at info level (plugin enabled/disabled, settings reset) - Remove duplicate logging in API routes (already logged by PluginManager) - Remove TODO comment in MailChimpPlugin, update PHPDoc - Add plugin management API tests (17 tests) - Add plugin management UI tests (15 tests) - Update custom-links tests with proper setup/teardown hooks
## Summary
Implement a comprehensive **WordPress-style plugin system** for
ChurchCRM that enables modular, extensible functionality. This PR
migrates 7 existing integrations to the new plugin architecture and adds
a Plugin Management UI for administrators.
All plugins are tested and are working.
## Features
### Plugin System Core
- **PluginManager** - Discovery, loading, activation, and route
registration (static class)
- **AbstractPlugin** - Base class with sensible defaults and sandboxed
config access
- **PluginInterface** - Contract all plugins must implement
- **HookManager** - WordPress-style actions and filters system
- **Plugin Management UI** - Admin interface to enable/disable plugins
and configure settings inline
### Plugin Architecture
- Plugins stored in `src/plugins/core/` (shipped) and
`src/plugins/community/` (third-party)
- Each plugin has: `plugin.json` manifest, main class, optional
routes/views
- Sandboxed configuration: plugins can only access `plugin.{id}.*`
config keys
- Head/footer content injection for client-side integrations
- Help documentation system (help.json per plugin)
## Migrated Plugins
| Plugin | Description | Features |
|--------|-------------|----------|
| **Google Analytics** | GA4 tracking | Injects tracking code via head
content |
| **Gravatar** | Profile photos | Client-side avatar rendering with
fallback |
| **OpenLP** | Projector integration | Display person names on church
projector |
| **Vonage** | SMS notifications | Send SMS to individuals/families |
| **MailChimp** | Email marketing | Sync contacts, view unsubscribers,
add missing |
| **Custom Links** | Navigation menu | Add custom external links to menu
|
| **External Backup** | WebDAV cloud backup | Automatic/manual backups
to Nextcloud, ownCloud, etc. |
<img width="2543" height="842" alt="image"
src="https://github.com/user-attachments/assets/7efd16fe-9d4c-4cc8-ba31-6510eb33e7f9"
/>
Merged
DawoudIO
added a commit
that referenced
this pull request
Feb 13, 2026
This PR updates the locale translation strings extracted from source code. ## Triggered By Commit: `c42f02c3e487a7f6c0549231afc4ee203a6165b5` Message: Start 7.0.0 release (#7957) This PR starts the 7.0.0 release build. ## Changes - Updated version to 7.0.0 in package.json, composer.json, and related files - Updated database version in upgrade.json - Updated demo database with new version entry ## Next Steps - Review and merge this PR to begin the 7.0.0 release cycle ## Changes - Updated `locale/terms/messages.po` with new/modified translatable strings - Updated JSON locale keys ## Next Steps - Review the new strings that need translation - Merge this PR to update the base locale file - Upload to POEditor for translation if needed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR starts the 7.0.0 release build.
Changes
Next Steps