added on local at 2026-07-01 13:47:41
| 1 | #!/usr/bin/perl | |
| 2 | #====================================================================== | |
| 3 | # WebSTLs - Seller Promotion Actions | |
| 4 | # | |
| 5 | # Handles create/update/delete/activate/pause for promotions owned by | |
| 6 | # the logged-in seller. Every action checks ownership via Promotions | |
| 7 | # helper before mutating. | |
| 8 | #====================================================================== | |
| 9 | use strict; | |
| 10 | use warnings; | |
| 11 | ||
| 12 | use lib '/var/www/vhosts/3dshawn.com/affiliate.3dshawn.com'; | |
| 13 | use CGI; | |
| 14 | use MODS::DBConnect; | |
| 15 | use MODS::Login; | |
| 16 | use MODS::AffSoft::Config; | |
| 17 | use MODS::AffSoft::Promotions; | |
| 18 | ||
| 19 | my $q = CGI->new; | |
| 20 | my $form = $q->Vars; | |
| 21 | my $auth = MODS::Login->new; | |
| 22 | my $db = MODS::DBConnect->new; | |
| 23 | my $cfg = MODS::AffSoft::Config->new; | |
| 24 | my $promo = MODS::AffSoft::Promotions->new; | |
| 25 | my $DB = $cfg->settings('database_name'); | |
| 26 | ||
| 27 | $|=1; | |
| 28 | my $userinfo = $auth->login_verify(); | |
| 29 | if (!$userinfo) { | |
| 30 | print "Status: 302 Found\nLocation: /login.cgi\n\n"; | |
| 31 | exit; | |
| 32 | } | |
| 33 | require MODS::AffSoft::Permissions; | |
| 34 | MODS::AffSoft::Permissions->new->require_feature($userinfo, 'edit_promotions'); | |
| 35 | my $uid = $userinfo->{user_id}; | |
| 36 | $uid =~ s/[^0-9]//g; | |
| 37 | ||
| 38 | my $act = defined $form->{act} ? $form->{act} : ''; | |
| 39 | ||
| 40 | my $dbh = $db->db_connect(); | |
| 41 | unless ($promo->schema_ready($db, $dbh, $DB)) { | |
| 42 | $db->db_disconnect($dbh); | |
| 43 | _redirect('/promotions.cgi?err=schema'); | |
| 44 | exit; | |
| 45 | } | |
| 46 | ||
| 47 | # Find the seller's primary storefront -- new rows are attached to it. | |
| 48 | my $sf = $db->db_readwrite($dbh, | |
| 49 | qq~SELECT id FROM `${DB}`.storefronts WHERE user_id='$uid' ORDER BY id LIMIT 1~, | |
| 50 | $ENV{SCRIPT_NAME}, __LINE__); | |
| 51 | my $store_id = ($sf && $sf->{id}) ? $sf->{id} : 0; | |
| 52 | ||
| 53 | if ($act eq 'create') { | |
| 54 | unless ($store_id) { | |
| 55 | $db->db_disconnect($dbh); | |
| 56 | _redirect('/promotions.cgi?err=no_storefront'); | |
| 57 | exit; | |
| 58 | } | |
| 59 | my $new_id = $promo->create($db, $dbh, $DB, | |
| 60 | scope => 'storefront', | |
| 61 | storefront_id => $store_id, | |
| 62 | kind => $form->{kind}, | |
| 63 | name => $form->{name}, | |
| 64 | description => $form->{description}, | |
| 65 | discount_type => $form->{discount_type}, | |
| 66 | discount_value_bp => _percent_to_bp($form->{discount_value_pct}), | |
| 67 | discount_value_cents => _dollars_to_cents($form->{discount_value_amt}), | |
| 68 | minimum_order_cents => _dollars_to_cents($form->{minimum_order}), | |
| 69 | code => $form->{code}, | |
| 70 | public_slug => $form->{public_slug}, | |
| 71 | starts_at => $form->{starts_at_local}, | |
| 72 | ends_at => $form->{ends_at_local}, | |
| 73 | max_redemptions => $form->{max_redemptions}, | |
| 74 | max_per_user => $form->{max_per_user}, | |
| 75 | new_customers_only => $form->{new_customers_only}, | |
| 76 | status => $form->{publish_now} ? 'active' : 'draft', | |
| 77 | created_by_user_id => $uid, | |
| 78 | ); | |
| 79 | unless ($new_id) { | |
| 80 | $db->db_disconnect($dbh); | |
| 81 | _redirect('/promotions.cgi?err=bad_input'); | |
| 82 | exit; | |
| 83 | } | |
| 84 | _attach_models($promo, $db, $dbh, $DB, $new_id, $form); | |
| 85 | $db->db_disconnect($dbh); | |
| 86 | _redirect('/promotions.cgi?ok=created'); | |
| 87 | exit; | |
| 88 | } | |
| 89 | ||
| 90 | if ($act eq 'update') { | |
| 91 | my $id = $form->{id}; $id =~ s/[^0-9]//g; | |
| 92 | my $row = $id ? $promo->get_for_seller($db, $dbh, $DB, $id, $uid) : {}; | |
| 93 | unless ($row && $row->{id}) { | |
| 94 | $db->db_disconnect($dbh); | |
| 95 | _redirect('/promotions.cgi?err=denied'); | |
| 96 | exit; | |
| 97 | } | |
| 98 | my $ok = $promo->update($db, $dbh, $DB, $id, | |
| 99 | scope => 'storefront', | |
| 100 | storefront_id => $store_id, | |
| 101 | kind => $form->{kind}, | |
| 102 | name => $form->{name}, | |
| 103 | description => $form->{description}, | |
| 104 | discount_type => $form->{discount_type}, | |
| 105 | discount_value_bp => _percent_to_bp($form->{discount_value_pct}), | |
| 106 | discount_value_cents => _dollars_to_cents($form->{discount_value_amt}), | |
| 107 | minimum_order_cents => _dollars_to_cents($form->{minimum_order}), | |
| 108 | code => $form->{code}, | |
| 109 | public_slug => $form->{public_slug}, | |
| 110 | starts_at => $form->{starts_at_local}, | |
| 111 | ends_at => $form->{ends_at_local}, | |
| 112 | max_redemptions => $form->{max_redemptions}, | |
| 113 | max_per_user => $form->{max_per_user}, | |
| 114 | new_customers_only => $form->{new_customers_only}, | |
| 115 | ); | |
| 116 | unless ($ok) { | |
| 117 | $db->db_disconnect($dbh); | |
| 118 | _redirect('/promotions.cgi?err=bad_input'); | |
| 119 | exit; | |
| 120 | } | |
| 121 | # Refresh model attachments. | |
| 122 | $promo->clear_models($db, $dbh, $DB, $id); | |
| 123 | _attach_models($promo, $db, $dbh, $DB, $id, $form); | |
| 124 | ||
| 125 | $db->db_disconnect($dbh); | |
| 126 | _redirect('/promotions.cgi?ok=updated'); | |
| 127 | exit; | |
| 128 | } | |
| 129 | ||
| 130 | if ($act eq 'activate' || $act eq 'pause' || $act eq 'archive') { | |
| 131 | my $id = $form->{id}; $id =~ s/[^0-9]//g; | |
| 132 | my $row = $id ? $promo->get_for_seller($db, $dbh, $DB, $id, $uid) : {}; | |
| 133 | unless ($row && $row->{id}) { | |
| 134 | $db->db_disconnect($dbh); | |
| 135 | _redirect('/promotions.cgi?err=denied'); | |
| 136 | exit; | |
| 137 | } | |
| 138 | my $new_status = | |
| 139 | $act eq 'activate' ? 'active' : | |
| 140 | $act eq 'pause' ? 'paused' : | |
| 141 | 'archived'; | |
| 142 | $promo->set_status($db, $dbh, $DB, $id, $new_status); | |
| 143 | $db->db_disconnect($dbh); | |
| 144 | _redirect('/promotions.cgi?ok=' . ($act eq 'activate' ? 'activated' : $act eq 'pause' ? 'paused' : 'updated')); | |
| 145 | exit; | |
| 146 | } | |
| 147 | ||
| 148 | if ($act eq 'delete') { | |
| 149 | my $id = $form->{id}; $id =~ s/[^0-9]//g; | |
| 150 | my $row = $id ? $promo->get_for_seller($db, $dbh, $DB, $id, $uid) : {}; | |
| 151 | unless ($row && $row->{id}) { | |
| 152 | $db->db_disconnect($dbh); | |
| 153 | _redirect('/promotions.cgi?err=denied'); | |
| 154 | exit; | |
| 155 | } | |
| 156 | $promo->delete_promo($db, $dbh, $DB, $id); | |
| 157 | $db->db_disconnect($dbh); | |
| 158 | _redirect('/promotions.cgi?ok=deleted'); | |
| 159 | exit; | |
| 160 | } | |
| 161 | ||
| 162 | $db->db_disconnect($dbh); | |
| 163 | _redirect('/promotions.cgi?err=unknown_act'); | |
| 164 | ||
| 165 | # ------------------------------------------------------------- helpers | |
| 166 | sub _redirect { | |
| 167 | my $url = shift; | |
| 168 | print "Status: 302 Found\nLocation: $url\n\n"; | |
| 169 | } | |
| 170 | ||
| 171 | sub _percent_to_bp { | |
| 172 | my $v = shift; | |
| 173 | return 0 unless defined $v && $v ne ''; | |
| 174 | $v =~ s/[^0-9.]//g; | |
| 175 | return 0 unless length $v; | |
| 176 | my $bp = int($v * 100); | |
| 177 | $bp = 0 if $bp < 0; | |
| 178 | $bp = 10000 if $bp > 10000; | |
| 179 | return $bp; | |
| 180 | } | |
| 181 | ||
| 182 | sub _dollars_to_cents { | |
| 183 | my $v = shift; | |
| 184 | return 0 unless defined $v && $v ne ''; | |
| 185 | $v =~ s/[^0-9.]//g; | |
| 186 | return 0 unless length $v; | |
| 187 | return int($v * 100 + 0.5); | |
| 188 | } | |
| 189 | ||
| 190 | # Pull the selected model ids out of the form (checkbox names like | |
| 191 | # model_id[]). Attach each at the requested role. | |
| 192 | sub _attach_models { | |
| 193 | my ($promo, $db, $dbh, $DB, $promo_id, $form) = @_; | |
| 194 | # CGI may store multi-valued fields as "\0"-joined strings. | |
| 195 | my @ids; | |
| 196 | my $raw = $form->{'model_ids'}; | |
| 197 | if (defined $raw && $raw ne '') { | |
| 198 | @ids = split(/\0/, $raw); | |
| 199 | } | |
| 200 | foreach my $mid (@ids) { | |
| 201 | $mid =~ s/[^0-9]//g; | |
| 202 | next unless $mid; | |
| 203 | my $role = $form->{"role_$mid"} || 'target'; | |
| 204 | $promo->add_model($db, $dbh, $DB, $promo_id, $mid, $role); | |
| 205 | } | |
| 206 | } |