1 /** 2 * This file Copyright (c) 2016-2018 Magnolia International 3 * Ltd. (http://www.magnolia-cms.com). All rights reserved. 4 * 5 * 6 * This file is dual-licensed under both the Magnolia 7 * Network Agreement and the GNU General Public License. 8 * You may elect to use one or the other of these licenses. 9 * 10 * This file is distributed in the hope that it will be 11 * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the 12 * implied warranty of MERCHANTABILITY or FITNESS FOR A 13 * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. 14 * Redistribution, except as permitted by whichever of the GPL 15 * or MNA you select, is prohibited. 16 * 17 * 1. For the GPL license (GPL), you can redistribute and/or 18 * modify this file under the terms of the GNU General 19 * Public License, Version 3, as published by the Free Software 20 * Foundation. You should have received a copy of the GNU 21 * General Public License, Version 3 along with this program; 22 * if not, write to the Free Software Foundation, Inc., 51 23 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 24 * 25 * 2. For the Magnolia Network Agreement (MNA), this file 26 * and the accompanying materials are made available under the 27 * terms of the MNA which accompanies this distribution, and 28 * is available at http://www.magnolia-cms.com/mna.html 29 * 30 * Any modifications to this file must keep this entire header 31 * intact. 32 * 33 */ 34 package info.magnolia.modules.resources.templating; 35 36 import info.magnolia.module.resources.ResourceLinker; 37 import info.magnolia.resourceloader.Resource; 38 import info.magnolia.resourceloader.ResourceOrigin; 39 import info.magnolia.resourceloader.ResourceVisitor; 40 41 import java.util.Arrays; 42 import java.util.LinkedHashMap; 43 import java.util.LinkedList; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.regex.PatternSyntaxException; 47 48 import javax.inject.Inject; 49 50 /** 51 * Templating functions for creating links to web resources such as css and js files by given patterns. 52 * 53 * <p>Provide a single pattern or a list of patterns to match files from all resources. Use methods {@link #css(String)}, 54 * {@link #css(List)}, {@link #cachedCss(String)}, {@link #cachedCss(List)}, {@link #js(String)}, {@link #js(List)}, 55 * {@link #cachedJs(String)} or {@link #cachedJs(List)} demanding on your needs.</p> 56 * 57 * <p>The methods create printable html tags such as the followings:<br/> 58 * <code> 59 * <script src="/.resources/travel-demo-theme/js/html5shiv.js"></script><br/> 60 * <link rel="stylesheet" type="text/css" href="/.resources/foobar/webresources/css/a.css"/><br/> 61 * <link rel="stylesheet" type="text/css" media="all" href="/.resources/foobar/webresources/css/a~2016-12-08-02-56-06-000~cache.css"/> 62 * </code></p> 63 * 64 * <p>Example calls: 65 * <pre> 66 * ${resfn.css("/travel-demo-theme.*css")} (single pattern) 67 * ${resfn.js("/travel-demo-theme.*js")} (single pattern) 68 * ${resfn.css(["/foobar.*css", ".*magnolia.*css"])} (pattern list) 69 * ${resfn.css(["/foobar.*css", ".*magnolia.*css"], "media='all'")} (pattern list and other properties argument with single quotes) 70 * ${resfn.css(["/foobar.*css", ".*magnolia.*css"], "media=\"all\"")} (pattern list and other properties argument with escaped double quotes) 71 * ${resfn.cachedJs(["/foobar/.*.js", "/travel-demo/.*.js"])} (call to cached files) 72 * </pre></p> 73 * 74 * <p>This templating functions class originates from the project <i>neat-resources</i> by rah003@github.com.</p> 75 * 76 * @see <a href="https://documentation.magnolia-cms.com/display/DOCS/resfn">https://documentation.magnolia-cms.com/display/DOCS/resfn</a> 77 */ 78 public class ResourcesTemplatingFunctions { 79 80 public static final String NAME = "resfn"; 81 82 protected static final String CSS_SUFFIX = "\"/>"; 83 protected static final String JS_PREFIX = "<script src=\""; 84 protected static final String JS_SUFFIX = "\"></script>"; 85 86 private final ResourceOrigin origin; 87 private final ResourceLinker linker; 88 89 @Inject 90 public ResourcesTemplatingFunctions(ResourceOrigin resourceOrigin, ResourceLinker linker) { 91 this.origin = resourceOrigin; 92 this.linker = linker; 93 } 94 95 /** 96 * Generates css links for all css files matching the provided pattern. 97 */ 98 public String css(String pattern) { 99 return css(Arrays.asList(pattern)); 100 } 101 102 /** 103 * Generates css links for all css files matching the provided pattern; css links will contain the provided other attributes. 104 */ 105 public String css(String pattern, String otherAttributes) { 106 return css(Arrays.asList(pattern), otherAttributes); 107 } 108 109 /** 110 * Generates css links for all css files matching the provided pattern(s). 111 */ 112 public String css(final List<String> patterns) { 113 return css(patterns, null); 114 } 115 116 /** 117 * Generates css links for all css files matching the provided pattern(s); css links will contain the provided other attributes. 118 */ 119 public String css(List<String> patterns, String otherAttributes) { 120 return generate(patterns, getCssPrefix(otherAttributes), CSS_SUFFIX, false); 121 } 122 123 /** 124 * Generates cached css links for all css files matching the provided pattern. 125 */ 126 public String cachedCss(String pattern) { 127 return cachedCss(Arrays.asList(pattern)); 128 } 129 130 /** 131 * Generates cached css links for all css files matching the provided pattern; css links will contain the provided other attributes. 132 */ 133 public String cachedCss(String pattern, String otherAttributes) { 134 return cachedCss(Arrays.asList(pattern), otherAttributes); 135 } 136 137 /** 138 * Generates cached css links for all css files matching the provided pattern(s). 139 */ 140 public String cachedCss(final List<String> patterns) { 141 return cachedCss(patterns, null); 142 } 143 144 /** 145 * Generates cached css links for all css files matching the provided pattern(s); css links will contain the provided other attributes. 146 */ 147 public String cachedCss(List<String> patterns, String otherAttributes) { 148 return generate(patterns, getCssPrefix(otherAttributes), CSS_SUFFIX, true); 149 } 150 151 /** 152 * Generates js links for all js files matching the provided pattern. 153 */ 154 public String js(String pattern) { 155 return js(Arrays.asList(pattern)); 156 } 157 158 /** 159 * Generates js links for all js files matching provided pattern(s). 160 */ 161 public String js(final List<String> patterns) { 162 return generate(patterns, JS_PREFIX, JS_SUFFIX, false); 163 } 164 165 /** 166 * Generates cached js links for all js files matching the provided pattern. 167 */ 168 public String cachedJs(String pattern) { 169 return cachedJs(Arrays.asList(pattern)); 170 } 171 172 /** 173 * Generates cached js links for all js files matching the provided pattern(s). 174 */ 175 public String cachedJs(final List<String> patterns) { 176 return generate(patterns, JS_PREFIX, JS_SUFFIX, true); 177 } 178 179 /** 180 * Generates the links to .css or .js files. Returns all "links" as printable html code. 181 */ 182 private String generate(List<String> patterns, String prefix, String suffix, final boolean isCached) { 183 // yes, order matters! 184 Map<String, List<String>> intermediates = new LinkedHashMap<>(); 185 patterns.forEach(pattern -> intermediates.put(pattern, new LinkedList<>())); 186 origin.traverseWith(new ResourceVisitor() { 187 188 @Override 189 public void visitFile(final Resource resource) { 190 patterns.stream() 191 .filter(pattern -> matchSafely(resource.getPath(), pattern)) 192 .map(intermediates::get) 193 .forEach(results -> results.add(prefix + linker.linkTo(resource.getPath(), isCached) + suffix)); 194 } 195 196 private boolean matchSafely(String resource, String pattern) { 197 try { 198 return resource.matches(pattern); 199 } catch (PatternSyntaxException e) { 200 // ignore 201 } 202 return false; 203 } 204 205 @Override 206 public boolean visitDirectory(Resource resource) { 207 return true; 208 } 209 }); 210 211 StringBuilder results = new StringBuilder(); 212 List<String> hits = new LinkedList<>(); 213 // de dup (first match wins) 214 intermediates.values() 215 .forEach(patternResults -> patternResults.stream() 216 .filter(potentialHit -> !hits.contains(potentialHit)) 217 .forEach(hit -> { 218 results.append(hit).append("\n"); 219 hits.add(hit); 220 })); 221 222 return results.toString(); 223 } 224 225 private static String getCssPrefix(String otherAttributes) { 226 return "<link rel=\"stylesheet\" type=\"text/css\" " + (otherAttributes == null ? "" : otherAttributes + " ") + "href=\""; 227 } 228 229 }