getRootNode(); $rootNode ->children() ->scalarNode('data_dir') ->isRequired() ->validate() ->ifTrue(function ($value) { return !file_exists($value); }) ->thenInvalid('Data directory does not exist') ->end() ->end() ->scalarNode('plugin_dir') ->isRequired() ->validate() ->ifTrue(function ($value) { return !file_exists($value); }) ->thenInvalid('Plugin directory does not exist') ->end() ->end() ->append($this->getUserNode()) ->append($this->getTimesheetNode()) ->append($this->getInvoiceNode()) ->append($this->getLanguagesNode()) ->append($this->getCalendarNode()) ->append($this->getThemeNode()) ->append($this->getDashboardNode()) ->append($this->getWidgetsNode()) ->append($this->getDefaultsNode()) ->append($this->getPermissionsNode()) ->end() ->end(); return $treeBuilder; } protected function getTimesheetNode() { $builder = new TreeBuilder('timesheet'); $node = $builder->getRootNode(); $node ->children() ->booleanNode('duration_only') ->defaultValue(false) ->setDeprecated() ->end() ->scalarNode('mode') ->defaultValue('default') ->validate() ->ifTrue(function ($value) { return !in_array($value, ['default', 'duration_only']); }) ->thenInvalid('Chosen timesheet mode is invalid, allowed values: default, duration_only') ->end() ->end() ->booleanNode('markdown_content') ->defaultValue(false) ->end() ->arrayNode('rounding') ->requiresAtLeastOneElement() ->useAttributeAsKey('key') ->arrayPrototype() ->children() ->arrayNode('days') ->requiresAtLeastOneElement() ->useAttributeAsKey('key') ->isRequired() ->prototype('scalar')->end() ->defaultValue([]) ->end() ->integerNode('begin') ->defaultValue(0) ->end() ->integerNode('end') ->defaultValue(0) ->end() ->integerNode('duration') ->defaultValue(0) ->end() ->scalarNode('mode') ->defaultValue('default') ->validate() ->ifTrue(function ($value) { $class = 'App\\Timesheet\\Rounding\\' . ucfirst($value) . 'Rounding'; if (class_exists($class)) { $rounding = new $class(); return !($rounding instanceof RoundingInterface); } return false; }) ->thenInvalid('Chosen rounding mode is invalid') ->end() ->end() ->end() ->end() ->defaultValue([]) ->end() ->arrayNode('rates') ->requiresAtLeastOneElement() ->useAttributeAsKey('key') ->arrayPrototype() ->children() ->arrayNode('days') ->requiresAtLeastOneElement() ->useAttributeAsKey('key') ->isRequired() ->prototype('scalar')->end() ->defaultValue([]) ->end() ->floatNode('factor') ->isRequired() ->defaultValue(1) ->validate() ->ifTrue(function ($value) { return $value <= 0; }) ->thenInvalid('A rate factor smaller or equals 0 is not allowed') ->end() ->end() ->end() ->end() ->defaultValue([]) ->end() ->arrayNode('active_entries') ->addDefaultsIfNotSet() ->children() ->integerNode('soft_limit') ->defaultValue(1) ->validate() ->ifTrue(function ($value) { return $value <= 0; }) ->thenInvalid('The soft_limit must be at least 1') ->end() ->end() ->integerNode('hard_limit') ->defaultValue(1) ->validate() ->ifTrue(function ($value) { return $value <= 0; }) ->thenInvalid('The hard_limit must be at least 1') ->end() ->end() ->end() ->end() ->arrayNode('rules') ->addDefaultsIfNotSet() ->children() ->booleanNode('allow_future_times') ->defaultTrue() ->end() ->end() ->end() ->end() ; return $node; } protected function getInvoiceNode() { $builder = new TreeBuilder('invoice'); $node = $builder->getRootNode(); $node ->addDefaultsIfNotSet() ->children() ->arrayNode('documents') ->requiresAtLeastOneElement() ->isRequired() ->scalarPrototype()->end() ->defaultValue([ 'var/invoices/', 'templates/invoice/renderer/' ]) ->end() ->end() ; return $node; } protected function getLanguagesNode() { $builder = new TreeBuilder('languages'); $node = $builder->getRootNode(); $node ->useAttributeAsKey('name', false) // see https://github.com/symfony/symfony/issues/18988 ->arrayPrototype() ->children() ->scalarNode('date_time_type')->defaultValue('yyyy-MM-dd HH:mm')->end() // for DateTimeType ->scalarNode('date_time_picker')->defaultValue('YYYY-MM-DD HH:mm')->end() // for DateTimeType JS component ->scalarNode('date_type')->defaultValue('yyyy-MM-dd')->end() // for DateType ->scalarNode('date_picker')->defaultValue('YYYY-MM-DD')->end() // for DateType JS component ->scalarNode('date')->defaultValue('Y-m-d')->end() // for display via twig ->scalarNode('date_time')->defaultValue('m-d H:i')->end() // for display via twig ->scalarNode('duration')->defaultValue('%%h:%%m h')->end() // for display via twig ->scalarNode('time')->defaultValue('H:i')->end() // for display via twig ->booleanNode('24_hours')->defaultTrue()->end() // for DateTimeType JS component ->end() ->end() ; return $node; } protected function getCalendarNode() { $builder = new TreeBuilder('calendar'); $node = $builder->getRootNode(); $node ->children() ->booleanNode('week_numbers')->defaultTrue()->end() ->integerNode('day_limit')->defaultValue(4)->end() ->arrayNode('businessHours') ->addDefaultsIfNotSet() ->children() ->arrayNode('days') ->requiresAtLeastOneElement() ->prototype('integer')->end() ->defaultValue([1, 2, 3, 4, 5]) ->end() ->scalarNode('begin')->defaultValue('08:00')->end() ->scalarNode('end')->defaultValue('20:00')->end() ->end() ->end() ->arrayNode('google') ->addDefaultsIfNotSet() ->children() ->scalarNode('api_key')->defaultNull()->end() ->arrayNode('sources') ->requiresAtLeastOneElement() ->useAttributeAsKey('key') ->arrayPrototype() ->children() ->scalarNode('id')->isRequired()->end() ->scalarNode('color')->defaultValue('#ccc')->end() ->end() ->end() ->end() ->end() ->end() ->end() ; return $node; } protected function getThemeNode() { $builder = new TreeBuilder('theme'); $node = $builder->getRootNode(); $node ->addDefaultsIfNotSet() ->children() ->integerNode('active_warning') ->defaultValue(3) ->setDeprecated('The node "%node%" at path "%path%" is deprecated, please use "kimai.timesheet.active_entries.soft_limit" instead.') ->end() ->scalarNode('box_color') ->defaultValue('green') ->end() ->scalarNode('select_type') ->defaultNull() ->end() ->booleanNode('show_about') ->defaultTrue() ->end() ->end() ; return $node; } protected function getUserNode() { $builder = new TreeBuilder('user'); $node = $builder->getRootNode(); $node ->addDefaultsIfNotSet() ->children() ->booleanNode('registration') ->defaultTrue() ->end() ->booleanNode('password_reset') ->defaultTrue() ->end() ->end() ; return $node; } protected function getWidgetsNode() { $builder = new TreeBuilder('widgets'); $node = $builder->getRootNode(); $node ->requiresAtLeastOneElement() ->useAttributeAsKey('key') ->arrayPrototype() ->addDefaultsIfNotSet() ->children() ->scalarNode('title')->isRequired()->end() ->scalarNode('query')->isRequired()->end() ->booleanNode('user')->defaultFalse()->end() ->scalarNode('begin')->end() ->scalarNode('end')->end() ->scalarNode('icon')->defaultValue('')->end() ->scalarNode('color')->defaultValue('')->end() ->scalarNode('type') ->validate() ->ifNotInArray([Widget::TYPE_COUNTER, Widget::TYPE_MORE])->thenInvalid('Unknown widget type') ->end() ->defaultValue(Widget::TYPE_COUNTER) ->end() ->end() ->end() ; return $node; } protected function getDashboardNode() { $builder = new TreeBuilder('dashboard'); $node = $builder->getRootNode(); $node ->requiresAtLeastOneElement() ->useAttributeAsKey('key') ->arrayPrototype() ->addDefaultsIfNotSet() ->children() ->scalarNode('type') ->validate() ->ifNotInArray([DashboardSection::TYPE_SIMPLE, DashboardSection::TYPE_CHART])->thenInvalid('Unknown section type') ->end() ->defaultValue(DashboardSection::TYPE_SIMPLE) ->end() ->integerNode('order')->defaultValue(0)->end() ->scalarNode('title')->end() ->scalarNode('permission')->isRequired()->end() ->arrayNode('widgets') ->isRequired() ->performNoDeepMerging() ->scalarPrototype() ->end() ->end() ->end() ->end() ; return $node; } protected function getDefaultsNode() { $builder = new TreeBuilder('defaults'); $node = $builder->getRootNode(); $node ->addDefaultsIfNotSet() ->children() ->arrayNode('customer') ->addDefaultsIfNotSet() ->children() ->scalarNode('timezone')->defaultValue('Europe/Berlin')->end() ->scalarNode('country')->defaultValue('DE')->end() ->scalarNode('currency')->defaultValue('EUR')->end() ->end() ->end() ->end() ; return $node; } protected function getPermissionsNode() { $builder = new TreeBuilder('permissions'); $node = $builder->getRootNode(); $node ->addDefaultsIfNotSet() ->children() ->arrayNode('sets') ->requiresAtLeastOneElement() ->useAttributeAsKey('key') ->performNoDeepMerging() ->arrayPrototype() ->useAttributeAsKey('key') ->isRequired() ->prototype('scalar')->end() ->defaultValue([]) ->end() ->end() ->arrayNode('maps') ->requiresAtLeastOneElement() ->useAttributeAsKey('key') ->performNoDeepMerging() ->arrayPrototype() ->useAttributeAsKey('key') ->isRequired() ->prototype('scalar')->end() ->defaultValue([]) ->end() ->end() ->arrayNode('roles') ->requiresAtLeastOneElement() ->useAttributeAsKey('key') ->performNoDeepMerging() ->arrayPrototype() ->useAttributeAsKey('key') ->isRequired() ->prototype('scalar')->end() ->defaultValue([]) ->end() ->end() ->end() ; return $node; } }