Diff -- /var/www/vhosts/3dshawn.com/repricer.3dshawn.com/_attic/update_storefront_field.cgi
Diff

/var/www/vhosts/3dshawn.com/repricer.3dshawn.com/_attic/update_storefront_field.cgi

added on local at 2026-07-01 21:47:36

Added
+201
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to b9395929d9dd
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# RePricer -- update a single storefront field.
4#
5# POST params:
6# field = one of: name, tagline, about_text, hero_image_1/2/3
7# value = new value (string)
8#
9# Used by the page editor's in-place WYSIWYG. On blur of an editable
10# element (or after an image is uploaded and selected) the iframe POSTs
11# here. We auth the user, verify they own the storefront, whitelist
12# the field, and UPDATE the row.
13#
14# Returns JSON { success: 1 } or { success: 0, error: "..." }.
15#======================================================================
16use strict;
17use warnings;
18
19use lib '/var/www/vhosts/3dshawn.com/repricer.3dshawn.com';
20use CGI;
21use MODS::DBConnect;
22use MODS::Login;
23use MODS::RePricer::Config;
24
25$| = 1;
26
27my $q = CGI->new;
28my $db = MODS::DBConnect->new;
29my $auth = MODS::Login->new;
30my $config = MODS::RePricer::Config->new;
31my $DB = $config->settings('database_name');
32
33print "Content-Type: application/json\nCache-Control: no-cache\n\n";
34
35# DBConnect's error() helper calls exit() on a failing SQL call, which
36# bypasses the eval{} around the UPDATE below and leaves the response
37# body empty -- the editor then sees "saveField: server returned 200
38# (empty body)". This END block fires after any premature exit and
39# emits a useful JSON message so the client always gets a real error.
40our $SAVE_DONE = 0;
41END {
42 if (!$SAVE_DONE) {
43 print qq~{"success":0,"error":"server aborted mid-update -- check MODS/DB_ERRORS.log for the failing SQL and DBI message"}~;
44 }
45}
46
47sub fail {
48 my ($msg) = @_;
49 $msg =~ s/"/\\"/g;
50 print qq~{"success":0,"error":"$msg"}~;
51 $SAVE_DONE = 1;
52 exit;
53}
54
55my $userinfo = $auth->login_verify();
56fail('not authenticated') unless $userinfo && $userinfo->{user_id};
57require MODS::RePricer::Permissions;
58fail('not authorized') unless MODS::RePricer::Permissions->new->has_feature($userinfo, 'edit_storefront');
59
60my $uid = $userinfo->{user_id};
61$uid =~ s/[^0-9]//g;
62fail('bad user id') unless $uid;
63
64# Whitelist of fields that can be updated through this endpoint.
65# Keys are the public field name (what the client sends); the value
66# is the SQL column to write (defense against column-injection even
67# though we sanitize separately).
68my %ALLOWED = (
69 name => 'name',
70 tagline => 'tagline',
71 about_text => 'about_text',
72 hero_image_1 => 'hero_image_1',
73 hero_image_2 => 'hero_image_2',
74 hero_image_3 => 'hero_image_3',
75 hero_image_1_pos => 'hero_image_1_pos',
76 hero_image_2_pos => 'hero_image_2_pos',
77 hero_image_3_pos => 'hero_image_3_pos',
78 cta_primary_label => 'cta_primary_label',
79 cta_secondary_label => 'cta_secondary_label',
80 featured_badge_label => 'featured_badge_label',
81 section_heading => 'section_heading',
82 footer_byline => 'footer_byline',
83 featured_title => 'featured_title',
84 featured_price => 'featured_price',
85 featured_hero_image => 'featured_hero_image',
86 featured_hero_pos => 'featured_hero_pos',
87 # Catalog-layout hero chrome -- added with the
88 # "edit everything on the page" pass. Schema-guarded in store.cgi
89 # (NULL falls back to the layout default until the seller overrides).
90 hero_title => 'hero_title',
91 hero_pill_label => 'hero_pill_label',
92 hero_cta_primary_label => 'hero_cta_primary_label',
93 hero_cta_secondary_label => 'hero_cta_secondary_label',
94 section_eyebrow => 'section_eyebrow',
95 header_signin_label => 'header_signin_label',
96 header_cart_label => 'header_cart_label',
97 # About-section + footer trust strip -- editable on every layout.
98 about_image => 'about_image',
99 about_image_pos => 'about_image_pos',
100 about_eyebrow => 'about_eyebrow',
101 about_heading => 'about_heading',
102 footer_trust => 'footer_trust',
103);
104
105my $field = $q->param('field') || '';
106fail('missing field') unless $field;
107fail("field '$field' is not editable") unless exists $ALLOWED{$field};
108my $column = $ALLOWED{$field};
109
110my $value = $q->param('value');
111$value = '' unless defined $value;
112
113# Per-field size caps and basic sanitation.
114my %MAX_LEN = (
115 name => 120,
116 tagline => 255,
117 about_text => 4000,
118 hero_image_1 => 255,
119 hero_image_2 => 255,
120 hero_image_3 => 255,
121 hero_image_1_pos => 20,
122 hero_image_2_pos => 20,
123 hero_image_3_pos => 20,
124 cta_primary_label => 60,
125 cta_secondary_label => 60,
126 featured_badge_label => 60,
127 section_heading => 120,
128 footer_byline => 160,
129 featured_title => 200,
130 featured_price => 40,
131 featured_hero_image => 255,
132 featured_hero_pos => 20,
133 # Catalog-layout hero chrome -- added with the
134 # "edit everything on the page" pass. Must mirror %ALLOWED above;
135 # missing entries here cause substr() to clamp the value to '' and
136 # silently wipe whatever the seller typed.
137 hero_title => 200,
138 hero_pill_label => 60,
139 hero_cta_primary_label => 60,
140 hero_cta_secondary_label => 60,
141 section_eyebrow => 60,
142 header_signin_label => 40,
143 header_cart_label => 40,
144 about_image => 255,
145 about_image_pos => 20,
146 about_eyebrow => 60,
147 about_heading => 120,
148 footer_trust => 255,
149);
150$value = substr($value, 0, $MAX_LEN{$field}) if defined $MAX_LEN{$field} && length($value) > $MAX_LEN{$field};
151
152# Find the user's storefront (single-storefront-per-user for now).
153my $dbh = $db->db_connect();
154my $store = $db->db_readwrite($dbh,
155 qq~SELECT id FROM `${DB}`.storefronts WHERE user_id='$uid' ORDER BY id LIMIT 1~,
156 $ENV{SCRIPT_NAME}, __LINE__);
157unless ($store && $store->{id}) {
158 $db->db_disconnect($dbh);
159 fail('no storefront for this user');
160}
161my $sid = $store->{id};
162
163# Escape ' and \ for the UPDATE. Other chars (HTML tags, line breaks,
164# etc.) are stored as-is -- the field rendering layer escapes for HTML.
165my $safe = $value;
166$safe =~ s/\\/\\\\/g;
167$safe =~ s/'/\\'/g;
168
169# Wrap the UPDATE so a missing column (or any other SQL error) gets
170# reported back to the editor as JSON instead of dying half-way and
171# producing the dreaded "Unexpected end of JSON input" on the client.
172my $err;
173eval {
174 $db->db_readwrite($dbh,
175 qq~UPDATE `${DB}`.storefronts
176 SET `$column` = '$safe'
177 WHERE id = '$sid' LIMIT 1~,
178 $ENV{SCRIPT_NAME}, __LINE__);
179 1;
180} or do { $err = $@ || 'unknown SQL error'; };
181
182$db->db_disconnect($dbh);
183
184if ($err) {
185 # Most likely cause: the storefronts table doesn't have the column
186 # yet -- the schema migration hasn't been run on this database.
187 my $msg = $err;
188 $msg =~ s/[\r\n]+/ /g;
189 $msg =~ s/"/\\"/g;
190 my $hint = '';
191 if ($msg =~ /Unknown column/i) {
192 $hint = " (the column may not exist yet -- run the storefronts ALTER from installation_instructions.html)";
193 }
194 print qq~{"success":0,"error":"SQL error: $msg$hint"}~;
195 $SAVE_DONE = 1;
196 exit;
197}
198
199print qq~{"success":1,"field":"$field"}~;
200$SAVE_DONE = 1;
201exit;
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help