/** * @param array $schema The table schema array. * @param array $parentRow The parent record being updated. * @return array */ public function addOrUpdateToManyRelationships($schema, $parentRow, &$childLogEntries = null, &$parentCollectionRelationshipsChanged = false, $parentData = []) { $log = $this->logger(); // Create foreign row and update local column with the data id foreach ($schema as $column) { $colName = $column['id']; if (!isset($column['relationship']) || !is_array($column['relationship'])) { continue; } // Ignore absent values & non-arrays if (!isset($parentRow[$colName]) || !is_array($parentRow[$colName])) { continue; } $relationship = $column['relationship']; $fieldIsCollectionAssociation = in_array($relationship['type'], TableSchema::$association_types); $lowercaseColumnType = strtolower($relationship['type']); // Ignore empty OneToMany collections $fieldIsOneToMany = 'onetomany' === $lowercaseColumnType; // Ignore non-arrays and empty collections if (empty($parentRow[$colName])) { //} || ($fieldIsOneToMany && )) { // Once they're managed, remove the foreign collections from the record array unset($parentRow[$colName]); continue; } $foreignDataSet = $parentRow[$colName]; $colUiType = $column['ui']; /** One-to-Many, Many-to-Many */ if ($fieldIsCollectionAssociation) { $this->enforceColumnHasNonNullValues($column['relationship'], ['related_table', 'junction_key_right'], $this->table); $foreignTableName = $column['relationship']['related_table']; $foreignJoinColumn = $column['relationship']['junction_key_right']; switch ($lowercaseColumnType) { /** One-to-Many */ case 'onetomany': $ForeignTable = new RelationalTableGateway($this->acl, $foreignTableName, $this->adapter); foreach ($foreignDataSet as &$foreignRecord) { if (empty($foreignRecord)) { continue; } $foreignSchemaArray = TableSchema::getSchemaArray($ForeignTable->table); $hasActiveColumn = $this->schemaHasActiveColumn($foreignSchemaArray); $foreignColumn = TableSchema::getColumnSchemaArray($ForeignTable->table, $foreignJoinColumn); $hasPrimaryKey = isset($foreignRecord[$ForeignTable->primaryKeyFieldName]); $canBeNull = $foreignColumn['is_nullable'] === 'YES'; if ($hasPrimaryKey && isset($foreignRecord[STATUS_COLUMN_NAME]) && $foreignRecord[STATUS_COLUMN_NAME] === STATUS_DELETED_NUM) { if (!$hasActiveColumn && !$canBeNull) { $Where = new Where(); $Where->equalTo($ForeignTable->primaryKeyFieldName, $foreignRecord[$ForeignTable->primaryKeyFieldName]); $ForeignTable->delete($Where); continue; } if (!$hasActiveColumn || $canBeNull) { unset($foreignRecord[STATUS_COLUMN_NAME]); } if (!$canBeNull) { $foreignRecord[$foreignJoinColumn] = $parentRow['id']; } } // only add parent id's to items that are lacking the parent column if (!array_key_exists($foreignJoinColumn, $foreignRecord)) { $foreignRecord[$foreignJoinColumn] = $parentRow['id']; } $foreignRecord = $this->manageRecordUpdate($foreignTableName, $foreignRecord, self::ACTIVITY_ENTRY_MODE_CHILD, $childLogEntries, $parentCollectionRelationshipsChanged, $parentData); } break; /** Many-to-Many */ /** Many-to-Many */ case 'manytomany': /** * [+] Many-to-Many payloads declare collection items this way: * $parentRecord['collectionName1'][0-9]['data']; // record key-value array * [+] With optional association metadata: * $parentRecord['collectionName1'][0-9]['id']; // for updating a pre-existing junction row * $parentRecord['collectionName1'][0-9]['active']; // for disassociating a junction via the '0' value */ $noDuplicates = isset($column['options']['no_duplicates']) ? $column['options']['no_duplicates'] : 0; $this->enforceColumnHasNonNullValues($column['relationship'], ['junction_table', 'junction_key_left'], $this->table); $junctionTableName = $column['relationship']['junction_table']; $junctionKeyLeft = $column['relationship']['junction_key_left']; $junctionKeyRight = $column['relationship']['junction_key_right']; $JunctionTable = new RelationalTableGateway($this->acl, $junctionTableName, $this->adapter); $ForeignTable = new RelationalTableGateway($this->acl, $foreignTableName, $this->adapter); foreach ($foreignDataSet as $junctionRow) { /** This association is designated for removal */ if (isset($junctionRow[STATUS_COLUMN_NAME]) && $junctionRow[STATUS_COLUMN_NAME] == STATUS_DELETED_NUM) { $Where = new Where(); $Where->equalTo($JunctionTable->primaryKeyFieldName, $junctionRow[$JunctionTable->primaryKeyFieldName]); $JunctionTable->delete($Where); // Flag the top-level record as having been altered. // (disassociating w/ existing M2M collection entry) $parentCollectionRelationshipsChanged = true; continue; } else { if (isset($junctionRow['data'][$JunctionTable->primaryKeyFieldName])) { // Is this a new element? // if the element `id` exists it's because is not a new element // and already had its id given. $Where = new Where(); $Where->equalTo($junctionKeyLeft, $parentRow[$this->primaryKeyFieldName])->equalTo($junctionKeyRight, $junctionRow['data'][$JunctionTable->primaryKeyFieldName]); // hard-coded check for sort diff // @todo fix this $junctionRowResult = $JunctionTable->select($Where); if ($junctionRowResult->count()) { // we are expecting one. $junctionRowResultArray = $junctionRowResult->toArray(); $junctionRowResultArray = end($junctionRowResultArray); if (array_key_exists('sort', $junctionRow) && array_key_exists('sort', $junctionRowResultArray)) { if ($junctionRowResultArray['sort'] === $junctionRow['sort']) { continue; } } } } } /** Update foreign record */ $foreignRecord = $ForeignTable->manageRecordUpdate($foreignTableName, $junctionRow['data'], self::ACTIVITY_ENTRY_MODE_CHILD, $childLogEntries, $parentCollectionRelationshipsChanged, $parentData); // Junction/Association row $junctionTableRecord = [$junctionKeyLeft => $parentRow[$this->primaryKeyFieldName], $foreignJoinColumn => $foreignRecord[$ForeignTable->primaryKeyFieldName]]; // Update fields on the Junction Record $junctionTableRecord = array_merge($junctionTableRecord, $junctionRow); $foreignRecord = (array) $foreignRecord; $relationshipChanged = $this->recordDataContainsNonPrimaryKeyData($foreignRecord, $ForeignTable->primaryKeyFieldName) || $this->recordDataContainsNonPrimaryKeyData($junctionTableRecord, $JunctionTable->primaryKeyFieldName); // Update Foreign Record if ($relationshipChanged) { unset($junctionTableRecord['data']); $JunctionTable->addOrUpdateRecordByArray($junctionTableRecord, $junctionTableName); } } break; } // Once they're managed, remove the foreign collections from the record array unset($parentRow[$colName]); } } return $parentRow; }