├── Command ├── ExportCommand.php └── ImportCommand.php ├── Controller ├── ContentController.php ├── Crud │ ├── ContentController.php │ ├── PageController.php │ └── ScopeController.php ├── PageController.php └── ToolsController.php ├── DependencyInjection ├── Compiler │ ├── ElementPass.php │ └── FormThemePass.php ├── Configuration.php └── SherlockodeAdvancedContentExtension.php ├── Doctrine ├── MigrationHelper.php ├── MigrationHelperAwareInterface.php └── MigrationHelperInterface.php ├── Element ├── AbstractElement.php └── ElementInterface.php ├── Event ├── AcbFilePostValidate.php └── AcbFilePreSubmitEvent.php ├── EventListener ├── AcbFileListener.php ├── ContentListener.php ├── PageListener.php └── VersionListener.php ├── Exception └── InvalidElementException.php ├── Export ├── ContentExport.php ├── ElementExport.php ├── PageExport.php └── ScopeExport.php ├── FieldType ├── AbstractFieldType.php ├── AbstractInputType.php ├── Content.php ├── FieldTypeInterface.php ├── File.php ├── Iframe.php ├── Image.php ├── ImageCarousel.php ├── Link.php ├── RelativeLink.php ├── Separator.php ├── Text.php ├── TextArea.php ├── Title.php ├── Video.php └── Wysiwyg.php ├── Form ├── DataTransformer │ └── StringToEntity.php └── Type │ ├── AcbContentType.php │ ├── AcbFileType.php │ ├── ColumnType.php │ ├── ContentDataType.php │ ├── ContentType.php │ ├── ElementAdvancedType.php │ ├── ElementDesignType.php │ ├── ElementHideOnType.php │ ├── ElementType.php │ ├── ElementsType.php │ ├── EntityType.php │ ├── ExportType.php │ ├── ImageCarouselType.php │ ├── ImageType.php │ ├── ImportType.php │ ├── LinkType.php │ ├── PageMetaType.php │ ├── PageType.php │ ├── PageTypeType.php │ ├── PictureEntryType.php │ ├── PictureType.php │ ├── RepeatedChildWrappedType.php │ ├── RepeaterType.php │ ├── RowType.php │ ├── ScopeChoiceType.php │ ├── ScopeType.php │ ├── TitleType.php │ ├── VideoType.php │ └── WysiwygType.php ├── Import ├── AbstractImport.php ├── ContentImport.php ├── ElementImport.php ├── ImportResult.php └── PageImport.php ├── LICENSE ├── LayoutType ├── AbstractLayoutType.php ├── Column.php ├── LayoutTypeInterface.php └── Row.php ├── Manager ├── ConfigurationManager.php ├── ContentManager.php ├── ElementManager.php ├── ExportManager.php ├── ImportManager.php ├── MimeTypeManager.php ├── PageManager.php ├── UploadManager.php ├── UrlBuilderManager.php └── VersionManager.php ├── Model ├── Content.php ├── ContentInterface.php ├── ContentVersion.php ├── ContentVersionInterface.php ├── LocaleScope.php ├── LocaleScopeInterface.php ├── Page.php ├── PageInterface.php ├── PageMeta.php ├── PageMetaInterface.php ├── PageMetaVersion.php ├── PageMetaVersionInterface.php ├── PageType.php ├── PageTypeInterface.php ├── PageVersion.php ├── PageVersionInterface.php ├── ScopableInterface.php ├── Scope.php ├── ScopeInterface.php ├── Version.php └── VersionInterface.php ├── Naming ├── ClientOriginalNamer.php ├── NamerInterface.php └── UniqueNamer.php ├── README.md ├── Resources ├── config │ ├── controllers.xml │ ├── doctrine-mapping │ │ ├── Content.orm.xml │ │ ├── ContentVersion.orm.xml │ │ ├── Page.orm.xml │ │ ├── PageMetaVersion.orm.xml │ │ ├── PageType.orm.xml │ │ └── PageVersion.orm.xml │ ├── field_types.xml │ ├── form.xml │ ├── import_export.xml │ ├── layout_types.xml │ ├── listeners.xml │ ├── mime_type.xml │ ├── routing │ │ ├── all.xml │ │ ├── base.xml │ │ ├── content.xml │ │ ├── content_crud.xml │ │ ├── page.xml │ │ ├── page_crud.xml │ │ ├── scope_crud.xml │ │ └── tools.xml │ └── services.xml ├── doc │ ├── custom_field_type.md │ ├── data_migration.md │ ├── elements.md │ ├── entities.md │ ├── export.md │ ├── field_types.md │ ├── import.md │ ├── import │ │ ├── Content │ │ │ └── standalone_content.yaml │ │ ├── Page │ │ │ └── custom_page.yaml │ │ └── files │ │ │ └── background.png │ ├── tools.md │ └── usage.md ├── js │ ├── acb-collection.js │ ├── acb-notification.js │ ├── acb-slide-collection.js │ ├── content.js │ ├── export.js │ ├── form.js │ ├── history.js │ ├── index.js │ ├── layout.js │ ├── slide.js │ ├── slug.js │ └── utils.js ├── public │ ├── css │ │ ├── acb.scss │ │ ├── advanced-content.css │ │ ├── field-list.scss │ │ ├── field.scss │ │ ├── history.scss │ │ ├── index.scss │ │ ├── page.scss │ │ ├── slide.scss │ │ └── tools.scss │ └── js │ │ └── speakingurl.min.js ├── translations │ ├── AdvancedContentBundle.en.yml │ └── AdvancedContentBundle.fr.yml └── views │ ├── Common │ └── Macros │ │ ├── classes.html.twig │ │ ├── collapse.html.twig │ │ ├── date.html.twig │ │ └── tabs.html.twig │ ├── Content │ ├── _button_submit_slide.html.twig │ ├── _edit_element.html.twig │ ├── _field_form.html.twig │ ├── _field_preview.html.twig │ ├── _form.html.twig │ ├── _select_new_field.html.twig │ ├── create_content.html.twig │ ├── edit_content.html.twig │ ├── list.html.twig │ └── show.html.twig │ ├── Field │ ├── front │ │ ├── content.html.twig │ │ ├── iframe.html.twig │ │ ├── image.html.twig │ │ ├── image_carousel.html.twig │ │ ├── link.html.twig │ │ ├── separator.html.twig │ │ ├── text.html.twig │ │ ├── textarea.html.twig │ │ ├── title.html.twig │ │ ├── video.html.twig │ │ └── wysiwyg.html.twig │ └── preview │ │ ├── content.html.twig │ │ ├── iframe.html.twig │ │ ├── image.html.twig │ │ ├── image_carousel.html.twig │ │ ├── link.html.twig │ │ ├── no_preview.html.twig │ │ ├── separator.html.twig │ │ ├── text.html.twig │ │ ├── textarea.html.twig │ │ ├── title.html.twig │ │ ├── video.html.twig │ │ └── wysiwyg.html.twig │ ├── Form │ ├── base_theme.html.twig │ ├── common.html.twig │ ├── content.html.twig │ └── theme.html.twig │ ├── Layout │ ├── front │ │ ├── column.html.twig │ │ └── row.html.twig │ └── preview │ │ ├── column.html.twig │ │ └── row.html.twig │ ├── Page │ ├── _form.html.twig │ ├── create.html.twig │ ├── edit.html.twig │ └── list.html.twig │ ├── Tools │ ├── _pageTypes.html.twig │ ├── _scopes.html.twig │ └── index.html.twig │ ├── Version │ ├── _line.html.twig │ ├── _list.html.twig │ ├── content_list.html.twig │ └── page_list.html.twig │ └── layout.html.twig ├── Scope ├── LocaleScopeHandler.php ├── ScopeHandler.php └── ScopeHandlerInterface.php ├── SherlockodeAdvancedContentBundle.php ├── Slug ├── SlugProvider.php └── SlugProviderInterface.php ├── Twig └── Extension │ ├── ContentExtension.php │ └── ScopeExtension.php ├── User ├── AnonymousUserProvider.php └── UserProviderInterface.php └── composer.json /Controller/Crud/ScopeController.php: -------------------------------------------------------------------------------- 1 | em = $em; 34 | $this->configurationManager = $configurationManager; 35 | } 36 | 37 | /** 38 | * @param int $id 39 | * 40 | * @return Response 41 | */ 42 | public function deleteAction($id) 43 | { 44 | $scope = $this->em->getRepository($this->configurationManager->getEntityClass('scope'))->find($id); 45 | 46 | if (!$scope instanceof ScopeInterface) { 47 | throw $this->createNotFoundException( 48 | sprintf('Entity %s with ID %s not found', $this->configurationManager->getEntityClass('scope'), $id) 49 | ); 50 | } 51 | 52 | $this->em->remove($scope); 53 | $this->em->flush(); 54 | 55 | return $this->redirectToRoute('sherlockode_acb_tools_index'); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/ElementPass.php: -------------------------------------------------------------------------------- 1 | has('sherlockode_advanced_content.element_manager')) { 14 | return; 15 | } 16 | $definition = $container->findDefinition('sherlockode_advanced_content.element_manager'); 17 | $taggedServices = $container->findTaggedServiceIds('sherlockode_advanced_content.fieldtype'); 18 | foreach ($taggedServices as $id => $tags) { 19 | $definition->addMethodCall('addFieldType', [new Reference($id)]); 20 | } 21 | 22 | $taggedServices = $container->findTaggedServiceIds('sherlockode_advanced_content.layouttype'); 23 | foreach ($taggedServices as $id => $tags) { 24 | $definition->addMethodCall('addLayoutType', [new Reference($id)]); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/FormThemePass.php: -------------------------------------------------------------------------------- 1 | has('twig')) { 14 | return; 15 | } 16 | 17 | $theme = 'bootstrap_5_layout.html.twig'; 18 | if (Kernel::VERSION_ID < 50300) { 19 | $theme = 'bootstrap_4_layout.html.twig'; 20 | } 21 | $container->getDefinition('sherlockode_advanced_content.content_extension')->setArgument('$baseFormTheme', $theme); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Doctrine/MigrationHelperAwareInterface.php: -------------------------------------------------------------------------------- 1 | getDefaultIconClass(); 13 | } 14 | 15 | /** 16 | * @return string 17 | */ 18 | protected function getDefaultIconClass() 19 | { 20 | return 'fa-solid fa-gear'; 21 | } 22 | 23 | /** 24 | * Add element's field(s) to content form 25 | * 26 | * @param FormBuilderInterface $builder 27 | * 28 | * @return void 29 | */ 30 | public function buildContentElement(FormBuilderInterface $builder) 31 | { 32 | $builder->add('elementType', HiddenType::class); 33 | $builder->add('position', HiddenType::class); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Element/ElementInterface.php: -------------------------------------------------------------------------------- 1 | uploadedFile = $uploadedFile; 29 | $this->fileName = $fileName; 30 | } 31 | 32 | /** 33 | * @return UploadedFile 34 | */ 35 | public function getUploadedFile(): UploadedFile 36 | { 37 | return $this->uploadedFile; 38 | } 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function getFileName(): string 44 | { 45 | return $this->fileName; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /EventListener/AcbFileListener.php: -------------------------------------------------------------------------------- 1 | uploadManager = $uploadManager; 31 | } 32 | 33 | /** 34 | * @return string[] 35 | */ 36 | public static function getSubscribedEvents(): array 37 | { 38 | return [ 39 | AcbFilePreSubmitEvent::NAME => 'onPreSubmit', 40 | AcbFilePostValidate::NAME => 'onPostValidate', 41 | ]; 42 | } 43 | 44 | /** 45 | * @param AcbFilePreSubmitEvent $event 46 | * 47 | * @return void 48 | */ 49 | public function onPreSubmit(AcbFilePreSubmitEvent $event): void 50 | { 51 | $this->files = array_merge($this->files, [['file' => $event->getUploadedFile(), 'fileName' => $event->getFileName()]]); 52 | 53 | } 54 | 55 | /** 56 | * @return void 57 | */ 58 | public function onPostValidate(): void 59 | { 60 | foreach ($this->files as $file) { 61 | if (null !== $file['file'] ?? null && null !== $file['fileName'] ?? null) { 62 | $this->uploadManager->upload($file['file'], $file['fileName']); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /EventListener/ContentListener.php: -------------------------------------------------------------------------------- 1 | configurationManager = $configurationManager; 30 | $this->versionManager = $versionManager; 31 | } 32 | 33 | /** 34 | * @param LifecycleEventArgs $args 35 | */ 36 | public function postLoad(LifecycleEventArgs $args) 37 | { 38 | $entity = $args->getEntity(); 39 | 40 | if (!$entity instanceof ContentInterface) { 41 | return; 42 | } 43 | if ($entity->getPage() !== null) { 44 | return; 45 | } 46 | 47 | $entity->setData($this->versionManager->getContentData($entity), false); 48 | } 49 | 50 | /** 51 | * @param OnFlushEventArgs $args 52 | */ 53 | public function onFlush(OnFlushEventArgs $args) 54 | { 55 | $em = $args->getEntityManager(); 56 | $uow = $em->getUnitOfWork(); 57 | 58 | $entities = [ 59 | ...$uow->getScheduledEntityInsertions(), 60 | ...$uow->getScheduledEntityUpdates() 61 | ]; 62 | 63 | $contentVersionClassMetadata = $em->getClassMetadata($this->configurationManager->getEntityClass('content_version')); 64 | $contentClassMetadata = $em->getClassMetadata($this->configurationManager->getEntityClass('content')); 65 | foreach ($entities as $entity) { 66 | if (!$entity instanceof ContentInterface) { 67 | continue; 68 | } 69 | if ($entity->getPage() !== null) { 70 | continue; 71 | } 72 | 73 | $contentVersion = $this->versionManager->getNewContentVersion($entity); 74 | $em->persist($contentVersion); 75 | $uow->computeChangeSet($contentVersionClassMetadata, $contentVersion); 76 | $uow->recomputeSingleEntityChangeSet($contentClassMetadata, $entity); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /EventListener/VersionListener.php: -------------------------------------------------------------------------------- 1 | getEntity(); 19 | 20 | if (!$entity instanceof VersionInterface || !$entity->isAutoSave()) { 21 | return; 22 | } 23 | 24 | if ($entity instanceof ContentVersionInterface && $entity->getContent()->getPage() === null) { 25 | $versions = $entity->getContent()->getVersions(); 26 | } elseif ($entity instanceof PageVersionInterface) { 27 | $versions = $entity->getPage()->getVersions(); 28 | } else { 29 | return; 30 | } 31 | 32 | $count = 0; 33 | foreach ($versions as $version) { 34 | if (!$version->isAutoSave()) { 35 | continue; 36 | } 37 | if ($version->getUserId() !== $entity->getUserId()) { 38 | continue; 39 | } 40 | if ($version->getCreatedAt() < $entity->getCreatedAt()) { 41 | $count++; 42 | } 43 | if ($count >= 10) { 44 | // Keep only the last 10 drafts by same user 45 | $args->getEntityManager()->remove($version); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Exception/InvalidElementException.php: -------------------------------------------------------------------------------- 1 | elementExport = $elementExport; 26 | $this->scopeExport = $scopeExport; 27 | } 28 | 29 | /** 30 | * @param ContentInterface $content 31 | * 32 | * @return array 33 | */ 34 | public function exportData(ContentInterface $content) 35 | { 36 | $data = []; 37 | $data['name'] = $content->getName(); 38 | $data = array_merge($data, $this->scopeExport->getEntityScopes($content)); 39 | 40 | $elements = $content->getData() ?? []; 41 | $data['children'] = $this->exportElements($elements); 42 | 43 | $data = [ 44 | 'contents' => [ 45 | $content->getSlug() => $data, 46 | ], 47 | ]; 48 | 49 | return $data; 50 | } 51 | 52 | /** 53 | * @param array|array[] $elements 54 | * 55 | * @return array 56 | */ 57 | public function exportElements($elements) 58 | { 59 | if (!is_array($elements)) { 60 | return []; 61 | } 62 | if (count($elements) === 0) { 63 | return []; 64 | } 65 | 66 | $data = []; 67 | foreach ($elements as $element) { 68 | $data[] = $this->elementExport->getElementExportData($element); 69 | } 70 | 71 | return $data; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Export/PageExport.php: -------------------------------------------------------------------------------- 1 | scopeExport = $scopeExport; 26 | } 27 | 28 | /** 29 | * @param PageInterface $page 30 | * 31 | * @return array 32 | */ 33 | public function exportData(PageInterface $page) 34 | { 35 | $data = []; 36 | $data['status'] = $page->getStatus(); 37 | if ($page->getPageType() instanceof PageTypeInterface) { 38 | $data['pageType'] = $page->getPageType()->getName(); 39 | } 40 | $data = array_merge($data, $this->scopeExport->getEntityScopes($page)); 41 | if ($page->getContent() !== null) { 42 | $data['content'] = $this->contentExport->exportElements($page->getContent()->getData()); 43 | } 44 | 45 | $pageMeta = $page->getPageMeta(); 46 | if ($pageMeta !== null) { 47 | $data['meta'] = [ 48 | 'title' => $pageMeta->getTitle(), 49 | 'slug' => $pageMeta->getSlug(), 50 | 'meta_title' => $pageMeta->getMetaTitle(), 51 | 'meta_description' => $pageMeta->getMetaDescription(), 52 | ]; 53 | } 54 | 55 | $data = [ 56 | 'pages' => [ 57 | $page->getPageIdentifier() => $data, 58 | ], 59 | ]; 60 | 61 | return $data; 62 | } 63 | 64 | /** 65 | * @param ContentExport $contentExport 66 | */ 67 | public function setContentExport(ContentExport $contentExport) 68 | { 69 | $this->contentExport = $contentExport; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Export/ScopeExport.php: -------------------------------------------------------------------------------- 1 | configurationManager = $configurationManager; 30 | $this->scopeHandler = $scopeHandler; 31 | } 32 | 33 | /** 34 | * @param ScopableInterface $entity 35 | * 36 | * @return array 37 | */ 38 | public function getEntityScopes(ScopableInterface $entity): array 39 | { 40 | if (!$this->configurationManager->isScopesEnabled()) { 41 | return []; 42 | } 43 | 44 | $scopes = []; 45 | foreach ($entity->getScopes() as $scope) { 46 | $scopes[] = $this->scopeHandler->getDataFromScope($scope); 47 | } 48 | 49 | return [ 50 | 'scopes' => $scopes, 51 | ]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /FieldType/AbstractInputType.php: -------------------------------------------------------------------------------- 1 | $fieldOptions['minLength']]); 26 | } 27 | if (isset($fieldOptions['maxLength'])) { 28 | $formFieldOptions['constraints'][] = new Length(['max' => $fieldOptions['maxLength']]); 29 | } 30 | 31 | return $formFieldOptions; 32 | } 33 | 34 | /** 35 | * @return string 36 | */ 37 | public function getFieldGroup() 38 | { 39 | return 'simple'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /FieldType/Content.php: -------------------------------------------------------------------------------- 1 | scopeHandler = $scopeHandler; 22 | } 23 | 24 | /** 25 | * @return string 26 | */ 27 | public function getFormFieldType() 28 | { 29 | return AcbContentType::class; 30 | } 31 | 32 | /** 33 | * Get field's code 34 | * 35 | * @return string 36 | */ 37 | public function getCode() 38 | { 39 | return 'content'; 40 | } 41 | 42 | /** 43 | * @param mixed $element 44 | * 45 | * @return array 46 | */ 47 | public function getRawValue($element) 48 | { 49 | $element['entity'] = null; 50 | 51 | $contentSlug = $element['content'] ?? null; 52 | if ($contentSlug === null) { 53 | return $element; 54 | } 55 | 56 | $element['entity'] = $this->scopeHandler->getEntityForCurrentScope('content', ['slug' => $contentSlug]); 57 | 58 | return $element; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /FieldType/FieldTypeInterface.php: -------------------------------------------------------------------------------- 1 | urlBuilderManager = $urlBuilderManager; 21 | } 22 | 23 | /** 24 | * @return string 25 | */ 26 | public function getFormFieldType() 27 | { 28 | return AcbFileType::class; 29 | } 30 | 31 | protected function getDefaultIconClass() 32 | { 33 | return 'fa-solid fa-paperclip'; 34 | } 35 | 36 | /** 37 | * Get field's code 38 | * 39 | * @return string 40 | */ 41 | public function getCode() 42 | { 43 | return 'file'; 44 | } 45 | 46 | /** 47 | * @param array $value 48 | * 49 | * @return string 50 | */ 51 | protected function getFilename($value) 52 | { 53 | return $this->urlBuilderManager->getFileUrl($value['src'] ?? ''); 54 | } 55 | 56 | /** 57 | * @param mixed $element 58 | * 59 | * @return mixed 60 | */ 61 | public function getRawValue($element) 62 | { 63 | $element['url'] = $this->getFilename($element); 64 | 65 | if (isset($element['delete'])) { 66 | unset($element['delete']); 67 | } 68 | 69 | return $element; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /FieldType/Iframe.php: -------------------------------------------------------------------------------- 1 | get('value') 32 | ->add('src', UrlType::class, ['label' => 'field_type.iframe.src']) 33 | ->add('width', IntegerType::class, ['required' => false, 'label' => 'field_type.iframe.width']) 34 | ->add('height', IntegerType::class, ['required' => false, 'label' => 'field_type.iframe.height']) 35 | ; 36 | } 37 | 38 | /** 39 | * Get field's code 40 | * 41 | * @return string 42 | */ 43 | public function getCode() 44 | { 45 | return 'iframe'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /FieldType/Image.php: -------------------------------------------------------------------------------- 1 | $source) { 38 | $element['sources'][$key] = parent::getRawValue($source); 39 | } 40 | } 41 | 42 | return $element; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /FieldType/ImageCarousel.php: -------------------------------------------------------------------------------- 1 | $imageData) { 41 | $url = $this->getFilename($imageData); 42 | if (empty($url)) { 43 | unset($element['images'][$key]); 44 | continue; 45 | } 46 | 47 | $imageData['url'] = $url; 48 | if (isset($imageData['delete'])) { 49 | unset($imageData['delete']); 50 | } 51 | $element['images'][$key] = $imageData; 52 | } 53 | 54 | return $element; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /FieldType/Link.php: -------------------------------------------------------------------------------- 1 | $this->getUrlFormType(), 32 | ]; 33 | } 34 | 35 | /** 36 | * @return string 37 | */ 38 | protected function getUrlFormType() 39 | { 40 | return UrlType::class; 41 | } 42 | 43 | /** 44 | * Get field's code 45 | * 46 | * @return string 47 | */ 48 | public function getCode() 49 | { 50 | return 'link'; 51 | } 52 | 53 | /** 54 | * @param mixed $element 55 | * 56 | * @return mixed 57 | */ 58 | public function getRawValue($element) 59 | { 60 | $url = $this->getUrlValue($element); 61 | 62 | if (!$url) { 63 | return null; 64 | } 65 | 66 | $element['url'] = $url; 67 | 68 | return $element; 69 | } 70 | 71 | /** 72 | * @param array $value 73 | * 74 | * @return string 75 | */ 76 | protected function getUrlValue($value) 77 | { 78 | return $value['url'] ?? ''; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /FieldType/RelativeLink.php: -------------------------------------------------------------------------------- 1 | urlBuilderManager = $urlBuilderManager; 21 | } 22 | 23 | public function getPreviewTemplate() 24 | { 25 | return '@SherlockodeAdvancedContent/Field/preview/link.html.twig'; 26 | } 27 | 28 | /** 29 | * Get field's code 30 | * 31 | * @return string 32 | */ 33 | public function getCode() 34 | { 35 | return 'relative_link'; 36 | } 37 | 38 | /** 39 | * @return string 40 | */ 41 | protected function getUrlFormType() 42 | { 43 | return TextType::class; 44 | } 45 | 46 | /** 47 | * @param array $value 48 | * 49 | * @return string 50 | */ 51 | protected function getUrlValue($value) 52 | { 53 | return $this->urlBuilderManager->getFullUrl($value['url'] ?? ''); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /FieldType/Separator.php: -------------------------------------------------------------------------------- 1 | $fieldOptions['nbRows']]; 21 | } 22 | 23 | return $formFieldOptions; 24 | } 25 | 26 | /** 27 | * @return string 28 | */ 29 | public function getFormFieldType() 30 | { 31 | return TextareaType::class; 32 | } 33 | 34 | /** 35 | * Get field's code 36 | * 37 | * @return string 38 | */ 39 | public function getCode() 40 | { 41 | return 'textarea'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /FieldType/Title.php: -------------------------------------------------------------------------------- 1 | em = $em; 34 | $this->entityClass = $entityClass; 35 | $this->identifierField = $identifierField; 36 | } 37 | 38 | /** 39 | * Transforms a string into an entity 40 | * 41 | * @param string $valueAsString 42 | * 43 | * @return object|null 44 | */ 45 | public function transform($valueAsString) 46 | { 47 | if (empty($valueAsString)) { 48 | return null; 49 | } 50 | 51 | $entity = $this->em->getRepository($this->entityClass)->findOneBy([ 52 | $this->identifierField => $valueAsString, 53 | ]); 54 | 55 | return $entity; 56 | } 57 | 58 | /** 59 | * Transforms an entity into a string 60 | * 61 | * @param object $entity 62 | * 63 | * @return string 64 | */ 65 | public function reverseTransform($entity) 66 | { 67 | if (empty($entity)) { 68 | return null; 69 | } 70 | 71 | $propertyAccessor = PropertyAccess::createPropertyAccessor(); 72 | 73 | return $propertyAccessor->getValue($entity, $this->identifierField); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Form/Type/AcbContentType.php: -------------------------------------------------------------------------------- 1 | configurationManager = $configurationManager; 30 | $this->em = $em; 31 | } 32 | 33 | /** 34 | * @param FormBuilderInterface $builder 35 | * @param array $options 36 | */ 37 | public function buildForm(FormBuilderInterface $builder, array $options) 38 | { 39 | $contents = $this->em->getRepository($this->configurationManager->getEntityClass('content'))->findBy([ 40 | 'page' => null, 41 | ], ['slug' => 'ASC']); 42 | $slugs = []; 43 | foreach ($contents as $content) { 44 | $slugs[$content->getSlug()] = $content->getSlug(); 45 | } 46 | 47 | $builder 48 | ->add('content', ChoiceType::class, [ 49 | 'label' => false, 50 | 'choices' => $slugs, 51 | ]) 52 | ; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Form/Type/ContentDataType.php: -------------------------------------------------------------------------------- 1 | add('class', TextType::class, [ 15 | 'label' => 'configuration.advanced.class', 16 | 'required' => false, 17 | ]) 18 | ->add('id', TextType::class, [ 19 | 'label' => 'configuration.advanced.id', 20 | 'required' => false, 21 | ]) 22 | ->add('hide_on', ElementHideOnType::class, [ 23 | 'label' => 'configuration.advanced.hide_on', 24 | 'required' => false, 25 | ]) 26 | ; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Form/Type/ElementHideOnType.php: -------------------------------------------------------------------------------- 1 | setDefaults([ 25 | 'choices' => [ 26 | 'responsive.devices.mobile_portrait' => 'xs', 27 | 'responsive.devices.mobile_landscape' => 'sm', 28 | 'responsive.devices.tablet_portrait' => 'md', 29 | 'responsive.devices.tablet_landscape' => 'lg', 30 | 'responsive.devices.desktop' => 'xl', 31 | ], 32 | 'multiple' => true, 33 | 'expanded' => true, 34 | ]); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Form/Type/ElementType.php: -------------------------------------------------------------------------------- 1 | buildContentElement($builder); 23 | $builder->add('extra', FormType::class, [ 24 | 'label' => false, 25 | ]); 26 | $builder->get('extra')->add('advanced', ElementAdvancedType::class, [ 27 | 'label' => false, 28 | ]); 29 | $builder->get('extra')->add('design', ElementDesignType::class, [ 30 | 'label' => false, 31 | ]); 32 | } 33 | 34 | public function buildView(FormView $view, FormInterface $form, array $options) 35 | { 36 | $view->vars['panel_label'] = $options['element_type']->getFormFieldLabel(); 37 | $view->vars['field_icon'] = $options['element_type']->getIconClass(); 38 | } 39 | 40 | /** 41 | * @param OptionsResolver $resolver 42 | */ 43 | public function configureOptions(OptionsResolver $resolver) 44 | { 45 | $resolver->setRequired(['element_type']); 46 | $resolver->setDefaults([ 47 | 'translation_domain' => 'AdvancedContentBundle', 48 | ]); 49 | } 50 | 51 | public function getBlockPrefix() 52 | { 53 | return 'acb_element'; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Form/Type/EntityType.php: -------------------------------------------------------------------------------- 1 | addModelTransformer(new StringToEntity($options['em'], $options['class'], 'id')); 15 | } 16 | 17 | public function getParent() 18 | { 19 | return SymfonyEntityType::class; 20 | } 21 | 22 | public function getBlockPrefix() 23 | { 24 | return 'acb_entity'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Form/Type/ImageCarouselType.php: -------------------------------------------------------------------------------- 1 | add('images', RepeaterType::class, [ 16 | 'entry_type' => ImageType::class, 17 | 'label' => 'field_type.image_carousel.images', 18 | 'constraints' => [ 19 | new Count(null, 1, null, null, null, null, null, null, $options['validation_groups']), 20 | ], 21 | 'error_bubbling' => false, 22 | ]) 23 | ->add('displayArrows', CheckboxType::class, [ 24 | 'label' => 'field_type.image_carousel.display_arrows', 25 | 'required' => false, 26 | ]) 27 | ->add('displayPagination', CheckboxType::class, [ 28 | 'label' => 'field_type.image_carousel.display_pagination', 29 | 'required' => false, 30 | ]) 31 | ; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Form/Type/ImageType.php: -------------------------------------------------------------------------------- 1 | mimeTypeManager = $mimeTypeManager; 24 | } 25 | 26 | public function buildForm(FormBuilderInterface $builder, array $options) 27 | { 28 | $builder->remove('title'); 29 | $builder 30 | ->add('alt', TextType::class, [ 31 | 'label' => 'field_type.image.alt', 32 | 'required' => false, 33 | ]) 34 | ; 35 | } 36 | 37 | /** 38 | * @return string 39 | */ 40 | public function getParent() 41 | { 42 | return AcbFileType::class; 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function getBlockPrefix() 49 | { 50 | return 'acb_image'; 51 | } 52 | 53 | /** 54 | * @param OptionsResolver $resolver 55 | */ 56 | public function configureOptions(OptionsResolver $resolver) 57 | { 58 | $resolver->setDefaults([ 59 | 'translation_domain' => 'AdvancedContentBundle', 60 | 'mime_types' => array_flip(array_map('ucfirst', $this->mimeTypeManager->getImageMimeTypesChoices())), 61 | 'mime_types_constraint' => $this->mimeTypeManager->getMimeTypesByCode(MimeTypeManager::MIME_TYPE_IMAGE), 62 | ]); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Form/Type/ImportType.php: -------------------------------------------------------------------------------- 1 | add('file', FileType::class, ['label' => 'tools.import.file']) 16 | ; 17 | } 18 | 19 | public function configureOptions(OptionsResolver $resolver) 20 | { 21 | $resolver->setDefaults([ 22 | 'translation_domain' => 'AdvancedContentBundle', 23 | ]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Form/Type/LinkType.php: -------------------------------------------------------------------------------- 1 | add('url', $options['url_form_type'], [ 18 | 'label' => 'field_type.link.url', 19 | 'constraints' => [ 20 | new NotBlank(null, null, null, null, $options['validation_groups']), 21 | ], 22 | ]) 23 | ->add('title', TextType::class, [ 24 | 'label' => 'field_type.link.title', 25 | 'constraints' => [ 26 | new NotBlank(null, null, null, null, $options['validation_groups']), 27 | ], 28 | ]) 29 | ; 30 | } 31 | 32 | /** 33 | * @param OptionsResolver $resolver 34 | */ 35 | public function configureOptions(OptionsResolver $resolver) 36 | { 37 | $resolver->setDefaults([ 38 | 'translation_domain' => 'AdvancedContentBundle', 39 | 'url_form_type' => UrlType::class, 40 | ]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Form/Type/PageTypeType.php: -------------------------------------------------------------------------------- 1 | configurationManager = $configurationManager; 25 | } 26 | 27 | public function buildForm(FormBuilderInterface $builder, array $options) 28 | { 29 | $builder 30 | ->add('name', TextType::class, [ 31 | 'label' => 'page_type.form.name', 32 | 'constraints' => [ 33 | new NotBlank(null, null, null, null, $options['validation_groups']), 34 | ], 35 | ]) 36 | ; 37 | } 38 | 39 | public function configureOptions(OptionsResolver $resolver) 40 | { 41 | $resolver->setDefaults([ 42 | 'data_class' => $this->configurationManager->getEntityClass('page_type'), 43 | 'translation_domain' => 'AdvancedContentBundle', 44 | ]); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Form/Type/PictureEntryType.php: -------------------------------------------------------------------------------- 1 | remove('alt'); 17 | $builder->remove('mime_type'); 18 | $builder 19 | ->add('media_query', TextType::class, [ 20 | 'label' => 'field_type.image.media_query', 21 | 'constraints' => [ 22 | new NotBlank(null, null, null, null, $options['validation_groups']), 23 | ], 24 | ]) 25 | ; 26 | 27 | $builder->addEventListener( 28 | FormEvents::PRE_SUBMIT, 29 | function (FormEvent $event) use ($options) { 30 | $data = $event->getData(); 31 | if (isset($data['mime_type'])) { 32 | // Remove mime type data to prevent form extra fields error 33 | unset($data['mime_type']); 34 | } 35 | $event->setData($data); 36 | }, 37 | 1 38 | ); 39 | } 40 | 41 | /** 42 | * @return string 43 | */ 44 | public function getParent() 45 | { 46 | return ImageType::class; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Form/Type/PictureType.php: -------------------------------------------------------------------------------- 1 | add('image', ImageType::class, [ 18 | 'label' => 'field_type.image.picture_main', 19 | 'row_attr' => ['class' => 'picture-main-image'], 20 | ]) 21 | ->add('sources', CollectionType::class, [ 22 | 'label' => 'field_type.image.picture_sources', 23 | 'entry_type' => PictureEntryType::class, 24 | 'allow_add' => true, 25 | 'allow_delete' => true, 26 | 'row_attr' => ['class' => 'picture-sources'], 27 | 'entry_options' => [ 28 | 'attr' => ['class' => 'picture-source'], 29 | 'label' => false, 30 | ], 31 | ]); 32 | 33 | $builder->addEventListener( 34 | FormEvents::PRE_SUBMIT, 35 | function (FormEvent $event) use ($options) { 36 | $data = $event->getData(); 37 | $globalMimeTypes = $data['image']['mime_type'] ?? []; 38 | if (isset($data['sources']) && is_array($data['sources'])) { 39 | foreach ($data['sources'] as $key => $source) { 40 | $data['sources'][$key]['mime_type'] = $globalMimeTypes; 41 | } 42 | } 43 | 44 | $event->setData($data); 45 | } 46 | ); 47 | } 48 | 49 | /** 50 | * @param OptionsResolver $resolver 51 | */ 52 | public function configureOptions(OptionsResolver $resolver) 53 | { 54 | $resolver->setDefaults([ 55 | 'row_attr' => ['class' => 'picture-field'], 56 | ]); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Form/Type/RepeatedChildWrappedType.php: -------------------------------------------------------------------------------- 1 | add('wrapped_child', $options['child_form'], $options['child_options']) 23 | ->add('position', HiddenType::class, ['data' => $options['position']]) 24 | ; 25 | 26 | $dataCallback = function (FormEvent $event) { 27 | $data = $event->getData(); 28 | 29 | $data = ['wrapped_child' => $data]; 30 | 31 | $event->setData($data); 32 | }; 33 | 34 | $builder->addEventListener(FormEvents::PRE_SET_DATA, $dataCallback, -5); 35 | } 36 | 37 | public function configureOptions(OptionsResolver $resolver) 38 | { 39 | $resolver->setDefaults([ 40 | 'position' => 0, 41 | 'child_options'=> [], 42 | ]); 43 | $resolver->setRequired(['child_form']); 44 | $resolver->setNormalizer('child_options', function (Options $options, $value) { 45 | unset($value['property_path']); 46 | 47 | return $value; 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Form/Type/RowType.php: -------------------------------------------------------------------------------- 1 | add('columns_gap', IntegerType::class, [ 17 | 'label' => 'layout_type.row.config.columns_gap', 18 | 'required' => false, 19 | 'attr' => ['placeholder' => 'layout_type.row.config.columns_gap_placeholder'], 20 | ]) 21 | ->add('mobile_reverse_columns', CheckboxType::class, [ 22 | 'label' => 'layout_type.row.config.mobile_reverse_columns', 23 | 'required' => false, 24 | ]) 25 | ->add('justify_content', ChoiceType::class, [ 26 | 'label' => 'layout_type.row.config.justify_content', 27 | 'required' => false, 28 | 'choices' => [ 29 | 'layout_type.row.config.justify_contents.start' => 'start', 30 | 'layout_type.row.config.justify_contents.end' => 'end', 31 | 'layout_type.row.config.justify_contents.center' => 'center', 32 | 'layout_type.row.config.justify_contents.between' => 'between', 33 | 'layout_type.row.config.justify_contents.around' => 'around', 34 | ], 35 | 'placeholder' => false, 36 | ]) 37 | ; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Form/Type/ScopeChoiceType.php: -------------------------------------------------------------------------------- 1 | configurationManager = $configurationManager; 32 | $this->scopeHandler = $scopeHandler; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function configureOptions(OptionsResolver $resolver) 39 | { 40 | $resolver->setDefaults([ 41 | 'class' => $this->configurationManager->getEntityClass('scope'), 42 | 'group_by' => $this->scopeHandler->getScopeGroupBy(), 43 | 'choice_label' => 'optionTitle', 44 | 'multiple' => true, 45 | 'expanded' => false, 46 | 'required' => false, 47 | 'by_reference' => false, 48 | ]); 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function getParent(): ?string 55 | { 56 | return EntityType::class; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Form/Type/ScopeType.php: -------------------------------------------------------------------------------- 1 | configurationManager = $configurationManager; 25 | } 26 | 27 | public function buildForm(FormBuilderInterface $builder, array $options) 28 | { 29 | $builder 30 | ->add('locale', TextType::class, [ 31 | 'label' => false, 32 | 'constraints' => [ 33 | new NotBlank(null, null, null, null, $options['validation_groups']), 34 | ], 35 | ]) 36 | ; 37 | } 38 | 39 | public function configureOptions(OptionsResolver $resolver) 40 | { 41 | $resolver->setDefaults([ 42 | 'data_class' => $this->configurationManager->getEntityClass('scope'), 43 | 'translation_domain' => 'AdvancedContentBundle', 44 | ]); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Form/Type/TitleType.php: -------------------------------------------------------------------------------- 1 | add('text', TextType::class, [ 23 | 'label' => 'field_type.title.text', 24 | 'constraints' => [ 25 | new NotBlank(null, null, null, null, $options['validation_groups']), 26 | ], 27 | ]) 28 | ->add('level', ChoiceType::class, [ 29 | 'choices' => $choices, 30 | 'label' => 'field_type.title.level', 31 | ]) 32 | ; 33 | } 34 | 35 | public function configureOptions(OptionsResolver $resolver) 36 | { 37 | $resolver->setDefaults([ 38 | 'translation_domain' => 'AdvancedContentBundle', 39 | ]); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Form/Type/VideoType.php: -------------------------------------------------------------------------------- 1 | add('url', UrlType::class, [ 18 | 'label' => 'field_type.video.url', 19 | 'constraints' => [ 20 | new NotBlank(null, null, null, null, $options['validation_groups']), 21 | ], 22 | ]) 23 | ->add('muted', CheckboxType::class, [ 24 | 'label' => 'field_type.video.muted', 25 | 'required' => false, 26 | ]) 27 | ->add('autoplay', CheckboxType::class, [ 28 | 'label' => 'field_type.video.autoplay', 29 | 'required' => false, 30 | ]) 31 | ->add('loop', CheckboxType::class, [ 32 | 'label' => 'field_type.video.loop', 33 | 'required' => false, 34 | ]) 35 | ->add('controls', CheckboxType::class, [ 36 | 'label' => 'field_type.video.controls', 37 | 'required' => false, 38 | ]) 39 | ->add('height', NumberType::class, [ 40 | 'label' => 'field_type.video.height', 41 | 'required' => false, 42 | ]) 43 | ->add('width', NumberType::class, [ 44 | 'label' => 'field_type.video.width', 45 | 'required' => false, 46 | ]) 47 | ; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Form/Type/WysiwygType.php: -------------------------------------------------------------------------------- 1 | configurationManager = $configurationManager; 23 | } 24 | 25 | public function configureOptions(OptionsResolver $resolver) 26 | { 27 | $toolbar = $this->configurationManager->getDefaultWysiwygToolbar(); 28 | $resolver->setDefaults([ 29 | 'config' => ['toolbar' => $toolbar], 30 | ]); 31 | } 32 | 33 | public function getParent() 34 | { 35 | return CKEditorType::class; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Import/ImportResult.php: -------------------------------------------------------------------------------- 1 | status = self::UNKNOWN; 24 | } 25 | 26 | /** 27 | * @return int 28 | */ 29 | public function getStatus() 30 | { 31 | return $this->status; 32 | } 33 | 34 | /** 35 | * @return bool 36 | */ 37 | public function isSuccess() 38 | { 39 | return $this->status == self::SUCCESS; 40 | } 41 | 42 | /** 43 | * @return $this 44 | */ 45 | public function success() 46 | { 47 | $this->status = self::SUCCESS; 48 | 49 | return $this; 50 | } 51 | 52 | /** 53 | * @return $this 54 | */ 55 | public function failure() 56 | { 57 | $this->status = self::FAILURE; 58 | 59 | return $this; 60 | } 61 | 62 | /** 63 | * @return array 64 | */ 65 | public function getMessages() 66 | { 67 | return $this->messages; 68 | } 69 | 70 | /** 71 | * @param string $message 72 | * 73 | * @return $this 74 | */ 75 | public function addMessage($message) 76 | { 77 | $this->messages[] = $message; 78 | 79 | return $this; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2023 Sherlockode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is furnished 10 | to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /LayoutType/AbstractLayoutType.php: -------------------------------------------------------------------------------- 1 | getCode() . '.label'; 17 | } 18 | 19 | /** 20 | * @return string 21 | */ 22 | public function getFrontTemplate() 23 | { 24 | return '@SherlockodeAdvancedContent/Layout/front/' . $this->getCode() . '.html.twig'; 25 | } 26 | 27 | /** 28 | * @return string 29 | */ 30 | public function getPreviewTemplate() 31 | { 32 | return '@SherlockodeAdvancedContent/Layout/preview/'. $this->getCode() .'.html.twig'; 33 | } 34 | 35 | /** 36 | * Add element's field(s) to content form 37 | * 38 | * @param FormBuilderInterface $builder 39 | * 40 | * @return void 41 | */ 42 | public function buildContentElement(FormBuilderInterface $builder) 43 | { 44 | parent::buildContentElement($builder); 45 | 46 | $builder->add('elements', ElementsType::class, [ 47 | 'label' => false, 48 | 'row_attr' => [ 49 | 'class' => 'acb-layout-elements-container', 50 | ], 51 | ]); 52 | 53 | $configurationFormType = $this->getConfigurationFormType(); 54 | if ($configurationFormType !== null) { 55 | $builder->add('config', $configurationFormType, [ 56 | 'label' => false, 57 | ]); 58 | } 59 | } 60 | 61 | /** 62 | * @param array $element 63 | * 64 | * @return array 65 | */ 66 | public function getRawData($element) 67 | { 68 | $elements = $element['elements'] ?? []; 69 | uasort($elements, function ($a, $b) { 70 | return ($a['position'] ?? 0) <=> ($b['position'] ?? 0); 71 | }); 72 | 73 | return [ 74 | 'elements' => $elements, 75 | 'config' => $element['config'] ?? [], 76 | 'extra' => $element['extra'] ?? [], 77 | ]; 78 | } 79 | 80 | /** 81 | * Get layout configuration form type 82 | * 83 | * @return string 84 | */ 85 | abstract protected function getConfigurationFormType(): ?string; 86 | } 87 | -------------------------------------------------------------------------------- /LayoutType/Column.php: -------------------------------------------------------------------------------- 1 | config = $config; 20 | } 21 | 22 | /** 23 | * Get entity class configuration for given type 24 | * 25 | * @param string $type 26 | * 27 | * @return string 28 | * 29 | * @throws \RuntimeException 30 | */ 31 | public function getEntityClass($type) 32 | { 33 | if (!isset($this->config['entity_class'][$type])) { 34 | throw new \RuntimeException('You are trying to access a configuration that does not exist.'); 35 | } 36 | 37 | return $this->config['entity_class'][$type]; 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | public function getEntityClasses() 44 | { 45 | return $this->config['entity_class']; 46 | } 47 | 48 | /** 49 | * @return mixed 50 | */ 51 | public function getImageDirectory() 52 | { 53 | return $this->config['upload']['image_directory']; 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function getInitDirectory() 60 | { 61 | return $this->config['init_command']['directory']; 62 | } 63 | 64 | /** 65 | * @return string 66 | */ 67 | public function getInitFilesDirectory() 68 | { 69 | return $this->config['init_command']['files_directory']; 70 | } 71 | 72 | /** 73 | * @return bool 74 | */ 75 | public function initCanUpdate() 76 | { 77 | return $this->config['init_command']['allow_update']; 78 | } 79 | 80 | /** 81 | * @return string 82 | */ 83 | public function getDefaultWysiwygToolbar() 84 | { 85 | return $this->getDefaultOptionValue('wysiwyg_toolbar'); 86 | } 87 | 88 | /** 89 | * @return bool 90 | */ 91 | public function getDefaultDateIncludeTime() 92 | { 93 | return $this->getDefaultOptionValue('date_include_time'); 94 | } 95 | 96 | /** 97 | * @return bool 98 | */ 99 | public function isScopesEnabled() 100 | { 101 | return $this->config['scopes']['enabled']; 102 | } 103 | 104 | /** 105 | * @param string $option 106 | * 107 | * @return mixed 108 | */ 109 | private function getDefaultOptionValue($option) 110 | { 111 | return $this->config['default_options'][$option]; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Manager/ContentManager.php: -------------------------------------------------------------------------------- 1 | configurationManager = $configurationManager; 39 | $this->em = $em; 40 | $this->slugProvider = $slugProvider; 41 | } 42 | 43 | /** 44 | * Get content by its id 45 | * 46 | * @param int $id 47 | * 48 | * @return null|ContentInterface 49 | */ 50 | public function getContentById($id) 51 | { 52 | return $this->em->getRepository($this->configurationManager->getEntityClass('content'))->find($id); 53 | } 54 | 55 | /** 56 | * Get all contents 57 | * 58 | * @return array 59 | */ 60 | public function getContents() 61 | { 62 | return $this->em->getRepository($this->configurationManager->getEntityClass('content'))->findAll(); 63 | } 64 | 65 | /** 66 | * @param ContentInterface $content 67 | * 68 | * @return ContentInterface 69 | */ 70 | public function duplicate(ContentInterface $content): ContentInterface 71 | { 72 | $newContent = clone $content; 73 | $this->slugProvider->setContentValidSlug($newContent); 74 | $newContent->setPage(null); 75 | 76 | return $newContent; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Manager/PageManager.php: -------------------------------------------------------------------------------- 1 | slugProvider = $slugProvider; 22 | } 23 | 24 | /** 25 | * @param PageInterface $page 26 | * 27 | * @return PageInterface 28 | */ 29 | public function duplicate(PageInterface $page): PageInterface 30 | { 31 | $newPage = clone $page; 32 | $this->slugProvider->setPageValidIdentifier($newPage); 33 | 34 | $pageMeta = $newPage->getPageMeta(); 35 | if ($pageMeta !== null) { 36 | $this->slugProvider->setPageValidSlug($newPage); 37 | } 38 | 39 | $content = $newPage->getContent(); 40 | if ($content !== null) { 41 | $this->slugProvider->setContentValidSlug($content); 42 | } 43 | 44 | return $newPage; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Manager/UrlBuilderManager.php: -------------------------------------------------------------------------------- 1 | uploadManager = $uploadManager; 31 | $this->assetPackages = $assetPackages; 32 | $this->requestStack = $requestStack; 33 | } 34 | 35 | /** 36 | * @param string $fileName 37 | * 38 | * @return string 39 | */ 40 | public function getFileUrl(string $fileName): string 41 | { 42 | if (!$fileName) { 43 | return ''; 44 | } 45 | 46 | $filePath = $this->uploadManager->getTargetDir() . DIRECTORY_SEPARATOR . $fileName; 47 | if (!file_exists($filePath)) { 48 | return ''; 49 | } 50 | 51 | return $this->assetPackages->getUrl($this->uploadManager->getWebPath() . '/' . $fileName); 52 | } 53 | 54 | /** 55 | * @param string $url 56 | * 57 | * @return string 58 | */ 59 | public function getFullUrl(string $url): string 60 | { 61 | if (!$url) { 62 | return ''; 63 | } 64 | 65 | if (substr($url, 0, 1) === '#') { 66 | return $url; 67 | } 68 | if (substr($url, 0, 4) === 'http') { 69 | return $url; 70 | } 71 | 72 | if (method_exists($this->requestStack, 'getMainRequest')) { 73 | // SF >= 5.3 74 | $mainRequest = $this->requestStack->getMainRequest(); 75 | } else { 76 | // compat SF < 5.3 77 | $mainRequest = $this->requestStack->getMasterRequest(); 78 | } 79 | if (!$mainRequest) { 80 | return $url; 81 | } 82 | 83 | return $mainRequest->getSchemeAndHttpHost() . '/' . ltrim($url, '/'); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Model/ContentInterface.php: -------------------------------------------------------------------------------- 1 | data = []; 23 | parent::__construct(); 24 | } 25 | 26 | /** 27 | * @return ContentInterface 28 | */ 29 | public function getContent(): ContentInterface 30 | { 31 | return $this->content; 32 | } 33 | 34 | /** 35 | * @param ContentInterface $content 36 | * 37 | * @return $this 38 | */ 39 | public function setContent(ContentInterface $content): self 40 | { 41 | $this->content = $content; 42 | 43 | return $this; 44 | } 45 | 46 | /** 47 | * @return array 48 | */ 49 | public function getData() 50 | { 51 | $data = $this->data ?? []; 52 | uasort($data, function ($a, $b) { 53 | return ($a['position'] ?? 0) <=> ($b['position'] ?? 0); 54 | }); 55 | 56 | return $data; 57 | } 58 | 59 | /** 60 | * @param array $data 61 | * 62 | * @return $this 63 | */ 64 | public function setData(array $data) 65 | { 66 | $this->data = $data; 67 | 68 | return $this; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Model/ContentVersionInterface.php: -------------------------------------------------------------------------------- 1 | locale; 18 | } 19 | 20 | /** 21 | * @param string $locale 22 | * 23 | * @return $this 24 | */ 25 | public function setLocale($locale): self 26 | { 27 | $this->locale = $locale; 28 | 29 | return $this; 30 | } 31 | 32 | /** 33 | * @return string 34 | */ 35 | public function getOptionTitle() 36 | { 37 | return (string)$this->locale; 38 | } 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function getUnicityIdentifier() 44 | { 45 | return (string)$this->locale; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Model/LocaleScopeInterface.php: -------------------------------------------------------------------------------- 1 | pageMeta; 41 | } 42 | 43 | /** 44 | * @param PageMetaInterface $pageMeta 45 | * 46 | * @return $this 47 | */ 48 | public function setPageMeta(PageMetaInterface $pageMeta): self 49 | { 50 | $this->pageMeta = $pageMeta; 51 | 52 | return $this; 53 | } 54 | 55 | /** 56 | * @return string 57 | */ 58 | public function getTitle() 59 | { 60 | return $this->title; 61 | } 62 | 63 | /** 64 | * @param string $title 65 | * 66 | * @return $this 67 | */ 68 | public function setTitle($title) 69 | { 70 | $this->title = $title; 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * @return string 77 | */ 78 | public function getSlug() 79 | { 80 | return $this->slug; 81 | } 82 | 83 | /** 84 | * @param string $slug 85 | * 86 | * @return $this 87 | */ 88 | public function setSlug($slug) 89 | { 90 | $this->slug = $slug; 91 | 92 | return $this; 93 | } 94 | 95 | /** 96 | * @return string 97 | */ 98 | public function getMetaTitle() 99 | { 100 | return $this->metaTitle; 101 | } 102 | 103 | /** 104 | * @param string $metaTitle 105 | * 106 | * @return $this 107 | */ 108 | public function setMetaTitle($metaTitle) 109 | { 110 | $this->metaTitle = $metaTitle; 111 | 112 | return $this; 113 | } 114 | 115 | /** 116 | * @return string 117 | */ 118 | public function getMetaDescription() 119 | { 120 | return $this->metaDescription; 121 | } 122 | 123 | /** 124 | * @param string $metaDescription 125 | * 126 | * @return $this 127 | */ 128 | public function setMetaDescription($metaDescription) 129 | { 130 | $this->metaDescription = $metaDescription; 131 | 132 | return $this; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Model/PageMetaVersionInterface.php: -------------------------------------------------------------------------------- 1 | id; 23 | } 24 | 25 | /** 26 | * @return string 27 | */ 28 | public function getName() 29 | { 30 | return $this->name; 31 | } 32 | 33 | /** 34 | * @param string $name 35 | * 36 | * @return $this 37 | */ 38 | public function setName($name) 39 | { 40 | $this->name = $name; 41 | 42 | return $this; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Model/PageTypeInterface.php: -------------------------------------------------------------------------------- 1 | page; 28 | } 29 | 30 | /** 31 | * @param PageInterface $page 32 | * 33 | * @return $this 34 | */ 35 | public function setPage(PageInterface $page): self 36 | { 37 | $this->page = $page; 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * @return ContentVersionInterface|null 44 | */ 45 | public function getContentVersion(): ?ContentVersionInterface 46 | { 47 | return $this->contentVersion; 48 | } 49 | 50 | /** 51 | * @param ContentVersionInterface $contentVersion 52 | * 53 | * @return $this 54 | */ 55 | public function setContentVersion(ContentVersionInterface $contentVersion): self 56 | { 57 | $this->contentVersion = $contentVersion; 58 | 59 | return $this; 60 | } 61 | 62 | /** 63 | * @return PageMetaVersionInterface|null 64 | */ 65 | public function getPageMetaVersion(): ?PageMetaVersionInterface 66 | { 67 | return $this->pageMetaVersion; 68 | } 69 | 70 | /** 71 | * @param PageMetaVersionInterface $pageMetaVersion 72 | * 73 | * @return $this 74 | */ 75 | public function setPageMetaVersion(PageMetaVersionInterface $pageMetaVersion): self 76 | { 77 | $this->pageMetaVersion = $pageMetaVersion; 78 | 79 | return $this; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Model/PageVersionInterface.php: -------------------------------------------------------------------------------- 1 | id; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Model/ScopeInterface.php: -------------------------------------------------------------------------------- 1 | autoSave = false; 30 | } 31 | 32 | /** 33 | * @return int 34 | */ 35 | public function getId(): int 36 | { 37 | return $this->id; 38 | } 39 | 40 | /** 41 | * @return int|null 42 | */ 43 | public function getUserId(): ?int 44 | { 45 | return $this->userId; 46 | } 47 | 48 | /** 49 | * @param int|null $userId 50 | * 51 | * @return $this 52 | */ 53 | public function setUserId(?int $userId): self 54 | { 55 | $this->userId = $userId; 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * @return \DateTimeInterface 62 | */ 63 | public function getCreatedAt(): \DateTimeInterface 64 | { 65 | return $this->createdAt; 66 | } 67 | 68 | /** 69 | * @param \DateTimeInterface $createdAt 70 | * 71 | * @return $this 72 | */ 73 | public function setCreatedAt(\DateTimeInterface $createdAt): self 74 | { 75 | $this->createdAt = $createdAt; 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * @return bool 82 | */ 83 | public function isAutoSave(): bool 84 | { 85 | return $this->autoSave; 86 | } 87 | 88 | /** 89 | * @param bool $autoSave 90 | * 91 | * @return $this 92 | */ 93 | public function setAutoSave(bool $autoSave): self 94 | { 95 | $this->autoSave = $autoSave; 96 | 97 | return $this; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Model/VersionInterface.php: -------------------------------------------------------------------------------- 1 | getFilename(); 18 | 19 | if ($file instanceof UploadedFile) { 20 | $fileName = $file->getClientOriginalName(); 21 | } 22 | 23 | return $fileName; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Naming/NamerInterface.php: -------------------------------------------------------------------------------- 1 | getExtension(); 18 | $fileName = $file->getFilename(); 19 | 20 | if ($file instanceof UploadedFile) { 21 | $extension = $file->getClientOriginalExtension(); 22 | $fileName = $file->getClientOriginalName(); 23 | } 24 | 25 | $fileName = str_replace('.' . $extension, '', $fileName); 26 | 27 | return sprintf('%s_%s.%s', $fileName, md5(uniqid()), $extension); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Resources/config/doctrine-mapping/Content.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Resources/config/doctrine-mapping/ContentVersion.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Resources/config/doctrine-mapping/Page.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Resources/config/doctrine-mapping/PageMetaVersion.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Resources/config/doctrine-mapping/PageType.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Resources/config/doctrine-mapping/PageVersion.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/config/layout_types.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Resources/config/listeners.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Resources/config/routing/all.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Resources/config/routing/base.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Resources/config/routing/content.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sherlockode_advanced_content.controller.content::addFieldAction 5 | 6 | 7 | sherlockode_advanced_content.controller.content::fieldFormAction 8 | 9 | 10 | sherlockode_advanced_content.controller.content::saveDraftAction 11 | 12 | 13 | sherlockode_advanced_content.controller.content::deleteVersionAction 14 | 15 | 16 | -------------------------------------------------------------------------------- /Resources/config/routing/content_crud.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sherlockode_advanced_content.controller.content_crud::createAction 5 | 6 | 7 | sherlockode_advanced_content.controller.content_crud::listAction 8 | 9 | 10 | sherlockode_advanced_content.controller.content_crud::editAction 11 | 12 | 13 | sherlockode_advanced_content.controller.content_crud::deleteAction 14 | 15 | 16 | sherlockode_advanced_content.controller.content_crud::showAction 17 | 18 | 19 | -------------------------------------------------------------------------------- /Resources/config/routing/page.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sherlockode_advanced_content.controller.page::saveDraftAction 5 | 6 | 7 | sherlockode_advanced_content.controller.page::deleteVersionAction 8 | 9 | 10 | -------------------------------------------------------------------------------- /Resources/config/routing/page_crud.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sherlockode_advanced_content.controller.page_crud::editAction 5 | 6 | 7 | sherlockode_advanced_content.controller.page_crud::createAction 8 | 9 | 10 | sherlockode_advanced_content.controller.page_crud::deleteAction 11 | 12 | 13 | sherlockode_advanced_content.controller.page_crud::listAction 14 | 15 | 16 | -------------------------------------------------------------------------------- /Resources/config/routing/scope_crud.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sherlockode_advanced_content.controller.scope_crud::deleteAction 5 | 6 | 7 | -------------------------------------------------------------------------------- /Resources/config/routing/tools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sherlockode_advanced_content.controller.tools::indexAction 5 | 6 | 7 | sherlockode_advanced_content.controller.tools::importAction 8 | 9 | 10 | sherlockode_advanced_content.controller.tools::exportAction 11 | 12 | 13 | sherlockode_advanced_content.controller.tools::deletePageTypeAction 14 | 15 | 16 | -------------------------------------------------------------------------------- /Resources/doc/elements.md: -------------------------------------------------------------------------------- 1 | # Elements 2 | 3 | ---- 4 | 5 | Elements are used to create your Page display.\ 6 | They are split between Layouts and FieldTypes. 7 | 8 | An Element is defined by: 9 | - a unique code 10 | - a custom FormType (optional) 11 | - a preview template for the admin panel (optional) 12 | - a front template 13 | 14 | # Layouts 15 | 16 | ---- 17 | 18 | Rows and Columns will allow you to place your FieldTypes as you wish.\ 19 | When adding a row, you can configure the columns gap, how content should be justified, and if you want to reverse the order of the columns on mobile. 20 | You can configure the size and the offset of Columns for each breakpoint. 21 | 22 | ![image](https://user-images.githubusercontent.com/22291441/230097935-4f85ec8f-cd7e-4271-888c-40ce9402bc2b.png) 23 | ![image](https://user-images.githubusercontent.com/22291441/230097827-3380be47-4e7c-4631-856b-ca75ac7732bb.png) 24 | 25 | 26 | # Field Types 27 | 28 | ---- 29 | 30 | We provide several [FieldTypes](field_types.md).\ 31 | If you want, you can add your own field types. See [our tutorial](custom_field_type.md) for more info on custom field types. 32 | 33 | # Extra configuration 34 | 35 | ---- 36 | 37 | On top of the Element FormType, each Element can be customized: 38 | - border size, radius, style and color 39 | - background color 40 | - padding and margin 41 | - element id and classes 42 | - you can also decide on which devices your element should be hidden 43 | 44 | ![img.png](https://user-images.githubusercontent.com/22291441/230097390-74edf614-0e14-41bc-9acd-a072fabc271f.png) 45 | ![image](https://user-images.githubusercontent.com/22291441/230097547-27dc4b85-da5b-4024-b56a-c5d44ce8d30d.png) 46 | 47 | -------------------------------------------------------------------------------- /Resources/doc/export.md: -------------------------------------------------------------------------------- 1 | # Export 2 | 3 | ---- 4 | 5 | The export command will generate yaml files of your existing pages and their content. 6 | 7 | ## Configuration 8 | 9 | ---- 10 | 11 | If you want to use our export command, you can configure the directory in which to write the files. 12 | If not defined, the export will write into var/acb directory. 13 | 14 | ```yaml 15 | # config/packages/sherlockode_advanced_content.yml 16 | sherlockode_advanced_content: 17 | init_command: 18 | directory: custom/dir 19 | ``` 20 | 21 | ## Command 22 | 23 | ---- 24 | 25 | ```bash 26 | $ php bin/console sherlockode:acb:export 27 | ``` 28 | 29 | The command has several options: 30 | - type: type of entity to export. Allowed types are Page and Content 31 | - dir: directory into which the command can write the files to export (will override your configuration) 32 | You can either use a relative path (relative from `%kernel.project_dir%`) or an absolute path (for example `/tmp/files`) 33 | 34 | ```bash 35 | $ php bin/console sherlockode:acb:export #export all types in custom/dir 36 | $ php bin/console sherlockode:acb:export --type=Content #export Content only in custom/dir 37 | $ php bin/console sherlockode:acb:export --type=Content --type=Page #export Content and Page in custom/dir 38 | $ php bin/console sherlockode:acb:export --dir=specific/dir #export all types in specific/dir 39 | ``` 40 | -------------------------------------------------------------------------------- /Resources/doc/import/Content/standalone_content.yaml: -------------------------------------------------------------------------------- 1 | contents: 2 | standalone-content: 3 | name: Standalone Content 4 | children: 5 | - type: row 6 | elements: 7 | - type: column 8 | elements: 9 | - 10 | type: heading 11 | value: 12 | text: Content title 13 | level: 2 14 | extra: 15 | advanced: 16 | id: content-title 17 | - 18 | type: text 19 | value: Hello world 20 | extra: 21 | advanced: 22 | hide_on: 23 | - lg 24 | - xl 25 | - 26 | type: image 27 | value: 28 | _file: background.png 29 | alt: Content Background 30 | - 31 | type: file 32 | value: 33 | _file: document.pdf 34 | title: File title 35 | -------------------------------------------------------------------------------- /Resources/doc/import/Page/custom_page.yaml: -------------------------------------------------------------------------------- 1 | pages: 2 | custom-identifier: 3 | status: 10 4 | pageType: Custom Page Type 5 | content: 6 | - type: row 7 | elements: 8 | - type: column 9 | elements: 10 | - 11 | type: text 12 | value: Text value 13 | extra: 14 | design: 15 | background_color: '#d9d9d9' 16 | background_color_select: pick 17 | - 18 | type: separator 19 | value: 20 | - 21 | type: content 22 | value: 23 | content: slug-of-the-content 24 | config: 25 | size: 6 26 | - type: column 27 | elements: 28 | - 29 | type: my-custom-field 30 | value: 31 | block1: 32 | title: Title value 33 | block2: 34 | title: Title layout value 35 | elements: 36 | - 37 | name: First group of repeater - title 38 | description: 'First group of repeater - description' 39 | - 40 | name: Second group of repeater - title 41 | description: 'Second group of repeater
description' 42 | config: 43 | size: 6 44 | meta: 45 | title: Custom Title 46 | slug: custom-page 47 | meta_description: "Custom Meta Description" 48 | -------------------------------------------------------------------------------- /Resources/doc/import/files/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sherlockode/advanced-content-bundle/bce811a0cea0460436a00e3e627f3f8d2668a3a6/Resources/doc/import/files/background.png -------------------------------------------------------------------------------- /Resources/doc/tools.md: -------------------------------------------------------------------------------- 1 | # Tools 2 | 3 | ---- 4 | 5 | The page acb/tools allows you to [import](import.md) / [export](export.md) your entities. 6 | It also includes a CRUD system for scopes and page types 7 | 8 | ## Configuration 9 | 10 | ---- 11 | 12 | By default, the template used for this page is `@SherlockodeAdvancedContent/Tools/index.html.twig` 13 | You can override this by adding your own file in the configuration : 14 | 15 | ```yaml 16 | # config/packages/sherlockode_advanced_content.yml 17 | sherlockode_advanced_content: 18 | templates: 19 | tools: 'dir/template.html.twig' 20 | ``` 21 | 22 | If you are using our extension for Sonata ([SonataBundle](https://github.com/sherlockode/SonataAdvancedContentBundle/)), 23 | there is already a customized template for Sonata that will change the default template to `SherlockodeSonataAdvancedContent/Tools/index.html.twig` 24 | To bypass this and use your template, you will also have to add the following configuration: 25 | 26 | ```yaml 27 | # config/packages/sherlockode_sonata_advanced_content.yml 28 | sherlockode_sonata_advanced_content: 29 | use_bundle_templates: 30 | tools: false 31 | ``` 32 | 33 | ## Import / Exports 34 | 35 | ---- 36 | 37 | On this page, you will be able to : 38 | - import your entities : upload any yaml file containing your entity. 39 | As long as you respect the [format](import.md), you can include as many entities in the file as you want 40 | - export your entities : you will be able to select which entity/entities you want to export. 41 | This will result in a zip file being downloaded in your browser. 42 | 43 | 44 | ## Scopes 45 | 46 | ---- 47 | 48 | By defaut, scopes are not enabled on the bundle. 49 | If you want to handle scopes on your Pages and Contents, you need to enable scope management and also create some scopes. 50 | By default, the provided Scope class is locale-based. You can override the entity if it does not meet your requirements. 51 | Scopes can be created on the tools page. 52 | 53 | ```yaml 54 | # config/packages/sherlockode_advanced_content.yml 55 | sherlockode_advanced_content: 56 | scopes: 57 | enabled: true 58 | ``` 59 | 60 | ## Page Types 61 | 62 | ---- 63 | 64 | Typing pages allows you to categorize them and use different layouts. 65 | However, if this functionality is not useful to you, you can setup your pages without any page type. 66 | -------------------------------------------------------------------------------- /Resources/js/acb-collection.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | 3 | let collectionClass = 'acb-collection-list'; 4 | let Collection = {}; 5 | Collection.init = function(element) { 6 | if (element.hasClass(collectionClass) && element.data('index') === undefined) { 7 | element.data('index', element.children('.acb-collection-item').length); 8 | } 9 | 10 | element.find('.' + collectionClass).each(function() { 11 | Collection.init($(this)); 12 | }); 13 | }; 14 | 15 | $(function () { 16 | 'use strict'; 17 | 18 | $('body').on('click', '.btn-add-acb-collection', function () { 19 | let collectionHolder = $(this).closest('.acb-collection-widget').find('.' + collectionClass); 20 | let prototypeTarget = $(this).data('target') || 'prototype'; 21 | 22 | if (collectionHolder.length) { 23 | addCollectionRow(collectionHolder.first(), prototypeTarget); 24 | } 25 | }); 26 | 27 | $('body').on('click', '.btn-remove-acb-collection', function () { 28 | let collectionHolder = $(this).closest('.acb-collection-widget').find('.' + collectionClass); 29 | $(this).closest('.acb-collection-item').remove(); 30 | collectionHolder.trigger('acb-collection-remove-item'); 31 | }); 32 | 33 | function addCollectionRow(collectionHolder, prototypeTarget = 'prototype') { 34 | let prototype = collectionHolder.data(prototypeTarget); 35 | let index = collectionHolder.data('index'); 36 | 37 | let newRow = $(prototype.replace(/__name__/g, index)); 38 | collectionHolder.data('index', index + 1); 39 | 40 | collectionHolder.append(newRow); 41 | collectionHolder.trigger('acb-collection-append-new-item', [newRow]); 42 | } 43 | }); 44 | 45 | export default Collection; 46 | -------------------------------------------------------------------------------- /Resources/js/acb-notification.js: -------------------------------------------------------------------------------- 1 | let notifAlert = function (message) { 2 | alert(message); 3 | }; 4 | 5 | let notifConfirm = function (message, callback) { 6 | if (confirm(message)) { 7 | callback(); 8 | } 9 | }; 10 | 11 | export {notifAlert, notifConfirm}; 12 | -------------------------------------------------------------------------------- /Resources/js/acb-slide-collection.js: -------------------------------------------------------------------------------- 1 | import slide from './slide'; 2 | import Collection from './acb-collection'; 3 | 4 | slide.element.on('slideContentUpdated', function() { 5 | Collection.init(slide.content); 6 | }); 7 | -------------------------------------------------------------------------------- /Resources/js/export.js: -------------------------------------------------------------------------------- 1 | import jQuery from "jquery"; 2 | 3 | jQuery(function ($) { 4 | $('.acb-export-all').on('change', function () { 5 | let checked = $(this).prop('checked'); 6 | $(this).closest('.acb-export-entities').find('.acb-export-entity input[type="checkbox"]').each(function () { 7 | $(this).prop('checked', checked); 8 | }); 9 | }); 10 | }); 11 | 12 | -------------------------------------------------------------------------------- /Resources/js/history.js: -------------------------------------------------------------------------------- 1 | import $ from "jquery"; 2 | import { notifConfirm } from './acb-notification.js'; 3 | import { hasFormChanged, setFormChanged, isFormUpdating } from './form.js'; 4 | 5 | let isAddingVersion = false; 6 | let pageDraftFields = $('[data-page-draft]'); 7 | if (pageDraftFields.length > 0) { 8 | pageDraftFields.on('change', function() { 9 | setFormChanged(true); 10 | }); 11 | } 12 | 13 | setInterval(function() { 14 | if (isFormUpdating() !== false || isAddingVersion !== false || hasFormChanged() === false) { 15 | return; 16 | } 17 | let history = $('.version-history-table'); 18 | if (history.length === 0) { 19 | return; 20 | } 21 | 22 | let dataToPost = {'__field_name__': $('#content-data-json').val()}; 23 | if (pageDraftFields.length > 0) { 24 | let pageDraftData = {}; 25 | pageDraftFields.each(function (index, element) { 26 | pageDraftData[$(element).data('page-draft')] = $(element).val(); 27 | }); 28 | dataToPost['__page_meta__'] = pageDraftData; 29 | } 30 | 31 | setFormChanged(false); 32 | isAddingVersion = true; 33 | $.ajax({ 34 | url: history.data('save-draft-url'), 35 | data: dataToPost, 36 | type: 'POST' 37 | }).done(function (data) { 38 | if (data.success) { 39 | $('.version-history > table tbody').replaceWith($(data.html).find('table tbody')); 40 | } else { 41 | setFormChanged(true); 42 | } 43 | isAddingVersion = false; 44 | }); 45 | }, 20 * 1000); 46 | $('body').on('click', '.version-history .see-all', function () { 47 | $(this).remove(); 48 | }); 49 | $('body').on('click', '.version-history .acb-version-load', function() { 50 | window.location = $(this).closest('.version-history-table').data('load-version-url') + '?versionId=' + $(this).closest('tr').data('version-id'); 51 | }); 52 | $('body').on('click', '.version-history .acb-version-remove', function() { 53 | let history = $(this).closest('.version-history-table'); 54 | let versionLine = $(this).closest('tr'); 55 | notifConfirm($(this).data('confirm-delete'), function() { 56 | $.ajax({ 57 | url: history.data('remove-version-url'), 58 | data: {'versionId': versionLine.data('version-id')}, 59 | type: 'POST' 60 | }).done(function (data) { 61 | if (data.success) { 62 | $('.version-history > table tbody').replaceWith($(data.html).find('table tbody')); 63 | } 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /Resources/js/slide.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | 3 | class Slide 4 | { 5 | constructor() { 6 | this.closeBtn = $(''); 7 | this.header = $('
'); 8 | this.content = $('
'); 9 | this.footer = $(''); 10 | this.element = $('
'); 11 | this.element.append(this.closeBtn); 12 | this.element.append(this.header); 13 | this.element.append(this.content); 14 | this.element.append(this.footer); 15 | this.slideLayer = $('
'); 16 | 17 | let self = this; 18 | this.element.on('click', '.btn-cancel, .btn-close', function () { 19 | self.close(); 20 | }); 21 | this.slideLayer.on('click', function () { 22 | self.close(); 23 | }); 24 | } 25 | 26 | setHeader(html) { 27 | this.header.html(html) 28 | } 29 | 30 | setContent(html) { 31 | this.content.html(html); 32 | this.element.trigger('slideContentUpdated'); 33 | } 34 | 35 | setFooter(html) { 36 | this.footer.html(html) 37 | } 38 | 39 | empty() 40 | { 41 | this.header.html(''); 42 | this.content.html(''); 43 | this.footer.html(''); 44 | } 45 | 46 | open() { 47 | if (!$('body').hasClass('acb-lateral-slide-open')) { 48 | $('body').append(this.slideLayer); 49 | $('body').append(this.element); 50 | setTimeout(() => $('body').addClass('acb-lateral-slide-open'), 10); 51 | } 52 | } 53 | 54 | close() { 55 | $('body').removeClass('acb-lateral-slide-open'); 56 | } 57 | } 58 | 59 | let slide = new Slide(); 60 | 61 | export default slide; 62 | -------------------------------------------------------------------------------- /Resources/js/slug.js: -------------------------------------------------------------------------------- 1 | import jQuery from "jquery"; 2 | import getSlug from "../public/js/speakingurl.min"; 3 | 4 | function initContentSlug() { 5 | let contentName = jQuery('.acb-content-name'); 6 | contentName.each(function(){ 7 | let contentSlug = jQuery('.acb-content-slug[data-slug-token="' + jQuery(this).data('slug-token') + '"]'); 8 | if (contentSlug.length > 0) { 9 | applySlug(jQuery(this), contentSlug); 10 | } 11 | }); 12 | } 13 | function initPageSlug() { 14 | let pageTitle = jQuery('.acb-pagemeta-title'); 15 | if (pageTitle.length > 0) { 16 | pageTitle.each(function(){ 17 | let pageSlug = jQuery('.acb-pagemeta-slug[data-slug-token="' + jQuery(this).data('slug-token') + '"]'); 18 | if (pageSlug.length > 0) { 19 | applySlug(jQuery(this), pageSlug); 20 | } 21 | }); 22 | } 23 | } 24 | 25 | function applySlug(refField, slugField) { 26 | var timer; 27 | refField.on('keyup', function () { 28 | clearTimeout(timer); 29 | timer = setTimeout(function () { 30 | slugField.val(generateSlug(refField.val())); 31 | }, 300); 32 | }); 33 | } 34 | 35 | function generateSlug (value) { 36 | // use speakingurl lib if available 37 | if (typeof getSlug === 'function') { 38 | return getSlug(value); 39 | } 40 | // simple custom slug generator for fallback 41 | // 1) convert to lowercase 42 | // 2) replace pluses, quotes and spaces with dashes 43 | // 3) replace consecutive dashes with a single one 44 | // 4) remove trailing dashes 45 | // 5) replace special chars with their slug friendly equivalent 46 | // 6) remove everything but alphanumeric characters and dashes 47 | return value 48 | .toLowerCase().trim() 49 | .replace(/[+'"\s]/g, '-') 50 | .replace(/-{2,}/g, '-') 51 | .replace(/^-/g, '') 52 | .replace(/-$/g, '') 53 | 54 | .replace(/[àáâãäå]/g, 'a') 55 | .replace(/æ/g, 'ae') 56 | .replace(/ç/g, 'c') 57 | .replace(/[èéêë]/g, 'e') 58 | .replace(/[ìíîï]/g, 'i') 59 | .replace(/ñ/g, 'n') 60 | .replace(/[òóôõö]/g, 'o') 61 | .replace(/œ/g, 'oe') 62 | .replace(/ß/g, 'ss') 63 | .replace(/[ùúûüµ]/g, 'u') 64 | .replace(/[ýÿ]/g, 'y') 65 | 66 | .replace(/[^a-z0-9-]/g, '') 67 | ; 68 | } 69 | 70 | jQuery(function ($) { 71 | $('body').on('focus', '.acb-slug', function(){ 72 | if ($(this).val() === '') { 73 | $(this).val(generateSlug($(this).closest('.acb-field').find('.acb-name').first().val())); 74 | } 75 | }); 76 | 77 | initContentSlug(); 78 | initPageSlug(); 79 | }); 80 | -------------------------------------------------------------------------------- /Resources/js/utils.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | 3 | function updateCKEditorElement(form) { 4 | if (typeof CKEDITOR === 'undefined') { 5 | return; 6 | } 7 | // look for ckeditor instances in order to update the value 8 | $(form).find('textarea').each(function () { 9 | if (typeof CKEDITOR.instances[this.id] === 'undefined') { 10 | return; 11 | } 12 | CKEDITOR.instances[this.id].updateElement(); 13 | }); 14 | } 15 | function getCounterFromContainer(container) { 16 | return typeof container.data('widget-counter') !== 'undefined' ? 17 | parseInt(container.data('widget-counter')) : 18 | container.find('> .acb-sortable').length; 19 | } 20 | 21 | function replacePlaceholderEditData(content, name, formIndex) { 22 | return content.replace(/__field_name__/g, name).replace(/__name__/g, formIndex); 23 | } 24 | function replacePlaceholderNewData(content, baseName, counter) { 25 | let name = baseName + '[__name__]'; 26 | content = content.replace(/__field_name__/g, name) 27 | .replace( 28 | /field_name__/g, 29 | name.replace(/(\]\[)/g, '_') 30 | .replace(/[\[\]]/g, '_') 31 | .replace(/_{3}$/g, '__') 32 | ); // replace placeholder in HTML "id" 33 | content = content.replace(/__name__/g, counter++); 34 | 35 | return content; 36 | } 37 | function escapeNameForRegExp(name) { 38 | return name.replace(/\[/g, '\\[').replace(/\]/g, '\\]'); 39 | } 40 | 41 | function getNewPreview(preview, newContainer, counter) { 42 | let newPreview = $('
').append(preview.clone()).html(); 43 | newPreview = $(newPreview.replace( 44 | new RegExp(escapeNameForRegExp(preview.data('name')), 'g'), 45 | newContainer.data('base-name') + '[' + counter + ']' 46 | )); 47 | newPreview.find('> .acb-element-toolbar').data('form-index', counter); 48 | 49 | return newPreview; 50 | } 51 | 52 | export { 53 | updateCKEditorElement, 54 | getCounterFromContainer, 55 | replacePlaceholderEditData, 56 | replacePlaceholderNewData, 57 | escapeNameForRegExp, 58 | getNewPreview 59 | }; 60 | -------------------------------------------------------------------------------- /Resources/public/css/acb.scss: -------------------------------------------------------------------------------- 1 | .acb-add-collection-item { 2 | margin-top: 10px; 3 | background: #fff; 4 | color: #333; 5 | border: 1px solid #ddd; 6 | display: flex; 7 | padding: 8px; 8 | align-items: center; 9 | } 10 | 11 | .element-position { 12 | display: none; 13 | } 14 | -------------------------------------------------------------------------------- /Resources/public/css/advanced-content.css: -------------------------------------------------------------------------------- 1 | /* CSS for base CRUD layout*/ 2 | .content .page-info { 3 | margin-top: 15px; 4 | margin-bottom: 20px; 5 | } 6 | .content .page-info h1 { 7 | display: inline; 8 | } 9 | .content .buttons { 10 | position: fixed; 11 | bottom: 30px; 12 | right: 30px; 13 | } 14 | .acb-fields .form-control.choice-label { 15 | display: inline-block; 16 | width: 90%; 17 | margin-right: 10px; 18 | } 19 | -------------------------------------------------------------------------------- /Resources/public/css/field-list.scss: -------------------------------------------------------------------------------- 1 | 2 | .acb-field-list-container { 3 | .group-name { 4 | font-size: 18px; 5 | } 6 | 7 | .acb-field-list { 8 | list-style: none; 9 | display: flex; 10 | flex-wrap: wrap; 11 | justify-content: start; 12 | padding: 0; 13 | 14 | .acb-field-item { 15 | display: block; 16 | position: relative; 17 | width: 23%; 18 | min-width: 120px; 19 | margin: 6px 12px 6px 0; 20 | 21 | input { 22 | position: absolute; 23 | visibility: hidden; 24 | 25 | &:checked + label { 26 | background: #e5e5e5; 27 | box-shadow: 0 0 4px #999; 28 | } 29 | } 30 | 31 | label { 32 | display: flex; 33 | justify-content: center; 34 | align-items: center; 35 | border: 1px solid #aaa; 36 | border-radius: 4px; 37 | padding: 16px; 38 | text-align: center; 39 | cursor: pointer; 40 | font-size: 16px; 41 | 42 | i { 43 | margin: 2px 12px 8px; 44 | font-size: 1.5em; 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Resources/public/css/history.scss: -------------------------------------------------------------------------------- 1 | .version-history-table { 2 | tr.see-all { 3 | td { 4 | text-align: center; 5 | cursor: pointer; 6 | i { 7 | margin-right: 5px; 8 | } 9 | } 10 | ~ tr { 11 | display: none; 12 | } 13 | } 14 | .current-version { 15 | margin-left: 5px; 16 | color: #6bd54c; 17 | font-size: 16px; 18 | } 19 | td { 20 | vertical-align: middle; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Resources/public/css/index.scss: -------------------------------------------------------------------------------- 1 | @import "acb"; 2 | @import "slide"; 3 | @import "field"; 4 | @import "field-list"; 5 | @import "page"; 6 | @import "history"; 7 | @import "tools"; 8 | -------------------------------------------------------------------------------- /Resources/public/css/page.scss: -------------------------------------------------------------------------------- 1 | .acb-page-field-container { 2 | padding: 0 0 0 12px; 3 | margin: 12px 0; 4 | border-left: 6px solid #f5f5f5; 5 | 6 | display: flex; 7 | flex-wrap: wrap; 8 | column-gap: 24px; 9 | } 10 | .acb-page-field-container > div { 11 | flex-grow: 1; 12 | flex-basis: 100%; 13 | } 14 | 15 | @media screen and (min-width: 768px) { 16 | .acb-page-meta-container > div { 17 | flex-basis: 40%; 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /Resources/public/css/slide.scss: -------------------------------------------------------------------------------- 1 | $zindex: 1000; 2 | 3 | .acb-lateral-slide { 4 | position: fixed; 5 | display: flex; 6 | flex-direction: column; 7 | right: 0; 8 | top: 0; 9 | bottom: 0; 10 | width: 100%; 11 | z-index: $zindex + 1; 12 | margin-right: -100%; 13 | background: #fff; 14 | transition: .5s ease; 15 | box-shadow: 0 0 4px #999; 16 | 17 | .acb-lateral-slide-open & { 18 | margin-right: 0; 19 | } 20 | 21 | .btn-close { 22 | position: absolute; 23 | top: 16px; 24 | right: 16px; 25 | width: 24px; 26 | height: 24px; 27 | border: none; 28 | background: none; 29 | font-size: 16px; 30 | } 31 | 32 | .acb-lateral-slide-header { 33 | padding: 16px; 34 | border-bottom: 1px solid #ccc; 35 | 36 | &:empty { 37 | display: none; 38 | } 39 | 40 | h1 { 41 | font-size: 24px; 42 | } 43 | } 44 | 45 | .acb-lateral-slide-content { 46 | padding: 16px; 47 | overflow: scroll; 48 | flex-grow: 1; 49 | } 50 | 51 | .acb-lateral-slide-footer { 52 | padding: 16px; 53 | border-top: 1px solid #ccc; 54 | 55 | &:empty { 56 | display: none; 57 | } 58 | } 59 | } 60 | 61 | .acb-lateral-slide-open .acb-lateral-slide-layer { 62 | content: ''; 63 | position: fixed; 64 | background: rgba(16, 16, 16, 0.8); 65 | left: 0; 66 | top: 0; 67 | z-index: $zindex; 68 | width: 100%; 69 | height: 100%; 70 | } 71 | 72 | @media screen and (min-width: 576px) { 73 | .acb-lateral-slide { 74 | width: 80%; 75 | margin-right: -80%; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Resources/public/css/tools.scss: -------------------------------------------------------------------------------- 1 | .acb-export-entity { 2 | &, & .fields { 3 | display: grid !important; 4 | grid-template-columns: repeat(3, auto); 5 | grid-gap: 10px; 6 | } 7 | } 8 | 9 | .acb-tools-import, .acb-tools-export { 10 | button[type="submit"] { 11 | float: right; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Resources/views/Common/Macros/classes.html.twig: -------------------------------------------------------------------------------- 1 | {%- macro table() -%} 2 | table 3 | {%- endmacro -%} 4 | 5 | {%- macro button() -%} 6 | btn btn-light 7 | {%- endmacro -%} 8 | {%- macro buttonPrimary() -%} 9 | btn btn-primary 10 | {%- endmacro -%} 11 | {%- macro buttonSuccess() -%} 12 | btn btn-success 13 | {%- endmacro -%} 14 | {%- macro buttonIcon() -%} 15 | btn 16 | {%- endmacro -%} 17 | -------------------------------------------------------------------------------- /Resources/views/Common/Macros/collapse.html.twig: -------------------------------------------------------------------------------- 1 | {% macro buildAccordion(data) %} 2 | {% for item in data %} 3 |
4 | {{ item.title|raw }} 5 | {{ item.content|raw }} 6 |
7 | {% endfor %} 8 | {% endmacro %} 9 | -------------------------------------------------------------------------------- /Resources/views/Common/Macros/date.html.twig: -------------------------------------------------------------------------------- 1 | {% macro formatDatetime(date, format = 'medium') %} 2 | {% if format == 'short' %} 3 | {% set format = 'Y/m/d H:i:s' %} 4 | {% elseif format == 'long' %} 5 | {% set format = 'd F Y H:i:s' %} 6 | {% elseif format == 'full' %} 7 | {% set format = 'l d F Y H:i:s' %} 8 | {% else %} 9 | {% set format = 'd M Y, H:i:s' %} 10 | {% endif %} 11 | {{ date|date(format) }} 12 | {% endmacro %} 13 | -------------------------------------------------------------------------------- /Resources/views/Common/Macros/tabs.html.twig: -------------------------------------------------------------------------------- 1 | {% macro buildTabs(tabsData, extraClass) %} 2 | 17 |
18 | {% for tabData in tabsData %} 19 |
24 | {{ tabData.content }} 25 |
26 | {% endfor %} 27 |
28 | {% endmacro %} 29 | 30 | {% macro buildEditElementsTabs(tabsData) %} 31 | {% import _self as tabs %} 32 | {{ tabs.buildTabs(tabsData, '') }} 33 | {% endmacro %} 34 | -------------------------------------------------------------------------------- /Resources/views/Content/_button_submit_slide.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'AdvancedContentBundle' %} 2 | 3 | {% import "@SherlockodeAdvancedContent/Common/Macros/classes.html.twig" as classes %} 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Resources/views/Content/_field_form.html.twig: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Resources/views/Content/_field_preview.html.twig: -------------------------------------------------------------------------------- 1 | {% import '@SherlockodeAdvancedContent/Form/content.html.twig' as common %} 2 | {% trans_default_domain 'AdvancedContentBundle' %} 3 | 4 | {{ common.acb_render_elements(form) }} 5 | -------------------------------------------------------------------------------- /Resources/views/Content/_form.html.twig: -------------------------------------------------------------------------------- 1 | {% form_theme form '@SherlockodeAdvancedContent/Form/content.html.twig' %} 2 | 3 | {% trans_default_domain 'AdvancedContentBundle' %} 4 | 5 | {{ form_start(form) }} 6 | {{ form_errors(form) }} 7 | {{ form_row(form.name) }} 8 | {{ form_row(form.slug) }} 9 | {% if form.scopes is defined %} 10 | {{ form_row(form.scopes) }} 11 | 12 | 13 | {% endif %} 14 | 15 | {{ form_row(form.data) }} 16 | 17 |
18 | {{ 'cancel'|trans }} 19 | 22 |
23 | 24 | {{ form_rest(form) }} 25 | {{ form_end(form) }} 26 | 27 | {% if form.vars.value is not null and form.vars.value.versions|length > 0 %} 28 |
29 |

{{ 'version.list.title'|trans }}

30 | {{ include('@SherlockodeAdvancedContent/Version/content_list.html.twig', {'content': form.vars.value}) }} 31 |
32 | {% endif %} 33 | -------------------------------------------------------------------------------- /Resources/views/Content/_select_new_field.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'AdvancedContentBundle' %} 2 | 3 |
4 |
5 | {% for groupName, elements in fields %} 6 |

{{ groupName|trans }}

7 | 19 | {% endfor %} 20 |
21 |
22 | -------------------------------------------------------------------------------- /Resources/views/Content/create_content.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@SherlockodeAdvancedContent/layout.html.twig' %} 2 | 3 | {% trans_default_domain 'AdvancedContentBundle' %} 4 | 5 | {% block body %} 6 | {{ parent() }} 7 |
8 |
9 |
10 |

{{ 'content.create'|trans }}

11 |
12 |
13 |
14 |
15 |
16 |
17 | {{ include('@SherlockodeAdvancedContent/Content/_form.html.twig') }} 18 |
19 |
20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /Resources/views/Content/edit_content.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@SherlockodeAdvancedContent/layout.html.twig' %} 2 | 3 | {% trans_default_domain 'AdvancedContentBundle' %} 4 | 5 | {% block body %} 6 | {{ parent() }} 7 |
8 |
9 |
10 |

{{ 'content.edit'|trans }}

11 | {{ 'back_to_list'|trans }} 12 |
13 |
14 |
15 | {% include '@SherlockodeAdvancedContent/Content/_form.html.twig' %} 16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /Resources/views/Content/list.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@SherlockodeAdvancedContent/layout.html.twig' %} 2 | {% trans_default_domain 'AdvancedContentBundle' %} 3 | 4 | {% block body %} 5 | {{ parent() }} 6 |
7 |
8 |
9 |

{{ 'content.label'|trans }}

10 | {{ 'content.create'|trans }} 11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% for content in contents %} 25 | 26 | 27 | 28 | 34 | 35 | {% endfor %} 36 | 37 |
#{{ 'content.form.name'|trans }}{{ 'actions'|trans }}
{{ content.id }}{{ content.name }} 29 | {{ 'show'|trans }} 30 | | {{ 'edit'|trans }} 31 | | {{ 'delete'|trans }} 32 | | {{ 'duplicate'|trans }} 33 |
38 |
39 |
40 |
41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /Resources/views/Content/show.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@SherlockodeAdvancedContent/layout.html.twig' %} 2 | 3 | {% block body %} 4 | {{ parent() }} 5 |
6 |
7 |
8 |

Show Content

9 |
10 |
11 |
12 |
13 |
14 | {% for element in content.data %} 15 | {{ acb_render_element(element) }} 16 | {% endfor %} 17 |
18 |
19 |
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /Resources/views/Field/front/content.html.twig: -------------------------------------------------------------------------------- 1 | {% if entity is not null %} 2 | {% set attributes = acb_get_element_attributes(extra|default([])) %} 3 | {% set style = attributes.style|default('') %} 4 |
8 | {% for element in entity.data %} 9 | {{ acb_render_element(element) }} 10 | {% endfor %} 11 |
12 | {% endif %} 13 | -------------------------------------------------------------------------------- /Resources/views/Field/front/iframe.html.twig: -------------------------------------------------------------------------------- 1 | {% set attributes = acb_get_element_attributes(extra|default([]), 'inline') %} 2 | {% set style = attributes.style|default('') %} 3 | 4 | 11 | -------------------------------------------------------------------------------- /Resources/views/Field/front/image.html.twig: -------------------------------------------------------------------------------- 1 | {% if url is not empty %} 2 | {% set attributes = acb_get_element_attributes(extra|default([]), 'inline') %} 3 | {% set style = attributes.style|default('') %} 4 | 5 | {% if sources is defined and sources|length > 0 %} 6 | 7 | {% for source in sources %} 8 | {% if source.url is defined and source.url is not empty and source.media_query is defined and source.media_query is not empty %} 9 | 10 | {% endif %} 11 | {% endfor %} 12 | {% endif %} 13 | 14 | {{ alt is defined ? alt : '' }} 20 | 21 | {% if sources is defined and sources|length > 0 %} 22 | 23 | {% endif %} 24 | {% endif %} 25 | -------------------------------------------------------------------------------- /Resources/views/Field/front/image_carousel.html.twig: -------------------------------------------------------------------------------- 1 | {% if images is defined and images|length > 0 %} 2 | {% set attributes = acb_get_element_attributes(extra|default([])) %} 3 | {% set style = attributes.style|default('') %} 4 | 5 | {% set carouselId = attributes.id ?? 'acb_image_carousel_' ~ random() %} 6 | 44 | {% endif %} 45 | -------------------------------------------------------------------------------- /Resources/views/Field/front/link.html.twig: -------------------------------------------------------------------------------- 1 | {% set attributes = acb_get_element_attributes(extra|default([])) %} 2 | {% set style = attributes.style|default('') %} 3 | 4 | 9 | {{ title }} 10 | 11 | -------------------------------------------------------------------------------- /Resources/views/Field/front/separator.html.twig: -------------------------------------------------------------------------------- 1 | {% set attributes = acb_get_element_attributes(extra|default([])) %} 2 | {% set style = attributes.style|default('') %} 3 | 4 |
8 | -------------------------------------------------------------------------------- /Resources/views/Field/front/text.html.twig: -------------------------------------------------------------------------------- 1 | {% set attributes = acb_get_element_attributes(extra|default([])) %} 2 | {% set style = attributes.style|default('') %} 3 | 4 |

8 | {{ value }} 9 |

10 | -------------------------------------------------------------------------------- /Resources/views/Field/front/textarea.html.twig: -------------------------------------------------------------------------------- 1 | {% set attributes = acb_get_element_attributes(extra|default([])) %} 2 | {% set style = attributes.style|default('') %} 3 | 4 |

8 | {{ value|nl2br }} 9 |

10 | -------------------------------------------------------------------------------- /Resources/views/Field/front/title.html.twig: -------------------------------------------------------------------------------- 1 | {% set attributes = acb_get_element_attributes(extra|default([])) %} 2 | {% set style = attributes.style|default('') %} 3 | 4 | {% if level > 0 and level < 7 %} 5 | {% set tag = 'h' ~ level %} 6 | {% else %} 7 | {% set tag = 'h1' %} 8 | {% endif %} 9 | <{{ tag }} class="{{ attributes.classes }}" 10 | {% if attributes.id %}id="{{ attributes.id }}"{% endif %} 11 | {% if style|trim is not empty %}style="{{ style }}"{% endif %} 12 | > 13 | {{ text }} 14 | 15 | -------------------------------------------------------------------------------- /Resources/views/Field/front/video.html.twig: -------------------------------------------------------------------------------- 1 | {% if url is defined and url is not empty %} 2 | {% set options = 'playsinline' %} 3 | {% set urlOptions = [] %} 4 | {% if muted is defined and muted == true %} 5 | {% set options = options ~ ' muted' %} 6 | {% set urlOptions = urlOptions|merge([{'param': 'muted', 'value': 1}]) %} 7 | {% endif %} 8 | {% if autoplay is defined and autoplay == true %} 9 | {% set options = options ~ ' autoplay' %} 10 | {% set urlOptions = urlOptions|merge([{'param': 'autoplay', 'value': 1}]) %} 11 | {% endif %} 12 | {% if loop is defined and loop == true %} 13 | {% set options = options ~ ' loop' %} 14 | {% set urlOptions = urlOptions|merge([{'param': 'loop', 'value': 1}]) %} 15 | {% endif %} 16 | {% if controls is defined and controls == true %} 17 | {% set options = options ~ ' controls' %} 18 | {% set urlOptions = urlOptions|merge([{'param': 'controls', 'value': 1}]) %} 19 | {% endif %} 20 | 21 | {% set videoHeight = '' %} 22 | {% if height is defined and height is not empty %} 23 | {% set videoHeight = 'height="' ~ height ~ '"' %} 24 | {% set urlOptions = urlOptions|merge([{'param': 'height', 'value': height}]) %} 25 | {% endif %} 26 | 27 | {% set videoWidth = '' %} 28 | {% if width is defined and width is not empty %} 29 | {% set videoWidth = 'width="' ~ width ~ '"' %} 30 | {% set urlOptions = urlOptions|merge([{'param': 'width', 'value': width}]) %} 31 | {% endif %} 32 | 33 | {% set attributes = acb_get_element_attributes(extra|default([]), 'inline') %} 34 | {% set style = attributes.style|default('') %} 35 | 36 | {% if '.mp4' in url %} 37 | 45 | {% else %} 46 | {% for option in urlOptions %} 47 | {% set url = url ~ ('?' in url ? '&' : '?') ~ option.param ~ '=' ~ option.value %} 48 | {% endfor %} 49 | 59 | {% endif %} 60 | {% endif %} 61 | -------------------------------------------------------------------------------- /Resources/views/Field/front/wysiwyg.html.twig: -------------------------------------------------------------------------------- 1 | {% set attributes = acb_get_element_attributes(extra|default([])) %} 2 | {% set style = attributes.style|default('') %} 3 | 4 |
8 | {{ value|raw }} 9 |
10 | -------------------------------------------------------------------------------- /Resources/views/Field/preview/content.html.twig: -------------------------------------------------------------------------------- 1 | {% if content is not null %} 2 | {{ content }} 3 | {% endif %} 4 | -------------------------------------------------------------------------------- /Resources/views/Field/preview/iframe.html.twig: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Resources/views/Field/preview/image.html.twig: -------------------------------------------------------------------------------- 1 | {% if url is not empty %} 2 |

3 | {% if sources is defined and sources|length > 0 %} 4 | 5 | {% for source in sources %} 6 | {% if source.url is defined and source.url is not empty and source.media_query is defined and source.media_query is not empty %} 7 | 8 | {% endif %} 9 | {% endfor %} 10 | {% endif %} 11 | 12 | {{ alt is defined ? alt : '' }} 13 | 14 | {% if sources is defined and sources|length > 0 %} 15 | 16 | {% endif %} 17 |

18 | {% endif %} 19 | -------------------------------------------------------------------------------- /Resources/views/Field/preview/image_carousel.html.twig: -------------------------------------------------------------------------------- 1 | {% if images is defined and images|length > 0 %} 2 | {% set carouselId = 'acb_image_carousel_' ~ random() %} 3 | 37 | {% endif %} 38 | -------------------------------------------------------------------------------- /Resources/views/Field/preview/link.html.twig: -------------------------------------------------------------------------------- 1 | {{ title }} 2 | -------------------------------------------------------------------------------- /Resources/views/Field/preview/no_preview.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'AdvancedContentBundle' %} 2 |
3 | 4 |

{{ 'field_type.missing_preview'|trans }}

5 |
6 | -------------------------------------------------------------------------------- /Resources/views/Field/preview/separator.html.twig: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /Resources/views/Field/preview/text.html.twig: -------------------------------------------------------------------------------- 1 |

{{ value }}

2 | -------------------------------------------------------------------------------- /Resources/views/Field/preview/textarea.html.twig: -------------------------------------------------------------------------------- 1 |

{{ value|nl2br }}

2 | -------------------------------------------------------------------------------- /Resources/views/Field/preview/title.html.twig: -------------------------------------------------------------------------------- 1 | {% if level > 0 and level < 7 %} 2 | {% set tag = 'h' ~ level %} 3 | {% else %} 4 | {% set tag = 'h1' %} 5 | {% endif %} 6 | <{{ tag }}>{{ text }} 7 | -------------------------------------------------------------------------------- /Resources/views/Field/preview/video.html.twig: -------------------------------------------------------------------------------- 1 | {% if url is defined and url is not empty %} 2 | {% set options = 'playsinline' %} 3 | {% set urlOptions = [] %} 4 | {% if muted is defined and muted == true %} 5 | {% set options = options ~ ' muted' %} 6 | {% set urlOptions = urlOptions|merge([{'param': 'muted', 'value': 1}]) %} 7 | {% endif %} 8 | {% if autoplay is defined and autoplay == true %} 9 | {% set options = options ~ ' autoplay' %} 10 | {% set urlOptions = urlOptions|merge([{'param': 'autoplay', 'value': 1}]) %} 11 | {% endif %} 12 | {% if loop is defined and loop == true %} 13 | {% set options = options ~ ' loop' %} 14 | {% set urlOptions = urlOptions|merge([{'param': 'loop', 'value': 1}]) %} 15 | {% endif %} 16 | {% if controls is defined and controls == true %} 17 | {% set options = options ~ ' controls' %} 18 | {% set urlOptions = urlOptions|merge([{'param': 'controls', 'value': 1}]) %} 19 | {% endif %} 20 | 21 | {% set videoHeight = '' %} 22 | {% if height is defined and height is not empty %} 23 | {% set videoHeight = 'height="' ~ height ~ '"' %} 24 | {% set urlOptions = urlOptions|merge([{'param': 'height', 'value': height}]) %} 25 | {% endif %} 26 | 27 | {% set videoWidth = '' %} 28 | {% if width is defined and width is not empty %} 29 | {% set videoWidth = 'width="' ~ width ~ '"' %} 30 | {% set urlOptions = urlOptions|merge([{'param': 'width', 'value': width}]) %} 31 | {% endif %} 32 | 33 | {% if '.mp4' in url %} 34 | 35 | {% else %} 36 | {% for option in urlOptions %} 37 | {% set url = url ~ ('?' in url ? '&' : '?') ~ option.param ~ '=' ~ option.value %} 38 | {% endfor %} 39 | 46 | {% endif %} 47 | {% endif %} 48 | -------------------------------------------------------------------------------- /Resources/views/Field/preview/wysiwyg.html.twig: -------------------------------------------------------------------------------- 1 | {{ value|raw }} 2 | -------------------------------------------------------------------------------- /Resources/views/Form/base_theme.html.twig: -------------------------------------------------------------------------------- 1 | {% extends acb_base_form_theme() %} 2 | -------------------------------------------------------------------------------- /Resources/views/Form/theme.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@SherlockodeAdvancedContent/Form/base_theme.html.twig' %} 2 | 3 | {% trans_default_domain 'AdvancedContentBundle' %} 4 | 5 | {% block form_row %} 6 | {% if form.parent and form.vars.compound and (form.parent.vars.name == 'value' or (form.parent.parent and form.parent.parent.vars.name == 'value')) %} 7 | {% set row_attr = row_attr|default({})|merge({'class': row_attr.class|default('') ~ ' acb-field-element-compound'}) %} 8 | {% endif %} 9 | {{ parent() }} 10 | {% endblock %} 11 | 12 | {% block collection_widget %} 13 |
14 |
15 | {% for child in form %} 16 | {{ form_widget(child) }} 17 | {% endfor %} 18 |
19 | {% if form.vars.allow_add %} 20 | 23 | {% endif %} 24 |
25 | {% endblock collection_widget %} 26 | 27 | {% block collection_entry_widget %} 28 |
29 | {{ form_widget(form) }} 30 | {% if form.parent.vars.allow_delete %} 31 | 34 | {% endif %} 35 |
36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /Resources/views/Layout/front/column.html.twig: -------------------------------------------------------------------------------- 1 | {% set attributes = acb_get_element_attributes(extra|default([])) %} 2 | {% set style = attributes.style|default('') %} 3 | 4 |
8 | {% for element in elements|default([]) %} 9 | {{ acb_render_element(element) }} 10 | {% endfor %} 11 |
12 | -------------------------------------------------------------------------------- /Resources/views/Layout/front/row.html.twig: -------------------------------------------------------------------------------- 1 | {% set attributes = acb_get_element_attributes(extra|default([]), 'flex') %} 2 | {% set style = attributes.style|default('') %} 3 | {% if config.columns_gap|default(null) %} 4 | {% set style = 'column-gap:' ~ config.columns_gap ~ 'px;' ~ style %} 5 | {% endif %} 6 | 7 |
11 | {% for element in elements|default([]) %} 12 | {{ acb_render_element(element) }} 13 | {% endfor %} 14 |
15 | -------------------------------------------------------------------------------- /Resources/views/Layout/preview/column.html.twig: -------------------------------------------------------------------------------- 1 | {% import '@SherlockodeAdvancedContent/Form/content.html.twig' as common %} 2 | {% trans_default_domain 'AdvancedContentBundle' %} 3 | 4 | {% set elements = elements|default([]) %} 5 |
6 |
7 | {{ common.acb_column_toolbar(form, true) }} 8 |
9 | {% for key, element in elements %} 10 | {% set childForm = form.children.elements.children[key]|default(null) %} 11 | {% if childForm is not null %} 12 |
13 | {{ common.acb_element_toolbar(childForm) }} 14 | {{ acb_element_preview(element, childForm) }} 15 |
16 | {% endif %} 17 | {% endfor %} 18 | 21 |
22 | {{ common.acb_column_toolbar(form, false) }} 23 |
24 |
25 | -------------------------------------------------------------------------------- /Resources/views/Layout/preview/row.html.twig: -------------------------------------------------------------------------------- 1 | {% import '@SherlockodeAdvancedContent/Form/content.html.twig' as common %} 2 | 3 |
9 | {{ common.acb_row_toolbar(form) }} 10 | {% for key, element in elements|default([]) %} 11 | {% set childForm = form.children.elements.children[key]|default(null) %} 12 | {% if childForm is not null %} 13 | {{ acb_element_preview(element, childForm) }} 14 | {% endif %} 15 | {% endfor %} 16 |
17 | -------------------------------------------------------------------------------- /Resources/views/Page/_form.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'AdvancedContentBundle' %} 2 | 3 | {% form_theme form with ['@SherlockodeAdvancedContent/Form/content.html.twig'] %} 4 | 5 | {{ form_start(form) }} 6 | {{ form_errors(form) }} 7 | {{ form_row(form.pageIdentifier) }} 8 | {{ form_row(form.pageType) }} 9 | {% if form.status is defined %} 10 | {{ form_row(form.status) }} 11 | {% endif %} 12 | 13 | {% if form.scopes is defined %} 14 |
15 | {{ form_row(form.scopes) }} 16 | 17 | 18 | {% endif %} 19 | 20 | {% if form.pageMeta is defined %} 21 |
22 | {{ form_row(form.pageMeta) }} 23 | {% endif %} 24 | {% if form.content is defined %} 25 |
26 | {{ form_row(form.content) }} 27 | {% endif %} 28 | 29 |
30 | {{ 'cancel'|trans }} 31 | 34 |
35 | 36 | {{ form_rest(form) }} 37 | {{ form_end(form) }} 38 | 39 | {% if form.vars.value is not null and form.vars.value.versions|length > 0 %} 40 |
41 |

{{ 'version.list.title'|trans }}

42 | {{ include('@SherlockodeAdvancedContent/Version/page_list.html.twig', {'page': form.vars.value}) }} 43 |
44 | {% endif %} 45 | -------------------------------------------------------------------------------- /Resources/views/Page/create.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@SherlockodeAdvancedContent/layout.html.twig' %} 2 | 3 | {% trans_default_domain 'AdvancedContentBundle' %} 4 | 5 | {% block body %} 6 | {{ parent() }} 7 |
8 |
9 |
10 |

{{ 'page.create'|trans }}

11 | {{ 'back_to_list'|trans }} 12 |
13 |
14 |
15 | {% include '@SherlockodeAdvancedContent/Page/_form.html.twig' %} 16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /Resources/views/Page/edit.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@SherlockodeAdvancedContent/layout.html.twig' %} 2 | 3 | {% trans_default_domain 'AdvancedContentBundle' %} 4 | 5 | {% block body %} 6 | {{ parent() }} 7 |
8 |
9 |
10 |

{{ 'page.edit'|trans }}

11 | {{ 'back_to_list'|trans }} 12 |
13 |
14 |
15 | {% include '@SherlockodeAdvancedContent/Page/_form.html.twig' %} 16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /Resources/views/Page/list.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@SherlockodeAdvancedContent/layout.html.twig' %} 2 | {% trans_default_domain 'AdvancedContentBundle' %} 3 | 4 | {% block body %} 5 | {{ parent() }} 6 |
7 |
8 |
9 |

{{ 'page.label'|trans }}

10 | {{ 'page.create'|trans }} 11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% for page in pages %} 25 | 26 | 27 | 28 | 33 | 34 | {% endfor %} 35 | 36 |
#{{ 'page.label'|trans }}{{ 'actions'|trans }}
{{ page.id }}{{ page.pageMeta.title }} 29 | {{ 'edit'|trans }} 30 | | {{ 'delete'|trans }} 31 | | {{ 'duplicate'|trans }} 32 |
37 |
38 |
39 |
40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /Resources/views/Tools/_pageTypes.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'AdvancedContentBundle' %} 2 | 3 | {% import "@SherlockodeAdvancedContent/Common/Macros/classes.html.twig" as classes %} 4 | {% form_theme form '@SherlockodeAdvancedContent/Form/theme.html.twig' %} 5 | 6 | {{ form_start(form) }} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% for pageType in pageTypes %} 17 | 18 | 19 | 20 | 23 | 24 | {% endfor %} 25 | 26 | 27 | 31 | 36 | 37 | 38 |
#{{ 'page_type.form.name'|trans }}{{ 'actions'|trans }}
{{ pageType.id }}{{ pageType.name }} 21 | {{ 'delete'|trans }} 22 |
28 | {{ form_widget(form.name) }} 29 | {{ form_errors(form) }} 30 | 32 | 35 |
39 | {{ form_rest(form) }} 40 | {{ form_end(form) }} 41 | -------------------------------------------------------------------------------- /Resources/views/Tools/_scopes.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'AdvancedContentBundle' %} 2 | 3 | {% import "@SherlockodeAdvancedContent/Common/Macros/classes.html.twig" as classes %} 4 | {% form_theme form '@SherlockodeAdvancedContent/Form/theme.html.twig' %} 5 | 6 | {{ form_start(form) }} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% for scope in scopes %} 17 | 18 | 19 | 20 | 23 | 24 | {% endfor %} 25 | 26 | 27 | 31 | 36 | 37 | 38 |
#{{ 'scope.locale'|trans }}{{ 'actions'|trans }}
{{ scope.id }}{{ scope.locale }} 21 | {{ 'delete'|trans }} 22 |
28 | {{ form_widget(form.locale) }} 29 | {{ form_errors(form) }} 30 | 32 | 35 |
39 | {{ form_rest(form) }} 40 | {{ form_end(form) }} 41 | -------------------------------------------------------------------------------- /Resources/views/Version/_line.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'AdvancedContentBundle' %} 2 | 3 | {% import "@SherlockodeAdvancedContent/Common/Macros/classes.html.twig" as classes %} 4 | {% import "@SherlockodeAdvancedContent/Common/Macros/date.html.twig" as date %} 5 | 6 | {% set editVersion = app.request.query.get('versionId') == version.id %} 7 | 8 | 9 | {{ version.id }}{% if isCurrentVersion %}{% endif %} 10 | {{ date.formatDatetime(version.createdAt) }} 11 | 12 | {{ acb_get_version_user_name(version) }} 13 | {% if isCurrentVersion %} 14 | ({{ 'version.list.statuses.published'|trans }}) 15 | {% elseif version.isAutoSave %} 16 | ({{ 'version.list.statuses.auto_save'|trans }}) 17 | {% endif %} 18 | 19 | 20 | {% if not editVersion %} 21 | 27 | {% if not isCurrentVersion %} 28 | 31 | {% endif %} 32 | {% endif %} 33 | 34 | 35 | -------------------------------------------------------------------------------- /Resources/views/Version/_list.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'AdvancedContentBundle' %} 2 | 3 | {% import "@SherlockodeAdvancedContent/Common/Macros/classes.html.twig" as classes %} 4 | 5 |
6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {% if currentVersion is not null %} 22 | {{ include('@SherlockodeAdvancedContent/Version/_line.html.twig', {'version': currentVersion, 'isCurrentVersion': true}) }} 23 | {% endif %} 24 | {% set i = 0 %} 25 | {% for version in entity.versions %} 26 | {% if currentVersion is null or currentVersion.id != version.id %} 27 | {{ include('@SherlockodeAdvancedContent/Version/_line.html.twig', {'version': version, 'isCurrentVersion': false}) }} 28 | {% set i = i + 1 %} 29 | {% endif %} 30 | {% if i == 4 and not loop.last %} 31 | 32 | 33 | 34 | {% endif %} 35 | {% endfor %} 36 | 37 |
{{ 'version.list.id'|trans }}{{ 'version.list.date'|trans }}{{ 'version.list.user'|trans }}{{ 'version.list.actions.title'|trans }}
{{ 'version.list.display_all'|trans }}
38 |
39 | -------------------------------------------------------------------------------- /Resources/views/Version/content_list.html.twig: -------------------------------------------------------------------------------- 1 | {{ include('@SherlockodeAdvancedContent/Version/_list.html.twig', { 2 | 'entity': content, 3 | 'currentVersion': content.contentVersion, 4 | 'saveDraftPath': 'sherlockode_acb_content_save_draft', 5 | 'removeVersionPath': 'sherlockode_acb_content_delete_version' 6 | }) }} 7 | -------------------------------------------------------------------------------- /Resources/views/Version/page_list.html.twig: -------------------------------------------------------------------------------- 1 | {{ include('@SherlockodeAdvancedContent/Version/_list.html.twig', { 2 | 'entity': page, 3 | 'currentVersion': page.pageVersion, 4 | 'saveDraftPath': 'sherlockode_acb_page_save_draft', 5 | 'removeVersionPath': 'sherlockode_acb_page_delete_version' 6 | }) }} 7 | -------------------------------------------------------------------------------- /Resources/views/layout.html.twig: -------------------------------------------------------------------------------- 1 | {% trans_default_domain 'AdvancedContentBundle' %} 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}Advanced Content Bundle{% endblock %} 8 | {% block stylesheets %} 9 | 10 | 11 | {{ encore_entry_link_tags('app') }} 12 | {% endblock %} 13 | 14 | 15 | 29 |
30 | {% block body %} 31 | {% endblock body %} 32 |
33 | {% block javascripts %} 34 | 35 | {{ encore_entry_script_tags('app') }} 36 | {% endblock %} 37 | 38 | 39 | -------------------------------------------------------------------------------- /Scope/LocaleScopeHandler.php: -------------------------------------------------------------------------------- 1 | requestStack = $requestStack; 31 | } 32 | 33 | /** 34 | * @return string|null 35 | */ 36 | public function getScopeGroupBy(): ?string 37 | { 38 | return null; 39 | } 40 | 41 | /** 42 | * @param array $data 43 | * 44 | * @return ScopeInterface|null 45 | */ 46 | public function getScopeFromData(array $data): ?ScopeInterface 47 | { 48 | if (empty($data['locale'])) { 49 | return null; 50 | } 51 | 52 | return $this->em->getRepository($this->configurationManager->getEntityClass('scope'))->findOneBy([ 53 | 'locale' => $data['locale'], 54 | ]); 55 | } 56 | 57 | /** 58 | * @param ScopeInterface $scope 59 | * 60 | * @return array 61 | */ 62 | public function getDataFromScope(ScopeInterface $scope): array 63 | { 64 | return [ 65 | 'locale' => $scope->getLocale(), 66 | ]; 67 | } 68 | 69 | /** 70 | * @return ScopeInterface|null 71 | */ 72 | public function getCurrentScope(): ?ScopeInterface 73 | { 74 | if (!$this->configurationManager->isScopesEnabled()) { 75 | return null; 76 | } 77 | 78 | if (method_exists($this->requestStack, 'getMainRequest')) { 79 | // SF >= 5.3 80 | $mainRequest = $this->requestStack->getMainRequest(); 81 | } else { 82 | // compat SF < 5.3 83 | $mainRequest = $this->requestStack->getMasterRequest(); 84 | } 85 | if ($mainRequest === null) { 86 | return null; 87 | } 88 | 89 | if (!$mainRequest->getLocale()) { 90 | return null; 91 | } 92 | 93 | return $this->em->getRepository($this->configurationManager->getEntityClass('scope'))->findOneBy([ 94 | 'locale' => $mainRequest->getLocale(), 95 | ]); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Scope/ScopeHandlerInterface.php: -------------------------------------------------------------------------------- 1 | addRegisterMappingsPass($container); 20 | $container->addCompilerPass(new ElementPass()); 21 | $container->addCompilerPass(new FormThemePass()); 22 | } 23 | 24 | /** 25 | * @param ContainerBuilder $container 26 | */ 27 | private function addRegisterMappingsPass(ContainerBuilder $container) 28 | { 29 | $mappings = array( 30 | realpath(__DIR__.'/Resources/config/doctrine-mapping') => 'Sherlockode\AdvancedContentBundle\Model', 31 | ); 32 | 33 | if (class_exists('Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass')) { 34 | $container->addCompilerPass(DoctrineOrmMappingsPass::createXmlMappingDriver($mappings)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Slug/SlugProvider.php: -------------------------------------------------------------------------------- 1 | scopeHandler = $scopeHandler; 22 | } 23 | 24 | /** 25 | * @param PageInterface $page 26 | */ 27 | public function setPageValidIdentifier(PageInterface $page): void 28 | { 29 | while (true) { 30 | if ($this->scopeHandler->isPageIdentifierValid($page)) { 31 | break; 32 | } 33 | $page->setPageIdentifier($this->getNewValue($page->getPageIdentifier())); 34 | } 35 | } 36 | 37 | /** 38 | * @param PageInterface $page 39 | */ 40 | public function setPageValidSlug(PageInterface $page): void 41 | { 42 | while (true) { 43 | if ($this->scopeHandler->isPageSlugValid($page)) { 44 | break; 45 | } 46 | $page->getPageMeta()->setSlug($this->getNewValue($page->getPageMeta()->getSlug())); 47 | } 48 | } 49 | 50 | /** 51 | * @param ContentInterface $content 52 | */ 53 | public function setContentValidSlug(ContentInterface $content): void 54 | { 55 | while (true) { 56 | if ($this->scopeHandler->isContentSlugValid($content)) { 57 | break; 58 | } 59 | $content->setSlug($this->getNewValue($content->getSlug())); 60 | } 61 | } 62 | 63 | /** 64 | * @param string $value 65 | * 66 | * @return string 67 | */ 68 | private function getNewValue($value): string 69 | { 70 | if (preg_match('/-(\d+)$/', $value, $matches) && array_key_exists(1, $matches)) { 71 | return preg_replace('/' . $matches[1] . '$/', $matches[1] + 1, $value); 72 | } 73 | 74 | return $value . '-1'; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Slug/SlugProviderInterface.php: -------------------------------------------------------------------------------- 1 | configurationManager = $configurationManager; 23 | } 24 | 25 | /** 26 | * Add specific twig function 27 | * 28 | * @return TwigFunction[] 29 | */ 30 | public function getFunctions() 31 | { 32 | return [ 33 | new TwigFunction('acb_is_scopes_enabled', [$this, 'isScopesEnabled']), 34 | ]; 35 | } 36 | 37 | /** 38 | * @return bool 39 | */ 40 | public function isScopesEnabled(): bool 41 | { 42 | return $this->configurationManager->isScopesEnabled(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /User/AnonymousUserProvider.php: -------------------------------------------------------------------------------- 1 | translator = $translator; 20 | } 21 | 22 | /** 23 | * @return int|null 24 | */ 25 | public function getUserId(): ?int 26 | { 27 | return null; 28 | } 29 | 30 | /** 31 | * @param int|null $userId 32 | * 33 | * @return string 34 | */ 35 | public function getUserName(?int $userId): string 36 | { 37 | return $this->translator->trans('version.user.anonymous', [], 'AdvancedContentBundle'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /User/UserProviderInterface.php: -------------------------------------------------------------------------------- 1 |