Example #1
0
function getWhereClauseParams($table, $searchName, $search, $searchType, &$langKeys)
{
    global $ALLOWED_NUMERIC_QUERY_OPERATORS, $ALLOWED_STRING_QUERY_OPERATORS;
    $haveAnyFulltextQueryOperators = false;
    $searchWhereClausePHP = '';
    $searchWhereClauseOr = '';
    $searchWhereClauseIndent = $searchType == 'autocomplete' ? "\n\t\t\t" : "\n\t\t";
    $searchWhereClauseConcat = '';
    $searchWhereAssignments = array();
    $searchWhereAssignmentsIndent = $searchType == 'autocomplete' ? "\t\t" : "\t";
    $searchableColumnsPHPArray = "array(\n";
    $searchableColumnsPHPArraySep = '';
    if (isset($search['searchableColumns'])) {
        foreach ($search['searchableColumns'] as $key => $column) {
            $queryOperator = isset($column['queryOperator']) ? $column['queryOperator'] : '';
            if ($queryOperator == 'fulltext') {
                $haveAnyFulltextQueryOperators = true;
                break;
            }
        }
        foreach ($search['searchableColumns'] as $key => $column) {
            $colName = isset($column['columnName']) ? $column['columnName'] : $key;
            $pfx = isset($column['tableAlias']) ? $column['tableAlias'] : 'pri';
            if ($pfx != '') {
                $pfx = $pfx .= '.';
            }
            $title = isset($column['title']) ? $column['title'] : '';
            if ($title == '') {
                $title = identifierToHumanReadable($colName);
            }
            $sqlType = isset($column['sqlType']) ? $column['sqlType'] : '';
            $queryOperator = isset($column['queryOperator']) ? $column['queryOperator'] : '';
            switch ($sqlType) {
                case 'integer':
                case 'smallint':
                case 'bigint':
                case 'decimal':
                    if (!in_array($queryOperator, $ALLOWED_NUMERIC_QUERY_OPERATORS)) {
                        $queryOperator = '=';
                    }
                    if (isset($column['unsignedSearch']) && $column['unsignedSearch']) {
                        if ($searchType == 'search') {
                            $searchWhereClausePHP .= $searchWhereClauseIndent . $searchWhereClauseConcat . var_export_normal_precision($searchWhereClauseOr . '(? <> 0 and abs(' . $pfx . $colName . ') ' . $queryOperator . ' ?)', true);
                            if ($searchWhereClauseOr == '') {
                                $searchWhereClauseOr = ' or ';
                            }
                            if ($searchWhereClauseConcat == '') {
                                $searchWhereClauseConcat = '.';
                            }
                            $searchWhereAssignments[] = $searchWhereAssignmentsIndent . "\$ps->setInt(((\$queryCol == '') || (\$queryCol == " . var_export_normal_precision($pfx . $colName, true) . ")) ? 1 : 0);";
                        } else {
                            $searchWhereClausePHP .= $searchWhereClauseIndent . $searchWhereClauseConcat . var_export_normal_precision($searchWhereClauseOr . 'abs(' . $pfx . $colName . ') ' . $queryOperator . ' ?', true);
                            if ($searchWhereClauseOr == '') {
                                $searchWhereClauseOr = ' or ';
                            }
                            if ($searchWhereClauseConcat == '') {
                                $searchWhereClauseConcat = '.';
                            }
                        }
                        if ($sqlType == 'decimal') {
                            $searchWhereAssignments[] = $searchWhereAssignmentsIndent . "\$ps->setDouble(abs((double)trim(\$query)));";
                        } else {
                            $searchWhereAssignments[] = $searchWhereAssignmentsIndent . "\$ps->setInt(abs((int)trim(\$query)));";
                        }
                    } else {
                        if ($searchType == 'search') {
                            $searchWhereClausePHP .= $searchWhereClauseIndent . $searchWhereClauseConcat . var_export_normal_precision($searchWhereClauseOr . '(? <> 0 and ' . $pfx . $colName . ' ' . $queryOperator . ' ?)', true);
                            if ($searchWhereClauseOr == '') {
                                $searchWhereClauseOr = ' or ';
                            }
                            if ($searchWhereClauseConcat == '') {
                                $searchWhereClauseConcat = '.';
                            }
                            $searchWhereAssignments[] = $searchWhereAssignmentsIndent . "\$ps->setInt(((\$queryCol == '') || (\$queryCol == " . var_export_normal_precision($pfx . $colName, true) . ")) ? 1 : 0);";
                        } else {
                            $searchWhereClausePHP .= $searchWhereClauseIndent . $searchWhereClauseConcat . var_export_normal_precision($searchWhereClauseOr . $pfx . $colName . ' ' . $queryOperator . ' ?', true);
                            if ($searchWhereClauseOr == '') {
                                $searchWhereClauseOr = ' or ';
                            }
                            if ($searchWhereClauseConcat == '') {
                                $searchWhereClauseConcat = '.';
                            }
                        }
                        if ($sqlType == 'decimal') {
                            $searchWhereAssignments[] = $searchWhereAssignmentsIndent . "\$ps->setDouble(\$query);";
                        } else {
                            $searchWhereAssignments[] = $searchWhereAssignmentsIndent . "\$ps->setInt(\$query);";
                        }
                    }
                    $langKey = $searchName . '_search.searchableColumn.' . $colName . '.title';
                    if (is_array($langKeys)) {
                        $langKeys[$langKey] = $title;
                    }
                    $searchableColumnsPHPArray .= sprintf(<<<EOF
%s\t\t(object)array(
\t\t\t'name'=>%s,
\t\t\t'pfx'=>%s,
\t\t\t'title'=>_t(%s, %s),
\t\t\t'sqlType'=>%s,
\t\t\t'queryOperator'=>%s,
\t\t),
EOF
, $searchableColumnsPHPArraySep, var_export_normal_precision($colName, true), var_export_normal_precision($pfx, true), var_export_normal_precision($langKey, true), var_export_normal_precision($title, true), var_export_normal_precision($sqlType, true), var_export_normal_precision($queryOperator, true));
                    if ($searchableColumnsPHPArraySep == '') {
                        $searchableColumnsPHPArraySep = "\n";
                    }
                    break;
                case 'char':
                case 'varchar':
                case 'text':
                    if (!in_array($queryOperator, $ALLOWED_STRING_QUERY_OPERATORS)) {
                        $queryOperator = '=';
                    }
                    if ($queryOperator == 'beginsWith' || $queryOperator == 'contains' || $queryOperator == 'endsWith' || $queryOperator == 'fulltext') {
                        if ($searchType == 'search') {
                            $tmppfx = '(? <> 0 and (';
                            $tmpsfx = '.\'))\'';
                        } else {
                            $tmppfx = $tmpsfx = '';
                        }
                        $searchWhereClausePHP .= $searchWhereClauseIndent . $searchWhereClauseConcat . ($searchWhereClauseOr != '' || $tmppfx != '' ? var_export_normal_precision($searchWhereClauseOr . $tmppfx, true) . '.' : '');
                        if ($queryOperator == 'fulltext') {
                            $searchWhereClausePHP .= '($canDoFulltextSearch ? (' . var_export_normal_precision('? = \'\' or match(' . $pfx . $colName . ') against (? in boolean mode)', true) . ') : (';
                        }
                        $searchWhereClausePHP .= '($db->hasCaseInsensitiveLike ? ' . var_export_normal_precision($pfx . $colName, true) . ' : ' . var_export_normal_precision('lower(' . $pfx . $colName . ')', true) . ')' . '.\' \'.$db->likeOperator.\' \'.' . '($db->hasCaseInsensitiveLike ? \'?\' : \'lower(?)\')';
                        if ($queryOperator == 'fulltext') {
                            $searchWhereClausePHP .= '))';
                        }
                        $searchWhereClausePHP .= $tmpsfx;
                        if ($searchWhereClauseOr == '') {
                            $searchWhereClauseOr = ' or ';
                        }
                        if ($searchWhereClauseConcat == '') {
                            $searchWhereClauseConcat = '.';
                        }
                    } else {
                        $searchWhereClausePHP .= $searchWhereClauseOr . '(? <> 0 and ' . $pfx . $colName . ' ' . $queryOperator . ' ?)';
                        if ($searchWhereClauseOr == '') {
                            $searchWhereClauseOr = ' or ';
                        }
                        if ($searchWhereClauseConcat == '') {
                            $searchWhereClauseConcat = '.';
                        }
                    }
                    if ($searchType == 'search') {
                        $searchWhereAssignments[] = $searchWhereAssignmentsIndent . "\$ps->setInt(((\$queryCol == '') || (\$queryCol == " . var_export_normal_precision($pfx . $colName, true) . ")) ? 1 : 0);";
                    }
                    if ($queryOperator == 'beginsWith') {
                        $searchWhereAssignments[] = $searchWhereAssignmentsIndent . "\$ps->setString(\$query.'%');";
                    } else {
                        if ($queryOperator == 'contains') {
                            $searchWhereAssignments[] = $searchWhereAssignmentsIndent . "\$ps->setString('%'.\$query.'%');";
                        } else {
                            if ($queryOperator == 'endsWith') {
                                $searchWhereAssignments[] = $searchWhereAssignmentsIndent . "\$ps->setString('%'.\$query);";
                            } else {
                                if ($queryOperator == 'fulltext') {
                                    $searchWhereAssignments[] = $searchWhereAssignmentsIndent . "if (\$canDoFulltextSearch) { \$ps->setString(\$ftquery); \$ps->setString(\$ftquery); } else { \$ps->setString('%'.\$query.'%'); }";
                                } else {
                                    $searchWhereAssignments[] = $searchWhereAssignmentsIndent . "\$ps->setString(\$query);";
                                }
                            }
                        }
                    }
                    $langKey = $searchName . '_search.searchableColumn.' . $colName . '.title';
                    if (is_array($langKeys)) {
                        $langKeys[$langKey] = $title;
                    }
                    $searchableColumnsPHPArray .= sprintf(<<<EOF
%s\t\t(object)array(
\t\t\t'name'=>%s,
\t\t\t'pfx'=>%s,
\t\t\t'title'=>_t(%s, %s),
\t\t\t'sqlType'=>%s,
\t\t\t'queryOperator'=>%s,
\t\t),
EOF
, $searchableColumnsPHPArraySep, var_export_normal_precision($colName, true), var_export_normal_precision($pfx, true), var_export_normal_precision($langKey, true), var_export_normal_precision($title, true), var_export_normal_precision($sqlType, true), var_export_normal_precision($queryOperator, true));
                    if ($searchableColumnsPHPArraySep == '') {
                        $searchableColumnsPHPArraySep = "\n";
                    }
                    break;
                default:
                    fprintf(STDERR, "WARNING: Invalid sqlType \"%s\" in searchable column \"%s\" in \"%s\" %s in %s table YAML file.\n", $sqlType, $colName, $searchName, $searchType, $table->tableName);
            }
        }
    }
    $searchableColumnsPHPArray .= "\n\t)";
    $andWhere = '';
    if (isset($search['andWhere'])) {
        $andWhere = trim((string) $search['andWhere']);
    }
    $andWhereAssignments = array();
    if (isset($search['andWhereAssignments']) && is_array($search['andWhereAssignments'])) {
        foreach ($search['andWhereAssignments'] as $assignment) {
            $expression = isset($assignment['expression']) && is_string($assignment['expression']) ? $assignment['expression'] : "''";
            switch (isset($assignment['psType']) ? $assignment['psType'] : 'string') {
                case 'boolean':
                    $andWhereAssignments[] = "\t\$ps->setBoolean({$expression});";
                    break;
                case 'int':
                    $andWhereAssignments[] = "\t\$ps->setInt({$expression});";
                    break;
                case 'float':
                    $andWhereAssignments[] = "\t\$ps->setFloat({$expression});";
                    break;
                case 'double':
                    $andWhereAssignments[] = "\t\$ps->setDouble({$expression});";
                    break;
                case 'string':
                default:
                    $andWhereAssignments[] = "\t\$ps->setString({$expression});";
                    break;
                case 'match':
                    $andWhereAssignments[] = "\t\$ps->setString('%'.{$expression}.'%');";
                    break;
                case 'binary':
                    $andWhereAssignments[] = "\t\$ps->setBinary({$expression});";
                    break;
            }
        }
    }
    return array('haveAnyFulltextQueryOperators' => $haveAnyFulltextQueryOperators, 'searchWhereClausePHP' => $searchWhereClausePHP, 'searchWhereAssignments' => $searchWhereAssignments, 'searchableColumnsPHPArray' => $searchableColumnsPHPArray, 'searchableColumnsPHPArraySep' => $searchableColumnsPHPArraySep, 'andWhere' => $andWhere, 'andWhereAssignments' => $andWhereAssignments);
}
Example #2
0
function processTable($table, $idCol, $cfg)
{
    global $SEARCH_PRESENTATION_DATATABLES_ONLY_PARAMS, $SEARCH_PRESENTATION_DATATABLES_ONLY_COLUMN_PARAMS, $SEARCH_PRESENTATION_AJAXSEARCHGRID_ONLY_PARAMS, $SEARCH_PRESENTATION_AJAXSEARCHGRID_ONLY_COLUMN_PARAMS, $templatesDir, $cfgDir, $docroot, $oldFileLayout, $enableLangFiles;
    $generatedFileMessage = <<<EOF
// DO NOT EDIT THIS FILE.
// This file was generated by crudgen.
// If you need to customize this file, please edit the corresponding
// yaml file in the gencfg directory, and then re-generate this file
// by running crudgen, passing in the table name.
EOF;
    $mainOkCheck = $oldFileLayout ? '' : "\nif ((!isset(\$mainOk)) || (!\$mainOk)) exit();\n";
    $jaxInclude = $cfg['jaxInclude'];
    $jaxJQuery = $cfg['jaxJQuery'];
    $jaxJS = $cfg['jaxJS'];
    $classAutoloadPaths = isset($cfg['classAutoloadPaths']) ? trim($cfg['classAutoloadPaths']) : '';
    $loggedInId = isset($cfg['loggedInId']) ? trim($cfg['loggedInId']) : '';
    if ($loggedInId == '') {
        $loggedInId = '$loggedInUser->id';
    }
    if ($classAutoloadPaths != '') {
        $classAutoloadPathsInit = sprintf("\$__CLASS_AUTO_LOAD_CLASS_PATHS = %s;\n", $classAutoloadPaths);
    } else {
        $classAutoloadPathsInit = '';
    }
    if (!isset($cfg['cruds']) || !is_array($cfg['cruds'])) {
        $cfg['cruds'] = array();
    }
    foreach ($cfg['cruds'] as $crudName => $crud) {
        $mainHooksInclude = $oldFileLayout ? "@include './{$crudName}_hooks.include.php';\n" : '';
        $viewHooksInclude = $oldFileLayout ? "@include './{$crudName}_view_hooks.include.php';\n" : '';
        if ($oldFileLayout) {
            $controllerIncludes = <<<EOF
\t\$headerScripts[] = str_replace('//', '/', dirname(\$_SERVER['SCRIPT_NAME']).'/{$crudName}_controller.js');
\tif (file_exists(dirname(__FILE__).'/{$crudName}_controller_hooks.js')) {
\t\t\$headerScripts[] = str_replace('//', '/', dirname(\$_SERVER['SCRIPT_NAME']).'/{$crudName}_controller_hooks.js');
\t}

EOF;
        } else {
            $controllerIncludes = <<<EOF
\t\$headerScripts[] = str_replace('//', '/', dirname(\$_SERVER['SCRIPT_NAME']).'/generated/{$crudName}_controller_generated.js');
\tif (file_exists(dirname(dirname(__FILE__)).'/{$crudName}_controller.js')) {
\t\t\$headerScripts[] = str_replace('//', '/', dirname(\$_SERVER['SCRIPT_NAME']).'/{$crudName}_controller.js');
\t}

EOF;
        }
        $langKeys = array();
        if (array_key_exists('tableDescription', $cfg)) {
            $langKeys['crud.' . $crudName . '.tableDescription'] = $cfg['tableDescription'];
        }
        if (array_key_exists('tableDescriptions', $cfg)) {
            $langKeys['crud.' . $crudName . '.tableDescriptions'] = $cfg['tableDescriptions'];
        }
        $outputPath = isset($crud['outputPath']) && is_string($crud['outputPath']) ? $crud['outputPath'] : '';
        $outputDir = $docroot . '/' . $outputPath;
        $generatedOutputDir = $oldFileLayout ? $outputDir : $outputDir . '/generated';
        @mkdir($generatedOutputDir, 0777, true);
        $numDirsDeepUnderHTML = calcDirDepth($outputPath) + ($oldFileLayout ? 0 : 1);
        if (isset($crud['docRootPath'])) {
            $docRootPath = '"' . addcslashes($crud['docRootPath']) . '"';
        } else {
            $docRootPath = 'dirname(__FILE__)';
            for ($i = 0; $i < $numDirsDeepUnderHTML; $i++) {
                $docRootPath = "dirname({$docRootPath})";
            }
        }
        $phpIncludes = getPHPClassesAndIncludes($crud, $numDirsDeepUnderHTML);
        $postInitPHPIncludes = getPostInitPHPIncludes($crud, $numDirsDeepUnderHTML);
        $crudSearchCommand = 'search' . ucfirst($table->tableName) . 's';
        $crudSearchPresentation = 'dataTables';
        $crudSearchTableDisplayColumns = '';
        /// TODO: Build these two up based on the column definitions.
        $crudSearchGridHeaderColumnsHTML = '';
        $crudSearchGridBodyColumnsHTML = '';
        $crudSearchColumnNames = array();
        $crudSearchTableCallbacks = '';
        if (isset($crud['crudSearch']) && is_array($crud['crudSearch'])) {
            $crudSearch = $crud['crudSearch'];
            // If we encounter a "likePopupSearch" attribute for this crudSearch, it must
            // reference an existing popupSearch defined in the same YAML file.
            // When this happens, we merge the popupSearch's attributes with the crudSearch's
            // attributes, allowing the crudSearch's attributes to override the popupSearch's
            // attributes of the same name.
            if (isset($crudSearch['likePopupSearch'])) {
                if (!is_string($crudSearch['likePopupSearch']) || !isset($cfg['popupSearches']) || !isset($cfg['popupSearches'][$crudSearch['likePopupSearch']]) || !is_array($cfg['popupSearches'][$crudSearch['likePopupSearch']])) {
                    fprintf(STDERR, "Invalid likePopupSearch entry in crudSearch section.\n");
                    return false;
                }
                $crudSearch = array_merge($cfg['popupSearches'][$crudSearch['likePopupSearch']], $crudSearch);
            }
            if (isset($crudSearch['searchCommand']) && is_string($crudSearch['searchCommand'])) {
                $crudSearchCommand = $crudSearch['searchCommand'];
            }
            if (isset($crudSearch['searchPresentation']) && is_string($crudSearch['searchPresentation'])) {
                $crudSearchPresentation = $crudSearch['searchPresentation'];
                if ($crudSearchPresentation != 'dataTables' && $crudSearchPresentation != 'AJAXSearchGrid') {
                    $crudSearchPresentation = 'dataTables';
                }
            }
            if ($crudSearchPresentation != 'dataTables') {
                $badattrs = array_intersect(array_keys($crudSearch), $SEARCH_PRESENTATION_DATATABLES_ONLY_PARAMS);
                if (!empty($badattrs)) {
                    fprintf(STDERR, "WARNING: The following popup search / CRUD search attributes are ignored when searchPresentation is not set to dataTables:\n    %s\n", implode("\n    ", $badattrs));
                }
            }
            if ($crudSearchPresentation != 'AJAXSearchGrid') {
                $badattrs = array_intersect(array_keys($crudSearch), $SEARCH_PRESENTATION_AJAXSEARCHGRID_ONLY_PARAMS);
                if (!empty($badattrs)) {
                    fprintf(STDERR, "WARNING: The following popup search / CRUD search attributes are ignored when searchPresentation is not set to AJAXSearchGrid:\n    %s\n", implode("\n    ", $badattrs));
                }
            }
            $badcolattrs_dataTables = array();
            $badcolattrs_AJAXSearchGrid = array();
            if (isset($crudSearch['columns']) && is_array($crudSearch['columns'])) {
                foreach ($crudSearch['columns'] as $col) {
                    if ($crudSearchPresentation != 'dataTables') {
                        $badattrs = array_intersect(array_keys($col), $SEARCH_PRESENTATION_DATATABLES_ONLY_COLUMN_PARAMS);
                        if (!empty($badattrs)) {
                            $badcolattrs_dataTables = array_unique(array_merge($badcolattrs_dataTables, $badattrs));
                        }
                    }
                    if ($crudSearchPresentation != 'AJAXSearchGrid') {
                        $badattrs = array_intersect(array_keys($col), $SEARCH_PRESENTATION_AJAXSEARCHGRID_ONLY_COLUMN_PARAMS);
                        if (!empty($badattrs)) {
                            $badcolattrs_AJAXSearchGrid = array_unique(array_merge($badcolattrs_AJAXSearchGrid, $badattrs));
                        }
                    }
                }
                unset($col);
            }
            if (!empty($badcolattrs_dataTables)) {
                fprintf(STDERR, "WARNING: The following popup search / CRUD search column attributes are ignored when searchPresentation is not set to dataTables:\n    %s\n", implode("\n    ", $badcolattrs_dataTables));
            }
            if (!empty($badcolattrs_AJAXSearchGrid)) {
                fprintf(STDERR, "WARNING: The following popup search / CRUD search column attributes are ignored when searchPresentation is not set to AJAXSearchGrid:\n    %s\n", implode("\n    ", $badcolattrs_AJAXSearchGrid));
            }
            unset($badattrs, $badcolattrs_dataTables, $badcolattrs_AJAXSearchGrid);
            $beforeSearchCallback = isset($crudSearch['beforeSearchCallback']) ? $crudSearch['beforeSearchCallback'] : '';
            $modifyURLCallback = isset($crudSearch['modifyURLCallback']) ? $crudSearch['modifyURLCallback'] : '';
            $afterSearchCallback = isset($crudSearch['afterSearchCallback']) ? $crudSearch['afterSearchCallback'] : '';
            $crudSearchCallbacks = '';
            if ($beforeSearchCallback != '') {
                $crudSearchCallbacks .= ",\n\t\t\tbeforeSearchCallback:{$beforeSearchCallback}";
            }
            if ($modifyURLCallback != '') {
                $crudSearchCallbacks .= ",\n\t\t\tmodifyURLCallback:{$modifyURLCallback}";
            }
            if ($afterSearchCallback != '') {
                $crudSearchCallbacks .= ",\n\t\t\tafterSearchCallback:{$afterSearchCallback}";
            }
            if (!isset($crudSearch['columns']) || !is_array($crudSearch['columns'])) {
                fprintf(STDERR, "Missing or invalid columns list on crudSearch for %s CRUD.  Skipping this CRUD.\n", $crudSearchName);
                continue;
            }
            $crudSearchTableDisplayColumns = getDataTableColumns($crudSearch['columns'], 'crud.' . $crudName, $langKeys);
            if ($crudSearchTableDisplayColumns != '') {
                $crudSearchTableDisplayColumns .= ",\n";
            }
            $crudSearchColumnNames = is_array($crudSearch['columns']) ? array_keys($crudSearch['columns']) : array();
            if (isset($crudSearch['invisibleColumns']) && is_array($crudSearch['invisibleColumns'])) {
                foreach ($crudSearch['invisibleColumns'] as $icn) {
                    if (!in_array($icn, $crudSearchColumnNames)) {
                        $crudSearchColumnNames[] = $icn;
                    }
                }
            }
            $crudSearchColumnFilters = isset($crudSearch['columnFilters']) && is_array($crudSearch['columnFilters']) ? $crudSearch['columnFilters'] : array();
            $tmp = '{';
            $sep = '';
            foreach ($crudSearchColumnFilters as $funcname => $functext) {
                $tmp .= $sep . json_encode($funcname) . ':' . $functext;
                if ($sep == '') {
                    $sep = ',';
                }
            }
            $tmp .= '}';
            $crudSearchColumnFilters = $tmp;
            unset($tmp);
            $crudSearchExtraQueryParams = isset($crudSearch['extraQueryParams']) && is_array($crudSearch['extraQueryParams']) ? (object) $crudSearch['extraQueryParams'] : (object) array();
            list($crudSearchGridHeaderColumnsHTML, $crudSearchGridBodyColumnsHTML) = getAJAXSearchGridColumns($crudSearch['columns'], 'crud.' . $crudName, $langKeys);
            $srch = array();
            $repl = array();
            foreach ($langKeys as $langKey => $text) {
                $srch[] = '<<langkey>>' . $langKey . '<</langkey>>';
                $repl[] = '<?php _e(' . var_export_normal_precision($langKey, true) . ', ' . var_export_normal_precision($text, true) . '); ?' . '>';
            }
            $crudSearchGridHeaderColumnsHTML = str_replace($srch, $repl, $crudSearchGridHeaderColumnsHTML);
            $crudSearchGridBodyColumnsHTML = str_replace($srch, $repl, $crudSearchGridBodyColumnsHTML);
            $crudSearchDefaultSorts = isset($crudSearch['defaultSorts']) && is_array($crudSearch['defaultSorts']) ? $crudSearch['defaultSorts'] : array();
            if (isset($crudSearch['fnDrawCallback']) && is_string($crudSearch['fnDrawCallback'])) {
                $s = trim($crudSearch['fnDrawCallback']);
                if ($s != '') {
                    $crudSearchTableCallbacks .= ', fnDrawCallback: ' . $s;
                }
                unset($s);
            }
            if (isset($crudSearch['fnServerData']) && is_string($crudSearch['fnServerData'])) {
                $s = trim($crudSearch['fnServerData']);
                if ($s != '') {
                    $crudSearchTableCallbacks .= ', fnServerData: ' . $s;
                }
                unset($s);
            }
        }
        $ajaxAutocompleteInitJS = '';
        $ajaxComboboxInitJS = '';
        $openContainers = array();
        $hiddenFormFields = '';
        $formFields = '';
        if (!isset($crud['formFields']) || !is_array($crud['formFields'])) {
            $crud['formFields'] = array();
        }
        foreach ($crud['formFields'] as $fieldName => $field) {
            $thisFormField = '';
            $inputType = isset($field['inputType']) ? $field['inputType'] : 'text';
            if ($inputType == 'select' && isset($field['multiple']) && $field['multiple'] && substr($fieldName, -2) != '[]') {
                fprintf(STDERR, "WARNING: For inputType: select, multiple: Yes, input name must end with '[]' in input \"%s\" in formFields section.\n", $fieldName);
            }
            $id = isset($field['id']) ? $field['id'] : $fieldName;
            if (substr($id, -2) == '[]') {
                $id = trim(substr($id, 0, strlen($id) - 2));
            }
            $titleLangKey = 'crud.' . $crudName . '.form.input.' . $id . '.label';
            $title = isset($field['title']) ? $field['title'] : ucwords(str_replace('_', ' ', $id));
            $langKeys[$titleLangKey] = $title;
            $titleExpr = "_t('" . $titleLangKey . "', " . var_export_normal_precision($title, true) . ")";
            if (($placeholder = isset($field['placeholder']) ? $field['placeholder'] : '') != '') {
                $placeholderLangKey = 'crud.' . $crudName . '.form.input.' . $id . '.placeholder';
                $langKeys[$placeholderLangKey] = $placeholder;
                $placeholderExpr = "<?php _e('" . $placeholderLangKey . "', " . var_export_normal_precision($placeholder, true) . "); ?>";
            } else {
                $placeholderLangKey = '';
                $placeholderExpr = '';
            }
            $onclickTag = isset($field['onclick']) && $field['onclick'] != '' ? ' onclick="' . $field['onclick'] . '"' : '';
            $onchangeTag = isset($field['onchange']) && $field['onchange'] != '' ? ' onchange="' . $field['onchange'] . '"' : '';
            $containerCSSClass = isset($field['containerCSSClass']) && $field['containerCSSClass'] != '' ? trim($field['containerCSSClass']) : '';
            $cssClass = isset($field['cssClass']) && $field['cssClass'] != '' ? trim($field['cssClass']) : '';
            $helpTopic = isset($field['helpTopic']) && $field['helpTopic'] != '' ? trim($field['helpTopic']) : '';
            $onPopupSearch = isset($field['onPopupSearch']) ? trim($field['onPopupSearch']) : '';
            $descriptionField = isset($field['descriptionField']) ? $field['descriptionField'] : '';
            $descriptionFieldSize = isset($field['descriptionFieldSize']) ? (int) $field['descriptionFieldSize'] : 0;
            if ($descriptionFieldSize <= 0) {
                $descriptionFieldSize = 40;
            }
            $descriptionFieldMaxLength = isset($field['descriptionFieldMaxLength']) ? (int) $field['descriptionFieldMaxLength'] : 0;
            if ($descriptionFieldMaxLength < 0) {
                $descriptionFieldMaxLength = 0;
            }
            if ($onPopupSearch != '') {
                if (substr($onPopupSearch, strlen($onPopupSearch) - 1) != ';') {
                    $onPopupSearch .= ';';
                }
                $onPopupSearch .= 'return false;';
                // Default popup search icon (controlled by CSS).
                $searchDescHTML = '<a class="btn btn-default popupSearchLink" href="#" onclick="' . $onPopupSearch . '"><i class="glyphicon glyphicon-search"></i></a>';
            } else {
                $searchDescHTML = '';
            }
            if ($descriptionField != '') {
                $descriptionClassTag = getBootstrapTextInputSizeClass($descriptionFieldSize);
                $descriptionClassTag = trim('form-control ' . $descriptionClassTag);
                if ($descriptionClassTag != '') {
                    $descriptionClassTag = ' class="' . $descriptionClassTag . '"';
                }
                $searchDescHTML .= '&nbsp;-&nbsp;<input type="text" name="' . $descriptionField . '" id="' . $descriptionField . '"' . $descriptionClassTag . ($descriptionFieldSize > 0 ? ' size="' . $descriptionFieldSize . '"' : '') . ($descriptionFieldMaxLength > 0 ? ' maxlength="' . $descriptionFieldMaxLength . '"' : '') . ' readonly="readonly" disabled="disabled"/>';
            }
            // For inputs which use label-before-input layout, open the row and emit the
            // first cell containing the label.  For checkboxes, open the row and emit a
            // blank cell.  In both cases, open the second cell so we're ready to emit
            // the input element.
            switch ($inputType) {
                case 'text':
                case 'password':
                case 'textarea':
                case 'select':
                case 'file':
                case 'radio':
                    $containerCSSClass = trim('form-group ' . $containerCSSClass);
                    $thisFormField .= '<div class="' . $containerCSSClass . '" id="' . $id . 'TR">' . '<label for="' . $id . '" class="col-sm-2 control-label"><?php echo ' . $titleExpr . '; ?>:</label>' . '<div class="col-sm-10">' . '<div id="fieldErrorMsg_' . $id . '" class="fieldErrorMsg"></div>';
                    break;
                case 'checkbox':
                    $containerCSSClass = trim('form-group ' . $containerCSSClass);
                    $thisFormField .= '<div class="' . $containerCSSClass . '" id="' . $id . 'TR">' . '<label class="col-sm-2 control-label"></label>' . '<div class="col-sm-10">' . '<div id="fieldErrorMsg_' . $id . '" class="fieldErrorMsg"></div>' . '<label for="' . $id . '">';
                    break;
            }
            switch ($inputType) {
                case 'hidden':
                    $hiddenFormFields .= '<input type="hidden" name="' . $fieldName . '" id="' . $id . '"/>' . "\n";
                    break;
                case 'text':
                case 'password':
                    $size = isset($field['size']) ? (int) $field['size'] : 0;
                    $maxlength = isset($field['maxlength']) ? (int) $field['maxlength'] : 0;
                    $cssClass = trim('form-control ' . $cssClass);
                    $cssClass = mergeSizeClass($cssClass, getBootstrapTextInputSizeClass($size));
                    $readonlyTag = isset($field['readonly']) && $field['readonly'] ? ' readonly="readonly"' : '';
                    $disabledTag = isset($field['disabled']) && $field['disabled'] ? ' disabled="disabled"' : '';
                    // Disabling the id field causes updates to become inserts.  Don't allow that.
                    if ($fieldName == $idCol) {
                        $disabledTag = '';
                    }
                    $thisFormField .= '<input type="' . $inputType . '" name="' . $fieldName . '" id="' . $id . '"' . ($size > 0 ? ' size="' . $size . '"' : '') . ($maxlength > 0 ? ' maxlength="' . $maxlength . '"' : '') . ($placeholderExpr != '' ? " placeholder=\"{$placeholderExpr}\"" : '') . ($cssClass != '' ? ' class="' . $cssClass . '"' : '') . ($helpTopic != '' ? ' data-help-topic="' . $helpTopic . '"' : '') . $readonlyTag . $disabledTag . $onclickTag . $onchangeTag . '/>' . $searchDescHTML;
                    break;
                case 'textarea':
                    $rows = isset($field['rows']) ? (int) $field['rows'] : 20;
                    $cols = isset($field['cols']) ? (int) $field['cols'] : 20;
                    $cssClass = trim('form-control ' . $cssClass);
                    $cssClass = mergeSizeClass($cssClass, getBootstrapTextInputSizeClass($cols));
                    $readonlyTag = isset($field['readonly']) && $field['readonly'] ? ' readonly="readonly"' : '';
                    $disabledTag = isset($field['disabled']) && $field['disabled'] ? ' disabled="disabled"' : '';
                    // Disabling the id field causes updates to become inserts.  Don't allow that.
                    if ($fieldName == $idCol) {
                        $disabledTag = '';
                    }
                    $thisFormField .= '<textarea name="' . $fieldName . '" id="' . $id . '" rows="' . $rows . '" cols="' . $cols . '"' . ($placeholderExpr != '' ? " placeholder=\"{$placeholderExpr}\"" : '') . ($cssClass != '' ? ' class="' . $cssClass . '"' : '') . ($helpTopic != '' ? ' data-help-topic="' . $helpTopic . '"' : '') . $readonlyTag . $disabledTag . $onclickTag . $onchangeTag . '></textarea>' . $searchDescHTML;
                    break;
                case 'select':
                    $size = isset($field['size']) ? (int) $field['size'] : 0;
                    $cssClass = trim('form-control ' . $cssClass);
                    $multipleTag = isset($field['multiple']) && $field['multiple'] ? ' multiple="multiple"' : '';
                    $disabledTag = isset($field['disabled']) && $field['disabled'] ? ' disabled="disabled"' : '';
                    // Disabling the id field causes updates to become inserts.  Don't allow that.
                    if ($fieldName == $idCol) {
                        $disabledTag = '';
                    }
                    $thisFormField .= '<select' . $multipleTag . ' name="' . $fieldName . '" id="' . $id . '"' . ($size > 0 ? ' size="' . $size . '"' : '') . ($cssClass != '' ? ' class="' . $cssClass . '"' : '') . ($helpTopic != '' ? ' data-help-topic="' . $helpTopic . '"' : '') . $disabledTag . $onclickTag . $onchangeTag . ">\n";
                    if (isset($field['optionsFromAssociativeArray']) && is_string($field['optionsFromAssociativeArray']) && $field['optionsFromAssociativeArray'] != '') {
                        $thisFormField .= '<?php foreach (' . $field['optionsFromAssociativeArray'] . ' as $__val=>$__desc) { ?' . ">\n" . ' <option value="<?php echo htmlspecialchars($__val); ?' . '>">' . '<?php echo htmlspecialchars($__desc); ?' . '>' . "</option>\n" . '<?php } ?' . ">\n";
                    } else {
                        $options = isset($field['options']) && is_array($field['options']) ? $field['options'] : array();
                        foreach ($options as $option => $optionParams) {
                            $optionTitleLangKey = 'crud.' . $crudName . '.form.input.' . $id . '.option.' . optionToName($option) . '.title';
                            $title = isset($optionParams['title']) ? $optionParams['title'] : $option;
                            $langKeys[$optionTitleLangKey] = $title;
                            $optionTitleExpr = "_t('" . $optionTitleLangKey . "', " . var_export_normal_precision($title, true) . ")";
                            $thisFormField .= '     <option value="' . htmlspecialchars($option) . '">' . '<?php echo ' . $optionTitleExpr . '; ?>' . "</option>\n";
                        }
                    }
                    $thisFormField .= '</select>' . $searchDescHTML;
                    break;
                case 'file':
                    $cssClass = trim('form-control ' . $cssClass);
                    $readonlyTag = isset($field['readonly']) && $field['readonly'] ? ' readonly="readonly"' : '';
                    $disabledTag = isset($field['disabled']) && $field['disabled'] ? ' disabled="disabled"' : '';
                    // Disabling the id field causes updates to become inserts.  Don't allow that.
                    if ($fieldName == $idCol) {
                        $disabledTag = '';
                    }
                    $acceptTag = isset($field['accept']) ? trim($field['accept']) : '';
                    if ($acceptTag != '') {
                        $acceptTag = " accept=\"{$acceptTag}\"";
                    }
                    $thisFormField .= '<input type="' . $inputType . '" name="' . $fieldName . '" id="' . $id . '"' . $acceptTag . ($cssClass != '' ? ' class="' . $cssClass . '"' : '') . ($helpTopic != '' ? ' data-help-topic="' . $helpTopic . '"' : '') . $readonlyTag . $disabledTag . $onclickTag . $onchangeTag . '/>' . $searchDescHTML;
                    break;
                case 'checkbox':
                    $disabledTag = isset($field['disabled']) && $field['disabled'] ? ' disabled="disabled"' : '';
                    $value = isset($field['value']) ? $field['value'] : '1';
                    // Disabling the id field causes updates to become inserts.  Don't allow that.
                    if ($fieldName == $idCol) {
                        $disabledTag = '';
                    }
                    $thisFormField .= '<input type="' . $inputType . '" name="' . $fieldName . '" id="' . $id . '"' . ' value="' . $value . '"' . ($cssClass != '' ? ' class="' . $cssClass . '"' : '') . ($helpTopic != '' ? ' data-help-topic="' . $helpTopic . '"' : '') . $disabledTag . $onclickTag . $onchangeTag . '/>' . ' <?php echo ' . $titleExpr . '; ?></label>' . $searchDescHTML;
                    break;
                case 'radio':
                    $disabledTag = isset($field['disabled']) && $field['disabled'] ? ' disabled="disabled"' : '';
                    // Disabling the id field causes updates to become inserts.  Don't allow that.
                    if ($fieldName == $idCol) {
                        $disabledTag = '';
                    }
                    if (isset($field['optionsFromAssociativeArray']) && is_string($field['optionsFromAssociativeArray']) && $field['optionsFromAssociativeArray'] != '') {
                        $thisFormField .= '<?php $__i = 0;' . "\n" . 'foreach (' . $field['optionsFromAssociativeArray'] . ' as $__val=>$__desc) { ?' . ">\n\t" . '$__i++;' . "\n" . '<div><input type="radio" name="' . $fieldName . '" id="' . $id . '__<?php echo $__i; ?>" value="<?php echo htmlspecialchars($__val); ?' . '>"' . ($cssClass != '' ? ' class="' . $cssClass . '"' : '') . ($helpTopic != '' ? ' data-help-topic="' . $helpTopic . '"' : '') . $disabledTag . '/>' . '<label for="' . $id . '__<?php echo $__i; ?>"><?php echo htmlspecialchars($__desc); ?' . '>' . "</label></div>\n" . '<?php } ?' . ">\n";
                    } else {
                        $options = isset($field['options']) && is_array($field['options']) ? $field['options'] : array();
                        $__i = 0;
                        foreach ($options as $option => $optionParams) {
                            $__i++;
                            $optionTitleLangKey = 'crud.' . $crudName . '.form.input.' . $id . '.option.' . optionToName($option) . '.title';
                            $title = isset($optionParams['title']) ? $optionParams['title'] : $option;
                            $langKeys[$optionTitleLangKey] = $title;
                            $optionTitleExpr = "_t(" . $optionTitleLangKey . "', " . var_export_normal_precision($title, true) . ")";
                            $thisFormField .= '<div><input type="radio" name="' . $fieldName . '" id="' . $id . '__' . $__i . '" value="' . htmlspecialchars($option) . '"' . ($cssClass != '' ? ' class="' . $cssClass . '"' : '') . ($helpTopic != '' ? ' data-help-topic="' . $helpTopic . '"' : '') . $disabledTag . '/>' . '<label for="' . $id . '__' . $__i . '">' . '<?php echo ' . $optionTitleExpr . '; ?>' . "</label></div>\n";
                        }
                    }
                    $thisFormField .= $searchDescHTML . "\n";
                    break;
                case 'htmlfragment':
                    $thisFormField .= isset($field['html']) ? $field['html'] . "\n" : '';
                    break;
                case 'tabs':
                    // *** WARNING: Tab-related elements manipulate $formFields directly instead of populating $thisFormField.
                    $tabsPosition = isset($field['tabsPosition']) ? strtolower(trim($field['tabsPosition'])) : 'top';
                    switch ($tabsPosition) {
                        case 'top':
                            $tabbableClasses = 'tabbable';
                            break;
                        case 'left':
                            $tabbableClasses = 'tabbable tabs-left';
                            break;
                        case 'right':
                            $tabbableClasses = 'tabbable tabs-right';
                            break;
                        case 'bottom':
                            $tabbableClasses = 'tabbable tabs-below';
                            break;
                        default:
                            fprintf(STDERR, "inputType:tabs tabsPosition attribute must be one of top, left, right, bottom, or omitted (defaults to top).\n");
                            return false;
                    }
                    $ulOpenHTML = " <ul class=\"nav nav-tabs\">\n";
                    $ulCloseHTML = " </ul>";
                    $formFields .= "<div class=\"{$tabbableClasses}\">\n";
                    if ($tabsPosition != 'bottom') {
                        $formFields .= $ulOpenHTML;
                    }
                    $openContainers[] = (object) array('id' => $id, 'type' => 'tabs', 'tabsPosition' => $tabsPosition, 'tabInsertIdx' => strlen($formFields), 'openGroupId' => null, 'numGroups' => 0, 'closingHTMLPart1' => $tabsPosition == 'bottom' ? $ulOpenHTML : '', 'closingHTMLPart2' => '', 'closingHTMLPart3' => ($tabsPosition == 'bottom' ? $ulCloseHTML : '') . "\n</div> <!-- {$id} -->\n</div> <!-- class=\"tabbable\" -->\n");
                    if ($tabsPosition != 'bottom') {
                        $formFields .= $ulCloseHTML;
                    }
                    $formFields .= "\n <div id=\"{$id}\" class=\"tab-content\">\n";
                    break;
                case 'tabsClose':
                    // *** WARNING: Tab-related elements manipulate $formFields directly instead of populating $thisFormField.
                    if (empty($openContainers)) {
                        fprintf(STDERR, "inputType:tabsClose with no corresponding inputType:tabs in formFields section.\n");
                        return false;
                    }
                    $tc = array_pop($openContainers);
                    if ($tc->type != 'tabs') {
                        fprintf(STDERR, "inputType:tabsClose trying to close non-tabs container of type %s.\n", $tc->type);
                        return false;
                    }
                    if ($tc->openGroupId !== null) {
                        // Close the last tab in this tabs container.
                        $formFields .= "  </div> <!-- {$tc->openGroupId} -->\n\n";
                    }
                    $formFields .= $tc->closingHTMLPart1 . $tc->closingHTMLPart2 . $tc->closingHTMLPart3;
                    break;
                case 'tab':
                    // *** WARNING: Tab-related elements manipulate $formFields directly instead of populating $thisFormField.
                    if (empty($openContainers)) {
                        fprintf(STDERR, "tab found outside of tabs ... tabsClose in formFields section.\n");
                        return false;
                    }
                    $tc = $openContainers[count($openContainers) - 1];
                    if ($tc->type != 'tabs') {
                        fprintf(STDERR, "inputType:tab inside non-tabs container of type %s.\n", $tc->type);
                        return false;
                    }
                    $tc->numGroups++;
                    if ($tc->openGroupId !== null) {
                        // Close the previous tab in this tabs container.
                        $formFields .= "  </div> <!-- {$tc->openGroupId} -->\n\n";
                    }
                    $tc->openGroupId = $id;
                    $liClasses = $tc->numGroups == 1 ? 'active' : '';
                    $liClassTag = $liClasses != '' ? " class=\"{$liClasses}\"" : '';
                    $tabLinkHTML = "  <li{$liClassTag}><a href=\"#{$id}\" data-toggle=\"tab\"><?php echo {$titleExpr}; ?></a></li>\n";
                    // For active (first) tab, in addition to the .active selector, the .fade.in
                    // selector must also be present in order to indicate that the fade is complete;
                    // otherwise, the content for the active tab will not be visible until the
                    // user actually clicks on a tab.
                    $divClasses = 'tab-pane fade' . ($tc->numGroups == 1 ? ' in active' : '');
                    $divClassTag = $divClasses != '' ? " class=\"{$divClasses}\"" : '';
                    if ($tc->tabsPosition == 'bottom') {
                        $tc->closingHTMLPart2 .= $tabLinkHTML;
                    } else {
                        $formFields = substr($formFields, 0, $tc->tabInsertIdx) . $tabLinkHTML . substr($formFields, $tc->tabInsertIdx);
                    }
                    $formFields .= "\n  <div{$divClassTag} id=\"{$id}\">\n";
                    $tc->tabInsertIdx += strlen($tabLinkHTML);
                    break;
                case 'accordion':
                    $openContainers[] = (object) array('id' => $id, 'type' => 'accordion', 'tabsPosition' => '', 'tabInsertIdx' => -1, 'openGroupId' => null, 'numGroups' => 0, 'closingHTMLPart1' => '', 'closingHTMLPart2' => '', 'closingHTMLPart3' => "\n</div> <!-- {$id} -->\n");
                    $thisFormField .= "<div class=\"accordion\" id=\"{$id}\">\n";
                    break;
                case 'accordionClose':
                    if (empty($openContainers)) {
                        fprintf(STDERR, "inputType:accordionClose with no corresponding inputType:accordion in formFields section.\n");
                        return false;
                    }
                    $tc = array_pop($openContainers);
                    if ($tc->type != 'accordion') {
                        fprintf(STDERR, "inputType:accordionClose trying to close non-accordion container of type %s.\n", $tc->type);
                        return false;
                    }
                    if ($tc->openGroupId !== null) {
                        // Close the last group in this accordion container.
                        $thisFormField .= <<<EOF
   </div> <!-- class=\\"accordion-inner\\" -->
  </div> <!-- {$tc->openGroupId}-body -->
 </div> <!-- class=\\"accordion-group\\" -->


EOF;
                    }
                    $thisFormField .= $tc->closingHTMLPart1 . $tc->closingHTMLPart2 . $tc->closingHTMLPart3;
                    break;
                case 'accordionGroup':
                    if (empty($openContainers)) {
                        fprintf(STDERR, "accordionGroup found outside of accordion ... accordionClose in formFields section.\n");
                        return false;
                    }
                    $tc = $openContainers[count($openContainers) - 1];
                    if ($tc->type != 'accordion') {
                        fprintf(STDERR, "inputType:accordionGroup inside non-accordion container of type %s.\n", $tc->type);
                        return false;
                    }
                    $tc->numGroups++;
                    if ($tc->openGroupId !== null) {
                        // Close the previous group in this accordion container.
                        $thisFormField .= <<<EOF
   </div> <!-- class=\\"accordion-inner\\" -->
  </div> <!-- {$tc->openGroupId}-body -->
 </div> <!-- class=\\"accordion-group\\" -->


EOF;
                    }
                    $tc->openGroupId = $id;
                    $accordionBodyClasses = 'accordion-body collapse';
                    if ($tc->numGroups == 1) {
                        $accordionBodyClasses .= ' in';
                    }
                    $accordionBodyClassesTag = $accordionBodyClasses != '' ? " class=\"{$accordionBodyClasses}\"" : '';
                    $thisFormField .= <<<EOF
 <div class="accordion-group" id="{$id}">
  <div class="accordion-heading">
   <a class="accordion-toggle" data-toggle="collapse" data-parent="#{$tc->id}" href="#{$id}-body"><?php echo {$titleExpr}; ?></a>
  </div>
 <div id="{$id}-body"{$accordionBodyClassesTag}>
  <div class="accordion-inner">

EOF;
                    break;
                    /// TODO: Add support for other input types.  Button may be the only remaining desirable type which is not supported.
                /// TODO: Add support for other input types.  Button may be the only remaining desirable type which is not supported.
                default:
                    fprintf(STDERR, "Invalid inputType \"%s\" on input \"%s\" in formFields section.  Skipping this input.\n", $inputType, $fieldName);
                    continue;
            }
            // switch ($inputType)
            // Close the layout cell and row.
            switch ($inputType) {
                case 'text':
                case 'password':
                case 'textarea':
                case 'select':
                case 'file':
                case 'radio':
                case 'checkbox':
                    $thisFormField .= "</div></div>\n";
                    break;
            }
            if ($inputType == 'text' || $inputType == 'textarea') {
                $ajaxAutocompleteCommand = isset($field['ajaxAutocompleteCommand']) ? $field['ajaxAutocompleteCommand'] : '';
                $ajaxAutocompleteMinLength = isset($field['ajaxAutocompleteMinLength']) ? (int) trim($field['ajaxAutocompleteMinLength']) : 0;
                if ($ajaxAutocompleteMinLength <= 0) {
                    $ajaxAutocompleteMinLength = 2;
                }
                if ($ajaxAutocompleteCommand != '') {
                    $ajaxAutocompleteInitJS .= sprintf(<<<EOF
\t\$('#%s').ajaxAutocomplete({
\t\tautocompleteCommand:'%s',
\t\tminimumInputLength:%d
\t});

EOF
, $id, $ajaxAutocompleteCommand, $ajaxAutocompleteMinLength);
                }
                // if ($ajaxAutocompleteCommand != '')
                if (isset($field['ajaxCombobox']) && is_array($field['ajaxCombobox'])) {
                    $arr = $field['ajaxCombobox'];
                    unset($arr['rowFetcher']);
                    $ajaxComboboxInitJS .= "\t\$('#" . $id . "').ajaxCombobox(" . json_encode($arr) . ");\n";
                }
            }
            // if (($inputType == 'text') || ($inputType == 'textarea'))
            if ($thisFormField != '') {
                $formFields .= $thisFormField;
            }
        }
        // foreach ($crud['formFields'] as $fieldName=>$field)
        if (!empty($openContainers)) {
            fprintf(STDERR, "One or more open containers (tabs or accordion) without corresponding close (tabsClose, accordionClose) found in formFields section.\n");
            return false;
        }
        $includesByClass = array();
        $filterCode = '';
        if (!isset($crud['filters']) || !is_array($crud['filters'])) {
            $crud['filters'] = array();
        }
        foreach ($crud['filters'] as $fieldName => $filterGroup) {
            if (!is_array($filterGroup)) {
                continue;
            }
            foreach ($filterGroup as $filter) {
                if (!isset($filter['class']) || !is_string($filter['class'])) {
                    continue;
                }
                $filterClass = $filter['class'];
                $filterInclude = isset($filter['include']) && is_string($filter['include']) ? $filter['include'] : '';
                if (!isset($filter['params']) || !is_array($filter['params'])) {
                    $filter['params'] = array();
                }
                $filterParams = $filter['params'];
                if (!isset($filterParams['valueName'])) {
                    $filterParams['valueName'] = $fieldName;
                }
                if ($filterInclude != '') {
                    if (!isset($includesByClass[$filterClass])) {
                        $includesByClass[$filterClass] = array($filterInclude);
                    } else {
                        if (!in_array($filterInclude, $includesByClass[$filterClass])) {
                            $includesByClass[$filterClass][] = $filterInclude;
                        }
                    }
                }
                $paramsEnc = var_export_normal_precision($filterParams, true);
                $filterCode .= "\t\t\$__filter = new {$filterClass}({$paramsEnc});\n\t\t\$__filter->filter(\$db, \$row);\n\n";
            }
        }
        $filterCode .= "\t\tunset(\$__filter);\n";
        foreach ($includesByClass as $cls => $incs) {
            foreach ($incs as $inc) {
                $phpIncludes .= "if (!class_exists('{$cls}', false)) include {$docRootPath}.'/{$inc}';\n";
            }
        }
        $includesByClass = array();
        $validationCode = '';
        if (!isset($crud['validators']) || !is_array($crud['validators'])) {
            $crud['validators'] = array();
        }
        foreach ($crud['validators'] as $fieldName => $validatorGroup) {
            if (!is_array($validatorGroup)) {
                continue;
            }
            $fieldNameEnc = var_export_normal_precision($fieldName, true);
            $validatorNameCounts = array();
            foreach ($validatorGroup as $validator) {
                if (!isset($validator['class']) || !is_string($validator['class'])) {
                    continue;
                }
                $validatorClass = $validator['class'];
                $validatorInclude = isset($validator['include']) && is_string($validator['include']) ? $validator['include'] : '';
                if (isset($validatorNameCounts[$validatorClass])) {
                    $validatorNameCounts[$validatorClass]++;
                } else {
                    $validatorNameCounts[$validatorClass] = 1;
                }
                if (!isset($validator['params']) || !is_array($validator['params'])) {
                    $validator['params'] = array();
                }
                $validatorParams = $validator['params'];
                if (!is_array($validatorParams)) {
                    $validatorParams = array();
                }
                if (!isset($validatorParams['valueName'])) {
                    $validatorParams['valueName'] = $fieldName;
                }
                if ($validatorInclude != '') {
                    if (!isset($includesByClass[$validatorClass])) {
                        $includesByClass[$validatorClass] = array($validatorInclude);
                    } else {
                        if (!in_array($validatorInclude, $includesByClass[$validatorClass])) {
                            $includesByClass[$validatorClass][] = $validatorInclude;
                        }
                    }
                }
                $phpCondition = isset($validator['phpCondition']) && is_string($validator['phpCondition']) ? trim($validator['phpCondition']) : '';
                $phpc1 = $phpCondition != '' ? '( ' : '';
                $phpc2 = $phpCondition != '' ? " && ({$phpCondition}) )" : '';
                if (isset($validatorParams['errorMsg'])) {
                    $errorMsg = $validatorParams['errorMsg'];
                    unset($validatorParams['errorMsg']);
                    $paramsEnc = var_export_normal_precision($validatorParams, true);
                    if (substr($paramsEnc, -1) == ')') {
                        $validatorErrorMsgLangKey = "crud.{$crudName}.validator.{$fieldName}.{$validatorClass}" . ($validatorNameCounts[$validatorClass] > 1 ? '.' . $validatorNameCounts[$validatorClass] : '') . '.errorMsg';
                        $langKeys[$validatorErrorMsgLangKey] = $errorMsg;
                        $paramsEnc = rtrim(substr($paramsEnc, 0, strlen($paramsEnc) - 1), ", \t\n\r\v") . ",\n  'errorMsg'=>_t('{$validatorErrorMsgLangKey}', " . var_export_normal_precision($errorMsg, true) . "),\n )\n";
                    }
                } else {
                    $paramsEnc = var_export_normal_precision($validatorParams, true);
                }
                $validationCode .= "\t\tif {$phpc1}(!isset(\$result->fieldErrors[{$fieldNameEnc}])){$phpc2} {\n" . "\t\t\t\$__validator = new {$validatorClass}({$paramsEnc});\n" . "\t\t\t\$__validatorError = \$__validator->validate(\$db, \$row);\n" . "\t\t\tif (\$__validatorError != '') {\n" . "\t\t\t\t\$result->fieldErrors[{$fieldNameEnc}] = \$__validatorError;\n" . "\t\t\t}\n" . "\t\t}\n" . "\n";
            }
        }
        $validationCode .= "\t\tunset(\$__validator);\n" . "\t\tunset(\$__validatorError);\n";
        foreach ($includesByClass as $cls => $incs) {
            foreach ($incs as $inc) {
                $phpIncludes .= "if (!class_exists('{$cls}', false)) include {$docRootPath}.'/{$inc}';\n";
            }
        }
        $crudLoadCommand = 'load' . ucfirst($table->tableName);
        $crudLoadSynchronousOrAsynchronous = 'asynchronous';
        if (isset($crud['crudLoad'])) {
            $load = $crud['crudLoad'];
            if (isset($load['loadCommand']) && is_string($load['loadCommand'])) {
                $crudLoadCommand = $load['loadCommand'];
            }
            if (isset($load['synchronous']) && $load['synchronous']) {
                $crudLoadSynchronousOrAsynchronous = 'synchronous';
            }
        }
        $addFocusJS = isset($crud['addFocusField']) && is_string($crud['addFocusField']) ? $crud['addFocusField'] : '';
        if ($addFocusJS != '') {
            $addFocusJS = "\t\t\t\$('#{$table->tableName}Form #{$addFocusJS}').focus();";
        }
        $editFocusJS = isset($crud['editFocusField']) && is_string($crud['editFocusField']) ? $crud['editFocusField'] : '';
        if ($editFocusJS != '') {
            $editFocusJS = "\t\t\t\$('#{$table->tableName}Form #{$editFocusJS}').focus();";
        }
        $onlyUpdateColumns = isset($crud['onlyUpdateColumns']) ? $crud['onlyUpdateColumns'] : array();
        if (!is_array($onlyUpdateColumns)) {
            $onlyUpdateColumns = array();
        }
        if (empty($onlyUpdateColumns)) {
            $onlyUpdateColumns = 'array()';
        } else {
            $onlyUpdateColumns = "array('" . implode("', '", $onlyUpdateColumns) . "')";
        }
        $neverUpdateColumns = isset($crud['neverUpdateColumns']) ? $crud['neverUpdateColumns'] : array();
        if (!is_array($neverUpdateColumns)) {
            $neverUpdateColumns = array();
        }
        if (empty($neverUpdateColumns)) {
            $neverUpdateColumns = 'array()';
        } else {
            $neverUpdateColumns = "array('" . implode("', '", $neverUpdateColumns) . "')";
        }
        $javaScriptFiles = '';
        if (isset($crud['javaScriptFiles']) && is_array($crud['javaScriptFiles'])) {
            foreach ($crud['javaScriptFiles'] as $file) {
                $javaScriptFiles .= "\$headerScripts[] = '{$file}';\n";
            }
        }
        $cssFiles = '';
        if (isset($crud['cssFiles']) && is_array($crud['cssFiles'])) {
            foreach ($crud['cssFiles'] as $file) {
                $cssFiles .= "\$headerStylesheets[] = '{$file}';\n";
            }
        }
        $allowAddSimilar = isset($crud['allowAddSimilar']) && $crud['allowAddSimilar'] ? true : false;
        $viewInclude = $oldFileLayout ? "dirname(__FILE__).'/{$crudName}_view.include.php'" : "dirname(dirname(__FILE__)).'/{$crudName}_view.include.php'";
        $searchFor = array('{{generatedFileMessage}}', '{{mainOkCheck}}', '{{classAutoloadPathsInit}}', '{{mainHooksInclude}}', '{{viewHooksInclude}}', '{{controllerIncludes}}', '{{docRootPath}}', '{{jaxInclude}}', '{{jaxJQuery}}', '{{jaxJS}}', '{{phpIncludes}}', '{{postInitPHPIncludes}}', '{{tableDescription}}', '{{tableDescriptions}}', '{{tableName}}', '{{uTableName}}', '{{crudName}}', '{{ucrudName}}', '{{idCol}}', '{{uIdCol}}', '{{loggedInId}}', '{{crudSearchCommand}}', '{{crudSearchCallbacks}}', '{{crudSearchTableDisplayColumns}}', '{{crudSearchGridHeaderColumnsHTML}}', '{{crudSearchGridBodyColumnsHTML}}', '{{crudSearchColumnNamesJSON}}', '{{crudSearchColumnFilters}}', '{{crudSearchExtraQueryParamsJSON}}', '{{crudSearchDefaultSortsJSON}}', '{{crudSearchTableCallbacks}}', '{{ajaxAutocompleteInitJS}}', '{{ajaxComboboxInitJS}}', '{{crudLoadCommand}}', '{{hiddenFormFields}}', '{{formFields}}', '{{filterCode}}', '{{validationCode}}', '{{addFocusJS}}', '{{editFocusJS}}', '{{onlyUpdateColumns}}', '{{neverUpdateColumns}}', '{{javaScriptFiles}}', '{{cssFiles}}', '{{allowAddSimilar}}', '{{viewInclude}}');
        $replaceWith = array($generatedFileMessage, $mainOkCheck, $classAutoloadPathsInit, $mainHooksInclude, $viewHooksInclude, $controllerIncludes, $docRootPath, $jaxInclude, $jaxJQuery, $jaxJS, $phpIncludes, $postInitPHPIncludes, $cfg['tableDescription'], $cfg['tableDescriptions'], $table->tableName, ucfirst($table->tableName), $crudName, ucfirst($crudName), $idCol, ucfirst($idCol), $loggedInId, $crudSearchCommand, $crudSearchCallbacks, $crudSearchTableDisplayColumns, $crudSearchGridHeaderColumnsHTML, $crudSearchGridBodyColumnsHTML, json_encode($crudSearchColumnNames), $crudSearchColumnFilters, json_encode($crudSearchExtraQueryParams), json_encode($crudSearchDefaultSorts), $crudSearchTableCallbacks, $ajaxAutocompleteInitJS, $ajaxComboboxInitJS, $crudLoadCommand, $hiddenFormFields, $formFields, $filterCode, $validationCode, $addFocusJS, $editFocusJS, $onlyUpdateColumns, $neverUpdateColumns, $javaScriptFiles, $cssFiles, $allowAddSimilar ? 'true' : 'false', $viewInclude);
        $content = str_replace($searchFor, $replaceWith, file_get_contents($templatesDir . '/main.php'));
        $fn = $generatedOutputDir . '/' . $crudName . ($oldFileLayout ? '' : '_generated.include') . '.php';
        if (!file_exists($fn) || file_get_contents($fn) != $content) {
            file_put_contents($fn, $content);
        }
        if ($enableLangFiles) {
            $langfn = $outputDir . '/' . $crudName . '.php.strings';
            $langResources = loadLangResourceFileContents($langfn);
            $anyResourcesChanged = false;
            foreach ($langKeys as $key => $val) {
                if (getLangResource($langResources, $key) != $val) {
                    setLangResource($langResources, $key, $val);
                    $anyResourcesChanged = true;
                }
            }
            if ($anyResourcesChanged) {
                saveLangResourceFileContents($langfn, $langResources);
            }
        }
        if (!$oldFileLayout) {
            $fn = $outputDir . '/' . $crudName . '.php';
            if (!file_exists($fn)) {
                file_put_contents($fn, sprintf(<<<EOF
<?php
\$mainOk = true;
include dirname(__FILE__).'/generated/%s_generated.include.php';

// Add any custom functionality or hook functions for the main page below.


EOF
, $crudName));
            }
        }
        $content = str_replace($searchFor, $replaceWith, filterCRUDLoadSynchronousOrAsynchronous(filterSearchPresentation(file_get_contents($templatesDir . '/view.include.php'), $crudSearchPresentation), $crudLoadSynchronousOrAsynchronous));
        $fn = $generatedOutputDir . '/' . $crudName . '_view' . ($oldFileLayout ? '' : '_generated') . '.include.php';
        if (!file_exists($fn) || file_get_contents($fn) != $content) {
            file_put_contents($fn, $content);
        }
        if (!$oldFileLayout) {
            $fn = $outputDir . '/' . $crudName . '_view.include.php';
            if (!file_exists($fn)) {
                file_put_contents($fn, sprintf(<<<EOF
<?php
include dirname(__FILE__).'/generated/%s_view_generated.include.php';

// Add any custom functionality or hook functions for the view below.


EOF
, $crudName));
            }
        }
        $content = str_replace($searchFor, $replaceWith, filterCRUDLoadSynchronousOrAsynchronous(filterSearchPresentation(file_get_contents($templatesDir . '/controller.js'), $crudSearchPresentation), $crudLoadSynchronousOrAsynchronous));
        $fn = $generatedOutputDir . '/' . $crudName . '_controller' . ($oldFileLayout ? '' : '_generated') . '.js';
        if (!file_exists($fn) || file_get_contents($fn) != $content) {
            file_put_contents($fn, $content);
        }
        if (!$oldFileLayout) {
            $fn = $outputDir . '/' . $crudName . '_controller.js';
            if (!file_exists($fn)) {
                file_put_contents($fn, sprintf(<<<EOF
// Add any custom functionality or hook functions for the controller below.


EOF
, $crudName));
            }
        }
    }
    return true;
}