Newer
Older
<?php
/**
* This file contains \QUI\Bricks\Manager
*/
namespace QUI\Bricks;
use QUI;
use QUI\Projects\Project;
use QUI\Projects\Site;
use QUI\Utils\Text\XML;
/**
* Brick Manager
*
* @package quiqqer/bricks
*/
class Manager
{
/**
* Bricks table name
*/
const TABLE = 'bricks';
/**
* Bricks uid table name
*/
const TABLE_UID = 'bricksUID';
/**
* Brick Cache table name
*/
const TABLE_CACHE = 'bricksCache';
/**
* Brick temp collector
*
* @var array
*/
protected array $bricks = [];
/**
* Brick UID temp collector
*
* @var array
*/
protected array $brickUIDs = [];
* @var null|Manager
public static ?Manager $BrickManager = null;
/**
* Return the global QUI\Bricks\Manager
*
* @return Manager
*/
public static function init(): ?Manager
self::$BrickManager = new Manager(true);
}
/**
* Constructor
* Please use \QUI\Bricks\Manager::init()
*
* @param boolean $init - please use \QUI\Bricks\Manager::init()
*/
public function __construct($init = false)
{
if ($init === false) {
QUI\System\Log::addWarning('Please use \QUI\Bricks\Manager::init()');
}
}
/**
* Returns the bricks table name
*
* @return String
*/
public static function getTable(): string
{
return QUI::getDBTableName(self::TABLE);
}
/**
* @return string
*/
public static function getUIDTable(): string
{
return QUI::getDBTableName(self::TABLE_UID);
}
/**
* Return the long time cache namespace
*
* @return string
*/
public static function getBrickCacheNamespace(): string
{
return 'quiqqer/package/quiqqer/bricks/';
}
/**
* Creates a new brick for the project
*
* @param Project $Project
* @param Brick $Brick
*
* @return integer - Brick-ID
public function createBrickForProject(Project $Project, Brick $Brick): int
QUI\Permissions\Permission::checkPermission('quiqqer.bricks.create');
QUI::getDataBase()->insert(
$this->getTable(),
'project' => $Project->getName(),
'lang' => $Project->getLang(),
'title' => $Brick->getAttribute('title'),
'description' => $Brick->getAttribute('description'),
'type' => $Brick->getAttribute('type')
return QUI::getPDO()->lastInsertId();

Henning Leutz
committed
/**
* Create and update a unique site brick
*
* @param Site $Site
* @param array $brickData
* @return string - Unique ID

Henning Leutz
committed
*/
public function createUniqueSiteBrick(Site $Site, $brickData = [])

Henning Leutz
committed
{

Henning Leutz
committed
$uid = $brickData['uid'];
if ($this->existsUniqueBrickId($uid) === false) {
$uid = $this->createUniqueBrickId((int)$brickData['brickId'], $Site);
}
} else {
$uid = $this->createUniqueBrickId((int)$brickData['brickId'], $Site);
}
$customFields = [];

Henning Leutz
committed
if (isset($brickData['customfields'])) {
$customFields = $brickData['customfields'];
}
if (\is_array($customFields)) {
$customFields = \json_encode($customFields);

Henning Leutz
committed
}
QUI::getDataBase()->update($this->getUIDTable(), [

Henning Leutz
committed
'customfields' => $customFields
], [

Henning Leutz
committed
'uid' => $uid
]);

Henning Leutz
committed
return $uid;
}
/**
* Create a new unique Brick ID
*
* @param integer $brickId - Brick ID
* @param Site $Site - Current Site
* @return bool

Henning Leutz
committed
*/
protected function createUniqueBrickId(int $brickId, Site $Site): bool

Henning Leutz
committed
{
$Project = $Site->getProject();

Henning Leutz
committed
$Brick = $this->getBrickById($brickId);
QUI::getDataBase()->insert($this->getUIDTable(), [
'uid' => $uuid,
'brickId' => $brickId,
'project' => $Project->getName(),
'lang' => $Project->getLang(),
'siteId' => $Site->getId(),
'attributes' => \json_encode($Brick->getAttributes())
]);

Henning Leutz
committed

Henning Leutz
committed
}
/**
* Check if an unique brick ID exists
*
* @param string $uid - Brick Unique ID
* @return bool
*/
public function existsUniqueBrickId(string $uid): bool

Henning Leutz
committed
{
try {
$result = QUI::getDataBase()->fetch([
'from' => $this->getUIDTable(),
'where' => [
'uid' => $uid
],
'limit' => 1
]);
} catch (QUI\DataBase\Exception $Exception) {
QUI\System\Log::addError($Exception->getMessage());
return false;
}

Henning Leutz
committed
return isset($result[0]);
}
/**
* CLears the bricks cache
*/
public function clearCache()
{
QUI\Cache\Manager::clear('quiqqer/bricks');
}
/**
* Delete the brick
*
QUI\Permissions\Permission::checkPermission('quiqqer.bricks.delete');

Henning Leutz
committed
$Brick = $this->getBrickById($brickId);
QUI::getDataBase()->delete($this->getTable(), [
]);

Henning Leutz
committed
if (isset($this->bricks[$brickId])) {
unset($this->bricks[$brickId]);
}
$uniqueBrickIds = QUI::getDataBase()->fetch([

Henning Leutz
committed
'select' => 'siteId, project, lang',
'from' => QUI\Bricks\Manager::getUIDTable(),
'where' => [

Henning Leutz
committed
'brickId' => $brickId,
'project' => $Brick->getAttribute('project'),
'lang' => $Brick->getAttribute('lang')

Henning Leutz
committed
'group' => 'siteId, project, lang'
]);

Henning Leutz
committed
// delete bricks in sites
foreach ($uniqueBrickIds as $uniqueBrickId) {
$project = $uniqueBrickId['project'];
$lang = $uniqueBrickId['lang'];
$Project = QUI::getProject($project, $lang);
$Site = $Project->get($uniqueBrickId['siteId']);
$Edit = $Site->getEdit();
$Edit->load();
$Edit->save(QUI::getUsers()->getSystemUser());
}
// delete unique ids
QUI::getDataBase()->delete(QUI\Bricks\Manager::getUIDTable(), [

Henning Leutz
committed
'brickId' => $brickId,
'project' => $Brick->getAttribute('project'),
'lang' => $Brick->getAttribute('lang')
]);
}
/**
* Return the areas which are available in the project
*
* @param Project $Project
* @param string|boolean $layoutType - optional, returns only the areas
* for the specific layout type
* (default = false)
* @return array
*/
public function getAreasByProject(Project $Project, $layoutType = false)
{
$templates = [];
$bricks = [];
$projectName = $Project->getName();
if ($Project->getAttribute('template')) {
$templates[] = $Project->getAttribute('template');
}
// inheritance
try {
$Package = QUI::getPackage($Project->getAttribute('template'));
$Parent = $Package->getTemplateParent();
if ($Parent) {
$templates[] = $Parent->getName();
}
} catch (QUI\Exception $Exception) {
}
// get all vhosts, and the used templates of the project
$vhosts = QUI::getRewrite()->getVHosts();
foreach ($vhosts as $vhost) {
if (!isset($vhost['template'])) {
continue;
}
if ($vhost['project'] != $projectName) {
continue;
}
$templates[] = $vhost['template'];
}
$templates = \array_unique($templates);
// get bricks
foreach ($templates as $template) {
$brickXML = \realpath(OPT_DIR.$template.'/bricks.xml');
$bricks = \array_merge(
$bricks,
Utils::getTemplateAreasFromXML($brickXML, $layoutType)
);
}
// unique values
$cleaned = [];
foreach ($bricks as $val) {
if (!isset($cleaned[$val['name']])) {
$cleaned[$val['name']] = $val;
}
}
$bricks = \array_values($cleaned);
@\usort($bricks, function ($a, $b) {
if (isset($a['priority']) && isset($b['priority'])) {
if ($a['priority'] == $b['priority']) {
return 0;
}
return ($a['priority'] < $b['priority']) ? -1 : 1;
}
$transA = QUI::getLocale()->get(
$a['title']['group'],
$a['title']['var']
);
$transB = QUI::getLocale()->get(
$b['title']['group'],
$b['title']['var']
);
return $transA > $transB ? 1 : -1;
});
try {
QUI::getEvents()->fireEvent(
'onBricksGetAreaByProject',
[$this, $Project, &$bricks]
);
} catch (QUI\Exception $Exception) {
QUI\System\Log::writeException($Exception);
}
return $bricks;
}
/**
* Returns the available bricks
*
* @return array
*/
public function getAvailableBricks(): array
{
$cache = 'quiqqer/bricks/availableBricks';
try {
return QUI\Cache\Manager::get($cache);
} catch (QUI\Exception $Exception) {
}
$xmlFiles = $this->getBricksXMLFiles();
$result = [];
$result[] = [
'title' => ['quiqqer/bricks', 'brick.content.title'],
'description' => [
'control' => 'content'
$result = \array_merge($result, Utils::getBricksFromXML($bricksXML));
$result = \array_filter($result, function ($brick) {
return !empty($brick['title']);
});
// js workaround
$list = [];
foreach ($result as $entry) {
$list[] = $entry;
}
try {
QUI\Cache\Manager::set($cache, $list);
} catch (\Exception $Exception) {
QUI\System\Log::writeException($Exception);
}
}
/**
* Get a Brick by its Brick-ID
*
*
* @return Brick
* @throws QUI\Exception
*/
public function getBrickById($id): Brick
$data = QUI::getDataBase()->fetch([
'from' => $this->getTable(),
'where' => [
]);
if (!isset($data[0])) {
throw new QUI\Exception('Brick not found');
}
$Brick = new Brick($data[0]);
$Brick->setAttribute('id', $id);
$this->bricks[$id] = $Brick;
/**
* Get a Brick by its unique ID
*
* @param string $uid - unique id
*
* @return Brick
* @throws QUI\Exception
*/
public function getBrickByUID($uid)
{
if (isset($this->brickUIDs[$uid])) {
return $this->brickUIDs[$uid];
}
$data = QUI::getDataBase()->fetch([

Henning Leutz
committed
'from' => $this->getUIDTable(),
'where' => [

Henning Leutz
committed
'uid' => $uid
]);
if (!isset($data[0])) {
throw new QUI\Exception('Brick not found');
}

Henning Leutz
committed
$data = $data[0];
$brickId = $data['brickId'];
$custom = $data['customfields'];
$attributes = $data['attributes'];
$attributes = \json_decode($attributes, true);

Henning Leutz
committed
$real = QUI::getDataBase()->fetch([
'where' => [
]);

Henning Leutz
committed
$Original->setAttribute('id', $brickId);
$Clone = clone $Original;
if (!empty($custom)) {
$custom = \json_decode($custom, true);

Henning Leutz
committed
if ($custom) {
$Clone->setSettings($custom);
}
// workaround
if (isset($custom['brickTitle'])) {
$Clone->setAttribute('frontendTitle', $custom['brickTitle']);
}
}

Henning Leutz
committed
$this->brickUIDs[$uid] = $Clone;

Henning Leutz
committed
return $Clone;
/**
* Return the available brick settings by the brick type
*
* @param $brickType
*
* @return array
*/
public function getAvailableBrickSettingsByBrickType($brickType): array
$cache = 'quiqqer/bricks/brickType/'.\md5($brickType);
try {
return QUI\Cache\Manager::get($cache);
} catch (QUI\Exception $Exception) {
}
$settings = [];
$settings[] = [
'name' => 'width',
'text' => ['quiqqer/bricks', 'site.area.window.settings.setting.width'],
'type' => '',
'class' => '',
'options' => false
$settings[] = [
'name' => 'height',
'text' => ['quiqqer/bricks', 'site.area.window.settings.setting.height'],
'type' => '',
'class' => '',
'options' => false
$settings[] = [
'name' => 'classes',
'text' => ['quiqqer/bricks', 'site.area.window.settings.setting.classes'],
'type' => '',
'class' => '',
'options' => false
$xmlFiles = $this->getBricksXMLFiles();
foreach ($xmlFiles as $brickXML) {
$Dom = XML::getDomFromXml($brickXML);
$Path = new \DOMXPath($Dom);
$Settings = $Path->query(
"//quiqqer/bricks/brick[@control='{$brickType}']/settings/setting"
);
$Globals = $Path->query(
"//quiqqer/bricks/brick[@control='*']/settings/setting"
);
foreach ($Globals as $Setting) {
$settings[] = $this->parseSettingToBrickArray($Setting);
$settings[] = $this->parseSettingToBrickArray($Setting);
}
}
// cleanup duplicated
// quiqqer/package-bricks#90
$exists = [];
$settings = \array_filter($settings, function ($entry) use (&$exists) {
$name = $entry['name'];
if (isset($exists[$name])) {
return false;
}
$exists[$name] = true;
return true;
});

Michael Danielczok
committed
$settings = \array_values($settings);
try {
QUI\Cache\Manager::set($cache, $settings);
} catch (\Exception $Exception) {
QUI\System\Log::writeException($Exception);
}
/**
* Parse a xml setting element to a brick array
*
* @param \DOMElement $Setting
* @return array
*/
protected function parseSettingToBrickArray(\DOMElement $Setting): array
{
/* @var $Option \DOMElement */
$options = false;
if ($Setting->getAttribute('type') == 'select') {
$optionElements = $Setting->getElementsByTagName('option');
foreach ($optionElements as $Option) {
$options[] = [
'value' => $Option->getAttribute('value'),
'text' => QUI\Utils\DOM::getTextFromNode($Option, false)

Michael Danielczok
committed
$dataAttributes = [];

Michael Danielczok
committed
foreach ($Setting->attributes as $attribute) {
if ($attribute->nodeName === 'data-qui') {
continue;
}
if (\strpos($attribute->nodeName, 'data-') !== false) {
$dataAttributes[$attribute->nodeName] = \trim($attribute->nodeValue);

Michael Danielczok
committed
}
}
$Description = $Setting->getElementsByTagName('description');
if ($Description->length) {
$description = QUI\Utils\DOM::getTextFromNode($Description->item(0), false);
}
return [

Michael Danielczok
committed
'name' => $Setting->getAttribute('name'),
'text' => QUI\Utils\DOM::getTextFromNode($Setting, false),

Michael Danielczok
committed
'type' => $Setting->getAttribute('type'),
'class' => $Setting->getAttribute('class'),
'data-qui' => $Setting->getAttribute('data-qui'),
'options' => $options,
'data-attributes' => $dataAttributes
}
/**
* Return the bricks from the area
*
* @param string $brickArea - Name of the area
* @param QUI\Interfaces\Projects\Site $Site
public function getBricksByArea($brickArea, QUI\Interfaces\Projects\Site $Site): array
return [];
}
$brickAreas = $Site->getAttribute('quiqqer.bricks.areas');
if (!\is_array($brickAreas)) {
$brickAreas = \json_decode($brickAreas, true);
}
if (!isset($brickAreas[$brickArea]) || empty($brickAreas[$brickArea])) {
$bricks = $this->getInheritedBricks($brickArea, $Site);
} else {
$bricks = [];
$brickData = $brickAreas[$brickArea];
foreach ($brickData as $brick) {
if (isset($brick['deactivate'])) {
break;
}
$bricks[] = $brick;
}
}
$result = [];
QUI::getEvents()->fireEvent(
'onQuiqqerBricksGetBricksByAreaBegin',
[$brickArea, $Site, &$result]
);

Henning Leutz
committed
foreach ($bricks as $key => $brickData) {
$brickId = (int)$brickData['brickId'];
try {
if (!empty($brickData['uid'])) {

Henning Leutz
committed
$Brick = $this->getBrickByUID($brickData['uid']);
$result[] = $Brick->check();
continue;
}
if (!$brickId) {
continue;
}

Henning Leutz
committed
// fallback
$Clone = clone $Brick;
if (isset($brickData['customfields']) && !empty($brickData['customfields'])) {
$custom = \json_decode($brickData['customfields'], true);
$Clone->setSettings($custom);
$result[] = $Clone->check();

Henning Leutz
committed
QUI\System\Log::writeRecursive($brickData);
QUI\System\Log::writeException($Exception);
QUI::getEvents()->fireEvent(
'onQuiqqerBricksGetBricksByAreaEnd',
[$brickArea, $Site, &$result]
);
return $result;
}
/**
* Return a list with \QUI\Bricks\Brick which are assigned to a project
*
* @param Project $Project
* @return array
public function getBricksFromProject(Project $Project): array
$result = [];
$list = QUI::getDataBase()->fetch([
'from' => $this->getTable(),
'where' => [
'lang' => $Project->getLang()
]
]);
foreach ($list as $entry) {
$result[] = $this->getBrickById($entry['id']);
}
return $result;
}
/**
* Return a list with \QUI\Bricks\Brick which are assigned to a project
*
* @param Brick $Brick
* @return array
*/
public function getSitesByBrick(Brick $Brick): array
try {
$list = QUI::getDataBase()->fetch([
'select' => ['brickId', 'project', 'lang', 'siteId'],
'from' => $this->getUIDTable(),
'where' => [
'project' => $Brick->getAttribute('project'),
'lang' => $Brick->getAttribute('lang'),
'brickId' => $Brick->getAttribute('id')
]
]);
$Project = QUI::getProject(
$Brick->getAttribute('project'),
$Brick->getAttribute('lang')
);
} catch (QUI\Exception $Exception) {
QUI\System\Log::addError($Exception->getMessage());
return [];
}
$result = [];
foreach ($list as $entry) {
try {
$result[] = $Project->get($entry['siteId']);
} catch (QUI\Exception $Exception) {
QUI\System\Log::writeDebugException($Exception);
continue;
}
}
return $result;
}
/**
* @param string|integer $brickId - Brick-ID
* @param array $brickData - Brick data
*/
public function saveBrick($brickId, array $brickData)
{
QUI\Permissions\Permission::checkPermission('quiqqer.bricks.edit');
$areas = [];
$areaString = '';
if (isset($brickData['id'])) {
unset($brickData['id']);
}
// check areas
$Project = QUI::getProjectManager()->getProject(
$Brick->getAttribute('project')
);
$availableAreas = \array_map(function ($data) {
if (isset($data['name'])) {
return $data['name'];
}
return '';
}, $this->getAreasByProject($Project));
if (isset($brickData['attributes']) && isset($brickData['attributes']['areas'])) {
$brickData['areas'] = $brickData['attributes']['areas'];
}
if (isset($brickData['areas'])) {
$parts = \explode(',', $brickData['areas']);
if (\in_array($area, $availableAreas)) {
$areas[] = $area;
}
}
}
if (!empty($areas)) {
$areaString = ','.\implode(',', $areas).',';
}
$Brick->setAttributes($brickData);
// fields
if (isset($brickData['attributes']) && \is_array($brickData['attributes'])) {
foreach ($brickData['attributes'] as $key => $value) {
if ($key == 'areas') {
continue;
}
$Brick->setAttribute($key, $value);
}
}
// brick settings
if (isset($brickData['settings'])) {
$Brick->setSettings($brickData['settings']);
}
$brickAttributes = Utils::getAttributesForBrick($Brick);
foreach ($brickAttributes as $attribute) {
if (isset($brickData['attributes'][$attribute])) {
$Brick->setSetting($attribute, $brickData['attributes'][$attribute]);
}
}
$customfields = [];
if (isset($brickData['customfields']) && \is_array($brickData['customfields'])) {
$availableSettings = $Brick->getSettings();
$availableSettings['width'] = true;
$availableSettings['height'] = true;
foreach ($brickData['customfields'] as $customfield) {
$customfield = \str_replace('flexible-', '', $customfield);
if ($customfield == 'classes') {
$customfields[] = $customfield;
continue;
}
if (isset($availableSettings[$customfield])) {
$customfields[] = $customfield;
}
}
}
$type = $Brick->getAttribute('type');
$checkType = function ($type) {
if ($type === 'content') {
return true;
}
if (\is_callable($type)) {
return true;
}
if (\class_exists($type)) {
return true;
}
throw new QUI\Exception(
'quiqqer/bricks',
'exception.type.is.not.allowed'
);
};
$checkType($type);

Henning Leutz
committed
// check duplicated titles
$result = QUI::getDataBase()->fetch([
'from' => $this->getTable(),
'where' => [
'title' => $Brick->getAttribute('title'),
'project' => $Brick->getAttribute('project'),
'lang' => $Brick->getAttribute('lang'),
'id' => [

Henning Leutz
committed
'type' => 'NOT',
'value' => (int)$brickId
]
],
'limit' => 1
]);
if (isset($result[0])) {
throw new QUI\Exception([
'quiqqer/bricks',

Michael Danielczok
committed
'exception.brick.title.already.exists',
['brickTitle' => $Brick->getAttribute('title')]

Henning Leutz
committed
]);
}
QUI::getDataBase()->update($this->getTable(), [