Files
namaste/classes/gaaNamasteCore.class.inc
gramps 373ebc8c93 Archive: Namaste PHP AMQP framework v1.0 (2017-2020)
952 days continuous production uptime, 40k+ tp/s single node.
Original corpo Bitbucket history not included — clean archive commit.
2026-04-05 09:49:30 -07:00

3389 lines
155 KiB
PHP

<?php
use MongoDB\Driver\WriteResult;
/**
* gaaNamasteCore.class.inc
*
* This is the core class abstraction for the namaste framework.
*
* Methods that are independent of data schema, yet required by all schemas, are maintained in this class.
*
* Within this class, we also declare the abstract methods required in all parent schema classes.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* HISTORY:
* ========
* 06-15-17 mks CORE-447: basic fw structure/first check-in
* 03-02-18 mks CORE-680: deprecated trace logging
* 06-06-18 mks CORE-1013: support for fetching data from remote services
* 08-01-18 mks CORE-774: PHP7.2 exception handling
* 09-01-18 mks DB-48: updated _createRecord() abstraction
* 10-17-18 mks DB-59: Support for audit/journaling
* 03-04-19 mks DB-116: Cache-Mapping v2, deprecated $cacheKeys
* 01-20-20 mks DB-150: PHP 7.4 compliance re-factoring, deprecated unused methods
*
*/
abstract class gaaNamasteCore
{
/*
* define the abstracted functions for CRUD and basic operations. Regardless of the storage schema used, all
* data-schema classes that extend this abstraction will be required to use CRUD methods that follow a
* common naming convention.
*/
public abstract function _createRecord(array $_data, string $_preValidated = DATA_NORM):void;
public abstract function _fetchRecords(array $_data):void;
public abstract function _updateRecord(array $_data):void;
public abstract function _deleteRecord(array $_data):void;
public abstract function _checkQuery(array $_query):bool;
protected abstract function _lockRecord();
protected abstract function _releaseLock();
protected abstract function _isLocked();
protected abstract function _getQC(array $_data):bool;
// CLASS MEMBERS
// -----------------------------------------------------------------------------------------------------------------
protected array $data; // container for 1...N records from the schema collection
protected array $config; // container for the xml configuration for the class
public bool $debug; // debug function enabled? (reads from config)
public int $count; // number of records currently stored in $data
public int $queryCount; // number of total records returned by the query
public int $recordsReturned = 0; // number of records returned (skip/limit)
public int $recordsInCollection = 0; // number of records in the current collection
public int $recordsInQuery; // number of records returned by a query
public ?array $recordTokenList = null; // list of pre-query fetch tokens for mongoDB Limit
public string $ext; // extension for the schema collection
public array $fieldList = array(); // indexed array of col names
public array $protectedFields = array(); // indexed array of protected column names
public array $fieldTypes = array(); // k-v paired of col names to data types
public array $indexes = array(); // which columns are indexes
public array $indexList = array(); // list of index names used for partial-index validation
public array $indexOrder = array(); // which indexes are DESC order
public array $fuzzyIndexes = array(); // which indexes are converted to fuzzy-searching
public array $uniqueIndexes = array(); // which columns are unique indexes
public array $singleIndexes = array(); // which columns are single-field indexes
public array $partialIndexes = array(); // which columns are partial indexes (mongo)
public array $compoundIndexes = array(); // which columns compose compound indexes
public array $ttlIndexes = array(); // which columns are time-to-live indexes (mongo)
public array $multiKey = array(); // which columns are multi-key indexes (mongo)
public array $requiredFields = []; // indexed array of minimally required columns for save
public ?object $historyData; // holds the gacMeta class
public string $pKey; // primary key for the schema collection
public string $entity; // which entity are we working with in this instantiation?
public array $eventMessages = array(); // event messages (result text for the broker return)
public string $class = ''; // which class was instantiated?
// class-setting variables (bool switches)
public bool $useCache; // does this instantiation use cache?
public bool $useDeletes; // true = hard, false = soft
public bool $useTimers; // does this instantiation use query timers?
public bool $useAuditing; // does this instantiation use auditing?
public bool $useJournaling; // does this instantiation use journaling?
public bool $useToken; // does this instantiation use a token identifier?
public bool $useLocking; // does this instantiation use record-level locking?
public bool $useDetailedHistory; // does this instantiation use detailed historyData?
public bool $allowUpdates; // does this instantiation allow updating of records?
public bool $event; // defines the CRUD event for processing
public array $sysEvents; // list container for generated system events
public array $returnData; // generic storage for method return data
public array $validTemplates; // list of currently-valid template names
public ?string $authToken; // container for SMAX-API client tokens
protected array $hiddenColumns; // contains list of column names to always hide from client
// string variables
public string $defaultStatus; // sets the default status for new record create events
public string $searchStatus; // sets the default search status for general fetch queries
public array $binaryFields; // defs bin fields for search-exceptions & amqp processing
public array $cacheMap; // provides mapping of fields names from schema -> cache
public ?array $exposedFields; // catalog of exposed fields instead of cacheMap
public int $rowsAffected = 0; // count of the number of rows affected by a query
protected bool $isMigration; // bypasses some operations for migration requests
protected int $cacheExpiry; // seconds class object remains cached before expiring
protected int $queryThreshold; // number in milliseconds to flag a query as slow
protected int $queryWarning; // number in milliseconds to flag a query as a slow warning
public ?array $queryResults = null; // contains return values from various API directives
protected WriteResult $bwResult; // contains the $results object from bulk write operations
protected string $templateName; // named of the template used to instantiate the class
protected string $templateClass; // contains the template class name
// public array $templateReport; // copy of the template report generated by the factory
protected gacMeta $meta; // contains the meta-data definition
protected array $metaPayload; // holds the current request meta payload data array
public string $dbService; // defines the current database service in use
public string $schema; // contains the template report/schema-report for the class
public ?string $strQuery = null; // stores the last query executed
public array $auditCreateQueries = []; // stores list of create queries for audit/journaling
public array $auditUndoQueries = []; // stores list of undo queries for audit/journaling
public ?array $auditRecordList = []; // list of record guids impacted by audit/journaling event
public array $tokenList = []; // list of tokens affected by a query, for cache-updates
public string $strSubCollectionQuery; // stores the last sub-collection query executed
public string $restoreQuery; // stores the restore query for journaling
protected array $subCollections; // stores the array(array) of sub-collections definitions
protected array $validStates; // a list of valid states
protected array $validStatus; // a list of valid statuses
protected bool $skipReadAudit = false; // excludes database reads from audit if set to true
public ?gacErrorLogger $logger = null; // logger class container for the core object
protected object $connection; // container for the database resource/connection
public string $collectionName; // container for the current table name or view
public string $queryTable; // can only be a table name (not a view)
protected int $recordLimit; // dynamic per class: number of records returned per query
public array $migrationConfig; // stores the migration information data from the template
public ?array $warehouseConfig; // stores the warehouse information data from the template
private string $res = 'CORE: '; // logger id
// note that the state/status values cover the health of the object - not the object's data!
public ?string $state; // the state of the current instantiation
public bool $status = false; // boolean indicator of health of current instantiation
public string $eventGUID = ''; // identifies the broker event
public string $sessionGUID = ''; // container for the current session GUID
public string $userGUID = ''; // container for the userGUID, if needed
public ?object $objUser = null; // container for the user object class
public ?object $objSession = null; // container for the session object class
public string $client; // defines requesting client entity (System, Batch, etc.)
public bool $isWHRequest = false; // defines if the current operation is a WH request
// public string $whType = ''; // defines the level of warehousing (cool, cold, etc.)
public gatAudit $templateAudit; // container for the audit template
public gatJournaling $templateJournal; // container for the journal template
public array $recordAudit; // array container for the audit record data
public array $recordJournal; // array container for the journal record data
// CORE-1013 -------------------------------------------------------------------------------------------------------
private gacBrokerClient $brokerClient; // defines broker client object for remote service requests
public ?object $template = null; // may contain a copy of the template file being processed
// CORE-1013 -------------------------------------------------------------------------------------------------------
/**
* gaaNamasteCore constructor.
*
* the NamasteCore abstraction constructor sets-up the logger (if not already done so in the parent class),
* and then loads several configuration settings into member properties, as well as loading some key constants
* that are used through-out the framework. It also scans the template directory and builds a list of valid
* templates.
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_eventGUID
*
* HISTORY:
* ========
* 06-15-17 mks CORE-447: original coding
* 09-13-17 mks CORE-561: added event guid as param for easier invocation
* 03-12-18 mks Fixing declarations of array params init'ing them to arrays instead of nulls - this will
* squelch error warnings in the PHP log.
* 03-04-19 mks DB-116: deprecated $cacheKeys for cacheMapping v2
* 01-03-20 mks DB-150: pacifying call to squelchIDEWarning()
*
*/
public function __construct(string $_eventGUID = null)
{
// DO NOT PUT A LOGGER_EVENT CALL IN THIS CONSTRUCTOR!
register_shutdown_function([$this, STRING_DESTRUCTOR]);
if (!isset($this->logger)) {
try {
$this->logger = new gacErrorLogger($_eventGUID);
} catch (Throwable $t) {
consoleLog($this->res, CON_ERROR, ERROR_THROWABLE_EXCEPTION . COLON . $t->getMessage());
return;
}
}
$this->squelchIDEWarnings(); // init some unused params to keep the IDE calm
// set-up the database timer parameters
if (gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_QUERY_TIMERS]) {
if (isset(gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_QUERY_TIMERS]) and gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_QUERY_TIMERS]) {
if (isset(gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_TIMER_SLOW_QUERY_ALERT])) {
$this->queryThreshold = intval(gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_TIMER_SLOW_QUERY_ALERT]);
if (isset(gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_QUERY_TIMER_WARNINGS]) and gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_QUERY_TIMER_WARNINGS]) {
$this->queryWarning = gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_TIMER_SLOW_QUERY_WARNING];
} elseif (!isset(gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_QUERY_TIMER_WARNINGS])) {
$this->queryWarning = $this->queryThreshold * NUMBER_DEF_HWM;
}
} else {
// todo - systemEvent::framework configuration violation
$this->useTimers = 0;
}
}
}
$this->debug = gasConfig::$settings[CONFIG_DEBUG];
$this->event = DB_EVENT_NONE;
$this->hiddenColumns = [DB_PKEY, MONGO_ID];
$this->sysEvents = [];
$this->data = array();
$this->validTemplates = gasStatic::loadValidTemplateNames();
$this->loadStates();
$this->loadStatus();
$this->data = [];
$this->count = 0;
$this->strQuery = null;
$this->collectionName = NONE;
}
/**
* remoteFetchRequest() -- public method
*
* This method requires one input parameter -- an array, that is a copy of the fetch-request submitted to the
* read broker.
*
* The method returns either a null, indicating a processing error was raised. Otherwise, we return a uncompressed
* data payload received by the remote service.
*
* We instantiate a broker client based on the current class' template service and we validate the service - if the
* service of the current class is the appServer, we deny the request because the appServer shouldn't farm out
* these request since all fetch requests are directed to the appServer.
*
* If the service is not (yet) supported (tercero) or unknown, generate an error and return.
*
* Once we pass validation, then we generate a new payload request and submit it to the remote service, process
* the return payload and pass that back to the calling client.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param array $_request
* @return array|null
*
* HISTORY:
* ========
* 06-06-18 mks CORE-1013: original coding
* 07-30-20 mks DB-142: tercero support cut; these events are now handled in functions::validateMetaData()
*
*/
public function remoteFetchRequest(array $_request): ?array
{
// lvar init
$this->state = STATE_DATA_ERROR;
$this->status = false;
// remember, the class object is already instantiated -- we "know" where this request will be sent...
// validation
if (!is_array($_request)) {
$this->eventMessages[] = ERROR_DATA_ARRAY_NOT_ARRAY . BROKER_REQUEST;
return null;
}
if (!isset($_request[BROKER_DATA])) {
$this->eventMessages[] = ERROR_ARRAY_KEY_404 . BROKER_DATA;
return null;
} elseif (!isset($_request[BROKER_META_DATA])) {
$this->eventMessages[] = ERROR_ARRAY_KEY_404 . BROKER_META_DATA;
return null;
}
switch ($this->template->service) {
case CONFIG_DATABASE_SERVICE_APPSERVER :
$this->eventMessages[] = ERROR_RSR_APPSERVER;
$this->state = STATE_SCHEMA_ERROR;
return null;
break;
case CONFIG_DATABASE_SERVICE_ADMIN :
$this->brokerClient = new gacBrokerClient(BROKER_QUEUE_AO, basename(__METHOD__) . AT . __LINE__);
break;
case CONFIG_DATABASE_SERVICE_SEGUNDO :
$this->brokerClient = new gacBrokerClient(BROKER_QUEUE_WH, basename(__METHOD__) . AT . __LINE__);
break;
default :
$this->eventMessages[] = sprintf(ERROR_RSR_NOT_DEF, $this->template->service);
$this->state = STATE_TEMPLATE_ERROR;
return null;
break;
}
// if we get to this point, we have a valid broker client - create the appropriate payload and submit
$meta = $_request[BROKER_META_DATA];
if (!isset($meta[META_CLIENT])) $meta[META_CLIENT] = CLIENT_SYSTEM;
if (!isset($meta[META_SYSTEM_NOTES])) $meta[META_SYSTEM_NOTES] = INFO_INTERNAL_REQUEST;
$request = [
BROKER_REQUEST => BROKER_REQUEST_REMOTE_FETCH,
BROKER_DATA => $_request[BROKER_DATA],
BROKER_META_DATA => $meta
];
$payload = gzcompress(json_encode($request));
// publish the request...
try {
$response = $this->brokerClient->call($payload);
if (is_object($this->brokerClient)) $this->brokerClient->__destruct();
unset($this->brokerClient);
return (json_decode(gzuncompress($response), true)); // don't judge
} catch (Throwable $t) {
$msg = ERROR_THROWABLE_EXCEPTION . COLON . $t->getMessage();
$this->eventMessages[] = $msg;
if (isset($this->logger) and $this->logger->available) {
$this->logger->warn($msg);
} else {
consoleLog($this->res, CON_ERROR, $msg);
}
}
return null;
}
/**
* loadStatus() -- private method
*
* this method takes no input parameters.
*
* the method loads a list of valid statuses into an array container and returns.
*
* Programmer's Note:
* ------------------
* If you change an existing status, you must update the status value here.
* If you delete an existing status, you must delete the status here.
* If you add a new status, you must add the status here.
*
* todo: create the "system" table/collection and pull values from there
*
*
* @author mike@givingassistant.org
* @version 1.0
*
*
* HISTORY:
* ========
* 06-20-17 mks original coding
*
*/
private function loadStatus(): void
{
$this->validStatus = [
STATUS_PENDING, STATUS_ACTIVE, STATUS_DELETED, STATUS_SUSPENDED, STATUS_EXPIRED, STATUS_REVOKED,
STATUS_LOCKED, STATUS_COMPLETE, STATUS_IN_PROGRESS, STATUS_ABANDONED, STATUS_INVALID, STATUS_VALID,
STATUS_REJECTED, STATUS_APPROVED, STATUS_FAILED, STATUS_CLOSED, STATUS_SENT, STATUS_REQUEST_SENT,
STATUS_INACTIVE, STATUS_NEW, STATUS_FAKE
];
}
/**
* setState() -- protected method
*
* this method sets the state of the current class instantiation.
*
* otherwise, the current class' state is set to the passed value:
* $_newState one of the defined error states
*
* method will also generate a back-trace informational message, add the message to both the diagnostics member
* array and as output to the log. This is optionally done if the second input parameter is explicitly
* over-rode by the calling client under the assumption that the state change was the result of a detected
* error or other problem.
*
* If the $_newState is invalid, set the status flag to false, log an error, and return.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_newState
* @param bool $_dbt
*
* HISTORY:
* ========
* 06-20-17 mks original coding
*
*/
protected function setState(string $_newState, bool $_dbt = false): void
{
if ($_dbt) {
$backTrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
$msg = $backTrace[0][ERROR_FILE] . '.' . $backTrace[1][ERROR_FUNCTION];
$msg .= '(' . $backTrace[0][ERROR_LINE] . ').' . $backTrace[2][ERROR_CLASS] . '(' . $backTrace[2][ERROR_LINE] . ')' . COLON . $_newState;
//var_dump($backTrace);
//echo 'class: ' . $this->class . PHP_EOL;
//var_dump($msg);
if (isset($this->logger) and $this->logger->available)
$this->logger->error($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
$this->eventMessages[] = $msg;
unset($backTrace);
}
if (empty($this->validStates) or !is_array($this->validStates)) $this->loadStates();
if (!in_array($_newState, $this->validStates)) {
$msg = ERROR_INVALID_STATE . $_newState;
$this->eventMessages[] = $msg;
if (isset($this->logger) and $this->logger->available)
$this->logger->error($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
$this->status = false;
return;
}
$this->state = $_newState;
}
/**
* loadStates -- private class method
*
* this method loads a list of the current, valid, states into a member array for validation.
*
* the method requires no input parameters, and the return is explicit in the setting of the class member.
*
* Programmer's Note:
* ------------------
* If you change an existing state, use the refactor option.
* If you delete an existing state, or add a new state, remember you need to update this list.
*
* todo: again, this data should come from a table and not the constants list for maintainability
*
*
* @author mike@givingassistant.org
* @version 1.0
*
*
* HISTORY:
* ========
* 06-20-17 mks original coding
* 09-28-17 mks CORE-572: updated for cache-fetch state, added sort feature
*
*/
private function loadStates()
{
$this->validStates = [
STATE_SUCCESS, STATE_VALIDATION_ERROR, STATE_SCHEMA_ERROR, STATE_NOT_FOUND, STATE_DOES_NOT_EXIST,
STATE_ALREADY_EXISTS, STATE_ALREADY_PROCESSED, STATE_REQUEST_REJECTED, STATE_DATA_ERROR,
STATE_CRYPTO_ERROR, STATE_SECURITY_ERROR, STATE_CLASS_INSTANTIATION_ERROR, STATE_GUID_ERROR,
STATE_TEMPLATE_ERROR, STATE_META_ERROR, STATE_FRAMEWORK_WARNING, STATE_FRAMEWORK_FAIL,
STATE_FAIL, STATE_FILE_ERROR, STATE_FILE_NOT_FOUND, STATE_PENDING, STATE_RESOURCE_ERROR,
STATE_RESOURCE_ERROR_MONGO, STATE_RESOURCE_ERROR_RABBIT, STATE_RESOURCE_ERROR_CACHE,
STATE_RESOURCE_ERROR_XML, STATE_DUPLICATE_RECORD, STATE_DATA_TYPE_ERROR, STATE_FILESYSTEM_ERROR,
STATE_AUTH_ERROR, STATE_DB_ERROR, STATE_AJ_ERROR, STATE_AMQP_ERROR, STATE_CACHE_ERROR,
STATE_LOCKED, STATE_ACCOUNT_LOCKED, STATE_ACCOUNT_NOT_LOCKED, STATE_TIMER, STATE_STATUS,
STATE_BLACK_LIST, STATE_NOT_WHITE_LIST, STATE_MAIL_FAIL, STATE_BIN_FAIL, STATE_JSON_FAIL,
STATE_ENCODE_FAIL, STATE_DECODE_FAIL, STATE_INDEX_ERROR, STATE_CACHE_FETCH, STATE_NOT_SUPPORTED
];
sort($this->validStates, SORT_STRING);
}
/**
* getColumn() -- public method
*
* this is one way data is fetched outside of the instantiation stack, since $data is a protected member variable,
* by the framework.
*
* this method allows the retrieval of a single value within a matrix of data that's determined by both the
* name of the column and the indexed row.
*
* this method will ONLY work with the defined column names -- it will NOT work with kepMapped column names.
*
* There's a wee bit o' flexibility built-in to the method in that, if the key passed does not have the class
* extension appended, this method will append the extension prior to referencing the matrix value.
*
* if any of the validation checks fail, or if the key is not found in the current classes' data matrix,
* then a null is returned to the calling client, the class member state/status variables are set accordingly,
* and the diagnostics array is populated if there was a non-NotFound error generated.
*
* If the data was located in the matrix, then the data is returned (as-is) to the calling client. Note that
* there is no restriction on the type($data) returned.
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_key -- the column name (extension optional)
* @param int $_row -- which row of the data matrix to return (defaults to the first record)
* @return null | mixed -- returns either a null or the data found at the referenced intersection
*
* HISTORY:
* ========
* 06-21-17 mks original coding
*
*/
public function getColumn(?string $_key, int $_row = 0)
{
$this->state = STATE_VALIDATION_ERROR;
$this->status = false;
$value = null;
if (empty($this->data)) {
$this->state = STATE_DOES_NOT_EXIST;
$this->eventMessages[] = ERROR_DATA_404;
} elseif (!is_array($this->data)) {
$this->eventMessages[] = ERROR_DATA_FIELD_NOT_MEMBER;
} elseif (($_row + 1) > $this->count) {
$this->eventMessages[] = ERROR_DATA_INCONSISTENT_COUNT;
} elseif (empty($_key)) {
$this->eventMessages[] = ERROR_DATA_INPUT_EMPTY . STRING_KEY;
} elseif (!is_string($_key)) {
$this->eventMessages[] = ERROR_DATA_INVALID_FORMAT . COLON . STRING_KEY;
} elseif (empty($this->ext)) {
$this->eventMessages[] = ERROR_CLASS_EXT_404;
} else {
try {
// if the key was passed without an extension, then append it
$_key = $this->addExtension($_key);
} catch (TypeError $t) {
$msg = ERROR_TYPE_EXCEPTION . COLON . $t->getMessage();
$this->eventMessages[] = $msg;
if (isset($this->logger) and $this->logger->available)
$this->logger->warn($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
return null;
}
if (is_null($_key)) return $_key;
// support for Boolean(false) content (false !== null)
if (false === @$this->data[$_row][$_key]) {
$value = false;
} elseif (isset($this->data[$_row][$_key])) {
$value = $this->data[$_row][$_key];
// $value = (empty($this->data[$_row][$_key]) and is_null($this->data[$_row][$_key])) ? null : $this->data[$_row][$_key];
}
if (!is_null($value) or false === $value) {
$this->state = STATE_SUCCESS;
$this->status = true;
return $value ;
} else {
$this->state = STATE_NOT_FOUND;
}
}
return null;
}
/**
* addExtension() -- public method
*
* this method requires one input-parameter:
*
* $_key - the key field which should be a collection column name
*
* we're not going to validate the key name as being a part of the current fieldList as there may some point where
* I just want to add an extension to some string.
*
* we are going to throw a fatal error (to the logs) if the current instantiation does not have a defined
* class extension.
*
* If the string passed-in to the method ($_key) is a mongo id (_id) or already has the extension appended,
* then return the same value passed-in. Otherwise, append the extension to the current string.
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param $_key
* @return null|string
*
* HISTORY:
* ========
* 06-21-17 mks original coding
*
*/
public function addExtension($_key): ?string
{
if (empty($this->ext)) {
if (isset($this->logger) and $this->logger->available)
$this->logger->warn(ERROR_CLASS_EXT_404);
else
consoleLog($this->res, CON_ERROR, ERROR_CLASS_EXT_404);
$this->eventMessages[] = ERROR_CLASS_EXT_404;
$this->state = STATE_FRAMEWORK_WARNING;
$this->status = false;
return null;
} elseif ($_key != MONGO_ID and $_key != DB_HISTORY) {
if ((strlen($_key) < strlen($this->ext)) or (false === @strpos($_key, $this->ext, (strlen($_key) - NUMBER_LEN_CLASS_EXT))))
$_key = $_key . $this->ext;
}
return($_key);
}
/**
* removeExtension() -- public function
*
* This function requires a single input parameter:
*
* $_key -- the field name that will have the extension dropped
*
* The method does a check to see if the incoming string has the class extension explicitly in the last-four
* chars of the string. If so, then it's removed and returned to the calling client. Otherwise, the input string
* is returned.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_key
* @return string
*
*
* HISTORY:
* ========
* 04-21-20 mks ECI-107: original coding
*
*/
public function removeExtension(string $_key):string
{
if (true === @strpos($_key, $this->ext, (strlen($_key) - NUMBER_LEN_CLASS_EXT))) {
return rtrim($_key, $this->ext);
}
return $_key;
}
/**
* getData() -- public method
*
* This is the public-facing method for fetching data from the protected $data member. Note that this process
* involved invoking the scrubber method which filters out the $hiddenColumns from the data-set.
*
* Note:
* -----
* Multiple records in $data is fully supported
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return array|null
*
* HISTORY:
* ========
* 06-22-17 mks original coding
* 08-14-17 mks CORE-493: removed meta-param, support for DB_HISTORY
* 02-28-19 mks DB-116: dropped support for the _clean param, now returning scrubbed-data as a pass-through
*
*/
public function getData(): ?array
{
if (empty($this->data)) return null;
if (!is_array($this->data)) {
$msg = ERROR_DATA_ARRAY_NOT_ARRAY . STRING_DATA;
$this->eventMessages[] = $msg;
if (isset($this->logger) and $this->logger->available)
$this->logger->error($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
return null;
}
// this function is always called and encodes known binary data columns for transport over AMQP wire
$this->makeDataAQMPSafe();
try {
return $this->dataScrub($this->data);
} catch (TypeError $t) {
$msg = ERROR_TYPE_EXCEPTION . COLON . $t->getMessage();
$this->eventMessages[] = $msg;
if (isset($this->logger) and $this->logger->available)
$this->logger->error($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
return null;
}
}
/**
* getCK() -- public method
*
* This method has no inputs.
*
* This method is called when the $data array contains cacheKeys and not class data and is provided to skip
* field validation that occurs when processing a normal data payload.
*
* This method returns whatever is stored in the $data member since the member is protected and cannot be
* accessed outside of the class stack.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return null|array -- returns whatever is stored in $data without evaluation
*
*
* HISTORY:
* ========
* 03-05-19 mks DB-116: original coding
*
*/
public function getCK(): ?array
{
return $this->data;
}
/** @noinspection PhpUnused */
/**
* getRecord() -- public method
*
* There are times, albeit mostly internally, when we just want to retrieve a single record from a data payload.
*
* There are two input parameters to the method:
*
* $_row -- an integer value which determines which row of the current data payload to return
* $_clean -- a boolean value that tells us to clean the returned record or not
*
* To avoid stripping extensions from the data, cache-mapping, filtering, etc., you must explicitly pass the
* boolean value (false) to the second parameter.
*
* The method validates the $_row as (a) an integer, (b) that the count is valid, and (c) within range of the
* current count. If any of these checks fail, then the eventMessages stack is populated with the corresponding
* error and, if debug is active, the failure is logged, and a null value is returned to the calling client.
*
* Otherwise, we grab the referenced record from the current $data payload, check the clean and clean if
* required. The record is then returned back (as an array) to the calling client.
*
*
* NOTE:
* -----
* The array (0) offset is adjusted in this method so that the calling client doesn't have to.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param int $_row
* @param bool $_clean
* @return array|null
*
*
* HISTORY:
* ========
* 05-16-18 mks _INF-188: original coding
*
*/
public function getRecord(int $_row, bool $_clean = true): ?array
{
$error = null;
$record = null;
if (!is_integer($_row))
$error = ERROR_DATA_TYPE_MISMATCH . COLON . ERROR_STUB_EXPECTING . DATA_TYPE_INTEGER . COLON . ERROR_STUB_RECEIVED . $_row;
elseif (!isset($this->count) or $this->count == 0)
$error = ERROR_DATA_ARRAY_EMPTY . COLON . STRING_DATA;
elseif ($_row < $this->count or $_row > $this->count)
$error = ERROR_DATA_RANGE . COLON . $_row . SLASH . $this->count;
if (!is_null($error)) {
$this->eventMessages[] = $error;
if ($this->debug) $this->logger->debug($error);
return null;
}
if (!is_bool($_clean)) $_clean = true;
$record[] = $this->data[($_row - 1)];
if ($_clean) {
try {
$this->dataScrub($record);
} catch (TypeError $t) {
$msg = ERROR_TYPE_EXCEPTION . COLON . $t->getMessage();
$this->eventMessages[] = $msg;
if (isset($this->logger) and $this->logger->available)
$this->logger->error($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
return null;
}
}
return $record[0];
}
/**
* makeDataAMQPSafe() -- public method
*
* this method is intended to take the existing class data and, if the class has binary fields set, then
* we'll examine each binary field within every tuple of data. If the specified field has binary data,
* then we're going to base-64 encode the data and replace the binary element with the encoded string making
* the data tuple safe for transport for AMQP.
*
* there are no input parameters to the method and the method is type void.
*
* @author mike@givingassistant.org
* @version 1.0
*
* HISTORY:
* ========
* 06-22-17 mks original coding
*
*/
public function makeDataAQMPSafe()
{
if (empty($this->binaryFields)) return;
for ($index = 0, $limit = $this->count; $index < $limit; $index++) {
foreach ($this->binaryFields as $binaryField) {
try {
$field = $this->getColumn($binaryField, $index);
if ($this->debug and isset($this->logger) and $this->logger->available)
$this->logger->debug(STRING_PROCESSING_BIN_FIELD . $binaryField);
else
consoleLog($this->res, CON_ERROR, STRING_PROCESSING_BIN_FIELD . $binaryField);
if (!empty($field->bin) and isBinStr($field->bin)) {
$field = base64_encode($field->bin);
// can't call setData() b/c type mismatch will cause rejection
$this->data[$index][$binaryField] = $field;
} elseif (empty($field)) {
if (isset($this->logger) and $this->logger->available)
$this->logger->data(ERROR_BIN_FIELD_404 . $field);
else
consoleLog($this->res, CON_ERROR, ERROR_BIN_FIELD_404 . $field);
}
} catch (Throwable $t) {
$msg = ERROR_THROWABLE_EXCEPTION . COLON . $t->getMessage();
$this->eventMessages[] = $msg;
if (isset($this->logger) and $this->logger->available)
$this->logger->error($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
return;
}
}
}
}
/**
* dataScrub() -- private method
*
* dataScrub() has traditionally been a problem child, especially with large data sets. For historians, a copy
* of the original (mostly) version is stored in the deprecated folder.
*
* This method was cleaned-up because it was notoriously slow and cache-mapping was moved to the broker-services
* level. As such, we no longer care about "cleaning" the data which mostly consisted of stripping off the
* extension from the array keys.
*
* Now, scrubbing happens with the array_diff_key() where we're saving the record sans the IDs from the various
* schemas. And, because we're using the array_diff_key() method, we're no longer looking at every column,
* every value in a record, including the recursive transversing. So, yeah, faster.
*
* Also, we're making a copy of the input parameter because, during testing, I learned that your results
* can be "inconsistent" when you unset() a call-by-reference parameter. (You reset the param pointers on
* the reset() call resetting any changed previously made.)
*
*
* @author mikegivingassistant.org
* @version 1.0
*
* @param array $_data
* @return array|null
*
* HISTORY:
* ========
* 06-22-17 mks original coding
* 08-14-17 mks CORE-493: removing meta param support (DB_HISTORY no longer supported)
* 02-28-19 mks DB-116: removed extension filtering, replaced key-value pair queries with vector-level
* filtering for quicker transversing of the record array. Input param $data is no
* longer a call-by-reference param and we're explicitly returning a copy of the
* modified array back to the calling client.
*
*/
private function dataScrub(array $_data): ?array
{
if (empty($_data)) {
$msg = ERROR_DATA_MISSING_ARRAY . STRING_DATA;
$this->eventMessages[] = $msg;
if (isset($this->logger) and $this->logger->available)
$this->logger->error($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
return null;
}
// make copies of data we're going to be manipulating
$dataCopy = null;
$hiddenFields = $this->hiddenColumns;
// if the current instantiation is PDO, add PKEY_ID plus the class extension, to hiddenFields
if ($this->schema == CONFIG_SCHEMA_MYSQL and isset($this->ext))
$hiddenFields[] = PKEY_ID . $this->ext;
// remove the hidden columns from a record if they exist
for ($index = 0, $last = count($_data); $index < $last; $index++)
$dataCopy[$index] = array_diff_key($_data[$index], $hiddenFields);
return $dataCopy;
}
/**
* setData() -- protected method
*
* the setData method is probably one of the most critical core routines in the framework. Because the $data
* class member is a protected variable, it cannot be modified outside of the abstraction stack. This method,
* then, provides a means of accessing the $data sub-array and allows the framework to set values within the
* sub-array, each of which corresponds to a collection column.
*
* The $data sub-array is designed to be a two-dimensional vector representing actual data in a collection
* or table. The first sub-script is the tuple (row) of the data, and the second subscript represents
* the collection/table column.
*
* The $data elements (columns) are always keyed using the current class' table extension. Always.
*
* The input parameters to the method are as follows:
*
* $_key the name of the column to be set
* $_value the value to set the column to
* $_row which tuple of data to set (defaults to 0th tuple)
*
* The method does not assume that the calling client appended the class extension to the the $_key, so we're
* going to make the following checks before allowing the assignment to occur:
*
* --- if the key, as is, is in the current fieldList, then save the key to a temp variable
* --- if the key, with the appended extension, is in the current field list, save the key to a temp variable
* --- if the key is a mapped key value, the save the referencing-index value (as the key) to a temp variable
*
* Example:
* --------
* $_key = 'lname' .... $this->fieldList['lname_usr'] is the desired column
* --- check 1 will fail because: 'lname' != 'lname_usr'
* --- check 2 will succeed because '(lname . _usr') == 'lname_usr'
* --- check 3 will not execute
*
* $_key = 'lastName' .... $this-fieldList['lname_usr'] is the desired column
* --- check 1 will fail because 'lastName' != 'lname_usr'
* --- check 2 will fail because ('lastName . _usr') != 'lname_usr'
* --- check 3 will succeed because 'lastName' == $this->cacheMap['lname_usr'] => 'lastName'
*
* If we find a matching key, then the next step in validation is to make sure that the value (input parameter 2)
* is the same TYPE as what's indicated in the class's fieldTypes array.
*
* If we found the right key, and the value is consistent, then assign the column to the new value for the $_row
* indicator (parameter 3).
*
* The method makes use of the class' state, status and diagnostics members as a diagnostic aid if the assignment
* request fails. It's the client's responsibility to evaluate the return value and take the appropriate action
* although the method will generate log messages (non-production) if the data is a type-mismatch or the key
* is not found.
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param $_key
* @param $_value
* @param $_row
* @return void
*
* HISTORY:
* ========
* 07-13-17 mks original coding
* 08-18-17 mks CORE-500: fixed PHP Notice for subCollection evaluation
* 09-04-18 mks DB-48: squelching some output limiting it to debug mode
* 02-19-18 mks DB-116: deprecated cache-mapping sections
* 11-06-20 mks DB-171: squelching cachemap log errors if there's no cachemap for the class
*
*/
protected function setData(string $_key, $_value, int $_row = 0): void
{
$this->state = STATE_VALIDATION_ERROR;
$this->status = false;
$loggerAvailable = (isset($this->logger) and $this->logger->available);
// if the current row index is greater than the count of the number of rows currently stored, then only
// only accept the row index if the value is equal to either the count or the count + 1 (new record)
if ($_row > ($this->count + 1)) {
$msg = ERROR_DATA_RANGE . ' _row > count';
$this->eventMessages[] = $msg;
if ($loggerAvailable)
$this->logger->data($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
} elseif (empty($_key)) {
$this->eventMessages[] = ERROR_DATA_INPUT_EMPTY;
if ($loggerAvailable)
$this->logger->data(ERROR_DATA_INPUT_EMPTY . ' missing _key');
else
consoleLog($this->res, CON_ERROR, ERROR_DATA_INPUT_EMPTY . ' missing _key');
} elseif (empty($_value) and ($_value !== false) and ($_value !== 0) and (!is_null($_value))) {
$this->eventMessages[] = ERROR_DATA_INPUT_EMPTY;
if ($loggerAvailable)
$this->logger->data(ERROR_DATA_INPUT_EMPTY . ' missing value for key: ' . $_key);
else
consoleLog($this->res, CON_ERROR, ERROR_DATA_INPUT_EMPTY . ' missing value for key: ' . $_key);
} elseif (empty($this->ext)) {
$this->eventMessages[] = ERROR_CLASS_EXT_404;
if ($loggerAvailable)
$this->logger->error(ERROR_CLASS_EXT_404);
else
consoleLog($this->res, CON_ERROR, ERROR_CLASS_EXT_404);
$this->state = STATE_CLASS_INSTANTIATION_ERROR;
} else {
$dataKey = false;
// check to see if the key is in the field list index:
if (in_array($_key, $this->fieldList)) {
$dataKey = $_key;
} elseif (in_array(($_key . $this->ext), $this->fieldList)) {
$dataKey = ($_key . $this->ext);
} elseif ($this->useCache and !empty($this->cacheMap)) {
$dataKey = array_search($_key, $this->cacheMap);
}
if ($dataKey !== false) {
$this->status = true;
$this->state = STATE_SUCCESS;
if (is_array($_value) and $this->fieldTypes[$dataKey] == DATA_TYPE_OBJECT) {
$_value = (object)$_value;
}
$dataValue = gettype($_value);
$fieldValue = $this->fieldTypes[$dataKey];
// try to recover simple (int <-> string) mismatches
if (gettype($_value) == $this->fieldTypes[$dataKey]) {
$this->data[$_row][$dataKey] = $_value;
$this->count = count($this->data);
} else {
if (($dataValue == DATA_TYPE_INTEGER or $dataValue == DATA_TYPE_DOUBLE) and ($fieldValue == DATA_TYPE_STRING)) {
$_value = (string)$_value;
$this->data[$_row][$dataKey] = $_value;
if ($this->debug) { // courtesy notice if debugging is on
$msg = sprintf(__LINE__ . COLON_NS . ERROR_DATA_FORCE_CAST, $dataKey, $dataValue, $fieldValue);
$this->eventMessages[] = $msg;
consoleLog($this->res, CON_ERROR, $msg);
}
} elseif ($dataValue == DATA_TYPE_STRING and ($fieldValue == DATA_TYPE_INTEGER or $fieldValue == DATA_TYPE_DOUBLE)) {
$_value = ($fieldValue == DATA_TYPE_DOUBLE) ? floatval($_value) : intval($_value);
$this->data[$_row][$dataKey] = $_value;
if ($this->debug) { // courtesy notice if debugging is on
$msg = sprintf(__LINE__ . COLON_NS . ERROR_DATA_FORCE_CAST, $dataKey, $dataValue, $fieldValue);
$this->eventMessages[] = $msg;
consoleLog($this->res, CON_ERROR, $msg);
}
} else {
// we can't allow the data to be added to the collection b/c of type mismatch
$msg = sprintf(ERROR_DATA_TYPE_MISMATCH_DETAILS, $dataKey, $this->fieldTypes[$dataKey], gettype($_value) . COLON . $_value);
$this->eventMessages[] = $msg;
if ($loggerAvailable)
$this->logger->error($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
$msg = ERROR_DATA_FIELD_DROPPED . $_key;
$this->eventMessages[] = $msg;
if ($loggerAvailable)
$this->logger->error($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
$this->state = STATE_DATA_TYPE_ERROR;
}
}
} elseif (count($this->cacheMap)) {
$msg = sprintf(ERROR_DATA_INVALID_CLASS_KEY, $_key, $this->class);
if ($loggerAvailable)
$this->logger->error($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
$this->state = STATE_NOT_FOUND;
}
}
}
/**
* loadPayloadData() -- protected method
*
* loadPayloadData is the method we use to initialize an instantiation class object with payload data and meta-data.
*
* It's protected so that it can be accessed only from within the framework stack.
*
* the method requires two inputs:
*
* $_data -- associative array of key->value pairs that match to the column values of the current collection
* $_event -- string that defines the event, defaults to DB create but can be any legit event
*
* each $_data element is parsed via the call to the setData() protected method (below) and, if found, will be
* inserted into the current $data array at $count position.
*
* if at least one $_data element was added to the current class, then we're going to call the protected method
* metaTransfer() (above) to save/add the meta data to the current record. Upon return from this method, we'll
* update the class count member variable to indicate the addition of a new record to the class $data.
*
* While the state/status members are populated with data determining processing success, the method explicitly
* returns a boolean value to make processing in the client more efficient.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param $_data
* @param $_event
* @return bool
*
*
* HISTORY:
* ========
* 07-13-17 mks CORE-464: original coding
* 10-20-17 mks CORE-585: moved from mongo instantiation class to core, added condition for checking
* schema (mongo) before invoking sub-collection processing
* 09-01-18 mks DB-48: improved for-loop processing to avoid infinite looping which is generally bad
*
*/
protected function loadPayloadData(array $_data, string $_event = DB_EVENT_CREATE): bool
{
$loggerAvailable = isset($this->logger) and $this->logger->available;
$this->state = STATE_VALIDATION_ERROR;
$this->status = false;
$tmpDataContainer = null;
// make sure we have data to save
if (empty($_data)) {
$this->logger->error(ERROR_DATA_404);
$this->eventMessages[] = ERROR_DATA_404;
return false;
} elseif (!@is_array($_data[0])) {
// make sure $_data is in array(array()) format
$this->logger->data(ERROR_DATA_INVALID_FORMAT);
$this->eventMessages[] = ERROR_DATA_INVALID_FORMAT;
return false;
}
if (empty($this->metaPayload) or !is_array($this->metaPayload)) {
$this->logger->error(ERROR_DATA_META_404);
$this->eventMessages[] = ERROR_DATA_META_404;
return false;
}
// process the $_data payload
// verify that the elements are valid keys...
for ($index = 0, $max = count($_data); $index < $max; $index++) {
foreach ($_data[$index] as $key => &$value) {
if (is_null($value)) {
unset($_data[$index][$key]);
}
try {
// determine if this is a sub-collection field for mongo schema
if ($this->schema == TEMPLATE_DB_MONGO and is_array($value)) {
if (!empty($this->subCollections) and (array_key_exists($key, $this->subCollections) or (array_key_exists($key . $this->ext, $this->subCollections)))) {
$this->validateSubCollectionColumnNames($key, $value);
}
}
// cache-mapping happens in setData()...
$this->setData($key, $value, $index);
} catch (Throwable $t) {
$msg = ERROR_THROWABLE_EXCEPTION . COLON . $t->getMessage();
$this->eventMessages[] = $msg;
if ($loggerAvailable)
$this->logger->error($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
$this->state = STATE_FRAMEWORK_WARNING;
return false;
}
}
}
if (empty($this->data)) {
$msg = basename(__METHOD__) . AT . __LINE__ . COLON . ERROR_DATA_PROCESSING;
if ($loggerAvailable)
$this->logger->error($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
$this->eventMessages[] = $msg;
$this->state = STATE_DATA_ERROR;
return false;
} else {
$this->metaPayload[META_SESSION_DATE] = time();
$this->metaPayload[META_SESSION_EVENT] = $_event;
$this->state = STATE_SUCCESS;
$this->status = true;
}
return true;
}
/**
* newRecordDataInjections() -- protected method
*
* This method was moved from the mongoDB instantiation class into the core as it's generic and can/should be used
* by any schema.
*
* The method takes no input parameters as it's required/assumed that the $data member has already been
* pre-populated with validated (cacheMapped/typed) records. If the $data member is empty, the class state/status
* members will be set accordingly and the method will return a false to the client.
*
* The method spins through all of the records currently stored in $data and ensures that none of the records
* already have a GUID record in the payload -- if they do, then processing stops immediately and control is
* returned with an error flag to the calling client. The logic being that records with GUIDs already present
* are records that should be updated, not created -- therefore, Namaste will reject the entire request.
*
* Data that is injected into each record in this method is:
*
* event GUID as pulled from the meta payload
* a record GUID
* the created date
* the default status
*
* Additionally, if the current class is a mongo class, and there is a sub-collection defined for the class, and
* if the sub-collection exists as a series of records, then for each sub-collection record, insert a GUID.
*
*
* The method will also return a false result if there's no data to be processed.
*
* Warnings will also be generated, but processing will be allowed to continue, if the meta-data payload is missing
* the EVENT guid (this is a framework error as this guid is supposed to be created and inserted into the meta
* payload by the receiving event broker), or if the data template is missing the event guid from the declared
* field list.
*
* The last task prior to returning, on a successful process, is to set the record count and the state/status
* member variables.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return bool
*
*
* HISTORY:
* ========
* 10-20-17 mks CORE-585: original coding
* 03-24-18 mks CORE-852: support for mysql-destination during migration
* 05-04-18 mks _INF-188: support for creating wh records
*
*/
protected function newRecordDataInjections(): bool
{
// todo - put some intelligence here to save un-saved records and update already-saved records instead of rejecting
// make sure all records are new "to be created" records
if (!empty($this->data) and is_array($this->data) and !$this->isWHRequest) {
foreach ($this->data as &$record) {
if (array_key_exists((DB_TOKEN . $this->ext), $record) and !$this->isMigration) {
// cannot create a new record if there already exists an ID field
$this->state = STATE_ALREADY_EXISTS;
$this->eventMessages[] = ERROR_DATA_CREATE_PRE_EXISTS . $record[(DB_TOKEN . $this->ext)];
return false;
}
// if the date-created field is a class member and is not set, then add it
if (in_array((DB_CREATED . $this->ext), $this->fieldList) and (!isset($record[(DB_CREATED . $this->ext)]) or (empty($record[(DB_CREATED . $this->ext)])))) {
switch ($this->schema) {
case TEMPLATE_DB_MONGO :
$record[(DB_CREATED . $this->ext)] = time();
break;
case TEMPLATE_DB_PDO :
$record[(DB_CREATED . $this->ext)] = date("Y-m-d H:i:s");
break;
default :
$msg = sprintf(ERROR_SCHEMA_NOT_SUPPORTED, $this->schema) . COLON . __METHOD__;
$this->eventMessages[] = $msg;
if (isset($this->logger) and $this->logger->available) $this->logger->info($msg);
$this->state = STATE_SCHEMA_ERROR;
$this->status = false;
return false;
break;
}
}
// generate the db-token GUID...
if ($this->useToken and (!isset($record[(DB_TOKEN . $this->ext)]) or empty($record[(DB_TOKEN . $this->ext)]))) {
// todo: check for duplicate token -- make this a unique function so $data isn't over-written
$record[(DB_TOKEN . $this->ext)] = guid();
}
// set the status to the default for the class, if status is part of the field list:
if (in_array((DB_STATUS . $this->ext), $this->fieldList) and empty($record[(DB_STATUS . $this->ext)])) {
$record[(DB_STATUS . $this->ext)] = (!empty($this->defaultStatus)) ? $this->defaultStatus : STATUS_ACTIVE;
}
// CORE-556: check if sub-collections are supported, and if data exists, add a GUID value to
// every sub-collection record if one does not already exist
if ($this->schema == TEMPLATE_DB_MONGO and !empty($this->subCollections)) {
foreach ($this->subCollections as $key => $value) {
if (array_key_exists($key, $record) and is_array($record[$key])) {
foreach ($record[$key] as &$subRecord) {
if (!array_key_exists((DB_TOKEN . $this->ext), $subRecord)) {
$subRecord[(DB_TOKEN . $this->ext)] = guid();
}
}
}
}
}
// CORE-529: add the event guid to the record data
if (in_array((DB_EVENT_GUID . $this->ext), $this->fieldList) and !isset($record[(DB_EVENT_GUID . $this->ext)])) {
if (!isset($this->eventGUID) or empty($this->eventGUID)) {
if (!isset($this->metaPayload[META_EVENT_GUID])) {
$msg = ERROR_DATA_META_EG_404;
$this->eventMessages[] = $msg;
$this->logger->error($msg);
} else {
$record[(DB_EVENT_GUID . $this->ext)] = $this->metaPayload[META_EVENT_GUID];
}
} else {
$record[(DB_EVENT_GUID . $this->ext)] = $this->eventGUID;
}
} elseif (!in_array((DB_EVENT_GUID . $this->ext), $this->fieldList)) {
// generate a warning and an eventMessage if the template is missing the event guid declaration
$msg = ERROR_TEMPLATE_EG_DECL_404 . $this->class;
$this->eventMessages[] = $msg;
$this->logger->error($msg);
}
}
} elseif (!empty($this->data) and is_array($this->data) and $this->isWHRequest) {
// this is a data warehouse request -- all data fields should already be present so that all that's
// required to do with the payload is inject the WH_TOKEN...
foreach ($this->data as &$record) {
switch ($this->schema) {
case TEMPLATE_DB_MONGO :
$record[(DB_WH_CREATED . $this->ext)] = time();
break;
case TEMPLATE_DB_PDO :
$record[(DB_WH_CREATED . $this->ext)] = date("Y-m-d H:i:s");
break;
default :
$msg = sprintf(ERROR_SCHEMA_NOT_SUPPORTED, $this->schema) . COLON . __METHOD__ . COLON_NS . __LINE__;
$this->eventMessages[] = $msg;
if (isset($this->logger) and $this->logger->available) $this->logger->error($msg);
$this->state = STATE_SCHEMA_ERROR;
$this->status = false;
return false;
break;
}
$record[(DB_WH_EVENT_GUID . $this->ext)] = $this->metaPayload[META_EVENT_GUID];
$record[(DB_WH_TOKEN . $this->ext)] = guid();
}
} else {
$this->eventMessages[] = ERROR_DATA_MISSING_ARRAY;
if ($this->debug) $this->logger->debug(sprintf(STUB_LOC, basename(__FILE__), __METHOD__, __LINE__) . COLON . ERROR_DATA_MISSING_ARRAY);
$this->eventMessages[] = sprintf(STUB_LOC, basename(__FILE__), __METHOD__, __LINE__) . COLON . ERROR_DATA_MISSING_ARRAY;
$this->state = STATE_DATA_ERROR;
$this->status = false;
return false;
}
$this->count = count($this->data);
$this->state = STATE_SUCCESS;
$this->status = true;
return true;
}
/**
* cmFieldValidation() -- protected class method
*
* This method is used to qualify a field value, represented by the input parameter, a string, as a valid member
* of the current table/collection.
*
* We do this by first looking at if the class uses the cache-map and, if so, then we expect (demand) that the
* passed $_field be located in the current cacheMap -- and we return the referencing key as a result.
*
* If the $_field is in the exposed field list, or if the field is in the current fieldList, then return the
* field as valid.
*
* Otherwise, in all other cases, return a null value to the calling client so as to flag $_field as a non-
* member of the current class.
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_field
* @return null|string
*
* HISTORY:
* ========
* 10-09-17 mks CORE-584: original coding
* 05-07-19 mks DB-116: suppressing PHP Warnings
*
*/
protected function cmFieldValidation(string $_field): ?string
{
if ($this->useCache and is_array($this->cacheMap) and !in_array($_field, $this->cacheMap)) {
// we're using cache-map but the array key is not in the cache map
$msg = sprintf(ERROR_CACHE_KEY_404, $_field, $this->class);
$this->eventMessages[] = $msg;
if ($this->debug) $this->logger->debug($msg);
return null;
} elseif ($this->useCache and is_array($this->cacheMap) and in_array($_field, $this->cacheMap)) {
return (array_search($_field, $this->cacheMap));
} elseif (!$this->useCache and in_array($_field, $this->cacheMap)) {
return (is_array($this->cacheMap) and array_search($_field, $this->cacheMap));
} elseif ((is_array($this->exposedFields) and in_array($_field, $this->exposedFields)) or in_array($_field, $this->fieldList)) {
return $_field;
} elseif ((is_array($this->exposedFields) and in_array(($_field . $this->ext), $this->exposedFields)) or in_array(($_field . $this->ext), $this->fieldList)) {
return $_field . $this->ext;
} else {
$msg = ERROR_KEY_404 . $_field;
$this->eventMessages[] = $msg;
if ($this->debug) $this->logger->debug($msg);
return null;
}
}
/**
* validateSubCollectionColumnNames() - protected method
*
* this method is a recursive method which validates the column names in a mongo sub-collection.
*
* todo: evaluate ddb subC potential and, if null, move this up to mongo instantiation class
*
* the method accepts two input parameters - one is the current key (sub-collection name) and the other is
* the indexed-array referenced by the key.
*
* while we're spinning through the indexed array (_value), if the type of value is an array and if the array
* key is a sub-collection, then we have found a nested sub-collection and the function will invoke itself
* by passing-in the new key and value sub-array.
*
* if a key is not in the sub-collection list, then remove it from the input data and record the removal in
* the system log and in the class diagnostics.
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param $_key
* @param $_value
*
* HISTORY:
* ========
* 07-13-17 mks CORE-464: original coding
*
*/
protected function validateSubCollectionColumnNames($_key, &$_value)
{
// spin through the sub-collection array and toss invalid fields
if (array_key_exists($_key, $this->subCollections)) {
if (is_array($_value)) {
foreach ($_value as $record) {
foreach ($record as $key => $value) {
// todo - fix this code and use for a recursive call for nested sub-collections...
// if (is_array($value) and array_key_exists($k, $this->subCollections)) {
// $this->validateSubCollectionColumnNames($k, $v);
if (!in_array($key, $this->subCollections[$_key])) {
$msg = ERROR_SUB_COLLECTION_NOT_MEMBER . $key;
$this->logger->data($msg);
$this->eventMessages[] = $msg;
unset($_value[$key]);
}
}
}
} else {
$this->logger->data(ERROR_SUB_COLLECTION_404);
$this->eventMessages[] = ERROR_SUB_COLLECTION_404;
}
}
}
/**
* softDeleteStatusInjection() -- protected method
*
* This method is called by the _fetchRecord() method in the instantiation classes. The method requires one
* parameter:
*
* $_query -- this is the STRING_QUERY_DATA array embedded in the query payload submitted to the broker.
*
* If the current class supports soft-deletes, and if the query did not include a filter for the record status,
* (e.g.: do not return deleted records) then we're going to inject that filter into the existing query.
*
* The method returns two parameters within an array -- the position of each parameter is relevant as the calling
* client should make the following assignment/invocation:
*
* list($query, $injection) = $this->hardDeleteStatusInjection($query);
*
* The reason why we inject the status filter is that classes for records that support soft-deletes should not
* be able to access records that were deleted either via the database or from a cache-fetch.
*
* NOTE:
* -----
* Although you will be tempted to, do not remove the "= null" from the $_query parameter in the function
* declaration -- it's permissible for this parameter to either be an array or null (to allow for blanket
* 'select *' type queries) -- allowing for the parameter to default to null works.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param array|null $_query
* @return array
*
* HISTORY:
* ========
* 09-26-17 mks CORE-572: original coding (moved from gacMongo::_fetchRecords() and modified)
* 10-11-17 mks CORE-584: updated default for $_query argument so that null queries won't cause crash
* 05-03-18 mks _INF-188: compensated for edge case where there may not be a cache map in a class that
* supports soft-deletes
* 04-01-20 mks PD-21: refactored processing algorithm - if hard-deletes for the current class are not
* enabled, then process the query looking for the presence of a STATUS identifier
* 11-05-20 mks DB-171: changed conditional statement processing order to ensure test for query being
* null is the first evaluation tested
*
*/
protected function softDeleteStatusInjection(array $_query = null): array
{
// if the current class supports soft-deletes ($this->useDeletes == false)...
if (!$this->useDeletes) {
// if there's a status field present in the non-null query, then we can return without processing
if (is_null($_query)) {
// query is empty so inject a status field...
$_query[(DB_STATUS . $this->ext)] = [OPERAND_NULL => [OPERATOR_DNE => [STATUS_DELETED]]];
return [$_query, true];
} elseif (array_key_exists(DB_STATUS . $this->ext, $_query) or array_key_exists(DB_STATUS, $_query) or
array_key_exists(CM_TST_FIELD_TEST_STATUS . $this->ext, $_query) or array_key_exists(CM_TST_FIELD_TEST_STATUS, $_query)) {
return [$_query, false];
} else {
// query is not empty so, if there's only one "query" in the query array, inject the AND operand
// to bind the two fields in the query
if (!array_key_exists(MONGO_AND, $_query)) {
$_query[(DB_STATUS . $this->ext)] = [OPERAND_NULL => [OPERATOR_DNE => [STATUS_DELETED]]];
$_query[OPERAND_AND] = null;
return [$_query, true];
} else {
// spin though the $_query to detect the AND operand and inject the status field before the row
// where the "AND" appears
$newQuery = [];
for($index = 0, $max = count($_query); $index < $max; $index++) {
if (key($_query[$index]) == OPERAND_AND) {
$newQuery[DB_STATUS . $this->ext] = [OPERAND_NULL => [OPERATOR_DNE => [STATUS_DELETED]]];
}
$newQuery[] = $_query[$index];
}
$_query = $newQuery;
}
return [$_query, true];
}
}
return[$_query, false];
}
/**
* doLogQueryTimer() -- public method
*
* this core method is responsible for logging a query timer to the database.
*
* a few checks have to be satisfied before a log message is generated and send to the error service:
*
* 1. the current class is using timers
* 2. the current db service isn't using the remote logger
* 3. the appserver user-timers setting is on
*
* if all of the above requirements are met, then we'll generate a timer event and send it to the error service,
* specifying that this is a metric event (via the last parameter to the error service method).
*
* The exception to the above is if the framework has a value configured for queryThreshold and if the query
* exceeded the threshold, then we'll log the slow-query event.
*
* If the current timer value exceeds the threshold (set in the configuration file) for slow queries, then
* adjust the header output to show this is a query timer alert
*
* there are two parameters as input to the method:
*
* $_query -- the query to be logged
* $_totalTime -- the amount of time to execute the query
*
* method returns no value nor does it update the class' state/status member variables
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param $_query
* @param $_totalTime
*
* HISTORY:
* ========
* 06-30-17 mks original coding
*
*/
public function doLogQueryTimer(string $_query, float $_totalTime)
{
// $sysEvents = true;
// $sysEvents = false; // todo delete me once we have systemEvent support
$data = null;
$stackBT = null;
// $objSystemEvent = new gacSystemEvents('', $this->metaPayload);
// if (!$objSystemEvent->status) {
// $this->logger->warn(ERROR_SCHEMA_INSTANTIATION . TEMPLATE_CLASS_SYS_EV);
// $sysEvents = false;
// }
if ($this->useTimers) {
if (($_totalTime * NUMBER_MS_PER_SEC) >= $this->queryThreshold) {
// slow query event
$timerHeading = '*** SLOW QUERY ALERT ***';
// if ($sysEvents) {
// $data[MONGO_SYSTEM_EVENT_SEV] = EVENT_SEV_HIGH;
// $data[MONGO_SYSTEM_EVENT_TYPE] = EVENT_TYPE_TIMER;
// $data[MONGO_SYSTEM_EVENT_NAME] = EVENT_NAME_TIMER_SLOW_QUERY;
// $data[MONGO_SYSTEM_EVENT_KEY] = STRING_DATA;
// $data[MONGO_SYSTEM_EVENT_QUERY] = $_query;
// $data[MONGO_SYSTEM_EVENT_QUERY_TIMER] = floatval($_totalTime);
// $data[MONGO_SYSTEM_EVENT_VALUE] = [
// STRING_QUERY => $_query,
// STRING_TOTAL_TIME => $_totalTime
// ];
// $data[MONGO_LOG_CLASS] = $this->class;
// if (isset($this->eventGUID)) $data[MONGO_SYSTEM_EVENT_EVENT_GUID] = $this->eventGUID;
// }
} elseif (($_totalTime * NUMBER_MS_PER_SEC) > $this->queryWarning) {
// slow query warning
$timerHeading = '~~~ SLOW QUERY WARNING ~~~';
// if ($sysEvents) {
// $data[MONGO_SYSTEM_EVENT_SEV] = EVENT_SEV_LOW;
// $data[MONGO_SYSTEM_EVENT_TYPE] = EVENT_TYPE_TIMER;
// $data[MONGO_SYSTEM_EVENT_NAME] = EVENT_NAME_TIMER_QUERY_WARN;
// $data[MONGO_SYSTEM_EVENT_KEY] = STRING_DATA;
// $data[MONGO_SYSTEM_EVENT_QUERY] = $_query;
// $data[MONGO_SYSTEM_EVENT_QUERY_TIMER] = floatval($_totalTime);
// $data[MONGO_SYSTEM_EVENT_VALUE] = [
// STRING_QUERY => $_query,
// STRING_TOTAL_TIME => $_totalTime
// ];
// $data[MONGO_LOG_CLASS] = $this->class;
// if (isset($this->eventGUID)) $data[MONGO_SYSTEM_EVENT_EVENT_GUID] = $this->eventGUID;
// }
} else {
// regular query logging
$timerHeading = 'QUERY TIMER';
// if ($sysEvents) {
// $data[MONGO_SYSTEM_EVENT_SEV] = EVENT_SEV_LOW;
// $data[MONGO_SYSTEM_EVENT_TYPE] = EVENT_TYPE_TIMER;
// $data[MONGO_SYSTEM_EVENT_NAME] = EVENT_NAME_TIMER_QUERY;
// $data[MONGO_SYSTEM_EVENT_KEY] = STRING_DATA;
// $data[MONGO_SYSTEM_EVENT_QUERY] = $_query;
// $data[MONGO_SYSTEM_EVENT_QUERY_TIMER] = floatval($_totalTime);
// $data[MONGO_SYSTEM_EVENT_VALUE] = [
// STRING_QUERY => $_query,
// STRING_TOTAL_TIME => $_totalTime
// ];
// $data[MONGO_LOG_CLASS] = $this->class;
// if (isset($this->eventGUID)) $data[MONGO_SYSTEM_EVENT_EVENT_GUID] = $this->eventGUID;
// }
}
$logMsg = $timerHeading . HTML_LINE_BREAK;
$logMsg .= strtoupper($this->schema . STRING_QUERY_EXEC_TIME . $_totalTime . ' seconds' . HTML_LINE_BREAK);
$logMsg .= $_query . HTML_LINE_BREAK;
$this->logger->metrics($logMsg, $_totalTime, $stackBT);
if (!is_null($stackBT)) $data[STRING_DATA] = $stackBT;
// if ($sysEvents and !is_null($data)) {
// $data[MONGO_SYSTEM_EVENT_DESC] = $logMsg;
// $objSystemEvent->saveEventArray([$data]);
// }
}
}
/**
* addMeta() -- public method
*
* This method was written as a means of being able to add to the meta-data for a class, post-factory instantiation.
* Since $metaPayload is a protected class, this public-facing method allows us to safely add meta-data elements
* to the meta-data payload of the current class object.
*
* This method is normally used only in testing and in unit-testing.
*
* The method requires two input parameters, the key-value pairs for the new meta-data property. The key is
* validated for it's name and the value is validated for the type assigned to the key. If both pass validation,
* then we'll add the new field to the protected property and return a boolean-true.
*
* Otherwise, we're going to generate an error message, log it, and and push it on to the eventMessages stack
* for the current class, and then return a boolean-false.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_key
* @param mixed $_value
* @return bool
*
* HISTORY:
* ========
* 07-25-17 mks original coding
*
*/
public function addMeta(string $_key, $_value): bool
{
$rd = false;
if (array_key_exists($_key, $this->meta->fields)) {
if (gettype($_value) == $this->meta->fields[$_key]) {
$this->metaPayload[$_key] = $_value;
$rd = true;
} else {
$msg = sprintf(ERROR_UNK_META_TYPE, gettype($_value), $_key);
$this->logger->data($msg);
$this->eventMessages[] = $msg;
}
} else {
$msg = sprintf(ERROR_DATA_META_REJECTED_FOR_CLASS, $_key, $this->class);
$this->logger->data($msg);
$this->eventMessages[] = $msg;
}
return ($rd);
}
/**
* removeMeta() -- public method
*
* This method allows us to cleanly remove an element from the class member $metaPayload since the class member
* is protected.
*
* If the element is removed from the class member, we'll return a Boolean(true) -- if not, we return a
* Boolean(false).
*
* There is one input parameter to the method:
* $_key -- the key (element) to be removed from the class member
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_key
* @return bool
*
*
* HISTORY:
* ========
* 03-22-19 mks DB-116:original coding
*
*/
public function removeMeta(string $_key): bool
{
if (array_key_exists($_key, $this->meta->fields)) {
unset($this->metaPayload[$_key]);
return true;
}
return false;
}
/**
* replaceMeta() -- public method
*
* This method requires a single input parameter - an associative array of meta data that will immediately,
* without validation or testing, overwrite the existing meta data payload already stored in the class.
*
* This method is considered to be a system-level method and should not be called casually.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param array $_meta
*
*
* HISTORY:
* ========
* 01-09-19 mks DB-80: original coding
*
*/
public function replaceMeta(array $_meta): void
{
$this->metaPayload = $_meta;
}
/** @noinspection PhpUnused */
/**
* addMetaTemplate() -- public function
*
* when I switched Namaste to the gacFactory class build model, I needed a way to save the meta template as there
* are times when the framework needs to inject new fields into the meta data stored in $this->metaPayload.
*
* Saving the meta template class to the local instantiation provides the framework with the authoritative data
* to answer these types of questions.
*
* The input parameter, which is required, is the meta-data template object instantiated in the parent factory
* class -- this method was written b/c the $meta member is protected and cannot be accessed by the factory class.
*
* tl;dr:
*
* $this->meta --- holds the meta template class definitions
* $this->metaPayload --- holds the meta data payload received in the broker request
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param gacMeta $_metaTemplate
*
* HISTORY:
* ========
* 05-04-18 mks _INF-188: original coding
* 12-13-18 mks DB-71: adjusting the properties of template values based on meta members
* 02-04-18 mks DB-107: added audit-client to disable-audit check
* 05-21-19 mks DB-116: fixed PHP Notices
*
*/
public function addMetaTemplate(gacMeta $_metaTemplate): void
{
if (isset($this->metaPayload[META_DONUT_FILTER])) {
$this->eventMessages[] = sprintf(INFO_META_OVERRIDE, STRING_CACHE) . $this->class;
$this->useCache = false;
}
if (isset($this->metaPayload[META_SKIP_AUDIT]) or (isset($this->metaPayload[META_CLIENT]) and $this->metaPayload[META_CLIENT] == CLIENT_AUDIT)) {
$this->eventMessages[] = sprintf(INFO_META_OVERRIDE, STRING_AUDIT) . $this->class;
$this->useAuditing = AUDIT_NOT_ENABLED;
$this->useJournaling = false;
}
$this->meta = $_metaTemplate;
}
/**
* loadBrokerAuditData() -- public method
*
* This method should only be invoked by the AdminIN broker on an audit event. The purpose of the method
* is to load the systemEvent token (generated in the broker event) into each record passed into the audit
* data payload. The data payload is then passed into a core function to add the array into the $data member.
*
* Note that if the function is called outside of the admin environment, the request is immediate rejected with
* an error returned.
*
* There are three input parameters to the method:
*
* $_data -- this should be $request[BROKER_DATA] culled from broker event processing
* $_sysEventGUID -- this is the systemEvent record token that we'll inject into each audit record (back-tracking)
* $_es -- call-by-reference parameter that records generated errors and passes them back to the calling client
*
* The method returns a boolean to indicate if we were able to successfully build the audit $data member.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param array $_data
* @param string $_sysEventGUID
* @param array $_es
* @return bool
*
*
* HISTORY:
* ========
* 10-18-18 mks DB-72: original coding
*
*/
public function loadBrokerAuditData(array $_data, string $_sysEventGUID, array &$_es): bool
{
if (!boolval($this->isServiceLocal(ENV_ADMIN))) {
$_es[] = ERROR_ENV_INVALID . gasConfig::$settings[CONFIG_ID][CONFIG_ID_ENV];
return false;
}
if (empty($_data)) {
$_es[] = ERROR_DATA_ARRAY_EMPTY;
return false;
}
if (!validateGUID($_sysEventGUID)) {
$_es[] = ERROR_INVALID_GUID . $_sysEventGUID;
return false;
}
// inject the systemEvent GUID for back-tracking
foreach ($_data as &$record) $record[AUDIT_SYS_EV_GUID] = $_sysEventGUID;
// import the broker data into the audit widget
if (!$this->addData($_data)) {
$_es[] = sprintf(ERROR_DATA_IMPORT, EVENT_TYPE_AUDIT, $this->class);
return false;
}
return true;
}
/**
* FQCN() -- public method
*
* Fully-Qualified-Column-Name (FQCN)
*
* This method requires a single input parameter, a string, which should be one of the following values:
*
* 1. A fully-qualified field name with extension as it appears in the database
* 2. A field name, sans extension, but is still a valid column name
* 3. A cache-mapped value representing the regular column name
*
* If any of the above are true, then return a string containing the fully-qualified-column-name back to the
* calling client.
*
* If none of the above are true, a null is returned.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_fieldName
* @return string|null
*
*
* HISTORY:
* ========
* 04-07-20 mks ECI-107: original coding
*
*/
public function FQCN(string $_fieldName):?string
{
// is this fieldName ok on it's own?
if (in_array($_fieldName, $this->fieldList)) return $_fieldName;
// or is it lacking an extension?
if (in_array($_fieldName . $this->ext, $this->fieldList)) return $_fieldName . $this->ext;
// or is it in the cache map?
if (!empty($this->cacheMap) and in_array($_fieldName, $this->cacheMap))
return (false === array_search($_fieldName, $this->cacheMap)) ? null : array_search($_fieldName, $this->cacheMap);
// $_fieldName does not exist as a valid field with/without extension, or as a cache-mapped value so...
return null;
}
/**
* getMetaDataPayload() -- public method
*
* The metaPayload member is declared private for this class -- however, some Namaste system processing requires
* access to the meta-data payload while out-of-scope of the class. This method permits those functions access
* to the meta payload by returning the meta-data as an array if it's defined and assigned to the member variable.
*
* Otherwise, the function returns a null value to the calling client.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return array|null
*
*
* HISTORY:
* ========
* 02-15-18 mks _INF-139: original coding
*
*/
public function getMetaDataPayload(): ?array
{
return (isset($this->metaPayload) ? $this->metaPayload : null);
}
// non-cache-mapped data containers
public function adjustSubCFieldNames(array &$_subC):bool
{
for ($index = 0, $max = count($_subC); $index < $max; $index++) {
foreach ($_subC[$index] as $key => $value) {
if (!is_array($value)) {
if (!is_null($newKey = $this->addExtension($key))) {
$_subC[$index][$newKey] = $value;
unset($_subC[$index][$key]);
} else return false;
} elseif (!$this->adjustSubCFieldNames($value)) return false;
}
}
return true;
}
/**
* addData() -- public method
*
* This method enables the framework to process a data-ball instead of populating a data record, one field
* at a time via setData() -- which this method ends-up calling anyway...
*
* The input parameter is an array of data. Please note that this should be an array of rows - even if you are
* submitting one-row of data, it must be encapsulated within a container array.
*
* For every row in the data ball, parse each field and if that field exists in the current classes' field list,
* then add that element to the current data property.
*
* NOTE: The data field type validation occurs in setData().
*
* Method returns a boolean value indicating success or failure for the request. If no data was copied into the
* data property, then a false is returned.
*
* NOTE: THIS IS A DESTRUCTIVE OPERATION -- WHATEVER INFO STORED IN $data PRIOR TO THIS CALL WILL BE DESTROYED!
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param array $_data
* @return bool
*
* HISTORY:
* ========
* 08-16-17 mks CORE-500: original coding
* 02-21-18 mks _INF-130: added condition for fields that may already have the extension appended to the
* key...the data fields take this form as a function of data migration so should
* be allowed to proceed.
* 01-05-21 mks DB-180: Updating member $count for the data reset, better exception handling
*
*/
public function addData(array $_data): bool
{
if (!is_array($_data)) {
$this->eventMessages[] = ERROR_DATA_ARRAY_EMPTY . COLON . STRING_DATA;
return(false);
}
$this->data = []; // kaboom, it's gone
$this->count = 0;
for ($index = 0, $max = count($_data); $index < $max; $index++) {
foreach ($_data[$index] as $key => $value) {
if (in_array(($key . $this->ext), $this->fieldList) or in_array($key, $this->fieldList)) {
try {
$this->setData($key, $value, $index); // setData() handles both key formats (w|w/o ext)
} catch (TypeError | Throwable $t) {
$hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__);
@handleExceptionMessaging($hdr, $t->getMessage(), $foo, true);
$this->eventMessages[] = ERROR_TYPE_EXCEPTION;
return false;
}
} else {
$this->eventMessages[] = sprintf(ERROR_DATA_INVALID_CLASS_KEY, $key, $this->class);
}
}
}
if (empty($this->data)) return false;
$this->count = count($this->data);
return true;
}
/**
* replaceData() -- public method
*
* This method was written for the cacheKey process where we're, post-processing, replacing all the $data with
* the cacheKey payload (That was the intended purpose of this method.) because $data is a protected member.
*
* There is one input parameter to the method which must be an array -- this array will overwrite the current
* contents of $data, replacing it.
*
* The method itself returns void.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param array $_data -- array of content to replace what ever is currently stored in the $data
*
*
* HISTORY:
* ========
* 04-16-19 mks DB-116: original coding
*
*/
public function replaceData(array $_data): void
{
$this->data = $_data;
}
/**
* removeData() -- public method
*
* This method clears out the existing $data property and resents dependent fields. The method has no input
* parameters and returns void.
*
* The method is provided to provide an approved and painless way to reset the class $data.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* HISTORY:
* ========
* 08-21-17 mks CORE-500: original coding
*
*/
public function removeData(): void
{
$this->data = [];
$this->count = 0;
$this->strQuery = '';
$this->queryResults = [];
}
/**
* setRecordLimit() -- public method
*
* This method should only be called during data migration. Data migration, as of this writing, is the only
* valid process which should be able to override the record-limit constraints set in the XML configuration.
*
* This method was necessary because $recordLimit is a protected variable and this method acts as a gateway to
* provide access to the variable outside of the protected-class stack.
*
* The method requires a single input parameter which must be of type integer -- calling clients should exception-
* wrap calls to this method to be safe.
*
* The method returns a Boolean indicating if the record limit was over-written or not. The record limit will
* only be over-written if the value of the input parameter ($_limit) is greater than the zero allowing us to test
* migrations with ridiculously-small payloads or, during real-runs, bump the limit up to processing thousands
* of records with each request.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param int $_limit
* @return bool
*
*
* HISTORY:
* ========
* 01-31-18 mks _INF-139: original coding
*
*/
public function setRecordLimit(int $_limit): bool
{
if ($_limit > 0) {
$this->recordLimit = $_limit;
return true;
}
return false;
// $this->recordLimit = ($_limit != $this->recordLimit) ? $_limit : $this->recordLimit;
// return ($this->recordLimit === $_limit);
}
/** @noinspection PhpUnused */
/**
* getRecordLimit() -- public function
*
* Since $recordLimit member is private, we need a subroutine to expose it. If the member is not set, then set
* it to the configured default.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return int
*
*
* HISTORY:
* ========
* 05-24-18 mks _INF-188: original coding
*
*/
public function getRecordLimit(): int
{
if (!isset($this->recordLimit))
$this->recordLimit = gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_QUERY_RECORD_LIMIT];
return ($this->recordLimit);
}
/**
* returnFilteredData() -- protected method
*
* This method requires that the existing property: $data already have a data-set assigned. We'll validate the
* $data member as an array and that it contains at least one record.
*
* Otherwise there are no input parameters for the method.
*
* The method returns a boolean value which indicates the success of the operation:
*
* We're going to first evaluate whether or not we're going to use cache for the return payload. If we do, then
* the process becomes one of caching the records and returning a cache-key. If one record is processed, we'll
* return a cacheKey using the token for that record. If there is more than one record to be cached, then
* we'll cache all the records under a random guid and return that as the cache key.
*
* Cache settings can be overridden in the meta payload variable META_DO_CACHE... if the value is set to 1, then
* the data will be cached regardless or the class setting. If the value is set to 0, then the data will NOT be
* cached, regardless of the class setting.
*
* If we're not using cache, then we're going to check to see if the class has exposedFields defined and, if so,
* we'll filter the return data set through via this list.
*
* If the caching has been overridden, but the class has caching enabled, we're still going to cacheMap the data.
* That feature of schema obfuscation cannot be bypassed.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return bool
*
* HISTORY:
* ========
* 07-17-17 mks CORE-464: initial coding (moved out of mongodb instantiation class)
* 08-14-17 mks CORE-493: removed support for getData() meta parameter
* 08-18-17 mks CORE-500: fixed NOTICE violation on accessing $this->metaPayload[META_DO_CACHE]
* 10-12-17 mks CORE-584: allowing bypass of cache with META_DO_CACHE = 0 and group-caching $data if
* more than one record exists in the current data payload...function now returns
* a boolean value to indicate success or fail in processing.
* 05-03-18 mks _INF-188: fixed bug where we were assuming data always has the class extension appended
* 11-20-18 mks DB-63: override data "cleaning" on getData() for non-namaste (admin) classes
* 02-15-19 mks DB-116: Refactored: removed cache-management code which is replaced by cache-processing
* at the broker-services level. Eliminating a level of cursive processing in-favor
* of an array function to improve performance.
*
*/
protected function returnFilteredData(): bool
{
$res = false;
if (empty($this->data)) {
$this->eventMessages[] = ERROR_DATA_404;
if ($this->debug) $this->logger->debug(ERROR_DATA_404);
return $res;
} elseif (!is_array($this->data)) {
$msg = ERROR_DATA_ARRAY_NOT_ARRAY . STRING_DATA;
$this->eventMessages[] = $msg;
if ($this->debug) $this->logger->debug($msg);
return $res;
}
// clean the data -- but only if this is a namaste class template - and filter all schemas against exposed
// fields while generating the tokenList which is now required for all CRUD operations.
// Also establishes the class record $count and $recordsReturned member values.
try {
$counter = 0;
$diff = [];
$rd = $this->getData();
if (!empty($this->exposedFields)) {
// if we're limiting the fields, then filter them here:
foreach ($rd as &$record) {
$diff[] = array_diff_key($record, $this->exposedFields);
if (in_array(DB_TOKEN, $record))
$this->tokenList[] = $record[DB_TOKEN];
elseif (in_array((DB_TOKEN . $this->ext), $record))
$this->tokenList[] = $record[(DB_TOKEN . $this->ext)];
else
$counter++;
}
if ($counter) {
$msg = sprintf(ERROR_GRID_MISSING_TOKENS, count($rd), $counter);
$this->eventMessages[] = $msg;
$this->logger->data($msg);
}
// if we ended up here with NO records in our diff array, do the error-processing thing
if (!empty($diff)) {
$this->data = $diff;
unset($diff);
$res = true;
} else {
$msg = sprintf(ERROR_DATA_ARRAY_FAIL, STRING_DIFFERENCE);
$this->eventMessages[] = $msg;
$this->logger->data($msg);
$this->data = [];
$res = false;
}
} else {
$this->data = (count($rd)) ? $rd : null;
$res = (bool) count($this->data);
}
unset($rd);
// data has been cleaned...
} catch (Throwable $t) {
$msg = ERROR_THROWABLE_EXCEPTION . COLON . $t->getMessage();
$this->eventMessages[] = $msg;
if (isset($this->logger) and $this->logger->available)
$this->logger->error($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
$res = false;
}
$this->count = count($this->data);
$this->recordsReturned = $this->count;
return $res;
}
/**
* getBWResults() -- public method
*
* because the BW (bulk-result) class member is protected, we need a gateway method what will return a copy
* of the write-results to the calling client. This should only be called during the data migration event.
*
* if the member is not set, then a null will be returned instead of the mongoDB WriteResult object.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return WriteResult|null
*
* HISTORY:
* ========
* 02-05-18 mks _INF-139: original coding
*
*/
public function getBWResults(): ?MongoDB\Driver\WriteResult
{
return (isset($this->bwResult)) ? $this->bwResult : null;
}
/**
* insertField() -- public gateway method
*
* This is a gateway method that allows other classes to invoke setData() -- which is a protected function and
* causes the spontaneous birth of all kinds of kittens when we try to call it directly during migration from
* a gacFactory class widget.
*
* The method takes three parameters which just magically happen to coincide with the setData() params.
*
* The method returns a Boolean value which is passed back from the setData() method.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_field
* @param $_value
* @param int $_row
* @param array $_errs
* @param string $_res
* @return bool
*
*
* HISTORY:
* ========
* 02-06-18 mks _INF-139: original coding
*
*/
public function insertField(string $_field, $_value, int $_row = 0, array &$_errs = null, string $_res = 'MGRA: '): bool
{
try {
$this->setData($_field, $_value, $_row);
} catch (TypeError $t) {
$msg = ERROR_TYPE_EXCEPTION . COLON . $t->getMessage();
$this->eventMessages[] = $msg;
if (isset($this->logger) and $this->logger->available)
$this->logger->error($msg);
else
consoleLog($this->res, CON_ERROR, $msg);
return false;
}
if (!$this->status) {
$msg = sprintf(ERROR_DATA_ADD_FAIL, MWH_NUM_RECS_MOVED, $this->class);
if (isset($this->logger) and $this->logger->available)
$this->logger->error($msg);
else
consoleLog($_res, CON_ERROR, $msg);
$_errs[] = $msg;
return false;
}
return true;
}
/**
* launchAudit() -- public method
*
* This method was originally embedded in the adminBrokerIn code, in the BROKER_REQUEST_ADMIN_AUDIT_CREATE event.
* It was moved to the core so as to reduce the overall code-footprint (and memory consumption) of the broker.
*
* This method requires the following parameters:
*
* $_req -- this is a copy of the broker request, ($request, not $_request) built during event pre-check
* $_hj -- boolean value for $haveJournal initialized in the broker
* $_seg -- string containing the system-event guid that was generated in the broker code
* $_jd -- array containing the journal data payload that was originally sent to the broker event
*
* The function returns a boolean to indicate if processing was successful or not. Any error messages raised
* will be copy to the class's eventMessages[] container and will be echo'd to the logger.
*
* ALGORITHM:
* ----------
* First, load the broker audit data which does some of the payload validation and is also responsible for
* injecting the system-event guid into the audit payload. If this request fails, processing ends.
*
* Next, we're going to call the function to create (save) the audit record and, if successful, pull the audit
* token assigned to the newly-created record.
*
* Next, we'll instantiate a new journal record (if journaling is enabled for this data class) and we'll call
* a method to create the journal data payload and save the journal record.
*
* Next, we save the journal record which has been linked back to the audit record.
*
* If any errors are raised, or if any methods fail, during the above processing, we generate appropriate error
* messages and output them to both the eventMessages[] container and to the error log.
*
* The entire method, except for lvar initialization, is wrapped in an exception handler.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param array $_req -- copy of the broker event $request array
* @param bool $_hj -- does this class have journaling enabled?
* @param string $_seg -- copy of the current system-event guid for the audit event
* @param array $_jd -- copy of the journal-data published as part of the broker event data
* @return bool
*
*
* HISTORY:
* ========
* 02-11-19 mks DB-100: moved from adminBrokerIn and refactored for local ($this->) processing
*
*/
public function launchAudit(array $_req, bool $_hj, string $_seg, array $_jd): bool
{
$status = false;
$errorList = [];
try {
if (!$this->loadBrokerAuditData($_req[BROKER_DATA], $_seg, $errorList)) {
if (!empty($errorList)) {
foreach ($errorList as $error)
$this->logger->error($error);
if (empty($this->eventMessages))
$this->eventMessages = $errorList;
else
$this->eventMessages = array_merge($this->eventMessages, $errorList);
}
$msg = sprintf(ERROR_DATA_IMPORT, SYSTEM_EVENT_DATA, $this->class);
$this->eventMessages[] = $msg;
$this->logger->error($msg);
} else {
// we've successfully created the audit object...save the audit data
$this->_createRecord([], DATA_AUDT);
if ($this->status) {
$auditToken = $this->getColumn(DB_TOKEN);
// DB-74: check to see if there is journaling data -- add new data and save
if ($_hj) {
$_req[BROKER_META_DATA][META_TEMPLATE] = TEMPLATE_CLASS_JOURNAL;
$objJournal = new gacFactory($_req[BROKER_META_DATA], FACTORY_EVENT_NEW_CLASS, '', $errorList);
if ($objJournal->status) {
$jData = $objJournal->widget->template->buildJournalData($_seg, $auditToken, $_jd, $errorList);
if (is_null($jData)) {
$this->eventMessages[] = ERROR_JOURNAL_BUILD_FAIL;
$this->logger->error(ERROR_JOURNAL_BUILD_FAIL);
} else {
$objJournal->widget->_createRecord($jData, DATA_AUDT);
if ($objJournal->widget->status) {
$status = true;
} else {
if (!empty($errorList)) {
if (empty($this->eventMessages))
$this->eventMessages = $errorList;
else
$this->eventMessages = array_merge($this->eventMessages, $errorList);
} elseif (!empty($objJournal->widget->eventMessages)) {
if (empty($this->eventMessages))
$this->eventMessages = $errorList;
else
$this->eventMessages = array_merge($this->eventMessages, $errorList);
} else {
$msg = ERROR_TEMPLATE_INSTANTIATE . TEMPLATE_CLASS_JOURNAL;
$this->eventMessages[] = $msg;
$this->logger->error($msg);
}
$this->logger->error(ERROR_JOURNAL_GENERIC_FAIL);
$this->eventMessages[] = ERROR_JOURNAL_GENERIC_FAIL;
}
}
} else {
if (!empty($errorList))
if (empty($this->eventMessages))
$this->eventMessages = $errorList;
else
$this->eventMessages = array_merge($this->eventMessages, $errorList);
if (!empty($objJournal->eventMessages))
if (empty($this->eventMessages))
$this->eventMessages = $errorList;
else
$this->eventMessages = array_merge($this->eventMessages, $errorList);
$msg = ERROR_TEMPLATE_INSTANTIATE . TEMPLATE_CLASS_JOURNAL;
$this->eventMessages[] = $msg;
$this->logger->error($msg);
}
if (is_object($objJournal)) $objJournal->__destruct();
unset($objJournal);
} else {
// successful audit event without a corresponding journaling event
$status = true;
}
} else {
if (!empty($errorList)) {
if (empty($this->eventMessages))
$this->eventMessages = $errorList;
else
$this->eventMessages = array_merge($this->eventMessages, $errorList);
}
$msg = ERROR_AUDIT_CREATE;
$this->eventMessages[] = $msg;
$this->logger->error($msg);
}
}
} catch (TypeError $t) {
$msg = ERROR_TYPE_EXCEPTION . COLON . $t->getMessage();
$errorList[] = $msg;
$this->logger->error($msg);
}
return $status;
}
/**
* restoreAuditRecord() -- public method
*
* This method requires a single input parameter to the method and that's and empty array value into which we'll
* populate copies of the before and after records for an implicit return back to the calling client.
*
* The method returns either a null or a boolean and the result should be parsed by the calling client. Only if
* the restoration was completely successful will we return a Boolean(true) value. In all cases of error, we'll
* post an appropriate message into the eventMessages member.
*
* The method also requires that the class be pre-loaded with the audit record as we're going to pull query
* discriminant value from the $data member.
*
* First, we confirm that this method is exec'ing only on the admin service. If not, return immediately.
*
* Next, we're going to submit a request to namaste readBroker to pull a copy of the original record - we've also
* set the skip-audit flag during lvar initialization so that no further audit records are created off this
* recovery event. We also set the DONUT filter so that we disable caching and return the literal record. This
* record will be returned implicitly in the call-by-reference parameter under the key STRING_ORIGINAL.
*
* If we're able to pull the original record from the audit data, we next fetch the journal record based on
* audit guid (which is legit b/c audit <--1:1--> journal) and, if not found -- we return with a message indicating
* that the namaste record class does not support journaling... otherwise, grab the restore query from the journal
* record.
*
* Instantiate a write-broker client back to namaste and submit the journal-stored query to the write broker.
* Evaluate the return payload and evaluate. If there was an error returned, mirror the error messages in to the
* current error container and return false.
*
* If the journal query executed successfully, submit a new remote fetch request for the record. There will be one
* of three outcomes from the remote fetch:
* 1. there was an error executing the request (merger error messages and return false)
* 2. the updated record was not found (when we remove records) -- populate the $_data container and return true
* 3. the updated record was fetched -- populate the $_data container and return true
*
*
* @author mike@givingassistant.org
* @version 1.0
*
*
* @param array $_data
* @return bool
*
*
* HISTORY:
* ========
* 12-13-18 mks DB-71: original coding completed and tested
* 02-04-18 mks DB-107: fixed sub-collection initialization ternary comparison that assumed we'd always
* have user data in the meta payload.
* fixed typo in comparison statement (a[foo == bar]) => (a[foo] == bar)
* 02-05-19 mks DB-107: detecting schema and loading table name from audit record - if remote fetch of
* data record is PDO, we want to NOT use the default template ... instead, set the
* view template to the audit-template which SHOULD be defined for the class.
* 09-08-20 mks DB-168: updated for XML changes definitively declaring service locality
*
*/
public function restoreAuditRecord(array &$_data): bool
{
// ensure that this method ONLY executes on admin
if (!gasConfig::$settings[CONFIG_ADMIN][CONFIG_IS_LOCAL]) {
$this->eventMessages[] = sprintf(ERROR_ENV_INVALID2, CONFIG_ADMIN);
return false;
}
// set-up some local vars
$auditGUID = $this->getColumn(DB_TOKEN);
$recordGUID = $this->getColumn(AUDIT_RECORD_TOKEN);
$template = $this->getColumn(AUDIT_TEMPLATE);
$tableName = $this->getColumn(AUDIT_COLLECTION);
$schema = $this->getColumn(AUDIT_SCHEMA);
$meta = $this->metaPayload;
$meta[META_TEMPLATE] = $template;
$meta[META_SKIP_AUDIT] = 1; // set the flag to disable further audit tracking on the target record
$meta[META_DONUT_FILTER] = 1; // set the flag to return an unfiltered (not cache-mapped) record
$meta[META_CLIENT] = CLIENT_AUDIT;
// instantiate a broker read client (brc)
$brc = new gacBrokerClient(BROKER_QUEUE_R, basename(__FILE__ . COLON . __LINE__));
if (!$brc->status) {
$msg = ERROR_BROKER_CLIENT_DECLARE . BROKER_QUEUE_R;
$this->eventMessages[] = $msg;
return false;
}
// first, we need to build a query to fetch the namaste record from the read broker -- we have to add a fake
// status filter so that we can pull soft-deleted records if needed
// $badQuery = [ DB_TOKEN => [ OPERAND_NULL => [ OPERATOR_EQ => [2] ]]];
$fetchQuery = [
DB_TOKEN => [ OPERAND_NULL => [ OPERATOR_EQ => [ $recordGUID ]]],
DB_STATUS => [ OPERAND_NULL => [ OPERATOR_DNE => [ STATUS_FAKE ]]],
OPERAND_AND => null
];
$request = [
BROKER_REQUEST => BROKER_REQUEST_FETCH,
BROKER_DATA => [ STRING_QUERY_DATA => $fetchQuery ],
BROKER_META_DATA => $meta
];
if ($schema == STRING_PDO) {
$request[BROKER_DATA][STRING_FROM_DATA] = PDO_VIEW_AUDIT . $tableName;
}
$payload = gzcompress(json_encode($request));
$response = $brc->call($payload);
$response = json_decode(gzuncompress($response), true);
if (!$response[PAYLOAD_STATUS] or $response[PAYLOAD_STATE] == STATE_NOT_FOUND) {
$msg = sprintf(ERROR_UT_BROKER_EVENT_FAIL, BROKER_REQUEST_FETCH, $response[PAYLOAD_STATE]);
$this->eventMessages[] = $msg;
$this->logger->error($msg);
$this->logger->error(json_encode($request) . STRING_GUID_KEY . COLON . $recordGUID . COMMA . 'class: ' . $template);
$this->eventMessages = array_merge($this->eventMessages, $response[PAYLOAD_DIAGNOSTICS]);
if (is_object($brc)) $brc->__destruct();
unset($brc);
$this->state = $response[PAYLOAD_STATE];
$this->status = $response[PAYLOAD_STATUS];
return false;
}
// copy over the record prior to the update
$_data[STRING_ORIGINAL] = $response[PAYLOAD_RESULTS][STRING_QUERY_RESULTS][0];
// DON'T GO PAST THIS POINT WHEN DEBUGGING!
// start to build the data payload by grabbing the journal record for the restore query
$meta[META_TEMPLATE] = TEMPLATE_CLASS_JOURNAL;
$objFactory = new gacFactory($meta, FACTORY_EVENT_NEW_CLASS, '', $this->eventMessages);
if (!$objFactory->status) {
$this->eventMessages[] = ERROR_FACTORY_LOAD . COLON . TEMPLATE_CLASS_JOURNAL;
if (is_object($objFactory)) $objFactory->__destruct();
unset($objFactory);
return false;
}
$objJournal = $objFactory->widget;
if (is_object($objFactory)) $objFactory->__destruct();
unset($objFactory);
// build a query to fetch the journal record based on the audit token
$query = [ JOURNAL_AUD_TOK => [ OPERAND_NULL => [ OPERATOR_EQ => [ $auditGUID ]]]];
$projection = [ JOURNAL_RESTORE_QUERY, DB_TOKEN ];
// fetch the restore query and the journal record token only and store in $data
$objJournal->_fetchRecords([ STRING_QUERY_DATA => $query, STRING_RETURN_DATA => $projection]);
if (!$objJournal->status and $objJournal->state != STATE_NOT_FOUND) {
$hdr = basename(__METHOD__) . AT . __LINE__ . COLON;
$this->eventMessages[] = ($this->schema == CONFIG_SCHEMA_MONGO) ? ERROR_NOSQL_FETCH : ERROR_PDO_FETCH;
$this->eventMessages[] = json_encode($query);
$this->logger->error($hdr . json_encode($query));
if (is_object($objJournal)) $objJournal->__destruct();
unset($objJournal);
return false;
}
// pull the pertinent data from the fetch and clear the object of data for re-use
$thisEventGUID = $this->metaPayload[META_EVENT_GUID];
$query = (array) $objJournal->getColumn(JOURNAL_RESTORE_QUERY);
$journalToken = $objJournal->getColumn(DB_TOKEN);
$objJournal->removeData();
// instantiate a remote broker write client (bwc) to publish the journal spin-back request
$bwc = new gacBrokerClient(BROKER_QUEUE_W, __METHOD__ . AT . __LINE__);
if (!$bwc->status) {
$this->eventMessages[] = basename(__FILE__) . DOT . __METHOD__ . AT . __LINE__;
$this->eventMessages[] = ERROR_BROKER_CLIENT_DECLARE . BROKER_QUEUE_W;
if (is_object($bwc)) $bwc->__destruct();
if (is_object($objJournal)) $objJournal->__destruct();
unset($bwc, $objJournal);
return false;
}
// replace the meta GUID in the original query with the current event GUID
$query[BROKER_META_DATA][META_EVENT_GUID] = $thisEventGUID;
// set the meta template to the target (namaste) call
$query[BROKER_META_DATA][META_TEMPLATE] = $template;
// reset the client and the event
$query[BROKER_META_DATA][META_CLIENT] = CLIENT_AUDIT;
$query[BROKER_META_DATA][META_SESSION_EVENT] = EVENT_NAME_AUDIT_UPDATE;
$query[BROKER_META_DATA][META_DONUT_FILTER] = 1;
// publish the roll-back query (taken from the journal record) request to the targeted data (namaste) class
$rc = json_decode(gzuncompress($bwc->call(gzcompress(json_encode($query)))), true);
if (is_object($bwc)) $bwc->__destruct();
unset($bwc);
if ($rc[PAYLOAD_STATUS] === true) {
// successfully updated the target record -- now update the journal record's history section
$subCollectionRecord = [
JOURNAL_HISTORY_DATE_RESTORED => time(),
JOURNAL_HISTORY_RESTORED_EVENT_GUID => $thisEventGUID,
JOURNAL_HISTORY_RESTORED_REASON => $this->metaPayload[META_SYSTEM_NOTES],
JOURNAL_HISTORY_RESTORED_BY => (isset($this->metaPayload[META_USER_GUID]) ? $this->metaPayload[META_USER_GUID] : ((isset($this->metaPayload[META_USER_INFO])) ? $this->metaPayload[META_USER_INFO] : STRING_NOT_DEFINED))
];
$payload = [
STRING_GUID_KEY => $journalToken,
STRING_SUBC_FIELD => JOURNAL_HISTORY,
STRING_DATA => $subCollectionRecord
];
$objJournal->pushSubCollectionEvent($payload);
// failed to update journaling sub-collection record
if (!$objJournal->status) {
$msg = sprintf(ERROR_SUB_C_INSERT_FAIL, $objJournal->class);
$this->logger->warn($msg);
$this->eventMessages[] = $msg;
if (is_object($objJournal)) $objJournal->__destruct();
unset($objJournal);
return false;
}
// submit a broker request to fetch the "new" record
$query = [ DB_TOKEN => [ OPERAND_NULL => [ OPERATOR_EQ => [ $recordGUID ]]]];
$meta[META_TEMPLATE] = $template;
$request = [
BROKER_REQUEST => BROKER_REQUEST_FETCH,
BROKER_DATA => [ STRING_QUERY_DATA => $query ],
BROKER_META_DATA => $meta
];
$rc = json_decode(gzuncompress($brc->call(gzcompress(json_encode($request)))), true);
if (is_object($brc)) $brc->__destruct();
unset($brc);
if (!$rc[PAYLOAD_STATUS]) {
$this->eventMessages[] = ERROR_BROKER_FETCH;
return false;
} elseif ($rc[PAYLOAD_STATE] == STATE_NOT_FOUND) {
$_data[STRING_CHANGED] = INFO_RECORD_NOT_FOUND;
} else {
$_data[STRING_CHANGED] = $rc[PAYLOAD_RESULTS];
}
} else {
// we were unable to restore the targeted record back to it's journal'd state
$this->eventMessages[] = ERROR_JOURNAL_REQ_BOMBED;
$this->logger->error(ERROR_JOURNAL_REQ_BOMBED);
if (!empty($rc[PAYLOAD_DIAGNOSTICS]))
$this->eventMessages = array_merge($this->eventMessages, $rc[PAYLOAD_DIAGNOSTICS]);
$this->state = STATE_AJ_ERROR;
return false;
}
return true;
}
/**
* registerAuditEvent() -- protected method
*
* This method is a gateway method for various private methods here in the core. It's intention is to route the
* request, passed as the only input parameter to the method, to the correct handler following validation.
*
* If an invalid request is received, an error is generated and a bool(false) is returned to the calling client.
*
* Otherwise, the method invokes a the handler method passing that return value, also a boolean, back to the
* calling client.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_eventType
* @return bool
*
*
* HISTORY:
* ========
* 10-18-18 mks DB-67: original coding
* 04-02-19 mks DB-116: check for audit-spawned event so we can bypass making a redundant call
*
*/
protected function registerAuditEvent(string $_eventType): bool
{
if (empty($_eventType)) {
$this->eventMessages[] = ERROR_PARAM_404 . STRING_EVENT_TYPE;
if ($this->debug) $this->logger->debug(ERROR_PARAM_404 . STRING_EVENT_TYPE);
return false;
}
if (isset($this->metaPayload[META_AUDIT_EVENT]) and $this->metaPayload[META_AUDIT_EVENT] !== 1) {
// if this value is set, then we're not going to register another audit event, inferring that the event
// itself was originally an audit-recovery request.
return true;
}
switch ($_eventType) {
case EVENT_NAME_AUDIT_CREATE :
return $this->auditEventCreate();
break;
case EVENT_NAME_AUDIT_FETCH :
return $this->auditEventFetch();
break;
case EVENT_NAME_AUDIT_UPDATE :
return $this->auditEventUpdate();
break;
case EVENT_NAME_AUDIT_DELETE :
return $this->auditEventDelete();
break;
default :
$msg = STRING_EVENT_TYPE . ' ' . ERROR_DATA_INVALID_KEY . $_eventType;
$this->eventMessages[] = $msg;
if ($this->debug) $this->logger->debug($msg);
return false;
break;
}
}
/**
* cloneSquelch() -- protected method
*
* This method has no input parameters and returns void.
*
* The method requires that a cloned object already exist and we use that object to invoke this method which
* toggles off several class member features.
*
* The main goal of this method, then, is to disable all the tracking features normally enabled in a data class
* so as to not generate additional audit records.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
*
* HISTORY:
* ========
* 05-06-19 mks DB-116: original coding
*
*/
protected function cloneSquelch(): void
{
$this->metaPayload[META_CLIENT] = CLIENT_AUDIT;
$this->useAuditing = false;
$this->useJournaling = false;
$this->useCache = false;
}
/**
* isTerceroLocal() -- protected method
*
* There is no input parameter to this method.
*
* The function returns a boolean value indicating whether or not the service is local or not.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_service -- should be one of the four defined environments
* @return bool
*
*
* HISTORY:
* ========
* 08-31-20 mks DB-168: original coding
*
*/
protected function isServiceLocal(string $_service): bool
{
$validServices = [
ENV_ADMIN,
ENV_TERCERO,
ENV_SEGUNDO,
ENV_APPSERVER
];
if (!in_array($_service, $validServices)) {
$this->eventMessages[] = ERROR_SERVICE_UNK . (!empty($_service) ? $_service : STRING_NOT_DEFINED);
return false;
}
// check to see if $_service is local to the current instance
if (isset(gasConfig::$settings[$_service][CONFIG_IS_LOCAL]) and (!gasConfig::$settings[$_service][CONFIG_IS_LOCAL])) {
$hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__);
$this->eventMessages[] = ERROR_LOCAL_SERVICE_404;
$this->logger->warn($hdr . ERROR_LOCAL_SERVICE_404 . COLON . $_service);
$this->logger->warn($hdr . STRING_CLASS_SERVICE . $this->dbService);
$this->logger->warn($hdr . STRING_CLASS . COLON . $this->class);
return false;
} elseif (!isset(gasConfig::$settings[$_service][CONFIG_IS_LOCAL])) {
// todo: security system event
$hdr = sprintf(INFO_LOC, basename(__METHOD__), __LINE__);
$msg = ERROR_CONFIG_RESOURCE_404 . $_service;
$this->eventMessages[] = ERROR_CONFIG_GENERIC;
$this->logger->warn($hdr . $msg);
return false;
}
return true;
}
/**
* auditEventUpdate() -- private method
*
* This method is invoked for a data class that has auditing enabled and has just performed an update event.
*
* There are no input parameters to the method; the requirement is that the class be fully instantiated and
* populated prior to the invocation of this event.
*
* Similar to the other methods, this audit-processing method builds the system-event data then injects the audit
* and journaling data into the payload before submitting the entire hot, sweaty, mess to the adminIN broker
* for processing.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return bool
*
*
* HISTORY:
* ========
* 11-01-18 mks DB-68: original coding
* 01-07-19 mks DB-79: fix for building the restore query in the journal record by removing the extension
* text from the query keys
* 01-23-19 mks DB-106: fixed bug in how the extension was being removed from the column name
* 06-16-20 mks ECI-164: setting TLTI and client data explicitly for the audit event
*
*/
private function auditEventUpdate(): bool
{
$data = $this->buildAuditDataPayload($this->strQuery, EVENT_NAME_AUDIT_UPDATE, STRING_AUDIT_DATA);
//if (is_null($data)) {
// echo PHP_EOL . 'DATA IS NULL!' . PHP_EOL;
// var_export($this->eventMessages);
// echo PHP_EOL;
// echo 'query:' . PHP_EOL;
// echo $this->strQuery . PHP_EOL;
// echo 'json version:' . PHP_EOL;
// echo json_encode($this->strQuery) . PHP_EOL;
//}
$meta = $this->metaPayload;
$meta[META_TLTI] = STRING_CLASS_GAT;
$meta[META_CLIENT] = CLIENT_SYSTEM;
$meta[META_TEMPLATE] = TEMPLATE_CLASS_AUDIT;
$meta[META_LIMIT_OVERRIDE] = 1;
// generate the system event data
$sysEvData[SYSTEM_EVENT_NAME] = EVENT_TYPE_AUDIT;
$sysEvData[SYSTEM_EVENT_TYPE] = EVENT_NAME_AUDIT_UPDATE;
$sysEvData[SYSTEM_EVENT_CLASS] = $this->class;
$sysEvData[SYSTEM_EVENT_BROKER_EVENT] = BROKER_REQUEST_UPDATE;
$sysEvData[SYSTEM_EVENT_OGUID] = $this->metaPayload[META_EVENT_GUID];
$sysEvData[SYSTEM_EVENT_CODE_LOC] = sprintf(STUB_LOC, basename(__FILE__), __METHOD__, __LINE__);
// piggyback the systemEvent data in the audit payload (will be extracted on the admin service side)
$data[SYSTEM_EVENT_DATA] = $sysEvData;
// check if journaling is enabled - if so, add the journal data to the payload
if ($this->useJournaling) {
if (empty($this->auditRecordList) or !is_array($this->auditRecordList)) {
$msg = sprintf(ERROR_AUDIT_DATA_404, STRING_JOURNAL_TOKEN_LIST, STRING_JOURNAL);
$this->eventMessages[] = $msg;
$this->logger->warn($msg);
consoleLog($this->res, CON_SYSTEM, $msg);
} else {
foreach ($this->auditRecordList as $record) {
$newRecord = [];
foreach ($record as $key => $value) {
if (false !== stripos($key, $this->ext)) {
$newKey = str_replace($this->ext, '', $key);
$newRecord[$newKey] = $value;
} else {
$newRecord[$key] = $value;
}
}
$data[STRING_JOURNAL_DATA][STRING_JOURNAL_TOKEN_LIST][] = $newRecord[DB_TOKEN];
$query = [ DB_TOKEN => [ OPERAND_NULL => [ OPERATOR_EQ => [ $newRecord[DB_TOKEN]]]]];
unset($newRecord[DB_TOKEN], $newRecord[$this->pKey]);
$request = [
BROKER_REQUEST => BROKER_REQUEST_UPDATE,
BROKER_DATA => [STRING_QUERY_DATA => $query, STRING_UPDATE_DATA => $newRecord],
BROKER_META_DATA => $meta
];
$data[STRING_JOURNAL_DATA][STRING_JOURNAL_QUERY_LIST][] = $request;
}
}
}
// get the data payload ready for transport
$payload = [
BROKER_REQUEST => BROKER_REQUEST_ADMIN_AUDIT_CREATE,
BROKER_DATA => $data,
BROKER_META_DATA => $meta,
];
$payload = gzcompress(json_encode($payload));
// next step -- call the method to publish the payload and return the results from that method to the client
return ($this->publishAndPerish($payload, $sysEvData[SYSTEM_EVENT_CODE_LOC]));
}
/**
* auditEventDelete() -- private method
*
* This method generates the system-event, audit, and journaling data (records) for the delete event. Once the
* data payloads are generated, a broker client is instantiated and the request is published to the adminIN broker.
*
* There are no input parameters for the method -- all of the required data is set in class members prior to
* this method being invoked.
*
* The return value is passed from the publishAndPerish() method which indicates if the request was successfully
* processed or not.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return bool
*
*
* HISTORY:
* ========
* 11-02-18 mks DB-70: original coding
* 06-16-20 mks ECI-164: setting TLTI and client data explicitly for the audit event
*
*/
private function auditEventDelete(): bool
{
$data = $this->buildAuditDataPayload($this->strQuery, EVENT_NAME_AUDIT_DELETE,STRING_AUDIT_DATA);
$meta = $this->metaPayload;
$meta[META_TLTI] = STRING_CLASS_GAT;
$meta[META_CLIENT] = CLIENT_SYSTEM;
$meta[META_TEMPLATE] = TEMPLATE_CLASS_AUDIT;
$meta[META_LIMIT_OVERRIDE] = 1;
// generate the system event data
$sysEvData[SYSTEM_EVENT_NAME] = EVENT_TYPE_AUDIT;
$sysEvData[SYSTEM_EVENT_TYPE] = EVENT_NAME_AUDIT_DELETE;
$sysEvData[SYSTEM_EVENT_CLASS] = $this->class;
$sysEvData[SYSTEM_EVENT_BROKER_EVENT] = BROKER_REQUEST_DELETE;
$sysEvData[SYSTEM_EVENT_OGUID] = $this->metaPayload[META_EVENT_GUID];
$sysEvData[SYSTEM_EVENT_CODE_LOC] = sprintf(STUB_LOC, basename(__FILE__), __METHOD__, __LINE__);
// piggyback the systemEvent data in the audit payload (will be extracted on the admin service side)
$data[SYSTEM_EVENT_DATA] = $sysEvData;
// check if journaling is enabled -- if so, add journal data to the payload
if ($this->useJournaling) {
if (empty($this->auditRecordList) or !is_array($this->auditRecordList)) {
$msg = sprintf(ERROR_AUDIT_DATA_404, STRING_JOURNAL_TOKEN_LIST, STRING_JOURNAL);
$this->eventMessages[] = $msg;
$this->logger->warn($msg);
consoleLog($this->res, CON_SYSTEM, $msg);
return false;
} else {
foreach ($this->auditRecordList as $record) {
$recordCopy = $record; // so we can update the record
$guid = $recordCopy[(DB_TOKEN . $this->ext)];
// remove the mongo _id field from the record if it exists
if (isset($recordCopy[$this->pKey])) unset($recordCopy[$this->pKey]);
$data[STRING_JOURNAL_DATA][STRING_JOURNAL_TOKEN_LIST][] = $guid;
if ($this->useDeletes === false) {
// soft delete restore query
$request = [
BROKER_REQUEST => BROKER_REQUEST_UPDATE,
BROKER_DATA => [
STRING_QUERY_DATA => [ DB_TOKEN => [ OPERAND_NULL => [ OPERATOR_EQ => [$guid]]]],
STRING_UPDATE_DATA => [ DB_STATUS => $recordCopy[(DB_STATUS . $this->ext)]]
],
BROKER_META_DATA => $this->metaPayload
];
} else {
// hard delete restore query
$request = [
BROKER_REQUEST => BROKER_REQUEST_CREATE,
BROKER_DATA => $record,
BROKER_META_DATA => $this->metaPayload
];
}
$data[STRING_JOURNAL_DATA][STRING_JOURNAL_QUERY_LIST][] = $request;
}
}
}
// get the data payload ready for transport
$payload = [
BROKER_REQUEST => BROKER_REQUEST_ADMIN_AUDIT_CREATE,
BROKER_DATA => $data,
BROKER_META_DATA => $meta
];
$payload = gzcompress(json_encode($payload));
// next step -- call the method to publish the payload and return the results from that method to the client
return ($this->publishAndPerish($payload, $sysEvData[SYSTEM_EVENT_CODE_LOC]));
}
/**
* auditEventFetch() -- private method
*
* This method is generates systemEvent and Audit data payloads that are then published to the adminIN broker
* on the admin service.
*
* There are no input parameters to the method as it's assumed the requisite data members are pre-populated
* and pre-validated by the calling client process(es).
*
* The method build the data and system event payloads, sets the event, then passes the data to another method
* to publish the remote request -- this method returns a status (Boolean) value which is passed-through to the
* calling client.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return bool
*
*
* HISTORY:
* ========
* 10-20-18 mks DB-68: original coding
* 06-16-20 mks ECI-164: setting TLTI and client data explicitly for the audit event
*
*/
private function auditEventFetch(): bool
{
$data = $this->buildAuditDataPayload($this->strQuery, EVENT_NAME_AUDIT_FETCH);
$meta = $this->metaPayload;
$meta[META_TEMPLATE] = TEMPLATE_CLASS_AUDIT;
$meta[META_TLTI] = STRING_CLASS_GAT;
$meta[META_CLIENT] = CLIENT_SYSTEM;
$meta[META_LIMIT_OVERRIDE] = 1;
// generate the system event data
$sysEvData[SYSTEM_EVENT_NAME] = EVENT_TYPE_AUDIT;
$sysEvData[SYSTEM_EVENT_TYPE] = EVENT_NAME_AUDIT_FETCH;
$sysEvData[SYSTEM_EVENT_CLASS] = $this->class;
$sysEvData[SYSTEM_EVENT_BROKER_EVENT] = BROKER_REQUEST_FETCH;
$sysEvData[SYSTEM_EVENT_OGUID] = $this->metaPayload[META_EVENT_GUID];
$sysEvData[SYSTEM_EVENT_CODE_LOC] = sprintf(STUB_LOC, basename(__FILE__), __METHOD__, __LINE__);
// piggyback the systemEvent data in the audit payload (will be extracted on the admin service side)
$data[SYSTEM_EVENT_DATA] = $sysEvData;
// get the data payload ready for transport
$payload = [
BROKER_REQUEST => BROKER_REQUEST_ADMIN_AUDIT_CREATE,
BROKER_DATA => $data,
BROKER_META_DATA => $meta
];
$payload = gzcompress(json_encode($payload));
// next step -- call the method to publish the payload and return the results from that method to the client
return ($this->publishAndPerish($payload, $sysEvData[SYSTEM_EVENT_CODE_LOC]));
}
/**
* auditEventCreate() -- private method
*
* This method is responsible for creating the data payload for both the systemEvent and the Audit (Create)
* records for the current data class.
*
* There are no input parameters to the method as all data is expected to already be populated in the class
* member variables.
*
* The method is meant to handle 1->N records in the current $data member. Note, please, that this is NOT the
* audit or systemEvent class - this is the Namaste data class where auditing has been enabled.
*
* We spin through the records in $data creating an audit record for each... next we create a single record for
* the systemEvent collection and, that data, is embedded within the indexed array as a key-value pair.
* (Extracted in the broker processing admin-side and removed prior to creating the admin records.)
*
* Finally, we create/instantiate a broker client to AdminIn and publish the audit event request. Since adminIN
* is non-RPC, we don't block on a response from the broker and just proceed to clean-up and return a bool(true)
* to the calling client.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @return bool
*
* HISTORY:
* ========
* 10-18-18 mks DB-67: original coding
* 11-26-18 mks DB-55: fixed parameter passed to buildAuditDataPayload(): if array, json-ize the array
* 06-16-20 mks ECI-164: setting TLTI and client data explicitly for the audit event
*
*/
private function auditEventCreate(): bool
{
// generate the data payload that will be submitted to the audit event...
$query = (is_array($this->auditCreateQueries)) ? json_encode($this->auditCreateQueries) : $this->auditCreateQueries;
$data = $this->buildAuditDataPayload($query, EVENT_NAME_AUDIT_CREATE);
$meta = $this->metaPayload;
$meta[META_TEMPLATE] = TEMPLATE_CLASS_AUDIT;
$meta[META_TLTI] = STRING_CLASS_GAT;
$meta[META_CLIENT] = CLIENT_SYSTEM;
$meta[META_LIMIT_OVERRIDE] = 1;
// generate the system event data
$sysEvData[SYSTEM_EVENT_NAME] = EVENT_TYPE_AUDIT;
$sysEvData[SYSTEM_EVENT_TYPE] = EVENT_NAME_AUDIT_CREATE;
$sysEvData[SYSTEM_EVENT_CLASS] = $this->class;
$sysEvData[SYSTEM_EVENT_BROKER_EVENT] = BROKER_REQUEST_CREATE;
$sysEvData[SYSTEM_EVENT_OGUID] = $this->metaPayload[META_EVENT_GUID];
$sysEvData[SYSTEM_EVENT_CODE_LOC] = sprintf(STUB_LOC, basename(__FILE__), __METHOD__, __LINE__);
$sysEvData[SYSTEM_EVENT_KEY] = 'hasJournaling';
$sysEvData[SYSTEM_EVENT_VAL] = intval($this->useJournaling);
// piggyback the systemEvent data in the audit payload (will be extracted on the admin service side)
$data[SYSTEM_EVENT_DATA] = $sysEvData;
// check if journaling is enabled - if so, add the journal data to the payload
if ($this->useJournaling) {
if (empty($this->auditRecordList) or !is_array($this->auditRecordList)) {
$msg = sprintf(ERROR_AUDIT_DATA_404, STRING_JOURNAL_TOKEN_LIST, STRING_JOURNAL);
$this->eventMessages[] = $msg;
$this->logger->warn($msg);
consoleLog($this->res, CON_SYSTEM, $msg);
} else {
$data[STRING_JOURNAL_DATA][STRING_JOURNAL_TOKEN_LIST] = $this->auditRecordList;
}
if (empty($this->auditUndoQueries) or !is_array($this->auditUndoQueries)) {
$msg = sprintf(ERROR_AUDIT_DATA_404, STRING_JOURNAL_QUERY_LIST, STRING_JOURNAL);
$this->eventMessages[] = $msg;
$this->logger->warn($msg);
consoleLog($this->res, CON_SYSTEM, $msg);
} else {
$data[STRING_JOURNAL_DATA][STRING_JOURNAL_QUERY_LIST] = $this->auditUndoQueries;
}
}
// get the data payload ready for transport
$payload = [
BROKER_REQUEST => BROKER_REQUEST_ADMIN_AUDIT_CREATE,
BROKER_DATA => $data,
BROKER_META_DATA => $meta
];
$payload = gzcompress(json_encode($payload));
// next step -- call the method to publish the payload and return the results from that method to the client
return ($this->publishAndPerish($payload, $sysEvData[SYSTEM_EVENT_CODE_LOC]));
}
/**
* publishAndPerish() -- private method
*
* This method is called by the CRUD audit events when a data payload needs to be published to the AdminIN broker.
*
* The method accepts the following parameters:
*
* $_payload -- this is the broker payload which has already been json-encoded and compressed
* $_loc -- string text that identifies the origin of the calling client
*
* The method instantiates a gacAdminClient and publishes the request to the AdminIN broker -- since there is no
* return payload, the boolean value this method returns to the calling client is based on the success of processing
* and submitting the request only.
*
* If the payload was unsuccessful, for whatever reasons, error messages are generated and control is returned to
* the calling client.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_payload
* @param string $_loc
* @return bool
*
*
* HISTORY:
* ========
* 10-20-18 mks DB-68: original coding
*
*/
private function publishAndPerish(string $_payload, string $_loc): bool
{
try {
$errMsg = ERROR_BROKER_CLIENT_DECLARE . CONFIG_ADMIN_BROKER_IN;
$objAdminClient = new gacWorkQueueClient($_loc);
if (!$objAdminClient->status) {
$this->eventMessages[] = $errMsg;
if ($this->debug and $this->logger->available) $this->logger->debug($errMsg);
if (is_object($objAdminClient)) $objAdminClient->__destruct();
unset($objAdminClient);
return false;
}
@$objAdminClient->call($_payload); // fire-n-forget broker
if (is_object($objAdminClient)) $objAdminClient->__destruct();
unset($objAdminClient);
// todo -- we can evaluate the success of the publish request by publishing a second request to fetch the newly-created audit record and return a bool based on the presence of the record
return true; // true status only indicates that we published the event
} catch (Throwable $t) {
$this->eventMessages[] = $errMsg;
$this->eventMessages[] = ERROR_THROWABLE_EXCEPTION;
$this->eventMessages[] = $t->getMessage();
$this->logger->warn($errMsg);
$this->logger->warn(ERROR_THROWABLE_EXCEPTION);
$this->logger->warn($t->getMessage());
consoleLog($this->res, CON_SYSTEM, ERROR_THROWABLE_EXCEPTION . $t->getMessage());
if (isset($objAdminClient) and is_object($objAdminClient)) $objAdminClient->__destruct();
unset($objAdminClient);
return false;
}
}
/**
* buildAuditDataPayload() -- private method
*
* This method is used by the audit CRUD routines and has the simple job of building the data payload for an
* audit event to be submitted to the adminIn broker.
*
* The single input parameter is the query string. Note that for destructive queries, this should be json-ized
* array containing the fully-functional broker payload that could be submitted, accepted and processed. For
* the fetch queries, it should be the query string built.
*
* Method requires that the $data member be populated and that the data class is fully-instantiated.
*
* Once each audit record has been generated, one for each record touched during the CRUD request, we return the
* array of audit-data records back to the calling client.
*
* If an error is raised, then a null value is returned and it's the responsibility of the calling client
* to handle that return.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
* @param string $_query
* @param string $_event -- defines which CRUD event is generating the audit event
* @param string $_which -- defines which data source are we pulling from ($data or $auditRecordList)
* @return array|null
*
*
* HISTORY:
* ========
* 10-29-18 mks DB-68: original coding
* 11-06-18 mks DB-82: added the $_event parameter because d'oh!
*
*/
private function buildAuditDataPayload(string $_query, string $_event, string $_which = STRING_DATA): ?array
{
$dbConfig = gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_MONGODB][$this->dbService];
$data = null;
$counter = 0;
if ($_which != STRING_DATA and $_which != STRING_AUDIT_DATA) {
$msg = ERROR_AUDIT_SOURCE;
$this->eventMessages[] = $msg;
if ($this->debug) $this->logger->debug($msg);
return null;
} else {
$sourceData = ($_which == STRING_DATA) ? $this->getData() : $this->auditRecordList;
}
if (!is_array($sourceData) or empty($sourceData)) {
$this->eventMessages[] = ERROR_AUDIT_NO_SOURCE;
if ($this->debug) $this->logger->error(ERROR_AUDIT_NO_SOURCE);
return $data;
}
// first step is to build the meta & data payloads that will be submitted to the systemEvent broker
foreach ($sourceData as $record) {
if (isset($this->metaPayload[META_SESSION_GUID]))
$data[$counter][AUDIT_SESSION_GUID] = $this->metaPayload[META_SESSION_GUID];
if (isset($this->metaPayload[META_USER_GUID]))
$data[$counter][AUDIT_USER_GUID] = $this->metaPayload[META_USER_GUID];
if (isset($this->metaPayload[META_SESSION_IP]))
$data[$counter][AUDIT_SESSION_IP] = $this->metaPayload[META_SESSION_IP];
if (isset($this->metaPayload[META_CLIENT]))
$data[$counter][AUDIT_ACCESS_CLIENT] = $this->metaPayload[META_CLIENT];
$data[$counter][AUDIT_SERVICE] = $this->dbService;
$data[$counter][AUDIT_SCHEMA] = $this->schema;
$data[$counter][AUDIT_DB] = gasConfig::$settings[CONFIG_ID][CONFIG_ID_ENV] . UDASH . $dbConfig[CONFIG_DATABASE_MONGODB_AUTH_SOURCE];
$data[$counter][AUDIT_COLLECTION] = $this->collectionName;
$data[$counter][AUDIT_TEMPLATE] = $this->templateClass;
$data[$counter][AUDIT_COLLECTION_EXT] = $this->ext;
$data[$counter][AUDIT_RECORD_TOKEN] = $record[DB_TOKEN . $this->ext];
$data[$counter][AUDIT_SNAPSHOT] = base64_encode(json_encode($record));
$data[$counter][AUDIT_QUERY] = $_query;
$data[$counter][AUDIT_OPERATION] = $_event;
$data[$counter][AUDIT_ACCESS_ALLOWED] = true;
$data[$counter][DB_EVENT_GUID] = $this->metaPayload[META_EVENT_GUID];
// fields that were not-defined at time of original coding todo: code this when data becomes available
$data[$counter][AUDIT_ACCESS_USER] = STRING_NOT_DEFINED;
$data[$counter++][AUDIT_USER_ROLE] = STRING_NOT_DEFINED;
}
return $data;
}
/**
* squelchIDEWarnings() -- private class method
*
* This method is called once, by the class constructor, and is used to init some member variables so as to
* suppress IDE warnings in the member-declaration section. Not all of these member variables are in-use as of
* yet (although they will be, someday...) so they're stationed here, in this little null-value routine.
*
*
* @author mike@givingassistant.org
* @version 1.0
*
*
* HISTORY:
* ========
* 01-03-20 mks DB-150: original coding
*
*/
private function squelchIDEWarnings()
{
$this->recordAudit = [];
$this->recordJournal = [];
$this->strSubCollectionQuery = '';
$this->restoreQuery = '';
$this->returnData = [];
$this->requiredFields = [];
$this->indexOrder = [];
$this->historyData = null;
$this->recordLimit = (gasConfig::$settings[CONFIG_DATABASE][CONFIG_DATABASE_QUERY_RECORD_LIMIT]) ?? 100;
$this->exposedFields = null;
}
/**
* __destruct() -- public function
*
* class destructor
*
* @author mike@givingassistant.org
* @version 1.0
*
* HISTORY:
* ========
* 06-15-17 mks original coding
*
*/
public function __destruct()
{
// As of PHP 5.3.10 destructors are not run on shutdown caused by fatal errors.
//
// destructor is registered shut-down function in constructor -- so any recovery
// efforts should go in this method.
// there is no destructor method defined in the core abstraction class, hence
// there is no call to that parent destructor in this class.
}
}