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

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

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

Added
+274
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to f38c719d7adf
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#############################################################
20my $TRIGGERS = [
21 ['task_created', 'When a task is created'],
22 ['task_status_changed', 'When a task changes status'],
23 ['task_assigned', 'When a task is reassigned'],
24 ['custom_field_set', 'When a custom field is set / changed'],
25 ['department_routed', 'When a ticket is routed to a department'],
26];
27my $STATUSES = [qw(open in_progress blocked review done cancelled)];
28my $PRIORITIES = [qw(low medium high urgent critical)];
29my $ACTIONS = [
30 ['set_priority', 'Set priority to...'],
31 ['set_status', 'Set status to...'],
32 ['assign_user', 'Assign to user...'],
33 ['add_comment', 'Post a comment...'],
34 ['notify_user', 'Send a notification to...'],
35 ['send_webhook', 'Send outbound webhook (any HTTPS endpoint)...'],
36];
37
38
39
40############################################################
41# Modules
42############################################################
43use CGI; my $query = new CGI; my $form = $query->Vars;
44use MODS::Login; my $login = new MODS::Login;
45use MODS::PTMatrix::Urls; my $getlist = new MODS::PTMatrix::Urls; my $url = $getlist->urls();
46use MODS::Template; my $tfile = new MODS::Template;
47use MODS::PTMatrix::Wrapper; my $load = new MODS::PTMatrix::Wrapper;
48use MODS::DBConnect; my $db = new MODS::DBConnect;
49use MODS::PTMatrix::Config; my $config = new MODS::PTMatrix::Config; my $database_name = $config->settings('database_name');
50use MODS::PTMatrix::PM;
51
52
53
54#############################################################
55## Get form data
56#############################################################
57my %rawform = %$form;
58
59foreach my $line(keys %$form){
60 $form->{$line} =~ s/[^!-~\s]//g;
61 $form->{$line} = quotemeta("$form->{$line}");
62}
63
64
65
66############################################################
67# Program Controls
68############################################################
69$|=1;
70my $userinfo = $login->login_verify();
71print qq~Content-type:text/html; Cache-Control:no-cache;\n\n~;
72if(!$userinfo){print qq~<script>window.location="$url->{login}";</script>~;}
73elsif(!MODS::PTMatrix::PM::get_company_id($userinfo)){print qq~<script>window.location="$url->{admin_companies}";</script>~;}
74elsif(!MODS::PTMatrix::PM::is_company_admin($userinfo)){print qq~<script>window.location="$url->{dashboard}";</script>~;}
75elsif(($ENV{REQUEST_METHOD}||'') eq 'POST'){&automation_action;}
76else{&automations_list;}
77
78
79############################################################
80# Subroutines
81############################################################
82#Build a <select> option list from a flat list or list-of-pairs
83sub opts{
84 my($list, $selected, $blank) = @_;
85 my $h = $blank ? qq~<option value="">$blank</option>~ : '';
86 for my $row(@$list){
87 my($val, $label) = ref($row) ? @$row : ($row, $row);
88 my $sel = (defined($selected) && $selected eq $val) ? ' selected' : '';
89 $h .= qq~<option value="$val"$sel>$label</option>~;
90 }
91 return $h;
92}
93
94sub proj_opts{
95 my($projects, $selected) = @_;
96 my $h = '<option value="">&mdash; any project &mdash;</option>';
97 for my $p(@$projects){
98 my $sel = ($selected && $p->{id} == $selected) ? ' selected' : '';
99 my $disp = MODS::PTMatrix::PM::h("$p->{key_prefix} &mdash; $p->{name}");
100 $h .= qq~<option value="$p->{id}"$sel>$disp</option>~;
101 }
102 return $h;
103}
104
105sub user_opts{
106 my($users, $selected) = @_;
107 my $h = '<option value="">&mdash; pick a user &mdash;</option>';
108 for my $usr(@$users){
109 my $sel = ($selected && $usr->{id} == $selected) ? ' selected' : '';
110 my $disp = MODS::PTMatrix::PM::h($usr->{display_name});
111 $h .= qq~<option value="$usr->{id}"$sel>$disp</option>~;
112 }
113 return $h;
114}
115
116sub automation_action{
117 my $cid = MODS::PTMatrix::PM::get_company_id($userinfo);
118 my $uid = MODS::PTMatrix::PM::get_user_id($userinfo);
119
120 my $dbh = $db->db_connect();
121 my $act = $form->{act} || '';
122 my $flash = '';
123
124 if($act eq 'save'){
125 my $id = MODS::PTMatrix::PM::safe_int($form->{id});
126 my $name = $rawform{name} || '';
127 my $pid = MODS::PTMatrix::PM::safe_int($form->{project_id});
128 my $is_active = $form->{is_active} ? 1 : 0;
129
130 #Allowlist every selectable - drop anything we don't recognize
131 my $trigger = $form->{trigger_kind} || '';
132 $trigger = '' unless grep { $_->[0] eq $trigger } @$TRIGGERS;
133 my $from_s = $form->{trigger_from_status} || '';
134 $from_s = '' unless grep { $_ eq $from_s } @$STATUSES;
135 my $to_s = $form->{trigger_to_status} || '';
136 $to_s = '' unless grep { $_ eq $to_s } @$STATUSES;
137 my $cond_p = $form->{cond_priority} || '';
138 $cond_p = '' unless grep { $_ eq $cond_p } @$PRIORITIES;
139 my $action = $form->{action_kind} || '';
140 $action = '' unless grep { $_->[0] eq $action } @$ACTIONS;
141 my $a_prio = $form->{action_priority} || '';
142 $a_prio = '' unless grep { $_ eq $a_prio } @$PRIORITIES;
143 my $a_status = $form->{action_status} || '';
144 $a_status = '' unless grep { $_ eq $a_status } @$STATUSES;
145
146 my $a_uid = MODS::PTMatrix::PM::safe_int($form->{action_user_id});
147 my $a_text = substr($rawform{action_comment_text} || '', 0, 500);
148 my $a_webhook = $rawform{action_webhook_url} || '';
149 $a_webhook = '' unless $a_webhook =~ m{^https?://};
150 $a_webhook = substr($a_webhook, 0, 500);
151
152 if(!$name || !$trigger || !$action){
153 $flash = 'Name, trigger, and action are required.';
154 }else{
155 my $name_q = MODS::PTMatrix::PM::sql_quote(substr($name, 0, 120));
156 my $text_q = MODS::PTMatrix::PM::sql_quote($a_text);
157 my $webhook_q = MODS::PTMatrix::PM::sql_quote($a_webhook);
158 my $pid_sql = $pid ? "'$pid'" : 'NULL';
159 my $from_sql = length($from_s) ? "'$from_s'" : 'NULL';
160 my $to_sql = length($to_s) ? "'$to_s'" : 'NULL';
161 my $cp_sql = length($cond_p) ? "'$cond_p'" : 'NULL';
162 my $ap_sql = length($a_prio) ? "'$a_prio'" : 'NULL';
163 my $as_sql = length($a_status)? "'$a_status'" : 'NULL';
164 my $au_sql = $a_uid ? "'$a_uid'" : 'NULL';
165
166 if($id){
167 my $sql = qq~UPDATE `${database_name}`.automations SET name='$name_q', project_id=$pid_sql, is_active='$is_active', trigger_kind='$trigger', trigger_from_status=$from_sql, trigger_to_status=$to_sql, cond_priority=$cp_sql, action_kind='$action', action_priority=$ap_sql, action_status=$as_sql, action_user_id=$au_sql, action_comment_text='$text_q', action_webhook_url='$webhook_q' WHERE id='$id' AND company_id='$cid'~;
168 $db->db_readwrite($dbh, $sql, $ENV{SCRIPT_NAME}, __LINE__);
169 $flash = "Automation <strong>$name_q</strong> saved.";
170 }else{
171 my $sql = qq~INSERT INTO `${database_name}`.automations (company_id, project_id, name, is_active, trigger_kind, trigger_from_status, trigger_to_status, cond_priority, action_kind, action_priority, action_status, action_user_id, action_comment_text, action_webhook_url, created_by_user_id, created_at) VALUES ('$cid', $pid_sql, '$name_q', '$is_active', '$trigger', $from_sql, $to_sql, $cp_sql, '$action', $ap_sql, $as_sql, $au_sql, '$text_q', '$webhook_q', '$uid', NOW())~;
172 $db->db_readwrite($dbh, $sql, $ENV{SCRIPT_NAME}, __LINE__);
173 $flash = "Automation <strong>$name_q</strong> created.";
174 }
175 }
176 }elsif($act eq 'toggle_active'){
177 my $id = MODS::PTMatrix::PM::safe_int($form->{id});
178 $db->db_readwrite($dbh, qq~UPDATE `${database_name}`.automations SET is_active=1-is_active WHERE id='$id' AND company_id='$cid'~, $ENV{SCRIPT_NAME}, __LINE__);
179 $flash = 'Toggled.';
180 }elsif($act eq 'delete'){
181 my $id = MODS::PTMatrix::PM::safe_int($form->{id});
182 $db->db_readwrite($dbh, qq~DELETE FROM `${database_name}`.automations WHERE id='$id' AND company_id='$cid'~, $ENV{SCRIPT_NAME}, __LINE__);
183 $flash = 'Deleted.';
184 }
185
186 $db->db_disconnect($dbh);
187 &automations_list($flash);
188}
189
190sub automations_list{
191 my($flash) = @_;
192 $flash ||= '';
193
194 my $cid = MODS::PTMatrix::PM::get_company_id($userinfo);
195 my $dbh = $db->db_connect();
196
197 #------------------------------------------------------------------------------------------------------
198 #Active rules first, newest first
199 my $sql_r = qq~SELECT a.*, p.name AS project_name, p.key_prefix, au.display_name AS action_user_name FROM `${database_name}`.automations a LEFT JOIN `${database_name}`.projects p ON p.id = a.project_id LEFT JOIN `${database_name}`.users au ON au.id = a.action_user_id WHERE a.company_id='$cid' ORDER BY a.is_active DESC, a.id DESC~;
200 my @rules = $db->db_readwrite_multiple($dbh, $sql_r, $ENV{SCRIPT_NAME}, __LINE__);
201
202 #All projects for the per-rule filter dropdown
203 my @projects = $db->db_readwrite_multiple($dbh, qq~SELECT id, name, key_prefix FROM `${database_name}`.projects WHERE company_id='$cid' ORDER BY name~, $ENV{SCRIPT_NAME}, __LINE__);
204
205 #Active users for the assign-to dropdown
206 my @users = $db->db_readwrite_multiple($dbh, qq~SELECT id, display_name, email FROM `${database_name}`.users WHERE company_id='$cid' AND account_status='active' ORDER BY display_name~, $ENV{SCRIPT_NAME}, __LINE__);
207 #------------------------------------------------------------------------------------------------------
208
209 $db->db_disconnect($dbh);
210
211 #Decorate each rule with its pre-rendered select-option HTML so the template
212 #doesn't have to do template-side iteration of pickers (which the loop engine
213 #handles awkwardly inside nested loops)
214 foreach my $r(@rules){
215 $r->{name} = MODS::PTMatrix::PM::h($r->{name});
216 $r->{action_comment_text} = MODS::PTMatrix::PM::h($r->{action_comment_text} || '');
217 $r->{action_webhook_url} = MODS::PTMatrix::PM::h($r->{action_webhook_url} || '');
218 $r->{project_display} = $r->{project_name} ? MODS::PTMatrix::PM::h("$r->{key_prefix} $r->{project_name}") : 'any project';
219 $r->{toggle_label} = $r->{is_active} ? 'Disable' : 'Enable';
220 $r->{active_attr} = $r->{is_active} ? 'checked' : '';
221 $r->{project_options_html} = &proj_opts(\@projects, $r->{project_id});
222 $r->{trigger_options_html} = &opts($TRIGGERS, $r->{trigger_kind});
223 $r->{action_options_html} = &opts($ACTIONS, $r->{action_kind});
224 $r->{from_options_html} = &opts($STATUSES, $r->{trigger_from_status}, '&mdash; any &mdash;');
225 $r->{to_options_html} = &opts($STATUSES, $r->{trigger_to_status}, '&mdash; any &mdash;');
226 $r->{cond_prio_options_html} = &opts($PRIORITIES, $r->{cond_priority}, '&mdash; any &mdash;');
227 $r->{action_prio_options_html} = &opts($PRIORITIES, $r->{action_priority}, '');
228 $r->{action_status_options_html} = &opts($STATUSES, $r->{action_status}, '');
229 $r->{user_options_html} = &user_opts(\@users, $r->{action_user_id});
230
231 $r->{trigger_label} = '';
232 for my $t(@$TRIGGERS){$r->{trigger_label} = $t->[1] if $t->[0] eq $r->{trigger_kind};}
233 $r->{action_label} = '';
234 for my $a(@$ACTIONS){$r->{action_label} = $a->[1] if $a->[0] eq $r->{action_kind};}
235 }
236
237 #Empty-rule pickers for the "create new" form
238 my $new_proj_opts = &proj_opts(\@projects, 0);
239 my $new_user_opts = &user_opts(\@users, 0);
240 my $new_trigger_opts = &opts($TRIGGERS, '');
241 my $new_action_opts = &opts($ACTIONS, '');
242 my $new_from_opts = &opts($STATUSES, '', '&mdash; any &mdash;');
243 my $new_to_opts = &opts($STATUSES, '', '&mdash; any &mdash;');
244 my $new_cond_prio_opts = &opts($PRIORITIES, '', '&mdash; any &mdash;');
245 my $new_action_prio_opts = &opts($PRIORITIES, '');
246 my $new_action_status_opts = &opts($STATUSES, '');
247
248 #Create all template variables
249 my $tvars;
250 $tvars->{flash} = $flash;
251 $tvars->{has_flash} = length($flash) ? 1 : 0;
252 $tvars->{rules} = \@rules;
253 $tvars->{has_rules} = scalar(@rules) ? 1 : 0;
254 $tvars->{has_projects} = scalar(@projects) ? 1 : 0;
255 $tvars->{has_users} = scalar(@users) ? 1 : 0;
256 $tvars->{new_proj_opts} = $new_proj_opts;
257 $tvars->{new_user_opts} = $new_user_opts;
258 $tvars->{new_trigger_opts} = $new_trigger_opts;
259 $tvars->{new_action_opts} = $new_action_opts;
260 $tvars->{new_from_opts} = $new_from_opts;
261 $tvars->{new_to_opts} = $new_to_opts;
262 $tvars->{new_cond_prio_opts} = $new_cond_prio_opts;
263 $tvars->{new_action_prio_opts} = $new_action_prio_opts;
264 $tvars->{new_action_status_opts} = $new_action_status_opts;
265
266 my $body = join('', $tfile->template('tf_automations.html', $tvars, $userinfo));
267
268 $load->render({
269 userinfo => $userinfo,
270 page_key => 'automations',
271 title => 'Automations',
272 body => $body,
273 });
274}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help