/// <summary>
        /// Executes the specified request.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns>Save response.</returns>
        /// <exception cref="CmsException">Failed to save page properties.</exception>
        public SavePageResponse Execute(EditPagePropertiesViewModel request)
        {
            var isMultilanguageEnabled = cmsConfiguration.EnableMultilanguage;

            ValidateRequest(request, isMultilanguageEnabled);

            var pageQuery =
                Repository.AsQueryable <PageProperties>(p => p.Id == request.Id)
                .FetchMany(p => p.Options)
                .Fetch(p => p.Layout)
                .ThenFetchMany(l => l.LayoutOptions)
                .FetchMany(p => p.MasterPages)
                .AsQueryable();

            if (cmsConfiguration.Security.AccessControlEnabled)
            {
                pageQuery = pageQuery.FetchMany(f => f.AccessRules);
            }

            var page         = pageQuery.ToList().FirstOne();
            var beforeChange = new UpdatingPagePropertiesModel(page);

            var roles = page.IsMasterPage
                            ? new[]
            {
                RootModuleConstants.UserRoles.EditContent, RootModuleConstants.UserRoles.PublishContent, RootModuleConstants.UserRoles.Administration
            }
                            : new[] { RootModuleConstants.UserRoles.EditContent, RootModuleConstants.UserRoles.PublishContent };

            if (cmsConfiguration.Security.AccessControlEnabled)
            {
                AccessControlService.DemandAccess(page, Context.Principal, AccessLevel.ReadWrite, roles);
            }
            else
            {
                AccessControlService.DemandAccess(Context.Principal, roles);
            }

            var canEdit = page.IsMasterPage
                              ? SecurityService.IsAuthorized(
                Context.Principal,
                RootModuleConstants.UserRoles.MultipleRoles(RootModuleConstants.UserRoles.EditContent, RootModuleConstants.UserRoles.Administration))
                              : SecurityService.IsAuthorized(Context.Principal, RootModuleConstants.UserRoles.EditContent);

            IList <PageProperties> translations = null;

            if (canEdit && isMultilanguageEnabled && !page.IsMasterPage)
            {
                translations = LoadAndValidateTranslations(page, request);
            }

            // Load master pages for updating page's master path and page's children master path
            IList <Guid>       newMasterIds;
            IList <Guid>       oldMasterIds;
            IList <Guid>       childrenPageIds;
            IList <MasterPage> existingChildrenMasterPages;

            masterPageService.PrepareForUpdateChildrenMasterPages(page, request.MasterPageId, out newMasterIds, out oldMasterIds, out childrenPageIds, out existingChildrenMasterPages);

            IList <SitemapNode> updatedNodes = null;

            // Start transaction, only when everything is already loaded
            UnitOfWork.BeginTransaction();

            Models.Redirect redirectCreated  = null;
            var             initialSeoStatus = page.HasSEO;

            request.PageUrl = urlService.FixUrl(request.PageUrl);

            if (canEdit && !string.Equals(page.PageUrl, request.PageUrl))
            {
                pageService.ValidatePageUrl(request.PageUrl, request.Id);
                if (request.RedirectFromOldUrl)
                {
                    var redirect = redirectService.CreateRedirectEntity(page.PageUrl, request.PageUrl);
                    if (redirect != null)
                    {
                        Repository.Save(redirect);
                        redirectCreated = redirect;
                    }
                }

                if (request.UpdateSitemap)
                {
                    updatedNodes = sitemapService.ChangeUrlsInAllSitemapsNodes(page.PageUrl, request.PageUrl);
                }

                page.PageUrl = request.PageUrl;
            }

            List <PageProperties> updatePageTranslations = null;

            if (canEdit)
            {
                page.PageUrlHash         = page.PageUrl.UrlHash();
                page.ForceAccessProtocol = request.ForceAccessProtocol;

                categoryService.CombineEntityCategories <PageProperties, PageCategory>(page, request.Categories);

                page.Title     = request.PageName;
                page.CustomCss = request.PageCSS;
                page.CustomJS  = request.PageJavascript;

                masterPageService.SetMasterOrLayout(page, request.MasterPageId, request.TemplateId);

                if (isMultilanguageEnabled && !page.IsMasterPage)
                {
                    updatePageTranslations = UpdatePageTranslations(page, translations, request);
                }
            }

            var publishDraftContent = false;

            if (request.CanPublishPage && !page.IsMasterPage)
            {
                AccessControlService.DemandAccess(Context.Principal, RootModuleConstants.UserRoles.PublishContent);

                if (request.IsPagePublished)
                {
                    if (page.Status != PageStatus.Published)
                    {
                        page.Status         = PageStatus.Published;
                        page.PublishedOn    = DateTime.Now;
                        publishDraftContent = true;
                    }
                }
                else
                {
                    page.Status = PageStatus.Unpublished;
                }
            }

            IList <PageOption> pageOptions = page.Options.Distinct().ToList();

            if (canEdit)
            {
                if (!page.IsMasterPage)
                {
                    page.UseNoFollow = request.UseNoFollow;
                    page.UseNoIndex  = request.UseNoIndex;
                    page.IsArchived  = request.IsArchived;
                }

                page.UseCanonicalUrl = request.UseCanonicalUrl;
                page.Version         = request.Version;

                page.Image          = request.Image != null && request.Image.ImageId.HasValue ? Repository.AsProxy <MediaImage>(request.Image.ImageId.Value) : null;
                page.SecondaryImage = request.SecondaryImage != null && request.SecondaryImage.ImageId.HasValue
                                          ? Repository.AsProxy <MediaImage>(request.SecondaryImage.ImageId.Value)
                                          : null;
                page.FeaturedImage = request.FeaturedImage != null && request.FeaturedImage.ImageId.HasValue
                                         ? Repository.AsProxy <MediaImage>(request.FeaturedImage.ImageId.Value)
                                         : null;

                pageOptions = optionService.SaveOptionValues(request.OptionValues, pageOptions, () => new PageOption {
                    Page = page
                });

                if (cmsConfiguration.Security.AccessControlEnabled)
                {
                    page.AccessRules.RemoveDuplicateEntities();

                    var accessRules = request.UserAccessList != null?request.UserAccessList.Cast <IAccessRule>().ToList() : null;

                    accessControlService.UpdateAccessControl(page, accessRules);
                }
            }

            // Notify about page properties changing.
            var cancelEventArgs = Events.PageEvents.Instance.OnPagePropertiesChanging(beforeChange, new UpdatingPagePropertiesModel(page));

            if (cancelEventArgs.Cancel)
            {
                Context.Messages.AddError(cancelEventArgs.CancellationErrorMessages.ToArray());
                return(null);
            }

            Repository.Save(page);

            IList <Tag> newTags = null;

            if (canEdit)
            {
                masterPageService.UpdateChildrenMasterPages(existingChildrenMasterPages, oldMasterIds, newMasterIds, childrenPageIds);
                tagService.SavePageTags(page, request.Tags, out newTags);
            }

            if (publishDraftContent)
            {
                contentService.PublishDraftContent(page.Id);
            }

            UnitOfWork.Commit();

            // Notify about page properties change.
            page.Options = pageOptions;
            Events.PageEvents.Instance.OnPagePropertiesChanged(page);

            // Notify about translation properties changed
            if (updatePageTranslations != null)
            {
                updatePageTranslations.ForEach(Events.PageEvents.Instance.OnPagePropertiesChanged);
            }

            // Notify about redirect creation.
            if (redirectCreated != null)
            {
                Events.PageEvents.Instance.OnRedirectCreated(redirectCreated);
            }

            // Notify about SEO status change.
            if (initialSeoStatus != page.HasSEO)
            {
                Events.PageEvents.Instance.OnPageSeoStatusChanged(page);
            }

            // Notify about new tags.
            Events.RootEvents.Instance.OnTagCreated(newTags);

            // Notify about updated sitemap nodes.
            if (updatedNodes != null)
            {
                var updatedSitemaps = new List <Models.Sitemap>();
                foreach (var node in updatedNodes)
                {
                    Events.SitemapEvents.Instance.OnSitemapNodeUpdated(node);
                    if (!updatedSitemaps.Contains(node.Sitemap))
                    {
                        updatedSitemaps.Add(node.Sitemap);
                    }
                }

                foreach (var updatedSitemap in updatedSitemaps)
                {
                    Events.SitemapEvents.Instance.OnSitemapUpdated(updatedSitemap);
                }
            }

            return(new SavePageResponse(page));
        }
        /// <summary>
        /// Puts the specified request.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns><c>PutPageResponse</c> with created or updated item id.</returns>
        public PutPagePropertiesResponse Put(PutPagePropertiesRequest request)
        {
            if (request.Data.IsMasterPage)
            {
                accessControlService.DemandAccess(securityService.GetCurrentPrincipal(), RootModuleConstants.UserRoles.Administration);
            }
            else
            {
                accessControlService.DemandAccess(securityService.GetCurrentPrincipal(), RootModuleConstants.UserRoles.EditContent);
            }

            PageProperties pageProperties = null;

            var isNew = !request.Id.HasValue || request.Id.Value.HasDefaultValue();

            if (!isNew)
            {
                pageProperties =
                    repository.AsQueryable <PageProperties>(e => e.Id == request.Id.GetValueOrDefault())
                    .FetchMany(p => p.Options)
                    .Fetch(p => p.Layout)
                    .ThenFetchMany(l => l.LayoutOptions)
                    .FetchMany(p => p.MasterPages)
                    .FetchMany(f => f.AccessRules)
                    .ToList()
                    .FirstOrDefault();

                isNew = pageProperties == null;
            }
            UpdatingPagePropertiesModel beforeChange = null;

            if (isNew)
            {
                pageProperties = new PageProperties
                {
                    Id          = request.Id.GetValueOrDefault(),
                    Status      = PageStatus.Unpublished,
                    AccessRules = new List <AccessRule>()
                };
            }
            else if (request.Data.Version > 0)
            {
                pageProperties.Version = request.Data.Version;
            }

            if (!isNew)
            {
                beforeChange = new UpdatingPagePropertiesModel(pageProperties);
            }

            if (!isNew && pageProperties.IsMasterPage != request.Data.IsMasterPage)
            {
                const string message    = "IsMasterPage cannot be changed for updating page. It can be modified only when creating a page.";
                var          logMessage = string.Format("{0} PageId: {1}", message, request.Id);
                throw new ValidationException(() => message, logMessage);
            }

            // Load master pages for updating page's master path and page's children master path
            IList <Guid>       newMasterIds;
            IList <Guid>       oldMasterIds;
            IList <Guid>       childrenPageIds;
            IList <MasterPage> existingChildrenMasterPages;

            masterPageService.PrepareForUpdateChildrenMasterPages(pageProperties, request.Data.MasterPageId, out newMasterIds, out oldMasterIds, out childrenPageIds, out existingChildrenMasterPages);

            unitOfWork.BeginTransaction();

            if (!string.IsNullOrEmpty(request.Data.PageUrl) || string.IsNullOrEmpty(pageProperties.PageUrl))
            {
                var pageUrl = request.Data.PageUrl;
                if (string.IsNullOrEmpty(pageUrl) && !string.IsNullOrWhiteSpace(request.Data.Title))
                {
                    pageUrl = pageService.CreatePagePermalink(request.Data.Title, null, null, request.Data.LanguageId, request.Data.CategoryId);
                }
                else
                {
                    pageUrl = urlService.FixUrl(pageUrl);
                    pageService.ValidatePageUrl(pageUrl, request.Id);
                }

                pageProperties.PageUrl     = pageUrl;
                pageProperties.PageUrlHash = pageUrl.UrlHash();
            }

            pageProperties.Title       = request.Data.Title;
            pageProperties.Description = request.Data.Description;

            var newStatus = request.Data.IsMasterPage || request.Data.IsPublished ? PageStatus.Published : PageStatus.Unpublished;

            if (!request.Data.IsMasterPage && pageProperties.Status != newStatus)
            {
                accessControlService.DemandAccess(securityService.GetCurrentPrincipal(), RootModuleConstants.UserRoles.PublishContent);
            }

            pageProperties.Status      = newStatus;
            pageProperties.PublishedOn = request.Data.IsPublished && !request.Data.PublishedOn.HasValue ? DateTime.Now : request.Data.PublishedOn;

            masterPageService.SetMasterOrLayout(pageProperties, request.Data.MasterPageId, request.Data.LayoutId);

            pageProperties.Category = request.Data.CategoryId.HasValue
                                    ? repository.AsProxy <Category>(request.Data.CategoryId.Value)
                                    : null;
            pageProperties.IsArchived              = request.Data.IsArchived;
            pageProperties.IsMasterPage            = request.Data.IsMasterPage;
            pageProperties.LanguageGroupIdentifier = request.Data.LanguageGroupIdentifier;
            pageProperties.Language = request.Data.LanguageId.HasValue && !request.Data.LanguageId.Value.HasDefaultValue()
                                    ? repository.AsProxy <Language>(request.Data.LanguageId.Value)
                                    : null;

            pageProperties.Image = request.Data.MainImageId.HasValue
                                    ? repository.AsProxy <MediaImage>(request.Data.MainImageId.Value)
                                    : null;
            pageProperties.FeaturedImage = request.Data.FeaturedImageId.HasValue
                                    ? repository.AsProxy <MediaImage>(request.Data.FeaturedImageId.Value)
                                    : null;
            pageProperties.SecondaryImage = request.Data.SecondaryImageId.HasValue
                                    ? repository.AsProxy <MediaImage>(request.Data.SecondaryImageId.Value)
                                    : null;

            pageProperties.CustomCss       = request.Data.CustomCss;
            pageProperties.CustomJS        = request.Data.CustomJavaScript;
            pageProperties.UseCanonicalUrl = request.Data.UseCanonicalUrl;
            pageProperties.UseNoFollow     = request.Data.UseNoFollow;
            pageProperties.UseNoIndex      = request.Data.UseNoIndex;

            if (request.Data.MetaData != null)
            {
                pageProperties.MetaTitle       = request.Data.MetaData.MetaTitle;
                pageProperties.MetaDescription = request.Data.MetaData.MetaDescription;
                pageProperties.MetaKeywords    = request.Data.MetaData.MetaKeywords;
            }

            IList <Tag> newTags = null;

            if (request.Data.Tags != null)
            {
                tagService.SavePageTags(pageProperties, request.Data.Tags, out newTags);
            }

            if (request.Data.AccessRules != null)
            {
                pageProperties.AccessRules.RemoveDuplicateEntities();
                var accessRules =
                    request.Data.AccessRules.Select(
                        r => (IAccessRule) new AccessRule {
                    AccessLevel = (Core.Security.AccessLevel)(int) r.AccessLevel, Identity = r.Identity, IsForRole = r.IsForRole
                })
                    .ToList();
                accessControlService.UpdateAccessControl(pageProperties, accessRules);
            }

            if (request.Data.PageOptions != null)
            {
                var options = request.Data.PageOptions.ToServiceModel();

                var pageOptions = pageProperties.Options != null?pageProperties.Options.Distinct() : null;

                pageProperties.Options = optionService.SaveOptionValues(options, pageOptions, () => new PageOption {
                    Page = pageProperties
                });
            }

            if (!isNew)
            {
                // Notify about page properties changing.
                var cancelEventArgs = Events.PageEvents.Instance.OnPagePropertiesChanging(beforeChange, new UpdatingPagePropertiesModel(pageProperties));
                if (cancelEventArgs.Cancel)
                {
                    throw new CmsApiValidationException(
                              cancelEventArgs.CancellationErrorMessages != null && cancelEventArgs.CancellationErrorMessages.Count > 0
                            ? string.Join(",", cancelEventArgs.CancellationErrorMessages)
                            : "Page properties saving was canceled.");
                }
            }

            repository.Save(pageProperties);

            //
            // If creating new page, page id is unknown when children pages are loaded, so Guid may be empty
            // Updating id to saved page's Id manually
            //
            if (isNew && childrenPageIds != null && childrenPageIds.Count == 1 && childrenPageIds[0].HasDefaultValue())
            {
                childrenPageIds[0] = pageProperties.Id;
            }

            masterPageService.UpdateChildrenMasterPages(existingChildrenMasterPages, oldMasterIds, newMasterIds, childrenPageIds);

            unitOfWork.Commit();

            // Fire events.
            Events.RootEvents.Instance.OnTagCreated(newTags);
            if (isNew)
            {
                Events.PageEvents.Instance.OnPageCreated(pageProperties);
            }
            else
            {
                Events.PageEvents.Instance.OnPagePropertiesChanged(pageProperties);
            }

            return(new PutPagePropertiesResponse {
                Data = pageProperties.Id
            });
        }
        /// <summary>
        /// Saves the blog post.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="childContentOptionValues">The child content option values.</param>
        /// <param name="principal">The principal.</param>
        /// <param name="errorMessages">The error messages.</param>
        /// <returns>
        /// Saved blog post entity
        /// </returns>
        /// <exception cref="System.ComponentModel.DataAnnotations.ValidationException"></exception>
        /// <exception cref="SecurityException">Forbidden: Access is denied.</exception>
        public BlogPost SaveBlogPost(BlogPostViewModel request, IList <ContentOptionValuesViewModel> childContentOptionValues, IPrincipal principal, out string[] errorMessages)
        {
            errorMessages = new string[0];
            string[] roles;
            if (request.DesirableStatus == ContentStatus.Published)
            {
                accessControlService.DemandAccess(principal, RootModuleConstants.UserRoles.PublishContent);
                roles = new[] { RootModuleConstants.UserRoles.PublishContent };
            }
            else
            {
                accessControlService.DemandAccess(principal, RootModuleConstants.UserRoles.EditContent);
                roles = new[] { RootModuleConstants.UserRoles.EditContent };
            }

            var isNew       = request.Id.HasDefaultValue();
            var userCanEdit = securityService.IsAuthorized(RootModuleConstants.UserRoles.EditContent);

            ValidateData(isNew, request);

            BlogPost        blogPost;
            BlogPostContent content;
            PageContent     pageContent;

            GetBlogPostAndContentEntities(request, principal, roles, ref isNew, out content, out pageContent, out blogPost);
            var beforeChange = new UpdatingBlogModel(blogPost);

            // Master page / layout
            Layout layout;
            Page   masterPage;
            Region region;

            LoadDefaultLayoutAndRegion(out layout, out masterPage, out region);

            if (masterPage != null)
            {
                var level = accessControlService.GetAccessLevel(masterPage, principal);
                if (level < AccessLevel.Read)
                {
                    var          message    = BlogGlobalization.SaveBlogPost_FailedToSave_InaccessibleMasterPage;
                    const string logMessage = "Failed to save blog post. Selected master page for page layout is inaccessible.";
                    throw new ValidationException(() => message, logMessage);
                }
            }

            if (pageContent.Region == null)
            {
                pageContent.Region = region;
            }

            // Load master pages for updating page's master path and page's children master path
            IList <Guid>       newMasterIds;
            IList <Guid>       oldMasterIds;
            IList <Guid>       childrenPageIds;
            IList <MasterPage> existingChildrenMasterPages;

            PrepareForUpdateChildrenMasterPages(isNew, blogPost, request, out newMasterIds, out oldMasterIds, out childrenPageIds, out existingChildrenMasterPages);

            // TODO: TEST AND TRY TO FIX IT: TRANSACTION HERE IS REQUIRED!
            // UnitOfWork.BeginTransaction(); // NOTE: this causes concurrent data exception.

            Redirect redirectCreated = null;

            if (!isNew && userCanEdit && !string.Equals(blogPost.PageUrl, request.BlogUrl) && !string.IsNullOrWhiteSpace(request.BlogUrl))
            {
                request.BlogUrl = urlService.FixUrl(request.BlogUrl);
                pageService.ValidatePageUrl(request.BlogUrl, request.Id);
                if (request.RedirectFromOldUrl)
                {
                    var redirect = redirectService.CreateRedirectEntity(blogPost.PageUrl, request.BlogUrl);
                    if (redirect != null)
                    {
                        repository.Save(redirect);
                        redirectCreated = redirect;
                    }
                }

                blogPost.PageUrl = urlService.FixUrl(request.BlogUrl);
            }

            // Push to change modified data each time.
            blogPost.ModifiedOn = DateTime.Now;

            if (userCanEdit)
            {
                blogPost.Title       = request.Title;
                blogPost.Description = request.IntroText;
                blogPost.Author      = request.AuthorId.HasValue ? repository.AsProxy <Author>(request.AuthorId.Value) : null;
                blogPost.Category    = request.CategoryId.HasValue ? repository.AsProxy <Category>(request.CategoryId.Value) : null;
                blogPost.Image       = (request.Image != null && request.Image.ImageId.HasValue) ? repository.AsProxy <MediaImage>(request.Image.ImageId.Value) : null;
                if (isNew || request.DesirableStatus == ContentStatus.Published)
                {
                    blogPost.ActivationDate = request.LiveFromDate;
                    blogPost.ExpirationDate = TimeHelper.FormatEndDate(request.LiveToDate);
                }
            }

            if (isNew)
            {
                if (!string.IsNullOrWhiteSpace(request.BlogUrl))
                {
                    blogPost.PageUrl = urlService.FixUrl(request.BlogUrl);
                    pageService.ValidatePageUrl(blogPost.PageUrl);
                }
                else
                {
                    blogPost.PageUrl = CreateBlogPermalink(request.Title, null, blogPost.Category != null ? (Guid?)blogPost.Category.Id : null);
                }

                blogPost.MetaTitle = request.MetaTitle ?? request.Title;
                if (masterPage != null)
                {
                    blogPost.MasterPage = masterPage;
                    masterPageService.SetPageMasterPages(blogPost, masterPage.Id);
                }
                else
                {
                    blogPost.Layout = layout;
                }
                UpdateStatus(blogPost, request.DesirableStatus);
                AddDefaultAccessRules(blogPost, principal, masterPage);
            }
            else if (request.DesirableStatus == ContentStatus.Published ||
                     blogPost.Status == PageStatus.Preview)
            {
                // Update only if publishing or current status is preview.
                // Else do not change, because it may change from published to draft status
                UpdateStatus(blogPost, request.DesirableStatus);
            }

            // Create content.
            var newContent = new BlogPostContent
            {
                Id               = content != null ? content.Id : Guid.Empty,
                Name             = request.Title,
                Html             = request.Content ?? string.Empty,
                EditInSourceMode = request.EditInSourceMode,
                ActivationDate   = request.LiveFromDate,
                ExpirationDate   = TimeHelper.FormatEndDate(request.LiveToDate)
            };

            // Preserve content if user is not authorized to change it.
            if (!userCanEdit)
            {
                if (content == null)
                {
                    throw new SecurityException("Forbidden: Access is denied."); // User has no rights to create new content.
                }

                var contentToPublish = (BlogPostContent)(content.History != null
                    ? content.History.FirstOrDefault(c => c.Status == ContentStatus.Draft) ?? content
                    : content);

                newContent.Name = contentToPublish.Name;
                newContent.Html = contentToPublish.Html;
            }

            content             = SaveContentWithStatusUpdate(isNew, newContent, request, principal);
            pageContent.Content = content;
            optionService.SaveChildContentOptions(content, childContentOptionValues, request.DesirableStatus);

            blogPost.PageUrlHash     = blogPost.PageUrl.UrlHash();
            blogPost.UseCanonicalUrl = request.UseCanonicalUrl;

            MapExtraProperties(isNew, blogPost, content, pageContent, request, principal);

            // Notify about page properties changing.
            var cancelEventArgs = Events.BlogEvents.Instance.OnBlogChanging(beforeChange, new UpdatingBlogModel(blogPost));

            if (cancelEventArgs.Cancel)
            {
                errorMessages = cancelEventArgs.CancellationErrorMessages.ToArray();
                return(null);
            }

            repository.Save(blogPost);
            repository.Save(content);
            repository.Save(pageContent);

            masterPageService.UpdateChildrenMasterPages(existingChildrenMasterPages, oldMasterIds, newMasterIds, childrenPageIds);

            pageContent.Content   = content;
            blogPost.PageContents = new [] { pageContent };



            IList <Tag> newTags = null;

            if (userCanEdit)
            {
                newTags = SaveTags(blogPost, request);
            }

            // Commit
            unitOfWork.Commit();

            // Notify about new created tags.
            Events.RootEvents.Instance.OnTagCreated(newTags);

            // Notify about new or updated blog post.
            if (isNew)
            {
                Events.BlogEvents.Instance.OnBlogCreated(blogPost);
            }
            else
            {
                Events.BlogEvents.Instance.OnBlogUpdated(blogPost);
            }

            // Notify about redirect creation.
            if (redirectCreated != null)
            {
                Events.PageEvents.Instance.OnRedirectCreated(redirectCreated);
            }

            return(blogPost);
        }