xaizek / rocketgit (License: AGPLv3+) (since 2018-12-09)
Light and fast Git hosting solution suitable to serve both as a hub or as a personal code storage with its tickets, pull requests, API and much more.
Commit 05068314021bbdf6f26bc92bee47177b170b2a1c

Added possibility for admin to mail users
Author: Catalin(ux) M. BOIE
Author date (UTC): 2020-08-23 14:43
Committer name: Catalin(ux) M. BOIE
Committer date (UTC): 2020-08-23 14:43
Parent(s): e4a39879513e000b5d9be588201916950fa629fc
Signing key:
Tree: 93a633580fa958feadb6f20122ad28001dff3472
File Lines added Lines deleted
inc/admin.inc.php 273 16
inc/admin/admin.php 3 2
inc/admin/mails/mails.php 21 0
inc/util.inc.php 8 5
root/themes/default/admin/mails/invites/invites.html 1 1
root/themes/default/admin/mails/menu.html 6 0
root/themes/default/admin/mails/users/form.html 90 0
root/themes/default/admin/mails/users/queued.html 3 0
root/themes/default/admin/menu.html 1 1
root/themes/default/hints/admin/mails_users.html 14 0
tests/http_admin.php 1 1
File inc/admin.inc.php changed (mode: 100644) (index 2cabba6..af420bb)
... ... $rg_admin_functions = array(
12 12 6001 => "rg_admin_invite_one", 6001 => "rg_admin_invite_one",
13 13 // new new style // new new style
14 14 'admin_invite' => 'rg_admin_invite', 'admin_invite' => 'rg_admin_invite',
15 'admin_invite_one' => 'rg_admin_invite_one'
15 'admin_invite_one' => 'rg_admin_invite_one',
16 'admin_mails_users' => 'rg_admin_mails_users',
17 'admin_mails_users_one' => 'rg_admin_mails_users_one',
16 18 ); );
17 19 rg_event_register_functions($rg_admin_functions); rg_event_register_functions($rg_admin_functions);
18 20
21
22 /*
23 * Event for invites (one e-mail version)
24 */
25 function rg_admin_invite_one($db, $event)
26 {
27 global $rg_admin_email, $rg_admin_name;
28
29 $admin_name = "=?UTF-8?B?"
30 . base64_encode($rg_admin_name) . "?=";
31
32 $rg = array();
33 $subject = str_replace('{NAME}', $event['name'], $event['subject']);
34 $subject = "=?UTF-8?B?" . base64_encode(trim($subject)) . "?=";
35 $header = rg_template("mail/common.head.txt", $rg, FALSE /*xss*/);
36 $header = trim($header);
37 $header .= "\nFrom: $admin_name <" . $rg_admin_email . ">";
38 rg_log_ml("DEBUG: header=$header");
39 $body = str_replace('{NAME}', $event['name'], $event['body']);
40
41 $r = mail($event['email'], $subject, $body, $header,
42 "-f $rg_admin_email");
43
44 if ($r === FALSE)
45 return FALSE;
46
47 return array();
48 }
49
19 50 /* /*
20 51 * Event for invites * Event for invites
21 52 */ */
 
... ... function rg_admin_invite($db, $event)
45 76 } }
46 77
47 78 /* /*
48 * Event for invites (one e-mail version)
79 * Event for mails/users (one e-mail version)
49 80 */ */
50 function rg_admin_invite_one($db, $event)
81 function rg_admin_mails_users_one($db, $ev)
51 82 { {
52 83 global $rg_admin_email, $rg_admin_name; global $rg_admin_email, $rg_admin_name;
53 84
54 $admin_name = "=?UTF-8?B?"
55 . base64_encode($rg_admin_name) . "?=";
85 rg_log_ml('admin_mails_users_one: ev: ' . print_r($ev, TRUE));
86
87 $admin_name = '=?UTF-8?B?'
88 . base64_encode($rg_admin_name) . '?=';
56 89
57 90 $rg = array(); $rg = array();
58 $subject = str_replace('{NAME}', $event['name'], $event['subject']);
59 $subject = "=?UTF-8?B?" . base64_encode(trim($subject)) . "?=";
60 $header = rg_template("mail/common.head.txt", $rg, FALSE /*xss*/);
91 $subject = '=?UTF-8?B?' . base64_encode(trim($ev['subject'])) . '?=';
92 $header = rg_template('mail/common.head.txt', $rg, FALSE /*xss*/);
61 93 $header = trim($header); $header = trim($header);
62 $header .= "\nFrom: $admin_name <" . $rg_admin_email . ">";
63 rg_log_ml("DEBUG: header=$header");
64 $body = str_replace('{NAME}', $event['name'], $event['body']);
94 $header .= "\n" . 'From: ' . $admin_name . ' <' . $rg_admin_email . '>';
95 rg_log_ml('DEBUG: header=' . $header);
65 96
66 $r = mail($event['email'], $subject, $body, $header,
67 "-f $rg_admin_email");
97 $r = mail($ev['email'], $subject, $ev['body'], $header,
98 '-f ' . $rg_admin_email);
68 99
69 100 if ($r === FALSE) if ($r === FALSE)
70 101 return FALSE; return FALSE;
 
... ... function rg_admin_invite_one($db, $event)
72 103 return array(); return array();
73 104 } }
74 105
106 /*
107 * Event for mails/users
108 * TODO: add also the 'plan' field
109 * TODO: Make the filtering a generic function
110 */
111 function rg_admin_mails_users($db, $ev)
112 {
113 $ret = array();
114
115 //rg_log_ml('admin_mails_users: ev: ' . print_r($ev, TRUE));
116
117 $sql = 'SELECT uid, username, email, disk_used_mb'
118 . ', git_mb, artifacts_mb, realname'
119 . ' FROM users'
120 . ' WHERE deleted = 0';
121
122 if ($ev['f']['conf'] == 0)
123 $sql .= ' AND confirmed > 0';
124 else if ($ev['f']['conf'] == 1)
125 $sql .= ' AND confirmed > 0';
126
127 if ($ev['f']['admin'] != 2)
128 $sql .= ' AND is_admin = ' . $ev['f']['admin'];
129
130 if ($ev['f']['suspended'] == 0)
131 $sql .= ' AND suspended = 0';
132 else if ($ev['f']['suspended'] == 1)
133 $sql .= ' AND suspended > 0';
134
135 if ($ev['f']['total_usage'] > 0)
136 $sql .= ' AND disk_used_mb >= ' . $ev['f']['total_usage'];
137
138 if ($ev['f']['artifact'] > 0)
139 $sql .= ' AND artifacts_mb >= ' . $ev['f']['artifact'];
140
141 if ($ev['f']['git'] > 0)
142 $sql .= ' AND git_mb <= ' . $ev['f']['git'];
143
144 $res = rg_sql_query($db, $sql);
145 if ($res === FALSE)
146 return FALSE;
147
148 $u = array();
149 while (($row = rg_sql_fetch_array($res))) {
150 if (!empty($ev['f']['username_filter'])
151 && @preg_match('/' . $ev['f']['username_filter'] . '/uD', $row['username']) === 0)
152 continue;
153
154 if (!empty($ev['f']['email_filter'])
155 && @preg_match('/' . $ev['f']['email_filter'] . '/uD', $row['email']) === 0)
156 continue;
157
158 $uid = $row['uid']; unset($row['uid']);
159 $row['no_of_repos'] = 0;
160 $row['no_of_webhooks'] = 0;
161 if (empty($row['realname']))
162 $row['realname'] = $row['username'];
163 $u[$uid] = $row;
164 }
165 rg_sql_free_result($res);
166
167 if ($ev['f']['repo'] == 1) {
168 $uids = array();
169 foreach ($u as $i)
170 $uids[] = $i['uid'];
171 $sql = 'SELECT uid FROM repos'
172 . ' WHERE uid IN (' . implode(',', $uids) . ')';
173 $res = rg_sql_query($db, $sql);
174 if ($res === FALSE)
175 return FALSE;
176 while (($row = rg_sql_fetch_array($res)))
177 $u[$row['uid']]['no_of_repos']++;
178 rg_sql_free_result($res);
179
180 foreach ($u as $uid => $i) {
181 if ($i['no_of_repos'] == 0)
182 unset($u[$uid]);
183 }
184 }
185
186 if ($ev['f']['webhook'] == 1) {
187 $uids = array();
188 foreach ($u as $i)
189 $uids[] = $i['uid'];
190
191 $sql = 'SELECT uid FROM webhooks'
192 . ' WHERE uid IN (' . implode(',', $uids) . ')';
193 $res = rg_sql_query($db, $sql);
194 if ($res === FALSE)
195 return FALSE;
196 while (($row = rg_sql_fetch_array($res)))
197 $u[$row['uid']]['no_of_webhooks']++;
198 rg_sql_free_result($res);
199
200 foreach ($u as $uid => $i) {
201 if ($i['no_of_webhooks'] == 0)
202 unset($u[$uid]);
203 }
204 }
205
206 $subject = $ev['f']['subject'];
207 $body = $ev['f']['body'];
208 unset($ev['f']);
209
210 foreach ($u as $uid => $i) {
211 $k = array(); $v = array();
212 foreach ($i as $_k => $_v) {
213 $k[] = '/@@' . $_k . '@@/uU';
214 $v[] = $_v;
215 }
216 $ev['subject'] = preg_replace($k, $v, $subject);
217 $ev['body'] = preg_replace($k, $v, $body);
218
219 $ret[] = array_merge($ev,
220 array(
221 'category' => 'admin_mails_users_one',
222 'prio' => 500,
223 'email' => $i['email'],
224 'name' => $i['realname']
225 )
226 );
227 }
228
229 return $ret;
230 }
231
232 /*
233 * Deals with mails to the users
234 */
235 function rg_admin_mails_users_high_level($db, $rg)
236 {
237 rg_log_enter('admin_mails_users_high_level');
238
239 $ret = '';
240
241 $f = array();
242 $f['email_filter'] = '';
243 $f['username_filter'] = '';
244 $f['conf'] = 1;
245 $f['admin'] = 2;
246 $f['suspended'] = 0;
247 $f['repo'] = 0;
248 $f['webhook'] = 0;
249 $f['total_usage'] = 0;
250 $f['git'] = 0;
251 $f['artifact'] = 0;
252 $f['subject'] = '';
253 $f['body'] = '';
254
255 $errmsg = array();
256 $show_form = TRUE;
257 while (1) {
258 if (rg_var_int('doit') == 0)
259 break;
260
261 $f['username_filter'] = rg_var_str_nocr('f::username_filter');
262 $f['email_filter'] = rg_var_str_nocr('f::email_filter');
263 $f['conf'] = rg_var_uint('f::conf');
264 $f['admin'] = rg_var_uint('f::admin');
265 $f['suspended'] = rg_var_uint('f::suspended');
266 $f['repo'] = rg_var_uint('f::repo');
267 $f['webhook'] = rg_var_uint('f::webhook');
268 $f['total_usage'] = rg_var_uint('f::total_usage');
269 $f['git'] = rg_var_uint('f::git');
270 $f['artifact'] = rg_var_uint('f::artifact');
271 $f['subject'] = rg_var_str_nocr('f::subject');
272 $f['body'] = trim(rg_var_str('f::body'));
273
274 if (!rg_valid_referer()) {
275 $errmsg[] = 'invalid referer; try again';
276 break;
277 }
278
279 if (!rg_token_valid($db, $rg, 'admin_mails_users_hl', FALSE)) {
280 $errmsg[] = 'invalid token; try again';
281 break;
282 }
283
284 if (empty($f['subject'])) {
285 $errmsg[] = 'subject is empty';
286 break;
287 }
288
289 if (empty($f['body'])) {
290 $errmsg[] = 'body is empty';
291 break;
292 }
293
294 if (!empty($errmsg))
295 break;
296
297 $ev = array(
298 'category' => 'admin_mails_users',
299 'prio' => 50,
300 'ui' => $rg['login_ui'],
301 'f' => $f);
302 $r = rg_event_add($db, $ev);
303 if ($r !== TRUE) {
304 $errmsg[] = 'cannot add event (' . rg_event_error() . ')';
305 break;
306 }
307 rg_event_signal_daemon('', 0);
308
309 $ret .= rg_template('admin/mails/users/queued.html', $rg, TRUE /*xss*/);
310 $show_form = FALSE;
311 break;
312 }
313
314 if ($show_form) {
315 $rg['f'] = $f;
316
317 // hints
318 $hints = array();
319 $hints[]['HTML:hint'] = rg_template('hints/admin/mails_users.html',
320 $rg, TRUE /*xss*/);
321 $rg['HTML:hints'] = rg_template_table('hints/list', $hints, $rg);
322
323 $rg['HTML:errmsg'] = rg_template_errmsg($errmsg);
324 $rg['rg_form_token'] = rg_token_get($db, $rg, 'admin_mails_users_hl');
325 $ret .= rg_template('admin/mails/users/form.html', $rg, TRUE /*xss*/);
326 }
327
328 rg_log_exit();
329 return $ret;
330 }
331
75 332 /* /*
76 333 * Deals with invites * Deals with invites
77 334 */ */
78 function rg_admin_invites_high_level($db, $rg)
335 function rg_admin_mails_invites_high_level($db, $rg)
79 336 { {
80 rg_log_enter("admin_invites_high_level");
337 rg_log_enter('admin_mails_invites_high_level');
81 338
82 339 $ret = ""; $ret = "";
83 340
 
... ... function rg_admin_invites_high_level($db, $rg)
93 350 break; break;
94 351
95 352 $inv['list'] = rg_var_str('inv::list'); $inv['list'] = rg_var_str('inv::list');
96 $inv['subject'] = trim(rg_var_str('inv::subject'));
353 $inv['subject'] = rg_var_str_nocr('inv::subject');
97 354 $inv['body'] = rg_var_str('inv::body'); $inv['body'] = rg_var_str('inv::body');
98 355
99 356 while (isset($_FILES['inv::file'])) { while (isset($_FILES['inv::file'])) {
File inc/admin/admin.php changed (mode: 100644) (index 81a4bd6..d0e61dd)
... ... case 'workers':
43 43 $_admin_body = rg_worker_high_level($db, $rg, $paras); $_admin_body = rg_worker_high_level($db, $rg, $paras);
44 44 break; break;
45 45
46 case 'invites': // invites
47 $_admin_body = rg_admin_invites_high_level($db, $rg);
46 case 'mails':
47 include($INC . '/admin/mails/mails.php');
48 $_admin_body = $_admin_mails;
48 49 break; break;
49 50
50 51 default: default:
File inc/admin/mails/mails.php added (mode: 100644) (index 0000000..4daed6b)
1 <?php
2 rg_log('FILE: /inc/admin/mails/mails');
3
4 $_admin_mails = '';
5
6 $_op = empty($paras) ? 'users' : array_shift($paras);
7 rg_log('DEBUG: _op=' . $_op . ' sparas=' . rg_array2string($paras));
8
9 $rg['admin_mails_menu'][$_op] = 1;
10 $rg['HTML:menu_level2'] = rg_template('admin/mails/menu.html', $rg, TRUE /*xss*/);
11
12 switch ($_op) {
13 case 'users':
14 $_admin_mails .= rg_admin_mails_users_high_level($db, $rg);
15 break;
16
17 case 'invites':
18 $_admin_mails .= rg_admin_mails_invites_high_level($db, $rg);
19 break;
20 }
21
File inc/util.inc.php changed (mode: 100644) (index a264987..936cef3)
... ... function rg_var_str($name)
478 478 return $c; return $c;
479 479 } }
480 480
481 function rg_var_str_nocr($name)
482 {
483 $k = array("\n", "\r");
484 $v = array('', '');
485 return str_replace($k, $v, rg_var_str($name));
486 }
487
481 488 function rg_var_int($name) function rg_var_int($name)
482 489 { {
483 490 $r = rg_var_str($name); $r = rg_var_str($name);
 
... ... function rg_var_bool($name)
515 522
516 523 function rg_var_email($name) function rg_var_email($name)
517 524 { {
518 $e = rg_var_str($name);
519 $e = str_replace("\n", '', $e);
520 $e = str_replace("\r", '', $e);
521
522 return $e;
525 return trim(rg_var_str_nocr($name));
523 526 } }
524 527
525 528 /* /*
File root/themes/default/admin/mails/invites/invites.html changed (mode: 100644) (index 9ff12b7..439d535)
4 4
5 5 @@errmsg@@ @@errmsg@@
6 6
7 <form method="post" action="/op/admin/invites" enctype="multipart/form-data">
7 <form method="post" action="/op/admin/mails/invites" enctype="multipart/form-data">
8 8 <input type="hidden" name="doit" value="1" /> <input type="hidden" name="doit" value="1" />
9 9 <input type="hidden" name="token" value="@@rg_form_token@@" /> <input type="hidden" name="token" value="@@rg_form_token@@" />
10 10
File root/themes/default/admin/mails/menu.html added (mode: 100644) (index 0000000..1a73f2f)
1 <div class="menu menu3">
2 <ul>
3 <li@@if(@@admin_mails_menu::users@@ == 1){{ class="selected"}}{{}}><a href="/op/admin/mails/users">Users</a></li>
4 <li@@if(@@admin_mails_menu::invites@@ == 1){{ class="selected"}}{{}}><a href="/op/admin/mails/invites">Invites</a></li>
5 </ul>
6 </div>
File root/themes/default/admin/mails/users/form.html added (mode: 100644) (index 0000000..db5e47e)
1 <div class="formarea">
2
3 <div class="formarea_title">Send mails to your users</div>
4
5 @@errmsg@@
6
7 <form method="post" action="/op/admin/mails/users" enctype="multipart/form-data">
8 <input type="hidden" name="doit" value="1" />
9 <input type="hidden" name="token" value="@@rg_form_token@@" />
10
11 <p>
12 <label for="f_username_filter">Username filter (regex; empty = all):</label>
13 <input class="form_short" type="text" name="f::username_filter" id="f_username_filter" value="@@f::username_filter@@" size="50" />
14 </p>
15
16 <p>
17 <label for="f_email_filter">E-mail filter (regex; empty = all):</label>
18 <input class="form_short" type="text" name="f::email_filter" id="f_email_filter" value="@@f::email_filter@@" size="50" />
19 </p>
20
21 <p>
22 <label for="f_conf">User must have the e-mail confirmed?</label>
23 <select class="form_short" name="f::conf" id="f_conf">
24 <option value="1"@@if(@@f::conf@@ == 1){{ selected="selected"}}{{}}>Yes</option>
25 <option value="2"@@if(@@f::conf@@ == 2){{ selected="selected"}}{{}}>Doesn't matter</option>
26 <option value="0"@@if(@@f::conf@@ == 0){{ selected="selected"}}{{}}>No</option>
27 </select>
28 </p>
29
30 <p>
31 <label for="f_admin">User must be an admin?</label>
32 <select class="form_short" name="f::admin" id="f_admin">
33 <option value="2"@@if(@@f::admin@@ == 2){{ selected="selected"}}{{}}>Doesn't matter</option>
34 <option value="0"@@if(@@f::admin@@ == 0){{ selected="selected"}}{{}}>No</option>
35 <option value="1"@@if(@@f::admin@@ == 1){{ selected="selected"}}{{}}>Yes</option>
36 </select>
37 </p>
38
39 <p>
40 <label for="f_suspended">User must be suspended?</label>
41 <select class="form_short" name="f::suspended" id="f_suspended">
42 <option value="0"@@if(@@f::suspended@@ == 0){{ selected="selected"}}{{}}>No</option>
43 <option value="2"@@if(@@f::suspended@@ == 2){{ selected="selected"}}{{}}>Doesn't matter</option>
44 <option value="1"@@if(@@f::suspended@@ == 1){{ selected="selected"}}{{}}>Yes</option>
45 </select>
46 </p>
47
48 <p>
49 <label for="f_repo">Minimum number of repos (0 = all):</label>
50 <input class="form_short" type="text" name="f::repo" id="f_repo" value="@@f::repo@@" size="5" />
51 </p>
52
53 <p>
54 <label for="f_webhook">Minimum number of webhooks (0 = all):</label>
55 <input class="form_short" type="text" name="f::webhook" id="f_webhook" value="@@f::webhook@@" size="5" />
56 </p>
57
58 <p>
59 <label for="f_total_usage">Total minimum disk usage (MiB; 0 = all):</label>
60 <input class="form_short" type="text" name="f::total_usage" id="f_total_usage" value="@@f::total_usage@@" size="5" />
61 </p>
62
63 <p>
64 <label for="f_git">Git minimum disk usage (MiB; 0 = all):</label>
65 <input class="form_short" type="text" name="f::git" id="f_git" value="@@f::git@@" size="5" />
66 </p>
67
68 <p>
69 <label for="f_artifact">Artifacts minimum disk usage (MiB; 0 = all):</label>
70 <input class="form_short" type="text" name="f::artifact" id="f_artifact" value="@@f::artifact@@" size="5" />
71 </p>
72
73 <p>
74 <label for="f_subject">Subject:</label>
75 <input type="text" name="f::subject" id="f_subject" value="@@f::subject@@" />
76 </p>
77
78 <p>
79 <label for="body">Body:</label><br />
80 <textarea name="f::body" id="f_body" rows="30">@@f::body@@</textarea>
81 </p>
82
83 <p>
84 <input type="submit" name="button" value="Send" />
85 </p>
86
87 </form>
88 </div>
89
90 @@hints@@
File root/themes/default/admin/mails/users/queued.html added (mode: 100644) (index 0000000..3732fa3)
1 <div class="mess ok">
2 The mails have been queued for sending.
3 </div>
File root/themes/default/admin/menu.html changed (mode: 100644) (index 9e86b0b..e70a5de)
8 8 <li@@if(@@admin_menu::repos@@ == 1){{ class="selected"}}{{}}><a href="/op/admin/repos">Repos</a></li> <li@@if(@@admin_menu::repos@@ == 1){{ class="selected"}}{{}}><a href="/op/admin/repos">Repos</a></li>
9 9 <li@@if(@@admin_menu::ldap@@ == 1){{ class="selected"}}{{}}><a href="/op/admin/ldap">LDAP</a></li> <li@@if(@@admin_menu::ldap@@ == 1){{ class="selected"}}{{}}><a href="/op/admin/ldap">LDAP</a></li>
10 10 <li@@if(@@admin_menu::workers@@ == 1){{ class="selected"}}{{}}><a href="/op/admin/workers">Workers</a></li> <li@@if(@@admin_menu::workers@@ == 1){{ class="selected"}}{{}}><a href="/op/admin/workers">Workers</a></li>
11 <li@@if(@@admin_menu::invites@@ == 1){{ class="selected"}}{{}}><a href="/op/admin/invites">Invites</a></li>
11 <li@@if(@@admin_menu::mails@@ == 1){{ class="selected"}}{{}}><a href="/op/admin/mails">Mails</a></li>
12 12 </ul> </ul>
13 13 </div> </div>
14 14 @@menu_level2@@ @@menu_level2@@
File root/themes/default/hints/admin/mails_users.html added (mode: 100644) (index 0000000..1de69a1)
1 <br />
2 In the subject and the body of the e-mail, you can use <b>@@var@@</b>
3 placeholders. They will be replaced with their value.
4 The placeholders are:<br />
5 <ul>
6 <li>username - the login name</li>
7 <li>email - user e-mail</li>
8 <li>realname - the real name, replaced by username if empty</li>
9 <li>disk_used_mb - total disk space used by the user</li>
10 <li>git_mb - disk spaced used for git repos</li>
11 <li>artifacts_mb - disk spaced used by artifacts</li>
12 <li>no_of_repos - the number of repositories</li>
13 <li>no_of_webhooks - the number of webhooks</li>
14 </ul>
File tests/http_admin.php changed (mode: 100644) (index a8ce1b4..f6b57d9)
... ... if (strstr($r['body'], "invalid user")) {
59 59 } }
60 60
61 61 rg_log("Loading invites form..."); rg_log("Loading invites form...");
62 $url = "/op/admin/invites";
62 $url = "/op/admin/mails/invites";
63 63 $data = array(); $data = array();
64 64 $r = do_req($test_url . $url, $data, $headers); $r = do_req($test_url . $url, $data, $headers);
65 65 if ($r === FALSE) { if ($r === FALSE) {
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/rocketgit

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/rocketgit

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master