GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — develop ( e54387...b62a26 )
by Lonnie
10s
created
myth/CIModules/auth/models/Login_model.php 1 patch
Indentation   +352 added lines, -352 removed lines patch added patch discarded remove patch
@@ -41,357 +41,357 @@
 block discarded – undo
41 41
  */
42 42
 class Login_model extends \Myth\Models\CIDbModel {
43 43
 
44
-    protected $table_name = 'auth_logins';
45
-
46
-    protected $set_created = false;
47
-    protected $set_modified = false;
48
-
49
-    //--------------------------------------------------------------------
50
-
51
-    //--------------------------------------------------------------------
52
-    // Login Attempts
53
-    //--------------------------------------------------------------------
54
-
55
-    /**
56
-     * Records a login attempt. This is used to implement
57
-     * throttling of application, ip address and user login attempts.
58
-     *
59
-     * @param $ip_address
60
-     * @param $user_id
61
-     * @return void
62
-     */
63
-    public function recordLoginAttempt($ip_address, $user_id = null)
64
-    {
65
-        $datetime = date('Y-m-d H:i:s');
66
-
67
-        // log attempt for app
68
-        $data = [
69
-            'type' => 'app',
70
-            'datetime' => $datetime
71
-        ];
72
-
73
-        $this->db->insert('auth_login_attempts', $data);
74
-
75
-        // log attempt for ip address
76
-        if (! empty($ip_address))
77
-        {
78
-            $data = [
79
-                'type' => 'ip',
80
-                'ip_address' => $ip_address,
81
-                'datetime' => $datetime
82
-            ];
83
-
84
-            $this->db->insert('auth_login_attempts', $data);
85
-        }
86
-
87
-        // log attempt for user
88
-        if ($user_id)
89
-        {
90
-            $data = [
91
-                'type' => 'user',
92
-                'user_id' => $user_id,
93
-                'datetime' => $datetime
94
-            ];
95
-
96
-            $this->db->insert('auth_login_attempts', $data);
97
-        }
98
-    }
99
-
100
-    //--------------------------------------------------------------------
101
-
102
-    /**
103
-     * Purges all login attempt records from the database.
104
-     *
105
-     * @param $ip_address
106
-     * @param $user_id
107
-     * @return mixed
108
-     */
109
-    public function purgeLoginAttempts($ip_address, $user_id)
110
-    {
111
-        if ($ip_address)
112
-        {
113
-            $this->db->where('ip_address', $ip_address);
114
-        }
115
-
116
-        if ($user_id)
117
-        {
118
-            $this->db->or_where('user_id', $user_id);
119
-        }
120
-
121
-        return $this->db->delete('auth_login_attempts');
122
-    }
123
-
124
-    //--------------------------------------------------------------------
125
-
126
-    /**
127
-     * Checks to see if how many login attempts have been attempted in the
128
-     * last 60 seconds. If over 100, it is considered to be under a
129
-     * brute force attempt.
130
-     *
131
-     * @param $ip_address
132
-     * @param $user_id
133
-     * @return bool
134
-     */
135
-    public function isBruteForced($ip_address, $user_id)
136
-    {
137
-        $start_time = date('Y-m-d H:i:s', time() - 60);
138
-
139
-        $attempts_ip = $this->db->where('type', 'ip')
140
-                                ->where('ip_address', $ip_address)
141
-                                ->where('datetime >=', $start_time)
142
-                                ->count_all_results('auth_login_attempts');
143
-
144
-        if (! $user_id)
145
-        {
146
-            return $attempts_ip > 100;
147
-        }
148
-
149
-        $attempts_user = $this->db->where('type', 'user')
150
-                                  ->where('user_id', $user_id)
151
-                                  ->where('datetime >=', $start_time)
152
-                                  ->count_all_results('auth_login_attempts');
153
-
154
-        if ($attempts_user > $attempts_ip) 
155
-        {
156
-            return $attempts_user > 100;
157
-        }
158
-        else
159
-        {
160
-            return $attempts_ip > 100;
161
-        }
162
-    }
163
-
164
-    //--------------------------------------------------------------------
165
-
166
-    /**
167
-     * Attempts to determine if the system is under a distributed
168
-     * brute force attack.
169
-     *
170
-     * To determine if we are in under a brute force attack, we first
171
-     * find the average number of bad logins per day that never converted to
172
-     * successful logins over the last 3 months. Then we compare
173
-     * that to the the average number of logins in the past 24 hours.
174
-     *
175
-     * If the number of attempts in the last 24 hours is more than X (see config)
176
-     * times the average, then institute additional throttling.
177
-     *
178
-     * @return int The time to add to any throttling.
179
-     */
180
-    public function distributedBruteForceTime()
181
-    {
182
-        if (! $time = $this->cache->get('dbrutetime'))
183
-        {
184
-            $time = 0;
185
-
186
-            // Compute our daily average over the last 3 months.
187
-            $avg_start_time = date('Y-m-d 00:00:00', strtotime('-3 months'));
188
-
189
-            $query = $this->db->query("SELECT COUNT(*) / COUNT(DISTINCT DATE(`datetime`)) as num_rows FROM `auth_login_attempts` WHERE `type` = 'app' AND `datetime` >= ?", $avg_start_time);
190
-
191
-            if (! $query->num_rows())
192
-            {
193
-                $average = 0;
194
-            }
195
-            else
196
-            {
197
-                $average = $query->row()->num_rows;
198
-            }
199
-
200
-            // Get the total in the last 24 hours
201
-            $today_start_time = date('Y-m-d H:i:s', strtotime('-24 hours'));
202
-
203
-            $attempts = $this->db->where('type', 'app')
204
-                                 ->where('datetime >=', $today_start_time)
205
-                                 ->count_all_results('auth_login_attempts');
206
-
207
-            if ($attempts > (config_item('auth.dbrute_multiplier') * $average))
208
-            {
209
-                $time = config_item('auth.distributed_brute_add_time');
210
-            }
211
-
212
-            // Cache it for 3 hours.
213
-            $this->cache->save('dbrutetime', $time, 60*60*3);
214
-        }
215
-
216
-        return $time;
217
-    }
218
-
219
-    //--------------------------------------------------------------------
220
-
221
-    //--------------------------------------------------------------------
222
-    // Logins
223
-    //--------------------------------------------------------------------
224
-
225
-    /**
226
-     * Records a successful login. This stores in a table so that a
227
-     * history can be pulled up later if needed for security analyses.
228
-     *
229
-     * @param $user
230
-     */
231
-    public function recordLogin($user)
232
-    {
233
-        $data = [
234
-            'user_id'    => (int)$user['id'],
235
-            'datetime'   => date('Y-m-d H:i:s'),
236
-            'ip_address' => $this->input->ip_address()
237
-        ];
238
-
239
-        return $this->db->insert('auth_logins', $data);
240
-    }
241
-
242
-    //--------------------------------------------------------------------
243
-
244
-    //--------------------------------------------------------------------
245
-    // Tokens
246
-    //--------------------------------------------------------------------
247
-
248
-    /**
249
-     * Generates a new token for the rememberme cookie.
250
-     *
251
-     * The token is based on the user's email address (since everyone will have one)
252
-     * with the '@' turned to a '.', followed by a pipe (|) and a random 128-character
253
-     * string with letters and numbers.
254
-     *
255
-     * @param $user
256
-     * @return mixed
257
-     */
258
-    public function generateRememberToken($user)
259
-    {
260
-        $this->load->helper('string');
261
-
262
-        return str_replace('@', '.', $user['email']) .'|' . random_string('alnum', 128);
263
-    }
264
-
265
-    //--------------------------------------------------------------------
266
-
267
-    /**
268
-     * Hashes the token for the Remember Me Functionality.
269
-     *
270
-     * @param $token
271
-     * @return string
272
-     */
273
-    public function hashRememberToken($token)
274
-    {
275
-        return sha1(config_item('auth.salt') . $token);
276
-    }
277
-
278
-    //--------------------------------------------------------------------
279
-
280
-    /**
281
-     * Deletes a single token that matches the email/token combo.
282
-     *
283
-     * @param $email
284
-     * @param $token
285
-     * @return mixed
286
-     */
287
-    public function deleteRememberToken($email, $token)
288
-    {
289
-        $where = [
290
-            'email' => $email,
291
-            'hash'  => $this->hashRememberToken($token)
292
-        ];
293
-
294
-        $this->db->delete('auth_tokens', $where);
295
-    }
296
-
297
-    //--------------------------------------------------------------------
298
-
299
-    /**
300
-     * Removes all persistent login tokens (RememberMe) for a single user
301
-     * across all devices they may have logged in with.
302
-     *
303
-     * @param $email
304
-     * @return mixed
305
-     */
306
-    public function purgeRememberTokens($email)
307
-    {
308
-        return $this->db->delete('auth_tokens', ['email' => $email]);
309
-    }
310
-
311
-    //--------------------------------------------------------------------
312
-
313
-
314
-    /**
315
-     * Purges the 'auth_tokens' table of any records that are too old
316
-     * to be of any use anymore. This equates to 1 week older than
317
-     * the remember_length set in the config file.
318
-     */
319
-    public function purgeOldRememberTokens()
320
-    {
321
-        if (! config_item('auth.allow_remembering'))
322
-        {
323
-            return;
324
-        }
325
-
326
-        $date = time() - config_item('auth.remember_length') - 604800; // 1 week
327
-        $date = date('Y-m-d 00:00:00', $date);
328
-
329
-        $this->db->where('created <=', $date)
330
-                 ->delete('auth_tokens');
331
-    }
332
-
333
-    //--------------------------------------------------------------------
334
-
335
-    /**
336
-     * Gets the timestamp of the last attempted login for this user.
337
-     *
338
-     * @param $ip_address
339
-     * @param $user_id
340
-     * @return int|null
341
-     */
342
-    public function lastLoginAttemptTime($ip_address, $user_id)
343
-    {
344
-        $query = $this->db->where('type', 'ip')
345
-                          ->where('ip_address', $ip_address)
346
-                          ->order_by('datetime', 'desc')
347
-                          ->limit(1)
348
-                          ->get('auth_login_attempts');
349
-
350
-        $last_ip = ! $query->num_rows() ? 0 : strtotime($query->row()->datetime);
351
-
352
-        if (! $user_id)
353
-        {
354
-            return $last_ip;
355
-        }
356
-
357
-        $query = $this->db->where('type', 'user')
358
-                          ->where('user_id', $user_id)
359
-                          ->order_by('datetime', 'desc')
360
-                          ->limit(1)
361
-                          ->get('auth_login_attempts');
362
-
363
-        $last_user = ! $query->num_rows() ? 0 : strtotime($query->row()->datetime);
364
-
365
-        return ($last_user > $last_ip) ? $last_user : $last_ip;
366
-    }
367
-
368
-    //--------------------------------------------------------------------
369
-
370
-    /**
371
-     * Returns the number of failed login attempts for a given type.
372
-     *
373
-     * @param $ip_address
374
-     * @param $user_id
375
-     * @return int
376
-     */
377
-    public function countLoginAttempts($ip_address, $user_id)
378
-    {
379
-        $count_ip = $this->db->where('type', 'ip')
380
-                             ->where('ip_address', $ip_address)
381
-                             ->count_all_results('auth_login_attempts');
382
-
383
-        if (! $user_id)
384
-        {
385
-            return $count_ip;
386
-        }
387
-
388
-        $count_user = $this->db->where('type', 'user')
389
-                               ->where('user_id', $user_id)
390
-                               ->count_all_results('auth_login_attempts');
391
-
392
-        return ($count_user > $count_ip) ? $count_user : $count_ip;
393
-    }
394
-
395
-    //--------------------------------------------------------------------
44
+	protected $table_name = 'auth_logins';
45
+
46
+	protected $set_created = false;
47
+	protected $set_modified = false;
48
+
49
+	//--------------------------------------------------------------------
50
+
51
+	//--------------------------------------------------------------------
52
+	// Login Attempts
53
+	//--------------------------------------------------------------------
54
+
55
+	/**
56
+	 * Records a login attempt. This is used to implement
57
+	 * throttling of application, ip address and user login attempts.
58
+	 *
59
+	 * @param $ip_address
60
+	 * @param $user_id
61
+	 * @return void
62
+	 */
63
+	public function recordLoginAttempt($ip_address, $user_id = null)
64
+	{
65
+		$datetime = date('Y-m-d H:i:s');
66
+
67
+		// log attempt for app
68
+		$data = [
69
+			'type' => 'app',
70
+			'datetime' => $datetime
71
+		];
72
+
73
+		$this->db->insert('auth_login_attempts', $data);
74
+
75
+		// log attempt for ip address
76
+		if (! empty($ip_address))
77
+		{
78
+			$data = [
79
+				'type' => 'ip',
80
+				'ip_address' => $ip_address,
81
+				'datetime' => $datetime
82
+			];
83
+
84
+			$this->db->insert('auth_login_attempts', $data);
85
+		}
86
+
87
+		// log attempt for user
88
+		if ($user_id)
89
+		{
90
+			$data = [
91
+				'type' => 'user',
92
+				'user_id' => $user_id,
93
+				'datetime' => $datetime
94
+			];
95
+
96
+			$this->db->insert('auth_login_attempts', $data);
97
+		}
98
+	}
99
+
100
+	//--------------------------------------------------------------------
101
+
102
+	/**
103
+	 * Purges all login attempt records from the database.
104
+	 *
105
+	 * @param $ip_address
106
+	 * @param $user_id
107
+	 * @return mixed
108
+	 */
109
+	public function purgeLoginAttempts($ip_address, $user_id)
110
+	{
111
+		if ($ip_address)
112
+		{
113
+			$this->db->where('ip_address', $ip_address);
114
+		}
115
+
116
+		if ($user_id)
117
+		{
118
+			$this->db->or_where('user_id', $user_id);
119
+		}
120
+
121
+		return $this->db->delete('auth_login_attempts');
122
+	}
123
+
124
+	//--------------------------------------------------------------------
125
+
126
+	/**
127
+	 * Checks to see if how many login attempts have been attempted in the
128
+	 * last 60 seconds. If over 100, it is considered to be under a
129
+	 * brute force attempt.
130
+	 *
131
+	 * @param $ip_address
132
+	 * @param $user_id
133
+	 * @return bool
134
+	 */
135
+	public function isBruteForced($ip_address, $user_id)
136
+	{
137
+		$start_time = date('Y-m-d H:i:s', time() - 60);
138
+
139
+		$attempts_ip = $this->db->where('type', 'ip')
140
+								->where('ip_address', $ip_address)
141
+								->where('datetime >=', $start_time)
142
+								->count_all_results('auth_login_attempts');
143
+
144
+		if (! $user_id)
145
+		{
146
+			return $attempts_ip > 100;
147
+		}
148
+
149
+		$attempts_user = $this->db->where('type', 'user')
150
+								  ->where('user_id', $user_id)
151
+								  ->where('datetime >=', $start_time)
152
+								  ->count_all_results('auth_login_attempts');
153
+
154
+		if ($attempts_user > $attempts_ip) 
155
+		{
156
+			return $attempts_user > 100;
157
+		}
158
+		else
159
+		{
160
+			return $attempts_ip > 100;
161
+		}
162
+	}
163
+
164
+	//--------------------------------------------------------------------
165
+
166
+	/**
167
+	 * Attempts to determine if the system is under a distributed
168
+	 * brute force attack.
169
+	 *
170
+	 * To determine if we are in under a brute force attack, we first
171
+	 * find the average number of bad logins per day that never converted to
172
+	 * successful logins over the last 3 months. Then we compare
173
+	 * that to the the average number of logins in the past 24 hours.
174
+	 *
175
+	 * If the number of attempts in the last 24 hours is more than X (see config)
176
+	 * times the average, then institute additional throttling.
177
+	 *
178
+	 * @return int The time to add to any throttling.
179
+	 */
180
+	public function distributedBruteForceTime()
181
+	{
182
+		if (! $time = $this->cache->get('dbrutetime'))
183
+		{
184
+			$time = 0;
185
+
186
+			// Compute our daily average over the last 3 months.
187
+			$avg_start_time = date('Y-m-d 00:00:00', strtotime('-3 months'));
188
+
189
+			$query = $this->db->query("SELECT COUNT(*) / COUNT(DISTINCT DATE(`datetime`)) as num_rows FROM `auth_login_attempts` WHERE `type` = 'app' AND `datetime` >= ?", $avg_start_time);
190
+
191
+			if (! $query->num_rows())
192
+			{
193
+				$average = 0;
194
+			}
195
+			else
196
+			{
197
+				$average = $query->row()->num_rows;
198
+			}
199
+
200
+			// Get the total in the last 24 hours
201
+			$today_start_time = date('Y-m-d H:i:s', strtotime('-24 hours'));
202
+
203
+			$attempts = $this->db->where('type', 'app')
204
+								 ->where('datetime >=', $today_start_time)
205
+								 ->count_all_results('auth_login_attempts');
206
+
207
+			if ($attempts > (config_item('auth.dbrute_multiplier') * $average))
208
+			{
209
+				$time = config_item('auth.distributed_brute_add_time');
210
+			}
211
+
212
+			// Cache it for 3 hours.
213
+			$this->cache->save('dbrutetime', $time, 60*60*3);
214
+		}
215
+
216
+		return $time;
217
+	}
218
+
219
+	//--------------------------------------------------------------------
220
+
221
+	//--------------------------------------------------------------------
222
+	// Logins
223
+	//--------------------------------------------------------------------
224
+
225
+	/**
226
+	 * Records a successful login. This stores in a table so that a
227
+	 * history can be pulled up later if needed for security analyses.
228
+	 *
229
+	 * @param $user
230
+	 */
231
+	public function recordLogin($user)
232
+	{
233
+		$data = [
234
+			'user_id'    => (int)$user['id'],
235
+			'datetime'   => date('Y-m-d H:i:s'),
236
+			'ip_address' => $this->input->ip_address()
237
+		];
238
+
239
+		return $this->db->insert('auth_logins', $data);
240
+	}
241
+
242
+	//--------------------------------------------------------------------
243
+
244
+	//--------------------------------------------------------------------
245
+	// Tokens
246
+	//--------------------------------------------------------------------
247
+
248
+	/**
249
+	 * Generates a new token for the rememberme cookie.
250
+	 *
251
+	 * The token is based on the user's email address (since everyone will have one)
252
+	 * with the '@' turned to a '.', followed by a pipe (|) and a random 128-character
253
+	 * string with letters and numbers.
254
+	 *
255
+	 * @param $user
256
+	 * @return mixed
257
+	 */
258
+	public function generateRememberToken($user)
259
+	{
260
+		$this->load->helper('string');
261
+
262
+		return str_replace('@', '.', $user['email']) .'|' . random_string('alnum', 128);
263
+	}
264
+
265
+	//--------------------------------------------------------------------
266
+
267
+	/**
268
+	 * Hashes the token for the Remember Me Functionality.
269
+	 *
270
+	 * @param $token
271
+	 * @return string
272
+	 */
273
+	public function hashRememberToken($token)
274
+	{
275
+		return sha1(config_item('auth.salt') . $token);
276
+	}
277
+
278
+	//--------------------------------------------------------------------
279
+
280
+	/**
281
+	 * Deletes a single token that matches the email/token combo.
282
+	 *
283
+	 * @param $email
284
+	 * @param $token
285
+	 * @return mixed
286
+	 */
287
+	public function deleteRememberToken($email, $token)
288
+	{
289
+		$where = [
290
+			'email' => $email,
291
+			'hash'  => $this->hashRememberToken($token)
292
+		];
293
+
294
+		$this->db->delete('auth_tokens', $where);
295
+	}
296
+
297
+	//--------------------------------------------------------------------
298
+
299
+	/**
300
+	 * Removes all persistent login tokens (RememberMe) for a single user
301
+	 * across all devices they may have logged in with.
302
+	 *
303
+	 * @param $email
304
+	 * @return mixed
305
+	 */
306
+	public function purgeRememberTokens($email)
307
+	{
308
+		return $this->db->delete('auth_tokens', ['email' => $email]);
309
+	}
310
+
311
+	//--------------------------------------------------------------------
312
+
313
+
314
+	/**
315
+	 * Purges the 'auth_tokens' table of any records that are too old
316
+	 * to be of any use anymore. This equates to 1 week older than
317
+	 * the remember_length set in the config file.
318
+	 */
319
+	public function purgeOldRememberTokens()
320
+	{
321
+		if (! config_item('auth.allow_remembering'))
322
+		{
323
+			return;
324
+		}
325
+
326
+		$date = time() - config_item('auth.remember_length') - 604800; // 1 week
327
+		$date = date('Y-m-d 00:00:00', $date);
328
+
329
+		$this->db->where('created <=', $date)
330
+				 ->delete('auth_tokens');
331
+	}
332
+
333
+	//--------------------------------------------------------------------
334
+
335
+	/**
336
+	 * Gets the timestamp of the last attempted login for this user.
337
+	 *
338
+	 * @param $ip_address
339
+	 * @param $user_id
340
+	 * @return int|null
341
+	 */
342
+	public function lastLoginAttemptTime($ip_address, $user_id)
343
+	{
344
+		$query = $this->db->where('type', 'ip')
345
+						  ->where('ip_address', $ip_address)
346
+						  ->order_by('datetime', 'desc')
347
+						  ->limit(1)
348
+						  ->get('auth_login_attempts');
349
+
350
+		$last_ip = ! $query->num_rows() ? 0 : strtotime($query->row()->datetime);
351
+
352
+		if (! $user_id)
353
+		{
354
+			return $last_ip;
355
+		}
356
+
357
+		$query = $this->db->where('type', 'user')
358
+						  ->where('user_id', $user_id)
359
+						  ->order_by('datetime', 'desc')
360
+						  ->limit(1)
361
+						  ->get('auth_login_attempts');
362
+
363
+		$last_user = ! $query->num_rows() ? 0 : strtotime($query->row()->datetime);
364
+
365
+		return ($last_user > $last_ip) ? $last_user : $last_ip;
366
+	}
367
+
368
+	//--------------------------------------------------------------------
369
+
370
+	/**
371
+	 * Returns the number of failed login attempts for a given type.
372
+	 *
373
+	 * @param $ip_address
374
+	 * @param $user_id
375
+	 * @return int
376
+	 */
377
+	public function countLoginAttempts($ip_address, $user_id)
378
+	{
379
+		$count_ip = $this->db->where('type', 'ip')
380
+							 ->where('ip_address', $ip_address)
381
+							 ->count_all_results('auth_login_attempts');
382
+
383
+		if (! $user_id)
384
+		{
385
+			return $count_ip;
386
+		}
387
+
388
+		$count_user = $this->db->where('type', 'user')
389
+							   ->where('user_id', $user_id)
390
+							   ->count_all_results('auth_login_attempts');
391
+
392
+		return ($count_user > $count_ip) ? $count_user : $count_ip;
393
+	}
394
+
395
+	//--------------------------------------------------------------------
396 396
 
397 397
 }
Please login to merge, or discard this patch.
myth/Auth/AuthenticateInterface.php 1 patch
Indentation   +192 added lines, -192 removed lines patch added patch discarded remove patch
@@ -42,197 +42,197 @@
 block discarded – undo
42 42
  */
43 43
 interface AuthenticateInterface {
44 44
 
45
-    /**
46
-     * Attempt to log a user into the system.
47
-     *
48
-     * $credentials is an array of key/value pairs needed to log the user in.
49
-     * This is often email/password, or username/password.
50
-     *
51
-     * @param $credentials
52
-     * @param bool $remember
53
-     */
54
-    public function login($credentials, $remember=false);
55
-
56
-    //--------------------------------------------------------------------
57
-
58
-    /**
59
-     * Validates user login information without logging them in.
60
-     *
61
-     * $credentials is an array of key/value pairs needed to log the user in.
62
-     * This is often email/password, or username/password.
63
-     *
64
-     * @param $credentials
65
-     * @param bool $return_user
66
-     * @return mixed
67
-     */
68
-    public function validate($credentials, $return_user=false);
69
-
70
-    //--------------------------------------------------------------------
71
-
72
-    /**
73
-     * Logs a user out and removes all session information.
74
-     *
75
-     * @return mixed
76
-     */
77
-    public function logout();
78
-
79
-    //--------------------------------------------------------------------
80
-
81
-    /**
82
-     * Checks whether a user is logged in or not.
83
-     *
84
-     * @return bool
85
-     */
86
-    public function isLoggedIn();
87
-
88
-    //--------------------------------------------------------------------
89
-
90
-    /**
91
-     * Attempts to log a user in based on the "remember me" cookie.
92
-     *
93
-     * @return bool
94
-     */
95
-    public function viaRemember();
96
-
97
-    //--------------------------------------------------------------------
98
-
99
-    /**
100
-     * Registers a new user and handles activation method.
101
-     *
102
-     * @param $user_data
103
-     * @return bool
104
-     */
105
-    public function registerUser($user_data);
106
-
107
-    //--------------------------------------------------------------------
108
-
109
-    /**
110
-     * Used to verify the user values and activate a user so they can
111
-     * visit the site.
112
-     *
113
-     * @param $data
114
-     * @return bool
115
-     */
116
-    public function activateUser($data);
117
-
118
-    //--------------------------------------------------------------------
119
-
120
-    /**
121
-     * Used to allow manual activation of a user with a known ID.
122
-     *
123
-     * @param $id
124
-     * @return bool
125
-     */
126
-    public function activateUserById($id);
127
-
128
-    //--------------------------------------------------------------------
129
-
130
-    /**
131
-     * Grabs the current user object. Returns NULL if nothing found.
132
-     *
133
-     * @return array|null
134
-     */
135
-    public function user();
136
-
137
-    //--------------------------------------------------------------------
138
-
139
-    /**
140
-     * A convenience method to grab the current user's ID.
141
-     *
142
-     * @return int|null
143
-     */
144
-    public function id();
145
-
146
-    //--------------------------------------------------------------------
147
-
148
-    /**
149
-     * Tells the system to start throttling a user. This may vary by implementation,
150
-     * but will often add additional time before another login is allowed.
151
-     *
152
-     * @param $email
153
-     * @return mixed
154
-     */
155
-    public function isThrottled($email);
156
-
157
-    //--------------------------------------------------------------------
158
-
159
-    /**
160
-     * Sends a password reminder email to the user associated with
161
-     * the passed in $email.
162
-     *
163
-     * @param $email
164
-     * @return mixed
165
-     */
166
-    public function remindUser($email);
167
-
168
-    //--------------------------------------------------------------------
169
-
170
-    /**
171
-     * Validates the credentials provided and, if valid, resets the password.
172
-     *
173
-     * @param $credentials
174
-     * @param $password
175
-     * @param $passConfirm
176
-     * @return mixed
177
-     */
178
-    public function resetPassword($credentials, $password, $passConfirm);
179
-
180
-    //--------------------------------------------------------------------
181
-
182
-    /**
183
-     * Provides a way for implementations to allow new statuses to be set
184
-     * on the user. The details will vary based upon implementation, but
185
-     * will often allow for banning or suspending users.
186
-     *
187
-     * @param $newStatus
188
-     * @param null $message
189
-     * @return mixed
190
-     */
191
-    public function changeStatus($newStatus, $message=null);
192
-
193
-    //--------------------------------------------------------------------
194
-
195
-    /**
196
-     * Allows the consuming application to pass in a reference to the
197
-     * model that should be used.
198
-     *
199
-     * The model MUST extend Myth\Models\CIDbModel.
200
-     *
201
-     * @param $model
202
-     * @return mixed
203
-     */
204
-    public function useModel($model);
205
-
206
-    //--------------------------------------------------------------------
207
-
208
-    /**
209
-     * Returns the current error string.
210
-     *
211
-     * @return mixed
212
-     */
213
-    public function error();
214
-
215
-    //--------------------------------------------------------------------
216
-
217
-    /**
218
-     * Purges all login attempt records from the database.
219
-     *
220
-     * @param null $ip_address
221
-     * @param null $user_id
222
-     */
223
-    public function purgeLoginAttempts($ip_address = null, $user_id = null);
224
-
225
-    //--------------------------------------------------------------------
226
-
227
-    /**
228
-     * Purges all remember tokens for a single user. Effectively logs
229
-     * a user out of all devices. Intended to allow users to log themselves
230
-     * out of all devices as a security measure.
231
-     *
232
-     * @param $email
233
-     */
234
-    public function purgeRememberTokens($email);
235
-
236
-    //--------------------------------------------------------------------
45
+	/**
46
+	 * Attempt to log a user into the system.
47
+	 *
48
+	 * $credentials is an array of key/value pairs needed to log the user in.
49
+	 * This is often email/password, or username/password.
50
+	 *
51
+	 * @param $credentials
52
+	 * @param bool $remember
53
+	 */
54
+	public function login($credentials, $remember=false);
55
+
56
+	//--------------------------------------------------------------------
57
+
58
+	/**
59
+	 * Validates user login information without logging them in.
60
+	 *
61
+	 * $credentials is an array of key/value pairs needed to log the user in.
62
+	 * This is often email/password, or username/password.
63
+	 *
64
+	 * @param $credentials
65
+	 * @param bool $return_user
66
+	 * @return mixed
67
+	 */
68
+	public function validate($credentials, $return_user=false);
69
+
70
+	//--------------------------------------------------------------------
71
+
72
+	/**
73
+	 * Logs a user out and removes all session information.
74
+	 *
75
+	 * @return mixed
76
+	 */
77
+	public function logout();
78
+
79
+	//--------------------------------------------------------------------
80
+
81
+	/**
82
+	 * Checks whether a user is logged in or not.
83
+	 *
84
+	 * @return bool
85
+	 */
86
+	public function isLoggedIn();
87
+
88
+	//--------------------------------------------------------------------
89
+
90
+	/**
91
+	 * Attempts to log a user in based on the "remember me" cookie.
92
+	 *
93
+	 * @return bool
94
+	 */
95
+	public function viaRemember();
96
+
97
+	//--------------------------------------------------------------------
98
+
99
+	/**
100
+	 * Registers a new user and handles activation method.
101
+	 *
102
+	 * @param $user_data
103
+	 * @return bool
104
+	 */
105
+	public function registerUser($user_data);
106
+
107
+	//--------------------------------------------------------------------
108
+
109
+	/**
110
+	 * Used to verify the user values and activate a user so they can
111
+	 * visit the site.
112
+	 *
113
+	 * @param $data
114
+	 * @return bool
115
+	 */
116
+	public function activateUser($data);
117
+
118
+	//--------------------------------------------------------------------
119
+
120
+	/**
121
+	 * Used to allow manual activation of a user with a known ID.
122
+	 *
123
+	 * @param $id
124
+	 * @return bool
125
+	 */
126
+	public function activateUserById($id);
127
+
128
+	//--------------------------------------------------------------------
129
+
130
+	/**
131
+	 * Grabs the current user object. Returns NULL if nothing found.
132
+	 *
133
+	 * @return array|null
134
+	 */
135
+	public function user();
136
+
137
+	//--------------------------------------------------------------------
138
+
139
+	/**
140
+	 * A convenience method to grab the current user's ID.
141
+	 *
142
+	 * @return int|null
143
+	 */
144
+	public function id();
145
+
146
+	//--------------------------------------------------------------------
147
+
148
+	/**
149
+	 * Tells the system to start throttling a user. This may vary by implementation,
150
+	 * but will often add additional time before another login is allowed.
151
+	 *
152
+	 * @param $email
153
+	 * @return mixed
154
+	 */
155
+	public function isThrottled($email);
156
+
157
+	//--------------------------------------------------------------------
158
+
159
+	/**
160
+	 * Sends a password reminder email to the user associated with
161
+	 * the passed in $email.
162
+	 *
163
+	 * @param $email
164
+	 * @return mixed
165
+	 */
166
+	public function remindUser($email);
167
+
168
+	//--------------------------------------------------------------------
169
+
170
+	/**
171
+	 * Validates the credentials provided and, if valid, resets the password.
172
+	 *
173
+	 * @param $credentials
174
+	 * @param $password
175
+	 * @param $passConfirm
176
+	 * @return mixed
177
+	 */
178
+	public function resetPassword($credentials, $password, $passConfirm);
179
+
180
+	//--------------------------------------------------------------------
181
+
182
+	/**
183
+	 * Provides a way for implementations to allow new statuses to be set
184
+	 * on the user. The details will vary based upon implementation, but
185
+	 * will often allow for banning or suspending users.
186
+	 *
187
+	 * @param $newStatus
188
+	 * @param null $message
189
+	 * @return mixed
190
+	 */
191
+	public function changeStatus($newStatus, $message=null);
192
+
193
+	//--------------------------------------------------------------------
194
+
195
+	/**
196
+	 * Allows the consuming application to pass in a reference to the
197
+	 * model that should be used.
198
+	 *
199
+	 * The model MUST extend Myth\Models\CIDbModel.
200
+	 *
201
+	 * @param $model
202
+	 * @return mixed
203
+	 */
204
+	public function useModel($model);
205
+
206
+	//--------------------------------------------------------------------
207
+
208
+	/**
209
+	 * Returns the current error string.
210
+	 *
211
+	 * @return mixed
212
+	 */
213
+	public function error();
214
+
215
+	//--------------------------------------------------------------------
216
+
217
+	/**
218
+	 * Purges all login attempt records from the database.
219
+	 *
220
+	 * @param null $ip_address
221
+	 * @param null $user_id
222
+	 */
223
+	public function purgeLoginAttempts($ip_address = null, $user_id = null);
224
+
225
+	//--------------------------------------------------------------------
226
+
227
+	/**
228
+	 * Purges all remember tokens for a single user. Effectively logs
229
+	 * a user out of all devices. Intended to allow users to log themselves
230
+	 * out of all devices as a security measure.
231
+	 *
232
+	 * @param $email
233
+	 */
234
+	public function purgeRememberTokens($email);
235
+
236
+	//--------------------------------------------------------------------
237 237
 
238 238
 }
Please login to merge, or discard this patch.
myth/Mail/Mail.php 1 patch
Indentation   +136 added lines, -136 removed lines patch added patch discarded remove patch
@@ -32,141 +32,141 @@
 block discarded – undo
32 32
 
33 33
 class Mail {
34 34
 
35
-    /**
36
-     * Sends an email, using an existing Mailer, which can
37
-     * be found in application/mailers/. The mailer is the one responsible
38
-     * for determining whether the email will be sent immediately or queued
39
-     * to be sent later.
40
-     *
41
-     * The $mailer_name must include both the mailer name as well as the
42
-     * task, separated by a single colon:
43
-     *      'UserMailer:newUser'
44
-     *
45
-     * @param $mailer_name
46
-     * @param array $params
47
-     * @param array $options
48
-     * @return mixed
49
-     */
50
-    public static function deliver($mailer_name, $params=[], $options=[])
51
-    {
52
-        // Protect users from themselves here.
53
-        str_replace('::', ':', $mailer_name);
54
-
55
-        // Try to load our mailer class.
56
-        list($class, $method) = explode(':', $mailer_name);
57
-
58
-        if (! is_file(APPPATH .'mailers/'. $class .'.php'))
59
-        {
60
-            throw new \RuntimeException( sprintf( lang('mail.cant_find_mailer'), $class) );
61
-        }
62
-
63
-        require_once APPPATH .'mailers/'. $class .'.php';
64
-
65
-        if (! class_exists($class, false))
66
-        {
67
-            throw new \RuntimeException( sprintf( lang('errors.cant_instantiate'), $class) );
68
-        }
69
-
70
-        $mailer = new $class( $options );
71
-
72
-        if (! method_exists($mailer, $method))
73
-        {
74
-            throw new \BadMethodCallException( sprintf( lang('mail.invalid_mailer_method'), $class, $method) );
75
-        }
76
-
77
-        // try to deliver the mail, but don't send back the contents
78
-        // since we don't want to force the mailers to return anything.
79
-        if (call_user_func_array([$mailer, $method], $params) )
80
-        {
81
-            return true;
82
-        }
83
-
84
-        return false;
85
-    }
86
-
87
-    //--------------------------------------------------------------------
88
-
89
-    /**
90
-     * Adds an item to the email queue to be sent out next time.
91
-     *
92
-     * @param string $mailer_name
93
-     * @param array $params
94
-     * @param array $options
95
-     * @param \Myth\Mail\Queue $queue
96
-     *
97
-     * @return mixed
98
-     */
99
-    public static function queue($mailer_name, $params=[], $options=[], &$queue=null)
100
-    {
101
-        $data = [
102
-            'mailer'    => $mailer_name,
103
-            'params'    => serialize($params),
104
-            'options'   => serialize($options)
105
-        ];
106
-
107
-        if (empty($queue))
108
-        {
109
-            $queue = new \Myth\Mail\Queue();
110
-        }
111
-
112
-        return $queue->insert($data);
113
-    }
114
-
115
-    //--------------------------------------------------------------------
116
-
117
-    /**
118
-     * Processes the Email queue sending out emails in chunks.
119
-     * Typically used in a cronjob to send out all queued emails.
120
-     *
121
-     * @param int $chunk_size   // How many emails to send per batch.
122
-     * @return string           // The output of the cronjob...
123
-     */
124
-    public static function process($chunk_size=50, &$db=null)
125
-    {
126
-        if (empty($db))
127
-        {
128
-            $db = new \Myth\Mail\Queue();
129
-        }
130
-
131
-        // Grab our batch of emails to process
132
-        $queue = $db->find_many_by('sent', 0);
133
-
134
-        if (! $queue)
135
-        {
136
-            // We didn't have an error, we simply
137
-            // didn't have anything to do.
138
-            return true;
139
-        }
140
-
141
-        $output = 'Started processing email Queue at '. date('Y-m-d H:i:s') .".\n\n";
142
-
143
-        foreach ($queue as $item)
144
-        {
145
-            try {
146
-                if (! Mail::deliver($item->mailer, unserialize($item->params), unserialize($item->options))) {
147
-                    $output .= '[FAILED] ';
148
-                } else {
149
-                    $data = [
150
-                        'sent'    => 1,
151
-                        'sent_on' => date('Y-m-d H:i:s')
152
-                    ];
153
-
154
-                    $db->update($item->id, $data);
155
-                }
156
-
157
-                $output .= "ID: {$item->id}, Mailer: {$item->mailer}. \n";
158
-            }
159
-            catch (\Exception $e)
160
-            {
161
-                $output .= "[EXCEPTION] ". $e->getMessage() ."\n";
162
-            }
163
-        }
164
-
165
-        $output .= "Done processing email Queue at ". date('H:i:s') .".\n";
166
-
167
-        return $output;
168
-    }
169
-
170
-    //--------------------------------------------------------------------
35
+	/**
36
+	 * Sends an email, using an existing Mailer, which can
37
+	 * be found in application/mailers/. The mailer is the one responsible
38
+	 * for determining whether the email will be sent immediately or queued
39
+	 * to be sent later.
40
+	 *
41
+	 * The $mailer_name must include both the mailer name as well as the
42
+	 * task, separated by a single colon:
43
+	 *      'UserMailer:newUser'
44
+	 *
45
+	 * @param $mailer_name
46
+	 * @param array $params
47
+	 * @param array $options
48
+	 * @return mixed
49
+	 */
50
+	public static function deliver($mailer_name, $params=[], $options=[])
51
+	{
52
+		// Protect users from themselves here.
53
+		str_replace('::', ':', $mailer_name);
54
+
55
+		// Try to load our mailer class.
56
+		list($class, $method) = explode(':', $mailer_name);
57
+
58
+		if (! is_file(APPPATH .'mailers/'. $class .'.php'))
59
+		{
60
+			throw new \RuntimeException( sprintf( lang('mail.cant_find_mailer'), $class) );
61
+		}
62
+
63
+		require_once APPPATH .'mailers/'. $class .'.php';
64
+
65
+		if (! class_exists($class, false))
66
+		{
67
+			throw new \RuntimeException( sprintf( lang('errors.cant_instantiate'), $class) );
68
+		}
69
+
70
+		$mailer = new $class( $options );
71
+
72
+		if (! method_exists($mailer, $method))
73
+		{
74
+			throw new \BadMethodCallException( sprintf( lang('mail.invalid_mailer_method'), $class, $method) );
75
+		}
76
+
77
+		// try to deliver the mail, but don't send back the contents
78
+		// since we don't want to force the mailers to return anything.
79
+		if (call_user_func_array([$mailer, $method], $params) )
80
+		{
81
+			return true;
82
+		}
83
+
84
+		return false;
85
+	}
86
+
87
+	//--------------------------------------------------------------------
88
+
89
+	/**
90
+	 * Adds an item to the email queue to be sent out next time.
91
+	 *
92
+	 * @param string $mailer_name
93
+	 * @param array $params
94
+	 * @param array $options
95
+	 * @param \Myth\Mail\Queue $queue
96
+	 *
97
+	 * @return mixed
98
+	 */
99
+	public static function queue($mailer_name, $params=[], $options=[], &$queue=null)
100
+	{
101
+		$data = [
102
+			'mailer'    => $mailer_name,
103
+			'params'    => serialize($params),
104
+			'options'   => serialize($options)
105
+		];
106
+
107
+		if (empty($queue))
108
+		{
109
+			$queue = new \Myth\Mail\Queue();
110
+		}
111
+
112
+		return $queue->insert($data);
113
+	}
114
+
115
+	//--------------------------------------------------------------------
116
+
117
+	/**
118
+	 * Processes the Email queue sending out emails in chunks.
119
+	 * Typically used in a cronjob to send out all queued emails.
120
+	 *
121
+	 * @param int $chunk_size   // How many emails to send per batch.
122
+	 * @return string           // The output of the cronjob...
123
+	 */
124
+	public static function process($chunk_size=50, &$db=null)
125
+	{
126
+		if (empty($db))
127
+		{
128
+			$db = new \Myth\Mail\Queue();
129
+		}
130
+
131
+		// Grab our batch of emails to process
132
+		$queue = $db->find_many_by('sent', 0);
133
+
134
+		if (! $queue)
135
+		{
136
+			// We didn't have an error, we simply
137
+			// didn't have anything to do.
138
+			return true;
139
+		}
140
+
141
+		$output = 'Started processing email Queue at '. date('Y-m-d H:i:s') .".\n\n";
142
+
143
+		foreach ($queue as $item)
144
+		{
145
+			try {
146
+				if (! Mail::deliver($item->mailer, unserialize($item->params), unserialize($item->options))) {
147
+					$output .= '[FAILED] ';
148
+				} else {
149
+					$data = [
150
+						'sent'    => 1,
151
+						'sent_on' => date('Y-m-d H:i:s')
152
+					];
153
+
154
+					$db->update($item->id, $data);
155
+				}
156
+
157
+				$output .= "ID: {$item->id}, Mailer: {$item->mailer}. \n";
158
+			}
159
+			catch (\Exception $e)
160
+			{
161
+				$output .= "[EXCEPTION] ". $e->getMessage() ."\n";
162
+			}
163
+		}
164
+
165
+		$output .= "Done processing email Queue at ". date('H:i:s') .".\n";
166
+
167
+		return $output;
168
+	}
169
+
170
+	//--------------------------------------------------------------------
171 171
 
172 172
 }
Please login to merge, or discard this patch.
myth/Auth/LocalAuthentication.php 1 patch
Indentation   +908 added lines, -908 removed lines patch added patch discarded remove patch
@@ -52,915 +52,915 @@
 block discarded – undo
52 52
  */
53 53
 class LocalAuthentication implements AuthenticateInterface {
54 54
 
55
-    protected $ci;
56
-
57
-    protected $user = null;
58
-
59
-    public $user_model = null;
60
-
61
-    public $error = null;
62
-
63
-    //--------------------------------------------------------------------
64
-
65
-    public function __construct( $ci=null )
66
-    {
67
-        if ($ci)
68
-        {
69
-            $this->ci= $ci;
70
-        }
71
-        else
72
-        {
73
-            $this->ci =& get_instance();
74
-        }
75
-
76
-        // Get our compatibility password file loaded up.
77
-        if (! function_exists('password_hash'))
78
-        {
79
-            require_once dirname(__FILE__) .'password.php';
80
-        }
81
-
82
-        if (empty($this->ci->session))
83
-        {
84
-            $this->ci->load->library('session');
85
-        }
86
-
87
-        $this->ci->config->load('auth');
88
-        $this->ci->load->model('auth/login_model');
89
-        $this->ci->load->language('auth/auth');
90
-    }
91
-
92
-    //--------------------------------------------------------------------
93
-
94
-    /**
95
-     * Attempt to log a user into the system.
96
-     *
97
-     * $credentials is an array of key/value pairs needed to log the user in.
98
-     * This is often email/password, or username/password.
99
-     *
100
-     * @param array $credentials
101
-     * @param bool  $remember
102
-     * @return bool|mixed
103
-     */
104
-    public function login($credentials, $remember=false)
105
-    {
106
-        $user = $this->validate($credentials, true);
107
-
108
-        if (! $user)
109
-        {
110
-            $this->user = null;
111
-            return $user;
112
-        }       
113
-
114
-        $this->loginUser($user);
115
-
116
-        if ($remember)
117
-        {
118
-            $this->rememberUser($user);
119
-        }
120
-
121
-        Events::trigger('didLogin', [$user]);
122
-
123
-        return true;
124
-    }
125
-
126
-    //--------------------------------------------------------------------
127
-
128
-    /**
129
-     * Validates user login information without logging them in.
130
-     *
131
-     * $credentials is an array of key/value pairs needed to log the user in.
132
-     * This is often email/password, or username/password.
133
-     *
134
-     * @param $credentials
135
-     * @param bool $return_user
136
-     * @return mixed
137
-     */
138
-    public function validate($credentials, $return_user=false)
139
-    {
140
-        // Can't validate without a password.
141
-        if (empty($credentials['password']) || count($credentials) < 2)
142
-        {
143
-            return null;
144
-        }
145
-
146
-        $password = $credentials['password'];
147
-        unset($credentials['password']);
148
-
149
-        // We should only be allowed 1 single other credential to
150
-        // test against.
151
-        if (count($credentials) > 1)
152
-        {
153
-            $this->error = lang('auth.too_many_credentials');
154
-            return false;
155
-        }
156
-
157
-        // Ensure that the fields are allowed validation fields
158
-        if (! in_array(key($credentials), config_item('auth.valid_fields')) )
159
-        {
160
-            $this->error = lang('auth.invalid_credentials');
161
-            return false;
162
-        }
163
-
164
-        // We do not want to force case-sensitivity on things
165
-        // like username and email for usability sake.
166
-        if (! empty($credentials['email']))
167
-        {
168
-            $credentials['email'] = strtolower($credentials['email']);
169
-        }
170
-
171
-        // Can we find a user with those credentials?
172
-        $user = $this->user_model->as_array()
173
-                                 ->where($credentials)
174
-                                 ->first();
175
-
176
-        // If the user is throttled due to too many invalid logins
177
-        // or the system is under attack, kick them back.
178
-
179
-        // If throttling time is above zero, we can't allow
180
-        // logins now.
181
-        $time = (int)$this->isThrottled($user);
182
-        if ($time > 0)
183
-        {
184
-            $this->error = sprintf(lang('auth.throttled'), $time);
185
-            return false;
186
-        }
187
-
188
-        // Get ip address
189
-        $ip_address = $this->ci->input->ip_address();
190
-
191
-        if (! $user)
192
-        {
193
-            $this->error = lang('auth.invalid_user');
194
-            $this->ci->login_model->recordLoginAttempt($ip_address);
195
-            return false;
196
-        }
197
-
198
-        // Now, try matching the passwords.
199
-        $result =  password_verify($password, $user['password_hash']);
200
-
201
-        if (! $result)
202
-        {
203
-            $this->error = lang('auth.invalid_password');
204
-            $this->ci->login_model->recordLoginAttempt($ip_address, $user['id']);
205
-            return false;
206
-        }
207
-
208
-        // Check to see if the password needs to be rehashed.
209
-        // This would be due to the hash algorithm or hash
210
-        // cost changing since the last time that a user
211
-        // logged in.
212
-        if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT, ['cost' => config_item('auth.hash_cost')] ))
213
-        {
214
-            $new_hash = Password::hashPassword($password);
215
-            $this->user_model->skip_validation()
216
-                             ->update($user['id'], ['password_hash' => $new_hash]);
217
-            unset($new_hash);
218
-        }
219
-
220
-        // Is the user active?
221
-        if (! $user['active'])
222
-        {
223
-            $this->error = lang('auth.inactive_account');
224
-            return false;
225
-        }
226
-
227
-        return $return_user ? $user : true;
228
-    }
229
-
230
-    //--------------------------------------------------------------------
231
-
232
-    /**
233
-     * Logs a user out and removes all session information.
234
-     *
235
-     * @return mixed
236
-     */
237
-    public function logout()
238
-    {
239
-        $this->ci->load->helper('cookie');
240
-
241
-        if (! Events::trigger('beforeLogout', [$this->user]))
242
-        {
243
-            return false;
244
-        }
245
-
246
-        // Destroy the session data - but ensure a session is still
247
-        // available for flash messages, etc.
248
-        if (isset($_SESSION))
249
-        {
250
-            foreach ( $_SESSION as $key => $value )
251
-            {
252
-                $_SESSION[ $key ] = NULL;
253
-                unset( $_SESSION[ $key ] );
254
-            }
255
-        }
256
-        // Also, regenerate the session ID for a touch of added safety.
257
-        $this->ci->session->sess_regenerate(true);
258
-
259
-        // Take care of any rememberme functionality.
260
-        if (config_item('auth.allow_remembering'))
261
-        {
262
-            $token = get_cookie('remember');
263
-
264
-            $this->invalidateRememberCookie($this->user['email'], $token);
265
-        }
266
-    }
267
-
268
-    //--------------------------------------------------------------------
269
-
270
-    /**
271
-     * Checks whether a user is logged in or not.
272
-     *
273
-     * @return bool
274
-     */
275
-    public function isLoggedIn()
276
-    {
277
-        $id = $this->ci->session->userdata('logged_in');
278
-
279
-        if (! $id)
280
-        {
281
-            return false;
282
-        }
283
-
284
-        // If the user var hasn't been filled in, we need to fill it in,
285
-        // since this method will typically be used as the only method
286
-        // to determine whether a user is logged in or not.
287
-        if (! $this->user)
288
-        {
289
-            $this->user = $this->user_model->as_array()
290
-                                           ->find_by('id', (int)$id);
291
-
292
-            if (empty($this->user))
293
-            {
294
-                return false;
295
-            }
296
-        }
297
-
298
-        // If logged in, ensure cache control
299
-        // headers are in place
300
-        $this->setHeaders();
301
-
302
-        return true;
303
-    }
304
-
305
-    //--------------------------------------------------------------------
306
-
307
-    /**
308
-     * Attempts to log a user in based on the "remember me" cookie.
309
-     *
310
-     * @return bool
311
-     */
312
-    public function viaRemember()
313
-    {
314
-        if (! config_item('auth.allow_remembering'))
315
-        {
316
-            return false;
317
-        }
318
-
319
-        $this->ci->load->helper('cookie');
320
-
321
-        if (! $token = get_cookie('remember'))
322
-        {
323
-            return false;
324
-        }
325
-
326
-        // Attempt to match the token against our auth_tokens table.
327
-        $query = $this->ci->db->where('hash', $this->ci->login_model->hashRememberToken($token))
328
-                              ->get('auth_tokens');
329
-
330
-        if (! $query->num_rows())
331
-        {
332
-            return false;
333
-        }
334
-
335
-        // Grab the user
336
-        $email = $query->row()->email;
337
-
338
-        $user = $this->user_model->as_array()
339
-                                 ->find_by('email', $email);
340
-
341
-        $this->loginUser($user);
342
-
343
-        // We only want our remember me tokens to be valid
344
-        // for a single use.
345
-        $this->refreshRememberCookie($user, $token);
346
-
347
-        return true;
348
-    }
349
-
350
-    //--------------------------------------------------------------------
351
-
352
-    /**
353
-     * Registers a new user and handles activation method.
354
-     *
355
-     * @param $user_data
356
-     * @return bool
357
-     */
358
-    public function registerUser($user_data)
359
-    {
360
-        // Anything special needed for Activation?
361
-        $method = config_item('auth.activation_method');
362
-
363
-        $user_data['active'] = $method == 'auto' ? 1 : 0;
364
-
365
-        // If via email, we need to generate a hash
366
-        $this->ci->load->helper('string');
367
-        $token = random_string('alnum', 24);
368
-        $user_data['activate_hash'] = hash('sha1', config_item('auth.salt') . $token);
369
-
370
-        // Email should NOT be case sensitive.
371
-        if (! empty($user_data['email']))
372
-        {
373
-            $user_data['email'] = strtolower($user_data['email']);
374
-        }
375
-
376
-        // Save the user
377
-        if (! $id = $this->user_model->insert($user_data))
378
-        {
379
-            $this->error = $this->user_model->error();
380
-            return false;
381
-        }
382
-
383
-        $data = [
384
-            'user_id' => $id,
385
-            'email'   => $user_data['email'],
386
-            'token'   => $token,
387
-            'method'  => $method
388
-        ];
389
-
390
-        Events::trigger('didRegisterUser', [$data]);
391
-
392
-        return true;
393
-    }
394
-
395
-    //--------------------------------------------------------------------
396
-
397
-    /**
398
-     * Used to verify the user values and activate a user so they can
399
-     * visit the site.
400
-     *
401
-     * @param $data
402
-     * @return bool
403
-     */
404
-    public function activateUser($data)
405
-    {
406
-        $post = [
407
-            'email'         => $data['email'],
408
-            'activate_hash' => hash('sha1', config_item('auth.salt') . $data['code'])
409
-        ];
410
-
411
-        $user = $this->user_model->where($post)
412
-                                 ->first();
413
-
414
-        if (! $user) {
415
-            $this->error = $this->user_model->error() ? $this->user_model->error() : lang('auth.activate_no_user');
416
-
417
-            return false;
418
-        }
419
-
420
-        if (! $this->user_model->update($user->id, ['active' => 1, 'activate_hash' => null]))
421
-        {
422
-            $this->error = $this->user_model->error();
423
-            return false;
424
-        }
425
-
426
-        Events::trigger('didActivate', [(array)$user]);
427
-
428
-        return true;
429
-    }
430
-
431
-    //--------------------------------------------------------------------
432
-
433
-    /**
434
-     * Used to allow manual activation of a user with a known ID.
435
-     *
436
-     * @param $id
437
-     * @return bool
438
-     */
439
-    public function activateUserById($id)
440
-    {
441
-        if (! $this->user_model->update($id, ['active' => 1, 'activate_hash' => null]))
442
-        {
443
-            $this->error = $this->user_model->error();
444
-            return false;
445
-        }
446
-
447
-        Events::trigger('didActivate', [$this->user_model->as_array()->find($id)]);
448
-
449
-        return true;
450
-    }
451
-
452
-    //--------------------------------------------------------------------
453
-
454
-    /**
455
-     * Grabs the current user object. Returns NULL if nothing found.
456
-     *
457
-     * @return array|null
458
-     */
459
-    public function user()
460
-    {
461
-        return $this->user;
462
-    }
463
-
464
-    //--------------------------------------------------------------------
465
-
466
-    /**
467
-     * A convenience method to grab the current user's ID.
468
-     *
469
-     * @return int|null
470
-     */
471
-    public function id()
472
-    {
473
-        if (! is_array($this->user) || empty($this->user['id']))
474
-        {
475
-            return null;
476
-        }
477
-
478
-        return (int)$this->user['id'];
479
-    }
480
-
481
-    //--------------------------------------------------------------------
482
-
483
-    /**
484
-     * Checks to see if the user is currently being throttled.
485
-     *
486
-     *  - If they are NOT, will return FALSE.
487
-     *  - If they ARE, will return the number of seconds until they can try again.
488
-     *
489
-     * @param $user
490
-     * @return mixed
491
-     */
492
-    public function isThrottled($user)
493
-    {
494
-        // Not throttling? Get outta here!
495
-        if (! config_item('auth.allow_throttling'))
496
-        {
497
-            return false;
498
-        }
499
-
500
-        // Get user_id
501
-        $user_id = $user ? $user['id'] : null;
55
+	protected $ci;
56
+
57
+	protected $user = null;
58
+
59
+	public $user_model = null;
60
+
61
+	public $error = null;
62
+
63
+	//--------------------------------------------------------------------
64
+
65
+	public function __construct( $ci=null )
66
+	{
67
+		if ($ci)
68
+		{
69
+			$this->ci= $ci;
70
+		}
71
+		else
72
+		{
73
+			$this->ci =& get_instance();
74
+		}
75
+
76
+		// Get our compatibility password file loaded up.
77
+		if (! function_exists('password_hash'))
78
+		{
79
+			require_once dirname(__FILE__) .'password.php';
80
+		}
81
+
82
+		if (empty($this->ci->session))
83
+		{
84
+			$this->ci->load->library('session');
85
+		}
86
+
87
+		$this->ci->config->load('auth');
88
+		$this->ci->load->model('auth/login_model');
89
+		$this->ci->load->language('auth/auth');
90
+	}
91
+
92
+	//--------------------------------------------------------------------
93
+
94
+	/**
95
+	 * Attempt to log a user into the system.
96
+	 *
97
+	 * $credentials is an array of key/value pairs needed to log the user in.
98
+	 * This is often email/password, or username/password.
99
+	 *
100
+	 * @param array $credentials
101
+	 * @param bool  $remember
102
+	 * @return bool|mixed
103
+	 */
104
+	public function login($credentials, $remember=false)
105
+	{
106
+		$user = $this->validate($credentials, true);
107
+
108
+		if (! $user)
109
+		{
110
+			$this->user = null;
111
+			return $user;
112
+		}       
113
+
114
+		$this->loginUser($user);
115
+
116
+		if ($remember)
117
+		{
118
+			$this->rememberUser($user);
119
+		}
120
+
121
+		Events::trigger('didLogin', [$user]);
122
+
123
+		return true;
124
+	}
125
+
126
+	//--------------------------------------------------------------------
127
+
128
+	/**
129
+	 * Validates user login information without logging them in.
130
+	 *
131
+	 * $credentials is an array of key/value pairs needed to log the user in.
132
+	 * This is often email/password, or username/password.
133
+	 *
134
+	 * @param $credentials
135
+	 * @param bool $return_user
136
+	 * @return mixed
137
+	 */
138
+	public function validate($credentials, $return_user=false)
139
+	{
140
+		// Can't validate without a password.
141
+		if (empty($credentials['password']) || count($credentials) < 2)
142
+		{
143
+			return null;
144
+		}
145
+
146
+		$password = $credentials['password'];
147
+		unset($credentials['password']);
148
+
149
+		// We should only be allowed 1 single other credential to
150
+		// test against.
151
+		if (count($credentials) > 1)
152
+		{
153
+			$this->error = lang('auth.too_many_credentials');
154
+			return false;
155
+		}
156
+
157
+		// Ensure that the fields are allowed validation fields
158
+		if (! in_array(key($credentials), config_item('auth.valid_fields')) )
159
+		{
160
+			$this->error = lang('auth.invalid_credentials');
161
+			return false;
162
+		}
163
+
164
+		// We do not want to force case-sensitivity on things
165
+		// like username and email for usability sake.
166
+		if (! empty($credentials['email']))
167
+		{
168
+			$credentials['email'] = strtolower($credentials['email']);
169
+		}
170
+
171
+		// Can we find a user with those credentials?
172
+		$user = $this->user_model->as_array()
173
+								 ->where($credentials)
174
+								 ->first();
175
+
176
+		// If the user is throttled due to too many invalid logins
177
+		// or the system is under attack, kick them back.
178
+
179
+		// If throttling time is above zero, we can't allow
180
+		// logins now.
181
+		$time = (int)$this->isThrottled($user);
182
+		if ($time > 0)
183
+		{
184
+			$this->error = sprintf(lang('auth.throttled'), $time);
185
+			return false;
186
+		}
187
+
188
+		// Get ip address
189
+		$ip_address = $this->ci->input->ip_address();
190
+
191
+		if (! $user)
192
+		{
193
+			$this->error = lang('auth.invalid_user');
194
+			$this->ci->login_model->recordLoginAttempt($ip_address);
195
+			return false;
196
+		}
197
+
198
+		// Now, try matching the passwords.
199
+		$result =  password_verify($password, $user['password_hash']);
200
+
201
+		if (! $result)
202
+		{
203
+			$this->error = lang('auth.invalid_password');
204
+			$this->ci->login_model->recordLoginAttempt($ip_address, $user['id']);
205
+			return false;
206
+		}
207
+
208
+		// Check to see if the password needs to be rehashed.
209
+		// This would be due to the hash algorithm or hash
210
+		// cost changing since the last time that a user
211
+		// logged in.
212
+		if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT, ['cost' => config_item('auth.hash_cost')] ))
213
+		{
214
+			$new_hash = Password::hashPassword($password);
215
+			$this->user_model->skip_validation()
216
+							 ->update($user['id'], ['password_hash' => $new_hash]);
217
+			unset($new_hash);
218
+		}
219
+
220
+		// Is the user active?
221
+		if (! $user['active'])
222
+		{
223
+			$this->error = lang('auth.inactive_account');
224
+			return false;
225
+		}
226
+
227
+		return $return_user ? $user : true;
228
+	}
229
+
230
+	//--------------------------------------------------------------------
231
+
232
+	/**
233
+	 * Logs a user out and removes all session information.
234
+	 *
235
+	 * @return mixed
236
+	 */
237
+	public function logout()
238
+	{
239
+		$this->ci->load->helper('cookie');
240
+
241
+		if (! Events::trigger('beforeLogout', [$this->user]))
242
+		{
243
+			return false;
244
+		}
245
+
246
+		// Destroy the session data - but ensure a session is still
247
+		// available for flash messages, etc.
248
+		if (isset($_SESSION))
249
+		{
250
+			foreach ( $_SESSION as $key => $value )
251
+			{
252
+				$_SESSION[ $key ] = NULL;
253
+				unset( $_SESSION[ $key ] );
254
+			}
255
+		}
256
+		// Also, regenerate the session ID for a touch of added safety.
257
+		$this->ci->session->sess_regenerate(true);
258
+
259
+		// Take care of any rememberme functionality.
260
+		if (config_item('auth.allow_remembering'))
261
+		{
262
+			$token = get_cookie('remember');
263
+
264
+			$this->invalidateRememberCookie($this->user['email'], $token);
265
+		}
266
+	}
267
+
268
+	//--------------------------------------------------------------------
269
+
270
+	/**
271
+	 * Checks whether a user is logged in or not.
272
+	 *
273
+	 * @return bool
274
+	 */
275
+	public function isLoggedIn()
276
+	{
277
+		$id = $this->ci->session->userdata('logged_in');
278
+
279
+		if (! $id)
280
+		{
281
+			return false;
282
+		}
283
+
284
+		// If the user var hasn't been filled in, we need to fill it in,
285
+		// since this method will typically be used as the only method
286
+		// to determine whether a user is logged in or not.
287
+		if (! $this->user)
288
+		{
289
+			$this->user = $this->user_model->as_array()
290
+										   ->find_by('id', (int)$id);
291
+
292
+			if (empty($this->user))
293
+			{
294
+				return false;
295
+			}
296
+		}
297
+
298
+		// If logged in, ensure cache control
299
+		// headers are in place
300
+		$this->setHeaders();
301
+
302
+		return true;
303
+	}
304
+
305
+	//--------------------------------------------------------------------
306
+
307
+	/**
308
+	 * Attempts to log a user in based on the "remember me" cookie.
309
+	 *
310
+	 * @return bool
311
+	 */
312
+	public function viaRemember()
313
+	{
314
+		if (! config_item('auth.allow_remembering'))
315
+		{
316
+			return false;
317
+		}
318
+
319
+		$this->ci->load->helper('cookie');
320
+
321
+		if (! $token = get_cookie('remember'))
322
+		{
323
+			return false;
324
+		}
325
+
326
+		// Attempt to match the token against our auth_tokens table.
327
+		$query = $this->ci->db->where('hash', $this->ci->login_model->hashRememberToken($token))
328
+							  ->get('auth_tokens');
329
+
330
+		if (! $query->num_rows())
331
+		{
332
+			return false;
333
+		}
334
+
335
+		// Grab the user
336
+		$email = $query->row()->email;
337
+
338
+		$user = $this->user_model->as_array()
339
+								 ->find_by('email', $email);
340
+
341
+		$this->loginUser($user);
342
+
343
+		// We only want our remember me tokens to be valid
344
+		// for a single use.
345
+		$this->refreshRememberCookie($user, $token);
346
+
347
+		return true;
348
+	}
349
+
350
+	//--------------------------------------------------------------------
351
+
352
+	/**
353
+	 * Registers a new user and handles activation method.
354
+	 *
355
+	 * @param $user_data
356
+	 * @return bool
357
+	 */
358
+	public function registerUser($user_data)
359
+	{
360
+		// Anything special needed for Activation?
361
+		$method = config_item('auth.activation_method');
362
+
363
+		$user_data['active'] = $method == 'auto' ? 1 : 0;
364
+
365
+		// If via email, we need to generate a hash
366
+		$this->ci->load->helper('string');
367
+		$token = random_string('alnum', 24);
368
+		$user_data['activate_hash'] = hash('sha1', config_item('auth.salt') . $token);
369
+
370
+		// Email should NOT be case sensitive.
371
+		if (! empty($user_data['email']))
372
+		{
373
+			$user_data['email'] = strtolower($user_data['email']);
374
+		}
375
+
376
+		// Save the user
377
+		if (! $id = $this->user_model->insert($user_data))
378
+		{
379
+			$this->error = $this->user_model->error();
380
+			return false;
381
+		}
382
+
383
+		$data = [
384
+			'user_id' => $id,
385
+			'email'   => $user_data['email'],
386
+			'token'   => $token,
387
+			'method'  => $method
388
+		];
389
+
390
+		Events::trigger('didRegisterUser', [$data]);
391
+
392
+		return true;
393
+	}
394
+
395
+	//--------------------------------------------------------------------
396
+
397
+	/**
398
+	 * Used to verify the user values and activate a user so they can
399
+	 * visit the site.
400
+	 *
401
+	 * @param $data
402
+	 * @return bool
403
+	 */
404
+	public function activateUser($data)
405
+	{
406
+		$post = [
407
+			'email'         => $data['email'],
408
+			'activate_hash' => hash('sha1', config_item('auth.salt') . $data['code'])
409
+		];
410
+
411
+		$user = $this->user_model->where($post)
412
+								 ->first();
413
+
414
+		if (! $user) {
415
+			$this->error = $this->user_model->error() ? $this->user_model->error() : lang('auth.activate_no_user');
416
+
417
+			return false;
418
+		}
419
+
420
+		if (! $this->user_model->update($user->id, ['active' => 1, 'activate_hash' => null]))
421
+		{
422
+			$this->error = $this->user_model->error();
423
+			return false;
424
+		}
425
+
426
+		Events::trigger('didActivate', [(array)$user]);
427
+
428
+		return true;
429
+	}
430
+
431
+	//--------------------------------------------------------------------
432
+
433
+	/**
434
+	 * Used to allow manual activation of a user with a known ID.
435
+	 *
436
+	 * @param $id
437
+	 * @return bool
438
+	 */
439
+	public function activateUserById($id)
440
+	{
441
+		if (! $this->user_model->update($id, ['active' => 1, 'activate_hash' => null]))
442
+		{
443
+			$this->error = $this->user_model->error();
444
+			return false;
445
+		}
446
+
447
+		Events::trigger('didActivate', [$this->user_model->as_array()->find($id)]);
448
+
449
+		return true;
450
+	}
451
+
452
+	//--------------------------------------------------------------------
453
+
454
+	/**
455
+	 * Grabs the current user object. Returns NULL if nothing found.
456
+	 *
457
+	 * @return array|null
458
+	 */
459
+	public function user()
460
+	{
461
+		return $this->user;
462
+	}
463
+
464
+	//--------------------------------------------------------------------
465
+
466
+	/**
467
+	 * A convenience method to grab the current user's ID.
468
+	 *
469
+	 * @return int|null
470
+	 */
471
+	public function id()
472
+	{
473
+		if (! is_array($this->user) || empty($this->user['id']))
474
+		{
475
+			return null;
476
+		}
477
+
478
+		return (int)$this->user['id'];
479
+	}
480
+
481
+	//--------------------------------------------------------------------
482
+
483
+	/**
484
+	 * Checks to see if the user is currently being throttled.
485
+	 *
486
+	 *  - If they are NOT, will return FALSE.
487
+	 *  - If they ARE, will return the number of seconds until they can try again.
488
+	 *
489
+	 * @param $user
490
+	 * @return mixed
491
+	 */
492
+	public function isThrottled($user)
493
+	{
494
+		// Not throttling? Get outta here!
495
+		if (! config_item('auth.allow_throttling'))
496
+		{
497
+			return false;
498
+		}
499
+
500
+		// Get user_id
501
+		$user_id = $user ? $user['id'] : null;
502 502
         
503
-        // Get ip address
504
-        $ip_address = $this->ci->input->ip_address();
505
-
506
-        // Have any attempts been made?
507
-        $attempts = $this->ci->login_model->countLoginAttempts($ip_address, $user_id);
508
-
509
-        // Grab the amount of time to add if the system thinks we're
510
-        // under a distributed brute force attack.
511
-        // Affect users that have at least 1 failure login attempt
512
-        $dbrute_time = ($attempts === 0) ? 0 : $this->ci->login_model->distributedBruteForceTime();
513
-
514
-        // If this user was found to possibly be under a brute
515
-        // force attack, their account would have been banned
516
-        // for 15 minutes.
517
-        if ($time = isset($_SESSION['bruteBan']) ? $_SESSION['bruteBan'] : false)
518
-        {
519
-            // If the current time is less than the
520
-            // the ban expiration, plus any distributed time
521
-            // then the user can't login just yet.
522
-            if ($time + $dbrute_time > time())
523
-            {
524
-                // The user is banned still...
525
-                $this->error = lang('auth.bruteBan_notice');
526
-                return ($time + $dbrute_time) - time();
527
-            }
528
-
529
-            // Still here? The the ban time is over...
530
-            unset($_SESSION['bruteBan']);
531
-        }
532
-
533
-        // Grab the time of last attempt and
534
-        // determine if we're throttled by amount of time passed.
535
-        $last_time = $this->ci->login_model->lastLoginAttemptTime($ip_address, $user_id);
536
-
537
-        $allowed = config_item('auth.allowed_login_attempts');
538
-
539
-        // We're not throttling if there are 0 attempts or
540
-        // the number is less than or equal to the allowed free attempts
541
-        if ($attempts === 0 || $attempts < $allowed)
542
-        {
543
-            // Before we can say there's nothing up here,
544
-            // we need to check dbrute time.
545
-            $time_left = $last_time + $dbrute_time - time();
546
-
547
-            if ($time_left > 0)
548
-            {
549
-                return $time_left;
550
-            }
551
-
552
-            return false;
553
-        }
554
-
555
-        // If the number of attempts is excessive (above 100) we need
556
-        // to check the elapsed time of all of these attacks. If they are
557
-        // less than 1 minute it's obvious this is a brute force attack,
558
-        // so we'll set a session flag and block that user for 15 minutes.
559
-        if ($attempts > 100 && $this->ci->login_model->isBruteForced($ip_address, $user_id))
560
-        {
561
-            $this->error = lang('auth.bruteBan_notice');
562
-
563
-            $ban_time = 60 * 15;    // 15 minutes
564
-            $_SESSION['bruteBan'] = time() + $ban_time;
565
-            return $ban_time;
566
-        }
567
-
568
-        // Get our allowed attempts out of the picture.
569
-        $attempts = $attempts - $allowed;
570
-
571
-        $max_time = config_item('auth.max_throttle_time');
572
-
573
-        $add_time = 5 * pow(2, $attempts);
574
-
575
-        if ($add_time > $max_time)
576
-        {
577
-            $add_time = $max_time;
578
-        }
579
-
580
-        $next_time = $last_time + $add_time + $dbrute_time;
581
-
582
-        $current = time();
583
-
584
-        // We are NOT throttled if we are already
585
-        // past the allowed time.
586
-        if ($current > $next_time)
587
-        {
588
-            return false;
589
-        }
590
-
591
-        return $next_time - $current;
592
-    }
593
-
594
-    //--------------------------------------------------------------------
595
-
596
-    /**
597
-     * Sends a password reset link email to the user associated with
598
-     * the passed in $email.
599
-     *
600
-     * @param $email
601
-     * @return mixed
602
-     */
603
-    public function remindUser($email)
604
-    {
605
-        // Emails should NOT be case sensitive.
606
-        $email = strtolower($email);
607
-
608
-        // Is it a valid user?
609
-        $user = $this->user_model->find_by('email', $email);
610
-
611
-        if (! $user)
612
-        {
613
-            $this->error = lang('auth.invalid_email');
614
-            return false;
615
-        }
616
-
617
-        // Generate/store our codes
618
-        $this->ci->load->helper('string');
619
-        $token = random_string('alnum', 24);
620
-        $hash = hash('sha1', config_item('auth.salt') .$token);
621
-
622
-        $result = $this->user_model->update($user->id, ['reset_hash' => $hash]);
623
-
624
-        if (! $result)
625
-        {
626
-            $this->error = $this->user_model->error();
627
-            return false;
628
-        }
629
-
630
-        Events::trigger('didRemindUser', [(array)$user, $token]);
631
-
632
-        return true;
633
-    }
634
-
635
-    //--------------------------------------------------------------------
636
-
637
-    /**
638
-     * Validates the credentials provided and, if valid, resets the password.
639
-     *
640
-     * The $credentials array MUST contain a 'code' key with the string to
641
-     * hash and check against the reset_hash.
642
-     *
643
-     * @param $credentials
644
-     * @param $password
645
-     * @param $passConfirm
646
-     * @return mixed
647
-     */
648
-    public function resetPassword($credentials, $password, $passConfirm)
649
-    {
650
-        if (empty($credentials['code']))
651
-        {
652
-            $this->error = lang('auth.need_reset_code');
653
-            return false;
654
-        }
655
-
656
-        // Generate a hash to match against the table.
657
-        $reset_hash = hash('sha1', config_item('auth.salt') .$credentials['code']);
658
-        unset($credentials['code']);
659
-
660
-        if (! empty($credentials['email']))
661
-        {
662
-            $credentials['email'] = strtolower($credentials['email']);
663
-        }
664
-
665
-        // Is there a matching user?
666
-        $user = $this->user_model->as_array()
667
-                                 ->where($credentials)
668
-                                 ->first();
669
-
670
-        // If throttling time is above zero, we can't allow
671
-        // logins now.
672
-        $time = (int)$this->isThrottled($user);
673
-        if ($time > 0)
674
-        {
675
-            $this->error = sprintf(lang('auth.throttled'), $time);
676
-            return false;
677
-        }
678
-
679
-        // Get ip address
680
-        $ip_address = $this->ci->input->ip_address();
681
-
682
-        if (! $user)
683
-        {
684
-            $this->error = lang('auth.reset_no_user');
685
-            $this->ci->login_model->recordLoginAttempt($ip_address);
686
-            return false;
687
-        }
688
-
689
-        // Is generated reset_hash string matches one from the table?
690
-        if ($reset_hash !== $user['reset_hash'])
691
-        {
692
-            $this->error = lang('auth.reset_no_user');
693
-            $this->ci->login_model->recordLoginAttempt($ip_address, $user['id']);
694
-            return false;
695
-        }
696
-
697
-        // Update their password and reset their reset_hash
698
-        $data = [
699
-            'password'     => $password,
700
-            'pass_confirm' => $passConfirm,
701
-            'reset_hash'   => null
702
-        ];
703
-
704
-        if (! $this->user_model->update($user['id'], $data))
705
-        {
706
-            $this->error = $this->user_model->error();
707
-            return false;
708
-        }
709
-
710
-        // Clear our login attempts
711
-        $this->ci->login_model->purgeLoginAttempts($ip_address, $user['id']);
712
-
713
-        Events::trigger('didResetPassword', [$user]);
714
-
715
-        return true;
716
-    }
717
-
718
-    //--------------------------------------------------------------------
719
-
720
-    /**
721
-     * Provides a way for implementations to allow new statuses to be set
722
-     * on the user. The details will vary based upon implementation, but
723
-     * will often allow for banning or suspending users.
724
-     *
725
-     * @param $newStatus
726
-     * @param null $message
727
-     * @return mixed
728
-     */
729
-    public function changeStatus($newStatus, $message=null)
730
-    {
731
-        // todo actually record new users status!
732
-    }
733
-
734
-    //--------------------------------------------------------------------
735
-
736
-    /**
737
-     * Allows the consuming application to pass in a reference to the
738
-     * model that should be used.
739
-     *
740
-     * The model MUST extend Myth\Models\CIDbModel.
741
-     *
742
-     * @param $model
743
-     * @param bool $allow_any_parent
744
-     * @return mixed
745
-     */
746
-    public function useModel($model, $allow_any_parent=false)
747
-    {
748
-        if (! $allow_any_parent && get_parent_class($model) != 'Myth\Models\CIDbModel')
749
-        {
750
-            throw new \RuntimeException('Models passed into LocalAuthenticate MUST extend Myth\Models\CIDbModel');
751
-        }
752
-
753
-        $this->user_model =& $model;
754
-
755
-        return $this;
756
-    }
757
-
758
-    //--------------------------------------------------------------------
759
-
760
-    public function error()
761
-    {
762
-        if (validation_errors())
763
-        {
764
-            return validation_errors();
765
-        }
766
-
767
-        return $this->error;
768
-    }
769
-
770
-    //--------------------------------------------------------------------
771
-
772
-    //--------------------------------------------------------------------
773
-    // Login Records
774
-    //--------------------------------------------------------------------
775
-
776
-    /**
777
-     * Purges all login attempt records from the database.
778
-     *
779
-     * @param null $ip_address
780
-     * @param null $user_id
781
-     */
782
-    public function purgeLoginAttempts($ip_address = null, $user_id = null)
783
-    {
784
-        $this->ci->login_model->purgeLoginAttempts($ip_address, $user_id);
785
-
786
-        // @todo record activity of login attempts purge.
787
-        Events::trigger('didPurgeLoginAttempts', [$email]);
788
-    }
789
-
790
-    //--------------------------------------------------------------------
791
-
792
-    /**
793
-     * Purges all remember tokens for a single user. Effectively logs
794
-     * a user out of all devices. Intended to allow users to log themselves
795
-     * out of all devices as a security measure.
796
-     *
797
-     * @param $email
798
-     */
799
-    public function purgeRememberTokens($email)
800
-    {
801
-        // Emails should NOT be case sensitive.
802
-        $email = strtolower($email);
803
-
804
-        $this->ci->login_model->purgeRememberTokens($email);
805
-
806
-        // todo record activity of remember me purges.
807
-        Events::trigger('didPurgeRememberTokens', [$email]);
808
-    }
809
-
810
-    //--------------------------------------------------------------------
811
-
812
-    //--------------------------------------------------------------------
813
-    // Protected Methods
814
-    //--------------------------------------------------------------------
815
-
816
-    /**
817
-     * Check if Allow Persistent Login Cookies is enable
818
-     *
819
-     * @param $user
820
-     */
821
-    protected function rememberUser($user)
822
-    {
823
-        if (! config_item('auth.allow_remembering'))
824
-        {
825
-            log_message('debug', 'Auth library set to refuse "Remember Me" functionality.');
826
-            return false;
827
-        }
828
-
829
-        $this->refreshRememberCookie($user);
830
-    }
831
-
832
-    //--------------------------------------------------------------------
833
-
834
-    /**
835
-     * Invalidates the current rememberme cookie/database entry, creates
836
-     * a new one, stores it and returns the new value.
837
-     *
838
-     * @param $user
839
-     * @param null $token
840
-     * @return mixed
841
-     */
842
-    protected function refreshRememberCookie($user, $token=null)
843
-    {
844
-        $this->ci->load->helper('cookie');
845
-
846
-        // If a token is passed in, we know we're removing the
847
-        // old one.
848
-        if (! empty($token))
849
-        {
850
-            $this->invalidateRememberCookie($user['email'], $token);
851
-        }
852
-
853
-        $new_token = $this->ci->login_model->generateRememberToken($user);
854
-
855
-        // Save the token to the database.
856
-        $data = [
857
-            'email'   => $user['email'],
858
-            'hash'    => sha1(config_item('auth.salt') . $new_token),
859
-            'created' => date('Y-m-d H:i:s')
860
-        ];
861
-
862
-        $this->ci->db->insert('auth_tokens', $data);
863
-
864
-        // Create the cookie
865
-        set_cookie(
866
-            'remember',                             // Cookie Name
867
-            $new_token,                             // Value
868
-            config_item('auth.remember_length'),    // # Seconds until it expires
869
-            config_item('cookie_domain'),
870
-            config_item('cookie_path'),
871
-            config_item('cookie_prefix'),
872
-            false,                                  // Only send over HTTPS?
873
-            true                                    // Hide from Javascript?
874
-        );
875
-
876
-        return $new_token;
877
-    }
878
-
879
-    //--------------------------------------------------------------------
880
-
881
-    /**
882
-     * Deletes any current remember me cookies and database entries.
883
-     *
884
-     * @param $email
885
-     * @param $token
886
-     * @return string The new token (not the hash).
887
-     */
888
-    protected function invalidateRememberCookie($email, $token)
889
-    {
890
-        // Emails should NOT be case sensitive.
891
-        $email = strtolower($email);
892
-
893
-        // Remove from the database
894
-        $this->ci->login_model->deleteRememberToken($email, $token);
895
-
896
-        // Remove the cookie
897
-        delete_cookie(
898
-            'remember',
899
-            config_item('cookie_domain'),
900
-            config_item('cookie_path'),
901
-            config_item('cookie_prefix')
902
-        );
903
-    }
904
-
905
-    //--------------------------------------------------------------------
906
-
907
-    /**
908
-     * Handles the nitty gritty of actually logging our user into the system.
909
-     * Does NOT perform the authentication, just sets the system up so that
910
-     * it knows we're here.
911
-     *
912
-     * @param $user
913
-     */
914
-    protected function loginUser($user)
915
-    {
916
-        // Save the user for later access
917
-        $this->user = $user;
918
-
919
-        // Get ip address
920
-        $ip_address = $this->ci->input->ip_address();
921
-
922
-        // Regenerate the session ID to help protect
923
-        // against session fixation
924
-        $this->ci->session->sess_regenerate();
925
-
926
-        // Let the session know that we're logged in.
927
-        $this->ci->session->set_userdata('logged_in', $user['id']);
928
-
929
-        // Clear our login attempts
930
-        $this->ci->login_model->purgeLoginAttempts($ip_address, $user['id']);
931
-
932
-        // Record a new Login
933
-        $this->ci->login_model->recordLogin($user);
934
-
935
-        // If logged in, ensure cache control
936
-        // headers are in place
937
-        $this->setHeaders();
938
-
939
-        // We'll give a 20% chance to need to do a purge since we
940
-        // don't need to purge THAT often, it's just a maintenance issue.
941
-        // to keep the table from getting out of control.
942
-        if (mt_rand(1, 100) < 20)
943
-        {
944
-            $this->ci->login_model->purgeOldRememberTokens();
945
-        }
946
-    }
947
-
948
-    //--------------------------------------------------------------------
949
-
950
-    /**
951
-     * Sets the headers to ensure that pages are not cached when a user
952
-     * is logged in, helping to protect against logging out and then
953
-     * simply hitting the Back button on the browser and getting private
954
-     * information because the page was loaded from cache.
955
-     */
956
-    protected function setHeaders()
957
-    {
958
-        $this->ci->output->set_header('Cache-Control: no-store, no-cache, must-revalidate');
959
-        $this->ci->output->set_header('Cache-Control: post-check=0, pre-check=0');
960
-        $this->ci->output->set_header('Pragma: no-cache');
961
-    }
962
-
963
-    //--------------------------------------------------------------------
503
+		// Get ip address
504
+		$ip_address = $this->ci->input->ip_address();
505
+
506
+		// Have any attempts been made?
507
+		$attempts = $this->ci->login_model->countLoginAttempts($ip_address, $user_id);
508
+
509
+		// Grab the amount of time to add if the system thinks we're
510
+		// under a distributed brute force attack.
511
+		// Affect users that have at least 1 failure login attempt
512
+		$dbrute_time = ($attempts === 0) ? 0 : $this->ci->login_model->distributedBruteForceTime();
513
+
514
+		// If this user was found to possibly be under a brute
515
+		// force attack, their account would have been banned
516
+		// for 15 minutes.
517
+		if ($time = isset($_SESSION['bruteBan']) ? $_SESSION['bruteBan'] : false)
518
+		{
519
+			// If the current time is less than the
520
+			// the ban expiration, plus any distributed time
521
+			// then the user can't login just yet.
522
+			if ($time + $dbrute_time > time())
523
+			{
524
+				// The user is banned still...
525
+				$this->error = lang('auth.bruteBan_notice');
526
+				return ($time + $dbrute_time) - time();
527
+			}
528
+
529
+			// Still here? The the ban time is over...
530
+			unset($_SESSION['bruteBan']);
531
+		}
532
+
533
+		// Grab the time of last attempt and
534
+		// determine if we're throttled by amount of time passed.
535
+		$last_time = $this->ci->login_model->lastLoginAttemptTime($ip_address, $user_id);
536
+
537
+		$allowed = config_item('auth.allowed_login_attempts');
538
+
539
+		// We're not throttling if there are 0 attempts or
540
+		// the number is less than or equal to the allowed free attempts
541
+		if ($attempts === 0 || $attempts < $allowed)
542
+		{
543
+			// Before we can say there's nothing up here,
544
+			// we need to check dbrute time.
545
+			$time_left = $last_time + $dbrute_time - time();
546
+
547
+			if ($time_left > 0)
548
+			{
549
+				return $time_left;
550
+			}
551
+
552
+			return false;
553
+		}
554
+
555
+		// If the number of attempts is excessive (above 100) we need
556
+		// to check the elapsed time of all of these attacks. If they are
557
+		// less than 1 minute it's obvious this is a brute force attack,
558
+		// so we'll set a session flag and block that user for 15 minutes.
559
+		if ($attempts > 100 && $this->ci->login_model->isBruteForced($ip_address, $user_id))
560
+		{
561
+			$this->error = lang('auth.bruteBan_notice');
562
+
563
+			$ban_time = 60 * 15;    // 15 minutes
564
+			$_SESSION['bruteBan'] = time() + $ban_time;
565
+			return $ban_time;
566
+		}
567
+
568
+		// Get our allowed attempts out of the picture.
569
+		$attempts = $attempts - $allowed;
570
+
571
+		$max_time = config_item('auth.max_throttle_time');
572
+
573
+		$add_time = 5 * pow(2, $attempts);
574
+
575
+		if ($add_time > $max_time)
576
+		{
577
+			$add_time = $max_time;
578
+		}
579
+
580
+		$next_time = $last_time + $add_time + $dbrute_time;
581
+
582
+		$current = time();
583
+
584
+		// We are NOT throttled if we are already
585
+		// past the allowed time.
586
+		if ($current > $next_time)
587
+		{
588
+			return false;
589
+		}
590
+
591
+		return $next_time - $current;
592
+	}
593
+
594
+	//--------------------------------------------------------------------
595
+
596
+	/**
597
+	 * Sends a password reset link email to the user associated with
598
+	 * the passed in $email.
599
+	 *
600
+	 * @param $email
601
+	 * @return mixed
602
+	 */
603
+	public function remindUser($email)
604
+	{
605
+		// Emails should NOT be case sensitive.
606
+		$email = strtolower($email);
607
+
608
+		// Is it a valid user?
609
+		$user = $this->user_model->find_by('email', $email);
610
+
611
+		if (! $user)
612
+		{
613
+			$this->error = lang('auth.invalid_email');
614
+			return false;
615
+		}
616
+
617
+		// Generate/store our codes
618
+		$this->ci->load->helper('string');
619
+		$token = random_string('alnum', 24);
620
+		$hash = hash('sha1', config_item('auth.salt') .$token);
621
+
622
+		$result = $this->user_model->update($user->id, ['reset_hash' => $hash]);
623
+
624
+		if (! $result)
625
+		{
626
+			$this->error = $this->user_model->error();
627
+			return false;
628
+		}
629
+
630
+		Events::trigger('didRemindUser', [(array)$user, $token]);
631
+
632
+		return true;
633
+	}
634
+
635
+	//--------------------------------------------------------------------
636
+
637
+	/**
638
+	 * Validates the credentials provided and, if valid, resets the password.
639
+	 *
640
+	 * The $credentials array MUST contain a 'code' key with the string to
641
+	 * hash and check against the reset_hash.
642
+	 *
643
+	 * @param $credentials
644
+	 * @param $password
645
+	 * @param $passConfirm
646
+	 * @return mixed
647
+	 */
648
+	public function resetPassword($credentials, $password, $passConfirm)
649
+	{
650
+		if (empty($credentials['code']))
651
+		{
652
+			$this->error = lang('auth.need_reset_code');
653
+			return false;
654
+		}
655
+
656
+		// Generate a hash to match against the table.
657
+		$reset_hash = hash('sha1', config_item('auth.salt') .$credentials['code']);
658
+		unset($credentials['code']);
659
+
660
+		if (! empty($credentials['email']))
661
+		{
662
+			$credentials['email'] = strtolower($credentials['email']);
663
+		}
664
+
665
+		// Is there a matching user?
666
+		$user = $this->user_model->as_array()
667
+								 ->where($credentials)
668
+								 ->first();
669
+
670
+		// If throttling time is above zero, we can't allow
671
+		// logins now.
672
+		$time = (int)$this->isThrottled($user);
673
+		if ($time > 0)
674
+		{
675
+			$this->error = sprintf(lang('auth.throttled'), $time);
676
+			return false;
677
+		}
678
+
679
+		// Get ip address
680
+		$ip_address = $this->ci->input->ip_address();
681
+
682
+		if (! $user)
683
+		{
684
+			$this->error = lang('auth.reset_no_user');
685
+			$this->ci->login_model->recordLoginAttempt($ip_address);
686
+			return false;
687
+		}
688
+
689
+		// Is generated reset_hash string matches one from the table?
690
+		if ($reset_hash !== $user['reset_hash'])
691
+		{
692
+			$this->error = lang('auth.reset_no_user');
693
+			$this->ci->login_model->recordLoginAttempt($ip_address, $user['id']);
694
+			return false;
695
+		}
696
+
697
+		// Update their password and reset their reset_hash
698
+		$data = [
699
+			'password'     => $password,
700
+			'pass_confirm' => $passConfirm,
701
+			'reset_hash'   => null
702
+		];
703
+
704
+		if (! $this->user_model->update($user['id'], $data))
705
+		{
706
+			$this->error = $this->user_model->error();
707
+			return false;
708
+		}
709
+
710
+		// Clear our login attempts
711
+		$this->ci->login_model->purgeLoginAttempts($ip_address, $user['id']);
712
+
713
+		Events::trigger('didResetPassword', [$user]);
714
+
715
+		return true;
716
+	}
717
+
718
+	//--------------------------------------------------------------------
719
+
720
+	/**
721
+	 * Provides a way for implementations to allow new statuses to be set
722
+	 * on the user. The details will vary based upon implementation, but
723
+	 * will often allow for banning or suspending users.
724
+	 *
725
+	 * @param $newStatus
726
+	 * @param null $message
727
+	 * @return mixed
728
+	 */
729
+	public function changeStatus($newStatus, $message=null)
730
+	{
731
+		// todo actually record new users status!
732
+	}
733
+
734
+	//--------------------------------------------------------------------
735
+
736
+	/**
737
+	 * Allows the consuming application to pass in a reference to the
738
+	 * model that should be used.
739
+	 *
740
+	 * The model MUST extend Myth\Models\CIDbModel.
741
+	 *
742
+	 * @param $model
743
+	 * @param bool $allow_any_parent
744
+	 * @return mixed
745
+	 */
746
+	public function useModel($model, $allow_any_parent=false)
747
+	{
748
+		if (! $allow_any_parent && get_parent_class($model) != 'Myth\Models\CIDbModel')
749
+		{
750
+			throw new \RuntimeException('Models passed into LocalAuthenticate MUST extend Myth\Models\CIDbModel');
751
+		}
752
+
753
+		$this->user_model =& $model;
754
+
755
+		return $this;
756
+	}
757
+
758
+	//--------------------------------------------------------------------
759
+
760
+	public function error()
761
+	{
762
+		if (validation_errors())
763
+		{
764
+			return validation_errors();
765
+		}
766
+
767
+		return $this->error;
768
+	}
769
+
770
+	//--------------------------------------------------------------------
771
+
772
+	//--------------------------------------------------------------------
773
+	// Login Records
774
+	//--------------------------------------------------------------------
775
+
776
+	/**
777
+	 * Purges all login attempt records from the database.
778
+	 *
779
+	 * @param null $ip_address
780
+	 * @param null $user_id
781
+	 */
782
+	public function purgeLoginAttempts($ip_address = null, $user_id = null)
783
+	{
784
+		$this->ci->login_model->purgeLoginAttempts($ip_address, $user_id);
785
+
786
+		// @todo record activity of login attempts purge.
787
+		Events::trigger('didPurgeLoginAttempts', [$email]);
788
+	}
789
+
790
+	//--------------------------------------------------------------------
791
+
792
+	/**
793
+	 * Purges all remember tokens for a single user. Effectively logs
794
+	 * a user out of all devices. Intended to allow users to log themselves
795
+	 * out of all devices as a security measure.
796
+	 *
797
+	 * @param $email
798
+	 */
799
+	public function purgeRememberTokens($email)
800
+	{
801
+		// Emails should NOT be case sensitive.
802
+		$email = strtolower($email);
803
+
804
+		$this->ci->login_model->purgeRememberTokens($email);
805
+
806
+		// todo record activity of remember me purges.
807
+		Events::trigger('didPurgeRememberTokens', [$email]);
808
+	}
809
+
810
+	//--------------------------------------------------------------------
811
+
812
+	//--------------------------------------------------------------------
813
+	// Protected Methods
814
+	//--------------------------------------------------------------------
815
+
816
+	/**
817
+	 * Check if Allow Persistent Login Cookies is enable
818
+	 *
819
+	 * @param $user
820
+	 */
821
+	protected function rememberUser($user)
822
+	{
823
+		if (! config_item('auth.allow_remembering'))
824
+		{
825
+			log_message('debug', 'Auth library set to refuse "Remember Me" functionality.');
826
+			return false;
827
+		}
828
+
829
+		$this->refreshRememberCookie($user);
830
+	}
831
+
832
+	//--------------------------------------------------------------------
833
+
834
+	/**
835
+	 * Invalidates the current rememberme cookie/database entry, creates
836
+	 * a new one, stores it and returns the new value.
837
+	 *
838
+	 * @param $user
839
+	 * @param null $token
840
+	 * @return mixed
841
+	 */
842
+	protected function refreshRememberCookie($user, $token=null)
843
+	{
844
+		$this->ci->load->helper('cookie');
845
+
846
+		// If a token is passed in, we know we're removing the
847
+		// old one.
848
+		if (! empty($token))
849
+		{
850
+			$this->invalidateRememberCookie($user['email'], $token);
851
+		}
852
+
853
+		$new_token = $this->ci->login_model->generateRememberToken($user);
854
+
855
+		// Save the token to the database.
856
+		$data = [
857
+			'email'   => $user['email'],
858
+			'hash'    => sha1(config_item('auth.salt') . $new_token),
859
+			'created' => date('Y-m-d H:i:s')
860
+		];
861
+
862
+		$this->ci->db->insert('auth_tokens', $data);
863
+
864
+		// Create the cookie
865
+		set_cookie(
866
+			'remember',                             // Cookie Name
867
+			$new_token,                             // Value
868
+			config_item('auth.remember_length'),    // # Seconds until it expires
869
+			config_item('cookie_domain'),
870
+			config_item('cookie_path'),
871
+			config_item('cookie_prefix'),
872
+			false,                                  // Only send over HTTPS?
873
+			true                                    // Hide from Javascript?
874
+		);
875
+
876
+		return $new_token;
877
+	}
878
+
879
+	//--------------------------------------------------------------------
880
+
881
+	/**
882
+	 * Deletes any current remember me cookies and database entries.
883
+	 *
884
+	 * @param $email
885
+	 * @param $token
886
+	 * @return string The new token (not the hash).
887
+	 */
888
+	protected function invalidateRememberCookie($email, $token)
889
+	{
890
+		// Emails should NOT be case sensitive.
891
+		$email = strtolower($email);
892
+
893
+		// Remove from the database
894
+		$this->ci->login_model->deleteRememberToken($email, $token);
895
+
896
+		// Remove the cookie
897
+		delete_cookie(
898
+			'remember',
899
+			config_item('cookie_domain'),
900
+			config_item('cookie_path'),
901
+			config_item('cookie_prefix')
902
+		);
903
+	}
904
+
905
+	//--------------------------------------------------------------------
906
+
907
+	/**
908
+	 * Handles the nitty gritty of actually logging our user into the system.
909
+	 * Does NOT perform the authentication, just sets the system up so that
910
+	 * it knows we're here.
911
+	 *
912
+	 * @param $user
913
+	 */
914
+	protected function loginUser($user)
915
+	{
916
+		// Save the user for later access
917
+		$this->user = $user;
918
+
919
+		// Get ip address
920
+		$ip_address = $this->ci->input->ip_address();
921
+
922
+		// Regenerate the session ID to help protect
923
+		// against session fixation
924
+		$this->ci->session->sess_regenerate();
925
+
926
+		// Let the session know that we're logged in.
927
+		$this->ci->session->set_userdata('logged_in', $user['id']);
928
+
929
+		// Clear our login attempts
930
+		$this->ci->login_model->purgeLoginAttempts($ip_address, $user['id']);
931
+
932
+		// Record a new Login
933
+		$this->ci->login_model->recordLogin($user);
934
+
935
+		// If logged in, ensure cache control
936
+		// headers are in place
937
+		$this->setHeaders();
938
+
939
+		// We'll give a 20% chance to need to do a purge since we
940
+		// don't need to purge THAT often, it's just a maintenance issue.
941
+		// to keep the table from getting out of control.
942
+		if (mt_rand(1, 100) < 20)
943
+		{
944
+			$this->ci->login_model->purgeOldRememberTokens();
945
+		}
946
+	}
947
+
948
+	//--------------------------------------------------------------------
949
+
950
+	/**
951
+	 * Sets the headers to ensure that pages are not cached when a user
952
+	 * is logged in, helping to protect against logging out and then
953
+	 * simply hitting the Back button on the browser and getting private
954
+	 * information because the page was loaded from cache.
955
+	 */
956
+	protected function setHeaders()
957
+	{
958
+		$this->ci->output->set_header('Cache-Control: no-store, no-cache, must-revalidate');
959
+		$this->ci->output->set_header('Cache-Control: post-check=0, pre-check=0');
960
+		$this->ci->output->set_header('Pragma: no-cache');
961
+	}
962
+
963
+	//--------------------------------------------------------------------
964 964
 
965 965
 
966 966
 }
Please login to merge, or discard this patch.
myth/Route.php 1 patch
Indentation   +639 added lines, -639 removed lines patch added patch discarded remove patch
@@ -43,644 +43,644 @@
 block discarded – undo
43 43
 class Route
44 44
 {
45 45
 
46
-    // Our routes, ripe for the picking.
47
-    public $routes = array();
48
-
49
-    // Holds key/value pairs of named routes
50
-    public static $names = array();
51
-
52
-    // Used for grouping routes together.
53
-    public $group = null;
54
-
55
-    // Holds the 'areas' of the site.
56
-    public static $areas = array();
57
-
58
-    // The default controller to use in case
59
-    // 'default_controller' is not in the routes file.
60
-    protected $default_home = 'home';
61
-
62
-    // The default constraint to use in route building
63
-    protected $default_constraint = 'any';
64
-
65
-    protected $constraints = [
66
-        'any'  => '(:any)',
67
-        'num'  => '(:num)',
68
-        'id'   => '(:num)',
69
-        'name' => "([a-zA-Z']+)"
70
-    ];
71
-
72
-    protected $current_subdomain = null;
73
-
74
-    //--------------------------------------------------------------------
75
-
76
-    /**
77
-     * Combines the routes that we've defined with the Route class with the
78
-     * routes passed in. This is intended to be used  after all routes have been
79
-     * defined to merge CI's default $route array with our routes.
80
-     *
81
-     * Example:
82
-     *     $route['default_controller'] = 'home';
83
-     *     Route::resource('posts');
84
-     *     $route = Route::map($route);
85
-     *
86
-     * @param array $routes
87
-     * @internal param array $route The array to merge
88
-     * @return array         The merge route array.
89
-     */
90
-    public function map($routes = array())
91
-    {
92
-        $controller = isset($routes['default_controller']) ? $routes['default_controller'] : $this->default_home;
93
-
94
-        $routes = array_merge($routes, $this->routes);
95
-
96
-        foreach ($routes as $from => $to) {
97
-            $routes[$from] = str_ireplace('{default_controller}', $controller, $to);
98
-        }
99
-
100
-        return $routes;
101
-    }
102
-
103
-    //--------------------------------------------------------------------
104
-
105
-    /**
106
-     * A single point to the basic routing. Can be used in place of CI's $route
107
-     * array if desired. Used internally by many of the methods.
108
-     *
109
-     * Available options are currently:
110
-     *      'as'        - remembers the route via a name that can be called outside of it.
111
-     *      'offset'    - Offsets and parameters ($1, $2, etc) in routes by the specified amount.
112
-     *                    Useful while doing versioning of API's, etc.
113
-     *
114
-     * Example:
115
-     *      $route->any('news', 'posts/index');
116
-     *
117
-     * @param string $from
118
-     * @param string $to
119
-     * @param array  $options
120
-     * @return void
121
-     */
122
-    public function any($from, $to, $options = array())
123
-    {
124
-        $this->create($from, $to, $options);
125
-    }
126
-
127
-    //--------------------------------------------------------------------
128
-
129
-    /**
130
-     * Sets the default constraint to be used in the system. Typically
131
-     * for use with the 'resources' method.
132
-     *
133
-     * @param $constraint
134
-     */
135
-    public function setDefaultConstraint($constraint)
136
-    {
137
-        if (array_key_exists($constraint, $this->constraints)) {
138
-            $this->default_constraint = $constraint;
139
-        }
140
-    }
141
-
142
-    //--------------------------------------------------------------------
143
-
144
-    /**
145
-     * Registers a new constraint to be used internally. Useful for creating
146
-     * very specific regex patterns, or simply to allow your routes to be
147
-     * a tad more readable.
148
-     *
149
-     * Example:
150
-     *      $route->registerConstraint('hero', '(^.*)');
151
-     *
152
-     *      $route->any('home/{hero}', 'heroes/journey');
153
-     *
154
-     *      // Route then looks like:
155
-     *      $route['home/(^.*)'] = 'heroes/journey';
156
-     *
157
-     * @param      $name
158
-     * @param      $pattern
159
-     * @param bool $overwrite
160
-     */
161
-    public function registerConstraint($name, $pattern, $overwrite = false)
162
-    {
163
-        // Ensure consistency
164
-        $name    = trim($name, '{} ');
165
-        $pattern = '(' . trim($pattern, '() ') . ')';
166
-
167
-        // Not here? Add it and leave...
168
-        if (! array_key_exists($name, $this->constraints)) {
169
-            $this->constraints[$name] = $pattern;
170
-
171
-            return;
172
-        }
173
-
174
-        // Here? Then it exists. Should we overwrite it?
175
-        if ($overwrite) {
176
-            $this->constraints[$name] = $pattern;
177
-        }
178
-    }
179
-
180
-    //--------------------------------------------------------------------
181
-
182
-    //--------------------------------------------------------------------
183
-    // Named Routes
184
-    //--------------------------------------------------------------------
185
-
186
-    /**
187
-     * Returns the value of a named route. Useful for getting named
188
-     * routes for use while building with site_url() or in templates
189
-     * where you don't need to instantiate the route class.
190
-     *
191
-     * Example:
192
-     *      $route->any('news', 'posts/index', ['as' => 'blog']);
193
-     *
194
-     *      // Returns http://mysite.com/news
195
-     *      site_url( Route::named('blog') );
196
-     *
197
-     * @param  [type] $name [description]
198
-     * @return [type]       [description]
199
-     */
200
-    public static function named($name)
201
-    {
202
-        if (isset(self::$names[$name])) {
203
-            return self::$names[$name];
204
-        }
205
-
206
-        return null;
207
-    }
208
-
209
-    //--------------------------------------------------------------------
210
-
211
-    //--------------------------------------------------------------------
212
-    // Grouping Routes
213
-    //--------------------------------------------------------------------
214
-
215
-    /**
216
-     * Group a series of routes under a single URL segment. This is handy
217
-     * for grouping items into an admin area, like:
218
-     *
219
-     * Example:
220
-     *     $route->group('admin', function() {
221
-     *            $route->resources('users');
222
-     *     });
223
-     *
224
-     * @param  string   $name     The name to group/prefix the routes with.
225
-     * @param  \Closure $callback An anonymous function that allows you route inside of this group.
226
-     * @return void
227
-     */
228
-    public function group($name, \Closure $callback)
229
-    {
230
-        $old_group = $this->group;
231
-
232
-        // To register a route, we'll set a flag so that our router
233
-        // so it will see the groupname.
234
-        $this->group = ltrim($old_group . '/' . $name, '/');
235
-
236
-        call_user_func($callback);
237
-
238
-        // Make sure to clear the group name so we don't accidentally
239
-        // group any ones we didn't want to.
240
-        $this->group = $old_group;
241
-    }
242
-
243
-    //--------------------------------------------------------------------
244
-
245
-    //--------------------------------------------------------------------
246
-    // HTTP Verb-based routing
247
-    //--------------------------------------------------------------------
248
-    // Routing works here because, as the routes config file is read in,
249
-    // the various HTTP verb-based routes will only be added to the in-memory
250
-    // routes if it is a call that should respond to that verb.
251
-    //
252
-    // The options array is typically used to pass in an 'as' or var, but may
253
-    // be expanded in the future. See the docblock for 'any' method above for
254
-    // current list of globally available options.
255
-    //
256
-
257
-    /**
258
-     * Specifies a single route to match for multiple HTTP Verbs.
259
-     *
260
-     * Example:
261
-     *  $route->match( ['get', 'post'], 'users/(:num)', 'users/$1);
262
-     *
263
-     * @param array $verbs
264
-     * @param       $from
265
-     * @param       $to
266
-     * @param array $options
267
-     */
268
-    public function match($verbs = [], $from, $to, $options = [])
269
-    {
270
-        foreach ($verbs as $verb) {
271
-            $verb = strtolower($verb);
272
-
273
-            $this->{$verb}($from, $to, $options);
274
-        }
275
-    }
276
-
277
-    //--------------------------------------------------------------------
278
-
279
-    /**
280
-     * Specifies a route that is only available to GET requests.
281
-     *
282
-     * @param       $from
283
-     * @param       $to
284
-     * @param array $options
285
-     */
286
-    public function get($from, $to, $options = [])
287
-    {
288
-        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'GET') {
289
-            $this->create($from, $to, $options);
290
-        }
291
-    }
292
-
293
-    //--------------------------------------------------------------------
294
-
295
-    /**
296
-     * Specifies a route that is only available to POST requests.
297
-     *
298
-     * @param       $from
299
-     * @param       $to
300
-     * @param array $options
301
-     */
302
-    public function post($from, $to, $options = [])
303
-    {
304
-        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST') {
305
-            $this->create($from, $to, $options);
306
-        }
307
-    }
308
-
309
-    //--------------------------------------------------------------------
310
-
311
-    /**
312
-     * Specifies a route that is only available to PUT requests.
313
-     *
314
-     * @param       $from
315
-     * @param       $to
316
-     * @param array $options
317
-     */
318
-    public function put($from, $to, $options = [])
319
-    {
320
-        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'PUT') {
321
-            $this->create($from, $to, $options);
322
-        }
323
-    }
324
-
325
-    //--------------------------------------------------------------------
326
-
327
-    /**
328
-     * Specifies a route that is only available to DELETE requests.
329
-     *
330
-     * @param       $from
331
-     * @param       $to
332
-     * @param array $options
333
-     */
334
-    public function delete($from, $to, $options = [])
335
-    {
336
-        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'DELETE') {
337
-            $this->create($from, $to, $options);
338
-        }
339
-    }
340
-
341
-    //--------------------------------------------------------------------
342
-
343
-    /**
344
-     * Specifies a route that is only available to HEAD requests.
345
-     *
346
-     * @param       $from
347
-     * @param       $to
348
-     * @param array $options
349
-     */
350
-    public function head($from, $to, $options = [])
351
-    {
352
-        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'HEAD') {
353
-            $this->create($from, $to, $options);
354
-        }
355
-    }
356
-
357
-    //--------------------------------------------------------------------
358
-
359
-    /**
360
-     * Specifies a route that is only available to PATCH requests.
361
-     *
362
-     * @param       $from
363
-     * @param       $to
364
-     * @param array $options
365
-     */
366
-    public function patch($from, $to, $options = [])
367
-    {
368
-        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'PATCH') {
369
-            $this->create($from, $to, $options);
370
-        }
371
-    }
372
-
373
-    //--------------------------------------------------------------------
374
-
375
-    /**
376
-     * Specifies a route that is only available to OPTIONS requests.
377
-     *
378
-     * @param       $from
379
-     * @param       $to
380
-     * @param array $options
381
-     */
382
-    public function options($from, $to, $options = [])
383
-    {
384
-        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
385
-            $this->create($from, $to, $options);
386
-        }
387
-    }
388
-
389
-    //--------------------------------------------------------------------
390
-
391
-    /**
392
-     * Creates a collections of HTTP-verb based routes for a controller.
393
-     *
394
-     * Possible Options:
395
-     *      'controller'    - Customize the name of the controller used in the 'to' route
396
-     *      'module'        - Prepend a module name to the generate 'to' routes
397
-     *      'constraint'    - The regex used by the Router. Defaults to '(:any)'
398
-     *
399
-     * Example:
400
-     *      $route->resources('photos');
401
-     *
402
-     *      // Generates the following routes:
403
-     *      HTTP Verb | Path        | Action        | Used for...
404
-     *      ----------+-------------+---------------+-----------------
405
-     *      GET         /photos             index           display a list of photos
406
-     *      GET         /photos/new         creation_form   return an HTML form for creating a new photo
407
-     *      GET         /photos/{id}        show            display a specific photo
408
-     *      GET         /photos/{id}/edit   editing_form    return an HTML form for editing the photo
409
-     *      POST        /photos             create          create a new photo
410
-     *      PUT         /photos/{id}        update          update an existing photo
411
-     *      DELETE      /photos/{id}/delete delete          delete an existing photo
412
-     *
413
-     * @param  string $name    The name of the controller to route to.
414
-     * @param  array  $options An list of possible ways to customize the routing.
415
-     */
416
-    public function resources($name, $options = [])
417
-    {
418
-        // In order to allow customization of the route the
419
-        // resources are sent to, we need to have a new name
420
-        // to store the values in.
421
-        $new_name = $name;
422
-
423
-        // If a new controller is specified, then we replace the
424
-        // $name value with the name of the new controller.
425
-        if (isset($options['controller'])) {
426
-            $new_name = $options['controller'];
427
-        }
428
-
429
-        // If a new module was specified, simply put that path
430
-        // in front of the controller.
431
-        if (isset($options['module'])) {
432
-            $new_name = $options['module'] . '/' . $new_name;
433
-        }
434
-
435
-        // In order to allow customization of allowed id values
436
-        // we need someplace to store them.
437
-        $id = isset($this->constraints[$this->default_constraint]) ? $this->constraints[$this->default_constraint] :
438
-            '(:any)';
439
-
440
-        if (isset($options['constraint'])) {
441
-            $id = $options['constraint'];
442
-        }
443
-
444
-        $this->get($name, $new_name . '/list_all', $options);
445
-        $this->get($name . '/' . $id, $new_name . '/show/$1', $options);
446
-        $this->post($name, $new_name . '/create', $options);
447
-        $this->put($name . '/' . $id, $new_name . '/update/$1', $options);
448
-        $this->delete($name . '/' . $id, $new_name . '/delete/$1', $options);
449
-        $this->options($name, $new_name . '/index', $options);
450
-    }
451
-
452
-    //--------------------------------------------------------------------
453
-
454
-    /**
455
-     * Lets the system know about different 'areas' within the site, like
456
-     * the admin area, that maps to certain controllers.
457
-     *
458
-     * @param  string $area       The name of the area.
459
-     * @param  string $controller The controller name to look for.
460
-     * @param         $options
461
-     */
462
-    public function area($area, $controller = null, $options = [])
463
-    {
464
-        // No controller? Match the area name.
465
-        $controller = is_null($controller) ? $area : $controller;
466
-
467
-        // Save the area so we can recognize it later.
468
-        self::$areas[$area] = $controller;
469
-
470
-        // Create routes for this area.
471
-        $this->create($area . '/(:any)/(:any)/(:any)/(:any)/(:any)', '$1/' . $controller . '/$2/$3/$4/$5', $options);
472
-        $this->create($area . '/(:any)/(:any)/(:any)/(:any)', '$1/' . $controller . '/$2/$3/$4', $options);
473
-        $this->create($area . '/(:any)/(:any)/(:any)', '$1/' . $controller . '/$2/$3', $options);
474
-        $this->create($area . '/(:any)/(:any)', '$1/' . $controller . '/$2', $options);
475
-        $this->create($area . '/(:any)', '$1/' . $controller, $options);
476
-    }
477
-
478
-    //--------------------------------------------------------------------
479
-
480
-    /**
481
-     * Returns the name of the area based on the controller name.
482
-     *
483
-     * @param  string $controller The name of the controller
484
-     * @return string             The name of the corresponding area
485
-     */
486
-    public static function getAreaName($controller)
487
-    {
488
-        foreach (self::$areas as $area => $cont) {
489
-            if ($controller == $cont) {
490
-                return $area;
491
-            }
492
-        }
493
-
494
-        return null;
495
-    }
496
-
497
-    //--------------------------------------------------------------------
498
-
499
-    /**
500
-     * Limits the routes to a specified ENVIRONMENT or they won't run.
501
-     *
502
-     * @param $env
503
-     * @param callable $callback
504
-     *
505
-     * @return bool|null
506
-     */
507
-    public function environment($env, \Closure $callback)
508
-    {
509
-        if (ENVIRONMENT == $env)
510
-        {
511
-            call_user_func($callback);
512
-            return true;
513
-        }
514
-
515
-        return null;
516
-    }
517
-
518
-    //--------------------------------------------------------------------
519
-
520
-
521
-
522
-    /**
523
-     * Allows you to easily block access to any number of routes by setting
524
-     * that route to an empty path ('').
525
-     *
526
-     * Example:
527
-     *     Route::block('posts', 'photos/(:num)');
528
-     *
529
-     *     // Same as...
530
-     *     $route['posts']          = '';
531
-     *     $route['photos/(:num)']  = '';
532
-     */
533
-    public function block()
534
-    {
535
-        $paths = func_get_args();
536
-
537
-        if (! is_array($paths) || ! count($paths)) {
538
-            return;
539
-        }
540
-
541
-        foreach ($paths as $path) {
542
-            $this->create($path, '');
543
-        }
544
-    }
545
-
546
-    //--------------------------------------------------------------------
547
-
548
-    /**
549
-     * Empties all named and un-named routes from the system.
550
-     *
551
-     * @return void
552
-     */
553
-    public function reset()
554
-    {
555
-        $this->routes = array();
556
-        $this->group  = null;
557
-        self::$names  = array();
558
-        self::$areas  = array();
559
-    }
560
-
561
-    //--------------------------------------------------------------------
562
-
563
-    //--------------------------------------------------------------------
564
-    // Private Methods
565
-    //--------------------------------------------------------------------
566
-
567
-    /**
568
-     * Does the heavy lifting of creating an actual route. You must specify
569
-     * the request method(s) that this route will work for. They can be separated
570
-     * by a pipe character "|" if there is more than one.
571
-     *
572
-     * @param  string $from
573
-     * @param  array  $to
574
-     * @param array   $options
575
-     *
576
-     * @return array          The built route.
577
-     */
578
-    private function create($from, $to, $options = array())
579
-    {
580
-        $prefix = is_null($this->group) ? '' : $this->group . '/';
581
-
582
-        $from = $prefix . $from;
583
-
584
-        // Are we saving the name for this one?
585
-        if (isset($options['as']) && !empty($options['as'])) {
586
-            self::$names[$options['as']] = $from;
587
-        }
588
-
589
-        // Limiting to subdomains?
590
-        if (isset($options['subdomain']) && !empty($options['subdomain'])) {
591
-            // If we don't match the current subdomain, then
592
-            // we don't need to add the route.
593
-            if (!$this->checkSubdomains($options['subdomain'])) {
594
-                return;
595
-            }
596
-        }
597
-
598
-        // Are we offsetting the parameters?
599
-        // If so, take care of them here in one
600
-        // fell swoop.
601
-        if (isset($options['offset'])) {
602
-            // Get a constant string to work with.
603
-            $to = preg_replace('/(\$\d+)/', '$X', $to);
604
-
605
-            for ($i = (int)$options['offset'] + 1; $i < (int)$options['offset'] + 7; $i ++) {
606
-                $to = preg_replace_callback(
607
-                    '/\$X/',
608
-                    function ($m) use ($i) {
609
-                        return '$' . $i;
610
-                    },
611
-                    $to,
612
-                    1
613
-                );
614
-            }
615
-        }
616
-
617
-        // Convert any custom constraints to the CI/pattern equivalent
618
-        foreach ($this->constraints as $name => $pattern) {
619
-            $from = str_replace('{' . $name . '}', $pattern, $from);
620
-        }
621
-
622
-        $this->routes[$from] = $to;
623
-    }
624
-
625
-    //--------------------------------------------------------------------
626
-
627
-    /**
628
-     * Compares the subdomain(s) passed in against the current subdomain
629
-     * on this page request.
630
-     *
631
-     * @param $subdomains
632
-     * @return bool
633
-     */
634
-    private function checkSubdomains($subdomains)
635
-    {
636
-        if (is_null($this->current_subdomain)) {
637
-            $this->determineCurrentSubdomain();
638
-        }
639
-
640
-        if (!is_array($subdomains)) {
641
-            $subdomains = array($subdomains);
642
-        }
643
-
644
-        $matched = false;
645
-
646
-        array_walk(
647
-            $subdomains,
648
-            function ($subdomain) use (&$matched) {
649
-                if ($subdomain == $this->current_subdomain || $subdomain == '*') {
650
-                    $matched = true;
651
-                }
652
-            }
653
-        );
654
-
655
-        return $matched;
656
-    }
657
-
658
-    //--------------------------------------------------------------------
659
-
660
-    /**
661
-     * Examines the HTTP_HOST to get a best match for the subdomain. It
662
-     * won't be perfect, but should work for our needs.
663
-     */
664
-    private function determineCurrentSubdomain()
665
-    {
666
-        $parsedUrl = parse_url($_SERVER['HTTP_HOST']);
667
-
668
-        $host = explode('.', $parsedUrl['host']);
669
-
670
-        // If we only have 2 parts, then we don't have a subdomain.
671
-        // This won't be totally accurate, since URL's like example.co.uk
672
-        // would still pass, but it helps to separate the chaff...
673
-        if (!is_array($host) || count($host) == 2) {
674
-            // Set it to false so we don't make it back here again.
675
-            $this->current_subdomain = false;
676
-            return;
677
-        }
678
-
679
-        // Now, we'll simply take the first element of the array. This should
680
-        // be fine even in cases like example.co.uk, since they won't be looking
681
-        // for 'example' when they try to match the subdomain, in most all cases.
682
-        $this->current_subdomain = array_shift($host);
683
-    }
684
-    //--------------------------------------------------------------------
46
+	// Our routes, ripe for the picking.
47
+	public $routes = array();
48
+
49
+	// Holds key/value pairs of named routes
50
+	public static $names = array();
51
+
52
+	// Used for grouping routes together.
53
+	public $group = null;
54
+
55
+	// Holds the 'areas' of the site.
56
+	public static $areas = array();
57
+
58
+	// The default controller to use in case
59
+	// 'default_controller' is not in the routes file.
60
+	protected $default_home = 'home';
61
+
62
+	// The default constraint to use in route building
63
+	protected $default_constraint = 'any';
64
+
65
+	protected $constraints = [
66
+		'any'  => '(:any)',
67
+		'num'  => '(:num)',
68
+		'id'   => '(:num)',
69
+		'name' => "([a-zA-Z']+)"
70
+	];
71
+
72
+	protected $current_subdomain = null;
73
+
74
+	//--------------------------------------------------------------------
75
+
76
+	/**
77
+	 * Combines the routes that we've defined with the Route class with the
78
+	 * routes passed in. This is intended to be used  after all routes have been
79
+	 * defined to merge CI's default $route array with our routes.
80
+	 *
81
+	 * Example:
82
+	 *     $route['default_controller'] = 'home';
83
+	 *     Route::resource('posts');
84
+	 *     $route = Route::map($route);
85
+	 *
86
+	 * @param array $routes
87
+	 * @internal param array $route The array to merge
88
+	 * @return array         The merge route array.
89
+	 */
90
+	public function map($routes = array())
91
+	{
92
+		$controller = isset($routes['default_controller']) ? $routes['default_controller'] : $this->default_home;
93
+
94
+		$routes = array_merge($routes, $this->routes);
95
+
96
+		foreach ($routes as $from => $to) {
97
+			$routes[$from] = str_ireplace('{default_controller}', $controller, $to);
98
+		}
99
+
100
+		return $routes;
101
+	}
102
+
103
+	//--------------------------------------------------------------------
104
+
105
+	/**
106
+	 * A single point to the basic routing. Can be used in place of CI's $route
107
+	 * array if desired. Used internally by many of the methods.
108
+	 *
109
+	 * Available options are currently:
110
+	 *      'as'        - remembers the route via a name that can be called outside of it.
111
+	 *      'offset'    - Offsets and parameters ($1, $2, etc) in routes by the specified amount.
112
+	 *                    Useful while doing versioning of API's, etc.
113
+	 *
114
+	 * Example:
115
+	 *      $route->any('news', 'posts/index');
116
+	 *
117
+	 * @param string $from
118
+	 * @param string $to
119
+	 * @param array  $options
120
+	 * @return void
121
+	 */
122
+	public function any($from, $to, $options = array())
123
+	{
124
+		$this->create($from, $to, $options);
125
+	}
126
+
127
+	//--------------------------------------------------------------------
128
+
129
+	/**
130
+	 * Sets the default constraint to be used in the system. Typically
131
+	 * for use with the 'resources' method.
132
+	 *
133
+	 * @param $constraint
134
+	 */
135
+	public function setDefaultConstraint($constraint)
136
+	{
137
+		if (array_key_exists($constraint, $this->constraints)) {
138
+			$this->default_constraint = $constraint;
139
+		}
140
+	}
141
+
142
+	//--------------------------------------------------------------------
143
+
144
+	/**
145
+	 * Registers a new constraint to be used internally. Useful for creating
146
+	 * very specific regex patterns, or simply to allow your routes to be
147
+	 * a tad more readable.
148
+	 *
149
+	 * Example:
150
+	 *      $route->registerConstraint('hero', '(^.*)');
151
+	 *
152
+	 *      $route->any('home/{hero}', 'heroes/journey');
153
+	 *
154
+	 *      // Route then looks like:
155
+	 *      $route['home/(^.*)'] = 'heroes/journey';
156
+	 *
157
+	 * @param      $name
158
+	 * @param      $pattern
159
+	 * @param bool $overwrite
160
+	 */
161
+	public function registerConstraint($name, $pattern, $overwrite = false)
162
+	{
163
+		// Ensure consistency
164
+		$name    = trim($name, '{} ');
165
+		$pattern = '(' . trim($pattern, '() ') . ')';
166
+
167
+		// Not here? Add it and leave...
168
+		if (! array_key_exists($name, $this->constraints)) {
169
+			$this->constraints[$name] = $pattern;
170
+
171
+			return;
172
+		}
173
+
174
+		// Here? Then it exists. Should we overwrite it?
175
+		if ($overwrite) {
176
+			$this->constraints[$name] = $pattern;
177
+		}
178
+	}
179
+
180
+	//--------------------------------------------------------------------
181
+
182
+	//--------------------------------------------------------------------
183
+	// Named Routes
184
+	//--------------------------------------------------------------------
185
+
186
+	/**
187
+	 * Returns the value of a named route. Useful for getting named
188
+	 * routes for use while building with site_url() or in templates
189
+	 * where you don't need to instantiate the route class.
190
+	 *
191
+	 * Example:
192
+	 *      $route->any('news', 'posts/index', ['as' => 'blog']);
193
+	 *
194
+	 *      // Returns http://mysite.com/news
195
+	 *      site_url( Route::named('blog') );
196
+	 *
197
+	 * @param  [type] $name [description]
198
+	 * @return [type]       [description]
199
+	 */
200
+	public static function named($name)
201
+	{
202
+		if (isset(self::$names[$name])) {
203
+			return self::$names[$name];
204
+		}
205
+
206
+		return null;
207
+	}
208
+
209
+	//--------------------------------------------------------------------
210
+
211
+	//--------------------------------------------------------------------
212
+	// Grouping Routes
213
+	//--------------------------------------------------------------------
214
+
215
+	/**
216
+	 * Group a series of routes under a single URL segment. This is handy
217
+	 * for grouping items into an admin area, like:
218
+	 *
219
+	 * Example:
220
+	 *     $route->group('admin', function() {
221
+	 *            $route->resources('users');
222
+	 *     });
223
+	 *
224
+	 * @param  string   $name     The name to group/prefix the routes with.
225
+	 * @param  \Closure $callback An anonymous function that allows you route inside of this group.
226
+	 * @return void
227
+	 */
228
+	public function group($name, \Closure $callback)
229
+	{
230
+		$old_group = $this->group;
231
+
232
+		// To register a route, we'll set a flag so that our router
233
+		// so it will see the groupname.
234
+		$this->group = ltrim($old_group . '/' . $name, '/');
235
+
236
+		call_user_func($callback);
237
+
238
+		// Make sure to clear the group name so we don't accidentally
239
+		// group any ones we didn't want to.
240
+		$this->group = $old_group;
241
+	}
242
+
243
+	//--------------------------------------------------------------------
244
+
245
+	//--------------------------------------------------------------------
246
+	// HTTP Verb-based routing
247
+	//--------------------------------------------------------------------
248
+	// Routing works here because, as the routes config file is read in,
249
+	// the various HTTP verb-based routes will only be added to the in-memory
250
+	// routes if it is a call that should respond to that verb.
251
+	//
252
+	// The options array is typically used to pass in an 'as' or var, but may
253
+	// be expanded in the future. See the docblock for 'any' method above for
254
+	// current list of globally available options.
255
+	//
256
+
257
+	/**
258
+	 * Specifies a single route to match for multiple HTTP Verbs.
259
+	 *
260
+	 * Example:
261
+	 *  $route->match( ['get', 'post'], 'users/(:num)', 'users/$1);
262
+	 *
263
+	 * @param array $verbs
264
+	 * @param       $from
265
+	 * @param       $to
266
+	 * @param array $options
267
+	 */
268
+	public function match($verbs = [], $from, $to, $options = [])
269
+	{
270
+		foreach ($verbs as $verb) {
271
+			$verb = strtolower($verb);
272
+
273
+			$this->{$verb}($from, $to, $options);
274
+		}
275
+	}
276
+
277
+	//--------------------------------------------------------------------
278
+
279
+	/**
280
+	 * Specifies a route that is only available to GET requests.
281
+	 *
282
+	 * @param       $from
283
+	 * @param       $to
284
+	 * @param array $options
285
+	 */
286
+	public function get($from, $to, $options = [])
287
+	{
288
+		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'GET') {
289
+			$this->create($from, $to, $options);
290
+		}
291
+	}
292
+
293
+	//--------------------------------------------------------------------
294
+
295
+	/**
296
+	 * Specifies a route that is only available to POST requests.
297
+	 *
298
+	 * @param       $from
299
+	 * @param       $to
300
+	 * @param array $options
301
+	 */
302
+	public function post($from, $to, $options = [])
303
+	{
304
+		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST') {
305
+			$this->create($from, $to, $options);
306
+		}
307
+	}
308
+
309
+	//--------------------------------------------------------------------
310
+
311
+	/**
312
+	 * Specifies a route that is only available to PUT requests.
313
+	 *
314
+	 * @param       $from
315
+	 * @param       $to
316
+	 * @param array $options
317
+	 */
318
+	public function put($from, $to, $options = [])
319
+	{
320
+		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'PUT') {
321
+			$this->create($from, $to, $options);
322
+		}
323
+	}
324
+
325
+	//--------------------------------------------------------------------
326
+
327
+	/**
328
+	 * Specifies a route that is only available to DELETE requests.
329
+	 *
330
+	 * @param       $from
331
+	 * @param       $to
332
+	 * @param array $options
333
+	 */
334
+	public function delete($from, $to, $options = [])
335
+	{
336
+		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'DELETE') {
337
+			$this->create($from, $to, $options);
338
+		}
339
+	}
340
+
341
+	//--------------------------------------------------------------------
342
+
343
+	/**
344
+	 * Specifies a route that is only available to HEAD requests.
345
+	 *
346
+	 * @param       $from
347
+	 * @param       $to
348
+	 * @param array $options
349
+	 */
350
+	public function head($from, $to, $options = [])
351
+	{
352
+		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'HEAD') {
353
+			$this->create($from, $to, $options);
354
+		}
355
+	}
356
+
357
+	//--------------------------------------------------------------------
358
+
359
+	/**
360
+	 * Specifies a route that is only available to PATCH requests.
361
+	 *
362
+	 * @param       $from
363
+	 * @param       $to
364
+	 * @param array $options
365
+	 */
366
+	public function patch($from, $to, $options = [])
367
+	{
368
+		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'PATCH') {
369
+			$this->create($from, $to, $options);
370
+		}
371
+	}
372
+
373
+	//--------------------------------------------------------------------
374
+
375
+	/**
376
+	 * Specifies a route that is only available to OPTIONS requests.
377
+	 *
378
+	 * @param       $from
379
+	 * @param       $to
380
+	 * @param array $options
381
+	 */
382
+	public function options($from, $to, $options = [])
383
+	{
384
+		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
385
+			$this->create($from, $to, $options);
386
+		}
387
+	}
388
+
389
+	//--------------------------------------------------------------------
390
+
391
+	/**
392
+	 * Creates a collections of HTTP-verb based routes for a controller.
393
+	 *
394
+	 * Possible Options:
395
+	 *      'controller'    - Customize the name of the controller used in the 'to' route
396
+	 *      'module'        - Prepend a module name to the generate 'to' routes
397
+	 *      'constraint'    - The regex used by the Router. Defaults to '(:any)'
398
+	 *
399
+	 * Example:
400
+	 *      $route->resources('photos');
401
+	 *
402
+	 *      // Generates the following routes:
403
+	 *      HTTP Verb | Path        | Action        | Used for...
404
+	 *      ----------+-------------+---------------+-----------------
405
+	 *      GET         /photos             index           display a list of photos
406
+	 *      GET         /photos/new         creation_form   return an HTML form for creating a new photo
407
+	 *      GET         /photos/{id}        show            display a specific photo
408
+	 *      GET         /photos/{id}/edit   editing_form    return an HTML form for editing the photo
409
+	 *      POST        /photos             create          create a new photo
410
+	 *      PUT         /photos/{id}        update          update an existing photo
411
+	 *      DELETE      /photos/{id}/delete delete          delete an existing photo
412
+	 *
413
+	 * @param  string $name    The name of the controller to route to.
414
+	 * @param  array  $options An list of possible ways to customize the routing.
415
+	 */
416
+	public function resources($name, $options = [])
417
+	{
418
+		// In order to allow customization of the route the
419
+		// resources are sent to, we need to have a new name
420
+		// to store the values in.
421
+		$new_name = $name;
422
+
423
+		// If a new controller is specified, then we replace the
424
+		// $name value with the name of the new controller.
425
+		if (isset($options['controller'])) {
426
+			$new_name = $options['controller'];
427
+		}
428
+
429
+		// If a new module was specified, simply put that path
430
+		// in front of the controller.
431
+		if (isset($options['module'])) {
432
+			$new_name = $options['module'] . '/' . $new_name;
433
+		}
434
+
435
+		// In order to allow customization of allowed id values
436
+		// we need someplace to store them.
437
+		$id = isset($this->constraints[$this->default_constraint]) ? $this->constraints[$this->default_constraint] :
438
+			'(:any)';
439
+
440
+		if (isset($options['constraint'])) {
441
+			$id = $options['constraint'];
442
+		}
443
+
444
+		$this->get($name, $new_name . '/list_all', $options);
445
+		$this->get($name . '/' . $id, $new_name . '/show/$1', $options);
446
+		$this->post($name, $new_name . '/create', $options);
447
+		$this->put($name . '/' . $id, $new_name . '/update/$1', $options);
448
+		$this->delete($name . '/' . $id, $new_name . '/delete/$1', $options);
449
+		$this->options($name, $new_name . '/index', $options);
450
+	}
451
+
452
+	//--------------------------------------------------------------------
453
+
454
+	/**
455
+	 * Lets the system know about different 'areas' within the site, like
456
+	 * the admin area, that maps to certain controllers.
457
+	 *
458
+	 * @param  string $area       The name of the area.
459
+	 * @param  string $controller The controller name to look for.
460
+	 * @param         $options
461
+	 */
462
+	public function area($area, $controller = null, $options = [])
463
+	{
464
+		// No controller? Match the area name.
465
+		$controller = is_null($controller) ? $area : $controller;
466
+
467
+		// Save the area so we can recognize it later.
468
+		self::$areas[$area] = $controller;
469
+
470
+		// Create routes for this area.
471
+		$this->create($area . '/(:any)/(:any)/(:any)/(:any)/(:any)', '$1/' . $controller . '/$2/$3/$4/$5', $options);
472
+		$this->create($area . '/(:any)/(:any)/(:any)/(:any)', '$1/' . $controller . '/$2/$3/$4', $options);
473
+		$this->create($area . '/(:any)/(:any)/(:any)', '$1/' . $controller . '/$2/$3', $options);
474
+		$this->create($area . '/(:any)/(:any)', '$1/' . $controller . '/$2', $options);
475
+		$this->create($area . '/(:any)', '$1/' . $controller, $options);
476
+	}
477
+
478
+	//--------------------------------------------------------------------
479
+
480
+	/**
481
+	 * Returns the name of the area based on the controller name.
482
+	 *
483
+	 * @param  string $controller The name of the controller
484
+	 * @return string             The name of the corresponding area
485
+	 */
486
+	public static function getAreaName($controller)
487
+	{
488
+		foreach (self::$areas as $area => $cont) {
489
+			if ($controller == $cont) {
490
+				return $area;
491
+			}
492
+		}
493
+
494
+		return null;
495
+	}
496
+
497
+	//--------------------------------------------------------------------
498
+
499
+	/**
500
+	 * Limits the routes to a specified ENVIRONMENT or they won't run.
501
+	 *
502
+	 * @param $env
503
+	 * @param callable $callback
504
+	 *
505
+	 * @return bool|null
506
+	 */
507
+	public function environment($env, \Closure $callback)
508
+	{
509
+		if (ENVIRONMENT == $env)
510
+		{
511
+			call_user_func($callback);
512
+			return true;
513
+		}
514
+
515
+		return null;
516
+	}
517
+
518
+	//--------------------------------------------------------------------
519
+
520
+
521
+
522
+	/**
523
+	 * Allows you to easily block access to any number of routes by setting
524
+	 * that route to an empty path ('').
525
+	 *
526
+	 * Example:
527
+	 *     Route::block('posts', 'photos/(:num)');
528
+	 *
529
+	 *     // Same as...
530
+	 *     $route['posts']          = '';
531
+	 *     $route['photos/(:num)']  = '';
532
+	 */
533
+	public function block()
534
+	{
535
+		$paths = func_get_args();
536
+
537
+		if (! is_array($paths) || ! count($paths)) {
538
+			return;
539
+		}
540
+
541
+		foreach ($paths as $path) {
542
+			$this->create($path, '');
543
+		}
544
+	}
545
+
546
+	//--------------------------------------------------------------------
547
+
548
+	/**
549
+	 * Empties all named and un-named routes from the system.
550
+	 *
551
+	 * @return void
552
+	 */
553
+	public function reset()
554
+	{
555
+		$this->routes = array();
556
+		$this->group  = null;
557
+		self::$names  = array();
558
+		self::$areas  = array();
559
+	}
560
+
561
+	//--------------------------------------------------------------------
562
+
563
+	//--------------------------------------------------------------------
564
+	// Private Methods
565
+	//--------------------------------------------------------------------
566
+
567
+	/**
568
+	 * Does the heavy lifting of creating an actual route. You must specify
569
+	 * the request method(s) that this route will work for. They can be separated
570
+	 * by a pipe character "|" if there is more than one.
571
+	 *
572
+	 * @param  string $from
573
+	 * @param  array  $to
574
+	 * @param array   $options
575
+	 *
576
+	 * @return array          The built route.
577
+	 */
578
+	private function create($from, $to, $options = array())
579
+	{
580
+		$prefix = is_null($this->group) ? '' : $this->group . '/';
581
+
582
+		$from = $prefix . $from;
583
+
584
+		// Are we saving the name for this one?
585
+		if (isset($options['as']) && !empty($options['as'])) {
586
+			self::$names[$options['as']] = $from;
587
+		}
588
+
589
+		// Limiting to subdomains?
590
+		if (isset($options['subdomain']) && !empty($options['subdomain'])) {
591
+			// If we don't match the current subdomain, then
592
+			// we don't need to add the route.
593
+			if (!$this->checkSubdomains($options['subdomain'])) {
594
+				return;
595
+			}
596
+		}
597
+
598
+		// Are we offsetting the parameters?
599
+		// If so, take care of them here in one
600
+		// fell swoop.
601
+		if (isset($options['offset'])) {
602
+			// Get a constant string to work with.
603
+			$to = preg_replace('/(\$\d+)/', '$X', $to);
604
+
605
+			for ($i = (int)$options['offset'] + 1; $i < (int)$options['offset'] + 7; $i ++) {
606
+				$to = preg_replace_callback(
607
+					'/\$X/',
608
+					function ($m) use ($i) {
609
+						return '$' . $i;
610
+					},
611
+					$to,
612
+					1
613
+				);
614
+			}
615
+		}
616
+
617
+		// Convert any custom constraints to the CI/pattern equivalent
618
+		foreach ($this->constraints as $name => $pattern) {
619
+			$from = str_replace('{' . $name . '}', $pattern, $from);
620
+		}
621
+
622
+		$this->routes[$from] = $to;
623
+	}
624
+
625
+	//--------------------------------------------------------------------
626
+
627
+	/**
628
+	 * Compares the subdomain(s) passed in against the current subdomain
629
+	 * on this page request.
630
+	 *
631
+	 * @param $subdomains
632
+	 * @return bool
633
+	 */
634
+	private function checkSubdomains($subdomains)
635
+	{
636
+		if (is_null($this->current_subdomain)) {
637
+			$this->determineCurrentSubdomain();
638
+		}
639
+
640
+		if (!is_array($subdomains)) {
641
+			$subdomains = array($subdomains);
642
+		}
643
+
644
+		$matched = false;
645
+
646
+		array_walk(
647
+			$subdomains,
648
+			function ($subdomain) use (&$matched) {
649
+				if ($subdomain == $this->current_subdomain || $subdomain == '*') {
650
+					$matched = true;
651
+				}
652
+			}
653
+		);
654
+
655
+		return $matched;
656
+	}
657
+
658
+	//--------------------------------------------------------------------
659
+
660
+	/**
661
+	 * Examines the HTTP_HOST to get a best match for the subdomain. It
662
+	 * won't be perfect, but should work for our needs.
663
+	 */
664
+	private function determineCurrentSubdomain()
665
+	{
666
+		$parsedUrl = parse_url($_SERVER['HTTP_HOST']);
667
+
668
+		$host = explode('.', $parsedUrl['host']);
669
+
670
+		// If we only have 2 parts, then we don't have a subdomain.
671
+		// This won't be totally accurate, since URL's like example.co.uk
672
+		// would still pass, but it helps to separate the chaff...
673
+		if (!is_array($host) || count($host) == 2) {
674
+			// Set it to false so we don't make it back here again.
675
+			$this->current_subdomain = false;
676
+			return;
677
+		}
678
+
679
+		// Now, we'll simply take the first element of the array. This should
680
+		// be fine even in cases like example.co.uk, since they won't be looking
681
+		// for 'example' when they try to match the subdomain, in most all cases.
682
+		$this->current_subdomain = array_shift($host);
683
+	}
684
+	//--------------------------------------------------------------------
685 685
 
686 686
 }
Please login to merge, or discard this patch.
application/helpers/markdown_extended_helper.php 1 patch
Indentation   +140 added lines, -140 removed lines patch added patch discarded remove patch
@@ -1,98 +1,98 @@  discard block
 block discarded – undo
1 1
 <?php
2 2
 
3 3
 if (! function_exists('Markdown')) {
4
-    require_once('markdown_helper.php');
4
+	require_once('markdown_helper.php');
5 5
 }
6 6
 define('MARKDOWNEXTRAEXTENDED_VERSION', "0.3");
7 7
 
8 8
 function MarkdownExtended($text, $default_classes = array())
9 9
 {
10
-    $parser = new MarkdownExtraExtended_Parser($default_classes);
11
-    return $parser->transform($text);
10
+	$parser = new MarkdownExtraExtended_Parser($default_classes);
11
+	return $parser->transform($text);
12 12
 }
13 13
 
14 14
 class MarkdownExtraExtended_Parser extends MarkdownExtra_Parser
15 15
 {
16 16
 
17
-    # Tags that are always treated as block tags:
18
-    var $block_tags_re = 'figure|figcaption|p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend';
19
-
20
-    var $default_classes;
21
-
22
-    function __construct($default_classes = array())
23
-    {
24
-        $default_classes = $default_classes;
25
-
26
-        $this->block_gamut += array(
27
-            "doFencedFigures" => 7,
28
-        );
29
-
30
-        parent::__construct();
31
-    }
32
-
33
-    function transform($text)
34
-    {
35
-        $text = parent::transform($text);
36
-        return $text;
37
-    }
38
-
39
-    function doHardBreaks($text)
40
-    {
41
-        # Do hard breaks:
42
-        # EXTENDED: changed to allow breaks without two spaces and just one new line
43
-        # original code /* return preg_replace_callback('/ {2,}\n/', */
44
-        return preg_replace_callback(
45
-            '/ *\n/',
46
-            array(&$this, '_doHardBreaks_callback'),
47
-            $text
48
-        );
49
-    }
50
-
51
-    function doBlockQuotes($text)
52
-    {
53
-        $text = preg_replace_callback(
54
-            '/
17
+	# Tags that are always treated as block tags:
18
+	var $block_tags_re = 'figure|figcaption|p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend';
19
+
20
+	var $default_classes;
21
+
22
+	function __construct($default_classes = array())
23
+	{
24
+		$default_classes = $default_classes;
25
+
26
+		$this->block_gamut += array(
27
+			"doFencedFigures" => 7,
28
+		);
29
+
30
+		parent::__construct();
31
+	}
32
+
33
+	function transform($text)
34
+	{
35
+		$text = parent::transform($text);
36
+		return $text;
37
+	}
38
+
39
+	function doHardBreaks($text)
40
+	{
41
+		# Do hard breaks:
42
+		# EXTENDED: changed to allow breaks without two spaces and just one new line
43
+		# original code /* return preg_replace_callback('/ {2,}\n/', */
44
+		return preg_replace_callback(
45
+			'/ *\n/',
46
+			array(&$this, '_doHardBreaks_callback'),
47
+			$text
48
+		);
49
+	}
50
+
51
+	function doBlockQuotes($text)
52
+	{
53
+		$text = preg_replace_callback(
54
+			'/
55 55
 			(?>^[ ]*>[ ]?
56 56
 				(?:\((.+?)\))?
57 57
 				[ ]*(.+\n(?:.+\n)*)
58 58
 			)+
59 59
 			/xm',
60
-            array(&$this, '_doBlockQuotes_callback'),
61
-            $text
62
-        );
63
-
64
-        return $text;
65
-    }
66
-
67
-    function _doBlockQuotes_callback($matches)
68
-    {
69
-        $cite = $matches[1];
70
-        $bq   = '> ' . $matches[2];
71
-        # trim one level of quoting - trim whitespace-only lines
72
-        $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
73
-        $bq = $this->runBlockGamut($bq); # recurse
74
-
75
-        $bq = preg_replace('/^/m', "  ", $bq);
76
-        # These leading spaces cause problem with <pre> content,
77
-        # so we need to fix that:
78
-        $bq = preg_replace_callback(
79
-            '{(\s*<pre>.+?</pre>)}sx',
80
-            array(&$this, '_doBlockQuotes_callback2'),
81
-            $bq
82
-        );
83
-
84
-        $res = "<blockquote";
85
-        $res .= empty($cite) ? ">" : " cite=\"$cite\">";
86
-        $res .= "\n$bq\n</blockquote>";
87
-        return "\n" . $this->hashBlock($res) . "\n\n";
88
-    }
89
-
90
-    function doFencedCodeBlocks($text)
91
-    {
92
-        $less_than_tab = $this->tab_width;
93
-
94
-        $text = preg_replace_callback(
95
-            '{
60
+			array(&$this, '_doBlockQuotes_callback'),
61
+			$text
62
+		);
63
+
64
+		return $text;
65
+	}
66
+
67
+	function _doBlockQuotes_callback($matches)
68
+	{
69
+		$cite = $matches[1];
70
+		$bq   = '> ' . $matches[2];
71
+		# trim one level of quoting - trim whitespace-only lines
72
+		$bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
73
+		$bq = $this->runBlockGamut($bq); # recurse
74
+
75
+		$bq = preg_replace('/^/m', "  ", $bq);
76
+		# These leading spaces cause problem with <pre> content,
77
+		# so we need to fix that:
78
+		$bq = preg_replace_callback(
79
+			'{(\s*<pre>.+?</pre>)}sx',
80
+			array(&$this, '_doBlockQuotes_callback2'),
81
+			$bq
82
+		);
83
+
84
+		$res = "<blockquote";
85
+		$res .= empty($cite) ? ">" : " cite=\"$cite\">";
86
+		$res .= "\n$bq\n</blockquote>";
87
+		return "\n" . $this->hashBlock($res) . "\n\n";
88
+	}
89
+
90
+	function doFencedCodeBlocks($text)
91
+	{
92
+		$less_than_tab = $this->tab_width;
93
+
94
+		$text = preg_replace_callback(
95
+			'{
96 96
 				(?:\n|\A)
97 97
 				# 1: Opening marker
98 98
 				(
@@ -112,34 +112,34 @@  discard block
 block discarded – undo
112 112
 				# Closing marker.
113 113
 				\1 [ ]* \n
114 114
 			}xm',
115
-            array(&$this, '_doFencedCodeBlocks_callback'),
116
-            $text
117
-        );
118
-
119
-        return $text;
120
-    }
121
-
122
-    function _doFencedCodeBlocks_callback($matches)
123
-    {
124
-        $codeblock = $matches[4];
125
-        $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
126
-        $codeblock = preg_replace_callback(
127
-            '/^\n+/',
128
-            array(&$this, '_doFencedCodeBlocks_newlines'),
129
-            $codeblock
130
-        );
131
-        //$codeblock = "<pre><code>$codeblock</code></pre>";
132
-        //$cb = "<pre><code";
133
-        $cb = empty($matches[3]) ? "<pre><code" : "<pre class=\"linenums:$matches[3]\"><code";
134
-        $cb .= empty($matches[2]) ? ">" : " class=\"language-$matches[2]\">";
135
-        $cb .= "$codeblock</code></pre>";
136
-        return "\n\n" . $this->hashBlock($cb) . "\n\n";
137
-    }
138
-
139
-    function doFencedFigures($text)
140
-    {
141
-        $text = preg_replace_callback(
142
-            '{
115
+			array(&$this, '_doFencedCodeBlocks_callback'),
116
+			$text
117
+		);
118
+
119
+		return $text;
120
+	}
121
+
122
+	function _doFencedCodeBlocks_callback($matches)
123
+	{
124
+		$codeblock = $matches[4];
125
+		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
126
+		$codeblock = preg_replace_callback(
127
+			'/^\n+/',
128
+			array(&$this, '_doFencedCodeBlocks_newlines'),
129
+			$codeblock
130
+		);
131
+		//$codeblock = "<pre><code>$codeblock</code></pre>";
132
+		//$cb = "<pre><code";
133
+		$cb = empty($matches[3]) ? "<pre><code" : "<pre class=\"linenums:$matches[3]\"><code";
134
+		$cb .= empty($matches[2]) ? ">" : " class=\"language-$matches[2]\">";
135
+		$cb .= "$codeblock</code></pre>";
136
+		return "\n\n" . $this->hashBlock($cb) . "\n\n";
137
+	}
138
+
139
+	function doFencedFigures($text)
140
+	{
141
+		$text = preg_replace_callback(
142
+			'{
143 143
 			(?:\n|\A)
144 144
 			# 1: Opening marker
145 145
 			(
@@ -159,41 +159,41 @@  discard block
 block discarded – undo
159 159
 			# Closing marker.
160 160
 			\1 [ ]?(?:\[([^\]]+)\])?[ ]* \n
161 161
 		}xm',
162
-            array(&$this, '_doFencedFigures_callback'),
163
-            $text
164
-        );
165
-
166
-        return $text;
167
-    }
168
-
169
-    function _doFencedFigures_callback($matches)
170
-    {
171
-        # get figcaption
172
-        $topcaption    = empty($matches[2]) ? null : $this->runBlockGamut($matches[2]);
173
-        $bottomcaption = empty($matches[5]) ? null : $this->runBlockGamut($matches[5]);
174
-        $figure        = $matches[3];
175
-        $figure        = $this->runBlockGamut($figure); # recurse
176
-
177
-        $figure = preg_replace('/^/m', "  ", $figure);
178
-        # These leading spaces cause problem with <pre> content,
179
-        # so we need to fix that - reuse blockqoute code to handle this:
180
-        $figure = preg_replace_callback(
181
-            '{(\s*<pre>.+?</pre>)}sx',
182
-            array(&$this, '_doBlockQuotes_callback2'),
183
-            $figure
184
-        );
185
-
186
-        $res = "<figure>";
187
-        if (!empty($topcaption)) {
188
-            $res .= "\n<figcaption>$topcaption</figcaption>";
189
-        }
190
-        $res .= "\n$figure\n";
191
-        if (!empty($bottomcaption) && empty($topcaption)) {
192
-            $res .= "<figcaption>$bottomcaption</figcaption>";
193
-        }
194
-        $res .= "</figure>";
195
-        return "\n" . $this->hashBlock($res) . "\n\n";
196
-    }
162
+			array(&$this, '_doFencedFigures_callback'),
163
+			$text
164
+		);
165
+
166
+		return $text;
167
+	}
168
+
169
+	function _doFencedFigures_callback($matches)
170
+	{
171
+		# get figcaption
172
+		$topcaption    = empty($matches[2]) ? null : $this->runBlockGamut($matches[2]);
173
+		$bottomcaption = empty($matches[5]) ? null : $this->runBlockGamut($matches[5]);
174
+		$figure        = $matches[3];
175
+		$figure        = $this->runBlockGamut($figure); # recurse
176
+
177
+		$figure = preg_replace('/^/m', "  ", $figure);
178
+		# These leading spaces cause problem with <pre> content,
179
+		# so we need to fix that - reuse blockqoute code to handle this:
180
+		$figure = preg_replace_callback(
181
+			'{(\s*<pre>.+?</pre>)}sx',
182
+			array(&$this, '_doBlockQuotes_callback2'),
183
+			$figure
184
+		);
185
+
186
+		$res = "<figure>";
187
+		if (!empty($topcaption)) {
188
+			$res .= "\n<figcaption>$topcaption</figcaption>";
189
+		}
190
+		$res .= "\n$figure\n";
191
+		if (!empty($bottomcaption) && empty($topcaption)) {
192
+			$res .= "<figcaption>$bottomcaption</figcaption>";
193
+		}
194
+		$res .= "</figure>";
195
+		return "\n" . $this->hashBlock($res) . "\n\n";
196
+	}
197 197
 }
198 198
 
199 199
 ?>
Please login to merge, or discard this patch.
application/helpers/markdown_helper.php 1 patch
Indentation   +2725 added lines, -2725 removed lines patch added patch discarded remove patch
@@ -56,15 +56,15 @@  discard block
 block discarded – undo
56 56
 #
57 57
 # Initialize the parser and return the result of its transform method.
58 58
 #
59
-    # Setup static parser variable.
60
-    static $parser;
61
-    if (!isset($parser)) {
62
-        $parser_class = MARKDOWN_PARSER_CLASS;
63
-        $parser       = new $parser_class;
64
-    }
65
-
66
-    # Transform text using parser.
67
-    return $parser->transform($text);
59
+	# Setup static parser variable.
60
+	static $parser;
61
+	if (!isset($parser)) {
62
+		$parser_class = MARKDOWN_PARSER_CLASS;
63
+		$parser       = new $parser_class;
64
+	}
65
+
66
+	# Transform text using parser.
67
+	return $parser->transform($text);
68 68
 }
69 69
 
70 70
 ### WordPress Plugin Interface ###
@@ -80,120 +80,120 @@  discard block
 block discarded – undo
80 80
 */
81 81
 
82 82
 if (isset($wp_version)) {
83
-    # More details about how it works here:
84
-    # <http://michelf.ca/weblog/2005/wordpress-text-flow-vs-markdown/>
85
-
86
-    # Post content and excerpts
87
-    # - Remove WordPress paragraph generator.
88
-    # - Run Markdown on excerpt, then remove all tags.
89
-    # - Add paragraph tag around the excerpt, but remove it for the excerpt rss.
90
-    if (MARKDOWN_WP_POSTS) {
91
-        remove_filter('the_content', 'wpautop');
92
-        remove_filter('the_content_rss', 'wpautop');
93
-        remove_filter('the_excerpt', 'wpautop');
94
-        add_filter('the_content', 'mdwp_MarkdownPost', 6);
95
-        add_filter('the_content_rss', 'mdwp_MarkdownPost', 6);
96
-        add_filter('get_the_excerpt', 'mdwp_MarkdownPost', 6);
97
-        add_filter('get_the_excerpt', 'trim', 7);
98
-        add_filter('the_excerpt', 'mdwp_add_p');
99
-        add_filter('the_excerpt_rss', 'mdwp_strip_p');
100
-
101
-        remove_filter('content_save_pre', 'balanceTags', 50);
102
-        remove_filter('excerpt_save_pre', 'balanceTags', 50);
103
-        add_filter('the_content', 'balanceTags', 50);
104
-        add_filter('get_the_excerpt', 'balanceTags', 9);
105
-    }
106
-
107
-    # Add a footnote id prefix to posts when inside a loop.
108
-    function mdwp_MarkdownPost($text)
109
-    {
110
-        static $parser;
111
-        if (!$parser) {
112
-            $parser_class = MARKDOWN_PARSER_CLASS;
113
-            $parser       = new $parser_class;
114
-        }
115
-        if (is_single() || is_page() || is_feed()) {
116
-            $parser->fn_id_prefix = "";
117
-        } else {
118
-            $parser->fn_id_prefix = get_the_ID() . ".";
119
-        }
120
-        return $parser->transform($text);
121
-    }
122
-
123
-    # Comments
124
-    # - Remove WordPress paragraph generator.
125
-    # - Remove WordPress auto-link generator.
126
-    # - Scramble important tags before passing them to the kses filter.
127
-    # - Run Markdown on excerpt then remove paragraph tags.
128
-    if (MARKDOWN_WP_COMMENTS) {
129
-        remove_filter('comment_text', 'wpautop', 30);
130
-        remove_filter('comment_text', 'make_clickable');
131
-        add_filter('pre_comment_content', 'Markdown', 6);
132
-        add_filter('pre_comment_content', 'mdwp_hide_tags', 8);
133
-        add_filter('pre_comment_content', 'mdwp_show_tags', 12);
134
-        add_filter('get_comment_text', 'Markdown', 6);
135
-        add_filter('get_comment_excerpt', 'Markdown', 6);
136
-        add_filter('get_comment_excerpt', 'mdwp_strip_p', 7);
137
-
138
-        global $mdwp_hidden_tags, $mdwp_placeholders;
139
-        $mdwp_hidden_tags  = explode(
140
-            ' ',
141
-            '<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>'
142
-        );
143
-        $mdwp_placeholders = explode(
144
-            ' ',
145
-            str_rot13(
146
-                'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR ' .
147
-                'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli'
148
-            )
149
-        );
150
-    }
151
-
152
-    function mdwp_add_p($text)
153
-    {
154
-        if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) {
155
-            $text = '<p>' . $text . '</p>';
156
-            $text = preg_replace('{\n{2,}}', "</p>\n\n<p>", $text);
157
-        }
158
-        return $text;
159
-    }
160
-
161
-    function mdwp_strip_p($t) { return preg_replace('{</?p>}i', '', $t); }
162
-
163
-    function mdwp_hide_tags($text)
164
-    {
165
-        global $mdwp_hidden_tags, $mdwp_placeholders;
166
-        return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text);
167
-    }
168
-
169
-    function mdwp_show_tags($text)
170
-    {
171
-        global $mdwp_hidden_tags, $mdwp_placeholders;
172
-        return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text);
173
-    }
83
+	# More details about how it works here:
84
+	# <http://michelf.ca/weblog/2005/wordpress-text-flow-vs-markdown/>
85
+
86
+	# Post content and excerpts
87
+	# - Remove WordPress paragraph generator.
88
+	# - Run Markdown on excerpt, then remove all tags.
89
+	# - Add paragraph tag around the excerpt, but remove it for the excerpt rss.
90
+	if (MARKDOWN_WP_POSTS) {
91
+		remove_filter('the_content', 'wpautop');
92
+		remove_filter('the_content_rss', 'wpautop');
93
+		remove_filter('the_excerpt', 'wpautop');
94
+		add_filter('the_content', 'mdwp_MarkdownPost', 6);
95
+		add_filter('the_content_rss', 'mdwp_MarkdownPost', 6);
96
+		add_filter('get_the_excerpt', 'mdwp_MarkdownPost', 6);
97
+		add_filter('get_the_excerpt', 'trim', 7);
98
+		add_filter('the_excerpt', 'mdwp_add_p');
99
+		add_filter('the_excerpt_rss', 'mdwp_strip_p');
100
+
101
+		remove_filter('content_save_pre', 'balanceTags', 50);
102
+		remove_filter('excerpt_save_pre', 'balanceTags', 50);
103
+		add_filter('the_content', 'balanceTags', 50);
104
+		add_filter('get_the_excerpt', 'balanceTags', 9);
105
+	}
106
+
107
+	# Add a footnote id prefix to posts when inside a loop.
108
+	function mdwp_MarkdownPost($text)
109
+	{
110
+		static $parser;
111
+		if (!$parser) {
112
+			$parser_class = MARKDOWN_PARSER_CLASS;
113
+			$parser       = new $parser_class;
114
+		}
115
+		if (is_single() || is_page() || is_feed()) {
116
+			$parser->fn_id_prefix = "";
117
+		} else {
118
+			$parser->fn_id_prefix = get_the_ID() . ".";
119
+		}
120
+		return $parser->transform($text);
121
+	}
122
+
123
+	# Comments
124
+	# - Remove WordPress paragraph generator.
125
+	# - Remove WordPress auto-link generator.
126
+	# - Scramble important tags before passing them to the kses filter.
127
+	# - Run Markdown on excerpt then remove paragraph tags.
128
+	if (MARKDOWN_WP_COMMENTS) {
129
+		remove_filter('comment_text', 'wpautop', 30);
130
+		remove_filter('comment_text', 'make_clickable');
131
+		add_filter('pre_comment_content', 'Markdown', 6);
132
+		add_filter('pre_comment_content', 'mdwp_hide_tags', 8);
133
+		add_filter('pre_comment_content', 'mdwp_show_tags', 12);
134
+		add_filter('get_comment_text', 'Markdown', 6);
135
+		add_filter('get_comment_excerpt', 'Markdown', 6);
136
+		add_filter('get_comment_excerpt', 'mdwp_strip_p', 7);
137
+
138
+		global $mdwp_hidden_tags, $mdwp_placeholders;
139
+		$mdwp_hidden_tags  = explode(
140
+			' ',
141
+			'<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>'
142
+		);
143
+		$mdwp_placeholders = explode(
144
+			' ',
145
+			str_rot13(
146
+				'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR ' .
147
+				'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli'
148
+			)
149
+		);
150
+	}
151
+
152
+	function mdwp_add_p($text)
153
+	{
154
+		if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) {
155
+			$text = '<p>' . $text . '</p>';
156
+			$text = preg_replace('{\n{2,}}', "</p>\n\n<p>", $text);
157
+		}
158
+		return $text;
159
+	}
160
+
161
+	function mdwp_strip_p($t) { return preg_replace('{</?p>}i', '', $t); }
162
+
163
+	function mdwp_hide_tags($text)
164
+	{
165
+		global $mdwp_hidden_tags, $mdwp_placeholders;
166
+		return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text);
167
+	}
168
+
169
+	function mdwp_show_tags($text)
170
+	{
171
+		global $mdwp_hidden_tags, $mdwp_placeholders;
172
+		return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text);
173
+	}
174 174
 }
175 175
 
176 176
 ### bBlog Plugin Info ###
177 177
 
178 178
 function identify_modifier_markdown()
179 179
 {
180
-    return array(
181
-        'name'        => 'markdown',
182
-        'type'        => 'modifier',
183
-        'nicename'    => 'PHP Markdown Extra',
184
-        'description' => 'A text-to-HTML conversion tool for web writers',
185
-        'authors'     => 'Michel Fortin and John Gruber',
186
-        'licence'     => 'GPL',
187
-        'version'     => MARKDOWNEXTRA_VERSION,
188
-        'help'        => '<a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://michelf.ca/projects/php-markdown/">More...</a>',
189
-    );
180
+	return array(
181
+		'name'        => 'markdown',
182
+		'type'        => 'modifier',
183
+		'nicename'    => 'PHP Markdown Extra',
184
+		'description' => 'A text-to-HTML conversion tool for web writers',
185
+		'authors'     => 'Michel Fortin and John Gruber',
186
+		'licence'     => 'GPL',
187
+		'version'     => MARKDOWNEXTRA_VERSION,
188
+		'help'        => '<a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://michelf.ca/projects/php-markdown/">More...</a>',
189
+	);
190 190
 }
191 191
 
192 192
 ### Smarty Modifier Interface ###
193 193
 
194 194
 function smarty_modifier_markdown($text)
195 195
 {
196
-    return Markdown($text);
196
+	return Markdown($text);
197 197
 }
198 198
 
199 199
 ### Textile Compatibility Mode ###
@@ -201,32 +201,32 @@  discard block
 block discarded – undo
201 201
 # Rename this file to "classTextile.php" and it can replace Textile everywhere.
202 202
 
203 203
 if (strcasecmp(substr(__FILE__, - 16), "classTextile.php") == 0) {
204
-    # Try to include PHP SmartyPants. Should be in the same directory.
205
-    @include_once 'smartypants.php';
206
-
207
-    # Fake Textile class. It calls Markdown instead.
208
-    class Textile
209
-    {
210
-        function TextileThis($text, $lite = '', $encode = '')
211
-        {
212
-            if ($lite == '' && $encode == '') {
213
-                $text = Markdown($text);
214
-            }
215
-            if (function_exists('SmartyPants')) {
216
-                $text = SmartyPants($text);
217
-            }
218
-            return $text;
219
-        }
220
-
221
-        # Fake restricted version: restrictions are not supported for now.
222
-        function TextileRestricted($text, $lite = '', $noimage = '')
223
-        {
224
-            return $this->TextileThis($text, $lite);
225
-        }
226
-
227
-        # Workaround to ensure compatibility with TextPattern 4.0.3.
228
-        function blockLite($text) { return $text; }
229
-    }
204
+	# Try to include PHP SmartyPants. Should be in the same directory.
205
+	@include_once 'smartypants.php';
206
+
207
+	# Fake Textile class. It calls Markdown instead.
208
+	class Textile
209
+	{
210
+		function TextileThis($text, $lite = '', $encode = '')
211
+		{
212
+			if ($lite == '' && $encode == '') {
213
+				$text = Markdown($text);
214
+			}
215
+			if (function_exists('SmartyPants')) {
216
+				$text = SmartyPants($text);
217
+			}
218
+			return $text;
219
+		}
220
+
221
+		# Fake restricted version: restrictions are not supported for now.
222
+		function TextileRestricted($text, $lite = '', $noimage = '')
223
+		{
224
+			return $this->TextileThis($text, $lite);
225
+		}
226
+
227
+		# Workaround to ensure compatibility with TextPattern 4.0.3.
228
+		function blockLite($text) { return $text; }
229
+	}
230 230
 }
231 231
 
232 232
 #
@@ -236,156 +236,156 @@  discard block
 block discarded – undo
236 236
 class Markdown_Parser
237 237
 {
238 238
 
239
-    ### Configuration Variables ###
239
+	### Configuration Variables ###
240 240
 
241
-    # Change to ">" for HTML output.
242
-    var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
241
+	# Change to ">" for HTML output.
242
+	var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
243 243
 
244
-    var $tab_width = MARKDOWN_TAB_WIDTH;
244
+	var $tab_width = MARKDOWN_TAB_WIDTH;
245 245
 
246
-    # Change to `true` to disallow markup or entities.
247
-    var $no_markup = false;
246
+	# Change to `true` to disallow markup or entities.
247
+	var $no_markup = false;
248 248
 
249
-    var $no_entities = false;
249
+	var $no_entities = false;
250 250
 
251
-    # Predefined urls and titles for reference links and images.
252
-    var $predef_urls = array();
251
+	# Predefined urls and titles for reference links and images.
252
+	var $predef_urls = array();
253 253
 
254
-    var $predef_titles = array();
254
+	var $predef_titles = array();
255 255
 
256
-    ### Parser Implementation ###
256
+	### Parser Implementation ###
257 257
 
258
-    # Regex to match balanced [brackets].
259
-    # Needed to insert a maximum bracked depth while converting to PHP.
260
-    var $nested_brackets_depth = 6;
258
+	# Regex to match balanced [brackets].
259
+	# Needed to insert a maximum bracked depth while converting to PHP.
260
+	var $nested_brackets_depth = 6;
261 261
 
262
-    var $nested_brackets_re;
262
+	var $nested_brackets_re;
263 263
 
264
-    var $nested_url_parenthesis_depth = 4;
264
+	var $nested_url_parenthesis_depth = 4;
265 265
 
266
-    var $nested_url_parenthesis_re;
266
+	var $nested_url_parenthesis_re;
267 267
 
268
-    # Table of hash values for escaped characters:
269
-    var $escape_chars = '\`*_{}[]()>#+-.!';
268
+	# Table of hash values for escaped characters:
269
+	var $escape_chars = '\`*_{}[]()>#+-.!';
270 270
 
271
-    var $escape_chars_re;
271
+	var $escape_chars_re;
272 272
 
273
-    function __construct()
274
-    {
275
-        #
276
-        # Constructor function. Initialize appropriate member variables.
277
-        #
278
-        $this->_initDetab();
279
-        $this->prepareItalicsAndBold();
273
+	function __construct()
274
+	{
275
+		#
276
+		# Constructor function. Initialize appropriate member variables.
277
+		#
278
+		$this->_initDetab();
279
+		$this->prepareItalicsAndBold();
280 280
 
281
-        $this->nested_brackets_re =
282
-            str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth) .
283
-            str_repeat('\])*', $this->nested_brackets_depth);
281
+		$this->nested_brackets_re =
282
+			str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth) .
283
+			str_repeat('\])*', $this->nested_brackets_depth);
284 284
 
285
-        $this->nested_url_parenthesis_re =
286
-            str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth) .
287
-            str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
285
+		$this->nested_url_parenthesis_re =
286
+			str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth) .
287
+			str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
288 288
 
289
-        $this->escape_chars_re = '[' . preg_quote($this->escape_chars) . ']';
289
+		$this->escape_chars_re = '[' . preg_quote($this->escape_chars) . ']';
290 290
 
291
-        # Sort document, block, and span gamut in ascendent priority order.
292
-        asort($this->document_gamut);
293
-        asort($this->block_gamut);
294
-        asort($this->span_gamut);
295
-    }
291
+		# Sort document, block, and span gamut in ascendent priority order.
292
+		asort($this->document_gamut);
293
+		asort($this->block_gamut);
294
+		asort($this->span_gamut);
295
+	}
296 296
 
297
-    # Internal hashes used during transformation.
298
-    var $urls = array();
297
+	# Internal hashes used during transformation.
298
+	var $urls = array();
299 299
 
300
-    var $titles = array();
300
+	var $titles = array();
301 301
 
302
-    var $html_hashes = array();
302
+	var $html_hashes = array();
303 303
 
304
-    # Status flag to avoid invalid nesting.
305
-    var $in_anchor = false;
304
+	# Status flag to avoid invalid nesting.
305
+	var $in_anchor = false;
306 306
 
307
-    function setup()
308
-    {
309
-        #
310
-        # Called before the transformation process starts to setup parser
311
-        # states.
312
-        #
313
-        # Clear global hashes.
314
-        $this->urls        = $this->predef_urls;
315
-        $this->titles      = $this->predef_titles;
316
-        $this->html_hashes = array();
307
+	function setup()
308
+	{
309
+		#
310
+		# Called before the transformation process starts to setup parser
311
+		# states.
312
+		#
313
+		# Clear global hashes.
314
+		$this->urls        = $this->predef_urls;
315
+		$this->titles      = $this->predef_titles;
316
+		$this->html_hashes = array();
317 317
 
318
-        $this->in_anchor = false;
319
-    }
318
+		$this->in_anchor = false;
319
+	}
320 320
 
321
-    function teardown()
322
-    {
323
-        #
324
-        # Called after the transformation process to clear any variable
325
-        # which may be taking up memory unnecessarly.
326
-        #
327
-        $this->urls        = array();
328
-        $this->titles      = array();
329
-        $this->html_hashes = array();
330
-    }
321
+	function teardown()
322
+	{
323
+		#
324
+		# Called after the transformation process to clear any variable
325
+		# which may be taking up memory unnecessarly.
326
+		#
327
+		$this->urls        = array();
328
+		$this->titles      = array();
329
+		$this->html_hashes = array();
330
+	}
331 331
 
332
-    function transform($text)
333
-    {
334
-        #
335
-        # Main function. Performs some preprocessing on the input text
336
-        # and pass it through the document gamut.
337
-        #
338
-        $this->setup();
332
+	function transform($text)
333
+	{
334
+		#
335
+		# Main function. Performs some preprocessing on the input text
336
+		# and pass it through the document gamut.
337
+		#
338
+		$this->setup();
339 339
 
340
-        # Remove UTF-8 BOM and marker character in input, if present.
341
-        $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
340
+		# Remove UTF-8 BOM and marker character in input, if present.
341
+		$text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
342 342
 
343
-        # Standardize line endings:
344
-        #   DOS to Unix and Mac to Unix
345
-        $text = preg_replace('{\r\n?}', "\n", $text);
343
+		# Standardize line endings:
344
+		#   DOS to Unix and Mac to Unix
345
+		$text = preg_replace('{\r\n?}', "\n", $text);
346 346
 
347
-        # Make sure $text ends with a couple of newlines:
348
-        $text .= "\n\n";
349
-
350
-        # Convert all tabs to spaces.
351
-        $text = $this->detab($text);
352
-
353
-        # Turn block-level HTML blocks into hash entries
354
-        $text = $this->hashHTMLBlocks($text);
355
-
356
-        # Strip any lines consisting only of spaces and tabs.
357
-        # This makes subsequent regexen easier to write, because we can
358
-        # match consecutive blank lines with /\n+/ instead of something
359
-        # contorted like /[ ]*\n+/ .
360
-        $text = preg_replace('/^[ ]+$/m', '', $text);
361
-
362
-        # Run document gamut methods.
363
-        foreach ($this->document_gamut as $method => $priority) {
364
-            $text = $this->$method($text);
365
-        }
366
-
367
-        $this->teardown();
368
-
369
-        return $text . "\n";
370
-    }
371
-
372
-    var $document_gamut = array(
373
-        # Strip link definitions, store in hashes.
374
-        "stripLinkDefinitions" => 20,
375
-        "runBasicBlockGamut"   => 30,
376
-    );
377
-
378
-    function stripLinkDefinitions($text)
379
-    {
380
-        #
381
-        # Strips link definitions from text, stores the URLs and titles in
382
-        # hash references.
383
-        #
384
-        $less_than_tab = $this->tab_width - 1;
385
-
386
-        # Link defs are in the form: ^[id]: url "optional title"
387
-        $text = preg_replace_callback(
388
-            '{
347
+		# Make sure $text ends with a couple of newlines:
348
+		$text .= "\n\n";
349
+
350
+		# Convert all tabs to spaces.
351
+		$text = $this->detab($text);
352
+
353
+		# Turn block-level HTML blocks into hash entries
354
+		$text = $this->hashHTMLBlocks($text);
355
+
356
+		# Strip any lines consisting only of spaces and tabs.
357
+		# This makes subsequent regexen easier to write, because we can
358
+		# match consecutive blank lines with /\n+/ instead of something
359
+		# contorted like /[ ]*\n+/ .
360
+		$text = preg_replace('/^[ ]+$/m', '', $text);
361
+
362
+		# Run document gamut methods.
363
+		foreach ($this->document_gamut as $method => $priority) {
364
+			$text = $this->$method($text);
365
+		}
366
+
367
+		$this->teardown();
368
+
369
+		return $text . "\n";
370
+	}
371
+
372
+	var $document_gamut = array(
373
+		# Strip link definitions, store in hashes.
374
+		"stripLinkDefinitions" => 20,
375
+		"runBasicBlockGamut"   => 30,
376
+	);
377
+
378
+	function stripLinkDefinitions($text)
379
+	{
380
+		#
381
+		# Strips link definitions from text, stores the URLs and titles in
382
+		# hash references.
383
+		#
384
+		$less_than_tab = $this->tab_width - 1;
385
+
386
+		# Link defs are in the form: ^[id]: url "optional title"
387
+		$text = preg_replace_callback(
388
+			'{
389 389
 							^[ ]{0,' . $less_than_tab . '}\[(.+)\][ ]?:	# id = $1
390 390
 							  [ ]*
391 391
 							  \n?				# maybe *one* newline
@@ -407,51 +407,51 @@  discard block
 block discarded – undo
407 407
 							)?	# title is optional
408 408
 							(?:\n+|\Z)
409 409
 			}xm',
410
-            array(&$this, '_stripLinkDefinitions_callback'),
411
-            $text
412
-        );
413
-        return $text;
414
-    }
415
-
416
-    function _stripLinkDefinitions_callback($matches)
417
-    {
418
-        $link_id                = strtolower($matches[1]);
419
-        $url                    = $matches[2] == '' ? $matches[3] : $matches[2];
420
-        $this->urls[$link_id]   = $url;
421
-        $this->titles[$link_id] =& $matches[4];
422
-        return ''; # String that will replace the block
423
-    }
424
-
425
-    function hashHTMLBlocks($text)
426
-    {
427
-        if ($this->no_markup) {
428
-            return $text;
429
-        }
430
-
431
-        $less_than_tab = $this->tab_width - 1;
432
-
433
-        # Hashify HTML blocks:
434
-        # We only want to do this for block-level HTML tags, such as headers,
435
-        # lists, and tables. That's because we still want to wrap <p>s around
436
-        # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
437
-        # phrase emphasis, and spans. The list of tags we're looking for is
438
-        # hard-coded:
439
-        #
440
-        # *  List "a" is made of tags which can be both inline or block-level.
441
-        #    These will be treated block-level when the start tag is alone on
442
-        #    its line, otherwise they're not matched here and will be taken as
443
-        #    inline later.
444
-        # *  List "b" is made of tags which are always block-level;
445
-        #
446
-        $block_tags_a_re = 'ins|del';
447
-        $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|' .
448
-                           'script|noscript|form|fieldset|iframe|math|svg|' .
449
-                           'article|section|nav|aside|hgroup|header|footer|' .
450
-                           'figure';
451
-
452
-        # Regular expression for the content of a block tag.
453
-        $nested_tags_level = 4;
454
-        $attr              = '
410
+			array(&$this, '_stripLinkDefinitions_callback'),
411
+			$text
412
+		);
413
+		return $text;
414
+	}
415
+
416
+	function _stripLinkDefinitions_callback($matches)
417
+	{
418
+		$link_id                = strtolower($matches[1]);
419
+		$url                    = $matches[2] == '' ? $matches[3] : $matches[2];
420
+		$this->urls[$link_id]   = $url;
421
+		$this->titles[$link_id] =& $matches[4];
422
+		return ''; # String that will replace the block
423
+	}
424
+
425
+	function hashHTMLBlocks($text)
426
+	{
427
+		if ($this->no_markup) {
428
+			return $text;
429
+		}
430
+
431
+		$less_than_tab = $this->tab_width - 1;
432
+
433
+		# Hashify HTML blocks:
434
+		# We only want to do this for block-level HTML tags, such as headers,
435
+		# lists, and tables. That's because we still want to wrap <p>s around
436
+		# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
437
+		# phrase emphasis, and spans. The list of tags we're looking for is
438
+		# hard-coded:
439
+		#
440
+		# *  List "a" is made of tags which can be both inline or block-level.
441
+		#    These will be treated block-level when the start tag is alone on
442
+		#    its line, otherwise they're not matched here and will be taken as
443
+		#    inline later.
444
+		# *  List "b" is made of tags which are always block-level;
445
+		#
446
+		$block_tags_a_re = 'ins|del';
447
+		$block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|' .
448
+						   'script|noscript|form|fieldset|iframe|math|svg|' .
449
+						   'article|section|nav|aside|hgroup|header|footer|' .
450
+						   'figure';
451
+
452
+		# Regular expression for the content of a block tag.
453
+		$nested_tags_level = 4;
454
+		$attr              = '
455 455
 			(?>				# optional tag attributes
456 456
 			  \s			# starts with whitespace
457 457
 			  (?>
@@ -465,9 +465,9 @@  discard block
 block discarded – undo
465 465
 			  )*
466 466
 			)?
467 467
 			';
468
-        $content           =
469
-            str_repeat(
470
-                '
468
+		$content           =
469
+			str_repeat(
470
+				'
471 471
 				(?>
472 472
 				  [^<]+			# content without tag
473 473
 				|
@@ -477,34 +477,34 @@  discard block
 block discarded – undo
477 477
 					  />
478 478
 					|
479 479
 					  >',
480
-                $nested_tags_level
481
-            ) . # end of opening tag
482
-            '.*?' . # last level nested tag content
483
-            str_repeat(
484
-                '
480
+				$nested_tags_level
481
+			) . # end of opening tag
482
+			'.*?' . # last level nested tag content
483
+			str_repeat(
484
+				'
485 485
 					  </\2\s*>	# closing nested tag
486 486
 					)
487 487
 				  |
488 488
 					<(?!/\2\s*>	# other tags with a different name
489 489
 				  )
490 490
 				)*',
491
-                $nested_tags_level
492
-            );
493
-        $content2          = str_replace('\2', '\3', $content);
494
-
495
-        # First, look for nested blocks, e.g.:
496
-        # 	<div>
497
-        # 		<div>
498
-        # 		tags for inner block must be indented.
499
-        # 		</div>
500
-        # 	</div>
501
-        #
502
-        # The outermost tags must start at the left margin for this to match, and
503
-        # the inner nested divs must be indented.
504
-        # We need to do this before the next, more liberal match, because the next
505
-        # match will start at the first `<div>` and stop at the first `</div>`.
506
-        $text = preg_replace_callback(
507
-            '{(?>
491
+				$nested_tags_level
492
+			);
493
+		$content2          = str_replace('\2', '\3', $content);
494
+
495
+		# First, look for nested blocks, e.g.:
496
+		# 	<div>
497
+		# 		<div>
498
+		# 		tags for inner block must be indented.
499
+		# 		</div>
500
+		# 	</div>
501
+		#
502
+		# The outermost tags must start at the left margin for this to match, and
503
+		# the inner nested divs must be indented.
504
+		# We need to do this before the next, more liberal match, because the next
505
+		# match will start at the first `<div>` and stop at the first `</div>`.
506
+		$text = preg_replace_callback(
507
+			'{(?>
508 508
 			(?>
509 509
 				(?<=\n\n)		# Starting after a blank line
510 510
 				|				# or
@@ -565,100 +565,100 @@  discard block
 block discarded – undo
565 565
 
566 566
 			)
567 567
 			)}Sxmi',
568
-            array(&$this, '_hashHTMLBlocks_callback'),
569
-            $text
570
-        );
571
-
572
-        return $text;
573
-    }
574
-
575
-    function _hashHTMLBlocks_callback($matches)
576
-    {
577
-        $text = $matches[1];
578
-        $key  = $this->hashBlock($text);
579
-        return "\n\n$key\n\n";
580
-    }
581
-
582
-    function hashPart($text, $boundary = 'X')
583
-    {
584
-        #
585
-        # Called whenever a tag must be hashed when a function insert an atomic
586
-        # element in the text stream. Passing $text to through this function gives
587
-        # a unique text-token which will be reverted back when calling unhash.
588
-        #
589
-        # The $boundary argument specify what character should be used to surround
590
-        # the token. By convension, "B" is used for block elements that needs not
591
-        # to be wrapped into paragraph tags at the end, ":" is used for elements
592
-        # that are word separators and "X" is used in the general case.
593
-        #
594
-        # Swap back any tag hash found in $text so we do not have to `unhash`
595
-        # multiple times at the end.
596
-        $text = $this->unhash($text);
597
-
598
-        # Then hash the block.
599
-        static $i = 0;
600
-        $key                     = "$boundary\x1A" . ++ $i . $boundary;
601
-        $this->html_hashes[$key] = $text;
602
-        return $key; # String that will replace the tag.
603
-    }
604
-
605
-    function hashBlock($text)
606
-    {
607
-        #
608
-        # Shortcut function for hashPart with block-level boundaries.
609
-        #
610
-        return $this->hashPart($text, 'B');
611
-    }
612
-
613
-    var $block_gamut = array(
614
-        #
615
-        # These are all the transformations that form block-level
616
-        # tags like paragraphs, headers, and list items.
617
-        #
618
-        "doHeaders"         => 10,
619
-        "doHorizontalRules" => 20,
620
-        "doLists"           => 40,
621
-        "doCodeBlocks"      => 50,
622
-        "doBlockQuotes"     => 60,
623
-    );
624
-
625
-    function runBlockGamut($text)
626
-    {
627
-        #
628
-        # Run block gamut tranformations.
629
-        #
630
-        # We need to escape raw HTML in Markdown source before doing anything
631
-        # else. This need to be done for each block, and not only at the
632
-        # begining in the Markdown function since hashed blocks can be part of
633
-        # list items and could have been indented. Indented blocks would have
634
-        # been seen as a code block in a previous pass of hashHTMLBlocks.
635
-        $text = $this->hashHTMLBlocks($text);
636
-
637
-        return $this->runBasicBlockGamut($text);
638
-    }
639
-
640
-    function runBasicBlockGamut($text)
641
-    {
642
-        #
643
-        # Run block gamut tranformations, without hashing HTML blocks. This is
644
-        # useful when HTML blocks are known to be already hashed, like in the first
645
-        # whole-document pass.
646
-        #
647
-        foreach ($this->block_gamut as $method => $priority) {
648
-            $text = $this->$method($text);
649
-        }
650
-
651
-        # Finally form paragraph and restore hashed blocks.
652
-        $text = $this->formParagraphs($text);
653
-
654
-        return $text;
655
-    }
656
-
657
-    function doHorizontalRules($text)
658
-    {
659
-        # Do Horizontal Rules:
660
-        return preg_replace(
661
-            '{
568
+			array(&$this, '_hashHTMLBlocks_callback'),
569
+			$text
570
+		);
571
+
572
+		return $text;
573
+	}
574
+
575
+	function _hashHTMLBlocks_callback($matches)
576
+	{
577
+		$text = $matches[1];
578
+		$key  = $this->hashBlock($text);
579
+		return "\n\n$key\n\n";
580
+	}
581
+
582
+	function hashPart($text, $boundary = 'X')
583
+	{
584
+		#
585
+		# Called whenever a tag must be hashed when a function insert an atomic
586
+		# element in the text stream. Passing $text to through this function gives
587
+		# a unique text-token which will be reverted back when calling unhash.
588
+		#
589
+		# The $boundary argument specify what character should be used to surround
590
+		# the token. By convension, "B" is used for block elements that needs not
591
+		# to be wrapped into paragraph tags at the end, ":" is used for elements
592
+		# that are word separators and "X" is used in the general case.
593
+		#
594
+		# Swap back any tag hash found in $text so we do not have to `unhash`
595
+		# multiple times at the end.
596
+		$text = $this->unhash($text);
597
+
598
+		# Then hash the block.
599
+		static $i = 0;
600
+		$key                     = "$boundary\x1A" . ++ $i . $boundary;
601
+		$this->html_hashes[$key] = $text;
602
+		return $key; # String that will replace the tag.
603
+	}
604
+
605
+	function hashBlock($text)
606
+	{
607
+		#
608
+		# Shortcut function for hashPart with block-level boundaries.
609
+		#
610
+		return $this->hashPart($text, 'B');
611
+	}
612
+
613
+	var $block_gamut = array(
614
+		#
615
+		# These are all the transformations that form block-level
616
+		# tags like paragraphs, headers, and list items.
617
+		#
618
+		"doHeaders"         => 10,
619
+		"doHorizontalRules" => 20,
620
+		"doLists"           => 40,
621
+		"doCodeBlocks"      => 50,
622
+		"doBlockQuotes"     => 60,
623
+	);
624
+
625
+	function runBlockGamut($text)
626
+	{
627
+		#
628
+		# Run block gamut tranformations.
629
+		#
630
+		# We need to escape raw HTML in Markdown source before doing anything
631
+		# else. This need to be done for each block, and not only at the
632
+		# begining in the Markdown function since hashed blocks can be part of
633
+		# list items and could have been indented. Indented blocks would have
634
+		# been seen as a code block in a previous pass of hashHTMLBlocks.
635
+		$text = $this->hashHTMLBlocks($text);
636
+
637
+		return $this->runBasicBlockGamut($text);
638
+	}
639
+
640
+	function runBasicBlockGamut($text)
641
+	{
642
+		#
643
+		# Run block gamut tranformations, without hashing HTML blocks. This is
644
+		# useful when HTML blocks are known to be already hashed, like in the first
645
+		# whole-document pass.
646
+		#
647
+		foreach ($this->block_gamut as $method => $priority) {
648
+			$text = $this->$method($text);
649
+		}
650
+
651
+		# Finally form paragraph and restore hashed blocks.
652
+		$text = $this->formParagraphs($text);
653
+
654
+		return $text;
655
+	}
656
+
657
+	function doHorizontalRules($text)
658
+	{
659
+		# Do Horizontal Rules:
660
+		return preg_replace(
661
+			'{
662 662
 				^[ ]{0,3}	# Leading space
663 663
 				([-*_])		# $1: First marker
664 664
 				(?>			# Repeated marker group
@@ -668,74 +668,74 @@  discard block
 block discarded – undo
668 668
 				[ ]*		# Tailing spaces
669 669
 				$			# End of line.
670 670
 			}mx',
671
-            "\n" . $this->hashBlock("<hr$this->empty_element_suffix") . "\n",
672
-            $text
673
-        );
674
-    }
675
-
676
-    var $span_gamut = array(
677
-        #
678
-        # These are all the transformations that occur *within* block-level
679
-        # tags like paragraphs, headers, and list items.
680
-        #
681
-        # Process character escapes, code spans, and inline HTML
682
-        # in one shot.
683
-        "parseSpan"           => - 30,
684
-        # Process anchor and image tags. Images must come first,
685
-        # because ![foo][f] looks like an anchor.
686
-        "doImages"            => 10,
687
-        "doAnchors"           => 20,
688
-        # Make links out of things like `<http://example.com/>`
689
-        # Must come after doAnchors, because you can use < and >
690
-        # delimiters in inline links like [this](<url>).
691
-        "doAutoLinks"         => 30,
692
-        "encodeAmpsAndAngles" => 40,
693
-        "doItalicsAndBold"    => 50,
694
-        "doHardBreaks"        => 60,
695
-    );
696
-
697
-    function runSpanGamut($text)
698
-    {
699
-        #
700
-        # Run span gamut tranformations.
701
-        #
702
-        foreach ($this->span_gamut as $method => $priority) {
703
-            $text = $this->$method($text);
704
-        }
705
-
706
-        return $text;
707
-    }
708
-
709
-    function doHardBreaks($text)
710
-    {
711
-        # Do hard breaks:
712
-        return preg_replace_callback(
713
-            '/ {2,}\n/',
714
-            array(&$this, '_doHardBreaks_callback'),
715
-            $text
716
-        );
717
-    }
718
-
719
-    function _doHardBreaks_callback($matches)
720
-    {
721
-        return $this->hashPart("<br$this->empty_element_suffix\n");
722
-    }
723
-
724
-    function doAnchors($text)
725
-    {
726
-        #
727
-        # Turn Markdown link shortcuts into XHTML <a> tags.
728
-        #
729
-        if ($this->in_anchor) {
730
-            return $text;
731
-        }
732
-        $this->in_anchor = true;
733
-
734
-        #
735
-        # First, handle reference-style links: [link text] [id]
736
-        #
737
-        $text = preg_replace_callback(
738
-            '{
671
+			"\n" . $this->hashBlock("<hr$this->empty_element_suffix") . "\n",
672
+			$text
673
+		);
674
+	}
675
+
676
+	var $span_gamut = array(
677
+		#
678
+		# These are all the transformations that occur *within* block-level
679
+		# tags like paragraphs, headers, and list items.
680
+		#
681
+		# Process character escapes, code spans, and inline HTML
682
+		# in one shot.
683
+		"parseSpan"           => - 30,
684
+		# Process anchor and image tags. Images must come first,
685
+		# because ![foo][f] looks like an anchor.
686
+		"doImages"            => 10,
687
+		"doAnchors"           => 20,
688
+		# Make links out of things like `<http://example.com/>`
689
+		# Must come after doAnchors, because you can use < and >
690
+		# delimiters in inline links like [this](<url>).
691
+		"doAutoLinks"         => 30,
692
+		"encodeAmpsAndAngles" => 40,
693
+		"doItalicsAndBold"    => 50,
694
+		"doHardBreaks"        => 60,
695
+	);
696
+
697
+	function runSpanGamut($text)
698
+	{
699
+		#
700
+		# Run span gamut tranformations.
701
+		#
702
+		foreach ($this->span_gamut as $method => $priority) {
703
+			$text = $this->$method($text);
704
+		}
705
+
706
+		return $text;
707
+	}
708
+
709
+	function doHardBreaks($text)
710
+	{
711
+		# Do hard breaks:
712
+		return preg_replace_callback(
713
+			'/ {2,}\n/',
714
+			array(&$this, '_doHardBreaks_callback'),
715
+			$text
716
+		);
717
+	}
718
+
719
+	function _doHardBreaks_callback($matches)
720
+	{
721
+		return $this->hashPart("<br$this->empty_element_suffix\n");
722
+	}
723
+
724
+	function doAnchors($text)
725
+	{
726
+		#
727
+		# Turn Markdown link shortcuts into XHTML <a> tags.
728
+		#
729
+		if ($this->in_anchor) {
730
+			return $text;
731
+		}
732
+		$this->in_anchor = true;
733
+
734
+		#
735
+		# First, handle reference-style links: [link text] [id]
736
+		#
737
+		$text = preg_replace_callback(
738
+			'{
739 739
 			(					# wrap whole match in $1
740 740
 			  \[
741 741
 				(' . $this->nested_brackets_re . ')	# link text = $2
@@ -749,15 +749,15 @@  discard block
 block discarded – undo
749 749
 			  \]
750 750
 			)
751 751
 			}xs',
752
-            array(&$this, '_doAnchors_reference_callback'),
753
-            $text
754
-        );
755
-
756
-        #
757
-        # Next, inline-style links: [link text](url "optional title")
758
-        #
759
-        $text = preg_replace_callback(
760
-            '{
752
+			array(&$this, '_doAnchors_reference_callback'),
753
+			$text
754
+		);
755
+
756
+		#
757
+		# Next, inline-style links: [link text](url "optional title")
758
+		#
759
+		$text = preg_replace_callback(
760
+			'{
761 761
 			(				# wrap whole match in $1
762 762
 			  \[
763 763
 				(' . $this->nested_brackets_re . ')	# link text = $2
@@ -779,97 +779,97 @@  discard block
 block discarded – undo
779 779
 			  \)
780 780
 			)
781 781
 			}xs',
782
-            array(&$this, '_doAnchors_inline_callback'),
783
-            $text
784
-        );
785
-
786
-        #
787
-        # Last, handle reference-style shortcuts: [link text]
788
-        # These must come last in case you've also got [link text][1]
789
-        # or [link text](/foo)
790
-        #
791
-        $text = preg_replace_callback(
792
-            '{
782
+			array(&$this, '_doAnchors_inline_callback'),
783
+			$text
784
+		);
785
+
786
+		#
787
+		# Last, handle reference-style shortcuts: [link text]
788
+		# These must come last in case you've also got [link text][1]
789
+		# or [link text](/foo)
790
+		#
791
+		$text = preg_replace_callback(
792
+			'{
793 793
 			(					# wrap whole match in $1
794 794
 			  \[
795 795
 				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
796 796
 			  \]
797 797
 			)
798 798
 			}xs',
799
-            array(&$this, '_doAnchors_reference_callback'),
800
-            $text
801
-        );
802
-
803
-        $this->in_anchor = false;
804
-        return $text;
805
-    }
806
-
807
-    function _doAnchors_reference_callback($matches)
808
-    {
809
-        $whole_match = $matches[1];
810
-        $link_text   = $matches[2];
811
-        $link_id     =& $matches[3];
812
-
813
-        if ($link_id == "") {
814
-            # for shortcut links like [this][] or [this].
815
-            $link_id = $link_text;
816
-        }
817
-
818
-        # lower-case and turn embedded newlines into spaces
819
-        $link_id = strtolower($link_id);
820
-        $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
821
-
822
-        if (isset($this->urls[$link_id])) {
823
-            $url = $this->urls[$link_id];
824
-            $url = $this->encodeAttribute($url);
825
-
826
-            $result = "<a href=\"$url\"";
827
-            if (isset($this->titles[$link_id])) {
828
-                $title = $this->titles[$link_id];
829
-                $title = $this->encodeAttribute($title);
830
-                $result .= " title=\"$title\"";
831
-            }
832
-
833
-            $link_text = $this->runSpanGamut($link_text);
834
-            $result .= ">$link_text</a>";
835
-            $result = $this->hashPart($result);
836
-        } else {
837
-            $result = $whole_match;
838
-        }
839
-        return $result;
840
-    }
841
-
842
-    function _doAnchors_inline_callback($matches)
843
-    {
844
-        $whole_match = $matches[1];
845
-        $link_text   = $this->runSpanGamut($matches[2]);
846
-        $url         = $matches[3] == '' ? $matches[4] : $matches[3];
847
-        $title       =& $matches[7];
848
-
849
-        $url = $this->encodeAttribute($url);
850
-
851
-        $result = "<a href=\"$url\"";
852
-        if (isset($title)) {
853
-            $title = $this->encodeAttribute($title);
854
-            $result .= " title=\"$title\"";
855
-        }
856
-
857
-        $link_text = $this->runSpanGamut($link_text);
858
-        $result .= ">$link_text</a>";
859
-
860
-        return $this->hashPart($result);
861
-    }
862
-
863
-    function doImages($text)
864
-    {
865
-        #
866
-        # Turn Markdown image shortcuts into <img> tags.
867
-        #
868
-        #
869
-        # First, handle reference-style labeled images: ![alt text][id]
870
-        #
871
-        $text = preg_replace_callback(
872
-            '{
799
+			array(&$this, '_doAnchors_reference_callback'),
800
+			$text
801
+		);
802
+
803
+		$this->in_anchor = false;
804
+		return $text;
805
+	}
806
+
807
+	function _doAnchors_reference_callback($matches)
808
+	{
809
+		$whole_match = $matches[1];
810
+		$link_text   = $matches[2];
811
+		$link_id     =& $matches[3];
812
+
813
+		if ($link_id == "") {
814
+			# for shortcut links like [this][] or [this].
815
+			$link_id = $link_text;
816
+		}
817
+
818
+		# lower-case and turn embedded newlines into spaces
819
+		$link_id = strtolower($link_id);
820
+		$link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
821
+
822
+		if (isset($this->urls[$link_id])) {
823
+			$url = $this->urls[$link_id];
824
+			$url = $this->encodeAttribute($url);
825
+
826
+			$result = "<a href=\"$url\"";
827
+			if (isset($this->titles[$link_id])) {
828
+				$title = $this->titles[$link_id];
829
+				$title = $this->encodeAttribute($title);
830
+				$result .= " title=\"$title\"";
831
+			}
832
+
833
+			$link_text = $this->runSpanGamut($link_text);
834
+			$result .= ">$link_text</a>";
835
+			$result = $this->hashPart($result);
836
+		} else {
837
+			$result = $whole_match;
838
+		}
839
+		return $result;
840
+	}
841
+
842
+	function _doAnchors_inline_callback($matches)
843
+	{
844
+		$whole_match = $matches[1];
845
+		$link_text   = $this->runSpanGamut($matches[2]);
846
+		$url         = $matches[3] == '' ? $matches[4] : $matches[3];
847
+		$title       =& $matches[7];
848
+
849
+		$url = $this->encodeAttribute($url);
850
+
851
+		$result = "<a href=\"$url\"";
852
+		if (isset($title)) {
853
+			$title = $this->encodeAttribute($title);
854
+			$result .= " title=\"$title\"";
855
+		}
856
+
857
+		$link_text = $this->runSpanGamut($link_text);
858
+		$result .= ">$link_text</a>";
859
+
860
+		return $this->hashPart($result);
861
+	}
862
+
863
+	function doImages($text)
864
+	{
865
+		#
866
+		# Turn Markdown image shortcuts into <img> tags.
867
+		#
868
+		#
869
+		# First, handle reference-style labeled images: ![alt text][id]
870
+		#
871
+		$text = preg_replace_callback(
872
+			'{
873 873
 			(				# wrap whole match in $1
874 874
 			  !\[
875 875
 				(' . $this->nested_brackets_re . ')		# alt text = $2
@@ -884,16 +884,16 @@  discard block
 block discarded – undo
884 884
 
885 885
 			)
886 886
 			}xs',
887
-            array(&$this, '_doImages_reference_callback'),
888
-            $text
889
-        );
890
-
891
-        #
892
-        # Next, handle inline images:  ![alt text](url "optional title")
893
-        # Don't forget: encode * and _
894
-        #
895
-        $text = preg_replace_callback(
896
-            '{
887
+			array(&$this, '_doImages_reference_callback'),
888
+			$text
889
+		);
890
+
891
+		#
892
+		# Next, handle inline images:  ![alt text](url "optional title")
893
+		# Don't forget: encode * and _
894
+		#
895
+		$text = preg_replace_callback(
896
+			'{
897 897
 			(				# wrap whole match in $1
898 898
 			  !\[
899 899
 				(' . $this->nested_brackets_re . ')		# alt text = $2
@@ -916,85 +916,85 @@  discard block
 block discarded – undo
916 916
 			  \)
917 917
 			)
918 918
 			}xs',
919
-            array(&$this, '_doImages_inline_callback'),
920
-            $text
921
-        );
922
-
923
-        return $text;
924
-    }
925
-
926
-    function _doImages_reference_callback($matches)
927
-    {
928
-        $whole_match = $matches[1];
929
-        $alt_text    = $matches[2];
930
-        $link_id     = strtolower($matches[3]);
931
-
932
-        if ($link_id == "") {
933
-            $link_id = strtolower($alt_text); # for shortcut links like ![this][].
934
-        }
935
-
936
-        $alt_text = $this->encodeAttribute($alt_text);
937
-        if (isset($this->urls[$link_id])) {
938
-            $url    = $this->encodeAttribute($this->urls[$link_id]);
939
-            $result = "<img src=\"$url\" alt=\"$alt_text\"";
940
-            if (isset($this->titles[$link_id])) {
941
-                $title = $this->titles[$link_id];
942
-                $title = $this->encodeAttribute($title);
943
-                $result .= " title=\"$title\"";
944
-            }
945
-            $result .= $this->empty_element_suffix;
946
-            $result = $this->hashPart($result);
947
-        } else {
948
-            # If there's no such link ID, leave intact:
949
-            $result = $whole_match;
950
-        }
951
-
952
-        return $result;
953
-    }
954
-
955
-    function _doImages_inline_callback($matches)
956
-    {
957
-        $whole_match = $matches[1];
958
-        $alt_text    = $matches[2];
959
-        $url         = $matches[3] == '' ? $matches[4] : $matches[3];
960
-        $title       =& $matches[7];
961
-
962
-        $alt_text = $this->encodeAttribute($alt_text);
963
-        $url      = $this->encodeAttribute($url);
964
-        $result   = "<img src=\"$url\" alt=\"$alt_text\"";
965
-        if (isset($title)) {
966
-            $title = $this->encodeAttribute($title);
967
-            $result .= " title=\"$title\""; # $title already quoted
968
-        }
969
-        $result .= $this->empty_element_suffix;
970
-
971
-        return $this->hashPart($result);
972
-    }
973
-
974
-    function doHeaders($text)
975
-    {
976
-        # Setext-style headers:
977
-        #	  Header 1
978
-        #	  ========
979
-        #
980
-        #	  Header 2
981
-        #	  --------
982
-        #
983
-        $text = preg_replace_callback(
984
-            '{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
985
-            array(&$this, '_doHeaders_callback_setext'),
986
-            $text
987
-        );
988
-
989
-        # atx-style headers:
990
-        #	# Header 1
991
-        #	## Header 2
992
-        #	## Header 2 with closing hashes ##
993
-        #	...
994
-        #	###### Header 6
995
-        #
996
-        $text = preg_replace_callback(
997
-            '{
919
+			array(&$this, '_doImages_inline_callback'),
920
+			$text
921
+		);
922
+
923
+		return $text;
924
+	}
925
+
926
+	function _doImages_reference_callback($matches)
927
+	{
928
+		$whole_match = $matches[1];
929
+		$alt_text    = $matches[2];
930
+		$link_id     = strtolower($matches[3]);
931
+
932
+		if ($link_id == "") {
933
+			$link_id = strtolower($alt_text); # for shortcut links like ![this][].
934
+		}
935
+
936
+		$alt_text = $this->encodeAttribute($alt_text);
937
+		if (isset($this->urls[$link_id])) {
938
+			$url    = $this->encodeAttribute($this->urls[$link_id]);
939
+			$result = "<img src=\"$url\" alt=\"$alt_text\"";
940
+			if (isset($this->titles[$link_id])) {
941
+				$title = $this->titles[$link_id];
942
+				$title = $this->encodeAttribute($title);
943
+				$result .= " title=\"$title\"";
944
+			}
945
+			$result .= $this->empty_element_suffix;
946
+			$result = $this->hashPart($result);
947
+		} else {
948
+			# If there's no such link ID, leave intact:
949
+			$result = $whole_match;
950
+		}
951
+
952
+		return $result;
953
+	}
954
+
955
+	function _doImages_inline_callback($matches)
956
+	{
957
+		$whole_match = $matches[1];
958
+		$alt_text    = $matches[2];
959
+		$url         = $matches[3] == '' ? $matches[4] : $matches[3];
960
+		$title       =& $matches[7];
961
+
962
+		$alt_text = $this->encodeAttribute($alt_text);
963
+		$url      = $this->encodeAttribute($url);
964
+		$result   = "<img src=\"$url\" alt=\"$alt_text\"";
965
+		if (isset($title)) {
966
+			$title = $this->encodeAttribute($title);
967
+			$result .= " title=\"$title\""; # $title already quoted
968
+		}
969
+		$result .= $this->empty_element_suffix;
970
+
971
+		return $this->hashPart($result);
972
+	}
973
+
974
+	function doHeaders($text)
975
+	{
976
+		# Setext-style headers:
977
+		#	  Header 1
978
+		#	  ========
979
+		#
980
+		#	  Header 2
981
+		#	  --------
982
+		#
983
+		$text = preg_replace_callback(
984
+			'{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
985
+			array(&$this, '_doHeaders_callback_setext'),
986
+			$text
987
+		);
988
+
989
+		# atx-style headers:
990
+		#	# Header 1
991
+		#	## Header 2
992
+		#	## Header 2 with closing hashes ##
993
+		#	...
994
+		#	###### Header 6
995
+		#
996
+		$text = preg_replace_callback(
997
+			'{
998 998
 				^(\#{1,6})	# $1 = string of #\'s
999 999
 				[ ]*
1000 1000
 				(.+?)		# $2 = Header text
@@ -1002,52 +1002,52 @@  discard block
 block discarded – undo
1002 1002
 				\#*			# optional closing #\'s (not counted)
1003 1003
 				\n+
1004 1004
 			}xm',
1005
-            array(&$this, '_doHeaders_callback_atx'),
1006
-            $text
1007
-        );
1008
-
1009
-        return $text;
1010
-    }
1011
-
1012
-    function _doHeaders_callback_setext($matches)
1013
-    {
1014
-        # Terrible hack to check we haven't found an empty list item.
1015
-        if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) {
1016
-            return $matches[0];
1017
-        }
1018
-
1019
-        $level = $matches[2]{0} == '=' ? 1 : 2;
1020
-        $block = "<h$level>" . $this->runSpanGamut($matches[1]) . "</h$level>";
1021
-        return "\n" . $this->hashBlock($block) . "\n\n";
1022
-    }
1023
-
1024
-    function _doHeaders_callback_atx($matches)
1025
-    {
1026
-        $level = strlen($matches[1]);
1027
-        $block = "<h$level>" . $this->runSpanGamut($matches[2]) . "</h$level>";
1028
-        return "\n" . $this->hashBlock($block) . "\n\n";
1029
-    }
1030
-
1031
-    function doLists($text)
1032
-    {
1033
-        #
1034
-        # Form HTML ordered (numbered) and unordered (bulleted) lists.
1035
-        #
1036
-        $less_than_tab = $this->tab_width - 1;
1037
-
1038
-        # Re-usable patterns to match list item bullets and number markers:
1039
-        $marker_ul_re  = '[*+-]';
1040
-        $marker_ol_re  = '\d+[\.]';
1041
-        $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
1042
-
1043
-        $markers_relist = array(
1044
-            $marker_ul_re => $marker_ol_re,
1045
-            $marker_ol_re => $marker_ul_re,
1046
-        );
1047
-
1048
-        foreach ($markers_relist as $marker_re => $other_marker_re) {
1049
-            # Re-usable pattern to match any entirel ul or ol list:
1050
-            $whole_list_re = '
1005
+			array(&$this, '_doHeaders_callback_atx'),
1006
+			$text
1007
+		);
1008
+
1009
+		return $text;
1010
+	}
1011
+
1012
+	function _doHeaders_callback_setext($matches)
1013
+	{
1014
+		# Terrible hack to check we haven't found an empty list item.
1015
+		if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) {
1016
+			return $matches[0];
1017
+		}
1018
+
1019
+		$level = $matches[2]{0} == '=' ? 1 : 2;
1020
+		$block = "<h$level>" . $this->runSpanGamut($matches[1]) . "</h$level>";
1021
+		return "\n" . $this->hashBlock($block) . "\n\n";
1022
+	}
1023
+
1024
+	function _doHeaders_callback_atx($matches)
1025
+	{
1026
+		$level = strlen($matches[1]);
1027
+		$block = "<h$level>" . $this->runSpanGamut($matches[2]) . "</h$level>";
1028
+		return "\n" . $this->hashBlock($block) . "\n\n";
1029
+	}
1030
+
1031
+	function doLists($text)
1032
+	{
1033
+		#
1034
+		# Form HTML ordered (numbered) and unordered (bulleted) lists.
1035
+		#
1036
+		$less_than_tab = $this->tab_width - 1;
1037
+
1038
+		# Re-usable patterns to match list item bullets and number markers:
1039
+		$marker_ul_re  = '[*+-]';
1040
+		$marker_ol_re  = '\d+[\.]';
1041
+		$marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
1042
+
1043
+		$markers_relist = array(
1044
+			$marker_ul_re => $marker_ol_re,
1045
+			$marker_ol_re => $marker_ul_re,
1046
+		);
1047
+
1048
+		foreach ($markers_relist as $marker_re => $other_marker_re) {
1049
+			# Re-usable pattern to match any entirel ul or ol list:
1050
+			$whole_list_re = '
1051 1051
 				(								# $1 = whole list
1052 1052
 				  (								# $2
1053 1053
 					([ ]{0,' . $less_than_tab . '})	# $3 = number of spaces
@@ -1074,88 +1074,88 @@  discard block
 block discarded – undo
1074 1074
 				)
1075 1075
 			'; // mx
1076 1076
 
1077
-            # We use a different prefix before nested lists than top-level lists.
1078
-            # See extended comment in _ProcessListItems().
1077
+			# We use a different prefix before nested lists than top-level lists.
1078
+			# See extended comment in _ProcessListItems().
1079 1079
 
1080
-            if ($this->list_level) {
1081
-                $text = preg_replace_callback(
1082
-                    '{
1080
+			if ($this->list_level) {
1081
+				$text = preg_replace_callback(
1082
+					'{
1083 1083
 						^
1084 1084
 						' . $whole_list_re . '
1085 1085
 					}mx',
1086
-                    array(&$this, '_doLists_callback'),
1087
-                    $text
1088
-                );
1089
-            } else {
1090
-                $text = preg_replace_callback(
1091
-                    '{
1086
+					array(&$this, '_doLists_callback'),
1087
+					$text
1088
+				);
1089
+			} else {
1090
+				$text = preg_replace_callback(
1091
+					'{
1092 1092
 						(?:(?<=\n)\n|\A\n?) # Must eat the newline
1093 1093
 						' . $whole_list_re . '
1094 1094
 					}mx',
1095
-                    array(&$this, '_doLists_callback'),
1096
-                    $text
1097
-                );
1098
-            }
1099
-        }
1100
-
1101
-        return $text;
1102
-    }
1103
-
1104
-    function _doLists_callback($matches)
1105
-    {
1106
-        # Re-usable patterns to match list item bullets and number markers:
1107
-        $marker_ul_re  = '[*+-]';
1108
-        $marker_ol_re  = '\d+[\.]';
1109
-        $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
1110
-
1111
-        $list      = $matches[1];
1112
-        $list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol";
1113
-
1114
-        $marker_any_re = ($list_type == "ul" ? $marker_ul_re : $marker_ol_re);
1115
-
1116
-        $list .= "\n";
1117
-        $result = $this->processListItems($list, $marker_any_re);
1118
-
1119
-        $result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
1120
-        return "\n" . $result . "\n\n";
1121
-    }
1122
-
1123
-    var $list_level = 0;
1124
-
1125
-    function processListItems($list_str, $marker_any_re)
1126
-    {
1127
-        #
1128
-        #	Process the contents of a single ordered or unordered list, splitting it
1129
-        #	into individual list items.
1130
-        #
1131
-        # The $this->list_level global keeps track of when we're inside a list.
1132
-        # Each time we enter a list, we increment it; when we leave a list,
1133
-        # we decrement. If it's zero, we're not in a list anymore.
1134
-        #
1135
-        # We do this because when we're not inside a list, we want to treat
1136
-        # something like this:
1137
-        #
1138
-        #		I recommend upgrading to version
1139
-        #		8. Oops, now this line is treated
1140
-        #		as a sub-list.
1141
-        #
1142
-        # As a single paragraph, despite the fact that the second line starts
1143
-        # with a digit-period-space sequence.
1144
-        #
1145
-        # Whereas when we're inside a list (or sub-list), that line will be
1146
-        # treated as the start of a sub-list. What a kludge, huh? This is
1147
-        # an aspect of Markdown's syntax that's hard to parse perfectly
1148
-        # without resorting to mind-reading. Perhaps the solution is to
1149
-        # change the syntax rules such that sub-lists must start with a
1150
-        # starting cardinal number; e.g. "1." or "a.".
1151
-
1152
-        $this->list_level ++;
1153
-
1154
-        # trim trailing blank lines:
1155
-        $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
1156
-
1157
-        $list_str = preg_replace_callback(
1158
-            '{
1095
+					array(&$this, '_doLists_callback'),
1096
+					$text
1097
+				);
1098
+			}
1099
+		}
1100
+
1101
+		return $text;
1102
+	}
1103
+
1104
+	function _doLists_callback($matches)
1105
+	{
1106
+		# Re-usable patterns to match list item bullets and number markers:
1107
+		$marker_ul_re  = '[*+-]';
1108
+		$marker_ol_re  = '\d+[\.]';
1109
+		$marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
1110
+
1111
+		$list      = $matches[1];
1112
+		$list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol";
1113
+
1114
+		$marker_any_re = ($list_type == "ul" ? $marker_ul_re : $marker_ol_re);
1115
+
1116
+		$list .= "\n";
1117
+		$result = $this->processListItems($list, $marker_any_re);
1118
+
1119
+		$result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
1120
+		return "\n" . $result . "\n\n";
1121
+	}
1122
+
1123
+	var $list_level = 0;
1124
+
1125
+	function processListItems($list_str, $marker_any_re)
1126
+	{
1127
+		#
1128
+		#	Process the contents of a single ordered or unordered list, splitting it
1129
+		#	into individual list items.
1130
+		#
1131
+		# The $this->list_level global keeps track of when we're inside a list.
1132
+		# Each time we enter a list, we increment it; when we leave a list,
1133
+		# we decrement. If it's zero, we're not in a list anymore.
1134
+		#
1135
+		# We do this because when we're not inside a list, we want to treat
1136
+		# something like this:
1137
+		#
1138
+		#		I recommend upgrading to version
1139
+		#		8. Oops, now this line is treated
1140
+		#		as a sub-list.
1141
+		#
1142
+		# As a single paragraph, despite the fact that the second line starts
1143
+		# with a digit-period-space sequence.
1144
+		#
1145
+		# Whereas when we're inside a list (or sub-list), that line will be
1146
+		# treated as the start of a sub-list. What a kludge, huh? This is
1147
+		# an aspect of Markdown's syntax that's hard to parse perfectly
1148
+		# without resorting to mind-reading. Perhaps the solution is to
1149
+		# change the syntax rules such that sub-lists must start with a
1150
+		# starting cardinal number; e.g. "1." or "a.".
1151
+
1152
+		$this->list_level ++;
1153
+
1154
+		# trim trailing blank lines:
1155
+		$list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
1156
+
1157
+		$list_str = preg_replace_callback(
1158
+			'{
1159 1159
 			(\n)?							# leading line = $1
1160 1160
 			(^[ ]*)							# leading whitespace = $2
1161 1161
 			(' . $marker_any_re . '				# list marker and space = $3
@@ -1165,45 +1165,45 @@  discard block
 block discarded – undo
1165 1165
 			(?:(\n+(?=\n))|\n)				# tailing blank line = $5
1166 1166
 			(?= \n* (\z | \2 (' . $marker_any_re . ') (?:[ ]+|(?=\n))))
1167 1167
 			}xm',
1168
-            array(&$this, '_processListItems_callback'),
1169
-            $list_str
1170
-        );
1171
-
1172
-        $this->list_level --;
1173
-        return $list_str;
1174
-    }
1175
-
1176
-    function _processListItems_callback($matches)
1177
-    {
1178
-        $item               = $matches[4];
1179
-        $leading_line       =& $matches[1];
1180
-        $leading_space      =& $matches[2];
1181
-        $marker_space       = $matches[3];
1182
-        $tailing_blank_line =& $matches[5];
1183
-
1184
-        if ($leading_line || $tailing_blank_line ||
1185
-            preg_match('/\n{2,}/', $item)
1186
-        ) {
1187
-            # Replace marker with the appropriate whitespace indentation
1188
-            $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
1189
-            $item = $this->runBlockGamut($this->outdent($item) . "\n");
1190
-        } else {
1191
-            # Recursion for sub-lists:
1192
-            $item = $this->doLists($this->outdent($item));
1193
-            $item = preg_replace('/\n+$/', '', $item);
1194
-            $item = $this->runSpanGamut($item);
1195
-        }
1196
-
1197
-        return "<li>" . $item . "</li>\n";
1198
-    }
1199
-
1200
-    function doCodeBlocks($text)
1201
-    {
1202
-        #
1203
-        #	Process Markdown `<pre><code>` blocks.
1204
-        #
1205
-        $text = preg_replace_callback(
1206
-            '{
1168
+			array(&$this, '_processListItems_callback'),
1169
+			$list_str
1170
+		);
1171
+
1172
+		$this->list_level --;
1173
+		return $list_str;
1174
+	}
1175
+
1176
+	function _processListItems_callback($matches)
1177
+	{
1178
+		$item               = $matches[4];
1179
+		$leading_line       =& $matches[1];
1180
+		$leading_space      =& $matches[2];
1181
+		$marker_space       = $matches[3];
1182
+		$tailing_blank_line =& $matches[5];
1183
+
1184
+		if ($leading_line || $tailing_blank_line ||
1185
+			preg_match('/\n{2,}/', $item)
1186
+		) {
1187
+			# Replace marker with the appropriate whitespace indentation
1188
+			$item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
1189
+			$item = $this->runBlockGamut($this->outdent($item) . "\n");
1190
+		} else {
1191
+			# Recursion for sub-lists:
1192
+			$item = $this->doLists($this->outdent($item));
1193
+			$item = preg_replace('/\n+$/', '', $item);
1194
+			$item = $this->runSpanGamut($item);
1195
+		}
1196
+
1197
+		return "<li>" . $item . "</li>\n";
1198
+	}
1199
+
1200
+	function doCodeBlocks($text)
1201
+	{
1202
+		#
1203
+		#	Process Markdown `<pre><code>` blocks.
1204
+		#
1205
+		$text = preg_replace_callback(
1206
+			'{
1207 1207
 				(?:\n\n|\A\n?)
1208 1208
 				(	            # $1 = the code block -- one or more lines, starting with a space/tab
1209 1209
 				  (?>
@@ -1213,206 +1213,206 @@  discard block
 block discarded – undo
1213 1213
 				)
1214 1214
 				((?=^[ ]{0,' . $this->tab_width . '}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
1215 1215
 			}xm',
1216
-            array(&$this, '_doCodeBlocks_callback'),
1217
-            $text
1218
-        );
1219
-
1220
-        return $text;
1221
-    }
1222
-
1223
-    function _doCodeBlocks_callback($matches)
1224
-    {
1225
-        $codeblock = $matches[1];
1226
-
1227
-        $codeblock = $this->outdent($codeblock);
1228
-        $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
1229
-
1230
-        # trim leading newlines and trailing newlines
1231
-        $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
1232
-
1233
-        $codeblock = "<pre><code>$codeblock\n</code></pre>";
1234
-        return "\n\n" . $this->hashBlock($codeblock) . "\n\n";
1235
-    }
1236
-
1237
-    function makeCodeSpan($code)
1238
-    {
1239
-        #
1240
-        # Create a code span markup for $code. Called from handleSpanToken.
1241
-        #
1242
-        $code = htmlspecialchars(trim($code), ENT_NOQUOTES);
1243
-        return $this->hashPart("<code>$code</code>");
1244
-    }
1245
-
1246
-    var $em_relist = array(
1247
-        ''  => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)',
1248
-        '*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
1249
-        '_' => '(?<=\S|^)(?<!_)_(?!_)',
1250
-    );
1251
-
1252
-    var $strong_relist = array(
1253
-        ''   => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)',
1254
-        '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
1255
-        '__' => '(?<=\S|^)(?<!_)__(?!_)',
1256
-    );
1257
-
1258
-    var $em_strong_relist = array(
1259
-        ''    => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)',
1260
-        '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
1261
-        '___' => '(?<=\S|^)(?<!_)___(?!_)',
1262
-    );
1263
-
1264
-    var $em_strong_prepared_relist;
1265
-
1266
-    function prepareItalicsAndBold()
1267
-    {
1268
-        #
1269
-        # Prepare regular expressions for searching emphasis tokens in any
1270
-        # context.
1271
-        #
1272
-        foreach ($this->em_relist as $em => $em_re) {
1273
-            foreach ($this->strong_relist as $strong => $strong_re) {
1274
-                # Construct list of allowed token expressions.
1275
-                $token_relist = array();
1276
-                if (isset($this->em_strong_relist["$em$strong"])) {
1277
-                    $token_relist[] = $this->em_strong_relist["$em$strong"];
1278
-                }
1279
-                $token_relist[] = $em_re;
1280
-                $token_relist[] = $strong_re;
1281
-
1282
-                # Construct master expression from list.
1283
-                $token_re                                      = '{(' . implode('|', $token_relist) . ')}';
1284
-                $this->em_strong_prepared_relist["$em$strong"] = $token_re;
1285
-            }
1286
-        }
1287
-    }
1288
-
1289
-    function doItalicsAndBold($text)
1290
-    {
1291
-        $token_stack  = array('');
1292
-        $text_stack   = array('');
1293
-        $em           = '';
1294
-        $strong       = '';
1295
-        $tree_char_em = false;
1296
-
1297
-        while (1) {
1298
-            #
1299
-            # Get prepared regular expression for seraching emphasis tokens
1300
-            # in current context.
1301
-            #
1302
-            $token_re = $this->em_strong_prepared_relist["$em$strong"];
1303
-
1304
-            #
1305
-            # Each loop iteration search for the next emphasis token.
1306
-            # Each token is then passed to handleSpanToken.
1307
-            #
1308
-            $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
1309
-            $text_stack[0] .= $parts[0];
1310
-            $token =& $parts[1];
1311
-            $text  =& $parts[2];
1312
-
1313
-            if (empty($token)) {
1314
-                # Reached end of text span: empty stack without emitting.
1315
-                # any more emphasis.
1316
-                while ($token_stack[0]) {
1317
-                    $text_stack[1] .= array_shift($token_stack);
1318
-                    $text_stack[0] .= array_shift($text_stack);
1319
-                }
1320
-                break;
1321
-            }
1322
-
1323
-            $token_len = strlen($token);
1324
-            if ($tree_char_em) {
1325
-                # Reached closing marker while inside a three-char emphasis.
1326
-                if ($token_len == 3) {
1327
-                    # Three-char closing marker, close em and strong.
1328
-                    array_shift($token_stack);
1329
-                    $span = array_shift($text_stack);
1330
-                    $span = $this->runSpanGamut($span);
1331
-                    $span = "<strong><em>$span</em></strong>";
1332
-                    $text_stack[0] .= $this->hashPart($span);
1333
-                    $em     = '';
1334
-                    $strong = '';
1335
-                } else {
1336
-                    # Other closing marker: close one em or strong and
1337
-                    # change current token state to match the other
1338
-                    $token_stack[0] = str_repeat($token{0}, 3 - $token_len);
1339
-                    $tag            = $token_len == 2 ? "strong" : "em";
1340
-                    $span           = $text_stack[0];
1341
-                    $span           = $this->runSpanGamut($span);
1342
-                    $span           = "<$tag>$span</$tag>";
1343
-                    $text_stack[0]  = $this->hashPart($span);
1344
-                    $$tag           = ''; # $$tag stands for $em or $strong
1345
-                }
1346
-                $tree_char_em = false;
1347
-            } else if ($token_len == 3) {
1348
-                if ($em) {
1349
-                    # Reached closing marker for both em and strong.
1350
-                    # Closing strong marker:
1351
-                    for ($i = 0; $i < 2; ++ $i) {
1352
-                        $shifted_token = array_shift($token_stack);
1353
-                        $tag           = strlen($shifted_token) == 2 ? "strong" : "em";
1354
-                        $span          = array_shift($text_stack);
1355
-                        $span          = $this->runSpanGamut($span);
1356
-                        $span          = "<$tag>$span</$tag>";
1357
-                        $text_stack[0] .= $this->hashPart($span);
1358
-                        $$tag = ''; # $$tag stands for $em or $strong
1359
-                    }
1360
-                } else {
1361
-                    # Reached opening three-char emphasis marker. Push on token
1362
-                    # stack; will be handled by the special condition above.
1363
-                    $em     = $token{0};
1364
-                    $strong = "$em$em";
1365
-                    array_unshift($token_stack, $token);
1366
-                    array_unshift($text_stack, '');
1367
-                    $tree_char_em = true;
1368
-                }
1369
-            } else if ($token_len == 2) {
1370
-                if ($strong) {
1371
-                    # Unwind any dangling emphasis marker:
1372
-                    if (strlen($token_stack[0]) == 1) {
1373
-                        $text_stack[1] .= array_shift($token_stack);
1374
-                        $text_stack[0] .= array_shift($text_stack);
1375
-                    }
1376
-                    # Closing strong marker:
1377
-                    array_shift($token_stack);
1378
-                    $span = array_shift($text_stack);
1379
-                    $span = $this->runSpanGamut($span);
1380
-                    $span = "<strong>$span</strong>";
1381
-                    $text_stack[0] .= $this->hashPart($span);
1382
-                    $strong = '';
1383
-                } else {
1384
-                    array_unshift($token_stack, $token);
1385
-                    array_unshift($text_stack, '');
1386
-                    $strong = $token;
1387
-                }
1388
-            } else {
1389
-                # Here $token_len == 1
1390
-                if ($em) {
1391
-                    if (strlen($token_stack[0]) == 1) {
1392
-                        # Closing emphasis marker:
1393
-                        array_shift($token_stack);
1394
-                        $span = array_shift($text_stack);
1395
-                        $span = $this->runSpanGamut($span);
1396
-                        $span = "<em>$span</em>";
1397
-                        $text_stack[0] .= $this->hashPart($span);
1398
-                        $em = '';
1399
-                    } else {
1400
-                        $text_stack[0] .= $token;
1401
-                    }
1402
-                } else {
1403
-                    array_unshift($token_stack, $token);
1404
-                    array_unshift($text_stack, '');
1405
-                    $em = $token;
1406
-                }
1407
-            }
1408
-        }
1409
-        return $text_stack[0];
1410
-    }
1411
-
1412
-    function doBlockQuotes($text)
1413
-    {
1414
-        $text = preg_replace_callback(
1415
-            '/
1216
+			array(&$this, '_doCodeBlocks_callback'),
1217
+			$text
1218
+		);
1219
+
1220
+		return $text;
1221
+	}
1222
+
1223
+	function _doCodeBlocks_callback($matches)
1224
+	{
1225
+		$codeblock = $matches[1];
1226
+
1227
+		$codeblock = $this->outdent($codeblock);
1228
+		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
1229
+
1230
+		# trim leading newlines and trailing newlines
1231
+		$codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
1232
+
1233
+		$codeblock = "<pre><code>$codeblock\n</code></pre>";
1234
+		return "\n\n" . $this->hashBlock($codeblock) . "\n\n";
1235
+	}
1236
+
1237
+	function makeCodeSpan($code)
1238
+	{
1239
+		#
1240
+		# Create a code span markup for $code. Called from handleSpanToken.
1241
+		#
1242
+		$code = htmlspecialchars(trim($code), ENT_NOQUOTES);
1243
+		return $this->hashPart("<code>$code</code>");
1244
+	}
1245
+
1246
+	var $em_relist = array(
1247
+		''  => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S|$)(?![\.,:;]\s)',
1248
+		'*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
1249
+		'_' => '(?<=\S|^)(?<!_)_(?!_)',
1250
+	);
1251
+
1252
+	var $strong_relist = array(
1253
+		''   => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![\.,:;]\s)',
1254
+		'**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
1255
+		'__' => '(?<=\S|^)(?<!_)__(?!_)',
1256
+	);
1257
+
1258
+	var $em_strong_relist = array(
1259
+		''    => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![\.,:;]\s)',
1260
+		'***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
1261
+		'___' => '(?<=\S|^)(?<!_)___(?!_)',
1262
+	);
1263
+
1264
+	var $em_strong_prepared_relist;
1265
+
1266
+	function prepareItalicsAndBold()
1267
+	{
1268
+		#
1269
+		# Prepare regular expressions for searching emphasis tokens in any
1270
+		# context.
1271
+		#
1272
+		foreach ($this->em_relist as $em => $em_re) {
1273
+			foreach ($this->strong_relist as $strong => $strong_re) {
1274
+				# Construct list of allowed token expressions.
1275
+				$token_relist = array();
1276
+				if (isset($this->em_strong_relist["$em$strong"])) {
1277
+					$token_relist[] = $this->em_strong_relist["$em$strong"];
1278
+				}
1279
+				$token_relist[] = $em_re;
1280
+				$token_relist[] = $strong_re;
1281
+
1282
+				# Construct master expression from list.
1283
+				$token_re                                      = '{(' . implode('|', $token_relist) . ')}';
1284
+				$this->em_strong_prepared_relist["$em$strong"] = $token_re;
1285
+			}
1286
+		}
1287
+	}
1288
+
1289
+	function doItalicsAndBold($text)
1290
+	{
1291
+		$token_stack  = array('');
1292
+		$text_stack   = array('');
1293
+		$em           = '';
1294
+		$strong       = '';
1295
+		$tree_char_em = false;
1296
+
1297
+		while (1) {
1298
+			#
1299
+			# Get prepared regular expression for seraching emphasis tokens
1300
+			# in current context.
1301
+			#
1302
+			$token_re = $this->em_strong_prepared_relist["$em$strong"];
1303
+
1304
+			#
1305
+			# Each loop iteration search for the next emphasis token.
1306
+			# Each token is then passed to handleSpanToken.
1307
+			#
1308
+			$parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
1309
+			$text_stack[0] .= $parts[0];
1310
+			$token =& $parts[1];
1311
+			$text  =& $parts[2];
1312
+
1313
+			if (empty($token)) {
1314
+				# Reached end of text span: empty stack without emitting.
1315
+				# any more emphasis.
1316
+				while ($token_stack[0]) {
1317
+					$text_stack[1] .= array_shift($token_stack);
1318
+					$text_stack[0] .= array_shift($text_stack);
1319
+				}
1320
+				break;
1321
+			}
1322
+
1323
+			$token_len = strlen($token);
1324
+			if ($tree_char_em) {
1325
+				# Reached closing marker while inside a three-char emphasis.
1326
+				if ($token_len == 3) {
1327
+					# Three-char closing marker, close em and strong.
1328
+					array_shift($token_stack);
1329
+					$span = array_shift($text_stack);
1330
+					$span = $this->runSpanGamut($span);
1331
+					$span = "<strong><em>$span</em></strong>";
1332
+					$text_stack[0] .= $this->hashPart($span);
1333
+					$em     = '';
1334
+					$strong = '';
1335
+				} else {
1336
+					# Other closing marker: close one em or strong and
1337
+					# change current token state to match the other
1338
+					$token_stack[0] = str_repeat($token{0}, 3 - $token_len);
1339
+					$tag            = $token_len == 2 ? "strong" : "em";
1340
+					$span           = $text_stack[0];
1341
+					$span           = $this->runSpanGamut($span);
1342
+					$span           = "<$tag>$span</$tag>";
1343
+					$text_stack[0]  = $this->hashPart($span);
1344
+					$$tag           = ''; # $$tag stands for $em or $strong
1345
+				}
1346
+				$tree_char_em = false;
1347
+			} else if ($token_len == 3) {
1348
+				if ($em) {
1349
+					# Reached closing marker for both em and strong.
1350
+					# Closing strong marker:
1351
+					for ($i = 0; $i < 2; ++ $i) {
1352
+						$shifted_token = array_shift($token_stack);
1353
+						$tag           = strlen($shifted_token) == 2 ? "strong" : "em";
1354
+						$span          = array_shift($text_stack);
1355
+						$span          = $this->runSpanGamut($span);
1356
+						$span          = "<$tag>$span</$tag>";
1357
+						$text_stack[0] .= $this->hashPart($span);
1358
+						$$tag = ''; # $$tag stands for $em or $strong
1359
+					}
1360
+				} else {
1361
+					# Reached opening three-char emphasis marker. Push on token
1362
+					# stack; will be handled by the special condition above.
1363
+					$em     = $token{0};
1364
+					$strong = "$em$em";
1365
+					array_unshift($token_stack, $token);
1366
+					array_unshift($text_stack, '');
1367
+					$tree_char_em = true;
1368
+				}
1369
+			} else if ($token_len == 2) {
1370
+				if ($strong) {
1371
+					# Unwind any dangling emphasis marker:
1372
+					if (strlen($token_stack[0]) == 1) {
1373
+						$text_stack[1] .= array_shift($token_stack);
1374
+						$text_stack[0] .= array_shift($text_stack);
1375
+					}
1376
+					# Closing strong marker:
1377
+					array_shift($token_stack);
1378
+					$span = array_shift($text_stack);
1379
+					$span = $this->runSpanGamut($span);
1380
+					$span = "<strong>$span</strong>";
1381
+					$text_stack[0] .= $this->hashPart($span);
1382
+					$strong = '';
1383
+				} else {
1384
+					array_unshift($token_stack, $token);
1385
+					array_unshift($text_stack, '');
1386
+					$strong = $token;
1387
+				}
1388
+			} else {
1389
+				# Here $token_len == 1
1390
+				if ($em) {
1391
+					if (strlen($token_stack[0]) == 1) {
1392
+						# Closing emphasis marker:
1393
+						array_shift($token_stack);
1394
+						$span = array_shift($text_stack);
1395
+						$span = $this->runSpanGamut($span);
1396
+						$span = "<em>$span</em>";
1397
+						$text_stack[0] .= $this->hashPart($span);
1398
+						$em = '';
1399
+					} else {
1400
+						$text_stack[0] .= $token;
1401
+					}
1402
+				} else {
1403
+					array_unshift($token_stack, $token);
1404
+					array_unshift($text_stack, '');
1405
+					$em = $token;
1406
+				}
1407
+			}
1408
+		}
1409
+		return $text_stack[0];
1410
+	}
1411
+
1412
+	function doBlockQuotes($text)
1413
+	{
1414
+		$text = preg_replace_callback(
1415
+			'/
1416 1416
 			  (								# Wrap whole match in $1
1417 1417
 				(?>
1418 1418
 				  ^[ ]*>[ ]?			# ">" at the start of a line
@@ -1422,66 +1422,66 @@  discard block
 block discarded – undo
1422 1422
 				)+
1423 1423
 			  )
1424 1424
 			/xm',
1425
-            array(&$this, '_doBlockQuotes_callback'),
1426
-            $text
1427
-        );
1428
-
1429
-        return $text;
1430
-    }
1431
-
1432
-    function _doBlockQuotes_callback($matches)
1433
-    {
1434
-        $bq = $matches[1];
1435
-        # trim one level of quoting - trim whitespace-only lines
1436
-        $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
1437
-        $bq = $this->runBlockGamut($bq); # recurse
1438
-
1439
-        $bq = preg_replace('/^/m', "  ", $bq);
1440
-        # These leading spaces cause problem with <pre> content,
1441
-        # so we need to fix that:
1442
-        $bq = preg_replace_callback(
1443
-            '{(\s*<pre>.+?</pre>)}sx',
1444
-            array(&$this, '_doBlockQuotes_callback2'),
1445
-            $bq
1446
-        );
1447
-
1448
-        return "\n" . $this->hashBlock("<blockquote>\n$bq\n</blockquote>") . "\n\n";
1449
-    }
1450
-
1451
-    function _doBlockQuotes_callback2($matches)
1452
-    {
1453
-        $pre = $matches[1];
1454
-        $pre = preg_replace('/^  /m', '', $pre);
1455
-        return $pre;
1456
-    }
1457
-
1458
-    function formParagraphs($text)
1459
-    {
1460
-        #
1461
-        #	Params:
1462
-        #		$text - string to process with html <p> tags
1463
-        #
1464
-        # Strip leading and trailing lines:
1465
-        $text = preg_replace('/\A\n+|\n+\z/', '', $text);
1466
-
1467
-        $grafs = preg_split('/\n{2,}/', $text, - 1, PREG_SPLIT_NO_EMPTY);
1468
-
1469
-        #
1470
-        # Wrap <p> tags and unhashify HTML blocks
1471
-        #
1472
-        foreach ($grafs as $key => $value) {
1473
-            if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
1474
-                # Is a paragraph.
1475
-                $value = $this->runSpanGamut($value);
1476
-                $value = preg_replace('/^([ ]*)/', "<p>", $value);
1477
-                $value .= "</p>";
1478
-                $grafs[$key] = $this->unhash($value);
1479
-            } else {
1480
-                # Is a block.
1481
-                # Modify elements of @grafs in-place...
1482
-                $graf  = $value;
1483
-                $block = $this->html_hashes[$graf];
1484
-                $graf  = $block;
1425
+			array(&$this, '_doBlockQuotes_callback'),
1426
+			$text
1427
+		);
1428
+
1429
+		return $text;
1430
+	}
1431
+
1432
+	function _doBlockQuotes_callback($matches)
1433
+	{
1434
+		$bq = $matches[1];
1435
+		# trim one level of quoting - trim whitespace-only lines
1436
+		$bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
1437
+		$bq = $this->runBlockGamut($bq); # recurse
1438
+
1439
+		$bq = preg_replace('/^/m', "  ", $bq);
1440
+		# These leading spaces cause problem with <pre> content,
1441
+		# so we need to fix that:
1442
+		$bq = preg_replace_callback(
1443
+			'{(\s*<pre>.+?</pre>)}sx',
1444
+			array(&$this, '_doBlockQuotes_callback2'),
1445
+			$bq
1446
+		);
1447
+
1448
+		return "\n" . $this->hashBlock("<blockquote>\n$bq\n</blockquote>") . "\n\n";
1449
+	}
1450
+
1451
+	function _doBlockQuotes_callback2($matches)
1452
+	{
1453
+		$pre = $matches[1];
1454
+		$pre = preg_replace('/^  /m', '', $pre);
1455
+		return $pre;
1456
+	}
1457
+
1458
+	function formParagraphs($text)
1459
+	{
1460
+		#
1461
+		#	Params:
1462
+		#		$text - string to process with html <p> tags
1463
+		#
1464
+		# Strip leading and trailing lines:
1465
+		$text = preg_replace('/\A\n+|\n+\z/', '', $text);
1466
+
1467
+		$grafs = preg_split('/\n{2,}/', $text, - 1, PREG_SPLIT_NO_EMPTY);
1468
+
1469
+		#
1470
+		# Wrap <p> tags and unhashify HTML blocks
1471
+		#
1472
+		foreach ($grafs as $key => $value) {
1473
+			if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
1474
+				# Is a paragraph.
1475
+				$value = $this->runSpanGamut($value);
1476
+				$value = preg_replace('/^([ ]*)/', "<p>", $value);
1477
+				$value .= "</p>";
1478
+				$grafs[$key] = $this->unhash($value);
1479
+			} else {
1480
+				# Is a block.
1481
+				# Modify elements of @grafs in-place...
1482
+				$graf  = $value;
1483
+				$block = $this->html_hashes[$graf];
1484
+				$graf  = $block;
1485 1485
 //				if (preg_match('{
1486 1486
 //					\A
1487 1487
 //					(							# $1 = <div> tag
@@ -1517,59 +1517,59 @@  discard block
 block discarded – undo
1517 1517
 //
1518 1518
 //					$graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1519 1519
 //				}
1520
-                $grafs[$key] = $graf;
1521
-            }
1522
-        }
1523
-
1524
-        return implode("\n\n", $grafs);
1525
-    }
1526
-
1527
-    function encodeAttribute($text)
1528
-    {
1529
-        #
1530
-        # Encode text for a double-quoted HTML attribute. This function
1531
-        # is *not* suitable for attributes enclosed in single quotes.
1532
-        #
1533
-        $text = $this->encodeAmpsAndAngles($text);
1534
-        $text = str_replace('"', '&quot;', $text);
1535
-        return $text;
1536
-    }
1537
-
1538
-    function encodeAmpsAndAngles($text)
1539
-    {
1540
-        #
1541
-        # Smart processing for ampersands and angle brackets that need to
1542
-        # be encoded. Valid character entities are left alone unless the
1543
-        # no-entities mode is set.
1544
-        #
1545
-        if ($this->no_entities) {
1546
-            $text = str_replace('&', '&amp;', $text);
1547
-        } else {
1548
-            # Ampersand-encoding based entirely on Nat Irons's Amputator
1549
-            # MT plugin: <http://bumppo.net/projects/amputator/>
1550
-            $text = preg_replace(
1551
-                '/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
1552
-                '&amp;',
1553
-                $text
1554
-            );;
1555
-        }
1556
-        # Encode remaining <'s
1557
-        $text = str_replace('<', '&lt;', $text);
1558
-
1559
-        return $text;
1560
-    }
1561
-
1562
-    function doAutoLinks($text)
1563
-    {
1564
-        $text = preg_replace_callback(
1565
-            '{<((https?|ftp|dict):[^\'">\s]+)>}i',
1566
-            array(&$this, '_doAutoLinks_url_callback'),
1567
-            $text
1568
-        );
1569
-
1570
-        # Email addresses: <[email protected]>
1571
-        $text = preg_replace_callback(
1572
-            '{
1520
+				$grafs[$key] = $graf;
1521
+			}
1522
+		}
1523
+
1524
+		return implode("\n\n", $grafs);
1525
+	}
1526
+
1527
+	function encodeAttribute($text)
1528
+	{
1529
+		#
1530
+		# Encode text for a double-quoted HTML attribute. This function
1531
+		# is *not* suitable for attributes enclosed in single quotes.
1532
+		#
1533
+		$text = $this->encodeAmpsAndAngles($text);
1534
+		$text = str_replace('"', '&quot;', $text);
1535
+		return $text;
1536
+	}
1537
+
1538
+	function encodeAmpsAndAngles($text)
1539
+	{
1540
+		#
1541
+		# Smart processing for ampersands and angle brackets that need to
1542
+		# be encoded. Valid character entities are left alone unless the
1543
+		# no-entities mode is set.
1544
+		#
1545
+		if ($this->no_entities) {
1546
+			$text = str_replace('&', '&amp;', $text);
1547
+		} else {
1548
+			# Ampersand-encoding based entirely on Nat Irons's Amputator
1549
+			# MT plugin: <http://bumppo.net/projects/amputator/>
1550
+			$text = preg_replace(
1551
+				'/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
1552
+				'&amp;',
1553
+				$text
1554
+			);;
1555
+		}
1556
+		# Encode remaining <'s
1557
+		$text = str_replace('<', '&lt;', $text);
1558
+
1559
+		return $text;
1560
+	}
1561
+
1562
+	function doAutoLinks($text)
1563
+	{
1564
+		$text = preg_replace_callback(
1565
+			'{<((https?|ftp|dict):[^\'">\s]+)>}i',
1566
+			array(&$this, '_doAutoLinks_url_callback'),
1567
+			$text
1568
+		);
1569
+
1570
+		# Email addresses: <[email protected]>
1571
+		$text = preg_replace_callback(
1572
+			'{
1573 1573
 			<
1574 1574
 			(?:mailto:)?
1575 1575
 			(
@@ -1587,81 +1587,81 @@  discard block
 block discarded – undo
1587 1587
 			)
1588 1588
 			>
1589 1589
 			}xi',
1590
-            array(&$this, '_doAutoLinks_email_callback'),
1591
-            $text
1592
-        );
1593
-
1594
-        return $text;
1595
-    }
1596
-
1597
-    function _doAutoLinks_url_callback($matches)
1598
-    {
1599
-        $url  = $this->encodeAttribute($matches[1]);
1600
-        $link = "<a href=\"$url\">$url</a>";
1601
-        return $this->hashPart($link);
1602
-    }
1603
-
1604
-    function _doAutoLinks_email_callback($matches)
1605
-    {
1606
-        $address = $matches[1];
1607
-        $link    = $this->encodeEmailAddress($address);
1608
-        return $this->hashPart($link);
1609
-    }
1610
-
1611
-    function encodeEmailAddress($addr)
1612
-    {
1613
-        #
1614
-        #	Input: an email address, e.g. "[email protected]"
1615
-        #
1616
-        #	Output: the email address as a mailto link, with each character
1617
-        #		of the address encoded as either a decimal or hex entity, in
1618
-        #		the hopes of foiling most address harvesting spam bots. E.g.:
1619
-        #
1620
-        #	  <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1621
-        #        &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
1622
-        #        &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
1623
-        #        &#101;&#46;&#x63;&#111;&#x6d;</a></p>
1624
-        #
1625
-        #	Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1626
-        #   With some optimizations by Milian Wolff.
1627
-        #
1628
-        $addr  = "mailto:" . $addr;
1629
-        $chars = preg_split('/(?<!^)(?!$)/', $addr);
1630
-        $seed  = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed.
1631
-
1632
-        foreach ($chars as $key => $char) {
1633
-            $ord = ord($char);
1634
-            # Ignore non-ascii chars.
1635
-            if ($ord < 128) {
1636
-                $r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
1637
-                # roughly 10% raw, 45% hex, 45% dec
1638
-                # '@' *must* be encoded. I insist.
1639
-                if ($r > 90 && $char != '@') /* do nothing */ {
1640
-                    ;
1641
-                } else if ($r < 45) {
1642
-                    $chars[$key] = '&#x' . dechex($ord) . ';';
1643
-                } else {
1644
-                    $chars[$key] = '&#' . $ord . ';';
1645
-                }
1646
-            }
1647
-        }
1648
-
1649
-        $addr = implode('', $chars);
1650
-        $text = implode('', array_slice($chars, 7)); # text without `mailto:`
1651
-        $addr = "<a href=\"$addr\">$text</a>";
1652
-
1653
-        return $addr;
1654
-    }
1655
-
1656
-    function parseSpan($str)
1657
-    {
1658
-        #
1659
-        # Take the string $str and parse it into tokens, hashing embeded HTML,
1660
-        # escaped characters and handling code spans.
1661
-        #
1662
-        $output = '';
1663
-
1664
-        $span_re = '{
1590
+			array(&$this, '_doAutoLinks_email_callback'),
1591
+			$text
1592
+		);
1593
+
1594
+		return $text;
1595
+	}
1596
+
1597
+	function _doAutoLinks_url_callback($matches)
1598
+	{
1599
+		$url  = $this->encodeAttribute($matches[1]);
1600
+		$link = "<a href=\"$url\">$url</a>";
1601
+		return $this->hashPart($link);
1602
+	}
1603
+
1604
+	function _doAutoLinks_email_callback($matches)
1605
+	{
1606
+		$address = $matches[1];
1607
+		$link    = $this->encodeEmailAddress($address);
1608
+		return $this->hashPart($link);
1609
+	}
1610
+
1611
+	function encodeEmailAddress($addr)
1612
+	{
1613
+		#
1614
+		#	Input: an email address, e.g. "[email protected]"
1615
+		#
1616
+		#	Output: the email address as a mailto link, with each character
1617
+		#		of the address encoded as either a decimal or hex entity, in
1618
+		#		the hopes of foiling most address harvesting spam bots. E.g.:
1619
+		#
1620
+		#	  <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1621
+		#        &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
1622
+		#        &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
1623
+		#        &#101;&#46;&#x63;&#111;&#x6d;</a></p>
1624
+		#
1625
+		#	Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1626
+		#   With some optimizations by Milian Wolff.
1627
+		#
1628
+		$addr  = "mailto:" . $addr;
1629
+		$chars = preg_split('/(?<!^)(?!$)/', $addr);
1630
+		$seed  = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed.
1631
+
1632
+		foreach ($chars as $key => $char) {
1633
+			$ord = ord($char);
1634
+			# Ignore non-ascii chars.
1635
+			if ($ord < 128) {
1636
+				$r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
1637
+				# roughly 10% raw, 45% hex, 45% dec
1638
+				# '@' *must* be encoded. I insist.
1639
+				if ($r > 90 && $char != '@') /* do nothing */ {
1640
+					;
1641
+				} else if ($r < 45) {
1642
+					$chars[$key] = '&#x' . dechex($ord) . ';';
1643
+				} else {
1644
+					$chars[$key] = '&#' . $ord . ';';
1645
+				}
1646
+			}
1647
+		}
1648
+
1649
+		$addr = implode('', $chars);
1650
+		$text = implode('', array_slice($chars, 7)); # text without `mailto:`
1651
+		$addr = "<a href=\"$addr\">$text</a>";
1652
+
1653
+		return $addr;
1654
+	}
1655
+
1656
+	function parseSpan($str)
1657
+	{
1658
+		#
1659
+		# Take the string $str and parse it into tokens, hashing embeded HTML,
1660
+		# escaped characters and handling code spans.
1661
+		#
1662
+		$output = '';
1663
+
1664
+		$span_re = '{
1665 1665
 				(
1666 1666
 					\\\\' . $this->escape_chars_re . '
1667 1667
 				|
@@ -1687,142 +1687,142 @@  discard block
 block discarded – undo
1687 1687
 				)
1688 1688
 				}xs';
1689 1689
 
1690
-        while (1) {
1691
-            #
1692
-            # Each loop iteration seach for either the next tag, the next
1693
-            # openning code span marker, or the next escaped character.
1694
-            # Each token is then passed to handleSpanToken.
1695
-            #
1696
-            $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
1697
-
1698
-            # Create token from text preceding tag.
1699
-            if ($parts[0] != "") {
1700
-                $output .= $parts[0];
1701
-            }
1702
-
1703
-            # Check if we reach the end.
1704
-            if (isset($parts[1])) {
1705
-                $output .= $this->handleSpanToken($parts[1], $parts[2]);
1706
-                $str = $parts[2];
1707
-            } else {
1708
-                break;
1709
-            }
1710
-        }
1711
-
1712
-        return $output;
1713
-    }
1714
-
1715
-    function handleSpanToken($token, &$str)
1716
-    {
1717
-        #
1718
-        # Handle $token provided by parseSpan by determining its nature and
1719
-        # returning the corresponding value that should replace it.
1720
-        #
1721
-        switch ($token{0}) {
1722
-            case "\\":
1723
-                return $this->hashPart("&#" . ord($token{1}) . ";");
1724
-            case "`":
1725
-                # Search for end marker in remaining text.
1726
-                if (preg_match(
1727
-                    '/^(.*?[^`])' . preg_quote($token) . '(?!`)(.*)$/sm',
1728
-                    $str,
1729
-                    $matches
1730
-                )) {
1731
-                    $str      = $matches[2];
1732
-                    $codespan = $this->makeCodeSpan($matches[1]);
1733
-                    return $this->hashPart($codespan);
1734
-                }
1735
-                return $token; // return as text since no ending marker found.
1736
-            default:
1737
-                return $this->hashPart($token);
1738
-        }
1739
-    }
1740
-
1741
-    function outdent($text)
1742
-    {
1743
-        #
1744
-        # Remove one level of line-leading tabs or spaces
1745
-        #
1746
-        return preg_replace('/^(\t|[ ]{1,' . $this->tab_width . '})/m', '', $text);
1747
-    }
1748
-
1749
-
1750
-    # String length function for detab. `_initDetab` will create a function to
1751
-    # hanlde UTF-8 if the default function does not exist.
1752
-    var $utf8_strlen = 'mb_strlen';
1753
-
1754
-    function detab($text)
1755
-    {
1756
-        #
1757
-        # Replace tabs with the appropriate amount of space.
1758
-        #
1759
-        # For each line we separate the line in blocks delemited by
1760
-        # tab characters. Then we reconstruct every line by adding the
1761
-        # appropriate number of space between each blocks.
1762
-
1763
-        $text = preg_replace_callback(
1764
-            '/^.*\t.*$/m',
1765
-            array(&$this, '_detab_callback'),
1766
-            $text
1767
-        );
1768
-
1769
-        return $text;
1770
-    }
1771
-
1772
-    function _detab_callback($matches)
1773
-    {
1774
-        $line   = $matches[0];
1775
-        $strlen = $this->utf8_strlen; # strlen function for UTF-8.
1776
-
1777
-        # Split in blocks.
1778
-        $blocks = explode("\t", $line);
1779
-        # Add each blocks to the line.
1780
-        $line = $blocks[0];
1781
-        unset($blocks[0]); # Do not add first block twice.
1782
-        foreach ($blocks as $block) {
1783
-            # Calculate amount of space, insert spaces, insert block.
1784
-            $amount = $this->tab_width -
1785
-                      $strlen($line, 'UTF-8') % $this->tab_width;
1786
-            $line .= str_repeat(" ", $amount) . $block;
1787
-        }
1788
-        return $line;
1789
-    }
1790
-
1791
-    function _initDetab()
1792
-    {
1793
-        #
1794
-        # Check for the availability of the function in the `utf8_strlen` property
1795
-        # (initially `mb_strlen`). If the function is not available, create a
1796
-        # function that will loosely count the number of UTF-8 characters with a
1797
-        # regular expression.
1798
-        #
1799
-        if (function_exists($this->utf8_strlen)) {
1800
-            return;
1801
-        }
1802
-        $this->utf8_strlen = create_function(
1803
-            '$text',
1804
-            'return preg_match_all(
1690
+		while (1) {
1691
+			#
1692
+			# Each loop iteration seach for either the next tag, the next
1693
+			# openning code span marker, or the next escaped character.
1694
+			# Each token is then passed to handleSpanToken.
1695
+			#
1696
+			$parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
1697
+
1698
+			# Create token from text preceding tag.
1699
+			if ($parts[0] != "") {
1700
+				$output .= $parts[0];
1701
+			}
1702
+
1703
+			# Check if we reach the end.
1704
+			if (isset($parts[1])) {
1705
+				$output .= $this->handleSpanToken($parts[1], $parts[2]);
1706
+				$str = $parts[2];
1707
+			} else {
1708
+				break;
1709
+			}
1710
+		}
1711
+
1712
+		return $output;
1713
+	}
1714
+
1715
+	function handleSpanToken($token, &$str)
1716
+	{
1717
+		#
1718
+		# Handle $token provided by parseSpan by determining its nature and
1719
+		# returning the corresponding value that should replace it.
1720
+		#
1721
+		switch ($token{0}) {
1722
+			case "\\":
1723
+				return $this->hashPart("&#" . ord($token{1}) . ";");
1724
+			case "`":
1725
+				# Search for end marker in remaining text.
1726
+				if (preg_match(
1727
+					'/^(.*?[^`])' . preg_quote($token) . '(?!`)(.*)$/sm',
1728
+					$str,
1729
+					$matches
1730
+				)) {
1731
+					$str      = $matches[2];
1732
+					$codespan = $this->makeCodeSpan($matches[1]);
1733
+					return $this->hashPart($codespan);
1734
+				}
1735
+				return $token; // return as text since no ending marker found.
1736
+			default:
1737
+				return $this->hashPart($token);
1738
+		}
1739
+	}
1740
+
1741
+	function outdent($text)
1742
+	{
1743
+		#
1744
+		# Remove one level of line-leading tabs or spaces
1745
+		#
1746
+		return preg_replace('/^(\t|[ ]{1,' . $this->tab_width . '})/m', '', $text);
1747
+	}
1748
+
1749
+
1750
+	# String length function for detab. `_initDetab` will create a function to
1751
+	# hanlde UTF-8 if the default function does not exist.
1752
+	var $utf8_strlen = 'mb_strlen';
1753
+
1754
+	function detab($text)
1755
+	{
1756
+		#
1757
+		# Replace tabs with the appropriate amount of space.
1758
+		#
1759
+		# For each line we separate the line in blocks delemited by
1760
+		# tab characters. Then we reconstruct every line by adding the
1761
+		# appropriate number of space between each blocks.
1762
+
1763
+		$text = preg_replace_callback(
1764
+			'/^.*\t.*$/m',
1765
+			array(&$this, '_detab_callback'),
1766
+			$text
1767
+		);
1768
+
1769
+		return $text;
1770
+	}
1771
+
1772
+	function _detab_callback($matches)
1773
+	{
1774
+		$line   = $matches[0];
1775
+		$strlen = $this->utf8_strlen; # strlen function for UTF-8.
1776
+
1777
+		# Split in blocks.
1778
+		$blocks = explode("\t", $line);
1779
+		# Add each blocks to the line.
1780
+		$line = $blocks[0];
1781
+		unset($blocks[0]); # Do not add first block twice.
1782
+		foreach ($blocks as $block) {
1783
+			# Calculate amount of space, insert spaces, insert block.
1784
+			$amount = $this->tab_width -
1785
+					  $strlen($line, 'UTF-8') % $this->tab_width;
1786
+			$line .= str_repeat(" ", $amount) . $block;
1787
+		}
1788
+		return $line;
1789
+	}
1790
+
1791
+	function _initDetab()
1792
+	{
1793
+		#
1794
+		# Check for the availability of the function in the `utf8_strlen` property
1795
+		# (initially `mb_strlen`). If the function is not available, create a
1796
+		# function that will loosely count the number of UTF-8 characters with a
1797
+		# regular expression.
1798
+		#
1799
+		if (function_exists($this->utf8_strlen)) {
1800
+			return;
1801
+		}
1802
+		$this->utf8_strlen = create_function(
1803
+			'$text',
1804
+			'return preg_match_all(
1805 1805
 			"/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/",
1806 1806
 			$text, $m);'
1807
-        );
1808
-    }
1809
-
1810
-    function unhash($text)
1811
-    {
1812
-        #
1813
-        # Swap back in all the tags hashed by _HashHTMLBlocks.
1814
-        #
1815
-        return preg_replace_callback(
1816
-            '/(.)\x1A[0-9]+\1/',
1817
-            array(&$this, '_unhash_callback'),
1818
-            $text
1819
-        );
1820
-    }
1821
-
1822
-    function _unhash_callback($matches)
1823
-    {
1824
-        return $this->html_hashes[$matches[0]];
1825
-    }
1807
+		);
1808
+	}
1809
+
1810
+	function unhash($text)
1811
+	{
1812
+		#
1813
+		# Swap back in all the tags hashed by _HashHTMLBlocks.
1814
+		#
1815
+		return preg_replace_callback(
1816
+			'/(.)\x1A[0-9]+\1/',
1817
+			array(&$this, '_unhash_callback'),
1818
+			$text
1819
+		);
1820
+	}
1821
+
1822
+	function _unhash_callback($matches)
1823
+	{
1824
+		return $this->html_hashes[$matches[0]];
1825
+	}
1826 1826
 }
1827 1827
 
1828 1828
 #
@@ -1832,178 +1832,178 @@  discard block
 block discarded – undo
1832 1832
 class MarkdownExtra_Parser extends Markdown_Parser
1833 1833
 {
1834 1834
 
1835
-    ### Configuration Variables ###
1836
-
1837
-    # Prefix for footnote ids.
1838
-    var $fn_id_prefix = "";
1839
-
1840
-    # Optional title attribute for footnote links and backlinks.
1841
-    var $fn_link_title = MARKDOWN_FN_LINK_TITLE;
1842
-
1843
-    var $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE;
1844
-
1845
-    # Optional class attribute for footnote links and backlinks.
1846
-    var $fn_link_class = MARKDOWN_FN_LINK_CLASS;
1847
-
1848
-    var $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS;
1849
-
1850
-    # Optional class prefix for fenced code block.
1851
-    var $code_class_prefix = MARKDOWN_CODE_CLASS_PREFIX;
1835
+	### Configuration Variables ###
1836
+
1837
+	# Prefix for footnote ids.
1838
+	var $fn_id_prefix = "";
1839
+
1840
+	# Optional title attribute for footnote links and backlinks.
1841
+	var $fn_link_title = MARKDOWN_FN_LINK_TITLE;
1842
+
1843
+	var $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE;
1844
+
1845
+	# Optional class attribute for footnote links and backlinks.
1846
+	var $fn_link_class = MARKDOWN_FN_LINK_CLASS;
1847
+
1848
+	var $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS;
1849
+
1850
+	# Optional class prefix for fenced code block.
1851
+	var $code_class_prefix = MARKDOWN_CODE_CLASS_PREFIX;
1852 1852
 
1853
-    # Class attribute for code blocks goes on the `code` tag;
1854
-    # setting this to true will put attributes on the `pre` tag instead.
1855
-    var $code_attr_on_pre = MARKDOWN_CODE_ATTR_ON_PRE;
1853
+	# Class attribute for code blocks goes on the `code` tag;
1854
+	# setting this to true will put attributes on the `pre` tag instead.
1855
+	var $code_attr_on_pre = MARKDOWN_CODE_ATTR_ON_PRE;
1856 1856
 
1857
-    # Predefined abbreviations.
1858
-    var $predef_abbr = array();
1859
-
1860
-    ### Parser Implementation ###
1861
-
1862
-    function __construct()
1863
-    {
1864
-        #
1865
-        # Constructor function. Initialize the parser object.
1866
-        #
1867
-        # Add extra escapable characters before parent constructor
1868
-        # initialize the table.
1869
-        $this->escape_chars .= ':|';
1870
-
1871
-        # Insert extra document, block, and span transformations.
1872
-        # Parent constructor will do the sorting.
1873
-        $this->document_gamut += array(
1874
-            "doFencedCodeBlocks" => 5,
1875
-            "stripFootnotes"     => 15,
1876
-            "stripAbbreviations" => 25,
1877
-            "appendFootnotes"    => 50,
1878
-        );
1879
-        $this->block_gamut += array(
1880
-            "doFencedCodeBlocks" => 5,
1881
-            "doTables"           => 15,
1882
-            "doDefLists"         => 45,
1883
-        );
1884
-        $this->span_gamut += array(
1885
-            "doFootnotes"     => 5,
1886
-            "doAbbreviations" => 70,
1887
-        );
1888
-
1889
-        parent::__construct();
1890
-    }
1891
-
1892
-    # Extra variables used during extra transformations.
1893
-    var $footnotes = array();
1894
-
1895
-    var $footnotes_ordered = array();
1896
-
1897
-    var $footnotes_ref_count = array();
1898
-
1899
-    var $footnotes_numbers = array();
1900
-
1901
-    var $abbr_desciptions = array();
1902
-
1903
-    var $abbr_word_re = '';
1904
-
1905
-    # Give the current footnote number.
1906
-    var $footnote_counter = 1;
1907
-
1908
-    function setup()
1909
-    {
1910
-        #
1911
-        # Setting up Extra-specific variables.
1912
-        #
1913
-        parent::setup();
1914
-
1915
-        $this->footnotes           = array();
1916
-        $this->footnotes_ordered   = array();
1917
-        $this->footnotes_ref_count = array();
1918
-        $this->footnotes_numbers   = array();
1919
-        $this->abbr_desciptions    = array();
1920
-        $this->abbr_word_re        = '';
1921
-        $this->footnote_counter    = 1;
1922
-
1923
-        foreach ($this->predef_abbr as $abbr_word => $abbr_desc) {
1924
-            if ($this->abbr_word_re) {
1925
-                $this->abbr_word_re .= '|';
1926
-            }
1927
-            $this->abbr_word_re .= preg_quote($abbr_word);
1928
-            $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
1929
-        }
1930
-    }
1931
-
1932
-    function teardown()
1933
-    {
1934
-        #
1935
-        # Clearing Extra-specific variables.
1936
-        #
1937
-        $this->footnotes           = array();
1938
-        $this->footnotes_ordered   = array();
1939
-        $this->footnotes_ref_count = array();
1940
-        $this->footnotes_numbers   = array();
1941
-        $this->abbr_desciptions    = array();
1942
-        $this->abbr_word_re        = '';
1943
-
1944
-        parent::teardown();
1945
-    }
1946
-
1947
-
1948
-    ### Extra Attribute Parser ###
1949
-
1950
-    # Expression to use to catch attributes (includes the braces)
1951
-    var $id_class_attr_catch_re = '\{((?:[ ]*[#.][-_:a-zA-Z0-9]+){1,})[ ]*\}';
1952
-
1953
-    # Expression to use when parsing in a context when no capture is desired
1954
-    var $id_class_attr_nocatch_re = '\{(?:[ ]*[#.][-_:a-zA-Z0-9]+){1,}[ ]*\}';
1955
-
1956
-    function doExtraAttributes($tag_name, $attr)
1957
-    {
1958
-        #
1959
-        # Parse attributes caught by the $this->id_class_attr_catch_re expression
1960
-        # and return the HTML-formatted list of attributes.
1961
-        #
1962
-        # Currently supported attributes are .class and #id.
1963
-        #
1964
-        if (empty($attr)) {
1965
-            return "";
1966
-        }
1967
-
1968
-        # Split on components
1969
-        preg_match_all('/[#.][-_:a-zA-Z0-9]+/', $attr, $matches);
1970
-        $elements = $matches[0];
1971
-
1972
-        # handle classes and ids (only first id taken into account)
1973
-        $classes = array();
1974
-        $id      = false;
1975
-        foreach ($elements as $element) {
1976
-            if ($element{0} == '.') {
1977
-                $classes[] = substr($element, 1);
1978
-            } else if ($element{0} == '#') {
1979
-                if ($id === false) {
1980
-                    $id = substr($element, 1);
1981
-                }
1982
-            }
1983
-        }
1984
-
1985
-        # compose attributes as string
1986
-        $attr_str = "";
1987
-        if (!empty($id)) {
1988
-            $attr_str .= ' id="' . $id . '"';
1989
-        }
1990
-        if (!empty($classes)) {
1991
-            $attr_str .= ' class="' . implode(" ", $classes) . '"';
1992
-        }
1993
-        return $attr_str;
1994
-    }
1995
-
1996
-    function stripLinkDefinitions($text)
1997
-    {
1998
-        #
1999
-        # Strips link definitions from text, stores the URLs and titles in
2000
-        # hash references.
2001
-        #
2002
-        $less_than_tab = $this->tab_width - 1;
2003
-
2004
-        # Link defs are in the form: ^[id]: url "optional title"
2005
-        $text = preg_replace_callback(
2006
-            '{
1857
+	# Predefined abbreviations.
1858
+	var $predef_abbr = array();
1859
+
1860
+	### Parser Implementation ###
1861
+
1862
+	function __construct()
1863
+	{
1864
+		#
1865
+		# Constructor function. Initialize the parser object.
1866
+		#
1867
+		# Add extra escapable characters before parent constructor
1868
+		# initialize the table.
1869
+		$this->escape_chars .= ':|';
1870
+
1871
+		# Insert extra document, block, and span transformations.
1872
+		# Parent constructor will do the sorting.
1873
+		$this->document_gamut += array(
1874
+			"doFencedCodeBlocks" => 5,
1875
+			"stripFootnotes"     => 15,
1876
+			"stripAbbreviations" => 25,
1877
+			"appendFootnotes"    => 50,
1878
+		);
1879
+		$this->block_gamut += array(
1880
+			"doFencedCodeBlocks" => 5,
1881
+			"doTables"           => 15,
1882
+			"doDefLists"         => 45,
1883
+		);
1884
+		$this->span_gamut += array(
1885
+			"doFootnotes"     => 5,
1886
+			"doAbbreviations" => 70,
1887
+		);
1888
+
1889
+		parent::__construct();
1890
+	}
1891
+
1892
+	# Extra variables used during extra transformations.
1893
+	var $footnotes = array();
1894
+
1895
+	var $footnotes_ordered = array();
1896
+
1897
+	var $footnotes_ref_count = array();
1898
+
1899
+	var $footnotes_numbers = array();
1900
+
1901
+	var $abbr_desciptions = array();
1902
+
1903
+	var $abbr_word_re = '';
1904
+
1905
+	# Give the current footnote number.
1906
+	var $footnote_counter = 1;
1907
+
1908
+	function setup()
1909
+	{
1910
+		#
1911
+		# Setting up Extra-specific variables.
1912
+		#
1913
+		parent::setup();
1914
+
1915
+		$this->footnotes           = array();
1916
+		$this->footnotes_ordered   = array();
1917
+		$this->footnotes_ref_count = array();
1918
+		$this->footnotes_numbers   = array();
1919
+		$this->abbr_desciptions    = array();
1920
+		$this->abbr_word_re        = '';
1921
+		$this->footnote_counter    = 1;
1922
+
1923
+		foreach ($this->predef_abbr as $abbr_word => $abbr_desc) {
1924
+			if ($this->abbr_word_re) {
1925
+				$this->abbr_word_re .= '|';
1926
+			}
1927
+			$this->abbr_word_re .= preg_quote($abbr_word);
1928
+			$this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
1929
+		}
1930
+	}
1931
+
1932
+	function teardown()
1933
+	{
1934
+		#
1935
+		# Clearing Extra-specific variables.
1936
+		#
1937
+		$this->footnotes           = array();
1938
+		$this->footnotes_ordered   = array();
1939
+		$this->footnotes_ref_count = array();
1940
+		$this->footnotes_numbers   = array();
1941
+		$this->abbr_desciptions    = array();
1942
+		$this->abbr_word_re        = '';
1943
+
1944
+		parent::teardown();
1945
+	}
1946
+
1947
+
1948
+	### Extra Attribute Parser ###
1949
+
1950
+	# Expression to use to catch attributes (includes the braces)
1951
+	var $id_class_attr_catch_re = '\{((?:[ ]*[#.][-_:a-zA-Z0-9]+){1,})[ ]*\}';
1952
+
1953
+	# Expression to use when parsing in a context when no capture is desired
1954
+	var $id_class_attr_nocatch_re = '\{(?:[ ]*[#.][-_:a-zA-Z0-9]+){1,}[ ]*\}';
1955
+
1956
+	function doExtraAttributes($tag_name, $attr)
1957
+	{
1958
+		#
1959
+		# Parse attributes caught by the $this->id_class_attr_catch_re expression
1960
+		# and return the HTML-formatted list of attributes.
1961
+		#
1962
+		# Currently supported attributes are .class and #id.
1963
+		#
1964
+		if (empty($attr)) {
1965
+			return "";
1966
+		}
1967
+
1968
+		# Split on components
1969
+		preg_match_all('/[#.][-_:a-zA-Z0-9]+/', $attr, $matches);
1970
+		$elements = $matches[0];
1971
+
1972
+		# handle classes and ids (only first id taken into account)
1973
+		$classes = array();
1974
+		$id      = false;
1975
+		foreach ($elements as $element) {
1976
+			if ($element{0} == '.') {
1977
+				$classes[] = substr($element, 1);
1978
+			} else if ($element{0} == '#') {
1979
+				if ($id === false) {
1980
+					$id = substr($element, 1);
1981
+				}
1982
+			}
1983
+		}
1984
+
1985
+		# compose attributes as string
1986
+		$attr_str = "";
1987
+		if (!empty($id)) {
1988
+			$attr_str .= ' id="' . $id . '"';
1989
+		}
1990
+		if (!empty($classes)) {
1991
+			$attr_str .= ' class="' . implode(" ", $classes) . '"';
1992
+		}
1993
+		return $attr_str;
1994
+	}
1995
+
1996
+	function stripLinkDefinitions($text)
1997
+	{
1998
+		#
1999
+		# Strips link definitions from text, stores the URLs and titles in
2000
+		# hash references.
2001
+		#
2002
+		$less_than_tab = $this->tab_width - 1;
2003
+
2004
+		# Link defs are in the form: ^[id]: url "optional title"
2005
+		$text = preg_replace_callback(
2006
+			'{
2007 2007
 							^[ ]{0,' . $less_than_tab . '}\[(.+)\][ ]?:	# id = $1
2008 2008
 							  [ ]*
2009 2009
 							  \n?				# maybe *one* newline
@@ -2026,117 +2026,117 @@  discard block
 block discarded – undo
2026 2026
 					(?:[ ]* ' . $this->id_class_attr_catch_re . ' )?  # $5 = extra id & class attr
2027 2027
 							(?:\n+|\Z)
2028 2028
 			}xm',
2029
-            array(&$this, '_stripLinkDefinitions_callback'),
2030
-            $text
2031
-        );
2032
-        return $text;
2033
-    }
2034
-
2035
-    function _stripLinkDefinitions_callback($matches)
2036
-    {
2037
-        $link_id                  = strtolower($matches[1]);
2038
-        $url                      = $matches[2] == '' ? $matches[3] : $matches[2];
2039
-        $this->urls[$link_id]     = $url;
2040
-        $this->titles[$link_id]   =& $matches[4];
2041
-        $this->ref_attr[$link_id] = $this->doExtraAttributes("", $dummy =& $matches[5]);
2042
-        return ''; # String that will replace the block
2043
-    }
2044
-
2045
-
2046
-    ### HTML Block Parser ###
2047
-
2048
-    # Tags that are always treated as block tags:
2049
-    var $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend|article|section|nav|aside|hgroup|header|footer|figcaption';
2050
-
2051
-    # Tags treated as block tags only if the opening tag is alone on its line:
2052
-    var $context_block_tags_re = 'script|noscript|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video';
2053
-
2054
-    # Tags where markdown="1" default to span mode:
2055
-    var $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
2056
-
2057
-    # Tags which must not have their contents modified, no matter where
2058
-    # they appear:
2059
-    var $clean_tags_re = 'script|math|svg';
2060
-
2061
-    # Tags that do not need to be closed.
2062
-    var $auto_close_tags_re = 'hr|img|param|source|track';
2063
-
2064
-    function hashHTMLBlocks($text)
2065
-    {
2066
-        #
2067
-        # Hashify HTML Blocks and "clean tags".
2068
-        #
2069
-        # We only want to do this for block-level HTML tags, such as headers,
2070
-        # lists, and tables. That's because we still want to wrap <p>s around
2071
-        # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
2072
-        # phrase emphasis, and spans. The list of tags we're looking for is
2073
-        # hard-coded.
2074
-        #
2075
-        # This works by calling _HashHTMLBlocks_InMarkdown, which then calls
2076
-        # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1"
2077
-        # attribute is found within a tag, _HashHTMLBlocks_InHTML calls back
2078
-        #  _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
2079
-        # These two functions are calling each other. It's recursive!
2080
-        #
2081
-        if ($this->no_markup) {
2082
-            return $text;
2083
-        }
2084
-
2085
-        #
2086
-        # Call the HTML-in-Markdown hasher.
2087
-        #
2088
-        list($text,) = $this->_hashHTMLBlocks_inMarkdown($text);
2089
-
2090
-        return $text;
2091
-    }
2092
-
2093
-    function _hashHTMLBlocks_inMarkdown(
2094
-        $text,
2095
-        $indent = 0,
2096
-        $enclosing_tag_re = '',
2097
-        $span = false
2098
-    ) {
2099
-        #
2100
-        # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
2101
-        #
2102
-        # *   $indent is the number of space to be ignored when checking for code
2103
-        #     blocks. This is important because if we don't take the indent into
2104
-        #     account, something like this (which looks right) won't work as expected:
2105
-        #
2106
-        #     <div>
2107
-        #         <div markdown="1">
2108
-        #         Hello World.  <-- Is this a Markdown code block or text?
2109
-        #         </div>  <-- Is this a Markdown code block or a real tag?
2110
-        #     <div>
2111
-        #
2112
-        #     If you don't like this, just don't indent the tag on which
2113
-        #     you apply the markdown="1" attribute.
2114
-        #
2115
-        # *   If $enclosing_tag_re is not empty, stops at the first unmatched closing
2116
-        #     tag with that name. Nested tags supported.
2117
-        #
2118
-        # *   If $span is true, text inside must treated as span. So any double
2119
-        #     newline will be replaced by a single newline so that it does not create
2120
-        #     paragraphs.
2121
-        #
2122
-        # Returns an array of that form: ( processed text , remaining text )
2123
-        #
2124
-        if ($text === '') {
2125
-            return array('', '');
2126
-        }
2127
-
2128
-        # Regex to check for the presense of newlines around a block tag.
2129
-        $newline_before_re = '/(?:^\n?|\n\n)*$/';
2130
-        $newline_after_re  =
2131
-            '{
2029
+			array(&$this, '_stripLinkDefinitions_callback'),
2030
+			$text
2031
+		);
2032
+		return $text;
2033
+	}
2034
+
2035
+	function _stripLinkDefinitions_callback($matches)
2036
+	{
2037
+		$link_id                  = strtolower($matches[1]);
2038
+		$url                      = $matches[2] == '' ? $matches[3] : $matches[2];
2039
+		$this->urls[$link_id]     = $url;
2040
+		$this->titles[$link_id]   =& $matches[4];
2041
+		$this->ref_attr[$link_id] = $this->doExtraAttributes("", $dummy =& $matches[5]);
2042
+		return ''; # String that will replace the block
2043
+	}
2044
+
2045
+
2046
+	### HTML Block Parser ###
2047
+
2048
+	# Tags that are always treated as block tags:
2049
+	var $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend|article|section|nav|aside|hgroup|header|footer|figcaption';
2050
+
2051
+	# Tags treated as block tags only if the opening tag is alone on its line:
2052
+	var $context_block_tags_re = 'script|noscript|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video';
2053
+
2054
+	# Tags where markdown="1" default to span mode:
2055
+	var $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
2056
+
2057
+	# Tags which must not have their contents modified, no matter where
2058
+	# they appear:
2059
+	var $clean_tags_re = 'script|math|svg';
2060
+
2061
+	# Tags that do not need to be closed.
2062
+	var $auto_close_tags_re = 'hr|img|param|source|track';
2063
+
2064
+	function hashHTMLBlocks($text)
2065
+	{
2066
+		#
2067
+		# Hashify HTML Blocks and "clean tags".
2068
+		#
2069
+		# We only want to do this for block-level HTML tags, such as headers,
2070
+		# lists, and tables. That's because we still want to wrap <p>s around
2071
+		# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
2072
+		# phrase emphasis, and spans. The list of tags we're looking for is
2073
+		# hard-coded.
2074
+		#
2075
+		# This works by calling _HashHTMLBlocks_InMarkdown, which then calls
2076
+		# _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1"
2077
+		# attribute is found within a tag, _HashHTMLBlocks_InHTML calls back
2078
+		#  _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
2079
+		# These two functions are calling each other. It's recursive!
2080
+		#
2081
+		if ($this->no_markup) {
2082
+			return $text;
2083
+		}
2084
+
2085
+		#
2086
+		# Call the HTML-in-Markdown hasher.
2087
+		#
2088
+		list($text,) = $this->_hashHTMLBlocks_inMarkdown($text);
2089
+
2090
+		return $text;
2091
+	}
2092
+
2093
+	function _hashHTMLBlocks_inMarkdown(
2094
+		$text,
2095
+		$indent = 0,
2096
+		$enclosing_tag_re = '',
2097
+		$span = false
2098
+	) {
2099
+		#
2100
+		# Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
2101
+		#
2102
+		# *   $indent is the number of space to be ignored when checking for code
2103
+		#     blocks. This is important because if we don't take the indent into
2104
+		#     account, something like this (which looks right) won't work as expected:
2105
+		#
2106
+		#     <div>
2107
+		#         <div markdown="1">
2108
+		#         Hello World.  <-- Is this a Markdown code block or text?
2109
+		#         </div>  <-- Is this a Markdown code block or a real tag?
2110
+		#     <div>
2111
+		#
2112
+		#     If you don't like this, just don't indent the tag on which
2113
+		#     you apply the markdown="1" attribute.
2114
+		#
2115
+		# *   If $enclosing_tag_re is not empty, stops at the first unmatched closing
2116
+		#     tag with that name. Nested tags supported.
2117
+		#
2118
+		# *   If $span is true, text inside must treated as span. So any double
2119
+		#     newline will be replaced by a single newline so that it does not create
2120
+		#     paragraphs.
2121
+		#
2122
+		# Returns an array of that form: ( processed text , remaining text )
2123
+		#
2124
+		if ($text === '') {
2125
+			return array('', '');
2126
+		}
2127
+
2128
+		# Regex to check for the presense of newlines around a block tag.
2129
+		$newline_before_re = '/(?:^\n?|\n\n)*$/';
2130
+		$newline_after_re  =
2131
+			'{
2132 2132
 				^						# Start of text following the tag.
2133 2133
 				(?>[ ]*<!--.*?-->)?		# Optional comment.
2134 2134
 				[ ]*\n					# Must be followed by newline.
2135 2135
 			}xs';
2136 2136
 
2137
-        # Regex to match any tag.
2138
-        $block_tag_re =
2139
-            '{
2137
+		# Regex to match any tag.
2138
+		$block_tag_re =
2139
+			'{
2140 2140
 				(					# $2: Capture whole tag.
2141 2141
 					</?					# Any opening or closing tag.
2142 2142
 						(?>				# Tag name.
@@ -2187,177 +2187,177 @@  discard block
 block discarded – undo
2187 2187
 				)
2188 2188
 			}xs';
2189 2189
 
2190
-        $depth  = 0; # Current depth inside the tag tree.
2191
-        $parsed = ""; # Parsed text that will be returned.
2192
-
2193
-        #
2194
-        # Loop through every tag until we find the closing tag of the parent
2195
-        # or loop until reaching the end of text if no parent tag specified.
2196
-        #
2197
-        do {
2198
-            #
2199
-            # Split the text using the first $tag_match pattern found.
2200
-            # Text before  pattern will be first in the array, text after
2201
-            # pattern will be at the end, and between will be any catches made
2202
-            # by the pattern.
2203
-            #
2204
-            $parts = preg_split(
2205
-                $block_tag_re,
2206
-                $text,
2207
-                2,
2208
-                PREG_SPLIT_DELIM_CAPTURE
2209
-            );
2210
-
2211
-            # If in Markdown span mode, add a empty-string span-level hash
2212
-            # after each newline to prevent triggering any block element.
2213
-            if ($span) {
2214
-                $void     = $this->hashPart("", ':');
2215
-                $newline  = "$void\n";
2216
-                $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
2217
-            }
2218
-
2219
-            $parsed .= $parts[0]; # Text before current tag.
2220
-
2221
-            # If end of $text has been reached. Stop loop.
2222
-            if (count($parts) < 3) {
2223
-                $text = "";
2224
-                break;
2225
-            }
2226
-
2227
-            $tag    = $parts[1]; # Tag to handle.
2228
-            $text   = $parts[2]; # Remaining text after current tag.
2229
-            $tag_re = preg_quote($tag); # For use in a regular expression.
2230
-
2231
-            #
2232
-            # Check for: Code span marker
2233
-            #
2234
-            if ($tag{0} == "`") {
2235
-                # Find corresponding end marker.
2236
-                $tag_re = preg_quote($tag);
2237
-                if (preg_match(
2238
-                    '{^(?>.+?|\n(?!\n))*?(?<!`)' . $tag_re . '(?!`)}',
2239
-                    $text,
2240
-                    $matches
2241
-                )) {
2242
-                    # End marker found: pass text unchanged until marker.
2243
-                    $parsed .= $tag . $matches[0];
2244
-                    $text = substr($text, strlen($matches[0]));
2245
-                } else {
2246
-                    # Unmatched marker: just skip it.
2247
-                    $parsed .= $tag;
2248
-                }
2249
-            }
2250
-            #
2251
-            # Check for: Fenced code block marker.
2252
-            #
2253
-            else if (preg_match('{^\n?([ ]{0,' . ($indent + 3) . '})(~+)}', $tag, $capture)) {
2254
-                # Fenced code block marker: find matching end marker.
2255
-                $fence_indent = strlen($capture[1]); # use captured indent in re
2256
-                $fence_re     = $capture[2]; # use captured fence in re
2257
-                if (preg_match(
2258
-                    '{^(?>.*\n)*?[ ]{' . ($fence_indent) . '}' . $fence_re . '[ ]*(?:\n|$)}',
2259
-                    $text,
2260
-                    $matches
2261
-                )) {
2262
-                    # End marker found: pass text unchanged until marker.
2263
-                    $parsed .= $tag . $matches[0];
2264
-                    $text = substr($text, strlen($matches[0]));
2265
-                } else {
2266
-                    # No end marker: just skip it.
2267
-                    $parsed .= $tag;
2268
-                }
2269
-            }
2270
-            #
2271
-            # Check for: Indented code block.
2272
-            #
2273
-            else if ($tag{0} == "\n" || $tag{0} == " ") {
2274
-                # Indented code block: pass it unchanged, will be handled
2275
-                # later.
2276
-                $parsed .= $tag;
2277
-            }
2278
-            #
2279
-            # Check for: Opening Block level tag or
2280
-            #            Opening Context Block tag (like ins and del)
2281
-            #               used as a block tag (tag is alone on it's line).
2282
-            #
2283
-            else if (preg_match('{^<(?:' . $this->block_tags_re . ')\b}', $tag) ||
2284
-                     (preg_match('{^<(?:' . $this->context_block_tags_re . ')\b}', $tag) &&
2285
-                      preg_match($newline_before_re, $parsed) &&
2286
-                      preg_match($newline_after_re, $text))
2287
-            ) {
2288
-                # Need to parse tag and following text using the HTML parser.
2289
-                list($block_text, $text) =
2290
-                    $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
2291
-
2292
-                # Make sure it stays outside of any paragraph by adding newlines.
2293
-                $parsed .= "\n\n$block_text\n\n";
2294
-            }
2295
-            #
2296
-            # Check for: Clean tag (like script, math)
2297
-            #            HTML Comments, processing instructions.
2298
-            #
2299
-            else if (preg_match('{^<(?:' . $this->clean_tags_re . ')\b}', $tag) ||
2300
-                     $tag{1} == '!' || $tag{1} == '?'
2301
-            ) {
2302
-                # Need to parse tag and following text using the HTML parser.
2303
-                # (don't check for markdown attribute)
2304
-                list($block_text, $text) =
2305
-                    $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
2306
-
2307
-                $parsed .= $block_text;
2308
-            }
2309
-            #
2310
-            # Check for: Tag with same name as enclosing tag.
2311
-            #
2312
-            else if ($enclosing_tag_re !== '' &&
2313
-                     # Same name as enclosing tag.
2314
-                     preg_match('{^</?(?:' . $enclosing_tag_re . ')\b}', $tag)
2315
-            ) {
2316
-                #
2317
-                # Increase/decrease nested tag count.
2318
-                #
2319
-                if ($tag{1} == '/') {
2320
-                    $depth --;
2321
-                } else if ($tag{strlen($tag) - 2} != '/') {
2322
-                    $depth ++;
2323
-                }
2324
-
2325
-                if ($depth < 0) {
2326
-                    #
2327
-                    # Going out of parent element. Clean up and break so we
2328
-                    # return to the calling function.
2329
-                    #
2330
-                    $text = $tag . $text;
2331
-                    break;
2332
-                }
2333
-
2334
-                $parsed .= $tag;
2335
-            } else {
2336
-                $parsed .= $tag;
2337
-            }
2338
-        } while ($depth >= 0);
2339
-
2340
-        return array($parsed, $text);
2341
-    }
2342
-
2343
-    function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr)
2344
-    {
2345
-        #
2346
-        # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
2347
-        #
2348
-        # *   Calls $hash_method to convert any blocks.
2349
-        # *   Stops when the first opening tag closes.
2350
-        # *   $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
2351
-        #     (it is not inside clean tags)
2352
-        #
2353
-        # Returns an array of that form: ( processed text , remaining text )
2354
-        #
2355
-        if ($text === '') {
2356
-            return array('', '');
2357
-        }
2358
-
2359
-        # Regex to match `markdown` attribute inside of a tag.
2360
-        $markdown_attr_re = '
2190
+		$depth  = 0; # Current depth inside the tag tree.
2191
+		$parsed = ""; # Parsed text that will be returned.
2192
+
2193
+		#
2194
+		# Loop through every tag until we find the closing tag of the parent
2195
+		# or loop until reaching the end of text if no parent tag specified.
2196
+		#
2197
+		do {
2198
+			#
2199
+			# Split the text using the first $tag_match pattern found.
2200
+			# Text before  pattern will be first in the array, text after
2201
+			# pattern will be at the end, and between will be any catches made
2202
+			# by the pattern.
2203
+			#
2204
+			$parts = preg_split(
2205
+				$block_tag_re,
2206
+				$text,
2207
+				2,
2208
+				PREG_SPLIT_DELIM_CAPTURE
2209
+			);
2210
+
2211
+			# If in Markdown span mode, add a empty-string span-level hash
2212
+			# after each newline to prevent triggering any block element.
2213
+			if ($span) {
2214
+				$void     = $this->hashPart("", ':');
2215
+				$newline  = "$void\n";
2216
+				$parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
2217
+			}
2218
+
2219
+			$parsed .= $parts[0]; # Text before current tag.
2220
+
2221
+			# If end of $text has been reached. Stop loop.
2222
+			if (count($parts) < 3) {
2223
+				$text = "";
2224
+				break;
2225
+			}
2226
+
2227
+			$tag    = $parts[1]; # Tag to handle.
2228
+			$text   = $parts[2]; # Remaining text after current tag.
2229
+			$tag_re = preg_quote($tag); # For use in a regular expression.
2230
+
2231
+			#
2232
+			# Check for: Code span marker
2233
+			#
2234
+			if ($tag{0} == "`") {
2235
+				# Find corresponding end marker.
2236
+				$tag_re = preg_quote($tag);
2237
+				if (preg_match(
2238
+					'{^(?>.+?|\n(?!\n))*?(?<!`)' . $tag_re . '(?!`)}',
2239
+					$text,
2240
+					$matches
2241
+				)) {
2242
+					# End marker found: pass text unchanged until marker.
2243
+					$parsed .= $tag . $matches[0];
2244
+					$text = substr($text, strlen($matches[0]));
2245
+				} else {
2246
+					# Unmatched marker: just skip it.
2247
+					$parsed .= $tag;
2248
+				}
2249
+			}
2250
+			#
2251
+			# Check for: Fenced code block marker.
2252
+			#
2253
+			else if (preg_match('{^\n?([ ]{0,' . ($indent + 3) . '})(~+)}', $tag, $capture)) {
2254
+				# Fenced code block marker: find matching end marker.
2255
+				$fence_indent = strlen($capture[1]); # use captured indent in re
2256
+				$fence_re     = $capture[2]; # use captured fence in re
2257
+				if (preg_match(
2258
+					'{^(?>.*\n)*?[ ]{' . ($fence_indent) . '}' . $fence_re . '[ ]*(?:\n|$)}',
2259
+					$text,
2260
+					$matches
2261
+				)) {
2262
+					# End marker found: pass text unchanged until marker.
2263
+					$parsed .= $tag . $matches[0];
2264
+					$text = substr($text, strlen($matches[0]));
2265
+				} else {
2266
+					# No end marker: just skip it.
2267
+					$parsed .= $tag;
2268
+				}
2269
+			}
2270
+			#
2271
+			# Check for: Indented code block.
2272
+			#
2273
+			else if ($tag{0} == "\n" || $tag{0} == " ") {
2274
+				# Indented code block: pass it unchanged, will be handled
2275
+				# later.
2276
+				$parsed .= $tag;
2277
+			}
2278
+			#
2279
+			# Check for: Opening Block level tag or
2280
+			#            Opening Context Block tag (like ins and del)
2281
+			#               used as a block tag (tag is alone on it's line).
2282
+			#
2283
+			else if (preg_match('{^<(?:' . $this->block_tags_re . ')\b}', $tag) ||
2284
+					 (preg_match('{^<(?:' . $this->context_block_tags_re . ')\b}', $tag) &&
2285
+					  preg_match($newline_before_re, $parsed) &&
2286
+					  preg_match($newline_after_re, $text))
2287
+			) {
2288
+				# Need to parse tag and following text using the HTML parser.
2289
+				list($block_text, $text) =
2290
+					$this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
2291
+
2292
+				# Make sure it stays outside of any paragraph by adding newlines.
2293
+				$parsed .= "\n\n$block_text\n\n";
2294
+			}
2295
+			#
2296
+			# Check for: Clean tag (like script, math)
2297
+			#            HTML Comments, processing instructions.
2298
+			#
2299
+			else if (preg_match('{^<(?:' . $this->clean_tags_re . ')\b}', $tag) ||
2300
+					 $tag{1} == '!' || $tag{1} == '?'
2301
+			) {
2302
+				# Need to parse tag and following text using the HTML parser.
2303
+				# (don't check for markdown attribute)
2304
+				list($block_text, $text) =
2305
+					$this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
2306
+
2307
+				$parsed .= $block_text;
2308
+			}
2309
+			#
2310
+			# Check for: Tag with same name as enclosing tag.
2311
+			#
2312
+			else if ($enclosing_tag_re !== '' &&
2313
+					 # Same name as enclosing tag.
2314
+					 preg_match('{^</?(?:' . $enclosing_tag_re . ')\b}', $tag)
2315
+			) {
2316
+				#
2317
+				# Increase/decrease nested tag count.
2318
+				#
2319
+				if ($tag{1} == '/') {
2320
+					$depth --;
2321
+				} else if ($tag{strlen($tag) - 2} != '/') {
2322
+					$depth ++;
2323
+				}
2324
+
2325
+				if ($depth < 0) {
2326
+					#
2327
+					# Going out of parent element. Clean up and break so we
2328
+					# return to the calling function.
2329
+					#
2330
+					$text = $tag . $text;
2331
+					break;
2332
+				}
2333
+
2334
+				$parsed .= $tag;
2335
+			} else {
2336
+				$parsed .= $tag;
2337
+			}
2338
+		} while ($depth >= 0);
2339
+
2340
+		return array($parsed, $text);
2341
+	}
2342
+
2343
+	function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr)
2344
+	{
2345
+		#
2346
+		# Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
2347
+		#
2348
+		# *   Calls $hash_method to convert any blocks.
2349
+		# *   Stops when the first opening tag closes.
2350
+		# *   $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
2351
+		#     (it is not inside clean tags)
2352
+		#
2353
+		# Returns an array of that form: ( processed text , remaining text )
2354
+		#
2355
+		if ($text === '') {
2356
+			return array('', '');
2357
+		}
2358
+
2359
+		# Regex to match `markdown` attribute inside of a tag.
2360
+		$markdown_attr_re = '
2361 2361
 			{
2362 2362
 				\s*			# Eat whitespace before the `markdown` attribute
2363 2363
 				markdown
@@ -2372,8 +2372,8 @@  discard block
 block discarded – undo
2372 2372
 				()				# $4: make $3 always defined (avoid warnings)
2373 2373
 			}xs';
2374 2374
 
2375
-        # Regex to match any tag.
2376
-        $tag_re = '{
2375
+		# Regex to match any tag.
2376
+		$tag_re = '{
2377 2377
 				(					# $2: Capture whole tag.
2378 2378
 					</?					# Any opening or closing tag.
2379 2379
 						[\w:$]+			# Tag name.
@@ -2395,169 +2395,169 @@  discard block
 block discarded – undo
2395 2395
 				)
2396 2396
 			}xs';
2397 2397
 
2398
-        $original_text = $text; # Save original text in case of faliure.
2399
-
2400
-        $depth      = 0; # Current depth inside the tag tree.
2401
-        $block_text = ""; # Temporary text holder for current text.
2402
-        $parsed     = ""; # Parsed text that will be returned.
2403
-
2404
-        #
2405
-        # Get the name of the starting tag.
2406
-        # (This pattern makes $base_tag_name_re safe without quoting.)
2407
-        #
2408
-        if (preg_match('/^<([\w:$]*)\b/', $text, $matches)) {
2409
-            $base_tag_name_re = $matches[1];
2410
-        }
2411
-
2412
-        #
2413
-        # Loop through every tag until we find the corresponding closing tag.
2414
-        #
2415
-        do {
2416
-            #
2417
-            # Split the text using the first $tag_match pattern found.
2418
-            # Text before  pattern will be first in the array, text after
2419
-            # pattern will be at the end, and between will be any catches made
2420
-            # by the pattern.
2421
-            #
2422
-            $parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
2423
-
2424
-            if (count($parts) < 3) {
2425
-                #
2426
-                # End of $text reached with unbalenced tag(s).
2427
-                # In that case, we return original text unchanged and pass the
2428
-                # first character as filtered to prevent an infinite loop in the
2429
-                # parent function.
2430
-                #
2431
-                return array($original_text{0}, substr($original_text, 1));
2432
-            }
2433
-
2434
-            $block_text .= $parts[0]; # Text before current tag.
2435
-            $tag  = $parts[1]; # Tag to handle.
2436
-            $text = $parts[2]; # Remaining text after current tag.
2437
-
2438
-            #
2439
-            # Check for: Auto-close tag (like <hr/>)
2440
-            #			 Comments and Processing Instructions.
2441
-            #
2442
-            if (preg_match('{^</?(?:' . $this->auto_close_tags_re . ')\b}', $tag) ||
2443
-                $tag{1} == '!' || $tag{1} == '?'
2444
-            ) {
2445
-                # Just add the tag to the block as if it was text.
2446
-                $block_text .= $tag;
2447
-            } else {
2448
-                #
2449
-                # Increase/decrease nested tag count. Only do so if
2450
-                # the tag's name match base tag's.
2451
-                #
2452
-                if (preg_match('{^</?' . $base_tag_name_re . '\b}', $tag)) {
2453
-                    if ($tag{1} == '/') {
2454
-                        $depth --;
2455
-                    } else if ($tag{strlen($tag) - 2} != '/') {
2456
-                        $depth ++;
2457
-                    }
2458
-                }
2459
-
2460
-                #
2461
-                # Check for `markdown="1"` attribute and handle it.
2462
-                #
2463
-                if ($md_attr &&
2464
-                    preg_match($markdown_attr_re, $tag, $attr_m) &&
2465
-                    preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3])
2466
-                ) {
2467
-                    # Remove `markdown` attribute from opening tag.
2468
-                    $tag = preg_replace($markdown_attr_re, '', $tag);
2469
-
2470
-                    # Check if text inside this tag must be parsed in span mode.
2471
-                    $this->mode = $attr_m[2] . $attr_m[3];
2472
-                    $span_mode  = $this->mode == 'span' || $this->mode != 'block' &&
2473
-                                                           preg_match(
2474
-                                                               '{^<(?:' . $this->contain_span_tags_re . ')\b}',
2475
-                                                               $tag
2476
-                                                           );
2477
-
2478
-                    # Calculate indent before tag.
2479
-                    if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {
2480
-                        $strlen = $this->utf8_strlen;
2481
-                        $indent = $strlen($matches[1], 'UTF-8');
2482
-                    } else {
2483
-                        $indent = 0;
2484
-                    }
2485
-
2486
-                    # End preceding block with this tag.
2487
-                    $block_text .= $tag;
2488
-                    $parsed .= $this->$hash_method($block_text);
2489
-
2490
-                    # Get enclosing tag name for the ParseMarkdown function.
2491
-                    # (This pattern makes $tag_name_re safe without quoting.)
2492
-                    preg_match('/^<([\w:$]*)\b/', $tag, $matches);
2493
-                    $tag_name_re = $matches[1];
2494
-
2495
-                    # Parse the content using the HTML-in-Markdown parser.
2496
-                    list ($block_text, $text)
2497
-                        = $this->_hashHTMLBlocks_inMarkdown(
2498
-                        $text,
2499
-                        $indent,
2500
-                        $tag_name_re,
2501
-                        $span_mode
2502
-                    );
2503
-
2504
-                    # Outdent markdown text.
2505
-                    if ($indent > 0) {
2506
-                        $block_text = preg_replace(
2507
-                            "/^[ ]{1,$indent}/m",
2508
-                            "",
2509
-                            $block_text
2510
-                        );
2511
-                    }
2512
-
2513
-                    # Append tag content to parsed text.
2514
-                    if (!$span_mode) {
2515
-                        $parsed .= "\n\n$block_text\n\n";
2516
-                    } else {
2517
-                        $parsed .= "$block_text";
2518
-                    }
2519
-
2520
-                    # Start over with a new block.
2521
-                    $block_text = "";
2522
-                } else {
2523
-                    $block_text .= $tag;
2524
-                }
2525
-            }
2526
-        } while ($depth > 0);
2527
-
2528
-        #
2529
-        # Hash last block text that wasn't processed inside the loop.
2530
-        #
2531
-        $parsed .= $this->$hash_method($block_text);
2532
-
2533
-        return array($parsed, $text);
2534
-    }
2535
-
2536
-    function hashClean($text)
2537
-    {
2538
-        #
2539
-        # Called whenever a tag must be hashed when a function inserts a "clean" tag
2540
-        # in $text, it passes through this function and is automaticaly escaped,
2541
-        # blocking invalid nested overlap.
2542
-        #
2543
-        return $this->hashPart($text, 'C');
2544
-    }
2545
-
2546
-    function doAnchors($text)
2547
-    {
2548
-        #
2549
-        # Turn Markdown link shortcuts into XHTML <a> tags.
2550
-        #
2551
-        if ($this->in_anchor) {
2552
-            return $text;
2553
-        }
2554
-        $this->in_anchor = true;
2555
-
2556
-        #
2557
-        # First, handle reference-style links: [link text] [id]
2558
-        #
2559
-        $text = preg_replace_callback(
2560
-            '{
2398
+		$original_text = $text; # Save original text in case of faliure.
2399
+
2400
+		$depth      = 0; # Current depth inside the tag tree.
2401
+		$block_text = ""; # Temporary text holder for current text.
2402
+		$parsed     = ""; # Parsed text that will be returned.
2403
+
2404
+		#
2405
+		# Get the name of the starting tag.
2406
+		# (This pattern makes $base_tag_name_re safe without quoting.)
2407
+		#
2408
+		if (preg_match('/^<([\w:$]*)\b/', $text, $matches)) {
2409
+			$base_tag_name_re = $matches[1];
2410
+		}
2411
+
2412
+		#
2413
+		# Loop through every tag until we find the corresponding closing tag.
2414
+		#
2415
+		do {
2416
+			#
2417
+			# Split the text using the first $tag_match pattern found.
2418
+			# Text before  pattern will be first in the array, text after
2419
+			# pattern will be at the end, and between will be any catches made
2420
+			# by the pattern.
2421
+			#
2422
+			$parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
2423
+
2424
+			if (count($parts) < 3) {
2425
+				#
2426
+				# End of $text reached with unbalenced tag(s).
2427
+				# In that case, we return original text unchanged and pass the
2428
+				# first character as filtered to prevent an infinite loop in the
2429
+				# parent function.
2430
+				#
2431
+				return array($original_text{0}, substr($original_text, 1));
2432
+			}
2433
+
2434
+			$block_text .= $parts[0]; # Text before current tag.
2435
+			$tag  = $parts[1]; # Tag to handle.
2436
+			$text = $parts[2]; # Remaining text after current tag.
2437
+
2438
+			#
2439
+			# Check for: Auto-close tag (like <hr/>)
2440
+			#			 Comments and Processing Instructions.
2441
+			#
2442
+			if (preg_match('{^</?(?:' . $this->auto_close_tags_re . ')\b}', $tag) ||
2443
+				$tag{1} == '!' || $tag{1} == '?'
2444
+			) {
2445
+				# Just add the tag to the block as if it was text.
2446
+				$block_text .= $tag;
2447
+			} else {
2448
+				#
2449
+				# Increase/decrease nested tag count. Only do so if
2450
+				# the tag's name match base tag's.
2451
+				#
2452
+				if (preg_match('{^</?' . $base_tag_name_re . '\b}', $tag)) {
2453
+					if ($tag{1} == '/') {
2454
+						$depth --;
2455
+					} else if ($tag{strlen($tag) - 2} != '/') {
2456
+						$depth ++;
2457
+					}
2458
+				}
2459
+
2460
+				#
2461
+				# Check for `markdown="1"` attribute and handle it.
2462
+				#
2463
+				if ($md_attr &&
2464
+					preg_match($markdown_attr_re, $tag, $attr_m) &&
2465
+					preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3])
2466
+				) {
2467
+					# Remove `markdown` attribute from opening tag.
2468
+					$tag = preg_replace($markdown_attr_re, '', $tag);
2469
+
2470
+					# Check if text inside this tag must be parsed in span mode.
2471
+					$this->mode = $attr_m[2] . $attr_m[3];
2472
+					$span_mode  = $this->mode == 'span' || $this->mode != 'block' &&
2473
+														   preg_match(
2474
+															   '{^<(?:' . $this->contain_span_tags_re . ')\b}',
2475
+															   $tag
2476
+														   );
2477
+
2478
+					# Calculate indent before tag.
2479
+					if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {
2480
+						$strlen = $this->utf8_strlen;
2481
+						$indent = $strlen($matches[1], 'UTF-8');
2482
+					} else {
2483
+						$indent = 0;
2484
+					}
2485
+
2486
+					# End preceding block with this tag.
2487
+					$block_text .= $tag;
2488
+					$parsed .= $this->$hash_method($block_text);
2489
+
2490
+					# Get enclosing tag name for the ParseMarkdown function.
2491
+					# (This pattern makes $tag_name_re safe without quoting.)
2492
+					preg_match('/^<([\w:$]*)\b/', $tag, $matches);
2493
+					$tag_name_re = $matches[1];
2494
+
2495
+					# Parse the content using the HTML-in-Markdown parser.
2496
+					list ($block_text, $text)
2497
+						= $this->_hashHTMLBlocks_inMarkdown(
2498
+						$text,
2499
+						$indent,
2500
+						$tag_name_re,
2501
+						$span_mode
2502
+					);
2503
+
2504
+					# Outdent markdown text.
2505
+					if ($indent > 0) {
2506
+						$block_text = preg_replace(
2507
+							"/^[ ]{1,$indent}/m",
2508
+							"",
2509
+							$block_text
2510
+						);
2511
+					}
2512
+
2513
+					# Append tag content to parsed text.
2514
+					if (!$span_mode) {
2515
+						$parsed .= "\n\n$block_text\n\n";
2516
+					} else {
2517
+						$parsed .= "$block_text";
2518
+					}
2519
+
2520
+					# Start over with a new block.
2521
+					$block_text = "";
2522
+				} else {
2523
+					$block_text .= $tag;
2524
+				}
2525
+			}
2526
+		} while ($depth > 0);
2527
+
2528
+		#
2529
+		# Hash last block text that wasn't processed inside the loop.
2530
+		#
2531
+		$parsed .= $this->$hash_method($block_text);
2532
+
2533
+		return array($parsed, $text);
2534
+	}
2535
+
2536
+	function hashClean($text)
2537
+	{
2538
+		#
2539
+		# Called whenever a tag must be hashed when a function inserts a "clean" tag
2540
+		# in $text, it passes through this function and is automaticaly escaped,
2541
+		# blocking invalid nested overlap.
2542
+		#
2543
+		return $this->hashPart($text, 'C');
2544
+	}
2545
+
2546
+	function doAnchors($text)
2547
+	{
2548
+		#
2549
+		# Turn Markdown link shortcuts into XHTML <a> tags.
2550
+		#
2551
+		if ($this->in_anchor) {
2552
+			return $text;
2553
+		}
2554
+		$this->in_anchor = true;
2555
+
2556
+		#
2557
+		# First, handle reference-style links: [link text] [id]
2558
+		#
2559
+		$text = preg_replace_callback(
2560
+			'{
2561 2561
 			(					# wrap whole match in $1
2562 2562
 			  \[
2563 2563
 				(' . $this->nested_brackets_re . ')	# link text = $2
@@ -2571,15 +2571,15 @@  discard block
 block discarded – undo
2571 2571
 			  \]
2572 2572
 			)
2573 2573
 			}xs',
2574
-            array(&$this, '_doAnchors_reference_callback'),
2575
-            $text
2576
-        );
2577
-
2578
-        #
2579
-        # Next, inline-style links: [link text](url "optional title")
2580
-        #
2581
-        $text = preg_replace_callback(
2582
-            '{
2574
+			array(&$this, '_doAnchors_reference_callback'),
2575
+			$text
2576
+		);
2577
+
2578
+		#
2579
+		# Next, inline-style links: [link text](url "optional title")
2580
+		#
2581
+		$text = preg_replace_callback(
2582
+			'{
2583 2583
 			(				# wrap whole match in $1
2584 2584
 			  \[
2585 2585
 				(' . $this->nested_brackets_re . ')	# link text = $2
@@ -2602,102 +2602,102 @@  discard block
 block discarded – undo
2602 2602
 			  (?:[ ]? ' . $this->id_class_attr_catch_re . ' )?	 # $8 = id/class attributes
2603 2603
 			)
2604 2604
 			}xs',
2605
-            array(&$this, '_doAnchors_inline_callback'),
2606
-            $text
2607
-        );
2608
-
2609
-        #
2610
-        # Last, handle reference-style shortcuts: [link text]
2611
-        # These must come last in case you've also got [link text][1]
2612
-        # or [link text](/foo)
2613
-        #
2614
-        $text = preg_replace_callback(
2615
-            '{
2605
+			array(&$this, '_doAnchors_inline_callback'),
2606
+			$text
2607
+		);
2608
+
2609
+		#
2610
+		# Last, handle reference-style shortcuts: [link text]
2611
+		# These must come last in case you've also got [link text][1]
2612
+		# or [link text](/foo)
2613
+		#
2614
+		$text = preg_replace_callback(
2615
+			'{
2616 2616
 			(					# wrap whole match in $1
2617 2617
 			  \[
2618 2618
 				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
2619 2619
 			  \]
2620 2620
 			)
2621 2621
 			}xs',
2622
-            array(&$this, '_doAnchors_reference_callback'),
2623
-            $text
2624
-        );
2625
-
2626
-        $this->in_anchor = false;
2627
-        return $text;
2628
-    }
2629
-
2630
-    function _doAnchors_reference_callback($matches)
2631
-    {
2632
-        $whole_match = $matches[1];
2633
-        $link_text   = $matches[2];
2634
-        $link_id     =& $matches[3];
2635
-
2636
-        if ($link_id == "") {
2637
-            # for shortcut links like [this][] or [this].
2638
-            $link_id = $link_text;
2639
-        }
2640
-
2641
-        # lower-case and turn embedded newlines into spaces
2642
-        $link_id = strtolower($link_id);
2643
-        $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
2644
-
2645
-        if (isset($this->urls[$link_id])) {
2646
-            $url = $this->urls[$link_id];
2647
-            $url = $this->encodeAttribute($url);
2648
-
2649
-            $result = "<a href=\"$url\"";
2650
-            if (isset($this->titles[$link_id])) {
2651
-                $title = $this->titles[$link_id];
2652
-                $title = $this->encodeAttribute($title);
2653
-                $result .= " title=\"$title\"";
2654
-            }
2655
-            if (isset($this->ref_attr[$link_id])) {
2656
-                $result .= $this->ref_attr[$link_id];
2657
-            }
2658
-
2659
-            $link_text = $this->runSpanGamut($link_text);
2660
-            $result .= ">$link_text</a>";
2661
-            $result = $this->hashPart($result);
2662
-        } else {
2663
-            $result = $whole_match;
2664
-        }
2665
-        return $result;
2666
-    }
2667
-
2668
-    function _doAnchors_inline_callback($matches)
2669
-    {
2670
-        $whole_match = $matches[1];
2671
-        $link_text   = $this->runSpanGamut($matches[2]);
2672
-        $url         = $matches[3] == '' ? $matches[4] : $matches[3];
2673
-        $title       =& $matches[7];
2674
-        $attr        = $this->doExtraAttributes("a", $dummy =& $matches[8]);
2675
-
2676
-        $url = $this->encodeAttribute($url);
2677
-
2678
-        $result = "<a href=\"$url\"";
2679
-        if (isset($title)) {
2680
-            $title = $this->encodeAttribute($title);
2681
-            $result .= " title=\"$title\"";
2682
-        }
2683
-        $result .= $attr;
2684
-
2685
-        $link_text = $this->runSpanGamut($link_text);
2686
-        $result .= ">$link_text</a>";
2687
-
2688
-        return $this->hashPart($result);
2689
-    }
2690
-
2691
-    function doImages($text)
2692
-    {
2693
-        #
2694
-        # Turn Markdown image shortcuts into <img> tags.
2695
-        #
2696
-        #
2697
-        # First, handle reference-style labeled images: ![alt text][id]
2698
-        #
2699
-        $text = preg_replace_callback(
2700
-            '{
2622
+			array(&$this, '_doAnchors_reference_callback'),
2623
+			$text
2624
+		);
2625
+
2626
+		$this->in_anchor = false;
2627
+		return $text;
2628
+	}
2629
+
2630
+	function _doAnchors_reference_callback($matches)
2631
+	{
2632
+		$whole_match = $matches[1];
2633
+		$link_text   = $matches[2];
2634
+		$link_id     =& $matches[3];
2635
+
2636
+		if ($link_id == "") {
2637
+			# for shortcut links like [this][] or [this].
2638
+			$link_id = $link_text;
2639
+		}
2640
+
2641
+		# lower-case and turn embedded newlines into spaces
2642
+		$link_id = strtolower($link_id);
2643
+		$link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
2644
+
2645
+		if (isset($this->urls[$link_id])) {
2646
+			$url = $this->urls[$link_id];
2647
+			$url = $this->encodeAttribute($url);
2648
+
2649
+			$result = "<a href=\"$url\"";
2650
+			if (isset($this->titles[$link_id])) {
2651
+				$title = $this->titles[$link_id];
2652
+				$title = $this->encodeAttribute($title);
2653
+				$result .= " title=\"$title\"";
2654
+			}
2655
+			if (isset($this->ref_attr[$link_id])) {
2656
+				$result .= $this->ref_attr[$link_id];
2657
+			}
2658
+
2659
+			$link_text = $this->runSpanGamut($link_text);
2660
+			$result .= ">$link_text</a>";
2661
+			$result = $this->hashPart($result);
2662
+		} else {
2663
+			$result = $whole_match;
2664
+		}
2665
+		return $result;
2666
+	}
2667
+
2668
+	function _doAnchors_inline_callback($matches)
2669
+	{
2670
+		$whole_match = $matches[1];
2671
+		$link_text   = $this->runSpanGamut($matches[2]);
2672
+		$url         = $matches[3] == '' ? $matches[4] : $matches[3];
2673
+		$title       =& $matches[7];
2674
+		$attr        = $this->doExtraAttributes("a", $dummy =& $matches[8]);
2675
+
2676
+		$url = $this->encodeAttribute($url);
2677
+
2678
+		$result = "<a href=\"$url\"";
2679
+		if (isset($title)) {
2680
+			$title = $this->encodeAttribute($title);
2681
+			$result .= " title=\"$title\"";
2682
+		}
2683
+		$result .= $attr;
2684
+
2685
+		$link_text = $this->runSpanGamut($link_text);
2686
+		$result .= ">$link_text</a>";
2687
+
2688
+		return $this->hashPart($result);
2689
+	}
2690
+
2691
+	function doImages($text)
2692
+	{
2693
+		#
2694
+		# Turn Markdown image shortcuts into <img> tags.
2695
+		#
2696
+		#
2697
+		# First, handle reference-style labeled images: ![alt text][id]
2698
+		#
2699
+		$text = preg_replace_callback(
2700
+			'{
2701 2701
 			(				# wrap whole match in $1
2702 2702
 			  !\[
2703 2703
 				(' . $this->nested_brackets_re . ')		# alt text = $2
@@ -2712,16 +2712,16 @@  discard block
 block discarded – undo
2712 2712
 
2713 2713
 			)
2714 2714
 			}xs',
2715
-            array(&$this, '_doImages_reference_callback'),
2716
-            $text
2717
-        );
2718
-
2719
-        #
2720
-        # Next, handle inline images:  ![alt text](url "optional title")
2721
-        # Don't forget: encode * and _
2722
-        #
2723
-        $text = preg_replace_callback(
2724
-            '{
2715
+			array(&$this, '_doImages_reference_callback'),
2716
+			$text
2717
+		);
2718
+
2719
+		#
2720
+		# Next, handle inline images:  ![alt text](url "optional title")
2721
+		# Don't forget: encode * and _
2722
+		#
2723
+		$text = preg_replace_callback(
2724
+			'{
2725 2725
 			(				# wrap whole match in $1
2726 2726
 			  !\[
2727 2727
 				(' . $this->nested_brackets_re . ')		# alt text = $2
@@ -2745,97 +2745,97 @@  discard block
 block discarded – undo
2745 2745
 			  (?:[ ]? ' . $this->id_class_attr_catch_re . ' )?	 # $8 = id/class attributes
2746 2746
 			)
2747 2747
 			}xs',
2748
-            array(&$this, '_doImages_inline_callback'),
2749
-            $text
2750
-        );
2751
-
2752
-        return $text;
2753
-    }
2754
-
2755
-    function _doImages_reference_callback($matches)
2756
-    {
2757
-        $whole_match = $matches[1];
2758
-        $alt_text    = $matches[2];
2759
-        $link_id     = strtolower($matches[3]);
2760
-
2761
-        if ($link_id == "") {
2762
-            $link_id = strtolower($alt_text); # for shortcut links like ![this][].
2763
-        }
2764
-
2765
-        $alt_text = $this->encodeAttribute($alt_text);
2766
-        if (isset($this->urls[$link_id])) {
2767
-            $url    = $this->encodeAttribute($this->urls[$link_id]);
2768
-            $result = "<img src=\"$url\" alt=\"$alt_text\"";
2769
-            if (isset($this->titles[$link_id])) {
2770
-                $title = $this->titles[$link_id];
2771
-                $title = $this->encodeAttribute($title);
2772
-                $result .= " title=\"$title\"";
2773
-            }
2774
-            if (isset($this->ref_attr[$link_id])) {
2775
-                $result .= $this->ref_attr[$link_id];
2776
-            }
2777
-            $result .= $this->empty_element_suffix;
2778
-            $result = $this->hashPart($result);
2779
-        } else {
2780
-            # If there's no such link ID, leave intact:
2781
-            $result = $whole_match;
2782
-        }
2783
-
2784
-        return $result;
2785
-    }
2786
-
2787
-    function _doImages_inline_callback($matches)
2788
-    {
2789
-        $whole_match = $matches[1];
2790
-        $alt_text    = $matches[2];
2791
-        $url         = $matches[3] == '' ? $matches[4] : $matches[3];
2792
-        $title       =& $matches[7];
2793
-        $attr        = $this->doExtraAttributes("img", $dummy =& $matches[8]);
2794
-
2795
-        $alt_text = $this->encodeAttribute($alt_text);
2796
-        $url      = $this->encodeAttribute($url);
2797
-        $result   = "<img src=\"$url\" alt=\"$alt_text\"";
2798
-        if (isset($title)) {
2799
-            $title = $this->encodeAttribute($title);
2800
-            $result .= " title=\"$title\""; # $title already quoted
2801
-        }
2802
-        $result .= $attr;
2803
-        $result .= $this->empty_element_suffix;
2804
-
2805
-        return $this->hashPart($result);
2806
-    }
2807
-
2808
-    function doHeaders($text)
2809
-    {
2810
-        #
2811
-        # Redefined to add id and class attribute support.
2812
-        #
2813
-        # Setext-style headers:
2814
-        #	  Header 1  {#header1}
2815
-        #	  ========
2816
-        #
2817
-        #	  Header 2  {#header2 .class1 .class2}
2818
-        #	  --------
2819
-        #
2820
-        $text = preg_replace_callback(
2821
-            '{
2748
+			array(&$this, '_doImages_inline_callback'),
2749
+			$text
2750
+		);
2751
+
2752
+		return $text;
2753
+	}
2754
+
2755
+	function _doImages_reference_callback($matches)
2756
+	{
2757
+		$whole_match = $matches[1];
2758
+		$alt_text    = $matches[2];
2759
+		$link_id     = strtolower($matches[3]);
2760
+
2761
+		if ($link_id == "") {
2762
+			$link_id = strtolower($alt_text); # for shortcut links like ![this][].
2763
+		}
2764
+
2765
+		$alt_text = $this->encodeAttribute($alt_text);
2766
+		if (isset($this->urls[$link_id])) {
2767
+			$url    = $this->encodeAttribute($this->urls[$link_id]);
2768
+			$result = "<img src=\"$url\" alt=\"$alt_text\"";
2769
+			if (isset($this->titles[$link_id])) {
2770
+				$title = $this->titles[$link_id];
2771
+				$title = $this->encodeAttribute($title);
2772
+				$result .= " title=\"$title\"";
2773
+			}
2774
+			if (isset($this->ref_attr[$link_id])) {
2775
+				$result .= $this->ref_attr[$link_id];
2776
+			}
2777
+			$result .= $this->empty_element_suffix;
2778
+			$result = $this->hashPart($result);
2779
+		} else {
2780
+			# If there's no such link ID, leave intact:
2781
+			$result = $whole_match;
2782
+		}
2783
+
2784
+		return $result;
2785
+	}
2786
+
2787
+	function _doImages_inline_callback($matches)
2788
+	{
2789
+		$whole_match = $matches[1];
2790
+		$alt_text    = $matches[2];
2791
+		$url         = $matches[3] == '' ? $matches[4] : $matches[3];
2792
+		$title       =& $matches[7];
2793
+		$attr        = $this->doExtraAttributes("img", $dummy =& $matches[8]);
2794
+
2795
+		$alt_text = $this->encodeAttribute($alt_text);
2796
+		$url      = $this->encodeAttribute($url);
2797
+		$result   = "<img src=\"$url\" alt=\"$alt_text\"";
2798
+		if (isset($title)) {
2799
+			$title = $this->encodeAttribute($title);
2800
+			$result .= " title=\"$title\""; # $title already quoted
2801
+		}
2802
+		$result .= $attr;
2803
+		$result .= $this->empty_element_suffix;
2804
+
2805
+		return $this->hashPart($result);
2806
+	}
2807
+
2808
+	function doHeaders($text)
2809
+	{
2810
+		#
2811
+		# Redefined to add id and class attribute support.
2812
+		#
2813
+		# Setext-style headers:
2814
+		#	  Header 1  {#header1}
2815
+		#	  ========
2816
+		#
2817
+		#	  Header 2  {#header2 .class1 .class2}
2818
+		#	  --------
2819
+		#
2820
+		$text = preg_replace_callback(
2821
+			'{
2822 2822
 				(^.+?)								# $1: Header text
2823 2823
 				(?:[ ]+ ' . $this->id_class_attr_catch_re . ' )?	 # $3 = id/class attributes
2824 2824
 				[ ]*\n(=+|-+)[ ]*\n+				# $3: Header footer
2825 2825
 			}mx',
2826
-            array(&$this, '_doHeaders_callback_setext'),
2827
-            $text
2828
-        );
2829
-
2830
-        # atx-style headers:
2831
-        #	# Header 1        {#header1}
2832
-        #	## Header 2       {#header2}
2833
-        #	## Header 2 with closing hashes ##  {#header3.class1.class2}
2834
-        #	...
2835
-        #	###### Header 6   {.class2}
2836
-        #
2837
-        $text = preg_replace_callback(
2838
-            '{
2826
+			array(&$this, '_doHeaders_callback_setext'),
2827
+			$text
2828
+		);
2829
+
2830
+		# atx-style headers:
2831
+		#	# Header 1        {#header1}
2832
+		#	## Header 2       {#header2}
2833
+		#	## Header 2 with closing hashes ##  {#header3.class1.class2}
2834
+		#	...
2835
+		#	###### Header 6   {.class2}
2836
+		#
2837
+		$text = preg_replace_callback(
2838
+			'{
2839 2839
 				^(\#{1,6})	# $1 = string of #\'s
2840 2840
 				[ ]*
2841 2841
 				(.+?)		# $2 = Header text
@@ -2845,48 +2845,48 @@  discard block
 block discarded – undo
2845 2845
 				[ ]*
2846 2846
 				\n+
2847 2847
 			}xm',
2848
-            array(&$this, '_doHeaders_callback_atx'),
2849
-            $text
2850
-        );
2851
-
2852
-        return $text;
2853
-    }
2854
-
2855
-    function _doHeaders_callback_setext($matches)
2856
-    {
2857
-        if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) {
2858
-            return $matches[0];
2859
-        }
2860
-        $level = $matches[3]{0} == '=' ? 1 : 2;
2861
-        $attr  = $this->doExtraAttributes("h$level", $dummy =& $matches[2]);
2862
-        $block = "<h$level$attr>" . $this->runSpanGamut($matches[1]) . "</h$level>";
2863
-        return "\n" . $this->hashBlock($block) . "\n\n";
2864
-    }
2865
-
2866
-    function _doHeaders_callback_atx($matches)
2867
-    {
2868
-        $level = strlen($matches[1]);
2869
-        $attr  = $this->doExtraAttributes("h$level", $dummy =& $matches[3]);
2870
-        $block = "<h$level$attr>" . $this->runSpanGamut($matches[2]) . "</h$level>";
2871
-        return "\n" . $this->hashBlock($block) . "\n\n";
2872
-    }
2873
-
2874
-    function doTables($text)
2875
-    {
2876
-        #
2877
-        # Form HTML tables.
2878
-        #
2879
-        $less_than_tab = $this->tab_width - 1;
2880
-        #
2881
-        # Find tables with leading pipe.
2882
-        #
2883
-        #	| Header 1 | Header 2
2884
-        #	| -------- | --------
2885
-        #	| Cell 1   | Cell 2
2886
-        #	| Cell 3   | Cell 4
2887
-        #
2888
-        $text = preg_replace_callback(
2889
-            '
2848
+			array(&$this, '_doHeaders_callback_atx'),
2849
+			$text
2850
+		);
2851
+
2852
+		return $text;
2853
+	}
2854
+
2855
+	function _doHeaders_callback_setext($matches)
2856
+	{
2857
+		if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) {
2858
+			return $matches[0];
2859
+		}
2860
+		$level = $matches[3]{0} == '=' ? 1 : 2;
2861
+		$attr  = $this->doExtraAttributes("h$level", $dummy =& $matches[2]);
2862
+		$block = "<h$level$attr>" . $this->runSpanGamut($matches[1]) . "</h$level>";
2863
+		return "\n" . $this->hashBlock($block) . "\n\n";
2864
+	}
2865
+
2866
+	function _doHeaders_callback_atx($matches)
2867
+	{
2868
+		$level = strlen($matches[1]);
2869
+		$attr  = $this->doExtraAttributes("h$level", $dummy =& $matches[3]);
2870
+		$block = "<h$level$attr>" . $this->runSpanGamut($matches[2]) . "</h$level>";
2871
+		return "\n" . $this->hashBlock($block) . "\n\n";
2872
+	}
2873
+
2874
+	function doTables($text)
2875
+	{
2876
+		#
2877
+		# Form HTML tables.
2878
+		#
2879
+		$less_than_tab = $this->tab_width - 1;
2880
+		#
2881
+		# Find tables with leading pipe.
2882
+		#
2883
+		#	| Header 1 | Header 2
2884
+		#	| -------- | --------
2885
+		#	| Cell 1   | Cell 2
2886
+		#	| Cell 3   | Cell 4
2887
+		#
2888
+		$text = preg_replace_callback(
2889
+			'
2890 2890
 			{
2891 2891
 				^							# Start of a line
2892 2892
 				[ ]{0,' . $less_than_tab . '}	# Allowed whitespace.
@@ -2904,20 +2904,20 @@  discard block
 block discarded – undo
2904 2904
 				)
2905 2905
 				(?=\n|\Z)					# Stop at final double newline.
2906 2906
 			}xm',
2907
-            array(&$this, '_doTable_leadingPipe_callback'),
2908
-            $text
2909
-        );
2910
-
2911
-        #
2912
-        # Find tables without leading pipe.
2913
-        #
2914
-        #	Header 1 | Header 2
2915
-        #	-------- | --------
2916
-        #	Cell 1   | Cell 2
2917
-        #	Cell 3   | Cell 4
2918
-        #
2919
-        $text = preg_replace_callback(
2920
-            '
2907
+			array(&$this, '_doTable_leadingPipe_callback'),
2908
+			$text
2909
+		);
2910
+
2911
+		#
2912
+		# Find tables without leading pipe.
2913
+		#
2914
+		#	Header 1 | Header 2
2915
+		#	-------- | --------
2916
+		#	Cell 1   | Cell 2
2917
+		#	Cell 3   | Cell 4
2918
+		#
2919
+		$text = preg_replace_callback(
2920
+			'
2921 2921
 			{
2922 2922
 				^							# Start of a line
2923 2923
 				[ ]{0,' . $less_than_tab . '}	# Allowed whitespace.
@@ -2933,101 +2933,101 @@  discard block
 block discarded – undo
2933 2933
 				)
2934 2934
 				(?=\n|\Z)					# Stop at final double newline.
2935 2935
 			}xm',
2936
-            array(&$this, '_DoTable_callback'),
2937
-            $text
2938
-        );
2939
-
2940
-        return $text;
2941
-    }
2942
-
2943
-    function _doTable_leadingPipe_callback($matches)
2944
-    {
2945
-        $head      = $matches[1];
2946
-        $underline = $matches[2];
2947
-        $content   = $matches[3];
2948
-
2949
-        # Remove leading pipe for each row.
2950
-        $content = preg_replace('/^ *[|]/m', '', $content);
2951
-
2952
-        return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
2953
-    }
2954
-
2955
-    function _doTable_callback($matches)
2956
-    {
2957
-        $head      = $matches[1];
2958
-        $underline = $matches[2];
2959
-        $content   = $matches[3];
2960
-
2961
-        # Remove any tailing pipes for each line.
2962
-        $head      = preg_replace('/[|] *$/m', '', $head);
2963
-        $underline = preg_replace('/[|] *$/m', '', $underline);
2964
-        $content   = preg_replace('/[|] *$/m', '', $content);
2965
-
2966
-        # Reading alignement from header underline.
2967
-        $separators = preg_split('/ *[|] */', $underline);
2968
-        foreach ($separators as $n => $s) {
2969
-            if (preg_match('/^ *-+: *$/', $s)) {
2970
-                $attr[$n] = ' align="right"';
2971
-            } else if (preg_match('/^ *:-+: *$/', $s)) {
2972
-                $attr[$n] = ' align="center"';
2973
-            } else if (preg_match('/^ *:-+ *$/', $s)) {
2974
-                $attr[$n] = ' align="left"';
2975
-            } else {
2976
-                $attr[$n] = '';
2977
-            }
2978
-        }
2979
-
2980
-        # Parsing span elements, including code spans, character escapes,
2981
-        # and inline HTML tags, so that pipes inside those gets ignored.
2982
-        $head      = $this->parseSpan($head);
2983
-        $headers   = preg_split('/ *[|] */', $head);
2984
-        $col_count = count($headers);
2985
-        $attr      = array_pad($attr, $col_count, '');
2986
-
2987
-        # Write column headers.
2988
-        $text = "<table>\n";
2989
-        $text .= "<thead>\n";
2990
-        $text .= "<tr>\n";
2991
-        foreach ($headers as $n => $header) {
2992
-            $text .= "  <th$attr[$n]>" . $this->runSpanGamut(trim($header)) . "</th>\n";
2993
-        }
2994
-        $text .= "</tr>\n";
2995
-        $text .= "</thead>\n";
2996
-
2997
-        # Split content by row.
2998
-        $rows = explode("\n", trim($content, "\n"));
2999
-
3000
-        $text .= "<tbody>\n";
3001
-        foreach ($rows as $row) {
3002
-            # Parsing span elements, including code spans, character escapes,
3003
-            # and inline HTML tags, so that pipes inside those gets ignored.
3004
-            $row = $this->parseSpan($row);
3005
-
3006
-            # Split row by cell.
3007
-            $row_cells = preg_split('/ *[|] */', $row, $col_count);
3008
-            $row_cells = array_pad($row_cells, $col_count, '');
3009
-
3010
-            $text .= "<tr>\n";
3011
-            foreach ($row_cells as $n => $cell) {
3012
-                $text .= "  <td$attr[$n]>" . $this->runSpanGamut(trim($cell)) . "</td>\n";
3013
-            }
3014
-            $text .= "</tr>\n";
3015
-        }
3016
-        $text .= "</tbody>\n";
3017
-        $text .= "</table>";
3018
-
3019
-        return $this->hashBlock($text) . "\n";
3020
-    }
3021
-
3022
-    function doDefLists($text)
3023
-    {
3024
-        #
3025
-        # Form HTML definition lists.
3026
-        #
3027
-        $less_than_tab = $this->tab_width - 1;
3028
-
3029
-        # Re-usable pattern to match any entire dl list:
3030
-        $whole_list_re = '(?>
2936
+			array(&$this, '_DoTable_callback'),
2937
+			$text
2938
+		);
2939
+
2940
+		return $text;
2941
+	}
2942
+
2943
+	function _doTable_leadingPipe_callback($matches)
2944
+	{
2945
+		$head      = $matches[1];
2946
+		$underline = $matches[2];
2947
+		$content   = $matches[3];
2948
+
2949
+		# Remove leading pipe for each row.
2950
+		$content = preg_replace('/^ *[|]/m', '', $content);
2951
+
2952
+		return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
2953
+	}
2954
+
2955
+	function _doTable_callback($matches)
2956
+	{
2957
+		$head      = $matches[1];
2958
+		$underline = $matches[2];
2959
+		$content   = $matches[3];
2960
+
2961
+		# Remove any tailing pipes for each line.
2962
+		$head      = preg_replace('/[|] *$/m', '', $head);
2963
+		$underline = preg_replace('/[|] *$/m', '', $underline);
2964
+		$content   = preg_replace('/[|] *$/m', '', $content);
2965
+
2966
+		# Reading alignement from header underline.
2967
+		$separators = preg_split('/ *[|] */', $underline);
2968
+		foreach ($separators as $n => $s) {
2969
+			if (preg_match('/^ *-+: *$/', $s)) {
2970
+				$attr[$n] = ' align="right"';
2971
+			} else if (preg_match('/^ *:-+: *$/', $s)) {
2972
+				$attr[$n] = ' align="center"';
2973
+			} else if (preg_match('/^ *:-+ *$/', $s)) {
2974
+				$attr[$n] = ' align="left"';
2975
+			} else {
2976
+				$attr[$n] = '';
2977
+			}
2978
+		}
2979
+
2980
+		# Parsing span elements, including code spans, character escapes,
2981
+		# and inline HTML tags, so that pipes inside those gets ignored.
2982
+		$head      = $this->parseSpan($head);
2983
+		$headers   = preg_split('/ *[|] */', $head);
2984
+		$col_count = count($headers);
2985
+		$attr      = array_pad($attr, $col_count, '');
2986
+
2987
+		# Write column headers.
2988
+		$text = "<table>\n";
2989
+		$text .= "<thead>\n";
2990
+		$text .= "<tr>\n";
2991
+		foreach ($headers as $n => $header) {
2992
+			$text .= "  <th$attr[$n]>" . $this->runSpanGamut(trim($header)) . "</th>\n";
2993
+		}
2994
+		$text .= "</tr>\n";
2995
+		$text .= "</thead>\n";
2996
+
2997
+		# Split content by row.
2998
+		$rows = explode("\n", trim($content, "\n"));
2999
+
3000
+		$text .= "<tbody>\n";
3001
+		foreach ($rows as $row) {
3002
+			# Parsing span elements, including code spans, character escapes,
3003
+			# and inline HTML tags, so that pipes inside those gets ignored.
3004
+			$row = $this->parseSpan($row);
3005
+
3006
+			# Split row by cell.
3007
+			$row_cells = preg_split('/ *[|] */', $row, $col_count);
3008
+			$row_cells = array_pad($row_cells, $col_count, '');
3009
+
3010
+			$text .= "<tr>\n";
3011
+			foreach ($row_cells as $n => $cell) {
3012
+				$text .= "  <td$attr[$n]>" . $this->runSpanGamut(trim($cell)) . "</td>\n";
3013
+			}
3014
+			$text .= "</tr>\n";
3015
+		}
3016
+		$text .= "</tbody>\n";
3017
+		$text .= "</table>";
3018
+
3019
+		return $this->hashBlock($text) . "\n";
3020
+	}
3021
+
3022
+	function doDefLists($text)
3023
+	{
3024
+		#
3025
+		# Form HTML definition lists.
3026
+		#
3027
+		$less_than_tab = $this->tab_width - 1;
3028
+
3029
+		# Re-usable pattern to match any entire dl list:
3030
+		$whole_list_re = '(?>
3031 3031
 			(								# $1 = whole list
3032 3032
 			  (								# $2
3033 3033
 				[ ]{0,' . $less_than_tab . '}
@@ -3054,44 +3054,44 @@  discard block
 block discarded – undo
3054 3054
 			)
3055 3055
 		)'; // mx
3056 3056
 
3057
-        $text = preg_replace_callback(
3058
-            '{
3057
+		$text = preg_replace_callback(
3058
+			'{
3059 3059
 				(?>\A\n?|(?<=\n\n))
3060 3060
 				' . $whole_list_re . '
3061 3061
 			}mx',
3062
-            array(&$this, '_doDefLists_callback'),
3063
-            $text
3064
-        );
3065
-
3066
-        return $text;
3067
-    }
3068
-
3069
-    function _doDefLists_callback($matches)
3070
-    {
3071
-        # Re-usable patterns to match list item bullets and number markers:
3072
-        $list = $matches[1];
3073
-
3074
-        # Turn double returns into triple returns, so that we can make a
3075
-        # paragraph for the last item in a list, if necessary:
3076
-        $result = trim($this->processDefListItems($list));
3077
-        $result = "<dl>\n" . $result . "\n</dl>";
3078
-        return $this->hashBlock($result) . "\n\n";
3079
-    }
3080
-
3081
-    function processDefListItems($list_str)
3082
-    {
3083
-        #
3084
-        #	Process the contents of a single definition list, splitting it
3085
-        #	into individual term and definition list items.
3086
-        #
3087
-        $less_than_tab = $this->tab_width - 1;
3088
-
3089
-        # trim trailing blank lines:
3090
-        $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
3091
-
3092
-        # Process definition terms.
3093
-        $list_str = preg_replace_callback(
3094
-            '{
3062
+			array(&$this, '_doDefLists_callback'),
3063
+			$text
3064
+		);
3065
+
3066
+		return $text;
3067
+	}
3068
+
3069
+	function _doDefLists_callback($matches)
3070
+	{
3071
+		# Re-usable patterns to match list item bullets and number markers:
3072
+		$list = $matches[1];
3073
+
3074
+		# Turn double returns into triple returns, so that we can make a
3075
+		# paragraph for the last item in a list, if necessary:
3076
+		$result = trim($this->processDefListItems($list));
3077
+		$result = "<dl>\n" . $result . "\n</dl>";
3078
+		return $this->hashBlock($result) . "\n\n";
3079
+	}
3080
+
3081
+	function processDefListItems($list_str)
3082
+	{
3083
+		#
3084
+		#	Process the contents of a single definition list, splitting it
3085
+		#	into individual term and definition list items.
3086
+		#
3087
+		$less_than_tab = $this->tab_width - 1;
3088
+
3089
+		# trim trailing blank lines:
3090
+		$list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
3091
+
3092
+		# Process definition terms.
3093
+		$list_str = preg_replace_callback(
3094
+			'{
3095 3095
 			(?>\A\n?|\n\n+)					# leading line
3096 3096
 			(								# definition terms = $1
3097 3097
 				[ ]{0,' . $less_than_tab . '}	# leading whitespace
@@ -3102,13 +3102,13 @@  discard block
 block discarded – undo
3102 3102
 			(?=\n?[ ]{0,3}:[ ])				# lookahead for following line feed
3103 3103
 											#   with a definition mark.
3104 3104
 			}xm',
3105
-            array(&$this, '_processDefListItems_callback_dt'),
3106
-            $list_str
3107
-        );
3105
+			array(&$this, '_processDefListItems_callback_dt'),
3106
+			$list_str
3107
+		);
3108 3108
 
3109
-        # Process actual definitions.
3110
-        $list_str = preg_replace_callback(
3111
-            '{
3109
+		# Process actual definitions.
3110
+		$list_str = preg_replace_callback(
3111
+			'{
3112 3112
 			\n(\n+)?						# leading line = $1
3113 3113
 			(								# marker space = $2
3114 3114
 				[ ]{0,' . $less_than_tab . '}	# whitespace before colon
@@ -3122,56 +3122,56 @@  discard block
 block discarded – undo
3122 3122
 				)
3123 3123
 			)
3124 3124
 			}xm',
3125
-            array(&$this, '_processDefListItems_callback_dd'),
3126
-            $list_str
3127
-        );
3128
-
3129
-        return $list_str;
3130
-    }
3131
-
3132
-    function _processDefListItems_callback_dt($matches)
3133
-    {
3134
-        $terms = explode("\n", trim($matches[1]));
3135
-        $text  = '';
3136
-        foreach ($terms as $term) {
3137
-            $term = $this->runSpanGamut(trim($term));
3138
-            $text .= "\n<dt>" . $term . "</dt>";
3139
-        }
3140
-        return $text . "\n";
3141
-    }
3142
-
3143
-    function _processDefListItems_callback_dd($matches)
3144
-    {
3145
-        $leading_line = $matches[1];
3146
-        $marker_space = $matches[2];
3147
-        $def          = $matches[3];
3148
-
3149
-        if ($leading_line || preg_match('/\n{2,}/', $def)) {
3150
-            # Replace marker with the appropriate whitespace indentation
3151
-            $def = str_repeat(' ', strlen($marker_space)) . $def;
3152
-            $def = $this->runBlockGamut($this->outdent($def . "\n\n"));
3153
-            $def = "\n" . $def . "\n";
3154
-        } else {
3155
-            $def = rtrim($def);
3156
-            $def = $this->runSpanGamut($this->outdent($def));
3157
-        }
3158
-
3159
-        return "\n<dd>" . $def . "</dd>\n";
3160
-    }
3161
-
3162
-    function doFencedCodeBlocks($text)
3163
-    {
3164
-        #
3165
-        # Adding the fenced code block syntax to regular Markdown:
3166
-        #
3167
-        # ~~~
3168
-        # Code block
3169
-        # ~~~
3170
-        #
3171
-        $less_than_tab = $this->tab_width;
3172
-
3173
-        $text = preg_replace_callback(
3174
-            '{
3125
+			array(&$this, '_processDefListItems_callback_dd'),
3126
+			$list_str
3127
+		);
3128
+
3129
+		return $list_str;
3130
+	}
3131
+
3132
+	function _processDefListItems_callback_dt($matches)
3133
+	{
3134
+		$terms = explode("\n", trim($matches[1]));
3135
+		$text  = '';
3136
+		foreach ($terms as $term) {
3137
+			$term = $this->runSpanGamut(trim($term));
3138
+			$text .= "\n<dt>" . $term . "</dt>";
3139
+		}
3140
+		return $text . "\n";
3141
+	}
3142
+
3143
+	function _processDefListItems_callback_dd($matches)
3144
+	{
3145
+		$leading_line = $matches[1];
3146
+		$marker_space = $matches[2];
3147
+		$def          = $matches[3];
3148
+
3149
+		if ($leading_line || preg_match('/\n{2,}/', $def)) {
3150
+			# Replace marker with the appropriate whitespace indentation
3151
+			$def = str_repeat(' ', strlen($marker_space)) . $def;
3152
+			$def = $this->runBlockGamut($this->outdent($def . "\n\n"));
3153
+			$def = "\n" . $def . "\n";
3154
+		} else {
3155
+			$def = rtrim($def);
3156
+			$def = $this->runSpanGamut($this->outdent($def));
3157
+		}
3158
+
3159
+		return "\n<dd>" . $def . "</dd>\n";
3160
+	}
3161
+
3162
+	function doFencedCodeBlocks($text)
3163
+	{
3164
+		#
3165
+		# Adding the fenced code block syntax to regular Markdown:
3166
+		#
3167
+		# ~~~
3168
+		# Code block
3169
+		# ~~~
3170
+		#
3171
+		$less_than_tab = $this->tab_width;
3172
+
3173
+		$text = preg_replace_callback(
3174
+			'{
3175 3175
 				(?:\n|\A)
3176 3176
 				# 1: Opening marker
3177 3177
 				(
@@ -3196,120 +3196,120 @@  discard block
 block discarded – undo
3196 3196
 				# Closing marker.
3197 3197
 				\1 [ ]* \n
3198 3198
 			}xm',
3199
-            array(&$this, '_doFencedCodeBlocks_callback'),
3200
-            $text
3201
-        );
3202
-
3203
-        return $text;
3204
-    }
3205
-
3206
-    function _doFencedCodeBlocks_callback($matches)
3207
-    {
3208
-        $classname =& $matches[2];
3209
-        $attrs     =& $matches[3];
3210
-        $codeblock = $matches[4];
3211
-        $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
3212
-        $codeblock = preg_replace_callback(
3213
-            '/^\n+/',
3214
-            array(&$this, '_doFencedCodeBlocks_newlines'),
3215
-            $codeblock
3216
-        );
3217
-
3218
-        if ($classname != "") {
3219
-            if ($classname{0} == '.') {
3220
-                $classname = substr($classname, 1);
3221
-            }
3222
-            $attr_str = ' class="' . $this->code_class_prefix . $classname . '"';
3223
-        } else {
3224
-            $attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs);
3225
-        }
3226
-        $pre_attr_str  = $this->code_attr_on_pre ? $attr_str : '';
3227
-        $code_attr_str = $this->code_attr_on_pre ? '' : $attr_str;
3228
-        $codeblock     = "<pre$pre_attr_str><code$code_attr_str>$codeblock</code></pre>";
3229
-
3230
-        return "\n\n" . $this->hashBlock($codeblock) . "\n\n";
3231
-    }
3232
-
3233
-    function _doFencedCodeBlocks_newlines($matches)
3234
-    {
3235
-        return str_repeat(
3236
-            "<br$this->empty_element_suffix",
3237
-            strlen($matches[0])
3238
-        );
3239
-    }
3240
-
3241
-
3242
-    #
3243
-    # Redefining emphasis markers so that emphasis by underscore does not
3244
-    # work in the middle of a word.
3245
-    #
3246
-    var $em_relist = array(
3247
-        ''  => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?=\S|$)(?![\.,:;]\s)',
3248
-        '*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
3249
-        '_' => '(?<=\S|^)(?<!_)_(?![a-zA-Z0-9_])',
3250
-    );
3251
-
3252
-    var $strong_relist = array(
3253
-        ''   => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?=\S|$)(?![\.,:;]\s)',
3254
-        '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
3255
-        '__' => '(?<=\S|^)(?<!_)__(?![a-zA-Z0-9_])',
3256
-    );
3257
-
3258
-    var $em_strong_relist = array(
3259
-        ''    => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?=\S|$)(?![\.,:;]\s)',
3260
-        '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
3261
-        '___' => '(?<=\S|^)(?<!_)___(?![a-zA-Z0-9_])',
3262
-    );
3263
-
3264
-    function formParagraphs($text)
3265
-    {
3266
-        #
3267
-        #	Params:
3268
-        #		$text - string to process with html <p> tags
3269
-        #
3270
-        # Strip leading and trailing lines:
3271
-        $text = preg_replace('/\A\n+|\n+\z/', '', $text);
3272
-
3273
-        $grafs = preg_split('/\n{2,}/', $text, - 1, PREG_SPLIT_NO_EMPTY);
3274
-
3275
-        #
3276
-        # Wrap <p> tags and unhashify HTML blocks
3277
-        #
3278
-        foreach ($grafs as $key => $value) {
3279
-            $value = trim($this->runSpanGamut($value));
3280
-
3281
-            # Check if this should be enclosed in a paragraph.
3282
-            # Clean tag hashes & block tag hashes are left alone.
3283
-            $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
3284
-
3285
-            if ($is_p) {
3286
-                $value = "<p>$value</p>";
3287
-            }
3288
-            $grafs[$key] = $value;
3289
-        }
3290
-
3291
-        # Join grafs in one text, then unhash HTML tags.
3292
-        $text = implode("\n\n", $grafs);
3293
-
3294
-        # Finish by removing any tag hashes still present in $text.
3295
-        $text = $this->unhash($text);
3296
-
3297
-        return $text;
3298
-    }
3299
-
3300
-    ### Footnotes
3301
-
3302
-    function stripFootnotes($text)
3303
-    {
3304
-        #
3305
-        # Strips link definitions from text, stores the URLs and titles in
3306
-        # hash references.
3307
-        #
3308
-        $less_than_tab = $this->tab_width - 1;
3309
-
3310
-        # Link defs are in the form: [^id]: url "optional title"
3311
-        $text = preg_replace_callback(
3312
-            '{
3199
+			array(&$this, '_doFencedCodeBlocks_callback'),
3200
+			$text
3201
+		);
3202
+
3203
+		return $text;
3204
+	}
3205
+
3206
+	function _doFencedCodeBlocks_callback($matches)
3207
+	{
3208
+		$classname =& $matches[2];
3209
+		$attrs     =& $matches[3];
3210
+		$codeblock = $matches[4];
3211
+		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
3212
+		$codeblock = preg_replace_callback(
3213
+			'/^\n+/',
3214
+			array(&$this, '_doFencedCodeBlocks_newlines'),
3215
+			$codeblock
3216
+		);
3217
+
3218
+		if ($classname != "") {
3219
+			if ($classname{0} == '.') {
3220
+				$classname = substr($classname, 1);
3221
+			}
3222
+			$attr_str = ' class="' . $this->code_class_prefix . $classname . '"';
3223
+		} else {
3224
+			$attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs);
3225
+		}
3226
+		$pre_attr_str  = $this->code_attr_on_pre ? $attr_str : '';
3227
+		$code_attr_str = $this->code_attr_on_pre ? '' : $attr_str;
3228
+		$codeblock     = "<pre$pre_attr_str><code$code_attr_str>$codeblock</code></pre>";
3229
+
3230
+		return "\n\n" . $this->hashBlock($codeblock) . "\n\n";
3231
+	}
3232
+
3233
+	function _doFencedCodeBlocks_newlines($matches)
3234
+	{
3235
+		return str_repeat(
3236
+			"<br$this->empty_element_suffix",
3237
+			strlen($matches[0])
3238
+		);
3239
+	}
3240
+
3241
+
3242
+	#
3243
+	# Redefining emphasis markers so that emphasis by underscore does not
3244
+	# work in the middle of a word.
3245
+	#
3246
+	var $em_relist = array(
3247
+		''  => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?=\S|$)(?![\.,:;]\s)',
3248
+		'*' => '(?<=\S|^)(?<!\*)\*(?!\*)',
3249
+		'_' => '(?<=\S|^)(?<!_)_(?![a-zA-Z0-9_])',
3250
+	);
3251
+
3252
+	var $strong_relist = array(
3253
+		''   => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?=\S|$)(?![\.,:;]\s)',
3254
+		'**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)',
3255
+		'__' => '(?<=\S|^)(?<!_)__(?![a-zA-Z0-9_])',
3256
+	);
3257
+
3258
+	var $em_strong_relist = array(
3259
+		''    => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?=\S|$)(?![\.,:;]\s)',
3260
+		'***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)',
3261
+		'___' => '(?<=\S|^)(?<!_)___(?![a-zA-Z0-9_])',
3262
+	);
3263
+
3264
+	function formParagraphs($text)
3265
+	{
3266
+		#
3267
+		#	Params:
3268
+		#		$text - string to process with html <p> tags
3269
+		#
3270
+		# Strip leading and trailing lines:
3271
+		$text = preg_replace('/\A\n+|\n+\z/', '', $text);
3272
+
3273
+		$grafs = preg_split('/\n{2,}/', $text, - 1, PREG_SPLIT_NO_EMPTY);
3274
+
3275
+		#
3276
+		# Wrap <p> tags and unhashify HTML blocks
3277
+		#
3278
+		foreach ($grafs as $key => $value) {
3279
+			$value = trim($this->runSpanGamut($value));
3280
+
3281
+			# Check if this should be enclosed in a paragraph.
3282
+			# Clean tag hashes & block tag hashes are left alone.
3283
+			$is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
3284
+
3285
+			if ($is_p) {
3286
+				$value = "<p>$value</p>";
3287
+			}
3288
+			$grafs[$key] = $value;
3289
+		}
3290
+
3291
+		# Join grafs in one text, then unhash HTML tags.
3292
+		$text = implode("\n\n", $grafs);
3293
+
3294
+		# Finish by removing any tag hashes still present in $text.
3295
+		$text = $this->unhash($text);
3296
+
3297
+		return $text;
3298
+	}
3299
+
3300
+	### Footnotes
3301
+
3302
+	function stripFootnotes($text)
3303
+	{
3304
+		#
3305
+		# Strips link definitions from text, stores the URLs and titles in
3306
+		# hash references.
3307
+		#
3308
+		$less_than_tab = $this->tab_width - 1;
3309
+
3310
+		# Link defs are in the form: [^id]: url "optional title"
3311
+		$text = preg_replace_callback(
3312
+			'{
3313 3313
 			^[ ]{0,' . $less_than_tab . '}\[\^(.+?)\][ ]?:	# note_id = $1
3314 3314
 			  [ ]*
3315 3315
 			  \n?					# maybe *one* newline
@@ -3324,215 +3324,215 @@  discard block
 block discarded – undo
3324 3324
 				)*
3325 3325
 			)
3326 3326
 			}xm',
3327
-            array(&$this, '_stripFootnotes_callback'),
3328
-            $text
3329
-        );
3330
-        return $text;
3331
-    }
3332
-
3333
-    function _stripFootnotes_callback($matches)
3334
-    {
3335
-        $note_id                   = $this->fn_id_prefix . $matches[1];
3336
-        $this->footnotes[$note_id] = $this->outdent($matches[2]);
3337
-        return ''; # String that will replace the block
3338
-    }
3339
-
3340
-    function doFootnotes($text)
3341
-    {
3342
-        #
3343
-        # Replace footnote references in $text [^id] with a special text-token
3344
-        # which will be replaced by the actual footnote marker in appendFootnotes.
3345
-        #
3346
-        if (!$this->in_anchor) {
3347
-            $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
3348
-        }
3349
-        return $text;
3350
-    }
3351
-
3352
-    function appendFootnotes($text)
3353
-    {
3354
-        #
3355
-        # Append footnote list to text.
3356
-        #
3357
-        $text = preg_replace_callback(
3358
-            '{F\x1Afn:(.*?)\x1A:}',
3359
-            array(&$this, '_appendFootnotes_callback'),
3360
-            $text
3361
-        );
3362
-
3363
-        if (!empty($this->footnotes_ordered)) {
3364
-            $text .= "\n\n";
3365
-            $text .= "<div class=\"footnotes\">\n";
3366
-            $text .= "<hr" . $this->empty_element_suffix . "\n";
3367
-            $text .= "<ol>\n\n";
3368
-
3369
-            $attr = " rev=\"footnote\"";
3370
-            if ($this->fn_backlink_class != "") {
3371
-                $class = $this->fn_backlink_class;
3372
-                $class = $this->encodeAttribute($class);
3373
-                $attr .= " class=\"$class\"";
3374
-            }
3375
-            if ($this->fn_backlink_title != "") {
3376
-                $title = $this->fn_backlink_title;
3377
-                $title = $this->encodeAttribute($title);
3378
-                $attr .= " title=\"$title\"";
3379
-            }
3380
-            $num = 0;
3381
-
3382
-            while (!empty($this->footnotes_ordered)) {
3383
-                $footnote = reset($this->footnotes_ordered);
3384
-                $note_id  = key($this->footnotes_ordered);
3385
-                unset($this->footnotes_ordered[$note_id]);
3386
-                $ref_count = $this->footnotes_ref_count[$note_id];
3387
-                unset($this->footnotes_ref_count[$note_id]);
3388
-                unset($this->footnotes[$note_id]);
3389
-
3390
-                $footnote .= "\n"; # Need to append newline before parsing.
3391
-                $footnote = $this->runBlockGamut("$footnote\n");
3392
-                $footnote = preg_replace_callback(
3393
-                    '{F\x1Afn:(.*?)\x1A:}',
3394
-                    array(&$this, '_appendFootnotes_callback'),
3395
-                    $footnote
3396
-                );
3397
-
3398
-                $attr    = str_replace("%%", ++ $num, $attr);
3399
-                $note_id = $this->encodeAttribute($note_id);
3400
-
3401
-                # Prepare backlink, multiple backlinks if multiple references
3402
-                $backlink = "<a href=\"#fnref:$note_id\"$attr>&#8617;</a>";
3403
-                for ($ref_num = 2; $ref_num <= $ref_count; ++ $ref_num) {
3404
-                    $backlink .= " <a href=\"#fnref$ref_num:$note_id\"$attr>&#8617;</a>";
3405
-                }
3406
-                # Add backlink to last paragraph; create new paragraph if needed.
3407
-                if (preg_match('{</p>$}', $footnote)) {
3408
-                    $footnote = substr($footnote, 0, - 4) . "&#160;$backlink</p>";
3409
-                } else {
3410
-                    $footnote .= "\n\n<p>$backlink</p>";
3411
-                }
3412
-
3413
-                $text .= "<li id=\"fn:$note_id\">\n";
3414
-                $text .= $footnote . "\n";
3415
-                $text .= "</li>\n\n";
3416
-            }
3417
-
3418
-            $text .= "</ol>\n";
3419
-            $text .= "</div>";
3420
-        }
3421
-        return $text;
3422
-    }
3423
-
3424
-    function _appendFootnotes_callback($matches)
3425
-    {
3426
-        $node_id = $this->fn_id_prefix . $matches[1];
3427
-
3428
-        # Create footnote marker only if it has a corresponding footnote *and*
3429
-        # the footnote hasn't been used by another marker.
3430
-        if (isset($this->footnotes[$node_id])) {
3431
-            $num =& $this->footnotes_numbers[$node_id];
3432
-            if (!isset($num)) {
3433
-                # Transfer footnote content to the ordered list and give it its
3434
-                # number
3435
-                $this->footnotes_ordered[$node_id]   = $this->footnotes[$node_id];
3436
-                $this->footnotes_ref_count[$node_id] = 1;
3437
-                $num                                 = $this->footnote_counter ++;
3438
-                $ref_count_mark                      = '';
3439
-            } else {
3440
-                $ref_count_mark = $this->footnotes_ref_count[$node_id] += 1;
3441
-            }
3442
-
3443
-            $attr = " rel=\"footnote\"";
3444
-            if ($this->fn_link_class != "") {
3445
-                $class = $this->fn_link_class;
3446
-                $class = $this->encodeAttribute($class);
3447
-                $attr .= " class=\"$class\"";
3448
-            }
3449
-            if ($this->fn_link_title != "") {
3450
-                $title = $this->fn_link_title;
3451
-                $title = $this->encodeAttribute($title);
3452
-                $attr .= " title=\"$title\"";
3453
-            }
3454
-
3455
-            $attr    = str_replace("%%", $num, $attr);
3456
-            $node_id = $this->encodeAttribute($node_id);
3457
-
3458
-            return
3459
-                "<sup id=\"fnref$ref_count_mark:$node_id\">" .
3460
-                "<a href=\"#fn:$node_id\"$attr>$num</a>" .
3461
-                "</sup>";
3462
-        }
3463
-
3464
-        return "[^" . $matches[1] . "]";
3465
-    }
3466
-
3467
-    ### Abbreviations ###
3468
-
3469
-    function stripAbbreviations($text)
3470
-    {
3471
-        #
3472
-        # Strips abbreviations from text, stores titles in hash references.
3473
-        #
3474
-        $less_than_tab = $this->tab_width - 1;
3475
-
3476
-        # Link defs are in the form: [id]*: url "optional title"
3477
-        $text = preg_replace_callback(
3478
-            '{
3327
+			array(&$this, '_stripFootnotes_callback'),
3328
+			$text
3329
+		);
3330
+		return $text;
3331
+	}
3332
+
3333
+	function _stripFootnotes_callback($matches)
3334
+	{
3335
+		$note_id                   = $this->fn_id_prefix . $matches[1];
3336
+		$this->footnotes[$note_id] = $this->outdent($matches[2]);
3337
+		return ''; # String that will replace the block
3338
+	}
3339
+
3340
+	function doFootnotes($text)
3341
+	{
3342
+		#
3343
+		# Replace footnote references in $text [^id] with a special text-token
3344
+		# which will be replaced by the actual footnote marker in appendFootnotes.
3345
+		#
3346
+		if (!$this->in_anchor) {
3347
+			$text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
3348
+		}
3349
+		return $text;
3350
+	}
3351
+
3352
+	function appendFootnotes($text)
3353
+	{
3354
+		#
3355
+		# Append footnote list to text.
3356
+		#
3357
+		$text = preg_replace_callback(
3358
+			'{F\x1Afn:(.*?)\x1A:}',
3359
+			array(&$this, '_appendFootnotes_callback'),
3360
+			$text
3361
+		);
3362
+
3363
+		if (!empty($this->footnotes_ordered)) {
3364
+			$text .= "\n\n";
3365
+			$text .= "<div class=\"footnotes\">\n";
3366
+			$text .= "<hr" . $this->empty_element_suffix . "\n";
3367
+			$text .= "<ol>\n\n";
3368
+
3369
+			$attr = " rev=\"footnote\"";
3370
+			if ($this->fn_backlink_class != "") {
3371
+				$class = $this->fn_backlink_class;
3372
+				$class = $this->encodeAttribute($class);
3373
+				$attr .= " class=\"$class\"";
3374
+			}
3375
+			if ($this->fn_backlink_title != "") {
3376
+				$title = $this->fn_backlink_title;
3377
+				$title = $this->encodeAttribute($title);
3378
+				$attr .= " title=\"$title\"";
3379
+			}
3380
+			$num = 0;
3381
+
3382
+			while (!empty($this->footnotes_ordered)) {
3383
+				$footnote = reset($this->footnotes_ordered);
3384
+				$note_id  = key($this->footnotes_ordered);
3385
+				unset($this->footnotes_ordered[$note_id]);
3386
+				$ref_count = $this->footnotes_ref_count[$note_id];
3387
+				unset($this->footnotes_ref_count[$note_id]);
3388
+				unset($this->footnotes[$note_id]);
3389
+
3390
+				$footnote .= "\n"; # Need to append newline before parsing.
3391
+				$footnote = $this->runBlockGamut("$footnote\n");
3392
+				$footnote = preg_replace_callback(
3393
+					'{F\x1Afn:(.*?)\x1A:}',
3394
+					array(&$this, '_appendFootnotes_callback'),
3395
+					$footnote
3396
+				);
3397
+
3398
+				$attr    = str_replace("%%", ++ $num, $attr);
3399
+				$note_id = $this->encodeAttribute($note_id);
3400
+
3401
+				# Prepare backlink, multiple backlinks if multiple references
3402
+				$backlink = "<a href=\"#fnref:$note_id\"$attr>&#8617;</a>";
3403
+				for ($ref_num = 2; $ref_num <= $ref_count; ++ $ref_num) {
3404
+					$backlink .= " <a href=\"#fnref$ref_num:$note_id\"$attr>&#8617;</a>";
3405
+				}
3406
+				# Add backlink to last paragraph; create new paragraph if needed.
3407
+				if (preg_match('{</p>$}', $footnote)) {
3408
+					$footnote = substr($footnote, 0, - 4) . "&#160;$backlink</p>";
3409
+				} else {
3410
+					$footnote .= "\n\n<p>$backlink</p>";
3411
+				}
3412
+
3413
+				$text .= "<li id=\"fn:$note_id\">\n";
3414
+				$text .= $footnote . "\n";
3415
+				$text .= "</li>\n\n";
3416
+			}
3417
+
3418
+			$text .= "</ol>\n";
3419
+			$text .= "</div>";
3420
+		}
3421
+		return $text;
3422
+	}
3423
+
3424
+	function _appendFootnotes_callback($matches)
3425
+	{
3426
+		$node_id = $this->fn_id_prefix . $matches[1];
3427
+
3428
+		# Create footnote marker only if it has a corresponding footnote *and*
3429
+		# the footnote hasn't been used by another marker.
3430
+		if (isset($this->footnotes[$node_id])) {
3431
+			$num =& $this->footnotes_numbers[$node_id];
3432
+			if (!isset($num)) {
3433
+				# Transfer footnote content to the ordered list and give it its
3434
+				# number
3435
+				$this->footnotes_ordered[$node_id]   = $this->footnotes[$node_id];
3436
+				$this->footnotes_ref_count[$node_id] = 1;
3437
+				$num                                 = $this->footnote_counter ++;
3438
+				$ref_count_mark                      = '';
3439
+			} else {
3440
+				$ref_count_mark = $this->footnotes_ref_count[$node_id] += 1;
3441
+			}
3442
+
3443
+			$attr = " rel=\"footnote\"";
3444
+			if ($this->fn_link_class != "") {
3445
+				$class = $this->fn_link_class;
3446
+				$class = $this->encodeAttribute($class);
3447
+				$attr .= " class=\"$class\"";
3448
+			}
3449
+			if ($this->fn_link_title != "") {
3450
+				$title = $this->fn_link_title;
3451
+				$title = $this->encodeAttribute($title);
3452
+				$attr .= " title=\"$title\"";
3453
+			}
3454
+
3455
+			$attr    = str_replace("%%", $num, $attr);
3456
+			$node_id = $this->encodeAttribute($node_id);
3457
+
3458
+			return
3459
+				"<sup id=\"fnref$ref_count_mark:$node_id\">" .
3460
+				"<a href=\"#fn:$node_id\"$attr>$num</a>" .
3461
+				"</sup>";
3462
+		}
3463
+
3464
+		return "[^" . $matches[1] . "]";
3465
+	}
3466
+
3467
+	### Abbreviations ###
3468
+
3469
+	function stripAbbreviations($text)
3470
+	{
3471
+		#
3472
+		# Strips abbreviations from text, stores titles in hash references.
3473
+		#
3474
+		$less_than_tab = $this->tab_width - 1;
3475
+
3476
+		# Link defs are in the form: [id]*: url "optional title"
3477
+		$text = preg_replace_callback(
3478
+			'{
3479 3479
 			^[ ]{0,' . $less_than_tab . '}\*\[(.+?)\][ ]?:	# abbr_id = $1
3480 3480
 			(.*)					# text = $2 (no blank lines allowed)
3481 3481
 			}xm',
3482
-            array(&$this, '_stripAbbreviations_callback'),
3483
-            $text
3484
-        );
3485
-        return $text;
3486
-    }
3487
-
3488
-    function _stripAbbreviations_callback($matches)
3489
-    {
3490
-        $abbr_word = $matches[1];
3491
-        $abbr_desc = $matches[2];
3492
-        if ($this->abbr_word_re) {
3493
-            $this->abbr_word_re .= '|';
3494
-        }
3495
-        $this->abbr_word_re .= preg_quote($abbr_word);
3496
-        $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
3497
-        return ''; # String that will replace the block
3498
-    }
3499
-
3500
-    function doAbbreviations($text)
3501
-    {
3502
-        #
3503
-        # Find defined abbreviations in text and wrap them in <abbr> elements.
3504
-        #
3505
-        if ($this->abbr_word_re) {
3506
-            // cannot use the /x modifier because abbr_word_re may
3507
-            // contain significant spaces:
3508
-            $text = preg_replace_callback(
3509
-                '{' .
3510
-                '(?<![\w\x1A])' .
3511
-                '(?:' . $this->abbr_word_re . ')' .
3512
-                '(?![\w\x1A])' .
3513
-                '}',
3514
-                array(&$this, '_doAbbreviations_callback'),
3515
-                $text
3516
-            );
3517
-        }
3518
-        return $text;
3519
-    }
3520
-
3521
-    function _doAbbreviations_callback($matches)
3522
-    {
3523
-        $abbr = $matches[0];
3524
-        if (isset($this->abbr_desciptions[$abbr])) {
3525
-            $desc = $this->abbr_desciptions[$abbr];
3526
-            if (empty($desc)) {
3527
-                return $this->hashPart("<abbr>$abbr</abbr>");
3528
-            } else {
3529
-                $desc = $this->encodeAttribute($desc);
3530
-                return $this->hashPart("<abbr title=\"$desc\">$abbr</abbr>");
3531
-            }
3532
-        } else {
3533
-            return $matches[0];
3534
-        }
3535
-    }
3482
+			array(&$this, '_stripAbbreviations_callback'),
3483
+			$text
3484
+		);
3485
+		return $text;
3486
+	}
3487
+
3488
+	function _stripAbbreviations_callback($matches)
3489
+	{
3490
+		$abbr_word = $matches[1];
3491
+		$abbr_desc = $matches[2];
3492
+		if ($this->abbr_word_re) {
3493
+			$this->abbr_word_re .= '|';
3494
+		}
3495
+		$this->abbr_word_re .= preg_quote($abbr_word);
3496
+		$this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
3497
+		return ''; # String that will replace the block
3498
+	}
3499
+
3500
+	function doAbbreviations($text)
3501
+	{
3502
+		#
3503
+		# Find defined abbreviations in text and wrap them in <abbr> elements.
3504
+		#
3505
+		if ($this->abbr_word_re) {
3506
+			// cannot use the /x modifier because abbr_word_re may
3507
+			// contain significant spaces:
3508
+			$text = preg_replace_callback(
3509
+				'{' .
3510
+				'(?<![\w\x1A])' .
3511
+				'(?:' . $this->abbr_word_re . ')' .
3512
+				'(?![\w\x1A])' .
3513
+				'}',
3514
+				array(&$this, '_doAbbreviations_callback'),
3515
+				$text
3516
+			);
3517
+		}
3518
+		return $text;
3519
+	}
3520
+
3521
+	function _doAbbreviations_callback($matches)
3522
+	{
3523
+		$abbr = $matches[0];
3524
+		if (isset($this->abbr_desciptions[$abbr])) {
3525
+			$desc = $this->abbr_desciptions[$abbr];
3526
+			if (empty($desc)) {
3527
+				return $this->hashPart("<abbr>$abbr</abbr>");
3528
+			} else {
3529
+				$desc = $this->encodeAttribute($desc);
3530
+				return $this->hashPart("<abbr title=\"$desc\">$abbr</abbr>");
3531
+			}
3532
+		} else {
3533
+			return $matches[0];
3534
+		}
3535
+	}
3536 3536
 }
3537 3537
 
3538 3538
 /*
Please login to merge, or discard this patch.
myth/Models/CIDbModel.php 1 patch
Indentation   +1420 added lines, -1420 removed lines patch added patch discarded remove patch
@@ -102,1479 +102,1479 @@
 block discarded – undo
102 102
 class CIDbModel
103 103
 {
104 104
 
105
-    /**
106
-     * The model's default table name.
107
-     *
108
-     * @var string;
109
-     */
110
-    protected $table_name;
111
-
112
-    /**
113
-     * The model's default primary key.
114
-     *
115
-     * @var string
116
-     */
117
-    protected $primary_key = 'id';
118
-
119
-    /**
120
-     * The type of date/time field used for created_on and modified_on fields.
121
-     * Valid types are: 'int', 'datetime', 'date'
122
-     *
123
-     * @var string
124
-     * @access protected
125
-     */
126
-    protected $date_format = 'datetime';
127
-
128
-    /*
105
+	/**
106
+	 * The model's default table name.
107
+	 *
108
+	 * @var string;
109
+	 */
110
+	protected $table_name;
111
+
112
+	/**
113
+	 * The model's default primary key.
114
+	 *
115
+	 * @var string
116
+	 */
117
+	protected $primary_key = 'id';
118
+
119
+	/**
120
+	 * The type of date/time field used for created_on and modified_on fields.
121
+	 * Valid types are: 'int', 'datetime', 'date'
122
+	 *
123
+	 * @var string
124
+	 * @access protected
125
+	 */
126
+	protected $date_format = 'datetime';
127
+
128
+	/*
129 129
         Var: $log_user
130 130
         If TRUE, will log user id for 'created_by', 'modified_by' and 'deleted_by'.
131 131
 
132 132
         Access:
133 133
             Protected
134 134
     */
135
-    protected $log_user = FALSE;
135
+	protected $log_user = FALSE;
136 136
 
137 137
 
138 138
 
139
-    /**
140
-     * Whether or not to auto-fill a 'created_on' field on inserts.
141
-     *
142
-     * @var boolean
143
-     * @access protected
144
-     */
145
-    protected $set_created = TRUE;
139
+	/**
140
+	 * Whether or not to auto-fill a 'created_on' field on inserts.
141
+	 *
142
+	 * @var boolean
143
+	 * @access protected
144
+	 */
145
+	protected $set_created = TRUE;
146 146
 
147
-    /**
148
-     * Field name to use to the created time column in the DB table.
149
-     *
150
-     * @var string
151
-     * @access protected
152
-     */
153
-    protected $created_field = 'created_on';
147
+	/**
148
+	 * Field name to use to the created time column in the DB table.
149
+	 *
150
+	 * @var string
151
+	 * @access protected
152
+	 */
153
+	protected $created_field = 'created_on';
154 154
 
155
-    /*
155
+	/*
156 156
         Var: $created_by_field
157 157
         Field name to use to the created by column in the DB table.
158 158
 
159 159
         Access:
160 160
             Protected
161 161
     */
162
-    protected $created_by_field = 'created_by';
162
+	protected $created_by_field = 'created_by';
163 163
 
164 164
 
165 165
 
166
-    /**
167
-     * Whether or not to auto-fill a 'modified_on' field on updates.
168
-     *
169
-     * @var boolean
170
-     * @access protected
171
-     */
172
-    protected $set_modified = TRUE;
166
+	/**
167
+	 * Whether or not to auto-fill a 'modified_on' field on updates.
168
+	 *
169
+	 * @var boolean
170
+	 * @access protected
171
+	 */
172
+	protected $set_modified = TRUE;
173 173
 
174
-    /**
175
-     * Field name to use to the modified time column in the DB table.
176
-     *
177
-     * @var string
178
-     * @access protected
179
-     */
180
-    protected $modified_field = 'modified_on';
174
+	/**
175
+	 * Field name to use to the modified time column in the DB table.
176
+	 *
177
+	 * @var string
178
+	 * @access protected
179
+	 */
180
+	protected $modified_field = 'modified_on';
181 181
 
182
-    /*
182
+	/*
183 183
         Var: $modified_by_field
184 184
         Field name to use to the modified by column in the DB table.
185 185
 
186 186
         Access:
187 187
             Protected
188 188
     */
189
-    protected $modified_by_field = 'modified_by';
189
+	protected $modified_by_field = 'modified_by';
190 190
 
191 191
 
192
-    /**
193
-     * Support for soft_deletes.
194
-     */
195
-    protected $soft_deletes = FALSE;
196
-    protected $soft_delete_key = 'deleted';
197
-    protected $temp_with_deleted = FALSE;
192
+	/**
193
+	 * Support for soft_deletes.
194
+	 */
195
+	protected $soft_deletes = FALSE;
196
+	protected $soft_delete_key = 'deleted';
197
+	protected $temp_with_deleted = FALSE;
198 198
 
199
-    /*
199
+	/*
200 200
         Var: $deleted_by_field
201 201
         Field name to use for the deleted by column in the DB table.
202 202
 
203 203
         Access:
204 204
             Protected
205 205
     */
206
-    protected $deleted_by_field = 'deleted_by';
206
+	protected $deleted_by_field = 'deleted_by';
207 207
 
208 208
 
209 209
 
210
-    /**
211
-     * Various callbacks available to the class. They are simple lists of
212
-     * method names (methods will be ran on $this).
213
-     */
214
-    protected $before_insert = array();
215
-    protected $after_insert = array();
216
-    protected $before_update = array();
217
-    protected $after_update = array();
218
-    protected $before_find = array();
219
-    protected $after_find = array();
220
-    protected $before_delete = array();
221
-    protected $after_delete = array();
210
+	/**
211
+	 * Various callbacks available to the class. They are simple lists of
212
+	 * method names (methods will be ran on $this).
213
+	 */
214
+	protected $before_insert = array();
215
+	protected $after_insert = array();
216
+	protected $before_update = array();
217
+	protected $after_update = array();
218
+	protected $before_find = array();
219
+	protected $after_find = array();
220
+	protected $before_delete = array();
221
+	protected $after_delete = array();
222 222
 
223
-    protected $callback_parameters = array();
223
+	protected $callback_parameters = array();
224 224
 
225 225
 
226 226
 
227
-    /*
227
+	/*
228 228
         If TRUE, inserts will return the last_insert_id. However,
229 229
         this can potentially slow down large imports drastically
230 230
         so you can turn it off with the return_insert_id(false) method.
231 231
      */
232
-    protected $return_insert_id = true;
233
-
234
-    /**
235
-     * By default, we return items as objects. You can change this for the
236
-     * entire class by setting this value to 'array' instead of 'object'.
237
-     * Alternatively, you can do it on a per-instance basis using the
238
-     * 'as_array()' and 'as_object()' methods.
239
-     */
240
-    protected $return_type = 'object';
241
-    protected $temp_return_type = NULL;
242
-
243
-    /**
244
-     * Protected, non-modifiable attributes
245
-     */
246
-    protected $protected_attributes = array();
247
-
248
-
249
-
250
-    /**
251
-     * An array of validation rules. This needs to be the same format
252
-     * as validation rules passed to the Form_validation library.
253
-     */
254
-    protected $validation_rules = array();
255
-
256
-    /**
257
-     * Optionally skip the validation. Used in conjunction with
258
-     * skip_validation() to skip data validation for any future calls.
259
-     */
260
-    protected $skip_validation = FALSE;
261
-
262
-    /**
263
-     * An array of extra rules to add to validation rules during inserts only.
264
-     * Often used for adding 'required' rules to fields on insert, but not udpates.
265
-     *
266
-     *   array( 'username' => 'required|strip_tags' );
267
-     * @var array
268
-     */
269
-    protected $insert_validate_rules = array();
270
-
271
-
272
-
273
-    /**
274
-     * @var Array Columns for the model's database fields
275
-     *
276
-     * This can be set to avoid a database call if using $this->prep_data()
277
-     */
278
-    protected $fields = array();
279
-
280
-    //--------------------------------------------------------------------
281
-
282
-    /**
283
-     * Does basic setup. Can pass the database connection into the constructor
284
-     * to use that $db instead of the global CI one.
285
-     *
286
-     * @param object $db // A database/driver instance
287
-     * @param object $form_validation // A form_validation library instance
288
-     */
289
-    public function __construct($db = null, $form_validation = null)
290
-    {
291
-        // Always protect our attributes
292
-        array_unshift($this->before_insert, 'protect_attributes');
293
-        array_unshift($this->before_update, 'protect_attributes');
294
-
295
-        // Check our auto-set features and make sure they are part of
296
-        // our observer system.
297
-        if ($this->set_created === true) array_unshift($this->before_insert, 'created_on');
298
-        if ($this->set_modified === true) array_unshift($this->before_update, 'modified_on');
299
-
300
-        // Make sure our temp return type is correct.
301
-        $this->temp_return_type = $this->return_type;
302
-
303
-        // Make sure our database is loaded
304
-        if (!is_null($db)) {
305
-            $this->db = $db;
306
-        }
307
-        else {
308
-            // Auto Init the damn database....
309
-            $this->load->database();
310
-        }
311
-
312
-        // Do we have a form_validation library?
313
-        if (! is_null($form_validation)) {
314
-            $this->form_validation = $form_validation;
315
-        }
316
-        else {
317
-            $this->load->library('form_validation');
318
-        }
232
+	protected $return_insert_id = true;
233
+
234
+	/**
235
+	 * By default, we return items as objects. You can change this for the
236
+	 * entire class by setting this value to 'array' instead of 'object'.
237
+	 * Alternatively, you can do it on a per-instance basis using the
238
+	 * 'as_array()' and 'as_object()' methods.
239
+	 */
240
+	protected $return_type = 'object';
241
+	protected $temp_return_type = NULL;
242
+
243
+	/**
244
+	 * Protected, non-modifiable attributes
245
+	 */
246
+	protected $protected_attributes = array();
247
+
248
+
249
+
250
+	/**
251
+	 * An array of validation rules. This needs to be the same format
252
+	 * as validation rules passed to the Form_validation library.
253
+	 */
254
+	protected $validation_rules = array();
255
+
256
+	/**
257
+	 * Optionally skip the validation. Used in conjunction with
258
+	 * skip_validation() to skip data validation for any future calls.
259
+	 */
260
+	protected $skip_validation = FALSE;
261
+
262
+	/**
263
+	 * An array of extra rules to add to validation rules during inserts only.
264
+	 * Often used for adding 'required' rules to fields on insert, but not udpates.
265
+	 *
266
+	 *   array( 'username' => 'required|strip_tags' );
267
+	 * @var array
268
+	 */
269
+	protected $insert_validate_rules = array();
270
+
271
+
272
+
273
+	/**
274
+	 * @var Array Columns for the model's database fields
275
+	 *
276
+	 * This can be set to avoid a database call if using $this->prep_data()
277
+	 */
278
+	protected $fields = array();
279
+
280
+	//--------------------------------------------------------------------
281
+
282
+	/**
283
+	 * Does basic setup. Can pass the database connection into the constructor
284
+	 * to use that $db instead of the global CI one.
285
+	 *
286
+	 * @param object $db // A database/driver instance
287
+	 * @param object $form_validation // A form_validation library instance
288
+	 */
289
+	public function __construct($db = null, $form_validation = null)
290
+	{
291
+		// Always protect our attributes
292
+		array_unshift($this->before_insert, 'protect_attributes');
293
+		array_unshift($this->before_update, 'protect_attributes');
294
+
295
+		// Check our auto-set features and make sure they are part of
296
+		// our observer system.
297
+		if ($this->set_created === true) array_unshift($this->before_insert, 'created_on');
298
+		if ($this->set_modified === true) array_unshift($this->before_update, 'modified_on');
299
+
300
+		// Make sure our temp return type is correct.
301
+		$this->temp_return_type = $this->return_type;
302
+
303
+		// Make sure our database is loaded
304
+		if (!is_null($db)) {
305
+			$this->db = $db;
306
+		}
307
+		else {
308
+			// Auto Init the damn database....
309
+			$this->load->database();
310
+		}
311
+
312
+		// Do we have a form_validation library?
313
+		if (! is_null($form_validation)) {
314
+			$this->form_validation = $form_validation;
315
+		}
316
+		else {
317
+			$this->load->library('form_validation');
318
+		}
319 319
         
320
-        log_message('debug', 'CIDbModel Class Initialized');
321
-    }
322
-
323
-    //--------------------------------------------------------------------
324
-
325
-    //--------------------------------------------------------------------
326
-    // CRUD Methods
327
-    //--------------------------------------------------------------------
328
-
329
-    /**
330
-     * A simple way to grab the first result of a search only.
331
-     */
332
-    public function first()
333
-    {
334
-        $rows = $this->limit(1, 0)->find_all();
335
-
336
-        if (is_array($rows) && count($rows)) {
337
-            return $rows[0];
338
-        }
339
-
340
-        return $rows;
341
-    }
342
-
343
-    //--------------------------------------------------------------------
344
-
345
-
346
-    /**
347
-     * Finds a single record based on it's primary key. Will ignore deleted rows.
348
-     *
349
-     * @param  mixed $id The primary_key value of the object to retrieve.
350
-     * @return object
351
-     */
352
-    public function find($id)
353
-    {
354
-        $this->trigger('before_find', ['id' => $id, 'method' => 'find']);
355
-
356
-        // Ignore any soft-deleted rows
357
-        if ($this->soft_deletes) {
358
-            // We only need to modify the where statement if
359
-            // temp_with_deleted is false.
360
-            if ($this->temp_with_deleted !== true) {
361
-                $this->db->where($this->soft_delete_key, false);
362
-            }
363
-
364
-            $this->temp_with_deleted = false;
365
-        }
366
-
367
-        $this->db->where($this->primary_key, $id);
368
-        $row = $this->db->get($this->table_name);
369
-        $row = $this->temp_return_type == 'array' ? $row->row_array() : $row->row(0, $this->temp_return_type);
370
-
371
-        if ( ! empty($row))
372
-        {
373
-            $row = $this->trigger('after_find', ['id' => $id, 'method' => 'find', 'fields' => $row]);
374
-        }
375
-
376
-        // Reset our return type
377
-        $this->temp_return_type = $this->return_type;
378
-
379
-        return $row;
380
-    }
381
-
382
-    //--------------------------------------------------------------------
383
-
384
-    /**
385
-     * Fetch a single record based on an arbitrary WHERE call. Can be
386
-     * any valid value to $this->db->where(). Will not pull in deleted rows
387
-     * if using soft deletes.
388
-     *
389
-     * @return object
390
-     */
391
-    public function find_by()
392
-    {
393
-        $where = func_get_args();
394
-        $this->_set_where($where);
395
-
396
-        // Ignore any soft-deleted rows
397
-        if ($this->soft_deletes) {
398
-            // We only need to modify the where statement if
399
-            // temp_with_deleted is false.
400
-            if ($this->temp_with_deleted !== true) {
401
-                $this->db->where($this->soft_delete_key, false);
402
-            }
403
-
404
-            $this->temp_with_deleted = false;
405
-        }
406
-
407
-        $this->trigger('before_find', ['method' => 'find_by', 'fields' => $where]);
408
-
409
-        $row = $this->db->get($this->table_name);
410
-        $row = $this->temp_return_type == 'array' ? $row->row_array() : $row->row(0, $this->temp_return_type);
411
-
412
-        if ( ! empty($row))
413
-        {
414
-            $row = $this->trigger('after_find', ['method' => 'find_by', 'fields' => $row]);
415
-        }
416
-
417
-        // Reset our return type
418
-        $this->temp_return_type = $this->return_type;
419
-
420
-        return $row;
421
-    }
422
-
423
-    //--------------------------------------------------------------------
424
-
425
-    /**
426
-     * Retrieves a number of items based on an array of primary_values passed in.
427
-     *
428
-     * @param  array $values An array of primary key values to find.
429
-     *
430
-     * @return object or FALSE
431
-     */
432
-    public function find_many($values)
433
-    {
434
-        $this->db->where_in($this->primary_key, $values);
435
-
436
-        return $this->find_all();
437
-    }
438
-
439
-    //--------------------------------------------------------------------
440
-
441
-    /**
442
-     * Retrieves a number of items based on an arbitrary WHERE call. Can be
443
-     * any set of parameters valid to $db->where.
444
-     *
445
-     * @return object or FALSE
446
-     */
447
-    public function find_many_by()
448
-    {
449
-        $where = func_get_args();
450
-        $this->_set_where($where);
451
-
452
-        return $this->find_all();
453
-    }
454
-
455
-    //--------------------------------------------------------------------
456
-
457
-    /**
458
-     * Fetch all of the records in the table. Can be used with scoped calls
459
-     * to restrict the results.
460
-     *
461
-     * @return object or FALSE
462
-     */
463
-    public function find_all()
464
-    {
465
-        $this->trigger('before_find', ['method' => 'find_all']);
466
-
467
-        // Ignore any soft-deleted rows
468
-        if ($this->soft_deletes) {
469
-            // We only need to modify the where statement if
470
-            // temp_with_deleted is false.
471
-            if ($this->temp_with_deleted !== true) {
472
-                $this->db->where($this->soft_delete_key, false);
473
-            }
474
-
475
-            $this->temp_with_deleted = false;
476
-        }
477
-
478
-        $rows = $this->db->get($this->table_name);
479
-        $rows = $this->temp_return_type == 'array' ? $rows->result_array() : $rows->result($this->temp_return_type);
480
-
481
-        if (is_array($rows)) {
482
-            foreach ($rows as $key => &$row) {
483
-                $row = $this->trigger('after_find', ['method' => 'find_all', 'fields' => $row] );
484
-            }
485
-        }
486
-
487
-        // Reset our return type
488
-        $this->temp_return_type = $this->return_type;
489
-
490
-        return $rows;
491
-    }
492
-
493
-    //--------------------------------------------------------------------
494
-
495
-    /**
496
-     * Inserts data into the database.
497
-     *
498
-     * @param  array $data An array of key/value pairs to insert to database.
499
-     * @param  array $skip_validation If TRUE, will skip validation of data for this call only.
500
-     * @return mixed       The primary_key value of the inserted record, or FALSE.
501
-     */
502
-    public function insert($data, $skip_validation = null)
503
-    {
504
-        $skip_validation = is_null($skip_validation) ? $this->skip_validation : $skip_validation;
505
-
506
-        if ($skip_validation === FALSE) {
507
-            $data = $this->validate($data, 'insert', $skip_validation);
508
-        }
509
-
510
-        if ($data !== FALSE) {
511
-            $data = $this->trigger('before_insert', ['method' => 'insert', 'fields' => $data]);
512
-
513
-            $this->db->insert($this->table_name, $this->prep_data($data) );
514
-
515
-            if ($this->return_insert_id) {
516
-                $id = $this->db->insert_id();
517
-
518
-                $this->trigger('after_insert', ['id' => $id, 'fields' => $data, 'method' => 'insert']);
519
-
520
-                return $id;
521
-            }
522
-
523
-            return TRUE;
524
-        } else {
525
-            return FALSE;
526
-        }
527
-    }
528
-
529
-    //--------------------------------------------------------------------
530
-
531
-    /**
532
-     * Inserts multiple rows into the database at once. Takes an associative
533
-     * array of value pairs.
534
-     *
535
-     * $data = array(
536
-     *     array(
537
-     *         'title' => 'My title'
538
-     *     ),
539
-     *     array(
540
-     *         'title'  => 'My Other Title'
541
-     *     )
542
-     * );
543
-     *
544
-     * @param  array $data An associate array of rows to insert
545
-     * @param  array $skip_validation If TRUE, will skip validation of data for this call only.
546
-     * @return bool
547
-     */
548
-    public function insert_batch($data, $skip_validation = null)
549
-    {
550
-        $skip_validation = is_null($skip_validation) ? $this->skip_validation : $skip_validation;
551
-
552
-        if ($skip_validation === FALSE) {
553
-            $data = $this->validate($data, 'insert', $skip_validation);
554
-        }
555
-
556
-        if ($data !== FALSE) {
557
-            $data['batch'] = true;
558
-            $data = $this->trigger('before_insert', ['method' => 'insert_batch', 'fields' => $data] );
559
-            unset($data['batch']);
560
-
561
-            return $this->db->insert_batch($this->table_name, $data);
562
-        } else {
563
-            return FALSE;
564
-        }
565
-    }
566
-
567
-    //--------------------------------------------------------------------
568
-
569
-    /**
570
-     * Performs the SQL standard for a combined DELETE + INSERT, using
571
-     * PRIMARY and UNIQUE keys to determine which row to replace.
572
-     *
573
-     * See CI's documentation for the replace method. We simply wrap
574
-     * our validation and triggers around their method.
575
-     *
576
-     * @param $data
577
-     * @param null $skip_validation
578
-     * @return bool
579
-     */
580
-    public function replace($data, $skip_validation=null)
581
-    {
582
-        $skip_validation = is_null($skip_validation) ? $this->skip_validation : $skip_validation;
583
-
584
-        if ($skip_validation === FALSE) {
585
-            $data = $this->validate($data, 'insert', $skip_validation);
586
-        }
587
-
588
-        if ($data !== FALSE) {
589
-            $this->db->replace($this->table_name, $this->prep_data($data));
590
-
591
-            if ($this->return_insert_id) {
592
-                $id = $this->db->insert_id();
593
-
594
-                $this->trigger('after_insert', ['id' => $id, 'fields' => $data, 'method'=>'replace']);
595
-
596
-                return $id;
597
-            }
598
-
599
-            return TRUE;
600
-        } else {
601
-            return FALSE;
602
-        }
603
-    }
604
-
605
-    //--------------------------------------------------------------------
606
-
607
-
608
-    /**
609
-     * Updates an existing record in the database.
610
-     *
611
-     * @param  mixed $id The primary_key value of the record to update.
612
-     * @param  array $data An array of value pairs to update in the record.
613
-     * @param  array $skip_validation If TRUE, will skip validation of data for this call only.
614
-     * @return bool
615
-     */
616
-    public function update($id, $data, $skip_validation = null)
617
-    {
618
-        $skip_validation = is_null($skip_validation) ? $this->skip_validation : $skip_validation;
619
-
620
-        if ($skip_validation === FALSE) {
621
-            $data = $this->validate($data);
622
-        }
623
-
624
-        // Will be false if it didn't validate.
625
-        if ($data !== FALSE) {
320
+		log_message('debug', 'CIDbModel Class Initialized');
321
+	}
322
+
323
+	//--------------------------------------------------------------------
324
+
325
+	//--------------------------------------------------------------------
326
+	// CRUD Methods
327
+	//--------------------------------------------------------------------
328
+
329
+	/**
330
+	 * A simple way to grab the first result of a search only.
331
+	 */
332
+	public function first()
333
+	{
334
+		$rows = $this->limit(1, 0)->find_all();
335
+
336
+		if (is_array($rows) && count($rows)) {
337
+			return $rows[0];
338
+		}
339
+
340
+		return $rows;
341
+	}
342
+
343
+	//--------------------------------------------------------------------
344
+
345
+
346
+	/**
347
+	 * Finds a single record based on it's primary key. Will ignore deleted rows.
348
+	 *
349
+	 * @param  mixed $id The primary_key value of the object to retrieve.
350
+	 * @return object
351
+	 */
352
+	public function find($id)
353
+	{
354
+		$this->trigger('before_find', ['id' => $id, 'method' => 'find']);
355
+
356
+		// Ignore any soft-deleted rows
357
+		if ($this->soft_deletes) {
358
+			// We only need to modify the where statement if
359
+			// temp_with_deleted is false.
360
+			if ($this->temp_with_deleted !== true) {
361
+				$this->db->where($this->soft_delete_key, false);
362
+			}
363
+
364
+			$this->temp_with_deleted = false;
365
+		}
366
+
367
+		$this->db->where($this->primary_key, $id);
368
+		$row = $this->db->get($this->table_name);
369
+		$row = $this->temp_return_type == 'array' ? $row->row_array() : $row->row(0, $this->temp_return_type);
370
+
371
+		if ( ! empty($row))
372
+		{
373
+			$row = $this->trigger('after_find', ['id' => $id, 'method' => 'find', 'fields' => $row]);
374
+		}
375
+
376
+		// Reset our return type
377
+		$this->temp_return_type = $this->return_type;
378
+
379
+		return $row;
380
+	}
381
+
382
+	//--------------------------------------------------------------------
383
+
384
+	/**
385
+	 * Fetch a single record based on an arbitrary WHERE call. Can be
386
+	 * any valid value to $this->db->where(). Will not pull in deleted rows
387
+	 * if using soft deletes.
388
+	 *
389
+	 * @return object
390
+	 */
391
+	public function find_by()
392
+	{
393
+		$where = func_get_args();
394
+		$this->_set_where($where);
395
+
396
+		// Ignore any soft-deleted rows
397
+		if ($this->soft_deletes) {
398
+			// We only need to modify the where statement if
399
+			// temp_with_deleted is false.
400
+			if ($this->temp_with_deleted !== true) {
401
+				$this->db->where($this->soft_delete_key, false);
402
+			}
403
+
404
+			$this->temp_with_deleted = false;
405
+		}
406
+
407
+		$this->trigger('before_find', ['method' => 'find_by', 'fields' => $where]);
408
+
409
+		$row = $this->db->get($this->table_name);
410
+		$row = $this->temp_return_type == 'array' ? $row->row_array() : $row->row(0, $this->temp_return_type);
411
+
412
+		if ( ! empty($row))
413
+		{
414
+			$row = $this->trigger('after_find', ['method' => 'find_by', 'fields' => $row]);
415
+		}
416
+
417
+		// Reset our return type
418
+		$this->temp_return_type = $this->return_type;
419
+
420
+		return $row;
421
+	}
422
+
423
+	//--------------------------------------------------------------------
424
+
425
+	/**
426
+	 * Retrieves a number of items based on an array of primary_values passed in.
427
+	 *
428
+	 * @param  array $values An array of primary key values to find.
429
+	 *
430
+	 * @return object or FALSE
431
+	 */
432
+	public function find_many($values)
433
+	{
434
+		$this->db->where_in($this->primary_key, $values);
435
+
436
+		return $this->find_all();
437
+	}
438
+
439
+	//--------------------------------------------------------------------
440
+
441
+	/**
442
+	 * Retrieves a number of items based on an arbitrary WHERE call. Can be
443
+	 * any set of parameters valid to $db->where.
444
+	 *
445
+	 * @return object or FALSE
446
+	 */
447
+	public function find_many_by()
448
+	{
449
+		$where = func_get_args();
450
+		$this->_set_where($where);
451
+
452
+		return $this->find_all();
453
+	}
454
+
455
+	//--------------------------------------------------------------------
456
+
457
+	/**
458
+	 * Fetch all of the records in the table. Can be used with scoped calls
459
+	 * to restrict the results.
460
+	 *
461
+	 * @return object or FALSE
462
+	 */
463
+	public function find_all()
464
+	{
465
+		$this->trigger('before_find', ['method' => 'find_all']);
466
+
467
+		// Ignore any soft-deleted rows
468
+		if ($this->soft_deletes) {
469
+			// We only need to modify the where statement if
470
+			// temp_with_deleted is false.
471
+			if ($this->temp_with_deleted !== true) {
472
+				$this->db->where($this->soft_delete_key, false);
473
+			}
474
+
475
+			$this->temp_with_deleted = false;
476
+		}
477
+
478
+		$rows = $this->db->get($this->table_name);
479
+		$rows = $this->temp_return_type == 'array' ? $rows->result_array() : $rows->result($this->temp_return_type);
480
+
481
+		if (is_array($rows)) {
482
+			foreach ($rows as $key => &$row) {
483
+				$row = $this->trigger('after_find', ['method' => 'find_all', 'fields' => $row] );
484
+			}
485
+		}
486
+
487
+		// Reset our return type
488
+		$this->temp_return_type = $this->return_type;
489
+
490
+		return $rows;
491
+	}
492
+
493
+	//--------------------------------------------------------------------
494
+
495
+	/**
496
+	 * Inserts data into the database.
497
+	 *
498
+	 * @param  array $data An array of key/value pairs to insert to database.
499
+	 * @param  array $skip_validation If TRUE, will skip validation of data for this call only.
500
+	 * @return mixed       The primary_key value of the inserted record, or FALSE.
501
+	 */
502
+	public function insert($data, $skip_validation = null)
503
+	{
504
+		$skip_validation = is_null($skip_validation) ? $this->skip_validation : $skip_validation;
505
+
506
+		if ($skip_validation === FALSE) {
507
+			$data = $this->validate($data, 'insert', $skip_validation);
508
+		}
509
+
510
+		if ($data !== FALSE) {
511
+			$data = $this->trigger('before_insert', ['method' => 'insert', 'fields' => $data]);
512
+
513
+			$this->db->insert($this->table_name, $this->prep_data($data) );
514
+
515
+			if ($this->return_insert_id) {
516
+				$id = $this->db->insert_id();
517
+
518
+				$this->trigger('after_insert', ['id' => $id, 'fields' => $data, 'method' => 'insert']);
519
+
520
+				return $id;
521
+			}
522
+
523
+			return TRUE;
524
+		} else {
525
+			return FALSE;
526
+		}
527
+	}
528
+
529
+	//--------------------------------------------------------------------
530
+
531
+	/**
532
+	 * Inserts multiple rows into the database at once. Takes an associative
533
+	 * array of value pairs.
534
+	 *
535
+	 * $data = array(
536
+	 *     array(
537
+	 *         'title' => 'My title'
538
+	 *     ),
539
+	 *     array(
540
+	 *         'title'  => 'My Other Title'
541
+	 *     )
542
+	 * );
543
+	 *
544
+	 * @param  array $data An associate array of rows to insert
545
+	 * @param  array $skip_validation If TRUE, will skip validation of data for this call only.
546
+	 * @return bool
547
+	 */
548
+	public function insert_batch($data, $skip_validation = null)
549
+	{
550
+		$skip_validation = is_null($skip_validation) ? $this->skip_validation : $skip_validation;
551
+
552
+		if ($skip_validation === FALSE) {
553
+			$data = $this->validate($data, 'insert', $skip_validation);
554
+		}
555
+
556
+		if ($data !== FALSE) {
557
+			$data['batch'] = true;
558
+			$data = $this->trigger('before_insert', ['method' => 'insert_batch', 'fields' => $data] );
559
+			unset($data['batch']);
560
+
561
+			return $this->db->insert_batch($this->table_name, $data);
562
+		} else {
563
+			return FALSE;
564
+		}
565
+	}
566
+
567
+	//--------------------------------------------------------------------
568
+
569
+	/**
570
+	 * Performs the SQL standard for a combined DELETE + INSERT, using
571
+	 * PRIMARY and UNIQUE keys to determine which row to replace.
572
+	 *
573
+	 * See CI's documentation for the replace method. We simply wrap
574
+	 * our validation and triggers around their method.
575
+	 *
576
+	 * @param $data
577
+	 * @param null $skip_validation
578
+	 * @return bool
579
+	 */
580
+	public function replace($data, $skip_validation=null)
581
+	{
582
+		$skip_validation = is_null($skip_validation) ? $this->skip_validation : $skip_validation;
583
+
584
+		if ($skip_validation === FALSE) {
585
+			$data = $this->validate($data, 'insert', $skip_validation);
586
+		}
587
+
588
+		if ($data !== FALSE) {
589
+			$this->db->replace($this->table_name, $this->prep_data($data));
590
+
591
+			if ($this->return_insert_id) {
592
+				$id = $this->db->insert_id();
593
+
594
+				$this->trigger('after_insert', ['id' => $id, 'fields' => $data, 'method'=>'replace']);
595
+
596
+				return $id;
597
+			}
598
+
599
+			return TRUE;
600
+		} else {
601
+			return FALSE;
602
+		}
603
+	}
604
+
605
+	//--------------------------------------------------------------------
606
+
607
+
608
+	/**
609
+	 * Updates an existing record in the database.
610
+	 *
611
+	 * @param  mixed $id The primary_key value of the record to update.
612
+	 * @param  array $data An array of value pairs to update in the record.
613
+	 * @param  array $skip_validation If TRUE, will skip validation of data for this call only.
614
+	 * @return bool
615
+	 */
616
+	public function update($id, $data, $skip_validation = null)
617
+	{
618
+		$skip_validation = is_null($skip_validation) ? $this->skip_validation : $skip_validation;
619
+
620
+		if ($skip_validation === FALSE) {
621
+			$data = $this->validate($data);
622
+		}
623
+
624
+		// Will be false if it didn't validate.
625
+		if ($data !== FALSE) {
626 626
             
627
-            $data = $this->trigger('before_update', ['id' => $id, 'method' =>'update', 'fields' => $data] );
627
+			$data = $this->trigger('before_update', ['id' => $id, 'method' =>'update', 'fields' => $data] );
628 628
             
629
-            $this->db->where($this->primary_key, $id);
630
-            $this->db->set( $this->prep_data($data) );
631
-            $result = $this->db->update($this->table_name);
632
-
633
-            $this->trigger('after_update', ['id' => $id, 'fields' => $data, 'result' => $result, 'method' => 'update']);
634
-
635
-            return $result;
636
-        } else {
637
-            return FALSE;
638
-        }
639
-    }
640
-
641
-    //--------------------------------------------------------------------
642
-
643
-    /**
644
-     * Updates multiple records in the database at once.
645
-     *
646
-     * $data = array(
647
-     *     array(
648
-     *         'title'  => 'My title',
649
-     *         'body'   => 'body 1'
650
-     *     ),
651
-     *     array(
652
-     *         'title'  => 'Another Title',
653
-     *         'body'   => 'body 2'
654
-     *     )
655
-     * );
656
-     *
657
-     * The $where_key should be the name of the column to match the record on.
658
-     * If $where_key == 'title', then each record would be matched on that
659
-     * 'title' value of the array. This does mean that the array key needs
660
-     * to be provided with each row's data.
661
-     *
662
-     * @param  array $data An associate array of row data to update.
663
-     * @param  string $where_key The column name to match on.
664
-     * @return bool
665
-     */
666
-    public function update_batch($data, $where_key)
667
-    {
668
-        foreach ($data as &$row) {
669
-            $row = $this->trigger('before_update', ['method' => 'update_batch', 'fields' => $row] );
670
-        }
671
-
672
-        $result = $this->db->update_batch($this->table_name, $data, $where_key);
673
-
674
-        foreach ($data as &$row) {
675
-            $this->trigger('after_update', ['fields' => $data, 'result' => $result, 'method' => 'update_batch']);
676
-        }
677
-
678
-        return $result;
679
-    }
680
-
681
-    //--------------------------------------------------------------------
682
-
683
-    /**
684
-     * Updates many records by an array of ids.
685
-     *
686
-     * While update_batch() allows modifying multiple, arbitrary rows of data
687
-     * on each row, update_many() sets the same values for each row.
688
-     *
689
-     * $ids = array(1, 2, 3, 5, 12);
690
-     * $data = array(
691
-     *     'deleted_by' => 1
692
-     * );
693
-     *
694
-     * $this->model->update_many($ids, $data);
695
-     *
696
-     * @param  array $ids An array of primary_key values to update.
697
-     * @param  array $data An array of value pairs to modify in each row.
698
-     * @param  array $skip_validation If TRUE, will skip validation of data for this call only.
699
-     * @return bool
700
-     */
701
-    public function update_many($ids, $data, $skip_validation = null)
702
-    {
703
-        if (!is_array($ids) || count($ids) == 0) return NULL;
704
-
705
-        $skip_validation = is_null($skip_validation) ? $this->skip_validation : $skip_validation;
706
-
707
-        if ($skip_validation === FALSE) {
708
-            $data = $this->validate($data, 'update', $skip_validation);
709
-        }
710
-
711
-        $data = $this->trigger('before_update', ['ids' => $ids, 'method' => 'update_many', 'fields' => $data]);
712
-
713
-        // Will be false if it didn't validate.
714
-        if ($data !== FALSE) {
715
-            $this->db->where_in($this->primary_key, $ids);
716
-            $this->db->set($data);
717
-            $result = $this->db->update($this->table_name);
718
-
719
-            $this->trigger('after_update', ['ids' => $ids, 'fields' => $data, 'result'=>$result, 'method' => 'update_many']);
720
-
721
-            return $result;
722
-        } else {
723
-            return FALSE;
724
-        }
725
-    }
726
-
727
-    //--------------------------------------------------------------------
728
-
729
-    /**
730
-     * Update records in the database using a standard WHERE clause.
731
-     *
732
-     * Your last parameter should be the $data array with values to update
733
-     * on the rows. Any additional parameters should be provided to make up
734
-     * a typical WHERE clause. This could be a single array, or a column name
735
-     * and a value.
736
-     *
737
-     * $data = array('deleted_by' => 1);
738
-     * $wheres = array('user_id' => 15);
739
-     *
740
-     * $this->update_by($wheres, $data);
741
-     * $this->update_by('user_id', 15, $data);
742
-     *
743
-     * @param array $data An array of data pairs to update
744
-     * @param one or more WHERE-acceptable entries.
745
-     * @return bool
746
-     */
747
-    public function update_by()
748
-    {
749
-        $args = func_get_args();
750
-        $data = array_pop($args);
751
-        $this->_set_where($args);
752
-
753
-        $data = $this->trigger('before_update', ['method' => 'update_by', 'fields' => $data]);
754
-
755
-        // Will be false if it didn't validate.
756
-        if ($this->validate($data) !== FALSE) {
757
-            $this->db->set( $this->prep_data($data) );
758
-            $result = $this->db->update($this->table_name);
759
-
760
-            $this->trigger('after_update', ['method' => 'update_by', 'fields' => $data, 'result' => $result] );
761
-
762
-            return $result;
763
-        } else {
764
-            return FALSE;
765
-        }
766
-    }
767
-
768
-    //--------------------------------------------------------------------
769
-
770
-    /**
771
-     * Updates all records and sets the value pairs passed in the array.
772
-     *
773
-     * @param  array $data An array of value pairs with the data to change.
774
-     * @param  array $skip_validation If TRUE, will skip validation of data for this call only.
775
-     * @return bool
776
-     */
777
-    public function update_all($data, $skip_validation = FALSE)
778
-    {
779
-        $data = $this->trigger('before_update', ['method' => 'update_all', 'fields' => $data] );
780
-
781
-        $skip_validation = is_null($skip_validation) ? $this->skip_validation : $skip_validation;
782
-
783
-        if ($skip_validation === FALSE) {
784
-            $data = $this->validate($data);
785
-        }
786
-
787
-        // Will be false if it didn't validate.
788
-        if ($data !== FALSE) {
789
-            $this->db->set( $this->prep_data($data) );
790
-            $result = $this->db->update($this->table_name);
791
-
792
-            $this->trigger('after_update', ['method' => 'update_all', 'fields' => $data, 'result' => $result] );
793
-
794
-            return $result;
795
-        } else {
796
-            return FALSE;
797
-        }
798
-    }
799
-
800
-    //--------------------------------------------------------------------
801
-
802
-    /**
803
-     * Increments the value of field for a given row, selected by the
804
-     * primary key for the table.
805
-     *
806
-     * @param $id
807
-     * @param $field
808
-     * @param int $value
809
-     * @return mixed
810
-     */
811
-    public function increment($id, $field, $value=1)
812
-    {
813
-        $value = (int)abs($value);
814
-
815
-        $this->db->where($this->primary_key, $id);
816
-        $this->db->set($field, "{$field}+{$value}", false);
817
-
818
-        return $this->db->update($this->table_name);
819
-    }
820
-
821
-    //--------------------------------------------------------------------
822
-
823
-    /**
824
-     * Increments the value of field for a given row, selected by the
825
-     * primary key for the table.
826
-     *
827
-     * @param $id
828
-     * @param $field
829
-     * @param int $value
830
-     * @return mixed
831
-     */
832
-    public function decrement($id, $field, $value=1)
833
-    {
834
-        $value = (int)abs($value);
835
-
836
-        $this->db->where($this->primary_key, $id);
837
-        $this->db->set($field, "{$field}-{$value}", false);
838
-
839
-        return $this->db->update($this->table_name);
840
-    }
841
-
842
-    //--------------------------------------------------------------------
843
-
844
-    /**
845
-     * Deletes a row by it's primary key value.
846
-     *
847
-     * @param  mixed $id The primary key value of the row to delete.
848
-     * @return bool
849
-     */
850
-    public function delete($id)
851
-    {
852
-        $this->trigger('before_delete', ['id' => $id, 'method' => 'delete'] );
853
-
854
-        $this->db->where($this->primary_key, $id);
855
-
856
-        if ($this->soft_deletes) {
857
-            $sets = $this->log_user && is_object($this->authenticate)
858
-                ? array($this->soft_delete_key => 1, $this->deleted_by_field => $this->authenticate->id())
859
-                : array($this->soft_delete_key => 1);
860
-
861
-            $result = $this->db->update($this->table_name, $sets);
862
-        } // Hard Delete
863
-        else {
864
-            $result = $this->db->delete($this->table_name);
865
-        }
866
-
867
-        $this->trigger('after_delete', ['id' => $id, 'method' => 'delete', 'result' => $result] );
629
+			$this->db->where($this->primary_key, $id);
630
+			$this->db->set( $this->prep_data($data) );
631
+			$result = $this->db->update($this->table_name);
632
+
633
+			$this->trigger('after_update', ['id' => $id, 'fields' => $data, 'result' => $result, 'method' => 'update']);
634
+
635
+			return $result;
636
+		} else {
637
+			return FALSE;
638
+		}
639
+	}
640
+
641
+	//--------------------------------------------------------------------
642
+
643
+	/**
644
+	 * Updates multiple records in the database at once.
645
+	 *
646
+	 * $data = array(
647
+	 *     array(
648
+	 *         'title'  => 'My title',
649
+	 *         'body'   => 'body 1'
650
+	 *     ),
651
+	 *     array(
652
+	 *         'title'  => 'Another Title',
653
+	 *         'body'   => 'body 2'
654
+	 *     )
655
+	 * );
656
+	 *
657
+	 * The $where_key should be the name of the column to match the record on.
658
+	 * If $where_key == 'title', then each record would be matched on that
659
+	 * 'title' value of the array. This does mean that the array key needs
660
+	 * to be provided with each row's data.
661
+	 *
662
+	 * @param  array $data An associate array of row data to update.
663
+	 * @param  string $where_key The column name to match on.
664
+	 * @return bool
665
+	 */
666
+	public function update_batch($data, $where_key)
667
+	{
668
+		foreach ($data as &$row) {
669
+			$row = $this->trigger('before_update', ['method' => 'update_batch', 'fields' => $row] );
670
+		}
671
+
672
+		$result = $this->db->update_batch($this->table_name, $data, $where_key);
673
+
674
+		foreach ($data as &$row) {
675
+			$this->trigger('after_update', ['fields' => $data, 'result' => $result, 'method' => 'update_batch']);
676
+		}
677
+
678
+		return $result;
679
+	}
680
+
681
+	//--------------------------------------------------------------------
682
+
683
+	/**
684
+	 * Updates many records by an array of ids.
685
+	 *
686
+	 * While update_batch() allows modifying multiple, arbitrary rows of data
687
+	 * on each row, update_many() sets the same values for each row.
688
+	 *
689
+	 * $ids = array(1, 2, 3, 5, 12);
690
+	 * $data = array(
691
+	 *     'deleted_by' => 1
692
+	 * );
693
+	 *
694
+	 * $this->model->update_many($ids, $data);
695
+	 *
696
+	 * @param  array $ids An array of primary_key values to update.
697
+	 * @param  array $data An array of value pairs to modify in each row.
698
+	 * @param  array $skip_validation If TRUE, will skip validation of data for this call only.
699
+	 * @return bool
700
+	 */
701
+	public function update_many($ids, $data, $skip_validation = null)
702
+	{
703
+		if (!is_array($ids) || count($ids) == 0) return NULL;
704
+
705
+		$skip_validation = is_null($skip_validation) ? $this->skip_validation : $skip_validation;
706
+
707
+		if ($skip_validation === FALSE) {
708
+			$data = $this->validate($data, 'update', $skip_validation);
709
+		}
710
+
711
+		$data = $this->trigger('before_update', ['ids' => $ids, 'method' => 'update_many', 'fields' => $data]);
712
+
713
+		// Will be false if it didn't validate.
714
+		if ($data !== FALSE) {
715
+			$this->db->where_in($this->primary_key, $ids);
716
+			$this->db->set($data);
717
+			$result = $this->db->update($this->table_name);
718
+
719
+			$this->trigger('after_update', ['ids' => $ids, 'fields' => $data, 'result'=>$result, 'method' => 'update_many']);
720
+
721
+			return $result;
722
+		} else {
723
+			return FALSE;
724
+		}
725
+	}
726
+
727
+	//--------------------------------------------------------------------
728
+
729
+	/**
730
+	 * Update records in the database using a standard WHERE clause.
731
+	 *
732
+	 * Your last parameter should be the $data array with values to update
733
+	 * on the rows. Any additional parameters should be provided to make up
734
+	 * a typical WHERE clause. This could be a single array, or a column name
735
+	 * and a value.
736
+	 *
737
+	 * $data = array('deleted_by' => 1);
738
+	 * $wheres = array('user_id' => 15);
739
+	 *
740
+	 * $this->update_by($wheres, $data);
741
+	 * $this->update_by('user_id', 15, $data);
742
+	 *
743
+	 * @param array $data An array of data pairs to update
744
+	 * @param one or more WHERE-acceptable entries.
745
+	 * @return bool
746
+	 */
747
+	public function update_by()
748
+	{
749
+		$args = func_get_args();
750
+		$data = array_pop($args);
751
+		$this->_set_where($args);
752
+
753
+		$data = $this->trigger('before_update', ['method' => 'update_by', 'fields' => $data]);
754
+
755
+		// Will be false if it didn't validate.
756
+		if ($this->validate($data) !== FALSE) {
757
+			$this->db->set( $this->prep_data($data) );
758
+			$result = $this->db->update($this->table_name);
759
+
760
+			$this->trigger('after_update', ['method' => 'update_by', 'fields' => $data, 'result' => $result] );
761
+
762
+			return $result;
763
+		} else {
764
+			return FALSE;
765
+		}
766
+	}
767
+
768
+	//--------------------------------------------------------------------
769
+
770
+	/**
771
+	 * Updates all records and sets the value pairs passed in the array.
772
+	 *
773
+	 * @param  array $data An array of value pairs with the data to change.
774
+	 * @param  array $skip_validation If TRUE, will skip validation of data for this call only.
775
+	 * @return bool
776
+	 */
777
+	public function update_all($data, $skip_validation = FALSE)
778
+	{
779
+		$data = $this->trigger('before_update', ['method' => 'update_all', 'fields' => $data] );
780
+
781
+		$skip_validation = is_null($skip_validation) ? $this->skip_validation : $skip_validation;
782
+
783
+		if ($skip_validation === FALSE) {
784
+			$data = $this->validate($data);
785
+		}
786
+
787
+		// Will be false if it didn't validate.
788
+		if ($data !== FALSE) {
789
+			$this->db->set( $this->prep_data($data) );
790
+			$result = $this->db->update($this->table_name);
791
+
792
+			$this->trigger('after_update', ['method' => 'update_all', 'fields' => $data, 'result' => $result] );
793
+
794
+			return $result;
795
+		} else {
796
+			return FALSE;
797
+		}
798
+	}
799
+
800
+	//--------------------------------------------------------------------
801
+
802
+	/**
803
+	 * Increments the value of field for a given row, selected by the
804
+	 * primary key for the table.
805
+	 *
806
+	 * @param $id
807
+	 * @param $field
808
+	 * @param int $value
809
+	 * @return mixed
810
+	 */
811
+	public function increment($id, $field, $value=1)
812
+	{
813
+		$value = (int)abs($value);
814
+
815
+		$this->db->where($this->primary_key, $id);
816
+		$this->db->set($field, "{$field}+{$value}", false);
817
+
818
+		return $this->db->update($this->table_name);
819
+	}
820
+
821
+	//--------------------------------------------------------------------
822
+
823
+	/**
824
+	 * Increments the value of field for a given row, selected by the
825
+	 * primary key for the table.
826
+	 *
827
+	 * @param $id
828
+	 * @param $field
829
+	 * @param int $value
830
+	 * @return mixed
831
+	 */
832
+	public function decrement($id, $field, $value=1)
833
+	{
834
+		$value = (int)abs($value);
835
+
836
+		$this->db->where($this->primary_key, $id);
837
+		$this->db->set($field, "{$field}-{$value}", false);
838
+
839
+		return $this->db->update($this->table_name);
840
+	}
841
+
842
+	//--------------------------------------------------------------------
843
+
844
+	/**
845
+	 * Deletes a row by it's primary key value.
846
+	 *
847
+	 * @param  mixed $id The primary key value of the row to delete.
848
+	 * @return bool
849
+	 */
850
+	public function delete($id)
851
+	{
852
+		$this->trigger('before_delete', ['id' => $id, 'method' => 'delete'] );
853
+
854
+		$this->db->where($this->primary_key, $id);
855
+
856
+		if ($this->soft_deletes) {
857
+			$sets = $this->log_user && is_object($this->authenticate)
858
+				? array($this->soft_delete_key => 1, $this->deleted_by_field => $this->authenticate->id())
859
+				: array($this->soft_delete_key => 1);
860
+
861
+			$result = $this->db->update($this->table_name, $sets);
862
+		} // Hard Delete
863
+		else {
864
+			$result = $this->db->delete($this->table_name);
865
+		}
866
+
867
+		$this->trigger('after_delete', ['id' => $id, 'method' => 'delete', 'result' => $result] );
868
+
869
+		return $result;
870
+	}
871
+
872
+	//--------------------------------------------------------------------
873
+
874
+	public function delete_by()
875
+	{
876
+		$where = func_get_args();
877
+		$this->_set_where($where);
878
+
879
+		$where = $this->trigger('before_delete', ['method' => 'delete_by', 'fields' => $where]);
880
+
881
+		if ($this->soft_deletes) {
882
+			$sets = $this->log_user && is_object($this->authenticate)
883
+				? array($this->soft_delete_key => 1, $this->deleted_by_field => $this->authenticate->id())
884
+				: array($this->soft_delete_key => 1);
885
+
886
+			$result = $this->db->update($this->table_name, $sets);
887
+		} else {
888
+			$result = $this->db->delete($this->table_name);
889
+		}
890
+
891
+		$this->trigger('after_delete', ['method' => 'delete_by', 'fields' => $where, 'result' => $result] );
892
+
893
+		return $result;
894
+	}
895
+
896
+	//--------------------------------------------------------------------
897
+
898
+	public function delete_many($ids)
899
+	{
900
+		if (!is_array($ids) || count($ids) == 0) return NULL;
901
+
902
+		$ids = $this->trigger('before_delete', ['ids' => $ids, 'method' => 'delete_many'] );
903
+
904
+		$this->db->where_in($this->primary_key, $ids);
905
+
906
+		if ($this->soft_deletes) {
907
+			$sets = $this->log_user && is_object($this->authenticate)
908
+				? array($this->soft_delete_key => 1, $this->deleted_by_field => $this->authenticate->id())
909
+				: array($this->soft_delete_key => 1);
910
+
911
+			$result = $this->db->update($this->table_name, $sets);
912
+		} else {
913
+			$result = $this->db->delete($this->table_name);
914
+		}
915
+
916
+		$this->trigger('after_delete', ['ids' => $ids, 'method' => 'delete_many', 'result' => $result]);
868 917
 
869
-        return $result;
870
-    }
918
+		return $result;
919
+	}
920
+
921
+	//--------------------------------------------------------------------
922
+
923
+	//--------------------------------------------------------------------
924
+	// Scope Methods
925
+	//--------------------------------------------------------------------
926
+
927
+	/**
928
+	 * Sets the value of the soft deletes flag.
929
+	 *
930
+	 * @param  boolean $val If TRUE, should perform a soft delete. If FALSE, a hard delete.
931
+	 */
932
+	public function soft_delete($val = TRUE)
933
+	{
934
+		$this->soft_deletes = $val;
871 935
 
872
-    //--------------------------------------------------------------------
873
-
874
-    public function delete_by()
875
-    {
876
-        $where = func_get_args();
877
-        $this->_set_where($where);
878
-
879
-        $where = $this->trigger('before_delete', ['method' => 'delete_by', 'fields' => $where]);
880
-
881
-        if ($this->soft_deletes) {
882
-            $sets = $this->log_user && is_object($this->authenticate)
883
-                ? array($this->soft_delete_key => 1, $this->deleted_by_field => $this->authenticate->id())
884
-                : array($this->soft_delete_key => 1);
885
-
886
-            $result = $this->db->update($this->table_name, $sets);
887
-        } else {
888
-            $result = $this->db->delete($this->table_name);
889
-        }
890
-
891
-        $this->trigger('after_delete', ['method' => 'delete_by', 'fields' => $where, 'result' => $result] );
892
-
893
-        return $result;
894
-    }
895
-
896
-    //--------------------------------------------------------------------
897
-
898
-    public function delete_many($ids)
899
-    {
900
-        if (!is_array($ids) || count($ids) == 0) return NULL;
901
-
902
-        $ids = $this->trigger('before_delete', ['ids' => $ids, 'method' => 'delete_many'] );
903
-
904
-        $this->db->where_in($this->primary_key, $ids);
905
-
906
-        if ($this->soft_deletes) {
907
-            $sets = $this->log_user && is_object($this->authenticate)
908
-                ? array($this->soft_delete_key => 1, $this->deleted_by_field => $this->authenticate->id())
909
-                : array($this->soft_delete_key => 1);
910
-
911
-            $result = $this->db->update($this->table_name, $sets);
912
-        } else {
913
-            $result = $this->db->delete($this->table_name);
914
-        }
915
-
916
-        $this->trigger('after_delete', ['ids' => $ids, 'method' => 'delete_many', 'result' => $result]);
917
-
918
-        return $result;
919
-    }
920
-
921
-    //--------------------------------------------------------------------
922
-
923
-    //--------------------------------------------------------------------
924
-    // Scope Methods
925
-    //--------------------------------------------------------------------
926
-
927
-    /**
928
-     * Sets the value of the soft deletes flag.
929
-     *
930
-     * @param  boolean $val If TRUE, should perform a soft delete. If FALSE, a hard delete.
931
-     */
932
-    public function soft_delete($val = TRUE)
933
-    {
934
-        $this->soft_deletes = $val;
935
-
936
-        return $this;
937
-    }
938
-
939
-    //--------------------------------------------------------------------
940
-
941
-    /**
942
-     * Temporarily sets our return type to an array.
943
-     */
944
-    public function as_array()
945
-    {
946
-        $this->temp_return_type = 'array';
947
-
948
-        return $this;
949
-    }
950
-
951
-    //--------------------------------------------------------------------
952
-
953
-    /**
954
-     * Temporarily sets our return type to an object.
955
-     *
956
-     * If $class is provided, the rows will be returned as objects that
957
-     * are instances of that class. $class MUST be an fully qualified
958
-     * class name, meaning that it must include the namespace, if applicable.
959
-     *
960
-     * @param string $class
961
-     * @return $this
962
-     */
963
-    public function as_object($class=null)
964
-    {
965
-        $this->temp_return_type = ! empty($class) ? $class : 'object';
966
-
967
-        return $this;
968
-    }
969
-
970
-    //--------------------------------------------------------------------
971
-
972
-    /**
973
-     * Also fetches deleted items for this request only.
974
-     */
975
-    public function with_deleted()
976
-    {
977
-        $this->temp_with_deleted = TRUE;
978
-
979
-        return $this;
980
-    }
981
-
982
-    //--------------------------------------------------------------------
983
-
984
-    /**
985
-     * Returns whether the current setup will return
986
-     * soft deleted rows.
987
-     *
988
-     * @return bool
989
-     */
990
-    public function get_with_deleted()
991
-    {
992
-        return $this->temp_with_deleted;
993
-    }
994
-
995
-    //--------------------------------------------------------------------
996
-
997
-
998
-    /**
999
-     * Sets the $skip_validation parameter.
1000
-     *
1001
-     * @param bool $skip
1002
-     * @return $this
1003
-     */
1004
-    public function skip_validation($skip = true)
1005
-    {
1006
-        $this->skip_validation = $skip;
1007
-
1008
-        return $this;
1009
-    }
1010
-
1011
-    //--------------------------------------------------------------------
1012
-
1013
-
1014
-    //--------------------------------------------------------------------
1015
-    // Utility Methods
1016
-    //--------------------------------------------------------------------
1017
-
1018
-    /**
1019
-     * Counts number of rows modified by an arbitrary WHERE call.
1020
-     * @return INT
1021
-     */
1022
-    public function count_by()
1023
-    {
1024
-        $where = func_get_args();
1025
-        $this->_set_where($where);
1026
-
1027
-        return $this->db->count_all_results($this->table_name);
1028
-    }
1029
-
1030
-    //--------------------------------------------------------------------
1031
-
1032
-    /**
1033
-     * Counts total number of records, disregarding any previous conditions.
1034
-     *
1035
-     * @return int
1036
-     */
1037
-    public function count_all()
1038
-    {
1039
-        return $this->db->count_all($this->table_name);
1040
-    }
1041
-
1042
-    //--------------------------------------------------------------------
1043
-
1044
-    /**
1045
-     * Getter for the table name.
1046
-     *
1047
-     * @return string The name of the table used by this class.
1048
-     */
1049
-    public function table()
1050
-    {
1051
-        return $this->table_name;
1052
-    }
1053
-
1054
-    //--------------------------------------------------------------------
1055
-
1056
-    /**
1057
-     * Set the return_insert_id value.
1058
-     *
1059
-     * @param  boolean $return If TRUE, insert will return the insert_id.
1060
-     */
1061
-    public function return_insert_id($return = true)
1062
-    {
1063
-        $this->return_insert_id = (bool)$return;
1064
-
1065
-        return $this;
1066
-    }
1067
-
1068
-    //--------------------------------------------------------------------
1069
-
1070
-    /**
1071
-     * A convenience method to return only a single field of the specified row.
1072
-     *
1073
-     * @param mixed $id The primary_key value to match against.
1074
-     * @param string $field The field to search for.
1075
-     *
1076
-     * @return bool|mixed The value of the field.
1077
-     */
1078
-    public function get_field($id = NULL, $field = '')
1079
-    {
1080
-        $this->db->select($field);
1081
-        $this->db->where($this->primary_key, $id);
1082
-        $query = $this->db->get($this->table_name);
1083
-
1084
-        if ($query && $query->num_rows() > 0) {
1085
-            return $query->row()->$field;
1086
-        }
1087
-
1088
-        return FALSE;
1089
-
1090
-    }
1091
-
1092
-    //---------------------------------------------------------------
1093
-
1094
-    /**
1095
-     * Checks whether a field/value pair exists within the table.
1096
-     *
1097
-     * @param string $field The field to search for.
1098
-     * @param string $value The value to match $field against.
1099
-     *
1100
-     * @return bool TRUE/FALSE
1101
-     */
1102
-    public function is_unique($field, $value)
1103
-    {
1104
-        $this->db->where($field, $value);
1105
-        $query = $this->db->get($this->table_name);
1106
-
1107
-        if ($query && $query->num_rows() == 0) {
1108
-            return TRUE;
1109
-        }
1110
-
1111
-        return FALSE;
1112
-
1113
-    }
1114
-
1115
-    //---------------------------------------------------------------
1116
-
1117
-    /**
1118
-     * Adds a field to the protected_attributes array.
1119
-     *
1120
-     * @param $field
1121
-     *
1122
-     * @return mixed
1123
-     */
1124
-    public function protect($field)
1125
-    {
1126
-        $this->protected_attributes[] = $field;
1127
-
1128
-        return $this;
1129
-    }
1130
-
1131
-    //--------------------------------------------------------------------
1132
-
1133
-    /**
1134
-     * Get the field names for this model's table.
1135
-     *
1136
-     * Returns the model's database fields stored in $this->fields
1137
-     * if set, else it tries to retrieve the field list from
1138
-     * $this->db->list_fields($this->table_name);
1139
-     *
1140
-     * @return array    Returns the database fields for this model
1141
-     */
1142
-    public function get_fields()
1143
-    {
1144
-        if (empty($this->fields)) {
1145
-            $this->fields = $this->db->list_fields($this->table_name);
1146
-        }
1147
-
1148
-        return $this->fields;
1149
-    }
1150
-
1151
-    //--------------------------------------------------------------------
1152
-
1153
-    /**
1154
-     * Extracts the model's fields (except the key and those handled by
1155
-     * Observers) from the $post_data and returns an array of name => value pairs
1156
-     *
1157
-     * @param Array $post_data The post data, usually $this->input->post() when called from the controller
1158
-     *
1159
-     * @return Array    An array of name => value pairs containing the data for the model's fields
1160
-     */
1161
-    public function prep_data($post_data)
1162
-    {
1163
-        $data = array();
1164
-        $skippedFields = array();
1165
-
1166
-        if (empty($post_data))
1167
-        {
1168
-            return [];
1169
-        }
1170
-
1171
-        // Though the model doesn't support multiple keys well, $this->key
1172
-        // could be an array or a string...
1173
-        $skippedFields = array_merge($skippedFields, (array)$this->primary_key);
1174
-
1175
-        // Remove any protected attributes
1176
-        $skippedFields = array_merge($skippedFields, $this->protected_attributes);
1177
-
1178
-        $fields = $this->get_fields();
1179
-
1180
-        // If the field is the primary key, one of the created/modified/deleted
1181
-        // fields, or has not been set in the $post_data, skip it
1182
-        foreach ($post_data as $field => $value) {
1183
-            if (in_array($field, $skippedFields) ||
1184
-                ! in_array($field, $fields))
1185
-            {
1186
-                continue;
1187
-            }
1188
-
1189
-            $data[$field] = $value;
1190
-        }
1191
-
1192
-        return $data;
1193
-    }
1194
-
1195
-    //--------------------------------------------------------------------
1196
-
1197
-    /**
1198
-     * Returns the last query string, if available. Simply a wrapper for
1199
-     * CodeIgniter's database method of the same name.
1200
-     *
1201
-     * @return string
1202
-     */
1203
-    public function last_query ()
1204
-    {
1205
-        return $this->db->last_query();
1206
-    }
1207
-
1208
-    //--------------------------------------------------------------------
1209
-
1210
-    /**
1211
-     * Returns the elapsed time for the last query that was executed, if
1212
-     * available, or NULL if not available, like if debug mode is off.
1213
-     *
1214
-     * @return mixed
1215
-     */
1216
-    public function last_query_time ()
1217
-    {
1218
-        $times = $this->db->query_times;
1219
-
1220
-        if (! is_array($this->db->query_times) || ! count($this->db->query_times))
1221
-        {
1222
-            return null;
1223
-        }
1224
-
1225
-        return end($times);
1226
-    }
1227
-
1228
-    //--------------------------------------------------------------------
1229
-
1230
-    //--------------------------------------------------------------------
1231
-    // Observers
1232
-    //--------------------------------------------------------------------
1233
-
1234
-    /**
1235
-     * Sets the created on date for the object based on the
1236
-     * current date/time and date_format. Will not overwrite existing.
1237
-     *
1238
-     * @param array $row The array of data to be inserted
1239
-     *
1240
-     * @return array
1241
-     */
1242
-    public function created_on($row)
1243
-    {
1244
-        if (empty($row['fields']))
1245
-        {
1246
-            return null;
1247
-        }
1248
-
1249
-        $row = $row['fields'];
1250
-
1251
-        // Created_on
1252
-        if (! array_key_exists($this->created_field, $row))
1253
-        {
1254
-            $row[$this->created_field] = $this->set_date();
1255
-        }
1256
-
1257
-        // Created by
1258
-        if ($this->log_user && ! array_key_exists($this->created_by_field, $row) && is_object($this->authenticate))
1259
-        {
1260
-            // If you're here because of an error with $this->authenticate
1261
-            // not being available, it's likely due to you not using
1262
-            // the AuthTrait and/or setting log_user after model is instantiated.
1263
-            $row[$this->created_by_field] = (int)$this->authenticate->id();
1264
-        }
1265
-
1266
-        return $row;
1267
-    } // end created_on()
1268
-
1269
-    //--------------------------------------------------------------------
1270
-
1271
-    /**
1272
-     * Sets the modified_on date for the object based on the
1273
-     * current date/time and date_format. Will not overwrite existing.
1274
-     *
1275
-     * @param array $row The array of data to be inserted
1276
-     *
1277
-     * @return array
1278
-     */
1279
-    public function modified_on($row)
1280
-    {
1281
-        if (empty($row['fields']))
1282
-        {
1283
-            return null;
1284
-        }
1285
-
1286
-        $row = $row['fields'];
1287
-
1288
-        if (is_array($row) && ! array_key_exists($this->modified_field, $row))
1289
-        {
1290
-            $row[$this->modified_field] = $this->set_date();
1291
-        }
1292
-
1293
-        // Modified by
1294
-        if ($this->log_user && ! array_key_exists($this->modified_by_field, $row) && is_object($this->authenticate))
1295
-        {
1296
-            // If you're here because of an error with $this->authenticate
1297
-            // not being available, it's likely due to you not using
1298
-            // the AuthTrait and/or setting log_user after model is instantiated.
1299
-            $row[$this->modified_by_field] = $this->authenticate->id();
1300
-        }
1301
-
1302
-        return $row;
1303
-    }
1304
-
1305
-    //--------------------------------------------------------------------
1306
-
1307
-    //--------------------------------------------------------------------
1308
-    // Internal Methods
1309
-    //--------------------------------------------------------------------
1310
-
1311
-    /**
1312
-     * Set WHERE parameters
1313
-     */
1314
-    protected function _set_where($params)
1315
-    {
1316
-        if (count($params) == 1) {
1317
-            $this->db->where($params[0]);
1318
-        } else {
1319
-            $this->db->where($params[0], $params[1]);
1320
-        }
1321
-    }
1322
-
1323
-    //--------------------------------------------------------------------
1324
-
1325
-    /**
1326
-     * Triggers a model-specific event and call each of it's observers.
1327
-     *
1328
-     * @param string $event The name of the event to trigger
1329
-     * @param mixed $data The data to be passed to the callback functions.
1330
-     *
1331
-     * @return mixed
1332
-     */
1333
-    public function trigger($event, $data = false)
1334
-    {
1335
-        if (! isset($this->$event) || ! is_array($this->$event))
1336
-        {
1337
-            if (isset($data['fields']))
1338
-            {
1339
-                return $data['fields'];
1340
-            }
1341
-
1342
-            return $data;
1343
-        }
1344
-
1345
-        foreach ($this->$event as $method)
1346
-        {
1347
-            if (strpos($method, '('))
1348
-            {
1349
-                preg_match('/([a-zA-Z0-9\_\-]+)(\(([a-zA-Z0-9\_\-\., ]+)\))?/', $method, $matches);
1350
-                $this->callback_parameters = explode(',', $matches[3]);
1351
-            }
1352
-
1353
-            $data = call_user_func_array(array($this, $method), array($data));
1354
-        }
1355
-
1356
-        // In case no method called or method returned
1357
-        // the entire data array, we typically just need the $fields
1358
-        if (isset($data['fields']))
1359
-        {
1360
-            return $data['fields'];
1361
-        }
1362
-
1363
-        // A few methods might need to return 'ids'
1364
-        if (isset($data['ids']))
1365
-        {
1366
-            return $data['ids'];
1367
-        }
1368
-
1369
-        return $data;
1370
-    }
1371
-
1372
-    //--------------------------------------------------------------------
1373
-
1374
-    /**
1375
-     * Validates the data passed into it based upon the form_validation rules
1376
-     * setup in the $this->validate property.
1377
-     *
1378
-     * If $type == 'insert', any additional rules in the class var $insert_validate_rules
1379
-     * for that field will be added to the rules.
1380
-     *
1381
-     * @param  array $data An array of validation rules
1382
-     * @param  string $type Either 'update' or 'insert'.
1383
-     * @return array/bool       The original data or FALSE
1384
-     */
1385
-    public function validate($data, $type = 'update', $skip_validation = null)
1386
-    {
1387
-        $skip_validation = is_null($skip_validation) ? $this->skip_validation : $skip_validation;
1388
-
1389
-        if ($skip_validation) {
1390
-            return $data;
1391
-        }
1392
-
1393
-        // We need the database to be loaded up at this point in case
1394
-        // we want to use callbacks that hit the database.
1395
-        if (empty($this->db))
1396
-        {
1397
-            $this->load->database();
1398
-        }
1399
-
1400
-        if (!empty($this->validation_rules)) {
1401
-            $this->form_validation->set_data($data);
1402
-
1403
-            if (is_array($this->validation_rules)) {
1404
-                // Any insert additions?
1405
-                if ($type == 'insert'
1406
-                    && !empty($this->insert_validate_rules)
1407
-                    && is_array($this->insert_validate_rules)
1408
-                ) {
1409
-                    foreach ($this->validation_rules as &$row) {
1410
-                        if (isset($this->insert_validate_rules[$row['field']])) {
1411
-                            $row ['rules'] .= '|' . $this->insert_validate_rules[$row['field']];
1412
-                        }
1413
-                    }
1414
-                }
1415
-
1416
-                $this->form_validation->set_rules($this->validation_rules);
1417
-
1418
-                if ($this->form_validation->run('', $this) === TRUE) {
1419
-                    return $data;
1420
-                } else {
1421
-                    return FALSE;
1422
-                }
1423
-            } else {
1424
-                if ($this->form_validation->run($this->validate, $this) === TRUE) {
1425
-                    return $data;
1426
-                } else {
1427
-                    return FALSE;
1428
-                }
1429
-            }
1430
-        } else {
1431
-            return $data;
1432
-        }
1433
-    }
1434
-
1435
-    //--------------------------------------------------------------------
1436
-
1437
-    /**
1438
-     * Protect attributes by removing them from $row array. Useful for
1439
-     * removing id, or submit buttons names if you simply throw your $_POST
1440
-     * array at your model. :)
1441
-     *
1442
-     * @param object /array $row The value pair item to remove.
1443
-     */
1444
-    public function protect_attributes($row)
1445
-    {
1446
-        foreach ($this->protected_attributes as $attr) {
1447
-            if (is_object($row)) {
1448
-                unset($row->$attr);
1449
-            } else {
1450
-                unset($row[$attr]);
1451
-            }
1452
-        }
1453
-
1454
-        return $row;
1455
-    }
1456
-
1457
-    //--------------------------------------------------------------------
1458
-
1459
-    /**
1460
-     * A utility function to allow child models to use the type of
1461
-     * date/time format that they prefer. This is primarily used for
1462
-     * setting created_on and modified_on values, but can be used by
1463
-     * inheriting classes.
1464
-     *
1465
-     * The available time formats are:
1466
-     * * 'int'      - Stores the date as an integer timestamp.
1467
-     * * 'datetime' - Stores the date and time in the SQL datetime format.
1468
-     * * 'date'     - Stores teh date (only) in the SQL date format.
1469
-     *
1470
-     * @param mixed $user_date An optional PHP timestamp to be converted.
1471
-     *
1472
-     * @access protected
1473
-     *
1474
-     * @return int|null|string The current/user time converted to the proper format.
1475
-     */
1476
-    protected function set_date($user_date = NULL)
1477
-    {
1478
-        $curr_date = !empty($user_date) ? $user_date : time();
1479
-
1480
-        switch ($this->date_format) {
1481
-            case 'int':
1482
-                return $curr_date;
1483
-                break;
1484
-            case 'datetime':
1485
-                return date('Y-m-d H:i:s', $curr_date);
1486
-                break;
1487
-            case 'date':
1488
-                return date('Y-m-d', $curr_date);
1489
-                break;
1490
-        }
1491
-
1492
-    }//end set_date()
1493
-
1494
-    //--------------------------------------------------------------------
1495
-
1496
-    /**
1497
-     * Returns an array containing the 'code' and 'message' of the
1498
-     * database's error, as provided by CI's database drivers.
1499
-     *
1500
-     * @return mixed
1501
-     */
1502
-    public function error($db_array_only=false)
1503
-    {
1504
-        // Send any validation errors if we have any.
1505
-        if (function_exists('validation_errors') && validation_errors() && ! $db_array_only)
1506
-        {
1507
-            return validation_errors();
1508
-        }
1509
-
1510
-        // No validation errors? Return the db error.
1511
-        $error = $this->db->error();
1512
-
1513
-        if ($db_array_only)
1514
-        {
1515
-            return $error;
1516
-        }
1517
-
1518
-        if (! empty($error['code']))
1519
-        {
1520
-            return "Database Error {$error['code']}: {$error['message']}.";
1521
-        }
1522
-
1523
-        // No errors found.
1524
-        return '';
1525
-    }
1526
-
1527
-    //--------------------------------------------------------------------
1528
-
1529
-    //--------------------------------------------------------------------
1530
-    // Magic Methods
1531
-    //--------------------------------------------------------------------
1532
-
1533
-    /**
1534
-     * __get magic
1535
-     *
1536
-     * Allows models to access CI's loaded classes using the same
1537
-     * syntax as controllers.
1538
-     *
1539
-     * This is the same as what CI's model uses, but we keep it
1540
-     * here since that's the ONLY thing that CI's model does.
1541
-     *
1542
-     * @param    string $key
1543
-     */
1544
-    public function __get($key)
1545
-    {
1546
-        // Give them first crack at any protected class vars
1547
-        if (isset($this->$key))
1548
-        {
1549
-            return $this->$key;
1550
-        }
1551
-
1552
-        // Debugging note:
1553
-        //	If you're here because you're getting an error message
1554
-        //	saying 'Undefined Property: system/core/Model.php', it's
1555
-        //	most likely a typo in your model code.
1556
-        return get_instance()->$key;
1557
-    }
1558
-
1559
-    //--------------------------------------------------------------------
1560
-
1561
-    /**
1562
-     * Provide direct access to any of CodeIgniter's DB methods but
1563
-     * make it look like it's part of the class, purely for convenience.
1564
-     *
1565
-     * @param $name
1566
-     * @param $params
1567
-     */
1568
-    public function __call($name, $params)
1569
-    {
1570
-        if (method_exists($this->db, $name))
1571
-        {
1572
-            call_user_func_array([$this->db, $name], $params);
1573
-            return $this;
1574
-        }
1575
-    }
1576
-
1577
-    //--------------------------------------------------------------------
936
+		return $this;
937
+	}
938
+
939
+	//--------------------------------------------------------------------
940
+
941
+	/**
942
+	 * Temporarily sets our return type to an array.
943
+	 */
944
+	public function as_array()
945
+	{
946
+		$this->temp_return_type = 'array';
947
+
948
+		return $this;
949
+	}
950
+
951
+	//--------------------------------------------------------------------
952
+
953
+	/**
954
+	 * Temporarily sets our return type to an object.
955
+	 *
956
+	 * If $class is provided, the rows will be returned as objects that
957
+	 * are instances of that class. $class MUST be an fully qualified
958
+	 * class name, meaning that it must include the namespace, if applicable.
959
+	 *
960
+	 * @param string $class
961
+	 * @return $this
962
+	 */
963
+	public function as_object($class=null)
964
+	{
965
+		$this->temp_return_type = ! empty($class) ? $class : 'object';
966
+
967
+		return $this;
968
+	}
969
+
970
+	//--------------------------------------------------------------------
971
+
972
+	/**
973
+	 * Also fetches deleted items for this request only.
974
+	 */
975
+	public function with_deleted()
976
+	{
977
+		$this->temp_with_deleted = TRUE;
978
+
979
+		return $this;
980
+	}
981
+
982
+	//--------------------------------------------------------------------
983
+
984
+	/**
985
+	 * Returns whether the current setup will return
986
+	 * soft deleted rows.
987
+	 *
988
+	 * @return bool
989
+	 */
990
+	public function get_with_deleted()
991
+	{
992
+		return $this->temp_with_deleted;
993
+	}
994
+
995
+	//--------------------------------------------------------------------
996
+
997
+
998
+	/**
999
+	 * Sets the $skip_validation parameter.
1000
+	 *
1001
+	 * @param bool $skip
1002
+	 * @return $this
1003
+	 */
1004
+	public function skip_validation($skip = true)
1005
+	{
1006
+		$this->skip_validation = $skip;
1007
+
1008
+		return $this;
1009
+	}
1010
+
1011
+	//--------------------------------------------------------------------
1012
+
1013
+
1014
+	//--------------------------------------------------------------------
1015
+	// Utility Methods
1016
+	//--------------------------------------------------------------------
1017
+
1018
+	/**
1019
+	 * Counts number of rows modified by an arbitrary WHERE call.
1020
+	 * @return INT
1021
+	 */
1022
+	public function count_by()
1023
+	{
1024
+		$where = func_get_args();
1025
+		$this->_set_where($where);
1026
+
1027
+		return $this->db->count_all_results($this->table_name);
1028
+	}
1029
+
1030
+	//--------------------------------------------------------------------
1031
+
1032
+	/**
1033
+	 * Counts total number of records, disregarding any previous conditions.
1034
+	 *
1035
+	 * @return int
1036
+	 */
1037
+	public function count_all()
1038
+	{
1039
+		return $this->db->count_all($this->table_name);
1040
+	}
1041
+
1042
+	//--------------------------------------------------------------------
1043
+
1044
+	/**
1045
+	 * Getter for the table name.
1046
+	 *
1047
+	 * @return string The name of the table used by this class.
1048
+	 */
1049
+	public function table()
1050
+	{
1051
+		return $this->table_name;
1052
+	}
1053
+
1054
+	//--------------------------------------------------------------------
1055
+
1056
+	/**
1057
+	 * Set the return_insert_id value.
1058
+	 *
1059
+	 * @param  boolean $return If TRUE, insert will return the insert_id.
1060
+	 */
1061
+	public function return_insert_id($return = true)
1062
+	{
1063
+		$this->return_insert_id = (bool)$return;
1064
+
1065
+		return $this;
1066
+	}
1067
+
1068
+	//--------------------------------------------------------------------
1069
+
1070
+	/**
1071
+	 * A convenience method to return only a single field of the specified row.
1072
+	 *
1073
+	 * @param mixed $id The primary_key value to match against.
1074
+	 * @param string $field The field to search for.
1075
+	 *
1076
+	 * @return bool|mixed The value of the field.
1077
+	 */
1078
+	public function get_field($id = NULL, $field = '')
1079
+	{
1080
+		$this->db->select($field);
1081
+		$this->db->where($this->primary_key, $id);
1082
+		$query = $this->db->get($this->table_name);
1083
+
1084
+		if ($query && $query->num_rows() > 0) {
1085
+			return $query->row()->$field;
1086
+		}
1087
+
1088
+		return FALSE;
1089
+
1090
+	}
1091
+
1092
+	//---------------------------------------------------------------
1093
+
1094
+	/**
1095
+	 * Checks whether a field/value pair exists within the table.
1096
+	 *
1097
+	 * @param string $field The field to search for.
1098
+	 * @param string $value The value to match $field against.
1099
+	 *
1100
+	 * @return bool TRUE/FALSE
1101
+	 */
1102
+	public function is_unique($field, $value)
1103
+	{
1104
+		$this->db->where($field, $value);
1105
+		$query = $this->db->get($this->table_name);
1106
+
1107
+		if ($query && $query->num_rows() == 0) {
1108
+			return TRUE;
1109
+		}
1110
+
1111
+		return FALSE;
1112
+
1113
+	}
1114
+
1115
+	//---------------------------------------------------------------
1116
+
1117
+	/**
1118
+	 * Adds a field to the protected_attributes array.
1119
+	 *
1120
+	 * @param $field
1121
+	 *
1122
+	 * @return mixed
1123
+	 */
1124
+	public function protect($field)
1125
+	{
1126
+		$this->protected_attributes[] = $field;
1127
+
1128
+		return $this;
1129
+	}
1130
+
1131
+	//--------------------------------------------------------------------
1132
+
1133
+	/**
1134
+	 * Get the field names for this model's table.
1135
+	 *
1136
+	 * Returns the model's database fields stored in $this->fields
1137
+	 * if set, else it tries to retrieve the field list from
1138
+	 * $this->db->list_fields($this->table_name);
1139
+	 *
1140
+	 * @return array    Returns the database fields for this model
1141
+	 */
1142
+	public function get_fields()
1143
+	{
1144
+		if (empty($this->fields)) {
1145
+			$this->fields = $this->db->list_fields($this->table_name);
1146
+		}
1147
+
1148
+		return $this->fields;
1149
+	}
1150
+
1151
+	//--------------------------------------------------------------------
1152
+
1153
+	/**
1154
+	 * Extracts the model's fields (except the key and those handled by
1155
+	 * Observers) from the $post_data and returns an array of name => value pairs
1156
+	 *
1157
+	 * @param Array $post_data The post data, usually $this->input->post() when called from the controller
1158
+	 *
1159
+	 * @return Array    An array of name => value pairs containing the data for the model's fields
1160
+	 */
1161
+	public function prep_data($post_data)
1162
+	{
1163
+		$data = array();
1164
+		$skippedFields = array();
1165
+
1166
+		if (empty($post_data))
1167
+		{
1168
+			return [];
1169
+		}
1170
+
1171
+		// Though the model doesn't support multiple keys well, $this->key
1172
+		// could be an array or a string...
1173
+		$skippedFields = array_merge($skippedFields, (array)$this->primary_key);
1174
+
1175
+		// Remove any protected attributes
1176
+		$skippedFields = array_merge($skippedFields, $this->protected_attributes);
1177
+
1178
+		$fields = $this->get_fields();
1179
+
1180
+		// If the field is the primary key, one of the created/modified/deleted
1181
+		// fields, or has not been set in the $post_data, skip it
1182
+		foreach ($post_data as $field => $value) {
1183
+			if (in_array($field, $skippedFields) ||
1184
+				! in_array($field, $fields))
1185
+			{
1186
+				continue;
1187
+			}
1188
+
1189
+			$data[$field] = $value;
1190
+		}
1191
+
1192
+		return $data;
1193
+	}
1194
+
1195
+	//--------------------------------------------------------------------
1196
+
1197
+	/**
1198
+	 * Returns the last query string, if available. Simply a wrapper for
1199
+	 * CodeIgniter's database method of the same name.
1200
+	 *
1201
+	 * @return string
1202
+	 */
1203
+	public function last_query ()
1204
+	{
1205
+		return $this->db->last_query();
1206
+	}
1207
+
1208
+	//--------------------------------------------------------------------
1209
+
1210
+	/**
1211
+	 * Returns the elapsed time for the last query that was executed, if
1212
+	 * available, or NULL if not available, like if debug mode is off.
1213
+	 *
1214
+	 * @return mixed
1215
+	 */
1216
+	public function last_query_time ()
1217
+	{
1218
+		$times = $this->db->query_times;
1219
+
1220
+		if (! is_array($this->db->query_times) || ! count($this->db->query_times))
1221
+		{
1222
+			return null;
1223
+		}
1224
+
1225
+		return end($times);
1226
+	}
1227
+
1228
+	//--------------------------------------------------------------------
1229
+
1230
+	//--------------------------------------------------------------------
1231
+	// Observers
1232
+	//--------------------------------------------------------------------
1233
+
1234
+	/**
1235
+	 * Sets the created on date for the object based on the
1236
+	 * current date/time and date_format. Will not overwrite existing.
1237
+	 *
1238
+	 * @param array $row The array of data to be inserted
1239
+	 *
1240
+	 * @return array
1241
+	 */
1242
+	public function created_on($row)
1243
+	{
1244
+		if (empty($row['fields']))
1245
+		{
1246
+			return null;
1247
+		}
1248
+
1249
+		$row = $row['fields'];
1250
+
1251
+		// Created_on
1252
+		if (! array_key_exists($this->created_field, $row))
1253
+		{
1254
+			$row[$this->created_field] = $this->set_date();
1255
+		}
1256
+
1257
+		// Created by
1258
+		if ($this->log_user && ! array_key_exists($this->created_by_field, $row) && is_object($this->authenticate))
1259
+		{
1260
+			// If you're here because of an error with $this->authenticate
1261
+			// not being available, it's likely due to you not using
1262
+			// the AuthTrait and/or setting log_user after model is instantiated.
1263
+			$row[$this->created_by_field] = (int)$this->authenticate->id();
1264
+		}
1265
+
1266
+		return $row;
1267
+	} // end created_on()
1268
+
1269
+	//--------------------------------------------------------------------
1270
+
1271
+	/**
1272
+	 * Sets the modified_on date for the object based on the
1273
+	 * current date/time and date_format. Will not overwrite existing.
1274
+	 *
1275
+	 * @param array $row The array of data to be inserted
1276
+	 *
1277
+	 * @return array
1278
+	 */
1279
+	public function modified_on($row)
1280
+	{
1281
+		if (empty($row['fields']))
1282
+		{
1283
+			return null;
1284
+		}
1285
+
1286
+		$row = $row['fields'];
1287
+
1288
+		if (is_array($row) && ! array_key_exists($this->modified_field, $row))
1289
+		{
1290
+			$row[$this->modified_field] = $this->set_date();
1291
+		}
1292
+
1293
+		// Modified by
1294
+		if ($this->log_user && ! array_key_exists($this->modified_by_field, $row) && is_object($this->authenticate))
1295
+		{
1296
+			// If you're here because of an error with $this->authenticate
1297
+			// not being available, it's likely due to you not using
1298
+			// the AuthTrait and/or setting log_user after model is instantiated.
1299
+			$row[$this->modified_by_field] = $this->authenticate->id();
1300
+		}
1301
+
1302
+		return $row;
1303
+	}
1304
+
1305
+	//--------------------------------------------------------------------
1306
+
1307
+	//--------------------------------------------------------------------
1308
+	// Internal Methods
1309
+	//--------------------------------------------------------------------
1310
+
1311
+	/**
1312
+	 * Set WHERE parameters
1313
+	 */
1314
+	protected function _set_where($params)
1315
+	{
1316
+		if (count($params) == 1) {
1317
+			$this->db->where($params[0]);
1318
+		} else {
1319
+			$this->db->where($params[0], $params[1]);
1320
+		}
1321
+	}
1322
+
1323
+	//--------------------------------------------------------------------
1324
+
1325
+	/**
1326
+	 * Triggers a model-specific event and call each of it's observers.
1327
+	 *
1328
+	 * @param string $event The name of the event to trigger
1329
+	 * @param mixed $data The data to be passed to the callback functions.
1330
+	 *
1331
+	 * @return mixed
1332
+	 */
1333
+	public function trigger($event, $data = false)
1334
+	{
1335
+		if (! isset($this->$event) || ! is_array($this->$event))
1336
+		{
1337
+			if (isset($data['fields']))
1338
+			{
1339
+				return $data['fields'];
1340
+			}
1341
+
1342
+			return $data;
1343
+		}
1344
+
1345
+		foreach ($this->$event as $method)
1346
+		{
1347
+			if (strpos($method, '('))
1348
+			{
1349
+				preg_match('/([a-zA-Z0-9\_\-]+)(\(([a-zA-Z0-9\_\-\., ]+)\))?/', $method, $matches);
1350
+				$this->callback_parameters = explode(',', $matches[3]);
1351
+			}
1352
+
1353
+			$data = call_user_func_array(array($this, $method), array($data));
1354
+		}
1355
+
1356
+		// In case no method called or method returned
1357
+		// the entire data array, we typically just need the $fields
1358
+		if (isset($data['fields']))
1359
+		{
1360
+			return $data['fields'];
1361
+		}
1362
+
1363
+		// A few methods might need to return 'ids'
1364
+		if (isset($data['ids']))
1365
+		{
1366
+			return $data['ids'];
1367
+		}
1368
+
1369
+		return $data;
1370
+	}
1371
+
1372
+	//--------------------------------------------------------------------
1373
+
1374
+	/**
1375
+	 * Validates the data passed into it based upon the form_validation rules
1376
+	 * setup in the $this->validate property.
1377
+	 *
1378
+	 * If $type == 'insert', any additional rules in the class var $insert_validate_rules
1379
+	 * for that field will be added to the rules.
1380
+	 *
1381
+	 * @param  array $data An array of validation rules
1382
+	 * @param  string $type Either 'update' or 'insert'.
1383
+	 * @return array/bool       The original data or FALSE
1384
+	 */
1385
+	public function validate($data, $type = 'update', $skip_validation = null)
1386
+	{
1387
+		$skip_validation = is_null($skip_validation) ? $this->skip_validation : $skip_validation;
1388
+
1389
+		if ($skip_validation) {
1390
+			return $data;
1391
+		}
1392
+
1393
+		// We need the database to be loaded up at this point in case
1394
+		// we want to use callbacks that hit the database.
1395
+		if (empty($this->db))
1396
+		{
1397
+			$this->load->database();
1398
+		}
1399
+
1400
+		if (!empty($this->validation_rules)) {
1401
+			$this->form_validation->set_data($data);
1402
+
1403
+			if (is_array($this->validation_rules)) {
1404
+				// Any insert additions?
1405
+				if ($type == 'insert'
1406
+					&& !empty($this->insert_validate_rules)
1407
+					&& is_array($this->insert_validate_rules)
1408
+				) {
1409
+					foreach ($this->validation_rules as &$row) {
1410
+						if (isset($this->insert_validate_rules[$row['field']])) {
1411
+							$row ['rules'] .= '|' . $this->insert_validate_rules[$row['field']];
1412
+						}
1413
+					}
1414
+				}
1415
+
1416
+				$this->form_validation->set_rules($this->validation_rules);
1417
+
1418
+				if ($this->form_validation->run('', $this) === TRUE) {
1419
+					return $data;
1420
+				} else {
1421
+					return FALSE;
1422
+				}
1423
+			} else {
1424
+				if ($this->form_validation->run($this->validate, $this) === TRUE) {
1425
+					return $data;
1426
+				} else {
1427
+					return FALSE;
1428
+				}
1429
+			}
1430
+		} else {
1431
+			return $data;
1432
+		}
1433
+	}
1434
+
1435
+	//--------------------------------------------------------------------
1436
+
1437
+	/**
1438
+	 * Protect attributes by removing them from $row array. Useful for
1439
+	 * removing id, or submit buttons names if you simply throw your $_POST
1440
+	 * array at your model. :)
1441
+	 *
1442
+	 * @param object /array $row The value pair item to remove.
1443
+	 */
1444
+	public function protect_attributes($row)
1445
+	{
1446
+		foreach ($this->protected_attributes as $attr) {
1447
+			if (is_object($row)) {
1448
+				unset($row->$attr);
1449
+			} else {
1450
+				unset($row[$attr]);
1451
+			}
1452
+		}
1453
+
1454
+		return $row;
1455
+	}
1456
+
1457
+	//--------------------------------------------------------------------
1458
+
1459
+	/**
1460
+	 * A utility function to allow child models to use the type of
1461
+	 * date/time format that they prefer. This is primarily used for
1462
+	 * setting created_on and modified_on values, but can be used by
1463
+	 * inheriting classes.
1464
+	 *
1465
+	 * The available time formats are:
1466
+	 * * 'int'      - Stores the date as an integer timestamp.
1467
+	 * * 'datetime' - Stores the date and time in the SQL datetime format.
1468
+	 * * 'date'     - Stores teh date (only) in the SQL date format.
1469
+	 *
1470
+	 * @param mixed $user_date An optional PHP timestamp to be converted.
1471
+	 *
1472
+	 * @access protected
1473
+	 *
1474
+	 * @return int|null|string The current/user time converted to the proper format.
1475
+	 */
1476
+	protected function set_date($user_date = NULL)
1477
+	{
1478
+		$curr_date = !empty($user_date) ? $user_date : time();
1479
+
1480
+		switch ($this->date_format) {
1481
+			case 'int':
1482
+				return $curr_date;
1483
+				break;
1484
+			case 'datetime':
1485
+				return date('Y-m-d H:i:s', $curr_date);
1486
+				break;
1487
+			case 'date':
1488
+				return date('Y-m-d', $curr_date);
1489
+				break;
1490
+		}
1491
+
1492
+	}//end set_date()
1493
+
1494
+	//--------------------------------------------------------------------
1495
+
1496
+	/**
1497
+	 * Returns an array containing the 'code' and 'message' of the
1498
+	 * database's error, as provided by CI's database drivers.
1499
+	 *
1500
+	 * @return mixed
1501
+	 */
1502
+	public function error($db_array_only=false)
1503
+	{
1504
+		// Send any validation errors if we have any.
1505
+		if (function_exists('validation_errors') && validation_errors() && ! $db_array_only)
1506
+		{
1507
+			return validation_errors();
1508
+		}
1509
+
1510
+		// No validation errors? Return the db error.
1511
+		$error = $this->db->error();
1512
+
1513
+		if ($db_array_only)
1514
+		{
1515
+			return $error;
1516
+		}
1517
+
1518
+		if (! empty($error['code']))
1519
+		{
1520
+			return "Database Error {$error['code']}: {$error['message']}.";
1521
+		}
1522
+
1523
+		// No errors found.
1524
+		return '';
1525
+	}
1526
+
1527
+	//--------------------------------------------------------------------
1528
+
1529
+	//--------------------------------------------------------------------
1530
+	// Magic Methods
1531
+	//--------------------------------------------------------------------
1532
+
1533
+	/**
1534
+	 * __get magic
1535
+	 *
1536
+	 * Allows models to access CI's loaded classes using the same
1537
+	 * syntax as controllers.
1538
+	 *
1539
+	 * This is the same as what CI's model uses, but we keep it
1540
+	 * here since that's the ONLY thing that CI's model does.
1541
+	 *
1542
+	 * @param    string $key
1543
+	 */
1544
+	public function __get($key)
1545
+	{
1546
+		// Give them first crack at any protected class vars
1547
+		if (isset($this->$key))
1548
+		{
1549
+			return $this->$key;
1550
+		}
1551
+
1552
+		// Debugging note:
1553
+		//	If you're here because you're getting an error message
1554
+		//	saying 'Undefined Property: system/core/Model.php', it's
1555
+		//	most likely a typo in your model code.
1556
+		return get_instance()->$key;
1557
+	}
1558
+
1559
+	//--------------------------------------------------------------------
1560
+
1561
+	/**
1562
+	 * Provide direct access to any of CodeIgniter's DB methods but
1563
+	 * make it look like it's part of the class, purely for convenience.
1564
+	 *
1565
+	 * @param $name
1566
+	 * @param $params
1567
+	 */
1568
+	public function __call($name, $params)
1569
+	{
1570
+		if (method_exists($this->db, $name))
1571
+		{
1572
+			call_user_func_array([$this->db, $name], $params);
1573
+			return $this;
1574
+		}
1575
+	}
1576
+
1577
+	//--------------------------------------------------------------------
1578 1578
 
1579 1579
 
1580 1580
 }
Please login to merge, or discard this patch.
myth/CIModules/auth/controllers/Auth.php 1 patch
Indentation   +234 added lines, -234 removed lines patch added patch discarded remove patch
@@ -36,225 +36,225 @@  discard block
 block discarded – undo
36 36
 class Auth extends \Myth\Controllers\ThemedController
37 37
 {
38 38
 
39
-    public function __construct()
40
-    {
41
-        parent::__construct();
42
-
43
-        $this->config->load('auth');
44
-        $this->lang->load('auth');
45
-        $this->load->library('session');
46
-    }
47
-
48
-    //--------------------------------------------------------------------
49
-
50
-    public function login()
51
-    {
52
-        $this->load->helper('form');
53
-
54
-        $auth = new LocalAuthentication();
55
-        $this->load->model('user_model');
56
-        $auth->useModel($this->user_model);
57
-
58
-        $redirect_url = $this->session->userdata('redirect_url');
59
-
60
-        // No need to login again if they are already logged in...
61
-        if ($auth->isLoggedIn())
62
-        {
63
-            unset($_SESSION['redirect_url']);
64
-            redirect($redirect_url);
65
-        }
66
-
67
-        if ($this->input->post())
68
-        {
69
-            $post_data = [
70
-                'email'    => $this->input->post('email'),
71
-                'password' => $this->input->post('password')
72
-            ];
73
-
74
-            $remember = (bool)$this->input->post('remember');
75
-
76
-            if ($auth->login($post_data, $remember))
77
-            {
78
-	            // Is the user being forced to reset their password?
79
-	            if ($auth->user()['force_pass_reset'] == 1)
80
-	            {
81
-		            redirect( Route::named('change_pass') );
82
-	            }
83
-
84
-                unset($_SESSION['redirect_url']);
85
-                $this->setMessage(lang('auth.did_login'), 'success');
86
-                redirect($redirect_url);
87
-            }
88
-
89
-            $this->setMessage(lang('auth.invalid_user'), 'danger');
90
-        }
91
-
92
-        $this->themer->setLayout('login');
93
-        $this->render();
94
-    }
95
-
96
-    //--------------------------------------------------------------------
97
-
98
-    public function logout()
99
-    {
100
-        $auth = new LocalAuthentication();
101
-        $this->load->model('user_model');
102
-        $auth->useModel($this->user_model);
103
-
104
-        if ($auth->isLoggedIn())
105
-        {
106
-            $auth->logout();
107
-
108
-            $this->setMessage(lang('auth.did_logout'), 'success');
109
-        }
110
-
111
-        redirect('/');
112
-    }
113
-
114
-    //--------------------------------------------------------------------
115
-
116
-    public function register()
117
-    {
118
-        $this->load->helper('form');
119
-
120
-        if ($this->input->post())
121
-        {
122
-            $auth = new LocalAuthentication();
123
-            $this->load->model('user_model');
124
-            $auth->useModel($this->user_model);
125
-
126
-            $post_data = [
127
-                'first_name'   => $this->input->post('first_name'),
128
-                'last_name'    => $this->input->post('last_name'),
129
-                'email'        => $this->input->post('email'),
130
-                'username'     => $this->input->post('username'),
131
-                'password'     => $this->input->post('password'),
132
-                'pass_confirm' => $this->input->post('pass_confirm')
133
-            ];
134
-
135
-            if ($auth->registerUser($post_data))
136
-            {
137
-                $this->setMessage(lang('auth.did_register'), 'success');
138
-                redirect( Route::named('login') );
139
-            }
140
-            else
141
-            {
142
-                $this->setMessage($auth->error(), 'danger');
143
-            }
144
-        }
145
-
146
-        $this->addScript('register.js');
147
-        $this->themer->setLayout('login');
148
-        $this->render();
149
-    }
150
-
151
-    //--------------------------------------------------------------------
152
-
153
-    public function activate_user()
154
-    {
155
-        $this->load->helper('form');
156
-
157
-        if ($this->input->post())
158
-        {
159
-            $auth = new LocalAuthentication();
160
-            $this->load->model('user_model');
161
-            $auth->useModel($this->user_model);
162
-
163
-            $post_data = [
164
-                  'email' => $this->input->post('email'),
165
-                  'code'  => $this->input->post('code')
166
-            ];
167
-
168
-            if ($auth->activateUser($post_data))
169
-            {
170
-                $this->setMessage(lang('auth.did_activate'), 'success');
171
-                redirect( Route::named('login') );
172
-            }
173
-            else
174
-            {
175
-                $this->setMessage($auth->error(), 'danger');
176
-            }
177
-        }
178
-
179
-        $data = [
180
-            'email' => $this->input->get('e'),
181
-            'code'  => $this->input->get('code')
182
-        ];
183
-
184
-        $this->themer->setLayout('login');
185
-        $this->render($data);
186
-    }
187
-
188
-    //--------------------------------------------------------------------
189
-
190
-
191
-    public function forgot_password()
192
-    {
193
-        $this->load->helper('form');
194
-
195
-        if ($this->input->post())
196
-        {
197
-            $auth = new LocalAuthentication();
198
-            $this->load->model('user_model');
199
-            $auth->useModel($this->user_model);
200
-
201
-            if ($auth->remindUser($this->input->post('email')))
202
-            {
203
-                $this->setMessage(lang('auth.send_success'), 'success');
204
-                redirect( Route::named('reset_pass') );
205
-            }
206
-            else
207
-            {
208
-                $this->setMessage($auth->error(), 'danger');
209
-            }
210
-        }
211
-
212
-        $this->themer->setLayout('login');
213
-        $this->render();
214
-    }
215
-
216
-    //--------------------------------------------------------------------
217
-
218
-    public function reset_password()
219
-    {
220
-        $this->load->helper('form');
221
-
222
-        if ($this->input->post())
223
-        {
224
-            $auth = new LocalAuthentication();
225
-            $this->load->model('user_model');
226
-            $auth->useModel($this->user_model);
227
-
228
-            $credentials = [
229
-                'email' => $this->input->post('email'),
230
-                'code'  => $this->input->post('code')
231
-            ];
232
-
233
-            $password     = $this->input->post('password');
234
-            $pass_confirm = $this->input->post('pass_confirm');
235
-
236
-            if ($auth->resetPassword($credentials, $password, $pass_confirm))
237
-            {
238
-                $this->setMessage(lang('auth.new_password_success'), 'success');
239
-                redirect( Route::named('login') );
240
-            }
241
-            else
242
-            {
243
-                $this->setMessage($auth->error(), 'danger');
244
-            }
245
-        }
246
-
247
-        $data = [
248
-            'email' => $this->input->get('e'),
249
-            'code'  => $this->input->get('code')
250
-        ];
251
-
252
-        $this->addScript('register.js');
253
-        $this->themer->setLayout('login');
254
-        $this->render($data);
255
-    }
256
-
257
-    //--------------------------------------------------------------------
39
+	public function __construct()
40
+	{
41
+		parent::__construct();
42
+
43
+		$this->config->load('auth');
44
+		$this->lang->load('auth');
45
+		$this->load->library('session');
46
+	}
47
+
48
+	//--------------------------------------------------------------------
49
+
50
+	public function login()
51
+	{
52
+		$this->load->helper('form');
53
+
54
+		$auth = new LocalAuthentication();
55
+		$this->load->model('user_model');
56
+		$auth->useModel($this->user_model);
57
+
58
+		$redirect_url = $this->session->userdata('redirect_url');
59
+
60
+		// No need to login again if they are already logged in...
61
+		if ($auth->isLoggedIn())
62
+		{
63
+			unset($_SESSION['redirect_url']);
64
+			redirect($redirect_url);
65
+		}
66
+
67
+		if ($this->input->post())
68
+		{
69
+			$post_data = [
70
+				'email'    => $this->input->post('email'),
71
+				'password' => $this->input->post('password')
72
+			];
73
+
74
+			$remember = (bool)$this->input->post('remember');
75
+
76
+			if ($auth->login($post_data, $remember))
77
+			{
78
+				// Is the user being forced to reset their password?
79
+				if ($auth->user()['force_pass_reset'] == 1)
80
+				{
81
+					redirect( Route::named('change_pass') );
82
+				}
83
+
84
+				unset($_SESSION['redirect_url']);
85
+				$this->setMessage(lang('auth.did_login'), 'success');
86
+				redirect($redirect_url);
87
+			}
88
+
89
+			$this->setMessage(lang('auth.invalid_user'), 'danger');
90
+		}
91
+
92
+		$this->themer->setLayout('login');
93
+		$this->render();
94
+	}
95
+
96
+	//--------------------------------------------------------------------
97
+
98
+	public function logout()
99
+	{
100
+		$auth = new LocalAuthentication();
101
+		$this->load->model('user_model');
102
+		$auth->useModel($this->user_model);
103
+
104
+		if ($auth->isLoggedIn())
105
+		{
106
+			$auth->logout();
107
+
108
+			$this->setMessage(lang('auth.did_logout'), 'success');
109
+		}
110
+
111
+		redirect('/');
112
+	}
113
+
114
+	//--------------------------------------------------------------------
115
+
116
+	public function register()
117
+	{
118
+		$this->load->helper('form');
119
+
120
+		if ($this->input->post())
121
+		{
122
+			$auth = new LocalAuthentication();
123
+			$this->load->model('user_model');
124
+			$auth->useModel($this->user_model);
125
+
126
+			$post_data = [
127
+				'first_name'   => $this->input->post('first_name'),
128
+				'last_name'    => $this->input->post('last_name'),
129
+				'email'        => $this->input->post('email'),
130
+				'username'     => $this->input->post('username'),
131
+				'password'     => $this->input->post('password'),
132
+				'pass_confirm' => $this->input->post('pass_confirm')
133
+			];
134
+
135
+			if ($auth->registerUser($post_data))
136
+			{
137
+				$this->setMessage(lang('auth.did_register'), 'success');
138
+				redirect( Route::named('login') );
139
+			}
140
+			else
141
+			{
142
+				$this->setMessage($auth->error(), 'danger');
143
+			}
144
+		}
145
+
146
+		$this->addScript('register.js');
147
+		$this->themer->setLayout('login');
148
+		$this->render();
149
+	}
150
+
151
+	//--------------------------------------------------------------------
152
+
153
+	public function activate_user()
154
+	{
155
+		$this->load->helper('form');
156
+
157
+		if ($this->input->post())
158
+		{
159
+			$auth = new LocalAuthentication();
160
+			$this->load->model('user_model');
161
+			$auth->useModel($this->user_model);
162
+
163
+			$post_data = [
164
+				  'email' => $this->input->post('email'),
165
+				  'code'  => $this->input->post('code')
166
+			];
167
+
168
+			if ($auth->activateUser($post_data))
169
+			{
170
+				$this->setMessage(lang('auth.did_activate'), 'success');
171
+				redirect( Route::named('login') );
172
+			}
173
+			else
174
+			{
175
+				$this->setMessage($auth->error(), 'danger');
176
+			}
177
+		}
178
+
179
+		$data = [
180
+			'email' => $this->input->get('e'),
181
+			'code'  => $this->input->get('code')
182
+		];
183
+
184
+		$this->themer->setLayout('login');
185
+		$this->render($data);
186
+	}
187
+
188
+	//--------------------------------------------------------------------
189
+
190
+
191
+	public function forgot_password()
192
+	{
193
+		$this->load->helper('form');
194
+
195
+		if ($this->input->post())
196
+		{
197
+			$auth = new LocalAuthentication();
198
+			$this->load->model('user_model');
199
+			$auth->useModel($this->user_model);
200
+
201
+			if ($auth->remindUser($this->input->post('email')))
202
+			{
203
+				$this->setMessage(lang('auth.send_success'), 'success');
204
+				redirect( Route::named('reset_pass') );
205
+			}
206
+			else
207
+			{
208
+				$this->setMessage($auth->error(), 'danger');
209
+			}
210
+		}
211
+
212
+		$this->themer->setLayout('login');
213
+		$this->render();
214
+	}
215
+
216
+	//--------------------------------------------------------------------
217
+
218
+	public function reset_password()
219
+	{
220
+		$this->load->helper('form');
221
+
222
+		if ($this->input->post())
223
+		{
224
+			$auth = new LocalAuthentication();
225
+			$this->load->model('user_model');
226
+			$auth->useModel($this->user_model);
227
+
228
+			$credentials = [
229
+				'email' => $this->input->post('email'),
230
+				'code'  => $this->input->post('code')
231
+			];
232
+
233
+			$password     = $this->input->post('password');
234
+			$pass_confirm = $this->input->post('pass_confirm');
235
+
236
+			if ($auth->resetPassword($credentials, $password, $pass_confirm))
237
+			{
238
+				$this->setMessage(lang('auth.new_password_success'), 'success');
239
+				redirect( Route::named('login') );
240
+			}
241
+			else
242
+			{
243
+				$this->setMessage($auth->error(), 'danger');
244
+			}
245
+		}
246
+
247
+		$data = [
248
+			'email' => $this->input->get('e'),
249
+			'code'  => $this->input->get('code')
250
+		];
251
+
252
+		$this->addScript('register.js');
253
+		$this->themer->setLayout('login');
254
+		$this->render($data);
255
+	}
256
+
257
+	//--------------------------------------------------------------------
258 258
 
259 259
 	/**
260 260
 	 * Allows a logged in user to enter their current password
@@ -320,24 +320,24 @@  discard block
 block discarded – undo
320 320
 	//--------------------------------------------------------------------
321 321
 
322 322
 
323
-    //--------------------------------------------------------------------
324
-    // AJAX Methods
325
-    //--------------------------------------------------------------------
323
+	//--------------------------------------------------------------------
324
+	// AJAX Methods
325
+	//--------------------------------------------------------------------
326 326
 
327
-    /**
328
-     * Checks the password strength and returns pass/fail.
329
-     *
330
-     * @param $str
331
-     */
332
-    public function password_check($str)
333
-    {
334
-        $this->load->helper('auth/password');
327
+	/**
328
+	 * Checks the password strength and returns pass/fail.
329
+	 *
330
+	 * @param $str
331
+	 */
332
+	public function password_check($str)
333
+	{
334
+		$this->load->helper('auth/password');
335 335
 
336
-        $strength = isStrongPassword($str);
336
+		$strength = isStrongPassword($str);
337 337
 
338
-        $this->renderJSON(['status' => $strength ? 'pass' : 'fail']);
339
-    }
338
+		$this->renderJSON(['status' => $strength ? 'pass' : 'fail']);
339
+	}
340 340
 
341
-    //--------------------------------------------------------------------
341
+	//--------------------------------------------------------------------
342 342
 
343 343
 }
Please login to merge, or discard this patch.