Diff -- /var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/MODS/PTMatrix/Config.pm
Diff

/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/MODS/PTMatrix/Config.pm

added on local at 2026-07-01 12:34:21

Added
+256
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 8dbb2e8723d3
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1package MODS::PTMatrix::Config;
2#======================================================================
3# TaskForge -- central configuration.
4#
5# Lookup order for settings():
6# 1. platform_settings table (DB-backed) -- editable by super_admin
7# via /admin_config.cgi. Highest precedence so no redeploy needed.
8# 2. The hardcoded %DEFAULTS hash below -- final fallback.
9#
10# Lookups are cached per-process in %_DB_CACHE so a page that reads
11# ten settings only hits the DB once.
12#======================================================================
13
14use strict;
15use warnings;
16
17my %EDITABLE = (
18 session_minutes => {
19 label => 'Session length (minutes)',
20 group => 'Defaults', secret => 0,
21 description => 'How long a user stays signed in after login. Default 720 = 12 hours. Affects both server-side user_sessions.expires_at AND the browser cookie Max-Age. Per-account override at users.session_minutes_override takes precedence.',
22 },
23 # ----- Branding ---------------------------------------------------
24 website_title => { label => 'Site title', group => 'Branding', secret => 0,
25 description => 'The HTML <title> tag value -- shown in browser tabs and link previews.' },
26 brand_name => { label => 'Brand name', group => 'Branding', secret => 0,
27 description => 'Short product name shown in the wrapper header.' },
28 brand_tagline => { label => 'Tagline', group => 'Branding', secret => 0,
29 description => 'One-line tagline shown under the brand name on marketing pages.' },
30
31 # ----- Email ------------------------------------------------------
32 feedback_emails => { label => 'Support / feedback inbox', group => 'Email', secret => 0,
33 description => 'Inbox that receives "Contact us" submissions. Comma-separate for multiple.' },
34 from_email => { label => 'Outbound "From" address', group => 'Email', secret => 0,
35 description => 'Sender address on system emails (invites, resets, due-soon notices).' },
36 smtp_host => { label => 'SMTP host', group => 'Email', secret => 0,
37 description => 'SMTP server hostname. Leave blank to use local sendmail / Plesk MTA.' },
38 smtp_port => { label => 'SMTP port', group => 'Email', secret => 0,
39 description => 'Usually 587 for STARTTLS or 465 for implicit TLS.' },
40 smtp_user => { label => 'SMTP username', group => 'Email', secret => 0,
41 description => 'Authentication username for the SMTP server.' },
42 smtp_pass => { label => 'SMTP password', group => 'Email', secret => 1,
43 description => 'Authentication password / app-specific password / API token.' },
44
45 # ----- Stripe -----------------------------------------------------
46 stripe_publishable => { label => 'Stripe publishable key', group => 'Stripe', secret => 0,
47 description => 'pk_live_ or pk_test_ from Stripe Dashboard > Developers > API keys.' },
48 stripe_secret => { label => 'Stripe secret key', group => 'Stripe', secret => 1,
49 description => 'sk_live_ or sk_test_. NEVER paste anywhere outside this field.' },
50 stripe_webhook_secret => { label => 'Stripe webhook signing secret', group => 'Stripe', secret => 1,
51 description => 'whsec_... from your webhook endpoint config in Stripe.' },
52 stripe_currency_default => { label => 'Default currency code', group => 'Stripe', secret => 0,
53 description => 'Three-letter ISO 4217, lowercase: usd, eur, gbp, cad.' },
54
55 # ----- SEO --------------------------------------------------------
56 'seo.title' => { label => 'SEO title', group => 'SEO', secret => 0, description => 'Default <title> for marketing pages.' },
57 'seo.description' => { label => 'SEO description', group => 'SEO', secret => 0, description => 'Default meta description.' },
58 'seo.keywords' => { label => 'SEO keywords', group => 'SEO', secret => 0, description => 'Comma-separated keyword list.' },
59 'seo.canonical_base' => { label => 'Canonical URL base', group => 'SEO', secret => 0, description => 'https://ptmatrix.3dshawn.com (no trailing slash)' },
60 'seo.og_site_name' => { label => 'Open Graph site name', group => 'SEO', secret => 0, description => 'Used in social cards.' },
61
62 # ----- Defaults ---------------------------------------------------
63 timezone_default => { label => 'Default timezone', group => 'Defaults', secret => 0,
64 description => 'IANA timezone for new accounts. Display only.' },
65 currency_default => { label => 'Default currency', group => 'Defaults', secret => 0,
66 description => 'Three-letter ISO 4217. Display only.' },
67
68 # ----- Billing ----------------------------------------------------
69 annual_discount_pct => { label => 'Annual prepay discount %', group => 'Billing', secret => 0,
70 description => 'Percent off when customers pay yearly instead of monthly. Industry-standard: 15-20. Aggressive: 25-30.' },
71
72);
73
74my %DEFAULTS = (
75 # ----- Database (NOT editable -- lookup depends on this) ---------
76 database_name => 'ptmatrix3dshawn',
77
78 # ----- Branding --------------------------------------------------
79 website_title => 'PTMatrix — Project Task Matrix',
80 brand_name => 'PTMatrix',
81 brand_tagline => 'Project Task Matrix — every project, every task, connected at a glance.',
82
83 # ----- Sessions / cookies ----------------------------------------
84 cookie_name => 'taskforge_session',
85 session_minutes => 720,
86 secure_cookies => 1,
87 cookie_domain => '',
88
89 # ----- Asset paths -----------------------------------------------
90 assets => '/assets',
91 assets_css => '/assets/css',
92 assets_js => '/assets/javascript',
93 assets_images => '/assets/images',
94
95 # ----- Email -----------------------------------------------------
96 feedback_emails => 'programmershawn@gmail.com',
97 from_email => 'noreply@ptmatrix.3dshawn.com',
98 smtp_host => '',
99 smtp_port => 587,
100 smtp_user => '',
101 smtp_pass => '',
102
103 # ----- Stripe ----------------------------------------------------
104 stripe_publishable => 'pk_test_REPLACE_ME',
105 stripe_secret => '',
106 stripe_webhook_secret => '',
107 stripe_currency_default => 'usd',
108
109 # ----- SEO -------------------------------------------------------
110 'seo.title' => 'TaskForge — Project Management for Teams',
111 'seo.description' => 'Kanban + Gantt + Reports + Time Tracking. Run every project, every department, every team from one place.',
112 'seo.keywords' => 'project management, kanban, gantt chart, task tracking, team collaboration, time tracking, ticketing system',
113 'seo.canonical_base' => 'https://ptmatrix.3dshawn.com',
114 'seo.og_site_name' => 'PTMatrix',
115
116 # ----- Misc ------------------------------------------------------
117 timezone_default => 'UTC',
118 currency_default => 'USD',
119 annual_discount_pct => 20,
120
121 # ----- Upload limits ---------------------------------------------
122 max_upload_bytes => 25 * 1024 * 1024, # 25 MB attachments
123 uploads_dir => '/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/uploads',
124);
125
126# Per-process cache. Populated on first DB hit; cheap after.
127my %_DB_CACHE;
128my $_DB_LOADED = 0;
129my $_DB_AVAILABLE;
130
131sub new {
132 my ($class) = @_;
133 return bless({}, $class);
134}
135
136sub editable_settings_meta {
137 my ($self) = @_;
138 my @rows;
139 foreach my $key (sort keys %EDITABLE) {
140 my $meta = $EDITABLE{$key};
141 my ($val, $source) = $self->_resolve_with_source($key);
142 my $masked = ($meta->{secret} && defined($val) && length $val) ? 1 : 0;
143 push @rows, {
144 key => $key,
145 label => $meta->{label},
146 group => $meta->{group},
147 secret => $meta->{secret},
148 description => $meta->{description} || '',
149 value => $masked ? '' : (defined $val ? $val : ''),
150 is_set => (defined $val && length $val) ? 1 : 0,
151 source => $source,
152 };
153 }
154 return @rows;
155}
156
157sub is_editable {
158 my ($self, $key) = @_;
159 return exists $EDITABLE{$key} ? 1 : 0;
160}
161
162sub editable_meta_for {
163 my ($self, $key) = @_;
164 return $EDITABLE{$key};
165}
166
167sub settings {
168 my ($self, $key) = @_;
169 return undef unless defined $key;
170 my ($val) = $self->_resolve_with_source($key);
171 return $val;
172}
173
174sub _resolve_with_source {
175 my ($self, $key) = @_;
176
177 if ($key ne 'database_name') {
178 $self->_load_db_cache;
179 if (exists $_DB_CACHE{$key}) {
180 return ($_DB_CACHE{$key}, 'db');
181 }
182 }
183
184 return (exists $DEFAULTS{$key} ? $DEFAULTS{$key} : undef, 'default');
185}
186
187sub _load_db_cache {
188 my ($self) = @_;
189 return if $_DB_LOADED;
190 $_DB_LOADED = 1;
191
192 eval "require MODS::DBConnect; 1" or return;
193 my $db = MODS::DBConnect->new;
194 return unless $db;
195
196 my $dbh = eval { $db->db_connect() };
197 return unless $dbh;
198
199 my $DB = $DEFAULTS{database_name};
200
201 my $col_check = eval {
202 $db->db_readwrite($dbh, qq~
203 SELECT COUNT(*) AS n FROM information_schema.tables
204 WHERE table_schema='$DB' AND table_name='platform_settings'
205 ~, $ENV{SCRIPT_NAME}, __LINE__);
206 };
207 unless ($col_check && $col_check->{n}) {
208 $_DB_AVAILABLE = 0;
209 eval { $db->db_disconnect($dbh) };
210 return;
211 }
212 $_DB_AVAILABLE = 1;
213
214 my @rows;
215 eval {
216 @rows = $db->db_readwrite_multiple($dbh, qq~
217 SELECT setting_key, setting_value
218 FROM `${DB}`.platform_settings
219 ~, $ENV{SCRIPT_NAME}, __LINE__);
220 1;
221 };
222 foreach my $r (@rows) {
223 next unless ref($r) eq 'HASH';
224 next unless defined $r->{setting_key};
225 my $val = defined $r->{setting_value} ? $r->{setting_value} : '';
226 next unless length $val;
227 $_DB_CACHE{ $r->{setting_key} } = $val;
228 }
229 eval { $db->db_disconnect($dbh) };
230}
231
232sub urls {
233 my $self = shift;
234 return {
235 home => '/',
236 login => '/login.cgi',
237 signup => '/signup.cgi',
238 logout => '/logout.cgi',
239 dashboard => '/dashboard.cgi',
240 projects => '/projects.cgi',
241 kanban => '/kanban.cgi',
242 gantt => '/gantt.cgi',
243 reports => '/reports.cgi',
244 team => '/team.cgi',
245 departments => '/departments.cgi',
246 settings => '/settings.cgi',
247 billing => '/billing.cgi',
248 admin => '/admin.cgi',
249 assets => '/assets',
250 assets_css => '/assets/css',
251 assets_js => '/assets/javascript',
252 assets_images => '/assets/images',
253 };
254}
255
2561;
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help