editwhines.cgi 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. #!/usr/bin/env perl -wT
  2. # -*- Mode: perl; indent-tabs-mode: nil -*-
  3. #
  4. # The contents of this file are subject to the Mozilla Public
  5. # License Version 1.1 (the "License"); you may not use this file
  6. # except in compliance with the License. You may obtain a copy of
  7. # the License at http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS
  10. # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11. # implied. See the License for the specific language governing
  12. # rights and limitations under the License.
  13. #
  14. # The Original Code is the Bugzilla Bug Tracking System.
  15. #
  16. # The Initial Developer of the Original Code is Netscape Communications
  17. # Corporation. Portions created by Netscape are
  18. # Copyright (C) 1998 Netscape Communications Corporation. All
  19. # Rights Reserved.
  20. #
  21. # Contributor(s): Erik Stambaugh <erik@dasbistro.com>
  22. #
  23. ################################################################################
  24. # Script Initialization
  25. ################################################################################
  26. use strict;
  27. use lib qw(. lib);
  28. use Bugzilla;
  29. use Bugzilla::Constants;
  30. use Bugzilla::Util;
  31. use Bugzilla::Error;
  32. use Bugzilla::User;
  33. use Bugzilla::Group;
  34. use Bugzilla::Token;
  35. # require the user to have logged in
  36. my $user = Bugzilla->login(LOGIN_REQUIRED);
  37. ###############################################################################
  38. # Main Body Execution
  39. ###############################################################################
  40. my $cgi = Bugzilla->cgi;
  41. my $template = Bugzilla->template;
  42. my $vars = {};
  43. my $dbh = Bugzilla->dbh;
  44. my $userid = $user->id;
  45. my $token = $cgi->param('token');
  46. my $sth; # database statement handle
  47. # $events is a hash ref, keyed by event id, that stores the active user's
  48. # events. It starts off with:
  49. # 'subject' - the subject line for the email message
  50. # 'body' - the text to be sent at the top of the message
  51. #
  52. # Eventually, it winds up with:
  53. # 'queries' - array ref containing hashes of:
  54. # 'name' - the name of the saved query
  55. # 'title' - The title line for the search results table
  56. # 'sort' - Numeric sort ID
  57. # 'id' - row ID for the query entry
  58. # 'onemailperbug' - whether a single message must be sent for each
  59. # result.
  60. # 'schedule' - array ref containing hashes of:
  61. # 'day' - Day or range of days this schedule will be run
  62. # 'time' - time or interval to run
  63. # 'mailto_type' - MAILTO_USER or MAILTO_GROUP
  64. # 'mailto' - person/group who will receive the results
  65. # 'id' - row ID for the schedule
  66. my $events = get_events($userid);
  67. # First see if this user may use whines
  68. $user->in_group('bz_canusewhines')
  69. || ThrowUserError("auth_failure", {group => "bz_canusewhines",
  70. action => "schedule",
  71. object => "reports"});
  72. # May this user send mail to other users?
  73. my $can_mail_others = Bugzilla->user->in_group('bz_canusewhineatothers');
  74. # If the form was submitted, we need to look for what needs to be added or
  75. # removed, then what was altered.
  76. if ($cgi->param('update')) {
  77. check_token_data($token, 'edit_whine');
  78. if ($cgi->param("add_event")) {
  79. # we create a new event
  80. $sth = $dbh->prepare("INSERT INTO whine_events " .
  81. "(owner_userid) " .
  82. "VALUES (?)");
  83. $sth->execute($userid);
  84. }
  85. else {
  86. for my $eventid (keys %{$events}) {
  87. # delete an entire event
  88. if ($cgi->param("remove_event_$eventid")) {
  89. # We need to make sure these belong to the same user,
  90. # otherwise we could simply delete whatever matched that ID.
  91. #
  92. # schedules
  93. $sth = $dbh->prepare("SELECT whine_schedules.id " .
  94. "FROM whine_schedules " .
  95. "LEFT JOIN whine_events " .
  96. "ON whine_events.id = " .
  97. "whine_schedules.eventid " .
  98. "WHERE whine_events.id = ? " .
  99. "AND whine_events.owner_userid = ?");
  100. $sth->execute($eventid, $userid);
  101. my @ids = @{$sth->fetchall_arrayref};
  102. $sth = $dbh->prepare("DELETE FROM whine_schedules "
  103. . "WHERE id=?");
  104. for (@ids) {
  105. my $delete_id = $_->[0];
  106. $sth->execute($delete_id);
  107. }
  108. # queries
  109. $sth = $dbh->prepare("SELECT whine_queries.id " .
  110. "FROM whine_queries " .
  111. "LEFT JOIN whine_events " .
  112. "ON whine_events.id = " .
  113. "whine_queries.eventid " .
  114. "WHERE whine_events.id = ? " .
  115. "AND whine_events.owner_userid = ?");
  116. $sth->execute($eventid, $userid);
  117. @ids = @{$sth->fetchall_arrayref};
  118. $sth = $dbh->prepare("DELETE FROM whine_queries " .
  119. "WHERE id=?");
  120. for (@ids) {
  121. my $delete_id = $_->[0];
  122. $sth->execute($delete_id);
  123. }
  124. # events
  125. $sth = $dbh->prepare("DELETE FROM whine_events " .
  126. "WHERE id=? AND owner_userid=?");
  127. $sth->execute($eventid, $userid);
  128. }
  129. else {
  130. # check the subject and body for changes
  131. my $subject = ($cgi->param("event_${eventid}_subject") or '');
  132. my $body = ($cgi->param("event_${eventid}_body") or '');
  133. trick_taint($subject) if $subject;
  134. trick_taint($body) if $body;
  135. if ( ($subject ne $events->{$eventid}->{'subject'})
  136. || ($body ne $events->{$eventid}->{'body'}) ) {
  137. $sth = $dbh->prepare("UPDATE whine_events " .
  138. "SET subject=?, body=? " .
  139. "WHERE id=?");
  140. $sth->execute($subject, $body, $eventid);
  141. }
  142. # add a schedule
  143. if ($cgi->param("add_schedule_$eventid")) {
  144. # the schedule table must be locked before altering
  145. $sth = $dbh->prepare("INSERT INTO whine_schedules " .
  146. "(eventid, mailto_type, mailto, " .
  147. "run_day, run_time) " .
  148. "VALUES (?, ?, ?, 'Sun', 2)");
  149. $sth->execute($eventid, MAILTO_USER, $userid);
  150. }
  151. # add a query
  152. elsif ($cgi->param("add_query_$eventid")) {
  153. $sth = $dbh->prepare("INSERT INTO whine_queries "
  154. . "(eventid) "
  155. . "VALUES (?)");
  156. $sth->execute($eventid);
  157. }
  158. }
  159. # now check all of the schedules and queries to see if they need
  160. # to be altered or deleted
  161. # Check schedules for changes
  162. $sth = $dbh->prepare("SELECT id " .
  163. "FROM whine_schedules " .
  164. "WHERE eventid=?");
  165. $sth->execute($eventid);
  166. my @scheduleids = ();
  167. while (my ($sid) = $sth->fetchrow_array) {
  168. push @scheduleids, $sid;
  169. }
  170. # we need to double-check all of the user IDs in mailto to make
  171. # sure they exist
  172. my $arglist = {}; # args for match_field
  173. for my $sid (@scheduleids) {
  174. if ($cgi->param("mailto_type_$sid") == MAILTO_USER) {
  175. $arglist->{"mailto_$sid"} = {
  176. 'type' => 'single',
  177. };
  178. }
  179. }
  180. if (scalar %{$arglist}) {
  181. &Bugzilla::User::match_field($cgi, $arglist);
  182. }
  183. for my $sid (@scheduleids) {
  184. if ($cgi->param("remove_schedule_$sid")) {
  185. # having the assignee id in here is a security failsafe
  186. $sth = $dbh->prepare("SELECT whine_schedules.id " .
  187. "FROM whine_schedules " .
  188. "LEFT JOIN whine_events " .
  189. "ON whine_events.id = " .
  190. "whine_schedules.eventid " .
  191. "WHERE whine_events.owner_userid=? " .
  192. "AND whine_schedules.id =?");
  193. $sth->execute($userid, $sid);
  194. my @ids = @{$sth->fetchall_arrayref};
  195. for (@ids) {
  196. $sth = $dbh->prepare("DELETE FROM whine_schedules " .
  197. "WHERE id=?");
  198. $sth->execute($_->[0]);
  199. }
  200. }
  201. else {
  202. my $o_day = $cgi->param("orig_day_$sid") || '';
  203. my $day = $cgi->param("day_$sid") || '';
  204. my $o_time = $cgi->param("orig_time_$sid") || 0;
  205. my $time = $cgi->param("time_$sid") || 0;
  206. my $o_mailto = $cgi->param("orig_mailto_$sid") || '';
  207. my $mailto = $cgi->param("mailto_$sid") || '';
  208. my $o_mailto_type = $cgi->param("orig_mailto_type_$sid") || 0;
  209. my $mailto_type = $cgi->param("mailto_type_$sid") || 0;
  210. my $mailto_id = $userid;
  211. # get an id for the mailto address
  212. if ($can_mail_others && $mailto) {
  213. if ($mailto_type == MAILTO_USER) {
  214. # The user login has already been validated.
  215. $mailto_id = login_to_id($mailto);
  216. }
  217. elsif ($mailto_type == MAILTO_GROUP) {
  218. # The group name is used in a placeholder.
  219. trick_taint($mailto);
  220. $mailto_id = Bugzilla::Group::ValidateGroupName($mailto, ($user))
  221. || ThrowUserError('invalid_group_name', { name => $mailto });
  222. }
  223. else {
  224. # bad value, so it will just mail to the whine
  225. # owner. $mailto_id was already set above.
  226. $mailto_type = MAILTO_USER;
  227. }
  228. }
  229. detaint_natural($mailto_type);
  230. if ( ($o_day ne $day) ||
  231. ($o_time ne $time) ||
  232. ($o_mailto ne $mailto) ||
  233. ($o_mailto_type != $mailto_type) ){
  234. trick_taint($day);
  235. trick_taint($time);
  236. # the schedule table must be locked
  237. $sth = $dbh->prepare("UPDATE whine_schedules " .
  238. "SET run_day=?, run_time=?, " .
  239. "mailto_type=?, mailto=?, " .
  240. "run_next=NULL " .
  241. "WHERE id=?");
  242. $sth->execute($day, $time, $mailto_type,
  243. $mailto_id, $sid);
  244. }
  245. }
  246. }
  247. # Check queries for changes
  248. $sth = $dbh->prepare("SELECT id " .
  249. "FROM whine_queries " .
  250. "WHERE eventid=?");
  251. $sth->execute($eventid);
  252. my @queries = ();
  253. while (my ($qid) = $sth->fetchrow_array) {
  254. push @queries, $qid;
  255. }
  256. for my $qid (@queries) {
  257. if ($cgi->param("remove_query_$qid")) {
  258. $sth = $dbh->prepare("SELECT whine_queries.id " .
  259. "FROM whine_queries " .
  260. "LEFT JOIN whine_events " .
  261. "ON whine_events.id = " .
  262. "whine_queries.eventid " .
  263. "WHERE whine_events.owner_userid=? " .
  264. "AND whine_queries.id =?");
  265. $sth->execute($userid, $qid);
  266. for (@{$sth->fetchall_arrayref}) {
  267. $sth = $dbh->prepare("DELETE FROM whine_queries " .
  268. "WHERE id=?");
  269. $sth->execute($_->[0]);
  270. }
  271. }
  272. else {
  273. my $o_sort = $cgi->param("orig_query_sort_$qid") || 0;
  274. my $sort = $cgi->param("query_sort_$qid") || 0;
  275. my $o_queryname = $cgi->param("orig_query_name_$qid") || '';
  276. my $queryname = $cgi->param("query_name_$qid") || '';
  277. my $o_title = $cgi->param("orig_query_title_$qid") || '';
  278. my $title = $cgi->param("query_title_$qid") || '';
  279. my $o_onemailperbug =
  280. $cgi->param("orig_query_onemailperbug_$qid") || 0;
  281. my $onemailperbug =
  282. $cgi->param("query_onemailperbug_$qid") ? 1 : 0;
  283. if ( ($o_sort != $sort) ||
  284. ($o_queryname ne $queryname) ||
  285. ($o_onemailperbug != $onemailperbug) ||
  286. ($o_title ne $title) ){
  287. detaint_natural($sort);
  288. trick_taint($queryname);
  289. trick_taint($title);
  290. $sth = $dbh->prepare("UPDATE whine_queries " .
  291. "SET sortkey=?, " .
  292. "query_name=?, " .
  293. "title=?, " .
  294. "onemailperbug=? " .
  295. "WHERE id=?");
  296. $sth->execute($sort, $queryname, $title,
  297. $onemailperbug, $qid);
  298. }
  299. }
  300. }
  301. }
  302. }
  303. delete_token($token);
  304. }
  305. $vars->{'mail_others'} = $can_mail_others;
  306. # Return the appropriate HTTP response headers.
  307. print $cgi->header();
  308. # Get events again, to cover any updates that were made
  309. $events = get_events($userid);
  310. # Here is the data layout as sent to the template:
  311. #
  312. # events
  313. # event_id #
  314. # schedule
  315. # day
  316. # time
  317. # mailto
  318. # queries
  319. # name
  320. # title
  321. # sort
  322. #
  323. # build the whine list by event id
  324. for my $event_id (keys %{$events}) {
  325. $events->{$event_id}->{'schedule'} = [];
  326. $events->{$event_id}->{'queries'} = [];
  327. # schedules
  328. $sth = $dbh->prepare("SELECT run_day, run_time, mailto_type, mailto, id " .
  329. "FROM whine_schedules " .
  330. "WHERE eventid=?");
  331. $sth->execute($event_id);
  332. for my $row (@{$sth->fetchall_arrayref}) {
  333. my $mailto_type = $row->[2];
  334. my $mailto = '';
  335. if ($mailto_type == MAILTO_USER) {
  336. my $mailto_user = new Bugzilla::User($row->[3]);
  337. $mailto = $mailto_user->login;
  338. }
  339. elsif ($mailto_type == MAILTO_GROUP) {
  340. $sth = $dbh->prepare("SELECT name FROM groups WHERE id=?");
  341. $sth->execute($row->[3]);
  342. $mailto = $sth->fetch->[0];
  343. $mailto = "" unless Bugzilla::Group::ValidateGroupName(
  344. $mailto, ($user));
  345. }
  346. my $this_schedule = {
  347. 'day' => $row->[0],
  348. 'time' => $row->[1],
  349. 'mailto_type' => $mailto_type,
  350. 'mailto' => $mailto,
  351. 'id' => $row->[4],
  352. };
  353. push @{$events->{$event_id}->{'schedule'}}, $this_schedule;
  354. }
  355. # queries
  356. $sth = $dbh->prepare("SELECT query_name, title, sortkey, id, " .
  357. "onemailperbug " .
  358. "FROM whine_queries " .
  359. "WHERE eventid=? " .
  360. "ORDER BY sortkey");
  361. $sth->execute($event_id);
  362. for my $row (@{$sth->fetchall_arrayref}) {
  363. my $this_query = {
  364. 'name' => $row->[0],
  365. 'title' => $row->[1],
  366. 'sort' => $row->[2],
  367. 'id' => $row->[3],
  368. 'onemailperbug' => $row->[4],
  369. };
  370. push @{$events->{$event_id}->{'queries'}}, $this_query;
  371. }
  372. }
  373. $vars->{'events'} = $events;
  374. # get the available queries
  375. $sth = $dbh->prepare("SELECT name FROM namedqueries WHERE userid=?");
  376. $sth->execute($userid);
  377. $vars->{'available_queries'} = [];
  378. while (my ($query) = $sth->fetchrow_array) {
  379. push @{$vars->{'available_queries'}}, $query;
  380. }
  381. $vars->{'token'} = issue_session_token('edit_whine');
  382. $template->process("whine/schedule.html.tmpl", $vars)
  383. || ThrowTemplateError($template->error());
  384. # get_events takes a userid and returns a hash, keyed by event ID, containing
  385. # the subject and body of each event that user owns
  386. sub get_events {
  387. my $userid = shift;
  388. my $dbh = Bugzilla->dbh;
  389. my $events = {};
  390. my $sth = $dbh->prepare("SELECT DISTINCT id, subject, body " .
  391. "FROM whine_events " .
  392. "WHERE owner_userid=?");
  393. $sth->execute($userid);
  394. while (my ($ev, $sub, $bod) = $sth->fetchrow_array) {
  395. $events->{$ev} = {
  396. 'subject' => $sub || '',
  397. 'body' => $bod || '',
  398. };
  399. }
  400. return $events;
  401. }