/**
* @param ACLRequest $aclRequest
*
* @return bool
*/
public function check(ACLRequest $aclRequest)
{
$objectKey = Objects::normalizeObjectKey($aclRequest->getObjectKey());
$targetType = $aclRequest->getTargetType();
$targetId = $aclRequest->getTargetId();
$pk = $aclRequest->getPrimaryKey();
$field = $aclRequest->getField();
$pk = $this->objects->normalizePkString($objectKey, $pk);
if (ACL::TARGET_TYPE_USER === $targetType && null === $targetId) {
//0 means guest
$targetId = $this->pageStack->getUser() ? $this->pageStack->getUser()->getId() : 0;
}
$user = $this->pageStack->getUser();
if ($user) {
$groupIds = $user->getGroupIds();
if (false !== strpos(',' . $groupIds . ',', ',1,')) {
//user is in the admin group, so he has always access.
return true;
}
}
if (ACL::TARGET_TYPE_USER === $targetType && 1 === $targetId) {
//user admin has always access
return true;
}
if (ACL::TARGET_TYPE_GROUP === $targetType && 1 === $targetId) {
//group admin has always access
return true;
}
if (0 === $targetId) {
//guests do always have no access
return false;
}
if (ACL::TARGET_TYPE_GROUP === $targetType && !$targetId) {
throw new \InvalidArgumentException('For type TARGET_TYPE_GROUP a targetId is required.');
}
$cacheKey = null;
if ($pk && $this->getCaching()) {
$pkString = $this->objects->getObjectUrlId($objectKey, $pk);
$cacheKey = md5($targetType . '.' . $targetId . '.' . $objectKey . '/' . $pkString . '/' . json_encode($field));
$cached = $this->cacher->getDistributedCache('core/acl/' . $cacheKey);
if (null !== $cached) {
return $cached;
}
}
$rules = self::getRules($objectKey, $aclRequest->getMode(), $targetType, $targetId);
if (count($rules) === 0) {
//no rules found, so we have no access
return false;
}
$access = null;
$currentObjectPk = $pk;
$definition = $this->objects->getDefinition($objectKey);
$not_found = true;
//starts directly as if we were in the parent checking.
$parent_acl = $aclRequest->isAsParent();
$fCount = null;
$fKey = null;
$fValue = null;
$fIsArray = is_array($field);
if ($fIsArray) {
$fCount = count($field);
$fKey = key($field);
$fValue = current($field);
if (is_int($fKey)) {
$fKey = $fValue;
$fValue = null;
}
}
$depth = 0;
$match = false;
$originObjectItemPk = $currentObjectPk;
while ($not_found) {
$currentObjectPkString = null;
if ($currentObjectPk) {
$currentObjectPkString = $this->objects->getObjectUrlId($objectKey, $currentObjectPk);
}
$depth++;
if ($depth > 50) {
$not_found = false;
break;
}
foreach ($rules as $aclRule) {
if ($parent_acl && !$aclRule['sub']) {
//as soon we enter the parent_acl mode we only take acl rules into consideration
//that are also valid for children (sub=true)
continue;
}
$match = false;
/*
* CUSTOM CONSTRAINT
*/
if ($aclRule['constraint_type'] === ACL::CONSTRAINT_CONDITION) {
$objectItem = null;
if ($originObjectItemPk === $currentObjectPk && null !== $aclRequest->getPrimaryObjectItem()) {
$objectItem = $aclRequest->getPrimaryObjectItem();
} else {
if ($originObjectItemPk) {
$objectItem = $this->objects->get($objectKey, $currentObjectPk);
}
}
if ($objectItem && $this->conditionOperator->satisfy($aclRule['constraint_code'], $objectItem, $objectKey)) {
$match = true;
}
/*
* EXACT
*/
} else {
if ($aclRule['constraint_type'] === ACL::CONSTRAINT_EXACT) {
if ($currentObjectPk && $aclRule['constraint_code'] === $currentObjectPkString) {
$match = true;
}
/**
* ALL
*/
} else {
$match = true;
}
}
if (!$match && $aclRule['sub'] && $currentObjectPk) {
// we need to check if a parent matches this $acl as we have sub=true
$parentItem = $this->objects->normalizePkString($objectKey, $currentObjectPk);
$parentCondition = Condition::create($aclRule['constraint_code']);
$parentOptions['fields'] = $this->conditionOperator->extractFields($parentCondition);
while ($parentItem = $this->objects->getParent($objectKey, $this->objects->getObjectPk($objectKey, $parentItem), $parentOptions)) {
if ($aclRule['constraint_type'] === ACL::CONSTRAINT_CONDITION && $this->conditionOperator->satisfy($parentCondition, $parentItem)) {
$match = true;
break;
} else {
if ($aclRule['constraint_type'] === ACL::CONSTRAINT_EXACT && $aclRule['constraint_code'] === $this->objects->getObjectUrlId($objectKey, $parentItem)) {
$match = true;
break;
}
}
}
}
if ($match) {
//match, check all $field
$field2Key = $field;
if ($field) {
if ($fIsArray && $fCount === 1) {
if (is_string($fKey) && is_array($aclRule['fields'][$fKey])) {
//this field has limits
if (($field2Acl = $aclRule['fields'][$fKey]) !== null) {
if (is_array($field2Acl[0])) {
//complex field rule, $field2Acl = ([{access: no, condition: [['id', '>', 2], ..]}, {}, ..])
foreach ($field2Acl as $fRule) {
$satisfy = false;
if (($f = $definition->getField($fKey)) && $f->getType() === 'object') {
$uri = $f->getObject() . '/' . $fValue;
$uriObject = $this->objects->getFromUrl($uri);
$satisfy = $this->conditionOperator->satisfy($fRule['condition'], $uriObject);
} else {
if (null !== $fValue) {
$satisfy = $this->conditionOperator->satisfy($fRule['condition'], $field);
}
}
if ($satisfy) {
return $fRule['access'] === 1 ? true : false;
}
}
//if no field rules fits, we consider the whole rule
if ($aclRule['access'] !== 2) {
return $aclRule['access'] === 1 ? true : false;
}
} else {
//simple field rule $field2Acl = ({"value1": yes, "value2": no}
if ($field2Acl[$fKey] !== null) {
return $field2Acl[$fKey] === 1 ? true : false;
} else {
//current($field) is not exactly defined in $field2Acl, so we set $access to $acl['access']
//
//if access = 2 then wo do not know it, cause 2 means 'inherited', so maybe
//a other rule has more detailed rule
if ($aclRule['access'] !== 2) {
$access = $aclRule['access'] === 1 ? true : false;
break;
}
}
}
}
} else {
//this field has only true or false
$field2Key = $fKey;
}
}
if (!is_array($field2Key)) {
if ($aclRule['fields'] && ($field2Acl = $aclRule['fields'][$field2Key]) !== null && !is_array($aclRule['fields'][$field2Key])) {
$access = $field2Acl === 1 ? true : false;
break;
} else {
//$field is not exactly defined, so we set $access to $acl['access']
//and maybe a rule with the same code has the field defined
// if access = 2 then this rule is only for exactly define fields
if ($aclRule['access'] !== 2) {
$access = $aclRule['access'] === 1 ? true : false;
break;
}
}
}
} else {
$access = $aclRule['access'] === 1 ? true : false;
break;
}
}
}
//foreach
if (null === $access && $definition->isNested() && $pk) {
//$access has not defined yet (no rule matched yet). Check if nested and $pk is given
//load its root and check again
if (null === ($currentObjectPk = $this->objects->getParentPk($objectKey, $currentObjectPk))) {
$access = $aclRequest->isRootHasAccess() ? true : $access;
break;
}
$parent_acl = true;
} else {
break;
}
}
$access = (bool) $access;
if ($pk && $this->getCaching()) {
$this->cacher->setDistributedCache('core/acl/' . $cacheKey, $access);
}
return $access;
}