Files
Cloud-CMS/lib/Factory/BaseFactory.php
Matt Batchelder 05ce0da296 Initial Upload
2025-12-02 10:32:59 -05:00

507 lines
16 KiB
PHP

<?php
/*
* Copyright (C) 2024 Xibo Signage Ltd
*
* Xibo - Digital Signage - https://xibosignage.com
*
* This file is part of Xibo.
*
* Xibo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Xibo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Xibo\Factory;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Xibo\Entity\User;
use Xibo\Helper\SanitizerService;
use Xibo\Service\BaseDependenciesService;
use Xibo\Service\ConfigServiceInterface;
use Xibo\Service\LogServiceInterface;
use Xibo\Storage\StorageServiceInterface;
/**
* Class BaseFactory
* @package Xibo\Factory
*/
class BaseFactory
{
/**
* Count records last query
* @var int
*/
protected $_countLast = 0;
/**
* @var StorageServiceInterface
*/
private $store;
/**
* @var LogServiceInterface
*/
private $log;
/**
* @var SanitizerService
*/
private $sanitizerService;
/**
* @var User
*/
private $user;
/**
* @var UserFactory
*/
private $userFactory;
/**
* @var BaseDependenciesService
*/
private $baseDependenciesService;
/**
* @param BaseDependenciesService $baseDependenciesService
*/
public function useBaseDependenciesService(BaseDependenciesService $baseDependenciesService)
{
$this->baseDependenciesService = $baseDependenciesService;
}
/**
* Set Acl Dependencies
* @param User $user
* @param UserFactory $userFactory
* @return $this
*/
public function setAclDependencies($user, $userFactory)
{
$this->user = $user;
$this->userFactory = $userFactory;
return $this;
}
/**
* Get Store
* @return StorageServiceInterface
*/
protected function getStore()
{
return $this->baseDependenciesService->getStore();
}
/**
* Get Log
* @return LogServiceInterface
*/
protected function getLog()
{
return $this->baseDependenciesService->getLogger();
}
/**
* @return SanitizerService
*/
protected function getSanitizerService()
{
return $this->baseDependenciesService->getSanitizer();
}
/**
* Get Sanitizer
* @param $array
* @return \Xibo\Support\Sanitizer\SanitizerInterface
*/
protected function getSanitizer($array)
{
return $this->getSanitizerService()->getSanitizer($array);
}
/**
* @return \Xibo\Support\Validator\ValidatorInterface
*/
protected function getValidator()
{
return $this->getSanitizerService()->getValidator();
}
/**
* Get User
* @return User
*/
public function getUser()
{
return $this->user;
}
/**
* Get User Factory
* @return UserFactory
*/
public function getUserFactory()
{
return $this->userFactory;
}
/**
* @return \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
public function getDispatcher(): EventDispatcherInterface
{
return $this->baseDependenciesService->getDispatcher();
}
/**
* @return \Xibo\Service\ConfigServiceInterface
*/
public function getConfig(): ConfigServiceInterface
{
return $this->baseDependenciesService->getConfig();
}
/**
* Count of records returned for the last query.
* @return int
*/
public function countLast()
{
return $this->_countLast;
}
/**
* View Permission SQL
* @param $entity
* @param $sql
* @param $params
* @param $idColumn
* @param null $ownerColumn
* @param array $filterBy
* @throws \Xibo\Support\Exception\NotFoundException
*/
public function viewPermissionSql(
$entity,
&$sql,
&$params,
$idColumn,
$ownerColumn = null,
$filterBy = [],
$permissionFolderIdColumn = null
) {
$parsedBody = $this->getSanitizer($filterBy);
$checkUserId = $parsedBody->getInt('userCheckUserId');
if ($checkUserId !== null) {
$this->getLog()->debug(sprintf('Checking permissions against a specific user: %d', $checkUserId));
$user = $this->getUserFactory()->getById($checkUserId);
}
else {
$user = $this->getUser();
if ($user !== null)
$this->getLog()->debug(sprintf('Checking permissions against the logged in user: ID: %d, Name: %s, UserType: %d', $user->userId, $user->userName, $user->userTypeId));
}
$permissionSql = '';
// Has the user check been disabled? 0 = no it hasn't
$performUserCheck = $parsedBody->getCheckbox('disableUserCheck') == 0;
if ($performUserCheck && !$user->isSuperAdmin()) {
$permissionSql .= '
AND (' . $idColumn . ' IN (
SELECT `permission`.objectId
FROM `permission`
INNER JOIN `permissionentity`
ON `permissionentity`.entityId = `permission`.entityId
INNER JOIN `group`
ON `group`.groupId = `permission`.groupId
INNER JOIN `lkusergroup`
ON `lkusergroup`.groupId = `group`.groupId
INNER JOIN `user`
ON lkusergroup.UserID = `user`.UserID
WHERE `permissionentity`.entity = :permissionEntity
AND `user`.userId = :currentUserId
AND `permission`.view = 1
UNION ALL
SELECT `permission`.objectId
FROM `permission`
INNER JOIN `permissionentity`
ON `permissionentity`.entityId = `permission`.entityId
INNER JOIN `group`
ON `group`.groupId = `permission`.groupId
WHERE `permissionentity`.entity = :permissionEntity
AND `group`.isEveryone = 1
AND `permission`.view = 1
)
';
$params['permissionEntity'] = $entity;
$params['currentUserId'] = $user->userId;
if ($ownerColumn != null) {
$permissionSql .= ' OR ' . $ownerColumn . ' = :currentUserId2';
$params['currentUserId2'] = $user->userId;
}
// Home folders (only for folder entity)
if ($entity === 'Xibo\Entity\Folder') {
$permissionSql .= ' OR folder.folderId = :permissionsHomeFolderId';
$permissionSql .= ' OR folder.permissionsFolderId = :permissionsHomeFolderId';
$params['permissionsHomeFolderId'] = $this->getUser()->homeFolderId;
}
// Group Admin?
if ($user->userTypeId == 2 && $ownerColumn != null) {
// OR the group admin and the owner of the media are in the same group
$permissionSql .= '
OR (
SELECT COUNT(lkUserGroupId)
FROM `lkusergroup`
WHERE userId = ' . $ownerColumn . '
AND groupId IN (
SELECT groupId
FROM `lkusergroup`
WHERE userId = :currentUserId3
)
) > 0
';
$params['currentUserId3'] = $user->userId;
}
if ($permissionFolderIdColumn != null) {
$permissionSql .= '
OR ' . $permissionFolderIdColumn . ' IN (
SELECT `permission`.objectId
FROM `permission`
INNER JOIN `permissionentity`
ON `permissionentity`.entityId = `permission`.entityId
INNER JOIN `group`
ON `group`.groupId = `permission`.groupId
INNER JOIN `lkusergroup`
ON `lkusergroup`.groupId = `group`.groupId
INNER JOIN `user`
ON lkusergroup.UserID = `user`.UserID
WHERE `permissionentity`.entity = :folderEntity
AND `permission`.view = 1
AND `user`.userId = :currentUserId
UNION ALL
SELECT `permission`.objectId
FROM `permission`
INNER JOIN `permissionentity`
ON `permissionentity`.entityId = `permission`.entityId
INNER JOIN `group`
ON `group`.groupId = `permission`.groupId
WHERE `permissionentity`.entity = :folderEntity
AND `group`.isEveryone = 1
AND `permission`.view = 1
)
';
$params['folderEntity'] = 'Xibo\Entity\Folder';
}
$permissionSql .= ' )';
//$this->getLog()->debug('Permission SQL = %s', $permissionSql);
}
// Set out params
$sql = $sql . $permissionSql;
}
/**
* @param $variable
* @return array
*/
protected function parseComparisonOperator($variable)
{
$operator = '=';
$allowedOperators = [
'less-than' => '<',
'greater-than' => '>',
'less-than-equal' => '<=',
'greater-than-equal' => '>='
];
if (stripos($variable, '|') !== false) {
$variable = explode('|', $variable);
if (array_key_exists($variable[0], $allowedOperators)) {
$operator = $allowedOperators[$variable[0]];
}
$variable = $variable[1];
}
return [
'operator' => $operator,
'variable' => $variable
];
}
/**
* Sets the name filter for all factories to use.
*
* @param string $tableName Table name
* @param string $tableColumn Column with the name
* @param array $terms An Array exploded by "," of the search names
* @param string $body Current SQL body passed by reference
* @param array $params Array of parameters passed by reference
* @param bool $useRegex flag to match against a regex pattern
*/
public function nameFilter(
$tableName,
$tableColumn,
$terms,
&$body,
&$params,
$useRegex = false,
$logicalOperator = 'OR'
) {
$i = 0;
$tableAndColumn = $tableName . '.' . $tableColumn;
// filter empty array elements, in an attempt to better handle spaces after `,`.
$filteredNames = array_filter($terms, function ($element) {
return is_string($element) && '' !== trim($element);
});
foreach ($filteredNames as $searchName) {
// Trim/Sanitise
$searchName = trim($searchName);
// Discard any incompatible
if (empty(ltrim($searchName, '-')) || empty($searchName)) {
continue;
}
// Validate the logical operator
if (!in_array($logicalOperator, ['AND', 'OR'])) {
$this->getLog()->error('Invalid logical operator ' . $logicalOperator);
return;
}
// increase here, after we expect additional sql to be added.
$i++;
// Not like, or like?
if (str_starts_with($searchName, '-')) {
if ($i === 1) {
$body .= ' AND ( '.$tableAndColumn.' NOT RLIKE (:search'.$i.') ';
} else {
$body .= ' ' . $logicalOperator . ' '.$tableAndColumn.' NOT RLIKE (:search'.$i.') ';
}
$params['search' . $i] = $useRegex ? ltrim(($searchName), '-') : preg_quote(ltrim(($searchName), '-'));
} else {
if ($i === 1) {
$body .= ' AND ( '.$tableAndColumn.' RLIKE (:search'.$i.') ';
} else {
$body .= ' ' . $logicalOperator . ' '.$tableAndColumn.' RLIKE (:search'.$i.') ';
}
$params['search' . $i] = $useRegex ? $searchName : preg_quote($searchName);
}
}
// append closing parenthesis only if we added any sql.
if (!empty($filteredNames) && $i > 0) {
$body .= ' ) ';
}
}
/**
* @param array $tags An array of tags
* @param string $lkTagTable name of the lktag table
* @param string $lkTagTableIdColumn name of the id column in the lktag table
* @param string $idColumn name of the id column in main table
* @param string $logicalOperator AND or OR logical operator passed from Factory
* @param string $operator exactTags passed from factory, determines if the search is LIKE or =
* @param string $body Current SQL body passed by reference
* @param array $params Array of parameters passed by reference
*/
public function tagFilter(
$tags,
$lkTagTable,
$lkTagTableIdColumn,
$idColumn,
$logicalOperator,
$operator,
$notTags,
&$body,
&$params
) {
$i = 0;
$paramName = ($notTags) ? 'notTags' : 'tags';
$paramValueName = ($notTags) ? 'notValue' : 'value';
foreach ($tags as $tag) {
$i++;
$tagV = explode('|', $tag);
// search tag without value
if (!isset($tagV[1])) {
if ($i == 1) {
$body .= ' WHERE `tag` ' . $operator . ' :'. $paramName . $i;
} else {
$body .= ' OR ' . ' `tag` ' . $operator . ' :' . $paramName . $i;
}
if ($operator === '=') {
$params[$paramName . $i] = $tag;
} else {
$params[$paramName . $i] = '%' . $tag . '%';
}
// search tag only by value
} elseif ($tagV[0] == '') {
if ($i == 1) {
$body .= ' WHERE `value` ' . $operator . ' :' . $paramValueName . $i;
} else {
$body .= ' OR ' . ' `value` ' . $operator . ' :' . $paramValueName . $i;
}
if ($operator === '=') {
$params[$paramValueName . $i] = $tagV[1];
} else {
$params[$paramValueName . $i] = '%' . $tagV[1] . '%';
}
// search tag by both tag and value
} else {
if ($i == 1) {
$body .= ' WHERE `tag` ' . $operator . ' :' . $paramName . $i .
' AND value ' . $operator . ' :' . $paramValueName . $i;
} else {
$body .= ' OR ' . ' `tag` ' . $operator . ' :' . $paramName . $i .
' AND value ' . $operator . ' :' . $paramValueName . $i;
}
if ($operator === '=') {
$params[$paramName . $i] = $tagV[0];
$params[$paramValueName . $i] = $tagV[1];
} else {
$params[$paramName . $i] = '%' . $tagV[0] . '%';
$params[$paramValueName . $i] = '%' . $tagV[1] . '%';
}
}
}
if ($logicalOperator === 'AND' && count($tags) > 1 && !$notTags) {
$body .= ' GROUP BY ' . $lkTagTable . '.' . $idColumn . ' HAVING count(' . $lkTagTable .'.'. $lkTagTableIdColumn .') = ' . count($tags);//@phpcs:ignore
}
$body .= ' ) ';
}
}