952 days continuous production uptime, 40k+ tp/s single node. Original corpo Bitbucket history not included — clean archive commit.
3389 lines
155 KiB
PHP
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.
|
|
}
|
|
|
|
}
|