17 using System.Collections.Generic;
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;
29 namespace erminas.SmartAPI.CMS.Project.Pages
31 internal class Page : PartialRedDotProjectObject, IPage
34 private DateTime _changeDate;
35 private Guid _changeUserGuid;
36 private DateTime _checkinDate;
38 private DateTime _createDate;
39 private string _headline;
41 private ILanguageVariant _lang;
43 private Guid _mainLinkGuid;
45 private Guid _mainLinkNavigationGuid;
48 private IPage _parentPage;
49 private DateTime _releaseDate;
52 internal Page(IProject project, XmlElement xmlElement) : base(project, xmlElement)
55 IsInitialized =
false;
59 internal Page(IProject project, Guid guid, ILanguageVariant languageVariant) : base(project, guid)
61 _lang = languageVariant;
70 internal string InitialHeadlineValue
72 set { _headline = value; }
75 public IAssignedKeywords AssignedKeywords {
get;
private set; }
77 public DateTime CreateDate
79 get {
return LazyLoad(ref _createDate); }
82 public DateTime CheckinDate
84 get {
return LazyLoad(ref _checkinDate); }
87 public DateTime LastChangeDate
89 get {
return LazyLoad(ref _changeDate); }
92 public IUser LastChangeUser
94 get {
return new User(Project.Session, LazyLoad(ref _changeUserGuid)); }
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();
103 Project.ExecuteRQL(SAVE_PAGE.RQLFormat(Guid, HttpUtility.HtmlEncode(Headline), HttpUtility.HtmlEncode(Filename), mainlink));
104 if (xmlDoc.GetElementsByTagName(
"PAGE")
107 throw new SmartAPIException(Session.ServerLogin,
string.Format(
"Could not save changes to page {0}",
this));
113 get {
return _contentClass ?? (_contentClass = Project.ContentClasses.GetByGuid(LazyLoad(ref _ccGuid))); }
116 public IIndexedRDList<string, IPageElement> ContentElements {
get;
private set; }
118 public IPageCopyAndConnectJob CreateCopyAndConnectJob(
ILinkElement connectionTarget,
121 return new PageCopyAndConnectJob(
this, connectionTarget, flags);
126 CreateCopyAndConnectJob(connectionTarget, flags)
135 public void DeleteFromRecycleBin()
139 const string DELETE_FINALLY =
140 @"<PAGE action=""deletefinally"" guid=""{0}"" alllanguages="""" forcedelete2910="""" forcedelete2911=""""/>";
141 Project.ExecuteRQL(
string.Format(DELETE_FINALLY, Guid.ToRQLString()));
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);
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);
153 IsInitialized =
false;
158 public void DeleteIfNotReferenced()
163 public void DeleteIrrevocably(
int maxWaitForDeletionInMs)
165 var start = DateTime.Now;
166 var maxWaitForDeletion =
new TimeSpan(0, 0, 0, 0, maxWaitForDeletionInMs);
176 bool isAlreadyDeleted = curStatus ==
PageState.IsInRecycleBin;
177 if (!isAlreadyDeleted)
188 WaitUntilPageIsInRecycleBin(maxWaitForDeletion);
191 var alreadyElapsed = DateTime.Now - start;
192 var maxWaitForDeletionFromRecycleBin = maxWaitForDeletion - alreadyElapsed;
194 WaitForDeletionFromRecycleBin(maxWaitForDeletionFromRecycleBin);
197 public void DisconnectFromParent()
199 var link = MainLinkElement;
202 link.Connections.Remove(
this);
208 get {
return Status !=
PageState.WillBeRemovedCompletely && Status !=
PageState.NotSet; }
211 public string Filename
214 set { Name = value; }
217 public string Headline
219 get {
return LazyLoad(ref _headline); }
222 EnsureInitialization();
229 get {
return LazyLoad(ref _id); }
230 internal set { _id = value; }
238 return ContentElements.TryGetByName(elementName, out result) ? result : LinkElements.GetByName(elementName);
242 public ILanguageVariant LanguageVariant
244 get {
return _lang; }
247 public IRDList<ILinkElement> LinkElements {
get;
private set; }
249 public IRDList<ILinkingAndAppearance> LinkedFrom {
get;
private set; }
253 get {
return MainLinkElement; }
254 set { MainLinkElement = value; }
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.")]
268 if (_mainLinkElement != null)
270 return _mainLinkElement;
272 if (LazyLoad(ref _mainLinkGuid)
277 _mainLinkElement = (
ILinkElement) PageElement.CreateElement(Project, _mainLinkGuid, LanguageVariant);
278 return _mainLinkElement;
283 _mainLinkElement = value;
284 _mainLinkGuid = value != null ? value.
Guid :
default(Guid);
288 [VersionIsGreaterThanOrEqual(9, VersionName =
"Version 9")]
293 VersionVerifier.EnsureVersion(Session);
294 if (_mainLinkNavigationElement != null)
296 return _mainLinkNavigationElement;
298 if (LazyLoad(ref _mainLinkNavigationGuid)
303 _mainLinkNavigationElement = (
ILinkElement) PageElement.CreateElement(Project, _mainLinkNavigationGuid, LanguageVariant);
304 return _mainLinkNavigationElement;
309 VersionVerifier.EnsureVersion(Session);
310 _mainLinkNavigationElement = value;
311 _mainLinkNavigationGuid = value != null ? value.
Guid :
default(Guid);
315 public new string Name
317 get {
return base.Name; }
320 EnsureInitialization();
330 get {
return _parentPage ?? (_parentPage = MainLinkElement != null ? MainLinkElement.Page : null); }
333 public IRDList<ILinkElement> ReferencedFrom {
get;
private set; }
335 public override void Refresh()
337 _contentClass = null;
338 _ccGuid =
default(Guid);
339 _mainLinkElement = null;
343 public IPage Refreshed()
354 public void Release()
359 public DateTime ReleaseDate
361 get {
return LazyLoad(ref _releaseDate); }
366 get {
return LazyLoad(ref _releaseStatus); }
371 SaveReleaseStatus(value);
373 ResetReleaseStatusTo(value);
377 public void ReplaceContentClass(
IContentClass replacement, IDictionary<string, string> oldToNewMapping,
Replace replace)
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>";
382 const string REPLACE_ELEMENT =
@"<ELEMENT originalguid=""{0}"" changeguid=""{1}""/>";
383 var oldElements = ContentClass.Elements;
384 var newElements = replacement.
Elements;
386 var unmappedElements = oldElements.Where(element => !oldToNewMapping.ContainsKey(element.Name));
387 var unmappedStr = unmappedElements.Aggregate(
389 (s, element) => s + REPLACE_ELEMENT.RQLFormat(element, RQL.SESSIONKEY_PLACEHOLDER));
390 var mappedStr =
string.Join(
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));
397 var isReplacingAll = replace ==
Replace.ForAllPagesOfContentClass;
398 var query = REPLACE_CC.RQLFormat(
this, isReplacingAll, ContentClass, replacement, mappedStr + unmappedStr);
400 Project.ExecuteRQL(query,
RqlType.SessionKeyInProject);
402 _contentClass = null;
403 _ccGuid =
default(Guid);
406 public void ResetToDraft()
411 public void Restore()
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;
421 public void SkipWorkflow()
423 const string SKIP_WORKFLOW =
@"<PAGE action=""save"" guid=""{0}"" globalsave=""0"" skip=""1"" actionflag=""{1}"" />";
425 Project.ExecuteRQL(
string.Format(SKIP_WORKFLOW, Guid.ToRQLString(), (int)
PageReleaseStatus.WorkFlow));
430 get {
return LazyLoad(ref _pageState); }
431 set { _pageState = value; }
434 public void SubmitToWorkflow()
441 const string UNDO =
@"<PAGE action=""rejecttempsaved"" guid=""{0}"" />";
442 Project.ExecuteRQL(
string.Format(UNDO, Guid.ToRQLString()));
449 var workflowElement = ((XmlElement) XmlElement.SelectSingleNode(
"descendant::WORKFLOW"));
450 return workflowElement == null ? null :
new Workflow(Project, workflowElement.GetGuid());
454 public IPagePublishJob CreatePublishJob()
456 return new PagePublishJob(
this);
462 const string PREVIEW =
463 "<PREVIEW mode=\"0\" projectguid=\"{0}\" versionguid=\"\" url=\"./PreviewHandler.ashx\" querystring=\"Action=RedDot&Mode=0&PageGuid={1}&WithCache=1&Type=page&Isolated=1&PageLocked=0&Rights1=-33673217&isArchivedPage=0&\" isolated=\"0\" templateguid=\"{2}\" rights1=\"-33673217\" />";
465 var answer = Project.Session.ExecuteRQLRaw(
466 PREVIEW.RQLFormat(Project,
this, ContentClass),
467 RQL.IODataFormat.SessionKeyAndLogonGuid);
472 answer = answer.ReplaceFirst(
474 string.Format(
"<head>\n\t<base href=\"{0}/WebClient/\"/>\n", Project.Session.ServerLogin.Address));
479 public override bool Equals(
object other)
481 if (!(other is IPage) || !base.Equals(other))
486 return LanguageVariant.Equals(((IPage) other).LanguageVariant);
489 public override int GetHashCode()
491 return Guid.GetHashCode() + 3 * LanguageVariant.GetHashCode();
494 public override string ToString()
496 return string.Format(
"{0} (Id: {1} Guid: {2} Language: {3})", Headline, Id, Guid.ToRQLString(), LanguageVariant.Abbreviation);
499 protected override void LoadWholeObject()
504 protected override XmlElement RetrieveWholeObject()
508 const string REQUEST_PAGE =
@"<PAGE action=""load"" guid=""{0}"" option=""extendedinfo""/>";
510 XmlDocument xmlDoc = Project.ExecuteRQL(
string.Format(REQUEST_PAGE, Guid.ToRQLString()));
511 XmlNodeList pages = xmlDoc.GetElementsByTagName(
"PAGE");
512 if (pages.Count != 1)
514 throw new SmartAPIException(Session.ServerLogin,
string.Format(
"Could not load page with guid {0}", Guid.ToRQLString()));
516 return (XmlElement) pages[0];
520 private void CheckReleaseStatusSettingSuccess(
PageReleaseStatus value, XmlDocument xmlDoc)
522 XmlNodeList pageElements = xmlDoc.GetElementsByTagName(
"PAGE");
523 if (pageElements.Count != 1)
525 var missingKeywords = xmlDoc.SelectNodes(
"/IODATA/EMPTYELEMENTS/ELEMENT[@type='1002']");
526 if (missingKeywords != null && missingKeywords.Count != 0)
531 var missingElements = xmlDoc.SelectNodes(
"/IODATA/EMPTYELEMENTS/ELEMENT");
532 if (missingElements != null && missingElements.Count > 0)
539 var element = (XmlElement) pageElements[0];
541 .GetValueOrDefault();
543 if (!flag.HasFlag(value) && !IsReleasedIntoWorkflow(value, flag))
549 private void DeleteImpl(
bool forceDeletion)
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())
558 throw new PageDeletionException(Project.Session.ServerLogin,
string.Format(
"Could not delete page {0}",
this));
565 IsInitialized =
false;
570 private List<IPageElement> GetContentElements()
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"));
580 private List<ILinkElement> GetLinks()
584 const string LOAD_LINKS =
@"<PAGE guid=""{0}""><LINKS action=""load"" /></PAGE>";
585 XmlDocument xmlDoc = Project.ExecuteRQL(
string.Format(LOAD_LINKS, Guid.ToRQLString()));
587 (from XmlElement curNode in xmlDoc.GetElementsByTagName(
"LINK")
588 select (
ILinkElement) PageElement.CreateElement(Project, curNode)).ToList();
592 private List<ILinkingAndAppearance> GetLinksFrom()
594 const string @LOAD_LINKING =
@"<PAGE guid=""{0}""><LINKSFROM action=""load"" /></PAGE>";
596 var xmlDoc = Project.ExecuteRQL(LOAD_LINKING.RQLFormat(
this));
598 (from XmlElement curLink in xmlDoc.GetElementsByTagName(
"LINK")
599 select (ILinkingAndAppearance) new LinkingAndAppearance(this, curLink)).ToList();
602 private static IEnumerable<
string> GetNames(XmlNodeList elements)
604 return elements.Cast<XmlElement>()
605 .Select(x => x.GetAttributeValue(
"name"));
608 private List<ILinkElement> GetReferencingLinks()
610 const string LIST_REFERENCES =
@"<REFERENCE action=""list"" guid=""{0}"" />";
611 XmlDocument xmlDoc = Project.ExecuteRQL(LIST_REFERENCES.RQLFormat(
this),
RqlType.SessionKeyInProject);
613 return (from XmlElement curLink in xmlDoc.GetElementsByTagName(
"LINK")
614 select (
ILinkElement) PageElement.CreateElement(Project, curLink.GetGuid(), LanguageVariant)).ToList();
617 private
void InitProperties()
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);
631 private void LoadXml()
636 EnsuredInit(ref _id,
"id",
int.Parse);
637 EnsuredInit(ref _lang,
"languagevariantid", Project.LanguageVariants.Get);
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));
650 _releaseStatus = ReleaseStatusFromFlags();
652 _checkinDate = _xmlElement.GetOADate(
"checkindate")
653 .GetValueOrDefault();
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);
660 var mainLinkElement = (XmlElement) _xmlElement.GetElementsByTagName(
"MAINLINK")[0];
661 _mainLinkNavigationGuid = mainLinkElement != null ? mainLinkElement.GetGuid() : Guid.Empty;
688 IsInitialized =
false;
689 _releaseStatus = value;
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);
700 private List<IPageElement> ToElementList(XmlNodeList elementNodes)
703 (from XmlElement curNode in elementNodes let element = TryCreateElement(curNode) where element != null select element)
708 private IPageElement TryCreateElement(XmlElement xmlElement)
712 return PageElement.CreateElement(Project, xmlElement);
714 catch (ArgumentException)
720 private void WaitForDeletionFromRecycleBin(TimeSpan maxWaitForDeletionFromRecycleBin)
725 var timeOutTracker =
new TimeOutTracker(maxWaitForDeletionFromRecycleBin);
728 DeleteFromRecycleBin();
742 }
while (!timeOutTracker.HasTimedOut);
745 Project.Session.ServerLogin,
746 string.Format(
"Timeout while waiting for remove from recycle bin for page {0}",
this));
749 private void WaitUntilPageIsInRecycleBin(TimeSpan maxWaitForDeletionInMs)
751 var timeoutTracker =
new TimeOutTracker(maxWaitForDeletionInMs);
762 if (!Exists || Status ==
PageState.IsInRecycleBin)
766 }
while (!timeoutTracker.HasTimedOut);
769 Project.Session.ServerLogin,
770 string.Format(
"Timeout while waiting for the page {0} to move into the recycle bin",
this));