JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
added licensing information
[contractor-progress.git] / tasks.php
1 <?php
2
3 #  Copyright (C) 2008  Jason Woofenden
4 #
5 #  This program is free software: you can redistribute it and/or modify
6 #  it under the terms of the GNU Affero General Public License as published by
7 #  the Free Software Foundation, either version 3 of the License, or
8 #  (at your option) any later version.
9 #
10 #  This program is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #  GNU Affero General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Affero General Public License
16 #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 require_once('code/tasks.php');
19
20 $GLOBALS['tasks_form_recipient'] = "fixme@example.com";
21
22
23
24 require_once('code/wfpl/template.php');
25 require_once('code/wfpl/format.php');
26 require_once('code/wfpl/messages.php');
27 require_once('code/wfpl/email.php');
28 require_once('code/db_connect.php');
29
30 function description_has_fixmes($description) {
31         return (strpos($description, 'FIXME') !== false);
32 }
33
34 # replace every character in $str with "&nbsp;"
35 function tonbsp($matches) {
36         return str_repeat('&nbsp;', strlen($matches[0]) * 2);
37 }
38
39 # encode as html, make it display newlines and leading spaces
40 function enc_htmlbrtab($str) {
41         $str = enc_htmlbr($str);
42         $str = preg_replace_callback("|^ *|m", tonbsp, $str);
43         return $str;
44 }
45
46 function tasks_get_fields() {
47         $title = format_oneline($_REQUEST['title']);
48         $url = format_oneline($_REQUEST['url']);
49         $description = format_unix($_REQUEST['description']);
50         $price = format_decimal($_REQUEST['price']);
51
52         tasks_tem_sets($title, $url, $description, $price);
53
54         return array($title, $url, $description, $price);
55 }
56
57 function tasks_tem_sets($title, $url, $description, $price) {
58         tem_set('title', $title);
59         tem_set('url', $url);
60         tem_set('description', $description);
61         tem_set('price', $price);
62 }
63
64 function tasks_main() {
65         if(!logged_in()) {
66                 $GLOBALS['url'] = this_url();
67                 return 'login';
68         }
69
70         if(isset($_REQUEST['tasks_id'])) {
71                 $ret = tasks_display_main();
72                 if($ret) {
73                         return $ret;
74                 }
75                 tem_show('display_body');
76         } else {
77                 $ret = tasks_edit_main();
78                 if($ret) {
79                         return $ret;
80                 }
81                 tem_show('edit_body');
82         }
83
84         tem_show('main_body');
85 }
86
87 function tasks_display_main() {
88         $task_id = format_int($_REQUEST['tasks_id']);;
89         $client_id = logged_in();
90         if(logged_in_as_contractor()) {
91                 $row = db_get_row('tasks', 'title,url,description,state,price', 'where id=%i', $task_id);
92         } else {
93                 $row = db_get_row('tasks', 'title,url,description,state,price', 'where id=%i && client_id=%i', $task_id, $client_id);
94         }
95         if($row) {
96                 list($title, $url, $description, $state, $price) = $row;
97                 tem_set('task_id', $task_id);
98                 tem_set('title', $title);
99                 tem_set('url', $url);
100                 tem_set('description', $description);
101                 tem_set('state', task_state_pretty($state));
102                 tem_set('price', $price);
103                 if($state == TASK_BUG) {
104                         tem_show('bug_title');
105                 } else {
106                         tem_show('normal_title');
107                 }
108                 if(logged_in_as_contractor()) {
109                         switch($state) {
110                                 case TASK_DRAFT:
111                                 case TASK_NEEDS_CLARIFICATION:
112                                 case TASK_NEEDS_QUOTE:
113                                 case TASK_BUG:
114                                 case TASK_NEEDS_GO_AHEAD:
115                                         tem_show('normal_edit_link');
116                                         tem_show('price_row');
117                                 break;
118                                 case TASK_QUEUED:
119                                         tem_show('normal_edit_link');
120                                         tem_show('working_link');
121                                         tem_show('price_row');
122                                 case TASK_WORKING:
123                                         tem_show('price_row');
124                                         tem_show('needs_testing_link');
125                                 break;
126                                 case TASK_NEEDS_TESTING:
127                                 case TASK_FINISHED:
128                                         tem_show('price_row');
129                                 break;
130                         }
131                 } else {
132                         switch($state) {
133                                 case TASK_DRAFT:
134                                 case TASK_NEEDS_CLARIFICATION:
135                                 case TASK_NEEDS_QUOTE:
136                                 case TASK_BUG:
137                                         tem_show('normal_edit_link');
138                                 break;
139                                 case TASK_NEEDS_GO_AHEAD:
140                                         tem_show('price_row');
141                                         tem_show('approve_price_link');
142                                         tem_show('normal_edit_link');
143                                 break;
144                                 case TASK_QUEUED:
145                                         tem_show('price_row');
146                                         tem_show('warning_edit_link');
147                                 break;
148                                 case TASK_WORKING:
149                                         tem_show('price_row');
150                                 break;
151                                 case TASK_NEEDS_TESTING:
152                                         tem_show('price_row');
153                                         tem_show('finished_link');
154                                 break;
155                                 case TASK_FINISHED:
156                                         tem_show('price_row');
157                                 break;
158                         }
159                 }
160         } else {
161                 message("Task #$task_id not found");
162                 return './';
163         }
164 }
165
166 function tasks_edit_main() {
167         $state = TASK_DRAFT; # will be overwritten
168         $client_id = logged_in(); # fixed shortly if we're contractor
169         $edit_id = format_int($_REQUEST['tasks_edit_id']);
170         unset($_REQUEST['tasks_edit_id']);
171         if($edit_id) {
172                 $owner = db_get_value('tasks', 'client_id', 'where id=%i', $edit_id);
173                 if(logged_in_as_contractor()) {
174                         $client_id = $owner;
175                 } elseif($owner != $client_id) {
176                         message('Sorry, that task was entered by/for another client.');
177                         return './';
178                 }
179
180                 # add hidden field for database id of row we're editing
181                 tem_set('tasks_edit_id', $edit_id);
182                 tem_show('editing');
183
184                 $state = db_get_value('tasks', 'state', 'where id=%i', $edit_id);
185         }
186
187         if(isset($_REQUEST['tasks_new_bug'])) {
188                 $state = TASK_BUG;
189         }
190
191         if(isset($_REQUEST['tasks_approve_price_id'])) {
192                 $id = $_REQUEST['tasks_approve_price_id'];
193                 $owner = db_get_value('tasks', 'client_id', 'where id=%i', $id);;
194                 if(logged_in() != $owner) {
195                         message("Error: can't approve a task entered by/for another client.");
196                         return './';
197                 }
198                 db_update('tasks', 'state', TASK_QUEUED, 'where id=%i', $id);
199                 message('Price approved.');
200                 return './';
201         }
202
203         if(isset($_REQUEST['tasks_working_id'])) {
204                 $id = $_REQUEST['tasks_working_id'];
205                 if(!logged_in_as_contractor()) {
206                         message("Error: only Jason can say what he's working on.");
207                         return './';
208                 }
209                 db_update('tasks', 'state', TASK_WORKING, 'where id=%i', $id);
210                 message('OK, client locked out of modifying that one.');
211                 return './';
212         }
213
214         if(isset($_REQUEST['tasks_needs_testing_id'])) {
215                 $id = $_REQUEST['tasks_needs_testing_id'];
216                 if(!logged_in_as_contractor()) {
217                         message("Error: only Jason can say when he's done.");
218                         return './';
219                 }
220                 db_update('tasks', 'state', TASK_NEEDS_TESTING, 'where id=%i', $id);
221                 message('Task awaits testing.');
222                 return './';
223         }
224
225         if(isset($_REQUEST['tasks_finished_id'])) {
226                 $id = $_REQUEST['tasks_finished_id'];
227                 $owner = db_get_value('tasks', 'client_id', 'where id=%i', $id);;
228                 if(logged_in() != $owner) {
229                         message("Error: can't test a task entered by/for another client.");
230                         return './';
231                 }
232                 db_update('tasks', 'state', TASK_FINISHED, 'where id=%i', $id);
233                 message('Task marked as finished.');
234                 # FIXME also mark it as paid if client's balance can cover it
235                 return './';
236         }
237
238         $delete_id = format_int($_REQUEST['tasks_delete_id']);
239         unset($_REQUEST['tasks_delete_id']);
240         if($delete_id) {
241                 db_delete('tasks', 'where id=%i', $delete_id);
242                 message('Task deleted.');
243
244                 return './tasks.html';
245         }
246
247         if(isset($_REQUEST['title'])) {
248                 list($title, $url, $description, $price) = tasks_get_fields();
249
250                 # FIXME
251                 if(isset($_REQUEST['save_draft'])) {
252                         $state = TASK_DRAFT;
253                 } elseif(isset($_REQUEST['save_bug'])) {
254                         $state = TASK_BUG;
255                 } elseif(isset($_REQUEST['save_price']) && logged_in_as_contractor()) {
256                         $tiny_agreement = db_get_value('people', 'tiny_agreement', 'where id=%i', $client_id);
257                         if($price < $tiny_agreement) {
258                                 $state = TASK_QUEUED;
259                         } else {
260                                 $state = TASK_NEEDS_GO_AHEAD;
261                         }
262                 } elseif(isset($_REQUEST['needs_clarification'])) {
263                         $state = TASK_NEEDS_CLARIFICATION;
264                 } else { # better be "request_price"
265                         if(description_has_fixmes($description)) {
266                                 $state = TASK_NEEDS_CLARIFICATION;
267                                 message('The description is not ready to be priced yet because it still contains at least one "FIXME".');
268                         } else {
269                                 $state = TASK_NEEDS_QUOTE;
270                         }
271                 }
272
273                 if("you're happy with the POSTed values") {
274                         if($edit_id) {
275                                 if(isset($_REQUEST['price']) && logged_in_as_contractor()) {
276                                         db_update('tasks', 'title,url,description,state,price', $title, $url, $description, $state, $price, 'where id=%i', $edit_id);
277                                 } else {
278                                         db_update('tasks', 'title,url,description,state', $title, $url, $description, $state, 'where id=%i', $edit_id);
279                                 }
280                                 message('Task updated.');
281                         } else {
282                                 # new task
283                                 $paid = 0;
284                                 $client_id = logged_in();
285                                 db_insert('tasks', 'client_id,title,url,description,state,paid', $client_id, $title, $url, $description, $state, $paid);
286                                 message('Task saved.');
287                         }
288                         if($GLOBALS['tasks_form_recipient'] != "fixme@example.com") {
289                                 $to = $GLOBALS['tasks_form_recipient'];
290                                 $from = $to;
291                                 $reply_to = '';
292                                 if(isset($_REQUEST['email']) and valid_email($_REQUEST['email'])) {
293                                         $reply_to = $_REQUEST['email'];
294                                         if($_REQUEST['name'] and ereg('^[a-zA-Z0-9_\' -]*$', $_REQUEST['name']) !== false) {
295                                                 $reply_to = "$_REQUEST[name] <$reply_to>";
296                                         }
297                                 }
298                                 $subject = 'tasks form submitted';
299                                 $message = tem_run('tasks.email.txt');
300                                 $cc = '';
301                                 $bcc = '';
302                                 if(email($from, $to, $subject, $message, $reply_to, $cc, $bcc)) {
303                                         message('Due to an internal error, your message could not be sent. Please try again later.');
304                                         $error = true;
305                                 }
306                         }
307                         if($error !== true) {
308                                 return './';
309                         }
310                 }
311                 # otherwise, we display the form again. tasks_get_fields() has
312                 # already put the posted values back into the template engine, so they will
313                 # show up in the form fields. You should add some message asking people to
314                 # fix their entry in whatever way you require.
315         } elseif($edit_id) {
316                 # we've recieved an edit id, but no data. So we grab the values to be edited from the database
317                 list($title, $url, $description, $state, $paid) = db_get_row('tasks', 'title,url,description,state,price', 'where id=%i', $edit_id);
318                 tasks_tem_sets($title, $url, $description, $price);
319         } else {
320                 # form not submitted, you can set default values like so:
321                 #tem_set('client_id', 'Yes');
322         }
323
324         # display header
325         if($edit_id) {
326                 tem_show('edit_msg');
327         } elseif($state == TASK_BUG) {
328                 tem_show('bug_msg');
329         } else {
330                 tem_show('new_msg');
331         }
332
333         # display instructions
334         if($state == TASK_BUG) {
335                 tem_show('bug_instructions');
336                 if(logged_in_as_contractor()) {
337                         tem_show('price_field');
338                         tem_show('contractor_submits');
339                 } else {
340                         tem_show('bug_submit');
341                 }
342         } elseif($state == TASK_NEEDS_QUOTE && logged_in_as_contractor()) {
343                 tem_show('set_price_instructions');
344                 tem_show('price_field');
345                 tem_show('contractor_submits');
346         } else {
347                 if(description_has_fixmes($description)) {
348                         tem_show('fixme_instructions');
349                 } else {
350                         tem_show('normal_instructions');
351                 }
352                 tem_show('normal_submits');
353         }
354 }
355
356 ?>