├── bundle
├── Resources
│ ├── translations
│ │ ├── fieldtypes.en.yml
│ │ ├── fieldtypes.fr.yml
│ │ ├── ibexa_fieldtypes.en.yml
│ │ ├── ibexa_fieldtypes.fr.yml
│ │ ├── validators.en.yml
│ │ ├── validators.fr.yml
│ │ ├── forms.en.yml
│ │ ├── forms.fr.yml
│ │ ├── content_type.en.yml
│ │ ├── ibexa_content_type.en.yml
│ │ ├── content_type.fr.yml
│ │ ├── ibexa_content_type.fr.yml
│ │ ├── netgen_tags_admin_flash.en.yml
│ │ └── netgen_tags_admin_flash.fr.yml
│ ├── views
│ │ ├── ibexa
│ │ │ └── admin
│ │ │ │ ├── field
│ │ │ │ ├── edit
│ │ │ │ │ └── view
│ │ │ │ │ │ ├── select.html.twig
│ │ │ │ │ │ └── default.html.twig
│ │ │ │ └── browse
│ │ │ │ │ └── view
│ │ │ │ │ └── browse.html.twig
│ │ │ │ ├── stylesheets.html.twig
│ │ │ │ ├── javascripts.html.twig
│ │ │ │ └── pagelayout.html.twig
│ │ ├── admin
│ │ │ ├── header.html.twig
│ │ │ ├── stylesheets.html.twig
│ │ │ ├── eztags_content_field.html.twig
│ │ │ ├── javascripts.html.twig
│ │ │ ├── main_menu.html.twig
│ │ │ ├── flash_messages.html.twig
│ │ │ ├── tag
│ │ │ │ ├── dashboard.html.twig
│ │ │ │ ├── add.html.twig
│ │ │ │ ├── update.html.twig
│ │ │ │ ├── select_translation.html.twig
│ │ │ │ ├── convert.html.twig
│ │ │ │ ├── merge.html.twig
│ │ │ │ ├── delete.html.twig
│ │ │ │ ├── tabs
│ │ │ │ │ ├── synonyms.html.twig
│ │ │ │ │ ├── subtree_limitations.html.twig
│ │ │ │ │ └── latest_content.html.twig
│ │ │ │ ├── copy_tags.html.twig
│ │ │ │ ├── move_tags.html.twig
│ │ │ │ └── delete_tags.html.twig
│ │ │ ├── tree.html.twig
│ │ │ └── path.html.twig
│ │ ├── eztags_content_field.html.twig
│ │ ├── field_definition
│ │ │ └── edit
│ │ │ │ └── eztags.html.twig
│ │ ├── form
│ │ │ ├── tag_tree.html.twig
│ │ │ └── tags.html.twig
│ │ └── tag
│ │ │ └── view.html.twig
│ ├── config
│ │ ├── ibexa
│ │ │ └── admin
│ │ │ │ ├── twig.yaml
│ │ │ │ └── default_settings.yaml
│ │ ├── framework
│ │ │ └── twig.yaml
│ │ ├── storage
│ │ │ ├── cache_disabled.yaml
│ │ │ └── cache_psr6.yaml
│ │ ├── search
│ │ │ ├── related_content.yaml
│ │ │ ├── legacy.yaml
│ │ │ ├── elasticsearch.yaml
│ │ │ └── solr.yaml
│ │ ├── admin
│ │ │ └── templating.yaml
│ │ ├── autowiring.yaml
│ │ ├── policies.yaml
│ │ ├── persistence.yaml
│ │ ├── routing
│ │ │ ├── admin
│ │ │ │ ├── tree.yaml
│ │ │ │ ├── synonym.yaml
│ │ │ │ ├── field.yaml
│ │ │ │ └── redirects.yaml
│ │ │ ├── internal.yaml
│ │ │ └── admin.yaml
│ │ ├── value_resolvers.yaml
│ │ ├── installer.yaml
│ │ ├── limitations.yaml
│ │ ├── routing.yaml
│ │ ├── netgen_tags.yaml
│ │ ├── ibexa.yaml
│ │ ├── templating.yaml
│ │ ├── http_cache.yaml
│ │ ├── forms.yaml
│ │ ├── pagerfanta.yaml
│ │ ├── papi.yaml
│ │ ├── fieldtypes.yaml
│ │ ├── services.yaml
│ │ └── validators.yaml
│ ├── public
│ │ ├── admin
│ │ │ ├── fonts
│ │ │ │ ├── Roboto
│ │ │ │ │ ├── Roboto-300.eot
│ │ │ │ │ ├── Roboto-300.ttf
│ │ │ │ │ ├── Roboto-500.eot
│ │ │ │ │ ├── Roboto-500.ttf
│ │ │ │ │ ├── Roboto-700.eot
│ │ │ │ │ ├── Roboto-700.ttf
│ │ │ │ │ ├── Roboto-300.woff
│ │ │ │ │ ├── Roboto-300.woff2
│ │ │ │ │ ├── Roboto-500.woff
│ │ │ │ │ ├── Roboto-500.woff2
│ │ │ │ │ ├── Roboto-700.woff
│ │ │ │ │ ├── Roboto-700.woff2
│ │ │ │ │ ├── Roboto-regular.eot
│ │ │ │ │ ├── Roboto-regular.ttf
│ │ │ │ │ ├── Roboto-regular.woff
│ │ │ │ │ └── Roboto-regular.woff2
│ │ │ │ ├── MaterialIcons-Regular.eot
│ │ │ │ ├── MaterialIcons-Regular.ttf
│ │ │ │ ├── MaterialIcons-Regular.woff
│ │ │ │ └── MaterialIcons-Regular.woff2
│ │ │ ├── images
│ │ │ │ ├── favicon
│ │ │ │ │ ├── favicon.ico
│ │ │ │ │ ├── favicon-16x16.png
│ │ │ │ │ ├── favicon-32x32.png
│ │ │ │ │ ├── apple-touch-icon.png
│ │ │ │ │ ├── mstile-150x150.png
│ │ │ │ │ ├── android-chrome-192x192.png
│ │ │ │ │ ├── android-chrome-512x512.png
│ │ │ │ │ ├── browserconfig.xml
│ │ │ │ │ ├── manifest.json
│ │ │ │ │ └── safari-pinned-tab.svg
│ │ │ │ ├── jqmodal
│ │ │ │ │ ├── buttons.gif
│ │ │ │ │ ├── corners.gif
│ │ │ │ │ └── horizontal.gif
│ │ │ │ ├── tags-logo-silhouette.svg
│ │ │ │ └── tags-logo.svg
│ │ │ ├── jstree
│ │ │ │ └── themes
│ │ │ │ │ ├── default
│ │ │ │ │ ├── 32px.png
│ │ │ │ │ ├── 40px.png
│ │ │ │ │ └── throbber.gif
│ │ │ │ │ └── default-dark
│ │ │ │ │ ├── 32px.png
│ │ │ │ │ ├── 40px.png
│ │ │ │ │ └── throbber.gif
│ │ │ └── css
│ │ │ │ └── field
│ │ │ │ └── jqmodal.css
│ │ └── ibexa
│ │ │ └── admin
│ │ │ └── js
│ │ │ └── field
│ │ │ └── init.js
│ ├── sql
│ │ ├── upgrade
│ │ │ ├── postgresql
│ │ │ │ ├── 2.2
│ │ │ │ │ └── dbupdate-2.1-to-2.2.sql
│ │ │ │ └── 3.4
│ │ │ │ │ └── dbupdate-3.3-to-3.4.sql
│ │ │ └── mysql
│ │ │ │ └── 2.2
│ │ │ │ └── dbupdate-2.1-to-2.2.sql
│ │ └── mysql
│ │ │ └── schema.sql
│ ├── sass
│ │ └── admin
│ │ │ ├── _resize.scss
│ │ │ ├── style.scss
│ │ │ ├── _typography.scss
│ │ │ ├── _content.scss
│ │ │ ├── _pagination.scss
│ │ │ ├── _variables.scss
│ │ │ ├── _modal.scss
│ │ │ └── _base.scss
│ └── encore
│ │ └── ibexa.config.manager.js
├── Exception
│ ├── Exception.php
│ ├── FacetingNotSupportedException.php
│ └── InvalidArgumentException.php
├── Core
│ ├── REST
│ │ ├── Values
│ │ │ ├── CreatedTag.php
│ │ │ ├── TagList.php
│ │ │ ├── RestTag.php
│ │ │ ├── ContentList.php
│ │ │ └── CachedValue.php
│ │ └── Output
│ │ │ └── ValueObjectVisitor
│ │ │ ├── CreatedTag.php
│ │ │ ├── ContentList.php
│ │ │ └── TagList.php
│ ├── Pagination
│ │ └── Pagerfanta
│ │ │ ├── TagAdapterInterface.php
│ │ │ ├── ChildrenTagsAdapter.php
│ │ │ └── SearchTagsAdapter.php
│ ├── HttpCache
│ │ ├── ResponseTagger
│ │ │ └── TagViewTagger.php
│ │ └── Tagger.php
│ ├── Search
│ │ ├── Solr
│ │ │ └── Query
│ │ │ │ └── Common
│ │ │ │ └── SortClauseVisitor
│ │ │ │ └── ContentTypeId.php
│ │ └── Elasticsearch
│ │ │ └── QueryDSL
│ │ │ └── PrefixQuery.php
│ └── FieldType
│ │ └── Tags
│ │ ├── Value.php
│ │ └── TagsStorage
│ │ └── Gateway.php
├── View
│ ├── TagValueView.php
│ ├── CacheableView.php
│ ├── TagView.php
│ ├── Builder
│ │ └── ParametersFilter
│ │ │ └── CurrentPage.php
│ └── Provider
│ │ └── TagViewProvider.php
├── TagsServiceAwareInterface.php
├── Validator
│ ├── Constraints
│ │ ├── Structs
│ │ │ ├── TagCreateStruct.php
│ │ │ ├── SynonymCreateStruct.php
│ │ │ └── TagUpdateStruct.php
│ │ ├── Language.php
│ │ ├── RemoteId.php
│ │ └── Tag.php
│ ├── LanguageValidator.php
│ ├── RemoteIdValidator.php
│ └── Structs
│ │ ├── TagCreateStructValidator.php
│ │ ├── SynonymCreateStructValidator.php
│ │ └── CreateStructValidator.php
├── TagsServiceAwareTrait.php
├── SPI
│ └── Persistence
│ │ └── Tags
│ │ ├── SearchResult.php
│ │ ├── UpdateStruct.php
│ │ ├── CreateStruct.php
│ │ ├── SynonymCreateStruct.php
│ │ ├── TagInfo.php
│ │ └── Tag.php
├── Controller
│ ├── TagViewController.php
│ └── Admin
│ │ └── AccessController.php
├── API
│ └── Repository
│ │ ├── Values
│ │ ├── User
│ │ │ └── Limitation
│ │ │ │ └── TagLimitation.php
│ │ ├── Tags
│ │ │ ├── TagUpdateStruct.php
│ │ │ ├── SearchResult.php
│ │ │ ├── TagCreateStruct.php
│ │ │ ├── SynonymCreateStruct.php
│ │ │ └── TagList.php
│ │ └── Content
│ │ │ └── Query
│ │ │ ├── SortClause
│ │ │ └── ContentTypeId.php
│ │ │ └── Criterion
│ │ │ ├── Value
│ │ │ └── TagKeywordValue.php
│ │ │ └── TagId.php
│ │ └── Events
│ │ └── Tags
│ │ ├── DeleteTagEvent.php
│ │ ├── BeforeDeleteTagEvent.php
│ │ ├── MergeTagsEvent.php
│ │ ├── BeforeMergeTagsEvent.php
│ │ ├── MoveSubtreeEvent.php
│ │ ├── ConvertToSynonymEvent.php
│ │ ├── CreateTagEvent.php
│ │ ├── UpdateTagEvent.php
│ │ ├── AddSynonymEvent.php
│ │ ├── CopySubtreeEvent.php
│ │ ├── BeforeCreateTagEvent.php
│ │ ├── BeforeAddSynonymEvent.php
│ │ ├── BeforeConvertToSynonymEvent.php
│ │ ├── BeforeMoveSubtreeEvent.php
│ │ ├── BeforeCopySubtreeEvent.php
│ │ └── BeforeUpdateTagEvent.php
├── Form
│ ├── Type
│ │ ├── AbstractType.php
│ │ ├── LanguageSelectType.php
│ │ ├── SynonymCreateType.php
│ │ ├── TagMergeType.php
│ │ ├── TagConvertType.php
│ │ ├── TagType.php
│ │ ├── CopyTagsType.php
│ │ ├── MoveTagsType.php
│ │ ├── TagCreateType.php
│ │ ├── FieldType
│ │ │ └── TagsFieldType.php
│ │ └── TagUpdateType.php
│ └── DataMapper
│ │ └── TagUpdateStructDataMapper.php
├── Matcher
│ └── Tag
│ │ ├── Depth.php
│ │ ├── Keyword.php
│ │ ├── Id
│ │ ├── Tag.php
│ │ ├── MainTag.php
│ │ ├── Remote.php
│ │ ├── ParentTag.php
│ │ └── ParentRemote.php
│ │ ├── ParentKeyword.php
│ │ └── MultipleValued.php
├── DependencyInjection
│ ├── Security
│ │ └── PolicyProvider
│ │ │ └── TagsPolicyProvider.php
│ └── Compiler
│ │ ├── TagViewBuilderPass.php
│ │ └── DefaultStorageEnginePass.php
├── Installer
│ └── BuildSchemaListener.php
├── NetgenTagsBundle.php
├── Templating
│ └── Twig
│ │ ├── AdminGlobalVariable.php
│ │ └── Extension
│ │ ├── NetgenTagsExtension.php
│ │ └── NetgenTagsRuntime.php
├── ValueResolver
│ └── TagValueResolver.php
└── AdminUI
│ └── EventListener
│ └── SetPageLayoutListener.php
├── .gitattributes
├── codecov.yml
├── tests
├── _fixtures
│ ├── schema
│ │ └── setval.postgresql.sql
│ ├── content_fields.php
│ └── content_type_field_definitions.php
├── Stubs
│ ├── EventDispatcherStub.php
│ └── ConfigResolverStub.php
├── Templating
│ └── Twig
│ │ ├── Extension
│ │ └── NetgenTagsExtensionTest.php
│ │ └── AdminGlobalVariableTest.php
├── settings
│ ├── settings.yaml
│ └── integration
│ │ └── legacy.yaml
└── API
│ └── Repository
│ └── TagsServiceTest.php
├── .gitignore
├── .editorconfig
├── .php-cs-fixer.php
├── .github
└── workflows
│ ├── coding_standards.yml
│ ├── static_analysis.yml
│ └── tests.yml
├── phpstan.tests.neon
├── package.json
├── phpunit.xml
├── doc
└── changelogs
│ ├── CHANGELOG-5.x.md
│ └── CHANGELOG-4.x.md
└── phpunit-integration-legacy.xml
/bundle/Resources/translations/fieldtypes.en.yml:
--------------------------------------------------------------------------------
1 | eztags.name: 'Netgen Tags'
2 |
--------------------------------------------------------------------------------
/bundle/Resources/translations/fieldtypes.fr.yml:
--------------------------------------------------------------------------------
1 | eztags.name: 'Netgen Tags'
2 |
--------------------------------------------------------------------------------
/bundle/Resources/translations/ibexa_fieldtypes.en.yml:
--------------------------------------------------------------------------------
1 | eztags.name: 'Netgen Tags'
2 |
--------------------------------------------------------------------------------
/bundle/Resources/translations/ibexa_fieldtypes.fr.yml:
--------------------------------------------------------------------------------
1 | eztags.name: 'Netgen Tags'
2 |
--------------------------------------------------------------------------------
/bundle/Resources/views/ibexa/admin/field/edit/view/select.html.twig:
--------------------------------------------------------------------------------
1 | {# This file is intentionally left blank #}
2 |
--------------------------------------------------------------------------------
/bundle/Resources/config/ibexa/admin/twig.yaml:
--------------------------------------------------------------------------------
1 | form_themes:
2 | - '@NetgenTags/ibexa/admin/field/edit/eztags.html.twig'
3 |
--------------------------------------------------------------------------------
/bundle/Resources/config/framework/twig.yaml:
--------------------------------------------------------------------------------
1 | globals:
2 | netgen_tags_admin: "@netgen_tags.templating.admin_global_variable"
3 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | bundle/Resources/public/admin/css/style.css binary -merge
2 | bundle/Resources/public/admin/css/*.css.map binary -merge
3 |
--------------------------------------------------------------------------------
/bundle/Exception/Exception.php:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/bundle/Resources/config/admin/templating.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | netgen_tags.templating.admin_global_variable:
3 | class: Netgen\TagsBundle\Templating\Twig\AdminGlobalVariable
4 | arguments:
5 | - "@ibexa.config.resolver"
6 |
--------------------------------------------------------------------------------
/bundle/Resources/config/autowiring.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | Netgen\TagsBundle\API\Repository\TagsService:
3 | alias: netgen_tags.api.service.tags
4 |
5 | Netgen\TagsBundle\Core\HttpCache\Tagger:
6 | alias: netgen_tags.http_cache.tagger
7 |
--------------------------------------------------------------------------------
/bundle/Resources/config/policies.yaml:
--------------------------------------------------------------------------------
1 | tags:
2 | read: ~
3 | view: ~
4 | add: [Tag]
5 | addsynonym: ~
6 | edit: ~
7 | editsynonym: ~
8 | delete: ~
9 | deletesynonym: ~
10 | makesynonym: ~
11 | merge: ~
12 |
--------------------------------------------------------------------------------
/bundle/Resources/views/admin/stylesheets.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/bundle/Resources/config/persistence.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | netgen_tags.api.persistence_handler.tags.storage:
3 | synthetic: true
4 |
5 | netgen_tags.api.persistence_handler.tags:
6 | alias: netgen_tags.api.persistence_handler.tags.cache
7 |
--------------------------------------------------------------------------------
/bundle/Resources/config/routing/admin/tree.yaml:
--------------------------------------------------------------------------------
1 | netgen_tags_admin_tree_get_children:
2 | path: /get/{tagId}/{isRoot}
3 | controller: netgen_tags.admin.controller.tree::getChildrenAction
4 | methods: [GET]
5 | defaults:
6 | isRoot: false
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .tmp/
3 | coverage/
4 | node_modules/
5 | /vendor/
6 | var/
7 | composer.phar
8 | composer.lock
9 | grunt.lock
10 | yarn.lock
11 | package-lock.json
12 | phpunit.phar
13 | .php_cs.cache
14 | .php-cs-fixer.cache
15 | .phpunit.result.cache
16 |
--------------------------------------------------------------------------------
/bundle/Exception/FacetingNotSupportedException.php:
--------------------------------------------------------------------------------
1 | {{ tag.keyword }}{% if not loop.last %}, {% endif %}
4 | {% endfor %}
5 | {% endblock %}
6 |
--------------------------------------------------------------------------------
/bundle/Resources/views/admin/eztags_content_field.html.twig:
--------------------------------------------------------------------------------
1 | {% block eztags_field %}
2 | {% for tag in field.value.tags %}
3 | {{ tag.keyword }}{% if not loop.last %}, {% endif %}
4 | {% endfor %}
5 | {% endblock %}
6 |
--------------------------------------------------------------------------------
/bundle/Resources/views/admin/javascripts.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/bundle/Resources/config/value_resolvers.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | netgen_tags.value_resolver.tag:
3 | class: Netgen\TagsBundle\ValueResolver\TagValueResolver
4 | arguments:
5 | - "@netgen_tags.api.service.tags"
6 | tags:
7 | - { name: controller.argument_value_resolver }
8 |
--------------------------------------------------------------------------------
/bundle/Core/REST/Values/CreatedTag.php:
--------------------------------------------------------------------------------
1 |
7 | {% include '@NetgenTags/admin/tree.html.twig' with {selectedTag: selectedTag} %}
8 |
9 |
--------------------------------------------------------------------------------
/bundle/Resources/sass/admin/_resize.scss:
--------------------------------------------------------------------------------
1 | .tags-resizable {
2 | position:relative;
3 | .tags-resizable-handle {
4 | position:absolute;
5 | right:-2px;
6 | top:0;
7 | bottom:0;
8 | z-index:100;
9 | width:4px;
10 | background:transparent;
11 | cursor:ew-resize;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/bundle/Resources/config/installer.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | netgen_tags.installer.listener.build_schema:
3 | class: Netgen\TagsBundle\Installer\BuildSchemaListener
4 | arguments:
5 | - '@=service("kernel").locateResource("@NetgenTagsBundle/Resources/schema/legacy.yaml")'
6 | tags:
7 | - { name: kernel.event_subscriber }
8 |
--------------------------------------------------------------------------------
/bundle/TagsServiceAwareInterface.php:
--------------------------------------------------------------------------------
1 | {
4 | ibexaConfigManager.add({
5 | ibexaConfig,
6 | entryName: 'ibexa-admin-ui-content-edit-parts-js',
7 | newItems: [path.resolve(__dirname, '../public/admin/js/fieldType/eztags.js')],
8 | });
9 | };
10 |
--------------------------------------------------------------------------------
/bundle/Resources/sql/upgrade/postgresql/3.4/dbupdate-3.3-to-3.4.sql:
--------------------------------------------------------------------------------
1 | ALTER SEQUENCE eztags_s RENAME TO eztags_id_seq;
2 | ALTER SEQUENCE eztags_attribute_link_s RENAME TO eztags_attribute_link_id_seq;
3 |
4 | ALTER TABLE eztags ALTER COLUMN id SET DEFAULT nextval('eztags_id_seq');
5 | ALTER TABLE eztags_attribute_link ALTER COLUMN id SET DEFAULT nextval('eztags_attribute_link_id_seq');
6 |
--------------------------------------------------------------------------------
/bundle/Resources/public/admin/images/favicon/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
{{ 'tag.merge.message'|trans }}
11 | 12 | {{ form_start(form) }} 13 | {{ form_widget(form) }} 14 | 15 | 19 | {{ form_end(form) }} 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | - '[0-9].[0-9]+' 8 | pull_request: ~ 9 | 10 | jobs: 11 | tests: 12 | name: ${{ matrix.php }} / ${{ matrix.phpunit }} 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | php: ['8.3'] 19 | phpunit: ['phpunit.xml', 'phpunit-integration-legacy.xml'] 20 | 21 | steps: 22 | - uses: actions/checkout@v6 23 | - uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: ${{ matrix.php }} 26 | coverage: none 27 | 28 | - run: composer --version 29 | - run: composer validate --strict 30 | 31 | - run: composer update --prefer-dist 32 | 33 | - run: vendor/bin/phpunit -c ${{ matrix.phpunit }} --colors=always 34 | -------------------------------------------------------------------------------- /bundle/Matcher/Tag/ParentKeyword.php: -------------------------------------------------------------------------------- 1 | getTag(); 21 | 22 | $parentTag = $this->tagsService->sudo( 23 | static fn (TagsService $tagsService): Tag => $tagsService->loadTag($tag->parentTagId), 24 | ); 25 | 26 | return isset($this->values[$parentTag->getKeyword()]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bundle/Resources/public/admin/images/favicon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 18 | -------------------------------------------------------------------------------- /bundle/Matcher/Tag/MultipleValued.php: -------------------------------------------------------------------------------- 1 | values = array_fill_keys($matchingConfig, true); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bundle/API/Repository/Values/Content/Query/Criterion/Value/TagKeywordValue.php: -------------------------------------------------------------------------------- 1 | {{ 'tag.delete.title'|trans }}: {{ tag.keyword }} [{{ tag.id }}] 7 | 8 |{{ 'tag.delete.message'|trans({'%tagKeyword%': tag.keyword }) }}
9 | 10 | 15 | 16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /bundle/Core/Search/Solr/Query/Common/SortClauseVisitor/ContentTypeId.php: -------------------------------------------------------------------------------- 1 | getDirection($sortClause); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /bundle/Core/REST/Output/ValueObjectVisitor/CreatedTag.php: -------------------------------------------------------------------------------- 1 | restTag); 17 | 18 | $visitor->setHeader( 19 | 'Location', 20 | $this->router->generate( 21 | 'ibexa.rest.netgen_tags_loadTag', 22 | [ 23 | 'tagPath' => mb_trim($data->restTag->tag->pathString, '/'), 24 | ], 25 | ), 26 | ); 27 | 28 | $visitor->setStatus(201); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /bundle/Matcher/Tag/Id/ParentRemote.php: -------------------------------------------------------------------------------- 1 | getTag(); 22 | 23 | $parentTag = $this->tagsService->sudo( 24 | static fn (TagsService $tagsService): Tag => $tagsService->loadTag($tag->parentTagId), 25 | ); 26 | 27 | return isset($this->values[$parentTag->remoteId]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bundle/Installer/BuildSchemaListener.php: -------------------------------------------------------------------------------- 1 | 'onBuildSchema', 21 | ]; 22 | } 23 | 24 | public function onBuildSchema(SchemaBuilderEvent $event): void 25 | { 26 | $event 27 | ->getSchemaBuilder() 28 | ->importSchemaFromFile($this->schemaPath); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /bundle/Resources/config/search/legacy.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | netgen_tags.search.legacy.gateway.criterion_handler.common.tag_id: 3 | class: Netgen\TagsBundle\Core\Search\Legacy\Content\Common\Gateway\CriterionHandler\Tags\TagId 4 | parent: ibexa.search.legacy.gateway.criterion_handler.base 5 | tags: 6 | - { name: ibexa.search.legacy.gateway.criterion_handler.content } 7 | - { name: ibexa.search.legacy.gateway.criterion_handler.location } 8 | 9 | netgen_tags.search.legacy.gateway.criterion_handler.common.tag_keyword: 10 | class: Netgen\TagsBundle\Core\Search\Legacy\Content\Common\Gateway\CriterionHandler\Tags\TagKeyword 11 | parent: ibexa.search.legacy.gateway.criterion_handler.base 12 | tags: 13 | - { name: ibexa.search.legacy.gateway.criterion_handler.content } 14 | - { name: ibexa.search.legacy.gateway.criterion_handler.location } 15 | -------------------------------------------------------------------------------- /tests/Templating/Twig/AdminGlobalVariableTest.php: -------------------------------------------------------------------------------- 1 | adminGlobalVariable = new AdminGlobalVariable($this->createMock(ConfigResolverInterface::class)); 18 | } 19 | 20 | public function testSetPageLayoutTemplate(): void 21 | { 22 | $this->adminGlobalVariable->setPageLayoutTemplate('@Acme/pagelayout.html.twig'); 23 | 24 | self::assertSame('@Acme/pagelayout.html.twig', $this->adminGlobalVariable->getPageLayoutTemplate()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bundle/NetgenTagsBundle.php: -------------------------------------------------------------------------------- 1 | addCompilerPass(new Compiler\TagViewBuilderPass()); 17 | $container->addCompilerPass(new Compiler\DefaultStorageEnginePass()); 18 | 19 | /** @var \Ibexa\Bundle\Core\DependencyInjection\IbexaCoreExtension $ibexaCoreExtension */ 20 | $ibexaCoreExtension = $container->getExtension('ibexa'); 21 | $ibexaCoreExtension->addPolicyProvider(new TagsPolicyProvider()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bundle/Resources/views/admin/tag/tabs/synonyms.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'netgen_tags_admin' %} 2 | 3 | {% if synonyms is not empty %} 4 || {{ 'tag.tag_id'|trans }} | 8 |{{ 'tag.tag_name'|trans }} | 9 |{{ 'tag.modified'|trans }} | 10 |
|---|---|---|
| {{ synonym.id }} | 16 |{{ synonym.keyword }} | 17 |{{ synonym.modificationDate|date }} | 18 |
{{ 'tag.synonyms.no_synonyms'|trans }}
24 | {% endif %} 25 | -------------------------------------------------------------------------------- /bundle/Core/Search/Elasticsearch/QueryDSL/PrefixQuery.php: -------------------------------------------------------------------------------- 1 | field = $field; 19 | 20 | return $this; 21 | } 22 | 23 | public function withValue(string $value): self 24 | { 25 | $this->value = $value; 26 | 27 | return $this; 28 | } 29 | 30 | public function toArray(): array 31 | { 32 | return [ 33 | 'prefix' => [ 34 | $this->field => $this->value, 35 | ], 36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /bundle/Resources/config/forms.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | netgen_tags.forms.tree: 3 | class: Netgen\TagsBundle\Form\Type\TagTreeType 4 | arguments: 5 | - "@netgen_tags.api.service.tags" 6 | tags: 7 | - { name: form.type } 8 | 9 | netgen_tags.forms.translation_list: 10 | class: Netgen\TagsBundle\Form\Type\TranslationListType 11 | arguments: 12 | - "@ibexa.api.service.language" 13 | - "@ibexa.config.resolver" 14 | tags: 15 | - { name: form.type } 16 | 17 | netgen_tags.forms.content_type_filter: 18 | class: Netgen\TagsBundle\Form\Type\RelatedContentFilterType 19 | arguments: 20 | - "@netgen_tags.repository.related_content_facets_loader" 21 | - "@ibexa.api.service.content_type" 22 | - "@netgen_tags.search.related_content.sort_service" 23 | tags: 24 | - { name: form.type } 25 | -------------------------------------------------------------------------------- /bundle/Resources/sass/admin/_typography.scss: -------------------------------------------------------------------------------- 1 | #{$namespace} { 2 | a { 3 | color:$linkColor; 4 | text-decoration:none; 5 | &:hover { 6 | text-decoration:underline; 7 | } 8 | } 9 | h1, h2, h3, h4 { 10 | font-weight:500; 11 | margin:2em 0 1em; 12 | } 13 | h1 { 14 | font-size:2.291em; 15 | } 16 | h2 { 17 | font-size:1.618em; 18 | } 19 | h3 { 20 | font-size:1.416em; 21 | } 22 | h4 { 23 | font-size:1em; 24 | } 25 | .tags-badge { 26 | display:inline-block; 27 | margin-left:4px; 28 | vertical-align:middle; 29 | padding:0 2px; 30 | font-size:12px; 31 | font-weight:400; 32 | line-height:20px; 33 | height:20px; 34 | min-width:20px; 35 | text-align:center; 36 | background:hsl(0, 0, 50); 37 | color:#fff; 38 | border-radius:500px; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bundle/SPI/Persistence/Tags/UpdateStruct.php: -------------------------------------------------------------------------------- 1 | "Hrvatska", "eng-GB" => "Croatia" ). 17 | * 18 | * @var string[]|null 19 | */ 20 | public ?array $keywords; 21 | 22 | /** 23 | * A global unique ID of the tag. 24 | */ 25 | public ?string $remoteId; 26 | 27 | /** 28 | * The main language code for the tag. 29 | */ 30 | public ?string $mainLanguageCode; 31 | 32 | /** 33 | * Indicates if the tag is shown in the main language if it's not present in an other requested language. 34 | */ 35 | public ?bool $alwaysAvailable; 36 | } 37 | -------------------------------------------------------------------------------- /bundle/Templating/Twig/AdminGlobalVariable.php: -------------------------------------------------------------------------------- 1 | pageLayoutTemplate = $pageLayoutTemplate; 22 | $this->isDefault = false; 23 | } 24 | 25 | public function getPageLayoutTemplate(): string 26 | { 27 | if ($this->isDefault) { 28 | $this->pageLayoutTemplate = $this->configResolver->getParameter('admin.pagelayout', 'netgen_tags'); 29 | } 30 | 31 | return $this->pageLayoutTemplate; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bundle/DependencyInjection/Compiler/TagViewBuilderPass.php: -------------------------------------------------------------------------------- 1 | has(ViewBuilderRegistry::class)) { 16 | return; 17 | } 18 | 19 | $viewBuilderRegistry = $container->findDefinition(ViewBuilderRegistry::class); 20 | $tagViewBuilder = $container->findDefinition('netgen_tags.view.tag_view_builder'); 21 | 22 | $viewBuilderRegistry->addMethodCall( 23 | 'addToRegistry', 24 | [[$tagViewBuilder]], 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/settings/settings.yaml: -------------------------------------------------------------------------------- 1 | parameters: 2 | storage_dir: vendor/ibexa/core/var/ezdemo_site/storage 3 | ibexa.api.storage_engine.default: legacy 4 | ibexa.site_access.config.default.user_content_type_identifier: ['user'] 5 | 6 | ibexa.api.role.policy_map: 7 | tags: 8 | add: { Tag: true } 9 | read: ~ 10 | editsynonym: ~ 11 | addsynonym: ~ 12 | makesynonym: ~ 13 | merge: ~ 14 | edit: ~ 15 | delete: ~ 16 | 17 | content: 18 | read: ~ 19 | create: ~ 20 | edit: ~ 21 | publish: ~ 22 | manage_locations: ~ 23 | remove: ~ 24 | versionread: ~ 25 | versionremove: ~ 26 | 27 | class: 28 | update: ~ 29 | create: ~ 30 | 31 | role: 32 | assign: ~ 33 | update: ~ 34 | create: ~ 35 | read: ~ 36 | -------------------------------------------------------------------------------- /bundle/View/TagView.php: -------------------------------------------------------------------------------- 1 | tag = $tag; 19 | } 20 | 21 | public function getTag(): Tag 22 | { 23 | return $this->tag; 24 | } 25 | 26 | public function setCacheEnabled($cacheEnabled): void 27 | { 28 | $this->isCacheEnabled = (bool) $cacheEnabled; 29 | } 30 | 31 | public function isCacheEnabled(): bool 32 | { 33 | return $this->isCacheEnabled; 34 | } 35 | 36 | protected function getInternalParameters(): array 37 | { 38 | return ['tag' => $this->tag ?? null]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bundle/Templating/Twig/Extension/NetgenTagsExtension.php: -------------------------------------------------------------------------------- 1 | repository = $this->getRepository(); 17 | $this->repository->getPermissionResolver()->setCurrentUserReference($this->getStubbedUser(14)); 18 | $this->tagsService = $this->getTagsService(); 19 | } 20 | 21 | private function getTagsService(): TagsService 22 | { 23 | /** @var \Netgen\TagsBundle\Tests\API\Repository\SetupFactory\Legacy $setUpFactory */ 24 | $setUpFactory = $this->getSetupFactory(); 25 | $this->tagsService = $this->tagsService ?? $setUpFactory->getTagsService(); 26 | 27 | return $this->tagsService; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bundle/Form/Type/LanguageSelectType.php: -------------------------------------------------------------------------------- 1 | setDefault('tag', null) 19 | ->setRequired('tag') 20 | ->setAllowedTypes('tag', [Tag::class, 'null']); 21 | } 22 | 23 | public function buildForm(FormBuilderInterface $builder, array $options): void 24 | { 25 | $builder->add( 26 | 'languageCode', 27 | TranslationListType::class, 28 | [ 29 | 'tag' => $options['tag'], 30 | ], 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bundle/Form/Type/SynonymCreateType.php: -------------------------------------------------------------------------------- 1 | setDefaults( 19 | [ 20 | 'data_class' => SynonymCreateStruct::class, 21 | 'constraints' => [ 22 | new SynonymCreateStructConstraint(), 23 | ], 24 | ], 25 | ); 26 | } 27 | 28 | public function getParent(): string 29 | { 30 | return TagType::class; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /bundle/Resources/views/admin/tag/tabs/subtree_limitations.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'netgen_tags_admin' %} 2 | 3 | {% if subTreeLimitations is not empty %} 4 || {{ 'tag.content.content_type_id'|trans }} | 8 |{{ 'tag.content.content_type'|trans }} | 9 |{{ 'tag.content.field_def_identifier'|trans }} | 10 |
|---|---|---|
| {{ subTreeLimitation.contentTypeId }} | 16 |{{ netgen_tags_content_type_name(subTreeLimitation.contentTypeId) }} | 17 |{{ subTreeLimitation.attributeIdentifier }} | 18 |
{{ 'tag.subtree_limitations.no_limitations'|trans }}
24 | {% endif %} 25 | -------------------------------------------------------------------------------- /bundle/Core/FieldType/Tags/Value.php: -------------------------------------------------------------------------------- 1 | $tag->getKeyword() ?? '', 35 | $this->tags, 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /bundle/Resources/public/ibexa/admin/js/field/init.js: -------------------------------------------------------------------------------- 1 | /*global $*/ 2 | 3 | (function() { 4 | 'use strict'; 5 | 6 | var $ = jQuery; 7 | 8 | var initTagsTranslations = function () { 9 | $.NetgenTags.Base.defaults.translations = { 10 | selectedTags: 'Selected tags', 11 | loading: 'Loading...', 12 | noSelectedTags: 'There are no selected tags', 13 | suggestedTags: 'Suggested tags', 14 | noSuggestedTags: 'There are no tags to suggest', 15 | addNew: 'Add new', 16 | clickAddThisTag: 'Click to add this tag', 17 | removeTag: 'Remove tag', 18 | translateTag: 'Translate tag', 19 | existingTranslations: 'Existing translations', 20 | noExistingTranslations: 'No existing translations', 21 | addTranslation: 'Add translation', 22 | cancel: 'Cancel', 23 | ok: 'OK', 24 | browse: 'Browse', 25 | }; 26 | }; 27 | 28 | initTagsTranslations(); 29 | $('.tagssuggest').NetgenTags(); 30 | $('.parent-selector-tree').find('.tags-modal-tree').tagsTree({'modal': true}); 31 | 32 | })(); 33 | -------------------------------------------------------------------------------- /bundle/Resources/sass/admin/_content.scss: -------------------------------------------------------------------------------- 1 | #{$namespace} { 2 | .tag-title { 3 | margin:0 0 1.5em; 4 | .tag-title-note { 5 | font-size:0.5297545471em; 6 | font-weight:400; 7 | color:#999; 8 | display:block; 9 | strong { 10 | font-weight:400; 11 | color:$textColor; 12 | } 13 | } 14 | } 15 | 16 | // tags table 17 | table { 18 | width:100%; 19 | border-spacing:0; 20 | border-collapse:collapse; 21 | margin:0 0 1em; 22 | th, 23 | td { 24 | padding:8px 14px; 25 | border-bottom:1px solid $tableBorder; 26 | text-align:left; 27 | } 28 | th { 29 | background:$tableBorder; 30 | } 31 | td { 32 | background:#fff; 33 | } 34 | input[type='checkbox'], 35 | input[type='radio'] { 36 | + label { 37 | margin:0; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bundle/Resources/views/ibexa/admin/field/edit/view/default.html.twig: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /bundle/DependencyInjection/Compiler/DefaultStorageEnginePass.php: -------------------------------------------------------------------------------- 1 | hasParameter('ibexa.api.storage_engine.default')) { 17 | return; 18 | } 19 | 20 | /** @var string $defaultStorageEngine */ 21 | $defaultStorageEngine = $container->getParameter('ibexa.api.storage_engine.default'); 22 | 23 | $container->setAlias( 24 | 'netgen_tags.api.persistence_handler.tags.storage', 25 | sprintf( 26 | 'netgen_tags.api.storage_engine.%s.handler.tags', 27 | $defaultStorageEngine, 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bundle/Resources/sass/admin/_pagination.scss: -------------------------------------------------------------------------------- 1 | .pagination-centered { 2 | text-align:center; 3 | } 4 | .tags-pagination { 5 | list-style-type:none; 6 | display:inline-block; 7 | margin:1em auto; 8 | padding:0; 9 | @extend %clearfix; 10 | li { 11 | float:left; 12 | margin:0 1px 0 0; 13 | &.to-first, 14 | &.to-last, 15 | .page-of { 16 | display:none; 17 | } 18 | &.active { 19 | .tags-btn { 20 | cursor:default; 21 | } 22 | } 23 | } 24 | .tags-btn { 25 | border-radius:0; 26 | .md-icon { 27 | margin:0; 28 | } 29 | } 30 | @media screen and (max-width:600px) { 31 | li { 32 | &.to-page:not(.active) { 33 | display:none; 34 | } 35 | &.to-first, 36 | &.to-last { 37 | display:block; 38 | } 39 | .page-of { 40 | display:inline-block; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /bundle/Form/Type/TagMergeType.php: -------------------------------------------------------------------------------- 1 | setRequired('tag') 19 | ->setAllowedTypes('tag', Tag::class); 20 | } 21 | 22 | public function buildForm(FormBuilderInterface $builder, array $options): void 23 | { 24 | $builder 25 | ->add( 26 | 'mainTag', 27 | TagTreeType::class, 28 | [ 29 | 'label' => 'tag.main_tag', 30 | 'disableSubtree' => [$options['tag']->id], 31 | 'allowRootTag' => false, 32 | ], 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /bundle/Form/Type/TagConvertType.php: -------------------------------------------------------------------------------- 1 | setRequired('tag') 19 | ->setAllowedTypes('tag', Tag::class); 20 | } 21 | 22 | public function buildForm(FormBuilderInterface $builder, array $options): void 23 | { 24 | $builder 25 | ->add( 26 | 'mainTag', 27 | TagTreeType::class, 28 | [ 29 | 'label' => 'tag.main_tag', 30 | 'disableSubtree' => [$options['tag']->id], 31 | 'allowRootTag' => false, 32 | ], 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Stubs/ConfigResolverStub.php: -------------------------------------------------------------------------------- 1 | parameters[$namespace ?? $this->defaultNamespace][$paramName] ?? null; 18 | } 19 | 20 | public function hasParameter(string $paramName, ?string $namespace = null, ?string $scope = null): bool 21 | { 22 | return isset($this->parameters[$namespace ?? $this->defaultNamespace][$paramName]); 23 | } 24 | 25 | public function setDefaultNamespace(string $defaultNamespace): void 26 | { 27 | $this->defaultNamespace = $defaultNamespace; 28 | } 29 | 30 | public function getDefaultNamespace(): string 31 | { 32 | return $this->defaultNamespace; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /bundle/Core/REST/Output/ValueObjectVisitor/ContentList.php: -------------------------------------------------------------------------------- 1 | startObjectElement('ContentList'); 16 | $visitor->setHeader('Content-Type', $generator->getMediaType('ContentList')); 17 | // @todo Needs refactoring, disabling certain headers should not be done this way 18 | $visitor->setHeader('Accept-Patch', ''); 19 | 20 | $generator->startAttribute('href', $data->path); 21 | $generator->endAttribute('href'); 22 | 23 | $generator->startList('ContentInfo'); 24 | foreach ($data->contents as $content) { 25 | $visitor->visitValueObject($content); 26 | } 27 | $generator->endList('ContentInfo'); 28 | 29 | $generator->endObjectElement('ContentList'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bundle/API/Repository/Values/Tags/TagList.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class TagList extends ArrayCollection 16 | { 17 | public function __construct(array $tags = []) 18 | { 19 | parent::__construct( 20 | array_filter( 21 | $tags, 22 | static fn (Tag $tag): bool => true, 23 | ), 24 | ); 25 | } 26 | 27 | /** 28 | * @return array| {{ 'tag.content.content_id'|trans }} | 8 |{{ 'tag.content.name'|trans }} | 9 |{{ 'tag.content.modified'|trans }} | 10 |{{ 'tag.content.content_type'|trans }} | 11 |
|---|---|---|---|
| {{ content.id }} | 17 |{{ content.name }} | 18 |{{ content.modificationDate|date }} | 19 |{{ netgen_tags_content_type_name(content.contentTypeId) }} | 20 |
{{ 'tag.latest_content.no_content'|trans }}
28 | {% endif %} 29 | -------------------------------------------------------------------------------- /phpunit-integration-legacy.xml: -------------------------------------------------------------------------------- 1 | 2 |23 | {{ 'pagelayout.you_are_here'|trans }}: 24 | 25 | {% for path_item in path_array %} 26 | {% if path_item.url is not same as(false) %} 27 | {{ path_item.text }} 28 | {% else %} 29 | {{ path_item.text }} 30 | {% endif %} 31 | 32 | {% if not loop.last %} 33 | / 34 | {% endif %} 35 | {% endfor %} 36 |
37 | {% endif %} 38 | -------------------------------------------------------------------------------- /bundle/Resources/config/services.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | netgen_tags.controller.tag_view: 3 | class: Netgen\TagsBundle\Controller\TagViewController 4 | calls: 5 | - setContainer: ["@Psr\\Container\\ContainerInterface"] 6 | tags: 7 | - { name: container.service_subscriber } 8 | - { name: controller.service_arguments } 9 | 10 | netgen_tags.routing.tag_url_generator: 11 | class: Netgen\TagsBundle\Routing\Generator\TagUrlGenerator 12 | parent: Ibexa\Core\MVC\Symfony\Routing\Generator 13 | arguments: 14 | - "@netgen_tags.api.service.tags" 15 | - "@router.default" 16 | - "@ibexa.config.resolver" 17 | 18 | netgen_tags.routing.tag_router: 19 | class: Netgen\TagsBundle\Routing\TagRouter 20 | arguments: 21 | - "@netgen_tags.api.service.tags" 22 | - "@netgen_tags.routing.tag_url_generator" 23 | - "@ibexa.config.resolver" 24 | - "@?router.request_context" 25 | - "@?logger" 26 | tags: 27 | - { name: router, priority: 200 } 28 | 29 | netgen_tags.repository.related_content_facets_loader: 30 | class: Netgen\TagsBundle\Core\Repository\RelatedContentFacetsLoader 31 | arguments: 32 | - "@netgen_tags.api.service.tags.inner" 33 | - "@ibexa.api.service.search" 34 | -------------------------------------------------------------------------------- /bundle/Resources/views/ibexa/admin/field/browse/view/browse.html.twig: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /bundle/API/Repository/Events/Tags/BeforeCreateTagEvent.php: -------------------------------------------------------------------------------- 1 | tagCreateStruct; 25 | } 26 | 27 | public function getTag(): Tag 28 | { 29 | $this->tag 30 | ?? throw new UnexpectedValueException( 31 | sprintf( 32 | 'Return value is not set or not a type of %s. Check with hasTag() or set it with setTag() before you call the getter.', 33 | Tag::class, 34 | ), 35 | ); 36 | 37 | return $this->tag; 38 | } 39 | 40 | public function setTag(Tag $tag): void 41 | { 42 | $this->tag = $tag; 43 | } 44 | 45 | public function hasTag(): bool 46 | { 47 | return isset($this->tag); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /bundle/Resources/views/form/tag_tree.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'netgen_tags_admin' %} 2 | 3 | {% block tag_tree_widget %} 4 | 26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /bundle/Resources/sass/admin/_variables.scss: -------------------------------------------------------------------------------- 1 | $namespace: '.ng-tags-app'; 2 | 3 | /* Colors */ 4 | $sidebarBg: hsl(0, 0, 90); 5 | $contentBg: hsl(0, 0, 96); 6 | $textColor: hsl(0, 0, 25); 7 | $linkColor: #2970ef; 8 | $borderColor: hsl(0, 0, 90); 9 | $errorColor: hsl(0, 100, 60); 10 | $brandDark: hsl(192, 74, 54); 11 | $brandLight: hsl(91, 72, 70); 12 | 13 | $btnDefault: #fff; 14 | $btnDefaultColor: hsl(0, 0, 35); 15 | $btnPrimary: $linkColor; 16 | $btnPrimaryColor: #fff; 17 | 18 | $tableBorder: hsl(0, 0, 90); 19 | 20 | /* Dimensions */ 21 | $gutter: 15px; 22 | $sidebarWidth: 240px; 23 | $borderRadius: 2px; 24 | $btnHeight: 36px; 25 | $baseFontSize: 14px; 26 | 27 | $baseFont: 'Roboto', Helvetica, Arial, sans-serif; 28 | 29 | %clearfix { 30 | &::after { 31 | display:table; 32 | clear:both; 33 | content:""; 34 | } 35 | } 36 | 37 | %material-icons { 38 | font-family: 'Material Icons'; 39 | font-weight: normal; 40 | font-style: normal; 41 | font-size: 24px; 42 | line-height: 1; 43 | letter-spacing: normal; 44 | text-transform: none; 45 | display: inline-block; 46 | white-space: nowrap; 47 | word-wrap: normal; 48 | direction: ltr; 49 | -webkit-font-feature-settings: 'liga'; 50 | -webkit-font-smoothing: antialiased; 51 | } 52 | 53 | //responsive breakpoints 54 | $break-lg:1200px; 55 | $break-md:992px; 56 | $break-sm:768px; 57 | $break-xs:480px; 58 | -------------------------------------------------------------------------------- /bundle/ValueResolver/TagValueResolver.php: -------------------------------------------------------------------------------- 1 | getType() === null || !is_a($argument->getType(), Tag::class, true)) { 24 | return []; 25 | } 26 | 27 | $tagId = null; 28 | 29 | if ($argument->getName() === 'tag' && $request->attributes->has('tagId')) { 30 | $tagId = $request->attributes->getInt('tagId'); 31 | } elseif ($argument->getName() === 'parentTag' && $request->attributes->has('parentId')) { 32 | $tagId = $request->attributes->getInt('parentId'); 33 | } 34 | 35 | if ($tagId === null || $tagId <= 0) { 36 | return []; 37 | } 38 | 39 | yield $this->tagsService->loadTag($tagId); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bundle/Core/REST/Output/ValueObjectVisitor/TagList.php: -------------------------------------------------------------------------------- 1 | startObjectElement('TagList'); 18 | $visitor->setHeader('Content-Type', $generator->getMediaType('TagList')); 19 | 20 | $generator->startAttribute('href', $data->path); 21 | $generator->endAttribute('href'); 22 | 23 | $generator->startList('Tag'); 24 | 25 | foreach ($data->tags as $restTag) { 26 | $generator->startObjectElement('Tag'); 27 | $generator->startAttribute( 28 | 'href', 29 | $this->router->generate( 30 | 'ibexa.rest.netgen_tags_loadTag', 31 | ['tagPath' => mb_trim($restTag->tag->pathString, '/')], 32 | ), 33 | ); 34 | $generator->endAttribute('href'); 35 | $generator->endObjectElement('Tag'); 36 | } 37 | 38 | $generator->endList('Tag'); 39 | 40 | $generator->endObjectElement('TagList'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /bundle/API/Repository/Events/Tags/BeforeAddSynonymEvent.php: -------------------------------------------------------------------------------- 1 | synonymCreateStruct; 25 | } 26 | 27 | public function getSynonym(): Tag 28 | { 29 | $this->synonym 30 | ?? throw new UnexpectedValueException( 31 | sprintf( 32 | 'Return value is not set or not a type of %s. Check with hasSynonym() or set it with setSynonym() before you call the getter.', 33 | Tag::class, 34 | ), 35 | ); 36 | 37 | return $this->synonym; 38 | } 39 | 40 | public function setSynonym(Tag $synonym): void 41 | { 42 | $this->synonym = $synonym; 43 | } 44 | 45 | public function hasSynonym(): bool 46 | { 47 | return isset($this->synonym); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /bundle/API/Repository/Events/Tags/BeforeConvertToSynonymEvent.php: -------------------------------------------------------------------------------- 1 | tag; 25 | } 26 | 27 | public function getMainTag(): Tag 28 | { 29 | return $this->mainTag; 30 | } 31 | 32 | public function getSynonym(): Tag 33 | { 34 | $this->synonym 35 | ?? throw new UnexpectedValueException( 36 | sprintf( 37 | 'Return value is not set or not a type of %s. Check with hasSynonym() or set it with setSynonym() before you call the getter.', 38 | Tag::class, 39 | ), 40 | ); 41 | 42 | return $this->synonym; 43 | } 44 | 45 | public function setSynonym(Tag $synonym): void 46 | { 47 | $this->synonym = $synonym; 48 | } 49 | 50 | public function hasSynonym(): bool 51 | { 52 | return isset($this->synonym); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /bundle/Core/REST/Values/CachedValue.php: -------------------------------------------------------------------------------- 1 | $cacheTags Associative array of cache tags. Example: array( 'tagId' => 42, 'tagKeyword' => 'Some tag|#eng-GB' ). 20 | */ 21 | public function __construct( 22 | public mixed $value, 23 | public array $cacheTags = [], 24 | ) { 25 | $this->cacheTags = $this->checkCacheTags($cacheTags); 26 | } 27 | 28 | /** 29 | * Checks for unsupported cache tags. 30 | * 31 | * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException If invalid cache tags are provided 32 | */ 33 | private function checkCacheTags(array $tags): array 34 | { 35 | $invalidTags = array_diff(array_keys($tags), ['tagId', 'tagKeyword']); 36 | if (count($invalidTags) > 0) { 37 | throw new InvalidArgumentException( 38 | 'cacheTags', 39 | 'Unknown cache tag(s): ' . implode(', ', $invalidTags), 40 | ); 41 | } 42 | 43 | return $tags; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /bundle/Core/Pagination/Pagerfanta/SearchTagsAdapter.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class SearchTagsAdapter implements AdapterInterface 14 | { 15 | private string $searchText; 16 | 17 | private string $language; 18 | 19 | private int $nbResults; 20 | 21 | public function __construct( 22 | private TagsService $tagsService, 23 | ) {} 24 | 25 | public function setSearchText(string $searchText): void 26 | { 27 | $this->searchText = $searchText; 28 | } 29 | 30 | public function setLanguage(string $language): void 31 | { 32 | $this->language = $language; 33 | } 34 | 35 | public function getNbResults(): int 36 | { 37 | if (!isset($this->nbResults)) { 38 | $this->getSlice(0, 1); 39 | } 40 | 41 | return $this->nbResults; 42 | } 43 | 44 | public function getSlice($offset, $length): iterable 45 | { 46 | $searchResult = $this->tagsService->searchTags($this->searchText, $this->language, true, $offset, $length === 0 ? -1 : $length); 47 | $this->nbResults = $searchResult->totalCount; 48 | 49 | return $searchResult->tags; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /bundle/API/Repository/Events/Tags/BeforeMoveSubtreeEvent.php: -------------------------------------------------------------------------------- 1 | tag; 25 | } 26 | 27 | public function getParentTag(): ?Tag 28 | { 29 | return $this->parentTag; 30 | } 31 | 32 | public function getMovedTag(): Tag 33 | { 34 | $this->movedTag 35 | ?? throw new UnexpectedValueException( 36 | sprintf( 37 | 'Return value is not set or not a type of %s. Check with hasMovedTag() or set it with setMovedTag() before you call the getter.', 38 | Tag::class, 39 | ), 40 | ); 41 | 42 | return $this->movedTag; 43 | } 44 | 45 | public function setMovedTag(Tag $movedTag): void 46 | { 47 | $this->movedTag = $movedTag; 48 | } 49 | 50 | public function hasMovedTag(): bool 51 | { 52 | return isset($this->movedTag); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /bundle/Resources/views/admin/tag/copy_tags.html.twig: -------------------------------------------------------------------------------- 1 | {% extends netgen_tags_admin.pageLayoutTemplate %} 2 | 3 | {% trans_default_domain 'netgen_tags_admin' %} 4 | 5 | {% form_theme form '@NetgenTags/form/tags.html.twig' %} 6 | 7 | {% block content %} 8 || {{ 'tag.title'|trans }} | 15 |
|---|
| 22 | 23 | {% if parentTag is not null %}{{ parentTag.keyword }} / {% endif %}{{ tag.keyword }} 24 | | 25 |
{{ 'tag.move_tags.message'|trans }}
11 | 12 | {{ form_start(form) }} 13 || {{ 'tag.title'|trans }} | 17 |
|---|
| 24 | 25 | {% if parentTag is not null %}{{ parentTag.keyword }} / {% endif %}{{ tag.keyword }} 26 | | 27 |
{{ 'tag.delete_tags.message'|trans }}
9 | 10 | 39 | 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /bundle/Validator/Structs/SynonymCreateStructValidator.php: -------------------------------------------------------------------------------- 1 | context->getValidator()->inContext($this->context); 35 | 36 | $validator->atPath('mainTagId')->validate( 37 | $value->mainTagId, 38 | [ 39 | new Constraints\Type(type: 'int'), 40 | new Constraints\NotBlank(), 41 | new Tag(['allowRootTag' => false]), 42 | ], 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /bundle/Core/HttpCache/Tagger.php: -------------------------------------------------------------------------------- 1 | id, 26 | ]; 27 | 28 | if ($tag->isSynonym()) { 29 | $tags[] = self::MAIN_TAG_PREFIX . $tag->mainTagId; 30 | } 31 | 32 | foreach ($tag->path as $tagId) { 33 | if ($tagId !== $tag->id) { 34 | $tags[] = self::PATH_TAG_PREFIX . $tagId; 35 | } 36 | } 37 | 38 | return $tags; 39 | } 40 | 41 | public function tagByTag(Tag $tag): void 42 | { 43 | $this->handler->addTags($this->resolveTags($tag)); 44 | } 45 | 46 | public function tagByTagId(int $tagId): void 47 | { 48 | $this->handler->addTags([self::TAG_PREFIX . $tagId]); 49 | } 50 | 51 | /** 52 | * @param int[] $tagIds 53 | */ 54 | public function tagByTagIds(array $tagIds): void 55 | { 56 | $this->handler->addTags( 57 | array_map( 58 | static fn (int $tagId) => self::TAG_PREFIX . $tagId, 59 | $tagIds, 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /bundle/Resources/config/validators.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | netgen_tags.validator.tag_create_struct: 3 | class: Netgen\TagsBundle\Validator\Structs\TagCreateStructValidator 4 | tags: 5 | - { name: validator.constraint_validator, alias: netgen_tags_tag_create_struct } 6 | 7 | netgen_tags.validator.tag_update_struct: 8 | class: Netgen\TagsBundle\Validator\Structs\TagUpdateStructValidator 9 | tags: 10 | - { name: validator.constraint_validator, alias: netgen_tags_tag_update_struct } 11 | 12 | netgen_tags.validator.synonym_create_struct: 13 | class: Netgen\TagsBundle\Validator\Structs\SynonymCreateStructValidator 14 | arguments: 15 | - "@netgen_tags.api.service.tags" 16 | tags: 17 | - { name: validator.constraint_validator, alias: netgen_tags_synonym_create_struct } 18 | 19 | netgen_tags.validator.tag: 20 | class: Netgen\TagsBundle\Validator\TagValidator 21 | arguments: 22 | - "@netgen_tags.api.service.tags" 23 | tags: 24 | - { name: validator.constraint_validator, alias: netgen_tags_tag } 25 | 26 | netgen_tags.validator.language: 27 | class: Netgen\TagsBundle\Validator\LanguageValidator 28 | arguments: 29 | - "@ibexa.api.service.language" 30 | tags: 31 | - { name: validator.constraint_validator, alias: netgen_tags_language } 32 | 33 | netgen_tags.validator.remote_id: 34 | class: Netgen\TagsBundle\Validator\RemoteIdValidator 35 | arguments: 36 | - "@netgen_tags.api.service.tags" 37 | tags: 38 | - { name: validator.constraint_validator, alias: netgen_tags_remote_id } 39 | -------------------------------------------------------------------------------- /bundle/Resources/sass/admin/_modal.scss: -------------------------------------------------------------------------------- 1 | /* The Modal (background) */ 2 | .ng-modal { 3 | display:none; 4 | position:fixed; 5 | z-index:1000; 6 | left:0; 7 | top:0; 8 | width:100%; 9 | height:100%; 10 | overflow:auto; 11 | background:hsla(0,0,0,0.54); 12 | animation:modalFadeIn 0.5s; 13 | .content { 14 | background-color:#fff; 15 | margin:0; 16 | padding:$gutter*2 $gutter $gutter; 17 | position:absolute; 18 | top:50%; 19 | left:50%; 20 | transform:translate(-50%, -50%); 21 | box-shadow:rgba(0, 0, 0, 0.247059) 0px 14px 45px, rgba(0, 0, 0, 0.219608) 0px 10px 18px; 22 | border-radius:$borderRadius; 23 | width:50%; 24 | min-width:300px; 25 | max-width:600px; 26 | animation:modalSlideIn 0.5s; 27 | max-height:98%; 28 | overflow-y:scroll; 29 | } 30 | 31 | .close { 32 | color:hsl(187, 100, 42); 33 | position:absolute; 34 | right:10px; 35 | top:10px; 36 | font-size:28px; 37 | display:block; 38 | width:24px; 39 | height:24px; 40 | line-height:24px; 41 | text-align:center; 42 | transition:color 0.2s; 43 | z-index:2; 44 | &:hover, 45 | &:focus { 46 | color:$textColor; 47 | text-decoration:none; 48 | cursor:pointer; 49 | } 50 | } 51 | } 52 | @keyframes modalFadeIn { 53 | 0% { 54 | opacity:0; 55 | } 56 | 100% { 57 | opacity:1; 58 | } 59 | } 60 | @keyframes modalSlideIn { 61 | 0% { 62 | margin-top:-100px; 63 | } 64 | 100% { 65 | margin-top:0; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /bundle/Resources/views/form/tags.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'netgen_tags_admin' %} 2 | 3 | {% use '@NetgenTags/form/tag_tree.html.twig' %} 4 | 5 | {%- block form_row -%} 6 | 17 | {%- endblock form_row -%} 18 | 19 | {% block translation_list_widget %} 20 | {% set add_translation_closed = false %} 21 | 22 |{{ 'tag.translations.edit_translation'|trans }}
25 | 26 |{{ 'tag.translations.add_translation'|trans }}
29 | 30 |{{ 'tag.translations.add_translation'|trans }}
38 | 39 |