source: trunk/lib/App.inc.php @ 20

Last change on this file since 20 was 20, checked in by scdev, 19 years ago

Tons of little updates and bugfixes. CSS updates to templates and core css files. File upload ability to module_maker. Remade Upload interface to use setParam/getParam.

File size: 38.2 KB
Line 
1<?php
2/**
3 * App.inc.php
4 * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
5 *
6 * Primary application framework class.
7 *
8 * @author  Quinn Comendant <quinn@strangecode.com>
9 * @version 1.0
10 */
11 
12//  Message Types
13define('MSG_SUCCESS', 0);
14define('MSG_NOTICE', 1);
15define('MSG_WARNING', 2);
16define('MSG_ERR', 3); // PHP user error style.
17define('MSG_ERROR', 3);
18
19require_once dirname(__FILE__) . '/Utilities.inc.php';
20
21class App {
22   
23    // Name of this application.
24    var $app = '_app_';
25
26    // If App::start has run successfully.
27    var $running = false;
28
29    // Instance of database object.
30    var $db;
31   
32    // Array of query arguments will be carried persistently between requests.
33    var $_carry_queries = array();
34
35    // Hash of global application parameters.
36    var $_params = array();
37
38    // Default parameters.
39    var $_param_defaults = array(
40
41        // Public name and email address for this application.
42        'site_name' => null,
43        'site_email' => null,
44
45        // The location the user will go if the system doesn't knew where else to send them.
46        'redirect_home_url' => '/',
47       
48        // SSL URL used when redirecting with App::sslOn().
49        'ssl_domain' => null,
50        'ssl_enabled' => false,
51   
52        // Character set for page output. Used in the Content-Type header and the HTML <meta content-type> tag.
53        'character_set' => 'utf-8',
54
55        // Human-readable format used to display dates.
56        'date_format' => 'd M Y',
57        'sql_date_format' => '%e %b %Y',
58        'sql_time_format' => '%k:%i',
59
60        // Use php sessions?
61        'enable_session' => false,
62        'session_name' => 'Strangecode',
63        'session_use_cookies' => true,
64   
65        // Use database?
66        'enable_db' => false,
67
68        // Use db-based sessions?
69        'enable_db_session_handler' => false,
70   
71        // DB passwords should be set as apache environment variables in httpd.conf, readable only by root.
72        'db_server' => 'localhost',
73        'db_name' => null,
74        'db_user' => null,
75        'db_pass' => null,
76
77        // Database debugging.
78        'db_always_debug' => false, // TRUE = display all SQL queries.
79        'db_debug' => false, // TRUE = display db errors.
80        'db_die_on_failure' => false, // TRUE = script stops on db error.
81       
82        // For classes that require db tables, do we check that a table exists and create if missing?
83        'db_create_tables' => null,
84
85        // The level of error reporting. Don't set this to 0 to suppress messages, instead use display_errors to control display.
86        'error_reporting' => E_ALL,
87
88        // Don't display errors by default; it is preferable to log them to a file.
89        'display_errors' => false,
90   
91        // Directory in which to store log files.
92        'log_directory' => '',
93
94        // PHP error log.
95        'php_error_log' => 'php_error_log',
96
97        // General application log.
98        'log_filename' => 'app_error_log',
99
100        // Logging priority can be any of the following, or false to deactivate:
101        // LOG_EMERG     system is unusable
102        // LOG_ALERT     action must be taken immediately
103        // LOG_CRIT      critical conditions
104        // LOG_ERR       error conditions
105        // LOG_WARNING   warning conditions
106        // LOG_NOTICE    normal, but significant, condition
107        // LOG_INFO      informational message
108        // LOG_DEBUG     debug-level message
109        'log_file_priority' => false,
110        'log_email_priority' => false,
111        'log_sms_priority' => false,
112        'log_screen_priority' => false,
113   
114        // Email address to receive log event emails.
115        'log_to_email_address' => null,
116       
117        // SMS Email address to receive log event SMS messages.
118        'log_to_sms_address' => null,
119       
120        // The from address used for system emails.
121        'envelope_sender_address' => '',
122   
123        // A key for calculating simple cryptographic signatures. Set using as an environment variables in the httpd.conf with 'SetEnv SIGNING_KEY <key>'.
124        'signing_key' => 'aae6abd6209d82a691a9f96384a7634a',
125    );
126   
127    /**
128     * This method enforces the singleton pattern for this class. Only one application is running at a time.
129     *
130     * @return  object  Reference to the global SessionCache object.
131     * @access  public
132     * @static
133     */
134    function &getInstance($app=null)
135    {
136        static $instance = null;
137
138        if ($instance === null) {
139            $instance = new App($app);
140        }
141
142        return $instance;
143    }
144   
145    /**
146     * Constructor.
147     */
148    function App($app=null)
149    {
150        if (isset($app)) {
151            $this->app .= $app;
152        }
153       
154        if (!isset($_SESSION[$this->app])) {
155            $_SESSION[$this->app] = array();
156        }
157
158        // Initialize default parameters.
159        $this->_params = array_merge($this->_params, $this->_param_defaults);
160    }
161
162    /**
163     * Set (or overwrite existing) parameters by passing an array of new parameters.
164     *
165     * @access public
166     * @param  array    $param     Array of parameters (key => val pairs).
167     */
168    function setParam($param=null)
169    {
170        if (!isset($this) || !is_a($this, 'App')) {
171            $this =& App::getInstance();
172        }
173
174        if (isset($param) && is_array($param)) {
175            // Merge new parameters with old overriding only those passed.
176            $this->_params = array_merge($this->_params, $param);
177        }
178    }
179
180    /**
181     * Return the value of a parameter.
182     *
183     * @access  public
184     * @param   string  $param      The key of the parameter to return.
185     * @return  mixed               Parameter value, or null if not existing.
186     */
187    function &getParam($param=null)
188    {
189        if (!isset($this) || !is_a($this, 'App')) {
190            $this =& App::getInstance();
191        }
192       
193        if ($param === null) {
194            return $this->_params;
195        } else if (isset($this->_params[$param])) {
196            return $this->_params[$param];
197        } else {
198            trigger_error(sprintf('Parameter is not set: %s', $param), E_USER_NOTICE);
199            return null;
200        }
201    }
202   
203    /**
204     * Begin running this application.
205     *
206     * @access  public
207     * @author  Quinn Comendant <quinn@strangecode.com>
208     * @since   15 Jul 2005 00:32:21
209     */
210    function start()
211    {
212        if ($this->running) {
213            return false;
214        }
215       
216        // Error reporting.
217        ini_set('error_reporting', $this->getParam('error_reporting'));
218        ini_set('display_errors', $this->getParam('display_errors'));
219        ini_set('log_errors', true);
220        if (is_dir($this->getParam('log_directory')) && is_writable($this->getParam('log_directory'))) {
221            ini_set('error_log', $this->getParam('log_directory') . '/' . $this->getParam('php_error_log'));
222        }
223       
224       
225        /**
226         * 1. Start Database.
227         */
228       
229        if ($this->getParam('enable_db')) {
230       
231            // DB connection parameters taken from environment variables in the httpd.conf file, readable only by root.
232            if (!empty($_SERVER['DB_SERVER'])) {
233                $this->setParam(array('db_server' => $_SERVER['DB_SERVER']));
234            }
235            if (!empty($_SERVER['DB_NAME'])) {
236                $this->setParam(array('db_name' => $_SERVER['DB_NAME']));
237            }
238            if (!empty($_SERVER['DB_USER'])) {
239                $this->setParam(array('db_user' => $_SERVER['DB_USER']));
240            }
241            if (!empty($_SERVER['DB_PASS'])) {
242                $this->setParam(array('db_pass' => $_SERVER['DB_PASS']));
243            }
244           
245            // The only instance of the DB object.
246            require_once dirname(__FILE__) . '/DB.inc.php';
247           
248            $this->db =& DB::getInstance();
249           
250            $this->db->setParam(array(
251                'db_server' => $this->getParam('db_server'),
252                'db_name' => $this->getParam('db_name'),
253                'db_user' => $this->getParam('db_user'),
254                'db_pass' => $this->getParam('db_pass'),
255                'db_always_debug' => $this->getParam('db_always_debug'),
256                'db_debug' => $this->getParam('db_debug'),
257                'db_die_on_failure' => $this->getParam('db_die_on_failure'),
258            ));
259
260            // Connect to database.
261            $this->db->connect();
262        }
263       
264       
265        /**
266         * 2. Start PHP session.
267         */
268   
269        // Skip session for some user agents.
270        if (preg_match('/Atomz|ApacheBench|Wget/i', getenv('HTTP_USER_AGENT'))) {
271            $this->setParam(array('enable_session' => false));
272        }
273       
274        if (true === $this->getParam('enable_session')) {
275       
276            // Set the session ID to one provided in GET/POST. This is necessary for linking
277            // between domains and keeping the same session.
278            if ($ses = getFormData($this->getParam('session_name'), false)) {
279                session_id($ses);
280            }
281       
282            if (true === $this->getParam('enable_db_session_handler') && true === $this->getParam('enable_db')) {
283                // Database session handling.
284                require_once dirname(__FILE__) . '/DBSessionHandler.inc.php';
285                $db_save_handler = new DBSessionHandler($this->db, array(
286                    'db_table' => 'session_tbl',
287                    'create_table' => $this->getParam('db_create_tables'),
288                ));
289            }
290           
291            // Session parameters.
292            ini_set('session.use_cookies', $this->getParam('session_use_cookies'));
293            ini_set('session.use_trans_sid', false);
294            ini_set('session.entropy_file', '/dev/urandom');
295            ini_set('session.entropy_length', '512');
296            session_name($this->getParam('session_name'));
297           
298            // Start the session. Access session data using: $_SESSION['...']
299            session_start();
300        }
301       
302       
303        /**
304         * 3. Misc setup.
305         */
306
307        // Script URI will be something like http://host.name.tld (no ending slash)
308        // and is used whenever a URL need be used to the current site.
309        // Not available on cli scripts obviously.
310        if (isset($_SERVER['HTTP_HOST']) && '' != $_SERVER['HTTP_HOST']) {
311            $this->setParam(array('site_url' => sprintf('%s://%s', ('on' == getenv('HTTPS') ? 'https' : 'http'), getenv('HTTP_HOST'))));
312        }
313
314        // A key for calculating simple cryptographic signatures.
315        if (isset($_SERVER['SIGNING_KEY'])) {
316            $this->setParam(array('signing_key' => $_SERVER['SIGNING_KEY']));
317        }
318       
319        // Used as the fifth parameter to mail() to set the return address for sent messages. Requires safe_mode off.
320        if ('' != $this->getParam('site_email') && !$this->getParam('envelope_sender_address')) {
321            $this->setParam(array('envelope_sender_address' => '-f ' . $this->getParam('site_email')));
322        }
323
324        // Character set. This should also be printed in the html header template.
325        header('Content-type: text/html; charset=' . $this->getParam('character_set'));
326       
327        $this->running = true;
328    }
329   
330    /**
331     * Stop running this application.
332     *
333     * @access  public
334     * @author  Quinn Comendant <quinn@strangecode.com>
335     * @since   17 Jul 2005 17:20:18
336     */
337    function stop()
338    {
339        session_write_close();
340        $this->db->close();
341        restore_include_path();
342        $this->running = false;
343    }
344   
345   
346    /**
347     * Add a message to the string globalmessage, which is printed in the header.
348     * Just a simple way to print messages to the user.
349     *
350     * @access public
351     *
352     * @param string $message The text description of the message.
353     * @param int    $type    The type of message: MSG_NOTICE,
354     *                        MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
355     * @param string $file    __FILE__.
356     * @param string $line    __LINE__.
357     */
358    function raiseMsg($message, $type=MSG_NOTICE, $file=null, $line=null)
359    {
360        if (!isset($this) || !is_a($this, 'App')) {
361            $this =& App::getInstance();
362        }
363
364        if (!$this->running) {
365            return false;
366        }
367       
368        $_SESSION[$this->app]['messages'][] = array(
369            'type'    => $type, 
370            'message' => $message,
371            'file'    => $file,
372            'line'    => $line
373        );
374       
375        if (!in_array($type, array(MSG_NOTICE, MSG_SUCCESS, MSG_WARNING, MSG_ERR))) {
376            App::logMsg(sprintf('Invalid MSG_* type: %s', $type), LOG_DEBUG, __FILE__, __LINE__);
377        }
378    }
379   
380    /**
381     * Prints the HTML for displaying raised messages.
382     *
383     * @access  public
384     * @author  Quinn Comendant <quinn@strangecode.com>
385     * @since   15 Jul 2005 01:39:14
386     */
387    function printRaisedMessages()
388    {
389        if (!isset($this) || !is_a($this, 'App')) {
390            $this =& App::getInstance();
391        }
392
393        if (!$this->running) {
394            return false;
395        }
396
397        while (isset($_SESSION[$this->app]['messages']) && $message = array_shift($_SESSION[$this->app]['messages'])) {
398            ?><div class="codebasemsg"><?php
399            if (error_reporting() > 0 && $this->getParam('display_errors')) {
400                echo "\n<!-- [" . $message['file'] . ' : ' . $message['line'] . '] -->';
401            }
402            switch ($message['type']) {
403            case MSG_ERR:
404                echo '<div class="error">' . $message['message'] . '</div>';
405                break;
406   
407            case MSG_WARNING:
408                echo '<div class="warning">' . $message['message'] . '</div>';
409                break;
410   
411            case MSG_SUCCESS:
412                echo '<div class="success">' . $message['message'] . '</div>';
413                break;
414   
415            case MSG_NOTICE:
416            default:
417                echo '<div class="notice">' . $message['message'] . '</div>';
418                break;
419   
420            }
421            ?></div><?php
422        }
423    }
424   
425    /**
426     * Logs a message to a user defined log file. Additional actions to take for
427     * different types of message types can be specified (ERROR, NOTICE, etc).
428     *
429     * @access public
430     *
431     * @param string $message   The text description of the message.
432     * @param int    $priority  The type of message priority (in descending order):
433     *                          LOG_EMERG     system is unusable
434     *                          LOG_ALERT     action must be taken immediately
435     *                          LOG_CRIT      critical conditions
436     *                          LOG_ERR       error conditions
437     *                          LOG_WARNING   warning conditions
438     *                          LOG_NOTICE    normal, but significant, condition
439     *                          LOG_INFO      informational message
440     *                          LOG_DEBUG     debug-level message
441     * @param string $file      The file where the log event occurs.
442     * @param string $line      The line of the file where the log event occurs.
443     */
444    function logMsg($message, $priority=LOG_INFO, $file=null, $line=null)
445    {
446        if (!isset($this) || !is_a($this, 'App')) {
447            $this =& App::getInstance();
448        }
449       
450        // If priority is not specified, assume the worst.
451        if (!$this->logPriorityToString($priority)) {
452            $this->logMsg(sprintf('Log priority %s not defined. (Message: %s)', $priority, $message), LOG_EMERG, $file, $line);
453            $priority = LOG_EMERG;
454        }
455   
456        // If log file is not specified, don't log to a file.
457        if (!$this->getParam('log_directory') || !$this->getParam('log_filename') || !is_dir($this->getParam('log_directory')) || !is_writable($this->getParam('log_directory'))) {
458            $this->setParam(array('log_file_priority' => false));
459            // We must use trigger_error to report this problem rather than calling App::logMsg, which might lead to an infinite loop.
460            trigger_error(sprintf('Codebase error: log directory (%s) not found or writable.', $this->getParam('log_directory')), E_USER_NOTICE);
461        }
462       
463        // Make sure to log in the system's locale.
464        $locale = setlocale(LC_TIME, 0);
465        setlocale(LC_TIME, 'C');
466       
467        // Data to be stored for a log event.
468        $event = array();
469        $event['date'] = date('Y-m-d H:i:s');
470        $event['remote ip'] = getRemoteAddr();
471        if (substr(PHP_OS, 0, 3) != 'WIN') {
472            $event['pid'] = posix_getpid();
473        }
474        $event['type'] = $this->logPriorityToString($priority);
475        $event['file:line'] = "$file : $line";
476        $event['message'] = strip_tags(preg_replace('/\s{2,}/', ' ', $message));
477   
478        $event_str = '[' . join('] [', $event) . ']';
479       
480        // FILE ACTION
481        if ($this->getParam('log_file_priority') && $priority <= $this->getParam('log_file_priority')) {
482            error_log($event_str . "\n", 3, $this->getParam('log_directory') . '/' . $this->getParam('log_filename'));
483        }
484   
485        // EMAIL ACTION
486        if ($this->getParam('log_email_priority') && $priority <= $this->getParam('log_email_priority')) {
487            $subject = sprintf('[%s %s] %s', getenv('HTTP_HOST'), $event['type'], $message);
488            $email_msg = sprintf("A %s log event occured on %s\n\n", $event['type'], getenv('HTTP_HOST'));
489            $headers = "From: codebase@strangecode.com\r\n";
490            foreach ($event as $k=>$v) {
491                $email_msg .= sprintf("%-11s%s\n", $k, $v);
492            }
493            mail($this->getParam('log_to_email_address'), $subject, $email_msg, $headers, '-f codebase@strangecode.com');
494        }
495       
496        // SMS ACTION
497        if ($this->getParam('log_sms_priority') && $priority <= $this->getParam('log_sms_priority')) {
498            $subject = sprintf('[%s %s]', getenv('HTTP_HOST'), $priority);
499            $sms_msg = sprintf('%s:%s %s', basename($file), $line, $event['message']);
500            $headers = "From: codebase@strangecode.com\r\n";
501            mail($this->getParam('log_to_sms_address'), $subject, $sms_msg, $headers, '-f codebase@strangecode.com');
502        }
503   
504        // SCREEN ACTION
505        if ($this->getParam('log_screen_priority') && $priority <= $this->getParam('log_screen_priority')) {
506            echo "[{$event['date']}] [{$event['type']}] [{$event['file:line']}] [{$event['message']}]\n";
507        }
508   
509        // Restore original locale.
510        setlocale(LC_TIME, $locale);
511    }
512   
513    /**
514     * Returns the string representation of a LOG_* integer constant.
515     *
516     * @param int  $priority  The LOG_* integer constant.
517     *
518     * @return                The string representation of $priority.
519     */
520    function logPriorityToString ($priority) {
521        $priorities = array(
522            LOG_EMERG   => 'emergency',
523            LOG_ALERT   => 'alert',
524            LOG_CRIT    => 'critical',
525            LOG_ERR     => 'error',
526            LOG_WARNING => 'warning',
527            LOG_NOTICE  => 'notice',
528            LOG_INFO    => 'info',
529            LOG_DEBUG   => 'debug'
530        );
531        if (isset($priorities[$priority])) {
532            return $priorities[$priority];
533        } else {
534            return false;
535        }
536    }
537   
538    /**
539     * Sets which query arguments will be carried persistently between requests.
540     * Values in the _carry_queries array will be copied to URLs (via oHREF()) and
541     * to hidden input values (via printHiddenSession()).
542     *
543     * @access  public
544     * @param   string  $query_key  The key of the query argument to save. 
545     * @author  Quinn Comendant <quinn@strangecode.com>
546     * @since   14 Nov 2005 19:24:52
547     */
548    function carryQuery($query_key)
549    {
550        if (!isset($this) || !is_a($this, 'App')) {
551            $this =& App::getInstance();
552        }
553       
554        // If not already set, and there is a non-empty value provided in the request...
555        if (!isset($this->_carry_queries[$query_key]) && getFormData($query_key, false)) {
556            // Copy the value of the specified query argument into the _carry_queries array.
557            $this->_carry_queries[$query_key] = getFormData($query_key);
558        }
559    }
560   
561    /**
562     * Outputs a fully qualified URL with a query of all the used (ie: not empty)
563     * keys and values, including optional queries. This allows simple printing of
564     * links without needing to know which queries to add to it. If cookies are not
565     * used, the session id will be propogated in the URL.
566     *
567     * @param  string $url                 The initial url
568     * @param  mixed  $carry_args          Additional url arguments to carry in the query,
569     *                                     or FALSE to prevent carrying queries. Can be any of the following formats:
570     *                                     -array('key1', key2', key3')  <-- to save these keys if in the form data.
571     *                                     -array('key1'=>'value', key2'='value')  <-- to set keys to default values if not present in form data.
572     *                                     -false  <-- To not carry any queries. If URL already has queries those will be retained.
573     *
574     * @param  mixed  $always_include_sid  Always add the session id, even if using_trans_sid = true. This is required when
575     *                                     URL starts with http, since PHP using_trans_sid doesn't do those and also for
576     *                                     header('Location...') redirections.
577     *
578     * @return string url with attached queries and, if not using cookies, the session id
579     */
580    function oHREF($url='', $carry_args=null, $always_include_sid=false)
581    {
582        if (!isset($this) || !is_a($this, 'App')) {
583            $this =& App::getInstance();
584        }
585
586        if (!$this->running) {
587            return false;
588        }
589   
590        // Get any provided query arguments to include in the final URL.
591        // If FALSE is a provided here, DO NOT carry the queries.
592        $do_carry_queries = true;
593        $one_time_carry_queries = array();
594        if (!is_null($carry_args)) {
595            if (is_array($carry_args) && !empty($carry_args)) {
596                foreach ($carry_args as $key=>$arg) {
597                    // Get query from appropriate source.
598                    if (false === $arg) {
599                        $do_carry_queries = false;
600                    } else if (false !== getFormData($arg, false)) {
601                        $one_time_carry_queries[$arg] = getFormData($arg); // Set arg to form data if available.
602                    } else if (!is_numeric($key) && '' != $arg) {
603                        $one_time_carry_queries[$key] = getFormData($key, $arg); // Set to arg to default if specified (overwritten by form data).
604                    }
605                }
606            } else if (false !== getFormData($carry_args, false)) {
607                $one_time_carry_queries[$carry_args] = getFormData($carry_args);
608            } else if (false === $carry_args) {
609                $do_carry_queries = false;
610            }
611        }
612       
613        // Get the first delimiter that is needed in the url.
614        $delim = preg_match('/\?/', $url) ? ini_get('arg_separator.output') : '?';
615       
616        $q = '';
617        if ($do_carry_queries) {
618            // Join the global _carry_queries and local one_time_carry_queries.
619            $query_args = urlEncodeArray(array_merge($this->_carry_queries, $one_time_carry_queries));
620            foreach ($query_args as $key=>$val) {
621                // Check value is set and value does not already exist in the url.
622                if (!preg_match('/[?&]' . preg_quote($key) . '=/', $url)) {
623                    $q .= $delim . $key . '=' . $val;
624                    $delim = ini_get('arg_separator.output');
625                }
626            }
627        }
628   
629        // Include the necessary SID if the following is true:
630        // - no cookie in http request OR cookies disabled in App
631        // - sessions are enabled
632        // - the link stays on our site
633        // - transparent SID propogation with session.use_trans_sid is not being used OR url begins with protocol (using_trans_sid has no effect here)
634        // OR
635        // - we must include the SID because we say so (it's used in a context where cookies will not be effective, ie. moving from http to https)
636        // AND
637        // - the SID is not already in the query.
638        if (
639            (
640                (
641                    (
642                        !isset($_COOKIE[session_name()]) 
643                        || !$this->getParam('session_use_cookies')
644                    ) 
645                    && $this->getParam('enable_session')
646                    && isMyDomain($url) 
647                    && 
648                    (
649                        !ini_get('session.use_trans_sid')
650                        || preg_match('!^(http|https)://!i', $url)
651                    )
652                ) 
653                || $always_include_sid
654            )
655            && !preg_match('/[?&]' . preg_quote(session_name()) . '=/', $url)
656        ) {
657            $url .= $q . $delim . session_name() . '=' . session_id();
658            return $url;
659        } else {
660            $url .= $q;
661            return $url;
662        }
663    }
664   
665    /**
666     * Prints a hidden form element with the PHPSESSID when cookies are not used, as well
667     * as hidden form elements for GET_VARS that might be in use.
668     *
669     * @param  mixed  $carry_args        Additional url arguments to carry in the query,
670     *                                   or FALSE to prevent carrying queries. Can be any of the following formats:
671     *                                   -array('key1', key2', key3')  <-- to save these keys if in the form data.
672     *                                   -array('key1'=>'value', key2'='value')  <-- to set keys to default values if not present in form data.
673     *                                   -false  <-- To not carry any queries. If URL already has queries those will be retained.
674     */
675    function printHiddenSession($carry_args=null)
676    {   
677        if (!isset($this) || !is_a($this, 'App')) {
678            $this =& App::getInstance();
679        }
680
681        if (!$this->running) {
682            return false;
683        }
684   
685        // Get any provided query arguments to include in the final hidden form data.
686        // If FALSE is a provided here, DO NOT carry the queries.
687        $do_carry_queries = true;
688        $one_time_carry_queries = array();
689        if (!is_null($carry_args)) {
690            if (is_array($carry_args) && !empty($carry_args)) {
691                foreach ($carry_args as $key=>$arg) {
692                    // Get query from appropriate source.
693                    if (false === $arg) {
694                        $do_carry_queries = false;
695                    } else if (false !== getFormData($arg, false)) {
696                        $one_time_carry_queries[$arg] = getFormData($arg); // Set arg to form data if available.
697                    } else if (!is_numeric($key) && '' != $arg) {
698                        $one_time_carry_queries[$key] = getFormData($key, $arg); // Set to arg to default if specified (overwritten by form data).
699                    }
700                }
701            } else if (false !== getFormData($carry_args, false)) {
702                $one_time_carry_queries[$carry_args] = getFormData($carry_args);
703            } else if (false === $carry_args) {
704                $do_carry_queries = false;
705            }
706        }
707       
708        // For each existing POST value, we create a hidden input to carry it through a form.
709        if ($do_carry_queries) {
710            // Join the global _carry_queries and local one_time_carry_queries.
711            // urlencode is not used here, not for form data!
712            $query_args = array_merge($this->_carry_queries, $one_time_carry_queries);
713            foreach ($query_args as $key=>$val) {
714                echo '<input type="hidden" name="' . $key . '" value="' . $val . '" />';
715            }
716        }
717       
718        // Include the SID if cookies are disabled.
719        if (!isset($_COOKIE[session_name()]) && !ini_get('session.use_trans_sid')) {
720            echo '<input type="hidden" name="' . session_name() . '" value="' . session_id() . '" />';
721        }
722    }
723   
724    /**
725     * Uses an http header to redirect the client to the given $url. If sessions are not used
726     * and the session is not already defined in the given $url, the SID is appended as a URI query.
727     * As with all header generating functions, make sure this is called before any other output.
728     *
729     * @param   string  $url                    The URL the client will be redirected to.
730     * @param   mixed   $carry_args             Additional url arguments to carry in the query,
731     *                                          or FALSE to prevent carrying queries. Can be any of the following formats:
732     *                                          -array('key1', key2', key3')  <-- to save these keys if in the form data.
733     *                                          -array('key1'=>'value', key2'='value')  <-- to set keys to default values if not present in form data.
734     *                                          -false  <-- To not carry any queries. If URL already has queries those will be retained.
735     * @param   bool    $always_include_sid     Force session id to be added to Location header.
736     */
737    function dieURL($url, $carry_args=null, $always_include_sid=false)
738    {
739        if (!isset($this) || !is_a($this, 'App')) {
740            $this =& App::getInstance();
741        }
742
743        if (!$this->running) {
744            return false;
745        }
746       
747        if ('' == $url) {
748            // If URL is not specified, use the redirect_home_url.
749            $url = $this->getParam('redirect_home_url');
750        }
751   
752        if (preg_match('!^/!', $url)) {
753            // If relative URL is given, prepend correct local hostname.
754            $my_url = parse_url($_SERVER['SCRIPT_URI']);
755            $url = sprintf('%s://%s%s', $my_url['scheme'], $my_url['host'], $url);
756        }
757   
758        $url = $this->oHREF($url, $carry_args, $always_include_sid);
759       
760        header(sprintf('Location: %s', $url));
761        $this->logMsg(sprintf('dieURL: %s', $url), LOG_DEBUG, __FILE__, __LINE__);
762       
763        // End this application.
764        // Recommended, although I'm not sure it's necessary: http://cn2.php.net/session_write_close
765        $this->stop();
766        die;
767    }
768   
769    /**
770     * Redirects a user by calling the App::dieURL(). It will use:
771     * 1. the stored boomerang URL, it it exists
772     * 2. the referring URL, it it exists.
773     * 3. an empty string, which will force App::dieURL to use the default URL.
774     */
775    function dieBoomerangURL($id=null, $carry_args=null)
776    {
777        if (!isset($this) || !is_a($this, 'App')) {
778            $this =& App::getInstance();
779        }
780
781        if (!$this->running) {
782            return false;
783        }
784       
785        // Get URL from stored boomerang. Allow non specific URL if ID not valid.
786        if ($this->validBoomerangURL($id, true)) {
787            if (isset($id) && isset($_SESSION[$this->app]['boomerang']['url'][$id])) {
788                $url = $_SESSION[$this->app]['boomerang']['url'][$id];
789            } else {
790                $url = end($_SESSION[$this->app]['boomerang']['url']);
791            }
792        } else if (!refererIsMe() && !preg_match('/admin_common/', getenv('SCRIPT_NAME'))) {
793            // Ensure that the redirecting page is not also the referrer.
794            // admin_common is an alias of 'admin', which confuses this function. Just here for local testing.
795            $url = getenv('HTTP_REFERER');
796        } else {
797            $url = '';
798        }
799   
800        $this->logMsg(sprintf('dieBoomerangURL: %s', $url), LOG_DEBUG, __FILE__, __LINE__);
801   
802        // Delete stored boomerang.
803        $this->deleteBoomerangURL($id);
804           
805        // A redirection will never happen immediatly twice.
806        // Set the time so ensure this doesn't happen.
807        $_SESSION[$this->app]['boomerang']['time'] = time();
808        $this->dieURL($url, $carry_args);
809    }
810   
811    /**
812     * Set the URL to return to when App::dieBoomerangURL() is called.
813     *
814     * @param string  $url  A fully validated URL.
815     * @param bool  $id     An identification tag for this url.
816     * FIXME: url garbage collection?
817     */
818    function setBoomerangURL($url=null, $id=null)
819    {
820        if (!isset($this) || !is_a($this, 'App')) {
821            $this =& App::getInstance();
822        }
823
824        if (!$this->running) {
825            return false;
826        }
827       
828        // A redirection will never happen immediatly after setting the boomerangURL.
829        // Set the time so ensure this doesn't happen. See App::validBoomerangURL for more.
830   
831        if (isset($url) && is_string($url)) {
832            // Delete any boomerang request keys in the query string.
833            $url = preg_replace('/boomerang=[\w]+/', '', $url);
834           
835            if (is_array($_SESSION[$this->app]['boomerang']['url']) && !empty($_SESSION[$this->app]['boomerang']['url'])) {
836                // If the URL currently exists in the boomerang array, delete.
837                while ($existing_key = array_search($url, $_SESSION[$this->app]['boomerang']['url'])) {
838                    unset($_SESSION[$this->app]['boomerang']['url'][$existing_key]);
839                }
840            }
841           
842            if (isset($id)) {
843                $_SESSION[$this->app]['boomerang']['url'][$id] = $url;
844            } else {
845                $_SESSION[$this->app]['boomerang']['url'][] = $url;
846            }
847            $this->logMsg(sprintf('setBoomerangURL: %s', $url), LOG_DEBUG, __FILE__, __LINE__);
848            return true;
849        } else {
850            return false;
851        }
852    }
853   
854    /**
855     * Return the URL set for the specified $id.
856     *
857     * @param string  $id     An identification tag for this url.
858     */
859    function getBoomerangURL($id=null)
860    {
861        if (!isset($this) || !is_a($this, 'App')) {
862            $this =& App::getInstance();
863        }
864
865        if (!$this->running) {
866            return false;
867        }
868       
869        if (isset($id)) {
870            if (isset($_SESSION[$this->app]['boomerang']['url'][$id])) {
871                return $_SESSION[$this->app]['boomerang']['url'][$id];
872            } else {
873                return '';
874            }
875        } else if (is_array($_SESSION[$this->app]['boomerang']['url'])) {
876            return end($_SESSION[$this->app]['boomerang']['url']);
877        } else {
878            return false;
879        }
880    }
881   
882    /**
883     * Delete the URL set for the specified $id.
884     *
885     * @param string  $id     An identification tag for this url.
886     */
887    function deleteBoomerangURL($id=null)
888    {
889        if (!isset($this) || !is_a($this, 'App')) {
890            $this =& App::getInstance();
891        }
892
893        if (!$this->running) {
894            return false;
895        }
896       
897        if (isset($id) && isset($_SESSION[$this->app]['boomerang']['url'][$id])) {
898            unset($_SESSION[$this->app]['boomerang']['url'][$id]);
899        } else if (is_array($_SESSION[$this->app]['boomerang']['url'])) {
900            array_pop($_SESSION[$this->app]['boomerang']['url']);
901        }
902    }
903   
904    /**
905     * Check if a valid boomerang URL value has been set.
906     * if it is not the current url, and has not been accessed within n seconds.
907     *
908     * @return bool  True if it is set and not the current URL.
909     */
910    function validBoomerangURL($id=null, $use_nonspecificboomerang=false)
911    {
912        if (!isset($this) || !is_a($this, 'App')) {
913            $this =& App::getInstance();
914        }
915
916        if (!$this->running) {
917            return false;
918        }
919       
920        if (!isset($_SESSION[$this->app]['boomerang']['url'])) {
921            return false;
922        }
923   
924        // Time is the timestamp of a boomerangURL redirection, or setting of a boomerangURL.
925        // a boomerang redirection will always occur at least several seconds after the last boomerang redirect
926        // or a boomerang being set.
927        $boomerang_time = isset($_SESSION[$this->app]['boomerang']['time']) ? $_SESSION[$this->app]['boomerang']['time'] : 0;
928       
929        if (isset($id) && isset($_SESSION[$this->app]['boomerang']['url'][$id])) {
930            $url = $_SESSION[$this->app]['boomerang']['url'][$id];
931        } else if (!isset($id) || $use_nonspecificboomerang) {
932            // Use non specific boomerang if available.
933            $url = end($_SESSION[$this->app]['boomerang']['url']);
934        }
935   
936        $this->logMsg(sprintf('validBoomerangURL testing url: %s', $url), LOG_DEBUG, __FILE__, __LINE__);
937        if (empty($url)) {
938            return false;
939        }
940        if ($url == absoluteMe()) {
941            // The URL we are directing to is the current page.
942            $this->logMsg(sprintf('Boomerang URL not valid, same as absoluteMe: %s', $url), LOG_WARNING, __FILE__, __LINE__);
943            return false;
944        }
945        if ($boomerang_time >= (time() - 2)) {
946            // Last boomerang direction was more than 2 seconds ago.
947            $this->logMsg(sprintf('Boomerang URL not valid, boomerang_time too short: %s', time() - $boomerang_time), LOG_WARNING, __FILE__, __LINE__);
948            return false;
949        }
950       
951        $this->logMsg(sprintf('validBoomerangURL found: %s', $url), LOG_DEBUG, __FILE__, __LINE__);
952        return true;
953    }
954
955    /**
956     * Force the user to connect via https (port 443) by redirecting them to
957     * the same page but with https.
958     */
959    function sslOn()
960    {
961        if (!isset($this) || !is_a($this, 'App')) {
962            $this =& App::getInstance();
963        }
964       
965        if ('on' != getenv('HTTPS') && $this->getParam('ssl_enabled') && preg_match('/mod_ssl/i', getenv('SERVER_SOFTWARE'))) {
966            $this->raiseMsg(sprintf(_("Secure SSL connection made to %s"), $this->getParam('ssl_domain')), MSG_NOTICE, __FILE__, __LINE__);
967            // Always append session because some browsers do not send cookie when crossing to SSL URL.
968            $this->dieURL('https://' . $this->getParam('ssl_domain') . getenv('REQUEST_URI'), null, true);
969        }
970    }
971       
972   
973    /**
974     * to enforce the user to connect via http (port 80) by redirecting them to
975     * a http version of the current url.
976     */
977    function sslOff()
978    {
979        if ('on' == getenv('HTTPS')) {
980            $this->dieURL('http://' . getenv('HTTP_HOST') . getenv('REQUEST_URI'), null, true);
981        }
982    }
983
984   
985} // End.
986
987?>
Note: See TracBrowser for help on using the repository browser.