1. Project Clover database Thu May 10 2018 23:28:11 CEST
  2. Package com.xpn.xwiki.doc

File XWikiDocument.java

 

Coverage histogram

../../../../img/srcFileCovDistChart7.png
64% of files have more coverage

Code metrics

1,020
2,568
510
2
8,929
5,473
1,162
0.45
5.04
255
2.28

Classes

Class Line # Actions
XWikiDocument 195 2,559 0% 1,155 1,215
0.7022788570.2%
XWikiDocument.XWikiAttachmentToRemove 205 9 0% 7 10
0.411764741.2%
 

Contributing tests

No tests hitting this source file were found.

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.doc;
21   
22    import java.io.ByteArrayInputStream;
23    import java.io.IOException;
24    import java.io.InputStream;
25    import java.io.OutputStream;
26    import java.io.StringWriter;
27    import java.lang.ref.SoftReference;
28    import java.lang.reflect.Constructor;
29    import java.lang.reflect.Method;
30    import java.lang.reflect.Type;
31    import java.net.URL;
32    import java.nio.charset.StandardCharsets;
33    import java.security.MessageDigest;
34    import java.security.NoSuchAlgorithmException;
35    import java.util.ArrayList;
36    import java.util.Collection;
37    import java.util.Collections;
38    import java.util.Date;
39    import java.util.HashMap;
40    import java.util.HashSet;
41    import java.util.Iterator;
42    import java.util.LinkedHashSet;
43    import java.util.List;
44    import java.util.Locale;
45    import java.util.Map;
46    import java.util.Map.Entry;
47    import java.util.Objects;
48    import java.util.Set;
49    import java.util.SortedMap;
50    import java.util.TreeMap;
51    import java.util.Vector;
52    import java.util.regex.Matcher;
53    import java.util.regex.Pattern;
54    import java.util.zip.ZipEntry;
55    import java.util.zip.ZipOutputStream;
56   
57    import javax.inject.Provider;
58    import javax.servlet.http.HttpServletResponse;
59   
60    import org.apache.commons.lang3.BooleanUtils;
61    import org.apache.commons.lang3.ObjectUtils;
62    import org.apache.commons.lang3.StringUtils;
63    import org.apache.commons.lang3.exception.ExceptionUtils;
64    import org.apache.velocity.VelocityContext;
65    import org.dom4j.Document;
66    import org.dom4j.Element;
67    import org.dom4j.dom.DOMDocument;
68    import org.dom4j.io.DocumentResult;
69    import org.dom4j.io.OutputFormat;
70    import org.slf4j.Logger;
71    import org.slf4j.LoggerFactory;
72    import org.suigeneris.jrcs.diff.Diff;
73    import org.suigeneris.jrcs.diff.DifferentiationFailedException;
74    import org.suigeneris.jrcs.diff.Revision;
75    import org.suigeneris.jrcs.diff.delta.Delta;
76    import org.suigeneris.jrcs.rcs.Version;
77    import org.suigeneris.jrcs.util.ToString;
78    import org.xwiki.bridge.DocumentModelBridge;
79    import org.xwiki.component.manager.ComponentLookupException;
80    import org.xwiki.component.util.DefaultParameterizedType;
81    import org.xwiki.context.Execution;
82    import org.xwiki.context.ExecutionContextException;
83    import org.xwiki.context.ExecutionContextManager;
84    import org.xwiki.display.internal.DocumentDisplayer;
85    import org.xwiki.display.internal.DocumentDisplayerParameters;
86    import org.xwiki.filter.input.DefaultInputStreamInputSource;
87    import org.xwiki.filter.input.InputSource;
88    import org.xwiki.filter.input.StringInputSource;
89    import org.xwiki.filter.instance.input.DocumentInstanceInputProperties;
90    import org.xwiki.filter.instance.output.DocumentInstanceOutputProperties;
91    import org.xwiki.filter.output.DefaultOutputStreamOutputTarget;
92    import org.xwiki.filter.output.DefaultWriterOutputTarget;
93    import org.xwiki.filter.output.OutputTarget;
94    import org.xwiki.filter.xar.input.XARInputProperties;
95    import org.xwiki.filter.xar.output.XAROutputProperties;
96    import org.xwiki.filter.xml.output.DefaultResultOutputTarget;
97    import org.xwiki.job.event.status.JobProgressManager;
98    import org.xwiki.localization.ContextualLocalizationManager;
99    import org.xwiki.localization.LocaleUtils;
100    import org.xwiki.model.EntityType;
101    import org.xwiki.model.internal.reference.DefaultSymbolScheme;
102    import org.xwiki.model.internal.reference.LocalStringEntityReferenceSerializer;
103    import org.xwiki.model.internal.reference.LocalUidStringEntityReferenceSerializer;
104    import org.xwiki.model.reference.AttachmentReference;
105    import org.xwiki.model.reference.DocumentReference;
106    import org.xwiki.model.reference.DocumentReferenceResolver;
107    import org.xwiki.model.reference.EntityReference;
108    import org.xwiki.model.reference.EntityReferenceProvider;
109    import org.xwiki.model.reference.EntityReferenceResolver;
110    import org.xwiki.model.reference.EntityReferenceSerializer;
111    import org.xwiki.model.reference.LocalDocumentReference;
112    import org.xwiki.model.reference.ObjectPropertyReference;
113    import org.xwiki.model.reference.ObjectReference;
114    import org.xwiki.model.reference.ObjectReferenceResolver;
115    import org.xwiki.model.reference.SpaceReference;
116    import org.xwiki.model.reference.WikiReference;
117    import org.xwiki.query.Query;
118    import org.xwiki.query.QueryException;
119    import org.xwiki.query.QueryFilter;
120    import org.xwiki.rendering.block.Block;
121    import org.xwiki.rendering.block.Block.Axes;
122    import org.xwiki.rendering.block.HeaderBlock;
123    import org.xwiki.rendering.block.LinkBlock;
124    import org.xwiki.rendering.block.MacroBlock;
125    import org.xwiki.rendering.block.SectionBlock;
126    import org.xwiki.rendering.block.XDOM;
127    import org.xwiki.rendering.block.match.ClassBlockMatcher;
128    import org.xwiki.rendering.block.match.MacroBlockMatcher;
129    import org.xwiki.rendering.listener.reference.ResourceReference;
130    import org.xwiki.rendering.listener.reference.ResourceType;
131    import org.xwiki.rendering.parser.ContentParser;
132    import org.xwiki.rendering.parser.MissingParserException;
133    import org.xwiki.rendering.parser.ParseException;
134    import org.xwiki.rendering.parser.Parser;
135    import org.xwiki.rendering.renderer.BlockRenderer;
136    import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter;
137    import org.xwiki.rendering.renderer.printer.WikiPrinter;
138    import org.xwiki.rendering.syntax.Syntax;
139    import org.xwiki.rendering.transformation.RenderingContext;
140    import org.xwiki.rendering.transformation.TransformationContext;
141    import org.xwiki.rendering.transformation.TransformationException;
142    import org.xwiki.rendering.transformation.TransformationManager;
143    import org.xwiki.rendering.util.ErrorBlockGenerator;
144    import org.xwiki.security.authorization.ContextualAuthorizationManager;
145    import org.xwiki.security.authorization.Right;
146    import org.xwiki.velocity.VelocityManager;
147    import org.xwiki.xar.internal.model.XarDocumentModel;
148    import org.xwiki.xml.XMLUtils;
149   
150    import com.xpn.xwiki.CoreConfiguration;
151    import com.xpn.xwiki.XWiki;
152    import com.xpn.xwiki.XWikiConstant;
153    import com.xpn.xwiki.XWikiContext;
154    import com.xpn.xwiki.XWikiException;
155    import com.xpn.xwiki.api.DocumentSection;
156    import com.xpn.xwiki.criteria.impl.RevisionCriteria;
157    import com.xpn.xwiki.doc.merge.MergeConfiguration;
158    import com.xpn.xwiki.doc.merge.MergeResult;
159    import com.xpn.xwiki.doc.rcs.XWikiRCSNodeInfo;
160    import com.xpn.xwiki.internal.cache.rendering.RenderingCache;
161    import com.xpn.xwiki.internal.doc.XWikiAttachmentList;
162    import com.xpn.xwiki.internal.filter.XWikiDocumentFilterUtils;
163    import com.xpn.xwiki.internal.merge.MergeUtils;
164    import com.xpn.xwiki.internal.render.LinkedResourceHelper;
165    import com.xpn.xwiki.internal.render.OldRendering;
166    import com.xpn.xwiki.internal.xml.DOMXMLWriter;
167    import com.xpn.xwiki.internal.xml.XMLWriter;
168    import com.xpn.xwiki.objects.BaseCollection;
169    import com.xpn.xwiki.objects.BaseObject;
170    import com.xpn.xwiki.objects.BaseObjectReference;
171    import com.xpn.xwiki.objects.BaseProperty;
172    import com.xpn.xwiki.objects.LargeStringProperty;
173    import com.xpn.xwiki.objects.ListProperty;
174    import com.xpn.xwiki.objects.ObjectDiff;
175    import com.xpn.xwiki.objects.PropertyInterface;
176    import com.xpn.xwiki.objects.classes.BaseClass;
177    import com.xpn.xwiki.objects.classes.ListClass;
178    import com.xpn.xwiki.objects.classes.PropertyClass;
179    import com.xpn.xwiki.objects.classes.StaticListClass;
180    import com.xpn.xwiki.objects.classes.TextAreaClass;
181    import com.xpn.xwiki.store.XWikiAttachmentStoreInterface;
182    import com.xpn.xwiki.store.XWikiHibernateAttachmentStore;
183    import com.xpn.xwiki.store.XWikiStoreInterface;
184    import com.xpn.xwiki.store.XWikiVersioningStoreInterface;
185    import com.xpn.xwiki.user.api.XWikiRightService;
186    import com.xpn.xwiki.util.Util;
187    import com.xpn.xwiki.validation.XWikiValidationInterface;
188    import com.xpn.xwiki.validation.XWikiValidationStatus;
189    import com.xpn.xwiki.web.EditForm;
190    import com.xpn.xwiki.web.ObjectAddForm;
191    import com.xpn.xwiki.web.ObjectPolicyType;
192    import com.xpn.xwiki.web.Utils;
193    import com.xpn.xwiki.web.XWikiRequest;
194   
 
195    public class XWikiDocument implements DocumentModelBridge, Cloneable
196    {
197    private static final Logger LOGGER = LoggerFactory.getLogger(XWikiDocument.class);
198   
199    /**
200    * An attachment waiting to be deleted at next document save.
201    *
202    * @version $Id: 8035275e4984c1aed5352eef30df5e94e7121625 $
203    * @since 5.2M1
204    */
 
205    public static class XWikiAttachmentToRemove
206    {
207    /**
208    * @see #getAttachment()
209    */
210    private XWikiAttachment attachment;
211   
212    /**
213    * @see #isToRecycleBin()
214    */
215    private boolean toRecycleBin;
216   
217    /**
218    * @param attachment the attachment to delete
219    * @param toRecycleBin true of the attachment should be moved to the recycle bin
220    */
 
221  10 toggle public XWikiAttachmentToRemove(XWikiAttachment attachment, boolean toRecycleBin)
222    {
223  10 this.attachment = attachment;
224  10 this.toRecycleBin = toRecycleBin;
225    }
226   
227    /**
228    * @return the attachment to delete
229    */
 
230  14 toggle public XWikiAttachment getAttachment()
231    {
232  14 return this.attachment;
233    }
234   
235    /**
236    * @return true of the attachment should be moved to the recycle bin
237    */
 
238  7 toggle public boolean isToRecycleBin()
239    {
240  7 return this.toRecycleBin;
241    }
242   
 
243  0 toggle @Override
244    public int hashCode()
245    {
246  0 return this.attachment.hashCode();
247    }
248   
 
249  0 toggle @Override
250    public boolean equals(Object obj)
251    {
252  0 if (obj instanceof XWikiAttachmentToRemove) {
253  0 return this.attachment.equals(((XWikiAttachmentToRemove) obj).getAttachment());
254    }
255   
256  0 return false;
257    }
258   
 
259  0 toggle @Override
260    public String toString()
261    {
262  0 return this.attachment.toString();
263    }
264    }
265   
266    /**
267    * Regex Pattern to recognize if there's HTML code in a XWiki page.
268    */
269    private static final Pattern HTML_TAG_PATTERN = Pattern.compile(
270    "</?+(html|img|a|i|br?|embed|script|form|input|textarea|object|font|li|[dou]l|table|center|hr|p) ?([^>]*+)>");
271   
272    /**
273    * Format for passing xproperties references in URLs. General format:
274    * {@code &lt;space&gt;.&lt;pageClass&gt;_&lt;number&gt;_&lt;propertyName&gt;} (e.g.
275    * {@code XWiki.XWikiRights_0_member}).
276    */
277    private static final Pattern XPROPERTY_REFERENCE_PATTERN = Pattern.compile("(.+?)_([0-9]+)_(.+)");
278   
279    public static final EntityReference COMMENTSCLASS_REFERENCE = new LocalDocumentReference("XWiki", "XWikiComments");
280   
281    public static final EntityReference SHEETCLASS_REFERENCE = new LocalDocumentReference("XWiki", "SheetClass");
282   
283    public static final int HAS_ATTACHMENTS = 1;
284   
285    public static final int HAS_OBJECTS = 2;
286   
287    public static final int HAS_CLASS = 4;
288   
289    /**
290    * The name of the key in the XWikiContext which contains the document used to check for programming rights.
291    */
292    public static final String CKEY_SDOC = "sdoc";
293   
294    /**
295    * Separator string between database name and space name.
296    */
297    public static final String DB_SPACE_SEP = ":";
298   
299    /**
300    * Separator string between space name and page name.
301    */
302    public static final String SPACE_NAME_SEP = ".";
303   
304    private static final LocalStringEntityReferenceSerializer LOCAL_REFERENCE_SERIALIZER =
305    new LocalStringEntityReferenceSerializer(new DefaultSymbolScheme());
306   
307    /**
308    * Used to resolve a string into a proper Document Reference using the current document's reference to fill the
309    * blanks.
310    */
 
311  216 toggle private static DocumentReferenceResolver<String> getCurrentDocumentReferenceResolver()
312    {
313  216 return Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "current");
314    }
315   
316    /**
317    * Used to resolve a ResourceReference into a proper Entity Reference using the current document to fill the blanks.
318    */
 
319  328 toggle private static EntityReferenceResolver<ResourceReference> getResourceReferenceEntityReferenceResolver()
320    {
321  328 return Utils
322    .getComponent(new DefaultParameterizedType(null, EntityReferenceResolver.class, ResourceReference.class));
323    }
324   
 
325  157364 toggle private static EntityReferenceResolver<String> getXClassEntityReferenceResolver()
326    {
327  157357 return Utils.getComponent(EntityReferenceResolver.TYPE_STRING, "xclass");
328    }
329   
330    /**
331    * Used to resolve a string into a proper Document Reference using the current document's reference to fill the
332    * blanks, except for the page name for which the default page name is used instead and for the wiki name for which
333    * the current wiki is used instead of the current document reference's wiki.
334    */
 
335  81808 toggle private static DocumentReferenceResolver<String> getCurrentMixedDocumentReferenceResolver()
336    {
337  81807 return Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "currentmixed");
338    }
339   
340    /**
341    * Used to normalize references.
342    */
 
343  3527590 toggle private static DocumentReferenceResolver<EntityReference> getCurrentReferenceDocumentReferenceResolver()
344    {
345  3527579 return Utils.getComponent(DocumentReferenceResolver.TYPE_REFERENCE, "current");
346    }
347   
348    /**
349    * Used to resolve parent references in the way they are stored externally (database, xml, etc), ie relative or
350    * absolute.
351    */
 
352  56652 toggle private static EntityReferenceResolver<String> getRelativeEntityReferenceResolver()
353    {
354  56655 return Utils.getComponent(EntityReferenceResolver.TYPE_STRING, "relative");
355    }
356   
357    /**
358    * Used to convert a proper Document Reference to string (compact form).
359    */
 
360  9 toggle private static EntityReferenceSerializer<String> getCompactEntityReferenceSerializer()
361    {
362  9 return Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, "compact");
363    }
364   
365    /**
366    * Used to convert a Document Reference to string (compact form without the wiki part if it matches the current
367    * wiki).
368    */
 
369  115149 toggle private static EntityReferenceSerializer<String> getCompactWikiEntityReferenceSerializer()
370    {
371  115149 return Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, "compactwiki");
372    }
373   
374    /**
375    * Used to normalize references.
376    */
 
377  583459 toggle private static ObjectReferenceResolver<EntityReference> getCurrentReferenceObjectReferenceResolver()
378    {
379  583459 return Utils.getComponent(ObjectReferenceResolver.TYPE_REFERENCE, "current");
380    }
381   
382    private String title;
383   
384    /**
385    * Reference to this document's parent.
386    * <p>
387    * Note that we're saving the parent reference as a relative reference instead of an absolute one because We want
388    * the ability (for example) to create a parent reference relative to the current space or wiki so that a copy of
389    * this XWikiDocument object would retain that relativity. This is for example useful when copying a Wiki into
390    * another Wiki so that the copied XWikiDcoument's parent reference points to the new wiki.
391    */
392    private EntityReference parentReference;
393   
394    private DocumentReference documentReference;
395   
396    private String content;
397   
398    private String meta;
399   
400    private String format;
401   
402    /**
403    * First author of the document.
404    */
405    private DocumentReference creatorReference;
406   
407    /**
408    * The Author is changed when any part of the document changes (content, objects, attachments).
409    */
410    private DocumentReference authorReference;
411   
412    /**
413    * The last user that has changed the document's content (ie not object, attachments). The Content author is only
414    * changed when the document content changes. Note that Content Author is used to check programming rights on a
415    * document and this is the reason we need to know the last author who's modified the content since programming
416    * rights depend on this.
417    */
418    private DocumentReference contentAuthorReference;
419   
420    private String customClass;
421   
422    private Date contentUpdateDate;
423   
424    private Date updateDate;
425   
426    private Date creationDate;
427   
428    protected Version version;
429   
430    private long id = 0;
431   
432    private boolean mostRecent = true;
433   
434    private boolean isNew = true;
435   
436    /**
437    * The reference to the document that is the template for the current document.
438    *
439    * @todo this field is not used yet since it's not currently saved in the database.
440    */
441    private DocumentReference templateDocumentReference;
442   
443    private Locale locale;
444   
445    private Locale defaultLocale;
446   
447    /**
448    * Indicates whether the document is 'hidden', meaning that it should not be returned in public search results.
449    * WARNING: this is a temporary hack until the new data model is designed and implemented. No code should rely on or
450    * use this property, since it will be replaced with a generic metadata.
451    */
452    private boolean hidden = false;
453   
454    /**
455    * Comment on the latest modification.
456    */
457    private String comment;
458   
459    /**
460    * Wiki syntax supported by this document. This is used to support different syntaxes inside the same wiki. For
461    * example a page can use the MediaWiki 1.0 syntax while another one uses the XWiki 2.1 syntax.
462    */
463    private Syntax syntax;
464   
465    /**
466    * Is latest modification a minor edit.
467    */
468    private boolean isMinorEdit = false;
469   
470    /**
471    * Used to make sure the MetaData String is regenerated.
472    */
473    private boolean isContentDirty = true;
474   
475    /**
476    * Used to make sure the MetaData String is regenerated.
477    */
478    private boolean isMetaDataDirty = true;
479   
480    private int elements = HAS_OBJECTS | HAS_ATTACHMENTS;
481   
482    // Meta Data
483    private BaseClass xClass;
484   
485    private String xClassXML;
486   
487    /**
488    * Map holding document objects indexed by XClass references (i.e. Document References since a XClass reference
489    * points to a document). The map is not synchronized, and uses a TreeMap implementation to preserve index ordering
490    * (consistent sorted order for output to XML, rendering in velocity, etc.)
491    */
492    private Map<DocumentReference, List<BaseObject>> xObjects = new TreeMap<DocumentReference, List<BaseObject>>();
493   
494    private final XWikiAttachmentList attachmentList = new XWikiAttachmentList(XWikiDocument.this);
495   
496    // Caching
497    private boolean fromCache = false;
498   
499    private List<BaseObject> xObjectsToRemove = new ArrayList<BaseObject>();
500   
501    private List<XWikiAttachmentToRemove> attachmentsToRemove = new ArrayList<XWikiAttachmentToRemove>();
502   
503    /**
504    * The view template (vm file) to use. When not set the default view template is used.
505    *
506    * @see com.xpn.xwiki.web.ViewAction#render(XWikiContext)
507    */
508    private String defaultTemplate;
509   
510    private String validationScript;
511   
512    private Object wikiNode;
513   
514    /**
515    * We are using a SoftReference which will allow the archive to be discarded by the Garbage collector as long as the
516    * context is closed (usually during the request)
517    */
518    private SoftReference<XWikiDocumentArchive> archive;
519   
520    private XWikiStoreInterface store;
521   
522    /**
523    * @see #getOriginalDocument()
524    */
525    private XWikiDocument originalDocument;
526   
527    /**
528    * Used to display the title and the content of this document. Do not inject the component here to avoid any simple
529    * new XWikiDocument to cause many useless initialization, in particular, during initialization of the stub context
530    * and other fake documents in tests.
531    */
532    private DocumentDisplayer documentDisplayer;
533   
534    /**
535    * @see #getDefaultEntityReferenceSerializer()
536    */
537    private EntityReferenceSerializer<String> defaultEntityReferenceSerializer;
538   
539    /**
540    * @see #getExplicitDocumentReferenceResolver()
541    */
542    private DocumentReferenceResolver<String> explicitDocumentReferenceResolver;
543   
544    /**
545    * @see #getExplicitReferenceDocumentReferenceResolver()
546    */
547    private DocumentReferenceResolver<EntityReference> explicitReferenceDocumentReferenceResolver;
548   
549    /**
550    * @see #getUidStringEntityReferenceSerializer()
551    */
552    private EntityReferenceSerializer<String> uidStringEntityReferenceSerializer;
553   
554    private Provider<OldRendering> oldRenderingProvider;
555   
556    private JobProgressManager progress;
557   
558    private ContextualLocalizationManager localization;
559   
560    /**
561    * The document structure expressed as a tree of Block objects. We store it for performance reasons since parsing is
562    * a costly operation that we don't want to repeat whenever some code ask for the XDOM information.
563    */
564    private XDOM xdomCache;
565   
566    /**
567    * Use to store rendered documents in #getRenderedContent(). Do not inject the component here to avoid any simple
568    * new XWikiDocument to cause many useless initialization, in particular, during initialization of the stub context
569    * and other fake documents in tests.
570    */
571    private RenderingCache renderingCache;
572   
573    /**
574    * Cache the parent reference resolved as an absolute reference for improved performance (so that we don't have to
575    * resolve the relative reference every time getParentReference() is called.
576    */
577    private DocumentReference parentReferenceCache;
578   
579    /**
580    * @see #getKey()
581    */
582    private String keyCache;
583   
584    /**
585    * @see #getLocalKey()
586    */
587    private String localKeyCache;
588   
589    private RenderingContext renderingContext;
590   
591    /**
592    * @since 2.2M1
593    */
 
594  11828459 toggle public XWikiDocument(DocumentReference reference)
595    {
596  11828439 init(reference);
597    }
598   
599    /**
600    * @since 6.2
601    */
 
602  14287394 toggle public XWikiDocument(DocumentReference reference, Locale locale)
603    {
604  14287372 init(reference);
605   
606  14287430 this.locale = locale;
607    }
608   
609    /**
610    * @deprecated since 2.2M1 use {@link #XWikiDocument(org.xwiki.model.reference.DocumentReference)} instead
611    */
 
612  52517 toggle @Deprecated
613    public XWikiDocument()
614    {
615  52518 this(null);
616    }
617   
618    /**
619    * Constructor that specifies the local document identifier: space name, document name. {@link #setDatabase(String)}
620    * must be called afterwards to specify the wiki name.
621    *
622    * @param space the space this document belongs to
623    * @param name the name of the document
624    * @deprecated since 2.2M1 use {@link #XWikiDocument(org.xwiki.model.reference.DocumentReference)} instead
625    */
 
626  2 toggle @Deprecated
627    public XWikiDocument(String space, String name)
628    {
629  2 this(null, space, name);
630    }
631   
632    /**
633    * Constructor that specifies the full document identifier: wiki name, space name, document name.
634    *
635    * @param wiki The wiki this document belongs to.
636    * @param space The space this document belongs to.
637    * @param name The name of the document (can contain either the page name or the space and page name)
638    * @deprecated since 2.2M1 use {@link #XWikiDocument(org.xwiki.model.reference.DocumentReference)} instead
639    */
 
640  4 toggle @Deprecated
641    public XWikiDocument(String wiki, String space, String name)
642    {
643    // We allow to specify the space in the name (eg name = "space.page"). In this case the passed space is
644    // ignored.
645   
646    // Build an entity reference that will serve as a current context reference against which to resolve if the
647    // passed name doesn't contain a space.
648  4 EntityReference contextReference = null;
649  4 if (!StringUtils.isEmpty(space)) {
650  4 contextReference = new EntityReference(space, EntityType.SPACE);
651    }
652   
653  4 DocumentReference reference = getCurrentDocumentReferenceResolver().resolve(name, contextReference);
654   
655  4 if (!StringUtils.isEmpty(wiki)) {
656  2 reference = reference.replaceParent(reference.getWikiReference(), new WikiReference(wiki));
657    }
658   
659  4 init(reference);
660    }
661   
662    /**
663    * Used to resolve a string into a proper Document Reference.
664    */
 
665  557535 toggle private DocumentReferenceResolver<String> getExplicitDocumentReferenceResolver()
666    {
667  557534 if (this.explicitDocumentReferenceResolver == null) {
668  145249 this.explicitDocumentReferenceResolver =
669    Utils.getComponent(DocumentReferenceResolver.TYPE_STRING, "explicit");
670    }
671   
672  557530 return this.explicitDocumentReferenceResolver;
673    }
674   
675    /**
676    * Used to normalize references.
677    */
 
678  74974 toggle private DocumentReferenceResolver<EntityReference> getExplicitReferenceDocumentReferenceResolver()
679    {
680  74970 if (this.explicitReferenceDocumentReferenceResolver == null) {
681  25923 this.explicitReferenceDocumentReferenceResolver =
682    Utils.getComponent(DocumentReferenceResolver.TYPE_REFERENCE, "explicit");
683    }
684   
685  74983 return this.explicitReferenceDocumentReferenceResolver;
686    }
687   
688    /**
689    * Used to convert a proper Document Reference to string (standard form).
690    */
 
691  454114 toggle private EntityReferenceSerializer<String> getDefaultEntityReferenceSerializer()
692    {
693  454116 if (this.defaultEntityReferenceSerializer == null) {
694  82653 this.defaultEntityReferenceSerializer = Utils.getComponent(EntityReferenceSerializer.TYPE_STRING);
695    }
696   
697  454123 return this.defaultEntityReferenceSerializer;
698    }
699   
700    /**
701    * Used to compute document identifier.
702    */
 
703  11 toggle private EntityReferenceSerializer<String> getUidStringEntityReferenceSerializer()
704    {
705  11 if (this.uidStringEntityReferenceSerializer == null) {
706  11 this.uidStringEntityReferenceSerializer = Utils.getComponent(EntityReferenceSerializer.TYPE_STRING, "uid");
707    }
708   
709  11 return this.uidStringEntityReferenceSerializer;
710    }
711   
 
712  5 toggle private ContextualLocalizationManager getLocalization()
713    {
714  5 if (this.localization == null) {
715  1 this.localization = Utils.getComponent(ContextualLocalizationManager.class);
716    }
717   
718  5 return this.localization;
719    }
720   
 
721  3 toggle private OldRendering getOldRendering()
722    {
723  3 if (this.oldRenderingProvider == null) {
724  1 this.oldRenderingProvider = Utils.getComponent(OldRendering.TYPE_PROVIDER);
725    }
726   
727  3 return this.oldRenderingProvider.get();
728    }
729   
 
730  198308 toggle private JobProgressManager getProgress()
731    {
732  198293 if (this.progress == null) {
733  10190 this.progress = Utils.getComponent(JobProgressManager.class);
734    }
735   
736  198301 return this.progress;
737    }
738   
 
739  5 toggle private String localizePlainOrKey(String key, Object... parameters)
740    {
741  5 return StringUtils.defaultString(getLocalization().getTranslationPlain(key, parameters), key);
742    }
743   
 
744  8269 toggle public XWikiStoreInterface getStore(XWikiContext context)
745    {
746  8264 return context.getWiki().getStore();
747    }
748   
749    /**
750    * @deprecated since 9.9RC1, use {@link XWiki#getDefaultAttachmentContentStore()} instead
751    */
 
752  0 toggle @Deprecated
753    public XWikiAttachmentStoreInterface getAttachmentStore(XWikiContext context)
754    {
755  0 return context.getWiki().getAttachmentStore();
756    }
757   
 
758  5819 toggle public XWikiVersioningStoreInterface getVersioningStore(XWikiContext context)
759    {
760  5819 return context.getWiki().getVersioningStore();
761    }
762   
 
763  280081 toggle public XWikiStoreInterface getStore()
764    {
765  280080 return this.store;
766    }
767   
 
768  3104565 toggle public void setStore(XWikiStoreInterface store)
769    {
770  3104480 this.store = store;
771    }
772   
 
773  80225 toggle private RenderingContext getRenderingContext()
774    {
775  80227 if (this.renderingContext == null) {
776  25448 this.renderingContext = Utils.getComponent(RenderingContext.class);
777    }
778   
779  80225 return this.renderingContext;
780    }
781   
782    /**
783    * Helper to produce and cache a local uid serialization of this document reference, including the language. Only
784    * translated document will have language appended.
785    *
786    * @return a unique name (in a wiki) (5:space4:name2:lg)
787    */
 
788  80200 toggle private String getLocalKey()
789    {
790  80202 if (this.localKeyCache == null) {
791  43043 this.localKeyCache =
792    LocalUidStringEntityReferenceSerializer.INSTANCE.serialize(getDocumentReferenceWithLocale());
793    }
794   
795  80206 return this.localKeyCache;
796    }
797   
798    /**
799    * Helper to produce and cache a uid serialization of this document reference, including the language. Only
800    * translated document will have language appended.
801    *
802    * @return a unique name (8:wikiname5:space4:name2:lg or 8:wikiname5:space4:name)
803    * @since 4.0M1
804    */
 
805  11 toggle public String getKey()
806    {
807  11 if (this.keyCache == null) {
808  11 this.keyCache = getUidStringEntityReferenceSerializer().serialize(getDocumentReferenceWithLocale());
809    }
810   
811  11 return this.keyCache;
812    }
813   
 
814  1468 toggle @Override
815    public int hashCode()
816    {
817  1468 return (int) Util.getHash(getLocalKey());
818    }
819   
820    /**
821    * @return the unique id used to represent the document, as a number. This id is technical and is equivalent to the
822    * Document Reference + the language of the Document. This technical id should only be used for the storage
823    * layer and all user APIs should instead use Document Reference and language as they are model-related
824    * while the id isn't (it's purely technical).
825    */
 
826  78755 toggle public long getId()
827    {
828    // TODO: Ensure uniqueness of the generated id
829    // The implementation doesn't guarantee a unique id since it uses a hashing method which never guarantee
830    // uniqueness. However, the hash algorithm is really unlikely to collide in a given wiki. This needs to be
831    // fixed to produce a real unique id since otherwise we can have clashes in the database.
832   
833    // Note: We don't use the wiki name in the document id's computation. The main historical reason is so
834    // that all things saved in a given wiki's database are always stored relative to that wiki so that
835    // changing that wiki's name is simpler.
836   
837  78741 this.id = Util.getHash(getLocalKey());
838   
839  78736 return this.id;
840    }
841   
842    /**
843    * @see #getId()
844    */
 
845  38371 toggle public void setId(long id)
846    {
847  38369 this.id = id;
848    }
849   
850    /**
851    * Return the full local space reference. For example a document located in sub-space <code>space11</code> of space
852    * <code>space1</code> will return <code>space1.space11</code>.
853    * <p>
854    * Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
855    *
856    * @return the local reference the space of the document as String
857    * @deprecated since 2.2M1 used {@link #getDocumentReference()} instead
858    */
 
859  637958 toggle @Deprecated
860    public String getSpace()
861    {
862  637959 return LOCAL_REFERENCE_SERIALIZER.serialize(getDocumentReference().getLastSpaceReference());
863    }
864   
865    /**
866    * Set the full local space reference.
867    * <p>
868    * Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
869    *
870    * @see #getSpace()
871    * @deprecated since 2.2M1 used {@link #setDocumentReference(DocumentReference)} instead
872    */
 
873  38053 toggle @Deprecated
874    public void setSpace(String spaces)
875    {
876  38053 if (spaces != null) {
877  38053 DocumentReference reference = getDocumentReference();
878  38055 EntityReference spaceReference = getRelativeEntityReferenceResolver().resolve(spaces, EntityType.SPACE);
879  38059 spaceReference = spaceReference.appendParent(getDocumentReference().getWikiReference());
880  38061 setDocumentReferenceInternal(
881    new DocumentReference(reference.getName(), new SpaceReference(spaceReference)));
882    }
883    }
884   
885    /**
886    * Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
887    *
888    * @return the name of the space of the document
889    * @see #getSpace()
890    * @deprecated use {@link #getDocumentReference()} instead
891    */
 
892  13452 toggle @Deprecated
893    public String getWeb()
894    {
895  13452 return getSpace();
896    }
897   
898    /**
899    * Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
900    *
901    * @deprecated use {@link #setDocumentReference(DocumentReference)} instead
902    */
 
903  19031 toggle @Deprecated
904    public void setWeb(String space)
905    {
906  19031 setSpace(space);
907    }
908   
 
909  348047 toggle @Override
910    public String getVersion()
911    {
912  348047 return getRCSVersion().toString();
913    }
914   
 
915  21804 toggle public void setVersion(String version)
916    {
917  21802 if (!StringUtils.isEmpty(version)) {
918  21804 this.version = new Version(version);
919    }
920    }
921   
 
922  360142 toggle public Version getRCSVersion()
923    {
924  360143 if (this.version == null) {
925  4567 return new Version("1.1");
926    }
927  355576 return this.version;
928    }
929   
 
930  8800 toggle public void setRCSVersion(Version version)
931    {
932  8800 this.version = version;
933    }
934   
935    /**
936    * @return the copy of this XWikiDocument instance before any modification was made to it. It is reset to the actual
937    * values when the document is saved in the database. This copy is used for finding out differences made to
938    * this document (useful for example to send the correct notifications to document change listeners).
939    */
 
940  65049 toggle @Override
941    public XWikiDocument getOriginalDocument()
942    {
943  65049 return this.originalDocument;
944    }
945   
946    /**
947    * @param originalDocument the original document representing this document instance before any change was made to
948    * it, prior to the last time it was saved
949    * @see #getOriginalDocument()
950    */
 
951  2847617 toggle public void setOriginalDocument(XWikiDocument originalDocument)
952    {
953  2847575 this.originalDocument = originalDocument;
954    }
955   
956    /**
957    * @return the parent reference or null if the parent is not set
958    * @since 2.2M1
959    */
 
960  52345 toggle public DocumentReference getParentReference()
961    {
962    // Ensure we always return absolute document references for the parent since we always want well-constructed
963    // references and since we store the parent reference as relative internally.
964  52347 if (this.parentReferenceCache == null && getRelativeParentReference() != null) {
965  12066 this.parentReferenceCache = getExplicitReferenceDocumentReferenceResolver()
966    .resolve(getRelativeParentReference(), getDocumentReference());
967    }
968   
969  52349 return this.parentReferenceCache;
970    }
971   
972    /**
973    * Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
974    *
975    * @return the parent reference stored in the database, which is relative to this document, or an empty string ("")
976    * if the parent is not set
977    * @see #getParentReference()
978    * @deprecated since 2.2M1 use {@link #getParentReference()} instead
979    */
 
980  43840 toggle @Deprecated
981    public String getParent()
982    {
983  43835 String parentReferenceAsString;
984  43834 if (getParentReference() != null) {
985  29856 parentReferenceAsString = getDefaultEntityReferenceSerializer().serialize(getRelativeParentReference());
986    } else {
987  13981 parentReferenceAsString = "";
988    }
989  43837 return parentReferenceAsString;
990    }
991   
992    /**
993    * @deprecated since 2.2M1 use {@link #getParentReference()} instead
994    */
 
995  0 toggle @Deprecated
996    public XWikiDocument getParentDoc()
997    {
998  0 return new XWikiDocument(getParentReference());
999    }
1000   
1001    /**
1002    * @since 2.2.3
1003    */
 
1004  260310 toggle public void setParentReference(EntityReference parentReference)
1005    {
1006  260319 if (!Objects.equals(getRelativeParentReference(), parentReference)) {
1007  204584 this.parentReference = parentReference;
1008   
1009    // Clean the absolute parent reference cache to rebuild it next time getParentReference is called.
1010  204581 this.parentReferenceCache = null;
1011   
1012  204585 setMetaDataDirty(true);
1013    }
1014    }
1015   
1016    /**
1017    * Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
1018    *
1019    * @param parent the reference of the parent relative to the document
1020    * @deprecated since 2.2M1, use {@link #setParentReference(EntityReference)} instead
1021    */
 
1022  19486 toggle @Deprecated
1023    public void setParent(String parent)
1024    {
1025    // If the passed parent is an empty string we also need to set the reference to null. The reason is that
1026    // in the database we store "" when the parent is empty and thus when Hibernate loads this class it'll call
1027    // setParent with "" if the parent had not been set when saved.
1028  19481 if (StringUtils.isEmpty(parent)) {
1029  887 setParentReference((EntityReference) null);
1030    } else {
1031  18600 setParentReference(getRelativeEntityReferenceResolver().resolve(parent, EntityType.DOCUMENT));
1032    }
1033    }
1034   
 
1035  356786 toggle @Override
1036    public String getContent()
1037    {
1038  356779 return this.content;
1039    }
1040   
 
1041  254295 toggle public void setContent(String content)
1042    {
1043  254305 if (content == null) {
1044  0 content = "";
1045    }
1046   
1047  254305 boolean notEqual = !content.equals(this.content);
1048   
1049  254306 this.content = content;
1050   
1051  254302 if (notEqual) {
1052    // invalidate parsed xdom
1053  77621 this.xdomCache = null;
1054  77621 setContentDirty(true);
1055  77620 setWikiNode(null);
1056    }
1057    }
1058   
 
1059  5 toggle public void setContent(XDOM content) throws XWikiException
1060    {
1061  5 setContent(renderXDOM(content, getSyntax()));
1062    }
1063   
1064    /**
1065    * @return the default rendering cache
1066    */
 
1067  54878 toggle private RenderingCache getRenderingCache()
1068    {
1069  54876 if (this.renderingCache == null) {
1070  9000 this.renderingCache = Utils.getComponent((Type) RenderingCache.class);
1071    }
1072  54874 return this.renderingCache;
1073    }
1074   
1075    /**
1076    * @return the configured document displayer
1077    */
 
1078  197942 toggle private DocumentDisplayer getDocumentDisplayer()
1079    {
1080  197939 if (this.documentDisplayer == null) {
1081  55898 this.documentDisplayer = Utils.getComponent((Type) DocumentDisplayer.class, "configured");
1082    }
1083  197941 return this.documentDisplayer;
1084    }
1085   
 
1086  80226 toggle private Syntax getOutputSyntax()
1087    {
1088  80226 return getRenderingContext().getTargetSyntax();
1089    }
1090   
1091    /**
1092    * Parse, execute and render the document.
1093    *
1094    * @param targetSyntax the syntax to use to render the document
1095    * @param executionContextIsolated see {@link DocumentDisplayerParameters#isExecutionContextIsolated()}
1096    * @param transformationContextIsolated see {@link DocumentDisplayerParameters#isTransformationContextIsolated()}
1097    * @param transformationContextRestricted see
1098    * {@link DocumentDisplayerParameters#isTransformationContextRestricted()}
1099    * @param translate get translated content of the document
1100    * @return the result of the document execution rendered in the passed syntax
1101    * @throws XWikiException when failing to display the document
1102    */
 
1103  27436 toggle private String display(Syntax targetSyntax, boolean executionContextIsolated, boolean transformationContextIsolated,
1104    boolean transformationContextRestricted, boolean translate) throws XWikiException
1105    {
1106    // Note: We are currently duplicating code from getRendered signature because some calling
1107    // code is expecting that the rendering will happen in the calling document's context and not in this
1108    // document's context. For example this is true for the Admin page, see
1109    // https://jira.xwiki.org/browse/XWIKI-4274 for more details.
1110   
1111  27434 getProgress().startStep(this, "document.progress.render", "Render document [{}] in syntax [{}]",
1112    getDocumentReference(), targetSyntax);
1113   
1114  27438 try {
1115  27437 getProgress().pushLevelProgress(3, getDocumentReference());
1116   
1117  27438 getProgress().startStep(getDocumentReference(), "document.progress.render.translatedcontent",
1118    "Get translated content");
1119   
1120  27439 XWikiContext xcontext = getXWikiContext();
1121   
1122  27435 XWikiDocument tdoc = translate ? getTranslatedDocument(xcontext) : this;
1123  27438 String translatedContent = tdoc.getContent();
1124   
1125  27439 getProgress().startStep(getDocumentReference(), "document.progress.render.cache",
1126    "Try to get content from the cache");
1127   
1128  27439 String renderedContent = getRenderingCache().getRenderedContent(tdoc.getDocumentReferenceWithLocale(),
1129    translatedContent, xcontext);
1130   
1131  27434 if (renderedContent == null) {
1132  27432 getProgress().startStep(getDocumentReference(), "document.progress.render.execute", "Execute content");
1133   
1134    // Configure display
1135  27439 DocumentDisplayerParameters parameters = new DocumentDisplayerParameters();
1136  27439 parameters.setExecutionContextIsolated(executionContextIsolated);
1137  27428 parameters.setTransformationContextIsolated(transformationContextIsolated);
1138  27428 parameters.setTransformationContextRestricted(transformationContextRestricted);
1139    // Render the translated content (matching the current language) using this document's syntax.
1140  27427 parameters.setContentTranslated(tdoc != this);
1141  27428 parameters.setTargetSyntax(targetSyntax);
1142   
1143    // Execute display
1144  27424 XDOM contentXDOM = getDocumentDisplayer().display(this, parameters);
1145   
1146    // Render the result
1147  27439 renderedContent = renderXDOM(contentXDOM, targetSyntax);
1148   
1149  27437 getRenderingCache().setRenderedContent(getDocumentReference(), translatedContent, renderedContent,
1150    xcontext);
1151    }
1152   
1153  27431 return renderedContent;
1154    } finally {
1155  27433 getProgress().popLevelProgress(getDocumentReference());
1156  27438 getProgress().endStep(this);
1157    }
1158    }
1159   
 
1160  23469 toggle public String getRenderedContent(Syntax targetSyntax, XWikiContext context) throws XWikiException
1161    {
1162  23461 return getRenderedContent(targetSyntax, true, context);
1163    }
1164   
1165    /**
1166    * @since 8.4RC1
1167    */
 
1168  2 toggle public String getRenderedContent(boolean transformationContextIsolated, XWikiContext context) throws XWikiException
1169    {
1170  2 return getRenderedContent(getOutputSyntax(), transformationContextIsolated, context);
1171    }
1172   
 
1173  24305 toggle public String getRenderedContent(Syntax targetSyntax, boolean transformationContextIsolated, XWikiContext context)
1174    throws XWikiException
1175    {
1176    // Make sure the context secure document is the current document so that it's executed with its own
1177    // rights
1178  24304 Object currrentSdoc = context.get("sdoc");
1179  24303 try {
1180    // If we execute a translation use translated document as secure document
1181  24306 XWikiDocument tdoc = getTranslatedDocument(context);
1182   
1183  24305 context.put("sdoc", tdoc);
1184   
1185  24308 return display(targetSyntax, false, transformationContextIsolated, false, true);
1186    } finally {
1187  24310 context.put("sdoc", currrentSdoc);
1188    }
1189    }
1190   
 
1191  13417 toggle public String getRenderedContent(XWikiContext context) throws XWikiException
1192    {
1193  13417 return getRenderedContent(getOutputSyntax(), context);
1194    }
1195   
1196    /**
1197    * @param text the text to render
1198    * @param syntaxId the id of the Syntax used by the passed text (e.g. {@code xwiki/2.1})
1199    * @param context the XWiki Context object
1200    * @return the given text rendered in the context of this document using the passed Syntax
1201    * @since 1.6M1
1202    */
 
1203  2648 toggle public String getRenderedContent(String text, String syntaxId, XWikiContext context)
1204    {
1205  2648 return getRenderedContent(text, syntaxId, getOutputSyntax().toIdString(), context);
1206    }
1207   
1208    /**
1209    * @param text the text to render
1210    * @param syntaxId the id of the Syntax used by the passed text (e.g. {@code xwiki/2.1})
1211    * @param restrictedTransformationContext see {@link DocumentDisplayerParameters#isTransformationContextRestricted}.
1212    * @param context the XWiki Context object
1213    * @return the given text rendered in the context of this document using the passed Syntax
1214    * @since 4.2M1
1215    */
 
1216  0 toggle public String getRenderedContent(String text, String syntaxId, boolean restrictedTransformationContext,
1217    XWikiContext context)
1218    {
1219  0 return getRenderedContent(text, syntaxId, getOutputSyntax().toIdString(), restrictedTransformationContext,
1220    context);
1221    }
1222   
1223    /**
1224    * @param text the text to render
1225    * @param syntaxId the id of the Syntax used by the passed text (e.g. {@code xwiki/2.1})
1226    * @param restrictedTransformationContext see {@link DocumentDisplayerParameters#isTransformationContextRestricted}.
1227    * @param sDocument the {@link XWikiDocument} to use as secure document, if null keep the current one
1228    * @param context the XWiki Context object
1229    * @return the given text rendered in the context of this document using the passed Syntax
1230    * @since 8.3
1231    */
 
1232  358 toggle public String getRenderedContent(String text, String syntaxId, boolean restrictedTransformationContext,
1233    XWikiDocument sDocument, XWikiContext context)
1234    {
1235  358 return getRenderedContent(text, syntaxId, getOutputSyntax().toIdString(), restrictedTransformationContext,
1236    sDocument, context);
1237    }
1238   
1239    /**
1240    * @param text the text to render
1241    * @param sourceSyntaxId the id of the Syntax used by the passed text (e.g. {@code xwiki/2.1})
1242    * @param targetSyntaxId the id of the syntax in which to render the document content
1243    * @param context the XWiki context
1244    * @return the given text rendered in the context of this document using the passed Syntax
1245    * @since 2.0M3
1246    */
 
1247  2663 toggle public String getRenderedContent(String text, String sourceSyntaxId, String targetSyntaxId, XWikiContext context)
1248    {
1249  2663 return getRenderedContent(text, sourceSyntaxId, targetSyntaxId, false, context);
1250    }
1251   
1252    /**
1253    * @param text the text to render
1254    * @param sourceSyntaxId the id of the Syntax used by the passed text (e.g. {@code xwiki/2.1})
1255    * @param targetSyntaxId the id of the syntax in which to render the document content
1256    * @param restrictedTransformationContext see {@link DocumentDisplayerParameters#isTransformationContextRestricted}.
1257    * @param context the XWiki context
1258    * @return the given text rendered in the context of this document using the passed Syntax
1259    * @since 4.2M1
1260    */
 
1261  2663 toggle public String getRenderedContent(String text, String sourceSyntaxId, String targetSyntaxId,
1262    boolean restrictedTransformationContext, XWikiContext context)
1263    {
1264  2663 return getRenderedContent(text, sourceSyntaxId, targetSyntaxId, restrictedTransformationContext, null, context);
1265    }
1266   
1267    /**
1268    * @param text the text to render
1269    * @param sourceSyntaxId the id of the Syntax used by the passed text (e.g. {@code xwiki/2.1})
1270    * @param targetSyntaxId the id of the syntax in which to render the document content
1271    * @param restrictedTransformationContext see {@link DocumentDisplayerParameters#isTransformationContextRestricted}.
1272    * @param sDocument the {@link XWikiDocument} to use as secure document, if null keep the current one
1273    * @param context the XWiki context
1274    * @return the given text rendered in the context of this document using the passed Syntax
1275    * @since 8.3
1276    */
 
1277  3127 toggle public String getRenderedContent(String text, String sourceSyntaxId, String targetSyntaxId,
1278    boolean restrictedTransformationContext, XWikiDocument sDocument, XWikiContext context)
1279    {
1280  3127 Map<String, Object> backup = null;
1281   
1282  3127 getProgress().startStep(this, "document.progress.renderText",
1283    "Execute content [{}] in the context of document [{}]",
1284  3127 StringUtils.substring(text, 0, 100) + (text.length() >= 100 ? "..." : ""), getDocumentReference());
1285   
1286  3127 XWikiDocument currentSDocument = (XWikiDocument) context.get(CKEY_SDOC);
1287  3127 try {
1288    // We have to render the given text in the context of this document. Check if this document is already
1289    // on the context (same Java object reference). We don't check if the document references are equal
1290    // because this document can have temporary changes that are not present on the context document even if
1291    // it has the same document reference.
1292  3127 if (context.getDoc() != this) {
1293  367 backup = new HashMap<>();
1294  367 backupContext(backup, context);
1295  367 setAsContextDoc(context);
1296    }
1297   
1298    // Make sure to execute the document with the right of the provided sdocument's author
1299  3127 if (sDocument != null) {
1300  462 context.put(CKEY_SDOC, sDocument);
1301    }
1302   
1303    // Reuse this document's reference so that the Velocity macro name-space is computed based on it.
1304  3127 XWikiDocument fakeDocument = new XWikiDocument(getDocumentReference());
1305  3127 fakeDocument.setSyntax(Syntax.valueOf(sourceSyntaxId));
1306  3127 fakeDocument.setContent(text);
1307   
1308    // We don't let displayer take care of the context isolation because we don't want the fake document to be
1309    // context document
1310  3127 return fakeDocument.display(Syntax.valueOf(targetSyntaxId), false, true, restrictedTransformationContext,
1311    false);
1312    } catch (Exception e) {
1313    // Failed to render for some reason. This method should normally throw an exception but this
1314    // requires changing the signature of calling methods too.
1315  0 LOGGER.warn("Failed to render content [" + text + "]", e);
1316    } finally {
1317  3127 if (backup != null) {
1318  367 restoreContext(backup, context);
1319    }
1320  3127 context.put(CKEY_SDOC, currentSDocument);
1321   
1322  3127 getProgress().endStep(this);
1323    }
1324   
1325  0 return "";
1326    }
1327   
 
1328  0 toggle public String getEscapedContent(XWikiContext context) throws XWikiException
1329    {
1330  0 return XMLUtils.escape(getTranslatedContent(context));
1331    }
1332   
1333    /**
1334    * Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
1335    *
1336    * @deprecated since 2.2M1 used {@link #getDocumentReference()} instead
1337    */
 
1338  547199 toggle @Deprecated
1339    public String getName()
1340    {
1341  547203 return getDocumentReference().getName();
1342    }
1343   
1344    /**
1345    * Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
1346    *
1347    * @deprecated since 2.2M1 used {@link #setDocumentReference(DocumentReference)} instead
1348    */
 
1349  19027 toggle @Deprecated
1350    public void setName(String name)
1351    {
1352  19029 if (name != null) {
1353  19027 DocumentReference reference = getDocumentReference();
1354    // TODO: ensure that other parameters are copied properly
1355  19030 setDocumentReferenceInternal(
1356    new DocumentReference(name, new SpaceReference(reference.getParent()), reference.getLocale()));
1357    }
1358    }
1359   
 
1360  185117862 toggle @Override
1361    public DocumentReference getDocumentReference()
1362    {
1363  185117780 return this.documentReference;
1364    }
1365   
1366    /**
1367    * @return the {@link DocumentReference} of the document also containing the document {@link Locale}
1368    * @since 5.3M2
1369    */
 
1370  11627702 toggle public DocumentReference getDocumentReferenceWithLocale()
1371    {
1372  11627677 return new DocumentReference(this.documentReference, getLocale());
1373    }
1374   
1375    /**
1376    * @return the document's space + page name (eg "space.page")
1377    * @deprecated since 2.2M1 use {@link #getDocumentReference()} instead
1378    */
 
1379  3108908 toggle @Deprecated
1380    @Override
1381    public String getFullName()
1382    {
1383  3108898 return LOCAL_REFERENCE_SERIALIZER.serialize(getDocumentReference());
1384    }
1385   
1386    /**
1387    * @return the docoument's wiki + space + page name (eg "wiki:space.page")
1388    * @deprecated since 2.2M1 use {@link #getDocumentReference()} instead
1389    */
 
1390  423673 toggle @Deprecated
1391    public String getPrefixedFullName()
1392    {
1393  423670 return getDefaultEntityReferenceSerializer().serialize(getDocumentReference());
1394    }
1395   
1396    /**
1397    * @since 2.2M1
1398    * @deprecated since 2.2.3 don't change the reference of a document once it's been constructed. Instead you can
1399    * clone the doc, rename it or copy it.
1400    */
 
1401  26195627 toggle @Deprecated
1402    public void setDocumentReference(DocumentReference reference)
1403    {
1404    // Don't allow setting a null reference for now, ie. don't do anything to preserve backward compatibility
1405    // with previous behavior (i.e. {@link #setFullName}.
1406  26195606 if (reference != null) {
1407    // Retro compatibility, make sure <code>this.documentReference</code> does not contain the Locale (for now)
1408  26195633 DocumentReference referenceWithoutLocale =
1409  26195503 reference.getLocale() != null ? new DocumentReference(reference, null) : reference;
1410   
1411  26195580 if (!referenceWithoutLocale.equals(getDocumentReference())) {
1412  26167833 setDocumentReferenceInternal(referenceWithoutLocale);
1413    }
1414    }
1415    }
1416   
 
1417  26224748 toggle private void setDocumentReferenceInternal(DocumentReference reference)
1418    {
1419  26224780 this.documentReference = reference;
1420   
1421  26224765 setMetaDataDirty(true);
1422   
1423    // Clean various caches
1424   
1425  26224779 this.keyCache = null;
1426  26224791 this.localKeyCache = null;
1427  26224794 this.parentReferenceCache = null;
1428    }
1429   
1430    /**
1431    * @deprecated since 2.2M1 use {@link #setDocumentReference(org.xwiki.model.reference.DocumentReference)} instead
1432    */
 
1433  19097 toggle @Deprecated
1434    public void setFullName(String name)
1435    {
1436  19094 setFullName(name, null);
1437    }
1438   
1439    /**
1440    * @deprecated since 2.2M1 use {@link #setDocumentReference(org.xwiki.model.reference.DocumentReference)} instead
1441    */
 
1442  68714 toggle @Deprecated
1443    public void setFullName(String fullName, XWikiContext context)
1444    {
1445    // We ignore the passed full name if it's null to be backward compatible with previous behaviors and to be
1446    // consistent with {@link #setName} and {@link #setSpace}.
1447  68733 if (fullName != null) {
1448    // Note: We use the CurrentMixed Resolver since we want to use the default page name if the page isn't
1449    // specified in the passed string, rather than use the current document's page name.
1450  68731 setDocumentReference(getCurrentMixedDocumentReferenceResolver().resolve(fullName));
1451    }
1452    }
1453   
1454    /**
1455    * {@inheritDoc}
1456    *
1457    * @see DocumentModelBridge#getWikiName()
1458    * @deprecated since 2.2M1 use {@link #getDocumentReference()} instead
1459    */
 
1460  464 toggle @Deprecated
1461    @Override
1462    public String getWikiName()
1463    {
1464  464 return getDatabase();
1465    }
1466   
1467    /**
1468    * {@inheritDoc}
1469    *
1470    * @see DocumentModelBridge#getSpaceName()
1471    * @deprecated since 2.2M1 use {@link #getDocumentReference()} instead
1472    */
 
1473  7 toggle @Deprecated
1474    @Override
1475    public String getSpaceName()
1476    {
1477  7 return this.getSpace();
1478    }
1479   
1480    /**
1481    * {@inheritDoc}
1482    *
1483    * @see DocumentModelBridge#getSpaceName()
1484    * @deprecated since 2.2M1 use {@link #getDocumentReference()} instead
1485    */
 
1486  8 toggle @Deprecated
1487    @Override
1488    public String getPageName()
1489    {
1490  8 return this.getName();
1491    }
1492   
 
1493  489525 toggle @Override
1494    public String getTitle()
1495    {
1496  489517 return (this.title != null) ? this.title : "";
1497    }
1498   
1499    /**
1500    * Get the rendered version of the document title. The title is extracted and then Velocity is applied on it and
1501    * it's then rendered using the passed Syntax. The following logic is used to extract the title:
1502    * <ul>
1503    * <li>If a Sheet is specified for the document and this Sheet document contains a non empty title then it's
1504    * used</li>
1505    * <li>If not and the document's title is specified then it's used</li>
1506    * <li>If not and if the title compatibility mode is turned on ({@code xwiki.title.compatibility=1} in
1507    * {@code xwiki.cfg}) then an attempt is made to extract the title from the first heading found in the document's
1508    * content</li>
1509    * <li>If not, then at last resort the page name is returned</li>
1510    * </ul>
1511    *
1512    * @param outputSyntax the syntax to render to; this is not taken into account for XWiki 1.0 syntax
1513    * @param context the XWiki context
1514    * @return the rendered version of the document title
1515    */
 
1516  170505 toggle public String getRenderedTitle(Syntax outputSyntax, XWikiContext context)
1517    {
1518  170505 DocumentDisplayerParameters parameters = new DocumentDisplayerParameters();
1519  170510 parameters.setTitleDisplayed(true);
1520  170509 parameters.setExecutionContextIsolated(true);
1521  170509 parameters.setTargetSyntax(outputSyntax);
1522  170512 try {
1523  170512 XDOM titleXDOM = getDocumentDisplayer().display(this, parameters);
1524  170508 return renderXDOM(titleXDOM, outputSyntax);
1525    } catch (Exception e) {
1526    // We've failed to extract the Document's title or to render it. We log an error but we use the page name
1527    // as the returned title in order to not generate errors in lots of places in the wiki (e.g. Activity
1528    // Stream, menus, etc). The title is used in a lots of places...
1529  0 LOGGER.error("Failed to render title for [{}]", getDocumentReference(), e);
1530  0 return getDocumentReference().getName();
1531    }
1532    }
1533   
1534    /**
1535    * Similar to {@link #getRenderedTitle(Syntax, XWikiContext)} but the output Syntax used is XHTML 1.0 unless the
1536    * current skin defines another output Syntax in which case it's the one used.
1537    *
1538    * @param context the XWiki context
1539    * @return the rendered version of the document title
1540    */
 
1541  63805 toggle public String getRenderedTitle(XWikiContext context)
1542    {
1543  63801 return getRenderedTitle(getOutputSyntax(), context);
1544    }
1545   
 
1546  278962 toggle public void setTitle(String title)
1547    {
1548  278957 if (title != null && !title.equals(this.title)) {
1549    // Document titles usually contain velocity script, so it is not enough to set the metadata dirty, since we
1550    // want to content author to be updated for programming or script rights to be updated.
1551  250864 setContentDirty(true);
1552    }
1553  278966 this.title = title;
1554    }
1555   
 
1556  227324 toggle public String getFormat()
1557    {
1558  227330 return this.format != null ? this.format : "";
1559    }
1560   
 
1561  227093 toggle public void setFormat(String format)
1562    {
1563  227096 if (!format.equals(this.format)) {
1564  0 this.format = format;
1565   
1566  0 setMetaDataDirty(true);
1567    }
1568    }
1569   
1570    /**
1571    * @param userString the user {@link String} to convert to {@link DocumentReference}
1572    * @return the user as {@link DocumentReference}
1573    */
 
1574  59321 toggle private DocumentReference userStringToReference(String userString)
1575    {
1576  59322 DocumentReference userReference;
1577   
1578  59329 if (StringUtils.isEmpty(userString)) {
1579  0 userReference = null;
1580    } else {
1581  59325 userReference = getExplicitReferenceDocumentReferenceResolver().resolve(
1582    getXClassEntityReferenceResolver().resolve(userString, EntityType.DOCUMENT), getDocumentReference());
1583   
1584  59334 if (userReference.getName().equals(XWikiRightService.GUEST_USER)) {
1585  40 userReference = null;
1586    }
1587    }
1588   
1589  59334 return userReference;
1590    }
1591   
1592    /**
1593    * @param userReference the user {@link DocumentReference} to convert to {@link String}
1594    * @return the user as String
1595    */
 
1596  118617 toggle private String userReferenceToString(DocumentReference userReference)
1597    {
1598  118617 String userString;
1599   
1600  118617 if (userReference != null) {
1601  114848 userString = getCompactWikiEntityReferenceSerializer().serialize(userReference, getDocumentReference());
1602    } else {
1603  3769 userString = XWikiRightService.GUEST_USER_FULLNAME;
1604    }
1605   
1606  118617 return userString;
1607    }
1608   
1609    /**
1610    * @since 3.0M3
1611    */
 
1612  1716257 toggle public DocumentReference getAuthorReference()
1613    {
1614  1716257 return this.authorReference;
1615    }
1616   
1617    /**
1618    * @since 3.0M3
1619    */
 
1620  1380053 toggle public void setAuthorReference(DocumentReference authorReference)
1621    {
1622  1380048 if (ObjectUtils.notEqual(authorReference, getAuthorReference())) {
1623  1329063 setMetaDataDirty(true);
1624    }
1625   
1626  1380039 this.authorReference = authorReference;
1627   
1628    // Log this since it's probably a mistake so that we find who is doing bad things
1629  1380016 if (this.authorReference != null && this.authorReference.getName().equals(XWikiRightService.GUEST_USER)) {
1630  0 LOGGER.warn("A reference to XWikiGuest user has been set instead of null. This is probably a mistake.",
1631    new Exception("See stack trace"));
1632    }
1633    }
1634   
1635    /**
1636    * Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
1637    *
1638    * @deprecated since 3.0M3 use {@link #getAuthorReference()} instead
1639    */
 
1640  56608 toggle @Deprecated
1641    public String getAuthor()
1642    {
1643  56608 return userReferenceToString(getAuthorReference());
1644    }
1645   
1646    /**
1647    * Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
1648    *
1649    * @deprecated since 3.0M3 use {@link #setAuthorReference} instead
1650    */
 
1651  19872 toggle @Deprecated
1652    public void setAuthor(String author)
1653    {
1654  19872 setAuthorReference(userStringToReference(author));
1655    }
1656   
1657    /**
1658    * @since 3.0M3
1659    */
 
1660  2220921 toggle @Override
1661    public DocumentReference getContentAuthorReference()
1662    {
1663  2220912 return this.contentAuthorReference;
1664    }
1665   
1666    /**
1667    * @since 3.0M3
1668    */
 
1669  1382814 toggle public void setContentAuthorReference(DocumentReference contentAuthorReference)
1670    {
1671  1382793 if (ObjectUtils.notEqual(contentAuthorReference, getContentAuthorReference())) {
1672  1332040 setMetaDataDirty(true);
1673    }
1674   
1675  1382765 this.contentAuthorReference = contentAuthorReference;
1676   
1677    // Log this since it's probably a mistake so that we find who is doing bad things
1678  1382746 if (this.contentAuthorReference != null
1679    && this.contentAuthorReference.getName().equals(XWikiRightService.GUEST_USER)) {
1680  0 LOGGER.warn("A reference to XWikiGuest user has been set instead of null. This is probably a mistake.",
1681    new Exception("See stack trace"));
1682    }
1683    }
1684   
1685    /**
1686    * Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
1687    *
1688    * @deprecated since 3.0M3 use {@link #getContentAuthorReference()} instead
1689    */
 
1690  27020 toggle @Deprecated
1691    public String getContentAuthor()
1692    {
1693  27020 return userReferenceToString(getContentAuthorReference());
1694    }
1695   
1696    /**
1697    * Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
1698    *
1699    * @deprecated since 3.0M3 use {@link #setContentAuthorReference} instead
1700    */
 
1701  19024 toggle @Deprecated
1702    public void setContentAuthor(String contentAuthor)
1703    {
1704  19024 setContentAuthorReference(userStringToReference(contentAuthor));
1705    }
1706   
1707    /**
1708    * @since 3.0M3
1709    */
 
1710  1646103 toggle public DocumentReference getCreatorReference()
1711    {
1712  1646101 return this.creatorReference;
1713    }
1714   
1715    /**
1716    * @since 3.0M3
1717    */
 
1718  1354757 toggle public void setCreatorReference(DocumentReference creatorReference)
1719    {
1720  1354795 if (ObjectUtils.notEqual(creatorReference, getCreatorReference())) {
1721  1312801 setMetaDataDirty(true);
1722    }
1723   
1724  1354800 this.creatorReference = creatorReference;
1725   
1726    // Log this since it's probably a mistake so that we find who is doing bad things
1727  1354786 if (this.creatorReference != null && this.creatorReference.getName().equals(XWikiRightService.GUEST_USER)) {
1728  0 LOGGER.warn("A reference to XWikiGuest user has been set instead of null. This is probably a mistake.",
1729    new Exception("See stack trace"));
1730    }
1731    }
1732   
1733    /**
1734    * Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
1735    *
1736    * @deprecated since 3.0M2 use {@link #getCreatorReference()} instead
1737    */
 
1738  34989 toggle @Deprecated
1739    public String getCreator()
1740    {
1741  34989 return userReferenceToString(getCreatorReference());
1742    }
1743   
1744    /**
1745    * Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
1746    *
1747    * @deprecated since 3.0M2 use {@link #setCreatorReference} instead
1748    */
 
1749  20429 toggle @Deprecated
1750    public void setCreator(String creator)
1751    {
1752  20430 setCreatorReference(userStringToReference(creator));
1753    }
1754   
 
1755  293528 toggle public Date getDate()
1756    {
1757  293529 if (this.updateDate == null) {
1758  0 return new Date();
1759    } else {
1760  293535 return this.updateDate;
1761    }
1762    }
1763   
 
1764  284247 toggle public void setDate(Date date)
1765    {
1766  284268 if ((date != null) && (!date.equals(this.updateDate))) {
1767  220755 setMetaDataDirty(true);
1768    }
1769    // Make sure we drop milliseconds for consistency with the database
1770  284265 if (date != null) {
1771  284260 date.setTime((date.getTime() / 1000) * 1000);
1772    }
1773  284249 this.updateDate = date;
1774    }
1775   
 
1776  289389 toggle public Date getCreationDate()
1777    {
1778  289394 if (this.creationDate == null) {
1779  45 return new Date();
1780    } else {
1781  289353 return this.creationDate;
1782    }
1783    }
1784   
 
1785  277505 toggle public void setCreationDate(Date date)
1786    {
1787  277516 if ((date != null) && (!date.equals(this.creationDate))) {
1788  210988 setMetaDataDirty(true);
1789    }
1790   
1791    // Make sure we drop milliseconds for consistency with the database
1792  277519 if (date != null) {
1793  273280 date.setTime((date.getTime() / 1000) * 1000);
1794    }
1795  277513 this.creationDate = date;
1796    }
1797   
 
1798  279643 toggle public Date getContentUpdateDate()
1799    {
1800  279643 if (this.contentUpdateDate == null) {
1801  0 return new Date();
1802    } else {
1803  279649 return this.contentUpdateDate;
1804    }
1805    }
1806   
 
1807  283124 toggle public void setContentUpdateDate(Date date)
1808    {
1809  283152 if ((date != null) && (!date.equals(this.contentUpdateDate))) {
1810  219302 setMetaDataDirty(true);
1811    }
1812   
1813    // Make sure we drop milliseconds for consistency with the database
1814  283148 if (date != null) {
1815  283142 date.setTime((date.getTime() / 1000) * 1000);
1816    }
1817  283153 this.contentUpdateDate = date;
1818    }
1819   
 
1820  227095 toggle public String getMeta()
1821    {
1822  227099 return this.meta;
1823    }
1824   
 
1825  227090 toggle public void setMeta(String meta)
1826    {
1827  227098 if (meta == null) {
1828  227096 if (this.meta != null) {
1829  0 setMetaDataDirty(true);
1830    }
1831  0 } else if (!meta.equals(this.meta)) {
1832  0 setMetaDataDirty(true);
1833    }
1834  227102 this.meta = meta;
1835    }
1836   
 
1837  0 toggle public void appendMeta(String meta)
1838    {
1839  0 StringBuilder buf = new StringBuilder(this.meta);
1840  0 buf.append(meta);
1841  0 buf.append("\n");
1842  0 this.meta = buf.toString();
1843  0 setMetaDataDirty(true);
1844    }
1845   
 
1846  243487 toggle public boolean isContentDirty()
1847    {
1848  243484 return this.isContentDirty;
1849    }
1850   
 
1851  4379 toggle public void incrementVersion()
1852    {
1853  4379 if (this.version == null) {
1854  3119 this.version = new Version("1.1");
1855    } else {
1856  1260 if (isMinorEdit()) {
1857  313 this.version = this.version.next();
1858    } else {
1859  947 this.version = this.version.getBranchPoint().next().newBranch(1);
1860    }
1861    }
1862    }
1863   
 
1864  11103012 toggle public void setContentDirty(boolean contentDirty)
1865    {
1866  11102970 this.isContentDirty = contentDirty;
1867    }
1868   
 
1869  235882 toggle public boolean isMetaDataDirty()
1870    {
1871  235882 return this.isMetaDataDirty;
1872    }
1873   
 
1874  61911659 toggle public void setMetaDataDirty(boolean metaDataDirty)
1875    {
1876  61911205 this.isMetaDataDirty = metaDataDirty;
1877    }
1878   
 
1879  12069 toggle public String getAttachmentURL(String filename, XWikiContext context)
1880    {
1881  12069 return getAttachmentURL(filename, "download", context);
1882    }
1883   
 
1884  12070 toggle public String getAttachmentURL(String filename, String action, XWikiContext context)
1885    {
1886  12070 return getAttachmentURL(filename, action, null, context);
1887    }
1888   
 
1889  0 toggle public String getExternalAttachmentURL(String filename, String action, XWikiContext context)
1890    {
1891  0 URL url = context.getURLFactory().createAttachmentURL(filename, getSpace(), getName(), action, null,
1892    getDatabase(), context);
1893  0 return url.toString();
1894    }
1895   
 
1896  12117 toggle public String getAttachmentURL(String filename, String action, String querystring, XWikiContext context)
1897    {
1898    // Attachment file name cannot be empty
1899  12117 if (StringUtils.isEmpty(filename)) {
1900  0 return null;
1901    }
1902   
1903  12117 return context.getWiki().getAttachmentURL(new AttachmentReference(filename, this.getDocumentReference()),
1904    action, querystring, context);
1905    }
1906   
 
1907  0 toggle public String getAttachmentRevisionURL(String filename, String revision, XWikiContext context)
1908    {
1909  0 return getAttachmentRevisionURL(filename, revision, null, context);
1910    }
1911   
 
1912  0 toggle public String getAttachmentRevisionURL(String filename, String revision, String querystring, XWikiContext context)
1913    {
1914    // Attachment file name cannot be empty
1915  0 if (StringUtils.isEmpty(filename)) {
1916  0 return null;
1917    }
1918   
1919  0 return context.getWiki().getAttachmentRevisionURL(new AttachmentReference(filename, getDocumentReference()),
1920    revision, querystring, context);
1921    }
1922   
1923    /**
1924    * @param action the action, see the {@code struts-config.xml} file for a list of all existing action names
1925    * @param params the URL query string
1926    * @param redirect true if the URL is going to be used in {@link HttpServletResponse#sendRedirect(String)}
1927    * @param context the XWiki context
1928    * @return the URL
1929    */
 
1930  229847 toggle public String getURL(String action, String params, boolean redirect, XWikiContext context)
1931    {
1932  229849 URL url =
1933    context.getURLFactory().createURL(getSpace(), getName(), action, params, null, getDatabase(), context);
1934   
1935  229848 if (redirect && isRedirectAbsolute(context)) {
1936  0 if (url == null) {
1937  0 return null;
1938    } else {
1939  0 return url.toString();
1940    }
1941    } else {
1942  229843 return context.getURLFactory().getURL(url, context);
1943    }
1944    }
1945   
 
1946  587 toggle private boolean isRedirectAbsolute(XWikiContext context)
1947    {
1948  587 return StringUtils.equals("1", context.getWiki().Param("xwiki.redirect.absoluteurl"));
1949    }
1950   
 
1951  229260 toggle public String getURL(String action, boolean redirect, XWikiContext context)
1952    {
1953  229262 return getURL(action, null, redirect, context);
1954    }
1955   
 
1956  229260 toggle public String getURL(String action, XWikiContext context)
1957    {
1958  229260 return getURL(action, false, context);
1959    }
1960   
 
1961  288674 toggle public String getURL(String action, String querystring, XWikiContext context)
1962    {
1963  288674 URL url =
1964    context.getURLFactory().createURL(getSpace(), getName(), action, querystring, null, getDatabase(), context);
1965  288674 return context.getURLFactory().getURL(url, context);
1966    }
1967   
 
1968  0 toggle public String getURL(String action, String querystring, String anchor, XWikiContext context)
1969    {
1970  0 URL url = context.getURLFactory().createURL(getSpace(), getName(), action, querystring, anchor, getDatabase(),
1971    context);
1972  0 return context.getURLFactory().getURL(url, context);
1973    }
1974   
 
1975  820 toggle public String getExternalURL(String action, XWikiContext context)
1976    {
1977  820 URL url = context.getURLFactory().createExternalURL(getSpace(), getName(), action, null, null, getDatabase(),
1978    context);
1979  820 return url.toString();
1980    }
1981   
 
1982  220 toggle public String getExternalURL(String action, String querystring, XWikiContext context)
1983    {
1984  220 URL url = context.getURLFactory().createExternalURL(getSpace(), getName(), action, querystring, null,
1985    getDatabase(), context);
1986  220 return url.toString();
1987    }
1988   
 
1989  0 toggle public String getParentURL(XWikiContext context) throws XWikiException
1990    {
1991  0 XWikiDocument doc = new XWikiDocument(getParentReference());
1992  0 URL url = context.getURLFactory().createURL(doc.getSpace(), doc.getName(), "view", null, null, getDatabase(),
1993    context);
1994  0 return context.getURLFactory().getURL(url, context);
1995    }
1996   
 
1997  2881 toggle public XWikiDocumentArchive getDocumentArchive(XWikiContext context) throws XWikiException
1998    {
1999  2881 loadArchive(context);
2000  2881 return getDocumentArchive();
2001    }
2002   
2003    /**
2004    * Create a new protected {@link com.xpn.xwiki.api.Document} public API to access page information and actions from
2005    * scripting.
2006    *
2007    * @param customClassName the name of the custom {@link com.xpn.xwiki.api.Document} class of the object to create.
2008    * @param context the XWiki context.
2009    * @return a wrapped version of an XWikiDocument. Prefer this function instead of new Document(XWikiDocument,
2010    * XWikiContext)
2011    */
 
2012  2330341 toggle public com.xpn.xwiki.api.Document newDocument(String customClassName, XWikiContext context)
2013    {
2014  2330351 if (!((customClassName == null) || (customClassName.equals("")))) {
2015  0 try {
2016  0 return newDocument(Class.forName(customClassName), context);
2017    } catch (ClassNotFoundException e) {
2018  0 LOGGER.error("Failed to get java Class object from class name", e);
2019    }
2020    }
2021   
2022  2330332 return new com.xpn.xwiki.api.Document(this, context);
2023    }
2024   
2025    /**
2026    * Create a new protected {@link com.xpn.xwiki.api.Document} public API to access page information and actions from
2027    * scripting.
2028    *
2029    * @param customClass the custom {@link com.xpn.xwiki.api.Document} class the object to create.
2030    * @param context the XWiki context.
2031    * @return a wrapped version of an XWikiDocument. Prefer this function instead of new Document(XWikiDocument,
2032    * XWikiContext)
2033    */
 
2034  0 toggle public com.xpn.xwiki.api.Document newDocument(Class<?> customClass, XWikiContext context)
2035    {
2036  0 if (customClass != null) {
2037  0 try {
2038  0 Class<?>[] classes = new Class[] { XWikiDocument.class, XWikiContext.class };
2039  0 Object[] args = new Object[] { this, context };
2040   
2041  0 return (com.xpn.xwiki.api.Document) customClass.getConstructor(classes).newInstance(args);
2042    } catch (Exception e) {
2043  0 LOGGER.error("Failed to create a custom Document object", e);
2044    }
2045    }
2046   
2047  0 return new com.xpn.xwiki.api.Document(this, context);
2048    }
2049   
 
2050  2330378 toggle public com.xpn.xwiki.api.Document newDocument(XWikiContext context)
2051    {
2052  2330381 String customClass = getCustomClass();
2053  2330337 return newDocument(customClass, context);
2054    }
2055   
 
2056  3046 toggle public void loadArchive(XWikiContext context) throws XWikiException
2057    {
2058  3046 if ((this.archive == null || this.archive.get() == null)) {
2059  2762 XWikiDocumentArchive arch;
2060    // A document not comming from the database cannot have an archive stored in the database
2061  2762 if (this.isNew()) {
2062  2295 arch = new XWikiDocumentArchive();
2063    } else {
2064  467 arch = getVersioningStore(context).getXWikiDocumentArchive(this, context);
2065    }
2066    // We are using a SoftReference which will allow the archive to be
2067    // discarded by the Garbage collector as long as the context is closed (usually during
2068    // the request)
2069  2762 this.archive = new SoftReference<>(arch);
2070    }
2071    }
2072   
2073    /**
2074    * @return the {@link XWikiDocumentArchive} for this document. If it is not stored in the document, null is
2075    * returned.
2076    */
 
2077  249910 toggle public XWikiDocumentArchive getDocumentArchive()
2078    {
2079    // If there is a soft reference, return it.
2080  249908 if (this.archive != null) {
2081  22468 return this.archive.get();
2082    }
2083  227438 return null;
2084    }
2085   
2086    /**
2087    * @return the {@link XWikiDocumentArchive} for this document. If it is not stored in the document, we get it using
2088    * the current context. If there is an exception, null is returned.
2089    */
 
2090  32 toggle public XWikiDocumentArchive loadDocumentArchive()
2091    {
2092  32 XWikiDocumentArchive arch = getDocumentArchive();
2093  32 if (arch != null) {
2094  29 return arch;
2095    }
2096   
2097    // A document not comming from the database cannot have an archive stored in the database
2098  3 if (this.isNew()) {
2099  0 arch = new XWikiDocumentArchive();
2100  0 setDocumentArchive(arch);
2101  0 return arch;
2102    }
2103   
2104  3 XWikiContext xcontext = getXWikiContext();
2105   
2106  3 try {
2107  3 arch = getVersioningStore(xcontext).getXWikiDocumentArchive(this, xcontext);
2108   
2109    // Put a copy of the archive in the soft reference for later use if needed.
2110  3 setDocumentArchive(arch);
2111   
2112  3 return arch;
2113    } catch (Exception e) {
2114    // VersioningStore.getXWikiDocumentArchive may throw an XWikiException, and xcontext or VersioningStore
2115    // may be null (tests)
2116    // To maintain the behavior of this method we can't throw an exception.
2117    // Formerly, null was returned if there was no SoftReference.
2118  0 LOGGER.warn("Could not get document archive", e);
2119  0 return null;
2120    }
2121    }
2122   
 
2123  231768 toggle public void setDocumentArchive(XWikiDocumentArchive arch)
2124    {
2125    // We are using a SoftReference which will allow the archive to be
2126    // discarded by the Garbage collector as long as the context is closed (usually during the
2127    // request)
2128  231770 if (arch != null) {
2129  13323 this.archive = new SoftReference<XWikiDocumentArchive>(arch);
2130    }
2131    }
2132   
 
2133  188 toggle public void setDocumentArchive(String sarch) throws XWikiException
2134    {
2135  188 XWikiDocumentArchive xda = new XWikiDocumentArchive(getId());
2136  188 xda.setArchive(sarch);
2137  188 setDocumentArchive(xda);
2138    }
2139   
 
2140  2963 toggle public Version[] getRevisions(XWikiContext context) throws XWikiException
2141    {
2142  2963 return getVersioningStore(context).getXWikiDocVersions(this, context);
2143    }
2144   
 
2145  4 toggle public String[] getRecentRevisions(int nb, XWikiContext context) throws XWikiException
2146    {
2147  4 try {
2148  4 Version[] revisions = getVersioningStore(context).getXWikiDocVersions(this, context);
2149  4 int length = nb;
2150    // 0 means all revisions
2151  4 if (nb == 0) {
2152  4 length = revisions.length;
2153    }
2154   
2155  4 if (revisions.length < length) {
2156  0 length = revisions.length;
2157    }
2158   
2159  4 String[] recentrevs = new String[length];
2160  4 for (int i = 1; i <= length; i++) {
2161  0 recentrevs[i - 1] = revisions[revisions.length - i].toString();
2162    }
2163  4 return recentrevs;
2164    } catch (Exception e) {
2165  0 return new String[0];
2166    }
2167    }
2168   
2169    /**
2170    * Get document versions matching criterias like author, minimum creation date, etc.
2171    *
2172    * @param criteria criteria used to match versions
2173    * @return a list of matching versions
2174    */
 
2175  67 toggle public List<String> getRevisions(RevisionCriteria criteria, XWikiContext context) throws XWikiException
2176    {
2177  67 List<String> results = new ArrayList<String>();
2178   
2179  67 Version[] revisions = getRevisions(context);
2180   
2181  67 XWikiRCSNodeInfo nextNodeinfo = null;
2182  67 XWikiRCSNodeInfo nodeinfo;
2183  67 for (Version revision : revisions) {
2184  178 nodeinfo = nextNodeinfo;
2185  178 nextNodeinfo = getRevisionInfo(revision.toString(), context);
2186   
2187  178 if (nodeinfo == null) {
2188  44 continue;
2189    }
2190   
2191    // Minor/Major version matching
2192  134 if (criteria.getIncludeMinorVersions() || !nextNodeinfo.isMinorEdit()) {
2193    // Author matching
2194  44 if (criteria.getAuthor().equals("") || criteria.getAuthor().equals(nodeinfo.getAuthor())) {
2195    // Date range matching
2196  44 Date versionDate = nodeinfo.getDate();
2197  44 if (versionDate.after(criteria.getMinDate()) && versionDate.before(criteria.getMaxDate())) {
2198  44 results.add(nodeinfo.getVersion().toString());
2199    }
2200    }
2201    }
2202    }
2203   
2204  67 nodeinfo = nextNodeinfo;
2205  67 if (nodeinfo != null) {
2206  44 if (criteria.getAuthor().equals("") || criteria.getAuthor().equals(nodeinfo.getAuthor())) {
2207    // Date range matching
2208  44 Date versionDate = nodeinfo.getDate();
2209  44 if (versionDate.after(criteria.getMinDate()) && versionDate.before(criteria.getMaxDate())) {
2210  44 results.add(nodeinfo.getVersion().toString());
2211    }
2212    }
2213    }
2214   
2215  67 return criteria.getRange().subList(results);
2216    }
2217   
 
2218  223 toggle public XWikiRCSNodeInfo getRevisionInfo(String version, XWikiContext context) throws XWikiException
2219    {
2220  223 return getDocumentArchive(context).getNode(new Version(version));
2221    }
2222   
2223    /**
2224    * @return Is this version the most recent one. False if and only if there are newer versions of this document in
2225    * the database.
2226    */
 
2227  235317 toggle public boolean isMostRecent()
2228    {
2229  235323 return this.mostRecent;
2230    }
2231   
2232    /**
2233    * must not be used unless in store system.
2234    *
2235    * @param mostRecent - mark document as most recent.
2236    */
 
2237  246278 toggle public void setMostRecent(boolean mostRecent)
2238    {
2239  246259 this.mostRecent = mostRecent;
2240    }
2241   
2242    /**
2243    * @since 2.2M1
2244    */
 
2245  875835 toggle public BaseClass getXClass()
2246    {
2247  875833 if (this.xClass == null) {
2248  21688 BaseClass emptyClass = new BaseClass();
2249    // Make sure not to cause any false document versions if this document is saved.
2250  21688 emptyClass.setDirty(false);
2251   
2252  21688 this.setXClass(emptyClass);
2253    }
2254  875831 return this.xClass;
2255    }
2256   
2257    /**
2258    * @since 2.2M1
2259    */
 
2260  231007 toggle public void setXClass(BaseClass xwikiClass)
2261    {
2262  231013 xwikiClass.setOwnerDocument(this);
2263   
2264  231028 this.xClass = xwikiClass;
2265    }
2266   
2267    /**
2268    * @since 2.2M1
2269    */
 
2270  5673203 toggle public Map<DocumentReference, List<BaseObject>> getXObjects()
2271    {
2272  5673193 return this.xObjects;
2273    }
2274   
2275    /**
2276    * @since 2.2M1
2277    */
 
2278  172 toggle public void setXObjects(Map<DocumentReference, List<BaseObject>> objects)
2279    {
2280  172 if (objects == null) {
2281    // Make sure we don`t set a null objects map since we assume everywhere that it is not null when using it.
2282  0 objects = new HashMap<>();
2283    }
2284   
2285  172 boolean isDirty = false;
2286   
2287  172 for (List<BaseObject> objList : objects.values()) {
2288  0 for (BaseObject obj : objList) {
2289  0 obj.setOwnerDocument(this);
2290  0 isDirty = true;
2291    }
2292    }
2293   
2294    // This operation resulted in marking the current document dirty.
2295  172 if (isDirty) {
2296  0 setMetaDataDirty(true);
2297    }
2298   
2299    // Replace the current objects with the provided ones.
2300  172 this.xObjects = objects;
2301    }
2302   
2303    /**
2304    * @since 2.2M1
2305    */
 
2306  936 toggle public BaseObject getXObject()
2307    {
2308  936 return getXObject(getDocumentReference());
2309    }
2310   
2311    /**
2312    * @deprecated since 2.2M1 use {@link #getXObject()} instead
2313    */
 
2314  0 toggle @Deprecated
2315    public BaseObject getxWikiObject()
2316    {
2317  0 return getXObject(getDocumentReference());
2318    }
2319   
2320    /**
2321    * @since 2.2M1
2322    */
 
2323  221 toggle public List<BaseClass> getXClasses(XWikiContext context)
2324    {
2325  221 List<BaseClass> list = new ArrayList<BaseClass>();
2326   
2327    // getXObjects() is a TreeMap, with elements sorted by className reference
2328  221 for (DocumentReference classReference : getXObjects().keySet()) {
2329  273 BaseClass bclass = null;
2330  273 List<BaseObject> objects = getXObjects(classReference);
2331  273 for (BaseObject obj : objects) {
2332  281 if (obj != null) {
2333  273 bclass = obj.getXClass(context);
2334  273 if (bclass != null) {
2335  273 break;
2336    }
2337    }
2338    }
2339  273 if (bclass != null) {
2340  273 list.add(bclass);
2341    }
2342    }
2343  221 return list;
2344    }
2345   
2346    /**
2347    * Create and add a new object to the document with the provided class.
2348    * <p>
2349    * Note that absolute reference are not supported for xclasses which mean that the wiki part (whatever the wiki is)
2350    * of the reference will be systematically removed.
2351    *
2352    * @param classReference the reference of the class
2353    * @param context the XWiki context
2354    * @return the index of teh newly created object
2355    * @throws XWikiException error when creating the new object
2356    * @since 2.2.3
2357    */
 
2358  2220 toggle public int createXObject(EntityReference classReference, XWikiContext context) throws XWikiException
2359    {
2360  2220 DocumentReference absoluteClassReference = resolveClassReference(classReference);
2361  2220 BaseObject object = BaseClass.newCustomClassInstance(absoluteClassReference, context);
2362  2220 object.setOwnerDocument(this);
2363  2220 object.setXClassReference(classReference);
2364  2220 List<BaseObject> objects = this.xObjects.get(absoluteClassReference);
2365  2220 if (objects == null) {
2366  2050 objects = new ArrayList<BaseObject>();
2367  2050 this.xObjects.put(absoluteClassReference, objects);
2368    }
2369  2220 objects.add(object);
2370  2220 int nb = objects.size() - 1;
2371  2220 object.setNumber(nb);
2372  2220 setMetaDataDirty(true);
2373  2220 return nb;
2374    }
2375   
2376    /**
2377    * @deprecated since 2.2M1 use {@link #createXObject(EntityReference, XWikiContext)} instead
2378    */
 
2379  276 toggle @Deprecated
2380    public int createNewObject(String className, XWikiContext context) throws XWikiException
2381    {
2382  276 return createXObject(
2383    getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
2384    context);
2385    }
2386   
2387    /**
2388    * @since 2.2M1
2389    */
 
2390  38834 toggle public int getXObjectSize(DocumentReference classReference)
2391    {
2392  38835 try {
2393  38835 return getXObjects().get(classReference).size();
2394    } catch (Exception e) {
2395  14435 return 0;
2396    }
2397    }
2398   
2399    /**
2400    * @since 7.3M1
2401    * @since 7.2.1
2402    * @since 7.1.3
2403    * @since 6.4.6
2404    */
 
2405  0 toggle public int getXObjectSize(EntityReference classReference)
2406    {
2407  0 return getXObjectSize(resolveClassReference(classReference));
2408    }
2409   
2410    /**
2411    * @deprecated since 2.2M1 use {@link #getXObjectSize(DocumentReference)} instead
2412    */
 
2413  0 toggle @Deprecated
2414    public int getObjectNumbers(String className)
2415    {
2416  0 return getXObjectSize(resolveClassReference(className));
2417    }
2418   
2419    /**
2420    * @since 2.2M1
2421    */
 
2422  505572 toggle public List<BaseObject> getXObjects(DocumentReference classReference)
2423    {
2424  505571 if (classReference == null) {
2425  0 return new ArrayList<BaseObject>();
2426    }
2427   
2428  505569 return getXObjects().get(classReference);
2429    }
2430   
2431    /**
2432    * @since 3.3M1
2433    */
 
2434  81518 toggle public List<BaseObject> getXObjects(EntityReference reference)
2435    {
2436  81518 if (reference.getType() == EntityType.DOCUMENT) {
2437    // class reference
2438  81518 return getXObjects(
2439    getCurrentReferenceDocumentReferenceResolver().resolve(reference, getDocumentReference()));
2440    }
2441   
2442  0 return Collections.emptyList();
2443    }
2444   
2445    /**
2446    * @deprecated since 2.2M1 use {@link #getXObjects(DocumentReference)} instead
2447    */
 
2448  62866 toggle @Deprecated
2449    public Vector<BaseObject> getObjects(String className)
2450    {
2451  62861 List<BaseObject> result = this.xObjects.get(resolveClassReference(className));
2452  62863 return result == null ? null : new Vector<BaseObject>(result);
2453    }
2454   
2455    /**
2456    * @since 2.2M1
2457    */
 
2458  1 toggle public void setXObjects(DocumentReference classReference, List<BaseObject> objects)
2459    {
2460    // Remove existing objects
2461  1 List<BaseObject> existingbjects = this.xObjects.get(classReference);
2462  1 if (existingbjects != null) {
2463  1 existingbjects.clear();
2464    }
2465   
2466  1 for (BaseObject obj : objects) {
2467  1 obj.setOwnerDocument(this);
2468    }
2469   
2470    // Add new objects
2471  1 if (objects.isEmpty()) {
2472    // Pretty wrong but can't remove that for retro compatibility reasons...
2473    // Note that it means that someone can put an unmodifiable list here make impossible to add any object of
2474    // this class.
2475  0 this.xObjects.put(classReference, objects);
2476    } else {
2477  1 for (BaseObject baseObject : objects) {
2478  1 addXObject(classReference, baseObject);
2479    }
2480    }
2481   
2482  1 setMetaDataDirty(true);
2483    }
2484   
2485    /**
2486    * @since 3.3M1
2487    */
 
2488  2813671 toggle public BaseObject getXObject(EntityReference reference)
2489    {
2490  2813592 if (reference instanceof DocumentReference) {
2491  0 return getXObject((DocumentReference) reference);
2492  2813614 } else if (reference.getType() == EntityType.DOCUMENT) {
2493    // class reference
2494  2230172 return getXObject(
2495    getCurrentReferenceDocumentReferenceResolver().resolve(reference, getDocumentReference()));
2496  583459 } else if (reference.getType() == EntityType.OBJECT) {
2497    // object reference
2498  583459 return getXObject(getCurrentReferenceObjectReferenceResolver().resolve(reference, getDocumentReference()));
2499    }
2500   
2501  0 return null;
2502    }
2503   
2504    /**
2505    * @since 2.2M1
2506    */
 
2507  2693598 toggle public BaseObject getXObject(DocumentReference classReference)
2508    {
2509  2693592 BaseObject result = null;
2510  2693594 List<BaseObject> objects = getXObjects().get(classReference);
2511  2693556 if (objects != null) {
2512  2173297 for (BaseObject object : objects) {
2513  2173300 if (object != null) {
2514  2173289 result = object;
2515  2173291 break;
2516    }
2517    }
2518    }
2519  2693579 return result;
2520    }
2521   
2522    /**
2523    * Get an object of this document based on its reference.
2524    *
2525    * @param objectReference the reference of the object
2526    * @return the XWiki object
2527    * @since 3.2M1
2528    */
 
2529  612252 toggle public BaseObject getXObject(ObjectReference objectReference)
2530    {
2531  612251 BaseObjectReference baseObjectReference;
2532  612252 if (objectReference instanceof BaseObjectReference) {
2533  28791 baseObjectReference = (BaseObjectReference) objectReference;
2534    } else {
2535  583460 baseObjectReference = new BaseObjectReference(objectReference);
2536    }
2537   
2538    // If the baseObjectReference has an object number, we return the object with this number,
2539    // otherwise, we consider it should be the first object, as specified by BaseObjectReference#getObjectNumber
2540  612254 return baseObjectReference.getObjectNumber() == null ? this.getXObject(baseObjectReference.getXClassReference())
2541    : getXObject(baseObjectReference.getXClassReference(), baseObjectReference.getObjectNumber());
2542    }
2543   
2544    /**
2545    * Get an object property of this document based on its reference.
2546    *
2547    * @param objectPropertyReference the reference of the object property
2548    * @return the object property
2549    * @since 3.2M3
2550    */
 
2551  24276 toggle public BaseProperty<ObjectPropertyReference> getXObjectProperty(ObjectPropertyReference objectPropertyReference)
2552    {
2553  24276 BaseObject object = getXObject((ObjectReference) objectPropertyReference.getParent());
2554   
2555  24276 if (object != null) {
2556  24233 return (BaseProperty<ObjectPropertyReference>) object.getField(objectPropertyReference.getName());
2557    }
2558   
2559  43 return null;
2560    }
2561   
2562    /**
2563    * @deprecated since 2.2M1 use {@link #getXObject(DocumentReference)} instead
2564    */
 
2565  143554 toggle @Deprecated
2566    public BaseObject getObject(String className)
2567    {
2568  143554 return getXObject(resolveClassReference(className));
2569    }
2570   
2571    /**
2572    * @since 2.2M1
2573    */
 
2574  659733 toggle public BaseObject getXObject(DocumentReference classReference, int nb)
2575    {
2576  659732 List<BaseObject> objects = getXObjects().get(classReference);
2577   
2578  659730 if (objects != null && objects.size() > nb) {
2579  644193 return objects.get(nb);
2580    }
2581   
2582  15537 return null;
2583    }
2584   
2585    /**
2586    * Get an xobject with the passed xclass at the passed location.
2587    * <p>
2588    * If <code>create</code> is true and the is no xobject at the passed located, it's created.
2589    *
2590    * @param classReference the xlcass of the object to retrieve
2591    * @param number the location of the xobject
2592    * @param create if true the xobject is created when it does not exist
2593    * @param xcontext the XWiki context
2594    * @return a {@link BaseObject} stored at passed location
2595    * @throws XWikiException when failing to create new xobject instance
2596    * @since 7.3M1
2597    * @since 7.2.1
2598    * @since 7.1.3
2599    * @since 6.4.6
2600    */
 
2601  0 toggle public BaseObject getXObject(EntityReference classReference, int number, boolean create, XWikiContext xcontext)
2602    throws XWikiException
2603    {
2604  0 DocumentReference absoluteClassReference = resolveClassReference(classReference);
2605   
2606  0 BaseObject xobject = getXObject(absoluteClassReference);
2607   
2608  0 if (xobject == null && create) {
2609  0 xobject = BaseClass.newCustomClassInstance(absoluteClassReference, xcontext);
2610   
2611  0 setXObject(number, xobject);
2612    }
2613   
2614  0 return xobject;
2615    }
2616   
2617    /**
2618    * @since 4.1M1
2619    */
 
2620  115 toggle public BaseObject getXObject(EntityReference classReference, int nb)
2621    {
2622  115 return getXObject(
2623    getCurrentReferenceDocumentReferenceResolver().resolve(classReference, getDocumentReference()), nb);
2624    }
2625   
2626    /**
2627    * @deprecated since 2.2M1 use {@link #getXObject(DocumentReference, int)} instead
2628    */
 
2629  31796 toggle @Deprecated
2630    public BaseObject getObject(String className, int nb)
2631    {
2632  31794 return getXObject(resolveClassReference(className), nb);
2633    }
2634   
2635    /**
2636    * @since 2.2M1
2637    */
 
2638  11016 toggle public BaseObject getXObject(DocumentReference classReference, String key, String value)
2639    {
2640  11016 return getXObject(classReference, key, value, false);
2641    }
2642   
2643    /**
2644    * @deprecated since 2.2M1 use {@link #getXObject(DocumentReference, String, String)} instead
2645    */
 
2646  279507 toggle @Deprecated
2647    public BaseObject getObject(String className, String key, String value)
2648    {
2649  279507 return getObject(className, key, value, false);
2650    }
2651   
2652    /**
2653    * @return 6.3M1
2654    */
 
2655  998913 toggle public BaseObject getXObject(EntityReference reference, String key, String value, boolean failover)
2656    {
2657  998905 if (reference instanceof DocumentReference) {
2658  0 return getXObject((DocumentReference) reference, key, value, failover);
2659  998899 } else if (reference.getType() == EntityType.DOCUMENT) {
2660    // class reference
2661  998912 return getXObject(getCurrentReferenceDocumentReferenceResolver().resolve(reference, getDocumentReference()),
2662    key, value, failover);
2663    }
2664   
2665  0 return null;
2666    }
2667   
2668    /**
2669    * @since 2.2M1
2670    */
 
2671  1289551 toggle public BaseObject getXObject(DocumentReference classReference, String key, String value, boolean failover)
2672    {
2673  1289554 try {
2674  1289546 if (value == null) {
2675  0 if (failover) {
2676  0 return getXObject(classReference);
2677    } else {
2678  0 return null;
2679    }
2680    }
2681   
2682  1289547 List<BaseObject> objects = getXObjects().get(classReference);
2683  1289556 if ((objects == null) || (objects.size() == 0)) {
2684  1143657 return null;
2685    }
2686  145891 for (BaseObject obj : objects) {
2687  586657 if (obj != null) {
2688  586627 if (value.equals(obj.getStringValue(key))) {
2689  60146 return obj;
2690    }
2691    }
2692    }
2693   
2694  85744 if (failover) {
2695  3999 return getXObject(classReference);
2696    } else {
2697  81745 return null;
2698    }
2699    } catch (Exception e) {
2700  0 if (failover) {
2701  0 return getXObject(classReference);
2702    }
2703   
2704  0 LOGGER.warn("Exception while accessing objects for document [{}]: {}", getDocumentReference(),
2705    e.getMessage(), e);
2706  0 return null;
2707    }
2708    }
2709   
2710    /**
2711    * @deprecated since 2.2M1 use {@link #getXObject(DocumentReference, String, String, boolean)} instead
2712    */
 
2713  279623 toggle @Deprecated
2714    public BaseObject getObject(String className, String key, String value, boolean failover)
2715    {
2716  279623 return getXObject(resolveClassReference(className), key, value, failover);
2717    }
2718   
2719    /**
2720    * @since 2.2M1
2721    * @deprecated use {@link #addXObject(BaseObject)} instead
2722    */
 
2723  99342 toggle @Deprecated
2724    public void addXObject(DocumentReference classReference, BaseObject object)
2725    {
2726  99342 List<BaseObject> vobj = this.xObjects.get(classReference);
2727  99342 if (vobj == null) {
2728  12562 setXObject(classReference, 0, object);
2729    } else {
2730  86780 setXObject(classReference, vobj.size(), object);
2731    }
2732    }
2733   
2734    /**
2735    * Add the object to the document.
2736    *
2737    * @param object the xobject to add
2738    * @throws NullPointerException if the specified object is null because we need the get the class reference from the
2739    * object
2740    * @since 2.2.3
2741    */
 
2742  568047 toggle public void addXObject(BaseObject object)
2743    {
2744  568044 object.setOwnerDocument(this);
2745   
2746  568046 List<BaseObject> vobj = this.xObjects.get(object.getXClassReference());
2747  568043 if (vobj == null) {
2748  351933 setXObject(0, object);
2749    } else {
2750  216109 setXObject(vobj.size(), object);
2751    }
2752    }
2753   
2754    /**
2755    * @deprecated since 2.2M1 use {@link #addXObject(BaseObject)} instead
2756    */
 
2757  9 toggle @Deprecated
2758    public void addObject(String className, BaseObject object)
2759    {
2760  9 addXObject(resolveClassReference(className), object);
2761    }
2762   
2763    /**
2764    * @since 2.2M1
2765    * @deprecated use {@link #setXObject(int, BaseObject)} instead
2766    */
 
2767  99343 toggle @Deprecated
2768    public void setXObject(DocumentReference classReference, int nb, BaseObject object)
2769    {
2770  99343 if (object != null) {
2771  15 object.setOwnerDocument(this);
2772  15 object.setNumber(nb);
2773    }
2774   
2775  99343 List<BaseObject> objects = this.xObjects.get(classReference);
2776  99343 if (objects == null) {
2777  12563 objects = new ArrayList<BaseObject>();
2778  12563 this.xObjects.put(classReference, objects);
2779    }
2780  198686 while (nb >= objects.size()) {
2781  99343 objects.add(null);
2782    }
2783  99343 objects.set(nb, object);
2784  99343 setMetaDataDirty(true);
2785    }
2786   
2787    /**
2788    * Replaces the object at the specified position and for the specified object's xclass.
2789    *
2790    * @param nb index of the element to replace
2791    * @param object the xobject to insert
2792    * @throws NullPointerException if the specified object is null because we need the get the class reference from the
2793    * object
2794    * @since 2.2.3
2795    */
 
2796  596313 toggle public void setXObject(int nb, BaseObject object)
2797    {
2798  596307 object.setOwnerDocument(this);
2799  596317 object.setNumber(nb);
2800   
2801  596312 List<BaseObject> objects = this.xObjects.get(object.getXClassReference());
2802  596317 if (objects == null) {
2803  372530 objects = new ArrayList<BaseObject>();
2804  372531 this.xObjects.put(object.getXClassReference(), objects);
2805    }
2806  1192726 while (nb >= objects.size()) {
2807  596420 objects.add(null);
2808    }
2809  596313 objects.set(nb, object);
2810  596308 setMetaDataDirty(true);
2811    }
2812   
2813    /**
2814    * @deprecated since 2.2M1 use {@link #setXObject(DocumentReference, int, BaseObject)} instead
2815    */
 
2816  0 toggle @Deprecated
2817    public void setObject(String className, int nb, BaseObject object)
2818    {
2819  0 setXObject(resolveClassReference(className), nb, object);
2820    }
2821   
2822    /**
2823    * @return true if the document is a new one (i.e. it has never been saved) or false otherwise
2824    */
 
2825  9611257 toggle public boolean isNew()
2826    {
2827  9611235 return this.isNew;
2828    }
2829   
 
2830  3055674 toggle public void setNew(boolean aNew)
2831    {
2832  3055613 this.isNew = aNew;
2833    }
2834   
2835    /**
2836    * @since 2.2M1
2837    */
 
2838  0 toggle public void mergeXClass(XWikiDocument templatedoc)
2839    {
2840  0 BaseClass bclass = getXClass();
2841  0 BaseClass tbclass = templatedoc.getXClass();
2842  0 if (tbclass != null) {
2843  0 if (bclass == null) {
2844  0 setXClass(tbclass.clone());
2845    } else {
2846  0 getXClass().merge(tbclass.clone());
2847    }
2848    }
2849  0 setMetaDataDirty(true);
2850    }
2851   
2852    /**
2853    * @deprecated since 2.2M1 use {@link #mergeXClass(XWikiDocument)} instead
2854    */
 
2855  0 toggle @Deprecated
2856    public void mergexWikiClass(XWikiDocument templatedoc)
2857    {
2858  0 mergeXClass(templatedoc);
2859    }
2860   
2861    /**
2862    * @since 2.2M1
2863    */
 
2864  178 toggle public void mergeXObjects(XWikiDocument templateDoc)
2865    {
2866  178 for (Map.Entry<DocumentReference, List<BaseObject>> entry : templateDoc.getXObjects().entrySet()) {
2867    // Documents can't have objects of types defined in a different wiki so we make sure the class reference
2868    // matches this document's wiki.
2869  269 DocumentReference classReference = entry.getKey().replaceParent(entry.getKey().getWikiReference(),
2870    getDocumentReference().getWikiReference());
2871    // Copy the objects from the template document only if this document doesn't have them already.
2872    //
2873    // Note: this might be a bit misleading since it will not add objects from the template if some objects of
2874    // that class already exist in the current document.
2875  269 if (getXObjectSize(classReference) == 0) {
2876  262 for (BaseObject object : entry.getValue()) {
2877  330 if (object != null) {
2878  330 addXObject(object.duplicate());
2879    }
2880    }
2881    }
2882    }
2883    }
2884   
2885    /**
2886    * @deprecated since 2.2M1 use {@link #mergeXObjects(XWikiDocument)} instead
2887    */
 
2888  0 toggle @Deprecated
2889    public void mergexWikiObjects(XWikiDocument templatedoc)
2890    {
2891  0 mergeXObjects(templatedoc);
2892    }
2893   
2894    /**
2895    * @since 2.2M1
2896    */
 
2897  226939 toggle public void cloneXObjects(XWikiDocument templatedoc)
2898    {
2899  226937 cloneXObjects(templatedoc, true);
2900    }
2901   
2902    /**
2903    * @since 2.2.3
2904    */
 
2905  170 toggle public void duplicateXObjects(XWikiDocument templatedoc)
2906    {
2907  170 cloneXObjects(templatedoc, false);
2908    }
2909   
2910    /**
2911    * Copy specified document objects into current document.
2912    *
2913    * @param templatedoc the document to copy
2914    * @param keepsIdentity if true it does an exact java copy, otherwise it duplicate objects with the new document
2915    * name (and new class names)
2916    */
 
2917  227087 toggle private void cloneXObjects(XWikiDocument templatedoc, boolean keepsIdentity)
2918    {
2919    // clean map
2920  227089 this.xObjects.clear();
2921   
2922    // fill map
2923  227100 for (Map.Entry<DocumentReference, List<BaseObject>> entry : templatedoc.getXObjects().entrySet()) {
2924  364181 List<BaseObject> tobjects = entry.getValue();
2925   
2926    // clone and insert xobjects
2927  364180 for (BaseObject otherObject : tobjects) {
2928  666872 if (otherObject != null) {
2929  567547 if (keepsIdentity) {
2930  567432 addXObject(otherObject.clone());
2931    } else {
2932  111 BaseObject newObject = otherObject.duplicate(getDocumentReference());
2933  111 setXObject(newObject.getNumber(), newObject);
2934    }
2935  99329 } else if (keepsIdentity) {
2936    // set null object to make sure to have exactly the same thing when cloning a document
2937  99327 addXObject(entry.getKey(), null);
2938    }
2939    }
2940    }
2941    }
2942   
2943    /**
2944    * @since 2.2M1
2945    */
 
2946  454351 toggle public DocumentReference getTemplateDocumentReference()
2947    {
2948  454365 return this.templateDocumentReference;
2949    }
2950   
2951    /**
2952    * @deprecated since 2.2M1 use {@link #getTemplateDocumentReference()} instead
2953    */
 
2954  0 toggle @Deprecated
2955    public String getTemplate()
2956    {
2957  0 String templateReferenceAsString;
2958  0 DocumentReference templateDocumentReference = getTemplateDocumentReference();
2959  0 if (templateDocumentReference != null) {
2960  0 templateReferenceAsString = LOCAL_REFERENCE_SERIALIZER.serialize(templateDocumentReference);
2961    } else {
2962  0 templateReferenceAsString = "";
2963    }
2964  0 return templateReferenceAsString;
2965    }
2966   
2967    /**
2968    * @since 2.2M1
2969    */
 
2970  227274 toggle public void setTemplateDocumentReference(DocumentReference templateDocumentReference)
2971    {
2972  227275 if (!Objects.equals(getTemplateDocumentReference(), templateDocumentReference)) {
2973  510 this.templateDocumentReference = templateDocumentReference;
2974  510 setMetaDataDirty(true);
2975    }
2976    }
2977   
2978    /**
2979    * @deprecated since 2.2M1 use {@link #setTemplateDocumentReference(DocumentReference)} instead
2980    */
 
2981  0 toggle @Deprecated
2982    public void setTemplate(String template)
2983    {
2984  0 DocumentReference templateReference = null;
2985  0 if (!StringUtils.isEmpty(template)) {
2986  0 templateReference = getCurrentMixedDocumentReferenceResolver().resolve(template);
2987    }
2988  0 setTemplateDocumentReference(templateReference);
2989    }
2990   
 
2991  90 toggle public String displayPrettyName(String fieldname, XWikiContext context)
2992    {
2993  90 return displayPrettyName(fieldname, false, true, context);
2994    }
2995   
 
2996  0 toggle public String displayPrettyName(String fieldname, boolean showMandatory, XWikiContext context)
2997    {
2998  0 return displayPrettyName(fieldname, showMandatory, true, context);
2999    }
3000   
 
3001  207 toggle public String displayPrettyName(String fieldname, boolean showMandatory, boolean before, XWikiContext context)
3002    {
3003  207 try {
3004  207 BaseObject object = getXObject();
3005  207 if (object == null) {
3006  207 object = getFirstObject(fieldname, context);
3007    }
3008  207 return displayPrettyName(fieldname, showMandatory, before, object, context);
3009    } catch (Exception e) {
3010  0 return "";
3011    }
3012    }
3013   
 
3014  2400 toggle public String displayPrettyName(String fieldname, BaseObject obj, XWikiContext context)
3015    {
3016  2400 return displayPrettyName(fieldname, false, true, obj, context);
3017    }
3018   
 
3019  0 toggle public String displayPrettyName(String fieldname, boolean showMandatory, BaseObject obj, XWikiContext context)
3020    {
3021  0 return displayPrettyName(fieldname, showMandatory, true, obj, context);
3022    }
3023   
 
3024  2896 toggle public String displayPrettyName(String fieldname, boolean showMandatory, boolean before, BaseObject obj,
3025    XWikiContext context)
3026    {
3027  2896 try {
3028  2896 PropertyClass pclass = (PropertyClass) obj.getXClass(context).get(fieldname);
3029  2779 String dprettyName = "";
3030  2779 if (showMandatory) {
3031  0 dprettyName = context.getWiki().addMandatory(context);
3032    }
3033  2779 if (before) {
3034  2490 return dprettyName + pclass.getPrettyName(context);
3035    } else {
3036  289 return pclass.getPrettyName(context) + dprettyName;
3037    }
3038    } catch (Exception e) {
3039  117 return "";
3040    }
3041    }
3042   
 
3043  0 toggle public String displayTooltip(String fieldname, XWikiContext context)
3044    {
3045  0 try {
3046  0 BaseObject object = getXObject();
3047  0 if (object == null) {
3048  0 object = getFirstObject(fieldname, context);
3049    }
3050  0 return displayTooltip(fieldname, object, context);
3051    } catch (Exception e) {
3052  0 return "";
3053    }
3054    }
3055   
 
3056  0 toggle public String displayTooltip(String fieldname, BaseObject obj, XWikiContext context)
3057    {
3058  0 String result = "";
3059   
3060  0 try {
3061  0 PropertyClass pclass = (PropertyClass) obj.getXClass(context).get(fieldname);
3062  0 String tooltip = pclass.getTooltip(context);
3063  0 if ((tooltip != null) && (!tooltip.trim().equals(""))) {
3064  0 String img = "<img src=\"" + context.getWiki().getSkinFile("info.gif", context)
3065    + "\" class=\"tooltip_image\" align=\"middle\" />";
3066  0 result = context.getWiki().addTooltip(img, tooltip, context);
3067    }
3068    } catch (Exception e) {
3069   
3070    }
3071   
3072  0 return result;
3073    }
3074   
3075    /**
3076    * @param fieldname the name of the field to display
3077    * @param context the XWiki context
3078    * @return the rendered field
3079    */
 
3080  359 toggle public String display(String fieldname, XWikiContext context)
3081    {
3082  359 String result = "";
3083   
3084  359 try {
3085  359 BaseObject object = getXObject();
3086  359 if (object == null) {
3087  357 object = getFirstObject(fieldname, context);
3088    }
3089   
3090  359 result = display(fieldname, object, context);
3091    } catch (Exception e) {
3092  0 LOGGER.error("Failed to display field [" + fieldname + "] of document ["
3093    + getDefaultEntityReferenceSerializer().serialize(getDocumentReference()) + "]", e);
3094    }
3095   
3096  359 return result;
3097    }
3098   
3099    /**
3100    * @param fieldname the name of the field to display
3101    * @param obj the object containing the field to display
3102    * @param context the XWiki context
3103    * @return the rendered field
3104    */
 
3105  12534 toggle public String display(String fieldname, BaseObject obj, XWikiContext context)
3106    {
3107  12534 String type = null;
3108  12534 try {
3109  12534 type = (String) context.get("display");
3110    } catch (Exception e) {
3111    }
3112   
3113  12534 if (type == null) {
3114  12179 type = "view";
3115    }
3116   
3117  12534 return display(fieldname, type, obj, context);
3118    }
3119   
3120    /**
3121    * @param fieldname the name of the field to display
3122    * @param mode the mode to use ("view", "edit", ...)
3123    * @param context the XWiki context
3124    * @return the rendered field
3125    */
 
3126  357 toggle public String display(String fieldname, String mode, XWikiContext context)
3127    {
3128  357 return display(fieldname, mode, "", context);
3129    }
3130   
3131    /**
3132    * @param fieldname the name of the field to display
3133    * @param type the type of the field to display
3134    * @param obj the object containing the field to display
3135    * @param context the XWiki context
3136    * @return the rendered field
3137    */
 
3138  17501 toggle public String display(String fieldname, String type, BaseObject obj, XWikiContext context)
3139    {
3140  17501 return display(fieldname, type, "", obj, context);
3141    }
3142   
3143    /**
3144    * @param fieldname the name of the field to display
3145    * @param mode the mode to use ("view", "edit", ...)
3146    * @param prefix the prefix to add in the field identifier in edit display for example
3147    * @param context the XWiki context
3148    * @return the rendered field
3149    */
 
3150  357 toggle public String display(String fieldname, String mode, String prefix, XWikiContext context)
3151    {
3152  357 try {
3153  357 BaseObject object = getXObject();
3154  357 if (object == null) {
3155  136 object = getFirstObject(fieldname, context);
3156    }
3157  357 if (object == null) {
3158  26 return "";
3159    } else {
3160  331 return display(fieldname, mode, prefix, object, context);
3161    }
3162    } catch (Exception e) {
3163  0 return "";
3164    }
3165    }
3166   
3167    /**
3168    * @param fieldname the name of the field to display
3169    * @param type the type of the field to display
3170    * @param obj the object containing the field to display
3171    * @param wrappingSyntaxId the syntax of the content in which the result will be included. This to take care of some
3172    * escaping depending of the syntax.
3173    * @param context the XWiki context
3174    * @return the rendered field
3175    */
 
3176  310 toggle public String display(String fieldname, String type, BaseObject obj, String wrappingSyntaxId, XWikiContext context)
3177    {
3178  310 return display(fieldname, type, "", obj, wrappingSyntaxId, context);
3179    }
3180   
3181    /**
3182    * @param fieldname the name of the field to display
3183    * @param type the type of the field to display
3184    * @param pref the prefix to add in the field identifier in edit display for example
3185    * @param obj the object containing the field to display
3186    * @param context the XWiki context
3187    * @return the rendered field
3188    */
 
3189  17832 toggle public String display(String fieldname, String type, String pref, BaseObject obj, XWikiContext context)
3190    {
3191  17832 return display(fieldname, type, pref, obj, context.getWiki().getCurrentContentSyntaxId(getSyntaxId(), context),
3192    context);
3193    }
3194   
3195    /**
3196    * @param fieldname the name of the field to display
3197    * @param type the type of the field to display
3198    * @param pref the prefix to add in the field identifier in edit display for example
3199    * @param obj the object containing the field to display
3200    * @param wrappingSyntaxId the syntax of the content in which the result will be included. This to take care of some
3201    * escaping depending of the syntax.
3202    * @param context the XWiki context
3203    * @return the rendered field
3204    */
 
3205  18142 toggle public String display(String fieldname, String type, String pref, BaseObject obj, String wrappingSyntaxId,
3206    XWikiContext context)
3207    {
3208  18142 if (obj == null) {
3209  164 return "";
3210    }
3211   
3212  17978 boolean isInRenderingEngine = BooleanUtils.toBoolean((Boolean) context.get("isInRenderingEngine"));
3213  17978 HashMap<String, Object> backup = new HashMap<String, Object>();
3214  17978 try {
3215  17978 backupContext(backup, context);
3216  17978 setAsContextDoc(context);
3217   
3218    // Make sure to execute with the right of the document author instead of the content author
3219    // (because modifying object property does not modify content author)
3220  17978 XWikiDocument sdoc = context.getDoc();
3221  17978 if (sdoc != null && !Objects.equals(sdoc.getContentAuthorReference(), sdoc.getAuthorReference())) {
3222    // Hack the sdoc to make test module believe the content author is the author
3223  3945 sdoc = sdoc.clone();
3224  3945 sdoc.setContentAuthorReference(sdoc.getAuthorReference());
3225  3945 context.put(CKEY_SDOC, sdoc);
3226    }
3227   
3228  17978 type = type.toLowerCase();
3229  17978 StringBuffer result = new StringBuffer();
3230  17978 PropertyClass pclass = (PropertyClass) obj.getXClass(context).get(fieldname);
3231  17978 String prefix = pref + LOCAL_REFERENCE_SERIALIZER.serialize(obj.getXClass(context).getDocumentReference())
3232    + "_" + obj.getNumber() + "_";
3233   
3234  17978 if (pclass == null) {
3235  44 return "";
3236  17934 } else if (pclass.isCustomDisplayed(context)) {
3237  787 pclass.displayCustom(result, fieldname, prefix, type, obj, context);
3238  17147 } else if (type.equals("view")) {
3239  13056 pclass.displayView(result, fieldname, prefix, obj, context);
3240  4091 } else if (type.equals("rendered")) {
3241  0 String fcontent = pclass.displayView(fieldname, prefix, obj, context);
3242    // This mode is deprecated for the new rendering and should also be removed for the old rendering
3243    // since the way to implement this now is to choose the type of rendering to do in the class itself.
3244    // Thus for the new rendering we simply make this mode work like the "view" mode.
3245  0 if (is10Syntax(wrappingSyntaxId)) {
3246  0 result.append(getRenderedContent(fcontent, getSyntaxId(), context));
3247    } else {
3248  0 result.append(fcontent);
3249    }
3250  4091 } else if (type.equals("edit")) {
3251  3964 context.addDisplayedField(fieldname);
3252    // If the Syntax id is "xwiki/1.0" then use the old rendering subsystem and prevent wiki syntax
3253    // rendering using the pre macro. In the new rendering system it's the XWiki Class itself that does the
3254    // escaping. For example for a textarea check the TextAreaClass class.
3255  3964 if (is10Syntax(wrappingSyntaxId)) {
3256    // Don't use pre when not in the rendernig engine since for template we don't evaluate wiki syntax.
3257  1 if (isInRenderingEngine) {
3258  1 result.append("{pre}");
3259    }
3260    }
3261  3964 pclass.displayEdit(result, fieldname, prefix, obj, context);
3262  3964 if (is10Syntax(wrappingSyntaxId)) {
3263  1 if (isInRenderingEngine) {
3264  1 result.append("{/pre}");
3265    }
3266    }
3267  127 } else if (type.equals("hidden")) {
3268    // If the Syntax id is "xwiki/1.0" then use the old rendering subsystem and prevent wiki syntax
3269    // rendering using the pre macro. In the new rendering system it's the XWiki Class itself that does the
3270    // escaping. For example for a textarea check the TextAreaClass class.
3271  127 if (is10Syntax(wrappingSyntaxId) && isInRenderingEngine) {
3272  0 result.append("{pre}");
3273    }
3274  127 pclass.displayHidden(result, fieldname, prefix, obj, context);
3275  127 if (is10Syntax(wrappingSyntaxId) && isInRenderingEngine) {
3276  0 result.append("{/pre}");
3277    }
3278  0 } else if (type.equals("search")) {
3279    // Backward compatibility
3280   
3281    // Check if the method has been injected using aspects
3282  0 Method searchMethod = null;
3283  0 for (Method method : pclass.getClass().getMethods()) {
3284  0 if (method.getName().equals("displaySearch") && method.getParameterTypes().length == 5) {
3285  0 searchMethod = method;
3286  0 break;
3287    }
3288    }
3289   
3290  0 if (searchMethod != null) {
3291    // If the Syntax id is "xwiki/1.0" then use the old rendering subsystem and prevent wiki syntax
3292    // rendering using the pre macro. In the new rendering system it's the XWiki Class itself that does
3293    // the
3294    // escaping. For example for a textarea check the TextAreaClass class.
3295  0 if (is10Syntax(wrappingSyntaxId) && isInRenderingEngine) {
3296  0 result.append("{pre}");
3297    }
3298  0 prefix = LOCAL_REFERENCE_SERIALIZER.serialize(obj.getXClass(context).getDocumentReference()) + "_";
3299  0 searchMethod.invoke(pclass, result, fieldname, prefix, context.get("query"), context);
3300  0 if (is10Syntax(wrappingSyntaxId) && isInRenderingEngine) {
3301  0 result.append("{/pre}");
3302    }
3303    } else {
3304  0 pclass.displayView(result, fieldname, prefix, obj, context);
3305    }
3306    } else {
3307  0 pclass.displayView(result, fieldname, prefix, obj, context);
3308    }
3309   
3310    // If we're in new rendering engine we want to wrap the HTML returned by displayView() in
3311    // a {{html/}} macro so that the user doesn't have to do it.
3312    // We test if we're inside the rendering engine since it's also possible that this display() method is
3313    // called
3314    // directly from a template and in this case we only want HTML as a result and not wiki syntax.
3315    // TODO: find a more generic way to handle html macro because this works only for XWiki 1.0 and XWiki 2.0
3316    // Add the {{html}}{{/html}} only when result really contains html since it's not needed for pure text
3317  17934 if (isInRenderingEngine && !is10Syntax(wrappingSyntaxId)
3318    && (result.indexOf("<") != -1 || result.indexOf(">") != -1)) {
3319  3949 result.insert(0, "{{html clean=\"false\" wiki=\"false\"}}");
3320  3949 result.append("{{/html}}");
3321    }
3322   
3323  17934 return result.toString();
3324    } catch (Exception ex) {
3325    // TODO: It would better to check if the field exists rather than catching an exception
3326    // raised by a NPE as this is currently the case here...
3327  0 LOGGER.warn("Failed to display field [" + fieldname + "] in [" + type + "] mode for Object of Class ["
3328    + getDefaultEntityReferenceSerializer().serialize(obj.getDocumentReference()) + "]", ex);
3329  0 return "";
3330    } finally {
3331  17978 restoreContext(backup, context);
3332    }
3333    }
3334   
3335    /**
3336    * @since 2.2M1
3337    */
 
3338  0 toggle public String displayForm(DocumentReference classReference, String header, String format, XWikiContext context)
3339    {
3340  0 return displayForm(classReference, header, format, true, context);
3341    }
3342   
3343    /**
3344    * @deprecated since 2.2M1, use {@link #displayForm(DocumentReference, String, String, XWikiContext)} instead
3345    */
 
3346  0 toggle @Deprecated
3347    public String displayForm(String className, String header, String format, XWikiContext context)
3348    {
3349  0 return displayForm(className, header, format, true, context);
3350    }
3351   
3352    /**
3353    * @since 2.2M1
3354    */
 
3355  0 toggle public String displayForm(DocumentReference classReference, String header, String format, boolean linebreak,
3356    XWikiContext context)
3357    {
3358  0 List<BaseObject> objects = getXObjects(classReference);
3359  0 if (format.endsWith("\\n")) {
3360  0 linebreak = true;
3361    }
3362   
3363  0 BaseObject firstobject = null;
3364  0 Iterator<BaseObject> foit = objects.iterator();
3365  0 while ((firstobject == null) && foit.hasNext()) {
3366  0 firstobject = foit.next();
3367    }
3368   
3369  0 if (firstobject == null) {
3370  0 return "";
3371    }
3372   
3373  0 BaseClass bclass = firstobject.getXClass(context);
3374  0 if (bclass.getPropertyList().size() == 0) {
3375  0 return "";
3376    }
3377   
3378  0 StringBuilder result = new StringBuilder();
3379  0 VelocityContext vcontext = new VelocityContext();
3380  0 for (String propertyName : bclass.getPropertyList()) {
3381  0 PropertyClass pclass = (PropertyClass) bclass.getField(propertyName);
3382  0 vcontext.put(pclass.getName(), pclass.getPrettyName());
3383    }
3384  0 result.append(evaluate(header, context.getDoc().getPrefixedFullName(), vcontext, context));
3385  0 if (linebreak) {
3386  0 result.append("\n");
3387    }
3388   
3389    // display each line
3390  0 for (int i = 0; i < objects.size(); i++) {
3391  0 vcontext.put("id", Integer.valueOf(i + 1));
3392  0 BaseObject object = objects.get(i);
3393  0 if (object != null) {
3394  0 for (String name : bclass.getPropertyList()) {
3395  0 vcontext.put(name, display(name, object, context));
3396    }
3397  0 result.append(evaluate(format, context.getDoc().getPrefixedFullName(), vcontext, context));
3398  0 if (linebreak) {
3399  0 result.append("\n");
3400    }
3401    }
3402    }
3403  0 return result.toString();
3404    }
3405   
 
3406  0 toggle private String evaluate(String content, String name, VelocityContext vcontext, XWikiContext context)
3407    {
3408  0 StringWriter writer = new StringWriter();
3409  0 try {
3410  0 VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
3411  0 velocityManager.getVelocityEngine().evaluate(vcontext, writer, name, content);
3412  0 return writer.toString();
3413    } catch (Exception e) {
3414  0 LOGGER.error("Error while parsing velocity template namespace [{}]", name, e);
3415  0 Object[] args = { name };
3416  0 XWikiException xe = new XWikiException(XWikiException.MODULE_XWIKI_RENDERING,
3417    XWikiException.ERROR_XWIKI_RENDERING_VELOCITY_EXCEPTION, "Error while parsing velocity page {0}", e,
3418    args);
3419  0 return Util.getHTMLExceptionMessage(xe, context);
3420    }
3421    }
3422   
3423    /**
3424    * @deprecated since 2.2M1, use {@link #displayForm(DocumentReference, String, String, boolean, XWikiContext)}
3425    * instead
3426    */
 
3427  0 toggle @Deprecated
3428    public String displayForm(String className, String header, String format, boolean linebreak, XWikiContext context)
3429    {
3430  0 return displayForm(resolveClassReference(className), header, format, linebreak, context);
3431    }
3432   
3433    /**
3434    * @since 2.2M1
3435    */
 
3436  0 toggle public String displayForm(DocumentReference classReference, XWikiContext context)
3437    {
3438  0 List<BaseObject> objects = getXObjects(classReference);
3439  0 if (objects == null) {
3440  0 return "";
3441    }
3442   
3443  0 BaseObject firstobject = null;
3444  0 Iterator<BaseObject> foit = objects.iterator();
3445  0 while ((firstobject == null) && foit.hasNext()) {
3446  0 firstobject = foit.next();
3447    }
3448   
3449  0 if (firstobject == null) {
3450  0 return "";
3451    }
3452   
3453  0 BaseClass bclass = firstobject.getXClass(context);
3454  0 if (bclass.getPropertyList().size() == 0) {
3455  0 return "";
3456    }
3457   
3458  0 StringBuilder result = new StringBuilder();
3459  0 result.append("{table}\n");
3460  0 boolean first = true;
3461  0 for (String propertyName : bclass.getPropertyList()) {
3462  0 if (first == true) {
3463  0 first = false;
3464    } else {
3465  0 result.append("|");
3466    }
3467  0 PropertyClass pclass = (PropertyClass) bclass.getField(propertyName);
3468  0 result.append(pclass.getPrettyName());
3469    }
3470  0 result.append("\n");
3471  0 for (int i = 0; i < objects.size(); i++) {
3472  0 BaseObject object = objects.get(i);
3473  0 if (object != null) {
3474  0 first = true;
3475  0 for (String propertyName : bclass.getPropertyList()) {
3476  0 if (first == true) {
3477  0 first = false;
3478    } else {
3479  0 result.append("|");
3480    }
3481  0 String data = display(propertyName, object, context);
3482  0 data = data.trim();
3483  0 data = data.replaceAll("\n", " ");
3484  0 if (data.length() == 0) {
3485  0 result.append("&nbsp;");
3486    } else {
3487  0 result.append(data);
3488    }
3489    }
3490  0 result.append("\n");
3491    }
3492    }
3493  0 result.append("{table}\n");
3494  0 return result.toString();
3495    }
3496   
3497    /**
3498    * @deprecated since 2.2M1, use {@link #displayForm(DocumentReference, XWikiContext)} instead
3499    */
 
3500  0 toggle @Deprecated
3501    public String displayForm(String className, XWikiContext context)
3502    {
3503  0 return displayForm(resolveClassReference(className), context);
3504    }
3505   
 
3506  227106 toggle public boolean isFromCache()
3507    {
3508  227107 return this.fromCache;
3509    }
3510   
 
3511  7973683 toggle public void setFromCache(boolean fromCache)
3512    {
3513  7973674 this.fromCache = fromCache;
3514    }
3515   
 
3516  1212 toggle public void readDocMetaFromForm(EditForm eform, XWikiContext context) throws XWikiException
3517    {
3518  1212 String defaultLanguage = eform.getDefaultLanguage();
3519  1212 if (defaultLanguage != null) {
3520  26 setDefaultLanguage(defaultLanguage);
3521    }
3522   
3523  1212 String defaultTemplate = eform.getDefaultTemplate();
3524  1212 if (defaultTemplate != null) {
3525  0 setDefaultTemplate(defaultTemplate);
3526    }
3527   
3528  1212 String creator = eform.getCreator();
3529  1212 if ((creator != null) && (!creator.equals(getCreator()))) {
3530  0 if ((getCreatorReference().equals(context.getUserReference()))
3531    || (context.getWiki().getRightService().hasAdminRights(context))) {
3532  0 setCreator(creator);
3533    }
3534    }
3535   
3536  1212 String parent = eform.getParent();
3537  1212 if (parent != null) {
3538  284 setParent(parent);
3539    }
3540   
3541    // Read the comment from the form
3542  1212 String comment = eform.getComment();
3543  1212 if (comment != null) {
3544  270 setComment(comment);
3545    }
3546   
3547    // Read the minor edit checkbox from the form
3548  1212 setMinorEdit(eform.isMinorEdit());
3549   
3550  1212 String tags = eform.getTags();
3551  1212 if (!StringUtils.isEmpty(tags)) {
3552  1 setTags(tags, context);
3553    }
3554   
3555    // Set the Syntax id if defined
3556  1212 String syntaxId = eform.getSyntaxId();
3557  1212 if (syntaxId != null) {
3558  78 setSyntaxId(syntaxId);
3559    }
3560   
3561    // Read the hidden checkbox from the form
3562  1212 if (eform.getHidden() != null) {
3563  80 setHidden("1".equals(eform.getHidden()));
3564    }
3565    }
3566   
3567    /**
3568    * add tags to the document.
3569    */
 
3570  1 toggle public void setTags(String tagsStr, XWikiContext context) throws XWikiException
3571    {
3572  1 BaseClass tagsClass = context.getWiki().getTagClass(context);
3573   
3574  1 StaticListClass tagProp = (StaticListClass) tagsClass.getField(XWikiConstant.TAG_CLASS_PROP_TAGS);
3575   
3576  1 BaseObject tags = getObject(XWikiConstant.TAG_CLASS, true, context);
3577   
3578  1 tags.safeput(XWikiConstant.TAG_CLASS_PROP_TAGS, tagProp.fromString(tagsStr));
3579   
3580  1 setMetaDataDirty(true);
3581    }
3582   
 
3583  11890 toggle public String getTags(XWikiContext context)
3584    {
3585  11890 ListProperty prop = (ListProperty) getTagProperty(context);
3586   
3587    // I don't know why we need to XML-escape the list of tags but for backwards compatibility we need to keep doing
3588    // this. When this method was added it was using ListProperty#getTextValue() which used to return
3589    // ListProperty#toFormString() before we fixed it to return the unescaped value because we need to save the raw
3590    // value in the database and ListProperty#getTextValue() is called when the list property is saved.
3591  11890 return prop != null ? prop.toFormString() : "";
3592    }
3593   
 
3594  419 toggle public List<String> getTagsList(XWikiContext context)
3595    {
3596  419 List<String> tagList = null;
3597   
3598  419 BaseProperty prop = getTagProperty(context);
3599  419 if (prop != null) {
3600  4 tagList = (List<String>) prop.getValue();
3601    }
3602   
3603  419 return tagList;
3604    }
3605   
 
3606  12309 toggle private BaseProperty getTagProperty(XWikiContext context)
3607    {
3608  12309 BaseObject tags = getObject(XWikiConstant.TAG_CLASS);
3609   
3610  12309 return tags != null ? ((BaseProperty) tags.safeget(XWikiConstant.TAG_CLASS_PROP_TAGS)) : null;
3611    }
3612   
 
3613  0 toggle public List<String> getTagsPossibleValues(XWikiContext context)
3614    {
3615  0 List<String> list;
3616   
3617  0 try {
3618  0 BaseClass tagsClass = context.getWiki().getTagClass(context);
3619   
3620  0 String possibleValues =
3621    ((StaticListClass) tagsClass.getField(XWikiConstant.TAG_CLASS_PROP_TAGS)).getValues();
3622   
3623  0 return ListClass.getListFromString(possibleValues);
3624    } catch (XWikiException e) {
3625  0 LOGGER.error("Failed to get tag class", e);
3626   
3627  0 list = Collections.emptyList();
3628    }
3629   
3630  0 return list;
3631    }
3632   
 
3633  569 toggle public void readTranslationMetaFromForm(EditForm eform, XWikiContext context) throws XWikiException
3634    {
3635  569 String content = eform.getContent();
3636  569 if (content != null) {
3637    // Cleanup in case we use HTMLAREA
3638    // content = context.getUtil().substitute("s/<br class=\\\"htmlarea\\\"\\/>/\\r\\n/g",
3639    // content);
3640  209 content = context.getUtil().substitute("s/<br class=\"htmlarea\" \\/>/\r\n/g", content);
3641  209 setContent(content);
3642    }
3643  569 String title = eform.getTitle();
3644  569 if (title != null) {
3645  221 setTitle(title);
3646    }
3647    }
3648   
 
3649  1054 toggle public void readObjectsFromForm(EditForm eform, XWikiContext context) throws XWikiException
3650    {
3651  1054 for (DocumentReference reference : getXObjects().keySet()) {
3652  979 List<BaseObject> oldObjects = getXObjects(reference);
3653  979 List<BaseObject> newObjects = new ArrayList<BaseObject>();
3654  2252 while (newObjects.size() < oldObjects.size()) {
3655  1273 newObjects.add(null);
3656    }
3657  2252 for (int i = 0; i < oldObjects.size(); i++) {
3658  1273 BaseObject oldobject = oldObjects.get(i);
3659  1273 if (oldobject != null) {
3660  1269 BaseClass baseclass = oldobject.getXClass(context);
3661  1269 BaseObject newobject = (BaseObject) baseclass.fromMap(
3662    eform.getObject(
3663    LOCAL_REFERENCE_SERIALIZER.serialize(baseclass.getDocumentReference()) + "_" + i),
3664    oldobject);
3665  1269 newobject.setNumber(oldobject.getNumber());
3666  1269 newobject.setGuid(oldobject.getGuid());
3667  1269 newobject.setOwnerDocument(this);
3668  1269 newObjects.set(newobject.getNumber(), newobject);
3669    }
3670    }
3671  979 getXObjects().put(reference, newObjects);
3672    }
3673    }
3674   
3675    /**
3676    * Generate a map from the request parameters of the form '<spacename>.<classname>_<number>_<propertyname>' Keys of
3677    * this map will be the reference '<spacename>.<classname>' to the Class (for example, 'XWiki.XWikiRights'), the
3678    * content is a list where each element describe property for the object <number>. Element of the list is a map
3679    * where key is <propertyname> and content is the array of corresponding values. Example with a list of HTTP
3680    * parameters:
3681    * <ul>
3682    * <li>XWiki.XWikiRights_0_users=XWiki.Admin</li>
3683    * <li>XWiki.XWikiRights_0_users=XWiki.Me</li>
3684    * <li>XWiki.XWikiRights_0_groups=XWiki.XWikiAllGroup</li>
3685    * <li>XWiki.XWikiRights_1_user=XWiki.Admin</li>
3686    * <li>XWiki.XWikiUsers_1_name=Spirou</li>
3687    * </ul>
3688    * will result in the following map <code><pre>
3689    * {
3690    * "XWiki.XWikiRights": {
3691    * "0": {
3692    * "users": ["XWiki.Admin", "XWiki.Me"],
3693    * "groups": ["XWiki.XWikiAllGroup"]
3694    * },
3695    * "1": {
3696    * "users": ["XWiki.Admin"]
3697    * }
3698    * ],
3699    * "XWiki.XWikiUsers":
3700    * "1": {
3701    * "name": ["Spirou"]
3702    * }
3703    * ]
3704    * }
3705    * </pre></code>
3706    *
3707    * @param request is the input HTTP request that provides the parameters
3708    * @param context
3709    * @return a map containing ordered data
3710    */
 
3711  160 toggle private Map<DocumentReference, SortedMap<Integer, Map<String, String[]>>> parseRequestUpdateOrCreate(
3712    XWikiRequest request, XWikiContext context)
3713    {
3714  160 Map<DocumentReference, SortedMap<Integer, Map<String, String[]>>> result = new HashMap<>();
3715  160 @SuppressWarnings("unchecked")
3716    Map<String, String[]> allParameters = request.getParameterMap();
3717  160 for (Entry<String, String[]> parameter : allParameters.entrySet()) {
3718  661 Matcher matcher = XPROPERTY_REFERENCE_PATTERN.matcher(parameter.getKey());
3719  661 if (matcher.matches() == false) {
3720  489 continue;
3721    }
3722  172 Integer classNumber;
3723  172 String className = matcher.group(1);
3724  172 String classNumberAsString = matcher.group(2);
3725  172 String classPropertyName = matcher.group(3);
3726  172 DocumentReference classReference = getCurrentDocumentReferenceResolver().resolve(className);
3727  172 try {
3728  172 BaseClass xClass = context.getWiki().getDocument(classReference, context).getXClass();
3729  172 if (xClass.getPropertyList().contains(classPropertyName) == false) {
3730  2 continue;
3731    }
3732  170 classNumber = Integer.parseInt(classNumberAsString);
3733    } catch (XWikiException e) {
3734    // If the class page cannot be found, skip the property update
3735  0 LOGGER.warn("Failed to load document [{}], ignoring property update [{}]. Reason: [{}]", classReference,
3736    parameter.getKey(), ExceptionUtils.getRootCauseMessage(e));
3737  0 continue;
3738    } catch (NumberFormatException e) {
3739    // If the numner isn't valid, skip the property update
3740  1 LOGGER.warn("Invalid xobject number [{}], ignoring property update [{}].", classNumberAsString,
3741    parameter.getKey());
3742  1 continue;
3743    }
3744  169 SortedMap<Integer, Map<String, String[]>> objectMap = result.get(classReference);
3745  169 if (objectMap == null) {
3746  150 objectMap = new TreeMap<>();
3747  150 result.put(classReference, objectMap);
3748    }
3749    // Get the property from the right object #objectNumber of type 'objectName'; create it if they don't exist
3750  169 Map<String, String[]> object = objectMap.get(classNumber);
3751  169 if (object == null) {
3752  153 object = new HashMap<>();
3753  153 objectMap.put(classNumber, object);
3754    }
3755  169 object.put(classPropertyName, parameter.getValue());
3756    }
3757  160 return result;
3758    }
3759   
3760    /**
3761    * Create and/or update objects in a document given a list of HTTP parameters of the form {@code
3762    * <spacename>.<classname>_<number>_<propertyname>}. If the object already exists, the field is replace by the given
3763    * value. If the object doesn't exist in the document, it is created then the property {@code <propertyname>} is
3764    * initialized with the given value. An object is only created if the given {@code <number>} is 'one-more' than the
3765    * existing number of objects. For example, if the document already has 2 objects of type {@code Space.Class}, then
3766    * it will create a new object only with {@code Space.Class_2_prop=something}. Every other parameter like {@code
3767    * Space.Class_42_prop=foobar} for example, will be ignore.
3768    *
3769    * @param eform is form information that contains all the query parameters
3770    * @param context
3771    * @throws XWikiException
3772    * @since 7.1M1
3773    */
 
3774  160 toggle public void readObjectsFromFormUpdateOrCreate(EditForm eform, XWikiContext context) throws XWikiException
3775    {
3776  160 Map<DocumentReference, SortedMap<Integer, Map<String, String[]>>> fromRequest =
3777    parseRequestUpdateOrCreate(eform.getRequest(), context);
3778  160 for (Entry<DocumentReference, SortedMap<Integer, Map<String, String[]>>> requestClassEntries : fromRequest
3779    .entrySet()) {
3780  150 DocumentReference requestClassReference = requestClassEntries.getKey();
3781  150 SortedMap<Integer, Map<String, String[]>> requestObjectMap = requestClassEntries.getValue();
3782  150 for (Entry<Integer, Map<String, String[]>> requestObjectEntry : requestObjectMap.entrySet()) {
3783  153 Integer requestObjectNumber = requestObjectEntry.getKey();
3784  153 Map<String, String[]> requestObjectPropertyMap = requestObjectEntry.getValue();
3785  153 BaseObject oldObject = getXObject(requestClassReference, requestObjectNumber);
3786  153 if (oldObject == null) {
3787    // Create the object only if it has been numbered one more than the number of existing objects
3788  5 if (requestObjectPropertyMap != null) {
3789  5 oldObject = newXObject(requestClassReference, context);
3790    } else {
3791  0 break;
3792    }
3793    }
3794  153 BaseClass baseClass = oldObject.getXClass(context);
3795  153 BaseObject newObject = (BaseObject) baseClass.fromMap(requestObjectPropertyMap, oldObject);
3796  153 newObject.setNumber(oldObject.getNumber());
3797  153 newObject.setGuid(oldObject.getGuid());
3798  153 newObject.setOwnerDocument(this);
3799  153 setXObject(requestObjectNumber, newObject);
3800    }
3801    }
3802    }
3803   
 
3804  569 toggle public void readFromForm(EditForm eform, XWikiContext context) throws XWikiException
3805    {
3806  569 readDocMetaFromForm(eform, context);
3807  569 readTranslationMetaFromForm(eform, context);
3808  569 ObjectPolicyType objectPolicy = eform.getObjectPolicy();
3809  569 if (objectPolicy == null || objectPolicy.equals(ObjectPolicyType.UPDATE)) {
3810  410 readObjectsFromForm(eform, context);
3811  159 } else if (objectPolicy.equals(ObjectPolicyType.UPDATE_OR_CREATE)) {
3812  159 readObjectsFromFormUpdateOrCreate(eform, context);
3813    }
3814    }
3815   
 
3816  1047 toggle public void readFromTemplate(EditForm eform, XWikiContext context) throws XWikiException
3817    {
3818  1047 String template = eform.getTemplate();
3819  1047 readFromTemplate(template, context);
3820    }
3821   
3822    /**
3823    * @since 2.2M1
3824    */
 
3825  1610 toggle public void readFromTemplate(DocumentReference templateDocumentReference, XWikiContext context)
3826    throws XWikiException
3827    {
3828  1610 if (templateDocumentReference != null) {
3829  175 String content = getContent();
3830  175 if (!content.equals("\n") && !content.equals("") && !isNew()) {
3831  0 Object[] args = { getDefaultEntityReferenceSerializer().serialize(getDocumentReference()) };
3832  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
3833    XWikiException.ERROR_XWIKI_APP_DOCUMENT_NOT_EMPTY,
3834    "Cannot add a template to document {0} because it already has content", null, args);
3835    } else {
3836  175 XWiki xwiki = context.getWiki();
3837  175 XWikiDocument templatedoc = xwiki.getDocument(templateDocumentReference, context);
3838  175 if (templatedoc.isNew()) {
3839  0 Object[] args = { getDefaultEntityReferenceSerializer().serialize(templateDocumentReference),
3840    getCompactEntityReferenceSerializer().serialize(getDocumentReference()) };
3841  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
3842    XWikiException.ERROR_XWIKI_APP_TEMPLATE_DOES_NOT_EXIST,
3843    "Template document {0} does not exist when adding to document {1}", null, args);
3844    } else {
3845  175 setTemplateDocumentReference(templateDocumentReference);
3846  175 setTitle(templatedoc.getTitle());
3847  175 setContent(templatedoc.getContent());
3848   
3849    // Set the new document syntax as the syntax of the template since the template content
3850    // is copied into the new document
3851  175 setSyntax(templatedoc.getSyntax());
3852   
3853    // If the parent is not set in the current document set the template parent as the parent.
3854  175 if (getParentReference() == null) {
3855  82 setParentReference(templatedoc.getRelativeParentReference());
3856    }
3857   
3858  175 if (isNew()) {
3859    // We might have received the objects from the cache and the template objects might have been
3860    // copied already we need to remove them
3861  172 setXObjects(new TreeMap<DocumentReference, List<BaseObject>>());
3862    }
3863    // Merge the external objects.
3864    // Currently the choice is not to merge the base class and object because it is not the preferred
3865    // way of using external classes and objects.
3866  175 mergeXObjects(templatedoc);
3867   
3868    // Copy the attachments from the template document, but don't overwrite existing attachments because
3869    // the user can add attachments from the WYSIWYG editor before the save button is clicked (and thus
3870    // before the template is applied).
3871  175 copyAttachments(templatedoc, false);
3872    }
3873    }
3874    }
3875    }
3876   
3877    /**
3878    * @deprecated since 2.2M1 use {@link #readFromTemplate(DocumentReference, XWikiContext)} instead
3879    */
 
3880  1607 toggle @Deprecated
3881    public void readFromTemplate(String template, XWikiContext context) throws XWikiException
3882    {
3883    // Keep the same behavior for backward compatibility
3884  1607 DocumentReference templateDocumentReference = null;
3885  1607 if (StringUtils.isNotEmpty(template)) {
3886  172 templateDocumentReference = getCurrentMixedDocumentReferenceResolver().resolve(template);
3887    }
3888  1607 readFromTemplate(templateDocumentReference, context);
3889    }
3890   
3891    /**
3892    * Use the document passed as parameter as the new identity for the current document.
3893    *
3894    * @param document the document containing the new identity
3895    * @throws XWikiException in case of error
3896    */
 
3897  2 toggle private void clone(XWikiDocument document)
3898    {
3899  2 this.id = document.id;
3900   
3901  2 setDocumentReference(document.getDocumentReference());
3902  2 setRCSVersion(document.getRCSVersion());
3903  2 setDocumentArchive(document.getDocumentArchive());
3904  2 setAuthorReference(document.getAuthorReference());
3905  2 setContentAuthorReference(document.getContentAuthorReference());
3906  2 setContent(document.getContent());
3907  2 setCreationDate(document.getCreationDate());
3908  2 setDate(document.getDate());
3909  2 setCustomClass(document.getCustomClass());
3910  2 setContentUpdateDate(document.getContentUpdateDate());
3911  2 setTitle(document.getTitle());
3912  2 setFormat(document.getFormat());
3913  2 setFromCache(document.isFromCache());
3914  2 setElements(document.getElements());
3915  2 setMeta(document.getMeta());
3916  2 setMostRecent(document.isMostRecent());
3917  2 setNew(document.isNew());
3918  2 setStore(document.getStore());
3919  2 setTemplateDocumentReference(document.getTemplateDocumentReference());
3920  2 setParentReference(document.getRelativeParentReference());
3921  2 setCreatorReference(document.getCreatorReference());
3922  2 setDefaultLocale(document.getDefaultLocale());
3923  2 setDefaultTemplate(document.getDefaultTemplate());
3924  2 setValidationScript(document.getValidationScript());
3925  2 setLocale(document.getLocale());
3926  2 setXClass(document.getXClass().clone());
3927  2 setXClassXML(document.getXClassXML());
3928  2 setComment(document.getComment());
3929  2 setMinorEdit(document.isMinorEdit());
3930  2 setSyntax(document.getSyntax());
3931  2 setHidden(document.isHidden());
3932   
3933  2 cloneXObjects(document);
3934  2 cloneAttachments(document);
3935   
3936  2 setContentDirty(document.isContentDirty());
3937  2 setMetaDataDirty(document.isMetaDataDirty());
3938   
3939  2 this.elements = document.elements;
3940   
3941  2 this.originalDocument = document.originalDocument;
3942    }
3943   
 
3944  226949 toggle @Override
3945    public XWikiDocument clone()
3946    {
3947  226949 return cloneInternal(getDocumentReference(), true);
3948    }
3949   
3950    /**
3951    * Duplicate this document and give it a new name.
3952    *
3953    * @since 2.2.3
3954    */
 
3955  170 toggle public XWikiDocument duplicate(DocumentReference newDocumentReference)
3956    {
3957  170 return cloneInternal(newDocumentReference, false);
3958    }
3959   
 
3960  227112 toggle private XWikiDocument cloneInternal(DocumentReference newDocumentReference, boolean keepsIdentity)
3961    {
3962  227115 XWikiDocument doc = null;
3963   
3964  227110 try {
3965  227115 Constructor<? extends XWikiDocument> constructor = getClass().getConstructor(DocumentReference.class);
3966  227108 doc = constructor.newInstance(newDocumentReference);
3967   
3968    // use version field instead of getRCSVersion because it returns "1.1" if version==null.
3969  227117 doc.version = this.version;
3970  227117 doc.id = this.id;
3971  227116 doc.setDocumentArchive(getDocumentArchive());
3972  227110 doc.setAuthorReference(getAuthorReference());
3973  227112 doc.setContentAuthorReference(getContentAuthorReference());
3974  227111 doc.setContent(getContent());
3975  227107 doc.setCreationDate(getCreationDate());
3976  227104 doc.setDate(getDate());
3977  227098 doc.setCustomClass(getCustomClass());
3978  227101 doc.setContentUpdateDate(getContentUpdateDate());
3979  227106 doc.setTitle(getTitle());
3980  227102 doc.setFormat(getFormat());
3981  227097 doc.setFromCache(isFromCache());
3982  227105 doc.setElements(getElements());
3983  227098 doc.setMeta(getMeta());
3984  227104 doc.setMostRecent(isMostRecent());
3985  227101 doc.setNew(isNew());
3986  227102 doc.setStore(getStore());
3987  227104 doc.setTemplateDocumentReference(getTemplateDocumentReference());
3988  227098 doc.setParentReference(getRelativeParentReference());
3989  227102 doc.setCreatorReference(getCreatorReference());
3990  227108 doc.setDefaultLocale(getDefaultLocale());
3991  227092 doc.setDefaultTemplate(getDefaultTemplate());
3992  227087 doc.setValidationScript(getValidationScript());
3993  227091 doc.setLocale(getLocale());
3994  227079 doc.setComment(getComment());
3995  227095 doc.setMinorEdit(isMinorEdit());
3996  227096 doc.setSyntax(getSyntax());
3997  227095 doc.setHidden(isHidden());
3998   
3999  227107 if (this.xClass != null) {
4000  190274 doc.setXClass(this.xClass.clone());
4001    }
4002   
4003  227111 if (keepsIdentity) {
4004  226934 doc.setXClassXML(getXClassXML());
4005  226929 doc.cloneXObjects(this);
4006  226937 doc.cloneAttachments(this);
4007    } else {
4008  170 doc.getXClass().setCustomMapping(null);
4009  170 doc.duplicateXObjects(this);
4010  170 doc.copyAttachments(this);
4011    }
4012   
4013  227104 doc.setContentDirty(isContentDirty());
4014  227101 doc.setMetaDataDirty(isMetaDataDirty());
4015   
4016  227104 doc.elements = this.elements;
4017   
4018  227101 doc.originalDocument = this.originalDocument;
4019    } catch (Exception e) {
4020    // This should not happen
4021  0 LOGGER.error("Exception while cloning document", e);
4022    }
4023  227099 return doc;
4024    }
4025   
4026    /**
4027    * Clone attachments from another document. This implementation expects that this document is the same as the other
4028    * document and thus attachments will be saved in the database in the same place as the ones which they are cloning.
4029    *
4030    * @param sourceDocument an XWikiDocument to copy attachments from
4031    */
 
4032  226938 toggle private void cloneAttachments(final XWikiDocument sourceDocument)
4033    {
4034  226946 this.getAttachmentList().clear();
4035  226936 for (XWikiAttachment attach : sourceDocument.getAttachmentList()) {
4036  232401 XWikiAttachment newAttach = (XWikiAttachment) attach.clone();
4037   
4038  232402 setAttachment(newAttach);
4039    }
4040    }
4041   
4042    /**
4043    * Copy attachments from one document to another. This implementation expects that you are copying the attachment
4044    * from one document to another and thus it should be saved separately from the original in the database.
4045    *
4046    * @param sourceDocument an XWikiDocument to copy attachments from
4047    */
 
4048  170 toggle public void copyAttachments(XWikiDocument sourceDocument)
4049    {
4050  170 copyAttachments(sourceDocument, true);
4051    }
4052   
4053    /**
4054    * Copy attachments from one document to another. This implementation expects that you are copying the attachment
4055    * from one document to another and thus it should be saved separately from the original in the database.
4056    *
4057    * @param sourceDocument an XWikiDocument to copy attachments from
4058    * @param overwrite whether to overwrite the existing attachments or not
4059    * @since 8.4.6
4060    * @since 9.6RC1
4061    */
 
4062  345 toggle private void copyAttachments(XWikiDocument sourceDocument, boolean overwrite)
4063    {
4064  345 if (overwrite) {
4065    // Note: when clearing the attachment list, we automatically mark the document's metadata as dirty.
4066  170 getAttachmentList().clear();
4067    }
4068   
4069  345 for (XWikiAttachment attachment : sourceDocument.getAttachmentList()) {
4070  3 if (overwrite || this.getAttachment(attachment.getFilename()) == null) {
4071  2 XWikiAttachment newAttachment = attachment.clone();
4072   
4073    // We need to set the content of the attachment to be dirty because the dirty bit
4074    // is used to signal that there is a reason to save the copied attachment, otherwise
4075    // the copied attachment will be empty since the original attachment content is not
4076    // modified in this operation.
4077  2 if (newAttachment.getAttachment_content() != null) {
4078  2 newAttachment.getAttachment_content().setContentDirty(true);
4079    }
4080   
4081  2 setAttachment(newAttachment);
4082    }
4083    }
4084    }
4085   
4086    /**
4087    * Load attachment content from database.
4088    *
4089    * @param context the XWiki context
4090    * @throws XWikiException when failing to load attachments
4091    * @since 5.3M2
4092    */
 
4093  0 toggle public void loadAttachmentsContent(XWikiContext context) throws XWikiException
4094    {
4095  0 for (XWikiAttachment attachment : getAttachmentList()) {
4096  0 attachment.loadAttachmentContent(context);
4097    }
4098    }
4099   
4100    /**
4101    * Same as {@link #loadAttachmentContent(XWikiAttachment, XWikiContext)} but in some context we don't really care if
4102    * an attachment content could not be loaded (we are going to overwrite or ignore it).
4103    *
4104    * @param context the XWiki context
4105    * @since 10.1RC1
4106    */
 
4107  1387 toggle public void loadAttachmentsContentSafe(XWikiContext context)
4108    {
4109  1387 for (XWikiAttachment attachment : getAttachmentList()) {
4110  21 try {
4111  21 attachment.loadAttachmentContent(context);
4112    } catch (XWikiException e) {
4113  0 LOGGER.warn("Failed to load attachment [{}]: {}", attachment.getReference(),
4114    ExceptionUtils.getRootCauseMessage(e));
4115    }
4116    }
4117    }
4118   
 
4119  165 toggle public void loadAttachments(XWikiContext context) throws XWikiException
4120    {
4121  165 for (XWikiAttachment attachment : getAttachmentList()) {
4122  1 attachment.loadAttachmentContent(context);
4123  1 attachment.loadArchive(context);
4124    }
4125    }
4126   
 
4127  29 toggle @Override
4128    public boolean equals(Object object)
4129    {
4130    // Same Java object, they sure are equal
4131  29 if (this == object) {
4132  23 return true;
4133    }
4134   
4135    // Reference/language (document identifier)
4136   
4137  6 XWikiDocument doc = (XWikiDocument) object;
4138  6 if (!getDocumentReference().equals(doc.getDocumentReference())) {
4139  0 return false;
4140    }
4141   
4142  6 if (!getDefaultLocale().equals(doc.getDefaultLocale())) {
4143  0 return false;
4144    }
4145   
4146  6 if (!getLocale().equals(doc.getLocale())) {
4147  0 return false;
4148    }
4149   
4150  6 if (getTranslation() != doc.getTranslation()) {
4151  0 return false;
4152    }
4153   
4154    // Authors
4155   
4156  6 if (ObjectUtils.notEqual(getAuthorReference(), doc.getAuthorReference())) {
4157  1 return false;
4158    }
4159   
4160  5 if (ObjectUtils.notEqual(getContentAuthorReference(), doc.getContentAuthorReference())) {
4161  0 return false;
4162    }
4163   
4164  5 if (ObjectUtils.notEqual(getCreatorReference(), doc.getCreatorReference())) {
4165  0 return false;
4166    }
4167   
4168    // Version
4169   
4170  5 if (!getVersion().equals(doc.getVersion())) {
4171  0 return false;
4172    }
4173   
4174  5 if (getDate().getTime() != doc.getDate().getTime()) {
4175  0 return false;
4176    }
4177   
4178  5 if (getContentUpdateDate().getTime() != doc.getContentUpdateDate().getTime()) {
4179  0 return false;
4180    }
4181   
4182  5 if (getCreationDate().getTime() != doc.getCreationDate().getTime()) {
4183  0 return false;
4184    }
4185   
4186  5 if (!getComment().equals(doc.getComment())) {
4187  0 return false;
4188    }
4189   
4190  5 if (isMinorEdit() != doc.isMinorEdit()) {
4191  0 return false;
4192    }
4193   
4194    // Datas
4195   
4196  5 if (!equalsData(doc)) {
4197  1 return false;
4198    }
4199   
4200    // We consider that 2 documents are still equal even when they have different original
4201    // documents (see getOriginalDocument() for more details as to what is an original
4202    // document).
4203   
4204  4 return true;
4205    }
4206   
4207    /**
4208    * Same as {@link #equals(Object)} but only for actual datas of the document.
4209    * <p>
4210    * The goal being to compare two versions of the same document this method skip every version/reference/author
4211    * related information. For example it allows to compare a document comming from a another wiki and easily check if
4212    * thoses actually are the same thing whatever the plumbing differences.
4213    *
4214    * @param otherDocument the document to compare
4215    * @return true if bith documents have the same datas
4216    * @since 4.1.1
4217    */
 
4218  115 toggle public boolean equalsData(XWikiDocument otherDocument)
4219    {
4220    // Same Java object, they sure are equal
4221  115 if (this == otherDocument) {
4222  0 return true;
4223    }
4224   
4225  115 if (ObjectUtils.notEqual(getParentReference(), otherDocument.getParentReference())) {
4226  0 return false;
4227    }
4228   
4229  115 if (!getFormat().equals(otherDocument.getFormat())) {
4230  0 return false;
4231    }
4232   
4233  115 if (!getTitle().equals(otherDocument.getTitle())) {
4234  0 return false;
4235    }
4236   
4237  115 if (!getContent().equals(otherDocument.getContent())) {
4238  6 return false;
4239    }
4240   
4241  109 if (!getDefaultTemplate().equals(otherDocument.getDefaultTemplate())) {
4242  0 return false;
4243    }
4244   
4245  109 if (!getValidationScript().equals(otherDocument.getValidationScript())) {
4246  0 return false;
4247    }
4248   
4249  109 if (ObjectUtils.notEqual(getSyntax(), otherDocument.getSyntax())) {
4250  0 return false;
4251    }
4252   
4253  109 if (isHidden() != otherDocument.isHidden()) {
4254  0 return false;
4255    }
4256   
4257    // XClass
4258   
4259  109 if (!getXClass().equals(otherDocument.getXClass())) {
4260  20 return false;
4261    }
4262   
4263    // XObjects
4264   
4265  89 Set<DocumentReference> myObjectClassReferences = getXObjects().keySet();
4266  89 Set<DocumentReference> otherObjectClassReferences = otherDocument.getXObjects().keySet();
4267  89 if (!myObjectClassReferences.equals(otherObjectClassReferences)) {
4268  0 return false;
4269    }
4270   
4271  89 for (DocumentReference reference : myObjectClassReferences) {
4272  20 List<BaseObject> myObjects = getXObjects(reference);
4273  20 List<BaseObject> otherObjects = otherDocument.getXObjects(reference);
4274  20 if (myObjects.size() != otherObjects.size()) {
4275  0 return false;
4276    }
4277  45 for (int i = 0; i < myObjects.size(); i++) {
4278  25 if ((myObjects.get(i) == null && otherObjects.get(i) != null)
4279    || (myObjects.get(i) != null && otherObjects.get(i) == null)) {
4280  0 return false;
4281    }
4282  25 if (myObjects.get(i) == null && otherObjects.get(i) == null) {
4283  4 continue;
4284    }
4285  21 if (!myObjects.get(i).equals(otherObjects.get(i))) {
4286  0 return false;
4287    }
4288    }
4289    }
4290   
4291    // Attachments
4292  89 List<XWikiAttachment> attachments = getAttachmentList();
4293  89 List<XWikiAttachment> otherAttachments = otherDocument.getAttachmentList();
4294  89 if (attachments.size() != otherAttachments.size()) {
4295  0 return false;
4296    }
4297  89 for (XWikiAttachment attachment : attachments) {
4298  5 XWikiAttachment otherAttachment = otherDocument.getAttachment(attachment.getFilename());
4299  5 try {
4300  5 if (otherAttachment == null || !attachment.equalsData(otherAttachment, null)) {
4301  2 return false;
4302    }
4303    } catch (XWikiException e) {
4304  0 throw new RuntimeException(
4305    String.format("Failed to compare attachments with reference [%s]", attachment.getReference()), e);
4306    }
4307    }
4308   
4309  87 return true;
4310    }
4311   
4312    /**
4313    * Retrieve the document in the current context language as an XML string. The rendrered document content and all
4314    * XObjects are included. Document attachments and archived versions are excluded. You should prefer
4315    * toXML(OutputStream, true, true, false, false, XWikiContext)} or toXML(com.xpn.xwiki.util.XMLWriter, true, true,
4316    * false, false, XWikiContext) on the translated document when possible to reduce memory load.
4317    *
4318    * @param context current XWikiContext
4319    * @return a string containing an XML representation of the document in the current context language
4320    * @throws XWikiException when an error occurs during wiki operation
4321    */
 
4322  0 toggle public String getXMLContent(XWikiContext context) throws XWikiException
4323    {
4324  0 XWikiDocument tdoc = getTranslatedDocument(context);
4325  0 return tdoc.toXML(true, true, false, false, context);
4326    }
4327   
4328    /**
4329    * Retrieve the document as an XML string. All XObject are included. Rendered content, attachments and archived
4330    * version are excluded. You should prefer toXML(OutputStream, true, false, false, false, XWikiContext)} or
4331    * toXML(com.xpn.xwiki.util.XMLWriter, true, false, false, false, XWikiContext) when possible to reduce memory load.
4332    *
4333    * @param context current XWikiContext
4334    * @return a string containing an XML representation of the document
4335    * @throws XWikiException when an error occurs during wiki operation
4336    */
 
4337  9657 toggle public String toXML(XWikiContext context) throws XWikiException
4338    {
4339  9657 return toXML(true, false, false, false, context);
4340    }
4341   
4342    /**
4343    * Retrieve the document as an XML string. All XObjects attachments and archived version are included. Rendered
4344    * content is excluded. You should prefer toXML(OutputStream, true, false, true, true, XWikiContext)} or
4345    * toXML(com.xpn.xwiki.util.XMLWriter, true, false, true, true, XWikiContext) when possible to reduce memory load.
4346    *
4347    * @param context current XWikiContext
4348    * @return a string containing an XML representation of the document
4349    * @throws XWikiException when an error occurs during wiki operation
4350    */
 
4351  0 toggle public String toFullXML(XWikiContext context) throws XWikiException
4352    {
4353  0 return toXML(true, false, true, true, context);
4354    }
4355   
4356    /**
4357    * Serialize the document into a new entry of an ZipOutputStream in XML format. All XObjects and attachments are
4358    * included. Rendered content is excluded.
4359    *
4360    * @param zos the ZipOutputStream to write to
4361    * @param zipname the name of the new entry to create
4362    * @param withVersions if true, also include archived version of the document
4363    * @param context current XWikiContext
4364    * @throws XWikiException when an error occurs during xwiki operations
4365    * @throws IOException when an error occurs during streaming operations
4366    * @since 2.3M2
4367    * @deprecated since 4.1M2
4368    */
 
4369  0 toggle @Deprecated
4370    public void addToZip(ZipOutputStream zos, String zipname, boolean withVersions, XWikiContext context)
4371    throws XWikiException, IOException
4372    {
4373  0 ZipEntry zipentry = new ZipEntry(zipname);
4374  0 zos.putNextEntry(zipentry);
4375  0 toXML(zos, true, false, true, withVersions, context);
4376  0 zos.closeEntry();
4377    }
4378   
4379    /**
4380    * Serialize the document into a new entry of an ZipOutputStream in XML format. The new entry is named
4381    * 'LastSpaceName/DocumentName'. All XObjects and attachments are included. Rendered content is excluded.
4382    *
4383    * @param zos the ZipOutputStream to write to
4384    * @param withVersions if true, also include archived version of the document
4385    * @param context current XWikiContext
4386    * @throws XWikiException when an error occurs during xwiki operations
4387    * @throws IOException when an error occurs during streaming operations
4388    * @since 2.3M2
4389    * @deprecated since 4.2M2
4390    */
 
4391  0 toggle @Deprecated
4392    public void addToZip(ZipOutputStream zos, boolean withVersions, XWikiContext context)
4393    throws XWikiException, IOException
4394    {
4395  0 String zipname =
4396    getDocumentReference().getLastSpaceReference().getName() + "/" + getDocumentReference().getName();
4397  0 String language = getLanguage();
4398  0 if (!StringUtils.isEmpty(language)) {
4399  0 zipname += "." + language;
4400    }
4401  0 addToZip(zos, zipname, withVersions, context);
4402    }
4403   
4404    /**
4405    * Serialize the document into a new entry of an ZipOutputStream in XML format. The new entry is named
4406    * 'LastSpaceName/DocumentName'. All XObjects, attachments and archived versions are included. Rendered content is
4407    * excluded.
4408    *
4409    * @param zos the ZipOutputStream to write to
4410    * @param context current XWikiContext
4411    * @throws XWikiException when an error occurs during xwiki operations
4412    * @throws IOException when an error occurs during streaming operations
4413    * @since 2.3M2
4414    * @deprecated since 4.1M2
4415    */
 
4416  0 toggle @Deprecated
4417    public void addToZip(ZipOutputStream zos, XWikiContext context) throws XWikiException, IOException
4418    {
4419  0 addToZip(zos, true, context);
4420    }
4421   
4422    /**
4423    * Serialize the document to an XML string. You should prefer
4424    * {@link #toXML(OutputStream, boolean, boolean, boolean, boolean, XWikiContext)} or
4425    * {@link #toXML(com.xpn.xwiki.internal.xml.XMLWriter, boolean, boolean, boolean, boolean, XWikiContext)} when
4426    * possible to reduce memory load.
4427    *
4428    * @param bWithObjects include XObjects
4429    * @param bWithRendering include the rendered content
4430    * @param bWithAttachmentContent include attachments content
4431    * @param bWithVersions include archived versions
4432    * @param context current XWikiContext
4433    * @return a string containing an XML representation of the document
4434    * @throws XWikiException when an errors occurs during wiki operations
4435    */
 
4436  9672 toggle public String toXML(boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent,
4437    boolean bWithVersions, XWikiContext context) throws XWikiException
4438    {
4439  9672 StringWriter writer = new StringWriter();
4440   
4441  9672 toXML(new DefaultWriterOutputTarget(writer), bWithObjects, bWithRendering, bWithAttachmentContent,
4442  9672 bWithVersions, true, context != null ? context.getWiki().getEncoding() : StandardCharsets.UTF_8.name());
4443   
4444  9672 return writer.toString();
4445    }
4446   
4447    /**
4448    * Serialize the document to an XML {@link DOMDocument}. All XObject are included. Rendered content, attachments and
4449    * archived version are excluded. You should prefer toXML(OutputStream, true, false, false, false, XWikiContext)} or
4450    * toXML(com.xpn.xwiki.util.XMLWriter, true, false, false, false, XWikiContext) when possible to reduce memory load.
4451    *
4452    * @param context current XWikiContext
4453    * @return a {@link DOMDocument} containing the serialized document.
4454    * @throws XWikiException when an errors occurs during wiki operations
4455    * @deprecated since 9.0RC1, use {@link #toXML(OutputTarget, boolean, boolean, boolean, boolean, boolean, String)}
4456    * instead
4457    */
 
4458  1 toggle @Deprecated
4459    public Document toXMLDocument(XWikiContext context) throws XWikiException
4460    {
4461  1 return toXMLDocument(true, false, false, false, context);
4462    }
4463   
4464    /**
4465    * Serialize the document to an XML {@link DOMDocument}. You should prefer
4466    * {@link #toXML(OutputStream, boolean, boolean, boolean, boolean, XWikiContext)} or
4467    * {@link #toXML(com.xpn.xwiki.internal.xml.XMLWriter, boolean, boolean, boolean, boolean, XWikiContext)} when
4468    * possible to reduce memory load.
4469    *
4470    * @param bWithObjects include XObjects
4471    * @param bWithRendering include the rendered content
4472    * @param bWithAttachmentContent include attachments content
4473    * @param bWithVersions include archived versions
4474    * @param context current XWikiContext
4475    * @return a {@link DOMDocument} containing the serialized document.
4476    * @throws XWikiException when an errors occurs during wiki operations
4477    * @deprecated since 9.0RC1, use {@link #toXML(OutputTarget, boolean, boolean, boolean, boolean, boolean, String)}
4478    * instead
4479    */
 
4480  1 toggle @Deprecated
4481    public Document toXMLDocument(boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent,
4482    boolean bWithVersions, XWikiContext context) throws XWikiException
4483    {
4484  1 Document doc = new DOMDocument();
4485  1 DOMXMLWriter wr = new DOMXMLWriter(doc, new OutputFormat("", true, context.getWiki().getEncoding()));
4486   
4487  1 try {
4488  1 toXML(wr, bWithObjects, bWithRendering, bWithAttachmentContent, bWithVersions, context);
4489  1 return doc;
4490    } catch (IOException e) {
4491  0 throw new RuntimeException(e);
4492    }
4493    }
4494   
4495    /**
4496    * Serialize the document to a {@link com.xpn.xwiki.internal.xml.XMLWriter}.
4497    *
4498    * @param bWithObjects include XObjects
4499    * @param bWithRendering include the rendered content
4500    * @param bWithAttachmentContent include attachments content
4501    * @param bWithVersions include archived versions
4502    * @param context current XWikiContext
4503    * @throws XWikiException when an errors occurs during wiki operations
4504    * @throws IOException when an errors occurs during streaming operations
4505    * @since 2.3M2
4506    * @deprecated since 9.0RC1, use {@link #toXML(OutputTarget, boolean, boolean, boolean, boolean, boolean, String)}
4507    * instead
4508    */
 
4509  1 toggle @Deprecated
4510    public void toXML(XMLWriter wr, boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent,
4511    boolean bWithVersions, XWikiContext context) throws XWikiException, IOException
4512    {
4513    // IMPORTANT: we don't use directly XMLWriter's SAX apis here because it's not really working well
4514  1 DocumentResult domResult = new DocumentResult();
4515   
4516  1 toXML(new DefaultResultOutputTarget(domResult), bWithObjects, bWithRendering, bWithAttachmentContent,
4517  1 bWithVersions, true, context != null ? context.getWiki().getEncoding() : StandardCharsets.UTF_8.name());
4518   
4519  1 wr.write(domResult.getDocument().getRootElement());
4520    }
4521   
4522    /**
4523    * Serialize the document to an OutputStream.
4524    *
4525    * @param bWithObjects include XObjects
4526    * @param bWithRendering include the rendered content
4527    * @param bWithAttachmentContent include attachments content
4528    * @param bWithVersions include archived versions
4529    * @param context current XWikiContext
4530    * @throws XWikiException when an errors occurs during wiki operations
4531    * @throws IOException when an errors occurs during streaming operations
4532    * @since 2.3M2
4533    */
 
4534  2736 toggle public void toXML(OutputStream out, boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent,
4535    boolean bWithVersions, XWikiContext context) throws XWikiException, IOException
4536    {
4537  2736 toXML(new DefaultOutputStreamOutputTarget(out), bWithObjects, bWithRendering, bWithAttachmentContent,
4538  2736 bWithVersions, true, context != null ? context.getWiki().getEncoding() : StandardCharsets.UTF_8.name());
4539    }
4540   
4541    /**
4542    * Serialize the document to an OutputStream.
4543    *
4544    * @param out the output where to write the XML
4545    * @param bWithObjects include XObjects
4546    * @param bWithRendering include the rendered content
4547    * @param bWithAttachmentContent include attachments content
4548    * @param bWithVersions include archived versions
4549    * @param format true if the XML should be formated
4550    * @param encoding the encoding to use to write the XML
4551    * @throws XWikiException when an errors occurs during wiki operations
4552    * @since 9.0RC1
4553    */
 
4554  12701 toggle public void toXML(OutputTarget out, boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent,
4555    boolean bWithVersions, boolean format, String encoding) throws XWikiException
4556    {
4557    // Input
4558  12701 DocumentInstanceInputProperties documentProperties = new DocumentInstanceInputProperties();
4559  12701 documentProperties.setWithWikiObjects(bWithObjects);
4560  12701 documentProperties.setWithWikiDocumentContentHTML(bWithRendering);
4561  12701 documentProperties.setWithWikiAttachmentsContent(bWithAttachmentContent);
4562  12701 documentProperties.setWithJRCSRevisions(bWithVersions);
4563  12701 documentProperties.setWithRevisions(false);
4564   
4565    // Output
4566  12701 XAROutputProperties xarProperties = new XAROutputProperties();
4567  12701 xarProperties.setPreserveVersion(bWithVersions);
4568  12701 xarProperties.setEncoding(encoding);
4569  12701 xarProperties.setFormat(format);
4570   
4571  12701 try {
4572  12701 Utils.getComponent(XWikiDocumentFilterUtils.class).exportEntity(this, out, xarProperties,
4573    documentProperties);
4574    } catch (Exception e) {
4575  0 throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_DOC_XML_PARSING,
4576    "Error parsing xml", e, null);
4577    }
4578    }
4579   
 
4580  0 toggle protected String encodedXMLStringAsUTF8(String xmlString)
4581    {
4582  0 if (xmlString == null) {
4583  0 return "";
4584    }
4585   
4586  0 int length = xmlString.length();
4587  0 char character;
4588  0 StringBuilder result = new StringBuilder();
4589  0 for (int i = 0; i < length; i++) {
4590  0 character = xmlString.charAt(i);
4591  0 switch (character) {
4592  0 case '&':
4593  0 result.append("&amp;");
4594  0 break;
4595  0 case '"':
4596  0 result.append("&quot;");
4597  0 break;
4598  0 case '<':
4599  0 result.append("&lt;");
4600  0 break;
4601  0 case '>':
4602  0 result.append("&gt;");
4603  0 break;
4604  0 case '\n':
4605  0 result.append("\n");
4606  0 break;
4607  0 case '\r':
4608  0 result.append("\r");
4609  0 break;
4610  0 case '\t':
4611  0 result.append("\t");
4612  0 break;
4613  0 default:
4614  0 if (character < 0x20) {
4615  0 } else if (character > 0x7F) {
4616  0 result.append("&#x");
4617  0 result.append(Integer.toHexString(character).toUpperCase());
4618  0 result.append(";");
4619    } else {
4620  0 result.append(character);
4621    }
4622  0 break;
4623    }
4624    }
4625   
4626  0 return result.toString();
4627    }
4628   
 
4629  0 toggle protected String getElement(Element docel, String name)
4630    {
4631  0 Element el = docel.element(name);
4632  0 if (el == null) {
4633  0 return "";
4634    } else {
4635  0 return el.getText();
4636    }
4637    }
4638   
 
4639  194 toggle public void fromXML(String xml) throws XWikiException
4640    {
4641  194 fromXML(xml, false);
4642    }
4643   
 
4644  12 toggle public void fromXML(InputStream is) throws XWikiException
4645    {
4646  12 fromXML(is, false);
4647    }
4648   
 
4649  2686 toggle public void fromXML(InputSource source, boolean withArchive) throws XWikiException
4650    {
4651    // Output
4652  2686 DocumentInstanceOutputProperties documentProperties = new DocumentInstanceOutputProperties();
4653  2686 XWikiContext xcontext = getXWikiContext();
4654  2686 if (xcontext != null) {
4655  2686 documentProperties.setDefaultReference(getXWikiContext().getWikiReference());
4656    }
4657  2686 documentProperties.setVersionPreserved(withArchive);
4658   
4659    // Input
4660  2686 XARInputProperties xarProperties = new XARInputProperties();
4661  2686 xarProperties.setWithHistory(withArchive);
4662   
4663  2686 try {
4664  2686 Utils.getComponent(XWikiDocumentFilterUtils.class).importEntity(XWikiDocument.class, this, source,
4665    xarProperties, documentProperties);
4666    } catch (Exception e) {
4667  0 throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_DOC_XML_PARSING,
4668    "Error parsing xml", e, null);
4669    }
4670   
4671    // We have been reading from XML so the document does not need a new version when saved
4672  2686 setMetaDataDirty(false);
4673  2686 setContentDirty(false);
4674    }
4675   
 
4676  195 toggle public void fromXML(String source, boolean withArchive) throws XWikiException
4677    {
4678  195 fromXML(new StringInputSource(source), withArchive);
4679    }
4680   
 
4681  2398 toggle public void fromXML(InputStream source, boolean withArchive) throws XWikiException
4682    {
4683  2398 fromXML(new DefaultInputStreamInputSource(source), withArchive);
4684    }
4685   
4686    /**
4687    * @deprecated since 9.0RC1, use {@link #fromXML(InputStream)} instead
4688    */
 
4689  1 toggle @Deprecated
4690    public void fromXML(Document domdoc, boolean withArchive) throws XWikiException
4691    {
4692    // Serialize the Document (could not find a way to convert a dom4j Document into a usable StAX source)
4693  1 StringWriter writer = new StringWriter();
4694  1 try {
4695  1 org.dom4j.io.XMLWriter domWriter = new org.dom4j.io.XMLWriter(writer);
4696  1 domWriter.write(domdoc);
4697  1 domWriter.flush();
4698    } catch (IOException e) {
4699  0 throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_DOC_XML_PARSING,
4700    "Error parsing xml", e, null);
4701    }
4702   
4703    // Actually parse the XML
4704  1 fromXML(writer.toString(), withArchive);
4705    }
4706   
4707    /**
4708    * Check if provided xml document is a wiki document.
4709    *
4710    * @param domdoc the xml document.
4711    * @return true if provided xml document is a wiki document.
4712    */
 
4713  0 toggle public static boolean containsXMLWikiDocument(Document domdoc)
4714    {
4715  0 return domdoc.getRootElement().getName().equals(XarDocumentModel.ELEMENT_DOCUMENT);
4716    }
4717   
 
4718  3 toggle public void setAttachmentList(List<XWikiAttachment> list)
4719    {
4720    // For backwards compatibility reasons (and in general), we need to allow callers to do something like
4721    // setAttachmentList(getAttachmentList())
4722  3 if (this.attachmentList != list) {
4723  1 this.attachmentList.clear();
4724  1 this.attachmentList.addAll(list);
4725    }
4726    }
4727   
 
4728  1492496 toggle public List<XWikiAttachment> getAttachmentList()
4729    {
4730  1492499 return this.attachmentList;
4731    }
4732   
4733    /**
4734    * @deprecated should not be used, save the document instead
4735    */
 
4736  0 toggle @Deprecated
4737    public void saveAllAttachments(XWikiContext context) throws XWikiException
4738    {
4739  0 saveAllAttachments(true, true, context);
4740    }
4741   
4742    /**
4743    * @deprecated should not be used, save the document instead
4744    */
 
4745  0 toggle @Deprecated
4746    public void saveAllAttachments(boolean updateParent, boolean transaction, XWikiContext context)
4747    throws XWikiException
4748    {
4749  0 for (XWikiAttachment attachment : this.attachmentList) {
4750  0 saveAttachmentContent(attachment, false, transaction, context);
4751    }
4752   
4753    // Save the document
4754  0 if (updateParent) {
4755  0 context.getWiki().saveDocument(this, context);
4756    }
4757    }
4758   
4759    /**
4760    * @deprecated should not be used, save the document instead
4761    */
 
4762  0 toggle @Deprecated
4763    public void saveAttachmentsContent(List<XWikiAttachment> attachments, XWikiContext context) throws XWikiException
4764    {
4765  0 for (XWikiAttachment attachment : attachments) {
4766  0 saveAttachmentContent(attachment, context);
4767    }
4768    }
4769   
4770    /**
4771    * @deprecated should not be used, save the document instead
4772    */
 
4773  0 toggle @Deprecated
4774    public void saveAttachmentContent(XWikiAttachment attachment, XWikiContext context) throws XWikiException
4775    {
4776  0 saveAttachmentContent(attachment, true, true, context);
4777    }
4778   
4779    /**
4780    * @deprecated should not be used, save the document instead
4781    */
 
4782  0 toggle @Deprecated
4783    public void saveAttachmentContent(XWikiAttachment attachment, boolean updateParent, boolean transaction,
4784    XWikiContext context) throws XWikiException
4785    {
4786  0 String currentWiki = context.getWikiId();
4787  0 try {
4788    // We might need to switch database to
4789    // get the translated content
4790  0 if (getDatabase() != null) {
4791  0 context.setWikiId(getDatabase());
4792    }
4793   
4794    // Save the attachment
4795  0 XWikiAttachmentStoreInterface store =
4796    resolveXWikiAttachmentStoreInterface(attachment.getContentStore(), context);
4797  0 store.saveAttachmentContent(attachment, false, context, transaction);
4798   
4799    // We need to make sure there is a version upgrade
4800  0 setMetaDataDirty(true);
4801   
4802    // Save the document
4803  0 if (updateParent) {
4804  0 context.getWiki().saveDocument(this, context);
4805    }
4806    } catch (OutOfMemoryError e) {
4807  0 throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_JAVA_HEAP_SPACE,
4808    "Out Of Memory Exception");
4809    } finally {
4810  0 if (currentWiki != null) {
4811  0 context.setWikiId(currentWiki);
4812    }
4813    }
4814    }
4815   
4816    /**
4817    * @deprecated since 9.9RC1, use {@link XWikiAttachment#loadContent(XWikiContext)}
4818    */
 
4819  0 toggle @Deprecated
4820    public void loadAttachmentContent(XWikiAttachment attachment, XWikiContext context) throws XWikiException
4821    {
4822  0 String database = context.getWikiId();
4823  0 try {
4824    // We might need to switch database to
4825    // get the translated content
4826  0 if (getDatabase() != null) {
4827  0 context.setWikiId(getDatabase());
4828    }
4829   
4830  0 attachment.loadAttachmentContent(context);
4831    } finally {
4832  0 if (database != null) {
4833  0 context.setWikiId(database);
4834    }
4835    }
4836    }
4837   
4838    /**
4839    * Remove the attachment from the document attachment list and put it in the list of attachments to remove for next
4840    * document save.
4841    * <p>
4842    * The attachment will be move to recycle bin.
4843    *
4844    * @param attachment the attachment to remove
4845    * @return the removed attachment, null if none could be found
4846    * @since 5.2M1
4847    */
 
4848  9 toggle public XWikiAttachment removeAttachment(XWikiAttachment attachment)
4849    {
4850  9 return removeAttachment(attachment, true);
4851    }
4852   
4853    /**
4854    * Remove the attachment from the document attachment list and put it in the list of attachments to remove for next
4855    * document save.
4856    *
4857    * @param attachmentToRemove the attachment to remove
4858    * @param toRecycleBin indicate if the attachment should be moved to recycle bin
4859    * @return the removed attachment, null if none could be found
4860    * @since 5.2M1
4861    */
 
4862  9 toggle public XWikiAttachment removeAttachment(XWikiAttachment attachmentToRemove, boolean toRecycleBin)
4863    {
4864  9 if (this.attachmentList.remove(attachmentToRemove)) {
4865  9 this.attachmentsToRemove.add(new XWikiAttachmentToRemove(attachmentToRemove, toRecycleBin));
4866  9 setMetaDataDirty(true);
4867    } else {
4868  0 attachmentToRemove = null;
4869    }
4870  9 return attachmentToRemove;
4871    }
4872   
4873    /**
4874    * @return the attachment planned for removal
4875    */
 
4876  12684 toggle public List<XWikiAttachmentToRemove> getAttachmentsToRemove()
4877    {
4878  12684 return this.attachmentsToRemove;
4879    }
4880   
4881    /**
4882    * Clear the list of attachments planned for removal.
4883    */
 
4884  7 toggle public void clearAttachmentsToRemove()
4885    {
4886  7 this.attachmentsToRemove.clear();
4887    }
4888   
4889    /**
4890    * Get the wiki document references pointing to this document.
4891    * <p>
4892    * Theses links are stored to the database when documents are saved. You can use "backlinks" in XWikiPreferences or
4893    * "xwiki.backlinks" in xwiki.cfg file to enable links storage in the database.
4894    *
4895    * @param context the XWiki context.
4896    * @return the found wiki document references
4897    * @throws XWikiException error when getting pages names from database.
4898    * @since 2.2M2
4899    */
 
4900  4 toggle public List<DocumentReference> getBackLinkedReferences(XWikiContext context) throws XWikiException
4901    {
4902  4 return getStore(context).loadBacklinks(getDocumentReference(), true, context);
4903    }
4904   
4905    /**
4906    * @deprecated since 2.2M2 use {@link #getBackLinkedReferences(XWikiContext)}
4907    */
 
4908  72 toggle @Deprecated
4909    public List<String> getBackLinkedPages(XWikiContext context) throws XWikiException
4910    {
4911  72 return getStore(context).loadBacklinks(getFullName(), context, true);
4912    }
4913   
4914    /**
4915    * Get a list of unique links from this document to others documents.
4916    * <ul>
4917    * <li>xwiki/1.0 content: get the unique links associated to document from database. This is stored when the
4918    * document is saved. You can use "backlinks" in XWikiPreferences or "xwiki.backlinks" in xwiki.cfg file to enable
4919    * links storage in the database.</li>
4920    * <li>Other content: call {@link #getUniqueLinkedPages(XWikiContext)} and generate the List.</li>
4921    * </ul>
4922    *
4923    * @param context the XWiki context
4924    * @return the found wiki links.
4925    * @throws XWikiException error when getting links from database when xwiki/1.0 content.
4926    * @since 1.9M2
4927    */
 
4928  6287 toggle public Set<XWikiLink> getUniqueWikiLinkedPages(XWikiContext context) throws XWikiException
4929    {
4930  6287 Set<XWikiLink> links;
4931   
4932  6287 if (is10Syntax()) {
4933  1 links = new LinkedHashSet<>(getStore(context).loadLinks(getId(), context, true));
4934    } else {
4935  6286 Set<String> linkedPages = getUniqueLinkedPages(context);
4936  6286 links = new LinkedHashSet<>(linkedPages.size());
4937  6286 for (String linkedPage : linkedPages) {
4938  154 XWikiLink wikiLink = new XWikiLink();
4939   
4940  154 wikiLink.setDocId(getId());
4941  154 wikiLink.setFullName(LOCAL_REFERENCE_SERIALIZER.serialize(getDocumentReference()));
4942  154 wikiLink.setLink(linkedPage);
4943   
4944  154 links.add(wikiLink);
4945    }
4946    }
4947   
4948  6287 return links;
4949    }
4950   
4951    /**
4952    * Extract all the unique static (i.e. not generated by macros) wiki links (pointing to wiki page) from this
4953    * xwiki/1.0 document's content to others documents.
4954    *
4955    * @param context the XWiki context.
4956    * @return the document names for linked pages, if null an error append.
4957    * @since 1.9M2
4958    */
 
4959  1 toggle private Set<String> getUniqueLinkedPages10(XWikiContext context)
4960    {
4961  1 Set<String> pageNames;
4962   
4963  1 try {
4964  1 List<String> list = context.getUtil().getUniqueMatches(getContent(), "\\[(.*?)\\]", 1);
4965  1 pageNames = new HashSet<String>(list.size());
4966   
4967  1 DocumentReference currentDocumentReference = getDocumentReference();
4968  1 for (String name : list) {
4969  7 int i1 = name.indexOf('>');
4970  7 if (i1 != -1) {
4971  3 name = name.substring(i1 + 1);
4972    }
4973  7 i1 = name.indexOf("&gt;");
4974  7 if (i1 != -1) {
4975  0 name = name.substring(i1 + 4);
4976    }
4977  7 i1 = name.indexOf('#');
4978  7 if (i1 != -1) {
4979  1 name = name.substring(0, i1);
4980    }
4981  7 i1 = name.indexOf('?');
4982  7 if (i1 != -1) {
4983  1 name = name.substring(0, i1);
4984    }
4985   
4986    // Let's get rid of anything that's not a real link
4987  7 if (name.trim().equals("") || (name.indexOf('$') != -1) || (name.indexOf("://") != -1)
4988    || (name.indexOf('"') != -1) || (name.indexOf('\'') != -1) || (name.indexOf("..") != -1)
4989    || (name.indexOf(':') != -1) || (name.indexOf('=') != -1)) {
4990  3 continue;
4991    }
4992   
4993    // generate the link
4994  4 String newname = StringUtils.replace(Util.noaccents(name), " ", "");
4995   
4996    // If it is a local link let's add the space
4997  4 if (newname.indexOf('.') == -1) {
4998  2 newname = getSpace() + "." + name;
4999    }
5000  4 if (context.getWiki().exists(newname, context)) {
5001  4 name = newname;
5002    } else {
5003    // If it is a local link let's add the space
5004  0 if (name.indexOf('.') == -1) {
5005  0 name = getSpace() + "." + name;
5006    }
5007    }
5008   
5009    // If the reference is empty, the link is an autolink
5010  4 if (!StringUtils.isEmpty(name)) {
5011    // The reference may not have the space or even document specified (in case of an empty
5012    // string)
5013    // Thus we need to find the fully qualified document name
5014  4 DocumentReference documentReference = getCurrentDocumentReferenceResolver().resolve(name);
5015   
5016    // Verify that the link is not an autolink (i.e. a link to the current document)
5017  4 if (!documentReference.equals(currentDocumentReference)) {
5018  4 pageNames.add(getCompactEntityReferenceSerializer().serialize(documentReference));
5019    }
5020    }
5021    }
5022   
5023  1 return pageNames;
5024    } catch (Exception e) {
5025    // This should never happen
5026  0 LOGGER.error("Failed to get linked documents", e);
5027   
5028  0 return null;
5029    }
5030    }
5031   
5032    /**
5033    * Extract all the unique static (i.e. not generated by macros) wiki links (pointing to wiki page) from this
5034    * document's content to others documents.
5035    *
5036    * @param context the XWiki context.
5037    * @return the document names for linked pages, if null an error append.
5038    * @since 1.9M2
5039    */
 
5040  6288 toggle public Set<String> getUniqueLinkedPages(XWikiContext context)
5041    {
5042  6288 Set<String> pageNames;
5043   
5044  6288 XWikiDocument contextDoc = context.getDoc();
5045  6288 String contextWiki = context.getWikiId();
5046   
5047  6288 try {
5048    // Make sure the right document is used as context document
5049  6288 context.setDoc(this);
5050    // Make sure the right wiki is used as context document
5051  6288 context.setWikiId(getDatabase());
5052   
5053  6288 if (is10Syntax()) {
5054  1 pageNames = getUniqueLinkedPages10(context);
5055    } else {
5056  6287 XDOM dom = getXDOM();
5057   
5058    // TODO: Add support for macro as well.
5059  6287 List<LinkBlock> linkBlocks =
5060    dom.getBlocks(new ClassBlockMatcher(LinkBlock.class), Block.Axes.DESCENDANT);
5061  6287 pageNames = new LinkedHashSet<String>(linkBlocks.size());
5062   
5063  6287 DocumentReference currentDocumentReference = getDocumentReference();
5064   
5065  6287 for (LinkBlock linkBlock : linkBlocks) {
5066  708 ResourceReference reference = linkBlock.getReference();
5067  708 String referenceString = reference.getReference();
5068  708 ResourceType resourceType = reference.getType();
5069   
5070    // TODO: Add support for ATTACHMENT as well.
5071  708 if (!ResourceType.DOCUMENT.equals(resourceType) && !ResourceType.SPACE.equals(resourceType)) {
5072    // We are only interested in Document or Space references.
5073  361 continue;
5074    }
5075   
5076    // Optimisation: If the reference is empty, the link is an autolink and we don`t include it.
5077  347 if (StringUtils.isEmpty(referenceString)) {
5078  21 continue;
5079    }
5080   
5081    // FIXME?: pass this.getDocumentReference as parameter to the resolve so that relative references
5082    // get resolved relative to this and not to the context document.
5083  326 EntityReference documentReference =
5084    getResourceReferenceEntityReferenceResolver().resolve(reference, EntityType.DOCUMENT);
5085   
5086    // Verify after resolving it that the link is not an autolink(i.e. a link to the current document)
5087  326 if (!documentReference.equals(currentDocumentReference)) {
5088    // Since this method is used for saving backlinks and since backlinks must be
5089    // saved with the space and page name but without the wiki part, we remove the wiki
5090    // part before serializing.
5091    // This is a bit of a hack since the default serializer should theoretically fail
5092    // if it's passed an invalid reference.
5093  300 pageNames.add(getCompactWikiEntityReferenceSerializer().serialize(documentReference));
5094    }
5095    }
5096    }
5097    } finally {
5098  6288 context.setDoc(contextDoc);
5099  6288 context.setWikiId(contextWiki);
5100    }
5101   
5102  6288 return pageNames;
5103    }
5104   
5105    /**
5106    * Returns a list of references of all documents which list this document as their parent, in the current wiki.
5107    * {@link #getChildren(int, int, com.xpn.xwiki.XWikiContext)}
5108    *
5109    * @since 2.2M2
5110    */
 
5111  7 toggle public List<DocumentReference> getChildrenReferences(XWikiContext context) throws XWikiException
5112    {
5113  7 return getChildrenReferences(0, 0, context);
5114    }
5115   
5116    /**
5117    * @deprecated since 2.2M2 use {@link #getChildrenReferences(XWikiContext)}
5118    */
 
5119  579 toggle @Deprecated
5120    public List<String> getChildren(XWikiContext context) throws XWikiException
5121    {
5122  579 return getChildren(0, 0, context);
5123    }
5124   
5125    /**
5126    * Returns a list of references of all documents which list this document as their parent, in the current wiki.
5127    *
5128    * @param nb The number of results to return.
5129    * @param start The number of results to skip before we begin returning results.
5130    * @param context The {@link com.xpn.xwiki.XWikiContext context}.
5131    * @return the list of document references
5132    * @throws XWikiException If there's an error querying the database.
5133    * @since 2.2M2
5134    */
 
5135  587 toggle public List<DocumentReference> getChildrenReferences(int nb, int start, XWikiContext context) throws XWikiException
5136    {
5137    // Use cases:
5138    // - the parent document reference saved in the database matches the reference of this document, in its fully
5139    // serialized form (eg "wiki:space.page"). Note that this is normally not required since the wiki part
5140    // isn't saved in the database when it matches the current wiki.
5141    // - the parent document reference saved in the database matches the reference of this document, in its
5142    // serialized form without the wiki part (eg "space.page"). The reason we don't need to specify the wiki
5143    // part is because document parents saved in the database don't have the wiki part specified when it matches
5144    // the current wiki.
5145    // - the parent document reference saved in the database matches the page name part of this document's
5146    // reference (eg "page") and the parent document's space is the same as this document's space.
5147  587 List<DocumentReference> children = new ArrayList<DocumentReference>();
5148   
5149  587 try {
5150  587 Query query = getStore().getQueryManager()
5151    .createQuery("select distinct doc.fullName from XWikiDocument doc where "
5152    + "doc.parent=:prefixedFullName or doc.parent=:fullName or (doc.parent=:name and doc.space=:space)",
5153    Query.XWQL);
5154  587 query.addFilter(Utils.getComponent(QueryFilter.class, "hidden"));
5155  587 query.bindValue("prefixedFullName",
5156    getDefaultEntityReferenceSerializer().serialize(getDocumentReference()));
5157  587 query.bindValue("fullName", LOCAL_REFERENCE_SERIALIZER.serialize(getDocumentReference()));
5158  587 query.bindValue("name", getDocumentReference().getName());
5159  587 query.bindValue("space",
5160    LOCAL_REFERENCE_SERIALIZER.serialize(getDocumentReference().getLastSpaceReference()));
5161  587 query.setLimit(nb).setOffset(start);
5162   
5163  587 List<String> queryResults = query.execute();
5164  587 WikiReference wikiReference = this.getDocumentReference().getWikiReference();
5165  587 for (String fullName : queryResults) {
5166  34 children.add(getCurrentDocumentReferenceResolver().resolve(fullName, wikiReference));
5167    }
5168    } catch (QueryException e) {
5169  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, XWikiException.ERROR_XWIKI_UNKNOWN,
5170    String.format("Failed to retrieve children for document [%s]", this.getDocumentReference()), e);
5171    }
5172   
5173  587 return children;
5174    }
5175   
5176    /**
5177    * @deprecated since 2.2M2 use {@link #getChildrenReferences(XWikiContext)}
5178    */
 
5179  579 toggle @Deprecated
5180    public List<String> getChildren(int nb, int start, XWikiContext context) throws XWikiException
5181    {
5182  579 List<String> childrenNames = new ArrayList<String>();
5183  579 for (DocumentReference reference : getChildrenReferences(nb, start, context)) {
5184  26 childrenNames.add(LOCAL_REFERENCE_SERIALIZER.serialize(reference));
5185    }
5186  579 return childrenNames;
5187    }
5188   
5189    /**
5190    * @since 2.2M2
5191    */
 
5192  92 toggle public void renameProperties(DocumentReference classReference, Map<String, String> fieldsToRename)
5193    {
5194  92 List<BaseObject> objects = this.xObjects.get(classReference);
5195  92 if (objects == null) {
5196  79 return;
5197    }
5198   
5199  13 boolean isDirty = false;
5200  13 for (BaseObject bobject : objects) {
5201  13 if (bobject == null) {
5202  0 continue;
5203    }
5204  13 for (Map.Entry<String, String> entry : fieldsToRename.entrySet()) {
5205  5 String origname = entry.getKey();
5206  5 String newname = entry.getValue();
5207  5 BaseProperty origprop = (BaseProperty) bobject.safeget(origname);
5208  5 if (origprop != null) {
5209  5 BaseProperty prop = origprop.clone();
5210  5 bobject.removeField(origname);
5211  5 prop.setName(newname);
5212  5 bobject.addField(newname, prop);
5213   
5214  5 isDirty = true;
5215    }
5216    }
5217    }
5218   
5219    // If at least one property was renamed, mark the document dirty.
5220  13 if (isDirty) {
5221  5 setMetaDataDirty(true);
5222    }
5223    }
5224   
5225    /**
5226    * @deprecated since 2.2M2 use {@link #renameProperties(DocumentReference, Map)} instead
5227    */
 
5228  26 toggle @Deprecated
5229    public void renameProperties(String className, Map<String, String> fieldsToRename)
5230    {
5231  26 renameProperties(resolveClassReference(className), fieldsToRename);
5232    }
5233   
5234    /**
5235    * @since 2.2M1
5236    */
 
5237  47 toggle public void addXObjectToRemove(BaseObject object)
5238    {
5239  47 getXObjectsToRemove().add(object);
5240  47 object.setOwnerDocument(null);
5241  47 setMetaDataDirty(true);
5242    }
5243   
5244    /**
5245    * Automatically add objects present in the old version, but not in the current document, to the list of objects
5246    * marked for removal from the database.
5247    *
5248    * @param previousVersion the version of the document present in the database
5249    * @since 3.3M2
5250    */
 
5251  143 toggle public void addXObjectsToRemoveFromVersion(XWikiDocument previousVersion)
5252    {
5253  143 if (previousVersion == null) {
5254  0 return;
5255    }
5256  143 for (List<BaseObject> objects : previousVersion.getXObjects().values()) {
5257  112 for (BaseObject originalObj : objects) {
5258  190 if (originalObj != null) {
5259  179 BaseObject newObj = getXObject(originalObj.getXClassReference(), originalObj.getNumber());
5260  179 if (newObj == null) {
5261    // The object was deleted.
5262  2 this.addXObjectToRemove(originalObj);
5263    }
5264    }
5265    }
5266    }
5267    }
5268   
5269    /**
5270    * @deprecated since 2.2M2 use {@link #addXObjectToRemove(BaseObject)} )} instead
5271    */
 
5272  0 toggle @Deprecated
5273    public void addObjectsToRemove(BaseObject object)
5274    {
5275  0 addXObjectToRemove(object);
5276    }
5277   
5278    /**
5279    * @since 2.2M2
5280    */
 
5281  6792 toggle public List<BaseObject> getXObjectsToRemove()
5282    {
5283  6792 return this.xObjectsToRemove;
5284    }
5285   
5286    /**
5287    * @deprecated since 2.2M2 use {@link #getObjectsToRemove()} instead
5288    */
 
5289  0 toggle @Deprecated
5290    public ArrayList<BaseObject> getObjectsToRemove()
5291    {
5292  0 return (ArrayList<BaseObject>) getXObjectsToRemove();
5293    }
5294   
5295    /**
5296    * @since 2.2M1
5297    */
 
5298  34 toggle public void setXObjectsToRemove(List<BaseObject> objectsToRemove)
5299    {
5300  34 this.xObjectsToRemove = objectsToRemove;
5301  34 setMetaDataDirty(true);
5302    }
5303   
 
5304  6451 toggle public List<String> getIncludedPages(XWikiContext context)
5305    {
5306  6451 try {
5307  6451 return getIncludedPagesInternal(context);
5308    } catch (Exception e) {
5309    // If an error happens then we return an empty list of included pages. We don't want to fail by throwing an
5310    // exception since it'll lead to several errors in the UI (such as in the Information Panel in edit mode).
5311  0 LOGGER.error("Failed to get included pages for [{}]", getDocumentReference(), e);
5312  0 return Collections.emptyList();
5313    }
5314    }
5315   
 
5316  6451 toggle private List<String> getIncludedPagesInternal(XWikiContext context)
5317    {
5318  6451 if (is10Syntax()) {
5319  1 return getIncludedPagesForXWiki10Syntax(getContent(), context);
5320    } else {
5321    // Find all include macros listed on the page
5322  6450 XDOM dom = getXDOM();
5323   
5324  6450 List<String> result = new ArrayList<String>();
5325  6450 List<MacroBlock> macroBlocks =
5326    dom.getBlocks(new ClassBlockMatcher(MacroBlock.class), Block.Axes.DESCENDANT);
5327  6450 for (MacroBlock macroBlock : macroBlocks) {
5328    // - Add each document pointed to by the include macro
5329    // - Also add all the included pages found in the velocity macro when using the deprecated #include*
5330    // macros
5331    // This should be removed when we fully drop support for the XWiki Syntax 1.0 but for now we want to
5332    // play nice with people migrating from 1.0 to 2.0 syntax
5333   
5334  3490 if (macroBlock.getId().equalsIgnoreCase("include") || macroBlock.getId().equalsIgnoreCase("display")) {
5335  536 String documentName = macroBlock.getParameters().get("reference");
5336  536 if (StringUtils.isEmpty(documentName)) {
5337  18 documentName = macroBlock.getParameters().get("document");
5338  18 if (StringUtils.isEmpty(documentName)) {
5339  4 continue;
5340    }
5341    }
5342   
5343  532 DocumentReference documentReference =
5344    getExplicitDocumentReferenceResolver().resolve(documentName, getDocumentReference());
5345  532 if (this.getDocumentReference().equals(documentReference)) {
5346    // Skip auto-includes since they are not allowed anyway.
5347  2 continue;
5348    }
5349   
5350  530 documentName = LOCAL_REFERENCE_SERIALIZER.serialize(documentReference);
5351   
5352  530 result.add(documentName);
5353  2954 } else if (macroBlock.getId().equalsIgnoreCase("velocity")
5354    && !StringUtils.isEmpty(macroBlock.getContent())) {
5355    // Try to find matching content inside each velocity macro
5356  1861 result.addAll(getIncludedPagesForXWiki10Syntax(macroBlock.getContent(), context));
5357    }
5358    }
5359   
5360  6450 return result;
5361    }
5362    }
5363   
 
5364  1862 toggle private List<String> getIncludedPagesForXWiki10Syntax(String content, XWikiContext context)
5365    {
5366  1862 try {
5367  1862 String pattern = "#include(Topic|InContext|Form|Macros|parseGroovyFromPage)\\([\"'](.*?)[\"']\\)";
5368  1862 List<String> list = context.getUtil().getUniqueMatches(content, pattern, 2);
5369  1863 for (int i = 0; i < list.size(); i++) {
5370  1 String name = list.get(i);
5371  1 if (name.indexOf('.') == -1) {
5372  0 list.set(i, getSpace() + "." + name);
5373    }
5374    }
5375   
5376  1862 return list;
5377    } catch (Exception e) {
5378  0 LOGGER.error("Failed to extract include target from provided content [" + content + "]", e);
5379   
5380  0 return null;
5381    }
5382    }
5383   
 
5384  0 toggle public List<String> getIncludedMacros(XWikiContext context)
5385    {
5386  0 return context.getWiki().getIncludedMacros(getSpace(), getContent(), context);
5387    }
5388   
 
5389  0 toggle public String displayRendered(PropertyClass pclass, String prefix, BaseCollection object, XWikiContext context)
5390    throws XWikiException
5391    {
5392  0 String result = pclass.displayView(pclass.getName(), prefix, object, context);
5393  0 return getRenderedContent(result, Syntax.XWIKI_1_0.toIdString(), context);
5394    }
5395   
 
5396  1085 toggle public String displayView(PropertyClass pclass, String prefix, BaseCollection object, XWikiContext context)
5397    {
5398  1085 return (pclass == null) ? "" : pclass.displayView(pclass.getName(), prefix, object, context);
5399    }
5400   
 
5401  7133 toggle public String displayEdit(PropertyClass pclass, String prefix, BaseCollection object, XWikiContext context)
5402    {
5403  7133 return (pclass == null) ? "" : pclass.displayEdit(pclass.getName(), prefix, object, context);
5404    }
5405   
 
5406  0 toggle public String displayHidden(PropertyClass pclass, String prefix, BaseCollection object, XWikiContext context)
5407    {
5408  0 return (pclass == null) ? "" : pclass.displayHidden(pclass.getName(), prefix, object, context);
5409    }
5410   
5411    /**
5412    * @param filename the file name of the attachment with or without the extension
5413    * @return the {@link XWikiAttachment} corresponding to the file name, null if none can be found
5414    */
 
5415  976300 toggle public XWikiAttachment getAttachment(String filename)
5416    {
5417  976300 XWikiAttachment output = this.attachmentList.getByFilename(filename);
5418  976259 if (output != null) {
5419  13504 return output;
5420    }
5421   
5422  962760 for (XWikiAttachment attach : getAttachmentList()) {
5423  40306 if (attach.getFilename().startsWith(filename + ".")) {
5424  36 return attach;
5425    }
5426    }
5427   
5428  962699 return null;
5429    }
5430   
5431    /**
5432    * Add passed attachment to the document.
5433    *
5434    * @param attachment the attachment to add
5435    * @since 5.3M2
5436    * @deprecated since 9.10RC1, use {@link #setAttachment(XWikiAttachment)} instead
5437    */
 
5438  657 toggle @Deprecated
5439    public void addAttachment(XWikiAttachment attachment)
5440    {
5441  657 setAttachment(attachment);
5442    }
5443   
5444    /**
5445    * Insert passed attachment in the document and return any pre-existing attachment with the same name.
5446    *
5447    * @param attachment the attachment to insert in the document
5448    * @return the attachment replaced by the passed attachment
5449    * @since 9.10RC1
5450    */
 
5451  236231 toggle public XWikiAttachment setAttachment(XWikiAttachment attachment)
5452    {
5453  236232 return this.attachmentList.set(attachment);
5454    }
5455   
5456    /**
5457    * @deprecated use {@link #setAttachment(String, InputStream, XWikiContext)} instead
5458    */
 
5459  8 toggle @Deprecated
5460    public XWikiAttachment addAttachment(String fileName, byte[] content, XWikiContext context) throws XWikiException
5461    {
5462  8 try {
5463  8 return setAttachment(fileName, new ByteArrayInputStream(content != null ? content : new byte[0]), context);
5464    } catch (IOException e) {
5465  0 throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_XWIKI_UNKNOWN,
5466    "Failed to set Attachment content", e);
5467    }
5468    }
5469   
5470    /**
5471    * @param fileName the name of the attachment
5472    * @param content the content of the attachment
5473    * @param context the XWiki context
5474    * @return the new attachment
5475    * @throws XWikiException never sent
5476    * @throws IOException when failing to read the passed content
5477    * @deprecated since 9.10RC1, use {@link #setAttachment(String, InputStream, XWikiContext)} instead
5478    */
 
5479  2 toggle @Deprecated
5480    public XWikiAttachment addAttachment(String fileName, InputStream content, XWikiContext context)
5481    throws XWikiException, IOException
5482    {
5483  2 return setAttachment(fileName, content, context);
5484    }
5485   
5486    /**
5487    * Create or update attachment with the passed name with the passed content.
5488    *
5489    * @param fileName the name of the attachment
5490    * @param content the content of the attachment
5491    * @param context the XWiki context
5492    * @return the new attachment
5493    * @throws IOException when failing to read the passed content
5494    * @since 9.10rc1
5495    */
 
5496  81 toggle public XWikiAttachment setAttachment(String fileName, InputStream content, XWikiContext context) throws IOException
5497    {
5498  81 int i = fileName.indexOf('\\');
5499  81 if (i == -1) {
5500  81 i = fileName.indexOf('/');
5501    }
5502   
5503  81 String filename = fileName.substring(i + 1);
5504   
5505  81 XWikiAttachment attachment = getAttachment(filename);
5506  81 if (attachment == null) {
5507  71 attachment = new XWikiAttachment(this, filename);
5508   
5509    // Add the attachment in the current doc
5510  71 setAttachment(attachment);
5511    }
5512   
5513  81 attachment.setContent(content);
5514  81 attachment.setAuthorReference(context.getUserReference());
5515   
5516  81 return attachment;
5517    }
5518   
 
5519  0 toggle public BaseObject getFirstObject(String fieldname)
5520    {
5521    // Keeping this function with context null for compatibility reasons.
5522    // It should not be used, since it would miss properties which are only defined in the class
5523    // and not present in the object because the object was not updated
5524  0 return getFirstObject(fieldname, null);
5525    }
5526   
 
5527  8874 toggle public BaseObject getFirstObject(String fieldname, XWikiContext context)
5528    {
5529  8874 Collection<List<BaseObject>> objectscoll = getXObjects().values();
5530  8874 if (objectscoll == null) {
5531  0 return null;
5532    }
5533   
5534  8874 for (List<BaseObject> objects : objectscoll) {
5535  25372 for (BaseObject obj : objects) {
5536  43722 if (obj != null) {
5537  43722 BaseClass bclass = obj.getXClass(context);
5538  43722 if (bclass != null) {
5539  43722 Set<String> set = bclass.getPropertyList();
5540  43722 if ((set != null) && set.contains(fieldname)) {
5541  8074 return obj;
5542    }
5543    }
5544  35648 Set<String> set = obj.getPropertyList();
5545  35648 if ((set != null) && set.contains(fieldname)) {
5546  1 return obj;
5547    }
5548    }
5549    }
5550    }
5551   
5552  799 return null;
5553    }
5554   
5555    /**
5556    * @since 2.2.3
5557    */
 
5558  0 toggle public void setProperty(EntityReference classReference, String fieldName, BaseProperty value)
5559    {
5560  0 BaseObject bobject = prepareXObject(classReference);
5561  0 bobject.safeput(fieldName, value);
5562    }
5563   
5564    /**
5565    * @deprecated since 2.2M2 use {@link #setProperty(EntityReference, String, BaseProperty)} instead
5566    */
 
5567  0 toggle @Deprecated
5568    public void setProperty(String className, String fieldName, BaseProperty value)
5569    {
5570  0 setProperty(getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
5571    fieldName, value);
5572    }
5573   
5574    /**
5575    * @since 2.2M2
5576    */
 
5577  61 toggle public int getIntValue(DocumentReference classReference, String fieldName)
5578    {
5579  61 BaseObject obj = getXObject(classReference, 0);
5580  61 if (obj == null) {
5581  12 return 0;
5582    }
5583   
5584  49 return obj.getIntValue(fieldName);
5585    }
5586   
5587    /**
5588    * @deprecated since 2.2M2 use {@link #getIntValue(DocumentReference, String)} instead
5589    */
 
5590  0 toggle @Deprecated
5591    public int getIntValue(String className, String fieldName)
5592    {
5593  0 return getIntValue(resolveClassReference(className), fieldName);
5594    }
5595   
5596    /**
5597    * @since 2.2M2
5598    */
 
5599  0 toggle public long getLongValue(DocumentReference classReference, String fieldName)
5600    {
5601  0 BaseObject obj = getXObject(classReference, 0);
5602  0 if (obj == null) {
5603  0 return 0;
5604    }
5605   
5606  0 return obj.getLongValue(fieldName);
5607    }
5608   
5609    /**
5610    * @deprecated since 2.2M2 use {@link #getLongValue(DocumentReference, String)} instead
5611    */
 
5612  0 toggle @Deprecated
5613    public long getLongValue(String className, String fieldName)
5614    {
5615  0 return getLongValue(resolveClassReference(className), fieldName);
5616    }
5617   
5618    /**
5619    * @since 6.2M1
5620    */
 
5621  0 toggle public String getStringValue(EntityReference classReference, String fieldName)
5622    {
5623  0 return getStringValue(resolveClassReference(classReference), fieldName);
5624    }
5625   
5626    /**
5627    * @since 2.2M2
5628    */
 
5629  5860 toggle public String getStringValue(DocumentReference classReference, String fieldName)
5630    {
5631  5860 BaseObject obj = getXObject(classReference);
5632  5852 if (obj == null) {
5633  2588 return "";
5634    }
5635   
5636  3255 String result = obj.getStringValue(fieldName);
5637  3256 if (result.equals(" ")) {
5638  0 return "";
5639    } else {
5640  3254 return result;
5641    }
5642    }
5643   
5644    /**
5645    * @deprecated since 2.2M2 use {@link #getStringValue(DocumentReference, String)} instead
5646    */
 
5647  2632 toggle @Deprecated
5648    public String getStringValue(String className, String fieldName)
5649    {
5650  2632 return getStringValue(resolveClassReference(className), fieldName);
5651    }
5652   
 
5653  0 toggle public int getIntValue(String fieldName)
5654    {
5655  0 BaseObject object = getFirstObject(fieldName, null);
5656  0 if (object == null) {
5657  0 return 0;
5658    } else {
5659  0 return object.getIntValue(fieldName);
5660    }
5661    }
5662   
 
5663  0 toggle public long getLongValue(String fieldName)
5664    {
5665  0 BaseObject object = getFirstObject(fieldName, null);
5666  0 if (object == null) {
5667  0 return 0;
5668    } else {
5669  0 return object.getLongValue(fieldName);
5670    }
5671    }
5672   
 
5673  0 toggle public String getStringValue(String fieldName)
5674    {
5675  0 BaseObject object = getFirstObject(fieldName, null);
5676  0 if (object == null) {
5677  0 return "";
5678    }
5679   
5680  0 String result = object.getStringValue(fieldName);
5681  0 if (result.equals(" ")) {
5682  0 return "";
5683    } else {
5684  0 return result;
5685    }
5686    }
5687   
5688    /**
5689    * @since 2.2.3
5690    */
 
5691  20 toggle public void setStringValue(EntityReference classReference, String fieldName, String value)
5692    {
5693  20 BaseObject bobject = prepareXObject(classReference);
5694  20 bobject.setStringValue(fieldName, value);
5695    }
5696   
5697    /**
5698    * @deprecated since 2.2M2 use {@link #setStringValue(EntityReference, String, String)} instead
5699    */
 
5700  19 toggle @Deprecated
5701    public void setStringValue(String className, String fieldName, String value)
5702    {
5703  19 setStringValue(
5704    getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
5705    fieldName, value);
5706    }
5707   
5708    /**
5709    * @since 2.2M2
5710    */
 
5711  0 toggle public List getListValue(DocumentReference classReference, String fieldName)
5712    {
5713  0 BaseObject obj = getXObject(classReference);
5714  0 if (obj == null) {
5715  0 return new ArrayList();
5716    }
5717   
5718  0 return obj.getListValue(fieldName);
5719    }
5720   
5721    /**
5722    * @deprecated since 2.2M2 use {@link #getListValue(DocumentReference, String)} instead
5723    */
 
5724  0 toggle @Deprecated
5725    public List getListValue(String className, String fieldName)
5726    {
5727  0 return getListValue(resolveClassReference(className), fieldName);
5728    }
5729   
 
5730  0 toggle public List getListValue(String fieldName)
5731    {
5732  0 BaseObject object = getFirstObject(fieldName, null);
5733  0 if (object == null) {
5734  0 return new ArrayList();
5735    }
5736   
5737  0 return object.getListValue(fieldName);
5738    }
5739   
5740    /**
5741    * @since 2.2.3
5742    */
 
5743  8 toggle public void setStringListValue(EntityReference classReference, String fieldName, List value)
5744    {
5745  8 BaseObject bobject = prepareXObject(classReference);
5746  8 bobject.setStringListValue(fieldName, value);
5747    }
5748   
5749    /**
5750    * @deprecated since 2.2M2 use {@link #setStringListValue(EntityReference, String, List)} instead
5751    */
 
5752  8 toggle @Deprecated
5753    public void setStringListValue(String className, String fieldName, List value)
5754    {
5755  8 setStringListValue(
5756    getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
5757    fieldName, value);
5758    }
5759   
5760    /**
5761    * @since 2.2.3
5762    */
 
5763  14 toggle public void setDBStringListValue(EntityReference classReference, String fieldName, List value)
5764    {
5765  14 BaseObject bobject = prepareXObject(classReference);
5766  14 bobject.setDBStringListValue(fieldName, value);
5767    }
5768   
5769    /**
5770    * @deprecated since 2.2M2 use {@link #setDBStringListValue(EntityReference, String, List)} instead
5771    */
 
5772  14 toggle @Deprecated
5773    public void setDBStringListValue(String className, String fieldName, List value)
5774    {
5775  14 setDBStringListValue(
5776    getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
5777    fieldName, value);
5778    }
5779   
5780    /**
5781    * @since 2.2.3
5782    */
 
5783  0 toggle public void setLargeStringValue(EntityReference classReference, String fieldName, String value)
5784    {
5785  0 BaseObject bobject = prepareXObject(classReference);
5786  0 bobject.setLargeStringValue(fieldName, value);
5787    }
5788   
5789    /**
5790    * @deprecated since 2.2M2 use {@link #setLargeStringValue(EntityReference, String, String)} instead
5791    */
 
5792  0 toggle @Deprecated
5793    public void setLargeStringValue(String className, String fieldName, String value)
5794    {
5795  0 setLargeStringValue(
5796    getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
5797    fieldName, value);
5798    }
5799   
5800    /**
5801    * @since 2.2.3
5802    */
 
5803  0 toggle public void setIntValue(EntityReference classReference, String fieldName, int value)
5804    {
5805  0 BaseObject bobject = prepareXObject(classReference);
5806  0 bobject.setIntValue(fieldName, value);
5807    }
5808   
5809    /**
5810    * @deprecated since 2.2M2 use {@link #setIntValue(EntityReference, String, int)} instead
5811    */
 
5812  0 toggle @Deprecated
5813    public void setIntValue(String className, String fieldName, int value)
5814    {
5815  0 setIntValue(getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
5816    fieldName, value);
5817    }
5818   
5819    /**
5820    * Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
5821    *
5822    * @deprecated since 2.2M1 use {@link #getDocumentReference()} instead
5823    */
 
5824  537831 toggle @Deprecated
5825    public String getDatabase()
5826    {
5827  537822 return getDocumentReference().getWikiReference().getName();
5828    }
5829   
5830    /**
5831    * Note that this method cannot be removed for now since it's used by Hibernate for loading a XWikiDocument.
5832    *
5833    * @deprecated since 2.2M1 use {@link #setDocumentReference(DocumentReference)} instead
5834    */
 
5835  6318 toggle @Deprecated
5836    public void setDatabase(String database)
5837    {
5838  6318 if (database != null) {
5839  6318 DocumentReference reference = getDocumentReference();
5840  6318 WikiReference wiki = reference.getWikiReference();
5841  6318 WikiReference newWiki = new WikiReference(database);
5842  6318 if (!newWiki.equals(wiki)) {
5843  1 setDocumentReferenceInternal(reference.replaceParent(wiki, newWiki));
5844    }
5845    }
5846    }
5847   
5848    /**
5849    * Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
5850    *
5851    * @deprecated since 4.3M2 use {@link #getLocale()} instead
5852    */
 
5853  101318 toggle @Deprecated
5854    public String getLanguage()
5855    {
5856  101317 return getLocale().toString();
5857    }
5858   
5859    /**
5860    * Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
5861    *
5862    * @deprecated since 4.3M2 use {@link #setLocale(Locale)} instead
5863    */
 
5864  25739 toggle @Deprecated
5865    public void setLanguage(String language)
5866    {
5867  25738 setLocale(LocaleUtils.toLocale(Util.normalizeLanguage(language), Locale.ROOT));
5868    }
5869   
5870    /**
5871    * @return the locale of the document
5872    */
 
5873  15404235 toggle public Locale getLocale()
5874    {
5875  15404226 return this.locale != null ? this.locale : Locale.ROOT;
5876    }
5877   
5878    /**
5879    * @param locale the locale of the document
5880    */
 
5881  263957 toggle public void setLocale(Locale locale)
5882    {
5883  263959 this.locale = locale;
5884   
5885  263954 setMetaDataDirty(true);
5886   
5887    // Clean various caches
5888   
5889  263956 this.keyCache = null;
5890  263954 this.localKeyCache = null;
5891    }
5892   
5893    /**
5894    * Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
5895    *
5896    * @deprecated since 4.3M2 use {@link #getDefaultLocale()} instead
5897    */
 
5898  64960 toggle @Deprecated
5899    public String getDefaultLanguage()
5900    {
5901  64960 return getDefaultLocale().toString();
5902    }
5903   
5904    /**
5905    * Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
5906    *
5907    * @deprecated since 4.3M2 use {@link #setDefaultLocale(Locale)} instead
5908    */
 
5909  19701 toggle @Deprecated
5910    public void setDefaultLanguage(String defaultLanguage)
5911    {
5912  19705 setDefaultLocale(LocaleUtils.toLocale(defaultLanguage, Locale.ROOT));
5913    }
5914   
 
5915  915738 toggle public Locale getDefaultLocale()
5916    {
5917  915769 return this.defaultLocale != null ? this.defaultLocale : Locale.ROOT;
5918    }
5919   
 
5920  259495 toggle public void setDefaultLocale(Locale defaultLocale)
5921    {
5922  259498 this.defaultLocale = defaultLocale;
5923   
5924  259501 setMetaDataDirty(true);
5925    }
5926   
 
5927  431109 toggle public int getTranslation()
5928    {
5929  431101 return getLocale().equals(Locale.ROOT) ? 0 : 1;
5930    }
5931   
5932    /**
5933    * Note that this method cannot be removed for now since it's called by Hibernate when loading a XWikiDocument.
5934    *
5935    * @deprecated since 5.4.6, stored in the database to speedup some queries (really ?) but in {@link XWikiDocument}
5936    * it's calculated based on the document locale
5937    */
 
5938  19084 toggle @Deprecated
5939    public void setTranslation(int translation)
5940    {
5941    // Do nothing
5942    }
5943   
 
5944  436 toggle public String getTranslatedContent(XWikiContext context) throws XWikiException
5945    {
5946  436 String language = context.getWiki().getLanguagePreference(context);
5947   
5948  436 return getTranslatedContent(language, context);
5949    }
5950   
 
5951  436 toggle public String getTranslatedContent(String locale, XWikiContext context) throws XWikiException
5952    {
5953  436 XWikiDocument tdoc = getTranslatedDocument(locale, context);
5954  436 return tdoc.getContent();
5955    }
5956   
 
5957  484569 toggle public XWikiDocument getTranslatedDocument(XWikiContext context) throws XWikiException
5958    {
5959  484569 String locale = context.getWiki().getLanguagePreference(context);
5960  484573 return getTranslatedDocument(locale, context);
5961    }
5962   
5963    /**
5964    * Return the document in the provided language.
5965    * <p>
5966    * This method return this if the provided language does not exists. See
5967    *
5968    * @param language the language of the document to return
5969    * @param context the XWiki Context
5970    * @return the document in the provided language or this if the provided language does not exists
5971    * @throws XWikiException error when loading the document
5972    * @deprecated since 4.3M2 use {@link #getTranslatedDocument(Locale, XWikiContext)} insead
5973    */
 
5974  486118 toggle @Deprecated
5975    public XWikiDocument getTranslatedDocument(String language, XWikiContext context) throws XWikiException
5976    {
5977  486146 return getTranslatedDocument(LocaleUtils.toLocale(language, Locale.ROOT), context);
5978    }
5979   
5980    /**
5981    * Return the document in the provided language.
5982    * <p>
5983    * This method return this if the provided language does not exists. See
5984    *
5985    * @param locale the locale of the document to return
5986    * @param context the XWiki Context
5987    * @return the document in the provided language or this if the provided language does not exists
5988    * @throws XWikiException error when loading the document
5989    */
 
5990  849321 toggle public XWikiDocument getTranslatedDocument(Locale locale, XWikiContext context) throws XWikiException
5991    {
5992  849326 XWikiDocument tdoc = this;
5993   
5994  849363 if (locale != null && !locale.equals(Locale.ROOT) && !locale.equals(getDefaultLocale())) {
5995  364573 try {
5996  364585 tdoc = context.getWiki().getDocument(new DocumentReference(getDocumentReference(), locale), context);
5997   
5998  364653 if (!tdoc.isNew()) {
5999  4053 return tdoc;
6000    }
6001    } catch (Exception e) {
6002  0 LOGGER.error("Error when loading document {} for locale {}", getDocumentReference(), locale, e);
6003    }
6004   
6005  360597 tdoc = getTranslatedDocument(LocaleUtils.getParentLocale(locale), context);
6006    }
6007   
6008  845306 return tdoc;
6009    }
6010   
6011    /**
6012    * @deprecated since 4.3M1 use {@link #getRealLocale()} instead
6013    */
 
6014  182 toggle @Deprecated
6015    public String getRealLanguage(XWikiContext context) throws XWikiException
6016    {
6017  182 return getRealLanguage();
6018    }
6019   
6020    /**
6021    * {@inheritDoc}
6022    *
6023    * @see org.xwiki.bridge.DocumentModelBridge#getRealLanguage()
6024    * @deprecated since 4.3M1 use {@link #getRealLocale()} instead
6025    */
 
6026  48738 toggle @Override
6027    @Deprecated
6028    public String getRealLanguage()
6029    {
6030  48738 String lang = getLanguage();
6031  48738 if (lang.equals("")) {
6032  48712 return getDefaultLanguage();
6033    } else {
6034  26 return lang;
6035    }
6036    }
6037   
6038    /**
6039    * @return the actual locale of the document
6040    */
 
6041  123623 toggle public Locale getRealLocale()
6042    {
6043  123623 Locale locale = getLocale();
6044  123624 if (locale.equals(Locale.ROOT)) {
6045  120897 locale = getDefaultLocale();
6046    }
6047   
6048  123623 return locale;
6049    }
6050   
6051    /**
6052    * @deprecated since 5.1M2 use {@link #getTranslationLocales(XWikiContext)} instead
6053    */
 
6054  51699 toggle @Deprecated
6055    public List<String> getTranslationList(XWikiContext context) throws XWikiException
6056    {
6057  51699 return getStore().getTranslationList(this, context);
6058    }
6059   
6060    /**
6061    * The locales of the translation of this document (the default locale is not included).
6062    *
6063    * @param context the XWiki context
6064    * @return the locales of the translations
6065    * @throws XWikiException if retriving the translations from the database failed
6066    */
 
6067  41856 toggle public List<Locale> getTranslationLocales(XWikiContext context) throws XWikiException
6068    {
6069  41856 List<String> translations = getTranslationList(context);
6070   
6071  41856 List<Locale> locales = new ArrayList<Locale>(translations.size());
6072  41855 for (String translationString : translations) {
6073  31185 locales.add(LocaleUtils.toLocale(translationString));
6074    }
6075   
6076  41855 return locales;
6077    }
6078   
 
6079  0 toggle public List<Delta> getXMLDiff(XWikiDocument fromDoc, XWikiDocument toDoc, XWikiContext context)
6080    throws XWikiException, DifferentiationFailedException
6081    {
6082  0 return getDeltas(
6083    Diff.diff(ToString.stringToArray(fromDoc.toXML(context)), ToString.stringToArray(toDoc.toXML(context))));
6084    }
6085   
 
6086  1 toggle public List<Delta> getContentDiff(XWikiDocument fromDoc, XWikiDocument toDoc, XWikiContext context)
6087    throws XWikiException, DifferentiationFailedException
6088    {
6089  1 return getDeltas(
6090    Diff.diff(ToString.stringToArray(fromDoc.getContent()), ToString.stringToArray(toDoc.getContent())));
6091    }
6092   
 
6093  0 toggle public List<Delta> getContentDiff(String fromRev, String toRev, XWikiContext context)
6094    throws XWikiException, DifferentiationFailedException
6095    {
6096  0 XWikiDocument fromDoc = context.getWiki().getDocument(this, fromRev, context);
6097  0 XWikiDocument toDoc = context.getWiki().getDocument(this, toRev, context);
6098  0 return getContentDiff(fromDoc, toDoc, context);
6099    }
6100   
 
6101  0 toggle public List<Delta> getContentDiff(String fromRev, XWikiContext context)
6102    throws XWikiException, DifferentiationFailedException
6103    {
6104  0 XWikiDocument revdoc = context.getWiki().getDocument(this, fromRev, context);
6105  0 return getContentDiff(revdoc, this, context);
6106    }
6107   
 
6108  0 toggle public List<Delta> getLastChanges(XWikiContext context) throws XWikiException, DifferentiationFailedException
6109    {
6110  0 Version version = getRCSVersion();
6111  0 try {
6112  0 String prev = getDocumentArchive(context).getPrevVersion(version).toString();
6113  0 XWikiDocument prevDoc = context.getWiki().getDocument(this, prev, context);
6114   
6115  0 return getDeltas(
6116    Diff.diff(ToString.stringToArray(prevDoc.getContent()), ToString.stringToArray(getContent())));
6117    } catch (Exception ex) {
6118  0 LOGGER.debug("Exception getting differences from previous version: " + ex.getMessage());
6119    }
6120   
6121  0 return new ArrayList<Delta>();
6122    }
6123   
 
6124  0 toggle public List<Delta> getRenderedContentDiff(XWikiDocument fromDoc, XWikiDocument toDoc, XWikiContext context)
6125    throws XWikiException, DifferentiationFailedException
6126    {
6127  0 String originalContent = fromDoc.getRenderedContent(context);
6128  0 String newContent = toDoc.getRenderedContent(context);
6129   
6130  0 return getDeltas(Diff.diff(ToString.stringToArray(originalContent), ToString.stringToArray(newContent)));
6131    }
6132   
 
6133  0 toggle public List<Delta> getRenderedContentDiff(String fromRev, String toRev, XWikiContext context)
6134    throws XWikiException, DifferentiationFailedException
6135    {
6136  0 XWikiDocument fromDoc = context.getWiki().getDocument(this, fromRev, context);
6137  0 XWikiDocument toDoc = context.getWiki().getDocument(this, toRev, context);
6138   
6139  0 return getRenderedContentDiff(fromDoc, toDoc, context);
6140    }
6141   
 
6142  0 toggle public List<Delta> getRenderedContentDiff(String fromRev, XWikiContext context)
6143    throws XWikiException, DifferentiationFailedException
6144    {
6145  0 XWikiDocument revdoc = context.getWiki().getDocument(this, fromRev, context);
6146   
6147  0 return getRenderedContentDiff(revdoc, this, context);
6148    }
6149   
 
6150  1 toggle protected List<Delta> getDeltas(Revision rev)
6151    {
6152  1 List<Delta> list = new ArrayList<Delta>();
6153  2 for (int i = 0; i < rev.size(); i++) {
6154  1 list.add(rev.getDelta(i));
6155    }
6156   
6157  1 return list;
6158    }
6159   
 
6160  0 toggle public List<MetaDataDiff> getMetaDataDiff(String fromRev, String toRev, XWikiContext context) throws XWikiException
6161    {
6162  0 XWikiDocument fromDoc = context.getWiki().getDocument(this, fromRev, context);
6163  0 XWikiDocument toDoc = context.getWiki().getDocument(this, toRev, context);
6164   
6165  0 return getMetaDataDiff(fromDoc, toDoc, context);
6166    }
6167   
 
6168  0 toggle public List<MetaDataDiff> getMetaDataDiff(String fromRev, XWikiContext context) throws XWikiException
6169    {
6170  0 XWikiDocument revdoc = context.getWiki().getDocument(this, fromRev, context);
6171   
6172  0 return getMetaDataDiff(revdoc, this, context);
6173    }
6174   
 
6175  17 toggle public List<MetaDataDiff> getMetaDataDiff(XWikiDocument fromDoc, XWikiDocument toDoc, XWikiContext context)
6176    throws XWikiException
6177    {
6178  17 List<MetaDataDiff> list = new ArrayList<MetaDataDiff>();
6179   
6180  17 if (fromDoc == null || toDoc == null) {
6181  0 return list;
6182    }
6183   
6184  17 if (!fromDoc.getTitle().equals(toDoc.getTitle())) {
6185  3 list.add(new MetaDataDiff("title", fromDoc.getTitle(), toDoc.getTitle()));
6186    }
6187   
6188  17 if (ObjectUtils.notEqual(fromDoc.getRelativeParentReference(), toDoc.getRelativeParentReference())) {
6189  1 list.add(new MetaDataDiff("parent", fromDoc.getParent(), toDoc.getParent()));
6190    }
6191   
6192  17 if (ObjectUtils.notEqual(fromDoc.getAuthorReference(), toDoc.getAuthorReference())) {
6193  6 list.add(new MetaDataDiff("author", fromDoc.getAuthor(), toDoc.getAuthor()));
6194    }
6195   
6196  17 if (ObjectUtils.notEqual(fromDoc.getDocumentReference(), toDoc.getDocumentReference())) {
6197  0 list.add(new MetaDataDiff("reference", fromDoc.getDocumentReference(), toDoc.getDocumentReference()));
6198    }
6199   
6200  17 if (!fromDoc.getSpace().equals(toDoc.getSpace())) {
6201  0 list.add(new MetaDataDiff("web", fromDoc.getSpace(), toDoc.getSpace()));
6202    }
6203   
6204  17 if (!fromDoc.getName().equals(toDoc.getName())) {
6205  0 list.add(new MetaDataDiff("name", fromDoc.getName(), toDoc.getName()));
6206    }
6207   
6208  17 if (ObjectUtils.notEqual(fromDoc.getLocale(), toDoc.getLocale())) {
6209  0 list.add(new MetaDataDiff("language", fromDoc.getLanguage(), toDoc.getLanguage()));
6210    }
6211   
6212  17 if (ObjectUtils.notEqual(fromDoc.getDefaultLocale(), toDoc.getDefaultLocale())) {
6213  4 list.add(new MetaDataDiff("defaultLanguage", fromDoc.getDefaultLanguage(), toDoc.getDefaultLanguage()));
6214    }
6215   
6216  17 if (ObjectUtils.notEqual(fromDoc.getSyntax(), toDoc.getSyntax())) {
6217  0 list.add(new MetaDataDiff("syntax", fromDoc.getSyntax(), toDoc.getSyntax()));
6218    }
6219   
6220  17 if (fromDoc.isHidden() != toDoc.isHidden()) {
6221  0 list.add(new MetaDataDiff("hidden", fromDoc.isHidden(), toDoc.isHidden()));
6222    }
6223   
6224  17 return list;
6225    }
6226   
 
6227  0 toggle public List<List<ObjectDiff>> getObjectDiff(String fromRev, String toRev, XWikiContext context)
6228    throws XWikiException
6229    {
6230  0 XWikiDocument fromDoc = context.getWiki().getDocument(this, fromRev, context);
6231  0 XWikiDocument toDoc = context.getWiki().getDocument(this, toRev, context);
6232   
6233  0 return getObjectDiff(fromDoc, toDoc, context);
6234    }
6235   
 
6236  0 toggle public List<List<ObjectDiff>> getObjectDiff(String fromRev, XWikiContext context) throws XWikiException
6237    {
6238  0 XWikiDocument revdoc = context.getWiki().getDocument(this, fromRev, context);
6239   
6240  0 return getObjectDiff(revdoc, this, context);
6241    }
6242   
6243    /**
6244    * Return the object differences between two document versions. There is no hard requirement on the order of the two
6245    * versions, but the results are semantically correct only if the two versions are given in the right order.
6246    *
6247    * @param fromDoc The old ('before') version of the document.
6248    * @param toDoc The new ('after') version of the document.
6249    * @param context The {@link com.xpn.xwiki.XWikiContext context}.
6250    * @return The object differences. The returned list's elements are other lists, one for each changed object. The
6251    * inner lists contain {@link ObjectDiff} elements, one object for each changed property of the object.
6252    * Additionally, if the object was added or removed, then the first entry in the list will be an
6253    * "object-added" or "object-removed" marker.
6254    */
 
6255  1385 toggle public List<List<ObjectDiff>> getObjectDiff(XWikiDocument fromDoc, XWikiDocument toDoc, XWikiContext context)
6256    {
6257  1385 List<List<ObjectDiff>> difflist = new ArrayList<List<ObjectDiff>>();
6258   
6259    // Since objects could have been deleted or added, we iterate on both the old and the new
6260    // object collections.
6261    // First, iterate over the old objects.
6262  1385 for (List<BaseObject> objects : fromDoc.getXObjects().values()) {
6263  2107 for (BaseObject originalObj : objects) {
6264    // This happens when objects are deleted, and the document is still in the cache
6265    // storage.
6266  3515 if (originalObj != null) {
6267  3483 BaseObject newObj = toDoc.getXObject(originalObj.getXClassReference(), originalObj.getNumber());
6268  3483 List<ObjectDiff> dlist;
6269  3483 if (newObj == null) {
6270    // The object was deleted.
6271  39 dlist = new BaseObject().getDiff(originalObj, context);
6272  39 ObjectDiff deleteMarker =
6273    new ObjectDiff(originalObj.getXClassReference(), originalObj.getNumber(),
6274    originalObj.getGuid(), ObjectDiff.ACTION_OBJECTREMOVED, "", "", "", "");
6275  39 dlist.add(0, deleteMarker);
6276    } else {
6277    // The object exists in both versions, but might have been changed.
6278  3444 dlist = newObj.getDiff(originalObj, context);
6279    }
6280  3483 if (!dlist.isEmpty()) {
6281  356 difflist.add(dlist);
6282    }
6283    }
6284    }
6285    }
6286   
6287    // Second, iterate over the objects which are only in the new version.
6288  1385 for (List<BaseObject> objects : toDoc.getXObjects().values()) {
6289  2271 for (BaseObject newObj : objects) {
6290    // This happens when objects are deleted, and the document is still in the cache
6291    // storage.
6292  3874 if (newObj != null) {
6293  3807 BaseObject originalObj = fromDoc.getXObject(newObj.getXClassReference(), newObj.getNumber());
6294  3807 if (originalObj == null) {
6295    // TODO: Refactor this so that getDiff() accepts null Object as input.
6296    // Only consider added objects, the other case was treated above.
6297  363 originalObj = new BaseObject();
6298  363 originalObj.setXClassReference(newObj.getRelativeXClassReference());
6299  363 originalObj.setNumber(newObj.getNumber());
6300  363 originalObj.setGuid(newObj.getGuid());
6301  363 List<ObjectDiff> dlist = newObj.getDiff(originalObj, context);
6302  363 ObjectDiff addMarker = new ObjectDiff(newObj.getXClassReference(), newObj.getNumber(),
6303    newObj.getGuid(), ObjectDiff.ACTION_OBJECTADDED, "", "", "", "");
6304  363 dlist.add(0, addMarker);
6305  363 if (!dlist.isEmpty()) {
6306  363 difflist.add(dlist);
6307    }
6308    }
6309    }
6310    }
6311    }
6312   
6313  1385 return difflist;
6314    }
6315   
 
6316  1327 toggle public List<List<ObjectDiff>> getClassDiff(XWikiDocument fromDoc, XWikiDocument toDoc, XWikiContext context)
6317    {
6318  1327 List<List<ObjectDiff>> difflist = new ArrayList<List<ObjectDiff>>();
6319  1327 BaseClass oldClass = fromDoc.getXClass();
6320  1327 BaseClass newClass = toDoc.getXClass();
6321   
6322  1327 if ((newClass == null) && (oldClass == null)) {
6323  0 return difflist;
6324    }
6325   
6326  1327 List<ObjectDiff> dlist = newClass.getDiff(oldClass, context);
6327  1327 if (!dlist.isEmpty()) {
6328  121 difflist.add(dlist);
6329    }
6330   
6331  1327 return difflist;
6332    }
6333   
6334    /**
6335    * @param fromDoc
6336    * @param toDoc
6337    * @param context
6338    * @return
6339    */
 
6340  1379 toggle public List<AttachmentDiff> getAttachmentDiff(XWikiDocument fromDoc, XWikiDocument toDoc, XWikiContext context)
6341    {
6342  1379 List<AttachmentDiff> difflist = new ArrayList<AttachmentDiff>();
6343  1379 for (XWikiAttachment origAttach : fromDoc.getAttachmentList()) {
6344  134 String fileName = origAttach.getFilename();
6345  134 XWikiAttachment newAttach = toDoc.getAttachment(fileName);
6346  134 if (newAttach == null) {
6347  9 difflist.add(new AttachmentDiff(fileName, org.xwiki.diff.Delta.Type.DELETE, origAttach, newAttach));
6348    } else {
6349  125 try {
6350  125 if (!origAttach.equalsData(newAttach, context)) {
6351  8 difflist
6352    .add(new AttachmentDiff(fileName, org.xwiki.diff.Delta.Type.CHANGE, origAttach, newAttach));
6353    }
6354    } catch (XWikiException e) {
6355  0 LOGGER.error("Failed to compare attachments [{}] and [{}]", origAttach.getReference(),
6356    newAttach.getReference(), e);
6357    }
6358    }
6359    }
6360   
6361  1379 for (XWikiAttachment newAttach : toDoc.getAttachmentList()) {
6362  196 String fileName = newAttach.getFilename();
6363  196 XWikiAttachment origAttach = fromDoc.getAttachment(fileName);
6364  196 if (origAttach == null) {
6365  71 difflist.add(new AttachmentDiff(fileName, org.xwiki.diff.Delta.Type.INSERT, origAttach, newAttach));
6366    }
6367    }
6368   
6369  1379 return difflist;
6370    }
6371   
6372    /**
6373    * Rename the current document and all the backlinks leading to it. Will also change parent field in all documents
6374    * which list the document we are renaming as their parent.
6375    * <p>
6376    * See {@link #rename(String, java.util.List, com.xpn.xwiki.XWikiContext)} for more details.
6377    *
6378    * @param newDocumentReference the new document reference
6379    * @param context the ubiquitous XWiki Context
6380    * @throws XWikiException in case of an error
6381    * @since 2.2M2
6382    */
 
6383  0 toggle public void rename(DocumentReference newDocumentReference, XWikiContext context) throws XWikiException
6384    {
6385  0 rename(newDocumentReference, getBackLinkedReferences(context), context);
6386    }
6387   
6388    /**
6389    * @deprecated since 2.2M2 use {@link #rename(DocumentReference, XWikiContext)}
6390    */
 
6391  0 toggle @Deprecated
6392    public void rename(String newDocumentName, XWikiContext context) throws XWikiException
6393    {
6394  0 rename(newDocumentName, getBackLinkedPages(context), context);
6395    }
6396   
6397    /**
6398    * Rename the current document and all the links pointing to it in the list of passed backlink documents. The
6399    * renaming algorithm takes into account the fact that there are several ways to write a link to a given page and
6400    * all those forms need to be renamed. For example the following links all point to the same page:
6401    * <ul>
6402    * <li>[Page]</li>
6403    * <li>[Page?param=1]</li>
6404    * <li>[currentwiki:Page]</li>
6405    * <li>[CurrentSpace.Page]</li>
6406    * <li>[currentwiki:CurrentSpace.Page]</li>
6407    * </ul>
6408    * <p>
6409    * Note: links without a space are renamed with the space added and all documents which have the document being
6410    * renamed as parent have their parent field set to "currentwiki:CurrentSpace.Page".
6411    * </p>
6412    *
6413    * @param newDocumentReference the new document reference
6414    * @param backlinkDocumentReferences the list of references of documents to parse and for which links will be
6415    * modified to point to the new document reference
6416    * @param context the ubiquitous XWiki Context
6417    * @throws XWikiException in case of an error
6418    * @since 2.2M2
6419    */
 
6420  0 toggle public void rename(DocumentReference newDocumentReference, List<DocumentReference> backlinkDocumentReferences,
6421    XWikiContext context) throws XWikiException
6422    {
6423  0 rename(newDocumentReference, backlinkDocumentReferences, getChildrenReferences(context), context);
6424    }
6425   
6426    /**
6427    * @deprecated since 2.2M2 use {@link #rename(DocumentReference, java.util.List, com.xpn.xwiki.XWikiContext)}
6428    */
 
6429  0 toggle @Deprecated
6430    public void rename(String newDocumentName, List<String> backlinkDocumentNames, XWikiContext context)
6431    throws XWikiException
6432    {
6433  0 rename(newDocumentName, backlinkDocumentNames, getChildren(context), context);
6434    }
6435   
6436    /**
6437    * Same as {@link #rename(DocumentReference, List, XWikiContext)} but the list of documents having the current
6438    * document as their parent is passed in parameter.
6439    *
6440    * @param newDocumentReference the new document reference
6441    * @param backlinkDocumentReferences the list of references of documents to parse and for which links will be
6442    * modified to point to the new document reference
6443    * @param childDocumentReferences the list of references of document whose parent field will be set to the new
6444    * document reference
6445    * @param context the ubiquitous XWiki Context
6446    * @throws XWikiException in case of an error
6447    * @since 2.2M2
6448    */
 
6449  2 toggle public void rename(DocumentReference newDocumentReference, List<DocumentReference> backlinkDocumentReferences,
6450    List<DocumentReference> childDocumentReferences, XWikiContext context) throws XWikiException
6451    {
6452    // TODO: Do all this in a single DB transaction as otherwise the state will be unknown if
6453    // something fails in the middle...
6454   
6455    // TODO: Why do we verify if the document has just been created and not been saved.
6456    // If the user is trying to rename to the same name... In that case, simply exits for efficiency.
6457  2 if (isNew() || getDocumentReference().equals(newDocumentReference)) {
6458  0 return;
6459    }
6460   
6461    // Grab the xwiki object, it gets used a few times.
6462  2 XWiki xwiki = context.getWiki();
6463   
6464    // Step 1: Copy the document and all its translations under a new document with the new reference.
6465  2 xwiki.copyDocument(getDocumentReference(), newDocumentReference, false, context);
6466   
6467    // Step 2: For each child document, update its parent reference.
6468  2 if (childDocumentReferences != null) {
6469  2 for (DocumentReference childDocumentReference : childDocumentReferences) {
6470  2 XWikiDocument childDocument = xwiki.getDocument(childDocumentReference, context);
6471  2 String compactReference = getCompactEntityReferenceSerializer().serialize(newDocumentReference);
6472  2 childDocument.setParent(compactReference);
6473  2 String saveMessage = localizePlainOrKey("core.comment.renameParent", compactReference);
6474  2 childDocument.setAuthorReference(context.getUserReference());
6475  2 xwiki.saveDocument(childDocument, saveMessage, true, context);
6476    }
6477    }
6478   
6479    // Step 3: For each backlink to rename, parse the backlink document and replace the links with the new name.
6480  2 for (DocumentReference backlinkDocumentReference : backlinkDocumentReferences) {
6481  3 XWikiDocument backlinkRootDocument = xwiki.getDocument(backlinkDocumentReference, context);
6482   
6483    // Update default locale instance
6484  3 renameLinks(backlinkRootDocument, getDocumentReference(), newDocumentReference, context);
6485   
6486    // Update translations
6487  3 for (Locale locale : backlinkRootDocument.getTranslationLocales(context)) {
6488  0 XWikiDocument backlinkDocument = backlinkRootDocument.getTranslatedDocument(locale, context);
6489   
6490  0 renameLinks(backlinkDocument, getDocumentReference(), newDocumentReference, context);
6491    }
6492    }
6493   
6494    // Get new document
6495  2 XWikiDocument newDocument = xwiki.getDocument(newDocumentReference, context);
6496   
6497    // Step 4: Refactor the relative links contained in the document to make sure they are relative to the new
6498    // document's location.
6499  2 if (Utils.getContextComponentManager().hasComponent(BlockRenderer.class, getSyntax().toIdString())) {
6500    // Only support syntax for which a renderer is provided
6501   
6502  1 LinkedResourceHelper linkedResourceHelper = Utils.getComponent(LinkedResourceHelper.class);
6503   
6504  1 DocumentReference oldDocumentReference = getDocumentReference();
6505   
6506  1 XDOM newDocumentXDOM = newDocument.getXDOM();
6507  1 List<Block> blocks = linkedResourceHelper.getBlocks(newDocumentXDOM);
6508   
6509    // FIXME: Duplicate code. See org.xwiki.refactoring.internal.DefaultLinkRefactoring#updateRelativeLinks in
6510    // xwiki-platform-refactoring-default
6511  1 boolean modified = false;
6512  1 for (Block block : blocks) {
6513  1 ResourceReference resourceReference = linkedResourceHelper.getResourceReference(block);
6514  1 if (resourceReference == null) {
6515    // Skip invalid blocks.
6516  0 continue;
6517    }
6518   
6519  1 ResourceType resourceType = resourceReference.getType();
6520   
6521    // TODO: support ATTACHMENT as well.
6522  1 if (!ResourceType.DOCUMENT.equals(resourceType) && !ResourceType.SPACE.equals(resourceType)) {
6523    // We are currently only interested in Document or Space references.
6524  0 continue;
6525    }
6526   
6527    // current link, use the old document's reference to fill in blanks.
6528  1 EntityReference oldLinkReference = getResourceReferenceEntityReferenceResolver()
6529    .resolve(resourceReference, null, oldDocumentReference);
6530    // new link, use the new document's reference to fill in blanks.
6531  1 EntityReference newLinkReference = getResourceReferenceEntityReferenceResolver()
6532    .resolve(resourceReference, null, newDocumentReference);
6533   
6534    // If the new and old link references don`t match, then we must update the relative link.
6535  1 if (!newLinkReference.equals(oldLinkReference)) {
6536  1 modified = true;
6537   
6538    // Serialize the old (original) link relative to the new document's location, in compact form.
6539  1 String serializedLinkReference =
6540    getCompactWikiEntityReferenceSerializer().serialize(oldLinkReference, newDocumentReference);
6541   
6542    // Update the reference in the XDOM.
6543  1 linkedResourceHelper.setResourceReferenceString(block, serializedLinkReference);
6544    }
6545    }
6546   
6547    // Set the new content and save document if needed
6548  1 if (modified) {
6549  1 newDocument.setContent(newDocumentXDOM);
6550  1 newDocument.setAuthorReference(context.getUserReference());
6551  1 xwiki.saveDocument(newDocument, context);
6552    }
6553    }
6554   
6555    // Step 5: Delete the old document
6556  2 xwiki.deleteDocument(this, context);
6557   
6558    // Step 6: The current document needs to point to the renamed document as otherwise it's pointing to an
6559    // invalid XWikiDocument object as it's been deleted...
6560  2 clone(newDocument);
6561    }
6562   
6563    /**
6564    * Rename links in passed document and save it if needed.
6565    */
 
6566  3 toggle private void renameLinks(XWikiDocument backlinkDocument, DocumentReference oldLink, DocumentReference newLink,
6567    XWikiContext context) throws XWikiException
6568    {
6569    // FIXME: Duplicate code. See org.xwiki.refactoring.internal.DefaultLinkRefactoring#renameLinks in
6570    // xwiki-platform-refactoring-default
6571  3 getOldRendering().renameLinks(backlinkDocument, oldLink, newLink, context);
6572   
6573    // Save if content changed
6574  3 if (backlinkDocument.isContentDirty()) {
6575  3 String saveMessage =
6576    localizePlainOrKey("core.comment.renameLink", getCompactEntityReferenceSerializer().serialize(newLink));
6577  3 backlinkDocument.setAuthorReference(context.getUserReference());
6578  3 context.getWiki().saveDocument(backlinkDocument, saveMessage, true, context);
6579    }
6580    }
6581   
6582    /**
6583    * @deprecated since 2.2M2 use {@link #rename(DocumentReference, List, List, com.xpn.xwiki.XWikiContext)}
6584    */
 
6585  0 toggle @Deprecated
6586    public void rename(String newDocumentName, List<String> backlinkDocumentNames, List<String> childDocumentNames,
6587    XWikiContext context) throws XWikiException
6588    {
6589  0 List<DocumentReference> backlinkDocumentReferences = new ArrayList<DocumentReference>();
6590  0 for (String backlinkDocumentName : backlinkDocumentNames) {
6591  0 backlinkDocumentReferences.add(getCurrentMixedDocumentReferenceResolver().resolve(backlinkDocumentName));
6592    }
6593   
6594  0 List<DocumentReference> childDocumentReferences = new ArrayList<DocumentReference>();
6595  0 for (String childDocumentName : childDocumentNames) {
6596  0 childDocumentReferences.add(getCurrentMixedDocumentReferenceResolver().resolve(childDocumentName));
6597    }
6598   
6599  0 rename(getCurrentMixedDocumentReferenceResolver().resolve(newDocumentName), backlinkDocumentReferences,
6600    childDocumentReferences, context);
6601    }
6602   
6603    /**
6604    * @since 2.2M1
6605    */
 
6606  165 toggle public XWikiDocument copyDocument(DocumentReference newDocumentReference, XWikiContext context)
6607    throws XWikiException
6608    {
6609  165 loadAttachments(context);
6610  165 loadArchive(context);
6611   
6612  165 XWikiDocument newdoc = duplicate(newDocumentReference);
6613   
6614    // If the copied document has a title set to the original page name then set the new title to be the new page
6615    // name.
6616  165 if (StringUtils.equals(newdoc.getTitle(), getPrettyName(this.getDocumentReference()))) {
6617  7 newdoc.setTitle(getPrettyName(newDocumentReference));
6618    }
6619   
6620  165 newdoc.setOriginalDocument(null);
6621  165 newdoc.setContentDirty(true);
6622  165 newdoc.getXClass().setOwnerDocument(newdoc);
6623   
6624  165 XWikiDocumentArchive archive = getDocumentArchive();
6625  165 if (archive != null) {
6626  156 newdoc.setDocumentArchive(archive.clone(newdoc.getId(), context));
6627    }
6628   
6629  165 return newdoc;
6630    }
6631   
6632    /**
6633    * Avoid the technical "WebHome" name.
6634    *
6635    * @param documentReference a document reference
6636    * @return the last space name if the document is the home of a space, the document name otherwise
6637    */
 
6638  172 toggle private String getPrettyName(DocumentReference documentReference)
6639    {
6640  172 EntityReferenceProvider defaultEntityReferenceProvider = Utils.getComponent(EntityReferenceProvider.class);
6641  172 if (defaultEntityReferenceProvider.getDefaultReference(documentReference.getType()).getName()
6642    .equals(documentReference.getName())) {
6643  13 return documentReference.getLastSpaceReference().getName();
6644    }
6645  159 return documentReference.getName();
6646    }
6647   
6648    /**
6649    * @deprecated since 2.2M1 use {@link #copyDocument(DocumentReference, XWikiContext)} instead
6650    */
 
6651  5 toggle @Deprecated
6652    public XWikiDocument copyDocument(String newDocumentName, XWikiContext context) throws XWikiException
6653    {
6654  5 return copyDocument(getCurrentMixedDocumentReferenceResolver().resolve(newDocumentName), context);
6655    }
6656   
 
6657  4740 toggle public XWikiLock getLock(XWikiContext context) throws XWikiException
6658    {
6659  4740 XWikiLock theLock = getStore(context).loadLock(getId(), context, true);
6660  4733 if (theLock != null) {
6661  2990 int timeout = context.getWiki().getXWikiPreferenceAsInt("lock_Timeout", 30 * 60, context);
6662  2990 if (theLock.getDate().getTime() + timeout * 1000 < new Date().getTime()) {
6663  0 getStore(context).deleteLock(theLock, context, true);
6664  0 theLock = null;
6665    }
6666    }
6667   
6668  4728 return theLock;
6669    }
6670   
 
6671  1912 toggle public void setLock(String userName, XWikiContext context) throws XWikiException
6672    {
6673  1915 XWikiLock lock = new XWikiLock(getId(), userName);
6674  1922 getStore(context).saveLock(lock, context, true);
6675    }
6676   
 
6677  767 toggle public void removeLock(XWikiContext context) throws XWikiException
6678    {
6679  767 XWikiLock lock = getStore(context).loadLock(getId(), context, true);
6680  767 if (lock != null) {
6681  767 getStore(context).deleteLock(lock, context, true);
6682    }
6683    }
6684   
 
6685  0 toggle public void insertText(String text, String marker, XWikiContext context) throws XWikiException
6686    {
6687  0 setContent(StringUtils.replaceOnce(getContent(), marker, text + marker));
6688  0 context.getWiki().saveDocument(this, context);
6689    }
6690   
 
6691  0 toggle public Object getWikiNode()
6692    {
6693  0 return this.wikiNode;
6694    }
6695   
 
6696  77622 toggle public void setWikiNode(Object wikiNode)
6697    {
6698  77622 this.wikiNode = wikiNode;
6699    }
6700   
6701    /**
6702    * @since 2.2M1
6703    */
 
6704  259430 toggle public String getXClassXML()
6705    {
6706  259433 return this.xClassXML;
6707    }
6708   
6709    /**
6710    * @deprecated since 2.2M1 use {@link #getXClassXML()} instead Hibernate uses this through reflection. It cannot be
6711    * removed without altering hibernate.cfg.xml
6712    */
 
6713  13452 toggle @Deprecated
6714    public String getxWikiClassXML()
6715    {
6716  13452 return getXClassXML();
6717    }
6718   
6719    /**
6720    * @since 2.2M1
6721    */
 
6722  252229 toggle public void setXClassXML(String xClassXML)
6723    {
6724  252223 this.xClassXML = xClassXML;
6725    }
6726   
6727    /**
6728    * @deprecated since 2.2M1 use {@link #setXClassXML(String)} ()} instead Hibernate uses this through reflection. It
6729    * cannot be removed without altering hibernate.cfg.xml
6730    */
 
6731  19028 toggle @Deprecated
6732    public void setxWikiClassXML(String xClassXML)
6733    {
6734  19025 setXClassXML(xClassXML);
6735    }
6736   
 
6737  240560 toggle public int getElements()
6738    {
6739  240559 return this.elements;
6740    }
6741   
 
6742  246937 toggle public void setElements(int elements)
6743    {
6744  246919 this.elements = elements;
6745    }
6746   
 
6747  12574 toggle public void setElement(int element, boolean toggle)
6748    {
6749  12574 if (toggle) {
6750  4134 this.elements = this.elements | element;
6751    } else {
6752  8440 this.elements = this.elements & (~element);
6753    }
6754    }
6755   
 
6756  50558 toggle public boolean hasElement(int element)
6757    {
6758  50559 return ((this.elements & element) == element);
6759    }
6760   
6761    /**
6762    * Gets the default edit mode for this document. An edit mode (other than the default "edit") can be enforced by
6763    * creating an {@code XWiki.EditModeClass} object in the current document, with the appropriate value for the
6764    * defaultEditMode property, or by adding this object in a sheet included by the document. This function also falls
6765    * back on the old {@code SheetClass}, deprecated since 3.1M2, which can be attached to included documents to
6766    * specify that the current document should be edited inline.
6767    *
6768    * @return the default edit mode for this document ("edit" or "inline" usually)
6769    * @param context the context of the request for this document
6770    * @throws XWikiException since XWiki 6.3M1 it's not used anymore and "edit" is returned in case of error, with an
6771    * error log
6772    */
 
6773  216871 toggle public String getDefaultEditMode(XWikiContext context) throws XWikiException
6774    {
6775  216868 try {
6776  216869 return getDefaultEditModeInternal(context);
6777    } catch (Exception e) {
6778    // If an error happens then we default to the "edit" mode. We don't want to fail by throwing an exception
6779    // since it'll lead to several errors in the UI (such as when evaluating contentview.vm for example).
6780  0 LOGGER.error("Failed to get the default edit mode for [{}]", getDocumentReference(), e);
6781  0 return "edit";
6782    }
6783    }
6784   
 
6785  216859 toggle private String getDefaultEditModeInternal(XWikiContext context) throws XWikiException
6786    {
6787  216857 String editModeProperty = "defaultEditMode";
6788  216858 DocumentReference editModeClass =
6789    getCurrentReferenceDocumentReferenceResolver().resolve(XWikiConstant.EDIT_MODE_CLASS);
6790    // check if the current document has any edit mode class object attached to it, and read the edit mode from it
6791  216874 BaseObject editModeObject = this.getXObject(editModeClass);
6792  216861 if (editModeObject != null) {
6793  0 String defaultEditMode = editModeObject.getStringValue(editModeProperty);
6794  0 if (StringUtils.isEmpty(defaultEditMode)) {
6795  0 return "edit";
6796    } else {
6797  0 return defaultEditMode;
6798    }
6799    }
6800    // otherwise look for included documents
6801  216863 com.xpn.xwiki.XWiki xwiki = context.getWiki();
6802  216860 if (is10Syntax()) {
6803  8 if (getContent().indexOf("includeForm(") != -1) {
6804  0 return "inline";
6805    }
6806    } else {
6807    // Algorithm: look in all include macros and for all document included check if one of them
6808    // has an EditModeClass object attached to it, or a SheetClass object (deprecated since 3.1M2) attached to
6809    // it. If so then the edit mode is inline.
6810   
6811    // Find all include macros and extract the document names
6812    // TODO: Is there a good way not to hardcode the macro name? The macro itself shouldn't know
6813    // its own name since it's a deployment time concern.
6814  216852 for (Block macroBlock : getXDOM().getBlocks(new MacroBlockMatcher("include"), Axes.CHILD)) {
6815    // Find the document reference to include by checking the macro's "reference" parameter.
6816    // For backward-compatibility we also check for a "document" parameter since this is the parameter name
6817    // that was used prior to XWiki 3.4M1 when the "reference" one was introduced and thus when the
6818    // "document" one was deprecated.
6819  12888 String includedDocumentReference = macroBlock.getParameter("reference");
6820  12888 if (includedDocumentReference == null) {
6821  0 includedDocumentReference = macroBlock.getParameter("document");
6822    }
6823  12889 if (includedDocumentReference != null) {
6824    // Resolve the document name into a valid Reference
6825  12889 DocumentReference documentReference =
6826    getCurrentMixedDocumentReferenceResolver().resolve(includedDocumentReference);
6827  12889 XWikiDocument includedDocument = xwiki.getDocument(documentReference, context);
6828  12889 if (!includedDocument.isNew()) {
6829    // get the edit mode object, first the new class and then the deprecated class if new class
6830    // is not found
6831  12836 editModeObject = includedDocument.getXObject(editModeClass);
6832  12836 if (editModeObject == null) {
6833  12836 editModeObject = includedDocument.getXObject(SHEETCLASS_REFERENCE);
6834    }
6835  12836 if (editModeObject != null) {
6836    // Use the user-defined default edit mode if set.
6837  2 String defaultEditMode = editModeObject.getStringValue(editModeProperty);
6838  2 if (StringUtils.isBlank(defaultEditMode)) {
6839    // TODO: maybe here the real value should be returned if the object is edit mode class,
6840    // and inline only if the object is sheetclass
6841  2 return "inline";
6842    } else {
6843  0 return defaultEditMode;
6844    }
6845    }
6846    }
6847    }
6848    }
6849    }
6850   
6851  216866 return "edit";
6852    }
6853   
 
6854  0 toggle public String getDefaultEditURL(XWikiContext context) throws XWikiException
6855    {
6856  0 String editMode = getDefaultEditMode(context);
6857   
6858  0 if ("inline".equals(editMode)) {
6859  0 return getEditURL("inline", "", context);
6860    } else {
6861  0 com.xpn.xwiki.XWiki xwiki = context.getWiki();
6862  0 String editor = xwiki.getEditorPreference(context);
6863  0 return getEditURL("edit", editor, context);
6864    }
6865    }
6866   
 
6867  0 toggle public String getEditURL(String action, String mode, XWikiContext context) throws XWikiException
6868    {
6869  0 com.xpn.xwiki.XWiki xwiki = context.getWiki();
6870  0 String language = "";
6871  0 XWikiDocument tdoc = (XWikiDocument) context.get("tdoc");
6872  0 String realLang = tdoc.getRealLanguage(context);
6873  0 if ((xwiki.isMultiLingual(context) == true) && (!realLang.equals(""))) {
6874  0 language = realLang;
6875    }
6876   
6877  0 return getEditURL(action, mode, language, context);
6878    }
6879   
 
6880  0 toggle public String getEditURL(String action, String mode, String language, XWikiContext context)
6881    {
6882  0 StringBuilder editparams = new StringBuilder();
6883  0 if (!mode.equals("")) {
6884  0 editparams.append("xpage=");
6885  0 editparams.append(mode);
6886    }
6887   
6888  0 if (!language.equals("")) {
6889  0 if (!mode.equals("")) {
6890  0 editparams.append("&");
6891    }
6892  0 editparams.append("language=");
6893  0 editparams.append(language);
6894    }
6895   
6896  0 return getURL(action, editparams.toString(), context);
6897    }
6898   
 
6899  266175 toggle public String getDefaultTemplate()
6900    {
6901  266160 if (this.defaultTemplate == null) {
6902  51530 return "";
6903    } else {
6904  214614 return this.defaultTemplate;
6905    }
6906    }
6907   
 
6908  258506 toggle public void setDefaultTemplate(String defaultTemplate)
6909    {
6910  258508 this.defaultTemplate = defaultTemplate;
6911   
6912  258511 setMetaDataDirty(true);
6913    }
6914   
 
6915  0 toggle public Vector<BaseObject> getComments()
6916    {
6917  0 return getComments(true);
6918    }
6919   
6920    /**
6921    * @return the syntax of the document
6922    * @since 2.3M1
6923    */
 
6924  1206913 toggle @Override
6925    public Syntax getSyntax()
6926    {
6927    // Can't be initialized in the XWikiDocument constructor because #getDefaultDocumentSyntax() need to create a
6928    // XWikiDocument object to get preferences from wiki preferences pages and would thus generate an infinite loop
6929  1206931 if (isNew() && this.syntax == null) {
6930  55896 this.syntax = getDefaultDocumentSyntax();
6931    }
6932   
6933  1206936 return this.syntax;
6934    }
6935   
6936    /**
6937    * {@inheritDoc}
6938    * <p>
6939    * Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
6940    * </p>
6941    *
6942    * @see org.xwiki.bridge.DocumentModelBridge#getSyntaxId()
6943    * @deprecated since 2.3M1, use {link #getSyntax()} instead
6944    */
 
6945  267240 toggle @Override
6946    @Deprecated
6947    public String getSyntaxId()
6948    {
6949  267231 return getSyntax().toIdString();
6950    }
6951   
6952    /**
6953    * @param syntax the new syntax to set for this document
6954    * @see #getSyntax()
6955    * @since 2.3M1
6956    */
 
6957  264360 toggle public void setSyntax(Syntax syntax)
6958    {
6959  264375 if (ObjectUtils.notEqual(this.syntax, syntax)) {
6960  255900 this.syntax = syntax;
6961    // invalidate parsed xdom
6962  255898 this.xdomCache = null;
6963    }
6964    }
6965   
6966    /**
6967    * Note that this method cannot be removed for now since it's used by Hibernate for saving a XWikiDocument.
6968    *
6969    * @param syntaxId the new syntax id to set (e.g. {@code xwiki/2.0}, {@code xwiki/2.1}, etc)
6970    * @see #getSyntaxId()
6971    * @deprecated since 2.3M1, use {link #setSyntax(Syntax)} instead
6972    */
 
6973  19103 toggle @Deprecated
6974    public void setSyntaxId(String syntaxId)
6975    {
6976  19106 Syntax syntax;
6977   
6978    // In order to preserve backward-compatibility with previous versions of XWiki in which the notion of Syntax Id
6979    // did not exist, we check the passed syntaxId parameter. Since this parameter comes from the database (it's
6980    // called automatically by Hibernate) it can be NULL or empty. In this case we consider the document is in
6981    // syntax/1.0 syntax.
6982  19106 if (StringUtils.isBlank(syntaxId)) {
6983  0 syntax = Syntax.XWIKI_1_0;
6984    } else {
6985  19096 try {
6986  19105 syntax = Syntax.valueOf(syntaxId);
6987    } catch (ParseException e) {
6988  0 syntax = getDefaultDocumentSyntax();
6989  0 LOGGER.warn("Failed to set syntax [" + syntaxId + "] for ["
6990    + getDefaultEntityReferenceSerializer().serialize(getDocumentReference()) + "], setting syntax ["
6991    + syntax.toIdString() + "] instead.", e);
6992    }
6993    }
6994   
6995  19106 setSyntax(syntax);
6996    }
6997   
 
6998  1443 toggle public Vector<BaseObject> getComments(boolean asc)
6999    {
7000  1443 List<BaseObject> list = getXObjects(COMMENTSCLASS_REFERENCE);
7001  1443 if (list == null) {
7002  1411 return null;
7003  32 } else if (asc) {
7004  32 return new Vector<BaseObject>(list);
7005    } else {
7006  0 Vector<BaseObject> newlist = new Vector<BaseObject>();
7007  0 for (int i = list.size() - 1; i >= 0; i--) {
7008  0 newlist.add(list.get(i));
7009    }
7010  0 return newlist;
7011    }
7012    }
7013   
 
7014  0 toggle public boolean isCurrentUserCreator(XWikiContext context)
7015    {
7016  0 return isCreator(context.getUserReference());
7017    }
7018   
7019    /**
7020    * @deprecated use {@link #isCreator(DocumentReference)} instead
7021    */
 
7022  0 toggle @Deprecated
7023    public boolean isCreator(String username)
7024    {
7025  0 if (username.equals(XWikiRightService.GUEST_USER_FULLNAME)) {
7026  0 return false;
7027    }
7028   
7029  0 return username.equals(getCreator());
7030    }
7031   
 
7032  0 toggle public boolean isCreator(DocumentReference username)
7033    {
7034  0 if (username == null) {
7035  0 return false;
7036    }
7037   
7038  0 return username.equals(getCreatorReference());
7039    }
7040   
 
7041  0 toggle public boolean isCurrentUserPage(XWikiContext context)
7042    {
7043  0 DocumentReference userReference = context.getUserReference();
7044  0 if (userReference == null) {
7045  0 return false;
7046    }
7047   
7048  0 return userReference.equals(getDocumentReference());
7049    }
7050   
 
7051  0 toggle public boolean isCurrentLocalUserPage(XWikiContext context)
7052    {
7053  0 final DocumentReference userRef = context.getUserReference();
7054  0 return userRef != null && userRef.equals(this.getDocumentReference());
7055    }
7056   
 
7057  2382 toggle public void resetArchive(XWikiContext context) throws XWikiException
7058    {
7059  2382 boolean hasVersioning = context.getWiki().hasVersioning(context);
7060  2382 if (hasVersioning) {
7061  2382 getVersioningStore(context).resetRCSArchive(this, true, context);
7062    }
7063    }
7064   
7065    /**
7066    * Adds an object from an new object creation form.
7067    *
7068    * @since 2.2M2
7069    */
 
7070  0 toggle public BaseObject addXObjectFromRequest(XWikiContext context) throws XWikiException
7071    {
7072    // Read info in object
7073  0 ObjectAddForm form = new ObjectAddForm();
7074  0 form.setRequest(context.getRequest());
7075  0 form.readRequest();
7076   
7077  0 EntityReference classReference = getXClassEntityReferenceResolver().resolve(form.getClassName(),
7078    EntityType.DOCUMENT, getDocumentReference());
7079  0 BaseObject object = newXObject(classReference, context);
7080  0 BaseClass baseclass = object.getXClass(context);
7081  0 baseclass.fromMap(form.getObject(LOCAL_REFERENCE_SERIALIZER.serialize(resolveClassReference(classReference))),
7082    object);
7083   
7084  0 return object;
7085    }
7086   
7087    /**
7088    * Adds an object from an new object creation form.
7089    *
7090    * @since 2.2.3
7091    */
 
7092  0 toggle public BaseObject addXObjectFromRequest(EntityReference classReference, XWikiContext context) throws XWikiException
7093    {
7094  0 return addXObjectFromRequest(classReference, "", 0, context);
7095    }
7096   
7097    /**
7098    * @deprecated since 2.2M2 use {@link #addXObjectFromRequest(EntityReference, XWikiContext)}
7099    */
 
7100  0 toggle @Deprecated
7101    public BaseObject addObjectFromRequest(String className, XWikiContext context) throws XWikiException
7102    {
7103  0 return addObjectFromRequest(className, "", 0, context);
7104    }
7105   
7106    /**
7107    * Adds an object from an new object creation form.
7108    *
7109    * @since 2.2M2
7110    */
 
7111  0 toggle public BaseObject addXObjectFromRequest(DocumentReference classReference, String prefix, XWikiContext context)
7112    throws XWikiException
7113    {
7114  0 return addXObjectFromRequest(classReference, prefix, 0, context);
7115    }
7116   
7117    /**
7118    * @deprecated since 2.2M2 use {@link #addXObjectFromRequest(DocumentReference, String, XWikiContext)}
7119    */
 
7120  0 toggle @Deprecated
7121    public BaseObject addObjectFromRequest(String className, String prefix, XWikiContext context) throws XWikiException
7122    {
7123  0 return addObjectFromRequest(className, prefix, 0, context);
7124    }
7125   
7126    /**
7127    * Adds multiple objects from an new objects creation form.
7128    *
7129    * @since 2.2M2
7130    */
 
7131  0 toggle public List<BaseObject> addXObjectsFromRequest(DocumentReference classReference, XWikiContext context)
7132    throws XWikiException
7133    {
7134  0 return addXObjectsFromRequest(classReference, "", context);
7135    }
7136   
7137    /**
7138    * @deprecated since 2.2M2 use {@link #addXObjectsFromRequest(DocumentReference, XWikiContext)}
7139    */
 
7140  0 toggle @Deprecated
7141    public List<BaseObject> addObjectsFromRequest(String className, XWikiContext context) throws XWikiException
7142    {
7143  0 return addObjectsFromRequest(className, "", context);
7144    }
7145   
7146    /**
7147    * Adds multiple objects from an new objects creation form.
7148    *
7149    * @since 2.2M2
7150    */
 
7151  0 toggle public List<BaseObject> addXObjectsFromRequest(DocumentReference classReference, String pref, XWikiContext context)
7152    throws XWikiException
7153    {
7154  0 @SuppressWarnings("unchecked")
7155    Map<String, String[]> map = context.getRequest().getParameterMap();
7156  0 List<Integer> objectsNumberDone = new ArrayList<Integer>();
7157  0 List<BaseObject> objects = new ArrayList<BaseObject>();
7158  0 String start = pref + LOCAL_REFERENCE_SERIALIZER.serialize(classReference) + "_";
7159   
7160  0 for (String name : map.keySet()) {
7161  0 if (name.startsWith(start)) {
7162  0 int pos = name.indexOf('_', start.length() + 1);
7163  0 String prefix = name.substring(0, pos);
7164  0 int num = Integer.decode(prefix.substring(prefix.lastIndexOf('_') + 1)).intValue();
7165  0 if (!objectsNumberDone.contains(Integer.valueOf(num))) {
7166  0 objectsNumberDone.add(Integer.valueOf(num));
7167  0 objects.add(addXObjectFromRequest(classReference, pref, num, context));
7168    }
7169    }
7170    }
7171   
7172  0 return objects;
7173    }
7174   
7175    /**
7176    * @deprecated since 2.2M2 use {@link #addXObjectsFromRequest(DocumentReference, String, XWikiContext)}
7177    */
 
7178  0 toggle @Deprecated
7179    public List<BaseObject> addObjectsFromRequest(String className, String pref, XWikiContext context)
7180    throws XWikiException
7181    {
7182  0 return addXObjectsFromRequest(resolveClassReference(className), pref, context);
7183    }
7184   
7185    /**
7186    * Adds object from an new object creation form.
7187    *
7188    * @since 2.2M2
7189    */
 
7190  0 toggle public BaseObject addXObjectFromRequest(DocumentReference classReference, int num, XWikiContext context)
7191    throws XWikiException
7192    {
7193  0 return addXObjectFromRequest(classReference, "", num, context);
7194    }
7195   
7196    /**
7197    * @deprecated since 2.2M2 use {@link #addXObjectFromRequest(DocumentReference, int, XWikiContext)}
7198    */
 
7199  0 toggle @Deprecated
7200    public BaseObject addObjectFromRequest(String className, int num, XWikiContext context) throws XWikiException
7201    {
7202  0 return addObjectFromRequest(className, "", num, context);
7203    }
7204   
7205    /**
7206    * Adds object from an new object creation form.
7207    *
7208    * @since 2.2.3
7209    */
 
7210  0 toggle public BaseObject addXObjectFromRequest(EntityReference classReference, String prefix, int num,
7211    XWikiContext context) throws XWikiException
7212    {
7213  0 BaseObject object = newXObject(classReference, context);
7214  0 BaseClass baseclass = object.getXClass(context);
7215  0 String newPrefix =
7216    prefix + LOCAL_REFERENCE_SERIALIZER.serialize(resolveClassReference(classReference)) + "_" + num;
7217  0 baseclass.fromMap(Util.getObject(context.getRequest(), newPrefix), object);
7218   
7219  0 return object;
7220    }
7221   
7222    /**
7223    * @deprecated since 2.2M2 use {@link #addXObjectFromRequest(EntityReference, String, int, XWikiContext)}
7224    */
 
7225  0 toggle @Deprecated
7226    public BaseObject addObjectFromRequest(String className, String prefix, int num, XWikiContext context)
7227    throws XWikiException
7228    {
7229  0 return addXObjectFromRequest(resolveClassReference(className), prefix, num, context);
7230    }
7231   
7232    /**
7233    * Adds an object from an new object creation form.
7234    *
7235    * @since 2.2.3
7236    */
 
7237  0 toggle public BaseObject updateXObjectFromRequest(EntityReference classReference, XWikiContext context)
7238    throws XWikiException
7239    {
7240  0 return updateXObjectFromRequest(classReference, "", context);
7241    }
7242   
7243    /**
7244    * @deprecated since 2.2M2 use {@link #updateXObjectFromRequest(EntityReference, XWikiContext)}
7245    */
 
7246  49 toggle @Deprecated
7247    public BaseObject updateObjectFromRequest(String className, XWikiContext context) throws XWikiException
7248    {
7249  49 return updateObjectFromRequest(className, "", context);
7250    }
7251   
7252    /**
7253    * Adds an object from an new object creation form.
7254    *
7255    * @since 2.2.3
7256    */
 
7257  0 toggle public BaseObject updateXObjectFromRequest(EntityReference classReference, String prefix, XWikiContext context)
7258    throws XWikiException
7259    {
7260  0 return updateXObjectFromRequest(classReference, prefix, 0, context);
7261    }
7262   
7263    /**
7264    * @deprecated since 2.2M2 use {@link #updateXObjectFromRequest(EntityReference, String, XWikiContext)}
7265    */
 
7266  49 toggle @Deprecated
7267    public BaseObject updateObjectFromRequest(String className, String prefix, XWikiContext context)
7268    throws XWikiException
7269    {
7270  49 return updateObjectFromRequest(className, prefix, 0, context);
7271    }
7272   
7273    /**
7274    * Adds an object from an new object creation form.
7275    *
7276    * @since 2.2.3
7277    */
 
7278  49 toggle public BaseObject updateXObjectFromRequest(EntityReference classReference, String prefix, int num,
7279    XWikiContext context) throws XWikiException
7280    {
7281  49 DocumentReference absoluteClassReference = resolveClassReference(classReference);
7282  49 int nb;
7283  49 BaseObject oldobject = getXObject(absoluteClassReference, num);
7284  49 if (oldobject == null) {
7285  20 nb = createXObject(classReference, context);
7286  20 oldobject = getXObject(absoluteClassReference, nb);
7287    } else {
7288  29 nb = oldobject.getNumber();
7289    }
7290  49 BaseClass baseclass = oldobject.getXClass(context);
7291  49 String newPrefix = prefix + LOCAL_REFERENCE_SERIALIZER.serialize(absoluteClassReference) + "_" + nb;
7292  49 BaseObject newobject =
7293    (BaseObject) baseclass.fromMap(Util.getObject(context.getRequest(), newPrefix), oldobject);
7294  49 newobject.setNumber(oldobject.getNumber());
7295  49 newobject.setGuid(oldobject.getGuid());
7296  49 setXObject(nb, newobject);
7297   
7298  49 return newobject;
7299    }
7300   
7301    /**
7302    * @deprecated since 2.2M2 use {@link #updateXObjectFromRequest(EntityReference, String, int, XWikiContext)}
7303    */
 
7304  49 toggle @Deprecated
7305    public BaseObject updateObjectFromRequest(String className, String prefix, int num, XWikiContext context)
7306    throws XWikiException
7307    {
7308  49 return updateXObjectFromRequest(
7309    getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()), prefix,
7310    num, context);
7311    }
7312   
7313    /**
7314    * Adds an object from an new object creation form.
7315    *
7316    * @since 2.2.3
7317    */
 
7318  0 toggle public List<BaseObject> updateXObjectsFromRequest(EntityReference classReference, XWikiContext context)
7319    throws XWikiException
7320    {
7321  0 return updateXObjectsFromRequest(classReference, "", context);
7322    }
7323   
7324    /**
7325    * @deprecated since 2.2M2 use {@link #updateXObjectsFromRequest(EntityReference, XWikiContext)}
7326    */
 
7327  0 toggle @Deprecated
7328    public List<BaseObject> updateObjectsFromRequest(String className, XWikiContext context) throws XWikiException
7329    {
7330  0 return updateObjectsFromRequest(className, "", context);
7331    }
7332   
7333    /**
7334    * Adds multiple objects from an new objects creation form.
7335    *
7336    * @since 2.2.3
7337    */
 
7338  0 toggle public List<BaseObject> updateXObjectsFromRequest(EntityReference classReference, String pref, XWikiContext context)
7339    throws XWikiException
7340    {
7341  0 DocumentReference absoluteClassReference = resolveClassReference(classReference);
7342  0 @SuppressWarnings("unchecked")
7343    Map<String, String[]> map = context.getRequest().getParameterMap();
7344  0 List<Integer> objectsNumberDone = new ArrayList<Integer>();
7345  0 List<BaseObject> objects = new ArrayList<BaseObject>();
7346  0 String start = pref + LOCAL_REFERENCE_SERIALIZER.serialize(absoluteClassReference) + "_";
7347   
7348  0 for (String name : map.keySet()) {
7349  0 if (name.startsWith(start)) {
7350  0 int pos = name.indexOf('_', start.length() + 1);
7351  0 String prefix = name.substring(0, pos);
7352  0 int num = Integer.decode(prefix.substring(prefix.lastIndexOf('_') + 1)).intValue();
7353  0 if (!objectsNumberDone.contains(Integer.valueOf(num))) {
7354  0 objectsNumberDone.add(Integer.valueOf(num));
7355  0 objects.add(updateXObjectFromRequest(classReference, pref, num, context));
7356    }
7357    }
7358    }
7359   
7360  0 return objects;
7361    }
7362   
7363    /**
7364    * @deprecated since 2.2M2 use {@link #updateXObjectsFromRequest(EntityReference, String, XWikiContext)}
7365    */
 
7366  0 toggle @Deprecated
7367    public List<BaseObject> updateObjectsFromRequest(String className, String pref, XWikiContext context)
7368    throws XWikiException
7369    {
7370  0 return updateXObjectsFromRequest(
7371    getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()), pref,
7372    context);
7373    }
7374   
 
7375  10 toggle public boolean isAdvancedContent()
7376    {
7377  10 String[] matches = { "<%", "#set", "#include", "#if", "public class", "/* Advanced content */",
7378    "## Advanced content", "/* Programmatic content */", "## Programmatic content" };
7379  10 String content2 = getContent().toLowerCase();
7380  10 for (String match : matches) {
7381  90 if (content2.indexOf(match.toLowerCase()) != -1) {
7382  0 return true;
7383    }
7384    }
7385   
7386  10 if (HTML_TAG_PATTERN.matcher(content2).find()) {
7387  0 return true;
7388    }
7389   
7390  10 return false;
7391    }
7392   
 
7393  0 toggle public boolean isProgrammaticContent()
7394    {
7395  0 String[] matches = { "<%", "\\$xwiki.xWiki", "$xcontext.context", "$doc.document", "$xwiki.getXWiki()",
7396    "$xcontext.getContext()", "$doc.getDocument()", "WithProgrammingRights(", "/* Programmatic content */",
7397    "## Programmatic content", "$xwiki.search(", "$xwiki.createUser", "$xwiki.createNewWiki",
7398    "$xwiki.addToAllGroup", "$xwiki.sendMessage", "$xwiki.copyDocument", "$xwiki.copyWikiWeb",
7399    "$xwiki.copySpaceBetweenWikis", "$xwiki.parseGroovyFromString", "$doc.toXML()", "$doc.toXMLDocument()", };
7400  0 String content2 = getContent().toLowerCase();
7401  0 for (String match : matches) {
7402  0 if (content2.indexOf(match.toLowerCase()) != -1) {
7403  0 return true;
7404    }
7405    }
7406   
7407  0 return false;
7408    }
7409   
7410    /**
7411    * Remove an XObject from the document. The changes are not persisted until the document is saved.
7412    *
7413    * @param object the object to remove
7414    * @return {@code true} if the object was successfully removed, {@code false} if the object was not found in the
7415    * current document.
7416    * @since 2.2M1
7417    */
 
7418  42 toggle public boolean removeXObject(BaseObject object)
7419    {
7420  42 List<BaseObject> objects = this.xObjects.get(object.getXClassReference());
7421    // No objects at all, nothing to remove
7422  42 if (objects == null) {
7423  0 return false;
7424    }
7425    // Sometimes the object vector is wrongly indexed, meaning that objects are not at the right position
7426    // Check if the right object is in place
7427  42 int objectPosition = object.getNumber();
7428  42 if (objectPosition < objects.size()) {
7429  42 BaseObject storedObject = objects.get(objectPosition);
7430  42 if (storedObject == null || !storedObject.equals(object)) {
7431    // Try to find the correct position
7432  1 objectPosition = objects.indexOf(object);
7433    }
7434    } else {
7435    // The object position is greater than the array, that's invalid!
7436  0 objectPosition = -1;
7437    }
7438    // If the object is not in the document, simply ignore this request
7439  42 if (objectPosition < 0) {
7440  0 return false;
7441    }
7442    // We don't remove objects, but set null in their place, so that the object number corresponds to its position
7443    // in the vector
7444  42 objects.set(objectPosition, null);
7445    // Schedule the object for removal from the storage
7446  42 addXObjectToRemove(object);
7447   
7448  42 return true;
7449    }
7450   
7451    /**
7452    * Remove an XObject from the document. The changes are not persisted until the document is saved.
7453    *
7454    * @param object the object to remove
7455    * @return {@code true} if the object was successfully removed, {@code false} if the object was not found in the
7456    * current document.
7457    * @deprecated since 2.2M1, use {@link #removeXObject(com.xpn.xwiki.objects.BaseObject)} instead
7458    */
 
7459  15 toggle @Deprecated
7460    public boolean removeObject(BaseObject object)
7461    {
7462  15 return removeXObject(object);
7463    }
7464   
7465    /**
7466    * Remove all the objects of a given type (XClass) from the document. The object counter is left unchanged, so that
7467    * future objects will have new (different) numbers. However, on some storage engines the counter will be reset if
7468    * the document is removed from the cache and reloaded from the persistent storage.
7469    *
7470    * @param classReference The XClass reference of the XObjects to be removed.
7471    * @return {@code true} if the objects were successfully removed, {@code false} if no object from the target class
7472    * was in the current document.
7473    * @since 2.2M1
7474    */
 
7475  3 toggle public boolean removeXObjects(DocumentReference classReference)
7476    {
7477  3 List<BaseObject> objects = this.xObjects.get(classReference);
7478    // No objects at all, nothing to remove
7479  3 if (objects == null) {
7480  0 return false;
7481    }
7482    // Schedule the object for removal from the storage
7483  3 for (BaseObject object : objects) {
7484  5 if (object != null) {
7485  3 addXObjectToRemove(object);
7486    }
7487    }
7488    // Empty the vector, retaining its size
7489  3 int currentSize = objects.size();
7490  3 objects.clear();
7491  8 for (int i = 0; i < currentSize; i++) {
7492  5 objects.add(null);
7493    }
7494   
7495  3 return true;
7496    }
7497   
7498    /**
7499    * Remove all the objects of a given type (XClass) from the document. The object counter is left unchanged, so that
7500    * future objects will have new (different) numbers. However, on some storage engines the counter will be reset if
7501    * the document is removed from the cache and reloaded from the persistent storage.
7502    *
7503    * @param reference The XClass reference of the XObjects to be removed.
7504    * @return {@code true} if the objects were successfully removed, {@code false} if no object from the target class
7505    * was in the current document.
7506    * @since 5.0M1
7507    */
 
7508  2 toggle public boolean removeXObjects(EntityReference reference)
7509    {
7510  2 return removeXObjects(
7511    getCurrentReferenceDocumentReferenceResolver().resolve(reference, getDocumentReference()));
7512    }
7513   
7514    /**
7515    * Remove all the objects of a given type (XClass) from the document. The object counter is left unchanged, so that
7516    * future objects will have new (different) numbers. However, on some storage engines the counter will be reset if
7517    * the document is removed from the cache and reloaded from the persistent storage.
7518    *
7519    * @param className The class name of the objects to be removed.
7520    * @return {@code true} if the objects were successfully removed, {@code false} if no object from the target class
7521    * was in the current document.
7522    * @deprecated since 2.2M1 use {@link #removeXObjects(org.xwiki.model.reference.DocumentReference)} instead
7523    */
 
7524  1 toggle @Deprecated
7525    public boolean removeObjects(String className)
7526    {
7527  1 return removeXObjects(resolveClassReference(className));
7528    }
7529   
7530    /**
7531    * Get the top sections contained in the document.
7532    * <p>
7533    * The section are filtered by xwiki.section.depth property on the maximum depth of the sections to return. This
7534    * method is usually used to get "editable" sections.
7535    *
7536    * @return the sections in the current document
7537    */
 
7538  38 toggle public List<DocumentSection> getSections() throws XWikiException
7539    {
7540  38 if (is10Syntax()) {
7541  19 return getSections10();
7542    } else {
7543  19 List<DocumentSection> splitSections = new ArrayList<DocumentSection>();
7544  19 List<HeaderBlock> headers = getFilteredHeaders();
7545   
7546  19 int sectionNumber = 1;
7547  19 for (HeaderBlock header : headers) {
7548    // put -1 as index since there is no way to get the position of the header in the source
7549  28 int documentSectionIndex = -1;
7550   
7551    // Need to do the same thing than 1.0 content here
7552  28 String documentSectionLevel = StringUtils.repeat("1.", header.getLevel().getAsInt() - 1) + "1";
7553   
7554  28 DocumentSection docSection = new DocumentSection(sectionNumber++, documentSectionIndex,
7555    documentSectionLevel, renderXDOM(new XDOM(header.getChildren()), getSyntax()));
7556  28 splitSections.add(docSection);
7557    }
7558   
7559  19 return splitSections;
7560    }
7561    }
7562   
7563    /**
7564    * Get XWiki context from execution context.
7565    *
7566    * @return the XWiki context for the current thread
7567    */
 
7568  32850 toggle private XWikiContext getXWikiContext()
7569    {
7570  32848 Provider<XWikiContext> xcontextProvider = Utils.getComponent(XWikiContext.TYPE_PROVIDER);
7571   
7572  32849 if (xcontextProvider != null) {
7573  32845 return xcontextProvider.get();
7574    }
7575   
7576  0 return null;
7577    }
7578   
7579    /**
7580    * Filter the headers from a document XDOM based on xwiki.section.depth property from xwiki.cfg file.
7581    *
7582    * @return the filtered headers
7583    */
 
7584  37 toggle private List<HeaderBlock> getFilteredHeaders()
7585    {
7586  37 List<HeaderBlock> filteredHeaders = new ArrayList<HeaderBlock>();
7587   
7588    // Get the maximum header level
7589  37 int sectionDepth = 2;
7590  37 XWikiContext context = getXWikiContext();
7591  37 if (context != null) {
7592  37 sectionDepth = (int) context.getWiki().getSectionEditingDepth();
7593    }
7594   
7595    // Get the headers.
7596    //
7597    // Note that we need to only take into account SectionBlock that are children of other SectionBlocks so that
7598    // we are in sync with the section editing buttons added in xwiki.js. Being able to section edit any heading is
7599    // too complex. For example if you have (in XWiki Syntax 2.0):
7600    // = Heading1 =
7601    // para1
7602    // == Heading2 ==
7603    // para2
7604    // (((
7605    // == Heading3 ==
7606    // para3
7607    // (((
7608    // == Heading4 ==
7609    // para4
7610    // )))
7611    // )))
7612    // == Heading5 ==
7613    // para5
7614    //
7615    // Then if we were to support editing "Heading4", its content would be:
7616    // para4
7617    // )))
7618    // )))
7619    //
7620    // Which obviously is not correct...
7621   
7622  37 final XDOM xdom = getXDOM();
7623  37 if (!xdom.getChildren().isEmpty()) {
7624  37 Block currentBlock = xdom.getChildren().get(0);
7625  333 while (currentBlock != null) {
7626  296 if (currentBlock instanceof SectionBlock) {
7627    // The next children block is a HeaderBlock but we check to be on the safe side...
7628  92 Block nextChildrenBlock = currentBlock.getChildren().get(0);
7629  92 if (nextChildrenBlock instanceof HeaderBlock) {
7630  88 HeaderBlock headerBlock = (HeaderBlock) nextChildrenBlock;
7631  88 if (headerBlock.getLevel().getAsInt() <= sectionDepth) {
7632  85 filteredHeaders.add(headerBlock);
7633    }
7634    }
7635  92 currentBlock = nextChildrenBlock;
7636    } else {
7637  204 Block nextSibling = currentBlock.getNextSibling();
7638  204 if (nextSibling == null) {
7639  59 currentBlock = currentBlock.getParent();
7640  166 while (currentBlock != null) {
7641  129 if (currentBlock.getNextSibling() != null) {
7642  22 currentBlock = currentBlock.getNextSibling();
7643  22 break;
7644    }
7645  107 currentBlock = currentBlock.getParent();
7646    }
7647    } else {
7648  145 currentBlock = nextSibling;
7649    }
7650    }
7651    }
7652    }
7653   
7654  37 return filteredHeaders;
7655    }
7656   
7657    /**
7658    * @return the sections in the current document
7659    */
 
7660  19 toggle private List<DocumentSection> getSections10()
7661    {
7662    // Pattern to match the title. Matches only level 1 and level 2 headings.
7663  19 Pattern headingPattern = Pattern.compile("^[ \\t]*+(1(\\.1){0,1}+)[ \\t]++(.++)$", Pattern.MULTILINE);
7664  19 Matcher matcher = headingPattern.matcher(getContent());
7665  19 List<DocumentSection> splitSections = new ArrayList<DocumentSection>();
7666  19 int sectionNumber = 0;
7667    // find title to split
7668  70 while (matcher.find()) {
7669  51 ++sectionNumber;
7670  51 String sectionLevel = matcher.group(1);
7671  51 String sectionTitle = matcher.group(3);
7672  51 int sectionIndex = matcher.start();
7673    // Initialize a documentSection object.
7674  51 DocumentSection docSection = new DocumentSection(sectionNumber, sectionIndex, sectionLevel, sectionTitle);
7675    // Add the document section to list.
7676  51 splitSections.add(docSection);
7677    }
7678   
7679  19 return splitSections;
7680    }
7681   
7682    /**
7683    * Return a Document section with parameter is sectionNumber.
7684    *
7685    * @param sectionNumber the index (+1) of the section in the list of all sections in the document.
7686    * @return
7687    * @throws XWikiException error when extracting sections from document
7688    */
 
7689  21 toggle public DocumentSection getDocumentSection(int sectionNumber) throws XWikiException
7690    {
7691    // return a document section according to section number
7692  21 return getSections().get(sectionNumber - 1);
7693    }
7694   
7695    /**
7696    * Return the content of a section.
7697    *
7698    * @param sectionNumber the index (+1) of the section in the list of all sections in the document.
7699    * @return the content of a section or null if the section can't be found.
7700    * @throws XWikiException error when trying to extract section content
7701    */
 
7702  21 toggle public String getContentOfSection(int sectionNumber) throws XWikiException
7703    {
7704  21 String content = null;
7705   
7706  21 if (is10Syntax()) {
7707  8 content = getContentOfSection10(sectionNumber);
7708    } else {
7709  13 List<HeaderBlock> headers = getFilteredHeaders();
7710   
7711  13 if (headers.size() >= sectionNumber) {
7712  13 SectionBlock section = headers.get(sectionNumber - 1).getSection();
7713  13 content = renderXDOM(new XDOM(Collections.<Block>singletonList(section)), getSyntax());
7714    }
7715    }
7716   
7717  21 return content;
7718    }
7719   
7720    /**
7721    * Return the content of a section.
7722    *
7723    * @param sectionNumber the index (+1) of the section in the list of all sections in the document.
7724    * @return the content of a section
7725    * @throws XWikiException error when trying to extract section content
7726    */
 
7727  8 toggle private String getContentOfSection10(int sectionNumber) throws XWikiException
7728    {
7729  8 List<DocumentSection> splitSections = getSections();
7730  8 int indexEnd = 0;
7731    // get current section
7732  8 DocumentSection section = splitSections.get(sectionNumber - 1);
7733  8 int indexStart = section.getSectionIndex();
7734  8 String sectionLevel = section.getSectionLevel();
7735    // Determine where this section ends, which is at the start of the next section of the
7736    // same or a higher level.
7737  11 for (int i = sectionNumber; i < splitSections.size(); i++) {
7738  7 DocumentSection nextSection = splitSections.get(i);
7739  7 String nextLevel = nextSection.getSectionLevel();
7740  7 if (sectionLevel.equals(nextLevel) || sectionLevel.length() > nextLevel.length()) {
7741  4 indexEnd = nextSection.getSectionIndex();
7742  4 break;
7743    }
7744    }
7745  8 String sectionContent = null;
7746  8 if (indexStart < 0) {
7747  0 indexStart = 0;
7748    }
7749   
7750  8 if (indexEnd == 0) {
7751  4 sectionContent = getContent().substring(indexStart);
7752    } else {
7753  4 sectionContent = getContent().substring(indexStart, indexEnd);
7754    }
7755   
7756  8 return sectionContent;
7757    }
7758   
7759    /**
7760    * Update a section content in document.
7761    *
7762    * @param sectionNumber the index (starting at 1) of the section in the list of all sections in the document.
7763    * @param newSectionContent the new section content.
7764    * @return the new document content.
7765    * @throws XWikiException error when updating content
7766    */
 
7767  6 toggle public String updateDocumentSection(int sectionNumber, String newSectionContent) throws XWikiException
7768    {
7769  6 String content;
7770  6 if (is10Syntax()) {
7771  1 content = updateDocumentSection10(sectionNumber, newSectionContent);
7772    } else {
7773    // Get the current section block
7774  5 HeaderBlock header = getFilteredHeaders().get(sectionNumber - 1);
7775   
7776  5 XDOM xdom = (XDOM) header.getRoot();
7777   
7778    // newSectionContent -> Blocks
7779  5 List<Block> blocks = parseContent(newSectionContent).getChildren();
7780  5 int sectionLevel = header.getLevel().getAsInt();
7781  6 for (int level = 1; level < sectionLevel && blocks.size() == 1
7782    && blocks.get(0) instanceof SectionBlock; ++level) {
7783  1 blocks = blocks.get(0).getChildren();
7784    }
7785   
7786    // replace old current SectionBlock with new Blocks
7787  5 Block section = header.getSection();
7788  5 section.getParent().replaceChild(blocks, section);
7789   
7790    // render back XDOM to document's content syntax
7791  5 content = renderXDOM(xdom, getSyntax());
7792    }
7793   
7794  6 return content;
7795    }
7796   
7797    /**
7798    * Update a section content in document.
7799    *
7800    * @param sectionNumber the index (+1) of the section in the list of all sections in the document.
7801    * @param newSectionContent the new section content.
7802    * @return the new document content.
7803    * @throws XWikiException error when updating document content with section content
7804    */
 
7805  1 toggle private String updateDocumentSection10(int sectionNumber, String newSectionContent) throws XWikiException
7806    {
7807  1 StringBuilder newContent = new StringBuilder();
7808    // get document section that will be edited
7809  1 DocumentSection docSection = getDocumentSection(sectionNumber);
7810  1 int numberOfSections = getSections().size();
7811  1 int indexSection = docSection.getSectionIndex();
7812  1 if (numberOfSections == 1) {
7813    // there is only a sections in document
7814  0 String contentBegin = getContent().substring(0, indexSection);
7815  0 newContent = newContent.append(contentBegin).append(newSectionContent);
7816  0 return newContent.toString();
7817  1 } else if (sectionNumber == numberOfSections) {
7818    // edit lastest section that doesn't contain subtitle
7819  1 String contentBegin = getContent().substring(0, indexSection);
7820  1 newContent = newContent.append(contentBegin).append(newSectionContent);
7821  1 return newContent.toString();
7822    } else {
7823  0 String sectionLevel = docSection.getSectionLevel();
7824  0 int nextSectionIndex = 0;
7825    // get index of next section
7826  0 for (int i = sectionNumber; i < numberOfSections; i++) {
7827  0 DocumentSection nextSection = getDocumentSection(i + 1); // get next section
7828  0 String nextSectionLevel = nextSection.getSectionLevel();
7829  0 if (sectionLevel.equals(nextSectionLevel)) {
7830  0 nextSectionIndex = nextSection.getSectionIndex();
7831  0 break;
7832  0 } else if (sectionLevel.length() > nextSectionLevel.length()) {
7833  0 nextSectionIndex = nextSection.getSectionIndex();
7834  0 break;
7835    }
7836    }
7837   
7838  0 if (nextSectionIndex == 0) {// edit the last section
7839  0 newContent = newContent.append(getContent().substring(0, indexSection)).append(newSectionContent);
7840  0 return newContent.toString();
7841    } else {
7842  0 String contentAfter = getContent().substring(nextSectionIndex);
7843  0 String contentBegin = getContent().substring(0, indexSection);
7844  0 newContent = newContent.append(contentBegin).append(newSectionContent).append(contentAfter);
7845    }
7846   
7847  0 return newContent.toString();
7848    }
7849    }
7850   
7851    /**
7852    * Computes a document hash, taking into account all document data: content, objects, attachments, metadata... TODO:
7853    * cache the hash value, update only on modification.
7854    */
 
7855  0 toggle public String getVersionHashCode(XWikiContext context)
7856    {
7857  0 MessageDigest md5 = null;
7858   
7859  0 try {
7860  0 md5 = MessageDigest.getInstance("MD5");
7861    } catch (NoSuchAlgorithmException ex) {
7862  0 LOGGER.error("Cannot create MD5 object", ex);
7863  0 return hashCode() + "";
7864    }
7865   
7866  0 try {
7867  0 String valueBeforeMD5 = toXML(true, false, true, false, context);
7868  0 md5.update(valueBeforeMD5.getBytes());
7869   
7870  0 byte[] array = md5.digest();
7871  0 StringBuilder sb = new StringBuilder();
7872  0 for (byte element : array) {
7873  0 int b = element & 0xFF;
7874  0 if (b < 0x10) {
7875  0 sb.append('0');
7876    }
7877  0 sb.append(Integer.toHexString(b));
7878    }
7879   
7880  0 return sb.toString();
7881    } catch (Exception ex) {
7882  0 LOGGER.error("Exception while computing document hash", ex);
7883    }
7884   
7885  0 return hashCode() + "";
7886    }
7887   
 
7888  0 toggle public static String getInternalPropertyName(String propname, XWikiContext context)
7889    {
7890  0 ContextualLocalizationManager localizationManager = Utils.getComponent(ContextualLocalizationManager.class);
7891  0 String cpropname = StringUtils.capitalize(propname);
7892   
7893  0 return localizationManager == null ? cpropname : localizationManager.getTranslationPlain(cpropname);
7894    }
7895   
 
7896  0 toggle public String getInternalProperty(String propname)
7897    {
7898  0 String methodName = "get" + StringUtils.capitalize(propname);
7899  0 try {
7900  0 Method method = getClass().getDeclaredMethod(methodName, (Class[]) null);
7901  0 return (String) method.invoke(this, (Object[]) null);
7902    } catch (Exception e) {
7903  0 return null;
7904    }
7905    }
7906   
 
7907  2583909 toggle public String getCustomClass()
7908    {
7909  2583871 if (this.customClass == null) {
7910  5954 return "";
7911    }
7912   
7913  2577887 return this.customClass;
7914    }
7915   
 
7916  258513 toggle public void setCustomClass(String customClass)
7917    {
7918  258517 this.customClass = customClass;
7919  258517 setMetaDataDirty(true);
7920    }
7921   
 
7922  258497 toggle public void setValidationScript(String validationScript)
7923    {
7924  258508 this.validationScript = validationScript;
7925   
7926  258506 setMetaDataDirty(true);
7927    }
7928   
 
7929  253701 toggle public String getValidationScript()
7930    {
7931  253711 if (this.validationScript == null) {
7932  47487 return "";
7933    } else {
7934  206221 return this.validationScript;
7935    }
7936    }
7937   
 
7938  276122 toggle public String getComment()
7939    {
7940  276133 if (this.comment == null) {
7941  0 return "";
7942    }
7943   
7944  276137 return this.comment;
7945    }
7946   
 
7947  265869 toggle public void setComment(String comment)
7948    {
7949  265855 this.comment = comment;
7950    }
7951   
 
7952  255248 toggle public boolean isMinorEdit()
7953    {
7954  255250 return this.isMinorEdit;
7955    }
7956   
 
7957  250340 toggle public void setMinorEdit(boolean isMinor)
7958    {
7959  250324 this.isMinorEdit = isMinor;
7960    }
7961   
7962    // methods for easy table update. It is need only for hibernate.
7963    // when hibernate update old database without minorEdit field, hibernate will create field with
7964    // null in despite of notnull in hbm.
7965    // (http://opensource.atlassian.com/projects/hibernate/browse/HB-1151)
7966    // so minorEdit will be null for old documents. But hibernate can't convert null to boolean.
7967    // so we need convert Boolean to boolean
 
7968  13452 toggle protected Boolean getMinorEdit1()
7969    {
7970  13452 return Boolean.valueOf(isMinorEdit());
7971    }
7972   
 
7973  19019 toggle protected void setMinorEdit1(Boolean isMinor)
7974    {
7975  19026 this.isMinorEdit = (isMinor != null && isMinor.booleanValue());
7976    }
7977   
7978    /**
7979    * Create, add and return a new object with the provided class.
7980    * <p>
7981    * Note that absolute reference are not supported for xclasses which mean that the wiki part (whatever the wiki is)
7982    * of the reference will be systematically removed.
7983    *
7984    * @param classReference the reference of the class
7985    * @param context the XWiki context
7986    * @return the newly created object
7987    * @throws XWikiException error when creating the new object
7988    * @since 2.2.3
7989    */
 
7990  1872 toggle public BaseObject newXObject(EntityReference classReference, XWikiContext context) throws XWikiException
7991    {
7992  1872 int nb = createXObject(classReference, context);
7993  1872 return getXObject(resolveClassReference(classReference), nb);
7994    }
7995   
7996    /**
7997    * @deprecated since 2.2M2 use {@link #newXObject(EntityReference, XWikiContext)}
7998    */
 
7999  69 toggle @Deprecated
8000    public BaseObject newObject(String className, XWikiContext context) throws XWikiException
8001    {
8002  69 return newXObject(
8003    getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()),
8004    context);
8005    }
8006   
8007    /**
8008    * @since 2.2M2
8009    */
 
8010  0 toggle public BaseObject getXObject(DocumentReference classReference, boolean create, XWikiContext context)
8011    {
8012  0 try {
8013  0 BaseObject obj = getXObject(classReference);
8014   
8015  0 if ((obj == null) && create) {
8016  0 return newXObject(classReference, context);
8017    }
8018   
8019  0 if (obj == null) {
8020  0 return null;
8021    } else {
8022  0 return obj;
8023    }
8024    } catch (Exception e) {
8025  0 return null;
8026    }
8027    }
8028   
8029    /**
8030    * @since 3.4M1
8031    */
 
8032  97618 toggle public BaseObject getXObject(EntityReference classReference, boolean create, XWikiContext context)
8033    {
8034  97619 try {
8035  97617 BaseObject obj = getXObject(classReference);
8036   
8037  97617 if ((obj == null) && create) {
8038  221 return newXObject(classReference, context);
8039    }
8040   
8041  97397 if (obj == null) {
8042  45336 return null;
8043    } else {
8044  52060 return obj;
8045    }
8046    } catch (Exception e) {
8047  0 return null;
8048    }
8049    }
8050   
8051    /**
8052    * @deprecated since 2.2M2 use {@link #getXObject(DocumentReference, boolean, XWikiContext)}
8053    */
 
8054  97600 toggle @Deprecated
8055    public BaseObject getObject(String className, boolean create, XWikiContext context)
8056    {
8057  97597 return getXObject(
8058    getXClassEntityReferenceResolver().resolve(className, EntityType.DOCUMENT, getDocumentReference()), create,
8059    context);
8060    }
8061   
 
8062  5 toggle public boolean validate(XWikiContext context) throws XWikiException
8063    {
8064  5 return validate(null, context);
8065    }
8066   
 
8067  5 toggle public boolean validate(String[] classNames, XWikiContext context) throws XWikiException
8068    {
8069  5 boolean isValid = true;
8070  5 if ((classNames == null) || (classNames.length == 0)) {
8071  5 for (DocumentReference classReference : getXObjects().keySet()) {
8072  5 BaseClass bclass = context.getWiki().getXClass(classReference, context);
8073  5 List<BaseObject> objects = getXObjects(classReference);
8074  5 for (BaseObject obj : objects) {
8075  7 if (obj != null) {
8076  7 isValid &= bclass.validateObject(obj, context);
8077    }
8078    }
8079    }
8080    } else {
8081  0 for (String className : classNames) {
8082  0 List<BaseObject> objects = getXObjects(getCurrentMixedDocumentReferenceResolver().resolve(className));
8083  0 if (objects != null) {
8084  0 for (BaseObject obj : objects) {
8085  0 if (obj != null) {
8086  0 BaseClass bclass = obj.getXClass(context);
8087  0 isValid &= bclass.validateObject(obj, context);
8088    }
8089    }
8090    }
8091    }
8092    }
8093   
8094  5 String validationScript = "";
8095  5 XWikiRequest req = context.getRequest();
8096  5 if (req != null) {
8097  3 validationScript = req.get("xvalidation");
8098    }
8099   
8100  5 if ((validationScript == null) || (validationScript.trim().equals(""))) {
8101  5 validationScript = getValidationScript();
8102    }
8103   
8104  5 if ((validationScript != null) && (!validationScript.trim().equals(""))) {
8105  2 isValid &= executeValidationScript(context, validationScript);
8106    }
8107   
8108  5 return isValid;
8109    }
8110   
 
8111  206614 toggle public static void backupContext(Map<String, Object> backup, XWikiContext context)
8112    {
8113    // The XWiki Context isn't recreated when the Execution Context is cloned so we have to backup some of its data.
8114    // Backup the current document on the XWiki Context.
8115  206615 backup.put("doc", context.getDoc());
8116   
8117  206609 backup.put("cdoc", context.get("cdoc"));
8118  206608 backup.put("tdoc", context.get("tdoc"));
8119   
8120    // Backup the secure document
8121  206612 backup.put(CKEY_SDOC, context.get(CKEY_SDOC));
8122   
8123    // Clone the Execution Context to provide isolation. The clone will have a new Velocity and Script Context.
8124  206615 Execution execution = Utils.getComponent(Execution.class);
8125  206619 try {
8126  206618 execution.pushContext(Utils.getComponent(ExecutionContextManager.class).clone(execution.getContext()));
8127    } catch (ExecutionContextException e) {
8128  0 throw new RuntimeException("Failed to clone the Execution Context", e);
8129    }
8130   
8131    // Bridge with old XWiki Context, required for legacy code.
8132  206611 execution.getContext().setProperty(XWikiContext.EXECUTIONCONTEXT_KEY, context);
8133    }
8134   
 
8135  206612 toggle public static void restoreContext(Map<String, Object> backup, XWikiContext context)
8136    {
8137    // Restore the Execution Context. This will also restore the previous Velocity and Script Context.
8138  206613 Execution execution = Utils.getComponent(Execution.class);
8139  206619 execution.popContext();
8140   
8141    // Restore the current document on the XWiki Context.
8142  206618 context.setDoc((XWikiDocument) backup.get("doc"));
8143   
8144  206619 context.put("cdoc", backup.get("cdoc"));
8145  206617 context.put("tdoc", backup.get("tdoc"));
8146   
8147    // Restore the secure document
8148  206618 context.put(CKEY_SDOC, backup.get(CKEY_SDOC));
8149    }
8150   
 
8151  206576 toggle public void setAsContextDoc(XWikiContext context)
8152    {
8153  206575 context.setDoc(this);
8154  206581 context.remove("cdoc");
8155  206577 context.remove("tdoc");
8156   
8157    // Get rid of secure document (so that it fallback on context document)
8158  206577 context.remove(CKEY_SDOC);
8159    }
8160   
8161    /**
8162    * @return the String representation of the previous version of this document or null if this is the first version.
8163    */
 
8164  32 toggle public String getPreviousVersion()
8165    {
8166  32 XWikiDocumentArchive archive = loadDocumentArchive();
8167  32 if (archive != null) {
8168  32 Version prevVersion = archive.getPrevVersion(getRCSVersion());
8169  32 if (prevVersion != null) {
8170  4 return prevVersion.toString();
8171    }
8172    }
8173  28 return null;
8174    }
8175   
 
8176  376867 toggle @Override
8177    public String toString()
8178    {
8179  376863 return getFullName();
8180    }
8181   
8182    /**
8183    * Indicates whether the document should be 'hidden' or not, meaning that it should not be returned in public search
8184    * results.
8185    *
8186    * @param hidden The new value of the {@link #hidden} property.
8187    */
 
8188  260311 toggle public void setHidden(Boolean hidden)
8189    {
8190  260305 if (hidden == null) {
8191  0 this.hidden = false;
8192    } else {
8193  260301 this.hidden = hidden;
8194    }
8195    }
8196   
8197    /**
8198    * Indicates whether the document is 'hidden' or not, meaning that it should not be returned in public search
8199    * results.
8200    *
8201    * @return <code>true</code> if the document is hidden and does not appear among the results of
8202    * {@link com.xpn.xwiki.api.XWiki#searchDocuments(String)}, <code>false</code> otherwise.
8203    */
 
8204  313684 toggle public Boolean isHidden()
8205    {
8206  313654 return this.hidden;
8207    }
8208   
8209    /**
8210    * Convert the current document content from its current syntax to the new syntax passed as parameter.
8211    *
8212    * @param targetSyntaxId the syntax to convert to (e.g. {@code xwiki/2.0}, {@code xhtml/1.0}, etc)
8213    * @throws XWikiException if an exception occurred during the conversion process
8214    */
 
8215  1 toggle public void convertSyntax(String targetSyntaxId, XWikiContext context) throws XWikiException
8216    {
8217  1 try {
8218  1 convertSyntax(Syntax.valueOf(targetSyntaxId), context);
8219    } catch (Exception e) {
8220  0 throw new XWikiException(XWikiException.MODULE_XWIKI_RENDERING, XWikiException.ERROR_XWIKI_UNKNOWN,
8221    "Failed to convert document to syntax [" + targetSyntaxId + "]", e);
8222    }
8223    }
8224   
8225    /**
8226    * Convert the current document content from its current syntax to the new syntax passed as parameter.
8227    *
8228    * @param targetSyntax the syntax to convert to (e.g. {@code xwiki/2.0}, {@code xhtml/1.0}, etc)
8229    * @throws XWikiException if an exception occurred during the conversion process
8230    */
 
8231  1 toggle public void convertSyntax(Syntax targetSyntax, XWikiContext context) throws XWikiException
8232    {
8233    // convert content
8234  1 setContent(performSyntaxConversion(getContent(), getDocumentReference(), getSyntax(), targetSyntax));
8235   
8236    // convert objects
8237  1 Map<DocumentReference, List<BaseObject>> objectsByClass = getXObjects();
8238   
8239  1 for (List<BaseObject> objects : objectsByClass.values()) {
8240  1 for (BaseObject bobject : objects) {
8241  2 if (bobject != null) {
8242  2 BaseClass bclass = bobject.getXClass(context);
8243  2 for (Object fieldClass : bclass.getProperties()) {
8244  14 if (fieldClass instanceof TextAreaClass && ((TextAreaClass) fieldClass).isWikiContent()) {
8245  2 TextAreaClass textAreaClass = (TextAreaClass) fieldClass;
8246  2 LargeStringProperty field = (LargeStringProperty) bobject.getField(textAreaClass.getName());
8247   
8248  2 if (field != null) {
8249  2 field.setValue(performSyntaxConversion(field.getValue(), getDocumentReference(),
8250    getSyntax(), targetSyntax));
8251    }
8252    }
8253    }
8254    }
8255    }
8256    }
8257   
8258    // change syntax
8259  1 setSyntax(targetSyntax);
8260    }
8261   
8262    /**
8263    * NOTE: This method caches the XDOM and returns a clone that can be safely modified.
8264    *
8265    * @return the XDOM corresponding to the document's string content
8266    */
 
8267  288642 toggle @Override
8268    public XDOM getXDOM()
8269    {
8270  288634 if (this.xdomCache == null) {
8271  63580 try {
8272  63580 this.xdomCache = parseContent(getContent());
8273    } catch (XWikiException e) {
8274  2 ErrorBlockGenerator errorBlockGenerator = Utils.getComponent(ErrorBlockGenerator.class);
8275  2 return new XDOM(errorBlockGenerator.generateErrorBlocks("Failed to render content", e, false));
8276    }
8277    }
8278   
8279  288638 return this.xdomCache.clone();
8280    }
8281   
8282    /**
8283    * @return true if the document has a xwiki/1.0 syntax content
8284    */
 
8285  235942 toggle public boolean is10Syntax()
8286    {
8287  235942 return is10Syntax(getSyntaxId());
8288    }
8289   
8290    /**
8291    * @return true if the document has a xwiki/1.0 syntax content
8292    */
 
8293  259024 toggle public boolean is10Syntax(String syntaxId)
8294    {
8295  259028 return Syntax.XWIKI_1_0.toIdString().equalsIgnoreCase(syntaxId);
8296    }
8297   
 
8298  26115795 toggle private void init(DocumentReference reference)
8299    {
8300    // if the passed reference is null consider it points to the default reference
8301  26115733 if (reference == null) {
8302  52516 setDocumentReference(
8303    Utils.<Provider<DocumentReference>>getComponent(DocumentReference.TYPE_PROVIDER).get());
8304    } else {
8305  26063263 setDocumentReference(reference);
8306    }
8307   
8308  26115639 this.updateDate = new Date();
8309  26115837 this.updateDate.setTime((this.updateDate.getTime() / 1000) * 1000);
8310  26115783 this.contentUpdateDate = new Date();
8311  26115816 this.contentUpdateDate.setTime((this.contentUpdateDate.getTime() / 1000) * 1000);
8312  26115848 this.creationDate = new Date();
8313  26115630 this.creationDate.setTime((this.creationDate.getTime() / 1000) * 1000);
8314  26115647 this.content = "";
8315  26115637 this.format = "";
8316  26115592 this.locale = Locale.ROOT;
8317  26115595 this.defaultLocale = Locale.ROOT;
8318  26115602 this.customClass = "";
8319  26115603 this.comment = "";
8320   
8321    // Note: As there's no notion of an Empty document we don't set the original document
8322    // field. Thus getOriginalDocument() may return null.
8323    }
8324   
 
8325  2 toggle private boolean executeValidationScript(XWikiContext context, String validationScript)
8326    {
8327  2 try {
8328  2 ContextualAuthorizationManager authorization = Utils.getComponent(ContextualAuthorizationManager.class);
8329  2 DocumentReference validationScriptReference =
8330    getCurrentDocumentReferenceResolver().resolve(validationScript, getDocumentReference());
8331   
8332    // Make sure target document is allowed to execute Groovy
8333    // TODO: this check should probably be right in XWiki#parseGroovyFromPage
8334  2 authorization.checkAccess(Right.PROGRAM, validationScriptReference);
8335   
8336  1 XWikiValidationInterface validObject =
8337    (XWikiValidationInterface) context.getWiki().parseGroovyFromPage(validationScript, context);
8338   
8339  1 return validObject.validateDocument(this, context);
8340    } catch (Throwable e) {
8341  1 XWikiValidationStatus.addExceptionToContext(getFullName(), "", e, context);
8342  1 return false;
8343    }
8344    }
8345   
8346    /**
8347    * Convert the passed content from the passed syntax to the passed new syntax.
8348    *
8349    * @param content the content to convert
8350    * @param source the reference to where the content comes from (eg document reference)
8351    * @param currentSyntaxId the syntax of the current content to convert
8352    * @param targetSyntax the new syntax after the conversion
8353    * @return the converted content in the new syntax
8354    * @throws XWikiException if an exception occurred during the conversion process
8355    * @since 2.4M2
8356    */
 
8357  3 toggle private static String performSyntaxConversion(String content, DocumentReference source, Syntax currentSyntaxId,
8358    Syntax targetSyntax) throws XWikiException
8359    {
8360  3 try {
8361  3 XDOM dom = parseContent(currentSyntaxId, content, source);
8362  3 return performSyntaxConversion(dom, targetSyntax, null);
8363    } catch (Exception e) {
8364  0 throw new XWikiException(XWikiException.MODULE_XWIKI_RENDERING, XWikiException.ERROR_XWIKI_UNKNOWN,
8365    "Failed to convert document to syntax [" + targetSyntax + "]", e);
8366    }
8367    }
8368   
8369    /**
8370    * Convert the passed content from the passed syntax to the passed new syntax.
8371    *
8372    * @param content the XDOM content to convert, the XDOM can be modified during the transformation
8373    * @param targetSyntax the new syntax after the conversion
8374    * @param txContext the context when Transformation are executed or null if transformation shouldn't be executed
8375    * @return the converted content in the new syntax
8376    * @throws XWikiException if an exception occurred during the conversion process
8377    * @since 2.4M2
8378    */
 
8379  3 toggle private static String performSyntaxConversion(XDOM content, Syntax targetSyntax, TransformationContext txContext)
8380    throws XWikiException
8381    {
8382  3 try {
8383  3 if (txContext != null) {
8384    // Transform XDOM
8385  0 TransformationManager transformations = Utils.getComponent(TransformationManager.class);
8386  0 if (txContext.getXDOM() == null) {
8387  0 txContext.setXDOM(content);
8388    }
8389  0 try {
8390  0 transformations.performTransformations(content, txContext);
8391    } catch (TransformationException te) {
8392    // An error happened during one of the transformations. Since the error has been logged
8393    // continue
8394    // TODO: We should have a visual clue for the user in the future to let him know something
8395    // didn't work as expected.
8396    }
8397    }
8398   
8399    // Render XDOM
8400  3 return renderXDOM(content, targetSyntax);
8401    } catch (Exception e) {
8402  0 throw new XWikiException(XWikiException.MODULE_XWIKI_RENDERING, XWikiException.ERROR_XWIKI_UNKNOWN,
8403    "Failed to convert document to syntax [" + targetSyntax + "]", e);
8404    }
8405    }
8406   
8407    /**
8408    * Render privided XDOM into content of the provided syntax identifier.
8409    *
8410    * @param content the XDOM content to render
8411    * @param targetSyntax the syntax identifier of the rendered content
8412    * @return the rendered content
8413    * @throws XWikiException if an exception occurred during the rendering process
8414    */
 
8415  197994 toggle protected static String renderXDOM(XDOM content, Syntax targetSyntax) throws XWikiException
8416    {
8417  197995 try {
8418  197991 BlockRenderer renderer = Utils.getComponent(BlockRenderer.class, targetSyntax.toIdString());
8419  198005 WikiPrinter printer = new DefaultWikiPrinter();
8420  198000 renderer.render(content, printer);
8421  197995 return printer.toString();
8422    } catch (Exception e) {
8423  0 throw new XWikiException(XWikiException.MODULE_XWIKI_RENDERING, XWikiException.ERROR_XWIKI_UNKNOWN,
8424    "Failed to render document to syntax [" + targetSyntax + "]", e);
8425    }
8426    }
8427   
 
8428  63588 toggle private XDOM parseContent(String content) throws XWikiException
8429    {
8430  63587 return parseContent(getSyntax(), content, getDocumentReference());
8431    }
8432   
8433    /**
8434    * @param source the reference to where the content comes from (eg document reference)
8435    */
 
8436  63584 toggle private static XDOM parseContent(Syntax syntax, String content, DocumentReference source) throws XWikiException
8437    {
8438  63585 ContentParser parser = Utils.getComponent(ContentParser.class);
8439   
8440  63592 try {
8441  63594 return parser.parse(content, syntax, source);
8442    } catch (MissingParserException e) {
8443  2 throw new XWikiException(XWikiException.MODULE_XWIKI_RENDERING, XWikiException.ERROR_XWIKI_UNKNOWN,
8444    "Failed to find a parser for syntax [" + syntax.toIdString() + "]", e);
8445    } catch (ParseException e) {
8446  0 throw new XWikiException(XWikiException.MODULE_XWIKI_RENDERING, XWikiException.ERROR_XWIKI_UNKNOWN,
8447    "Failed to parse content of syntax [" + syntax.toIdString() + "]", e);
8448    }
8449    }
8450   
8451    /**
8452    * If there's no parser available for the specified syntax default to the XWiki 2.1 syntax.
8453    */
 
8454  55898 toggle private Syntax getDefaultDocumentSyntax()
8455    {
8456  55897 Syntax syntax = Utils.getComponent(CoreConfiguration.class).getDefaultDocumentSyntax();
8457   
8458  55899 if (syntax == null || (!Utils.getComponentManager().hasComponent(Parser.class, syntax.toIdString())
8459    && !Syntax.XWIKI_2_1.equals(syntax))) {
8460  27 LOGGER.warn("Failed to find parser for the default syntax [{}]. Defaulting to xwiki/2.1 syntax.", syntax);
8461  27 syntax = Syntax.XWIKI_2_1;
8462    }
8463   
8464  55902 return syntax;
8465    }
8466   
8467    /**
8468    * Backward-compatibility method to use in order to resolve a class reference passed as a String into a
8469    * DocumentReference proper.
8470    *
8471    * @return the resolved class reference but using this document's wiki if the passed String doesn't specify a wiki,
8472    * the "XWiki" space if the passed String doesn't specify a space and this document's page if the passed
8473    * String doesn't specify a page.
8474    */
 
8475  556994 toggle public DocumentReference resolveClassReference(String documentName)
8476    {
8477  556995 DocumentReference defaultReference = new DocumentReference(getDocumentReference().getWikiReference().getName(),
8478    XWiki.SYSTEM_SPACE, getDocumentReference().getName());
8479  556999 return getExplicitDocumentReferenceResolver().resolve(documentName, defaultReference);
8480    }
8481   
8482    /**
8483    * Transforms a XClass reference relative to this document into an absolute reference.
8484    */
 
8485  4183 toggle private DocumentReference resolveClassReference(EntityReference reference)
8486    {
8487  4183 if (reference instanceof DocumentReference) {
8488  271 return (DocumentReference) reference;
8489  3912 } else if (reference instanceof LocalDocumentReference) {
8490  330 return new DocumentReference((LocalDocumentReference) reference, getDocumentReference().getWikiReference());
8491    } else {
8492  3582 DocumentReference defaultReference =
8493    new DocumentReference(getDocumentReference().getWikiReference().getName(), XWiki.SYSTEM_SPACE,
8494    getDocumentReference().getName());
8495  3582 return getExplicitReferenceDocumentReferenceResolver().resolve(reference, defaultReference);
8496    }
8497    }
8498   
8499    /**
8500    * Return the reference of the parent document as it stored and passed to
8501    * {@link #setParentReference(EntityReference)}.
8502    * <p>
8503    * You should use {@link #getParentReference()} reference if you want the complete parent reference.
8504    *
8505    * @return the relative parent reference
8506    * @since 2.2.3
8507    */
 
8508  581933 toggle public EntityReference getRelativeParentReference()
8509    {
8510  581929 return this.parentReference;
8511    }
8512   
 
8513  42 toggle private BaseObject prepareXObject(EntityReference classReference)
8514    {
8515  42 DocumentReference absoluteClassReference = resolveClassReference(classReference);
8516  42 BaseObject bobject = getXObject(absoluteClassReference);
8517  42 if (bobject == null) {
8518  0 bobject = new BaseObject();
8519  0 bobject.setXClassReference(classReference);
8520   
8521  0 addXObject(bobject);
8522    }
8523  42 bobject.setDocumentReference(getDocumentReference());
8524  42 setMetaDataDirty(true);
8525  42 return bobject;
8526    }
8527   
8528    /**
8529    * Apply a 3 ways merge on the current document based on provided previous and new version of the document.
8530    * <p>
8531    * All 3 documents are supposed to have the same document reference and language already since that's what makes
8532    * them uniques.
8533    *
8534    * @param previousDocument the previous version of the document
8535    * @param newDocument the next version of the document
8536    * @param configuration the configuration of the merge indicates how to deal with some conflicts use cases, etc.
8537    * @param context the XWiki context
8538    * @return a repport of what happen during the merge (errors, etc.)
8539    * @since 3.2M1
8540    */
 
8541  57 toggle public MergeResult merge(XWikiDocument previousDocument, XWikiDocument newDocument,
8542    MergeConfiguration configuration, XWikiContext context)
8543    {
8544  57 MergeResult mergeResult = new MergeResult();
8545   
8546    // Title
8547  57 setTitle(MergeUtils.mergeOject(previousDocument.getTitle(), newDocument.getTitle(), getTitle(), mergeResult));
8548   
8549    // Content
8550  57 setContent(
8551    MergeUtils.mergeLines(previousDocument.getContent(), newDocument.getContent(), getContent(), mergeResult));
8552   
8553    // Syntax
8554  57 setSyntax(
8555    MergeUtils.mergeOject(previousDocument.getSyntax(), newDocument.getSyntax(), getSyntax(), mergeResult));
8556   
8557    // Default locale
8558  57 setDefaultLocale(MergeUtils.mergeOject(previousDocument.getDefaultLocale(), newDocument.getDefaultLocale(),
8559    getDefaultLocale(), mergeResult));
8560   
8561    // Parent
8562  57 setParentReference(MergeUtils.mergeOject(previousDocument.getRelativeParentReference(),
8563    newDocument.getRelativeParentReference(), getRelativeParentReference(), mergeResult));
8564   
8565    // DefaultTemplate
8566  57 setDefaultTemplate(MergeUtils.mergeOject(previousDocument.getDefaultTemplate(),
8567    newDocument.getDefaultTemplate(), getDefaultTemplate(), mergeResult));
8568   
8569    // Hidden
8570  57 setHidden(MergeUtils.mergeOject(previousDocument.isHidden(), newDocument.isHidden(), isHidden(), mergeResult));
8571   
8572    // CustomClass
8573  57 setCustomClass(MergeUtils.mergeLines(previousDocument.getCustomClass(), newDocument.getCustomClass(),
8574    getCustomClass(), mergeResult));
8575   
8576    // ValidationScript
8577  57 setValidationScript(MergeUtils.mergeLines(previousDocument.getValidationScript(),
8578    newDocument.getValidationScript(), getValidationScript(), mergeResult));
8579   
8580    // Objects
8581  57 List<List<ObjectDiff>> objectsDiff = previousDocument.getObjectDiff(previousDocument, newDocument, context);
8582  57 if (!objectsDiff.isEmpty()) {
8583    // Apply diff on result
8584  9 for (List<ObjectDiff> objectClassDiff : objectsDiff) {
8585  14 for (ObjectDiff diff : objectClassDiff) {
8586  71 BaseObject objectResult = getXObject(diff.getXClassReference(), diff.getNumber());
8587  71 BaseObject previousObject =
8588    previousDocument.getXObject(diff.getXClassReference(), diff.getNumber());
8589  71 BaseObject newObject = newDocument.getXObject(diff.getXClassReference(), diff.getNumber());
8590  71 PropertyInterface propertyResult =
8591  71 objectResult != null ? objectResult.getField(diff.getPropName()) : null;
8592  71 PropertyInterface previousProperty =
8593  71 previousObject != null ? previousObject.getField(diff.getPropName()) : null;
8594  71 PropertyInterface newProperty = newObject != null ? newObject.getField(diff.getPropName()) : null;
8595   
8596  71 if (diff.getAction() == ObjectDiff.ACTION_OBJECTADDED) {
8597  6 if (objectResult == null) {
8598  6 setXObject(newObject.getNumber(),
8599  6 configuration.isProvidedVersionsModifiables() ? newObject : newObject.clone());
8600  6 mergeResult.setModified(true);
8601    } else {
8602    // collision between DB and new: object to add but already exists in the DB
8603  0 mergeResult.getLog().error("Collision found on object [{}]", objectResult.getReference());
8604    }
8605  65 } else if (diff.getAction() == ObjectDiff.ACTION_OBJECTREMOVED) {
8606  1 if (objectResult != null) {
8607  1 if (objectResult.equals(previousObject)) {
8608  1 removeXObject(objectResult);
8609  1 mergeResult.setModified(true);
8610    } else {
8611    // collision between DB and new: object to remove but not the same as previous
8612    // version
8613  0 mergeResult.getLog().error("Collision found on object [{}]",
8614    objectResult.getReference());
8615    }
8616    } else {
8617    // Already removed from DB, lets assume the user is prescient
8618  0 mergeResult.getLog().warn("Object [{}] already removed", previousObject.getReference());
8619    }
8620  64 } else if (previousObject != null && newObject != null) {
8621  11 if (objectResult != null) {
8622  9 if (diff.getAction() == ObjectDiff.ACTION_PROPERTYADDED) {
8623  0 if (propertyResult == null) {
8624  0 objectResult.safeput(diff.getPropName(), newProperty);
8625  0 mergeResult.setModified(true);
8626    } else {
8627    // collision between DB and new: property to add but already exists in the DB
8628  0 mergeResult.getLog().error("Collision found on object property [{}]",
8629    propertyResult.getReference());
8630    }
8631  9 } else if (diff.getAction() == ObjectDiff.ACTION_PROPERTYREMOVED) {
8632  0 if (propertyResult != null) {
8633  0 if (propertyResult.equals(previousProperty)) {
8634  0 objectResult.removeField(diff.getPropName());
8635  0 mergeResult.setModified(true);
8636    } else {
8637    // collision between DB and new: supposed to be removed but the DB version is
8638    // not the same as the previous version
8639  0 mergeResult.getLog().error("Collision found on object property [{}]",
8640    propertyResult.getReference());
8641    }
8642    } else {
8643    // Already removed from DB, lets assume the user is prescient
8644  0 mergeResult.getLog().warn("Object property [{}] already removed",
8645    previousProperty.getReference());
8646    }
8647  9 } else if (diff.getAction() == ObjectDiff.ACTION_PROPERTYCHANGED) {
8648  9 if (propertyResult != null) {
8649  9 if (propertyResult.equals(previousProperty)) {
8650  8 objectResult.safeput(diff.getPropName(), newProperty);
8651  8 mergeResult.setModified(true);
8652    } else {
8653    // Try to apply a 3 ways merge on the property
8654  1 propertyResult.merge(previousProperty, newProperty, configuration, context,
8655    mergeResult);
8656    }
8657    } else {
8658    // collision between DB and new: property to modify but does not exists in DB
8659    // Lets assume it's a mistake to fix
8660  0 mergeResult.getLog().warn("Object [{}] does not exists",
8661    newProperty.getReference());
8662   
8663  0 objectResult.safeput(diff.getPropName(), newProperty);
8664  0 mergeResult.setModified(true);
8665    }
8666    }
8667    } else {
8668    // Object explitely removed from the DB, lets assume we don't care about the changes
8669  2 mergeResult.getLog().warn("Object [{}] already removed", previousObject.getReference());
8670    }
8671    }
8672    }
8673    }
8674    }
8675   
8676    // Class
8677  57 BaseClass classResult = getXClass();
8678  57 BaseClass previousClass = previousDocument.getXClass();
8679  57 BaseClass newClass = newDocument.getXClass();
8680  57 classResult.merge(previousClass, newClass, configuration, context, mergeResult);
8681   
8682    // Attachments
8683  57 List<AttachmentDiff> attachmentsDiff =
8684    previousDocument.getAttachmentDiff(previousDocument, newDocument, context);
8685  57 if (!attachmentsDiff.isEmpty()) {
8686    // Apply deleted attachment diff on result (new attachment has already been saved)
8687  8 for (AttachmentDiff diff : attachmentsDiff) {
8688  8 XWikiAttachment previousAttachment = diff.getOrigAttachment();
8689  8 XWikiAttachment nextAttachment = diff.getNewAttachment();
8690  8 XWikiAttachment attachment = getAttachment(diff.getFileName());
8691   
8692  8 switch (diff.getType()) {
8693  2 case DELETE:
8694  2 if (attachment != null) {
8695  2 try {
8696  2 if (attachment.equalsData(previousAttachment, context)) {
8697  2 removeAttachment(attachment);
8698  2 mergeResult.setModified(true);
8699    } else {
8700    // collision between DB and new: attachment modified by user
8701  0 mergeResult.getLog().error("Collision found on attachment [{}]",
8702    attachment.getReference());
8703    }
8704    } catch (XWikiException e) {
8705  0 mergeResult.getLog().error("Failed to compare attachments with reference [{}]",
8706    attachment.getReference());
8707    }
8708    } else {
8709    // Already removed from DB, lets assume the user is prescient
8710  0 mergeResult.getLog().warn("Attachment [{}] already removed",
8711    previousAttachment.getReference());
8712    }
8713  2 break;
8714  5 case INSERT:
8715  5 if (attachment != null) {
8716  0 try {
8717  0 if (!attachment.equalsData(nextAttachment, context)) {
8718    // collision between DB and new: attachment to add but a different one already
8719    // exists in the DB
8720  0 mergeResult.getLog().error("Collision found on attachment [{}]",
8721    attachment.getReference());
8722    } else {
8723    // Already added to the DB, lets assume the user is prescient
8724  0 mergeResult.getLog().warn("Attachment [{}] already added",
8725    previousAttachment.getReference());
8726    }
8727    } catch (XWikiException e) {
8728  0 mergeResult.getLog().error("Failed to compare attachments with reference [{}]",
8729    attachment.getReference());
8730    }
8731    } else {
8732  5 addAttachment(configuration.isProvidedVersionsModifiables() ? nextAttachment
8733    : (XWikiAttachment) nextAttachment.clone());
8734  5 mergeResult.setModified(true);
8735    }
8736  5 break;
8737  1 case CHANGE:
8738  1 if (attachment != null) {
8739  1 attachment.merge(previousAttachment, nextAttachment, configuration, context, mergeResult);
8740    } else {
8741    // collision between DB and new: attachment modified but does not exist in the DB
8742  0 mergeResult.getLog().error("Collision found on attachment [{}]",
8743    previousAttachment.getReference());
8744    }
8745  1 break;
8746  0 default:
8747  0 break;
8748    }
8749    }
8750    }
8751   
8752  57 return mergeResult;
8753    }
8754   
8755    /**
8756    * Apply modification comming from provided document.
8757    * <p>
8758    * Thid method does not take into account versions and author related informations and the provided document should
8759    * have the same reference. Like {@link #merge(XWikiDocument, XWikiDocument, MergeConfiguration, XWikiContext)},
8760    * this method is dealing with "real" data and should not change anything related to version management and document
8761    * identifier.
8762    * <p>
8763    * Important note: this method does not take care of attachments contents related operations and only remove
8764    * attachments which need to be removed from the list. For memory handling reasons all attachments contents related
8765    * operations should be done elsewhere.
8766    *
8767    * @param document the document to apply
8768    * @return false is nothing changed
8769    */
 
8770  25 toggle public boolean apply(XWikiDocument document)
8771    {
8772  25 return apply(document, true);
8773    }
8774   
8775    /**
8776    * Apply modification comming from provided document.
8777    * <p>
8778    * Thid method does not take into account versions and author related informations and the provided document should
8779    * have the same reference. Like {@link #merge(XWikiDocument, XWikiDocument, MergeConfiguration, XWikiContext)},
8780    * this method is dealing with "real" data and should not change everything related to version management and
8781    * document identifier.
8782    *
8783    * @param document the document to apply
8784    * @return false is nothing changed
8785    */
 
8786  27 toggle public boolean apply(XWikiDocument document, boolean clean)
8787    {
8788  27 boolean modified = false;
8789   
8790    // /////////////////////////////////
8791    // Document
8792   
8793  27 if (!StringUtils.equals(getContent(), document.getContent())) {
8794  14 setContent(document.getContent());
8795  14 modified = true;
8796    }
8797  27 if (ObjectUtils.notEqual(getSyntax(), document.getSyntax())) {
8798  23 setSyntax(document.getSyntax());
8799  23 modified = true;
8800    }
8801   
8802  27 if (ObjectUtils.notEqual(getDefaultLocale(), document.getDefaultLocale())) {
8803  21 setDefaultLocale(document.getDefaultLocale());
8804  21 modified = true;
8805    }
8806   
8807  27 if (!StringUtils.equals(getTitle(), document.getTitle())) {
8808  6 setTitle(document.getTitle());
8809  6 modified = true;
8810    }
8811   
8812  27 if (!StringUtils.equals(getDefaultTemplate(), document.getDefaultTemplate())) {
8813  5 setDefaultTemplate(document.getDefaultTemplate());
8814  5 modified = true;
8815    }
8816  27 if (ObjectUtils.notEqual(getRelativeParentReference(), document.getRelativeParentReference())) {
8817  10 setParentReference(document.getRelativeParentReference());
8818  10 modified = true;
8819    }
8820   
8821  27 if (!StringUtils.equals(getCustomClass(), document.getCustomClass())) {
8822  5 setCustomClass(document.getCustomClass());
8823  5 modified = true;
8824    }
8825   
8826  27 if (!StringUtils.equals(getValidationScript(), document.getValidationScript())) {
8827  5 setValidationScript(document.getValidationScript());
8828  5 modified = true;
8829    }
8830   
8831  27 if (isHidden() != document.isHidden()) {
8832  5 setHidden(document.isHidden());
8833  5 modified = true;
8834    }
8835   
8836    // /////////////////////////////////
8837    // XObjects
8838   
8839  27 if (clean) {
8840    // Delete objects that don't exist anymore
8841  27 for (List<BaseObject> objects : getXObjects().values()) {
8842    // Duplicate the list since we are potentially going to modify it
8843  8 for (BaseObject originalObj : new ArrayList<BaseObject>(objects)) {
8844  8 if (originalObj != null) {
8845  8 BaseObject newObj =
8846    document.getXObject(originalObj.getXClassReference(), originalObj.getNumber());
8847  8 if (newObj == null) {
8848    // The object was deleted
8849  3 removeXObject(originalObj);
8850  3 modified = true;
8851    }
8852    }
8853    }
8854    }
8855    }
8856    // Add new objects or update existing objects
8857  27 for (List<BaseObject> objects : document.getXObjects().values()) {
8858  28 for (BaseObject newObj : objects) {
8859  28 if (newObj != null) {
8860  28 BaseObject originalObj = getXObject(newObj.getXClassReference(), newObj.getNumber());
8861  28 if (originalObj == null) {
8862    // The object added or modified
8863  23 setXObject(newObj.getNumber(), newObj);
8864  23 modified = true;
8865    } else {
8866    // The object added or modified
8867  5 modified |= originalObj.apply(newObj, clean);
8868    }
8869    }
8870    }
8871    }
8872   
8873    // /////////////////////////////////
8874    // XClass
8875   
8876  27 modified |= getXClass().apply(document.getXClass(), clean);
8877  27 if (ObjectUtils.notEqual(getXClassXML(), document.getXClassXML())) {
8878  1 setXClassXML(document.getXClassXML());
8879  1 modified = true;
8880    }
8881   
8882    // /////////////////////////////////
8883    // Attachments
8884   
8885  27 if (clean) {
8886    // Delete attachments that don't exist anymore
8887  27 for (XWikiAttachment attachment : new ArrayList<XWikiAttachment>(getAttachmentList())) {
8888  0 if (document.getAttachment(attachment.getFilename()) == null) {
8889  0 removeAttachment(attachment);
8890    }
8891    }
8892    }
8893    // Add new attachments or update existing attachments
8894  27 for (XWikiAttachment attachment : document.getAttachmentList()) {
8895  7 XWikiAttachment originalAttachment = getAttachment(attachment.getFilename());
8896  7 if (originalAttachment == null) {
8897  7 addAttachment(attachment);
8898    } else {
8899  0 originalAttachment.apply(attachment);
8900    }
8901    }
8902   
8903  27 return modified;
8904    }
8905   
 
8906  0 toggle private XWikiAttachmentStoreInterface resolveXWikiAttachmentStoreInterface(String storeType, XWikiContext xcontext)
8907    {
8908  0 XWikiAttachmentStoreInterface store = getXWikiAttachmentStoreInterface(storeType);
8909   
8910  0 if (store != null) {
8911  0 return store;
8912    }
8913   
8914  0 return xcontext.getWiki().getDefaultAttachmentContentStore();
8915    }
8916   
 
8917  0 toggle private XWikiAttachmentStoreInterface getXWikiAttachmentStoreInterface(String storeType)
8918    {
8919  0 if (storeType != null && !storeType.equals(XWikiHibernateAttachmentStore.HINT)) {
8920  0 try {
8921  0 return Utils.getContextComponentManager().getInstance(XWikiAttachmentStoreInterface.class, storeType);
8922    } catch (ComponentLookupException e) {
8923  0 LOGGER.warn("Can't find attachment content store for type [{}]", storeType, e);
8924    }
8925    }
8926   
8927  0 return null;
8928    }
8929    }