Diff -- /var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/admin_config.cgi
Diff

/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/admin_config.cgi

added on local at 2026-07-01 12:33:54

Added
+192
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 955e1472aabf
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1#!/usr/bin/perl
2
3##################################################################################################################################
4# ______ ____ ____ ______ ______ ____ ___ __ ___ ______ _ __ ____ ____ __ __ _ __ _____ ____
5# / ____// __ \ / __ \ / ____/ / ____// __ \ / | / |/ // ____/| | / // __ \ / __ \ / //_/ | | / /|__ / / __ \
6# / / / / / // / / // __/ / /_ / /_/ // /| | / /|_/ // __/ | | /| / // / / // /_/ // ,< | | / / /_ < / / / /
7# / /___ / /_/ // /_/ // /___ / __/ / _, _// ___ | / / / // /___ | |/ |/ // /_/ // _, _// /| | | |/ / ___/ /_ / /_/ /
8# \____/ \____//_____//_____/ /_/ /_/ |_|/_/ |_|/_/ /_//_____/ |__/|__/ \____//_/ |_|/_/ |_| |___/ /____/(_)\____/
9#
10# - Written by: Shawn Pickering
11# - shawn@shawnpickering.com
12##################################################################################################################################
13use strict;
14use lib '/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com';
15
16
17#############################################################
18## Variables
19#############################################################
20
21
22
23############################################################
24# Modules
25############################################################
26use CGI; my $query = new CGI; my $form = $query->Vars;
27use MODS::Login; my $login = new MODS::Login;
28use MODS::PTMatrix::Urls; my $getlist = new MODS::PTMatrix::Urls; my $url = $getlist->urls();
29use MODS::Template; my $tfile = new MODS::Template;
30use MODS::PTMatrix::Wrapper; my $load = new MODS::PTMatrix::Wrapper;
31use MODS::DBConnect; my $db = new MODS::DBConnect;
32use MODS::PTMatrix::Config; my $config = new MODS::PTMatrix::Config; my $database_name = $config->settings('database_name');
33use MODS::PTMatrix::PM;
34
35
36
37#############################################################
38## Get form data
39#############################################################
40#Snapshot raw values - platform settings hold URLs / API keys / SMTP passwords that
41#quotemeta would mangle. sql_quote on save handles escaping.
42my %rawform = %$form;
43
44foreach my $line(keys %$form){
45 $form->{$line} =~ s/[^!-~\s]//g;
46 $form->{$line} = quotemeta("$form->{$line}");
47}
48
49
50
51############################################################
52# Program Controls
53############################################################
54$|=1;
55my $userinfo = $login->login_verify();
56print qq~Content-type:text/html; Cache-Control:no-cache;\n\n~;
57if(!$userinfo){print qq~<script>window.location="$url->{login}";</script>~;}
58elsif(!($userinfo->{is_super_admin} || $userinfo->{_admin_is_super_admin})){print qq~<script>window.location="$url->{dashboard}";</script>~;}
59elsif(($ENV{REQUEST_METHOD}||'') eq 'POST'){&save_settings;}
60else{&config_panel;}
61
62
63############################################################
64# Subroutines
65############################################################
66sub save_settings{
67 my $dbh = $db->db_connect();
68
69 #Every k_<key> field on the form is a platform setting to upsert
70 foreach my $key(sort keys %rawform){
71 next unless $key =~ /^k_(.+)$/;
72 my $real_key = $1;
73 next unless $config->is_editable($real_key);
74
75 my $val = $rawform{$key};
76
77 #For secret fields, an empty submission means "don't change" - skip
78 my $meta = $config->editable_meta_for($real_key);
79 next if $meta->{secret} && !length($val);
80
81 my $sq_key = MODS::PTMatrix::PM::sql_quote($real_key);
82 my $sq_val = MODS::PTMatrix::PM::sql_quote($val);
83 my $sql = qq~INSERT INTO `${database_name}`.platform_settings (setting_key, setting_value) VALUES ('$sq_key', '$sq_val') ON DUPLICATE KEY UPDATE setting_value=VALUES(setting_value)~;
84 $db->db_readwrite($dbh, $sql, $ENV{SCRIPT_NAME}, __LINE__);
85 }
86
87 $db->db_disconnect($dbh);
88 &config_panel('Settings saved. Some take effect on next page load (cache).');
89}
90
91sub config_panel{
92 my($flash) = @_;
93 $flash ||= '';
94
95 my @rows = $config->editable_settings_meta;
96
97 #Group editable settings by their group label
98 my %by_group;
99 foreach my $r(@rows){push @{$by_group{$r->{group}}}, $r;}
100
101 #Per-group help content - rendered as a click-to-open overlay next to the section title
102 my %group_help = (
103 'Billing' => {
104 summary => 'Knobs that affect what your customers pay.',
105 detail => qq~<p><strong>Annual prepay discount %:</strong> the percent off shown when a customer picks the "billed yearly" toggle on the pricing page. Industry-standard is 15-20%; aggressive is 25-30%.</p>
106<p>Other billing knobs live in the dedicated <a href="$url->{admin_billing}">Platform Billing</a> page (price overrides per company, locked rates, credits) and <a href="$url->{admin_discounts}">Discounts &amp; Deals</a> (promo codes, intro offers).</p>~,
107 },
108 'Branding' => {
109 summary => 'How your product appears to customers.',
110 detail => q[<p><strong>Brand name and tagline</strong> show in the sidebar, marketing pages, and email headers.</p>
111<p><strong>Site title</strong> is the title tag of marketing pages &mdash; this is what shows in browser tabs and Google search results, so include a clear value-prop.</p>
112<p>Changes take effect on next page load (template cache).</p>],
113 },
114 'Defaults' => {
115 summary => 'Sensible defaults for new accounts.',
116 detail => q[<p><strong>Default timezone &amp; currency</strong> are applied when a new company signs up. Each customer can override their own settings later.</p>],
117 },
118 'Email' => {
119 summary => 'Outbound email delivery for receipts, invites, and notifications.',
120 detail => q[<p><strong>SMTP host / port / user / password:</strong> credentials for the SMTP server PTMatrix will use to send mail. Common choices: SendGrid (<em>smtp.sendgrid.net:587</em>), Mailgun, Amazon SES, or your own mail server.</p>
121<p><strong>From address:</strong> the "From:" header on outbound mail. Use a real address on a domain whose DNS you control (SPF / DKIM passing) or deliverability tanks.</p>
122<p><strong>Support / feedback inbox:</strong> where customer support replies are routed. Customers reply to a per-ticket address; replies get parsed back into PTMatrix automatically and the rest are forwarded here.</p>],
123 },
124 'SEO' => {
125 summary => 'Default search / social metadata for marketing pages.',
126 detail => q[<p><strong>Title &amp; description</strong> are what Google shows in search results. Aim for 50-60 chars for title, 150-160 for description, with a clear value-prop and one keyword phrase.</p>
127<p><strong>Canonical URL base:</strong> the absolute base for canonical link headers (e.g. <code>https://ptmatrix.3dshawn.com</code>, no trailing slash). Prevents duplicate-content penalties when pages are reachable via multiple URLs.</p>
128<p><strong>OG site name:</strong> the "site" that appears on social-share cards (Open Graph). Usually just your brand name.</p>],
129 },
130 'Stripe' => {
131 summary => 'Card-processing credentials. Without them, paid plans cannot collect money.',
132 detail => q[<p><strong>Publishable key</strong> (starts <code>pk_</code>) is safe to expose in client-side code &mdash; it goes into the checkout JavaScript.</p>
133<p><strong>Secret key</strong> (starts <code>sk_</code>) <em>never</em> leaves the server. Anyone with this key can charge cards on your account, so rotate it if exposed.</p>
134<p><strong>Webhook signing secret</strong> (starts <code>whsec_</code>) verifies that incoming webhook events actually came from Stripe (and not an attacker spoofing payment events). Generate it from the Stripe Dashboard <em>Developers &rarr; Webhooks &rarr; (your endpoint) &rarr; Signing secret</em>.</p>
135<p><strong>Live vs. test:</strong> test keys (<code>pk_test_...</code>, <code>sk_test_...</code>) only process test card numbers. Live keys (<code>pk_live_...</code>) process real money. Always start in test mode.</p>],
136 },
137 );
138
139 #Pre-render the modal overlays in Perl - the template loop engine
140 #was double-emitting the second iteration, so we concat once and pass
141 #the finished HTML to the template as $modals_html
142 my @groups;
143 my $modals_html = '';
144 foreach my $g(sort keys %by_group){
145 my $h = $group_help{$g} || { summary => '', detail => '' };
146 my $slug = lc $g; $slug =~ s/[^a-z0-9]/_/g;
147 my $has_help = (length($h->{summary}) || length($h->{detail})) ? 1 : 0;
148
149 push @groups, {
150 name => $g,
151 items => $by_group{$g},
152 help_summary => $h->{summary},
153 help_detail => $h->{detail},
154 has_help => $has_help,
155 slug => $slug,
156 };
157
158 if($has_help){
159 my $name_esc = $g; $name_esc =~ s/&/&amp;/g; $name_esc =~ s/</&lt;/g; $name_esc =~ s/>/&gt;/g;
160 my $summary_esc = $h->{summary};
161 $modals_html .= qq~
162<div class="acfg-modal-overlay" id="acfg-help-$slug" onclick="acfgBackdrop(event, '$slug')">
163 <div class="acfg-modal" role="dialog" aria-modal="true">
164 <div class="acfg-modal-head">
165 <div>
166 <h2>$name_esc</h2>
167 <div class="acfg-sum">$summary_esc</div>
168 </div>
169 <button type="button" class="acfg-modal-close" onclick="acfgClose('$slug')" aria-label="Close">&times;</button>
170 </div>
171 <div class="acfg-modal-body">$h->{detail}</div>
172 </div>
173</div>~;
174 }
175 }
176
177 #Create all template variables
178 my $tvars;
179 $tvars->{groups} = \@groups;
180 $tvars->{flash} = $flash;
181 $tvars->{has_flash} = length($flash) ? 1 : 0;
182 $tvars->{modals_html} = $modals_html;
183
184 my $body = join('', $tfile->template('tf_admin_config.html', $tvars, $userinfo));
185
186 $load->render({
187 userinfo => $userinfo,
188 page_key => 'admin_config',
189 title => 'Configuration',
190 body => $body,
191 });
192}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help