1642 lines
76 KiB
Plaintext
1642 lines
76 KiB
Plaintext
<?php
|
||
|
||
use Illuminate\Validation\Rule;
|
||
|
||
return [
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Initial passwords for 1st login after installation,
|
||
| should be changed after login!
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
*/
|
||
'init_passwords' => [
|
||
'super-user' => 'SecurePassword', // Initial password, should be changed after 1st login
|
||
'super-admin' => 'SecurePassword',
|
||
'bank' => 'SecurePassword',
|
||
'admin' => 'SecurePassword',
|
||
'test-profile' => 'password',
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Central Bank (Level 0 Source Bank)
|
||
|--------------------------------------------------------------------------
|
||
| Configuration for the central bank that is created during database seeding.
|
||
| The central bank is the source of currency creation/removal in the system.
|
||
|
|
||
*/
|
||
'central_bank' => [
|
||
'name' => 'Central Bank',
|
||
'full_name' => 'Central Bank',
|
||
'email' => 'bank@example.org',
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Mail addresses
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
*/
|
||
'mail' => [
|
||
'system_admin' => [
|
||
'email' => 'admin@timebank.cc',
|
||
'name' => 'admin@timebank.cc',
|
||
],
|
||
'content_admin' => [
|
||
'email' => 'info@timebank.cc',
|
||
'name' => config('app.name'),
|
||
],
|
||
'payments' => [
|
||
'email' => 'noreply@timebank.cc',
|
||
'name' => config('app.name'),
|
||
],
|
||
'chat_messenger' => [
|
||
'email' => 'support@timebank.cc',
|
||
'name' => config('app.name'),
|
||
],
|
||
'support' => [
|
||
'email' => env('MAIL_SUPPORT_ADDRESS', 'support@timebank.cc'),
|
||
'name' => env('MAIL_SUPPORT_NAME', 'Timebank.cc Support Team'),
|
||
],
|
||
'platform_sign_off_signature' => [
|
||
'en' => 'See you around and happy Timebanking!',
|
||
'nl' => 'Tot ziens en veel plezier met Timebanken!',
|
||
'fr' => 'À bientôt et bon Timebanking !',
|
||
'es' => '¡Hasta pronto y feliz Timebanking!',
|
||
'de' => 'Bis bald und viel Spaß beim Timebanking!',
|
||
],
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Platform specific translations
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
*/
|
||
'platform_translations' => [
|
||
'en' => [
|
||
'platform_name' => 'Timebank.cc',
|
||
'platform_name_legal' => 'association Timebank.cc',
|
||
'platform_name_short' => 'Timebank',
|
||
'platform_slogan' => 'Your time is currency',
|
||
'platform_currency_name' => 'Hour',
|
||
'platform_currency_name_plural' => 'Hours',
|
||
'platform_currency_symbol' => 'H',
|
||
'platform_currency_position_end' => false, // true will place currency symbol behind currency amount
|
||
'platform_user' => 'timebanker',
|
||
'platform_users' => 'timebankers',
|
||
'platform_principles' => 'Timebank principles',
|
||
],
|
||
'nl' => [
|
||
'platform_name' => 'Timebank.cc',
|
||
'platform_name_legal' => 'vereniging Timebank.cc',
|
||
'platform_name_short' => 'Timebank',
|
||
'platform_slogan' => 'Jouw tijd is deelbaar',
|
||
'platform_currency_name' => 'Uur',
|
||
'platform_currency_name_plural' => 'Uren',
|
||
'platform_currency_symbol' => 'H',
|
||
'platform_currency_position_end' => false, // true will place currency symbol behind currency amount
|
||
'platform_user' => 'timebanker',
|
||
'platform_users' => 'timebankers',
|
||
'platform_principles' => 'Timebank principes',
|
||
],
|
||
'fr' => [
|
||
'platform_name' => 'Timebank.cc',
|
||
'platform_name_legal' => 'association Timebank.cc',
|
||
'platform_name_short' => 'Timebank',
|
||
'platform_slogan' => 'Ton temps est une monnaie',
|
||
'platform_currency_name' => 'Heure',
|
||
'platform_currency_name_plural' => 'Heures',
|
||
'platform_currency_symbol' => 'H',
|
||
'platform_currency_position_end' => false, // true will place currency symbol behind currency amount
|
||
'platform_user' => 'tempobanquier',
|
||
'platform_users' => 'tempobanquiers',
|
||
'platform_principles' => 'Principes de Timebank',
|
||
],
|
||
'es' => [
|
||
'platform_name' => 'Timebank.cc',
|
||
'platform_name_legal' => 'asociación Timebank.cc',
|
||
'platform_name_short' => 'Timebank',
|
||
'platform_slogan' => 'Tu tiempo es compartible',
|
||
'platform_currency_name' => 'Hora',
|
||
'platform_currency_name_plural' => 'Horas',
|
||
'platform_currency_symbol' => 'H',
|
||
'platform_currency_position_end' => false, // true will place currency symbol behind currency amount
|
||
'platform_user' => 'tiempobanker',
|
||
'platform_users' => 'tiempobankers',
|
||
'platform_principles' => 'Principios de Timebank',
|
||
],
|
||
'de' => [
|
||
'platform_name' => 'Timebank.cc',
|
||
'platform_name_legal' => 'Verein Timebank.cc',
|
||
'platform_name_short' => 'Timebank',
|
||
'platform_slogan' => 'Deine Zeit ist Währung',
|
||
'platform_currency_name' => 'Stunde',
|
||
'platform_currency_name_plural' => 'Stunden',
|
||
'platform_currency_symbol' => 'Std',
|
||
'platform_currency_position_end' => false, // true will place currency symbol behind currency amount
|
||
'platform_user' => 'Zeitbanker',
|
||
'platform_users' => 'Zeitbankers',
|
||
'platform_principles' => 'Timebank Prinzipien',
|
||
],
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Admin settings
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
*/
|
||
'admin_settings' => [
|
||
'log_lines' => 100, // Show last nr of lines of the log on admin dashboard
|
||
'activity_log_delete_records_older_than_days' => 366 // 1 year
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| IP Address Retention
|
||
|--------------------------------------------------------------------------
|
||
| Configure retention period for IP addresses stored in profile tables
|
||
| (last_login_ip) and activity logs. IP addresses older than this period
|
||
| should be anonymized or deleted for GDPR compliance.
|
||
|
|
||
*/
|
||
'ip_retention' => [
|
||
'retention_days' => 180, // Retain IP addresses for 6 months (180 days)
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Logging settings
|
||
|--------------------------------------------------------------------------
|
||
| Configure Laravel's default log rotation and retention.
|
||
| These settings apply to the standard laravel.log files.
|
||
|
|
||
*/
|
||
'logging' => [
|
||
'daily_retention_days' => 14, // Number of days to keep daily log files (default: 14)
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Presence System settings
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
*/
|
||
'presence_settings' => [
|
||
'keep_last_presence_updates' => 10, // nr of most recent activity log records to keep
|
||
'update_interval' => 60, // Update presence time in seconds
|
||
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Profile Session Timeouts
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
| Define the inactivity timeout in minutes for each profile type.
|
||
| After the specified timeout, the user's session will expire and they
|
||
| will be logged out automatically. This provides security by ensuring
|
||
| inactive sessions are terminated.
|
||
|
|
||
| The key should be the fully qualified class name of the model.
|
||
| A default value is used if the active profile type isn't found here.
|
||
|
|
||
| IMPORTANT: These timeouts OVERRIDE the SESSION_LIFETIME setting from .env
|
||
| They are enforced by ProfileSessionTimeout middleware.
|
||
|
|
||
| Security Best Practices:
|
||
| - User profiles: Short timeout (30-120 min) for regular accounts
|
||
| - Organizations: Medium timeout (30-60 min) for community profiles
|
||
| - Banks: Short timeout (15-30 min) for financial operations
|
||
| - Admins: Very short timeout (15-30 min) for privileged access
|
||
|
|
||
*/
|
||
'profile_timeouts' => [
|
||
App\Models\User::class => 120, // minutes
|
||
App\Models\Organization::class => 60,
|
||
App\Models\Bank::class => 30,
|
||
App\Models\Admin::class => 30,
|
||
],
|
||
'profile_timeout_default' => 120, // minutes. Fallback default if type not listed or no profile active
|
||
|
||
'profile_inactive' => [
|
||
'days_not_logged_in' => 350, // Mark profile as inactive after not logging in for this many days (~1 year)
|
||
're-activate_at_login' => true, // Remove inactive_at record at login
|
||
'messenger_hidden' => true, // Not searchable in chat messenger
|
||
'profile_search_hidden' => true, // Profile page is hidden from main search bar for non-Admin and non-Banks
|
||
'profile_hidden' => true, // Profile page is hidden for non-Admin and non-Banks
|
||
'profile_labeled' => true, // Profile has inactive label
|
||
],
|
||
|
||
'profile_email_unverified' => [
|
||
'messenger_hidden' => true, // Not searchable in chat messenger
|
||
'profile_search_hidden' => true, // Profile page is hidden from main search bar for non-Admin and non-Banks
|
||
'profile_hidden' => false, // Profile page is hidden for non-Admin and non-Banks
|
||
'profile_labeled' => true, // Profile has email unverified label
|
||
],
|
||
|
||
'profile_incomplete' => [
|
||
'messenger_hidden' => false, // Not searchable in chat messenger
|
||
'profile_search_hidden' => true, // Profile page is hidden from main search bar for non-Admin and non-Banks
|
||
'profile_hidden' => false, // Profile page is hidden for non-Admin and non-Banks
|
||
'profile_labeled' => true, // Profile has incomplete label
|
||
'check_fields' => ['about', 'about_short', 'motivation', 'cyclos_skills'], // One these fields that must be filled in to be considered as a complete profile
|
||
'check_fields_min_total_length' => 100, // Minimum total length of all check_fields that must be filled in to be considered as a complete profile
|
||
'check_relations' => ['tags', 'languages', 'locations'], // One these relations that must be filled in to be considered as a complete profile
|
||
'show_warning_modal' => true, // Show profile incomplete warning modal on edit profile page
|
||
'no_exchanges_yet_label' => true, // Show 'No exchanges yet, but ready to help' label when profile has never received a transaction
|
||
],
|
||
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Default Profile Properties
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
*/
|
||
'profiles' => [
|
||
'user' => [
|
||
'limit_min' => 0,
|
||
'limit_max' => 6000, // 100 H
|
||
'profile_photo_path_new' => 'app-images/profile-user-new.svg',
|
||
'profile_photo_path_default' => 'app-images/profile-user-default.svg',
|
||
'messenger_can_create_groups' => true, // true: profile can start a group chat
|
||
|
||
],
|
||
'organization' => [
|
||
'limit_min' => 0,
|
||
'limit_max' => 6000, // 100 H4
|
||
'profile_photo_path_new' => 'app-images/profile-organization-default.svg',
|
||
'profile_photo_path_default' => 'app-images/profile-organization-default.svg',
|
||
'messenger_can_create_groups' => true, // true: profile can start a group chat
|
||
|
||
],
|
||
'bank' => [
|
||
'level' => 1, // by default a non-public system bank is created as this can be privately installed and later changed to a public level 2 bank
|
||
'limit_min' => 0,
|
||
'limit_max' => null, // unlimited H
|
||
'profile_photo_path_new' => 'app-images/profile-bank-default.svg',
|
||
'profile_photo_path_default' => 'app-images/profile-bank-default.svg',
|
||
'messenger_can_create_groups' => true, // true: profile can start a group chat
|
||
],
|
||
'admin' => [
|
||
'limit_min' => 0,
|
||
'limit_max' => 0,
|
||
'profile_photo_path_new' => 'app-images/profile-admin-default.svg',
|
||
'profile_photo_path_default' => 'app-images/profile-admin-default.svg',
|
||
'messenger_can_create_groups' => true, // true: profile can start a group chat
|
||
],
|
||
],
|
||
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Default Account Properties
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
| The default account properties that will be set into the database when new accounts are created.
|
||
| The balance limits are in minutes. A negative balance limit should be set as 'limit_min' = -300
|
||
|
|
||
| Receiving types - defines which transaction types the account can receive:
|
||
| 1 => worked time
|
||
| 2 => gift
|
||
| 3 => donation
|
||
| 4 => currency creation
|
||
| 5 => currency removal
|
||
| 6 => migration
|
||
|
|
||
| Note: When renaming the account names, make sure also translation keys exists for the new names!
|
||
|
|
||
|
||
*/
|
||
// TODO JOERI: Check transaction types
|
||
'accounts' => [
|
||
'user' => [
|
||
'name' => 'personal',
|
||
'limit_min' => 0,
|
||
'limit_max' => 6000, // 100 H
|
||
'receiving_types' => [1,2,6],
|
||
],
|
||
'user_project' => [
|
||
'name' => 'personal project',
|
||
'limit_min' => 0,
|
||
'receiving_types' => [1,3,6],
|
||
],
|
||
'organization' => [
|
||
'name' => 'organization',
|
||
'limit_min' => 0,
|
||
'limit_max' => 12000, // 200 H, default value, manually set organizations with a big turn-over to a higher limit
|
||
'receiving_types' => [1,3,6],
|
||
],
|
||
'bank' => [
|
||
'name' => 'banking system',
|
||
'limit_min' => 0, // The 'source' bank and the debit account should have limit_min = NULL, other banks can use this config
|
||
'limit_max' => 600000, // 10.000 H
|
||
'receiving_types' => [1,4,6],
|
||
],
|
||
'community' => [
|
||
'name' => 'community',
|
||
'limit_min' => 0, // The 'source' bank and the debit account should have limit_min = NULL, other banks can use this config
|
||
'limit_max' => null,
|
||
'receiving_types' => [3,4],
|
||
],
|
||
'debit' => [
|
||
'name' => 'debit',
|
||
'limit_min' => null, // The 'source' bank and the debit account should have limit_min = NULL, other banks can use this config
|
||
'limit_max' => 0,
|
||
'receiving_types' => [5],
|
||
],
|
||
],
|
||
'maxLengthHoursInput' => [ // Sets the default max length the amount component can have for the hours input box
|
||
'user' => 3,
|
||
'organization' => 3,
|
||
'bank' => 5,
|
||
'admin' => 10,
|
||
'transaction_types' => [],
|
||
],
|
||
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Bank Properties
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
*/
|
||
|
||
'banks' => [
|
||
'level' => [
|
||
0 => 'Source', // Single source bank that has the debit account and can create / remove currency
|
||
1 => 'System', // System banks that are private and bridge between level 0 and 2
|
||
2 => 'Public', // Public banks that bring currency into circulation, or remove currency from circulation
|
||
],
|
||
],
|
||
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Profile type permission settings
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
| Payment types - defines which transaction types the user type can pay:
|
||
| 1 => worked time
|
||
| 2 => gift
|
||
| 3 => donation
|
||
| 4 => currency creation
|
||
| 5 => currency removal
|
||
| 6 => migration
|
||
|
|
||
| Note: Internal migration payments between accounts of the same owner are always allowed.
|
||
| External migration payments are defined below.
|
||
*/
|
||
// TODO JOERI: Check transaction types
|
||
'permissions' => [
|
||
'user' => [
|
||
'payment_types' => [1,2,3],
|
||
],
|
||
'user_project' => [
|
||
'payment_types' => [1,2,3],
|
||
],
|
||
'organization' => [
|
||
'payment_types' => [1,2,3],
|
||
],
|
||
'bank' => [
|
||
'payment_types' => [1,2,3,5,6],
|
||
],
|
||
'admin' => [
|
||
'payment_types' => [], // Admins do not have an account and can not make payments
|
||
],
|
||
],
|
||
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Public / Private settings
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
*/
|
||
|
||
'account_info' => [
|
||
'user' => [
|
||
'balance_public' => true, //Privacy during transactions (payment flow)
|
||
'sumBalances_public' => true, //Privacy on profile pages (viewing profiles)
|
||
'countTransfersSince' => 366 * 2, // days ago
|
||
'countTransfersSince_humanReadable' => 'past 2 years', // Short description of countTransfersSince in base language: must have translation key!
|
||
'countTransfers_public' => true,
|
||
'countTransfersReceived_public' => true,
|
||
'countTransfersGiven_public' => true,
|
||
'countTransfersReceivedOrGiven_public' => true,
|
||
'lastTransferDate_public' => true,
|
||
],
|
||
'organization' => [
|
||
'balance_public' => true, //Privacy during transactions (payment flow)
|
||
'sumBalances_public' => true, //Privacy on profile pages (viewing profiles)
|
||
'countTransfersSince' => 366, // days ago
|
||
'countTransfersSince_humanReadable' => 'past year', // Short description of countTransfersSince in base language: must have translation key!
|
||
'countTransfers_public' => true,
|
||
'countTransfersReceived_public' => true,
|
||
'countTransfersGiven_public' => true,
|
||
'countTransfersReceivedOrGiven_public' => true,
|
||
'lastTransferDate_public' => true,
|
||
],
|
||
'bank' => [
|
||
'balance_public' => false, //Privacy during transactions (payment flow)
|
||
'sumBalances_public' => true, //Privacy on profile pages (viewing profiles)
|
||
'countTransfersSince' => 366, // days ago
|
||
'countTransfersSince_humanReadable' => 'past year', // Short description of countTransfersSince in base language: must have translation key!
|
||
'countTransfers_public' => true,
|
||
'countTransfersReceived_public' => true,
|
||
'countTransfersGiven_public' => true,
|
||
'countTransfersReceivedOrGiven_public' => true,
|
||
'lastTransferDate_public' => true,
|
||
],
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Verification rules and file size limits
|
||
|--------------------------------------------------------------------------
|
||
| Here you can set the verification rules that will be used to verify data
|
||
| that will be submitted in forms.
|
||
|
|
||
| Also you can set here the uploaded file properties and limits as well as
|
||
| the default files that will be used when no file is uploaded by the user.
|
||
*/
|
||
|
||
|
||
|
||
'rules' => [
|
||
'phone' => 'nullable|phone:mobile,strict', //mobile phone for future sms verification, strict mode, no spaces, no dashes, no brackets
|
||
'phone_public' => 'boolean|nullable',
|
||
'comment' => 'nullable|string|max:1000',
|
||
'profile_user' => [
|
||
'name' => [
|
||
'required',
|
||
'string',
|
||
'unique:users,name',
|
||
'unique:organizations,name',
|
||
'unique:banks,name',
|
||
'unique:banks,full_name',
|
||
'unique:admins,name',
|
||
'unique:admins,full_name',
|
||
'min:3',
|
||
'max:40',
|
||
function ($attribute, $value, $fail) {
|
||
// Disallow the following words to be used inside the name:
|
||
$disallowedWords = [
|
||
'admin',
|
||
'administrator',
|
||
'superuser',
|
||
'super-user',
|
||
'super-admin',
|
||
'supervisor',
|
||
'webmaster',
|
||
'web-master',
|
||
'tijd-bank',
|
||
'tijdbank',
|
||
'bank',
|
||
'timebank',
|
||
'time-bank',
|
||
'moderator',
|
||
'regulator',
|
||
'belasting',
|
||
'tax',
|
||
'test',
|
||
];
|
||
|
||
// Disallowed names as they might conflict with (future) url paths
|
||
$completelyDisallowedNames = [
|
||
'test',
|
||
'debug',
|
||
'user',
|
||
'users',
|
||
'member',
|
||
'members',
|
||
'profile',
|
||
'organization',
|
||
'organizations',
|
||
'organisation',
|
||
'organisations',
|
||
'bank',
|
||
'banks',
|
||
'admin',
|
||
'transaction',
|
||
'transactions',
|
||
'transfer',
|
||
'transfers',
|
||
'statement',
|
||
'statements',
|
||
'payment',
|
||
'payments',
|
||
'pay',
|
||
'paid',
|
||
'invoice',
|
||
'request',
|
||
'requests',
|
||
'edit',
|
||
'show',
|
||
'update',
|
||
'message',
|
||
'messages',
|
||
'messenger',
|
||
'messengers',
|
||
'berichten',
|
||
'chat',
|
||
'talk',
|
||
'meet',
|
||
'drive',
|
||
'cloud',
|
||
'config',
|
||
'settings',
|
||
'agenda',
|
||
'calendar',
|
||
'news',
|
||
'nieuws',
|
||
'vote',
|
||
'poll',
|
||
'auth',
|
||
'authenticate',
|
||
'verify',
|
||
'verification',
|
||
'date',
|
||
'datum',
|
||
'confirm',
|
||
'mail',
|
||
'post',
|
||
'posts',
|
||
'blog',
|
||
];
|
||
|
||
// Check for disallowed substrings
|
||
foreach ($disallowedWords as $word) {
|
||
if (str_contains(strtolower($value), $word)) {
|
||
$fail(trans('validation.custom.profile_user.name.disallowed', ['word' => $word]));
|
||
}
|
||
}
|
||
|
||
// Check for completely disallowed names
|
||
if (in_array(strtolower($value), array_map('strtolower', $completelyDisallowedNames))) {
|
||
$fail(trans('validation.custom.profile_user.name.completely_disallowed', ['name' => $value]));
|
||
}
|
||
},
|
||
'regex:/^[\p{L}0-9-_ ]+$/u', // letters (including accented), numbers, spaces, dashes and underscores
|
||
],
|
||
'full_name' => [
|
||
'nullable',
|
||
'string',
|
||
'unique:organizations,name',
|
||
'unique:banks,name',
|
||
'unique:banks,full_name',
|
||
'unique:admins,name',
|
||
'unique:admins,full_name',
|
||
'min:3',
|
||
'max:40',
|
||
'regex:/^[\p{L}0-9-_ ]+$/u', // letters (including accented), numbers, spaces, dashes and underscores
|
||
function ($attribute, $value, $fail) {
|
||
// Disallow the following words to be used inside the name:
|
||
$disallowedWords = [
|
||
'admin',
|
||
'administrator',
|
||
'superuser',
|
||
'super-user',
|
||
'super-admin',
|
||
'supervisor',
|
||
'webmaster',
|
||
'web-master',
|
||
'tijd-bank',
|
||
'tijdbank',
|
||
'bank',
|
||
'timebank',
|
||
'time-bank',
|
||
'moderator',
|
||
'regulator',
|
||
'belasting',
|
||
'tax',
|
||
'test',
|
||
];
|
||
|
||
// Check for disallowed substrings
|
||
foreach ($disallowedWords as $word) {
|
||
if (str_contains(strtolower($value), $word)) {
|
||
$fail(trans('validation.custom.profile_user.name.disallowed', ['word' => $word]));
|
||
}
|
||
}
|
||
},
|
||
],
|
||
'email' => 'required|email|unique:users,email|unique:organizations,email|unique:banks,email|unique:admins,email|max:40',
|
||
// 'password' => 'required|min:6|same:passwordConfirmation',
|
||
'password' => 'required|min:6|confirmed',
|
||
'profile_photo' => 'nullable|mimes:gif,jpg,jpeg,png,svg|max:6144', // max 6 MB
|
||
'about' => 'nullable|string|max:1000', //Cyclos: no max characters
|
||
'about_max_input' => 1000, // should match validation rule!
|
||
'about_short' => 'nullable|string|max:150', //Cyclos: no max characters
|
||
'about_short_max_input' => 150, // should match validation rule!
|
||
'motivation' => 'nullable|string|max:300', //Cyclos: no max characters
|
||
'motivation_max_input' => 300, // should match validation rule!
|
||
'date_of_birth' => 'nullable|date',
|
||
'languages' => 'required',
|
||
'languages_id' => 'int',
|
||
'website' => 'nullable|regex:/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/',
|
||
],
|
||
'profile_organization' => [
|
||
'name' => [
|
||
'required',
|
||
'string',
|
||
'unique:users,name',
|
||
'unique:organizations,name',
|
||
'unique:banks,name',
|
||
'unique:banks,full_name',
|
||
'unique:admins,name',
|
||
'unique:admins,full_name',
|
||
'min:3',
|
||
'max:40',
|
||
function ($attribute, $value, $fail) {
|
||
// Disallow the following words to be used inside the name:
|
||
$disallowedWords = [
|
||
'admin',
|
||
'administrator',
|
||
'superuser',
|
||
'super-user',
|
||
'supervisor',
|
||
'webmaster',
|
||
'web-master',
|
||
'tijd-bank',
|
||
'tijdbank',
|
||
'bank',
|
||
'timebank',
|
||
'time-bank',
|
||
'moderator',
|
||
'regulator',
|
||
'belasting',
|
||
'tax',
|
||
'test',
|
||
];
|
||
|
||
// Disallowed names as they might conflict with (future) url paths
|
||
$completelyDisallowedNames = [
|
||
'test',
|
||
'debug',
|
||
'user',
|
||
'users',
|
||
'member',
|
||
'members',
|
||
'profile',
|
||
'organization',
|
||
'organizations',
|
||
'organisation',
|
||
'organisations',
|
||
'bank',
|
||
'banks',
|
||
'admin',
|
||
'transaction',
|
||
'transactions',
|
||
'transfer',
|
||
'transfers',
|
||
'statement',
|
||
'statements',
|
||
'payment',
|
||
'payments',
|
||
'pay',
|
||
'paid',
|
||
'invoice',
|
||
'request',
|
||
'requests',
|
||
'edit',
|
||
'show',
|
||
'update',
|
||
'message',
|
||
'messages',
|
||
'messenger',
|
||
'messengers',
|
||
'berichten',
|
||
'chat',
|
||
'talk',
|
||
'meet',
|
||
'drive',
|
||
'cloud',
|
||
'config',
|
||
'settings',
|
||
'agenda',
|
||
'calendar',
|
||
'news',
|
||
'nieuws',
|
||
'vote',
|
||
'poll',
|
||
'auth',
|
||
'authenticate',
|
||
'verify',
|
||
'verification',
|
||
'date',
|
||
'datum',
|
||
'confirm',
|
||
'mail',
|
||
'post',
|
||
'posts',
|
||
'blog',
|
||
];
|
||
|
||
// Check for disallowed substrings
|
||
foreach ($disallowedWords as $word) {
|
||
if (str_contains(strtolower($value), $word)) {
|
||
$fail(trans('validation.custom.profile_user.name.disallowed', ['word' => $word]));
|
||
}
|
||
}
|
||
|
||
// Check for completely disallowed names
|
||
if (in_array(strtolower($value), array_map('strtolower', $completelyDisallowedNames))) {
|
||
$fail(trans('validation.custom.profile_user.name.completely_disallowed', ['name' => $value]));
|
||
}
|
||
},
|
||
'regex:/^[\p{L}0-9-_ ]+$/u', // letters (including accented), numbers, spaces, dashes and underscores
|
||
],
|
||
'full_name' => [
|
||
'nullable',
|
||
'string',
|
||
'unique:users,name',
|
||
'unique:organizations,name',
|
||
'unique:banks,name',
|
||
'unique:banks,full_name',
|
||
'unique:admins,name',
|
||
'unique:admins,full_name',
|
||
'min:3',
|
||
'max:40',
|
||
'regex:/^[\p{L}0-9-_ ]+$/u', // letters (including accented), numbers, spaces, dashes and underscores
|
||
],
|
||
'email' => 'required|email|unique:users,email|unique:organizations,email|unique:banks,email|unique:admins,email|max:40',
|
||
'password' => 'required|string|min:8|confirmed',
|
||
'profile_photo' => 'nullable|mimes:gif,jpg,jpeg,png,svg|max:6144', // max 6 MB
|
||
'about' => 'nullable|string|max:1000',
|
||
'about_max_input' => 1000, // should match validation rule!
|
||
'about_short' => 'nullable|string|max:150',
|
||
'about_short_max_input' => 150, // should match validation rule!
|
||
'motivation' => 'nullable|string|max:200',
|
||
'motivation_max_input' => 200, // should match validation rule!
|
||
'languages' => 'required',
|
||
'languages_id' => 'int',
|
||
'website' => 'nullable|regex:/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/',
|
||
],
|
||
'profile_bank' => [
|
||
'name' => [
|
||
'required',
|
||
'string',
|
||
'unique:users,name',
|
||
'unique:organizations,name',
|
||
'unique:banks,name',
|
||
'unique:banks,full_name',
|
||
'unique:admins,name',
|
||
'unique:admins,full_name',
|
||
'min:3',
|
||
'max:40',
|
||
'regex:/^[\p{L}0-9-_ ]+$/u', // letters (including accented), numbers, spaces, dashes and underscores
|
||
],
|
||
'full_name' => [
|
||
'nullable',
|
||
'string',
|
||
'unique:users,name',
|
||
'unique:organizations,name',
|
||
'unique:banks,name',
|
||
'unique:banks,full_name',
|
||
'unique:admins,name',
|
||
'unique:admins,full_name',
|
||
'min:3',
|
||
'max:40',
|
||
'regex:/^[\p{L}0-9-_ ]+$/u', // letters (including accented), numbers, spaces, dashes and underscores
|
||
],
|
||
'email' => 'required|email|unique:users,email|unique:organizations,email|unique:banks,email|unique:admins,email|max:40',
|
||
'password' => 'required|string|min:12|confirmed',
|
||
'profile_photo' => 'nullable|mimes:gif,jpg,jpeg,png,svg|max:6144', // max 6 MB
|
||
'about' => 'nullable|string|max:1000',
|
||
'about_max_input' => 1000, // should match validation rule!
|
||
'about_short' => 'nullable|string|max:150',
|
||
'about_short_max_input' => 150, // should match validation rule!
|
||
'motivation' => 'nullable|string|max:200',
|
||
'motivation_max_input' => 200, // should match validation rule!
|
||
'languages' => 'required',
|
||
'languages_id' => 'int',
|
||
'website' => 'nullable|regex:/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/',
|
||
],
|
||
'profile_admin' => [
|
||
'name' => [
|
||
'required',
|
||
'string',
|
||
'unique:users,name',
|
||
'unique:organizations,name',
|
||
'unique:banks,name',
|
||
'unique:banks,full_name',
|
||
'unique:admins,name',
|
||
'unique:admins,full_name',
|
||
'min:3',
|
||
'max:40',
|
||
'regex:/^[\p{L}0-9-_ ]+$/u', // letters (including accented), numbers, spaces, dashes and underscores
|
||
],
|
||
'full_name' => [
|
||
'nullable',
|
||
'string',
|
||
'unique:users,name',
|
||
'unique:organizations,name',
|
||
'unique:banks,name',
|
||
'unique:banks,full_name',
|
||
'unique:admins,name',
|
||
'unique:admins,full_name',
|
||
'min:3',
|
||
'max:40',
|
||
'regex:/^[\p{L}0-9-_ ]+$/u', // letters (including accented), numbers, spaces, dashes and underscores
|
||
],
|
||
'email' => 'required|email|unique:users,email|unique:organizations,email|unique:banks,email|unique:admins,email|max:40',
|
||
'password' => 'required|string|min:12|confirmed',
|
||
'profile_photo' => 'nullable|mimes:gif,jpg,jpeg,png,svg|max:6144', // max 6 MB
|
||
'about' => 'nullable|string|max:1000',
|
||
'about_max_input' => 1000, // should match validation rule!
|
||
'about_short' => 'nullable|string|max:150',
|
||
'about_short_max_input' => 150, // should match validation rule!
|
||
'motivation' => 'nullable|string|max:200',
|
||
'motivation_max_input' => 200, // should match validation rule!
|
||
'languages' => 'required',
|
||
'languages_id' => 'int',
|
||
'website' => 'nullable|regex:/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/',
|
||
],
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Authentication Settings
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
*/
|
||
'auth' => [
|
||
'minimum_registration_age' => 18, // Minimum age for registration (GDPR Article 8 compliance)
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Base Language
|
||
|--------------------------------------------------------------------------
|
||
| Translations are linked by their context to one base language.
|
||
|
|
||
| IMPORTANT: This language is also used as fallback locale, therefore all names, titles, terms, etc. must be at least in this language!
|
||
| IMPORTANT: The base language can not be changed in an existing project, unless the new base language pre-exists for all translations!
|
||
*/
|
||
'base_language' => 'en', // Do not change in existing project, see note above
|
||
'base_language_name' => 'English', // Do not change in existing project, see note above
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Search settings
|
||
|--------------------------------------------------------------------------
|
||
| Configuration of Elasticsearch matching and highlighting.
|
||
| More info: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html
|
||
|
|
||
*/
|
||
'main_search_bar' => [
|
||
'boosted_fields' => [ // fields with a boost factor, 1,0 neutral, 1,5 boosted, 0,5 penalized
|
||
'profile' => [
|
||
'name' => 3,
|
||
'full_name' => 3,
|
||
'cyclos_skills' => 2,
|
||
'tags' => 5,
|
||
'tag_categories' => 4,
|
||
'motivation' => 1,
|
||
'about_short' => 1,
|
||
'about' => 1,
|
||
'district' => 1,
|
||
'city' => 1,
|
||
'division' => 1,
|
||
'country' => 1,
|
||
],
|
||
'post' => [
|
||
'title' => 2, // title is the most important field
|
||
'content' => 1,
|
||
'excerpt' => 1.5,
|
||
'post_category_name' => 2, // category name is important for posts
|
||
],
|
||
],
|
||
'boosted_models' => [ // search score multiplier
|
||
'user' => 1,
|
||
'organization' => 3,
|
||
'bank' => 3,
|
||
'post' => 4,
|
||
],
|
||
'search' => [
|
||
'type' => 'best_fields', // 'best_fields', 'most_fields', 'cross_fields', 'phrase', 'phrase_prefix'
|
||
'prefix_length' => 4, //characters at the beginning of the word that must match
|
||
'fragment_size' => 80, //The size of the highlighted fragment in characters.
|
||
'fragmenter' => 'span', // 'simple' or 'span'
|
||
'number_of_fragments' => 2, // The maximum number of fragments to return. If the number of fragments is set to 0, no fragments are returned. Instead, the entire field contents are highlighted and returned.
|
||
'pre-tags' => '<span class="font-semibold text-white leading-tight">', // HTML tags to wrap around highlighted text
|
||
'post-tags' => '</span>', // HTML tags to wrap around highlighted text
|
||
'order' => 'score', // 'score' or 'none', the order of the fragments
|
||
'max_results' => 500, // defines setSize property for maximum amount of search results. Setting a high number will consume a lot of cache memory!
|
||
],
|
||
'model_indices' => [ // Elasticsearch indices that will be searched (defined in Models and imported by Scout)
|
||
'posts_index',
|
||
'users_index',
|
||
'organizations_index',
|
||
'banks_index',
|
||
'calls_index',
|
||
],
|
||
'suggestions' => 4, // max number of suggestions to show in search bar. Showing suggestions slows down the reactivity of the search bar!
|
||
'category_ids_posts' => [4,5,6,7,8,113], //Include these posts category_id's
|
||
'cache_results' => 5 // minutes (TTL) to store search results in cache memory. TTL is extended whenever a search result is opened.
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Search Optimization settings (Optional)
|
||
|--------------------------------------------------------------------------
|
||
| These settings work with the SearchOptimizationHelper class.
|
||
| Add this section to timebank-cc.php config if you want
|
||
| to use the advanced search optimization features.
|
||
*/
|
||
'search_optimization' => [
|
||
'enabled' => true,
|
||
'cache_ttl' => 3600, // 1 hour for category hierarchies
|
||
'analytics' => [
|
||
'enabled' => true,
|
||
'track_searches' => true,
|
||
'max_recent_searches' => 10,
|
||
'retention_hours' => 24,
|
||
'track_location_data' => true, // Track location-based search patterns
|
||
],
|
||
'boost_factors' => [
|
||
'verified_profiles' => 1.2,
|
||
'complete_profiles' => 1.1,
|
||
'recent_activity' => 1.05,
|
||
'exact_category_match' => 2.0, // Multiplier for exact category name matches
|
||
'fuzzy_category_match' => 1.0, // Multiplier for fuzzy category matches
|
||
],
|
||
'location_boosts' => [
|
||
'same_district' => 1.0, //5.0, // Highest boost for same district
|
||
'same_city' => 1.1, //3.0, // High boost for same city
|
||
'same_division' => 1.5, //2.0, // Medium boost for same division/state
|
||
'same_country' => 1.0, //1.5, // Base boost for same country
|
||
'different_country' => 1.0, // Neutral for different countries
|
||
'no_location' => 1.0, //0.9, // Slight penalty for profiles without location
|
||
],
|
||
'performance' => [
|
||
'enable_query_caching' => true,
|
||
'cache_search_results' => true,
|
||
'optimize_highlights' => true,
|
||
'batch_process_results' => true,
|
||
'enable_location_caching' => true, // Cache location hierarchy lookups
|
||
],
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Payment settings
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
*/
|
||
'payment' => [
|
||
'amount_rule' => 'required|integer|min:1',
|
||
'description_rule' => 'required|string|min:3|max:500',
|
||
],
|
||
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Post settings
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
*/
|
||
'posts' => [
|
||
'postable_is_auth_user' => true, // Post editor profile that is stored. Set to true: Users, set to false: active profile models stored in session (users, organizations, banks, admins)
|
||
'site-content-writer' => 'Timebank.cc', // Writer name for general site content such as static pages. This name can be a non-exsisting user / organization
|
||
'title_rule' => 'required|string|min:3|max:150',
|
||
'title_max_input' => 150, // should match tile_rule
|
||
'slug_rule' => ['required', 'string', 'min:3', 'max:150', 'regex:/^[\pL\pM\pN-]+$/u'],
|
||
'excerpt_rule' => 'nullable|string|string|max:500',
|
||
'excerpt_max_input' => 500, // should match excerpt_rule
|
||
'content_rule' => 'nullable|string|max:1048576', // max 1 MB in bytes
|
||
'content_max_input' => 10000, // 10000 characters is equivalent of a main newspaper article / 10 min of reading time
|
||
'image_rule' => 'nullable|image|mimes:jpg,jpeg,png,gif,webp|max:12288', // max 12 MB - check also the max file size in the .htaccess file and php.ini!
|
||
'media_owner_rule' => 'nullable|string|max:150',
|
||
'media_caption_rule' => 'nullable|string|max:300',
|
||
'media_caption_max_input' => 300, // should match media_caption_rule
|
||
'meeting_venue_rule' => 'nullable|string|max:50',
|
||
'meeting_address_rule' => 'nullable|string|max:100',
|
||
'meeting_transaction_types' => [1,2,3,], // Possible transaction types: 1 = Work, 2 = Gift, 3 = Donation
|
||
|
||
'static' => [
|
||
'getting-started' => [
|
||
'limit' => 1, // Maximum number of posts to show in the static getting-started page
|
||
'hideAuthor' => true, // Hide the author of the static posts in the getting-started page
|
||
],
|
||
'faq' => [
|
||
'limit' => null, // Maximum number of posts to show in the static FAQ page
|
||
'hideAuthor' => true, // Hide the author of the static posts in the FAQ page
|
||
],
|
||
'organizations' => [
|
||
'limit' => null, // Maximum number of posts to show in the static organizations page
|
||
'hideAuthor' => true, // Hide the author of the static posts in the organizations page
|
||
],
|
||
'principles' => [
|
||
'limit' => 1, // Maximum number of posts to show in the static principles page
|
||
'hideAuthor' => true, // Hide the author of the static posts in the principles page
|
||
],
|
||
'events' => [
|
||
'limit' => 5, // Maximum number of posts to show in the static events page
|
||
'hideAuthor' => true, // Hide the author of the static posts in the events page
|
||
],
|
||
'the-hague' => [
|
||
'limit' => 1, // Maximum number of posts to show in the static the-hague page
|
||
'hideAuthor' => true, // Hide the author of the static posts in the the-hague page
|
||
],
|
||
'lekkernassuh' => [
|
||
'limit' => 1, // Maximum number of posts to show in the static lekkernassuh page
|
||
'hideAuthor' => true, // Hide the author of the static posts in the lekkernassuh page
|
||
],
|
||
'amst-brus-lisb' => [
|
||
'limit' => 1, // Maximum number of posts to show in the static ams-bru-lis page
|
||
'hideAuthor' => true, // Hide the author of the static posts in the ams-bru-lis page
|
||
],
|
||
'work-with-us' => [
|
||
'limit' => 1, // Maximum number of posts to show in the static work-with-us page
|
||
'hideAuthor' => false, // Hide the author of the static posts in the work-with-us page
|
||
],
|
||
'philosophy' => [
|
||
'limit' => 1, // Maximum number of posts to show in the static philosophy page
|
||
'hideAuthor' => true, // Hide the author of the static posts in the philosophy page
|
||
],
|
||
'open-source' => [
|
||
'limit' => 1, // Maximum number of posts to show in the static open-source page
|
||
'hideAuthor' => true, // Hide the author of the static posts in the open-source page
|
||
],
|
||
'timebank-organization' => [
|
||
'limit' => null, // Maximum number of posts to show in the static timebank-organization page
|
||
'hideAuthor' => true, // Hide the author of the static posts in the timebank-organization page
|
||
],
|
||
'history' => [
|
||
'limit' => null, // Maximum number of posts to show in the static history page
|
||
'hideAuthor' => false, // Hide the author of the static posts in the history page
|
||
],
|
||
'press-media' => [
|
||
'limit' => 10, // Maximum number of posts to show in the static press-media page
|
||
'hideAuthor' => true, // Hide the author of the static posts in the press-media page
|
||
],
|
||
'economics-and-research' => [
|
||
'limit' => null, // Maximum number of posts to show in the static economics-and-research page
|
||
'hideAuthor' => false, // Hide the author of the static posts in the economics-and-research page
|
||
],
|
||
'meet-the-team' => [
|
||
'limit' => 1, // Maximum number of posts to show in the static meet-the-team page
|
||
'hideAuthor' => false, // Hide the author of the static posts in the meet-the-team page
|
||
],
|
||
'messenger' => [
|
||
'limit' => 1, // Maximum number of posts to show in the static messenger page
|
||
'hideAuthor' => true, // Hide the author of the static posts in the messenger page
|
||
],
|
||
]
|
||
],
|
||
|
||
'calls' => [
|
||
'till_max_days' => 60, // Maximum number of days ahead a Call expiry date can be set for regular users. Set to null for no maximum.
|
||
'till_max_days_non_user' => 366, // Maximum number of days ahead for non-user callables (Organization, Bank). Set to null for no maximum. When both till_max_days and till_max_days_non_user are null, no max expiry is enforced for anyone.
|
||
'default_expiry_days' => null, // Default expiry in days from creation date. Set to null for no default, or e.g. 14 for 14 days
|
||
'content_max_input' => 200, // Max characters for call description
|
||
'expiry_warning_days' => 7, // Show an "Expires" badge on the call view when expiry is within this many days. Set to null to disable.
|
||
|
||
// CSS filter applied to callable profile photos shown to unauthenticated (guest) users.
|
||
// Set guest_photo_blur_px to 0 to disable all filters entirely.
|
||
// blur_px: pixels of blur — controls dot size in the halftone effect (higher = larger/softer dots, lower = finer dots)
|
||
// contrast: percentage — crushes blurred blobs into sharp dots (higher = more defined dots, e.g. 500 for strong effect)
|
||
// saturate: percentage — 0 = greyscale, 100 = full colour, values in between desaturate partially
|
||
// brightness: percentage — lifts overall brightness (100 = neutral, 110 = slightly brighter, lower = darker)
|
||
// TODO remove this unused config?
|
||
'guest_photo_blur_px' => 2,
|
||
'guest_photo_contrast' => 150, // %
|
||
'guest_photo_saturate' => 60, // %
|
||
'guest_photo_brightness' => 130, // %
|
||
|
||
'carousel' => [
|
||
|
||
// --- Locality filtering -------------------------------------------
|
||
'include_unknown_location' => true,
|
||
'include_same_division' => true,
|
||
'include_same_country' => true,
|
||
|
||
// --- Exclusion ---------------------------------------------------
|
||
// Note: paused, suppressed and expired calls are always excluded
|
||
// regardless of config to prevent abuse.
|
||
'exclude_non_public' => false,
|
||
'exclude_own_calls' => true,
|
||
|
||
// --- Pool size ---------------------------------------------------
|
||
'max_cards' => 12,
|
||
'pool_multiplier' => 5, // Fetch (pool_multiplier × max_cards) candidates from DB, score them in PHP, then show the top max_cards. Higher = better scoring quality but more DB load.
|
||
|
||
// --- Boost factors -----------------------------------------------
|
||
'boost_same_district' => 3.0,
|
||
'boost_location_city' => 2.0,
|
||
'boost_location_division' => 1.5,
|
||
'boost_location_country' => 1.1,
|
||
'boost_location_unknown' => 2.5,
|
||
|
||
'boost_like_count' => 0.05,
|
||
'boost_star_count' => 0.10,
|
||
|
||
'boost_recent_from' => 1.3,
|
||
'recent_days' => 14,
|
||
'boost_soon_till' => 1.2,
|
||
'soon_days' => 7,
|
||
|
||
'boost_callable_user' => 1.0,
|
||
'boost_callable_organization' => 1.2,
|
||
'boost_callable_bank' => 1.0,
|
||
|
||
'show_score' => false,
|
||
'show_score_for_admins' => true,
|
||
],
|
||
|
||
'welcome_carousel' => [
|
||
'max_cards' => 12,
|
||
'pool_multiplier' => 5, // Fetch (pool_multiplier × max_cards) candidates from DB, score in PHP, then show the top max_cards.
|
||
|
||
// Boost factors (no location boosts — no profile context for guests)
|
||
'boost_like_count' => 0.05,
|
||
'boost_star_count' => 0.10,
|
||
'boost_recent_from' => 1.3,
|
||
'recent_days' => 14,
|
||
'boost_soon_till' => 1.2,
|
||
'soon_days' => 7,
|
||
'boost_callable_user' => 1.0,
|
||
'boost_callable_organization' => 1.2,
|
||
'boost_callable_bank' => 1.0,
|
||
|
||
'show_score' => false,
|
||
'show_score_for_admins' => true,
|
||
],
|
||
],
|
||
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Tags settings
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
*/
|
||
'tags' => [
|
||
'allow_tag_transations_for_non_admins' => false, // Set to true if all profile types can also add a translation when creating a new skill tag
|
||
'name_rule' => [
|
||
'required',
|
||
'string',
|
||
'min:3',
|
||
'max:80',
|
||
function ($attribute, $value, $fail) {
|
||
if (preg_match('/[!?@#\$*\_\+{}\[\]<>\/|=.,\\\\]/', $value)) {
|
||
$fail(__('The :attribute cannot contain special characters like !?@#$*_+{}[]<>/\|=,.'));
|
||
}
|
||
},
|
||
function ($attribute, $value, $fail) {
|
||
if (!preg_match('/\S+\s+\S+/', $value)) {
|
||
// If the input doesn't have at least 2 words, fail the validation for this field
|
||
$fail(__('The :attribute must be at least 2 words.'));
|
||
}
|
||
},
|
||
],
|
||
'exists_in_current_locale_rule' => [
|
||
function ($attribute, $value, $fail) {
|
||
$existsInCurrentLocale = Illuminate\Support\Facades\DB::table('taggable_tags')
|
||
->join('taggable_locales', 'taggable_tags.tag_id', '=', 'taggable_locales.taggable_tag_id')
|
||
->where('taggable_locales.locale', app()->getLocale())
|
||
->where(function ($query) use ($value) {
|
||
$query->where('taggable_tags.name', $value)
|
||
->orWhere('taggable_tags.normalized', $value);
|
||
})
|
||
->exists();
|
||
|
||
if ($existsInCurrentLocale) {
|
||
$fail(__('This :attribute name already exists.'));
|
||
}
|
||
},
|
||
],
|
||
'comment_rule' => 'string|max:500|nullable',
|
||
],
|
||
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Media Library settings
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
*/
|
||
'media_library' => [
|
||
'max_file_size' => 1024 * 1024 * 12, // 12 MB
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Custom Messenger settings
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
*/
|
||
'messenger' => [
|
||
'default_unread_mail_delay' => 8, // In hours. After this default delay an email will be send to notify an unread chat message. Users / profiles can change this in their settings
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| WireChat Configuration
|
||
|--------------------------------------------------------------------------
|
||
| Configuration for the WireChat messaging system including disappearing
|
||
| messages, cleanup schedules, user permissions, and attachments.
|
||
*/
|
||
'wirechat' => [
|
||
'disappearing_messages' => [
|
||
// Allow users to mark messages as "kept" to prevent immediate deletion
|
||
'allow_users_to_keep' => true,
|
||
|
||
// Duration in DAYS before regular messages are deleted
|
||
'duration' => 120, // 120 days (4 months) - timebank_cc custom value
|
||
|
||
// Duration in DAYS before kept messages are also deleted
|
||
// Even "kept" messages are eventually cleaned up (prevents orphaned data)
|
||
'kept_messages_duration' => 720, // 720 days (24 months / 2 years)
|
||
|
||
// Laravel schedule frequency for cleanup job
|
||
// Options: 'everyMinute', 'everyFiveMinutes', 'everyTenMinutes', 'hourly'
|
||
'cleanup_schedule' => 'hourly',
|
||
],
|
||
|
||
'profile_deletion' => [
|
||
// When a profile is permanently deleted, release their kept messages
|
||
// This sets kept_at = null, allowing normal cleanup to process them
|
||
'release_kept_messages' => true,
|
||
],
|
||
|
||
'attachments' => [
|
||
'storage_folder' => 'attachments',
|
||
'storage_disk' => 'public',
|
||
'disk_visibility' => 'public', // Use 'private' to enforce temporary URLs
|
||
'max_uploads' => 5, // Maximum number of files that can be uploaded at once
|
||
// Media Upload Settings (images/videos)
|
||
'media_mimes' => ['png', 'jpg', 'jpeg', 'gif'], // Allowed media file types
|
||
'media_max_upload_size' => 12288, // Size in KB (12 MB)
|
||
// File Upload Settings (documents)
|
||
'file_mimes' => ['zip', 'rar', 'txt', 'pdf'], // Allowed document file types
|
||
'file_max_upload_size' => 12288, // Size in KB (12 MB)
|
||
],
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Reaction settings
|
||
|--------------------------------------------------------------------------
|
||
*/
|
||
'reactions' => [
|
||
'star' => [
|
||
'enabled' => true,
|
||
'only_with_interaction' => true, // Require interaction between profiles
|
||
'interaction' => 'hasTransactionsWith', // Method to check for interaction
|
||
'icon_file' => 'star.svg',
|
||
'display_name' => 'Star',
|
||
'description' => 'Give a star to recommend and show appreciation.', // Tooltip text
|
||
'disabled_reasons' => [
|
||
'cannot_react_own_profile' => 'You cannot give yourself a star.',
|
||
'no_interaction' => 'You first need to have an exchange with each other.',
|
||
],
|
||
],
|
||
'bookmark' => [
|
||
'enabled' => true,
|
||
'only_with_interaction' => false, // Anyone can bookmark
|
||
'interaction' => 'hasTransactionsWith', // Not used since only_with_interaction is false
|
||
'icon_file' => 'bookmark.svg',
|
||
'display_name' => 'Bookmark',
|
||
'description' => 'Save in your contact list.', // Tooltip text
|
||
'disabled_reasons' => [
|
||
'cannot_react_own_profile' => 'You cannot bookmark your own profile.',
|
||
'no_interaction' => 'You need an interaction to bookmark.',
|
||
],
|
||
],
|
||
'reserve' => [
|
||
'enabled' => true,
|
||
'only_with_interaction' => false, // Anyone can reserve
|
||
'interaction' => 'hasTransactionsWith', // Not used since only_with_interaction is false
|
||
'icon_file' => 'reserved.svg',
|
||
'display_name' => 'Reserve',
|
||
'description' => 'Puts you on the reservations lists.', // Tooltip text
|
||
'disabled_reasons' => [
|
||
'cannot_react_own_profile' => 'You cannot reserve right now.',
|
||
'no_interaction' => 'You need an interaction to reserve this.',
|
||
],
|
||
'count_public_threshold' => 3 // Threshold before nr of reservations is shown publicly
|
||
],
|
||
'like' => [
|
||
'enabled' => true,
|
||
'only_with_interaction' => false,
|
||
'interaction' => 'hasTransactionsWith', // WARNING: new interaction methods should always be tested on development server!
|
||
'icon_file' => 'heart-icon.svg',
|
||
'display_name' => 'Like',
|
||
'description' => 'Show appreciation.',
|
||
'disabled_reasons' => [
|
||
'cannot_react_own_profile' => 'You cannot like your own content.',
|
||
'no_interaction' => 'You need an interaction to like this.',
|
||
],
|
||
],
|
||
'vote' => [
|
||
'enabled' => false, // Disabled reaction type
|
||
'only_with_interaction' => false,
|
||
'interaction' => 'hasParticipatedWithExample', // WARNING: new interaction methods should always be tested on development server!
|
||
'icon_file' => 'hand-raised.svg',
|
||
'display_name' => 'Vote',
|
||
'description' => 'Cast a vote.',
|
||
'disabled_reasons' => [
|
||
'cannot_react_own_profile' => 'You cannot vote for yourself.',
|
||
'no_interaction' => 'You need to have participated to vote.',
|
||
],
|
||
],
|
||
'dislike' => [
|
||
'enabled' => false,
|
||
'only_with_interaction' => false,
|
||
'interaction' => 'hasCollaboratedWithExample', // WARNING: new interaction methods should always be tested on development server!
|
||
'icon_file' => 'thumbs-down.svg',
|
||
'display_name' => 'Dislike',
|
||
'description' => 'Show disapproval.',
|
||
'disabled_reasons' => [
|
||
'cannot_react_own_profile' => 'You cannot dislike your own content.',
|
||
'no_interaction' => 'You need an interaction to dislike this.',
|
||
],
|
||
],
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Online settings
|
||
|--------------------------------------------------------------------------
|
||
*/
|
||
'online' => [
|
||
'contact_list' => [
|
||
'dashboard' => [
|
||
'enabled' => true, // enable / disable online contact list on dashboard
|
||
'reaction_types_for_user' => ['Star', 'Bookmark'], // show in contact list if exists in array of reaction types
|
||
'reaction_types_for_organization' => ['Bookmark'], // show in contact list if exists in array of reaction types
|
||
'reaction_types_for_bank' => ['Star, Bookmark'], // show in contact list if exists in array of reaction types
|
||
],
|
||
],
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Mailing (Bulk Mail Newsletter) Configuration
|
||
|--------------------------------------------------------------------------
|
||
*/
|
||
'mailing' => [
|
||
'send_delay_seconds' => 5, // Delay between individual emails
|
||
'batch_size' => 10, // Process emails in batches
|
||
'max_retries' => 3, // Maximum retry attempts for failed emails
|
||
'retry_delay_minutes' => 15, // Base delay before first retry (exponential backoff applies)
|
||
'retry_multiplier' => 2, // Multiplier for exponential backoff (delay * multiplier^attempt)
|
||
'max_retry_delay_hours' => 24, // Maximum delay between retries (caps exponential backoff)
|
||
'abandon_after_hours' => 72, // Completely abandon email retries after this time
|
||
'use_fallback_locale' => false, // Whether to use fallback locale when recipient's preferred locale is unavailable
|
||
'fallback_locale' => 'en', // Fallback locale to use when recipient's preferred locale has no translations
|
||
'from_address' => [
|
||
'local_newsletter' => 'noreply@timebank.cc',
|
||
'general_newsletter' => 'noreply@timebank.cc',
|
||
'system_message' => 'admin@timebank.cc'
|
||
],
|
||
'copy_to_mailpit' => true, // Set to true to send a copy of every outgoing email to Mailpit. Run: php artisan queue:restart after changing.
|
||
'mailpit_host' => 'localhost', // Mailpit SMTP host
|
||
'mailpit_port' => 1025, // Mailpit SMTP port
|
||
'bounce_address' => env('MAIL_BOUNCE_ADDRESS', 'bounces@timebank.cc'),
|
||
'templates' => [
|
||
'wrapper' => 'emails.newsletter.wrapper',
|
||
'news_block' => 'emails.newsletter.blocks.news',
|
||
'article_block' => 'emails.newsletter.blocks.article',
|
||
'event_block' => 'emails.newsletter.blocks.event',
|
||
'image_block' => 'emails.newsletter.blocks.image'
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Bounce Handling Thresholds
|
||
|--------------------------------------------------------------------------
|
||
| Configure how many hard bounces are required before taking action.
|
||
| This provides a conservative approach to prevent false positives.
|
||
*/
|
||
'bounce_thresholds' => [
|
||
// Number of hard bounces before email is suppressed from future mailings
|
||
'suppression_threshold' => 1,
|
||
|
||
// Number of hard bounces before email_verified_at is set to null
|
||
'verification_reset_threshold' => 1,
|
||
|
||
// Time window in days to count bounces (prevents old bounces from accumulating)
|
||
'counting_window_days' => 365,
|
||
|
||
// Only count these bounce types toward thresholds
|
||
'counted_bounce_types' => ['hard'],
|
||
|
||
// Specific bounce reasons that count as definitive hard bounces
|
||
'definitive_hard_bounce_patterns' => [
|
||
'user unknown',
|
||
'no such user',
|
||
'mailbox unavailable',
|
||
'does not exist',
|
||
'invalid recipient',
|
||
'address rejected',
|
||
'5.1.1', // User unknown
|
||
'5.1.2', // Domain not found
|
||
'5.1.3', // Invalid address
|
||
'550', // Mailbox unavailable
|
||
'551', // User not local
|
||
],
|
||
|
||
// Automatic cleanup settings
|
||
'automatic_cleanup' => [
|
||
// Day of week for cleanup (0 = Sunday, 1 = Monday, etc.)
|
||
'day_of_week' => 1, // Monday
|
||
|
||
// Time to run cleanup (24-hour format)
|
||
'time' => '03:00',
|
||
|
||
// Number of days after which to delete soft bounces
|
||
'cleanup_days' => 90,
|
||
|
||
// Only cleanup these bounce types (hard bounces are kept for suppression)
|
||
'cleanup_bounce_types' => ['soft', 'unknown'],
|
||
],
|
||
]
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Footer Navigation Configuration
|
||
|--------------------------------------------------------------------------
|
||
| Configure footer sections and links for white-labeling.
|
||
| Each section can be reordered, hidden, or customized per platform.
|
||
| All titles should have translation keys.
|
||
*/
|
||
'footer' => [
|
||
'sections' => [
|
||
[
|
||
'title' => 'Who we are',
|
||
'order' => 1,
|
||
'visible' => true,
|
||
'links' => [
|
||
['route' => 'static-philosophy', 'title' => 'Our philosophy', 'order' => 1, 'visible' => true],
|
||
['route' => 'static-timebank-organization', 'title' => 'Timebank organization', 'order' => 2, 'visible' => true],
|
||
['route' => 'static-history', 'title' => 'History', 'order' => 3, 'visible' => true],
|
||
['route' => 'static-press-media', 'title' => 'Press and media', 'order' => 4, 'visible' => true],
|
||
['route' => 'static-research', 'title' => 'Economics and research', 'order' => 5, 'visible' => true],
|
||
],
|
||
],
|
||
[
|
||
'title' => 'Community',
|
||
'order' => 2,
|
||
'visible' => true,
|
||
'links' => [
|
||
['route' => 'static-events', 'title' => 'Events', 'order' => 1, 'visible' => true],
|
||
['route' => 'static-the-hague', 'title' => 'The Hague', 'order' => 2, 'visible' => true],
|
||
['route' => 'static-lekkernassuh', 'title' => 'Lekkernassûh', 'order' => 3, 'visible' => true],
|
||
['route' => 'static-amst-brus-lisb', 'title' => 'Amsterdam, Brussels, Lisbon', 'order' => 4, 'visible' => true],
|
||
['route' => 'static-work-w-us', 'title' => 'Work with us', 'order' => 5, 'visible' => true],
|
||
['route' => 'static-open-source', 'title' => 'Open source', 'order' => 6, 'visible' => true],
|
||
],
|
||
],
|
||
[
|
||
'title' => 'Help',
|
||
'order' => 3,
|
||
'visible' => true,
|
||
'links' => [
|
||
['route' => 'static-getting-started', 'title' => 'Getting started', 'order' => 1, 'visible' => true],
|
||
['route' => 'static-faq', 'title' => 'FAQ', 'order' => 2, 'visible' => true],
|
||
['route' => 'static-organizations', 'title' => 'Organizations', 'order' => 3, 'visible' => true],
|
||
['route' => 'static-principles', 'title' => 'messages.platform_principles', 'order' => 4, 'visible' => true],
|
||
['route' => 'static-privacy', 'title' => 'Privacy policy', 'order' => 5, 'visible' => true],
|
||
],
|
||
],
|
||
[
|
||
'title' => 'Contact us',
|
||
'order' => 4,
|
||
'visible' => true,
|
||
'links' => [
|
||
['route' => 'static-team', 'title' => 'Meet the team', 'order' => 1, 'visible' => true],
|
||
['route' => 'static-report-issue', 'title' => 'Report an issue', 'order' => 2, 'visible' => true],
|
||
// ['route' => 'static-report-error', 'title' => 'Report an issue', 'order' => 2, 'visible' => false],
|
||
['route' => 'static-messenger', 'title' => 'Chat messenger', 'order' => 3, 'visible' => true, 'auth_required' => true],
|
||
['url' => 'mailto:info@timebank.cc', 'title' => 'info@timebank.cc', 'order' => 4, 'visible' => true],
|
||
],
|
||
],
|
||
],
|
||
'tagline' => 'Your time is currency',
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Search Engine Indexing
|
||
|--------------------------------------------------------------------------
|
||
*/
|
||
'seo' => [
|
||
'allow_indexing_guest' => true, // Allow search engines to index guest pages
|
||
'allow_indexing_auth' => false, // Allow search engines to index authenticated pages
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Profile Session Timeouts
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
| Define the inactivity timeout in minutes for each profile type.
|
||
| After the specified timeout, the user's session will expire and they
|
||
| will be logged out automatically. This provides security by ensuring
|
||
| inactive sessions are terminated.
|
||
|
|
||
| The key should be the fully qualified class name of the model.
|
||
| A default value is used if the active profile type isn't found here.
|
||
|
|
||
| IMPORTANT: These timeouts OVERRIDE the SESSION_LIFETIME setting from .env
|
||
| They are enforced by ProfileSessionTimeout middleware.
|
||
|
|
||
| Security Best Practices:
|
||
| - User profiles: Short timeout (30-120 min) for regular accounts
|
||
| - Organizations: Medium timeout (30-60 min) for community profiles
|
||
| - Banks: Short timeout (15-30 min) for financial operations
|
||
| - Admins: Very short timeout (15-30 min) for privileged access
|
||
|
|
||
*/
|
||
'profile_timeouts' => [
|
||
App\Models\User::class => 120, // minutes
|
||
App\Models\Organization::class => 120,
|
||
App\Models\Bank::class => 120,
|
||
App\Models\Admin::class => 120,
|
||
],
|
||
'profile_timeout_default' => 120, // minutes. Fallback default if type not listed or no profile active
|
||
|
||
'profile_inactive' => [
|
||
'days_not_logged_in' => 366 * 2, // Profile marked as inactive after this many days without login // TODO change back to 2???
|
||
're-activate_at_login' => true, // Remove inactive_at record at login
|
||
'messenger_hidden' => false, // Not searchable in chat messenger is true
|
||
'profile_search_hidden' => true, // Profile page is hidden from main search bar for non-Admin and non-Banks
|
||
'profile_hidden' => true, // Profile page is hidden for non-Admin and non-Banks
|
||
'profile_labeled' => true, // Profile has inactive label
|
||
],
|
||
|
||
'profile_email_unverified' => [
|
||
'messenger_hidden' => true, // True means not searchable in chat messenger
|
||
'profile_search_hidden' => true, // Profile page is hidden from main search bar for non-Admin and non-Banks
|
||
'profile_hidden' => false, // Profile page is hidden for non-Admin and non-Banks
|
||
'profile_labeled' => true, // Profile has email unverified label
|
||
],
|
||
|
||
'profile_incomplete' => [
|
||
'messenger_hidden' => false, // True means not searchable in chat messenger
|
||
'profile_search_hidden' => true, // Profile page is hidden from main search bar for non-Admin and non-Banks
|
||
'profile_hidden' => false, // Profile page is hidden for non-Admin and non-Banks
|
||
'profile_labeled' => false, // Profile has incomplete label
|
||
'check_fields' => ['about', 'about_short', 'motivation', 'cyclos_skills'], // One these fields that must have data to be considered as a complete profile
|
||
'check_fields_min_total_length' => 10, // Minimum total length of all check_fields that must be filled in to be considered as a complete profile
|
||
'check_relations' => ['tags', 'languages', 'locations'], // One these relations that must be filled in to be considered as a complete profile
|
||
'show_warning_modal' => true, // Show profile incomplete warning modal on edit profile page
|
||
'no_exchanges_yet_label' => true, // Show 'No exchanges yet, but ready to help' label when profile has never received a transaction
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Delete Profile Settings
|
||
|--------------------------------------------------------------------------
|
||
| Configure what happens to account balances when a profile is deleted.
|
||
|
|
||
| The logic follows this elseif structure:
|
||
| 1. If 'donate_balances_to_organization_account_specified' is true:
|
||
| User can select an organization to donate balance to
|
||
| 2. Else if 'transfer_balances_to_bank_client' is true:
|
||
| Transfer balances to a bank the profile was a client of
|
||
| 3. Else if 'transfer_balances_to_account_id' is set (not null):
|
||
| Transfer balances to this specific account id
|
||
| 4. Else if 'transfer_balances_to_debit_account' is true:
|
||
| Transfer balances to debit account (removes currency from circulation)
|
||
*/
|
||
'delete_profile' => [
|
||
// Grace period: Days to wait before permanent deletion (allows restoration)
|
||
'grace_period_days' => 1, // Profiles can be restored within this many days after deletion
|
||
|
||
// NOTE: These are days AFTER the profile is marked inactive (not days since last login)
|
||
// Total time = days_not_logged_in (350) + days_after_inactive
|
||
'days_after_inactive' => [
|
||
'warning_1' => 0, // First warning
|
||
'warning_2' => 30, // Second warning
|
||
'warning_final' => 60, // Final warning
|
||
'run_delete' => 90, // Delete profile
|
||
],
|
||
'account_balances' => [
|
||
// If accounts of deleted profile are not 0?
|
||
'donate_balances_to_organization_account_specified' => true, // Allow profile to donate all balances to an organization they specify - only when self-deleting
|
||
// elseif other options are valid when auto-deleting or when no donation to organisation is specified when self-deleteing
|
||
'transfer_balances_to_bank_client' => false, // BANKCLIENTS ARE NOT USED YET - Transfer all balances to a (local) bank the profile was a bankClient of
|
||
// elseif
|
||
'transfer_balances_to_account_id' => null, // Or transfer balances to this specific account id. Set to null to disable setting
|
||
// else
|
||
'transfer_balances_to_debit_account' => true, // Set always to true as dead currency needs to be cleaned
|
||
],
|
||
'log_trimming' => [
|
||
// Automatic log file trimming for inactive profile processing logs
|
||
'retention_days' => 30, // Keep log entries for this many days (default: 30)
|
||
'schedule_frequency' => 'monthly', // Laravel schedule frequency: daily, weekly, monthly (default: monthly)
|
||
'schedule_time' => '03:30', // Time to run log trimming (24-hour format, default: 03:30)
|
||
],
|
||
],
|
||
];
|