├── README
├── index.html
├── Cache
│ ├── index.html
│ ├── drivers
│ │ ├── index.html
│ │ ├── Cache_dummy.php
│ │ ├── Cache_apc.php
│ │ ├── Cache_wincache.php
│ │ ├── Cache_file.php
│ │ ├── Cache_memcached.php
│ │ └── Cache_redis.php
│ └── Cache.php
├── Session
│ ├── index.html
│ ├── drivers
│ │ ├── index.html
│ │ ├── Session_memcached_driver.php
│ │ ├── Session_files_driver.php
│ │ ├── Session_database_driver.php
│ │ └── Session_redis_driver.php
│ ├── SessionHandlerInterface.php
│ └── Session_driver.php
├── Javascript
│ └── index.html
├── Parser.php
├── Driver.php
├── Unit_test.php
├── Encrypt.php
├── Migration.php
├── Zip.php
├── Table.php
├── Trackback.php
└── Typography.php
└── project.md
/README/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 403 Forbidden
5 |
6 |
7 |
8 | Directory access is forbidden.
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README/Cache/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 403 Forbidden
5 |
6 |
7 |
8 | Directory access is forbidden.
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README/Session/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 403 Forbidden
5 |
6 |
7 |
8 | Directory access is forbidden.
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README/Cache/drivers/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 403 Forbidden
5 |
6 |
7 |
8 | Directory access is forbidden.
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README/Javascript/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 403 Forbidden
5 |
6 |
7 |
8 | Directory access is forbidden.
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README/Session/drivers/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 403 Forbidden
5 |
6 |
7 |
8 | Directory access is forbidden.
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/project.md:
--------------------------------------------------------------------------------
1 | Github个人收集的优秀JavaScript项目
2 | - [Angular和Webpack种子文件](https://github.com/AngularClass/angular2-webpack-starter)
3 | - [Fis3面向前端的工程构建系统](https://github.com/fex-team/fis3)
4 | - [Fis3 DEMO](https://github.com/fex-team/fis3-demo)
5 | - [前端JQuery系列:入门教程 | 源码剖析 | 框架设计 | 慕课网教程](https://github.com/JsAaron/jQuery)
6 | - [avalon框架](https://github.com/RubyLouvre/avalon)
7 | - [Microsoft ChakraCore 微软的Chakra引擎](https://github.com/Microsoft/ChakraCore)
8 | - [Quintus HTML游戏引擎](https://github.com/cykod/Quintus)
9 | - [一个用node.js搭建的有趣博客](https://github.com/STRML/strml.net)
10 | - [Web前端助手--FeHelper(Chrome扩展)](https://github.com/zxlie/FeHelper)
11 | - [百度前端技术学院](https://github.com/baidu-ife/ife)
12 | - [Cheerio(node.js中的jQuery)](https://github.com/cheeriojs/cheerio)
13 | - [nodejs的一个聊天软件 类似微信](https://github.com/BryanYang/freechat)
14 | - [使用html5和node.js构建的网易云音乐](https://github.com/stkevintan/Cube)
15 | - [babel ES6转换为ES5](https://github.com/babel/babel)
16 | - [一个JS富文本编辑器](https://github.com/fex-team/ueditor)
17 | - [一个JS脑图可视化工具](https://github.com/fex-team/kityminder-core)
18 | - [一个JS写的Flappy Bird Game](https://github.com/ellisonleao/clumsy-bird)
19 | - [一个JS写的GBA模拟器](https://github.com/taisel/IodineGBA)
20 | - [SegmentFault写的Markdown解析器](https://github.com/SegmentFault/HyperDown.js)
21 | - [基于node.js的Ghost博客](https://github.com/TryGhost/Ghost)
22 | - [学习react的demos](https://github.com/ruanyf/react-demos)
23 |
--------------------------------------------------------------------------------
/README/Session/SessionHandlerInterface.php:
--------------------------------------------------------------------------------
1 | _config =& $params;
88 | }
89 |
90 | // ------------------------------------------------------------------------
91 |
92 | /**
93 | * Cookie destroy
94 | *
95 | * Internal method to force removal of a cookie by the client
96 | * when session_destroy() is called.
97 | *
98 | * @return bool
99 | */
100 | protected function _cookie_destroy()
101 | {
102 | return setcookie(
103 | $this->_config['cookie_name'],
104 | NULL,
105 | 1,
106 | $this->_config['cookie_path'],
107 | $this->_config['cookie_domain'],
108 | $this->_config['cookie_secure'],
109 | TRUE
110 | );
111 | }
112 |
113 | // ------------------------------------------------------------------------
114 |
115 | /**
116 | * Get lock
117 | *
118 | * A dummy method allowing drivers with no locking functionality
119 | * (databases other than PostgreSQL and MySQL) to act as if they
120 | * do acquire a lock.
121 | *
122 | * @param string $session_id
123 | * @return bool
124 | */
125 | protected function _get_lock($session_id)
126 | {
127 | $this->_lock = TRUE;
128 | return TRUE;
129 | }
130 |
131 | // ------------------------------------------------------------------------
132 |
133 | /**
134 | * Release lock
135 | *
136 | * @return bool
137 | */
138 | protected function _release_lock()
139 | {
140 | if ($this->_lock)
141 | {
142 | $this->_lock = FALSE;
143 | }
144 |
145 | return TRUE;
146 | }
147 |
148 | }
149 |
--------------------------------------------------------------------------------
/README/Cache/drivers/Cache_dummy.php:
--------------------------------------------------------------------------------
1 | $time + $ttl,
185 | 'mtime' => $time,
186 | 'data' => unserialize($data)
187 | );
188 | }
189 |
190 | // ------------------------------------------------------------------------
191 |
192 | /**
193 | * is_supported()
194 | *
195 | * Check to see if APC is available on this system, bail if it isn't.
196 | *
197 | * @return bool
198 | */
199 | public function is_supported()
200 | {
201 | if ( ! extension_loaded('apc') OR ! ini_get('apc.enabled'))
202 | {
203 | log_message('debug', 'The APC PHP extension must be loaded to use APC Cache.');
204 | return FALSE;
205 | }
206 |
207 | return TRUE;
208 | }
209 |
210 | }
211 |
--------------------------------------------------------------------------------
/README/Cache/drivers/Cache_wincache.php:
--------------------------------------------------------------------------------
1 | $ttl - $age,
177 | 'hitcount' => $hitcount,
178 | 'age' => $age,
179 | 'ttl' => $ttl
180 | );
181 | }
182 |
183 | return FALSE;
184 | }
185 |
186 | // ------------------------------------------------------------------------
187 |
188 | /**
189 | * is_supported()
190 | *
191 | * Check to see if WinCache is available on this system, bail if it isn't.
192 | *
193 | * @return bool
194 | */
195 | public function is_supported()
196 | {
197 | if ( ! extension_loaded('wincache') OR ! ini_get('wincache.ucenabled'))
198 | {
199 | log_message('debug', 'The Wincache PHP extension must be loaded to use Wincache Cache.');
200 | return FALSE;
201 | }
202 |
203 | return TRUE;
204 | }
205 |
206 | }
207 |
--------------------------------------------------------------------------------
/README/Parser.php:
--------------------------------------------------------------------------------
1 | CI =& get_instance();
82 | log_message('info', 'Parser Class Initialized');
83 | }
84 |
85 | // --------------------------------------------------------------------
86 |
87 | /**
88 | * Parse a template
89 | *
90 | * Parses pseudo-variables contained in the specified template view,
91 | * replacing them with the data in the second param
92 | *
93 | * @param string
94 | * @param array
95 | * @param bool
96 | * @return string
97 | */
98 | public function parse($template, $data, $return = FALSE)
99 | {
100 | $template = $this->CI->load->view($template, $data, TRUE);
101 |
102 | return $this->_parse($template, $data, $return);
103 | }
104 |
105 | // --------------------------------------------------------------------
106 |
107 | /**
108 | * Parse a String
109 | *
110 | * Parses pseudo-variables contained in the specified string,
111 | * replacing them with the data in the second param
112 | *
113 | * @param string
114 | * @param array
115 | * @param bool
116 | * @return string
117 | */
118 | public function parse_string($template, $data, $return = FALSE)
119 | {
120 | return $this->_parse($template, $data, $return);
121 | }
122 |
123 | // --------------------------------------------------------------------
124 |
125 | /**
126 | * Parse a template
127 | *
128 | * Parses pseudo-variables contained in the specified template,
129 | * replacing them with the data in the second param
130 | *
131 | * @param string
132 | * @param array
133 | * @param bool
134 | * @return string
135 | */
136 | protected function _parse($template, $data, $return = FALSE)
137 | {
138 | if ($template === '')
139 | {
140 | return FALSE;
141 | }
142 |
143 | $replace = array();
144 | foreach ($data as $key => $val)
145 | {
146 | $replace = array_merge(
147 | $replace,
148 | is_array($val)
149 | ? $this->_parse_pair($key, $val, $template)
150 | : $this->_parse_single($key, (string) $val, $template)
151 | );
152 | }
153 |
154 | unset($data);
155 | $template = strtr($template, $replace);
156 |
157 | if ($return === FALSE)
158 | {
159 | $this->CI->output->append_output($template);
160 | }
161 |
162 | return $template;
163 | }
164 |
165 | // --------------------------------------------------------------------
166 |
167 | /**
168 | * Set the left/right variable delimiters
169 | *
170 | * @param string
171 | * @param string
172 | * @return void
173 | */
174 | public function set_delimiters($l = '{', $r = '}')
175 | {
176 | $this->l_delim = $l;
177 | $this->r_delim = $r;
178 | }
179 |
180 | // --------------------------------------------------------------------
181 |
182 | /**
183 | * Parse a single key/value
184 | *
185 | * @param string
186 | * @param string
187 | * @param string
188 | * @return string
189 | */
190 | protected function _parse_single($key, $val, $string)
191 | {
192 | return array($this->l_delim.$key.$this->r_delim => (string) $val);
193 | }
194 |
195 | // --------------------------------------------------------------------
196 |
197 | /**
198 | * Parse a tag pair
199 | *
200 | * Parses tag pairs: {some_tag} string... {/some_tag}
201 | *
202 | * @param string
203 | * @param array
204 | * @param string
205 | * @return string
206 | */
207 | protected function _parse_pair($variable, $data, $string)
208 | {
209 | $replace = array();
210 | preg_match_all(
211 | '#'.preg_quote($this->l_delim.$variable.$this->r_delim).'(.+?)'.preg_quote($this->l_delim.'/'.$variable.$this->r_delim).'#s',
212 | $string,
213 | $matches,
214 | PREG_SET_ORDER
215 | );
216 |
217 | foreach ($matches as $match)
218 | {
219 | $str = '';
220 | foreach ($data as $row)
221 | {
222 | $temp = array();
223 | foreach ($row as $key => $val)
224 | {
225 | if (is_array($val))
226 | {
227 | $pair = $this->_parse_pair($key, $val, $match[1]);
228 | if ( ! empty($pair))
229 | {
230 | $temp = array_merge($temp, $pair);
231 | }
232 |
233 | continue;
234 | }
235 |
236 | $temp[$this->l_delim.$key.$this->r_delim] = $val;
237 | }
238 |
239 | $str .= strtr($match[1], $temp);
240 | }
241 |
242 | $replace[$match[0]] = $str;
243 | }
244 |
245 | return $replace;
246 | }
247 |
248 | }
249 |
--------------------------------------------------------------------------------
/README/Cache/Cache.php:
--------------------------------------------------------------------------------
1 | _adapter = $config['adapter'];
104 | isset($config['backup']) && $this->_backup_driver = $config['backup'];
105 | isset($config['key_prefix']) && $this->key_prefix = $config['key_prefix'];
106 |
107 | // If the specified adapter isn't available, check the backup.
108 | if ( ! $this->is_supported($this->_adapter))
109 | {
110 | if ( ! $this->is_supported($this->_backup_driver))
111 | {
112 | // Backup isn't supported either. Default to 'Dummy' driver.
113 | log_message('error', 'Cache adapter "'.$this->_adapter.'" and backup "'.$this->_backup_driver.'" are both unavailable. Cache is now using "Dummy" adapter.');
114 | $this->_adapter = 'dummy';
115 | }
116 | else
117 | {
118 | // Backup is supported. Set it to primary.
119 | log_message('debug', 'Cache adapter "'.$this->_adapter.'" is unavailable. Falling back to "'.$this->_backup_driver.'" backup adapter.');
120 | $this->_adapter = $this->_backup_driver;
121 | }
122 | }
123 | }
124 |
125 | // ------------------------------------------------------------------------
126 |
127 | /**
128 | * Get
129 | *
130 | * Look for a value in the cache. If it exists, return the data
131 | * if not, return FALSE
132 | *
133 | * @param string $id
134 | * @return mixed value matching $id or FALSE on failure
135 | */
136 | public function get($id)
137 | {
138 | return $this->{$this->_adapter}->get($this->key_prefix.$id);
139 | }
140 |
141 | // ------------------------------------------------------------------------
142 |
143 | /**
144 | * Cache Save
145 | *
146 | * @param string $id Cache ID
147 | * @param mixed $data Data to store
148 | * @param int $ttl Cache TTL (in seconds)
149 | * @param bool $raw Whether to store the raw value
150 | * @return bool TRUE on success, FALSE on failure
151 | */
152 | public function save($id, $data, $ttl = 60, $raw = FALSE)
153 | {
154 | return $this->{$this->_adapter}->save($this->key_prefix.$id, $data, $ttl, $raw);
155 | }
156 |
157 | // ------------------------------------------------------------------------
158 |
159 | /**
160 | * Delete from Cache
161 | *
162 | * @param string $id Cache ID
163 | * @return bool TRUE on success, FALSE on failure
164 | */
165 | public function delete($id)
166 | {
167 | return $this->{$this->_adapter}->delete($this->key_prefix.$id);
168 | }
169 |
170 | // ------------------------------------------------------------------------
171 |
172 | /**
173 | * Increment a raw value
174 | *
175 | * @param string $id Cache ID
176 | * @param int $offset Step/value to add
177 | * @return mixed New value on success or FALSE on failure
178 | */
179 | public function increment($id, $offset = 1)
180 | {
181 | return $this->{$this->_adapter}->increment($this->key_prefix.$id, $offset);
182 | }
183 |
184 | // ------------------------------------------------------------------------
185 |
186 | /**
187 | * Decrement a raw value
188 | *
189 | * @param string $id Cache ID
190 | * @param int $offset Step/value to reduce by
191 | * @return mixed New value on success or FALSE on failure
192 | */
193 | public function decrement($id, $offset = 1)
194 | {
195 | return $this->{$this->_adapter}->decrement($this->key_prefix.$id, $offset);
196 | }
197 |
198 | // ------------------------------------------------------------------------
199 |
200 | /**
201 | * Clean the cache
202 | *
203 | * @return bool TRUE on success, FALSE on failure
204 | */
205 | public function clean()
206 | {
207 | return $this->{$this->_adapter}->clean();
208 | }
209 |
210 | // ------------------------------------------------------------------------
211 |
212 | /**
213 | * Cache Info
214 | *
215 | * @param string $type = 'user' user/filehits
216 | * @return mixed array containing cache info on success OR FALSE on failure
217 | */
218 | public function cache_info($type = 'user')
219 | {
220 | return $this->{$this->_adapter}->cache_info($type);
221 | }
222 |
223 | // ------------------------------------------------------------------------
224 |
225 | /**
226 | * Get Cache Metadata
227 | *
228 | * @param string $id key to get cache metadata on
229 | * @return mixed cache item metadata
230 | */
231 | public function get_metadata($id)
232 | {
233 | return $this->{$this->_adapter}->get_metadata($this->key_prefix.$id);
234 | }
235 |
236 | // ------------------------------------------------------------------------
237 |
238 | /**
239 | * Is the requested driver supported in this environment?
240 | *
241 | * @param string $driver The driver to test
242 | * @return array
243 | */
244 | public function is_supported($driver)
245 | {
246 | static $support;
247 |
248 | if ( ! isset($support, $support[$driver]))
249 | {
250 | $support[$driver] = $this->{$driver}->is_supported();
251 | }
252 |
253 | return $support[$driver];
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/README/Cache/drivers/Cache_file.php:
--------------------------------------------------------------------------------
1 | load->helper('file');
67 | $path = $CI->config->item('cache_path');
68 | $this->_cache_path = ($path === '') ? APPPATH.'cache/' : $path;
69 | }
70 |
71 | // ------------------------------------------------------------------------
72 |
73 | /**
74 | * Fetch from cache
75 | *
76 | * @param string $id Cache ID
77 | * @return mixed Data on success, FALSE on failure
78 | */
79 | public function get($id)
80 | {
81 | $data = $this->_get($id);
82 | return is_array($data) ? $data['data'] : FALSE;
83 | }
84 |
85 | // ------------------------------------------------------------------------
86 |
87 | /**
88 | * Save into cache
89 | *
90 | * @param string $id Cache ID
91 | * @param mixed $data Data to store
92 | * @param int $ttl Time to live in seconds
93 | * @param bool $raw Whether to store the raw value (unused)
94 | * @return bool TRUE on success, FALSE on failure
95 | */
96 | public function save($id, $data, $ttl = 60, $raw = FALSE)
97 | {
98 | $contents = array(
99 | 'time' => time(),
100 | 'ttl' => $ttl,
101 | 'data' => $data
102 | );
103 |
104 | if (write_file($this->_cache_path.$id, serialize($contents)))
105 | {
106 | chmod($this->_cache_path.$id, 0640);
107 | return TRUE;
108 | }
109 |
110 | return FALSE;
111 | }
112 |
113 | // ------------------------------------------------------------------------
114 |
115 | /**
116 | * Delete from Cache
117 | *
118 | * @param mixed unique identifier of item in cache
119 | * @return bool true on success/false on failure
120 | */
121 | public function delete($id)
122 | {
123 | return file_exists($this->_cache_path.$id) ? unlink($this->_cache_path.$id) : FALSE;
124 | }
125 |
126 | // ------------------------------------------------------------------------
127 |
128 | /**
129 | * Increment a raw value
130 | *
131 | * @param string $id Cache ID
132 | * @param int $offset Step/value to add
133 | * @return New value on success, FALSE on failure
134 | */
135 | public function increment($id, $offset = 1)
136 | {
137 | $data = $this->_get($id);
138 |
139 | if ($data === FALSE)
140 | {
141 | $data = array('data' => 0, 'ttl' => 60);
142 | }
143 | elseif ( ! is_int($data['data']))
144 | {
145 | return FALSE;
146 | }
147 |
148 | $new_value = $data['data'] + $offset;
149 | return $this->save($id, $new_value, $data['ttl'])
150 | ? $new_value
151 | : FALSE;
152 | }
153 |
154 | // ------------------------------------------------------------------------
155 |
156 | /**
157 | * Decrement a raw value
158 | *
159 | * @param string $id Cache ID
160 | * @param int $offset Step/value to reduce by
161 | * @return New value on success, FALSE on failure
162 | */
163 | public function decrement($id, $offset = 1)
164 | {
165 | $data = $this->_get($id);
166 |
167 | if ($data === FALSE)
168 | {
169 | $data = array('data' => 0, 'ttl' => 60);
170 | }
171 | elseif ( ! is_int($data['data']))
172 | {
173 | return FALSE;
174 | }
175 |
176 | $new_value = $data['data'] - $offset;
177 | return $this->save($id, $new_value, $data['ttl'])
178 | ? $new_value
179 | : FALSE;
180 | }
181 |
182 | // ------------------------------------------------------------------------
183 |
184 | /**
185 | * Clean the Cache
186 | *
187 | * @return bool false on failure/true on success
188 | */
189 | public function clean()
190 | {
191 | return delete_files($this->_cache_path, FALSE, TRUE);
192 | }
193 |
194 | // ------------------------------------------------------------------------
195 |
196 | /**
197 | * Cache Info
198 | *
199 | * Not supported by file-based caching
200 | *
201 | * @param string user/filehits
202 | * @return mixed FALSE
203 | */
204 | public function cache_info($type = NULL)
205 | {
206 | return get_dir_file_info($this->_cache_path);
207 | }
208 |
209 | // ------------------------------------------------------------------------
210 |
211 | /**
212 | * Get Cache Metadata
213 | *
214 | * @param mixed key to get cache metadata on
215 | * @return mixed FALSE on failure, array on success.
216 | */
217 | public function get_metadata($id)
218 | {
219 | if ( ! file_exists($this->_cache_path.$id))
220 | {
221 | return FALSE;
222 | }
223 |
224 | $data = unserialize(file_get_contents($this->_cache_path.$id));
225 |
226 | if (is_array($data))
227 | {
228 | $mtime = filemtime($this->_cache_path.$id);
229 |
230 | if ( ! isset($data['ttl']))
231 | {
232 | return FALSE;
233 | }
234 |
235 | return array(
236 | 'expire' => $mtime + $data['ttl'],
237 | 'mtime' => $mtime
238 | );
239 | }
240 |
241 | return FALSE;
242 | }
243 |
244 | // ------------------------------------------------------------------------
245 |
246 | /**
247 | * Is supported
248 | *
249 | * In the file driver, check to see that the cache directory is indeed writable
250 | *
251 | * @return bool
252 | */
253 | public function is_supported()
254 | {
255 | return is_really_writable($this->_cache_path);
256 | }
257 |
258 | // ------------------------------------------------------------------------
259 |
260 | /**
261 | * Get all data
262 | *
263 | * Internal method to get all the relevant data about a cache item
264 | *
265 | * @param string $id Cache ID
266 | * @return mixed Data array on success or FALSE on failure
267 | */
268 | protected function _get($id)
269 | {
270 | if ( ! is_file($this->_cache_path.$id))
271 | {
272 | return FALSE;
273 | }
274 |
275 | $data = unserialize(file_get_contents($this->_cache_path.$id));
276 |
277 | if ($data['ttl'] > 0 && time() > $data['time'] + $data['ttl'])
278 | {
279 | unlink($this->_cache_path.$id);
280 | return FALSE;
281 | }
282 |
283 | return $data;
284 | }
285 |
286 | }
287 |
--------------------------------------------------------------------------------
/README/Cache/drivers/Cache_memcached.php:
--------------------------------------------------------------------------------
1 | array(
65 | 'host' => '127.0.0.1',
66 | 'port' => 11211,
67 | 'weight' => 1
68 | )
69 | );
70 |
71 | // ------------------------------------------------------------------------
72 |
73 | /**
74 | * Class constructor
75 | *
76 | * Setup Memcache(d)
77 | *
78 | * @return void
79 | */
80 | public function __construct()
81 | {
82 | // Try to load memcached server info from the config file.
83 | $CI =& get_instance();
84 | $defaults = $this->_memcache_conf['default'];
85 |
86 | if ($CI->config->load('memcached', TRUE, TRUE))
87 | {
88 | if (is_array($CI->config->config['memcached']))
89 | {
90 | $this->_memcache_conf = array();
91 |
92 | foreach ($CI->config->config['memcached'] as $name => $conf)
93 | {
94 | $this->_memcache_conf[$name] = $conf;
95 | }
96 | }
97 | }
98 |
99 | if (class_exists('Memcached', FALSE))
100 | {
101 | $this->_memcached = new Memcached();
102 | }
103 | elseif (class_exists('Memcache', FALSE))
104 | {
105 | $this->_memcached = new Memcache();
106 | }
107 | else
108 | {
109 | log_message('error', 'Cache: Failed to create Memcache(d) object; extension not loaded?');
110 | }
111 |
112 | foreach ($this->_memcache_conf as $cache_server)
113 | {
114 | isset($cache_server['hostname']) OR $cache_server['hostname'] = $defaults['host'];
115 | isset($cache_server['port']) OR $cache_server['port'] = $defaults['port'];
116 | isset($cache_server['weight']) OR $cache_server['weight'] = $defaults['weight'];
117 |
118 | if (get_class($this->_memcached) === 'Memcache')
119 | {
120 | // Third parameter is persistance and defaults to TRUE.
121 | $this->_memcached->addServer(
122 | $cache_server['hostname'],
123 | $cache_server['port'],
124 | TRUE,
125 | $cache_server['weight']
126 | );
127 | }
128 | else
129 | {
130 | $this->_memcached->addServer(
131 | $cache_server['hostname'],
132 | $cache_server['port'],
133 | $cache_server['weight']
134 | );
135 | }
136 | }
137 | }
138 |
139 | // ------------------------------------------------------------------------
140 |
141 | /**
142 | * Fetch from cache
143 | *
144 | * @param string $id Cache ID
145 | * @return mixed Data on success, FALSE on failure
146 | */
147 | public function get($id)
148 | {
149 | $data = $this->_memcached->get($id);
150 |
151 | return is_array($data) ? $data[0] : $data;
152 | }
153 |
154 | // ------------------------------------------------------------------------
155 |
156 | /**
157 | * Save
158 | *
159 | * @param string $id Cache ID
160 | * @param mixed $data Data being cached
161 | * @param int $ttl Time to live
162 | * @param bool $raw Whether to store the raw value
163 | * @return bool TRUE on success, FALSE on failure
164 | */
165 | public function save($id, $data, $ttl = 60, $raw = FALSE)
166 | {
167 | if ($raw !== TRUE)
168 | {
169 | $data = array($data, time(), $ttl);
170 | }
171 |
172 | if (get_class($this->_memcached) === 'Memcached')
173 | {
174 | return $this->_memcached->set($id, $data, $ttl);
175 | }
176 | elseif (get_class($this->_memcached) === 'Memcache')
177 | {
178 | return $this->_memcached->set($id, $data, 0, $ttl);
179 | }
180 |
181 | return FALSE;
182 | }
183 |
184 | // ------------------------------------------------------------------------
185 |
186 | /**
187 | * Delete from Cache
188 | *
189 | * @param mixed key to be deleted.
190 | * @return bool true on success, false on failure
191 | */
192 | public function delete($id)
193 | {
194 | return $this->_memcached->delete($id);
195 | }
196 |
197 | // ------------------------------------------------------------------------
198 |
199 | /**
200 | * Increment a raw value
201 | *
202 | * @param string $id Cache ID
203 | * @param int $offset Step/value to add
204 | * @return mixed New value on success or FALSE on failure
205 | */
206 | public function increment($id, $offset = 1)
207 | {
208 | return $this->_memcached->increment($id, $offset);
209 | }
210 |
211 | // ------------------------------------------------------------------------
212 |
213 | /**
214 | * Decrement a raw value
215 | *
216 | * @param string $id Cache ID
217 | * @param int $offset Step/value to reduce by
218 | * @return mixed New value on success or FALSE on failure
219 | */
220 | public function decrement($id, $offset = 1)
221 | {
222 | return $this->_memcached->decrement($id, $offset);
223 | }
224 |
225 | // ------------------------------------------------------------------------
226 |
227 | /**
228 | * Clean the Cache
229 | *
230 | * @return bool false on failure/true on success
231 | */
232 | public function clean()
233 | {
234 | return $this->_memcached->flush();
235 | }
236 |
237 | // ------------------------------------------------------------------------
238 |
239 | /**
240 | * Cache Info
241 | *
242 | * @return mixed array on success, false on failure
243 | */
244 | public function cache_info()
245 | {
246 | return $this->_memcached->getStats();
247 | }
248 |
249 | // ------------------------------------------------------------------------
250 |
251 | /**
252 | * Get Cache Metadata
253 | *
254 | * @param mixed key to get cache metadata on
255 | * @return mixed FALSE on failure, array on success.
256 | */
257 | public function get_metadata($id)
258 | {
259 | $stored = $this->_memcached->get($id);
260 |
261 | if (count($stored) !== 3)
262 | {
263 | return FALSE;
264 | }
265 |
266 | list($data, $time, $ttl) = $stored;
267 |
268 | return array(
269 | 'expire' => $time + $ttl,
270 | 'mtime' => $time,
271 | 'data' => $data
272 | );
273 | }
274 |
275 | // ------------------------------------------------------------------------
276 |
277 | /**
278 | * Is supported
279 | *
280 | * Returns FALSE if memcached is not supported on the system.
281 | * If it is, we setup the memcached object & return TRUE
282 | *
283 | * @return bool
284 | */
285 | public function is_supported()
286 | {
287 | return (extension_loaded('memcached') OR extension_loaded('memcache'));
288 | }
289 | }
290 |
--------------------------------------------------------------------------------
/README/Cache/drivers/Cache_redis.php:
--------------------------------------------------------------------------------
1 |
47 | * @link
48 | */
49 | class CI_Cache_redis extends CI_Driver
50 | {
51 | /**
52 | * Default config
53 | *
54 | * @static
55 | * @var array
56 | */
57 | protected static $_default_config = array(
58 | 'socket_type' => 'tcp',
59 | 'host' => '127.0.0.1',
60 | 'password' => NULL,
61 | 'port' => 6379,
62 | 'timeout' => 0
63 | );
64 |
65 | /**
66 | * Redis connection
67 | *
68 | * @var Redis
69 | */
70 | protected $_redis;
71 |
72 | /**
73 | * An internal cache for storing keys of serialized values.
74 | *
75 | * @var array
76 | */
77 | protected $_serialized = array();
78 |
79 | // ------------------------------------------------------------------------
80 |
81 | /**
82 | * Class constructor
83 | *
84 | * Setup Redis
85 | *
86 | * Loads Redis config file if present. Will halt execution
87 | * if a Redis connection can't be established.
88 | *
89 | * @return void
90 | * @see Redis::connect()
91 | */
92 | public function __construct()
93 | {
94 | $config = array();
95 | $CI =& get_instance();
96 |
97 | if ($CI->config->load('redis', TRUE, TRUE))
98 | {
99 | $config = $CI->config->item('redis');
100 | }
101 |
102 | $config = array_merge(self::$_default_config, $config);
103 | $this->_redis = new Redis();
104 |
105 | try
106 | {
107 | if ($config['socket_type'] === 'unix')
108 | {
109 | $success = $this->_redis->connect($config['socket']);
110 | }
111 | else // tcp socket
112 | {
113 | $success = $this->_redis->connect($config['host'], $config['port'], $config['timeout']);
114 | }
115 |
116 | if ( ! $success)
117 | {
118 | log_message('error', 'Cache: Redis connection failed. Check your configuration.');
119 | }
120 |
121 | if (isset($config['password']) && ! $this->_redis->auth($config['password']))
122 | {
123 | log_message('error', 'Cache: Redis authentication failed.');
124 | }
125 | }
126 | catch (RedisException $e)
127 | {
128 | log_message('error', 'Cache: Redis connection refused ('.$e->getMessage().')');
129 | }
130 |
131 | // Initialize the index of serialized values.
132 | $serialized = $this->_redis->sMembers('_ci_redis_serialized');
133 | empty($serialized) OR $this->_serialized = array_flip($serialized);
134 | }
135 |
136 | // ------------------------------------------------------------------------
137 |
138 | /**
139 | * Get cache
140 | *
141 | * @param string Cache ID
142 | * @return mixed
143 | */
144 | public function get($key)
145 | {
146 | $value = $this->_redis->get($key);
147 |
148 | if ($value !== FALSE && isset($this->_serialized[$key]))
149 | {
150 | return unserialize($value);
151 | }
152 |
153 | return $value;
154 | }
155 |
156 | // ------------------------------------------------------------------------
157 |
158 | /**
159 | * Save cache
160 | *
161 | * @param string $id Cache ID
162 | * @param mixed $data Data to save
163 | * @param int $ttl Time to live in seconds
164 | * @param bool $raw Whether to store the raw value (unused)
165 | * @return bool TRUE on success, FALSE on failure
166 | */
167 | public function save($id, $data, $ttl = 60, $raw = FALSE)
168 | {
169 | if (is_array($data) OR is_object($data))
170 | {
171 | if ( ! $this->_redis->sIsMember('_ci_redis_serialized', $id) && ! $this->_redis->sAdd('_ci_redis_serialized', $id))
172 | {
173 | return FALSE;
174 | }
175 |
176 | isset($this->_serialized[$id]) OR $this->_serialized[$id] = TRUE;
177 | $data = serialize($data);
178 | }
179 | elseif (isset($this->_serialized[$id]))
180 | {
181 | $this->_serialized[$id] = NULL;
182 | $this->_redis->sRemove('_ci_redis_serialized', $id);
183 | }
184 |
185 | return $this->_redis->set($id, $data, $ttl);
186 | }
187 |
188 | // ------------------------------------------------------------------------
189 |
190 | /**
191 | * Delete from cache
192 | *
193 | * @param string Cache key
194 | * @return bool
195 | */
196 | public function delete($key)
197 | {
198 | if ($this->_redis->delete($key) !== 1)
199 | {
200 | return FALSE;
201 | }
202 |
203 | if (isset($this->_serialized[$key]))
204 | {
205 | $this->_serialized[$key] = NULL;
206 | $this->_redis->sRemove('_ci_redis_serialized', $key);
207 | }
208 |
209 | return TRUE;
210 | }
211 |
212 | // ------------------------------------------------------------------------
213 |
214 | /**
215 | * Increment a raw value
216 | *
217 | * @param string $id Cache ID
218 | * @param int $offset Step/value to add
219 | * @return mixed New value on success or FALSE on failure
220 | */
221 | public function increment($id, $offset = 1)
222 | {
223 | return $this->_redis->incr($id, $offset);
224 | }
225 |
226 | // ------------------------------------------------------------------------
227 |
228 | /**
229 | * Decrement a raw value
230 | *
231 | * @param string $id Cache ID
232 | * @param int $offset Step/value to reduce by
233 | * @return mixed New value on success or FALSE on failure
234 | */
235 | public function decrement($id, $offset = 1)
236 | {
237 | return $this->_redis->decr($id, $offset);
238 | }
239 |
240 | // ------------------------------------------------------------------------
241 |
242 | /**
243 | * Clean cache
244 | *
245 | * @return bool
246 | * @see Redis::flushDB()
247 | */
248 | public function clean()
249 | {
250 | return $this->_redis->flushDB();
251 | }
252 |
253 | // ------------------------------------------------------------------------
254 |
255 | /**
256 | * Get cache driver info
257 | *
258 | * @param string Not supported in Redis.
259 | * Only included in order to offer a
260 | * consistent cache API.
261 | * @return array
262 | * @see Redis::info()
263 | */
264 | public function cache_info($type = NULL)
265 | {
266 | return $this->_redis->info();
267 | }
268 |
269 | // ------------------------------------------------------------------------
270 |
271 | /**
272 | * Get cache metadata
273 | *
274 | * @param string Cache key
275 | * @return array
276 | */
277 | public function get_metadata($key)
278 | {
279 | $value = $this->get($key);
280 |
281 | if ($value !== FALSE)
282 | {
283 | return array(
284 | 'expire' => time() + $this->_redis->ttl($key),
285 | 'data' => $value
286 | );
287 | }
288 |
289 | return FALSE;
290 | }
291 |
292 | // ------------------------------------------------------------------------
293 |
294 | /**
295 | * Check if Redis driver is supported
296 | *
297 | * @return bool
298 | */
299 | public function is_supported()
300 | {
301 | return extension_loaded('redis');
302 | }
303 |
304 | // ------------------------------------------------------------------------
305 |
306 | /**
307 | * Class destructor
308 | *
309 | * Closes the connection to Redis if present.
310 | *
311 | * @return void
312 | */
313 | public function __destruct()
314 | {
315 | if ($this->_redis)
316 | {
317 | $this->_redis->close();
318 | }
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/README/Driver.php:
--------------------------------------------------------------------------------
1 | load_driver($child);
81 | }
82 |
83 | /**
84 | * Load driver
85 | *
86 | * Separate load_driver call to support explicit driver load by library or user
87 | *
88 | * @param string Driver name (w/o parent prefix)
89 | * @return object Child class
90 | */
91 | public function load_driver($child)
92 | {
93 | // Get CodeIgniter instance and subclass prefix
94 | $prefix = config_item('subclass_prefix');
95 |
96 | if ( ! isset($this->lib_name))
97 | {
98 | // Get library name without any prefix
99 | $this->lib_name = str_replace(array('CI_', $prefix), '', get_class($this));
100 | }
101 |
102 | // The child will be prefixed with the parent lib
103 | $child_name = $this->lib_name.'_'.$child;
104 |
105 | // See if requested child is a valid driver
106 | if ( ! in_array($child, $this->valid_drivers))
107 | {
108 | // The requested driver isn't valid!
109 | $msg = 'Invalid driver requested: '.$child_name;
110 | log_message('error', $msg);
111 | show_error($msg);
112 | }
113 |
114 | // Get package paths and filename case variations to search
115 | $CI = get_instance();
116 | $paths = $CI->load->get_package_paths(TRUE);
117 |
118 | // Is there an extension?
119 | $class_name = $prefix.$child_name;
120 | $found = class_exists($class_name, FALSE);
121 | if ( ! $found)
122 | {
123 | // Check for subclass file
124 | foreach ($paths as $path)
125 | {
126 | // Does the file exist?
127 | $file = $path.'libraries/'.$this->lib_name.'/drivers/'.$prefix.$child_name.'.php';
128 | if (file_exists($file))
129 | {
130 | // Yes - require base class from BASEPATH
131 | $basepath = BASEPATH.'libraries/'.$this->lib_name.'/drivers/'.$child_name.'.php';
132 | if ( ! file_exists($basepath))
133 | {
134 | $msg = 'Unable to load the requested class: CI_'.$child_name;
135 | log_message('error', $msg);
136 | show_error($msg);
137 | }
138 |
139 | // Include both sources and mark found
140 | include_once($basepath);
141 | include_once($file);
142 | $found = TRUE;
143 | break;
144 | }
145 | }
146 | }
147 |
148 | // Do we need to search for the class?
149 | if ( ! $found)
150 | {
151 | // Use standard class name
152 | $class_name = 'CI_'.$child_name;
153 | if ( ! class_exists($class_name, FALSE))
154 | {
155 | // Check package paths
156 | foreach ($paths as $path)
157 | {
158 | // Does the file exist?
159 | $file = $path.'libraries/'.$this->lib_name.'/drivers/'.$child_name.'.php';
160 | if (file_exists($file))
161 | {
162 | // Include source
163 | include_once($file);
164 | break;
165 | }
166 | }
167 | }
168 | }
169 |
170 | // Did we finally find the class?
171 | if ( ! class_exists($class_name, FALSE))
172 | {
173 | if (class_exists($child_name, FALSE))
174 | {
175 | $class_name = $child_name;
176 | }
177 | else
178 | {
179 | $msg = 'Unable to load the requested driver: '.$class_name;
180 | log_message('error', $msg);
181 | show_error($msg);
182 | }
183 | }
184 |
185 | // Instantiate, decorate and add child
186 | $obj = new $class_name();
187 | $obj->decorate($this);
188 | $this->$child = $obj;
189 | return $this->$child;
190 | }
191 |
192 | }
193 |
194 | // --------------------------------------------------------------------------
195 |
196 | /**
197 | * CodeIgniter Driver Class
198 | *
199 | * This class enables you to create drivers for a Library based on the Driver Library.
200 | * It handles the drivers' access to the parent library
201 | *
202 | * @package CodeIgniter
203 | * @subpackage Libraries
204 | * @category Libraries
205 | * @author EllisLab Dev Team
206 | * @link
207 | */
208 | class CI_Driver {
209 |
210 | /**
211 | * Instance of the parent class
212 | *
213 | * @var object
214 | */
215 | protected $_parent;
216 |
217 | /**
218 | * List of methods in the parent class
219 | *
220 | * @var array
221 | */
222 | protected $_methods = array();
223 |
224 | /**
225 | * List of properties in the parent class
226 | *
227 | * @var array
228 | */
229 | protected $_properties = array();
230 |
231 | /**
232 | * Array of methods and properties for the parent class(es)
233 | *
234 | * @static
235 | * @var array
236 | */
237 | protected static $_reflections = array();
238 |
239 | /**
240 | * Decorate
241 | *
242 | * Decorates the child with the parent driver lib's methods and properties
243 | *
244 | * @param object
245 | * @return void
246 | */
247 | public function decorate($parent)
248 | {
249 | $this->_parent = $parent;
250 |
251 | // Lock down attributes to what is defined in the class
252 | // and speed up references in magic methods
253 |
254 | $class_name = get_class($parent);
255 |
256 | if ( ! isset(self::$_reflections[$class_name]))
257 | {
258 | $r = new ReflectionObject($parent);
259 |
260 | foreach ($r->getMethods() as $method)
261 | {
262 | if ($method->isPublic())
263 | {
264 | $this->_methods[] = $method->getName();
265 | }
266 | }
267 |
268 | foreach ($r->getProperties() as $prop)
269 | {
270 | if ($prop->isPublic())
271 | {
272 | $this->_properties[] = $prop->getName();
273 | }
274 | }
275 |
276 | self::$_reflections[$class_name] = array($this->_methods, $this->_properties);
277 | }
278 | else
279 | {
280 | list($this->_methods, $this->_properties) = self::$_reflections[$class_name];
281 | }
282 | }
283 |
284 | // --------------------------------------------------------------------
285 |
286 | /**
287 | * __call magic method
288 | *
289 | * Handles access to the parent driver library's methods
290 | *
291 | * @param string
292 | * @param array
293 | * @return mixed
294 | */
295 | public function __call($method, $args = array())
296 | {
297 | if (in_array($method, $this->_methods))
298 | {
299 | return call_user_func_array(array($this->_parent, $method), $args);
300 | }
301 |
302 | throw new BadMethodCallException('No such method: '.$method.'()');
303 | }
304 |
305 | // --------------------------------------------------------------------
306 |
307 | /**
308 | * __get magic method
309 | *
310 | * Handles reading of the parent driver library's properties
311 | *
312 | * @param string
313 | * @return mixed
314 | */
315 | public function __get($var)
316 | {
317 | if (in_array($var, $this->_properties))
318 | {
319 | return $this->_parent->$var;
320 | }
321 | }
322 |
323 | // --------------------------------------------------------------------
324 |
325 | /**
326 | * __set magic method
327 | *
328 | * Handles writing to the parent driver library's properties
329 | *
330 | * @param string
331 | * @param array
332 | * @return mixed
333 | */
334 | public function __set($var, $val)
335 | {
336 | if (in_array($var, $this->_properties))
337 | {
338 | $this->_parent->$var = $val;
339 | }
340 | }
341 |
342 | }
343 |
--------------------------------------------------------------------------------
/README/Session/drivers/Session_memcached_driver.php:
--------------------------------------------------------------------------------
1 | _config['save_path']))
85 | {
86 | log_message('error', 'Session: No Memcached save path configured.');
87 | }
88 |
89 | if ($this->_config['match_ip'] === TRUE)
90 | {
91 | $this->_key_prefix .= $_SERVER['REMOTE_ADDR'].':';
92 | }
93 | }
94 |
95 | // ------------------------------------------------------------------------
96 |
97 | /**
98 | * Open
99 | *
100 | * Sanitizes save_path and initializes connections.
101 | *
102 | * @param string $save_path Server path(s)
103 | * @param string $name Session cookie name, unused
104 | * @return bool
105 | */
106 | public function open($save_path, $name)
107 | {
108 | $this->_memcached = new Memcached();
109 | $this->_memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, TRUE); // required for touch() usage
110 | $server_list = array();
111 | foreach ($this->_memcached->getServerList() as $server)
112 | {
113 | $server_list[] = $server['host'].':'.$server['port'];
114 | }
115 |
116 | if ( ! preg_match_all('#,?([^,:]+)\:(\d{1,5})(?:\:(\d+))?#', $this->_config['save_path'], $matches, PREG_SET_ORDER))
117 | {
118 | $this->_memcached = NULL;
119 | log_message('error', 'Session: Invalid Memcached save path format: '.$this->_config['save_path']);
120 | return FALSE;
121 | }
122 |
123 | foreach ($matches as $match)
124 | {
125 | // If Memcached already has this server (or if the port is invalid), skip it
126 | if (in_array($match[1].':'.$match[2], $server_list, TRUE))
127 | {
128 | log_message('debug', 'Session: Memcached server pool already has '.$match[1].':'.$match[2]);
129 | continue;
130 | }
131 |
132 | if ( ! $this->_memcached->addServer($match[1], $match[2], isset($match[3]) ? $match[3] : 0))
133 | {
134 | log_message('error', 'Could not add '.$match[1].':'.$match[2].' to Memcached server pool.');
135 | }
136 | else
137 | {
138 | $server_list[] = $match[1].':'.$match[2];
139 | }
140 | }
141 |
142 | if (empty($server_list))
143 | {
144 | log_message('error', 'Session: Memcached server pool is empty.');
145 | return FALSE;
146 | }
147 |
148 | return TRUE;
149 | }
150 |
151 | // ------------------------------------------------------------------------
152 |
153 | /**
154 | * Read
155 | *
156 | * Reads session data and acquires a lock
157 | *
158 | * @param string $session_id Session ID
159 | * @return string Serialized session data
160 | */
161 | public function read($session_id)
162 | {
163 | if (isset($this->_memcached) && $this->_get_lock($session_id))
164 | {
165 | // Needed by write() to detect session_regenerate_id() calls
166 | $this->_session_id = $session_id;
167 |
168 | $session_data = (string) $this->_memcached->get($this->_key_prefix.$session_id);
169 | $this->_fingerprint = md5($session_data);
170 | return $session_data;
171 | }
172 |
173 | return FALSE;
174 | }
175 |
176 | // ------------------------------------------------------------------------
177 |
178 | /**
179 | * Write
180 | *
181 | * Writes (create / update) session data
182 | *
183 | * @param string $session_id Session ID
184 | * @param string $session_data Serialized session data
185 | * @return bool
186 | */
187 | public function write($session_id, $session_data)
188 | {
189 | if ( ! isset($this->_memcached))
190 | {
191 | return FALSE;
192 | }
193 | // Was the ID regenerated?
194 | elseif ($session_id !== $this->_session_id)
195 | {
196 | if ( ! $this->_release_lock() OR ! $this->_get_lock($session_id))
197 | {
198 | return FALSE;
199 | }
200 |
201 | $this->_fingerprint = md5('');
202 | $this->_session_id = $session_id;
203 | }
204 |
205 | if (isset($this->_lock_key))
206 | {
207 | $this->_memcached->replace($this->_lock_key, time(), 300);
208 | if ($this->_fingerprint !== ($fingerprint = md5($session_data)))
209 | {
210 | if ($this->_memcached->set($this->_key_prefix.$session_id, $session_data, $this->_config['expiration']))
211 | {
212 | $this->_fingerprint = $fingerprint;
213 | return TRUE;
214 | }
215 |
216 | return FALSE;
217 | }
218 |
219 | return $this->_memcached->touch($this->_key_prefix.$session_id, $this->_config['expiration']);
220 | }
221 |
222 | return FALSE;
223 | }
224 |
225 | // ------------------------------------------------------------------------
226 |
227 | /**
228 | * Close
229 | *
230 | * Releases locks and closes connection.
231 | *
232 | * @return bool
233 | */
234 | public function close()
235 | {
236 | if (isset($this->_memcached))
237 | {
238 | isset($this->_lock_key) && $this->_memcached->delete($this->_lock_key);
239 | if ( ! $this->_memcached->quit())
240 | {
241 | return FALSE;
242 | }
243 |
244 | $this->_memcached = NULL;
245 | return TRUE;
246 | }
247 |
248 | return FALSE;
249 | }
250 |
251 | // ------------------------------------------------------------------------
252 |
253 | /**
254 | * Destroy
255 | *
256 | * Destroys the current session.
257 | *
258 | * @param string $session_id Session ID
259 | * @return bool
260 | */
261 | public function destroy($session_id)
262 | {
263 | if (isset($this->_memcached, $this->_lock_key))
264 | {
265 | $this->_memcached->delete($this->_key_prefix.$session_id);
266 | return $this->_cookie_destroy();
267 | }
268 |
269 | return FALSE;
270 | }
271 |
272 | // ------------------------------------------------------------------------
273 |
274 | /**
275 | * Garbage Collector
276 | *
277 | * Deletes expired sessions
278 | *
279 | * @param int $maxlifetime Maximum lifetime of sessions
280 | * @return bool
281 | */
282 | public function gc($maxlifetime)
283 | {
284 | // Not necessary, Memcached takes care of that.
285 | return TRUE;
286 | }
287 |
288 | // ------------------------------------------------------------------------
289 |
290 | /**
291 | * Get lock
292 | *
293 | * Acquires an (emulated) lock.
294 | *
295 | * @param string $session_id Session ID
296 | * @return bool
297 | */
298 | protected function _get_lock($session_id)
299 | {
300 | if (isset($this->_lock_key))
301 | {
302 | return $this->_memcached->replace($this->_lock_key, time(), 300);
303 | }
304 |
305 | // 30 attempts to obtain a lock, in case another request already has it
306 | $lock_key = $this->_key_prefix.$session_id.':lock';
307 | $attempt = 0;
308 | do
309 | {
310 | if ($this->_memcached->get($lock_key))
311 | {
312 | sleep(1);
313 | continue;
314 | }
315 |
316 | if ( ! $this->_memcached->set($lock_key, time(), 300))
317 | {
318 | log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id);
319 | return FALSE;
320 | }
321 |
322 | $this->_lock_key = $lock_key;
323 | break;
324 | }
325 | while (++$attempt < 30);
326 |
327 | if ($attempt === 30)
328 | {
329 | log_message('error', 'Session: Unable to obtain lock for '.$this->_key_prefix.$session_id.' after 30 attempts, aborting.');
330 | return FALSE;
331 | }
332 |
333 | $this->_lock = TRUE;
334 | return TRUE;
335 | }
336 |
337 | // ------------------------------------------------------------------------
338 |
339 | /**
340 | * Release lock
341 | *
342 | * Releases a previously acquired lock
343 | *
344 | * @return bool
345 | */
346 | protected function _release_lock()
347 | {
348 | if (isset($this->_memcached, $this->_lock_key) && $this->_lock)
349 | {
350 | if ( ! $this->_memcached->delete($this->_lock_key) && $this->_memcached->getResultCode() !== Memcached::RES_NOTFOUND)
351 | {
352 | log_message('error', 'Session: Error while trying to free lock for '.$this->_lock_key);
353 | return FALSE;
354 | }
355 |
356 | $this->_lock_key = NULL;
357 | $this->_lock = FALSE;
358 | }
359 |
360 | return TRUE;
361 | }
362 |
363 | }
364 |
--------------------------------------------------------------------------------
/README/Unit_test.php:
--------------------------------------------------------------------------------
1 | _test_items_visible = $items;
132 | }
133 | }
134 |
135 | // --------------------------------------------------------------------
136 |
137 | /**
138 | * Run the tests
139 | *
140 | * Runs the supplied tests
141 | *
142 | * @param mixed $test
143 | * @param mixed $expected
144 | * @param string $test_name
145 | * @param string $notes
146 | * @return string
147 | */
148 | public function run($test, $expected = TRUE, $test_name = 'undefined', $notes = '')
149 | {
150 | if ($this->active === FALSE)
151 | {
152 | return FALSE;
153 | }
154 |
155 | if (in_array($expected, array('is_object', 'is_string', 'is_bool', 'is_true', 'is_false', 'is_int', 'is_numeric', 'is_float', 'is_double', 'is_array', 'is_null', 'is_resource'), TRUE))
156 | {
157 | $expected = str_replace('is_double', 'is_float', $expected);
158 | $result = $expected($test);
159 | $extype = str_replace(array('true', 'false'), 'bool', str_replace('is_', '', $expected));
160 | }
161 | else
162 | {
163 | $result = ($this->strict === TRUE) ? ($test === $expected) : ($test == $expected);
164 | $extype = gettype($expected);
165 | }
166 |
167 | $back = $this->_backtrace();
168 |
169 | $report = array (
170 | 'test_name' => $test_name,
171 | 'test_datatype' => gettype($test),
172 | 'res_datatype' => $extype,
173 | 'result' => ($result === TRUE) ? 'passed' : 'failed',
174 | 'file' => $back['file'],
175 | 'line' => $back['line'],
176 | 'notes' => $notes
177 | );
178 |
179 | $this->results[] = $report;
180 |
181 | return $this->report($this->result(array($report)));
182 | }
183 |
184 | // --------------------------------------------------------------------
185 |
186 | /**
187 | * Generate a report
188 | *
189 | * Displays a table with the test data
190 | *
191 | * @param array $result
192 | * @return string
193 | */
194 | public function report($result = array())
195 | {
196 | if (count($result) === 0)
197 | {
198 | $result = $this->result();
199 | }
200 |
201 | $CI =& get_instance();
202 | $CI->load->language('unit_test');
203 |
204 | $this->_parse_template();
205 |
206 | $r = '';
207 | foreach ($result as $res)
208 | {
209 | $table = '';
210 |
211 | foreach ($res as $key => $val)
212 | {
213 | if ($key === $CI->lang->line('ut_result'))
214 | {
215 | if ($val === $CI->lang->line('ut_passed'))
216 | {
217 | $val = ''.$val.'';
218 | }
219 | elseif ($val === $CI->lang->line('ut_failed'))
220 | {
221 | $val = ''.$val.'';
222 | }
223 | }
224 |
225 | $table .= str_replace(array('{item}', '{result}'), array($key, $val), $this->_template_rows);
226 | }
227 |
228 | $r .= str_replace('{rows}', $table, $this->_template);
229 | }
230 |
231 | return $r;
232 | }
233 |
234 | // --------------------------------------------------------------------
235 |
236 | /**
237 | * Use strict comparison
238 | *
239 | * Causes the evaluation to use === rather than ==
240 | *
241 | * @param bool $state
242 | * @return void
243 | */
244 | public function use_strict($state = TRUE)
245 | {
246 | $this->strict = (bool) $state;
247 | }
248 |
249 | // --------------------------------------------------------------------
250 |
251 | /**
252 | * Make Unit testing active
253 | *
254 | * Enables/disables unit testing
255 | *
256 | * @param bool
257 | * @return void
258 | */
259 | public function active($state = TRUE)
260 | {
261 | $this->active = (bool) $state;
262 | }
263 |
264 | // --------------------------------------------------------------------
265 |
266 | /**
267 | * Result Array
268 | *
269 | * Returns the raw result data
270 | *
271 | * @param array $results
272 | * @return array
273 | */
274 | public function result($results = array())
275 | {
276 | $CI =& get_instance();
277 | $CI->load->language('unit_test');
278 |
279 | if (count($results) === 0)
280 | {
281 | $results = $this->results;
282 | }
283 |
284 | $retval = array();
285 | foreach ($results as $result)
286 | {
287 | $temp = array();
288 | foreach ($result as $key => $val)
289 | {
290 | if ( ! in_array($key, $this->_test_items_visible))
291 | {
292 | continue;
293 | }
294 | elseif (in_array($key, array('test_name', 'test_datatype', 'test_res_datatype', 'result'), TRUE))
295 | {
296 | if (FALSE !== ($line = $CI->lang->line(strtolower('ut_'.$val), FALSE)))
297 | {
298 | $val = $line;
299 | }
300 | }
301 |
302 | $temp[$CI->lang->line('ut_'.$key, FALSE)] = $val;
303 | }
304 |
305 | $retval[] = $temp;
306 | }
307 |
308 | return $retval;
309 | }
310 |
311 | // --------------------------------------------------------------------
312 |
313 | /**
314 | * Set the template
315 | *
316 | * This lets us set the template to be used to display results
317 | *
318 | * @param string
319 | * @return void
320 | */
321 | public function set_template($template)
322 | {
323 | $this->_template = $template;
324 | }
325 |
326 | // --------------------------------------------------------------------
327 |
328 | /**
329 | * Generate a backtrace
330 | *
331 | * This lets us show file names and line numbers
332 | *
333 | * @return array
334 | */
335 | protected function _backtrace()
336 | {
337 | $back = debug_backtrace();
338 | return array(
339 | 'file' => (isset($back[1]['file']) ? $back[1]['file'] : ''),
340 | 'line' => (isset($back[1]['line']) ? $back[1]['line'] : '')
341 | );
342 | }
343 |
344 | // --------------------------------------------------------------------
345 |
346 | /**
347 | * Get Default Template
348 | *
349 | * @return string
350 | */
351 | protected function _default_template()
352 | {
353 | $this->_template = "\n".'";
354 |
355 | $this->_template_rows = "\n\t\n\t\t".'| {item} | '
356 | ."\n\t\t".'{result} | '."\n\t
";
357 | }
358 |
359 | // --------------------------------------------------------------------
360 |
361 | /**
362 | * Parse Template
363 | *
364 | * Harvests the data within the template {pseudo-variables}
365 | *
366 | * @return void
367 | */
368 | protected function _parse_template()
369 | {
370 | if ($this->_template_rows !== NULL)
371 | {
372 | return;
373 | }
374 |
375 | if ($this->_template === NULL OR ! preg_match('/\{rows\}(.*?)\{\/rows\}/si', $this->_template, $match))
376 | {
377 | $this->_default_template();
378 | return;
379 | }
380 |
381 | $this->_template_rows = $match[1];
382 | $this->_template = str_replace($match[0], '{rows}', $this->_template);
383 | }
384 |
385 | }
386 |
387 | /**
388 | * Helper function to test boolean TRUE
389 | *
390 | * @param mixed $test
391 | * @return bool
392 | */
393 | function is_true($test)
394 | {
395 | return ($test === TRUE);
396 | }
397 |
398 | /**
399 | * Helper function to test boolean FALSE
400 | *
401 | * @param mixed $test
402 | * @return bool
403 | */
404 | function is_false($test)
405 | {
406 | return ($test === FALSE);
407 | }
408 |
--------------------------------------------------------------------------------
/README/Session/drivers/Session_files_driver.php:
--------------------------------------------------------------------------------
1 | _config['save_path']))
92 | {
93 | $this->_config['save_path'] = rtrim($this->_config['save_path'], '/\\');
94 | ini_set('session.save_path', $this->_config['save_path']);
95 | }
96 | else
97 | {
98 | $this->_config['save_path'] = rtrim(ini_get('session.save_path'), '/\\');
99 | }
100 | }
101 |
102 | // ------------------------------------------------------------------------
103 |
104 | /**
105 | * Open
106 | *
107 | * Sanitizes the save_path directory.
108 | *
109 | * @param string $save_path Path to session files' directory
110 | * @param string $name Session cookie name
111 | * @return bool
112 | */
113 | public function open($save_path, $name)
114 | {
115 | if ( ! is_dir($save_path))
116 | {
117 | if ( ! mkdir($save_path, 0700, TRUE))
118 | {
119 | throw new Exception("Session: Configured save path '".$this->_config['save_path']."' is not a directory, doesn't exist or cannot be created.");
120 | }
121 | }
122 | elseif ( ! is_writable($save_path))
123 | {
124 | throw new Exception("Session: Configured save path '".$this->_config['save_path']."' is not writable by the PHP process.");
125 | }
126 |
127 | $this->_config['save_path'] = $save_path;
128 | $this->_file_path = $this->_config['save_path'].DIRECTORY_SEPARATOR
129 | .$name // we'll use the session cookie name as a prefix to avoid collisions
130 | .($this->_config['match_ip'] ? md5($_SERVER['REMOTE_ADDR']) : '');
131 |
132 | return TRUE;
133 | }
134 |
135 | // ------------------------------------------------------------------------
136 |
137 | /**
138 | * Read
139 | *
140 | * Reads session data and acquires a lock
141 | *
142 | * @param string $session_id Session ID
143 | * @return string Serialized session data
144 | */
145 | public function read($session_id)
146 | {
147 | // This might seem weird, but PHP 5.6 introduces session_reset(),
148 | // which re-reads session data
149 | if ($this->_file_handle === NULL)
150 | {
151 | // Just using fopen() with 'c+b' mode would be perfect, but it is only
152 | // available since PHP 5.2.6 and we have to set permissions for new files,
153 | // so we'd have to hack around this ...
154 | if (($this->_file_new = ! file_exists($this->_file_path.$session_id)) === TRUE)
155 | {
156 | if (($this->_file_handle = fopen($this->_file_path.$session_id, 'w+b')) === FALSE)
157 | {
158 | log_message('error', "Session: File '".$this->_file_path.$session_id."' doesn't exist and cannot be created.");
159 | return FALSE;
160 | }
161 | }
162 | elseif (($this->_file_handle = fopen($this->_file_path.$session_id, 'r+b')) === FALSE)
163 | {
164 | log_message('error', "Session: Unable to open file '".$this->_file_path.$session_id."'.");
165 | return FALSE;
166 | }
167 |
168 | if (flock($this->_file_handle, LOCK_EX) === FALSE)
169 | {
170 | log_message('error', "Session: Unable to obtain lock for file '".$this->_file_path.$session_id."'.");
171 | fclose($this->_file_handle);
172 | $this->_file_handle = NULL;
173 | return FALSE;
174 | }
175 |
176 | // Needed by write() to detect session_regenerate_id() calls
177 | $this->_session_id = $session_id;
178 |
179 | if ($this->_file_new)
180 | {
181 | chmod($this->_file_path.$session_id, 0600);
182 | $this->_fingerprint = md5('');
183 | return '';
184 | }
185 | }
186 | else
187 | {
188 | rewind($this->_file_handle);
189 | }
190 |
191 | $session_data = '';
192 | for ($read = 0, $length = filesize($this->_file_path.$session_id); $read < $length; $read += strlen($buffer))
193 | {
194 | if (($buffer = fread($this->_file_handle, $length - $read)) === FALSE)
195 | {
196 | break;
197 | }
198 |
199 | $session_data .= $buffer;
200 | }
201 |
202 | $this->_fingerprint = md5($session_data);
203 | return $session_data;
204 | }
205 |
206 | // ------------------------------------------------------------------------
207 |
208 | /**
209 | * Write
210 | *
211 | * Writes (create / update) session data
212 | *
213 | * @param string $session_id Session ID
214 | * @param string $session_data Serialized session data
215 | * @return bool
216 | */
217 | public function write($session_id, $session_data)
218 | {
219 | // If the two IDs don't match, we have a session_regenerate_id() call
220 | // and we need to close the old handle and open a new one
221 | if ($session_id !== $this->_session_id && ( ! $this->close() OR $this->read($session_id) === FALSE))
222 | {
223 | return FALSE;
224 | }
225 |
226 | if ( ! is_resource($this->_file_handle))
227 | {
228 | return FALSE;
229 | }
230 | elseif ($this->_fingerprint === md5($session_data))
231 | {
232 | return ($this->_file_new)
233 | ? TRUE
234 | : touch($this->_file_path.$session_id);
235 | }
236 |
237 | if ( ! $this->_file_new)
238 | {
239 | ftruncate($this->_file_handle, 0);
240 | rewind($this->_file_handle);
241 | }
242 |
243 | if (($length = strlen($session_data)) > 0)
244 | {
245 | for ($written = 0; $written < $length; $written += $result)
246 | {
247 | if (($result = fwrite($this->_file_handle, substr($session_data, $written))) === FALSE)
248 | {
249 | break;
250 | }
251 | }
252 |
253 | if ( ! is_int($result))
254 | {
255 | $this->_fingerprint = md5(substr($session_data, 0, $written));
256 | log_message('error', 'Session: Unable to write data.');
257 | return FALSE;
258 | }
259 | }
260 |
261 | $this->_fingerprint = md5($session_data);
262 | return TRUE;
263 | }
264 |
265 | // ------------------------------------------------------------------------
266 |
267 | /**
268 | * Close
269 | *
270 | * Releases locks and closes file descriptor.
271 | *
272 | * @return bool
273 | */
274 | public function close()
275 | {
276 | if (is_resource($this->_file_handle))
277 | {
278 | flock($this->_file_handle, LOCK_UN);
279 | fclose($this->_file_handle);
280 |
281 | $this->_file_handle = $this->_file_new = $this->_session_id = NULL;
282 | return TRUE;
283 | }
284 |
285 | return TRUE;
286 | }
287 |
288 | // ------------------------------------------------------------------------
289 |
290 | /**
291 | * Destroy
292 | *
293 | * Destroys the current session.
294 | *
295 | * @param string $session_id Session ID
296 | * @return bool
297 | */
298 | public function destroy($session_id)
299 | {
300 | if ($this->close())
301 | {
302 | return file_exists($this->_file_path.$session_id)
303 | ? (unlink($this->_file_path.$session_id) && $this->_cookie_destroy())
304 | : TRUE;
305 | }
306 | elseif ($this->_file_path !== NULL)
307 | {
308 | clearstatcache();
309 | return file_exists($this->_file_path.$session_id)
310 | ? (unlink($this->_file_path.$session_id) && $this->_cookie_destroy())
311 | : TRUE;
312 | }
313 |
314 | return FALSE;
315 | }
316 |
317 | // ------------------------------------------------------------------------
318 |
319 | /**
320 | * Garbage Collector
321 | *
322 | * Deletes expired sessions
323 | *
324 | * @param int $maxlifetime Maximum lifetime of sessions
325 | * @return bool
326 | */
327 | public function gc($maxlifetime)
328 | {
329 | if ( ! is_dir($this->_config['save_path']) OR ($directory = opendir($this->_config['save_path'])) === FALSE)
330 | {
331 | log_message('debug', "Session: Garbage collector couldn't list files under directory '".$this->_config['save_path']."'.");
332 | return FALSE;
333 | }
334 |
335 | $ts = time() - $maxlifetime;
336 |
337 | $pattern = sprintf(
338 | '/^%s[0-9a-f]{%d}$/',
339 | preg_quote($this->_config['cookie_name'], '/'),
340 | ($this->_config['match_ip'] === TRUE ? 72 : 40)
341 | );
342 |
343 | while (($file = readdir($directory)) !== FALSE)
344 | {
345 | // If the filename doesn't match this pattern, it's either not a session file or is not ours
346 | if ( ! preg_match($pattern, $file)
347 | OR ! is_file($this->_config['save_path'].DIRECTORY_SEPARATOR.$file)
348 | OR ($mtime = filemtime($this->_config['save_path'].DIRECTORY_SEPARATOR.$file)) === FALSE
349 | OR $mtime > $ts)
350 | {
351 | continue;
352 | }
353 |
354 | unlink($this->_config['save_path'].DIRECTORY_SEPARATOR.$file);
355 | }
356 |
357 | closedir($directory);
358 |
359 | return TRUE;
360 | }
361 |
362 | }
363 |
--------------------------------------------------------------------------------
/README/Session/drivers/Session_database_driver.php:
--------------------------------------------------------------------------------
1 | db) OR $CI->load->database();
86 | $this->_db = $CI->db;
87 |
88 | if ( ! $this->_db instanceof CI_DB_query_builder)
89 | {
90 | throw new Exception('Query Builder not enabled for the configured database. Aborting.');
91 | }
92 | elseif ($this->_db->pconnect)
93 | {
94 | throw new Exception('Configured database connection is persistent. Aborting.');
95 | }
96 | elseif ($this->_db->cache_on)
97 | {
98 | throw new Exception('Configured database connection has cache enabled. Aborting.');
99 | }
100 |
101 | $db_driver = $this->_db->dbdriver.(empty($this->_db->subdriver) ? '' : '_'.$this->_db->subdriver);
102 | if (strpos($db_driver, 'mysql') !== FALSE)
103 | {
104 | $this->_platform = 'mysql';
105 | }
106 | elseif (in_array($db_driver, array('postgre', 'pdo_pgsql'), TRUE))
107 | {
108 | $this->_platform = 'postgre';
109 | }
110 |
111 | // Note: BC work-around for the old 'sess_table_name' setting, should be removed in the future.
112 | isset($this->_config['save_path']) OR $this->_config['save_path'] = config_item('sess_table_name');
113 | }
114 |
115 | // ------------------------------------------------------------------------
116 |
117 | /**
118 | * Open
119 | *
120 | * Initializes the database connection
121 | *
122 | * @param string $save_path Table name
123 | * @param string $name Session cookie name, unused
124 | * @return bool
125 | */
126 | public function open($save_path, $name)
127 | {
128 | return empty($this->_db->conn_id)
129 | ? (bool) $this->_db->db_connect()
130 | : TRUE;
131 | }
132 |
133 | // ------------------------------------------------------------------------
134 |
135 | /**
136 | * Read
137 | *
138 | * Reads session data and acquires a lock
139 | *
140 | * @param string $session_id Session ID
141 | * @return string Serialized session data
142 | */
143 | public function read($session_id)
144 | {
145 | if ($this->_get_lock($session_id) !== FALSE)
146 | {
147 | // Needed by write() to detect session_regenerate_id() calls
148 | $this->_session_id = $session_id;
149 |
150 | $this->_db
151 | ->select('data')
152 | ->from($this->_config['save_path'])
153 | ->where('id', $session_id);
154 |
155 | if ($this->_config['match_ip'])
156 | {
157 | $this->_db->where('ip_address', $_SERVER['REMOTE_ADDR']);
158 | }
159 |
160 | if (($result = $this->_db->get()->row()) === NULL)
161 | {
162 | $this->_fingerprint = md5('');
163 | return '';
164 | }
165 |
166 | // PostgreSQL's variant of a BLOB datatype is Bytea, which is a
167 | // PITA to work with, so we use base64-encoded data in a TEXT
168 | // field instead.
169 | $result = ($this->_platform === 'postgre')
170 | ? base64_decode(rtrim($result->data))
171 | : $result->data;
172 |
173 | $this->_fingerprint = md5($result);
174 | $this->_row_exists = TRUE;
175 | return $result;
176 | }
177 |
178 | $this->_fingerprint = md5('');
179 | return '';
180 | }
181 |
182 | // ------------------------------------------------------------------------
183 |
184 | /**
185 | * Write
186 | *
187 | * Writes (create / update) session data
188 | *
189 | * @param string $session_id Session ID
190 | * @param string $session_data Serialized session data
191 | * @return bool
192 | */
193 | public function write($session_id, $session_data)
194 | {
195 | // Was the ID regenerated?
196 | if ($session_id !== $this->_session_id)
197 | {
198 | if ( ! $this->_release_lock() OR ! $this->_get_lock($session_id))
199 | {
200 | return FALSE;
201 | }
202 |
203 | $this->_row_exists = FALSE;
204 | $this->_session_id = $session_id;
205 | }
206 | elseif ($this->_lock === FALSE)
207 | {
208 | return FALSE;
209 | }
210 |
211 | if ($this->_row_exists === FALSE)
212 | {
213 | $insert_data = array(
214 | 'id' => $session_id,
215 | 'ip_address' => $_SERVER['REMOTE_ADDR'],
216 | 'timestamp' => time(),
217 | 'data' => ($this->_platform === 'postgre' ? base64_encode($session_data) : $session_data)
218 | );
219 |
220 | if ($this->_db->insert($this->_config['save_path'], $insert_data))
221 | {
222 | $this->_fingerprint = md5($session_data);
223 | return $this->_row_exists = TRUE;
224 | }
225 |
226 | return FALSE;
227 | }
228 |
229 | $this->_db->where('id', $session_id);
230 | if ($this->_config['match_ip'])
231 | {
232 | $this->_db->where('ip_address', $_SERVER['REMOTE_ADDR']);
233 | }
234 |
235 | $update_data = array('timestamp' => time());
236 | if ($this->_fingerprint !== md5($session_data))
237 | {
238 | $update_data['data'] = ($this->_platform === 'postgre')
239 | ? base64_encode($session_data)
240 | : $session_data;
241 | }
242 |
243 | if ($this->_db->update($this->_config['save_path'], $update_data))
244 | {
245 | $this->_fingerprint = md5($session_data);
246 | return TRUE;
247 | }
248 |
249 | return FALSE;
250 | }
251 |
252 | // ------------------------------------------------------------------------
253 |
254 | /**
255 | * Close
256 | *
257 | * Releases locks
258 | *
259 | * @return bool
260 | */
261 | public function close()
262 | {
263 | return ($this->_lock)
264 | ? $this->_release_lock()
265 | : TRUE;
266 | }
267 |
268 | // ------------------------------------------------------------------------
269 |
270 | /**
271 | * Destroy
272 | *
273 | * Destroys the current session.
274 | *
275 | * @param string $session_id Session ID
276 | * @return bool
277 | */
278 | public function destroy($session_id)
279 | {
280 | if ($this->_lock)
281 | {
282 | $this->_db->where('id', $session_id);
283 | if ($this->_config['match_ip'])
284 | {
285 | $this->_db->where('ip_address', $_SERVER['REMOTE_ADDR']);
286 | }
287 |
288 | return $this->_db->delete($this->_config['save_path'])
289 | ? ($this->close() && $this->_cookie_destroy())
290 | : FALSE;
291 | }
292 |
293 | return ($this->close() && $this->_cookie_destroy());
294 | }
295 |
296 | // ------------------------------------------------------------------------
297 |
298 | /**
299 | * Garbage Collector
300 | *
301 | * Deletes expired sessions
302 | *
303 | * @param int $maxlifetime Maximum lifetime of sessions
304 | * @return bool
305 | */
306 | public function gc($maxlifetime)
307 | {
308 | return $this->_db->delete($this->_config['save_path'], 'timestamp < '.(time() - $maxlifetime));
309 | }
310 |
311 | // ------------------------------------------------------------------------
312 |
313 | /**
314 | * Get lock
315 | *
316 | * Acquires a lock, depending on the underlying platform.
317 | *
318 | * @param string $session_id Session ID
319 | * @return bool
320 | */
321 | protected function _get_lock($session_id)
322 | {
323 | if ($this->_platform === 'mysql')
324 | {
325 | $arg = $session_id.($this->_config['match_ip'] ? '_'.$_SERVER['REMOTE_ADDR'] : '');
326 | if ($this->_db->query("SELECT GET_LOCK('".$arg."', 300) AS ci_session_lock")->row()->ci_session_lock)
327 | {
328 | $this->_lock = $arg;
329 | return TRUE;
330 | }
331 |
332 | return FALSE;
333 | }
334 | elseif ($this->_platform === 'postgre')
335 | {
336 | $arg = "hashtext('".$session_id."')".($this->_config['match_ip'] ? ", hashtext('".$_SERVER['REMOTE_ADDR']."')" : '');
337 | if ($this->_db->simple_query('SELECT pg_advisory_lock('.$arg.')'))
338 | {
339 | $this->_lock = $arg;
340 | return TRUE;
341 | }
342 |
343 | return FALSE;
344 | }
345 |
346 | return parent::_get_lock($session_id);
347 | }
348 |
349 | // ------------------------------------------------------------------------
350 |
351 | /**
352 | * Release lock
353 | *
354 | * Releases a previously acquired lock
355 | *
356 | * @return bool
357 | */
358 | protected function _release_lock()
359 | {
360 | if ( ! $this->_lock)
361 | {
362 | return TRUE;
363 | }
364 |
365 | if ($this->_platform === 'mysql')
366 | {
367 | if ($this->_db->query("SELECT RELEASE_LOCK('".$this->_lock."') AS ci_session_lock")->row()->ci_session_lock)
368 | {
369 | $this->_lock = FALSE;
370 | return TRUE;
371 | }
372 |
373 | return FALSE;
374 | }
375 | elseif ($this->_platform === 'postgre')
376 | {
377 | if ($this->_db->simple_query('SELECT pg_advisory_unlock('.$this->_lock.')'))
378 | {
379 | $this->_lock = FALSE;
380 | return TRUE;
381 | }
382 |
383 | return FALSE;
384 | }
385 |
386 | return parent::_release_lock();
387 | }
388 |
389 | }
390 |
--------------------------------------------------------------------------------
/README/Session/drivers/Session_redis_driver.php:
--------------------------------------------------------------------------------
1 | _config['save_path']))
85 | {
86 | log_message('error', 'Session: No Redis save path configured.');
87 | }
88 | elseif (preg_match('#(?:tcp://)?([^:?]+)(?:\:(\d+))?(\?.+)?#', $this->_config['save_path'], $matches))
89 | {
90 | isset($matches[3]) OR $matches[3] = ''; // Just to avoid undefined index notices below
91 | $this->_config['save_path'] = array(
92 | 'host' => $matches[1],
93 | 'port' => empty($matches[2]) ? NULL : $matches[2],
94 | 'password' => preg_match('#auth=([^\s&]+)#', $matches[3], $match) ? $match[1] : NULL,
95 | 'database' => preg_match('#database=(\d+)#', $matches[3], $match) ? (int) $match[1] : NULL,
96 | 'timeout' => preg_match('#timeout=(\d+\.\d+)#', $matches[3], $match) ? (float) $match[1] : NULL
97 | );
98 |
99 | preg_match('#prefix=([^\s&]+)#', $matches[3], $match) && $this->_key_prefix = $match[1];
100 | }
101 | else
102 | {
103 | log_message('error', 'Session: Invalid Redis save path format: '.$this->_config['save_path']);
104 | }
105 |
106 | if ($this->_config['match_ip'] === TRUE)
107 | {
108 | $this->_key_prefix .= $_SERVER['REMOTE_ADDR'].':';
109 | }
110 | }
111 |
112 | // ------------------------------------------------------------------------
113 |
114 | /**
115 | * Open
116 | *
117 | * Sanitizes save_path and initializes connection.
118 | *
119 | * @param string $save_path Server path
120 | * @param string $name Session cookie name, unused
121 | * @return bool
122 | */
123 | public function open($save_path, $name)
124 | {
125 | if (empty($this->_config['save_path']))
126 | {
127 | return FALSE;
128 | }
129 |
130 | $redis = new Redis();
131 | if ( ! $redis->connect($this->_config['save_path']['host'], $this->_config['save_path']['port'], $this->_config['save_path']['timeout']))
132 | {
133 | log_message('error', 'Session: Unable to connect to Redis with the configured settings.');
134 | }
135 | elseif (isset($this->_config['save_path']['password']) && ! $redis->auth($this->_config['save_path']['password']))
136 | {
137 | log_message('error', 'Session: Unable to authenticate to Redis instance.');
138 | }
139 | elseif (isset($this->_config['save_path']['database']) && ! $redis->select($this->_config['save_path']['database']))
140 | {
141 | log_message('error', 'Session: Unable to select Redis database with index '.$this->_config['save_path']['database']);
142 | }
143 | else
144 | {
145 | $this->_redis = $redis;
146 | return TRUE;
147 | }
148 |
149 | return FALSE;
150 | }
151 |
152 | // ------------------------------------------------------------------------
153 |
154 | /**
155 | * Read
156 | *
157 | * Reads session data and acquires a lock
158 | *
159 | * @param string $session_id Session ID
160 | * @return string Serialized session data
161 | */
162 | public function read($session_id)
163 | {
164 | if (isset($this->_redis) && $this->_get_lock($session_id))
165 | {
166 | // Needed by write() to detect session_regenerate_id() calls
167 | $this->_session_id = $session_id;
168 |
169 | $session_data = (string) $this->_redis->get($this->_key_prefix.$session_id);
170 | $this->_fingerprint = md5($session_data);
171 | return $session_data;
172 | }
173 |
174 | return FALSE;
175 | }
176 |
177 | // ------------------------------------------------------------------------
178 |
179 | /**
180 | * Write
181 | *
182 | * Writes (create / update) session data
183 | *
184 | * @param string $session_id Session ID
185 | * @param string $session_data Serialized session data
186 | * @return bool
187 | */
188 | public function write($session_id, $session_data)
189 | {
190 | if ( ! isset($this->_redis))
191 | {
192 | return FALSE;
193 | }
194 | // Was the ID regenerated?
195 | elseif ($session_id !== $this->_session_id)
196 | {
197 | if ( ! $this->_release_lock() OR ! $this->_get_lock($session_id))
198 | {
199 | return FALSE;
200 | }
201 |
202 | $this->_fingerprint = md5('');
203 | $this->_session_id = $session_id;
204 | }
205 |
206 | if (isset($this->_lock_key))
207 | {
208 | $this->_redis->setTimeout($this->_lock_key, 300);
209 | if ($this->_fingerprint !== ($fingerprint = md5($session_data)))
210 | {
211 | if ($this->_redis->set($this->_key_prefix.$session_id, $session_data, $this->_config['expiration']))
212 | {
213 | $this->_fingerprint = $fingerprint;
214 | return TRUE;
215 | }
216 |
217 | return FALSE;
218 | }
219 |
220 | return $this->_redis->setTimeout($this->_key_prefix.$session_id, $this->_config['expiration']);
221 | }
222 |
223 | return FALSE;
224 | }
225 |
226 | // ------------------------------------------------------------------------
227 |
228 | /**
229 | * Close
230 | *
231 | * Releases locks and closes connection.
232 | *
233 | * @return bool
234 | */
235 | public function close()
236 | {
237 | if (isset($this->_redis))
238 | {
239 | try {
240 | if ($this->_redis->ping() === '+PONG')
241 | {
242 | isset($this->_lock_key) && $this->_redis->delete($this->_lock_key);
243 | if ( ! $this->_redis->close())
244 | {
245 | return FALSE;
246 | }
247 | }
248 | }
249 | catch (RedisException $e)
250 | {
251 | log_message('error', 'Session: Got RedisException on close(): '.$e->getMessage());
252 | }
253 |
254 | $this->_redis = NULL;
255 | return TRUE;
256 | }
257 |
258 | return TRUE;
259 | }
260 |
261 | // ------------------------------------------------------------------------
262 |
263 | /**
264 | * Destroy
265 | *
266 | * Destroys the current session.
267 | *
268 | * @param string $session_id Session ID
269 | * @return bool
270 | */
271 | public function destroy($session_id)
272 | {
273 | if (isset($this->_redis, $this->_lock_key))
274 | {
275 | if (($result = $this->_redis->delete($this->_key_prefix.$session_id)) !== 1)
276 | {
277 | log_message('debug', 'Session: Redis::delete() expected to return 1, got '.var_export($result, TRUE).' instead.');
278 | }
279 |
280 | return $this->_cookie_destroy();
281 | }
282 |
283 | return FALSE;
284 | }
285 |
286 | // ------------------------------------------------------------------------
287 |
288 | /**
289 | * Garbage Collector
290 | *
291 | * Deletes expired sessions
292 | *
293 | * @param int $maxlifetime Maximum lifetime of sessions
294 | * @return bool
295 | */
296 | public function gc($maxlifetime)
297 | {
298 | // Not necessary, Redis takes care of that.
299 | return TRUE;
300 | }
301 |
302 | // ------------------------------------------------------------------------
303 |
304 | /**
305 | * Get lock
306 | *
307 | * Acquires an (emulated) lock.
308 | *
309 | * @param string $session_id Session ID
310 | * @return bool
311 | */
312 | protected function _get_lock($session_id)
313 | {
314 | if (isset($this->_lock_key))
315 | {
316 | return $this->_redis->setTimeout($this->_lock_key, 300);
317 | }
318 |
319 | // 30 attempts to obtain a lock, in case another request already has it
320 | $lock_key = $this->_key_prefix.$session_id.':lock';
321 | $attempt = 0;
322 | do
323 | {
324 | if (($ttl = $this->_redis->ttl($lock_key)) > 0)
325 | {
326 | sleep(1);
327 | continue;
328 | }
329 |
330 | if ( ! $this->_redis->setex($lock_key, 300, time()))
331 | {
332 | log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id);
333 | return FALSE;
334 | }
335 |
336 | $this->_lock_key = $lock_key;
337 | break;
338 | }
339 | while (++$attempt < 30);
340 |
341 | if ($attempt === 30)
342 | {
343 | log_message('error', 'Session: Unable to obtain lock for '.$this->_key_prefix.$session_id.' after 30 attempts, aborting.');
344 | return FALSE;
345 | }
346 | elseif ($ttl === -1)
347 | {
348 | log_message('debug', 'Session: Lock for '.$this->_key_prefix.$session_id.' had no TTL, overriding.');
349 | }
350 |
351 | $this->_lock = TRUE;
352 | return TRUE;
353 | }
354 |
355 | // ------------------------------------------------------------------------
356 |
357 | /**
358 | * Release lock
359 | *
360 | * Releases a previously acquired lock
361 | *
362 | * @return bool
363 | */
364 | protected function _release_lock()
365 | {
366 | if (isset($this->_redis, $this->_lock_key) && $this->_lock)
367 | {
368 | if ( ! $this->_redis->delete($this->_lock_key))
369 | {
370 | log_message('error', 'Session: Error while trying to free lock for '.$this->_lock_key);
371 | return FALSE;
372 | }
373 |
374 | $this->_lock_key = NULL;
375 | $this->_lock = FALSE;
376 | }
377 |
378 | return TRUE;
379 | }
380 |
381 | }
382 |
--------------------------------------------------------------------------------
/README/Encrypt.php:
--------------------------------------------------------------------------------
1 | _mcrypt_exists = function_exists('mcrypt_encrypt')) === FALSE)
96 | {
97 | show_error('The Encrypt library requires the Mcrypt extension.');
98 | }
99 |
100 | log_message('info', 'Encrypt Class Initialized');
101 | }
102 |
103 | // --------------------------------------------------------------------
104 |
105 | /**
106 | * Fetch the encryption key
107 | *
108 | * Returns it as MD5 in order to have an exact-length 128 bit key.
109 | * Mcrypt is sensitive to keys that are not the correct length
110 | *
111 | * @param string
112 | * @return string
113 | */
114 | public function get_key($key = '')
115 | {
116 | if ($key === '')
117 | {
118 | if ($this->encryption_key !== '')
119 | {
120 | return $this->encryption_key;
121 | }
122 |
123 | $key = config_item('encryption_key');
124 |
125 | if ( ! strlen($key))
126 | {
127 | show_error('In order to use the encryption class requires that you set an encryption key in your config file.');
128 | }
129 | }
130 |
131 | return md5($key);
132 | }
133 |
134 | // --------------------------------------------------------------------
135 |
136 | /**
137 | * Set the encryption key
138 | *
139 | * @param string
140 | * @return CI_Encrypt
141 | */
142 | public function set_key($key = '')
143 | {
144 | $this->encryption_key = $key;
145 | return $this;
146 | }
147 |
148 | // --------------------------------------------------------------------
149 |
150 | /**
151 | * Encode
152 | *
153 | * Encodes the message string using bitwise XOR encoding.
154 | * The key is combined with a random hash, and then it
155 | * too gets converted using XOR. The whole thing is then run
156 | * through mcrypt using the randomized key. The end result
157 | * is a double-encrypted message string that is randomized
158 | * with each call to this function, even if the supplied
159 | * message and key are the same.
160 | *
161 | * @param string the string to encode
162 | * @param string the key
163 | * @return string
164 | */
165 | public function encode($string, $key = '')
166 | {
167 | return base64_encode($this->mcrypt_encode($string, $this->get_key($key)));
168 | }
169 |
170 | // --------------------------------------------------------------------
171 |
172 | /**
173 | * Decode
174 | *
175 | * Reverses the above process
176 | *
177 | * @param string
178 | * @param string
179 | * @return string
180 | */
181 | public function decode($string, $key = '')
182 | {
183 | if (preg_match('/[^a-zA-Z0-9\/\+=]/', $string) OR base64_encode(base64_decode($string)) !== $string)
184 | {
185 | return FALSE;
186 | }
187 |
188 | return $this->mcrypt_decode(base64_decode($string), $this->get_key($key));
189 | }
190 |
191 | // --------------------------------------------------------------------
192 |
193 | /**
194 | * Encode from Legacy
195 | *
196 | * Takes an encoded string from the original Encryption class algorithms and
197 | * returns a newly encoded string using the improved method added in 2.0.0
198 | * This allows for backwards compatibility and a method to transition to the
199 | * new encryption algorithms.
200 | *
201 | * For more details, see http://codeigniter.com/user_guide/installation/upgrade_200.html#encryption
202 | *
203 | * @param string
204 | * @param int (mcrypt mode constant)
205 | * @param string
206 | * @return string
207 | */
208 | public function encode_from_legacy($string, $legacy_mode = MCRYPT_MODE_ECB, $key = '')
209 | {
210 | if (preg_match('/[^a-zA-Z0-9\/\+=]/', $string))
211 | {
212 | return FALSE;
213 | }
214 |
215 | // decode it first
216 | // set mode temporarily to what it was when string was encoded with the legacy
217 | // algorithm - typically MCRYPT_MODE_ECB
218 | $current_mode = $this->_get_mode();
219 | $this->set_mode($legacy_mode);
220 |
221 | $key = $this->get_key($key);
222 | $dec = base64_decode($string);
223 | if (($dec = $this->mcrypt_decode($dec, $key)) === FALSE)
224 | {
225 | $this->set_mode($current_mode);
226 | return FALSE;
227 | }
228 |
229 | $dec = $this->_xor_decode($dec, $key);
230 |
231 | // set the mcrypt mode back to what it should be, typically MCRYPT_MODE_CBC
232 | $this->set_mode($current_mode);
233 |
234 | // and re-encode
235 | return base64_encode($this->mcrypt_encode($dec, $key));
236 | }
237 |
238 | // --------------------------------------------------------------------
239 |
240 | /**
241 | * XOR Decode
242 | *
243 | * Takes an encoded string and key as input and generates the
244 | * plain-text original message
245 | *
246 | * @param string
247 | * @param string
248 | * @return string
249 | */
250 | protected function _xor_decode($string, $key)
251 | {
252 | $string = $this->_xor_merge($string, $key);
253 |
254 | $dec = '';
255 | for ($i = 0, $l = strlen($string); $i < $l; $i++)
256 | {
257 | $dec .= ($string[$i++] ^ $string[$i]);
258 | }
259 |
260 | return $dec;
261 | }
262 |
263 | // --------------------------------------------------------------------
264 |
265 | /**
266 | * XOR key + string Combiner
267 | *
268 | * Takes a string and key as input and computes the difference using XOR
269 | *
270 | * @param string
271 | * @param string
272 | * @return string
273 | */
274 | protected function _xor_merge($string, $key)
275 | {
276 | $hash = $this->hash($key);
277 | $str = '';
278 | for ($i = 0, $ls = strlen($string), $lh = strlen($hash); $i < $ls; $i++)
279 | {
280 | $str .= $string[$i] ^ $hash[($i % $lh)];
281 | }
282 |
283 | return $str;
284 | }
285 |
286 | // --------------------------------------------------------------------
287 |
288 | /**
289 | * Encrypt using Mcrypt
290 | *
291 | * @param string
292 | * @param string
293 | * @return string
294 | */
295 | public function mcrypt_encode($data, $key)
296 | {
297 | $init_size = mcrypt_get_iv_size($this->_get_cipher(), $this->_get_mode());
298 | $init_vect = mcrypt_create_iv($init_size, MCRYPT_RAND);
299 | return $this->_add_cipher_noise($init_vect.mcrypt_encrypt($this->_get_cipher(), $key, $data, $this->_get_mode(), $init_vect), $key);
300 | }
301 |
302 | // --------------------------------------------------------------------
303 |
304 | /**
305 | * Decrypt using Mcrypt
306 | *
307 | * @param string
308 | * @param string
309 | * @return string
310 | */
311 | public function mcrypt_decode($data, $key)
312 | {
313 | $data = $this->_remove_cipher_noise($data, $key);
314 | $init_size = mcrypt_get_iv_size($this->_get_cipher(), $this->_get_mode());
315 |
316 | if ($init_size > strlen($data))
317 | {
318 | return FALSE;
319 | }
320 |
321 | $init_vect = substr($data, 0, $init_size);
322 | $data = substr($data, $init_size);
323 | return rtrim(mcrypt_decrypt($this->_get_cipher(), $key, $data, $this->_get_mode(), $init_vect), "\0");
324 | }
325 |
326 | // --------------------------------------------------------------------
327 |
328 | /**
329 | * Adds permuted noise to the IV + encrypted data to protect
330 | * against Man-in-the-middle attacks on CBC mode ciphers
331 | * http://www.ciphersbyritter.com/GLOSSARY.HTM#IV
332 | *
333 | * @param string
334 | * @param string
335 | * @return string
336 | */
337 | protected function _add_cipher_noise($data, $key)
338 | {
339 | $key = $this->hash($key);
340 | $str = '';
341 |
342 | for ($i = 0, $j = 0, $ld = strlen($data), $lk = strlen($key); $i < $ld; ++$i, ++$j)
343 | {
344 | if ($j >= $lk)
345 | {
346 | $j = 0;
347 | }
348 |
349 | $str .= chr((ord($data[$i]) + ord($key[$j])) % 256);
350 | }
351 |
352 | return $str;
353 | }
354 |
355 | // --------------------------------------------------------------------
356 |
357 | /**
358 | * Removes permuted noise from the IV + encrypted data, reversing
359 | * _add_cipher_noise()
360 | *
361 | * Function description
362 | *
363 | * @param string $data
364 | * @param string $key
365 | * @return string
366 | */
367 | protected function _remove_cipher_noise($data, $key)
368 | {
369 | $key = $this->hash($key);
370 | $str = '';
371 |
372 | for ($i = 0, $j = 0, $ld = strlen($data), $lk = strlen($key); $i < $ld; ++$i, ++$j)
373 | {
374 | if ($j >= $lk)
375 | {
376 | $j = 0;
377 | }
378 |
379 | $temp = ord($data[$i]) - ord($key[$j]);
380 |
381 | if ($temp < 0)
382 | {
383 | $temp += 256;
384 | }
385 |
386 | $str .= chr($temp);
387 | }
388 |
389 | return $str;
390 | }
391 |
392 | // --------------------------------------------------------------------
393 |
394 | /**
395 | * Set the Mcrypt Cipher
396 | *
397 | * @param int
398 | * @return CI_Encrypt
399 | */
400 | public function set_cipher($cipher)
401 | {
402 | $this->_mcrypt_cipher = $cipher;
403 | return $this;
404 | }
405 |
406 | // --------------------------------------------------------------------
407 |
408 | /**
409 | * Set the Mcrypt Mode
410 | *
411 | * @param int
412 | * @return CI_Encrypt
413 | */
414 | public function set_mode($mode)
415 | {
416 | $this->_mcrypt_mode = $mode;
417 | return $this;
418 | }
419 |
420 | // --------------------------------------------------------------------
421 |
422 | /**
423 | * Get Mcrypt cipher Value
424 | *
425 | * @return int
426 | */
427 | protected function _get_cipher()
428 | {
429 | if ($this->_mcrypt_cipher === NULL)
430 | {
431 | return $this->_mcrypt_cipher = MCRYPT_RIJNDAEL_256;
432 | }
433 |
434 | return $this->_mcrypt_cipher;
435 | }
436 |
437 | // --------------------------------------------------------------------
438 |
439 | /**
440 | * Get Mcrypt Mode Value
441 | *
442 | * @return int
443 | */
444 | protected function _get_mode()
445 | {
446 | if ($this->_mcrypt_mode === NULL)
447 | {
448 | return $this->_mcrypt_mode = MCRYPT_MODE_CBC;
449 | }
450 |
451 | return $this->_mcrypt_mode;
452 | }
453 |
454 | // --------------------------------------------------------------------
455 |
456 | /**
457 | * Set the Hash type
458 | *
459 | * @param string
460 | * @return void
461 | */
462 | public function set_hash($type = 'sha1')
463 | {
464 | $this->_hash_type = in_array($type, hash_algos()) ? $type : 'sha1';
465 | }
466 |
467 | // --------------------------------------------------------------------
468 |
469 | /**
470 | * Hash encode a string
471 | *
472 | * @param string
473 | * @return string
474 | */
475 | public function hash($str)
476 | {
477 | return hash($this->_hash_type, $str);
478 | }
479 |
480 | }
481 |
--------------------------------------------------------------------------------
/README/Migration.php:
--------------------------------------------------------------------------------
1 | $val)
125 | {
126 | $this->{'_'.$key} = $val;
127 | }
128 |
129 | log_message('info', 'Migrations Class Initialized');
130 |
131 | // Are they trying to use migrations while it is disabled?
132 | if ($this->_migration_enabled !== TRUE)
133 | {
134 | show_error('Migrations has been loaded but is disabled or set up incorrectly.');
135 | }
136 |
137 | // If not set, set it
138 | $this->_migration_path !== '' OR $this->_migration_path = APPPATH.'migrations/';
139 |
140 | // Add trailing slash if not set
141 | $this->_migration_path = rtrim($this->_migration_path, '/').'/';
142 |
143 | // Load migration language
144 | $this->lang->load('migration');
145 |
146 | // They'll probably be using dbforge
147 | $this->load->dbforge();
148 |
149 | // Make sure the migration table name was set.
150 | if (empty($this->_migration_table))
151 | {
152 | show_error('Migrations configuration file (migration.php) must have "migration_table" set.');
153 | }
154 |
155 | // Migration basename regex
156 | $this->_migration_regex = ($this->_migration_type === 'timestamp')
157 | ? '/^\d{14}_(\w+)$/'
158 | : '/^\d{3}_(\w+)$/';
159 |
160 | // Make sure a valid migration numbering type was set.
161 | if ( ! in_array($this->_migration_type, array('sequential', 'timestamp')))
162 | {
163 | show_error('An invalid migration numbering type was specified: '.$this->_migration_type);
164 | }
165 |
166 | // If the migrations table is missing, make it
167 | if ( ! $this->db->table_exists($this->_migration_table))
168 | {
169 | $this->dbforge->add_field(array(
170 | 'version' => array('type' => 'BIGINT', 'constraint' => 20),
171 | ));
172 |
173 | $this->dbforge->create_table($this->_migration_table, TRUE);
174 |
175 | $this->db->insert($this->_migration_table, array('version' => 0));
176 | }
177 |
178 | // Do we auto migrate to the latest migration?
179 | if ($this->_migration_auto_latest === TRUE && ! $this->latest())
180 | {
181 | show_error($this->error_string());
182 | }
183 | }
184 |
185 | // --------------------------------------------------------------------
186 |
187 | /**
188 | * Migrate to a schema version
189 | *
190 | * Calls each migration step required to get to the schema version of
191 | * choice
192 | *
193 | * @param string $target_version Target schema version
194 | * @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure
195 | */
196 | public function version($target_version)
197 | {
198 | // Note: We use strings, so that timestamp versions work on 32-bit systems
199 | $current_version = $this->_get_version();
200 |
201 | if ($this->_migration_type === 'sequential')
202 | {
203 | $target_version = sprintf('%03d', $target_version);
204 | }
205 | else
206 | {
207 | $target_version = (string) $target_version;
208 | }
209 |
210 | $migrations = $this->find_migrations();
211 |
212 | if ($target_version > 0 && ! isset($migrations[$target_version]))
213 | {
214 | $this->_error_string = sprintf($this->lang->line('migration_not_found'), $target_version);
215 | return FALSE;
216 | }
217 |
218 | if ($target_version > $current_version)
219 | {
220 | // Moving Up
221 | $method = 'up';
222 | }
223 | else
224 | {
225 | // Moving Down, apply in reverse order
226 | $method = 'down';
227 | krsort($migrations);
228 | }
229 |
230 | if (empty($migrations))
231 | {
232 | return TRUE;
233 | }
234 |
235 | $previous = FALSE;
236 |
237 | // Validate all available migrations, and run the ones within our target range
238 | foreach ($migrations as $number => $file)
239 | {
240 | // Check for sequence gaps
241 | if ($this->_migration_type === 'sequential' && $previous !== FALSE && abs($number - $previous) > 1)
242 | {
243 | $this->_error_string = sprintf($this->lang->line('migration_sequence_gap'), $number);
244 | return FALSE;
245 | }
246 |
247 | include_once($file);
248 | $class = 'Migration_'.ucfirst(strtolower($this->_get_migration_name(basename($file, '.php'))));
249 |
250 | // Validate the migration file structure
251 | if ( ! class_exists($class, FALSE))
252 | {
253 | $this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class);
254 | return FALSE;
255 | }
256 |
257 | $previous = $number;
258 |
259 | // Run migrations that are inside the target range
260 | if (
261 | ($method === 'up' && $number > $current_version && $number <= $target_version) OR
262 | ($method === 'down' && $number <= $current_version && $number > $target_version)
263 | )
264 | {
265 | $instance = new $class();
266 | if ( ! is_callable(array($instance, $method)))
267 | {
268 | $this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class);
269 | return FALSE;
270 | }
271 |
272 | log_message('debug', 'Migrating '.$method.' from version '.$current_version.' to version '.$number);
273 | call_user_func(array($instance, $method));
274 | $current_version = $number;
275 | $this->_update_version($current_version);
276 | }
277 | }
278 |
279 | // This is necessary when moving down, since the the last migration applied
280 | // will be the down() method for the next migration up from the target
281 | if ($current_version <> $target_version)
282 | {
283 | $current_version = $target_version;
284 | $this->_update_version($current_version);
285 | }
286 |
287 | log_message('debug', 'Finished migrating to '.$current_version);
288 |
289 | return $current_version;
290 | }
291 |
292 | // --------------------------------------------------------------------
293 |
294 | /**
295 | * Sets the schema to the latest migration
296 | *
297 | * @return mixed Current version string on success, FALSE on failure
298 | */
299 | public function latest()
300 | {
301 | $migrations = $this->find_migrations();
302 |
303 | if (empty($migrations))
304 | {
305 | $this->_error_string = $this->lang->line('migration_none_found');
306 | return FALSE;
307 | }
308 |
309 | $last_migration = basename(end($migrations));
310 |
311 | // Calculate the last migration step from existing migration
312 | // filenames and proceed to the standard version migration
313 | return $this->version($this->_get_migration_number($last_migration));
314 | }
315 |
316 | // --------------------------------------------------------------------
317 |
318 | /**
319 | * Sets the schema to the migration version set in config
320 | *
321 | * @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure
322 | */
323 | public function current()
324 | {
325 | return $this->version($this->_migration_version);
326 | }
327 |
328 | // --------------------------------------------------------------------
329 |
330 | /**
331 | * Error string
332 | *
333 | * @return string Error message returned as a string
334 | */
335 | public function error_string()
336 | {
337 | return $this->_error_string;
338 | }
339 |
340 | // --------------------------------------------------------------------
341 |
342 | /**
343 | * Retrieves list of available migration scripts
344 | *
345 | * @return array list of migration file paths sorted by version
346 | */
347 | public function find_migrations()
348 | {
349 | $migrations = array();
350 |
351 | // Load all *_*.php files in the migrations path
352 | foreach (glob($this->_migration_path.'*_*.php') as $file)
353 | {
354 | $name = basename($file, '.php');
355 |
356 | // Filter out non-migration files
357 | if (preg_match($this->_migration_regex, $name))
358 | {
359 | $number = $this->_get_migration_number($name);
360 |
361 | // There cannot be duplicate migration numbers
362 | if (isset($migrations[$number]))
363 | {
364 | $this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $number);
365 | show_error($this->_error_string);
366 | }
367 |
368 | $migrations[$number] = $file;
369 | }
370 | }
371 |
372 | ksort($migrations);
373 | return $migrations;
374 | }
375 |
376 | // --------------------------------------------------------------------
377 |
378 | /**
379 | * Extracts the migration number from a filename
380 | *
381 | * @param string $migration
382 | * @return string Numeric portion of a migration filename
383 | */
384 | protected function _get_migration_number($migration)
385 | {
386 | return sscanf($migration, '%[0-9]+', $number)
387 | ? $number : '0';
388 | }
389 |
390 | // --------------------------------------------------------------------
391 |
392 | /**
393 | * Extracts the migration class name from a filename
394 | *
395 | * @param string $migration
396 | * @return string text portion of a migration filename
397 | */
398 | protected function _get_migration_name($migration)
399 | {
400 | $parts = explode('_', $migration);
401 | array_shift($parts);
402 | return implode('_', $parts);
403 | }
404 |
405 | // --------------------------------------------------------------------
406 |
407 | /**
408 | * Retrieves current schema version
409 | *
410 | * @return string Current migration version
411 | */
412 | protected function _get_version()
413 | {
414 | $row = $this->db->select('version')->get($this->_migration_table)->row();
415 | return $row ? $row->version : '0';
416 | }
417 |
418 | // --------------------------------------------------------------------
419 |
420 | /**
421 | * Stores the current schema version
422 | *
423 | * @param string $migration Migration reached
424 | * @return void
425 | */
426 | protected function _update_version($migration)
427 | {
428 | $this->db->update($this->_migration_table, array(
429 | 'version' => $migration
430 | ));
431 | }
432 |
433 | // --------------------------------------------------------------------
434 |
435 | /**
436 | * Enable the use of CI super-global
437 | *
438 | * @param string $var
439 | * @return mixed
440 | */
441 | public function __get($var)
442 | {
443 | return get_instance()->$var;
444 | }
445 |
446 | }
447 |
--------------------------------------------------------------------------------
/README/Zip.php:
--------------------------------------------------------------------------------
1 | now = time();
116 | log_message('info', 'Zip Compression Class Initialized');
117 | }
118 |
119 | // --------------------------------------------------------------------
120 |
121 | /**
122 | * Add Directory
123 | *
124 | * Lets you add a virtual directory into which you can place files.
125 | *
126 | * @param mixed $directory the directory name. Can be string or array
127 | * @return void
128 | */
129 | public function add_dir($directory)
130 | {
131 | foreach ((array) $directory as $dir)
132 | {
133 | if ( ! preg_match('|.+/$|', $dir))
134 | {
135 | $dir .= '/';
136 | }
137 |
138 | $dir_time = $this->_get_mod_time($dir);
139 | $this->_add_dir($dir, $dir_time['file_mtime'], $dir_time['file_mdate']);
140 | }
141 | }
142 |
143 | // --------------------------------------------------------------------
144 |
145 | /**
146 | * Get file/directory modification time
147 | *
148 | * If this is a newly created file/dir, we will set the time to 'now'
149 | *
150 | * @param string $dir path to file
151 | * @return array filemtime/filemdate
152 | */
153 | protected function _get_mod_time($dir)
154 | {
155 | // filemtime() may return false, but raises an error for non-existing files
156 | $date = file_exists($dir) ? getdate(filemtime($dir)) : getdate($this->now);
157 |
158 | return array(
159 | 'file_mtime' => ($date['hours'] << 11) + ($date['minutes'] << 5) + $date['seconds'] / 2,
160 | 'file_mdate' => (($date['year'] - 1980) << 9) + ($date['mon'] << 5) + $date['mday']
161 | );
162 | }
163 |
164 | // --------------------------------------------------------------------
165 |
166 | /**
167 | * Add Directory
168 | *
169 | * @param string $dir the directory name
170 | * @param int $file_mtime
171 | * @param int $file_mdate
172 | * @return void
173 | */
174 | protected function _add_dir($dir, $file_mtime, $file_mdate)
175 | {
176 | $dir = str_replace('\\', '/', $dir);
177 |
178 | $this->zipdata .=
179 | "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00"
180 | .pack('v', $file_mtime)
181 | .pack('v', $file_mdate)
182 | .pack('V', 0) // crc32
183 | .pack('V', 0) // compressed filesize
184 | .pack('V', 0) // uncompressed filesize
185 | .pack('v', strlen($dir)) // length of pathname
186 | .pack('v', 0) // extra field length
187 | .$dir
188 | // below is "data descriptor" segment
189 | .pack('V', 0) // crc32
190 | .pack('V', 0) // compressed filesize
191 | .pack('V', 0); // uncompressed filesize
192 |
193 | $this->directory .=
194 | "\x50\x4b\x01\x02\x00\x00\x0a\x00\x00\x00\x00\x00"
195 | .pack('v', $file_mtime)
196 | .pack('v', $file_mdate)
197 | .pack('V',0) // crc32
198 | .pack('V',0) // compressed filesize
199 | .pack('V',0) // uncompressed filesize
200 | .pack('v', strlen($dir)) // length of pathname
201 | .pack('v', 0) // extra field length
202 | .pack('v', 0) // file comment length
203 | .pack('v', 0) // disk number start
204 | .pack('v', 0) // internal file attributes
205 | .pack('V', 16) // external file attributes - 'directory' bit set
206 | .pack('V', $this->offset) // relative offset of local header
207 | .$dir;
208 |
209 | $this->offset = strlen($this->zipdata);
210 | $this->entries++;
211 | }
212 |
213 | // --------------------------------------------------------------------
214 |
215 | /**
216 | * Add Data to Zip
217 | *
218 | * Lets you add files to the archive. If the path is included
219 | * in the filename it will be placed within a directory. Make
220 | * sure you use add_dir() first to create the folder.
221 | *
222 | * @param mixed $filepath A single filepath or an array of file => data pairs
223 | * @param string $data Single file contents
224 | * @return void
225 | */
226 | public function add_data($filepath, $data = NULL)
227 | {
228 | if (is_array($filepath))
229 | {
230 | foreach ($filepath as $path => $data)
231 | {
232 | $file_data = $this->_get_mod_time($path);
233 | $this->_add_data($path, $data, $file_data['file_mtime'], $file_data['file_mdate']);
234 | }
235 | }
236 | else
237 | {
238 | $file_data = $this->_get_mod_time($filepath);
239 | $this->_add_data($filepath, $data, $file_data['file_mtime'], $file_data['file_mdate']);
240 | }
241 | }
242 |
243 | // --------------------------------------------------------------------
244 |
245 | /**
246 | * Add Data to Zip
247 | *
248 | * @param string $filepath the file name/path
249 | * @param string $data the data to be encoded
250 | * @param int $file_mtime
251 | * @param int $file_mdate
252 | * @return void
253 | */
254 | protected function _add_data($filepath, $data, $file_mtime, $file_mdate)
255 | {
256 | $filepath = str_replace('\\', '/', $filepath);
257 |
258 | $uncompressed_size = strlen($data);
259 | $crc32 = crc32($data);
260 | $gzdata = substr(gzcompress($data, $this->compression_level), 2, -4);
261 | $compressed_size = strlen($gzdata);
262 |
263 | $this->zipdata .=
264 | "\x50\x4b\x03\x04\x14\x00\x00\x00\x08\x00"
265 | .pack('v', $file_mtime)
266 | .pack('v', $file_mdate)
267 | .pack('V', $crc32)
268 | .pack('V', $compressed_size)
269 | .pack('V', $uncompressed_size)
270 | .pack('v', strlen($filepath)) // length of filename
271 | .pack('v', 0) // extra field length
272 | .$filepath
273 | .$gzdata; // "file data" segment
274 |
275 | $this->directory .=
276 | "\x50\x4b\x01\x02\x00\x00\x14\x00\x00\x00\x08\x00"
277 | .pack('v', $file_mtime)
278 | .pack('v', $file_mdate)
279 | .pack('V', $crc32)
280 | .pack('V', $compressed_size)
281 | .pack('V', $uncompressed_size)
282 | .pack('v', strlen($filepath)) // length of filename
283 | .pack('v', 0) // extra field length
284 | .pack('v', 0) // file comment length
285 | .pack('v', 0) // disk number start
286 | .pack('v', 0) // internal file attributes
287 | .pack('V', 32) // external file attributes - 'archive' bit set
288 | .pack('V', $this->offset) // relative offset of local header
289 | .$filepath;
290 |
291 | $this->offset = strlen($this->zipdata);
292 | $this->entries++;
293 | $this->file_num++;
294 | }
295 |
296 | // --------------------------------------------------------------------
297 |
298 | /**
299 | * Read the contents of a file and add it to the zip
300 | *
301 | * @param string $path
302 | * @param bool $archive_filepath
303 | * @return bool
304 | */
305 | public function read_file($path, $archive_filepath = FALSE)
306 | {
307 | if (file_exists($path) && FALSE !== ($data = file_get_contents($path)))
308 | {
309 | if (is_string($archive_filepath))
310 | {
311 | $name = str_replace('\\', '/', $archive_filepath);
312 | }
313 | else
314 | {
315 | $name = str_replace('\\', '/', $path);
316 |
317 | if ($archive_filepath === FALSE)
318 | {
319 | $name = preg_replace('|.*/(.+)|', '\\1', $name);
320 | }
321 | }
322 |
323 | $this->add_data($name, $data);
324 | return TRUE;
325 | }
326 |
327 | return FALSE;
328 | }
329 |
330 | // ------------------------------------------------------------------------
331 |
332 | /**
333 | * Read a directory and add it to the zip.
334 | *
335 | * This function recursively reads a folder and everything it contains (including
336 | * sub-folders) and creates a zip based on it. Whatever directory structure
337 | * is in the original file path will be recreated in the zip file.
338 | *
339 | * @param string $path path to source directory
340 | * @param bool $preserve_filepath
341 | * @param string $root_path
342 | * @return bool
343 | */
344 | public function read_dir($path, $preserve_filepath = TRUE, $root_path = NULL)
345 | {
346 | $path = rtrim($path, '/\\').DIRECTORY_SEPARATOR;
347 | if ( ! $fp = @opendir($path))
348 | {
349 | return FALSE;
350 | }
351 |
352 | // Set the original directory root for child dir's to use as relative
353 | if ($root_path === NULL)
354 | {
355 | $root_path = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, dirname($path)).DIRECTORY_SEPARATOR;
356 | }
357 |
358 | while (FALSE !== ($file = readdir($fp)))
359 | {
360 | if ($file[0] === '.')
361 | {
362 | continue;
363 | }
364 |
365 | if (is_dir($path.$file))
366 | {
367 | $this->read_dir($path.$file.DIRECTORY_SEPARATOR, $preserve_filepath, $root_path);
368 | }
369 | elseif (FALSE !== ($data = file_get_contents($path.$file)))
370 | {
371 | $name = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $path);
372 | if ($preserve_filepath === FALSE)
373 | {
374 | $name = str_replace($root_path, '', $name);
375 | }
376 |
377 | $this->add_data($name.$file, $data);
378 | }
379 | }
380 |
381 | closedir($fp);
382 | return TRUE;
383 | }
384 |
385 | // --------------------------------------------------------------------
386 |
387 | /**
388 | * Get the Zip file
389 | *
390 | * @return string (binary encoded)
391 | */
392 | public function get_zip()
393 | {
394 | // Is there any data to return?
395 | if ($this->entries === 0)
396 | {
397 | return FALSE;
398 | }
399 |
400 | return $this->zipdata
401 | .$this->directory."\x50\x4b\x05\x06\x00\x00\x00\x00"
402 | .pack('v', $this->entries) // total # of entries "on this disk"
403 | .pack('v', $this->entries) // total # of entries overall
404 | .pack('V', strlen($this->directory)) // size of central dir
405 | .pack('V', strlen($this->zipdata)) // offset to start of central dir
406 | ."\x00\x00"; // .zip file comment length
407 | }
408 |
409 | // --------------------------------------------------------------------
410 |
411 | /**
412 | * Write File to the specified directory
413 | *
414 | * Lets you write a file
415 | *
416 | * @param string $filepath the file name
417 | * @return bool
418 | */
419 | public function archive($filepath)
420 | {
421 | if ( ! ($fp = @fopen($filepath, 'w+b')))
422 | {
423 | return FALSE;
424 | }
425 |
426 | flock($fp, LOCK_EX);
427 |
428 | for ($result = $written = 0, $data = $this->get_zip(), $length = strlen($data); $written < $length; $written += $result)
429 | {
430 | if (($result = fwrite($fp, substr($data, $written))) === FALSE)
431 | {
432 | break;
433 | }
434 | }
435 |
436 | flock($fp, LOCK_UN);
437 | fclose($fp);
438 |
439 | return is_int($result);
440 | }
441 |
442 | // --------------------------------------------------------------------
443 |
444 | /**
445 | * Download
446 | *
447 | * @param string $filename the file name
448 | * @return void
449 | */
450 | public function download($filename = 'backup.zip')
451 | {
452 | if ( ! preg_match('|.+?\.zip$|', $filename))
453 | {
454 | $filename .= '.zip';
455 | }
456 |
457 | get_instance()->load->helper('download');
458 | $get_zip = $this->get_zip();
459 | $zip_content =& $get_zip;
460 |
461 | force_download($filename, $zip_content);
462 | }
463 |
464 | // --------------------------------------------------------------------
465 |
466 | /**
467 | * Initialize Data
468 | *
469 | * Lets you clear current zip data. Useful if you need to create
470 | * multiple zips with different data.
471 | *
472 | * @return CI_Zip
473 | */
474 | public function clear_data()
475 | {
476 | $this->zipdata = '';
477 | $this->directory = '';
478 | $this->entries = 0;
479 | $this->file_num = 0;
480 | $this->offset = 0;
481 | return $this;
482 | }
483 |
484 | }
485 |
--------------------------------------------------------------------------------
/README/Table.php:
--------------------------------------------------------------------------------
1 | $val)
119 | {
120 | $this->template[$key] = $val;
121 | }
122 |
123 | log_message('info', 'Table Class Initialized');
124 | }
125 |
126 | // --------------------------------------------------------------------
127 |
128 | /**
129 | * Set the template
130 | *
131 | * @param array $template
132 | * @return bool
133 | */
134 | public function set_template($template)
135 | {
136 | if ( ! is_array($template))
137 | {
138 | return FALSE;
139 | }
140 |
141 | $this->template = $template;
142 | return TRUE;
143 | }
144 |
145 | // --------------------------------------------------------------------
146 |
147 | /**
148 | * Set the table heading
149 | *
150 | * Can be passed as an array or discreet params
151 | *
152 | * @param mixed
153 | * @return CI_Table
154 | */
155 | public function set_heading($args = array())
156 | {
157 | $this->heading = $this->_prep_args(func_get_args());
158 | return $this;
159 | }
160 |
161 | // --------------------------------------------------------------------
162 |
163 | /**
164 | * Set columns. Takes a one-dimensional array as input and creates
165 | * a multi-dimensional array with a depth equal to the number of
166 | * columns. This allows a single array with many elements to be
167 | * displayed in a table that has a fixed column count.
168 | *
169 | * @param array $array
170 | * @param int $col_limit
171 | * @return array
172 | */
173 | public function make_columns($array = array(), $col_limit = 0)
174 | {
175 | if ( ! is_array($array) OR count($array) === 0 OR ! is_int($col_limit))
176 | {
177 | return FALSE;
178 | }
179 |
180 | // Turn off the auto-heading feature since it's doubtful we
181 | // will want headings from a one-dimensional array
182 | $this->auto_heading = FALSE;
183 |
184 | if ($col_limit === 0)
185 | {
186 | return $array;
187 | }
188 |
189 | $new = array();
190 | do
191 | {
192 | $temp = array_splice($array, 0, $col_limit);
193 |
194 | if (count($temp) < $col_limit)
195 | {
196 | for ($i = count($temp); $i < $col_limit; $i++)
197 | {
198 | $temp[] = ' ';
199 | }
200 | }
201 |
202 | $new[] = $temp;
203 | }
204 | while (count($array) > 0);
205 |
206 | return $new;
207 | }
208 |
209 | // --------------------------------------------------------------------
210 |
211 | /**
212 | * Set "empty" cells
213 | *
214 | * Can be passed as an array or discreet params
215 | *
216 | * @param mixed $value
217 | * @return CI_Table
218 | */
219 | public function set_empty($value)
220 | {
221 | $this->empty_cells = $value;
222 | return $this;
223 | }
224 |
225 | // --------------------------------------------------------------------
226 |
227 | /**
228 | * Add a table row
229 | *
230 | * Can be passed as an array or discreet params
231 | *
232 | * @param mixed
233 | * @return CI_Table
234 | */
235 | public function add_row($args = array())
236 | {
237 | $this->rows[] = $this->_prep_args(func_get_args());
238 | return $this;
239 | }
240 |
241 | // --------------------------------------------------------------------
242 |
243 | /**
244 | * Prep Args
245 | *
246 | * Ensures a standard associative array format for all cell data
247 | *
248 | * @param array
249 | * @return array
250 | */
251 | protected function _prep_args($args)
252 | {
253 | // If there is no $args[0], skip this and treat as an associative array
254 | // This can happen if there is only a single key, for example this is passed to table->generate
255 | // array(array('foo'=>'bar'))
256 | if (isset($args[0]) && count($args) === 1 && is_array($args[0]) && ! isset($args[0]['data']))
257 | {
258 | $args = $args[0];
259 | }
260 |
261 | foreach ($args as $key => $val)
262 | {
263 | is_array($val) OR $args[$key] = array('data' => $val);
264 | }
265 |
266 | return $args;
267 | }
268 |
269 | // --------------------------------------------------------------------
270 |
271 | /**
272 | * Add a table caption
273 | *
274 | * @param string $caption
275 | * @return CI_Table
276 | */
277 | public function set_caption($caption)
278 | {
279 | $this->caption = $caption;
280 | }
281 |
282 | // --------------------------------------------------------------------
283 |
284 | /**
285 | * Generate the table
286 | *
287 | * @param mixed $table_data
288 | * @return string
289 | */
290 | public function generate($table_data = NULL)
291 | {
292 | // The table data can optionally be passed to this function
293 | // either as a database result object or an array
294 | if ( ! empty($table_data))
295 | {
296 | if ($table_data instanceof CI_DB_result)
297 | {
298 | $this->_set_from_db_result($table_data);
299 | }
300 | elseif (is_array($table_data))
301 | {
302 | $this->_set_from_array($table_data);
303 | }
304 | }
305 |
306 | // Is there anything to display? No? Smite them!
307 | if (empty($this->heading) && empty($this->rows))
308 | {
309 | return 'Undefined table data';
310 | }
311 |
312 | // Compile and validate the template date
313 | $this->_compile_template();
314 |
315 | // Validate a possibly existing custom cell manipulation function
316 | if (isset($this->function) && ! is_callable($this->function))
317 | {
318 | $this->function = NULL;
319 | }
320 |
321 | // Build the table!
322 |
323 | $out = $this->template['table_open'].$this->newline;
324 |
325 | // Add any caption here
326 | if ($this->caption)
327 | {
328 | $out .= ''.$this->caption.''.$this->newline;
329 | }
330 |
331 | // Is there a table heading to display?
332 | if ( ! empty($this->heading))
333 | {
334 | $out .= $this->template['thead_open'].$this->newline.$this->template['heading_row_start'].$this->newline;
335 |
336 | foreach ($this->heading as $heading)
337 | {
338 | $temp = $this->template['heading_cell_start'];
339 |
340 | foreach ($heading as $key => $val)
341 | {
342 | if ($key !== 'data')
343 | {
344 | $temp = str_replace('template['heading_cell_end'];
349 | }
350 |
351 | $out .= $this->template['heading_row_end'].$this->newline.$this->template['thead_close'].$this->newline;
352 | }
353 |
354 | // Build the table rows
355 | if ( ! empty($this->rows))
356 | {
357 | $out .= $this->template['tbody_open'].$this->newline;
358 |
359 | $i = 1;
360 | foreach ($this->rows as $row)
361 | {
362 | if ( ! is_array($row))
363 | {
364 | break;
365 | }
366 |
367 | // We use modulus to alternate the row colors
368 | $name = fmod($i++, 2) ? '' : 'alt_';
369 |
370 | $out .= $this->template['row_'.$name.'start'].$this->newline;
371 |
372 | foreach ($row as $cell)
373 | {
374 | $temp = $this->template['cell_'.$name.'start'];
375 |
376 | foreach ($cell as $key => $val)
377 | {
378 | if ($key !== 'data')
379 | {
380 | $temp = str_replace(' | empty_cells;
390 | }
391 | elseif (isset($this->function))
392 | {
393 | $out .= call_user_func($this->function, $cell);
394 | }
395 | else
396 | {
397 | $out .= $cell;
398 | }
399 |
400 | $out .= $this->template['cell_'.$name.'end'];
401 | }
402 |
403 | $out .= $this->template['row_'.$name.'end'].$this->newline;
404 | }
405 |
406 | $out .= $this->template['tbody_close'].$this->newline;
407 | }
408 |
409 | $out .= $this->template['table_close'];
410 |
411 | // Clear table class properties before generating the table
412 | $this->clear();
413 |
414 | return $out;
415 | }
416 |
417 | // --------------------------------------------------------------------
418 |
419 | /**
420 | * Clears the table arrays. Useful if multiple tables are being generated
421 | *
422 | * @return CI_Table
423 | */
424 | public function clear()
425 | {
426 | $this->rows = array();
427 | $this->heading = array();
428 | $this->auto_heading = TRUE;
429 | return $this;
430 | }
431 |
432 | // --------------------------------------------------------------------
433 |
434 | /**
435 | * Set table data from a database result object
436 | *
437 | * @param CI_DB_result $db_result Database result object
438 | * @return void
439 | */
440 | protected function _set_from_db_result($object)
441 | {
442 | // First generate the headings from the table column names
443 | if ($this->auto_heading === TRUE && empty($this->heading))
444 | {
445 | $this->heading = $this->_prep_args($object->list_fields());
446 | }
447 |
448 | foreach ($object->result_array() as $row)
449 | {
450 | $this->rows[] = $this->_prep_args($row);
451 | }
452 | }
453 |
454 | // --------------------------------------------------------------------
455 |
456 | /**
457 | * Set table data from an array
458 | *
459 | * @param array $data
460 | * @return void
461 | */
462 | protected function _set_from_array($data)
463 | {
464 | if ($this->auto_heading === TRUE && empty($this->heading))
465 | {
466 | $this->heading = $this->_prep_args(array_shift($data));
467 | }
468 |
469 | foreach ($data as &$row)
470 | {
471 | $this->rows[] = $this->_prep_args($row);
472 | }
473 | }
474 |
475 | // --------------------------------------------------------------------
476 |
477 | /**
478 | * Compile Template
479 | *
480 | * @return void
481 | */
482 | protected function _compile_template()
483 | {
484 | if ($this->template === NULL)
485 | {
486 | $this->template = $this->_default_template();
487 | return;
488 | }
489 |
490 | $this->temp = $this->_default_template();
491 | foreach (array('table_open', 'thead_open', 'thead_close', 'heading_row_start', 'heading_row_end', 'heading_cell_start', 'heading_cell_end', 'tbody_open', 'tbody_close', 'row_start', 'row_end', 'cell_start', 'cell_end', 'row_alt_start', 'row_alt_end', 'cell_alt_start', 'cell_alt_end', 'table_close') as $val)
492 | {
493 | if ( ! isset($this->template[$val]))
494 | {
495 | $this->template[$val] = $this->temp[$val];
496 | }
497 | }
498 | }
499 |
500 | // --------------------------------------------------------------------
501 |
502 | /**
503 | * Default Template
504 | *
505 | * @return array
506 | */
507 | protected function _default_template()
508 | {
509 | return array(
510 | 'table_open' => '',
511 |
512 | 'thead_open' => '',
513 | 'thead_close' => '',
514 |
515 | 'heading_row_start' => '',
516 | 'heading_row_end' => ' ',
517 | 'heading_cell_start' => '',
518 | 'heading_cell_end' => ' | ',
519 |
520 | 'tbody_open' => '',
521 | 'tbody_close' => '',
522 |
523 | 'row_start' => '',
524 | 'row_end' => ' ',
525 | 'cell_start' => '',
526 | 'cell_end' => ' | ',
527 |
528 | 'row_alt_start' => '',
529 | 'row_alt_end' => ' ',
530 | 'cell_alt_start' => '',
531 | 'cell_alt_end' => ' | ',
532 |
533 | 'table_close' => ' '
534 | );
535 | }
536 |
537 | }
538 |
--------------------------------------------------------------------------------
/README/Trackback.php:
--------------------------------------------------------------------------------
1 | '',
67 | 'title' => '',
68 | 'excerpt' => '',
69 | 'blog_name' => '',
70 | 'charset' => ''
71 | );
72 |
73 | /**
74 | * Convert ASCII flag
75 | *
76 | * Whether to convert high-ASCII and MS Word
77 | * characters to HTML entities.
78 | *
79 | * @var bool
80 | */
81 | public $convert_ascii = TRUE;
82 |
83 | /**
84 | * Response
85 | *
86 | * @var string
87 | */
88 | public $response = '';
89 |
90 | /**
91 | * Error messages list
92 | *
93 | * @var string[]
94 | */
95 | public $error_msg = array();
96 |
97 | // --------------------------------------------------------------------
98 |
99 | /**
100 | * Constructor
101 | *
102 | * @return void
103 | */
104 | public function __construct()
105 | {
106 | log_message('info', 'Trackback Class Initialized');
107 | }
108 |
109 | // --------------------------------------------------------------------
110 |
111 | /**
112 | * Send Trackback
113 | *
114 | * @param array
115 | * @return bool
116 | */
117 | public function send($tb_data)
118 | {
119 | if ( ! is_array($tb_data))
120 | {
121 | $this->set_error('The send() method must be passed an array');
122 | return FALSE;
123 | }
124 |
125 | // Pre-process the Trackback Data
126 | foreach (array('url', 'title', 'excerpt', 'blog_name', 'ping_url') as $item)
127 | {
128 | if ( ! isset($tb_data[$item]))
129 | {
130 | $this->set_error('Required item missing: '.$item);
131 | return FALSE;
132 | }
133 |
134 | switch ($item)
135 | {
136 | case 'ping_url':
137 | $$item = $this->extract_urls($tb_data[$item]);
138 | break;
139 | case 'excerpt':
140 | $$item = $this->limit_characters($this->convert_xml(strip_tags(stripslashes($tb_data[$item]))));
141 | break;
142 | case 'url':
143 | $$item = str_replace('-', '-', $this->convert_xml(strip_tags(stripslashes($tb_data[$item]))));
144 | break;
145 | default:
146 | $$item = $this->convert_xml(strip_tags(stripslashes($tb_data[$item])));
147 | break;
148 | }
149 |
150 | // Convert High ASCII Characters
151 | if ($this->convert_ascii === TRUE && in_array($item, array('excerpt', 'title', 'blog_name'), TRUE))
152 | {
153 | $$item = $this->convert_ascii($$item);
154 | }
155 | }
156 |
157 | // Build the Trackback data string
158 | $charset = isset($tb_data['charset']) ? $tb_data['charset'] : $this->charset;
159 |
160 | $data = 'url='.rawurlencode($url).'&title='.rawurlencode($title).'&blog_name='.rawurlencode($blog_name)
161 | .'&excerpt='.rawurlencode($excerpt).'&charset='.rawurlencode($charset);
162 |
163 | // Send Trackback(s)
164 | $return = TRUE;
165 | if (count($ping_url) > 0)
166 | {
167 | foreach ($ping_url as $url)
168 | {
169 | if ($this->process($url, $data) === FALSE)
170 | {
171 | $return = FALSE;
172 | }
173 | }
174 | }
175 |
176 | return $return;
177 | }
178 |
179 | // --------------------------------------------------------------------
180 |
181 | /**
182 | * Receive Trackback Data
183 | *
184 | * This function simply validates the incoming TB data.
185 | * It returns FALSE on failure and TRUE on success.
186 | * If the data is valid it is set to the $this->data array
187 | * so that it can be inserted into a database.
188 | *
189 | * @return bool
190 | */
191 | public function receive()
192 | {
193 | foreach (array('url', 'title', 'blog_name', 'excerpt') as $val)
194 | {
195 | if (empty($_POST[$val]))
196 | {
197 | $this->set_error('The following required POST variable is missing: '.$val);
198 | return FALSE;
199 | }
200 |
201 | $this->data['charset'] = isset($_POST['charset']) ? strtoupper(trim($_POST['charset'])) : 'auto';
202 |
203 | if ($val !== 'url' && MB_ENABLED === TRUE)
204 | {
205 | if (MB_ENABLED === TRUE)
206 | {
207 | $_POST[$val] = mb_convert_encoding($_POST[$val], $this->charset, $this->data['charset']);
208 | }
209 | elseif (ICONV_ENABLED === TRUE)
210 | {
211 | $_POST[$val] = @iconv($this->data['charset'], $this->charset.'//IGNORE', $_POST[$val]);
212 | }
213 | }
214 |
215 | $_POST[$val] = ($val !== 'url') ? $this->convert_xml(strip_tags($_POST[$val])) : strip_tags($_POST[$val]);
216 |
217 | if ($val === 'excerpt')
218 | {
219 | $_POST['excerpt'] = $this->limit_characters($_POST['excerpt']);
220 | }
221 |
222 | $this->data[$val] = $_POST[$val];
223 | }
224 |
225 | return TRUE;
226 | }
227 |
228 | // --------------------------------------------------------------------
229 |
230 | /**
231 | * Send Trackback Error Message
232 | *
233 | * Allows custom errors to be set. By default it
234 | * sends the "incomplete information" error, as that's
235 | * the most common one.
236 | *
237 | * @param string
238 | * @return void
239 | */
240 | public function send_error($message = 'Incomplete Information')
241 | {
242 | exit('\n\n1\n".$message."\n");
243 | }
244 |
245 | // --------------------------------------------------------------------
246 |
247 | /**
248 | * Send Trackback Success Message
249 | *
250 | * This should be called when a trackback has been
251 | * successfully received and inserted.
252 | *
253 | * @return void
254 | */
255 | public function send_success()
256 | {
257 | exit('\n\n0\n");
258 | }
259 |
260 | // --------------------------------------------------------------------
261 |
262 | /**
263 | * Fetch a particular item
264 | *
265 | * @param string
266 | * @return string
267 | */
268 | public function data($item)
269 | {
270 | return isset($this->data[$item]) ? $this->data[$item] : '';
271 | }
272 |
273 | // --------------------------------------------------------------------
274 |
275 | /**
276 | * Process Trackback
277 | *
278 | * Opens a socket connection and passes the data to
279 | * the server. Returns TRUE on success, FALSE on failure
280 | *
281 | * @param string
282 | * @param string
283 | * @return bool
284 | */
285 | public function process($url, $data)
286 | {
287 | $target = parse_url($url);
288 |
289 | // Open the socket
290 | if ( ! $fp = @fsockopen($target['host'], 80))
291 | {
292 | $this->set_error('Invalid Connection: '.$url);
293 | return FALSE;
294 | }
295 |
296 | // Build the path
297 | $path = isset($target['path']) ? $target['path'] : $url;
298 | empty($target['query']) OR $path .= '?'.$target['query'];
299 |
300 | // Add the Trackback ID to the data string
301 | if ($id = $this->get_id($url))
302 | {
303 | $data = 'tb_id='.$id.'&'.$data;
304 | }
305 |
306 | // Transfer the data
307 | fputs($fp, 'POST '.$path." HTTP/1.0\r\n");
308 | fputs($fp, 'Host: '.$target['host']."\r\n");
309 | fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
310 | fputs($fp, 'Content-length: '.strlen($data)."\r\n");
311 | fputs($fp, "Connection: close\r\n\r\n");
312 | fputs($fp, $data);
313 |
314 | // Was it successful?
315 |
316 | $this->response = '';
317 | while ( ! feof($fp))
318 | {
319 | $this->response .= fgets($fp, 128);
320 | }
321 | @fclose($fp);
322 |
323 | if (stripos($this->response, '0') === FALSE)
324 | {
325 | $message = preg_match('/(.*?)<\/message>/is', $this->response, $match)
326 | ? trim($match[1])
327 | : 'An unknown error was encountered';
328 | $this->set_error($message);
329 | return FALSE;
330 | }
331 |
332 | return TRUE;
333 | }
334 |
335 | // --------------------------------------------------------------------
336 |
337 | /**
338 | * Extract Trackback URLs
339 | *
340 | * This function lets multiple trackbacks be sent.
341 | * It takes a string of URLs (separated by comma or
342 | * space) and puts each URL into an array
343 | *
344 | * @param string
345 | * @return string
346 | */
347 | public function extract_urls($urls)
348 | {
349 | // Remove the pesky white space and replace with a comma, then replace doubles.
350 | $urls = str_replace(',,', ',', preg_replace('/\s*(\S+)\s*/', '\\1,', $urls));
351 |
352 | // Break into an array via commas and remove duplicates
353 | $urls = array_unique(preg_split('/[,]/', rtrim($urls, ',')));
354 |
355 | array_walk($urls, array($this, 'validate_url'));
356 | return $urls;
357 | }
358 |
359 | // --------------------------------------------------------------------
360 |
361 | /**
362 | * Validate URL
363 | *
364 | * Simply adds "http://" if missing
365 | *
366 | * @param string
367 | * @return void
368 | */
369 | public function validate_url(&$url)
370 | {
371 | $url = trim($url);
372 |
373 | if (strpos($url, 'http') !== 0)
374 | {
375 | $url = 'http://'.$url;
376 | }
377 | }
378 |
379 | // --------------------------------------------------------------------
380 |
381 | /**
382 | * Find the Trackback URL's ID
383 | *
384 | * @param string
385 | * @return string
386 | */
387 | public function get_id($url)
388 | {
389 | $tb_id = '';
390 |
391 | if (strpos($url, '?') !== FALSE)
392 | {
393 | $tb_array = explode('/', $url);
394 | $tb_end = $tb_array[count($tb_array)-1];
395 |
396 | if ( ! is_numeric($tb_end))
397 | {
398 | $tb_end = $tb_array[count($tb_array)-2];
399 | }
400 |
401 | $tb_array = explode('=', $tb_end);
402 | $tb_id = $tb_array[count($tb_array)-1];
403 | }
404 | else
405 | {
406 | $url = rtrim($url, '/');
407 |
408 | $tb_array = explode('/', $url);
409 | $tb_id = $tb_array[count($tb_array)-1];
410 |
411 | if ( ! is_numeric($tb_id))
412 | {
413 | $tb_id = $tb_array[count($tb_array)-2];
414 | }
415 | }
416 |
417 | return ctype_digit((string) $tb_id) ? $tb_id : FALSE;
418 | }
419 |
420 | // --------------------------------------------------------------------
421 |
422 | /**
423 | * Convert Reserved XML characters to Entities
424 | *
425 | * @param string
426 | * @return string
427 | */
428 | public function convert_xml($str)
429 | {
430 | $temp = '__TEMP_AMPERSANDS__';
431 |
432 | $str = preg_replace(array('/(\d+);/', '/&(\w+);/'), $temp.'\\1;', $str);
433 |
434 | $str = str_replace(array('&', '<', '>', '"', "'", '-'),
435 | array('&', '<', '>', '"', ''', '-'),
436 | $str);
437 |
438 | return preg_replace(array('/'.$temp.'(\d+);/', '/'.$temp.'(\w+);/'), array('\\1;', '&\\1;'), $str);
439 | }
440 |
441 | // --------------------------------------------------------------------
442 |
443 | /**
444 | * Character limiter
445 | *
446 | * Limits the string based on the character count. Will preserve complete words.
447 | *
448 | * @param string
449 | * @param int
450 | * @param string
451 | * @return string
452 | */
453 | public function limit_characters($str, $n = 500, $end_char = '…')
454 | {
455 | if (strlen($str) < $n)
456 | {
457 | return $str;
458 | }
459 |
460 | $str = preg_replace('/\s+/', ' ', str_replace(array("\r\n", "\r", "\n"), ' ', $str));
461 |
462 | if (strlen($str) <= $n)
463 | {
464 | return $str;
465 | }
466 |
467 | $out = '';
468 | foreach (explode(' ', trim($str)) as $val)
469 | {
470 | $out .= $val.' ';
471 | if (strlen($out) >= $n)
472 | {
473 | return rtrim($out).$end_char;
474 | }
475 | }
476 | }
477 |
478 | // --------------------------------------------------------------------
479 |
480 | /**
481 | * High ASCII to Entities
482 | *
483 | * Converts Hight ascii text and MS Word special chars
484 | * to character entities
485 | *
486 | * @param string
487 | * @return string
488 | */
489 | public function convert_ascii($str)
490 | {
491 | $count = 1;
492 | $out = '';
493 | $temp = array();
494 |
495 | for ($i = 0, $s = strlen($str); $i < $s; $i++)
496 | {
497 | $ordinal = ord($str[$i]);
498 |
499 | if ($ordinal < 128)
500 | {
501 | $out .= $str[$i];
502 | }
503 | else
504 | {
505 | if (count($temp) === 0)
506 | {
507 | $count = ($ordinal < 224) ? 2 : 3;
508 | }
509 |
510 | $temp[] = $ordinal;
511 |
512 | if (count($temp) === $count)
513 | {
514 | $number = ($count === 3)
515 | ? (($temp[0] % 16) * 4096) + (($temp[1] % 64) * 64) + ($temp[2] % 64)
516 | : (($temp[0] % 32) * 64) + ($temp[1] % 64);
517 |
518 | $out .= ''.$number.';';
519 | $count = 1;
520 | $temp = array();
521 | }
522 | }
523 | }
524 |
525 | return $out;
526 | }
527 |
528 | // --------------------------------------------------------------------
529 |
530 | /**
531 | * Set error message
532 | *
533 | * @param string
534 | * @return void
535 | */
536 | public function set_error($msg)
537 | {
538 | log_message('error', $msg);
539 | $this->error_msg[] = $msg;
540 | }
541 |
542 | // --------------------------------------------------------------------
543 |
544 | /**
545 | * Show error messages
546 | *
547 | * @param string
548 | * @param string
549 | * @return string
550 | */
551 | public function display_errors($open = '', $close = ' ')
552 | {
553 | return (count($this->error_msg) > 0) ? $open.implode($close.$open, $this->error_msg).$close : '';
554 | }
555 |
556 | }
557 |
--------------------------------------------------------------------------------
/README/Typography.php:
--------------------------------------------------------------------------------
1 | tags
53 | *
54 | * @var string
55 | */
56 | public $block_elements = 'address|blockquote|div|dl|fieldset|form|h\d|hr|noscript|object|ol|p|pre|script|table|ul';
57 |
58 | /**
59 | * Elements that should not have and tags within them.
60 | *
61 | * @var string
62 | */
63 | public $skip_elements = 'p|pre|ol|ul|dl|object|table|h\d';
64 |
65 | /**
66 | * Tags we want the parser to completely ignore when splitting the string.
67 | *
68 | * @var string
69 | */
70 | public $inline_elements = 'a|abbr|acronym|b|bdo|big|br|button|cite|code|del|dfn|em|i|img|ins|input|label|map|kbd|q|samp|select|small|span|strong|sub|sup|textarea|tt|var';
71 |
72 | /**
73 | * array of block level elements that require inner content to be within another block level element
74 | *
75 | * @var array
76 | */
77 | public $inner_block_required = array('blockquote');
78 |
79 | /**
80 | * the last block element parsed
81 | *
82 | * @var string
83 | */
84 | public $last_block_element = '';
85 |
86 | /**
87 | * whether or not to protect quotes within { curly braces }
88 | *
89 | * @var bool
90 | */
91 | public $protect_braced_quotes = FALSE;
92 |
93 | /**
94 | * Auto Typography
95 | *
96 | * This function converts text, making it typographically correct:
97 | * - Converts double spaces into paragraphs.
98 | * - Converts single line breaks into tags
99 | * - Converts single and double quotes into correctly facing curly quote entities.
100 | * - Converts three dots into ellipsis.
101 | * - Converts double dashes into em-dashes.
102 | * - Converts two spaces into entities
103 | *
104 | * @param string
105 | * @param bool whether to reduce more then two consecutive newlines to two
106 | * @return string
107 | */
108 | public function auto_typography($str, $reduce_linebreaks = FALSE)
109 | {
110 | if ($str === '')
111 | {
112 | return '';
113 | }
114 |
115 | // Standardize Newlines to make matching easier
116 | if (strpos($str, "\r") !== FALSE)
117 | {
118 | $str = str_replace(array("\r\n", "\r"), "\n", $str);
119 | }
120 |
121 | // Reduce line breaks. If there are more than two consecutive linebreaks
122 | // we'll compress them down to a maximum of two since there's no benefit to more.
123 | if ($reduce_linebreaks === TRUE)
124 | {
125 | $str = preg_replace("/\n\n+/", "\n\n", $str);
126 | }
127 |
128 | // HTML comment tags don't conform to patterns of normal tags, so pull them out separately, only if needed
129 | $html_comments = array();
130 | if (strpos($str, ' |