Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
0.00% |
0 / 87 |
|
0.00% |
0 / 45 |
|
0.00% |
0 / 32 |
|
0.00% |
0 / 14 |
CRAP | |
0.00% |
0 / 1 |
| ViewDisplay | |
0.00% |
0 / 83 |
|
0.00% |
0 / 45 |
|
0.00% |
0 / 32 |
|
0.00% |
0 / 14 |
812 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 11 |
|
0.00% |
0 / 5 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
| getBuilderUrl | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| checkInstanceId | |
0.00% |
0 / 7 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| checkAccess | |
0.00% |
0 / 1 |
|
0.00% |
0 / 4 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| getUrlFromInstanceId | |
0.00% |
0 / 4 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| getDisplayUrlFromInstanceId | |
0.00% |
0 / 4 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| getProfile | |
0.00% |
0 / 8 |
|
0.00% |
0 / 7 |
|
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
| getSources | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| saveSources | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getInstanceId | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| collectInstances | |
0.00% |
0 / 20 |
|
0.00% |
0 / 9 |
|
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
| getRuntimeContexts | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getInitializationMessage | |
0.00% |
0 / 3 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| getInitialSources | |
0.00% |
0 / 5 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Drupal\display_builder_views\Plugin\display_builder\Buildable; |
| 6 | |
| 7 | use Drupal\Core\Access\AccessResult; |
| 8 | use Drupal\Core\Access\AccessResultInterface; |
| 9 | use Drupal\Core\Plugin\Context\Context; |
| 10 | use Drupal\Core\Plugin\Context\ContextDefinition; |
| 11 | use Drupal\Core\Plugin\Context\EntityContext; |
| 12 | use Drupal\Core\Session\AccountInterface; |
| 13 | use Drupal\Core\StringTranslation\TranslatableMarkup; |
| 14 | use Drupal\Core\Url; |
| 15 | use Drupal\display_builder\Attribute\DisplayBuildable; |
| 16 | use Drupal\display_builder\DisplayBuildableInterface; |
| 17 | use Drupal\display_builder\DisplayBuildablePluginBase; |
| 18 | use Drupal\display_builder\DisplayBuilderHelpers; |
| 19 | use Drupal\display_builder\Entity\ProfileInterface; |
| 20 | use Drupal\ui_patterns\Plugin\Context\RequirementsContext; |
| 21 | use Drupal\views\Entity\View; |
| 22 | use Drupal\views\Plugin\views\PluginBase; |
| 23 | |
| 24 | /** |
| 25 | * Plugin implementation of the display_buildable. |
| 26 | */ |
| 27 | #[DisplayBuildable( |
| 28 | id: 'view_display', |
| 29 | label: new TranslatableMarkup('Views'), |
| 30 | instance_prefix: 'views__', |
| 31 | )] |
| 32 | final class ViewDisplay extends DisplayBuildablePluginBase { |
| 33 | |
| 34 | /** |
| 35 | * View display extender plugin. |
| 36 | */ |
| 37 | protected PluginBase $extender; |
| 38 | |
| 39 | /** |
| 40 | * {@inheritdoc} |
| 41 | */ |
| 42 | public function __construct(array $configuration, $plugin_id, $plugin_definition) { |
| 43 | parent::__construct($configuration, $plugin_id, $plugin_definition); |
| 44 | |
| 45 | // It is better to take the plugin from configuration when available in |
| 46 | // order to manipulate the view "executable" from the tempstore. So, we |
| 47 | // have access to the state not yet saved in config. |
| 48 | if (isset($configuration['extender'])) { |
| 49 | $this->extender = $configuration['extender']; |
| 50 | // Configuration to store in the Instance entity. |
| 51 | unset($this->configuration['extender']); |
| 52 | $this->configuration['view_id'] = $this->extender->view->id(); |
| 53 | $this->configuration['view_display'] = $this->extender->view->current_display; |
| 54 | |
| 55 | return; |
| 56 | } |
| 57 | |
| 58 | // If extender plugin is not directly passed, we can get it from the |
| 59 | // configuration data (as stored in Instance entity): |
| 60 | // - view_id (string) |
| 61 | // - view_display (string) |
| 62 | // However, this is loading a "real" View entity, as stored in config. So |
| 63 | // it may miss unsaved parameters. |
| 64 | $view = View::load($configuration['view_id'] ?? '')?->getExecutable() ?? NULL; |
| 65 | |
| 66 | if ($view) { |
| 67 | $view->setDisplay($configuration['view_display'] ?? ''); |
| 68 | $this->extender = $view->getDisplay()->getExtenders()['display_builder']; |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | /** |
| 73 | * {@inheritdoc} |
| 74 | */ |
| 75 | public function getBuilderUrl(): Url { |
| 76 | $params = [ |
| 77 | 'view' => $this->extender->view->id(), |
| 78 | 'display' => $this->extender->view->current_display, |
| 79 | ]; |
| 80 | |
| 81 | return Url::fromRoute('display_builder_views.views.manage', $params); |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * {@inheritdoc} |
| 86 | */ |
| 87 | public static function checkInstanceId(string $instance_id): ?array { |
| 88 | if (!\str_starts_with($instance_id, self::getPrefix())) { |
| 89 | return NULL; |
| 90 | } |
| 91 | [, $view, $display] = \explode('__', $instance_id); |
| 92 | |
| 93 | return [ |
| 94 | 'view' => $view, |
| 95 | 'display' => $display, |
| 96 | ]; |
| 97 | } |
| 98 | |
| 99 | /** |
| 100 | * {@inheritdoc} |
| 101 | */ |
| 102 | public static function checkAccess(string $instance_id, AccountInterface $account): AccessResultInterface { |
| 103 | return $account->hasPermission('administer views') ? AccessResult::allowed() : AccessResult::forbidden(); |
| 104 | } |
| 105 | |
| 106 | /** |
| 107 | * {@inheritdoc} |
| 108 | */ |
| 109 | public static function getUrlFromInstanceId(string $instance_id): Url { |
| 110 | $params = self::checkInstanceId($instance_id); |
| 111 | |
| 112 | if (!$params) { |
| 113 | // Fallback to the list of instances. |
| 114 | return Url::fromRoute('entity.display_builder_instance.collection'); |
| 115 | } |
| 116 | |
| 117 | return Url::fromRoute('display_builder_views.views.manage', $params); |
| 118 | } |
| 119 | |
| 120 | /** |
| 121 | * {@inheritdoc} |
| 122 | */ |
| 123 | public static function getDisplayUrlFromInstanceId(string $instance_id): Url { |
| 124 | $params = self::checkInstanceId($instance_id); |
| 125 | |
| 126 | if (!$params) { |
| 127 | // Fallback to the list of instances. |
| 128 | return Url::fromRoute('entity.display_builder_instance.collection'); |
| 129 | } |
| 130 | |
| 131 | return Url::fromRoute('entity.view.edit_form', $params); |
| 132 | } |
| 133 | |
| 134 | /** |
| 135 | * {@inheritdoc} |
| 136 | */ |
| 137 | public function getProfile(): ?ProfileInterface { |
| 138 | $display_builder_id = $this->extender->options[DisplayBuildableInterface::PROFILE_PROPERTY] ?? NULL; |
| 139 | |
| 140 | if ($display_builder_id === NULL && $this->getInstance()) { |
| 141 | return $this->getInstance()->getProfile(); |
| 142 | } |
| 143 | |
| 144 | if (empty($display_builder_id)) { |
| 145 | return NULL; |
| 146 | } |
| 147 | $storage = $this->entityTypeManager->getStorage('display_builder_profile'); |
| 148 | |
| 149 | /** @var \Drupal\display_builder\Entity\ProfileInterface $display_builder */ |
| 150 | $display_builder = $storage->load($display_builder_id); |
| 151 | |
| 152 | return $display_builder; |
| 153 | } |
| 154 | |
| 155 | /** |
| 156 | * {@inheritdoc} |
| 157 | */ |
| 158 | public function getSources(): array { |
| 159 | return $this->extender->options[DisplayBuildableInterface::SOURCES_PROPERTY] ?? []; |
| 160 | } |
| 161 | |
| 162 | /** |
| 163 | * {@inheritdoc} |
| 164 | */ |
| 165 | public function saveSources(): void { |
| 166 | $sources = $this->getInstance()->getCurrentState(); |
| 167 | // First, we save in the "live" object. |
| 168 | $this->extender->options[DisplayBuildableInterface::SOURCES_PROPERTY] = $sources; |
| 169 | // Then, we save in the permanent storage. |
| 170 | $displays = $this->extender->view->storage->get('display'); |
| 171 | $display_id = $this->extender->view->current_display; |
| 172 | // It is risky to alter a View like that. We need to be careful to not |
| 173 | // break the storage integrity, but we didn't find a better way. |
| 174 | $displays[$display_id]['display_options']['display_extenders']['display_builder'][DisplayBuildableInterface::SOURCES_PROPERTY] = $sources; |
| 175 | $this->extender->view->storage->set('display', $displays); |
| 176 | $this->extender->view->storage->save(); |
| 177 | // @todo Test if we still need to invalidate the cache manually here. |
| 178 | $this->extender->view->storage->invalidateCaches(); |
| 179 | } |
| 180 | |
| 181 | /** |
| 182 | * {@inheritdoc} |
| 183 | */ |
| 184 | public function getInstanceId(): string { |
| 185 | return \sprintf('%s%s__%s', self::getPrefix(), $this->extender->view->id(), $this->extender->view->current_display); |
| 186 | } |
| 187 | |
| 188 | /** |
| 189 | * {@inheritdoc} |
| 190 | */ |
| 191 | public static function collectInstances(): array { |
| 192 | $entityTypeManager = \Drupal::service('entity_type.manager'); |
| 193 | $displayBuildableManager = \Drupal::service('plugin.manager.display_buildable'); |
| 194 | $storage = $entityTypeManager->getStorage('view'); |
| 195 | $instances = []; |
| 196 | |
| 197 | foreach ($storage->loadMultiple() as $view) { |
| 198 | // @phpstan-ignore-next-line |
| 199 | foreach ($view->display as $display_id => $display) { |
| 200 | $profile = $display['display_options']['display_extenders']['display_builder'][DisplayBuildableInterface::PROFILE_PROPERTY] ?? NULL; |
| 201 | |
| 202 | if (!$profile) { |
| 203 | continue; |
| 204 | } |
| 205 | $instance_id = \sprintf('%s%s__%s', self::getPrefix(), $view->id(), $display_id); |
| 206 | /** @var \Drupal\display_builder\DisplayBuildableInterface $buildable */ |
| 207 | $buildable = $displayBuildableManager->createInstance('view_display', |
| 208 | [ |
| 209 | 'view_id' => $view->id(), |
| 210 | 'view_display' => $display_id, |
| 211 | ] |
| 212 | ); |
| 213 | $buildable->initInstanceIfMissing(); |
| 214 | $instance_id = $buildable->getInstanceId(); |
| 215 | $instances[$instance_id] = $buildable->getInstance(); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | return $instances; |
| 220 | } |
| 221 | |
| 222 | /** |
| 223 | * {@inheritdoc} |
| 224 | */ |
| 225 | public function getRuntimeContexts(array $unqualified_context_ids): array { |
| 226 | // Contexts needed by UI Patterns Source can be added by each Display |
| 227 | // Buildable plugin by overriding this method. |
| 228 | // @todo filter by $unqualified_context_ids. |
| 229 | $contexts = []; |
| 230 | $contexts['ui_patterns_views:view_entity'] = EntityContext::fromEntity($this->extender->view->storage); |
| 231 | // Needed by ui_patterns_views's ViewRowsSource. |
| 232 | // Will be filled by \Drupal\display_builder_views\Hook\PreprocessViewsView. |
| 233 | $contexts['ui_patterns_views:rows'] = new Context(new ContextDefinition('any'), []); |
| 234 | $contexts = RequirementsContext::addToContext(['views:style'], $contexts); |
| 235 | |
| 236 | return $contexts; |
| 237 | } |
| 238 | |
| 239 | /** |
| 240 | * {@inheritdoc} |
| 241 | */ |
| 242 | protected function getInitializationMessage(): TranslatableMarkup { |
| 243 | if ($this->initialDataSource === 'fixture') { |
| 244 | return $this->t('Initialize display from default configuration'); |
| 245 | } |
| 246 | |
| 247 | return $this->t('Initialize display from existing View configuration'); |
| 248 | } |
| 249 | |
| 250 | /** |
| 251 | * {@inheritdoc} |
| 252 | */ |
| 253 | protected function getInitialSources(): array { |
| 254 | // Get the sources stored in config. |
| 255 | $sources = $this->getSources(); |
| 256 | |
| 257 | if (empty($sources)) { |
| 258 | // Fallback to a fixture mimicking the standard view layout. |
| 259 | $sources = DisplayBuilderHelpers::getFixtureDataFromExtension('display_builder_views', 'default_view'); |
| 260 | $this->initialDataSource = 'fixture'; |
| 261 | } |
| 262 | |
| 263 | return $sources; |
| 264 | } |
| 265 | |
| 266 | } |
Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not
necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once.
Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 42 | public function __construct(array $configuration, $plugin_id, $plugin_definition) { |
| 43 | parent::__construct($configuration, $plugin_id, $plugin_definition); |
| 44 | |
| 45 | // It is better to take the plugin from configuration when available in |
| 46 | // order to manipulate the view "executable" from the tempstore. So, we |
| 47 | // have access to the state not yet saved in config. |
| 48 | if (isset($configuration['extender'])) { |
| 49 | $this->extender = $configuration['extender']; |
| 50 | // Configuration to store in the Instance entity. |
| 51 | unset($this->configuration['extender']); |
| 52 | $this->configuration['view_id'] = $this->extender->view->id(); |
| 53 | $this->configuration['view_display'] = $this->extender->view->current_display; |
| 54 | |
| 55 | return; |
| 64 | $view = View::load($configuration['view_id'] ?? '')?->getExecutable() ?? NULL; |
| 65 | |
| 66 | if ($view) { |
| 67 | $view->setDisplay($configuration['view_display'] ?? ''); |
| 68 | $this->extender = $view->getDisplay()->getExtenders()['display_builder']; |
| 69 | } |
| 70 | } |
| 70 | } |
| 102 | public static function checkAccess(string $instance_id, AccountInterface $account): AccessResultInterface { |
| 103 | return $account->hasPermission('administer views') ? AccessResult::allowed() : AccessResult::forbidden(); |
| 103 | return $account->hasPermission('administer views') ? AccessResult::allowed() : AccessResult::forbidden(); |
| 103 | return $account->hasPermission('administer views') ? AccessResult::allowed() : AccessResult::forbidden(); |
| 103 | return $account->hasPermission('administer views') ? AccessResult::allowed() : AccessResult::forbidden(); |
| 104 | } |
| 87 | public static function checkInstanceId(string $instance_id): ?array { |
| 88 | if (!\str_starts_with($instance_id, self::getPrefix())) { |
| 89 | return NULL; |
| 91 | [, $view, $display] = \explode('__', $instance_id); |
| 92 | |
| 93 | return [ |
| 94 | 'view' => $view, |
| 95 | 'display' => $display, |
| 96 | ]; |
| 97 | } |
| 192 | $entityTypeManager = \Drupal::service('entity_type.manager'); |
| 193 | $displayBuildableManager = \Drupal::service('plugin.manager.display_buildable'); |
| 194 | $storage = $entityTypeManager->getStorage('view'); |
| 195 | $instances = []; |
| 196 | |
| 197 | foreach ($storage->loadMultiple() as $view) { |
| 197 | foreach ($storage->loadMultiple() as $view) { |
| 199 | foreach ($view->display as $display_id => $display) { |
| 199 | foreach ($view->display as $display_id => $display) { |
| 199 | foreach ($view->display as $display_id => $display) { |
| 200 | $profile = $display['display_options']['display_extenders']['display_builder'][DisplayBuildableInterface::PROFILE_PROPERTY] ?? NULL; |
| 201 | |
| 202 | if (!$profile) { |
| 203 | continue; |
| 199 | foreach ($view->display as $display_id => $display) { |
| 200 | $profile = $display['display_options']['display_extenders']['display_builder'][DisplayBuildableInterface::PROFILE_PROPERTY] ?? NULL; |
| 201 | |
| 202 | if (!$profile) { |
| 203 | continue; |
| 204 | } |
| 205 | $instance_id = \sprintf('%s%s__%s', self::getPrefix(), $view->id(), $display_id); |
| 197 | foreach ($storage->loadMultiple() as $view) { |
| 198 | // @phpstan-ignore-next-line |
| 199 | foreach ($view->display as $display_id => $display) { |
| 197 | foreach ($storage->loadMultiple() as $view) { |
| 198 | // @phpstan-ignore-next-line |
| 199 | foreach ($view->display as $display_id => $display) { |
| 200 | $profile = $display['display_options']['display_extenders']['display_builder'][DisplayBuildableInterface::PROFILE_PROPERTY] ?? NULL; |
| 201 | |
| 202 | if (!$profile) { |
| 203 | continue; |
| 204 | } |
| 205 | $instance_id = \sprintf('%s%s__%s', self::getPrefix(), $view->id(), $display_id); |
| 206 | /** @var \Drupal\display_builder\DisplayBuildableInterface $buildable */ |
| 207 | $buildable = $displayBuildableManager->createInstance('view_display', |
| 208 | [ |
| 209 | 'view_id' => $view->id(), |
| 210 | 'view_display' => $display_id, |
| 211 | ] |
| 212 | ); |
| 213 | $buildable->initInstanceIfMissing(); |
| 214 | $instance_id = $buildable->getInstanceId(); |
| 215 | $instances[$instance_id] = $buildable->getInstance(); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | return $instances; |
| 220 | } |
| 77 | 'view' => $this->extender->view->id(), |
| 78 | 'display' => $this->extender->view->current_display, |
| 79 | ]; |
| 80 | |
| 81 | return Url::fromRoute('display_builder_views.views.manage', $params); |
| 82 | } |
| 123 | public static function getDisplayUrlFromInstanceId(string $instance_id): Url { |
| 124 | $params = self::checkInstanceId($instance_id); |
| 125 | |
| 126 | if (!$params) { |
| 128 | return Url::fromRoute('entity.display_builder_instance.collection'); |
| 131 | return Url::fromRoute('entity.view.edit_form', $params); |
| 132 | } |
| 255 | $sources = $this->getSources(); |
| 256 | |
| 257 | if (empty($sources)) { |
| 259 | $sources = DisplayBuilderHelpers::getFixtureDataFromExtension('display_builder_views', 'default_view'); |
| 260 | $this->initialDataSource = 'fixture'; |
| 261 | } |
| 262 | |
| 263 | return $sources; |
| 263 | return $sources; |
| 264 | } |
| 243 | if ($this->initialDataSource === 'fixture') { |
| 244 | return $this->t('Initialize display from default configuration'); |
| 247 | return $this->t('Initialize display from existing View configuration'); |
| 248 | } |
| 185 | return \sprintf('%s%s__%s', self::getPrefix(), $this->extender->view->id(), $this->extender->view->current_display); |
| 186 | } |
| 138 | $display_builder_id = $this->extender->options[DisplayBuildableInterface::PROFILE_PROPERTY] ?? NULL; |
| 139 | |
| 140 | if ($display_builder_id === NULL && $this->getInstance()) { |
| 140 | if ($display_builder_id === NULL && $this->getInstance()) { |
| 140 | if ($display_builder_id === NULL && $this->getInstance()) { |
| 141 | return $this->getInstance()->getProfile(); |
| 144 | if (empty($display_builder_id)) { |
| 145 | return NULL; |
| 147 | $storage = $this->entityTypeManager->getStorage('display_builder_profile'); |
| 148 | |
| 149 | /** @var \Drupal\display_builder\Entity\ProfileInterface $display_builder */ |
| 150 | $display_builder = $storage->load($display_builder_id); |
| 151 | |
| 152 | return $display_builder; |
| 153 | } |
| 225 | public function getRuntimeContexts(array $unqualified_context_ids): array { |
| 226 | // Contexts needed by UI Patterns Source can be added by each Display |
| 227 | // Buildable plugin by overriding this method. |
| 228 | // @todo filter by $unqualified_context_ids. |
| 229 | $contexts = []; |
| 230 | $contexts['ui_patterns_views:view_entity'] = EntityContext::fromEntity($this->extender->view->storage); |
| 231 | // Needed by ui_patterns_views's ViewRowsSource. |
| 232 | // Will be filled by \Drupal\display_builder_views\Hook\PreprocessViewsView. |
| 233 | $contexts['ui_patterns_views:rows'] = new Context(new ContextDefinition('any'), []); |
| 234 | $contexts = RequirementsContext::addToContext(['views:style'], $contexts); |
| 235 | |
| 236 | return $contexts; |
| 237 | } |
| 159 | return $this->extender->options[DisplayBuildableInterface::SOURCES_PROPERTY] ?? []; |
| 160 | } |
| 109 | public static function getUrlFromInstanceId(string $instance_id): Url { |
| 110 | $params = self::checkInstanceId($instance_id); |
| 111 | |
| 112 | if (!$params) { |
| 114 | return Url::fromRoute('entity.display_builder_instance.collection'); |
| 117 | return Url::fromRoute('display_builder_views.views.manage', $params); |
| 118 | } |
| 166 | $sources = $this->getInstance()->getCurrentState(); |
| 167 | // First, we save in the "live" object. |
| 168 | $this->extender->options[DisplayBuildableInterface::SOURCES_PROPERTY] = $sources; |
| 169 | // Then, we save in the permanent storage. |
| 170 | $displays = $this->extender->view->storage->get('display'); |
| 171 | $display_id = $this->extender->view->current_display; |
| 172 | // It is risky to alter a View like that. We need to be careful to not |
| 173 | // break the storage integrity, but we didn't find a better way. |
| 174 | $displays[$display_id]['display_options']['display_extenders']['display_builder'][DisplayBuildableInterface::SOURCES_PROPERTY] = $sources; |
| 175 | $this->extender->view->storage->set('display', $displays); |
| 176 | $this->extender->view->storage->save(); |
| 177 | // @todo Test if we still need to invalidate the cache manually here. |
| 178 | $this->extender->view->storage->invalidateCaches(); |
| 179 | } |