diff --git a/core/lib/Drupal/Core/Condition/ConditionAccessResolverTrait.php b/core/lib/Drupal/Core/Condition/ConditionAccessResolverTrait.php new file mode 100644 index 0000000..f5723e8 --- /dev/null +++ b/core/lib/Drupal/Core/Condition/ConditionAccessResolverTrait.php @@ -0,0 +1,76 @@ +getConfiguration(); + if (isset($configuration['context_assignments'])) { + $assignments = array_flip($configuration['context_assignments']); + } + } + $this->contextHandler()->applyContextMapping($condition, $contexts, $assignments); + } + + try { + $pass = $condition->execute(); + } + catch (PluginException $e) { + // If a condition is missing context, consider that a fail. + $pass = FALSE; + } + + // If a condition fails and all conditions were required, deny access. + if (!$pass && $condition_logic == 'and') { + return FALSE; + } + // If a condition passes and one condition was required, grant access. + elseif ($pass && $condition_logic == 'or') { + return TRUE; + } + } + + // If no conditions passed and one condition was required, deny access. + return $condition_logic == 'and'; + } + +} diff --git a/core/lib/Drupal/Core/Condition/ConditionPluginBag.php b/core/lib/Drupal/Core/Condition/ConditionPluginBag.php new file mode 100644 index 0000000..b15f302 --- /dev/null +++ b/core/lib/Drupal/Core/Condition/ConditionPluginBag.php @@ -0,0 +1,26 @@ +hasPermission('access news feeds'); } diff --git a/core/modules/block/src/BlockBase.php b/core/modules/block/src/BlockBase.php index d0ebe65..4997c23 100644 --- a/core/modules/block/src/BlockBase.php +++ b/core/modules/block/src/BlockBase.php @@ -7,6 +7,9 @@ namespace Drupal\block; +use Drupal\Core\Condition\ConditionAccessResolverTrait; +use Drupal\Core\Condition\ConditionPluginBag; +use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\ContextAwarePluginBase; use Drupal\block\BlockInterface; use Drupal\Component\Utility\Unicode; @@ -14,8 +17,9 @@ use Drupal\Core\Language\Language; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheableInterface; -use Drupal\Core\Plugin\DefaultPluginBag; use Drupal\Core\Session\AccountInterface; +use Drupal\user\Entity\User; +use Symfony\Cmf\Component\Routing\RouteObjectInterface; /** * Defines a base block implementation that most blocks plugins will extend. @@ -28,10 +32,12 @@ */ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface { + use ConditionAccessResolverTrait; + /** * The condition plugin bag. * - * @var \Drupal\Core\Plugin\DefaultPluginBag + * @var \Drupal\Core\Condition\ConditionPluginBag */ protected $conditionBag; @@ -45,15 +51,6 @@ /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition) { - parent::__construct($configuration, $plugin_id, $plugin_definition); - - $this->setConfiguration($configuration); - } - - /** - * {@inheritdoc} - */ public function label() { if (!empty($this->configuration['label'])) { return $this->configuration['label']; @@ -68,8 +65,19 @@ public function label() { /** * {@inheritdoc} */ + public function __construct(array $configuration, $plugin_id, $plugin_definition) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + + $this->setConfiguration($configuration); + } + + /** + * {@inheritdoc} + */ public function getConfiguration() { - return array('visibility' => $this->getConditions()->getConfiguration()) + $this->configuration; + return array( + 'visibility' => $this->getConditions()->getConfiguration(), + ) + $this->configuration; } /** @@ -90,6 +98,20 @@ public function setConfiguration(array $configuration) { * An associative array with the default configuration. */ protected function baseConfigurationDefaults() { + // @todo Allow list of conditions to be configured. + $visibility = array(); + if (\Drupal::moduleHandler()->moduleExists('system')) { + $visibility['request_path'] = array('id' => 'request_path'); + } + if (\Drupal::moduleHandler()->moduleExists('user')) { + $visibility['user_role'] = array('id' => 'user_role'); + } + if (\Drupal::moduleHandler()->moduleExists('node')) { + $visibility['node_type'] = array('id' => 'node_type'); + } + if (\Drupal::moduleHandler()->moduleExists('language')) { + $visibility['language'] = array('id' => 'language'); + } return array( 'id' => $this->getPluginId(), 'label' => '', @@ -99,6 +121,7 @@ protected function baseConfigurationDefaults() { 'max_age' => 0, 'contexts' => array(), ), + 'visibility' => $visibility, ); } @@ -127,6 +150,55 @@ public function calculateDependencies() { * {@inheritdoc} */ public function access(AccountInterface $account) { + if ($this->resolveConditions($this->getConditions(), 'and', $this->getConditionContexts()) === FALSE) { + return FALSE; + } + return $this->blockAccess($account); + } + + /** + * Gets the values for all defined contexts. + * + * @return \Drupal\Component\Plugin\Context\ContextInterface[] + * An array of set contexts, keyed by context name. + */ + protected function getConditionContexts() { + $context = new Context(array( + 'type' => 'entity:user', + 'label' => $this->t('Current user'), + )); + $context->setContextValue(User::load(\Drupal::currentUser()->id())); + $contexts['user'] = $context; + + $request = \Drupal::request(); + if ($route_contexts = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)->getOption('parameters')) { + foreach ($route_contexts as $route_context_name => $route_context) { + // Skip the block itself. + if ($route_context_name == 'block') { + continue; + } + $context = new Context($route_context); + if ($request->attributes->has($route_context_name)) { + $context->setContextValue($request->attributes->get($route_context_name)); + } + $contexts[$route_context_name] = $context; + } + } + return $contexts; + } + + /** + * Indicates whether the block should be shown. + * + * @param \Drupal\Core\Session\AccountInterface $account + * The user session for which to check access. + * + * @return bool + * TRUE if the block should be shown, or FALSE otherwise. + * + * @see self::access() + */ + protected function blockAccess(AccountInterface $account) { // By default, the block is visible unless user-configured rules indicate // that it should be hidden. return TRUE; @@ -212,10 +284,27 @@ public function buildConfigurationForm(array $form, array &$form_state) { $form['cache']['contexts']['#description'] .= ' ' . t('This block is always varied by the following contexts: %required-context-list.', array('%required-context-list' => $required_context_list)); } + $form['visibility'] = array( + '#type' => 'vertical_tabs', + '#title' => $this->t('Visibility'), + ); + foreach ($this->getConditions() as $condition_id => $condition) { + $form['visibility'][$condition_id] = array( + '#type' => 'details', + '#title' => $condition->getPluginDefinition()['label'], + '#group' => 'visibility', + ); + $form['visibility'][$condition_id]['form'] = $condition->buildConfigurationForm(array(), $form_state); + $form['visibility'][$condition_id]['form']['#process'][] = array($this, 'processVisibilityForm'); + } // Add plugin-specific settings for this block type. $form += $this->blockForm($form, $form_state); return $form; } + public function processVisibilityForm($element, &$form_state) { + array_pop($element['#parents']); + return $element; + } /** * {@inheritdoc} @@ -236,6 +325,15 @@ public function validateConfigurationForm(array &$form, array &$form_state) { // Transform the #type = checkboxes value to a numerically indexed array. $form_state['values']['cache']['contexts'] = array_values(array_filter($form_state['values']['cache']['contexts'])); + foreach ($this->getConditions() as $condition_id => $condition) { + // Allow the condition to validate the form. + $condition_values = array( + 'values' => &$form_state['values']['visibility'][$condition_id], + ); + $condition->validateConfigurationForm($form, $condition_values); + + } + $this->blockValidate($form, $form_state); } @@ -259,6 +357,13 @@ public function submitConfigurationForm(array &$form, array &$form_state) { $this->configuration['label_display'] = $form_state['values']['label_display']; $this->configuration['provider'] = $form_state['values']['provider']; $this->configuration['cache'] = $form_state['values']['cache']; + foreach ($this->getConditions() as $condition_id => $condition) { + // Allow the condition to submit the form. + $condition_values = array( + 'values' => &$form_state['values']['visibility'][$condition_id], + ); + $condition->submitConfigurationForm($form, $condition_values); + } $this->blockSubmit($form, $form_state); } } @@ -349,16 +454,13 @@ public function isCacheable() { /** * Gets conditions for this block. + * + * @return \Drupal\Core\Condition\ConditionInterface[]|\Drupal\Core\Condition\ConditionPluginBag + * An array of configured condition plugins. */ public function getConditions() { if (!isset($this->conditionBag)) { - // @todo Allow list of conditions to be configured. - $this->conditionBag = new DefaultPluginBag($this->conditionPluginManager(), array( - 'request_path', - 'node_type', - 'user_role', - 'language', - )); + $this->conditionBag = new ConditionPluginBag($this->conditionPluginManager(), $this->configuration['visibility']); } return $this->conditionBag; } diff --git a/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestBlockInstantiation.php b/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestBlockInstantiation.php index 85856a3..9a4db70 100644 --- a/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestBlockInstantiation.php +++ b/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestBlockInstantiation.php @@ -32,7 +32,7 @@ public function defaultConfiguration() { /** * {@inheritdoc} */ - public function access(AccountInterface $account) { + protected function blockAccess(AccountInterface $account) { return $account->hasPermission('access content'); } diff --git a/core/modules/block/tests/src/BlockBaseTest.php b/core/modules/block/tests/src/BlockBaseTest.php index 6e70895..dcd6e8c 100644 --- a/core/modules/block/tests/src/BlockBaseTest.php +++ b/core/modules/block/tests/src/BlockBaseTest.php @@ -34,12 +34,14 @@ public static function getInfo() { * @see \Drupal\block\BlockBase::getMachineNameSuggestion(). */ public function testGetMachineNameSuggestion() { + $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); $transliteraton = $this->getMockBuilder('Drupal\Core\Transliteration\PHPTransliteration') // @todo Inject the module handler into PHPTransliteration. ->setMethods(array('readLanguageOverrides')) ->getMock(); $container = new ContainerBuilder(); + $container->set('module_handler', $module_handler); $container->set('transliteration', $transliteraton); \Drupal::setContainer($container); @@ -64,6 +66,18 @@ public function testGetMachineNameSuggestion() { * Tests initialising the condition plugins initialisation. */ public function testCondtionsBagInitialisation() { + $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); + $module_handler->expects($this->exactly(4)) + ->method('moduleExists') + ->will($this->returnValueMap(array( + array('system', TRUE), + array('user', TRUE), + array('node', TRUE), + array('language', TRUE), + ))); + $container = new ContainerBuilder(); + $container->set('module_handler', $module_handler); + \Drupal::setContainer($container); $config = array(); $definition = array( 'admin_label' => 'Admin label', @@ -108,5 +122,4 @@ protected function conditionPluginManager() { return $this->mockPluginManager; } - } diff --git a/core/modules/forum/src/Plugin/Block/ForumBlockBase.php b/core/modules/forum/src/Plugin/Block/ForumBlockBase.php index d7dfc31..6a5b492 100644 --- a/core/modules/forum/src/Plugin/Block/ForumBlockBase.php +++ b/core/modules/forum/src/Plugin/Block/ForumBlockBase.php @@ -56,7 +56,7 @@ public function defaultConfiguration() { /** * {@inheritdoc} */ - public function access(AccountInterface $account) { + protected function blockAccess(AccountInterface $account) { return $account->hasPermission('access content'); } diff --git a/core/modules/language/src/Plugin/Block/LanguageBlock.php b/core/modules/language/src/Plugin/Block/LanguageBlock.php index 4a6c7bc..42d5bf3 100644 --- a/core/modules/language/src/Plugin/Block/LanguageBlock.php +++ b/core/modules/language/src/Plugin/Block/LanguageBlock.php @@ -66,7 +66,7 @@ public static function create(ContainerInterface $container, array $configuratio /** * {@inheritdoc} */ - function access(AccountInterface $account) { + protected function blockAccess(AccountInterface $account) { return $this->languageManager->isMultilingual(); } diff --git a/core/modules/node/src/Plugin/Block/SyndicateBlock.php b/core/modules/node/src/Plugin/Block/SyndicateBlock.php index efc1bdf..ace314a 100644 --- a/core/modules/node/src/Plugin/Block/SyndicateBlock.php +++ b/core/modules/node/src/Plugin/Block/SyndicateBlock.php @@ -33,7 +33,7 @@ public function defaultConfiguration() { /** * {@inheritdoc} */ - public function access(AccountInterface $account) { + protected function blockAccess(AccountInterface $account) { return $account->hasPermission('access content'); } diff --git a/core/modules/node/src/Plugin/Condition/NodeType.php b/core/modules/node/src/Plugin/Condition/NodeType.php index 74cdf5d..1aeb9e2 100644 --- a/core/modules/node/src/Plugin/Condition/NodeType.php +++ b/core/modules/node/src/Plugin/Condition/NodeType.php @@ -37,7 +37,6 @@ public function buildConfigurationForm(array $form, array &$form_state) { '#title' => t('Node types'), '#type' => 'checkboxes', '#options' => $options, - '#required' => TRUE, '#default_value' => isset($this->configuration['bundles']) ? $this->configuration['bundles'] : array(), ); return $form; @@ -81,7 +80,7 @@ public function summary() { */ public function evaluate() { $node = $this->getContextValue('node'); - return !empty($this->configuration['bundles'][$node->getType()]); + return empty($this->configuration['bundles']) || !empty($this->configuration['bundles'][$node->getType()]); } } diff --git a/core/modules/search/src/Plugin/Block/SearchBlock.php b/core/modules/search/src/Plugin/Block/SearchBlock.php index c2e3e51..64b3096 100644 --- a/core/modules/search/src/Plugin/Block/SearchBlock.php +++ b/core/modules/search/src/Plugin/Block/SearchBlock.php @@ -26,7 +26,7 @@ class SearchBlock extends BlockBase { /** * {@inheritdoc} */ - public function access(AccountInterface $account) { + protected function blockAccess(AccountInterface $account) { return $account->hasPermission('search content'); } diff --git a/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php b/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php index 1a19761..3bdacac 100644 --- a/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php +++ b/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php @@ -55,7 +55,7 @@ public function defaultConfiguration() { /** * {@inheritdoc} */ - public function access(AccountInterface $account) { + protected function blockAccess(AccountInterface $account) { if ($account->hasPermission('access content')) { $daytop = $this->configuration['top_day_num']; if (!$daytop || !($result = statistics_title_list('daycount', $daytop)) || !($this->day_list = node_title_list($result, t("Today's:")))) { diff --git a/core/modules/system/src/Plugin/Block/SystemHelpBlock.php b/core/modules/system/src/Plugin/Block/SystemHelpBlock.php index d15cfc6..e99787c 100644 --- a/core/modules/system/src/Plugin/Block/SystemHelpBlock.php +++ b/core/modules/system/src/Plugin/Block/SystemHelpBlock.php @@ -78,7 +78,7 @@ public static function create(ContainerInterface $container, array $configuratio /** * {@inheritdoc} */ - public function access(AccountInterface $account) { + protected function blockAccess(AccountInterface $account) { $this->help = $this->getActiveHelp($this->request); return (bool) $this->help; } diff --git a/core/modules/system/src/Plugin/Condition/RequestPath.php b/core/modules/system/src/Plugin/Condition/RequestPath.php index 7923554..2484461 100644 --- a/core/modules/system/src/Plugin/Condition/RequestPath.php +++ b/core/modules/system/src/Plugin/Condition/RequestPath.php @@ -134,6 +134,9 @@ public function evaluate() { // Convert path to lowercase. This allows comparison of the same path // with different case. Ex: /Page, /page, /PAGE. $pages = Unicode::strtolower($this->configuration['pages']); + if (!$pages) { + return TRUE; + } $request = $this->requestStack->getCurrentRequest(); // Compare the lowercase path alias (if any) and internal path. diff --git a/core/modules/system/tests/modules/form_test/src/Plugin/Block/RedirectFormBlock.php b/core/modules/system/tests/modules/form_test/src/Plugin/Block/RedirectFormBlock.php index 9703f4c..21b79b3 100644 --- a/core/modules/system/tests/modules/form_test/src/Plugin/Block/RedirectFormBlock.php +++ b/core/modules/system/tests/modules/form_test/src/Plugin/Block/RedirectFormBlock.php @@ -66,7 +66,7 @@ public static function create(ContainerInterface $container, array $configuratio /** * {@inheritdoc} */ - public function access(AccountInterface $account) { + protected function blockAccess(AccountInterface $account) { return TRUE; } diff --git a/core/modules/user/src/Plugin/Block/UserLoginBlock.php b/core/modules/user/src/Plugin/Block/UserLoginBlock.php index 3466c21..7fca086 100644 --- a/core/modules/user/src/Plugin/Block/UserLoginBlock.php +++ b/core/modules/user/src/Plugin/Block/UserLoginBlock.php @@ -25,7 +25,7 @@ class UserLoginBlock extends BlockBase { /** * {@inheritdoc} */ - public function access(AccountInterface $account) { + protected function blockAccess(AccountInterface $account) { $route_name = \Drupal::request()->attributes->get(RouteObjectInterface::ROUTE_NAME); return ($account->isAnonymous() && !in_array($route_name, array('user.register', 'user.login', 'user.logout'))); } diff --git a/core/modules/user/src/Plugin/Condition/UserRole.php b/core/modules/user/src/Plugin/Condition/UserRole.php index 8724489..293cd88 100644 --- a/core/modules/user/src/Plugin/Condition/UserRole.php +++ b/core/modules/user/src/Plugin/Condition/UserRole.php @@ -35,7 +35,6 @@ public function buildConfigurationForm(array $form, array &$form_state) { '#default_value' => $this->configuration['roles'], '#options' => array_map('\Drupal\Component\Utility\String::checkPlain', user_role_names()), '#description' => $this->t('If you select no roles, the condition will evaluate to TRUE for all users.'), - '#required' => TRUE, ); return $form; } @@ -82,7 +81,7 @@ public function summary() { */ public function evaluate() { $user = $this->getContextValue('user'); - return (bool) array_intersect($this->configuration['roles'], $user->getRoles()); + return empty($this->configuration['roles']) || array_intersect($this->configuration['roles'], $user->getRoles()); } } diff --git a/core/modules/views/src/Plugin/Block/ViewsBlockBase.php b/core/modules/views/src/Plugin/Block/ViewsBlockBase.php index b0bfdba..5a63e76 100644 --- a/core/modules/views/src/Plugin/Block/ViewsBlockBase.php +++ b/core/modules/views/src/Plugin/Block/ViewsBlockBase.php @@ -91,7 +91,7 @@ public static function create(ContainerInterface $container, array $configuratio /** * {@inheritdoc} */ - public function access(AccountInterface $account) { + protected function blockAccess(AccountInterface $account) { return $this->view->access($this->displayID); } diff --git a/core/modules/views/tests/src/Plugin/Block/ViewsBlockTest.php b/core/modules/views/tests/src/Plugin/Block/ViewsBlockTest.php index eb46cb3..34a0b99 100644 --- a/core/modules/views/tests/src/Plugin/Block/ViewsBlockTest.php +++ b/core/modules/views/tests/src/Plugin/Block/ViewsBlockTest.php @@ -7,6 +7,7 @@ namespace Drupal\views\Tests\Plugin\Block { +use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Tests\UnitTestCase; use Drupal\views\Plugin\Block\ViewsBlock; use Drupal\block\Plugin\views\display\Block; @@ -71,6 +72,10 @@ public static function getInfo() { */ protected function setUp() { parent::setUp(); // TODO: Change the autogenerated stub + $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); + $container = new ContainerBuilder(); + $container->set('module_handler', $module_handler); + \Drupal::setContainer($container); $this->executable = $this->getMockBuilder('Drupal\views\ViewExecutable') ->disableOriginalConstructor() @@ -128,6 +133,10 @@ public function testBuild() { $definition['provider'] = 'views'; $plugin = new ViewsBlock($config, $block_id, $definition, $this->executableFactory, $this->storage, $this->account); + $reflector = new \ReflectionClass($plugin); + $property = $reflector->getProperty('conditionPluginManager'); + $property->setAccessible(TRUE); + $property->setValue($plugin, $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface')); $this->assertEquals($build, $plugin->build()); } @@ -150,6 +159,10 @@ public function testBuildFailed() { $definition['provider'] = 'views'; $plugin = new ViewsBlock($config, $block_id, $definition, $this->executableFactory, $this->storage, $this->account); + $reflector = new \ReflectionClass($plugin); + $property = $reflector->getProperty('conditionPluginManager'); + $property->setAccessible(TRUE); + $property->setValue($plugin, $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface')); $this->assertEquals(array(), $plugin->build()); } diff --git a/core/tests/Drupal/Tests/Core/Condition/ConditionAccessResolverTraitTest.php b/core/tests/Drupal/Tests/Core/Condition/ConditionAccessResolverTraitTest.php new file mode 100644 index 0000000..dc676c5 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Condition/ConditionAccessResolverTraitTest.php @@ -0,0 +1,107 @@ + 'Tests resolving a set of conditions', + 'description' => '', + 'group' => 'Condition', + ); + } + + /** + * Tests the resolveConditions() method. + * + * @covers ::resolveConditions + * + * @dataProvider providerTestResolveConditions + */ + public function testResolveConditions($conditions, $logic, $expected) { + $trait_object = new TestConditionAccessResolverTrait(); + $this->assertEquals($expected, $trait_object->resolveConditions($conditions, $logic)); + } + + public function providerTestResolveConditions() { + $data = array(); + + $condition_true = $this->getMock('Drupal\Core\Condition\ConditionInterface'); + $condition_true->expects($this->any()) + ->method('execute') + ->will($this->returnValue(TRUE)); + $condition_false = $this->getMock('Drupal\Core\Condition\ConditionInterface'); + $condition_false->expects($this->any()) + ->method('execute') + ->will($this->returnValue(FALSE)); + $condition_exception = $this->getMock('Drupal\Core\Condition\ConditionInterface'); + $condition_exception->expects($this->any()) + ->method('execute') + ->will($this->throwException(new PluginException())); + + $conditions = array(); + $data[] = array($conditions, 'and', TRUE); + $data[] = array($conditions, 'or', FALSE); + + $conditions = array($condition_false); + $data[] = array($conditions, 'or', FALSE); + $data[] = array($conditions, 'and', FALSE); + + $conditions = array($condition_true); + $data[] = array($conditions, 'or', TRUE); + $data[] = array($conditions, 'and', TRUE); + + $conditions = array($condition_true, $condition_false); + $data[] = array($conditions, 'or', TRUE); + $data[] = array($conditions, 'and', FALSE); + + $conditions = array($condition_exception); + $data[] = array($conditions, 'or', FALSE); + $data[] = array($conditions, 'and', FALSE); + + $conditions = array($condition_true, $condition_exception); + $data[] = array($conditions, 'or', TRUE); + $data[] = array($conditions, 'and', FALSE); + + $conditions = array($condition_exception, $condition_true); + $data[] = array($conditions, 'or', TRUE); + $data[] = array($conditions, 'and', FALSE); + + $conditions = array($condition_false, $condition_exception); + $data[] = array($conditions, 'or', FALSE); + $data[] = array($conditions, 'and', FALSE); + + $conditions = array($condition_exception, $condition_false); + $data[] = array($conditions, 'or', FALSE); + $data[] = array($conditions, 'and', FALSE); + return $data; + } + +} + +class TestConditionAccessResolverTrait { + use \Drupal\Core\Condition\ConditionAccessResolverTrait { + resolveConditions as public; + } +}