001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portlet.journal.service.impl;
016    
017    import com.liferay.portal.LocaleException;
018    import com.liferay.portal.NoSuchImageException;
019    import com.liferay.portal.kernel.dao.orm.ActionableDynamicQuery;
020    import com.liferay.portal.kernel.dao.orm.DynamicQuery;
021    import com.liferay.portal.kernel.dao.orm.Property;
022    import com.liferay.portal.kernel.dao.orm.PropertyFactoryUtil;
023    import com.liferay.portal.kernel.dao.orm.QueryDefinition;
024    import com.liferay.portal.kernel.dao.orm.QueryUtil;
025    import com.liferay.portal.kernel.diff.DiffHtmlUtil;
026    import com.liferay.portal.kernel.exception.PortalException;
027    import com.liferay.portal.kernel.exception.SystemException;
028    import com.liferay.portal.kernel.json.JSONFactoryUtil;
029    import com.liferay.portal.kernel.json.JSONObject;
030    import com.liferay.portal.kernel.language.LanguageUtil;
031    import com.liferay.portal.kernel.lar.ExportImportThreadLocal;
032    import com.liferay.portal.kernel.log.Log;
033    import com.liferay.portal.kernel.log.LogFactoryUtil;
034    import com.liferay.portal.kernel.notifications.UserNotificationDefinition;
035    import com.liferay.portal.kernel.portlet.PortletRequestModel;
036    import com.liferay.portal.kernel.repository.model.FileEntry;
037    import com.liferay.portal.kernel.sanitizer.SanitizerUtil;
038    import com.liferay.portal.kernel.search.BaseModelSearchResult;
039    import com.liferay.portal.kernel.search.Field;
040    import com.liferay.portal.kernel.search.Hits;
041    import com.liferay.portal.kernel.search.Indexable;
042    import com.liferay.portal.kernel.search.IndexableType;
043    import com.liferay.portal.kernel.search.Indexer;
044    import com.liferay.portal.kernel.search.IndexerRegistryUtil;
045    import com.liferay.portal.kernel.search.QueryConfig;
046    import com.liferay.portal.kernel.search.SearchContext;
047    import com.liferay.portal.kernel.search.SearchException;
048    import com.liferay.portal.kernel.search.Sort;
049    import com.liferay.portal.kernel.systemevent.SystemEvent;
050    import com.liferay.portal.kernel.systemevent.SystemEventHierarchyEntryThreadLocal;
051    import com.liferay.portal.kernel.template.TemplateConstants;
052    import com.liferay.portal.kernel.util.ArrayUtil;
053    import com.liferay.portal.kernel.util.CalendarFactoryUtil;
054    import com.liferay.portal.kernel.util.CharPool;
055    import com.liferay.portal.kernel.util.Constants;
056    import com.liferay.portal.kernel.util.ContentTypes;
057    import com.liferay.portal.kernel.util.FileUtil;
058    import com.liferay.portal.kernel.util.GetterUtil;
059    import com.liferay.portal.kernel.util.HtmlUtil;
060    import com.liferay.portal.kernel.util.HttpUtil;
061    import com.liferay.portal.kernel.util.ListUtil;
062    import com.liferay.portal.kernel.util.LocaleUtil;
063    import com.liferay.portal.kernel.util.LocalizationUtil;
064    import com.liferay.portal.kernel.util.MathUtil;
065    import com.liferay.portal.kernel.util.ObjectValuePair;
066    import com.liferay.portal.kernel.util.OrderByComparator;
067    import com.liferay.portal.kernel.util.ParamUtil;
068    import com.liferay.portal.kernel.util.PropsKeys;
069    import com.liferay.portal.kernel.util.StringBundler;
070    import com.liferay.portal.kernel.util.StringPool;
071    import com.liferay.portal.kernel.util.StringUtil;
072    import com.liferay.portal.kernel.util.Time;
073    import com.liferay.portal.kernel.util.UnicodeProperties;
074    import com.liferay.portal.kernel.util.Validator;
075    import com.liferay.portal.kernel.workflow.WorkflowConstants;
076    import com.liferay.portal.kernel.workflow.WorkflowHandlerRegistryUtil;
077    import com.liferay.portal.kernel.xml.Document;
078    import com.liferay.portal.kernel.xml.DocumentException;
079    import com.liferay.portal.kernel.xml.Element;
080    import com.liferay.portal.kernel.xml.Node;
081    import com.liferay.portal.kernel.xml.SAXReaderUtil;
082    import com.liferay.portal.kernel.xml.XPath;
083    import com.liferay.portal.model.Company;
084    import com.liferay.portal.model.Group;
085    import com.liferay.portal.model.Image;
086    import com.liferay.portal.model.ResourceConstants;
087    import com.liferay.portal.model.SystemEventConstants;
088    import com.liferay.portal.model.User;
089    import com.liferay.portal.service.ServiceContext;
090    import com.liferay.portal.service.ServiceContextUtil;
091    import com.liferay.portal.servlet.filters.cache.CacheUtil;
092    import com.liferay.portal.theme.ThemeDisplay;
093    import com.liferay.portal.util.GroupSubscriptionCheckSubscriptionSender;
094    import com.liferay.portal.util.PortalUtil;
095    import com.liferay.portal.util.PortletKeys;
096    import com.liferay.portal.util.PrefsPropsUtil;
097    import com.liferay.portal.util.PropsValues;
098    import com.liferay.portal.util.SubscriptionSender;
099    import com.liferay.portal.webserver.WebServerServletTokenUtil;
100    import com.liferay.portlet.asset.model.AssetEntry;
101    import com.liferay.portlet.asset.model.AssetLink;
102    import com.liferay.portlet.asset.model.AssetLinkConstants;
103    import com.liferay.portlet.documentlibrary.util.DLUtil;
104    import com.liferay.portlet.dynamicdatamapping.NoSuchTemplateException;
105    import com.liferay.portlet.dynamicdatamapping.StorageFieldNameException;
106    import com.liferay.portlet.dynamicdatamapping.StorageFieldRequiredException;
107    import com.liferay.portlet.dynamicdatamapping.StructureDefinitionException;
108    import com.liferay.portlet.dynamicdatamapping.model.DDMForm;
109    import com.liferay.portlet.dynamicdatamapping.model.DDMFormField;
110    import com.liferay.portlet.dynamicdatamapping.model.DDMStructure;
111    import com.liferay.portlet.dynamicdatamapping.model.DDMTemplate;
112    import com.liferay.portlet.dynamicdatamapping.model.LocalizedValue;
113    import com.liferay.portlet.dynamicdatamapping.storage.Fields;
114    import com.liferay.portlet.dynamicdatamapping.util.DDMUtil;
115    import com.liferay.portlet.dynamicdatamapping.util.DDMXMLUtil;
116    import com.liferay.portlet.journal.ArticleContentException;
117    import com.liferay.portlet.journal.ArticleDisplayDateException;
118    import com.liferay.portlet.journal.ArticleExpirationDateException;
119    import com.liferay.portlet.journal.ArticleIdException;
120    import com.liferay.portlet.journal.ArticleReviewDateException;
121    import com.liferay.portlet.journal.ArticleSmallImageNameException;
122    import com.liferay.portlet.journal.ArticleSmallImageSizeException;
123    import com.liferay.portlet.journal.ArticleTitleException;
124    import com.liferay.portlet.journal.ArticleVersionException;
125    import com.liferay.portlet.journal.DuplicateArticleIdException;
126    import com.liferay.portlet.journal.InvalidDDMStructureException;
127    import com.liferay.portlet.journal.NoSuchArticleException;
128    import com.liferay.portlet.journal.model.JournalArticle;
129    import com.liferay.portlet.journal.model.JournalArticleConstants;
130    import com.liferay.portlet.journal.model.JournalArticleDisplay;
131    import com.liferay.portlet.journal.model.JournalArticleResource;
132    import com.liferay.portlet.journal.model.JournalFolder;
133    import com.liferay.portlet.journal.model.impl.JournalArticleDisplayImpl;
134    import com.liferay.portlet.journal.service.base.JournalArticleLocalServiceBaseImpl;
135    import com.liferay.portlet.journal.service.permission.JournalPermission;
136    import com.liferay.portlet.journal.social.JournalActivityKeys;
137    import com.liferay.portlet.journal.util.JournalContentUtil;
138    import com.liferay.portlet.journal.util.JournalUtil;
139    import com.liferay.portlet.journal.util.comparator.ArticleIDComparator;
140    import com.liferay.portlet.journal.util.comparator.ArticleVersionComparator;
141    import com.liferay.portlet.social.model.SocialActivityConstants;
142    import com.liferay.portlet.trash.model.TrashEntry;
143    import com.liferay.portlet.trash.model.TrashVersion;
144    import com.liferay.portlet.trash.util.TrashUtil;
145    
146    import java.io.File;
147    import java.io.IOException;
148    import java.io.Serializable;
149    
150    import java.util.ArrayList;
151    import java.util.Calendar;
152    import java.util.Date;
153    import java.util.HashMap;
154    import java.util.HashSet;
155    import java.util.LinkedHashMap;
156    import java.util.List;
157    import java.util.Locale;
158    import java.util.Map;
159    import java.util.Set;
160    
161    import javax.portlet.PortletPreferences;
162    
163    /**
164     * Provides the local service for accessing, adding, deleting, and updating web
165     * content articles.
166     *
167     * <p>
168     * The web content articles hold HTML content wrapped in XML. The XML lets you
169     * specify the article's default locale and available locales. Here is a content
170     * example:
171     * </p>
172     *
173     * <p>
174     * <pre>
175     * <code>
176     * &lt;?xml version='1.0' encoding='UTF-8'?&gt;
177     * &lt;root default-locale="en_US" available-locales="en_US"&gt;
178     *      &lt;static-content language-id="en_US"&gt;
179     *              &lt;![CDATA[&lt;p&gt;&lt;b&gt;&lt;i&gt;test&lt;i&gt; content&lt;b&gt;&lt;/p&gt;]]&gt;
180     *      &lt;/static-content&gt;
181     * &lt;/root&gt;
182     * </code>
183     * </pre>
184     * </p>
185     *
186     * @author Brian Wing Shun Chan
187     * @author Raymond Aug??
188     * @author Bruno Farache
189     * @author Juan Fern??ndez
190     * @author Sergio Gonz??lez
191     */
192    public class JournalArticleLocalServiceImpl
193            extends JournalArticleLocalServiceBaseImpl {
194    
195            /**
196             * Adds a web content article with additional parameters.
197             *
198             * @param  userId the primary key of the web content article's creator/owner
199             * @param  groupId the primary key of the web content article's group
200             * @param  folderId the primary key of the web content article folder
201             * @param  classNameId the primary key of the DDMStructure class if the web
202             *         content article is related to a DDM structure, the primary key of
203             *         the class name associated with the article, or {@link
204             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
205             * @param  classPK the primary key of the DDM structure, if the primary key
206             *         of the DDMStructure class is given as the
207             *         <code>classNameId</code> parameter, the primary key of the class
208             *         associated with the web content article, or <code>0</code>
209             *         otherwise
210             * @param  articleId the primary key of the web content article
211             * @param  autoArticleId whether to auto generate the web content article ID
212             * @param  version the web content article's version
213             * @param  titleMap the web content article's locales and localized titles
214             * @param  descriptionMap the web content article's locales and localized
215             *         descriptions
216             * @param  content the HTML content wrapped in XML. For more information,
217             *         see the content example in the class description for {@link
218             *         JournalArticleLocalServiceImpl}.
219             * @param  ddmStructureKey the primary key of the web content article's DDM
220             *         structure, if the article is related to a DDM structure, or
221             *         <code>null</code> otherwise
222             * @param  ddmTemplateKey the primary key of the web content article's DDM
223             *         template
224             * @param  layoutUuid the unique string identifying the web content
225             *         article's display page
226             * @param  displayDateMonth the month the web content article is set to
227             *         display
228             * @param  displayDateDay the calendar day the web content article is set to
229             *         display
230             * @param  displayDateYear the year the web content article is set to
231             *         display
232             * @param  displayDateHour the hour the web content article is set to
233             *         display
234             * @param  displayDateMinute the minute the web content article is set to
235             *         display
236             * @param  expirationDateMonth the month the web content article is set to
237             *         expire
238             * @param  expirationDateDay the calendar day the web content article is set
239             *         to expire
240             * @param  expirationDateYear the year the web content article is set to
241             *         expire
242             * @param  expirationDateHour the hour the web content article is set to
243             *         expire
244             * @param  expirationDateMinute the minute the web content article is set to
245             *         expire
246             * @param  neverExpire whether the web content article is not set to auto
247             *         expire
248             * @param  reviewDateMonth the month the web content article is set for
249             *         review
250             * @param  reviewDateDay the calendar day the web content article is set for
251             *         review
252             * @param  reviewDateYear the year the web content article is set for review
253             * @param  reviewDateHour the hour the web content article is set for review
254             * @param  reviewDateMinute the minute the web content article is set for
255             *         review
256             * @param  neverReview whether the web content article is not set for review
257             * @param  indexable whether the web content article is searchable
258             * @param  smallImage whether the web content article has a small image
259             * @param  smallImageURL the web content article's small image URL
260             * @param  smallImageFile the web content article's small image file
261             * @param  images the web content's images
262             * @param  articleURL the web content article's accessible URL
263             * @param  serviceContext the service context to be applied. Can set the
264             *         UUID, creation date, modification date, expando bridge
265             *         attributes, guest permissions, group permissions, asset category
266             *         IDs, asset tag names, asset link entry IDs, the "urlTitle"
267             *         attribute, and workflow actions for the web content article. Can
268             *         also set whether to add the default guest and group permissions.
269             * @return the web content article
270             * @throws PortalException if a portal exception occurred
271             */
272            @Indexable(type = IndexableType.REINDEX)
273            @Override
274            public JournalArticle addArticle(
275                            long userId, long groupId, long folderId, long classNameId,
276                            long classPK, String articleId, boolean autoArticleId,
277                            double version, Map<Locale, String> titleMap,
278                            Map<Locale, String> descriptionMap, String content,
279                            String ddmStructureKey, String ddmTemplateKey, String layoutUuid,
280                            int displayDateMonth, int displayDateDay, int displayDateYear,
281                            int displayDateHour, int displayDateMinute, int expirationDateMonth,
282                            int expirationDateDay, int expirationDateYear,
283                            int expirationDateHour, int expirationDateMinute,
284                            boolean neverExpire, int reviewDateMonth, int reviewDateDay,
285                            int reviewDateYear, int reviewDateHour, int reviewDateMinute,
286                            boolean neverReview, boolean indexable, boolean smallImage,
287                            String smallImageURL, File smallImageFile,
288                            Map<String, byte[]> images, String articleURL,
289                            ServiceContext serviceContext)
290                    throws PortalException {
291    
292                    // Article
293    
294                    User user = userPersistence.findByPrimaryKey(userId);
295                    articleId = StringUtil.toUpperCase(articleId.trim());
296    
297                    Date displayDate = null;
298                    Date expirationDate = null;
299                    Date reviewDate = null;
300    
301                    if (classNameId == JournalArticleConstants.CLASSNAME_ID_DEFAULT) {
302                            displayDate = PortalUtil.getDate(
303                                    displayDateMonth, displayDateDay, displayDateYear,
304                                    displayDateHour, displayDateMinute, user.getTimeZone(),
305                                    ArticleDisplayDateException.class);
306    
307                            if (!neverExpire) {
308                                    expirationDate = PortalUtil.getDate(
309                                            expirationDateMonth, expirationDateDay, expirationDateYear,
310                                            expirationDateHour, expirationDateMinute,
311                                            user.getTimeZone(), ArticleExpirationDateException.class);
312                            }
313    
314                            if (!neverReview) {
315                                    reviewDate = PortalUtil.getDate(
316                                            reviewDateMonth, reviewDateDay, reviewDateYear,
317                                            reviewDateHour, reviewDateMinute, user.getTimeZone(),
318                                            ArticleReviewDateException.class);
319                            }
320                    }
321    
322                    byte[] smallImageBytes = null;
323    
324                    try {
325                            smallImageBytes = FileUtil.getBytes(smallImageFile);
326                    }
327                    catch (IOException ioe) {
328                    }
329    
330                    Date now = new Date();
331    
332                    validateDDMStructureId(groupId, folderId, ddmStructureKey);
333    
334                    validate(
335                            user.getCompanyId(), groupId, classNameId, articleId, autoArticleId,
336                            version, titleMap, content, ddmStructureKey, ddmTemplateKey,
337                            expirationDate, smallImage, smallImageURL, smallImageFile,
338                            smallImageBytes, serviceContext);
339    
340                    if (autoArticleId) {
341                            articleId = String.valueOf(counterLocalService.increment());
342                    }
343    
344                    serviceContext.setAttribute("articleId", articleId);
345    
346                    long id = counterLocalService.increment();
347    
348                    String articleResourceUuid = GetterUtil.getString(
349                            serviceContext.getAttribute("articleResourceUuid"));
350    
351                    long resourcePrimKey =
352                            journalArticleResourceLocalService.getArticleResourcePrimKey(
353                                    articleResourceUuid, groupId, articleId);
354    
355                    JournalArticle article = journalArticlePersistence.create(id);
356    
357                    Locale locale = getArticleDefaultLocale(content);
358    
359                    String title = titleMap.get(locale);
360    
361                    content = format(
362                            user, groupId, articleId, version, false, content, ddmStructureKey,
363                            images);
364    
365                    article.setUuid(serviceContext.getUuid());
366                    article.setResourcePrimKey(resourcePrimKey);
367                    article.setGroupId(groupId);
368                    article.setCompanyId(user.getCompanyId());
369                    article.setUserId(user.getUserId());
370                    article.setUserName(user.getFullName());
371                    article.setCreateDate(serviceContext.getCreateDate(now));
372                    article.setModifiedDate(serviceContext.getModifiedDate(now));
373                    article.setFolderId(folderId);
374                    article.setClassNameId(classNameId);
375                    article.setClassPK(classPK);
376                    article.setTreePath(article.buildTreePath());
377                    article.setArticleId(articleId);
378                    article.setVersion(version);
379                    article.setTitleMap(titleMap, locale);
380                    article.setUrlTitle(
381                            getUniqueUrlTitle(id, articleId, title, null, serviceContext));
382                    article.setDescriptionMap(descriptionMap, locale);
383                    article.setContent(content);
384                    article.setDDMStructureKey(ddmStructureKey);
385                    article.setDDMTemplateKey(ddmTemplateKey);
386                    article.setLayoutUuid(layoutUuid);
387                    article.setDisplayDate(displayDate);
388                    article.setExpirationDate(expirationDate);
389                    article.setReviewDate(reviewDate);
390                    article.setIndexable(indexable);
391                    article.setSmallImage(smallImage);
392                    article.setSmallImageId(counterLocalService.increment());
393                    article.setSmallImageURL(smallImageURL);
394    
395                    if ((expirationDate == null) || expirationDate.after(now)) {
396                            article.setStatus(WorkflowConstants.STATUS_DRAFT);
397                    }
398                    else {
399                            article.setStatus(WorkflowConstants.STATUS_EXPIRED);
400                    }
401    
402                    article.setStatusByUserId(userId);
403                    article.setStatusDate(serviceContext.getModifiedDate(now));
404                    article.setExpandoBridgeAttributes(serviceContext);
405    
406                    journalArticlePersistence.update(article);
407    
408                    // Resources
409    
410                    if (serviceContext.isAddGroupPermissions() ||
411                            serviceContext.isAddGuestPermissions()) {
412    
413                            addArticleResources(
414                                    article, serviceContext.isAddGroupPermissions(),
415                                    serviceContext.isAddGuestPermissions());
416                    }
417                    else {
418                            addArticleResources(
419                                    article, serviceContext.getGroupPermissions(),
420                                    serviceContext.getGuestPermissions());
421                    }
422    
423                    // Small image
424    
425                    saveImages(
426                            smallImage, article.getSmallImageId(), smallImageFile,
427                            smallImageBytes);
428    
429                    // Asset
430    
431                    updateAsset(
432                            userId, article, serviceContext.getAssetCategoryIds(),
433                            serviceContext.getAssetTagNames(),
434                            serviceContext.getAssetLinkEntryIds());
435    
436                    // Dynamic data mapping
437    
438                    if (classNameLocalService.getClassNameId(DDMStructure.class) ==
439                                    classNameId) {
440    
441                            updateDDMStructurePredefinedValues(
442                                    classPK, content, serviceContext);
443                    }
444    
445                    // Message boards
446    
447                    if (PropsValues.JOURNAL_ARTICLE_COMMENTS_ENABLED) {
448                            mbMessageLocalService.addDiscussionMessage(
449                                    userId, article.getUserName(), groupId,
450                                    JournalArticle.class.getName(), resourcePrimKey,
451                                    WorkflowConstants.ACTION_PUBLISH);
452                    }
453    
454                    // Email
455    
456                    PortletPreferences preferences =
457                            ServiceContextUtil.getPortletPreferences(serviceContext);
458    
459                    articleURL = buildArticleURL(articleURL, groupId, folderId, articleId);
460    
461                    serviceContext.setAttribute("articleURL", articleURL);
462    
463                    sendEmail(
464                            article, articleURL, preferences, "requested", serviceContext);
465    
466                    // Workflow
467    
468                    if (classNameId == JournalArticleConstants.CLASSNAME_ID_DEFAULT) {
469                            startWorkflowInstance(userId, article, serviceContext);
470                    }
471                    else {
472                            updateStatus(
473                                    userId, article, WorkflowConstants.STATUS_APPROVED, null,
474                                    serviceContext, new HashMap<String, Serializable>());
475                    }
476    
477                    return journalArticlePersistence.findByPrimaryKey(article.getId());
478            }
479    
480            /**
481             * Adds a web content article.
482             *
483             * @param  userId the primary key of the web content article's creator/owner
484             * @param  groupId the primary key of the web content article's group
485             * @param  folderId the primary key of the web content article folder
486             * @param  titleMap the web content article's locales and localized titles
487             * @param  descriptionMap the web content article's locales and localized
488             *         descriptions
489             * @param  content the HTML content wrapped in XML. For more information,
490             *         see the content example in the class description for {@link
491             *         JournalArticleLocalServiceImpl}.
492             * @param  ddmStructureKey the primary key of the web content article's DDM
493             *         structure, if the article is related to a DDM structure, or
494             *         <code>null</code> otherwise
495             * @param  ddmTemplateKey the primary key of the web content article's DDM
496             *         template
497             * @param  serviceContext the service context to be applied. Can set the
498             *         UUID, creation date, modification date, expando bridge
499             *         attributes, guest permissions, group permissions, asset category
500             *         IDs, asset tag names, asset link entry IDs, the "urlTitle"
501             *         attribute, and workflow actions for the web content article. Can
502             *         also set whether to add the default guest and group permissions.
503             * @return the web content article
504             * @throws PortalException if a portal exception occurred
505             */
506            @Override
507            public JournalArticle addArticle(
508                            long userId, long groupId, long folderId,
509                            Map<Locale, String> titleMap, Map<Locale, String> descriptionMap,
510                            String content, String ddmStructureKey, String ddmTemplateKey,
511                            ServiceContext serviceContext)
512                    throws PortalException {
513    
514                    User user = userPersistence.findByPrimaryKey(userId);
515    
516                    Calendar calendar = CalendarFactoryUtil.getCalendar(user.getTimeZone());
517    
518                    int displayDateMonth = calendar.get(Calendar.MONTH);
519                    int displayDateDay = calendar.get(Calendar.DAY_OF_MONTH);
520                    int displayDateYear = calendar.get(Calendar.YEAR);
521                    int displayDateHour = calendar.get(Calendar.HOUR_OF_DAY);
522                    int displayDateMinute = calendar.get(Calendar.MINUTE);
523    
524                    return journalArticleLocalService.addArticle(
525                            userId, groupId, folderId,
526                            JournalArticleConstants.CLASSNAME_ID_DEFAULT, 0, StringPool.BLANK,
527                            true, 1, titleMap, descriptionMap, content, ddmStructureKey,
528                            ddmTemplateKey, null, displayDateMonth, displayDateDay,
529                            displayDateYear, displayDateHour, displayDateMinute, 0, 0, 0, 0, 0,
530                            true, 0, 0, 0, 0, 0, true, true, false, null, null, null, null,
531                            serviceContext);
532            }
533    
534            /**
535             * Adds the resources to the web content article.
536             *
537             * @param  article the web content article
538             * @param  addGroupPermissions whether to add group permissions
539             * @param  addGuestPermissions whether to add guest permissions
540             * @throws PortalException if no portal actions could be found associated
541             *         with the web content article or if a portal exception occurred
542             */
543            @Override
544            public void addArticleResources(
545                            JournalArticle article, boolean addGroupPermissions,
546                            boolean addGuestPermissions)
547                    throws PortalException {
548    
549                    resourceLocalService.addResources(
550                            article.getCompanyId(), article.getGroupId(), article.getUserId(),
551                            JournalArticle.class.getName(), article.getResourcePrimKey(), false,
552                            addGroupPermissions, addGuestPermissions);
553            }
554    
555            /**
556             * Adds the model resources with the permissions to the web content article.
557             *
558             * @param  article the web content article to add resources to
559             * @param  groupPermissions the group permissions to be added
560             * @param  guestPermissions the guest permissions to be added
561             * @throws PortalException if a portal exception occurred
562             */
563            @Override
564            public void addArticleResources(
565                            JournalArticle article, String[] groupPermissions,
566                            String[] guestPermissions)
567                    throws PortalException {
568    
569                    resourceLocalService.addModelResources(
570                            article.getCompanyId(), article.getGroupId(), article.getUserId(),
571                            JournalArticle.class.getName(), article.getResourcePrimKey(),
572                            groupPermissions, guestPermissions);
573            }
574    
575            /**
576             * Adds the resources to the most recently created web content article.
577             *
578             * @param  groupId the primary key of the web content article's group
579             * @param  articleId the primary key of the web content article
580             * @param  addGroupPermissions whether to add group permissions
581             * @param  addGuestPermissions whether to add guest permissions
582             * @throws PortalException if a portal exception occurred
583             */
584            @Override
585            public void addArticleResources(
586                            long groupId, String articleId, boolean addGroupPermissions,
587                            boolean addGuestPermissions)
588                    throws PortalException {
589    
590                    JournalArticle article = getLatestArticle(groupId, articleId);
591    
592                    addArticleResources(article, addGroupPermissions, addGuestPermissions);
593            }
594    
595            /**
596             * Adds the resources with the permissions to the most recently created web
597             * content article.
598             *
599             * @param  groupId the primary key of the web content article's group
600             * @param  articleId the primary key of the web content article
601             * @param  groupPermissions the group permissions to be added
602             * @param  guestPermissions the guest permissions to be added
603             * @throws PortalException if a portal exception occurred
604             */
605            @Override
606            public void addArticleResources(
607                            long groupId, String articleId, String[] groupPermissions,
608                            String[] guestPermissions)
609                    throws PortalException {
610    
611                    JournalArticle article = getLatestArticle(groupId, articleId);
612    
613                    addArticleResources(article, groupPermissions, guestPermissions);
614            }
615    
616            /**
617             * Returns the web content article with the group, article ID, and version.
618             * This method checks for the article's resource primary key and, if not
619             * found, creates a new one.
620             *
621             * @param  groupId the primary key of the web content article's group
622             * @param  articleId the primary key of the web content article
623             * @param  version the web content article's version
624             * @return the matching web content article
625             * @throws PortalException if a matching web content article could not be
626             *         found
627             */
628            @Override
629            public JournalArticle checkArticleResourcePrimKey(
630                            long groupId, String articleId, double version)
631                    throws PortalException {
632    
633                    JournalArticle article = journalArticlePersistence.findByG_A_V(
634                            groupId, articleId, version);
635    
636                    if (article.getResourcePrimKey() > 0) {
637                            return article;
638                    }
639    
640                    long resourcePrimKey =
641                            journalArticleResourceLocalService.getArticleResourcePrimKey(
642                                    groupId, articleId);
643    
644                    article.setResourcePrimKey(resourcePrimKey);
645    
646                    journalArticlePersistence.update(article);
647    
648                    return article;
649            }
650    
651            /**
652             * Checks all web content articles by handling their expirations and sending
653             * review notifications based on their current workflow.
654             *
655             * @throws PortalException if a portal exception occurred
656             */
657            @Override
658            public void checkArticles() throws PortalException {
659                    Date now = new Date();
660    
661                    checkArticlesByExpirationDate(now);
662    
663                    checkArticlesByReviewDate(now);
664    
665                    checkArticlesByDisplayDate(now);
666    
667                    _previousCheckDate = now;
668            }
669    
670            /**
671             * Checks the web content article matching the group, article ID, and
672             * version, replacing escaped newline and return characters with non-escaped
673             * newline and return characters.
674             *
675             * @param  groupId the primary key of the web content article's group
676             * @param  articleId the primary key of the web content article
677             * @param  version the web content article's version
678             * @throws PortalException if a matching web content article could not be
679             *         found
680             */
681            @Override
682            public void checkNewLine(long groupId, String articleId, double version)
683                    throws PortalException {
684    
685                    JournalArticle article = journalArticlePersistence.findByG_A_V(
686                            groupId, articleId, version);
687    
688                    String content = GetterUtil.getString(article.getContent());
689    
690                    if (content.contains("\\n")) {
691                            content = StringUtil.replace(
692                                    content, new String[] {"\\n", "\\r"},
693                                    new String[] {"\n", "\r"});
694    
695                            article.setContent(content);
696    
697                            journalArticlePersistence.update(article);
698                    }
699            }
700    
701            /**
702             * Checks the web content article matching the group, article ID, and
703             * version for an associated structure. If no structure is associated,
704             * return; otherwise check that the article and structure match.
705             *
706             * @param  groupId the primary key of the web content article's group
707             * @param  articleId the primary key of the web content article
708             * @param  version the web content article's version
709             * @throws PortalException if a matching web content article could not be
710             *         found, if the article's structure does not match it, or if a
711             *         portal exception occurred
712             */
713            @Override
714            public void checkStructure(long groupId, String articleId, double version)
715                    throws PortalException {
716    
717                    JournalArticle article = journalArticlePersistence.findByG_A_V(
718                            groupId, articleId, version);
719    
720                    checkStructure(article);
721            }
722    
723            /**
724             * Copies the web content article matching the group, article ID, and
725             * version. This method creates a new article, extracting all the values
726             * from the old one and updating its article ID.
727             *
728             * @param  userId the primary key of the web content article's creator/owner
729             * @param  groupId the primary key of the web content article's group
730             * @param  oldArticleId the primary key of the old web content article
731             * @param  newArticleId the primary key of the new web content article
732             * @param  autoArticleId whether to auto-generate the web content article ID
733             * @param  version the web content article's version
734             * @return the new web content article
735             * @throws PortalException if a matching web content article could not be
736             *         found or if a portal exception occurred
737             */
738            @Indexable(type = IndexableType.REINDEX)
739            @Override
740            public JournalArticle copyArticle(
741                            long userId, long groupId, String oldArticleId, String newArticleId,
742                            boolean autoArticleId, double version)
743                    throws PortalException {
744    
745                    // Article
746    
747                    User user = userPersistence.findByPrimaryKey(userId);
748                    oldArticleId = StringUtil.toUpperCase(oldArticleId.trim());
749                    newArticleId = StringUtil.toUpperCase(newArticleId.trim());
750                    Date now = new Date();
751    
752                    JournalArticle oldArticle = journalArticlePersistence.findByG_A_V(
753                            groupId, oldArticleId, version);
754    
755                    if (autoArticleId) {
756                            newArticleId = String.valueOf(counterLocalService.increment());
757                    }
758                    else {
759                            validate(newArticleId);
760    
761                            if (journalArticlePersistence.countByG_A(
762                                            groupId, newArticleId) > 0) {
763    
764                                    StringBundler sb = new StringBundler(5);
765    
766                                    sb.append("{groupId=");
767                                    sb.append(groupId);
768                                    sb.append(", articleId=");
769                                    sb.append(newArticleId);
770                                    sb.append("}");
771    
772                                    throw new DuplicateArticleIdException(sb.toString());
773                            }
774                    }
775    
776                    long id = counterLocalService.increment();
777    
778                    long resourcePrimKey =
779                            journalArticleResourceLocalService.getArticleResourcePrimKey(
780                                    groupId, newArticleId);
781    
782                    JournalArticle newArticle = journalArticlePersistence.create(id);
783    
784                    newArticle.setResourcePrimKey(resourcePrimKey);
785                    newArticle.setGroupId(groupId);
786                    newArticle.setCompanyId(user.getCompanyId());
787                    newArticle.setUserId(user.getUserId());
788                    newArticle.setUserName(user.getFullName());
789                    newArticle.setCreateDate(now);
790                    newArticle.setModifiedDate(now);
791                    newArticle.setFolderId(oldArticle.getFolderId());
792                    newArticle.setTreePath(oldArticle.getTreePath());
793                    newArticle.setArticleId(newArticleId);
794                    newArticle.setVersion(JournalArticleConstants.VERSION_DEFAULT);
795                    newArticle.setTitle(oldArticle.getTitle());
796                    newArticle.setUrlTitle(
797                            getUniqueUrlTitle(
798                                    id, groupId, newArticleId, oldArticle.getTitleCurrentValue()));
799                    newArticle.setDescription(oldArticle.getDescription());
800    
801                    try {
802                            copyArticleImages(oldArticle, newArticle);
803                    }
804                    catch (Exception e) {
805                            newArticle.setContent(oldArticle.getContent());
806                    }
807    
808                    newArticle.setDDMStructureKey(oldArticle.getDDMStructureKey());
809                    newArticle.setDDMTemplateKey(oldArticle.getDDMTemplateKey());
810                    newArticle.setLayoutUuid(oldArticle.getLayoutUuid());
811                    newArticle.setDisplayDate(oldArticle.getDisplayDate());
812                    newArticle.setExpirationDate(oldArticle.getExpirationDate());
813                    newArticle.setReviewDate(oldArticle.getReviewDate());
814                    newArticle.setIndexable(oldArticle.isIndexable());
815                    newArticle.setSmallImage(oldArticle.isSmallImage());
816                    newArticle.setSmallImageId(counterLocalService.increment());
817                    newArticle.setSmallImageURL(oldArticle.getSmallImageURL());
818    
819                    if (oldArticle.isPending() ||
820                            workflowDefinitionLinkLocalService.hasWorkflowDefinitionLink(
821                                    user.getCompanyId(), groupId, JournalArticle.class.getName())) {
822    
823                            newArticle.setStatus(WorkflowConstants.STATUS_DRAFT);
824                    }
825                    else {
826                            newArticle.setStatus(oldArticle.getStatus());
827                    }
828    
829                    newArticle.setExpandoBridgeAttributes(oldArticle);
830    
831                    journalArticlePersistence.update(newArticle);
832    
833                    // Resources
834    
835                    addArticleResources(newArticle, true, true);
836    
837                    // Small image
838    
839                    if (oldArticle.isSmallImage()) {
840                            Image image = imageLocalService.fetchImage(
841                                    oldArticle.getSmallImageId());
842    
843                            if (image != null) {
844                                    byte[] smallImageBytes = image.getTextObj();
845    
846                                    imageLocalService.updateImage(
847                                            newArticle.getSmallImageId(), smallImageBytes);
848                            }
849                    }
850    
851                    // Asset
852    
853                    long[] assetCategoryIds = assetCategoryLocalService.getCategoryIds(
854                            JournalArticle.class.getName(), oldArticle.getResourcePrimKey());
855                    String[] assetTagNames = assetTagLocalService.getTagNames(
856                            JournalArticle.class.getName(), oldArticle.getResourcePrimKey());
857    
858                    AssetEntry oldAssetEntry = assetEntryLocalService.getEntry(
859                            JournalArticle.class.getName(), oldArticle.getResourcePrimKey());
860    
861                    List<AssetLink> assetLinks = assetLinkLocalService.getDirectLinks(
862                            oldAssetEntry.getEntryId());
863    
864                    long[] assetLinkEntryIds = ListUtil.toLongArray(
865                            assetLinks, AssetLink.ENTRY_ID2_ACCESSOR);
866    
867                    updateAsset(
868                            userId, newArticle, assetCategoryIds, assetTagNames,
869                            assetLinkEntryIds);
870    
871                    return newArticle;
872            }
873    
874            /**
875             * Deletes the web content article and its resources.
876             *
877             * @param  article the web content article
878             * @return the deleted web content article
879             * @throws PortalException if a portal exception occurred
880             */
881            @Override
882            @SystemEvent(
883                    action = SystemEventConstants.ACTION_SKIP, send = false,
884                    type = SystemEventConstants.TYPE_DELETE)
885            public JournalArticle deleteArticle(JournalArticle article)
886                    throws PortalException {
887    
888                    return journalArticleLocalService.deleteArticle(
889                            article, StringPool.BLANK, null);
890            }
891    
892            /**
893             * Deletes the web content article and its resources, optionally sending
894             * email notifying denial of the article if it had not yet been approved.
895             *
896             * @param  article the web content article
897             * @param  articleURL the web content article's accessible URL to include in
898             *         email notifications (optionally <code>null</code>)
899             * @param  serviceContext the service context to be applied (optionally
900             *         <code>null</code>). Can set the portlet preferences that include
901             *         email information to notify recipients of the unapproved web
902             *         content's denial.
903             * @return the deleted web content article
904             * @throws PortalException if a portal exception occurred
905             */
906            @Indexable(type = IndexableType.DELETE)
907            @Override
908            @SystemEvent(
909                    action = SystemEventConstants.ACTION_SKIP, send = false,
910                    type = SystemEventConstants.TYPE_DELETE)
911            public JournalArticle deleteArticle(
912                            JournalArticle article, String articleURL,
913                            ServiceContext serviceContext)
914                    throws PortalException {
915    
916                    JournalArticleResource articleResource =
917                            journalArticleResourceLocalService.fetchArticleResource(
918                                    article.getGroupId(), article.getArticleId());
919    
920                    if (article.isApproved() &&
921                            isLatestVersion(
922                                    article.getGroupId(), article.getArticleId(),
923                                    article.getVersion(), WorkflowConstants.STATUS_APPROVED)) {
924    
925                            updatePreviousApprovedArticle(article);
926                    }
927    
928                    // Email
929    
930                    if ((serviceContext != null) && Validator.isNotNull(articleURL)) {
931                            PortletPreferences preferences =
932                                    ServiceContextUtil.getPortletPreferences(serviceContext);
933    
934                            if ((preferences != null) && !article.isApproved() &&
935                                    isLatestVersion(
936                                            article.getGroupId(), article.getArticleId(),
937                                            article.getVersion())) {
938    
939                                    articleURL = buildArticleURL(
940                                            articleURL, article.getGroupId(), article.getFolderId(),
941                                            article.getArticleId());
942    
943                                    sendEmail(
944                                            article, articleURL, preferences, "denied", serviceContext);
945                            }
946                    }
947    
948                    // Images
949    
950                    String articleId = article.getArticleId();
951    
952                    if (article.isInTrash()) {
953                            articleId = TrashUtil.getOriginalTitle(article.getArticleId());
954                    }
955    
956                    journalArticleImageLocalService.deleteImages(
957                            article.getGroupId(), articleId, article.getVersion());
958    
959                    // Expando
960    
961                    expandoRowLocalService.deleteRows(article.getId());
962    
963                    // Trash
964    
965                    if (article.isInTrash()) {
966                            TrashEntry trashEntry = article.getTrashEntry();
967    
968                            if (trashEntry != null) {
969                                    trashVersionLocalService.deleteTrashVersion(
970                                            JournalArticle.class.getName(), article.getId());
971                            }
972                    }
973    
974                    // Workflow
975    
976                    if (!article.isDraft()) {
977                            workflowInstanceLinkLocalService.deleteWorkflowInstanceLink(
978                                    article.getCompanyId(), article.getGroupId(),
979                                    JournalArticle.class.getName(), article.getId());
980                    }
981    
982                    int articlesCount = journalArticlePersistence.countByG_A(
983                            article.getGroupId(), article.getArticleId());
984    
985                    if (articlesCount == 1) {
986    
987                            // Ratings
988    
989                            ratingsStatsLocalService.deleteStats(
990                                    JournalArticle.class.getName(), article.getResourcePrimKey());
991    
992                            // Message boards
993    
994                            mbMessageLocalService.deleteDiscussionMessages(
995                                    JournalArticle.class.getName(), article.getResourcePrimKey());
996    
997                            // Asset
998    
999                            assetEntryLocalService.deleteEntry(
1000                                    JournalArticle.class.getName(), article.getResourcePrimKey());
1001    
1002                            // Content searches
1003    
1004                            journalContentSearchLocalService.deleteArticleContentSearches(
1005                                    article.getGroupId(), article.getArticleId());
1006    
1007                            // Small image
1008    
1009                            imageLocalService.deleteImage(article.getSmallImageId());
1010    
1011                            // Trash
1012    
1013                            trashEntryLocalService.deleteEntry(
1014                                    JournalArticle.class.getName(), article.getResourcePrimKey());
1015    
1016                            // Resources
1017    
1018                            resourceLocalService.deleteResource(
1019                                    article.getCompanyId(), JournalArticle.class.getName(),
1020                                    ResourceConstants.SCOPE_INDIVIDUAL,
1021                                    article.getResourcePrimKey());
1022    
1023                            // Resource
1024    
1025                            if (articleResource != null) {
1026                                    journalArticleResourceLocalService.deleteJournalArticleResource(
1027                                            articleResource);
1028                            }
1029                    }
1030    
1031                    // Article
1032    
1033                    journalArticlePersistence.remove(article);
1034    
1035                    // System event
1036    
1037                    if (articleResource != null) {
1038                            JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();
1039    
1040                            extraDataJSONObject.put("uuid", article.getUuid());
1041                            extraDataJSONObject.put("version", article.getVersion());
1042    
1043                            systemEventLocalService.addSystemEvent(
1044                                    0, article.getGroupId(), article.getModelClassName(),
1045                                    article.getPrimaryKey(), articleResource.getUuid(), null,
1046                                    SystemEventConstants.TYPE_DELETE,
1047                                    extraDataJSONObject.toString());
1048                    }
1049    
1050                    return article;
1051            }
1052    
1053            /**
1054             * Deletes the web content article and its resources matching the group,
1055             * article ID, and version, optionally sending email notifying denial of the
1056             * web content article if it had not yet been approved.
1057             *
1058             * @param  groupId the primary key of the web content article's group
1059             * @param  articleId the primary key of the web content article
1060             * @param  version the web content article's version
1061             * @param  articleURL the web content article's accessible URL
1062             * @param  serviceContext the service context to be applied. Can set the
1063             *         portlet preferences that include email information to notify
1064             *         recipients of the unapproved web content article's denial.
1065             * @return the deleted web content article
1066             * @throws PortalException if a matching web content article could not be
1067             *         found or if a portal exception occurred
1068             */
1069            @Override
1070            public JournalArticle deleteArticle(
1071                            long groupId, String articleId, double version, String articleURL,
1072                            ServiceContext serviceContext)
1073                    throws PortalException {
1074    
1075                    JournalArticle article = journalArticlePersistence.findByG_A_V(
1076                            groupId, articleId, version);
1077    
1078                    return journalArticleLocalService.deleteArticle(
1079                            article, articleURL, serviceContext);
1080            }
1081    
1082            /**
1083             * Deletes all web content articles and their resources matching the group
1084             * and article ID, optionally sending email notifying denial of article if
1085             * it had not yet been approved.
1086             *
1087             * @param  groupId the primary key of the web content article's group
1088             * @param  articleId the primary key of the web content article
1089             * @param  serviceContext the service context to be applied. Can set the
1090             *         portlet preferences that include email information to notify
1091             *         recipients of the unapproved web content article's denial.
1092             * @throws PortalException if a portal exception occurred
1093             */
1094            @Override
1095            public void deleteArticle(
1096                            long groupId, String articleId, ServiceContext serviceContext)
1097                    throws PortalException {
1098    
1099                    SystemEventHierarchyEntryThreadLocal.push(JournalArticle.class);
1100    
1101                    JournalArticleResource articleResource =
1102                            journalArticleResourceLocalService.fetchArticleResource(
1103                                    groupId, articleId);
1104    
1105                    try {
1106                            List<JournalArticle> articles = journalArticlePersistence.findByG_A(
1107                                    groupId, articleId, QueryUtil.ALL_POS, QueryUtil.ALL_POS,
1108                                    new ArticleVersionComparator(true));
1109    
1110                            for (JournalArticle article : articles) {
1111                                    journalArticleLocalService.deleteArticle(
1112                                            article, null, serviceContext);
1113                            }
1114                    }
1115                    finally {
1116                            SystemEventHierarchyEntryThreadLocal.pop(JournalArticle.class);
1117                    }
1118    
1119                    if (articleResource != null) {
1120                            systemEventLocalService.addSystemEvent(
1121                                    0, groupId, JournalArticle.class.getName(),
1122                                    articleResource.getResourcePrimKey(), articleResource.getUuid(),
1123                                    null, SystemEventConstants.TYPE_DELETE, StringPool.BLANK);
1124                    }
1125            }
1126    
1127            /**
1128             * Deletes all the group's web content articles and resources.
1129             *
1130             * @param  groupId the primary key of the web content article's group
1131             * @throws PortalException if a portal exception occurred
1132             */
1133            @Override
1134            public void deleteArticles(long groupId) throws PortalException {
1135                    SystemEventHierarchyEntryThreadLocal.push(JournalArticle.class);
1136    
1137                    List<JournalArticleResource> articleResources =
1138                            new ArrayList<JournalArticleResource>();
1139    
1140                    try {
1141                            JournalArticleResource articleResource = null;
1142    
1143                            for (JournalArticle article :
1144                                            journalArticlePersistence.findByGroupId(groupId)) {
1145    
1146                                    if ((articleResource == null) ||
1147                                            (articleResource.getPrimaryKey() !=
1148                                                    article.getResourcePrimKey())) {
1149    
1150                                            articleResource =
1151                                                    journalArticleResourceLocalService.getArticleResource(
1152                                                            article.getResourcePrimKey());
1153    
1154                                            articleResources.add(articleResource);
1155                                    }
1156    
1157                                    journalArticleLocalService.deleteArticle(article, null, null);
1158                            }
1159                    }
1160                    finally {
1161                            SystemEventHierarchyEntryThreadLocal.pop(JournalArticle.class);
1162                    }
1163    
1164                    for (JournalArticleResource articleResource : articleResources) {
1165                            systemEventLocalService.addSystemEvent(
1166                                    0, groupId, JournalArticle.class.getName(),
1167                                    articleResource.getResourcePrimKey(), articleResource.getUuid(),
1168                                    null, SystemEventConstants.TYPE_DELETE, StringPool.BLANK);
1169                    }
1170            }
1171    
1172            /**
1173             * Deletes all the group's web content articles and resources in the folder,
1174             * including recycled articles.
1175             *
1176             * @param  groupId the primary key of the web content article's group
1177             * @param  folderId the primary key of the web content article folder
1178             * @throws PortalException if a portal exception occurred
1179             */
1180            @Override
1181            public void deleteArticles(long groupId, long folderId)
1182                    throws PortalException {
1183    
1184                    deleteArticles(groupId, folderId, true);
1185            }
1186    
1187            /**
1188             * Deletes all the group's web content articles and resources in the folder,
1189             * optionally including recycled articles.
1190             *
1191             * @param  groupId the primary key of the web content article's group
1192             * @param  folderId the primary key of the web content article folder
1193             * @param  includeTrashedEntries whether to include recycled web content
1194             *         articles
1195             * @throws PortalException if a portal exception occurred
1196             */
1197            @Override
1198            public void deleteArticles(
1199                            long groupId, long folderId, boolean includeTrashedEntries)
1200                    throws PortalException {
1201    
1202                    SystemEventHierarchyEntryThreadLocal.push(JournalArticle.class);
1203    
1204                    List<JournalArticleResource> articleResources =
1205                            new ArrayList<JournalArticleResource>();
1206    
1207                    try {
1208                            JournalArticleResource articleResource = null;
1209    
1210                            for (JournalArticle article :
1211                                            journalArticlePersistence.findByG_F(groupId, folderId)) {
1212    
1213                                    if ((articleResource == null) ||
1214                                            (articleResource.getPrimaryKey() !=
1215                                                    article.getResourcePrimKey())) {
1216    
1217                                            articleResource =
1218                                                    journalArticleResourceLocalService.getArticleResource(
1219                                                            article.getResourcePrimKey());
1220    
1221                                            articleResources.add(articleResource);
1222                                    }
1223    
1224                                    if (includeTrashedEntries || !article.isInTrashExplicitly()) {
1225                                            journalArticleLocalService.deleteArticle(
1226                                                    article, null, null);
1227                                    }
1228                                    else {
1229                                            articleResources.remove(articleResource);
1230                                    }
1231                            }
1232                    }
1233                    finally {
1234                            SystemEventHierarchyEntryThreadLocal.pop(JournalArticle.class);
1235                    }
1236    
1237                    for (JournalArticleResource articleResource : articleResources) {
1238                            systemEventLocalService.addSystemEvent(
1239                                    0, groupId, JournalArticle.class.getName(),
1240                                    articleResource.getResourcePrimKey(), articleResource.getUuid(),
1241                                    null, SystemEventConstants.TYPE_DELETE, StringPool.BLANK);
1242                    }
1243            }
1244    
1245            /**
1246             * Deletes the layout's association with the web content articles for the
1247             * group.
1248             *
1249             * @param groupId the primary key of the web content article's group
1250             * @param layoutUuid the unique string identifying the web content article's
1251             *        display page
1252             */
1253            @Override
1254            public void deleteLayoutArticleReferences(long groupId, String layoutUuid) {
1255                    List<JournalArticle> articles = journalArticlePersistence.findByG_L(
1256                            groupId, layoutUuid);
1257    
1258                    for (JournalArticle article : articles) {
1259                            article.setLayoutUuid(StringPool.BLANK);
1260    
1261                            journalArticlePersistence.update(article);
1262                    }
1263            }
1264    
1265            /**
1266             * Expires the web content article matching the group, article ID, and
1267             * version.
1268             *
1269             * @param  userId the primary key of the user updating the web content
1270             *         article
1271             * @param  groupId the primary key of the web content article's group
1272             * @param  articleId the primary key of the web content article
1273             * @param  version the web content article's version
1274             * @param  articleURL the web content article's accessible URL
1275             * @param  serviceContext the service context to be applied. Can set the
1276             *         modification date, status date, portlet preferences, and can set
1277             *         whether to add the default command update for the web content
1278             *         article. With respect to social activities, by setting the
1279             *         service context's command to {@link
1280             *         com.liferay.portal.kernel.util.Constants#UPDATE}, the invocation
1281             *         is considered a web content update activity; otherwise it is
1282             *         considered a web content add activity.
1283             * @return the web content article
1284             * @throws PortalException if a matching web content article could not be
1285             *         found or if a portal exception occurred
1286             */
1287            @Indexable(type = IndexableType.REINDEX)
1288            @Override
1289            public JournalArticle expireArticle(
1290                            long userId, long groupId, String articleId, double version,
1291                            String articleURL, ServiceContext serviceContext)
1292                    throws PortalException {
1293    
1294                    return updateStatus(
1295                            userId, groupId, articleId, version,
1296                            WorkflowConstants.STATUS_EXPIRED, articleURL,
1297                            new HashMap<String, Serializable>(), serviceContext);
1298            }
1299    
1300            /**
1301             * Expires the web content article matching the group and article ID,
1302             * expiring all of its versions if the
1303             * <code>journal.article.expire.all.versions</code> portal property is
1304             * <code>true</code>, otherwise expiring only its latest approved version.
1305             *
1306             * @param  userId the primary key of the user updating the web content
1307             *         article
1308             * @param  groupId the primary key of the web content article's group
1309             * @param  articleId the primary key of the web content article
1310             * @param  articleURL the web content article's accessible URL
1311             * @param  serviceContext the service context to be applied. Can set the
1312             *         modification date, status date, portlet preferences, and can set
1313             *         whether to add the default command update for the web content
1314             *         article. With respect to social activities, by setting the
1315             *         service context's command to {@link
1316             *         com.liferay.portal.kernel.util.Constants#UPDATE}, the invocation
1317             *         is considered a web content update activity; otherwise it is
1318             *         considered a web content add activity.
1319             * @throws PortalException if a matching web content article could not be
1320             *         found or if a portal exception occurred
1321             */
1322            @Override
1323            public void expireArticle(
1324                            long userId, long groupId, String articleId, String articleURL,
1325                            ServiceContext serviceContext)
1326                    throws PortalException {
1327    
1328                    if (PropsValues.JOURNAL_ARTICLE_EXPIRE_ALL_VERSIONS) {
1329                            List<JournalArticle> articles = journalArticlePersistence.findByG_A(
1330                                    groupId, articleId, QueryUtil.ALL_POS, QueryUtil.ALL_POS,
1331                                    new ArticleVersionComparator(true));
1332    
1333                            for (JournalArticle article : articles) {
1334                                    journalArticleLocalService.expireArticle(
1335                                            userId, groupId, article.getArticleId(),
1336                                            article.getVersion(), articleURL, serviceContext);
1337                            }
1338                    }
1339                    else {
1340                            JournalArticle article = getLatestArticle(
1341                                    groupId, articleId, WorkflowConstants.STATUS_APPROVED);
1342    
1343                            journalArticleLocalService.expireArticle(
1344                                    userId, groupId, article.getArticleId(), article.getVersion(),
1345                                    articleURL, serviceContext);
1346                    }
1347            }
1348    
1349            /**
1350             * Returns the web content article matching the group, article ID, and
1351             * version.
1352             *
1353             * @param  groupId the primary key of the web content article's group
1354             * @param  articleId the primary key of the web content article
1355             * @param  version the web content article's version
1356             * @return the web content article matching the group, article ID, and
1357             *         version, or <code>null</code> if no web content article could be
1358             *         found
1359             */
1360            @Override
1361            public JournalArticle fetchArticle(
1362                    long groupId, String articleId, double version) {
1363    
1364                    return journalArticlePersistence.fetchByG_A_V(
1365                            groupId, articleId, version);
1366            }
1367    
1368            @Override
1369            public JournalArticle fetchLatestArticle(long resourcePrimKey) {
1370                    return fetchLatestArticle(
1371                            resourcePrimKey, WorkflowConstants.STATUS_ANY);
1372            }
1373    
1374            @Override
1375            public JournalArticle fetchLatestArticle(long resourcePrimKey, int status) {
1376                    return fetchLatestArticle(resourcePrimKey, status, true);
1377            }
1378    
1379            /**
1380             * Returns the latest web content article matching the resource primary key
1381             * and workflow status, optionally preferring articles with approved
1382             * workflow status.
1383             *
1384             * @param  resourcePrimKey the primary key of the resource instance
1385             * @param  status the web content article's workflow status. For more
1386             *         information see {@link WorkflowConstants} for constants starting
1387             *         with the "STATUS_" prefix.
1388             * @param  preferApproved whether to prefer returning the latest matching
1389             *         article that has workflow status {@link
1390             *         WorkflowConstants#STATUS_APPROVED} over returning one that has a
1391             *         different status
1392             * @return the latest web content article matching the resource primary key
1393             *         and workflow status, optionally preferring articles with an
1394             *         approved workflow status, or <code>null</code> if no matching web
1395             *         content article could be found
1396             */
1397            @Override
1398            public JournalArticle fetchLatestArticle(
1399                    long resourcePrimKey, int status, boolean preferApproved) {
1400    
1401                    JournalArticle article = null;
1402    
1403                    OrderByComparator<JournalArticle> orderByComparator =
1404                            new ArticleVersionComparator();
1405    
1406                    if (status == WorkflowConstants.STATUS_ANY) {
1407                            if (preferApproved) {
1408                                    article = journalArticlePersistence.fetchByR_ST_First(
1409                                            resourcePrimKey, WorkflowConstants.STATUS_APPROVED,
1410                                            orderByComparator);
1411                            }
1412    
1413                            if (article == null) {
1414                                    article =
1415                                            journalArticlePersistence.fetchByResourcePrimKey_First(
1416                                                    resourcePrimKey, orderByComparator);
1417                            }
1418                    }
1419                    else {
1420                            article = journalArticlePersistence.fetchByR_ST_First(
1421                                    resourcePrimKey, status, orderByComparator);
1422                    }
1423    
1424                    return article;
1425            }
1426    
1427            @Override
1428            public JournalArticle fetchLatestArticle(
1429                    long resourcePrimKey, int[] statuses) {
1430    
1431                    OrderByComparator<JournalArticle> orderByComparator =
1432                            new ArticleVersionComparator();
1433    
1434                    List<JournalArticle> articles = journalArticlePersistence.findByR_ST(
1435                            resourcePrimKey, statuses, 0, 1, orderByComparator);
1436    
1437                    if (!articles.isEmpty()) {
1438                            return articles.get(0);
1439                    }
1440    
1441                    return null;
1442            }
1443    
1444            /**
1445             * Returns the latest web content article matching the group, article ID,
1446             * and workflow status.
1447             *
1448             * @param  groupId the primary key of the web content article's group
1449             * @param  articleId the primary key of the web content article
1450             * @param  status the web content article's workflow status. For more
1451             *         information see {@link WorkflowConstants} for constants starting
1452             *         with the "STATUS_" prefix.
1453             * @return the latest matching web content article, or <code>null</code> if
1454             *         no matching web content article could be found
1455             */
1456            @Override
1457            public JournalArticle fetchLatestArticle(
1458                    long groupId, String articleId, int status) {
1459    
1460                    OrderByComparator<JournalArticle> orderByComparator =
1461                            new ArticleVersionComparator();
1462    
1463                    if (status == WorkflowConstants.STATUS_ANY) {
1464                            return journalArticlePersistence.fetchByG_A_NotST_First(
1465                                    groupId, articleId, WorkflowConstants.STATUS_IN_TRASH,
1466                                    orderByComparator);
1467                    }
1468    
1469                    return journalArticlePersistence.fetchByG_A_ST_First(
1470                            groupId, articleId, status, orderByComparator);
1471            }
1472    
1473            /**
1474             * Returns the latest indexable web content article matching the resource
1475             * primary key.
1476             *
1477             * @param  resourcePrimKey the primary key of the resource instance
1478             * @return the latest indexable web content article matching the resource
1479             *         primary key, or <code>null</code> if no matching web content
1480             *         article could be found
1481             */
1482            @Override
1483            public JournalArticle fetchLatestIndexableArticle(long resourcePrimKey) {
1484                    OrderByComparator<JournalArticle> orderByComparator =
1485                            new ArticleVersionComparator();
1486    
1487                    int[] statuses = new int[] {
1488                            WorkflowConstants.STATUS_APPROVED, WorkflowConstants.STATUS_IN_TRASH
1489                    };
1490    
1491                    List<JournalArticle> articles = journalArticlePersistence.findByR_I_S(
1492                            resourcePrimKey, true, statuses, 0, 1, orderByComparator);
1493    
1494                    if (articles.isEmpty()) {
1495                            return null;
1496                    }
1497    
1498                    return articles.get(0);
1499            }
1500    
1501            /**
1502             * Returns the web content article with the ID.
1503             *
1504             * @param  id the primary key of the web content article
1505             * @return the web content article with the ID
1506             * @throws PortalException if a matching web content article could not be
1507             *         found
1508             */
1509            @Override
1510            public JournalArticle getArticle(long id) throws PortalException {
1511                    return journalArticlePersistence.findByPrimaryKey(id);
1512            }
1513    
1514            /**
1515             * Returns the latest approved web content article, or the latest unapproved
1516             * article if none are approved. Both approved and unapproved articles must
1517             * match the group and article ID.
1518             *
1519             * @param  groupId the primary key of the web content article's group
1520             * @param  articleId the primary key of the web content article
1521             * @return the matching web content article
1522             * @throws PortalException if a matching web content article could not be
1523             *         found
1524             */
1525            @Override
1526            public JournalArticle getArticle(long groupId, String articleId)
1527                    throws PortalException {
1528    
1529                    // Get the latest article that is approved, if none are approved, get
1530                    // the latest unapproved article
1531    
1532                    try {
1533                            return getLatestArticle(
1534                                    groupId, articleId, WorkflowConstants.STATUS_APPROVED);
1535                    }
1536                    catch (NoSuchArticleException nsae) {
1537                            return getLatestArticle(
1538                                    groupId, articleId, WorkflowConstants.STATUS_ANY);
1539                    }
1540            }
1541    
1542            /**
1543             * Returns the web content article matching the group, article ID, and
1544             * version.
1545             *
1546             * @param  groupId the primary key of the web content article's group
1547             * @param  articleId the primary key of the web content article
1548             * @param  version the web content article's version
1549             * @return the matching web content article
1550             * @throws PortalException if a matching web content article could not be
1551             *         found
1552             */
1553            @Override
1554            public JournalArticle getArticle(
1555                            long groupId, String articleId, double version)
1556                    throws PortalException {
1557    
1558                    return journalArticlePersistence.findByG_A_V(
1559                            groupId, articleId, version);
1560            }
1561    
1562            /**
1563             * Returns the web content article matching the group, class name, and class
1564             * PK.
1565             *
1566             * @param  groupId the primary key of the web content article's group
1567             * @param  className the DDMStructure class name if the web content article
1568             *         is related to a DDM structure, the primary key of the class name
1569             *         associated with the article, or {@link
1570             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
1571             * @param  classPK the primary key of the DDM structure, if the the
1572             *         DDMStructure class name is given as the <code>className</code>
1573             *         parameter, the primary key of the class associated with the web
1574             *         content article, or <code>0</code> otherwise
1575             * @return the matching web content article
1576             * @throws PortalException if a matching web content article could not be
1577             *         found
1578             */
1579            @Override
1580            public JournalArticle getArticle(
1581                            long groupId, String className, long classPK)
1582                    throws PortalException {
1583    
1584                    long classNameId = classNameLocalService.getClassNameId(className);
1585    
1586                    List<JournalArticle> articles = journalArticlePersistence.findByG_C_C(
1587                            groupId, classNameId, classPK);
1588    
1589                    if (articles.isEmpty()) {
1590                            throw new NoSuchArticleException(
1591                                    "No approved JournalArticle exists with the key {groupId=" +
1592                                            groupId + ", className=" + className + ", classPK=" +
1593                                                    classPK + "}");
1594                    }
1595    
1596                    return articles.get(0);
1597            }
1598    
1599            /**
1600             * Returns the latest web content article that is approved, or the latest
1601             * unapproved article if none are approved. Both approved and unapproved
1602             * articles must match the group and URL title.
1603             *
1604             * @param  groupId the primary key of the web content article's group
1605             * @param  urlTitle the web content article's accessible URL title
1606             * @return the matching web content article
1607             * @throws PortalException if a portal exception occurred
1608             */
1609            @Override
1610            public JournalArticle getArticleByUrlTitle(long groupId, String urlTitle)
1611                    throws PortalException {
1612    
1613                    // Get the latest article that is approved, if none are approved, get
1614                    // the latest unapproved article
1615    
1616                    try {
1617                            return getLatestArticleByUrlTitle(
1618                                    groupId, urlTitle, WorkflowConstants.STATUS_APPROVED);
1619                    }
1620                    catch (NoSuchArticleException nsae) {
1621                            return getLatestArticleByUrlTitle(
1622                                    groupId, urlTitle, WorkflowConstants.STATUS_PENDING);
1623                    }
1624            }
1625    
1626            /**
1627             * Returns the web content from the web content article associated with the
1628             * portlet request model and the DDM template.
1629             *
1630             * @param  article the web content article
1631             * @param  ddmTemplateKey the primary key of the web content article's DDM
1632             *         template
1633             * @param  viewMode the mode in which the web content is being viewed
1634             * @param  languageId the primary key of the language translation to get
1635             * @param  portletRequestModel the portlet request model
1636             * @param  themeDisplay the theme display
1637             * @return the web content from the web content article associated with the
1638             *         portlet request model and the DDM template
1639             * @throws PortalException if a matching DDM template could not be found, or
1640             *         if a portal exception occurred
1641             */
1642            @Override
1643            public String getArticleContent(
1644                            JournalArticle article, String ddmTemplateKey, String viewMode,
1645                            String languageId, PortletRequestModel portletRequestModel,
1646                            ThemeDisplay themeDisplay)
1647                    throws PortalException {
1648    
1649                    JournalArticleDisplay articleDisplay = getArticleDisplay(
1650                            article, ddmTemplateKey, viewMode, languageId, 1,
1651                            portletRequestModel, themeDisplay);
1652    
1653                    if (articleDisplay == null) {
1654                            return StringPool.BLANK;
1655                    }
1656                    else {
1657                            return articleDisplay.getContent();
1658                    }
1659            }
1660    
1661            /**
1662             * Returns the web content from the web content article associated with the
1663             * DDM template.
1664             *
1665             * @param      article the web content article
1666             * @param      ddmTemplateKey the primary key of the web content article's
1667             *             DDM template
1668             * @param      viewMode the mode in which the web content is being viewed
1669             * @param      languageId the primary key of the language translation to get
1670             * @param      themeDisplay the theme display
1671             * @return     the web content from the matching web content article
1672             * @throws     PortalException if a matching DDM template could not be
1673             *             found, or if a portal exception occurred
1674             * @deprecated As of 7.0.0, replaced by {@link
1675             *             #getArticleContent(JournalArticle, String, String, String,
1676             *             PortletRequestModel,ThemeDisplay)}
1677             */
1678            @Deprecated
1679            @Override
1680            public String getArticleContent(
1681                            JournalArticle article, String ddmTemplateKey, String viewMode,
1682                            String languageId, ThemeDisplay themeDisplay)
1683                    throws PortalException {
1684    
1685                    return getArticleContent(
1686                            article, ddmTemplateKey, viewMode, languageId, null, themeDisplay);
1687            }
1688    
1689            /**
1690             * Returns the web content from the web content article matching the group,
1691             * article ID, and version, and associated with the portlet request model
1692             * and the DDM template.
1693             *
1694             * @param  groupId the primary key of the web content article's group
1695             * @param  articleId the primary key of the web content article
1696             * @param  version the web content article's version
1697             * @param  viewMode the mode in which the web content is being viewed
1698             * @param  ddmTemplateKey the primary key of the web content article's DDM
1699             *         template
1700             * @param  languageId the primary key of the language translation to get
1701             * @param  portletRequestModel the portlet request model
1702             * @param  themeDisplay the theme display
1703             * @return the web content from the matching web content article
1704             * @throws PortalException if a matching web content article or DDM template
1705             *         could not be found, or if a portal exception occurred
1706             */
1707            @Override
1708            public String getArticleContent(
1709                            long groupId, String articleId, double version, String viewMode,
1710                            String ddmTemplateKey, String languageId,
1711                            PortletRequestModel portletRequestModel, ThemeDisplay themeDisplay)
1712                    throws PortalException {
1713    
1714                    JournalArticleDisplay articleDisplay = getArticleDisplay(
1715                            groupId, articleId, version, ddmTemplateKey, viewMode, languageId,
1716                            1, portletRequestModel, themeDisplay);
1717    
1718                    if (articleDisplay == null) {
1719                            return StringPool.BLANK;
1720                    }
1721                    else {
1722                            return articleDisplay.getContent();
1723                    }
1724            }
1725    
1726            /**
1727             * Returns the web content from the web content article matching the group,
1728             * article ID, and version, and associated with the DDM template.
1729             *
1730             * @param      groupId the primary key of the web content article's group
1731             * @param      articleId the primary key of the web content article
1732             * @param      version the web content article's version
1733             * @param      viewMode the mode in which the web content is being viewed
1734             * @param      ddmTemplateKey the primary key of the web content article's
1735             *             DDM template (optionally <code>null</code>). If the article
1736             *             is related to a DDM structure, the template's structure must
1737             *             match it.
1738             * @param      languageId the primary key of the language translation to get
1739             * @param      themeDisplay the theme display
1740             * @return     the web content from the matching web content article
1741             * @throws     PortalException if a matching web content article or DDM
1742             *             template could not be found, or if a portal exception
1743             *             occurred
1744             * @deprecated As of 7.0.0, replaced by {@link #getArticleContent(long,
1745             *             String, double, String, String, String, PortletRequestModel,
1746             *             ThemeDisplay)}
1747             */
1748            @Deprecated
1749            @Override
1750            public String getArticleContent(
1751                            long groupId, String articleId, double version, String viewMode,
1752                            String ddmTemplateKey, String languageId, ThemeDisplay themeDisplay)
1753                    throws PortalException {
1754    
1755                    return getArticleContent(
1756                            groupId, articleId, version, viewMode, ddmTemplateKey, languageId,
1757                            null, themeDisplay);
1758            }
1759    
1760            /**
1761             * Returns the web content from the web content article matching the group,
1762             * article ID, and version.
1763             *
1764             * @param      groupId the primary key of the web content article's group
1765             * @param      articleId the primary key of the web content article
1766             * @param      version the web content article's version
1767             * @param      viewMode the mode in which the web content is being viewed
1768             * @param      languageId the primary key of the language translation to get
1769             * @param      themeDisplay the theme display
1770             * @return     the web content from the matching web content article
1771             * @throws     PortalException if a matching web content article or DDM
1772             *             template could not be found, or if a portal exception
1773             *             occurred
1774             * @deprecated As of 7.0.0, replaced by {@link #getArticleContent(long,
1775             *             String, double, String, String, String, PortletRequestModel,
1776             *             ThemeDisplay)}
1777             */
1778            @Deprecated
1779            @Override
1780            public String getArticleContent(
1781                            long groupId, String articleId, double version, String viewMode,
1782                            String languageId, ThemeDisplay themeDisplay)
1783                    throws PortalException {
1784    
1785                    return getArticleContent(
1786                            groupId, articleId, version, viewMode, null, languageId, null,
1787                            themeDisplay);
1788            }
1789    
1790            /**
1791             * Returns the latest web content from the web content article matching the
1792             * group and article ID, and associated with the portlet request model and
1793             * the DDM template.
1794             *
1795             * @param  groupId the primary key of the web content article's group
1796             * @param  articleId the primary key of the web content article
1797             * @param  viewMode the mode in which the web content is being viewed
1798             * @param  ddmTemplateKey the primary key of the web content article's DDM
1799             *         template
1800             * @param  languageId the primary key of the language translation to get
1801             * @param  portletRequestModel the portlet request model
1802             * @param  themeDisplay the theme display
1803             * @return the latest web content from the matching web content article
1804             * @throws PortalException if a matching web content article or DDM template
1805             *         could not be found, or if a portal exception occurred
1806             */
1807            @Override
1808            public String getArticleContent(
1809                            long groupId, String articleId, String viewMode,
1810                            String ddmTemplateKey, String languageId,
1811                            PortletRequestModel portletRequestModel, ThemeDisplay themeDisplay)
1812                    throws PortalException {
1813    
1814                    JournalArticleDisplay articleDisplay = getArticleDisplay(
1815                            groupId, articleId, ddmTemplateKey, viewMode, languageId, 1,
1816                            portletRequestModel, themeDisplay);
1817    
1818                    return articleDisplay.getContent();
1819            }
1820    
1821            /**
1822             * Returns the latest web content from the web content article matching the
1823             * group and article ID, and associated with the DDM template.
1824             *
1825             * @param      groupId the primary key of the web content article's group
1826             * @param      articleId the primary key of the web content article
1827             * @param      viewMode the mode in which the web content is being viewed
1828             * @param      ddmTemplateKey the primary key of the web content article's
1829             *             DDM template
1830             * @param      languageId the primary key of the language translation to get
1831             * @param      themeDisplay the theme display
1832             * @return     the latest web content from the matching web content article
1833             * @throws     PortalException if a matching web content article or DDM
1834             *             template could not be found, or if a portal exception
1835             *             occurred
1836             * @deprecated As of 7.0.0, replaced by {@link #getArticleContent(long,
1837             *             String, String, String, String, PortletRequestModel,
1838             *             ThemeDisplay)}
1839             */
1840            @Deprecated
1841            @Override
1842            public String getArticleContent(
1843                            long groupId, String articleId, String viewMode,
1844                            String ddmTemplateKey, String languageId, ThemeDisplay themeDisplay)
1845                    throws PortalException {
1846    
1847                    return getArticleContent(
1848                            groupId, articleId, viewMode, ddmTemplateKey, languageId, null,
1849                            themeDisplay);
1850            }
1851    
1852            /**
1853             * Returns the latest web content from the web content article matching the
1854             * group and article ID.
1855             *
1856             * @param      groupId the primary key of the web content article's group
1857             * @param      articleId the primary key of the web content article
1858             * @param      viewMode the mode in which the web content is being viewed
1859             * @param      languageId the primary key of the language translation to get
1860             * @param      themeDisplay the theme display
1861             * @return     the latest web content from the matching web content article
1862             * @throws     PortalException if a matching web content article or DDM
1863             *             template could not be found, or if a portal exception
1864             *             occurred
1865             * @deprecated As of 7.0.0, replaced by {@link #getArticleContent(long,
1866             *             String, String, String, String, PortletRequestModel,
1867             *             ThemeDisplay)}
1868             */
1869            @Deprecated
1870            @Override
1871            public String getArticleContent(
1872                            long groupId, String articleId, String viewMode, String languageId,
1873                            ThemeDisplay themeDisplay)
1874                    throws PortalException {
1875    
1876                    return getArticleContent(
1877                            groupId, articleId, viewMode, null, languageId, null, themeDisplay);
1878            }
1879    
1880            /**
1881             * Returns a web content article display for the specified page of the
1882             * latest version of the web content article, based on the DDM template. Web
1883             * content transformation tokens are added using the portlet request model
1884             * and theme display.
1885             *
1886             * @param  article the primary key of the web content article
1887             * @param  ddmTemplateKey the primary key of the web content article's DDM
1888             *         template
1889             * @param  viewMode the mode in which the web content is being viewed
1890             * @param  languageId the primary key of the language translation to get
1891             * @param  page the web content article page to display
1892             * @param  portletRequestModel the portlet request model
1893             * @param  themeDisplay the theme display
1894             * @return the web content article display, or <code>null</code> if the
1895             *         article has expired or if article's display date/time is after
1896             *         the current date/time
1897             * @throws PortalException if a portal exception occurred
1898             */
1899            @Override
1900            public JournalArticleDisplay getArticleDisplay(
1901                            JournalArticle article, String ddmTemplateKey, String viewMode,
1902                            String languageId, int page,
1903                            PortletRequestModel portletRequestModel, ThemeDisplay themeDisplay)
1904                    throws PortalException {
1905    
1906                    return getArticleDisplay(
1907                            article, ddmTemplateKey, viewMode, languageId, page,
1908                            portletRequestModel, themeDisplay, false);
1909            }
1910    
1911            /**
1912             * Returns a web content article display for the specified page of the
1913             * specified version of the web content article matching the group, article
1914             * ID, and DDM template. Web content transformation tokens are added using
1915             * the portlet request model and theme display.
1916             *
1917             * @param  groupId the primary key of the web content article's group
1918             * @param  articleId the primary key of the web content article
1919             * @param  version the web content article's version
1920             * @param  ddmTemplateKey the primary key of the web content article's DDM
1921             *         template
1922             * @param  viewMode the mode in which the web content is being viewed
1923             * @param  languageId the primary key of the language translation to get
1924             * @param  page the web content article page to display
1925             * @param  portletRequestModel the portlet request model
1926             * @param  themeDisplay the theme display
1927             * @return the web content article display, or <code>null</code> if the
1928             *         article has expired or if article's display date/time is after
1929             *         the current date/time
1930             * @throws PortalException if a portal exception occurred
1931             */
1932            @Override
1933            public JournalArticleDisplay getArticleDisplay(
1934                            long groupId, String articleId, double version,
1935                            String ddmTemplateKey, String viewMode, String languageId, int page,
1936                            PortletRequestModel portletRequestModel, ThemeDisplay themeDisplay)
1937                    throws PortalException {
1938    
1939                    Date now = new Date();
1940    
1941                    JournalArticle article = journalArticlePersistence.findByG_A_V(
1942                            groupId, articleId, version);
1943    
1944                    if (article.isExpired()) {
1945                            Date expirationDate = article.getExpirationDate();
1946    
1947                            if ((expirationDate != null) && expirationDate.before(now)) {
1948                                    return null;
1949                            }
1950                    }
1951    
1952                    Date displayDate = article.getDisplayDate();
1953    
1954                    if (displayDate.after(now)) {
1955                            return null;
1956                    }
1957    
1958                    return getArticleDisplay(
1959                            article, ddmTemplateKey, viewMode, languageId, page,
1960                            portletRequestModel, themeDisplay);
1961            }
1962    
1963            /**
1964             * Returns a web content article display for the first page of the specified
1965             * version of the web content article matching the group, article ID, and
1966             * DDM template. Web content transformation tokens are added from the theme
1967             * display (if not <code>null</code>).
1968             *
1969             * @param  groupId the primary key of the web content article's group
1970             * @param  articleId the primary key of the web content article
1971             * @param  version the web content article's version
1972             * @param  ddmTemplateKey the primary key of the web content article's DDM
1973             *         template
1974             * @param  viewMode the mode in which the web content is being viewed
1975             * @param  languageId the primary key of the language translation to get
1976             * @param  themeDisplay the theme display
1977             * @return the web content article display, or <code>null</code> if the
1978             *         article has expired or if article's display date/time is after
1979             *         the current date/time
1980             * @throws PortalException if a matching web content article or DDM template
1981             *         could not be found, or if a portal exception occurred
1982             */
1983            @Override
1984            public JournalArticleDisplay getArticleDisplay(
1985                            long groupId, String articleId, double version,
1986                            String ddmTemplateKey, String viewMode, String languageId,
1987                            ThemeDisplay themeDisplay)
1988                    throws PortalException {
1989    
1990                    return getArticleDisplay(
1991                            groupId, articleId, version, ddmTemplateKey, viewMode, languageId,
1992                            1, null, themeDisplay);
1993            }
1994    
1995            /**
1996             * Returns a web content article display for the specified page of the
1997             * latest version of the web content article matching the group and article
1998             * ID. Web content transformation tokens are added from the theme display
1999             * (if not <code>null</code>).
2000             *
2001             * @param  groupId the primary key of the web content article's group
2002             * @param  articleId the primary key of the web content article
2003             * @param  viewMode the mode in which the web content is being viewed
2004             * @param  languageId the primary key of the language translation to get
2005             * @param  page the web content article page to display
2006             * @param  portletRequestModel the portlet request model
2007             * @param  themeDisplay the theme display
2008             * @return the web content article display, or <code>null</code> if the
2009             *         article has expired or if article's display date/time is after
2010             *         the current date/time
2011             * @throws PortalException if a portal exception occurred
2012             */
2013            @Override
2014            public JournalArticleDisplay getArticleDisplay(
2015                            long groupId, String articleId, String viewMode, String languageId,
2016                            int page, PortletRequestModel portletRequestModel,
2017                            ThemeDisplay themeDisplay)
2018                    throws PortalException {
2019    
2020                    return getArticleDisplay(
2021                            groupId, articleId, null, viewMode, languageId, page,
2022                            portletRequestModel, themeDisplay);
2023            }
2024    
2025            /**
2026             * Returns a web content article display for the specified page of the
2027             * latest version of the web content article matching the group, article ID,
2028             * and DDM template. Web content transformation tokens are added using the
2029             * portlet request model and theme display.
2030             *
2031             * @param  groupId the primary key of the web content article's group
2032             * @param  articleId the primary key of the web content article
2033             * @param  ddmTemplateKey the primary key of the web content article's DDM
2034             *         template
2035             * @param  viewMode the mode in which the web content is being viewed
2036             * @param  languageId the primary key of the language translation to get
2037             * @param  page the web content article page to display
2038             * @param  portletRequestModel the portlet request model
2039             * @param  themeDisplay the theme display
2040             * @return the web content article display, or <code>null</code> if the
2041             *         article has expired or if article's display date/time is after
2042             *         the current date/time
2043             * @throws PortalException if a portal exception occurred
2044             */
2045            @Override
2046            public JournalArticleDisplay getArticleDisplay(
2047                            long groupId, String articleId, String ddmTemplateKey,
2048                            String viewMode, String languageId, int page,
2049                            PortletRequestModel portletRequestModel, ThemeDisplay themeDisplay)
2050                    throws PortalException {
2051    
2052                    JournalArticle article = getDisplayArticle(groupId, articleId);
2053    
2054                    return getArticleDisplay(
2055                            groupId, articleId, article.getVersion(), ddmTemplateKey, viewMode,
2056                            languageId, page, portletRequestModel, themeDisplay);
2057            }
2058    
2059            /**
2060             * Returns a web content article display for the first page of the latest
2061             * version of the web content article matching the group, article ID, and
2062             * DDM template. Web content transformation tokens are added from the theme
2063             * display (if not <code>null</code>).
2064             *
2065             * @param  groupId the primary key of the web content article's group
2066             * @param  articleId the primary key of the web content article
2067             * @param  ddmTemplateKey the primary key of the web content article's DDM
2068             *         template
2069             * @param  viewMode the mode in which the web content is being viewed
2070             * @param  languageId the primary key of the language translation to get
2071             * @param  themeDisplay the theme display
2072             * @return the web content article display, or <code>null</code> if the
2073             *         article has expired or if article's display date/time is after
2074             *         the current date/time
2075             * @throws PortalException if a matching web content article or DDM template
2076             *         could not be found, or if a portal exception occurred
2077             */
2078            @Override
2079            public JournalArticleDisplay getArticleDisplay(
2080                            long groupId, String articleId, String ddmTemplateKey,
2081                            String viewMode, String languageId, ThemeDisplay themeDisplay)
2082                    throws PortalException {
2083    
2084                    JournalArticle article = getDisplayArticle(groupId, articleId);
2085    
2086                    return getArticleDisplay(
2087                            groupId, articleId, article.getVersion(), ddmTemplateKey, viewMode,
2088                            languageId, themeDisplay);
2089            }
2090    
2091            /**
2092             * Returns a web content article display for the first page of the latest
2093             * version of the web content article matching the group and article ID. Web
2094             * content transformation tokens are added from the theme display (if not
2095             * <code>null</code>).
2096             *
2097             * @param  groupId the primary key of the web content article's group
2098             * @param  articleId the primary key of the web content article
2099             * @param  viewMode the mode in which the web content is being viewed
2100             * @param  languageId the primary key of the language translation to get
2101             * @param  themeDisplay the theme display
2102             * @return the web content article display, or <code>null</code> if the
2103             *         article has expired or if article's display date/time is after
2104             *         the current date/time
2105             * @throws PortalException if a matching web content article or DDM template
2106             *         could not be found, or if a portal exception occurred
2107             */
2108            @Override
2109            public JournalArticleDisplay getArticleDisplay(
2110                            long groupId, String articleId, String viewMode, String languageId,
2111                            ThemeDisplay themeDisplay)
2112                    throws PortalException {
2113    
2114                    return getArticleDisplay(
2115                            groupId, articleId, null, viewMode, languageId, themeDisplay);
2116            }
2117    
2118            /**
2119             * Returns all the web content articles present in the system.
2120             *
2121             * @return the web content articles present in the system
2122             */
2123            @Override
2124            public List<JournalArticle> getArticles() {
2125                    return journalArticlePersistence.findAll();
2126            }
2127    
2128            /**
2129             * Returns all the web content articles belonging to the group.
2130             *
2131             * @param  groupId the primary key of the web content article's group
2132             * @return the web content articles belonging to the group
2133             */
2134            @Override
2135            public List<JournalArticle> getArticles(long groupId) {
2136                    return journalArticlePersistence.findByGroupId(groupId);
2137            }
2138    
2139            /**
2140             * Returns a range of all the web content articles belonging to the group.
2141             *
2142             * <p>
2143             * Useful when paginating results. Returns a maximum of <code>end -
2144             * start</code> instances. <code>start</code> and <code>end</code> are not
2145             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2146             * refers to the first result in the set. Setting both <code>start</code>
2147             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2148             * result set.
2149             * </p>
2150             *
2151             * @param  groupId the primary key of the web content article's group
2152             * @param  start the lower bound of the range of web content articles to
2153             *         return
2154             * @param  end the upper bound of the range of web content articles to
2155             *         return (not inclusive)
2156             * @return the range of matching web content articles
2157             */
2158            @Override
2159            public List<JournalArticle> getArticles(long groupId, int start, int end) {
2160                    return journalArticlePersistence.findByGroupId(groupId, start, end);
2161            }
2162    
2163            /**
2164             * Returns an ordered range of all the web content articles belonging to the
2165             * group.
2166             *
2167             * <p>
2168             * Useful when paginating results. Returns a maximum of <code>end -
2169             * start</code> instances. <code>start</code> and <code>end</code> are not
2170             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2171             * refers to the first result in the set. Setting both <code>start</code>
2172             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2173             * result set.
2174             * </p>
2175             *
2176             * @param  groupId the primary key of the web content article's group
2177             * @param  start the lower bound of the range of web content articles to
2178             *         return
2179             * @param  end the upper bound of the range of web content articles to
2180             *         return (not inclusive)
2181             * @param  obc the comparator to order the web content articles
2182             * @return the range of matching web content articles ordered by the
2183             *         comparator
2184             */
2185            @Override
2186            public List<JournalArticle> getArticles(
2187                    long groupId, int start, int end,
2188                    OrderByComparator<JournalArticle> obc) {
2189    
2190                    return journalArticlePersistence.findByGroupId(
2191                            groupId, start, end, obc);
2192            }
2193    
2194            /**
2195             * Returns all the web content articles matching the group and folder.
2196             *
2197             * @param  groupId the primary key of the web content article's group
2198             * @param  folderId the primary key of the web content article folder
2199             * @return the matching web content articles
2200             */
2201            @Override
2202            public List<JournalArticle> getArticles(long groupId, long folderId) {
2203                    return journalArticlePersistence.findByG_F(groupId, folderId);
2204            }
2205    
2206            /**
2207             * Returns a range of all the web content articles matching the group and
2208             * folder.
2209             *
2210             * <p>
2211             * Useful when paginating results. Returns a maximum of <code>end -
2212             * start</code> instances. <code>start</code> and <code>end</code> are not
2213             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2214             * refers to the first result in the set. Setting both <code>start</code>
2215             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2216             * result set.
2217             * </p>
2218             *
2219             * @param  groupId the primary key of the web content article's group
2220             * @param  folderId the primary key of the web content article's folder
2221             * @param  start the lower bound of the range of web content articles to
2222             *         return
2223             * @param  end the upper bound of the range of web content articles to
2224             *         return (not inclusive)
2225             * @return the range of matching web content articles
2226             */
2227            @Override
2228            public List<JournalArticle> getArticles(
2229                    long groupId, long folderId, int start, int end) {
2230    
2231                    return journalArticlePersistence.findByG_F(
2232                            groupId, folderId, start, end);
2233            }
2234    
2235            /**
2236             * Returns a range of all the web content articles matching the group,
2237             * folder, and status.
2238             *
2239             * <p>
2240             * Useful when paginating results. Returns a maximum of <code>end -
2241             * start</code> instances. <code>start</code> and <code>end</code> are not
2242             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2243             * refers to the first result in the set. Setting both <code>start</code>
2244             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2245             * result set.
2246             * </p>
2247             *
2248             * @param  groupId the primary key of the web content article's group
2249             * @param  folderId the primary key of the web content article's folder
2250             * @param  status the web content article's workflow status. For more
2251             *         information see {@link WorkflowConstants} for constants starting
2252             *         with the "STATUS_" prefix.
2253             * @param  start the lower bound of the range of web content articles to
2254             *         return
2255             * @param  end the upper bound of the range of web content articles to
2256             *         return (not inclusive)
2257             * @return the range of matching web content articles
2258             */
2259            @Override
2260            public List<JournalArticle> getArticles(
2261                    long groupId, long folderId, int status, int start, int end) {
2262    
2263                    return journalArticlePersistence.findByG_F_ST(
2264                            groupId, folderId, status, start, end);
2265            }
2266    
2267            /**
2268             * Returns an ordered range of all the web content articles matching the
2269             * group and folder.
2270             *
2271             * <p>
2272             * Useful when paginating results. Returns a maximum of <code>end -
2273             * start</code> instances. <code>start</code> and <code>end</code> are not
2274             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2275             * refers to the first result in the set. Setting both <code>start</code>
2276             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2277             * result set.
2278             * </p>
2279             *
2280             * @param  groupId the primary key of the web content article's group
2281             * @param  folderId the primary key of the web content article's folder
2282             * @param  start the lower bound of the range of web content articles to
2283             *         return
2284             * @param  end the upper bound of the range of web content articles to
2285             *         return (not inclusive)
2286             * @param  orderByComparator the comparator to order the web content
2287             *         articles
2288             * @return the range of matching web content articles ordered by the
2289             *         comparator
2290             */
2291            @Override
2292            public List<JournalArticle> getArticles(
2293                    long groupId, long folderId, int start, int end,
2294                    OrderByComparator<JournalArticle> orderByComparator) {
2295    
2296                    return journalArticlePersistence.findByG_F(
2297                            groupId, folderId, start, end, orderByComparator);
2298            }
2299    
2300            /**
2301             * Returns all the web content articles matching the group and article ID.
2302             *
2303             * @param  groupId the primary key of the web content article's group
2304             * @param  articleId the primary key of the web content article
2305             * @return the matching web content articles
2306             */
2307            @Override
2308            public List<JournalArticle> getArticles(long groupId, String articleId) {
2309                    return journalArticlePersistence.findByG_A(groupId, articleId);
2310            }
2311    
2312            @Override
2313            public List<JournalArticle> getArticles(
2314                    long groupId, String articleId, int start, int end,
2315                    OrderByComparator<JournalArticle> orderByComparator) {
2316    
2317                    return journalArticlePersistence.findByG_A(
2318                            groupId, articleId, start, end, orderByComparator);
2319            }
2320    
2321            /**
2322             * Returns all the web content articles matching the resource primary key.
2323             *
2324             * @param  resourcePrimKey the primary key of the resource instance
2325             * @return the web content articles matching the resource primary key
2326             */
2327            @Override
2328            public List<JournalArticle> getArticlesByResourcePrimKey(
2329                    long resourcePrimKey) {
2330    
2331                    return journalArticlePersistence.findByResourcePrimKey(resourcePrimKey);
2332            }
2333    
2334            /**
2335             * Returns all the web content articles matching the small image ID.
2336             *
2337             * @param  smallImageId the primary key of the web content article's small
2338             *         image
2339             * @return the web content articles matching the small image ID
2340             */
2341            @Override
2342            public List<JournalArticle> getArticlesBySmallImageId(long smallImageId) {
2343                    return journalArticlePersistence.findBySmallImageId(smallImageId);
2344            }
2345    
2346            /**
2347             * Returns the number of web content articles belonging to the group.
2348             *
2349             * @param  groupId the primary key of the web content article's group
2350             * @return the number of web content articles belonging to the group
2351             */
2352            @Override
2353            public int getArticlesCount(long groupId) {
2354                    return journalArticlePersistence.countByGroupId(groupId);
2355            }
2356    
2357            /**
2358             * Returns the number of web content articles matching the group and folder.
2359             *
2360             * @param  groupId the primary key of the web content article's group
2361             * @param  folderId the primary key of the web content article's folder
2362             * @return the number of matching web content articles
2363             */
2364            @Override
2365            public int getArticlesCount(long groupId, long folderId) {
2366                    return journalArticlePersistence.countByG_F(groupId, folderId);
2367            }
2368    
2369            /**
2370             * Returns the number of web content articles matching the group, folder,
2371             * and status.
2372             *
2373             * @param  groupId the primary key of the web content article's group
2374             * @param  folderId the primary key of the web content article's folder
2375             * @param  status the web content article's workflow status. For more
2376             *         information see {@link WorkflowConstants} for constants starting
2377             *         with the "STATUS_" prefix.
2378             * @return the number of matching web content articles
2379             */
2380            @Override
2381            public int getArticlesCount(long groupId, long folderId, int status) {
2382                    return journalArticlePersistence.countByG_F_ST(
2383                            groupId, folderId, status);
2384            }
2385    
2386            @Override
2387            public int getArticlesCount(long groupId, String articleId) {
2388                    return journalArticlePersistence.countByG_A(groupId, articleId);
2389            }
2390    
2391            /**
2392             * Returns an ordered range of all the web content articles matching the
2393             * company, version, and workflow status.
2394             *
2395             * <p>
2396             * Useful when paginating results. Returns a maximum of <code>end -
2397             * start</code> instances. <code>start</code> and <code>end</code> are not
2398             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2399             * refers to the first result in the set. Setting both <code>start</code>
2400             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2401             * result set.
2402             * </p>
2403             *
2404             * @param  companyId the primary key of the web content article's company
2405             * @param  version the web content article's version
2406             * @param  status the web content article's workflow status. For more
2407             *         information see {@link WorkflowConstants} for constants starting
2408             *         with the "STATUS_" prefix.
2409             * @param  start the lower bound of the range of web content articles to
2410             *         return
2411             * @param  end the upper bound of the range of web content articles to
2412             *         return (not inclusive)
2413             * @return the range of matching web content articles ordered by article ID
2414             */
2415            @Override
2416            public List<JournalArticle> getCompanyArticles(
2417                    long companyId, double version, int status, int start, int end) {
2418    
2419                    if (status == WorkflowConstants.STATUS_ANY) {
2420                            return journalArticlePersistence.findByC_V(
2421                                    companyId, version, start, end, new ArticleIDComparator(true));
2422                    }
2423                    else {
2424                            return journalArticlePersistence.findByC_V_ST(
2425                                    companyId, version, status, start, end,
2426                                    new ArticleIDComparator(true));
2427                    }
2428            }
2429    
2430            /**
2431             * Returns an ordered range of all the web content articles matching the
2432             * company and workflow status.
2433             *
2434             * <p>
2435             * Useful when paginating results. Returns a maximum of <code>end -
2436             * start</code> instances. <code>start</code> and <code>end</code> are not
2437             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2438             * refers to the first result in the set. Setting both <code>start</code>
2439             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2440             * result set.
2441             * </p>
2442             *
2443             * @param  companyId the primary key of the web content article's company
2444             * @param  status the web content article's workflow status. For more
2445             *         information see {@link WorkflowConstants} for constants starting
2446             *         with the "STATUS_" prefix.
2447             * @param  start the lower bound of the range of web content articles to
2448             *         return
2449             * @param  end the upper bound of the range of web content articles to
2450             *         return (not inclusive)
2451             * @return the range of matching web content articles ordered by article ID
2452             */
2453            @Override
2454            public List<JournalArticle> getCompanyArticles(
2455                    long companyId, int status, int start, int end) {
2456    
2457                    if (status == WorkflowConstants.STATUS_ANY) {
2458                            return journalArticlePersistence.findByCompanyId(
2459                                    companyId, start, end, new ArticleIDComparator(true));
2460                    }
2461                    else {
2462                            return journalArticlePersistence.findByC_ST(
2463                                    companyId, status, start, end, new ArticleIDComparator(true));
2464                    }
2465            }
2466    
2467            /**
2468             * Returns the number of web content articles matching the company, version,
2469             * and workflow status.
2470             *
2471             * <p>
2472             * Useful when paginating results. Returns a maximum of <code>end -
2473             * start</code> instances. <code>start</code> and <code>end</code> are not
2474             * primary keys, they are indexes in the result set. Thus, <code>0</code>
2475             * refers to the first result in the set. Setting both <code>start</code>
2476             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
2477             * result set.
2478             * </p>
2479             *
2480             * @param  companyId the primary key of the web content article's company
2481             * @param  version the web content article's version
2482             * @param  status the web content article's workflow status. For more
2483             *         information see {@link WorkflowConstants} for constants starting
2484             *         with the "STATUS_" prefix.
2485             * @param  start the lower bound of the range of web content articles to
2486             *         return
2487             * @param  end the upper bound of the range of web content articles to
2488             *         return (not inclusive)
2489             * @return the number of matching web content articles
2490             */
2491            @Override
2492            public int getCompanyArticlesCount(
2493                    long companyId, double version, int status, int start, int end) {
2494    
2495                    if (status == WorkflowConstants.STATUS_ANY) {
2496                            return journalArticlePersistence.countByC_V(companyId, version);
2497                    }
2498                    else {
2499                            return journalArticlePersistence.countByC_V_ST(
2500                                    companyId, version, status);
2501                    }
2502            }
2503    
2504            /**
2505             * Returns the number of web content articles matching the company and
2506             * workflow status.
2507             *
2508             * @param  companyId the primary key of the web content article's company
2509             * @param  status the web content article's workflow status. For more
2510             *         information see {@link WorkflowConstants} for constants starting
2511             *         with the "STATUS_" prefix.
2512             * @return the number of matching web content articles
2513             */
2514            @Override
2515            public int getCompanyArticlesCount(long companyId, int status) {
2516                    if (status == WorkflowConstants.STATUS_ANY) {
2517                            return journalArticlePersistence.countByCompanyId(companyId);
2518                    }
2519                    else {
2520                            return journalArticlePersistence.countByC_ST(companyId, status);
2521                    }
2522            }
2523    
2524            /**
2525             * Returns the matching web content article currently displayed or next to
2526             * be displayed if no article is currently displayed.
2527             *
2528             * @param  groupId the primary key of the web content article's group
2529             * @param  articleId the primary key of the web content article
2530             * @return the matching web content article currently displayed, or the next
2531             *         one to be displayed if no version of the article is currently
2532             *         displayed
2533             * @throws PortalException if no approved matching web content articles
2534             *         could be found
2535             */
2536            @Override
2537            public JournalArticle getDisplayArticle(long groupId, String articleId)
2538                    throws PortalException {
2539    
2540                    List<JournalArticle> articles = journalArticlePersistence.findByG_A_ST(
2541                            groupId, articleId, WorkflowConstants.STATUS_APPROVED);
2542    
2543                    if (articles.isEmpty()) {
2544                            throw new NoSuchArticleException(
2545                                    "No approved JournalArticle exists with the key {groupId=" +
2546                                            groupId + ", " + "articleId=" + articleId + "}");
2547                    }
2548    
2549                    Date now = new Date();
2550    
2551                    for (int i = 0; i < articles.size(); i++) {
2552                            JournalArticle article = articles.get(i);
2553    
2554                            Date displayDate = article.getDisplayDate();
2555                            Date expirationDate = article.getExpirationDate();
2556    
2557                            if (((displayDate == null) || displayDate.before(now)) &&
2558                                    ((expirationDate == null) || expirationDate.after(now))) {
2559    
2560                                    return article;
2561                            }
2562                    }
2563    
2564                    return articles.get(0);
2565            }
2566    
2567            /**
2568             * Returns the web content article matching the URL title that is currently
2569             * displayed or next to be displayed if no article is currently displayed.
2570             *
2571             * @param  groupId the primary key of the web content article's group
2572             * @param  urlTitle the web content article's accessible URL title
2573             * @return the web content article matching the URL title that is currently
2574             *         displayed, or next one to be displayed if no version of the
2575             *         article is currently displayed
2576             * @throws PortalException if no approved matching web content articles
2577             *         could be found
2578             */
2579            @Override
2580            public JournalArticle getDisplayArticleByUrlTitle(
2581                            long groupId, String urlTitle)
2582                    throws PortalException {
2583    
2584                    List<JournalArticle> articles = null;
2585    
2586                    OrderByComparator<JournalArticle> orderByComparator =
2587                            new ArticleVersionComparator();
2588    
2589                    articles = journalArticlePersistence.findByG_UT_ST(
2590                            groupId, urlTitle, WorkflowConstants.STATUS_APPROVED,
2591                            QueryUtil.ALL_POS, QueryUtil.ALL_POS, orderByComparator);
2592    
2593                    if (articles.isEmpty()) {
2594                            throw new NoSuchArticleException(
2595                                    "No JournalArticle exists with the key {groupId=" + groupId +
2596                                            ", urlTitle=" + urlTitle + "}");
2597                    }
2598    
2599                    Date now = new Date();
2600    
2601                    for (JournalArticle article : articles) {
2602                            Date displayDate = article.getDisplayDate();
2603                            Date expirationDate = article.getExpirationDate();
2604    
2605                            if ((displayDate != null) && displayDate.before(now) &&
2606                                    ((expirationDate == null) || expirationDate.after(now)) ) {
2607    
2608                                    return article;
2609                            }
2610                    }
2611    
2612                    return articles.get(0);
2613            }
2614    
2615            /**
2616             * Returns the indexable web content articles matching the resource primary
2617             * key.
2618             *
2619             * @param  resourcePrimKey the primary key of the resource instance
2620             * @return the indexable web content articles matching the resource primary
2621             *         key
2622             */
2623            @Override
2624            public List<JournalArticle> getIndexableArticlesByResourcePrimKey(
2625                    long resourcePrimKey) {
2626    
2627                    return journalArticlePersistence.findByR_I(resourcePrimKey, true);
2628            }
2629    
2630            /**
2631             * Returns the latest web content article matching the resource primary key,
2632             * preferring articles with approved workflow status.
2633             *
2634             * @param  resourcePrimKey the primary key of the resource instance
2635             * @return the latest web content article matching the resource primary key,
2636             *         preferring articles with approved workflow status
2637             * @throws PortalException if a matching web content article could not be
2638             *         found
2639             */
2640            @Override
2641            public JournalArticle getLatestArticle(long resourcePrimKey)
2642                    throws PortalException {
2643    
2644                    return getLatestArticle(resourcePrimKey, WorkflowConstants.STATUS_ANY);
2645            }
2646    
2647            /**
2648             * Returns the latest web content article matching the resource primary key
2649             * and workflow status, preferring articles with approved workflow status.
2650             *
2651             * @param  resourcePrimKey the primary key of the resource instance
2652             * @param  status the web content article's workflow status. For more
2653             *         information see {@link WorkflowConstants} for constants starting
2654             *         with the "STATUS_" prefix.
2655             * @return the latest web content article matching the resource primary key
2656             *         and workflow status, preferring articles with approved workflow
2657             *         status
2658             * @throws PortalException if a matching web content article could not be
2659             *         found
2660             */
2661            @Override
2662            public JournalArticle getLatestArticle(long resourcePrimKey, int status)
2663                    throws PortalException {
2664    
2665                    return getLatestArticle(resourcePrimKey, status, true);
2666            }
2667    
2668            /**
2669             * Returns the latest web content article matching the resource primary key
2670             * and workflow status, optionally preferring articles with approved
2671             * workflow status.
2672             *
2673             * @param  resourcePrimKey the primary key of the resource instance
2674             * @param  status the web content article's workflow status. For more
2675             *         information see {@link WorkflowConstants} for constants starting
2676             *         with the "STATUS_" prefix.
2677             * @param  preferApproved whether to prefer returning the latest matching
2678             *         article that has workflow status {@link
2679             *         WorkflowConstants#STATUS_APPROVED} over returning one that has a
2680             *         different status
2681             * @return the latest web content article matching the resource primary key
2682             *         and workflow status, optionally preferring articles with approved
2683             *         workflow status
2684             * @throws PortalException if a matching web content article could not be
2685             *         found
2686             */
2687            @Override
2688            public JournalArticle getLatestArticle(
2689                            long resourcePrimKey, int status, boolean preferApproved)
2690                    throws PortalException {
2691    
2692                    List<JournalArticle> articles = null;
2693    
2694                    OrderByComparator<JournalArticle> orderByComparator =
2695                            new ArticleVersionComparator();
2696    
2697                    if (status == WorkflowConstants.STATUS_ANY) {
2698                            if (preferApproved) {
2699                                    articles = journalArticlePersistence.findByR_ST(
2700                                            resourcePrimKey, WorkflowConstants.STATUS_APPROVED, 0, 1,
2701                                            orderByComparator);
2702                            }
2703    
2704                            if (ListUtil.isEmpty(articles)) {
2705                                    articles = journalArticlePersistence.findByResourcePrimKey(
2706                                            resourcePrimKey, 0, 1, orderByComparator);
2707                            }
2708                    }
2709                    else {
2710                            articles = journalArticlePersistence.findByR_ST(
2711                                    resourcePrimKey, status, 0, 1, orderByComparator);
2712                    }
2713    
2714                    if (articles.isEmpty()) {
2715                            throw new NoSuchArticleException(
2716                                    "No JournalArticle exists with the key {resourcePrimKey=" +
2717                                            resourcePrimKey + "}");
2718                    }
2719    
2720                    return articles.get(0);
2721            }
2722    
2723            /**
2724             * Returns the latest web content article with the group and article ID.
2725             *
2726             * @param  groupId the primary key of the web content article's group
2727             * @param  articleId the primary key of the web content article
2728             * @return the latest matching web content article
2729             * @throws PortalException if a matching web content article could not be
2730             *         found
2731             */
2732            @Override
2733            public JournalArticle getLatestArticle(long groupId, String articleId)
2734                    throws PortalException {
2735    
2736                    return getLatestArticle(
2737                            groupId, articleId, WorkflowConstants.STATUS_ANY);
2738            }
2739    
2740            /**
2741             * Returns the latest web content article matching the group, article ID,
2742             * and workflow status.
2743             *
2744             * @param  groupId the primary key of the web content article's group
2745             * @param  articleId the primary key of the web content article
2746             * @param  status the web content article's workflow status. For more
2747             *         information see {@link WorkflowConstants} for constants starting
2748             *         with the "STATUS_" prefix.
2749             * @return the latest matching web content article
2750             * @throws PortalException if a matching web content article could not be
2751             *         found
2752             */
2753            @Override
2754            public JournalArticle getLatestArticle(
2755                            long groupId, String articleId, int status)
2756                    throws PortalException {
2757    
2758                    return getFirstArticle(
2759                            groupId, articleId, status, new ArticleVersionComparator());
2760            }
2761    
2762            /**
2763             * Returns the latest web content article matching the group, class name ID,
2764             * and class PK.
2765             *
2766             * @param  groupId the primary key of the web content article's group
2767             * @param  className the DDMStructure class name if the web content article
2768             *         is related to a DDM structure, the class name associated with the
2769             *         article, or {@link JournalArticleConstants#CLASSNAME_ID_DEFAULT}
2770             *         otherwise
2771             * @param  classPK the primary key of the DDM structure, if the DDMStructure
2772             *         class name is given as the <code>className</code> parameter, the
2773             *         primary key of the class associated with the web content article,
2774             *         or <code>0</code> otherwise
2775             * @return the latest matching web content article
2776             * @throws PortalException if a matching web content article could not be
2777             *         found
2778             */
2779            @Override
2780            public JournalArticle getLatestArticle(
2781                            long groupId, String className, long classPK)
2782                    throws PortalException {
2783    
2784                    long classNameId = classNameLocalService.getClassNameId(className);
2785    
2786                    List<JournalArticle> articles = journalArticlePersistence.findByG_C_C(
2787                            groupId, classNameId, classPK, 0, 1,
2788                            new ArticleVersionComparator());
2789    
2790                    if (articles.isEmpty()) {
2791                            throw new NoSuchArticleException(
2792                                    "No JournalArticle exists with the key {groupId=" + groupId +
2793                                            ", className=" + className + ", classPK =" + classPK + "}");
2794                    }
2795    
2796                    return articles.get(0);
2797            }
2798    
2799            /**
2800             * Returns the latest web content article matching the group, URL title, and
2801             * workflow status.
2802             *
2803             * @param  groupId the primary key of the web content article's group
2804             * @param  urlTitle the web content article's accessible URL title
2805             * @param  status the web content article's workflow status. For more
2806             *         information see {@link WorkflowConstants} for constants starting
2807             *         with the "STATUS_" prefix.
2808             * @return the latest matching web content article
2809             * @throws PortalException if a matching web content article could not be
2810             *         found
2811             */
2812            @Override
2813            public JournalArticle getLatestArticleByUrlTitle(
2814                            long groupId, String urlTitle, int status)
2815                    throws PortalException {
2816    
2817                    List<JournalArticle> articles = null;
2818    
2819                    OrderByComparator<JournalArticle> orderByComparator =
2820                            new ArticleVersionComparator();
2821    
2822                    if (status == WorkflowConstants.STATUS_ANY) {
2823                            articles = journalArticlePersistence.findByG_UT(
2824                                    groupId, urlTitle, 0, 1, orderByComparator);
2825                    }
2826                    else {
2827                            articles = journalArticlePersistence.findByG_UT_ST(
2828                                    groupId, urlTitle, status, 0, 1, orderByComparator);
2829                    }
2830    
2831                    if (articles.isEmpty()) {
2832                            throw new NoSuchArticleException(
2833                                    "No JournalArticle exists with the key {groupId=" + groupId +
2834                                            ", urlTitle=" + urlTitle + ", status=" + status + "}");
2835                    }
2836    
2837                    return articles.get(0);
2838            }
2839    
2840            /**
2841             * Returns the latest version number of the web content with the group and
2842             * article ID.
2843             *
2844             * @param  groupId the primary key of the web content article's group
2845             * @param  articleId the primary key of the web content article
2846             * @return the latest version number of the matching web content
2847             * @throws PortalException if a matching web content article could not be
2848             *         found
2849             */
2850            @Override
2851            public double getLatestVersion(long groupId, String articleId)
2852                    throws PortalException {
2853    
2854                    JournalArticle article = getLatestArticle(groupId, articleId);
2855    
2856                    return article.getVersion();
2857            }
2858    
2859            /**
2860             * Returns the latest version number of the web content with the group,
2861             * article ID, and workflow status.
2862             *
2863             * @param  groupId the primary key of the web content article's group
2864             * @param  articleId the primary key of the web content article
2865             * @param  status the web content article's workflow status. For more
2866             *         information see {@link WorkflowConstants} for constants starting
2867             *         with the "STATUS_" prefix.
2868             * @return the latest version number of the matching web content
2869             * @throws PortalException if a matching web content article could not be
2870             *         found
2871             */
2872            @Override
2873            public double getLatestVersion(long groupId, String articleId, int status)
2874                    throws PortalException {
2875    
2876                    JournalArticle article = getLatestArticle(groupId, articleId, status);
2877    
2878                    return article.getVersion();
2879            }
2880    
2881            @Override
2882            public List<JournalArticle> getNoAssetArticles() {
2883                    return journalArticleFinder.findByNoAssets();
2884            }
2885    
2886            @Override
2887            public List<JournalArticle> getNoPermissionArticles() {
2888                    return journalArticleFinder.findByNoPermissions();
2889            }
2890    
2891            /**
2892             * Returns the number of web content articles that are not recycled.
2893             *
2894             * @param  groupId the primary key of the web content article's group
2895             * @param  folderId the primary key of the web content article folder
2896             * @return the number of web content articles that are not recycled
2897             */
2898            @Override
2899            public int getNotInTrashArticlesCount(long groupId, long folderId) {
2900                    QueryDefinition<JournalArticle> queryDefinition =
2901                            new QueryDefinition<JournalArticle>(WorkflowConstants.STATUS_ANY);
2902    
2903                    List<Long> folderIds = new ArrayList<Long>();
2904    
2905                    folderIds.add(folderId);
2906    
2907                    return journalArticleFinder.countByG_F(
2908                            groupId, folderIds, queryDefinition);
2909            }
2910    
2911            /**
2912             * Returns the oldest web content article with the group and article ID.
2913             *
2914             * @param  groupId the primary key of the web content article's group
2915             * @param  articleId the primary key of the web content article
2916             * @return the oldest matching web content article
2917             * @throws PortalException if a matching web content article could not be
2918             *         found
2919             */
2920            @Override
2921            public JournalArticle getOldestArticle(long groupId, String articleId)
2922                    throws PortalException {
2923    
2924                    return getOldestArticle(
2925                            groupId, articleId, WorkflowConstants.STATUS_ANY);
2926            }
2927    
2928            /**
2929             * Returns the oldest web content article matching the group, article ID,
2930             * and workflow status.
2931             *
2932             * @param  groupId the primary key of the web content article's group
2933             * @param  articleId the primary key of the web content article
2934             * @param  status the web content article's workflow status. For more
2935             *         information see {@link WorkflowConstants} for constants starting
2936             *         with the "STATUS_" prefix.
2937             * @return the oldest matching web content article
2938             * @throws PortalException if a matching web content article could not be
2939             *         found
2940             */
2941            @Override
2942            public JournalArticle getOldestArticle(
2943                            long groupId, String articleId, int status)
2944                    throws PortalException {
2945    
2946                    return getFirstArticle(
2947                            groupId, articleId, status, new ArticleVersionComparator(false));
2948            }
2949    
2950            /**
2951             * Returns the previously approved version of the web content article. For
2952             * more information on the approved workflow status, see {@link
2953             * WorkflowConstants#STATUS_APPROVED}.
2954             *
2955             * @param  article the web content article
2956             * @return the previously approved version of the web content article, or
2957             *         the current web content article if there are no previously
2958             *         approved web content articles
2959             */
2960            @Override
2961            public JournalArticle getPreviousApprovedArticle(JournalArticle article) {
2962                    List<JournalArticle> approvedArticles =
2963                            journalArticlePersistence.findByG_A_ST(
2964                                    article.getGroupId(), article.getArticleId(),
2965                                    WorkflowConstants.STATUS_APPROVED, 0, 2);
2966    
2967                    if (approvedArticles.isEmpty() ||
2968                            ((approvedArticles.size() == 1) &&
2969                             (article.getStatus() == WorkflowConstants.STATUS_APPROVED))) {
2970    
2971                            return article;
2972                    }
2973    
2974                    JournalArticle previousApprovedArticle = approvedArticles.get(0);
2975    
2976                    if (article.getStatus() == WorkflowConstants.STATUS_APPROVED) {
2977                            previousApprovedArticle = approvedArticles.get(1);
2978                    }
2979    
2980                    return previousApprovedArticle;
2981            }
2982    
2983            /**
2984             * Returns the web content articles matching the group and DDM structure
2985             * key.
2986             *
2987             * @param  groupId the primary key of the web content article's group
2988             * @param  ddmStructureKey the primary key of the web content article's DDM
2989             *         structure
2990             * @return the matching web content articles
2991             */
2992            @Override
2993            public List<JournalArticle> getStructureArticles(
2994                    long groupId, String ddmStructureKey) {
2995    
2996                    return journalArticlePersistence.findByG_DDMSK(
2997                            groupId, ddmStructureKey);
2998            }
2999    
3000            /**
3001             * Returns an ordered range of all the web content articles matching the
3002             * group and DDM structure key.
3003             *
3004             * <p>
3005             * Useful when paginating results. Returns a maximum of <code>end -
3006             * start</code> instances. <code>start</code> and <code>end</code> are not
3007             * primary keys, they are indexes in the result set. Thus, <code>0</code>
3008             * refers to the first result in the set. Setting both <code>start</code>
3009             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
3010             * result set.
3011             * </p>
3012             *
3013             * @param  groupId the primary key of the web content article's group
3014             * @param  ddmStructureKey the primary key of the web content article's DDM
3015             *         structure
3016             * @param  start the lower bound of the range of web content articles to
3017             *         return
3018             * @param  end the upper bound of the range of web content articles to
3019             *         return (not inclusive)
3020             * @param  obc the comparator to order the web content articles
3021             * @return the range of matching web content articles ordered by the
3022             *         comparator
3023             */
3024            @Override
3025            public List<JournalArticle> getStructureArticles(
3026                    long groupId, String ddmStructureKey, int start, int end,
3027                    OrderByComparator<JournalArticle> obc) {
3028    
3029                    return journalArticlePersistence.findByG_DDMSK(
3030                            groupId, ddmStructureKey, start, end, obc);
3031            }
3032    
3033            /**
3034             * Returns the web content articles matching the DDM structure keys.
3035             *
3036             * @param  ddmStructureKeys the primary keys of the web content article's
3037             *         DDM structures
3038             * @return the web content articles matching the DDM structure keys
3039             */
3040            @Override
3041            public List<JournalArticle> getStructureArticles(
3042                    String[] ddmStructureKeys) {
3043    
3044                    return journalArticlePersistence.findByDDMStructureKey(
3045                            ddmStructureKeys);
3046            }
3047    
3048            /**
3049             * Returns the number of web content articles matching the group and DDM
3050             * structure key.
3051             *
3052             * @param  groupId the primary key of the web content article's group
3053             * @param  ddmStructureKey the primary key of the web content article's DDM
3054             *         structure
3055             * @return the number of matching web content articles
3056             */
3057            @Override
3058            public int getStructureArticlesCount(long groupId, String ddmStructureKey) {
3059                    return journalArticlePersistence.countByG_DDMSK(
3060                            groupId, ddmStructureKey);
3061            }
3062    
3063            /**
3064             * Returns the web content articles matching the group and DDM template key.
3065             *
3066             * @param  groupId the primary key of the web content article's group
3067             * @param  ddmTemplateKey the primary key of the web content article's DDM
3068             *         template
3069             * @return the matching web content articles
3070             */
3071            @Override
3072            public List<JournalArticle> getTemplateArticles(
3073                    long groupId, String ddmTemplateKey) {
3074    
3075                    return journalArticlePersistence.findByG_DDMTK(groupId, ddmTemplateKey);
3076            }
3077    
3078            /**
3079             * Returns an ordered range of all the web content articles matching the
3080             * group and DDM template key.
3081             *
3082             * <p>
3083             * Useful when paginating results. Returns a maximum of <code>end -
3084             * start</code> instances. <code>start</code> and <code>end</code> are not
3085             * primary keys, they are indexes in the result set. Thus, <code>0</code>
3086             * refers to the first result in the set. Setting both <code>start</code>
3087             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
3088             * result set.
3089             * </p>
3090             *
3091             * @param  groupId the primary key of the web content article's group
3092             * @param  ddmTemplateKey the primary key of the web content article's DDM
3093             *         template
3094             * @param  start the lower bound of the range of web content articles to
3095             *         return
3096             * @param  end the upper bound of the range of web content articles to
3097             *         return (not inclusive)
3098             * @param  obc the comparator to order the web content articles
3099             * @return the range of matching web content articles ordered by the
3100             *         comparator
3101             */
3102            @Override
3103            public List<JournalArticle> getTemplateArticles(
3104                    long groupId, String ddmTemplateKey, int start, int end,
3105                    OrderByComparator<JournalArticle> obc) {
3106    
3107                    return journalArticlePersistence.findByG_DDMTK(
3108                            groupId, ddmTemplateKey, start, end, obc);
3109            }
3110    
3111            /**
3112             * Returns the number of web content articles matching the group and DDM
3113             * template key.
3114             *
3115             * @param  groupId the primary key of the web content article's group
3116             * @param  ddmTemplateKey the primary key of the web content article's DDM
3117             *         template
3118             * @return the number of matching web content articles
3119             */
3120            @Override
3121            public int getTemplateArticlesCount(long groupId, String ddmTemplateKey) {
3122                    return journalArticlePersistence.countByG_DDMTK(
3123                            groupId, ddmTemplateKey);
3124            }
3125    
3126            /**
3127             * Returns the web content article's unique URL title.
3128             *
3129             * @param  groupId the primary key of the web content article's group
3130             * @param  articleId the primary key of the web content article
3131             * @param  urlTitle the web content article's accessible URL title
3132             * @return the web content article's unique URL title
3133             * @throws PortalException if a portal exception occurred
3134             */
3135            @Override
3136            public String getUniqueUrlTitle(
3137                            long groupId, String articleId, String urlTitle)
3138                    throws PortalException {
3139    
3140                    for (int i = 1;; i++) {
3141                            JournalArticle article = null;
3142    
3143                            try {
3144                                    article = getArticleByUrlTitle(groupId, urlTitle);
3145                            }
3146                            catch (NoSuchArticleException nsae) {
3147                            }
3148    
3149                            if ((article == null) || articleId.equals(article.getArticleId())) {
3150                                    break;
3151                            }
3152                            else {
3153                                    String suffix = StringPool.DASH + i;
3154    
3155                                    String prefix = urlTitle;
3156    
3157                                    if (urlTitle.length() > suffix.length()) {
3158                                            prefix = urlTitle.substring(
3159                                                    0, urlTitle.length() - suffix.length());
3160                                    }
3161    
3162                                    urlTitle = prefix + suffix;
3163                            }
3164                    }
3165    
3166                    return urlTitle;
3167            }
3168    
3169            /**
3170             * Returns <code>true</code> if the specified web content article exists.
3171             *
3172             * @param  groupId the primary key of the group
3173             * @param  articleId the primary key of the web content article
3174             * @return <code>true</code> if the specified web content article exists;
3175             *         <code>false</code> otherwise
3176             */
3177            @Override
3178            public boolean hasArticle(long groupId, String articleId) {
3179                    try {
3180                            getArticle(groupId, articleId);
3181    
3182                            return true;
3183                    }
3184                    catch (PortalException pe) {
3185                            return false;
3186                    }
3187            }
3188    
3189            /**
3190             * Returns <code>true</code> if the web content article, specified by group
3191             * and article ID, is the latest version.
3192             *
3193             * @param  groupId the primary key of the web content article's group
3194             * @param  articleId the primary key of the web content article
3195             * @param  version the web content article's version
3196             * @return <code>true</code> if the specified web content article is the
3197             *         latest version; <code>false</code> otherwise
3198             * @throws PortalException if a matching web content article could not be
3199             *         found
3200             */
3201            @Override
3202            public boolean isLatestVersion(
3203                            long groupId, String articleId, double version)
3204                    throws PortalException {
3205    
3206                    if (getLatestVersion(groupId, articleId) == version) {
3207                            return true;
3208                    }
3209                    else {
3210                            return false;
3211                    }
3212            }
3213    
3214            /**
3215             * Returns <code>true</code> if the web content article, specified by group,
3216             * article ID, and workflow status, is the latest version.
3217             *
3218             * @param  groupId the primary key of the web content article's group
3219             * @param  articleId the primary key of the web content article
3220             * @param  version the web content article's version
3221             * @param  status the web content article's workflow status. For more
3222             *         information see {@link WorkflowConstants} for constants starting
3223             *         with the "STATUS_" prefix.
3224             * @return <code>true</code> if the specified web content article is the
3225             *         latest version; <code>false</code> otherwise
3226             * @throws PortalException if a matching web content article could not be
3227             *         found
3228             */
3229            @Override
3230            public boolean isLatestVersion(
3231                            long groupId, String articleId, double version, int status)
3232                    throws PortalException {
3233    
3234                    if (getLatestVersion(groupId, articleId, status) == version) {
3235                            return true;
3236                    }
3237                    else {
3238                            return false;
3239                    }
3240            }
3241    
3242            @Override
3243            public boolean isRenderable(
3244                    JournalArticle article, PortletRequestModel portletRequestModel,
3245                    ThemeDisplay themeDisplay) {
3246    
3247                    try {
3248                            getArticleDisplay(
3249                                    article, null, Constants.VIEW, article.getDefaultLanguageId(),
3250                                    0, portletRequestModel, themeDisplay, true);
3251                    }
3252                    catch (Exception e) {
3253                            return false;
3254                    }
3255    
3256                    return true;
3257            }
3258    
3259            /**
3260             * Moves the web content article matching the group and article ID to a new
3261             * folder.
3262             *
3263             * @param  groupId the primary key of the web content article's group
3264             * @param  articleId the primary key of the web content article
3265             * @param  newFolderId the primary key of the web content article's new
3266             *         folder
3267             * @return the updated web content article, which was moved to a new folder
3268             * @throws PortalException if a matching web content article could not be
3269             *         found
3270             */
3271            @Indexable(type = IndexableType.REINDEX)
3272            @Override
3273            public JournalArticle moveArticle(
3274                            long groupId, String articleId, long newFolderId)
3275                    throws PortalException {
3276    
3277                    JournalArticle latestArticle = getLatestArticle(groupId, articleId);
3278    
3279                    validateDDMStructureId(
3280                            groupId, newFolderId, latestArticle.getDDMStructureKey());
3281    
3282                    List<JournalArticle> articles = journalArticlePersistence.findByG_A(
3283                            groupId, articleId);
3284    
3285                    for (JournalArticle article : articles) {
3286                            article.setFolderId(newFolderId);
3287                            article.setTreePath(article.buildTreePath());
3288    
3289                            journalArticlePersistence.update(article);
3290                    }
3291    
3292                    return getArticle(groupId, articleId);
3293            }
3294    
3295            /**
3296             * Moves the web content article from the Recycle Bin to a new folder.
3297             *
3298             * @param  userId the primary key of the user updating the web content
3299             *         article
3300             * @param  groupId the primary key of the web content article's group
3301             * @param  article the web content article
3302             * @param  newFolderId the primary key of the web content article's new
3303             *         folder
3304             * @param  serviceContext the service context to be applied. Can set the
3305             *         modification date, portlet preferences, and can set whether to
3306             *         add the default command update for the web content article. With
3307             *         respect to social activities, by setting the service context's
3308             *         command to {@link
3309             *         com.liferay.portal.kernel.util.Constants#UPDATE}, the invocation
3310             *         is considered a web content update activity; otherwise it is
3311             *         considered a web content add activity.
3312             * @return the updated web content article, which was moved from the Recycle
3313             *         Bin to a new folder
3314             * @throws PortalException if a trashed web content article with the primary
3315             *         key could not be found or if a portal exception occurred
3316             */
3317            @Indexable(type = IndexableType.REINDEX)
3318            @Override
3319            public JournalArticle moveArticleFromTrash(
3320                            long userId, long groupId, JournalArticle article, long newFolderId,
3321                            ServiceContext serviceContext)
3322                    throws PortalException {
3323    
3324                    if (article.isInTrashExplicitly()) {
3325                            restoreArticleFromTrash(userId, article);
3326                    }
3327                    else {
3328    
3329                            // Article
3330    
3331                            TrashVersion trashVersion = trashVersionLocalService.fetchVersion(
3332                                    JournalArticle.class.getName(), article.getResourcePrimKey());
3333    
3334                            int status = WorkflowConstants.STATUS_APPROVED;
3335    
3336                            if (trashVersion != null) {
3337                                    status = trashVersion.getStatus();
3338                            }
3339    
3340                            updateStatus(
3341                                    userId, article, status, null, serviceContext,
3342                                    new HashMap<String, Serializable>());
3343    
3344                            // Trash
3345    
3346                            if (trashVersion != null) {
3347                                    trashVersionLocalService.deleteTrashVersion(trashVersion);
3348                            }
3349                    }
3350    
3351                    return moveArticle(groupId, article.getArticleId(), newFolderId);
3352            }
3353    
3354            /**
3355             * Moves the latest version of the web content article matching the group
3356             * and article ID to the recycle bin.
3357             *
3358             * @param  userId the primary key of the user updating the web content
3359             *         article
3360             * @param  article the web content article
3361             * @return the updated web content article, which was moved to the Recycle
3362             *         Bin
3363             * @throws PortalException if the user did not have permission to move the
3364             *         article to the Recycle Bin or if a portal exception occurred
3365             */
3366            @Indexable(type = IndexableType.REINDEX)
3367            @Override
3368            public JournalArticle moveArticleToTrash(
3369                            long userId, JournalArticle article)
3370                    throws PortalException {
3371    
3372                    // Article
3373    
3374                    int oldStatus = article.getStatus();
3375    
3376                    if (oldStatus == WorkflowConstants.STATUS_PENDING) {
3377                            article.setStatus(WorkflowConstants.STATUS_DRAFT);
3378    
3379                            journalArticlePersistence.update(article);
3380                    }
3381    
3382                    List<JournalArticle> articleVersions =
3383                            journalArticlePersistence.findByG_A(
3384                                    article.getGroupId(), article.getArticleId());
3385    
3386                    articleVersions = ListUtil.sort(
3387                            articleVersions, new ArticleVersionComparator());
3388    
3389                    List<ObjectValuePair<Long, Integer>> articleVersionStatusOVPs =
3390                            new ArrayList<ObjectValuePair<Long, Integer>>();
3391    
3392                    if ((articleVersions != null) && !articleVersions.isEmpty()) {
3393                            articleVersionStatusOVPs = getArticleVersionStatuses(
3394                                    articleVersions);
3395                    }
3396    
3397                    article = updateStatus(
3398                            userId, article.getId(), WorkflowConstants.STATUS_IN_TRASH,
3399                            new HashMap<String, Serializable>(), new ServiceContext());
3400    
3401                    // Trash
3402    
3403                    JournalArticleResource articleResource =
3404                            journalArticleResourceLocalService.getArticleResource(
3405                                    article.getResourcePrimKey());
3406    
3407                    UnicodeProperties typeSettingsProperties = new UnicodeProperties();
3408    
3409                    typeSettingsProperties.put("title", article.getArticleId());
3410    
3411                    TrashEntry trashEntry = trashEntryLocalService.addTrashEntry(
3412                            userId, article.getGroupId(), JournalArticle.class.getName(),
3413                            article.getResourcePrimKey(), articleResource.getUuid(), null,
3414                            oldStatus, articleVersionStatusOVPs, typeSettingsProperties);
3415    
3416                    String trashArticleId = TrashUtil.getTrashTitle(
3417                            trashEntry.getEntryId());
3418    
3419                    for (JournalArticle articleVersion : articleVersions) {
3420                            articleVersion.setArticleId(trashArticleId);
3421                            articleVersion.setStatus(WorkflowConstants.STATUS_IN_TRASH);
3422    
3423                            journalArticlePersistence.update(articleVersion);
3424                    }
3425    
3426                    articleResource.setArticleId(trashArticleId);
3427    
3428                    journalArticleResourcePersistence.update(articleResource);
3429    
3430                    article.setArticleId(trashArticleId);
3431    
3432                    article = journalArticlePersistence.update(article);
3433    
3434                    // Asset
3435    
3436                    assetEntryLocalService.updateVisible(
3437                            JournalArticle.class.getName(), article.getResourcePrimKey(),
3438                            false);
3439    
3440                    // Social
3441    
3442                    JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();
3443    
3444                    extraDataJSONObject.put("title", article.getTitle());
3445    
3446                    socialActivityLocalService.addActivity(
3447                            userId, article.getGroupId(), JournalArticle.class.getName(),
3448                            article.getResourcePrimKey(),
3449                            SocialActivityConstants.TYPE_MOVE_TO_TRASH,
3450                            extraDataJSONObject.toString(), 0);
3451    
3452                    if (oldStatus == WorkflowConstants.STATUS_PENDING) {
3453                            workflowInstanceLinkLocalService.deleteWorkflowInstanceLink(
3454                                    article.getCompanyId(), article.getGroupId(),
3455                                    JournalArticle.class.getName(), article.getId());
3456                    }
3457    
3458                    return article;
3459            }
3460    
3461            /**
3462             * Moves the latest version of the web content article matching the group
3463             * and article ID to the recycle bin.
3464             *
3465             * @param  userId the primary key of the user updating the web content
3466             *         article
3467             * @param  groupId the primary key of the web content article's group
3468             * @param  articleId the primary key of the web content article
3469             * @return the moved web content article or <code>null</code> if no matching
3470             *         article was found
3471             * @throws PortalException if the user did not have permission to move the
3472             *         article to the Recycle Bin or if a portal exception occurred
3473             */
3474            @Override
3475            public JournalArticle moveArticleToTrash(
3476                            long userId, long groupId, String articleId)
3477                    throws PortalException {
3478    
3479                    List<JournalArticle> articles = journalArticlePersistence.findByG_A(
3480                            groupId, articleId, 0, 1, new ArticleVersionComparator());
3481    
3482                    if (!articles.isEmpty()) {
3483                            return journalArticleLocalService.moveArticleToTrash(
3484                                    userId, articles.get(0));
3485                    }
3486    
3487                    return null;
3488            }
3489    
3490            /**
3491             * Rebuilds the web content article's tree path using tree traversal.
3492             *
3493             * <p>
3494             * For example, here is a conceptualization of a web content article tree
3495             * path:
3496             * </p>
3497             *
3498             * <p>
3499             * <pre>
3500             * <code>
3501             * /(Folder's folderId)/(Subfolder's folderId)/(article's articleId)
3502             * </code>
3503             * </pre>
3504             * </p>
3505             *
3506             * @param  companyId the primary key of the web content article's company
3507             * @throws PortalException if a portal exception occurred
3508             */
3509            @Override
3510            public void rebuildTree(long companyId) throws PortalException {
3511                    journalFolderLocalService.rebuildTree(companyId);
3512            }
3513    
3514            /**
3515             * Removes the web content of the web content article matching the group,
3516             * article ID, and version, and language.
3517             *
3518             * @param  groupId the primary key of the web content article's group
3519             * @param  articleId the primary key of the web content article
3520             * @param  version the web content article's version
3521             * @param  languageId the primary key of the language locale to remove
3522             * @return the updated web content article with the locale removed
3523             * @throws PortalException if a matching web content article could not be
3524             *         found
3525             */
3526            @Indexable(type = IndexableType.REINDEX)
3527            @Override
3528            public JournalArticle removeArticleLocale(
3529                            long groupId, String articleId, double version, String languageId)
3530                    throws PortalException {
3531    
3532                    JournalArticle article = journalArticlePersistence.findByG_A_V(
3533                            groupId, articleId, version);
3534    
3535                    String title = article.getTitle();
3536    
3537                    title = LocalizationUtil.removeLocalization(
3538                            title, "static-content", languageId, true);
3539    
3540                    article.setTitle(title);
3541    
3542                    String description = article.getDescription();
3543    
3544                    description = LocalizationUtil.removeLocalization(
3545                            description, "static-content", languageId, true);
3546    
3547                    article.setDescription(description);
3548    
3549                    String content = article.getContent();
3550    
3551                    Document document = article.getDocument();
3552    
3553                    if (document != null) {
3554                            content = JournalUtil.removeArticleLocale(
3555                                    document, content, languageId);
3556    
3557                            article.setContent(content);
3558                    }
3559    
3560                    journalArticlePersistence.update(article);
3561    
3562                    return article;
3563            }
3564    
3565            /**
3566             * Restores the web content article from the Recycle Bin.
3567             *
3568             * @param  userId the primary key of the user restoring the web content
3569             *         article
3570             * @param  article the web content article
3571             * @return the restored web content article from the Recycle Bin
3572             * @throws PortalException if the web content article with the primary key
3573             *         could not be found in the Recycle Bin, if the user did not have
3574             *         permission to restore the article, or if a portal exception
3575             *         occurred
3576             */
3577            @Indexable(type = IndexableType.REINDEX)
3578            @Override
3579            public JournalArticle restoreArticleFromTrash(
3580                            long userId, JournalArticle article)
3581                    throws PortalException {
3582    
3583                    // Article
3584    
3585                    String trashArticleId = TrashUtil.getOriginalTitle(
3586                            article.getArticleId());
3587    
3588                    List<JournalArticle> articleVersions =
3589                            journalArticlePersistence.findByG_A(
3590                                    article.getGroupId(), article.getArticleId());
3591    
3592                    for (JournalArticle articleVersion : articleVersions) {
3593                            articleVersion.setArticleId(trashArticleId);
3594    
3595                            journalArticlePersistence.update(articleVersion);
3596                    }
3597    
3598                    article.setArticleId(trashArticleId);
3599    
3600                    journalArticlePersistence.update(article);
3601    
3602                    JournalArticleResource articleResource =
3603                            journalArticleResourcePersistence.fetchByPrimaryKey(
3604                                    article.getResourcePrimKey());
3605    
3606                    articleResource.setArticleId(trashArticleId);
3607    
3608                    journalArticleResourcePersistence.update(articleResource);
3609    
3610                    TrashEntry trashEntry = trashEntryLocalService.getEntry(
3611                            JournalArticle.class.getName(), article.getResourcePrimKey());
3612    
3613                    ServiceContext serviceContext = new ServiceContext();
3614    
3615                    serviceContext.setScopeGroupId(article.getGroupId());
3616    
3617                    updateStatus(
3618                            userId, article, trashEntry.getStatus(), null, serviceContext,
3619                            new HashMap<String, Serializable>());
3620    
3621                    // Trash
3622    
3623                    List<TrashVersion> trashVersions = trashVersionLocalService.getVersions(
3624                            trashEntry.getEntryId());
3625    
3626                    for (TrashVersion trashVersion : trashVersions) {
3627                            JournalArticle trashArticleVersion =
3628                                    journalArticlePersistence.findByPrimaryKey(
3629                                            trashVersion.getClassPK());
3630    
3631                            trashArticleVersion.setStatus(trashVersion.getStatus());
3632    
3633                            journalArticlePersistence.update(trashArticleVersion);
3634                    }
3635    
3636                    trashEntryLocalService.deleteEntry(
3637                            JournalArticle.class.getName(), article.getResourcePrimKey());
3638    
3639                    // Social
3640    
3641                    JSONObject extraDataJSONObject = JSONFactoryUtil.createJSONObject();
3642    
3643                    extraDataJSONObject.put("title", article.getTitle());
3644    
3645                    socialActivityLocalService.addActivity(
3646                            userId, article.getGroupId(), JournalArticle.class.getName(),
3647                            article.getResourcePrimKey(),
3648                            SocialActivityConstants.TYPE_RESTORE_FROM_TRASH,
3649                            extraDataJSONObject.toString(), 0);
3650    
3651                    return article;
3652            }
3653    
3654            /**
3655             * Returns a range of all the web content articles matching the parameters
3656             * without using the indexer. It is preferable to use the indexed version
3657             * {@link #search(long, long, long, int, int, int)} instead of this method
3658             * wherever possible for performance reasons.
3659             *
3660             * <p>
3661             * Useful when paginating results. Returns a maximum of <code>end -
3662             * start</code> instances. <code>start</code> and <code>end</code> are not
3663             * primary keys, they are indexes in the result set. Thus, <code>0</code>
3664             * refers to the first result in the set. Setting both <code>start</code>
3665             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
3666             * result set.
3667             * </p>
3668             *
3669             * @param  groupId the primary key of the group (optionally <code>0</code>)
3670             * @param  folderIds the primary keys of the web content article folders
3671             *         (optionally {@link java.util.Collections#EMPTY_LIST})
3672             * @param  status the web content article's workflow status. For more
3673             *         information see {@link WorkflowConstants} for constants starting
3674             *         with the "STATUS_" prefix.
3675             * @param  start the lower bound of the range of web content articles to
3676             *         return
3677             * @param  end the upper bound of the range of web content articles to
3678             *         return (not inclusive)
3679             * @return the matching web content articles
3680             */
3681            @Override
3682            public List<JournalArticle> search(
3683                    long groupId, List<Long> folderIds, int status, int start, int end) {
3684    
3685                    QueryDefinition<JournalArticle> queryDefinition =
3686                            new QueryDefinition<JournalArticle>(status, start, end, null);
3687    
3688                    return journalArticleFinder.findByG_F(
3689                            groupId, folderIds, queryDefinition);
3690            }
3691    
3692            /**
3693             * Returns a range of all the web content articles in a single folder
3694             * matching the parameters without using the indexer. It is preferable to
3695             * use the indexed version {@link #search(long, long, long, int, int, int)}
3696             * instead of this method wherever possible for performance reasons.
3697             *
3698             * <p>
3699             * Useful when paginating results. Returns a maximum of <code>end -
3700             * start</code> instances. <code>start</code> and <code>end</code> are not
3701             * primary keys, they are indexes in the result set. Thus, <code>0</code>
3702             * refers to the first result in the set. Setting both <code>start</code>
3703             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
3704             * result set.
3705             * </p>
3706             *
3707             * @param  groupId the primary key of the group (optionally <code>0</code>)
3708             * @param  folderId the primary key of the web content article folder
3709             * @param  status the web content article's workflow status. For more
3710             *         information see {@link WorkflowConstants} for constants starting
3711             *         with the "STATUS_" prefix.
3712             * @param  start the lower bound of the range of web content articles to
3713             *         return
3714             * @param  end the upper bound of the range of web content articles to
3715             *         return (not inclusive)
3716             * @return the matching web content articles
3717             */
3718            @Override
3719            public List<JournalArticle> search(
3720                    long groupId, long folderId, int status, int start, int end) {
3721    
3722                    List<Long> folderIds = new ArrayList<Long>();
3723    
3724                    folderIds.add(folderId);
3725    
3726                    return search(groupId, folderIds, status, start, end);
3727            }
3728    
3729            /**
3730             * Returns an ordered range of all the web content articles matching the
3731             * parameters without using the indexer, including a keywords parameter for
3732             * matching with the article's ID, title, description, and content, a DDM
3733             * structure key parameter, and a DDM template key parameter. It is
3734             * preferable to use the indexed version {@link #search(long, long, List,
3735             * long, String, String, String, LinkedHashMap, int, int, Sort)} instead of
3736             * this method wherever possible for performance reasons.
3737             *
3738             * <p>
3739             * Useful when paginating results. Returns a maximum of <code>end -
3740             * start</code> instances. <code>start</code> and <code>end</code> are not
3741             * primary keys, they are indexes in the result set. Thus, <code>0</code>
3742             * refers to the first result in the set. Setting both <code>start</code>
3743             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
3744             * result set.
3745             * </p>
3746             *
3747             * @param  companyId the primary key of the web content article's company
3748             * @param  groupId the primary key of the group (optionally <code>0</code>)
3749             * @param  folderIds the primary keys of the web content article folders
3750             *         (optionally {@link java.util.Collections#EMPTY_LIST})
3751             * @param  classNameId the primary key of the DDMStructure class if the web
3752             *         content article is related to a DDM structure, the primary key of
3753             *         the class name associated with the article, or {@link
3754             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
3755             * @param  keywords the keywords (space separated), which may occur in the
3756             *         web content article ID, title, description, or content
3757             *         (optionally <code>null</code>). If the keywords value is not
3758             *         <code>null</code>, the search uses the OR operator in connecting
3759             *         query criteria; otherwise it uses the AND operator.
3760             * @param  version the web content article's version (optionally
3761             *         <code>null</code>)
3762             * @param  ddmStructureKey the primary key of the web content article's DDM
3763             *         structure, if the article is related to a DDM structure, or
3764             *         <code>null</code> otherwise
3765             * @param  ddmTemplateKey the primary key of the web content article's DDM
3766             *         template
3767             * @param  displayDateGT the date after which a matching web content
3768             *         article's display date must be after (optionally
3769             *         <code>null</code>)
3770             * @param  displayDateLT the date before which a matching web content
3771             *         article's display date must be before (optionally
3772             *         <code>null</code>)
3773             * @param  status the web content article's workflow status. For more
3774             *         information see {@link WorkflowConstants} for constants starting
3775             *         with the "STATUS_" prefix.
3776             * @param  reviewDate the web content article's scheduled review date
3777             *         (optionally <code>null</code>)
3778             * @param  start the lower bound of the range of web content articles to
3779             *         return
3780             * @param  end the upper bound of the range of web content articles to
3781             *         return (not inclusive)
3782             * @param  obc the comparator to order the web content articles
3783             * @return the range of matching web content articles ordered by the
3784             *         comparator
3785             */
3786            @Override
3787            public List<JournalArticle> search(
3788                    long companyId, long groupId, List<Long> folderIds, long classNameId,
3789                    String keywords, Double version, String ddmStructureKey,
3790                    String ddmTemplateKey, Date displayDateGT, Date displayDateLT,
3791                    int status, Date reviewDate, int start, int end,
3792                    OrderByComparator<JournalArticle> obc) {
3793    
3794                    return journalArticleFinder.findByKeywords(
3795                            companyId, groupId, folderIds, classNameId, keywords, version,
3796                            ddmStructureKey, ddmTemplateKey, displayDateGT, displayDateLT,
3797                            status, reviewDate, start, end, obc);
3798            }
3799    
3800            /**
3801             * Returns an ordered range of all the web content articles matching the
3802             * parameters without using the indexer, including keyword parameters for
3803             * article ID, title, description, and content, a DDM structure key
3804             * parameter, a DDM template key parameter, and an AND operator switch. It
3805             * is preferable to use the indexed version {@link #search(long, long, List,
3806             * long, String, String, String, String, int, String, String, LinkedHashMap,
3807             * boolean, int, int, Sort)} instead of this method wherever possible for
3808             * performance reasons.
3809             *
3810             * <p>
3811             * Useful when paginating results. Returns a maximum of <code>end -
3812             * start</code> instances. <code>start</code> and <code>end</code> are not
3813             * primary keys, they are indexes in the result set. Thus, <code>0</code>
3814             * refers to the first result in the set. Setting both <code>start</code>
3815             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
3816             * result set.
3817             * </p>
3818             *
3819             * @param  companyId the primary key of the web content article's company
3820             * @param  groupId the primary key of the group (optionally <code>0</code>)
3821             * @param  folderIds the primary keys of the web content article folders
3822             *         (optionally {@link java.util.Collections#EMPTY_LIST})
3823             * @param  classNameId the primary key of the DDMStructure class if the web
3824             *         content article is related to a DDM structure, the primary key of
3825             *         the class name associated with the article, or {@link
3826             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
3827             * @param  articleId the article ID keywords (space separated, optionally
3828             *         <code>null</code>)
3829             * @param  version the web content article's version (optionally
3830             *         <code>null</code>)
3831             * @param  title the title keywords (space separated, optionally
3832             *         <code>null</code>)
3833             * @param  description the description keywords (space separated, optionally
3834             *         <code>null</code>)
3835             * @param  content the content keywords (space separated, optionally
3836             *         <code>null</code>)
3837             * @param  ddmStructureKey the primary key of the web content article's DDM
3838             *         structure, if the article is related to a DDM structure, or
3839             *         <code>null</code> otherwise
3840             * @param  ddmTemplateKey the primary key of the web content article's DDM
3841             *         template
3842             * @param  displayDateGT the date after which a matching web content
3843             *         article's display date must be after (optionally
3844             *         <code>null</code>)
3845             * @param  displayDateLT the date before which a matching web content
3846             *         article's display date must be before (optionally
3847             *         <code>null</code>)
3848             * @param  status the web content article's workflow status. For more
3849             *         information see {@link WorkflowConstants} for constants starting
3850             *         with the "STATUS_" prefix.
3851             * @param  reviewDate the web content article's scheduled review date
3852             *         (optionally <code>null</code>)
3853             * @param  andOperator whether every field must match its value or keywords,
3854             *         or just one field must match. Company, group, folder IDs, class
3855             *         name ID, and status must all match their values.
3856             * @param  start the lower bound of the range of web content articles to
3857             *         return
3858             * @param  end the upper bound of the range of web content articles to
3859             *         return (not inclusive)
3860             * @param  obc the comparator to order the web content articles
3861             * @return the range of matching web content articles ordered by the
3862             *         comparator
3863             */
3864            @Override
3865            public List<JournalArticle> search(
3866                    long companyId, long groupId, List<Long> folderIds, long classNameId,
3867                    String articleId, Double version, String title, String description,
3868                    String content, String ddmStructureKey, String ddmTemplateKey,
3869                    Date displayDateGT, Date displayDateLT, int status, Date reviewDate,
3870                    boolean andOperator, int start, int end,
3871                    OrderByComparator<JournalArticle> obc) {
3872    
3873                    QueryDefinition<JournalArticle> queryDefinition =
3874                            new QueryDefinition<JournalArticle>(status, start, end, obc);
3875    
3876                    return journalArticleFinder.findByC_G_F_C_A_V_T_D_C_S_T_D_R(
3877                            companyId, groupId, folderIds, classNameId, articleId, version,
3878                            title, description, content, ddmStructureKey, ddmTemplateKey,
3879                            displayDateGT, displayDateLT, reviewDate, andOperator,
3880                            queryDefinition);
3881            }
3882    
3883            /**
3884             * Returns an ordered range of all the web content articles matching the
3885             * parameters without using the indexer, including keyword parameters for
3886             * article ID, title, description, and content, a DDM structure keys
3887             * (plural) parameter, a DDM template keys (plural) parameter, and an AND
3888             * operator switch.
3889             *
3890             * <p>
3891             * Useful when paginating results. Returns a maximum of <code>end -
3892             * start</code> instances. <code>start</code> and <code>end</code> are not
3893             * primary keys, they are indexes in the result set. Thus, <code>0</code>
3894             * refers to the first result in the set. Setting both <code>start</code>
3895             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
3896             * result set.
3897             * </p>
3898             *
3899             * @param  companyId the primary key of the web content article's company
3900             * @param  groupId the primary key of the group (optionally <code>0</code>)
3901             * @param  folderIds the primary keys of the web content article folders
3902             *         (optionally {@link java.util.Collections#EMPTY_LIST})
3903             * @param  classNameId the primary key of the DDMStructure class if the web
3904             *         content article is related to a DDM structure, the primary key of
3905             *         the class name associated with the article, or {@link
3906             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
3907             * @param  articleId the article ID keywords (space separated, optionally
3908             *         <code>null</code>)
3909             * @param  version the web content article's version (optionally
3910             *         <code>null</code>)
3911             * @param  title the title keywords (space separated, optionally
3912             *         <code>null</code>)
3913             * @param  description the description keywords (space separated, optionally
3914             *         <code>null</code>)
3915             * @param  content the content keywords (space separated, optionally
3916             *         <code>null</code>)
3917             * @param  ddmStructureKeys the primary keys of the web content article's
3918             *         DDM structures, if the article is related to a DDM structure, or
3919             *         <code>null</code> otherwise
3920             * @param  ddmTemplateKeys the primary keys of the web content article's DDM
3921             *         templates (originally <code>null</code>). If the articles are
3922             *         related to a DDM structure, the template's structure must match
3923             *         it.
3924             * @param  displayDateGT the date after which a matching web content
3925             *         article's display date must be after (optionally
3926             *         <code>null</code>)
3927             * @param  displayDateLT the date before which a matching web content
3928             *         article's display date must be before (optionally
3929             *         <code>null</code>)
3930             * @param  status the web content article's workflow status. For more
3931             *         information see {@link WorkflowConstants} for constants starting
3932             *         with the "STATUS_" prefix.
3933             * @param  reviewDate the web content article's scheduled review date
3934             *         (optionally <code>null</code>)
3935             * @param  andOperator whether every field must match its value or keywords,
3936             *         or just one field must match.  Company, group, folder IDs, class
3937             *         name ID, and status must all match their values.
3938             * @param  start the lower bound of the range of web content articles to
3939             *         return
3940             * @param  end the upper bound of the range of web content articles to
3941             *         return (not inclusive)
3942             * @param  obc the comparator to order the web content articles
3943             * @return the range of matching web content articles ordered by the
3944             *         comparator
3945             */
3946            @Override
3947            public List<JournalArticle> search(
3948                    long companyId, long groupId, List<Long> folderIds, long classNameId,
3949                    String articleId, Double version, String title, String description,
3950                    String content, String[] ddmStructureKeys, String[] ddmTemplateKeys,
3951                    Date displayDateGT, Date displayDateLT, int status, Date reviewDate,
3952                    boolean andOperator, int start, int end,
3953                    OrderByComparator<JournalArticle> obc) {
3954    
3955                    QueryDefinition<JournalArticle> queryDefinition =
3956                            new QueryDefinition<JournalArticle>(status, start, end, obc);
3957    
3958                    return journalArticleFinder.findByC_G_F_C_A_V_T_D_C_S_T_D_R(
3959                            companyId, groupId, folderIds, classNameId, articleId, version,
3960                            title, description, content, ddmStructureKeys, ddmTemplateKeys,
3961                            displayDateGT, displayDateLT, reviewDate, andOperator,
3962                            queryDefinition);
3963            }
3964    
3965            /**
3966             * Returns an ordered range of all the web content articles matching the
3967             * parameters using the indexer, including a keywords parameter for matching
3968             * an article's ID, title, description, or content, a DDM structure key
3969             * parameter, a DDM template key parameter, and a finder hash map parameter.
3970             * It is preferable to use this method instead of the non-indexed version
3971             * whenever possible for performance reasons.
3972             *
3973             * <p>
3974             * Useful when paginating results. Returns a maximum of <code>end -
3975             * start</code> instances. <code>start</code> and <code>end</code> are not
3976             * primary keys, they are indexes in the result set. Thus, <code>0</code>
3977             * refers to the first result in the set. Setting both <code>start</code>
3978             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
3979             * result set.
3980             * </p>
3981             *
3982             * @param  companyId the primary key of the web content article's company
3983             * @param  groupId the primary key of the group (optionally <code>0</code>)
3984             * @param  folderIds the primary keys of the web content article folders
3985             *         (optionally {@link java.util.Collections#EMPTY_LIST})
3986             * @param  classNameId the primary key of the DDMStructure class if the web
3987             *         content article is related to a DDM structure, the primary key of
3988             *         the class name associated with the article, or {@link
3989             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
3990             * @param  ddmStructureKey the primary key of the web content article's DDM
3991             *         structure, if the article is related to a DDM structure, or
3992             *         <code>null</code> otherwise
3993             * @param  ddmTemplateKey the primary key of the web content article's DDM
3994             *         template
3995             * @param  keywords the keywords (space separated), which may occur in the
3996             *         web content article ID, title, description, or content
3997             *         (optionally <code>null</code>). If the keywords value is not
3998             *         <code>null</code>, the search uses the OR operator in connecting
3999             *         query criteria; otherwise it uses the AND operator.
4000             * @param  params the finder parameters (optionally <code>null</code>)
4001             * @param  start the lower bound of the range of web content articles to
4002             *         return
4003             * @param  end the upper bound of the range of web content articles to
4004             *         return (not inclusive)
4005             * @param  sort the field, type, and direction by which to sort (optionally
4006             *         <code>null</code>)
4007             * @return the matching web content articles ordered by <code>sort</code>
4008             */
4009            @Override
4010            public Hits search(
4011                    long companyId, long groupId, List<Long> folderIds, long classNameId,
4012                    String ddmStructureKey, String ddmTemplateKey, String keywords,
4013                    LinkedHashMap<String, Object> params, int start, int end, Sort sort) {
4014    
4015                    String articleId = null;
4016                    String title = null;
4017                    String description = null;
4018                    String content = null;
4019                    boolean andOperator = false;
4020    
4021                    if (Validator.isNotNull(keywords)) {
4022                            articleId = keywords;
4023                            title = keywords;
4024                            description = keywords;
4025                            content = keywords;
4026                    }
4027                    else {
4028                            andOperator = true;
4029                    }
4030    
4031                    if (params != null) {
4032                            params.put("keywords", keywords);
4033                    }
4034    
4035                    return search(
4036                            companyId, groupId, folderIds, classNameId, articleId, title,
4037                            description, content, WorkflowConstants.STATUS_ANY, ddmStructureKey,
4038                            ddmTemplateKey, params, andOperator, start, end, sort);
4039            }
4040    
4041            /**
4042             * Returns an ordered range of all the web content articles matching the
4043             * parameters using the indexer, including a keywords parameter for matching
4044             * an article's ID, title, description, or content, a DDM structure key
4045             * parameter, a DDM template key parameter, an AND operator switch, and
4046             * parameters for type, status, a finder hash map. It is preferable to use
4047             * this method instead of the non-indexed version whenever possible for
4048             * performance reasons.
4049             *
4050             * <p>
4051             * Useful when paginating results. Returns a maximum of <code>end -
4052             * start</code> instances. <code>start</code> and <code>end</code> are not
4053             * primary keys, they are indexes in the result set. Thus, <code>0</code>
4054             * refers to the first result in the set. Setting both <code>start</code>
4055             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
4056             * result set.
4057             * </p>
4058             *
4059             * @param  companyId the primary key of the web content article's company
4060             * @param  groupId the primary key of the group (optionally <code>0</code>)
4061             * @param  folderIds the primary keys of the web content article folders
4062             *         (optionally {@link java.util.Collections#EMPTY_LIST})
4063             * @param  classNameId the primary key of the DDMStructure class if the web
4064             *         content article is related to a DDM structure, the primary key of
4065             *         the class name associated with the article, or {@link
4066             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
4067             * @param  articleId the article ID keywords (space separated, optionally
4068             *         <code>null</code>)
4069             * @param  title the title keywords (space separated, optionally
4070             *         <code>null</code>)
4071             * @param  description the description keywords (space separated, optionally
4072             *         <code>null</code>)
4073             * @param  content the content keywords (space separated, optionally
4074             *         <code>null</code>)
4075             * @param  status the web content article's workflow status. For more
4076             *         information see {@link WorkflowConstants} for constants starting
4077             *         with the "STATUS_" prefix.
4078             * @param  ddmStructureKey the primary key of the web content article's DDM
4079             *         structure, if the article is related to a DDM structure, or
4080             *         <code>null</code> otherwise
4081             * @param  ddmTemplateKey the primary key of the web content article's DDM
4082             *         template
4083             * @param  params the finder parameters (optionally <code>null</code>). The
4084             *         <code>includeDiscussions</code> parameter can be set to
4085             *         <code>true</code> to search for the keywords in the web content
4086             *         article discussions.
4087             * @param  andSearch whether every field must match its value or keywords,
4088             *         or just one field must match
4089             * @param  start the lower bound of the range of web content articles to
4090             *         return
4091             * @param  end the upper bound of the range of web content articles to
4092             *         return (not inclusive)
4093             * @param  sort the field, type, and direction by which to sort (optionally
4094             *         <code>null</code>)
4095             * @return the matching web content articles ordered by <code>sort</code>
4096             */
4097            @Override
4098            public Hits search(
4099                    long companyId, long groupId, List<Long> folderIds, long classNameId,
4100                    String articleId, String title, String description, String content,
4101                    int status, String ddmStructureKey, String ddmTemplateKey,
4102                    LinkedHashMap<String, Object> params, boolean andSearch, int start,
4103                    int end, Sort sort) {
4104    
4105                    try {
4106                            Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
4107                                    JournalArticle.class);
4108    
4109                            SearchContext searchContext = buildSearchContext(
4110                                    companyId, groupId, folderIds, classNameId, articleId, title,
4111                                    description, content, status, ddmStructureKey, ddmTemplateKey,
4112                                    params, andSearch, start, end, sort);
4113    
4114                            return indexer.search(searchContext);
4115                    }
4116                    catch (Exception e) {
4117                            throw new SystemException(e);
4118                    }
4119            }
4120    
4121            /**
4122             * @deprecated As of 7.0.0, replaced by {@link #search(long, long, List,
4123             *             long, String, String, String, String, int, String, String,
4124             *             LinkedHashMap, boolean, int, int, Sort)}
4125             */
4126            @Deprecated
4127            @Override
4128            public Hits search(
4129                    long companyId, long groupId, List<Long> folderIds, long classNameId,
4130                    String articleId, String title, String description, String content,
4131                    String type, String statusString, String ddmStructureKey,
4132                    String ddmTemplateKey, LinkedHashMap<String, Object> params,
4133                    boolean andSearch, int start, int end, Sort sort) {
4134    
4135                    int status = GetterUtil.getInteger(statusString);
4136    
4137                    return search(
4138                            companyId, groupId, folderIds, classNameId, articleId, title,
4139                            description, content, status, ddmStructureKey, ddmTemplateKey,
4140                            params, andSearch, start, end, sort);
4141            }
4142    
4143            /**
4144             * Returns a range of all the web content articles matching the group,
4145             * creator, and status using the indexer. It is preferable to use this
4146             * method instead of the non-indexed version whenever possible for
4147             * performance reasons.
4148             *
4149             * <p>
4150             * Useful when paginating results. Returns a maximum of <code>end -
4151             * start</code> instances. <code>start</code> and <code>end</code> are not
4152             * primary keys, they are indexes in the result set. Thus, <code>0</code>
4153             * refers to the first result in the set. Setting both <code>start</code>
4154             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
4155             * result set.
4156             * </p>
4157             *
4158             * @param  groupId the primary key of the group (optionally <code>0</code>)
4159             * @param  userId the primary key of the user searching for web content
4160             *         articles
4161             * @param  creatorUserId the primary key of the web content article's
4162             *         creator
4163             * @param  status the web content article's workflow status. For more
4164             *         information see {@link WorkflowConstants} for constants starting
4165             *         with the "STATUS_" prefix.
4166             * @param  start the lower bound of the range of web content articles to
4167             *         return
4168             * @param  end the upper bound of the range of web content articles to
4169             *         return (not inclusive)
4170             * @return the matching web content articles
4171             * @throws PortalException if a portal exception occurred
4172             */
4173            @Override
4174            public Hits search(
4175                            long groupId, long userId, long creatorUserId, int status,
4176                            int start, int end)
4177                    throws PortalException {
4178    
4179                    Indexer indexer = IndexerRegistryUtil.getIndexer(
4180                            JournalArticle.class.getName());
4181    
4182                    SearchContext searchContext = buildSearchContext(
4183                            groupId, userId, creatorUserId, status, start, end);
4184    
4185                    return indexer.search(searchContext);
4186            }
4187    
4188            /**
4189             * Returns the number of web content articles matching the group, folders,
4190             * and status.
4191             *
4192             * @param  groupId the primary key of the group (optionally <code>0</code>)
4193             * @param  folderIds the primary keys of the web content article folders
4194             *         (optionally {@link java.util.Collections#EMPTY_LIST})
4195             * @param  status the web content article's workflow status. For more
4196             *         information see {@link WorkflowConstants} for constants starting
4197             *         with the "STATUS_" prefix.
4198             * @return the number of matching web content articles
4199             */
4200            @Override
4201            public int searchCount(long groupId, List<Long> folderIds, int status) {
4202                    QueryDefinition<JournalArticle> queryDefinition =
4203                            new QueryDefinition<JournalArticle>(status);
4204    
4205                    return journalArticleFinder.countByG_F(
4206                            groupId, folderIds, queryDefinition);
4207            }
4208    
4209            /**
4210             * Returns the number of web content articles matching the group, folder,
4211             * and status.
4212             *
4213             * @param  groupId the primary key of the group (optionally <code>0</code>)
4214             * @param  folderId the primary key of the web content article folder
4215             * @param  status the web content article's workflow status. For more
4216             *         information see {@link WorkflowConstants} for constants starting
4217             *         with the "STATUS_" prefix.
4218             * @return the number of matching web content articles
4219             */
4220            @Override
4221            public int searchCount(long groupId, long folderId, int status) {
4222                    List<Long> folderIds = new ArrayList<Long>();
4223    
4224                    folderIds.add(folderId);
4225    
4226                    return searchCount(groupId, folderIds, status);
4227            }
4228    
4229            /**
4230             * Returns the number of web content articles matching the parameters,
4231             * including a keywords parameter for matching with the article's ID, title,
4232             * description, and content, a DDM structure key parameter, and a DDM
4233             * template key parameter.
4234             *
4235             * @param  companyId the primary key of the web content article's company
4236             * @param  groupId the primary key of the group (optionally <code>0</code>)
4237             * @param  folderIds the primary keys of the web content article folders
4238             *         (optionally {@link java.util.Collections#EMPTY_LIST})
4239             * @param  classNameId the primary key of the DDMStructure class if the web
4240             *         content article is related to a DDM structure, the primary key of
4241             *         the class name associated with the article, or {@link
4242             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
4243             * @param  keywords the keywords (space separated), which may occur in the
4244             *         web content article ID, title, description, or content
4245             *         (optionally <code>null</code>). If the keywords value is not
4246             *         <code>null</code>, the search uses the OR operator in connecting
4247             *         query criteria; otherwise it uses the AND operator.
4248             * @param  version the web content article's version (optionally
4249             *         <code>null</code>)
4250             * @param  ddmStructureKey the primary key of the web content article's DDM
4251             *         structure, if the article is related to a DDM structure, or
4252             *         <code>null</code> otherwise
4253             * @param  ddmTemplateKey the primary key of the web content article's DDM
4254             *         template
4255             * @param  displayDateGT the date after which a matching web content
4256             *         article's display date must be after (optionally
4257             *         <code>null</code>)
4258             * @param  displayDateLT the date before which a matching web content
4259             *         article's display date must be before (optionally
4260             *         <code>null</code>)
4261             * @param  status the web content article's workflow status. For more
4262             *         information see {@link WorkflowConstants} for constants starting
4263             *         with the "STATUS_" prefix.
4264             * @param  reviewDate the web content article's scheduled review date
4265             *         (optionally <code>null</code>)
4266             * @return the number of matching web content articles
4267             */
4268            @Override
4269            public int searchCount(
4270                    long companyId, long groupId, List<Long> folderIds, long classNameId,
4271                    String keywords, Double version, String ddmStructureKey,
4272                    String ddmTemplateKey, Date displayDateGT, Date displayDateLT,
4273                    int status, Date reviewDate) {
4274    
4275                    return journalArticleFinder.countByKeywords(
4276                            companyId, groupId, folderIds, classNameId, keywords, version,
4277                            ddmStructureKey, ddmTemplateKey, displayDateGT, displayDateLT,
4278                            status, reviewDate);
4279            }
4280    
4281            /**
4282             * Returns the number of web content articles matching the parameters,
4283             * including keyword parameters for article ID, title, description, and
4284             * content, a DDM structure key parameter, a DDM template key parameter, and
4285             * an AND operator switch.
4286             *
4287             * @param  companyId the primary key of the web content article's company
4288             * @param  groupId the primary key of the group (optionally <code>0</code>)
4289             * @param  folderIds the primary keys of the web content article folders
4290             *         (optionally {@link java.util.Collections#EMPTY_LIST})
4291             * @param  classNameId the primary key of the DDMStructure class if the web
4292             *         content article is related to a DDM structure, the primary key of
4293             *         the class name associated with the article, or {@link
4294             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
4295             * @param  articleId the article ID keywords (space separated, optionally
4296             *         <code>null</code>)
4297             * @param  version the web content article's version (optionally
4298             *         <code>null</code>)
4299             * @param  title the title keywords (space separated, optionally
4300             *         <code>null</code>)
4301             * @param  description the description keywords (space separated, optionally
4302             *         <code>null</code>)
4303             * @param  content the content keywords (space separated, optionally
4304             *         <code>null</code>)
4305             * @param  ddmStructureKey the primary key of the web content article's DDM
4306             *         structure, if the article is related to a DDM structure, or
4307             *         <code>null</code> otherwise
4308             * @param  ddmTemplateKey the primary key of the web content article's DDM
4309             *         template
4310             * @param  displayDateGT the date after which a matching web content
4311             *         article's display date must be after (optionally
4312             *         <code>null</code>)
4313             * @param  displayDateLT the date before which a matching web content
4314             *         article's display date must be before (optionally
4315             *         <code>null</code>)
4316             * @param  status the web content article's workflow status. For more
4317             *         information see {@link WorkflowConstants} for constants starting
4318             *         with the "STATUS_" prefix.
4319             * @param  reviewDate the web content article's scheduled review date
4320             *         (optionally <code>null</code>)
4321             * @param  andOperator whether every field must match its value or keywords,
4322             *         or just one field must match. Group, folder IDs, class name ID,
4323             *         and status must all match their values.
4324             * @return the number of matching web content articles
4325             */
4326            @Override
4327            public int searchCount(
4328                    long companyId, long groupId, List<Long> folderIds, long classNameId,
4329                    String articleId, Double version, String title, String description,
4330                    String content, String ddmStructureKey, String ddmTemplateKey,
4331                    Date displayDateGT, Date displayDateLT, int status, Date reviewDate,
4332                    boolean andOperator) {
4333    
4334                    return journalArticleFinder.countByC_G_F_C_A_V_T_D_C_S_T_D_R(
4335                            companyId, groupId, folderIds, classNameId, articleId, version,
4336                            title, description, content, ddmStructureKey, ddmTemplateKey,
4337                            displayDateGT, displayDateLT, reviewDate, andOperator,
4338                            new QueryDefinition<JournalArticle>(status));
4339            }
4340    
4341            /**
4342             * Returns the number of web content articles matching the parameters,
4343             * including keyword parameters for article ID, title, description, and
4344             * content, a DDM structure keys (plural) parameter, a DDM template keys
4345             * (plural) parameter, and an AND operator switch.
4346             *
4347             * @param  companyId the primary key of the web content article's company
4348             * @param  groupId the primary key of the group (optionally <code>0</code>)
4349             * @param  folderIds the primary keys of the web content article folders
4350             *         (optionally {@link java.util.Collections#EMPTY_LIST})
4351             * @param  classNameId the primary key of the DDMStructure class if the web
4352             *         content article is related to a DDM structure, the primary key of
4353             *         the class name associated with the article, or {@link
4354             *         JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
4355             * @param  articleId the article ID keywords (space separated, optionally
4356             *         <code>null</code>)
4357             * @param  version the web content article's version (optionally
4358             *         <code>null</code>)
4359             * @param  title the title keywords (space separated, optionally
4360             *         <code>null</code>)
4361             * @param  description the description keywords (space separated, optionally
4362             *         <code>null</code>)
4363             * @param  content the content keywords (space separated, optionally
4364             *         <code>null</code>)
4365             * @param  ddmStructureKeys the primary keys of the web content article's
4366             *         DDM structures, if the article is related to a DDM structure, or
4367             *         <code>null</code> otherwise
4368             * @param  ddmTemplateKeys the primary keys of the web content article's DDM
4369             *         templates (originally <code>null</code>). If the articles are
4370             *         related to a DDM structure, the template's structure must match
4371             *         it.
4372             * @param  displayDateGT the date after which a matching web content
4373             *         article's display date must be after (optionally
4374             *         <code>null</code>)
4375             * @param  displayDateLT the date before which a matching web content
4376             *         article's display date must be before (optionally
4377             *         <code>null</code>)
4378             * @param  status the web content article's workflow status. For more
4379             *         information see {@link WorkflowConstants} for constants starting
4380             *         with the "STATUS_" prefix.
4381             * @param  reviewDate the web content article's scheduled review date
4382             *         (optionally <code>null</code>)
4383             * @param  andOperator whether every field must match its value or keywords,
4384             *         or just one field must match.  Group, folder IDs, class name ID,
4385             *         and status must all match their values.
4386             * @return the number of matching web content articles
4387             */
4388            @Override
4389            public int searchCount(
4390                    long companyId, long groupId, List<Long> folderIds, long classNameId,
4391                    String articleId, Double version, String title, String description,
4392                    String content, String[] ddmStructureKeys, String[] ddmTemplateKeys,
4393                    Date displayDateGT, Date displayDateLT, int status, Date reviewDate,
4394                    boolean andOperator) {
4395    
4396                    return journalArticleFinder.countByC_G_F_C_A_V_T_D_C_S_T_D_R(
4397                            companyId, groupId, folderIds, classNameId, articleId, version,
4398                            title, description, content, ddmStructureKeys, ddmTemplateKeys,
4399                            displayDateGT, displayDateLT, reviewDate, andOperator,
4400                            new QueryDefinition<JournalArticle>(status));
4401            }
4402    
4403            /**
4404             * Returns a {@link BaseModelSearchResult} containing the total number of
4405             * hits and an ordered range of all the web content articles matching the
4406             * parameters using the indexer, including a keywords parameter for matching
4407             * an article's ID, title, description, or content, a DDM structure key
4408             * parameter, a DDM template key parameter, and a finder hash map parameter.
4409             * It is preferable to use this method instead of the non-indexed version
4410             * whenever possible for performance reasons.
4411             *
4412             * <p>
4413             * The <code>start</code> and <code>end</code> parameters only affect the
4414             * amount of web content articles returned as results, not the total number
4415             * of hits.
4416             * </p>
4417             *
4418             * <p>
4419             * Useful when paginating results. Returns a maximum of <code>end -
4420             * start</code> instances. <code>start</code> and <code>end</code> are not
4421             * primary keys, they are indexes in the result set. Thus, <code>0</code>
4422             * refers to the first result in the set. Setting both <code>start</code>
4423             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
4424             * result set.
4425             * </p>
4426             *
4427             * @param  companyId the primary key of the web content article's company
4428             * @param  groupId the primary key of the group (optionally <code>0</code>)
4429             * @param  folderIds the primary keys of the web content article folders
4430             *         (optionally {@link java.util.Collections#EMPTY_LIST})
4431             * @param  classNameId the primary key of the DDMStructure class, the
4432             *         primary key of the class name associated with the article, or
4433             *         {@link JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
4434             * @param  ddmStructureKey the primary key of the web content article's DDM
4435             *         structure
4436             * @param  ddmTemplateKey the primary key of the web content article's DDM
4437             *         template
4438             * @param  keywords the keywords (space separated), which may occur in the
4439             *         web content article ID, title, description, or content
4440             *         (optionally <code>null</code>). If the keywords value is not
4441             *         <code>null</code>, the search uses the OR operator in connecting
4442             *         query criteria; otherwise it uses the AND operator.
4443             * @param  params the finder parameters (optionally <code>null</code>)
4444             * @param  start the lower bound of the range of web content articles to
4445             *         return
4446             * @param  end the upper bound of the range of web content articles to
4447             *         return (not inclusive)
4448             * @param  sort the field, type, and direction by which to sort (optionally
4449             *         <code>null</code>)
4450             * @return a {@link BaseModelSearchResult} containing the total number of
4451             *         hits and an ordered range of all the matching web content
4452             *         articles ordered by <code>sort</code>
4453             * @throws PortalException if a portal exception occurred
4454             */
4455            @Override
4456            public BaseModelSearchResult<JournalArticle> searchJournalArticles(
4457                            long companyId, long groupId, List<Long> folderIds,
4458                            long classNameId, String ddmStructureKey, String ddmTemplateKey,
4459                            String keywords, LinkedHashMap<String, Object> params, int start,
4460                            int end, Sort sort)
4461                    throws PortalException {
4462    
4463                    String articleId = null;
4464                    String title = null;
4465                    String description = null;
4466                    String content = null;
4467                    boolean andOperator = false;
4468    
4469                    if (Validator.isNotNull(keywords)) {
4470                            articleId = keywords;
4471                            title = keywords;
4472                            description = keywords;
4473                            content = keywords;
4474                    }
4475                    else {
4476                            andOperator = true;
4477                    }
4478    
4479                    if (params != null) {
4480                            params.put("keywords", keywords);
4481                    }
4482    
4483                    return searchJournalArticles(
4484                            companyId, groupId, folderIds, classNameId, articleId, title,
4485                            description, content, WorkflowConstants.STATUS_ANY, ddmStructureKey,
4486                            ddmTemplateKey, params, andOperator, start, end, sort);
4487            }
4488    
4489            /**
4490             * Returns a {@link BaseModelSearchResult} containing the total number of
4491             * hits and an ordered range of all the web content articles matching the
4492             * parameters using the indexer, including keyword parameters for article
4493             * ID, title, description, or content, a DDM structure key parameter, a DDM
4494             * template key parameter, an AND operator switch, and parameters for type,
4495             * status, and a finder hash map. It is preferable to use this method
4496             * instead of the non-indexed version whenever possible for performance
4497             * reasons.
4498             *
4499             * <p>
4500             * The <code>start</code> and <code>end</code> parameters only affect the
4501             * amount of web content articles returned as results, not the total number
4502             * of hits.
4503             * </p>
4504             *
4505             * <p>
4506             * Useful when paginating results. Returns a maximum of <code>end -
4507             * start</code> instances. <code>start</code> and <code>end</code> are not
4508             * primary keys, they are indexes in the result set. Thus, <code>0</code>
4509             * refers to the first result in the set. Setting both <code>start</code>
4510             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
4511             * result set.
4512             * </p>
4513             *
4514             * @param  companyId the primary key of the web content article's company
4515             * @param  groupId the primary key of the group (optionally <code>0</code>)
4516             * @param  folderIds the primary keys of the web content article folders
4517             *         (optionally {@link java.util.Collections#EMPTY_LIST})
4518             * @param  classNameId the primary key of the DDMStructure class, the
4519             *         primary key of the class name associated with the article, or
4520             *         {@link JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
4521             * @param  articleId the article ID keywords (space separated, optionally
4522             *         <code>null</code>)
4523             * @param  title the title keywords (space separated, optionally
4524             *         <code>null</code>)
4525             * @param  description the description keywords (space separated, optionally
4526             *         <code>null</code>)
4527             * @param  content the content keywords (space separated, optionally
4528             *         <code>null</code>)
4529             * @param  status the web content article's workflow status. For more
4530             *         information see {@link WorkflowConstants} for constants starting
4531             *         with the "STATUS_" prefix.
4532             * @param  ddmStructureKey the primary key of the web content article's DDM
4533             *         structure
4534             * @param  ddmTemplateKey the primary key of the web content article's DDM
4535             *         template
4536             * @param  params the finder parameters (optionally <code>null</code>). The
4537             *         <code>includeDiscussions</code> parameter can be set to
4538             *         <code>true</code> to search for the keywords in the web content
4539             *         article discussions.
4540             * @param  andSearch whether every field must match its value or keywords,
4541             *         or just one field must match
4542             * @param  start the lower bound of the range of web content articles to
4543             *         return
4544             * @param  end the upper bound of the range of web content articles to
4545             *         return (not inclusive)
4546             * @param  sort the field, type, and direction by which to sort (optionally
4547             *         <code>null</code>)
4548             * @return a {@link BaseModelSearchResult} containing the total number of
4549             *         hits and an ordered range of all the matching web content
4550             *         articles ordered by <code>sort</code>
4551             * @throws PortalException if a portal exception occurred
4552             */
4553            @Override
4554            public BaseModelSearchResult<JournalArticle> searchJournalArticles(
4555                            long companyId, long groupId, List<Long> folderIds,
4556                            long classNameId, String articleId, String title,
4557                            String description, String content, int status,
4558                            String ddmStructureKey, String ddmTemplateKey,
4559                            LinkedHashMap<String, Object> params, boolean andSearch, int start,
4560                            int end, Sort sort)
4561                    throws PortalException {
4562    
4563                    SearchContext searchContext = buildSearchContext(
4564                            companyId, groupId, folderIds, classNameId, articleId, title,
4565                            description, content, status, ddmStructureKey, ddmTemplateKey,
4566                            params, andSearch, start, end, sort);
4567    
4568                    return searchJournalArticles(searchContext);
4569            }
4570    
4571            /**
4572             * Returns a {@link BaseModelSearchResult} containing the total number of
4573             * hits and an ordered range of all the web content articles matching the
4574             * parameters using the indexer, including the web content article's creator
4575             * ID and status. It is preferable to use this method instead of the
4576             * non-indexed version whenever possible for performance reasons.
4577             *
4578             * <p>
4579             * The <code>start</code> and <code>end</code> parameters only affect the
4580             * amount of web content articles returned as results, not the total number
4581             * of hits.
4582             * </p>
4583             *
4584             * <p>
4585             * Useful when paginating results. Returns a maximum of <code>end -
4586             * start</code> instances. <code>start</code> and <code>end</code> are not
4587             * primary keys, they are indexes in the result set. Thus, <code>0</code>
4588             * refers to the first result in the set. Setting both <code>start</code>
4589             * and <code>end</code> to {@link QueryUtil#ALL_POS} will return the full
4590             * result set.
4591             * </p>
4592             *
4593             * @param  groupId the primary key of the group (optionally <code>0</code>)
4594             * @param  userId the primary key of the user searching for web content
4595             *         articles
4596             * @param  creatorUserId the primary key of the web content article's
4597             *         creator
4598             * @param  status the web content article's workflow status. For more
4599             *         information see {@link WorkflowConstants} for constants starting
4600             *         with the "STATUS_" prefix.
4601             * @param  start the lower bound of the range of web content articles to
4602             *         return
4603             * @param  end the upper bound of the range of web content articles to
4604             *         return (not inclusive)
4605             * @return a {@link BaseModelSearchResult} containing the total number of
4606             *         hits and an ordered range of all the matching web content
4607             *         articles ordered by <code>sort</code>
4608             * @throws PortalException if a portal exception occurred
4609             */
4610            @Override
4611            public BaseModelSearchResult<JournalArticle> searchJournalArticles(
4612                            long groupId, long userId, long creatorUserId, int status,
4613                            int start, int end)
4614                    throws PortalException {
4615    
4616                    SearchContext searchContext = buildSearchContext(
4617                            groupId, userId, creatorUserId, status, start, end);
4618    
4619                    return searchJournalArticles(searchContext);
4620            }
4621    
4622            @Override
4623            public void setTreePaths(
4624                            final long folderId, final String treePath, final boolean reindex)
4625                    throws PortalException {
4626    
4627                    ActionableDynamicQuery actionableDynamicQuery =
4628                            getActionableDynamicQuery();
4629    
4630                    actionableDynamicQuery.setAddCriteriaMethod(
4631                            new ActionableDynamicQuery.AddCriteriaMethod() {
4632    
4633                                    @Override
4634                                    public void addCriteria(DynamicQuery dynamicQuery) {
4635                                            Property folderIdProperty = PropertyFactoryUtil.forName(
4636                                                    "folderId");
4637    
4638                                            dynamicQuery.add(folderIdProperty.eq(folderId));
4639    
4640                                            Property treePathProperty = PropertyFactoryUtil.forName(
4641                                                    "treePath");
4642    
4643                                            dynamicQuery.add(treePathProperty.ne(treePath));
4644                                    }
4645    
4646                            });
4647    
4648                    final Indexer indexer = IndexerRegistryUtil.getIndexer(
4649                            JournalArticle.class.getName());
4650    
4651                    actionableDynamicQuery.setPerformActionMethod(
4652                            new ActionableDynamicQuery.PerformActionMethod() {
4653    
4654                                    @Override
4655                                    public void performAction(Object object)
4656                                            throws PortalException {
4657    
4658                                            JournalArticle article = (JournalArticle)object;
4659    
4660                                            article.setTreePath(treePath);
4661    
4662                                            updateJournalArticle(article);
4663    
4664                                            if (!reindex) {
4665                                                    return;
4666                                            }
4667    
4668                                            indexer.reindex(article);
4669                                    }
4670    
4671                            });
4672    
4673                    actionableDynamicQuery.performActions();
4674            }
4675    
4676            /**
4677             * Subscribes the user to changes in elements that belong to the web content
4678             * article's DDM structure.
4679             *
4680             * @param  groupId the primary key of the folder's group
4681             * @param  userId the primary key of the user to be subscribed
4682             * @param  ddmStructureId the primary key of the structure to subscribe to
4683             * @throws PortalException if a matching user or group could not be found
4684             */
4685            @Override
4686            public void subscribeStructure(
4687                            long groupId, long userId, long ddmStructureId)
4688                    throws PortalException {
4689    
4690                    subscriptionLocalService.addSubscription(
4691                            userId, groupId, DDMStructure.class.getName(), ddmStructureId);
4692            }
4693    
4694            /**
4695             * Unsubscribes the user from changes in elements that belong to the web
4696             * content article's DDM structure.
4697             *
4698             * @param  groupId the primary key of the folder's group
4699             * @param  userId the primary key of the user to be subscribed
4700             * @param  ddmStructureId the primary key of the structure to subscribe to
4701             * @throws PortalException if a matching user or subscription could not be
4702             *         found
4703             */
4704            @Override
4705            public void unsubscribeStructure(
4706                            long groupId, long userId, long ddmStructureId)
4707                    throws PortalException {
4708    
4709                    subscriptionLocalService.deleteSubscription(
4710                            userId, DDMStructure.class.getName(), ddmStructureId);
4711            }
4712    
4713            /**
4714             * Updates the web content article matching the version, replacing its
4715             * folder, title, description, content, and layout UUID.
4716             *
4717             * @param  userId the primary key of the user updating the web content
4718             *         article
4719             * @param  groupId the primary key of the web content article's group
4720             * @param  folderId the primary key of the web content article folder
4721             * @param  articleId the primary key of the web content article
4722             * @param  version the web content article's version
4723             * @param  titleMap the web content article's locales and localized titles
4724             * @param  descriptionMap the web content article's locales and localized
4725             *         descriptions
4726             * @param  content the HTML content wrapped in XML. For more information,
4727             *         see the content example in the class description for {@link
4728             *         JournalArticleLocalServiceImpl}.
4729             * @param  layoutUuid the unique string identifying the web content
4730             *         article's display page
4731             * @param  serviceContext the service context to be applied. Can set the
4732             *         modification date, expando bridge attributes, asset category IDs,
4733             *         asset tag names, asset link entry IDs, workflow actions, the and
4734             *         "urlTitle" attributes, and can set whether to add the default
4735             *         command update for the web content article. With respect to
4736             *         social activities, by setting the service context's command to
4737             *         {@link com.liferay.portal.kernel.util.Constants#UPDATE}, the
4738             *         invocation is considered a web content update activity; otherwise
4739             *         it is considered a web content add activity.
4740             * @return the updated web content article
4741             * @throws PortalException if a user with the primary key or a matching web
4742             *         content article could not be found, or if a portal exception
4743             *         occurred
4744             */
4745            @Override
4746            public JournalArticle updateArticle(
4747                            long userId, long groupId, long folderId, String articleId,
4748                            double version, Map<Locale, String> titleMap,
4749                            Map<Locale, String> descriptionMap, String content,
4750                            String layoutUuid, ServiceContext serviceContext)
4751                    throws PortalException {
4752    
4753                    User user = userPersistence.findByPrimaryKey(userId);
4754    
4755                    JournalArticle article = journalArticlePersistence.findByG_A_V(
4756                            groupId, articleId, version);
4757    
4758                    Date displayDate = article.getDisplayDate();
4759    
4760                    int displayDateMonth = 0;
4761                    int displayDateDay = 0;
4762                    int displayDateYear = 0;
4763                    int displayDateHour = 0;
4764                    int displayDateMinute = 0;
4765    
4766                    if (displayDate != null) {
4767                            Calendar displayCal = CalendarFactoryUtil.getCalendar(
4768                                    user.getTimeZone());
4769    
4770                            displayCal.setTime(displayDate);
4771    
4772                            displayDateMonth = displayCal.get(Calendar.MONTH);
4773                            displayDateDay = displayCal.get(Calendar.DATE);
4774                            displayDateYear = displayCal.get(Calendar.YEAR);
4775                            displayDateHour = displayCal.get(Calendar.HOUR);
4776                            displayDateMinute = displayCal.get(Calendar.MINUTE);
4777    
4778                            if (displayCal.get(Calendar.AM_PM) == Calendar.PM) {
4779                                    displayDateHour += 12;
4780                            }
4781                    }
4782    
4783                    Date expirationDate = article.getExpirationDate();
4784    
4785                    int expirationDateMonth = 0;
4786                    int expirationDateDay = 0;
4787                    int expirationDateYear = 0;
4788                    int expirationDateHour = 0;
4789                    int expirationDateMinute = 0;
4790                    boolean neverExpire = true;
4791    
4792                    if (expirationDate != null) {
4793                            Calendar expirationCal = CalendarFactoryUtil.getCalendar(
4794                                    user.getTimeZone());
4795    
4796                            expirationCal.setTime(expirationDate);
4797    
4798                            expirationDateMonth = expirationCal.get(Calendar.MONTH);
4799                            expirationDateDay = expirationCal.get(Calendar.DATE);
4800                            expirationDateYear = expirationCal.get(Calendar.YEAR);
4801                            expirationDateHour = expirationCal.get(Calendar.HOUR);
4802                            expirationDateMinute = expirationCal.get(Calendar.MINUTE);
4803                            neverExpire = false;
4804    
4805                            if (expirationCal.get(Calendar.AM_PM) == Calendar.PM) {
4806                                    expirationDateHour += 12;
4807                            }
4808                    }
4809    
4810                    Date reviewDate = article.getReviewDate();
4811    
4812                    int reviewDateMonth = 0;
4813                    int reviewDateDay = 0;
4814                    int reviewDateYear = 0;
4815                    int reviewDateHour = 0;
4816                    int reviewDateMinute = 0;
4817                    boolean neverReview = true;
4818    
4819                    if (reviewDate != null) {
4820                            Calendar reviewCal = CalendarFactoryUtil.getCalendar(
4821                                    user.getTimeZone());
4822    
4823                            reviewCal.setTime(reviewDate);
4824    
4825                            reviewDateMonth = reviewCal.get(Calendar.MONTH);
4826                            reviewDateDay = reviewCal.get(Calendar.DATE);
4827                            reviewDateYear = reviewCal.get(Calendar.YEAR);
4828                            reviewDateHour = reviewCal.get(Calendar.HOUR);
4829                            reviewDateMinute = reviewCal.get(Calendar.MINUTE);
4830                            neverReview = false;
4831    
4832                            if (reviewCal.get(Calendar.AM_PM) == Calendar.PM) {
4833                                    reviewDateHour += 12;
4834                            }
4835                    }
4836    
4837                    return journalArticleLocalService.updateArticle(
4838                            userId, groupId, folderId, articleId, version, titleMap,
4839                            descriptionMap, content, article.getDDMStructureKey(),
4840                            article.getDDMTemplateKey(), layoutUuid, displayDateMonth,
4841                            displayDateDay, displayDateYear, displayDateHour, displayDateMinute,
4842                            expirationDateMonth, expirationDateDay, expirationDateYear,
4843                            expirationDateHour, expirationDateMinute, neverExpire,
4844                            reviewDateMonth, reviewDateDay, reviewDateYear, reviewDateHour,
4845                            reviewDateMinute, neverReview, article.getIndexable(),
4846                            article.isSmallImage(), article.getSmallImageURL(), null, null,
4847                            null, serviceContext);
4848            }
4849    
4850            /**
4851             * Updates the web content article with additional parameters.
4852             *
4853             * @param  userId the primary key of the user updating the web content
4854             *         article
4855             * @param  groupId the primary key of the web content article's group
4856             * @param  folderId the primary key of the web content article folder
4857             * @param  articleId the primary key of the web content article
4858             * @param  version the web content article's version
4859             * @param  titleMap the web content article's locales and localized titles
4860             * @param  descriptionMap the web content article's locales and localized
4861             *         descriptions
4862             * @param  content the HTML content wrapped in XML. For more information,
4863             *         see the content example in the class description for {@link
4864             *         JournalArticleLocalServiceImpl}.
4865             * @param  ddmStructureKey the primary key of the web content article's DDM
4866             *         structure, if the article is related to a DDM structure, or
4867             *         <code>null</code> otherwise
4868             * @param  ddmTemplateKey the primary key of the web content article's DDM
4869             *         template
4870             * @param  layoutUuid the unique string identifying the web content
4871             *         article's display page
4872             * @param  displayDateMonth the month the web content article is set to
4873             *         display
4874             * @param  displayDateDay the calendar day the web content article is set to
4875             *         display
4876             * @param  displayDateYear the year the web content article is set to
4877             *         display
4878             * @param  displayDateHour the hour the web content article is set to
4879             *         display
4880             * @param  displayDateMinute the minute the web content article is set to
4881             *         display
4882             * @param  expirationDateMonth the month the web content article is set to
4883             *         expire
4884             * @param  expirationDateDay the calendar day the web content article is set
4885             *         to expire
4886             * @param  expirationDateYear the year the web content article is set to
4887             *         expire
4888             * @param  expirationDateHour the hour the web content article is set to
4889             *         expire
4890             * @param  expirationDateMinute the minute the web content article is set to
4891             *         expire
4892             * @param  neverExpire whether the web content article is not set to auto
4893             *         expire
4894             * @param  reviewDateMonth the month the web content article is set for
4895             *         review
4896             * @param  reviewDateDay the calendar day the web content article is set for
4897             *         review
4898             * @param  reviewDateYear the year the web content article is set for review
4899             * @param  reviewDateHour the hour the web content article is set for review
4900             * @param  reviewDateMinute the minute the web content article is set for
4901             *         review
4902             * @param  neverReview whether the web content article is not set for review
4903             * @param  indexable whether the web content is searchable
4904             * @param  smallImage whether to update web content article's a small image.
4905             *         A file must be passed in as <code>smallImageFile</code> value,
4906             *         otherwise the current small image is deleted.
4907             * @param  smallImageURL the web content article's small image URL
4908             *         (optionally <code>null</code>)
4909             * @param  smallImageFile the web content article's new small image file
4910             *         (optionally <code>null</code>). Must pass in
4911             *         <code>smallImage</code> value of <code>true</code> to replace the
4912             *         article's small image file.
4913             * @param  images the web content's images (optionally <code>null</code>)
4914             * @param  articleURL the web content article's accessible URL (optionally
4915             *         <code>null</code>)
4916             * @param  serviceContext the service context to be applied. Can set the
4917             *         modification date, expando bridge attributes, asset category IDs,
4918             *         asset tag names, asset link entry IDs, workflow actions, the and
4919             *         "urlTitle" attributes, and can set whether to add the default
4920             *         command update for the web content article. With respect to
4921             *         social activities, by setting the service context's command to
4922             *         {@link com.liferay.portal.kernel.util.Constants#UPDATE}, the
4923             *         invocation is considered a web content update activity; otherwise
4924             *         it is considered a web content add activity.
4925             * @return the updated web content article
4926             * @throws PortalException if a user with the primary key or a matching web
4927             *         content article could not be found, or if a portal exception
4928             *         occurred
4929             */
4930            @Indexable(type = IndexableType.REINDEX)
4931            @Override
4932            public JournalArticle updateArticle(
4933                            long userId, long groupId, long folderId, String articleId,
4934                            double version, Map<Locale, String> titleMap,
4935                            Map<Locale, String> descriptionMap, String content,
4936                            String ddmStructureKey, String ddmTemplateKey, String layoutUuid,
4937                            int displayDateMonth, int displayDateDay, int displayDateYear,
4938                            int displayDateHour, int displayDateMinute, int expirationDateMonth,
4939                            int expirationDateDay, int expirationDateYear,
4940                            int expirationDateHour, int expirationDateMinute,
4941                            boolean neverExpire, int reviewDateMonth, int reviewDateDay,
4942                            int reviewDateYear, int reviewDateHour, int reviewDateMinute,
4943                            boolean neverReview, boolean indexable, boolean smallImage,
4944                            String smallImageURL, File smallImageFile,
4945                            Map<String, byte[]> images, String articleURL,
4946                            ServiceContext serviceContext)
4947                    throws PortalException {
4948    
4949                    // Article
4950    
4951                    User user = userPersistence.findByPrimaryKey(userId);
4952                    articleId = StringUtil.toUpperCase(articleId.trim());
4953    
4954                    byte[] smallImageBytes = null;
4955    
4956                    try {
4957                            smallImageBytes = FileUtil.getBytes(smallImageFile);
4958                    }
4959                    catch (IOException ioe) {
4960                    }
4961    
4962                    JournalArticle latestArticle = getLatestArticle(
4963                            groupId, articleId, WorkflowConstants.STATUS_ANY);
4964    
4965                    JournalArticle article = latestArticle;
4966    
4967                    boolean imported = ExportImportThreadLocal.isImportInProcess();
4968    
4969                    double latestVersion = latestArticle.getVersion();
4970    
4971                    boolean addNewVersion = false;
4972    
4973                    if (imported) {
4974                            article = getArticle(groupId, articleId, version);
4975                    }
4976                    else {
4977                            if ((version > 0) && (version != latestVersion)) {
4978                                    throw new ArticleVersionException();
4979                            }
4980    
4981                            serviceContext.validateModifiedDate(
4982                                    latestArticle, ArticleVersionException.class);
4983    
4984                            if (latestArticle.isApproved() || latestArticle.isExpired() ||
4985                                    latestArticle.isScheduled()) {
4986    
4987                                    addNewVersion = true;
4988    
4989                                    version = MathUtil.format(latestVersion + 0.1, 1, 1);
4990                            }
4991                    }
4992    
4993                    Date displayDate = null;
4994                    Date expirationDate = null;
4995                    Date reviewDate = null;
4996    
4997                    if (article.getClassNameId() ==
4998                                    JournalArticleConstants.CLASSNAME_ID_DEFAULT) {
4999    
5000                            displayDate = PortalUtil.getDate(
5001                                    displayDateMonth, displayDateDay, displayDateYear,
5002                                    displayDateHour, displayDateMinute, user.getTimeZone(),
5003                                    ArticleDisplayDateException.class);
5004    
5005                            if (!neverExpire) {
5006                                    expirationDate = PortalUtil.getDate(
5007                                            expirationDateMonth, expirationDateDay, expirationDateYear,
5008                                            expirationDateHour, expirationDateMinute,
5009                                            user.getTimeZone(), ArticleExpirationDateException.class);
5010                            }
5011    
5012                            if (!neverReview) {
5013                                    reviewDate = PortalUtil.getDate(
5014                                            reviewDateMonth, reviewDateDay, reviewDateYear,
5015                                            reviewDateHour, reviewDateMinute, user.getTimeZone(),
5016                                            ArticleReviewDateException.class);
5017                            }
5018                    }
5019    
5020                    Date now = new Date();
5021    
5022                    boolean expired = false;
5023    
5024                    if ((expirationDate != null) && expirationDate.before(now)) {
5025                            expired = true;
5026                    }
5027    
5028                    validate(
5029                            user.getCompanyId(), groupId, latestArticle.getClassNameId(),
5030                            titleMap, content, ddmStructureKey, ddmTemplateKey, expirationDate,
5031                            smallImage, smallImageURL, smallImageFile, smallImageBytes,
5032                            serviceContext);
5033    
5034                    if (addNewVersion) {
5035                            long id = counterLocalService.increment();
5036    
5037                            article = journalArticlePersistence.create(id);
5038    
5039                            article.setResourcePrimKey(latestArticle.getResourcePrimKey());
5040                            article.setGroupId(latestArticle.getGroupId());
5041                            article.setCompanyId(latestArticle.getCompanyId());
5042                            article.setCreateDate(latestArticle.getCreateDate());
5043                            article.setClassNameId(latestArticle.getClassNameId());
5044                            article.setClassPK(latestArticle.getClassPK());
5045                            article.setArticleId(articleId);
5046                            article.setVersion(version);
5047                            article.setSmallImageId(latestArticle.getSmallImageId());
5048                    }
5049    
5050                    Locale locale = getArticleDefaultLocale(content);
5051    
5052                    String title = titleMap.get(locale);
5053    
5054                    content = format(
5055                            user, groupId, articleId, article.getVersion(), addNewVersion,
5056                            content, ddmStructureKey, images);
5057    
5058                    article.setUserId(user.getUserId());
5059                    article.setUserName(user.getFullName());
5060                    article.setModifiedDate(serviceContext.getModifiedDate(now));
5061                    article.setFolderId(folderId);
5062                    article.setTreePath(article.buildTreePath());
5063                    article.setTitleMap(titleMap, locale);
5064                    article.setUrlTitle(
5065                            getUniqueUrlTitle(
5066                                    article.getId(), article.getArticleId(), title,
5067                                    latestArticle.getUrlTitle(), serviceContext));
5068                    article.setDescriptionMap(descriptionMap, locale);
5069                    article.setContent(content);
5070                    article.setDDMStructureKey(ddmStructureKey);
5071                    article.setDDMTemplateKey(ddmTemplateKey);
5072                    article.setLayoutUuid(layoutUuid);
5073                    article.setDisplayDate(displayDate);
5074                    article.setExpirationDate(expirationDate);
5075                    article.setReviewDate(reviewDate);
5076                    article.setIndexable(indexable);
5077                    article.setSmallImage(smallImage);
5078    
5079                    if (smallImage) {
5080                            if ((smallImageFile != null) && (smallImageBytes != null)) {
5081                                    article.setSmallImageId(counterLocalService.increment());
5082                            }
5083                    }
5084                    else {
5085                            article.setSmallImageId(0);
5086                    }
5087    
5088                    article.setSmallImageURL(smallImageURL);
5089    
5090                    if (latestArticle.isPending()) {
5091                            article.setStatus(latestArticle.getStatus());
5092                    }
5093                    else if (!expired) {
5094                            article.setStatus(WorkflowConstants.STATUS_DRAFT);
5095                    }
5096                    else {
5097                            article.setStatus(WorkflowConstants.STATUS_EXPIRED);
5098                    }
5099    
5100                    article.setExpandoBridgeAttributes(serviceContext);
5101    
5102                    journalArticlePersistence.update(article);
5103    
5104                    // Asset
5105    
5106                    updateAsset(
5107                            userId, article, serviceContext.getAssetCategoryIds(),
5108                            serviceContext.getAssetTagNames(),
5109                            serviceContext.getAssetLinkEntryIds());
5110    
5111                    // Dynamic data mapping
5112    
5113                    if (classNameLocalService.getClassNameId(DDMStructure.class) ==
5114                                    article.getClassNameId()) {
5115    
5116                            updateDDMStructurePredefinedValues(
5117                                    article.getClassPK(), content, serviceContext);
5118                    }
5119    
5120                    // Small image
5121    
5122                    saveImages(
5123                            smallImage, article.getSmallImageId(), smallImageFile,
5124                            smallImageBytes);
5125    
5126                    // Email
5127    
5128                    PortletPreferences preferences =
5129                            ServiceContextUtil.getPortletPreferences(serviceContext);
5130    
5131                    // Workflow
5132    
5133                    if (expired && imported) {
5134                            updateStatus(
5135                                    userId, article, article.getStatus(), articleURL,
5136                                    serviceContext, new HashMap<String, Serializable>());
5137                    }
5138    
5139                    if (serviceContext.getWorkflowAction() ==
5140                                    WorkflowConstants.ACTION_PUBLISH) {
5141    
5142                            articleURL = buildArticleURL(
5143                                    articleURL, groupId, folderId, articleId);
5144    
5145                            serviceContext.setAttribute("articleURL", articleURL);
5146    
5147                            sendEmail(
5148                                    article, articleURL, preferences, "requested", serviceContext);
5149    
5150                            startWorkflowInstance(userId, article, serviceContext);
5151                    }
5152    
5153                    return journalArticlePersistence.findByPrimaryKey(article.getId());
5154            }
5155    
5156            /**
5157             * Updates the web content article matching the version, replacing its
5158             * folder and content.
5159             *
5160             * @param  userId the primary key of the user updating the web content
5161             *         article
5162             * @param  groupId the primary key of the web content article's group
5163             * @param  folderId the primary key of the web content article folder
5164             * @param  articleId the primary key of the web content article
5165             * @param  version the web content article's version
5166             * @param  content the HTML content wrapped in XML. For more information,
5167             *         see the content example in the class description for {@link
5168             *         JournalArticleLocalServiceImpl}.
5169             * @param  serviceContext the service context to be applied. Can set the
5170             *         modification date, expando bridge attributes, asset category IDs,
5171             *         asset tag names, asset link entry IDs, workflow actions, the and
5172             *         "urlTitle" attributes, and can set whether to add the default
5173             *         command update for the web content article. With respect to
5174             *         social activities, by setting the service context's command to
5175             *         {@link com.liferay.portal.kernel.util.Constants#UPDATE}, the
5176             *         invocation is considered a web content update activity; otherwise
5177             *         it is considered a web content add activity.
5178             * @return the updated web content article
5179             * @throws PortalException if a user with the primary key or a matching web
5180             *         content article could not be found, or if a portal exception
5181             *         occurred
5182             */
5183            @Override
5184            public JournalArticle updateArticle(
5185                            long userId, long groupId, long folderId, String articleId,
5186                            double version, String content, ServiceContext serviceContext)
5187                    throws PortalException {
5188    
5189                    JournalArticle article = journalArticlePersistence.findByG_A_V(
5190                            groupId, articleId, version);
5191    
5192                    return journalArticleLocalService.updateArticle(
5193                            userId, groupId, folderId, articleId, version,
5194                            article.getTitleMap(), article.getDescriptionMap(), content,
5195                            article.getLayoutUuid(), serviceContext);
5196            }
5197    
5198            /**
5199             * @deprecated As of 6.2.0, replaced by {@link
5200             *             #updateArticleTranslation(long, String, double, Locale,
5201             *             String, String, String, Map, ServiceContext)}
5202             */
5203            @Deprecated
5204            @Override
5205            public JournalArticle updateArticleTranslation(
5206                            long groupId, String articleId, double version, Locale locale,
5207                            String title, String description, String content,
5208                            Map<String, byte[]> images)
5209                    throws PortalException {
5210    
5211                    return journalArticleLocalService.updateArticleTranslation(
5212                            groupId, articleId, version, locale, title, description, content,
5213                            images, null);
5214            }
5215    
5216            /**
5217             * Updates the translation of the web content article.
5218             *
5219             * @param  groupId the primary key of the web content article's group
5220             * @param  articleId the primary key of the web content article
5221             * @param  version the web content article's version
5222             * @param  locale the locale of the web content article's display template
5223             * @param  title the translated web content article title
5224             * @param  description the translated web content article description
5225             * @param  content the HTML content wrapped in XML. For more information,
5226             *         see the content example in the class description for {@link
5227             *         JournalArticleLocalServiceImpl}.
5228             * @param  images the web content's images
5229             * @param  serviceContext the service context to be applied. Can set the
5230             *         modification date and "urlTitle" attribute for the web content
5231             *         article.
5232             * @return the updated web content article
5233             * @throws PortalException if a user with the primary key or a matching web
5234             *         content article could not be found, or if a portal exception
5235             *         occurred
5236             */
5237            @Indexable(type = IndexableType.REINDEX)
5238            @Override
5239            public JournalArticle updateArticleTranslation(
5240                            long groupId, String articleId, double version, Locale locale,
5241                            String title, String description, String content,
5242                            Map<String, byte[]> images, ServiceContext serviceContext)
5243                    throws PortalException {
5244    
5245                    validateContent(content);
5246    
5247                    JournalArticle oldArticle = getLatestArticle(
5248                            groupId, articleId, WorkflowConstants.STATUS_ANY);
5249    
5250                    double oldVersion = oldArticle.getVersion();
5251    
5252                    if ((version > 0) && (version != oldVersion)) {
5253                            throw new ArticleVersionException();
5254                    }
5255    
5256                    boolean incrementVersion = false;
5257    
5258                    if (oldArticle.isApproved() || oldArticle.isExpired()) {
5259                            incrementVersion = true;
5260                    }
5261    
5262                    if (serviceContext != null) {
5263                            serviceContext.validateModifiedDate(
5264                                    oldArticle, ArticleVersionException.class);
5265                    }
5266    
5267                    JournalArticle article = null;
5268    
5269                    User user = userPersistence.fetchByC_U(
5270                            oldArticle.getCompanyId(), oldArticle.getUserId());
5271    
5272                    if (user == null) {
5273                            user = userPersistence.fetchByC_DU(oldArticle.getCompanyId(), true);
5274                    }
5275    
5276                    Locale defaultLocale = getArticleDefaultLocale(content);
5277    
5278                    if (incrementVersion) {
5279                            double newVersion = MathUtil.format(oldVersion + 0.1, 1, 1);
5280    
5281                            long id = counterLocalService.increment();
5282    
5283                            article = journalArticlePersistence.create(id);
5284    
5285                            article.setResourcePrimKey(oldArticle.getResourcePrimKey());
5286                            article.setGroupId(oldArticle.getGroupId());
5287                            article.setCompanyId(oldArticle.getCompanyId());
5288                            article.setUserId(oldArticle.getUserId());
5289                            article.setUserName(user.getFullName());
5290                            article.setCreateDate(oldArticle.getCreateDate());
5291                            article.setModifiedDate(new Date());
5292                            article.setClassNameId(oldArticle.getClassNameId());
5293                            article.setClassPK(oldArticle.getClassPK());
5294                            article.setArticleId(articleId);
5295                            article.setVersion(newVersion);
5296                            article.setTitleMap(oldArticle.getTitleMap(), defaultLocale);
5297                            article.setUrlTitle(
5298                                    getUniqueUrlTitle(
5299                                            id, articleId, title, oldArticle.getUrlTitle(),
5300                                            serviceContext));
5301                            article.setDescriptionMap(oldArticle.getDescriptionMap());
5302                            article.setDDMStructureKey(oldArticle.getDDMStructureKey());
5303                            article.setDDMTemplateKey(oldArticle.getDDMTemplateKey());
5304                            article.setLayoutUuid(oldArticle.getLayoutUuid());
5305                            article.setDisplayDate(oldArticle.getDisplayDate());
5306                            article.setExpirationDate(oldArticle.getExpirationDate());
5307                            article.setReviewDate(oldArticle.getReviewDate());
5308                            article.setIndexable(oldArticle.getIndexable());
5309                            article.setSmallImage(oldArticle.getSmallImage());
5310                            article.setSmallImageId(oldArticle.getSmallImageId());
5311    
5312                            if (article.getSmallImageId() == 0) {
5313                                    article.setSmallImageId(counterLocalService.increment());
5314                            }
5315    
5316                            article.setSmallImageURL(oldArticle.getSmallImageURL());
5317    
5318                            article.setStatus(WorkflowConstants.STATUS_DRAFT);
5319                            article.setStatusDate(new Date());
5320                            article.setExpandoBridgeAttributes(oldArticle);
5321                    }
5322                    else {
5323                            article = oldArticle;
5324                    }
5325    
5326                    Map<Locale, String> titleMap = article.getTitleMap();
5327    
5328                    titleMap.put(locale, title);
5329    
5330                    article.setTitleMap(titleMap, defaultLocale);
5331    
5332                    Map<Locale, String> descriptionMap = article.getDescriptionMap();
5333    
5334                    descriptionMap.put(locale, description);
5335    
5336                    article.setDescriptionMap(descriptionMap);
5337    
5338                    content = format(
5339                            user, groupId, articleId, article.getVersion(),
5340                            !oldArticle.isDraft(), content, oldArticle.getDDMStructureKey(),
5341                            images);
5342    
5343                    article.setContent(content);
5344    
5345                    journalArticlePersistence.update(article);
5346    
5347                    return article;
5348            }
5349    
5350            /**
5351             * Updates the web content article's asset with the new asset categories,
5352             * tag names, and link entries, removing and adding them as necessary.
5353             *
5354             * @param  userId the primary key of the user updating the web content
5355             *         article's asset
5356             * @param  article the web content article
5357             * @param  assetCategoryIds the primary keys of the new asset categories
5358             * @param  assetTagNames the new asset tag names
5359             * @param  assetLinkEntryIds the primary keys of the new asset link entries
5360             * @throws PortalException if a portal exception occurred
5361             */
5362            @Override
5363            public void updateAsset(
5364                            long userId, JournalArticle article, long[] assetCategoryIds,
5365                            String[] assetTagNames, long[] assetLinkEntryIds)
5366                    throws PortalException {
5367    
5368                    boolean visible = article.isApproved();
5369    
5370                    if (article.getClassNameId() !=
5371                                    JournalArticleConstants.CLASSNAME_ID_DEFAULT) {
5372    
5373                            visible = false;
5374                    }
5375    
5376                    boolean addDraftAssetEntry = false;
5377    
5378                    if (!article.isApproved() &&
5379                            (article.getVersion() != JournalArticleConstants.VERSION_DEFAULT)) {
5380    
5381                            int approvedArticlesCount = journalArticlePersistence.countByG_A_ST(
5382                                    article.getGroupId(), article.getArticleId(),
5383                                    JournalArticleConstants.ASSET_ENTRY_CREATION_STATUSES);
5384    
5385                            if (approvedArticlesCount > 0) {
5386                                    addDraftAssetEntry = true;
5387                            }
5388                    }
5389    
5390                    AssetEntry assetEntry = null;
5391    
5392                    if (addDraftAssetEntry) {
5393                            assetEntry = assetEntryLocalService.updateEntry(
5394                                    userId, article.getGroupId(), article.getCreateDate(),
5395                                    article.getModifiedDate(), JournalArticle.class.getName(),
5396                                    article.getPrimaryKey(), article.getUuid(),
5397                                    getClassTypeId(article), assetCategoryIds, assetTagNames, false,
5398                                    null, null, null, ContentTypes.TEXT_HTML, article.getTitle(),
5399                                    article.getDescription(), article.getDescription(), null,
5400                                    article.getLayoutUuid(), 0, 0, null, false);
5401                    }
5402                    else {
5403                            JournalArticleResource journalArticleResource =
5404                                    journalArticleResourceLocalService.getArticleResource(
5405                                            article.getResourcePrimKey());
5406    
5407                            assetEntry = assetEntryLocalService.updateEntry(
5408                                    userId, article.getGroupId(), article.getCreateDate(),
5409                                    article.getModifiedDate(), JournalArticle.class.getName(),
5410                                    journalArticleResource.getResourcePrimKey(),
5411                                    journalArticleResource.getUuid(), getClassTypeId(article),
5412                                    assetCategoryIds, assetTagNames, visible, null, null, null,
5413                                    ContentTypes.TEXT_HTML, article.getTitle(),
5414                                    article.getDescription(), article.getDescription(), null,
5415                                    article.getLayoutUuid(), 0, 0, null, false);
5416                    }
5417    
5418                    assetLinkLocalService.updateLinks(
5419                            userId, assetEntry.getEntryId(), assetLinkEntryIds,
5420                            AssetLinkConstants.TYPE_RELATED);
5421            }
5422    
5423            /**
5424             * Updates the web content article matching the group, article ID, and
5425             * version, replacing its content.
5426             *
5427             * @param  groupId the primary key of the web content article's group
5428             * @param  articleId the primary key of the web content article
5429             * @param  version the web content article's version
5430             * @param  content the HTML content wrapped in XML. For more information,
5431             *         see the content example in the class description for {@link
5432             *         JournalArticleLocalServiceImpl}.
5433             * @return the updated web content article
5434             * @throws PortalException if a matching web content article could not be
5435             *         found
5436             */
5437            @Indexable(type = IndexableType.REINDEX)
5438            @Override
5439            public JournalArticle updateContent(
5440                            long groupId, String articleId, double version, String content)
5441                    throws PortalException {
5442    
5443                    JournalArticle article = journalArticlePersistence.findByG_A_V(
5444                            groupId, articleId, version);
5445    
5446                    article.setContent(content);
5447    
5448                    journalArticlePersistence.update(article);
5449    
5450                    return article;
5451            }
5452    
5453            /**
5454             * Updates the web content articles matching the group, class name ID, and
5455             * DDM template key, replacing the DDM template key with a new one.
5456             *
5457             * @param groupId the primary key of the web content article's group
5458             * @param classNameId the primary key of the DDMStructure class if the web
5459             *        content article is related to a DDM structure, the primary key of
5460             *        the class name associated with the article, or {@link
5461             *        JournalArticleConstants#CLASSNAME_ID_DEFAULT} otherwise
5462             * @param oldDDMTemplateKey the primary key of the web content article's old
5463             *        DDM template
5464             * @param newDDMTemplateKey the primary key of the web content article's new
5465             *        DDM template
5466             */
5467            @Override
5468            public void updateDDMTemplateKey(
5469                    long groupId, long classNameId, String oldDDMTemplateKey,
5470                    String newDDMTemplateKey) {
5471    
5472                    List<JournalArticle> articles =
5473                            journalArticlePersistence.findByG_C_DDMTK(
5474                                    groupId, classNameId, oldDDMTemplateKey);
5475    
5476                    for (JournalArticle article : articles) {
5477                            article.setDDMTemplateKey(newDDMTemplateKey);
5478    
5479                            journalArticlePersistence.update(article);
5480                    }
5481            }
5482    
5483            /**
5484             * Updates the workflow status of the web content article.
5485             *
5486             * @param  userId the primary key of the user updating the web content
5487             *         article's status
5488             * @param  article the web content article
5489             * @param  status the web content article's workflow status. For more
5490             *         information see {@link WorkflowConstants} for constants starting
5491             *         with the "STATUS_" prefix.
5492             * @param  articleURL the web content article's accessible URL
5493             * @param  serviceContext the service context to be applied. Can set the
5494             *         modification date, status date, and portlet preferences. With
5495             *         respect to social activities, by setting the service context's
5496             *         command to {@link
5497             *         com.liferay.portal.kernel.util.Constants#UPDATE}, the invocation
5498             *         is considered a web content update activity; otherwise it is
5499             *         considered a web content add activity.
5500             * @param  workflowContext the web content article's configured workflow
5501             *         context
5502             * @return the updated web content article
5503             * @throws PortalException if a portal exception occurred
5504             */
5505            @Indexable(type = IndexableType.REINDEX)
5506            @Override
5507            public JournalArticle updateStatus(
5508                            long userId, JournalArticle article, int status, String articleURL,
5509                            ServiceContext serviceContext,
5510                            Map<String, Serializable> workflowContext)
5511                    throws PortalException {
5512    
5513                    // Article
5514    
5515                    User user = userPersistence.findByPrimaryKey(userId);
5516                    Date now = new Date();
5517    
5518                    if ((status == WorkflowConstants.STATUS_APPROVED) &&
5519                            (article.getClassNameId() ==
5520                                    JournalArticleConstants.CLASSNAME_ID_DEFAULT) &&
5521                            (article.getDisplayDate() != null) &&
5522                            now.before(article.getDisplayDate())) {
5523    
5524                            status = WorkflowConstants.STATUS_SCHEDULED;
5525                    }
5526    
5527                    int oldStatus = article.getStatus();
5528    
5529                    if (status == WorkflowConstants.STATUS_APPROVED) {
5530                            Date expirationDate = article.getExpirationDate();
5531    
5532                            if ((expirationDate != null) && expirationDate.before(now)) {
5533                                    article.setExpirationDate(null);
5534                            }
5535                    }
5536    
5537                    if (status == WorkflowConstants.STATUS_EXPIRED) {
5538                            article.setExpirationDate(now);
5539                    }
5540    
5541                    article.setStatus(status);
5542                    article.setStatusByUserId(user.getUserId());
5543                    article.setStatusByUserName(user.getFullName());
5544                    article.setStatusDate(serviceContext.getModifiedDate(now));
5545    
5546                    journalArticlePersistence.update(article);
5547    
5548                    if (hasModifiedLatestApprovedVersion(
5549                                    article.getGroupId(), article.getArticleId(),
5550                                    article.getVersion())) {
5551    
5552                            if (status == WorkflowConstants.STATUS_APPROVED) {
5553                                    updateUrlTitles(
5554                                            article.getGroupId(), article.getArticleId(),
5555                                            article.getUrlTitle());
5556    
5557                                    // Asset
5558    
5559                                    if ((oldStatus != WorkflowConstants.STATUS_APPROVED) &&
5560                                            (article.getVersion() !=
5561                                                    JournalArticleConstants.VERSION_DEFAULT)) {
5562    
5563                                            AssetEntry draftAssetEntry =
5564                                                    assetEntryLocalService.fetchEntry(
5565                                                            JournalArticle.class.getName(),
5566                                                            article.getPrimaryKey());
5567    
5568                                            if (draftAssetEntry != null) {
5569                                                    long[] assetCategoryIds =
5570                                                            draftAssetEntry.getCategoryIds();
5571                                                    String[] assetTagNames = draftAssetEntry.getTagNames();
5572    
5573                                                    List<AssetLink> assetLinks =
5574                                                            assetLinkLocalService.getDirectLinks(
5575                                                                    draftAssetEntry.getEntryId(),
5576                                                                    AssetLinkConstants.TYPE_RELATED);
5577    
5578                                                    long[] assetLinkEntryIds = ListUtil.toLongArray(
5579                                                            assetLinks, AssetLink.ENTRY_ID2_ACCESSOR);
5580    
5581                                                    AssetEntry assetEntry =
5582                                                            assetEntryLocalService.updateEntry(
5583                                                                    userId, article.getGroupId(),
5584                                                                    article.getCreateDate(),
5585                                                                    article.getModifiedDate(),
5586                                                                    JournalArticle.class.getName(),
5587                                                                    article.getResourcePrimKey(), article.getUuid(),
5588                                                                    getClassTypeId(article), assetCategoryIds,
5589                                                                    assetTagNames, false, null, null, null,
5590                                                                    ContentTypes.TEXT_HTML, article.getTitle(),
5591                                                                    article.getDescription(),
5592                                                                    article.getDescription(), null,
5593                                                                    article.getLayoutUuid(), 0, 0, null, false);
5594    
5595                                                    assetLinkLocalService.updateLinks(
5596                                                            userId, assetEntry.getEntryId(), assetLinkEntryIds,
5597                                                            AssetLinkConstants.TYPE_RELATED);
5598    
5599                                                    assetEntryLocalService.deleteEntry(draftAssetEntry);
5600                                            }
5601                                    }
5602    
5603                                    if (article.getClassNameId() ==
5604                                                    JournalArticleConstants.CLASSNAME_ID_DEFAULT) {
5605    
5606                                            assetEntryLocalService.updateEntry(
5607                                                    JournalArticle.class.getName(),
5608                                                    article.getResourcePrimKey(), article.getDisplayDate(),
5609                                                    article.getExpirationDate(), true);
5610                                    }
5611    
5612                                    // Social
5613    
5614                                    JSONObject extraDataJSONObject =
5615                                            JSONFactoryUtil.createJSONObject();
5616    
5617                                    extraDataJSONObject.put("title", article.getTitle());
5618    
5619                                    if (serviceContext.isCommandUpdate()) {
5620                                            socialActivityLocalService.addActivity(
5621                                                    user.getUserId(), article.getGroupId(),
5622                                                    JournalArticle.class.getName(),
5623                                                    article.getResourcePrimKey(),
5624                                                    JournalActivityKeys.UPDATE_ARTICLE,
5625                                                    extraDataJSONObject.toString(), 0);
5626                                    }
5627                                    else {
5628                                            socialActivityLocalService.addUniqueActivity(
5629                                                    user.getUserId(), article.getGroupId(),
5630                                                    JournalArticle.class.getName(),
5631                                                    article.getResourcePrimKey(),
5632                                                    JournalActivityKeys.ADD_ARTICLE,
5633                                                    extraDataJSONObject.toString(), 0);
5634                                    }
5635                            }
5636                            else if (oldStatus == WorkflowConstants.STATUS_APPROVED) {
5637                                    updatePreviousApprovedArticle(article);
5638                            }
5639                    }
5640    
5641                    if ((article.getClassNameId() ==
5642                                    JournalArticleConstants.CLASSNAME_ID_DEFAULT) &&
5643                            (oldStatus != WorkflowConstants.STATUS_IN_TRASH) &&
5644                            (status != WorkflowConstants.STATUS_IN_TRASH)) {
5645    
5646                            // Email
5647    
5648                            if ((oldStatus == WorkflowConstants.STATUS_PENDING) &&
5649                                    ((status == WorkflowConstants.STATUS_APPROVED) ||
5650                                     (status == WorkflowConstants.STATUS_DENIED))) {
5651    
5652                                    String msg = "granted";
5653    
5654                                    if (status == WorkflowConstants.STATUS_DENIED) {
5655                                            msg = "denied";
5656                                    }
5657    
5658                                    try {
5659                                            PortletPreferences preferences =
5660                                                    ServiceContextUtil.getPortletPreferences(
5661                                                            serviceContext);
5662    
5663                                            articleURL = buildArticleURL(
5664                                                    articleURL, article.getGroupId(), article.getFolderId(),
5665                                                    article.getArticleId());
5666    
5667                                            sendEmail(
5668                                                    article, articleURL, preferences, msg, serviceContext);
5669                                    }
5670                                    catch (Exception e) {
5671                                            _log.error(
5672                                                    "Unable to send email to notify the change of status " +
5673                                                            " to " + msg + " for article " + article.getId() +
5674                                                                    ": " + e.getMessage());
5675                                    }
5676                            }
5677    
5678                            // Subscriptions
5679    
5680                            notifySubscribers(
5681                                    article,
5682                                    (String)workflowContext.get(WorkflowConstants.CONTEXT_URL),
5683                                    serviceContext);
5684                    }
5685    
5686                    return article;
5687            }
5688    
5689            /**
5690             * Updates the workflow status of the web content article matching the class
5691             * PK.
5692             *
5693             * @param  userId the primary key of the user updating the web content
5694             *         article's status
5695             * @param  classPK the primary key of the DDM structure, if the web content
5696             *         article is related to a DDM structure, the primary key of the
5697             *         class associated with the article, or <code>0</code> otherwise
5698             * @param  status the web content article's workflow status. For more
5699             *         information see {@link WorkflowConstants} for constants starting
5700             *         with the "STATUS_" prefix.
5701             * @param  workflowContext the web content article's configured workflow
5702             * @param  serviceContext the service context to be applied. Can set the
5703             *         modification date, portlet preferences, and can set whether to
5704             *         add the default command update for the web content article.
5705             * @return the updated web content article
5706             * @throws PortalException if a matching web content article could not be
5707             *         found or if a portal exception occurred
5708             */
5709            @Override
5710            public JournalArticle updateStatus(
5711                            long userId, long classPK, int status,
5712                            Map<String, Serializable> workflowContext,
5713                            ServiceContext serviceContext)
5714                    throws PortalException {
5715    
5716                    JournalArticle article = getArticle(classPK);
5717    
5718                    return journalArticleLocalService.updateStatus(
5719                            userId, article, status, null, serviceContext, workflowContext);
5720            }
5721    
5722            /**
5723             * Updates the workflow status of the web content article matching the
5724             * group, article ID, and version.
5725             *
5726             * @param  userId the primary key of the user updating the web content
5727             *         article's status
5728             * @param  groupId the primary key of the web content article's group
5729             * @param  articleId the primary key of the web content article
5730             * @param  version the web content article's version
5731             * @param  status the web content article's workflow status. For more
5732             *         information see {@link WorkflowConstants} for constants starting
5733             *         with the "STATUS_" prefix.
5734             * @param  articleURL the web content article's accessible URL
5735             * @param  workflowContext the web content article's configured workflow
5736             * @param  serviceContext the service context to be applied. Can set the
5737             *         modification date, portlet preferences, and can set whether to
5738             *         add the default command update for the web content article.
5739             * @return the updated web content article
5740             * @throws PortalException if a matching web content article could not be
5741             *         found or if a portal exception occurred
5742             */
5743            @Override
5744            public JournalArticle updateStatus(
5745                            long userId, long groupId, String articleId, double version,
5746                            int status, String articleURL,
5747                            Map<String, Serializable> workflowContext,
5748                            ServiceContext serviceContext)
5749                    throws PortalException {
5750    
5751                    JournalArticle article = journalArticlePersistence.findByG_A_V(
5752                            groupId, articleId, version);
5753    
5754                    return journalArticleLocalService.updateStatus(
5755                            userId, article, status, articleURL, serviceContext,
5756                            workflowContext);
5757            }
5758    
5759            /**
5760             * Updates the web content articles matching the group, class name ID, and
5761             * DDM template key, replacing the DDM template key with a new one.
5762             *
5763             * @param      groupId the primary key of the web content article's group
5764             * @param      classNameId the primary key of the DDMStructure class if the
5765             *             web content article is related to a DDM structure, the
5766             *             primary key of the class name associated with the article, or
5767             *             {@link JournalArticleConstants#CLASSNAME_ID_DEFAULT}
5768             *             otherwise
5769             * @param      oldDDMTemplateKey the primary key of the web content
5770             *             article's old DDM template
5771             * @param      newDDMTemplateKey the primary key of the web content
5772             *             article's new DDM template
5773             * @deprecated As of 7.0.0, replaced by {@link #updateDDMTemplateKey}
5774             */
5775            @Deprecated
5776            @Override
5777            public void updateTemplateId(
5778                    long groupId, long classNameId, String oldDDMTemplateKey,
5779                    String newDDMTemplateKey) {
5780    
5781                    updateDDMTemplateKey(
5782                            groupId, classNameId, oldDDMTemplateKey, newDDMTemplateKey);
5783            }
5784    
5785            protected String buildArticleURL(
5786                    String articleURL, long groupId, long folderId, String articleId) {
5787    
5788                    StringBundler sb = new StringBundler(13);
5789    
5790                    sb.append(articleURL);
5791                    sb.append(StringPool.AMPERSAND);
5792                    sb.append(PortalUtil.getPortletNamespace(PortletKeys.JOURNAL));
5793                    sb.append("groupId=");
5794                    sb.append(groupId);
5795                    sb.append(StringPool.AMPERSAND);
5796                    sb.append(PortalUtil.getPortletNamespace(PortletKeys.JOURNAL));
5797                    sb.append("folderId=");
5798                    sb.append(folderId);
5799                    sb.append(StringPool.AMPERSAND);
5800                    sb.append(PortalUtil.getPortletNamespace(PortletKeys.JOURNAL));
5801                    sb.append("articleId=");
5802                    sb.append(articleId);
5803    
5804                    return sb.toString();
5805            }
5806    
5807            protected SearchContext buildSearchContext(
5808                    long companyId, long groupId, List<Long> folderIds, long classNameId,
5809                    String articleId, String title, String description, String content,
5810                    int status, String ddmStructureKey, String ddmTemplateKey,
5811                    LinkedHashMap<String, Object> params, boolean andSearch, int start,
5812                    int end, Sort sort) {
5813    
5814                    SearchContext searchContext = new SearchContext();
5815    
5816                    searchContext.setAndSearch(andSearch);
5817    
5818                    Map<String, Serializable> attributes =
5819                            new HashMap<String, Serializable>();
5820    
5821                    attributes.put(Field.ARTICLE_ID, articleId);
5822                    attributes.put(Field.CLASS_NAME_ID, classNameId);
5823                    attributes.put(Field.CONTENT, content);
5824                    attributes.put(Field.DESCRIPTION, description);
5825                    attributes.put(Field.STATUS, status);
5826                    attributes.put(Field.TITLE, title);
5827                    attributes.put("ddmStructureKey", ddmStructureKey);
5828                    attributes.put("ddmTemplateKey", ddmTemplateKey);
5829                    attributes.put("params", params);
5830    
5831                    searchContext.setAttributes(attributes);
5832    
5833                    searchContext.setCompanyId(companyId);
5834                    searchContext.setEnd(end);
5835                    searchContext.setFolderIds(folderIds);
5836                    searchContext.setGroupIds(new long[] {groupId});
5837                    searchContext.setIncludeDiscussions(
5838                            GetterUtil.getBoolean(params.get("includeDiscussions")));
5839    
5840                    if (params != null) {
5841                            String keywords = (String)params.remove("keywords");
5842    
5843                            if (Validator.isNotNull(keywords)) {
5844                                    searchContext.setKeywords(keywords);
5845                            }
5846                    }
5847    
5848                    QueryConfig queryConfig = new QueryConfig();
5849    
5850                    queryConfig.setHighlightEnabled(false);
5851                    queryConfig.setScoreEnabled(false);
5852    
5853                    searchContext.setQueryConfig(queryConfig);
5854    
5855                    if (sort != null) {
5856                            searchContext.setSorts(sort);
5857                    }
5858    
5859                    searchContext.setStart(start);
5860    
5861                    return searchContext;
5862            }
5863    
5864            protected SearchContext buildSearchContext(
5865                            long groupId, long userId, long creatorUserId, int status,
5866                            int start, int end)
5867                    throws PortalException {
5868    
5869                    SearchContext searchContext = new SearchContext();
5870    
5871                    searchContext.setAttribute(Field.STATUS, status);
5872    
5873                    searchContext.setAttribute("paginationType", "none");
5874    
5875                    if (creatorUserId > 0) {
5876                            searchContext.setAttribute(
5877                                    Field.USER_ID, String.valueOf(creatorUserId));
5878                    }
5879    
5880                    Group group = groupLocalService.getGroup(groupId);
5881    
5882                    searchContext.setCompanyId(group.getCompanyId());
5883    
5884                    searchContext.setEnd(end);
5885                    searchContext.setGroupIds(new long[] {groupId});
5886                    searchContext.setSorts(new Sort(Field.MODIFIED_DATE, true));
5887                    searchContext.setStart(start);
5888                    searchContext.setUserId(userId);
5889    
5890                    return searchContext;
5891            }
5892    
5893            protected void checkArticlesByDisplayDate(Date displayDate)
5894                    throws PortalException {
5895    
5896                    List<JournalArticle> articles = journalArticlePersistence.findByLtD_S(
5897                            displayDate, WorkflowConstants.STATUS_SCHEDULED);
5898    
5899                    for (JournalArticle article : articles) {
5900                            ServiceContext serviceContext = new ServiceContext();
5901    
5902                            serviceContext.setCommand(Constants.UPDATE);
5903    
5904                            String layoutFullURL = PortalUtil.getLayoutFullURL(
5905                                    article.getGroupId(), PortletKeys.JOURNAL);
5906    
5907                            serviceContext.setLayoutFullURL(layoutFullURL);
5908    
5909                            serviceContext.setScopeGroupId(article.getGroupId());
5910    
5911                            journalArticleLocalService.updateStatus(
5912                                    article.getUserId(), article, WorkflowConstants.STATUS_APPROVED,
5913                                    null, serviceContext, new HashMap<String, Serializable>());
5914                    }
5915            }
5916    
5917            protected void checkArticlesByExpirationDate(Date expirationDate)
5918                    throws PortalException {
5919    
5920                    List<JournalArticle> articles =
5921                            journalArticleFinder.findByExpirationDate(
5922                                    JournalArticleConstants.CLASSNAME_ID_DEFAULT,
5923                                    new Date(
5924                                            expirationDate.getTime() + _JOURNAL_ARTICLE_CHECK_INTERVAL),
5925                                    new QueryDefinition<JournalArticle>(
5926                                            WorkflowConstants.STATUS_APPROVED));
5927    
5928                    if (_log.isDebugEnabled()) {
5929                            _log.debug("Expiring " + articles.size() + " articles");
5930                    }
5931    
5932                    Set<Long> companyIds = new HashSet<Long>();
5933    
5934                    for (JournalArticle article : articles) {
5935                            if (PropsValues.JOURNAL_ARTICLE_EXPIRE_ALL_VERSIONS) {
5936                                    List<JournalArticle> currentArticles =
5937                                            journalArticlePersistence.findByG_A(
5938                                                    article.getGroupId(), article.getArticleId(),
5939                                                    QueryUtil.ALL_POS, QueryUtil.ALL_POS,
5940                                                    new ArticleVersionComparator(true));
5941    
5942                                    for (JournalArticle currentArticle : currentArticles) {
5943                                            currentArticle.setExpirationDate(
5944                                                    article.getExpirationDate());
5945                                            currentArticle.setStatus(WorkflowConstants.STATUS_EXPIRED);
5946    
5947                                            journalArticlePersistence.update(currentArticle);
5948                                    }
5949                            }
5950                            else {
5951                                    article.setStatus(WorkflowConstants.STATUS_EXPIRED);
5952    
5953                                    journalArticlePersistence.update(article);
5954                            }
5955    
5956                            updatePreviousApprovedArticle(article);
5957    
5958                            Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
5959                                    JournalArticle.class);
5960    
5961                            indexer.reindex(article);
5962    
5963                            JournalContentUtil.clearCache(
5964                                    article.getGroupId(), article.getArticleId(),
5965                                    article.getDDMTemplateKey());
5966    
5967                            companyIds.add(article.getCompanyId());
5968                    }
5969    
5970                    for (long companyId : companyIds) {
5971                            CacheUtil.clearCache(companyId);
5972                    }
5973    
5974                    if (_previousCheckDate == null) {
5975                            _previousCheckDate = new Date(
5976                                    expirationDate.getTime() - _JOURNAL_ARTICLE_CHECK_INTERVAL);
5977                    }
5978            }
5979    
5980            protected void checkArticlesByReviewDate(Date reviewDate)
5981                    throws PortalException {
5982    
5983                    List<JournalArticle> articles = journalArticleFinder.findByReviewDate(
5984                            JournalArticleConstants.CLASSNAME_ID_DEFAULT, reviewDate,
5985                            _previousCheckDate);
5986    
5987                    if (_log.isDebugEnabled()) {
5988                            _log.debug(
5989                                    "Sending review notifications for " + articles.size() +
5990                                            " articles");
5991                    }
5992    
5993                    for (JournalArticle article : articles) {
5994                            String articleURL = StringPool.BLANK;
5995    
5996                            long ownerId = article.getGroupId();
5997                            int ownerType = PortletKeys.PREFS_OWNER_TYPE_GROUP;
5998                            long plid = PortletKeys.PREFS_PLID_SHARED;
5999                            String portletId = PortletKeys.JOURNAL;
6000    
6001                            PortletPreferences preferences =
6002                                    portletPreferencesLocalService.getPreferences(
6003                                            article.getCompanyId(), ownerId, ownerType, plid,
6004                                            portletId);
6005    
6006                            sendEmail(
6007                                    article, articleURL, preferences, "review",
6008                                    new ServiceContext());
6009                    }
6010            }
6011    
6012            protected void checkStructure(Document contentDocument, DDMForm ddmForm)
6013                    throws PortalException {
6014    
6015                    for (DDMFormField ddmFormField : ddmForm.getDDMFormFields()) {
6016                            if (isPrivateDDMFormField(ddmFormField)) {
6017                                    continue;
6018                            }
6019    
6020                            checkStructureField(ddmFormField, contentDocument.getRootElement());
6021                    }
6022            }
6023    
6024            protected void checkStructure(JournalArticle article)
6025                    throws PortalException {
6026    
6027                    DDMStructure ddmStructure = article.getDDMStructure();
6028    
6029                    try {
6030                            checkStructure(article, ddmStructure);
6031                    }
6032                    catch (StructureDefinitionException sde) {
6033                            if (_log.isWarnEnabled()) {
6034                                    StringBundler sb = new StringBundler(8);
6035    
6036                                    sb.append("Article {groupId=");
6037                                    sb.append(article.getGroupId());
6038                                    sb.append(", articleId=");
6039                                    sb.append(article.getArticleId());
6040                                    sb.append(", version=");
6041                                    sb.append(article.getVersion());
6042                                    sb.append("} has content that does not match its structure: ");
6043                                    sb.append(sde.getMessage());
6044    
6045                                    _log.warn(sb.toString());
6046                            }
6047                    }
6048            }
6049    
6050            protected void checkStructure(
6051                            JournalArticle article, DDMStructure ddmStructure)
6052                    throws PortalException {
6053    
6054                    checkStructure(article.getDocument(), ddmStructure.getDDMForm());
6055            }
6056    
6057            protected void checkStructureField(
6058                            DDMFormField ddmFormField, Element contentElement)
6059                    throws PortalException {
6060    
6061                    String fieldName = ddmFormField.getName();
6062    
6063                    boolean hasField = false;
6064    
6065                    for (Element childElement : contentElement.elements()) {
6066                            if (fieldName.equals(
6067                                            childElement.attributeValue("name", StringPool.BLANK))) {
6068    
6069                                    hasField = true;
6070    
6071                                    for (DDMFormField childDDMFormField :
6072                                                    ddmFormField.getNestedDDMFormFields()) {
6073    
6074                                            checkStructureField(childDDMFormField, childElement);
6075                                    }
6076    
6077                                    break;
6078                            }
6079                    }
6080    
6081                    if (!hasField) {
6082                            String contentElementType = contentElement.attributeValue(
6083                                    "type", StringPool.BLANK);
6084    
6085                            if (!contentElementType.equals("list") &&
6086                                    !contentElementType.equals("multi-list")) {
6087    
6088                                    throw new StructureDefinitionException(fieldName);
6089                            }
6090                    }
6091            }
6092    
6093            protected void copyArticleImages(
6094                            JournalArticle oldArticle, JournalArticle newArticle)
6095                    throws Exception {
6096    
6097                    Document contentDocument = oldArticle.getDocument();
6098    
6099                    contentDocument = contentDocument.clone();
6100    
6101                    XPath xPathSelector = SAXReaderUtil.createXPath(
6102                            "//dynamic-element[@type='image']");
6103    
6104                    List<Node> imageNodes = xPathSelector.selectNodes(contentDocument);
6105    
6106                    for (Node imageNode : imageNodes) {
6107                            Element imageEl = (Element)imageNode;
6108    
6109                            String instanceId = imageEl.attributeValue("instance-id");
6110                            String name = imageEl.attributeValue("name");
6111    
6112                            List<Element> dynamicContentEls = imageEl.elements(
6113                                    "dynamic-content");
6114    
6115                            for (Element dynamicContentEl : dynamicContentEls) {
6116                                    long imageId = GetterUtil.getLong(
6117                                            dynamicContentEl.attributeValue("id"));
6118                                    String languageId = dynamicContentEl.attributeValue(
6119                                            "language-id");
6120    
6121                                    Image oldImage = null;
6122    
6123                                    try {
6124                                            oldImage = imageLocalService.getImage(imageId);
6125                                    }
6126                                    catch (NoSuchImageException nsie) {
6127                                            continue;
6128                                    }
6129    
6130                                    imageId = journalArticleImageLocalService.getArticleImageId(
6131                                            newArticle.getGroupId(), newArticle.getArticleId(),
6132                                            newArticle.getVersion(), instanceId, name, languageId);
6133    
6134                                    imageLocalService.updateImage(imageId, oldImage.getTextObj());
6135    
6136                                    String elContent =
6137                                            "/image/journal/article?img_id=" + imageId + "&t=" +
6138                                                    WebServerServletTokenUtil.getToken(imageId);
6139    
6140                                    dynamicContentEl.setText(elContent);
6141                                    dynamicContentEl.addAttribute("id", String.valueOf(imageId));
6142                            }
6143                    }
6144    
6145                    newArticle.setContent(contentDocument.formattedString());
6146            }
6147    
6148            protected Map<String, String> createFieldsValuesMap(Element parentElement) {
6149                    Map<String, String> fieldsValuesMap = new HashMap<String, String>();
6150    
6151                    List<Element> dynamicElementElements = parentElement.elements(
6152                            "dynamic-element");
6153    
6154                    for (Element dynamicElementElement : dynamicElementElements) {
6155                            String fieldName = dynamicElementElement.attributeValue(
6156                                    "name", StringPool.BLANK);
6157    
6158                            List<Element> dynamicContentElements =
6159                                    dynamicElementElement.elements("dynamic-content");
6160    
6161                            for (Element dynamicContentElement : dynamicContentElements) {
6162                                    String value = dynamicContentElement.getText();
6163    
6164                                    fieldsValuesMap.put(fieldName, value);
6165                            }
6166    
6167                            fieldsValuesMap.putAll(
6168                                    createFieldsValuesMap(dynamicElementElement));
6169                    }
6170    
6171                    return fieldsValuesMap;
6172            }
6173    
6174            protected Map<String, String> createFieldsValuesMap(String content) {
6175                    try {
6176                            Document document = SAXReaderUtil.read(content);
6177    
6178                            Element rootElement = document.getRootElement();
6179    
6180                            return createFieldsValuesMap(rootElement);
6181                    }
6182                    catch (DocumentException de) {
6183                            throw new SystemException(de);
6184                    }
6185            }
6186    
6187            protected void format(
6188                            User user, long groupId, String articleId, double version,
6189                            boolean incrementVersion, Element root, Map<String, byte[]> images)
6190                    throws PortalException {
6191    
6192                    for (Element element : root.elements()) {
6193                            String elInstanceId = element.attributeValue(
6194                                    "instance-id", StringPool.BLANK);
6195                            String elType = element.attributeValue("type", StringPool.BLANK);
6196    
6197                            if (elType.equals("document_library")) {
6198                                    formatDocumentLibrary(element);
6199                            }
6200                            else if (elType.equals("image")) {
6201                                    String elName = element.attributeValue(
6202                                            "name", StringPool.BLANK);
6203                                    String elIndex = element.attributeValue(
6204                                            "index", StringPool.BLANK);
6205    
6206                                    String name = elName + "_" + elIndex;
6207    
6208                                    formatImage(
6209                                            groupId, articleId, version, incrementVersion, element,
6210                                            elInstanceId, name, images);
6211                            }
6212                            else if (elType.equals("text_area") || elType.equals("text") ||
6213                                             elType.equals("text_box")) {
6214    
6215                                    List<Element> dynamicContentElements = element.elements(
6216                                            "dynamic-content");
6217    
6218                                    for (Element dynamicContentElement : dynamicContentElements) {
6219                                            String dynamicContent = dynamicContentElement.getText();
6220    
6221                                            if (Validator.isNotNull(dynamicContent)) {
6222                                                    String contentType = ContentTypes.TEXT_PLAIN;
6223    
6224                                                    if (elType.equals("text_area")) {
6225                                                            contentType = ContentTypes.TEXT_HTML;
6226                                                    }
6227    
6228                                                    dynamicContent = SanitizerUtil.sanitize(
6229                                                            user.getCompanyId(), groupId, user.getUserId(),
6230                                                            JournalArticle.class.getName(), 0, contentType,
6231                                                            dynamicContent);
6232    
6233                                                    dynamicContentElement.clearContent();
6234    
6235                                                    dynamicContentElement.addCDATA(dynamicContent);
6236                                            }
6237                                    }
6238                            }
6239    
6240                            format(
6241                                    user, groupId, articleId, version, incrementVersion, element,
6242                                    images);
6243                    }
6244            }
6245    
6246            protected String format(
6247                            User user, long groupId, String articleId, double version,
6248                            boolean incrementVersion, String content, String ddmStructureKey,
6249                            Map<String, byte[]> images)
6250                    throws PortalException {
6251    
6252                    Document document = null;
6253    
6254                    try {
6255                            document = SAXReaderUtil.read(content);
6256    
6257                            Element rootElement = document.getRootElement();
6258    
6259                            format(
6260                                    user, groupId, articleId, version, incrementVersion,
6261                                    rootElement, images);
6262    
6263                            content = DDMXMLUtil.formatXML(document);
6264                    }
6265                    catch (DocumentException de) {
6266                            _log.error(de, de);
6267                    }
6268    
6269                    return content;
6270            }
6271    
6272            protected void formatDocumentLibrary(Element dynamicElementElement)
6273                    throws PortalException {
6274    
6275                    for (Element dynamicContentElement :
6276                                    dynamicElementElement.elements("dynamic-content")) {
6277    
6278                            String value = dynamicContentElement.getText();
6279    
6280                            if (Validator.isNull(value)) {
6281                                    continue;
6282                            }
6283    
6284                            JSONObject jsonObject = JSONFactoryUtil.createJSONObject(value);
6285    
6286                            String uuid = jsonObject.getString("uuid");
6287                            long groupId = jsonObject.getLong("groupId");
6288                            boolean tempFile = jsonObject.getBoolean("tempFile");
6289    
6290                            FileEntry fileEntry =
6291                                    dlAppLocalService.getFileEntryByUuidAndGroupId(uuid, groupId);
6292    
6293                            if (tempFile) {
6294                                    String fileEntryName = DLUtil.getFileName(
6295                                            fileEntry.getGroupId(), fileEntry.getFolderId(),
6296                                            fileEntry.getFileName());
6297    
6298                                    fileEntry = dlAppLocalService.addFileEntry(
6299                                            fileEntry.getUserId(), fileEntry.getGroupId(), 0,
6300                                            fileEntryName, fileEntry.getMimeType(), fileEntryName,
6301                                            StringPool.BLANK, StringPool.BLANK,
6302                                            fileEntry.getContentStream(), fileEntry.getSize(),
6303                                            new ServiceContext());
6304                            }
6305    
6306                            String previewURL = DLUtil.getPreviewURL(
6307                                    fileEntry, fileEntry.getFileVersion(), null, StringPool.BLANK,
6308                                    false, true);
6309    
6310                            dynamicContentElement.clearContent();
6311    
6312                            dynamicContentElement.addCDATA(previewURL);
6313                    }
6314            }
6315    
6316            protected void formatImage(
6317                            long groupId, String articleId, double version,
6318                            boolean incrementVersion, Element el, String elInstanceId,
6319                            String elName, Map<String, byte[]> images)
6320                    throws PortalException {
6321    
6322                    List<Element> imageContents = el.elements("dynamic-content");
6323    
6324                    for (Element dynamicContent : imageContents) {
6325                            String elLanguage = dynamicContent.attributeValue(
6326                                    "language-id", StringPool.BLANK);
6327    
6328                            if (!elLanguage.equals(StringPool.BLANK)) {
6329                                    elLanguage = "_" + elLanguage;
6330                            }
6331    
6332                            long imageId = journalArticleImageLocalService.getArticleImageId(
6333                                    groupId, articleId, version, elInstanceId, elName, elLanguage);
6334    
6335                            if (dynamicContent.getText().equals("delete") ||
6336                                    Validator.isNull(dynamicContent.getText())) {
6337    
6338                                    dynamicContent.setText(StringPool.BLANK);
6339    
6340                                    imageLocalService.deleteImage(imageId);
6341    
6342                                    String defaultElLanguage = "";
6343    
6344                                    if (Validator.isNull(elLanguage)) {
6345                                            defaultElLanguage =
6346                                                    "_" +
6347                                                            LocaleUtil.toLanguageId(
6348                                                                    LocaleUtil.getSiteDefault());
6349                                    }
6350    
6351                                    long defaultImageId =
6352                                            journalArticleImageLocalService.getArticleImageId(
6353                                                    groupId, articleId, version, elInstanceId, elName,
6354                                                    defaultElLanguage);
6355    
6356                                    imageLocalService.deleteImage(defaultImageId);
6357    
6358                                    continue;
6359                            }
6360    
6361                            String elContent =
6362                                    "/image/journal/article?img_id=" + imageId + "&t=" +
6363                                            WebServerServletTokenUtil.getToken(imageId);
6364    
6365                            byte[] bytes = null;
6366    
6367                            if (images != null) {
6368                                    bytes = images.get(elInstanceId + "_" + elName + elLanguage);
6369                            }
6370    
6371                            if (ArrayUtil.isNotEmpty(bytes)) {
6372                                    dynamicContent.setText(elContent);
6373                                    dynamicContent.addAttribute("id", String.valueOf(imageId));
6374    
6375                                    imageLocalService.updateImage(imageId, bytes);
6376    
6377                                    continue;
6378                            }
6379    
6380                            if ((version > JournalArticleConstants.VERSION_DEFAULT) &&
6381                                    incrementVersion) {
6382    
6383                                    double oldVersion = MathUtil.format(version - 0.1, 1, 1);
6384    
6385                                    long oldImageId = 0;
6386    
6387                                    if ((oldVersion >= 1) && incrementVersion) {
6388                                            oldImageId =
6389                                                    journalArticleImageLocalService.getArticleImageId(
6390                                                            groupId, articleId, oldVersion, elInstanceId,
6391                                                            elName, elLanguage);
6392                                    }
6393    
6394                                    Image oldImage = null;
6395    
6396                                    if (oldImageId > 0) {
6397                                            oldImage = imageLocalService.getImage(oldImageId);
6398                                    }
6399    
6400                                    if (oldImage != null) {
6401                                            dynamicContent.setText(elContent);
6402                                            dynamicContent.addAttribute("id", String.valueOf(imageId));
6403    
6404                                            bytes = oldImage.getTextObj();
6405    
6406                                            imageLocalService.updateImage(imageId, bytes);
6407                                    }
6408                                    else if (dynamicContent.getText().equals("update")) {
6409                                            dynamicContent.setText(StringPool.BLANK);
6410                                    }
6411    
6412                                    continue;
6413                            }
6414    
6415                            Image image = imageLocalService.getImage(imageId);
6416    
6417                            if (image != null) {
6418                                    dynamicContent.setText(elContent);
6419                                    dynamicContent.addAttribute("id", String.valueOf(imageId));
6420    
6421                                    continue;
6422                            }
6423                            else if (dynamicContent.getText().equals("update")) {
6424                                    dynamicContent.setText(StringPool.BLANK);
6425    
6426                                    continue;
6427                            }
6428    
6429                            long contentImageId = GetterUtil.getLong(
6430                                    HttpUtil.getParameter(dynamicContent.getText(), "img_id"));
6431    
6432                            if (contentImageId <= 0) {
6433                                    contentImageId = GetterUtil.getLong(
6434                                            HttpUtil.getParameter(
6435                                                    dynamicContent.getText(), "img_id", false));
6436                            }
6437    
6438                            if (contentImageId > 0) {
6439                                    image = imageLocalService.getImage(contentImageId);
6440    
6441                                    if (image != null) {
6442                                            dynamicContent.addAttribute(
6443                                                    "id", String.valueOf(contentImageId));
6444    
6445                                            continue;
6446                                    }
6447                            }
6448    
6449                            String defaultElLanguage = "";
6450    
6451                            if (Validator.isNull(elLanguage)) {
6452                                    defaultElLanguage =
6453                                            "_" + LocaleUtil.toLanguageId(LocaleUtil.getSiteDefault());
6454                            }
6455    
6456                            long defaultImageId =
6457                                    journalArticleImageLocalService.getArticleImageId(
6458                                            groupId, articleId, version, elInstanceId, elName,
6459                                            defaultElLanguage);
6460    
6461                            Image defaultImage = imageLocalService.getImage(defaultImageId);
6462    
6463                            if (defaultImage != null) {
6464                                    dynamicContent.setText(elContent);
6465                                    dynamicContent.addAttribute(
6466                                            "id", String.valueOf(defaultImageId));
6467    
6468                                    bytes = defaultImage.getTextObj();
6469    
6470                                    imageLocalService.updateImage(defaultImageId, bytes);
6471    
6472                                    continue;
6473                            }
6474    
6475                            if (Validator.isNotNull(elLanguage)) {
6476                                    dynamicContent.setText(StringPool.BLANK);
6477                            }
6478                    }
6479            }
6480    
6481            protected Locale getArticleDefaultLocale(String content) {
6482                    String defaultLanguageId = LocalizationUtil.getDefaultLanguageId(
6483                            content);
6484    
6485                    if (Validator.isNotNull(defaultLanguageId)) {
6486                            return LocaleUtil.fromLanguageId(defaultLanguageId);
6487                    }
6488    
6489                    return LocaleUtil.getSiteDefault();
6490            }
6491    
6492            protected JournalArticleDisplay getArticleDisplay(
6493                            JournalArticle article, String ddmTemplateKey, String viewMode,
6494                            String languageId, int page,
6495                            PortletRequestModel portletRequestModel, ThemeDisplay themeDisplay,
6496                            boolean propagateException)
6497                    throws PortalException {
6498    
6499                    String content = null;
6500    
6501                    if (page < 1) {
6502                            page = 1;
6503                    }
6504    
6505                    int numberOfPages = 1;
6506                    boolean paginate = false;
6507                    boolean pageFlow = false;
6508    
6509                    boolean cacheable = true;
6510    
6511                    Map<String, String> tokens = JournalUtil.getTokens(
6512                            article.getGroupId(), portletRequestModel, themeDisplay);
6513    
6514                    if ((themeDisplay == null) && (portletRequestModel == null)) {
6515                            tokens.put("company_id", String.valueOf(article.getCompanyId()));
6516    
6517                            Group companyGroup = groupLocalService.getCompanyGroup(
6518                                    article.getCompanyId());
6519    
6520                            tokens.put(
6521                                    "article_group_id", String.valueOf(article.getGroupId()));
6522                            tokens.put(
6523                                    "company_group_id", String.valueOf(companyGroup.getGroupId()));
6524    
6525                            // Deprecated tokens
6526    
6527                            tokens.put("group_id", String.valueOf(article.getGroupId()));
6528                    }
6529    
6530                    tokens.put(
6531                            TemplateConstants.CLASS_NAME_ID,
6532                            String.valueOf(
6533                                    classNameLocalService.getClassNameId(DDMStructure.class)));
6534                    tokens.put(
6535                            "article_resource_pk",
6536                            String.valueOf(article.getResourcePrimKey()));
6537    
6538                    String defaultDDMTemplateKey = article.getDDMTemplateKey();
6539    
6540                    if (Validator.isNull(ddmTemplateKey)) {
6541                            ddmTemplateKey = defaultDDMTemplateKey;
6542                    }
6543    
6544                    tokens.put("structure_id", article.getDDMStructureKey());
6545                    tokens.put("template_id", ddmTemplateKey);
6546    
6547                    Document document = article.getDocument();
6548    
6549                    document = document.clone();
6550    
6551                    Element rootElement = document.getRootElement();
6552    
6553                    List<Element> pages = rootElement.elements("page");
6554    
6555                    if (!pages.isEmpty()) {
6556                            pageFlow = true;
6557    
6558                            String targetPage = null;
6559    
6560                            Map<String, String[]> parameters =
6561                                    portletRequestModel.getParameters();
6562    
6563                            if (parameters != null) {
6564                                    String[] values = parameters.get("targetPage");
6565    
6566                                    if ((values != null) && (values.length > 0)) {
6567                                            targetPage = values[0];
6568                                    }
6569                            }
6570    
6571                            Element pageElement = null;
6572    
6573                            if (Validator.isNotNull(targetPage)) {
6574                                    targetPage = HtmlUtil.escapeXPathAttribute(targetPage);
6575    
6576                                    XPath xPathSelector = SAXReaderUtil.createXPath(
6577                                            "/root/page[@id = " + targetPage + "]");
6578    
6579                                    pageElement = (Element)xPathSelector.selectSingleNode(document);
6580                            }
6581    
6582                            if (pageElement != null) {
6583                                    document = SAXReaderUtil.createDocument(pageElement);
6584    
6585                                    rootElement = document.getRootElement();
6586    
6587                                    numberOfPages = pages.size();
6588                            }
6589                            else {
6590                                    if (page > pages.size()) {
6591                                            page = 1;
6592                                    }
6593    
6594                                    pageElement = pages.get(page - 1);
6595    
6596                                    document = SAXReaderUtil.createDocument(pageElement);
6597    
6598                                    rootElement = document.getRootElement();
6599    
6600                                    numberOfPages = pages.size();
6601                                    paginate = true;
6602                            }
6603                    }
6604    
6605                    JournalUtil.addAllReservedEls(
6606                            rootElement, tokens, article, languageId, themeDisplay);
6607    
6608                    try {
6609                            if (_log.isDebugEnabled()) {
6610                                    _log.debug(
6611                                            "Transforming " + article.getArticleId() + " " +
6612                                                    article.getVersion() + " " + languageId);
6613                            }
6614    
6615                            // Try with specified template first (in the current group and the
6616                            // global group). If a template is not specified, use the default
6617                            // one. If the specified template does not exist, use the default
6618                            // one. If the default one does not exist, throw an exception.
6619    
6620                            DDMTemplate ddmTemplate = null;
6621    
6622                            try {
6623                                    ddmTemplate = ddmTemplateLocalService.getTemplate(
6624                                            PortalUtil.getSiteGroupId(article.getGroupId()),
6625                                            classNameLocalService.getClassNameId(DDMStructure.class),
6626                                            ddmTemplateKey, true);
6627    
6628                                    Group companyGroup = groupLocalService.getCompanyGroup(
6629                                            article.getCompanyId());
6630    
6631                                    if (companyGroup.getGroupId() == ddmTemplate.getGroupId()) {
6632                                            tokens.put(
6633                                                    "company_group_id",
6634                                                    String.valueOf(companyGroup.getGroupId()));
6635                                    }
6636                            }
6637                            catch (NoSuchTemplateException nste) {
6638                                    if (!defaultDDMTemplateKey.equals(ddmTemplateKey)) {
6639                                            ddmTemplate = ddmTemplatePersistence.findByG_C_T(
6640                                                    PortalUtil.getSiteGroupId(article.getGroupId()),
6641                                                    classNameLocalService.getClassNameId(
6642                                                            DDMStructure.class),
6643                                                    defaultDDMTemplateKey);
6644                                    }
6645                                    else {
6646                                            throw nste;
6647                                    }
6648                            }
6649    
6650                            String script = ddmTemplate.getScript();
6651                            String langType = ddmTemplate.getLanguage();
6652                            cacheable = ddmTemplate.isCacheable();
6653    
6654                            content = JournalUtil.transform(
6655                                    themeDisplay, tokens, viewMode, languageId, document,
6656                                    portletRequestModel, script, langType, propagateException);
6657    
6658                            if (!pageFlow) {
6659                                    String[] pieces = StringUtil.split(
6660                                            content, PropsValues.JOURNAL_ARTICLE_TOKEN_PAGE_BREAK);
6661    
6662                                    if (pieces.length > 1) {
6663                                            if (page > pieces.length) {
6664                                                    page = 1;
6665                                            }
6666    
6667                                            content = pieces[page - 1];
6668                                            numberOfPages = pieces.length;
6669                                            paginate = true;
6670                                    }
6671                            }
6672                    }
6673                    catch (Exception e) {
6674                            throw new SystemException(e);
6675                    }
6676    
6677                    return new JournalArticleDisplayImpl(
6678                            article.getCompanyId(), article.getId(),
6679                            article.getResourcePrimKey(), article.getGroupId(),
6680                            article.getUserId(), article.getArticleId(), article.getVersion(),
6681                            article.getTitle(languageId), article.getUrlTitle(),
6682                            article.getDescription(languageId),
6683                            article.getAvailableLanguageIds(), content,
6684                            article.getDDMStructureKey(), ddmTemplateKey,
6685                            article.isSmallImage(), article.getSmallImageId(),
6686                            article.getSmallImageURL(), numberOfPages, page, paginate,
6687                            cacheable);
6688            }
6689    
6690            protected List<ObjectValuePair<Long, Integer>> getArticleVersionStatuses(
6691                    List<JournalArticle> articles) {
6692    
6693                    List<ObjectValuePair<Long, Integer>> articleVersionStatusOVPs =
6694                            new ArrayList<ObjectValuePair<Long, Integer>>(articles.size());
6695    
6696                    for (JournalArticle article : articles) {
6697                            int status = article.getStatus();
6698    
6699                            if (status == WorkflowConstants.STATUS_PENDING) {
6700                                    status = WorkflowConstants.STATUS_DRAFT;
6701                            }
6702    
6703                            ObjectValuePair<Long, Integer> articleVersionStatusOVP =
6704                                    new ObjectValuePair<Long, Integer>(article.getId(), status);
6705    
6706                            articleVersionStatusOVPs.add(articleVersionStatusOVP);
6707                    }
6708    
6709                    return articleVersionStatusOVPs;
6710            }
6711    
6712            protected long getClassTypeId(JournalArticle article)
6713                    throws PortalException {
6714    
6715                    long classNameId = classNameLocalService.getClassNameId(
6716                            JournalArticle.class);
6717    
6718                    DDMStructure ddmStructure = ddmStructureLocalService.fetchStructure(
6719                            article.getGroupId(), classNameId, article.getDDMStructureKey(),
6720                            true);
6721    
6722                    return ddmStructure.getStructureId();
6723            }
6724    
6725            protected JournalArticle getFirstArticle(
6726                            long groupId, String articleId, int status,
6727                            OrderByComparator<JournalArticle> orderByComparator)
6728                    throws PortalException {
6729    
6730                    if (status == WorkflowConstants.STATUS_ANY) {
6731                            return journalArticlePersistence.findByG_A_NotST_First(
6732                                    groupId, articleId, WorkflowConstants.STATUS_IN_TRASH,
6733                                    orderByComparator);
6734                    }
6735                    else {
6736                            return journalArticlePersistence.findByG_A_ST_First(
6737                                    groupId, articleId, status, orderByComparator);
6738                    }
6739            }
6740    
6741            protected String getUniqueUrlTitle(
6742                            long id, long groupId, String articleId, String title)
6743                    throws PortalException {
6744    
6745                    String urlTitle = JournalUtil.getUrlTitle(id, title);
6746    
6747                    return getUniqueUrlTitle(groupId, articleId, urlTitle);
6748            }
6749    
6750            protected String getUniqueUrlTitle(
6751                            long id, String articleId, String title, String oldUrlTitle,
6752                            ServiceContext serviceContext)
6753                    throws PortalException {
6754    
6755                    String serviceContextUrlTitle = ParamUtil.getString(
6756                            serviceContext, "urlTitle");
6757    
6758                    String urlTitle = null;
6759    
6760                    if (Validator.isNotNull(serviceContextUrlTitle)) {
6761                            urlTitle = JournalUtil.getUrlTitle(id, serviceContextUrlTitle);
6762                    }
6763                    else if (Validator.isNotNull(oldUrlTitle)) {
6764                            return oldUrlTitle;
6765                    }
6766                    else {
6767                            urlTitle = getUniqueUrlTitle(
6768                                    id, serviceContext.getScopeGroupId(), articleId, title);
6769                    }
6770    
6771                    JournalArticle urlTitleArticle = null;
6772    
6773                    try {
6774                            urlTitleArticle = getArticleByUrlTitle(
6775                                    serviceContext.getScopeGroupId(), urlTitle);
6776                    }
6777                    catch (NoSuchArticleException nsae) {
6778                    }
6779    
6780                    if ((urlTitleArticle != null) &&
6781                            !Validator.equals(
6782                                    urlTitleArticle.getArticleId(), articleId)) {
6783    
6784                            urlTitle = getUniqueUrlTitle(
6785                                    id, serviceContext.getScopeGroupId(), articleId, urlTitle);
6786                    }
6787    
6788                    return urlTitle;
6789            }
6790    
6791            protected boolean hasModifiedLatestApprovedVersion(
6792                            long groupId, String articleId, double version)
6793                    throws PortalException {
6794    
6795                    double latestApprovedVersion;
6796    
6797                    try {
6798                            latestApprovedVersion = getLatestVersion(
6799                                    groupId, articleId, WorkflowConstants.STATUS_APPROVED);
6800    
6801                            if (version >= latestApprovedVersion) {
6802                                    return true;
6803                            }
6804                            else {
6805                                    return false;
6806                            }
6807                    }
6808                    catch (NoSuchArticleException nsae) {
6809                            return true;
6810                    }
6811            }
6812    
6813            protected boolean isPrivateDDMFormField(DDMFormField ddmFormField) {
6814                    String name = ddmFormField.getName();
6815    
6816                    if (name.startsWith(StringPool.UNDERLINE)) {
6817                            return true;
6818                    }
6819    
6820                    return false;
6821            }
6822    
6823            protected void notifySubscribers(
6824                            JournalArticle article, String articleURL,
6825                            ServiceContext serviceContext)
6826                    throws PortalException {
6827    
6828                    if (!article.isApproved() || Validator.isNull(articleURL)) {
6829                            return;
6830                    }
6831    
6832                    String articleTitle = article.getTitle(serviceContext.getLanguageId());
6833    
6834                    articleURL = buildArticleURL(
6835                            articleURL, article.getGroupId(), article.getFolderId(),
6836                            article.getArticleId());
6837    
6838                    PortletPreferences preferences =
6839                            ServiceContextUtil.getPortletPreferences(serviceContext);
6840    
6841                    if (preferences == null) {
6842                            long ownerId = article.getGroupId();
6843                            int ownerType = PortletKeys.PREFS_OWNER_TYPE_GROUP;
6844                            long plid = PortletKeys.PREFS_PLID_SHARED;
6845                            String portletId = PortletKeys.JOURNAL;
6846                            String defaultPreferences = null;
6847    
6848                            preferences = portletPreferencesLocalService.getPreferences(
6849                                    article.getCompanyId(), ownerId, ownerType, plid, portletId,
6850                                    defaultPreferences);
6851                    }
6852    
6853                    if ((article.getVersion() == 1.0) &&
6854                            JournalUtil.getEmailArticleAddedEnabled(preferences)) {
6855                    }
6856                    else if ((article.getVersion() != 1.0) &&
6857                                     JournalUtil.getEmailArticleUpdatedEnabled(preferences)) {
6858                    }
6859                    else {
6860                            return;
6861                    }
6862    
6863                    String fromName = JournalUtil.getEmailFromName(
6864                            preferences, article.getCompanyId());
6865                    String fromAddress = JournalUtil.getEmailFromAddress(
6866                            preferences, article.getCompanyId());
6867    
6868                    Map<Locale, String> localizedSubjectMap = null;
6869                    Map<Locale, String> localizedBodyMap = null;
6870    
6871                    if (article.getVersion() == 1.0) {
6872                            localizedSubjectMap = JournalUtil.getEmailArticleAddedSubjectMap(
6873                                    preferences);
6874                            localizedBodyMap = JournalUtil.getEmailArticleAddedBodyMap(
6875                                    preferences);
6876                    }
6877                    else {
6878                            localizedSubjectMap = JournalUtil.getEmailArticleUpdatedSubjectMap(
6879                                    preferences);
6880                            localizedBodyMap = JournalUtil.getEmailArticleUpdatedBodyMap(
6881                                    preferences);
6882                    }
6883    
6884                    String articleContent = StringPool.BLANK;
6885                    String articleDiffs = StringPool.BLANK;
6886    
6887                    JournalArticle previousApprovedArticle = getPreviousApprovedArticle(
6888                            article);
6889    
6890                    try {
6891                            PortletRequestModel portletRequestModel = new PortletRequestModel(
6892                                    serviceContext.getLiferayPortletRequest(),
6893                                    serviceContext.getLiferayPortletResponse());
6894    
6895                            JournalArticleDisplay articleDisplay = getArticleDisplay(
6896                                    article, null, Constants.VIEW,
6897                                    LocaleUtil.toLanguageId(LocaleUtil.getSiteDefault()), 1,
6898                                    portletRequestModel, serviceContext.getThemeDisplay());
6899    
6900                            articleContent = articleDisplay.getContent();
6901    
6902                            articleDiffs = JournalUtil.diffHtml(
6903                                    article.getGroupId(), article.getArticleId(),
6904                                    previousApprovedArticle.getVersion(), article.getVersion(),
6905                                    LocaleUtil.toLanguageId(LocaleUtil.getSiteDefault()),
6906                                    portletRequestModel, serviceContext.getThemeDisplay());
6907                    }
6908                    catch (Exception e) {
6909                    }
6910    
6911                    SubscriptionSender subscriptionSender =
6912                            new GroupSubscriptionCheckSubscriptionSender(
6913                                    JournalPermission.RESOURCE_NAME);
6914    
6915                    subscriptionSender.setClassName(article.getModelClassName());
6916                    subscriptionSender.setClassPK(article.getId());
6917                    subscriptionSender.setCompanyId(article.getCompanyId());
6918                    subscriptionSender.setContextAttribute(
6919                            "[$ARTICLE_CONTENT$]", articleContent, false);
6920                    subscriptionSender.setContextAttribute(
6921                            "[$ARTICLE_DIFFS$]", DiffHtmlUtil.replaceStyles(articleDiffs),
6922                            false);
6923                    subscriptionSender.setContextAttributes(
6924                            "[$ARTICLE_ID$]", article.getArticleId(), "[$ARTICLE_TITLE$]",
6925                            articleTitle, "[$ARTICLE_URL$]", articleURL, "[$ARTICLE_VERSION$]",
6926                            article.getVersion());
6927                    subscriptionSender.setContextUserPrefix("ARTICLE");
6928                    subscriptionSender.setEntryTitle(articleTitle);
6929                    subscriptionSender.setEntryURL(articleURL);
6930                    subscriptionSender.setFrom(fromAddress, fromName);
6931                    subscriptionSender.setHtmlFormat(true);
6932                    subscriptionSender.setLocalizedBodyMap(localizedBodyMap);
6933                    subscriptionSender.setLocalizedSubjectMap(localizedSubjectMap);
6934                    subscriptionSender.setMailId("journal_article", article.getId());
6935    
6936                    int notificationType =
6937                            UserNotificationDefinition.NOTIFICATION_TYPE_ADD_ENTRY;
6938    
6939                    if (serviceContext.isCommandUpdate()) {
6940                            notificationType =
6941                                    UserNotificationDefinition.NOTIFICATION_TYPE_UPDATE_ENTRY;
6942                    }
6943    
6944                    subscriptionSender.setNotificationType(notificationType);
6945    
6946                    subscriptionSender.setPortletId(PortletKeys.JOURNAL);
6947                    subscriptionSender.setReplyToAddress(fromAddress);
6948                    subscriptionSender.setScopeGroupId(article.getGroupId());
6949                    subscriptionSender.setServiceContext(serviceContext);
6950                    subscriptionSender.setUserId(article.getUserId());
6951    
6952                    JournalFolder folder = article.getFolder();
6953    
6954                    subscriptionSender.addPersistedSubscribers(
6955                            JournalFolder.class.getName(), article.getGroupId());
6956    
6957                    if (folder != null) {
6958                            subscriptionSender.addPersistedSubscribers(
6959                                    JournalFolder.class.getName(), folder.getFolderId());
6960    
6961                            for (Long ancestorFolderId : folder.getAncestorFolderIds()) {
6962                                    subscriptionSender.addPersistedSubscribers(
6963                                            JournalFolder.class.getName(), ancestorFolderId);
6964                            }
6965                    }
6966    
6967                    DDMStructure ddmStructure = ddmStructureLocalService.getStructure(
6968                            article.getGroupId(),
6969                            classNameLocalService.getClassNameId(JournalArticle.class),
6970                            article.getDDMStructureKey(), true);
6971    
6972                    subscriptionSender.addPersistedSubscribers(
6973                            DDMStructure.class.getName(), ddmStructure.getStructureId());
6974    
6975                    subscriptionSender.addPersistedSubscribers(
6976                            JournalArticle.class.getName(), article.getResourcePrimKey());
6977    
6978                    subscriptionSender.flushNotificationsAsync();
6979            }
6980    
6981            protected void saveImages(
6982                            boolean smallImage, long smallImageId, File smallImageFile,
6983                            byte[] smallImageBytes)
6984                    throws PortalException {
6985    
6986                    if (smallImage) {
6987                            if ((smallImageFile != null) && (smallImageBytes != null)) {
6988                                    imageLocalService.updateImage(smallImageId, smallImageBytes);
6989                            }
6990                    }
6991                    else {
6992                            imageLocalService.deleteImage(smallImageId);
6993                    }
6994            }
6995    
6996            protected BaseModelSearchResult<JournalArticle> searchJournalArticles(
6997                            SearchContext searchContext)
6998                    throws PortalException {
6999    
7000                    Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(
7001                            JournalArticle.class);
7002    
7003                    for (int i = 0; i < 10; i++) {
7004                            Hits hits = indexer.search(
7005                                    searchContext, JournalUtil.SELECTED_FIELD_NAMES);
7006    
7007                            List<JournalArticle> articles = JournalUtil.getArticles(hits);
7008    
7009                            if (articles != null) {
7010                                    return new BaseModelSearchResult<JournalArticle>(
7011                                            articles, hits.getLength());
7012                            }
7013                    }
7014    
7015                    throw new SearchException(
7016                            "Unable to fix the search index after 10 attempts");
7017            }
7018    
7019            protected void sendEmail(
7020                            JournalArticle article, String articleURL,
7021                            PortletPreferences preferences, String emailType,
7022                            ServiceContext serviceContext)
7023                    throws PortalException {
7024    
7025                    if (preferences == null) {
7026                            return;
7027                    }
7028                    else if (emailType.equals("denied") &&
7029                                     JournalUtil.getEmailArticleApprovalDeniedEnabled(
7030                                             preferences)) {
7031                    }
7032                    else if (emailType.equals("granted") &&
7033                                     JournalUtil.getEmailArticleApprovalGrantedEnabled(
7034                                             preferences)) {
7035                    }
7036                    else if (emailType.equals("requested") &&
7037                                     JournalUtil.getEmailArticleApprovalRequestedEnabled(
7038                                             preferences)) {
7039                    }
7040                    else if (emailType.equals("review") &&
7041                                     JournalUtil.getEmailArticleReviewEnabled(preferences)) {
7042                    }
7043                    else {
7044                            return;
7045                    }
7046    
7047                    Company company = companyPersistence.findByPrimaryKey(
7048                            article.getCompanyId());
7049    
7050                    User user = userPersistence.findByPrimaryKey(article.getUserId());
7051    
7052                    String fromName = JournalUtil.getEmailFromName(
7053                            preferences, article.getCompanyId());
7054                    String fromAddress = JournalUtil.getEmailFromAddress(
7055                            preferences, article.getCompanyId());
7056    
7057                    String toName = user.getFullName();
7058                    String toAddress = user.getEmailAddress();
7059    
7060                    if (emailType.equals("requested")) {
7061                            String tempToName = fromName;
7062                            String tempToAddress = fromAddress;
7063    
7064                            fromName = toName;
7065                            fromAddress = toAddress;
7066    
7067                            toName = tempToName;
7068                            toAddress = tempToAddress;
7069                    }
7070    
7071                    Map<Locale, String> localizedSubjectMap = null;
7072                    Map<Locale, String> localizedBodyMap = null;
7073    
7074                    if (emailType.equals("denied")) {
7075                            localizedSubjectMap =
7076                                    JournalUtil.getEmailArticleApprovalDeniedSubjectMap(
7077                                            preferences);
7078                            localizedBodyMap = JournalUtil.getEmailArticleApprovalDeniedBodyMap(
7079                                    preferences);
7080                    }
7081                    else if (emailType.equals("granted")) {
7082                            localizedSubjectMap =
7083                                    JournalUtil.getEmailArticleApprovalGrantedSubjectMap(
7084                                            preferences);
7085                            localizedBodyMap =
7086                                    JournalUtil.getEmailArticleApprovalGrantedBodyMap(preferences);
7087                    }
7088                    else if (emailType.equals("requested")) {
7089                            localizedSubjectMap =
7090                                    JournalUtil.getEmailArticleApprovalRequestedSubjectMap(
7091                                            preferences);
7092                            localizedBodyMap =
7093                                    JournalUtil.getEmailArticleApprovalRequestedBodyMap(
7094                                            preferences);
7095                    }
7096                    else if (emailType.equals("review")) {
7097                            localizedSubjectMap = JournalUtil.getEmailArticleReviewSubjectMap(
7098                                    preferences);
7099                            localizedBodyMap = JournalUtil.getEmailArticleReviewBodyMap(
7100                                    preferences);
7101                    }
7102    
7103                    SubscriptionSender subscriptionSender = new SubscriptionSender();
7104    
7105                    subscriptionSender.setCompanyId(company.getCompanyId());
7106                    subscriptionSender.setContextAttributes(
7107                            "[$ARTICLE_ID$]", article.getArticleId(), "[$ARTICLE_TITLE$]",
7108                            article.getTitle(serviceContext.getLanguageId()), "[$ARTICLE_URL$]",
7109                            articleURL, "[$ARTICLE_USER_NAME$]", article.getUserName(),
7110                            "[$ARTICLE_VERSION$]", article.getVersion());
7111                    subscriptionSender.setContextUserPrefix("ARTICLE");
7112                    subscriptionSender.setFrom(fromAddress, fromName);
7113                    subscriptionSender.setHtmlFormat(true);
7114                    subscriptionSender.setLocalizedBodyMap(localizedBodyMap);
7115                    subscriptionSender.setLocalizedSubjectMap(localizedSubjectMap);
7116                    subscriptionSender.setMailId("journal_article", article.getId());
7117                    subscriptionSender.setPortletId(PortletKeys.JOURNAL);
7118                    subscriptionSender.setScopeGroupId(article.getGroupId());
7119                    subscriptionSender.setServiceContext(serviceContext);
7120                    subscriptionSender.setUserId(article.getUserId());
7121    
7122                    subscriptionSender.addRuntimeSubscribers(toAddress, toName);
7123    
7124                    subscriptionSender.flushNotificationsAsync();
7125            }
7126    
7127            protected void startWorkflowInstance(
7128                            long userId, JournalArticle article, ServiceContext serviceContext)
7129                    throws PortalException {
7130    
7131                    Map<String, Serializable> workflowContext =
7132                            new HashMap<String, Serializable>();
7133    
7134                    workflowContext.put(
7135                            WorkflowConstants.CONTEXT_URL,
7136                            PortalUtil.getControlPanelFullURL(
7137                                    serviceContext.getScopeGroupId(), PortletKeys.JOURNAL, null));
7138    
7139                    WorkflowHandlerRegistryUtil.startWorkflowInstance(
7140                            article.getCompanyId(), article.getGroupId(), userId,
7141                            JournalArticle.class.getName(), article.getId(), article,
7142                            serviceContext, workflowContext);
7143            }
7144    
7145            protected void updateDDMFormFieldPredefinedValue(
7146                    DDMFormField ddmFormField, String ddmFormFieldValue) {
7147    
7148                    LocalizedValue predefinedValue = ddmFormField.getPredefinedValue();
7149    
7150                    for (Locale locale : predefinedValue.getAvailableLocales()) {
7151                            predefinedValue.addString(locale, ddmFormFieldValue);
7152                    }
7153            }
7154    
7155            protected void updateDDMStructurePredefinedValues(
7156                    long ddmStructureId, String content, ServiceContext serviceContext) {
7157    
7158                    DDMStructure ddmStructure = ddmStructureLocalService.fetchDDMStructure(
7159                            ddmStructureId);
7160    
7161                    if (ddmStructure == null) {
7162                            return;
7163                    }
7164    
7165                    DDMForm ddmForm = ddmStructure.getDDMForm();
7166    
7167                    Map<String, DDMFormField> ddmFormFieldsMap =
7168                            ddmStructure.getFullHierarchyDDMFormFieldsMap(true);
7169    
7170                    Map<String, String> fieldsValuesMap = createFieldsValuesMap(content);
7171    
7172                    for (Map.Entry<String, String> fieldValue :
7173                                    fieldsValuesMap.entrySet()) {
7174    
7175                            String ddmFormFieldName = fieldValue.getKey();
7176                            String ddmFormFieldValue = fieldValue.getValue();
7177    
7178                            updateDDMFormFieldPredefinedValue(
7179                                    ddmFormFieldsMap.get(ddmFormFieldName), ddmFormFieldValue);
7180                    }
7181    
7182                    ddmStructure.updateDDMForm(ddmForm);
7183    
7184                    ddmStructureLocalService.updateDDMStructure(ddmStructure);
7185            }
7186    
7187            protected void updatePreviousApprovedArticle(JournalArticle article)
7188                    throws PortalException {
7189    
7190                    JournalArticle previousApprovedArticle = getPreviousApprovedArticle(
7191                            article);
7192    
7193                    if (previousApprovedArticle.getVersion() == article.getVersion()) {
7194                            assetEntryLocalService.updateVisible(
7195                                    JournalArticle.class.getName(), article.getResourcePrimKey(),
7196                                    false);
7197                    }
7198                    else {
7199                            AssetEntry assetEntry = assetEntryLocalService.updateEntry(
7200                                    JournalArticle.class.getName(), article.getResourcePrimKey(),
7201                                    previousApprovedArticle.getDisplayDate(),
7202                                    previousApprovedArticle.getExpirationDate(), true);
7203    
7204                            assetEntry.setModifiedDate(
7205                                    previousApprovedArticle.getModifiedDate());
7206                            assetEntry.setTitle(previousApprovedArticle.getTitle());
7207    
7208                            assetEntryPersistence.update(assetEntry);
7209                    }
7210            }
7211    
7212            protected void updateUrlTitles(
7213                            long groupId, String articleId, String urlTitle)
7214                    throws PortalException {
7215    
7216                    JournalArticle firstArticle = journalArticlePersistence.findByG_A_First(
7217                            groupId, articleId, new ArticleVersionComparator(false));
7218    
7219                    if (firstArticle.getUrlTitle().equals(urlTitle)) {
7220                            return;
7221                    }
7222    
7223                    List<JournalArticle> articles = journalArticlePersistence.findByG_A(
7224                            groupId, articleId);
7225    
7226                    for (JournalArticle article : articles) {
7227                            if (!article.getUrlTitle().equals(urlTitle)) {
7228                                    article.setUrlTitle(urlTitle);
7229    
7230                                    journalArticlePersistence.update(article);
7231                            }
7232                    }
7233            }
7234    
7235            protected void validate(
7236                            long companyId, long groupId, long classNameId,
7237                            Map<Locale, String> titleMap, String content,
7238                            String ddmStructureKey, String ddmTemplateKey, Date expirationDate,
7239                            boolean smallImage, String smallImageURL, File smallImageFile,
7240                            byte[] smallImageBytes, ServiceContext serviceContext)
7241                    throws PortalException {
7242    
7243                    Locale articleDefaultLocale = LocaleUtil.fromLanguageId(
7244                            LocalizationUtil.getDefaultLanguageId(content));
7245    
7246                    Locale[] availableLocales = LanguageUtil.getAvailableLocales(groupId);
7247    
7248                    if (!ArrayUtil.contains(availableLocales, articleDefaultLocale)) {
7249                            LocaleException le = new LocaleException(
7250                                    LocaleException.TYPE_CONTENT,
7251                                    "The locale " + articleDefaultLocale +
7252                                            " is not available in site with groupId" + groupId);
7253    
7254                            Locale[] sourceAvailableLocales = {articleDefaultLocale};
7255    
7256                            le.setSourceAvailableLocales(sourceAvailableLocales);
7257                            le.setTargetAvailableLocales(availableLocales);
7258    
7259                            throw le;
7260                    }
7261    
7262                    if ((classNameId == JournalArticleConstants.CLASSNAME_ID_DEFAULT) &&
7263                            (titleMap.isEmpty() ||
7264                             Validator.isNull(titleMap.get(articleDefaultLocale)))) {
7265    
7266                            throw new ArticleTitleException();
7267                    }
7268    
7269                    validateContent(content);
7270    
7271                    DDMStructure ddmStructure = ddmStructureLocalService.getStructure(
7272                            PortalUtil.getSiteGroupId(groupId),
7273                            classNameLocalService.getClassNameId(JournalArticle.class),
7274                            ddmStructureKey, true);
7275    
7276                    validateDDMStructureFields(ddmStructure, classNameId, serviceContext);
7277    
7278                    if (Validator.isNotNull(ddmTemplateKey)) {
7279                            DDMTemplate ddmTemplate = ddmTemplateLocalService.getTemplate(
7280                                    PortalUtil.getSiteGroupId(groupId),
7281                                    classNameLocalService.getClassNameId(DDMStructure.class),
7282                                    ddmTemplateKey, true);
7283    
7284                            if (ddmTemplate.getClassPK() != ddmStructure.getStructureId()) {
7285                                    throw new NoSuchTemplateException(
7286                                            "{templateKey=" + ddmTemplateKey + "}");
7287                            }
7288                    }
7289                    else if (classNameId == JournalArticleConstants.CLASSNAME_ID_DEFAULT) {
7290                            throw new NoSuchTemplateException();
7291                    }
7292    
7293                    if ((expirationDate != null) && expirationDate.before(new Date()) &&
7294                            !ExportImportThreadLocal.isImportInProcess()) {
7295    
7296                            throw new ArticleExpirationDateException();
7297                    }
7298    
7299                    String[] imageExtensions = PrefsPropsUtil.getStringArray(
7300                            PropsKeys.JOURNAL_IMAGE_EXTENSIONS, StringPool.COMMA);
7301    
7302                    if (!smallImage || Validator.isNotNull(smallImageURL) ||
7303                            (smallImageFile == null) || (smallImageBytes == null)) {
7304    
7305                            return;
7306                    }
7307    
7308                    String smallImageName = smallImageFile.getName();
7309    
7310                    if (smallImageName != null) {
7311                            boolean validSmallImageExtension = false;
7312    
7313                            for (String _imageExtension : imageExtensions) {
7314                                    if (StringPool.STAR.equals(_imageExtension) ||
7315                                            StringUtil.endsWith(smallImageName, _imageExtension)) {
7316    
7317                                            validSmallImageExtension = true;
7318    
7319                                            break;
7320                                    }
7321                            }
7322    
7323                            if (!validSmallImageExtension) {
7324                                    throw new ArticleSmallImageNameException(smallImageName);
7325                            }
7326                    }
7327    
7328                    long smallImageMaxSize = PrefsPropsUtil.getLong(
7329                            PropsKeys.JOURNAL_IMAGE_SMALL_MAX_SIZE);
7330    
7331                    if ((smallImageMaxSize > 0) &&
7332                            ((smallImageBytes == null) ||
7333                             (smallImageBytes.length > smallImageMaxSize))) {
7334    
7335                            throw new ArticleSmallImageSizeException();
7336                    }
7337            }
7338    
7339            protected void validate(
7340                            long companyId, long groupId, long classNameId, String articleId,
7341                            boolean autoArticleId, double version, Map<Locale, String> titleMap,
7342                            String content, String ddmStructureKey, String ddmTemplateKey,
7343                            Date expirationDate, boolean smallImage, String smallImageURL,
7344                            File smallImageFile, byte[] smallImageBytes,
7345                            ServiceContext serviceContext)
7346                    throws PortalException {
7347    
7348                    if (!autoArticleId) {
7349                            validate(articleId);
7350                    }
7351    
7352                    JournalArticle article = journalArticlePersistence.fetchByG_A_V(
7353                            groupId, articleId, version);
7354    
7355                    if (article != null) {
7356                            StringBundler sb = new StringBundler(7);
7357    
7358                            sb.append("{groupId=");
7359                            sb.append(groupId);
7360                            sb.append(", articleId=");
7361                            sb.append(articleId);
7362                            sb.append(", version=");
7363                            sb.append(version);
7364                            sb.append("}");
7365    
7366                            throw new DuplicateArticleIdException(sb.toString());
7367                    }
7368    
7369                    validate(
7370                            companyId, groupId, classNameId, titleMap, content, ddmStructureKey,
7371                            ddmTemplateKey, expirationDate, smallImage, smallImageURL,
7372                            smallImageFile, smallImageBytes, serviceContext);
7373            }
7374    
7375            protected void validate(String articleId) throws PortalException {
7376                    if (Validator.isNull(articleId) ||
7377                            (articleId.indexOf(CharPool.COMMA) != -1) ||
7378                            (articleId.indexOf(CharPool.SPACE) != -1)) {
7379    
7380                            throw new ArticleIdException();
7381                    }
7382            }
7383    
7384            protected void validateContent(String content) throws PortalException {
7385                    if (Validator.isNull(content)) {
7386                            throw new ArticleContentException("Content is null");
7387                    }
7388    
7389                    try {
7390                            SAXReaderUtil.read(content);
7391                    }
7392                    catch (DocumentException de) {
7393                            if (_log.isDebugEnabled()) {
7394                                    _log.debug("Invalid content:\n" + content);
7395                            }
7396    
7397                            throw new ArticleContentException(
7398                                    "Unable to read content with an XML parser", de);
7399                    }
7400            }
7401    
7402            protected void validateDDMStructureFields(
7403                            DDMStructure ddmStructure, long classNameId,
7404                            ServiceContext serviceContext)
7405                    throws PortalException {
7406    
7407                    Fields fields = DDMUtil.getFields(
7408                            ddmStructure.getStructureId(), serviceContext);
7409    
7410                    for (com.liferay.portlet.dynamicdatamapping.storage.Field field :
7411                                    fields) {
7412    
7413                            if (!ddmStructure.hasField(field.getName())) {
7414                                    throw new StorageFieldNameException();
7415                            }
7416    
7417                            if (ddmStructure.getFieldRequired(field.getName()) &&
7418                                    Validator.isNull(field.getValue()) &&
7419                                    (classNameId == JournalArticleConstants.CLASSNAME_ID_DEFAULT)) {
7420    
7421                                    throw new StorageFieldRequiredException();
7422                            }
7423                    }
7424            }
7425    
7426            protected void validateDDMStructureId(
7427                            long groupId, long folderId, String ddmStructureKey)
7428                    throws PortalException {
7429    
7430                    int restrictionType = JournalUtil.getRestrictionType(folderId);
7431    
7432                    DDMStructure ddmStructure = ddmStructureLocalService.getStructure(
7433                            PortalUtil.getSiteGroupId(groupId),
7434                            classNameLocalService.getClassNameId(JournalArticle.class),
7435                            ddmStructureKey, true);
7436    
7437                    List<DDMStructure> folderDDMStructures =
7438                            ddmStructureLocalService.getJournalFolderStructures(
7439                                    PortalUtil.getCurrentAndAncestorSiteGroupIds(groupId), folderId,
7440                                    restrictionType);
7441    
7442                    for (DDMStructure folderDDMStructure : folderDDMStructures) {
7443                            if (folderDDMStructure.getStructureId() ==
7444                                            ddmStructure.getStructureId()) {
7445    
7446                                    return;
7447                            }
7448                    }
7449    
7450                    throw new InvalidDDMStructureException(
7451                            "Invalid structure " + ddmStructure.getStructureId() +
7452                                    " for folder " + folderId);
7453            }
7454    
7455            private static final long _JOURNAL_ARTICLE_CHECK_INTERVAL =
7456                    PropsValues.JOURNAL_ARTICLE_CHECK_INTERVAL * Time.MINUTE;
7457    
7458            private static Log _log = LogFactoryUtil.getLog(
7459                    JournalArticleLocalServiceImpl.class);
7460    
7461            private Date _previousCheckDate;
7462    
7463    }