errors.php 30.6 KB
Newer Older
1
2
<?php
/**
Francois Marier's avatar
Francois Marier committed
3
 * Mahara: Electronic portfolio, weblog, resume builder and social networking
4
5
 * Copyright (C) 2006-2009 Catalyst IT Ltd and others; see:
 *                         http://wiki.mahara.org/Contributors
6
 *
Francois Marier's avatar
Francois Marier committed
7
8
9
10
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
11
 *
Francois Marier's avatar
Francois Marier committed
12
13
14
15
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
16
 *
Francois Marier's avatar
Francois Marier committed
17
18
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20
 *
 * @package    mahara
Penny Leach's avatar
Penny Leach committed
21
 * @subpackage core
22
 * @author     Catalyst IT Ltd
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
24
 * @copyright  (C) 2006-2009 Catalyst IT Ltd http://catalyst.net.nz
25
26
27
 *
 */

28
29
defined('INTERNAL') || die();

30
31
32
/**#@+
 * @access private
 */
33
// These are bitmaps - the next one should be 4
34
/** Display the errors on the screen */
35
define('LOG_TARGET_SCREEN', 1);
36
/** Write the errors to the error log, as specified in your php configuration */
37
define('LOG_TARGET_ERRORLOG', 2);
38
39
/** Write the error to stdout (using echo) */
define('LOG_TARGET_STDOUT', 4);
40
41
42
/** Display the errors on the screen in the admin area only (short term hack
    until we create an admin notifications page) */
define('LOG_TARGET_ADMIN', 8);
43
44
/** Log to a specific file */
define('LOG_TARGET_FILE', 16);
45
46

// Logging levels
47
/** Environment type errors, such as register_globals being on */
48
define('LOG_LEVEL_ENVIRON', 1);
49
/** Debug messages */
50
define('LOG_LEVEL_DBG', 2);
51
/** Informational messages */
52
define('LOG_LEVEL_INFO', 4);
53
/** Warnings */
54
define('LOG_LEVEL_WARN', 8);
55
56
57
58
59
60
61
62
63
64

// developermodes,  also bitmaps
/** inlude debug.js */
define('DEVMODE_DEBUGJS', 1);
/** include debug.css */
define('DEVMODE_DEBUGCSS', 2);
/** include firebug lite */
define('DEVMODE_FIREBUGLITE', 4);
/** include unpacked mochikit */
define('DEVMODE_UNPACKEDJS', 8);
65
// more here.. start at 16 :)
66

67
/**#@-*/
68

69

70
// Tell PHP about our error settings
71
error_reporting(E_ALL);
72
73
set_error_handler('error');
set_exception_handler('exception');
74
75
76
77


// Logging functions

78
79
80
81
82
83
84
85
86
87
/**
 * Logs a message at the debug level
 *
 * @param string $message   The message to display
 * @param bool   $escape    Whether to HTML escape the message
 * @param bool   $backtrace Whether to provide a backtrace if the system is
 *                          configured to give backtraces at this level.
 */
function log_debug ($message, $escape=true, $backtrace=true) {
    log_message($message, LOG_LEVEL_DBG, $escape, $backtrace);
88
89
}

90
91
92
93
94
95
96
97
98
99
/**
 * Logs a message at the info level
 *
 * @param string $message   The message to display
 * @param bool   $escape    Whether to HTML escape the message
 * @param bool   $backtrace Whether to provide a backtrace if the system is
 *                          configured to give backtraces at this level.
 */
function log_info ($message, $escape=true, $backtrace=true) {
    log_message($message, LOG_LEVEL_INFO, $escape, $backtrace);
100
101
}

102
103
104
105
106
107
108
109
110
111
/**
 * Logs a message at the warning level
 *
 * @param string $message   The message to display
 * @param bool   $escape    Whether to HTML escape the message
 * @param bool   $backtrace Whether to provide a backtrace if the system is
 *                          configured to give backtraces at this level.
 */
function log_warn ($message, $escape=true, $backtrace=true) {
    log_message($message, LOG_LEVEL_WARN, $escape, $backtrace);
112
113
}

114
115
116
117
118
119
120
121
122
123
/**
 * Logs a message at the environment level
 *
 * @param string $message   The message to display
 * @param bool   $escape    Whether to HTML escape the message
 * @param bool   $backtrace Whether to provide a backtrace if the system is
 *                          configured to give backtraces at this level.
 */
function log_environ ($message, $escape=true, $backtrace=true) {
    log_message($message, LOG_LEVEL_ENVIRON, $escape, $backtrace);
124
125
}

126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
 * Logs a message at the given log level. This function should not be called by
 * any code outside of this module.
 *
 * @param string $message   The message to display
 * @param int    $loglevel  The level to log the message at
 * @param bool   $escape    Whether to HTML escape the message
 * @param bool   $backtrace Whether to provide a backtrace if the system is
 *                          configured to give backtraces at this level.
 * @param string $file      The file the error occured in
 * @param int    $line      The line number the error occured on
 * @param array  $trace     The backtrace for the error
 * @access private
 */
function log_message ($message, $loglevel, $escape, $backtrace, $file=null, $line=null, $trace=null) {
141
142
    global $SESSION, $CFG;
    if (!$SESSION && function_exists('get_config') && $CFG) {
143
144
145
        require_once(get_config('docroot') . 'auth/lib.php');
        $SESSION = Session::singleton();
    }
146
147
148
149
150
151

    static $requestprefix = '';
    if (!$requestprefix) {
        $requestprefix = substr(md5(microtime()), 0, 2) . ' ';
    }

152
153
154
155
156
157
158
159
160
161
162
163
    static $loglevelnames = array(
        LOG_LEVEL_ENVIRON => 'environ',
        LOG_LEVEL_DBG     => 'dbg',
        LOG_LEVEL_INFO    => 'info',
        LOG_LEVEL_WARN    => 'warn'
    );

    if (!function_exists('get_config') || null === ($targets = get_config('log_' . $loglevelnames[$loglevel] . '_targets'))) {
        $targets = LOG_TARGET_SCREEN | LOG_TARGET_ERRORLOG;
    }

    // Get nice backtrace information if required
164
    $trace = ($trace) ? $trace : debug_backtrace();
165
166
167
168
169
170
    // If the last caller was the 'error' function then it came from a PHP warning
    if (!is_null($file)) {
        $filename = $file;
        $linenum  = $line;
    }
    else {
171
172
        $filename  = $trace[1]['file'];
        $linenum   = $trace[1]['line'];
173
    }
174

175
    if (!function_exists('get_config') || get_config('log_backtrace_levels') & $loglevel) {
176
        list($textbacktrace, $htmlbacktrace) = log_build_backtrace($trace);
177
178
179
180
181
    }
    else {
        $textbacktrace = $htmlbacktrace = '';
    }

182
183
184
    if (is_bool($message)) {
        $loglines = array(($message ? 'bool(true)' : 'bool(false)'));
    }
185
186
187
    else if (is_null($message)) {
        $loglines = array('NULL');
    }
188
189
190
    else {
        $loglines = explode("\n", print_r($message, true));
    }
191
192

    // Make a prefix for each line, if we are logging a normal debug/info/warn message
193
    $prefix = $requestprefix;
194
    if ($loglevel != LOG_LEVEL_ENVIRON && function_exists('get_config')) {
195
196
197
198
        $docroot = get_config('docroot');
        $prefixfilename = (substr($filename, 0, strlen($docroot)) == $docroot)
            ? substr($filename, strlen($docroot))
            : $filename;
199
        $prefix .= '(' . $prefixfilename . ':' . $linenum . ') ';
200
    }
201
    $prefix = '[' . str_pad(substr(strtoupper($loglevelnames[$loglevel]), 0, 3), 3) . '] ' . $prefix;
202

203
    if ($targets & LOG_TARGET_SCREEN || (defined('ADMIN') && $targets & LOG_TARGET_ADMIN)) {
204
205
206
207
208
        // Work out which method to call for displaying the message
        if ($loglevel == LOG_LEVEL_DBG || $loglevel == LOG_LEVEL_INFO) {
            $method = 'add_info_msg';
        }
        else {
209
            $method = 'add_error_msg';
210
211
        }

212
213
214
215
216
217
        $message = implode("\n", $loglines);
        if ($escape) {
            $message = htmlspecialchars($message, ENT_COMPAT, 'UTF-8');
            $message = str_replace('  ', '&nbsp; ', $message);
        }
        $message = nl2br($message);
218
        $message = '<div class="backtrace">' . $prefix . $message . "</div>\n";
219
220
        if (is_a($SESSION, 'Session')) {
            $SESSION->$method($message, false);
221
        }
222
223
224
225
226
227
        else if (!function_exists('get_config') || get_config('installed')) {
            // Don't output when we are not installed, since this will cause the
            // redirect to the install page to fail.
            echo $message;
        }

228
        if ($backtrace && $htmlbacktrace) {
229
230
231
            if (is_a($SESSION, 'Session')) {
                $SESSION->add_info_msg($htmlbacktrace, false);
            }
232
            else if (!function_exists('get_config') || get_config('installed')) {
233
234
                echo $htmlbacktrace;
            }
235
236
237
238
239
240
241
        }
    }

    if ($targets & LOG_TARGET_ERRORLOG) {
        foreach ($loglines as $line) {
            error_log($prefix . $line);
        }
242
        if ($backtrace && $textbacktrace) {
243
244
245
246
247
248
            $lines = explode("\n", $textbacktrace);
            foreach ($lines as $line) {
                error_log($line);
            }
        }
    }
249
250
251
252
253
254
255
256
257

    if ($targets & LOG_TARGET_STDOUT) {
        foreach ($loglines as $line) {
            echo($prefix . $line . "\n");
        }
        if ($backtrace && $textbacktrace) {
            echo $textbacktrace;
        }
    }
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291

    if (function_exists('get_config')) {
        $logfilename = get_config('log_file');
        if (($targets & LOG_TARGET_FILE) && $logfilename) {
            global $LOGFILE_FH;
            static $logfile_open_attempted = null;
            if (!$logfile_open_attempted) {
                $logfile_open_attempted = true;
                $LOGFILE_FH = fopen($logfilename, 'wb');
                if ($LOGFILE_FH !== false) {
                    function _close_logfile() {
                        global $LOGFILE_FH;
                        fclose($LOGFILE_FH);
                    }
                    register_shutdown_function('_close_logfile');
                }
                else {
                    error_log("Could not open your custom log file ($logfilename)");
                }
            }

            if (is_resource($LOGFILE_FH)) {
                foreach ($loglines as $line) {
                    fwrite($LOGFILE_FH, $prefix . $line . "\n");
                }
                if ($backtrace && $textbacktrace) {
                    $lines = explode("\n", $textbacktrace);
                    foreach ($lines as $line) {
                        fwrite($LOGFILE_FH, $line . "\n");
                    }
                }
            }
        }
    }
292
293
}

294
295
296
297
298
299
300
301
302
303
/**
 * Given an array that contains a backtrace, builds two versions of it - one in
 * HTML form and one in text form - for logging.
 *
 * @param array $backtrace The backtrace to build
 * @return array           An array containing the backtraces, index 0
 *                         containing the text version and index 1 containing
 *                         the HTML version.
 * @access private
 */
304
305
306
function log_build_backtrace($backtrace) {
    $calls = array();

307
    // Remove the call to log_message
308
    //array_shift($backtrace);
309

310
311
312
313
314
315
316
317
    foreach ($backtrace as $bt) {
        $bt['file']  = (isset($bt['file'])) ? $bt['file'] : 'Unknown';
        $bt['line']  = (isset($bt['line'])) ? $bt['line'] : 0;
        $bt['class'] = (isset($bt['class'])) ? $bt['class'] : '';
        $bt['type']  = (isset($bt['type'])) ? $bt['type'] : '';
        $bt['args']  = (isset($bt['args'])) ? $bt['args'] : '';

        $args = '';
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
        if ($bt['args']) {
            foreach ($bt['args'] as $arg) {
                if (!empty($args)) {
                    $args .= ', ';
                }
                switch (gettype($arg)) {
                    case 'integer':
                    case 'double':
                        $args .= $arg;
                        break;
                    case 'string':
                        $arg = substr($arg, 0, 50) . ((strlen($arg) > 50) ? '...' : '');
                        $args .= '"' . $arg . '"';
                        break;
                    case 'array':
                        $args .= 'array(size ' . count($arg) . ')';
                        break;
                    case 'object':
                        $args .= 'object(' . get_class($arg) . ')';
                        break;
                    case 'resource':
                        $args .= 'resource(' . strstr($arg, '#') . ')';
                        break;
                    case 'boolean':
                        $args .= $arg ? 'true' : 'false';
                        break;
                    case 'NULL':
                        $args .= 'null';
                        break;
                    default:
                        $args .= 'unknown';
                }
350
351
352
353
354
355
356
357
358
359
360
361
362
363
            }
        }

        $calls[] = array(
            'file'  => $bt['file'],
            'line'  => $bt['line'],
            'class' => $bt['class'],
            'type'  => $bt['type'],
            'func'  => $bt['function'],
            'args'  => $args
        );
    }

    $textmessage = "Call stack (most recent first):\n";
364
    $htmlmessage = "<div class=\"backtrace\"><strong>Call stack (most recent first):</strong>\n<ul>";
365
366
367
368

    foreach ($calls as $call) {
        $textmessage .= "  * {$call['class']}{$call['type']}{$call['func']}({$call['args']}) at {$call['file']}:{$call['line']}\n";
        $htmlmessage .= '<li><span style="color:#933;">' . htmlspecialchars($call['class'], ENT_COMPAT, 'UTF-8') . '</span><span style="color:#060;">'
369
            . htmlspecialchars($call['type'], ENT_COMPAT, 'UTF-8') . '</span><span style="color:#339;">' . htmlspecialchars($call['func'], ENT_COMPAT, 'UTF-8')
370
371
            . '</span><span style="color:#060;">(</span><span style="color:#f00;">' . htmlspecialchars($call['args'], ENT_COMPAT, 'UTF-8') . '</span><span style="color:#060;">)</span> at <strong>' . htmlspecialchars($call['file'], ENT_COMPAT, 'UTF-8') . ':' . $call['line'] . '</strong></li>';
    }
372
    $htmlmessage .= "</div>\n";
373
374
375

    return array($textmessage, $htmlmessage);
}
376
377
378
379
380

/**
 * Ends the script with an informational message
 *
 * @param string $message The message to display
381
 * @todo this function should go away
382
383
 */
function die_info($message) {
384
    $smarty = smarty(array(), array(), array(), array('sidebars' => false));
385
386
387
388
389
390
391
    $smarty->assign('message', $message);
    $smarty->assign('type', 'info');
    $smarty->display('message.tpl');
    exit;
}


392
393
// Error/Exception handling

394
395
396
397
398
399
400
401
402
/**
 * Called when any error occurs, due to a PHP error or through a call to
 * {@link trigger_error}.
 *
 * @param int    $code    The code of the error message
 * @param string $message The message reported
 * @param string $file    The file the error was detected in
 * @param string $line    The line number the error was detected on
 * @param array  $vars    The contents of $GLOBALS at the time the error was detected
403
 * @access private
404
 */
405
406
407
function error ($code, $message, $file, $line, $vars) {
    static $error_lookup = array(
        E_NOTICE => 'Notice',
408
409
410
411
412
413
414
        E_WARNING => 'Warning',
        // Not sure if these ever get handled here
        E_ERROR => 'Error',
        // These three are not used by this application but may be used by third parties
        E_USER_NOTICE => 'User Notice',
        E_USER_WARNING => 'User Warning',
        E_USER_ERROR => 'User Error'
415
416
    );

417
418
419
420
    if (defined('E_RECOVERABLE_ERROR')) {
        $error_lookup[E_RECOVERABLE_ERROR] = 'Warning';
    }

421
422
423
424
425
426
427
428
    if (!error_reporting()) {
        return;
    }

    if (!isset($error_lookup[$code])) {
        return;
    }

429
    // Ignore errors from smarty templates, which happen all too often
430
    if (function_exists('get_config')) {
431
        $compiledir = realpath(get_config('dataroot') . 'dwoo/compile');
432
433
434
435

        if (E_NOTICE == $code && substr($file, 0, strlen($compiledir)) == $compiledir) {
            return;
        }
436
437
438
439

        if (E_NOTICE == $code && preg_match('#^' . quotemeta(get_config('docroot') . 'theme/') . '[a-z0-9-]+/pieforms#', $file)) {
            return;
        }
440
441
    }

442
443
444
445
    // Fix up the message, which is in HTML form
    $message = strip_tags($message);
    $message = htmlspecialchars_decode($message);

446
    log_message($message, LOG_LEVEL_WARN, true, true, $file, $line);
447
448
}

449
/**
450
451
452
453
454
 * Catches all otherwise uncaught exceptions. Will be deliberately used in some
 * situations. After this is called the script will end, so make sure to catch
 * any exceptions that you can deal with.
 *
 * @param Exception $e The exception that was thrown.
455
 * @access private
456
 */
457
function exception (Exception $e) {
458
459
    global $USER;
    if ($USER) {
Martyn Smith's avatar
Martyn Smith committed
460
        if (!($e instanceof MaharaException) || get_class($e) == 'MaharaException') {
461
            log_warn("An exception was thrown of class " . get_class($e) . ". \nTHIS IS BAD "
462
463
                     . "and should be changed to something extending MaharaException,\n" 
                     . "unless the exception is from a third party library.\n"
464
465
466
467
468
                     . "Original trace follows", true, false);
            log_message($e->getMessage(), LOG_LEVEL_WARN, true, true, $e->getFile(), $e->getLine(), $e->getTrace());
            $e = new SystemException($e->getMessage());
            $e->set_log_off();
        }
469
    }
470
471

    // Display the message and die
472
    $e->handle_exception();
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
}


interface MaharaThrowable {
    
    public function render_exception();
    
}

// Standard exceptions  - top level exception class. 
// all exceptions should extend one of these three.

/**
 * Very top of the tree for exceptions in Mahara.
 * Nothing should extend this directly. 
 * Contains a few helper functions for all exceptions.
 */
class MaharaException extends Exception {

    protected $log = true;

494
495
496
497
498
499
500
    public function __construct($message, $code=0) {
        parent::__construct($message, $code);
        if (!defined('MAHARA_CRASHING')) {
            define('MAHARA_CRASHING', true);
        }
    }

501
502
    public function get_string() {
        $args = func_get_args();
503
        if (function_exists('get_string')) {
504
505
506
507
508
            $args[0] = strtolower(get_class($this)) . $args[0];
            $args[1] = 'error';
            $str = call_user_func_array('get_string', $args);
            if (strpos($str, '[[') !== 0) {
                return $str;
509
510
            }
        }
511
512
513
514
515

        $tag = func_get_arg(0);
        $strings = $this->strings();
        if (array_key_exists($tag, $strings)) {
            return $strings[$tag];
516
        }
517
518
519
520
521
522
523
        
        return 'An error occured';
    }

    public function get_sitename() {
        if (!function_exists('get_config') || !$sitename = @get_config('sitename')) {
            $sitename = 'Mahara';
524
        }
525
526
527
528
529
530
        return $sitename;
    }

    public function strings() {
        return array('title' => $this->get_sitename() . ': Site unavailable');
    }
531

532
533
534
535
536
537
538
539
540
541
542
543
    public function set_log() {
        $this->log = true;
    }

    public function set_log_off() {
        $this->log = false;
    }

    public function render_exception() {
        return $this->getMessage();
    }

544
    public final function handle_exception() {
545

546
        if (!empty($this->log)) {
547
            log_message($this->getMessage(), LOG_LEVEL_WARN, true, true, $this->getFile(), $this->getLine(), $this->getTrace());
548
549
550
551
552
553
554
555
        }

        if (defined('JSON')) { // behave differently
            @header('Content-type: text/plain');
            @header('Pragma: no-cache');
            echo json_encode(array('error' => true, 'message' => $this->render_exception()));
            exit;
        }
556
557
558
559
560
        
        if (defined('CRON')) {
            echo $this->render_exception();
            exit;
        }
561

Donal McMullan's avatar
Donal McMullan committed
562
563
564
565
566
        if (defined('XMLRPC')) { // it's preferable to throw an XmlrpcServerException
            echo xmlrpc_error($this->render_exception(), $this->getCode());
            exit;
        }

567
568
569
570
571
        if (headers_sent()) {
            echo '<span style="color: red;">ERROR - something bad happened after headers have been sent. Check the error log for more information.</span>';
            die();
        }

572
        $outputtitle = $this->get_string('title');
573
        $outputmessage = trim($this->render_exception());
574

575
        if (function_exists('smarty') && !$this instanceof ConfigSanityException) {
576
            $smarty = smarty(array(), array(), array(), array('sidebars' => false));
577
578
579
580
            $smarty->assign('title', $outputtitle);
            $smarty->assign('message', $outputmessage);
            $smarty->display('error.tpl');
        }
581
        else {
582
583
            $outputtitle   = htmlspecialchars($outputtitle, ENT_COMPAT, 'UTF-8');
            $outputmessage = nl2br(htmlspecialchars($outputmessage, ENT_COMPAT, 'UTF-8'));
584
            echo <<<EOF
585
586
<html>
<head>
587
    <title>$outputtitle</title>
588
    <style type="text/css">
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
        html {
            margin: 0;
            padding: 0;
            font-family: Arial, sans-serif;
        }
        body {
            width: 600px;
            margin: 100px auto;
            font-size: 12px;
        }
        h1 {
            color: #547c22;
            font-size: 20px;
            font-weight: normal;	
            margin: 0 0 5px 0;
            padding: 0;
            text-transform: capitalize;
            border-bottom: 1px solid #819f18;
            text-align: center;
        }
        #message {
            width: 90%;
            margin: 0 auto;
            text-align: justify;
        }
614
615
616
617
618
619
        #reason {
            margin: 0 3em;
        }
    </style>
</head>
<body>
620
621
EOF;
    echo <<<EOF
622
<h1>$outputtitle</h1>
623
<div id="message">$outputmessage</div>
624
625
626
</body>
</html>
EOF;
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
        }
        // end of printing stuff to the screen...
        die();
    }
}






/**
 * SystemException - this is basically a bug in the system.
 */
class SystemException extends MaharaException implements MaharaThrowable {

643
644
645
646
647
    public function __construct($message, $code=0) {
        parent::__construct($message, $code);
        $this->set_log();
    }

648
649
650
651
652
653
654
655
656
657
    public function render_exception () {
        return $this->get_string('message');
    }

    public function strings() {
        return array_merge(parent::strings(), 
                           array('message' => 'A nonrecoverable error occured. '
                                 . 'This probably means you have encountered a bug in the system'));
    }
    
658
659
}

660
661
662
663
664
665
666
/**
 * ConfigException - something is misconfigured that's causing a problem.
 * Generally these will be the fault of admins
 */
class ConfigException extends MaharaException  implements MaharaThrowable {
     
    public function render_exception () {
667
        return $this->get_string('message') . "\n\n" . $this->getMessage();
668
669
670
671
    }

    public function strings() {
        return array_merge(parent::strings(), 
672
673
674
675
                           array('message' => 'The environment where ' . $this->get_sitename() 
                           . ' is running is misconfigured and this is causing problems. '
                           . 'You probably need to contact a server administrator to get this fixed. '
                           . 'Details, if any, follow:'));
676
677
678
679
680
681
    }
}

/**
 * UserException - the user has done something they shouldn't (or tried to)
 */
Martyn Smith's avatar
Martyn Smith committed
682
class UserException extends MaharaException implements MaharaThrowable {
683
684

    protected $log = false;
685

686
    public function render_exception() {
687
        return $this->get_string('message') . "\n\n" . $this->getMessage();
688
689
690
691
692
693
    }

    public function strings() {
        return array_merge(parent::strings(),  
                           array('message' => 'Something in the way you\'re interacting with ' 
                                 . $this->get_sitename()
694
                                 . " is causing an error.\nDetails if any, follow:"));
695
696
697
    }
}

698
699
700
701
702
703
704
/**
 * Exception - Not found. Throw this if a user is trying to view something
 * that doesn't exist
 */
class NotFoundException extends UserException {
    public function strings() {
        return array_merge(parent::strings(), 
705
706
                           array('message' => get_string('notfoundexception', 'error'),
                                 'title'   => get_string('notfound', 'error')));
707
708
709
710
711
712
713
714
    }

    public function render_exception() {
        header('HTTP/1.0 404 Not Found', true);
        return parent::render_exception();
    }
}

715
716
717

/** 
 * The configuration that Mahara is trying to be run in is insane
718
 */
719
720
721
722
723
class ConfigSanityException extends ConfigException {
    public function strings() {
        return array_merge(parent::strings(), array('message' => ''));
    }
}
724
725

/**
726
 * An SQL related error occured
727
 */
728
class SQLException extends SystemException {
729
    public function __construct($message=null, $code=0) {
730
731
        global $DB_IGNORE_SQL_EXCEPTIONS;

732
        if ($GLOBALS['_TRANSACTION_LEVEL'] > 0) {
733
734
            db_rollback();
        }
735
        parent::__construct($message, $code);
736
737
738
739

        if (empty($DB_IGNORE_SQL_EXCEPTIONS)) {
            log_warn($this->getMessage());
        }
740
741
    }
}
742

743
744
745
/**
 * An exception generated by invalid GET or POST parameters
 */
746
class ParameterException extends UserException {
747
    public function strings() {
748
749
750
751
        return array_merge(parent::strings(), array(
            'title'   => 'Mahara: Invalid Parameter',
            'message' => 'A required parameter is missing or malformed')
        );
752
753
    }
}
754

755
756
757
758
759
760
761
762
763
764
765
/**
 * A function or method has been passed the wrong kind of argument
 * Unfortunately, broken type-hints cause fatal errors - not exceptions
 */
class ParamOutOfRangeException extends SystemException {}

/**
 * Remote Server exception - something has gone wrong at the remote machine
 */
class RemoteServerException extends SystemException {}

Donal McMullan's avatar
Donal McMullan committed
766
767
768
/**
 * Xmlrpc Server exception - must output well formed XMLRPC error to the client
 */
Donal McMullan's avatar
Donal McMullan committed
769
770
771
class XmlrpcServerException extends SystemException {}

/**
772
 * Xmlrpc Client exception - Something has gone wrong in the networking
Donal McMullan's avatar
Donal McMullan committed
773
 */
774
775
776
class XmlrpcClientException extends SystemException {
    public function strings() {
        return array_merge(parent::strings(), array(
777
778
            'title'   => get_string('xmlrpccouldnotlogyouin', 'auth'),
            'message' => get_string('xmlrpccouldnotlogyouindetail', 'auth'))
779
780
781
        );
    }
}
Donal McMullan's avatar
Donal McMullan committed
782
783
784
785
786

/**
 * Error with SSL and encryption
 */
class CryptException extends SystemException {}
Donal McMullan's avatar
Donal McMullan committed
787

788
789
790
/**
 * An exception generated when e-mail can't be sent
 */
791
class EmailException extends SystemException {}
792

793
794
795
/** 
 * Exception - artefact not found 
 */
796
class ArtefactNotFoundException extends NotFoundException {}
797

798
799
800
801
/**
 * Exception - block instance not found
 */
class BlockInstanceNotFoundException extends NotFoundException {}
802
803
804
805
806

/** 
 * Exception - interaction instance not found
 */
class InteractionInstanceNotFoundException extends NotFoundException {}
807

808
809
810
/**
 * Exception - view not found
 */
811
class ViewNotFoundException extends NotFoundException {}
812

813
814
815
816
817
818
/**
 * Exception - view limit exceeded
 * for example, multiple profile views
 */
class ViewLimitExceededException extends UserException {}

Penny Leach's avatar
Penny Leach committed
819
820
821
/**
 * Exception - user not found
 */
822
class UserNotFoundException extends NotFoundException {}
823

824
825
826
827
828
829
830
831
832
833
834
/**
 * Exception - user not found while doing XMLRPC authentication
 */
class XmlrpcUserNotFoundException extends UserNotFoundException {
    public function strings() {
        return array_merge(parent::strings(),
            array('message' => ''),
            array('title'   => get_string('unabletosigninviasso', 'auth')));
    }
}

835
/**
836
 * Exception - group not found
837
 */
838
class GroupNotFoundException extends NotFoundException {}
839

Martyn Smith's avatar
Martyn Smith committed
840
841
842
843
844
/**
 * Exception - fired when something happens that would make the user exceed their quota
 */
class QuotaExceededException extends UserException {}

845
846
847
848
849
/**
 * Exception - fired when uploading a file fails
 */
class UploadException extends UserException {}

Penny Leach's avatar
Penny Leach committed
850
851
852
/**
 * Exception - Access denied. Throw this if a user is trying to view something they can't
 */
853
854
855
class AccessDeniedException extends UserException {
    public function strings() {
        return array_merge(parent::strings(), 
856
857
                           array('message' => get_string('accessdeniedexception', 'error'),
                                 'title'   => get_string('accessdenied', 'error')));
858
859
860
    }

    public function render_exception() {
861
862
        global $USER;
        if (defined('PUBLIC') && !$USER->is_logged_in()) {
863
            $loginurl = substr($_SERVER['REQUEST_URI'], strlen(get_mahara_install_subdirectory()) - 1);
864
865
866
867
            $loginurl .= (false === strpos($loginurl, '?')) ? '?' : '&';
            $loginurl .= 'login';
            redirect($loginurl);
        }
868
869
870
871
872
        header("HTTP/1.0 403 Forbidden", true);
        return parent::render_exception();
    }
}

873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
/**
 * Exception - Access denied to a group page because the user isn't a member
 * Give the user a chance to join then continue
 */
class GroupAccessDeniedException extends AccessDeniedException {
    public function render_exception() {
        global $USER, $SESSION;
        if (defined('GROUP') && $USER->is_logged_in()) {
            $roles = $USER->get('grouproles');
            if (!isset($roles[GROUP])) {
                $group = group_current_group();
                if ($group->jointype == 'open'
                    || $group->jointype == 'invite' && get_record('group_member_invite', 'group', GROUP, 'member', $USER->get('id'))) {
                    $SESSION->add_error_msg(get_string('notmembermayjoin', 'group', $group->name));
                    $next = substr($_SERVER['REQUEST_URI'], strlen(get_mahara_install_subdirectory()) - 1);
                    redirect(get_config('wwwroot') . 'group/view.php?id=' . GROUP . '&next=' . urlencode($next));
                }
                if ($group->jointype == 'request' && !get_record('group_member_request', 'group', GROUP, 'member', $USER->get('id'))) {
                    $SESSION->add_error_msg(get_string('notamember', 'group'));
                    redirect(get_config('wwwroot') . 'group/requestjoin.php?id=' . GROUP . '&returnto=view');
                }
            }
        }
        header("HTTP/1.0 403 Forbidden", true);
        return parent::render_exception();
    }
}

901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
/**
 * Exception - Access totally denied, the user won't be able to access it even if they log in 
 * as the administrator
 */
class AccessTotallyDeniedException extends UserException {
    public function strings() {
        return array_merge(parent::strings(), 
                           array('message' => get_string('accessdeniedexception', 'error'),
                                 'title'   => get_string('accessdenied', 'error')));
    }

    public function render_exception() {
        header("HTTP/1.0 403 Forbidden", true);
        return parent::render_exception();
    }
}

918
919
920
921
922
923
924
/**
* something has happened during import.
* either: the user is there, in which case they get the bug screen,
*         it's a spawned request during an xmlrpc server ping (content_ready) in which case XMLRPC will be defined
*         or it's a queued fetch, in which case CRON will be defined.
* @todo maybe refactor at the point that we have something other than importing over mnet (eg userland)
*/
925
class ImportException extends SystemException {
926
927
928

    public function __construct($importer, $message=null, $code=0) {
        parent::__construct($message, $code);
929
930
931
        if ($importer instanceof PluginImport) {
            $importer->cleanup();
        }
932
933
    }

934
935
936
    public function render_exception() {
        return $this->getMessage();
    }
937
938
939
}

class ExportException extends SystemException {
940

941
942
943
    public function __construct($exporter, $message=null, $code=0) {
        parent::__construct($message, $code);
    }
944
}
945

946

947
?>