internal TabUrlOptions GetTabUrlOptions(int tabId)
        {
            TabUrlOptions result = null;

            if (_tabUrlOptions != null)
            {
                if (_tabUrlOptions.ContainsKey(tabId))
                {
                    result = _tabUrlOptions[tabId];
                }
                else
                if (tabId == -1 && _noDnnPagePathTabId > 0)
                {
                    if (_tabUrlOptions.ContainsKey(_noDnnPagePathTabId))
                    {
                        result = _tabUrlOptions[_noDnnPagePathTabId];
                    }
                    else
                    if (_tabUrlOptions.ContainsKey(-1))
                    {
                        result = _tabUrlOptions[-1];
                    }
                }
                else
                //929 : default option if no tab Id specified settings
                if (_tabUrlOptions.ContainsKey(-1))
                {
                    result = _tabUrlOptions[-1];
                }
            }
            return(result);
        }
        /// <summary>
        /// UpdateSettings is called when the 'update' button is clicked on the interface.
        /// This should take any values from the page, and set the individual properties on the
        /// instance of the module provider.
        /// </summary>
        /// <param name="provider"></param>
        public Dictionary <string, string> UpdateSettings()
        {
            //check type safety before cast
            if (_provider.ProviderType == Convert.ToString(typeof(NewsArticlesFriendlyUrlProvider)))
            {
                //take values from the page and set values on provider
                _providerSettings = _provider.Settings;

                //starting articleId
                int startingArticleId = -1;
                if (int.TryParse(txtStartingArticleId.Text, out startingArticleId))
                {
                    if (startingArticleId > -1)
                    {
                        _provider.Settings["StartingArticleId"] = startingArticleId.ToString();
                    }
                }
                _provider.Settings["RedirectUrls"] = chkRedirectOldUrls.Checked.ToString();

                //now get the individual settings from the provider, as they are now
                Hashtable articleUrlStyles  = TabUrlOptions.GetHashTableFromSetting(_providerSettings["articleUrlStyle"]);
                Hashtable articleUrlSource  = TabUrlOptions.GetHashTableFromSetting(_providerSettings["articleUrlSource"]);
                Hashtable pageUrlStyles     = TabUrlOptions.GetHashTableFromSetting(_providerSettings["pageUrlStyle"]);
                Hashtable authorUrlStyles   = TabUrlOptions.GetHashTableFromSetting(_providerSettings["authorUrlStyle"]);
                Hashtable categoryUrlStyles = TabUrlOptions.GetHashTableFromSetting(_providerSettings["categoryUrlStyle"]);

                //build a super-list of hashtables, where there is an entry for each setting
                Hashtable allHashtables = new Hashtable();
                allHashtables.Add("articleUrlStyle", articleUrlStyles);
                allHashtables.Add("articleUrlSource", articleUrlSource);
                allHashtables.Add("pageUrlStyle", pageUrlStyles);
                allHashtables.Add("authorUrlStyle", authorUrlStyles);
                allHashtables.Add("categoryUrlStyle", categoryUrlStyles);
                //in case there is any leftover settings from tabs that have been removed.
                RemoveInactiveTabIds(_provider.TabIds, _provider.AllTabs, allHashtables);
                //934 : reset the noDnnPagePath
                _provider.Settings["NoDnnPagePathTabId"] = Convert.ToString(-1); //will be set in 'TabFromScreen' if still set
                //get the valeus for each tab
                foreach (RepeaterItem item in rptTabs.Items)
                {
                    TabFromScreen(_provider, item, allHashtables);
                }

                //now feed back the hashtables back into the actual attributes for the provider
                SetProviderProperty(_provider, allHashtables, "ArticleUrlStyle", "articleUrlStyle");
                SetProviderProperty(_provider, allHashtables, "ArticleUrlSource", "articleUrlSource");
                SetProviderProperty(_provider, allHashtables, "PageUrlStyle", "pageUrlStyle");
                SetProviderProperty(_provider, allHashtables, "CategoryUrlStyle", "categoryUrlStyle");
                SetProviderProperty(_provider, allHashtables, "AuthorUrlStyle", "authorUrlStyle");
            }
            return(_providerSettings);
        }
        private void IsInitialized()
        {
            //look for an attribute specifying which tab the module
            //will not use a page path for.  There can only be one
            //tab specified per portal (because there is no page path, then it
            //can only be a single page, as the way of determining one dnn
            //page from another isn't in the reuqested Url)
            if (_initialized == false)
            {
                _eventLogController.AddLog("Message", "News Article Module Provider being intialised", PortalSettings.Current, -1, EventLogController.EventLogType.ADMIN_ALERT);

                var attributes = this.GetProviderPortalSettings();
                //_ignoreRedirectRegex = attributes["ignoreRedirectRegex"];
                //look for an attribute specifying which tab the module
                //will not use a page path for.  There can only be one
                //tab specified per portal (because there is no page path, then it
                //can only be a single page, as the way of determining one dnn
                //page from another isn't in the reuqested Url)
                string noDnnPagePathTabRaw = attributes["noDnnPagePathTabId"];
                int.TryParse(noDnnPagePathTabRaw, out _noDnnPagePathTabId);

                _urlPath = attributes["urlPath"];
                bool.TryParse(attributes["redirectUrls"], out _redirectUrls);
                string startingArticleIdRaw = attributes["startingArticleId"];
                if (!int.TryParse(startingArticleIdRaw, out _startingArticleId))
                {
                    _startingArticleId = 0;
                }

                //read in the different url styles for this instance
                _tabUrlOptions = new Dictionary <int, TabUrlOptions>();
                Hashtable articleUrlStyles  = TabUrlOptions.GetHashTableFromSetting(attributes["articleUrlStyle"], out _articleUrlStyle);
                Hashtable articleUrlSource  = TabUrlOptions.GetHashTableFromSetting(attributes["articleUrlSource"], out _articleUrlSource);
                Hashtable pageUrlStyles     = TabUrlOptions.GetHashTableFromSetting(attributes["pageUrlStyle"], out _pageUrlStyle);
                Hashtable authorUrlStyles   = TabUrlOptions.GetHashTableFromSetting(attributes["authorUrlStyle"], out _authorUrlStyle);
                Hashtable categoryUrlStyles = TabUrlOptions.GetHashTableFromSetting(attributes["categoryUrlStyle"], out _categoryUrlStyle);

                //if (this.AllTabs)
                _tabUrlOptions.Add(-1, new TabUrlOptions(-1, _startingArticleId, articleUrlStyles, articleUrlSource, pageUrlStyles, authorUrlStyles, categoryUrlStyles));
                //foreach (int tabId in this.TabIds)
                //{
                //    //create a tab option for each set tab
                //    TabUrlOptions urlOptions = new TabUrlOptions(tabId, _startingArticleId, articleUrlStyles, articleUrlSource, pageUrlStyles, authorUrlStyles, categoryUrlStyles);
                //    _tabUrlOptions.Add(tabId, urlOptions);
                //}
                _initialized = true;
            }
        }
        private void GetFragmentNames(TabUrlOptions urlOptions, out string articleFragmentName, out string pageFragmentName, out string authorFragmentName)
        {
            articleFragmentName = "UrlFragment1";
            pageFragmentName    = "UrlFragment1";
            authorFragmentName  = "UrlFragment1";
            if (urlOptions != null)
            {
                switch (urlOptions.ArticleSource)
                {
                case ArticleUrlSource.ShortUrl:
                    articleFragmentName = "UrlFragment3";
                    break;

                case ArticleUrlSource.MetaTitle:
                    articleFragmentName = "UrlFragment2";
                    break;

                default:
                    articleFragmentName = "UrlFragment1";
                    break;
                }
                switch (urlOptions.PageStyle)
                {
                case PageUrlStyle.TitleAndNum:
                    pageFragmentName = "UrlFragment2";
                    break;

                default:
                    pageFragmentName = "UrlFragment1";
                    break;
                }

                switch (urlOptions.AuthorStyle)
                {
                case AuthorUrlStyle.UserName:
                    authorFragmentName = "UrlFragment2";
                    break;

                default:
                    authorFragmentName = "UrlFragment1";
                    break;
                }
            }
        }
        /// <summary>
        /// Constructor for the NewsArticles Url Provider.  This is called by the Url Master module when loading the Provider
        /// </summary>
        /// <param name="name">Name is supplied from the web.config file, and specifies the unique name of the provider</param>
        /// <param name="attributes">Attributes are the xml attributes from the file</param>
        /// <param name="portalId">The portalId is supplied for the calling portal.  Each instance of the provider is portal specific.</param>
        //  public NewsArticlesFriendlyUrlProvider(string name, NameValueCollection attributes, int portalId): base(name, attributes, portalId)
        public NewsArticlesFriendlyUrlProvider() : base()
        {
            var attributes = this.GetProviderPortalSettings();

            //look for an attribute specifying which tab the module
            //will not use a page path for.  There can only be one
            //tab specified per portal (because there is no page path, then it
            //can only be a single page, as the way of determining one dnn
            //page from another isn't in the reuqested Url)
            string noDnnPagePathTabRaw = attributes["noDnnPagePathTabId"];

            int.TryParse(noDnnPagePathTabRaw, out _noDnnPagePathTabId);

            _urlPath = attributes["urlPath"];
            bool.TryParse(attributes["redirectUrls"], out _redirectUrls);
            string startingArticleIdRaw = attributes["startingArticleId"];

            if (!int.TryParse(startingArticleIdRaw, out _startingArticleId))
            {
                _startingArticleId = 0;
            }

            var prInfo = new NewsArticlesFriendlyUrlProviderInfo();

            //read in the different url styles for this instance
            _tabUrlOptions = new Dictionary <int, TabUrlOptions>();
            var articleUrlStyles  = TabUrlOptions.GetHashTableFromSetting(attributes["articleUrlStyle"], out _articleUrlStyle);
            var articleUrlSource  = TabUrlOptions.GetHashTableFromSetting(attributes["articleUrlSource"], out _articleUrlSource);
            var pageUrlStyles     = TabUrlOptions.GetHashTableFromSetting(attributes["pageUrlStyle"], out _pageUrlStyle);
            var authorUrlStyles   = TabUrlOptions.GetHashTableFromSetting(attributes["authorUrlStyle"], out _authorUrlStyle);
            var categoryUrlStyles = TabUrlOptions.GetHashTableFromSetting(attributes["categoryUrlStyle"], out _categoryUrlStyle);

            if (prInfo.AllTabs)
            {
                _tabUrlOptions.Add(-1, new TabUrlOptions(-1, _startingArticleId, articleUrlStyles, articleUrlSource, pageUrlStyles, authorUrlStyles, categoryUrlStyles));
            }
            foreach (int tabId in prInfo.TabIds)
            {
                //create a tab option for each set tab
                TabUrlOptions urlOptions = new TabUrlOptions(tabId, _startingArticleId, articleUrlStyles, articleUrlSource, pageUrlStyles, authorUrlStyles, categoryUrlStyles);
                _tabUrlOptions.Add(tabId, urlOptions);
            }
        }
        private void TabToScreen(RepeaterItem item)
        {
            TabController tc      = new TabController();
            int           tabId   = (int)item.DataItem;
            string        tabName = "";

            if (tabId > -1)
            {
                //TabInfo tab = tc.GetTab(tabId);
                TabInfo tab = tc.GetTab(tabId, PortalId);
                tabName = tab.TabName;
            }
            else
            {
                tabName = GetResourceString(this.LocalResourceFile, "AllTabsName.Text", "All Tabs");
            }
            Label lblPageName = (Label)item.FindControl("lblPageName");

            lblPageName.Text = tabName;

            //now get the individual settings
            Hashtable articleUrlStyles  = TabUrlOptions.GetHashTableFromSetting(_providerSettings["articleUrlStyle"]);
            Hashtable articleUrlSource  = TabUrlOptions.GetHashTableFromSetting(_providerSettings["articleUrlSource"]);
            Hashtable pageUrlStyles     = TabUrlOptions.GetHashTableFromSetting(_providerSettings["pageUrlStyle"]);
            Hashtable authorUrlStyles   = TabUrlOptions.GetHashTableFromSetting(_providerSettings["authorUrlStyle"]);
            Hashtable categoryUrlStyles = TabUrlOptions.GetHashTableFromSetting(_providerSettings["categoryUrlStyle"]);
            //build options from lists
            TabUrlOptions tabOptions = new TabUrlOptions(tabId, -1, articleUrlStyles, articleUrlSource, pageUrlStyles, authorUrlStyles, categoryUrlStyles);
            //now get controls
            HiddenField hdnTabId = (HiddenField)item.FindControl("hdnTabId");

            hdnTabId.Value = tabId.ToString();
            DropDownList ddlArticleUrlStyle  = (DropDownList)item.FindControl("ddlArticleUrlStyle");
            DropDownList ddlArticleUrlSource = (DropDownList)item.FindControl("ddlArticleUrlSource");
            DropDownList ddlCategoryUrlStyle = (DropDownList)item.FindControl("ddlCategoryUrlStyle");
            DropDownList ddlAuthorUrlStyle   = (DropDownList)item.FindControl("ddlAuthorUrlStyle");
            DropDownList ddlPageUrlStyle     = (DropDownList)item.FindControl("ddlPageUrlStyle");
            CheckBox     chkNoDnnPagePath    = (CheckBox)item.FindControl("chkNoDnnPagePath");

            BuildDropDownList(ddlArticleUrlStyle, tabOptions.ArticleStyle, typeof(ArticleUrlStyle));
            BuildDropDownList(ddlArticleUrlSource, tabOptions.ArticleSource, typeof(ArticleUrlSource));
            BuildDropDownList(ddlCategoryUrlStyle, tabOptions.CategoryStyle, typeof(CategoryUrlStyle));
            BuildDropDownList(ddlAuthorUrlStyle, tabOptions.AuthorStyle, typeof(AuthorUrlStyle));
            BuildDropDownList(ddlPageUrlStyle, tabOptions.PageStyle, typeof(PageUrlStyle));
            Label lblArticleUrlStyle  = (Label)item.FindControl("lblArticleUrlStyle");
            Label lblArticleUrlSource = (Label)item.FindControl("lblArticleUrlSource");
            Label lblCategoryUrlStyle = (Label)item.FindControl("lblCategoryUrlStyle");
            Label lblAuthorUrlStyle   = (Label)item.FindControl("lblAuthorUrlStyle");
            Label lblPageUrlStyle     = (Label)item.FindControl("lblPageUrlStyle");
            Label lblNoDnnPagePath    = (Label)item.FindControl("lblNoDnnPagePath");

            if (tabId > -1)
            {
                chkNoDnnPagePath.Checked = (_noDnnPagePathTabId == tabId);
                lblNoDnnPagePath.Text    = _noDnnPagePathText;
            }
            else
            {
                chkNoDnnPagePath.Visible = false;
                lblNoDnnPagePath.Text    = _needTabSpecifiedText;
            }
            lblArticleUrlStyle.Text  = _articleUrlStyleText;
            lblArticleUrlSource.Text = _articleUrlSourceText;
            lblCategoryUrlStyle.Text = _categoryUrlStyleText;
            lblAuthorUrlStyle.Text   = _authorUrlStyleText;
            lblPageUrlStyle.Text     = _pageUrlStyleText;
        }
        /// <summary>
        /// Determines when to do a redirect.  This is separate to the rewriting process.  The module developer can create any type of Url redirect here, because the entire Url of the original request is passed in.
        /// </summary>
        /// <param name="tabId">Identified TabId, if known.  -1 if no valid tabid identified.</param>
        /// <param name="portalid">Identified portalId.</param>
        /// <param name="httpAlias">Identified httpAlias of the request.</param>
        /// <param name="requestUri">The original requested Url</param>
        /// <param name="queryStringCol">The querystring collection of the original request</param>
        /// <param name="options">The friendly url options that currently apply.</param>
        /// <param name="redirectLocation">Out parameter that shows where to redirect to.</param>
        /// <param name="messages">List of messages for debug purposes.  Add to this list to help debug your module.</param>
        /// <returns>true if 301 redirect is required, false if not.  If true, the redirectLocation value must be a valid fully qualified Url.</returns>
        public override bool CheckForRedirect(int tabId, int portalid, string httpAlias, Uri requestUri, System.Collections.Specialized.NameValueCollection queryStringCol, FriendlyUrlOptions options, out string redirectLocation, ref List <string> messages)
        {
            bool doRedirect = false;

            if (messages == null)
            {
                messages = new List <string>();
            }
            redirectLocation = "";//set blank location
            //compare to known pattern of old Urls
            if (_redirectUrls)
            {
                Regex oldNewsRegex = new Regex(@"(&articleType=(?<type>[^&]+))?((&(?<idname>[a-z]*Id)=(?<id>\d+))|((&month=(?<mm>[\d]{1,2}))?&year=(?<yyyy>[\d]{4})))(&(?<pgname>PageId|CurrentPage)=(?<pg>[\d]+))?", RegexOptions.IgnoreCase);
                Match oldNewsMatch = oldNewsRegex.Match(queryStringCol.ToString());
                if (oldNewsMatch.Success)
                {
                    Group  typeGroup   = oldNewsMatch.Groups["type"];
                    Group  idNameGroup = oldNewsMatch.Groups["idname"];
                    Group  idGroup     = oldNewsMatch.Groups["id"];
                    Group  pageGroup   = oldNewsMatch.Groups["pg"];
                    Group  pgNameGrp   = oldNewsMatch.Groups["pgname"];
                    string msg         = "";
                    string id          = null;
                    string furlKey     = null;
                    string friendlyUrl = null;
                    if (idGroup != null && idGroup.Success)
                    {
                        id = idGroup.Value;
                    }
                    string idType = null;
                    if (typeGroup != null && typeGroup.Success)
                    {
                        idType = typeGroup.Value.ToLower();
                    }
                    else
                    {
                        if (idNameGroup != null && idNameGroup.Success)
                        {
                            //check if it's the 'ID' value
                            if (idNameGroup.Value.ToLower() == "id")
                            {
                                idType = "id";
                            }
                        }
                    }
                    //now look at the idType
                    string pagePath = null;
                    if (pgNameGrp != null && pgNameGrp.Success == true && pageGroup != null && pageGroup.Success)
                    {
                        pagePath = pgNameGrp.Value + "/" + pageGroup.Value;
                    }
                    switch (idType)
                    {
                    case "articleview":
                    case "id":
                        msg = "Identified as old-style news article";
                        //article
                        if (pageGroup != null && pageGroup.Success)
                        {
                            furlKey  = "p" + pageGroup.Value;
                            pagePath = null;     //taking care of page separately
                        }
                        else
                        {
                            int articleId = -1;
                            //only for items that are in the range of allowed article ids
                            if (int.TryParse(id, out articleId))
                            {
                                if (articleId >= this.StartingArticleId)
                                {
                                    furlKey = "a" + id;
                                }
                            }
                        }

                        break;

                    case "categoryview":
                        msg     = "Identified as old-style news category";
                        furlKey = "c" + id;
                        break;

                    case "archiveview":
                        //get the mm and yyyy
                        msg = "Identified as old-style news archive";
                        Group yyyyGrp = oldNewsMatch.Groups["yyyy"];
                        if (yyyyGrp != null && yyyyGrp.Success)
                        {
                            string yyyy  = yyyyGrp.Value;
                            string mm    = null;
                            Group  mmGrp = oldNewsMatch.Groups["mm"];
                            if (mmGrp != null && mmGrp.Success)
                            {
                                mm = mmGrp.Value;
                            }
                            friendlyUrl = yyyy;
                            if (mm != null)
                            {
                                friendlyUrl += "/" + mm;
                            }
                        }
                        break;

                    case "authorview":
                        msg     = "Identified as old-style news author";
                        furlKey = "u" + id;
                        break;
                    }
                    if (furlKey != null)
                    {
                        //now lookup the friendly url index
                        TabUrlOptions urlOptions       = GetTabUrlOptions(tabId);
                        Hashtable     friendlyUrlIndex = UrlController.GetFriendlyUrlIndex(tabId, portalid, this, options, urlOptions);
                        if (friendlyUrlIndex != null && friendlyUrlIndex.ContainsKey(furlKey))
                        {
                            //look up the index for the item if we don't already have a friendly Url
                            friendlyUrl = (string)friendlyUrlIndex[furlKey];
                        }
                    }
                    if (friendlyUrl != null)
                    {
                        //now merge with the friendly url for the selected page
                        PortalAliasInfo alias = PortalAliasController.GetPortalAliasInfo(httpAlias);
                        PortalSettings  ps    = new PortalSettings(tabId, alias);
                        if (pagePath != null)
                        {
                            friendlyUrl += this.EnsureLeadingChar("/", pagePath);
                        }
                        string baseUrl = "";
                        if (_noDnnPagePathTabId == tabId)
                        {
                            baseUrl = requestUri.Scheme + Uri.SchemeDelimiter + httpAlias + this.EnsureLeadingChar("/", friendlyUrl) + options.PageExtension;//put onto http Alias with no page path
                        }
                        else
                        {
                            baseUrl = DotNetNuke.Common.Globals.NavigateURL(tabId, ps, "", friendlyUrl); //add on with page path
                        }
                        if (baseUrl != null)
                        {
                            redirectLocation = baseUrl;
                            doRedirect       = true;
                            msg += ", found friendly url " + friendlyUrl + ", redirecting";
                            messages.Add(msg);
                        }
                    }
                }
            }
            return(doRedirect);
        }
        /// <summary>
        /// Return the index of all the querystrings that belong to friendly urls for the specific tab.
        /// </summary>
        /// <param name="tabId"></param>
        /// <param name="provider"></param>
        /// <returns></returns>
        internal static Hashtable GetQueryStringIndex(int tabId, int portalId, NewsArticlesModuleProvider provider, FriendlyUrlOptions options, TabUrlOptions urlOptions, bool forceRebuild)
        {
            string    qsCacheKey       = GetQueryStringIndexCacheKeyForTab(tabId);
            Hashtable queryStringIndex = DataCache.GetCache <Hashtable>(qsCacheKey);

            if (queryStringIndex == null || forceRebuild)
            {
                string    furlCacheKey     = GetFriendlyUrlIndexKeyForTab(tabId);
                Hashtable friendlyUrlIndex = null;
                //build index for tab
                BuildUrlIndexes(tabId, portalId, provider, options, urlOptions, out friendlyUrlIndex, out queryStringIndex);
                StoreIndexes(friendlyUrlIndex, furlCacheKey, queryStringIndex, qsCacheKey);
            }
            return(queryStringIndex);
        }
        /// <summary>
        /// The Change Friendly Url method is called for every Url generated when a page is generated by DotNetNuke.  This call sits 'underneath' the 'NavigateUrl' call in DotNetNuke.
        /// Whenever your module calls NavigateUrl, this method will be also called.  In here, the module developer should modify the friendlyUrlPath to the final state required.
        /// However, because this call is used for all Urls on the page, not just those generated by the target module, some type of high-level filter should be used to make sure only
        /// the module Urls are modified.
        ///
        /// </summary>
        /// <param name="tab">Current Tab</param>
        /// <param name="friendlyUrlPath">Current Friendly Url Path after going through the Friendly Url Generation process of the Url Master module.</param>
        /// <param name="options">The options currently applying to Urls in this portal (space replacement, max length, etc)</param>
        /// <param name="cultureCode">The culture code being used for this Url (if supplied, may be empty)</param>
        /// <param name="endingPageName">The page name the Url has been called with. Normally default.aspx, but may be different for some modules.</param>
        /// <param name="useDnnPagePath">Out parameter to be set by the module.  If true, the path of the DNN page will be in the Url (ie /pagename).  If false, this part of the Url will be removed. </param>
        /// <param name="messages">List of debug messages.  Add any debug information to this collection to help debug your provider.  This can be seen in the repsonse headers, and also in the 'test Url Rewrite' page in the Url Master module.</param>
        /// <returns></returns>
        public override string ChangeFriendlyUrl(DotNetNuke.Entities.Tabs.TabInfo tab, string friendlyUrlPath, FriendlyUrlOptions options, string cultureCode, ref string endingPageName, out bool useDnnPagePath, ref List <string> messages)
        {
            IsInitialized();

            _options = options;//keep local copy of options
            TabUrlOptions urlOptions = GetTabUrlOptions(tab.TabID);

            //set default values for out parameters
            useDnnPagePath = true;
            if (messages == null)
            {
                messages = new List <string>();
            }
            //check if we want to try and modify this Url
            //first check to see if this Url is an 'edit' Url - something that loads a module-specific page.
            //we don't want to mess with these, because they're always permissions based Urls and thus
            //no need to be friendly
            if (Regex.IsMatch(friendlyUrlPath, @"(^|/)(mid|moduleId)/\d+/?", RegexOptions.IgnoreCase) == false)
            {
                //try and match incoming friendly url path to what we would expect from the module
                //NOTE: regex used here but can be any type of logic to determine if this is a friendly url that applies to the module
                //There will be many different urls created for this tab, so we only want to change the ones we know apply to this module
                //by way of looking for a certain pattern or other unique marker in the Url.
                //For this example, match by looking for 'articleId' - normally the Url would be /pagename/tabid/xx/articleId/yy/default.aspx
                bool replacementFound = false;
                if (!replacementFound)
                {
                    //redundant if, but blocks together statements for consistency and localises variables
                    //matches category and page optionally, because these are sometime-addendums to the base article ID
                    Regex articleUrlRegex = new Regex(@"((?<l>/)?articleType/ArticleView/articleId/|/id/)(?<artid>\d+)(/PageId/(?<pageid>\d+))?(/categoryId/(?<catid>\d+))?", RegexOptions.IgnoreCase);
                    Match articleUrlMatch = articleUrlRegex.Match(friendlyUrlPath);

                    if (articleUrlMatch.Success)
                    {
                        string articleUrl = "";
                        replacementFound = UrlController.MakeArticleUrl(this, articleUrlMatch, articleUrlRegex, friendlyUrlPath, tab, options, urlOptions, cultureCode, ref endingPageName, ref useDnnPagePath, ref messages, out articleUrl);
                        if (replacementFound)
                        {
                            friendlyUrlPath = articleUrl;
                        }
                    }
                }

                if (!replacementFound)
                {
                    //no match on article  - next check is for a category match
                    Regex authorUrlRegex = new Regex(@"(?<l>/)?articleType/AuthorView/authorId/(?<authid>\d+)", RegexOptions.IgnoreCase);
                    Match authorUrlMatch = authorUrlRegex.Match(friendlyUrlPath);
                    if (authorUrlMatch.Success)
                    {
                        string authorUrl = "";
                        replacementFound = UrlController.MakeAuthorUrl(this, authorUrlMatch, authorUrlRegex, friendlyUrlPath, tab, options, urlOptions, cultureCode, ref endingPageName, ref useDnnPagePath, ref messages, out authorUrl);
                        if (replacementFound)
                        {
                            friendlyUrlPath = authorUrl;
                        }
                    }
                }
                if (!replacementFound)
                {
                    //no match on article  - next check is for a category match
                    Regex categoryUrlRegex = new Regex(@"(?<l>/)?articleType/CategoryView/categoryId/(?<catid>\d+)", RegexOptions.IgnoreCase);
                    Match categoryUrlMatch = categoryUrlRegex.Match(friendlyUrlPath);
                    if (categoryUrlMatch.Success)
                    {
                        string categoryUrl = "";
                        replacementFound = UrlController.MakeCategoryUrl(this, categoryUrlMatch, categoryUrlRegex, friendlyUrlPath, tab, options, urlOptions, cultureCode, ref endingPageName, ref useDnnPagePath, ref messages, out categoryUrl);
                        if (replacementFound)
                        {
                            friendlyUrlPath = categoryUrl;
                        }
                    }
                }
                if (!replacementFound)
                {
                    Regex archiveUrlRegex = new Regex(@"(?<l>/)?articleType/ArchiveView(?<mth>/month/(?<mm>\d+))?(?<yr>/year/(?<yyyy>\d+))?", RegexOptions.IgnoreCase);
                    Match archiveUrlMatch = archiveUrlRegex.Match(friendlyUrlPath);
                    if (archiveUrlMatch.Success)
                    {
                        string archiveUrl = "";
                        replacementFound = UrlController.MakeArchiveUrl(this, archiveUrlMatch, archiveUrlRegex, friendlyUrlPath, tab, options, urlOptions, cultureCode, ref endingPageName, ref useDnnPagePath, ref messages, out archiveUrl);
                        if (replacementFound)
                        {
                            friendlyUrlPath = archiveUrl;
                        }
                    }
                }
                if (replacementFound)
                {
                    friendlyUrlPath = base.EnsureLeadingChar("/", friendlyUrlPath);
                }
            }
            return(friendlyUrlPath);
        }
        /// <summary>
        /// This method is used by the Url Master Url Rewriting process.  The purpose of this method is to take the supplied array of Url parameters, and transform them into a module-specific querystring for the underlying re-written Url.
        /// </summary>
        /// <param name="urlParms">The array of parameters found after the DNN page path has been identified.  No key/valeu pairs are identified, the parameters are converted from the /key/value/key2/value2 format into [key,value,key2,value2] format.</param>
        /// <param name="tabId">TabId of identified DNN page. </param>
        /// <param name="portalId">PortalId of identified DNN portal.</param>
        /// <param name="options">The current Friendly Url options being used by the module.</param>
        /// <param name="cultureCode">Identified language/culture code, if supplied.</param>
        /// <param name="portalAlias">Identified portalAlias object for the request.</param>
        /// <param name="messages">List of debug messages.  Add to this list to help debug your module.  Can be viewed in the reponse headers of the request, or in the 'Test Url Rewriting' section of the Url Master module.</param>
        /// <param name="status">Out parameter, returns the Http status of the request.  May be 200,301,302, or 404.  For normal rewriting, return a 200 value.</param>
        /// <param name="location">If a 301 or 302 is returned in the status parameter, then this must contain a valid redirect location.  This should be a fully-qualified Url.</param>
        /// <returns>The querystring to be used for rewriting the Url. NOTE: doesn't need to include the tabid if the tabid parameter is > -1</returns>
        public override string TransformFriendlyUrlToQueryString(string[] urlParms, int tabId, int portalId, FriendlyUrlOptions options, string cultureCode, DotNetNuke.Entities.Portals.PortalAliasInfo portalAlias, ref List <string> messages, out int status, out string location)
        {
            string path = string.Join("/", urlParms);

            //initialise results and output variables
            location = null; //no redirect location
            if (messages == null)
            {
                messages = new List <string>();
            }
            string result = ""; status = 200; //OK

            //prevent incorrect matches of Urls
            if (!Regex.IsMatch(path, @"(articleType/(?<type>[^/]+))|(ctl/[^/]+/(mid|moduleid)/\d)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
            {
                //store local options variable
                _options = options;
                //get the tab options
                TabUrlOptions urlOptions       = GetTabUrlOptions(tabId);
                Hashtable     queryStringIndex = null;
                int           skipUpToIndex    = -1;
                bool          found            = false;
                bool          siteRootMatch    = false;
                bool          tabHasNAModule   = false;
                //foreach (ModuleInfo mi in ModuleController.Instance.GetTabModules(tabId).Values) {

                //}
                if ((from ModuleInfo mi in ModuleController.Instance.GetTabModules(tabId).Values where mi.DesktopModule.FolderName.ToLower().Contains("dnnforge - newsarticles") select mi.ModuleTitle).Count() != 0)
                {
                    tabHasNAModule = true;
                }

                //look for match on pattern for date and title - the pattern used by this provider
                //string path = string.Join("/", urlParms);
                //messages.Add("Checking for Items in Friendly Url path: " + path);
                if (urlParms.Length > 0)
                {
                    //tabid == -1 when no dnn page path is in the Url.  This means the Url Master module can't determine the DNN page based on the Url.
                    //In this case, it is up to this provider to identify the correct tabId that matches the Url.  Failure to do so will result in the incorrect tab being loaded when the page is rendered.
                    if (tabId == -1)
                    {
                        siteRootMatch = true;
                        if (_noDnnPagePathTabId > -1)
                        {
                            //tabid -1 means a 'site root' match - meaning that the dnn page path wasn't included in the Url
                            tabId = _noDnnPagePathTabId;//if tabid = -1, it means a site root match (no dnn page path) so we substitute in the tabid where this is being used
                        }
                    }
                    queryStringIndex = UrlController.GetQueryStringIndex(tabId, portalId, this, options, urlOptions, false);
                    string pathBasedKey = string.Join("/", urlParms).ToLower();
                    string qs           = null;
                    if (queryStringIndex.ContainsKey(pathBasedKey))
                    {
                        //that was easy-  direct match
                        qs            = (string)queryStringIndex[pathBasedKey];
                        skipUpToIndex = urlParms.GetUpperBound(0);
                    }
                    else
                    {
                        //go through the parameter list backwards until we find a match
                        for (int i = urlParms.GetUpperBound(0); i >= 0; i--)
                        {
                            //copy all the array minus the i index item
                            int      tempLength = i + 1;
                            string[] tempParms  = new string[tempLength];
                            Array.Copy(urlParms, 0, tempParms, 0, i + 1);
                            //make a new key from the shorter array
                            pathBasedKey = string.Join("/", tempParms).ToLower();
                            //check if that matches
                            if (queryStringIndex.ContainsKey(pathBasedKey))
                            {
                                qs = (string)queryStringIndex[pathBasedKey];
                                if (qs != null)
                                {
                                    //the trimmed pieces need to be included
                                    skipUpToIndex = i;
                                    break;
                                }
                            }
                        }
                    }
                    if (qs != null)
                    {
                        //found a querystring match
                        found = true;
                        messages.Add("Item Matched in Friendly Url Provider.  Url : " + pathBasedKey + " Path : " + path);
                        result += qs;
                    }
                    else
                    {
                        //no match, but look for a date archive pattern
                        //903 : issue with matching other Urls that aren't archive Urls
                        Regex archivePatternRegex = new Regex(@"(?<!year)(?<yr>(^|/)(?<yyyy>[\d]{4}))(?<mth>/(?<mm>[\d]{1,2}))?", RegexOptions.IgnoreCase);
                        Match archivePatternMatch = archivePatternRegex.Match(path);
                        if (archivePatternMatch.Success)
                        {
                            bool   month = false, year = false;
                            string mm = null, yyyy = null;
                            //matched on date pattern, extract month/year
                            Group mthGrp = archivePatternMatch.Groups["mth"];
                            if (mthGrp != null && mthGrp.Success)
                            {
                                mm    = archivePatternMatch.Groups["mm"].Value;
                                month = true;
                            }
                            Group yrGrp = archivePatternMatch.Groups["yyyy"];
                            if (yrGrp != null && yrGrp.Success)
                            {
                                //902 : don't allow invalid dates to be passed down
                                int yearVal = 0;
                                yyyy = yrGrp.Value;
                                //check that year is a valid int, and that year is later than sql min date time
                                if (int.TryParse(yyyy, out yearVal) && yearVal > 1753 && tabHasNAModule)
                                {
                                    year = true;
                                }
                            }
                            if (year)
                            {
                                qs = "";
                                if (this.NoDnnPagePathTabId == tabId)
                                {
                                    qs += "?tabid=" + tabId.ToString();
                                }
                                //add on the year
                                qs           += "&articleType=ArchiveView&year=" + yyyy;
                                skipUpToIndex = 0;//1st position
                            }
                            if (year && month)
                            {
                                int mmVal = 0;
                                if (int.TryParse(mm, out mmVal) && mmVal > 0 && mmVal < 13)
                                {
                                    qs           += "&month=" + mm;
                                    skipUpToIndex = 1;//2nd position
                                }
                            }
                            if (year || month)
                            {
                                result += qs;
                            }
                        }
                    }
                }

                if (skipUpToIndex >= 0)
                {
                    //put on any remainder of the path that wasn't to do with the friendly Url
                    //but only if there was *something* in the friendly url that we interpreted
                    string remainder = base.CreateQueryStringFromParameters(urlParms, skipUpToIndex);
                    //put it all together for the final rewrite string
                    result += remainder;
                }
            }
            return(result);
        }
 /// <summary>
 /// Returns list of Urls related to your module
 /// </summary>
 /// <param name="tabId">The tabid the module is on.</param>
 /// <param name="entries">out parameter returns a zero-n list of NewsArticles Urls</param>
 /// <remarks>Example only - this call can be anything you like.</remarks>
 internal abstract void GetNewsArticlesItemsForTab(int tabId, TabUrlOptions urlOptions, out FriendlyUrlInfoCol urls, out NewsArticleOptions naOptions);
 internal abstract FriendlyUrlInfo GetNewsArticleItem(int itemId, string itemType, TabUrlOptions urlOptions, int tabId);
        private static void BuildUrlIndexes(int tabId, int portalId, NewsArticlesModuleProvider provider, FriendlyUrlOptions options, TabUrlOptions urlOptions, out Hashtable friendlyUrlIndex, out Hashtable queryStringIndex)
        {
            if (urlOptions == null)
            {
                urlOptions = new TabUrlOptions();
                urlOptions.RedirectOtherStyles = true;
            }
            friendlyUrlIndex = new Hashtable();
            queryStringIndex = new Hashtable();
            //call database procedure to get list of
            FriendlyUrlInfoCol itemUrls  = null;
            NewsArticleOptions naOptions = null; //list of module settings for the NA module

            if (tabId > 0 && portalId > -1)      //927 : don't call db for tabid -1, it doesn't exist
            {
                Data.DataProvider.Instance().GetNewsArticlesItemsForTab(tabId, urlOptions, out itemUrls, out naOptions);
                options.PunctuationReplacement = naOptions.TitleReplacement;//override value with NA setting
                Dictionary <string, string> categoryParents = new Dictionary <string, string>();
                if (itemUrls != null)
                {
                    //build up the dictionary
                    foreach (FriendlyUrlInfo itemUrl in itemUrls)
                    {
                        string furlKey = itemUrl.FUrlKey;

                        //querystring index - look up by url, find querystring for the item
                        string furlValue = MakeItemFriendlyUrl(itemUrl, provider, options, urlOptions);
                        string qsKey     = furlValue.ToLower();//the querystring lookup is the friendly Url value - but converted to lower case

                        string qsValue  = null;
                        string itemId   = itemUrl.itemId.ToString();
                        string parentId = itemUrl.parentId.ToString();
                        switch (itemUrl.itemType.ToLower())
                        {
                        case "article":
                            qsValue = "&articleType=ArticleView&articleId=" + itemId;    //the querystring is just the entryId parameter
                            break;

                        case "page":
                            qsValue = "&articleType=ArticleView&pageId=" + itemId + "&articleId=" + parentId;
                            break;

                        case "author":
                            qsValue = "&articleType=AuthorView&authorId=" + itemId;
                            break;

                        case "category":
                            qsValue = "&articleType=CategoryView&categoryId=" + itemId;
                            if (parentId != "-1" && urlOptions.CategoryStyle == CategoryUrlStyle.CatHierarchy)
                            {
                                //this category has a parent
                                categoryParents.Add(furlKey, itemUrl.FUrlPrefix + parentId);
                            }
                            break;

                        case "archive":
                            if (parentId == "-1")
                            {
                                //yearly
                                qsValue = "&articleType=ArchiveView&year=" + itemId;
                            }
                            else
                            {
                                //monthly
                                qsValue = "&articleType=ArchiveView&year=" + parentId + "&month=" + itemUrl.urlNum.ToString();    //url num holds the month
                            }
                            break;
                        }


                        //when not including the dnn page path into the friendly Url, then include the tabid in the querystring
                        if (provider.AlwaysUsesDnnPagePath(portalId) == false)
                        {
                            qsValue = "?TabId=" + tabId.ToString() + qsValue;
                        }

                        string suffix = "";
                        AddUniqueUrlToIndex(furlKey, ref qsKey, qsValue, portalId, queryStringIndex, options, true, out suffix);

                        //if the suffix for the qsKey was changed, we need to add it to the friendly url used for the friendly url index
                        furlValue += suffix;

                        //friendly url index - look up by entryid, find Url
                        //check to see if friendly url matches any page paths
                        if (friendlyUrlIndex.ContainsKey(furlKey) == false)//shouldn't return duplicate because content is controlled by module logic
                        {
                            friendlyUrlIndex.Add(furlKey, furlValue);
                        }

                        //if the options aren't standard, also add in some other versions that will identify the right entry but will get redirected
                        if (options.PunctuationReplacement != "")
                        {
                            FriendlyUrlOptions altOptions = options.Clone();
                            altOptions.PunctuationReplacement = "";                                                       //how the urls look with no replacement
                            string altQsKey   = MakeItemFriendlyUrl(itemUrl, provider, altOptions, urlOptions).ToLower(); //keys are always lowercase
                            string altQsValue = qsValue + "&do301=true&&rr=Title_Space_Replacement";
                            AddUniqueUrlToIndex(furlKey, ref altQsKey, altQsValue, portalId, queryStringIndex, options, false, out suffix);
                        }
                        //now build the alternative for the redirects
                        if (urlOptions.RedirectOtherStyles)
                        {
                            TabUrlOptions tempOptions = urlOptions.Clone();
                            if (urlOptions.ArticleStyle == ArticleUrlStyle.BlogStyle)
                            {
                                tempOptions.ArticleStyle = ArticleUrlStyle.TitleStyle;
                            }
                            else
                            {
                                tempOptions.ArticleStyle = ArticleUrlStyle.BlogStyle;
                            }
                            //get the Url for this alternate style
                            string altArtQsKey   = MakeItemFriendlyUrl(itemUrl, provider, options, tempOptions).ToLower();
                            string altArtQsValue = qsValue += "&do301=true&rr=Wrong_Article_Style";
                            AddUniqueUrlToIndex(furlKey, ref altArtQsKey, altArtQsValue, portalId, queryStringIndex, options, false, out suffix);
                        }
                    }
                    //go back and recursively check for category parents to be updated
                    if (categoryParents != null && categoryParents.Count > 0)
                    {
                        Dictionary <string, string> updates = new Dictionary <string, string>();
                        //reallocate the friendly urls recursively so that categories include their parent path
                        foreach (string furlKey in categoryParents.Keys)
                        {
                            //got the key for the friendly url
                            //now find the parent
                            string parentKey  = categoryParents[furlKey];
                            string childPath  = (string)friendlyUrlIndex[furlKey];
                            string parentPath = GetParentPath(furlKey, parentKey, childPath, ref categoryParents, ref friendlyUrlIndex);
                            if (parentPath != null)
                            {
                                childPath = parentPath + "/" + childPath;
                            }
                            updates.Add(furlKey, childPath);//don't update until all done
                        }
                        //now process the update list and update any values that had hierarchial categories
                        foreach (string key in updates.Keys)
                        {
                            string oldVal = (string)friendlyUrlIndex[key];
                            string qsKey  = oldVal.ToLower();
                            if (queryStringIndex.ContainsKey(qsKey))
                            {
                                //update the querystring index
                                string qsVal = (string)queryStringIndex[qsKey];
                                queryStringIndex.Remove(qsKey);
                                queryStringIndex.Add(updates[key].ToLower(), qsVal);
                            }
                            //update the new friendly url index
                            friendlyUrlIndex[key] = updates[key];
                        }
                    }
                }
            }
        }
        internal override FriendlyUrlInfo GetNewsArticleItem(int itemId, string itemType, TabUrlOptions urlOptions, int tabId)
        {
            FriendlyUrlInfo fuf = null;
            //[dnn_ifty_nap_GetNewsArticlesEntry]
            string sp = GetFullyQualifiedName("GetNewsArticlesEntry");

            SqlParameter[] parms = new SqlParameter[3];
            parms[0] = new SqlParameter("@TabId", tabId);
            parms[1] = new SqlParameter("@startingArticleId", urlOptions.StartingArticleId);
            parms[2] = new SqlParameter("@itemId", itemId);
            SqlDataReader rdr = SqlHelper.ExecuteReader(_connectionString, CommandType.StoredProcedure, sp, parms);

            //work out which url fragment to use
            string articleFragmentName = "";
            string pageFragmentName    = "";
            string authorFragmentName  = "";

            GetFragmentNames(urlOptions, out articleFragmentName, out pageFragmentName, out authorFragmentName);

            if (rdr.Read())
            {
                BindReaderToFriendlyUrl(ref fuf, rdr, articleFragmentName, pageFragmentName, authorFragmentName);
            }
            rdr.Close();
            rdr.Dispose();
            return(fuf);
        }
        /// <summary>
        /// Checks for, and adds to the indexes, a missing item.
        /// </summary>
        /// <param name="itemId"></param>
        /// <param name="tabId"></param>
        /// <param name="portalId"></param>
        /// <param name="provider"></param>
        /// <param name="options"></param>
        /// <param name="messages"></param>
        /// <returns>Valid path if found</returns>
        internal static string CheckForMissingNewsArticleItem(int itemId, string itemType, int tabId, int portalId, NewsArticlesModuleProvider provider, FriendlyUrlOptions options, TabUrlOptions urlOptions, ref List <string> messages)
        {
            string          path        = null;
            FriendlyUrlInfo friendlyUrl = Data.DataProvider.Instance().GetNewsArticleItem(itemId, itemType, urlOptions, tabId);

            messages.Add("articleId not found : " + itemId.ToString() + " Checking Item directly");
            if (friendlyUrl != null)
            {
                messages.Add("articleId found : " + itemId.ToString() + " Rebuilding indexes");
                //call and get the path
                path = UrlController.MakeItemFriendlyUrl(friendlyUrl, provider, options, urlOptions);
                //so this entry did exist but wasn't in the index.  Rebuild the index
                UrlController.RebuildIndexes(tabId, portalId, provider, options, urlOptions);
            }
            return(path);
        }
        internal override void GetNewsArticlesItemsForTab(int tabId, TabUrlOptions urlOptions, out FriendlyUrlInfoCol urls, out NewsArticleOptions naOptions)
        {
            urls = new FriendlyUrlInfoCol();

            string sp = GetFullyQualifiedName("GetNewsArticlesEntriesForTab");

            SqlParameter[] parms = new SqlParameter[2];
            parms[0] = new SqlParameter("@TabId", tabId);
            if (urlOptions != null)
            {
                parms[1] = new SqlParameter("@startingArticleId", urlOptions.StartingArticleId);
            }
            else
            {
                parms[1] = new SqlParameter("@startingArticleId", 0);
            }
            //call the db
            SqlDataReader rdr = SqlHelper.ExecuteReader(_connectionString, CommandType.StoredProcedure, sp, parms);

            //work out which url fragment to use
            string articleFragmentName = "";
            string pageFragmentName    = "";
            string authorFragmentName  = "";

            GetFragmentNames(urlOptions, out articleFragmentName, out pageFragmentName, out authorFragmentName);

            while (rdr.Read())
            {
                FriendlyUrlInfo fuf = new FriendlyUrlInfo();
                BindReaderToFriendlyUrl(ref fuf, rdr, pageFragmentName, articleFragmentName, authorFragmentName);
                urls.Add(fuf);
            }
            //get any options in the mix
            naOptions = new NewsArticleOptions();
            if (rdr.NextResult())
            {
                /*AlwaysShowPageID	False
                 * SEOShorternID	ID
                 * SEOUrlMode	Shorterned
                 * TitleReplacementType	Dash*/
                //set defaults - module settings may not be there
                naOptions.TitleReplacement = "-";
                while (rdr.Read())
                {
                    string settingName  = (string)rdr["SettingName"];
                    string settingValue = (string)rdr["SettingValue"];
                    if (settingName != null)
                    {
                        switch (settingName.ToLower())
                        {
                        case "seoshorternid":
                            naOptions.SeoShortenId = settingValue;
                            break;

                        case "seourlmode":
                            naOptions.SeoUrlMode = settingValue;
                            break;

                        case "titlereplacementtype":
                            if (settingValue.ToLower() == "dash")
                            {
                                naOptions.TitleReplacement = "-";
                            }
                            else
                            {
                                naOptions.TitleReplacement = "_";
                            }
                            break;

                        case "alwaysshowpageid":
                            bool result = false;
                            bool.TryParse(settingValue, out result);
                            naOptions.AlwaysShowPageId = result;
                            break;
                        }
                    }
                    else
                    {
                        //no value, use defaults
                        naOptions.TitleReplacement = "-";
                        naOptions.AlwaysShowPageId = false;
                    }
                }
            }
            rdr.Close();
            rdr.Dispose();
        }
        internal static bool MakeArchiveUrl(NewsArticlesModuleProvider provider, Match archiveUrlMatch, Regex archiveUrlRegex, string friendlyUrlPath, DotNetNuke.Entities.Tabs.TabInfo tab, FriendlyUrlOptions options, TabUrlOptions urlOptions, string cultureCode, ref string endingPageName, ref bool useDnnPagePath, ref List <string> messages, out string archiveUrl)
        {
            archiveUrl = friendlyUrlPath;
            bool   result = false;
            Group  mthGrp = archiveUrlMatch.Groups["mth"];
            Group  yrGrp = archiveUrlMatch.Groups["yr"];
            bool   month = false, year = false;
            string mm = null, yyyy = null;
            string path = null;

            if (mthGrp != null && mthGrp.Success)
            {
                //contains a month
                month = true;
                mm    = archiveUrlMatch.Groups["mm"].Value;
            }
            if (yrGrp != null && yrGrp.Success)
            {
                year = true;
                yyyy = archiveUrlMatch.Groups["yyyy"].Value;
            }
            if (year)
            {
                path = "/" + yyyy;
            }
            if (month)
            {
                path += "/" + mm;
            }

            if (path != null) //got a valid path
            {
                //have a valid url replacement for this url.  So replace the matched part of the path with the friendly url
                if (archiveUrlMatch.Groups["l"].Success) //if the path had a leading /, then make sure to add that onto the replacement
                {
                    path = provider.EnsureLeadingChar("/", path);
                }

                /* finish it all off */
                messages.Add("Item Friendly Url Replacing Archive Url : " + friendlyUrlPath + " with Path : " + path);

                //this is the point where the Url is modified!
                //replace the path in the path - which leaves any other parts of a path intact.
                archiveUrl = archiveUrlRegex.Replace(friendlyUrlPath, path);//replace the part in the friendly Url path with it's replacement.

                //check if this tab is the one specified to not use a path
                if (provider.NoDnnPagePathTabId == tab.TabID)
                {
                    useDnnPagePath = false;//make this Url relative from the site root
                }
                //set back to default.aspx so that Url Master removes it - just in case it wasn't standard
                endingPageName = DotNetNuke.Common.Globals.glbDefaultPage;
                //return success
                result = true;
            }
            return(result);
        }
        internal static bool MakeCategoryUrl(NewsArticlesModuleProvider provider, Match categoryUrlMatch, Regex categoryUrlRegex, string friendlyUrlPath, DotNetNuke.Entities.Tabs.TabInfo tab, FriendlyUrlOptions options, TabUrlOptions urlOptions, string cultureCode, ref string endingPageName, ref bool useDnnPagePath, ref List <string> messages, out string categoryUrl)
        {
            bool result = false;

            categoryUrl = null;
            //this is a url that looks like an category url.  We want to modify it and create the new one.
            string rawId      = categoryUrlMatch.Groups["catid"].Value;
            int    categoryId = 0;

            if (int.TryParse(rawId, out categoryId))
            {
                Hashtable friendlyUrlIndex = null; //the friendly url index is the lookup we use
                //we have obtained the item Id out of the Url
                //get the friendlyUrlIndex (it comes from the database via the cache)
                friendlyUrlIndex = UrlController.GetFriendlyUrlIndex(tab.TabID, tab.PortalID, provider, options, urlOptions);
                if (friendlyUrlIndex != null)
                {
                    //item urls are indexed with a + category id ("c5") - this is so authors/articles/categories can be mixed and matched
                    string furlkey = "c" + categoryId.ToString();       //create the lookup key for the friendly url index
                    string path    = (string)friendlyUrlIndex[furlkey]; //check if in the index
                    if (path == null)
                    {
                        //don't normally expect to have a no-match with a friendly url path when an categoryId was in the Url.
                        //could be a new item that has been created and isn't in the index
                        //do a direct call and find out if it's there
                        //path = UrlController.CheckForMissingNewscategoryItem(categoryId, "category", tab.TabID, tab.PortalID, provider, options, urlOptions, ref messages);
                    }
                    if (path != null) //got a valid path
                    {
                        //url found in the index for this entry.  So replace the matched part of the path with the friendly url
                        if (categoryUrlMatch.Groups["l"].Success) //if the path had a leading /, then make sure to add that onto the replacement
                        {
                            path = provider.EnsureLeadingChar("/", path);
                        }

                        /* finish it all off */
                        messages.Add("Item Friendly Url Replacing : " + friendlyUrlPath + " in Path : " + path);

                        //this is the point where the Url is modified!
                        //replace the path in the path - which leaves any other parts of a path intact.
                        categoryUrl = categoryUrlRegex.Replace(friendlyUrlPath, path);//replace the part in the friendly Url path with it's replacement.

                        //check if this tab is the one specified to not use a path
                        if (provider.NoDnnPagePathTabId == tab.TabID)
                        {
                            useDnnPagePath = false;//make this Url relative from the site root
                        }
                        //set back to default.aspx so that Url Master removes it - just in case it wasn't standard
                        endingPageName = DotNetNuke.Common.Globals.glbDefaultPage;

                        result = true;
                    }
                }
            }
            return(result);
        }
        /// <summary>
        /// Creates a Friendly Url For the Item
        /// </summary>
        /// <param name="friendlyUrl">Object containing the relevant properties to create a friendly url from</param>
        /// <param name="provider">The active module provider</param>
        /// <param name="options">THe current friendly Url Options</param>
        /// <returns></returns>
        private static string MakeItemFriendlyUrl(FriendlyUrlInfo friendlyUrl, NewsArticlesModuleProvider provider, FriendlyUrlOptions options, TabUrlOptions urlOptions)
        {
            //calls back up the module provider to utilise the CleanNameForUrl method, which creates a safe Url using the current Url Master options.
            string friendlyUrlPath = provider.CleanNameForUrl(friendlyUrl.urlFragment, options);

            switch (friendlyUrl.itemType.ToLower())
            {
            case "article":
            case "page":
                switch (urlOptions.ArticleStyle)
                {
                case ArticleUrlStyle.BlogStyle:
                    friendlyUrlPath = MakeBlogStyleUrl(friendlyUrl.urlDate, friendlyUrlPath, friendlyUrl.urlNum, DateUrlStyle.Day);
                    break;

                case ArticleUrlStyle.TitleStyle:
                    if (friendlyUrl.urlNum > 1)        //page number is whole index
                    {
                        friendlyUrlPath += "/p/" + friendlyUrl.urlNum.ToString();
                    }
                    break;
                }

                /*if (friendlyUrl.urlNum > 1)//page number is whole index, so add on page
                 *  friendlyUrlPath += "/p/" + friendlyUrl.urlNum.ToString();*/
                break;

            case "author":
                //no difference - authorStyle chosen at index build stage
                break;

            case "category":
                //no difference - category style chosen at index build stage
                break;

            case "archive":
                //holds dates in format of yyyy/mm, which is how it is returned from db
                break;
            }
            return(friendlyUrlPath);
        }
        /// <summary>
        /// Creates a friendly article url, depending on the options
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="articleUrlMatch"></param>
        /// <param name="articleUrlRegex"></param>
        /// <param name="friendlyUrlPath"></param>
        /// <param name="tab"></param>
        /// <param name="options"></param>
        /// <param name="urlOptions"></param>
        /// <param name="cultureCode"></param>
        /// <param name="endingPageName"></param>
        /// <param name="useDnnPagePath"></param>
        /// <param name="messages"></param>
        /// <param name="articleUrl"></param>
        /// <returns></returns>
        internal static bool MakeArticleUrl(NewsArticlesModuleProvider provider, Match articleUrlMatch, Regex articleUrlRegex, string friendlyUrlPath, DotNetNuke.Entities.Tabs.TabInfo tab, FriendlyUrlOptions options, TabUrlOptions urlOptions, string cultureCode, ref string endingPageName, ref bool useDnnPagePath, ref List <string> messages, out string articleUrl)
        {
            bool result = false;

            articleUrl = null;
            //this is a url that looks like an article url.  We want to modify it and create the new one.
            string rawId     = articleUrlMatch.Groups["artid"].Value;
            int    articleId = 0;

            if (int.TryParse(rawId, out articleId) && (provider.StartingArticleId <= articleId))
            {
                Hashtable friendlyUrlIndex = null; //the friendly url index is the lookup we use
                //we have obtained the item Id out of the Url
                //get the friendlyUrlIndex (it comes from the database via the cache)
                friendlyUrlIndex = UrlController.GetFriendlyUrlIndex(tab.TabID, tab.PortalID, provider, options, urlOptions);
                if (friendlyUrlIndex != null)
                {
                    string furlkey = null; int pageId = -1;
                    //first check if we are seeking page or article
                    if (articleUrlMatch.Groups["pageid"].Success)
                    {
                        //page item urls are index by p + page id.  But we only use this if it is present
                        //ie pages override articles when both are present
                        string rawPageId = articleUrlMatch.Groups["pageid"].Value;
                        if (int.TryParse(rawPageId, out pageId))
                        {
                            furlkey = "p" + rawPageId;
                        }
                    }
                    else
                    {
                        //item urls are indexed with a + articleId ("a5") - this is so we could mix and match entities if necessary
                        furlkey = "a" + articleId.ToString();        //create the lookup key for the friendly url index
                    }
                    string path = (string)friendlyUrlIndex[furlkey]; //check if in the index
                    if (path == null)
                    {
                        //don't normally expect to have a no-match with a friendly url path when an articleId was in the Url.
                        //could be that the page id is bunk - in that case, just use the article Id
                        if (furlkey.Contains("p"))
                        {
                            furlkey = "a" + articleId.ToString();        //create the lookup key for the friendly url index
                            path    = (string)friendlyUrlIndex[furlkey]; //check if in the index
                        }
                        if (path == null)
                        {
                            //could be a new item that has been created and isn't in the index
                            //do a direct call and find out if it's there
                            path = UrlController.CheckForMissingNewsArticleItem(articleId, "article", tab.TabID, tab.PortalID, provider, options, urlOptions, ref messages);
                        }
                    }
                    if (path != null) //got a valid path
                    {
                        //url found in the index for this entry.  So replace the matched part of the path with the friendly url
                        if (articleUrlMatch.Groups["l"].Success) //if the path had a leading /, then make sure to add that onto the replacement
                        {
                            path = provider.EnsureLeadingChar("/", path);
                        }

                        /* finish it all off */
                        messages.Add("Item Friendly Url Replacing : " + friendlyUrlPath + " in Path : " + path);

                        //this is the point where the Url is modified!
                        //replace the path in the path - which leaves any other parts of a path intact.
                        articleUrl = articleUrlRegex.Replace(friendlyUrlPath, path);//replace the part in the friendly Url path with it's replacement.

                        //check if this tab is the one specified to not use a path
                        if (provider.NoDnnPagePathTabId == tab.TabID)
                        {
                            useDnnPagePath = false;//make this Url relative from the site root
                        }
                        //set back to default.aspx so that Url Master removes it - just in case it wasn't standard
                        endingPageName = DotNetNuke.Common.Globals.glbDefaultPage;

                        result = true;
                    }
                }
            }
            return(result);
        }
        /// <summary>
        /// REbuilds the two indexes and re-stores them into the cache
        /// </summary>
        /// <param name="tabId"></param>
        /// <param name="portalId"></param>
        /// <param name="provider"></param>
        /// <param name="options"></param>
        private static void RebuildIndexes(int tabId, int portalId, NewsArticlesModuleProvider provider, FriendlyUrlOptions options, TabUrlOptions urlOptions)
        {
            Hashtable queryStringIndex = null;
            Hashtable friendlyUrlIndex = null;
            string    qsCacheKey       = GetQueryStringIndexCacheKeyForTab(tabId);
            string    furlCacheKey     = GetFriendlyUrlIndexKeyForTab(tabId);

            //build index for tab
            BuildUrlIndexes(tabId, portalId, provider, options, urlOptions, out friendlyUrlIndex, out queryStringIndex);
            StoreIndexes(friendlyUrlIndex, furlCacheKey, queryStringIndex, qsCacheKey);
        }