Example #1
0
        /// <summary>
        /// Returns relative URL for the specified tree node, using the Url Slug if the class is not in the excluded list.
        /// </summary>
        /// <param name="node">Tree node</param>
        /// <returns>The Relative Url</returns>
        protected override string GetUrlInternal(TreeNode node)
        {
            try
            {
                if (node == null)
                {
                    return(base.GetUrlInternal(node));
                }
                if (!DynamicRouteInternalHelper.UrlSlugExcludedClassNames().Contains(node.ClassName.ToLower()))
                {
                    var FoundSlug = CacheHelper.Cache(cs =>
                    {
                        if (cs.Cached)
                        {
                            cs.CacheDependency = CacheHelper.GetCacheDependency("DynamicRouting.UrlSlug|all");
                        }
                        return(UrlSlugInfoProvider.GetUrlSlugs()
                               .WhereEquals("UrlSlugNodeID", node.NodeID)
                               .WhereEquals("UrlSlugCultureCode", node.DocumentCulture)
                               .FirstOrDefault());
                    }, new CacheSettings(1440, "GetUrlSlugByNode", node.NodeID, node.DocumentCulture));

                    if (FoundSlug != null)
                    {
                        return(FoundSlug.UrlSlug);
                    }
                }
            } catch (Exception ex) {
                EventLogProvider.LogException("DynamicRouting", "DocumentUrlProvider_GetUrlInternal_Error", ex, additionalMessage: "for node " + (node == null || node.NodeGUID == null ? "null" : node.NodeGUID.ToString()));
            }
            return(base.GetUrlInternal(node));
        }
        /// <summary>
        /// Build Node (and upward), and build the children and update recursively only if changes detected.
        /// </summary>
        /// <param name="UrlSlugID"></param>
        public static void UrlSlugModified(int UrlSlugID)
        {
            // Convert UrlSlugID to NodeID
            int NodeID = UrlSlugInfoProvider.GetUrlSlugInfo(UrlSlugID).UrlSlugNodeID;

            DynamicRouteHelper.RebuildRoutesByNode(NodeID);
        }
Example #3
0
        /// <summary>
        /// Returns presentation URL for the specified node, using UrlSlug if the class is not in the excluded list. This is the absolute URL where live presentation of given node can be found.
        /// </summary>
        /// <param name="node">Tree node to return presentation URL for.</param>
        /// <param name="preferredDomainName">A preferred domain name that should be used as the host part of the URL. Preferred domain must be assigned to the site as a domain alias otherwise site main domain is used.</param>
        /// <returns></returns>
        protected override string GetPresentationUrlInternal(TreeNode node, string preferredDomainName = null)
        {
            try
            {
                if (node == null)
                {
                    return(base.GetPresentationUrlInternal(node, preferredDomainName));
                }

                if (!DynamicRouteInternalHelper.UrlSlugExcludedClassNames().Contains(node.ClassName.ToLower()))
                {
                    var FoundSlug = CacheHelper.Cache(cs =>
                    {
                        if (cs.Cached)
                        {
                            cs.CacheDependency = CacheHelper.GetCacheDependency("DynamicRouting.UrlSlug|all");
                        }
                        return(UrlSlugInfoProvider.GetUrlSlugs()
                               .WhereEquals("UrlSlugNodeID", node.NodeID)
                               .WhereEquals("UrlSlugCultureCode", node.DocumentCulture)
                               .FirstOrDefault());
                    }, new CacheSettings(1440, "GetUrlSlugByNode", node.NodeID, node.DocumentCulture));

                    if (FoundSlug != null)
                    {
                        SiteInfo site = node.Site;
                        string   url  = FoundSlug.UrlSlug;
                        if (!string.IsNullOrEmpty(site.SitePresentationURL))
                        {
                            return(URLHelper.CombinePath(url, '/', site.SitePresentationURL, null));
                        }
                        if (!string.IsNullOrEmpty(preferredDomainName))
                        {
                            return(URLHelper.GetAbsoluteUrl(url, preferredDomainName));
                        }
                        return(URLHelper.GetAbsoluteUrl(url, site.DomainName));
                    }
                }
            } catch (Exception ex)
            {
                EventLogProvider.LogException("DynamicRouting", "DocumentUrlProvider_GetPresentationUrlInternal_Error", ex, additionalMessage: "for node " + (node == null || node.NodeGUID == null ? "null" : node.NodeGUID.ToString()));
            }

            return(base.GetPresentationUrlInternal(node, preferredDomainName));
        }
Example #4
0
        private void StagingTask_ProcessTask_Before(object sender, StagingSynchronizationEventArgs e)
        {
            if (e.ObjectType.Equals(UrlSlugInfo.OBJECT_TYPE, StringComparison.InvariantCultureIgnoreCase))
            {
                // Get the URL slug itself
                UrlSlugInfo UrlSlug   = new UrlSlugInfo(e.TaskData.Tables[0].Rows[0]);
                int         NewNodeID = TranslateBindingTranslateID(UrlSlug.UrlSlugNodeID, e.TaskData, "cms.node");
                // Get the Site's current Url Slug
                UrlSlugInfo CurrentUrlSlug = UrlSlugInfoProvider.GetUrlSlugs()
                                             .WhereEquals("UrlSlugNodeID", NewNodeID)
                                             .WhereEquals("UrlSlugCultureCode", UrlSlug.UrlSlugCultureCode)
                                             .FirstOrDefault();

                bool UpdateCurrentUrlSlug = false;
                if (UrlSlug.UrlSlugIsCustom)
                {
                    if (!CurrentUrlSlug.UrlSlugIsCustom)
                    {
                        CurrentUrlSlug.UrlSlugIsCustom = true;
                        UpdateCurrentUrlSlug           = true;
                    }
                    if (CurrentUrlSlug.UrlSlug != UrlSlug.UrlSlug)
                    {
                        CurrentUrlSlug.UrlSlug = UrlSlug.UrlSlug;
                        UpdateCurrentUrlSlug   = true;
                    }
                }
                else
                {
                    if (CurrentUrlSlug.UrlSlugIsCustom)
                    {
                        CurrentUrlSlug.UrlSlugIsCustom = false;
                        UpdateCurrentUrlSlug           = true;
                    }
                }
                if (UpdateCurrentUrlSlug)
                {
                    UrlSlugInfoProvider.SetUrlSlugInfo(CurrentUrlSlug);
                }
                e.TaskHandled = true;
            }
        }
        public static object ParentUrl(EvaluationContext context, params object[] parameters)
        {
            // Based on the Macro Resolver which has the TreeNode Data, return the ParentUrl
            int    NodeID         = ValidationHelper.GetInteger(context.Resolver.ResolveMacros("{% NodeID %}"), 0);
            int    NodeParentID   = ValidationHelper.GetInteger(context.Resolver.ResolveMacros("{% NodeParentID %}"), 0);
            string Culture        = ValidationHelper.GetString(context.Resolver.ResolveMacros("{% DocumentCulture %}"), "en-US");
            string DefaultCulture = DynamicRouteInternalHelper.SiteContextSafe().DefaultVisitorCulture;

            return(CacheHelper.Cache(cs =>
            {
                UrlSlugInfo Slug = UrlSlugInfoProvider.GetUrlSlugs()
                                   .WhereEquals("UrlSlugNodeID", NodeParentID)
                                   .OrderBy($"case when UrlSlugCultureCode = '{Culture}' then 0 else 1 end, case when UrlSlugCultureCode = '{DefaultCulture}' then 0 else 1 end")
                                   .Columns("UrlSlug")
                                   .FirstOrDefault();
                if (cs.Cached)
                {
                    cs.CacheDependency = CacheHelper.GetCacheDependency("dynamicrouting.urlslug|all");
                }
                return Slug != null ? Slug.UrlSlug : "";
            }, new CacheSettings(1440, "GetUrlSlug", NodeParentID, Culture, DefaultCulture)));
        }
        /// <summary>
        /// Returns presentation URL for the specified node, using UrlSlug if the class is not in the excluded list. This is the absolute URL where live presentation of given node can be found.
        /// </summary>
        /// <param name="node">Tree node to return presentation URL for.</param>
        /// <param name="preferredDomainName">A preferred domain name that should be used as the host part of the URL. Preferred domain must be assigned to the site as a domain alias otherwise site main domain is used.</param>
        /// <returns></returns>
        protected override string GetPresentationUrlInternal(TreeNode node, string preferredDomainName = null)
        {
            if (!DynamicRouteInternalHelper.UrlSlugExcludedClassNames().Contains(node.ClassName.ToLower()))
            {
                if (node == null)
                {
                    return(null);
                }
                var FoundSlug = CacheHelper.Cache(cs =>
                {
                    if (cs.Cached)
                    {
                        cs.CacheDependency = CacheHelper.GetCacheDependency("DynamicRouting.UrlSlug|all");
                    }
                    return(UrlSlugInfoProvider.GetUrlSlugs()
                           .WhereEquals("UrlSlugNodeID", node.NodeID)
                           .WhereEquals("UrlSlugCultureCode", node.DocumentCulture)
                           .FirstOrDefault());
                }, new CacheSettings(1440, "GetUrlSlugByNode", node.NodeID, node.DocumentCulture));

                if (FoundSlug != null)
                {
                    SiteInfo site = node.Site;
                    string   url  = FoundSlug.UrlSlug;
                    if (!string.IsNullOrEmpty(site.SitePresentationURL))
                    {
                        return(URLHelper.CombinePath(url, '/', site.SitePresentationURL, null));
                    }
                    if (!string.IsNullOrEmpty(preferredDomainName))
                    {
                        return(URLHelper.GetAbsoluteUrl(url, preferredDomainName));
                    }
                    return(URLHelper.GetAbsoluteUrl(url, site.DomainName));
                }
            }

            return(base.GetPresentationUrlInternal(node, preferredDomainName));
        }
        /// <summary>
        /// Returns relative URL for the specified tree node, using the Url Slug if the class is not in the excluded list.
        /// </summary>
        /// <param name="node">Tree node</param>
        /// <returns>The Relative Url</returns>
        protected override string GetUrlInternal(TreeNode node)
        {
            if (!DynamicRouteInternalHelper.UrlSlugExcludedClassNames().Contains(node.ClassName.ToLower()))
            {
                var FoundSlug = CacheHelper.Cache(cs =>
                {
                    if (cs.Cached)
                    {
                        cs.CacheDependency = CacheHelper.GetCacheDependency("DynamicRouting.UrlSlug|all");
                    }
                    return(UrlSlugInfoProvider.GetUrlSlugs()
                           .WhereEquals("UrlSlugNodeID", node.NodeID)
                           .WhereEquals("UrlSlugCultureCode", node.DocumentCulture)
                           .FirstOrDefault());
                }, new CacheSettings(1440, "GetUrlSlugByNode", node.NodeID, node.DocumentCulture));

                if (FoundSlug != null)
                {
                    return(FoundSlug.UrlSlug);
                }
            }
            return(base.GetUrlInternal(node));
        }
        private void UrlSlug_Update_Before_301Redirect(object sender, ObjectEventArgs e)
        {
            UrlSlugInfo UrlSlug = (UrlSlugInfo)e.Object;

            #region "Create Alternative Url of Previous Url"
            try
            {
                // Alternative Urls don't have the slash at the beginning
                string OriginalUrlSlugNoTrim = ValidationHelper.GetString(UrlSlug.GetOriginalValue("UrlSlug"), UrlSlug.UrlSlug);
                string OriginalUrlSlug       = OriginalUrlSlugNoTrim.Trim('/');

                // save previous Url to 301 redirects
                // Get DocumentID
                var Document = DocumentHelper.GetDocuments()
                               .WhereEquals("NodeID", UrlSlug.UrlSlugNodeID)
                               .CombineWithDefaultCulture()
                               .CombineWithAnyCulture()
                               .Culture(UrlSlug.UrlSlugCultureCode)
                               .FirstOrDefault();
                var AlternativeUrl = AlternativeUrlInfoProvider.GetAlternativeUrls()
                                     .WhereEquals("AlternativeUrlUrl", OriginalUrlSlug)
                                     .FirstOrDefault();

                SiteInfo Site           = SiteInfoProvider.GetSiteInfo(Document.NodeSiteID);
                string   DefaultCulture = SettingsKeyInfoProvider.GetValue("CMSDefaultCultureCode", new SiteInfoIdentifier(Site.SiteName));

                UrlSlugInfo CultureSiblingUrlSlug = UrlSlugInfoProvider.GetUrlSlugs()
                                                    .WhereEquals("UrlSlug", OriginalUrlSlugNoTrim)
                                                    .WhereEquals("UrlSlugNodeID", UrlSlug.UrlSlugNodeID)
                                                    .WhereNotEquals("UrlSlugCultureCode", UrlSlug.UrlSlugCultureCode)
                                                    .FirstOrDefault();

                if (AlternativeUrl != null)
                {
                    if (AlternativeUrl.AlternativeUrlDocumentID != Document.DocumentID)
                    {
                        // If Same NodeID, then make sure the DocumentID is of the one that is the DefaultCulture, if no DefaultCulture
                        // Exists, then just ignore
                        var AlternativeUrlDocument = DocumentHelper.GetDocument(AlternativeUrl.AlternativeUrlDocumentID, new TreeProvider());

                        // Log a warning
                        if (AlternativeUrlDocument.NodeID != UrlSlug.UrlSlugNodeID)
                        {
                            EventLogProvider.LogEvent("W", "DynamicRouting", "AlternativeUrlConflict", eventDescription: string.Format("Conflict between Alternative Url '{0}' exists for Document {1} [{2}] which already exists as an Alternative Url for Document {3} [{4}].",
                                                                                                                                       AlternativeUrl.AlternativeUrlUrl,
                                                                                                                                       Document.NodeAliasPath,
                                                                                                                                       Document.DocumentCulture,
                                                                                                                                       AlternativeUrlDocument.NodeAliasPath,
                                                                                                                                       AlternativeUrlDocument.DocumentCulture
                                                                                                                                       ));
                        }
                        TreeNode DefaultLanguage = DocumentHelper.GetDocuments()
                                                   .WhereEquals("NodeID", UrlSlug.UrlSlugNodeID)
                                                   .Culture(DefaultCulture)
                                                   .CombineWithDefaultCulture()
                                                   .FirstOrDefault();

                        // Save only if there is no default language, or it is the default language, or if there is a default language adn it isn't it, that the Url doesn't match
                        // Any of the default languages urls, as this often happens when you clone from an existing language and then save a new url.
                        bool DefaultLanguageExists = DefaultLanguage != null;
                        bool IsNotDefaultLanguage  = DefaultLanguageExists && AlternativeUrl.AlternativeUrlDocumentID != DefaultLanguage.DocumentID;
                        bool MatchesDefaultLang    = false;
                        if (DefaultLanguageExists && IsNotDefaultLanguage)
                        {
                            // See if the OriginalUrlSlug matches the default document, or one of it's alternates
                            var DefaultLangUrlSlug = UrlSlugInfoProvider.GetUrlSlugs()
                                                     .WhereEquals("UrlSlugNodeID", UrlSlug.UrlSlugNodeID)
                                                     .WhereEquals("UrlSlugCultureCode", DefaultLanguage.DocumentCulture)
                                                     .WhereEquals("UrlSlug", "/" + OriginalUrlSlug)
                                                     .FirstOrDefault();
                            var DefaultLangAltUrl = AlternativeUrlInfoProvider.GetAlternativeUrls()
                                                    .WhereEquals("AlternativeUrlDocumentID", DefaultLanguage.DocumentID)
                                                    .WhereEquals("AlternativeUrlUrl", OriginalUrlSlug)
                                                    .FirstOrDefault();
                            MatchesDefaultLang = DefaultLangUrlSlug != null || DefaultLangAltUrl != null;
                        }

                        if (!DefaultLanguageExists || !IsNotDefaultLanguage || (DefaultLanguageExists && IsNotDefaultLanguage && !MatchesDefaultLang))
                        {
                            AlternativeUrl.AlternativeUrlDocumentID = DefaultLanguage.DocumentID;
                            AlternativeUrlInfoProvider.SetAlternativeUrlInfo(AlternativeUrl);
                        }
                    }
                }
                // Create new one if there are no other Url Slugs with the same pattern for that node
                else if (CultureSiblingUrlSlug == null)
                {
                    AlternativeUrl = new AlternativeUrlInfo()
                    {
                        AlternativeUrlDocumentID = Document.DocumentID,
                        AlternativeUrlSiteID     = Document.NodeSiteID,
                    };
                    AlternativeUrl.SetValue("AlternativeUrlUrl", OriginalUrlSlug);

                    // Save only if there is no default language, or it is the default language, or if there is a default language adn it isn't it, that the Url doesn't match
                    // Any of the default languages urls, as this often happens when you clone from an existing language and then save a new url.
                    TreeNode DefaultLanguage = DocumentHelper.GetDocuments()
                                               .WhereEquals("NodeID", UrlSlug.UrlSlugNodeID)
                                               .Culture(DefaultCulture)
                                               .FirstOrDefault();
                    bool DefaultLanguageExists = DefaultLanguage != null;
                    bool IsNotDefaultLanguage  = DefaultLanguageExists && AlternativeUrl.AlternativeUrlDocumentID != DefaultLanguage.DocumentID;
                    bool MatchesDefaultLang    = false;
                    if (DefaultLanguageExists && IsNotDefaultLanguage)
                    {
                        // See if the OriginalUrlSlug matches the default document, or one of it's alternates
                        var DefaultLangUrlSlug = UrlSlugInfoProvider.GetUrlSlugs()
                                                 .WhereEquals("UrlSlugNodeID", UrlSlug.UrlSlugNodeID)
                                                 .WhereEquals("UrlSlugCultureCode", DefaultLanguage.DocumentCulture)
                                                 .WhereEquals("UrlSlug", "/" + OriginalUrlSlug)
                                                 .FirstOrDefault();
                        var DefaultLangAltUrl = AlternativeUrlInfoProvider.GetAlternativeUrls()
                                                .WhereEquals("AlternativeUrlDocumentID", DefaultLanguage.DocumentID)
                                                .WhereEquals("AlternativeUrlUrl", OriginalUrlSlug)
                                                .FirstOrDefault();
                        MatchesDefaultLang = DefaultLangUrlSlug != null || DefaultLangAltUrl != null;
                    }
                    if (!DefaultLanguageExists || !IsNotDefaultLanguage || (DefaultLanguageExists && IsNotDefaultLanguage && !MatchesDefaultLang))
                    {
                        try
                        {
                            AlternativeUrlInfoProvider.SetAlternativeUrlInfo(AlternativeUrl);
                        }
                        catch (InvalidAlternativeUrlException ex)
                        {
                            // Figure out what to do, it doesn't match the pattern constraints.
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogErrorsInSeparateThread(ex, "DynamicRouting", "AlternateUrlError", $"Occurred on Url Slug Update for Url Slug {UrlSlug.UrlSlug} {UrlSlug.UrlSlugCultureCode}");
            }
            #endregion

            #region "Remove any Alternative Url of the new Url for this node"
            try
            {
                // Alternative Urls don't have the slash at the beginning
                string NewUrlSlug = UrlSlug.UrlSlug.Trim('/');

                // Check for any Alternative Urls for this node that match and remove
                // Get DocumentID
                var Document = DocumentHelper.GetDocuments()
                               .WhereEquals("NodeID", UrlSlug.UrlSlugNodeID)
                               .CombineWithDefaultCulture()
                               .CombineWithAnyCulture()
                               .Culture(UrlSlug.UrlSlugCultureCode)
                               .FirstOrDefault();
                var AllDocumentIDs = DocumentHelper.GetDocuments()
                                     .WhereEquals("NodeID", UrlSlug.UrlSlugNodeID)
                                     .AllCultures()
                                     .Columns("DocumentID")
                                     .Select(x => x.DocumentID).ToList();

                // Delete any Alternate Urls for any of the culture variations of this node that match the new Url Slug, this is to prevent infinite redirect loops
                AlternativeUrlInfoProvider.GetAlternativeUrls()
                .WhereEquals("AlternativeUrlUrl", NewUrlSlug)
                .WhereIn("AlternativeUrlDocumentID", AllDocumentIDs)
                .ForEachObject(x => x.Delete());

                SiteInfo Site           = SiteInfoProvider.GetSiteInfo(Document.NodeSiteID);
                string   DefaultCulture = SettingsKeyInfoProvider.GetValue("CMSDefaultCultureCode", new SiteInfoIdentifier(Site.SiteName));

                var AltUrlsOnOtherNodes = AlternativeUrlInfoProvider.GetAlternativeUrls()
                                          .WhereEquals("AlternativeUrlUrl", NewUrlSlug)
                                          .WhereNotIn("AlternativeUrlDocumentID", AllDocumentIDs)
                                          .ToList();

                // Add warning about conflict.
                if (AltUrlsOnOtherNodes.Count > 0)
                {
                    EventLogProvider.LogEvent("W", "DynamicRouting", "AlternateUrlConflict", $"Another page with an alternate Url matching {UrlSlug.UrlSlug} was found, please adjust and correct.");
                }
            }
            catch (Exception ex)
            {
                LogErrorsInSeparateThread(ex, "DynamicRouting", "AlternateUrlError", $"Occurred on Url Slug Update for Url Slug {UrlSlug.UrlSlug} {UrlSlug.UrlSlugCultureCode}");
            }

            #endregion
        }
Example #9
0
        private void UrlSlug_Update_Before_IsCustomRebuild(object sender, ObjectEventArgs e)
        {
            UrlSlugInfo UrlSlug = (UrlSlugInfo)e.Object;
            TreeNode    Node    = new DocumentQuery()
                                  .WhereEquals("NodeID", UrlSlug.UrlSlugNodeID)
                                  .Columns("NodeSiteID, NodeAliasPath, NodeGuid, NodeAlias")
                                  .FirstOrDefault();

            // First check if there is already a custom URL slug, if so cancel
            if (UrlSlug.UrlSlugIsCustom)
            {
                var ExistingMatchingSlug = UrlSlugInfoProvider.GetUrlSlugs()
                                           .WhereNotEquals("UrlSlugNodeID", UrlSlug.UrlSlugNodeID)
                                           .WhereEquals("UrlSlug", UrlSlug.UrlSlug)
                                           .Where($"UrlSlugNodeID in (Select NodeID from CMS_Tree where NodeSiteID = {Node.NodeSiteID})")
                                           .Columns("UrlSlugNodeID")
                                           .FirstOrDefault();
                if (ExistingMatchingSlug != null)
                {
                    TreeNode ConflictNode = new DocumentQuery()
                                            .WhereEquals("NodeID", ExistingMatchingSlug.UrlSlugNodeID)
                                            .Columns("NodeSiteID, NodeAliasPath")
                                            .FirstOrDefault();
                    var Error = new NotSupportedException($"This custom URL Slug '{UrlSlug.UrlSlug}' on {Node.NodeAliasPath} is already the pattern of an existing document ({ConflictNode.NodeAliasPath}).  Operation aborted.");
                    EventLogProvider.LogException("DynamicRouting", "CustomUrlSlugConflict", Error, Node.NodeSiteID);
                    throw Error;
                }
            }


            // This prevents the recursive url slug updates from the first update from removing the "Ignore" on this item.
            RecursionControl AlreadyDeterminedIfShouldIgnore = new RecursionControl("AlreadyDeterminedIfShouldIgnore_" + UrlSlug.UrlSlugGuid);

            if (CMSActionContext.CurrentLogSynchronization && AlreadyDeterminedIfShouldIgnore.Continue)
            {
                // Delete existing ignore records
                DynamicRouteInternalHelper.RemoveIgnoreStagingTaskOfUrlSlug(UrlSlug.UrlSlugID);
                // If the staging task Should be created
                if (
                    (UrlSlug.UrlSlugIsCustom && !ValidationHelper.GetBoolean(UrlSlug.GetOriginalValue("UrlSlugIsCustom"), true))
                    ||
                    (!UrlSlug.UrlSlugIsCustom && ValidationHelper.GetBoolean(UrlSlug.GetOriginalValue("UrlSlugIsCustom"), false))
                    ||
                    (UrlSlug.UrlSlugIsCustom && ValidationHelper.GetBoolean(UrlSlug.GetOriginalValue("UrlSlugIsCustom"), false) && UrlSlug.UrlSlug != ValidationHelper.GetString(UrlSlug.GetOriginalValue("UrlSlug"), UrlSlug.UrlSlug))
                    )
                {
                    // Staging task should be created, so proceed as normal
                }
                else
                {
                    // Staging task should not be created, add entry so this task won't be generated
                    UrlSlugStagingTaskIgnoreInfoProvider.SetUrlSlugStagingTaskIgnoreInfo(new UrlSlugStagingTaskIgnoreInfo()
                    {
                        UrlSlugStagingTaskIgnoreUrlSlugID = UrlSlug.UrlSlugID
                    });
                }
            }

            // If the Url Slug is custom or was custom, then need to rebuild after.
            if (UrlSlug.UrlSlugIsCustom || ValidationHelper.GetBoolean(UrlSlug.GetOriginalValue("UrlSlugIsCustom"), UrlSlug.UrlSlugIsCustom))
            {
                // Add hook so the Url Slug will be re-rendered after it's updated
                RecursionControl Trigger = new RecursionControl("UrlSlugNoLongerCustom_" + UrlSlug.UrlSlugGuid);
                var Triggered            = Trigger.Continue;
            }
        }