diff --git a/.styleci.yml b/.styleci.yml index b30d120f5..19a5d035d 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -11,3 +11,4 @@ disabled: - phpdoc_indent - phpdoc_to_comment - blankline_after_open_tag + - single_line_class_definition diff --git a/Content/ArticleDataProvider.php b/Content/ArticleDataProvider.php index d093127d3..e3f4a1e55 100644 --- a/Content/ArticleDataProvider.php +++ b/Content/ArticleDataProvider.php @@ -17,6 +17,7 @@ use ONGR\ElasticsearchDSL\Query\MatchAllQuery; use ONGR\ElasticsearchDSL\Query\TermQuery; use ONGR\ElasticsearchDSL\Search; +use ONGR\ElasticsearchDSL\Sort\FieldSort; use ProxyManager\Factory\LazyLoadingValueHolderFactory; use ProxyManager\Proxy\LazyLoadingInterface; use Sulu\Bundle\ArticleBundle\Document\ArticleDocument; @@ -108,15 +109,17 @@ public function resolveDataItems( $filters['type'] = $type; } + $queryResult = $this->getSearchResult($filters, $limit, $page, $pageSize); + $result = []; $uuids = []; /** @var ArticleViewDocumentInterface $document */ - foreach ($this->getSearchResult($filters, $limit, $page, $pageSize) as $document) { + foreach ($queryResult as $document) { $uuids[] = $document->getUuid(); $result[] = new ArticleDataItem($document->getUuid(), $document->getTitle(), $document); } - return new DataProviderResult($result, false, $uuids); + return new DataProviderResult($result, $this->hasNextPage($queryResult, $limit, $page, $pageSize), $uuids); } /** @@ -134,10 +137,12 @@ public function resolveResourceItems( $filters['type'] = $type; } + $queryResult = $this->getSearchResult($filters, $limit, $page, $pageSize); + $result = []; $uuids = []; /** @var ArticleViewDocumentInterface $document */ - foreach ($this->getSearchResult($filters, $limit, $page, $pageSize) as $document) { + foreach ($queryResult as $document) { $uuids[] = $document->getUuid(); $result[] = new ArticleResourceItem( $document, @@ -145,7 +150,7 @@ public function resolveResourceItems( ); } - return new DataProviderResult($result, false, $uuids); + return new DataProviderResult($result, $this->hasNextPage($queryResult, $limit, $page, $pageSize), $uuids); } /** @@ -156,6 +161,27 @@ public function resolveDatasource($datasource, array $propertyParameter, array $ return; } + /** + * Returns flag "hasNextPage". + * It combines the limit/query-count with the page and page-size. + * + * @param DocumentIterator $queryResult + * @param int $limit + * @param int $page + * @param int $pageSize + * + * @return bool + */ + private function hasNextPage(DocumentIterator $queryResult, $limit, $page, $pageSize) + { + $count = $queryResult->count(); + if ($limit && $limit < $count) { + $count = $limit; + } + + return $count > ($page * $pageSize); + } + /** * Creates search for filters and returns search-result. * @@ -175,14 +201,14 @@ private function getSearchResult(array $filters, $limit, $page, $pageSize) $queriesCount = 0; $operator = $this->getFilter($filters, 'tagOperator', 'or'); - $this->addBoolQuery('tags', $filters, 'excerpt.tags', $operator, $query, $queriesCount); + $this->addBoolQuery('tags', $filters, 'excerpt.tags.id', $operator, $query, $queriesCount); $operator = $this->getFilter($filters, 'websiteTagsOperator', 'or'); - $this->addBoolQuery('websiteTags', $filters, 'excerpt.tags', $operator, $query, $queriesCount); + $this->addBoolQuery('websiteTags', $filters, 'excerpt.tags.id', $operator, $query, $queriesCount); $operator = $this->getFilter($filters, 'categoryOperator', 'or'); - $this->addBoolQuery('categories', $filters, 'excerpt.categories', $operator, $query, $queriesCount); + $this->addBoolQuery('categories', $filters, 'excerpt.categories.id', $operator, $query, $queriesCount); $operator = $this->getFilter($filters, 'websiteCategoriesOperator', 'or'); - $this->addBoolQuery('websiteCategories', $filters, 'excerpt.categories', $operator, $query, $queriesCount); + $this->addBoolQuery('websiteCategories', $filters, 'excerpt.categories.id', $operator, $query, $queriesCount); if (array_key_exists('type', $filters)) { $query->add(new TermQuery('type', $filters['type'])); @@ -201,6 +227,8 @@ private function getSearchResult(array $filters, $limit, $page, $pageSize) $search->setSize($limit); } + $search->addSort(new FieldSort('title')); + return $repository->execute($search); } diff --git a/Content/ArticleResourceItem.php b/Content/ArticleResourceItem.php index 30845fd0f..7d17fac60 100644 --- a/Content/ArticleResourceItem.php +++ b/Content/ArticleResourceItem.php @@ -142,6 +142,26 @@ public function getSeo() return $this->article->getSeo(); } + /** + * Returns route-path. + * + * @return string + */ + public function getRoutePath() + { + return $this->article->getRoutePath(); + } + + /** + * Returns view-object. + * + * @return ArticleViewDocumentInterface + */ + public function getContent() + { + return $this->article; + } + /** * {@inheritdoc} */ diff --git a/DependencyInjection/SuluArticleExtension.php b/DependencyInjection/SuluArticleExtension.php index a9da8fe0d..05bdda39e 100644 --- a/DependencyInjection/SuluArticleExtension.php +++ b/DependencyInjection/SuluArticleExtension.php @@ -107,5 +107,10 @@ public function load(array $configs, ContainerBuilder $container) $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); $loader->load('services.xml'); + + $bundles = $container->getParameter('kernel.bundles'); + if (array_key_exists('SuluAutomationBundle', $bundles)) { + $loader->load('automation.xml'); + } } } diff --git a/README.md b/README.md index 32cb1830a..37c6045c3 100644 --- a/README.md +++ b/README.md @@ -1,121 +1,50 @@ # SuluArticleBundle +[![Build Status](https://travis-ci.org/sulu/SuluArticleBundle.svg?branch=master)](https://travis-ci.org/sulu/SuluArticleBundle) +[![StyleCI](https://styleci.io/repos/61883398/shield?branch=develop)](https://styleci.io/repos/61883398) + +The SuluArticleBundle adds support for managing articles in Sulu. Articles can be used in a lot of different ways to +manage unstructured data with an own URL in an admin-list. Most of the features, which can be used in pages, can also +be used on articles - like templates, versioning, drafting, publishing and automation. + +Additional features included: + +* Build in view-layer with elasticsearch +* Segmentation of article-templates (called article-types) +* Define URL schemas per type + +## Status + +This repository will become version 1.0 of SuluArticleBundle. It is under **heavy development** and currently its APIs +and code are not stable yet (pre 1.0). + +## Requirements + +* Composer +* PHP `^5.5 || ^7.0` +* Sulu `^1.4` +* Elasticsearch `^2.2` + +For detailed requirements see [composer.json](https://github.com/sulu/SuluArticleBundle/blob/master/composer.json). + +## Documentation + +The the Documentation is stored in the +[Resources/doc/](https://github.com/sulu/SuluArticleBundle/blob/master/Resources/doc) folder. + ## Installation -Install ElasticSearch - -Install bundle over composer: - -```bash -composer require sulu/article-bundle -``` - -Possible bundle configurations: - -```yml -sulu_article: - documents: - article: - view: Sulu\Bundle\ArticleBundle\Document\ArticleViewDocument - types: - - # Prototype - name: - translation_key: ~ - - # Display tab 'all' in list view - display_tab_all: true -``` - -Configure the bundles: - -```yml -sulu_route: - mappings: - Sulu\Bundle\ArticleBundle\Document\ArticleDocument: - generator: schema - options: - route_schema: /articles/{object.getTitle()} - -sulu_core: - content: - structure: - default_type: - article: "article_default" - paths: - article: - path: "%kernel.root_dir%/Resources/templates/articles" - type: "article" - -ongr_elasticsearch: - connections: - default: - index_name: su_articles - live: - index_name: su_articles_live - managers: - default: - connection: default - mappings: - - SuluArticleBundle - live: - connection: live - mappings: - - SuluArticleBundle -``` - -Add xml template for structure in configured folder: - -``` -%kernel.root_dir%/Resources/templates/articles/article_default.xml -``` - -Example is located in Bundle: - -``` -Resources/doc/article_default.xml -``` - -Add template for article type in configured folder: - -``` -%kernel.root_dir%/Resources/views/articles/article_default.html.twig -``` - -Example is located in Bundle: - -``` -Resources/doc/article_default.html.twig -``` - -Configure the routing - -```yml -sulu_arictle_api: - resource: "@SuluArticleBundle/Resources/config/routing_api.xml" - type: rest - prefix: /admin/api - -sulu_article: - resource: "@SuluArticleBundle/Resources/config/routing.xml" - prefix: /admin/articles -``` - -Add bundle to AbstractKernel: - -```php -new Sulu\Bundle\ArticleBundle\SuluArticleBundle(), -new ONGR\ElasticsearchBundle\ONGRElasticsearchBundle(), -``` - -Create required phpcr nodes: - -```bash -bin/console sulu:document:init -``` - -Create elasticsearch index: - -```bash -bin/console ongr:es:index:create -``` +All the installation instructions are located in the +[Documentation](https://github.com/sulu/SuluArticleBundle/blob/master/Resources/doc/installation.md). + +## License + +This bundle is under the MIT license. See the complete license [in the bundle](LICENSE) + +## Reporting an issue or a feature request + +Issues and feature requests are tracked in the [Github issue tracker](https://github.com/Sulu/SuluArticleBundle/issues). + +When reporting a bug, it may be a good idea to reproduce it in a basic project built using the +[Sulu Minimal Edition](https://github.com/sulu/sulu-minimal) to allow developers of the bundle to reproduce the issue +by simply cloning it and following some steps. diff --git a/Resources/config/automation.xml b/Resources/config/automation.xml new file mode 100644 index 000000000..6c72d3977 --- /dev/null +++ b/Resources/config/automation.xml @@ -0,0 +1,20 @@ + + + + Sulu\Bundle\ArticleBundle\Document\ArticleDocument + + + + + + + %sulu_article.article_document.class% + + + + + + diff --git a/Resources/doc/installation.md b/Resources/doc/installation.md new file mode 100644 index 000000000..aa3728f3c --- /dev/null +++ b/Resources/doc/installation.md @@ -0,0 +1,113 @@ +# Installation + +Install ElasticSearch + +Install bundle over composer: + +```bash +composer require sulu/article-bundle +``` + +Possible bundle configurations: + +```yml +sulu_article: + documents: + article: + view: Sulu\Bundle\ArticleBundle\Document\ArticleViewDocument + types: + + # Prototype + name: + translation_key: ~ + + # Display tab 'all' in list view + display_tab_all: true +``` + +Configure the bundles: + +```yml +sulu_route: + mappings: + Sulu\Bundle\ArticleBundle\Document\ArticleDocument: + generator: schema + options: + route_schema: /articles/{object.getTitle()} + +sulu_core: + content: + structure: + default_type: + article: "article_default" + paths: + article: + path: "%kernel.root_dir%/Resources/templates/articles" + type: "article" + +ongr_elasticsearch: + connections: + default: + index_name: su_articles + live: + index_name: su_articles_live + managers: + default: + connection: default + mappings: + - SuluArticleBundle + live: + connection: live + mappings: + - SuluArticleBundle +``` + +Add xml template for structure in configured folder: + +``` +%kernel.root_dir%/Resources/templates/articles/article_default.xml +``` + +Example is located in Bundle +[article_default.xml](https://github.com/sulu/SuluArticleBundle/blob/master/Resources/doc/article_default.xml). + +Add template for article type in configured folder: `` + +``` +%kernel.root_dir%/Resources/views/articles/article_default.html.twig +``` + +Example is located in Bundle +[article_default.xml](https://github.com/sulu/SuluArticleBundle/blob/master/Resources/doc/article_default.html.twig). + +Configure the routing + +```yml +sulu_arictle_api: + resource: "@SuluArticleBundle/Resources/config/routing_api.xml" + type: rest + prefix: /admin/api + +sulu_article: + resource: "@SuluArticleBundle/Resources/config/routing.xml" + prefix: /admin/articles +``` + +Add bundle to AbstractKernel: + +```php +new Sulu\Bundle\ArticleBundle\SuluArticleBundle(), +new ONGR\ElasticsearchBundle\ONGRElasticsearchBundle(), +``` + +Create required phpcr nodes: + +```bash +bin/console sulu:document:init +``` + +Create elasticsearch index: + +```bash +bin/console ongr:es:index:create +``` diff --git a/Resources/public/dist/components/articles/edit/main.js b/Resources/public/dist/components/articles/edit/main.js index dd4b26c83..501331306 100644 --- a/Resources/public/dist/components/articles/edit/main.js +++ b/Resources/public/dist/components/articles/edit/main.js @@ -1 +1 @@ -define(["jquery","underscore","suluarticle/services/article-manager","sulusecurity/services/user-manager","services/sulupreview/preview","sulusecurity/services/security-checker"],function(a,b,c,d,e,f){"use strict";return{defaults:{options:{config:{}},templates:{url:"/admin/api/articles<% if (!!id) { %>/<%= id %><% } %>?locale=<%= locale %>"},translations:{headline:"sulu_article.edit.title",draftLabel:"sulu-document-manager.draft-label",removeDraft:"sulu-content.delete-draft",unpublish:"sulu-document-manager.unpublish",unpublishConfirmTextNoDraft:"sulu-content.unpublish-confirm-text-no-draft",unpublishConfirmTextWithDraft:"sulu-content.unpublish-confirm-text-with-draft",unpublishConfirmTitle:"sulu-content.unpublish-confirm-title",deleteDraftConfirmTitle:"sulu-content.delete-draft-confirm-title",deleteDraftConfirmText:"sulu-content.delete-draft-confirm-text"}},layout:function(){return{navigation:{collapsed:!0},content:{shrinkable:!!this.options.id},sidebar:!!this.options.id&&"max"}},header:function(){var a={},b={},c={};return f.hasPermission(this.data,"edit")&&(c.saveDraft={},f.hasPermission(this.data,"live")&&(c.savePublish={},c.publish={}),a.save={parent:"saveWithDraft",options:{callback:function(){this.sandbox.emit("sulu.toolbar.save","publish")}.bind(this),dropdownItems:c}},a.template={options:{dropdownOptions:{url:"/admin/articles/templates?type="+(this.options.type||this.data.type),callback:function(a){this.template=a.template,this.sandbox.emit("sulu.tab.template-change",a)}.bind(this)}}}),f.hasPermission(this.data,"live")&&(b.unpublish={options:{title:this.translations.unpublish,disabled:!this.data.published,callback:this.unpublish.bind(this)}},b.divider={options:{divider:!0}}),f.hasPermission(this.data,"delete")&&(b["delete"]={options:{disabled:!this.options.id,callback:this.deleteArticle.bind(this)}}),this.sandbox.util.isEmpty(b)||(a.edit={options:{dropdownItems:b}}),a.statePublished={},a.stateTest={},{tabs:{url:"/admin/content-navigations?alias=article",options:{data:function(){return this.sandbox.util.deepCopy(this.data)}.bind(this),url:function(){return this.templates.url({id:this.options.id,locale:this.options.locale})}.bind(this),config:this.options.config,preview:this.preview},componentOptions:{values:this.data}},toolbar:{buttons:a}}},initialize:function(){this.bindCustomEvents(),this.showDraftLabel(),this.setHeaderBar(!0)},bindCustomEvents:function(){this.sandbox.on("sulu.header.back",this.toList.bind(this)),this.sandbox.on("sulu.tab.dirty",this.setHeaderBar.bind(this)),this.sandbox.on("sulu.toolbar.save",this.save.bind(this)),this.sandbox.on("sulu.tab.data-changed",this.setData.bind(this)),this.sandbox.on("sulu.article.error",this.handleError.bind(this)),this.sandbox.on("sulu.header.language-changed",function(a){this.sandbox.sulu.saveUserSetting(this.options.config.settingsKey,a.id),this.toEdit(a.id)}.bind(this))},handleError:function(a,b,c){switch(a){default:this.sandbox.emit("sulu.labels.error.show","labels.error.content-save-desc","labels.error"),this.sandbox.emit("sulu.header.toolbar.item.enable","save")}},deleteArticle:function(){this.sandbox.sulu.showDeleteDialog(function(a){a&&c["delete"](this.options.id).then(function(){this.toList()}.bind(this))}.bind(this))},toEdit:function(a,b){this.sandbox.emit("sulu.router.navigate","articles/"+(a||this.options.locale)+"/edit:"+(b||this.options.id)+"/details",!0,!0)},toList:function(){1===this.options.config.typeNames.length?this.sandbox.emit("sulu.router.navigate","articles/"+this.options.locale):this.sandbox.emit("sulu.router.navigate","articles:"+(this.options.type||this.data.type)+"/"+this.options.locale)},toAdd:function(){1===this.options.config.typeNames.length?this.sandbox.emit("sulu.router.navigate","articles/"+this.options.locale+"/add",!0,!0):this.sandbox.emit("sulu.router.navigate","articles/"+this.options.locale+"/add:"+(this.options.type||this.data.type),!0,!0)},save:function(a){this.loadingSave(),this.saveTab(a).then(function(b){this.saved(b.id,b,a)}.bind(this))},setData:function(a){this.data=a},saveTab:function(b){var c=a.Deferred();return this.sandbox.emit("sulu.header.toolbar.item.loading","save"),this.sandbox.once("sulu.tab.saved",function(a,b){c.resolve(b)}.bind(this)),this.sandbox.emit("sulu.tab.save",b),c},setHeaderBar:function(a){var b=!a,c=!a,d=!!a&&!this.data.publishedState;this.setSaveToolbarItems.call(this,"saveDraft",b),this.setSaveToolbarItems.call(this,"savePublish",c),this.setSaveToolbarItems.call(this,"publish",d),this.setSaveToolbarItems.call(this,"unpublish",!!this.data.published),b||c||d?this.sandbox.emit("sulu.header.toolbar.item.enable","save",!1):this.sandbox.emit("sulu.header.toolbar.item.disable","save",!1),this.showState(!!this.data.published)},setSaveToolbarItems:function(a,b){this.sandbox.emit("sulu.header.toolbar.item."+(b?"enable":"disable"),a,!1)},loadingSave:function(){this.sandbox.emit("sulu.header.toolbar.item.loading","save")},afterSaveAction:function(a,b){"back"===a?this.toList():"new"===a?this.toAdd():b&&this.toEdit(this.options.locale,this.data.id)},showDraftLabel:function(){this.sandbox.emit("sulu.header.tabs.label.hide"),this.hasDraft(this.data)||d.find(this.data.changer).then(function(a){this.sandbox.emit("sulu.header.tabs.label.show",this.sandbox.util.sprintf(this.translations.draftLabel,{changed:this.sandbox.date.format(this.data.changed,!0),user:a.username}),[{id:"delete-draft",title:this.translations.removeDraft,skin:"critical",onClick:this.deleteDraft.bind(this)}])}.bind(this))},deleteDraft:function(){this.sandbox.sulu.showDeleteDialog(function(a){a&&(this.sandbox.emit("husky.label.header.loading"),c.removeDraft(this.data.id,this.options.locale).always(function(){this.sandbox.emit("sulu.header.toolbar.item.enable","edit")}.bind(this)).then(function(a){this.sandbox.emit("sulu.router.navigate",this.sandbox.mvc.history.fragment,!0,!0),this.saved(a.id,a)}.bind(this)).fail(function(){this.sandbox.emit("husky.label.header.reset"),this.sandbox.emit("sulu.labels.error.show","labels.error.remove-draft-desc","labels.error")}.bind(this)))}.bind(this),this.translations.deleteDraftConfirmTitle,this.translations.deleteDraftConfirmText)},hasDraft:function(a){return!a.id||!!a.publishedState||!a.published},getUrl:function(a){var c=b.template(this.defaults.templates.url,{id:this.options.id,locale:this.options.locale});return a&&(c+="&action="+a),c},loadComponentData:function(){var b=a.Deferred();return this.options.id?(this.sandbox.util.load(this.getUrl()).done(function(a){this.preview=e.initialize({}),this.preview.start("Sulu\\Bundle\\ArticleBundle\\Document\\ArticleDocument",this.options.id,this.options.locale,a),b.resolve(a)}.bind(this)),b):(b.resolve({}),b)},destroy:function(){this.preview&&e.destroy(this.preview)},showState:function(a){a?(this.sandbox.emit("sulu.header.toolbar.item.hide","stateTest"),this.sandbox.emit("sulu.header.toolbar.item.show","statePublished")):(this.sandbox.emit("sulu.header.toolbar.item.hide","statePublished"),this.sandbox.emit("sulu.header.toolbar.item.show","stateTest"))},unpublish:function(){this.sandbox.sulu.showConfirmationDialog({callback:function(a){a&&(this.sandbox.emit("sulu.header.toolbar.item.loading","edit"),c.unpublish(this.data.id,this.options.locale).always(function(){this.sandbox.emit("sulu.header.toolbar.item.enable","edit")}.bind(this)).then(function(a){this.sandbox.emit("sulu.labels.success.show","labels.success.content-unpublish-desc","labels.success"),this.saved(a.id,a)}.bind(this)).fail(function(){this.sandbox.emit("sulu.labels.error.show","labels.error.content-unpublish-desc","labels.error")}.bind(this)))}.bind(this),title:this.translations.unpublishConfirmTitle,description:this.hasDraft(this.data)?this.translations.unpublishConfirmTextNoDraft:this.translations.unpublishConfirmTextWithDraft})},saved:function(a,b,c){this.setData(b),this.options.id?(this.setHeaderBar(!0),this.showDraftLabel(),this.sandbox.emit("sulu.header.saved",b),this.sandbox.emit("sulu.labels.success.show","labels.success.content-save-desc","labels.success")):this.sandbox.sulu.viewStates.justSaved=!0,this.afterSaveAction(c,!this.options.id)}}}); \ No newline at end of file +define(["jquery","underscore","config","suluarticle/services/article-manager","sulusecurity/services/user-manager","services/sulupreview/preview","sulusecurity/services/security-checker"],function(a,b,c,d,e,f,g){"use strict";return{defaults:{options:{config:{}},templates:{url:"/admin/api/articles<% if (!!id) { %>/<%= id %><% } %>?locale=<%= locale %>"},translations:{headline:"sulu_article.edit.title",draftLabel:"sulu-document-manager.draft-label",removeDraft:"sulu-content.delete-draft",unpublish:"sulu-document-manager.unpublish",unpublishConfirmTextNoDraft:"sulu-content.unpublish-confirm-text-no-draft",unpublishConfirmTextWithDraft:"sulu-content.unpublish-confirm-text-with-draft",unpublishConfirmTitle:"sulu-content.unpublish-confirm-title",deleteDraftConfirmTitle:"sulu-content.delete-draft-confirm-title",deleteDraftConfirmText:"sulu-content.delete-draft-confirm-text"}},layout:function(){return{navigation:{collapsed:!0},content:{shrinkable:!!this.options.id},sidebar:!!this.options.id&&"max"}},header:function(){var a={},b={},d={};return g.hasPermission(this.data,"edit")&&(d.saveDraft={},g.hasPermission(this.data,"live")&&(d.savePublish={},d.publish={}),c.has("sulu_automation.enabled")&&(d.automationInfo={options:{entityId:this.options.id,entityClass:"Sulu\\Bundle\\ArticleBundle\\Document\\ArticleDocument",handlerClass:["Sulu\\Bundle\\ContentBundle\\Automation\\DocumentPublishHandler","Sulu\\Bundle\\ContentBundle\\Automation\\DocumentUnpublishHandler"]}}),a.save={parent:"saveWithDraft",options:{callback:function(){this.sandbox.emit("sulu.toolbar.save","publish")}.bind(this),dropdownItems:d}},a.template={options:{dropdownOptions:{url:"/admin/articles/templates?type="+(this.options.type||this.data.type),callback:function(a){this.template=a.template,this.sandbox.emit("sulu.tab.template-change",a)}.bind(this)}}}),g.hasPermission(this.data,"live")&&(b.unpublish={options:{title:this.translations.unpublish,disabled:!this.data.published,callback:this.unpublish.bind(this)}},b.divider={options:{divider:!0}}),g.hasPermission(this.data,"delete")&&(b["delete"]={options:{disabled:!this.options.id,callback:this.deleteArticle.bind(this)}}),this.sandbox.util.isEmpty(b)||(a.edit={options:{dropdownItems:b}}),a.statePublished={},a.stateTest={},{tabs:{url:"/admin/content-navigations?alias=article&id="+this.options.id+"&locale="+this.options.locale,options:{data:function(){return this.sandbox.util.deepCopy(this.data)}.bind(this),url:function(){return this.templates.url({id:this.options.id,locale:this.options.locale})}.bind(this),config:this.options.config,preview:this.preview},componentOptions:{values:this.data}},toolbar:{buttons:a}}},initialize:function(){this.bindCustomEvents(),this.showDraftLabel(),this.setHeaderBar(!0)},bindCustomEvents:function(){this.sandbox.on("sulu.header.back",this.toList.bind(this)),this.sandbox.on("sulu.tab.dirty",this.setHeaderBar.bind(this)),this.sandbox.on("sulu.toolbar.save",this.save.bind(this)),this.sandbox.on("sulu.tab.data-changed",this.setData.bind(this)),this.sandbox.on("sulu.article.error",this.handleError.bind(this)),this.sandbox.on("sulu.header.language-changed",function(a){this.sandbox.sulu.saveUserSetting(this.options.config.settingsKey,a.id),this.toEdit(a.id)}.bind(this))},handleError:function(a,b,c){switch(a){default:this.sandbox.emit("sulu.labels.error.show","labels.error.content-save-desc","labels.error"),this.sandbox.emit("sulu.header.toolbar.item.enable","save")}},deleteArticle:function(){this.sandbox.sulu.showDeleteDialog(function(a){a&&d["delete"](this.options.id).then(function(){this.toList()}.bind(this))}.bind(this))},toEdit:function(a,b){this.sandbox.emit("sulu.router.navigate","articles/"+(a||this.options.locale)+"/edit:"+(b||this.options.id)+"/details",!0,!0)},toList:function(){1===this.options.config.typeNames.length?this.sandbox.emit("sulu.router.navigate","articles/"+this.options.locale):this.sandbox.emit("sulu.router.navigate","articles:"+(this.options.type||this.data.type)+"/"+this.options.locale)},toAdd:function(){1===this.options.config.typeNames.length?this.sandbox.emit("sulu.router.navigate","articles/"+this.options.locale+"/add",!0,!0):this.sandbox.emit("sulu.router.navigate","articles/"+this.options.locale+"/add:"+(this.options.type||this.data.type),!0,!0)},save:function(a){this.loadingSave(),this.saveTab(a).then(function(b){this.saved(b.id,b,a)}.bind(this))},setData:function(a){this.data=a},saveTab:function(b){var c=a.Deferred();return this.sandbox.emit("sulu.header.toolbar.item.loading","save"),this.sandbox.once("sulu.tab.saved",function(a,b){c.resolve(b)}.bind(this)),this.sandbox.emit("sulu.tab.save",b),c},setHeaderBar:function(a){var b=!a,c=!a,d=!!a&&!this.data.publishedState;this.setSaveToolbarItems.call(this,"saveDraft",b),this.setSaveToolbarItems.call(this,"savePublish",c),this.setSaveToolbarItems.call(this,"publish",d),this.setSaveToolbarItems.call(this,"unpublish",!!this.data.published),b||c||d?this.sandbox.emit("sulu.header.toolbar.item.enable","save",!1):this.sandbox.emit("sulu.header.toolbar.item.disable","save",!1),this.showState(!!this.data.published)},setSaveToolbarItems:function(a,b){this.sandbox.emit("sulu.header.toolbar.item."+(b?"enable":"disable"),a,!1)},loadingSave:function(){this.sandbox.emit("sulu.header.toolbar.item.loading","save")},afterSaveAction:function(a,b){"back"===a?this.toList():"new"===a?this.toAdd():b&&this.toEdit(this.options.locale,this.data.id)},showDraftLabel:function(){this.sandbox.emit("sulu.header.tabs.label.hide"),this.hasDraft(this.data)||e.find(this.data.changer).then(function(a){this.sandbox.emit("sulu.header.tabs.label.show",this.sandbox.util.sprintf(this.translations.draftLabel,{changed:this.sandbox.date.format(this.data.changed,!0),user:a.username}),[{id:"delete-draft",title:this.translations.removeDraft,skin:"critical",onClick:this.deleteDraft.bind(this)}])}.bind(this))},deleteDraft:function(){this.sandbox.sulu.showDeleteDialog(function(a){a&&(this.sandbox.emit("husky.label.header.loading"),d.removeDraft(this.data.id,this.options.locale).always(function(){this.sandbox.emit("sulu.header.toolbar.item.enable","edit")}.bind(this)).then(function(a){this.sandbox.emit("sulu.router.navigate",this.sandbox.mvc.history.fragment,!0,!0),this.saved(a.id,a)}.bind(this)).fail(function(){this.sandbox.emit("husky.label.header.reset"),this.sandbox.emit("sulu.labels.error.show","labels.error.remove-draft-desc","labels.error")}.bind(this)))}.bind(this),this.translations.deleteDraftConfirmTitle,this.translations.deleteDraftConfirmText)},hasDraft:function(a){return!a.id||!!a.publishedState||!a.published},getUrl:function(a){var c=b.template(this.defaults.templates.url,{id:this.options.id,locale:this.options.locale});return a&&(c+="&action="+a),c},loadComponentData:function(){var b=a.Deferred();return this.options.id?(this.sandbox.util.load(this.getUrl()).done(function(a){this.preview=f.initialize({}),this.preview.start("Sulu\\Bundle\\ArticleBundle\\Document\\ArticleDocument",this.options.id,this.options.locale,a),b.resolve(a)}.bind(this)),b):(b.resolve({}),b)},destroy:function(){this.preview&&f.destroy(this.preview)},showState:function(a){a?(this.sandbox.emit("sulu.header.toolbar.item.hide","stateTest"),this.sandbox.emit("sulu.header.toolbar.item.show","statePublished")):(this.sandbox.emit("sulu.header.toolbar.item.hide","statePublished"),this.sandbox.emit("sulu.header.toolbar.item.show","stateTest"))},unpublish:function(){this.sandbox.sulu.showConfirmationDialog({callback:function(a){a&&(this.sandbox.emit("sulu.header.toolbar.item.loading","edit"),d.unpublish(this.data.id,this.options.locale).always(function(){this.sandbox.emit("sulu.header.toolbar.item.enable","edit")}.bind(this)).then(function(a){this.sandbox.emit("sulu.labels.success.show","labels.success.content-unpublish-desc","labels.success"),this.saved(a.id,a)}.bind(this)).fail(function(){this.sandbox.emit("sulu.labels.error.show","labels.error.content-unpublish-desc","labels.error")}.bind(this)))}.bind(this),title:this.translations.unpublishConfirmTitle,description:this.hasDraft(this.data)?this.translations.unpublishConfirmTextNoDraft:this.translations.unpublishConfirmTextWithDraft})},saved:function(a,b,c){this.setData(b),this.options.id?(this.setHeaderBar(!0),this.showDraftLabel(),this.sandbox.emit("sulu.header.saved",b),this.sandbox.emit("sulu.labels.success.show","labels.success.content-save-desc","labels.success")):this.sandbox.sulu.viewStates.justSaved=!0,this.afterSaveAction(c,!this.options.id)}}}); \ No newline at end of file diff --git a/Resources/public/js/components/articles/edit/main.js b/Resources/public/js/components/articles/edit/main.js index 83310f7e4..b984d7680 100644 --- a/Resources/public/js/components/articles/edit/main.js +++ b/Resources/public/js/components/articles/edit/main.js @@ -10,11 +10,12 @@ define([ 'jquery', 'underscore', + 'config', 'suluarticle/services/article-manager', 'sulusecurity/services/user-manager', 'services/sulupreview/preview', 'sulusecurity/services/security-checker' -], function($, _, ArticleManager, UserManager, Preview, SecurityChecker) { +], function($, _, config, ArticleManager, UserManager, Preview, SecurityChecker) { 'use strict'; @@ -65,6 +66,19 @@ define([ saveDropdown.publish = {}; } + if (!!config.has('sulu_automation.enabled')) { + saveDropdown.automationInfo = { + options: { + entityId: this.options.id, + entityClass: 'Sulu\\Bundle\\ArticleBundle\\Document\\ArticleDocument', + handlerClass: [ + 'Sulu\\Bundle\\ContentBundle\\Automation\\DocumentPublishHandler', + 'Sulu\\Bundle\\ContentBundle\\Automation\\DocumentUnpublishHandler' + ] + } + }; + } + buttons.save = { parent: 'saveWithDraft', options: { @@ -126,7 +140,7 @@ define([ return { tabs: { - url: '/admin/content-navigations?alias=article', + url: '/admin/content-navigations?alias=article&id=' + this.options.id + '&locale=' + this.options.locale, options: { data: function() { return this.sandbox.util.deepCopy(this.data); diff --git a/Tests/Functional/Content/ArticleDataProviderTest.php b/Tests/Functional/Content/ArticleDataProviderTest.php index 5b5f8af3a..ac930df4a 100644 --- a/Tests/Functional/Content/ArticleDataProviderTest.php +++ b/Tests/Functional/Content/ArticleDataProviderTest.php @@ -64,6 +64,84 @@ public function testResolveDataItemsTypeParam() $this->assertEquals($item['id'], $result->getItems()[0]->getId()); } + public function testResolveDataItemsPagination() + { + $items = [ + $this->createArticle(), + $this->createArticle(), + $this->createArticle(), + ]; + + /** @var DataProviderInterface $dataProvider */ + $dataProvider = $this->getContainer()->get('sulu_article.content.data_provider'); + + $result = $dataProvider->resolveDataItems([], [], [], null, 1, 2); + $this->assertCount(2, $result->getItems()); + $this->assertTrue($result->getHasNextPage()); + $result = $dataProvider->resolveDataItems([], [], [], null, 2, 2); + $this->assertCount(1, $result->getItems()); + $this->assertFalse($result->getHasNextPage()); + } + + public function testResolveResourceItemsPaginationWithLimit() + { + $items = [ + $this->createArticle(), + $this->createArticle(), + $this->createArticle(), + $this->createArticle(), + ]; + + /** @var DataProviderInterface $dataProvider */ + $dataProvider = $this->getContainer()->get('sulu_article.content.data_provider'); + + $result = $dataProvider->resolveResourceItems([], [], [], 3, 1, 2); + $this->assertCount(2, $result->getItems()); + $this->assertTrue($result->getHasNextPage()); + $result = $dataProvider->resolveResourceItems([], [], [], 3, 2, 2); + $this->assertCount(1, $result->getItems()); + $this->assertFalse($result->getHasNextPage()); + } + + public function testResolveResourceItemsPagination() + { + $items = [ + $this->createArticle(), + $this->createArticle(), + $this->createArticle(), + ]; + + /** @var DataProviderInterface $dataProvider */ + $dataProvider = $this->getContainer()->get('sulu_article.content.data_provider'); + + $result = $dataProvider->resolveResourceItems([], [], [], null, 1, 2); + $this->assertCount(2, $result->getItems()); + $this->assertTrue($result->getHasNextPage()); + $result = $dataProvider->resolveResourceItems([], [], [], null, 2, 2); + $this->assertCount(1, $result->getItems()); + $this->assertFalse($result->getHasNextPage()); + } + + public function testResolveDataItemsPaginationWithLimit() + { + $items = [ + $this->createArticle(), + $this->createArticle(), + $this->createArticle(), + $this->createArticle(), + ]; + + /** @var DataProviderInterface $dataProvider */ + $dataProvider = $this->getContainer()->get('sulu_article.content.data_provider'); + + $result = $dataProvider->resolveDataItems([], [], [], 3, 1, 2); + $this->assertCount(2, $result->getItems()); + $this->assertTrue($result->getHasNextPage()); + $result = $dataProvider->resolveDataItems([], [], [], 3, 2, 2); + $this->assertCount(1, $result->getItems()); + $this->assertFalse($result->getHasNextPage()); + } + private function createArticle($title = 'Test-Article', $template = 'default') { $client = $this->createAuthenticatedClient(); diff --git a/composer.json b/composer.json index 2f3c4b675..c5b5a8f50 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "license": "MIT", "require": { "php": "^5.5 || ^7.0", - "sulu/sulu": "dev-develop", + "sulu/sulu": "dev-develop || ^1.4", "sulu/document-manager": "@dev", "massive/search-bundle": "@dev", "ongr/elasticsearch-bundle": "~1.0" @@ -16,7 +16,10 @@ "symfony/monolog-bundle": "2.4.*", "doctrine/doctrine-bundle": "1.4.*", "jackalope/jackalope-doctrine-dbal": "^1.2.5", - "phpunit/phpunit": ">=4.8" + "phpunit/phpunit": ">=4.8", + + "php-task/task-bundle": "@dev", + "php-task/php-task": "@dev" }, "keywords": [ "articles"