function _upgradeAccounts()
{
    // add new upload fields
    $schema = loadSchema('accounts');
    // make schema and menu visible
    if (@$schema['tableHidden']) {
        $schema['tableHidden'] = 0;
    }
    if (@$schema['menuHidden']) {
        $schema['menuHidden'] = 0;
    }
    // add new fields
    if (!@$schema['createdDate']) {
        $schema['createdDate'] = array('type' => 'none', 'label' => "Created", 'isSystemField' => '1');
    }
    if (!@$schema['createdByUserNum']) {
        $schema['createdByUserNum'] = array('type' => 'none', 'label' => "Created By", 'isSystemField' => '1');
    }
    if (!@$schema['updatedDate']) {
        $schema['updatedDate'] = array('type' => 'none', 'label' => "Last Updated", 'isSystemField' => '1');
    }
    if (!@$schema['updatedByUserNum']) {
        $schema['updatedByUserNum'] = array('type' => 'none', 'label' => "Last Updated By", 'isSystemField' => '1');
    }
    if (!@$schema['accessList']) {
        $schema['accessList'] = array('type' => 'accessList', 'label' => "Section Access", 'isSystemField' => '1', 'order' => time());
    }
    if (!@$schema['lastLoginDate']) {
        // added in v2.08
        $schema['lastLoginDate'] = array('type' => 'date', 'label' => "Last Login", 'defaultDate' => 'none', 'order' => time(), 'showTime' => '1', 'use24HourFormat' => '0', 'showSeconds' => '1', 'yearRangeStart' => '2010', 'yearRangeEnd' => '2020');
    }
    // remove fields
    foreach (array_keys($schema) as $fieldname) {
        $fieldSchema =& $schema[$fieldname];
        if (!is_array($fieldSchema)) {
            continue;
        }
        // fields are stored as arrays, other entries are table metadata, skip metadata
        // remove old "show tablenames" field for old access settings
        if (@$fieldSchema['type'] == 'separator' && preg_match("/listTableNames\\(\\)'>MySQL Tablenames/", @$fieldSchema['separatorHTML'])) {
            unset($schema[$fieldname]);
        }
    }
    ### update order
    // increase field order for all fields
    foreach (array_keys($schema) as $fieldname) {
        $fieldSchema =& $schema[$fieldname];
        if (!is_array($fieldSchema)) {
            continue;
        }
        // fields are stored as arrays, other entries are table metadata, skip metadata
        $fieldSchema['order'] += 10;
    }
    // hard code field order
    if (@$schema['num']) {
        $schema['num']['order'] = '1';
    }
    if (@$schema['createdDate']) {
        $schema['createdDate']['order'] = '2';
    }
    if (@$schema['createdByUserNum']) {
        $schema['createdByUserNum']['order'] = '3';
    }
    if (@$schema['updatedDate']) {
        $schema['updatedDate']['order'] = '4';
    }
    if (@$schema['updatedByUserNum']) {
        $schema['updatedByUserNum']['order'] = '5';
    }
    ### change fields
    // Set checked/unchecked values for 'isAdmin' field
    if (@$schema['isAdmin']) {
        if (@$schema['isAdmin']['checkedValue'] == '') {
            $schema['isAdmin']['checkedValue'] = 'Yes';
        }
        if (@$schema['isAdmin']['uncheckedValue'] == '') {
            $schema['isAdmin']['uncheckedValue'] = '-';
        }
        $schema['isAdmin']['adminOnly'] = "2";
    }
    // Set accessList to be a system field
    if (@$schema['accessList']) {
        $schema['accessList']['isSystemField'] = 1;
    }
    // v1.32 - add "My Account" fields
    $myAccountFields = array('fullname', 'username', 'email', 'password');
    foreach ($myAccountFields as $field) {
        if (!is_array(@$schema[$field])) {
            continue;
        }
        if (array_key_exists('myAccountField', $schema[$field])) {
            continue;
        }
        // ignore if already set
        $schema[$field]['myAccountField'] = 1;
    }
    // save changes
    saveSchema('accounts', $schema);
    // add to schema
    createMissingSchemaTablesAndFields();
    // add to database
    clearAlertsAndNotices();
    // don't show "created table/field" alerts
}
function _updateSchema($schema)
{
    $oldColumnName = $_REQUEST['fieldname'];
    $newColumnName = $_REQUEST['newFieldname'];
    // remove old column
    unset($schema[$oldColumnName]);
    // create new column (duplicate fieldname check already done in submitFormViaAjax)
    $schema[$newColumnName] = array();
    // ignore fields
    if (@$_REQUEST['separatorType'] != 'header bar') {
        unset($_REQUEST['separatorHeader']);
    }
    if (@$_REQUEST['separatorType'] != 'html') {
        unset($_REQUEST['separatorHTML']);
    }
    if (@$_REQUEST['optionsType'] != 'text' && @$_REQUEST['optionsText'] != '') {
        $_REQUEST['optionsText'] = '';
    }
    if (@$_REQUEST['optionsType'] != 'table') {
        $_REQUEST['optionsTablename'] = '';
        $_REQUEST['optionsValueField'] = '';
        $_REQUEST['optionsLabelField'] = '';
    }
    if (@$_REQUEST['optionsType'] != 'query') {
        $_REQUEST['optionsQuery'] = '';
        $_REQUEST['filterField'] = '';
    }
    if (@(!$_REQUEST['useCustomUploadDir'])) {
        $_REQUEST['customUploadDir'] = '';
        $_REQUEST['customUploadUrl'] = '';
    }
    // update field schema (save these fields)
    $fieldsIgnoredIfEmpty = array('customColumnType', 'isSystemField', 'adminOnly', 'optionsText', 'optionsTablename', 'optionsValueField', 'optionsLabelField', 'optionsQuery', 'filterField', 'myAccountField');
    // these fields aren't saved if they are blank or zero
    $fieldAttributesByType = array('none' => array('customColumnType', 'order', 'label', 'type', 'isSystemField', 'adminOnly', 'myAccountField', 'indexed'), 'textfield' => array('customColumnType', 'order', 'label', 'type', 'isSystemField', 'adminOnly', 'myAccountField', 'indexed', 'defaultValue', 'fieldPrefix', 'description', 'fieldWidth', 'isPasswordField', 'isRequired', 'isUnique', 'minLength', 'maxLength', 'charsetRule', 'charset'), 'textbox' => array('customColumnType', 'order', 'label', 'type', 'isSystemField', 'adminOnly', 'myAccountField', 'indexed', 'defaultContent', 'fieldPrefix', 'description', 'isRequired', 'isUnique', 'minLength', 'maxLength', 'fieldHeight', 'autoFormat'), 'date' => array('customColumnType', 'order', 'label', 'type', 'isSystemField', 'adminOnly', 'myAccountField', 'indexed', 'fieldPrefix', 'description', 'isRequired', 'isUnique', 'defaultDate', 'defaultDateString', 'showTime', 'showSeconds', 'use24HourFormat', 'yearRangeStart', 'yearRangeEnd'), 'list' => array('customColumnType', 'order', 'label', 'type', 'isSystemField', 'adminOnly', 'myAccountField', 'indexed', 'defaultValue', 'fieldPrefix', 'description', 'isRequired', 'isUnique', 'listType', 'optionsType', 'optionsText', 'optionsTablename', 'optionsValueField', 'optionsLabelField', 'optionsQuery', 'filterField'), 'checkbox' => array('customColumnType', 'order', 'label', 'type', 'isSystemField', 'adminOnly', 'myAccountField', 'indexed', 'fieldPrefix', 'checkedByDefault', 'description', 'checkedValue', 'uncheckedValue'), 'upload' => array('customColumnType', 'order', 'label', 'type', 'isSystemField', 'adminOnly', 'myAccountField', 'fieldPrefix', 'description', 'isRequired', 'allowedExtensions', 'checkMaxUploadSize', 'maxUploadSizeKB', 'checkMaxUploads', 'maxUploads', 'resizeOversizedImages', 'maxImageHeight', 'maxImageWidth', 'createThumbnails', 'maxThumbnailHeight', 'maxThumbnailWidth', 'createThumbnails2', 'maxThumbnailHeight2', 'maxThumbnailWidth2', 'createThumbnails3', 'maxThumbnailHeight3', 'maxThumbnailWidth3', 'createThumbnails4', 'maxThumbnailHeight4', 'maxThumbnailWidth4', 'useCustomUploadDir', 'customUploadDir', 'customUploadUrl', 'infoField1', 'infoField2', 'infoField3', 'infoField4', 'infoField5'), 'wysiwyg' => array('customColumnType', 'order', 'label', 'type', 'isSystemField', 'adminOnly', 'myAccountField', 'indexed', 'fieldPrefix', 'description', 'defaultContent', 'allowUploads', 'isRequired', 'isUnique', 'minLength', 'maxLength', 'fieldHeight', 'allowedExtensions', 'checkMaxUploadSize', 'maxUploadSizeKB', 'checkMaxUploads', 'maxUploads', 'resizeOversizedImages', 'maxImageHeight', 'maxImageWidth', 'createThumbnails', 'maxThumbnailHeight', 'maxThumbnailWidth', 'createThumbnails2', 'maxThumbnailHeight2', 'maxThumbnailWidth2', 'createThumbnails3', 'maxThumbnailHeight3', 'maxThumbnailWidth3', 'createThumbnails4', 'maxThumbnailHeight4', 'maxThumbnailWidth4', 'useCustomUploadDir', 'customUploadDir', 'customUploadUrl'), 'separator' => array('customColumnType', 'order', 'label', 'type', 'isSystemField', 'adminOnly', 'myAccountField', 'separatorType', 'separatorHeader', 'separatorHTML'), 'relatedRecords' => array('order', 'label', 'type', 'relatedTable', 'relatedLimit', 'relatedView', 'relatedModify', 'relatedErase', 'relatedCreate', 'relatedWhere', 'relatedMoreLink', 'isSystemField', 'adminOnly', 'myAccountField'), 'parentCategory' => array('customColumnType', 'order', 'label', 'type', 'isSystemField', 'adminOnly', 'myAccountField', 'indexed'));
    foreach ($fieldAttributesByType[$_REQUEST['type']] as $name) {
        $value = array_key_exists($name, $_REQUEST) ? $_REQUEST[$name] : $GLOBALS['defaultValue'][$name];
        // use default value if no form value defined (such as with quick add)
        $skipField = empty($value) && in_array($name, $fieldsIgnoredIfEmpty);
        // don't record empty values for these fields
        if (!$skipField) {
            $schema[$newColumnName][$name] = $value;
        }
    }
    //
    $tablenameWithoutPrefix = getTableNameWithoutPrefix($GLOBALS['tableName']);
    // save field schema
    saveSchema($_REQUEST['tableName'], $schema);
}
function eraseField()
{
    global $TABLE_PREFIX, $schema;
    //
    security_dieUnlessPostForm();
    security_dieUnlessInternalReferer();
    security_dieOnInvalidCsrfToken();
    //
    disableInDemoMode('', 'ajax');
    $tableName = $_REQUEST['tableName'];
    $fieldname = $_REQUEST['fieldname'];
    if (!$tableName) {
        die("no tableName specified!\n");
    }
    if (!$fieldname) {
        die("no tableName specified!\n");
    }
    // erase from schema
    unset($schema[$fieldname]);
    saveSchema($tableName, $schema);
    // erase from mySQL
    $columnType = getMysqlColumnType($tableName, $fieldname);
    if ($columnType != '') {
        $result = mysql_query("ALTER TABLE `" . mysql_escape($tableName) . "`\n                              DROP COLUMN `" . mysql_escape($fieldname) . "`") or die("There was an error removing the MySQL Column, the error was:\n\n" . htmlencode(mysql_error()) . "\n");
    }
    // expire uploads (mark files for erasing by blanking out fieldname - they get erased when upload form is submitted)
    $tableNameWithoutPrefix = getTableNameWithoutPrefix($tableName);
    $query = "UPDATE `{$TABLE_PREFIX}uploads`";
    $query .= "   SET fieldName = ''";
    $query .= " WHERE fieldName = '" . mysql_escape($fieldname) . "' AND";
    $query .= "       tableName = '" . mysql_escape($tableNameWithoutPrefix) . "'";
    mysql_query($query) or die("There was an error erasing old uploads:\n\n" . htmlencode(mysql_error()) . "\n");
    // this function is called via ajax.  Output is returned as errors via javascript alert.  Output nothing on success.
    exit;
}
function addTable()
{
    global $TABLE_PREFIX, $APP;
    $menuType = @$_REQUEST['menuType'];
    $presetTableName = @$_REQUEST['presetName'];
    $advancedType = @$_REQUEST['advancedType'];
    //
    security_dieUnlessPostForm();
    security_dieUnlessInternalReferer();
    security_dieOnInvalidCsrfToken();
    //
    disableInDemoMode('', 'ajax');
    // error checking
    $errors = '';
    if (!$menuType) {
        $errors .= "No menu type selected!\n";
    }
    if (!@$_REQUEST['menuName']) {
        $errors .= "No menu name specified!\n";
    }
    $errors .= getTablenameErrors(@$_REQUEST['tableName']);
    $newSchema = null;
    if ($menuType == 'copy') {
        if ($errors) {
            die($errors);
        }
        $sourceSchemaName = @$_REQUEST['copy'];
        if (!in_array($sourceSchemaName, getSchemaTables())) {
            die("Couldn't load source schema");
        }
        $newSchema = loadSchema($sourceSchemaName) or die("Couldn't load source schema");
    } else {
        if ($menuType == 'preset') {
            $schemaPresets = getSchemaPresets();
            $presetFound = array_key_exists(@$_REQUEST['preset'], $schemaPresets);
            if (!@$_REQUEST['preset']) {
                $errors .= "You must select a preset from the pulldown!\n";
            } elseif (!$presetFound) {
                $errors .= "No schema preset file found for '" . htmlencode($presetTableName) . "'\n";
            }
        }
        if ($errors) {
            die($errors);
        }
        // create new schema data
        if ($menuType == 'single') {
            $presetTableName = "customSingle";
        } elseif ($menuType == 'multi') {
            $presetTableName = "customMulti";
        } elseif ($menuType == 'preset') {
            $presetTableName = @$_REQUEST['preset'];
        } elseif ($menuType == 'advanced' && $advancedType == 'category') {
            $presetTableName = "customCategory";
        } elseif ($menuType == 'advanced' && $advancedType == 'textlink') {
            $presetTableName = "customTextLink";
        } elseif ($menuType == 'advanced' && $advancedType == 'menugroup') {
            $presetTableName = "customMenuGroup";
        } else {
            die("Unable to determine preset table name to load!");
        }
        $schemaPresetDir = DATA_DIR . "/schemaPresets/";
        $newSchema = loadSchema($presetTableName, $schemaPresetDir) or die("Couldn't load preset schema");
    }
    $newSchema['menuName'] = @$_REQUEST['menuName'];
    // change menu name
    $newSchema['menuOrder'] = time();
    // use time to sort to bottom
    // create mysql table
    // (this isn't required but done here so we catch get mysql errors creating the table)
    // createMissingSchemaTablesAndFields() creates if this doesn't.
    $tableNameWithPrefix = $TABLE_PREFIX . @$_REQUEST['tableName'];
    $result = mysql_query("CREATE TABLE `" . mysql_escape($tableNameWithPrefix) . "` (\n                                          num int(10) unsigned NOT NULL auto_increment,\n                                          PRIMARY KEY (num)\n                                        ) ENGINE=MyISAM DEFAULT CHARSET=utf8;");
    if (!$result) {
        print "Error creating MySQL table.\n\nMySQL error was: " . htmlencode(mysql_error()) . "\n";
        exit;
    }
    // save new schema
    saveSchema(@$_REQUEST['tableName'], $newSchema);
    // Create schema table and fields in MySQL
    createMissingSchemaTablesAndFields();
    clearAlertsAndNotices();
    // don't display alerts about adding new fields
    exit;
    // this is called with ajax so returning nothing means success - see: addTable_functions.js - initSubmitFormWithAjax
}
function createSchemaFromFile($sourcePath)
{
    $schemaFile = basename($sourcePath);
    $targetPath = realpath(DATA_DIR . '/schema') . '/' . $schemaFile;
    static $order;
    if (!$order) {
        $order = time();
    }
    $order++;
    // add schema sequentially, even if adding them in the same second.
    // copy schema file
    if (file_exists($targetPath)) {
        return;
    }
    @copy($sourcePath, $targetPath) or die(t("Error copying schema file!") . $php_errormsg);
    // update schema file
    list($tableName) = explode('.', $schemaFile);
    $schema = loadSchema($tableName);
    $schema['menuOrder'] = $order;
    // sort new schema to the bottom
    saveSchema($tableName, $schema);
    // display message and create tables
    createMissingSchemaTablesAndFields();
    // create mysql tables
}