function CACF_CreateFolderAuditDataAndWriteToCsv(&$db, &$CACFconstants, &$altiumUserNamesByGuid, &$altiumFoldersByGuid, &$altiumFolderUserParmValuesByGuid, &$altiumUserParmNames, &$altiumAclDataByObjectGuid)
{
    /* Retrieve necessary global constants. */
    $ofs = $CACFconstants["ofs"];
    $constNamingScheme = $CACFconstants["constNamingScheme"];
    $constAbsent = $CACFconstants["constAbsent"];
    $auditFoldersFileName = $CACFconstants["auditFoldersFileName"];
    /** Modify $altiumUserParmNames to add one FOLDER level special parameter string. **/
    /* WARNING:  Do not move this code block above the audit components operation located above here! */
    $altiumUserParmNames[$constNamingScheme] = 1;
    /* Re-sort all Altium user parameter names stored in the array, by array key. */
    $rc = ksort($altiumUserParmNames);
    if ($rc == FALSE) {
        my_die("ksort() failed!");
    }
    /** Open audit folders file for writing. **/
    $auditFoldersFile = my_fopen($auditFoldersFileName);
    /** Output column headers. **/
    $line = "";
    $line = $line . "FOLDERGUID" . $ofs . "DESCRIPTION" . $ofs . "FOLDERTYPE" . $ofs . "FOLDERPATH" . $ofs . "CREATEDBY" . $ofs . "CREATEDAT" . $ofs . "LASTMODIFIEDBY" . $ofs . "LASTMODIFIEDAT";
    /* Get a random array slice to know what fields exist. */
    $slice = array_slice($altiumAclDataByObjectGuid, 0, 1);
    //echo "slice is:\n";
    //print_r($slice);
    /* Output an ordered list of all the permission fields that exist. */
    foreach ($slice as $key => $value) {
        foreach ($slice[$key] as $PERMNAME => $value2) {
            /* Output $PERMNAME. */
            $line = $line . $ofs . $PERMNAME;
        }
        /* end foreach */
    }
    /* end foreach */
    /* Output an ordered list of all the Altium user parameters that exist in our universe as columns in the csv file. */
    foreach ($altiumUserParmNames as $PARAMETERNAME => $value) {
        /* Output $PARAMETERNAME. */
        $line = $line . $ofs . $PARAMETERNAME;
    }
    /* end foreach */
    //  echo "altiumAclDataByObjectGuid is:\n";
    //  print_r($altiumAclDataByObjectGuid);
    /* Write line to file.  Note:  explicitly use DOS (CR/LF) \r\n line endings! */
    fputs($auditFoldersFile, $line . "\r\n");
    /** Create array to temporarily hold all lines that we generate, since we need to sort before writing to file. **/
    $auditFoldersLines = array();
    /* Setup query SQL commands. */
    $queryText = '
SELECT FOLDER.GUID AS FOLDERGUID, FOLDER.HRID, FOLDER.CREATEDBYGUID, FOLDER.LASTMODIFIEDBYGUID, FOLDER.CREATEDAT, FOLDER.LASTMODIFIEDAT, FOLDER.PARENTFOLDERGUID, FOLDER.DESCRIPTION, FT.HRID AS FOLDERTYPE
FROM ALU_FOLDER FOLDER
LEFT JOIN ALU_FOLDERTYPE FT ON FOLDER.FOLDERTYPEGUID = FT.GUID
;
';
    echo date('H:i:s') . " Begin query to read in all folder info from Vault database...\n";
    /* Execute SQL query. */
    $resultHandle = odbc_exec($db, $queryText);
    /* Loop over all rows returned by SQL query. */
    while (odbc_fetch_row($resultHandle)) {
        /* Extract the fields of interest from this query result. */
        $FOLDERGUID = odbc_result($resultHandle, "FOLDERGUID");
        $DESCRIPTION = odbc_result($resultHandle, "DESCRIPTION");
        $FOLDERTYPE = odbc_result($resultHandle, "FOLDERTYPE");
        $CREATEDBYGUID = odbc_result($resultHandle, "CREATEDBYGUID");
        $LASTMODIFIEDBYGUID = odbc_result($resultHandle, "LASTMODIFIEDBYGUID");
        $CREATEDAT = odbc_result($resultHandle, "CREATEDAT");
        $LASTMODIFIEDAT = odbc_result($resultHandle, "LASTMODIFIEDAT");
        /* Lookup the usernames of the person to create and last modify this folder. */
        $CREATEDBY = CACF_LookupUsername($altiumUserNamesByGuid, $CREATEDBYGUID);
        $LASTMODIFIEDBY = CACF_LookupUsername($altiumUserNamesByGuid, $LASTMODIFIEDBYGUID);
        /* Lookup the full path of this folder, based on cached data from having already read in this table once already. */
        $FOLDERPATH = CACF_TraceFolderPath($CACFconstants, $altiumFoldersByGuid, $FOLDERGUID);
        /** Output actual data. **/
        $line = "";
        $line = $line . "{$FOLDERGUID}" . $ofs . "{$DESCRIPTION}" . $ofs . "{$FOLDERTYPE}" . $ofs . "{$FOLDERPATH}" . $ofs . "{$CREATEDBY}" . $ofs . "{$CREATEDAT}" . $ofs . "{$LASTMODIFIEDBY}" . $ofs . "{$LASTMODIFIEDAT}";
        /** Output an ordered list of all the Altium user parameters that exist in our universe as columns in the csv file.
            If this part has a given parameter, list its value. **/
        /* Get all the permission fields that exist for this folder. */
        foreach ($altiumAclDataByObjectGuid[$FOLDERGUID] as $PERMNAME => $PERMVALUE) {
            /* Output $PERMVALUE. */
            $line = $line . $ofs . $PERMVALUE;
        }
        /* end foreach */
        /* Loop over all the defined Altium user parameter names. */
        foreach ($altiumUserParmNames as $PARAMETERNAME => $value) {
            /* Unconditionally print out a field separator. */
            $line = $line . $ofs;
            /* If this component has a stored folder user parameter named $PARAMETERNAME, then output it. */
            if (isset($altiumFolderUserParmValuesByGuid[$FOLDERGUID][$PARAMETERNAME])) {
                $line = $line . $altiumFolderUserParmValuesByGuid[$FOLDERGUID][$PARAMETERNAME];
            } else {
                $line = $line . $constAbsent;
            }
        }
        /* end foreach */
        /* Store this line in an in-memory array, so we can sort it all just before writing to disk. */
        $auditFoldersLines[$FOLDERPATH] = $line;
    }
    /* endwhile */
    /* Free memory that was holding query results. */
    odbc_free_result($resultHandle);
    /* Sort all output lines here by folder path (key). */
    $rc = ksort($auditFoldersLines);
    if ($rc == FALSE) {
        my_die("ksort() failed!");
    }
    /* Loop over all lines in what will be our output file. */
    foreach ($auditFoldersLines as $key => $line) {
        /* Write line to file.  Note:  explicitly use DOS (CR/LF) \r\n line endings! */
        fputs($auditFoldersFile, $line . "\r\n");
    }
    /* endforeach */
    /** Clear array that held all lines prior to writing to file. **/
    $auditFoldersLines = array();
    /** Close the audit output file. **/
    fclose($auditFoldersFile);
    echo date('H:i:s') . " Done writing audit folders file \"{$auditFoldersFileName}.\"\n";
}
function UCTCF_ExtractGroupsAndComponentsByXpathFromCmpLib(&$CACFconstants, &$UCTCFconstants, &$altiumFoldersByGuid, $path, $xpath, &$xmlNode, &$cmpLibGroupXpathByPath, &$cmpLibCompByItemHrid)
{
    /* Retrieve necessary global constants. */
    $pathSep = $CACFconstants["pathSep"];
    //  echo "Hello world from UCTCF_ExtractGroupsAndComponentsByXpathFromCmpLib().\n";
    //  echo "\nIn UCTCF_ExtractGroupsAndComponentsByXpathFromCmpLib(), xmlNode is:\n";
    //  print_r($xmlNode);
    /* Retrieve name of this node. */
    $name = $xmlNode->getName();
    /* Update xpath with name of this node. */
    $xpath = $xpath . "/{$name}";
    //  echo "In UCTCF_ExtractGroupsAndComponentsByXpathFromCmpLib(), xpath is now \"$xpath\".\n";
    //  echo "In node \"$name\", count is " . $xmlNode->count() . "!\n";
    /* Handle the base case where we're given a path of "". */
    /* Look up the GUID of the initial path element in database to get starting path. */
    if ($path == "") {
        /* Make sure we have the root path element's GUID field. */
        if (!isset($xmlNode->GUID)) {
            my_die('In UCTCF_ExtractGroupsAndComponentsByXpathFromCmpLib(), could not find GUID for root path element!');
        }
        /* Extract GUID field. */
        $GUID = (string) $xmlNode->GUID;
        //      echo "GUID is \"$GUID\".\n";
        /* Lookup folder path for this starting point. */
        $path = CACF_TraceFolderPath($CACFconstants, $altiumFoldersByGuid, $GUID);
        //      echo "In UCTCF_ExtractGroupsAndComponentsByXpathFromCmpLib(), base path is \"".$path."\".\n";
        /* Store xpath query corresponding to this (database) path. */
        $cmpLibGroupXpathByPath[$path] = $xpath;
        //      echo "In UCTCF_ExtractGroupsAndComponentsByXpathFromCmpLib(), for group path \"$path\", stored xpath \"$xpath\".\n";
    } else {
        if ($name == "TGroup") {
            /* Make sure we have the group's HRID field. */
            if (!isset($xmlNode->HRID)) {
                my_die('In UCTCF_ExtractGroupsAndComponentsByXpathFromCmpLib(), could not find HRID for TGroup!');
            }
            /* Attempt to extract new path element (TGroup's HRID field). */
            $HRID = (string) $xmlNode->HRID;
            /* Add new element to path. */
            $path = $path . $HRID . $pathSep;
            //      echo "In UCTCF_ExtractGroupsAndComponentsByXpathFromCmpLib(), path is \"".$path."\".\n";
            /* Retrieve the ID of this group. */
            $groupId = (string) $xmlNode['id'];
            /* Add a query to our xpath to disambiguate it from other peer groups. */
            $xpath = $xpath . "[@id=\"{$groupId}\"]";
            //      echo "xpath is \"$xpath\".\n";
            /* Store xpath query corresponding to this (database) path. */
            $cmpLibGroupXpathByPath[$path] = $xpath;
            //      echo "In UCTCF_ExtractGroupsAndComponentsByXpathFromCmpLib(), for group path \"$path\", stored xpath \"$xpath\".\n";
        } else {
            if ($name == "TComponentDefinition") {
                /* Make sure we have the component's ItemHRID field. */
                if (!isset($xmlNode->ItemHRID)) {
                    my_die('In UCTCF_ExtractGroupsAndComponentsByXpathFromCmpLib(), could not find ItemHRID for TComponentDefinition!');
                }
                /* Attempt to extract component's ItemHRID field). */
                $ItemHRID = (string) $xmlNode->ItemHRID;
                /* Add a query to our xpath to disambiguate it from other peer components. */
                $xpath = $xpath . "[ItemHRID=\"{$ItemHRID}\"]";
                //      echo "Component xpath is \"$xpath\".\n";
                /* Store path and xpath query corresponding to this component (by component ItemHrid). */
                $cmpLibCompByItemHrid[$ItemHRID] = array();
                $cmpLibCompByItemHrid[$ItemHRID]["path"] = $path;
                $cmpLibCompByItemHrid[$ItemHRID]["xpath"] = $xpath;
                //      echo "In UCTCF_ExtractGroupsAndComponentsByXpathFromCmpLib(), for component ItemHrid \"$ItemHRID\", stored xpath \"$xpath\".\n";
            }
        }
    }
    /* end elsif */
    /** Recurse into child nodes to find any TGroups or TComponentDefinitions. **/
    /* See if this XML node has any children. */
    if ($xmlNode->count() > 0) {
        /* Attempt to loop over all child nodes. */
        foreach ($xmlNode->children() as $childNode) {
            //          echo "Recursing from node \"$name\"!\n";
            /* Recursively call this function. */
            $xmlNode =& $childNode;
            UCTCF_ExtractGroupsAndComponentsByXpathFromCmpLib($CACFconstants, $UCTCFconstants, $altiumFoldersByGuid, $path, $xpath, $xmlNode, $cmpLibGroupXpathByPath, $cmpLibCompByItemHrid);
        }
        /* end foreach */
    }
    /* endif node has children. */
}