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

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

Added Email() class to work with verification, and sending of emails and templates. Updated Formvalidator to use the regex in Email().

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