From c2ec309a01588f02c1e2fc0241ded1467ae07e57 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Fri, 25 Feb 2022 09:52:35 +0100 Subject: [PATCH] more work on tablelisting Signed-off-by: Michael Kaufmann --- customer_domains.php | 70 ++------- lib/Froxlor/UI/Callbacks/Domain.php | 47 ++++++ lib/Froxlor/UI/Callbacks/Text.php | 40 ++--- lib/Froxlor/UI/Collection.php | 17 ++- lib/Froxlor/UI/Panel/UI.php | 17 +++ lib/Froxlor/User.php | 2 +- .../domains/formfield.domains_add.php | 19 ++- lib/init.php | 1 + .../admin/tablelisting.customers.php | 137 ++++++++--------- .../admin/tablelisting.domains.php | 144 +++++++++--------- .../customer/tablelisting.domains.php | 79 ++++++++++ 11 files changed, 336 insertions(+), 237 deletions(-) create mode 100644 lib/Froxlor/UI/Callbacks/Domain.php create mode 100644 lib/tablelisting/customer/tablelisting.domains.php diff --git a/customer_domains.php b/customer_domains.php index 6c30cb57..8d250987 100644 --- a/customer_domains.php +++ b/customer_domains.php @@ -39,69 +39,23 @@ if ($page == 'overview') { } elseif ($page == 'domains') { if ($action == '') { $log->logAction(\Froxlor\FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_domains::domains"); - $fields = array( - 'd.domain_ace' => $lng['domains']['domainname'], - 'd.aliasdomain' => $lng['domains']['aliasdomain'] - ); + + $parentdomain_id = (int) Request::get('pid', '0'); + try { - // get total count - $json_result = SubDomains::getLocal($userinfo)->listingCount(); - $result = json_decode($json_result, true)['data']; - // initialize pagination and filtering - $paging = new \Froxlor\UI\Pagination($userinfo, $fields, $result); - // get list - $json_result = SubDomains::getLocal($userinfo, $paging->getApiCommandParams())->listing(); + $domain_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.domains.php'; + $list = (new \Froxlor\UI\Collection(\Froxlor\Api\Commands\SubDomains::class, $userinfo)) +// ->addParam(['sql_search' => ['d.parentdomainid' => $parentdomain_id]]) + ->withPagination($domain_list_data['domain_list']['columns']) + ->getList(); } catch (Exception $e) { \Froxlor\UI\Response::dynamic_error($e->getMessage()); } - $result = json_decode($json_result, true)['data']; - $sortcode = $paging->getHtmlSortCode($lng); - $arrowcode = $paging->getHtmlArrowCode($filename . '?page=' . $page . '&s=' . $s); - $searchcode = $paging->getHtmlSearchCode($lng); - $pagingcode = $paging->getHtmlPagingCode($filename . '?page=' . $page . '&s=' . $s); - $domains = ''; - $parentdomains_count = 0; - $domains_count = $paging->getEntries(); - $domain_array = array(); - - foreach ($result['list'] as $row) { - formatDomainEntry($row, $idna_convert); - if ($row['parentdomainid'] == '0' && $row['caneditdomain'] == '1') { - $parentdomains_count++; - } - $domain_array[$row['parentdomainname']][] = $row; - } - - foreach ($domain_array as $parentdomain => $sdomains) { - // PARENTDOMAIN - if (Settings::Get('system.awstats_enabled') == '1') { - $statsapp = 'awstats'; - } else { - $statsapp = 'webalizer'; - } - $row = [ - 'domain' => $idna_convert->decode($parentdomain) - ]; - eval("\$domains.=\"" . \Froxlor\UI\Template::getTemplate("domains/domains_delimiter") . "\";"); - - foreach ($sdomains as $domain) { - $row = \Froxlor\PhpHelper::htmlentitiesArray($domain); - - // show docroot nicely - if (strpos($row['documentroot'], $userinfo['documentroot']) === 0) { - $row['documentroot'] = \Froxlor\FileDir::makeCorrectDir(str_replace($userinfo['documentroot'], "/", $row['documentroot'])); - } - // get ssl-ips if activated - $show_ssledit = false; - if (Settings::Get('system.use_ssl') == '1' && \Froxlor\Domain\Domain::domainHasSslIpPort($row['id']) && $row['caneditdomain'] == '1' && $row['letsencrypt'] == 0) { - $show_ssledit = true; - } - eval("\$domains.=\"" . \Froxlor\UI\Template::getTemplate("domains/domains_domain") . "\";"); - } - } - - eval("echo \"" . \Froxlor\UI\Template::getTemplate("domains/domainlist") . "\";"); + UI::twigBuffer('user/table.html.twig', [ + 'listing' => \Froxlor\UI\Listing::format($list, $domain_list_data['domain_list']), + ]); + UI::twigOutputBuffer(); } elseif ($action == 'delete' && $id != 0) { try { $json_result = SubDomains::getLocal($userinfo, array( diff --git a/lib/Froxlor/UI/Callbacks/Domain.php b/lib/Froxlor/UI/Callbacks/Domain.php new file mode 100644 index 00000000..7ba4ad8e --- /dev/null +++ b/lib/Froxlor/UI/Callbacks/Domain.php @@ -0,0 +1,47 @@ + (2010-) + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Listing + * + */ + +class Domain +{ + public static function domainTarget(string $data, array $attributes): mixed + { + if (empty($attributes['aliasdomain'])) { + // path or redirect + if (preg_match('/^https?\:\/\//', $attributes['documentroot'])) { + return [ + 'type' => 'link', + 'data' => [ + 'text' => $attributes['documentroot'], + 'href' => $attributes['documentroot'], + 'target' => '_blank' + ] + ]; + } else { + // show docroot nicely + if (strpos($attributes['documentroot'], UI::getCurrentUser()['documentroot']) === 0) { + $attributes['documentroot'] = \Froxlor\FileDir::makeCorrectDir(str_replace(UI::getCurrentUser()['documentroot'], "/", $attributes['documentroot'])); + } + return $attributes['documentroot']; + } + } + return UI::getLng('domains.aliasdomain') . ' ' . $attributes['aliasdomain']; + } +} diff --git a/lib/Froxlor/UI/Callbacks/Text.php b/lib/Froxlor/UI/Callbacks/Text.php index 04d9b4e0..cdf3e301 100644 --- a/lib/Froxlor/UI/Callbacks/Text.php +++ b/lib/Froxlor/UI/Callbacks/Text.php @@ -1,4 +1,5 @@ 'boolean', - 'data' => (bool) $data - ]; - } + public static function boolean(?string $data): array + { + return [ + 'type' => 'boolean', + 'data' => (bool) $data + ]; + } - public static function domainWithSan(string $data, array $attributes): array - { - return [ - 'type' => 'domainWithSan', - 'data' => [ - 'domain' => $data, - 'san' => implode(', ', $attributes['san'] ?? []), - ] - ]; - } + public static function domainWithSan(string $data, array $attributes): array + { + return [ + 'type' => 'domainWithSan', + 'data' => [ + 'domain' => $data, + 'san' => implode(', ', $attributes['san'] ?? []), + ] + ]; + } + + public static function customerfullname(string $data, array $attributes): string + { + return \Froxlor\User::getCorrectFullUserDetails($attributes); + } } diff --git a/lib/Froxlor/UI/Collection.php b/lib/Froxlor/UI/Collection.php index 82f216c8..bdc67e7f 100644 --- a/lib/Froxlor/UI/Collection.php +++ b/lib/Froxlor/UI/Collection.php @@ -81,16 +81,23 @@ class Collection public function has(string $column, string $class, string $parentKey = 'id', string $childKey = 'id', array $params = []): Collection { $this->has[] = [ - 'column' => $column, - 'class' => $class, - 'parentKey' => $parentKey, - 'childKey' => $childKey, - 'params' => $params + 'column' => $column, + 'class' => $class, + 'parentKey' => $parentKey, + 'childKey' => $childKey, + 'params' => $params ]; return $this; } + public function addParam(array $keyval): Collection + { + $this->params = array_merge($this->params, $keyval); + + return $this; + } + public function withPagination(array $columns): Collection { // TODO: handle 'sortable' => true in $columns diff --git a/lib/Froxlor/UI/Panel/UI.php b/lib/Froxlor/UI/Panel/UI.php index 9025b610..696ca466 100644 --- a/lib/Froxlor/UI/Panel/UI.php +++ b/lib/Froxlor/UI/Panel/UI.php @@ -35,6 +35,13 @@ class UI */ private static $linker = null; + /** + * current logged in user + * + * @var array + */ + private static $userinfo = []; + /** * default fallback theme * @@ -224,6 +231,16 @@ class UI return self::$linker; } + public static function setCurrentUser($userinfo = null) + { + self::$userinfo = $userinfo; + } + + public static function getCurrentUser(): array + { + return self::$userinfo; + } + public static function setLng($lng = array()) { self::$lng = $lng; diff --git a/lib/Froxlor/User.php b/lib/Froxlor/User.php index 717579e9..0600683d 100644 --- a/lib/Froxlor/User.php +++ b/lib/Froxlor/User.php @@ -24,7 +24,7 @@ class User $returnval = $userinfo['name'] . ', ' . $userinfo['firstname']; } else { if ($userinfo['name'] != '' && $userinfo['firstname'] != '') { - $returnval = $userinfo['name'] . ', ' . $userinfo['firstname'] . ' | ' . $userinfo['company']; + $returnval = $userinfo['name'] . ', ' . $userinfo['firstname'] . '
' . $userinfo['company'] . ''; } else { $returnval = $userinfo['company']; } diff --git a/lib/formfields/customer/domains/formfield.domains_add.php b/lib/formfields/customer/domains/formfield.domains_add.php index 937975e6..914848f0 100644 --- a/lib/formfields/customer/domains/formfield.domains_add.php +++ b/lib/formfields/customer/domains/formfield.domains_add.php @@ -14,6 +14,9 @@ * @package Formfields * */ + +use Froxlor\Settings; + return array( 'domain_add' => array( 'title' => $lng['domains']['subdomain_add'], @@ -41,19 +44,19 @@ return array( ), 'path' => array( 'label' => $lng['panel']['path'], - 'desc' => (\Froxlor\Settings::Get('panel.pathedit') != 'Dropdown' ? $lng['panel']['pathDescriptionSubdomain'] : null), + 'desc' => (Settings::Get('panel.pathedit') != 'Dropdown' ? $lng['panel']['pathDescriptionSubdomain'] : null), 'type' => $pathSelect['type'], 'select_var' => $pathSelect['select_var'] ?? '', 'value' => $pathSelect['value'], 'note' => $pathSelect['note'] ?? '', ), 'url' => array( - 'visible' => (\Froxlor\Settings::Get('panel.pathedit') == 'Dropdown' ? true : false), + 'visible' => (Settings::Get('panel.pathedit') == 'Dropdown' ? true : false), 'label' => $lng['panel']['urloverridespath'], 'type' => 'text' ), 'redirectcode' => array( - 'visible' => (\Froxlor\Settings::Get('customredirect.enabled') == '1' ? true : false), + 'visible' => (Settings::Get('customredirect.enabled') == '1' ? true : false), 'label' => $lng['domains']['redirectifpathisurl'], 'desc' => $lng['domains']['redirectifpathisurlinfo'], 'type' => 'select', @@ -71,18 +74,18 @@ return array( 'select_var' => $openbasedir ), 'phpsettingid' => array( - 'visible' => (((int) \Froxlor\Settings::Get('system.mod_fcgid') == 1 || (int) \Froxlor\Settings::Get('phpfpm.enabled') == 1) && count($phpconfigs) > 0 ? true : false), + 'visible' => (((int) Settings::Get('system.mod_fcgid') == 1 || (int) Settings::Get('phpfpm.enabled') == 1) && count($phpconfigs) > 0 ? true : false), 'label' => $lng['admin']['phpsettings']['title'], 'type' => 'select', 'select_var' => $phpconfigs, - 'selected' => (int) Settings::Get('phpfpm.enabled') == 1) ? Settings::Get('phpfpm.defaultini') : Settings::Get('system.mod_fcgid_defaultini') + 'selected' => (int) Settings::Get('phpfpm.enabled') == 1 ? Settings::Get('phpfpm.defaultini') : Settings::Get('system.mod_fcgid_defaultini') ) ) ), 'section_bssl' => array( 'title' => $lng['admin']['webserversettings_ssl'], 'image' => 'icons/domain_add.png', - 'visible' => \Froxlor\Settings::Get('system.use_ssl') == '1' ? ($ssl_ipsandports ? true : false) : false, + 'visible' => Settings::Get('system.use_ssl') == '1' ? ($ssl_ipsandports ? true : false) : false, 'fields' => array( 'sslenabled' => array( 'label' => $lng['admin']['domain_sslenabled'], @@ -98,7 +101,7 @@ return array( 'checked' => false ), 'letsencrypt' => array( - 'visible' => (\Froxlor\Settings::Get('system.leenabled') == '1' ? true : false), + 'visible' => (Settings::Get('system.leenabled') == '1' ? true : false), 'label' => $lng['customer']['letsencrypt']['title'], 'desc' => $lng['customer']['letsencrypt']['description'], 'type' => 'checkbox', @@ -106,7 +109,7 @@ return array( 'checked' => false ), 'http2' => array( - 'visible' => ($ssl_ipsandports ? true : false) && \Froxlor\Settings::Get('system.webserver') != 'lighttpd' && \Froxlor\Settings::Get('system.http2_support') == '1', + 'visible' => ($ssl_ipsandports ? true : false) && Settings::Get('system.webserver') != 'lighttpd' && Settings::Get('system.http2_support') == '1', 'label' => $lng['admin']['domain_http2']['title'], 'desc' => $lng['admin']['domain_http2']['description'], 'type' => 'checkbox', diff --git a/lib/init.php b/lib/init.php index 5c64f2c6..c82a8320 100644 --- a/lib/init.php +++ b/lib/init.php @@ -362,6 +362,7 @@ if ($nosession == 1 && AREA != 'login') { } UI::twig()->addGlobal('userinfo', ($userinfo ?? [])); +UI::setCurrentUser($userinfo); /** * Logic moved out of lng-file diff --git a/lib/tablelisting/admin/tablelisting.customers.php b/lib/tablelisting/admin/tablelisting.customers.php index 629bf7a4..b431c698 100644 --- a/lib/tablelisting/admin/tablelisting.customers.php +++ b/lib/tablelisting/admin/tablelisting.customers.php @@ -16,81 +16,74 @@ * */ +use Froxlor\UI\Callbacks\Text; use Froxlor\UI\Callbacks\Impersonate; use Froxlor\UI\Callbacks\ProgressBar; use Froxlor\UI\Listing; return [ - 'customer_list' => [ - 'title' => $lng['admin']['customers'], - 'icon' => 'fa-solid fa-user', - 'columns' => [ - 'c.loginname' => [ - 'label' => $lng['login']['username'], - 'column' => 'loginname', - 'format_callback' => [Impersonate::class, 'customer'], - ], - 'a.loginname' => [ - 'label' => $lng['admin']['admin'], - 'column' => 'admin.loginname', - 'format_callback' => [Impersonate::class, 'admin'], - ], - 'c.name' => [ - 'label' => $lng['customer']['name'], - 'column' => 'name', - ], - 'c.email' => [ - 'label' => $lng['customer']['email'], - 'column' => 'email', - ], - 'c.firstname' => [ - 'label' => $lng['customer']['firstname'], - 'column' => 'firstname', - ], - 'c.company' => [ - 'label' => $lng['customer']['company'], - 'column' => 'company', - ], - 'c.diskspace' => [ - 'label' => $lng['customer']['diskspace'], - 'column' => 'diskspace', - 'format_callback' => [ProgressBar::class, 'diskspace'], - ], - 'c.traffic' => [ - 'label' => $lng['customer']['traffic'], - 'column' => 'traffic', - 'format_callback' => [ProgressBar::class, 'traffic'], - ], - ], - 'visible_columns' => Listing::getVisibleColumnsForListing('customer_list', [ - 'c.loginname', - 'a.loginname', - 'c.email', - 'c.firstname', - 'c.company', - 'c.diskspace', - 'c.traffic', - ]), - 'actions' => [ - 'edit' => [ - 'icon' => 'fa fa-edit', - 'href' => [ - 'section' => 'customers', - 'page' => 'customers', - 'action' => 'edit', - 'id' => ':customerid' - ], - ], - 'delete' => [ - 'icon' => 'fa fa-trash', - 'class' => 'text-danger', - 'href' => [ - 'section' => 'customers', - 'page' => 'customers', - 'action' => 'delete', - 'id' => ':customerid' - ], - ], - ], - ] + 'customer_list' => [ + 'title' => $lng['admin']['customers'], + 'icon' => 'fa-solid fa-user', + 'columns' => [ + 'c.name' => [ + 'label' => $lng['customer']['name'], + 'column' => 'name', + 'format_callback' => [Text::class, 'customerfullname'], + ], + 'c.loginname' => [ + 'label' => $lng['login']['username'], + 'column' => 'loginname', + 'format_callback' => [Impersonate::class, 'customer'], + ], + 'a.loginname' => [ + 'label' => $lng['admin']['admin'], + 'column' => 'admin.loginname', + 'format_callback' => [Impersonate::class, 'admin'], + ], + 'c.email' => [ + 'label' => $lng['customer']['email'], + 'column' => 'email', + ], + 'c.diskspace' => [ + 'label' => $lng['customer']['diskspace'], + 'column' => 'diskspace', + 'format_callback' => [ProgressBar::class, 'diskspace'], + ], + 'c.traffic' => [ + 'label' => $lng['customer']['traffic'], + 'column' => 'traffic', + 'format_callback' => [ProgressBar::class, 'traffic'], + ], + ], + 'visible_columns' => Listing::getVisibleColumnsForListing('customer_list', [ + 'c.name', + 'c.loginname', + 'a.loginname', + 'c.email', + 'c.diskspace', + 'c.traffic', + ]), + 'actions' => [ + 'edit' => [ + 'icon' => 'fa fa-edit', + 'href' => [ + 'section' => 'customers', + 'page' => 'customers', + 'action' => 'edit', + 'id' => ':customerid' + ], + ], + 'delete' => [ + 'icon' => 'fa fa-trash', + 'class' => 'text-danger', + 'href' => [ + 'section' => 'customers', + 'page' => 'customers', + 'action' => 'delete', + 'id' => ':customerid' + ], + ], + ], + ] ]; diff --git a/lib/tablelisting/admin/tablelisting.domains.php b/lib/tablelisting/admin/tablelisting.domains.php index fb54fdcc..42001055 100644 --- a/lib/tablelisting/admin/tablelisting.domains.php +++ b/lib/tablelisting/admin/tablelisting.domains.php @@ -16,84 +16,76 @@ * */ +use Froxlor\UI\Callbacks\Text; use Froxlor\UI\Callbacks\Impersonate; use Froxlor\UI\Listing; return [ - 'domain_list' => [ - 'title' => $lng['admin']['domains'], - 'icon' => 'fa-solid fa-user', - 'columns' => [ - 'd.domain_ace' => [ - 'label' => $lng['domains']['domainname'], - 'column' => 'domain_ace', - ], - 'c.name' => [ - 'label' => $lng['customer']['name'], - 'column' => 'customer.name', - ], - 'c.firstname' => [ - 'label' => $lng['customer']['firstname'], - 'column' => 'customer.firstname', - ], - 'c.company' => [ - 'label' => $lng['customer']['company'], - 'column' => 'customer.company', - ], - 'c.loginname' => [ - 'label' => $lng['login']['username'], - 'column' => 'customer.loginname', - 'format_callback' => [Impersonate::class, 'customer'], - ], - 'd.aliasdomain' => [ - 'label' => $lng['domains']['aliasdomain'], - 'column' => 'aliasdomain', - ], - ], - 'visible_columns' => Listing::getVisibleColumnsForListing('domain_list', [ - 'd.domain_ace', - 'c.name', - 'c.firstname', - 'c.company', - 'c.loginname', - 'd.aliasdomain', - ]), - 'actions' => [ - 'edit' => [ - 'icon' => 'fa fa-edit', - 'href' => [ - 'section' => 'domains', - 'page' => 'domains', - 'action' => 'edit', - 'id' => ':id' - ], - ], - 'logfiles' => [ - 'icon' => 'fa fa-file', - 'href' => [ - 'section' => 'domains', - 'page' => 'logfiles', - 'domain_id' => ':id' - ], - ], - 'domaindnseditor' => [ - 'icon' => 'fa fa-globe', - 'href' => [ - 'section' => 'domains', - 'page' => 'domaindnseditor', - 'domain_id' => ':id' - ], - ], - 'delete' => [ - 'icon' => 'fa fa-trash', - 'class' => 'text-danger', - 'href' => [ - 'section' => 'domains', - 'page' => 'domains', - 'action' => 'delete', - 'id' => ':id' - ], - ], - ] - ] + 'domain_list' => [ + 'title' => $lng['admin']['domains'], + 'icon' => 'fa-solid fa-user', + 'columns' => [ + 'd.domain_ace' => [ + 'label' => $lng['domains']['domainname'], + 'column' => 'domain_ace', + ], + 'c.name' => [ + 'label' => $lng['customer']['name'], + 'column' => 'customer.name', + 'format_callback' => [Text::class, 'customerfullname'], + ], + 'c.loginname' => [ + 'label' => $lng['login']['username'], + 'column' => 'customer.loginname', + 'format_callback' => [Impersonate::class, 'customer'], + ], + 'd.aliasdomain' => [ + 'label' => $lng['domains']['aliasdomain'], + 'column' => 'aliasdomain', + ], + ], + 'visible_columns' => Listing::getVisibleColumnsForListing('domain_list', [ + 'd.domain_ace', + 'c.name', + 'c.loginname', + 'd.aliasdomain', + ]), + 'actions' => [ + 'edit' => [ + 'icon' => 'fa fa-edit', + 'href' => [ + 'section' => 'domains', + 'page' => 'domains', + 'action' => 'edit', + 'id' => ':id' + ], + ], + 'logfiles' => [ + 'icon' => 'fa fa-file', + 'href' => [ + 'section' => 'domains', + 'page' => 'logfiles', + 'domain_id' => ':id' + ], + ], + 'domaindnseditor' => [ + 'icon' => 'fa fa-globe', + 'href' => [ + 'section' => 'domains', + 'page' => 'domaindnseditor', + 'domain_id' => ':id' + ], + ], + 'delete' => [ + 'icon' => 'fa fa-trash', + 'class' => 'text-danger', + 'href' => [ + 'section' => 'domains', + 'page' => 'domains', + 'action' => 'delete', + 'id' => ':id' + ], + ] + ] + ] ]; diff --git a/lib/tablelisting/customer/tablelisting.domains.php b/lib/tablelisting/customer/tablelisting.domains.php new file mode 100644 index 00000000..84ba85f5 --- /dev/null +++ b/lib/tablelisting/customer/tablelisting.domains.php @@ -0,0 +1,79 @@ + (2010-) + * @author Maurice Preuß + * @license GPLv2 http://files.froxlor.org/misc/COPYING.txt + * @package Tabellisting + * + */ + +use Froxlor\UI\Callbacks\Domain; +use Froxlor\UI\Listing; + +return [ + 'domain_list' => [ + 'title' => $lng['admin']['domains'], + 'icon' => 'fa-solid fa-user', + 'columns' => [ + 'd.domain_ace' => [ + 'label' => $lng['domains']['domainname'], + 'column' => 'domain_ace', + ], + 'd.documentroot' => [ + 'label' => $lng['panel']['path'], + 'column' => 'documentroot', + 'format_callback' => [Domain::class, 'domainTarget'], + ] + ], + 'visible_columns' => Listing::getVisibleColumnsForListing('domain_list', [ + 'd.domain_ace', + 'd.documentroot' + ]), + 'actions' => [ + 'edit' => [ + 'icon' => 'fa fa-edit', + 'href' => [ + 'section' => 'domains', + 'page' => 'domains', + 'action' => 'edit', + 'id' => ':id' + ], + ], + 'logfiles' => [ + 'icon' => 'fa fa-file', + 'href' => [ + 'section' => 'domains', + 'page' => 'logfiles', + 'domain_id' => ':id' + ], + ], + 'domaindnseditor' => [ + 'icon' => 'fa fa-globe', + 'href' => [ + 'section' => 'domains', + 'page' => 'domaindnseditor', + 'domain_id' => ':id' + ], + ], + 'delete' => [ + 'icon' => 'fa fa-trash', + 'class' => 'text-danger', + 'href' => [ + 'section' => 'domains', + 'page' => 'domains', + 'action' => 'delete', + 'id' => ':id' + ], + ] + ] + ] +];