errors.php 30.7 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
796
797
/**
 * Exception - Email is disabled for this user
 */
class EmailDisabledException extends EmailException {}

798
799
800
/** 
 * Exception - artefact not found 
 */
801
class ArtefactNotFoundException extends NotFoundException {}
802

803
804
805
806
/**
 * Exception - block instance not found
 */
class BlockInstanceNotFoundException extends NotFoundException {}
807
808
809
810
811

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

813
814
815
/**
 * Exception - view not found
 */
816
class ViewNotFoundException extends NotFoundException {}
817

818
819
820
821
822
823
/**
 * Exception - view limit exceeded
 * for example, multiple profile views
 */
class ViewLimitExceededException extends UserException {}

Penny Leach's avatar
Penny Leach committed
824
825
826
/**
 * Exception - user not found
 */
827
class UserNotFoundException extends NotFoundException {}
828

829
830
831
832
833
834
835
836
837
838
839
/**
 * 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')));
    }
}

840
/**
841
 * Exception - group not found
842
 */
843
class GroupNotFoundException extends NotFoundException {}
844

Martyn Smith's avatar
Martyn Smith committed
845
846
847
848
849
/**
 * Exception - fired when something happens that would make the user exceed their quota
 */
class QuotaExceededException extends UserException {}

850
851
852
853
854
/**
 * Exception - fired when uploading a file fails
 */
class UploadException extends UserException {}

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

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

878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
/**
 * 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();
    }
}

906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
/**
 * 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();
    }
}

923
924
925
926
927
928
929
/**
* 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)
*/
930
class ImportException extends SystemException {
931
932
933

    public function __construct($importer, $message=null, $code=0) {
        parent::__construct($message, $code);
934
935
936
        if ($importer instanceof PluginImport) {
            $importer->cleanup();
        }
937
938
    }

939
940
941
    public function render_exception() {
        return $this->getMessage();
    }
942
943
944
}

class ExportException extends SystemException {
945

946
947
948
    public function __construct($exporter, $message=null, $code=0) {
        parent::__construct($message, $code);
    }
949
}
950

951

952
?>