Rework BlockKit to include validation on size and counts

This commit is contained in:
Deon George 2022-01-18 14:34:57 +11:00
parent b20d7e2988
commit b2cd5c7d46
30 changed files with 1147 additions and 1058 deletions

View File

@ -24,7 +24,7 @@ use Slack\Response\User as ResponseUser;
use Slack\Response\Team as ResponseTeam;
use Slack\Response\Test;
class API
final class API
{
private const LOGKEY = 'API';

View File

@ -110,4 +110,4 @@ abstract class Base
return $o;
}
}
}

View File

@ -2,6 +2,7 @@
namespace Slack;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
/**
@ -9,7 +10,7 @@ use Illuminate\Support\Collection;
*
* @package Slack
*/
class BlockKit implements \JsonSerializable
abstract class BlockKit implements \JsonSerializable
{
protected Collection $_data;
@ -18,6 +19,14 @@ class BlockKit implements \JsonSerializable
$this->_data = collect();
}
public function __get($key) {
return $this->_data->get($key);
}
public function __set(string $key,$value) {
return $this->_data->put($key,$value);
}
public function count()
{
return $this->_data->count();
@ -27,4 +36,12 @@ class BlockKit implements \JsonSerializable
{
return $this->_data;
}
}
protected function validate(string $key,$value)
{
if (Arr::get(static::LIMITS,$key) && (strlen($value) > static::LIMITS[$key]))
throw new Exception(sprintf('%s must be %d chars or less for buttons %s',$key,self::LIMITS[$key],get_class($this)));
return $value;
}
}

View File

@ -1,40 +0,0 @@
<?php
namespace Slack\Blockkit;
use Slack\BlockKit;
use Slack\Blockkit\Blocks\TextEmoji;
/**
* This class creates a slack actions used in BlockKit Actions
* @todo Still needed?
*/
class BlockAction extends BlockKit
{
/**
* Add a block button
*
* @param string $text
* @param string $action
* @param string $value
* @param string $style
* @return BlockAction
* @throws \Exception
* @deprecated Move to Blocks/Button?
*/
public function addButton(TextEmoji $text,string $action,string $value,string $style=''): self
{
if ($style AND ! in_array($style,['primary','danger']))
abort('Invalid style: '.$style);
$this->_data->put('type','button');
$this->_data->put('action_id',$action);
$this->_data->put('text',$text);
$this->_data->put('value',$value);
if ($style)
$this->_data->put('style',$style);
return $this;
}
}

View File

@ -2,350 +2,8 @@
namespace Slack\Blockkit;
use Illuminate\Support\Collection;
use Slack\BlockKit;
use Slack\Exceptions\SlackException;
use Slack\Blockkit\Blocks\{Button,Text,TextEmoji};
/**
* Class Blockkit Block
* Represents a list of Blockit blocks
*
* @package Slack\Blockkit
*/
class Blocks extends BlockKit
abstract class Blocks extends BlockKit
{
/**
* Actions
*
* @param array $items
* @return Blocks
*/
private function actions(array $items): self
{
$this->_data->push(array_merge(['type'=>'actions'],$items));
return $this;
}
/**
* Add actions block
*
* @param Collection $elements
* @return Blocks
*/
public function addActionElements(Collection $elements): self
{
$this->actions(['elements'=>$elements]);
return $this;
}
/**
* Add context items
*
* @param Collection $items
* @return Blocks
*/
public function addContextElements(Collection $items): self
{
return $this->context(['elements'=>$items]);
}
/**
* Add a bock divider
*
* @returns Blocks
*/
public function addDivider(): self
{
$this->_data->push(['type'=>'divider']);
return $this;
}
/**
* Add a block header
*
* @param string $text
* @return Blocks
*/
public function addHeader(string $text): self
{
$this->_data->push(['type'=>'header','text'=>TextEmoji::item($text,TRUE)]);
return $this;
}
/**
* @param Collection $options
* @param string $action_id
* @return Collection
* @todo To Check
*/
public function addOverflow(Collection $options,string $action_id): Collection
{
return collect([
'type'=>'overflow',
'options'=>$options,
'action_id'=>$action_id,
]);
}
/**
* Add a section with accessories
*
* @param Text $text
* @param Button $accessory
* @return Blocks
*/
public function addSectionAccessoryButton(Text $text,Button $accessory): self
{
return $this->section([
'text'=>$text,
'accessory'=>$accessory,
]);
}
/**
* @param Text $label
* @param string $action
* @param Collection $options
* @param string|null $default
* @return Blocks
* @deprecated
* @todo Look at addSectionAccessory
*/
public function addSelect(Text $label,string $action,Collection $options,string $default=NULL): self
{
$this->_data->put('type','section');
$this->_data->put('text',$label);
// Accessories
$x = collect();
$x->put('action_id',$action);
$x->put('type','static_select');
$x->put('options',$options->map(function ($item) {
if (is_array($item))
$item = (object)$item;
return [
'text'=>[
'type'=>'plain_text',
'text'=>(string)$item->name,
],
'value'=>(string)($item->value ?: $item->id)
];
}));
if ($default) {
$choice = $options->filter(function($item) use ($default) {
if (is_array($item))
$item = (object)$item;
return ($item->value == $default) ? $item : NULL;
})->filter()->pop();
if ($choice) {
$x->put('initial_option',[
'text'=>TextEmoji::item($choice['name']),
'value'=>(string)$choice['value']
]);
}
}
$this->_data->put('accessory',$x);
return $this;
}
/**
* Add a section with fields
* @param Collection $items
* @return Blocks
*/
public function addSectionFields(Collection $items): self
{
return $this->section(['fields'=>$items]);
}
/**
* Generates a multiselect that queries back to the server for values
*
* @param Text $label
* @param string $action
* @return Blocks
* @todo To Change - and rename addSectionMultiSelectInput()
* @deprecated
*/
public function addMultiSelectInput(Text $label,string $action): self
{
$this->_data->put('type','section');
$this->_data->put('text',$label);
$this->_data->put('accessory',[
'action_id'=>$action,
'type'=>'multi_external_select',
]);
return $this;
}
/**
* Add a section with a multi select list
*
* @param Text $label
* @param string $action
* @param Collection $options
* @param Collection|null $selected
* @param int|null $maximum
* @return Blocks
* @throws \Exception
* @note Slack only allows 100 items
*/
public function addSectionMultiSelectStaticInput(Text $label,string $action,Collection $options,Collection $selected=NULL,int $maximum=NULL): self
{
if ($options->count() > 100)
throw new SlackException('Selection list cannot have more than 100 items.');
$x = collect();
$x->put('action_id',$action);
$x->put('type','multi_static_select');
$x->put('options',$options->transform(function ($item) {
return ['text'=>TextEmoji::item($item->name),'value'=>(string)$item->id];
}));
if ($selected and $selected->count())
$x->put('initial_options',$selected->transform(function ($item) {
return ['text'=>TextEmoji::item($item->name),'value'=>(string)$item->id];
}));
if ($maximum)
$x->put('max_selected_items',$maximum);
return $this->section([
'text' => $label,
'accessory' => $x
]);
}
/**
* Add a section with just text
*
* @param Text $text
* @return Blocks
*/
public function addSectionText(Text $text): self
{
return $this->section(['text'=>$text]);
}
/**
* Add a spacer line
*
* @return $this
*/
public function addSpacer(): self
{
return $this->section(['text'=>Text::item(' ')]);
}
/**
* A context block
*
* @param array $items
* @return Blocks
*/
private function context(array $items): self
{
$this->_data->push(array_merge(['type'=>'context'],$items));
return $this;
}
/**
* Render the input dialog
*
* @param string $label
* @param string $action
* @param int $minlength
* @param string $placeholder
* @param bool $multiline
* @param string $hint
* @param string $initial
* @return $this
* @throws \Exception
* @deprecated - to optimize
*/
protected function input(string $label,string $action,int $minlength,string $placeholder='',bool $multiline=FALSE,string $hint='',string $initial='')
{
$this->_data->put('type','input');
$this->_data->put('element',[
'type'=>'plain_text_input',
'action_id'=>$action,
'placeholder'=>$this->text($placeholder ?: ' ','plain_text'),
'multiline'=>$multiline,
'min_length'=>$minlength,
'initial_value'=>$initial,
]);
$this->_data->put('label',[
'type'=>'plain_text',
'text'=>$label,
'emoji'=>true,
]);
$this->_data->put('optional',$minlength ? FALSE : TRUE);
if ($hint)
$this->_data->put('hint',$this->text($hint,'plain_text'));
return $this;
}
/**
* A section block
*
* @param array $items
* @return Blocks
*/
private function section(array $items): self
{
$this->_data->push(array_merge(['type'=>'section'],$items));
return $this;
}
/**
* Generates a single-line input dialog
*
* @param string $label
* @param string $action
* @param string $placeholder
* @param int $minlength
* @param string $hint
* @param string $initial
* @return Blocks
* @throws \Exception
* @deprecated - to optimize
*/
public function addSingleLineInput(string $label,string $action,string $placeholder='',int $minlength=5,string $hint='',string $initial=''): self
{
return $this->input($label,$action,$minlength,$placeholder,FALSE,$hint,$initial);
}
/**
* Generates a multi-line input dialog
*
* @param string $label
* @param string $action
* @param string $placeholder
* @param int $minlength
* @param string $hint
* @param string $initial
* @return Blocks
* @throws \Exception
* @deprecated - to optimize
*/
public function addMultiLineInput(string $label,string $action,string $placeholder='',int $minlength=20,string $hint='',string $initial=''): self
{
return $this->input($label,$action,$minlength,$placeholder,TRUE,$hint,$initial);
}
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Slack\Blockkit\Blocks;
use \Exception;
use Illuminate\Support\Collection;
use Slack\Blockkit\Blocks;
use Slack\Blockkit\Blocks\Elements\Text;
use Slack\Blockkit\Element;
final class Actions extends Blocks
{
protected const LIMITS = [
'block_id' => 255, // @todo Should be unique for each message
];
private const MAX_ELEMENTS = 5;
/**
* @param Text $text
* @throws Exception
*/
public function __construct()
{
parent::__construct();
// Defaults
$this->type = 'actions';
}
public static function item(): self
{
return new self();
}
/* OPTIONAL ITEMS */
public function block_id(string $string): self
{
$this->block_id = $this->validate('block_id',$string);
return $this;
}
public function elements(Collection $collection): self
{
if (count($collection) > self::MAX_ELEMENTS)
throw new Exception(sprintf('Can only have maximum %d elements',self::MAX_ELEMENTS));
$this->elements = $collection;
return $this;
}
}

View File

@ -1,30 +0,0 @@
<?php
namespace Slack\Blockkit\Blocks;
final class Button
{
public string $type;
public TextEmoji $text;
public string $value;
public string $url;
public string $action_id;
public string $style;
public function __construct(TextEmoji $text,string $value,string $action_id,string $url=NULL,string $style=NULL)
{
$this->type = 'button';
$this->text = $text;
$this->value = $value ?: '-';
$this->action_id = $action_id;
if ($url)
$this->url = $url;
if ($style)
$this->style = $style;
}
public static function item(TextEmoji $text,string $value,string $action_id,string $url=NULL,string $style=NULL): self
{
return new self($text,$value,$action_id,$url,$style);
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Slack\Blockkit\Blocks;
use \Exception;
use Illuminate\Support\Collection;
use Slack\Blockkit\Blocks;
final class Context extends Blocks
{
protected const LIMITS = [
'block_id' => 255, // @todo Should be unique for each message
];
private const MAX_ELEMENTS = 10;
/**
* @param Collection $collection
* @throws Exception
* @todo Collection can only be image or text elements
*/
public function __construct(Collection $collection)
{
parent::__construct();
// Defaults
$this->type = 'context';
if (count($collection) > self::MAX_ELEMENTS)
throw new Exception(sprintf('Can only have maximum %d elements',self::MAX_ELEMENTS));
$this->elements = $collection;
}
public static function item(Collection $collection): self
{
return new self($collection);
}
/* OPTIONAL ITEMS */
public function block_id(string $string): self
{
$this->block_id = $this->validate('block_id',$string);
return $this;
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Slack\Blockkit\Blocks;
use \Exception;
use Slack\Blockkit\Blocks;
use Slack\Blockkit\Blocks\Elements\Text;
final class Divider extends Blocks
{
protected const LIMITS = [
'block_id' => 255, // @todo Should be unique for each message
];
/**
* @param Text|NULL $text
* @throws Exception
*/
public function __construct()
{
parent::__construct();
// Defaults
$this->type = 'divider';
}
public static function item(): self
{
return new self();
}
/* OPTIONAL ITEMS */
public function block_id(string $string): self
{
$this->block_id = $this->validate('block_id',$string);
return $this;
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace Slack\Blockkit\Blocks\Elements;
use \Exception;
use Slack\BlockKit;
use Slack\Blockkit\Element;
final class Button extends Element
{
protected const LIMITS = [
'action_id' => 255,
'text' => 75,
'url' => 3000,
'value' => 2000,
];
public function __construct(Text $text,string $value,string $action_id)
{
parent::__construct();
// Defaults
$this->type = 'button';
if ($text->type != 'plain_text')
throw new Exception(sprintf('Text must be plain_text not %s',$text->type));
if (strlen($text->text) > self::LIMITS['text'])
throw new Exception(sprintf('Text must be %d chars or less',self::LIMITS['text']));
$this->text = $text;
$this->value = $this->validate('value',$value);
$this->action_id = $this->validate('action_id',$action_id);
}
public static function item(Text $text,string $value,string $action_id): self
{
return new self($text,$value,$action_id);
}
/* OPTIONAL ITEMS */
public function confirm(Confirm $confirm): self
{
$this->confirm = $confirm;
return $this;
}
public function style(string $string): self
{
if (! in_array($string,['default','primary','danger']))
throw new Exception(sprintf('Unknown style %s',$string));
$this->style = $string;
return $this;
}
public function url(string $string): self
{
$this->url = $this->validate('url',$string);
return $this;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Slack\Blockkit\Blocks\Elements;
use \Exception;
use Slack\BlockKit;
use Slack\Blockkit\Element;
final class Confirm extends Element
{
public function __construct()
{
abort(500,'Not Implememted');
}
public static function item(): self
{
return new self();
}
}

View File

@ -0,0 +1,97 @@
<?php
namespace Slack\Blockkit\Blocks\Elements;
use \Exception;
use Illuminate\Support\Collection;
use Slack\BlockKit\Blocks;
use Slack\Blockkit\Element;
final class MultiStaticSelect extends Element
{
protected const LIMITS = [
'action_id' => 255,
'placeholder' => 150,
];
private const MAX_OPTIONS = 100;
/**
* @param Text $placeholder
* @param string $action_id
* @param Collection $options
* @throws Exception
* @todo We dont handle option_groups yet.
*/
public function __construct(Text $placeholder,string $action_id,Collection $options)
{
parent::__construct();
// Defaults
$this->type = 'multi_static_select';
if ($placeholder->type != 'plain_text')
throw new Exception(sprintf('Text must be plain_text not %s',$placeholder->type));
if (strlen($placeholder->text) > self::LIMITS['placeholder'])
throw new Exception(sprintf('Text must be %d chars or less',self::LIMITS['placeholder']));
$this->placeholder = $placeholder;
$this->action_id = $this->validate('action_id',$action_id);
if (count($options) > self::MAX_OPTIONS)
throw new Exception(sprintf('Can only have maximum %d options',self::MAX_OPTIONS));
$this->options = $options->transform(function($item) {
return ['text'=>Text::item($item->name,'plain_text'),'value'=>(string)$item->id];
});
}
public static function item(Text $placeholder,string $action_id,Collection $options): self
{
return new self($placeholder,$action_id,$options);
}
/* OPTIONAL ITEMS */
public function confirm(Confirm $confirm): self
{
$this->confirm = $confirm;
return $this;
}
public function focus_on_load(bool $bool): self
{
$this->focus_on_load = $bool ? 'true' : 'false';
return $this;
}
public function initial_options(Collection $collection): self
{
// No initial options.
if (! count($collection))
return $this;
if (count($collection) > self::MAX_OPTIONS)
throw new Exception(sprintf('Can only have maximum %d options',self::MAX_OPTIONS));
$this->initial_options = $collection->transform(function($item) {
return ['text'=>Text::item($item->name,'plain_text'),'value'=>(string)$item->id];
});
return $this;
}
public function max_selected_items(int $int): self
{
if ($int < 1)
throw new Exception('Minimum 1 options must be configured');
$this->max_selected_items = $int;
return $this;
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace Slack\Blockkit\Blocks\Elements;
use \Exception;
use Slack\BlockKit;
use Slack\Blockkit\Element;
/**
* @note Overflow, select, and multi-select menus can only use plain_text objects,
* while radio buttons and checkboxes can use mrkdwn text objects
*/
final class Options extends Element
{
protected const LIMITS = [
'description' => 75,
'text' => 75,
'value' => 75,
'url' => 3000,
];
public function __construct(Text $text,string $value)
{
if (strlen($text->text) > self::LIMITS['text'])
throw new Exception(sprintf('Text must be %d chars or less',self::LIMITS['text']));
$this->text = $text;
$this->value = $this->validate('value',$value);
}
public static function item(Text $text,string $value): self
{
return new self($text,$value);
}
/* OPTIONAL ITEMS */
public function description(Text $text): self
{
if ($text->type != 'plain_text')
throw new Exception(sprintf('Text must be plain_text not %s',$text->type));
if (strlen($text->text) > self::LIMITS['description'])
throw new Exception(sprintf('Text must be %d chars or less',self::LIMITS['description']));
$this->description = $text;
return $this;
}
public function url(string $string): self
{
$this->url = $this->validate('url',$string);
return $this;
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Slack\Blockkit\Blocks\Elements;
use \Exception;
use Slack\BlockKit;
use Slack\Blockkit\Element;
final class Text extends Element
{
private const TYPES = ['mrkdwn','plain_text'];
public function __construct(string $text,string $type)
{
parent::__construct();
if (! in_array($type,self::TYPES))
throw new Exception(sprintf('Type [%s] not valid',$type));
$this->type = $type;
$this->text = $text;
}
public static function item(string $text,string $type='mrkdwn'): self
{
return new self($text,$type);
}
/* OPTIONAL ITEMS */
public function emoji(bool $bool): self
{
if ($x=$this->type != 'plain_text')
throw new Exception(sprintf('Cannnot use emoji when type is [%s]',$x));
$this->emoji = $bool ? 'true' : 'false';
return $this;
}
public function verbatim(bool $bool): self
{
$this->verbatim = $bool ? 'true' : 'false';
return $this;
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Slack\Blockkit\Blocks;
use \Exception;
use Slack\Blockkit\Blocks;
use Slack\Blockkit\Blocks\Elements\Text;
final class Header extends Blocks
{
protected const LIMITS = [
'block_id' => 255, // @todo Should be unique for each message
'text' => 150,
];
/**
* @param Text $text
* @throws Exception
*/
public function __construct(Text $text)
{
parent::__construct();
// Defaults
$this->type = 'header';
if ($text->type != 'plain_text')
throw new Exception(sprintf('Text must be plain_text not %s',$text->type));
if (strlen($text->text) > self::LIMITS['text'])
throw new Exception(sprintf('Text must be %d chars or less',self::LIMITS['text']));
$this->text = $text;
}
public static function item(Text $text): self
{
return new self($text);
}
/* OPTIONAL ITEMS */
public function block_id(string $string): self
{
$this->block_id = $this->validate('block_id',$string);
return $this;
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace Slack\Blockkit\Blocks;
use Slack\Blockkit\Input\Element;
final class Input
{
public string $type;
public Element $element;
public TextEmoji $label;
public function __construct(Element $element,TextEmoji $label)
{
$this->type = 'input';
$this->element = $element;
$this->label = $label;
}
public static function item(Element $element,TextEmoji $label): self
{
return new self($element,$label);
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace Slack\Blockkit\Blocks;
use \Exception;
use Illuminate\Support\Collection;
use Slack\Blockkit\{Blocks,Element};
use Slack\BLockKit\Blocks\Elements\Text;
final class Section extends Blocks
{
protected const LIMITS = [
'block_id' => 255, // @todo Should be unique for each message
'text' => 3000,
];
private const MAX_FIELDS = 10;
private const MAX_FIELDS_TEXT = 2000;
/**
* @param Text|NULL $text not required if fields is provided
* @throws Exception
*/
public function __construct(Text $text=NULL)
{
parent::__construct();
// Defaults
$this->type = 'section';
if ($text) {
if (strlen($text->text) > self::LIMITS['text'])
throw new Exception(sprintf('Text must be %d chars or less',self::LIMITS['text']));
$this->text = $text;
}
}
public static function item(Text $text=NULL): self
{
return new self($text);
}
public function jsonSerialize()
{
if (! $this->text && ! $this->fields)
throw new Exception('Must define text or fields');
return parent::jsonSerialize();
}
/* OPTIONAL ITEMS */
public function accessory(Element $object): self
{
$this->accessory = $object;
return $this;
}
public function block_id(string $string): self
{
$this->block_id = $this->validate('block_id',$string);
return $this;
}
public function fields(Collection $collection): self
{
if (count($collection) > self::MAX_FIELDS)
throw new Exception(sprintf('Can only have maximum %d fields',self::MAX_FIELDS));
if ($collection->map(function($item) { return strlen($item->text); })->max() > self::MAX_FIELDS_TEXT)
throw new Exception(sprintf('The maximum size of text in a field is %d',self::MAX_FIELDS_TEXT));
$this->fields = $collection;
return $this;
}
}

View File

@ -1,20 +0,0 @@
<?php
namespace Slack\Blockkit\Blocks;
final class Text
{
public string $type;
public string $text;
public function __construct(string $text,string $type='mrkdwn')
{
$this->type = $type;
$this->text = $text;
}
public static function item(string $text,string $type='mrkdwn'): self
{
return new self($text,$type);
}
}

View File

@ -1,22 +0,0 @@
<?php
namespace Slack\Blockkit\Blocks;
final class TextEmoji
{
public bool $emoji;
public string $text;
public string $type;
public function __construct(string $text,bool $emoji=TRUE)
{
$this->emoji = $emoji;
$this->text = $text;
$this->type = 'plain_text';
}
public static function item(string $text,bool $emoji=TRUE): self
{
return new self($text,$emoji);
}
}

9
src/Blockkit/Element.php Normal file
View File

@ -0,0 +1,9 @@
<?php
namespace Slack\Blockkit;
use Slack\BlockKit;
abstract class Element extends BlockKit
{
}

View File

@ -2,30 +2,68 @@
namespace Slack\Blockkit;
use \Exception;
use Slack\BlockKit;
use Slack\Blockkit\Blocks\TextEmoji;
use Slack\Blockkit\Blocks\Divider;
use Slack\Blockkit\Blocks\Elements\Text;
use Slack\Blockkit\Blocks\Section;
/**
* This class creates a slack Modal Response
*/
class Modal extends BlockKit
final class Modal extends BlockKit
{
protected const LIMITS = [
'callback_id' => 255,
'close' => 24,
'private_metadata' => 3000,
'text' => 24,
];
private const MAX_BLOCKS = 100;
private $action = NULL;
public function __construct(TextEmoji $title)
public function __construct(Text $title=NULL,string $type='modal')
{
parent::__construct();
$this->_data->put('type','modal');
$this->_data->put('title',$title);
if (! in_array($type,['modal','home']))
throw new Exception(sprintf('Unknown type %s',$type));
if ($type != 'modal' && $title)
throw new Exception(sprintf('Titles are not required for %s',$type));
if ($title) {
if ($title->type != 'plain_text')
throw new Exception(sprintf('Text must be plain_text not %s',$title->type));
if (strlen($title->text) > self::LIMITS['text'])
throw new Exception(sprintf('Text must be %d chars or less',self::LIMITS['text']));
$this->title = $title;
}
$this->type = $type;
$this->blocks = collect();
}
/*
public function action(string $action)
/**
* Add a block to this modal
*
* @param BlockKit $block
* @return $this
*/
public function addBlock(Blocks $block): self
{
$this->action = $action;
$this->blocks->push($block);
if ($x=$this->blocks->count() > self::MAX_BLOCKS)
throw new Exception(sprintf('Modal can only have %d blocks',self::MAX_BLOCKS));
return $this;
}
*/
/**
* The data that will be returned when converted to JSON.
@ -37,18 +75,133 @@ class Modal extends BlockKit
return ['response_action'=>'clear'];
case 'update':
return ['response_action'=>'update','view'=>$this->_data];
return ['response_action'=>'update','view'=>parent::jsonSerialize()];
default:
return $this->_data;
return parent::jsonSerialize();
}
}
/* OPTIONAL ITEMS */
public function action(string $string)
{
if (! in_array($string,['clear','update']))
throw new Exception(sprintf('Unknown action %s',$string));
$this->action = $string;
}
public function clear_on_close(bool $bool): self
{
if ($this->type != 'modal')
throw new Exception(sprintf('clear_on_close is not required for %s',$type));
$this->clear_on_close = $bool ? 'true' : 'false';
return $this;
}
public function callback_id(string $string): self
{
$this->callback_id = $this->validate('callback_id',$string);
return $this;
}
public function close(Text $text): self
{
if ($this->type != 'modal')
throw new Exception(sprintf('Close is not required for %s',$type));
if ($text->type != 'plain_text')
throw new Exception(sprintf('Text must be plain_text not %s',$text->type));
if (strlen($text->text) > self::LIMITS['close'])
throw new Exception(sprintf('Text must be %d chars or less',self::LIMITS['close']));
$this->close = $text;
return $this;
}
// This is a helper method
public function divider(): self
{
$this->blocks->push(Divider::item());
return $this;
}
/**
* @param string $string
* @return $this
* @note A custom identifier that must be unique for all views on a per-team basis.
*/
public function external_id(string $string): self
{
$this->external_id = $string;
return $this;
}
public function notify_on_close(bool $bool): self
{
if ($this->type != 'modal')
throw new Exception(sprintf('notify_on_close is not required for %s',$type));
$this->notify_on_close = $bool ? 'true' : 'false';
return $this;
}
public function private_metadata(string $string): self
{
$this->private_metadata = $this->validate('private_metadata',$string);
return $this;
}
// This is a helper method
public function spacer(): self
{
$this->blocks->push(Section::item(Text::item(' ')));
return $this;
}
public function submit(Text $text): self
{
if ($this->type != 'modal')
throw new Exception(sprintf('Submit is not required for %s',$type));
if ($text->type != 'plain_text')
throw new Exception(sprintf('Text must be plain_text not %s',$text->type));
if (strlen($text->text) > self::LIMITS['submit'])
throw new Exception(sprintf('Text must be %d chars or less',self::LIMITS['submit']));
$this->submit = $text;
return $this;
}
public function submit_disabled(bool $bool): self
{
if ($this->type != 'modal')
throw new Exception(sprintf('submit_disabled is not required for %s',$type));
$this->submit_disabled = $bool ? 'true' : 'false';
return $this;
}
/**
* Add a block to the modal
*
* @param Blocks $blocks
* @return $this
* @deprecated use addBlock() instead
*/
public function setBlocks(Blocks $blocks): self
{
@ -56,58 +209,4 @@ class Modal extends BlockKit
return $this;
}
public function setCallback(string $id): self
{
$this->_data->put('callback_id',$id);
return $this;
}
/*
public function close(string $text='Cancel'): self
{
$this->_data->put('close',
[
'type'=>'plain_text',
'text'=>$text,
'emoji'=>true,
]);
return $this;
}
public function meta(string $id): self
{
$this->_data->put('private_metadata',$id);
return $this;
}
public function notifyClose(): self
{
$this->_data->put('notify_on_close',TRUE);
return $this;
}
public function private(array $data): self
{
$this->_data->put('private_metadata',json_encode($data));
return $this;
}
public function submit(string $text='Submit'): self
{
$this->_data->put('submit',
[
'type'=>'plain_text',
'text'=>$text,
'emoji'=>true,
]);
return $this;
}
*/
}
}

View File

@ -60,12 +60,14 @@ abstract class Base extends SlackBase
if (! $this->channel() || ! $this->channel()->active) {
$blocks = new Blocks;
$blocks->addHeader(':robot_face: Bot not in this channel');
$blocks->addSectionText(Blocks\Text::item(sprintf('Please add %s to this channel and try this again.',$this->team()->bot->name ?: 'the BOT')));
$o->setAttachments((new Message\Attachments())->setBlocks($blocks)->setColor('#ff0000'));
$o->addAttachment(
Message\Attachment::item()
->title(':robot_face: Bot not in this channel')
->text(sprintf('Please add %s to this channel and try this again.',$this->team()->bot->name ?: 'the BOT'))
->color('#ff0000')
);
}
return $o->isEmpty() ? NULL : $o;
}
}
}

View File

@ -42,4 +42,4 @@ class Factory {
return self::create($command ?: 'help',Arr::get($request->getData(),'payload'));
}
}
}

View File

@ -3,7 +3,6 @@
namespace Slack\Command;
use Slack\Message;
use Slack\Message\Attachments;
class Help extends Base
{
@ -13,13 +12,14 @@ class Help extends Base
{
$o = new Message;
$o->setText('Hi, I am a *NEW* Bot');
$o->text('Hi, I am a *NEW* Bot');
// Version
$a = new Attachments;
$a->addField('Version',config('app.version','unknown'),TRUE);
$o->setAttachments($a);
$o->addAttachment(
Message\Attachment::item()
->addField('Version',config('app.version','unknown'),TRUE)
);
return $o;
}
}
}

View File

@ -24,8 +24,8 @@ final class Unknown extends Base
{
$o = new Message;
$o->setText(sprintf('I didnt understand your command "%s". You might like to try `%s help` instead.',$this->command,$this->slashcommand));
$o->text(sprintf('I didnt understand your command "%s". You might like to try `%s help` instead.',$this->command,$this->slashcommand));
return $o;
}
}
}

View File

@ -3,7 +3,8 @@
namespace App\Listeners;
use Illuminate\Support\Facades\Log;
use Slack\Blockkit\Blocks;
use Slack\Blockkit\Blocks\Elements\Text;
use Slack\Blockkit\Blocks\{Header,Section};
use Slack\Blockkit\Modal;
use Slack\Interactive\Shortcut;
use Slack\Message;
@ -25,13 +26,15 @@ class ShortcutListener //implements ShouldQueue
public function handle(Shortcut $event): Message
{
if (! $event->channel() || ! $event->channel()->active) {
$modal = new Modal(Blocks\TextEmoji::item(config('app.name')));
$modal = new Modal(Text::item(config('app.name'),'plain_text'));
$blocks = new Blocks;
$blocks->addHeader(':robot_face: Bot not in this channel');
$blocks->addSectionText(Blocks\Text::item('Please add the BOT to this channel and try this again.'));
$modal->setBlocks($blocks);
$modal->addBlock(
Header::item(Text::item(':robot_face: Bot not in this channel','plain_text'))
)
->addBlock(
Section::item(Text::item('Please add the BOT to this channel and try this again.'))
);
try {
$event->team()->slackAPI()->viewOpen($event->trigger_id,json_encode($modal));

View File

@ -5,23 +5,28 @@ namespace Slack;
use Carbon\Carbon;
use Carbon\CarbonInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Slack\Blockkit\Blocks;
use Slack\Blockkit\Blocks\Context;
use Slack\Blockkit\Blocks\Elements\Text;
use Slack\Exceptions\SlackException;
use Slack\Jobs\DeleteChat;
use Slack\Message\Attachments;
use Slack\Message\Attachment;
use Slack\Models\{Channel,User};
use Slack\Response\Generic;
/**
* This class is used when composing a message to send to Slack.
*/
class Message implements \JsonSerializable
final class Message extends BlockKit
{
protected const LOGKEY = 'SM-';
private const MAX_ATTACHMENTS = 20;
private Model $o;
private Blocks $blocks;
private ?Carbon $selfdestruct = NULL;
/**
@ -32,7 +37,7 @@ class Message implements \JsonSerializable
*/
public function __construct(Model $o=NULL)
{
$this->_data = collect();
parent::__construct();
if ($o) {
// Message is to a channel
@ -49,10 +54,10 @@ class Message implements \JsonSerializable
$this->o = $o;
}
$this->blocks = new Blocks;
}
/* HELPER METHODS */
/**
* Add a block to the message
*
@ -60,9 +65,25 @@ class Message implements \JsonSerializable
* @return Message
* @todo to test
*/
public function addBlock(Blocks $blocks): self
public function addAttachment(Attachment $attachment): self
{
$this->blocks = $blocks;
if (! Arr::get($this->_data,'attachments'))
$this->attachments = collect();
$this->attachments->push($attachment);
if (count($this->attachments) > self::MAX_ATTACHMENTS)
throw new Exception(sprintf('Messages should not have more than %d attachments',self::MAX_ATTACHMENTS));
return $this;
}
public function addBlock(Blocks $block): self
{
if (! Arr::get($this->_data,'blocks'))
$this->blocks = collect();
$this->blocks->push($block);
return $this;
}
@ -75,21 +96,10 @@ class Message implements \JsonSerializable
public function blank(): self
{
$this->_data = collect();
$this->blocks = new Blocks;
return $this;
}
/*
* @todo This doesnt appear to work
public function ephemeral(): self
{
$this->_data->put('ephemeral',TRUE);
return $this;
}
*/
public function forgetTS(): self
{
$this->_data->forget('ts');
@ -112,11 +122,8 @@ class Message implements \JsonSerializable
*/
public function jsonSerialize()
{
if ($this->blocks->count())
$this->_data->put('blocks',$this->blocks);
// For interactive messages that generate a dialog, we need to return NULL
return $this->_data->count() ? $this->_data : NULL;
return $this->_data->count() ? parent::jsonSerialize() : NULL;
}
/**
@ -134,7 +141,7 @@ class Message implements \JsonSerializable
if ($this->_data->has('ephemeral'))
abort('500','Cannot post ephemeral messages.');
if ($this->blocks->count() && $this->_data->get('attachments'))
if ($this->_data->get('blocks') && $this->_data->get('attachments'))
throw new SlackException('Message cannot have blocks and attachments.');
$api = $this->o->team->slackAPI();
@ -150,42 +157,6 @@ class Message implements \JsonSerializable
return $response;
}
/**
* Schedule a message
*
* @param Carbon $time
* @return Generic
*/
public function schedule(Carbon $time): Generic
{
$this->_data->put('post_at',$time->timestamp);
$api = $this->o->team->slackAPI();
$response = $this->_data->has('ts') ? $api->updateMessage($this) : $api->scheduleMessage($this);
return $response;
}
public function setReplace(bool $replace=TRUE): self
{
$this->_data->put('replace_original',$replace ? 'true' : 'false');
return $this;
}
/**
* To slack from rendering URLs in the message
*
* @param bool $unfurl
* @return $this
*/
public function setUnfurlLinks(bool $unfurl): self
{
$this->_data->put('unfurl_links',$unfurl ? 'true' : 'false');
return $this;
}
/**
* Post a message to slack using the respond_url
*
@ -228,6 +199,22 @@ class Message implements \JsonSerializable
return $result;
}
/**
* Schedule a message
*
* @param Carbon $time
* @return Generic
*/
public function schedule(Carbon $time): Generic
{
$this->_data->put('post_at',$time->timestamp);
$api = $this->o->team->slackAPI();
$response = $this->_data->has('ts') ? $api->updateMessage($this) : $api->scheduleMessage($this);
return $response;
}
/**
* Make the message self destruct
*
@ -236,21 +223,161 @@ class Message implements \JsonSerializable
*/
public function selfdestruct(Carbon $time): self
{
$this->blocks->addContextElements(collect([
Blocks\Text::item(sprintf('This message will self destruct in %s...',$time->diffForHumans(Carbon::now(),['syntax' => CarbonInterface::DIFF_RELATIVE_TO_NOW])))
]));
$this->addBlock(
Context::item(collect([
Text::item(sprintf('This message will self destruct in %s...',$time->diffForHumans(Carbon::now(),['syntax' => CarbonInterface::DIFF_RELATIVE_TO_NOW])))
]))
);
$this->selfdestruct = $time;
return $this;
}
/**
* Add an attachment to a message
* Set our channel
*
* @param Attachments $attachments
* @param Channel $o
* @return Message
*/
public function setAttachments(Attachments $attachments): self
public function setChannel(Channel $o): self
{
$this->channel = $o->channel_id;
$this->o = $o;
return $this;
}
/**
* Set our channel
*
* @param User $o
* @return Message
*/
public function setUser(User $o): self
{
$this->channel = $o->user_id;
$this->o = $o;
return $this;
}
/* CONFIGURATION METHODS */
/**
* @return Message
* @todo - Check this is a valid option
*/
public function ephemeral(): self
{
$this->ephemeral = 'true';
return $this;
}
/**
* Set the icon next to the message
*
* @param string $icon
* @return Message
* @deprecated
*/
public function icon_emoji(string $icon): self
{
$this->icon_emoji = $icon;
return $this;
}
/**
* Option groups are used by the interactive Options controller and hold no other attributes
*
* @param Collection $collection
* @return Message
* @todo - Check this is a valid option
*/
public function option_groups(Collection $collection): self
{
$this->option_groups = $collection;
}
/**
* @return Message
* @todo - Check this is a valid option
*/
public function replace_original(bool $bool=TRUE): self
{
$this->replace_original = $bool ? 'true' : 'false';
return $this;
}
/**
* Message text
*
* @param string $string
* @return Message
*/
public function text(string $string): self
{
$this->text = $string;
return $this;
}
/**
* Set the thread timestamp, used when adding a threaded response
*
* @param string $string
* @return Message
*/
public function thread_ts(string $string): self
{
$this->thread_ts = $string;
return $this;
}
/**
* Set the timestamp, used when replacing messages
*
* @param string $string
* @return Message
*/
public function ts(string $string): self
{
$this->ts = $string;
return $this;
}
/**
* To slack from rendering URLs in the message
*
* @param bool $bool
* @return $this
*/
public function unfurl_links(bool $bool): self
{
$this->unfurl_links = $bool ? 'true' : 'false';
return $this;
}
public function username(string $user): self
{
$this->username = $user;
return $this;
}
/**
* Add an attachment to a message
*
* @param Attachment $attachments
* @return Message
* @deprecated use addAttachment()
*/
public function setAttachments(Attachment $attachments): self
{
$this->_data->put('attachments',[$attachments]);
@ -263,6 +390,7 @@ class Message implements \JsonSerializable
* @param Blocks $blocks
* @return Message
* @throws \Exception
* @deprecated use addBlocks()
*/
public function setBlocks(Blocks $blocks): self
{
@ -274,55 +402,16 @@ class Message implements \JsonSerializable
return $this;
}
/**
* Set our channel
*
* @param Channel $o
* @return Message
*/
public function setChannel(Channel $o): self
{
$this->_data->put('channel',$o->channel_id);
$this->o = $o;
return $this;
}
/**
* Set the icon next to the message
*
* @param string $icon
* @return Message
* @deprecated
*/
public function setIcon(string $icon): self
{
$this->_data->put('icon_emoji',$icon);
return $this;
}
/**
* Option groups are used by the interactive Options controller and hold no other attributes
*
* @param array $array
* @return void
*/
public function setOptionGroup(array $array): void
{
$this->_data = collect(); // @todo Why are clearing our data?
$this->_data->put('option_groups',$array);
}
/**
* Message text
*
* @param string $string
* @return Message
* @deprecated use text()
*/
public function setText(string $string): self
{
$this->_data->put('text',$string);
$this->text = $string;
return $this;
}
@ -332,6 +421,7 @@ class Message implements \JsonSerializable
*
* @param string $string
* @return Message
* @deprecated use ts()
*/
public function setTS(string $string): self
{
@ -345,6 +435,7 @@ class Message implements \JsonSerializable
*
* @param string $string
* @return Message
* @deprecated use thead_ts()
*/
public function setThreadTS(string $string): self
{
@ -352,25 +443,4 @@ class Message implements \JsonSerializable
return $this;
}
/**
* Set our channel
*
* @param User $o
* @return Message
*/
public function setUser(User $o): self
{
$this->_data->put('channel',$o->user_id);
$this->o = $o;
return $this;
}
public function setUserName(string $user): self
{
$this->_data->put('username',$user);
return $this;
}
}

168
src/Message/Attachment.php Normal file
View File

@ -0,0 +1,168 @@
<?php
namespace Slack\Message;
use \Exception;
use Illuminate\Support\Arr;
use Slack\BlockKit;
use Slack\Blockkit\Blocks;
final class Attachment extends BlockKit
{
protected const LIMITS = [
'footer' => 300,
];
/**
* @param Text $text
* @throws Exception
*/
public function __construct()
{
parent::__construct();
}
public static function item(): self
{
return new self();
}
/* OPTIONAL ITEMS */
/**
* @note These are now legacy - use blocks instead
* @note more information on this available https://api.slack.com/legacy/interactive-message-field-guide
*/
public function addAction(string $name,string $text,string $type,string $value,string $style=NULL,array $confirm=NULL): self
{
if (! Arr::get($this->_data,'actions')) {
$this->actions = collect();
$this->attachment_type = 'default';
}
$action = ['name'=>$name,'text'=>$text,'type'=>$type,'value'=>$value];
if ($style)
$action['style'] = $style;
// Confirm dialog is an array of 'title','text','ok_text','dismiss_text'
if ($confirm)
$action['confirm'] = $confirm;
$this->actions->push();
return $this;
}
public function addBlock(Blocks $block): self
{
if (! Arr::get($this->_data,'blocks'))
$this->blocks = collect();
$this->blocks->push($block);
return $this;
}
public function addField(string $title,string $value,bool $short): self
{
if (! Arr::get($this->_data,'fields'))
$this->fields = collect();
$this->fields->push(['title'=>$title,'value'=>$value,'short'=>$short ? 'true' : 'false']);
return $this;
}
/**
* @param string $string
* @return $this
* @note Can either be one of good (green), warning (yellow), danger (red), or any hex color code (eg. #439FE0)
*/
public function color(string $string): self
{
$this->color = $string;
return $this;
}
/**
* @param string $string
* @return $this
* @note Appears to be only used with actions
*/
public function callback_id(string $string): self
{
$this->callback_id = $string;
return $this;
}
public function fallback(string $string): self
{
$this->fallback = $string;
return $this;
}
public function footer(string $string): self
{
$this->footer = $this->validate('footer',$string);
return $this;
}
public function footer_icon(string $string): self
{
$this->footer_icon = $string;
return $this;
}
/**
* Set where markdown should be parsed by slack
*
* @param array $array
*/
public function mrkdwn_in(array $array): self
{
// @todo Add array check to make sure it has valid items
$this->mrkdwn_in = $array;
return $this;
}
public function pretext(string $string): self
{
$this->pretext = $string;
return $this;
}
public function text(string $string): self
{
$this->text = $string;
return $this;
}
public function title(string $string): self
{
$this->title = $string;
return $this;
}
public function title_link(string $string): self
{
$this->title_link = $string;
return $this;
}
public function ts(string $string): self
{
$this->ts = $string;
return $this;
}
}

View File

@ -1,133 +0,0 @@
<?php
namespace Slack\Message;
/**
* Class MessageAttachmentAction - Slack Message Attachments Actions
* Represents an Single Action for a Slack Message Attachment
*
* @package Slack\Message
*/
class AttachmentAction implements \JsonSerializable
{
private $_data;
public function __construct()
{
$this->_data = collect();
}
public function jsonSerialize()
{
return $this->_data;
}
public function minSize(int $int): self
{
$this->_data->put('min_query_length',$int);
return $this;
}
/**
* Set a confirmation diaglog when this action is selected
*
* @param string $title
* @param string $text
* @param string $ok_text
* @param string $dismiss_text
* @return $this
*/
public function setConfirm(string $title,string $text,string $ok_text,string $dismiss_text): self
{
$this->_data->put('confirm',[
'title'=>$title,
'text'=>$text,
'ok_text'=>$ok_text,
'dismiss_text'=>$dismiss_text
]);
return $this;
}
/**
* Set the name of the action
*
* @param string $string
* @return $this
*/
public function setName(string $string): self
{
$this->_data->put('name',$string);
return $this;
}
/**
* Set the text displayed in the action
*
* @param string $type
* @return $this
*/
public function setStyle(string $style): self
{
if (! in_array($style,['danger','primary']))
abort(500,'Style not supported: '.$style);
$this->_data->put('style',$style);
return $this;
}
/**
* Set the text displayed in the action
*
* @param string $string
* @return $this
*/
public function setText(string $string): self
{
$this->_data->put('text',$string);
return $this;
}
/**
* Set the text displayed in the action
*
* @param string $type
* @return $this
*/
public function setType(string $type): self
{
if (! in_array($type,['button','select']))
abort(500,'Type not supported: '.$type);
$this->_data->put('type',$type);
return $this;
}
/**
* Set the value for the action
*
* @param string $string
* @return $this
*/
public function setValue(string $string): self
{
$this->_data->put('value',$string);
return $this;
}
public function source(string $string): self
{
if (! in_array($string,['external']))
abort(500,'Dont know how to handle: '.$string);
$this->_data->put('data_source',$string);
return $this;
}
}

View File

@ -1,227 +0,0 @@
<?php
namespace Slack\Message;
use Slack\BlockKit;
use Slack\Blockkit\BlockAction;
use Slack\Blockkit\Blocks;
/**
* Class MessageAttachment - Slack Message Attachments
* Represents an Single Attachment that can be added to a Message
*
* @package Slack\Message
*/
class Attachments implements \JsonSerializable
{
private $_data;
private $actions;
//private $blocks;
private $blockactions;
// @todo To rework
public function __construct()
{
$this->actions = collect();
//$this->blocks = collect();
$this->blockactions = collect();
$this->_data = collect();
}
// @todo To rework
public function jsonSerialize()
{
/*
if ($this->actions->count() AND ! $this->_data->has('callback_id'))
abort(500,'Actions without a callback ID');
if ($this->blockactions->count()) {
$x = collect();
$x->put('type','actions');
$x->put('elements',$this->blockactions);
$this->blocks->push($x);
// Empty out our blockactions, incase we are converted to json a second time.
$this->blockactions = collect();
}
if ($this->actions->count())
$this->_data->put('actions',$this->actions);
if ($this->blocks->count())
$this->_data->put('blocks',$this->blocks);
*/
return $this->_data;
}
/**
* Add an attachment to a message
*
* @param AttachmentAction $action
* @return Attachment
* @todo To rework
*/
public function addAction(AttachmentAction $action): self
{
$this->actions->push($action);
return $this;
}
/**
* Add a block to message
*
* @param BlockKit $block
* @return Attachment
* @deprecated
*/
public function addBlock(BlockKit $block): self
{
$this->blocks->push($block);
return $this;
}
/**
* Add a BlockAction to a Block
*
* @param BlockAction $action
* @return $this
* @todo To rework
*/
public function addBlockAction(BlockAction $action): self
{
$this->blockactions->push($action);
return $this;
}
//* @todo To rework
public function addField(string $title,string $value,bool $short): self
{
if (! $this->_data->has('fields'))
$this->_data->put('fields',collect());
$this->_data->get('fields')->push([
'title'=>$title,
'value'=>$value,
'short'=>$short
]);
return $this;
}
/**
* Set where markdown should be parsed by slack
*
* @param array $array
* @return $this
* @todo To rework
*/
public function markdownIn(array $array): self
{
// @todo Add array check to make sure it has valid items
$this->_data->put('mrkdown_in',$array);
return $this;
}
/**
* Configure the attachment color (on the left of the attachment)
*
* @param string $string
* @return $this
* @todo To rework
*/
public function setCallbackID(string $string): self
{
$this->_data->put('callback_id',$string);
return $this;
}
/**
* Add a blocks to the message attachment
*
* @param Blocks $blocks
* @return self
*/
public function setBlocks(Blocks $blocks): self
{
$this->_data->put('blocks',$blocks);
return $this;
}
/**
* Configure the attachment color (on the left of the attachment)
*
* @param string $string
* @return $this
* @todo To rework
*/
public function setColor(string $string): self
{
$this->_data->put('color',$string);
return $this;
}
/**
* Set the text used in the attachments footer
*
* @param string $string
* @return $this
* @todo To rework
*/
public function setFooter(string $string): self
{
$this->_data->put('footer',$string);
return $this;
}
/**
* Add the pre-text, displayed after the title.
*
* @param string $string
* @return $this
* @todo To rework
*/
public function setPretext(string $string): self
{
$this->_data->put('pretext',$string);
return $this;
}
/**
* Set the text used in the attachment
*
* @param string $string
* @return $this
* @todo To rework
*/
public function setText(string $string): self
{
$this->_data->put('text',$string);
return $this;
}
/**
* Set the Title used in the attachment
*
* @param string $string
* @return $this
* @todo To rework
*/
public function setTitle(string $string): self
{
$this->_data->put('title',$string);
return $this;
}
}