DATA_TYPE_STRING, // unique key exposed externally and is REQUIRED, DB_EVENT_GUID => DATA_TYPE_STRING, // track-back identifier for broker/events DB_CREATED => DATA_TYPE_INTEGER, // epoch time DB_STATUS => DATA_TYPE_STRING, // record status DB_ACCESSED => DATA_TYPE_INTEGER, // epoch time COLLECTION_MONGO_CSL_ADDRESS => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_ADDRESS1 => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_ADDRESS2 => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_ADDRESS3 => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_ADDR_LIST => DATA_TYPE_ARRAY, COLLECTION_MONGO_CSL_AKA => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_AKA_LIST => DATA_TYPE_ARRAY, COLLECTION_MONGO_CSL_CATEGORY => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_CITIZENSHIP => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_CITIZENSHIP_LIST => DATA_TYPE_ARRAY, COLLECTION_MONGO_CSL_CITY => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_COUNTRY => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_DOB => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_DOB_LIST => DATA_TYPE_ARRAY, COLLECTION_MONGO_CSL_FN => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_LN => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_ID => DATA_TYPE_INTEGER, COLLECTION_MONGO_CSL_ID_COUNTRY => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_ID_LIST => DATA_TYPE_ARRAY, COLLECTION_MONGO_CSL_ID_NUMBER => DATA_TYPE_STRING, // because leading zeros COLLECTION_MONGO_CSL_ID_TYPE => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_MAIN_ENTRY => DATA_TYPE_BOOL, COLLECTION_MONGO_CSL_POB => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_POB_LIST => DATA_TYPE_ARRAY, COLLECTION_MONGO_CSL_POSTAL_CODE => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_PRG => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_PRG_LIST => DATA_TYPE_ARRAY, COLLECTION_MONGO_CSL_REMARKS => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_SOP => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_TYPE => DATA_TYPE_STRING, COLLECTION_MONGO_CSL_UID => DATA_TYPE_INTEGER, COLLECTION_MONGO_CSL_SDN_TYPE => DATA_TYPE_STRING ]; // protected fields are fields that a client is unable to modify or delete. If a client submits a query that // updates these fields, the query will be rejected (worst case) or the directive to update/delete the field // will be silently dropped (best case). In either way, updating or removing this fields cannot be accomplished. // // Minimally, this array should contain the following fields: // -- DB_TOKEN, DB_EVENT_GUID, DB_CREATED, DB_ACCESSED, DB_STATUS // -- the ID field (either PDO_ID or MONGO_ID) // -- DB_WH_CREATED, DB_WH_EVENT_GUID, DB_WH_TOKEN // public ?array $protectedFields = [ DB_TOKEN, DB_CREATED, DB_EVENT_GUID, DB_ACCESSED, MONGO_ID, DB_STATUS ]; // all fields that appear in any of the index declarations must appear in this list as this is the property // that's used in the framework as an authoritative check to qualify discriminant fields as indexes. // // indexes are always declared with the template column name and not the cache-map column name // // warehouse indexes are limited to the original record's created date and the three WH fields only // public array $indexFields = [ MONGO_ID, DB_TOKEN, DB_CREATED, DB_STATUS, DB_EVENT_GUID, COLLECTION_MONGO_CSL_AKA_LIST, COLLECTION_MONGO_CSL_DOB_LIST, COLLECTION_MONGO_CSL_ADDR_LIST, COLLECTION_MONGO_CSL_SDN_TYPE ]; // all index names that are explicitly declared in the indexes below must also appear in this array. If there are // no pre-defined index names, then this field should be set to null. // // Note that if you're allowing mysql to generate the index names for you, and if you use a partial index (below) // that references that randomly-generated index name, and that name does not appear in this list, then you will // fail to load that template at run time, every time. // // You have been warned. // public ?array $indexNameList = ['EntityNameIndex']; // single field index declarations -- since you can have a field in more than one index // (MONGO_ID should NEVER be listed as it's the default single-field index.) // the format for the single-field index declaration is the same format used for all the // index declarations: // [ FIELD_NAME => ] where = [ 1 | -1 ] // // NOTE: if you're going to declare a single column as a property, then do NOT also declare it as a single index! // public ?array $singleFields = [ DB_TOKEN => 1, DB_CREATED => -1, DB_STATUS => 1, COLLECTION_MONGO_CSL_SDN_TYPE => 1 ]; // compound indexes have format of: // [ INDEX-NAME => [ FIELD_NAME => , ... ]] // where INDEX-NAME is a unique string and SORT-DIR = [1|-1] // unless it's for mongoDB -- mongoDB does not use index labels public ?array $compoundIndexes = [ 'EntityNameIndex' => [COLLECTION_MONGO_CSL_SDN_TYPE, COLLECTION_MONGO_CSL_LN, COLLECTION_MONGO_CSL_LN] ]; // multiKey indexes are indexes on fields that are arrays (not the same as sub-collections) which indexes the // content stored in the array based on the column names. // // mongo, as of 3.4, automatically creates a multi-key index on any field declared as a (sic) index that's // an array. Meaning: we don't need to explicitly create a multi-key index on an array field if that field // is declared as a single-key, compound, or unique index. // // ----------------------------------------------------------------------------------------------------------------- // NOTES: if you implicitly declare a multi-key index by using the column as a compound-index field, then you // may, at MOST, have one array within the compound index. // // You may NOT declare a multi-key index as a shard key. // // Hashed keys may NOT be multi-key. // ----------------------------------------------------------------------------------------------------------------- // // In other words, if you want to apply an index to ALL of the array element then declare the column as singleField, // or compound, or unique. This will have the multi-key index automagically applied by mongoDB. // // If you want to index a subset of the array, then declare the fields to be indexed by using dot notation: // // [ 'someIndex' => [ arrayColumnName.subField1 => 1, arrayColumnName.subField3 => -1 ... ] ] // // And this will apply the multi-key index property to subField1 and subField3 only. // // multiKey indexes are referenced by an index name in order to remove ambiguity when parsing index-properties // against this and other indexes that may have the same field name. In other words, index-properties that will // be applied to a multiKey index must reference the multiKey index by the index (and not the column) name. // // example: // [ 'mIdx1Test' => [ ARRAY_FIELD_NAME => <1|-1>, ... ]] // public ?array $multiKey = null; /* * Valid index-type constants are: * MONGO_INDEX_TYPE_SINGLE * MONGO_INDEX_TYPE_COMPOUND * MONGO_INDEX_TYPE_MULTIKEY * * INDEXES NOT SUPPORTED BY NAMASTE AT THIS TIME: * ---------------------------------------------- * geoSpatial * text * hashed * */ // ================================================================================================================= // INDEX PROPERTIES // ---------------- // Index properties are applied to indexes. The supported properties are: // unique, partial and ttl // sparse is not supported because partial // // If a property is not in-use, then you must still declare the property as a class object but the // value of the property will be set to null. // // Sparse property types are not supported in favor of partials. // // ================================================================================================================= // Partial Indexes are supported as of MongoDB 3.2 and replace sparse indexes. Format for declaration is the // column name as an array key, with the value being a sub-array of a mongo operand and a value, all of which is // associated with either an existing column name or index label. // // If an existing column name is used, then that field must be defined (exists) in one of the above index // declarations for single, compound, or multikey indexes. // // Sparse indexes only add the row to the index if the column referenced satisfies the conditions specified // in the query condition (expr2). // // Format: // { expr1 }, { expr2 } // Where: // expr1 is an indexed column and the index direction. e.g.: { created_tst : 1 } // AND // expr2 is the keyword "partialFilterExpression : { [ query ] } // e.g.: { partialFilterExpression : { integer_tst : { $gte : 10 }} // // db.myTable.createIndex({ lastName: -1, firstName : 1 }, { partialFilterExpression : { age : { $gte : 62 }}) // The above index would return a list of names (sorted DESC by last name) for people aged 62 or older. // // public ?array $partialIndexes = null; // unique indexes cause MongoDB to reject duplicate values for the indexed field. Unique indexes // are functionally interchangeable with other mongo indexes. // Format: // [ < FIELD_NAME | INDEX-NAME > => , ... ] // public ?array $uniqueIndexes = [ DB_TOKEN => 1 // MONGO_TOKEN should always appear ]; // ttl indexes contain the column name and the time-to-live in seconds (e.g.: MONGO_TOKEN => 3600) // ttl indexes can only be applied to fields that are MongoDB Date() (object) types, or an array that // contains date values. // // If the field is an array, and there are multiple date values in the index, MongoDB uses lowest // (i.e. earliest) date value in the array to calculate the expiration threshold. If the indexed // field in a document is not a date or an array that holds a date value(s), the document will not expire. // // Format: // [ SOME_FIELD_NAME => ExpireVal ] // // Example: // [ SOME_FIELD_NAME => 86400 ] --- record will be sorted ASC and deleted after 1 day // public ?array $ttlIndexes = null; // cache maps are required for namaste service classes. Even if caching is disabled for a class, a cache map is // still required for the class. For PDO classes, the PDO_ID is never included in the mapping, nor is MONGO_ID. public ?array $cacheMap = [ DB_TOKEN => CM_TOKEN, DB_CREATED => CM_DATE_CREATED, DB_ACCESSED => CM_DATE_ACCESSED, DB_STATUS => CM_STATUS, DB_EVENT_GUID => CM_EVENT_GUID, CM_CSL_ADDR => COLLECTION_MONGO_CSL_ADDRESS, CM_CSL_ADDR1 => COLLECTION_MONGO_CSL_ADDRESS1, CM_CSL_ADDR2 => COLLECTION_MONGO_CSL_ADDRESS2, CM_CSL_ADDR3 => COLLECTION_MONGO_CSL_ADDRESS3, CM_CSL_ADDR_LIST => COLLECTION_MONGO_CSL_ADDR_LIST, CM_CSL_AKA => COLLECTION_MONGO_CSL_AKA, CM_CSL_AKA_LIST => COLLECTION_MONGO_CSL_AKA_LIST, CM_CSL_CAT => COLLECTION_MONGO_CSL_CATEGORY, CM_CSL_CITIZENSHIP => COLLECTION_MONGO_CSL_CITIZENSHIP, CM_CSL_CITIZENSHIP_LIST => COLLECTION_MONGO_CSL_CITIZENSHIP_LIST, CM_CSL_CITY => COLLECTION_MONGO_CSL_CITY, CM_CSL_COUNTRY => COLLECTION_MONGO_CSL_COUNTRY, CM_CSL_DOB => COLLECTION_MONGO_CSL_DOB, CM_CSL_DOB_LIST => COLLECTION_MONGO_CSL_DOB_LIST, CM_CSL_FIRST_NAME => COLLECTION_MONGO_CSL_FN, CM_CSL_LAST_NAME => COLLECTION_MONGO_CSL_LN, CM_CSL_ID => COLLECTION_MONGO_CSL_ID, CM_CSL_ID_COUNTRY => COLLECTION_MONGO_CSL_ID_COUNTRY, CM_CSL_ID_LIST => COLLECTION_MONGO_CSL_ID_LIST, CM_CSL_ID_NUM => COLLECTION_MONGO_CSL_ID_NUMBER, CM_CSL_ID_TYPE => COLLECTION_MONGO_CSL_ID_TYPE, CM_CSL_MAIN_ENTRY => COLLECTION_MONGO_CSL_MAIN_ENTRY, CM_CSL_POB => COLLECTION_MONGO_CSL_POB, CM_CSL_POB_LIST => COLLECTION_MONGO_CSL_POB_LIST, CM_CSL_POST_CODE => COLLECTION_MONGO_CSL_POSTAL_CODE, CM_CSL_PRG => COLLECTION_MONGO_CSL_PRG, CM_CSL_PRG_LIST => COLLECTION_MONGO_CSL_PRG_LIST, CM_CSL_REM => COLLECTION_MONGO_CSL_REMARKS, CM_CSL_STATE_OR_PROVINCE => COLLECTION_MONGO_CSL_SOP, CM_CSL_TYPE => COLLECTION_MONGO_CSL_TYPE, CM_CSL_UID => COLLECTION_MONGO_CSL_UID, CM_CSL_SDN_TYPE => COLLECTION_MONGO_CSL_SDN_TYPE ]; /* * if there is no cache-mapping supported for the class, and you want to limit the fields returned, * then those fields are listed here as the associative array: $exposedFields. Only those fields, * enumerated within this container, will be exposed to the client. * * NOTE: You can have caching disabled for the class and still have a cache-map -- this controls the labels * assigned to the returned data column names exposed to the client. Schema should never be exposed. * * NOTE: if you do not support caching for the class and this class is one that is returned to a client, * (some classes are limited to internal use only, like logging), then you should (at a minimum) * exclude the primary key field (integer). * * * This array is an associative array -- the key is the native column name and the value doesn't matter. The * important thing is that the keys are the column names that you want to return back to the client. * * If $exposedFields is to be undefined for the class, then assign it to null. * */ public ?array $exposedFields = null; public ?array $binFields = null; // binary fields require special handling; define binary fields here // regex fields -- within the indexFields array, which fields enable regex searches? // this does not define an index, but rather to control when to use a regex operand in a query... public ?array $regexFields = null; /* * sub-collections represent the implementation of a 1:M relationship at the record-entity level in mongoDB. * * A great example of a sub-collection implementation would be a parent collection called questions and * a sub-collection called answers. * * sub-collections are declared as key->value pairs where each key value is, itself, an array of field names: * * public $subC = [ * FIELD_ONE => [ * SUB_COLLECTION_FIELD_ONE, * SUB_COLLECTION_FIELD_TWO, * ... * ], * ... * ]; * * Each sub-collection field should also appear in both the fields list (to define the types), and in the * cacheMap (if used). If you're not using a cacheMap, and you're limiting the exposed fields, then each * sub-collection field exposed must be listed in the exposed field list. (e.g.: normal rules for exposure * for a collection are applied the same way to a sub-collection.) * * Note that if a sub-Collection key is not listed in either the cacheMap or the exposed field list, then * the entire sub-collection will be invisible to the client. If you list the sub-collection key, you can * limit the sub-collection fields that are exposed by not listing them in either the cacheMap or the * exposed-field lists, respectively. * * Sub-collections are managed within Namaste to allow the sub-collection elements to be either inserted, * or deleted (an update is a delete + insert) without changing the parent field values and, accordingly, * are enabled via discrete class methods. * */ // sub-collection fields must be declared here (need not be indexed) public ?array $subC = [ COLLECTION_MONGO_CSL_ADDR_LIST => [ COLLECTION_MONGO_CSL_UID, COLLECTION_MONGO_CSL_ADDRESS1, COLLECTION_MONGO_CSL_ADDRESS2, COLLECTION_MONGO_CSL_ADDRESS3, COLLECTION_MONGO_CSL_CITY, COLLECTION_MONGO_CSL_POSTAL_CODE, COLLECTION_MONGO_CSL_COUNTRY, COLLECTION_MONGO_CSL_SOP ], COLLECTION_MONGO_CSL_AKA_LIST => [ COLLECTION_MONGO_CSL_UID, COLLECTION_MONGO_CSL_TYPE, COLLECTION_MONGO_CSL_CATEGORY, COLLECTION_MONGO_CSL_LN, COLLECTION_MONGO_CSL_FN ], COLLECTION_MONGO_CSL_ID_LIST => [ COLLECTION_MONGO_CSL_UID, COLLECTION_MONGO_CSL_ID_TYPE, COLLECTION_MONGO_CSL_ID_NUMBER ] // COLLECTION_MONGO_CSL_DOB_LIST => [ // COLLECTION_MONGO_CSL_UID, // COLLECTION_MONGO_CSL_DOB, // COLLECTION_MONGO_CSL_MAIN_ENTRY // ], // COLLECTION_MONGO_CSL_POB_LIST => [ // COLLECTION_MONGO_CSL_UID, // COLLECTION_MONGO_CSL_POB, // COLLECTION_MONGO_CSL_MAIN_ENTRY // ], // COLLECTION_MONGO_CSL_PRG_LIST => [ // COLLECTION_MONGO_CSL_PRG // ] ]; //================================================================================================================= // WAREHOUSE DECLARATIONS // ---------------------- // This section handles the warehousing configuration for the class. If a data table is eligible to be ware- // housed, then this section contains all the configuration information, including permissions, for the destination // repository. Note that we need to support bi-directional flow for data. // // Terms/Definitions: // ------------------ // HOT -- data is in production // COOL -- data has been warehoused, maintains schema, but with indexing changes. // COLD -- data has been warehoused but formatted to the destination schema, usually CSV. // WARM -- indicates any data moving from COLD -> HOT // // Design Features: // ---------------- // Supported // This is a boolean value that indicates if the class supports warehousing. If this is set to false, then // warehousing requests for the class will be rejected. // // Remote Support // -------------- // This is a boolean value that indicates if the class will support a warehouse source outside of the Namaste // framework. If this is set to false, and a user submits a request defining the data source as a remote // repository, the request will be rejected. // // Automated // This is a boolean value that indicates if the class allows automated warehousing, meaning that data will be // warehoused once the qualifying condition has been met. // // Dynamic // Boolean value that, if set to true, indicates that the class will accept dynamic requests. Otherwise, the // warehousing operations will follow the interval schedule. Defaults to false. // // Interval // This is a string value that tells the AT_micro-service how often to run automated warehousing on the data. // D = Daily, M = 1st of every month, Q = 1st of every quarter, Y = 1st of every year // The default setting for this value should be monthly (M). // // Qualifier // This is a query string, similar to what you would provide to Namaste for a fetch operation, that establishes // the filter/criteria for moving data to the warehouse. If Supported is set to true, this cannot be blank. // // Override // Boolean value indicating if, and only for dynamic event requests, if the Qualifier can be overridden. If // set to true, the the event request must contain a valid query filter. // // Delete // This is a string value that tells Namaste what to do with the source data once successfully warehoused. // H = hard delete, S = soft delete // Note that this value overrides the $setDeletes setting. // //================================================================================================================= public ?array $wareHouse = [ WH_SUPPORTED => false, // must be set to true for data class to support any warehousing WH_REMOTE_SUPPORT => false, // must be set to true to import data into this class from remote source WH_AUTOMATED => false, // must be set to true for warehousing to be automatically processed WH_DYNAMIC => false, // must be set to true to allow non-scheduled event requests WH_INTERVAL => 'M', // must be either D, M, Q or A, defaults to M WH_OVERRIDE => false, // must be set to true to allow an ad-hoc query filter WH_DELETE => 'H', // must be either H, or S. Can be reset to T via meta. Default: H // default warehouse query to grab records where the date is LT a value and the status is active: // the null value will be replaced with the value provided by the client in the wh request payload. WH_QUALIFIER => [ DB_CREATED => [OPERAND_NULL => [OPERATOR_LT => [null]]], DB_STATUS => [OPERAND_NULL => [OPERATOR_EQ => [STATUS_ACTIVE]]], OPERAND_AND => null ] ]; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // CLASS METHODS... //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * __construct() -- public method * * Constructor in this template not only registers the shutdown method, but also allows us to generate a custom * GUID string during instantiation by use of the input parameters: * * $_getGUID - boolean, defaults to false but, if true, will generate a GUID value and store it in the class member * $_lc - boolean, defaults to false but, if true, will generate a GUID using lower-case alpha characters * * If we generate a GUID on instantiation, the GUID will be stored in the class member. This allows us to both * instantiate a session class object and a GUID value, (the most requested, post-instantiation, action), at the * same time. All the efficient. * * * HISTORY: * ======== * 12-03-20 mks DB-179: original coding * * @author mike@givingassistant.org * @version 1.0 * */ public function __construct() { $this->authToken = NULL_TOKEN; register_shutdown_function([$this, STRING_DESTRUCTOR]); } /** * processCONSList() -- public template method * * This template method is accessible through the factory widget->template object only. * * The CONS list is available from the US Treasure Department and contains a list of Individuals and Entities * that have sanctions applied that prohibits the transfer of funds. * * The list, available at: * https://home.treasury.gov/policy-issues/financial-sanctions/consolidated-sanctions-list-data-files * and known as: consolidated.xml, contains the entire sanctions list. * * The format of the list is not conducive to efficient data storage so we're going to manipulate some of the * columnar elements, which are lists (arrays), s.t. we're removed the superflous and redundant sub-container * headers so that all lists are associative arrays of indexed arrays. * * The function requires the following input parameters: * * $_file - the fqfn file containing the CONS XML list * $_errs - a call-by-reference parameter that will return processing errors back to the calling client * $_lastUpdated - a call-by-reference parameter that returns the date when the list was updated last * $_recCount - a call-by-reference parameter containing the total number of records as report by USDoT * * On successful processing, the function returns an array, the processed cons list with the header removed * and the sub-arrays all nice and homogeneous. * * If there was an error raised in processing, we'll store a copy of the error in $_errs and return a null * value back to the calling client. * * If an exception is raised, a null will be returned and the error(s) logged to the db and to the console. * * * @author mike@givingassistant.org * @version 1.0 * * * @param string $_file * @param array|null $_errs * @param string $_lastUpdated * @param int $_recCount * @return array|null * * * HISTORY: * ======== * 12-07-20 mks DB-180: original coding * */ public function processCONSList(string $_file, ?array &$_errs, string &$_lastUpdated = '', int &$_recCount = 0): ?array { $method = basename(__METHOD__); $aryRetData = null; if (empty($_file)) { $hdr = sprintf(INFO_LOC, $method, __LINE__); $_errs[] = $hdr . ERROR_PARAM_404 . STRING_LIST; return null; } // see if we can open the file for reading $fp = simplexml_load_file($_file); if (false === $fp) { $hdr = sprintf(INFO_LOC, $method, __LINE__); $_errs[] = $hdr . ERROR_OPEN_XML_FILE . $_file; return null; } else { try { if (is_null($consData = objectToArray($fp))) { $hdr = sprintf(INFO_LOC, $method, __LINE__); $_errs[] = $hdr . ERROR_DATA_OBJ_2_ARY_FAIL; return null; } } catch (Throwable | TypeError $t) { $hdr = sprintf(INFO_LOC, $method, __LINE__); @handleExceptionMessaging($hdr, $t->getMessage(), $_errs, true); return null; } } // at this point, we have successfully loaded the CONS XML file and have stored it, as an array, in $consData // we need to clean up the "lists" embedded as sub-arrays since they XML introduced an artificial layer // pointing to the data: $consData['sdnEntry']['akaList']['aka'][0, ..., n] // ^^^^^ <--- this is the layer to be removed $records = $consData[CONS_SDN_ENTRY]; $consMeta = $consData[CONS_PUB_INFO]; $_lastUpdated = $consMeta[CONS_PUB_DATE]; $_recCount = intval($consMeta[CONS_REC_COUNT]); // list of list (entities) -- these are the sub-collections as opposed to sub-arrays $lol = [COLLECTION_MONGO_CSL_AKA_LIST => COLLECTION_MONGO_CSL_AKA, COLLECTION_MONGO_CSL_ADDR_LIST => COLLECTION_MONGO_CSL_ADDRESS, COLLECTION_MONGO_CSL_ID_LIST => COLLECTION_MONGO_CSL_ID, COLLECTION_MONGO_CSL_PRG_LIST => COLLECTION_MONGO_CSL_PRG, COLLECTION_MONGO_CSL_DOB_LIST => COLLECTION_MONGO_CSL_DOB_ITEM, COLLECTION_MONGO_CSL_POB_LIST => COLLECTION_MONGO_CSL_POB_ITEM ]; try { foreach ($records as &$record) { foreach ($record as $field => $fieldValue) { if (is_array($fieldValue) and array_key_exists($field, $lol)) { if (is_array($record[$field][$lol[$field]]) and is_numeric(key($record[$field][$lol[$field]]))) { // sub-array has multiple elements foreach ($record[$field][$lol[$field]] as $subRecord) { $record[$field][] = $subRecord; } } else { // sub-array has but a single element $record[$field][] = $record[$field][$lol[$field]]; } unset($record[$field][$lol[$field]]); } } } } catch (Throwable | TypeError $t) { $hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__); @handleExceptionMessaging($hdr, $t->getMessage(), $_errs, true); return null; } return $records; } /** * saveCONSList() -- public template method * * This method has the following input parameters: * * $_data -- this is the broker request data array * $_errs -- a call-by-reference parameter for returning processing errors back to the calling client * * The method will return a null value when there are errors in parsing the input parameters, or from saving * the XML file to disk. * * Otherwise, on success, the method returns the FQFN of the saved XML file. * * * @author mike@givingassistant.org * @version 1.0 * * @param array $_data * @param array|null $_errs * @return string|null * * * HISTORY: * ======== * 12-09-20 mks DB-180: original programming * */ public function saveCONSList(array $_data, ?array &$_errs): ?string { $method = basename(__METHOD__); if (empty($_data)) { $hdr = sprintf(INFO_LOC, $method, __LINE__); @handleExceptionMessaging($hdr, ERROR_DATA_ARRAY_EMPTY, $_errs, true); return null; } if (!is_array($_data)) { $hdr = sprintf(INFO_LOC, $method, __LINE__); @handleExceptionMessaging($hdr, ERROR_DATA_ARRAY_NOT_ARRAY . STRING_DATA, $_errs, true); return null; } if (!array_key_exists(STRING_DATA, $_data) or empty($_data[STRING_DATA])) { $hdr = sprintf(INFO_LOC, $method, __LINE__); @handleExceptionMessaging($hdr, ERROR_DATA_ARRAY_EMPTY . COLON . STRING_DATA, $_errs, true); return null; } try { // extract the XML file from the data payload $xmlFile = $_data[STRING_DATA]; // write the file to tmp storage $guid = guid(); $fqfn = DIR_TMP . SLASH . $guid . DOT . FILE_TYPE_XML; if (false === file_put_contents($fqfn, $xmlFile)) { $hdr = sprintf(INFO_LOC, $method, __LINE__); @handleExceptionMessaging($hdr, ERROR_SAVE_XML_FILE . $fqfn, $_errs, true); return null; } } catch (Throwable | TypeError $t) { $hdr = sprintf(INFO_LOC, $method, __LINE__); @handleExceptionMessaging($hdr, $t->getMessage(), $_errs, true); return null; } return $fqfn; } /** * cleanUp() -- public template method * * This file has a single, required, input parameter: the FQFN of the original XML file. Once validated, we'll * load the XML file into a variable and use that string as the VALUE value in the system-data table for row #2. * * This means that the last CONS list added to the Segundo collection has been stored (for archival and validation * purposes) as a flat-file in a column in a mongo table. * * * @author mike@givingassistant.org * @version 1.0 * * @param string $_file * * * HISTORY: * ======== * 12-15-20 mks DB-180: original coding * */ public function cleanUp(string $_file = ''):void { $errors = []; try { if (strlen($_file) and file_exists($_file)) { $contents = file_get_contents($_file); if (false === $contents) { consoleLog('CONS: ', CON_ERROR, ERROR_OPEN_XML_FILE . $_file); return; } // delete the XML file unlink($_file); // instantiate a system-data widget $meta = [ META_TEMPLATE => TEMPLATE_CLASS_SYS_DATA, META_CLIENT => CLIENT_SYSTEM, META_EVENT_GUID => guid() ]; /** @var gacMongoDB $widget */ if (is_null($widget = grabWidget($meta, '', $errors))) { consoleLog('CONS: ', CON_ERROR, ERROR_FAILED_TO_INSTANTIATE . TEMPLATE_CLASS_SYS_DATA); } else { // save the XML data to the system-data table $data = [ DATA_KEY => DATA_CONS, DATA_VALUE => $contents, ROW_ID => SYS_DATA_ROW_ID_CONS ]; $bc = new gacWorkQueueClient( basename(__METHOD__) . AT . __LINE__); if (!$bc->status) { $hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__); @handleExceptionMessaging($hdr, sprintf(ERROR_BROKER_CLIENT_INSTANTIATION, BROKER_QUEUE_AI, null, true)); } else { $payload = [ BROKER_REQUEST => BROKER_REQUEST_CREATE, BROKER_DATA => [$data], BROKER_META_DATA => $meta ]; if (false === $bc->call(gzcompress(json_encode($payload)))) consoleLog('CONS: ', CON_ERROR, sprintf(ERROR_MDB_QUERY_FAIL, STRING_UPSERT)); } if (is_object($bc)) $bc->__destruct(); unset($bc); } } } catch (TypeError | Throwable $t) { $hdr = sprintf(INFO_LOC, basename(__FILE__), __LINE__); @handleExceptionMessaging($hdr, $t->getMessage(), $errors, true); } if (isset($widget) and is_object($widget)) { $widget->__destruct(); unset($widget); } } /** * __clone() -- private function * * Silently disallows cloning of the object * * @return null * * HISTORY: * ======== * 12-03-20 mks DB-179: original coding * * @version 1.0 * * @author mike@givingassistant.org */ private function __clone() { return (null); } /** * __destruct() -- public function * * As of PHP 5.3.10 destructors are not run on shutdown caused by fatal errors. * * The destructor is registered as a shut-down function in the constructor -- so any recovery * efforts should go in this method. * * @author mike@givingassistant.org * @version 1.0 * * HISTORY: * ======== * 12-03-20 mks DB-179: original coding * */ public function __destruct() { // blank } }