src/Slack/AbstractBlockFactory.php
<?php
namespace bdk\Slack;
use OverflowException;
use UnexpectedValueException;
/**
* Utilities/Helpers/Assertions for blocks
*/
abstract class AbstractBlockFactory
{
use AssertionTrait;
/** @var array<string,list<string>> */
protected static $validElementTypes = array(
'actions' => [
'button',
'checkboxes',
'datepicker',
'datetimepicker',
'multi_static_select', 'static_select',
'overflow',
'radio_buttons',
'timepicker',
],
'context' => [
'image',
'mrkdwn',
'plain_text',
],
);
/**
* Normalize checkbox, radio, or select options
*
* @param array $options checkbox, radio, overflow, or select block element
* @param int $max Maximum number of allowed options
* @param string $where Name of element options belong to
*
* @return array
*
* @throws OverflowException
* @throws UnexpectedValueException
*/
protected static function normalizeOptions(array $options, $max, $where)
{
/** @psalm-var mixed $option */
foreach ($options as $k => $option) {
$options[$k] = self::normalizeOption($option, $k);
}
$count = \count($options);
if ($count > $max) {
throw new OverflowException(\sprintf('A maximum of %d options are allowed in %s element. %d provided.', $max, $where, $count));
}
return \array_values($options);
}
/**
* Normalize a single option "object"
*
* Overflow, select, and multi-select menus can only use plain_text
* Radio buttons and checkboxes can use mrkdwn text objects.
*
* @param mixed $option option text/value
* @param int|string|null $key The option's array key...
* if a string, it will used as the option's text (label)
*
* @return array<string,mixed>
*
* @throws UnexpectedValueException
*/
private static function normalizeOption($option, $key = null)
{
self::assertValidText($option, 'Option');
if (\is_array($option) === false) {
$option = array(
'text' => $option,
'value' => $option,
);
}
if (\is_string($key)) {
$option['text'] = $key;
}
$isMrkdwn = isset($option['type']) && $option['type'] === 'mrkdwn';
$option['text'] = self::normalizeText($option['text'], 'text', $isMrkdwn);
$option = \array_intersect_key($option, array(
'description' => null,
'text' => null,
'url' => null, // only for overflow,
'value' => null,
));
return self::removeNull($option);
}
/**
* Normalize text object
*
* @param mixed $text Text object value
* @param string $what What are we normalizing (for feedback)
* @param bool $isMrkdwn Is text formatted with markdown?
*
* @return array{emoji?:bool,text:string,type:'mrkdwn'|'plain_text',verbatim?:bool}|null
*
* @throws UnexpectedValueException
*/
protected static function normalizeText($text, $what = 'text', $isMrkdwn = false)
{
self::assertValidText($text, $what);
$default = array(
'emoji' => true, // only applies to plain_text
'text' => '',
'type' => $isMrkdwn ? 'mrkdwn' : 'plain_text',
'verbatim' => false, // only applies to mrkdwn
);
if (\is_array($text) === false) {
$text = array('text' => $text);
}
$text = \array_merge($default, $text);
$text = \array_intersect_key($text, $default);
$text['text'] = (string) $text['text'];
if ($text['text'] === '') {
return null;
}
if ($text['type'] !== 'plain_text') {
unset($text['emoji']);
} elseif ($text['type'] !== 'mrkdwn') {
unset($text['verbatim']);
}
/** @psalm-var array{emoji?:bool, text:string, type:'mrkdwn'|'plain_text', verbatim?:bool} psalm bug - should infer */
return \array_diff_assoc($text, array(
'emoji' => true,
'verbatim' => false,
));
}
/**
* Remove null values from array
*
* @param array<string,mixed> $values Input array
*
* @return array<string,mixed>
*/
protected static function removeNull(array $values)
{
if (isset($values['focus_on_load']) && $values['focus_on_load'] === false) {
unset($values['focus_on_load']);
}
$values = \array_filter($values, static function ($value) {
return $value !== null;
});
\ksort($values);
return $values;
}
/**
* Rename 'default' to block-specific name
*
* @param array{type:string,...} $block block values
*
* @return array{type:string,...}
*/
protected static function renameDefault(array $block)
{
$typeToDefault = array(
'checkboxes' => 'initial_options',
'datepicker' => 'initial_date',
'datetimepicker' => 'initial_date_time',
'multi_static_select' => 'initial_options',
'radio_buttons' => 'initial_option',
'static_select' => 'initial_option',
'timepicker' => 'initial_time',
);
$renameTo = isset($typeToDefault[$block['type']])
? $typeToDefault[$block['type']]
: 'initial_value';
if (isset($block['default'])) {
/** @psalm-var mixed */
$block[$renameTo] = $block['default'];
unset($block['default']);
}
return $block;
}
}