24 |
#include "query.h" |
#include "query.h" |
25 |
#include "mail.h" |
#include "mail.h" |
26 |
|
|
|
static int applyChangeActions (PR *pr, PR *oldPR, FieldIndex field, |
|
|
ChangeActions actions, ErrorDesc *err, |
|
|
FormatNamedParameter *params); |
|
|
|
|
|
static int processFieldChange (PR *pr, PR *oldPR, FieldIndex field, |
|
|
ErrorDesc *err, |
|
|
const char *editUserEmailAddr, |
|
|
const char *oldValue, const char *newValue); |
|
|
|
|
27 |
/* Euuugh. There's gotta be a better way to keep this around. */ |
/* Euuugh. There's gotta be a better way to keep this around. */ |
28 |
static char *newAuditTrailEntries = NULL; |
static char *newAuditTrailEntries = NULL; |
29 |
|
|
30 |
static void sendAuditMail (PR *pr, PR *oldPR, const char *editUserEmailAddr, |
static void |
31 |
const char *newAuditTrailEntries, |
sendAuditMail (PR *pr, PR *oldPR, const char *editUserEmailAddr, |
32 |
ErrorDesc *err); |
const char *newAuditTrailEntries, ErrorDesc *err) |
33 |
|
{ |
34 |
|
FormatNamedParameter *parms = NULL; |
35 |
|
const DatabaseInfo database = pr->database; |
36 |
|
|
37 |
int |
parms = allocateNamedParameter ("$EditUserEmailAddr", editUserEmailAddr, |
38 |
replace_pr (const DatabaseInfo database, FILE *fp, |
parms); |
39 |
const char *editUserEmailAddr, ErrorDesc *err) |
parms = allocateNamedParameter ("$NewAuditTrail", newAuditTrailEntries, |
40 |
|
parms); |
41 |
|
|
42 |
|
if (strcmp (field_value (pr, RESPONSIBLE (database)), |
43 |
|
field_value (oldPR, RESPONSIBLE (database))) != 0) |
44 |
|
{ |
45 |
|
parms |
46 |
|
= allocateNamedParameter ("$OldResponsible", |
47 |
|
field_value (oldPR, RESPONSIBLE (database)), |
48 |
|
parms); |
49 |
|
} |
50 |
|
|
51 |
|
composeMailMessage (pr, oldPR, "audit-mail", parms, NULL, err); |
52 |
|
freeFormatParameterList (parms); |
53 |
|
} |
54 |
|
|
55 |
|
/* Add an entry to the AUDIT_TRAIL field. PARAMS are the format |
56 |
|
parameters used when composing the entry. FMT is the format of the |
57 |
|
audit-trail entry to add. */ |
58 |
|
|
59 |
|
static int |
60 |
|
addAuditTrailEnt (PR *pr, QueryFormat *fmt, |
61 |
|
FormatNamedParameter *params, ErrorDesc *err) |
62 |
{ |
{ |
63 |
PR *pr = allocPR (database); |
char *newAuditString = NULL; |
64 |
|
char *finalAuditString; |
65 |
|
const char *t; |
66 |
|
char *currDate = get_curr_date (); |
67 |
|
|
68 |
/* Read the message header in. */ |
if (fmt == NULL) |
|
if (read_header (pr, fp) < 0) |
|
69 |
{ |
{ |
70 |
setError (err, CODE_EOF_PR, "Couldn't read PR header"); |
fmt = getAuditTrailFormat (pr->database); |
|
return 0; |
|
71 |
} |
} |
72 |
|
|
73 |
read_pr (pr, fp, 0); |
/* Format the new audit entry. */ |
74 |
|
process_format (NULL, &newAuditString, pr, NULL, fmt, "\n", params); |
75 |
|
|
76 |
return rewrite_pr (pr, editUserEmailAddr, err); |
/* Squirrel it away, because we'll need to mail it later. */ |
77 |
|
append_string (&newAuditTrailEntries, newAuditString); |
78 |
|
|
79 |
|
/* Now append the formatted string to the audit-trail field. */ |
80 |
|
t = field_value (pr, AUDIT_TRAIL (pr->database)); |
81 |
|
if (t == NULL) |
82 |
|
{ |
83 |
|
t = ""; |
84 |
|
} |
85 |
|
finalAuditString = xstrdup (t); |
86 |
|
append_string (&finalAuditString, newAuditString); |
87 |
|
set_field (pr, AUDIT_TRAIL (pr->database), finalAuditString, err); |
88 |
|
|
89 |
|
free (finalAuditString); |
90 |
|
free (newAuditString); |
91 |
|
free (currDate); |
92 |
|
|
93 |
|
return 0; |
94 |
|
} |
95 |
|
|
96 |
|
static int |
97 |
|
applyChangeAction (ChangeActions action, PR *pr, PR *oldPR, FieldIndex field, |
98 |
|
ErrorDesc *err, FormatNamedParameter *params) |
99 |
|
{ |
100 |
|
FieldEdit *fieldEdits = action->edits; |
101 |
|
FieldList fields = action->requiredFields; |
102 |
|
|
103 |
|
while (fields != NULL) |
104 |
|
{ |
105 |
|
const char *fldval = get_field_value (pr, oldPR, fields->ent, NULL, NULL); |
106 |
|
if (value_is_empty (fldval)) |
107 |
|
{ |
108 |
|
setError (err, CODE_INVALID_PR_CONTENTS, |
109 |
|
"Required field %s missing from PR %s.", |
110 |
|
complexFieldIndexToString (fields->ent), |
111 |
|
field_value (pr, NUMBER (pr->database))); |
112 |
|
return 1; |
113 |
|
} |
114 |
|
fields = fields->next; |
115 |
|
} |
116 |
|
|
117 |
|
if (action->requireChangeReason && field_change_reason (pr, field) == NULL) |
118 |
|
{ |
119 |
|
setError (err, CODE_INVALID_PR_CONTENTS, |
120 |
|
"Edit of field %s requires a change reason.", |
121 |
|
fieldDefForIndex (field)->name); |
122 |
|
return 1; |
123 |
|
} |
124 |
|
|
125 |
|
while (fieldEdits != NULL) |
126 |
|
{ |
127 |
|
if (applyFieldEdit (pr, fieldEdits, err, params) != 0) |
128 |
|
{ |
129 |
|
return 1; |
130 |
|
} |
131 |
|
fieldEdits = fieldEdits->next; |
132 |
|
} |
133 |
|
return 0; |
134 |
} |
} |
135 |
|
|
136 |
static int |
static int |
163 |
} |
} |
164 |
|
|
165 |
static int |
static int |
166 |
|
applyChangeActions (PR *pr, PR *oldPR, FieldIndex field, |
167 |
|
ChangeActions actions, ErrorDesc *err, |
168 |
|
FormatNamedParameter *params) |
169 |
|
{ |
170 |
|
{ |
171 |
|
ChangeActions actionList = actions; |
172 |
|
|
173 |
|
while (actionList != NULL) |
174 |
|
{ |
175 |
|
if (actionList->expr == NULL |
176 |
|
|| pr_matches_expr (pr, oldPR, actionList->expr, params)) |
177 |
|
{ |
178 |
|
if (applyChangeAction (actionList, pr, oldPR, field, err, params)) |
179 |
|
{ |
180 |
|
return 1; |
181 |
|
} |
182 |
|
} |
183 |
|
actionList = actionList->next; |
184 |
|
} |
185 |
|
} |
186 |
|
|
187 |
|
if (field != InvalidFieldIndex && addAuditEntryP (pr->database, |
188 |
|
field, actions)) |
189 |
|
{ |
190 |
|
ChangeActions action = actions; |
191 |
|
while (actions != NULL) |
192 |
|
{ |
193 |
|
if (actions->addAuditTrail) |
194 |
|
{ |
195 |
|
break; |
196 |
|
} |
197 |
|
actions = actions->next; |
198 |
|
} |
199 |
|
|
200 |
|
if (action != NULL) |
201 |
|
{ |
202 |
|
addAuditTrailEnt (pr, action->auditTrailFormat, params, err); |
203 |
|
} |
204 |
|
else |
205 |
|
{ |
206 |
|
addAuditTrailEnt (pr, NULL, params, err); |
207 |
|
} |
208 |
|
} |
209 |
|
|
210 |
|
return 0; |
211 |
|
} |
212 |
|
|
213 |
|
static int |
214 |
|
processFieldChange (PR *pr, PR *oldPR, FieldIndex field, |
215 |
|
ErrorDesc *err, const char *editUserEmailAddr, |
216 |
|
const char *oldValue, const char *newValue) |
217 |
|
{ |
218 |
|
ChangeActions actions; |
219 |
|
FormatNamedParameter *params = NULL; |
220 |
|
int res; |
221 |
|
|
222 |
|
if (oldValue == NULL) |
223 |
|
{ |
224 |
|
oldValue = ""; |
225 |
|
} |
226 |
|
if (newValue == NULL) |
227 |
|
{ |
228 |
|
newValue = ""; |
229 |
|
} |
230 |
|
|
231 |
|
if (fieldDefForIndex (field)->readonly) |
232 |
|
{ |
233 |
|
setError (err, CODE_READONLY_FIELD, |
234 |
|
newBadFieldEntry (field, NULL, NULL), |
235 |
|
"Field %s is read-only: `%s'->`%s'", |
236 |
|
fieldDefForIndex (field)->name, |
237 |
|
oldValue, |
238 |
|
newValue); |
239 |
|
return 1; |
240 |
|
} |
241 |
|
|
242 |
|
{ |
243 |
|
char *currDate = get_curr_date (); |
244 |
|
params = allocateNamedParameter ("$CurrentDate", currDate, params); |
245 |
|
free (currDate); |
246 |
|
} |
247 |
|
|
248 |
|
params = allocateNamedParameter ("$FieldName", |
249 |
|
fieldDefForIndex (field)->name, params); |
250 |
|
params = allocateNamedParameter ("$OldValue", oldValue, params); |
251 |
|
params = allocateNamedParameter ("$NewValue", newValue, params); |
252 |
|
params = allocateNamedParameter ("$EditUserEmailAddr", editUserEmailAddr, |
253 |
|
params); |
254 |
|
{ |
255 |
|
const char *reason = field_change_reason (pr, field); |
256 |
|
if (reason == NULL) |
257 |
|
{ |
258 |
|
reason = ""; |
259 |
|
} |
260 |
|
params = allocateNamedParameter ("$ChangeReason", reason , params); |
261 |
|
} |
262 |
|
|
263 |
|
actions = fieldDefForIndex (field)->changeActions; |
264 |
|
res = applyChangeActions (pr, oldPR, field, actions, err, params); |
265 |
|
freeFormatParameterList (params); |
266 |
|
return res; |
267 |
|
} |
268 |
|
|
269 |
|
static int |
270 |
processPRChanges (const char *editUserEmailAddr, PR *old_pr, PR *new_pr, |
processPRChanges (const char *editUserEmailAddr, PR *old_pr, PR *new_pr, |
271 |
ErrorDesc *err) |
ErrorDesc *err) |
272 |
{ |
{ |
331 |
if (fieldDefForIndex (field)->readonly) |
if (fieldDefForIndex (field)->readonly) |
332 |
{ |
{ |
333 |
fieldsChanged[x] = 0; |
fieldsChanged[x] = 0; |
334 |
set_field (new_pr, field, old_value, err); |
if (old_value == NULL) |
335 |
|
{ |
336 |
|
unsetField (new_pr, field); |
337 |
|
} |
338 |
|
else |
339 |
|
{ |
340 |
|
set_field (new_pr, field, old_value, err); |
341 |
|
} |
342 |
} |
} |
343 |
else |
else |
344 |
{ |
{ |
397 |
/* Replace an existing PR with the same PR number as NEW_PR. |
/* Replace an existing PR with the same PR number as NEW_PR. |
398 |
EDITUSEREMAILADDR is the email address of the user that is performing the |
EDITUSEREMAILADDR is the email address of the user that is performing the |
399 |
edit. */ |
edit. */ |
400 |
int |
static int |
401 |
rewrite_pr (PR *new_pr, const char *editUserEmailAddr, ErrorDesc *err) |
rewrite_pr (PR *pr, PR *new_pr, const char *editUserEmailAddr, ErrorDesc *err) |
402 |
{ |
{ |
403 |
const DatabaseInfo database = new_pr->database; |
const DatabaseInfo database = new_pr->database; |
404 |
FILE *prfile; |
FILE *prfile; |
405 |
|
|
406 |
char *path = NULL; |
char *new_path = NULL; |
407 |
char *old_path = NULL; |
char *old_path = NULL; |
408 |
char *bkup_path = NULL; |
char *bkup_path = NULL; |
409 |
|
|
|
PR *old_pr = NULL; |
|
|
|
|
410 |
int res = 1; |
int res = 1; |
411 |
|
|
412 |
newAuditTrailEntries = NULL; |
newAuditTrailEntries = NULL; |
413 |
|
|
|
if (field_value (new_pr, NUMBER (database)) == NULL) |
|
|
{ |
|
|
free_pr (new_pr); |
|
|
setError (err, CODE_INVALID_PR_CONTENTS, |
|
|
"Missing PR number from new PR contents"); |
|
|
return 0; |
|
|
} |
|
|
|
|
414 |
if (field_value (new_pr, CATEGORY (database)) == NULL) |
if (field_value (new_pr, CATEGORY (database)) == NULL) |
415 |
{ |
{ |
|
free_pr (new_pr); |
|
416 |
setError (err, CODE_INVALID_PR_CONTENTS, |
setError (err, CODE_INVALID_PR_CONTENTS, |
417 |
"Missing category from new PR contents"); |
"Missing category from new PR contents"); |
418 |
return 0; |
return 0; |
419 |
} |
} |
420 |
|
|
|
if (! prExists (new_pr->database, field_value (new_pr, NUMBER (database)), |
|
|
err)) |
|
|
{ |
|
|
free_pr (new_pr); |
|
|
return 0; |
|
|
} |
|
|
|
|
421 |
if (! isPrLocked (new_pr->database, field_value (new_pr, NUMBER (database)))) |
if (! isPrLocked (new_pr->database, field_value (new_pr, NUMBER (database)))) |
422 |
{ |
{ |
423 |
setError (err, CODE_PR_NOT_LOCKED, "PR %s is not locked", |
setError (err, CODE_PR_NOT_LOCKED, "PR %s is not locked", |
424 |
field_value (new_pr, NUMBER (database))); |
field_value (new_pr, NUMBER (database))); |
|
free_pr (new_pr); |
|
425 |
return 0; |
return 0; |
426 |
} |
} |
427 |
|
|
428 |
if (! check_pr (new_pr, err, 0)) |
if (processPRChanges (editUserEmailAddr, pr, new_pr, err) != 0) |
|
{ |
|
|
return 0; |
|
|
} |
|
|
|
|
|
old_pr = replaceCurrentPRInIndex (new_pr); |
|
|
|
|
|
if (old_pr == NULL) |
|
429 |
{ |
{ |
|
setError (err, CODE_NO_INDEX, "Unable to locate existing PR %s", |
|
|
field_value (new_pr, NUMBER (database))); |
|
|
free_pr (new_pr); |
|
|
return 0; |
|
|
} |
|
|
|
|
|
old_path = gen_pr_path (old_pr); |
|
|
|
|
|
/* set this to be the file to be saved. now called .old. */ |
|
|
asprintf (&bkup_path, "%s.old", old_path); |
|
|
|
|
|
if (processPRChanges (editUserEmailAddr, old_pr, new_pr, err) != 0) |
|
|
{ |
|
|
clearPRChain (database); |
|
|
free_pr (old_pr); |
|
|
free (bkup_path); |
|
|
free (old_path); |
|
430 |
return 0; |
return 0; |
431 |
} |
} |
432 |
|
|
433 |
/* check to see if the category changes, and if so, write the |
/* check to see if the category changes, and if so, write the |
434 |
new file out, and unlink the old one. */ |
new file out, and unlink the old one. */ |
435 |
if (strcmp (field_value (old_pr, CATEGORY (database)), |
if (strcmp (field_value (pr, CATEGORY (database)), |
436 |
field_value (new_pr, CATEGORY (database))) != 0) |
field_value (new_pr, CATEGORY (database))) != 0) |
437 |
{ |
{ |
438 |
char *dirPath; |
char *dirPath; |
452 |
"Error creating directory for category %s: %s", |
"Error creating directory for category %s: %s", |
453 |
field_value (new_pr, CATEGORY (database)), |
field_value (new_pr, CATEGORY (database)), |
454 |
strerror (errno)); |
strerror (errno)); |
|
clearPRChain (database); |
|
|
free_pr (old_pr); |
|
|
free (bkup_path); |
|
|
free (old_path); |
|
455 |
free (dirPath); |
free (dirPath); |
456 |
return 0; |
return 0; |
457 |
} |
} |
461 |
setError (err, CODE_FILE_ERROR, |
setError (err, CODE_FILE_ERROR, |
462 |
"Directory for category %s does not exist", |
"Directory for category %s does not exist", |
463 |
field_value (new_pr, CATEGORY (database))); |
field_value (new_pr, CATEGORY (database))); |
|
clearPRChain (database); |
|
|
free_pr (old_pr); |
|
|
free (bkup_path); |
|
|
free (old_path); |
|
464 |
free (dirPath); |
free (dirPath); |
465 |
return 0; |
return 0; |
466 |
} |
} |
467 |
} |
} |
468 |
free (dirPath); |
free (dirPath); |
|
path = gen_pr_path (new_pr); |
|
|
} |
|
|
else |
|
|
{ |
|
|
path = gen_pr_path (old_pr); |
|
469 |
} |
} |
470 |
|
|
471 |
|
/* backup the current PR file */ |
472 |
|
old_path = gen_pr_path (pr); |
473 |
|
asprintf (&bkup_path, "%s.old", old_path); |
474 |
if (rename (old_path, bkup_path) < 0) |
if (rename (old_path, bkup_path) < 0) |
475 |
{ |
{ |
476 |
if (errno != EXDEV) |
if (errno != EXDEV) |
477 |
{ |
{ |
478 |
setError (err, CODE_FILE_ERROR, "Could not rename %s: %s", |
setError (err, CODE_FILE_ERROR, "Could not rename %s: %s", |
479 |
old_path, strerror (errno)); |
old_path, strerror (errno)); |
|
clearPRChain (database); |
|
|
free_pr (old_pr); |
|
480 |
free (bkup_path); |
free (bkup_path); |
481 |
free (old_path); |
free (old_path); |
|
free (path); |
|
482 |
return 0; |
return 0; |
483 |
} |
} |
484 |
if (copy_file (old_path, bkup_path) != 0) |
if (copy_file (old_path, bkup_path) != 0) |
485 |
{ |
{ |
486 |
setError (err, CODE_FILE_ERROR, "Could not copy %s to %s: %s", |
setError (err, CODE_FILE_ERROR, "Could not copy %s to %s: %s", |
487 |
old_path, bkup_path, strerror (errno)); |
old_path, bkup_path, strerror (errno)); |
|
clearPRChain (database); |
|
|
free_pr (old_pr); |
|
488 |
free (bkup_path); |
free (bkup_path); |
489 |
free (old_path); |
free (old_path); |
|
free (path); |
|
490 |
return 0; |
return 0; |
491 |
} |
} |
492 |
|
|
496 |
} |
} |
497 |
|
|
498 |
/* Now build the file. */ |
/* Now build the file. */ |
499 |
prfile = fopen (path, "w+"); |
new_path = gen_pr_path (new_pr); |
500 |
|
prfile = fopen (new_path, "w+"); |
501 |
if (prfile == (FILE *) NULL) |
if (prfile == (FILE *) NULL) |
502 |
{ |
{ |
503 |
setError (err, CODE_FILE_ERROR, "Cannot write the PR to %s: %s", |
setError (err, CODE_FILE_ERROR, "Cannot write the PR to %s: %s", |
504 |
path, strerror (errno)); |
new_path, strerror (errno)); |
|
clearPRChain (database); |
|
|
free_pr (old_pr); |
|
505 |
free (bkup_path); |
free (bkup_path); |
506 |
free (old_path); |
free (old_path); |
507 |
free (path); |
free (new_path); |
508 |
return 0; |
return 0; |
509 |
} |
} |
510 |
|
|
520 |
if (fclose (prfile) == EOF) |
if (fclose (prfile) == EOF) |
521 |
{ |
{ |
522 |
setError (err, CODE_FILE_ERROR, "Error writing out PR %s: %s", |
setError (err, CODE_FILE_ERROR, "Error writing out PR %s: %s", |
523 |
path, strerror (errno)); |
new_path, strerror (errno)); |
|
clearPRChain (database); |
|
|
free_pr (old_pr); |
|
524 |
free (bkup_path); |
free (bkup_path); |
525 |
free (old_path); |
free (old_path); |
526 |
free (path); |
free (new_path); |
527 |
return 0; |
return 0; |
528 |
} |
} |
529 |
|
|
530 |
unlink (bkup_path); |
unlink (bkup_path); |
531 |
|
|
532 |
/* unlink the old file, if it is in another category dir. */ |
/* unlink the old file, if it is in another category dir. */ |
533 |
if (strcmp (field_value (old_pr, CATEGORY (database)), |
if (strcmp (field_value (pr, CATEGORY (database)), |
534 |
field_value (new_pr, CATEGORY (database))) != 0) |
field_value (new_pr, CATEGORY (database))) != 0) |
535 |
{ |
{ |
536 |
unlink (old_path); |
unlink (old_path); |
537 |
} |
} |
538 |
|
|
539 |
|
free (bkup_path); |
540 |
|
free (old_path); |
541 |
|
free (new_path); |
542 |
|
|
543 |
/* write out the new index. */ |
/* write out the new index. */ |
544 |
|
replaceCurrentPRInIndex (pr, new_pr, err); |
545 |
if (writeIndex (database, err) != 0) |
if (writeIndex (database, err) != 0) |
546 |
{ |
{ |
547 |
res = 0; |
res = 0; |
550 |
/* Send mail about the edit. */ |
/* Send mail about the edit. */ |
551 |
if (newAuditTrailEntries != NULL) |
if (newAuditTrailEntries != NULL) |
552 |
{ |
{ |
553 |
sendAuditMail (new_pr, old_pr, editUserEmailAddr, newAuditTrailEntries, |
sendAuditMail (new_pr, pr, editUserEmailAddr, newAuditTrailEntries, |
554 |
err); |
err); |
555 |
free (newAuditTrailEntries); |
free (newAuditTrailEntries); |
556 |
newAuditTrailEntries = NULL; |
newAuditTrailEntries = NULL; |
557 |
} |
} |
558 |
|
|
559 |
free_pr (old_pr); |
return res; |
560 |
free (bkup_path); |
} |
561 |
free (old_path); |
|
562 |
free (path); |
|
563 |
|
int |
564 |
|
replace_pr (const DatabaseInfo database, FILE *fp, |
565 |
|
const char *editUserEmailAddr, ErrorDesc *err) |
566 |
|
{ |
567 |
|
int res; |
568 |
|
const char *prnum = NULL; |
569 |
|
PR *curr_pr; |
570 |
|
PR *new_pr = allocPR (database); |
571 |
|
|
572 |
|
/* Read the message header in. */ |
573 |
|
if (read_header (new_pr, fp) < 0) |
574 |
|
{ |
575 |
|
setError (err, CODE_EOF_PR, "Couldn't read PR header"); |
576 |
|
return 0; |
577 |
|
} |
578 |
|
|
579 |
|
read_pr (new_pr, fp, 0); |
580 |
|
|
581 |
|
prnum = field_value (new_pr, NUMBER (database)); |
582 |
|
if (prnum == NULL) |
583 |
|
{ |
584 |
|
setError (err, CODE_INVALID_PR_CONTENTS, |
585 |
|
"Missing PR number from new PR contents"); |
586 |
|
return 0; |
587 |
|
} |
588 |
|
|
589 |
|
if (! prExists (database, prnum, err)) |
590 |
|
{ |
591 |
|
return 0; |
592 |
|
} |
593 |
|
|
594 |
|
curr_pr = readPRWithNum (database, prnum, 0, err); |
595 |
|
if (curr_pr == NULL) |
596 |
|
{ |
597 |
|
setError (err, CODE_NO_INDEX, |
598 |
|
"Unable to locate existing PR %s", prnum); |
599 |
|
return 0; |
600 |
|
} |
601 |
|
|
602 |
|
if (! check_pr (new_pr, err, 0)) |
603 |
|
{ |
604 |
|
return 0; |
605 |
|
} |
606 |
|
|
607 |
|
res = rewrite_pr (curr_pr, new_pr, editUserEmailAddr, err); |
608 |
|
|
609 |
|
free_pr (curr_pr); |
610 |
|
free_pr (new_pr); |
611 |
|
|
612 |
return res; |
return res; |
613 |
} |
} |
992 |
char pid[32]; |
char pid[32]; |
993 |
const char *oldfield; |
const char *oldfield; |
994 |
int res; |
int res; |
995 |
PR *pr; |
PR *pr, *new_pr; |
996 |
|
|
997 |
if (! prExists (database, prnum, err)) |
if (! prExists (database, prnum, err)) |
998 |
{ |
{ |
999 |
free (newcontents); |
free (newcontents); |
1000 |
|
if (changeReason != NULL) |
1001 |
|
{ |
1002 |
|
free (changeReason); |
1003 |
|
} |
1004 |
return 0; |
return 0; |
1005 |
} |
} |
1006 |
if (fieldIndex == InvalidFieldIndex) |
if (fieldIndex == InvalidFieldIndex) |
1007 |
{ |
{ |
1008 |
setError (err, CODE_INVALID_FIELD_NAME, "Invalid field name"); |
setError (err, CODE_INVALID_FIELD_NAME, "Invalid field name"); |
1009 |
free (newcontents); |
free (newcontents); |
1010 |
|
if (changeReason != NULL) |
1011 |
|
{ |
1012 |
|
free (changeReason); |
1013 |
|
} |
1014 |
return 0; |
return 0; |
1015 |
} |
} |
1016 |
if (requiresChangeReason (fieldDefForIndex (fieldIndex)) |
if (requiresChangeReason (fieldDefForIndex (fieldIndex)) |
1022 |
free (newcontents); |
free (newcontents); |
1023 |
return 0; |
return 0; |
1024 |
} |
} |
1025 |
|
|
1026 |
sprintf (pid, "%d", (int) getpid ()); |
sprintf (pid, "%d", (int) getpid ()); |
1027 |
if (lock_pr (database, prnum, "edit_field", pid, err) == 0) |
if (lock_pr (database, prnum, "edit_field", pid, err) == 0) |
1028 |
{ |
{ |
1029 |
free (newcontents); |
free (newcontents); |
1030 |
|
if (changeReason != NULL) |
1031 |
|
{ |
1032 |
|
free (changeReason); |
1033 |
|
} |
1034 |
return 0; |
return 0; |
1035 |
} |
} |
1036 |
|
|
1039 |
{ |
{ |
1040 |
unlock_pr (database, prnum, err); |
unlock_pr (database, prnum, err); |
1041 |
setError (err, CODE_UNREADABLE_PR, "Error reading PR %s.", prnum); |
setError (err, CODE_UNREADABLE_PR, "Error reading PR %s.", prnum); |
|
free_pr (pr); |
|
1042 |
free (newcontents); |
free (newcontents); |
1043 |
if (changeReason != NULL) |
if (changeReason != NULL) |
1044 |
{ |
{ |
1065 |
newcontents = totalVal; |
newcontents = totalVal; |
1066 |
} |
} |
1067 |
|
|
1068 |
|
/* create a copy of the currently active pr so that we can modify it and |
1069 |
|
do subsequent validation allowing for us to bail out cleanly */ |
1070 |
|
new_pr = allocPR (database); |
1071 |
|
set_field (new_pr, NUMBER (database), prnum, err); |
1072 |
|
set_field (new_pr, CATEGORY (database), field_value (pr, CATEGORY (database)), |
1073 |
|
err); |
1074 |
|
fillInPR (new_pr, err); |
1075 |
|
|
1076 |
/* set_field () verifies that the value is valid before doing the |
/* set_field () verifies that the value is valid before doing the |
1077 |
set. */ |
set. */ |
1078 |
if (set_field (pr, fieldIndex, newcontents, err) == 0) |
/* set_field returns a boolean!!! */ |
1079 |
|
if (set_field (new_pr, fieldIndex, newcontents, err) == 0) |
1080 |
{ |
{ |
1081 |
res = 0; |
res = 0; |
|
free_pr (pr); |
|
1082 |
} |
} |
1083 |
else |
else |
1084 |
{ |
{ |
1085 |
if (changeReason != NULL) |
if (changeReason != NULL) |
1086 |
{ |
{ |
1087 |
setFieldChangeReason (pr, fieldIndex, changeReason); |
setFieldChangeReason (new_pr, fieldIndex, changeReason); |
1088 |
free (changeReason); |
free (changeReason); |
1089 |
changeReason = NULL; |
changeReason = NULL; |
1090 |
} |
} |
1091 |
|
|
1092 |
res = rewrite_pr (pr, editUserEmailAddr, err); |
res = rewrite_pr (pr, new_pr, editUserEmailAddr, err); |
|
} |
|
|
|
|
|
if (unlock_pr (database, prnum, err) == 0) |
|
|
{ |
|
|
res = 0; |
|
1093 |
} |
} |
1094 |
|
|
1095 |
|
free_pr (pr); |
1096 |
|
free_pr (new_pr); |
1097 |
free (newcontents); |
free (newcontents); |
1098 |
if (changeReason != NULL) |
if (changeReason != NULL) |
1099 |
{ |
{ |
1100 |
free (changeReason); |
free (changeReason); |
1101 |
changeReason = NULL; |
changeReason = NULL; |
1102 |
} |
} |
1103 |
|
|
1104 |
|
if (unlock_pr (database, prnum, err) == 0) |
1105 |
|
{ |
1106 |
|
res = 0; |
1107 |
|
} |
1108 |
|
|
1109 |
return res; |
return res; |
1110 |
} |
} |
1111 |
|
|
1229 |
return res; |
return res; |
1230 |
} |
} |
1231 |
|
|
|
/* Add an entry to the AUDIT_TRAIL field. PARAMS are the format |
|
|
parameters used when composing the entry. FMT is the format of the |
|
|
audit-trail entry to add. */ |
|
|
|
|
|
static int |
|
|
addAuditTrailEnt (PR *pr, QueryFormat *fmt, |
|
|
FormatNamedParameter *params, ErrorDesc *err) |
|
|
{ |
|
|
char *newAuditString = NULL; |
|
|
char *finalAuditString; |
|
|
const char *t; |
|
|
char *currDate = get_curr_date (); |
|
|
|
|
|
if (fmt == NULL) |
|
|
{ |
|
|
fmt = getAuditTrailFormat (pr->database); |
|
|
} |
|
|
|
|
|
/* Format the new audit entry. */ |
|
|
process_format (NULL, &newAuditString, pr, NULL, fmt, "\n", params); |
|
|
|
|
|
/* Squirrel it away, because we'll need to mail it later. */ |
|
|
append_string (&newAuditTrailEntries, newAuditString); |
|
|
|
|
|
/* Now append the formatted string to the audit-trail field. */ |
|
|
t = field_value (pr, AUDIT_TRAIL (pr->database)); |
|
|
if (t == NULL) |
|
|
{ |
|
|
t = ""; |
|
|
} |
|
|
finalAuditString = xstrdup (t); |
|
|
append_string (&finalAuditString, newAuditString); |
|
|
set_field (pr, AUDIT_TRAIL (pr->database), finalAuditString, err); |
|
|
|
|
|
free (finalAuditString); |
|
|
free (newAuditString); |
|
|
free (currDate); |
|
|
|
|
|
return 0; |
|
|
} |
|
|
|
|
|
static void |
|
|
sendAuditMail (PR *pr, PR *oldPR, const char *editUserEmailAddr, |
|
|
const char *newAuditTrailEntries, ErrorDesc *err) |
|
|
{ |
|
|
FormatNamedParameter *parms = NULL; |
|
|
const DatabaseInfo database = pr->database; |
|
|
|
|
|
parms = allocateNamedParameter ("$EditUserEmailAddr", editUserEmailAddr, |
|
|
parms); |
|
|
parms = allocateNamedParameter ("$NewAuditTrail", newAuditTrailEntries, |
|
|
parms); |
|
|
|
|
|
if (strcmp (field_value (pr, RESPONSIBLE (database)), |
|
|
field_value (oldPR, RESPONSIBLE (database))) != 0) |
|
|
{ |
|
|
parms |
|
|
= allocateNamedParameter ("$OldResponsible", |
|
|
field_value (oldPR, RESPONSIBLE (database)), |
|
|
parms); |
|
|
} |
|
|
|
|
|
composeMailMessage (pr, oldPR, "audit-mail", parms, NULL, err); |
|
|
freeFormatParameterList (parms); |
|
|
} |
|
|
|
|
|
static int |
|
|
applyChangeAction (ChangeActions action, PR *pr, PR *oldPR, FieldIndex field, |
|
|
ErrorDesc *err, FormatNamedParameter *params) |
|
|
{ |
|
|
FieldEdit *fieldEdits = action->edits; |
|
|
FieldList fields = action->requiredFields; |
|
|
|
|
|
while (fields != NULL) |
|
|
{ |
|
|
const char *fldval = get_field_value (pr, oldPR, fields->ent, NULL, NULL); |
|
|
if (value_is_empty (fldval)) |
|
|
{ |
|
|
setError (err, CODE_INVALID_PR_CONTENTS, |
|
|
"Required field %s missing from PR %s.", |
|
|
complexFieldIndexToString (fields->ent), |
|
|
field_value (pr, NUMBER (pr->database))); |
|
|
return 1; |
|
|
} |
|
|
fields = fields->next; |
|
|
} |
|
|
|
|
|
if (action->requireChangeReason && field_change_reason (pr, field) == NULL) |
|
|
{ |
|
|
setError (err, CODE_INVALID_PR_CONTENTS, |
|
|
"Edit of field %s requires a change reason.", |
|
|
fieldDefForIndex (field)->name); |
|
|
return 1; |
|
|
} |
|
|
|
|
|
while (fieldEdits != NULL) |
|
|
{ |
|
|
if (applyFieldEdit (pr, fieldEdits, err, params) != 0) |
|
|
{ |
|
|
return 1; |
|
|
} |
|
|
fieldEdits = fieldEdits->next; |
|
|
} |
|
|
return 0; |
|
|
} |
|
|
|
|
|
static int |
|
|
applyChangeActions (PR *pr, PR *oldPR, FieldIndex field, |
|
|
ChangeActions actions, ErrorDesc *err, |
|
|
FormatNamedParameter *params) |
|
|
{ |
|
|
{ |
|
|
ChangeActions actionList = actions; |
|
|
|
|
|
while (actionList != NULL) |
|
|
{ |
|
|
if (actionList->expr == NULL |
|
|
|| pr_matches_expr (pr, oldPR, actionList->expr, params)) |
|
|
{ |
|
|
if (applyChangeAction (actionList, pr, oldPR, field, err, params)) |
|
|
{ |
|
|
return 1; |
|
|
} |
|
|
} |
|
|
actionList = actionList->next; |
|
|
} |
|
|
} |
|
|
|
|
|
if (field != InvalidFieldIndex && addAuditEntryP (pr->database, |
|
|
field, actions)) |
|
|
{ |
|
|
ChangeActions action = actions; |
|
|
while (actions != NULL) |
|
|
{ |
|
|
if (actions->addAuditTrail) |
|
|
{ |
|
|
break; |
|
|
} |
|
|
actions = actions->next; |
|
|
} |
|
|
|
|
|
if (action != NULL) |
|
|
{ |
|
|
addAuditTrailEnt (pr, action->auditTrailFormat, params, err); |
|
|
} |
|
|
else |
|
|
{ |
|
|
addAuditTrailEnt (pr, NULL, params, err); |
|
|
} |
|
|
} |
|
|
|
|
|
return 0; |
|
|
} |
|
|
|
|
|
static int |
|
|
processFieldChange (PR *pr, PR *oldPR, FieldIndex field, |
|
|
ErrorDesc *err, const char *editUserEmailAddr, |
|
|
const char *oldValue, const char *newValue) |
|
|
{ |
|
|
ChangeActions actions; |
|
|
FormatNamedParameter *params = NULL; |
|
|
int res; |
|
|
|
|
|
if (oldValue == NULL) |
|
|
{ |
|
|
oldValue = ""; |
|
|
} |
|
|
if (newValue == NULL) |
|
|
{ |
|
|
newValue = ""; |
|
|
} |
|
|
|
|
|
if (fieldDefForIndex (field)->readonly) |
|
|
{ |
|
|
setError (err, CODE_READONLY_FIELD, |
|
|
newBadFieldEntry (field, NULL, NULL), |
|
|
"Field %s is read-only: `%s'->`%s'", |
|
|
fieldDefForIndex (field)->name, |
|
|
oldValue, |
|
|
newValue); |
|
|
return 1; |
|
|
} |
|
|
|
|
|
{ |
|
|
char *currDate = get_curr_date (); |
|
|
params = allocateNamedParameter ("$CurrentDate", currDate, params); |
|
|
free (currDate); |
|
|
} |
|
|
|
|
|
params = allocateNamedParameter ("$FieldName", |
|
|
fieldDefForIndex (field)->name, params); |
|
|
params = allocateNamedParameter ("$OldValue", oldValue, params); |
|
|
params = allocateNamedParameter ("$NewValue", newValue, params); |
|
|
params = allocateNamedParameter ("$EditUserEmailAddr", editUserEmailAddr, |
|
|
params); |
|
|
{ |
|
|
const char *reason = field_change_reason (pr, field); |
|
|
if (reason == NULL) |
|
|
{ |
|
|
reason = ""; |
|
|
} |
|
|
params = allocateNamedParameter ("$ChangeReason", reason , params); |
|
|
} |
|
|
|
|
|
actions = fieldDefForIndex (field)->changeActions; |
|
|
res = applyChangeActions (pr, oldPR, field, actions, err, params); |
|
|
freeFormatParameterList (params); |
|
|
return res; |
|
|
} |
|
|
|
|
1232 |
/* Delete PR PRNUM. */ |
/* Delete PR PRNUM. */ |
1233 |
int |
int |
1234 |
deletePR (const DatabaseInfo database, const char *prnum, |
deletePR (const DatabaseInfo database, const char *prnum, |
1235 |
const char *editUserEmailAddr, ErrorDesc *err) |
const char *editUserEmailAddr, ErrorDesc *err) |
1236 |
{ |
{ |
|
char *path = lookup_pr_path (database, prnum, err); |
|
1237 |
char pid[32]; |
char pid[32]; |
1238 |
FormatNamedParameter *parms = NULL; |
FormatNamedParameter *parms = NULL; |
1239 |
|
short delete_status; |
|
if (path == NULL) |
|
|
{ |
|
|
return -1; |
|
|
} |
|
1240 |
|
|
1241 |
if (client_lock_gnats (database, err)) |
if (client_lock_gnats (database, err)) |
1242 |
{ |
{ |
|
free (path); |
|
1243 |
return -4; |
return -4; |
1244 |
} |
} |
1245 |
|
|
1246 |
if (lock_pr (database, prnum, GNATS_USER, pid, err) == 0) |
if (lock_pr (database, prnum, GNATS_USER, pid, err) == 0) |
1247 |
{ |
{ |
1248 |
client_unlock_gnats (); |
client_unlock_gnats (); |
|
free (path); |
|
1249 |
return -4; |
return -4; |
1250 |
} |
} |
1251 |
|
|
1252 |
if (removePRFromIndex (database, prnum, err)) |
if ((delete_status = pr_delete (database, prnum, err)) < 0) |
|
{ |
|
|
unlock_pr (database, prnum, err); |
|
|
client_unlock_gnats (); |
|
|
free (path); |
|
|
return -5; |
|
|
} |
|
|
|
|
|
if (unlink (path)) |
|
1253 |
{ |
{ |
|
setError (err, CODE_FILE_ERROR, "Unable to unlink file %s\n", path); |
|
1254 |
unlock_pr (database, prnum, err); |
unlock_pr (database, prnum, err); |
1255 |
client_unlock_gnats (); |
client_unlock_gnats (); |
1256 |
free (path); |
return delete_status; |
|
return -6; |
|
1257 |
} |
} |
1258 |
|
|
|
free (path); |
|
|
|
|
1259 |
if (unlock_pr (database, prnum, err) == 0) |
if (unlock_pr (database, prnum, err) == 0) |
1260 |
{ |
{ |
1261 |
client_unlock_gnats (); |
client_unlock_gnats (); |