├── index.php ├── .DS_Store ├── logout.php ├── includes ├── class.objects.php ├── class.tag.php ├── class.pagepref.php ├── class.dbpager.php ├── master.inc.php ├── class.loop.php ├── class.dbsession.php ├── class.dbloop.php ├── class.urlcache.php ├── class.pager.php ├── class.config-sample.php ├── class.stats.php ├── class.rss.php ├── class.gd.php ├── class.dbobject.php ├── class.database.php ├── class.spferror.php ├── class.auth.php └── functions.inc.php ├── .htaccess ├── mysql.sql ├── LICENSE ├── login.php └── README.markdown /index.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylerhall/simple-php-framework/HEAD/.DS_Store -------------------------------------------------------------------------------- /logout.php: -------------------------------------------------------------------------------- 1 | logout(); 6 | redirect('index.php'); -------------------------------------------------------------------------------- /includes/class.objects.php: -------------------------------------------------------------------------------- 1 | select($id, 'name'); 8 | if(!$this->ok()) 9 | { 10 | $this->name = $id; 11 | $this->insert(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | # This turns on mod_rewrite and redirects any paths that don't physically exist 2 | # to /index.php. You can then access that path info (to determine what to do) in 3 | # PHP using $_SERVER['REQUEST_URI'] and $_SERVER['REDIRECT_URL']. This is an easy 4 | # way to avoid having hundreds of rewrite rules slowing down Apache and making things 5 | # more complicated than they should be. 6 | 7 | # Note: If you're having trouble making Apache pickup your .htaccess file, 8 | # make sure AllowOverride is set to "All" instead of "None". 9 | 10 | # RewriteEngine On 11 | # RewriteBase / 12 | 13 | # RewriteCond %{REQUEST_FILENAME} !-f # If not a file... 14 | # RewriteCond %{REQUEST_FILENAME} !-d # and not a directory... 15 | # RewriteRule . /index.php [L] # serve index.php -------------------------------------------------------------------------------- /includes/class.pagepref.php: -------------------------------------------------------------------------------- 1 | _id = 'pp' . sha1($_SERVER['PHP_SELF']); 11 | 12 | if(isset($_SESSION[$this->_id])) 13 | $this->_data = unserialize($_SESSION[$this->_id]); 14 | } 15 | 16 | public function __get($key) 17 | { 18 | return $this->_data[$key]; 19 | } 20 | 21 | public function __set($key, $val) 22 | { 23 | if(!is_array($this->_data)) $this->_data = array(); 24 | $this->_data[$key] = $val; 25 | $_SESSION[$this->_id] = serialize($this->_data); 26 | } 27 | 28 | public function clear() 29 | { 30 | unset($_SESSION[$this->_id]); 31 | unset($this->_data); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /includes/class.dbpager.php: -------------------------------------------------------------------------------- 1 | itemClass = $itemClass; 11 | $this->countSql = $countSql; 12 | $this->pageSql = $pageSql; 13 | 14 | $db = Database::getDatabase(); 15 | $num_records = intval($db->getValue($countSql)); 16 | 17 | parent::__construct($page, $per_page, $num_records); 18 | } 19 | 20 | public function calculate() 21 | { 22 | parent::calculate(); 23 | // load records .. see $this->firstRecord, $this->perPage 24 | $limitSql = sprintf(' LIMIT %s,%s', $this->firstRecord, $this->perPage); 25 | $this->records = array_values(DBObject::glob($this->itemClass, $this->pageSql . $limitSql)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mysql.sql: -------------------------------------------------------------------------------- 1 | # Dump of table sessions 2 | # ------------------------------------------------------------ 3 | 4 | CREATE TABLE `sessions` ( 5 | `id` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '', 6 | `data` text COLLATE utf8_unicode_ci NOT NULL, 7 | `updated_on` int(10) NOT NULL DEFAULT '0', 8 | PRIMARY KEY (`id`) 9 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 10 | 11 | 12 | 13 | # Dump of table users 14 | # ------------------------------------------------------------ 15 | 16 | CREATE TABLE `users` ( 17 | `id` int(11) NOT NULL AUTO_INCREMENT, 18 | `nid` varchar(40) COLLATE utf8_unicode_ci DEFAULT NULL, 19 | `username` varchar(65) COLLATE utf8_unicode_ci NOT NULL DEFAULT '', 20 | `password` varchar(65) COLLATE utf8_unicode_ci NOT NULL DEFAULT '', 21 | `level` enum('user','admin') COLLATE utf8_unicode_ci NOT NULL DEFAULT 'user', 22 | `twostep` varchar(6) COLLATE utf8_unicode_ci DEFAULT NULL, 23 | PRIMARY KEY (`id`), 24 | UNIQUE KEY `username` (`username`) 25 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 26 | -------------------------------------------------------------------------------- /includes/master.inc.php: -------------------------------------------------------------------------------- 1 | index = 0; 11 | $this->elements = func_get_args(); 12 | $this->numElements = func_num_args(); 13 | } 14 | 15 | public function __tostring() 16 | { 17 | return (string) $this->get(); 18 | } 19 | 20 | public function get() 21 | { 22 | if($this->numElements == 0) return null; 23 | 24 | $val = $this->elements[$this->index]; 25 | 26 | if(++$this->index >= $this->numElements) 27 | $this->index = 0; 28 | 29 | return $val; 30 | } 31 | 32 | public function rand() 33 | { 34 | return $this->elements[array_rand($this->elements)]; 35 | } 36 | } 37 | 38 | // Example: 39 | // $color = new Loop('white', 'black'); 40 | // 41 | // echo ""; 42 | // echo ""; 43 | // echo ""; 44 | // 45 | // Or 46 | // 47 | // while($row = mysql_fetch_array($result)) 48 | // echo "the row colors will alternate"; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | * Copyright (c) 2006 - 2019, Simple PHP Framework 2 | * 3 | * http://github.com/tylerhall/simple-php-framework/ 4 | * 5 | * This software is released under the MIT License 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 8 | * documentation files (the "Software"), to deal in the Software without restriction, including without 9 | * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 10 | * Software, and to permit persons to whom the Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions 14 | * of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 19 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /includes/class.dbsession.php: -------------------------------------------------------------------------------- 1 | isConnected(); 13 | } 14 | 15 | public static function close() 16 | { 17 | return true; 18 | } 19 | 20 | public static function read($id) 21 | { 22 | $db = Database::getDatabase(true); 23 | $db->query('SELECT `data` FROM `sessions` WHERE `id` = :id', array('id' => $id)); 24 | return $db->hasRows() ? $db->getValue() : ''; 25 | } 26 | 27 | public static function write($id, $data) 28 | { 29 | $db = Database::getDatabase(true); 30 | $db->query('DELETE FROM `sessions` WHERE `id` = :id', array('id' => $id)); 31 | $db->query('INSERT INTO `sessions` (`id`, `data`, `updated_on`) VALUES (:id, :data, :updated_on)', array('id' => $id, 'data' => $data, 'updated_on' => time())); 32 | return ($db->affectedRows() == 1); 33 | } 34 | 35 | public static function destroy($id) 36 | { 37 | $db = Database::getDatabase(true); 38 | $db->query('DELETE FROM `sessions` WHERE `id` = :id', array('id' => $id)); 39 | return ($db->affectedRows() == 1); 40 | } 41 | 42 | public static function gc($max) 43 | { 44 | $db = Database::getDatabase(true); 45 | $db->query('DELETE FROM `sessions` WHERE `updated_on` < :updated_on', array('updated_on' => time() - $max)); 46 | return true; 47 | } 48 | } -------------------------------------------------------------------------------- /login.php: -------------------------------------------------------------------------------- 1 | loggedIn()) redirect('index.php'); 6 | 7 | // Try to log in... 8 | if(!empty($_POST['username'])) { 9 | if(empty($_POST['PIN'])) { 10 | $Auth->sendTwoStep($_POST['username']); 11 | } else { 12 | $Auth->login($_POST['username'], $_POST['password'], $_POST['PIN']); 13 | if($Auth->loggedIn()) { 14 | redirect('index.php'); 15 | } 16 | } 17 | } 18 | 19 | // Clean the submitted username before redisplaying it. 20 | $username = isset($_POST['username']) ? htmlspecialchars($_POST['username']) : ''; 21 | ?> 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | SPF Sample Login Page 30 | 31 | 32 | 33 |
34 |
35 |
36 |

Login

37 |
38 |
39 | 40 | 41 |
42 |
43 | 44 | 45 |
46 |
47 | 48 | 49 |
50 | 51 | 52 |
53 |
54 |
55 |
56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /includes/class.dbloop.php: -------------------------------------------------------------------------------- 1 | position = 0; 12 | $this->className = $class_name; 13 | $this->extraColumns = $extra_columns; 14 | 15 | // Make sure the class exists before we instantiate it... 16 | if(!class_exists($class_name)) 17 | return; 18 | 19 | $tmp_obj = new $class_name; 20 | 21 | // Also, it needs to be a subclass of DBObject... 22 | if(!is_subclass_of($tmp_obj, 'DBObject')) 23 | return; 24 | 25 | if(is_null($sql)) 26 | $sql = "SELECT * FROM `{$tmp_obj->tableName}`"; 27 | 28 | $db = Database::getDatabase(); 29 | $this->result = $db->query($sql); 30 | } 31 | 32 | public function rewind() 33 | { 34 | $this->position = 0; 35 | } 36 | 37 | public function current() 38 | { 39 | mysqli_data_seek($this->result, $this->position); 40 | $row = mysqli_fetch_array($this->result, MYSQLI_ASSOC); 41 | if($row === false) 42 | return false; 43 | 44 | $o = new $this->className; 45 | $o->load($row); 46 | 47 | foreach($this->extraColumns as $c) 48 | { 49 | $o->addColumn($c); 50 | $o->$c = isset($row[$c]) ? $row[$c] : null; 51 | } 52 | 53 | return $o; 54 | } 55 | 56 | public function key() 57 | { 58 | return $this->position; 59 | } 60 | 61 | public function next() 62 | { 63 | $this->position++; 64 | } 65 | 66 | public function valid() 67 | { 68 | if($this->position < mysqli_num_rows($this->result)) 69 | return mysqli_data_seek($this->result, $this->position); 70 | else 71 | return false; 72 | } 73 | 74 | public function count() 75 | { 76 | return mysqli_num_rows($this->result); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /includes/class.urlcache.php: -------------------------------------------------------------------------------- 1 | query("SELECT * FROM url_cache WHERE url = :url LIMIT 1", array('url' => $url)); 21 | $row = $db->getRow(); 22 | 23 | if($row === false) 24 | { 25 | return self::refreshContent($url, $expires_in); 26 | } 27 | elseif(strtotime($row['dt_expires']) < time()) 28 | { 29 | $data = self::refreshContent($url, $expires_in); 30 | return ($data === false) ? $row['data'] : $data; 31 | } 32 | else 33 | { 34 | return $row['data']; 35 | } 36 | } 37 | 38 | public static function refreshContent($url, $expires_in = 300) 39 | { 40 | $str = self::getURL($url); 41 | $data = self::decodeStrData($str); 42 | if($data === false) return false; 43 | 44 | $db = Database::getDatabase(); 45 | $db->query("REPLACE INTO url_cache (url, dt_refreshed, dt_expires, data) VALUES (:url, :dt_refreshed, :dt_expires, :data)", 46 | array('url' => $url, 47 | 'dt_refreshed' => dater_utc(), 48 | 'dt_expires' => dater_utc(time() + $expires_in), 49 | 'data' => $str)); 50 | return $str; 51 | } 52 | 53 | private static function getURL($url) 54 | { 55 | $ch = curl_init(); 56 | curl_setopt($ch, CURLOPT_URL, $url); 57 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 58 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); 59 | // curl_setopt($ch, CURLOPT_VERBOSE, 1); 60 | $data = curl_exec($ch); 61 | curl_close($ch); 62 | return $data; 63 | } 64 | } 65 | 66 | class XMLCache extends URLCache 67 | { 68 | public static function decodeStrData($str) 69 | { 70 | return simplexml_load_string($str); 71 | } 72 | } 73 | 74 | class JSONCache extends URLCache 75 | { 76 | public static function decodeStrData($str) 77 | { 78 | return json_decode($str); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | [The Simple PHP Framework](http://github.com/tylerhall/simple-php-framework/) is a pragmatic approach to building websites with PHP 7.2+. It's geared towards web design shops and freelance programmers looking for a common foundation to quickly bring web projects to life. Without getting too technical, SPF follows the [no-framework Framework](http://toys.lerdorf.com/archives/38-The-no-framework-PHP-MVC-framework.html) method coined by Rasmus Lerdorf -- with a little [Active Record](http://en.wikipedia.org/wiki/Active_record_pattern) thrown in for good measure. 2 | 3 | ### Project History ### 4 | 5 | This framework is the foundation that (almost) all of my websites are built with. I've been using this code base (or some form of it) since 2006 (yes, it's that old, but still works great) across hundreds of different projects - both personal and professional. It's served me well for the smallest of projects up to sites receiving millions of visitors per month. Less framework and more foundation, it provides a quick starting point and does a lot of the grunt work — user authentication, database calls, object lifecycle management, etc. It's exactly enough to get your project bootstrapped and moving forward quickly. 6 | 7 | This framework wasn't built overnight or even on purpose. It's really a development pattern and collection of (useful) classes that have evolved naturally over the last thirteen years or so. I've tried to walk a fine line and not add unnecessary features that most people won't use. I've done my best to keep it as minimal as possible yet still allow plenty of flexibility. 8 | 9 | The Simple PHP Framework is designed to _help_ you build websites — not build them for you. There are plenty out there that already try to do that. 10 | 11 | > All the web frameworks in the world won't turn a shitty programmer into a good one." — [uncov](https://web.archive.org/web/20070510011917/http://www.uncov.com/2007/5/4/contactify-the-hello-world-of-web-2-0) 12 | 13 | A branch of the framework was forked internally at Yahoo! in 2008. Improvements from that branch made their way back into the main trunk as appropriate. 14 | 15 | ### Download the Code ### 16 | 17 | The Simple PHP Framework is hosted on [GitHub](http://github.com/tylerhall/simple-php-framework/) 18 | and licensed under the [MIT Open Source License](http://www.opensource.org/licenses/mit-license.php). 19 | 20 | ### Documentation and Examples ### 21 | 22 | As is the tradition with most open source software, the code is self-documenting — which is a nice way of saying I'm too lazy to write any formal documentation myself. That said, I'm always happy to answer questions about the code. You're also welcome to join our [discussion group](http://groups.google.com/group/simple-php-framework). There's not much activity, but if you ask a question you'll typically get an answer back quickly. 23 | 24 | If you'd like to see a full website built using the framework, take a look at [Shine](https://github.com/tylerhall/Shine). It's a good, (mostly) clean example of how to use the framework. 25 | -------------------------------------------------------------------------------- /includes/class.pager.php: -------------------------------------------------------------------------------- 1 | page = $page; 20 | $this->perPage = $per_page; 21 | $this->numRecords = $num_records; 22 | $this->calculate(); 23 | } 24 | 25 | // Do the math. 26 | // Note: Pager always calculates there to be *at least* 1 page. Even if there are 0 records, we still, 27 | // by convention, assume it takes 1 page to display those 0 records. While mathematically stupid, it 28 | // makes sense from a UI perspective. 29 | public function calculate() 30 | { 31 | $this->numPages = ceil($this->numRecords / $this->perPage); 32 | if($this->numPages == 0) $this->numPages = 1; 33 | 34 | $this->page = intval($this->page); 35 | if($this->page < 1) $this->page = 1; 36 | if($this->page > $this->numPages) $this->page = $this->numPages; 37 | 38 | $this->firstRecord = (int) ($this->page - 1) * $this->perPage; 39 | $this->lastRecord = (int) $this->firstRecord + $this->perPage - 1; 40 | if($this->lastRecord >= $this->numRecords) $this->lastRecord = $this->numRecords - 1; 41 | 42 | $this->records = range($this->firstRecord, $this->lastRecord, 1); 43 | } 44 | 45 | // Will return current page if no previous page exists 46 | public function prevPage() 47 | { 48 | return max(1, $this->page - 1); 49 | } 50 | 51 | // Will return current page if no next page exists 52 | public function nextPage() 53 | { 54 | return min($this->numPages, $this->page + 1); 55 | } 56 | 57 | // Is there a valid previous page? 58 | public function hasPrevPage() 59 | { 60 | return $this->page > 1; 61 | } 62 | 63 | // Is there a valid next page? 64 | public function hasNextPage() 65 | { 66 | return $this->page < $this->numPages; 67 | } 68 | 69 | public function rewind() 70 | { 71 | reset($this->records); 72 | } 73 | 74 | public function current() 75 | { 76 | return current($this->records); 77 | } 78 | 79 | public function key() 80 | { 81 | return key($this->records); 82 | } 83 | 84 | public function next() 85 | { 86 | return next($this->records); 87 | } 88 | 89 | public function valid() 90 | { 91 | return $this->current() !== false; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /includes/class.config-sample.php: -------------------------------------------------------------------------------- 1 | everywhere(); 42 | 43 | $i_am_here = $this->whereAmI(); 44 | 45 | if('production' == $i_am_here) 46 | $this->production(); 47 | elseif('staging' == $i_am_here) 48 | $this->staging(); 49 | elseif('local' == $i_am_here) 50 | $this->local(); 51 | elseif('shell' == $i_am_here) 52 | $this->shell(); 53 | else 54 | die('

Where am I?

You need to setup your server names in class.config.php

55 |

$_SERVER[\'HTTP_HOST\'] reported ' . $_SERVER['HTTP_HOST'] . '

'); 56 | } 57 | 58 | // Get Singleton object 59 | public static function getConfig() 60 | { 61 | if(is_null(self::$me)) { 62 | self::$me = new Config(); 63 | } 64 | return self::$me; 65 | } 66 | 67 | // Allow access to config settings statically. 68 | // Ex: Config::get('some_value') 69 | public static function get($key) 70 | { 71 | return self::$me->$key; 72 | } 73 | 74 | // Add code to be run on all servers 75 | private function everywhere() 76 | { 77 | // Store sesions in the database? 78 | $this->useDBSessions = true; 79 | 80 | // Settings for the Auth class 81 | $this->authDomain = $_SERVER['HTTP_HOST']; 82 | $this->useHashedPasswords = true; 83 | $this->authSalt = 'Pick any random string of characters'; 84 | } 85 | 86 | // Add code/variables to be run only on production servers 87 | private function production() 88 | { 89 | ini_set('display_errors', '0'); 90 | ini_set('error_reporting', 0); // disable 91 | 92 | define('WEB_ROOT', '/'); 93 | 94 | $this->dbHost = ''; 95 | $this->dbName = ''; 96 | $this->dbUsername = ''; 97 | $this->dbPassword = ''; 98 | $this->dbDieOnError = false; 99 | 100 | $this->useTwoStepAuth = false; 101 | } 102 | 103 | // Add code/variables to be run only on staging servers 104 | private function staging() 105 | { 106 | ini_set('display_errors', '1'); 107 | ini_set('error_reporting', E_ALL); 108 | 109 | define('WEB_ROOT', ''); 110 | 111 | $this->dbHost = ''; 112 | $this->dbName = ''; 113 | $this->dbUsername = ''; 114 | $this->dbPassword = ''; 115 | $this->dbDieOnError = false; 116 | } 117 | 118 | // Add code/variables to be run only on local (testing) servers 119 | private function local() 120 | { 121 | ini_set('display_errors', '1'); 122 | ini_set('error_reporting', E_ALL); 123 | 124 | define('WEB_ROOT', '/'); 125 | 126 | $this->dbHost = ''; 127 | $this->dbName = ''; 128 | $this->dbUsername = ''; 129 | $this->dbPassword = ''; 130 | $this->dbDieOnError = true; 131 | } 132 | 133 | // Add code/variables to be run only on when script is launched from the shell 134 | private function shell() 135 | { 136 | ini_set('display_errors', '1'); 137 | ini_set('error_reporting', E_ALL); 138 | 139 | define('WEB_ROOT', ''); 140 | 141 | $this->dbHost = ''; 142 | $this->dbName = ''; 143 | $this->dbUsername = ''; 144 | $this->dbPassword = ''; 145 | $this->dbDieOnError = true; 146 | } 147 | 148 | public function whereAmI() 149 | { 150 | if(in_array($_SERVER['HTTP_HOST'], $this->productionServers)) 151 | return 'production'; 152 | elseif(in_array($_SERVER['HTTP_HOST'], $this->stagingServers)) 153 | return 'staging'; 154 | elseif(in_array($_SERVER['HTTP_HOST'], $this->localServers)) 155 | return 'local'; 156 | elseif(isset($_ENV['SHELL'])) 157 | return 'shell'; 158 | else 159 | return false; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /includes/class.stats.php: -------------------------------------------------------------------------------- 1 | numQueries(); 41 | 42 | $sql = "INSERT INTO stats (dt, referer, referer_is_local, url, page_title, search_terms, img_search, browser_family, browser_version, os, os_version, ip, user_agent, exec_time, num_queries) 43 | VALUES (:dt, :referer, :referer_is_local, :url, :page_title, :search_terms, :img_search, :browser_family, :browser_version, :os, :os_version, :ip, :user_agent, :exec_time, :num_queries)"; 44 | $vals = array('dt' => $dt, 45 | 'referer_is_local' => $referer_is_local, 46 | 'referer' => $referer, 47 | 'url' => $url, 48 | 'page_title' => $page_title, 49 | 'search_terms' => $search_terms, 50 | 'img_search' => $img_search, 51 | 'ip' => $ip, 52 | 'browser_family' => $browser_family, 53 | 'browser_version' => $browser_version, 54 | 'os_version' => $os_version, 55 | 'os' => $os, 56 | 'user_agent' => $user_agent, 57 | 'exec_time' => $exec_time, 58 | 'num_queries' => $num_queries); 59 | $db->query($sql, $vals); 60 | } 61 | 62 | public static function refererIsLocal($referer = null) 63 | { 64 | if(is_null($referer)) $referer = getenv('HTTP_REFERER'); 65 | if(!strlen($referer)) return 0; 66 | $regex_host = preg_quote(getenv('HTTP_HOST')); 67 | return (preg_match("!^https?://$regex_host!i", $referer) !== false) ? 1 : 0; 68 | } 69 | 70 | public static function getIP() 71 | { 72 | $ip = getenv('HTTP_X_FORWARDED_FOR'); 73 | if(!$ip) $ip = getenv('HTTP_CLIENT_IP'); 74 | if(!$ip) $ip = getenv('REMOTE_ADDR'); 75 | return $ip; 76 | } 77 | 78 | public static function searchTerms($url = null) 79 | { 80 | if(is_null($url)) $url = full_url(); 81 | // if(self::refererIsLocal($url)) return; 82 | 83 | $arr = array(); 84 | parse_str(parse_url($url, PHP_URL_QUERY), $arr); 85 | 86 | return isset($arr['q']) ? $arr['q'] : ''; 87 | } 88 | 89 | // From http://us3.php.net/get_browser comments 90 | public static function browserInfo($a_browser = false, $a_version = false, $name = false) 91 | { 92 | $browser_list = 'msie firefox konqueror chrome safari netscape navigator opera mosaic lynx amaya omniweb avant camino flock seamonkey aol mozilla gecko'; 93 | $user_browser = strtolower(getenv('HTTP_USER_AGENT')); 94 | $this_version = $this_browser = ''; 95 | 96 | $browser_limit = strlen($user_browser); 97 | foreach(explode(' ', $browser_list) as $row) 98 | { 99 | $row = ($a_browser !== false) ? $a_browser : $row; 100 | $n = stristr($user_browser, $row); 101 | if(!$n || !empty($this_browser)) continue; 102 | 103 | $this_browser = $row; 104 | $j = strpos($user_browser, $row) + strlen($row) + 1; 105 | for(; $j <= $browser_limit; $j++) 106 | { 107 | $s = trim(substr($user_browser, $j, 1)); 108 | $this_version .= $s; 109 | 110 | if($s === '') break; 111 | } 112 | } 113 | 114 | if($a_browser !== false) 115 | { 116 | $ret = false; 117 | if(strtolower($a_browser) == $this_browser) 118 | { 119 | $ret = true; 120 | 121 | if($a_version !== false && !empty($this_version)) 122 | { 123 | $a_sign = explode(' ', $a_version); 124 | if(version_compare($this_version, $a_sign[1], $a_sign[0]) === false) 125 | { 126 | $ret = false; 127 | } 128 | } 129 | } 130 | 131 | return $ret; 132 | } 133 | 134 | $this_platform = ''; 135 | if(strpos($user_browser, 'linux')) 136 | { 137 | $this_platform = 'linux'; 138 | } 139 | elseif(strpos($user_browser, 'macintosh') || strpos($user_browser, 'mac platform x')) 140 | { 141 | $this_platform = 'mac'; 142 | } 143 | elseif(strpos($user_browser, 'windows') || strpos($user_browser, 'win32')) 144 | { 145 | $this_platform = 'windows'; 146 | } 147 | 148 | if($name !== false) 149 | { 150 | return $this_browser . ' ' . $this_version; 151 | } 152 | 153 | return array("browser" => $this_browser, 154 | "version" => $this_version, 155 | "platform" => $this_platform, 156 | "useragent" => $user_browser); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /includes/class.rss.php: -------------------------------------------------------------------------------- 1 | items = array(); 17 | $this->tags = array(); 18 | $this->useCDataTags = true; 19 | $this->setPubDate(); 20 | $this->url = $this->fullUrl(); 21 | } 22 | 23 | public function addItem($item) 24 | { 25 | $this->items[] = $item; 26 | } 27 | 28 | public function setPubDate($date = null) 29 | { 30 | if(is_null($date)) $date = time(); 31 | if(!ctype_digit($date)) $date = strtotime($date); 32 | $this->pubDate = date('D, d M Y H:i:s O', $date); 33 | } 34 | 35 | public function addTag($tag, $value) 36 | { 37 | $this->tags[$tag] = $value; 38 | } 39 | 40 | public function loadRecordset($result, $title, $link, $description, $pub_date) 41 | { 42 | while($row = mysql_fetch_array($result, MYSQL_ASSOC)) 43 | { 44 | $item = new RSSItem(); 45 | $item->title = $row[$title]; 46 | $item->link = $row[$link]; 47 | $item->description = $row[$description]; 48 | $item->setPubDate($row[$pub_date]); 49 | $this->addItem($item); 50 | } 51 | } 52 | 53 | public function out() 54 | { 55 | $bad = array('&', '<'); 56 | $good = array('&', '<'); 57 | $title = str_replace($bad, $good, $this->title); 58 | $description = str_replace($bad, $good, $this->description); 59 | 60 | $out = $this->header(); 61 | $out .= "\n"; 62 | $out .= "" . $title . "\n"; 63 | $out .= "" . $this->link . "\n"; 64 | $out .= "" . $description . "\n"; 65 | $out .= "" . $this->language . "\n"; 66 | $out .= "" . $this->pubDate . "\n"; 67 | $out .= '' . "\n"; 68 | 69 | foreach($this->tags as $k => $v) 70 | $out .= "<$k>$v\n"; 71 | 72 | foreach($this->items as $item) 73 | $out .= $item->out(); 74 | 75 | $out .= "\n"; 76 | 77 | $out .= $this->footer(); 78 | 79 | return $out; 80 | } 81 | 82 | public function serve($contentType = 'application/xml') 83 | { 84 | $xml = $this->out(); 85 | header("Content-type: $contentType"); 86 | echo $xml; 87 | } 88 | 89 | private function header() 90 | { 91 | $out = '' . "\n"; 92 | $out .= '' . "\n"; 93 | return $out; 94 | } 95 | 96 | private function footer() 97 | { 98 | return ''; 99 | } 100 | 101 | private function fullUrl() 102 | { 103 | $s = empty($_SERVER['HTTPS']) ? '' : ($_SERVER['HTTPS'] == 'on') ? 's' : ''; 104 | $protocol = substr(strtolower($_SERVER['SERVER_PROTOCOL']), 0, strpos(strtolower($_SERVER['SERVER_PROTOCOL']), '/')) . $s; 105 | $port = ($_SERVER['SERVER_PORT'] == '80') ? '' : (":".$_SERVER['SERVER_PORT']); 106 | return $protocol . "://" . $_SERVER['HTTP_HOST'] . $port . $_SERVER['REQUEST_URI']; 107 | } 108 | 109 | private function cdata($str) 110 | { 111 | if($this->useCDataTags) 112 | { 113 | $str = ''; 114 | } 115 | return $str; 116 | } 117 | } 118 | 119 | class RSSItem 120 | { 121 | public $title; 122 | public $link; 123 | public $description; 124 | public $pubDate; 125 | public $guid; 126 | public $tags; 127 | public $enclosureUrl; 128 | public $enclosureType; 129 | public $enclosureLength; 130 | public $useCDataTags; 131 | 132 | public function __construct() 133 | { 134 | $this->useCDataTags = true; 135 | $this->tags = array(); 136 | $this->setPubDate(); 137 | } 138 | 139 | public function setPubDate($date = null) 140 | { 141 | if(is_null($date)) $date = time(); 142 | if(!ctype_digit($date)) $date = strtotime($date); 143 | $this->pubDate = date('D, d M Y H:i:s O', $date); 144 | } 145 | 146 | public function addTag($tag, $value) 147 | { 148 | $this->tags[$tag] = $value; 149 | } 150 | 151 | public function out() 152 | { 153 | $bad = array('&', '<'); 154 | $good = array('&', '<'); 155 | $title = str_replace($bad, $good, $this->title); 156 | 157 | $out = "\n"; 158 | $out .= "" . $title . "\n"; 159 | $out .= "" . $this->link . "\n"; 160 | $out .= "" . $this->cdata($this->description) . "\n"; 161 | $out .= "" . $this->pubDate . "\n"; 162 | 163 | if(is_null($this->guid)) 164 | $this->guid = $this->link; 165 | 166 | $out .= "" . $this->guid . "\n"; 167 | 168 | if(!is_null($this->enclosureUrl)) 169 | $out .= "\n"; 170 | 171 | foreach($this->tags as $k => $v) 172 | $out .= "<$k>$v\n"; 173 | 174 | $out .= "\n"; 175 | return $out; 176 | } 177 | 178 | public function enclosure($url, $type, $length) 179 | { 180 | $this->enclosureUrl = $url; 181 | $this->enclosureType = $type; 182 | $this->enclosureLength = $length; 183 | } 184 | 185 | private function cdata($str) 186 | { 187 | if($this->useCDataTags) 188 | { 189 | $str = ''; 190 | } 191 | return $str; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /includes/class.gd.php: -------------------------------------------------------------------------------- 1 | loadResource($data); 15 | elseif(@file_exists($data) && is_readable($data)) 16 | return $this->loadFile($data); 17 | elseif(is_string($data)) 18 | return $this->loadString($data); 19 | else 20 | return false; 21 | } 22 | 23 | private function loadResource($im) 24 | { 25 | if(!is_resource($im) || !get_resource_type($im) == 'gd') return false; 26 | 27 | $this->im = $im; 28 | $this->width = imagesx($im); 29 | $this->height = imagesy($im); 30 | 31 | return true; 32 | } 33 | 34 | private function loadFile($filename) 35 | { 36 | if(!file_exists($filename) || !is_readable($filename)) return false; 37 | 38 | $info = getimagesize($filename); 39 | $this->width = $info[0]; 40 | $this->height = $info[1]; 41 | $this->type = image_type_to_extension($info[2], false); 42 | $this->mime = $info['mime']; 43 | 44 | if($this->type == 'jpeg' && (imagetypes() & IMG_JPG)) 45 | $this->im = imagecreatefromjpeg($filename); 46 | elseif($this->type == 'png' && (imagetypes() & IMG_PNG)) 47 | $this->im = imagecreatefrompng($filename); 48 | elseif($this->type == 'gif' && (imagetypes() & IMG_GIF)) 49 | $this->im = imagecreatefromgif($filename); 50 | else 51 | return false; 52 | 53 | return true; 54 | } 55 | 56 | private function loadString($str) 57 | { 58 | $im = imagecreatefromstring($str); 59 | return ($im === false) ? false : $this->loadResource($im); 60 | } 61 | 62 | public function saveAs($filename, $type = 'jpg', $quality = 75) 63 | { 64 | if($type == 'jpg' && (imagetypes() & IMG_JPG)) 65 | return imagejpeg($this->im, $filename, $quality); 66 | elseif($type == 'png' && (imagetypes() & IMG_PNG)) 67 | return imagepng($this->im, $filename); 68 | elseif($type == 'gif' && (imagetypes() & IMG_GIF)) 69 | return imagegif($this->im, $filename); 70 | else 71 | return false; 72 | } 73 | 74 | // Output file to browser 75 | public function output($type = 'jpg', $quality = 75) 76 | { 77 | if($type == 'jpg' && (imagetypes() & IMG_JPG)) 78 | { 79 | header("Content-Type: image/jpeg"); 80 | imagejpeg($this->im, null, $quality); 81 | return true; 82 | } 83 | elseif($type == 'png' && (imagetypes() & IMG_PNG)) 84 | { 85 | header("Content-Type: image/png"); 86 | imagepng($this->im); 87 | return true; 88 | } 89 | elseif($type == 'gif' && (imagetypes() & IMG_GIF)) 90 | { 91 | header("Content-Type: image/gif"); 92 | imagegif($this->im); 93 | return true; 94 | } 95 | else 96 | return false; 97 | } 98 | 99 | // Return image data as a string. 100 | // Is there a way to do this without using output buffering? 101 | public function toString($type = 'jpg', $quality = 75) 102 | { 103 | ob_start(); 104 | 105 | if($type == 'jpg' && (imagetypes() & IMG_JPG)) 106 | imagejpeg($this->im, null, $quality); 107 | elseif($type == 'png' && (imagetypes() & IMG_PNG)) 108 | imagepng($this->im); 109 | elseif($type == 'gif' && (imagetypes() & IMG_GIF)) 110 | imagegif($this->im); 111 | 112 | return ob_get_clean(); 113 | } 114 | 115 | // Resizes an image and maintains aspect ratio. 116 | public function scale($new_width = null, $new_height = null) 117 | { 118 | if(!is_null($new_width) && is_null($new_height)) 119 | $new_height = $new_width * $this->height / $this->width; 120 | elseif(is_null($new_width) && !is_null($new_height)) 121 | $new_width = $this->width / $this->height * $new_height; 122 | elseif(!is_null($new_width) && !is_null($new_height)) 123 | { 124 | if($this->width < $this->height) 125 | $new_width = $this->width / $this->height * $new_height; 126 | else 127 | $new_height = $new_width * $this->height / $this->width; 128 | } 129 | else 130 | return false; 131 | 132 | return $this->resize($new_width, $new_height); 133 | } 134 | 135 | // Resizes an image to an exact size 136 | public function resize($new_width, $new_height) 137 | { 138 | $dest = imagecreatetruecolor($new_width, $new_height); 139 | 140 | // Transparency fix contributed by Google Code user 'desfrenes' 141 | imagealphablending($dest, false); 142 | imagesavealpha($dest, true); 143 | 144 | if(imagecopyresampled($dest, $this->im, 0, 0, 0, 0, $new_width, $new_height, $this->width, $this->height)) 145 | { 146 | $this->im = $dest; 147 | $this->width = imagesx($this->im); 148 | $this->height = imagesy($this->im); 149 | return true; 150 | } 151 | 152 | return false; 153 | } 154 | 155 | public function crop($x, $y, $w, $h) 156 | { 157 | $dest = imagecreatetruecolor($w, $h); 158 | 159 | if(imagecopyresampled($dest, $this->im, 0, 0, $x, $y, $w, $h, $w, $h)) 160 | { 161 | $this->im = $dest; 162 | $this->width = $w; 163 | $this->height = $h; 164 | return true; 165 | } 166 | 167 | return false; 168 | } 169 | 170 | public function cropCentered($w, $h) 171 | { 172 | $cx = $this->width / 2; 173 | $cy = $this->height / 2; 174 | $x = $cx - $w / 2; 175 | $y = $cy - $h / 2; 176 | if($x < 0) $x = 0; 177 | if($y < 0) $y = 0; 178 | return $this->crop($x, $y, $w, $h); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /includes/class.dbobject.php: -------------------------------------------------------------------------------- 1 | tableName = $table_name; 12 | 13 | // A note on hardcoding $this->idColumnName = 'id'... 14 | // In many years working with this framework, I've used 15 | // a different id name exactly once - so I've decided to 16 | // drop the option from the constructor. You can overload 17 | // the constructor yourself if you have the need. 18 | $this->idColumnName = 'id'; 19 | 20 | foreach($columns as $col) { 21 | $this->columns[$col] = null; 22 | } 23 | 24 | if(!is_null($id)) { 25 | $this->select($id); 26 | } 27 | } 28 | 29 | public function __get($key) 30 | { 31 | if(array_key_exists($key, $this->columns)) { 32 | return $this->columns[$key]; 33 | } 34 | 35 | if((substr($key, 0, 2) == '__') && array_key_exists(substr($key, 2), $this->columns)) { 36 | return htmlspecialchars($this->columns[substr($key, 2)]); 37 | } 38 | 39 | $trace = debug_backtrace(); 40 | trigger_error("Undefined property via DBObject::__get(): $key in {$trace[0]['file']} on line {$trace[0]['line']}", E_USER_NOTICE); 41 | return null; 42 | } 43 | 44 | public function __set($key, $value) 45 | { 46 | if(array_key_exists($key, $this->columns)) { 47 | $this->columns[$key] = $value; 48 | } 49 | 50 | return $value; // Seriously. 51 | } 52 | 53 | public function __unset($key) 54 | { 55 | unset($this->columns[$key]); 56 | } 57 | 58 | public function __isset($key) 59 | { 60 | return array_key_exists($key, $this->columns); 61 | } 62 | 63 | public function select($id, $column = null, $order_by = null, $sort_direction = 'DESC') 64 | { 65 | $db = Database::getDatabase(); 66 | 67 | if(is_null($column)) $column = $this->idColumnName; 68 | $column = $db->escape($column); 69 | 70 | if(isset($order_by) && isset($sort_direction)) { 71 | $db->query("SELECT * FROM `{$this->tableName}` WHERE `$column` = :id ORDER BY `$order_by` $sort_direction LIMIT 1", array('id' => $id)); 72 | } else { 73 | $db->query("SELECT * FROM `{$this->tableName}` WHERE `$column` = :id LIMIT 1", array('id' => $id)); 74 | } 75 | 76 | if($db->hasRows()) 77 | { 78 | $row = $db->getRow(); 79 | $this->load($row); 80 | return true; 81 | } 82 | 83 | return false; 84 | } 85 | 86 | public function ok() 87 | { 88 | return !is_null($this->id); 89 | } 90 | 91 | public function save() 92 | { 93 | if(is_null($this->id)) 94 | $this->insert(); 95 | else 96 | $this->update(); 97 | return $this->id; 98 | } 99 | 100 | public function insert($cmd = 'INSERT INTO') 101 | { 102 | $db = Database::getDatabase(); 103 | 104 | if(count($this->columns) == 0) return false; 105 | 106 | $data = array(); 107 | foreach($this->columns as $k => $v) { 108 | if(isset($v) && !is_null($v)) { 109 | if(is_bool($v)) { 110 | $data[$k] = $v ? 'TRUE' : 'FALSE'; 111 | } else if(is_int($v) || is_float($v)) { 112 | $data[$k] = "$v"; 113 | } else if(is_string($v)) { 114 | $data[$k] = $db->quote($v); 115 | } 116 | } else { 117 | $data[$k] = 'NULL'; 118 | } 119 | } 120 | 121 | $columns = '`' . implode('`, `', array_keys($data)) . '`'; 122 | $values = implode(',', $data); 123 | 124 | $db->query("$cmd `{$this->tableName}` ($columns) VALUES ($values)"); 125 | $this->id = $db->insertID(); 126 | return $this->id; 127 | } 128 | 129 | public function replace() 130 | { 131 | return $this->delete() && $this->insert(); 132 | } 133 | 134 | public function update() 135 | { 136 | if(is_null($this->id)) return false; 137 | 138 | $db = Database::getDatabase(); 139 | 140 | if(count($this->columns) == 0) return; 141 | 142 | $data = array(); 143 | foreach($this->columns as $k => $v) { 144 | if(isset($v) && !is_null($v)) { 145 | if(is_bool($v)) { 146 | $data[$k] = $v ? 'TRUE' : 'FALSE'; 147 | } else if(is_int($v) || is_float($v)) { 148 | $data[$k] = "$v"; 149 | } else if(is_string($v)) { 150 | $data[$k] = $db->quote($v); 151 | } 152 | } else { 153 | $data[$k] = 'NULL'; 154 | } 155 | } 156 | 157 | $sql = "UPDATE {$this->tableName} SET "; 158 | foreach($data as $k => $v) { 159 | $sql .= "`$k` = $v,"; 160 | } 161 | $sql[strlen($sql) - 1] = ' '; 162 | 163 | $sql .= " WHERE `{$this->idColumnName}` = " . $db->quote($this->id); 164 | $db->query($sql); 165 | 166 | return $db->affectedRows(); 167 | } 168 | 169 | public function delete() 170 | { 171 | if(is_null($this->id)) return false; 172 | $db = Database::getDatabase(); 173 | $db->query("DELETE FROM `{$this->tableName}` WHERE `{$this->idColumnName}` = :id LIMIT 1", array('id' => $this->id)); 174 | return $db->affectedRows(); 175 | } 176 | 177 | public function load($row) 178 | { 179 | foreach($row as $k => $v) 180 | { 181 | if($k == $this->idColumnName) 182 | $this->id = $v; 183 | elseif(array_key_exists($k, $this->columns)) 184 | $this->columns[$k] = $v; 185 | } 186 | } 187 | 188 | // Grabs a large block of instantiated $class_name objects from the database using only one query. 189 | public static function glob($sql = null, $extra_columns = array()) 190 | { 191 | $db = Database::getDatabase(); 192 | 193 | // Make sure the class exists before we instantiate it... 194 | $class_name = get_called_class(); 195 | if(!class_exists($class_name)) { 196 | return false; 197 | } 198 | 199 | $tmp_obj = new $class_name; 200 | 201 | // Also, it needs to be a subclass of DBObject... 202 | if(!is_subclass_of($tmp_obj, 'DBObject')) { 203 | return false; 204 | } 205 | 206 | if(is_null($sql)) { 207 | $sql = "SELECT * FROM `{$tmp_obj->tableName}`"; 208 | } 209 | 210 | $objs = array(); 211 | $rows = $db->getRows($sql); 212 | foreach($rows as $row) 213 | { 214 | $o = new $class_name; 215 | $o->load($row); 216 | $objs[$o->id] = $o; 217 | 218 | foreach($extra_columns as $c) 219 | { 220 | $o->addColumn($c); 221 | $o->$c = isset($row[$c]) ? $row[$c] : null; 222 | } 223 | } 224 | return $objs; 225 | } 226 | 227 | public function addColumn($key, $val = null) 228 | { 229 | if(!in_array($key, array_keys($this->columns))) 230 | $this->columns[$key] = $val; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /includes/class.database.php: -------------------------------------------------------------------------------- 1 | host = $Config->dbHost; 24 | $this->name = $Config->dbName; 25 | $this->username = $Config->dbUsername; 26 | $this->password = $Config->dbPassword; 27 | $this->dieOnError = $Config->dbDieOnError; 28 | 29 | $this->db = false; 30 | $this->queries = array(); 31 | 32 | if($connect === true) 33 | $this->connect(); 34 | } 35 | 36 | // Waiting (not so) patiently for 5.3.0... 37 | public static function __callStatic($name, $args) 38 | { 39 | return self::$me->__call($name, $args); 40 | } 41 | 42 | // Get Singleton object 43 | public static function getDatabase($connect = true) 44 | { 45 | if(is_null(self::$me)) 46 | self::$me = new Database($connect); 47 | return self::$me; 48 | } 49 | 50 | // Do we have a valid database connection? 51 | public function isConnected() 52 | { 53 | return is_object($this->db); 54 | } 55 | 56 | // Do we have a valid database connection and have we selected a database? 57 | public function databaseSelected() 58 | { 59 | if(!$this->isConnected()) return false; 60 | $result = mysqli_query($this->db, "SHOW TABLES"); 61 | return is_object($result); 62 | } 63 | 64 | public function connect() 65 | { 66 | $this->db = mysqli_connect($this->host, $this->username, $this->password, $this->name) or $this->notify(); 67 | if($this->db === false) return false; 68 | return $this->isConnected(); 69 | } 70 | 71 | public function query($sql, $args_to_prepare = null, $exception_on_missing_args = true) 72 | { 73 | if(!$this->isConnected()) $this->connect(); 74 | 75 | // Allow for prepared arguments. Example: 76 | // query("SELECT * FROM table WHERE id = :id", array('id' => $some_val)); 77 | if(is_array($args_to_prepare)) 78 | { 79 | foreach($args_to_prepare as $name => $val) 80 | { 81 | $val = $this->quote($val); 82 | $sql = str_replace(":$name", $val, $sql, $count); 83 | if($exception_on_missing_args && (0 == $count)) 84 | throw new Exception(":$name was not found in prepared SQL query."); 85 | } 86 | } 87 | 88 | $this->queries[] = $sql; 89 | $this->result = mysqli_query($this->db, $sql) or $this->notify(); 90 | return $this->result; 91 | } 92 | 93 | // Returns the number of rows. 94 | // You can pass in nothing, a string, or a db result 95 | public function numRows($arg = null) 96 | { 97 | $result = $this->resulter($arg); 98 | return ($result !== false) ? mysqli_num_rows($result) : false; 99 | } 100 | 101 | // Returns true / false if the result has one or more rows 102 | public function hasRows($arg = null) 103 | { 104 | $result = $this->resulter($arg); 105 | return is_object($result) && (mysqli_num_rows($result) > 0); 106 | } 107 | 108 | // Returns the number of rows affected by the previous operation 109 | public function affectedRows() 110 | { 111 | if(!$this->isConnected()) return false; 112 | return mysqli_affected_rows($this->db); 113 | } 114 | 115 | // Returns the auto increment ID generated by the previous insert statement 116 | public function insertID() 117 | { 118 | if(!$this->isConnected()) return false; 119 | $id = mysqli_insert_id($this->db); 120 | if($id === 0 || $id === false) 121 | return false; 122 | else 123 | return $id; 124 | } 125 | 126 | // Returns a single value. 127 | // You can pass in nothing, a string, or a db result 128 | public function getValue($arg = null) 129 | { 130 | $result = $this->resulter($arg); 131 | if($this->hasRows($result)) 132 | { 133 | $row = mysqli_fetch_row($result); 134 | return $row[0]; 135 | } 136 | else 137 | { 138 | return false; 139 | } 140 | } 141 | 142 | // Returns an array of the first value in each row. 143 | // You can pass in nothing, a string, or a db result 144 | public function getValues($arg = null) 145 | { 146 | $result = $this->resulter($arg); 147 | if(!$this->hasRows($result)) return array(); 148 | 149 | $values = array(); 150 | mysqli_data_seek($result, 0); 151 | while($row = mysqli_fetch_row($result)) 152 | $values[] = array_pop($row); 153 | return $values; 154 | } 155 | 156 | // Returns the first row. 157 | // You can pass in nothing, a string, or a db result 158 | public function getRow($arg = null) 159 | { 160 | $result = $this->resulter($arg); 161 | return $this->hasRows() ? mysqli_fetch_array($result, MYSQLI_ASSOC) : false; 162 | } 163 | 164 | // Returns an array of all the rows. 165 | // You can pass in nothing, a string, or a db result 166 | public function getRows($arg = null) 167 | { 168 | $result = $this->resulter($arg); 169 | if(!$this->hasRows($result)) return array(); 170 | 171 | $rows = array(); 172 | mysqli_data_seek($result, 0); 173 | while($row = mysqli_fetch_array($result, MYSQLI_BOTH)) 174 | $rows[] = $row; 175 | return $rows; 176 | } 177 | 178 | // Escapes a value and wraps it in single quotes. 179 | public function quote($var) 180 | { 181 | if(!$this->isConnected()) $this->connect(); 182 | return "'" . $this->escape($var) . "'"; 183 | } 184 | 185 | // Escapes a value. 186 | public function escape($var) 187 | { 188 | if(!$this->isConnected()) $this->connect(); 189 | return mysqli_real_escape_string($this->db, $var); 190 | } 191 | 192 | public function numQueries() 193 | { 194 | return count($this->queries); 195 | } 196 | 197 | public function lastQuery() 198 | { 199 | if($this->numQueries() > 0) 200 | return $this->queries[$this->numQueries() - 1]; 201 | else 202 | return false; 203 | } 204 | 205 | private function notify() 206 | { 207 | $err_msg = mysqli_error($this->db); 208 | error_log($err_msg); 209 | 210 | if($this->dieOnError === true) 211 | { 212 | echo "

Database Error:
$err_msg

"; 213 | echo "

Last Query:
" . $this->lastQuery() . "

"; 214 | echo "
";
215 |                 debug_print_backtrace();
216 |                 echo "
"; 217 | exit; 218 | } 219 | 220 | if(is_string($this->redirect)) 221 | { 222 | header("Location: {$this->redirect}"); 223 | exit; 224 | } 225 | } 226 | 227 | // Takes nothing, a MySQL result, or a query string and returns 228 | // the correspsonding MySQL result resource or false if none available. 229 | private function resulter($arg = null) 230 | { 231 | if(is_null($arg) && is_object($this->result)) 232 | return $this->result; 233 | elseif(is_object($arg)) 234 | return $arg; 235 | elseif(is_string($arg)) 236 | { 237 | $this->query($arg); 238 | if(is_object($this->result)) 239 | return $this->result; 240 | else 241 | return false; 242 | } 243 | else 244 | return false; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /includes/class.spferror.php: -------------------------------------------------------------------------------- 1 | errors = array(); 13 | $this->style = $style; 14 | } 15 | 16 | // Get Singleton object 17 | public static function getError() 18 | { 19 | if(is_null(self::$me)) 20 | self::$me = new SPFError(); 21 | return self::$me; 22 | } 23 | 24 | // Returns an unordered list of error messages 25 | public function __tostring() 26 | { 27 | return $this->alert(); 28 | } 29 | 30 | // Returns true if there are no errors 31 | public function ok() 32 | { 33 | return count($this->errors) == 0; 34 | } 35 | 36 | // Manually add an error 37 | public function add($id, $msg) 38 | { 39 | if(isset($this->errors[$id]) && !is_array($this->errors[$id])) 40 | $this->errors[$id] = array($msg); 41 | else 42 | $this->errors[$id][] = $msg; 43 | } 44 | 45 | // Delete all errors associated with an element's id 46 | public function delete($id) 47 | { 48 | unset($this->errors[$id]); 49 | } 50 | 51 | // Returns the error message associated with an element. 52 | // This may return a string or an array - so be sure to test before echoing! 53 | public function msg($id) 54 | { 55 | return $this->errors[$id]; 56 | } 57 | 58 | // Outputs the CSS to style the error elements 59 | public function css($header = true) 60 | { 61 | $out = ''; 62 | if(count($this->errors) > 0) 63 | { 64 | if($header) $out .= ''; 67 | } 68 | echo $out; 69 | } 70 | 71 | // Returns an unordered list of error messages 72 | public function ul($class = 'warn') 73 | { 74 | if(count($this->errors) == 0) return ''; 75 | 76 | $out = ""; 80 | 81 | return $out; 82 | } 83 | 84 | // Returns error alerts 85 | public function alert() 86 | { 87 | if(count($this->errors) == 0) return ''; 88 | $out = ''; 89 | foreach($this->errors as $error) 90 | $out .= "

" . implode(' ', $error) . "

"; 91 | 92 | return $out; 93 | } 94 | 95 | // Below are a collection of tests for error conditions in your user's input... 96 | // Be sure to customize these to suit your app's needs. Especially the error messages. 97 | 98 | // Is the (string) value empty? 99 | public function blank($val, $id, $name = null) 100 | { 101 | if(trim($val) == '') 102 | { 103 | if(is_null($name)) $name = ucwords($id); 104 | $this->add($id, "$name cannot be left blank."); 105 | return false; 106 | } 107 | 108 | return true; 109 | } 110 | 111 | // Is a number between a given range? (inclusive) 112 | public function range($val, $lower, $upper, $id, $name = null) 113 | { 114 | if($val < $lower || $val > $upper) 115 | { 116 | if(is_null($name)) $name = ucwords($id); 117 | $this->add($id, "$name must be between $lower and $upper."); 118 | return false; 119 | } 120 | 121 | return true; 122 | } 123 | 124 | // Is a string an appropriate length? 125 | public function length($val, $lower, $upper, $id, $name = null) 126 | { 127 | if(strlen($val) < $lower) 128 | { 129 | if(is_null($name)) $name = ucwords($id); 130 | $this->add($id, "$name must be at least $lower characters."); 131 | return false; 132 | } 133 | elseif(strlen($val) > $upper) 134 | { 135 | if(is_null($name)) $name = ucwords($id); 136 | $this->add($id, "$name cannot be more than $upper characters long."); 137 | return false; 138 | } 139 | 140 | return true; 141 | } 142 | 143 | // Do the passwords match? 144 | public function passwords($pass1, $pass2, $id) 145 | { 146 | if($pass1 !== $pass2) 147 | { 148 | $this->add($id, 'The passwords you entered do not match.'); 149 | return false; 150 | } 151 | 152 | return true; 153 | } 154 | 155 | // Does a value match a given regex? 156 | public function regex($val, $regex, $id, $msg) 157 | { 158 | if(preg_match($regex, $val) === 0) 159 | { 160 | $this->add($id, $msg); 161 | return false; 162 | } 163 | 164 | return true; 165 | } 166 | 167 | // Is an email address valid? 168 | public function email($val, $id = 'email') 169 | { 170 | if(!preg_match("/^.+@.+\..+$/i", $val)) 171 | { 172 | $this->add($id, 'The email address you entered is not valid.'); 173 | return false; 174 | } 175 | 176 | return true; 177 | } 178 | 179 | // Is a string a parseable and valid date? 180 | public function date($val, $id) 181 | { 182 | if(chkdate($val) === false) 183 | { 184 | $this->add($id, 'Please enter a valid date'); 185 | return false; 186 | } 187 | 188 | return true; 189 | } 190 | 191 | // Is a birth date at least 18 years old? 192 | public function adult($val, $id) 193 | { 194 | if( dater($val) > ( (date('Y') - 18) . date('-m-d H:i:s') ) ) 195 | { 196 | $this->add($id, 'You must be at least 18 years old.'); 197 | return false; 198 | } 199 | 200 | return true; 201 | } 202 | 203 | // Is a string a valid phone number? 204 | public function phone($val, $id) 205 | { 206 | $val = preg_replace('/[^0-9]/', '', $val); 207 | if(strlen($val) != 7 && strlen($val) != 10) 208 | { 209 | $this->add($id, 'Please enter a valid 7 or 10 digit phone number.'); 210 | return false; 211 | } 212 | 213 | return true; 214 | } 215 | 216 | // Did we get a successful file upload? 217 | // Typically, you'd pass in $_FILES['file'] 218 | public function upload($val, $id) 219 | { 220 | if(!is_uploaded_file($val['tmp_name']) || !is_readable($val['tmp_name'])) 221 | { 222 | $this->add($id, 'Your file was not uploaded successfully. Please try again.'); 223 | return false; 224 | } 225 | 226 | return true; 227 | } 228 | 229 | // Valid 5 digit zip code? 230 | public function zip($val, $id, $name = null) 231 | { 232 | // From http://www.zend.com//code/codex.php?ozid=991&single=1 233 | $ranges = array(array('99500', '99929'), array('35000', '36999'), array('71600', '72999'), array('75502', '75505'), array('85000', '86599'), array('90000', '96199'), array('80000', '81699'), array('06000', '06999'), array('20000', '20099'), array('20200', '20599'), array('19700', '19999'), array('32000', '33999'), array('34100', '34999'), array('30000', '31999'), array('96700', '96798'), array('96800', '96899'), array('50000', '52999'), array('83200', '83899'), array('60000', '62999'), array('46000', '47999'), array('66000', '67999'), array('40000', '42799'), array('45275', '45275'), array('70000', '71499'), array('71749', '71749'), array('01000', '02799'), array('20331', '20331'), array('20600', '21999'), array('03801', '03801'), array('03804', '03804'), array('03900', '04999'), array('48000', '49999'), array('55000', '56799'), array('63000', '65899'), array('38600', '39799'), array('59000', '59999'), array('27000', '28999'), array('58000', '58899'), array('68000', '69399'), array('03000', '03803'), array('03809', '03899'), array('07000', '08999'), array('87000', '88499'), array('89000', '89899'), array('00400', '00599'), array('06390', '06390'), array('09000', '14999'), array('43000', '45999'), array('73000', '73199'), array('73400', '74999'), array('97000', '97999'), array('15000', '19699'), array('02800', '02999'), array('06379', '06379'), array('29000', '29999'), array('57000', '57799'), array('37000', '38599'), array('72395', '72395'), array('73300', '73399'), array('73949', '73949'), array('75000', '79999'), array('88501', '88599'), array('84000', '84799'), array('20105', '20199'), array('20301', '20301'), array('20370', '20370'), array('22000', '24699'), array('05000', '05999'), array('98000', '99499'), array('49936', '49936'), array('53000', '54999'), array('24700', '26899'), array('82000', '83199')); 234 | foreach($ranges as $r) 235 | { 236 | if($val >= $r[0] && $val <= $r[1]) 237 | return true; 238 | } 239 | 240 | if(is_null($name)) $name = ucwords($id); 241 | $this->add($id, "Please enter a valid, 5-digit zip code."); 242 | return false; 243 | } 244 | 245 | // Test if string $val is a valid, decimal number. 246 | public function nan($val, $id, $name = null) 247 | { 248 | if(preg_match('/^-?[0-9]+(\.[0-9]+)?$/', $val) == 0) 249 | { 250 | if(is_null($name)) $name = ucwords($id); 251 | $this->add($id, "$name must be a number."); 252 | return false; 253 | } 254 | return true; 255 | } 256 | 257 | // Valid URL? 258 | // This is hardly perfect, but it's good enough for now... 259 | // TODO: Make URL validation more robust 260 | public function url($val, $id, $name = null) 261 | { 262 | $info = @parse_url($val); 263 | if(($info === false) || ($info['scheme'] != 'http' && $info['scheme'] != 'https') || ($info['host'] == '')) 264 | { 265 | if(is_null($name)) $name = ucwords($id); 266 | $this->add($id, "$name is not a valid URL."); 267 | return false; 268 | } 269 | return true; 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /includes/class.auth.php: -------------------------------------------------------------------------------- 1 | id = null; 20 | $this->nid = null; 21 | $this->username = null; 22 | $this->user = null; 23 | $this->loggedIn = false; 24 | $this->expiryDate = mktime(0, 0, 0, 6, 2, 2037); 25 | $this->user = new User(); 26 | } 27 | 28 | public static function getAuth() 29 | { 30 | if(is_null(self::$me)) 31 | { 32 | self::$me = new Auth(); 33 | self::$me->init(); 34 | } 35 | return self::$me; 36 | } 37 | 38 | public function init() 39 | { 40 | $this->setACookie(); 41 | $this->loggedIn = $this->attemptCookieLogin(); 42 | } 43 | 44 | public function sendTwoStep($id_or_username) 45 | { 46 | if(ctype_digit($id_or_username)) { 47 | $u = new User($id_or_username); 48 | } else { 49 | $u = new User(); 50 | $u->select($id_or_username, 'username'); 51 | } 52 | 53 | if($u->ok()) 54 | { 55 | $u->twostep = $this->generateNewTwoStep(); 56 | $u->update(); 57 | } 58 | 59 | // At this point you would send the 2FA code to the user somehow. 60 | // This could be via an email, text, etc. It's up to you to write 61 | // a function to deliver the code. 62 | // some_function($u->twostep); 63 | } 64 | 65 | public function login($username, $password, $twostep = null) 66 | { 67 | $this->loggedIn = false; 68 | 69 | $db = Database::getDatabase(); 70 | $hashed_password = self::hashedPassword($password); 71 | $row = $db->getRow("SELECT * FROM users WHERE username = " . $db->quote($username) . " AND password = " . $db->quote($hashed_password)); 72 | 73 | if($row === false) 74 | return false; 75 | 76 | if(Config::get('useTwoStepAuth')) { 77 | if($twostep !== $row['twostep']) { 78 | return false; 79 | } 80 | } 81 | 82 | $this->id = $row['id']; 83 | $this->nid = $row['nid']; 84 | $this->username = $row['username']; 85 | $this->user = new User(); 86 | $this->user->id = $this->id; 87 | $this->user->load($row); 88 | 89 | $this->generateBCCookies(); 90 | 91 | $this->loggedIn = true; 92 | 93 | return true; 94 | } 95 | 96 | public function logout() 97 | { 98 | $this->loggedIn = false; 99 | $this->clearCookies(); 100 | $this->sendToLoginPage(); 101 | } 102 | 103 | public function loggedIn() 104 | { 105 | return $this->loggedIn; 106 | } 107 | 108 | public function requireUser() 109 | { 110 | if(!$this->loggedIn()) 111 | $this->sendToLoginPage(); 112 | } 113 | 114 | public function requireAdmin() 115 | { 116 | if(!$this->loggedIn() || !$this->isAdmin()) 117 | $this->sendToLoginPage(); 118 | } 119 | 120 | public function isAdmin() 121 | { 122 | return ($this->user->level === 'admin'); 123 | } 124 | 125 | public function generateNewTwoStep() 126 | { 127 | $twostep = ''; 128 | while(strlen($twostep) < 6) { 129 | $twostep .= preg_replace('/[^0-9]/', '', sha1(microtime() . self::SALT)); 130 | } 131 | $twostep = substr($twostep, 0, 6); 132 | return $twostep; 133 | } 134 | 135 | public function changeCurrentUsername($new_username) 136 | { 137 | $db = Database::getDatabase(); 138 | srand(time()); 139 | $this->user->nid = Auth::newNid(); 140 | $this->nid = $this->user->nid; 141 | $this->user->username = $new_username; 142 | $this->username = $this->user->username; 143 | $this->user->update(); 144 | $this->generateBCCookies(); 145 | } 146 | 147 | public function changeCurrentPassword($new_password) 148 | { 149 | $db = Database::getDatabase(); 150 | srand(time()); 151 | $this->user->nid = self::newNid(); 152 | $this->user->password = self::hashedPassword($new_password); 153 | $this->user->update(); 154 | $this->nid = $this->user->nid; 155 | $this->generateBCCookies(); 156 | } 157 | 158 | public static function changeUsername($id_or_username, $new_username) 159 | { 160 | if(ctype_digit($id_or_username)) 161 | $u = new User($id_or_username); 162 | else 163 | { 164 | $u = new User(); 165 | $u->select($id_or_username, 'username'); 166 | } 167 | 168 | if($u->ok()) 169 | { 170 | $u->username = $new_username; 171 | $u->update(); 172 | } 173 | } 174 | 175 | public static function changePassword($id_or_username, $new_password) 176 | { 177 | if(ctype_digit($id_or_username)) 178 | $u = new User($id_or_username); 179 | else 180 | { 181 | $u = new User(); 182 | $u->select($id_or_username, 'username'); 183 | } 184 | 185 | if($u->ok()) 186 | { 187 | $u->nid = self::newNid(); 188 | $u->password = self::hashedPassword($new_password); 189 | $u->update(); 190 | } 191 | } 192 | 193 | public static function createNewUser($username, $password = null, $level = 'user') 194 | { 195 | if(is_null($password)) 196 | $password = Auth::generateStrongPassword(); 197 | 198 | srand(time()); 199 | $u = new User(); 200 | $u->username = $username; 201 | $u->nid = self::newNid(); 202 | $u->password = self::hashedPassword($password); 203 | $u->level = $level; 204 | $u->insert(); 205 | return $u; 206 | } 207 | 208 | public static function generateStrongPassword($length = 9, $add_dashes = false, $available_sets = 'luds') 209 | { 210 | $sets = array(); 211 | if(strpos($available_sets, 'l') !== false) 212 | $sets[] = 'abcdefghjkmnpqrstuvwxyz'; 213 | if(strpos($available_sets, 'u') !== false) 214 | $sets[] = 'ABCDEFGHJKMNPQRSTUVWXYZ'; 215 | if(strpos($available_sets, 'd') !== false) 216 | $sets[] = '23456789'; 217 | if(strpos($available_sets, 's') !== false) 218 | $sets[] = '!@#$%&*?'; 219 | 220 | $all = ''; 221 | $password = ''; 222 | foreach($sets as $set) 223 | { 224 | $password .= $set[array_rand(str_split($set))]; 225 | $all .= $set; 226 | } 227 | 228 | $all = str_split($all); 229 | for($i = 0; $i < $length - count($sets); $i++) 230 | $password .= $all[array_rand($all)]; 231 | 232 | $password = str_shuffle($password); 233 | 234 | if(!$add_dashes) 235 | return $password; 236 | 237 | $dash_len = floor(sqrt($length)); 238 | $dash_str = ''; 239 | while(strlen($password) > $dash_len) 240 | { 241 | $dash_str .= substr($password, 0, $dash_len) . '-'; 242 | $password = substr($password, $dash_len); 243 | } 244 | $dash_str .= $password; 245 | return $dash_str; 246 | } 247 | 248 | public function impersonateUser($id_or_username) 249 | { 250 | if(ctype_digit($id_or_username)) 251 | $u = new User($id_or_username); 252 | else 253 | { 254 | $u = new User(); 255 | $u->select($id_or_username, 'username'); 256 | } 257 | 258 | if(!$u->ok()) return false; 259 | 260 | $this->id = $u->id; 261 | $this->nid = $u->nid; 262 | $this->username = $u->username; 263 | $this->user = $u; 264 | $this->generateBCCookies(); 265 | 266 | return true; 267 | } 268 | 269 | private function attemptCookieLogin() 270 | { 271 | if(!isset($_COOKIE['A']) || !isset($_COOKIE['B']) || !isset($_COOKIE['C'])) 272 | return false; 273 | 274 | $ccookie = base64_decode(str_rot13($_COOKIE['C'])); 275 | if($ccookie === false) 276 | return false; 277 | 278 | $c = array(); 279 | parse_str($ccookie, $c); 280 | if(!isset($c['n']) || !isset($c['l'])) 281 | return false; 282 | 283 | $bcookie = base64_decode(str_rot13($_COOKIE['B'])); 284 | if($bcookie === false) 285 | return false; 286 | 287 | $b = array(); 288 | parse_str($bcookie, $b); 289 | if(!isset($b['s']) || !isset($b['x'])) 290 | return false; 291 | 292 | if($b['x'] < time()) 293 | return false; 294 | 295 | $computed_sig = sha1(str_rot13(base64_encode($ccookie)) . $b['x'] . self::SALT); 296 | if($computed_sig != $b['s']) 297 | return false; 298 | 299 | $nid = base64_decode($c['n']); 300 | if($nid === false) 301 | return false; 302 | 303 | $db = Database::getDatabase(); 304 | 305 | // We SELECT * so we can load the full user record into the user DBObject later 306 | $row = $db->getRow('SELECT * FROM users WHERE nid = ' . $db->quote($nid)); 307 | if($row === false) 308 | return false; 309 | 310 | $this->id = $row['id']; 311 | $this->nid = $row['nid']; 312 | $this->username = $row['username']; 313 | $this->user = new User(); 314 | $this->user->id = $this->id; 315 | $this->user->load($row); 316 | 317 | return true; 318 | } 319 | 320 | private function setACookie() 321 | { 322 | if(!isset($_COOKIE['A'])) 323 | { 324 | srand(time()); 325 | $a = sha1(rand() . microtime()); 326 | setcookie('A', $a, $this->expiryDate, '/', Config::get('authDomain')); 327 | } 328 | } 329 | 330 | private function generateBCCookies() 331 | { 332 | $c = ''; 333 | $c .= 'n=' . base64_encode($this->nid) . '&'; 334 | $c .= 'l=' . str_rot13($this->username) . '&'; 335 | $c = base64_encode($c); 336 | $c = str_rot13($c); 337 | 338 | $sig = sha1($c . $this->expiryDate . self::SALT); 339 | $b = "x={$this->expiryDate}&s=$sig"; 340 | $b = base64_encode($b); 341 | $b = str_rot13($b); 342 | 343 | setcookie('B', $b, $this->expiryDate, '/', Config::get('authDomain')); 344 | setcookie('C', $c, $this->expiryDate, '/', Config::get('authDomain')); 345 | } 346 | 347 | private function clearCookies() 348 | { 349 | setcookie('B', '', time() - 3600, '/', Config::get('authDomain')); 350 | setcookie('C', '', time() - 3600, '/', Config::get('authDomain')); 351 | } 352 | 353 | private function sendToLoginPage() 354 | { 355 | $url = $this->loginUrl; 356 | 357 | $full_url = full_url(); 358 | if(strpos($full_url, 'logout') === false) 359 | { 360 | $url .= '?r=' . $full_url; 361 | } 362 | 363 | redirect($url); 364 | } 365 | 366 | private static function hashedPassword($password) 367 | { 368 | return sha1($password . self::SALT); 369 | } 370 | 371 | private static function newNid() 372 | { 373 | srand(time()); 374 | return sha1(rand() . microtime()); 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /includes/functions.inc.php: -------------------------------------------------------------------------------- 1 | setTimezone(new DateTimeZone('UTC')); 10 | return $dt->format('Y-m-d H:i:s'); 11 | } 12 | 13 | function twitterfy($str) 14 | { 15 | // Via http://www.snipe.net/2009/09/php-twitter-clickable-links/ 16 | $str = preg_replace("#(^|[\n ])([\w]+?://[\w]+[^ \"\n\r\t< ]*)#", "\\1\\2", $str); 17 | $str = preg_replace("#(^|[\n ])((www|ftp)\.[^ \"\t\n\r< ]*)#", "\\1\\2", $str); 18 | $str = preg_replace("/@(\w+)/", "@\\1", $str); 19 | $str = preg_replace("/#(\w+)/", "#\\1", $str); 20 | return $str; 21 | } 22 | 23 | function set_option($key, $val) 24 | { 25 | $db = Database::getDatabase(); 26 | $db->query('REPLACE INTO options (`key`, `value`) VALUES (:key, :value)', array('key' => $key, 'value' => $val)); 27 | } 28 | 29 | function get_option($key, $default = null) 30 | { 31 | $db = Database::getDatabase(); 32 | $db->query('SELECT `value` FROM options WHERE `key` = :key', array('key' => $key)); 33 | if($db->hasRows()) 34 | return $db->getValue(); 35 | else 36 | return $default; 37 | } 38 | 39 | function printr($var) 40 | { 41 | $output = print_r($var, true); 42 | $output = str_replace("\n", "
", $output); 43 | $output = str_replace(' ', ' ', $output); 44 | echo "
$output
"; 45 | } 46 | 47 | // Formats a given number of seconds into proper mm:ss format 48 | function format_time($seconds) 49 | { 50 | return floor($seconds / 60) . ':' . str_pad($seconds % 60, 2, '0'); 51 | } 52 | 53 | // Given a string such as "comment_123" or "id_57", it returns the final, numeric id. 54 | function split_id($str) 55 | { 56 | return match('/[_-]([0-9]+)$/', $str, 1); 57 | } 58 | 59 | // Creates a friendly URL slug from a string 60 | function slugify($str) 61 | { 62 | $str = preg_replace('/[^a-zA-Z0-9 -]/', '', $str); 63 | $str = strtolower(str_replace(' ', '-', trim($str))); 64 | $str = preg_replace('/-+/', '-', $str); 65 | return $str; 66 | } 67 | 68 | // Computes the *full* URL of the current page (protocol, server, path, query parameters, etc) 69 | function full_url() 70 | { 71 | $s = empty($_SERVER['HTTPS']) ? '' : ($_SERVER['HTTPS'] == 'on') ? 's' : ''; 72 | $protocol = substr(strtolower($_SERVER['SERVER_PROTOCOL']), 0, strpos(strtolower($_SERVER['SERVER_PROTOCOL']), '/')) . $s; 73 | $port = ($_SERVER['SERVER_PORT'] == '80') ? '' : (":".$_SERVER['SERVER_PORT']); 74 | return $protocol . "://" . $_SERVER['HTTP_HOST'] . $port . $_SERVER['REQUEST_URI']; 75 | } 76 | 77 | // Returns an English representation of a past date within the last month 78 | // Graciously stolen from http://ejohn.org/files/pretty.js 79 | function time2str($ts) 80 | { 81 | if(!ctype_digit($ts)) { 82 | $ts = strtotime($ts); 83 | } 84 | 85 | $diff = time() - $ts; 86 | if($diff == 0) { 87 | return 'now'; 88 | } else if($diff > 0) { 89 | $day_diff = floor($diff / 86400); 90 | if($day_diff == 0) 91 | { 92 | if($diff < 60) return 'just now'; 93 | if($diff < 120) return '1 minute ago'; 94 | if($diff < 3600) return floor($diff / 60) . ' minutes ago'; 95 | if($diff < 7200) return '1 hour ago'; 96 | if($diff < 86400) return floor($diff / 3600) . ' hours ago'; 97 | } 98 | if($day_diff == 1) return 'Yesterday'; 99 | if($day_diff < 7) return $day_diff . ' days ago'; 100 | if($day_diff < 31) return ceil($day_diff / 7) . ' weeks ago'; 101 | if($day_diff < 60) return 'last month'; 102 | $ret = date('F Y', $ts); 103 | return ($ret == 'December 1969') ? '' : $ret; 104 | } else { 105 | $diff = abs($diff); 106 | $day_diff = floor($diff / 86400); 107 | if($day_diff == 0) 108 | { 109 | if($diff < 120) return 'in a minute'; 110 | if($diff < 3600) return 'in ' . floor($diff / 60) . ' minutes'; 111 | if($diff < 7200) return 'in an hour'; 112 | if($diff < 86400) return 'in ' . floor($diff / 3600) . ' hours'; 113 | } 114 | if($day_diff == 1) return 'Tomorrow'; 115 | if($day_diff < 4) return date('l', $ts); 116 | if($day_diff < 7 + (7 - date('w'))) return 'next week'; 117 | if(ceil($day_diff / 7) < 4) return 'in ' . ceil($day_diff / 7) . ' weeks'; 118 | if(date('n', $ts) == date('n') + 1) return 'next month'; 119 | $ret = date('F Y', $ts); 120 | return ($ret == 'December 1969') ? '' : $ret; 121 | } 122 | } 123 | 124 | // Returns an array representation of the given calendar month. 125 | // The array values are timestamps which allow you to easily format 126 | // and manipulate the dates as needed. 127 | function calendar($month = null, $year = null) 128 | { 129 | if(is_null($month)) $month = date('n'); 130 | if(is_null($year)) $year = date('Y'); 131 | 132 | $first = mktime(0, 0, 0, $month, 1, $year); 133 | $last = mktime(23, 59, 59, $month, date('t', $first), $year); 134 | 135 | $start = $first - (86400 * date('w', $first)); 136 | $stop = $last + (86400 * (7 - date('w', $first))); 137 | 138 | $out = array(); 139 | while($start < $stop) 140 | { 141 | $week = array(); 142 | if($start > $last) break; 143 | for($i = 0; $i < 7; $i++) 144 | { 145 | $week[$i] = $start; 146 | $start += 86400; 147 | } 148 | $out[] = $week; 149 | } 150 | 151 | return $out; 152 | } 153 | 154 | // Processes mod_rewrite URLs into key => value pairs 155 | // See .htacess for more info. 156 | function pick_off($grab_first = false, $sep = '/') 157 | { 158 | $ret = array(); 159 | $arr = explode($sep, trim($_SERVER['REQUEST_URI'], $sep)); 160 | if($grab_first) $ret[0] = array_shift($arr); 161 | while(count($arr) > 0) { 162 | $ret[array_shift($arr)] = array_shift($arr); 163 | } 164 | return (count($ret) > 0) ? $ret : false; 165 | } 166 | 167 | // Creates a list of '; 186 | elseif(is_array($default) && in_array($row[$val],$default)) 187 | $out .= ''; 188 | else 189 | $out .= ''; 190 | } 191 | return $out; 192 | } 193 | 194 | // More robust strict date checking for string representations 195 | function chkdate($str) 196 | { 197 | $info = date_parse($str); 198 | if($info !== false && $info['error_count'] == 0) 199 | { 200 | if(checkdate($info['month'], $info['day'], $info['year'])) { 201 | return true; 202 | } 203 | } 204 | 205 | return false; 206 | } 207 | 208 | // Converts a date/timestamp into the specified format 209 | function dater($date = null, $format = null) 210 | { 211 | if(is_null($format)) { 212 | $format = 'Y-m-d H:i:s'; 213 | } 214 | 215 | if(is_null($date)) { 216 | $date = time(); 217 | } 218 | 219 | // if $date contains only numbers, treat it as a timestamp 220 | if(ctype_digit($date) === true) 221 | return date($format, $date); 222 | else 223 | return date($format, strtotime($date)); 224 | } 225 | 226 | // Formats a phone number as (xxx) xxx-xxxx or xxx-xxxx depending on the length. 227 | function format_phone($phone) 228 | { 229 | $phone = preg_replace("/[^0-9]/", '', $phone); 230 | 231 | if(strlen($phone) == 7) 232 | return preg_replace("/([0-9]{3})([0-9]{4})/", "$1-$2", $phone); 233 | elseif(strlen($phone) == 10) 234 | return preg_replace("/([0-9]{3})([0-9]{3})([0-9]{4})/", "($1) $2-$3", $phone); 235 | else 236 | return $phone; 237 | } 238 | 239 | // Outputs hour, minute, am/pm dropdown boxes 240 | function hourmin($hid = 'hour', $mid = 'minute', $pid = 'ampm', $hval = null, $mval = null, $pval = null) 241 | { 242 | // Dumb hack to let you just pass in a timestamp instead 243 | if(func_num_args() == 1) 244 | { 245 | list($hval, $mval, $pval) = explode(' ', date('g i a', strtotime($hid))); 246 | $hid = 'hour'; 247 | $mid = 'minute'; 248 | $aid = 'ampm'; 249 | } 250 | else 251 | { 252 | if(is_null($hval)) $hval = date('h'); 253 | if(is_null($mval)) $mval = date('i'); 254 | if(is_null($pval)) $pval = date('a'); 255 | } 256 | 257 | $hours = array(12, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11); 258 | $out = ""; 263 | 264 | $minutes = array('00', 15, 30, 45); 265 | $out .= ""; 270 | 271 | $out .= ""; 276 | 277 | return $out; 278 | } 279 | 280 | // Returns the HTML for a month, day, and year dropdown boxes. 281 | // You can set the default date by passing in a timestamp OR a parseable date string. 282 | // $prefix_ will be appened to the name/id's of each dropdown, allowing for multiple calls in the same form. 283 | // $output_format lets you specify which dropdowns appear and in what order. 284 | function mdy($date = null, $prefix = null, $output_format = 'm d y') 285 | { 286 | if(is_null($date)) $date = time(); 287 | if(!ctype_digit($date)) $date = strtotime($date); 288 | if(!is_null($prefix)) $prefix .= '_'; 289 | list($yval, $mval, $dval) = explode(' ', date('Y n j', $date)); 290 | 291 | $month_dd = ""; 298 | 299 | $day_dd = ""; 306 | 307 | $year_dd = ""; 314 | 315 | $trans = array('m' => $month_dd, 'd' => $day_dd, 'y' => $year_dd); 316 | return strtr($output_format, $trans); 317 | } 318 | 319 | // Redirects user to $url 320 | function redirect($url = null) 321 | { 322 | if(is_null($url)) $url = $_SERVER['PHP_SELF']; 323 | header("Location: $url"); 324 | exit(); 325 | } 326 | 327 | // Ensures $str ends with a single / 328 | function slash($str) 329 | { 330 | return rtrim($str, '/') . '/'; 331 | } 332 | 333 | // Ensures $str DOES NOT end with a / 334 | function unslash($str) 335 | { 336 | return rtrim($str, '/'); 337 | } 338 | 339 | // Returns an array of the values of the specified column from a multi-dimensional array 340 | function gimme($arr, $key = null, $mod = 1) 341 | { 342 | if(is_null($key)) 343 | $key = current(array_keys($arr)); 344 | 345 | if(is_numeric($key)) { 346 | $keys = array_keys($arr[0]); 347 | $key = $keys[$key]; 348 | } 349 | 350 | $out = array(); 351 | $i = 0; 352 | foreach($arr as $k => $a) 353 | { 354 | if($i % $mod == 0) 355 | $out[] = $a[$key]; 356 | $i++; 357 | } 358 | 359 | return $out; 360 | } 361 | 362 | // Returns the first $num words of $str 363 | function max_words($str, $num, $suffix = '') 364 | { 365 | $words = explode(' ', $str); 366 | if(count($words) < $num) 367 | return $str; 368 | else 369 | return implode(' ', array_slice($words, 0, $num)) . $suffix; 370 | } 371 | 372 | // Serves an external document for download as an HTTP attachment. 373 | function download_document($filename, $mimetype = 'application/octet-stream') 374 | { 375 | if(!file_exists($filename) || !is_readable($filename)) return false; 376 | $base = basename($filename); 377 | header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); 378 | header("Content-Disposition: attachment; filename=$base"); 379 | header("Content-Length: " . filesize($filename)); 380 | header("Content-Type: $mimetype"); 381 | readfile($filename); 382 | exit(); 383 | } 384 | 385 | // Retrieves the filesize of a remote file. 386 | function remote_filesize($url, $user = null, $pw = null) 387 | { 388 | $ch = curl_init($url); 389 | curl_setopt($ch, CURLOPT_HEADER, 1); 390 | curl_setopt($ch, CURLOPT_NOBODY, 1); 391 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 392 | 393 | if(!is_null($user) && !is_null($pw)) 394 | { 395 | $headers = array('Authorization: Basic ' . base64_encode("$user:$pw")); 396 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 397 | } 398 | 399 | $head = curl_exec($ch); 400 | curl_close($ch); 401 | 402 | preg_match('/Content-Length:\s([0-9].+?)\s/', $head, $matches); 403 | 404 | return isset($matches[1]) ? $matches[1] : false; 405 | } 406 | 407 | // Outputs a filesize in human readable format. 408 | function bytes2str($val, $round = 0) 409 | { 410 | $unit = array('','K','M','G','T','P','E','Z','Y'); 411 | while($val >= 1000) 412 | { 413 | $val /= 1024; 414 | array_shift($unit); 415 | } 416 | return round($val, $round) . array_shift($unit) . 'B'; 417 | } 418 | 419 | // Tests for a valid email address and optionally tests for valid MX records, too. 420 | function valid_email($email, $test_mx = false) 421 | { 422 | list($user, $domain) = explode('@', $email); 423 | if(strlen($user) > 0 && strlen($domain) > 0) { 424 | $parts = explode('.', $domain); 425 | if($parts >= 2) { 426 | if($test_mx) { 427 | return getmxrr($domain, $mxrecords); 428 | } else { 429 | return true; 430 | } 431 | } 432 | } 433 | 434 | return false; 435 | } 436 | 437 | // Grabs the contents of a remote URL. Can perform basic authentication if un/pw are provided. 438 | function geturl($url, $username = null, $password = null) 439 | { 440 | if(function_exists('curl_init')) 441 | { 442 | $ch = curl_init(); 443 | if(!is_null($username) && !is_null($password)) { 444 | curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Basic ' . base64_encode("$username:$password"))); 445 | } 446 | curl_setopt($ch, CURLOPT_URL, $url); 447 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 448 | $html = curl_exec($ch); 449 | curl_close($ch); 450 | return $html; 451 | } 452 | elseif(ini_get('allow_url_fopen') == true) 453 | { 454 | if(!is_null($username) && !is_null($password)) 455 | $url = str_replace("://", "://$username:$password@", $url); 456 | $html = file_get_contents($url); 457 | return $html; 458 | } 459 | else 460 | { 461 | // Cannot open url. Either install curl-php or set allow_url_fopen = true in php.ini 462 | return false; 463 | } 464 | } 465 | 466 | // Returns the user's browser info. 467 | // browscap.ini must be available for this to work. 468 | // See the PHP manual for more details. 469 | function browser_info() 470 | { 471 | $info = get_browser(null, true); 472 | $browser = $info['browser'] . ' ' . $info['version']; 473 | $os = $info['platform']; 474 | $ip = $_SERVER['REMOTE_ADDR']; 475 | return array('ip' => $ip, 'browser' => $browser, 'os' => $os); 476 | } 477 | 478 | // Quick wrapper for preg_match 479 | function match($regex, $str, $i = 0) 480 | { 481 | if(preg_match($regex, $str, $match) == 1) 482 | return $match[$i]; 483 | else 484 | return false; 485 | } 486 | 487 | // Sends an HTML formatted email 488 | function send_html_mail($to, $subject, $msg, $from, $plaintext = '') 489 | { 490 | if(!is_array($to)) $to = array($to); 491 | 492 | foreach($to as $address) 493 | { 494 | $boundary = uniqid(rand(), true); 495 | 496 | $headers = "From: $from\n"; 497 | $headers .= "MIME-Version: 1.0\n"; 498 | $headers .= "Content-Type: multipart/alternative; boundary = $boundary\n"; 499 | $headers .= "This is a MIME encoded message.\n\n"; 500 | $headers .= "--$boundary\n" . 501 | "Content-Type: text/plain; charset=ISO-8859-1\n" . 502 | "Content-Transfer-Encoding: base64\n\n"; 503 | $headers .= chunk_split(base64_encode($plaintext)); 504 | $headers .= "--$boundary\n" . 505 | "Content-Type: text/html; charset=ISO-8859-1\n" . 506 | "Content-Transfer-Encoding: base64\n\n"; 507 | $headers .= chunk_split(base64_encode($msg)); 508 | $headers .= "--$boundary--\n" . 509 | 510 | mail($address, $subject, '', $headers); 511 | } 512 | } 513 | 514 | // Quick and dirty wrapper for curl scraping. 515 | function curl($url, $referer = null, $post = null) 516 | { 517 | static $tmpfile; 518 | 519 | if(!isset($tmpfile) || ($tmpfile == '')) $tmpfile = tempnam('/tmp', 'FOO'); 520 | 521 | $ch = curl_init($url); 522 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 523 | curl_setopt($ch, CURLOPT_COOKIEFILE, $tmpfile); 524 | curl_setopt($ch, CURLOPT_COOKIEJAR, $tmpfile); 525 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 526 | curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Safari/605.1.15"); 527 | // curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 528 | // curl_setopt($ch, CURLOPT_VERBOSE, 1); 529 | 530 | if(!is_null($referer)) { 531 | curl_setopt($ch, CURLOPT_REFERER, $referer); 532 | } 533 | 534 | if(!is_null($post)) { 535 | curl_setopt($ch, CURLOPT_POST, true); 536 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post); 537 | } 538 | 539 | $html = curl_exec($ch); 540 | 541 | // $last_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); 542 | return $html; 543 | } 544 | 545 | // Accepts any number of arguments and returns the first non-empty one 546 | function pick() 547 | { 548 | foreach(func_get_args() as $arg) { 549 | if(!empty($arg)) { 550 | return $arg; 551 | } 552 | } 553 | return ''; 554 | } 555 | 556 | // Secure a PHP script using basic HTTP authentication 557 | function http_auth($un, $pw, $realm = "Secured Area") 558 | { 559 | if(!(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']) && $_SERVER['PHP_AUTH_USER'] == $un && $_SERVER['PHP_AUTH_PW'] == $pw)) 560 | { 561 | header('WWW-Authenticate: Basic realm="' . $realm . '"'); 562 | header('Status: 401 Unauthorized'); 563 | exit(); 564 | } 565 | } 566 | 567 | // This is easier than typing 'echo WEB_ROOT' 568 | function WEBROOT() 569 | { 570 | echo WEB_ROOT; 571 | } 572 | 573 | // Class Autloader 574 | function spf_autoload($class_name) 575 | { 576 | $fn = DOC_ROOT . '/includes/class.' . strtolower($class_name) . '.php'; 577 | if(file_exists($fn)) 578 | { 579 | return include $fn; 580 | } 581 | } 582 | 583 | // Returns a file's mimetype based on its extension 584 | function mime_type($filename, $default = 'application/octet-stream') 585 | { 586 | $mime_types = array('323' => 'text/h323', 587 | 'acx' => 'application/internet-property-stream', 588 | 'ai' => 'application/postscript', 589 | 'aif' => 'audio/x-aiff', 590 | 'aifc' => 'audio/x-aiff', 591 | 'aiff' => 'audio/x-aiff', 592 | 'asf' => 'video/x-ms-asf', 593 | 'asr' => 'video/x-ms-asf', 594 | 'asx' => 'video/x-ms-asf', 595 | 'au' => 'audio/basic', 596 | 'avi' => 'video/x-msvideo', 597 | 'axs' => 'application/olescript', 598 | 'bas' => 'text/plain', 599 | 'bcpio' => 'application/x-bcpio', 600 | 'bin' => 'application/octet-stream', 601 | 'bmp' => 'image/bmp', 602 | 'c' => 'text/plain', 603 | 'cat' => 'application/vnd.ms-pkiseccat', 604 | 'cdf' => 'application/x-cdf', 605 | 'cer' => 'application/x-x509-ca-cert', 606 | 'class' => 'application/octet-stream', 607 | 'clp' => 'application/x-msclip', 608 | 'cmx' => 'image/x-cmx', 609 | 'cod' => 'image/cis-cod', 610 | 'cpio' => 'application/x-cpio', 611 | 'crd' => 'application/x-mscardfile', 612 | 'crl' => 'application/pkix-crl', 613 | 'crt' => 'application/x-x509-ca-cert', 614 | 'csh' => 'application/x-csh', 615 | 'css' => 'text/css', 616 | 'dcr' => 'application/x-director', 617 | 'der' => 'application/x-x509-ca-cert', 618 | 'dir' => 'application/x-director', 619 | 'dll' => 'application/x-msdownload', 620 | 'dms' => 'application/octet-stream', 621 | 'doc' => 'application/msword', 622 | 'dot' => 'application/msword', 623 | 'dvi' => 'application/x-dvi', 624 | 'dxr' => 'application/x-director', 625 | 'eps' => 'application/postscript', 626 | 'etx' => 'text/x-setext', 627 | 'evy' => 'application/envoy', 628 | 'exe' => 'application/octet-stream', 629 | 'fif' => 'application/fractals', 630 | 'flac' => 'audio/flac', 631 | 'flr' => 'x-world/x-vrml', 632 | 'gif' => 'image/gif', 633 | 'gtar' => 'application/x-gtar', 634 | 'gz' => 'application/x-gzip', 635 | 'h' => 'text/plain', 636 | 'hdf' => 'application/x-hdf', 637 | 'hlp' => 'application/winhlp', 638 | 'hqx' => 'application/mac-binhex40', 639 | 'hta' => 'application/hta', 640 | 'htc' => 'text/x-component', 641 | 'htm' => 'text/html', 642 | 'html' => 'text/html', 643 | 'htt' => 'text/webviewhtml', 644 | 'ico' => 'image/x-icon', 645 | 'ief' => 'image/ief', 646 | 'iii' => 'application/x-iphone', 647 | 'ins' => 'application/x-internet-signup', 648 | 'isp' => 'application/x-internet-signup', 649 | 'jfif' => 'image/pipeg', 650 | 'jpe' => 'image/jpeg', 651 | 'jpeg' => 'image/jpeg', 652 | 'jpg' => 'image/jpeg', 653 | 'js' => 'application/x-javascript', 654 | 'latex' => 'application/x-latex', 655 | 'lha' => 'application/octet-stream', 656 | 'lsf' => 'video/x-la-asf', 657 | 'lsx' => 'video/x-la-asf', 658 | 'lzh' => 'application/octet-stream', 659 | 'm13' => 'application/x-msmediaview', 660 | 'm14' => 'application/x-msmediaview', 661 | 'm3u' => 'audio/x-mpegurl', 662 | 'man' => 'application/x-troff-man', 663 | 'mdb' => 'application/x-msaccess', 664 | 'me' => 'application/x-troff-me', 665 | 'mht' => 'message/rfc822', 666 | 'mhtml' => 'message/rfc822', 667 | 'mid' => 'audio/mid', 668 | 'mny' => 'application/x-msmoney', 669 | 'mov' => 'video/quicktime', 670 | 'movie' => 'video/x-sgi-movie', 671 | 'mp2' => 'video/mpeg', 672 | 'mp3' => 'audio/mpeg', 673 | 'mpa' => 'video/mpeg', 674 | 'mpe' => 'video/mpeg', 675 | 'mpeg' => 'video/mpeg', 676 | 'mpg' => 'video/mpeg', 677 | 'mpp' => 'application/vnd.ms-project', 678 | 'mpv2' => 'video/mpeg', 679 | 'ms' => 'application/x-troff-ms', 680 | 'mvb' => 'application/x-msmediaview', 681 | 'nws' => 'message/rfc822', 682 | 'oda' => 'application/oda', 683 | 'oga' => 'audio/ogg', 684 | 'ogg' => 'audio/ogg', 685 | 'ogv' => 'video/ogg', 686 | 'ogx' => 'application/ogg', 687 | 'p10' => 'application/pkcs10', 688 | 'p12' => 'application/x-pkcs12', 689 | 'p7b' => 'application/x-pkcs7-certificates', 690 | 'p7c' => 'application/x-pkcs7-mime', 691 | 'p7m' => 'application/x-pkcs7-mime', 692 | 'p7r' => 'application/x-pkcs7-certreqresp', 693 | 'p7s' => 'application/x-pkcs7-signature', 694 | 'pbm' => 'image/x-portable-bitmap', 695 | 'pdf' => 'application/pdf', 696 | 'pfx' => 'application/x-pkcs12', 697 | 'pgm' => 'image/x-portable-graymap', 698 | 'pko' => 'application/ynd.ms-pkipko', 699 | 'pma' => 'application/x-perfmon', 700 | 'pmc' => 'application/x-perfmon', 701 | 'pml' => 'application/x-perfmon', 702 | 'pmr' => 'application/x-perfmon', 703 | 'pmw' => 'application/x-perfmon', 704 | 'pnm' => 'image/x-portable-anymap', 705 | 'pot' => 'application/vnd.ms-powerpoint', 706 | 'ppm' => 'image/x-portable-pixmap', 707 | 'pps' => 'application/vnd.ms-powerpoint', 708 | 'ppt' => 'application/vnd.ms-powerpoint', 709 | 'prf' => 'application/pics-rules', 710 | 'ps' => 'application/postscript', 711 | 'pub' => 'application/x-mspublisher', 712 | 'qt' => 'video/quicktime', 713 | 'ra' => 'audio/x-pn-realaudio', 714 | 'ram' => 'audio/x-pn-realaudio', 715 | 'ras' => 'image/x-cmu-raster', 716 | 'rgb' => 'image/x-rgb', 717 | 'rmi' => 'audio/mid', 718 | 'roff' => 'application/x-troff', 719 | 'rtf' => 'application/rtf', 720 | 'rtx' => 'text/richtext', 721 | 'scd' => 'application/x-msschedule', 722 | 'sct' => 'text/scriptlet', 723 | 'setpay' => 'application/set-payment-initiation', 724 | 'setreg' => 'application/set-registration-initiation', 725 | 'sh' => 'application/x-sh', 726 | 'shar' => 'application/x-shar', 727 | 'sit' => 'application/x-stuffit', 728 | 'snd' => 'audio/basic', 729 | 'spc' => 'application/x-pkcs7-certificates', 730 | 'spl' => 'application/futuresplash', 731 | 'src' => 'application/x-wais-source', 732 | 'sst' => 'application/vnd.ms-pkicertstore', 733 | 'stl' => 'application/vnd.ms-pkistl', 734 | 'stm' => 'text/html', 735 | 'svg' => "image/svg+xml", 736 | 'sv4cpio' => 'application/x-sv4cpio', 737 | 'sv4crc' => 'application/x-sv4crc', 738 | 't' => 'application/x-troff', 739 | 'tar' => 'application/x-tar', 740 | 'tcl' => 'application/x-tcl', 741 | 'tex' => 'application/x-tex', 742 | 'texi' => 'application/x-texinfo', 743 | 'texinfo' => 'application/x-texinfo', 744 | 'tgz' => 'application/x-compressed', 745 | 'tif' => 'image/tiff', 746 | 'tiff' => 'image/tiff', 747 | 'tr' => 'application/x-troff', 748 | 'trm' => 'application/x-msterminal', 749 | 'tsv' => 'text/tab-separated-values', 750 | 'txt' => 'text/plain', 751 | 'uls' => 'text/iuls', 752 | 'ustar' => 'application/x-ustar', 753 | 'vcf' => 'text/x-vcard', 754 | 'vrml' => 'x-world/x-vrml', 755 | 'wav' => 'audio/x-wav', 756 | 'wcm' => 'application/vnd.ms-works', 757 | 'wdb' => 'application/vnd.ms-works', 758 | 'wks' => 'application/vnd.ms-works', 759 | 'wmf' => 'application/x-msmetafile', 760 | 'wps' => 'application/vnd.ms-works', 761 | 'wri' => 'application/x-mswrite', 762 | 'wrl' => 'x-world/x-vrml', 763 | 'wrz' => 'x-world/x-vrml', 764 | 'xaf' => 'x-world/x-vrml', 765 | 'xbm' => 'image/x-xbitmap', 766 | 'xla' => 'application/vnd.ms-excel', 767 | 'xlc' => 'application/vnd.ms-excel', 768 | 'xlm' => 'application/vnd.ms-excel', 769 | 'xls' => 'application/vnd.ms-excel', 770 | 'xlt' => 'application/vnd.ms-excel', 771 | 'xlw' => 'application/vnd.ms-excel', 772 | 'xof' => 'x-world/x-vrml', 773 | 'xpm' => 'image/x-xpixmap', 774 | 'xwd' => 'image/x-xwindowdump', 775 | 'z' => 'application/x-compress', 776 | 'zip' => 'application/zip'); 777 | $ext = pathinfo($filename, PATHINFO_EXTENSION); 778 | return isset($mime_types[$ext]) ? $mime_types[$ext] : $default; 779 | } 780 | --------------------------------------------------------------------------------