├── 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 |
3 | 4 |
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 | 3 | 4 | 5 | 6 | #34bfe0 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /bundle/Resources/sass/admin/style.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | //IMPORTANT: only add imports to this file 3 | 4 | @import "fonts"; 5 | @import "variables"; 6 | @import "base"; 7 | @import "typography"; 8 | @import "buttons"; 9 | @import "forms"; 10 | @import "pagination"; 11 | @import "tree"; 12 | @import "tabs"; 13 | @import "modal"; 14 | @import "resize"; 15 | @import "content"; 16 | -------------------------------------------------------------------------------- /bundle/Resources/config/limitations.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | netgen_tags.role.limitation_type.tag: 3 | class: Netgen\TagsBundle\Core\Limitation\TagLimitationType 4 | arguments: 5 | - "@ibexa.api.persistence_handler" 6 | - "@netgen_tags.api.persistence_handler.tags" 7 | tags: 8 | - { name: ibexa.permissions.limitation_type, alias: Tag } 9 | -------------------------------------------------------------------------------- /bundle/Core/Pagination/Pagerfanta/TagAdapterInterface.php: -------------------------------------------------------------------------------- 1 | [ 7 | [ 8 | 'id' => '1856', 9 | 'version' => '1', 10 | 'content_type_field_definition_id' => '794', 11 | 'language_code' => 'eng-GB', 12 | 'sort_key_string' => 'data_text', 13 | ], 14 | ], 15 | ]; 16 | -------------------------------------------------------------------------------- /bundle/Resources/translations/validators.en.yml: -------------------------------------------------------------------------------- 1 | netgen_tags.tag.no_tag: 'Tag with ID %tagId% does not exist.' 2 | netgen_tags.tag.synonym: 'Selected tag can not be a synonym.' 3 | netgen_tags.tag.invalid_tag: 'Tag is invalid.' 4 | 5 | netgen_tags.language.no_language: 'Language code %languageCode% does not exist.' 6 | 7 | netgen_tags.remote_id.remote_id_exists: 'Tag with remote ID "%remoteId%" already exists.' 8 | -------------------------------------------------------------------------------- /bundle/Validator/Constraints/Structs/TagCreateStruct.php: -------------------------------------------------------------------------------- 1 | 3 | {{ message }} 4 | 5 | {% endfor %} 6 | 7 | {% for message in app.session.flashBag.get('tags.errors') %} 8 |
9 | {{ message }} 10 |
11 | {% endfor %} 12 | -------------------------------------------------------------------------------- /bundle/TagsServiceAwareTrait.php: -------------------------------------------------------------------------------- 1 | tagsService = $tagsService; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /bundle/Resources/config/routing.yaml: -------------------------------------------------------------------------------- 1 | netgen_tags_rest: 2 | resource: "routing/rest.yaml" 3 | prefix: "%ibexa.rest.path_prefix%" 4 | 5 | netgen_tags_internal: 6 | resource: "routing/internal.yaml" 7 | 8 | netgen_tags_admin_redirects: 9 | resource: "routing/admin/redirects.yaml" 10 | prefix: /tags 11 | 12 | netgen_tags_admin: 13 | resource: "routing/admin.yaml" 14 | prefix: /tags/admin 15 | -------------------------------------------------------------------------------- /bundle/Resources/config/routing/admin/synonym.yaml: -------------------------------------------------------------------------------- 1 | netgen_tags_admin_synonym_add_select: 2 | path: /add/{mainTagId} 3 | controller: netgen_tags.admin.controller.synonym::addSynonymSelectAction 4 | methods: [GET, POST] 5 | 6 | netgen_tags_admin_synonym_add: 7 | path: /add/{mainTagId}/{languageCode} 8 | controller: netgen_tags.admin.controller.synonym::addSynonymAction 9 | methods: [GET, POST] 10 | -------------------------------------------------------------------------------- /bundle/Resources/public/admin/images/tags-logo-silhouette.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /bundle/Validator/Constraints/Structs/SynonymCreateStruct.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | addRules([ 8 | 'phpdoc_no_alias_tag' => false, 9 | ]) 10 | ->setFinder( 11 | PhpCsFixer\Finder::create() 12 | ->in('bundle') 13 | ) 14 | ; 15 | -------------------------------------------------------------------------------- /bundle/Validator/Constraints/Language.php: -------------------------------------------------------------------------------- 1 | {{ 'dashboard.title'|trans }} 7 | 8 | {% include '@NetgenTags/admin/flash_messages.html.twig' %} 9 | 10 | {% include '@NetgenTags/admin/tag/children.html.twig' with {'childrenTags': childrenTags} %} 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /bundle/Validator/Constraints/RemoteId.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /bundle/Controller/TagViewController.php: -------------------------------------------------------------------------------- 1 | [ 7 | [ 8 | 'id' => '794', 9 | 'is_searchable' => '1', 10 | 'data_type_string' => 'eztags', 11 | 'identifier' => 'tags', 12 | 'category' => '', 13 | 'content_type_id' => '21', 14 | 'serialized_name_list' => '', 15 | ], 16 | ], 17 | ]; 18 | -------------------------------------------------------------------------------- /bundle/Form/Type/AbstractType.php: -------------------------------------------------------------------------------- 1 | setDefault('translation_domain', 'netgen_tags_admin'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /bundle/Controller/Admin/AccessController.php: -------------------------------------------------------------------------------- 1 | isGranted('ibexa:tags:add')); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /bundle/Matcher/Tag/Depth.php: -------------------------------------------------------------------------------- 1 | values[$view->getTag()->depth]); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bundle/Matcher/Tag/Keyword.php: -------------------------------------------------------------------------------- 1 | values[$view->getTag()->getKeyword()]); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bundle/API/Repository/Events/Tags/DeleteTagEvent.php: -------------------------------------------------------------------------------- 1 | tag; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bundle/API/Repository/Events/Tags/BeforeDeleteTagEvent.php: -------------------------------------------------------------------------------- 1 | tag; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bundle/API/Repository/Values/Tags/SearchResult.php: -------------------------------------------------------------------------------- 1 | {{ 'tag.add.title'|trans }} 9 | 10 | {{ form_start(form) }} 11 | {{ form_widget(form) }} 12 | 13 | 14 | {{ form_end(form) }} 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /bundle/Matcher/Tag/Id/Tag.php: -------------------------------------------------------------------------------- 1 | values[$view->getTag()->id]); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /bundle/Matcher/Tag/Id/MainTag.php: -------------------------------------------------------------------------------- 1 | values[$view->getTag()->mainTagId]); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /bundle/Matcher/Tag/Id/Remote.php: -------------------------------------------------------------------------------- 1 | values[$view->getTag()->remoteId]); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /bundle/Resources/views/admin/tag/update.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.edit.title'|trans }}: {{ tag.keyword }} [{{ tag.id }}]

9 | 10 | {{ form_start(form) }} 11 | {{ form_widget(form) }} 12 | 13 | 14 | {{ form_end(form) }} 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /bundle/Matcher/Tag/Id/ParentTag.php: -------------------------------------------------------------------------------- 1 | values[$view->getTag()->parentTagId]); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/coding_standards.yml: -------------------------------------------------------------------------------- 1 | name: Coding standards 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | - '[0-9].[0-9]+' 8 | pull_request: ~ 9 | 10 | jobs: 11 | php-cs-fixer: 12 | name: PHP CS Fixer 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v6 17 | - uses: actions/checkout@v6 18 | with: 19 | repository: netgen/coding-standard 20 | path: vendor/netgen/coding-standard 21 | - uses: docker://oskarstark/php-cs-fixer-ga 22 | with: 23 | args: --diff --dry-run 24 | -------------------------------------------------------------------------------- /bundle/Resources/config/ibexa.yaml: -------------------------------------------------------------------------------- 1 | system: 2 | admin_group: 3 | field_templates: 4 | - {template: "@NetgenTags/admin/eztags_content_field.html.twig", priority: 1} 5 | default: 6 | field_templates: 7 | - {template: "@NetgenTags/eztags_content_field.html.twig", priority: 0} 8 | fielddefinition_edit_templates: 9 | - {template: "@NetgenTags/field_definition/edit/eztags.html.twig", priority: 0} 10 | fielddefinition_settings_templates: 11 | - {template: "@NetgenTags/field_definition/view/eztags.html.twig", priority: 0} 12 | -------------------------------------------------------------------------------- /bundle/Resources/config/templating.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | netgen_tags.templating.twig.extension: 3 | class: Netgen\TagsBundle\Templating\Twig\Extension\NetgenTagsExtension 4 | tags: 5 | - { name: twig.extension } 6 | 7 | netgen_tags.templating.twig.runtime: 8 | class: Netgen\TagsBundle\Templating\Twig\Extension\NetgenTagsRuntime 9 | arguments: 10 | - "@netgen_tags.api.service.tags" 11 | - "@ibexa.api.service.language" 12 | - "@ibexa.api.service.content_type" 13 | tags: 14 | - { name: twig.runtime } 15 | -------------------------------------------------------------------------------- /bundle/Resources/translations/forms.en.yml: -------------------------------------------------------------------------------- 1 | 'role.policy.tags': 'Netgen Tags' 2 | 'role.policy.tags.all_functions': 'All functions' 3 | 'role.policy.tags.read': 'Read tags' 4 | 'role.policy.tags.view': 'Use "/tags/view" page' 5 | 'role.policy.tags.add': 'Add tags' 6 | 'role.policy.tags.addsynonym': 'Add synonyms' 7 | 'role.policy.tags.edit': 'Edit tags' 8 | 'role.policy.tags.editsynonym': 'Edit synonyms' 9 | 'role.policy.tags.delete': 'Delete tags' 10 | 'role.policy.tags.deletesynonym': 'Delete synonyms' 11 | 'role.policy.tags.makesynonym': 'Convert tag to synonym' 12 | 'role.policy.tags.merge': 'Merge tags' 13 | -------------------------------------------------------------------------------- /bundle/Validator/Constraints/Tag.php: -------------------------------------------------------------------------------- 1 | 0.2%", 18 | "not dead", 19 | "not ie <= 11", 20 | "not op_mini all" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /bundle/API/Repository/Values/Content/Query/SortClause/ContentTypeId.php: -------------------------------------------------------------------------------- 1 | tag; 20 | } 21 | 22 | public function getTargetTag(): Tag 23 | { 24 | return $this->targetTag; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bundle/API/Repository/Values/Tags/SynonymCreateStruct.php: -------------------------------------------------------------------------------- 1 | tag; 20 | } 21 | 22 | public function getTargetTag(): Tag 23 | { 24 | return $this->targetTag; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bundle/API/Repository/Events/Tags/MoveSubtreeEvent.php: -------------------------------------------------------------------------------- 1 | tag; 20 | } 21 | 22 | public function getParentTag(): ?Tag 23 | { 24 | return $this->parentTag; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/static_analysis.yml: -------------------------------------------------------------------------------- 1 | name: Static analysis 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | - '[0-9].[0-9]+' 8 | pull_request: ~ 9 | 10 | jobs: 11 | static-analysis: 12 | name: ${{ matrix.script }} 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | script: ['phpstan', 'phpstan-tests'] 19 | 20 | steps: 21 | - uses: actions/checkout@v6 22 | - uses: shivammathur/setup-php@v2 23 | with: 24 | php-version: '8.3' 25 | coverage: none 26 | 27 | - run: composer update --prefer-dist 28 | 29 | - run: composer ${{ matrix.script }} 30 | -------------------------------------------------------------------------------- /bundle/API/Repository/Events/Tags/ConvertToSynonymEvent.php: -------------------------------------------------------------------------------- 1 | synonym; 20 | } 21 | 22 | public function getMainTag(): Tag 23 | { 24 | return $this->mainTag; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bundle/Resources/translations/forms.fr.yml: -------------------------------------------------------------------------------- 1 | 'role.policy.tags': 'Netgen Tags' 2 | 'role.policy.tags.all_functions': 'Toutes les fonctions' 3 | 'role.policy.tags.read': 'Lire les tags' 4 | 'role.policy.tags.view': 'Utiliser la page "/tags/view"' 5 | 'role.policy.tags.add': 'Ajouter des tags' 6 | 'role.policy.tags.addsynonym': 'Ajouter des synonymes' 7 | 'role.policy.tags.edit': 'Modifier les tags' 8 | 'role.policy.tags.editsynonym': 'Modifier les synonymes' 9 | 'role.policy.tags.delete': 'Supprimer les tags' 10 | 'role.policy.tags.deletesynonym': 'Supprimer les synonymes' 11 | 'role.policy.tags.makesynonym': 'Convertir un tag en synonyme' 12 | 'role.policy.tags.merge': 'Fusionner les tags' 13 | -------------------------------------------------------------------------------- /bundle/Core/HttpCache/ResponseTagger/TagViewTagger.php: -------------------------------------------------------------------------------- 1 | tagger->tagByTag($value->getTag()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /bundle/Resources/views/admin/tag/select_translation.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 | {% if tag is defined %} 9 |

{{ 'tag.edit.title'|trans }}: {{ tag.keyword }} [{{ tag.id }}]

10 | {% else %} 11 |

{{ 'tag.add.title'|trans }}

12 | {% endif %} 13 | 14 | {{ form_start(form) }} 15 | {{ form_widget(form) }} 16 | 17 | 18 | {{ form_end(form) }} 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /tests/Templating/Twig/Extension/NetgenTagsExtensionTest.php: -------------------------------------------------------------------------------- 1 | extension = new NetgenTagsExtension(); 18 | } 19 | 20 | public function testGetFunctions(): void 21 | { 22 | self::assertContainsOnlyInstancesOf(TwigFunction::class, $this->extension->getFunctions()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /bundle/Resources/views/admin/tree.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'netgen_tags_admin' %} 2 | 3 | {% set rootTagId = rootTagId|default(0) %} 4 | {% set showRootTag = showRootTag|default(true) %} 5 | {% set selectedTag = selectedTag|default(null) %} 6 | {% set disableSubtree = disableSubtree|default([]) %} 7 | 8 | {% block tags_tree %} 9 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /bundle/Resources/views/ibexa/admin/javascripts.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /bundle/API/Repository/Events/Tags/CreateTagEvent.php: -------------------------------------------------------------------------------- 1 | tagCreateStruct; 21 | } 22 | 23 | public function getTag(): Tag 24 | { 25 | return $this->tag; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /bundle/API/Repository/Events/Tags/UpdateTagEvent.php: -------------------------------------------------------------------------------- 1 | tagUpdateStruct; 21 | } 22 | 23 | public function getTag(): Tag 24 | { 25 | return $this->tag; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /bundle/Resources/views/admin/tag/convert.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.convert.title'|trans }}: {{ tag.keyword }} [{{ tag.id }}]

9 | 10 | {{ form_start(form) }} 11 | {{ form_widget(form) }} 12 | 13 |
14 | 15 | {{ 'tag.button.discard'|trans }} 16 |
17 | {{ form_end(form) }} 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /bundle/Resources/translations/content_type.en.yml: -------------------------------------------------------------------------------- 1 | field_definition.tags.validator.subtree_limit: 'Limit by tags subtree' 2 | field_definition.tags.validator.subtree_limit.no_limit: 'No subtree limit' 3 | 4 | field_definition.tags.validator.max_tags: 'Maximum number of allowed tags' 5 | field_definition.tags.validator.max_tags.no_limit: 'No limit' 6 | 7 | field_definition.tags.settings.edit_view: 'Edit view' 8 | field_definition.tags.settings.edit_view.default: 'Default view' 9 | field_definition.tags.settings.edit_view.select: 'Select view' 10 | field_definition.tags.settings.edit_view.tree: 'Tree view' 11 | 12 | field_definition.tags.settings.hide_root_tag: 'Hide root subtree limit tag when editing object' 13 | 14 | field_definition.tags.settings.yes: 'Yes' 15 | field_definition.tags.settings.no: 'No' 16 | -------------------------------------------------------------------------------- /bundle/Resources/translations/ibexa_content_type.en.yml: -------------------------------------------------------------------------------- 1 | field_definition.tags.validator.subtree_limit: 'Limit by tags subtree' 2 | field_definition.tags.validator.subtree_limit.no_limit: 'No subtree limit' 3 | 4 | field_definition.tags.validator.max_tags: 'Maximum number of allowed tags' 5 | field_definition.tags.validator.max_tags.no_limit: 'No limit' 6 | 7 | field_definition.tags.settings.edit_view: 'Edit view' 8 | field_definition.tags.settings.edit_view.default: 'Default view' 9 | field_definition.tags.settings.edit_view.select: 'Select view' 10 | field_definition.tags.settings.edit_view.tree: 'Tree view' 11 | 12 | field_definition.tags.settings.hide_root_tag: 'Hide root subtree limit tag when editing object' 13 | 14 | field_definition.tags.settings.yes: 'Yes' 15 | field_definition.tags.settings.no: 'No' 16 | -------------------------------------------------------------------------------- /bundle/API/Repository/Events/Tags/AddSynonymEvent.php: -------------------------------------------------------------------------------- 1 | synonymCreateStruct; 21 | } 22 | 23 | public function getSynonym(): Tag 24 | { 25 | return $this->synonym; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /bundle/API/Repository/Events/Tags/CopySubtreeEvent.php: -------------------------------------------------------------------------------- 1 | tag; 21 | } 22 | 23 | public function getCopiedTag(): Tag 24 | { 25 | return $this->copiedTag; 26 | } 27 | 28 | public function getParentTag(): ?Tag 29 | { 30 | return $this->parentTag; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /bundle/Resources/views/admin/tag/merge.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.merge.title'|trans }}: {{ tag.keyword }} [{{ tag.id }}]

9 | 10 |

{{ 'tag.merge.message'|trans }}

11 | 12 | {{ form_start(form) }} 13 | {{ form_widget(form) }} 14 | 15 |
16 | 17 | {{ 'tag.button.discard'|trans }} 18 |
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 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 14 | 16 | 17 | 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 |
11 | 12 | 13 | {{ 'tag.button.no'|trans }} 14 |
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 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% for synonym in synonyms %} 14 | 15 | 16 | 17 | 18 | 19 | {% endfor %} 20 | 21 |
{{ 'tag.tag_id'|trans }}{{ 'tag.tag_name'|trans }}{{ 'tag.modified'|trans }}
{{ synonym.id }}{{ synonym.keyword }}{{ synonym.modificationDate|date }}
22 | {% else %} 23 |

{{ '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 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% for subTreeLimitation in subTreeLimitations %} 14 | 15 | 16 | 17 | 18 | 19 | {% endfor %} 20 | 21 |
{{ 'tag.content.content_type_id'|trans }}{{ 'tag.content.content_type'|trans }}{{ 'tag.content.field_def_identifier'|trans }}
{{ subTreeLimitation.contentTypeId }}{{ netgen_tags_content_type_name(subTreeLimitation.contentTypeId) }}{{ subTreeLimitation.attributeIdentifier }}
22 | {% else %} 23 |

{{ '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 |
2 |
3 |
Select tag
4 |
5 |
6 |
14 |
15 |
16 | 17 |
18 |
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 29 | */ 30 | public function getTags(): array 31 | { 32 | return $this->toArray(); 33 | } 34 | 35 | /** 36 | * @return int[] 37 | */ 38 | public function getTagIds(): array 39 | { 40 | return array_map( 41 | static fn (Tag $tag): int => $tag->id, 42 | $this->getTags(), 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /bundle/SPI/Persistence/Tags/CreateStruct.php: -------------------------------------------------------------------------------- 1 | "Hrvatska", "eng-GB" => "Croatia" ). 31 | * 32 | * @required 33 | * 34 | * @var string[] 35 | */ 36 | public array $keywords; 37 | 38 | /** 39 | * A global unique ID of the tag. 40 | */ 41 | public ?string $remoteId; 42 | 43 | /** 44 | * Indicates if the tag is shown in the main language if it's not present in an other requested language. 45 | */ 46 | public ?bool $alwaysAvailable; 47 | } 48 | -------------------------------------------------------------------------------- /bundle/SPI/Persistence/Tags/SynonymCreateStruct.php: -------------------------------------------------------------------------------- 1 | "Hrvatska", "eng-GB" => "Croatia" ). 31 | * 32 | * @required 33 | * 34 | * @var string[] 35 | */ 36 | public array $keywords; 37 | 38 | /** 39 | * A global unique ID of the tag. 40 | */ 41 | public ?string $remoteId; 42 | 43 | /** 44 | * Indicates if the tag is shown in the main language if it's not present in an other requested language. 45 | */ 46 | public ?bool $alwaysAvailable; 47 | } 48 | -------------------------------------------------------------------------------- /bundle/Resources/config/pagerfanta.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | netgen_tags.pagerfanta.related_content_adapter: 3 | class: Netgen\TagsBundle\Core\Pagination\Pagerfanta\RelatedContentAdapter 4 | arguments: 5 | - "@netgen_tags.api.service.tags" 6 | - "@ibexa.config.resolver" 7 | 8 | netgen_tags.pagerfanta.children_tags_adapter: 9 | class: Netgen\TagsBundle\Core\Pagination\Pagerfanta\ChildrenTagsAdapter 10 | arguments: 11 | - "@netgen_tags.api.service.tags" 12 | - 13 | netgen_tags.pagerfanta.search_tags_adapter: 14 | class: Netgen\TagsBundle\Core\Pagination\Pagerfanta\SearchTagsAdapter 15 | arguments: 16 | - "@netgen_tags.api.service.tags" 17 | 18 | netgen_tags.pagerfanta.view.netgen_tags_admin: 19 | class: Netgen\TagsBundle\Core\Pagination\Pagerfanta\View\TagsAdminView 20 | # Not being lazy results in out of memory exceptions on Ibexa 2.0, 21 | # probably due to @twig dependency 22 | lazy: true 23 | arguments: 24 | - "@twig" 25 | - "%netgen_tags.admin.default_pager_template%" 26 | tags: 27 | - { name: pagerfanta.view, alias: netgen_tags_admin } 28 | -------------------------------------------------------------------------------- /bundle/Resources/views/field_definition/edit/eztags.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain "content_type" %} 2 | 3 | {% block eztags_field_definition_edit %} 4 | {% form_theme form.subTreeLimit '@NetgenTags/form/tag_tree.html.twig' %} 5 | 6 |
7 | {{- form_label(form.subTreeLimit, null, {'translation_domain': 'content_type', 'label_attr': {'class': 'ibexa-label'}}) -}} 8 | {{- form_widget(form.subTreeLimit, {label: false}) -}} 9 |
10 | 11 |
12 | {{- form_row(form.maxTags, {'label_attr': {'class': 'ibexa-label'}}) -}} 13 |
14 | 15 |
16 | {{- form_row(form.hideRootTag, {'label_attr': {'class': 'ibexa-label'}}) -}} 17 |
18 | 19 |
20 | {{- form_row(form.editView, {'label_attr': {'class': 'ibexa-label'}}) -}} 21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /bundle/Form/Type/TagType.php: -------------------------------------------------------------------------------- 1 | add( 17 | 'keyword', 18 | TextType::class, 19 | [ 20 | 'label' => 'tag.tag_name', 21 | ], 22 | ) 23 | ->add( 24 | 'alwaysAvailable', 25 | CheckboxType::class, 26 | [ 27 | 'label' => 'tag.translations.always_available', 28 | 'required' => false, 29 | ], 30 | ) 31 | ->add( 32 | 'remoteId', 33 | TextType::class, 34 | [ 35 | 'label' => 'tag.remote_id', 36 | 'required' => false, 37 | ], 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bundle/Resources/translations/netgen_tags_admin_flash.en.yml: -------------------------------------------------------------------------------- 1 | errors.invalid_csrf_token: 'Invalid CSRF token' 2 | errors.no_selected_tags: 'You have to select at least one tag' 3 | errors.no_translation: 'Translation for locale "%locale%" does not exist' 4 | errors.no_translation_selected: 'You have to select at least one translation' 5 | errors.main_translation: 'You cannot remove the main translation' 6 | 7 | success.tag_added: 'New tag "%tagKeyword%" has been created' 8 | success.tag_updated: 'Tag "%tagKeyword%" has been updated' 9 | success.tag_deleted: 'Tag "%tagKeyword%" has been deleted' 10 | success.tag_merged: 'Tag "%tagKeyword%" has been merged with tag "%sourceTagKeyword%"' 11 | success.tag_converted: 'Tag "%tagKeyword%" has been converted to synonym of tag "%mainTagKeyword%"' 12 | success.tags_moved: 'Tags have been moved successfully' 13 | success.tags_copied: 'Tags have been copied successfully' 14 | success.tags_deleted: 'Tags have been deleted successfully' 15 | success.translation_removed: 'Translation for locale "%locale%" has been successfully removed' 16 | success.main_translation_set: 'Translation for locale "%locale%" has been set as new main translation' 17 | success.always_available_set: 'Always available flag has been successfully updated' 18 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | tests/Core 16 | 17 | 18 | tests/Templating 19 | 20 | 21 | 22 | 23 | 24 | bundle 25 | 26 | 27 | bundle/Controller 28 | bundle/DependencyInjection 29 | bundle/Resources 30 | bundle/NetgenTagsBundle.php 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /bundle/Core/FieldType/Tags/TagsStorage/Gateway.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | abstract public function loadTagData(int $parentTagId, string $keyword, string $language): array; 35 | } 36 | -------------------------------------------------------------------------------- /bundle/Resources/config/papi.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | netgen_tags.api.service.tags: 3 | alias: netgen_tags.site_access_aware.service.tags 4 | 5 | # For BC with Netgen Tags 4.0 6 | eztags.api.service.tags: 7 | alias: netgen_tags.api.service.tags 8 | 9 | netgen_tags.event.service.tags: 10 | class: Netgen\TagsBundle\Core\Event\TagsService 11 | arguments: 12 | - "@netgen_tags.api.service.tags.inner" 13 | - "@event_dispatcher" 14 | 15 | netgen_tags.site_access_aware.service.tags: 16 | class: Netgen\TagsBundle\Core\SiteAccessAware\TagsService 17 | arguments: 18 | - "@netgen_tags.event.service.tags" 19 | - "@ibexa.helper.language_resolver" 20 | 21 | netgen_tags.api.service.tags.mapper: 22 | class: Netgen\TagsBundle\Core\Repository\TagsMapper 23 | arguments: 24 | - "@Ibexa\\Contracts\\Core\\Persistence\\Content\\Language\\Handler" 25 | 26 | netgen_tags.api.service.tags.inner: 27 | class: Netgen\TagsBundle\Core\Repository\TagsService 28 | arguments: 29 | - "@ibexa.siteaccessaware.repository" 30 | - "@netgen_tags.api.persistence_handler.tags" 31 | - "@netgen_tags.api.service.tags.mapper" 32 | -------------------------------------------------------------------------------- /doc/changelogs/CHANGELOG-5.x.md: -------------------------------------------------------------------------------- 1 | Netgen Tags Bundle 5.x changelog 2 | ================================ 3 | 4 | 5.0.0 (06.05.2022) 5 | ------------------ 6 | 7 | Since 5.0 is a new major release, there were a number of breaking changes, so be sure to read [upgrade instructions](UPGRADE.md#upgrade-from-40-to-50). 8 | 9 | The following lists only the most important changes from version 4.0 to version 5.0: 10 | 11 | * Minimum supported version of PHP is now PHP 7.4 or 8.0+ 12 | * Minimum supported version of Ibexa Platform is now 4.0 13 | * Service name for Tags service has been renamed to `netgen_tags.api.service.tags` (old name is kept for BC) 14 | * The codebase has been modernized across the board: 15 | - Property typehints have been added to all code 16 | 17 | 5.1.0 (06.09.2023) 18 | ------------------ 19 | 20 | * Implemented TagId and TagKeyword visitors for elasticsearch (Thanks @petarjakopec) 21 | * Bumped all code to PHP 8.1 22 | 23 | 5.2.0 (29.03.2024) 24 | ------------------ 25 | 26 | * Bumped jQuery to 3.7.1 (Thanks @ljacmi) 27 | 28 | 5.3.0 (09.05.2025) 29 | ------------------ 30 | 31 | * Implemented HTTP cache clearing mechanism on various tag actions (thanks @pspanja) 32 | * Fixed infinite creation of same tags on content autosave 33 | * Bumped PHP to 8.2 34 | -------------------------------------------------------------------------------- /bundle/Resources/config/search/elasticsearch.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | netgen_tags.search.elasticsearch.query.common.criterion_visitor.tag_id: 3 | class: Netgen\TagsBundle\Core\Search\Elasticsearch\Query\Common\CriterionVisitor\Tags\TagId 4 | arguments: 5 | - "@Ibexa\\Core\\Search\\Common\\FieldNameResolver" 6 | - "@Ibexa\\Contracts\\Core\\Persistence\\Content\\Type\\Handler" 7 | - "eztags" 8 | - "tag_ids" 9 | tags: 10 | - { name: ibexa.search.elasticsearch.query.content.criterion.visitor } 11 | - { name: ibexa.search.elasticsearch.query.location.criterion.visitor } 12 | 13 | netgen_tags.search.elasticsearch.query.common.criterion_visitor.tag_keyword: 14 | class: Netgen\TagsBundle\Core\Search\Elasticsearch\Query\Common\CriterionVisitor\Tags\TagKeyword 15 | arguments: 16 | - "@Ibexa\\Core\\Search\\Common\\FieldNameResolver" 17 | - "@Ibexa\\Contracts\\Core\\Persistence\\Content\\Type\\Handler" 18 | - "eztags" 19 | - "tag_keywords" 20 | tags: 21 | - { name: ibexa.search.elasticsearch.query.content.criterion.visitor } 22 | - { name: ibexa.search.elasticsearch.query.location.criterion.visitor } 23 | -------------------------------------------------------------------------------- /bundle/Resources/views/admin/tag/tabs/latest_content.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'netgen_tags_admin' %} 2 | 3 | {% if latestContent is not empty %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for content in latestContent %} 15 | 16 | 17 | 18 | 19 | 20 | 21 | {% endfor %} 22 | 23 |
{{ 'tag.content.content_id'|trans }}{{ 'tag.content.name'|trans }}{{ 'tag.content.modified'|trans }}{{ 'tag.content.content_type'|trans }}
{{ content.id }}{{ content.name }}{{ content.modificationDate|date }}{{ netgen_tags_content_type_name(content.contentTypeId) }}
24 | 25 | {{ 'tag.latest_content.all_content'|trans }} 26 | {% else %} 27 |

{{ 'tag.latest_content.no_content'|trans }}

28 | {% endif %} 29 | -------------------------------------------------------------------------------- /phpunit-integration-legacy.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | tests/API 20 | 21 | 22 | 23 | 24 | 25 | bundle 26 | 27 | 28 | bundle/Controller 29 | bundle/DependencyInjection 30 | bundle/Resources 31 | bundle/NetgenTagsBundle.php 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/settings/integration/legacy.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | event_dispatcher: 3 | class: Netgen\TagsBundle\Tests\Stubs\EventDispatcherStub 4 | 5 | ibexa.config.resolver: 6 | class: Netgen\TagsBundle\Tests\Stubs\ConfigResolverStub 7 | arguments: 8 | - 9 | netgen_tags: { edit_views: { default: { identifier: Default } } } 10 | ibexa.site_access.config: { user_content_type_identifier: ['user'] } 11 | 12 | netgen_tags.field_type.eztags: 13 | class: Netgen\TagsBundle\Core\FieldType\Tags\Type 14 | parent: Ibexa\Core\FieldType\FieldType 15 | arguments: 16 | - "@netgen_tags.api.service.tags" 17 | - "@ibexa.config.resolver" 18 | tags: 19 | - { name: ibexa.field_type, alias: eztags } 20 | 21 | netgen_tags.api.persistence_handler.tags.storage: 22 | alias: netgen_tags.api.storage_engine.legacy.handler.tags 23 | 24 | netgen_tags.ibexa.spi.persistence.legacy.language.handler: 25 | alias: ibexa.spi.persistence.legacy.language.handler 26 | public: true 27 | 28 | netgen_tags.ibexa.persistence.legacy.language.mask_generator: 29 | alias: Ibexa\Core\Persistence\Legacy\Content\Language\MaskGenerator 30 | public: true 31 | -------------------------------------------------------------------------------- /bundle/View/Builder/ParametersFilter/CurrentPage.php: -------------------------------------------------------------------------------- 1 | 'addCurrentPage', 21 | ]; 22 | } 23 | 24 | /** 25 | * Adds the current page to the parameters. 26 | */ 27 | public function addCurrentPage(FilterViewBuilderParametersEvent $event): void 28 | { 29 | $parameterBag = $event->getParameters(); 30 | 31 | $route = $parameterBag->get('_route'); 32 | if (!in_array($route, [TagRouter::TAG_URL_ROUTE_NAME, TagUrlGenerator::INTERNAL_TAG_ROUTE], true)) { 33 | return; 34 | } 35 | 36 | $parameterBag->set('page', (int) $event->getRequest()->get('page', 1)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /bundle/Resources/views/ibexa/admin/pagelayout.html.twig: -------------------------------------------------------------------------------- 1 | {% set content = block('content') %} 2 | 3 | {# 4 | We embed the main layout.html.twig instead of extending it 5 | to be able to inject additional markup before and after its 6 | content block. 7 | #} 8 | {% embed '@ibexadesign/ui/layout.html.twig' %} 9 | {% trans_default_domain 'netgen_tags_admin' %} 10 | 11 | {% block title %}{{ 'pagelayout.title'|trans }}{% endblock %} 12 | 13 | {% block pageTitle %}{% endblock %} 14 | 15 | {% block content %} 16 |
17 | {% include '@NetgenTags/admin/header.html.twig' %} 18 | 19 |
20 |
21 | 24 |
25 | 26 |
27 |
28 | {% include '@NetgenTags/admin/path.html.twig' %} 29 |
30 | 31 | {{ content|raw }} 32 | 33 |
34 |
35 |
36 | {% endblock %} 37 | {% endembed %} 38 | -------------------------------------------------------------------------------- /bundle/Resources/translations/netgen_tags_admin_flash.fr.yml: -------------------------------------------------------------------------------- 1 | errors.invalid_csrf_token: 'Jeton CSRF invalide' 2 | errors.no_selected_tags: 'Vous devez sélectionner au moins un tag.' 3 | errors.no_translation: "La traduction pour la locale %locale% n'existe pas" 4 | errors.no_translation_selected: 'Vous devez sélectionner au moins une traduction' 5 | errors.main_translation: 'Vous ne pouvez pas supprimer la traduction principale' 6 | 7 | success.tag_added: 'Le nouveau tag "%tagKeyword%" a été créé' 8 | success.tag_updated: 'Le tag "%tagKeyword%" a été mis à jour' 9 | success.tag_deleted: 'Le tag "%tagKeyword%" a été supprimé' 10 | success.tag_merged: 'Le tag "%tagKeyword%" a été fusionné avec le tag "%sourceTagKeyword%"' 11 | success.tag_converted: 'Tag "%tagKeyword%" has been converted to synonym of tag "%mainTagKeyword%"' 12 | success.tags_moved: 'Les tags ont été déplacés avec succès' 13 | success.tags_copied: 'Les tags ont été copiés avec succès' 14 | success.tags_deleted: 'Les tags ont été supprimées avec succès' 15 | success.translation_removed: 'La traduction de la locale "%locale%" a été supprimée avec succès' 16 | success.main_translation_set: 'La traduction pour la locale "%locale%" a été définie comme nouvelle traduction principale' 17 | success.always_available_set: 'Le drapeau Toujours disponible a été mis à jour avec succès.' 18 | -------------------------------------------------------------------------------- /bundle/Resources/config/fieldtypes.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | netgen_tags.field_type.eztags: 3 | class: Netgen\TagsBundle\Core\FieldType\Tags\Type 4 | parent: Ibexa\Core\FieldType\FieldType 5 | arguments: 6 | - "@netgen_tags.api.service.tags" 7 | - "@ibexa.config.resolver" 8 | tags: 9 | - { name: ibexa.field_type, alias: eztags } 10 | 11 | netgen_tags.field_type.eztags.external_storage: 12 | class: Netgen\TagsBundle\Core\FieldType\Tags\TagsStorage 13 | arguments: 14 | - "@netgen_tags.field_type.eztags.storage_gateway" 15 | - "@netgen_tags.api.service.tags" 16 | tags: 17 | - { name: ibexa.field_type.storage.external.handler, alias: eztags } 18 | 19 | netgen_tags.field_type.eztags.indexable: 20 | class: Netgen\TagsBundle\Core\FieldType\Tags\SearchField 21 | tags: 22 | - { name: ibexa.field_type.indexable, alias: eztags } 23 | 24 | netgen_tags.field_type.eztags.form_mapper: 25 | class: Netgen\TagsBundle\Core\FieldType\Tags\FormMapper 26 | arguments: 27 | - "@ibexa.config.resolver" 28 | tags: 29 | - { name: ibexa.admin_ui.field_type.form.mapper.definition, fieldType: eztags } 30 | - { name: ibexa.admin_ui.field_type.form.mapper.value, fieldType: eztags } 31 | -------------------------------------------------------------------------------- /bundle/Core/Pagination/Pagerfanta/ChildrenTagsAdapter.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class ChildrenTagsAdapter implements AdapterInterface, TagAdapterInterface 15 | { 16 | private ?Tag $tag = null; 17 | 18 | private int $nbResults; 19 | 20 | public function __construct( 21 | private TagsService $tagsService, 22 | ) {} 23 | 24 | public function setTag(Tag $tag): void 25 | { 26 | $this->tag = $tag; 27 | } 28 | 29 | public function getNbResults(): int 30 | { 31 | $this->nbResults = $this->nbResults ?? $this->tagsService->getTagChildrenCount($this->tag); 32 | 33 | return $this->nbResults; 34 | } 35 | 36 | public function getSlice($offset, $length): iterable 37 | { 38 | $childrenTags = $this->tagsService->loadTagChildren($this->tag, $offset, $length); 39 | 40 | $this->nbResults = $this->nbResults ?? $this->tagsService->getTagChildrenCount($this->tag); 41 | 42 | return $childrenTags; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /bundle/Validator/LanguageValidator.php: -------------------------------------------------------------------------------- 1 | languageService->loadLanguage($value); 35 | } catch (NotFoundException) { 36 | $this->context->buildViolation($constraint->message) 37 | ->setParameter('%languageCode%', $value) 38 | ->addViolation(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bundle/Resources/views/admin/path.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'netgen_tags_admin' %} 2 | 3 | {% set current_tag = null %} 4 | {% set tag_path = [] %} 5 | {% set path_array = [] %} 6 | 7 | {% if app.request.attributes.has('tag') %} 8 | {% set current_tag = app.request.attributes.get('tag') %} 9 | {% set tag_path = current_tag.pathString|trim('/')|split('/') %} 10 | {% endif %} 11 | 12 | {% if current_tag is not null %} 13 | {% for tag_id in tag_path %} 14 | {% set path_array = path_array|merge([{ 15 | url: tag_id != current_tag.id ? 16 | path('netgen_tags_admin_tag_show', {tagId: tag_id}) : 17 | false, 18 | text: netgen_tags_tag_keyword(tag_id) 19 | }]) %} 20 | {% endfor %} 21 | 22 |

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 |
2 |
3 |
4 | 5 | Browse tags 6 | 7 |
8 |
9 |
10 |
11 |
12 |
13 |
21 |
22 |
23 |
24 |
25 |
26 | 27 |
28 |
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 |
5 |
6 | {{- form_label(form) -}} 7 | 8 | 9 | {% if tag is not null %}{{ tag.keyword }}{% else %}({{ 'tag.tree.no_tag_selected'|trans }}){% endif %} 10 | 11 | 12 | {# Renders a tree inside modal window, for selecting tag inside forms. #} 13 |
14 |
15 | close 16 | {% include '@NetgenTags/admin/tree.html.twig' with { 17 | 'selectedTag': tag, 18 | 'disableSubtree': disableSubtree 19 | } %} 20 |
21 |
22 | 23 | {{- form_errors(form) -}} 24 |
25 |
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.copy_tags.title'|trans }}

9 | 10 | {{ form_start(form) }} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for tag in tags %} 20 | 21 | 25 | 26 | {% endfor %} 27 | 28 |
{{ 'tag.title'|trans }}
22 | 23 | {% if parentTag is not null %}{{ parentTag.keyword }} / {% endif %}{{ tag.keyword }} 24 |
29 | 30 | {{ form_widget(form) }} 31 | 32 |
33 | 34 | 35 | 36 | {{ 'tag.button.discard'|trans }} 37 | 38 |
39 | {{ form_end(form) }} 40 | 41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /bundle/API/Repository/Events/Tags/BeforeCopySubtreeEvent.php: -------------------------------------------------------------------------------- 1 | tag; 25 | } 26 | 27 | public function getParentTag(): ?Tag 28 | { 29 | return $this->parentTag; 30 | } 31 | 32 | public function getCopiedTag(): Tag 33 | { 34 | $this->copiedTag 35 | ?? throw new UnexpectedValueException( 36 | sprintf( 37 | 'Return value is not set or not a type of %s. Check with hasCopiedTag() or set it with setCopiedTag() before you call the getter.', 38 | Tag::class, 39 | ), 40 | ); 41 | 42 | return $this->copiedTag; 43 | } 44 | 45 | public function setCopiedTag(Tag $copiedTag): void 46 | { 47 | $this->copiedTag = $copiedTag; 48 | } 49 | 50 | public function hasCopiedTag(): bool 51 | { 52 | return isset($this->copiedTag); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /bundle/Form/Type/CopyTagsType.php: -------------------------------------------------------------------------------- 1 | setRequired('tags') 21 | ->setAllowedTypes('tags', 'array') 22 | ->setAllowedValues('tags', static function (array $tags): bool { 23 | foreach ($tags as $tag) { 24 | if (!$tag instanceof Tag) { 25 | return false; 26 | } 27 | } 28 | 29 | return true; 30 | }); 31 | } 32 | 33 | public function buildForm(FormBuilderInterface $builder, array $options): void 34 | { 35 | $builder 36 | ->add( 37 | 'parentTag', 38 | TagTreeType::class, 39 | [ 40 | 'label' => 'tag.parent_tag', 41 | 'disableSubtree' => array_map( 42 | static fn (Tag $tag): int => $tag->id, 43 | $options['tags'], 44 | ), 45 | ], 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /bundle/Form/Type/MoveTagsType.php: -------------------------------------------------------------------------------- 1 | setRequired('tags') 21 | ->setAllowedTypes('tags', 'array') 22 | ->setAllowedValues('tags', static function (array $tags): bool { 23 | foreach ($tags as $tag) { 24 | if (!$tag instanceof Tag) { 25 | return false; 26 | } 27 | } 28 | 29 | return true; 30 | }); 31 | } 32 | 33 | public function buildForm(FormBuilderInterface $builder, array $options): void 34 | { 35 | $builder 36 | ->add( 37 | 'parentTag', 38 | TagTreeType::class, 39 | [ 40 | 'label' => 'tag.parent_tag', 41 | 'disableSubtree' => array_map( 42 | static fn (Tag $tag): int => $tag->id, 43 | $options['tags'], 44 | ), 45 | ], 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /bundle/Form/Type/TagCreateType.php: -------------------------------------------------------------------------------- 1 | setDefaults( 20 | [ 21 | 'data_class' => TagCreateStruct::class, 22 | 'constraints' => [ 23 | new TagCreateStructConstraint(), 24 | ], 25 | ], 26 | ); 27 | } 28 | 29 | public function buildForm(FormBuilderInterface $builder, array $options): void 30 | { 31 | $builder->add( 32 | 'parentTagId', 33 | TagTreeType::class, 34 | [ 35 | 'label' => 'tag.parent_tag', 36 | // Disable constraints specified in TagTreeType, since 37 | // they are validated in TagCreateStructConstraint 38 | 'constraints' => [], 39 | ], 40 | ); 41 | } 42 | 43 | public function getParent(): string 44 | { 45 | return TagType::class; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /bundle/Resources/views/admin/tag/move_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.move_tags.title'|trans }}

9 | 10 |

{{ 'tag.move_tags.message'|trans }}

11 | 12 | {{ form_start(form) }} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {% for tag in tags %} 22 | 23 | 27 | 28 | {% endfor %} 29 | 30 |
{{ 'tag.title'|trans }}
24 | 25 | {% if parentTag is not null %}{{ parentTag.keyword }} / {% endif %}{{ tag.keyword }} 26 |
31 | 32 | {{ form_widget(form) }} 33 | 34 |
35 | 36 | 37 | 38 | {{ 'tag.button.discard'|trans }} 39 | 40 |
41 | {{ form_end(form) }} 42 | 43 | {% endblock %} 44 | -------------------------------------------------------------------------------- /bundle/SPI/Persistence/Tags/TagInfo.php: -------------------------------------------------------------------------------- 1 | setRequired(['field']); 23 | $resolver->setAllowedTypes('field', Field::class); 24 | } 25 | 26 | public function buildForm(FormBuilderInterface $builder, array $options): void 27 | { 28 | $builder 29 | ->add('ids', HiddenType::class) 30 | ->add('parent_ids', HiddenType::class) 31 | ->add('keywords', HiddenType::class) 32 | ->add('locales', HiddenType::class) 33 | ->addModelTransformer( 34 | new FieldValueTransformer( 35 | $this->fieldTypeService->getFieldType('eztags'), 36 | $options['field'], 37 | ), 38 | ); 39 | } 40 | 41 | public function getBlockPrefix(): string 42 | { 43 | return 'ibexa_fieldtype_eztags'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /bundle/Validator/RemoteIdValidator.php: -------------------------------------------------------------------------------- 1 | tagsService->loadTagByRemoteId($value); 36 | 37 | if (!$constraint->payload instanceof Tag || $tag->id !== $constraint->payload->id) { 38 | $this->context->buildViolation($constraint->message) 39 | ->setParameter('%remoteId%', $value) 40 | ->addViolation(); 41 | } 42 | } catch (NotFoundException) { 43 | // Do nothing 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /doc/changelogs/CHANGELOG-4.x.md: -------------------------------------------------------------------------------- 1 | Netgen Tags Bundle 4.x changelog 2 | ================================ 3 | 4 | 4.0.6 (23.11.2020) 5 | ------------------ 6 | 7 | * Fix filtering when using the target argument (thanks @Plopix) 8 | 9 | 4.0.5 (09.11.2020) 10 | ------------------ 11 | 12 | * Make sure siteaccess exists in request before using it in `SetPageLayoutListener` 13 | 14 | 4.0.4 (06.11.2020) 15 | ------------------ 16 | 17 | * Fixed missing browser validation for required field (thanks @hgiesenow) 18 | 19 | 4.0.3 (10.07.2020) 20 | ------------------ 21 | 22 | * Support `cmf_routing_object` as the route name for tag objects 23 | * Fix move/copy/delete routes in admin 24 | 25 | 4.0.2 (10.07.2020) 26 | ------------------ 27 | 28 | * General fixes and cleanup 29 | 30 | 4.0.1 (21.04.2020) 31 | ------------------ 32 | 33 | * Support using Tag object with `ez_url` / `ez_path` Twig functions 34 | 35 | 4.0.0 (14.04.2020) 36 | ------------------ 37 | 38 | Since 4.0 is a new major release, there were a number of breaking changes, so be sure to read [upgrade instructions](UPGRADE.md#upgrade-from-34-to-40). 39 | 40 | The following lists only the most important changes from version 3.4 to version 4.0: 41 | 42 | * Minimum supported version of PHP is now PHP 7.3 43 | * Minimum supported version of eZ Platform is now 3.0 44 | * The codebase has been modernized across the board: 45 | - The entire code base is migrated to PHP 7 style code (scalar and return typehints, strict type checking) 46 | - Most of the classes are now `final`. Use composition to extend the functionality of the bundle 47 | -------------------------------------------------------------------------------- /bundle/Validator/Structs/TagCreateStructValidator.php: -------------------------------------------------------------------------------- 1 | context->getValidator()->inContext($this->context); 35 | 36 | $validator->atPath('parentTagId')->validate( 37 | $value->parentTagId, 38 | [ 39 | new Constraints\Type(type: 'int'), 40 | new Constraints\NotBlank(), 41 | new Tag(), 42 | ], 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /bundle/API/Repository/Events/Tags/BeforeUpdateTagEvent.php: -------------------------------------------------------------------------------- 1 | tagUpdateStruct; 26 | } 27 | 28 | public function getTag(): Tag 29 | { 30 | return $this->tag; 31 | } 32 | 33 | public function getUpdatedTag(): Tag 34 | { 35 | $this->updatedTag 36 | ?? throw new UnexpectedValueException( 37 | sprintf( 38 | 'Return value is not set or not a type of %s. Check with hasUpdatedTag() or set it with setUpdatedTag() before you call the getter.', 39 | Tag::class, 40 | ), 41 | ); 42 | 43 | return $this->updatedTag; 44 | } 45 | 46 | public function setUpdatedTag(Tag $updatedTag): void 47 | { 48 | $this->updatedTag = $updatedTag; 49 | } 50 | 51 | public function hasUpdatedTag(): bool 52 | { 53 | return isset($this->updatedTag); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /bundle/API/Repository/Values/Content/Query/Criterion/TagId.php: -------------------------------------------------------------------------------- 1 | setData($viewData->getKeyword($this->languageCode)); 28 | $forms['alwaysAvailable']->setData($viewData->alwaysAvailable); 29 | $forms['remoteId']->setData($viewData->remoteId); 30 | } 31 | 32 | public function mapFormsToData(Traversable $forms, mixed &$viewData): void 33 | { 34 | if (!$viewData instanceof TagUpdateStruct) { 35 | return; 36 | } 37 | 38 | $forms = iterator_to_array($forms); 39 | 40 | $keyword = $forms['keyword']->getData(); 41 | 42 | $keyword !== null 43 | ? $viewData->setKeyword($keyword, $this->languageCode) 44 | : $viewData->removeKeyword($this->languageCode); 45 | 46 | $viewData->alwaysAvailable = $forms['alwaysAvailable']->getData(); 47 | $viewData->remoteId = $forms['remoteId']->getData(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /bundle/Resources/views/admin/tag/delete_tags.html.twig: -------------------------------------------------------------------------------- 1 | {% extends netgen_tags_admin.pageLayoutTemplate %} 2 | 3 | {% trans_default_domain 'netgen_tags_admin' %} 4 | 5 | {% block content %} 6 |

{{ 'tag.delete_tags.title'|trans }}

7 | 8 |

{{ 'tag.delete_tags.message'|trans }}

9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for tag in tags %} 20 | 21 | 25 | 26 | {% endfor %} 27 | 28 |
{{ 'tag.title'|trans }}
22 | 23 | {% if parentTag is not null %}{{ parentTag.keyword }} / {% endif %}{{ tag.keyword }} 24 |
29 | 30 |
31 | 32 | 33 | 34 | 35 | {{ 'tag.button.no'|trans }} 36 | 37 |
38 |
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 |
7 | {%- if 'checkbox' in block_prefixes or 'radio' in block_prefixes -%} 8 | {{- form_widget(form) -}} 9 | {{- form_label(form) -}} 10 | {{- form_errors(form) -}} 11 | {%- else -%} 12 | {{- form_label(form) -}} 13 | {{- form_widget(form) -}} 14 | {{- form_errors(form) -}} 15 | {%- endif -%} 16 |
17 | {%- endblock form_row -%} 18 | 19 | {% block translation_list_widget %} 20 | {% set add_translation_closed = false %} 21 | 22 |
23 | {% if tag is not null %} 24 |

{{ 'tag.translations.edit_translation'|trans }}

25 | 26 |
27 | {% else %} 28 |

{{ 'tag.translations.add_translation'|trans }}

29 | 30 |
31 | {% endif %} 32 | 33 | {%- for child in form %} 34 | {% if tag is not null and child.vars.value not in tag.languageCodes and add_translation_closed == false %} 35 |
36 | 37 |

{{ 'tag.translations.add_translation'|trans }}

38 | 39 |
40 | 41 | {% set add_translation_closed = true %} 42 | {% endif %} 43 | 44 | {{- form_row(child) -}} 45 | {% endfor -%} 46 |
47 |
48 | {% endblock %} 49 | -------------------------------------------------------------------------------- /bundle/Resources/public/admin/css/field/jqmodal.css: -------------------------------------------------------------------------------- 1 | div.whiteOverlay {background:white;} 2 | 3 | .ng-tags-input-ui div.jqmDialog {display:none; position:fixed; top:17%; left:50%; width:400px;} 4 | .ng-tags-input-ui div.jqmdIn {position:relative; left:-200px; padding:10px;} 5 | 6 | .ng-tags-input-ui div.jqmdTC {height:23px; font-size:11px; color:white; font-weight:bold; margin:0 6px; position:relative; 7 | background:url(../../images/jqmodal/horizontal.gif) repeat-x left -23px;} 8 | .ng-tags-input-ui span.jqmdTCLeft {display:block; height:23px; width:6px; position:absolute; top:0; left:-6px; background:url(../../images/jqmodal/corners.gif) no-repeat -6px top;} 9 | .ng-tags-input-ui span.jqmdTCRight {display:block; height:23px; width:6px; position:absolute; top:0; right:-6px; background:url(../../images/jqmodal/corners.gif) no-repeat -18px top;} 10 | .ng-tags-input-ui span.jqDrag {cursor:move; display:block; height:23px; line-height:23px; margin:0 35px 0 0; padding:0 0 0 35px; text-align:center;} 11 | 12 | .ng-tags-input-ui div.jqmdBL {border:1px solid black; border-top:none; padding:0 1px 1px 1px; background:white;} 13 | .ng-tags-input-ui div.jqmdBR {border:2px solid #efebe7; border-top:none;} 14 | .ng-tags-input-ui div.jqmdBC {border:1px solid #b5a69c; border-top:none; background:#f0f0ee; padding:8px; overflow:auto; max-height:350px;} 15 | .ng-tags-input-ui div.jqmdBCIn {border:1px solid #919b9c; padding:13px; background:#e6e6e6;} 16 | 17 | .ng-tags-input-ui a.jqmdX {display:block; height:16px; width:29px; position: absolute; right:16px; top:14px; cursor:pointer; outline:none; 18 | background:url(../../images/jqmodal/buttons.gif) no-repeat -87px -16px;} 19 | .ng-tags-input-ui a.jqmdX:hover {background-position:-87px -32px;} 20 | -------------------------------------------------------------------------------- /bundle/Resources/views/tag/view.html.twig: -------------------------------------------------------------------------------- 1 | {% extends ibexa.configResolver.parameter('page_layout') %} 2 | 3 | {% block page_head %} 4 | {% set title = tag.keyword %} 5 | {{ parent() }} 6 | {% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 |
12 |
13 |

{{ tag.keyword }}

14 |
15 | 16 | {% if related_content|length > 0 %} 17 |
18 | {% for related_content_item in related_content %} 19 | {{ render( 20 | controller( 21 | "ibexa_content::viewAction", { 22 | "contentId": related_content_item.id, 23 | "locationId": related_content_item.mainLocationId, 24 | "viewType": "line", 25 | "layout": false 26 | } 27 | ) 28 | ) }} 29 | {% endfor %} 30 |
31 | 32 | {% if related_content.haveToPaginate() %} 33 |
34 | {{ pagerfanta(related_content, 'twitter_bootstrap_translated') }} 35 |
36 | {% endif %} 37 | {% endif %} 38 |
39 |
40 |
41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /bundle/Validator/Structs/CreateStructValidator.php: -------------------------------------------------------------------------------- 1 | context->getValidator()->inContext($this->context); 18 | 19 | $validator->atPath('alwaysAvailable')->validate( 20 | $value->alwaysAvailable, 21 | [ 22 | new Constraints\Type(type: 'bool'), 23 | new Constraints\NotNull(), 24 | ], 25 | ); 26 | 27 | $validator->atPath('keyword')->validate( 28 | $value->getKeyword(), 29 | [ 30 | new Constraints\Type(type: 'string'), 31 | new Constraints\NotBlank(), 32 | ], 33 | ); 34 | 35 | $validator->atPath('remoteId')->validate( 36 | $value->remoteId, 37 | [ 38 | new Constraints\Type(type: 'string'), 39 | new RemoteId(), 40 | ], 41 | ); 42 | 43 | $validator->atPath('mainLanguageCode')->validate( 44 | $value->mainLanguageCode, 45 | [ 46 | new Constraints\Type(type: 'string'), 47 | new Constraints\NotBlank(), 48 | new Language(), 49 | ], 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /bundle/SPI/Persistence/Tags/Tag.php: -------------------------------------------------------------------------------- 1 | "Hrvatska", "eng-GB" => "Croatia" ). 34 | * 35 | * @var string[] 36 | */ 37 | public array $keywords = []; 38 | 39 | /** 40 | * The depth tag has in tag tree. 41 | */ 42 | public int $depth; 43 | 44 | /** 45 | * The path to this tag e.g. /1/6/21/42 where 42 is the current ID. 46 | */ 47 | public string $pathString; 48 | 49 | /** 50 | * Tag modification date as a UNIX timestamp. 51 | */ 52 | public int $modificationDate; 53 | 54 | /** 55 | * A global unique ID of the tag. 56 | */ 57 | public string $remoteId; 58 | 59 | /** 60 | * Indicates if the tag is shown in the main language if its not present in an other requested language. 61 | */ 62 | public bool $alwaysAvailable; 63 | 64 | /** 65 | * The main language code of the tag. 66 | */ 67 | public string $mainLanguageCode; 68 | 69 | /** 70 | * List of languages in this tag. 71 | * 72 | * @var int[] 73 | */ 74 | public array $languageIds = []; 75 | } 76 | -------------------------------------------------------------------------------- /bundle/AdminUI/EventListener/SetPageLayoutListener.php: -------------------------------------------------------------------------------- 1 | ['onKernelRequest', 10]]; 26 | } 27 | 28 | /** 29 | * Sets the pagelayout template for admin interface. 30 | */ 31 | public function onKernelRequest(RequestEvent $event): void 32 | { 33 | if (!$event->isMainRequest()) { 34 | return; 35 | } 36 | 37 | $request = $event->getRequest(); 38 | if (!$request->attributes->has('siteaccess')) { 39 | return; 40 | } 41 | 42 | $siteAccess = $request->attributes->get('siteaccess')->name; 43 | if (!isset($this->groupsBySiteAccess[$siteAccess])) { 44 | return; 45 | } 46 | 47 | if (!in_array(IbexaAdminUiBundle::ADMIN_GROUP_NAME, $this->groupsBySiteAccess[$siteAccess], true)) { 48 | return; 49 | } 50 | 51 | $this->globalVariable->setPageLayoutTemplate($this->pageLayoutTemplate); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /bundle/View/Provider/TagViewProvider.php: -------------------------------------------------------------------------------- 1 | matcherFactory->match($view)) === null) { 24 | return null; 25 | } 26 | 27 | return $this->buildTagView($configHash); 28 | } 29 | 30 | public function setSiteAccess(?SiteAccess $siteAccess = null): void 31 | { 32 | if ($this->matcherFactory instanceof SiteAccessAware) { 33 | $this->matcherFactory->setSiteAccess($siteAccess); 34 | } 35 | } 36 | 37 | /** 38 | * Builds a TagView object from $viewConfig. 39 | */ 40 | private function buildTagView(array $viewConfig): TagView 41 | { 42 | $view = new TagView(); 43 | $view->setConfigHash($viewConfig); 44 | 45 | if (isset($viewConfig['template'])) { 46 | $view->setTemplateIdentifier($viewConfig['template']); 47 | } 48 | 49 | if (isset($viewConfig['controller'])) { 50 | $view->setControllerReference(new ControllerReference($viewConfig['controller'])); 51 | } 52 | 53 | return $view; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /bundle/Resources/sass/admin/_base.scss: -------------------------------------------------------------------------------- 1 | body.tags-resizing { 2 | cursor:ew-resize; 3 | } 4 | 5 | #{$namespace} { 6 | min-height:100%; 7 | font-family:$baseFont; 8 | font-size:$baseFontSize; 9 | line-height:1.6; 10 | color:$textColor; 11 | background:$contentBg; 12 | -webkit-font-smoothing:antialiased; 13 | -moz-osx-font-smoothing:grayscale; 14 | display:flex; 15 | flex-direction:column; 16 | * { 17 | box-sizing:border-box; 18 | } 19 | .ng-tags-container { 20 | height:100%; 21 | width:100%; 22 | max-width:1600px; 23 | margin:0 auto; 24 | display:flex; 25 | flex:1; 26 | align-items:stretch; 27 | } 28 | .tags-sidebar { 29 | width:$sidebarWidth; 30 | background:$sidebarBg; 31 | .jstree-ng-tags { 32 | padding:$gutter; 33 | overflow-x:hidden; 34 | } 35 | } 36 | .tags-content { 37 | flex:1; 38 | padding:$gutter; 39 | overflow-x:auto; 40 | } 41 | 42 | /* messages */ 43 | .tags-message { 44 | margin:2em 0 3em; 45 | padding:$gutter; 46 | background:hsl(205, 30, 92); 47 | &.tags-message-success { 48 | background:hsl(160, 80, 80); 49 | } 50 | &.tags-message-error { 51 | background:hsl(0, 100, 90); 52 | } 53 | } 54 | 55 | /* header */ 56 | .ng-tags-header { 57 | background:hsl(0, 0, 30); 58 | .ng-tags-logo { 59 | height:50px; 60 | width:$sidebarWidth; 61 | background:hsl(0, 0, 20) url(../images/tags-logo-full.svg) no-repeat center center; 62 | background-size:auto 60%; 63 | } 64 | } 65 | } 66 | .layout-column.main-column { 67 | background:#f5f5f5; 68 | } 69 | -------------------------------------------------------------------------------- /bundle/Form/Type/TagUpdateType.php: -------------------------------------------------------------------------------- 1 | setRequired(['tag', 'languageCode']) 23 | ->setAllowedTypes('tag', Tag::class) 24 | ->setAllowedTypes('languageCode', 'string') 25 | ->setDefaults( 26 | [ 27 | 'data_class' => TagUpdateStruct::class, 28 | 'constraints' => static fn (Options $options): array => [ 29 | new TagUpdateStructConstraint( 30 | [ 31 | 'payload' => $options['tag'], 32 | 'languageCode' => $options['languageCode'], 33 | ], 34 | ), 35 | ], 36 | ], 37 | ); 38 | } 39 | 40 | public function buildForm(FormBuilderInterface $builder, array $options): void 41 | { 42 | $builder->setDataMapper(new TagUpdateStructDataMapper($options['languageCode'])); 43 | } 44 | 45 | public function getParent(): string 46 | { 47 | return TagType::class; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /bundle/Resources/sql/mysql/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `eztags` ( 2 | `id` int(11) NOT NULL auto_increment, 3 | `parent_id` int(11) NOT NULL default '0', 4 | `main_tag_id` int(11) NOT NULL default '0', 5 | `keyword` varchar(255) NOT NULL default '', 6 | `depth` int(11) NOT NULL default '1', 7 | `path_string` varchar(255) NOT NULL default '', 8 | `modified` int(11) NOT NULL default '0', 9 | `remote_id` varchar(100) NOT NULL default '', 10 | `main_language_id` int(11) NOT NULL default '0', 11 | `language_mask` int(11) NOT NULL default '0', 12 | PRIMARY KEY ( `id` ), 13 | KEY `idx_eztags_keyword` ( `keyword`(191) ), 14 | KEY `idx_eztags_keyword_id` ( `keyword`(191), `id` ), 15 | UNIQUE KEY `idx_eztags_remote_id` ( `remote_id` ) 16 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 17 | 18 | CREATE TABLE `eztags_attribute_link` ( 19 | `id` int(11) NOT NULL auto_increment, 20 | `keyword_id` int(11) NOT NULL default '0', 21 | `objectattribute_id` int(11) NOT NULL default '0', 22 | `objectattribute_version` int(11) NOT NULL default '0', 23 | `object_id` int(11) NOT NULL default '0', 24 | `priority` int(11) NOT NULL default '0', 25 | PRIMARY KEY ( `id` ), 26 | KEY `idx_eztags_attr_link_keyword_id` ( `keyword_id` ), 27 | KEY `idx_eztags_attr_link_kid_oaid_oav` ( `keyword_id`, `objectattribute_id`, `objectattribute_version` ), 28 | KEY `idx_eztags_attr_link_kid_oid` ( `keyword_id`, `object_id` ), 29 | KEY `idx_eztags_attr_link_oaid_oav` ( `objectattribute_id`, `objectattribute_version` ) 30 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 31 | 32 | CREATE TABLE `eztags_keyword` ( 33 | `keyword_id` int(11) NOT NULL default '0', 34 | `language_id` int(11) NOT NULL default '0', 35 | `keyword` varchar(255) NOT NULL default '', 36 | `locale` varchar(20) NOT NULL default '', 37 | `status` int(11) NOT NULL default '0', 38 | PRIMARY KEY ( `keyword_id`, `locale` ) 39 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 40 | -------------------------------------------------------------------------------- /bundle/Templating/Twig/Extension/NetgenTagsRuntime.php: -------------------------------------------------------------------------------- 1 | tagsService->loadTag((int) $tagId); 28 | } catch (NotFoundException) { 29 | return ''; 30 | } 31 | 32 | return $tag->getKeyword() ?? ''; 33 | } 34 | 35 | /** 36 | * Returns the language name for specified language code. 37 | */ 38 | public function getLanguageName(string $languageCode): string 39 | { 40 | return $this->languageService->loadLanguage($languageCode)->name; 41 | } 42 | 43 | /** 44 | * Returns content type name for provided content type ID or content type object. 45 | */ 46 | public function getContentTypeName(ContentType|int $contentType): string 47 | { 48 | if (!$contentType instanceof ContentType) { 49 | try { 50 | $contentType = $this->contentTypeService->loadContentType($contentType); 51 | } catch (NotFoundException) { 52 | return ''; 53 | } 54 | } 55 | 56 | return $contentType->getName() ?? ''; 57 | } 58 | } 59 | --------------------------------------------------------------------------------