Diff -- /var/www/vhosts/3dshawn.com/admin.3dshawn.com/admin_maintenance.cgi
Diff

/var/www/vhosts/3dshawn.com/admin.3dshawn.com/admin_maintenance.cgi

added on local at 2026-07-01 14:20:15

Added
+607
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to e8a04da15aae
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# Meta-Admin -- /admin_maintenance.cgi
4#
5# Lock meta-admin for non-super-admins. WYSIWYG edit of the message
6# users see while the site is locked.
7#======================================================================
8use strict;
9use warnings;
10use lib '/var/www/vhosts/3dshawn.com/admin.3dshawn.com';
11use CGI;
12use MODS::Login;
13use MODS::DBConnect;
14use MODS::MetaAdmin::Config;
15use MODS::MetaAdmin::Wrapper;
16use MODS::MetaAdmin::SiteConn;
17
18my $q = CGI->new;
19my $form = $q->Vars;
20my $auth = MODS::Login->new;
21my $u = $auth->login_verify;
22
23my $entry = ($ENV{SCRIPT_NAME} || '') =~ m{/([^/]+)\.cgi$} ? $1 : 'admin_maintenance';
24
25if ($entry eq 'admin_maintenance_action') {
26 unless ($u && $u->{is_super_admin}) {
27 print "Status: 403 Forbidden\nContent-Type: text/plain\n\nsuper-admin only\n";
28 exit;
29 }
30 _handle_action($q, $form);
31 exit;
32}
33
34unless ($u) { print "Status: 302 Found\nLocation: /login.cgi\n\n"; exit; }
35unless ($u->{is_super_admin}) {
36 print "Status: 403 Forbidden\nContent-Type: text/html\n\n<h1>Forbidden</h1><p>Only super-admins can manage maintenance mode.</p>\n";
37 exit;
38}
39
40my $cfg = MODS::MetaAdmin::Config->new;
41my $locked = int($cfg->settings('maintenance_locked') || 0);
42my $msg = $cfg->settings('maintenance_message') || '';
43
44sub _esc {
45 my $s = shift; $s //= '';
46 $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g; $s =~ s/"/&quot;/g;
47 return $s;
48}
49
50my $flash = '';
51if ($form->{saved}) { $flash = '<div class="banner success">Maintenance message saved.</div>'; }
52if ($form->{locked}) { $flash = '<div class="banner warn"><strong>Meta-admin is now LOCKED.</strong> Only super-admins can sign in or stay signed in.</div>'; }
53if ($form->{unlocked}) { $flash = '<div class="banner success">Meta-admin is now open.</div>'; }
54if ($form->{applied_locked} || $form->{applied_unlocked}) {
55 my @parts;
56 if ($form->{applied_locked}) { my $n = _esc($form->{applied_locked}); push @parts, "<strong>$n</strong> site" . ($n eq '1' ? '' : 's') . ' LOCKED'; }
57 if ($form->{applied_unlocked}) { my $n = _esc($form->{applied_unlocked}); push @parts, "<strong>$n</strong> site" . ($n eq '1' ? '' : 's') . ' UNLOCKED'; }
58 $flash = '<div class="banner success">' . join(' &middot; ', @parts) . '.</div>';
59}
60if ($form->{applied_none}) { $flash = '<div class="banner">No changes to apply.</div>'; }
61if ($form->{all_locked}) { my $n = _esc($form->{all_locked}); $flash = qq~<div class="banner warn"><strong>$n site~ . ($n eq '1' ? '' : 's') . qq~ now LOCKED.</strong></div>~; }
62if ($form->{all_unlocked}) { my $n = _esc($form->{all_unlocked}); $flash = qq~<div class="banner success">$n site~ . ($n eq '1' ? '' : 's') . qq~ now OPEN.</div>~; }
63if ($form->{site_locked}) { my $s = _esc($form->{site_locked}); $flash = qq~<div class="banner warn"><strong>$s</strong> now LOCKED.</div>~; }
64if ($form->{site_unlocked}) { my $s = _esc($form->{site_unlocked}); $flash = qq~<div class="banner success"><strong>$s</strong> now OPEN.</div>~; }
65if ($form->{err}) { my $e = _esc($form->{err}); $flash = qq~<div class="banner danger">$e</div>~; }
66
67my $msg_h = _esc($msg);
68
69## === Pre-compute portfolio status (8 sites: meta-admin + 7 sister) ===
70my $_sc = MODS::MetaAdmin::SiteConn->new;
71my $_sites = $_sc->list_sites(active => 1);
72my $_meta_locked = int($cfg->settings('maintenance_locked') || 0);
73# Build a combined site rows list with meta-admin first.
74my @combined_rows = ({
75 slug => 'meta-admin',
76 display_name => 'Meta-Admin',
77 brand_color => '#d97706',
78 locked => $_meta_locked,
79 is_meta => 1,
80});
81foreach my $_s (@$_sites) {
82 my $st = $_sc->get_maintenance_status($_s->{slug});
83 push @combined_rows, {
84 slug => $_s->{slug},
85 display_name => $_s->{display_name},
86 brand_color => $_s->{brand_color},
87 locked => $st,
88 is_meta => 0,
89 };
90}
91my $_n_total = scalar @combined_rows;
92my $_n_locked = (grep { $_->{locked} } @combined_rows);
93my $_all_pill_cls = ($_n_locked == $_n_total) ? 'pill-bad' : ($_n_locked == 0 ? 'pill-ok' : 'pill-warn');
94my $_all_status_lbl = ($_n_locked == $_n_total) ? "ALL $_n_total LOCKED" : ($_n_locked == 0 ? "ALL $_n_total OPEN" : "$_n_locked OF $_n_total LOCKED");
95
96my $body = qq~
97<div class="page-head"><div class="left">
98 <span class="page-eyebrow"><span class="dot"></span> Operations</span>
99 <h1 class="page-title">Maintenance mode</h1>
100 <p class="page-subtitle">Block all non-super-admin users from signing in (or staying signed in), with a custom message shown on the login page.</p>
101</div></div>
102$flash
103
104<div class="module">
105 <div class="module-head"><div class="left"><div class="module-title">Message users see while locked</div></div></div>
106 <div class="module-body">
107 <form method="POST" action="/admin_maintenance_action.cgi">
108 <input type="hidden" name="op" value="save_message">
109 <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:12px">
110 <div>
111 <label class="lbl"><span class="t">HTML source</span>
112 <style>
113 #maint_src{width:100%;height:360px;padding:12px 14px;background-color:#0a0a0a;color:#cbd5e1;color-scheme:dark;border:1px solid rgba(255,255,255,.08);border-radius:8px;font-family:'JetBrains Mono','Consolas',monospace;font-size:12px;line-height:1.5;resize:vertical;box-sizing:border-box}
114 </style>
115 <textarea id="maint_src" name="message" spellcheck="false">$msg_h</textarea>
116 </label>
117 <div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:4px;font-size:11px">
118 <button type="button" class="btn btn-sm" onclick="wrap('<strong>','</strong>')">B</button>
119 <button type="button" class="btn btn-sm" onclick="wrap('<em>','</em>')"><em>I</em></button>
120 <button type="button" class="btn btn-sm" onclick="wrap('<h2>','</h2>')">H2</button>
121 <button type="button" class="btn btn-sm" onclick="wrap('<p>','</p>')">P</button>
122 <button type="button" class="btn btn-sm" onclick="ins('<br>')">BR</button>
123 <button type="button" class="btn btn-sm" onclick="linkPrompt()">Link</button>
124 <button type="button" class="btn btn-sm" onclick="resetMsg()">Reset to default</button>
125 </div>
126 </div>
127 <div>
128 <label class="lbl"><span class="t">Live preview</span>
129 <iframe id="maint_preview"
130 style="width:100%;height:360px;background:#0a0a0a;border:1px solid var(--col-border);border-radius:5px"></iframe>
131 </label>
132 <div class="dim" style="font-size:11px;margin-top:4px">Preview updates as you type. Bg is the dark theme users see on /login.cgi.</div>
133 </div>
134 </div>
135 <div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
136 <button type="submit" class="btn btn-primary">Save message</button>
137 <a href="/login.cgi?_preview_maintenance=1" target="_blank" class="btn">Preview lock screen (opens login in new tab)</a>
138 <div class="dim" style="font-size:11px;margin-left:auto">Preview opens meta-admin&#39;s login page with the lock overlay rendered, the way a non-admin would see it.</div>
139 </div>
140 </form>
141 </div>
142</div>
143
144<script>
145(function(){
146 var ta = document.getElementById('maint_src');
147 var pv = document.getElementById('maint_preview');
148 function refresh() {
149 var html = '<style>body{font-family:system-ui,-apple-system,Segoe UI,Arial,sans-serif;background:#0a0a0a;color:#e5e5e5;margin:0;padding:24px;min-height:100vh;display:flex;align-items:center;justify-content:center}</style>' + ta.value;
150 pv.srcdoc = html;
151 }
152 ta.addEventListener('input', refresh);
153 refresh();
154 window.wrap = function(o,c){
155 var s=ta.selectionStart, e=ta.selectionEnd, v=ta.value;
156 ta.value = v.slice(0,s) + o + v.slice(s,e) + c + v.slice(e);
157 ta.focus(); ta.selectionStart=s+o.length; ta.selectionEnd=e+o.length; refresh();
158 };
159 window.ins = function(t){
160 var s=ta.selectionStart, v=ta.value;
161 ta.value = v.slice(0,s) + t + v.slice(s);
162 ta.focus(); ta.selectionStart=ta.selectionEnd=s+t.length; refresh();
163 };
164 window.linkPrompt = function(){
165 var url = prompt('Link URL', 'https://');
166 if (!url) return;
167 wrap('<a href="' + url + '">', '</a>');
168 };
169 window.resetMsg = function(){
170 showConfirm({
171 title: 'Reset message to default?',
172 body: 'Unsaved edits to the lock-screen message will be lost.',
173 confirmLabel: 'Reset',
174 confirmClass: 'btn btn-danger',
175 onConfirm: function(){
176 ta.value = '<div style="text-align:center;padding:40px 20px;max-width:560px;margin:0 auto">' +
177 '<h2 style="margin:0 0 14px;font-size:28px">We will be right back</h2>' +
178 '<p style="margin:0 0 14px;color:#aaa;font-size:16px;line-height:1.6">We are rolling out a quick improvement to the system. Most updates wrap up in under an hour.</p>' +
179 '<p style="margin:24px 0 0;color:#888;font-size:13px">Thanks for your patience. -- The team</p>' +
180 '</div>';
181 refresh();
182 }
183 });
184 };
185 // Override the inline prompt() in linkPrompt with the styled modal.
186 window.linkPrompt = function(){
187 showPrompt({
188 title: 'Insert link',
189 body: 'Enter the URL the selected text should link to.',
190 defaultValue: 'https://',
191 confirmLabel: 'Insert link',
192 onConfirm: function(url){
193 if (!url) return;
194 wrap('<a href="' + url + '">', '</a>');
195 }
196 });
197 };
198})();
199</script>
200
201<!-- === Styled modal (notice/confirm/prompt) -- replaces native alert/confirm/prompt === -->
202<div id="ma-modal-backdrop" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.88);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);z-index:10000;align-items:center;justify-content:center;padding:24px">
203 <div id="ma-modal-card" role="dialog" aria-modal="true" aria-labelledby="ma-modal-title" style="background:#0f0f0f;border:1px solid rgba(217,119,6,0.6);border-radius:12px;max-width:520px;width:100%;padding:32px;box-shadow:0 30px 90px rgba(0,0,0,0.6),0 0 0 1px rgba(217,119,6,0.15);position:relative">
204 <div id="ma-modal-title" style="font-size:20px;font-weight:600;color:#fff;margin-bottom:12px"></div>
205 <div id="ma-modal-body" style="color:#bbb;font-size:14px;line-height:1.55;margin-bottom:22px"></div>
206 <div id="ma-modal-input-wrap" style="display:none;margin-bottom:22px">
207 <input id="ma-modal-input" type="text" style="width:100%;padding:10px 12px;background:#1a1a1f;color:#fff;border:1px solid #333;border-radius:6px;font-size:14px;box-sizing:border-box">
208 </div>
209 <div style="display:flex;gap:10px;justify-content:flex-end">
210 <button id="ma-modal-cancel" type="button" class="btn">Cancel</button>
211 <button id="ma-modal-confirm" type="button" class="btn btn-primary">OK</button>
212 </div>
213 </div>
214</div>
215<script>
216(function(){
217 var backdrop = document.getElementById('ma-modal-backdrop');
218 var card = document.getElementById('ma-modal-card');
219 var titleEl = document.getElementById('ma-modal-title');
220 var bodyEl = document.getElementById('ma-modal-body');
221 var inputWrap= document.getElementById('ma-modal-input-wrap');
222 var inputEl = document.getElementById('ma-modal-input');
223 var cancelBtn= document.getElementById('ma-modal-cancel');
224 var confirmBtn=document.getElementById('ma-modal-confirm');
225 var current = null;
226
227 function close() {
228 backdrop.style.display = 'none';
229 document.body.style.overflow = '';
230 current = null;
231 }
232 function open(opts) {
233 current = opts || {};
234 titleEl.textContent = opts.title || '';
235 bodyEl.textContent = opts.body || '';
236 if (opts.showInput) {
237 inputWrap.style.display = 'block';
238 inputEl.value = opts.defaultValue || '';
239 } else {
240 inputWrap.style.display = 'none';
241 }
242 confirmBtn.textContent = opts.confirmLabel || 'OK';
243 confirmBtn.className = opts.confirmClass || 'btn btn-primary';
244 cancelBtn.style.display = (opts.hideCancel ? 'none' : '');
245 backdrop.style.display = 'flex';
246 document.body.style.overflow = 'hidden';
247 // Default focus to Cancel for destructive ops; to input or confirm otherwise.
248 setTimeout(function(){
249 if (opts.showInput) { inputEl.focus(); inputEl.select(); }
250 else if (opts.confirmClass && opts.confirmClass.indexOf('btn-danger') >= 0) { cancelBtn.focus(); }
251 else { confirmBtn.focus(); }
252 }, 30);
253 }
254
255 cancelBtn.addEventListener('click', function(){
256 var c = current;
257 close();
258 if (c && c.onCancel) c.onCancel();
259 });
260 confirmBtn.addEventListener('click', function(){
261 var c = current;
262 var v = c && c.showInput ? inputEl.value : true;
263 close();
264 if (c && c.onConfirm) c.onConfirm(v);
265 });
266 // Backdrop click closes (treated as cancel).
267 backdrop.addEventListener('click', function(e){
268 if (e.target === backdrop) { cancelBtn.click(); }
269 });
270 // Esc closes (cancel).
271 document.addEventListener('keydown', function(e){
272 if (backdrop.style.display !== 'none' && e.key === 'Escape') { cancelBtn.click(); }
273 });
274 inputEl.addEventListener('keydown', function(e){
275 if (e.key === 'Enter') { e.preventDefault(); confirmBtn.click(); }
276 });
277
278 // Public helpers
279 window.showNotice = function(opts) {
280 open({
281 title: opts.title || 'Notice',
282 body: opts.body || '',
283 confirmLabel: opts.confirmLabel || 'OK',
284 confirmClass: 'btn btn-primary',
285 hideCancel: true
286 });
287 };
288 window.showConfirm = function(opts) {
289 open({
290 title: opts.title || 'Confirm',
291 body: opts.body || '',
292 confirmLabel: opts.confirmLabel || 'OK',
293 confirmClass: opts.confirmClass || 'btn btn-primary',
294 onConfirm: opts.onConfirm,
295 onCancel: opts.onCancel
296 });
297 };
298 window.showPrompt = function(opts) {
299 open({
300 title: opts.title || 'Enter value',
301 body: opts.body || '',
302 confirmLabel: opts.confirmLabel || 'OK',
303 confirmClass: opts.confirmClass || 'btn btn-primary',
304 showInput: true,
305 defaultValue: opts.defaultValue || '',
306 onConfirm: opts.onConfirm,
307 onCancel: opts.onCancel
308 });
309 };
310})();
311</script>
312~;
313
314## === Portfolio-wide maintenance controls ===
315my $sc = MODS::MetaAdmin::SiteConn->new;
316my $sites = $sc->list_sites(active => 1);
317my @site_rows;
318foreach my $s (@$sites) {
319 my $st = $sc->get_maintenance_status($s->{slug});
320 push @site_rows, {
321 slug => $s->{slug},
322 display_name => $s->{display_name},
323 brand_color => $s->{brand_color},
324 locked => $st,
325 };
326}
327my $n_total = scalar @site_rows;
328my $n_locked = (grep { $_->{locked} } @site_rows);
329my $portfolio_all_locked = ($n_total > 0 && $n_locked == $n_total) ? 1 : 0;
330my $portfolio_status_lbl = $portfolio_all_locked ? 'ALL LOCKED' : ($n_locked == 0 ? 'ALL OPEN' : "$n_locked OF $n_total LOCKED");
331my $portfolio_status_cls = $portfolio_all_locked ? 'pill-bad' : ($n_locked == 0 ? 'pill-ok' : 'pill-warn');
332
333my %brand_letters = (
334 webstls => 'WS', ptmatrix => 'PT', affsoft => 'AS', shopcart => 'SC',
335 repricer => 'RP', abforge => 'AB', contactforge => 'CF',
336);
337
338# Per-site lock control (now uses @combined_rows which includes meta-admin)
339%brand_letters = (%brand_letters, 'meta-admin' => 'MT');
340my $portfolio_html = qq~
341<form method="POST" action="/admin_maintenance_action.cgi" id="bulk-form" style="margin:0;margin-bottom:24px">
342 <input type="hidden" name="op" value="apply_state">
343 <div class="module">
344 <div class="module-head"><div class="left"><div class="module-title">Per-site lock control (all 8 sites)</div></div></div>
345 <div class="module-body" style="padding:0">
346 <div style="display:flex;gap:10px;padding:14px 18px;border-bottom:1px solid var(--col-border);align-items:center;flex-wrap:wrap">
347 <span class="pill $_all_pill_cls" style="font-size:13px;padding:3px 12px">$_all_status_lbl</span>
348 <button type="button" onclick="saveChanges()" class="btn btn-primary btn-sm">Save changes</button>
349 <span class="dim" style="font-size:12px">Tick = locked, untick = open. Save to apply.</span>
350 </div>
351 <table class="tbl">
352 <thead><tr>
353 <th style="padding:10px 4px 10px 12px;white-space:nowrap;overflow:visible">
354 <label style="display:inline-flex;align-items:center;gap:8px;cursor:pointer;font-weight:600;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--col-text-dim);vertical-align:middle">
355 <input type="checkbox" id="select-all" onchange="toggleAll(this)" style="width:auto;margin:0;cursor:pointer;vertical-align:middle">
356 Select all
357 </label>
358 </th>
359 <th></th><th>Site</th><th style="text-align:right">Status</th>
360 </tr></thead>
361 <tbody>
362~;
363
364foreach my $r (@combined_rows) {
365 my $name = _esc($r->{display_name});
366 my $color = _esc($r->{brand_color} || '#92400e');
367 my $ltr = _esc($brand_letters{$r->{slug}} || uc(substr($r->{display_name} || '?', 0, 1)));
368 my $st_pill = $r->{locked} ? '<span class="pill pill-bad">LOCKED</span>' : '<span class="pill pill-ok">OPEN</span>';
369 my $slug = _esc($r->{slug});
370 my $meta_badge = $r->{is_meta} ? ' <span class="dim" style="font-size:11px;margin-left:6px">(this console)</span>' : '';
371 # Meta-admin gets its own plate variant (matches the sidebar brand mark).
372 my $plate_class = $r->{is_meta} ? 'brand-icon meta-plate' : 'brand-icon';
373 # Pre-check the checkbox if this site is currently locked, so the
374 # column doubles as a visual status indicator at a glance.
375 # data-current encodes server-side state so the JS diff knows what
376 # actually needs changing when the user hits Save.
377 my $checked = $r->{locked} ? ' checked' : '';
378 my $current = $r->{locked} ? '1' : '0';
379 $portfolio_html .= qq~<tr>
380 <td><input type="checkbox" name="slugs" value="$slug" class="row-cb"$checked data-current="$current" data-name="$name" style="width:auto;margin:0;cursor:pointer"></td>
381 <td><span class="$plate_class" style="--site-glow:$color" data-letter="$ltr"></span></td>
382 <td><strong>$name</strong>$meta_badge</td>
383 <td style="text-align:right">$st_pill</td>
384 </tr>~;
385}
386
387$portfolio_html .= q~
388 </tbody>
389 </table>
390 </div>
391 </div>
392</form>
393<script>
394function saveChanges() {
395 var rows = document.querySelectorAll('.row-cb');
396 var toLock = [], toUnlock = [];
397 rows.forEach(function(cb){
398 var current = cb.getAttribute('data-current') === '1';
399 var wanted = cb.checked;
400 if (wanted === current) return;
401 if (wanted) toLock.push(cb.getAttribute('data-name') || cb.value);
402 else toUnlock.push(cb.getAttribute('data-name') || cb.value);
403 });
404 if (toLock.length === 0 && toUnlock.length === 0) {
405 showNotice({
406 title: 'No changes to save',
407 body: 'The current lock state already matches what is ticked. Adjust the checkboxes first, then Save.'
408 });
409 return;
410 }
411 var lines = [];
412 if (toLock.length) lines.push('LOCK (' + toLock.length + '): ' + toLock.join(', '));
413 if (toUnlock.length) lines.push('UNLOCK (' + toUnlock.length + '): ' + toUnlock.join(', '));
414 var summary = lines.join(' | ');
415 var isDestructive = toLock.length > 0;
416 showConfirm({
417 title: 'Save lock state changes?',
418 body: summary + '. Other sites stay as they are.',
419 confirmLabel: 'Save changes',
420 confirmClass: isDestructive ? 'btn btn-danger' : 'btn btn-primary',
421 onConfirm: function(){ document.getElementById('bulk-form').submit(); }
422 });
423}
424function toggleAll(cb) {
425 document.querySelectorAll('.row-cb').forEach(function(r){ r.checked = cb.checked; });
426}
427</script>
428~;
429
430# Insert the bulk table AFTER the page-head/flash but BEFORE the message editor.
431# Look for the message editor's opening tag and inject before it.
432my $msg_editor_marker = '<div class="module">' . "\n" . ' <div class="module-head"><div class="left"><div class="module-title">Message users see while locked</div></div></div>';
433$body =~ s/\Q$msg_editor_marker\E/$portfolio_html$msg_editor_marker/;
434
435MODS::MetaAdmin::Wrapper->new->render(title => 'Maintenance', page_key => 'maintenance', body => $body, userinfo => $u);
436
437#======================================================================
438# admin_maintenance_action entry: POST handler
439#======================================================================
440sub _handle_action {
441 my ($q, $form) = @_;
442
443 my $db = MODS::DBConnect->new;
444 my $dbh = $db->db_connect;
445 unless ($dbh) {
446 print "Status: 302 Found\nLocation: /admin_maintenance.cgi?err=db_unavailable\n\n";
447 return;
448 }
449
450 my $op = $form->{op} || '';
451
452 if ($op eq 'lock' || $op eq 'unlock') {
453 my $v = ($op eq 'lock') ? '1' : '0';
454 $db->db_readwrite($dbh, qq~
455 INSERT INTO platform_settings (setting_key, setting_value)
456 VALUES ('maintenance_locked', '$v')
457 ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)
458 ~, __FILE__, __LINE__);
459 $db->db_disconnect($dbh);
460 my $flag = ($op eq 'lock') ? 'locked=1' : 'unlocked=1';
461 print "Status: 302 Found\nLocation: /admin_maintenance.cgi?$flag\n\n";
462 return;
463 }
464 elsif ($op eq 'save_message') {
465 my $msg = $form->{message} // '';
466 $msg = substr($msg, 0, 20000) if length($msg) > 20000;
467 my $msg_q = $msg; $msg_q =~ s/\\/\\\\/g; $msg_q =~ s/'/''/g;
468 $db->db_readwrite($dbh, qq~
469 INSERT INTO platform_settings (setting_key, setting_value)
470 VALUES ('maintenance_message', '$msg_q')
471 ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)
472 ~, __FILE__, __LINE__);
473 $db->db_disconnect($dbh);
474 print "Status: 302 Found\nLocation: /admin_maintenance.cgi?saved=1\n\n";
475 return;
476 }
477 elsif ($op eq 'apply_state') {
478 my @checked = $q->multi_param('slugs');
479 if (!@checked && defined $form->{slugs}) {
480 @checked = split /\0/, $form->{slugs};
481 }
482 my %wanted; foreach my $s (@checked) {
483 $s =~ s/[^a-z0-9_\-]//g;
484 $wanted{$s} = 1 if length $s;
485 }
486
487 my $sc = MODS::MetaAdmin::SiteConn->new;
488 my $sites = $sc->list_sites(active => 1);
489 my @all = ({ slug => 'meta-admin', is_meta => 1 });
490 push @all, { slug => $_->{slug}, is_meta => 0 } for @$sites;
491
492 my $locked_n = 0; my $unlocked_n = 0; my $fail_n = 0;
493 foreach my $s (@all) {
494 my $desired = $wanted{ $s->{slug} } ? 1 : 0;
495 my $current;
496 if ($s->{is_meta}) {
497 my $cur = $db->db_readwrite($dbh, q{
498 SELECT setting_value FROM platform_settings WHERE setting_key='maintenance_locked' LIMIT 1
499 }, __FILE__, __LINE__);
500 $current = ($cur && $cur->{setting_value}) ? int($cur->{setting_value}) : 0;
501 } else {
502 $current = $sc->get_maintenance_status($s->{slug});
503 }
504 next if $current == $desired;
505
506 my $ok = 0;
507 if ($s->{is_meta}) {
508 $db->db_readwrite($dbh, qq~
509 INSERT INTO platform_settings (setting_key, setting_value)
510 VALUES ('maintenance_locked', '$desired')
511 ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)
512 ~, __FILE__, __LINE__);
513 $ok = 1;
514 } else {
515 $ok = $sc->set_maintenance_locked($s->{slug}, $desired);
516 }
517 if ($ok) { $desired ? $locked_n++ : $unlocked_n++; } else { $fail_n++; }
518 }
519 $db->db_disconnect($dbh);
520
521 my @flags;
522 push @flags, "applied_locked=$locked_n" if $locked_n;
523 push @flags, "applied_unlocked=$unlocked_n" if $unlocked_n;
524 push @flags, "fail=$fail_n" if $fail_n;
525 push @flags, "applied_none=1" unless @flags;
526 print "Status: 302 Found\nLocation: /admin_maintenance.cgi?" . join('&', @flags) . "\n\n";
527 return;
528 }
529 elsif ($op eq 'lock_selected' || $op eq 'unlock_selected') {
530 my $v = ($op eq 'lock_selected') ? 1 : 0;
531 my @slugs = $q->multi_param('slugs');
532 if (!@slugs && defined $form->{slugs}) {
533 @slugs = split /\0/, $form->{slugs};
534 }
535 my $sc = MODS::MetaAdmin::SiteConn->new;
536 my $ok = 0; my $fail = 0;
537 foreach my $slug (@slugs) {
538 $slug =~ s/[^a-z0-9_\-]//g;
539 next unless length $slug;
540 if ($slug eq 'meta-admin' || $slug eq 'metaadmin') {
541 $db->db_readwrite($dbh, qq~
542 INSERT INTO platform_settings (setting_key, setting_value)
543 VALUES ('maintenance_locked', '$v')
544 ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)
545 ~, __FILE__, __LINE__);
546 $ok++;
547 }
548 elsif ($sc->set_maintenance_locked($slug, $v)) { $ok++; }
549 else { $fail++; }
550 }
551 $db->db_disconnect($dbh);
552 my $flag = ($op eq 'lock_selected') ? "all_locked=$ok" : "all_unlocked=$ok";
553 print "Status: 302 Found\nLocation: /admin_maintenance.cgi?$flag" . ($fail ? "&fail=$fail" : "") . "\n\n";
554 return;
555 }
556 elsif ($op eq 'lock_all_8' || $op eq 'unlock_all_8') {
557 my $v = ($op eq 'lock_all_8') ? 1 : 0;
558 $db->db_readwrite($dbh, qq~
559 INSERT INTO platform_settings (setting_key, setting_value)
560 VALUES ('maintenance_locked', '$v')
561 ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)
562 ~, __FILE__, __LINE__);
563 my $sc = MODS::MetaAdmin::SiteConn->new;
564 my $sites = $sc->list_sites(active => 1);
565 my $ok = 1;
566 my $fail = 0;
567 foreach my $s (@$sites) {
568 if ($sc->set_maintenance_locked($s->{slug}, $v)) { $ok++; } else { $fail++; }
569 }
570 $db->db_disconnect($dbh);
571 my $flag = ($op eq 'lock_all_8') ? "all_locked=$ok" : "all_unlocked=$ok";
572 print "Status: 302 Found\nLocation: /admin_maintenance.cgi?$flag" . ($fail ? "&fail=$fail" : "") . "\n\n";
573 return;
574 }
575 elsif ($op eq 'lock_all' || $op eq 'unlock_all') {
576 my $v = ($op eq 'lock_all') ? 1 : 0;
577 my $sc = MODS::MetaAdmin::SiteConn->new;
578 my $sites = $sc->list_sites(active => 1);
579 my $ok = 0; my $fail = 0;
580 foreach my $s (@$sites) {
581 if ($sc->set_maintenance_locked($s->{slug}, $v)) { $ok++; } else { $fail++; }
582 }
583 $db->db_disconnect($dbh);
584 my $flag = ($op eq 'lock_all') ? "all_locked=$ok" : "all_unlocked=$ok";
585 print "Status: 302 Found\nLocation: /admin_maintenance.cgi?$flag" . ($fail ? "&fail=$fail" : "") . "\n\n";
586 return;
587 }
588 elsif ($op eq 'lock_site' || $op eq 'unlock_site') {
589 my $slug = $form->{slug} || '';
590 $slug =~ s/[^a-z0-9_\-]//g;
591 unless (length $slug) {
592 $db->db_disconnect($dbh);
593 print "Status: 302 Found\nLocation: /admin_maintenance.cgi?err=missing_slug\n\n";
594 return;
595 }
596 my $v = ($op eq 'lock_site') ? 1 : 0;
597 my $sc = MODS::MetaAdmin::SiteConn->new;
598 my $ok = $sc->set_maintenance_locked($slug, $v);
599 $db->db_disconnect($dbh);
600 my $flag = $ok ? ($v ? "site_locked=$slug" : "site_unlocked=$slug") : "err=" . ($sc->last_error || 'failed');
601 print "Status: 302 Found\nLocation: /admin_maintenance.cgi?$flag\n\n";
602 return;
603 }
604
605 $db->db_disconnect($dbh);
606 print "Status: 302 Found\nLocation: /admin_maintenance.cgi?err=unknown_op\n\n";
607}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help