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