src/base/InternalBaseBlock.php
<?php
namespace luya\cms\base;
use luya\admin\base\TypesInterface;
use luya\cms\frontend\blockgroups\MainGroup;
use luya\helpers\ArrayHelper;
use luya\helpers\FileHelper;
use luya\helpers\Html;
use luya\helpers\Url;
use yii\base\BaseObject;
use yii\helpers\Inflector;
/**
* Concret Block implementation based on BlockInterface.
*
* This is an use case for the block implemenation as InternBaseBlock fro
* two froms of implementations.
*
* + {{\luya\cms\base\PhpBlock}}
*
* @author Basil Suter <basil@nadar.io>
* @since 1.0.0
*/
abstract class InternalBaseBlock extends BaseObject implements BlockInterface, TypesInterface, \ArrayAccess
{
/**
* @var string Defines the injector config type `var`.
*/
public const INJECTOR_VAR = 'var';
/**
* @var string Defines the injector config type `cfg`.
*/
public const INJECTOR_CFG = 'cfg';
/**
* @var bool Enable or disable the block caching
*/
public $cacheEnabled = false;
/**
* @var int The cache lifetime for this block in seconds (3600 = 1 hour), only affects when cacheEnabled is true. 0 means never expire.
*/
public $cacheExpiration = 3600;
/**
* @var bool Choose whether block is a layout/container/segmnet/section block or not, Container elements will be optically displayed
* in a different way for a better user experience. Container block will not display isDirty colorizing.
*/
public $isContainer = false;
/**
* @var string Containing the name of the environment (used to find the view files to render). The
* module(Name) can be started with the Yii::getAlias() prefix `@`, otherwhise the `@` will be
* added automatically. Since version 3.1.0 its possible to set `null` or empty string in order to lookup
* view files in the same folder where the block is located. With version 4.0 null is the default value.
*
* - `app`: The alias mode allows you to map the view files to a certain alias
* - `@app`: Either alias with prefixed @ or not is possible
* - `null`: Empty or null will lookup the view files in the same folder where block is located (sub folder views).
*/
public $module = null;
/**
* Returns the configuration array.
*
* An array with either `var`, `cfg` or `placeholder`. An example with vars
* with a required text input:
*
* ```php
* return [
* 'vars' => [
* [
* 'var' => 'userInputText',
* 'label' => 'Description of userInputText',
* 'type' => self::TYPE_TEXT,
* 'required' => true,
* ]
* ]
* ];
* ```
*
* @see [[app-block-types.md]]
* @return array
*/
abstract public function config();
/**
* {@inheritDoc}
*/
public function setup()
{
}
private $_injectorObjects;
/**
* Setup injectors.
*/
protected function injectorSetup()
{
if ($this->_injectorObjects === null) {
foreach ($this->injectors() as $varName => $injector) {
$injector->setContext($this);
$injector->varName = $varName;
$injector->setup();
$this->_injectorObjects[$injector->varName] = $injector;
}
}
}
/**
* {@inheritDoc}
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
$this->_injectorObjects[$offset] = $value;
}
/**
* {@inheritDoc}
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return isset($this->_injectorObjects[$offset]);
}
/**
* {@inheritDoc}
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
unset($this->_injectorObjects[$offset]);
}
/**
* Array Access Get
*
* @param string $offset The name of the registered Injector name.
* @return \luya\cms\base\BaseBlockInjector
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->_injectorObjects[$offset] ?? null;
}
/**
* @inheritdoc
*/
public function getIsCacheEnabled()
{
return $this->cacheEnabled;
}
/**
* @inheritdoc
*/
public function getCacheExpirationTime()
{
return $this->cacheExpiration;
}
/**
* @inheritdoc
*/
public function getIsDirtyDialogEnabled()
{
return true;
}
/**
* @inheritdoc
*/
public function getIsContainer()
{
return $this->isContainer;
}
/**
* Contains the class name for the block group class
*
* @return string The classname on which the block should be stored in.
* @since 1.0.0
*/
public function blockGroup()
{
return MainGroup::class;
}
/**
* Injectors are like huge helper objects which are going to automate functions, configs and variable assignement.
*
* An example of an Injector which builds a select dropdown and assigns the active query data into the extra vars `foobar`.
*
* ```php
* public function injectors()
* {
* return [
* 'foobar' => new cms\injector\ActiveQueryCheckboxInjector([
* 'query' => MyModel::find()->where(['id' => 1]),
* 'type' => self::INJECTOR_VAR, // could be self::INJECTOR_CFG,
* 'varLabel' => 'The Field Label',
* ]);
* ];
* }
* ```
*
* Now the generated injector ActiveQueryCheckbox is going to grab all informations from the defined query and assign
* them into the extra var foobar. Now you can access `$this->extraValue('foobar')` which returns all seleced rows from the checkbox
* you have assigend.
*
* In order to access the injectors object api you can use the ArrayAccess getter method like `$this['foobar']` and you can access the public
* method for this Injector.
*/
public function injectors()
{
return [];
}
/**
* Return link for usage in ajax request, the link will call the defined callback inside
* this block. All callback methods must start with `callback`. An example for a callback method:.
*
* ```php
* public function callbackTestAjax($arg1)
* {
* return 'hello callback test ajax with argument: arg1 ' . $arg1;
* }
* ```
*
* The above defined callback link can be created with the follow code:
*
* ```php
* $this->createAjaxLink('TestAjax', ['arg1' => 'My Value for Arg1']);
* ```
*
* The most convient way to assign the variable is via extraVars
*
* ```php
* public function extraVars()
* {
* return [
* 'ajaxLinkToTestAjax' => $this->createAjaxLink('TestAjax', ['arg1' => 'Value for Arg1']),
* ];
* }
* ```
*
* @param string $callbackName The callback name in uppercamelcase to call. The method must exists in the block class.
* @param array $params A list of parameters who have to match the argument list in the method.
*
* @return string
*/
public function createAjaxLink($callbackName, array $params = [])
{
$params['callback'] = Inflector::camel2id($callbackName);
$params['id'] = $this->getEnvOption('id', 0);
return Url::toAjax('cms/block/index', $params);
}
/**
* Contains the icon
*/
public function icon()
{
return;
}
/**
* Returns true if block is active in backend.
*
* @return bool
*/
public function isAdminContext()
{
return ($this->getEnvOption('context', false) === 'admin') ? true : false;
}
/**
* Returns true if block is active in frontend.
*
* @return bool
*/
public function isFrontendContext()
{
return ($this->getEnvOption('context', false) === 'frontend') ? true : false;
}
private array $_envOptions = [];
/**
* Sets a key => value pair in env options.
*
* @param string $key The string to be set as key
* @param mixed $value The value that will be stored associated with the given key
*/
public function setEnvOption($key, $value)
{
$this->_envOptions[$key] = $value;
}
/**
* Returns all environment/context informations where the block have been placed.
*
* @see {{PhpBlockView::env()}} for all possible values.
* @return array Returns an array with key value parings.
*/
public function getEnvOptions()
{
return $this->_envOptions;
}
/**
* Get a env option by $key. If $key does not exist it will return given $default or false.
*
* @param $key
*
* @return mixed
*/
public function getEnvOption($key, mixed $default = false)
{
return (array_key_exists($key, $this->_envOptions)) ? $this->_envOptions[$key] : $default;
}
private array $_placeholderValues = [];
/**
* @inheritdoc
*/
public function setPlaceholderValues(array $placeholders)
{
$this->_placeholderValues = $placeholders;
}
/**
* @inheritdoc
*/
public function getPlaceholderValues()
{
return $this->_placeholderValues;
}
/**
*
* @param unknown $placholder
* @return boolean
*/
public function getPlaceholderValue($placholder)
{
return $this->getPlaceholderValues()[$placholder] ?? false;
}
private array $_varValues = [];
/**
* @inheritdoc
*/
public function setVarValues(array $values)
{
foreach ($values as $key => $value) {
$this->_varValues[$key] = $value;
}
}
/**
*
* @return array
*/
public function getVarValues()
{
return $this->_varValues;
}
/**
* Get var value.
*
* If the key does not exist in the array, is an empty string or null the default value will be returned.
*
* @param string $key The name of the key you want to retrieve
* @param mixed $default A default value that will be returned if the key isn't found or empty.
* @return mixed
*/
public function getVarValue($key, mixed $default = false)
{
return (isset($this->_varValues[$key]) && $this->_varValues[$key] != '') ? $this->_varValues[$key] : $default;
}
private array $_cfgValues = [];
/**
* @inheritdoc
*/
public function setCfgValues(array $values)
{
foreach ($values as $key => $value) {
$this->_cfgValues[$key] = $value;
}
}
/**
*
* @return array
*/
public function getCfgValues()
{
return $this->_cfgValues;
}
/**
* Get cfg value.
*
* If the key does not exist in the array, is an empty string or null the default value will be returned.
*
* @param string $key The name of the key you want to retrieve
* @param mixed $default A default value that will be returned if the key isn't found or empty.
* @return mixed
*/
public function getCfgValue($key, mixed $default = false)
{
return (isset($this->_cfgValues[$key]) && $this->_cfgValues[$key] != '') ? $this->_cfgValues[$key] : $default;
}
/**
* Define additional variables.
*
* @return array
*/
public function extraVars()
{
return [];
}
/**
* Add an extra var entry.
*
* If the extra var is defined in extraVars() the key will be overriden.
* @param string $key
* @param mixed $value
*/
public function addExtraVar($key, mixed $value)
{
$this->_extraVars[$key] = $value;
}
private array $_extraVars = [];
/**
* @inheritdoc
*/
public function getExtraVarValues()
{
$this->_extraVars = ArrayHelper::merge($this->extraVars(), $this->_extraVars);
return $this->_extraVars;
}
private bool $_assignExtraVars = false;
/**
*
* @param string $key
* @param string $default
* @return string|mixed
*/
public function getExtraValue($key, $default = false)
{
if (!$this->_assignExtraVars) {
$this->getExtraVarValues();
$this->_assignExtraVars = true;
}
return $this->_extraVars[$key] ?? $default;
}
/**
* Returns an array with additional help informations for specific field (var or cfg).
*
* @return array An array where the key is the cfg/var field var name and the value the helper text.
*/
public function getFieldHelp()
{
return [];
}
private array $_vars = [];
/**
* @inheritdoc
*/
public function getConfigVarsExport()
{
$config = $this->config();
if (isset($config['vars'])) {
foreach ($config['vars'] as $item) {
$iteration = count($this->_vars) + 500;
$this->_vars[$iteration] = (new BlockVar($item))->toArray();
}
}
ksort($this->_vars);
return array_values($this->_vars);
}
/**
* Add a var variable to the config.
*
* @param boolean Whether the variable should be append to the end instead of prepanding.
*/
public function addVar(array $varConfig, $append = false)
{
$count = count($this->_vars);
$iteration = $append ? $count + 1000 : $count;
$this->_vars[$iteration] = (new BlockVar($varConfig))->toArray();
}
/**
* @inheritdoc
*/
public function getConfigPlaceholdersExport()
{
$array = array_key_exists('placeholders', $this->config()) ? $this->config()['placeholders'] : [];
$holders = [];
foreach ($array as $holder) {
if (isset($holder['var'])) {
$holders[] = $holder;
} else {
foreach ($holder as $columnHolder) {
$holders[] = $columnHolder;
}
}
}
return $holders;
}
/**
* @inheritdoc
*/
public function getConfigPlaceholdersByRowsExport()
{
$array = array_key_exists('placeholders', $this->config()) ? $this->config()['placeholders'] : [];
$rows = [];
foreach ($array as $holder) {
if (isset($holder['var'])) {
$holder['cols'] = 12;
$rows[] = [$holder];
} else {
$rows[] = $holder;
}
}
return $rows;
}
private array $_cfgs = [];
/**
* @inheritdoc
*/
public function getConfigCfgsExport()
{
$config = $this->config();
if (isset($config['cfgs'])) {
foreach ($config['cfgs'] as $item) {
$iteration = count($this->_cfgs) + 500;
$this->_cfgs[$iteration] = (new BlockCfg($item))->toArray();
}
}
ksort($this->_cfgs);
return array_values($this->_cfgs);
}
/**
* Add a cfg variable to the config.
*
* @param boolean Whether the variable should be append to the end instead of prepanding.
*/
public function addCfg(array $cfgConfig, $append = false)
{
$count = count($this->_cfgs);
$iteration = $append ? $count + 1000 : $count;
$this->_cfgs[$iteration] = (new BlockCfg($cfgConfig))->toArray();
}
/**
* Returns the name of the php file to be rendered.
*
* @return string The name of the php file (example.php)
*/
public function getViewFileName($extension)
{
$className = $this::class;
if (preg_match('/\\\\([\w]+)$/', $className, $matches)) {
$className = $matches[1];
}
return $className.'.'.$extension;
}
/**
* Make sure the module contains its alias prefix @
*
* @return string The module name with alias prefix @.
*/
protected function ensureModule()
{
$moduleName = $this->module;
if (!str_starts_with($moduleName, '@')) {
$moduleName = '@'.$moduleName;
}
return $moduleName;
}
/**
* Configure Variations.
*
* ```php
* TextBlock::variations()
* ->add('bold', 'Bold Font with Markdown')->cfgs(['cssClass' => 'bold-font-class'])->vars(['textType' => 1])
* ->add('italic', 'Italic Font')->cfgs(['cssClass' => 'italic-font-class'])
* ->register(),
* VideoBlock::variations()
* ->add('bold', 'Bold Videos')->cfgs([])->register(),
* ```
*
* @return \luya\cms\base\BlockVariationRegister
*/
public static function variations()
{
/** @phpstan-ignore-next-line */
return (new BlockVariationRegister(new static()));
}
/**
* @inheritDoc
*/
public function onRegister()
{
}
/**
* @inheritDoc
*/
public function onRegisterFromCache()
{
}
/**
* @inheritDoc
*/
public function renderAdminPreview()
{
$image = $this->getPreviewImageSource();
if ($image) {
return Html::img($image);
}
return false;
}
/**
* Path to the preview image.
* @since 1.0.8
*/
protected function getPreviewImageSource()
{
$imageName = $this->getViewFileName('jpg');
$reflector = new \ReflectionClass($this);
$dirPath = dirname($reflector->getFileName(), 2);
$imagePath = $dirPath . '/images/blocks/' . $imageName;
// file get content resolved Yii aliases.
$data = FileHelper::getFileContent($imagePath);
if ($data) {
return 'data:image/jpg;base64,' . base64_encode($data);
}
return false;
}
/**
* @inheritDoc
*/
public function placeholderRenderIteration(BlockInterface $block)
{
return $block->renderFrontend();
}
}