public override IList<ResourceRequiredContext> BuildRequiredResources(string stringResourceType)
        {
            // It's necessary to make a copy since making a change to the local variable also changes the private one.
            var resources = new List<ResourceRequiredContext>(base.BuildRequiredResources(stringResourceType));

            var settingsPart = _cacheManager.Get("Piedone.Combinator.CombinatorSettingsPart", ctx =>
            {
                _combinatorEventMonitor.MonitorConfigurationChanged(ctx);

                return _siteService.GetSiteSettings().As<CombinatorSettingsPart>();
            });

            if (resources.Count == 0
                || Orchard.UI.Admin.AdminFilter.IsApplied(_httpContextAccessor.Current().Request.RequestContext) && !settingsPart.EnableForAdmin) return resources;

            var resourceType = ResourceTypeHelper.StringTypeToEnum(stringResourceType);

            try
            {
                var settings = new CombinatorSettings
                {
                    CombineCDNResources = settingsPart.CombineCdnResources,
                    ResourceDomain = settingsPart.ResourceDomain,
                    EmbedCssImages = settingsPart.EmbedCssImages,
                    EmbeddedImagesMaxSizeKB = settingsPart.EmbeddedImagesMaxSizeKB,
                    GenerateImageSprites = settingsPart.GenerateImageSprites,
                    MinifyResources = settingsPart.MinifyResources
                };

                if (!String.IsNullOrEmpty(settingsPart.CombinationExcludeRegex)) settings.CombinationExcludeFilter = new Regex(settingsPart.CombinationExcludeRegex);
                if (!String.IsNullOrEmpty(settingsPart.EmbedCssImagesStylesheetExcludeRegex)) settings.EmbedCssImagesStylesheetExcludeFilter = new Regex(settingsPart.EmbedCssImagesStylesheetExcludeRegex);
                if (!String.IsNullOrEmpty(settingsPart.MinificationExcludeRegex)) settings.MinificationExcludeFilter = new Regex(settingsPart.MinificationExcludeRegex);

                if (!String.IsNullOrEmpty(settingsPart.ResourceSetRegexes))
                {
                    var setRegexes = new List<Regex>();
                    foreach (var regex in settingsPart.ResourceSetRegexes.Trim().Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        if (!String.IsNullOrEmpty(regex)) setRegexes.Add(new Regex(regex));
                    }
                    settings.ResourceSetFilters = setRegexes.ToArray();
                }

                DiscoverResourceOverrides(resources, resourceType);

                IList<ResourceRequiredContext> result;

                if (resourceType == ResourceType.Style) result = _combinatorService.CombineStylesheets(resources, settings);
                else if (resourceType == ResourceType.JavaScript) result = _combinatorService.CombineScripts(resources, settings);
                else return base.BuildRequiredResources(stringResourceType);

                RemoveOriginalResourceShapes(result, resourceType);

                return result;
            }
            catch (Exception ex)
            {
                if (ex.IsFatal()) throw;
                Logger.Error(ex, "Error when combining " + resourceType + " files");
                return base.BuildRequiredResources(stringResourceType);
            }
        }
        public void MinificationExclusionWorks()
        {
            _resourceRepository.FillWithTestStyles();

            var settings = new CombinatorSettings
            {
                MinificationExcludeFilter = new Regex("test\\.css"),
                MinifyResources = true
            };

            var resource = _resourceRepository.GetResource("~/Modules/Piedone.Combinator/Styles/test.css");
            _resourceProcessingService.ProcessResource(resource, new StringBuilder(), settings);
            Assert.That(resource.Content.StartsWith("minified:") && resource.Content.Contains("/Modules/Piedone.Combinator/Styles/test.css"), Is.False);

            resource = _resourceRepository.GetResource("~/Modules/Piedone.Combinator/Styles/test2.css");
            _resourceProcessingService.ProcessResource(resource, new StringBuilder(), settings);
            Assert.That(resource.Content.StartsWith("minified:") && resource.Content.Contains("/Modules/Piedone.Combinator/Styles/test2.css"), Is.True);
        }
Example #3
0
        /// <summary>
        /// Combines (and minifies) the content of resources and saves the combinations.
        /// </summary>
        /// <param name="resources">Resources to combine.</param>
        /// <param name="fingerprint">Just so it shouldn't be recalculated.</param>
        /// <param name="resourceType">Type of the resources.</param>
        /// <param name="settings">Combination setting.s</param>
        /// <exception cref="ApplicationException">Thrown if there was a problem with a resource file (e.g. it was missing or could not be opened).</exception>
        private void Combine(IList<ResourceRequiredContext> resources, string fingerprint, ResourceType resourceType, ICombinatorSettings settings)
        {
            if (resources.Count == 0) return;

            var combinatorResources = new List<CombinatorResource>(resources.Count);
            foreach (var resource in resources)
            {
                var combinatorResource = _combinatorResourceManager.ResourceFactory(resourceType);

                // Copying the context so the original one won't be touched.
                combinatorResource.FillRequiredContext(
                    resource.Resource.Name,
                    resource.Resource.GetFullPath(),
                    resource.Settings.Culture,
                    resource.Settings.Condition,
                    resource.Settings.Attributes,
                    resource.Resource.TagBuilder.Attributes);

                combinatorResource.IsRemoteStorageResource = settings.RemoteStorageUrlPattern != null && settings.RemoteStorageUrlPattern.IsMatch(combinatorResource.AbsoluteUrl.ToString());

                combinatorResources.Add(combinatorResource);
            }

            var combinedContent = new StringBuilder(1000);
            var resourceBaseUri = settings.ResourceBaseUri != null ? settings.ResourceBaseUri : new Uri(_hca.Current().Request.Url, "/");

            Action<CombinatorResource, List<CombinatorResource>> saveCombination =
                (combinedResource, containedResources) =>
                {
                    if (combinedResource == null) return;

                    // Don't save emtpy resources.
                    if (combinedContent.Length == 0 && !combinedResource.IsOriginal) return;

                    if (!containedResources.Any()) containedResources = new List<CombinatorResource> { combinedResource };

                    var bundleFingerprint = containedResources.GetCombinatorResourceListFingerprint(settings);

                    var localSettings = new CombinatorSettings(settings);
                    if (localSettings.EnableResourceSharing && settings.ResourceSharingExcludeFilter != null)
                    {
                        foreach (var resource in containedResources)
                        {
                            if (localSettings.EnableResourceSharing)
                            {
                                localSettings.EnableResourceSharing = !settings.ResourceSharingExcludeFilter.IsMatch(resource.AbsoluteUrl.ToString());
                            }
                        }
                    }

                    if (!combinedResource.IsOriginal)
                    {
                        combinedResource.Content = combinedContent.ToString();

                        if (combinedResource.Type == ResourceType.Style && !string.IsNullOrEmpty(combinedResource.Content) && settings.GenerateImageSprites)
                        {
                            _resourceProcessingService.ReplaceCssImagesWithSprite(combinedResource);
                        }

                        combinedResource.Content =
                            "/*" + Environment.NewLine
                            + "Resource bundle created by Combinator (http://combinator.codeplex.com/)" + Environment.NewLine + Environment.NewLine
                            + "Resources in this bundle:" + Environment.NewLine
                            + string.Join(Environment.NewLine, containedResources.Select(resource =>
                                {
                                    var url = resource.AbsoluteUrl.ToString();
                                    if (localSettings.EnableResourceSharing && !resource.IsCdnResource && !resource.IsRemoteStorageResource)
                                    {
                                        var uriBuilder = new UriBuilder(url);
                                        uriBuilder.Host = "DefaultTenant";
                                        url = uriBuilder.Uri.ToStringWithoutScheme();
                                    }
                                    return "- " + url;
                                }))
                            + Environment.NewLine + "*/"
                            + Environment.NewLine + Environment.NewLine + Environment.NewLine + combinedResource.Content;
                    }

                    // We save a bundle now. First the bundle should be saved separately under its unique name, then for this resource list.
                    if (bundleFingerprint != fingerprint && containedResources.Count > 1)
                    {
                        if (!_cacheFileService.Exists(bundleFingerprint, localSettings))
                        {
                            _cacheFileService.Save(bundleFingerprint, combinedResource, localSettings);
                        }

                        // Overriding the url for the resource in this resource list with the url of the set.
                        combinedResource.IsOriginal = true;

                        // The following should fetch one result theoretically but can more if the above Exists()-Save() happens
                        // in multiple requests at the same time.
                        var set = _cacheFileService.GetCombinedResources(bundleFingerprint, localSettings).First();

                        combinedResource.LastUpdatedUtc = set.LastUpdatedUtc;

                        if (IsOwnedResource(set))
                        {
                            if (settings.ResourceBaseUri != null && !set.IsRemoteStorageResource)
                            {
                                combinedResource.RequiredContext.Resource.SetUrl(UriHelper.Combine(resourceBaseUri.ToStringWithoutScheme(), set.AbsoluteUrl.PathAndQuery));
                            }
                            else if (set.IsRemoteStorageResource)
                            {
                                combinedResource.IsRemoteStorageResource = true;
                                combinedResource.RequiredContext.Resource.SetUrl(set.AbsoluteUrl.ToStringWithoutScheme());
                            }
                            else
                            {
                                combinedResource.RequiredContext.Resource.SetUrl(set.RelativeUrl.ToString());
                            }

                            AddTimestampToUrlIfNecessary(combinedResource);
                        }
                        else
                        {
                            combinedResource.RequiredContext.Resource.SetUrl(set.AbsoluteUrl.ToStringWithoutScheme());
                        }
                    }

                    _cacheFileService.Save(fingerprint, combinedResource, localSettings);

                    combinedContent.Clear();
                    containedResources.Clear();
                };

            Regex currentSetRegex = null;
            var resourcesInCombination = new List<CombinatorResource>();

            for (int i = 0; i < combinatorResources.Count; i++)
            {
                var resource = combinatorResources[i];
                var previousResource = (i != 0) ? combinatorResources[i - 1] : null;
                var absoluteUrlString = "";
                var saveOriginalResource = false;

                try
                {
                    absoluteUrlString = resource.AbsoluteUrl.ToString();

                    if (settings.CombinationExcludeFilter == null || !settings.CombinationExcludeFilter.IsMatch(absoluteUrlString))
                    {
                        // If this resource differs from the previous one in terms of settings or CDN they can't be combined
                        if (previousResource != null
                            && (!previousResource.SettingsEqual(resource) || (previousResource.IsCdnResource != resource.IsCdnResource && !settings.CombineCdnResources)))
                        {
                            saveCombination(previousResource, resourcesInCombination);
                            previousResource = null; // So it doesn't get combined again in if (saveOriginalResource) below.
                        }

                        // If this resource is in a different set than the previous, they can't be combined
                        if (currentSetRegex != null && !currentSetRegex.IsMatch(absoluteUrlString))
                        {
                            currentSetRegex = null;
                            saveCombination(previousResource, resourcesInCombination);
                        }

                        // Calculate if this resource is in a set
                        if (currentSetRegex == null && settings.ResourceSetFilters != null && settings.ResourceSetFilters.Length > 0)
                        {
                            int r = 0;
                            while (currentSetRegex == null && r < settings.ResourceSetFilters.Length)
                            {
                                if (settings.ResourceSetFilters[r].IsMatch(absoluteUrlString)) currentSetRegex = settings.ResourceSetFilters[r];
                                r++;
                            }

                            // The previous resource is in a different set or in no set so it can't be combined with this resource
                            if (currentSetRegex != null && previousResource != null && resourcesInCombination.Any())
                            {
                                saveCombination(previousResource, resourcesInCombination);
                            }
                        }

                        // Nesting resources in such blocks is needed because some syntax is valid if in its own file but not anymore
                        // when bundled.
                        if (resourceType == ResourceType.JavaScript) combinedContent.Append("{");
                        combinedContent.Append(Environment.NewLine);

                        _resourceProcessingService.ProcessResource(resource, combinedContent, settings);

                        // This can be because e.g. it's a CDN resource and CDN combination is disabled.
                        if (resource.IsOriginal) saveOriginalResource = true;

                        combinedContent.Append(Environment.NewLine);
                        if (resourceType == ResourceType.JavaScript) combinedContent.Append("}");
                        combinedContent.Append(Environment.NewLine);

                        resourcesInCombination.Add(resource);
                    }
                    else saveOriginalResource = true;

                    if (saveOriginalResource)
                    {
                        // This is a fully excluded resource
                        if (previousResource != null) saveCombination(previousResource, resourcesInCombination);
                        resource.IsOriginal = true;
                        saveCombination(resource, resourcesInCombination);
                        combinatorResources[i] = null; // So previous resource detection works correctly
                    }
                }
                catch (Exception ex)
                {
                    if (ex.IsFatal()) throw;
                    throw new OrchardException(T("Processing of resource {0} failed.", absoluteUrlString), ex);
                }
            }

            saveCombination(combinatorResources[combinatorResources.Count - 1], resourcesInCombination);
        }
        private void SaveTestResources()
        {
            var settings = new CombinatorSettings { ResourceBaseUri = new Uri("http://localhost") };

            var resource1 = _resourceRepository.NewResource(ResourceType.Style);
            resource1.Content = "test";
            _cacheFileService.Save(_cssResourcesFingerprint, resource1, settings);

            _cacheFileService.Save(_jsResourceFingerprint, _resourceRepository.NewResource(ResourceType.JavaScript), settings);
            _cacheFileService.Save(_jsResourceFingerprint, _resourceRepository.NewResource(ResourceType.JavaScript), settings);

            ClearSession();
        }
        public void SpriteGenerationRuns()
        {
            _resourceRepository.Clear();
            CombinatorResource resource;

            var type = ResourceType.Style;

            resource = _resourceRepository.SaveResource("~/Modules/Piedone.Combinator/Styles/1.css", type);

            var settings = new CombinatorSettings
            {
                GenerateImageSprites = true
            };

            var combinedResources = _combinatorService.CombineStylesheets(_resourceRepository.GetResources(type), settings);
            resource = _resourceRepository.GetResource(combinedResources.First());

            Assert.That(resource.Content.Contains("ImageSprite"), Is.True);
        }
        public void SetsAreSplit()
        {
            _resourceRepository.Clear();
            CombinatorResource resource;

            var type = ResourceType.Style;

            resource = _resourceRepository.SaveResource("~/Modules/Piedone.Combinator/Styles/1.css", type);
            resource = _resourceRepository.SaveResource("~/Modules/Piedone.Combinator/Styles/2.css", type);
            resource = _resourceRepository.SaveResource("~/Modules/Piedone.Combinator/Styles/3.css", type);
            resource = _resourceRepository.SaveResource("~/Modules/Piedone.Combinator/Styles/4.css", type);
            resource = _resourceRepository.SaveResource("~/Modules/Piedone.Combinator/Styles/5.css", type);
            resource = _resourceRepository.SaveResource("~/Modules/Piedone.Combinator/Styles/6.css", type);

            var settings = new CombinatorSettings
            {
                ResourceSetFilters = new Regex[] { new Regex("1"), new Regex("4|5") }
            };

            var combinedResources = _combinatorService.CombineStylesheets(_resourceRepository.GetResources(type), settings);

            Assert.That(combinedResources.Count, Is.EqualTo(4));
        }
        public override IList<ResourceRequiredContext> BuildRequiredResources(string stringResourceType)
        {
            // It's necessary to make a copy since making a change to the local variable also changes the private one.
            var resources = new List<ResourceRequiredContext>(base.BuildRequiredResources(stringResourceType));

            var settingsPart = _cacheManager.Get("Piedone.Combinator.CombinatorSettingsPart", ctx =>
            {
                _combinatorEventMonitor.MonitorConfigurationChanged(ctx);

                return _siteService.GetSiteSettings().As<CombinatorSettingsPart>();
            });

            if (resources.Count == 0 
                || Orchard.UI.Admin.AdminFilter.IsApplied(_httpContextAccessor.Current().Request.RequestContext) && !settingsPart.EnableForAdmin) return resources;

            var resourceType = ResourceTypeHelper.StringTypeToEnum(stringResourceType);

            try
            {
                var settings = new CombinatorSettings
                {
                    CombineCDNResources = settingsPart.CombineCDNResources,
                    EmbedCssImages = settingsPart.EmbedCssImages,
                    EmbeddedImagesMaxSizeKB = settingsPart.EmbeddedImagesMaxSizeKB,
                    MinifyResources = settingsPart.MinifyResources
                };

                if (!String.IsNullOrEmpty(settingsPart.CombinationExcludeRegex)) settings.CombinationExcludeFilter = new Regex(settingsPart.CombinationExcludeRegex);
                if (!String.IsNullOrEmpty(settingsPart.EmbedCssImagesStylesheetExcludeRegex)) settings.EmbedCssImagesStylesheetExcludeFilter = new Regex(settingsPart.EmbedCssImagesStylesheetExcludeRegex);
                if (!String.IsNullOrEmpty(settingsPart.MinificationExcludeRegex)) settings.MinificationExcludeFilter = new Regex(settingsPart.MinificationExcludeRegex);

                if (!String.IsNullOrEmpty(settingsPart.ResourceSetRegexes))
                {
                    var setRegexes = new List<Regex>();
                    foreach (var regex in settingsPart.ResourceSetRegexes.Trim().Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        if (!String.IsNullOrEmpty(regex)) setRegexes.Add(new Regex(regex));
                    }
                    settings.ResourceSetFilters = setRegexes.ToArray(); 
                }

                if (resourceType == ResourceType.Style)
                {
                    // Checking for overridden stylesheets
                    var currentTheme = _themeManager.GetRequestTheme(_httpContextAccessor.Current().Request.RequestContext);
                    var shapeTable = _shapeTableLocator.Lookup(currentTheme.Id);

                    foreach (var resource in resources)
                    {
                        var shapeName = StylesheetBindingStrategy.GetAlternateShapeNameFromFileName(resource.Resource.GetFullPath());

                        // Simply included CDN stylesheets are not in the ShapeTable, so we have to check
                        if (shapeTable.Bindings.ContainsKey("Style__" + shapeName))
                        {
                            var binding = shapeTable.Bindings["Style__" + shapeName].BindingSource;
                            resource.Resource.SetUrl(binding, null);
                        }
                    }

                    return _combinatorService.CombineStylesheets(resources, settings);
                }
                else if (resourceType == ResourceType.JavaScript)
                {
                    return _combinatorService.CombineScripts(resources, settings);
                }

                return base.BuildRequiredResources(stringResourceType);
            }
            catch (Exception ex)
            {
                if (ex.IsFatal()) throw;
                Logger.Error(ex, "Error when combining " + resourceType + " files");
                return base.BuildRequiredResources(stringResourceType);
            }
        }
        public override IList<ResourceRequiredContext> BuildRequiredResources(string stringResourceType)
        {
            // It's necessary to make a copy since making a change to the local variable also changes the private one.
            var resources = new List<ResourceRequiredContext>(base.BuildRequiredResources(stringResourceType));

            var settingsPart = _siteService.GetSiteSettings().As<CombinatorSettingsPart>();

            if (resources.Count == 0
                || Orchard.UI.Admin.AdminFilter.IsApplied(_httpContextAccessor.Current().Request.RequestContext) && !settingsPart.EnableForAdmin) return resources;

            var resourceType = ResourceTypeHelper.StringTypeToEnum(stringResourceType);

            try
            {
                Uri resourceBaseUri = null;
                if (!string.IsNullOrEmpty(settingsPart.ResourceBaseUrl)) resourceBaseUri = UriHelper.CreateUri(settingsPart.ResourceBaseUrl);

                var settings = new CombinatorSettings
                {
                    CombineCdnResources = settingsPart.CombineCdnResources,
                    ResourceBaseUri = resourceBaseUri,
                    EmbedCssImages = settingsPart.EmbedCssImages,
                    EmbeddedImagesMaxSizeKB = settingsPart.EmbeddedImagesMaxSizeKB,
                    GenerateImageSprites = settingsPart.GenerateImageSprites,
                    MinifyResources = settingsPart.MinifyResources,
                    EnableResourceSharing = settingsPart.EnableResourceSharing
                };

                if (!string.IsNullOrEmpty(settingsPart.CombinationExcludeRegex)) settings.CombinationExcludeFilter = new Regex(settingsPart.CombinationExcludeRegex);
                if (!string.IsNullOrEmpty(settingsPart.RemoteStorageUrlRegex)) settings.RemoteStorageUrlPattern = new Regex(settingsPart.RemoteStorageUrlRegex);
                if (!string.IsNullOrEmpty(settingsPart.EmbedCssImagesStylesheetExcludeRegex)) settings.EmbedCssImagesStylesheetExcludeFilter = new Regex(settingsPart.EmbedCssImagesStylesheetExcludeRegex);
                if (!string.IsNullOrEmpty(settingsPart.MinificationExcludeRegex)) settings.MinificationExcludeFilter = new Regex(settingsPart.MinificationExcludeRegex);
                if (!string.IsNullOrEmpty(settingsPart.ResourceSharingExcludeRegex)) settings.ResourceSharingExcludeFilter = new Regex(settingsPart.ResourceSharingExcludeRegex);

                if (!string.IsNullOrEmpty(settingsPart.ResourceSetRegexes))
                {
                    var setRegexes = new List<Regex>();
                    foreach (var regex in settingsPart.ResourceSetRegexesEnumerable)
                    {
                        if (!string.IsNullOrEmpty(regex)) setRegexes.Add(new Regex(regex));
                    }
                    settings.ResourceSetFilters = setRegexes.ToArray();
                }

                DiscoverResourceOverrides(resources, resourceType);

                IList<ResourceRequiredContext> result;

                if (resourceType == ResourceType.Style) result = _combinatorService.CombineStylesheets(resources, settings);
                else if (resourceType == ResourceType.JavaScript) result = _combinatorService.CombineScripts(resources, settings);
                else return base.BuildRequiredResources(stringResourceType);

                RemoveOriginalResourceShapes(result, resourceType);

                return result;
            }
            catch (Exception ex)
            {
                if (ex.IsFatal()) throw;
                Logger.Error(ex, "Error when combining " + resourceType + " files");
                return base.BuildRequiredResources(stringResourceType);
            }
        }