'Access forbidden'), JSON_FORCE_OBJECT));
} // END - if
//-----------------------------------------------------------------------------
// Generic call-back functions, they all rely on session data
//-----------------------------------------------------------------------------
// Establish a database link
function establishAjaxInstallerDatabaseLink () {
// This requires some session data
if (!isSessionDataSet(array('mysql_host', 'mysql_dbase', 'mysql_prefix', 'mysql_login', 'mysql_password1', 'mysql_password2', 'mysql_engine', 'database_extension'))) {
// Some required session data is not set
reportBug(__FUNCTION__, __LINE__, 'Required session data for this step not found.');
} // END - if
// Remove any previous flag
unsetSqlLinkUp(__FUNCTION__, __LINE__);
// Establish link
$linkResource = sqlConnectToDatabase(getSession('mysql_host'), getSession('mysql_login'), getSession('mysql_password1'), __FUNCTION__, __LINE__);
// Is this a link resource?
if (!isValidSqlLink($linkResource)) {
// Is not a resource
reportBug(__FUNCTION__, __LINE__, 'linkResource[]=' . gettype($linkResource) . ', expected: link resource');
} elseif (!isSqlLinkUp()) {
// SQL link is not up
reportBug(__FUNCTION__, __LINE__, 'Could not bring up SQL link.');
}
// Does selecting the database work?
if (!sqlSelectDatabase(getSession('mysql_dbase'), __FUNCTION__, __LINE__)) {
// Could not be selected
reportBug(__FUNCTION__, __LINE__, 'Could not select database ' . getSession('mysql_dbase'));
} elseif (!isInstallerSqlsReadable(getSession('base_path'))) {
// Installation area not found
reportBug(__FUNCTION__, __LINE__, 'SQL dumps not found. Please extract ALL files from the archive or checkout all files out from SVN.');
} elseif (ifFatalErrorsDetected()) {
// Some other fatal error occured
reportBug(__FUNCTION__, __LINE__, 'Some fatal error detected, please check debug.log for details.');
}
// Set type, prefix from POST data and database name for later queries
setConfigEntry('_TABLE_TYPE' , getSession('mysql_engine'));
setConfigEntry('_DB_TYPE' , getSession('database_extension'));
setConfigEntry('_MYSQL_PREFIX', getSession('mysql_prefix'));
setConfigEntry('__DB_NAME' , getSession('mysql_dbase'));
}
//-----------------------------------------------------------------------------
// Call-back functions for processing AJAX requests
//-----------------------------------------------------------------------------
// Processes AJAX requests for installer
function doAjaxProcessInstall () {
// 'do' must always be set and installation phase must be true
if (!isInstaller()) {
// This shall not happen
reportBug(__FUNCTION__, __LINE__, 'This AJAX request handler was called outside the installer.');
} elseif (!isPostRequestElementSet('do')) {
// This shall not happen
reportBug(__FUNCTION__, __LINE__, 'The JavaScript did not send "do" which is fatal.');
} // END - if
// Notify all modules that we are installing
$GLOBALS['__mailer_installing'] = TRUE;
// Again we do a call-back, so generate a function name depending on 'do'
$callbackName = 'doAjaxInstaller' . capitalizeUnderscoreString(postRequestElement('do'));
$GLOBALS['ajax_callback_function'] = $callbackName;
// Is the call-back function there?
if (!function_exists($callbackName)) {
// This shall not happen
reportBug(__FUNCTION__, __LINE__, 'AJAX call-back ' . $callbackName . ' does not exist.');
} // END - if
// Call the function
call_user_func($callbackName);
// Is the status fine or template not found (404)?
sendAjaxContent();
}
// Processes installer request for testing
function doAjaxInstallerTest () {
// Load the "test passed" template
setAjaxReplyContent(loadTemplate('ajax_test_passed', TRUE));
// All okay if we reach this point
setHttpStatus('200 OK');
}
// Processes installer requests for footer navigation
function doAjaxInstallerFooterNavigation () {
// 'tab' must always be set to determine which navigation buttons shall be visible
if (!isPostRequestElementSet('tab')) {
// This shall not happen
reportBug(__FUNCTION__, __LINE__, 'The JavaScript did not send "tab" which is fatal.');
} // END - if
// Init array for footer navigation
$enabledNavigations = array();
// "Detect" the 'tab' value
switch (postRequestElement('tab')) {
case 'base_data': // Also 'previous' is valid
case 'database_config':
case 'smtp_config':
case 'other_config':
case 'extensions':
case 'first_admin':
array_push($enabledNavigations, 'previous');
case 'welcome': // Only 'next' works for welcome page
array_push($enabledNavigations, 'next');
break;
case 'overview': // Enable only 'previous'
array_push($enabledNavigations, 'previous');
if (isInstallationDataCompleted()) {
// Add 'finish'
array_push($enabledNavigations, 'finish');
} // END - if
break;
default: // Unsupported value
// This shall not happen
reportBug(__FUNCTION__, __LINE__, 'Unsupported "tab" value ' . postRequestElement('tab') . ' detected.');
// This will never be reached
break;
} // END - switch
// Output the array for JSON reply
setAjaxReplyContent(encodeJson($enabledNavigations));
// All okay if we reach this point
setHttpStatus('200 OK');
}
// Processes installer AJAX calls for content-requests
function doAjaxInstallerDoStep () {
// 'step' must be there
if (!isPostRequestElementSet('step')) {
// This shall not happen
reportBug(__FUNCTION__, __LINE__, 'The JavaScript did not send "step" which is fatal.');
} // END - if
// Construct call-back name
$callbackName = 'doAjaxInstallerStep' . capitalizeUnderscoreString(postRequestElement('step'));
// Is the function there?
if (function_exists($callbackName)) {
// Call it for setting values in session
$status = call_user_func($callbackName);
} else {
// Log missing functions
reportBug(__FUNCTION__, __LINE__, 'Call-back function ' . $callbackName . ' does not exist.');
}
// Did the installation step went fine?
if ($status === TRUE) {
// All fine
setAjaxReplyContent(encodeJson(postRequestElement('step') . '=OK'));
// All okay if we reach this point
setHttpStatus('200 OK');
} // END - if
}
// Processes installer AJAX calls for content-requests
function doAjaxInstallerRequestContent () {
// 'tab' must be there
if (!isPostRequestElementSet('tab')) {
// This shall not happen
reportBug(__FUNCTION__, __LINE__, 'The JavaScript did not send "tab" which is fatal.');
} // END - if
// Construct call-back name for value-preset
$callbackName = 'doAjaxPrepareInstaller' . capitalizeUnderscoreString(postRequestElement('tab'));
// Is the function there?
if (function_exists($callbackName)) {
// Call it for setting values in session
call_user_func($callbackName);
} else {
// Log missing functions
reportBug(__FUNCTION__, __LINE__, 'Call-back function ' . $callbackName . ' does not exist.');
}
// Is the HTTP status still the same? (204 No Content)
if (getHttpStatus() == '204 No Content') {
// We use the current access level 'install' as prefix and construct a template name
setAjaxReplyContent(loadTemplate('install_page_' . trim(postRequestElement('tab')), TRUE));
// Has the template been loaded?
if (isset($GLOBALS['template_content']['html']['install_page_' . trim(postRequestElement('tab'))])) {
// All okay if we reach this point
setHttpStatus('200 OK');
} else {
// Set 404 error
setHttpStatus('404 Not Found');
}
} // END - if
}
// Process installer AJAX call for change-warning
function doAjaxInstallerChangeWarning () {
// 'elements' and 'button' must be there
if ((!isPostRequestElementSet('elements')) || (!isPostRequestElementSet('button'))) {
// This shall not happen
reportBug(__FUNCTION__, __LINE__, 'The JavaScript did not send "elements" and/or "button" which is fatal.');
} // END - if
// "Walk" through all elements
$OUT = '
';
foreach (explode(':', postRequestElement('elements')) as $element) {
// Is it an extension?
if (substr($element, 0, 4) == 'ext_') {
// Add row for extension
$OUT .= '- {%message,INSTALLER_CHANGED_ELEMENT_EXTENSION=' . str_replace('_', '-', $element) . '%}
';
} else {
// Add generic row
$OUT .= '- {--INSTALLER_CHANGED_ELEMENT_' . strtoupper($element) . '--}
';
}
} // END - foreach
$OUT .= '
';
// Prepare content
$content = array(
'out' => $OUT,
'button' => postRequestElement('button'),
'message' => '{--INSTALLER_TAB_NAVIGATION_' . strtoupper(postRequestElement('button')) . '_LINK--}',
);
if (in_array(postRequestElement('button'), array('previous', 'next'))) {
// Load 'prefixed' template
setAjaxReplyContent(loadTemplate('install_warning_' . postRequestElement('button'), TRUE, $content));
} else {
// Load 'tab' template
setAjaxReplyContent(loadTemplate('install_warning_tab', TRUE, $content));
}
// All okay if we reach this point
setHttpStatus('200 OK');
}
// Process installer AJAC call for saving changes
function doAjaxInstallerSaveChanges () {
// 'tab' must always be set to create a post-check-callback
if (!isPostRequestElementSet('tab')) {
// This shall not happen
reportBug(__FUNCTION__, __LINE__, 'The JavaScript did not send "tab" which is fatal.');
} // END - if
// Save the tab for pre-"filtering"
$currentTab = postRequestElement('tab');
// Remove some elements which should not be saved
foreach (array('tab', 'do', 'level') as $removedElement) {
// Remove this element from POST data
unsetPostRequestElement($removedElement);
} // END - foreach
// Default is failed save attempt (e.g. nothing to save)
$saveStatus = array(
'status' => 'failed',
'message' => '{--INSTALLER_SAVE_CHANGES_FAILED--}',
// Don't set this to false, or else it will be returned as 'failed' but is saved
'is_saved' => TRUE,
'failed_fields' => array()
);
// Init overall status
$isAllSaved = TRUE;
// Now set all remaining data in session
foreach (postRequestArray() as $key => $value) {
// Set it, if it is valid, else it will be added to $saveStatus (call-by-reference)
//* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'key=' . $key . ',value[' . gettype($value) . '=' . $value);
$saveStatus['is_saved'] = (
// Is the data valid?
(isInstallerDataValid($saveStatus, $key, $value))
&&
// And can it be stored in session?
(setSessionCompiled($key, $value))
);
// Save the overall status for below final check
$isAllSaved = (($isAllSaved === TRUE) && ($saveStatus['is_saved'] === TRUE));
//* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'key=' . $key . ',value[' . gettype($value) . '=' . $value . ',is_saved=' . intval($saveStatus['is_saved']) . ',isAllSaved=' . intval($isAllSaved));
} // END - foreach
// 'is_saved' is still true?
if ($isAllSaved === TRUE) {
// Set 'done' and message
$saveStatus['status'] = 'done';
$saveStatus['message'] = '{--INSTALLER_SAVE_CHANGES_DONE--}';
// Then do the post-check
doInstallerPostCheck($currentTab, $saveStatus);
} // END - if
// Output the status array for JSON reply
setAjaxReplyContent(encodeJson($saveStatus));
// All okay if we reach this point
setHttpStatus('200 OK');
}
// ----------------------------------------------------------------------------
// Call-back functions for preparing installer page requests
// ----------------------------------------------------------------------------
// Prepare AJAX request 'welcome'
function doAjaxPrepareInstallerWelcome () {
// Kept empty to prevent logfile entry
}
// Prepare AJAX request 'base_data'
function doAjaxPrepareInstallerBaseData () {
// Is 'base_path' not set?
if (!isSessionVariableSet('base_path')) {
// Then set it from PATH
setSessionCompiled('base_path', getPath());
} // END - if
// Is 'base_url' not set?
if (!isSessionVariableSet('base_url')) {
// Then set it from URL
setSessionCompiled('base_url', getUrl());
} // END - if
// Is 'main_title' not set?
if (!isSessionVariableSet('main_title')) {
// Then set it from default main title
setSessionCompiled('main_title', compileRawCode(getMessage('DEFAULT_MAIN_TITLE')));
} // END - if
// Is 'slogan' not set?
if (!isSessionVariableSet('slogan')) {
// Then set it from default slogan
setSessionCompiled('slogan', compileRawCode(getMessage('DEFAULT_SLOGAN')));
} // END - if
// Is 'webmaster' not set?
if (!isSessionVariableSet('webmaster')) {
// Then set it from default webmaster email address
setSessionCompiled('webmaster', '{--DEFAULT_WEBMASTER--}');
} // END - if
}
// Prepare AJAX request 'database_config'
function doAjaxPrepareInstallerDatabaseConfig () {
// Is 'mysql_host' not set?
if (!isSessionVariableSet('mysql_host')) {
// Then set it directly
setSessionCompiled('mysql_host', 'localhost');
} // END - if
// Is 'mysql_dbase' not set?
if (!isSessionVariableSet('mysql_dbase')) {
// Then set it directly
setSessionCompiled('mysql_dbase', 'your_database');
} // END - if
// Is 'mysql_prefix' not set?
if (!isSessionVariableSet('mysql_prefix')) {
// Then set it directly
setSessionCompiled('mysql_prefix', 'mailer');
} // END - if
// Is 'mysql_login' not set?
if (!isSessionVariableSet('mysql_login')) {
// Then set it directly
setSessionCompiled('mysql_login', 'your_login');
} // END - if
// Is 'mysql_dbase' not set?
if (!isSessionVariableSet('mysql_password1')) {
// Then set it directly
setSessionCompiled('mysql_password1', '');
} // END - if
// Is 'mysql_password2' not set?
if (!isSessionVariableSet('mysql_password2')) {
// Then set it directly
setSessionCompiled('mysql_password2', '');
} // END - if
// Is 'mysql_engine' not set?
if (!isSessionVariableSet('mysql_engine')) {
// Then set it directly
setSessionCompiled('mysql_engine', 'MyISAM');
} // END - if
// Is 'mysql_engine' not set?
if (!isSessionVariableSet('database_extension')) {
// Then set it directly
setSessionCompiled('database_extension', 'mysqli');
} // END - if
}
// Prepare AJAX request 'smtp_config'
function doAjaxPrepareInstallerSmtpConfig () {
// Kept empty to prevent logfile entry because SMTP settings are optional
}
// Prepare AJAX request 'other_config'
function doAjaxPrepareInstallerOtherConfig () {
// Is 'output_mode' not set?
if (!isSessionVariableSet('output_mode')) {
// Then set it directly
setSessionCompiled('output_mode', 'render');
} // END - if
// Is 'warn_no_pass' not set?
if (!isSessionVariableSet('warn_no_pass')) {
// Then set it directly
setSessionCompiled('warn_no_pass', 'Y');
} // END - if
// Is 'write_footer' not set?
if (!isSessionVariableSet('write_footer')) {
// Then set it directly
setSessionCompiled('write_footer', 'Y');
} // END - if
// Is 'enable_backlink' not set?
if (!isSessionVariableSet('enable_backlink')) {
// Then set it directly
setSessionCompiled('enable_backlink', 'Y');
} // END - if
}
// Prepare AJAX request 'extensions'
function doAjaxPrepareInstallerExtensions () {
// Is 'extensions' set?
if (!isSessionVariableSet('extensions')) {
/*
* At least ext-admins, ext-sql_patches and ext-task should be installed
* (ext-sql_patches is a must!)
*/
setSessionCompiled('extensions', 'admins:sql_patches:task');
} elseif (strpos(getSession('extensions'), 'sql_patches') === FALSE) {
// Add missing ext-sql_patches
setSessionCompiled('extensions', getSession('extensions') . ':sql_patches');
}
}
// Prepare AJAX request 'first_admin'
function doAjaxPrepareInstallerFirstAdmin () {
// Is 'admin_login' set?
if (!isSessionVariableSet('admin_login')) {
// Set it
setSessionCompiled('admin_login', 'admin');
} // END - if
// Is 'admin_email' set?
if (!isSessionVariableSet('admin_email')) {
// Set it
setSessionCompiled('admin_email', getSession('webmaster'));
} // END - if
// Is 'admin_password1' set?
if (!isSessionVariableSet('admin_password1')) {
// Set it
setSessionCompiled('admin_password1', '');
} // END - if
// Is 'admin_password2' set?
if (!isSessionVariableSet('admin_password2')) {
// Set it
setSessionCompiled('admin_password2', '');
} // END - if
}
// Prepare AJAX request 'overview'
function doAjaxPrepareInstallerOverview () {
// 'tab' must always be set to create a post-check-callback
if (!isPostRequestElementSet('tab')) {
// This shall not happen
reportBug(__FUNCTION__, __LINE__, 'The JavaScript did not send "tab" which is fatal.');
} // END - if
// Save the tab for pre-"filtering"
$currentTab = postRequestElement('tab');
// Default is failed save attempt (e.g. nothing to save)
$verificationStatus = array(
// Status code, can be 'failed' or 'done'
'status' => 'failed',
// Status message (e.g. for output)
'message' => '{--INSTALLER_OVERVIEW_FINAL_CHECK_FAILED--}',
// Don't set this to false, or else it will be returned as 'failed' but is saved
'is_valid' => TRUE,
// Failed fields
'failed_fields' => array()
);
// Init overall status and final output
$isAllValid = TRUE;
$output = '';
// Check all data in session
foreach (array_keys($GLOBALS['installer_groups']) as $key) {
// Get values from session
$value = getSession($key);
// Is the data valid?
$verificationStatus['is_valid'] = (isInstallerDataValid($verificationStatus, $key, $value));
// Is this step okay?
if ($verificationStatus['is_valid'] === TRUE) {
// Add this key/value pair to a overview group
addKeyValueToInstallerOverviewGroup($key, $value);
} // END - if
// Save the overall status for below final check
//* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'key=' . $key . ',value=' . $value . ',is_valid=' . intval($verificationStatus['is_valid']) . ',isAllValid=' . intval($isAllValid));
$isAllValid = (($isAllValid === TRUE) && ($verificationStatus['is_valid'] === TRUE));
} // END - foreach
// Is it still true?
if ((isInstallationDataCompleted()) && ($isAllValid === TRUE)) {
// Set 'done' and message
$verificationStatus['status'] = 'done';
$verificationStatus['message'] = '{--INSTALLER_OVERVIEW_FINAL_CHECK_DONE--}';
// Then do the post-check
doInstallerPostCheck($currentTab, $verificationStatus);
} // END - if
// Is it still valid?
if ($verificationStatus['status'] != 'done') {
// Log message away
//* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'Final check on all stored data failed. message=' . $verificationStatus['message']);
// Process failed fields
$verificationStatus['failed_fields'] = handleInstallerFailedFields($verificationStatus['failed_fields']);
// Output the array for JSON reply
setAjaxReplyContent(loadTemplate('install_overview_failed', TRUE, $verificationStatus));
/*
* Something went wrong, this might happen when e.g. the user has tried
* to save invalid database login data but hit reload button on error
* message.
*/
setHttpStatus('200 OK');
// Abort here
return;
} // END - if
}
// ----------------------------------------------------------------------------
// Call-back functions for doing installation steps
// ----------------------------------------------------------------------------
// Call-back function to import first tables.sql file
function doAjaxInstallerStepImportTablesSql () {
// Establish database link
establishAjaxInstallerDatabaseLink();
// Init SQL array
initSqls();
// Import tables.sql
importInstallSqlDump('tables');
// Are some SQLs found?
if (countSqls() == 0) {
// Abort here
reportBug(__FUNCTION__, __LINE__, '{--INSTALLER_SQL_IMPORT_FAILED--}');
} // END - if
// Now run all queries through
runFilterChain('run_sqls');
// Close SQL link
sqlCloseLink(__FUNCTION__, __LINE__);
// All fine
return TRUE;
}
// Call-back function to import menu SQL file
function doAjaxInstallerStepImportMenuSql () {
// Establish database link
establishAjaxInstallerDatabaseLink();
// Init SQL array
initSqls();
// Import tables.sql
importInstallSqlDump('menu-' . getLanguage());
// Are some SQLs found?
if (countSqls() == 0) {
// Abort here
reportBug(__FUNCTION__, __LINE__, '{--INSTALLER_SQL_IMPORT_FAILED--}');
} // END - if
// Now run all queries through
runFilterChain('run_sqls');
// Close SQL link
sqlCloseLink(__FUNCTION__, __LINE__);
// All fine
return TRUE;
}
// Call-back function to install some important extensions
function doAjaxInstallerStepInstallExtensions () {
// Only one element is required
if (!isSessionVariableSet('extensions')) {
// Some required session data is not set
reportBug(__FUNCTION__, __LINE__, 'Required session data for this step not found.');
} // END - if
// Establish database link
establishAjaxInstallerDatabaseLink();
// Get all extensions
$extensions = explode(':', getSession('extensions'));
// Make sure ext-sql_patches is first
array_unshift($extensions, 'sql_patches');
// "Walk" through all extensions
foreach ($extensions as $key => $ext_name) {
// Debug message
//* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'key=' . $key . ',ext_name=' . $ext_name);
// Is ext-sql_patches not at key=0?
if (($ext_name == 'sql_patches') && ($key > 0)) {
// Then skip this entry
//* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'Skipping sql_changes at key=' . $key);
continue;
} elseif (isExtensionInstalled($ext_name)) {
// Skip already installed extensions
//* DEBUG: */ logDebugMessage(__FUNCTION__, __LINE__, 'Skipping extension ' . $ext_name . ' at key=' . $key . ': Already installed.');
continue;
} elseif ((!loadExtension($ext_name, 'test', '0.0.0', TRUE)) || (!registerExtension($ext_name, NULL))) {
// Didn't work
reportBug(__FUNCTION__, __LINE__, 'Cannot load/register extension ' . $ext_name . '.');
} // END - if
} // END - foreach
// All fine
return TRUE;
}
// Call-back function to write local configuration file
function doAjaxInstallerStepWriteLocalConfig () {
// Is all set?
if (!isSessionDataSet(array('base_path', 'base_url', 'main_title', 'slogan', 'webmaster', 'mysql_host', 'mysql_dbase', 'mysql_prefix', 'mysql_login', 'mysql_password1', 'mysql_password2', 'mysql_engine', 'database_extension', 'output_mode', 'warn_no_pass', 'write_footer', 'enable_backlink'))) {
// Some required session data is not set
reportBug(__FUNCTION__, __LINE__, 'Required session data for this step not found.');
} elseif (isInstalled()) {
// Is already installed = local config written
reportBug(__FUNCTION__, __LINE__, 'Local config file is already written.');
} elseif (isAdminRegistered()) {
// Admin is already registered
reportBug(__FUNCTION__, __LINE__, 'First administrator account is already registered.');
}
// Establish database link
establishAjaxInstallerDatabaseLink();
// Write config file
if (!doInstallWriteLocalConfigurationFile(
getSession('base_path'),
getSession('base_url'),
getSession('main_title'),
getSession('slogan'),
getSession('webmaster'),
getSession('warn_no_pass'),
getSession('write_footer'),
getSession('enable_backlink'),
getSession('mysql_host'),
getSession('mysql_dbase'),
getSession('mysql_login'),
getSession('mysql_password1'),
getSession('mysql_prefix'),
getSession('mysql_engine'),
getSession('database_extension'),
getSession('smtp_host'),
getSession('smtp_user'),
getSession('smtp_password1')
)) {
// Something bad went wrong
removeFile(getSession('base_path') . getCachePath() . 'config-local.php');
reportBug(__FUNCTION__, __LINE__, 'Did not fully write config-local.php .');
}
// Change ADMIN_REGISTERED flag
$done = changeDataInLocalConfigurationFile('ADMIN-SETUP', "setConfigEntry('ADMIN_REGISTERED', '", "');", 'Y', 0);
// All fine
return $done;
}
// Call-back function to register first admin
function doAjaxInstallerStepRegisterFirstAdmin () {
// Is all set?
if (!isSessionDataSet(array('admin_login', 'admin_email', 'admin_password1', 'admin_password2'))) {
// Some required session data is not set
reportBug(__FUNCTION__, __LINE__, 'Required session data for this step not found.');
} elseif (isAdminRegistered()) {
// First admin is already registered
reportBug(__FUNCTION__, __LINE__, 'First administrator is already registered.');
}
// Establish database link
establishAjaxInstallerDatabaseLink();
// Load admin include
loadIncludeOnce('inc/modules/admin/admin-inc.php');
// Register first admin
$ret = addAdminAccount(getSession('admin_login'), md5(getSession('admin_password1')), getSession('admin_email'), 'allow');
// Did it work?
return ($ret == 'done');
}
// [EOF]
?>