Failed Conditions
Branch release-2.1 (4e22cf)
by Rick
06:39
created

Sources/Subs-Admin.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * This file contains functions that are specifically done by administrators.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2017 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Get a list of versions that are currently installed on the server.
21
 * @param array $checkFor An array of what to check versions for - can contain one or more of 'gd', 'imagemagick', 'db_server', 'phpa', 'memcache', 'xcache', 'apc', 'php' or 'server'
22
 * @return array An array of versions (keys are same as what was in $checkFor, values are the versions)
23
 */
24
function getServerVersions($checkFor)
25
{
26
	global $txt, $db_connection, $_PHPA, $smcFunc, $memcached, $modSettings;
27
28
	loadLanguage('Admin');
29
30
	$versions = array();
31
32
	// Is GD available?  If it is, we should show version information for it too.
33
	if (in_array('gd', $checkFor) && function_exists('gd_info'))
34
	{
35
		$temp = gd_info();
36
		$versions['gd'] = array('title' => $txt['support_versions_gd'], 'version' => $temp['GD Version']);
37
	}
38
39
	// Why not have a look at ImageMagick? If it's installed, we should show version information for it too.
40
	if (in_array('imagemagick', $checkFor) && (class_exists('Imagick') || function_exists('MagickGetVersionString')))
41
	{
42
		if (class_exists('Imagick'))
43
		{
44
			$temp = New Imagick;
45
			$temp2 = $temp->getVersion();
46
			$im_version = $temp2['versionString'];
47
			$extension_version = 'Imagick ' . phpversion('Imagick');
48
		}
49
		else
50
		{
51
			$im_version = MagickGetVersionString();
52
			$extension_version = 'MagickWand ' . phpversion('MagickWand');
53
		}
54
55
		// We already know it's ImageMagick and the website isn't needed...
56
		$im_version = str_replace(array('ImageMagick ', ' https://www.imagemagick.org'), '', $im_version);
57
		$versions['imagemagick'] = array('title' => $txt['support_versions_imagemagick'], 'version' => $im_version . ' (' . $extension_version . ')');
58
	}
59
60
	// Now lets check for the Database.
61
	if (in_array('db_server', $checkFor))
62
	{
63
		db_extend();
64
		if (!isset($db_connection) || $db_connection === false)
65
			trigger_error('getServerVersions(): you need to be connected to the database in order to get its server version', E_USER_NOTICE);
66
		else
67
		{
68
			$versions['db_engine'] = array('title' => sprintf($txt['support_versions_db_engine'], $smcFunc['db_title']), 'version' => '');
69
			$versions['db_engine']['version'] = $smcFunc['db_get_engine']();
70
71
			$versions['db_server'] = array('title' => sprintf($txt['support_versions_db'], $smcFunc['db_title']), 'version' => '');
72
			$versions['db_server']['version'] = $smcFunc['db_get_version']();
73
		}
74
	}
75
76
	// If we're using memcache we need the server info.
77
	if (empty($memcached) && function_exists('memcache_get') && isset($modSettings['cache_memcached']) && trim($modSettings['cache_memcached']) != '')
78
		get_memcached_server();
79
80
	// Check to see if we have any accelerators installed...
81
	if (in_array('phpa', $checkFor) && isset($_PHPA))
82
		$versions['phpa'] = array('title' => 'ionCube PHP-Accelerator', 'version' => $_PHPA['VERSION']);
83
	if (in_array('apc', $checkFor) && extension_loaded('apc'))
84
		$versions['apc'] = array('title' => 'Alternative PHP Cache', 'version' => phpversion('apc'));
85
	if (in_array('memcache', $checkFor) && function_exists('memcache_set'))
86
		$versions['memcache'] = array('title' => 'Memcached', 'version' => empty($memcached) ? '???' : memcache_get_version($memcached));
87
	if (in_array('xcache', $checkFor) && function_exists('xcache_set'))
88
		$versions['xcache'] = array('title' => 'XCache', 'version' => XCACHE_VERSION);
89
90
	if (in_array('php', $checkFor))
91
		$versions['php'] = array('title' => 'PHP', 'version' => PHP_VERSION, 'more' => '?action=admin;area=serversettings;sa=phpinfo');
92
93
	if (in_array('server', $checkFor))
94
		$versions['server'] = array('title' => $txt['support_versions_server'], 'version' => $_SERVER['SERVER_SOFTWARE']);
95
96
	return $versions;
97
}
98
99
/**
100
 * Search through source, theme and language files to determine their version.
101
 * Get detailed version information about the physical SMF files on the server.
102
 *
103
 * - the input parameter allows to set whether to include SSI.php and whether
104
 *   the results should be sorted.
105
 * - returns an array containing information on source files, templates and
106
 *   language files found in the default theme directory (grouped by language).
107
 *
108
 * @param array &$versionOptions An array of options. Can contain one or more of 'include_ssi', 'include_subscriptions', 'include_tasks' and 'sort_results'
109
 * @return array An array of file version info.
110
 */
111
function getFileVersions(&$versionOptions)
112
{
113
	global $boarddir, $sourcedir, $settings, $tasksdir;
114
115
	// Default place to find the languages would be the default theme dir.
116
	$lang_dir = $settings['default_theme_dir'] . '/languages';
117
118
	$version_info = array(
119
		'file_versions' => array(),
120
		'default_template_versions' => array(),
121
		'template_versions' => array(),
122
		'default_language_versions' => array(),
123
		'tasks_versions' => array(),
124
	);
125
126
	// Find the version in SSI.php's file header.
127 View Code Duplication
	if (!empty($versionOptions['include_ssi']) && file_exists($boarddir . '/SSI.php'))
128
	{
129
		$fp = fopen($boarddir . '/SSI.php', 'rb');
130
		$header = fread($fp, 4096);
131
		fclose($fp);
132
133
		// The comment looks rougly like... that.
134
		if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
135
			$version_info['file_versions']['SSI.php'] = $match[1];
136
		// Not found!  This is bad.
137
		else
138
			$version_info['file_versions']['SSI.php'] = '??';
139
	}
140
141
	// Do the paid subscriptions handler?
142 View Code Duplication
	if (!empty($versionOptions['include_subscriptions']) && file_exists($boarddir . '/subscriptions.php'))
143
	{
144
		$fp = fopen($boarddir . '/subscriptions.php', 'rb');
145
		$header = fread($fp, 4096);
146
		fclose($fp);
147
148
		// Found it?
149
		if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
150
			$version_info['file_versions']['subscriptions.php'] = $match[1];
151
		// If we haven't how do we all get paid?
152
		else
153
			$version_info['file_versions']['subscriptions.php'] = '??';
154
	}
155
156
	// Load all the files in the Sources directory, except this file and the redirect.
157
	$sources_dir = dir($sourcedir);
158 View Code Duplication
	while ($entry = $sources_dir->read())
159
	{
160
		if (substr($entry, -4) === '.php' && !is_dir($sourcedir . '/' . $entry) && $entry !== 'index.php')
161
		{
162
			// Read the first 4k from the file.... enough for the header.
163
			$fp = fopen($sourcedir . '/' . $entry, 'rb');
164
			$header = fread($fp, 4096);
165
			fclose($fp);
166
167
			// Look for the version comment in the file header.
168
			if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
169
				$version_info['file_versions'][$entry] = $match[1];
170
			// It wasn't found, but the file was... show a '??'.
171
			else
172
				$version_info['file_versions'][$entry] = '??';
173
		}
174
	}
175
	$sources_dir->close();
176
177
	// Load all the files in the tasks directory.
178
	if (!empty($versionOptions['include_tasks']))
179
	{
180
		$tasks_dir = dir($tasksdir);
181 View Code Duplication
		while ($entry = $tasks_dir->read())
182
		{
183
			if (substr($entry, -4) === '.php' && !is_dir($tasksdir . '/' . $entry) && $entry !== 'index.php')
184
			{
185
				// Read the first 4k from the file.... enough for the header.
186
				$fp = fopen($tasksdir . '/' . $entry, 'rb');
187
				$header = fread($fp, 4096);
188
				fclose($fp);
189
190
				// Look for the version comment in the file header.
191
				if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
192
					$version_info['tasks_versions'][$entry] = $match[1];
193
				// It wasn't found, but the file was... show a '??'.
194
				else
195
					$version_info['tasks_versions'][$entry] = '??';
196
			}
197
		}
198
		$tasks_dir->close();
199
	}
200
201
	// Load all the files in the default template directory - and the current theme if applicable.
202
	$directories = array('default_template_versions' => $settings['default_theme_dir']);
203
	if ($settings['theme_id'] != 1)
204
		$directories += array('template_versions' => $settings['theme_dir']);
205
206
	foreach ($directories as $type => $dirname)
207
	{
208
		$this_dir = dir($dirname);
209
		while ($entry = $this_dir->read())
210
		{
211
			if (substr($entry, -12) == 'template.php' && !is_dir($dirname . '/' . $entry))
212
			{
213
				// Read the first 768 bytes from the file.... enough for the header.
214
				$fp = fopen($dirname . '/' . $entry, 'rb');
215
				$header = fread($fp, 768);
216
				fclose($fp);
217
218
				// Look for the version comment in the file header.
219
				if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
220
					$version_info[$type][$entry] = $match[1];
221
				// It wasn't found, but the file was... show a '??'.
222
				else
223
					$version_info[$type][$entry] = '??';
224
			}
225
		}
226
		$this_dir->close();
227
	}
228
229
	// Load up all the files in the default language directory and sort by language.
230
	$this_dir = dir($lang_dir);
231
	while ($entry = $this_dir->read())
232
	{
233
		if (substr($entry, -4) == '.php' && $entry != 'index.php' && !is_dir($lang_dir . '/' . $entry))
234
		{
235
			// Read the first 768 bytes from the file.... enough for the header.
236
			$fp = fopen($lang_dir . '/' . $entry, 'rb');
237
			$header = fread($fp, 768);
238
			fclose($fp);
239
240
			// Split the file name off into useful bits.
241
			list ($name, $language) = explode('.', $entry);
242
243
			// Look for the version comment in the file header.
244
			if (preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '(?:[\s]{2}|\*/)~i', $header, $match) == 1)
245
				$version_info['default_language_versions'][$language][$name] = $match[1];
246
			// It wasn't found, but the file was... show a '??'.
247
			else
248
				$version_info['default_language_versions'][$language][$name] = '??';
249
		}
250
	}
251
	$this_dir->close();
252
253
	// Sort the file versions by filename.
254
	if (!empty($versionOptions['sort_results']))
255
	{
256
		ksort($version_info['file_versions']);
257
		ksort($version_info['default_template_versions']);
258
		ksort($version_info['template_versions']);
259
		ksort($version_info['default_language_versions']);
260
		ksort($version_info['tasks_versions']);
261
262
		// For languages sort each language too.
263
		foreach ($version_info['default_language_versions'] as $language => $dummy)
264
			ksort($version_info['default_language_versions'][$language]);
265
	}
266
	return $version_info;
267
}
268
269
/**
270
 * Update the Settings.php file.
271
 *
272
 * The most important function in this file for mod makers happens to be the
273
 * updateSettingsFile() function, but it shouldn't be used often anyway.
274
 *
275
 * - updates the Settings.php file with the changes supplied in config_vars.
276
 * - expects config_vars to be an associative array, with the keys as the
277
 *   variable names in Settings.php, and the values the variable values.
278
 * - does not escape or quote values.
279
 * - preserves case, formatting, and additional options in file.
280
 * - writes nothing if the resulting file would be less than 10 lines
281
 *   in length (sanity check for read lock.)
282
 * - check for changes to db_last_error and passes those off to a separate handler
283
 * - attempts to create a backup file and will use it should the writing of the
284
 *   new settings file fail
285
 *
286
 * @param array $config_vars An array of one or more variables to update
287
 */
288
function updateSettingsFile($config_vars)
289
{
290
	global $boarddir, $cachedir, $context;
291
292
	// Updating the db_last_error, then don't mess around with Settings.php
293
	if (count($config_vars) === 1 && isset($config_vars['db_last_error']))
294
	{
295
		updateDbLastError($config_vars['db_last_error']);
296
		return;
297
	}
298
299
	// When was Settings.php last changed?
300
	$last_settings_change = filemtime($boarddir . '/Settings.php');
301
302
	// Load the settings file.
303
	$settingsArray = trim(file_get_contents($boarddir . '/Settings.php'));
304
305
	// Break it up based on \r or \n, and then clean out extra characters.
306
	if (strpos($settingsArray, "\n") !== false)
307
		$settingsArray = explode("\n", $settingsArray);
308
	elseif (strpos($settingsArray, "\r") !== false)
309
		$settingsArray = explode("\r", $settingsArray);
310
	else
311
		return;
312
313
	// Presumably, the file has to have stuff in it for this function to be called :P.
314
	if (count($settingsArray) < 10)
315
		return;
316
317
	// remove any /r's that made there way in here
318
	foreach ($settingsArray as $k => $dummy)
319
		$settingsArray[$k] = strtr($dummy, array("\r" => '')) . "\n";
320
321
	// go line by line and see whats changing
322
	for ($i = 0, $n = count($settingsArray); $i < $n; $i++)
323
	{
324
		// Don't trim or bother with it if it's not a variable.
325
		if (substr($settingsArray[$i], 0, 1) != '$')
326
			continue;
327
328
		$settingsArray[$i] = trim($settingsArray[$i]) . "\n";
329
330
		// Look through the variables to set....
331
		foreach ($config_vars as $var => $val)
332
		{
333
			// be sure someone is not updating db_last_error this with a group
334
			if ($var === 'db_last_error')
335
			{
336
				updateDbLastError($val);
337
				unset($config_vars[$var]);
338
			}
339
			elseif (strncasecmp($settingsArray[$i], '$' . $var, 1 + strlen($var)) == 0)
340
			{
341
				$comment = strstr(substr($settingsArray[$i], strpos($settingsArray[$i], ';')), '#');
342
				$settingsArray[$i] = '$' . $var . ' = ' . $val . ';' . ($comment == '' ? '' : "\t\t" . rtrim($comment)) . "\n";
343
344
				// This one's been 'used', so to speak.
345
				unset($config_vars[$var]);
346
			}
347
		}
348
349
		// End of the file ... maybe
350
		if (substr(trim($settingsArray[$i]), 0, 2) == '?' . '>')
351
			$end = $i;
352
	}
353
354
	// This should never happen, but apparently it is happening.
355
	if (empty($end) || $end < 10)
356
		$end = count($settingsArray) - 1;
357
358
	// Still more variables to go?  Then lets add them at the end.
359
	if (!empty($config_vars))
360
	{
361
		if (trim($settingsArray[$end]) == '?' . '>')
362
			$settingsArray[$end++] = '';
363
		else
364
			$end++;
365
366
		// Add in any newly defined vars that were passed
367
		foreach ($config_vars as $var => $val)
368
			$settingsArray[$end++] = '$' . $var . ' = ' . $val . ';' . "\n";
369
370
		$settingsArray[$end] = '?' . '>';
371
	}
372
	else
373
		$settingsArray[$end] = trim($settingsArray[$end]);
374
375
	// Sanity error checking: the file needs to be at least 12 lines.
376
	if (count($settingsArray) < 12)
377
		return;
378
379
	// Try to avoid a few pitfalls:
380
	//  - like a possible race condition,
381
	//  - or a failure to write at low diskspace
382
	//
383
	// Check before you act: if cache is enabled, we can do a simple write test
384
	// to validate that we even write things on this filesystem.
385 View Code Duplication
	if ((empty($cachedir) || !file_exists($cachedir)) && file_exists($boarddir . '/cache'))
386
		$cachedir = $boarddir . '/cache';
387
388
	$test_fp = @fopen($cachedir . '/settings_update.tmp', "w+");
389
	if ($test_fp)
390
	{
391
		fclose($test_fp);
392
		$written_bytes = file_put_contents($cachedir . '/settings_update.tmp', 'test', LOCK_EX);
393
		@unlink($cachedir . '/settings_update.tmp');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
394
395
		if ($written_bytes !== 4)
396
		{
397
			// Oops. Low disk space, perhaps. Don't mess with Settings.php then.
398
			// No means no. :P
399
			return;
400
		}
401
	}
402
403
	// Protect me from what I want! :P
404
	clearstatcache();
405
	if (filemtime($boarddir . '/Settings.php') === $last_settings_change)
406
	{
407
		// save the old before we do anything
408
		$settings_backup_fail = !@is_writable($boarddir . '/Settings_bak.php') || !@copy($boarddir . '/Settings.php', $boarddir . '/Settings_bak.php');
409
		$settings_backup_fail = !$settings_backup_fail ? (!file_exists($boarddir . '/Settings_bak.php') || filesize($boarddir . '/Settings_bak.php') === 0) : $settings_backup_fail;
410
411
		// write out the new
412
		$write_settings = implode('', $settingsArray);
413
		$written_bytes = file_put_contents($boarddir . '/Settings.php', $write_settings, LOCK_EX);
414
415
		// survey says ...
416
		if ($written_bytes !== strlen($write_settings) && !$settings_backup_fail)
417
		{
418
			// Well this is not good at all, lets see if we can save this
419
			$context['settings_message'] = 'settings_error';
420
421
			if (file_exists($boarddir . '/Settings_bak.php'))
422
				@copy($boarddir . '/Settings_bak.php', $boarddir . '/Settings.php');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
423
		}
424
	}
425
426
	// Even though on normal installations the filemtime should prevent this being used by the installer incorrectly
427
	// it seems that there are times it might not. So let's MAKE it dump the cache.
428
	if (function_exists('opcache_invalidate'))
429
		opcache_invalidate($boarddir . '/Settings.php', true);
430
}
431
432
/**
433
 * Saves the time of the last db error for the error log
434
 * - Done separately from updateSettingsFile to avoid race conditions
435
 *   which can occur during a db error
436
 * - If it fails Settings.php will assume 0
437
 *
438
 * @param int $time The timestamp of the last DB error
439
 */
440
function updateDbLastError($time)
441
{
442
	global $boarddir;
443
444
	// Write out the db_last_error file with the error timestamp
445
	file_put_contents($boarddir . '/db_last_error.php', '<' . '?' . "php\n" . '$db_last_error = ' . $time . ';' . "\n" . '?' . '>', LOCK_EX);
446
	@touch($boarddir . '/' . 'Settings.php');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
447
}
448
/**
449
 * Saves the admin's current preferences to the database.
450
 */
451
function updateAdminPreferences()
452
{
453
	global $options, $context, $smcFunc, $settings, $user_info;
454
455
	// This must exist!
456
	if (!isset($context['admin_preferences']))
457
		return false;
458
459
	// This is what we'll be saving.
460
	$options['admin_preferences'] = $smcFunc['json_encode']($context['admin_preferences']);
461
462
	// Just check we haven't ended up with something theme exclusive somehow.
463
	$smcFunc['db_query']('', '
464
		DELETE FROM {db_prefix}themes
465
		WHERE id_theme != {int:default_theme}
466
		AND variable = {string:admin_preferences}',
467
		array(
468
			'default_theme' => 1,
469
			'admin_preferences' => 'admin_preferences',
470
		)
471
	);
472
473
	// Update the themes table.
474
	$smcFunc['db_insert']('replace',
475
		'{db_prefix}themes',
476
		array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
477
		array($user_info['id'], 1, 'admin_preferences', $options['admin_preferences']),
478
		array('id_member', 'id_theme', 'variable')
479
	);
480
481
	// Make sure we invalidate any cache.
482
	cache_put_data('theme_settings-' . $settings['theme_id'] . ':' . $user_info['id'], null, 0);
483
}
484
485
/**
486
 * Send all the administrators a lovely email.
487
 * - loads all users who are admins or have the admin forum permission.
488
 * - uses the email template and replacements passed in the parameters.
489
 * - sends them an email.
490
 *
491
 * @param string $template Which email template to use
492
 * @param array $replacements An array of items to replace the variables in the template
493
 * @param array $additional_recipients An array of arrays of info for additional recipients. Should have 'id', 'email' and 'name' for each.
494
 */
495
function emailAdmins($template, $replacements = array(), $additional_recipients = array())
496
{
497
	global $smcFunc, $sourcedir, $language, $modSettings;
498
499
	// We certainly want this.
500
	require_once($sourcedir . '/Subs-Post.php');
501
502
	// Load all members which are effectively admins.
503
	require_once($sourcedir . '/Subs-Members.php');
504
	$members = membersAllowedTo('admin_forum');
505
506
	// Load their alert preferences
507
	require_once($sourcedir . '/Subs-Notify.php');
508
	$prefs = getNotifyPrefs($members, 'announcements', true);
509
510
	$request = $smcFunc['db_query']('', '
511
		SELECT id_member, member_name, real_name, lngfile, email_address
512
		FROM {db_prefix}members
513
		WHERE id_member IN({array_int:members})',
514
		array(
515
			'members' => $members,
516
		)
517
	);
518
	$emails_sent = array();
519
	while ($row = $smcFunc['db_fetch_assoc']($request))
520
	{
521
		if (empty($prefs[$row['id_member']]['announcements']))
522
			continue;
523
524
		// Stick their particulars in the replacement data.
525
		$replacements['IDMEMBER'] = $row['id_member'];
526
		$replacements['REALNAME'] = $row['member_name'];
527
		$replacements['USERNAME'] = $row['real_name'];
528
529
		// Load the data from the template.
530
		$emaildata = loadEmailTemplate($template, $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']);
531
532
		// Then send the actual email.
533
		sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, $template, $emaildata['is_html'], 1);
534
535
		// Track who we emailed so we don't do it twice.
536
		$emails_sent[] = $row['email_address'];
537
	}
538
	$smcFunc['db_free_result']($request);
539
540
	// Any additional users we must email this to?
541
	if (!empty($additional_recipients))
542
		foreach ($additional_recipients as $recipient)
543
		{
544
			if (in_array($recipient['email'], $emails_sent))
545
				continue;
546
547
			$replacements['IDMEMBER'] = $recipient['id'];
548
			$replacements['REALNAME'] = $recipient['name'];
549
			$replacements['USERNAME'] = $recipient['name'];
550
551
			// Load the template again.
552
			$emaildata = loadEmailTemplate($template, $replacements, empty($recipient['lang']) || empty($modSettings['userLanguage']) ? $language : $recipient['lang']);
553
554
			// Send off the email.
555
			sendmail($recipient['email'], $emaildata['subject'], $emaildata['body'], null, $template, $emaildata['is_html'], 1);
556
		}
557
}
558
559
?>