function getRecord($options)
{
    global $VIEWER_NAME, $TABLE_PREFIX;
    $VIEWER_NAME = "Page Viewer ({$options['tableName']})";
    // error checking
    $requiredOptions = array('tableName');
    $validOptions = array('tableName', 'recordNum', 'where', 'titleField', 'orderBy');
    $errors = _getOptionErrors($requiredOptions, $validOptions, $options);
    if ($errors) {
        die("{$VIEWER_NAME} errors<br/>\n{$errors}");
    }
    // set defaults
    $schema = loadSchema($options['tableName']);
    if (!@$options['recordNum']) {
        $options['recordNum'] = getLastNumberInUrl();
    }
    if (@$schema['menuType'] == 'single') {
        $options['recordNum'] = "1";
    }
    // always load record 1 for single menus
    // get where condition
    $whereConditions = '';
    $escapedRecordNum = mysql_escape((int) $options['recordNum']);
    if ($options['where']) {
        $whereConditions = $options['where'];
    } elseif ($options['recordNum']) {
        $whereConditions = "num = '{$escapedRecordNum}'";
    }
    // get record
    $fullTableName = getTableNameWithPrefix($options['tableName']);
    $escapedTableName = mysql_escape($fullTableName);
    $where = _addWhereConditionsForSpecialFields($schema, $whereConditions, $options);
    $orderBy = @$options['orderBy'] ? "ORDER BY {$options['orderBy']}" : '';
    $query = "SELECT * FROM `{$escapedTableName}` {$where} {$orderBy} LIMIT 0, 1";
    $result = mysql_query($query) or die("{$VIEWER_NAME}: MySQL Error: " . htmlencode(mysql_error()) . "\n");
    $record = mysql_fetch_assoc($result);
    // add _link field
    if ($record) {
        $filenameValue = getFilenameFieldValue($record, @$options['titleField']);
        $record['_link'] = _getLink($_SERVER['SCRIPT_NAME'], $filenameValue, $record['num'], @$options['useSeoUrls']);
    }
    // define upload fields
    if ($record) {
        foreach ($schema as $fieldname => $fieldSchema) {
            if (!is_array($fieldSchema)) {
                continue;
            }
            // not a field definition, table metadata field
            if (@$fieldSchema['type'] != 'upload') {
                continue;
            }
            // skip all but upload fields
            $record[$fieldname] = "Use getUploads() function to list uploads (See code generator).\n";
        }
    }
    //
    return $record;
}
function getCategories($options)
{
    $VIEWER_NAME = "Category Viewer ({$options['tableName']})";
    // error checking
    $errors = '';
    $validOptions = array('tableName', 'useSeoUrls', 'debugSql', 'selectedCategoryNum', 'categoryFormat', 'loadUploads', 'defaultCategory', 'rootCategoryNum', 'ulAttributes', 'ulAttributesCallback', 'liAttributesCallback', 'loadCreatedBy', 'ignoreHidden');
    $validFormats = array('', 'showall', 'onelevel', 'twolevel', 'breadcrumb');
    if (!is_array($options)) {
        $errors .= "First argument for getRecords() must be an array!<br/>\n";
    } elseif (!@$options['tableName']) {
        $errors .= "No 'tableName' value specified in options!<br/>\n";
    } else {
        // check options are value
        $unknownOptions = array_diff(array_keys($options), $validOptions);
        foreach ($unknownOptions as $optionName) {
            $errors .= "Unknown option '{$optionName}' specified<br/>\n";
        }
        if ($unknownOptions) {
            $errors .= "Valid option names are: (" . join(', ', $validOptions) . ")<br/>\n";
        }
    }
    if (!in_array(@$options['categoryFormat'], $validFormats)) {
        $errors .= "categoryFormat must be one of: " . join(", ", $validFormats) . "<br/>\n";
    }
    if ($errors) {
        die("{$VIEWER_NAME} errors<br/>\n{$errors}");
    }
    // set defaults
    $options['loadUploads'] = array_key_exists('loadUploads', $options) ? $options['loadUploads'] : true;
    // default to true
    $options['ignoreHidden'] = array_key_exists('ignoreHidden', $options) ? $options['ignoreHidden'] : false;
    // default to false
    // create where
    $where = '';
    if (@$options['rootCategoryNum']) {
        $rootCategoryNum = (int) $options['rootCategoryNum'];
        $where = " lineage LIKE '%:{$rootCategoryNum}:%' AND num != '{$rootCategoryNum}' ";
    }
    //
    list($categoryRecords) = getRecords(array('tableName' => $options['tableName'], 'loadUploads' => $options['loadUploads'], 'where' => $where, 'allowSearch' => false, 'loadCreatedBy' => @$options['loadCreatedBy'], 'loadListDetails' => false, 'useSeoUrls' => @$options['useSeoUrls'], 'debugSql' => @$options['debugSql'], 'ignoreHidden' => @$options['ignoreHidden']));
    // set defaults (for category display)
    if (!array_key_exists('selectedCategoryNum', $options) || @$options['selectedCategoryNum'] == '') {
        if (getLastNumberInUrl()) {
            $options['selectedCategoryNum'] = getLastNumberInUrl();
        } else {
            if (@$options['defaultCategory'] == 'first') {
                $options['selectedCategoryNum'] = @$categoryRecords[0]['num'];
            } else {
                if ((int) @$options['defaultCategory']) {
                    $options['selectedCategoryNum'] = $options['defaultCategory'];
                }
            }
        }
    }
    if (!@$options['categoryFormat']) {
        $options['categoryFormat'] = 'showall';
    }
    // get selectedCategory and root depth
    $selectedCategory = array();
    $rootDepth = 999;
    for ($i = 0; $i < count($categoryRecords); $i++) {
        $category =& $categoryRecords[$i];
        if ($category['num'] == @$options['selectedCategoryNum']) {
            $selectedCategory =& $category;
        }
        if (@$category['depth'] != '') {
            $rootDepth = min($rootDepth, $category['depth']);
        }
        unset($category);
    }
    // reduce category depths if only showing part of tree (so we don't print extra <ul></ul> tags)
    if (@$options['rootCategoryNum'] && $rootDepth) {
        for ($i = 0; $i < count($categoryRecords); $i++) {
            $category =& $categoryRecords[$i];
            $category['depth'] -= $rootDepth;
        }
        unset($category);
    }
    // get category format rules
    if ($options['categoryFormat'] == 'showall') {
        $rootDepthVisible = 'all';
        $childDepthVisible = 'all';
        $parentVisibility = 'parentBranches';
    } else {
        if ($options['categoryFormat'] == 'onelevel') {
            $rootDepthVisible = '1';
            $childDepthVisible = '1';
            $parentVisibility = 'parentBranches';
        } else {
            if ($options['categoryFormat'] == 'twolevel') {
                $rootDepthVisible = '2';
                $childDepthVisible = '1';
                $parentVisibility = 'parentBranches';
            } else {
                if ($options['categoryFormat'] == 'breadcrumb') {
                    $rootDepthVisible = '0';
                    $childDepthVisible = '0';
                    $parentVisibility = 'parentsOnly';
                } else {
                    die("Unknown category format '" . htmlencode($options['categoryFormat']) . "'!");
                }
            }
        }
    }
    // get category meta-details
    $categoryNumHasChildren = array();
    $firstChildOf = array();
    $lastChildOf = array();
    foreach ($categoryRecords as $record) {
        $categoryNumHasChildren[$record['parentNum']] = 1;
        if (!array_key_exists($record['parentNum'], $firstChildOf)) {
            $firstChildOf[$record['parentNum']] = $record['num'];
        }
        $lastChildOf[$record['parentNum']] = $record['num'];
    }
    // remove categories we aren't displaying
    $visibleCategoryRecords = array();
    for ($index = 0; $index < count($categoryRecords); $index++) {
        $category =& $categoryRecords[$index];
        $showThisCategory = _categoryMatchesFormatRules($category, $selectedCategory, $rootDepthVisible, $childDepthVisible, $parentVisibility);
        if (!$showThisCategory) {
            continue;
        }
        // skip categories not matching categoryFormat rules
        $visibleCategoryRecords[] =& $category;
        unset($category);
    }
    $categoryRecords = $visibleCategoryRecords;
    // get displayed categories
    $displayedCategories = array();
    for ($index = 0; $index < count($categoryRecords); $index++) {
        $category =& $categoryRecords[$index];
        // add pseudo fields
        $prevCategory = $index > 0 ? $categoryRecords[$index - 1] : array();
        $nextCategory = $index < count($categoryRecords) - 1 ? $categoryRecords[$index + 1] : array();
        @(list($selectedRootNum) = preg_split('/:/', @$selectedCategory['lineage'], -1, PREG_SPLIT_NO_EMPTY));
        // root num of selected records branch
        $category['_isSelected'] = (int) ($category['num'] == @$options['selectedCategoryNum']);
        $category['_isAncestorSelected'] = $selectedCategory && preg_match("/:{$selectedCategory['num']}:/", $category['lineage']) && !$category['_isSelected'];
        $category['_isDescendantSelected'] = $selectedCategory && preg_match("/:{$category['num']}:/", $selectedCategory['lineage']) && !$category['_isSelected'];
        $category['_isSelectedBranch'] = $selectedCategory && preg_match("/:{$selectedRootNum}:/", $category['lineage']);
        $category['_isBreadcrumb'] = $category['_isDescendantSelected'] || $category['_isSelected'];
        $category['_hasParent'] = (int) ($category['parentNum'] != 0);
        $category['_hasChild'] = (int) @$categoryNumHasChildren[$category['num']];
        $category['_isFirstChild'] = intval($firstChildOf[$category['parentNum']] == $category['num']);
        $category['_isLastChild'] = intval($lastChildOf[$category['parentNum']] == $category['num']);
        $category['_hasSiblings'] = intval(!($category['_isFirstChild'] && $category['_isLastChild']));
        // added in 2.10
        $category['_isSiblingSelected'] = intval($selectedCategory && $selectedCategory['parentNum'] == $category['parentNum'] && !$category['_isSelected']);
        $category['_isParentSelected'] = intval($selectedCategory && $selectedCategory['num'] == $category['parentNum']);
        $category['_isChildSelected'] = intval($selectedCategory && $category['num'] == $selectedCategory['parentNum']);
        $category['_listItemStart'] = _getListItemStartTags($prevCategory, $category, $nextCategory, $options);
        $category['_listItemEnd'] = _getListItemEndTags($prevCategory, $category, $nextCategory);
        //
        $displayedCategories[] =& $category;
        unset($category);
    }
    // restore category depths if only showing part of tree (done above so we don't print extra <ul></ul> tags)
    if (@$options['rootCategoryNum'] && $rootDepth) {
        for ($i = 0; $i < count($categoryRecords); $i++) {
            $category =& $categoryRecords[$i];
            $category['depth'] += $rootDepth;
        }
        unset($category);
    }
    //
    return array($displayedCategories, $selectedCategory);
}
function getNumberFromEndOfUrl()
{
    return getLastNumberInUrl();
}