001/*
002 *  Copyright 2020 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.workspaces.minisite;
017
018import java.util.HashMap;
019import java.util.List;
020import java.util.Map;
021import java.util.Objects;
022import java.util.Set;
023
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.commons.lang.StringUtils;
027
028import org.ametys.cms.data.Binary;
029import org.ametys.cms.repository.Content;
030import org.ametys.cms.repository.ModifiableWorkflowAwareContent;
031import org.ametys.cms.search.content.ContentSearcherFactory;
032import org.ametys.core.observation.Event;
033import org.ametys.plugins.repository.AmetysObjectIterable;
034import org.ametys.plugins.repository.jcr.NameHelper;
035import org.ametys.plugins.workflow.AbstractWorkflowComponent;
036import org.ametys.plugins.workflow.support.WorkflowProvider;
037import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow;
038import org.ametys.plugins.workspaces.AbstractWorkspaceModule;
039import org.ametys.plugins.workspaces.ObservationConstants;
040import org.ametys.plugins.workspaces.WorkspacesConstants;
041import org.ametys.plugins.workspaces.project.ProjectConstants;
042import org.ametys.plugins.workspaces.project.objects.Project;
043import org.ametys.plugins.workspaces.util.StatisticColumn;
044import org.ametys.plugins.workspaces.util.StatisticsColumnType;
045import org.ametys.runtime.i18n.I18nizableText;
046import org.ametys.web.repository.page.ModifiablePage;
047import org.ametys.web.repository.page.ModifiableZone;
048import org.ametys.web.repository.page.ModifiableZoneItem;
049import org.ametys.web.repository.page.Page.PageType;
050import org.ametys.web.repository.page.ZoneItem.ZoneType;
051import org.ametys.web.repository.site.Site;
052import org.ametys.web.search.query.SiteQuery;
053import org.ametys.web.workflow.CreateContentFunction;
054
055import com.google.common.collect.ImmutableSet;
056import com.opensymphony.workflow.WorkflowException;
057
058/**
059 * Workspaces module for editorial pages
060 */
061public class MiniSiteWorkspaceModule extends AbstractWorkspaceModule
062{
063    /** Avalon ROLE */
064    public static final String MINISITE_MODULE_ID = MiniSiteWorkspaceModule.class.getName();
065    
066    /** Workspaces tasks list node name */
067    private static final String __WORKSPACES_MINISITE_NODE_NAME = "minisite";
068    
069    private static final int __INITIAL_WORKFLOW_ACTION_ID = 13;
070    
071    private static final String __ARTICLE_NUMBER_HEADER_ID = __WORKSPACES_MINISITE_NODE_NAME + "$article_number";
072    
073    /** The workflow provider */
074    protected WorkflowProvider _workflowProvider;
075
076    /** The content searcher factory */
077    protected ContentSearcherFactory _contentSearcherFactory;
078    
079    @Override
080    public void service(ServiceManager manager) throws ServiceException
081    {
082        super.service(manager);
083        _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE);
084        _contentSearcherFactory = (ContentSearcherFactory) manager.lookup(ContentSearcherFactory.ROLE);
085    }
086    
087    @Override
088    public String getId()
089    {
090        return MINISITE_MODULE_ID;
091    }
092    
093    public boolean isUnactivatedByDefault()
094    {
095        return true;
096    }
097    
098    @Override
099    public String getModuleName()
100    {
101        return __WORKSPACES_MINISITE_NODE_NAME;
102    }
103    
104    public int getOrder()
105    {
106        return ORDER_MINISITE;
107    }
108    
109    @Override
110    protected String getModulePageName()
111    {
112        return "minisite";
113    }
114    
115    public I18nizableText getModuleTitle()
116    {
117        return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_SERVICE_MODULE_MINISITE_LABEL");
118    }
119    public I18nizableText getModuleDescription()
120    {
121        return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_SERVICE_MODULE_MINISITE_DESCRIPTION");
122    }
123    @Override
124    protected I18nizableText getModulePageTitle()
125    {
126        return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_WORKSPACE_PAGE_MINISITE_TITLE");
127    }
128    
129    /**
130     * Get the name of the first section of minisite
131     * @return the section's name
132     */
133    protected String getFirstSectionName()
134    {
135        return "section";
136    }
137    
138    /**
139     * Get the title of the first section of minisite
140     * @return the section's title
141     */
142    protected I18nizableText getFirstSectionTitle()
143    {
144        return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_WORKSPACE_PAGE_MINISITE_FIRST_SECTION_TITLE");
145    }
146    
147    @Override
148    protected String getModulePageTemplate()
149    {
150        // the module page is a NODE page
151        return null;
152    }
153    
154    @Override
155    protected void initializeModulePage(ModifiablePage modulePage)
156    {
157        modulePage.untag("SECTION");
158        
159        ModifiablePage firstSection = _createAndInitializeFirstSectionPage(modulePage, getFirstSectionName(), getFirstSectionTitle());
160        if (firstSection != null)
161        {
162            _initializeFirstSectionDefaultZone(firstSection);
163        }
164    }
165    
166    /**
167     * Create the first section of minisite if does not exist
168     * @param modulePage the module page
169     * @param name the name of section to create
170     * @param pageTitle the page title
171     * @return the created page or null if already exist
172     */
173    protected ModifiablePage _createAndInitializeFirstSectionPage(ModifiablePage modulePage, String name, I18nizableText pageTitle)
174    {
175        if (!modulePage.hasChild(name))
176        {
177            ModifiablePage page = modulePage.createChild(name, "ametys:defaultPage");
178            
179            // Title should not be missing, but just in case if the i18n message or the whole catalog does not exists in the requested language
180            // to prevent a non-user-friendly error and still generate the project workspace.
181            page.setTitle(StringUtils.defaultIfEmpty(_i18nUtils.translate(pageTitle, modulePage.getSitemapName()), "Missing title"));
182            page.setType(PageType.CONTAINER);
183            page.setSiteName(modulePage.getSiteName());
184            page.setSitemapName(modulePage.getSitemapName());
185            page.setTemplate(ProjectConstants.MINISITE_TEMPLATE);
186            page.tag("SECTION");
187            
188            page.saveChanges();
189            
190            Map<String, Object> eventParams = new HashMap<>();
191            eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page);
192            _observationManager.notify(new Event(org.ametys.web.ObservationConstants.EVENT_PAGE_ADDED, _currentUserProvider.getUser(), eventParams));
193            
194            return page;
195        }
196        else
197        {
198            return null;
199        }
200    }
201    
202    /**
203     * Initialize the default zone for the first section of mini site
204     * @param page The first page of minisite
205     */
206    protected void _initializeFirstSectionDefaultZone(ModifiablePage page)
207    {
208        ModifiableZone defaultZone = page.createZone("default");
209        
210        ModifiableZoneItem defaultZoneItem = defaultZone.addZoneItem();
211        defaultZoneItem.setType(ZoneType.CONTENT);
212        
213        try
214        {
215            ModifiableWorkflowAwareContent content = _createDefaultContent(page.getSite(), page.getSitemapName(), page.getTitle());
216            defaultZoneItem.setContent(content);
217            content.saveChanges();
218        }
219        catch (WorkflowException e)
220        {
221            getLogger().error("Unable to initialize the first page for minisite module, the page will not be editable until the content is manually created in the BackOffice", e);
222        }
223    }
224    
225    /**
226     * Create a new content for a minisite page of the minisite module
227     * @param site The site
228     * @param sitemapName the name of the sitemap
229     * @param title The content title
230     * @return The content
231     * @throws WorkflowException if an error occurred
232     */
233    protected ModifiableWorkflowAwareContent _createDefaultContent(Site site, String sitemapName, String title) throws WorkflowException
234    {
235        Map<String, Object> inputs = new HashMap<>();
236        inputs.put(org.ametys.cms.workflow.CreateContentFunction.CONTENT_TITLE_KEY, title);
237        inputs.put(org.ametys.cms.workflow.CreateContentFunction.CONTENT_NAME_KEY, NameHelper.filterName(title));
238        inputs.put(org.ametys.cms.workflow.CreateContentFunction.CONTENT_TYPES_KEY, new String[] {WorkspacesConstants.PROJECT_ARTICLE_CONTENT_TYPE});
239        inputs.put(org.ametys.cms.workflow.CreateContentFunction.CONTENT_LANGUAGE_KEY, sitemapName);
240        inputs.put(CreateContentFunction.SITE_KEY, site.getName());
241        
242        AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow();
243        workflow.initialize(WorkspacesConstants.CONTENT_WORKFLOW_NAME, __INITIAL_WORKFLOW_ACTION_ID, inputs);
244    
245        @SuppressWarnings("unchecked")
246        Map<String, Object> results = (Map<String, Object>) inputs.get(AbstractWorkflowComponent.RESULT_MAP_KEY);
247        ModifiableWorkflowAwareContent content = (ModifiableWorkflowAwareContent) results.get(Content.class.getName());
248        
249        return content;
250    }
251    
252    @Override
253    public Set<String> getAllowedEventTypes()
254    {
255        return ImmutableSet.of(ObservationConstants.EVENT_MINISITE_PAGE_CREATED,
256                               ObservationConstants.EVENT_MINISITE_PAGE_UPDATED,
257                               ObservationConstants.EVENT_MINISITE_PAGE_RENAMED,
258                               ObservationConstants.EVENT_MINISITE_PAGE_DELETED);
259    }
260
261    @Override
262    public Map<String, Object> _getInternalStatistics(Project project, boolean isActive)
263    { 
264        if (isActive)
265        {
266            Map<String, Object> statistics = new HashMap<>();
267            try
268            {
269                AmetysObjectIterable<Content> results = _contentSearcherFactory.create(WorkspacesConstants.PROJECT_ARTICLE_CONTENT_TYPE)
270                        .search(new SiteQuery(project.getName()));
271                statistics.put(__ARTICLE_NUMBER_HEADER_ID, results.getSize());
272            }
273            catch (Exception e)
274            {
275                getLogger().error("Error searching wall content in project " + project.getId(), e);
276            }
277            return statistics;
278        }
279        else
280        {
281            return Map.of(__ARTICLE_NUMBER_HEADER_ID, __SIZE_INACTIVE);
282        }
283    }
284    
285    @Override
286    public List<StatisticColumn> _getInternalStatisticModel()
287    {
288        return List.of(new StatisticColumn(__ARTICLE_NUMBER_HEADER_ID, new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_MINISITE_ARTICLE_NUMBER"))
289                .withRenderer("Ametys.plugins.workspaces.project.tool.ProjectsGridHelper.renderElements")
290                .withType(StatisticsColumnType.LONG)
291                .withGroup(GROUP_HEADER_ELEMENTS_ID));
292    }
293    
294    @Override
295    protected long _getModuleSize(Project project)
296    {
297        try
298        {
299            AmetysObjectIterable<Content> resultsIterable = _contentSearcherFactory.create(WorkspacesConstants.PROJECT_ARTICLE_CONTENT_TYPE)
300                    .search(new SiteQuery(project.getName()));
301
302            return resultsIterable.stream()
303                .map(content -> content.getValue("illustration/image"))
304                .filter(Objects::nonNull)
305                .filter(Binary.class::isInstance)
306                .map(Binary.class::cast)
307                .mapToLong(Binary::getLength)
308                .sum();
309        }
310        catch (Exception e)
311        {
312            getLogger().error("Error searching minisite content images in project " + project.getId(), e);
313            return __SIZE_ERROR;
314        }
315    }
316
317    @Override
318    protected boolean _showModuleSize()
319    {
320        return true;
321    }
322}