SmartAPI
Open Source .NET RQL library for RedDot CMS / OpenText WSM Management Server
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Properties Pages
Page.cs
Go to the documentation of this file.
1 // SmartAPI - .Net programmatic access to RedDot servers
2 //
3 // Copyright (C) 2013 erminas GbR
4 //
5 // This program is free software: you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License as published by the Free Software Foundation,
7 // either version 3 of the License, or (at your option) any later version.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 // See the GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License along with this program.
14 // If not, see <http://www.gnu.org/licenses/>.
15 
16 using System;
17 using System.Collections.Generic;
18 using System.Linq;
19 using System.Web;
20 using System.Xml;
21 using erminas.SmartAPI.CMS.Project.ContentClasses;
22 using erminas.SmartAPI.CMS.Project.Pages.Elements;
23 using erminas.SmartAPI.CMS.Project.Workflows;
24 using erminas.SmartAPI.CMS.ServerManagement;
25 using erminas.SmartAPI.Exceptions;
26 using erminas.SmartAPI.Utils;
27 using erminas.SmartAPI.Utils.CachedCollections;
28 
29 namespace erminas.SmartAPI.CMS.Project.Pages
30 {
31  internal class Page : PartialRedDotProjectObject, IPage
32  {
33  private Guid _ccGuid;
34  private DateTime _changeDate;
35  private Guid _changeUserGuid;
36  private DateTime _checkinDate;
37  private IContentClass _contentClass;
38  private DateTime _createDate;
39  private string _headline;
40  private int _id;
41  private ILanguageVariant _lang;
42  private ILinkElement _mainLinkElement;
43  private Guid _mainLinkGuid;
44  private ILinkElement _mainLinkNavigationElement;
45  private Guid _mainLinkNavigationGuid;
46  private PageFlags _pageFlags = PageFlags.Null;
47  private PageState _pageState;
48  private IPage _parentPage;
49  private DateTime _releaseDate;
50  private PageReleaseStatus _releaseStatus;
51 
52  internal Page(IProject project, XmlElement xmlElement) : base(project, xmlElement)
53  {
54  LoadXml();
55  IsInitialized = false;
56  InitProperties();
57  }
58 
59  internal Page(IProject project, Guid guid, ILanguageVariant languageVariant) : base(project, guid)
60  {
61  _lang = languageVariant;
62  InitProperties();
63  }
64 
70  internal string InitialHeadlineValue
71  {
72  set { _headline = value; }
73  }
74 
75  public IAssignedKeywords AssignedKeywords { get; private set; }
76 
77  public DateTime CreateDate
78  {
79  get { return LazyLoad(ref _createDate); }
80  }
81 
82  public DateTime CheckinDate
83  {
84  get { return LazyLoad(ref _checkinDate); }
85  }
86 
87  public DateTime LastChangeDate
88  {
89  get { return LazyLoad(ref _changeDate); }
90  }
91 
92  public IUser LastChangeUser
93  {
94  get { return new User(Project.Session, LazyLoad(ref _changeUserGuid)); }
95  }
96 
97  public void Commit()
98  {
99  const string SAVE_PAGE = @"<PAGE action=""save"" guid=""{0}"" headline=""{1}"" name=""{2}"" mainlinkguid=""{3}"" />";
100  var mainlink = MainLinkElement == null ? RQL.SESSIONKEY_PLACEHOLDER : MainLinkElement.Guid.ToRQLString();
101 
102  XmlDocument xmlDoc =
103  Project.ExecuteRQL(SAVE_PAGE.RQLFormat(Guid, HttpUtility.HtmlEncode(Headline), HttpUtility.HtmlEncode(Filename), mainlink));
104  if (xmlDoc.GetElementsByTagName("PAGE")
105  .Count != 1)
106  {
107  throw new SmartAPIException(Session.ServerLogin, string.Format("Could not save changes to page {0}", this));
108  }
109  }
110 
111  public IContentClass ContentClass
112  {
113  get { return _contentClass ?? (_contentClass = Project.ContentClasses.GetByGuid(LazyLoad(ref _ccGuid))); }
114  }
115 
116  public IIndexedRDList<string, IPageElement> ContentElements { get; private set; }
117 
118  public IPageCopyAndConnectJob CreateCopyAndConnectJob(ILinkElement connectionTarget,
120  {
121  return new PageCopyAndConnectJob(this, connectionTarget, flags);
122  }
123 
124  public void CopyAndConnectAsync(ILinkElement connectionTarget, PageCopyAndConnectFlags flags = PageCopyAndConnectFlags.None)
125  {
126  CreateCopyAndConnectJob(connectionTarget, flags)
127  .RunAsync();
128  }
129 
130  public void Delete()
131  {
132  DeleteImpl(true);
133  }
134 
135  public void DeleteFromRecycleBin()
136  {
137  using (new LanguageContext(LanguageVariant))
138  {
139  const string DELETE_FINALLY =
140  @"<PAGE action=""deletefinally"" guid=""{0}"" alllanguages="""" forcedelete2910="""" forcedelete2911=""""/>";
141  Project.ExecuteRQL(string.Format(DELETE_FINALLY, Guid.ToRQLString()));
142 
143  const string MARK_DIRTY =
144  @"<PAGEBUILDER><PAGES sessionkey=""{0}"" action=""pagevaluesetdirty""><PAGE sessionkey=""{0}"" guid=""{1}"" languages=""{2}""/></PAGES></PAGEBUILDER>";
145  Project.Session.ExecuteRQLRaw(
146  MARK_DIRTY.RQLFormat(Project.Session.SessionKey, this, LanguageVariant.Abbreviation),
147  RQL.IODataFormat.SessionKeyOnly);
148 
149  const string LINKING =
150  @"<PAGEBUILDER><LINKING sessionkey=""{0}""><PAGES><PAGE sessionkey=""{0}"" guid=""{1}""/></PAGES></LINKING></PAGEBUILDER>";
151  Project.Session.ExecuteRQLRaw(LINKING.RQLFormat(Project.Session.SessionKey, this), RQL.IODataFormat.SessionKeyOnly);
152  }
153  IsInitialized = false;
154  _releaseStatus = PageReleaseStatus.NotSet;
155  Status = PageState.NotSet;
156  }
157 
158  public void DeleteIfNotReferenced()
159  {
160  DeleteImpl(false);
161  }
162 
163  public void DeleteIrrevocably(int maxWaitForDeletionInMs)
164  {
165  var start = DateTime.Now;
166  var maxWaitForDeletion = new TimeSpan(0, 0, 0, 0, maxWaitForDeletionInMs);
167 
168  if (!Exists)
169  {
170  return;
171  }
172 
173  //status gets loaded lazily, and we need need to know status at this point (before Delete() gets called), so we store it in a local var.
174  PageState curStatus = Status;
175 
176  bool isAlreadyDeleted = curStatus == PageState.IsInRecycleBin;
177  if (!isAlreadyDeleted)
178  {
179  Delete();
180 
181  //pages in draft status don't get moved to recycle bin, but deleted completely from a normal delete call
182  if (curStatus == PageState.SavedAsDraft)
183  {
184  return;
185  }
186 
187  //deletion is an asynchronous process on the server, so we have to wait until it is done
188  WaitUntilPageIsInRecycleBin(maxWaitForDeletion);
189  }
190 
191  var alreadyElapsed = DateTime.Now - start;
192  var maxWaitForDeletionFromRecycleBin = maxWaitForDeletion - alreadyElapsed;
193 
194  WaitForDeletionFromRecycleBin(maxWaitForDeletionFromRecycleBin);
195  }
196 
197  public void DisconnectFromParent()
198  {
199  var link = MainLinkElement;
200  if (link != null)
201  {
202  link.Connections.Remove(this);
203  }
204  }
205 
206  public bool Exists
207  {
208  get { return Status != PageState.WillBeRemovedCompletely && Status != PageState.NotSet; }
209  }
210 
211  public string Filename
212  {
213  get { return Name; }
214  set { Name = value; }
215  }
216 
217  public string Headline
218  {
219  get { return LazyLoad(ref _headline); }
220  set
221  {
222  EnsureInitialization();
223  _headline = value;
224  }
225  }
226 
227  public int Id
228  {
229  get { return LazyLoad(ref _id); }
230  internal set { _id = value; }
231  }
232 
233  public IPageElement this[string elementName]
234  {
235  get
236  {
237  IPageElement result;
238  return ContentElements.TryGetByName(elementName, out result) ? result : LinkElements.GetByName(elementName);
239  }
240  }
241 
242  public ILanguageVariant LanguageVariant
243  {
244  get { return _lang; }
245  }
246 
247  public IRDList<ILinkElement> LinkElements { get; private set; }
248 
249  public IRDList<ILinkingAndAppearance> LinkedFrom { get; private set; }
250 
251  public ILinkElement MainParentLinkElement
252  {
253  get { return MainLinkElement; }
254  set { MainLinkElement = value; }
255  }
256 
262  [Obsolete(
263  "This is the PARENT link of the page, in a future version this will change to the main navigation link element of this page.")]
264  public ILinkElement MainLinkElement
265  {
266  get
267  {
268  if (_mainLinkElement != null)
269  {
270  return _mainLinkElement;
271  }
272  if (LazyLoad(ref _mainLinkGuid)
273  .Equals(Guid.Empty))
274  {
275  return null;
276  }
277  _mainLinkElement = (ILinkElement) PageElement.CreateElement(Project, _mainLinkGuid, LanguageVariant);
278  return _mainLinkElement;
279  }
280 
281  set
282  {
283  _mainLinkElement = value;
284  _mainLinkGuid = value != null ? value.Guid : default(Guid);
285  }
286  }
287 
288  [VersionIsGreaterThanOrEqual(9, VersionName = "Version 9")]
289  public ILinkElement MainLinkNavigationElement
290  {
291  get
292  {
293  VersionVerifier.EnsureVersion(Session);
294  if (_mainLinkNavigationElement != null)
295  {
296  return _mainLinkNavigationElement;
297  }
298  if (LazyLoad(ref _mainLinkNavigationGuid)
299  .Equals(Guid.Empty))
300  {
301  return null;
302  }
303  _mainLinkNavigationElement = (ILinkElement) PageElement.CreateElement(Project, _mainLinkNavigationGuid, LanguageVariant);
304  return _mainLinkNavigationElement;
305  }
306 
307  set
308  {
309  VersionVerifier.EnsureVersion(Session);
310  _mainLinkNavigationElement = value;
311  _mainLinkNavigationGuid = value != null ? value.Guid : default(Guid);
312  }
313  }
314 
315  public new string Name
316  {
317  get { return base.Name; }
318  set
319  {
320  EnsureInitialization();
321  base.Name = value;
322  }
323  }
324 
328  public IPage Parent
329  {
330  get { return _parentPage ?? (_parentPage = MainLinkElement != null ? MainLinkElement.Page : null); }
331  }
332 
333  public IRDList<ILinkElement> ReferencedFrom { get; private set; }
334 
335  public override void Refresh()
336  {
337  _contentClass = null;
338  _ccGuid = default(Guid);
339  _mainLinkElement = null;
340  base.Refresh();
341  }
342 
343  public IPage Refreshed()
344  {
345  Refresh();
346  return this;
347  }
348 
349  public void Reject()
350  {
351  ReleaseStatus = PageReleaseStatus.Rejected;
352  }
353 
354  public void Release()
355  {
356  ReleaseStatus = PageReleaseStatus.Released;
357  }
358 
359  public DateTime ReleaseDate
360  {
361  get { return LazyLoad(ref _releaseDate); }
362  }
363 
364  public PageReleaseStatus ReleaseStatus
365  {
366  get { return LazyLoad(ref _releaseStatus); }
367  set
368  {
369  using (new LanguageContext(LanguageVariant))
370  {
371  SaveReleaseStatus(value);
372  }
373  ResetReleaseStatusTo(value);
374  }
375  }
376 
377  public void ReplaceContentClass(IContentClass replacement, IDictionary<string, string> oldToNewMapping, Replace replace)
378  {
379  const string REPLACE_CC =
380  @"<PAGE action=""changetemplate"" guid=""{0}"" changeall=""{1}"" holdreferences=""1"" holdexportsettings=""1"" holdauthorizations=""1"" holdworkflow=""1""><TEMPLATE originalguid=""{2}"" changeguid=""{3}"">{4}</TEMPLATE></PAGE>";
381 
382  const string REPLACE_ELEMENT = @"<ELEMENT originalguid=""{0}"" changeguid=""{1}""/>";
383  var oldElements = ContentClass.Elements;
384  var newElements = replacement.Elements;
385 
386  var unmappedElements = oldElements.Where(element => !oldToNewMapping.ContainsKey(element.Name));
387  var unmappedStr = unmappedElements.Aggregate(
388  "",
389  (s, element) => s + REPLACE_ELEMENT.RQLFormat(element, RQL.SESSIONKEY_PLACEHOLDER));
390  var mappedStr = string.Join(
391  "",
392  from entry in oldToNewMapping
393  let oldElement = oldElements[entry.Key]
394  let newElement = newElements.GetByName(entry.Value)
395  select REPLACE_ELEMENT.RQLFormat(oldElement, newElement));
396 
397  var isReplacingAll = replace == Replace.ForAllPagesOfContentClass;
398  var query = REPLACE_CC.RQLFormat(this, isReplacingAll, ContentClass, replacement, mappedStr + unmappedStr);
399 
400  Project.ExecuteRQL(query, RqlType.SessionKeyInProject);
401 
402  _contentClass = null;
403  _ccGuid = default(Guid);
404  }
405 
406  public void ResetToDraft()
407  {
408  ReleaseStatus = PageReleaseStatus.Draft;
409  }
410 
411  public void Restore()
412  {
413  const string RESTORE_PAGE =
414  @"<PAGE action=""restore"" guid=""{0}"" alllanguages="""" forcedelete2910="""" forcedelete2911=""""/>";
415  Project.ExecuteRQL(string.Format(RESTORE_PAGE, Guid.ToRQLString()));
416  IsInitialized = false;
417  ReleaseStatus = PageReleaseStatus.NotSet;
418  Status = PageState.NotSet;
419  }
420 
421  public void SkipWorkflow()
422  {
423  const string SKIP_WORKFLOW = @"<PAGE action=""save"" guid=""{0}"" globalsave=""0"" skip=""1"" actionflag=""{1}"" />";
424 
425  Project.ExecuteRQL(string.Format(SKIP_WORKFLOW, Guid.ToRQLString(), (int) PageReleaseStatus.WorkFlow));
426  }
427 
428  public PageState Status
429  {
430  get { return LazyLoad(ref _pageState); }
431  set { _pageState = value; }
432  }
433 
434  public void SubmitToWorkflow()
435  {
436  ReleaseStatus = PageReleaseStatus.WorkFlow;
437  }
438 
439  public void Undo()
440  {
441  const string UNDO = @"<PAGE action=""rejecttempsaved"" guid=""{0}"" />";
442  Project.ExecuteRQL(string.Format(UNDO, Guid.ToRQLString()));
443  }
444 
445  public IWorkflow Workflow
446  {
447  get
448  {
449  var workflowElement = ((XmlElement) XmlElement.SelectSingleNode("descendant::WORKFLOW"));
450  return workflowElement == null ? null : new Workflow(Project, workflowElement.GetGuid());
451  }
452  }
453 
454  public IPagePublishJob CreatePublishJob()
455  {
456  return new PagePublishJob(this);
457  }
458 
459  //TODO versionguid?
460  public string GetPreviewHtml(PreviewHtmlType previewType = PreviewHtmlType.Raw)
461  {
462  const string PREVIEW =
463  "<PREVIEW mode=\"0\" projectguid=\"{0}\" versionguid=\"\" url=\"./PreviewHandler.ashx\" querystring=\"Action=RedDot&amp;Mode=0&amp;PageGuid={1}&amp;WithCache=1&amp;Type=page&amp;Isolated=1&amp;PageLocked=0&amp;Rights1=-33673217&amp;isArchivedPage=0&amp;\" isolated=\"0\" templateguid=\"{2}\" rights1=\"-33673217\" />";
464 
465  var answer = Project.Session.ExecuteRQLRaw(
466  PREVIEW.RQLFormat(Project, this, ContentClass),
467  RQL.IODataFormat.SessionKeyAndLogonGuid);
468 
469  if (previewType == PreviewHtmlType.AddCmsBaseUrlToHeadSection)
470  {
471  //TODO this is a very naive approach, e.g. head isn't necessarily there or could be in a comment above a real head element
472  answer = answer.ReplaceFirst(
473  "<head>",
474  string.Format("<head>\n\t<base href=\"{0}/WebClient/\"/>\n", Project.Session.ServerLogin.Address));
475  }
476  return answer;
477  }
478 
479  public override bool Equals(object other)
480  {
481  if (!(other is IPage) || !base.Equals(other))
482  {
483  return false;
484  }
485 
486  return LanguageVariant.Equals(((IPage) other).LanguageVariant);
487  }
488 
489  public override int GetHashCode()
490  {
491  return Guid.GetHashCode() + 3 * LanguageVariant.GetHashCode();
492  }
493 
494  public override string ToString()
495  {
496  return string.Format("{0} (Id: {1} Guid: {2} Language: {3})", Headline, Id, Guid.ToRQLString(), LanguageVariant.Abbreviation);
497  }
498 
499  protected override void LoadWholeObject()
500  {
501  LoadXml();
502  }
503 
504  protected override XmlElement RetrieveWholeObject()
505  {
506  using (new LanguageContext(LanguageVariant))
507  {
508  const string REQUEST_PAGE = @"<PAGE action=""load"" guid=""{0}"" option=""extendedinfo""/>";
509 
510  XmlDocument xmlDoc = Project.ExecuteRQL(string.Format(REQUEST_PAGE, Guid.ToRQLString()));
511  XmlNodeList pages = xmlDoc.GetElementsByTagName("PAGE");
512  if (pages.Count != 1)
513  {
514  throw new SmartAPIException(Session.ServerLogin, string.Format("Could not load page with guid {0}", Guid.ToRQLString()));
515  }
516  return (XmlElement) pages[0];
517  }
518  }
519 
520  private void CheckReleaseStatusSettingSuccess(PageReleaseStatus value, XmlDocument xmlDoc)
521  {
522  XmlNodeList pageElements = xmlDoc.GetElementsByTagName("PAGE");
523  if (pageElements.Count != 1)
524  {
525  var missingKeywords = xmlDoc.SelectNodes("/IODATA/EMPTYELEMENTS/ELEMENT[@type='1002']");
526  if (missingKeywords != null && missingKeywords.Count != 0)
527  {
528  throw new MissingKeywordsException(this, GetNames(missingKeywords));
529  }
530 
531  var missingElements = xmlDoc.SelectNodes("/IODATA/EMPTYELEMENTS/ELEMENT");
532  if (missingElements != null && missingElements.Count > 0)
533  {
534  throw new MissingElementValueException(this, GetNames(missingElements));
535  }
536 
537  throw new PageStatusException(this, "Could not set release status to " + value);
538  }
539  var element = (XmlElement) pageElements[0];
540  var flag = (PageReleaseStatus) element.GetIntAttributeValue("actionflag")
541  .GetValueOrDefault();
542 
543  if (!flag.HasFlag(value) && !IsReleasedIntoWorkflow(value, flag))
544  {
545  throw new PageStatusException(this, "Could not set release status to " + value);
546  }
547  }
548 
549  private void DeleteImpl(bool forceDeletion)
550  {
551  try
552  {
553  const string DELETE_PAGE =
554  @"<PAGE action=""delete"" guid=""{0}"" forcedelete2910=""{1}"" forcedelete2911=""{1}""><LANGUAGEVARIANTS><LANGUAGEVARIANT language=""{2}""/></LANGUAGEVARIANTS></PAGE>";
555  XmlDocument xmlDoc = Project.ExecuteRQL(DELETE_PAGE.RQLFormat(this, forceDeletion, LanguageVariant.Abbreviation));
556  if (!xmlDoc.IsContainingOk())
557  {
558  throw new PageDeletionException(Project.Session.ServerLogin, string.Format("Could not delete page {0}", this));
559  }
560  }
561  catch (RQLException e)
562  {
563  throw new PageDeletionException(e);
564  }
565  IsInitialized = false;
566  _releaseStatus = PageReleaseStatus.NotSet;
567  Status = PageState.NotSet;
568  }
569 
570  private List<IPageElement> GetContentElements()
571  {
572  using (new LanguageContext(LanguageVariant))
573  {
574  const string LOAD_PAGE_ELEMENTS = @"<PROJECT><PAGE guid=""{0}""><ELEMENTS action=""load""/></PAGE></PROJECT>";
575  XmlDocument xmlDoc = Project.ExecuteRQL(string.Format(LOAD_PAGE_ELEMENTS, Guid.ToRQLString()));
576  return ToElementList(xmlDoc.GetElementsByTagName("ELEMENT"));
577  }
578  }
579 
580  private List<ILinkElement> GetLinks()
581  {
582  using (new LanguageContext(LanguageVariant))
583  {
584  const string LOAD_LINKS = @"<PAGE guid=""{0}""><LINKS action=""load"" /></PAGE>";
585  XmlDocument xmlDoc = Project.ExecuteRQL(string.Format(LOAD_LINKS, Guid.ToRQLString()));
586  return
587  (from XmlElement curNode in xmlDoc.GetElementsByTagName("LINK")
588  select (ILinkElement) PageElement.CreateElement(Project, curNode)).ToList();
589  }
590  }
591 
592  private List<ILinkingAndAppearance> GetLinksFrom()
593  {
594  const string @LOAD_LINKING = @"<PAGE guid=""{0}""><LINKSFROM action=""load"" /></PAGE>";
595 
596  var xmlDoc = Project.ExecuteRQL(LOAD_LINKING.RQLFormat(this));
597  return
598  (from XmlElement curLink in xmlDoc.GetElementsByTagName("LINK")
599  select (ILinkingAndAppearance) new LinkingAndAppearance(this, curLink)).ToList();
600  }
601 
602  private static IEnumerable<string> GetNames(XmlNodeList elements)
603  {
604  return elements.Cast<XmlElement>()
605  .Select(x => x.GetAttributeValue("name"));
606  }
607 
608  private List<ILinkElement> GetReferencingLinks()
609  {
610  const string LIST_REFERENCES = @"<REFERENCE action=""list"" guid=""{0}"" />";
611  XmlDocument xmlDoc = Project.ExecuteRQL(LIST_REFERENCES.RQLFormat(this), RqlType.SessionKeyInProject);
612 
613  return (from XmlElement curLink in xmlDoc.GetElementsByTagName("LINK")
614  select (ILinkElement) PageElement.CreateElement(Project, curLink.GetGuid(), LanguageVariant)).ToList();
615  }
616 
617  private void InitProperties()
618  {
619  LinkElements = new RDList<ILinkElement>(GetLinks, Caching.Enabled);
620  ContentElements = new NameIndexedRDList<IPageElement>(GetContentElements, Caching.Enabled);
621  ReferencedFrom = new RDList<ILinkElement>(GetReferencingLinks, Caching.Enabled);
622  AssignedKeywords = new PageAssignedKeywords(this, Caching.Enabled);
623  LinkedFrom = new RDList<ILinkingAndAppearance>(GetLinksFrom, Caching.Enabled);
624  }
625 
626  private static bool IsReleasedIntoWorkflow(PageReleaseStatus value, PageReleaseStatus flag)
627  {
628  return value == PageReleaseStatus.Released && flag.HasFlag(PageReleaseStatus.WorkFlow);
629  }
630 
631  private void LoadXml()
632  {
633  //TODO schoenere loesung fuer partielles nachladen von pages wegen unterschiedlicher anfragen fuer unterschiedliche infos
634  try
635  {
636  EnsuredInit(ref _id, "id", int.Parse);
637  EnsuredInit(ref _lang, "languagevariantid", Project.LanguageVariants.Get);
638  }
639  catch (SmartAPIException e)
640  {
641  throw new NoSuchPageException(e);
642  }
643  //parentguid seems to be the mainlinkguid
644  //InitIfPresent(ref _parentPage, "parentguid", x => new Page(Project, GuidConvert(x), LanguageVariant));
645  InitIfPresent(ref _headline, "headline", x => x);
646  InitIfPresent(ref _pageFlags, "flags", x => (PageFlags) int.Parse(x));
647  InitIfPresent(ref _ccGuid, "templateguid", Guid.Parse);
648  InitIfPresent(ref _pageState, "status", x => (PageState) int.Parse(x));
649 
650  _releaseStatus = ReleaseStatusFromFlags();
651 
652  _checkinDate = _xmlElement.GetOADate("checkindate")
653  .GetValueOrDefault();
654 
655  InitIfPresent(ref _mainLinkGuid, "mainlinkguid", GuidConvert);
656  InitIfPresent(ref _releaseDate, "releasedate", XmlUtil.ToOADate);
657  InitIfPresent(ref _createDate, "createdate", XmlUtil.ToOADate);
658  InitIfPresent(ref _changeDate, "changeDate", XmlUtil.ToOADate);
659 
660  var mainLinkElement = (XmlElement) _xmlElement.GetElementsByTagName("MAINLINK")[0];
661  _mainLinkNavigationGuid = mainLinkElement != null ? mainLinkElement.GetGuid() : Guid.Empty;
662  }
663 
664  private PageReleaseStatus ReleaseStatusFromFlags()
665  {
666  if (_pageFlags == PageFlags.Null)
667  {
668  return default(PageReleaseStatus);
669  }
670  if ((_pageFlags & PageFlags.Draft) == PageFlags.Draft)
671  {
672  return PageReleaseStatus.Draft;
673  }
674  if ((_pageFlags & PageFlags.Workflow) == PageFlags.Workflow)
675  {
676  return PageReleaseStatus.WorkFlow;
677  }
678  if ((_pageFlags & PageFlags.WaitingForCorrection) == PageFlags.WaitingForCorrection)
679  {
680  return PageReleaseStatus.Rejected;
681  }
682 
683  return default(PageReleaseStatus);
684  }
685 
686  private void ResetReleaseStatusTo(PageReleaseStatus value)
687  {
688  IsInitialized = false;
689  _releaseStatus = value;
690  Status = PageState.NotSet;
691  }
692 
693  private void SaveReleaseStatus(PageReleaseStatus value)
694  {
695  const string SET_RELEASE_STATUS = @"<PAGE action=""save"" guid=""{0}"" actionflag=""{1}""/>";
696  XmlDocument xmlDoc = Project.ExecuteRQL(string.Format(SET_RELEASE_STATUS, Guid.ToRQLString(), (int) value));
697  CheckReleaseStatusSettingSuccess(value, xmlDoc);
698  }
699 
700  private List<IPageElement> ToElementList(XmlNodeList elementNodes)
701  {
702  return
703  (from XmlElement curNode in elementNodes let element = TryCreateElement(curNode) where element != null select element)
704  .Cast<IPageElement>()
705  .ToList();
706  }
707 
708  private IPageElement TryCreateElement(XmlElement xmlElement)
709  {
710  try
711  {
712  return PageElement.CreateElement(Project, xmlElement);
713  }
714  catch (ArgumentException)
715  {
716  return null;
717  }
718  }
719 
720  private void WaitForDeletionFromRecycleBin(TimeSpan maxWaitForDeletionFromRecycleBin)
721  {
722  //At this point we are at a race condition with the server.
723  //It can happen that although the status is set to IsInRecycleBin, it can't be removed from there yet.
724  //Therefor we have to try again, until it works (or a timeout is reached to avoid infinite loops on errors).
725  var timeOutTracker = new TimeOutTracker(maxWaitForDeletionFromRecycleBin);
726  do
727  {
728  DeleteFromRecycleBin();
729 
730  try
731  {
732  Refresh();
733  }
734  catch (NoSuchPageException)
735  {
736  return;
737  }
738  if (!Exists)
739  {
740  return;
741  }
742  } while (!timeOutTracker.HasTimedOut);
743 
744  throw new PageDeletionException(
745  Project.Session.ServerLogin,
746  string.Format("Timeout while waiting for remove from recycle bin for page {0}", this));
747  }
748 
749  private void WaitUntilPageIsInRecycleBin(TimeSpan maxWaitForDeletionInMs)
750  {
751  var timeoutTracker = new TimeOutTracker(maxWaitForDeletionInMs);
752  do
753  {
754  try
755  {
756  Refresh();
757  }
758  catch (NoSuchPageException)
759  {
760  return;
761  }
762  if (!Exists || Status == PageState.IsInRecycleBin)
763  {
764  return;
765  }
766  } while (!timeoutTracker.HasTimedOut);
767 
768  throw new PageDeletionException(
769  Project.Session.ServerLogin,
770  string.Format("Timeout while waiting for the page {0} to move into the recycle bin", this));
771  }
772  }
773 }