/ www / include /
/www/include/lib_upload.php.inc
1 <?php
2 /* Copyright (C) 2012, 2013 The Meme Factory, Inc. http://www.meme.com/
3 * Copyright (C) 2007-2009, 2011, Karl O. Pinc <kop@meme.com>
4 *
5 * This file is part of Gombe-MI.
6 *
7 * Gombe-MI is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Gombe-MI. If not, see <http://www.gnu.org/licenses/>.
19 *
20 * Library of php functions supporting file upload for Gombe-MI.
21 *
22 * Karl O. Pinc <kop@meme.com>
23 */
24
25 // Includes
26 require('constants.php.inc');
27
28 // Constants
29
30 // Values for the 'action' form variable.
31 define('ACTION_UPLOAD', 'u');
32 define('ACTION_NONE', '');
33
34 // Values for the login session keys.
35
36 // Where the login timestamp is kept.
37 define('LOGIN_TIMESTAMP', 'LOGIN_TIMESTAMP');
38 // How long, in seconds, is the user allowed to stay logged in
39 // without refereshing the page?
40 // (Note that this must be <= to the PHP session.cookie_lifetime.)
41 define('LOGIN_INACTIVITY_TIMEOUT', 3600); // 1 hour
42 // The (basename of the) last file loaded into the database.
43 define('UPLOADED_FILE', 'UPLOADED_FILE');
44
45 // Values for the Upload_Connection class's savepoints.
46 define('SP_NAME', 'SLOPPY_SAVEPOINT');
47 define('SP_COMMAND', 'SAVEPOINT ' . SP_NAME . ';');
48
49 /////////////////////////////////////////////////////////////////////
50 function maintain_login_session() {
51 /*
52 * Maintain the session variables throughout a login.
53 *
54 * Input:
55 *
56 * Returns: n/a
57 *
58 * Side Effects:
59 * Messes with the PHP session.
60 *
61 * Remarks:
62 * Use this instead of php's session_start() function.
63 *
64 * This uses a specific timestamp variable to determine
65 * how long the login session lasts. This is safer than
66 * relying on the browser to respect the session cookie lifetime.
67 */
68
69 define('SESSION_NAME', 'GOMBEMI');
70
71 session_name(SESSION_NAME);
72 session_start();
73
74 if (array_key_exists(LOGIN_TIMESTAMP, $_SESSION)
75 && time() > ($_SESSION[LOGIN_TIMESTAMP] + LOGIN_INACTIVITY_TIMEOUT)) {
76 // The user has been inactive too long.
77 // Erase their session.
78
79 // Unset all of the session variables.
80 $_SESSION = array();
81 }
82
83 // Update the login timestamp so we know when we were last seen.
84 $_SESSION[LOGIN_TIMESTAMP] = time();
85
86 // Reset the cookie lifetime so it stays around.
87 $cookie_lifetime = ini_get('session.cookie_lifetime');
88 $cookie_path = ini_get('session.cookie_path');
89
90 if ($cookie_lifetime > 0) {
91 // Extend cookie lifetime.
92 // For unknown reasons this breaks without the right session id.
93 // (The cookie path and domain must match or the cookie won't be updated.)
94 setcookie(session_name(), session_id(), time() + $cookie_lifetime, $cookie_path);
95 }
96
97 return;
98 }
99 /////////////////////////////////////////////////////////////////////
100 function show_html_start($title) {
101 /*
102 * Start the html on a page.
103 *
104 * Input:
105 * $title The window title to use.
106 *
107 * Returns: n/a
108 *
109 * Side Effects:
110 * Outputs html.
111 */
112
113 ?><?xml version="1.0" encoding="UTF-8" standalone="no"?>
114 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
115 <html xmlns="http://www.w3.org/1999/xhtml">
116 <head>
117 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
118 <title><?php print($title); ?></title>
119 <link rel="stylesheet" href="/gombemi.css" type="text/css" />
120 </head>
121 <body>
122
123 <?php
124 show_navbar();
125 return;
126 }
127
128 /////////////////////////////////////////////////////////////////////
129 function show_html_finish() {
130 /*
131 * Finish the html on a page.
132 *
133 * Input:
134 *
135 * Returns: n/a
136 *
137 * Side Effects:
138 * Outputs html.
139 */
140
141 show_navbar();
142 ?>
143
144 </body>
145 </html>
146 <?php
147
148 return;
149 }
150
151 /////////////////////////////////////////////////////////////////////
152 function show_navbar() {
153 /*
154 * Show the stock navbar for page top and bottom.
155 *
156 * Input:
157 *
158 * Returns: n/a
159 *
160 * Side Effects:
161 * Display's HTML.
162 */
163
164 ?>
165 <p class="navbar">
166 <a href="<?php print HOME_TARGET; ?>">HOME</a> | <a href="<?php print PROGRAM_BASE; ?>logout">Logout</a>
167 </p>
168 <?php
169
170 return;
171 }
172
173 /////////////////////////////////////////////////////////////////////
174 function get_session_var($varname, $escape_html) {
175 /*
176 * Get a HTML form varaiable that's kept in in the session.
177 *
178 * Input:
179 * $varname Name of the form/session variable.
180 * $escape_html When true, escape all html special chars in the
181 * HTML form variable before saving it in the session
182 * or returning it. Also remove leading and trailing
183 * spaces.
184 *
185 * Returns:
186 * The value of the form variable if present, otherwise the value
187 * of the session varaible, if present, otherwise "".
188 *
189 * Uses (reads and sets):
190 * $SESSION[$varname] Set to the HTML form varaible value (if any).
191 *
192 */
193
194 if (array_key_exists($varname, $_REQUEST)) {
195 $v = $_REQUEST[$varname];
196 if ($escape_html) {
197 $v = htmlentities(trim($v), ENT_QUOTES);
198 }
199 $_SESSION[$varname] = $v;
200 } else {
201 if (array_key_exists($varname, $_SESSION)) {
202 $v = $_SESSION[$varname];
203 } else {
204 $v = '';
205 }
206 }
207
208 return $v;
209 }
210
211 /////////////////////////////////////////////////////////////////////
212
213 //
214 // Classes
215 //
216
217 /////////////////////////////////////////////////////////////////////
218 class Upload_form {
219 /*
220 * Abstraction of the html form that asks what data to upload where by who.
221 */
222
223 // Private variables. (Set 'em and forget 'em.)
224 var $db; // The database to connect to.
225 var $user; // The userid of the user (htmlescaped).
226 var $password; // The user's password.
227 var $desc; // Description (name) of data to upload.
228
229 var $errors; // String with htmlized error text.
230
231 // Public variables.
232 var $extra_input; // Extra html, one element for each data item,
233 // to add at the bottom of the form.
234 // Each array item is an array, which may have
235 // either of the following two keys:
236 // 'label', containing a textual label
237 // 'data', containing the form to input data
238
239 function Upload_form($db, $user, $password, $desc) {
240 $this->db = $db;
241 $this->user = $user;
242 $this->password = $password;
243 $this->desc = $desc;
244 $this->extra_input = array();
245
246 $this->errors = '';
247
248 return;
249 }
250
251 function error($l, $e) {
252 /*
253 * Syntax: error($l, $e, $faulting, $desc)
254 *
255 * Save an error to display on the form.
256 *
257 * Input:
258 * $l Line number in the input file, or ''.
259 * $e HTML string containing an error message.
260 * Must be able to be put in a HTML paragraph block.
261 * $faulting Boolean. Optional. Defaults to FALSE.
262 * TRUE when this error may be a false positive.
263 * $desc Optional. Defaults to ''.
264 * Extra description of the error.
265 *
266 * Class variables:
267 * $errors
268 *
269 * Side Effects:
270 * Updates $this->errors.
271 */
272
273 //
274 // Parse args.
275 //
276 if (func_num_args() > 2) {
277 $faulting = func_get_arg(2);
278 } else {
279 $faulting = FALSE;
280 }
281
282 if (func_num_args() > 3) {
283 $desc = func_get_arg(3);
284 } else {
285 $desc = '';
286 }
287
288 // Initialize
289 if ($desc != '') {
290 $desc = ': ' . $desc;
291 }
292
293 if ($l == '') {
294 $line_desc = '';
295 } else {
296 $line_desc = "Line $l: ";
297 }
298
299 // Save the error.
300 $this->errors .= '<p>'
301 . $this->make_caution($faulting)
302 . $line_desc
303 . '<em class="error">'
304 . $e
305 . "</em>$desc</p>\n";
306 return;
307 }
308
309 function make_caution($faulting) {
310 /*
311 * Syntax: make_caution($faulting)
312 *
313 * Return a blurb when an error message may be an artifact.
314 *
315 * Input:
316 * $faulting Boolean. When TRUE input data was ignored.
317 *
318 * Returns:
319 * A htmlized string describing the problem.
320 *
321 * Side Effects:
322 */
323
324 if ($faulting) {
325 // We already had one error, so subsequent problems may or
326 // may not be real.
327 $prefix = 'CAUTION -- This error may not be real; prior row(s) of the uploaded file were rejected:<br />';
328 } else {
329 $prefix = '';
330 }
331
332 return $prefix;
333 }
334
335 function show() {
336 /*
337 * Syntax: show([$error [, $password]])
338 *
339 * Show the upload form.
340 *
341 * Input:
342 * $error (optional) A HTML string with an error message.
343 * $password (optional) A database password to use in place of
344 * the class variable.
345 *
346 * Class variables:
347 * $db
348 * $user
349 * $password
350 * $desc
351 * $extra_input
352 * $errors (cleared)
353 *
354 *
355 * Returns: n/a
356 *
357 * Side Effects:
358 * Outputs html.
359 * Form variables:
360 * action ACTION_UPLOAD on submission
361 * db Database name
362 * user Database user
363 * password Database password
364 * datafile Uploaded file
365 * Current page re-invoked up submission.
366 *
367 *
368 * Remarks:
369 * Shows the error, or $this->errors, if any, at the top of the page.
370 *
371 * If any of $this->db, $this->user, or the password used
372 * are the empty string then the user
373 * is allowed to login again. Otherwise, only the database
374 * can be changed.
375 *
376 * One of the main pages of the program.
377 */
378
379 //
380 // Get arguments, if any.
381 //
382
383 // The error message
384 if (func_num_args() > 0) {
385 $error = func_get_arg(0);
386 } else {
387 $error = '';
388 }
389
390 $error = $this->errors . $error;
391 $this->errors = ''; // reset for next form display
392
393 // The password
394 if (func_num_args() > 1) {
395 $password = func_get_arg(1);
396 } else {
397 $password = $this->password;
398 }
399
400 //
401 // Display the error message, if any.
402 //
403 if ($error !== '') {
404 if (isset($_FILES['datafile']) && isset($_FILES['datafile']['name'])) {
405 print '<p>File supplied: <em class="filename">'
406 . htmlentities($_FILES['datafile']['name'], ENT_QUOTES)
407 . '</em></p>';
408 }
409 print $error;
410 print "<hr />\n";
411 }
412
413 //
414 // Ouput a form where the user can upload files to compare.
415 //
416 ?>
417
418 <form action="" enctype="multipart/form-data" method="post">
419 <div>
420 <input name="action"
421 type="hidden"
422 value="<?php print ACTION_UPLOAD; ?>"
423 />
424 </div>
425
426 <table>
427 <tr>
428 <td class="label">
429 <label for="db_id">Database:</label>
430 </td>
431 <td>
432 <input name="db"
433 tabindex="1"
434 id="db_id"
435 type="text"
436 size="30"
437 value="<?php print $this->db; ?>"
438 />
439 </td>
440 </tr>
441 <tr>
442 <?php
443 if ($this->db == '' || $this->user == '' || $password == '') {
444 ?>
445 <td class="label">
446 <label for="user_id">User:</label>
447 </td>
448 <td>
449 <input name="user"
450 tabindex="2"
451 id="user_id"
452 type="text"
453 size="30"
454 value="<?php print $this->user; ?>"
455 />
456 </td>
457 <?php
458 } else {
459 ?>
460 <td class="label">
461 User:
462 </td>
463 <td>
464 <?php
465 print $this->user;
466 ?>
467 </td>
468 <?php
469 }
470 ?>
471 </tr>
472
473 <?php
474 if ($this->db == '' || $this->user == '' || $password == '') {
475 ?>
476 <tr>
477 <td class="label">
478 <label for="password_id">Password:</label>
479 </td>
480 <td>
481 <input name="password"
482 tabindex="3"
483 id="password_id"
484 type="password"
485 size="30"
486 />
487 </td>
488 </tr>
489 <?php
490 }
491 $tabindex = 4;
492 // Display the extra input fields
493 foreach ($this->extra_input as $more_html) {
494 $tabindex++;
495 print '<tr>';
496 if (array_key_exists('label', $more_html))
497 print '<td class="label">' . $more_html['label'] . '</td>';
498 if (array_key_exists('data', $more_html))
499 print '<td>' . $more_html['data'] . '</td>';
500 print '</tr>';
501 }
502 ?>
503 <tr>
504 <td class="label">
505 <label for="datafile_id">
506 File with <?php print($this->desc); ?>:
507 </label>
508 </td>
509 </tr>
510 <tr>
511 <td colspan="2">
512 <input name="datafile"
513 tabindex="<?php print $tabindex; ?>"
514 id="datafile_id"
515 type="file"
516 size="75"
517 />
518 </td>
519 </tr>
520 </table>
521
522 <p>
523 <input value="Upload" tabindex="20" type="submit" />
524 </p>
525 </form>
526
527 <?php
528 return;
529 }
530 }
531
532 /////////////////////////////////////////////////////////////////////
533 class Uploaded_data{
534 /*
535 * Abstraction of the data that was uploaded.
536 *
537 * Remarks:
538 * Uses the HTML form variables of the Upload_form class.
539 */
540
541 // Public variables.
542 var $data = array();
543 var $filename = '';
544
545 // Private variables.
546 var $upload_form;
547
548 function Uploaded_data(&$upload_form) {
549 /*
550 * Class constructor.
551 *
552 * Input:
553 * $upload_form An instance of the Upload_form class.
554 *
555 * Returns: n/a
556 *
557 * Side Effects:
558 * none
559 */
560
561 $this->upload_form =& $upload_form;
562
563 if (array_key_exists('datafile', $_FILES)) {
564 $this->filename = htmlentities($_FILES['datafile']['name'], ENT_QUOTES);
565 } else {
566 $this->filename = '';
567 }
568 }
569
570 function success() {
571 /*
572 * Note a successful upload into the database.
573 *
574 * Input:
575 *
576 * Returns: n/a
577 *
578 * Side Effects:
579 * Remembers the file successfully uploaded
580 *
581 * Remarks:
582 * Must be called so we know not to allow the same file
583 * to be loaded into the same db twice (in a row.)
584 */
585
586 $_SESSION[UPLOADED_FILE][$this->upload_form->db] = $this->filename;
587
588 return;
589 }
590
591 function upload() {
592 /*
593 * Get the uploaded data and clean it up.
594 *
595 * Input:
596 *
597 * Returns:
598 * TRUE when file successfully uploaded, FALSE otherwise.
599 *
600 * Sets:
601 * $data Array containing the uploaded data file.
602 *
603 * Side Effects:
604 * Calls $upload_form->show() to output html when there's an error.
605 *
606 * HTML form variables:
607 * datafile Uploaded file
608 */
609
610 // Do a little de-referencing up-front.
611 $upload_form = $this->upload_form;
612
613 // Get uploaded data
614
615 // $datafile
616 $datafile = $this->filename;
617 if ($datafile !== '') {
618 $datafile_tmp = $_FILES['datafile']['tmp_name'];
619 $datafile_err = $_FILES['datafile']['error'];
620 } else {
621 $datafile_tmp = '';
622 $datafile_err = UPLOAD_ERR_NO_FILE;
623 }
624
625 // Get uploaded file.
626
627 switch ($datafile_err) {
628 case UPLOAD_ERR_OK:
629 break;
630
631 case UPLOAD_ERR_NO_FILE:
632 // No file was uploaded.
633 $upload_form->show('<p class="error">Error uploading file: No file uploaded</p>');
634 return FALSE;
635
636 default:
637 // Got an error on the file upload.
638 // Let the user try again.
639 $upload_form->show('<p class="error">'
640 . explain_error('Error uploading file', $datafile_err)
641 . '</p>');
642 return FALSE;
643 }
644
645 if (array_key_exists(UPLOADED_FILE, $_SESSION)
646 && array_key_exists($upload_form->db
647 , $_SESSION[UPLOADED_FILE])
648 && $this->filename
649 == $_SESSION[UPLOADED_FILE][$upload_form->db]) {
650 // The user is trying to upload a file (with the same name as)
651 // the file which was last successfully put into the database.
652 $upload_form->show('<p class="error">Upload aborted. A file with this name was the last file successfully loaded into the ' . $upload_form->db . ' database.</p>');
653 return FALSE;
654 }
655
656 if ($_FILES['datafile']['size'] == 0) {
657 // Let the user try again.
658 $upload_form->show('<p class="error">The file uploaded'
659 . " ($datafile) is empty.</p>");
660 return FALSE;
661 }
662
663 // Get rid of OS specific cruft.
664 $tmpf = '/tmp/upcen.' . getmypid();
665 if (move_and_fix_eol($datafile_tmp, $tmpf) != 0) {
666 // Let the user try again.
667 $upload_form->show('<p class="error">Unexpected failure trying'
668 . 'to move uploaded file.</p>');
669 return FALSE;
670 }
671
672 // Read the file
673 $content = file($tmpf);
674 unlink($tmpf); // cleanup
675 if ($content === FALSE) {
676 // Let the user try again.
677 $upload_form->show('<p class="error">Unexpected failure trying'
678 . ' to read uploaded file.</p>');
679 return FALSE;
680 }
681
682 // Get rid of any stupid MS Windows eof character
683 $last = count($content) - 1;
684 if (substr($content[$last], strlen($content[$last]) - 1, 1) == chr(26)) {
685 // File ends in a Ctrl-Z, the DOS EOF character.
686 // Remove it.
687 $content[$last] = substr($content[$last]
688 , 0
689 , strlen($content[$last]) - 1);
690 }
691
692 $this->data = $content;
693
694 return TRUE;
695 }
696 }
697
698 /////////////////////////////////////////////////////////////////////
699 class Upload_connection {
700
701 /*
702 * Abstracts database operations over a postgresql connection.
703 */
704
705 /*
706 * Bugs:
707 * It does not seem (php-4.3.9-3.22) that you can cache
708 * a query result resource in an object. So we've two
709 * different functions sql() and sql_result().
710 * This could just be the result of php4 stupidness with
711 * objects and references. I've not thought it through.
712 */
713
714 // Private variables.
715 var $upload_form; // An Upload_form instance.
716 var $pg; // The postgresql connection handle.
717 var $db; // The database connected to.
718 var $user; // The user used to authenticate.
719 var $password; // The password used to authenticate.
720 var $last_pg_error; // The last error from postgres
721 // Set to '' by any function that might cause a pg
722 // error, except when we know that we're going to
723 // do operations that might trash pg_last_error().
724
725 function Upload_connection($db, $user, $password, &$upload_form) {
726 /*
727 * Class constructor.
728 *
729 * Input:
730 * $db The database to connect to.
731 * $user The username to login with.
732 * $password The password to login with.
733 * $upload_form The HTML form used to get info from the user.
734 *
735 * Returns: A class instance.
736 *
737 * Side Effects: none
738 *
739 * Remarks:
740 */
741
742 // Save the instance varaibles..
743 $this->upload_form =& $upload_form;
744 $this->db = $db;
745 $this->user = $user;
746 $this->password = $password;
747 unset($this->last_pg_error);
748
749 return;
750 }
751
752 function connect() {
753 /*
754 * Connect to the database
755 *
756 * Input:
757 *
758 * Returns:
759 * TRUE on success, FALSE otherwise.
760 *
761 * Globals:
762 * $last_pg_error Cleared.
763 *
764 * Side Effects:
765 * Connects to the db with the username and password.
766 * On error re-displays html form with error.
767 */
768
769 //
770 // Login to postgres.
771 //
772 $this->pg = pg_connect('host=' . PG_HOST
773 . ' port=' . PG_PORT
774 . " dbname=$this->db user=$this->user password='"
775 . str_replace("'"
776 , "\\'"
777 , str_replace('\\', '\\' . '\\', $this->password))
778 . "'");
779 if ($this->pg === FALSE) {
780 // Let the user try again.
781 // Allow user to login again by calling with blank password.
782 $this->upload_form->show('<p> <em class="error">Login failed.</em>'
783 . ' Is the database, user,'
784 . ' and password correct?</p>'
785 , '');
786 return FALSE;
787 }
788
789 $this->sloppy = FALSE;
790 unset($this->last_pg_error);
791
792 return TRUE;
793 }
794
795 function begin() {
796 /*
797 * Begin a transation.
798 *
799 * Syntax: begin()
800 * begin(sloppy_errors)
801 *
802 * Input:
803 * sloppy_errors (boolean, optional, defaults to FALSE)
804 * When TRUE simulate the ability to continue executing statements
805 * within a transaction after an error, reporting a new error
806 * for each error that occurs. Queries can still fail but
807 * subsequent queries within the simulated transaction can
808 * appear to succeed, although they will never be committed
809 * to the database.
810 *
811 * Note that this works by rolling back on error and starting a
812 * new transaction that will never be committed. So all statements
813 * executed after a failure will not see the effects of statements
814 * executed before the failure. This can produce errors that would
815 * not otherwise exist.
816 *
817 * Returns:
818 * TRUE on success, FALSE otherwise.
819 *
820 * Globals:
821 * $last_pg_error Cleared.
822 *
823 * Side Effects:
824 * Begins a db transaction.
825 * Can establish a savepoint.
826 * On error re-displays html form with error.
827 */
828
829 if (func_num_args() > 0) {
830 $sloppy_errors = func_get_arg(0);
831 } else {
832 $sloppy_errors = FALSE;
833 }
834
835 $this->sloppy = $sloppy_errors;
836 $this->faulting = FALSE;
837 unset($this->last_pg_error);
838
839 //
840 // Start the transaction.
841 //
842 if (pg_query($this->pg, 'BEGIN;') === FALSE) {
843 // Let the user try again.
844 $this->upload_form->show('<p>Failed to begin transaction:'
845 . '<br /><em class="error">'
846 . htmlentities(pg_last_error($this->pg))
847 . '</em></p>');
848 return FALSE;
849 }
850
851 if ($this->sloppy) {
852 // We need a savepoint so we've one to release.
853 if (pg_query($this->pg, SP_COMMAND) === FALSE) {
854 $this->upload_form->show('<p>Failed to establish savepoint at'
855 . ' transaction start:<br /><em class="error">'
856 . htmlentities(pg_last_error($this->pg))
857 . '</em></p>');
858 return FALSE;
859 }
860 }
861
862 return TRUE;
863 }
864
865
866 function rollback($error) {
867 /*
868 * Rollback the ongoing transation.
869 *
870 * Input:
871 * $error A HTML string with an error message.
872 *
873 * Returns:
874 * TRUE on success, FALSE otherwise.
875 *
876 * Globals:
877 * $last_pg_error Cleared.
878 *
879 * Side Effects:
880 * Rolls back the db transaction.
881 * Re-displays html form with error allowing user another chance.
882 */
883
884 // DEBUG
885 //error_log($error);
886
887 // Give up.
888 if (pg_query($this->pg, 'ROLLBACK;') === FALSE) {
889 // Tell user original error and that the rollback failed.
890 $error .= '<p>Failed to rollback transaction:'
891 . '<br /><em class="error">'
892 . htmlentities(pg_last_error($this->pg))
893 . '</em></p>';
894 }
895
896 $this->upload_form->show($error);
897 $this->sloppy = FALSE;
898 unset($this->last_pg_error);
899
900 return;
901 }
902
903 function sloppy_restart() {
904 /*
905 * Private function, internal use only.
906 *
907 * Restart a sloppy transaction after an error
908 *
909 * Input:
910 *
911 * Returns: n/a
912 *
913 * Globals:
914 * $last_pg_error Set.
915 *
916 * Side Effects:
917 * Rolls back transaction that caused an error to the savepoint.
918 * Begins a new transaction.
919 * Remembers that the sloppy transaction should fail.
920 * Can abort the entire script.
921 */
922
923 // In a sloppy transaction.
924 $this->faulting = TRUE;
925 $this->last_pg_error = pg_last_error($this->pg);
926
927 // Rollback the existing transaction.
928 if (pg_query($this->pg, 'ROLLBACK TO SAVEPOINT ' . SP_NAME . ';') === FALSE) {
929 error_log('Warning: Failed to roll sloppy transaction back to savepoint'
930 + ' after error');
931 $this->upload_form->show('<p class="error">Program aborted: Internal error:'
932 . ' Failed to rollback to savepoint</p>');
933 exit();
934 }
935
936 return;
937 }
938
939 function redo_savepoint() {
940 /*
941 * Redo the savepoint we use for sloppy transactions.
942 *
943 * Input:
944 *
945 * Returns: n/a/
946 *
947 * Side Effects:
948 * May send html with an error to the user and abort.
949 * Re-establishes the savepoint.
950 *
951 * Remarks:
952 * $last_pg_error not altered because we only call this after
953 * we know that the last query succeeded.
954 */
955
956 if (pg_query($this->pg, 'RELEASE SAVEPOINT ' . SP_NAME . ';') === FALSE) {
957 error_log('Program aborted: Internal error: Failed to release savepoint');
958 $this->upload_form->show('<p class="error">Internal error:'
959 . ' Failed to release savepoint');
960 exit();
961 }
962 if (pg_query($this->pg, SP_COMMAND) === FALSE) {
963 error_log('Program aborted: Internal error: Failed to establish savepoint');
964 $this->upload_form->show('<p class="error">Internal error:'
965 . ' Failed to establish savepoint');
966 exit();
967 }
968
969 return;
970 }
971
972 function sql($sql) {
973 /*
974 * Submit some SQL to the db.
975 *
976 * Syntax: sql($sql)
977 * sql($sql, $side_effects)
978 *
979 * Input:
980 * $sql The SQL to send to the db.
981 * $side_effects Boolean. Optional. Defaults to TRUE.
982 * When true the sql may alter the db.
983 *
984 * Returns:
985 * TRUE if the SQL executed successfully, FALSE otherwise.
986 *
987 * Globals:
988 * $last_pg_error Cleared.
989 *
990 * Side Effects:
991 * SQL may alter db.
992 */
993
994 if (func_num_args() > 1) {
995 $side_effects = func_get_arg(1);
996 } else {
997 $side_effects = TRUE;
998 }
999
1000 unset($this->last_pg_error);
1001
1002 if (pg_query($this->pg, $sql) === FALSE) {
1003 error_log("Failed query: $sql");
1004
1005 if ($this->sloppy === TRUE) {
1006 // In a sloppy transaction.
1007 $this->sloppy_restart();
1008 }
1009 return FAlSE;
1010 }
1011
1012
1013 if ($this->sloppy === TRUE && $side_effects) {
1014 // Get rid of the old savepoint and make a new one.
1015 $this->redo_savepoint();
1016 }
1017
1018 return TRUE;
1019 }
1020
1021 function sql_result($sql) {
1022 /*
1023 * Submit some SQL to the db and get a result.
1024 *
1025 * Syntax: sql_result($sql)
1026 * sql_result($sql, $side_effects)
1027 *
1028 * Input:
1029 * $sql The SQL to send to the db.
1030 * $side_effects Boolean. Optional. Defaults to FALSE.
1031 * When true the sql may alter the db.
1032 *
1033 * Returns:
1034 * The first column of the first row of the most recent
1035 * query result set or FALSE on error or the integer 0
1036 * if there is no result.
1037 *
1038 * Globals:
1039 * $last_pg_error Cleared.
1040 *
1041 * Side Effects:
1042 * SQL may alter db.
1043 *
1044 * Remarks:
1045 * Values are returned as strings. Distingush no rows from the string
1046 * "0" using ===.
1047 *
1048 * Bugs:
1049 * Booleans are returned as the strings "t" and "f" and NULL as
1050 * the string "NULL".
1051 */
1052
1053 if (func_num_args() > 1) {
1054 $side_effects = func_get_arg(1);
1055 } else {
1056 $side_effects = FALSE;
1057 }
1058
1059 unset($this->last_pg_error);
1060 $qr = pg_query($this->pg, $sql);
1061
1062 if ($qr === FALSE) {
1063 error_log("Failed query: $sql");
1064
1065 if ($this->sloppy === TRUE) {
1066 // In a sloppy transaction.
1067 $this->sloppy_restart();
1068 }
1069
1070 return FALSE;
1071 }
1072
1073 if (pg_num_rows($qr) == 0) {
1074 return 0;
1075 }
1076
1077 // Stash the result.
1078 $r = pg_fetch_result($qr, 0, 0);
1079
1080 if ($this->sloppy === TRUE && $side_effects) {
1081 // Get rid of the old savepoint and make a new one.
1082 $this->redo_savepoint();
1083 }
1084
1085 return $r;
1086
1087 }
1088
1089 function last_error() {
1090 /*
1091 * Return string describing the last error on the connection.
1092 *
1093 * Input:
1094 *
1095 * Returns:
1096 * String describing last error on the connection.
1097 *
1098 * Globals:
1099 * $last_pg_error Referenced.
1100 *
1101 * Side Effects:
1102 */
1103
1104 if (isset($this->last_pg_error)) {
1105 return $this->last_pg_error;
1106 }
1107
1108 return pg_last_error($this->pg);
1109 }
1110
1111 function sloppy_rollback() {
1112 /*
1113 * Private function, for internal use only.
1114 *
1115 * Rollback the current (sloppy) transaction.
1116 *
1117 * Input:
1118 *
1119 * Returns: n/a
1120 *
1121 * Globals:
1122 * $last_pg_error Cleared.
1123 *
1124 * Side Effects:
1125 * Rolls back the current transaction.
1126 *
1127 * Remarks:
1128 * Called only when in a sloppy transaction that has been continued
1129 * and has 'completed' without further error.
1130 *
1131 */
1132
1133 // The sloppy transaction had a failure.
1134 // Rollback the 'continued' transaction.
1135 if (pg_query($this->pg, 'ROLLBACK;') === FALSE) {
1136 error_log('The sloppy transaction could not be rolled back');
1137 // This is kinda bad, we don't want to commit things that we shouldn't.
1138 exit('Internal error: Failed to rollback a sloppy transaction');
1139 }
1140
1141 unset($this->last_pg_error);
1142 return;
1143 }
1144
1145 function commit() {
1146 /*
1147 * Commit the current transaction.
1148 *
1149 * Input:
1150 *
1151 * Returns:
1152 * TRUE on success or FALSE on failure.
1153 *
1154 * Globals:
1155 * $last_pg_error Cleared.
1156 *
1157 * Side Effects:
1158 * Completes the current transaction.
1159 *
1160 */
1161
1162
1163 if ($this->sloppy && $this->faulting) {
1164 // The sloppy transaction had a failure.
1165 $this->sloppy_rollback();
1166 return FALSE; // The transaction did not commit.
1167 }
1168
1169 unset($this->last_pg_error);
1170
1171 if (pg_query($this->pg, 'COMMIT;') === FALSE) {
1172 return FALSE;
1173 }
1174
1175 return TRUE;
1176 }
1177
1178 function complete() {
1179 /*
1180 * Commit the current transaction, if possible.
1181 *
1182 * Input:
1183 *
1184 * Returns:
1185 * TRUE if the transaction completed or FALSE if it did not.
1186 *
1187 * Globals:
1188 * $last_pg_error Cleared.
1189 *
1190 * Side Effects:
1191 * Completes the current transaction.
1192 *
1193 * Remarks:
1194 * If the transaction won't commit, roll it back instead.
1195 */
1196
1197 if ($this->sloppy && $this->faulting) {
1198 // The sloppy transaction had a failure.
1199 $this->sloppy_rollback();
1200 return FALSE; // The sloppy transaction did not commit.
1201 }
1202
1203 if (!$this->commit()) {
1204 $this->rollback('<p>Failed to commit transaction:'
1205 . '<br /><em class="error">'
1206 . htmlentities(pg_last_error($this->pg))
1207 . '</em></p>');
1208 return FALSE;
1209 }
1210
1211 unset($this->last_pg_error);
1212 return TRUE;
1213 }
1214 }